From fcba102e0e4a3d39316ca9286b5619f161da4baa Mon Sep 17 00:00:00 2001 From: Nora Schiffer Date: Sun, 28 Jun 2026 17:07:28 +0200 Subject: [PATCH 0001/1433] batman-adv: create hardif only for netdevs that are part of a mesh batman-adv is using netdev notifiers to create a hard_iface struct for every Ethernet-like netdev in the system. These hardifs are tracked in a global linked list, which results in a few performance issues: Lookups in this list are O(n) in the total number of netdevs. As a hardif is looked up when a netdev is removed, this also takes O(n) in the number of netdevs, and removing n netdevs may take O(n^2). This slowdown will always happen when the batman-adv module is loaded, no mesh needs to be active. With the hardif being referenced as iflink private data, the global list is only needed for hardifs that are *not* part of a mesh (that is, the hardif is unused). To prepare for removing the global list, only create a hardif struct when an interface is added to a mesh and destroy it on removal. As adding/removing and enabling/disabling a hardif become one and the same, batadv_hardif_add_interface() is merged into batadv_hardif_enable_interface(), and batadv_hardif_remove_interface() can be dropped altogether. Signed-off-by: Nora Schiffer Signed-off-by: Sven Eckelmann --- net/batman-adv/hard-interface.c | 120 +++++++++++--------------------- net/batman-adv/hard-interface.h | 2 +- net/batman-adv/mesh-interface.c | 13 +--- 3 files changed, 44 insertions(+), 91 deletions(-) diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c index 03d01c20a954..9c9a892d22c6 100644 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@ -723,33 +723,58 @@ batadv_hardif_deactivate_interface(struct batadv_hard_iface *hard_iface) } /** - * batadv_hardif_enable_interface() - Enslave hard interface to mesh interface - * @hard_iface: hard interface to add to mesh interface + * batadv_hardif_enable_interface() - Enslave interface to mesh interface + * @net_dev: netdev struct of the interface to add to mesh interface * @mesh_iface: netdev struct of the mesh interface * * Return: 0 on success or negative error number in case of failure */ -int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface, +int batadv_hardif_enable_interface(struct net_device *net_dev, struct net_device *mesh_iface) { struct batadv_priv *bat_priv; __be16 ethertype = htons(ETH_P_BATMAN); int max_header_len = batadv_max_header_len(); + struct batadv_hard_iface *hard_iface; unsigned int required_mtu; unsigned int hardif_mtu; bool fragmentation; int ret; - hardif_mtu = READ_ONCE(hard_iface->net_dev->mtu); + ASSERT_RTNL(); + + if (!batadv_is_valid_iface(net_dev)) + return -EINVAL; + + hardif_mtu = READ_ONCE(net_dev->mtu); required_mtu = READ_ONCE(mesh_iface->mtu) + max_header_len; if (hardif_mtu < ETH_MIN_MTU + max_header_len) return -EINVAL; - if (hard_iface->if_status != BATADV_IF_NOT_IN_USE) - goto out; + hard_iface = kzalloc_obj(*hard_iface, GFP_ATOMIC); + if (!hard_iface) + return -ENOMEM; - kref_get(&hard_iface->refcount); + netdev_hold(net_dev, &hard_iface->dev_tracker, GFP_ATOMIC); + hard_iface->net_dev = net_dev; + + hard_iface->if_status = BATADV_IF_INACTIVE; + + INIT_LIST_HEAD(&hard_iface->list); + INIT_HLIST_HEAD(&hard_iface->neigh_list); + + mutex_init(&hard_iface->bat_iv.ogm_buff_mutex); + spin_lock_init(&hard_iface->neigh_list_lock); + kref_init(&hard_iface->refcount); + + hard_iface->num_bcasts = BATADV_NUM_BCASTS_DEFAULT; + if (batadv_is_wifi_hardif(hard_iface)) + hard_iface->num_bcasts = BATADV_NUM_BCASTS_WIRELESS; + + WRITE_ONCE(hard_iface->hop_penalty, 0); + + batadv_v_hardif_init(hard_iface); netdev_hold(mesh_iface, &hard_iface->meshif_dev_tracker, GFP_ATOMIC); hard_iface->mesh_iface = mesh_iface; @@ -764,9 +789,6 @@ int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface, if (ret < 0) goto err_upper; - hard_iface->if_status = BATADV_IF_INACTIVE; - - kref_get(&hard_iface->refcount); hard_iface->batman_adv_ptype.type = ethertype; hard_iface->batman_adv_ptype.func = batadv_batman_skb_recv; hard_iface->batman_adv_ptype.dev = hard_iface->net_dev; @@ -802,7 +824,9 @@ int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface, if (bat_priv->algo_ops->iface.enabled) bat_priv->algo_ops->iface.enabled(hard_iface); -out: + list_add_tail_rcu(&hard_iface->list, &batadv_hardif_list); + batadv_hardif_generation++; + return 0; err_upper: @@ -823,15 +847,19 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); struct batadv_hard_iface *primary_if = NULL; + ASSERT_RTNL(); + batadv_hardif_deactivate_interface(hard_iface); if (hard_iface->if_status != BATADV_IF_INACTIVE) goto out; + list_del_rcu(&hard_iface->list); + batadv_hardif_generation++; + batadv_info(hard_iface->mesh_iface, "Removing interface: %s\n", hard_iface->net_dev->name); dev_remove_pack(&hard_iface->batman_adv_ptype); - batadv_hardif_put(hard_iface); primary_if = batadv_primary_if_get_selected(bat_priv); if (hard_iface == primary_if) { @@ -844,7 +872,7 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) } bat_priv->algo_ops->iface.disable(hard_iface); - hard_iface->if_status = BATADV_IF_NOT_IN_USE; + hard_iface->if_status = BATADV_IF_TO_BE_REMOVED; /* delete all references to this hard_iface */ batadv_purge_orig_ref(bat_priv); @@ -865,63 +893,6 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) batadv_hardif_put(primary_if); } -static struct batadv_hard_iface * -batadv_hardif_add_interface(struct net_device *net_dev) -{ - struct batadv_hard_iface *hard_iface; - - ASSERT_RTNL(); - - if (!batadv_is_valid_iface(net_dev)) - return NULL; - - hard_iface = kzalloc_obj(*hard_iface, GFP_ATOMIC); - if (!hard_iface) - return NULL; - - netdev_hold(net_dev, &hard_iface->dev_tracker, GFP_ATOMIC); - hard_iface->net_dev = net_dev; - - hard_iface->mesh_iface = NULL; - hard_iface->if_status = BATADV_IF_NOT_IN_USE; - - INIT_LIST_HEAD(&hard_iface->list); - INIT_HLIST_HEAD(&hard_iface->neigh_list); - - mutex_init(&hard_iface->bat_iv.ogm_buff_mutex); - spin_lock_init(&hard_iface->neigh_list_lock); - kref_init(&hard_iface->refcount); - - hard_iface->num_bcasts = BATADV_NUM_BCASTS_DEFAULT; - if (batadv_is_wifi_hardif(hard_iface)) - hard_iface->num_bcasts = BATADV_NUM_BCASTS_WIRELESS; - - WRITE_ONCE(hard_iface->hop_penalty, 0); - - batadv_v_hardif_init(hard_iface); - - kref_get(&hard_iface->refcount); - list_add_tail_rcu(&hard_iface->list, &batadv_hardif_list); - batadv_hardif_generation++; - - return hard_iface; -} - -static void batadv_hardif_remove_interface(struct batadv_hard_iface *hard_iface) -{ - ASSERT_RTNL(); - - /* first deactivate interface */ - if (hard_iface->if_status != BATADV_IF_NOT_IN_USE) - batadv_hardif_disable_interface(hard_iface); - - if (hard_iface->if_status != BATADV_IF_NOT_IN_USE) - return; - - hard_iface->if_status = BATADV_IF_TO_BE_REMOVED; - batadv_hardif_put(hard_iface); -} - /** * batadv_hard_if_event_meshif() - Handle events for mesh interfaces * @event: NETDEV_* event to handle @@ -1082,10 +1053,6 @@ static int batadv_hard_if_event(struct notifier_block *this, batadv_wifi_net_device_event(event, net_dev); hard_iface = batadv_hardif_get_by_netdev(net_dev); - if (!hard_iface && (event == NETDEV_REGISTER || - event == NETDEV_POST_TYPE_CHANGE)) - hard_iface = batadv_hardif_add_interface(net_dev); - if (!hard_iface) goto out; @@ -1099,10 +1066,7 @@ static int batadv_hard_if_event(struct notifier_block *this, break; case NETDEV_UNREGISTER: case NETDEV_PRE_TYPE_CHANGE: - list_del_rcu(&hard_iface->list); - batadv_hardif_generation++; - - batadv_hardif_remove_interface(hard_iface); + batadv_hardif_disable_interface(hard_iface); break; case NETDEV_CHANGEMTU: if (hard_iface->mesh_iface) diff --git a/net/batman-adv/hard-interface.h b/net/batman-adv/hard-interface.h index af31696c3978..6d72dbdd5c20 100644 --- a/net/batman-adv/hard-interface.h +++ b/net/batman-adv/hard-interface.h @@ -75,7 +75,7 @@ u32 batadv_hardif_get_wifi_flags(struct batadv_hard_iface *hard_iface); bool batadv_is_wifi_hardif(struct batadv_hard_iface *hard_iface); struct batadv_hard_iface* batadv_hardif_get_by_netdev(const struct net_device *net_dev); -int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface, +int batadv_hardif_enable_interface(struct net_device *net_dev, struct net_device *mesh_iface); void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface); int batadv_hardif_min_mtu(struct net_device *mesh_iface); diff --git a/net/batman-adv/mesh-interface.c b/net/batman-adv/mesh-interface.c index 44026810b99c..a37368c1f5b5 100644 --- a/net/batman-adv/mesh-interface.c +++ b/net/batman-adv/mesh-interface.c @@ -836,18 +836,7 @@ static int batadv_meshif_slave_add(struct net_device *dev, struct net_device *slave_dev, struct netlink_ext_ack *extack) { - struct batadv_hard_iface *hard_iface; - int ret = -EINVAL; - - hard_iface = batadv_hardif_get_by_netdev(slave_dev); - if (!hard_iface || hard_iface->mesh_iface) - goto out; - - ret = batadv_hardif_enable_interface(hard_iface, dev); - -out: - batadv_hardif_put(hard_iface); - return ret; + return batadv_hardif_enable_interface(slave_dev, dev); } /** From 49e9de6e124fe80da68dfe9e7fe4e0b1ddfc4b35 Mon Sep 17 00:00:00 2001 From: Nora Schiffer Date: Sun, 28 Jun 2026 17:07:29 +0200 Subject: [PATCH 0002/1433] batman-adv: remove global hardif list With interfaces being kept track of as iflink private data, there is no need for the global list anymore. batadv_hardif_get_by_netdev() can now use netdev_master_upper_dev_get()+netdev_lower_dev_get_private() to find the hardif corresponding to a netdev. Signed-off-by: Nora Schiffer Signed-off-by: Sven Eckelmann --- net/batman-adv/hard-interface.c | 29 ++++++++++++----------------- net/batman-adv/hard-interface.h | 2 +- net/batman-adv/main.c | 5 ----- net/batman-adv/main.h | 1 - net/batman-adv/netlink.c | 2 ++ net/batman-adv/types.h | 3 --- 6 files changed, 15 insertions(+), 27 deletions(-) diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c index 9c9a892d22c6..ace81348ddef 100644 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@ -75,21 +75,21 @@ void batadv_hardif_release(struct kref *ref) * Return: batadv_hard_iface of net_dev (with increased refcnt), NULL on errors */ struct batadv_hard_iface * -batadv_hardif_get_by_netdev(const struct net_device *net_dev) +batadv_hardif_get_by_netdev(struct net_device *net_dev) { struct batadv_hard_iface *hard_iface; + struct net_device *mesh_iface; - rcu_read_lock(); - list_for_each_entry_rcu(hard_iface, &batadv_hardif_list, list) { - if (hard_iface->net_dev == net_dev && - kref_get_unless_zero(&hard_iface->refcount)) - goto out; - } + ASSERT_RTNL(); - hard_iface = NULL; + mesh_iface = netdev_master_upper_dev_get(net_dev); + if (!mesh_iface || !batadv_meshif_is_valid(mesh_iface)) + return NULL; + + hard_iface = netdev_lower_dev_get_private(mesh_iface, net_dev); + if (!kref_get_unless_zero(&hard_iface->refcount)) + return NULL; -out: - rcu_read_unlock(); return hard_iface; } @@ -761,7 +761,6 @@ int batadv_hardif_enable_interface(struct net_device *net_dev, hard_iface->if_status = BATADV_IF_INACTIVE; - INIT_LIST_HEAD(&hard_iface->list); INIT_HLIST_HEAD(&hard_iface->neigh_list); mutex_init(&hard_iface->bat_iv.ogm_buff_mutex); @@ -780,6 +779,7 @@ int batadv_hardif_enable_interface(struct net_device *net_dev, hard_iface->mesh_iface = mesh_iface; bat_priv = netdev_priv(hard_iface->mesh_iface); + batadv_hardif_generation++; ret = netdev_master_upper_dev_link(hard_iface->net_dev, mesh_iface, hard_iface, NULL, NULL); if (ret) @@ -824,9 +824,6 @@ int batadv_hardif_enable_interface(struct net_device *net_dev, if (bat_priv->algo_ops->iface.enabled) bat_priv->algo_ops->iface.enabled(hard_iface); - list_add_tail_rcu(&hard_iface->list, &batadv_hardif_list); - batadv_hardif_generation++; - return 0; err_upper: @@ -854,9 +851,6 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) if (hard_iface->if_status != BATADV_IF_INACTIVE) goto out; - list_del_rcu(&hard_iface->list); - batadv_hardif_generation++; - batadv_info(hard_iface->mesh_iface, "Removing interface: %s\n", hard_iface->net_dev->name); dev_remove_pack(&hard_iface->batman_adv_ptype); @@ -879,6 +873,7 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) batadv_purge_outstanding_packets(bat_priv, hard_iface); netdev_put(hard_iface->mesh_iface, &hard_iface->meshif_dev_tracker); + batadv_hardif_generation++; netdev_upper_dev_unlink(hard_iface->net_dev, hard_iface->mesh_iface); batadv_hardif_recalc_extra_skbroom(hard_iface->mesh_iface); diff --git a/net/batman-adv/hard-interface.h b/net/batman-adv/hard-interface.h index 6d72dbdd5c20..aa9275dec097 100644 --- a/net/batman-adv/hard-interface.h +++ b/net/batman-adv/hard-interface.h @@ -74,7 +74,7 @@ u32 batadv_netdev_get_wifi_flags(struct net_device *net_dev); u32 batadv_hardif_get_wifi_flags(struct batadv_hard_iface *hard_iface); bool batadv_is_wifi_hardif(struct batadv_hard_iface *hard_iface); struct batadv_hard_iface* -batadv_hardif_get_by_netdev(const struct net_device *net_dev); +batadv_hardif_get_by_netdev(struct net_device *net_dev); int batadv_hardif_enable_interface(struct net_device *net_dev, struct net_device *mesh_iface); void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface); diff --git a/net/batman-adv/main.c b/net/batman-adv/main.c index 3c4572284b53..1d82f3a841a1 100644 --- a/net/batman-adv/main.c +++ b/net/batman-adv/main.c @@ -59,10 +59,6 @@ #include "tp_meter.h" #include "translation-table.h" -/* List manipulations on hardif_list have to be rtnl_lock()'ed, - * list traversals just rcu-locked - */ -struct list_head batadv_hardif_list; unsigned int batadv_hardif_generation; static int (*batadv_rx_handler[256])(struct sk_buff *skb, struct batadv_hard_iface *recv_if); @@ -95,7 +91,6 @@ static int __init batadv_init(void) if (ret < 0) return ret; - INIT_LIST_HEAD(&batadv_hardif_list); batadv_algo_init(); batadv_recv_handler_init(); diff --git a/net/batman-adv/main.h b/net/batman-adv/main.h index f68fc8b7239c..e34145047a34 100644 --- a/net/batman-adv/main.h +++ b/net/batman-adv/main.h @@ -226,7 +226,6 @@ static inline int batadv_print_vid(unsigned short vid) return -1; } -extern struct list_head batadv_hardif_list; extern unsigned int batadv_hardif_generation; extern struct workqueue_struct *batadv_event_workqueue; diff --git a/net/batman-adv/netlink.c b/net/batman-adv/netlink.c index 4cf9e3c54ad3..62ea91aa3ead 100644 --- a/net/batman-adv/netlink.c +++ b/net/batman-adv/netlink.c @@ -1211,7 +1211,9 @@ batadv_netlink_get_hardif_from_ifindex(struct batadv_priv *bat_priv, if (!hard_dev) return ERR_PTR(-ENODEV); + rtnl_lock(); hard_iface = batadv_hardif_get_by_netdev(hard_dev); + rtnl_unlock(); if (!hard_iface) goto err_put_harddev; diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h index b1f9f8964c3f..1671380b3792 100644 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@ -214,9 +214,6 @@ struct batadv_wifi_net_device_state { * struct batadv_hard_iface - network device known to batman-adv */ struct batadv_hard_iface { - /** @list: list node for batadv_hardif_list */ - struct list_head list; - /** @if_status: status of the interface for batman-adv */ char if_status; From f846e779532ea99dea34005c1cfbb4820b582d1e Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Sun, 28 Jun 2026 17:07:30 +0200 Subject: [PATCH 0003/1433] batman-adv: make hard_iface->mesh_iface immutable With the hard_iface now being created for a specific mesh_iface, it is beneficial not to set mesh_iface to NULL when the interface is disabled, but instead keeping it immutable after the initial setup of the hard_iface. By also holding the reference to the mesh_iface until the hard_iface is released, hard_ifaces iterated over under RCU will always point to a valid mesh_iface. Co-developed-by: Nora Schiffer Signed-off-by: Nora Schiffer Signed-off-by: Sven Eckelmann --- net/batman-adv/hard-interface.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c index ace81348ddef..a0b8b06f9a64 100644 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@ -63,6 +63,7 @@ void batadv_hardif_release(struct kref *ref) struct batadv_hard_iface *hard_iface; hard_iface = container_of(ref, struct batadv_hard_iface, refcount); + netdev_put(hard_iface->mesh_iface, &hard_iface->meshif_dev_tracker); netdev_put(hard_iface->net_dev, &hard_iface->dev_tracker); kfree_rcu(hard_iface, rcu); @@ -829,8 +830,6 @@ int batadv_hardif_enable_interface(struct net_device *net_dev, err_upper: netdev_upper_dev_unlink(hard_iface->net_dev, mesh_iface); err_dev: - hard_iface->mesh_iface = NULL; - netdev_put(mesh_iface, &hard_iface->meshif_dev_tracker); batadv_hardif_put(hard_iface); return ret; } @@ -871,7 +870,6 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) /* delete all references to this hard_iface */ batadv_purge_orig_ref(bat_priv); batadv_purge_outstanding_packets(bat_priv, hard_iface); - netdev_put(hard_iface->mesh_iface, &hard_iface->meshif_dev_tracker); batadv_hardif_generation++; netdev_upper_dev_unlink(hard_iface->net_dev, hard_iface->mesh_iface); @@ -881,7 +879,6 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) if (list_empty(&hard_iface->mesh_iface->adj_list.lower)) batadv_gw_check_client_stop(bat_priv); - hard_iface->mesh_iface = NULL; batadv_hardif_put(hard_iface); out: From 48ea7b6b7b9474d8c350b1180f49a210d972f99c Mon Sep 17 00:00:00 2001 From: Nora Schiffer Date: Sun, 28 Jun 2026 17:07:31 +0200 Subject: [PATCH 0004/1433] batman-adv: remove BATADV_IF_NOT_IN_USE hardif state With hardifs only existing while an interface is part of a mesh, the BATADV_IF_NOT_IN_USE state has become redundant. Signed-off-by: Nora Schiffer Signed-off-by: Sven Eckelmann --- net/batman-adv/bat_iv_ogm.c | 3 +-- net/batman-adv/bat_v_elp.c | 3 +-- net/batman-adv/hard-interface.c | 9 --------- net/batman-adv/hard-interface.h | 6 ------ net/batman-adv/originator.c | 4 ---- 5 files changed, 2 insertions(+), 23 deletions(-) diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c index bb2f012b454e..4514c51bba77 100644 --- a/net/batman-adv/bat_iv_ogm.c +++ b/net/batman-adv/bat_iv_ogm.c @@ -910,8 +910,7 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface) static void batadv_iv_ogm_schedule(struct batadv_hard_iface *hard_iface) { - if (hard_iface->if_status == BATADV_IF_NOT_IN_USE || - hard_iface->if_status == BATADV_IF_TO_BE_REMOVED) + if (hard_iface->if_status == BATADV_IF_TO_BE_REMOVED) return; mutex_lock(&hard_iface->bat_iv.ogm_buff_mutex); diff --git a/net/batman-adv/bat_v_elp.c b/net/batman-adv/bat_v_elp.c index 4841f0f1a9b1..bc3e4f264afa 100644 --- a/net/batman-adv/bat_v_elp.c +++ b/net/batman-adv/bat_v_elp.c @@ -311,8 +311,7 @@ static void batadv_v_elp_periodic_work(struct work_struct *work) goto out; /* we are in the process of shutting this interface down */ - if (hard_iface->if_status == BATADV_IF_NOT_IN_USE || - hard_iface->if_status == BATADV_IF_TO_BE_REMOVED) + if (hard_iface->if_status == BATADV_IF_TO_BE_REMOVED) goto out; /* the interface was enabled but may not be ready yet */ diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c index a0b8b06f9a64..86010bc32818 100644 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@ -547,9 +547,6 @@ static void batadv_check_known_mac_addr(const struct batadv_hard_iface *hard_ifa if (tmp_hard_iface == hard_iface) continue; - if (tmp_hard_iface->if_status == BATADV_IF_NOT_IN_USE) - continue; - if (!batadv_compare_eth(tmp_hard_iface->net_dev->dev_addr, hard_iface->net_dev->dev_addr)) continue; @@ -575,9 +572,6 @@ static void batadv_hardif_recalc_extra_skbroom(struct net_device *mesh_iface) rcu_read_lock(); netdev_for_each_lower_private_rcu(mesh_iface, hard_iface, iter) { - if (hard_iface->if_status == BATADV_IF_NOT_IN_USE) - continue; - lower_header_len = max_t(unsigned short, lower_header_len, hard_iface->net_dev->hard_header_len); @@ -1065,9 +1059,6 @@ static int batadv_hard_if_event(struct notifier_block *this, batadv_update_min_mtu(hard_iface->mesh_iface); break; case NETDEV_CHANGEADDR: - if (hard_iface->if_status == BATADV_IF_NOT_IN_USE) - goto hardif_put; - batadv_check_known_mac_addr(hard_iface); bat_priv = netdev_priv(hard_iface->mesh_iface); diff --git a/net/batman-adv/hard-interface.h b/net/batman-adv/hard-interface.h index aa9275dec097..935f47ca9a48 100644 --- a/net/batman-adv/hard-interface.h +++ b/net/batman-adv/hard-interface.h @@ -21,12 +21,6 @@ * enum batadv_hard_if_state - State of a hard interface */ enum batadv_hard_if_state { - /** - * @BATADV_IF_NOT_IN_USE: interface is not used as slave interface of a - * batman-adv mesh interface - */ - BATADV_IF_NOT_IN_USE, - /** * @BATADV_IF_TO_BE_REMOVED: interface will be removed from mesh * interface diff --git a/net/batman-adv/originator.c b/net/batman-adv/originator.c index 9b38bd9e8da7..48f837cf665a 100644 --- a/net/batman-adv/originator.c +++ b/net/batman-adv/originator.c @@ -1033,7 +1033,6 @@ batadv_purge_neigh_ifinfo(struct batadv_priv *bat_priv, /* don't purge if the interface is not (going) down */ if (if_outgoing->if_status != BATADV_IF_INACTIVE && - if_outgoing->if_status != BATADV_IF_NOT_IN_USE && if_outgoing->if_status != BATADV_IF_TO_BE_REMOVED) continue; @@ -1077,7 +1076,6 @@ batadv_purge_orig_ifinfo(struct batadv_priv *bat_priv, /* don't purge if the interface is not (going) down */ if (if_outgoing->if_status != BATADV_IF_INACTIVE && - if_outgoing->if_status != BATADV_IF_NOT_IN_USE && if_outgoing->if_status != BATADV_IF_TO_BE_REMOVED) continue; @@ -1127,10 +1125,8 @@ batadv_purge_orig_neighbors(struct batadv_priv *bat_priv, if (batadv_has_timed_out(last_seen, BATADV_PURGE_TIMEOUT) || if_incoming->if_status == BATADV_IF_INACTIVE || - if_incoming->if_status == BATADV_IF_NOT_IN_USE || if_incoming->if_status == BATADV_IF_TO_BE_REMOVED) { if (if_incoming->if_status == BATADV_IF_INACTIVE || - if_incoming->if_status == BATADV_IF_NOT_IN_USE || if_incoming->if_status == BATADV_IF_TO_BE_REMOVED) batadv_dbg(BATADV_DBG_BATMAN, bat_priv, "neighbor purge: originator %pM, neighbor: %pM, iface: %s\n", From cbda6a6cf2a5abe0dda94819f4e1aaba40678913 Mon Sep 17 00:00:00 2001 From: Nora Schiffer Date: Sun, 28 Jun 2026 17:07:32 +0200 Subject: [PATCH 0005/1433] batman-adv: move hardif generation counter into batadv_priv The counter doesn't need to be global. Signed-off-by: Nora Schiffer Signed-off-by: Sven Eckelmann --- net/batman-adv/hard-interface.c | 4 ++-- net/batman-adv/main.c | 1 - net/batman-adv/main.h | 2 -- net/batman-adv/netlink.c | 2 +- net/batman-adv/types.h | 3 +++ 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c index 86010bc32818..9b8108d464db 100644 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@ -774,7 +774,7 @@ int batadv_hardif_enable_interface(struct net_device *net_dev, hard_iface->mesh_iface = mesh_iface; bat_priv = netdev_priv(hard_iface->mesh_iface); - batadv_hardif_generation++; + bat_priv->hardif_generation++; ret = netdev_master_upper_dev_link(hard_iface->net_dev, mesh_iface, hard_iface, NULL, NULL); if (ret) @@ -865,7 +865,7 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) batadv_purge_orig_ref(bat_priv); batadv_purge_outstanding_packets(bat_priv, hard_iface); - batadv_hardif_generation++; + bat_priv->hardif_generation++; netdev_upper_dev_unlink(hard_iface->net_dev, hard_iface->mesh_iface); batadv_hardif_recalc_extra_skbroom(hard_iface->mesh_iface); diff --git a/net/batman-adv/main.c b/net/batman-adv/main.c index 1d82f3a841a1..badc1df0af1d 100644 --- a/net/batman-adv/main.c +++ b/net/batman-adv/main.c @@ -59,7 +59,6 @@ #include "tp_meter.h" #include "translation-table.h" -unsigned int batadv_hardif_generation; static int (*batadv_rx_handler[256])(struct sk_buff *skb, struct batadv_hard_iface *recv_if); diff --git a/net/batman-adv/main.h b/net/batman-adv/main.h index e34145047a34..e738758ee4a7 100644 --- a/net/batman-adv/main.h +++ b/net/batman-adv/main.h @@ -226,8 +226,6 @@ static inline int batadv_print_vid(unsigned short vid) return -1; } -extern unsigned int batadv_hardif_generation; - extern struct workqueue_struct *batadv_event_workqueue; int batadv_mesh_init(struct net_device *mesh_iface); diff --git a/net/batman-adv/netlink.c b/net/batman-adv/netlink.c index 62ea91aa3ead..d2bc48c70714 100644 --- a/net/batman-adv/netlink.c +++ b/net/batman-adv/netlink.c @@ -968,7 +968,7 @@ batadv_netlink_dump_hardif(struct sk_buff *msg, struct netlink_callback *cb) bat_priv = netdev_priv(mesh_iface); rtnl_lock(); - cb->seq = batadv_hardif_generation << 1 | 1; + cb->seq = bat_priv->hardif_generation << 1 | 1; netdev_for_each_lower_private(mesh_iface, hard_iface, iter) { if (i++ < skip) diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h index 1671380b3792..e1463a029e83 100644 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@ -1676,6 +1676,9 @@ struct batadv_priv { /** @tp_num: number of currently active tp sessions */ atomic_t tp_num; + /** @hardif_generation: generation counter added to netlink hardif dumps */ + unsigned int hardif_generation; + /** @orig_work: work queue callback item for orig node purging */ struct delayed_work orig_work; From 2b429dbc50b43db1d85429d5bab0498bc4906736 Mon Sep 17 00:00:00 2001 From: Nora Schiffer Date: Sun, 28 Jun 2026 17:07:33 +0200 Subject: [PATCH 0006/1433] batman-adv: drop unneeded goto and initialization from batadv_hardif_disable_interface() The only use of the label was too early for primary_if to be set anyways. Also move the put of primary_if further up to hold the reference only as long as necessary, hopefully avoiding the need to re-introduce the goto label with future code changes. Signed-off-by: Nora Schiffer Signed-off-by: Sven Eckelmann --- net/batman-adv/hard-interface.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c index 9b8108d464db..6fc49ad47fd8 100644 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@ -835,14 +835,14 @@ int batadv_hardif_enable_interface(struct net_device *net_dev, void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) { struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); - struct batadv_hard_iface *primary_if = NULL; + struct batadv_hard_iface *primary_if; ASSERT_RTNL(); batadv_hardif_deactivate_interface(hard_iface); if (hard_iface->if_status != BATADV_IF_INACTIVE) - goto out; + return; batadv_info(hard_iface->mesh_iface, "Removing interface: %s\n", hard_iface->net_dev->name); @@ -857,6 +857,7 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) batadv_hardif_put(new_if); } + batadv_hardif_put(primary_if); bat_priv->algo_ops->iface.disable(hard_iface); hard_iface->if_status = BATADV_IF_TO_BE_REMOVED; @@ -874,9 +875,6 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) batadv_gw_check_client_stop(bat_priv); batadv_hardif_put(hard_iface); - -out: - batadv_hardif_put(primary_if); } /** From b58c36b804f4447655a9eb353bf3a9d310f50e06 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 4 Jun 2026 08:30:07 +0200 Subject: [PATCH 0007/1433] batman-adv: drop NULL check for immutable hardif->mesh_iface The batadv_hard_iface->mesh_iface became immutable after the global batadv_hardif_list was removed and batadv_hard_iface only exists when it is assigned to an mesh_iface. This member can never become NULL and thus a check is now unnecessary. Signed-off-by: Sven Eckelmann --- net/batman-adv/bat_v_elp.c | 6 ------ net/batman-adv/bridge_loop_avoidance.c | 9 ++------- net/batman-adv/hard-interface.c | 8 ++------ net/batman-adv/main.c | 3 --- 4 files changed, 4 insertions(+), 22 deletions(-) diff --git a/net/batman-adv/bat_v_elp.c b/net/batman-adv/bat_v_elp.c index bc3e4f264afa..262e40040007 100644 --- a/net/batman-adv/bat_v_elp.c +++ b/net/batman-adv/bat_v_elp.c @@ -90,12 +90,6 @@ static bool batadv_v_elp_get_throughput(struct batadv_hardif_neigh_node *neigh, u32 throughput; int ret; - /* don't query throughput when no longer associated with any - * batman-adv interface - */ - if (!mesh_iface) - return false; - /* if the user specified a customised value for this interface, then * return it directly */ diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c index 5c73f6ba16cf..f9a1fadf8de9 100644 --- a/net/batman-adv/bridge_loop_avoidance.c +++ b/net/batman-adv/bridge_loop_avoidance.c @@ -344,7 +344,6 @@ static void batadv_bla_send_claim(struct batadv_priv *bat_priv, const u8 *mac, struct sk_buff *skb; struct ethhdr *ethhdr; struct batadv_hard_iface *primary_if; - struct net_device *mesh_iface; u8 *hw_src; struct batadv_bla_claim_dst local_claim_dest; __be32 zeroip = 0; @@ -357,14 +356,10 @@ static void batadv_bla_send_claim(struct batadv_priv *bat_priv, const u8 *mac, sizeof(local_claim_dest)); local_claim_dest.type = claimtype; - mesh_iface = READ_ONCE(primary_if->mesh_iface); - if (!mesh_iface) - goto out; - skb = arp_create(ARPOP_REPLY, ETH_P_ARP, /* IP DST: 0.0.0.0 */ zeroip, - mesh_iface, + primary_if->mesh_iface, /* IP SRC: 0.0.0.0 */ zeroip, /* Ethernet DST: Broadcast */ @@ -442,7 +437,7 @@ static void batadv_bla_send_claim(struct batadv_priv *bat_priv, const u8 *mac, } skb_reset_mac_header(skb); - skb->protocol = eth_type_trans(skb, mesh_iface); + skb->protocol = eth_type_trans(skb, primary_if->mesh_iface); batadv_inc_counter(bat_priv, BATADV_CNT_RX); batadv_add_counter(bat_priv, BATADV_CNT_RX_BYTES, skb->len + ETH_HLEN); diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c index 6fc49ad47fd8..b6867576bbaf 100644 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@ -246,7 +246,7 @@ struct net_device *__batadv_get_real_netdev(struct net_device *netdev) } hard_iface = batadv_hardif_get_by_netdev(netdev); - if (!hard_iface || !hard_iface->mesh_iface) + if (!hard_iface) goto out; net = dev_net(hard_iface->mesh_iface); @@ -540,9 +540,6 @@ static void batadv_check_known_mac_addr(const struct batadv_hard_iface *hard_ifa const struct batadv_hard_iface *tmp_hard_iface; struct list_head *iter; - if (!mesh_iface) - return; - netdev_for_each_lower_private(mesh_iface, tmp_hard_iface, iter) { if (tmp_hard_iface == hard_iface) continue; @@ -1053,8 +1050,7 @@ static int batadv_hard_if_event(struct notifier_block *this, batadv_hardif_disable_interface(hard_iface); break; case NETDEV_CHANGEMTU: - if (hard_iface->mesh_iface) - batadv_update_min_mtu(hard_iface->mesh_iface); + batadv_update_min_mtu(hard_iface->mesh_iface); break; case NETDEV_CHANGEADDR: batadv_check_known_mac_addr(hard_iface); diff --git a/net/batman-adv/main.c b/net/batman-adv/main.c index badc1df0af1d..04bb030ef299 100644 --- a/net/batman-adv/main.c +++ b/net/batman-adv/main.c @@ -444,9 +444,6 @@ int batadv_batman_skb_recv(struct sk_buff *skb, struct net_device *dev, if (unlikely(skb->mac_len != ETH_HLEN || !skb_mac_header(skb))) goto err_free; - if (!hard_iface->mesh_iface) - goto err_free; - bat_priv = netdev_priv(hard_iface->mesh_iface); if (READ_ONCE(bat_priv->mesh_state) != BATADV_MESH_ACTIVE) From 5fa5f64fc0189bb37eeb4fe11687648c557c6f99 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 4 Jun 2026 08:45:30 +0200 Subject: [PATCH 0008/1433] Revert "batman-adv: v: stop OGMv2 on disabled interface" With the immutability guarantee of batadv_hard_iface->mesh_iface, the check for "changed" (or NULL) mesh_iface doesn't work anymore and is also no longer necessary. The extra (complicated) code for the sending of OGMv2s can therefore be removed and the original code can be used again. This reverts commit f8ce8b8331a1bc44ad4905886a482214d428b253. Signed-off-by: Sven Eckelmann --- net/batman-adv/bat_v_ogm.c | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c index 037921aad35d..e921d49f7ece 100644 --- a/net/batman-adv/bat_v_ogm.c +++ b/net/batman-adv/bat_v_ogm.c @@ -115,14 +115,14 @@ static void batadv_v_ogm_start_timer(struct batadv_priv *bat_priv) /** * batadv_v_ogm_send_to_if() - send a batman ogm using a given interface - * @bat_priv: the bat priv with all the mesh interface information * @skb: the OGM to send * @hard_iface: the interface to use to send the OGM */ -static void batadv_v_ogm_send_to_if(struct batadv_priv *bat_priv, - struct sk_buff *skb, +static void batadv_v_ogm_send_to_if(struct sk_buff *skb, struct batadv_hard_iface *hard_iface) { + struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); + if (hard_iface->if_status != BATADV_IF_ACTIVE) { kfree_skb(skb); return; @@ -189,7 +189,6 @@ static void batadv_v_ogm_aggr_list_free(struct batadv_hard_iface *hard_iface) /** * batadv_v_ogm_aggr_send() - flush & send aggregation queue - * @bat_priv: the bat priv with all the mesh interface information * @hard_iface: the interface with the aggregation queue to flush * * Aggregates all OGMv2 packets currently in the aggregation queue into a @@ -199,8 +198,7 @@ static void batadv_v_ogm_aggr_list_free(struct batadv_hard_iface *hard_iface) * * Caller needs to hold the hard_iface->bat_v.aggr_list.lock. */ -static void batadv_v_ogm_aggr_send(struct batadv_priv *bat_priv, - struct batadv_hard_iface *hard_iface) +static void batadv_v_ogm_aggr_send(struct batadv_hard_iface *hard_iface) { unsigned int aggr_len = hard_iface->bat_v.aggr_len; struct sk_buff *skb_aggr; @@ -230,26 +228,21 @@ static void batadv_v_ogm_aggr_send(struct batadv_priv *bat_priv, consume_skb(skb); } - batadv_v_ogm_send_to_if(bat_priv, skb_aggr, hard_iface); + batadv_v_ogm_send_to_if(skb_aggr, hard_iface); } /** * batadv_v_ogm_queue_on_if() - queue a batman ogm on a given interface - * @bat_priv: the bat priv with all the mesh interface information * @skb: the OGM to queue * @hard_iface: the interface to queue the OGM on */ -static void batadv_v_ogm_queue_on_if(struct batadv_priv *bat_priv, - struct sk_buff *skb, +static void batadv_v_ogm_queue_on_if(struct sk_buff *skb, struct batadv_hard_iface *hard_iface) { - if (hard_iface->mesh_iface != bat_priv->mesh_iface) { - kfree_skb(skb); - return; - } + struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); if (!READ_ONCE(bat_priv->aggregated_ogms)) { - batadv_v_ogm_send_to_if(bat_priv, skb, hard_iface); + batadv_v_ogm_send_to_if(skb, hard_iface); return; } @@ -260,7 +253,7 @@ static void batadv_v_ogm_queue_on_if(struct batadv_priv *bat_priv, } if (!batadv_v_ogm_queue_left(skb, hard_iface)) - batadv_v_ogm_aggr_send(bat_priv, hard_iface); + batadv_v_ogm_aggr_send(hard_iface); hard_iface->bat_v.aggr_len += batadv_v_ogm_len(skb); __skb_queue_tail(&hard_iface->bat_v.aggr_list, skb); @@ -357,7 +350,7 @@ static void batadv_v_ogm_send_meshif(struct batadv_priv *bat_priv) break; } - batadv_v_ogm_queue_on_if(bat_priv, skb_tmp, hard_iface); + batadv_v_ogm_queue_on_if(skb_tmp, hard_iface); batadv_hardif_put(hard_iface); } rcu_read_unlock(); @@ -397,14 +390,12 @@ void batadv_v_ogm_aggr_work(struct work_struct *work) { struct batadv_hard_iface_bat_v *batv; struct batadv_hard_iface *hard_iface; - struct batadv_priv *bat_priv; batv = container_of(work, struct batadv_hard_iface_bat_v, aggr_wq.work); hard_iface = container_of(batv, struct batadv_hard_iface, bat_v); - bat_priv = netdev_priv(hard_iface->mesh_iface); spin_lock_bh(&hard_iface->bat_v.aggr_list.lock); - batadv_v_ogm_aggr_send(bat_priv, hard_iface); + batadv_v_ogm_aggr_send(hard_iface); spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock); batadv_v_ogm_start_queue_timer(hard_iface); @@ -601,7 +592,7 @@ static void batadv_v_ogm_forward(struct batadv_priv *bat_priv, if_outgoing->net_dev->name, ntohl(ogm_forward->throughput), ogm_forward->ttl, if_incoming->net_dev->name); - batadv_v_ogm_queue_on_if(bat_priv, skb, if_outgoing); + batadv_v_ogm_queue_on_if(skb, if_outgoing); out: batadv_orig_ifinfo_put(orig_ifinfo); From ba2d97e8736ef2752ac717bc21a111b63b7e6496 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 4 Jun 2026 08:51:30 +0200 Subject: [PATCH 0009/1433] batman-adv: iv: drop migration check for batadv_hard_iface With the immutability guarantee of batadv_hard_iface->mesh_iface, the check for "changed" (or NULL) mesh_iface is no longer necessary because a batadv_hard_iface can no longer migrate from one batadv_mesh_iface to another one. Signed-off-by: Sven Eckelmann --- net/batman-adv/bat_iv_ogm.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c index 4514c51bba77..22622283f59b 100644 --- a/net/batman-adv/bat_iv_ogm.c +++ b/net/batman-adv/bat_iv_ogm.c @@ -404,23 +404,14 @@ static void batadv_iv_ogm_send_to_if(struct batadv_forw_packet *forw_packet, /* send a batman ogm packet */ static void batadv_iv_ogm_emit(struct batadv_forw_packet *forw_packet) { - struct net_device *mesh_iface; - if (!forw_packet->if_incoming) { pr_err("Error - can't forward packet: incoming iface not specified\n"); return; } - mesh_iface = forw_packet->if_incoming->mesh_iface; - if (WARN_ON(!forw_packet->if_outgoing)) return; - if (forw_packet->if_outgoing->mesh_iface != mesh_iface) { - pr_warn("%s: mesh interface switch for queued OGM\n", __func__); - return; - } - if (forw_packet->if_incoming->if_status != BATADV_IF_ACTIVE) return; From 278e5890704ceb5b93f4d13c44a640c44ed35a92 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Sun, 14 Jun 2026 11:55:27 +0200 Subject: [PATCH 0010/1433] batman-adv: tvlv: extract tvlv header iterator batadv_tvlv_containers_contain() and batadv_tvlv_containers_process() are using the same code to iterate through the TVLV containers. To simplify the code, extract the shared portions of both functions. Signed-off-by: Sven Eckelmann --- net/batman-adv/tvlv.c | 86 +++++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 35 deletions(-) diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c index 1c9fb21985f6..49bf2ed9ecdc 100644 --- a/net/batman-adv/tvlv.c +++ b/net/batman-adv/tvlv.c @@ -442,6 +442,54 @@ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv, return NET_RX_SUCCESS; } +/** + * batadv_tvlv_hdr_next() - move a tvlv buffer cursor to the next container + * @tvlv_value: cursor into the tvlv buffer, advanced past the returned + * container's content on success + * @tvlv_value_len: remaining length of the tvlv buffer, reduced by the returned + * container's size on success + * + * Parses a single container header at the current cursor position and, if a + * complete container is available, advances the cursor and remaining length + * past it. The returned header stays valid; its content is located at + * (returned header + 1) and is ntohs(hdr->len) bytes long. + * + * Return: pointer to the next tvlv container header, or NULL if no further + * complete container is present in the buffer. + */ +static struct batadv_tvlv_hdr *batadv_tvlv_hdr_next(void **tvlv_value, u16 *tvlv_value_len) +{ + struct batadv_tvlv_hdr *tvlv_hdr; + u16 tvlv_value_cont_len; + void *tvlv_value_cont; + u16 tvlv_len; + + tvlv_value_cont = *tvlv_value; + tvlv_len = *tvlv_value_len; + + if (tvlv_len < sizeof(*tvlv_hdr)) + return NULL; + + tvlv_hdr = tvlv_value_cont; + tvlv_value_cont_len = ntohs(tvlv_hdr->len); + tvlv_value_cont = tvlv_hdr + 1; + tvlv_len -= sizeof(*tvlv_hdr); + + if (tvlv_value_cont_len > tvlv_len) + return NULL; + + /* the next tvlv header is accessed assuming (at least) 2-byte + * alignment, so it must start at an even offset. + */ + if (tvlv_value_cont_len & 1) + return NULL; + + *tvlv_value = (u8 *)tvlv_value_cont + tvlv_value_cont_len; + *tvlv_value_len = tvlv_len - tvlv_value_cont_len; + + return tvlv_hdr; +} + /** * batadv_tvlv_containers_contain() - check if a tvlv buffer holds a container * @tvlv_value: tvlv content @@ -457,28 +505,10 @@ static bool batadv_tvlv_containers_contain(void *tvlv_value, u8 version) { struct batadv_tvlv_hdr *tvlv_hdr; - u16 tvlv_value_cont_len; - - while (tvlv_value_len >= sizeof(*tvlv_hdr)) { - tvlv_hdr = tvlv_value; - tvlv_value_cont_len = ntohs(tvlv_hdr->len); - tvlv_value = tvlv_hdr + 1; - tvlv_value_len -= sizeof(*tvlv_hdr); - - if (tvlv_value_cont_len > tvlv_value_len) - break; - - /* the next tvlv header is accessed assuming (at least) 2-byte - * alignment, so it must start at an even offset. - */ - if (tvlv_value_cont_len & 1) - break; + while ((tvlv_hdr = batadv_tvlv_hdr_next(&tvlv_value, &tvlv_value_len))) { if (tvlv_hdr->type == type && tvlv_hdr->version == version) return true; - - tvlv_value = (u8 *)tvlv_value + tvlv_value_cont_len; - tvlv_value_len -= tvlv_value_cont_len; } return false; @@ -511,20 +541,8 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv, u8 cifnotfound = BATADV_TVLV_HANDLER_OGM_CIFNOTFND; int ret = NET_RX_SUCCESS; - while (tvlv_value_len >= sizeof(*tvlv_hdr)) { - tvlv_hdr = tvlv_value; + while ((tvlv_hdr = batadv_tvlv_hdr_next(&tvlv_value, &tvlv_value_len))) { tvlv_value_cont_len = ntohs(tvlv_hdr->len); - tvlv_value = tvlv_hdr + 1; - tvlv_value_len -= sizeof(*tvlv_hdr); - - if (tvlv_value_cont_len > tvlv_value_len) - break; - - /* the next tvlv header is accessed assuming (at least) 2-byte - * alignment, so it must start at an even offset. - */ - if (tvlv_value_cont_len & 1) - break; tvlv_handler = batadv_tvlv_handler_get(bat_priv, tvlv_hdr->type, @@ -532,11 +550,9 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv, ret |= batadv_tvlv_call_handler(bat_priv, tvlv_handler, packet_type, orig_node, skb, - tvlv_value, + tvlv_hdr + 1, tvlv_value_cont_len); batadv_tvlv_handler_put(tvlv_handler); - tvlv_value = (u8 *)tvlv_value + tvlv_value_cont_len; - tvlv_value_len -= tvlv_value_cont_len; } if (packet_type != BATADV_IV_OGM && From 71b7987ec001a60dff689e56f4b041cfc4222c6b Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 10 Jun 2026 23:16:18 +0200 Subject: [PATCH 0011/1433] batman-adv: tp_meter: simplify unordered ack calculation When batadv_tp_ack_unordered() goes through the list of unacked sequence numbers and checks for now closed gaps, it is first calculating a delta of the sequence numbers which could be acked. Just to revert this calculation in the next steps to the sequence number which would be ackable. Skip the delta step and directly work with the sequence numbers. Signed-off-by: Sven Eckelmann --- net/batman-adv/tp_meter.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c index c2eea7dbc488..b7fee6e55f03 100644 --- a/net/batman-adv/tp_meter.c +++ b/net/batman-adv/tp_meter.c @@ -1493,10 +1493,10 @@ static void batadv_tp_ack_unordered(struct batadv_tp_receiver *tp_vars) if (batadv_seq_before(tp_vars->last_recv, un->seqno)) break; - to_ack = un->seqno + un->len - tp_vars->last_recv; + to_ack = un->seqno + un->len; - if (batadv_seq_before(tp_vars->last_recv, un->seqno + un->len)) - tp_vars->last_recv += to_ack; + if (batadv_seq_before(tp_vars->last_recv, to_ack)) + tp_vars->last_recv = to_ack; list_del(&un->list); kfree(un); From 936a1c10cecee351e10f9efc85532e9f7477f67c Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 10 Jun 2026 23:14:34 +0200 Subject: [PATCH 0012/1433] batman-adv: tp_meter: combine adjacent/overlapping unacked entries Right at the point when the receiver gets the first packet with a seqno gap (due to some packet loss/reordering), entries in the unacked list are created. They are (besides direct seqno matches) are not combined. A lot more then necessary entries are therefore created. Not for each gap but for each packet. This increases the memory consumption and management overhead. But it is trivial to handle overlapping or adjacent sequence number ranges during the insert. Only the handling of closed gaps by a new packets requires an extra step after the insert. Signed-off-by: Sven Eckelmann --- net/batman-adv/tp_meter.c | 65 ++++++++++++++++++++++++++++++++------- net/batman-adv/types.h | 2 +- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c index b7fee6e55f03..5cc719c81ea0 100644 --- a/net/batman-adv/tp_meter.c +++ b/net/batman-adv/tp_meter.c @@ -1406,6 +1406,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, __must_hold(&tp_vars->common.unacked_lock) { struct batadv_tp_unacked *un, *new; + struct batadv_tp_unacked *safe; bool added = false; new = kmalloc_obj(*new, GFP_ATOMIC); @@ -1430,20 +1431,46 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, * seqno than all the others already stored. */ list_for_each_entry_reverse(un, &tp_vars->common.unacked_list, list) { - /* check for duplicates */ - if (new->seqno == un->seqno) { - if (new->len > un->len) - un->len = new->len; - kfree(new); - added = true; - break; - } - - /* look for the right position */ + /* look for the right position - an un which is smaller */ if (batadv_seq_before(new->seqno, un->seqno)) continue; - /* as soon as an entry having a bigger seqno is found, the new + /* smaller/equal seqno was found but they might be directly + * after another or overlapping. keep only a single entry + * + * It is already known that: + * + * un->seqno <= new->seqno + * + * When establishing that: + * + * new->seqno <= un->seqno + un->len + * + * Then it is not necessary to add a new entry because the + * smaller/equal seqno of un might already contain the new + * received packet or we only add new data directly after + * the end of un. The latter can be identified using: + * + * un->seqno + un->len <= new->seqno + new->len + */ + if (!batadv_seq_before(un->seqno + un->len, new->seqno)) { + /* new data directly after un? */ + if (!batadv_seq_before(new->seqno + new->len, + un->seqno + un->len)) + un->len = new->seqno + new->len - un->seqno; + + /* un now represents both old un + new */ + kfree(new); + added = true; + + /* un has to be used to check if the gap to the next + * seqno range was closed + */ + new = un; + break; + } + + /* as soon as an entry having a smaller seqno is found, the new * one is attached _after_ it. In this way the list is kept in * ascending order */ @@ -1459,6 +1486,22 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, tp_vars->common.unacked_count++; } + /* check if new filled the gap to the next list entries */ + un = new; + list_for_each_entry_safe_continue(un, safe, &tp_vars->common.unacked_list, list) { + if (batadv_seq_before(new->seqno + new->len, un->seqno)) + break; + + /* next entry is overlapping or adjacent - combine both */ + if (batadv_seq_before(new->seqno + new->len, + un->seqno + un->len)) + new->len = un->seqno + un->len - new->seqno; + + list_del(&un->list); + kfree(un); + tp_vars->common.unacked_count--; + } + /* remove the last (biggest) unacked seqno when list is too large */ if (tp_vars->common.unacked_count > BATADV_TP_MAX_UNACKED) { un = list_last_entry(&tp_vars->common.unacked_list, diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h index e1463a029e83..c2ab00d8ef16 100644 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@ -1332,7 +1332,7 @@ struct batadv_tp_unacked { u32 seqno; /** @len: length of the packet */ - u16 len; + u32 len; /** @list: list node for &batadv_tp_vars_common.unacked_list */ struct list_head list; From 3bbb8b94217ae27210935f1cb40306be7200da40 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 10 Jun 2026 21:05:37 +0200 Subject: [PATCH 0013/1433] batman-adv: tp_meter: keep unacked list for receivers There is no need to share the unacked list between sender and receivers. Only receivers will ever write to and read from it. The initialization in batadv_tp_start() was therefore never needed. After its removal, it is enough to just store it in struct batadv_tp_receiver. Signed-off-by: Sven Eckelmann --- net/batman-adv/tp_meter.c | 110 +++++++++++++++++++++----------------- net/batman-adv/types.h | 20 +++---- 2 files changed, 71 insertions(+), 59 deletions(-) diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c index 5cc719c81ea0..f18ce360839d 100644 --- a/net/batman-adv/tp_meter.c +++ b/net/batman-adv/tp_meter.c @@ -358,28 +358,16 @@ batadv_tp_list_find_sender_session(struct batadv_priv *bat_priv, const u8 *dst, } /** - * batadv_tp_vars_common_release() - release batadv_tp_vars_common from lists + * batadv_tp_sender_release() - release batadv_tp_sender * and queue for free after rcu grace period - * @ref: kref pointer of the batadv_tp_vars_common + * @ref: kref pointer of the batadv_tp_sender */ -static void batadv_tp_vars_common_release(struct kref *ref) +static void batadv_tp_sender_release(struct kref *ref) { - struct batadv_tp_vars_common *tp_vars; - struct batadv_tp_unacked *un, *safe; + struct batadv_tp_sender *tp_vars; - tp_vars = container_of(ref, struct batadv_tp_vars_common, refcount); - - /* lock should not be needed because this object is now out of any - * context! - */ - spin_lock_bh(&tp_vars->unacked_lock); - list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) { - list_del(&un->list); - kfree(un); - } - spin_unlock_bh(&tp_vars->unacked_lock); - - kfree_rcu(tp_vars, rcu); + tp_vars = container_of(ref, struct batadv_tp_sender, common.refcount); + kfree_rcu(tp_vars, common.rcu); } /** @@ -392,7 +380,7 @@ static void batadv_tp_sender_put(struct batadv_tp_sender *tp_vars) if (!tp_vars) return; - kref_put(&tp_vars->common.refcount, batadv_tp_vars_common_release); + kref_put(&tp_vars->common.refcount, batadv_tp_sender_release); } /** @@ -1145,9 +1133,6 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst, init_waitqueue_head(&tp_vars->more_bytes); init_completion(&tp_vars->finished); - spin_lock_init(&tp_vars->common.unacked_lock); - INIT_LIST_HEAD(&tp_vars->common.unacked_list); - spin_lock_init(&tp_vars->cc_lock); tp_vars->prerandom_offset = 0; @@ -1251,6 +1236,33 @@ batadv_tp_list_find_receiver_session(struct batadv_priv *bat_priv, const u8 *dst return tp_vars; } +/** + * batadv_tp_receiver_release() - release batadv_tp_receiver + * and queue for free after rcu grace period + * @ref: kref pointer of the batadv_tp_receiver + */ +static void batadv_tp_receiver_release(struct kref *ref) +{ + struct batadv_tp_receiver *tp_vars; + struct batadv_tp_unacked *safe; + struct batadv_tp_unacked *un; + + tp_vars = container_of(ref, struct batadv_tp_receiver, common.refcount); + + /* lock should not be needed because this object is now out of any + * context! + */ + spin_lock_bh(&tp_vars->unacked_lock); + list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) { + list_del(&un->list); + kfree(un); + tp_vars->unacked_count--; + } + spin_unlock_bh(&tp_vars->unacked_lock); + + kfree_rcu(tp_vars, common.rcu); +} + /** * batadv_tp_receiver_put() - decrement the batadv_tp_receiver * refcounter and possibly release it @@ -1261,7 +1273,7 @@ static void batadv_tp_receiver_put(struct batadv_tp_receiver *tp_vars) if (!tp_vars) return; - kref_put(&tp_vars->common.refcount, batadv_tp_vars_common_release); + kref_put(&tp_vars->common.refcount, batadv_tp_receiver_release); } /** @@ -1304,13 +1316,13 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t) if (batadv_tp_list_detach(&tp_vars->common)) batadv_tp_receiver_put(tp_vars); - spin_lock_bh(&tp_vars->common.unacked_lock); - list_for_each_entry_safe(un, safe, &tp_vars->common.unacked_list, list) { + spin_lock_bh(&tp_vars->unacked_lock); + list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) { list_del(&un->list); kfree(un); - tp_vars->common.unacked_count--; + tp_vars->unacked_count--; } - spin_unlock_bh(&tp_vars->common.unacked_lock); + spin_unlock_bh(&tp_vars->unacked_lock); /* drop reference of timer */ if (WARN_ON(atomic_xchg(&tp_vars->receiving, 0) != 1)) @@ -1403,7 +1415,7 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst, */ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, u32 seqno, u32 payload_len) - __must_hold(&tp_vars->common.unacked_lock) + __must_hold(&tp_vars->unacked_lock) { struct batadv_tp_unacked *un, *new; struct batadv_tp_unacked *safe; @@ -1417,9 +1429,9 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, new->len = payload_len; /* if the list is empty immediately attach this new object */ - if (list_empty(&tp_vars->common.unacked_list)) { - list_add(&new->list, &tp_vars->common.unacked_list); - tp_vars->common.unacked_count++; + if (list_empty(&tp_vars->unacked_list)) { + list_add(&new->list, &tp_vars->unacked_list); + tp_vars->unacked_count++; return true; } @@ -1430,7 +1442,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, * the last received packet (the one being processed now) has a bigger * seqno than all the others already stored. */ - list_for_each_entry_reverse(un, &tp_vars->common.unacked_list, list) { + list_for_each_entry_reverse(un, &tp_vars->unacked_list, list) { /* look for the right position - an un which is smaller */ if (batadv_seq_before(new->seqno, un->seqno)) continue; @@ -1476,19 +1488,19 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, */ list_add(&new->list, &un->list); added = true; - tp_vars->common.unacked_count++; + tp_vars->unacked_count++; break; } /* received packet with smallest seqno out of order; add it to front */ if (!added) { - list_add(&new->list, &tp_vars->common.unacked_list); - tp_vars->common.unacked_count++; + list_add(&new->list, &tp_vars->unacked_list); + tp_vars->unacked_count++; } /* check if new filled the gap to the next list entries */ un = new; - list_for_each_entry_safe_continue(un, safe, &tp_vars->common.unacked_list, list) { + list_for_each_entry_safe_continue(un, safe, &tp_vars->unacked_list, list) { if (batadv_seq_before(new->seqno + new->len, un->seqno)) break; @@ -1499,16 +1511,16 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, list_del(&un->list); kfree(un); - tp_vars->common.unacked_count--; + tp_vars->unacked_count--; } /* remove the last (biggest) unacked seqno when list is too large */ - if (tp_vars->common.unacked_count > BATADV_TP_MAX_UNACKED) { - un = list_last_entry(&tp_vars->common.unacked_list, + if (tp_vars->unacked_count > BATADV_TP_MAX_UNACKED) { + un = list_last_entry(&tp_vars->unacked_list, struct batadv_tp_unacked, list); list_del(&un->list); kfree(un); - tp_vars->common.unacked_count--; + tp_vars->unacked_count--; } return true; @@ -1520,7 +1532,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, * @tp_vars: the private data of the current TP meter session */ static void batadv_tp_ack_unordered(struct batadv_tp_receiver *tp_vars) - __must_hold(&tp_vars->common.unacked_lock) + __must_hold(&tp_vars->unacked_lock) { struct batadv_tp_unacked *un, *safe; u32 to_ack; @@ -1528,7 +1540,7 @@ static void batadv_tp_ack_unordered(struct batadv_tp_receiver *tp_vars) /* go through the unacked packet list and possibly ACK them as * well */ - list_for_each_entry_safe(un, safe, &tp_vars->common.unacked_list, list) { + list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) { /* the list is ordered, therefore it is possible to stop as soon * there is a gap between the last acked seqno and the seqno of * the packet under inspection @@ -1543,7 +1555,7 @@ static void batadv_tp_ack_unordered(struct batadv_tp_receiver *tp_vars) list_del(&un->list); kfree(un); - tp_vars->common.unacked_count--; + tp_vars->unacked_count--; } } @@ -1590,9 +1602,9 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv, tp_vars->common.bat_priv = bat_priv; kref_init(&tp_vars->common.refcount); - spin_lock_init(&tp_vars->common.unacked_lock); - INIT_LIST_HEAD(&tp_vars->common.unacked_list); - tp_vars->common.unacked_count = 0; + spin_lock_init(&tp_vars->unacked_lock); + INIT_LIST_HEAD(&tp_vars->unacked_list); + tp_vars->unacked_count = 0; kref_get(&tp_vars->common.refcount); timer_setup(&tp_vars->common.timer, batadv_tp_receiver_shutdown, 0); @@ -1652,7 +1664,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv, WRITE_ONCE(tp_vars->last_recv_time, jiffies); } - spin_lock_bh(&tp_vars->common.unacked_lock); + spin_lock_bh(&tp_vars->unacked_lock); /* if the packet is a duplicate, it may be the case that an ACK has been * lost. Resend the ACK @@ -1668,7 +1680,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv, * not been enqueued correctly */ if (!batadv_tp_handle_out_of_order(tp_vars, seqno, payload_len)) { - spin_unlock_bh(&tp_vars->common.unacked_lock); + spin_unlock_bh(&tp_vars->unacked_lock); goto out; } @@ -1684,7 +1696,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv, send_ack: to_ack = tp_vars->last_recv; - spin_unlock_bh(&tp_vars->common.unacked_lock); + spin_unlock_bh(&tp_vars->unacked_lock); /* send the ACK. If the received packet was out of order, the ACK that * is going to be sent is a duplicate (the sender will count them and diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h index c2ab00d8ef16..c194d8069774 100644 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@ -1334,7 +1334,7 @@ struct batadv_tp_unacked { /** @len: length of the packet */ u32 len; - /** @list: list node for &batadv_tp_vars_common.unacked_list */ + /** @list: list node for &batadv_tp_receiver.unacked_list */ struct list_head list; }; @@ -1357,15 +1357,6 @@ struct batadv_tp_vars_common { /** @session: TP session identifier */ u8 session[2]; - /** @unacked_list: list of unacked packets (meta-info only) */ - struct list_head unacked_list; - - /** @unacked_lock: protect unacked_list + &batadv_tp_receiver.last_recv */ - spinlock_t unacked_lock; - - /** @unacked_count: number of unacked entries */ - size_t unacked_count; - /** @refcount: number of context where the object is used */ struct kref refcount; @@ -1479,6 +1470,15 @@ struct batadv_tp_receiver { /** @last_recv_time: time (jiffies) a msg was received */ unsigned long last_recv_time; + + /** @unacked_list: list of unacked packets (meta-info only) */ + struct list_head unacked_list; + + /** @unacked_lock: protect unacked_list + &batadv_tp_receiver.last_recv */ + spinlock_t unacked_lock; + + /** @unacked_count: number of unacked entries */ + size_t unacked_count; }; /** From 6cd45ef4dfa5ba4daae126143a3d009d36687d80 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 11 Jun 2026 09:48:34 +0200 Subject: [PATCH 0014/1433] batman-adv: tp_meter: adjust name of receiver lock The lock used to protect the receiver from reading/writing in parallel to ack sequence number relevant data was still called unacked_lock. But it is no longer only about the unacked_list. Use a broader term to reflect this. Signed-off-by: Sven Eckelmann --- net/batman-adv/tp_meter.c | 20 ++++++++++---------- net/batman-adv/types.h | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c index f18ce360839d..ffd3171d4b99 100644 --- a/net/batman-adv/tp_meter.c +++ b/net/batman-adv/tp_meter.c @@ -1252,13 +1252,13 @@ static void batadv_tp_receiver_release(struct kref *ref) /* lock should not be needed because this object is now out of any * context! */ - spin_lock_bh(&tp_vars->unacked_lock); + spin_lock_bh(&tp_vars->ack_seqno_lock); list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) { list_del(&un->list); kfree(un); tp_vars->unacked_count--; } - spin_unlock_bh(&tp_vars->unacked_lock); + spin_unlock_bh(&tp_vars->ack_seqno_lock); kfree_rcu(tp_vars, common.rcu); } @@ -1316,13 +1316,13 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t) if (batadv_tp_list_detach(&tp_vars->common)) batadv_tp_receiver_put(tp_vars); - spin_lock_bh(&tp_vars->unacked_lock); + spin_lock_bh(&tp_vars->ack_seqno_lock); list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) { list_del(&un->list); kfree(un); tp_vars->unacked_count--; } - spin_unlock_bh(&tp_vars->unacked_lock); + spin_unlock_bh(&tp_vars->ack_seqno_lock); /* drop reference of timer */ if (WARN_ON(atomic_xchg(&tp_vars->receiving, 0) != 1)) @@ -1415,7 +1415,7 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst, */ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, u32 seqno, u32 payload_len) - __must_hold(&tp_vars->unacked_lock) + __must_hold(&tp_vars->ack_seqno_lock) { struct batadv_tp_unacked *un, *new; struct batadv_tp_unacked *safe; @@ -1532,7 +1532,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, * @tp_vars: the private data of the current TP meter session */ static void batadv_tp_ack_unordered(struct batadv_tp_receiver *tp_vars) - __must_hold(&tp_vars->unacked_lock) + __must_hold(&tp_vars->ack_seqno_lock) { struct batadv_tp_unacked *un, *safe; u32 to_ack; @@ -1602,7 +1602,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv, tp_vars->common.bat_priv = bat_priv; kref_init(&tp_vars->common.refcount); - spin_lock_init(&tp_vars->unacked_lock); + spin_lock_init(&tp_vars->ack_seqno_lock); INIT_LIST_HEAD(&tp_vars->unacked_list); tp_vars->unacked_count = 0; @@ -1664,7 +1664,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv, WRITE_ONCE(tp_vars->last_recv_time, jiffies); } - spin_lock_bh(&tp_vars->unacked_lock); + spin_lock_bh(&tp_vars->ack_seqno_lock); /* if the packet is a duplicate, it may be the case that an ACK has been * lost. Resend the ACK @@ -1680,7 +1680,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv, * not been enqueued correctly */ if (!batadv_tp_handle_out_of_order(tp_vars, seqno, payload_len)) { - spin_unlock_bh(&tp_vars->unacked_lock); + spin_unlock_bh(&tp_vars->ack_seqno_lock); goto out; } @@ -1696,7 +1696,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv, send_ack: to_ack = tp_vars->last_recv; - spin_unlock_bh(&tp_vars->unacked_lock); + spin_unlock_bh(&tp_vars->ack_seqno_lock); /* send the ACK. If the received packet was out of order, the ACK that * is going to be sent is a duplicate (the sender will count them and diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h index c194d8069774..cd12755d21f3 100644 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@ -1474,8 +1474,8 @@ struct batadv_tp_receiver { /** @unacked_list: list of unacked packets (meta-info only) */ struct list_head unacked_list; - /** @unacked_lock: protect unacked_list + &batadv_tp_receiver.last_recv */ - spinlock_t unacked_lock; + /** @ack_seqno_lock: protect unacked_list + &batadv_tp_receiver.last_recv */ + spinlock_t ack_seqno_lock; /** @unacked_count: number of unacked entries */ size_t unacked_count; From 247691642fd4de7a029de253e47dba936542ce9f Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 11 Jun 2026 10:48:34 +0200 Subject: [PATCH 0015/1433] batman-adv: tp_meter: delay allocation of unacked entry When batadv_tp_handle_out_of_order() searches the already existing list of unacked packets, it can often find an entry to merge with. In this case, it would be a waste of time and resources to allocate a batadv_tp_unacked which is then immediately freed again. Instead, search first through the list. Only when no mergeable entry could be found, it is necessary to record the place to allocate+store the new entry. Signed-off-by: Sven Eckelmann --- net/batman-adv/tp_meter.c | 88 ++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 47 deletions(-) diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c index ffd3171d4b99..00467aa79de9 100644 --- a/net/batman-adv/tp_meter.c +++ b/net/batman-adv/tp_meter.c @@ -1417,26 +1417,15 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, u32 seqno, u32 payload_len) __must_hold(&tp_vars->ack_seqno_lock) { - struct batadv_tp_unacked *un, *new; + struct list_head *pos = &tp_vars->unacked_list; + struct batadv_tp_unacked *new = NULL; + u32 end_seqno = seqno + payload_len; struct batadv_tp_unacked *safe; - bool added = false; + struct batadv_tp_unacked *un; - new = kmalloc_obj(*new, GFP_ATOMIC); - if (unlikely(!new)) - return false; - - new->seqno = seqno; - new->len = payload_len; - - /* if the list is empty immediately attach this new object */ - if (list_empty(&tp_vars->unacked_list)) { - list_add(&new->list, &tp_vars->unacked_list); - tp_vars->unacked_count++; - return true; - } - - /* otherwise loop over the list and either drop the packet because this - * is a duplicate or store it at the right position. + /* loop over the list to find either an existing entry which the new + * seqno range can be merged with or the position at which a new entry + * has to be inserted. * * The iteration is done in the reverse way because it is likely that * the last received packet (the one being processed now) has a bigger @@ -1444,7 +1433,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, */ list_for_each_entry_reverse(un, &tp_vars->unacked_list, list) { /* look for the right position - an un which is smaller */ - if (batadv_seq_before(new->seqno, un->seqno)) + if (batadv_seq_before(seqno, un->seqno)) continue; /* smaller/equal seqno was found but they might be directly @@ -1452,62 +1441,67 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, * * It is already known that: * - * un->seqno <= new->seqno + * un->seqno <= seqno * * When establishing that: * - * new->seqno <= un->seqno + un->len + * seqno <= un->seqno + un->len * * Then it is not necessary to add a new entry because the * smaller/equal seqno of un might already contain the new * received packet or we only add new data directly after * the end of un. The latter can be identified using: * - * un->seqno + un->len <= new->seqno + new->len + * un->seqno + un->len <= end_seqno */ - if (!batadv_seq_before(un->seqno + un->len, new->seqno)) { + if (!batadv_seq_before(un->seqno + un->len, seqno)) { /* new data directly after un? */ - if (!batadv_seq_before(new->seqno + new->len, - un->seqno + un->len)) - un->len = new->seqno + new->len - un->seqno; + if (!batadv_seq_before(end_seqno, un->seqno + un->len)) + un->len = end_seqno - un->seqno; - /* un now represents both old un + new */ - kfree(new); - added = true; - - /* un has to be used to check if the gap to the next - * seqno range was closed + /* un now represents both old un + new range and has to + * be used to check if the gap to the next seqno range + * was closed */ new = un; - break; + } else { + /* as soon as an entry having a smaller seqno is found, + * the new one is attached _after_ it. In this way the + * list is kept in ascending order + */ + pos = &un->list; } - /* as soon as an entry having a smaller seqno is found, the new - * one is attached _after_ it. In this way the list is kept in - * ascending order - */ - list_add(&new->list, &un->list); - added = true; - tp_vars->unacked_count++; break; } - /* received packet with smallest seqno out of order; add it to front */ - if (!added) { - list_add(&new->list, &tp_vars->unacked_list); + /* no entry to merge with was found; insert a new one after the entry + * with the next smaller seqno (or at the front of the list when the + * new seqno is the smallest or the list is empty) + */ + if (!new) { + new = kmalloc_obj(*new, GFP_ATOMIC); + if (unlikely(!new)) + return false; + + new->seqno = seqno; + new->len = payload_len; + + list_add(&new->list, pos); tp_vars->unacked_count++; } /* check if new filled the gap to the next list entries */ un = new; list_for_each_entry_safe_continue(un, safe, &tp_vars->unacked_list, list) { - if (batadv_seq_before(new->seqno + new->len, un->seqno)) + if (batadv_seq_before(end_seqno, un->seqno)) break; /* next entry is overlapping or adjacent - combine both */ - if (batadv_seq_before(new->seqno + new->len, - un->seqno + un->len)) - new->len = un->seqno + un->len - new->seqno; + if (batadv_seq_before(end_seqno, un->seqno + un->len)) { + end_seqno = un->seqno + un->len; + new->len = end_seqno - new->seqno; + } list_del(&un->list); kfree(un); From 2c599da8231ff45260d3267c6d334b80147f16c5 Mon Sep 17 00:00:00 2001 From: Alejandro Lucero Date: Mon, 29 Jun 2026 19:37:26 +0100 Subject: [PATCH 0016/1433] cxl: Support Type2 cxl regs mapping Export cxl core functions for a Type2 driver being able to discover and map the device registers. Signed-off-by: Alejandro Lucero Reviewed-by: Dan Williams Reviewed-by: Jonathan Cameron Reviewed-by: Dave Jiang Reviewed-by: Ben Cheatham Acked-by: Edward Cree Link: https://patch.msgid.link/20260629183727.51502-2-alejandro.lucero-palau@amd.com Signed-off-by: Dave Jiang --- drivers/cxl/core/pci.c | 1 + drivers/cxl/core/port.c | 1 + drivers/cxl/core/regs.c | 1 + drivers/cxl/cxlpci.h | 12 ------------ drivers/cxl/pci.c | 1 + include/cxl/pci.h | 22 ++++++++++++++++++++++ 6 files changed, 26 insertions(+), 12 deletions(-) create mode 100644 include/cxl/pci.h diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c index e4338fd7e01b..9d807c1a002c 100644 --- a/drivers/cxl/core/pci.c +++ b/drivers/cxl/core/pci.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/cxl/core/port.c b/drivers/cxl/core/port.c index 1215ee4f4035..cb633e19151b 100644 --- a/drivers/cxl/core/port.c +++ b/drivers/cxl/core/port.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/cxl/core/regs.c b/drivers/cxl/core/regs.c index 93710cf4f0a6..20c2d9fbcfe7 100644 --- a/drivers/cxl/core/regs.c +++ b/drivers/cxl/core/regs.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/cxl/cxlpci.h b/drivers/cxl/cxlpci.h index b826eb53cf7b..110ec9c44f09 100644 --- a/drivers/cxl/cxlpci.h +++ b/drivers/cxl/cxlpci.h @@ -13,16 +13,6 @@ */ #define CXL_PCI_DEFAULT_MAX_VECTORS 16 -/* Register Block Identifier (RBI) */ -enum cxl_regloc_type { - CXL_REGLOC_RBI_EMPTY = 0, - CXL_REGLOC_RBI_COMPONENT, - CXL_REGLOC_RBI_VIRT, - CXL_REGLOC_RBI_MEMDEV, - CXL_REGLOC_RBI_PMU, - CXL_REGLOC_RBI_TYPES -}; - /* * Table Access DOE, CDAT Read Entry Response * @@ -112,6 +102,4 @@ static inline void devm_cxl_port_ras_setup(struct cxl_port *port) } #endif -int cxl_pci_setup_regs(struct pci_dev *pdev, enum cxl_regloc_type type, - struct cxl_register_map *map); #endif /* __CXL_PCI_H__ */ diff --git a/drivers/cxl/pci.c b/drivers/cxl/pci.c index 267c679b0b3c..bb892dbfdd6d 100644 --- a/drivers/cxl/pci.c +++ b/drivers/cxl/pci.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "cxlmem.h" #include "cxlpci.h" diff --git a/include/cxl/pci.h b/include/cxl/pci.h new file mode 100644 index 000000000000..3e0000015871 --- /dev/null +++ b/include/cxl/pci.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright(c) 2020 Intel Corporation. All rights reserved. */ + +#ifndef __CXL_CXL_PCI_H__ +#define __CXL_CXL_PCI_H__ + +/* Register Block Identifier (RBI) */ +enum cxl_regloc_type { + CXL_REGLOC_RBI_EMPTY = 0, + CXL_REGLOC_RBI_COMPONENT, + CXL_REGLOC_RBI_VIRT, + CXL_REGLOC_RBI_MEMDEV, + CXL_REGLOC_RBI_PMU, + CXL_REGLOC_RBI_TYPES +}; + +struct cxl_register_map; +struct pci_dev; + +int cxl_pci_setup_regs(struct pci_dev *pdev, enum cxl_regloc_type type, + struct cxl_register_map *map); +#endif From 96ddf1af34f5f9e29891a5bfb7a18dd0a5bab9d6 Mon Sep 17 00:00:00 2001 From: Alejandro Lucero Date: Mon, 29 Jun 2026 19:37:27 +0100 Subject: [PATCH 0017/1433] cxl: Support dpa without a mailbox Type3 relies on mailbox CXL_MBOX_OP_IDENTIFY command for initializing memdev state params which end up being used for DPA initialization. Allow a Type2 driver to initialize DPA simply by giving the size of its volatile hardware partition. Move related functions to memdev. Signed-off-by: Alejandro Lucero Reviewed-by: Dan Williams Reviewed-by: Dave Jiang Reviewed-by: Ben Cheatham Reviewed-by: Jonathan Cameron Acked-by: Edward Cree Link: https://patch.msgid.link/20260629183727.51502-3-alejandro.lucero-palau@amd.com Signed-off-by: Dave Jiang --- drivers/cxl/core/core.h | 2 ++ drivers/cxl/core/mbox.c | 51 +---------------------------- drivers/cxl/core/memdev.c | 67 +++++++++++++++++++++++++++++++++++++++ include/cxl/cxl.h | 2 ++ 4 files changed, 72 insertions(+), 50 deletions(-) diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h index 07555ae63859..f7cebb026552 100644 --- a/drivers/cxl/core/core.h +++ b/drivers/cxl/core/core.h @@ -101,6 +101,8 @@ void __iomem *devm_cxl_iomap_block(struct device *dev, resource_size_t addr, struct dentry *cxl_debugfs_create_dir(const char *dir); int cxl_dpa_set_part(struct cxl_endpoint_decoder *cxled, enum cxl_partition_mode mode); +struct cxl_memdev_state; +int cxl_mem_get_partition_info(struct cxl_memdev_state *mds); int cxl_dpa_alloc(struct cxl_endpoint_decoder *cxled, u64 size); int cxl_dpa_free(struct cxl_endpoint_decoder *cxled); resource_size_t cxl_dpa_size(struct cxl_endpoint_decoder *cxled); diff --git a/drivers/cxl/core/mbox.c b/drivers/cxl/core/mbox.c index 7c6c5b7450a5..97b1e61ad018 100644 --- a/drivers/cxl/core/mbox.c +++ b/drivers/cxl/core/mbox.c @@ -1152,7 +1152,7 @@ EXPORT_SYMBOL_NS_GPL(cxl_mem_get_event_records, "CXL"); * * See CXL @8.2.9.5.2.1 Get Partition Info */ -static int cxl_mem_get_partition_info(struct cxl_memdev_state *mds) +int cxl_mem_get_partition_info(struct cxl_memdev_state *mds) { struct cxl_mailbox *cxl_mbox = &mds->cxlds.cxl_mbox; struct cxl_mbox_get_partition_info pi; @@ -1308,55 +1308,6 @@ int cxl_mem_sanitize(struct cxl_memdev *cxlmd, u16 cmd) return -EBUSY; } -static void add_part(struct cxl_dpa_info *info, u64 start, u64 size, enum cxl_partition_mode mode) -{ - int i = info->nr_partitions; - - if (size == 0) - return; - - info->part[i].range = (struct range) { - .start = start, - .end = start + size - 1, - }; - info->part[i].mode = mode; - info->nr_partitions++; -} - -int cxl_mem_dpa_fetch(struct cxl_memdev_state *mds, struct cxl_dpa_info *info) -{ - struct cxl_dev_state *cxlds = &mds->cxlds; - struct device *dev = cxlds->dev; - int rc; - - if (!cxlds->media_ready) { - info->size = 0; - return 0; - } - - info->size = mds->total_bytes; - - if (mds->partition_align_bytes == 0) { - add_part(info, 0, mds->volatile_only_bytes, CXL_PARTMODE_RAM); - add_part(info, mds->volatile_only_bytes, - mds->persistent_only_bytes, CXL_PARTMODE_PMEM); - return 0; - } - - rc = cxl_mem_get_partition_info(mds); - if (rc) { - dev_err(dev, "Failed to query partition information\n"); - return rc; - } - - add_part(info, 0, mds->active_volatile_bytes, CXL_PARTMODE_RAM); - add_part(info, mds->active_volatile_bytes, mds->active_persistent_bytes, - CXL_PARTMODE_PMEM); - - return 0; -} -EXPORT_SYMBOL_NS_GPL(cxl_mem_dpa_fetch, "CXL"); - int cxl_get_dirty_count(struct cxl_memdev_state *mds, u32 *count) { struct cxl_mailbox *cxl_mbox = &mds->cxlds.cxl_mbox; diff --git a/drivers/cxl/core/memdev.c b/drivers/cxl/core/memdev.c index 33a3d2e7b13a..2e457b1ebc7d 100644 --- a/drivers/cxl/core/memdev.c +++ b/drivers/cxl/core/memdev.c @@ -594,6 +594,73 @@ bool is_cxl_memdev(const struct device *dev) } EXPORT_SYMBOL_NS_GPL(is_cxl_memdev, "CXL"); +static void add_part(struct cxl_dpa_info *info, u64 start, u64 size, enum cxl_partition_mode mode) +{ + int i = info->nr_partitions; + + if (size == 0) + return; + + info->part[i].range = (struct range) { + .start = start, + .end = start + size - 1, + }; + info->part[i].mode = mode; + info->nr_partitions++; +} + +int cxl_mem_dpa_fetch(struct cxl_memdev_state *mds, struct cxl_dpa_info *info) +{ + struct cxl_dev_state *cxlds = &mds->cxlds; + struct device *dev = cxlds->dev; + int rc; + + if (!cxlds->media_ready) { + info->size = 0; + return 0; + } + + info->size = mds->total_bytes; + + if (mds->partition_align_bytes == 0) { + add_part(info, 0, mds->volatile_only_bytes, CXL_PARTMODE_RAM); + add_part(info, mds->volatile_only_bytes, + mds->persistent_only_bytes, CXL_PARTMODE_PMEM); + return 0; + } + + rc = cxl_mem_get_partition_info(mds); + if (rc) { + dev_err(dev, "Failed to query partition information\n"); + return rc; + } + + add_part(info, 0, mds->active_volatile_bytes, CXL_PARTMODE_RAM); + add_part(info, mds->active_volatile_bytes, mds->active_persistent_bytes, + CXL_PARTMODE_PMEM); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(cxl_mem_dpa_fetch, "CXL"); + + +/** + * cxl_set_capacity: initialize dpa by a driver without a mailbox. + * + * @cxlds: pointer to cxl_dev_state + * @capacity: device volatile memory size + */ +int cxl_set_capacity(struct cxl_dev_state *cxlds, u64 capacity) +{ + struct cxl_dpa_info range_info = { + .size = capacity, + }; + + add_part(&range_info, 0, capacity, CXL_PARTMODE_RAM); + return cxl_dpa_setup(cxlds, &range_info); +} +EXPORT_SYMBOL_NS_GPL(cxl_set_capacity, "CXL"); + /** * set_exclusive_cxl_commands() - atomically disable user cxl commands * @mds: The device state to operate on diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h index 016c74fb747c..802b143de83d 100644 --- a/include/cxl/cxl.h +++ b/include/cxl/cxl.h @@ -226,4 +226,6 @@ struct cxl_dev_state *_devm_cxl_dev_state_create(struct device *dev, struct cxl_memdev *devm_cxl_probe_mem(struct cxl_dev_state *cxlds, struct range *range); + +int cxl_set_capacity(struct cxl_dev_state *cxlds, u64 capacity); #endif /* __CXL_CXL_H__ */ From ba7ad852e1ae934adf506e1e854910a7ab52aa3f Mon Sep 17 00:00:00 2001 From: Evgenii Burenchev Date: Thu, 25 Jun 2026 14:48:31 +0300 Subject: [PATCH 0018/1433] mlxsw: spectrum_acl_erp: Fix const qualifier of delta_clear() mlxsw_sp_acl_erp_delta_clear() takes 'const char *enc_key' but modifies the memory it points to. This is a logical error in the function declaration. The only caller passes a non-const buffer (aentry->ht_key.enc_key), so the const qualifier is misleading and unnecessary. Remove const from the enc_key parameter to match the actual usage. Signed-off-by: Evgenii Burenchev Reviewed-by: Petr Machata Link: https://patch.msgid.link/20260625114831.17386-1-evg28bur@yandex.ru Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_erp.c | 2 +- drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_tcam.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_erp.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_erp.c index cbb272a96359..0d0cd093b3c6 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_erp.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_erp.c @@ -1118,7 +1118,7 @@ u8 mlxsw_sp_acl_erp_delta_value(const struct mlxsw_sp_acl_erp_delta *delta, } void mlxsw_sp_acl_erp_delta_clear(const struct mlxsw_sp_acl_erp_delta *delta, - const char *enc_key) + char *enc_key) { u16 start = delta->start; u8 mask = delta->mask; diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_tcam.h b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_tcam.h index 010204f73ea4..67cc7a5737dd 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_tcam.h +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_tcam.h @@ -245,7 +245,7 @@ u8 mlxsw_sp_acl_erp_delta_mask(const struct mlxsw_sp_acl_erp_delta *delta); u8 mlxsw_sp_acl_erp_delta_value(const struct mlxsw_sp_acl_erp_delta *delta, const char *enc_key); void mlxsw_sp_acl_erp_delta_clear(const struct mlxsw_sp_acl_erp_delta *delta, - const char *enc_key); + char *enc_key); struct mlxsw_sp_acl_erp_mask; From 895bad9cc4cecdef54e4ef66208544d108799761 Mon Sep 17 00:00:00 2001 From: Nirmoy Das Date: Fri, 26 Jun 2026 07:49:02 -0700 Subject: [PATCH 0019/1433] selftests: net: make busywait timeout clock portable loopy_wait() expects millisecond timestamps. However, Ubuntu Resolute can use uutils date, where `date -u +%s%3N` returns seconds plus full nanoseconds instead of a 3-digit millisecond field. This makes busywait expire too early and can make vlan_bridge_binding.sh read a stale operstate. Link: https://github.com/uutils/coreutils/issues/11658 Signed-off-by: Nirmoy Das Link: https://patch.msgid.link/20260626144902.3214350-1-nirmoyd@nvidia.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/lib.sh | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/net/lib.sh b/tools/testing/selftests/net/lib.sh index b3827b43782b..d389a965d8f1 100644 --- a/tools/testing/selftests/net/lib.sh +++ b/tools/testing/selftests/net/lib.sh @@ -70,12 +70,27 @@ ksft_exit_status_merge() $ksft_xfail $ksft_pass $ksft_skip $ksft_fail } +timestamp_ms() +{ + local now=$(date -u +%s:%N) + local seconds=${now%:*} + local nanoseconds=${now#*:} + + if [[ $nanoseconds =~ ^[0-9]+$ ]]; then + nanoseconds=${nanoseconds:0:9} + else + nanoseconds=0 + fi + + echo $((seconds * 1000 + 10#$nanoseconds / 1000000)) +} + loopy_wait() { local sleep_cmd=$1; shift local timeout_ms=$1; shift - local start_time="$(date -u +%s%3N)" + local start_time=$(timestamp_ms) while true do local out @@ -84,7 +99,7 @@ loopy_wait() return 0 fi - local current_time="$(date -u +%s%3N)" + local current_time=$(timestamp_ms) if ((current_time - start_time > timeout_ms)); then echo -n "$out" return 1 From cef9d6804030793cf8b8796fd6936197d065dd3e Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Fri, 26 Jun 2026 15:52:28 -0700 Subject: [PATCH 0020/1433] net: gianfar: dispose irq mappings on probe failure and device removal irq_of_parse_and_map() creates irqdomain mappings that should be balanced with irq_dispose_mapping(). The driver never called irq_dispose_mapping(), leaking mappings on probe failure and device removal. Fix by adding irq_dispose_mapping() in free_gfar_dev() and expanding its loop from priv->num_grps to MAXGROUPS so the error path also catches partially-initialized groups. All irqinfo pointers are pre-initialized to NULL in gfar_of_init(), making the NULL-guarded walk in free_gfar_dev() safe for every scenario. gfar_parse_group() itself is left as a simple parse function with no resource management; cleanup is centralized in the caller's error path. Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260626225228.427392-1-rosenp@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/gianfar.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/freescale/gianfar.c b/drivers/net/ethernet/freescale/gianfar.c index 3271de5844f8..89215e1ddc2d 100644 --- a/drivers/net/ethernet/freescale/gianfar.c +++ b/drivers/net/ethernet/freescale/gianfar.c @@ -469,10 +469,13 @@ static void free_gfar_dev(struct gfar_private *priv) { int i, j; - for (i = 0; i < priv->num_grps; i++) + for (i = 0; i < MAXGROUPS; i++) for (j = 0; j < GFAR_NUM_IRQS; j++) { - kfree(priv->gfargrp[i].irqinfo[j]); - priv->gfargrp[i].irqinfo[j] = NULL; + if (priv->gfargrp[i].irqinfo[j]) { + irq_dispose_mapping(priv->gfargrp[i].irqinfo[j]->irq); + kfree(priv->gfargrp[i].irqinfo[j]); + priv->gfargrp[i].irqinfo[j] = NULL; + } } free_netdev(priv->ndev); @@ -616,7 +619,7 @@ static phy_interface_t gfar_get_interface(struct net_device *dev) static int gfar_of_init(struct platform_device *ofdev, struct net_device **pdev) { const char *model; - int err = 0, i; + int err = 0, i, j; phy_interface_t interface; struct net_device *dev = NULL; struct gfar_private *priv = NULL; @@ -702,8 +705,11 @@ static int gfar_of_init(struct platform_device *ofdev, struct net_device **pdev) priv->rx_list.count = 0; mutex_init(&priv->rx_queue_access); - for (i = 0; i < MAXGROUPS; i++) + for (i = 0; i < MAXGROUPS; i++) { priv->gfargrp[i].regs = NULL; + for (j = 0; j < GFAR_NUM_IRQS; j++) + priv->gfargrp[i].irqinfo[j] = NULL; + } /* Parse and initialize group specific information */ if (priv->mode == MQ_MG_MODE) { From f456c1922c49e6be5ce407ddb74a6e61af5b65cf Mon Sep 17 00:00:00 2001 From: Arseniy Krasnov Date: Sun, 28 Jun 2026 21:20:52 +0300 Subject: [PATCH 0021/1433] vsock/virtio: rewrite MSG_ZEROCOPY flag handling Logically it was based on TCP implementation, so to make further support easier, rewrite it in the TCP way (like in 'tcp_sendmsg_locked()'). By this way, patch also adds handling case when 'msg_ubuf' is already set. Signed-off-by: Arseniy Krasnov Acked-by: Michael S. Tsirkin Reviewed-by: Stefano Garzarella Link: https://patch.msgid.link/20260628182052.951760-1-avkrasnov@rulkc.org Signed-off-by: Paolo Abeni --- net/vmw_vsock/virtio_transport_common.c | 49 ++++++++++++------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c index 09475007165b..41c2a0b82a8e 100644 --- a/net/vmw_vsock/virtio_transport_common.c +++ b/net/vmw_vsock/virtio_transport_common.c @@ -328,38 +328,35 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk, if (pkt_len == 0 && info->op == VIRTIO_VSOCK_OP_RW) return pkt_len; - if (info->msg) { - /* If zerocopy is not enabled by 'setsockopt()', we behave as - * there is no MSG_ZEROCOPY flag set. + if (info->msg && (info->msg->msg_flags & MSG_ZEROCOPY)) { + /* If 'info->msg' is not NULL, this is only VIRTIO_VSOCK_OP_RW. + * 'MSG_ZEROCOPY' flag handling here is based on the same flag + * handling from 'tcp_sendmsg_locked()'. */ - if (!sock_flag(sk_vsock(vsk), SOCK_ZEROCOPY)) - info->msg->msg_flags &= ~MSG_ZEROCOPY; - - if (info->msg->msg_flags & MSG_ZEROCOPY) + if (info->msg->msg_ubuf) { + uarg = info->msg->msg_ubuf; can_zcopy = virtio_transport_can_zcopy(t_ops, info, pkt_len); + } else if (sock_flag(sk_vsock(vsk), SOCK_ZEROCOPY)) { + uarg = msg_zerocopy_realloc(sk_vsock(vsk), pkt_len, + NULL, false); + if (!uarg) { + virtio_transport_put_credit(vvs, pkt_len); + return -ENOMEM; + } + can_zcopy = virtio_transport_can_zcopy(t_ops, info, pkt_len); + if (!can_zcopy) + uarg_to_msgzc(uarg)->zerocopy = 0; + + have_uref = true; + } + + /* 'can_zcopy' means that this transmission will be + * in zerocopy way (e.g. using 'frags' array). + */ if (can_zcopy) max_skb_len = min_t(u32, VIRTIO_VSOCK_MAX_PKT_BUF_SIZE, (MAX_SKB_FRAGS * PAGE_SIZE)); - - if (info->msg->msg_flags & MSG_ZEROCOPY && - info->op == VIRTIO_VSOCK_OP_RW) { - uarg = info->msg->msg_ubuf; - - if (!uarg) { - uarg = msg_zerocopy_realloc(sk_vsock(vsk), - pkt_len, NULL, false); - if (!uarg) { - virtio_transport_put_credit(vvs, pkt_len); - return -ENOMEM; - } - - if (!can_zcopy) - uarg_to_msgzc(uarg)->zerocopy = 0; - - have_uref = true; - } - } } rest_len = pkt_len; From 25e638447e329f08febae2b64c7f85b3bb95e998 Mon Sep 17 00:00:00 2001 From: Hangtian Zhu Date: Tue, 19 May 2026 09:16:26 +0800 Subject: [PATCH 0022/1433] genirq: export irq_can_set_affinity() for module drivers Export irq_can_set_affinity() for loadable drivers that need a runtime check for IRQ affinity capability. In hierarchical IRQ setups where the effective irqchip path lacks .irq_set_affinity(), drivers may need to switch to a fallback policy. Without this export, module drivers cannot use the core helper and have to open-code equivalent checks. Signed-off-by: Hangtian Zhu Acked-by: Thomas Gleixner Link: https://patch.msgid.link/20260519011627.713068-2-hangtian.zhu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- kernel/irq/manage.c | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c index 7eb07e3bdb4c..f73fda08417a 100644 --- a/kernel/irq/manage.c +++ b/kernel/irq/manage.c @@ -171,6 +171,7 @@ int irq_can_set_affinity(unsigned int irq) { return __irq_can_set_affinity(irq_to_desc(irq)); } +EXPORT_SYMBOL_GPL(irq_can_set_affinity); /** * irq_can_set_affinity_usr - Check if affinity of a irq can be set from user space From 74e1a4762c79a2d6879495d83f22b83acc4dae33 Mon Sep 17 00:00:00 2001 From: Hangtian Zhu Date: Tue, 19 May 2026 09:16:27 +0800 Subject: [PATCH 0023/1433] wifi: ath12k: enable threaded NAPI when DP IRQ affinity is unavailable Determine threaded NAPI policy from runtime IRQ capability of the DP MSI IRQ. If irq_can_set_affinity() reports that affinity cannot be set, enable threaded NAPI for DP interrupt groups so datapath processing is not constrained by a single-CPU softirq context. On RB3Gen2, where IRQ affinity is unavailable in the effective IRQ path, EHT160 UDP downlink throughput improved from 802 Mbps to 2.58 Gbps after enabling threaded NAPI. Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00074-QCACOLSWPL_V1_TO_SILICONZ-1 Signed-off-by: Hangtian Zhu Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260519011627.713068-3-hangtian.zhu@oss.qualcomm.com [Fixed checkpatch "Missing a blank line after declarations"] Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/pci.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c index d9a22d6afbb0..ae8a9acee3f1 100644 --- a/drivers/net/wireless/ath/ath12k/pci.c +++ b/drivers/net/wireless/ath/ath12k/pci.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -537,6 +538,8 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab) int i, j, n, ret, num_vectors = 0; u32 user_base_data = 0, base_vector = 0, base_idx; struct ath12k_ext_irq_grp *irq_grp; + bool threaded_napi = false; + int irq; base_idx = ATH12K_PCI_IRQ_CE0_OFFSET + CE_COUNT_MAX; ret = ath12k_pci_get_user_msi_assignment(ab, "DP", @@ -546,6 +549,10 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab) if (ret < 0) return ret; + irq = ath12k_pci_get_msi_irq(ab->dev, base_vector); + if (irq >= 0) + threaded_napi = !irq_can_set_affinity(irq); + for (i = 0; i < ATH12K_EXT_IRQ_GRP_NUM_MAX; i++) { irq_grp = &ab->ext_irq_grp[i]; u32 num_irq = 0; @@ -560,6 +567,8 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab) netif_napi_add(irq_grp->napi_ndev, &irq_grp->napi, ath12k_pci_ext_grp_napi_poll); + if (threaded_napi) + netif_threaded_enable(irq_grp->napi_ndev); if (ab->hw_params->ring_mask->tx[i] || ab->hw_params->ring_mask->rx[i] || @@ -578,7 +587,8 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab) for (j = 0; j < irq_grp->num_irq; j++) { int irq_idx = irq_grp->irqs[j]; int vector = (i % num_vectors) + base_vector; - int irq = ath12k_pci_get_msi_irq(ab->dev, vector); + + irq = ath12k_pci_get_msi_irq(ab->dev, vector); ab->irq_num[irq_idx] = irq; From c29d1550e166598b9ee6d48c488eec0e79f98d52 Mon Sep 17 00:00:00 2001 From: Raj Kumar Bhagat Date: Tue, 23 Jun 2026 09:43:09 +0530 Subject: [PATCH 0024/1433] wifi: ath12k: Fix inconsistencies in struct qmi_elem_info initializers Currently, the struct qmi_elem_info initializers in qmi.c are inconsistent in how they align the assignments, with tabs being used in the majority of places but spaces being used in some places. In those places replace the spaces with tabs for consistency. Also fix incorrect and missing terminating records in the following qmi_elem_info initializers: - qmi_wlanfw_shadow_reg_cfg_s_v01_ei[] - qmi_wlanfw_mem_ready_ind_msg_v01_ei[] - qmi_wlanfw_fw_ready_ind_msg_v01_ei[] Tested-on: Compile tested only. Signed-off-by: Raj Kumar Bhagat Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260623-qmi-inconsistencies-v1-1-0fc17f2b8338@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/qmi.c | 144 ++++++++++++++------------ 1 file changed, 75 insertions(+), 69 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index fd762b5d7bb5..c176cc150f64 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -21,45 +21,45 @@ static const struct qmi_elem_info wlfw_host_mlo_chip_info_s_v01_ei[] = { { - .data_type = QMI_UNSIGNED_1_BYTE, - .elem_len = 1, - .elem_size = sizeof(u8), + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), .array_type = NO_ARRAY, - .tlv_type = 0, - .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, + .tlv_type = 0, + .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, chip_id), }, { - .data_type = QMI_UNSIGNED_1_BYTE, - .elem_len = 1, - .elem_size = sizeof(u8), + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), .array_type = NO_ARRAY, - .tlv_type = 0, - .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, + .tlv_type = 0, + .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, num_local_links), }, { - .data_type = QMI_UNSIGNED_1_BYTE, - .elem_len = QMI_WLFW_MAX_NUM_MLO_LINKS_PER_CHIP_V01, - .elem_size = sizeof(u8), - .array_type = STATIC_ARRAY, - .tlv_type = 0, - .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = QMI_WLFW_MAX_NUM_MLO_LINKS_PER_CHIP_V01, + .elem_size = sizeof(u8), + .array_type = STATIC_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, hw_link_id), }, { - .data_type = QMI_UNSIGNED_1_BYTE, - .elem_len = QMI_WLFW_MAX_NUM_MLO_LINKS_PER_CHIP_V01, - .elem_size = sizeof(u8), - .array_type = STATIC_ARRAY, - .tlv_type = 0, - .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = QMI_WLFW_MAX_NUM_MLO_LINKS_PER_CHIP_V01, + .elem_size = sizeof(u8), + .array_type = STATIC_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, valid_mlo_link_id), }, { - .data_type = QMI_EOTI, + .data_type = QMI_EOTI, .array_type = NO_ARRAY, - .tlv_type = QMI_COMMON_TLV_TYPE, + .tlv_type = QMI_COMMON_TLV_TYPE, }, }; @@ -585,21 +585,21 @@ static const struct qmi_elem_info qmi_wlanfw_phy_cap_resp_msg_v01_ei[] = { board_id), }, { - .data_type = QMI_OPT_FLAG, - .elem_len = 1, - .elem_size = sizeof(u8), - .array_type = NO_ARRAY, - .tlv_type = 0x13, - .offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01, + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01, single_chip_mlo_support_valid), }, { - .data_type = QMI_UNSIGNED_1_BYTE, - .elem_len = 1, - .elem_size = sizeof(u8), - .array_type = NO_ARRAY, - .tlv_type = 0x13, - .offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01, + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01, single_chip_mlo_support), }, { @@ -1625,42 +1625,45 @@ static const struct qmi_elem_info qmi_wlanfw_m3_info_resp_msg_v01_ei[] = { static const struct qmi_elem_info qmi_wlanfw_aux_uc_info_req_msg_v01_ei[] = { { - .data_type = QMI_UNSIGNED_8_BYTE, - .elem_len = 1, - .elem_size = sizeof(u64), - .array_type = NO_ARRAY, - .tlv_type = 0x01, - .offset = offsetof(struct qmi_wlanfw_aux_uc_info_req_msg_v01, addr), + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(u64), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct qmi_wlanfw_aux_uc_info_req_msg_v01, + addr), }, { - .data_type = QMI_UNSIGNED_4_BYTE, - .elem_len = 1, - .elem_size = sizeof(u32), - .array_type = NO_ARRAY, - .tlv_type = 0x02, - .offset = offsetof(struct qmi_wlanfw_aux_uc_info_req_msg_v01, size), + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct qmi_wlanfw_aux_uc_info_req_msg_v01, + size), }, { - .data_type = QMI_EOTI, - .array_type = NO_ARRAY, - .tlv_type = QMI_COMMON_TLV_TYPE, + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, }, }; static const struct qmi_elem_info qmi_wlanfw_aux_uc_info_resp_msg_v01_ei[] = { { - .data_type = QMI_STRUCT, - .elem_len = 1, - .elem_size = sizeof(struct qmi_response_type_v01), - .array_type = NO_ARRAY, - .tlv_type = 0x02, - .offset = offsetof(struct qmi_wlanfw_aux_uc_info_resp_msg_v01, resp), - .ei_array = qmi_response_type_v01_ei, + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct qmi_wlanfw_aux_uc_info_resp_msg_v01, + resp), + .ei_array = qmi_response_type_v01_ei, }, { - .data_type = QMI_EOTI, - .array_type = NO_ARRAY, - .tlv_type = QMI_COMMON_TLV_TYPE, + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, }, }; @@ -1772,7 +1775,8 @@ static const struct qmi_elem_info qmi_wlanfw_shadow_reg_cfg_s_v01_ei[] = { }, { .data_type = QMI_EOTI, - .array_type = QMI_COMMON_TLV_TYPE, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, }, }; @@ -1925,7 +1929,7 @@ static const struct qmi_elem_info qmi_wlanfw_wlan_cfg_req_msg_v01_ei[] = { .data_type = QMI_OPT_FLAG, .elem_len = 1, .elem_size = sizeof(u8), - .array_type = NO_ARRAY, + .array_type = NO_ARRAY, .tlv_type = 0x13, .offset = offsetof(struct qmi_wlanfw_wlan_cfg_req_msg_v01, shadow_reg_valid), @@ -1934,7 +1938,7 @@ static const struct qmi_elem_info qmi_wlanfw_wlan_cfg_req_msg_v01_ei[] = { .data_type = QMI_DATA_LEN, .elem_len = 1, .elem_size = sizeof(u8), - .array_type = NO_ARRAY, + .array_type = NO_ARRAY, .tlv_type = 0x13, .offset = offsetof(struct qmi_wlanfw_wlan_cfg_req_msg_v01, shadow_reg_len), @@ -1943,7 +1947,7 @@ static const struct qmi_elem_info qmi_wlanfw_wlan_cfg_req_msg_v01_ei[] = { .data_type = QMI_STRUCT, .elem_len = QMI_WLANFW_MAX_NUM_SHADOW_REG_V01, .elem_size = sizeof(struct qmi_wlanfw_shadow_reg_cfg_s_v01), - .array_type = VAR_LEN_ARRAY, + .array_type = VAR_LEN_ARRAY, .tlv_type = 0x13, .offset = offsetof(struct qmi_wlanfw_wlan_cfg_req_msg_v01, shadow_reg), @@ -2003,15 +2007,17 @@ static const struct qmi_elem_info qmi_wlanfw_wlan_cfg_resp_msg_v01_ei[] = { static const struct qmi_elem_info qmi_wlanfw_mem_ready_ind_msg_v01_ei[] = { { - .data_type = QMI_EOTI, - .array_type = NO_ARRAY, + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, }, }; static const struct qmi_elem_info qmi_wlanfw_fw_ready_ind_msg_v01_ei[] = { { - .data_type = QMI_EOTI, - .array_type = NO_ARRAY, + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, }, }; From 26d529b99861707ed0a4c626184b9399bedca808 Mon Sep 17 00:00:00 2001 From: Raj Kumar Bhagat Date: Tue, 23 Jun 2026 09:34:17 +0530 Subject: [PATCH 0025/1433] wifi: ath12k: use %u for unsigned variables in QMI debug logs Replace incorrect %d format specifiers with %u for unsigned variables in qmi.c debug messages. Also add missing trailing '\n' in log messages to ensure proper termination. No functional change intended. Tested-on: Compile tested only. Signed-off-by: Raj Kumar Bhagat Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260623-qmi-debug-log-v1-1-79471aa8b898@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/qmi.c | 70 +++++++++++++-------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index c176cc150f64..1f3efcc16ac3 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -2100,14 +2100,14 @@ static int ath12k_host_cap_parse_mlo(struct ath12k_base *ab, if (!ag->mlo_capable) { ath12k_dbg(ab, ATH12K_DBG_QMI, - "MLO is disabled hence skip QMI MLO cap"); + "MLO is disabled hence skip QMI MLO cap\n"); return 0; } if (!ab->qmi.num_radios || ab->qmi.num_radios == U8_MAX) { ag->mlo_capable = false; ath12k_dbg(ab, ATH12K_DBG_QMI, - "skip QMI MLO cap due to invalid num_radio %d\n", + "skip QMI MLO cap due to invalid num_radio %u\n", ab->qmi.num_radios); return 0; } @@ -2131,7 +2131,7 @@ static int ath12k_host_cap_parse_mlo(struct ath12k_base *ab, req->mlo_num_chips_valid = 1; req->mlo_num_chips = ag->num_devices; - ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo capability advertisement device_id %d group_id %d num_devices %d", + ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo capability advertisement device_id %u group_id %u num_devices %u\n", req->mlo_chip_id, req->mlo_group_id, req->mlo_num_chips); mutex_lock(&ag->mutex); @@ -2152,14 +2152,14 @@ static int ath12k_host_cap_parse_mlo(struct ath12k_base *ab, info->chip_id = partner_ab->device_id; info->num_local_links = partner_ab->qmi.num_radios; - ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo device id %d num_link %d\n", + ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo device id %u num_link %u\n", info->chip_id, info->num_local_links); for (j = 0; j < info->num_local_links; j++) { info->hw_link_id[j] = partner_ab->wsi_info.hw_link_id_base + j; info->valid_mlo_link_id[j] = 1; - ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo hw_link_id %d\n", + ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo hw_link_id %u\n", info->hw_link_id[j]); hw_link_id++; @@ -2274,7 +2274,7 @@ int ath12k_qmi_host_cap_send(struct ath12k_base *ab) goto out; if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "Host capability request failed, result: %d, err: %d\n", + ath12k_warn(ab, "Host capability request failed, result: %u, err: %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -2326,7 +2326,7 @@ static void ath12k_qmi_phy_cap_send(struct ath12k_base *ab) ab->qmi.num_radios = resp.num_phy; ath12k_dbg(ab, ATH12K_DBG_QMI, - "phy capability resp valid %d single_chip_mlo_support %d valid %d num_phy %d valid %d board_id %d\n", + "phy capability resp valid %u single_chip_mlo_support %u valid %u num_phy %u valid %u board_id %u\n", resp.single_chip_mlo_support_valid, resp.single_chip_mlo_support, resp.num_phy_valid, resp.num_phy, resp.board_id_valid, resp.board_id); @@ -2338,7 +2338,7 @@ static void ath12k_qmi_phy_cap_send(struct ath12k_base *ab) ab->qmi.num_radios = ab->hw_params->def_num_link; ath12k_dbg(ab, ATH12K_DBG_QMI, - "no valid response from PHY capability, choose default num_phy %d\n", + "no valid response from PHY capability, choose default num_phy %u\n", ab->qmi.num_radios); } @@ -2399,7 +2399,7 @@ static int ath12k_qmi_fw_ind_register_send(struct ath12k_base *ab) } if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "FW Ind register request failed, result: %d, err: %d\n", + ath12k_warn(ab, "FW Ind register request failed, result: %u, err: %u\n", resp->resp.result, resp->resp.error); ret = -EINVAL; goto out; @@ -2434,7 +2434,7 @@ int ath12k_qmi_respond_fw_mem_request(struct ath12k_base *ab) if (!test_bit(ATH12K_FLAG_FIXED_MEM_REGION, &ab->dev_flags) && ab->qmi.target_mem_delayed) { delayed = true; - ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi delays mem_request %d\n", + ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi delays mem_request %u\n", ab->qmi.mem_seg_count); } else { delayed = false; @@ -2480,7 +2480,7 @@ int ath12k_qmi_respond_fw_mem_request(struct ath12k_base *ab) if (delayed && resp.resp.error == 0) goto out; - ath12k_warn(ab, "Respond mem req failed, result: %d, err: %d\n", + ath12k_warn(ab, "Respond mem req failed, result: %u, err: %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -2612,13 +2612,13 @@ static int ath12k_qmi_alloc_chunk(struct ath12k_base *ab, if (chunk->size > ATH12K_QMI_MAX_CHUNK_SIZE) { ab->qmi.target_mem_delayed = true; ath12k_warn(ab, - "qmi dma allocation failed (%d B type %u), will try later with small size\n", + "qmi dma allocation failed (%u B type %u), will try later with small size\n", chunk->size, chunk->type); ath12k_qmi_free_target_mem_chunk(ab); return -EAGAIN; } - ath12k_warn(ab, "memory allocation failure for %u size: %d\n", + ath12k_warn(ab, "memory allocation failure for %u size: %u\n", chunk->type, chunk->size); return -ENOMEM; } @@ -2665,7 +2665,7 @@ static int ath12k_qmi_alloc_target_mem_chunk(struct ath12k_base *ab) mlo_size += chunk->size; if (ag->mlo_mem.mlo_mem_size && mlo_size > ag->mlo_mem.mlo_mem_size) { - ath12k_err(ab, "QMI MLO memory allocation failure, requested size %d is more than allocated size %d", + ath12k_err(ab, "QMI MLO memory allocation failure, requested size %d is more than allocated size %d\n", mlo_size, ag->mlo_mem.mlo_mem_size); ret = -EINVAL; goto err; @@ -2674,7 +2674,7 @@ static int ath12k_qmi_alloc_target_mem_chunk(struct ath12k_base *ab) mlo_chunk = &ag->mlo_mem.chunk[mlo_idx]; if (mlo_chunk->paddr) { if (chunk->size != mlo_chunk->size) { - ath12k_err(ab, "QMI MLO chunk memory allocation failure for index %d, requested size %d is more than allocated size %d", + ath12k_err(ab, "QMI MLO chunk memory allocation failure for index %d, requested size %u is more than allocated size %u\n", mlo_idx, chunk->size, mlo_chunk->size); ret = -EINVAL; goto err; @@ -2705,7 +2705,7 @@ static int ath12k_qmi_alloc_target_mem_chunk(struct ath12k_base *ab) if (!ag->mlo_mem.mlo_mem_size) { ag->mlo_mem.mlo_mem_size = mlo_size; } else if (ag->mlo_mem.mlo_mem_size != mlo_size) { - ath12k_err(ab, "QMI MLO memory size error, expected size is %d but requested size is %d", + ath12k_err(ab, "QMI MLO memory size error, expected size is %d but requested size is %d\n", ag->mlo_mem.mlo_mem_size, mlo_size); ret = -EINVAL; goto err; @@ -2890,7 +2890,7 @@ int ath12k_qmi_request_target_cap(struct ath12k_base *ab) } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "qmi targetcap req failed, result: %d, err: %d\n", + ath12k_warn(ab, "qmi targetcap req failed, result: %u, err: %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -2942,7 +2942,7 @@ int ath12k_qmi_request_target_cap(struct ath12k_base *ab) ab->qmi.target.chip_id, ab->qmi.target.chip_family, ab->qmi.target.board_id, ab->qmi.target.soc_id); - ath12k_info(ab, "fw_version 0x%x fw_build_timestamp %s fw_build_id %s", + ath12k_info(ab, "fw_version 0x%x fw_build_timestamp %s fw_build_id %s\n", ab->qmi.target.fw_version, ab->qmi.target.fw_build_timestamp, ab->qmi.target.fw_build_id); @@ -3012,7 +3012,7 @@ static int ath12k_qmi_load_file_target_mem(struct ath12k_base *ab, if (ret < 0) goto out; - ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi bdf download req fixed addr type %d\n", + ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi bdf download req fixed addr type %u\n", type); ret = qmi_send_request(&ab->qmi.handle, NULL, &txn, @@ -3029,7 +3029,7 @@ static int ath12k_qmi_load_file_target_mem(struct ath12k_base *ab, goto out; if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "qmi BDF download failed, result: %d, err: %d\n", + ath12k_warn(ab, "qmi BDF download failed, result: %u, err: %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -3042,7 +3042,7 @@ static int ath12k_qmi_load_file_target_mem(struct ath12k_base *ab, temp += req->data_len; req->seg_id++; ath12k_dbg(ab, ATH12K_DBG_QMI, - "qmi bdf download request remaining %i\n", + "qmi bdf download request remaining %u\n", remaining); } } @@ -3133,7 +3133,7 @@ int ath12k_qmi_load_bdf_qmi(struct ath12k_base *ab, release_firmware(fw_entry); return ret; default: - ath12k_warn(ab, "unknown file type for load %d", type); + ath12k_warn(ab, "unknown file type for load %d\n", type); goto out; } @@ -3245,7 +3245,7 @@ int ath12k_qmi_wlanfw_m3_info_send(struct ath12k_base *ab) if (ab->hw_params->fw.m3_loader == ath12k_m3_fw_loader_driver) { ret = ath12k_qmi_m3_load(ab); if (ret) { - ath12k_err(ab, "failed to load m3 firmware: %d", ret); + ath12k_err(ab, "failed to load m3 firmware: %d\n", ret); return ret; } req.addr = m3_mem->paddr; @@ -3275,7 +3275,7 @@ int ath12k_qmi_wlanfw_m3_info_send(struct ath12k_base *ab) } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "qmi M3 info request failed, result: %d, err: %d\n", + ath12k_warn(ab, "qmi M3 info request failed, result: %u, err: %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -3370,7 +3370,7 @@ int ath12k_qmi_wlanfw_aux_uc_info_send(struct ath12k_base *ab) ret = ath12k_qmi_aux_uc_load(ab); if (ret) { - ath12k_err(ab, "failed to load aux_uc firmware: %d", ret); + ath12k_err(ab, "failed to load aux_uc firmware: %d\n", ret); return ret; } @@ -3400,7 +3400,7 @@ int ath12k_qmi_wlanfw_aux_uc_info_send(struct ath12k_base *ab) } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "qmi AUX_UC info request failed, result: %d, err: %d\n", + ath12k_warn(ab, "qmi AUX_UC info request failed, result: %u, err: %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -3432,7 +3432,7 @@ static int ath12k_qmi_wlanfw_mode_send(struct ath12k_base *ab, qmi_wlanfw_wlan_mode_req_msg_v01_ei, &req); if (ret < 0) { qmi_txn_cancel(&txn); - ath12k_warn(ab, "qmi failed to send mode request, mode: %d, err = %d\n", + ath12k_warn(ab, "qmi failed to send mode request, mode: %u, err = %d\n", mode, ret); goto out; } @@ -3443,13 +3443,13 @@ static int ath12k_qmi_wlanfw_mode_send(struct ath12k_base *ab, ath12k_warn(ab, "WLFW service is dis-connected\n"); return 0; } - ath12k_warn(ab, "qmi failed set mode request, mode: %d, err = %d\n", + ath12k_warn(ab, "qmi failed set mode request, mode: %u, err = %d\n", mode, ret); goto out; } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "Mode request failed, mode: %d, result: %d err: %d\n", + ath12k_warn(ab, "Mode request failed, mode: %u, result: %u err: %u\n", mode, resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -3542,7 +3542,7 @@ static int ath12k_qmi_wlanfw_wlan_cfg_send(struct ath12k_base *ab) } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "qmi wlan config request failed, result: %d, err: %d\n", + ath12k_warn(ab, "qmi wlan config request failed, result: %u, err: %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -3586,7 +3586,7 @@ static int ath12k_qmi_wlanfw_wlan_ini_send(struct ath12k_base *ab) } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "QMI wlan ini response failure: %d %d\n", + ath12k_warn(ab, "QMI wlan ini response failure: %u %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -3669,7 +3669,7 @@ void ath12k_qmi_trigger_host_cap(struct ath12k_base *ab) spin_unlock(&qmi->event_lock); - ath12k_dbg(ab, ATH12K_DBG_QMI, "trigger host cap for device id %d\n", + ath12k_dbg(ab, ATH12K_DBG_QMI, "trigger host cap for device id %u\n", ab->device_id); ath12k_qmi_driver_event_post(qmi, ATH12K_QMI_EVENT_HOST_CAP, NULL); @@ -3839,7 +3839,7 @@ static void ath12k_qmi_msg_mem_request_cb(struct qmi_handle *qmi_hdl, for (i = 0; i < qmi->mem_seg_count ; i++) { ab->qmi.target_mem[i].type = msg->mem_seg[i].type; ab->qmi.target_mem[i].size = msg->mem_seg[i].size; - ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi mem seg type %d size %d\n", + ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi mem seg type %d size %u\n", msg->mem_seg[i].type, msg->mem_seg[i].size); } @@ -3960,7 +3960,7 @@ static int ath12k_qmi_event_host_cap(struct ath12k_qmi *qmi) ret = ath12k_qmi_host_cap_send(ab); if (ret < 0) { - ath12k_warn(ab, "failed to send qmi host cap for device id %d: %d\n", + ath12k_warn(ab, "failed to send qmi host cap for device id %u: %d\n", ab->device_id, ret); return ret; } @@ -4030,7 +4030,7 @@ static void ath12k_qmi_driver_event_work(struct work_struct *work) set_bit(ATH12K_FLAG_QMI_FAIL, &ab->dev_flags); break; default: - ath12k_warn(ab, "invalid event type: %d", event->type); + ath12k_warn(ab, "invalid event type: %d\n", event->type); break; } From 007b638ed7242daeea7c1078e8f732d127c790f3 Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Tue, 23 Jun 2026 09:21:04 +0530 Subject: [PATCH 0026/1433] wifi: ath12k: remove unused QMI definitions The driver contains several unused QMI definitions such as response length macros, message IDs, firmware segment length definitions, and CALDB address size definitions. Remove these unused definitions as they are not referenced anywhere in the driver. No functional change intended. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Signed-off-by: Aaradhana Sahu Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260623035104.3765404-1-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/qmi.h | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/qmi.h b/drivers/net/wireless/ath/ath12k/qmi.h index 2a63e214eb42..80a9b42a2548 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.h +++ b/drivers/net/wireless/ath/ath12k/qmi.h @@ -13,7 +13,6 @@ #define ATH12K_HOST_VERSION_STRING "WIN" #define ATH12K_QMI_WLANFW_TIMEOUT_MS 10000 #define ATH12K_QMI_MAX_BDF_FILE_NAME_SIZE 64 -#define ATH12K_QMI_CALDB_ADDRESS 0x4BA00000 #define ATH12K_QMI_WLANFW_MAX_BUILD_ID_LEN_V01 128 #define ATH12K_QMI_WLFW_SERVICE_VERS_V01 0x01 #define ATH12K_QMI_WLFW_SERVICE_INS_ID_V01 0x02 @@ -24,9 +23,7 @@ #define ATH12K_QMI_WLANFW_MAX_TIMESTAMP_LEN_V01 32 #define ATH12K_QMI_RESP_LEN_MAX 8192 #define ATH12K_QMI_WLANFW_MAX_NUM_MEM_SEG_V01 52 -#define ATH12K_QMI_CALDB_SIZE 0x480000 #define ATH12K_QMI_BDF_EXT_STR_LENGTH 0x20 -#define ATH12K_QMI_FW_MEM_REQ_SEGMENT_CNT 3 #define ATH12K_QMI_WLFW_MAX_DEV_MEM_NUM_V01 4 #define ATH12K_QMI_DEVMEM_CMEM_INDEX 0 @@ -160,8 +157,6 @@ struct ath12k_qmi { #define QMI_WLANFW_HOST_CAP_REQ_MSG_V01_MAX_LEN 261 #define QMI_WLANFW_HOST_CAP_REQ_V01 0x0034 -#define QMI_WLANFW_HOST_CAP_RESP_MSG_V01_MAX_LEN 7 -#define QMI_WLFW_HOST_CAP_RESP_V01 0x0034 #define QMI_WLFW_MAX_NUM_GPIO_V01 32 #define QMI_WLANFW_MAX_PLATFORM_NAME_LEN_V01 64 #define QMI_WLANFW_MAX_HOST_DDR_RANGE_SIZE_V01 3 @@ -267,8 +262,6 @@ struct qmi_wlanfw_host_cap_resp_msg_v01 { #define QMI_WLANFW_PHY_CAP_REQ_MSG_V01_MAX_LEN 0 #define QMI_WLANFW_PHY_CAP_REQ_V01 0x0057 -#define QMI_WLANFW_PHY_CAP_RESP_MSG_V01_MAX_LEN 18 -#define QMI_WLANFW_PHY_CAP_RESP_V01 0x0057 struct qmi_wlanfw_phy_cap_req_msg_v01 { }; @@ -285,8 +278,6 @@ struct qmi_wlanfw_phy_cap_resp_msg_v01 { #define QMI_WLANFW_IND_REGISTER_REQ_MSG_V01_MAX_LEN 54 #define QMI_WLANFW_IND_REGISTER_REQ_V01 0x0020 -#define QMI_WLANFW_IND_REGISTER_RESP_MSG_V01_MAX_LEN 18 -#define QMI_WLANFW_IND_REGISTER_RESP_V01 0x0020 #define QMI_WLANFW_CLIENT_ID 0x4b4e454c struct qmi_wlanfw_ind_register_req_msg_v01 { @@ -322,12 +313,8 @@ struct qmi_wlanfw_ind_register_resp_msg_v01 { u64 fw_status; }; -#define QMI_WLANFW_REQUEST_MEM_IND_MSG_V01_MAX_LEN 1824 #define QMI_WLANFW_RESPOND_MEM_REQ_MSG_V01_MAX_LEN 888 -#define QMI_WLANFW_RESPOND_MEM_RESP_MSG_V01_MAX_LEN 7 -#define QMI_WLANFW_REQUEST_MEM_IND_V01 0x0035 #define QMI_WLANFW_RESPOND_MEM_REQ_V01 0x0036 -#define QMI_WLANFW_RESPOND_MEM_RESP_V01 0x0036 #define QMI_WLANFW_MAX_NUM_MEM_CFG_V01 2 #define QMI_WLANFW_MAX_STR_LEN_V01 16 @@ -385,9 +372,7 @@ struct qmi_wlanfw_fw_ready_ind_msg_v01 { }; #define QMI_WLANFW_CAP_REQ_MSG_V01_MAX_LEN 0 -#define QMI_WLANFW_CAP_RESP_MSG_V01_MAX_LEN 207 #define QMI_WLANFW_CAP_REQ_V01 0x0024 -#define QMI_WLANFW_CAP_RESP_V01 0x0024 enum qmi_wlanfw_pipedir_enum_v01 { QMI_WLFW_PIPEDIR_NONE_V01 = 0, @@ -500,8 +485,6 @@ struct qmi_wlanfw_cap_req_msg_v01 { }; #define QMI_WLANFW_BDF_DOWNLOAD_REQ_MSG_V01_MAX_LEN 6182 -#define QMI_WLANFW_BDF_DOWNLOAD_RESP_MSG_V01_MAX_LEN 7 -#define QMI_WLANFW_BDF_DOWNLOAD_RESP_V01 0x0025 #define QMI_WLANFW_BDF_DOWNLOAD_REQ_V01 0x0025 /* TODO: Need to check with MCL and FW team that data can be pointer and * can be last element in structure @@ -529,8 +512,6 @@ struct qmi_wlanfw_bdf_download_resp_msg_v01 { }; #define QMI_WLANFW_M3_INFO_REQ_MSG_V01_MAX_MSG_LEN 18 -#define QMI_WLANFW_M3_INFO_RESP_MSG_V01_MAX_MSG_LEN 7 -#define QMI_WLANFW_M3_INFO_RESP_V01 0x003C #define QMI_WLANFW_M3_INFO_REQ_V01 0x003C struct qmi_wlanfw_m3_info_req_msg_v01 { @@ -543,7 +524,6 @@ struct qmi_wlanfw_m3_info_resp_msg_v01 { }; #define QMI_WLANFW_AUX_UC_INFO_REQ_MSG_V01_MAX_MSG_LEN 18 -#define QMI_WLANFW_AUX_UC_INFO_RESP_MSG_V01_MAX_MSG_LEN 7 #define QMI_WLANFW_AUX_UC_INFO_REQ_V01 0x005A struct qmi_wlanfw_aux_uc_info_req_msg_v01 { @@ -556,13 +536,9 @@ struct qmi_wlanfw_aux_uc_info_resp_msg_v01 { }; #define QMI_WLANFW_WLAN_MODE_REQ_MSG_V01_MAX_LEN 11 -#define QMI_WLANFW_WLAN_MODE_RESP_MSG_V01_MAX_LEN 7 #define QMI_WLANFW_WLAN_CFG_REQ_MSG_V01_MAX_LEN 803 -#define QMI_WLANFW_WLAN_CFG_RESP_MSG_V01_MAX_LEN 7 #define QMI_WLANFW_WLAN_MODE_REQ_V01 0x0022 -#define QMI_WLANFW_WLAN_MODE_RESP_V01 0x0022 #define QMI_WLANFW_WLAN_CFG_REQ_V01 0x0023 -#define QMI_WLANFW_WLAN_CFG_RESP_V01 0x0023 #define QMI_WLANFW_MAX_STR_LEN_V01 16 #define QMI_WLANFW_MAX_NUM_CE_V01 12 #define QMI_WLANFW_MAX_NUM_SVC_V01 24 @@ -605,9 +581,7 @@ struct qmi_wlanfw_wlan_cfg_resp_msg_v01 { }; #define ATH12K_QMI_WLANFW_WLAN_INI_REQ_V01 0x002F -#define ATH12K_QMI_WLANFW_WLAN_INI_RESP_V01 0x002F #define QMI_WLANFW_WLAN_INI_REQ_MSG_V01_MAX_LEN 7 -#define QMI_WLANFW_WLAN_INI_RESP_MSG_V01_MAX_LEN 7 struct qmi_wlanfw_wlan_ini_req_msg_v01 { /* Must be set to true if enable_fwlog is being passed */ From eaf478b3ea68e1b5df659acd24c4df5850e12325 Mon Sep 17 00:00:00 2001 From: Nicolas Escande Date: Tue, 23 Jun 2026 17:16:13 +0200 Subject: [PATCH 0027/1433] wifi: ath12k: avoid setting 320MHz support on non 6GHz band On a split phy qcn9274 (2.4GHz + 5GHz low), "iw phy" reports 320MHz related features on the 5GHz band while it should not: Wiphy phy1 [...] Band 2: [...] EHT Iftypes: managed [...] EHT PHY Capabilities: (0xe2ffdbe018778000): 320MHz in 6GHz Supported [...] Beamformee SS (320MHz): 7 [...] Number Of Sounding Dimensions (320MHz): 3 [...] EHT MCS/NSS: (0x22222222222222222200000000): This is also reflected in the beacons sent by a mesh interface started on that band. They erroneously advertise 320MHz support too. This should not happen as IEEE Std 802.11-2024, subclause 9.4.2.323.3 says we should not set the 320MHz related fields when not operating on a 6GHz band. For example it says about Bit 0 "Support For 320 MHz In 6 GHz" "Reserved if the EHT Capabilities element is indicating capabilities for the 2.4 GHz or 5 GHz bands." Fix this by clearing the related bits when converting from WMI eht phy capabilities to mac80211 phy capabilities, for bands other than 6GHz. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.3.1-00218-QCAHKSWPL_SILICONZ-1 Signed-off-by: Nicolas Escande Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260623151613.72113-1-nico.escande@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index 84a31b953db8..e7689ee3e701 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -5154,6 +5154,7 @@ static void ath12k_wmi_eht_caps_parse(struct ath12k_pdev *pdev, u32 band, __le32 cap_info_internal) { struct ath12k_band_cap *cap_band = &pdev->cap.band[band]; + u8 *phy_cap = (u8 *)&cap_band->eht_cap_phy_info[0]; u32 support_320mhz; u8 i; @@ -5167,8 +5168,22 @@ static void ath12k_wmi_eht_caps_parse(struct ath12k_pdev *pdev, u32 band, for (i = 0; i < WMI_MAX_EHTCAP_PHY_SIZE; i++) cap_band->eht_cap_phy_info[i] = le32_to_cpu(cap_phy_info[i]); - if (band == NL80211_BAND_6GHZ) + if (band == NL80211_BAND_6GHZ) { cap_band->eht_cap_phy_info[0] |= support_320mhz; + } else { + /* + * Firmware may report 6 GHz/320 MHz specific capabilities for + * non-6 GHz bands, so explicitly clear them. + */ + phy_cap[0] &= ~IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ; + phy_cap[1] &= ~IEEE80211_EHT_PHY_CAP1_BEAMFORMEE_SS_320MHZ_MASK; + phy_cap[2] &= ~IEEE80211_EHT_PHY_CAP2_SOUNDING_DIM_320MHZ_MASK; + phy_cap[3] &= ~IEEE80211_EHT_PHY_CAP3_SOUNDING_DIM_320MHZ_MASK; + phy_cap[6] &= ~IEEE80211_EHT_PHY_CAP6_MCS15_SUPP_320MHZ; + phy_cap[6] &= ~IEEE80211_EHT_PHY_CAP6_EHT_DUP_6GHZ_SUPP; + phy_cap[7] &= ~IEEE80211_EHT_PHY_CAP7_NON_OFDMA_UL_MU_MIMO_320MHZ; + phy_cap[7] &= ~IEEE80211_EHT_PHY_CAP7_MU_BEAMFORMER_320MHZ; + } cap_band->eht_mcs_20_only = le32_to_cpu(supp_mcs[0]); cap_band->eht_mcs_80 = le32_to_cpu(supp_mcs[1]); From d762bbc08ca70a1985c9f9420c4bf67e0ba0e9be Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Sat, 9 May 2026 10:58:15 +0800 Subject: [PATCH 0028/1433] wifi: ath12k: fix TLV32 length mask HAL_TLV_HDR_LEN was using the wrong bitmask; fix it to cover bits [21:10]. Also drop HAL_SRNG_TLV_HDR_{TAG,LEN} and use the generic TLV header bit definitions for TLV32/TLV64 encode/decode to avoid redundant macros. Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00068-QCACOLSWPL_V1_TO_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Signed-off-by: Miaoqing Pan Reviewed-by: Vasanthakumar Thiagarajan Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260509025819.1641630-2-miaoqing.pan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/hal.c | 8 ++++---- drivers/net/wireless/ath/ath12k/hal.h | 5 +---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/hal.c b/drivers/net/wireless/ath/ath12k/hal.c index a164563fff28..f03817b2fbc5 100644 --- a/drivers/net/wireless/ath/ath12k/hal.c +++ b/drivers/net/wireless/ath/ath12k/hal.c @@ -828,8 +828,8 @@ void *ath12k_hal_encode_tlv64_hdr(void *tlv, u64 tag, u64 len) { struct hal_tlv_64_hdr *tlv64 = tlv; - tlv64->tl = le64_encode_bits(tag, HAL_TLV_HDR_TAG) | - le64_encode_bits(len, HAL_TLV_HDR_LEN); + tlv64->tl = le64_encode_bits(tag, HAL_TLV_64_HDR_TAG) | + le64_encode_bits(len, HAL_TLV_64_HDR_LEN); return tlv64->value; } @@ -851,7 +851,7 @@ u16 ath12k_hal_decode_tlv64_hdr(void *tlv, void **desc) struct hal_tlv_64_hdr *tlv64 = tlv; u16 tag; - tag = le64_get_bits(tlv64->tl, HAL_SRNG_TLV_HDR_TAG); + tag = le64_get_bits(tlv64->tl, HAL_TLV_64_HDR_TAG); *desc = tlv64->value; return tag; @@ -863,7 +863,7 @@ u16 ath12k_hal_decode_tlv32_hdr(void *tlv, void **desc) struct hal_tlv_hdr *tlv32 = tlv; u16 tag; - tag = le32_get_bits(tlv32->tl, HAL_SRNG_TLV_HDR_TAG); + tag = le32_get_bits(tlv32->tl, HAL_TLV_HDR_TAG); *desc = tlv32->value; return tag; diff --git a/drivers/net/wireless/ath/ath12k/hal.h b/drivers/net/wireless/ath/ath12k/hal.h index 21c551d8b248..3ee49d93e24a 100644 --- a/drivers/net/wireless/ath/ath12k/hal.h +++ b/drivers/net/wireless/ath/ath12k/hal.h @@ -1444,7 +1444,7 @@ struct hal_ops { }; #define HAL_TLV_HDR_TAG GENMASK(9, 1) -#define HAL_TLV_HDR_LEN GENMASK(25, 10) +#define HAL_TLV_HDR_LEN GENMASK(21, 10) #define HAL_TLV_USR_ID GENMASK(31, 26) #define HAL_TLV_ALIGN 4 @@ -1464,9 +1464,6 @@ struct hal_tlv_64_hdr { u8 value[]; } __packed; -#define HAL_SRNG_TLV_HDR_TAG GENMASK(9, 1) -#define HAL_SRNG_TLV_HDR_LEN GENMASK(25, 10) - dma_addr_t ath12k_hal_srng_get_tp_addr(struct ath12k_base *ab, struct hal_srng *srng); dma_addr_t ath12k_hal_srng_get_hp_addr(struct ath12k_base *ab, From e264a3dbe866870ed21ef83c67ad956a45391859 Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Sat, 9 May 2026 10:58:16 +0800 Subject: [PATCH 0029/1433] wifi: ath12k: refactor HAL TLV32/64 decode helpers Change TLV decode helpers to return the TLV value pointer and optionally decode tag/len/usrid via out parameters. This allows reusing the helpers for DP monitor RX status header TLV parsing and avoids duplicated header decoding in callers. No functional change intended. Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00068-QCACOLSWPL_V1_TO_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Miaoqing Pan Reviewed-by: Vasanthakumar Thiagarajan Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260509025819.1641630-3-miaoqing.pan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/hal.c | 26 ++++++++++++------- drivers/net/wireless/ath/ath12k/hal.h | 4 +-- .../wireless/ath/ath12k/wifi7/hal_qcc2072.c | 2 +- .../wireless/ath/ath12k/wifi7/hal_qcn9274.c | 11 +++++++- .../wireless/ath/ath12k/wifi7/hal_wcn7850.c | 11 +++++++- 5 files changed, 39 insertions(+), 15 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/hal.c b/drivers/net/wireless/ath/ath12k/hal.c index f03817b2fbc5..d940f83cd92f 100644 --- a/drivers/net/wireless/ath/ath12k/hal.c +++ b/drivers/net/wireless/ath/ath12k/hal.c @@ -846,26 +846,32 @@ void *ath12k_hal_encode_tlv32_hdr(void *tlv, u64 tag, u64 len) } EXPORT_SYMBOL(ath12k_hal_encode_tlv32_hdr); -u16 ath12k_hal_decode_tlv64_hdr(void *tlv, void **desc) +void *ath12k_hal_decode_tlv64_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid) { struct hal_tlv_64_hdr *tlv64 = tlv; - u16 tag; - tag = le64_get_bits(tlv64->tl, HAL_TLV_64_HDR_TAG); - *desc = tlv64->value; + if (tag) + *tag = le64_get_bits(tlv64->tl, HAL_TLV_64_HDR_TAG); + if (len) + *len = le64_get_bits(tlv64->tl, HAL_TLV_64_HDR_LEN); + if (usrid) + *usrid = le64_get_bits(tlv64->tl, HAL_TLV_64_USR_ID); - return tag; + return tlv64->value; } EXPORT_SYMBOL(ath12k_hal_decode_tlv64_hdr); -u16 ath12k_hal_decode_tlv32_hdr(void *tlv, void **desc) +void *ath12k_hal_decode_tlv32_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid) { struct hal_tlv_hdr *tlv32 = tlv; - u16 tag; - tag = le32_get_bits(tlv32->tl, HAL_TLV_HDR_TAG); - *desc = tlv32->value; + if (tag) + *tag = le32_get_bits(tlv32->tl, HAL_TLV_HDR_TAG); + if (len) + *len = le32_get_bits(tlv32->tl, HAL_TLV_HDR_LEN); + if (usrid) + *usrid = le32_get_bits(tlv32->tl, HAL_TLV_USR_ID); - return tag; + return tlv32->value; } EXPORT_SYMBOL(ath12k_hal_decode_tlv32_hdr); diff --git a/drivers/net/wireless/ath/ath12k/hal.h b/drivers/net/wireless/ath/ath12k/hal.h index 3ee49d93e24a..d141c516c97f 100644 --- a/drivers/net/wireless/ath/ath12k/hal.h +++ b/drivers/net/wireless/ath/ath12k/hal.h @@ -1553,6 +1553,6 @@ void ath12k_hal_rx_reo_ent_buf_paddr_get(struct ath12k_hal *hal, void *rx_desc, u8 *rbm, u32 *msdu_cnt); void *ath12k_hal_encode_tlv64_hdr(void *tlv, u64 tag, u64 len); void *ath12k_hal_encode_tlv32_hdr(void *tlv, u64 tag, u64 len); -u16 ath12k_hal_decode_tlv64_hdr(void *tlv, void **desc); -u16 ath12k_hal_decode_tlv32_hdr(void *tlv, void **desc); +void *ath12k_hal_decode_tlv64_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid); +void *ath12k_hal_decode_tlv32_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid); #endif diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c index 8cebb229ebed..6383d6965625 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c @@ -455,7 +455,7 @@ static u16 ath12k_hal_reo_status_dec_tlv_hdr_qcc2072(void *tlv, void **desc) struct hal_reo_get_queue_stats_status_qcc2072 *status_tlv; u16 tag; - tag = ath12k_hal_decode_tlv32_hdr(tlv, (void **)&status_tlv); + status_tlv = ath12k_hal_decode_tlv32_hdr(tlv, &tag, NULL, NULL); /* * actual desc of REO status entry starts after tlv32_padding, * see hal_reo_get_queue_stats_status_qcc2072 diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c index 9d5180ef83b4..156a861e021d 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c @@ -950,6 +950,15 @@ void ath12k_hal_extract_rx_desc_data_qcn9274(struct hal_rx_desc_data *rx_desc_da rx_desc_data->err_bitmap = ath12k_hal_rx_h_mpdu_err_qcn9274(rx_desc); } +static u16 ath12k_hal_reo_status_dec_tlv_hdr_qcn9274(void *tlv, void **desc) +{ + u16 tag; + + *desc = ath12k_hal_decode_tlv64_hdr(tlv, &tag, NULL, NULL); + + return tag; +} + const struct ath12k_hw_hal_params ath12k_hw_hal_params_qcn9274 = { .rx_buf_rbm = HAL_RX_BUF_RBM_SW3_BM, .wbm2sw_cc_enable = HAL_WBM_SW_COOKIE_CONV_CFG_WBM2SW0_EN | @@ -1138,5 +1147,5 @@ const struct hal_ops hal_qcn9274_ops = { .rx_msdu_list_get = ath12k_wifi7_hal_rx_msdu_list_get, .rx_reo_ent_buf_paddr_get = ath12k_wifi7_hal_rx_reo_ent_buf_paddr_get, .reo_cmd_enc_tlv_hdr = ath12k_hal_encode_tlv64_hdr, - .reo_status_dec_tlv_hdr = ath12k_hal_decode_tlv64_hdr, + .reo_status_dec_tlv_hdr = ath12k_hal_reo_status_dec_tlv_hdr_qcn9274, }; diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c index efbbc1cbd3e4..1ae4ff9b9448 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c @@ -756,6 +756,15 @@ int ath12k_hal_srng_create_config_wcn7850(struct ath12k_hal *hal) return 0; } +static u16 ath12k_hal_reo_status_dec_tlv_hdr_wcn7850(void *tlv, void **desc) +{ + u16 tag; + + *desc = ath12k_hal_decode_tlv64_hdr(tlv, &tag, NULL, NULL); + + return tag; +} + const struct ath12k_hal_tcl_to_wbm_rbm_map ath12k_hal_tcl_to_wbm_rbm_map_wcn7850[DP_TCL_NUM_RING_MAX] = { { @@ -821,5 +830,5 @@ const struct hal_ops hal_wcn7850_ops = { .rx_msdu_list_get = ath12k_wifi7_hal_rx_msdu_list_get, .rx_reo_ent_buf_paddr_get = ath12k_wifi7_hal_rx_reo_ent_buf_paddr_get, .reo_cmd_enc_tlv_hdr = ath12k_hal_encode_tlv64_hdr, - .reo_status_dec_tlv_hdr = ath12k_hal_decode_tlv64_hdr, + .reo_status_dec_tlv_hdr = ath12k_hal_reo_status_dec_tlv_hdr_wcn7850, }; From 9d1d61121b05c0a854e6da227e37b99b3740dae9 Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Sat, 9 May 2026 10:58:17 +0800 Subject: [PATCH 0030/1433] wifi: ath12k: add HAL ops for monitor TLV header decode and alignment Wi-Fi 7 monitor RX status TLV parsing needs to decode TLV headers and advance the pointer with the correct header alignment. Different targets use different TLV header layouts (32-bit vs 64-bit), but the HAL ops for dp_mon RX status header decode and header alignment were not populated for all wifi7 targets. Add dp_mon RX status TLV header decode callbacks and TLV header alignment helpers to the wifi7 HAL ops for QCC2072, QCN9274 and WCN7850. Export helpers to query the required TLV header alignment for 32-bit and 64-bit TLV headers so the caller can align the TLV walk correctly across targets. Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00068-QCACOLSWPL_V1_TO_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Miaoqing Pan Reviewed-by: Vasanthakumar Thiagarajan Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260509025819.1641630-4-miaoqing.pan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/hal.c | 12 ++++++++++++ drivers/net/wireless/ath/ath12k/hal.h | 4 ++++ drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c | 2 ++ drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c | 2 ++ drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c | 2 ++ 5 files changed, 22 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/hal.c b/drivers/net/wireless/ath/ath12k/hal.c index d940f83cd92f..c0c3d2f047ef 100644 --- a/drivers/net/wireless/ath/ath12k/hal.c +++ b/drivers/net/wireless/ath/ath12k/hal.c @@ -875,3 +875,15 @@ void *ath12k_hal_decode_tlv32_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid) return tlv32->value; } EXPORT_SYMBOL(ath12k_hal_decode_tlv32_hdr); + +u32 ath12k_hal_get_tlv64_hdr_align(void) +{ + return HAL_TLV_64_ALIGN; +} +EXPORT_SYMBOL(ath12k_hal_get_tlv64_hdr_align); + +u32 ath12k_hal_get_tlv32_hdr_align(void) +{ + return HAL_TLV_ALIGN; +} +EXPORT_SYMBOL(ath12k_hal_get_tlv32_hdr_align); diff --git a/drivers/net/wireless/ath/ath12k/hal.h b/drivers/net/wireless/ath/ath12k/hal.h index d141c516c97f..ba2c22fb2982 100644 --- a/drivers/net/wireless/ath/ath12k/hal.h +++ b/drivers/net/wireless/ath/ath12k/hal.h @@ -1441,6 +1441,8 @@ struct hal_ops { u8 *rbm, u32 *msdu_cnt); void *(*reo_cmd_enc_tlv_hdr)(void *tlv, u64 tag, u64 len); u16 (*reo_status_dec_tlv_hdr)(void *tlv, void **desc); + void *(*mon_rx_status_dec_tlv_hdr)(void *tlv, u16 *tag, u16 *len, u16 *usrid); + u32 (*get_tlv_hdr_align)(void); }; #define HAL_TLV_HDR_TAG GENMASK(9, 1) @@ -1555,4 +1557,6 @@ void *ath12k_hal_encode_tlv64_hdr(void *tlv, u64 tag, u64 len); void *ath12k_hal_encode_tlv32_hdr(void *tlv, u64 tag, u64 len); void *ath12k_hal_decode_tlv64_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid); void *ath12k_hal_decode_tlv32_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid); +u32 ath12k_hal_get_tlv64_hdr_align(void); +u32 ath12k_hal_get_tlv32_hdr_align(void); #endif diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c index 6383d6965625..7cf00fda996f 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c @@ -506,6 +506,8 @@ const struct hal_ops hal_qcc2072_ops = { .rx_reo_ent_buf_paddr_get = ath12k_wifi7_hal_rx_reo_ent_buf_paddr_get, .reo_cmd_enc_tlv_hdr = ath12k_hal_encode_tlv32_hdr, .reo_status_dec_tlv_hdr = ath12k_hal_reo_status_dec_tlv_hdr_qcc2072, + .mon_rx_status_dec_tlv_hdr = ath12k_hal_decode_tlv32_hdr, + .get_tlv_hdr_align = ath12k_hal_get_tlv32_hdr_align, }; u32 ath12k_hal_rx_desc_get_mpdu_start_offset_qcc2072(void) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c index 156a861e021d..052b59265af8 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c @@ -1148,4 +1148,6 @@ const struct hal_ops hal_qcn9274_ops = { .rx_reo_ent_buf_paddr_get = ath12k_wifi7_hal_rx_reo_ent_buf_paddr_get, .reo_cmd_enc_tlv_hdr = ath12k_hal_encode_tlv64_hdr, .reo_status_dec_tlv_hdr = ath12k_hal_reo_status_dec_tlv_hdr_qcn9274, + .mon_rx_status_dec_tlv_hdr = ath12k_hal_decode_tlv64_hdr, + .get_tlv_hdr_align = ath12k_hal_get_tlv64_hdr_align, }; diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c index 1ae4ff9b9448..61be8443e46e 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c @@ -831,4 +831,6 @@ const struct hal_ops hal_wcn7850_ops = { .rx_reo_ent_buf_paddr_get = ath12k_wifi7_hal_rx_reo_ent_buf_paddr_get, .reo_cmd_enc_tlv_hdr = ath12k_hal_encode_tlv64_hdr, .reo_status_dec_tlv_hdr = ath12k_hal_reo_status_dec_tlv_hdr_wcn7850, + .mon_rx_status_dec_tlv_hdr = ath12k_hal_decode_tlv64_hdr, + .get_tlv_hdr_align = ath12k_hal_get_tlv64_hdr_align, }; From 4c09bbf0c1e11bae19a0643bd9824d4f05d9c281 Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Sat, 9 May 2026 10:58:18 +0800 Subject: [PATCH 0031/1433] wifi: ath12k: add dp_mon support 32-bit TLV headers Wi-Fi 7 monitor status parsing in dp_mon currently assumes a 64-bit TLV header and directly decodes tag/len/userid from struct hal_tlv_64_hdr. On chips using a 32-bit TLV header (e.g. QCC2072), this causes monitor RX status packets to be dropped during TLV parsing. Introduce HAL helpers to decode TLV header fields (tag/len/userid/value) for both 32-bit and 64-bit header layouts. Without changing the actual TLV parsing logic. Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00068-QCACOLSWPL_V1_TO_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Miaoqing Pan Reviewed-by: Vasanthakumar Thiagarajan Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260509025819.1641630-5-miaoqing.pan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- .../net/wireless/ath/ath12k/wifi7/dp_mon.c | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c index 7dd4a49d64d5..06ca96c3cc7e 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c @@ -1565,16 +1565,17 @@ ath12k_wifi7_dp_mon_parse_status_msdu_end(struct ath12k_mon_data *pmon, static enum hal_rx_mon_status ath12k_wifi7_dp_mon_rx_parse_status_tlv(struct ath12k_pdev_dp *dp_pdev, struct ath12k_mon_data *pmon, - const struct hal_tlv_64_hdr *tlv) + const void *tlv) { struct hal_rx_mon_ppdu_info *ppdu_info = &pmon->mon_ppdu_info; - const void *tlv_data = tlv->value; - u32 info[7], userid; - u16 tlv_tag, tlv_len; + struct ath12k *ar = ath12k_pdev_dp_to_ar(dp_pdev); + struct ath12k_hal *hal = &ar->ab->hal; + u16 tlv_tag, tlv_len, userid; + void *tlv_data; + u32 info[7]; - tlv_tag = le64_get_bits(tlv->tl, HAL_TLV_64_HDR_TAG); - tlv_len = le64_get_bits(tlv->tl, HAL_TLV_64_HDR_LEN); - userid = le64_get_bits(tlv->tl, HAL_TLV_64_USR_ID); + tlv_data = hal->ops->mon_rx_status_dec_tlv_hdr((void *)tlv, &tlv_tag, + &tlv_len, &userid); if (ppdu_info->tlv_aggr.in_progress && ppdu_info->tlv_aggr.tlv_tag != tlv_tag) { ath12k_wifi7_dp_mon_parse_eht_sig_hdr(ppdu_info, @@ -2930,11 +2931,12 @@ static enum dp_mon_status_buf_state ath12k_wifi7_dp_rx_mon_buf_done(struct ath12k_base *ab, struct hal_srng *srng, struct dp_rxdma_mon_ring *rx_ring) { + struct ath12k_hal *hal = &ab->hal; struct ath12k_skb_rxcb *rxcb; - struct hal_tlv_64_hdr *tlv; struct sk_buff *skb; void *status_desc; dma_addr_t paddr; + u16 tlv_tag; u32 cookie; int buf_id; u8 rbm; @@ -2959,8 +2961,8 @@ ath12k_wifi7_dp_rx_mon_buf_done(struct ath12k_base *ab, struct hal_srng *srng, skb->len + skb_tailroom(skb), DMA_FROM_DEVICE); - tlv = (struct hal_tlv_64_hdr *)skb->data; - if (le64_get_bits(tlv->tl, HAL_TLV_HDR_TAG) != HAL_RX_STATUS_BUFFER_DONE) + hal->ops->mon_rx_status_dec_tlv_hdr(skb->data, &tlv_tag, NULL, NULL); + if (tlv_tag != HAL_RX_STATUS_BUFFER_DONE) return DP_MON_STATUS_NO_DMA; return DP_MON_STATUS_REPLINISH; @@ -2972,39 +2974,38 @@ ath12k_wifi7_dp_mon_parse_rx_dest(struct ath12k_pdev_dp *dp_pdev, struct sk_buff *skb) { struct ath12k *ar = ath12k_pdev_dp_to_ar(dp_pdev); - struct hal_tlv_64_hdr *tlv; + struct ath12k_hal *hal = &ar->ab->hal; + u8 *tlv_value, *tlv = skb->data; struct ath12k_skb_rxcb *rxcb; enum hal_rx_mon_status hal_status; u16 tlv_tag, tlv_len; - u8 *ptr = skb->data; + u32 tlv_hdr_len; + + tlv_hdr_len = hal->ops->get_tlv_hdr_align(); do { - tlv = (struct hal_tlv_64_hdr *)ptr; - tlv_tag = le64_get_bits(tlv->tl, HAL_TLV_64_HDR_TAG); + tlv_value = hal->ops->mon_rx_status_dec_tlv_hdr(tlv, &tlv_tag, + &tlv_len, NULL); /* The actual length of PPDU_END is the combined length of many PHY * TLVs that follow. Skip the TLV header and * rx_rxpcu_classification_overview that follows the header to get to * next TLV. */ - if (tlv_tag == HAL_RX_PPDU_END) tlv_len = sizeof(struct hal_rx_rxpcu_classification_overview); - else - tlv_len = le64_get_bits(tlv->tl, HAL_TLV_64_HDR_LEN); hal_status = ath12k_wifi7_dp_mon_rx_parse_status_tlv(dp_pdev, pmon, tlv); if (ar->monitor_started && ar->ab->hw_params->rxdma1_enable && ath12k_wifi7_dp_mon_parse_rx_dest_tlv(dp_pdev, pmon, hal_status, - tlv->value)) + tlv_value)) return HAL_RX_MON_STATUS_PPDU_DONE; - ptr += sizeof(*tlv) + tlv_len; - ptr = PTR_ALIGN(ptr, HAL_TLV_64_ALIGN); + tlv = PTR_ALIGN(tlv + tlv_len + tlv_hdr_len, tlv_hdr_len); - if ((ptr - skb->data) > skb->len) + if ((tlv - skb->data) > skb->len) break; } while ((hal_status == HAL_RX_MON_STATUS_PPDU_NOT_DONE) || @@ -3056,15 +3057,16 @@ ath12k_wifi7_dp_rx_reap_mon_status_ring(struct ath12k_base *ab, int mac_id, int buf_id, srng_id, num_buffs_reaped = 0; enum dp_mon_status_buf_state reap_status; struct dp_rxdma_mon_ring *rx_ring; + struct ath12k_hal *hal = &ab->hal; struct ath12k_mon_data *pmon; struct ath12k_skb_rxcb *rxcb; - struct hal_tlv_64_hdr *tlv; void *rx_mon_status_desc; struct hal_srng *srng; struct ath12k_dp *dp; struct sk_buff *skb; struct ath12k *ar; dma_addr_t paddr; + u16 tlv_tag; u32 cookie; u8 rbm; @@ -3109,14 +3111,13 @@ ath12k_wifi7_dp_rx_reap_mon_status_ring(struct ath12k_base *ab, int mac_id, skb->len + skb_tailroom(skb), DMA_FROM_DEVICE); - tlv = (struct hal_tlv_64_hdr *)skb->data; - if (le64_get_bits(tlv->tl, HAL_TLV_HDR_TAG) != - HAL_RX_STATUS_BUFFER_DONE) { + hal->ops->mon_rx_status_dec_tlv_hdr(skb->data, &tlv_tag, + NULL, NULL); + if (tlv_tag != HAL_RX_STATUS_BUFFER_DONE) { pmon->buf_state = DP_MON_STATUS_NO_DMA; ath12k_warn(ab, - "mon status DONE not set %llx, buf_id %d\n", - le64_get_bits(tlv->tl, HAL_TLV_HDR_TAG), - buf_id); + "mon status DONE not set %x, buf_id %d\n", + tlv_tag, buf_id); /* RxDMA status done bit might not be set even * though tp is moved by HW. */ From fffa54aeaeb2e9ac923254b39e89bf07799615aa Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Sat, 9 May 2026 10:58:19 +0800 Subject: [PATCH 0032/1433] wifi: ath12k: tighten RX monitor TLV bounds check Validate the pointer to the next RX monitor TLV more strictly by ensuring that at least a full TLV header is available within the status buffer before continuing TLV parsing. Prevent potential out-of-bounds access when handling malformed or truncated RX monitor status data. Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00068-QCACOLSWPL_V1_TO_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Miaoqing Pan Reviewed-by: Vasanthakumar Thiagarajan Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260509025819.1641630-6-miaoqing.pan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c index 06ca96c3cc7e..c84c42a3d377 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c @@ -3005,9 +3005,9 @@ ath12k_wifi7_dp_mon_parse_rx_dest(struct ath12k_pdev_dp *dp_pdev, tlv = PTR_ALIGN(tlv + tlv_len + tlv_hdr_len, tlv_hdr_len); - if ((tlv - skb->data) > skb->len) + if (unlikely(tlv - skb->data > skb->len || + skb->len - (tlv - skb->data) < tlv_hdr_len)) break; - } while ((hal_status == HAL_RX_MON_STATUS_PPDU_NOT_DONE) || (hal_status == HAL_RX_MON_STATUS_BUF_ADDR) || (hal_status == HAL_RX_MON_STATUS_MPDU_START) || From c2d60ab8e3827de2cbf951491e5de339e4bb2eb9 Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Thu, 4 Jun 2026 08:45:51 +0530 Subject: [PATCH 0033/1433] wifi: ath12k: expand UserPD ID mask to support up to 8 PDs Currently ATH12K_USERPD_ID_MASK uses GENMASK(9, 8), which defines a 2-bit field and limits supported UserPD IDs to values 0-3. Future IPQ5332 multi-PD platform variants support more than three UserPDs. Expand ATH12K_USERPD_ID_MASK to GENMASK(10, 8), increasing the field width to 3 bits and allowing UserPD IDs from 0-7. ATH12K_USERPD_ID_MASK is currently used only while constructing the ath12k AHB PAS ID, so this change does not affect existing platforms. Also remove the unused ATH12K_MAX_UPDS definition. Tested-on: IPQ5332 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1 Signed-off-by: Aaradhana Sahu Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260604031551.4178754-1-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/ahb.c | 1 - drivers/net/wireless/ath/ath12k/ahb.h | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/ahb.c b/drivers/net/wireless/ath/ath12k/ahb.c index 30733a244454..4912172e106e 100644 --- a/drivers/net/wireless/ath/ath12k/ahb.c +++ b/drivers/net/wireless/ath/ath12k/ahb.c @@ -17,7 +17,6 @@ #include "hif.h" #define ATH12K_IRQ_CE0_OFFSET 4 -#define ATH12K_MAX_UPDS 1 #define ATH12K_UPD_IRQ_WRD_LEN 18 static struct ath12k_ahb_driver *ath12k_ahb_family_drivers[ATH12K_DEVICE_FAMILY_MAX]; diff --git a/drivers/net/wireless/ath/ath12k/ahb.h b/drivers/net/wireless/ath/ath12k/ahb.h index 0fa15daaa3e6..a153db6cf1d3 100644 --- a/drivers/net/wireless/ath/ath12k/ahb.h +++ b/drivers/net/wireless/ath/ath12k/ahb.h @@ -27,7 +27,7 @@ #define ATH12K_USERPD_SPAWN_TIMEOUT (5 * HZ) #define ATH12K_USERPD_READY_TIMEOUT (10 * HZ) #define ATH12K_USERPD_STOP_TIMEOUT (5 * HZ) -#define ATH12K_USERPD_ID_MASK GENMASK(9, 8) +#define ATH12K_USERPD_ID_MASK GENMASK(10, 8) #define ATH12K_USERPD_FW_NAME_LEN 35 enum ath12k_ahb_smp2p_msg_id { From fe2b006c15f6b1f81524b4c1af8013bc32fa0abc Mon Sep 17 00:00:00 2001 From: Aishwarya R Date: Fri, 19 Jun 2026 17:37:51 +0530 Subject: [PATCH 0034/1433] wifi: ath12k: reset REOQ LUT addresses before firmware stop During module removal, REOQ LUT cleanup writes 0 to the REOQ/ML-REOQ LUT address registers. That cleanup runs from ath12k_core_stop(), after ath12k_qmi_firmware_stop() has already stopped the firmware (mode OFF), so the register writes can hit an invalid target access. Move the REOQ LUT register reset before ath12k_qmi_firmware_stop(), so the registers are cleared before stopping the firmware, while register access is still valid. Additionally, handle the error path where firmware-ready setup fails after LUT programming but before core_stop() is reached, ensuring the registers are properly reset in that case as well. On the crash-recovery path, ath12k_core_reconfigure_on_crash() calls ath12k_core_qmi_firmware_ready(), which re-enters ath12k_dp_setup() and ath12k_dp_reoq_lut_setup(), so the LUT registers are reprogrammed before use and stale values do not persist across recovery. There is a brief window between the crash and when the LUT registers are reprogrammed during recovery, during which the registers still hold the freed DMA memory addresses. This is safe because the device is non-functional in that window and will not initiate any DMA access until firmware is restarted and the registers are reprogrammed. No functional issue has been observed so far due to this sequence. However, this change proactively avoids potential issues such as invalid register accesses after firmware stop during module removal and error handling. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Co-developed-by: P Praneesh Signed-off-by: P Praneesh Signed-off-by: Aishwarya R Reviewed-by: Baochen Qiang Reviewed-by: Tamizh Chelvam Raja Link: https://patch.msgid.link/20260619120751.363340-1-aishwarya.r@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.c | 5 ++++- drivers/net/wireless/ath/ath12k/dp.c | 14 ++++++++++++-- drivers/net/wireless/ath/ath12k/dp.h | 1 + 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index 742d4fd1b598..efe37dc91afd 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -708,8 +708,10 @@ static void ath12k_core_stop(struct ath12k_base *ab) ath12k_core_to_group_ref_put(ab); - if (!test_bit(ATH12K_FLAG_CRASH_FLUSH, &ab->dev_flags)) + if (!test_bit(ATH12K_FLAG_CRASH_FLUSH, &ab->dev_flags)) { + ath12k_dp_reoq_lut_addr_reset(ath12k_ab_to_dp(ab)); ath12k_qmi_firmware_stop(ab); + } ath12k_acpi_stop(ab); @@ -1371,6 +1373,7 @@ int ath12k_core_qmi_firmware_ready(struct ath12k_base *ab) goto exit; err_deinit: + ath12k_dp_reoq_lut_addr_reset(ath12k_ab_to_dp(ab)); ath12k_dp_cmn_device_deinit(ath12k_ab_to_dp(ab)); mutex_unlock(&ab->core_lock); mutex_unlock(&ag->mutex); diff --git a/drivers/net/wireless/ath/ath12k/dp.c b/drivers/net/wireless/ath/ath12k/dp.c index af5f11fc1d84..fbc0788b37a0 100644 --- a/drivers/net/wireless/ath/ath12k/dp.c +++ b/drivers/net/wireless/ath/ath12k/dp.c @@ -1097,7 +1097,6 @@ static void ath12k_dp_reoq_lut_cleanup(struct ath12k_base *ab) return; if (dp->reoq_lut.vaddr_unaligned) { - ath12k_hal_write_reoq_lut_addr(ab, 0); dma_free_coherent(ab->dev, dp->reoq_lut.size, dp->reoq_lut.vaddr_unaligned, dp->reoq_lut.paddr_unaligned); @@ -1105,7 +1104,6 @@ static void ath12k_dp_reoq_lut_cleanup(struct ath12k_base *ab) } if (dp->ml_reoq_lut.vaddr_unaligned) { - ath12k_hal_write_ml_reoq_lut_addr(ab, 0); dma_free_coherent(ab->dev, dp->ml_reoq_lut.size, dp->ml_reoq_lut.vaddr_unaligned, dp->ml_reoq_lut.paddr_unaligned); @@ -1568,6 +1566,7 @@ static int ath12k_dp_setup(struct ath12k_base *ab) ath12k_dp_rx_free(ab); fail_cmn_reoq_cleanup: + ath12k_dp_reoq_lut_addr_reset(dp); ath12k_dp_reoq_lut_cleanup(ab); fail_cmn_srng_cleanup: @@ -1627,3 +1626,14 @@ void ath12k_dp_cmn_hw_group_assign(struct ath12k_dp *dp, dp->device_id = ab->device_id; dp_hw_grp->dp[dp->device_id] = dp; } + +void ath12k_dp_reoq_lut_addr_reset(struct ath12k_dp *dp) +{ + struct ath12k_base *ab = dp->ab; + + if (dp->reoq_lut.vaddr_unaligned) + ath12k_hal_write_reoq_lut_addr(ab, 0); + + if (dp->ml_reoq_lut.vaddr_unaligned) + ath12k_hal_write_ml_reoq_lut_addr(ab, 0); +} diff --git a/drivers/net/wireless/ath/ath12k/dp.h b/drivers/net/wireless/ath/ath12k/dp.h index f8cfc7bb29dd..9b39146e65e1 100644 --- a/drivers/net/wireless/ath/ath12k/dp.h +++ b/drivers/net/wireless/ath/ath12k/dp.h @@ -701,4 +701,5 @@ struct ath12k_rx_desc_info *ath12k_dp_get_rx_desc(struct ath12k_dp *dp, u32 cookie); struct ath12k_tx_desc_info *ath12k_dp_get_tx_desc(struct ath12k_dp *dp, u32 desc_id); +void ath12k_dp_reoq_lut_addr_reset(struct ath12k_dp *dp); #endif From 784f7dabf5d3bce23c69c48a0441ff2b1536f069 Mon Sep 17 00:00:00 2001 From: Manish Dharanenthiran Date: Tue, 23 Jun 2026 11:16:21 +0530 Subject: [PATCH 0035/1433] wifi: ath12k: advertise ieee_link_id in vdev start MLO params Firmware builds the AP MLD partner profile from the hw_link_id passed in the vdev start parameters. However, hw_link_id is not always the same as the logical per-MLD ieee_link_id, since ieee_link_id is assigned per MLD and not per pdev. This matters in mixed MLO and SLO setups. For example: MLD 1 - 5 GHz + 6 GHz (2-link MLO): ieee_link_id 0 and 1 MLD 2 - 6 GHz only (1-link SLO): ieee_link_id 0 MLD 3 - 5 GHz only (1-link SLO): ieee_link_id 0 The same physical 6 GHz radio can use ieee_link_id 1 for one MLD and ieee_link_id 0 for another. Pass the correct ieee_link_id to firmware so it can build accurate per-STA profile elements. Add ieee_link_id to wmi_vdev_start_mlo_params for the self link and to wmi_partner_link_info for each partner link. Populate these fields in ath12k_mac_mlo_get_vdev_args() from the corresponding vdev link_id before encoding the WMI command. Introduce two new flags in ML params to indicate to firmware when the new fields are valid: ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID BIT(18) for the self link ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID_PARTNER BIT(19) for partner links Firmware parses ieee_link_id only when the matching flag is set. Also fix the debug message by using correct format specifiers and host-endian values instead of __le32 values. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Co-developed-by: Hari Naraayana Desikan Kannan Signed-off-by: Hari Naraayana Desikan Kannan Co-developed-by: Karthik M Signed-off-by: Karthik M Signed-off-by: Manish Dharanenthiran Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260623-ieee_link_id-v2-1-8a89d71baf58@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 3 +++ drivers/net/wireless/ath/ath12k/wmi.c | 30 +++++++++++++++++---------- drivers/net/wireless/ath/ath12k/wmi.h | 7 +++++++ 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index af354bef5c0d..773ecd6da8e5 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -11253,6 +11253,8 @@ ath12k_mac_mlo_get_vdev_args(struct ath12k_link_vif *arvif, ml_arg->assoc_link = arvif->is_sta_assoc_link; + ml_arg->ieee_link_id = arvif->link_id; + partner_info = ml_arg->partner_info; links = ahvif->links_map; @@ -11276,6 +11278,7 @@ ath12k_mac_mlo_get_vdev_args(struct ath12k_link_vif *arvif, partner_info->vdev_id = arvif_p->vdev_id; partner_info->hw_link_id = arvif_p->ar->pdev->hw_link_id; + partner_info->ieee_link_id = arvif_p->link_id; ether_addr_copy(partner_info->addr, link_conf->addr); ml_arg->num_partner_links++; partner_info++; diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index e7689ee3e701..ad739bffcf88 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -1228,10 +1228,14 @@ int ath12k_wmi_vdev_start(struct ath12k *ar, struct wmi_vdev_start_req_arg *arg, le32_encode_bits(arg->ml.mcast_link, ATH12K_WMI_FLAG_MLO_MCAST_VDEV) | le32_encode_bits(arg->ml.link_add, - ATH12K_WMI_FLAG_MLO_LINK_ADD); + ATH12K_WMI_FLAG_MLO_LINK_ADD) | + cpu_to_le32(ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID); - ath12k_dbg(ar->ab, ATH12K_DBG_WMI, "vdev %d start ml flags 0x%x\n", - arg->vdev_id, ml_params->flags); + ml_params->ieee_link_id = cpu_to_le32(arg->ml.ieee_link_id); + + ath12k_dbg(ar->ab, ATH12K_DBG_WMI, "vdev %u start link_id %u ml flags 0x%x\n", + arg->vdev_id, arg->ml.ieee_link_id, + le32_to_cpu(ml_params->flags)); ptr += sizeof(*ml_params); @@ -1244,19 +1248,23 @@ int ath12k_wmi_vdev_start(struct ath12k *ar, struct wmi_vdev_start_req_arg *arg, partner_info = ptr; for (i = 0; i < arg->ml.num_partner_links; i++) { + struct wmi_ml_partner_info *pinfo = &arg->ml.partner_info[i]; + partner_info->tlv_header = ath12k_wmi_tlv_cmd_hdr(WMI_TAG_MLO_PARTNER_LINK_PARAMS, sizeof(*partner_info)); - partner_info->vdev_id = - cpu_to_le32(arg->ml.partner_info[i].vdev_id); - partner_info->hw_link_id = - cpu_to_le32(arg->ml.partner_info[i].hw_link_id); + partner_info->vdev_id = cpu_to_le32(pinfo->vdev_id); + partner_info->hw_link_id = cpu_to_le32(pinfo->hw_link_id); ether_addr_copy(partner_info->vdev_addr.addr, - arg->ml.partner_info[i].addr); + pinfo->addr); + partner_info->flags = + cpu_to_le32(ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID_PARTNER); + partner_info->ieee_link_id = cpu_to_le32(pinfo->ieee_link_id); - ath12k_dbg(ar->ab, ATH12K_DBG_WMI, "partner vdev %d hw_link_id %d macaddr%pM\n", - partner_info->vdev_id, partner_info->hw_link_id, - partner_info->vdev_addr.addr); + ath12k_dbg(ar->ab, ATH12K_DBG_WMI, "partner vdev %u hw_link_id %u macaddr %pM link_id %u ml flags 0x%x\n", + pinfo->vdev_id, pinfo->hw_link_id, + pinfo->addr, pinfo->ieee_link_id, + le32_to_cpu(partner_info->flags)); partner_info++; } diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index c452e3d57a29..51f3426e1fcd 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -2954,10 +2954,13 @@ struct wmi_vdev_create_mlo_params { #define ATH12K_WMI_FLAG_MLO_EMLSR_SUPPORT BIT(6) #define ATH12K_WMI_FLAG_MLO_FORCED_INACTIVE BIT(7) #define ATH12K_WMI_FLAG_MLO_LINK_ADD BIT(8) +#define ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID BIT(18) +#define ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID_PARTNER BIT(19) struct wmi_vdev_start_mlo_params { __le32 tlv_header; __le32 flags; + __le32 ieee_link_id; } __packed; struct wmi_partner_link_info { @@ -2965,6 +2968,8 @@ struct wmi_partner_link_info { __le32 vdev_id; __le32 hw_link_id; struct ath12k_wmi_mac_addr_params vdev_addr; + __le32 flags; + __le32 ieee_link_id; } __packed; struct wmi_vdev_delete_cmd { @@ -3120,6 +3125,7 @@ struct wmi_ml_partner_info { bool primary_umac; bool logical_link_idx_valid; u32 logical_link_idx; + u32 ieee_link_id; }; struct wmi_ml_arg { @@ -3127,6 +3133,7 @@ struct wmi_ml_arg { bool assoc_link; bool mcast_link; bool link_add; + u32 ieee_link_id; u8 num_partner_links; struct wmi_ml_partner_info partner_info[ATH12K_WMI_MLO_MAX_LINKS]; }; From e47d6c9bb4165721f61356f5fccae8f7dd78876b Mon Sep 17 00:00:00 2001 From: Tamizh Chelvam Raja Date: Tue, 23 Jun 2026 15:35:01 +0530 Subject: [PATCH 0036/1433] wifi: ath12k: Advertise multicast Ethernet encapsulation offload support Advertise IEEE80211_OFFLOAD_ENCAP_MCAST to inform mac80211 that multicast frame encapsulation is handled in hardware. This allows mac80211 to pass Ethernet-formatted multicast frames directly to the driver. In ath12k_wifi7_mac_op_tx(), refine the logic that selects the MLO multicast replication path. Add a sta pointer check so that only unicast Hardware-encap frames use the direct transmit path, while multicast Hardware-encap frames fall through to the MLO replication loop and are transmitted on each active link. In the MLO replication loop, use skb_clone() for Hardware-encap frames. These frames are already in Ethernet format and do not require 802.11 link address rewriting by ath12k_mlo_mcast_update_tx_link_address(). Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Signed-off-by: Tamizh Chelvam Raja Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260623100501.2100119-1-tamizh.raja@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 6 ++- drivers/net/wireless/ath/ath12k/wifi7/hw.c | 61 +++++++++++++++++----- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 773ecd6da8e5..16339469c24c 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -10117,7 +10117,8 @@ static void ath12k_mac_update_vif_offload(struct ath12k_link_vif *arvif) if (vif->type != NL80211_IFTYPE_STATION && vif->type != NL80211_IFTYPE_AP) vif->offload_flags &= ~(IEEE80211_OFFLOAD_ENCAP_ENABLED | - IEEE80211_OFFLOAD_DECAP_ENABLED); + IEEE80211_OFFLOAD_DECAP_ENABLED | + IEEE80211_OFFLOAD_ENCAP_MCAST); if (vif->offload_flags & IEEE80211_OFFLOAD_ENCAP_ENABLED) { ahvif->dp_vif.tx_encap_type = ATH12K_HW_TXRX_ETHERNET; @@ -10136,6 +10137,9 @@ static void ath12k_mac_update_vif_offload(struct ath12k_link_vif *arvif) vif->offload_flags &= ~IEEE80211_OFFLOAD_ENCAP_ENABLED; } + if (vif->offload_flags & IEEE80211_OFFLOAD_ENCAP_ENABLED) + vif->offload_flags |= IEEE80211_OFFLOAD_ENCAP_MCAST; + param_id = WMI_VDEV_PARAM_RX_DECAP_TYPE; if (vif->offload_flags & IEEE80211_OFFLOAD_DECAP_ENABLED) param_value = ATH12K_HW_TXRX_ETHERNET; diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c index 3d59fa452ec0..e5bf9d218104 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c @@ -903,6 +903,7 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, struct ethhdr *eth; bool is_prb_rsp; u16 mcbc_gsn; + u8 cb_flags; u8 link_id; int ret; struct ath12k_dp *tmp_dp; @@ -996,8 +997,13 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, ieee80211_has_protected(hdr->frame_control)) is_dvlan = true; + /* + * Add a sta pointer check to differentiate multicast encapsulation + * offload packets, as the ATH12K_SKB_HW_80211_ENCAP flag is also set + * for such packets. + */ if (!vif->valid_links || !is_mcast || is_dvlan || - (skb_cb->flags & ATH12K_SKB_HW_80211_ENCAP) || + ((skb_cb->flags & ATH12K_SKB_HW_80211_ENCAP) && sta) || test_bit(ATH12K_FLAG_RAW_MODE, &ar->ab->dev_flags)) { ret = ath12k_wifi7_dp_tx(dp_pdev, arvif, arsta, skb, false, 0, is_mcast); if (unlikely(ret)) { @@ -1009,6 +1015,7 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, mcbc_gsn = atomic_inc_return(&ahvif->dp_vif.mcbc_gsn) & 0xfff; links_map = ahvif->links_map; + cb_flags = skb_cb->flags; for_each_set_bit(link_id, &links_map, IEEE80211_MLD_MAX_NUM_LINKS) { tmp_arvif = rcu_dereference(ahvif->link[link_id]); @@ -1016,21 +1023,45 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, continue; tmp_ar = tmp_arvif->ar; - tmp_dp_pdev = ath12k_dp_to_pdev_dp(tmp_ar->ab->dp, + tmp_dp = ath12k_ab_to_dp(tmp_ar->ab); + tmp_dp_pdev = ath12k_dp_to_pdev_dp(tmp_dp, tmp_ar->pdev_idx); if (!tmp_dp_pdev) continue; - msdu_copied = skb_copy(skb, GFP_ATOMIC); - if (!msdu_copied) { - ath12k_err(ar->ab, - "skb copy failure link_id 0x%X vdevid 0x%X\n", - link_id, tmp_arvif->vdev_id); - continue; - } - ath12k_mlo_mcast_update_tx_link_address(vif, link_id, - msdu_copied, - info_flags); + if (cb_flags & ATH12K_SKB_HW_80211_ENCAP) { + /* + * skb->data may be modified for the iova_mask devices. + * It is better to use skb_copy() for such devices + * to avoid any potential skb corruption related issues. + */ + if (tmp_dp->hw_params->iova_mask) + msdu_copied = skb_copy(skb, GFP_ATOMIC); + else + /* + * ath12k_wifi7_dp_tx() should treat cloned HW-encap + * Ethernet multicast frames as read-only. + */ + msdu_copied = skb_clone(skb, GFP_ATOMIC); + if (!msdu_copied) { + ath12k_err(ar->ab, + "skb copy/clone failure link_id 0x%X vdevid 0x%X\n", + link_id, tmp_arvif->vdev_id); + continue; + } + } else { + msdu_copied = skb_copy(skb, GFP_ATOMIC); + if (!msdu_copied) { + ath12k_err(ar->ab, + "skb copy failure link_id 0x%X vdevid 0x%X\n", + link_id, tmp_arvif->vdev_id); + continue; + } + + ath12k_mlo_mcast_update_tx_link_address(vif, link_id, + msdu_copied, + info_flags); + } skb_cb = ATH12K_SKB_CB(msdu_copied); skb_cb->link_id = link_id; @@ -1046,7 +1077,6 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, if (unlikely(!ahvif->dp_vif.key_cipher)) goto skip_peer_find; - tmp_dp = ath12k_ab_to_dp(tmp_ar->ab); spin_lock_bh(&tmp_dp->dp_lock); peer = ath12k_dp_link_peer_find_by_addr(tmp_dp, tmp_arvif->bssid); @@ -1065,11 +1095,16 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, skb_cb->cipher = key->cipher; skb_cb->flags |= ATH12K_SKB_CIPHER_SET; + if (skb_cb->flags & ATH12K_SKB_HW_80211_ENCAP) + goto skip_fctl_protected_check; + hdr = (struct ieee80211_hdr *)msdu_copied->data; if (!ieee80211_has_protected(hdr->frame_control)) hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_PROTECTED); } + +skip_fctl_protected_check: spin_unlock_bh(&tmp_dp->dp_lock); skip_peer_find: From 34620d1890cc1fcf87a95219d6781d7f7b9d8cbd Mon Sep 17 00:00:00 2001 From: Sreeramya Soratkal Date: Fri, 26 Jun 2026 14:22:51 +0530 Subject: [PATCH 0037/1433] wifi: ath12k: Use runtime device count in dp stats display The REO Rx Received and Rx WBM REL SRC Errors display loops in ath12k_debugfs_dump_device_dp_stats() iterate up to the compile-time constant ATH12K_MAX_DEVICES. This unconditionally prints zeros in columns with no hardware behind it, making the output misleading. Replace the compile-time bound with the runtime ab->ag->num_devices so only live device slots appear in the output. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Signed-off-by: Sreeramya Soratkal Reviewed-by: Baochen Qiang Reviewed-by: Aishwarya R Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260626085253.3927269-2-sreeramya.soratkal@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/debugfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/debugfs.c b/drivers/net/wireless/ath/ath12k/debugfs.c index d17d4a8f1cb7..bab9d96d6acc 100644 --- a/drivers/net/wireless/ath/ath12k/debugfs.c +++ b/drivers/net/wireless/ath/ath12k/debugfs.c @@ -1173,7 +1173,7 @@ static ssize_t ath12k_debugfs_dump_device_dp_stats(struct file *file, for (i = 0; i < DP_REO_DST_RING_MAX; i++) { len += scnprintf(buf + len, size - len, "Ring%d:", i + 1); - for (j = 0; j < ATH12K_MAX_DEVICES; j++) { + for (j = 0; j < ab->ag->num_devices; j++) { len += scnprintf(buf + len, size - len, "\t%d:%u", j, device_stats->reo_rx[i][j]); @@ -1190,7 +1190,7 @@ static ssize_t ath12k_debugfs_dump_device_dp_stats(struct file *file, for (i = 0; i < HAL_WBM_REL_SRC_MODULE_MAX; i++) { len += scnprintf(buf + len, size - len, "%s:", wbm_rel_src[i]); - for (j = 0; j < ATH12K_MAX_DEVICES; j++) { + for (j = 0; j < ab->ag->num_devices; j++) { len += scnprintf(buf + len, size - len, "\t%d:%u", j, From 6cc84fce7b999b0c6c8aaccdfa8669f0a55e8586 Mon Sep 17 00:00:00 2001 From: Sreeramya Soratkal Date: Fri, 26 Jun 2026 14:22:52 +0530 Subject: [PATCH 0038/1433] wifi: ath12k: Add timestamp to dp stats display In MLO configurations the device_dp_stats debugfs file is read separately for each ath12k device. Without a timestamp it is impossible to know whether two snapshots were taken at the same moment, making counter comparisons across devices unreliable. Prepend a ktime-based millisecond timestamp to the output header so the reader can confirm when the snapshot was taken. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Signed-off-by: Sreeramya Soratkal Reviewed-by: Baochen Qiang Reviewed-by: Aishwarya R Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260626085253.3927269-3-sreeramya.soratkal@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/debugfs.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/debugfs.c b/drivers/net/wireless/ath/ath12k/debugfs.c index bab9d96d6acc..57c213111259 100644 --- a/drivers/net/wireless/ath/ath12k/debugfs.c +++ b/drivers/net/wireless/ath/ath12k/debugfs.c @@ -1082,6 +1082,9 @@ static ssize_t ath12k_debugfs_dump_device_dp_stats(struct file *file, if (!buf) return -ENOMEM; + len += scnprintf(buf + len, size - len, + "DEVICE DP STATS (timestamp: %lldms):\n\n", + ktime_to_ms(ktime_get())); len += scnprintf(buf + len, size - len, "DEVICE RX STATS:\n\n"); len += scnprintf(buf + len, size - len, "err ring pkts: %u\n", device_stats->err_ring_pkts); From b1d8d626e206a757b745af2adcbc7127ec593a20 Mon Sep 17 00:00:00 2001 From: Sreeramya Soratkal Date: Fri, 26 Jun 2026 14:22:53 +0530 Subject: [PATCH 0039/1433] wifi: ath12k: Show per-radio center freq in dp stats Currently, the frequency on which each radio is operating is not available in device_dp_stats. This information is helpful in debugging the channel-specific throughput and is available with iw/nl80211 dump. Extend the device_dp_stats dump to display the center frequency in the existing per-radio loop. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Signed-off-by: Sreeramya Soratkal Reviewed-by: Baochen Qiang Reviewed-by: Aishwarya R Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260626085253.3927269-4-sreeramya.soratkal@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/debugfs.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/debugfs.c b/drivers/net/wireless/ath/ath12k/debugfs.c index 57c213111259..d54995b7adb2 100644 --- a/drivers/net/wireless/ath/ath12k/debugfs.c +++ b/drivers/net/wireless/ath/ath12k/debugfs.c @@ -1031,6 +1031,7 @@ static ssize_t ath12k_debugfs_dump_device_dp_stats(struct file *file, struct ath12k_device_dp_stats *device_stats = &dp->device_stats; int len = 0, i, j, ret; struct ath12k *ar; + u32 center_freq; const int size = 4096; static const char *rxdma_err[HAL_REO_ENTR_RING_RXDMA_ECODE_MAX] = { [HAL_REO_ENTR_RING_RXDMA_ECODE_OVERFLOW_ERR] = "Overflow", @@ -1164,6 +1165,12 @@ static ssize_t ath12k_debugfs_dump_device_dp_stats(struct file *file, for (i = 0; i < ab->num_radios; i++) { ar = ath12k_mac_get_ar_by_pdev_id(ab, DP_SW2HW_MACID(i)); if (ar) { + spin_lock_bh(&ar->data_lock); + center_freq = ar->rx_channel ? ar->rx_channel->center_freq : 0; + spin_unlock_bh(&ar->data_lock); + len += scnprintf(buf + len, size - len, + "\nradio%d center_freq: %u\n", + i, center_freq); len += scnprintf(buf + len, size - len, "\nradio%d tx_pending: %u\n", i, atomic_read(&ar->dp.num_tx_pending)); From 5a2b5d6a5a4a19b86d1c0698a3eb3d21f0b06401 Mon Sep 17 00:00:00 2001 From: Sushant Butta Date: Tue, 9 Jun 2026 12:18:55 +0530 Subject: [PATCH 0040/1433] wifi: ath12k: Skip setting RX_FLAG_8023 for Ethernet-II (DIX) frames in monitor mode Monitor mode delivers raw 802.11 frames, not 802.3/Ethernet frames. Setting RX_FLAG_8023 for monitor RX is incorrect and can break userspace capture and analysis. Do not update this flag in the monitor path to ensure correct handling of captured frames. In the monitor path, RX_FLAG_ONLY_MONITOR is always set before decap is evaluated, which forces decap to remain DP_RX_DECAP_TYPE_RAW. As a result, the condition to set RX_FLAG_8023 can never be satisfied. Hence, drop this unreachable code. Also remove the unused hal_rx_mon_ppdu_info parameter from ath12k_dp_mon_rx_deliver_msdu(), as it was passed but never used. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Signed-off-by: Sushant Butta Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260609064856.547032-2-sushant.butta@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp_mon.c | 16 +--------------- drivers/net/wireless/ath/ath12k/dp_mon.h | 4 +--- drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c | 7 +------ 3 files changed, 3 insertions(+), 24 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/dp_mon.c b/drivers/net/wireless/ath/ath12k/dp_mon.c index 44c5cff75f16..cfcfa93eeb44 100644 --- a/drivers/net/wireless/ath/ath12k/dp_mon.c +++ b/drivers/net/wireless/ath/ath12k/dp_mon.c @@ -493,9 +493,7 @@ EXPORT_SYMBOL(ath12k_dp_mon_update_radiotap); void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struct *napi, struct sk_buff *msdu, - const struct hal_rx_mon_ppdu_info *ppduinfo, - struct ieee80211_rx_status *status, - u8 decap) + struct ieee80211_rx_status *status) { struct ath12k_dp *dp = dp_pdev->dp; struct ath12k_base *ab = dp->ab; @@ -511,7 +509,6 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct ath12k_skb_rxcb *rxcb = ATH12K_SKB_RXCB(msdu); struct hal_rx_desc_data rx_info; bool is_mcbc = rxcb->is_mcbc; - bool is_eapol_tkip = rxcb->is_eapol; struct hal_rx_desc *rx_desc = (struct hal_rx_desc *)msdu->data; u8 addr[ETH_ALEN] = {}; @@ -570,17 +567,6 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, rx_status = IEEE80211_SKB_RXCB(msdu); *rx_status = *status; - /* TODO: trace rx packet */ - - /* PN for multicast packets are not validate in HW, - * so skip 802.3 rx path - * Also, fast_rx expects the STA to be authorized, hence - * eapol packets are sent in slow path. - */ - if (decap == DP_RX_DECAP_TYPE_ETHERNET2_DIX && !is_eapol_tkip && - !(is_mcbc && rx_status->flag & RX_FLAG_DECRYPTED)) - rx_status->flag |= RX_FLAG_8023; - ieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), pubsta, msdu, napi); } EXPORT_SYMBOL(ath12k_dp_mon_rx_deliver_msdu); diff --git a/drivers/net/wireless/ath/ath12k/dp_mon.h b/drivers/net/wireless/ath/ath12k/dp_mon.h index 167028d27513..162cdcaa57a7 100644 --- a/drivers/net/wireless/ath/ath12k/dp_mon.h +++ b/drivers/net/wireless/ath/ath12k/dp_mon.h @@ -112,9 +112,7 @@ void ath12k_dp_mon_update_radiotap(struct ath12k_pdev_dp *dp_pdev, void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struct *napi, struct sk_buff *msdu, - const struct hal_rx_mon_ppdu_info *ppduinfo, - struct ieee80211_rx_status *status, - u8 decap); + struct ieee80211_rx_status *status); struct sk_buff * ath12k_dp_mon_rx_merg_msdus(struct ath12k_pdev_dp *dp_pdev, struct dp_mon_mpdu *mon_mpdu, diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c index c84c42a3d377..016b0c38e51e 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c @@ -2481,7 +2481,6 @@ ath12k_wifi7_dp_mon_rx_deliver(struct ath12k_pdev_dp *dp_pdev, { struct sk_buff *mon_skb, *skb_next, *header; struct ieee80211_rx_status *rxs = &dp_pdev->rx_status; - u8 decap = DP_RX_DECAP_TYPE_RAW; mon_skb = ath12k_dp_mon_rx_merg_msdus(dp_pdev, mon_mpdu, ppduinfo, rxs); if (!mon_skb) @@ -2508,12 +2507,8 @@ ath12k_wifi7_dp_mon_rx_deliver(struct ath12k_pdev_dp *dp_pdev, } rxs->flag |= RX_FLAG_ONLY_MONITOR; - if (!(rxs->flag & RX_FLAG_ONLY_MONITOR)) - decap = mon_mpdu->decap_format; - ath12k_dp_mon_update_radiotap(dp_pdev, ppduinfo, mon_skb, rxs); - ath12k_dp_mon_rx_deliver_msdu(dp_pdev, napi, mon_skb, ppduinfo, - rxs, decap); + ath12k_dp_mon_rx_deliver_msdu(dp_pdev, napi, mon_skb, rxs); mon_skb = skb_next; } while (mon_skb); rxs->flag = 0; From 56f8f12c1a3c5312de0d7312b229d7bca03dbb81 Mon Sep 17 00:00:00 2001 From: Sushant Butta Date: Tue, 9 Jun 2026 12:18:56 +0530 Subject: [PATCH 0041/1433] wifi: ath12k: Skip peer link info update in rx_status for monitor MSDUs Do not populate peer and link_id in ieee80211_rx_status for monitor MSDUs. The monitor RX path is handled differently in mac80211 when RX_FLAG_ONLY_MONITOR is set, and does not consume peer/link metadata. As such, looking up the peer and updating link_id here is unnecessary. Additionally, this metadata is not required for monitor mode delivery, and performing the lookup/update introduces redundant work and the potential for inconsistent rx_status state if multiple paths modify it. Hence, remove the peer lookup and link_id update from the monitor MSDU delivery path. This also removes the per-MSDU debug logging in the monitor path, slightly reducing debuggability, but avoids unnecessary overhead in the monitor RX path. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Signed-off-by: Sushant Butta Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260609064856.547032-3-sushant.butta@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp_mon.c | 54 +----------------------- 1 file changed, 1 insertion(+), 53 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/dp_mon.c b/drivers/net/wireless/ath/ath12k/dp_mon.c index cfcfa93eeb44..7d5be77b081f 100644 --- a/drivers/net/wireless/ath/ath12k/dp_mon.c +++ b/drivers/net/wireless/ath/ath12k/dp_mon.c @@ -495,8 +495,6 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct sk_buff *msdu, struct ieee80211_rx_status *status) { - struct ath12k_dp *dp = dp_pdev->dp; - struct ath12k_base *ab = dp->ab; static const struct ieee80211_radiotap_he known = { .data1 = cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_DATA_MCS_KNOWN | IEEE80211_RADIOTAP_HE_DATA1_BW_RU_ALLOC_KNOWN), @@ -504,13 +502,6 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, }; struct ieee80211_rx_status *rx_status; struct ieee80211_radiotap_he *he = NULL; - struct ieee80211_sta *pubsta = NULL; - struct ath12k_dp_link_peer *peer; - struct ath12k_skb_rxcb *rxcb = ATH12K_SKB_RXCB(msdu); - struct hal_rx_desc_data rx_info; - bool is_mcbc = rxcb->is_mcbc; - struct hal_rx_desc *rx_desc = (struct hal_rx_desc *)msdu->data; - u8 addr[ETH_ALEN] = {}; status->link_valid = 0; @@ -521,53 +512,10 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, status->flag |= RX_FLAG_RADIOTAP_HE; } - ath12k_dp_extract_rx_desc_data(dp->hal, &rx_info, rx_desc, rx_desc); - - rcu_read_lock(); - spin_lock_bh(&dp->dp_lock); - peer = ath12k_dp_rx_h_find_link_peer(dp_pdev, msdu, &rx_info); - if (peer && peer->sta) { - pubsta = peer->sta; - memcpy(addr, peer->addr, ETH_ALEN); - if (pubsta->valid_links) { - status->link_valid = 1; - status->link_id = peer->link_id; - } - } - - spin_unlock_bh(&dp->dp_lock); - rcu_read_unlock(); - - ath12k_dbg(ab, ATH12K_DBG_DATA, - "rx skb %p len %u peer %pM %u %s %s%s%s%s%s%s%s%s %srate_idx %u vht_nss %u freq %u band %u flag 0x%x fcs-err %i mic-err %i amsdu-more %i\n", - msdu, - msdu->len, - addr, - rxcb->tid, - (is_mcbc) ? "mcast" : "ucast", - (status->encoding == RX_ENC_LEGACY) ? "legacy" : "", - (status->encoding == RX_ENC_HT) ? "ht" : "", - (status->encoding == RX_ENC_VHT) ? "vht" : "", - (status->encoding == RX_ENC_HE) ? "he" : "", - (status->bw == RATE_INFO_BW_40) ? "40" : "", - (status->bw == RATE_INFO_BW_80) ? "80" : "", - (status->bw == RATE_INFO_BW_160) ? "160" : "", - (status->bw == RATE_INFO_BW_320) ? "320" : "", - status->enc_flags & RX_ENC_FLAG_SHORT_GI ? "sgi " : "", - status->rate_idx, - status->nss, - status->freq, - status->band, status->flag, - !!(status->flag & RX_FLAG_FAILED_FCS_CRC), - !!(status->flag & RX_FLAG_MMIC_ERROR), - !!(status->flag & RX_FLAG_AMSDU_MORE)); - - ath12k_dbg_dump(ab, ATH12K_DBG_DP_RX, NULL, "dp rx msdu: ", - msdu->data, msdu->len); rx_status = IEEE80211_SKB_RXCB(msdu); *rx_status = *status; - ieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), pubsta, msdu, napi); + ieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), NULL, msdu, napi); } EXPORT_SYMBOL(ath12k_dp_mon_rx_deliver_msdu); From 58aeb412495ada7fe5495c7805504d7cf1d45453 Mon Sep 17 00:00:00 2001 From: Yingying Tang Date: Tue, 9 Jun 2026 20:13:58 -0700 Subject: [PATCH 0042/1433] wifi: ath12k: change MAC buffer ring size to 4096 For WCN7850, MAC buffer ring size is updated to 2048 in 955df16f2a4c3 ("wifi: ath12k: change MAC buffer ring size to 2048") to increase peak throughput. But during the RX process, a phenomenon can still be observed where the throughput drops by about 30% from its peak value and then recovers, and this behavior repeats during RX. After increasing MAC buffer ring size to 4096, the data rate drop has gone. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Yingying Tang Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260610031358.2043716-1-yingying.tang@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/dp.h b/drivers/net/wireless/ath/ath12k/dp.h index 9b39146e65e1..64f79e43341e 100644 --- a/drivers/net/wireless/ath/ath12k/dp.h +++ b/drivers/net/wireless/ath/ath12k/dp.h @@ -205,7 +205,7 @@ struct ath12k_pdev_dp { #define DP_REO_CMD_RING_SIZE 256 #define DP_REO_STATUS_RING_SIZE 2048 #define DP_RXDMA_BUF_RING_SIZE 4096 -#define DP_RX_MAC_BUF_RING_SIZE 2048 +#define DP_RX_MAC_BUF_RING_SIZE 4096 #define DP_RXDMA_REFILL_RING_SIZE 2048 #define DP_RXDMA_ERR_DST_RING_SIZE 1024 #define DP_RXDMA_MON_STATUS_RING_SIZE 1024 From 913998f903fb1432c0046c33003db38a9e8bedb1 Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Tue, 16 Jun 2026 11:53:42 +0530 Subject: [PATCH 0043/1433] wifi: ath12k: correct monitor destination ring size The default memory profile configures rxdma_monitor_dst_ring_size as 8092, which is a typo. The intended value is 8192, consistent with all other ring sizes in the table being powers of two. Correct the monitor destination ring size to 8192. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Fixes: defae535dd63 ("wifi: ath12k: Add a table of parameters entries impacting memory consumption") Signed-off-by: Aaradhana Sahu Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260616062342.4079796-1-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index efe37dc91afd..0e7c732f8222 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -49,7 +49,7 @@ ath12k_mem_profile_based_param ath12k_mem_profile_based_param[] = { .dp_params = { .tx_comp_ring_size = 32768, .rxdma_monitor_buf_ring_size = 4096, - .rxdma_monitor_dst_ring_size = 8092, + .rxdma_monitor_dst_ring_size = 8192, .num_pool_tx_desc = 32768, .rx_desc_count = 12288, }, From a3fc56b12e3acc0e04680091a73c1ae6db8dbb81 Mon Sep 17 00:00:00 2001 From: Alejandro Lucero Date: Tue, 30 Jun 2026 16:13:42 +0100 Subject: [PATCH 0044/1433] sfc: add cxl support Add CXL initialization based on new CXL API for accel drivers and make it dependent on kernel CXL configuration. Signed-off-by: Alejandro Lucero Reviewed-by: Jonathan Cameron Acked-by: Edward Cree Reviewed-by: Alison Schofield Reviewed-by: Dan Williams Reviewed-by: Dave Jiang Link: https://patch.msgid.link/20260630151346.31201-2-alejandro.lucero-palau@amd.com Signed-off-by: Dave Jiang --- drivers/net/ethernet/sfc/Kconfig | 9 +++++ drivers/net/ethernet/sfc/Makefile | 1 + drivers/net/ethernet/sfc/efx.c | 16 ++++++++- drivers/net/ethernet/sfc/efx_cxl.c | 50 +++++++++++++++++++++++++++ drivers/net/ethernet/sfc/efx_cxl.h | 29 ++++++++++++++++ drivers/net/ethernet/sfc/net_driver.h | 8 +++++ 6 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 drivers/net/ethernet/sfc/efx_cxl.c create mode 100644 drivers/net/ethernet/sfc/efx_cxl.h diff --git a/drivers/net/ethernet/sfc/Kconfig b/drivers/net/ethernet/sfc/Kconfig index c4c43434f314..979f2801e2a8 100644 --- a/drivers/net/ethernet/sfc/Kconfig +++ b/drivers/net/ethernet/sfc/Kconfig @@ -66,6 +66,15 @@ config SFC_MCDI_LOGGING Driver-Interface) commands and responses, allowing debugging of driver/firmware interaction. The tracing is actually enabled by a sysfs file 'mcdi_logging' under the PCI device. +config SFC_CXL + bool "Solarflare SFC9100-family CXL support" + depends on SFC && CXL_BUS >= SFC + default SFC + help + This enables SFC CXL support if the kernel is configuring CXL for + using CTPIO with CXL.mem. The SFC device with CXL support and + with a CXL-aware firmware can be used for minimizing latencies + when sending through CTPIO. source "drivers/net/ethernet/sfc/falcon/Kconfig" source "drivers/net/ethernet/sfc/siena/Kconfig" diff --git a/drivers/net/ethernet/sfc/Makefile b/drivers/net/ethernet/sfc/Makefile index d99039ec468d..bb0f1891cde6 100644 --- a/drivers/net/ethernet/sfc/Makefile +++ b/drivers/net/ethernet/sfc/Makefile @@ -13,6 +13,7 @@ sfc-$(CONFIG_SFC_SRIOV) += sriov.o ef10_sriov.o ef100_sriov.o ef100_rep.o \ mae.o tc.o tc_bindings.o tc_counters.o \ tc_encap_actions.o tc_conntrack.o +sfc-$(CONFIG_SFC_CXL) += efx_cxl.o obj-$(CONFIG_SFC) += sfc.o obj-$(CONFIG_SFC_FALCON) += falcon/ diff --git a/drivers/net/ethernet/sfc/efx.c b/drivers/net/ethernet/sfc/efx.c index 8f136a11d396..61cbb6cfc360 100644 --- a/drivers/net/ethernet/sfc/efx.c +++ b/drivers/net/ethernet/sfc/efx.c @@ -34,6 +34,7 @@ #include "selftest.h" #include "sriov.h" #include "efx_devlink.h" +#include "efx_cxl.h" #include "mcdi_port_common.h" #include "mcdi_pcol.h" @@ -981,12 +982,14 @@ static void efx_pci_remove(struct pci_dev *pci_dev) efx_pci_remove_main(efx); efx_fini_io(efx); + + probe_data = container_of(efx, struct efx_probe_data, efx); + pci_dbg(efx->pci_dev, "shutdown successful\n"); efx_fini_devlink_and_unlock(efx); efx_fini_struct(efx); free_netdev(efx->net_dev); - probe_data = container_of(efx, struct efx_probe_data, efx); kfree(probe_data); }; @@ -1190,6 +1193,17 @@ static int efx_pci_probe(struct pci_dev *pci_dev, if (rc) goto fail2; + /* A successful cxl initialization implies a CXL region created to be + * used for PIO buffers. If there is no CXL support legacy PIO buffers + * defined at specific PCI BAR regions will be used. If there is CXL + * support and the cxl initialization fails, the driver probe fails. + */ + rc = efx_cxl_init(probe_data); + if (rc) { + pci_err(pci_dev, "CXL initialization failed with error %d\n", rc); + goto fail3; + } + rc = efx_pci_probe_post_io(efx); if (rc) { /* On failure, retry once immediately. diff --git a/drivers/net/ethernet/sfc/efx_cxl.c b/drivers/net/ethernet/sfc/efx_cxl.c new file mode 100644 index 000000000000..be252af972ab --- /dev/null +++ b/drivers/net/ethernet/sfc/efx_cxl.c @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0-only +/**************************************************************************** + * + * Driver for AMD network controllers and boards + * Copyright (C) 2025, Advanced Micro Devices, Inc. + */ + +#include + +#include "net_driver.h" +#include "efx_cxl.h" + +#define EFX_CTPIO_BUFFER_SIZE SZ_256M + +int efx_cxl_init(struct efx_probe_data *probe_data) +{ + struct efx_nic *efx = &probe_data->efx; + struct pci_dev *pci_dev = efx->pci_dev; + struct efx_cxl *cxl; + u16 dvsec; + + /* Is the device configured with and using CXL? */ + if (!pcie_is_cxl(pci_dev)) + return 0; + + dvsec = pci_find_dvsec_capability(pci_dev, PCI_VENDOR_ID_CXL, + PCI_DVSEC_CXL_DEVICE); + if (!dvsec) { + pci_info(pci_dev, "CXL_DVSEC_PCIE_DEVICE capability not found\n"); + return 0; + } + + pci_dbg(pci_dev, "CXL_DVSEC_PCIE_DEVICE capability found\n"); + + /* Create a cxl_dev_state embedded in the cxl struct using cxl core api + * specifying no mbox available. + */ + cxl = devm_cxl_dev_state_create(&pci_dev->dev, CXL_DEVTYPE_DEVMEM, + pci_get_dsn(pci_dev), dvsec, + struct efx_cxl, cxlds, false); + + if (!cxl) + return -ENOMEM; + + probe_data->cxl = cxl; + + return 0; +} + +MODULE_IMPORT_NS("CXL"); diff --git a/drivers/net/ethernet/sfc/efx_cxl.h b/drivers/net/ethernet/sfc/efx_cxl.h new file mode 100644 index 000000000000..04e46278464d --- /dev/null +++ b/drivers/net/ethernet/sfc/efx_cxl.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/**************************************************************************** + * Driver for AMD network controllers and boards + * Copyright (C) 2025, Advanced Micro Devices, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation, incorporated herein by reference. + */ + +#ifndef EFX_CXL_H +#define EFX_CXL_H + +#ifdef CONFIG_SFC_CXL + +#include + +struct efx_probe_data; + +struct efx_cxl { + struct cxl_dev_state cxlds; + struct cxl_memdev *cxlmd; +}; + +int efx_cxl_init(struct efx_probe_data *probe_data); +#else +static inline int efx_cxl_init(struct efx_probe_data *probe_data) { return 0; } +#endif +#endif diff --git a/drivers/net/ethernet/sfc/net_driver.h b/drivers/net/ethernet/sfc/net_driver.h index b98c259f672d..563e6a6e85f1 100644 --- a/drivers/net/ethernet/sfc/net_driver.h +++ b/drivers/net/ethernet/sfc/net_driver.h @@ -1197,14 +1197,22 @@ struct efx_nic { atomic_t n_rx_noskb_drops; }; +#ifdef CONFIG_SFC_CXL +struct efx_cxl; +#endif + /** * struct efx_probe_data - State after hardware probe * @pci_dev: The PCI device * @efx: Efx NIC details + * @cxl: details of related cxl objects */ struct efx_probe_data { struct pci_dev *pci_dev; struct efx_nic efx; +#ifdef CONFIG_SFC_CXL + struct efx_cxl *cxl; +#endif }; static inline struct efx_nic *efx_netdev_priv(struct net_device *dev) From 08699796dabf6f6256e49ebd52af3008cfd96d64 Mon Sep 17 00:00:00 2001 From: Alejandro Lucero Date: Tue, 30 Jun 2026 16:13:43 +0100 Subject: [PATCH 0045/1433] sfc: Map cxl regs Use cxl core functions for discovering and mapping CXL device registers. Signed-off-by: Alejandro Lucero Reviewed-by: Dan Williams Reviewed-by: Jonathan Cameron Reviewed-by: Dave Jiang Reviewed-by: Ben Cheatham Acked-by: Edward Cree Link: https://patch.msgid.link/20260630151346.31201-3-alejandro.lucero-palau@amd.com Signed-off-by: Dave Jiang --- drivers/net/ethernet/sfc/efx_cxl.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/drivers/net/ethernet/sfc/efx_cxl.c b/drivers/net/ethernet/sfc/efx_cxl.c index be252af972ab..704b0ebae937 100644 --- a/drivers/net/ethernet/sfc/efx_cxl.c +++ b/drivers/net/ethernet/sfc/efx_cxl.c @@ -7,6 +7,8 @@ #include +#include +#include #include "net_driver.h" #include "efx_cxl.h" @@ -18,6 +20,7 @@ int efx_cxl_init(struct efx_probe_data *probe_data) struct pci_dev *pci_dev = efx->pci_dev; struct efx_cxl *cxl; u16 dvsec; + int rc; /* Is the device configured with and using CXL? */ if (!pcie_is_cxl(pci_dev)) @@ -42,6 +45,29 @@ int efx_cxl_init(struct efx_probe_data *probe_data) if (!cxl) return -ENOMEM; + rc = cxl_pci_setup_regs(pci_dev, CXL_REGLOC_RBI_COMPONENT, + &cxl->cxlds.reg_map); + if (rc) { + pci_err(pci_dev, "No component registers\n"); + return rc; + } + + if (!cxl->cxlds.reg_map.component_map.hdm_decoder.valid) { + pci_err(pci_dev, "Expected HDM component register not found\n"); + return -ENODEV; + } + + if (!cxl->cxlds.reg_map.component_map.ras.valid) { + pci_err(pci_dev, "Expected RAS component register not found\n"); + return -ENODEV; + } + + /* Set media ready explicitly as there are neither mailbox for checking + * this state nor the CXL register involved, both not mandatory for + * type2. + */ + cxl->cxlds.media_ready = true; + probe_data->cxl = cxl; return 0; From 230284c1b1654d9ba51d9e33bd5fd15c7847c13e Mon Sep 17 00:00:00 2001 From: Alejandro Lucero Date: Tue, 30 Jun 2026 16:13:44 +0100 Subject: [PATCH 0046/1433] sfc: Initialize cxl dpa Use cxl_set_capacity() for DPA initialization as no mailbox is available. Signed-off-by: Alejandro Lucero Reviewed-by: Dan Williams Reviewed-by: Dave Jiang Reviewed-by: Ben Cheatham Reviewed-by: Jonathan Cameron Acked-by: Edward Cree Link: https://patch.msgid.link/20260630151346.31201-4-alejandro.lucero-palau@amd.com Signed-off-by: Dave Jiang --- drivers/net/ethernet/sfc/efx_cxl.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/ethernet/sfc/efx_cxl.c b/drivers/net/ethernet/sfc/efx_cxl.c index 704b0ebae937..18b535b3ea40 100644 --- a/drivers/net/ethernet/sfc/efx_cxl.c +++ b/drivers/net/ethernet/sfc/efx_cxl.c @@ -68,6 +68,11 @@ int efx_cxl_init(struct efx_probe_data *probe_data) */ cxl->cxlds.media_ready = true; + if (cxl_set_capacity(&cxl->cxlds, EFX_CTPIO_BUFFER_SIZE)) { + pci_err(pci_dev, "dpa capacity setup failed\n"); + return -ENODEV; + } + probe_data->cxl = cxl; return 0; From 0418860ef2ca8ab4e696ce3913fd76660c881c0e Mon Sep 17 00:00:00 2001 From: Alejandro Lucero Date: Tue, 30 Jun 2026 16:13:45 +0100 Subject: [PATCH 0047/1433] sfc: obtain and map cxl range using devm_cxl_probe_mem Use core API for safely obtain the CXL range linked to an HDM committed by the BIOS. Map such a range for being used as the ctpio buffer. A potential user space action through sysfs unbinding or core cxl modules remove will trigger sfc driver device detachment, with that case not racing with this mapping as this is done during driver probe and therefore protected with device lock against those user space actions. Signed-off-by: Alejandro Lucero Reviewed-by: Dave Jiang Acked-by: Edward Cree Signed-off-by: Dan Williams Link: https://patch.msgid.link/20260630151346.31201-5-alejandro.lucero-palau@amd.com Signed-off-by: Dave Jiang --- drivers/net/ethernet/sfc/efx.c | 2 ++ drivers/net/ethernet/sfc/efx_cxl.c | 23 +++++++++++++++++++++++ drivers/net/ethernet/sfc/efx_cxl.h | 3 +++ 3 files changed, 28 insertions(+) diff --git a/drivers/net/ethernet/sfc/efx.c b/drivers/net/ethernet/sfc/efx.c index 61cbb6cfc360..3806cd3dd7f4 100644 --- a/drivers/net/ethernet/sfc/efx.c +++ b/drivers/net/ethernet/sfc/efx.c @@ -984,6 +984,7 @@ static void efx_pci_remove(struct pci_dev *pci_dev) efx_fini_io(efx); probe_data = container_of(efx, struct efx_probe_data, efx); + efx_cxl_exit(probe_data); pci_dbg(efx->pci_dev, "shutdown successful\n"); @@ -1242,6 +1243,7 @@ static int efx_pci_probe(struct pci_dev *pci_dev, return 0; fail3: + efx_cxl_exit(probe_data); efx_fini_io(efx); fail2: efx_fini_struct(efx); diff --git a/drivers/net/ethernet/sfc/efx_cxl.c b/drivers/net/ethernet/sfc/efx_cxl.c index 18b535b3ea40..3e7c950f83e9 100644 --- a/drivers/net/ethernet/sfc/efx_cxl.c +++ b/drivers/net/ethernet/sfc/efx_cxl.c @@ -18,6 +18,7 @@ int efx_cxl_init(struct efx_probe_data *probe_data) { struct efx_nic *efx = &probe_data->efx; struct pci_dev *pci_dev = efx->pci_dev; + struct range cxl_pio_range; struct efx_cxl *cxl; u16 dvsec; int rc; @@ -73,9 +74,31 @@ int efx_cxl_init(struct efx_probe_data *probe_data) return -ENODEV; } + cxl->cxlmd = devm_cxl_probe_mem(&cxl->cxlds, &cxl_pio_range); + if (IS_ERR(cxl->cxlmd)) { + pci_err(pci_dev, "CXL accel memdev creation failed\n"); + return PTR_ERR(cxl->cxlmd); + } + + cxl->ctpio_cxl = ioremap_wc(cxl_pio_range.start, + range_len(&cxl_pio_range)); + if (!cxl->ctpio_cxl) { + pci_err(pci_dev, "CXL ioremap region (%pra) failed\n", + &cxl_pio_range); + return -ENOMEM; + } + probe_data->cxl = cxl; return 0; } +void efx_cxl_exit(struct efx_probe_data *probe_data) +{ + if (!probe_data->cxl) + return; + + iounmap(probe_data->cxl->ctpio_cxl); +} + MODULE_IMPORT_NS("CXL"); diff --git a/drivers/net/ethernet/sfc/efx_cxl.h b/drivers/net/ethernet/sfc/efx_cxl.h index 04e46278464d..3e2705cb063f 100644 --- a/drivers/net/ethernet/sfc/efx_cxl.h +++ b/drivers/net/ethernet/sfc/efx_cxl.h @@ -20,10 +20,13 @@ struct efx_probe_data; struct efx_cxl { struct cxl_dev_state cxlds; struct cxl_memdev *cxlmd; + void __iomem *ctpio_cxl; }; int efx_cxl_init(struct efx_probe_data *probe_data); +void efx_cxl_exit(struct efx_probe_data *probe_data); #else static inline int efx_cxl_init(struct efx_probe_data *probe_data) { return 0; } +static inline void efx_cxl_exit(struct efx_probe_data *probe_data) {} #endif #endif From bd6550bcdb0c0bcc6e29706ffe2e64708192342b Mon Sep 17 00:00:00 2001 From: Alejandro Lucero Date: Tue, 30 Jun 2026 16:13:46 +0100 Subject: [PATCH 0048/1433] sfc: support pio mapping based on cxl A PIO buffer is a region of device memory to which the driver can write a packet for TX, with the device handling the transmit doorbell without requiring a DMA for getting the packet data, which helps reducing latency in certain exchanges. With CXL mem protocol this latency can be lowered further. With a device supporting CXL and successfully initialised, use the cxl region to map the memory range and use this mapping for PIO buffers. Signed-off-by: Alejandro Lucero Reviewed-by: Dave Jiang Acked-by: Edward Cree Link: https://patch.msgid.link/20260630151346.31201-6-alejandro.lucero-palau@amd.com Signed-off-by: Dave Jiang --- drivers/net/ethernet/sfc/ef10.c | 41 ++++++++++++++++++++++----- drivers/net/ethernet/sfc/efx_cxl.c | 1 + drivers/net/ethernet/sfc/net_driver.h | 2 ++ drivers/net/ethernet/sfc/nic.h | 3 ++ 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/sfc/ef10.c b/drivers/net/ethernet/sfc/ef10.c index 7e04f115bbaa..73bc064929f6 100644 --- a/drivers/net/ethernet/sfc/ef10.c +++ b/drivers/net/ethernet/sfc/ef10.c @@ -24,6 +24,7 @@ #include #include #include +#include "efx_cxl.h" /* Hardware control for EF10 architecture including 'Huntington'. */ @@ -106,7 +107,7 @@ static int efx_ef10_get_vf_index(struct efx_nic *efx) static int efx_ef10_init_datapath_caps(struct efx_nic *efx) { - MCDI_DECLARE_BUF(outbuf, MC_CMD_GET_CAPABILITIES_V4_OUT_LEN); + MCDI_DECLARE_BUF(outbuf, MC_CMD_GET_CAPABILITIES_V7_OUT_LEN); struct efx_ef10_nic_data *nic_data = efx->nic_data; size_t outlen; int rc; @@ -177,6 +178,12 @@ static int efx_ef10_init_datapath_caps(struct efx_nic *efx) efx->num_mac_stats); } + if (outlen < MC_CMD_GET_CAPABILITIES_V7_OUT_LEN) + nic_data->datapath_caps3 = 0; + else + nic_data->datapath_caps3 = MCDI_DWORD(outbuf, + GET_CAPABILITIES_V7_OUT_FLAGS3); + return 0; } @@ -1140,6 +1147,9 @@ static int efx_ef10_dimension_resources(struct efx_nic *efx) unsigned int channel_vis, pio_write_vi_base, max_vis; struct efx_ef10_nic_data *nic_data = efx->nic_data; unsigned int uc_mem_map_size, wc_mem_map_size; +#ifdef CONFIG_SFC_CXL + struct efx_probe_data *probe_data; +#endif void __iomem *membase; int rc; @@ -1263,8 +1273,23 @@ static int efx_ef10_dimension_resources(struct efx_nic *efx) iounmap(efx->membase); efx->membase = membase; - /* Set up the WC mapping if needed */ - if (wc_mem_map_size) { + if (!wc_mem_map_size) + goto skip_pio; + + /* Set up the WC mapping */ + +#ifdef CONFIG_SFC_CXL + probe_data = container_of(efx, struct efx_probe_data, efx); + if ((nic_data->datapath_caps3 & + (1 << MC_CMD_GET_CAPABILITIES_V7_OUT_CXL_CONFIG_ENABLE_LBN)) && + probe_data->cxl_pio_initialised) { + /* Using PIO through CXL mapping */ + nic_data->pio_write_base = probe_data->cxl->ctpio_cxl; + nic_data->pio_write_vi_base = pio_write_vi_base; + } else +#endif + { + /* Using legacy PIO BAR mapping */ nic_data->wc_membase = ioremap_wc(efx->membase_phys + uc_mem_map_size, wc_mem_map_size); @@ -1279,12 +1304,14 @@ static int efx_ef10_dimension_resources(struct efx_nic *efx) nic_data->wc_membase + (pio_write_vi_base * efx->vi_stride + ER_DZ_TX_PIOBUF - uc_mem_map_size); - - rc = efx_ef10_link_piobufs(efx); - if (rc) - efx_ef10_free_piobufs(efx); } + rc = efx_ef10_link_piobufs(efx); + if (rc) + efx_ef10_free_piobufs(efx); + +skip_pio: + netif_dbg(efx, probe, efx->net_dev, "memory BAR at %pa (virtual %p+%x UC, %p+%x WC)\n", &efx->membase_phys, efx->membase, uc_mem_map_size, diff --git a/drivers/net/ethernet/sfc/efx_cxl.c b/drivers/net/ethernet/sfc/efx_cxl.c index 3e7c950f83e9..348d7404cd7a 100644 --- a/drivers/net/ethernet/sfc/efx_cxl.c +++ b/drivers/net/ethernet/sfc/efx_cxl.c @@ -88,6 +88,7 @@ int efx_cxl_init(struct efx_probe_data *probe_data) return -ENOMEM; } + probe_data->cxl_pio_initialised = true; probe_data->cxl = cxl; return 0; diff --git a/drivers/net/ethernet/sfc/net_driver.h b/drivers/net/ethernet/sfc/net_driver.h index 563e6a6e85f1..3964b2c56609 100644 --- a/drivers/net/ethernet/sfc/net_driver.h +++ b/drivers/net/ethernet/sfc/net_driver.h @@ -1206,12 +1206,14 @@ struct efx_cxl; * @pci_dev: The PCI device * @efx: Efx NIC details * @cxl: details of related cxl objects + * @cxl_pio_initialised: cxl initialization outcome. */ struct efx_probe_data { struct pci_dev *pci_dev; struct efx_nic efx; #ifdef CONFIG_SFC_CXL struct efx_cxl *cxl; + bool cxl_pio_initialised; #endif }; diff --git a/drivers/net/ethernet/sfc/nic.h b/drivers/net/ethernet/sfc/nic.h index ec3b2df43b68..7480f9995dfb 100644 --- a/drivers/net/ethernet/sfc/nic.h +++ b/drivers/net/ethernet/sfc/nic.h @@ -152,6 +152,8 @@ enum { * %MC_CMD_GET_CAPABILITIES response) * @datapath_caps2: Further Capabilities of datapath firmware (FLAGS2 field of * %MC_CMD_GET_CAPABILITIES response) + * @datapath_caps3: Further Capabilities of datapath firmware (FLAGS3 field of + * %MC_CMD_GET_CAPABILITIES response) * @rx_dpcpu_fw_id: Firmware ID of the RxDPCPU * @tx_dpcpu_fw_id: Firmware ID of the TxDPCPU * @must_probe_vswitching: Flag: vswitching has yet to be setup after MC reboot @@ -187,6 +189,7 @@ struct efx_ef10_nic_data { bool must_check_datapath_caps; u32 datapath_caps; u32 datapath_caps2; + u32 datapath_caps3; unsigned int rx_dpcpu_fw_id; unsigned int tx_dpcpu_fw_id; bool must_probe_vswitching; From a53d1872f2be6574460bf3980777d3f26cf3f107 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 29 Jun 2026 15:26:26 +0200 Subject: [PATCH 0049/1433] net: replace linux/gpio.h inclusions linux/gpio.h should no longer be used, change these in drivers/net to linux/gpio/consumer.h where possible, with b53 being the only one still using linux/gpio/legacy.h. Signed-off-by: Arnd Bergmann Acked-by: Bartosz Golaszewski Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260629132633.1300009-7-arnd@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/dsa/b53/b53_priv.h | 3 ++- drivers/net/dsa/microchip/ksz8.c | 2 +- drivers/net/ethernet/allwinner/sun4i-emac.c | 2 +- drivers/net/ethernet/apm/xgene/xgene_enet_main.c | 2 +- drivers/net/ethernet/oki-semi/pch_gbe/pch_gbe_main.c | 2 +- drivers/net/phy/mdio_device.c | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/net/dsa/b53/b53_priv.h b/drivers/net/dsa/b53/b53_priv.h index cd27a7344e89..29cca7945df7 100644 --- a/drivers/net/dsa/b53/b53_priv.h +++ b/drivers/net/dsa/b53/b53_priv.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "b53_regs.h" @@ -467,7 +468,7 @@ static inline void b53_arl_search_read(struct b53_device *dev, u8 idx, #ifdef CONFIG_BCM47XX #include -#include +#include #include static inline struct gpio_desc *b53_switch_get_reset_gpio(struct b53_device *dev) { diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index 138f2ab0774e..586916570a84 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/drivers/net/ethernet/allwinner/sun4i-emac.c b/drivers/net/ethernet/allwinner/sun4i-emac.c index fc7341a5cbb7..42174249ef61 100644 --- a/drivers/net/ethernet/allwinner/sun4i-emac.c +++ b/drivers/net/ethernet/allwinner/sun4i-emac.c @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/drivers/net/ethernet/apm/xgene/xgene_enet_main.c b/drivers/net/ethernet/apm/xgene/xgene_enet_main.c index 3b2951030a38..507db46daf2b 100644 --- a/drivers/net/ethernet/apm/xgene/xgene_enet_main.c +++ b/drivers/net/ethernet/apm/xgene/xgene_enet_main.c @@ -7,7 +7,7 @@ * Keyur Chudgar */ -#include +#include #include "xgene_enet_main.h" #include "xgene_enet_hw.h" #include "xgene_enet_sgmac.h" diff --git a/drivers/net/ethernet/oki-semi/pch_gbe/pch_gbe_main.c b/drivers/net/ethernet/oki-semi/pch_gbe/pch_gbe_main.c index 48b94ce77490..88c5c52e0e38 100644 --- a/drivers/net/ethernet/oki-semi/pch_gbe/pch_gbe_main.c +++ b/drivers/net/ethernet/oki-semi/pch_gbe/pch_gbe_main.c @@ -16,7 +16,7 @@ #include #include #include -#include +#include #define PCH_GBE_MAR_ENTRIES 16 #define PCH_GBE_SHORT_PKT 64 diff --git a/drivers/net/phy/mdio_device.c b/drivers/net/phy/mdio_device.c index 56080d3d2d25..a18263d5bb02 100644 --- a/drivers/net/phy/mdio_device.c +++ b/drivers/net/phy/mdio_device.c @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include #include From 333289d1690d6089a656edb46c7cde68afe77223 Mon Sep 17 00:00:00 2001 From: Maciej Fijalkowski Date: Mon, 29 Jun 2026 21:12:21 +0200 Subject: [PATCH 0050/1433] selftests/xsk: Preserve UMEM view in BIDIRECTIONAL test The UMEM state refactor made __send_pkts() use xsk->umem for Tx address generation. At the same time, the shared-UMEM Tx setup copies the Rx UMEM state into a Tx-local state object and resets base_addr and next_buffer before configuring the Tx socket. Passing that Tx-local object to xsk_configure() makes xsk->umem point to the zero-based Tx allocator state. This breaks the BIDIRECTIONAL test once the roles are switched: the same socket is then used for Rx validation, but received descriptors from the other logical UMEM half are checked against base_addr == 0. With the new UMEM bounds check, a valid address such as base_addr + XDP_PACKET_HEADROOM is rejected as being outside the UMEM window. Keep xsk->umem as the shared/Rx UMEM view used for socket configuration and Rx validation. Use the ifobject-local UMEM copy only for Tx descriptor address generation, preserving the BIDIRECTIONAL test's intent of using the proper logical UMEM half after the direction switch. Reviewed-by: Jason Xing Reviewed-by: Tushar Vyavahare Tested-by: Tushar Vyavahare Signed-off-by: Maciej Fijalkowski Link: https://patch.msgid.link/20260629191221.2700-1-maciej.fijalkowski@intel.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/bpf/prog_tests/test_xsk.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.c b/tools/testing/selftests/bpf/prog_tests/test_xsk.c index 6eb9096d084c..477aedbb01ba 100644 --- a/tools/testing/selftests/bpf/prog_tests/test_xsk.c +++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.c @@ -1164,8 +1164,8 @@ static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk, bool test_timeout) { u32 i, idx = 0, valid_pkts = 0, valid_frags = 0, buffer_len; + struct xsk_umem_info *umem = ifobject->xsk_arr[0].umem_real; struct pkt_stream *pkt_stream = xsk->pkt_stream; - struct xsk_umem_info *umem = xsk->umem; bool use_poll = ifobject->use_poll; struct pollfd fds = { }; int ret; @@ -1514,7 +1514,7 @@ static int thread_common_ops_tx(struct test_spec *test, struct ifobject *ifobjec umem_tx->base_addr = 0; umem_tx->next_buffer = 0; - ret = xsk_configure(test, ifobject, umem_tx, true); + ret = xsk_configure(test, ifobject, umem_rx, true); if (ret) return ret; ifobject->xsk = &ifobject->xsk_arr[0]; From 317cefdcaacc409dfa371f086392ddbef914c99d Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 29 Jun 2026 17:32:00 +0000 Subject: [PATCH 0051/1433] bonding: no longer rely on RTNL in bond_fill_info() Add READ_ONCE()/WRITE_ONCE() annotations on port->is_enabled. While this field is written under bond->mode_lock protection, is is read without this lock being held. Change bond_fill_info() to acquire RCU and use READ_ONCE() to read bond->params fields that can be updated concurrently from sysfs/procfs/rtnetlink. Add const qualifiers to bond_uses_primary(), __agg_active_ports(), bond_option_active_slave_get_rcu(), bond_3ad_get_active_agg_info(), __bond_3ad_get_active_agg_info() helpers. Signed-off-by: Eric Dumazet Cc: Jay Vosburgh Cc: Andrew Lunn Reviewed-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260629173200.469953-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/bonding/bond_3ad.c | 24 ++++--- drivers/net/bonding/bond_netlink.c | 109 ++++++++++++++++------------- drivers/net/bonding/bond_options.c | 8 +-- include/net/bond_3ad.h | 4 +- include/net/bonding.h | 8 +-- 5 files changed, 85 insertions(+), 68 deletions(-) diff --git a/drivers/net/bonding/bond_3ad.c b/drivers/net/bonding/bond_3ad.c index acbba08dbdfa..b8e4b4d68dd6 100644 --- a/drivers/net/bonding/bond_3ad.c +++ b/drivers/net/bonding/bond_3ad.c @@ -760,14 +760,14 @@ static int __agg_usable_ports(struct aggregator *agg) return valid; } -static int __agg_active_ports(struct aggregator *agg) +static int __agg_active_ports(const struct aggregator *agg) { - struct port *port; + const struct port *port; int active = 0; for (port = agg->lag_ports; port; port = port->next_port_in_aggregator) { - if (port->is_enabled) + if (READ_ONCE(port->is_enabled)) active++; } @@ -2801,11 +2801,11 @@ void bond_3ad_handle_link_change(struct slave *slave, char link) * some of he adaptors(ce1000.lan) report. */ if (link == BOND_LINK_UP) { - port->is_enabled = true; + WRITE_ONCE(port->is_enabled, true); ad_update_actor_keys(port, false); } else { /* link has failed */ - port->is_enabled = false; + WRITE_ONCE(port->is_enabled, false); ad_update_actor_keys(port, true); } agg = __get_first_agg(port); @@ -2878,16 +2878,20 @@ int bond_3ad_set_carrier(struct bonding *bond) * Returns: 0 on success * < 0 on error */ -int __bond_3ad_get_active_agg_info(struct bonding *bond, +int __bond_3ad_get_active_agg_info(const struct bonding *bond, struct ad_info *ad_info) { - struct aggregator *aggregator = NULL, *tmp; + const struct aggregator *aggregator = NULL, *tmp; + struct ad_slave_info *ad_slave_info; + const struct port *port; struct list_head *iter; struct slave *slave; - struct port *port; bond_for_each_slave_rcu(bond, slave, iter) { - port = &(SLAVE_AD_INFO(slave)->port); + ad_slave_info = SLAVE_AD_INFO(slave); + if (!ad_slave_info) + continue; + port = &ad_slave_info->port; tmp = rcu_dereference(port->aggregator); if (tmp && tmp->is_active) { aggregator = tmp; @@ -2907,7 +2911,7 @@ int __bond_3ad_get_active_agg_info(struct bonding *bond, return 0; } -int bond_3ad_get_active_agg_info(struct bonding *bond, struct ad_info *ad_info) +int bond_3ad_get_active_agg_info(const struct bonding *bond, struct ad_info *ad_info) { int ret; diff --git a/drivers/net/bonding/bond_netlink.c b/drivers/net/bonding/bond_netlink.c index 4a11572f663d..55d2f8a539d4 100644 --- a/drivers/net/bonding/bond_netlink.c +++ b/drivers/net/bonding/bond_netlink.c @@ -686,53 +686,58 @@ static size_t bond_get_size(const struct net_device *bond_dev) 0; } -static int bond_option_active_slave_get_ifindex(struct bonding *bond) +static int bond_option_active_slave_get_ifindex_rcu(const struct bonding *bond) { - const struct net_device *slave; - int ifindex; + const struct net_device *dev = NULL; + const struct slave *slave; - rcu_read_lock(); - slave = bond_option_active_slave_get_rcu(bond); - ifindex = slave ? slave->ifindex : 0; - rcu_read_unlock(); - return ifindex; + slave = rcu_dereference(bond->curr_active_slave); + if (slave) + dev = slave->dev; + return dev ? dev->ifindex : 0; } static int bond_fill_info(struct sk_buff *skb, const struct net_device *bond_dev) { - struct bonding *bond = netdev_priv(bond_dev); - unsigned int packets_per_slave; - int ifindex, i, targets_added; + const struct bonding *bond = netdev_priv(bond_dev); + int i, targets_added, miimon, mode; + const struct slave *primary; struct nlattr *targets; - struct slave *primary; - if (nla_put_u8(skb, IFLA_BOND_MODE, BOND_MODE(bond))) + rcu_read_lock(); + mode = READ_ONCE(bond->params.mode); + if (nla_put_u8(skb, IFLA_BOND_MODE, mode)) goto nla_put_failure; - ifindex = bond_option_active_slave_get_ifindex(bond); - if (ifindex && nla_put_u32(skb, IFLA_BOND_ACTIVE_SLAVE, ifindex)) - goto nla_put_failure; + if (bond_mode_uses_primary(mode)) { + int ifindex = bond_option_active_slave_get_ifindex_rcu(bond); - if (nla_put_u32(skb, IFLA_BOND_MIIMON, bond->params.miimon)) + if (ifindex && nla_put_u32(skb, IFLA_BOND_ACTIVE_SLAVE, ifindex)) + goto nla_put_failure; + } + + miimon = READ_ONCE(bond->params.miimon); + if (nla_put_u32(skb, IFLA_BOND_MIIMON, miimon)) goto nla_put_failure; if (nla_put_u32(skb, IFLA_BOND_UPDELAY, - bond->params.updelay * bond->params.miimon)) + READ_ONCE(bond->params.updelay) * miimon)) goto nla_put_failure; if (nla_put_u32(skb, IFLA_BOND_DOWNDELAY, - bond->params.downdelay * bond->params.miimon)) + READ_ONCE(bond->params.downdelay) * miimon)) goto nla_put_failure; if (nla_put_u32(skb, IFLA_BOND_PEER_NOTIF_DELAY, - bond->params.peer_notif_delay * bond->params.miimon)) + READ_ONCE(bond->params.peer_notif_delay) * miimon)) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_USE_CARRIER, 1)) goto nla_put_failure; - if (nla_put_u32(skb, IFLA_BOND_ARP_INTERVAL, bond->params.arp_interval)) + if (nla_put_u32(skb, IFLA_BOND_ARP_INTERVAL, + READ_ONCE(bond->params.arp_interval))) goto nla_put_failure; targets = nla_nest_start_noflag(skb, IFLA_BOND_ARP_IP_TARGET); @@ -741,8 +746,10 @@ static int bond_fill_info(struct sk_buff *skb, targets_added = 0; for (i = 0; i < BOND_MAX_ARP_TARGETS; i++) { - if (bond->params.arp_targets[i]) { - if (nla_put_be32(skb, i, bond->params.arp_targets[i])) + __be32 t = READ_ONCE(bond->params.arp_targets[i]); + + if (t) { + if (nla_put_be32(skb, i, t)) goto nla_put_failure; targets_added = 1; } @@ -753,11 +760,12 @@ static int bond_fill_info(struct sk_buff *skb, else nla_nest_cancel(skb, targets); - if (nla_put_u32(skb, IFLA_BOND_ARP_VALIDATE, bond->params.arp_validate)) + if (nla_put_u32(skb, IFLA_BOND_ARP_VALIDATE, + READ_ONCE(bond->params.arp_validate))) goto nla_put_failure; if (nla_put_u32(skb, IFLA_BOND_ARP_ALL_TARGETS, - bond->params.arp_all_targets)) + READ_ONCE(bond->params.arp_all_targets))) goto nla_put_failure; #if IS_ENABLED(CONFIG_IPV6) @@ -767,6 +775,9 @@ static int bond_fill_info(struct sk_buff *skb, targets_added = 0; for (i = 0; i < BOND_MAX_NS_TARGETS; i++) { + /* Note: IPv6 addresses can not be read in an atomic READ_ONCE() yet. + * We accept this minor race for the moment. + */ if (!ipv6_addr_any(&bond->params.ns_targets[i])) { if (nla_put_in6_addr(skb, i, &bond->params.ns_targets[i])) goto nla_put_failure; @@ -780,97 +791,97 @@ static int bond_fill_info(struct sk_buff *skb, nla_nest_cancel(skb, targets); #endif - primary = rtnl_dereference(bond->primary_slave); + primary = rcu_dereference(bond->primary_slave); if (primary && nla_put_u32(skb, IFLA_BOND_PRIMARY, primary->dev->ifindex)) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_PRIMARY_RESELECT, - bond->params.primary_reselect)) + READ_ONCE(bond->params.primary_reselect))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_FAIL_OVER_MAC, - bond->params.fail_over_mac)) + READ_ONCE(bond->params.fail_over_mac))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_XMIT_HASH_POLICY, - bond->params.xmit_policy)) + READ_ONCE(bond->params.xmit_policy))) goto nla_put_failure; if (nla_put_u32(skb, IFLA_BOND_RESEND_IGMP, - bond->params.resend_igmp)) + READ_ONCE(bond->params.resend_igmp))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_NUM_PEER_NOTIF, - bond->params.num_peer_notif)) + READ_ONCE(bond->params.num_peer_notif))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_ALL_SLAVES_ACTIVE, - bond->params.all_slaves_active)) + READ_ONCE(bond->params.all_slaves_active))) goto nla_put_failure; if (nla_put_u32(skb, IFLA_BOND_MIN_LINKS, - bond->params.min_links)) + READ_ONCE(bond->params.min_links))) goto nla_put_failure; if (nla_put_u32(skb, IFLA_BOND_LP_INTERVAL, - bond->params.lp_interval)) + READ_ONCE(bond->params.lp_interval))) goto nla_put_failure; - packets_per_slave = bond->params.packets_per_slave; if (nla_put_u32(skb, IFLA_BOND_PACKETS_PER_SLAVE, - packets_per_slave)) + READ_ONCE(bond->params.packets_per_slave))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_AD_LACP_ACTIVE, - bond->params.lacp_active)) + READ_ONCE(bond->params.lacp_active))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_AD_LACP_RATE, - bond->params.lacp_fast)) + READ_ONCE(bond->params.lacp_fast))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_AD_SELECT, - bond->params.ad_select)) + READ_ONCE(bond->params.ad_select))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_TLB_DYNAMIC_LB, - bond->params.tlb_dynamic_lb)) + READ_ONCE(bond->params.tlb_dynamic_lb))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_MISSED_MAX, - bond->params.missed_max)) + READ_ONCE(bond->params.missed_max))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_COUPLED_CONTROL, - bond->params.coupled_control)) + READ_ONCE(bond->params.coupled_control))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_BROADCAST_NEIGH, - bond->params.broadcast_neighbor)) + READ_ONCE(bond->params.broadcast_neighbor))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_LACP_STRICT, - bond->params.lacp_strict)) + READ_ONCE(bond->params.lacp_strict))) goto nla_put_failure; - if (BOND_MODE(bond) == BOND_MODE_8023AD) { + if (mode == BOND_MODE_8023AD) { struct ad_info info; if (capable(CAP_NET_ADMIN)) { if (nla_put_u16(skb, IFLA_BOND_AD_ACTOR_SYS_PRIO, - bond->params.ad_actor_sys_prio)) + READ_ONCE(bond->params.ad_actor_sys_prio))) goto nla_put_failure; if (nla_put_u16(skb, IFLA_BOND_AD_USER_PORT_KEY, - bond->params.ad_user_port_key)) + READ_ONCE(bond->params.ad_user_port_key))) goto nla_put_failure; + /* Small race here, this is a minor trade off. */ if (nla_put(skb, IFLA_BOND_AD_ACTOR_SYSTEM, ETH_ALEN, &bond->params.ad_actor_system)) goto nla_put_failure; } - if (!bond_3ad_get_active_agg_info(bond, &info)) { + if (!__bond_3ad_get_active_agg_info(bond, &info)) { struct nlattr *nest; nest = nla_nest_start_noflag(skb, IFLA_BOND_AD_INFO); @@ -898,9 +909,11 @@ static int bond_fill_info(struct sk_buff *skb, } } + rcu_read_unlock(); return 0; nla_put_failure: + rcu_read_unlock(); return -EMSGSIZE; } diff --git a/drivers/net/bonding/bond_options.c b/drivers/net/bonding/bond_options.c index e590c8dee86e..36b8d89387ee 100644 --- a/drivers/net/bonding/bond_options.c +++ b/drivers/net/bonding/bond_options.c @@ -934,14 +934,14 @@ static int bond_option_mode_set(struct bonding *bond, /* don't cache arp_validate between modes */ WRITE_ONCE(bond->params.arp_validate, BOND_ARP_VALIDATE_NONE); - bond->params.mode = newval->value; + WRITE_ONCE(bond->params.mode, newval->value); /* When changing mode, the bond device is down, we may reduce * the bond_bcast_neigh_enabled in bond_close() if broadcast_neighbor * enabled in 8023ad mode. Therefore, only clear broadcast_neighbor * to 0. */ - bond->params.broadcast_neighbor = 0; + WRITE_ONCE(bond->params.broadcast_neighbor, 0); if (bond->dev->reg_state == NETREG_REGISTERED) { bool update = false; @@ -1706,7 +1706,7 @@ static int bond_option_lacp_strict_set(struct bonding *bond, { netdev_dbg(bond->dev, "Setting LACP fallback to %s (%llu)\n", newval->string, newval->value); - bond->params.lacp_strict = newval->value; + WRITE_ONCE(bond->params.lacp_strict, newval->value); bond_3ad_set_carrier(bond); return 0; @@ -1927,7 +1927,7 @@ static int bond_option_broadcast_neigh_set(struct bonding *bond, if (bond->params.broadcast_neighbor == newval->value) return 0; - bond->params.broadcast_neighbor = newval->value; + WRITE_ONCE(bond->params.broadcast_neighbor, newval->value); if (bond->dev->flags & IFF_UP) { if (bond->params.broadcast_neighbor) static_branch_inc(&bond_bcast_neigh_enabled); diff --git a/include/net/bond_3ad.h b/include/net/bond_3ad.h index 05572c19e14b..ef667dff2972 100644 --- a/include/net/bond_3ad.h +++ b/include/net/bond_3ad.h @@ -302,8 +302,8 @@ void bond_3ad_state_machine_handler(struct work_struct *); void bond_3ad_initiate_agg_selection(struct bonding *bond, int timeout); void bond_3ad_adapter_speed_duplex_changed(struct slave *slave); void bond_3ad_handle_link_change(struct slave *slave, char link); -int bond_3ad_get_active_agg_info(struct bonding *bond, struct ad_info *ad_info); -int __bond_3ad_get_active_agg_info(struct bonding *bond, +int bond_3ad_get_active_agg_info(const struct bonding *bond, struct ad_info *ad_info); +int __bond_3ad_get_active_agg_info(const struct bonding *bond, struct ad_info *ad_info); int bond_3ad_lacpdu_recv(const struct sk_buff *skb, struct bonding *bond, struct slave *slave); diff --git a/include/net/bonding.h b/include/net/bonding.h index 2c54a36a8477..598d56b1bc97 100644 --- a/include/net/bonding.h +++ b/include/net/bonding.h @@ -345,14 +345,14 @@ static inline bool bond_mode_uses_primary(int mode) mode == BOND_MODE_ALB; } -static inline bool bond_uses_primary(struct bonding *bond) +static inline bool bond_uses_primary(const struct bonding *bond) { return bond_mode_uses_primary(BOND_MODE(bond)); } -static inline struct net_device *bond_option_active_slave_get_rcu(struct bonding *bond) +static inline struct net_device *bond_option_active_slave_get_rcu(const struct bonding *bond) { - struct slave *slave = rcu_dereference_rtnl(bond->curr_active_slave); + const struct slave *slave = rcu_dereference_rtnl(bond->curr_active_slave); return bond_uses_primary(bond) && slave ? slave->dev : NULL; } @@ -703,7 +703,7 @@ void bond_setup(struct net_device *bond_dev); unsigned int bond_get_num_tx_queues(void); int bond_netlink_init(void); void bond_netlink_fini(void); -struct net_device *bond_option_active_slave_get_rcu(struct bonding *bond); +struct net_device *bond_option_active_slave_get_rcu(const struct bonding *bond); const char *bond_slave_link_status(s8 link); struct bond_vlan_tag *bond_verify_device_path(struct net_device *start_dev, struct net_device *end_dev, From 18a28f3e107e7f621527b6d5a5fb061489f54a53 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Mon, 29 Jun 2026 16:50:53 +0800 Subject: [PATCH 0052/1433] net: sgi: ioc3-eth: unregister netdev before freeing DMA rings ioc3eth_remove() frees the coherent RX and TX descriptor rings before unregistering the netdev. If the interface is running, unregister_netdev() invokes ioc3_close() through ndo_stop. ioc3_close() stops the device and then calls ioc3_free_rx_bufs() and ioc3_clean_tx_ring(). Both cleanup functions access descriptors in the rings, so the current ordering causes CPU accesses to freed coherent memory. Until ioc3_stop() disables RX and TX DMA, the device may also continue using the freed ring addresses. Unregister the netdev before releasing the rings. This lets the core close a running interface and quiesce the device while the rings are still valid. Keep the explicit timer deletion because ndo_stop is not called when the interface is already down. Cc: # untested fix for ancient HW Signed-off-by: Xu Rao Reviewed-by: Thomas Bogendoerfer Link: https://patch.msgid.link/40CD736C4911C181+20260629085053.964383-1-raoxu@uniontech.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/sgi/ioc3-eth.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/sgi/ioc3-eth.c b/drivers/net/ethernet/sgi/ioc3-eth.c index 39731069d99e..b35f692b1a0e 100644 --- a/drivers/net/ethernet/sgi/ioc3-eth.c +++ b/drivers/net/ethernet/sgi/ioc3-eth.c @@ -967,11 +967,12 @@ static void ioc3eth_remove(struct platform_device *pdev) struct net_device *dev = platform_get_drvdata(pdev); struct ioc3_private *ip = netdev_priv(dev); + unregister_netdev(dev); + timer_delete_sync(&ip->ioc3_timer); + dma_free_coherent(ip->dma_dev, RX_RING_SIZE, ip->rxr, ip->rxr_dma); dma_free_coherent(ip->dma_dev, TX_RING_SIZE + SZ_16K - 1, ip->tx_ring, ip->txr_dma); - unregister_netdev(dev); - timer_delete_sync(&ip->ioc3_timer); free_netdev(dev); } From cd066559a07371e0b97b6155ba4eeaafeb233009 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Mon, 29 Jun 2026 16:06:23 +0800 Subject: [PATCH 0053/1433] net: sgi: ioc3-eth: fix split TX DMA mapping lengths When a linear skb crosses a 16 KiB boundary, ioc3_start_xmit() splits it into two buffers of lengths s1 and s2. The descriptor advertises those lengths through B1CNT and B2CNT. The first buffer is mapped with s1, but the second buffer is also mapped with s1 even though the device is told to fetch s2 bytes from it. When the lengths differ, the DMA mapping does not cover the same region as the second descriptor buffer, which can result in incorrect cache maintenance or a DMA fault on implementations that enforce the mapped range. There is a separate mismatch in the error path. If mapping the second buffer fails, only d1 needs to be unmapped. d1 was mapped for s1 bytes, but the driver unmaps it using the full packet length. Streaming DMA mappings must be unmapped with the same size used for the corresponding map operation. Map the second buffer with s2 and unmap the first buffer with s1 when the second mapping fails. Cc: # untested fix for ancient HW Signed-off-by: Xu Rao Reviewed-by: Thomas Bogendoerfer Link: https://patch.msgid.link/4E1486BC4536407E+20260629080623.908426-1-raoxu@uniontech.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/sgi/ioc3-eth.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/sgi/ioc3-eth.c b/drivers/net/ethernet/sgi/ioc3-eth.c index b35f692b1a0e..009f37105eaf 100644 --- a/drivers/net/ethernet/sgi/ioc3-eth.c +++ b/drivers/net/ethernet/sgi/ioc3-eth.c @@ -1062,9 +1062,9 @@ static netdev_tx_t ioc3_start_xmit(struct sk_buff *skb, struct net_device *dev) d1 = dma_map_single(ip->dma_dev, skb->data, s1, DMA_TO_DEVICE); if (dma_mapping_error(ip->dma_dev, d1)) goto drop_packet; - d2 = dma_map_single(ip->dma_dev, (void *)b2, s1, DMA_TO_DEVICE); + d2 = dma_map_single(ip->dma_dev, (void *)b2, s2, DMA_TO_DEVICE); if (dma_mapping_error(ip->dma_dev, d2)) { - dma_unmap_single(ip->dma_dev, d1, len, DMA_TO_DEVICE); + dma_unmap_single(ip->dma_dev, d1, s1, DMA_TO_DEVICE); goto drop_packet; } desc->p1 = cpu_to_be64(ioc3_map(d1, PCI64_ATTR_PREF)); From 09f7a613a14fd6683e8e4437c97bde0f1ca7062c Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 29 Jun 2026 04:45:40 -0700 Subject: [PATCH 0054/1433] netconsole: do not warn when the best-effort skb allocation fails find_skb() allocates the skb with GFP_ATOMIC as a best-effort attempt: on failure it falls back to the preallocated skb pool and, failing that, polls the device and retries. The allocation failing is therefore an expected and fully handled condition, but without __GFP_NOWARN the page allocator still emits a warn_alloc() splat with a full stack trace on every miss, which then consumes the whole SKB pool, that would be useful printing the real issue rather than the memory failure. Pass __GFP_NOWARN so the best-effort allocation stays quiet and lets the existing fallback path do its job. Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260629-netpoll_no_warn-v1-1-f380f0b2cd0c@debian.org Signed-off-by: Jakub Kicinski --- drivers/net/netconsole.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 862001d09aa8..c1812a98365b 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -1737,7 +1737,7 @@ static struct sk_buff *find_skb(struct netpoll *np, int len, int reserve) netpoll_zap_completion_queue(); repeat: - skb = alloc_skb(len, GFP_ATOMIC); + skb = alloc_skb(len, GFP_ATOMIC | __GFP_NOWARN); if (!skb) skb = netcons_skb_pop(np, len); From 84c0ff1efb62b0053aa265b8deb13842f68f1a74 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 29 Jun 2026 04:45:41 -0700 Subject: [PATCH 0055/1433] netpoll: do not warn when the best-effort pool refill fails refill_skbs() tops up the per-netpoll skb pool with GFP_ATOMIC and simply stops on the first allocation failure, leaving the pool partially filled; a later refill tops it up once memory frees up. The allocation failing is therefore an expected and fully handled condition, but without __GFP_NOWARN the page allocator emits a warn_alloc() splat with a full stack trace on every miss. Pass __GFP_NOWARN so the best-effort refill stays quiet, mirroring the same change in netconsole's find_skb(). Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260629-netpoll_no_warn-v1-2-f380f0b2cd0c@debian.org Signed-off-by: Jakub Kicinski --- net/core/netpoll.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/core/netpoll.c b/net/core/netpoll.c index 229dde818ab3..85aa51350881 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -221,7 +221,7 @@ static void refill_skbs(struct netpoll *np) skb_pool = &np->skb_pool; while (READ_ONCE(skb_pool->qlen) < MAX_SKBS) { - skb = alloc_skb(MAX_SKB_SIZE, GFP_ATOMIC); + skb = alloc_skb(MAX_SKB_SIZE, GFP_ATOMIC | __GFP_NOWARN); if (!skb) break; From 97cb4ae7511bd1ddaaca743bb3b6bcc53ea4e52a Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Mon, 29 Jun 2026 14:22:57 +0300 Subject: [PATCH 0056/1433] dpaa2-switch: remove unnecessary dev_mc_add/dev_mc_del calls The DPSW object does not implement strict address filtering thus any call to the dev_mc_add() / dev_mc_del() is pointless. Remove these calls from the dpaa2_switch_port_mdb_add() and dpaa2_switch_port_mdb_del() functions. And since the multicast addresses no longer reach the netdev->mc list, there is no point in keeping the dpaa2_switch_port_lookup_address() function which searches through that list to verify if the same address is added multiple times. Signed-off-by: Ioana Ciornei Link: https://patch.msgid.link/20260629112309.154328-2-ioana.ciornei@nxp.com Signed-off-by: Paolo Abeni --- .../ethernet/freescale/dpaa2/dpaa2-switch.c | 50 +------------------ 1 file changed, 2 insertions(+), 48 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 858ba844ac51..d70e6f06ac15 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -1860,44 +1860,12 @@ int dpaa2_switch_port_vlans_add(struct net_device *netdev, vlan->changed); } -static int dpaa2_switch_port_lookup_address(struct net_device *netdev, int is_uc, - const unsigned char *addr) -{ - struct netdev_hw_addr_list *list = (is_uc) ? &netdev->uc : &netdev->mc; - struct netdev_hw_addr *ha; - - netif_addr_lock_bh(netdev); - list_for_each_entry(ha, &list->list, list) { - if (ether_addr_equal(ha->addr, addr)) { - netif_addr_unlock_bh(netdev); - return 1; - } - } - netif_addr_unlock_bh(netdev); - return 0; -} - static int dpaa2_switch_port_mdb_add(struct net_device *netdev, const struct switchdev_obj_port_mdb *mdb) { struct ethsw_port_priv *port_priv = netdev_priv(netdev); - int err; - /* Check if address is already set on this port */ - if (dpaa2_switch_port_lookup_address(netdev, 0, mdb->addr)) - return -EEXIST; - - err = dpaa2_switch_port_fdb_add_mc(port_priv, mdb->addr); - if (err) - return err; - - err = dev_mc_add(netdev, mdb->addr); - if (err) { - netdev_err(netdev, "dev_mc_add err %d\n", err); - dpaa2_switch_port_fdb_del_mc(port_priv, mdb->addr); - } - - return err; + return dpaa2_switch_port_fdb_add_mc(port_priv, mdb->addr); } static int dpaa2_switch_port_obj_add(struct net_device *netdev, @@ -2000,22 +1968,8 @@ static int dpaa2_switch_port_mdb_del(struct net_device *netdev, const struct switchdev_obj_port_mdb *mdb) { struct ethsw_port_priv *port_priv = netdev_priv(netdev); - int err; - if (!dpaa2_switch_port_lookup_address(netdev, 0, mdb->addr)) - return -ENOENT; - - err = dpaa2_switch_port_fdb_del_mc(port_priv, mdb->addr); - if (err) - return err; - - err = dev_mc_del(netdev, mdb->addr); - if (err) { - netdev_err(netdev, "dev_mc_del err %d\n", err); - return err; - } - - return err; + return dpaa2_switch_port_fdb_del_mc(port_priv, mdb->addr); } static int dpaa2_switch_port_obj_del(struct net_device *netdev, From 0cf0b8ac40aef39e3e9c172944c043a7209362ce Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Mon, 29 Jun 2026 14:22:58 +0300 Subject: [PATCH 0057/1433] dpaa2-switch: avoid holding rtnl_lock in dpaa2_switch_event_work() The only reason why the rtnl_lock is held in the dpaa2_switch_event_work() is so that there is no concurency between the changeupper notifier which manages the per port FDB assignment and the workqueue which adds / deletes addresses into that forwarding database. To avoid this kind of concurency without a rtnl_lock, flush the event workqueue as the last step from the pre_bridge_leave so that any in-flight operations targeting the current FDB are finalized before the bridge layout (and the per port FDB assignment) changes. Signed-off-by: Ioana Ciornei Link: https://patch.msgid.link/20260629112309.154328-3-ioana.ciornei@nxp.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index d70e6f06ac15..67c639fad0db 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -2069,7 +2069,15 @@ static int dpaa2_switch_port_restore_rxvlan(struct net_device *vdev, int vid, vo static void dpaa2_switch_port_pre_bridge_leave(struct net_device *netdev) { + struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct ethsw_core *ethsw = port_priv->ethsw_data; + switchdev_bridge_port_unoffload(netdev, NULL, NULL, NULL); + + /* Make sure that any FDB add/del operations are completed before the + * bridge layout changes + */ + flush_workqueue(ethsw->workqueue); } static int dpaa2_switch_port_bridge_leave(struct net_device *netdev) @@ -2281,7 +2289,6 @@ static void dpaa2_switch_event_work(struct work_struct *work) struct switchdev_notifier_fdb_info *fdb_info; int err; - rtnl_lock(); fdb_info = &switchdev_work->fdb_info; switch (switchdev_work->event) { @@ -2310,7 +2317,6 @@ static void dpaa2_switch_event_work(struct work_struct *work) break; } - rtnl_unlock(); kfree(switchdev_work->fdb_info.addr); kfree(switchdev_work); dev_put(dev); From 900c915030f696be8cf48a13c274040a06bda40d Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Mon, 29 Jun 2026 14:22:59 +0300 Subject: [PATCH 0058/1433] dpaa2-switch: extend the FDB management to cover bond scenarios The dpaa2_switch_fdb_for_join() function is responsible with determining what FDB should be used by a port as a consequence of it joining a bridge. The rule is that all DPAA2 switch ports under the same bridge will use the FDB of the first port which joined that bridge. Extend the function so that the function also covers the scenario in which there is bridged bond device. For this to happen, in case a bond device is encountered through the bridge ports the function needs to descend one level through its lowers as well. Signed-off-by: Ioana Ciornei Link: https://patch.msgid.link/20260629112309.154328-4-ioana.ciornei@nxp.com Signed-off-by: Paolo Abeni --- .../ethernet/freescale/dpaa2/dpaa2-switch.c | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 67c639fad0db..eacab00b586a 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -71,9 +71,9 @@ static struct dpaa2_switch_fdb * dpaa2_switch_fdb_for_join(struct ethsw_port_priv *port_priv, struct net_device *upper_dev) { - struct ethsw_port_priv *other_port_priv; - struct net_device *other_dev; - struct list_head *iter; + struct ethsw_port_priv *other_port_priv = NULL; + struct net_device *other_dev, *other_dev2; + struct list_head *iter, *iter2; /* The below call to netdev_for_each_lower_dev() demands the RTNL lock * being held. Assert on it so that it's easier to catch new code @@ -82,17 +82,32 @@ dpaa2_switch_fdb_for_join(struct ethsw_port_priv *port_priv, ASSERT_RTNL(); /* If part of a bridge, use the FDB of the first dpaa2 switch interface - * to be present in that bridge + * to be present in that bridge. The search descends one level through + * a bridged bond's lowers as well. */ netdev_for_each_lower_dev(upper_dev, other_dev, iter) { - if (!dpaa2_switch_port_dev_check(other_dev)) - continue; + if (netif_is_lag_master(other_dev)) { + netdev_for_each_lower_dev(other_dev, other_dev2, iter2) { + if (!dpaa2_switch_port_dev_check(other_dev2)) + continue; - if (other_dev == port_priv->netdev) - continue; + if (other_dev2 == port_priv->netdev) + continue; - other_port_priv = netdev_priv(other_dev); - return other_port_priv->fdb; + other_port_priv = netdev_priv(other_dev2); + break; + } + } else { + if (!dpaa2_switch_port_dev_check(other_dev)) + continue; + + if (other_dev == port_priv->netdev) + continue; + + other_port_priv = netdev_priv(other_dev); + } + if (other_port_priv) + return other_port_priv->fdb; } return port_priv->fdb; From da7ec6b81b0bcc7599d3518023a2cdac0609105c Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Mon, 29 Jun 2026 14:23:00 +0300 Subject: [PATCH 0059/1433] dpaa2-switch: create a separate dpaa2_switch_port_fdb_event() function Create a separate dpaa2_switch_port_fdb_event() function that will only handle the FDB related events. With this change, the dpaa2_switch_port_event() notifier handler can be written in a way that it's easier to follow. Signed-off-by: Ioana Ciornei Link: https://patch.msgid.link/20260629112309.154328-5-ioana.ciornei@nxp.com Signed-off-by: Paolo Abeni --- .../ethernet/freescale/dpaa2/dpaa2-switch.c | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index eacab00b586a..c7c84bf2fde7 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -2337,21 +2337,18 @@ static void dpaa2_switch_event_work(struct work_struct *work) dev_put(dev); } -/* Called under rcu_read_lock() */ -static int dpaa2_switch_port_event(struct notifier_block *nb, - unsigned long event, void *ptr) +static int dpaa2_switch_port_fdb_event(struct notifier_block *nb, + unsigned long event, void *ptr) { struct net_device *dev = switchdev_notifier_info_to_dev(ptr); struct ethsw_port_priv *port_priv = netdev_priv(dev); struct ethsw_switchdev_event_work *switchdev_work; struct switchdev_notifier_fdb_info *fdb_info = ptr; - struct ethsw_core *ethsw = port_priv->ethsw_data; - - if (event == SWITCHDEV_PORT_ATTR_SET) - return dpaa2_switch_port_attr_set_event(dev, ptr); + struct ethsw_core *ethsw; if (!dpaa2_switch_port_dev_check(dev)) return NOTIFY_DONE; + ethsw = port_priv->ethsw_data; switchdev_work = kzalloc_obj(*switchdev_work, GFP_ATOMIC); if (!switchdev_work) @@ -2390,6 +2387,23 @@ static int dpaa2_switch_port_event(struct notifier_block *nb, return NOTIFY_BAD; } +/* Called under rcu_read_lock() */ +static int dpaa2_switch_port_event(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct net_device *dev = switchdev_notifier_info_to_dev(ptr); + + switch (event) { + case SWITCHDEV_PORT_ATTR_SET: + return dpaa2_switch_port_attr_set_event(dev, ptr); + case SWITCHDEV_FDB_ADD_TO_DEVICE: + case SWITCHDEV_FDB_DEL_TO_DEVICE: + return dpaa2_switch_port_fdb_event(nb, event, ptr); + default: + return NOTIFY_DONE; + } +} + static int dpaa2_switch_port_obj_event(unsigned long event, struct net_device *netdev, struct switchdev_notifier_port_obj_info *port_obj_info) From 0199ff706da1fa9e0ec2576fc38d270549ccefeb Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Mon, 29 Jun 2026 14:23:01 +0300 Subject: [PATCH 0060/1433] dpaa2-switch: check early if an FDB entry should be added Instead of waiting until the last moment to check if an FDB entry should be added to HW, move the check earlier (before even scheduling the work item) so that we don't just waste time. Signed-off-by: Ioana Ciornei Link: https://patch.msgid.link/20260629112309.154328-6-ioana.ciornei@nxp.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index c7c84bf2fde7..d4975d08fa44 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -2308,8 +2308,6 @@ static void dpaa2_switch_event_work(struct work_struct *work) switch (switchdev_work->event) { case SWITCHDEV_FDB_ADD_TO_DEVICE: - if (!fdb_info->added_by_user || fdb_info->is_local) - break; if (is_unicast_ether_addr(fdb_info->addr)) err = dpaa2_switch_port_fdb_add_uc(netdev_priv(dev), fdb_info->addr); @@ -2323,8 +2321,6 @@ static void dpaa2_switch_event_work(struct work_struct *work) &fdb_info->info, NULL); break; case SWITCHDEV_FDB_DEL_TO_DEVICE: - if (!fdb_info->added_by_user || fdb_info->is_local) - break; if (is_unicast_ether_addr(fdb_info->addr)) dpaa2_switch_port_fdb_del_uc(netdev_priv(dev), fdb_info->addr); else @@ -2350,6 +2346,9 @@ static int dpaa2_switch_port_fdb_event(struct notifier_block *nb, return NOTIFY_DONE; ethsw = port_priv->ethsw_data; + if (!fdb_info->added_by_user || fdb_info->is_local) + return NOTIFY_DONE; + switchdev_work = kzalloc_obj(*switchdev_work, GFP_ATOMIC); if (!switchdev_work) return NOTIFY_BAD; From 06840a236334fdccfbaa87654e1361e33dd08e4c Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Mon, 29 Jun 2026 14:23:02 +0300 Subject: [PATCH 0061/1433] dpaa2-switch: add dpaa2_switch_port_to_bridge_port() helper In preparation for adding offloading support for upper bond devices we have to let the switchdev framework know if a specific bridge port is offloaded or not, even if that brport is an upper device. For this to happen, create the dpaa2_switch_port_to_bridge_port function which will determine the bridge port corresponding to a particular DPAA2 switch interface and use it in the switchdev_bridge_port_offload call. Signed-off-by: Ioana Ciornei Link: https://patch.msgid.link/20260629112309.154328-7-ioana.ciornei@nxp.com Signed-off-by: Paolo Abeni --- .../ethernet/freescale/dpaa2/dpaa2-switch.c | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index d4975d08fa44..88d199befbd9 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -2017,6 +2017,15 @@ static int dpaa2_switch_port_attr_set_event(struct net_device *netdev, return notifier_from_errno(err); } +static struct net_device * +dpaa2_switch_port_to_bridge_port(struct ethsw_port_priv *port_priv) +{ + if (!port_priv->fdb->bridge_dev) + return NULL; + + return port_priv->netdev; +} + static int dpaa2_switch_port_bridge_join(struct net_device *netdev, struct net_device *upper_dev, struct netlink_ext_ack *extack) @@ -2024,6 +2033,7 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev, struct ethsw_port_priv *port_priv = netdev_priv(netdev); struct dpaa2_switch_fdb *old_fdb = port_priv->fdb; struct ethsw_core *ethsw = port_priv->ethsw_data; + struct net_device *brport_dev; bool learn_ena; int err; @@ -2035,7 +2045,8 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev, dpaa2_switch_port_set_fdb(port_priv, upper_dev, true); /* Inherit the initial bridge port learning state */ - learn_ena = br_port_flag_is_set(netdev, BR_LEARNING); + brport_dev = dpaa2_switch_port_to_bridge_port(port_priv); + learn_ena = br_port_flag_is_set(brport_dev, BR_LEARNING); err = dpaa2_switch_port_set_learning(port_priv, learn_ena); port_priv->learn_ena = learn_ena; @@ -2049,7 +2060,8 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev, if (err) goto err_egress_flood; - err = switchdev_bridge_port_offload(netdev, netdev, NULL, + brport_dev = dpaa2_switch_port_to_bridge_port(port_priv); + err = switchdev_bridge_port_offload(brport_dev, netdev, NULL, NULL, NULL, false, extack); if (err) goto err_switchdev_offload; @@ -2086,8 +2098,13 @@ static void dpaa2_switch_port_pre_bridge_leave(struct net_device *netdev) { struct ethsw_port_priv *port_priv = netdev_priv(netdev); struct ethsw_core *ethsw = port_priv->ethsw_data; + struct net_device *brport_dev; - switchdev_bridge_port_unoffload(netdev, NULL, NULL, NULL); + brport_dev = dpaa2_switch_port_to_bridge_port(port_priv); + if (!brport_dev) + return; + + switchdev_bridge_port_unoffload(brport_dev, NULL, NULL, NULL); /* Make sure that any FDB add/del operations are completed before the * bridge layout changes From 28b79b55852aa932b214452659ccb5de37b85227 Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Mon, 29 Jun 2026 14:23:03 +0300 Subject: [PATCH 0062/1433] dpaa2-switch: consolidate unicast and multicast management This patch consolidates the unicast and multicast management by creating two new functions - dpaa2_switch_port_fdb_[add|del]() - which can be used for either uc or mc addresses. Having this common entrypoint for both types of addresses will help us in the next patches to streamline the same addresses but on LAG ports. Signed-off-by: Ioana Ciornei Link: https://patch.msgid.link/20260629112309.154328-8-ioana.ciornei@nxp.com Signed-off-by: Paolo Abeni --- .../ethernet/freescale/dpaa2/dpaa2-switch.c | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 88d199befbd9..3472f5d5b08a 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -552,6 +552,28 @@ static int dpaa2_switch_port_fdb_del_mc(struct ethsw_port_priv *port_priv, return err; } +static int dpaa2_switch_port_fdb_add(struct ethsw_port_priv *port_priv, + const unsigned char *addr) +{ + int err; + + if (is_unicast_ether_addr(addr)) + err = dpaa2_switch_port_fdb_add_uc(port_priv, addr); + else + err = dpaa2_switch_port_fdb_add_mc(port_priv, addr); + + return err; +} + +static int dpaa2_switch_port_fdb_del(struct ethsw_port_priv *port_priv, + const unsigned char *addr) +{ + if (is_unicast_ether_addr(addr)) + return dpaa2_switch_port_fdb_del_uc(port_priv, addr); + else + return dpaa2_switch_port_fdb_del_mc(port_priv, addr); +} + static void dpaa2_switch_port_get_stats(struct net_device *netdev, struct rtnl_link_stats64 *stats) { @@ -1880,7 +1902,7 @@ static int dpaa2_switch_port_mdb_add(struct net_device *netdev, { struct ethsw_port_priv *port_priv = netdev_priv(netdev); - return dpaa2_switch_port_fdb_add_mc(port_priv, mdb->addr); + return dpaa2_switch_port_fdb_add(port_priv, mdb->addr); } static int dpaa2_switch_port_obj_add(struct net_device *netdev, @@ -1984,7 +2006,7 @@ static int dpaa2_switch_port_mdb_del(struct net_device *netdev, { struct ethsw_port_priv *port_priv = netdev_priv(netdev); - return dpaa2_switch_port_fdb_del_mc(port_priv, mdb->addr); + return dpaa2_switch_port_fdb_del(port_priv, mdb->addr); } static int dpaa2_switch_port_obj_del(struct net_device *netdev, @@ -2325,12 +2347,8 @@ static void dpaa2_switch_event_work(struct work_struct *work) switch (switchdev_work->event) { case SWITCHDEV_FDB_ADD_TO_DEVICE: - if (is_unicast_ether_addr(fdb_info->addr)) - err = dpaa2_switch_port_fdb_add_uc(netdev_priv(dev), - fdb_info->addr); - else - err = dpaa2_switch_port_fdb_add_mc(netdev_priv(dev), - fdb_info->addr); + err = dpaa2_switch_port_fdb_add(netdev_priv(dev), + fdb_info->addr); if (err) break; fdb_info->offloaded = true; @@ -2338,10 +2356,7 @@ static void dpaa2_switch_event_work(struct work_struct *work) &fdb_info->info, NULL); break; case SWITCHDEV_FDB_DEL_TO_DEVICE: - if (is_unicast_ether_addr(fdb_info->addr)) - dpaa2_switch_port_fdb_del_uc(netdev_priv(dev), fdb_info->addr); - else - dpaa2_switch_port_fdb_del_mc(netdev_priv(dev), fdb_info->addr); + dpaa2_switch_port_fdb_del(netdev_priv(dev), fdb_info->addr); break; } From f27ad9b45b13c235a71082ef1aa9bc5080caa781 Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Mon, 29 Jun 2026 14:23:04 +0300 Subject: [PATCH 0063/1433] dpaa2-switch: add LAG configuration API Add the necessary APIs to configure and control the LAG support on the DPAA2 switch object. - The dpsw_lag_set() function will be used to either verify that a LAG configuration can be support or to actually apply it in HW. - The dpsw_if_set_lag_state() will get used in the next patches to change the per port LAG state of a specific DPSW interface. Signed-off-by: Ioana Ciornei Link: https://patch.msgid.link/20260629112309.154328-9-ioana.ciornei@nxp.com Signed-off-by: Paolo Abeni --- .../net/ethernet/freescale/dpaa2/dpsw-cmd.h | 18 +++++- drivers/net/ethernet/freescale/dpaa2/dpsw.c | 60 +++++++++++++++++++ drivers/net/ethernet/freescale/dpaa2/dpsw.h | 30 ++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpsw-cmd.h b/drivers/net/ethernet/freescale/dpaa2/dpsw-cmd.h index 397d55f2bd99..9a2055c64983 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpsw-cmd.h +++ b/drivers/net/ethernet/freescale/dpaa2/dpsw-cmd.h @@ -12,7 +12,7 @@ /* DPSW Version */ #define DPSW_VER_MAJOR 8 -#define DPSW_VER_MINOR 9 +#define DPSW_VER_MINOR 13 #define DPSW_CMD_BASE_VERSION 1 #define DPSW_CMD_VERSION_2 2 @@ -92,11 +92,14 @@ #define DPSW_CMDID_CTRL_IF_SET_POOLS DPSW_CMD_ID(0x0A1) #define DPSW_CMDID_CTRL_IF_ENABLE DPSW_CMD_ID(0x0A2) #define DPSW_CMDID_CTRL_IF_DISABLE DPSW_CMD_ID(0x0A3) +#define DPSW_CMDID_SET_LAG DPSW_CMD_V2(0x0A4) #define DPSW_CMDID_CTRL_IF_SET_QUEUE DPSW_CMD_ID(0x0A6) #define DPSW_CMDID_SET_EGRESS_FLOOD DPSW_CMD_ID(0x0AC) #define DPSW_CMDID_IF_SET_LEARNING_MODE DPSW_CMD_ID(0x0AD) +#define DPSW_CMDID_IF_SET_LAG_STATE DPSW_CMD_ID(0x0B0) + /* Macros for accessing command fields smaller than 1byte */ #define DPSW_MASK(field) \ GENMASK(DPSW_##field##_SHIFT + DPSW_##field##_SIZE - 1, \ @@ -552,5 +555,18 @@ struct dpsw_cmd_if_reflection { /* only 2 bits from the LSB */ u8 filter; }; + +struct dpsw_cmd_lag { + u8 group_id; + u8 num_ifs; + u8 pad[6]; + u8 if_id[DPSW_MAX_LAG_IFS]; + u8 phase; +}; + +struct dpsw_cmd_if_set_lag_state { + __le16 if_id; + u8 tx_enabled; +}; #pragma pack(pop) #endif /* __FSL_DPSW_CMD_H */ diff --git a/drivers/net/ethernet/freescale/dpaa2/dpsw.c b/drivers/net/ethernet/freescale/dpaa2/dpsw.c index ab921d75deb2..f75cbdce42ba 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpsw.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpsw.c @@ -1659,3 +1659,63 @@ int dpsw_if_remove_reflection(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, return mc_send_command(mc_io, &cmd); } + +/** + * dpsw_lag_set() - Set LAG configuration + * @mc_io: Pointer to MC portal's I/O object + * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' + * @token: Token of DPSW object + * @cfg: pointer to LAG configuration + * + * Return: '0' on Success; Error code otherwise. + */ +int dpsw_lag_set(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, + const struct dpsw_lag_cfg *cfg) +{ + struct fsl_mc_command cmd = { 0 }; + struct dpsw_cmd_lag *cmd_params; + int i = 0; + + cmd.header = mc_encode_cmd_header(DPSW_CMDID_SET_LAG, cmd_flags, token); + + if (cfg->num_ifs > DPSW_MAX_LAG_IFS) + return -EOPNOTSUPP; + + cmd_params = (struct dpsw_cmd_lag *)cmd.params; + cmd_params->group_id = cfg->group_id; + cmd_params->num_ifs = cfg->num_ifs; + cmd_params->phase = cfg->phase; + + for (i = 0; i < cfg->num_ifs; i++) + cmd_params->if_id[i] = cfg->if_id[i]; + + return mc_send_command(mc_io, &cmd); +} + +/** + * dpsw_if_set_lag_state() - Change per port LAG state + * @mc_io: Pointer to MC portal's I/O object + * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' + * @token: Token of DPSW object + * @if_id: ID of the switch interface + * @tx_enabled: Value of the per port LAG state + * - 0 if the interface will not be active as part of the LAG group + * - 1 if the interface will be active in the LAG group + * + * Return: '0' on Success; Error code otherwise. + */ +int dpsw_if_set_lag_state(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, + u16 if_id, u8 tx_enabled) +{ + struct dpsw_cmd_if_set_lag_state *cmd_params; + struct fsl_mc_command cmd = { 0 }; + + cmd.header = mc_encode_cmd_header(DPSW_CMDID_IF_SET_LAG_STATE, + cmd_flags, token); + + cmd_params = (struct dpsw_cmd_if_set_lag_state *)cmd.params; + cmd_params->if_id = cpu_to_le16(if_id); + cmd_params->tx_enabled = tx_enabled; + + return mc_send_command(mc_io, &cmd); +} diff --git a/drivers/net/ethernet/freescale/dpaa2/dpsw.h b/drivers/net/ethernet/freescale/dpaa2/dpsw.h index b90bd363f47a..89f0267de8e9 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpsw.h +++ b/drivers/net/ethernet/freescale/dpaa2/dpsw.h @@ -20,6 +20,8 @@ struct fsl_mc_io; #define DPSW_MAX_IF 64 +#define DPSW_MAX_LAG_IFS 8 + int dpsw_open(struct fsl_mc_io *mc_io, u32 cmd_flags, int dpsw_id, u16 *token); int dpsw_close(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token); @@ -788,4 +790,32 @@ int dpsw_if_add_reflection(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, int dpsw_if_remove_reflection(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, u16 if_id, const struct dpsw_reflection_cfg *cfg); + +/* Link Aggregation Group configuration */ + +#define DPSW_LAG_SET_PHASE_APPLY 0 +#define DPSW_LAG_SET_PHASE_CHECK 1 + +/** + * struct dpsw_lag_cfg - Configuration structure for a LAG group + * @group_id: Link aggregation group ID. Valid values are in the + * [1, DPSW_MAX_LAG_IFS] range. + * @num_ifs: Number of interfaces in this LAG group, valid range is + * [0, DPSW_MAX_LAG_IFS]. + * @if_id: Array containing the interface IDs of the ports part of a LAG group + * @phase: Use DPSW_LAG_SET_PHASE_APPLY for LAG configuration processing or + * DPSW_LAG_SET_PHASE_CHECK for LAG configuration validation. + */ +struct dpsw_lag_cfg { + u8 group_id; + u8 num_ifs; + u8 if_id[DPSW_MAX_LAG_IFS]; + u8 phase; +}; + +int dpsw_lag_set(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, + const struct dpsw_lag_cfg *cfg); + +int dpsw_if_set_lag_state(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, + u16 if_id, u8 tx_enabled); #endif /* __FSL_DPSW_H */ From 9ca09640bfc8c8265af7c550ce35bc8f7fda6a46 Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Mon, 29 Jun 2026 14:23:05 +0300 Subject: [PATCH 0064/1433] dpaa2-switch: add support for LAG offload This patch adds the bulk of the changes needed in order to support offloading of an upper bond device. First of all, handling of the NETDEV_CHANGEUPPER and NETDEV_PRECHANGEUPPER events is extended so that the driver is capable to handle joining or leaving an upper bond device. All the restrictions around the LAG offload support are added in the newly added dpaa2_switch_pre_lag_join() function. The same events are extended to also detect if one of our upper bond devices changes its own upper device. In this case, on each lower device that is DPAA2 the corresponding dpaa2_switch_port_[pre]changeupper() function will be called. This will start the process of joining the same FDB as the one used by the bridge device. Setting the 'offload_fwd_mark' field on the skbs is also extended to be setup not only when the port is under a bridge but also under a bond device that is offloaded. Signed-off-by: Ioana Ciornei Link: https://patch.msgid.link/20260629112309.154328-10-ioana.ciornei@nxp.com Signed-off-by: Paolo Abeni --- .../ethernet/freescale/dpaa2/dpaa2-switch.c | 473 +++++++++++++++++- .../ethernet/freescale/dpaa2/dpaa2-switch.h | 15 +- 2 files changed, 476 insertions(+), 12 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 3472f5d5b08a..949a7241a00f 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -51,6 +51,17 @@ dpaa2_switch_filter_block_get_unused(struct ethsw_core *ethsw) return NULL; } +static struct dpaa2_switch_lag * +dpaa2_switch_lag_get_unused(struct ethsw_core *ethsw) +{ + int i; + + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) + if (!ethsw->lags[i].in_use) + return ðsw->lags[i]; + return NULL; +} + static bool dpaa2_switch_fdb_in_use_by_others(struct ethsw_core *ethsw, struct dpaa2_switch_fdb *fdb, struct ethsw_port_priv *except) @@ -2042,9 +2053,15 @@ static int dpaa2_switch_port_attr_set_event(struct net_device *netdev, static struct net_device * dpaa2_switch_port_to_bridge_port(struct ethsw_port_priv *port_priv) { + struct dpaa2_switch_lag *lag; + if (!port_priv->fdb->bridge_dev) return NULL; + lag = rtnl_dereference(port_priv->lag); + if (lag) + return lag->bond_dev; + return port_priv->netdev; } @@ -2193,30 +2210,53 @@ static int dpaa2_switch_port_bridge_leave(struct net_device *netdev) false); } +static int +dpaa2_switch_have_vlan_upper(struct net_device *upper_dev, + __always_unused struct netdev_nested_priv *priv) +{ + return is_vlan_dev(upper_dev); +} + static int dpaa2_switch_prevent_bridging_with_8021q_upper(struct net_device *netdev) { - struct net_device *upper_dev; - struct list_head *iter; + struct netdev_nested_priv priv = {}; /* RCU read lock not necessary because we have write-side protection - * (rtnl_mutex), however a non-rcu iterator does not exist. + * (rtnl_mutex), however a non-rcu iterator does not exist. Walk the + * entire upper chain so that a VLAN device stacked on a intermediate + * bond is caught too. */ - netdev_for_each_upper_dev_rcu(netdev, upper_dev, iter) - if (is_vlan_dev(upper_dev)) - return -EOPNOTSUPP; + if (netdev_walk_all_upper_dev_rcu(netdev, dpaa2_switch_have_vlan_upper, + &priv)) + return -EOPNOTSUPP; return 0; } +static int dpaa2_switch_check_dpsw_instance(struct net_device *dev, + struct netdev_nested_priv *priv) +{ + struct ethsw_port_priv *port_priv = (struct ethsw_port_priv *)priv->data; + struct ethsw_port_priv *other_priv = netdev_priv(dev); + + if (!dpaa2_switch_port_dev_check(dev)) + return 0; + + if (other_priv->ethsw_data == port_priv->ethsw_data) + return 0; + + return 1; +} + static int dpaa2_switch_prechangeupper_sanity_checks(struct net_device *netdev, struct net_device *upper_dev, struct netlink_ext_ack *extack) { struct ethsw_port_priv *port_priv = netdev_priv(netdev); - struct ethsw_port_priv *other_port_priv; - struct net_device *other_dev; - struct list_head *iter; + struct netdev_nested_priv data = { + .data = (void *)port_priv, + }; int err; if (!br_vlan_enabled(upper_dev)) { @@ -2231,6 +2271,70 @@ dpaa2_switch_prechangeupper_sanity_checks(struct net_device *netdev, return err; } + err = netdev_walk_all_lower_dev(upper_dev, + dpaa2_switch_check_dpsw_instance, + &data); + if (err) { + NL_SET_ERR_MSG_MOD(extack, + "Interface from a different DPSW is in the bridge already"); + return -EINVAL; + } + + return 0; +} + +static int dpaa2_switch_pre_lag_join(struct net_device *netdev, + struct net_device *upper_dev, + struct netdev_lag_upper_info *info, + struct netlink_ext_ack *extack) +{ + struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct ethsw_port_priv *other_port_priv; + struct dpaa2_switch_lag *lag = NULL; + struct dpsw_lag_cfg cfg = {0}; + struct net_device *other_dev; + int i, num_ifs = 0, err; + struct list_head *iter; + + if (!(ethsw->features & ETHSW_FEATURE_LAG_OFFLOAD)) { + NL_SET_ERR_MSG_MOD(extack, + "LAG offload is supported only for DPSW >= v8.13"); + return -EOPNOTSUPP; + } + + if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH) { + NL_SET_ERR_MSG_MOD(extack, + "Can only offload LAG using hash TX type"); + return -EOPNOTSUPP; + } + + if (info->hash_type != NETDEV_LAG_HASH_L23) { + NL_SET_ERR_MSG_MOD(extack, "Can only offload L2+L3 Tx hash"); + return -EOPNOTSUPP; + } + + if (!dpaa2_switch_port_has_mac(port_priv)) { + NL_SET_ERR_MSG_MOD(extack, + "Only switch interfaces connected to MACs can be under a LAG"); + return -EINVAL; + } + + if (vlan_uses_dev(upper_dev)) { + NL_SET_ERR_MSG_MOD(extack, + "Cannot join a LAG upper that has a VLAN"); + return -EOPNOTSUPP; + } + + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + if (!ethsw->lags[i].in_use) + continue; + if (ethsw->lags[i].bond_dev != upper_dev) + continue; + lag = ðsw->lags[i]; + break; + } + netdev_for_each_lower_dev(upper_dev, other_dev, iter) { if (!dpaa2_switch_port_dev_check(other_dev)) continue; @@ -2238,9 +2342,227 @@ dpaa2_switch_prechangeupper_sanity_checks(struct net_device *netdev, other_port_priv = netdev_priv(other_dev); if (other_port_priv->ethsw_data != port_priv->ethsw_data) { NL_SET_ERR_MSG_MOD(extack, - "Interface from a different DPSW is in the bridge already"); + "Interface from a different DPSW is in the bond already"); return -EINVAL; } + + cfg.if_id[num_ifs++] = other_port_priv->idx; + + if (num_ifs >= DPSW_MAX_LAG_IFS) { + NL_SET_ERR_MSG_MOD(extack, + "Cannot add more than 8 DPAA2 switch ports under the same bond"); + return -EINVAL; + } + } + + if (lag) { + cfg.group_id = lag->id; + cfg.if_id[num_ifs++] = port_priv->idx; + cfg.num_ifs = num_ifs; + cfg.phase = DPSW_LAG_SET_PHASE_CHECK; + + err = dpsw_lag_set(ethsw->mc_io, 0, ethsw->dpsw_handle, &cfg); + if (err) { + NL_SET_ERR_MSG_MOD(extack, + "Cannot offload LAG configuration"); + return -EOPNOTSUPP; + } + } + + return 0; +} + +static void dpaa2_switch_port_set_lag_group(struct ethsw_port_priv *port_priv, + struct net_device *bond_dev) +{ + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct ethsw_port_priv *other_port_priv = NULL; + struct dpaa2_switch_lag *lag = NULL; + struct dpaa2_switch_lag *other_lag; + struct net_device *other_dev; + struct list_head *iter; + + netdev_for_each_lower_dev(bond_dev, other_dev, iter) { + if (!dpaa2_switch_port_dev_check(other_dev)) + continue; + + other_port_priv = netdev_priv(other_dev); + other_lag = rtnl_dereference(other_port_priv->lag); + if (!other_lag) + continue; + + if (other_lag->bond_dev == bond_dev) { + rcu_assign_pointer(port_priv->lag, other_lag); + return; + } + } + + /* This is the first interface to be added under a bond device. Find an + * unused LAG group. No need to check for NULL since there are the same + * amount of DPSW ports as LAG groups, meaning that each port can have + * its own LAG group. + */ + lag = dpaa2_switch_lag_get_unused(ethsw); + lag->in_use = true; + lag->bond_dev = bond_dev; + lag->primary = port_priv; + rcu_assign_pointer(port_priv->lag, lag); +} + +static bool dpaa2_switch_port_in_lag(struct ethsw_port_priv *port_priv, + struct net_device *bond_dev) +{ + struct dpaa2_switch_lag *lag; + + if (!port_priv) + return false; + + lag = rtnl_dereference(port_priv->lag); + return lag && lag->bond_dev == bond_dev; +} + +static int dpaa2_switch_set_lag_cfg(struct net_device *bond_dev, u8 lag_id, + struct ethsw_core *ethsw) +{ + struct dpaa2_switch_lag *lag = ðsw->lags[lag_id - 1]; + struct ethsw_port_priv *primary, *new_primary = NULL; + struct ethsw_port_priv *port_priv = NULL; + struct dpsw_lag_cfg cfg = {0}; + u8 num_ifs = 0; + int err, i; + + cfg.group_id = lag_id; + + /* Determine the primary port. The caller clears ->lag on the port that + * is leaving, so a NULL ->lag on the current primary means it is the + * one leaving: elect the first remaining member as the new primary. + * Otherwise keep the current primary. + */ + if (rtnl_dereference(lag->primary->lag)) { + primary = lag->primary; + } else { + primary = NULL; + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + if (dpaa2_switch_port_in_lag(ethsw->ports[i], bond_dev)) { + new_primary = ethsw->ports[i]; + primary = new_primary; + break; + } + } + } + + /* Build the interface list, always placing the primary first */ + if (primary) + cfg.if_id[num_ifs++] = primary->idx; + + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + port_priv = ethsw->ports[i]; + if (port_priv == primary) + continue; + if (!dpaa2_switch_port_in_lag(port_priv, bond_dev)) + continue; + + cfg.if_id[num_ifs++] = port_priv->idx; + } + cfg.num_ifs = num_ifs; + + /* No more interfaces under this LAG group, mark it as not in use. Wait + * for a grace period so that any readers of the lag structure finished. + */ + if (!num_ifs) { + synchronize_net(); + + lag->bond_dev = NULL; + lag->primary = NULL; + lag->in_use = false; + } + + err = dpsw_lag_set(ethsw->mc_io, 0, ethsw->dpsw_handle, &cfg); + if (err) + return err; + + if (new_primary) { + synchronize_net(); + lag->primary = new_primary; + } + + return 0; +} + +static int dpaa2_switch_port_bond_join(struct net_device *netdev, + struct net_device *bond_dev, + struct netdev_lag_upper_info *info, + struct netlink_ext_ack *extack) +{ + struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct net_device *bridge_dev; + struct dpaa2_switch_lag *lag; + int err = 0; + u8 lag_id; + + /* Setup the port_priv->lag pointer for this switch port */ + dpaa2_switch_port_set_lag_group(port_priv, bond_dev); + + /* Create the LAG configuration and apply it in MC */ + lag = rtnl_dereference(port_priv->lag); + lag_id = lag->id; + err = dpaa2_switch_set_lag_cfg(bond_dev, lag_id, ethsw); + if (err) + goto err_lag_cfg; + + /* If the bond device is a switch port, join the bridge as well */ + bridge_dev = netdev_master_upper_dev_get(bond_dev); + if (!bridge_dev || !netif_is_bridge_master(bridge_dev)) + return 0; + + err = dpaa2_switch_port_bridge_join(netdev, bridge_dev, extack); + if (err) + goto err_lag_cfg; + + return err; + +err_lag_cfg: + rcu_assign_pointer(port_priv->lag, NULL); + dpaa2_switch_set_lag_cfg(bond_dev, lag_id, ethsw); + + return err; +} + +static int dpaa2_switch_port_bond_leave(struct net_device *netdev, + struct net_device *bond_dev) +{ + struct net_device *bridge_dev = netdev_master_upper_dev_get(bond_dev); + struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct dpaa2_switch_lag *lag = rtnl_dereference(port_priv->lag); + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct net_device *brpdev; + bool learn_ena; + int err; + + if (!lag) + return 0; + + /* Recreate the LAG configuration for the LAG group that we left. */ + rcu_assign_pointer(port_priv->lag, NULL); + dpaa2_switch_set_lag_cfg(bond_dev, lag->id, ethsw); + + if (bridge_dev && netif_is_bridge_master(bridge_dev)) { + /* Make sure that the new primary inherits the learning state */ + if (lag->primary) { + brpdev = dpaa2_switch_port_to_bridge_port(lag->primary); + learn_ena = br_port_flag_is_set(brpdev, BR_LEARNING); + err = dpaa2_switch_port_set_learning(lag->primary, + learn_ena); + if (err) + return err; + lag->primary->learn_ena = learn_ena; + } + + /* In case the bond is a bridge port, leave the upper bridge as + * well. + */ + return dpaa2_switch_port_bridge_leave(netdev); } return 0; @@ -2250,8 +2572,8 @@ static int dpaa2_switch_port_prechangeupper(struct net_device *netdev, struct netdev_notifier_changeupper_info *info) { struct ethsw_port_priv *port_priv; + struct net_device *upper_dev, *br; struct netlink_ext_ack *extack; - struct net_device *upper_dev; int err; if (!dpaa2_switch_port_dev_check(netdev)) @@ -2268,6 +2590,24 @@ static int dpaa2_switch_port_prechangeupper(struct net_device *netdev, if (!info->linking) dpaa2_switch_port_pre_bridge_leave(netdev); + } else if (netif_is_lag_master(upper_dev)) { + if (!info->linking) { + if (netif_is_bridge_port(upper_dev)) + dpaa2_switch_port_pre_bridge_leave(netdev); + return 0; + } + + if (netif_is_bridge_port(upper_dev)) { + br = netdev_master_upper_dev_get(upper_dev); + err = dpaa2_switch_prechangeupper_sanity_checks(netdev, + br, + extack); + if (err) + return err; + } + + return dpaa2_switch_pre_lag_join(netdev, upper_dev, + info->upper_info, extack); } else if (is_vlan_dev(upper_dev)) { port_priv = netdev_priv(netdev); if (port_priv->fdb->bridge_dev) { @@ -2299,6 +2639,80 @@ static int dpaa2_switch_port_changeupper(struct net_device *netdev, extack); else return dpaa2_switch_port_bridge_leave(netdev); + } else if (netif_is_lag_master(upper_dev)) { + if (info->linking) + return dpaa2_switch_port_bond_join(netdev, upper_dev, + info->upper_info, + extack); + else + return dpaa2_switch_port_bond_leave(netdev, upper_dev); + } + + return 0; +} + +static int +dpaa2_switch_lag_prechangeupper(struct net_device *netdev, + struct netdev_notifier_changeupper_info *info) +{ + struct net_device *lower; + struct list_head *iter; + int err = 0; + + if (!netif_is_lag_master(netdev)) + return 0; + + netdev_for_each_lower_dev(netdev, lower, iter) { + if (!dpaa2_switch_port_dev_check(lower)) + continue; + + err = dpaa2_switch_port_prechangeupper(lower, info); + if (err) + return err; + } + + return err; +} + +static int +dpaa2_switch_lag_changeupper(struct net_device *netdev, + struct netdev_notifier_changeupper_info *info) +{ + struct net_device *lower; + struct list_head *iter; + int err = 0; + + if (!netif_is_lag_master(netdev)) + return 0; + + netdev_for_each_lower_dev(netdev, lower, iter) { + if (!dpaa2_switch_port_dev_check(lower)) + continue; + + err = dpaa2_switch_port_changeupper(lower, info); + if (err) + return err; + } + + return 0; +} + +static int +dpaa2_switch_port_changelowerstate(struct net_device *netdev, + struct netdev_lag_lower_state_info *linfo) +{ + struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct ethsw_core *ethsw = port_priv->ethsw_data; + int err; + + if (!rtnl_dereference(port_priv->lag)) + return 0; + + err = dpsw_if_set_lag_state(ethsw->mc_io, 0, ethsw->dpsw_handle, + port_priv->idx, linfo->tx_enabled ? 1 : 0); + if (err) { + netdev_err(netdev, "dpsw_if_set_lag_state() = %d\n", err); + return err; } return 0; @@ -2308,6 +2722,7 @@ static int dpaa2_switch_port_netdevice_event(struct notifier_block *nb, unsigned long event, void *ptr) { struct net_device *netdev = netdev_notifier_info_to_dev(ptr); + struct netdev_notifier_changelowerstate_info *info; int err = 0; switch (event) { @@ -2316,13 +2731,29 @@ static int dpaa2_switch_port_netdevice_event(struct notifier_block *nb, if (err) return notifier_from_errno(err); + err = dpaa2_switch_lag_prechangeupper(netdev, ptr); + if (err) + return notifier_from_errno(err); + break; case NETDEV_CHANGEUPPER: err = dpaa2_switch_port_changeupper(netdev, ptr); if (err) return notifier_from_errno(err); + err = dpaa2_switch_lag_changeupper(netdev, ptr); + if (err) + return notifier_from_errno(err); + break; + case NETDEV_CHANGELOWERSTATE: + info = ptr; + if (!dpaa2_switch_port_dev_check(netdev)) + break; + + err = dpaa2_switch_port_changelowerstate(netdev, + info->lower_state_info); + return notifier_from_errno(err); } return NOTIFY_DONE; @@ -2581,6 +3012,9 @@ static void dpaa2_switch_detect_features(struct ethsw_core *ethsw) if (ethsw->major > 8 || (ethsw->major == 8 && ethsw->minor >= 6)) ethsw->features |= ETHSW_FEATURE_MAC_ADDR; + + if (ethsw->major > 8 || (ethsw->major == 8 && ethsw->minor >= 13)) + ethsw->features |= ETHSW_FEATURE_LAG_OFFLOAD; } static int dpaa2_switch_setup_fqs(struct ethsw_core *ethsw) @@ -3370,6 +3804,7 @@ static void dpaa2_switch_remove(struct fsl_mc_device *sw_dev) kfree(ethsw->fdbs); kfree(ethsw->filter_blocks); kfree(ethsw->ports); + kfree(ethsw->lags); dpaa2_switch_teardown(sw_dev); @@ -3397,6 +3832,7 @@ static int dpaa2_switch_probe_port(struct ethsw_core *ethsw, port_priv = netdev_priv(port_netdev); port_priv->netdev = port_netdev; port_priv->ethsw_data = ethsw; + rcu_assign_pointer(port_priv->lag, NULL); mutex_init(&port_priv->mac_lock); @@ -3504,6 +3940,19 @@ static int dpaa2_switch_probe(struct fsl_mc_device *sw_dev) goto err_free_fdbs; } + ethsw->lags = kcalloc(ethsw->sw_attr.num_ifs, sizeof(*ethsw->lags), + GFP_KERNEL); + if (!ethsw->lags) { + err = -ENOMEM; + goto err_free_filter; + } + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + ethsw->lags[i].bond_dev = NULL; + ethsw->lags[i].ethsw = ethsw; + ethsw->lags[i].id = i + 1; + ethsw->lags[i].in_use = 0; + } + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { err = dpaa2_switch_probe_port(ethsw, i); if (err) @@ -3550,6 +3999,8 @@ static int dpaa2_switch_probe(struct fsl_mc_device *sw_dev) err_free_netdev: for (i--; i >= 0; i--) dpaa2_switch_remove_port(ethsw, i); + kfree(ethsw->lags); +err_free_filter: kfree(ethsw->filter_blocks); err_free_fdbs: kfree(ethsw->fdbs); diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h index 42b3ca73f55d..c98bddd7e359 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h @@ -41,7 +41,8 @@ #define ETHSW_MAX_FRAME_LENGTH (DPAA2_MFL - VLAN_ETH_HLEN - ETH_FCS_LEN) #define ETHSW_L2_MAX_FRM(mtu) ((mtu) + VLAN_ETH_HLEN + ETH_FCS_LEN) -#define ETHSW_FEATURE_MAC_ADDR BIT(0) +#define ETHSW_FEATURE_MAC_ADDR BIT(0) +#define ETHSW_FEATURE_LAG_OFFLOAD BIT(1) /* Number of receive queues (one RX and one TX_CONF) */ #define DPAA2_SWITCH_RX_NUM_FQS 2 @@ -105,6 +106,14 @@ struct dpaa2_switch_fdb { bool in_use; }; +struct dpaa2_switch_lag { + struct ethsw_core *ethsw; + struct net_device *bond_dev; + bool in_use; + u8 id; + struct ethsw_port_priv *primary; +}; + struct dpaa2_switch_acl_entry { struct list_head list; u16 prio; @@ -163,6 +172,8 @@ struct ethsw_port_priv { struct dpaa2_mac *mac; /* Protects against changes to port_priv->mac */ struct mutex mac_lock; + + struct dpaa2_switch_lag __rcu *lag; }; /* Switch data */ @@ -190,6 +201,8 @@ struct ethsw_core { struct dpaa2_switch_fdb *fdbs; struct dpaa2_switch_filter_block *filter_blocks; u16 mirror_port; + + struct dpaa2_switch_lag *lags; }; static inline int dpaa2_switch_get_index(struct ethsw_core *ethsw, From 711c0beea13f40a425dbecb3f4cdaf02ad5a06fe Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Mon, 29 Jun 2026 14:23:06 +0300 Subject: [PATCH 0065/1433] dpaa2-switch: offload FDBs added on an upper bond device This patch adds support for offloading FDB entries added on upper bond devices. First of all, the call to switchdev_bridge_port_offload() is updated so that the notifier blocks needed for FDB events replay are available to the bridge core. Using switchdev_handle_*() helpers is also necessary because each FDB event needs to be fanned out to any DPAA2 switch lower device. This triggers another change in the return type used by the dpaa2_switch_port_fdb_event() - from notifier types to regular errno types. Handling of the SWITCHDEV_FDB_ADD_TO_DEVICE/SWITCHDEV_FDB_DEL_TO_DEVICE events is updated so that the newly dpaa2_switch_lag_fdb_add() / dpaa2_switch_lag_fdb_del() functions are called anytime a port is under a bond device. This will allow us to manage refcounting on FDB entries which are added on the upper bond devices. The DPAA2 switch uses shared-VLAN learning which means that the vid parameter is not used when adding an FDB entry to HW. The current behavior when dealing with FDB entries with the same MAC address but different VLANs is to add the entry to HW every time while removal will get done on the first 'bridge fdb del' command issued by the user. The same behavior is kept also for FDBs added on bond devices by keeping the refcount on the {vid, addr} pair while the HW operation disregards entirely the vid parameter. Signed-off-by: Ioana Ciornei Link: https://patch.msgid.link/20260629112309.154328-11-ioana.ciornei@nxp.com Signed-off-by: Paolo Abeni --- .../ethernet/freescale/dpaa2/dpaa2-switch.c | 227 ++++++++++++++++-- .../ethernet/freescale/dpaa2/dpaa2-switch.h | 24 ++ 2 files changed, 225 insertions(+), 26 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 949a7241a00f..307b3b7a1bfb 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -25,6 +25,9 @@ #define DEFAULT_VLAN_ID 1 +static struct notifier_block dpaa2_switch_port_switchdev_nb; +static struct notifier_block dpaa2_switch_port_switchdev_blocking_nb; + static u16 dpaa2_switch_port_get_fdb_id(struct ethsw_port_priv *port_priv) { return port_priv->fdb->fdb_id; @@ -585,6 +588,81 @@ static int dpaa2_switch_port_fdb_del(struct ethsw_port_priv *port_priv, return dpaa2_switch_port_fdb_del_mc(port_priv, addr); } +static struct dpaa2_mac_addr * +dpaa2_switch_mac_addr_find(struct list_head *addr_list, + const unsigned char *addr, u16 vid) +{ + struct dpaa2_mac_addr *a; + + list_for_each_entry(a, addr_list, list) + if (ether_addr_equal(a->addr, addr) && a->vid == vid) + return a; + + return NULL; +} + +static int dpaa2_switch_lag_fdb_add(struct dpaa2_switch_lag *lag, + const unsigned char *addr, u16 vid) +{ + struct ethsw_port_priv *port_priv = lag->primary; + struct dpaa2_mac_addr *a; + int err = 0; + + mutex_lock(&lag->fdb_lock); + + a = dpaa2_switch_mac_addr_find(&lag->fdbs, addr, vid); + if (a) { + refcount_inc(&a->refcount); + goto out; + } + + a = kzalloc(sizeof(*a), GFP_KERNEL); + if (!a) { + err = -ENOMEM; + goto out; + } + + err = dpaa2_switch_port_fdb_add(port_priv, addr); + if (err) { + kfree(a); + goto out; + } + + ether_addr_copy(a->addr, addr); + a->vid = vid; + refcount_set(&a->refcount, 1); + list_add_tail(&a->list, &lag->fdbs); + +out: + mutex_unlock(&lag->fdb_lock); + + return err; +} + +static void dpaa2_switch_lag_fdb_del(struct dpaa2_switch_lag *lag, + const unsigned char *addr, u16 vid) +{ + struct ethsw_port_priv *port_priv = lag->primary; + struct dpaa2_mac_addr *a; + + mutex_lock(&lag->fdb_lock); + + a = dpaa2_switch_mac_addr_find(&lag->fdbs, addr, vid); + if (!a) + goto out; + + if (!refcount_dec_and_test(&a->refcount)) + goto out; + + list_del(&a->list); + kfree(a); + + dpaa2_switch_port_fdb_del(port_priv, addr); + +out: + mutex_unlock(&lag->fdb_lock); +} + static void dpaa2_switch_port_get_stats(struct net_device *netdev, struct rtnl_link_stats64 *stats) { @@ -1533,6 +1611,33 @@ bool dpaa2_switch_port_dev_check(const struct net_device *netdev) return netdev->netdev_ops == &dpaa2_switch_port_ops; } +static bool dpaa2_switch_foreign_dev_check(const struct net_device *dev, + const struct net_device *foreign_dev) +{ + struct ethsw_port_priv *port_priv = netdev_priv(dev); + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct ethsw_port_priv *other_port; + int i; + + if (netif_is_bridge_master(foreign_dev)) + if (port_priv->fdb->bridge_dev == foreign_dev) + return false; + + if (netif_is_bridge_port(foreign_dev)) { + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + other_port = ethsw->ports[i]; + + if (!other_port) + continue; + if (dpaa2_switch_port_offloads_bridge_port(other_port, + foreign_dev)) + return false; + } + } + + return true; +} + static int dpaa2_switch_port_connect_mac(struct ethsw_port_priv *port_priv) { struct fsl_mc_device *dpsw_port_dev, *dpmac_dev; @@ -2100,8 +2205,10 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev, goto err_egress_flood; brport_dev = dpaa2_switch_port_to_bridge_port(port_priv); - err = switchdev_bridge_port_offload(brport_dev, netdev, NULL, - NULL, NULL, false, extack); + err = switchdev_bridge_port_offload(brport_dev, netdev, port_priv, + &dpaa2_switch_port_switchdev_nb, + &dpaa2_switch_port_switchdev_blocking_nb, + false, extack); if (err) goto err_switchdev_offload; @@ -2143,7 +2250,9 @@ static void dpaa2_switch_port_pre_bridge_leave(struct net_device *netdev) if (!brport_dev) return; - switchdev_bridge_port_unoffload(brport_dev, NULL, NULL, NULL); + switchdev_bridge_port_unoffload(brport_dev, port_priv, + &dpaa2_switch_port_switchdev_nb, + &dpaa2_switch_port_switchdev_blocking_nb); /* Make sure that any FDB add/del operations are completed before the * bridge layout changes @@ -2425,9 +2534,10 @@ static int dpaa2_switch_set_lag_cfg(struct net_device *bond_dev, u8 lag_id, struct ethsw_core *ethsw) { struct dpaa2_switch_lag *lag = ðsw->lags[lag_id - 1]; - struct ethsw_port_priv *primary, *new_primary = NULL; - struct ethsw_port_priv *port_priv = NULL; + struct ethsw_port_priv *primary, *port_priv; + struct ethsw_port_priv *new_primary = NULL; struct dpsw_lag_cfg cfg = {0}; + struct dpaa2_mac_addr *a; u8 num_ifs = 0; int err, i; @@ -2454,7 +2564,6 @@ static int dpaa2_switch_set_lag_cfg(struct net_device *bond_dev, u8 lag_id, /* Build the interface list, always placing the primary first */ if (primary) cfg.if_id[num_ifs++] = primary->idx; - for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { port_priv = ethsw->ports[i]; if (port_priv == primary) @@ -2477,11 +2586,32 @@ static int dpaa2_switch_set_lag_cfg(struct net_device *bond_dev, u8 lag_id, lag->in_use = false; } + /* When the primary changes, migrate the FDB entries from the old + * primary to the new one: remove them before reconfiguring the LAG in + * hardware and re-add them on the new primary afterwards. We do not + * touch any refcounting since the intention is to change the HW entry, + * not the parallel software tracking. + */ + if (new_primary) { + mutex_lock(&lag->fdb_lock); + list_for_each_entry(a, &lag->fdbs, list) + dpaa2_switch_port_fdb_del(lag->primary, a->addr); + mutex_unlock(&lag->fdb_lock); + } + err = dpsw_lag_set(ethsw->mc_io, 0, ethsw->dpsw_handle, &cfg); if (err) return err; if (new_primary) { + mutex_lock(&lag->fdb_lock); + list_for_each_entry(a, &lag->fdbs, list) { + err = dpaa2_switch_port_fdb_add(new_primary, a->addr); + if (err) + netdev_err(new_primary->netdev, "Unable to migrate FDB\n"); + } + mutex_unlock(&lag->fdb_lock); + synchronize_net(); lag->primary = new_primary; } @@ -2763,67 +2893,97 @@ struct ethsw_switchdev_event_work { struct work_struct work; struct switchdev_notifier_fdb_info fdb_info; struct net_device *dev; + struct net_device *orig_dev; unsigned long event; + u16 vid; }; static void dpaa2_switch_event_work(struct work_struct *work) { struct ethsw_switchdev_event_work *switchdev_work = container_of(work, struct ethsw_switchdev_event_work, work); + struct net_device *orig_dev = switchdev_work->orig_dev; struct net_device *dev = switchdev_work->dev; + struct ethsw_port_priv *port_priv = netdev_priv(dev); struct switchdev_notifier_fdb_info *fdb_info; + struct dpaa2_switch_lag *lag; int err; fdb_info = &switchdev_work->fdb_info; + /* The lag structures are freed only from dpaa2_switch_remove(), which + * first flushes this workqueue, so the pointer stays valid for the + * lifetime of the work item. Only the dereference needs the RCU + * read-side lock; the FDB helpers below can sleep and must run outside + * of it. + */ + rcu_read_lock(); + lag = rcu_dereference(port_priv->lag); + rcu_read_unlock(); + switch (switchdev_work->event) { case SWITCHDEV_FDB_ADD_TO_DEVICE: - err = dpaa2_switch_port_fdb_add(netdev_priv(dev), - fdb_info->addr); + if (lag) + err = dpaa2_switch_lag_fdb_add(lag, fdb_info->addr, + switchdev_work->vid); + else + err = dpaa2_switch_port_fdb_add(port_priv, + fdb_info->addr); if (err) break; fdb_info->offloaded = true; - call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, dev, + call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, orig_dev, &fdb_info->info, NULL); break; case SWITCHDEV_FDB_DEL_TO_DEVICE: - dpaa2_switch_port_fdb_del(netdev_priv(dev), fdb_info->addr); + if (lag) + dpaa2_switch_lag_fdb_del(lag, fdb_info->addr, + switchdev_work->vid); + else + dpaa2_switch_port_fdb_del(port_priv, fdb_info->addr); break; } kfree(switchdev_work->fdb_info.addr); kfree(switchdev_work); dev_put(dev); + dev_put(orig_dev); } -static int dpaa2_switch_port_fdb_event(struct notifier_block *nb, - unsigned long event, void *ptr) +static int +dpaa2_switch_port_fdb_event(struct net_device *dev, + struct net_device *orig_dev, + unsigned long event, const void *ctx, + const struct switchdev_notifier_fdb_info *fdb_info) { - struct net_device *dev = switchdev_notifier_info_to_dev(ptr); struct ethsw_port_priv *port_priv = netdev_priv(dev); struct ethsw_switchdev_event_work *switchdev_work; - struct switchdev_notifier_fdb_info *fdb_info = ptr; - struct ethsw_core *ethsw; + struct ethsw_core *ethsw = port_priv->ethsw_data; - if (!dpaa2_switch_port_dev_check(dev)) - return NOTIFY_DONE; - ethsw = port_priv->ethsw_data; + if (ctx && ctx != port_priv) + return 0; + + /* For the moment, do nothing with entries towards foreign devices */ + if (dpaa2_switch_foreign_dev_check(dev, orig_dev)) + return 0; if (!fdb_info->added_by_user || fdb_info->is_local) - return NOTIFY_DONE; + return 0; switchdev_work = kzalloc_obj(*switchdev_work, GFP_ATOMIC); if (!switchdev_work) - return NOTIFY_BAD; + return -ENOMEM; INIT_WORK(&switchdev_work->work, dpaa2_switch_event_work); switchdev_work->dev = dev; switchdev_work->event = event; + switchdev_work->orig_dev = orig_dev; + switchdev_work->vid = fdb_info->vid; switch (event) { case SWITCHDEV_FDB_ADD_TO_DEVICE: case SWITCHDEV_FDB_DEL_TO_DEVICE: - memcpy(&switchdev_work->fdb_info, ptr, + memcpy(&switchdev_work->fdb_info, fdb_info, sizeof(switchdev_work->fdb_info)); switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC); if (!switchdev_work->fdb_info.addr) @@ -2834,19 +2994,20 @@ static int dpaa2_switch_port_fdb_event(struct notifier_block *nb, /* Take a reference on the device to avoid being freed. */ dev_hold(dev); + dev_hold(orig_dev); break; default: kfree(switchdev_work); - return NOTIFY_DONE; + return 0; } queue_work(ethsw->workqueue, &switchdev_work->work); - return NOTIFY_DONE; + return 0; err_addr_alloc: kfree(switchdev_work); - return NOTIFY_BAD; + return -ENOMEM; } /* Called under rcu_read_lock() */ @@ -2854,13 +3015,18 @@ static int dpaa2_switch_port_event(struct notifier_block *nb, unsigned long event, void *ptr) { struct net_device *dev = switchdev_notifier_info_to_dev(ptr); + int err; switch (event) { case SWITCHDEV_PORT_ATTR_SET: return dpaa2_switch_port_attr_set_event(dev, ptr); case SWITCHDEV_FDB_ADD_TO_DEVICE: case SWITCHDEV_FDB_DEL_TO_DEVICE: - return dpaa2_switch_port_fdb_event(nb, event, ptr); + err = switchdev_handle_fdb_event_to_device(dev, event, ptr, + dpaa2_switch_port_dev_check, + dpaa2_switch_foreign_dev_check, + dpaa2_switch_port_fdb_event); + return notifier_from_errno(err); default: return NOTIFY_DONE; } @@ -3785,6 +3951,9 @@ static void dpaa2_switch_remove(struct fsl_mc_device *sw_dev) dev = &sw_dev->dev; ethsw = dev_get_drvdata(dev); + /* Make sure that all events were handled before we kfree anything */ + flush_workqueue(ethsw->workqueue); + dpaa2_switch_teardown_irqs(sw_dev); dpsw_disable(ethsw->mc_io, 0, ethsw->dpsw_handle); @@ -3798,8 +3967,10 @@ static void dpaa2_switch_remove(struct fsl_mc_device *sw_dev) for (i = 0; i < DPAA2_SWITCH_RX_NUM_FQS; i++) netif_napi_del(ðsw->fq[i].napi); - for (i = 0; i < ethsw->sw_attr.num_ifs; i++) + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { dpaa2_switch_remove_port(ethsw, i); + mutex_destroy(ðsw->lags[i].fdb_lock); + } kfree(ethsw->fdbs); kfree(ethsw->filter_blocks); @@ -3951,6 +4122,8 @@ static int dpaa2_switch_probe(struct fsl_mc_device *sw_dev) ethsw->lags[i].ethsw = ethsw; ethsw->lags[i].id = i + 1; ethsw->lags[i].in_use = 0; + mutex_init(ðsw->lags[i].fdb_lock); + INIT_LIST_HEAD(ðsw->lags[i].fdbs); } for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { @@ -3999,6 +4172,8 @@ static int dpaa2_switch_probe(struct fsl_mc_device *sw_dev) err_free_netdev: for (i--; i >= 0; i--) dpaa2_switch_remove_port(ethsw, i); + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) + mutex_destroy(ðsw->lags[i].fdb_lock); kfree(ethsw->lags); err_free_filter: kfree(ethsw->filter_blocks); diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h index c98bddd7e359..e8bc1469cbf7 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h @@ -100,6 +100,13 @@ struct dpaa2_switch_fq { u32 fqid; }; +struct dpaa2_mac_addr { + unsigned char addr[ETH_ALEN]; + u16 vid; + refcount_t refcount; + struct list_head list; +}; + struct dpaa2_switch_fdb { struct net_device *bridge_dev; u16 fdb_id; @@ -112,6 +119,9 @@ struct dpaa2_switch_lag { bool in_use; u8 id; struct ethsw_port_priv *primary; + /* Protects the list of fdbs installed on this LAG */ + struct mutex fdb_lock; + struct list_head fdbs; }; struct dpaa2_switch_acl_entry { @@ -287,4 +297,18 @@ int dpaa2_switch_block_offload_mirror(struct dpaa2_switch_filter_block *block, int dpaa2_switch_block_unoffload_mirror(struct dpaa2_switch_filter_block *block, struct ethsw_port_priv *port_priv); + +static inline bool +dpaa2_switch_port_offloads_bridge_port(struct ethsw_port_priv *port_priv, + const struct net_device *dev) +{ + struct dpaa2_switch_lag *lag = rcu_dereference_rtnl(port_priv->lag); + + if (lag && lag->bond_dev == dev) + return true; + if (port_priv->netdev == dev) + return true; + return false; +} + #endif /* __ETHSW_H */ From f0a7468fdbeb5cb2b232c4a25f3300b4c4802066 Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Mon, 29 Jun 2026 14:23:07 +0300 Subject: [PATCH 0066/1433] dpaa2-switch: offload port objects on an upper bond device This patch adds support for offloading port objects, VLANs and MDBs, added on upper bond devices. First of all, the use of the switchdev_handle_*() replication helpers is introduced for the SWITCHDEV_PORT_OBJ_ADD/SWITCHDEV_PORT_OBJ_DEL events. With this change, setting up the 'port_obj_info->handled = true' is not needed anymore since it's now handled by the new helpers. In the DPAA2 architecture, there is no difference in adding a FDB or MDB which points towards a LAG port. Unlike other architectures, we do not need to populate all the possible destinations which are under the LAG, we only have to specify a single queueing destination (QDID) which represents the LAG. This all means that handling of MDBs in bond devices needs to have refcount mechanism as with the FDBs. This mechanism is triggered by calling the dpaa2_switch_lag_fdb_add() / dpaa2_switch_lag_fdb_del() functions which were added in the previous patch. Also change how dpaa2_switch_port_mdb_del() behaves in case the underlying HW operation failed. Since the delete operations cannot be stopped from a switchdev standpoint, go ahead and ignore the return code from the dpaa2_switch_*_fdb_del() calls. Signed-off-by: Ioana Ciornei Link: https://patch.msgid.link/20260629112309.154328-12-ioana.ciornei@nxp.com Signed-off-by: Paolo Abeni --- .../ethernet/freescale/dpaa2/dpaa2-switch.c | 69 +++++++++++-------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 307b3b7a1bfb..1f7875ecefe2 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -2017,15 +2017,28 @@ static int dpaa2_switch_port_mdb_add(struct net_device *netdev, const struct switchdev_obj_port_mdb *mdb) { struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct dpaa2_switch_lag *lag; - return dpaa2_switch_port_fdb_add(port_priv, mdb->addr); + lag = rtnl_dereference(port_priv->lag); + if (lag) + return dpaa2_switch_lag_fdb_add(lag, mdb->addr, mdb->vid); + else + return dpaa2_switch_port_fdb_add(port_priv, mdb->addr); } -static int dpaa2_switch_port_obj_add(struct net_device *netdev, - const struct switchdev_obj *obj) +static int dpaa2_switch_port_obj_add(struct net_device *netdev, const void *ctx, + const struct switchdev_obj *obj, + struct netlink_ext_ack *extack) { + struct ethsw_port_priv *port_priv = netdev_priv(netdev); int err; + if (ctx && ctx != port_priv) + return 0; + + if (!dpaa2_switch_port_offloads_bridge_port(port_priv, obj->orig_dev)) + return -EOPNOTSUPP; + switch (obj->id) { case SWITCHDEV_OBJ_ID_PORT_VLAN: err = dpaa2_switch_port_vlans_add(netdev, @@ -2121,15 +2134,29 @@ static int dpaa2_switch_port_mdb_del(struct net_device *netdev, const struct switchdev_obj_port_mdb *mdb) { struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct dpaa2_switch_lag *lag; - return dpaa2_switch_port_fdb_del(port_priv, mdb->addr); + lag = rtnl_dereference(port_priv->lag); + if (lag) + dpaa2_switch_lag_fdb_del(lag, mdb->addr, mdb->vid); + else + dpaa2_switch_port_fdb_del(port_priv, mdb->addr); + + return 0; } -static int dpaa2_switch_port_obj_del(struct net_device *netdev, +static int dpaa2_switch_port_obj_del(struct net_device *netdev, const void *ctx, const struct switchdev_obj *obj) { + struct ethsw_port_priv *port_priv = netdev_priv(netdev); int err; + if (ctx && ctx != port_priv) + return 0; + + if (!dpaa2_switch_port_offloads_bridge_port(port_priv, obj->orig_dev)) + return -EOPNOTSUPP; + switch (obj->id) { case SWITCHDEV_OBJ_ID_PORT_VLAN: err = dpaa2_switch_port_vlans_del(netdev, SWITCHDEV_OBJ_PORT_VLAN(obj)); @@ -3032,37 +3059,23 @@ static int dpaa2_switch_port_event(struct notifier_block *nb, } } -static int dpaa2_switch_port_obj_event(unsigned long event, - struct net_device *netdev, - struct switchdev_notifier_port_obj_info *port_obj_info) -{ - int err = -EOPNOTSUPP; - - if (!dpaa2_switch_port_dev_check(netdev)) - return NOTIFY_DONE; - - switch (event) { - case SWITCHDEV_PORT_OBJ_ADD: - err = dpaa2_switch_port_obj_add(netdev, port_obj_info->obj); - break; - case SWITCHDEV_PORT_OBJ_DEL: - err = dpaa2_switch_port_obj_del(netdev, port_obj_info->obj); - break; - } - - port_obj_info->handled = true; - return notifier_from_errno(err); -} - static int dpaa2_switch_port_blocking_event(struct notifier_block *nb, unsigned long event, void *ptr) { struct net_device *dev = switchdev_notifier_info_to_dev(ptr); + int err; switch (event) { case SWITCHDEV_PORT_OBJ_ADD: + err = switchdev_handle_port_obj_add(dev, ptr, + dpaa2_switch_port_dev_check, + dpaa2_switch_port_obj_add); + return notifier_from_errno(err); case SWITCHDEV_PORT_OBJ_DEL: - return dpaa2_switch_port_obj_event(event, dev, ptr); + err = switchdev_handle_port_obj_del(dev, ptr, + dpaa2_switch_port_dev_check, + dpaa2_switch_port_obj_del); + return notifier_from_errno(err); case SWITCHDEV_PORT_ATTR_SET: return dpaa2_switch_port_attr_set_event(dev, ptr); } From a0a8970b516d7aa8ca94ec84d12be4b5cfbf0025 Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Mon, 29 Jun 2026 14:23:08 +0300 Subject: [PATCH 0067/1433] dpaa2-switch: trap all link local reserved addresses to the CPU Do not trap only STP frames to the control interface but rather trap all link local reserved addresses. This will still be done by looking at the destination MAC address but keeping in mind to not take into account the last byte. This change will benefit LACP frames which now will reach the control interface. While at it, change the prototype of the dpaa2_switch_port_trap_mac_addr() function so that we directly pass a 'const u8 *' so that it matches the ether_addr_copy() used. Signed-off-by: Ioana Ciornei Link: https://patch.msgid.link/20260629112309.154328-13-ioana.ciornei@nxp.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 1f7875ecefe2..b94d83f5ef06 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -3828,17 +3828,15 @@ static int dpaa2_switch_init(struct fsl_mc_device *sw_dev) return err; } -/* Add an ACL to redirect frames with specific destination MAC address to - * control interface - */ +/* Add an ACL to redirect frames to control interface based on the dst MAC */ static int dpaa2_switch_port_trap_mac_addr(struct ethsw_port_priv *port_priv, - const char *mac) + const u8 *mac, const u8 *mask) { struct dpaa2_switch_acl_entry acl_entry = {0}; /* Match on the destination MAC address */ ether_addr_copy(acl_entry.key.match.l2_dest_mac, mac); - eth_broadcast_addr(acl_entry.key.mask.l2_dest_mac); + ether_addr_copy(acl_entry.key.mask.l2_dest_mac, mask); /* Trap to CPU */ acl_entry.cfg.precedence = 0; @@ -3849,7 +3847,8 @@ static int dpaa2_switch_port_trap_mac_addr(struct ethsw_port_priv *port_priv, static int dpaa2_switch_port_init(struct ethsw_port_priv *port_priv, u16 port) { - const char stpa[ETH_ALEN] = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x00}; + const u8 ll_mac[ETH_ALEN] = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x00}; + const u8 ll_mask[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xf0}; struct switchdev_obj_port_vlan vlan = { .obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN, .vid = DEFAULT_VLAN_ID, @@ -3924,7 +3923,7 @@ static int dpaa2_switch_port_init(struct ethsw_port_priv *port_priv, u16 port) if (err) return err; - err = dpaa2_switch_port_trap_mac_addr(port_priv, stpa); + err = dpaa2_switch_port_trap_mac_addr(port_priv, ll_mac, ll_mask); if (err) return err; From f985358f4ee231a77e00eb45c5b0f99a30c0a3d4 Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Mon, 29 Jun 2026 14:23:09 +0300 Subject: [PATCH 0068/1433] dpaa2-switch: add support for imprecise source port Switch ports configured as part of a LAG group are not able to provide a precise source port for all packets which reach the control interface. The only frames which will have a precise source port are those that are explicitly trapped, for example STP and LCAP frames. For any other frames (for example, those which are flooded) we can only know the ingress LAG group. Take into account the DPAA2_ETHSW_FLC_IMPRECISE_IF_ID bit and based on its value target the bond device or the specific source netdevice. Signed-off-by: Ioana Ciornei Link: https://patch.msgid.link/20260629112309.154328-14-ioana.ciornei@nxp.com Signed-off-by: Paolo Abeni --- .../net/ethernet/freescale/dpaa2/dpaa2-switch.c | 15 +++++++++++++-- .../net/ethernet/freescale/dpaa2/dpaa2-switch.h | 3 +++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index b94d83f5ef06..8320b26c3f72 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -3120,19 +3120,22 @@ static void dpaa2_switch_rx(struct dpaa2_switch_fq *fq, dma_addr_t addr = dpaa2_fd_get_addr(fd); struct ethsw_core *ethsw = fq->ethsw; struct ethsw_port_priv *port_priv; + struct dpaa2_switch_lag *lag; struct net_device *netdev; struct vlan_ethhdr *hdr; struct sk_buff *skb; u16 vlan_tci, vid; int if_id, err; void *vaddr; + u64 flc; vaddr = dpaa2_iova_to_virt(ethsw->iommu_domain, addr); dma_unmap_page(ethsw->dev, addr, DPAA2_SWITCH_RX_BUF_SIZE, DMA_FROM_DEVICE); /* get switch ingress interface ID */ - if_id = upper_32_bits(dpaa2_fd_get_flc(fd)) & 0x0000FFFF; + flc = dpaa2_fd_get_flc(fd); + if_id = DPAA2_ETHSW_FLC_IF_ID(flc); if (if_id >= ethsw->sw_attr.num_ifs) { dev_err(ethsw->dev, "Frame received from unknown interface!\n"); goto err_free_fd; @@ -3171,12 +3174,20 @@ static void dpaa2_switch_rx(struct dpaa2_switch_fq *fq, } } - skb->dev = netdev; + rcu_read_lock(); + + lag = rcu_dereference(port_priv->lag); + if (DPAA2_ETHSW_FLC_IMPRECISE_IF_ID(flc) && lag) + skb->dev = lag->bond_dev; + else + skb->dev = netdev; skb->protocol = eth_type_trans(skb, skb->dev); /* Setup the offload_fwd_mark only if the port is under a bridge */ skb->offload_fwd_mark = !!(port_priv->fdb->bridge_dev); + rcu_read_unlock(); + netif_receive_skb(skb); return; diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h index e8bc1469cbf7..63b702b0000c 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h @@ -87,6 +87,9 @@ #define DPAA2_ETHSW_PORT_ACL_CMD_BUF_SIZE 256 +#define DPAA2_ETHSW_FLC_IF_ID(flc) (((flc) >> 32) & GENMASK(15, 0)) +#define DPAA2_ETHSW_FLC_IMPRECISE_IF_ID(flc) ((flc) & BIT_ULL(63)) + extern const struct ethtool_ops dpaa2_switch_port_ethtool_ops; struct ethsw_core; From 54fd3962c99df50056660747a9e783af27410126 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Mon, 29 Jun 2026 18:10:53 +0000 Subject: [PATCH 0069/1433] net: fib_rules: Make fib_rules_ops.delete() return void. Since commit d954a67a7dfa ("ipv4: fib_rule: Move fib4_rules_exit() to ->exit()."), both fib4_rule_delete() and fib6_rule_delete() always return 0. Let's change the return type to void. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260629181226.1929658-2-kuniyu@google.com Reviewed-by: Ido Schimmel Signed-off-by: Paolo Abeni --- include/net/fib_rules.h | 2 +- net/core/fib_rules.c | 7 ++----- net/ipv4/fib_rules.c | 4 +--- net/ipv6/fib6_rules.c | 4 +--- 4 files changed, 5 insertions(+), 12 deletions(-) diff --git a/include/net/fib_rules.h b/include/net/fib_rules.h index 7dee0ae616e3..f9a4bca51eda 100644 --- a/include/net/fib_rules.h +++ b/include/net/fib_rules.h @@ -82,7 +82,7 @@ struct fib_rules_ops { struct fib_rule_hdr *, struct nlattr **, struct netlink_ext_ack *); - int (*delete)(struct fib_rule *); + void (*delete)(struct fib_rule *); int (*compare)(struct fib_rule *, struct fib_rule_hdr *, struct nlattr **); diff --git a/net/core/fib_rules.c b/net/core/fib_rules.c index cf374c208732..961eb709f256 100644 --- a/net/core/fib_rules.c +++ b/net/core/fib_rules.c @@ -1055,11 +1055,8 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, goto errout_free; } - if (ops->delete) { - err = ops->delete(rule); - if (err) - goto errout_free; - } + if (ops->delete) + ops->delete(rule); if (rule->tun_id) ip_tunnel_unneed_metadata(); diff --git a/net/ipv4/fib_rules.c b/net/ipv4/fib_rules.c index e068a5bace73..51d0ab423ed4 100644 --- a/net/ipv4/fib_rules.c +++ b/net/ipv4/fib_rules.c @@ -349,7 +349,7 @@ static int fib4_rule_configure(struct fib_rule *rule, struct sk_buff *skb, return err; } -static int fib4_rule_delete(struct fib_rule *rule) +static void fib4_rule_delete(struct fib_rule *rule) { struct net *net = rule->fr_net; @@ -361,8 +361,6 @@ static int fib4_rule_delete(struct fib_rule *rule) if (net->ipv4.fib_rules_require_fldissect && fib_rule_requires_fldissect(rule)) net->ipv4.fib_rules_require_fldissect--; - - return 0; } static int fib4_rule_compare(struct fib_rule *rule, struct fib_rule_hdr *frh, diff --git a/net/ipv6/fib6_rules.c b/net/ipv6/fib6_rules.c index e1b2b4fa6e18..5ab4dde07225 100644 --- a/net/ipv6/fib6_rules.c +++ b/net/ipv6/fib6_rules.c @@ -480,15 +480,13 @@ static int fib6_rule_configure(struct fib_rule *rule, struct sk_buff *skb, return err; } -static int fib6_rule_delete(struct fib_rule *rule) +static void fib6_rule_delete(struct fib_rule *rule) { struct net *net = rule->fr_net; if (net->ipv6.fib6_rules_require_fldissect && fib_rule_requires_fldissect(rule)) net->ipv6.fib6_rules_require_fldissect--; - - return 0; } static int fib6_rule_compare(struct fib_rule *rule, struct fib_rule_hdr *frh, From 5cb890ff73573f2924877d9a6b4a298f021a9cc5 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Mon, 29 Jun 2026 18:10:54 +0000 Subject: [PATCH 0070/1433] ipv4: fib_rules: Make the need for fib_unmerge() explicit. IPv4 local and main route tables are merged by default to avoid unnecessary rule lookups. When the first IPv4 rule is created, fib_unmerge() splits the two tables. However, fib4_rule_configure() currently always calls fib_unmerge(), and even fetching a table via fib_get_table() requires RTNL (or RCU). We will drop RTNL from fib_newrule() if not needed. Let's call fib_unmerge() only once for the first rule. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260629181226.1929658-3-kuniyu@google.com Reviewed-by: Ido Schimmel Signed-off-by: Paolo Abeni --- net/ipv4/fib_rules.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/net/ipv4/fib_rules.c b/net/ipv4/fib_rules.c index 51d0ab423ed4..16d202246a36 100644 --- a/net/ipv4/fib_rules.c +++ b/net/ipv4/fib_rules.c @@ -301,10 +301,12 @@ static int fib4_rule_configure(struct fib_rule *rule, struct sk_buff *skb, fib4_nl2rule_dscp_mask(tb[FRA_DSCP_MASK], rule4, extack) < 0) goto errout; - /* split local/main if they are not already split */ - err = fib_unmerge(net); - if (err) - goto errout; + if (!net->ipv4.fib_has_custom_rules) { + /* split local/main if they are not already split */ + err = fib_unmerge(net); + if (err) + goto errout; + } if (rule->table == RT_TABLE_UNSPEC && !rule->l3mdev) { if (rule->action == FR_ACT_TO_TBL) { From 4b8f5c974d14dc955b4252c02d5e4f185ddc9b23 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Mon, 29 Jun 2026 18:10:55 +0000 Subject: [PATCH 0071/1433] ipv4: fib: Protect fib_new_table() with spinlock. fib_newrule() will drop RTNL except for the first IPv4 rule. Then, fib4_rule_configure() could call fib_empty_table() and create a new IPv4 fib_table without RTNL. Currently, net->ipv4.fib_table_hash[] is only protected by RTNL. As a prep, let's protect net->ipv4.fib_table_hash[] with a dedicated spinlock. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260629181226.1929658-4-kuniyu@google.com Reviewed-by: Ido Schimmel Signed-off-by: Paolo Abeni --- include/net/netns/ipv4.h | 1 + net/ipv4/fib_frontend.c | 25 +++++++++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/include/net/netns/ipv4.h b/include/net/netns/ipv4.h index 6e27c56514df..59506320558a 100644 --- a/include/net/netns/ipv4.h +++ b/include/net/netns/ipv4.h @@ -127,6 +127,7 @@ struct netns_ipv4 { atomic_t fib_num_tclassid_users; #endif struct hlist_head *fib_table_hash; + spinlock_t fib_table_hash_lock; struct sock *fibnl; struct hlist_head *fib_info_hash; unsigned int fib_info_hash_bits; diff --git a/net/ipv4/fib_frontend.c b/net/ipv4/fib_frontend.c index 42212970d735..336d70649eb9 100644 --- a/net/ipv4/fib_frontend.c +++ b/net/ipv4/fib_frontend.c @@ -76,7 +76,7 @@ static int __net_init fib4_rules_init(struct net *net) struct fib_table *fib_new_table(struct net *net, u32 id) { - struct fib_table *tb, *alias = NULL; + struct fib_table *tb, *new_tb, *alias = NULL; unsigned int h; if (id == 0) @@ -85,14 +85,27 @@ struct fib_table *fib_new_table(struct net *net, u32 id) if (tb) return tb; + if (!check_net(net)) + return NULL; + if (id == RT_TABLE_LOCAL && !net->ipv4.fib_has_custom_rules) alias = fib_new_table(net, RT_TABLE_MAIN); - if (check_net(net)) - tb = fib_trie_table(id, alias); - if (!tb) + new_tb = fib_trie_table(id, alias); + if (!new_tb) return NULL; + spin_lock(&net->ipv4.fib_table_hash_lock); + + tb = fib_get_table(net, id); + if (tb) { + spin_unlock(&net->ipv4.fib_table_hash_lock); + fib_free_table(new_tb); + return tb; + } + + tb = new_tb; + switch (id) { case RT_TABLE_MAIN: rcu_assign_pointer(net->ipv4.fib_main, tb); @@ -106,6 +119,9 @@ struct fib_table *fib_new_table(struct net *net, u32 id) h = id & (FIB_TABLE_HASHSZ - 1); hlist_add_head_rcu(&tb->tb_hlist, &net->ipv4.fib_table_hash[h]); + + spin_unlock(&net->ipv4.fib_table_hash_lock); + return tb; } EXPORT_SYMBOL_GPL(fib_new_table); @@ -1565,6 +1581,7 @@ static int __net_init ip_fib_net_init(struct net *net) net->ipv4.sysctl_fib_multipath_hash_fields = FIB_MULTIPATH_HASH_FIELD_DEFAULT_MASK; #endif + spin_lock_init(&net->ipv4.fib_table_hash_lock); /* Avoid false sharing : Use at least a full cache line */ size = max_t(size_t, size, L1_CACHE_BYTES); From 763a9437101b9f6210bcfbfd72ce51eb90b7a56e Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Mon, 29 Jun 2026 18:10:56 +0000 Subject: [PATCH 0072/1433] ipv4: fib: Drop RTNL annotation for net->ipv4.fib_table_hash[]. fib_newrule() will drop RTNL except for the first IPv4 rule. net->ipv4.fib_table_hash[] will be read with no protection, but this is fine because fib_table is not destroyed until netns dismantle except for the merged main/local table. fib_unmerge() will continue to be called under RTNL, so other readers (fib_flush() and fib_info_notify_update()) just have to care about the concurrent hlist_add(). IPv6 and IPMR/IP6MR also take this strategy and use RCU helpers to avoid data race against concurrent hlist_add(). Let's not use lockdep_rtnl_is_held() and rcu_dereference_rtnl() for net->ipv4.fib_table_hash[]. Note that commit a7e53531234d ("fib_trie: Make fib_table rcu safe") started to use the _safe version in fib_flush(), but it is not needed thanks to RTNL. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260629181226.1929658-5-kuniyu@google.com Reviewed-by: Ido Schimmel Signed-off-by: Paolo Abeni --- include/net/ip_fib.h | 3 ++- net/ipv4/fib_frontend.c | 23 +++++++++++++---------- net/ipv4/fib_trie.c | 3 +-- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/include/net/ip_fib.h b/include/net/ip_fib.h index c63a3c4967ae..0a35355fb0f3 100644 --- a/include/net/ip_fib.h +++ b/include/net/ip_fib.h @@ -302,7 +302,8 @@ static inline struct fib_table *fib_get_table(struct net *net, u32 id) &net->ipv4.fib_table_hash[TABLE_LOCAL_INDEX] : &net->ipv4.fib_table_hash[TABLE_MAIN_INDEX]; - tb_hlist = rcu_dereference_rtnl(hlist_first_rcu(ptr)); + /* Only fib4_rules_init() adds fib_table. */ + tb_hlist = rcu_dereference_protected(hlist_first_rcu(ptr), true); return hlist_entry(tb_hlist, struct fib_table, tb_hlist); } diff --git a/net/ipv4/fib_frontend.c b/net/ipv4/fib_frontend.c index 336d70649eb9..54eb72695093 100644 --- a/net/ipv4/fib_frontend.c +++ b/net/ipv4/fib_frontend.c @@ -126,24 +126,28 @@ struct fib_table *fib_new_table(struct net *net, u32 id) } EXPORT_SYMBOL_GPL(fib_new_table); -/* caller must hold either rtnl or rcu read lock */ struct fib_table *fib_get_table(struct net *net, u32 id) { - struct fib_table *tb; + struct fib_table *tb = NULL; struct hlist_head *head; unsigned int h; if (id == 0) id = RT_TABLE_MAIN; h = id & (FIB_TABLE_HASHSZ - 1); - head = &net->ipv4.fib_table_hash[h]; - hlist_for_each_entry_rcu(tb, head, tb_hlist, - lockdep_rtnl_is_held()) { + + /* fib_table is not destroyed until ip_fib_net_exit() + * except for the merged main/local table. + * fib_unmerge() is called under RTNL, so other readers + * under RTNL (e.g. fib_flush(), fib_info_notify_update()) + * can safely traverse the list with rcu_dereference_raw(). + */ + hlist_for_each_entry_rcu(tb, head, tb_hlist, true) if (tb->tb_id == id) - return tb; - } - return NULL; + break; + + return tb; } #endif /* CONFIG_IP_MULTIPLE_TABLES */ @@ -206,10 +210,9 @@ void fib_flush(struct net *net) for (h = 0; h < FIB_TABLE_HASHSZ; h++) { struct hlist_head *head = &net->ipv4.fib_table_hash[h]; - struct hlist_node *tmp; struct fib_table *tb; - hlist_for_each_entry_safe(tb, tmp, head, tb_hlist) + hlist_for_each_entry_rcu(tb, head, tb_hlist, true) flushed += fib_table_flush(net, tb, false); } diff --git a/net/ipv4/fib_trie.c b/net/ipv4/fib_trie.c index e11dc86ceda0..d1d342d7148e 100644 --- a/net/ipv4/fib_trie.c +++ b/net/ipv4/fib_trie.c @@ -2137,8 +2137,7 @@ void fib_info_notify_update(struct net *net, struct nl_info *info) struct hlist_head *head = &net->ipv4.fib_table_hash[h]; struct fib_table *tb; - hlist_for_each_entry_rcu(tb, head, tb_hlist, - lockdep_rtnl_is_held()) + hlist_for_each_entry_rcu(tb, head, tb_hlist, true) __fib_info_notify_update(net, tb, info); } } From 8e133ba99cd83e70495554f5e51b8062ffe5ba6b Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Mon, 29 Jun 2026 18:10:57 +0000 Subject: [PATCH 0073/1433] net: fib_rules: Add fib_rules_ops.lock. We will no longer hold RTNL for RTM_NEWRULE and RMT_DELRULE except for the first IPv4 RTM_NEWRULE. Let's add per-fib_rules_ops mutex inside RTNL. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260629181226.1929658-6-kuniyu@google.com Reviewed-by: Ido Schimmel Signed-off-by: Paolo Abeni --- include/net/fib_rules.h | 1 + net/core/fib_rules.c | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/include/net/fib_rules.h b/include/net/fib_rules.h index f9a4bca51eda..7636ef4da5ad 100644 --- a/include/net/fib_rules.h +++ b/include/net/fib_rules.h @@ -98,6 +98,7 @@ struct fib_rules_ops { struct list_head rules_list; struct module *owner; struct net *fro_net; + struct mutex lock; struct rcu_head rcu; }; diff --git a/net/core/fib_rules.c b/net/core/fib_rules.c index 961eb709f256..8b9dac1bd4a7 100644 --- a/net/core/fib_rules.c +++ b/net/core/fib_rules.c @@ -172,6 +172,7 @@ fib_rules_register(const struct fib_rules_ops *tmpl, struct net *net) return ERR_PTR(-ENOMEM); INIT_LIST_HEAD(&ops->rules_list); + mutex_init(&ops->lock); ops->fro_net = net; err = __fib_rules_register(ops); @@ -392,6 +393,7 @@ static int call_fib_rule_notifiers(struct net *net, }; ASSERT_RTNL_NET(net); + lockdep_assert_held(&ops->lock); /* Paired with READ_ONCE() in fib_rules_seq() */ WRITE_ONCE(ops->fib_rules_seq, ops->fib_rules_seq + 1); @@ -910,6 +912,7 @@ int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, if (!rtnl_held) rtnl_net_lock(net); + mutex_lock(&ops->lock); err = fib_nl2rule_rtnl(rule, ops, tb, extack); if (err) @@ -978,6 +981,7 @@ int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, fib_rule_get(rule); + mutex_unlock(&ops->lock); if (!rtnl_held) rtnl_net_unlock(net); @@ -988,6 +992,7 @@ int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, return 0; errout_free: + mutex_unlock(&ops->lock); if (!rtnl_held) rtnl_net_unlock(net); kfree(rule); @@ -1039,6 +1044,7 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, if (!rtnl_held) rtnl_net_lock(net); + mutex_lock(&ops->lock); err = fib_nl2rule_rtnl(nlrule, ops, tb, extack); if (err) @@ -1093,6 +1099,7 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, call_fib_rule_notifiers(net, FIB_EVENT_RULE_DEL, rule, ops, NULL); + mutex_unlock(&ops->lock); if (!rtnl_held) rtnl_net_unlock(net); @@ -1104,6 +1111,7 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, return 0; errout_free: + mutex_unlock(&ops->lock); if (!rtnl_held) rtnl_net_unlock(net); kfree(nlrule); @@ -1403,20 +1411,28 @@ static int fib_rules_event(struct notifier_block *this, unsigned long event, switch (event) { case NETDEV_REGISTER: - list_for_each_entry(ops, &net->rules_ops, list) + list_for_each_entry(ops, &net->rules_ops, list) { + mutex_lock(&ops->lock); attach_rules(&ops->rules_list, dev); + mutex_unlock(&ops->lock); + } break; case NETDEV_CHANGENAME: list_for_each_entry(ops, &net->rules_ops, list) { + mutex_lock(&ops->lock); detach_rules(&ops->rules_list, dev); attach_rules(&ops->rules_list, dev); + mutex_unlock(&ops->lock); } break; case NETDEV_UNREGISTER: - list_for_each_entry(ops, &net->rules_ops, list) + list_for_each_entry(ops, &net->rules_ops, list) { + mutex_lock(&ops->lock); detach_rules(&ops->rules_list, dev); + mutex_unlock(&ops->lock); + } break; } From a7e87ee40980b66c57e4527b20c39bbb794068de Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Mon, 29 Jun 2026 18:10:58 +0000 Subject: [PATCH 0074/1433] net: fib_rules: Remove unnecessary EXPORT_SYMBOL. All fib_rule users cannot be compiled as module. $ grep -E "config (INET|IPV6|IP_MROUTE|IPV6_MROUTE)\b" -A1 \ net/{Kconfig,{ipv4,ipv6}/Kconfig} net/Kconfig:config INET net/Kconfig- bool "TCP/IP networking" -- net/ipv4/Kconfig:config IP_MROUTE net/ipv4/Kconfig- bool "IP: multicast routing" -- net/ipv6/Kconfig:menuconfig IPV6 net/ipv6/Kconfig- bool "The IPv6 protocol" -- net/ipv6/Kconfig:config IPV6_MROUTE net/ipv6/Kconfig- bool "IPv6: multicast routing" Let's remove EXPORT_SYMBOL and friends for fib_rule. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260629181226.1929658-7-kuniyu@google.com Reviewed-by: Ido Schimmel Signed-off-by: Paolo Abeni --- net/core/fib_rules.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/net/core/fib_rules.c b/net/core/fib_rules.c index 8b9dac1bd4a7..25a3fd997782 100644 --- a/net/core/fib_rules.c +++ b/net/core/fib_rules.c @@ -51,7 +51,6 @@ bool fib_rule_matchall(const struct fib_rule *rule) return false; return true; } -EXPORT_SYMBOL_GPL(fib_rule_matchall); int fib_default_rule_add(struct fib_rules_ops *ops, u32 pref, u32 table) @@ -78,7 +77,6 @@ int fib_default_rule_add(struct fib_rules_ops *ops, list_add_tail(&r->list, &ops->rules_list); return 0; } -EXPORT_SYMBOL(fib_default_rule_add); static u32 fib_default_rule_pref(struct fib_rules_ops *ops) { @@ -183,7 +181,6 @@ fib_rules_register(const struct fib_rules_ops *tmpl, struct net *net) return ops; } -EXPORT_SYMBOL_GPL(fib_rules_register); static void fib_rules_cleanup_ops(struct fib_rules_ops *ops) { @@ -208,7 +205,6 @@ void fib_rules_unregister(struct fib_rules_ops *ops) fib_rules_cleanup_ops(ops); kfree_rcu(ops, rcu); } -EXPORT_SYMBOL_GPL(fib_rules_unregister); static int uid_range_set(struct fib_kuid_range *range) { @@ -364,7 +360,6 @@ int fib_rules_lookup(struct fib_rules_ops *ops, struct flowi *fl, return err; } -EXPORT_SYMBOL_GPL(fib_rules_lookup); static int call_fib_rule_notifier(struct notifier_block *nb, enum fib_event_type event_type, @@ -425,7 +420,6 @@ int fib_rules_dump(struct net *net, struct notifier_block *nb, int family, return err; } -EXPORT_SYMBOL_GPL(fib_rules_dump); unsigned int fib_rules_seq_read(const struct net *net, int family) { @@ -441,7 +435,6 @@ unsigned int fib_rules_seq_read(const struct net *net, int family) return fib_rules_seq; } -EXPORT_SYMBOL_GPL(fib_rules_seq_read); static struct fib_rule *rule_find(struct fib_rules_ops *ops, struct fib_rule_hdr *frh, From facce49f29ccec1cf7fd916331c9a25c9ad75869 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Mon, 29 Jun 2026 18:10:59 +0000 Subject: [PATCH 0075/1433] net: fib_rules: Drop RTNL assertions. Now, fib_rule structs are protected by per-fib_rules_ops mutex. Let's drop ASSERT_RTNL_NET() and rtnl_dereference(). Note that fib_rules_event() iterates over net->rules_ops without net->rules_mod_lock, but this is fine because all fib_rule users are built-in and concurrent fib_rules_unregister() does not happen. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260629181226.1929658-8-kuniyu@google.com Reviewed-by: Ido Schimmel Signed-off-by: Paolo Abeni --- net/core/fib_rules.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/net/core/fib_rules.c b/net/core/fib_rules.c index 25a3fd997782..5eef5d6ace82 100644 --- a/net/core/fib_rules.c +++ b/net/core/fib_rules.c @@ -387,7 +387,6 @@ static int call_fib_rule_notifiers(struct net *net, .rule = rule, }; - ASSERT_RTNL_NET(net); lockdep_assert_held(&ops->lock); /* Paired with READ_ONCE() in fib_rules_seq() */ @@ -955,7 +954,7 @@ int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, list_for_each_entry(r, &ops->rules_list, list) { if (r->action == FR_ACT_GOTO && r->target == rule->pref && - rtnl_dereference(r->ctarget) == NULL) { + !rcu_access_pointer(r->ctarget)) { rcu_assign_pointer(r->ctarget, rule); if (--ops->unresolved_rules == 0) break; @@ -1064,7 +1063,7 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, if (rule->action == FR_ACT_GOTO) { ops->nr_goto_rules--; - if (rtnl_dereference(rule->ctarget) == NULL) + if (!rcu_access_pointer(rule->ctarget)) ops->unresolved_rules--; } @@ -1082,7 +1081,7 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, if (&n->list == &ops->rules_list || n->pref != rule->pref) n = NULL; list_for_each_entry(r, &ops->rules_list, list) { - if (rtnl_dereference(r->ctarget) != rule) + if (rcu_access_pointer(r->ctarget) != rule) continue; rcu_assign_pointer(r->ctarget, n); if (!n) @@ -1400,8 +1399,6 @@ static int fib_rules_event(struct notifier_block *this, unsigned long event, struct net *net = dev_net(dev); struct fib_rules_ops *ops; - ASSERT_RTNL(); - switch (event) { case NETDEV_REGISTER: list_for_each_entry(ops, &net->rules_ops, list) { From 34ea2499389e7a2f1522682e003b257eccda26be Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Mon, 29 Jun 2026 18:11:00 +0000 Subject: [PATCH 0076/1433] net: fib_rules: Use dev_get_by_name_rcu(). We will no longer hold RTNL for RTM_NEWRULE and RMT_DELRULE except for the first IPv4 RTM_NEWRULE. Let's covnert __dev_get_by_name() in fib_nl2rule_rtnl() to dev_get_by_name_rcu() and rename it to fib_nl2rule_locked(). Note that dev_get_by_name_rcu() must be called inside ops->lock to serialise fib_rules_event() by __dev_change_net_namespace(). Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260629181226.1929658-9-kuniyu@google.com Reviewed-by: Ido Schimmel Signed-off-by: Paolo Abeni --- net/core/fib_rules.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/net/core/fib_rules.c b/net/core/fib_rules.c index 5eef5d6ace82..2b652dd83241 100644 --- a/net/core/fib_rules.c +++ b/net/core/fib_rules.c @@ -734,10 +734,10 @@ static int fib_nl2rule(struct net *net, struct nlmsghdr *nlh, return err; } -static int fib_nl2rule_rtnl(struct fib_rule *nlrule, - struct fib_rules_ops *ops, - struct nlattr *tb[], - struct netlink_ext_ack *extack) +static int fib_nl2rule_locked(struct fib_rule *nlrule, + struct fib_rules_ops *ops, + struct nlattr *tb[], + struct netlink_ext_ack *extack) { if (!tb[FRA_PRIORITY]) nlrule->pref = fib_default_rule_pref(ops); @@ -748,12 +748,14 @@ static int fib_nl2rule_rtnl(struct fib_rule *nlrule, return -EINVAL; } + rcu_read_lock(); + if (tb[FRA_IIFNAME]) { struct net_device *dev; - dev = __dev_get_by_name(nlrule->fr_net, nlrule->iifname); + dev = dev_get_by_name_rcu(nlrule->fr_net, nlrule->iifname); if (dev) { - nlrule->iifindex = dev->ifindex; + nlrule->iifindex = READ_ONCE(dev->ifindex); nlrule->iif_is_l3_master = netif_is_l3_master(dev); } } @@ -761,13 +763,15 @@ static int fib_nl2rule_rtnl(struct fib_rule *nlrule, if (tb[FRA_OIFNAME]) { struct net_device *dev; - dev = __dev_get_by_name(nlrule->fr_net, nlrule->oifname); + dev = dev_get_by_name_rcu(nlrule->fr_net, nlrule->oifname); if (dev) { - nlrule->oifindex = dev->ifindex; + nlrule->oifindex = READ_ONCE(dev->ifindex); nlrule->oif_is_l3_master = netif_is_l3_master(dev); } } + rcu_read_unlock(); + return 0; } @@ -906,7 +910,7 @@ int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, rtnl_net_lock(net); mutex_lock(&ops->lock); - err = fib_nl2rule_rtnl(rule, ops, tb, extack); + err = fib_nl2rule_locked(rule, ops, tb, extack); if (err) goto errout_free; @@ -1038,7 +1042,7 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, rtnl_net_lock(net); mutex_lock(&ops->lock); - err = fib_nl2rule_rtnl(nlrule, ops, tb, extack); + err = fib_nl2rule_locked(nlrule, ops, tb, extack); if (err) goto errout_free; From eef9bddc3313b01679c60892825afd2a7a83fba6 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Mon, 29 Jun 2026 18:11:01 +0000 Subject: [PATCH 0077/1433] net: fib_rules: Only hold RTNL for the first IPv4 RTM_NEWRULE. Now, RTM_DELRULE no longer needs RTNL, and the only RTNL dependant in RTM_NEWRULE is fib_unmerge(), which is called for the first IPv4 rule. Let's add fib_rules_ops.need_rtnl() and hold RTNL only for the first IPv4 rule. Tested: The script below creates 1K rules in parallel in 4K netns, and it got 20x/30x faster for IPv4/IPv6. #!/bin/bash N=4096 F=rules.txt for i in $(seq $N); do ip netns add ns-$i; done printf 'rule add from all table %d\n' {1..1024} > $F for v in 4 6; do echo "=== IPv${v} ===" time { for i in $(seq $N); do nsenter \ --net=/var/run/netns/ns-$i ip -$v -batch $F & done; wait; } done for i in $(seq $N); do ip netns del ns-$i; done rm -f $F Without this series: # ./test.sh === IPv4 === real 0m22.752s user 0m7.834s sys 92m46.721s === IPv6 === real 0m35.181s user 0m8.635s sys 142m30.479s With this series: # ./test.sh === IPv4 === real 0m0.918s user 0m5.675s sys 2m7.024s === IPv6 === real 0m1.214s user 0m7.917s sys 4m19.489s Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260629181226.1929658-10-kuniyu@google.com Reviewed-by: Ido Schimmel Signed-off-by: Paolo Abeni --- include/net/fib_rules.h | 1 + net/core/fib_rules.c | 15 ++++++--------- net/ipv4/fib_rules.c | 6 ++++++ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/include/net/fib_rules.h b/include/net/fib_rules.h index 7636ef4da5ad..c6b94790fa81 100644 --- a/include/net/fib_rules.h +++ b/include/net/fib_rules.h @@ -93,6 +93,7 @@ struct fib_rules_ops { /* Called after modifications to the rules set, must flush * the route cache if one exists. */ void (*flush_cache)(struct fib_rules_ops *ops); + bool (*need_rtnl)(struct net *net); int nlgroup; struct list_head rules_list; diff --git a/net/core/fib_rules.c b/net/core/fib_rules.c index 2b652dd83241..22e5e5e1a9c4 100644 --- a/net/core/fib_rules.c +++ b/net/core/fib_rules.c @@ -881,6 +881,7 @@ int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, struct nlattr *tb[FRA_MAX + 1]; bool user_priority = false; struct fib_rule_hdr *frh; + bool unlock_rtnl = false; frh = nlmsg_payload(nlh, sizeof(*frh)); if (!frh) { @@ -906,8 +907,10 @@ int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, if (err) goto errout; - if (!rtnl_held) + if (!rtnl_held && ops->need_rtnl && ops->need_rtnl(net)) { + unlock_rtnl = true; rtnl_net_lock(net); + } mutex_lock(&ops->lock); err = fib_nl2rule_locked(rule, ops, tb, extack); @@ -978,7 +981,7 @@ int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, fib_rule_get(rule); mutex_unlock(&ops->lock); - if (!rtnl_held) + if (unlock_rtnl) rtnl_net_unlock(net); notify_rule_change(RTM_NEWRULE, rule, ops, nlh, NETLINK_CB(skb).portid); @@ -989,7 +992,7 @@ int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, errout_free: mutex_unlock(&ops->lock); - if (!rtnl_held) + if (unlock_rtnl) rtnl_net_unlock(net); kfree(rule); errout: @@ -1038,8 +1041,6 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, if (err) goto errout; - if (!rtnl_held) - rtnl_net_lock(net); mutex_lock(&ops->lock); err = fib_nl2rule_locked(nlrule, ops, tb, extack); @@ -1096,8 +1097,6 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, call_fib_rule_notifiers(net, FIB_EVENT_RULE_DEL, rule, ops, NULL); mutex_unlock(&ops->lock); - if (!rtnl_held) - rtnl_net_unlock(net); notify_rule_change(RTM_DELRULE, rule, ops, nlh, NETLINK_CB(skb).portid); fib_rule_put(rule); @@ -1108,8 +1107,6 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, errout_free: mutex_unlock(&ops->lock); - if (!rtnl_held) - rtnl_net_unlock(net); kfree(nlrule); errout: rules_ops_put(ops); diff --git a/net/ipv4/fib_rules.c b/net/ipv4/fib_rules.c index 16d202246a36..4edb0dca7be8 100644 --- a/net/ipv4/fib_rules.c +++ b/net/ipv4/fib_rules.c @@ -460,6 +460,11 @@ static void fib4_rule_flush_cache(struct fib_rules_ops *ops) rt_cache_flush(ops->fro_net); } +static bool fib4_rule_need_rtnl(struct net *net) +{ + return !net->ipv4.fib_has_custom_rules; +} + static const struct fib_rules_ops __net_initconst fib4_rules_ops_template = { .family = AF_INET, .rule_size = sizeof(struct fib4_rule), @@ -473,6 +478,7 @@ static const struct fib_rules_ops __net_initconst fib4_rules_ops_template = { .fill = fib4_rule_fill, .nlmsg_payload = fib4_rule_nlmsg_payload, .flush_cache = fib4_rule_flush_cache, + .need_rtnl = fib4_rule_need_rtnl, .nlgroup = RTNLGRP_IPV4_RULE, .owner = THIS_MODULE, }; From ffc8a4b9ad2bdee41a207aaf546eef6ee3f19a2c Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Mon, 29 Jun 2026 18:11:02 +0000 Subject: [PATCH 0078/1433] ipv6: fib_rules: Convert fib6_rules_net_exit_rtnl() to ->exit(). Now fib_rule is protected by per-ops mutex. fib6_rules_net_exit_batch() no longer needs RTNL. Let's convert it to ->exit() and drop RTNL. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260629181226.1929658-11-kuniyu@google.com Reviewed-by: Ido Schimmel Signed-off-by: Paolo Abeni --- net/ipv6/fib6_rules.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/net/ipv6/fib6_rules.c b/net/ipv6/fib6_rules.c index 5ab4dde07225..04dab9329d0c 100644 --- a/net/ipv6/fib6_rules.c +++ b/net/ipv6/fib6_rules.c @@ -635,21 +635,14 @@ static int __net_init fib6_rules_net_init(struct net *net) goto out; } -static void __net_exit fib6_rules_net_exit_batch(struct list_head *net_list) +static void __net_exit fib6_rules_net_exit(struct net *net) { - struct net *net; - - rtnl_lock(); - list_for_each_entry(net, net_list, exit_list) { - fib_rules_unregister(net->ipv6.fib6_rules_ops); - cond_resched(); - } - rtnl_unlock(); + fib_rules_unregister(net->ipv6.fib6_rules_ops); } static struct pernet_operations fib6_rules_net_ops = { .init = fib6_rules_net_init, - .exit_batch = fib6_rules_net_exit_batch, + .exit = fib6_rules_net_exit, }; int __init fib6_rules_init(void) From 66731a51b1fbf5be58cc8bea0a1324e416386b33 Mon Sep 17 00:00:00 2001 From: Kohei Enju Date: Sat, 31 Jan 2026 16:29:36 +0000 Subject: [PATCH 0079/1433] igc: prepare for RSS key get/set support Store the RSS key inside struct igc_adapter and introduce the igc_write_rss_key() helper function. This allows the driver to program the RSSRK registers using a persistent RSS key, instead of using a stack-local buffer in igc_setup_mrqc(). This is a preparation patch for adding RSS key get/set support in subsequent changes, and no functional change is intended in this patch. Signed-off-by: Kohei Enju Reviewed-by: Aleksandr Loktionov Reviewed-by: Simon Horman Tested-by: Avigail Dahan Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/igc/igc.h | 3 +++ drivers/net/ethernet/intel/igc/igc_ethtool.c | 20 ++++++++++++++++++++ drivers/net/ethernet/intel/igc/igc_main.c | 8 ++++---- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/intel/igc/igc.h b/drivers/net/ethernet/intel/igc/igc.h index 46d625b15f44..17f213cc93e4 100644 --- a/drivers/net/ethernet/intel/igc/igc.h +++ b/drivers/net/ethernet/intel/igc/igc.h @@ -30,6 +30,7 @@ void igc_ethtool_set_ops(struct net_device *); #define MAX_ETYPE_FILTER 8 #define IGC_RETA_SIZE 128 +#define IGC_RSS_KEY_SIZE 40 /* SDP support */ #define IGC_N_EXTTS 2 @@ -302,6 +303,7 @@ struct igc_adapter { unsigned int nfc_rule_count; u8 rss_indir_tbl[IGC_RETA_SIZE]; + u8 rss_key[IGC_RSS_KEY_SIZE]; unsigned long link_check_timeout; struct igc_info ei; @@ -361,6 +363,7 @@ unsigned int igc_get_max_rss_queues(struct igc_adapter *adapter); void igc_set_flag_queue_pairs(struct igc_adapter *adapter, const u32 max_rss_queues); int igc_reinit_queues(struct igc_adapter *adapter); +void igc_write_rss_key(struct igc_adapter *adapter); void igc_write_rss_indir_tbl(struct igc_adapter *adapter); bool igc_has_link(struct igc_adapter *adapter); void igc_reset(struct igc_adapter *adapter); diff --git a/drivers/net/ethernet/intel/igc/igc_ethtool.c b/drivers/net/ethernet/intel/igc/igc_ethtool.c index 0122009bedd0..f01222f12776 100644 --- a/drivers/net/ethernet/intel/igc/igc_ethtool.c +++ b/drivers/net/ethernet/intel/igc/igc_ethtool.c @@ -1460,6 +1460,26 @@ static int igc_ethtool_set_rxnfc(struct net_device *dev, } } +/** + * igc_write_rss_key - Program the RSS key into device registers + * @adapter: board private structure + * + * Write the RSS key stored in adapter->rss_key to the IGC_RSSRK registers. + * Each 32-bit chunk of the key is read using get_unaligned_le32() and written + * to the appropriate register. + */ +void igc_write_rss_key(struct igc_adapter *adapter) +{ + struct igc_hw *hw = &adapter->hw; + u32 val; + int i; + + for (i = 0; i < IGC_RSS_KEY_SIZE / 4; i++) { + val = get_unaligned_le32(&adapter->rss_key[i * 4]); + wr32(IGC_RSSRK(i), val); + } +} + void igc_write_rss_indir_tbl(struct igc_adapter *adapter) { struct igc_hw *hw = &adapter->hw; diff --git a/drivers/net/ethernet/intel/igc/igc_main.c b/drivers/net/ethernet/intel/igc/igc_main.c index 2c9e2dfd8499..5ef229a5931f 100644 --- a/drivers/net/ethernet/intel/igc/igc_main.c +++ b/drivers/net/ethernet/intel/igc/igc_main.c @@ -785,11 +785,8 @@ static void igc_setup_mrqc(struct igc_adapter *adapter) struct igc_hw *hw = &adapter->hw; u32 j, num_rx_queues; u32 mrqc, rxcsum; - u32 rss_key[10]; - netdev_rss_key_fill(rss_key, sizeof(rss_key)); - for (j = 0; j < 10; j++) - wr32(IGC_RSSRK(j), rss_key[j]); + igc_write_rss_key(adapter); num_rx_queues = adapter->rss_queues; @@ -5048,6 +5045,9 @@ static int igc_sw_init(struct igc_adapter *adapter) pci_read_config_word(pdev, PCI_COMMAND, &hw->bus.pci_cmd_word); + /* init RSS key */ + netdev_rss_key_fill(adapter->rss_key, sizeof(adapter->rss_key)); + /* set default ring sizes */ adapter->tx_ring_count = IGC_DEFAULT_TXD; adapter->rx_ring_count = IGC_DEFAULT_RXD; From f243be8edeabac0b2ab3ccf27741a17c8129e253 Mon Sep 17 00:00:00 2001 From: Kohei Enju Date: Sat, 31 Jan 2026 16:29:37 +0000 Subject: [PATCH 0080/1433] igc: expose RSS key via ethtool get_rxfh Implement igc_ethtool_get_rxfh_key_size() and extend igc_ethtool_get_rxfh() to return the RSS key to userspace. This can be tested using `ethtool -x `. Signed-off-by: Kohei Enju Tested-by: Avigail Dahan Reviewed-by: Vitaly Lifshits Reviewed-by: Simon Horman Reviewed-by: Aleksandr Loktionov Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/igc/igc_ethtool.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/intel/igc/igc_ethtool.c b/drivers/net/ethernet/intel/igc/igc_ethtool.c index f01222f12776..0e76ffe7be65 100644 --- a/drivers/net/ethernet/intel/igc/igc_ethtool.c +++ b/drivers/net/ethernet/intel/igc/igc_ethtool.c @@ -1502,6 +1502,11 @@ void igc_write_rss_indir_tbl(struct igc_adapter *adapter) } } +static u32 igc_ethtool_get_rxfh_key_size(struct net_device *netdev) +{ + return IGC_RSS_KEY_SIZE; +} + static u32 igc_ethtool_get_rxfh_indir_size(struct net_device *netdev) { return IGC_RETA_SIZE; @@ -1514,10 +1519,13 @@ static int igc_ethtool_get_rxfh(struct net_device *netdev, int i; rxfh->hfunc = ETH_RSS_HASH_TOP; - if (!rxfh->indir) - return 0; - for (i = 0; i < IGC_RETA_SIZE; i++) - rxfh->indir[i] = adapter->rss_indir_tbl[i]; + + if (rxfh->indir) + for (i = 0; i < IGC_RETA_SIZE; i++) + rxfh->indir[i] = adapter->rss_indir_tbl[i]; + + if (rxfh->key) + memcpy(rxfh->key, adapter->rss_key, sizeof(adapter->rss_key)); return 0; } @@ -2195,6 +2203,7 @@ static const struct ethtool_ops igc_ethtool_ops = { .get_rxnfc = igc_ethtool_get_rxnfc, .set_rxnfc = igc_ethtool_set_rxnfc, .get_rx_ring_count = igc_ethtool_get_rx_ring_count, + .get_rxfh_key_size = igc_ethtool_get_rxfh_key_size, .get_rxfh_indir_size = igc_ethtool_get_rxfh_indir_size, .get_rxfh = igc_ethtool_get_rxfh, .set_rxfh = igc_ethtool_set_rxfh, From 3fc4c1ee5f843255fd884dabacdddb045b3db9e2 Mon Sep 17 00:00:00 2001 From: Kohei Enju Date: Sat, 31 Jan 2026 16:29:38 +0000 Subject: [PATCH 0081/1433] igc: allow configuring RSS key via ethtool set_rxfh Change igc_ethtool_set_rxfh() to accept and save a userspace-provided RSS key. When a key is provided, store it in the adapter and write the RSSRK registers accordingly. This can be tested using `ethtool -X hkey `. Signed-off-by: Kohei Enju Reviewed-by: Simon Horman Tested-by: Avigail Dahan Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/igc/igc_ethtool.c | 30 +++++++++++--------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/drivers/net/ethernet/intel/igc/igc_ethtool.c b/drivers/net/ethernet/intel/igc/igc_ethtool.c index 0e76ffe7be65..fbba3e84673a 100644 --- a/drivers/net/ethernet/intel/igc/igc_ethtool.c +++ b/drivers/net/ethernet/intel/igc/igc_ethtool.c @@ -1539,24 +1539,28 @@ static int igc_ethtool_set_rxfh(struct net_device *netdev, int i; /* We do not allow change in unsupported parameters */ - if (rxfh->key || - (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE && - rxfh->hfunc != ETH_RSS_HASH_TOP)) + if (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE && + rxfh->hfunc != ETH_RSS_HASH_TOP) return -EOPNOTSUPP; - if (!rxfh->indir) - return 0; - num_queues = adapter->rss_queues; + if (rxfh->indir) { + num_queues = adapter->rss_queues; - /* Verify user input. */ - for (i = 0; i < IGC_RETA_SIZE; i++) - if (rxfh->indir[i] >= num_queues) - return -EINVAL; + /* Verify user input. */ + for (i = 0; i < IGC_RETA_SIZE; i++) + if (rxfh->indir[i] >= num_queues) + return -EINVAL; - for (i = 0; i < IGC_RETA_SIZE; i++) - adapter->rss_indir_tbl[i] = rxfh->indir[i]; + for (i = 0; i < IGC_RETA_SIZE; i++) + adapter->rss_indir_tbl[i] = rxfh->indir[i]; - igc_write_rss_indir_tbl(adapter); + igc_write_rss_indir_tbl(adapter); + } + + if (rxfh->key) { + memcpy(adapter->rss_key, rxfh->key, sizeof(adapter->rss_key)); + igc_write_rss_key(adapter); + } return 0; } From dfaf57ef99cf8901e4a5fc2e89629be44f922f80 Mon Sep 17 00:00:00 2001 From: Takashi Kozu Date: Tue, 3 Feb 2026 21:54:11 +0900 Subject: [PATCH 0082/1433] igb: prepare for RSS key get/set support Store the RSS key inside struct igb_adapter and introduce the igb_write_rss_key() helper function. This allows the driver to program the E1000 registers using a persistent RSS key, instead of using a stack-local buffer in igb_setup_mrqc(). Reviewed-by: Simon Horman Reviewed-by: Piotr Kwapulinski Reviewed-by: Aleksandr Loktionov Signed-off-by: Takashi Kozu Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/igb/igb.h | 3 +++ drivers/net/ethernet/intel/igb/igb_ethtool.c | 21 ++++++++++++++++++++ drivers/net/ethernet/intel/igb/igb_main.c | 8 ++++---- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/intel/igb/igb.h b/drivers/net/ethernet/intel/igb/igb.h index 0fff1df81b7b..8c9b02058cec 100644 --- a/drivers/net/ethernet/intel/igb/igb.h +++ b/drivers/net/ethernet/intel/igb/igb.h @@ -495,6 +495,7 @@ struct hwmon_buff { #define IGB_N_PEROUT 2 #define IGB_N_SDP 4 #define IGB_RETA_SIZE 128 +#define IGB_RSS_KEY_SIZE 40 enum igb_filter_match_flags { IGB_FILTER_FLAG_ETHER_TYPE = 0x1, @@ -655,6 +656,7 @@ struct igb_adapter { struct i2c_client *i2c_client; u32 rss_indir_tbl_init; u8 rss_indir_tbl[IGB_RETA_SIZE]; + u8 rss_key[IGB_RSS_KEY_SIZE]; unsigned long link_check_timeout; int copper_tries; @@ -735,6 +737,7 @@ void igb_down(struct igb_adapter *); void igb_reinit_locked(struct igb_adapter *); void igb_reset(struct igb_adapter *); int igb_reinit_queues(struct igb_adapter *); +void igb_write_rss_key(struct igb_adapter *adapter); void igb_write_rss_indir_tbl(struct igb_adapter *); int igb_set_spd_dplx(struct igb_adapter *, u32, u8); int igb_setup_tx_resources(struct igb_ring *); diff --git a/drivers/net/ethernet/intel/igb/igb_ethtool.c b/drivers/net/ethernet/intel/igb/igb_ethtool.c index f7938c1da835..9a105e59f432 100644 --- a/drivers/net/ethernet/intel/igb/igb_ethtool.c +++ b/drivers/net/ethernet/intel/igb/igb_ethtool.c @@ -3019,6 +3019,27 @@ static int igb_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd) return ret; } +/** + * igb_write_rss_key - Program the RSS key into device registers + * @adapter: board private structure + * + * Write the RSS key stored in adapter->rss_key to the E1000 hardware registers. + * Each 32-bit chunk of the key is read using get_unaligned_le32() and written + * to the appropriate register. + */ +void igb_write_rss_key(struct igb_adapter *adapter) +{ + struct e1000_hw *hw = &adapter->hw; + + ASSERT_RTNL(); + + for (int i = 0; i < IGB_RSS_KEY_SIZE / 4; i++) { + u32 val = get_unaligned_le32(&adapter->rss_key[i * 4]); + + wr32(E1000_RSSRK(i), val); + } +} + static int igb_get_eee(struct net_device *netdev, struct ethtool_keee *edata) { struct igb_adapter *adapter = netdev_priv(netdev); diff --git a/drivers/net/ethernet/intel/igb/igb_main.c b/drivers/net/ethernet/intel/igb/igb_main.c index a1e89a375744..b7d36dd0b8e4 100644 --- a/drivers/net/ethernet/intel/igb/igb_main.c +++ b/drivers/net/ethernet/intel/igb/igb_main.c @@ -4048,6 +4048,9 @@ static int igb_sw_init(struct igb_adapter *adapter) pci_read_config_word(pdev, PCI_COMMAND, &hw->bus.pci_cmd_word); + /* init RSS key */ + netdev_rss_key_fill(adapter->rss_key, sizeof(adapter->rss_key)); + /* set default ring sizes */ adapter->tx_ring_count = IGB_DEFAULT_TXD; adapter->rx_ring_count = IGB_DEFAULT_RXD; @@ -4522,11 +4525,8 @@ static void igb_setup_mrqc(struct igb_adapter *adapter) struct e1000_hw *hw = &adapter->hw; u32 mrqc, rxcsum; u32 j, num_rx_queues; - u32 rss_key[10]; - netdev_rss_key_fill(rss_key, sizeof(rss_key)); - for (j = 0; j < 10; j++) - wr32(E1000_RSSRK(j), rss_key[j]); + igb_write_rss_key(adapter); num_rx_queues = adapter->rss_queues; From 1ae67b2b28bcd027d9630d316208e88136cd960f Mon Sep 17 00:00:00 2001 From: Takashi Kozu Date: Tue, 3 Feb 2026 21:54:12 +0900 Subject: [PATCH 0083/1433] igb: expose RSS key via ethtool get_rxfh Implement igb_get_rxfh_key_size() and extend igb_get_rxfh() to return the RSS key to userspace. This can be tested using `ethtool -x `. Reviewed-by: Simon Horman Reviewed-by: Aleksandr Loktionov Signed-off-by: Takashi Kozu Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/igb/igb_ethtool.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/intel/igb/igb_ethtool.c b/drivers/net/ethernet/intel/igb/igb_ethtool.c index 9a105e59f432..47fc620026a9 100644 --- a/drivers/net/ethernet/intel/igb/igb_ethtool.c +++ b/drivers/net/ethernet/intel/igb/igb_ethtool.c @@ -3297,10 +3297,12 @@ static int igb_get_rxfh(struct net_device *netdev, int i; rxfh->hfunc = ETH_RSS_HASH_TOP; - if (!rxfh->indir) - return 0; - for (i = 0; i < IGB_RETA_SIZE; i++) - rxfh->indir[i] = adapter->rss_indir_tbl[i]; + if (rxfh->indir) + for (i = 0; i < IGB_RETA_SIZE; i++) + rxfh->indir[i] = adapter->rss_indir_tbl[i]; + + if (rxfh->key) + memcpy(rxfh->key, adapter->rss_key, sizeof(adapter->rss_key)); return 0; } @@ -3340,6 +3342,11 @@ void igb_write_rss_indir_tbl(struct igb_adapter *adapter) } } +static u32 igb_get_rxfh_key_size(struct net_device *netdev) +{ + return IGB_RSS_KEY_SIZE; +} + static int igb_set_rxfh(struct net_device *netdev, struct ethtool_rxfh_param *rxfh, struct netlink_ext_ack *extack) @@ -3504,6 +3511,7 @@ static const struct ethtool_ops igb_ethtool_ops = { .get_module_eeprom = igb_get_module_eeprom, .get_rxfh_indir_size = igb_get_rxfh_indir_size, .get_rxfh = igb_get_rxfh, + .get_rxfh_key_size = igb_get_rxfh_key_size, .set_rxfh = igb_set_rxfh, .get_rxfh_fields = igb_get_rxfh_fields, .set_rxfh_fields = igb_set_rxfh_fields, From e3c94e9782a7076cca3c4ec0fc9578f4f6e0447b Mon Sep 17 00:00:00 2001 From: Takashi Kozu Date: Tue, 3 Feb 2026 21:54:13 +0900 Subject: [PATCH 0084/1433] igb: allow configuring RSS key via ethtool set_rxfh Change igb_set_rxfh() to accept and save a userspace-provided RSS key. When a key is provided, store it in the adapter and write the E1000 registers accordingly. This can be tested using `ethtool -X hkey `. Reviewed-by: Simon Horman Signed-off-by: Takashi Kozu Tested-by: Kohei Enju Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/igb/igb_ethtool.c | 52 +++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/drivers/net/ethernet/intel/igb/igb_ethtool.c b/drivers/net/ethernet/intel/igb/igb_ethtool.c index 47fc620026a9..65014a54a6d1 100644 --- a/drivers/net/ethernet/intel/igb/igb_ethtool.c +++ b/drivers/net/ethernet/intel/igb/igb_ethtool.c @@ -3357,35 +3357,39 @@ static int igb_set_rxfh(struct net_device *netdev, u32 num_queues; /* We do not allow change in unsupported parameters */ - if (rxfh->key || - (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE && - rxfh->hfunc != ETH_RSS_HASH_TOP)) + if (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE && + rxfh->hfunc != ETH_RSS_HASH_TOP) return -EOPNOTSUPP; - if (!rxfh->indir) - return 0; - num_queues = adapter->rss_queues; + if (rxfh->indir) { + num_queues = adapter->rss_queues; - switch (hw->mac.type) { - case e1000_82576: - /* 82576 supports 2 RSS queues for SR-IOV */ - if (adapter->vfs_allocated_count) - num_queues = 2; - break; - default: - break; + switch (hw->mac.type) { + case e1000_82576: + /* 82576 supports 2 RSS queues for SR-IOV */ + if (adapter->vfs_allocated_count) + num_queues = 2; + break; + default: + break; + } + + /* Verify user input. */ + for (i = 0; i < IGB_RETA_SIZE; i++) + if (rxfh->indir[i] >= num_queues) + return -EINVAL; + + + for (i = 0; i < IGB_RETA_SIZE; i++) + adapter->rss_indir_tbl[i] = rxfh->indir[i]; + + igb_write_rss_indir_tbl(adapter); } - /* Verify user input. */ - for (i = 0; i < IGB_RETA_SIZE; i++) - if (rxfh->indir[i] >= num_queues) - return -EINVAL; - - - for (i = 0; i < IGB_RETA_SIZE; i++) - adapter->rss_indir_tbl[i] = rxfh->indir[i]; - - igb_write_rss_indir_tbl(adapter); + if (rxfh->key) { + memcpy(adapter->rss_key, rxfh->key, sizeof(adapter->rss_key)); + igb_write_rss_key(adapter); + } return 0; } From 17cd41a9733da811d118c84ddee9751e3b759352 Mon Sep 17 00:00:00 2001 From: Kohei Enju Date: Thu, 22 Jan 2026 13:47:46 +0000 Subject: [PATCH 0085/1433] igb: set skb hash type from RSS_TYPE igb always marks the RX hash as L3 regardless of RSS_TYPE in the advanced descriptor, which may indicate L4 (TCP/UDP) hash. This can trigger unnecessary SW hash recalculation and breaks toeplitz selftests. Use RSS_TYPE from pkt_info to set the correct PKT_HASH_TYPE_* Tested by toeplitz.py with the igb RSS key get/set patches applied as they are required for toeplitz.py (see Link below). # ethtool -N $DEV rx-flow-hash udp4 sdfn # ethtool -N $DEV rx-flow-hash udp6 sdfn # python toeplitz.py | grep -E "^# Totals" Without patch: # Totals: pass:0 fail:12 xfail:0 xpass:0 skip:0 error:0 With patch: # Totals: pass:12 fail:0 xfail:0 xpass:0 skip:0 error:0 Link: https://lore.kernel.org/intel-wired-lan/20260119084511.95287-5-takkozu@amazon.com/ Signed-off-by: Kohei Enju Reviewed-by: Aleksandr Loktionov Reviewed-by: Paul Menzel Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/igb/e1000_82575.h | 21 ++++++++++++++++++++ drivers/net/ethernet/intel/igb/igb_main.c | 17 ++++++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/intel/igb/e1000_82575.h b/drivers/net/ethernet/intel/igb/e1000_82575.h index 63ec253ac788..9e696d55e512 100644 --- a/drivers/net/ethernet/intel/igb/e1000_82575.h +++ b/drivers/net/ethernet/intel/igb/e1000_82575.h @@ -87,6 +87,27 @@ union e1000_adv_rx_desc { } wb; /* writeback */ }; +#define E1000_RSS_TYPE_NO_HASH 0 +#define E1000_RSS_TYPE_HASH_TCP_IPV4 1 +#define E1000_RSS_TYPE_HASH_IPV4 2 +#define E1000_RSS_TYPE_HASH_TCP_IPV6 3 +#define E1000_RSS_TYPE_HASH_IPV6_EX 4 +#define E1000_RSS_TYPE_HASH_IPV6 5 +#define E1000_RSS_TYPE_HASH_TCP_IPV6_EX 6 +#define E1000_RSS_TYPE_HASH_UDP_IPV4 7 +#define E1000_RSS_TYPE_HASH_UDP_IPV6 8 +#define E1000_RSS_TYPE_HASH_UDP_IPV6_EX 9 + +#define E1000_RSS_TYPE_MASK GENMASK(3, 0) + +#define E1000_RSS_L4_TYPES_MASK \ + (BIT(E1000_RSS_TYPE_HASH_TCP_IPV4) | \ + BIT(E1000_RSS_TYPE_HASH_TCP_IPV6) | \ + BIT(E1000_RSS_TYPE_HASH_TCP_IPV6_EX) | \ + BIT(E1000_RSS_TYPE_HASH_UDP_IPV4) | \ + BIT(E1000_RSS_TYPE_HASH_UDP_IPV6) | \ + BIT(E1000_RSS_TYPE_HASH_UDP_IPV6_EX)) + #define E1000_RXDADV_HDRBUFLEN_MASK 0x7FE0 #define E1000_RXDADV_HDRBUFLEN_SHIFT 5 #define E1000_RXDADV_STAT_TS 0x10000 /* Pkt was time stamped */ diff --git a/drivers/net/ethernet/intel/igb/igb_main.c b/drivers/net/ethernet/intel/igb/igb_main.c index b7d36dd0b8e4..d4a897a8c82c 100644 --- a/drivers/net/ethernet/intel/igb/igb_main.c +++ b/drivers/net/ethernet/intel/igb/igb_main.c @@ -8820,10 +8820,19 @@ static inline void igb_rx_hash(struct igb_ring *ring, union e1000_adv_rx_desc *rx_desc, struct sk_buff *skb) { - if (ring->netdev->features & NETIF_F_RXHASH) - skb_set_hash(skb, - le32_to_cpu(rx_desc->wb.lower.hi_dword.rss), - PKT_HASH_TYPE_L3); + u16 rss_type; + + if (!(ring->netdev->features & NETIF_F_RXHASH)) + return; + + rss_type = le16_to_cpu(rx_desc->wb.lower.lo_dword.pkt_info) & + E1000_RSS_TYPE_MASK; + if (!rss_type) + return; + + skb_set_hash(skb, le32_to_cpu(rx_desc->wb.lower.hi_dword.rss), + (E1000_RSS_L4_TYPES_MASK & BIT(rss_type)) ? + PKT_HASH_TYPE_L4 : PKT_HASH_TYPE_L3); } /** From 1ee93ee2e085e13de2af55b431d4673ab2cdeecb Mon Sep 17 00:00:00 2001 From: Faizal Rahim Date: Fri, 8 May 2026 05:47:03 +0800 Subject: [PATCH 0086/1433] igc: remove unused autoneg_failed field autoneg_failed in struct igc_mac_info is never set in the igc driver. Remove the field and the dead code checking it in igc_config_fc_after_link_up(). The field originates from the e1000/e1000e fiber/serdes forced-link path, where MAC-level autoneg timeout sets it to signal the flow-control code to force pause. igc supports only copper, so it never needs to set this field. Reviewed-by: Looi Hong Aun Reviewed-by: Aleksandr Loktionov Signed-off-by: Faizal Rahim Signed-off-by: Khai Wen Tan Reviewed-by: Dima Ruinskiy Reviewed-by: Piotr Kwapulinski Reviewed-by: Simon Horman Tested-by: Moriya Kadosh Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/igc/igc_hw.h | 1 - drivers/net/ethernet/intel/igc/igc_mac.c | 16 +--------------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/drivers/net/ethernet/intel/igc/igc_hw.h b/drivers/net/ethernet/intel/igc/igc_hw.h index be8a49a86d09..86ab8f566f44 100644 --- a/drivers/net/ethernet/intel/igc/igc_hw.h +++ b/drivers/net/ethernet/intel/igc/igc_hw.h @@ -92,7 +92,6 @@ struct igc_mac_info { bool asf_firmware_present; bool arc_subsystem_valid; - bool autoneg_failed; bool get_link_status; }; diff --git a/drivers/net/ethernet/intel/igc/igc_mac.c b/drivers/net/ethernet/intel/igc/igc_mac.c index 7ac6637f8db7..142beb9ae557 100644 --- a/drivers/net/ethernet/intel/igc/igc_mac.c +++ b/drivers/net/ethernet/intel/igc/igc_mac.c @@ -438,28 +438,14 @@ void igc_config_collision_dist(struct igc_hw *hw) * Checks the status of auto-negotiation after link up to ensure that the * speed and duplex were not forced. If the link needed to be forced, then * flow control needs to be forced also. If auto-negotiation is enabled - * and did not fail, then we configure flow control based on our link - * partner. + * then we configure flow control based on our link partner. */ s32 igc_config_fc_after_link_up(struct igc_hw *hw) { u16 mii_status_reg, mii_nway_adv_reg, mii_nway_lp_ability_reg; - struct igc_mac_info *mac = &hw->mac; u16 speed, duplex; s32 ret_val = 0; - /* Check for the case where we have fiber media and auto-neg failed - * so we had to force link. In this case, we need to force the - * configuration of the MAC to match the "fc" parameter. - */ - if (mac->autoneg_failed) - ret_val = igc_force_mac_fc(hw); - - if (ret_val) { - hw_dbg("Error forcing flow control settings\n"); - goto out; - } - /* In auto-neg, we need to check and see if Auto-Neg has completed, * and if so, how the PHY and link partner has flow control * configured. From c731361cfef90d110f856a7bab6e496ed94e9a02 Mon Sep 17 00:00:00 2001 From: Faizal Rahim Date: Fri, 8 May 2026 05:47:04 +0800 Subject: [PATCH 0087/1433] igc: move autoneg-enabled settings into igc_handle_autoneg_enabled() Move the advertised link modes and flow control configuration from igc_ethtool_set_link_ksettings() into igc_handle_autoneg_enabled(). No functional change. Reviewed-by: Looi Hong Aun Reviewed-by: Aleksandr Loktionov Signed-off-by: Faizal Rahim Signed-off-by: Khai Wen Tan Reviewed-by: Dima Ruinskiy Reviewed-by: Simon Horman Tested-by: Moriya Kadosh Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/igc/igc_ethtool.c | 78 ++++++++++++-------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/drivers/net/ethernet/intel/igc/igc_ethtool.c b/drivers/net/ethernet/intel/igc/igc_ethtool.c index fbba3e84673a..7ee84c24dc4e 100644 --- a/drivers/net/ethernet/intel/igc/igc_ethtool.c +++ b/drivers/net/ethernet/intel/igc/igc_ethtool.c @@ -2032,38 +2032,20 @@ static int igc_ethtool_get_link_ksettings(struct net_device *netdev, return 0; } -static int -igc_ethtool_set_link_ksettings(struct net_device *netdev, - const struct ethtool_link_ksettings *cmd) +/** + * igc_handle_autoneg_enabled - Configure autonegotiation advertisement + * @adapter: private driver structure + * @cmd: ethtool link ksettings from user + * + * Records advertised speeds and flow control settings when autoneg + * is enabled. + */ +static void igc_handle_autoneg_enabled(struct igc_adapter *adapter, + const struct ethtool_link_ksettings *cmd) { - struct igc_adapter *adapter = netdev_priv(netdev); - struct net_device *dev = adapter->netdev; struct igc_hw *hw = &adapter->hw; u16 advertised = 0; - /* When adapter in resetting mode, autoneg/speed/duplex - * cannot be changed - */ - if (igc_check_reset_block(hw)) { - netdev_err(dev, "Cannot change link characteristics when reset is active\n"); - return -EINVAL; - } - - /* MDI setting is only allowed when autoneg enabled because - * some hardware doesn't allow MDI setting when speed or - * duplex is forced. - */ - if (cmd->base.eth_tp_mdix_ctrl) { - if (cmd->base.eth_tp_mdix_ctrl != ETH_TP_MDI_AUTO && - cmd->base.autoneg != AUTONEG_ENABLE) { - netdev_err(dev, "Forcing MDI/MDI-X state is not supported when link speed and/or duplex are forced\n"); - return -EINVAL; - } - } - - while (test_and_set_bit(__IGC_RESETTING, &adapter->state)) - usleep_range(1000, 2000); - if (ethtool_link_ksettings_test_link_mode(cmd, advertising, 2500baseT_Full)) advertised |= ADVERTISE_2500_FULL; @@ -2088,10 +2070,44 @@ igc_ethtool_set_link_ksettings(struct net_device *netdev, 10baseT_Half)) advertised |= ADVERTISE_10_HALF; + hw->phy.autoneg_advertised = advertised; + if (adapter->fc_autoneg) + hw->fc.requested_mode = igc_fc_default; +} + +static int +igc_ethtool_set_link_ksettings(struct net_device *netdev, + const struct ethtool_link_ksettings *cmd) +{ + struct igc_adapter *adapter = netdev_priv(netdev); + struct net_device *dev = adapter->netdev; + struct igc_hw *hw = &adapter->hw; + + /* When adapter in resetting mode, autoneg/speed/duplex + * cannot be changed + */ + if (igc_check_reset_block(hw)) { + netdev_err(dev, "Cannot change link characteristics when reset is active\n"); + return -EINVAL; + } + + /* MDI setting is only allowed when autoneg enabled because + * some hardware doesn't allow MDI setting when speed or + * duplex is forced. + */ + if (cmd->base.eth_tp_mdix_ctrl) { + if (cmd->base.eth_tp_mdix_ctrl != ETH_TP_MDI_AUTO && + cmd->base.autoneg != AUTONEG_ENABLE) { + netdev_err(dev, "Forcing MDI/MDI-X state is not supported when link speed and/or duplex are forced\n"); + return -EINVAL; + } + } + + while (test_and_set_bit(__IGC_RESETTING, &adapter->state)) + usleep_range(1000, 2000); + if (cmd->base.autoneg == AUTONEG_ENABLE) { - hw->phy.autoneg_advertised = advertised; - if (adapter->fc_autoneg) - hw->fc.requested_mode = igc_fc_default; + igc_handle_autoneg_enabled(adapter, cmd); } else { netdev_info(dev, "Force mode currently not supported\n"); } From fa7315482f582839342d7be534b7cc423a9d7996 Mon Sep 17 00:00:00 2001 From: Faizal Rahim Date: Fri, 8 May 2026 05:47:05 +0800 Subject: [PATCH 0088/1433] igc: replace goto out with direct returns in igc_config_fc_after_link_up() The out: label only returns ret_val with no cleanup. The kernel coding style guide states: "If there is no cleanup needed then just return directly." (Documentation/process/coding-style.rst, section 7). This improves readability ahead of a subsequent patch that introduces a new goto label in this function. No functional change. Reviewed-by: Looi Hong Aun Signed-off-by: Faizal Rahim Signed-off-by: Khai Wen Tan Reviewed-by: Dima Ruinskiy Reviewed-by: Simon Horman Tested-by: Moriya Kadosh Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/igc/igc_mac.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/intel/igc/igc_mac.c b/drivers/net/ethernet/intel/igc/igc_mac.c index 142beb9ae557..0a3d3f357505 100644 --- a/drivers/net/ethernet/intel/igc/igc_mac.c +++ b/drivers/net/ethernet/intel/igc/igc_mac.c @@ -458,15 +458,15 @@ s32 igc_config_fc_after_link_up(struct igc_hw *hw) ret_val = hw->phy.ops.read_reg(hw, PHY_STATUS, &mii_status_reg); if (ret_val) - goto out; + return ret_val; ret_val = hw->phy.ops.read_reg(hw, PHY_STATUS, &mii_status_reg); if (ret_val) - goto out; + return ret_val; if (!(mii_status_reg & MII_SR_AUTONEG_COMPLETE)) { hw_dbg("Copper PHY and Auto Neg has not completed.\n"); - goto out; + return ret_val; } /* The AutoNeg process has completed, so we now need to @@ -478,11 +478,11 @@ s32 igc_config_fc_after_link_up(struct igc_hw *hw) ret_val = hw->phy.ops.read_reg(hw, PHY_AUTONEG_ADV, &mii_nway_adv_reg); if (ret_val) - goto out; + return ret_val; ret_val = hw->phy.ops.read_reg(hw, PHY_LP_ABILITY, &mii_nway_lp_ability_reg); if (ret_val) - goto out; + return ret_val; /* Two bits in the Auto Negotiation Advertisement Register * (Address 4) and two bits in the Auto Negotiation Base * Page Ability Register (Address 5) determine flow control @@ -598,7 +598,7 @@ s32 igc_config_fc_after_link_up(struct igc_hw *hw) ret_val = hw->mac.ops.get_speed_and_duplex(hw, &speed, &duplex); if (ret_val) { hw_dbg("Error getting link speed and duplex\n"); - goto out; + return ret_val; } if (duplex == HALF_DUPLEX) @@ -610,10 +610,9 @@ s32 igc_config_fc_after_link_up(struct igc_hw *hw) ret_val = igc_force_mac_fc(hw); if (ret_val) { hw_dbg("Error forcing flow control settings\n"); - goto out; + return ret_val; } -out: return ret_val; } From acb138b8235c63564aa1bcd2666fdc9707e2f1e0 Mon Sep 17 00:00:00 2001 From: Faizal Rahim Date: Fri, 8 May 2026 05:47:06 +0800 Subject: [PATCH 0089/1433] igc: add support for forcing link speed without autonegotiation Allow users to force 10/100 Mb/s link speed and duplex via ethtool when autonegotiation is disabled. Previously, the driver rejected these requests with "Force mode currently not supported.". Forcing at 1000 Mb/s and 2500 Mb/s is not supported. Reviewed-by: Looi Hong Aun Signed-off-by: Faizal Rahim Signed-off-by: Khai Wen Tan Reviewed-by: Simon Horman Reviewed-by: Dima Ruinskiy Tested-by: Moriya Kadosh Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/igc/igc_base.c | 33 ++++- drivers/net/ethernet/intel/igc/igc_defines.h | 9 +- drivers/net/ethernet/intel/igc/igc_ethtool.c | 136 ++++++++++++++----- drivers/net/ethernet/intel/igc/igc_hw.h | 9 ++ drivers/net/ethernet/intel/igc/igc_mac.c | 12 ++ drivers/net/ethernet/intel/igc/igc_main.c | 2 +- drivers/net/ethernet/intel/igc/igc_phy.c | 65 ++++++++- drivers/net/ethernet/intel/igc/igc_phy.h | 1 + 8 files changed, 218 insertions(+), 49 deletions(-) diff --git a/drivers/net/ethernet/intel/igc/igc_base.c b/drivers/net/ethernet/intel/igc/igc_base.c index 1613b562d17c..ab9120a3127f 100644 --- a/drivers/net/ethernet/intel/igc/igc_base.c +++ b/drivers/net/ethernet/intel/igc/igc_base.c @@ -114,11 +114,35 @@ static s32 igc_setup_copper_link_base(struct igc_hw *hw) u32 ctrl; ctrl = rd32(IGC_CTRL); - ctrl |= IGC_CTRL_SLU; - ctrl &= ~(IGC_CTRL_FRCSPD | IGC_CTRL_FRCDPX); - wr32(IGC_CTRL, ctrl); + ctrl &= ~(IGC_CTRL_FRCSPD | IGC_CTRL_FRCDPX | + IGC_CTRL_SPEED_MASK | IGC_CTRL_FD); - ret_val = igc_setup_copper_link(hw); + if (hw->mac.autoneg_enabled) { + ctrl |= IGC_CTRL_SLU; + wr32(IGC_CTRL, ctrl); + ret_val = igc_setup_copper_link(hw); + } else { + ctrl |= IGC_CTRL_SLU | IGC_CTRL_FRCSPD | IGC_CTRL_FRCDPX; + + switch (hw->mac.forced_speed_duplex) { + case IGC_FORCED_10H: + ctrl |= IGC_CTRL_SPEED_10; + break; + case IGC_FORCED_10F: + ctrl |= IGC_CTRL_SPEED_10 | IGC_CTRL_FD; + break; + case IGC_FORCED_100H: + ctrl |= IGC_CTRL_SPEED_100; + break; + case IGC_FORCED_100F: + ctrl |= IGC_CTRL_SPEED_100 | IGC_CTRL_FD; + break; + default: + return -IGC_ERR_CONFIG; + } + wr32(IGC_CTRL, ctrl); + ret_val = igc_setup_copper_link(hw); + } return ret_val; } @@ -443,6 +467,7 @@ static const struct igc_phy_operations igc_phy_ops_base = { .reset = igc_phy_hw_reset, .read_reg = igc_read_phy_reg_gpy, .write_reg = igc_write_phy_reg_gpy, + .force_speed_duplex = igc_force_speed_duplex, }; const struct igc_info igc_base_info = { diff --git a/drivers/net/ethernet/intel/igc/igc_defines.h b/drivers/net/ethernet/intel/igc/igc_defines.h index 9482ab11f050..3f504751c2d9 100644 --- a/drivers/net/ethernet/intel/igc/igc_defines.h +++ b/drivers/net/ethernet/intel/igc/igc_defines.h @@ -129,10 +129,13 @@ #define IGC_ERR_SWFW_SYNC 13 /* Device Control */ +#define IGC_CTRL_FD BIT(0) /* Full Duplex */ #define IGC_CTRL_RST 0x04000000 /* Global reset */ - #define IGC_CTRL_PHY_RST 0x80000000 /* PHY Reset */ #define IGC_CTRL_SLU 0x00000040 /* Set link up (Force Link) */ +#define IGC_CTRL_SPEED_MASK GENMASK(10, 8) +#define IGC_CTRL_SPEED_10 FIELD_PREP(IGC_CTRL_SPEED_MASK, 0) +#define IGC_CTRL_SPEED_100 FIELD_PREP(IGC_CTRL_SPEED_MASK, 1) #define IGC_CTRL_FRCSPD 0x00000800 /* Force Speed */ #define IGC_CTRL_FRCDPX 0x00001000 /* Force Duplex */ #define IGC_CTRL_VME 0x40000000 /* IEEE VLAN mode enable */ @@ -673,6 +676,10 @@ #define IGC_GEN_POLL_TIMEOUT 1920 /* PHY Control Register */ +#define MII_CR_SPEED_MASK (BIT(6) | BIT(13)) +#define MII_CR_SPEED_10 0x0000 /* SSM=0, SSL=0: 10 Mb/s */ +#define MII_CR_SPEED_100 BIT(13) /* SSM=0, SSL=1: 100 Mb/s */ +#define MII_CR_DUPLEX_EN BIT(8) /* 0 = Half Duplex, 1 = Full Duplex */ #define MII_CR_RESTART_AUTO_NEG 0x0200 /* Restart auto negotiation */ #define MII_CR_POWER_DOWN 0x0800 /* Power down */ #define MII_CR_AUTO_NEG_EN 0x1000 /* Auto Neg Enable */ diff --git a/drivers/net/ethernet/intel/igc/igc_ethtool.c b/drivers/net/ethernet/intel/igc/igc_ethtool.c index 7ee84c24dc4e..89fe2788a565 100644 --- a/drivers/net/ethernet/intel/igc/igc_ethtool.c +++ b/drivers/net/ethernet/intel/igc/igc_ethtool.c @@ -1946,44 +1946,58 @@ static int igc_ethtool_get_link_ksettings(struct net_device *netdev, ethtool_link_ksettings_add_link_mode(cmd, supported, TP); ethtool_link_ksettings_add_link_mode(cmd, advertising, TP); - /* advertising link modes */ - if (hw->phy.autoneg_advertised & ADVERTISE_10_HALF) - ethtool_link_ksettings_add_link_mode(cmd, advertising, 10baseT_Half); - if (hw->phy.autoneg_advertised & ADVERTISE_10_FULL) - ethtool_link_ksettings_add_link_mode(cmd, advertising, 10baseT_Full); - if (hw->phy.autoneg_advertised & ADVERTISE_100_HALF) - ethtool_link_ksettings_add_link_mode(cmd, advertising, 100baseT_Half); - if (hw->phy.autoneg_advertised & ADVERTISE_100_FULL) - ethtool_link_ksettings_add_link_mode(cmd, advertising, 100baseT_Full); - if (hw->phy.autoneg_advertised & ADVERTISE_1000_FULL) - ethtool_link_ksettings_add_link_mode(cmd, advertising, 1000baseT_Full); - if (hw->phy.autoneg_advertised & ADVERTISE_2500_FULL) - ethtool_link_ksettings_add_link_mode(cmd, advertising, 2500baseT_Full); - /* set autoneg settings */ ethtool_link_ksettings_add_link_mode(cmd, supported, Autoneg); - ethtool_link_ksettings_add_link_mode(cmd, advertising, Autoneg); + if (hw->mac.autoneg_enabled) { + ethtool_link_ksettings_add_link_mode(cmd, advertising, Autoneg); + cmd->base.autoneg = AUTONEG_ENABLE; - /* Set pause flow control settings */ - ethtool_link_ksettings_add_link_mode(cmd, supported, Pause); + /* advertising link modes only apply when autoneg is on */ + if (hw->phy.autoneg_advertised & ADVERTISE_10_HALF) + ethtool_link_ksettings_add_link_mode(cmd, advertising, + 10baseT_Half); + if (hw->phy.autoneg_advertised & ADVERTISE_10_FULL) + ethtool_link_ksettings_add_link_mode(cmd, advertising, + 10baseT_Full); + if (hw->phy.autoneg_advertised & ADVERTISE_100_HALF) + ethtool_link_ksettings_add_link_mode(cmd, advertising, + 100baseT_Half); + if (hw->phy.autoneg_advertised & ADVERTISE_100_FULL) + ethtool_link_ksettings_add_link_mode(cmd, advertising, + 100baseT_Full); + if (hw->phy.autoneg_advertised & ADVERTISE_1000_FULL) + ethtool_link_ksettings_add_link_mode(cmd, advertising, + 1000baseT_Full); + if (hw->phy.autoneg_advertised & ADVERTISE_2500_FULL) + ethtool_link_ksettings_add_link_mode(cmd, advertising, + 2500baseT_Full); - switch (hw->fc.requested_mode) { - case igc_fc_full: - ethtool_link_ksettings_add_link_mode(cmd, advertising, Pause); - break; - case igc_fc_rx_pause: - ethtool_link_ksettings_add_link_mode(cmd, advertising, Pause); - ethtool_link_ksettings_add_link_mode(cmd, advertising, - Asym_Pause); - break; - case igc_fc_tx_pause: - ethtool_link_ksettings_add_link_mode(cmd, advertising, - Asym_Pause); - break; - default: - break; + /* Set pause flow control advertising */ + switch (hw->fc.requested_mode) { + case igc_fc_full: + ethtool_link_ksettings_add_link_mode(cmd, advertising, + Pause); + break; + case igc_fc_rx_pause: + ethtool_link_ksettings_add_link_mode(cmd, advertising, + Pause); + ethtool_link_ksettings_add_link_mode(cmd, advertising, + Asym_Pause); + break; + case igc_fc_tx_pause: + ethtool_link_ksettings_add_link_mode(cmd, advertising, + Asym_Pause); + break; + default: + break; + } + } else { + cmd->base.autoneg = AUTONEG_DISABLE; } + /* Pause is always supported */ + ethtool_link_ksettings_add_link_mode(cmd, supported, Pause); + status = pm_runtime_suspended(&adapter->pdev->dev) ? 0 : rd32(IGC_STATUS); @@ -2015,7 +2029,6 @@ static int igc_ethtool_get_link_ksettings(struct net_device *netdev, cmd->base.duplex = DUPLEX_UNKNOWN; } cmd->base.speed = speed; - cmd->base.autoneg = AUTONEG_ENABLE; /* MDI-X => 2; MDI =>1; Invalid =>0 */ if (hw->phy.media_type == igc_media_type_copper) @@ -2032,6 +2045,37 @@ static int igc_ethtool_get_link_ksettings(struct net_device *netdev, return 0; } +/** + * igc_handle_autoneg_disabled - Configure forced speed/duplex settings + * @adapter: private driver structure + * @speed: requested speed (must be SPEED_10 or SPEED_100) + * @duplex: requested duplex + * + * Records forced speed/duplex when autoneg is disabled. + * Caller must validate speed before calling this function. + */ +static void igc_handle_autoneg_disabled(struct igc_adapter *adapter, u32 speed, + u8 duplex) +{ + struct igc_mac_info *mac = &adapter->hw.mac; + + switch (speed) { + case SPEED_10: + mac->forced_speed_duplex = (duplex == DUPLEX_FULL) ? + IGC_FORCED_10F : IGC_FORCED_10H; + break; + case SPEED_100: + mac->forced_speed_duplex = (duplex == DUPLEX_FULL) ? + IGC_FORCED_100F : IGC_FORCED_100H; + break; + default: + WARN_ONCE(1, "Unsupported speed %u\n", speed); + return; + } + + mac->autoneg_enabled = false; +} + /** * igc_handle_autoneg_enabled - Configure autonegotiation advertisement * @adapter: private driver structure @@ -2070,6 +2114,7 @@ static void igc_handle_autoneg_enabled(struct igc_adapter *adapter, 10baseT_Half)) advertised |= ADVERTISE_10_HALF; + hw->mac.autoneg_enabled = true; hw->phy.autoneg_advertised = advertised; if (adapter->fc_autoneg) hw->fc.requested_mode = igc_fc_default; @@ -2091,6 +2136,12 @@ igc_ethtool_set_link_ksettings(struct net_device *netdev, return -EINVAL; } + if (cmd->base.autoneg != AUTONEG_ENABLE && + cmd->base.autoneg != AUTONEG_DISABLE) { + netdev_info(dev, "Unsupported autoneg setting\n"); + return -EINVAL; + } + /* MDI setting is only allowed when autoneg enabled because * some hardware doesn't allow MDI setting when speed or * duplex is forced. @@ -2103,14 +2154,25 @@ igc_ethtool_set_link_ksettings(struct net_device *netdev, } } + if (cmd->base.autoneg == AUTONEG_DISABLE) { + if (cmd->base.speed != SPEED_10 && cmd->base.speed != SPEED_100) { + netdev_info(dev, "Unsupported speed for forced link\n"); + return -EINVAL; + } + if (cmd->base.duplex != DUPLEX_HALF && cmd->base.duplex != DUPLEX_FULL) { + netdev_info(dev, "Duplex must be half or full for forced link\n"); + return -EINVAL; + } + } + while (test_and_set_bit(__IGC_RESETTING, &adapter->state)) usleep_range(1000, 2000); - if (cmd->base.autoneg == AUTONEG_ENABLE) { + if (cmd->base.autoneg == AUTONEG_ENABLE) igc_handle_autoneg_enabled(adapter, cmd); - } else { - netdev_info(dev, "Force mode currently not supported\n"); - } + else + igc_handle_autoneg_disabled(adapter, cmd->base.speed, + cmd->base.duplex); /* MDI-X => 2; MDI => 1; Auto => 3 */ if (cmd->base.eth_tp_mdix_ctrl) { diff --git a/drivers/net/ethernet/intel/igc/igc_hw.h b/drivers/net/ethernet/intel/igc/igc_hw.h index 86ab8f566f44..62aaee55668a 100644 --- a/drivers/net/ethernet/intel/igc/igc_hw.h +++ b/drivers/net/ethernet/intel/igc/igc_hw.h @@ -73,6 +73,13 @@ struct igc_info { extern const struct igc_info igc_base_info; +enum igc_forced_speed_duplex { + IGC_FORCED_10H, + IGC_FORCED_10F, + IGC_FORCED_100H, + IGC_FORCED_100F, +}; + struct igc_mac_info { struct igc_mac_operations ops; @@ -93,6 +100,8 @@ struct igc_mac_info { bool arc_subsystem_valid; bool get_link_status; + bool autoneg_enabled; + enum igc_forced_speed_duplex forced_speed_duplex; }; struct igc_nvm_operations { diff --git a/drivers/net/ethernet/intel/igc/igc_mac.c b/drivers/net/ethernet/intel/igc/igc_mac.c index 0a3d3f357505..d6f3f6618469 100644 --- a/drivers/net/ethernet/intel/igc/igc_mac.c +++ b/drivers/net/ethernet/intel/igc/igc_mac.c @@ -446,6 +446,17 @@ s32 igc_config_fc_after_link_up(struct igc_hw *hw) u16 speed, duplex; s32 ret_val = 0; + /* Without autoneg, flow control capability is not exchanged with the + * link partner. IEEE 802.3 prohibits flow control in half-duplex mode. + */ + if (!hw->mac.autoneg_enabled) { + if (hw->mac.forced_speed_duplex == IGC_FORCED_10H || + hw->mac.forced_speed_duplex == IGC_FORCED_100H) + hw->fc.current_mode = igc_fc_none; + + goto force_fc; + } + /* In auto-neg, we need to check and see if Auto-Neg has completed, * and if so, how the PHY and link partner has flow control * configured. @@ -607,6 +618,7 @@ s32 igc_config_fc_after_link_up(struct igc_hw *hw) /* Now we call a subroutine to actually force the MAC * controller to use the correct flow control settings. */ +force_fc: ret_val = igc_force_mac_fc(hw); if (ret_val) { hw_dbg("Error forcing flow control settings\n"); diff --git a/drivers/net/ethernet/intel/igc/igc_main.c b/drivers/net/ethernet/intel/igc/igc_main.c index 5ef229a5931f..e6e9441fc3d4 100644 --- a/drivers/net/ethernet/intel/igc/igc_main.c +++ b/drivers/net/ethernet/intel/igc/igc_main.c @@ -7298,7 +7298,7 @@ static int igc_probe(struct pci_dev *pdev, /* Initialize link properties that are user-changeable */ adapter->fc_autoneg = true; hw->phy.autoneg_advertised = 0xaf; - + hw->mac.autoneg_enabled = true; hw->fc.requested_mode = igc_fc_default; hw->fc.current_mode = igc_fc_default; diff --git a/drivers/net/ethernet/intel/igc/igc_phy.c b/drivers/net/ethernet/intel/igc/igc_phy.c index 6c4d204aecfa..4cf737fb3b21 100644 --- a/drivers/net/ethernet/intel/igc/igc_phy.c +++ b/drivers/net/ethernet/intel/igc/igc_phy.c @@ -494,12 +494,20 @@ s32 igc_setup_copper_link(struct igc_hw *hw) s32 ret_val = 0; bool link; - /* Setup autoneg and flow control advertisement and perform - * autonegotiation. - */ - ret_val = igc_copper_link_autoneg(hw); - if (ret_val) - goto out; + if (hw->mac.autoneg_enabled) { + /* Setup autoneg and flow control advertisement and perform + * autonegotiation. + */ + ret_val = igc_copper_link_autoneg(hw); + if (ret_val) + goto out; + } else { + ret_val = hw->phy.ops.force_speed_duplex(hw); + if (ret_val) { + hw_dbg("Error Forcing Speed/Duplex\n"); + goto out; + } + } /* Check link status. Wait up to 100 microseconds for link to become * valid. @@ -778,3 +786,48 @@ u16 igc_read_phy_fw_version(struct igc_hw *hw) return gphy_version; } + +/** + * igc_force_speed_duplex - Force PHY speed and duplex settings + * @hw: pointer to the HW structure + * + * Programs the GPY PHY control register to disable autonegotiation + * and force the speed/duplex indicated by hw->mac.forced_speed_duplex. + */ +s32 igc_force_speed_duplex(struct igc_hw *hw) +{ + struct igc_phy_info *phy = &hw->phy; + u16 phy_ctrl; + s32 ret_val; + + ret_val = phy->ops.read_reg(hw, PHY_CONTROL, &phy_ctrl); + if (ret_val) + return ret_val; + + phy_ctrl &= ~(MII_CR_SPEED_MASK | MII_CR_DUPLEX_EN | + MII_CR_AUTO_NEG_EN | MII_CR_RESTART_AUTO_NEG); + + switch (hw->mac.forced_speed_duplex) { + case IGC_FORCED_10H: + phy_ctrl |= MII_CR_SPEED_10; + break; + case IGC_FORCED_10F: + phy_ctrl |= MII_CR_SPEED_10 | MII_CR_DUPLEX_EN; + break; + case IGC_FORCED_100H: + phy_ctrl |= MII_CR_SPEED_100; + break; + case IGC_FORCED_100F: + phy_ctrl |= MII_CR_SPEED_100 | MII_CR_DUPLEX_EN; + break; + default: + return -IGC_ERR_CONFIG; + } + + ret_val = phy->ops.write_reg(hw, PHY_CONTROL, phy_ctrl); + if (ret_val) + return ret_val; + + hw->mac.get_link_status = true; + return 0; +} diff --git a/drivers/net/ethernet/intel/igc/igc_phy.h b/drivers/net/ethernet/intel/igc/igc_phy.h index 832a7e359f18..d37a89174826 100644 --- a/drivers/net/ethernet/intel/igc/igc_phy.h +++ b/drivers/net/ethernet/intel/igc/igc_phy.h @@ -18,5 +18,6 @@ void igc_power_down_phy_copper(struct igc_hw *hw); s32 igc_write_phy_reg_gpy(struct igc_hw *hw, u32 offset, u16 data); s32 igc_read_phy_reg_gpy(struct igc_hw *hw, u32 offset, u16 *data); u16 igc_read_phy_fw_version(struct igc_hw *hw); +s32 igc_force_speed_duplex(struct igc_hw *hw); #endif From 7693eadcbb8cc32e7cde77ff2cdac67ac026a920 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Tue, 30 Jun 2026 10:37:00 +0200 Subject: [PATCH 0090/1433] net: phylink: Drop references to the .validate() method in comments The phylink_mac_ops '.validate()' has been removed in: commit da5f6b80ad64 ("net: phylink: remove .validate() method") There are still a few comments around in phylink that references that, related to the ports fields as well as the Pause configuration. Let's drop these references and update the comments related to Pause handling. Signed-off-by: Maxime Chevallier Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260630083700.2041915-1-maxime.chevallier@bootlin.com Signed-off-by: Paolo Abeni --- drivers/net/phy/phylink.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 087ac63f9193..59dfe35afa54 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -153,8 +153,7 @@ static DECLARE_PHY_INTERFACE_MASK(phylink_sfp_interfaces); * phylink_set_port_modes() - set the port type modes in the ethtool mask * @mask: ethtool link mode mask * - * Sets all the port type modes in the ethtool mask. MAC drivers should - * use this in their 'validate' callback. + * Sets all the port type modes in the ethtool mask. */ void phylink_set_port_modes(unsigned long *mask) { @@ -2095,9 +2094,9 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy, /* * This is the new way of dealing with flow control for PHYs, * as described by Timur Tabi in commit 529ed1275263 ("net: phy: - * phy drivers should not set SUPPORTED_[Asym_]Pause") except - * using our validate call to the MAC, we rely upon the MAC - * clearing the bits from both supported and advertising fields. + * phy drivers should not set SUPPORTED_[Asym_]Pause"). MAC drivers + * set their support using the MAC_SYM_PAUSE and MAC_ASYM_PAUSE + * capabilities and must NOT change the phy's pause settings directly. */ phy_support_asym_pause(phy); @@ -3931,9 +3930,9 @@ static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy) /* * This is the new way of dealing with flow control for PHYs, * as described by Timur Tabi in commit 529ed1275263 ("net: phy: - * phy drivers should not set SUPPORTED_[Asym_]Pause") except - * using our validate call to the MAC, we rely upon the MAC - * clearing the bits from both supported and advertising fields. + * phy drivers should not set SUPPORTED_[Asym_]Pause"). MAC drivers + * set their support using the MAC_SYM_PAUSE and MAC_ASYM_PAUSE + * capabilities and must NOT change the phy's pause settings directly. */ phy_support_asym_pause(phy); From 07d3aaa046ceffb2ed72010d88cb6071717c4906 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 29 Jun 2026 16:43:53 -0700 Subject: [PATCH 0091/1433] selftests: drv-net: toeplitz: cap the Rx queue count The RPS test needs a free CPU within the first RPS_MAX_CPUS (16) cores. This is easily violated if the NIC or env allocates the IRQs to cores linearly. Cap the Rx queues at 8, we don't need more. This makes the test pass on CX7 in NIPA. Signed-off-by: Jakub Kicinski Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260629234354.2154541-1-kuba@kernel.org Signed-off-by: Paolo Abeni --- .../selftests/drivers/net/hw/toeplitz.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tools/testing/selftests/drivers/net/hw/toeplitz.py b/tools/testing/selftests/drivers/net/hw/toeplitz.py index cd7e080e6f84..571732198b93 100755 --- a/tools/testing/selftests/drivers/net/hw/toeplitz.py +++ b/tools/testing/selftests/drivers/net/hw/toeplitz.py @@ -21,6 +21,8 @@ from lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx, KsftFailEx ETH_RSS_HASH_TOP = 1 # Must match RPS_MAX_CPUS in toeplitz.c RPS_MAX_CPUS = 16 +# Cap Rx queues so IRQ pinning leaves free CPUs in the RPS_MAX_CPUS range +QUEUE_CAP = 8 def _check_rps_and_rfs_not_configured(cfg): @@ -48,6 +50,25 @@ def _get_cpu_for_irq(irq): return int(data) +def _cap_queue_count(cfg): + ehdr = {"header": {"dev-index": cfg.ifindex}} + chans = cfg.ethnl.channels_get(ehdr) + + config = {} + restore = {} + for key in ("combined-count", "rx-count"): + cur = chans.get(key, 0) + if cur > QUEUE_CAP: + config[key] = QUEUE_CAP + restore[key] = cur + + if not config: + return + + cfg.ethnl.channels_set(ehdr | config) + defer(cfg.ethnl.channels_set, ehdr | restore) + + def _get_irq_cpus(cfg): """ Read the list of IRQs for the device Rx queues. @@ -177,6 +198,7 @@ def test(cfg, proto_flag, ipver, grp): ] if grp: + _cap_queue_count(cfg) _check_rps_and_rfs_not_configured(cfg) if grp == "rss": irq_cpus = ",".join([str(x) for x in _get_irq_cpus(cfg)]) From eb56577ae9a5aad5c15725a4121f9e560842bd79 Mon Sep 17 00:00:00 2001 From: David Christensen Date: Mon, 29 Jun 2026 16:13:42 -0500 Subject: [PATCH 0092/1433] ehea: remove the ehea driver The IBM eHEA (Ethernet Host Ethernet Adapter) driver has been orphaned since April 2024 with no active maintainer. The hardware was last supported on IBM POWER7 systems which reached end-of-support in December 2020. The driver has received no functional updates since October 2022, with all subsequent changes being mechanical API migrations affecting the entire kernel tree. A search of lore.kernel.org for the last 24 months reveals no user reports, no objections to the orphan status, and no maintenance discussions indicating active hardware deployment. The code is preserved in git history and can be restored if a maintainer steps forward to take ownership. Signed-off-by: David Christensen Reviewed-by: Christophe Leroy (CS GROUP) Link: https://patch.msgid.link/20260629211343.3712775-2-drc@linux.ibm.com Signed-off-by: Paolo Abeni --- MAINTAINERS | 5 - drivers/net/ethernet/ibm/Kconfig | 9 - drivers/net/ethernet/ibm/Makefile | 1 - drivers/net/ethernet/ibm/ehea/Makefile | 7 - drivers/net/ethernet/ibm/ehea/ehea.h | 477 --- drivers/net/ethernet/ibm/ehea/ehea_ethtool.c | 277 -- drivers/net/ethernet/ibm/ehea/ehea_hw.h | 253 -- drivers/net/ethernet/ibm/ehea/ehea_main.c | 3581 ------------------ drivers/net/ethernet/ibm/ehea/ehea_phyp.c | 612 --- drivers/net/ethernet/ibm/ehea/ehea_phyp.h | 433 --- drivers/net/ethernet/ibm/ehea/ehea_qmr.c | 999 ----- drivers/net/ethernet/ibm/ehea/ehea_qmr.h | 390 -- 12 files changed, 7044 deletions(-) delete mode 100644 drivers/net/ethernet/ibm/ehea/Makefile delete mode 100644 drivers/net/ethernet/ibm/ehea/ehea.h delete mode 100644 drivers/net/ethernet/ibm/ehea/ehea_ethtool.c delete mode 100644 drivers/net/ethernet/ibm/ehea/ehea_hw.h delete mode 100644 drivers/net/ethernet/ibm/ehea/ehea_main.c delete mode 100644 drivers/net/ethernet/ibm/ehea/ehea_phyp.c delete mode 100644 drivers/net/ethernet/ibm/ehea/ehea_phyp.h delete mode 100644 drivers/net/ethernet/ibm/ehea/ehea_qmr.c delete mode 100644 drivers/net/ethernet/ibm/ehea/ehea_qmr.h diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..ee4ee7b8e947 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9498,11 +9498,6 @@ S: Orphan W: http://aeschi.ch.eu.org/efs/ F: fs/efs/ -EHEA (IBM pSeries eHEA 10Gb ethernet adapter) DRIVER -L: netdev@vger.kernel.org -S: Orphan -F: drivers/net/ethernet/ibm/ehea/ - ELM327 CAN NETWORK DRIVER M: Max Staudt L: linux-can@vger.kernel.org diff --git a/drivers/net/ethernet/ibm/Kconfig b/drivers/net/ethernet/ibm/Kconfig index 4f4b23465c47..8e55faac2035 100644 --- a/drivers/net/ethernet/ibm/Kconfig +++ b/drivers/net/ethernet/ibm/Kconfig @@ -42,15 +42,6 @@ config IBMVETH_KUNIT_TEST source "drivers/net/ethernet/ibm/emac/Kconfig" -config EHEA - tristate "eHEA Ethernet support" - depends on IBMEBUS && SPARSEMEM - help - This driver supports the IBM pSeries eHEA ethernet adapter. - - To compile the driver as a module, choose M here. The module - will be called ehea. - config IBMVNIC tristate "IBM Virtual NIC support" depends on PPC_PSERIES diff --git a/drivers/net/ethernet/ibm/Makefile b/drivers/net/ethernet/ibm/Makefile index 1d17d0c33d4d..c7e5d891c946 100644 --- a/drivers/net/ethernet/ibm/Makefile +++ b/drivers/net/ethernet/ibm/Makefile @@ -6,4 +6,3 @@ obj-$(CONFIG_IBMVETH) += ibmveth.o obj-$(CONFIG_IBMVNIC) += ibmvnic.o obj-$(CONFIG_IBM_EMAC) += emac/ -obj-$(CONFIG_EHEA) += ehea/ diff --git a/drivers/net/ethernet/ibm/ehea/Makefile b/drivers/net/ethernet/ibm/ehea/Makefile deleted file mode 100644 index 9e1e5c7aafe2..000000000000 --- a/drivers/net/ethernet/ibm/ehea/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# -# Makefile for the eHEA ethernet device driver for IBM eServer System p -# -ehea-y = ehea_main.o ehea_phyp.o ehea_qmr.o ehea_ethtool.o -obj-$(CONFIG_EHEA) += ehea.o - diff --git a/drivers/net/ethernet/ibm/ehea/ehea.h b/drivers/net/ethernet/ibm/ehea/ehea.h deleted file mode 100644 index 208c440a602b..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea.h +++ /dev/null @@ -1,477 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea.h - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch - * Jan-Bernd Themann - * Thomas Klein - */ - -#ifndef __EHEA_H__ -#define __EHEA_H__ - -#include -#include -#include -#include -#include - -#include -#include - -#define DRV_NAME "ehea" -#define DRV_VERSION "EHEA_0107" - -/* eHEA capability flags */ -#define DLPAR_PORT_ADD_REM 1 -#define DLPAR_MEM_ADD 2 -#define DLPAR_MEM_REM 4 -#define EHEA_CAPABILITIES (DLPAR_PORT_ADD_REM | DLPAR_MEM_ADD | DLPAR_MEM_REM) - -#define EHEA_MSG_DEFAULT (NETIF_MSG_LINK | NETIF_MSG_TIMER \ - | NETIF_MSG_RX_ERR | NETIF_MSG_TX_ERR) - -#define EHEA_MAX_ENTRIES_RQ1 32767 -#define EHEA_MAX_ENTRIES_RQ2 16383 -#define EHEA_MAX_ENTRIES_RQ3 16383 -#define EHEA_MAX_ENTRIES_SQ 32767 -#define EHEA_MIN_ENTRIES_QP 127 - -#define EHEA_SMALL_QUEUES - -#ifdef EHEA_SMALL_QUEUES -#define EHEA_MAX_CQE_COUNT 1023 -#define EHEA_DEF_ENTRIES_SQ 1023 -#define EHEA_DEF_ENTRIES_RQ1 1023 -#define EHEA_DEF_ENTRIES_RQ2 1023 -#define EHEA_DEF_ENTRIES_RQ3 511 -#else -#define EHEA_MAX_CQE_COUNT 4080 -#define EHEA_DEF_ENTRIES_SQ 4080 -#define EHEA_DEF_ENTRIES_RQ1 8160 -#define EHEA_DEF_ENTRIES_RQ2 2040 -#define EHEA_DEF_ENTRIES_RQ3 2040 -#endif - -#define EHEA_MAX_ENTRIES_EQ 20 - -#define EHEA_SG_SQ 2 -#define EHEA_SG_RQ1 1 -#define EHEA_SG_RQ2 0 -#define EHEA_SG_RQ3 0 - -#define EHEA_MAX_PACKET_SIZE 9022 /* for jumbo frames */ -#define EHEA_RQ2_PKT_SIZE 2048 -#define EHEA_L_PKT_SIZE 256 /* low latency */ - -/* Send completion signaling */ - -/* Protection Domain Identifier */ -#define EHEA_PD_ID 0xaabcdeff - -#define EHEA_RQ2_THRESHOLD 1 -#define EHEA_RQ3_THRESHOLD 4 /* use RQ3 threshold of 2048 bytes */ - -#define EHEA_SPEED_10G 10000 -#define EHEA_SPEED_1G 1000 -#define EHEA_SPEED_100M 100 -#define EHEA_SPEED_10M 10 -#define EHEA_SPEED_AUTONEG 0 - -/* Broadcast/Multicast registration types */ -#define EHEA_BCMC_SCOPE_ALL 0x08 -#define EHEA_BCMC_SCOPE_SINGLE 0x00 -#define EHEA_BCMC_MULTICAST 0x04 -#define EHEA_BCMC_BROADCAST 0x00 -#define EHEA_BCMC_UNTAGGED 0x02 -#define EHEA_BCMC_TAGGED 0x00 -#define EHEA_BCMC_VLANID_ALL 0x01 -#define EHEA_BCMC_VLANID_SINGLE 0x00 - -#define EHEA_CACHE_LINE 128 - -/* Memory Regions */ -#define EHEA_MR_ACC_CTRL 0x00800000 - -#define EHEA_BUSMAP_START 0x8000000000000000ULL -#define EHEA_INVAL_ADDR 0xFFFFFFFFFFFFFFFFULL -#define EHEA_DIR_INDEX_SHIFT 13 /* 8k Entries in 64k block */ -#define EHEA_TOP_INDEX_SHIFT (EHEA_DIR_INDEX_SHIFT * 2) -#define EHEA_MAP_ENTRIES (1 << EHEA_DIR_INDEX_SHIFT) -#define EHEA_MAP_SIZE (0x10000) /* currently fixed map size */ -#define EHEA_INDEX_MASK (EHEA_MAP_ENTRIES - 1) - - -#define EHEA_WATCH_DOG_TIMEOUT 10*HZ - -/* utility functions */ - -void ehea_dump(void *adr, int len, char *msg); - -#define EHEA_BMASK(pos, length) (((pos) << 16) + (length)) - -#define EHEA_BMASK_IBM(from, to) (((63 - to) << 16) + ((to) - (from) + 1)) - -#define EHEA_BMASK_SHIFTPOS(mask) (((mask) >> 16) & 0xffff) - -#define EHEA_BMASK_MASK(mask) \ - (0xffffffffffffffffULL >> ((64 - (mask)) & 0xffff)) - -#define EHEA_BMASK_SET(mask, value) \ - ((EHEA_BMASK_MASK(mask) & ((u64)(value))) << EHEA_BMASK_SHIFTPOS(mask)) - -#define EHEA_BMASK_GET(mask, value) \ - (EHEA_BMASK_MASK(mask) & (((u64)(value)) >> EHEA_BMASK_SHIFTPOS(mask))) - -/* - * Generic ehea page - */ -struct ehea_page { - u8 entries[PAGE_SIZE]; -}; - -/* - * Generic queue in linux kernel virtual memory - */ -struct hw_queue { - u64 current_q_offset; /* current queue entry */ - struct ehea_page **queue_pages; /* array of pages belonging to queue */ - u32 qe_size; /* queue entry size */ - u32 queue_length; /* queue length allocated in bytes */ - u32 pagesize; - u32 toggle_state; /* toggle flag - per page */ - u32 reserved; /* 64 bit alignment */ -}; - -/* - * For pSeries this is a 64bit memory address where - * I/O memory is mapped into CPU address space - */ -struct h_epa { - void __iomem *addr; -}; - -struct h_epa_user { - u64 addr; -}; - -struct h_epas { - struct h_epa kernel; /* kernel space accessible resource, - set to 0 if unused */ - struct h_epa_user user; /* user space accessible resource - set to 0 if unused */ -}; - -/* - * Memory map data structures - */ -struct ehea_dir_bmap -{ - u64 ent[EHEA_MAP_ENTRIES]; -}; -struct ehea_top_bmap -{ - struct ehea_dir_bmap *dir[EHEA_MAP_ENTRIES]; -}; -struct ehea_bmap -{ - struct ehea_top_bmap *top[EHEA_MAP_ENTRIES]; -}; - -struct ehea_qp; -struct ehea_cq; -struct ehea_eq; -struct ehea_port; -struct ehea_av; - -/* - * Queue attributes passed to ehea_create_qp() - */ -struct ehea_qp_init_attr { - /* input parameter */ - u32 qp_token; /* queue token */ - u8 low_lat_rq1; - u8 signalingtype; /* cqe generation flag */ - u8 rq_count; /* num of receive queues */ - u8 eqe_gen; /* eqe generation flag */ - u16 max_nr_send_wqes; /* max number of send wqes */ - u16 max_nr_rwqes_rq1; /* max number of receive wqes */ - u16 max_nr_rwqes_rq2; - u16 max_nr_rwqes_rq3; - u8 wqe_size_enc_sq; - u8 wqe_size_enc_rq1; - u8 wqe_size_enc_rq2; - u8 wqe_size_enc_rq3; - u8 swqe_imm_data_len; /* immediate data length for swqes */ - u16 port_nr; - u16 rq2_threshold; - u16 rq3_threshold; - u64 send_cq_handle; - u64 recv_cq_handle; - u64 aff_eq_handle; - - /* output parameter */ - u32 qp_nr; - u16 act_nr_send_wqes; - u16 act_nr_rwqes_rq1; - u16 act_nr_rwqes_rq2; - u16 act_nr_rwqes_rq3; - u8 act_wqe_size_enc_sq; - u8 act_wqe_size_enc_rq1; - u8 act_wqe_size_enc_rq2; - u8 act_wqe_size_enc_rq3; - u32 nr_sq_pages; - u32 nr_rq1_pages; - u32 nr_rq2_pages; - u32 nr_rq3_pages; - u32 liobn_sq; - u32 liobn_rq1; - u32 liobn_rq2; - u32 liobn_rq3; -}; - -/* - * Event Queue attributes, passed as parameter - */ -struct ehea_eq_attr { - u32 type; - u32 max_nr_of_eqes; - u8 eqe_gen; /* generate eqe flag */ - u64 eq_handle; - u32 act_nr_of_eqes; - u32 nr_pages; - u32 ist1; /* Interrupt service token */ - u32 ist2; - u32 ist3; - u32 ist4; -}; - - -/* - * Event Queue - */ -struct ehea_eq { - struct ehea_adapter *adapter; - struct hw_queue hw_queue; - u64 fw_handle; - struct h_epas epas; - spinlock_t spinlock; - struct ehea_eq_attr attr; -}; - -/* - * HEA Queues - */ -struct ehea_qp { - struct ehea_adapter *adapter; - u64 fw_handle; /* QP handle for firmware calls */ - struct hw_queue hw_squeue; - struct hw_queue hw_rqueue1; - struct hw_queue hw_rqueue2; - struct hw_queue hw_rqueue3; - struct h_epas epas; - struct ehea_qp_init_attr init_attr; -}; - -/* - * Completion Queue attributes - */ -struct ehea_cq_attr { - /* input parameter */ - u32 max_nr_of_cqes; - u32 cq_token; - u64 eq_handle; - - /* output parameter */ - u32 act_nr_of_cqes; - u32 nr_pages; -}; - -/* - * Completion Queue - */ -struct ehea_cq { - struct ehea_adapter *adapter; - u64 fw_handle; - struct hw_queue hw_queue; - struct h_epas epas; - struct ehea_cq_attr attr; -}; - -/* - * Memory Region - */ -struct ehea_mr { - struct ehea_adapter *adapter; - u64 handle; - u64 vaddr; - u32 lkey; -}; - -/* - * Port state information - */ -struct port_stats { - int poll_receive_errors; - int queue_stopped; - int err_tcp_cksum; - int err_ip_cksum; - int err_frame_crc; -}; - -#define EHEA_IRQ_NAME_SIZE 20 - -/* - * Queue SKB Array - */ -struct ehea_q_skb_arr { - struct sk_buff **arr; /* skb array for queue */ - int len; /* array length */ - int index; /* array index */ - int os_skbs; /* rq2/rq3 only: outstanding skbs */ -}; - -/* - * Port resources - */ -struct ehea_port_res { - struct napi_struct napi; - struct port_stats p_stats; - struct ehea_mr send_mr; /* send memory region */ - struct ehea_mr recv_mr; /* receive memory region */ - struct ehea_port *port; - char int_recv_name[EHEA_IRQ_NAME_SIZE]; - char int_send_name[EHEA_IRQ_NAME_SIZE]; - struct ehea_qp *qp; - struct ehea_cq *send_cq; - struct ehea_cq *recv_cq; - struct ehea_eq *eq; - struct ehea_q_skb_arr rq1_skba; - struct ehea_q_skb_arr rq2_skba; - struct ehea_q_skb_arr rq3_skba; - struct ehea_q_skb_arr sq_skba; - int sq_skba_size; - int swqe_refill_th; - atomic_t swqe_avail; - int swqe_ll_count; - u32 swqe_id_counter; - u64 tx_packets; - u64 tx_bytes; - u64 rx_packets; - u64 rx_bytes; - int sq_restart_flag; -}; - - -#define EHEA_MAX_PORTS 16 - -#define EHEA_NUM_PORTRES_FW_HANDLES 6 /* QP handle, SendCQ handle, - RecvCQ handle, EQ handle, - SendMR handle, RecvMR handle */ -#define EHEA_NUM_PORT_FW_HANDLES 1 /* EQ handle */ -#define EHEA_NUM_ADAPTER_FW_HANDLES 2 /* MR handle, NEQ handle */ - -struct ehea_adapter { - u64 handle; - struct platform_device *ofdev; - struct ehea_port *port[EHEA_MAX_PORTS]; - struct ehea_eq *neq; /* notification event queue */ - struct tasklet_struct neq_tasklet; - struct ehea_mr mr; - u32 pd; /* protection domain */ - u64 max_mc_mac; /* max number of multicast mac addresses */ - int active_ports; - struct list_head list; -}; - - -struct ehea_mc_list { - struct list_head list; - u64 macaddr; -}; - -/* kdump support */ -struct ehea_fw_handle_entry { - u64 adh; /* Adapter Handle */ - u64 fwh; /* Firmware Handle */ -}; - -struct ehea_fw_handle_array { - struct ehea_fw_handle_entry *arr; - int num_entries; - struct mutex lock; -}; - -struct ehea_bcmc_reg_entry { - u64 adh; /* Adapter Handle */ - u32 port_id; /* Logical Port Id */ - u8 reg_type; /* Registration Type */ - u64 macaddr; -}; - -struct ehea_bcmc_reg_array { - struct ehea_bcmc_reg_entry *arr; - int num_entries; - spinlock_t lock; -}; - -#define EHEA_PORT_UP 1 -#define EHEA_PORT_DOWN 0 -#define EHEA_PHY_LINK_UP 1 -#define EHEA_PHY_LINK_DOWN 0 -#define EHEA_MAX_PORT_RES 16 -struct ehea_port { - struct ehea_adapter *adapter; /* adapter that owns this port */ - struct net_device *netdev; - struct rtnl_link_stats64 stats; - struct ehea_port_res port_res[EHEA_MAX_PORT_RES]; - struct platform_device ofdev; /* Open Firmware Device */ - struct ehea_mc_list *mc_list; /* Multicast MAC addresses */ - struct ehea_eq *qp_eq; - struct work_struct reset_task; - struct delayed_work stats_work; - struct mutex port_lock; - char int_aff_name[EHEA_IRQ_NAME_SIZE]; - int allmulti; /* Indicates IFF_ALLMULTI state */ - int promisc; /* Indicates IFF_PROMISC state */ - int num_mcs; - int resets; - unsigned long flags; - u64 mac_addr; - u32 logical_port_id; - u32 port_speed; - u32 msg_enable; - u32 sig_comp_iv; - u32 state; - u8 phy_link; - u8 full_duplex; - u8 autoneg; - u8 num_def_qps; - wait_queue_head_t swqe_avail_wq; - wait_queue_head_t restart_wq; -}; - -struct port_res_cfg { - int max_entries_rcq; - int max_entries_scq; - int max_entries_sq; - int max_entries_rq1; - int max_entries_rq2; - int max_entries_rq3; -}; - -enum ehea_flag_bits { - __EHEA_STOP_XFER, - __EHEA_DISABLE_PORT_RESET -}; - -void ehea_set_ethtool_ops(struct net_device *netdev); -int ehea_sense_port_attr(struct ehea_port *port); -int ehea_set_portspeed(struct ehea_port *port, u32 port_speed); - -#endif /* __EHEA_H__ */ diff --git a/drivers/net/ethernet/ibm/ehea/ehea_ethtool.c b/drivers/net/ethernet/ibm/ehea/ehea_ethtool.c deleted file mode 100644 index 1db5b6790a41..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea_ethtool.c +++ /dev/null @@ -1,277 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea_ethtool.c - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch - * Jan-Bernd Themann - * Thomas Klein - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include "ehea.h" -#include "ehea_phyp.h" - -static int ehea_get_link_ksettings(struct net_device *dev, - struct ethtool_link_ksettings *cmd) -{ - struct ehea_port *port = netdev_priv(dev); - u32 supported, advertising; - u32 speed; - int ret; - - ret = ehea_sense_port_attr(port); - - if (ret) - return ret; - - if (netif_carrier_ok(dev)) { - switch (port->port_speed) { - case EHEA_SPEED_10M: - speed = SPEED_10; - break; - case EHEA_SPEED_100M: - speed = SPEED_100; - break; - case EHEA_SPEED_1G: - speed = SPEED_1000; - break; - case EHEA_SPEED_10G: - speed = SPEED_10000; - break; - default: - speed = -1; - break; /* BUG */ - } - cmd->base.duplex = port->full_duplex == 1 ? - DUPLEX_FULL : DUPLEX_HALF; - } else { - speed = SPEED_UNKNOWN; - cmd->base.duplex = DUPLEX_UNKNOWN; - } - cmd->base.speed = speed; - - if (cmd->base.speed == SPEED_10000) { - supported = (SUPPORTED_10000baseT_Full | SUPPORTED_FIBRE); - advertising = (ADVERTISED_10000baseT_Full | ADVERTISED_FIBRE); - cmd->base.port = PORT_FIBRE; - } else { - supported = (SUPPORTED_1000baseT_Full | SUPPORTED_100baseT_Full - | SUPPORTED_100baseT_Half | SUPPORTED_10baseT_Full - | SUPPORTED_10baseT_Half | SUPPORTED_Autoneg - | SUPPORTED_TP); - advertising = (ADVERTISED_1000baseT_Full | ADVERTISED_Autoneg - | ADVERTISED_TP); - cmd->base.port = PORT_TP; - } - - cmd->base.autoneg = port->autoneg == 1 ? - AUTONEG_ENABLE : AUTONEG_DISABLE; - - ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.supported, - supported); - ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.advertising, - advertising); - - return 0; -} - -static int ehea_set_link_ksettings(struct net_device *dev, - const struct ethtool_link_ksettings *cmd) -{ - struct ehea_port *port = netdev_priv(dev); - int ret = 0; - u32 sp; - - if (cmd->base.autoneg == AUTONEG_ENABLE) { - sp = EHEA_SPEED_AUTONEG; - goto doit; - } - - switch (cmd->base.speed) { - case SPEED_10: - if (cmd->base.duplex == DUPLEX_FULL) - sp = H_SPEED_10M_F; - else - sp = H_SPEED_10M_H; - break; - - case SPEED_100: - if (cmd->base.duplex == DUPLEX_FULL) - sp = H_SPEED_100M_F; - else - sp = H_SPEED_100M_H; - break; - - case SPEED_1000: - if (cmd->base.duplex == DUPLEX_FULL) - sp = H_SPEED_1G_F; - else - ret = -EINVAL; - break; - - case SPEED_10000: - if (cmd->base.duplex == DUPLEX_FULL) - sp = H_SPEED_10G_F; - else - ret = -EINVAL; - break; - - default: - ret = -EINVAL; - break; - } - - if (ret) - goto out; -doit: - ret = ehea_set_portspeed(port, sp); - - if (!ret) - netdev_info(dev, - "Port speed successfully set: %dMbps %s Duplex\n", - port->port_speed, - port->full_duplex == 1 ? "Full" : "Half"); -out: - return ret; -} - -static int ehea_nway_reset(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - int ret; - - ret = ehea_set_portspeed(port, EHEA_SPEED_AUTONEG); - - if (!ret) - netdev_info(port->netdev, - "Port speed successfully set: %dMbps %s Duplex\n", - port->port_speed, - port->full_duplex == 1 ? "Full" : "Half"); - return ret; -} - -static void ehea_get_drvinfo(struct net_device *dev, - struct ethtool_drvinfo *info) -{ - strscpy(info->driver, DRV_NAME, sizeof(info->driver)); - strscpy(info->version, DRV_VERSION, sizeof(info->version)); -} - -static u32 ehea_get_msglevel(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - return port->msg_enable; -} - -static void ehea_set_msglevel(struct net_device *dev, u32 value) -{ - struct ehea_port *port = netdev_priv(dev); - port->msg_enable = value; -} - -static const char ehea_ethtool_stats_keys[][ETH_GSTRING_LEN] = { - {"sig_comp_iv"}, - {"swqe_refill_th"}, - {"port resets"}, - {"Receive errors"}, - {"TCP cksum errors"}, - {"IP cksum errors"}, - {"Frame cksum errors"}, - {"num SQ stopped"}, - {"PR0 free_swqes"}, - {"PR1 free_swqes"}, - {"PR2 free_swqes"}, - {"PR3 free_swqes"}, - {"PR4 free_swqes"}, - {"PR5 free_swqes"}, - {"PR6 free_swqes"}, - {"PR7 free_swqes"}, - {"PR8 free_swqes"}, - {"PR9 free_swqes"}, - {"PR10 free_swqes"}, - {"PR11 free_swqes"}, - {"PR12 free_swqes"}, - {"PR13 free_swqes"}, - {"PR14 free_swqes"}, - {"PR15 free_swqes"}, -}; - -static void ehea_get_strings(struct net_device *dev, u32 stringset, u8 *data) -{ - if (stringset == ETH_SS_STATS) { - memcpy(data, &ehea_ethtool_stats_keys, - sizeof(ehea_ethtool_stats_keys)); - } -} - -static int ehea_get_sset_count(struct net_device *dev, int sset) -{ - switch (sset) { - case ETH_SS_STATS: - return ARRAY_SIZE(ehea_ethtool_stats_keys); - default: - return -EOPNOTSUPP; - } -} - -static void ehea_get_ethtool_stats(struct net_device *dev, - struct ethtool_stats *stats, u64 *data) -{ - int i, k, tmp; - struct ehea_port *port = netdev_priv(dev); - - for (i = 0; i < ehea_get_sset_count(dev, ETH_SS_STATS); i++) - data[i] = 0; - i = 0; - - data[i++] = port->sig_comp_iv; - data[i++] = port->port_res[0].swqe_refill_th; - data[i++] = port->resets; - - for (k = 0, tmp = 0; k < EHEA_MAX_PORT_RES; k++) - tmp += port->port_res[k].p_stats.poll_receive_errors; - data[i++] = tmp; - - for (k = 0, tmp = 0; k < EHEA_MAX_PORT_RES; k++) - tmp += port->port_res[k].p_stats.err_tcp_cksum; - data[i++] = tmp; - - for (k = 0, tmp = 0; k < EHEA_MAX_PORT_RES; k++) - tmp += port->port_res[k].p_stats.err_ip_cksum; - data[i++] = tmp; - - for (k = 0, tmp = 0; k < EHEA_MAX_PORT_RES; k++) - tmp += port->port_res[k].p_stats.err_frame_crc; - data[i++] = tmp; - - for (k = 0, tmp = 0; k < EHEA_MAX_PORT_RES; k++) - tmp += port->port_res[k].p_stats.queue_stopped; - data[i++] = tmp; - - for (k = 0; k < 16; k++) - data[i++] = atomic_read(&port->port_res[k].swqe_avail); -} - -static const struct ethtool_ops ehea_ethtool_ops = { - .get_drvinfo = ehea_get_drvinfo, - .get_msglevel = ehea_get_msglevel, - .set_msglevel = ehea_set_msglevel, - .get_link = ethtool_op_get_link, - .get_strings = ehea_get_strings, - .get_sset_count = ehea_get_sset_count, - .get_ethtool_stats = ehea_get_ethtool_stats, - .nway_reset = ehea_nway_reset, /* Restart autonegotiation */ - .get_link_ksettings = ehea_get_link_ksettings, - .set_link_ksettings = ehea_set_link_ksettings, -}; - -void ehea_set_ethtool_ops(struct net_device *netdev) -{ - netdev->ethtool_ops = &ehea_ethtool_ops; -} diff --git a/drivers/net/ethernet/ibm/ehea/ehea_hw.h b/drivers/net/ethernet/ibm/ehea/ehea_hw.h deleted file mode 100644 index 590933a45d65..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea_hw.h +++ /dev/null @@ -1,253 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea_hw.h - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch - * Jan-Bernd Themann - * Thomas Klein - */ - -#ifndef __EHEA_HW_H__ -#define __EHEA_HW_H__ - -#define QPX_SQA_VALUE EHEA_BMASK_IBM(48, 63) -#define QPX_RQ1A_VALUE EHEA_BMASK_IBM(48, 63) -#define QPX_RQ2A_VALUE EHEA_BMASK_IBM(48, 63) -#define QPX_RQ3A_VALUE EHEA_BMASK_IBM(48, 63) - -#define QPTEMM_OFFSET(x) offsetof(struct ehea_qptemm, x) - -struct ehea_qptemm { - u64 qpx_hcr; - u64 qpx_c; - u64 qpx_herr; - u64 qpx_aer; - u64 qpx_sqa; - u64 qpx_sqc; - u64 qpx_rq1a; - u64 qpx_rq1c; - u64 qpx_st; - u64 qpx_aerr; - u64 qpx_tenure; - u64 qpx_reserved1[(0x098 - 0x058) / 8]; - u64 qpx_portp; - u64 qpx_reserved2[(0x100 - 0x0A0) / 8]; - u64 qpx_t; - u64 qpx_sqhp; - u64 qpx_sqptp; - u64 qpx_reserved3[(0x140 - 0x118) / 8]; - u64 qpx_sqwsize; - u64 qpx_reserved4[(0x170 - 0x148) / 8]; - u64 qpx_sqsize; - u64 qpx_reserved5[(0x1B0 - 0x178) / 8]; - u64 qpx_sigt; - u64 qpx_wqecnt; - u64 qpx_rq1hp; - u64 qpx_rq1ptp; - u64 qpx_rq1size; - u64 qpx_reserved6[(0x220 - 0x1D8) / 8]; - u64 qpx_rq1wsize; - u64 qpx_reserved7[(0x240 - 0x228) / 8]; - u64 qpx_pd; - u64 qpx_scqn; - u64 qpx_rcqn; - u64 qpx_aeqn; - u64 reserved49; - u64 qpx_ram; - u64 qpx_reserved8[(0x300 - 0x270) / 8]; - u64 qpx_rq2a; - u64 qpx_rq2c; - u64 qpx_rq2hp; - u64 qpx_rq2ptp; - u64 qpx_rq2size; - u64 qpx_rq2wsize; - u64 qpx_rq2th; - u64 qpx_rq3a; - u64 qpx_rq3c; - u64 qpx_rq3hp; - u64 qpx_rq3ptp; - u64 qpx_rq3size; - u64 qpx_rq3wsize; - u64 qpx_rq3th; - u64 qpx_lpn; - u64 qpx_reserved9[(0x400 - 0x378) / 8]; - u64 reserved_ext[(0x500 - 0x400) / 8]; - u64 reserved2[(0x1000 - 0x500) / 8]; -}; - -#define MRx_HCR_LPARID_VALID EHEA_BMASK_IBM(0, 0) - -#define MRMWMM_OFFSET(x) offsetof(struct ehea_mrmwmm, x) - -struct ehea_mrmwmm { - u64 mrx_hcr; - u64 mrx_c; - u64 mrx_herr; - u64 mrx_aer; - u64 mrx_pp; - u64 reserved1; - u64 reserved2; - u64 reserved3; - u64 reserved4[(0x200 - 0x40) / 8]; - u64 mrx_ctl[64]; -}; - -#define QPEDMM_OFFSET(x) offsetof(struct ehea_qpedmm, x) - -struct ehea_qpedmm { - - u64 reserved0[(0x400) / 8]; - u64 qpedx_phh; - u64 qpedx_ppsgp; - u64 qpedx_ppsgu; - u64 qpedx_ppdgp; - u64 qpedx_ppdgu; - u64 qpedx_aph; - u64 qpedx_apsgp; - u64 qpedx_apsgu; - u64 qpedx_apdgp; - u64 qpedx_apdgu; - u64 qpedx_apav; - u64 qpedx_apsav; - u64 qpedx_hcr; - u64 reserved1[4]; - u64 qpedx_rrl0; - u64 qpedx_rrrkey0; - u64 qpedx_rrva0; - u64 reserved2; - u64 qpedx_rrl1; - u64 qpedx_rrrkey1; - u64 qpedx_rrva1; - u64 reserved3; - u64 qpedx_rrl2; - u64 qpedx_rrrkey2; - u64 qpedx_rrva2; - u64 reserved4; - u64 qpedx_rrl3; - u64 qpedx_rrrkey3; - u64 qpedx_rrva3; -}; - -#define CQX_FECADDER EHEA_BMASK_IBM(32, 63) -#define CQX_FEC_CQE_CNT EHEA_BMASK_IBM(32, 63) -#define CQX_N1_GENERATE_COMP_EVENT EHEA_BMASK_IBM(0, 0) -#define CQX_EP_EVENT_PENDING EHEA_BMASK_IBM(0, 0) - -#define CQTEMM_OFFSET(x) offsetof(struct ehea_cqtemm, x) - -struct ehea_cqtemm { - u64 cqx_hcr; - u64 cqx_c; - u64 cqx_herr; - u64 cqx_aer; - u64 cqx_ptp; - u64 cqx_tp; - u64 cqx_fec; - u64 cqx_feca; - u64 cqx_ep; - u64 cqx_eq; - u64 reserved1; - u64 cqx_n0; - u64 cqx_n1; - u64 reserved2[(0x1000 - 0x60) / 8]; -}; - -#define EQTEMM_OFFSET(x) offsetof(struct ehea_eqtemm, x) - -struct ehea_eqtemm { - u64 eqx_hcr; - u64 eqx_c; - u64 eqx_herr; - u64 eqx_aer; - u64 eqx_ptp; - u64 eqx_tp; - u64 eqx_ssba; - u64 eqx_psba; - u64 eqx_cec; - u64 eqx_meql; - u64 eqx_xisbi; - u64 eqx_xisc; - u64 eqx_it; -}; - -/* - * These access functions will be changed when the dissuccsion about - * the new access methods for POWER has settled. - */ - -static inline u64 epa_load(struct h_epa epa, u32 offset) -{ - return __raw_readq((void __iomem *)(epa.addr + offset)); -} - -static inline void epa_store(struct h_epa epa, u32 offset, u64 value) -{ - __raw_writeq(value, (void __iomem *)(epa.addr + offset)); - epa_load(epa, offset); /* synchronize explicitly to eHEA */ -} - -static inline void epa_store_acc(struct h_epa epa, u32 offset, u64 value) -{ - __raw_writeq(value, (void __iomem *)(epa.addr + offset)); -} - -#define epa_store_cq(epa, offset, value)\ - epa_store(epa, CQTEMM_OFFSET(offset), value) -#define epa_load_cq(epa, offset)\ - epa_load(epa, CQTEMM_OFFSET(offset)) - -static inline void ehea_update_sqa(struct ehea_qp *qp, u16 nr_wqes) -{ - struct h_epa epa = qp->epas.kernel; - epa_store_acc(epa, QPTEMM_OFFSET(qpx_sqa), - EHEA_BMASK_SET(QPX_SQA_VALUE, nr_wqes)); -} - -static inline void ehea_update_rq3a(struct ehea_qp *qp, u16 nr_wqes) -{ - struct h_epa epa = qp->epas.kernel; - epa_store_acc(epa, QPTEMM_OFFSET(qpx_rq3a), - EHEA_BMASK_SET(QPX_RQ1A_VALUE, nr_wqes)); -} - -static inline void ehea_update_rq2a(struct ehea_qp *qp, u16 nr_wqes) -{ - struct h_epa epa = qp->epas.kernel; - epa_store_acc(epa, QPTEMM_OFFSET(qpx_rq2a), - EHEA_BMASK_SET(QPX_RQ2A_VALUE, nr_wqes)); -} - -static inline void ehea_update_rq1a(struct ehea_qp *qp, u16 nr_wqes) -{ - struct h_epa epa = qp->epas.kernel; - epa_store_acc(epa, QPTEMM_OFFSET(qpx_rq1a), - EHEA_BMASK_SET(QPX_RQ3A_VALUE, nr_wqes)); -} - -static inline void ehea_update_feca(struct ehea_cq *cq, u32 nr_cqes) -{ - struct h_epa epa = cq->epas.kernel; - epa_store_acc(epa, CQTEMM_OFFSET(cqx_feca), - EHEA_BMASK_SET(CQX_FECADDER, nr_cqes)); -} - -static inline void ehea_reset_cq_n1(struct ehea_cq *cq) -{ - struct h_epa epa = cq->epas.kernel; - epa_store_cq(epa, cqx_n1, - EHEA_BMASK_SET(CQX_N1_GENERATE_COMP_EVENT, 1)); -} - -static inline void ehea_reset_cq_ep(struct ehea_cq *my_cq) -{ - struct h_epa epa = my_cq->epas.kernel; - epa_store_acc(epa, CQTEMM_OFFSET(cqx_ep), - EHEA_BMASK_SET(CQX_EP_EVENT_PENDING, 0)); -} - -#endif /* __EHEA_HW_H__ */ diff --git a/drivers/net/ethernet/ibm/ehea/ehea_main.c b/drivers/net/ethernet/ibm/ehea/ehea_main.c deleted file mode 100644 index bfc8699a05b9..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea_main.c +++ /dev/null @@ -1,3581 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea_main.c - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch - * Jan-Bernd Themann - * Thomas Klein - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "ehea.h" -#include "ehea_qmr.h" -#include "ehea_phyp.h" - - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Christoph Raisch "); -MODULE_DESCRIPTION("IBM eServer HEA Driver"); -MODULE_VERSION(DRV_VERSION); - - -static int msg_level = -1; -static int rq1_entries = EHEA_DEF_ENTRIES_RQ1; -static int rq2_entries = EHEA_DEF_ENTRIES_RQ2; -static int rq3_entries = EHEA_DEF_ENTRIES_RQ3; -static int sq_entries = EHEA_DEF_ENTRIES_SQ; -static int use_mcs = 1; -static int prop_carrier_state; - -module_param(msg_level, int, 0); -module_param(rq1_entries, int, 0); -module_param(rq2_entries, int, 0); -module_param(rq3_entries, int, 0); -module_param(sq_entries, int, 0); -module_param(prop_carrier_state, int, 0); -module_param(use_mcs, int, 0); - -MODULE_PARM_DESC(msg_level, "msg_level"); -MODULE_PARM_DESC(prop_carrier_state, "Propagate carrier state of physical " - "port to stack. 1:yes, 0:no. Default = 0 "); -MODULE_PARM_DESC(rq3_entries, "Number of entries for Receive Queue 3 " - "[2^x - 1], x = [7..14]. Default = " - __MODULE_STRING(EHEA_DEF_ENTRIES_RQ3) ")"); -MODULE_PARM_DESC(rq2_entries, "Number of entries for Receive Queue 2 " - "[2^x - 1], x = [7..14]. Default = " - __MODULE_STRING(EHEA_DEF_ENTRIES_RQ2) ")"); -MODULE_PARM_DESC(rq1_entries, "Number of entries for Receive Queue 1 " - "[2^x - 1], x = [7..14]. Default = " - __MODULE_STRING(EHEA_DEF_ENTRIES_RQ1) ")"); -MODULE_PARM_DESC(sq_entries, " Number of entries for the Send Queue " - "[2^x - 1], x = [7..14]. Default = " - __MODULE_STRING(EHEA_DEF_ENTRIES_SQ) ")"); -MODULE_PARM_DESC(use_mcs, " Multiple receive queues, 1: enable, 0: disable, " - "Default = 1"); - -static int port_name_cnt; -static LIST_HEAD(adapter_list); -static unsigned long ehea_driver_flags; -static DEFINE_MUTEX(dlpar_mem_lock); -static struct ehea_fw_handle_array ehea_fw_handles; -static struct ehea_bcmc_reg_array ehea_bcmc_regs; - - -static int ehea_probe_adapter(struct platform_device *dev); - -static void ehea_remove(struct platform_device *dev); - -static const struct of_device_id ehea_module_device_table[] = { - { - .name = "lhea", - .compatible = "IBM,lhea", - }, - { - .type = "network", - .compatible = "IBM,lhea-ethernet", - }, - {}, -}; -MODULE_DEVICE_TABLE(of, ehea_module_device_table); - -static const struct of_device_id ehea_device_table[] = { - { - .name = "lhea", - .compatible = "IBM,lhea", - }, - {}, -}; -MODULE_DEVICE_TABLE(of, ehea_device_table); - -static struct platform_driver ehea_driver = { - .driver = { - .name = "ehea", - .owner = THIS_MODULE, - .of_match_table = ehea_device_table, - }, - .probe = ehea_probe_adapter, - .remove = ehea_remove, -}; - -void ehea_dump(void *adr, int len, char *msg) -{ - int x; - unsigned char *deb = adr; - for (x = 0; x < len; x += 16) { - pr_info("%s adr=%p ofs=%04x %016llx %016llx\n", - msg, deb, x, *((u64 *)&deb[0]), *((u64 *)&deb[8])); - deb += 16; - } -} - -static void ehea_schedule_port_reset(struct ehea_port *port) -{ - if (!test_bit(__EHEA_DISABLE_PORT_RESET, &port->flags)) - schedule_work(&port->reset_task); -} - -static void ehea_update_firmware_handles(void) -{ - struct ehea_fw_handle_entry *arr = NULL; - struct ehea_adapter *adapter; - int num_adapters = 0; - int num_ports = 0; - int num_portres = 0; - int i = 0; - int num_fw_handles, k, l; - - /* Determine number of handles */ - mutex_lock(&ehea_fw_handles.lock); - - list_for_each_entry(adapter, &adapter_list, list) { - num_adapters++; - - for (k = 0; k < EHEA_MAX_PORTS; k++) { - struct ehea_port *port = adapter->port[k]; - - if (!port || (port->state != EHEA_PORT_UP)) - continue; - - num_ports++; - num_portres += port->num_def_qps; - } - } - - num_fw_handles = num_adapters * EHEA_NUM_ADAPTER_FW_HANDLES + - num_ports * EHEA_NUM_PORT_FW_HANDLES + - num_portres * EHEA_NUM_PORTRES_FW_HANDLES; - - if (num_fw_handles) { - arr = kzalloc_objs(*arr, num_fw_handles); - if (!arr) - goto out; /* Keep the existing array */ - } else - goto out_update; - - list_for_each_entry(adapter, &adapter_list, list) { - if (num_adapters == 0) - break; - - for (k = 0; k < EHEA_MAX_PORTS; k++) { - struct ehea_port *port = adapter->port[k]; - - if (!port || (port->state != EHEA_PORT_UP) || - (num_ports == 0)) - continue; - - for (l = 0; l < port->num_def_qps; l++) { - struct ehea_port_res *pr = &port->port_res[l]; - - arr[i].adh = adapter->handle; - arr[i++].fwh = pr->qp->fw_handle; - arr[i].adh = adapter->handle; - arr[i++].fwh = pr->send_cq->fw_handle; - arr[i].adh = adapter->handle; - arr[i++].fwh = pr->recv_cq->fw_handle; - arr[i].adh = adapter->handle; - arr[i++].fwh = pr->eq->fw_handle; - arr[i].adh = adapter->handle; - arr[i++].fwh = pr->send_mr.handle; - arr[i].adh = adapter->handle; - arr[i++].fwh = pr->recv_mr.handle; - } - arr[i].adh = adapter->handle; - arr[i++].fwh = port->qp_eq->fw_handle; - num_ports--; - } - - arr[i].adh = adapter->handle; - arr[i++].fwh = adapter->neq->fw_handle; - - if (adapter->mr.handle) { - arr[i].adh = adapter->handle; - arr[i++].fwh = adapter->mr.handle; - } - num_adapters--; - } - -out_update: - kfree(ehea_fw_handles.arr); - ehea_fw_handles.arr = arr; - ehea_fw_handles.num_entries = i; -out: - mutex_unlock(&ehea_fw_handles.lock); -} - -static void ehea_update_bcmc_registrations(void) -{ - unsigned long flags; - struct ehea_bcmc_reg_entry *arr = NULL; - struct ehea_adapter *adapter; - struct ehea_mc_list *mc_entry; - int num_registrations = 0; - int i = 0; - int k; - - spin_lock_irqsave(&ehea_bcmc_regs.lock, flags); - - /* Determine number of registrations */ - list_for_each_entry(adapter, &adapter_list, list) - for (k = 0; k < EHEA_MAX_PORTS; k++) { - struct ehea_port *port = adapter->port[k]; - - if (!port || (port->state != EHEA_PORT_UP)) - continue; - - num_registrations += 2; /* Broadcast registrations */ - - list_for_each_entry(mc_entry, &port->mc_list->list,list) - num_registrations += 2; - } - - if (num_registrations) { - arr = kzalloc_objs(*arr, num_registrations, GFP_ATOMIC); - if (!arr) - goto out; /* Keep the existing array */ - } else - goto out_update; - - list_for_each_entry(adapter, &adapter_list, list) { - for (k = 0; k < EHEA_MAX_PORTS; k++) { - struct ehea_port *port = adapter->port[k]; - - if (!port || (port->state != EHEA_PORT_UP)) - continue; - - if (num_registrations == 0) - goto out_update; - - arr[i].adh = adapter->handle; - arr[i].port_id = port->logical_port_id; - arr[i].reg_type = EHEA_BCMC_BROADCAST | - EHEA_BCMC_UNTAGGED; - arr[i++].macaddr = port->mac_addr; - - arr[i].adh = adapter->handle; - arr[i].port_id = port->logical_port_id; - arr[i].reg_type = EHEA_BCMC_BROADCAST | - EHEA_BCMC_VLANID_ALL; - arr[i++].macaddr = port->mac_addr; - num_registrations -= 2; - - list_for_each_entry(mc_entry, - &port->mc_list->list, list) { - if (num_registrations == 0) - goto out_update; - - arr[i].adh = adapter->handle; - arr[i].port_id = port->logical_port_id; - arr[i].reg_type = EHEA_BCMC_MULTICAST | - EHEA_BCMC_UNTAGGED; - if (mc_entry->macaddr == 0) - arr[i].reg_type |= EHEA_BCMC_SCOPE_ALL; - arr[i++].macaddr = mc_entry->macaddr; - - arr[i].adh = adapter->handle; - arr[i].port_id = port->logical_port_id; - arr[i].reg_type = EHEA_BCMC_MULTICAST | - EHEA_BCMC_VLANID_ALL; - if (mc_entry->macaddr == 0) - arr[i].reg_type |= EHEA_BCMC_SCOPE_ALL; - arr[i++].macaddr = mc_entry->macaddr; - num_registrations -= 2; - } - } - } - -out_update: - kfree(ehea_bcmc_regs.arr); - ehea_bcmc_regs.arr = arr; - ehea_bcmc_regs.num_entries = i; -out: - spin_unlock_irqrestore(&ehea_bcmc_regs.lock, flags); -} - -static void ehea_get_stats64(struct net_device *dev, - struct rtnl_link_stats64 *stats) -{ - struct ehea_port *port = netdev_priv(dev); - u64 rx_packets = 0, tx_packets = 0, rx_bytes = 0, tx_bytes = 0; - int i; - - for (i = 0; i < port->num_def_qps; i++) { - rx_packets += port->port_res[i].rx_packets; - rx_bytes += port->port_res[i].rx_bytes; - } - - for (i = 0; i < port->num_def_qps; i++) { - tx_packets += port->port_res[i].tx_packets; - tx_bytes += port->port_res[i].tx_bytes; - } - - stats->tx_packets = tx_packets; - stats->rx_bytes = rx_bytes; - stats->tx_bytes = tx_bytes; - stats->rx_packets = rx_packets; - - stats->multicast = port->stats.multicast; - stats->rx_errors = port->stats.rx_errors; -} - -static void ehea_update_stats(struct work_struct *work) -{ - struct ehea_port *port = - container_of(work, struct ehea_port, stats_work.work); - struct net_device *dev = port->netdev; - struct rtnl_link_stats64 *stats = &port->stats; - struct hcp_ehea_port_cb2 *cb2; - u64 hret; - - cb2 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb2) { - netdev_err(dev, "No mem for cb2. Some interface statistics were not updated\n"); - goto resched; - } - - hret = ehea_h_query_ehea_port(port->adapter->handle, - port->logical_port_id, - H_PORT_CB2, H_PORT_CB2_ALL, cb2); - if (hret != H_SUCCESS) { - netdev_err(dev, "query_ehea_port failed\n"); - goto out_herr; - } - - if (netif_msg_hw(port)) - ehea_dump(cb2, sizeof(*cb2), "net_device_stats"); - - stats->multicast = cb2->rxmcp; - stats->rx_errors = cb2->rxuerr; - -out_herr: - free_page((unsigned long)cb2); -resched: - schedule_delayed_work(&port->stats_work, - round_jiffies_relative(msecs_to_jiffies(1000))); -} - -static void ehea_refill_rq1(struct ehea_port_res *pr, int index, int nr_of_wqes) -{ - struct sk_buff **skb_arr_rq1 = pr->rq1_skba.arr; - struct net_device *dev = pr->port->netdev; - int max_index_mask = pr->rq1_skba.len - 1; - int fill_wqes = pr->rq1_skba.os_skbs + nr_of_wqes; - int adder = 0; - int i; - - pr->rq1_skba.os_skbs = 0; - - if (unlikely(test_bit(__EHEA_STOP_XFER, &ehea_driver_flags))) { - if (nr_of_wqes > 0) - pr->rq1_skba.index = index; - pr->rq1_skba.os_skbs = fill_wqes; - return; - } - - for (i = 0; i < fill_wqes; i++) { - if (!skb_arr_rq1[index]) { - skb_arr_rq1[index] = netdev_alloc_skb(dev, - EHEA_L_PKT_SIZE); - if (!skb_arr_rq1[index]) { - pr->rq1_skba.os_skbs = fill_wqes - i; - break; - } - } - index--; - index &= max_index_mask; - adder++; - } - - if (adder == 0) - return; - - /* Ring doorbell */ - ehea_update_rq1a(pr->qp, adder); -} - -static void ehea_init_fill_rq1(struct ehea_port_res *pr, int nr_rq1a) -{ - struct sk_buff **skb_arr_rq1 = pr->rq1_skba.arr; - struct net_device *dev = pr->port->netdev; - int i; - - if (nr_rq1a > pr->rq1_skba.len) { - netdev_err(dev, "NR_RQ1A bigger than skb array len\n"); - return; - } - - for (i = 0; i < nr_rq1a; i++) { - skb_arr_rq1[i] = netdev_alloc_skb(dev, EHEA_L_PKT_SIZE); - if (!skb_arr_rq1[i]) - break; - } - /* Ring doorbell */ - ehea_update_rq1a(pr->qp, i - 1); -} - -static int ehea_refill_rq_def(struct ehea_port_res *pr, - struct ehea_q_skb_arr *q_skba, int rq_nr, - int num_wqes, int wqe_type, int packet_size) -{ - struct net_device *dev = pr->port->netdev; - struct ehea_qp *qp = pr->qp; - struct sk_buff **skb_arr = q_skba->arr; - struct ehea_rwqe *rwqe; - int i, index, max_index_mask, fill_wqes; - int adder = 0; - int ret = 0; - - fill_wqes = q_skba->os_skbs + num_wqes; - q_skba->os_skbs = 0; - - if (unlikely(test_bit(__EHEA_STOP_XFER, &ehea_driver_flags))) { - q_skba->os_skbs = fill_wqes; - return ret; - } - - index = q_skba->index; - max_index_mask = q_skba->len - 1; - for (i = 0; i < fill_wqes; i++) { - u64 tmp_addr; - struct sk_buff *skb; - - skb = netdev_alloc_skb_ip_align(dev, packet_size); - if (!skb) { - q_skba->os_skbs = fill_wqes - i; - if (q_skba->os_skbs == q_skba->len - 2) { - netdev_info(pr->port->netdev, - "rq%i ran dry - no mem for skb\n", - rq_nr); - ret = -ENOMEM; - } - break; - } - - skb_arr[index] = skb; - tmp_addr = ehea_map_vaddr(skb->data); - if (tmp_addr == -1) { - dev_consume_skb_any(skb); - q_skba->os_skbs = fill_wqes - i; - ret = 0; - break; - } - - rwqe = ehea_get_next_rwqe(qp, rq_nr); - rwqe->wr_id = EHEA_BMASK_SET(EHEA_WR_ID_TYPE, wqe_type) - | EHEA_BMASK_SET(EHEA_WR_ID_INDEX, index); - rwqe->sg_list[0].l_key = pr->recv_mr.lkey; - rwqe->sg_list[0].vaddr = tmp_addr; - rwqe->sg_list[0].len = packet_size; - rwqe->data_segments = 1; - - index++; - index &= max_index_mask; - adder++; - } - - q_skba->index = index; - if (adder == 0) - goto out; - - /* Ring doorbell */ - iosync(); - if (rq_nr == 2) - ehea_update_rq2a(pr->qp, adder); - else - ehea_update_rq3a(pr->qp, adder); -out: - return ret; -} - - -static int ehea_refill_rq2(struct ehea_port_res *pr, int nr_of_wqes) -{ - return ehea_refill_rq_def(pr, &pr->rq2_skba, 2, - nr_of_wqes, EHEA_RWQE2_TYPE, - EHEA_RQ2_PKT_SIZE); -} - - -static int ehea_refill_rq3(struct ehea_port_res *pr, int nr_of_wqes) -{ - return ehea_refill_rq_def(pr, &pr->rq3_skba, 3, - nr_of_wqes, EHEA_RWQE3_TYPE, - EHEA_MAX_PACKET_SIZE); -} - -static inline int ehea_check_cqe(struct ehea_cqe *cqe, int *rq_num) -{ - *rq_num = (cqe->type & EHEA_CQE_TYPE_RQ) >> 5; - if ((cqe->status & EHEA_CQE_STAT_ERR_MASK) == 0) - return 0; - if (((cqe->status & EHEA_CQE_STAT_ERR_TCP) != 0) && - (cqe->header_length == 0)) - return 0; - return -EINVAL; -} - -static inline void ehea_fill_skb(struct net_device *dev, - struct sk_buff *skb, struct ehea_cqe *cqe, - struct ehea_port_res *pr) -{ - int length = cqe->num_bytes_transfered - 4; /*remove CRC */ - - skb_put(skb, length); - skb->protocol = eth_type_trans(skb, dev); - - /* The packet was not an IPV4 packet so a complemented checksum was - calculated. The value is found in the Internet Checksum field. */ - if (cqe->status & EHEA_CQE_BLIND_CKSUM) { - skb->ip_summed = CHECKSUM_COMPLETE; - skb->csum = csum_unfold(~cqe->inet_checksum_value); - } else - skb->ip_summed = CHECKSUM_UNNECESSARY; - - skb_record_rx_queue(skb, pr - &pr->port->port_res[0]); -} - -static inline struct sk_buff *get_skb_by_index(struct sk_buff **skb_array, - int arr_len, - struct ehea_cqe *cqe) -{ - int skb_index = EHEA_BMASK_GET(EHEA_WR_ID_INDEX, cqe->wr_id); - struct sk_buff *skb; - void *pref; - int x; - - x = skb_index + 1; - x &= (arr_len - 1); - - pref = skb_array[x]; - if (pref) { - prefetchw(pref); - prefetchw(pref + EHEA_CACHE_LINE); - - pref = (skb_array[x]->data); - prefetch(pref); - prefetch(pref + EHEA_CACHE_LINE); - prefetch(pref + EHEA_CACHE_LINE * 2); - prefetch(pref + EHEA_CACHE_LINE * 3); - } - - skb = skb_array[skb_index]; - skb_array[skb_index] = NULL; - return skb; -} - -static inline struct sk_buff *get_skb_by_index_ll(struct sk_buff **skb_array, - int arr_len, int wqe_index) -{ - struct sk_buff *skb; - void *pref; - int x; - - x = wqe_index + 1; - x &= (arr_len - 1); - - pref = skb_array[x]; - if (pref) { - prefetchw(pref); - prefetchw(pref + EHEA_CACHE_LINE); - - pref = (skb_array[x]->data); - prefetchw(pref); - prefetchw(pref + EHEA_CACHE_LINE); - } - - skb = skb_array[wqe_index]; - skb_array[wqe_index] = NULL; - return skb; -} - -static int ehea_treat_poll_error(struct ehea_port_res *pr, int rq, - struct ehea_cqe *cqe, int *processed_rq2, - int *processed_rq3) -{ - struct sk_buff *skb; - - if (cqe->status & EHEA_CQE_STAT_ERR_TCP) - pr->p_stats.err_tcp_cksum++; - if (cqe->status & EHEA_CQE_STAT_ERR_IP) - pr->p_stats.err_ip_cksum++; - if (cqe->status & EHEA_CQE_STAT_ERR_CRC) - pr->p_stats.err_frame_crc++; - - if (rq == 2) { - *processed_rq2 += 1; - skb = get_skb_by_index(pr->rq2_skba.arr, pr->rq2_skba.len, cqe); - dev_kfree_skb(skb); - } else if (rq == 3) { - *processed_rq3 += 1; - skb = get_skb_by_index(pr->rq3_skba.arr, pr->rq3_skba.len, cqe); - dev_kfree_skb(skb); - } - - if (cqe->status & EHEA_CQE_STAT_FAT_ERR_MASK) { - if (netif_msg_rx_err(pr->port)) { - pr_err("Critical receive error for QP %d. Resetting port.\n", - pr->qp->init_attr.qp_nr); - ehea_dump(cqe, sizeof(*cqe), "CQE"); - } - ehea_schedule_port_reset(pr->port); - return 1; - } - - return 0; -} - -static int ehea_proc_rwqes(struct net_device *dev, - struct ehea_port_res *pr, - int budget) -{ - struct ehea_port *port = pr->port; - struct ehea_qp *qp = pr->qp; - struct ehea_cqe *cqe; - struct sk_buff *skb; - struct sk_buff **skb_arr_rq1 = pr->rq1_skba.arr; - struct sk_buff **skb_arr_rq2 = pr->rq2_skba.arr; - struct sk_buff **skb_arr_rq3 = pr->rq3_skba.arr; - int skb_arr_rq1_len = pr->rq1_skba.len; - int skb_arr_rq2_len = pr->rq2_skba.len; - int skb_arr_rq3_len = pr->rq3_skba.len; - int processed, processed_rq1, processed_rq2, processed_rq3; - u64 processed_bytes = 0; - int wqe_index, last_wqe_index, rq, port_reset; - - processed = processed_rq1 = processed_rq2 = processed_rq3 = 0; - last_wqe_index = 0; - - cqe = ehea_poll_rq1(qp, &wqe_index); - while ((processed < budget) && cqe) { - ehea_inc_rq1(qp); - processed_rq1++; - processed++; - if (netif_msg_rx_status(port)) - ehea_dump(cqe, sizeof(*cqe), "CQE"); - - last_wqe_index = wqe_index; - rmb(); - if (!ehea_check_cqe(cqe, &rq)) { - if (rq == 1) { - /* LL RQ1 */ - skb = get_skb_by_index_ll(skb_arr_rq1, - skb_arr_rq1_len, - wqe_index); - if (unlikely(!skb)) { - netif_info(port, rx_err, dev, - "LL rq1: skb=NULL\n"); - - skb = netdev_alloc_skb(dev, - EHEA_L_PKT_SIZE); - if (!skb) - break; - } - skb_copy_to_linear_data(skb, ((char *)cqe) + 64, - cqe->num_bytes_transfered - 4); - ehea_fill_skb(dev, skb, cqe, pr); - } else if (rq == 2) { - /* RQ2 */ - skb = get_skb_by_index(skb_arr_rq2, - skb_arr_rq2_len, cqe); - if (unlikely(!skb)) { - netif_err(port, rx_err, dev, - "rq2: skb=NULL\n"); - break; - } - ehea_fill_skb(dev, skb, cqe, pr); - processed_rq2++; - } else { - /* RQ3 */ - skb = get_skb_by_index(skb_arr_rq3, - skb_arr_rq3_len, cqe); - if (unlikely(!skb)) { - netif_err(port, rx_err, dev, - "rq3: skb=NULL\n"); - break; - } - ehea_fill_skb(dev, skb, cqe, pr); - processed_rq3++; - } - - processed_bytes += skb->len; - - if (cqe->status & EHEA_CQE_VLAN_TAG_XTRACT) - __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), - cqe->vlan_tag); - - napi_gro_receive(&pr->napi, skb); - } else { - pr->p_stats.poll_receive_errors++; - port_reset = ehea_treat_poll_error(pr, rq, cqe, - &processed_rq2, - &processed_rq3); - if (port_reset) - break; - } - cqe = ehea_poll_rq1(qp, &wqe_index); - } - - pr->rx_packets += processed; - pr->rx_bytes += processed_bytes; - - ehea_refill_rq1(pr, last_wqe_index, processed_rq1); - ehea_refill_rq2(pr, processed_rq2); - ehea_refill_rq3(pr, processed_rq3); - - return processed; -} - -#define SWQE_RESTART_CHECK 0xdeadbeaff00d0000ull - -static void reset_sq_restart_flag(struct ehea_port *port) -{ - int i; - - for (i = 0; i < port->num_def_qps; i++) { - struct ehea_port_res *pr = &port->port_res[i]; - pr->sq_restart_flag = 0; - } - wake_up(&port->restart_wq); -} - -static void check_sqs(struct ehea_port *port) -{ - struct ehea_swqe *swqe; - int swqe_index; - int i; - - for (i = 0; i < port->num_def_qps; i++) { - struct ehea_port_res *pr = &port->port_res[i]; - int ret; - swqe = ehea_get_swqe(pr->qp, &swqe_index); - memset(swqe, 0, SWQE_HEADER_SIZE); - atomic_dec(&pr->swqe_avail); - - swqe->tx_control |= EHEA_SWQE_PURGE; - swqe->wr_id = SWQE_RESTART_CHECK; - swqe->tx_control |= EHEA_SWQE_SIGNALLED_COMPLETION; - swqe->tx_control |= EHEA_SWQE_IMM_DATA_PRESENT; - swqe->immediate_data_length = 80; - - ehea_post_swqe(pr->qp, swqe); - - ret = wait_event_timeout(port->restart_wq, - pr->sq_restart_flag == 0, - msecs_to_jiffies(100)); - - if (!ret) { - pr_err("HW/SW queues out of sync\n"); - ehea_schedule_port_reset(pr->port); - return; - } - } -} - - -static struct ehea_cqe *ehea_proc_cqes(struct ehea_port_res *pr, int my_quota) -{ - struct sk_buff *skb; - struct ehea_cq *send_cq = pr->send_cq; - struct ehea_cqe *cqe; - int quota = my_quota; - int cqe_counter = 0; - int swqe_av = 0; - int index; - struct netdev_queue *txq = netdev_get_tx_queue(pr->port->netdev, - pr - &pr->port->port_res[0]); - - cqe = ehea_poll_cq(send_cq); - while (cqe && (quota > 0)) { - ehea_inc_cq(send_cq); - - cqe_counter++; - rmb(); - - if (cqe->wr_id == SWQE_RESTART_CHECK) { - pr->sq_restart_flag = 1; - swqe_av++; - break; - } - - if (cqe->status & EHEA_CQE_STAT_ERR_MASK) { - pr_err("Bad send completion status=0x%04X\n", - cqe->status); - - if (netif_msg_tx_err(pr->port)) - ehea_dump(cqe, sizeof(*cqe), "Send CQE"); - - if (cqe->status & EHEA_CQE_STAT_RESET_MASK) { - pr_err("Resetting port\n"); - ehea_schedule_port_reset(pr->port); - break; - } - } - - if (netif_msg_tx_done(pr->port)) - ehea_dump(cqe, sizeof(*cqe), "CQE"); - - if (likely(EHEA_BMASK_GET(EHEA_WR_ID_TYPE, cqe->wr_id) - == EHEA_SWQE2_TYPE)) { - - index = EHEA_BMASK_GET(EHEA_WR_ID_INDEX, cqe->wr_id); - skb = pr->sq_skba.arr[index]; - dev_consume_skb_any(skb); - pr->sq_skba.arr[index] = NULL; - } - - swqe_av += EHEA_BMASK_GET(EHEA_WR_ID_REFILL, cqe->wr_id); - quota--; - - cqe = ehea_poll_cq(send_cq); - } - - ehea_update_feca(send_cq, cqe_counter); - atomic_add(swqe_av, &pr->swqe_avail); - - if (unlikely(netif_tx_queue_stopped(txq) && - (atomic_read(&pr->swqe_avail) >= pr->swqe_refill_th))) { - __netif_tx_lock(txq, smp_processor_id()); - if (netif_tx_queue_stopped(txq) && - (atomic_read(&pr->swqe_avail) >= pr->swqe_refill_th)) - netif_tx_wake_queue(txq); - __netif_tx_unlock(txq); - } - - wake_up(&pr->port->swqe_avail_wq); - - return cqe; -} - -#define EHEA_POLL_MAX_CQES 65535 - -static int ehea_poll(struct napi_struct *napi, int budget) -{ - struct ehea_port_res *pr = container_of(napi, struct ehea_port_res, - napi); - struct net_device *dev = pr->port->netdev; - struct ehea_cqe *cqe; - struct ehea_cqe *cqe_skb = NULL; - int wqe_index; - int rx = 0; - - cqe_skb = ehea_proc_cqes(pr, EHEA_POLL_MAX_CQES); - rx += ehea_proc_rwqes(dev, pr, budget - rx); - - while (rx != budget) { - napi_complete(napi); - ehea_reset_cq_ep(pr->recv_cq); - ehea_reset_cq_ep(pr->send_cq); - ehea_reset_cq_n1(pr->recv_cq); - ehea_reset_cq_n1(pr->send_cq); - rmb(); - cqe = ehea_poll_rq1(pr->qp, &wqe_index); - cqe_skb = ehea_poll_cq(pr->send_cq); - - if (!cqe && !cqe_skb) - return rx; - - if (!napi_schedule(napi)) - return rx; - - cqe_skb = ehea_proc_cqes(pr, EHEA_POLL_MAX_CQES); - rx += ehea_proc_rwqes(dev, pr, budget - rx); - } - - return rx; -} - -static irqreturn_t ehea_recv_irq_handler(int irq, void *param) -{ - struct ehea_port_res *pr = param; - - napi_schedule(&pr->napi); - - return IRQ_HANDLED; -} - -static irqreturn_t ehea_qp_aff_irq_handler(int irq, void *param) -{ - struct ehea_port *port = param; - struct ehea_eqe *eqe; - struct ehea_qp *qp; - u32 qp_token; - u64 resource_type, aer, aerr; - int reset_port = 0; - - eqe = ehea_poll_eq(port->qp_eq); - - while (eqe) { - qp_token = EHEA_BMASK_GET(EHEA_EQE_QP_TOKEN, eqe->entry); - pr_err("QP aff_err: entry=0x%llx, token=0x%x\n", - eqe->entry, qp_token); - - qp = port->port_res[qp_token].qp; - - resource_type = ehea_error_data(port->adapter, qp->fw_handle, - &aer, &aerr); - - if (resource_type == EHEA_AER_RESTYPE_QP) { - if ((aer & EHEA_AER_RESET_MASK) || - (aerr & EHEA_AERR_RESET_MASK)) - reset_port = 1; - } else - reset_port = 1; /* Reset in case of CQ or EQ error */ - - eqe = ehea_poll_eq(port->qp_eq); - } - - if (reset_port) { - pr_err("Resetting port\n"); - ehea_schedule_port_reset(port); - } - - return IRQ_HANDLED; -} - -static struct ehea_port *ehea_get_port(struct ehea_adapter *adapter, - int logical_port) -{ - int i; - - for (i = 0; i < EHEA_MAX_PORTS; i++) - if (adapter->port[i]) - if (adapter->port[i]->logical_port_id == logical_port) - return adapter->port[i]; - return NULL; -} - -int ehea_sense_port_attr(struct ehea_port *port) -{ - int ret; - u64 hret; - struct hcp_ehea_port_cb0 *cb0; - - /* may be called via ehea_neq_tasklet() */ - cb0 = (void *)get_zeroed_page(GFP_ATOMIC); - if (!cb0) { - pr_err("no mem for cb0\n"); - ret = -ENOMEM; - goto out; - } - - hret = ehea_h_query_ehea_port(port->adapter->handle, - port->logical_port_id, H_PORT_CB0, - EHEA_BMASK_SET(H_PORT_CB0_ALL, 0xFFFF), - cb0); - if (hret != H_SUCCESS) { - ret = -EIO; - goto out_free; - } - - /* MAC address */ - port->mac_addr = cb0->port_mac_addr << 16; - - if (!is_valid_ether_addr((u8 *)&port->mac_addr)) { - ret = -EADDRNOTAVAIL; - goto out_free; - } - - /* Port speed */ - switch (cb0->port_speed) { - case H_SPEED_10M_H: - port->port_speed = EHEA_SPEED_10M; - port->full_duplex = 0; - break; - case H_SPEED_10M_F: - port->port_speed = EHEA_SPEED_10M; - port->full_duplex = 1; - break; - case H_SPEED_100M_H: - port->port_speed = EHEA_SPEED_100M; - port->full_duplex = 0; - break; - case H_SPEED_100M_F: - port->port_speed = EHEA_SPEED_100M; - port->full_duplex = 1; - break; - case H_SPEED_1G_F: - port->port_speed = EHEA_SPEED_1G; - port->full_duplex = 1; - break; - case H_SPEED_10G_F: - port->port_speed = EHEA_SPEED_10G; - port->full_duplex = 1; - break; - default: - port->port_speed = 0; - port->full_duplex = 0; - break; - } - - port->autoneg = 1; - port->num_mcs = cb0->num_default_qps; - - /* Number of default QPs */ - if (use_mcs) - port->num_def_qps = cb0->num_default_qps; - else - port->num_def_qps = 1; - - if (!port->num_def_qps) { - ret = -EINVAL; - goto out_free; - } - - ret = 0; -out_free: - if (ret || netif_msg_probe(port)) - ehea_dump(cb0, sizeof(*cb0), "ehea_sense_port_attr"); - free_page((unsigned long)cb0); -out: - return ret; -} - -int ehea_set_portspeed(struct ehea_port *port, u32 port_speed) -{ - struct hcp_ehea_port_cb4 *cb4; - u64 hret; - int ret = 0; - - cb4 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb4) { - pr_err("no mem for cb4\n"); - ret = -ENOMEM; - goto out; - } - - cb4->port_speed = port_speed; - - netif_carrier_off(port->netdev); - - hret = ehea_h_modify_ehea_port(port->adapter->handle, - port->logical_port_id, - H_PORT_CB4, H_PORT_CB4_SPEED, cb4); - if (hret == H_SUCCESS) { - port->autoneg = port_speed == EHEA_SPEED_AUTONEG ? 1 : 0; - - hret = ehea_h_query_ehea_port(port->adapter->handle, - port->logical_port_id, - H_PORT_CB4, H_PORT_CB4_SPEED, - cb4); - if (hret == H_SUCCESS) { - switch (cb4->port_speed) { - case H_SPEED_10M_H: - port->port_speed = EHEA_SPEED_10M; - port->full_duplex = 0; - break; - case H_SPEED_10M_F: - port->port_speed = EHEA_SPEED_10M; - port->full_duplex = 1; - break; - case H_SPEED_100M_H: - port->port_speed = EHEA_SPEED_100M; - port->full_duplex = 0; - break; - case H_SPEED_100M_F: - port->port_speed = EHEA_SPEED_100M; - port->full_duplex = 1; - break; - case H_SPEED_1G_F: - port->port_speed = EHEA_SPEED_1G; - port->full_duplex = 1; - break; - case H_SPEED_10G_F: - port->port_speed = EHEA_SPEED_10G; - port->full_duplex = 1; - break; - default: - port->port_speed = 0; - port->full_duplex = 0; - break; - } - } else { - pr_err("Failed sensing port speed\n"); - ret = -EIO; - } - } else { - if (hret == H_AUTHORITY) { - pr_info("Hypervisor denied setting port speed\n"); - ret = -EPERM; - } else { - ret = -EIO; - pr_err("Failed setting port speed\n"); - } - } - if (!prop_carrier_state || (port->phy_link == EHEA_PHY_LINK_UP)) - netif_carrier_on(port->netdev); - - free_page((unsigned long)cb4); -out: - return ret; -} - -static void ehea_parse_eqe(struct ehea_adapter *adapter, u64 eqe) -{ - int ret; - u8 ec; - u8 portnum; - struct ehea_port *port; - struct net_device *dev; - - ec = EHEA_BMASK_GET(NEQE_EVENT_CODE, eqe); - portnum = EHEA_BMASK_GET(NEQE_PORTNUM, eqe); - port = ehea_get_port(adapter, portnum); - if (!port) { - netdev_err(NULL, "unknown portnum %x\n", portnum); - return; - } - dev = port->netdev; - - switch (ec) { - case EHEA_EC_PORTSTATE_CHG: /* port state change */ - - if (EHEA_BMASK_GET(NEQE_PORT_UP, eqe)) { - if (!netif_carrier_ok(dev)) { - ret = ehea_sense_port_attr(port); - if (ret) { - netdev_err(dev, "failed resensing port attributes\n"); - break; - } - - netif_info(port, link, dev, - "Logical port up: %dMbps %s Duplex\n", - port->port_speed, - port->full_duplex == 1 ? - "Full" : "Half"); - - netif_carrier_on(dev); - netif_wake_queue(dev); - } - } else - if (netif_carrier_ok(dev)) { - netif_info(port, link, dev, - "Logical port down\n"); - netif_carrier_off(dev); - netif_tx_disable(dev); - } - - if (EHEA_BMASK_GET(NEQE_EXTSWITCH_PORT_UP, eqe)) { - port->phy_link = EHEA_PHY_LINK_UP; - netif_info(port, link, dev, - "Physical port up\n"); - if (prop_carrier_state) - netif_carrier_on(dev); - } else { - port->phy_link = EHEA_PHY_LINK_DOWN; - netif_info(port, link, dev, - "Physical port down\n"); - if (prop_carrier_state) - netif_carrier_off(dev); - } - - if (EHEA_BMASK_GET(NEQE_EXTSWITCH_PRIMARY, eqe)) - netdev_info(dev, - "External switch port is primary port\n"); - else - netdev_info(dev, - "External switch port is backup port\n"); - - break; - case EHEA_EC_ADAPTER_MALFUNC: - netdev_err(dev, "Adapter malfunction\n"); - break; - case EHEA_EC_PORT_MALFUNC: - netdev_info(dev, "Port malfunction\n"); - netif_carrier_off(dev); - netif_tx_disable(dev); - break; - default: - netdev_err(dev, "unknown event code %x, eqe=0x%llX\n", ec, eqe); - break; - } -} - -static void ehea_neq_tasklet(struct tasklet_struct *t) -{ - struct ehea_adapter *adapter = from_tasklet(adapter, t, neq_tasklet); - struct ehea_eqe *eqe; - u64 event_mask; - - eqe = ehea_poll_eq(adapter->neq); - pr_debug("eqe=%p\n", eqe); - - while (eqe) { - pr_debug("*eqe=%lx\n", (unsigned long) eqe->entry); - ehea_parse_eqe(adapter, eqe->entry); - eqe = ehea_poll_eq(adapter->neq); - pr_debug("next eqe=%p\n", eqe); - } - - event_mask = EHEA_BMASK_SET(NELR_PORTSTATE_CHG, 1) - | EHEA_BMASK_SET(NELR_ADAPTER_MALFUNC, 1) - | EHEA_BMASK_SET(NELR_PORT_MALFUNC, 1); - - ehea_h_reset_events(adapter->handle, - adapter->neq->fw_handle, event_mask); -} - -static irqreturn_t ehea_interrupt_neq(int irq, void *param) -{ - struct ehea_adapter *adapter = param; - tasklet_hi_schedule(&adapter->neq_tasklet); - return IRQ_HANDLED; -} - - -static int ehea_fill_port_res(struct ehea_port_res *pr) -{ - int ret; - struct ehea_qp_init_attr *init_attr = &pr->qp->init_attr; - - ehea_init_fill_rq1(pr, pr->rq1_skba.len); - - ret = ehea_refill_rq2(pr, init_attr->act_nr_rwqes_rq2 - 1); - - ret |= ehea_refill_rq3(pr, init_attr->act_nr_rwqes_rq3 - 1); - - return ret; -} - -static int ehea_reg_interrupts(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_port_res *pr; - int i, ret; - - - snprintf(port->int_aff_name, EHEA_IRQ_NAME_SIZE - 1, "%s-aff", - dev->name); - - ret = ibmebus_request_irq(port->qp_eq->attr.ist1, - ehea_qp_aff_irq_handler, - 0, port->int_aff_name, port); - if (ret) { - netdev_err(dev, "failed registering irq for qp_aff_irq_handler:ist=%X\n", - port->qp_eq->attr.ist1); - goto out_free_qpeq; - } - - netif_info(port, ifup, dev, - "irq_handle 0x%X for function qp_aff_irq_handler registered\n", - port->qp_eq->attr.ist1); - - - for (i = 0; i < port->num_def_qps; i++) { - pr = &port->port_res[i]; - snprintf(pr->int_send_name, EHEA_IRQ_NAME_SIZE - 1, - "%s-queue%d", dev->name, i); - ret = ibmebus_request_irq(pr->eq->attr.ist1, - ehea_recv_irq_handler, - 0, pr->int_send_name, pr); - if (ret) { - netdev_err(dev, "failed registering irq for ehea_queue port_res_nr:%d, ist=%X\n", - i, pr->eq->attr.ist1); - goto out_free_req; - } - netif_info(port, ifup, dev, - "irq_handle 0x%X for function ehea_queue_int %d registered\n", - pr->eq->attr.ist1, i); - } -out: - return ret; - - -out_free_req: - while (--i >= 0) { - u32 ist = port->port_res[i].eq->attr.ist1; - ibmebus_free_irq(ist, &port->port_res[i]); - } - -out_free_qpeq: - ibmebus_free_irq(port->qp_eq->attr.ist1, port); - i = port->num_def_qps; - - goto out; - -} - -static void ehea_free_interrupts(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_port_res *pr; - int i; - - /* send */ - - for (i = 0; i < port->num_def_qps; i++) { - pr = &port->port_res[i]; - ibmebus_free_irq(pr->eq->attr.ist1, pr); - netif_info(port, intr, dev, - "free send irq for res %d with handle 0x%X\n", - i, pr->eq->attr.ist1); - } - - /* associated events */ - ibmebus_free_irq(port->qp_eq->attr.ist1, port); - netif_info(port, intr, dev, - "associated event interrupt for handle 0x%X freed\n", - port->qp_eq->attr.ist1); -} - -static int ehea_configure_port(struct ehea_port *port) -{ - int ret, i; - u64 hret, mask; - struct hcp_ehea_port_cb0 *cb0; - - ret = -ENOMEM; - cb0 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb0) - goto out; - - cb0->port_rc = EHEA_BMASK_SET(PXLY_RC_VALID, 1) - | EHEA_BMASK_SET(PXLY_RC_IP_CHKSUM, 1) - | EHEA_BMASK_SET(PXLY_RC_TCP_UDP_CHKSUM, 1) - | EHEA_BMASK_SET(PXLY_RC_VLAN_XTRACT, 1) - | EHEA_BMASK_SET(PXLY_RC_VLAN_TAG_FILTER, - PXLY_RC_VLAN_FILTER) - | EHEA_BMASK_SET(PXLY_RC_JUMBO_FRAME, 1); - - for (i = 0; i < port->num_mcs; i++) - if (use_mcs) - cb0->default_qpn_arr[i] = - port->port_res[i].qp->init_attr.qp_nr; - else - cb0->default_qpn_arr[i] = - port->port_res[0].qp->init_attr.qp_nr; - - if (netif_msg_ifup(port)) - ehea_dump(cb0, sizeof(*cb0), "ehea_configure_port"); - - mask = EHEA_BMASK_SET(H_PORT_CB0_PRC, 1) - | EHEA_BMASK_SET(H_PORT_CB0_DEFQPNARRAY, 1); - - hret = ehea_h_modify_ehea_port(port->adapter->handle, - port->logical_port_id, - H_PORT_CB0, mask, cb0); - ret = -EIO; - if (hret != H_SUCCESS) - goto out_free; - - ret = 0; - -out_free: - free_page((unsigned long)cb0); -out: - return ret; -} - -static int ehea_gen_smrs(struct ehea_port_res *pr) -{ - int ret; - struct ehea_adapter *adapter = pr->port->adapter; - - ret = ehea_gen_smr(adapter, &adapter->mr, &pr->send_mr); - if (ret) - goto out; - - ret = ehea_gen_smr(adapter, &adapter->mr, &pr->recv_mr); - if (ret) - goto out_free; - - return 0; - -out_free: - ehea_rem_mr(&pr->send_mr); -out: - pr_err("Generating SMRS failed\n"); - return -EIO; -} - -static int ehea_rem_smrs(struct ehea_port_res *pr) -{ - if ((ehea_rem_mr(&pr->send_mr)) || - (ehea_rem_mr(&pr->recv_mr))) - return -EIO; - else - return 0; -} - -static int ehea_init_q_skba(struct ehea_q_skb_arr *q_skba, int max_q_entries) -{ - int arr_size = sizeof(void *) * max_q_entries; - - q_skba->arr = vzalloc(arr_size); - if (!q_skba->arr) - return -ENOMEM; - - q_skba->len = max_q_entries; - q_skba->index = 0; - q_skba->os_skbs = 0; - - return 0; -} - -static int ehea_init_port_res(struct ehea_port *port, struct ehea_port_res *pr, - struct port_res_cfg *pr_cfg, int queue_token) -{ - struct ehea_adapter *adapter = port->adapter; - enum ehea_eq_type eq_type = EHEA_EQ; - struct ehea_qp_init_attr *init_attr = NULL; - int ret = -EIO; - u64 tx_bytes, rx_bytes, tx_packets, rx_packets; - - tx_bytes = pr->tx_bytes; - tx_packets = pr->tx_packets; - rx_bytes = pr->rx_bytes; - rx_packets = pr->rx_packets; - - memset(pr, 0, sizeof(struct ehea_port_res)); - - pr->tx_bytes = tx_bytes; - pr->tx_packets = tx_packets; - pr->rx_bytes = rx_bytes; - pr->rx_packets = rx_packets; - - pr->port = port; - - pr->eq = ehea_create_eq(adapter, eq_type, EHEA_MAX_ENTRIES_EQ, 0); - if (!pr->eq) { - pr_err("create_eq failed (eq)\n"); - goto out_free; - } - - pr->recv_cq = ehea_create_cq(adapter, pr_cfg->max_entries_rcq, - pr->eq->fw_handle, - port->logical_port_id); - if (!pr->recv_cq) { - pr_err("create_cq failed (cq_recv)\n"); - goto out_free; - } - - pr->send_cq = ehea_create_cq(adapter, pr_cfg->max_entries_scq, - pr->eq->fw_handle, - port->logical_port_id); - if (!pr->send_cq) { - pr_err("create_cq failed (cq_send)\n"); - goto out_free; - } - - if (netif_msg_ifup(port)) - pr_info("Send CQ: act_nr_cqes=%d, Recv CQ: act_nr_cqes=%d\n", - pr->send_cq->attr.act_nr_of_cqes, - pr->recv_cq->attr.act_nr_of_cqes); - - init_attr = kzalloc_obj(*init_attr); - if (!init_attr) { - ret = -ENOMEM; - pr_err("no mem for ehea_qp_init_attr\n"); - goto out_free; - } - - init_attr->low_lat_rq1 = 1; - init_attr->signalingtype = 1; /* generate CQE if specified in WQE */ - init_attr->rq_count = 3; - init_attr->qp_token = queue_token; - init_attr->max_nr_send_wqes = pr_cfg->max_entries_sq; - init_attr->max_nr_rwqes_rq1 = pr_cfg->max_entries_rq1; - init_attr->max_nr_rwqes_rq2 = pr_cfg->max_entries_rq2; - init_attr->max_nr_rwqes_rq3 = pr_cfg->max_entries_rq3; - init_attr->wqe_size_enc_sq = EHEA_SG_SQ; - init_attr->wqe_size_enc_rq1 = EHEA_SG_RQ1; - init_attr->wqe_size_enc_rq2 = EHEA_SG_RQ2; - init_attr->wqe_size_enc_rq3 = EHEA_SG_RQ3; - init_attr->rq2_threshold = EHEA_RQ2_THRESHOLD; - init_attr->rq3_threshold = EHEA_RQ3_THRESHOLD; - init_attr->port_nr = port->logical_port_id; - init_attr->send_cq_handle = pr->send_cq->fw_handle; - init_attr->recv_cq_handle = pr->recv_cq->fw_handle; - init_attr->aff_eq_handle = port->qp_eq->fw_handle; - - pr->qp = ehea_create_qp(adapter, adapter->pd, init_attr); - if (!pr->qp) { - pr_err("create_qp failed\n"); - ret = -EIO; - goto out_free; - } - - if (netif_msg_ifup(port)) - pr_info("QP: qp_nr=%d\n act_nr_snd_wqe=%d\n nr_rwqe_rq1=%d\n nr_rwqe_rq2=%d\n nr_rwqe_rq3=%d\n", - init_attr->qp_nr, - init_attr->act_nr_send_wqes, - init_attr->act_nr_rwqes_rq1, - init_attr->act_nr_rwqes_rq2, - init_attr->act_nr_rwqes_rq3); - - pr->sq_skba_size = init_attr->act_nr_send_wqes + 1; - - ret = ehea_init_q_skba(&pr->sq_skba, pr->sq_skba_size); - ret |= ehea_init_q_skba(&pr->rq1_skba, init_attr->act_nr_rwqes_rq1 + 1); - ret |= ehea_init_q_skba(&pr->rq2_skba, init_attr->act_nr_rwqes_rq2 + 1); - ret |= ehea_init_q_skba(&pr->rq3_skba, init_attr->act_nr_rwqes_rq3 + 1); - if (ret) - goto out_free; - - pr->swqe_refill_th = init_attr->act_nr_send_wqes / 10; - if (ehea_gen_smrs(pr) != 0) { - ret = -EIO; - goto out_free; - } - - atomic_set(&pr->swqe_avail, init_attr->act_nr_send_wqes - 1); - - kfree(init_attr); - - netif_napi_add(pr->port->netdev, &pr->napi, ehea_poll); - - ret = 0; - goto out; - -out_free: - kfree(init_attr); - vfree(pr->sq_skba.arr); - vfree(pr->rq1_skba.arr); - vfree(pr->rq2_skba.arr); - vfree(pr->rq3_skba.arr); - ehea_destroy_qp(pr->qp); - ehea_destroy_cq(pr->send_cq); - ehea_destroy_cq(pr->recv_cq); - ehea_destroy_eq(pr->eq); -out: - return ret; -} - -static int ehea_clean_portres(struct ehea_port *port, struct ehea_port_res *pr) -{ - int ret, i; - - if (pr->qp) - netif_napi_del(&pr->napi); - - ret = ehea_destroy_qp(pr->qp); - - if (!ret) { - ehea_destroy_cq(pr->send_cq); - ehea_destroy_cq(pr->recv_cq); - ehea_destroy_eq(pr->eq); - - for (i = 0; i < pr->rq1_skba.len; i++) - dev_kfree_skb(pr->rq1_skba.arr[i]); - - for (i = 0; i < pr->rq2_skba.len; i++) - dev_kfree_skb(pr->rq2_skba.arr[i]); - - for (i = 0; i < pr->rq3_skba.len; i++) - dev_kfree_skb(pr->rq3_skba.arr[i]); - - for (i = 0; i < pr->sq_skba.len; i++) - dev_kfree_skb(pr->sq_skba.arr[i]); - - vfree(pr->rq1_skba.arr); - vfree(pr->rq2_skba.arr); - vfree(pr->rq3_skba.arr); - vfree(pr->sq_skba.arr); - ret = ehea_rem_smrs(pr); - } - return ret; -} - -static void write_swqe2_immediate(struct sk_buff *skb, struct ehea_swqe *swqe, - u32 lkey) -{ - int skb_data_size = skb_headlen(skb); - u8 *imm_data = &swqe->u.immdata_desc.immediate_data[0]; - struct ehea_vsgentry *sg1entry = &swqe->u.immdata_desc.sg_entry; - unsigned int immediate_len = SWQE2_MAX_IMM; - - swqe->descriptors = 0; - - if (skb_is_gso(skb)) { - swqe->tx_control |= EHEA_SWQE_TSO; - swqe->mss = skb_shinfo(skb)->gso_size; - /* - * For TSO packets we only copy the headers into the - * immediate area. - */ - immediate_len = skb_tcp_all_headers(skb); - } - - if (skb_is_gso(skb) || skb_data_size >= SWQE2_MAX_IMM) { - skb_copy_from_linear_data(skb, imm_data, immediate_len); - swqe->immediate_data_length = immediate_len; - - if (skb_data_size > immediate_len) { - sg1entry->l_key = lkey; - sg1entry->len = skb_data_size - immediate_len; - sg1entry->vaddr = - ehea_map_vaddr(skb->data + immediate_len); - swqe->descriptors++; - } - } else { - skb_copy_from_linear_data(skb, imm_data, skb_data_size); - swqe->immediate_data_length = skb_data_size; - } -} - -static inline void write_swqe2_data(struct sk_buff *skb, struct net_device *dev, - struct ehea_swqe *swqe, u32 lkey) -{ - struct ehea_vsgentry *sg_list, *sg1entry, *sgentry; - skb_frag_t *frag; - int nfrags, sg1entry_contains_frag_data, i; - - nfrags = skb_shinfo(skb)->nr_frags; - sg1entry = &swqe->u.immdata_desc.sg_entry; - sg_list = (struct ehea_vsgentry *)&swqe->u.immdata_desc.sg_list; - sg1entry_contains_frag_data = 0; - - write_swqe2_immediate(skb, swqe, lkey); - - /* write descriptors */ - if (nfrags > 0) { - if (swqe->descriptors == 0) { - /* sg1entry not yet used */ - frag = &skb_shinfo(skb)->frags[0]; - - /* copy sg1entry data */ - sg1entry->l_key = lkey; - sg1entry->len = skb_frag_size(frag); - sg1entry->vaddr = - ehea_map_vaddr(skb_frag_address(frag)); - swqe->descriptors++; - sg1entry_contains_frag_data = 1; - } - - for (i = sg1entry_contains_frag_data; i < nfrags; i++) { - - frag = &skb_shinfo(skb)->frags[i]; - sgentry = &sg_list[i - sg1entry_contains_frag_data]; - - sgentry->l_key = lkey; - sgentry->len = skb_frag_size(frag); - sgentry->vaddr = ehea_map_vaddr(skb_frag_address(frag)); - swqe->descriptors++; - } - } -} - -static int ehea_broadcast_reg_helper(struct ehea_port *port, u32 hcallid) -{ - int ret = 0; - u64 hret; - u8 reg_type; - - /* De/Register untagged packets */ - reg_type = EHEA_BCMC_BROADCAST | EHEA_BCMC_UNTAGGED; - hret = ehea_h_reg_dereg_bcmc(port->adapter->handle, - port->logical_port_id, - reg_type, port->mac_addr, 0, hcallid); - if (hret != H_SUCCESS) { - pr_err("%sregistering bc address failed (tagged)\n", - hcallid == H_REG_BCMC ? "" : "de"); - ret = -EIO; - goto out_herr; - } - - /* De/Register VLAN packets */ - reg_type = EHEA_BCMC_BROADCAST | EHEA_BCMC_VLANID_ALL; - hret = ehea_h_reg_dereg_bcmc(port->adapter->handle, - port->logical_port_id, - reg_type, port->mac_addr, 0, hcallid); - if (hret != H_SUCCESS) { - pr_err("%sregistering bc address failed (vlan)\n", - hcallid == H_REG_BCMC ? "" : "de"); - ret = -EIO; - } -out_herr: - return ret; -} - -static int ehea_set_mac_addr(struct net_device *dev, void *sa) -{ - struct ehea_port *port = netdev_priv(dev); - struct sockaddr *mac_addr = sa; - struct hcp_ehea_port_cb0 *cb0; - int ret; - u64 hret; - - if (!is_valid_ether_addr(mac_addr->sa_data)) { - ret = -EADDRNOTAVAIL; - goto out; - } - - cb0 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb0) { - pr_err("no mem for cb0\n"); - ret = -ENOMEM; - goto out; - } - - memcpy(&(cb0->port_mac_addr), &(mac_addr->sa_data[0]), ETH_ALEN); - - cb0->port_mac_addr = cb0->port_mac_addr >> 16; - - hret = ehea_h_modify_ehea_port(port->adapter->handle, - port->logical_port_id, H_PORT_CB0, - EHEA_BMASK_SET(H_PORT_CB0_MAC, 1), cb0); - if (hret != H_SUCCESS) { - ret = -EIO; - goto out_free; - } - - eth_hw_addr_set(dev, mac_addr->sa_data); - - /* Deregister old MAC in pHYP */ - if (port->state == EHEA_PORT_UP) { - ret = ehea_broadcast_reg_helper(port, H_DEREG_BCMC); - if (ret) - goto out_upregs; - } - - port->mac_addr = cb0->port_mac_addr << 16; - - /* Register new MAC in pHYP */ - if (port->state == EHEA_PORT_UP) { - ret = ehea_broadcast_reg_helper(port, H_REG_BCMC); - if (ret) - goto out_upregs; - } - - ret = 0; - -out_upregs: - ehea_update_bcmc_registrations(); -out_free: - free_page((unsigned long)cb0); -out: - return ret; -} - -static void ehea_promiscuous_error(u64 hret, int enable) -{ - if (hret == H_AUTHORITY) - pr_info("Hypervisor denied %sabling promiscuous mode\n", - enable == 1 ? "en" : "dis"); - else - pr_err("failed %sabling promiscuous mode\n", - enable == 1 ? "en" : "dis"); -} - -static void ehea_promiscuous(struct net_device *dev, int enable) -{ - struct ehea_port *port = netdev_priv(dev); - struct hcp_ehea_port_cb7 *cb7; - u64 hret; - - if (enable == port->promisc) - return; - - cb7 = (void *)get_zeroed_page(GFP_ATOMIC); - if (!cb7) { - pr_err("no mem for cb7\n"); - goto out; - } - - /* Modify Pxs_DUCQPN in CB7 */ - cb7->def_uc_qpn = enable == 1 ? port->port_res[0].qp->fw_handle : 0; - - hret = ehea_h_modify_ehea_port(port->adapter->handle, - port->logical_port_id, - H_PORT_CB7, H_PORT_CB7_DUCQPN, cb7); - if (hret) { - ehea_promiscuous_error(hret, enable); - goto out; - } - - port->promisc = enable; -out: - free_page((unsigned long)cb7); -} - -static u64 ehea_multicast_reg_helper(struct ehea_port *port, u64 mc_mac_addr, - u32 hcallid) -{ - u64 hret; - u8 reg_type; - - reg_type = EHEA_BCMC_MULTICAST | EHEA_BCMC_UNTAGGED; - if (mc_mac_addr == 0) - reg_type |= EHEA_BCMC_SCOPE_ALL; - - hret = ehea_h_reg_dereg_bcmc(port->adapter->handle, - port->logical_port_id, - reg_type, mc_mac_addr, 0, hcallid); - if (hret) - goto out; - - reg_type = EHEA_BCMC_MULTICAST | EHEA_BCMC_VLANID_ALL; - if (mc_mac_addr == 0) - reg_type |= EHEA_BCMC_SCOPE_ALL; - - hret = ehea_h_reg_dereg_bcmc(port->adapter->handle, - port->logical_port_id, - reg_type, mc_mac_addr, 0, hcallid); -out: - return hret; -} - -static int ehea_drop_multicast_list(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_mc_list *mc_entry = port->mc_list; - struct list_head *pos; - struct list_head *temp; - int ret = 0; - u64 hret; - - list_for_each_safe(pos, temp, &(port->mc_list->list)) { - mc_entry = list_entry(pos, struct ehea_mc_list, list); - - hret = ehea_multicast_reg_helper(port, mc_entry->macaddr, - H_DEREG_BCMC); - if (hret) { - pr_err("failed deregistering mcast MAC\n"); - ret = -EIO; - } - - list_del(pos); - kfree(mc_entry); - } - return ret; -} - -static void ehea_allmulti(struct net_device *dev, int enable) -{ - struct ehea_port *port = netdev_priv(dev); - u64 hret; - - if (!port->allmulti) { - if (enable) { - /* Enable ALLMULTI */ - ehea_drop_multicast_list(dev); - hret = ehea_multicast_reg_helper(port, 0, H_REG_BCMC); - if (!hret) - port->allmulti = 1; - else - netdev_err(dev, - "failed enabling IFF_ALLMULTI\n"); - } - } else { - if (!enable) { - /* Disable ALLMULTI */ - hret = ehea_multicast_reg_helper(port, 0, H_DEREG_BCMC); - if (!hret) - port->allmulti = 0; - else - netdev_err(dev, - "failed disabling IFF_ALLMULTI\n"); - } - } -} - -static void ehea_add_multicast_entry(struct ehea_port *port, u8 *mc_mac_addr) -{ - struct ehea_mc_list *ehea_mcl_entry; - u64 hret; - - ehea_mcl_entry = kzalloc_obj(*ehea_mcl_entry, GFP_ATOMIC); - if (!ehea_mcl_entry) - return; - - INIT_LIST_HEAD(&ehea_mcl_entry->list); - - memcpy(&ehea_mcl_entry->macaddr, mc_mac_addr, ETH_ALEN); - - hret = ehea_multicast_reg_helper(port, ehea_mcl_entry->macaddr, - H_REG_BCMC); - if (!hret) - list_add(&ehea_mcl_entry->list, &port->mc_list->list); - else { - pr_err("failed registering mcast MAC\n"); - kfree(ehea_mcl_entry); - } -} - -static void ehea_set_multicast_list(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - struct netdev_hw_addr *ha; - int ret; - - ehea_promiscuous(dev, !!(dev->flags & IFF_PROMISC)); - - if (dev->flags & IFF_ALLMULTI) { - ehea_allmulti(dev, 1); - goto out; - } - ehea_allmulti(dev, 0); - - if (!netdev_mc_empty(dev)) { - ret = ehea_drop_multicast_list(dev); - if (ret) { - /* Dropping the current multicast list failed. - * Enabling ALL_MULTI is the best we can do. - */ - ehea_allmulti(dev, 1); - } - - if (netdev_mc_count(dev) > port->adapter->max_mc_mac) { - pr_info("Mcast registration limit reached (0x%llx). Use ALLMULTI!\n", - port->adapter->max_mc_mac); - goto out; - } - - netdev_for_each_mc_addr(ha, dev) - ehea_add_multicast_entry(port, ha->addr); - - } -out: - ehea_update_bcmc_registrations(); -} - -static void xmit_common(struct sk_buff *skb, struct ehea_swqe *swqe) -{ - swqe->tx_control |= EHEA_SWQE_IMM_DATA_PRESENT | EHEA_SWQE_CRC; - - if (vlan_get_protocol(skb) != htons(ETH_P_IP)) - return; - - if (skb->ip_summed == CHECKSUM_PARTIAL) - swqe->tx_control |= EHEA_SWQE_IP_CHECKSUM; - - swqe->ip_start = skb_network_offset(skb); - swqe->ip_end = swqe->ip_start + ip_hdrlen(skb) - 1; - - switch (ip_hdr(skb)->protocol) { - case IPPROTO_UDP: - if (skb->ip_summed == CHECKSUM_PARTIAL) - swqe->tx_control |= EHEA_SWQE_TCP_CHECKSUM; - - swqe->tcp_offset = swqe->ip_end + 1 + - offsetof(struct udphdr, check); - break; - - case IPPROTO_TCP: - if (skb->ip_summed == CHECKSUM_PARTIAL) - swqe->tx_control |= EHEA_SWQE_TCP_CHECKSUM; - - swqe->tcp_offset = swqe->ip_end + 1 + - offsetof(struct tcphdr, check); - break; - } -} - -static void ehea_xmit2(struct sk_buff *skb, struct net_device *dev, - struct ehea_swqe *swqe, u32 lkey) -{ - swqe->tx_control |= EHEA_SWQE_DESCRIPTORS_PRESENT; - - xmit_common(skb, swqe); - - write_swqe2_data(skb, dev, swqe, lkey); -} - -static void ehea_xmit3(struct sk_buff *skb, struct net_device *dev, - struct ehea_swqe *swqe) -{ - u8 *imm_data = &swqe->u.immdata_nodesc.immediate_data[0]; - - xmit_common(skb, swqe); - - if (!skb->data_len) - skb_copy_from_linear_data(skb, imm_data, skb->len); - else - skb_copy_bits(skb, 0, imm_data, skb->len); - - swqe->immediate_data_length = skb->len; - dev_consume_skb_any(skb); -} - -static netdev_tx_t ehea_start_xmit(struct sk_buff *skb, struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_swqe *swqe; - u32 lkey; - int swqe_index; - struct ehea_port_res *pr; - struct netdev_queue *txq; - - pr = &port->port_res[skb_get_queue_mapping(skb)]; - txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb)); - - swqe = ehea_get_swqe(pr->qp, &swqe_index); - memset(swqe, 0, SWQE_HEADER_SIZE); - atomic_dec(&pr->swqe_avail); - - if (skb_vlan_tag_present(skb)) { - swqe->tx_control |= EHEA_SWQE_VLAN_INSERT; - swqe->vlan_tag = skb_vlan_tag_get(skb); - } - - pr->tx_packets++; - pr->tx_bytes += skb->len; - - if (skb->len <= SWQE3_MAX_IMM) { - u32 sig_iv = port->sig_comp_iv; - u32 swqe_num = pr->swqe_id_counter; - ehea_xmit3(skb, dev, swqe); - swqe->wr_id = EHEA_BMASK_SET(EHEA_WR_ID_TYPE, EHEA_SWQE3_TYPE) - | EHEA_BMASK_SET(EHEA_WR_ID_COUNT, swqe_num); - if (pr->swqe_ll_count >= (sig_iv - 1)) { - swqe->wr_id |= EHEA_BMASK_SET(EHEA_WR_ID_REFILL, - sig_iv); - swqe->tx_control |= EHEA_SWQE_SIGNALLED_COMPLETION; - pr->swqe_ll_count = 0; - } else - pr->swqe_ll_count += 1; - } else { - swqe->wr_id = - EHEA_BMASK_SET(EHEA_WR_ID_TYPE, EHEA_SWQE2_TYPE) - | EHEA_BMASK_SET(EHEA_WR_ID_COUNT, pr->swqe_id_counter) - | EHEA_BMASK_SET(EHEA_WR_ID_REFILL, 1) - | EHEA_BMASK_SET(EHEA_WR_ID_INDEX, pr->sq_skba.index); - pr->sq_skba.arr[pr->sq_skba.index] = skb; - - pr->sq_skba.index++; - pr->sq_skba.index &= (pr->sq_skba.len - 1); - - lkey = pr->send_mr.lkey; - ehea_xmit2(skb, dev, swqe, lkey); - swqe->tx_control |= EHEA_SWQE_SIGNALLED_COMPLETION; - } - pr->swqe_id_counter += 1; - - netif_info(port, tx_queued, dev, - "post swqe on QP %d\n", pr->qp->init_attr.qp_nr); - if (netif_msg_tx_queued(port)) - ehea_dump(swqe, 512, "swqe"); - - if (unlikely(test_bit(__EHEA_STOP_XFER, &ehea_driver_flags))) { - netif_tx_stop_queue(txq); - swqe->tx_control |= EHEA_SWQE_PURGE; - } - - ehea_post_swqe(pr->qp, swqe); - - if (unlikely(atomic_read(&pr->swqe_avail) <= 1)) { - pr->p_stats.queue_stopped++; - netif_tx_stop_queue(txq); - } - - return NETDEV_TX_OK; -} - -static int ehea_vlan_rx_add_vid(struct net_device *dev, __be16 proto, u16 vid) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_adapter *adapter = port->adapter; - struct hcp_ehea_port_cb1 *cb1; - int index; - u64 hret; - int err = 0; - - cb1 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb1) { - pr_err("no mem for cb1\n"); - err = -ENOMEM; - goto out; - } - - hret = ehea_h_query_ehea_port(adapter->handle, port->logical_port_id, - H_PORT_CB1, H_PORT_CB1_ALL, cb1); - if (hret != H_SUCCESS) { - pr_err("query_ehea_port failed\n"); - err = -EINVAL; - goto out; - } - - index = (vid / 64); - cb1->vlan_filter[index] |= ((u64)(0x8000000000000000 >> (vid & 0x3F))); - - hret = ehea_h_modify_ehea_port(adapter->handle, port->logical_port_id, - H_PORT_CB1, H_PORT_CB1_ALL, cb1); - if (hret != H_SUCCESS) { - pr_err("modify_ehea_port failed\n"); - err = -EINVAL; - } -out: - free_page((unsigned long)cb1); - return err; -} - -static int ehea_vlan_rx_kill_vid(struct net_device *dev, __be16 proto, u16 vid) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_adapter *adapter = port->adapter; - struct hcp_ehea_port_cb1 *cb1; - int index; - u64 hret; - int err = 0; - - cb1 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb1) { - pr_err("no mem for cb1\n"); - err = -ENOMEM; - goto out; - } - - hret = ehea_h_query_ehea_port(adapter->handle, port->logical_port_id, - H_PORT_CB1, H_PORT_CB1_ALL, cb1); - if (hret != H_SUCCESS) { - pr_err("query_ehea_port failed\n"); - err = -EINVAL; - goto out; - } - - index = (vid / 64); - cb1->vlan_filter[index] &= ~((u64)(0x8000000000000000 >> (vid & 0x3F))); - - hret = ehea_h_modify_ehea_port(adapter->handle, port->logical_port_id, - H_PORT_CB1, H_PORT_CB1_ALL, cb1); - if (hret != H_SUCCESS) { - pr_err("modify_ehea_port failed\n"); - err = -EINVAL; - } -out: - free_page((unsigned long)cb1); - return err; -} - -static int ehea_activate_qp(struct ehea_adapter *adapter, struct ehea_qp *qp) -{ - int ret = -EIO; - u64 hret; - u16 dummy16 = 0; - u64 dummy64 = 0; - struct hcp_modify_qp_cb0 *cb0; - - cb0 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb0) { - ret = -ENOMEM; - goto out; - } - - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), cb0); - if (hret != H_SUCCESS) { - pr_err("query_ehea_qp failed (1)\n"); - goto out; - } - - cb0->qp_ctl_reg = H_QP_CR_STATE_INITIALIZED; - hret = ehea_h_modify_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_QP_CTL_REG, 1), cb0, - &dummy64, &dummy64, &dummy16, &dummy16); - if (hret != H_SUCCESS) { - pr_err("modify_ehea_qp failed (1)\n"); - goto out; - } - - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), cb0); - if (hret != H_SUCCESS) { - pr_err("query_ehea_qp failed (2)\n"); - goto out; - } - - cb0->qp_ctl_reg = H_QP_CR_ENABLED | H_QP_CR_STATE_INITIALIZED; - hret = ehea_h_modify_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_QP_CTL_REG, 1), cb0, - &dummy64, &dummy64, &dummy16, &dummy16); - if (hret != H_SUCCESS) { - pr_err("modify_ehea_qp failed (2)\n"); - goto out; - } - - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), cb0); - if (hret != H_SUCCESS) { - pr_err("query_ehea_qp failed (3)\n"); - goto out; - } - - cb0->qp_ctl_reg = H_QP_CR_ENABLED | H_QP_CR_STATE_RDY2SND; - hret = ehea_h_modify_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_QP_CTL_REG, 1), cb0, - &dummy64, &dummy64, &dummy16, &dummy16); - if (hret != H_SUCCESS) { - pr_err("modify_ehea_qp failed (3)\n"); - goto out; - } - - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), cb0); - if (hret != H_SUCCESS) { - pr_err("query_ehea_qp failed (4)\n"); - goto out; - } - - ret = 0; -out: - free_page((unsigned long)cb0); - return ret; -} - -static int ehea_port_res_setup(struct ehea_port *port, int def_qps) -{ - int ret, i; - struct port_res_cfg pr_cfg, pr_cfg_small_rx; - enum ehea_eq_type eq_type = EHEA_EQ; - - port->qp_eq = ehea_create_eq(port->adapter, eq_type, - EHEA_MAX_ENTRIES_EQ, 1); - if (!port->qp_eq) { - ret = -EINVAL; - pr_err("ehea_create_eq failed (qp_eq)\n"); - goto out_kill_eq; - } - - pr_cfg.max_entries_rcq = rq1_entries + rq2_entries + rq3_entries; - pr_cfg.max_entries_scq = sq_entries * 2; - pr_cfg.max_entries_sq = sq_entries; - pr_cfg.max_entries_rq1 = rq1_entries; - pr_cfg.max_entries_rq2 = rq2_entries; - pr_cfg.max_entries_rq3 = rq3_entries; - - pr_cfg_small_rx.max_entries_rcq = 1; - pr_cfg_small_rx.max_entries_scq = sq_entries; - pr_cfg_small_rx.max_entries_sq = sq_entries; - pr_cfg_small_rx.max_entries_rq1 = 1; - pr_cfg_small_rx.max_entries_rq2 = 1; - pr_cfg_small_rx.max_entries_rq3 = 1; - - for (i = 0; i < def_qps; i++) { - ret = ehea_init_port_res(port, &port->port_res[i], &pr_cfg, i); - if (ret) - goto out_clean_pr; - } - for (i = def_qps; i < def_qps; i++) { - ret = ehea_init_port_res(port, &port->port_res[i], - &pr_cfg_small_rx, i); - if (ret) - goto out_clean_pr; - } - - return 0; - -out_clean_pr: - while (--i >= 0) - ehea_clean_portres(port, &port->port_res[i]); - -out_kill_eq: - ehea_destroy_eq(port->qp_eq); - return ret; -} - -static int ehea_clean_all_portres(struct ehea_port *port) -{ - int ret = 0; - int i; - - for (i = 0; i < port->num_def_qps; i++) - ret |= ehea_clean_portres(port, &port->port_res[i]); - - ret |= ehea_destroy_eq(port->qp_eq); - - return ret; -} - -static void ehea_remove_adapter_mr(struct ehea_adapter *adapter) -{ - if (adapter->active_ports) - return; - - ehea_rem_mr(&adapter->mr); -} - -static int ehea_add_adapter_mr(struct ehea_adapter *adapter) -{ - if (adapter->active_ports) - return 0; - - return ehea_reg_kernel_mr(adapter, &adapter->mr); -} - -static int ehea_up(struct net_device *dev) -{ - int ret, i; - struct ehea_port *port = netdev_priv(dev); - - if (port->state == EHEA_PORT_UP) - return 0; - - ret = ehea_port_res_setup(port, port->num_def_qps); - if (ret) { - netdev_err(dev, "port_res_failed\n"); - goto out; - } - - /* Set default QP for this port */ - ret = ehea_configure_port(port); - if (ret) { - netdev_err(dev, "ehea_configure_port failed. ret:%d\n", ret); - goto out_clean_pr; - } - - ret = ehea_reg_interrupts(dev); - if (ret) { - netdev_err(dev, "reg_interrupts failed. ret:%d\n", ret); - goto out_clean_pr; - } - - for (i = 0; i < port->num_def_qps; i++) { - ret = ehea_activate_qp(port->adapter, port->port_res[i].qp); - if (ret) { - netdev_err(dev, "activate_qp failed\n"); - goto out_free_irqs; - } - } - - for (i = 0; i < port->num_def_qps; i++) { - ret = ehea_fill_port_res(&port->port_res[i]); - if (ret) { - netdev_err(dev, "out_free_irqs\n"); - goto out_free_irqs; - } - } - - ret = ehea_broadcast_reg_helper(port, H_REG_BCMC); - if (ret) { - ret = -EIO; - goto out_free_irqs; - } - - port->state = EHEA_PORT_UP; - - ret = 0; - goto out; - -out_free_irqs: - ehea_free_interrupts(dev); - -out_clean_pr: - ehea_clean_all_portres(port); -out: - if (ret) - netdev_info(dev, "Failed starting. ret=%i\n", ret); - - ehea_update_bcmc_registrations(); - ehea_update_firmware_handles(); - - return ret; -} - -static void port_napi_disable(struct ehea_port *port) -{ - int i; - - for (i = 0; i < port->num_def_qps; i++) - napi_disable(&port->port_res[i].napi); -} - -static void port_napi_enable(struct ehea_port *port) -{ - int i; - - for (i = 0; i < port->num_def_qps; i++) - napi_enable(&port->port_res[i].napi); -} - -static int ehea_open(struct net_device *dev) -{ - int ret; - struct ehea_port *port = netdev_priv(dev); - - mutex_lock(&port->port_lock); - - netif_info(port, ifup, dev, "enabling port\n"); - - netif_carrier_off(dev); - - ret = ehea_up(dev); - if (!ret) { - port_napi_enable(port); - netif_tx_start_all_queues(dev); - } - - mutex_unlock(&port->port_lock); - schedule_delayed_work(&port->stats_work, - round_jiffies_relative(msecs_to_jiffies(1000))); - - return ret; -} - -static int ehea_down(struct net_device *dev) -{ - int ret; - struct ehea_port *port = netdev_priv(dev); - - if (port->state == EHEA_PORT_DOWN) - return 0; - - ehea_drop_multicast_list(dev); - ehea_allmulti(dev, 0); - ehea_broadcast_reg_helper(port, H_DEREG_BCMC); - - ehea_free_interrupts(dev); - - port->state = EHEA_PORT_DOWN; - - ehea_update_bcmc_registrations(); - - ret = ehea_clean_all_portres(port); - if (ret) - netdev_info(dev, "Failed freeing resources. ret=%i\n", ret); - - ehea_update_firmware_handles(); - - return ret; -} - -static int ehea_stop(struct net_device *dev) -{ - int ret; - struct ehea_port *port = netdev_priv(dev); - - netif_info(port, ifdown, dev, "disabling port\n"); - - set_bit(__EHEA_DISABLE_PORT_RESET, &port->flags); - cancel_work_sync(&port->reset_task); - cancel_delayed_work_sync(&port->stats_work); - mutex_lock(&port->port_lock); - netif_tx_stop_all_queues(dev); - port_napi_disable(port); - ret = ehea_down(dev); - mutex_unlock(&port->port_lock); - clear_bit(__EHEA_DISABLE_PORT_RESET, &port->flags); - return ret; -} - -static void ehea_purge_sq(struct ehea_qp *orig_qp) -{ - struct ehea_qp qp = *orig_qp; - struct ehea_qp_init_attr *init_attr = &qp.init_attr; - struct ehea_swqe *swqe; - int wqe_index; - int i; - - for (i = 0; i < init_attr->act_nr_send_wqes; i++) { - swqe = ehea_get_swqe(&qp, &wqe_index); - swqe->tx_control |= EHEA_SWQE_PURGE; - } -} - -static void ehea_flush_sq(struct ehea_port *port) -{ - int i; - - for (i = 0; i < port->num_def_qps; i++) { - struct ehea_port_res *pr = &port->port_res[i]; - int swqe_max = pr->sq_skba_size - 2 - pr->swqe_ll_count; - int ret; - - ret = wait_event_timeout(port->swqe_avail_wq, - atomic_read(&pr->swqe_avail) >= swqe_max, - msecs_to_jiffies(100)); - - if (!ret) { - pr_err("WARNING: sq not flushed completely\n"); - break; - } - } -} - -static int ehea_stop_qps(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_adapter *adapter = port->adapter; - struct hcp_modify_qp_cb0 *cb0; - int ret = -EIO; - int dret; - int i; - u64 hret; - u64 dummy64 = 0; - u16 dummy16 = 0; - - cb0 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb0) { - ret = -ENOMEM; - goto out; - } - - for (i = 0; i < (port->num_def_qps); i++) { - struct ehea_port_res *pr = &port->port_res[i]; - struct ehea_qp *qp = pr->qp; - - /* Purge send queue */ - ehea_purge_sq(qp); - - /* Disable queue pair */ - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), - cb0); - if (hret != H_SUCCESS) { - pr_err("query_ehea_qp failed (1)\n"); - goto out; - } - - cb0->qp_ctl_reg = (cb0->qp_ctl_reg & H_QP_CR_RES_STATE) << 8; - cb0->qp_ctl_reg &= ~H_QP_CR_ENABLED; - - hret = ehea_h_modify_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_QP_CTL_REG, - 1), cb0, &dummy64, - &dummy64, &dummy16, &dummy16); - if (hret != H_SUCCESS) { - pr_err("modify_ehea_qp failed (1)\n"); - goto out; - } - - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), - cb0); - if (hret != H_SUCCESS) { - pr_err("query_ehea_qp failed (2)\n"); - goto out; - } - - /* deregister shared memory regions */ - dret = ehea_rem_smrs(pr); - if (dret) { - pr_err("unreg shared memory region failed\n"); - goto out; - } - } - - ret = 0; -out: - free_page((unsigned long)cb0); - - return ret; -} - -static void ehea_update_rqs(struct ehea_qp *orig_qp, struct ehea_port_res *pr) -{ - struct ehea_qp qp = *orig_qp; - struct ehea_qp_init_attr *init_attr = &qp.init_attr; - struct ehea_rwqe *rwqe; - struct sk_buff **skba_rq2 = pr->rq2_skba.arr; - struct sk_buff **skba_rq3 = pr->rq3_skba.arr; - struct sk_buff *skb; - u32 lkey = pr->recv_mr.lkey; - - - int i; - int index; - - for (i = 0; i < init_attr->act_nr_rwqes_rq2 + 1; i++) { - rwqe = ehea_get_next_rwqe(&qp, 2); - rwqe->sg_list[0].l_key = lkey; - index = EHEA_BMASK_GET(EHEA_WR_ID_INDEX, rwqe->wr_id); - skb = skba_rq2[index]; - if (skb) - rwqe->sg_list[0].vaddr = ehea_map_vaddr(skb->data); - } - - for (i = 0; i < init_attr->act_nr_rwqes_rq3 + 1; i++) { - rwqe = ehea_get_next_rwqe(&qp, 3); - rwqe->sg_list[0].l_key = lkey; - index = EHEA_BMASK_GET(EHEA_WR_ID_INDEX, rwqe->wr_id); - skb = skba_rq3[index]; - if (skb) - rwqe->sg_list[0].vaddr = ehea_map_vaddr(skb->data); - } -} - -static int ehea_restart_qps(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_adapter *adapter = port->adapter; - int ret = 0; - int i; - - struct hcp_modify_qp_cb0 *cb0; - u64 hret; - u64 dummy64 = 0; - u16 dummy16 = 0; - - cb0 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb0) - return -ENOMEM; - - for (i = 0; i < (port->num_def_qps); i++) { - struct ehea_port_res *pr = &port->port_res[i]; - struct ehea_qp *qp = pr->qp; - - ret = ehea_gen_smrs(pr); - if (ret) { - netdev_err(dev, "creation of shared memory regions failed\n"); - goto out; - } - - ehea_update_rqs(qp, pr); - - /* Enable queue pair */ - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), - cb0); - if (hret != H_SUCCESS) { - netdev_err(dev, "query_ehea_qp failed (1)\n"); - ret = -EFAULT; - goto out; - } - - cb0->qp_ctl_reg = (cb0->qp_ctl_reg & H_QP_CR_RES_STATE) << 8; - cb0->qp_ctl_reg |= H_QP_CR_ENABLED; - - hret = ehea_h_modify_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_QP_CTL_REG, - 1), cb0, &dummy64, - &dummy64, &dummy16, &dummy16); - if (hret != H_SUCCESS) { - netdev_err(dev, "modify_ehea_qp failed (1)\n"); - ret = -EFAULT; - goto out; - } - - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), - cb0); - if (hret != H_SUCCESS) { - netdev_err(dev, "query_ehea_qp failed (2)\n"); - ret = -EFAULT; - goto out; - } - - /* refill entire queue */ - ehea_refill_rq1(pr, pr->rq1_skba.index, 0); - ehea_refill_rq2(pr, 0); - ehea_refill_rq3(pr, 0); - } -out: - free_page((unsigned long)cb0); - - return ret; -} - -static void ehea_reset_port(struct work_struct *work) -{ - int ret; - struct ehea_port *port = - container_of(work, struct ehea_port, reset_task); - struct net_device *dev = port->netdev; - - mutex_lock(&dlpar_mem_lock); - port->resets++; - mutex_lock(&port->port_lock); - netif_tx_disable(dev); - - port_napi_disable(port); - - ehea_down(dev); - - ret = ehea_up(dev); - if (ret) - goto out; - - ehea_set_multicast_list(dev); - - netif_info(port, timer, dev, "reset successful\n"); - - port_napi_enable(port); - - netif_tx_wake_all_queues(dev); -out: - mutex_unlock(&port->port_lock); - mutex_unlock(&dlpar_mem_lock); -} - -static void ehea_rereg_mrs(void) -{ - int ret, i; - struct ehea_adapter *adapter; - - pr_info("LPAR memory changed - re-initializing driver\n"); - - list_for_each_entry(adapter, &adapter_list, list) - if (adapter->active_ports) { - /* Shutdown all ports */ - for (i = 0; i < EHEA_MAX_PORTS; i++) { - struct ehea_port *port = adapter->port[i]; - struct net_device *dev; - - if (!port) - continue; - - dev = port->netdev; - - if (dev->flags & IFF_UP) { - mutex_lock(&port->port_lock); - netif_tx_disable(dev); - ehea_flush_sq(port); - ret = ehea_stop_qps(dev); - if (ret) { - mutex_unlock(&port->port_lock); - goto out; - } - port_napi_disable(port); - mutex_unlock(&port->port_lock); - } - reset_sq_restart_flag(port); - } - - /* Unregister old memory region */ - ret = ehea_rem_mr(&adapter->mr); - if (ret) { - pr_err("unregister MR failed - driver inoperable!\n"); - goto out; - } - } - - clear_bit(__EHEA_STOP_XFER, &ehea_driver_flags); - - list_for_each_entry(adapter, &adapter_list, list) - if (adapter->active_ports) { - /* Register new memory region */ - ret = ehea_reg_kernel_mr(adapter, &adapter->mr); - if (ret) { - pr_err("register MR failed - driver inoperable!\n"); - goto out; - } - - /* Restart all ports */ - for (i = 0; i < EHEA_MAX_PORTS; i++) { - struct ehea_port *port = adapter->port[i]; - - if (port) { - struct net_device *dev = port->netdev; - - if (dev->flags & IFF_UP) { - mutex_lock(&port->port_lock); - ret = ehea_restart_qps(dev); - if (!ret) { - check_sqs(port); - port_napi_enable(port); - netif_tx_wake_all_queues(dev); - } else { - netdev_err(dev, "Unable to restart QPS\n"); - } - mutex_unlock(&port->port_lock); - } - } - } - } - pr_info("re-initializing driver complete\n"); -out: - return; -} - -static void ehea_tx_watchdog(struct net_device *dev, unsigned int txqueue) -{ - struct ehea_port *port = netdev_priv(dev); - - if (netif_carrier_ok(dev) && - !test_bit(__EHEA_STOP_XFER, &ehea_driver_flags)) - ehea_schedule_port_reset(port); -} - -static int ehea_sense_adapter_attr(struct ehea_adapter *adapter) -{ - struct hcp_query_ehea *cb; - u64 hret; - int ret; - - cb = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb) { - ret = -ENOMEM; - goto out; - } - - hret = ehea_h_query_ehea(adapter->handle, cb); - - if (hret != H_SUCCESS) { - ret = -EIO; - goto out_herr; - } - - adapter->max_mc_mac = cb->max_mc_mac - 1; - ret = 0; - -out_herr: - free_page((unsigned long)cb); -out: - return ret; -} - -static int ehea_get_jumboframe_status(struct ehea_port *port, int *jumbo) -{ - struct hcp_ehea_port_cb4 *cb4; - u64 hret; - int ret = 0; - - *jumbo = 0; - - /* (Try to) enable *jumbo frames */ - cb4 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb4) { - pr_err("no mem for cb4\n"); - ret = -ENOMEM; - goto out; - } else { - hret = ehea_h_query_ehea_port(port->adapter->handle, - port->logical_port_id, - H_PORT_CB4, - H_PORT_CB4_JUMBO, cb4); - if (hret == H_SUCCESS) { - if (cb4->jumbo_frame) - *jumbo = 1; - else { - cb4->jumbo_frame = 1; - hret = ehea_h_modify_ehea_port(port->adapter-> - handle, - port-> - logical_port_id, - H_PORT_CB4, - H_PORT_CB4_JUMBO, - cb4); - if (hret == H_SUCCESS) - *jumbo = 1; - } - } else - ret = -EINVAL; - - free_page((unsigned long)cb4); - } -out: - return ret; -} - -static ssize_t log_port_id_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct ehea_port *port = container_of(dev, struct ehea_port, ofdev.dev); - return sprintf(buf, "%d", port->logical_port_id); -} - -static DEVICE_ATTR_RO(log_port_id); - -static void logical_port_release(struct device *dev) -{ - struct ehea_port *port = container_of(dev, struct ehea_port, ofdev.dev); - of_node_put(port->ofdev.dev.of_node); -} - -static struct device *ehea_register_port(struct ehea_port *port, - struct device_node *dn) -{ - int ret; - - port->ofdev.dev.of_node = of_node_get(dn); - port->ofdev.dev.parent = &port->adapter->ofdev->dev; - port->ofdev.dev.bus = &ibmebus_bus_type; - - dev_set_name(&port->ofdev.dev, "port%d", port_name_cnt++); - port->ofdev.dev.release = logical_port_release; - - ret = of_device_register(&port->ofdev); - if (ret) { - pr_err("failed to register device. ret=%d\n", ret); - put_device(&port->ofdev.dev); - goto out; - } - - ret = device_create_file(&port->ofdev.dev, &dev_attr_log_port_id); - if (ret) { - pr_err("failed to register attributes, ret=%d\n", ret); - goto out_unreg_of_dev; - } - - return &port->ofdev.dev; - -out_unreg_of_dev: - of_device_unregister(&port->ofdev); -out: - return NULL; -} - -static void ehea_unregister_port(struct ehea_port *port) -{ - device_remove_file(&port->ofdev.dev, &dev_attr_log_port_id); - of_device_unregister(&port->ofdev); -} - -static const struct net_device_ops ehea_netdev_ops = { - .ndo_open = ehea_open, - .ndo_stop = ehea_stop, - .ndo_start_xmit = ehea_start_xmit, - .ndo_get_stats64 = ehea_get_stats64, - .ndo_set_mac_address = ehea_set_mac_addr, - .ndo_validate_addr = eth_validate_addr, - .ndo_set_rx_mode = ehea_set_multicast_list, - .ndo_vlan_rx_add_vid = ehea_vlan_rx_add_vid, - .ndo_vlan_rx_kill_vid = ehea_vlan_rx_kill_vid, - .ndo_tx_timeout = ehea_tx_watchdog, -}; - -static struct ehea_port *ehea_setup_single_port(struct ehea_adapter *adapter, - u32 logical_port_id, - struct device_node *dn) -{ - int ret; - struct net_device *dev; - struct ehea_port *port; - struct device *port_dev; - int jumbo; - - /* allocate memory for the port structures */ - dev = alloc_etherdev_mq(sizeof(struct ehea_port), EHEA_MAX_PORT_RES); - - if (!dev) { - ret = -ENOMEM; - goto out_err; - } - - port = netdev_priv(dev); - - mutex_init(&port->port_lock); - port->state = EHEA_PORT_DOWN; - port->sig_comp_iv = sq_entries / 10; - - port->adapter = adapter; - port->netdev = dev; - port->logical_port_id = logical_port_id; - - port->msg_enable = netif_msg_init(msg_level, EHEA_MSG_DEFAULT); - - port->mc_list = kzalloc_obj(struct ehea_mc_list); - if (!port->mc_list) { - ret = -ENOMEM; - goto out_free_ethdev; - } - - INIT_LIST_HEAD(&port->mc_list->list); - - ret = ehea_sense_port_attr(port); - if (ret) - goto out_free_mc_list; - - netif_set_real_num_rx_queues(dev, port->num_def_qps); - netif_set_real_num_tx_queues(dev, port->num_def_qps); - - port_dev = ehea_register_port(port, dn); - if (!port_dev) - goto out_free_mc_list; - - SET_NETDEV_DEV(dev, port_dev); - - /* initialize net_device structure */ - eth_hw_addr_set(dev, (u8 *)&port->mac_addr); - - dev->netdev_ops = &ehea_netdev_ops; - ehea_set_ethtool_ops(dev); - - dev->hw_features = NETIF_F_SG | NETIF_F_TSO | - NETIF_F_IP_CSUM | NETIF_F_HW_VLAN_CTAG_TX; - dev->features = NETIF_F_SG | NETIF_F_TSO | - NETIF_F_HIGHDMA | NETIF_F_IP_CSUM | - NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX | - NETIF_F_HW_VLAN_CTAG_FILTER | NETIF_F_RXCSUM; - dev->vlan_features = NETIF_F_SG | NETIF_F_TSO | NETIF_F_HIGHDMA | - NETIF_F_IP_CSUM; - dev->watchdog_timeo = EHEA_WATCH_DOG_TIMEOUT; - - /* MTU range: 68 - 9022 */ - dev->min_mtu = ETH_MIN_MTU; - dev->max_mtu = EHEA_MAX_PACKET_SIZE; - - INIT_WORK(&port->reset_task, ehea_reset_port); - INIT_DELAYED_WORK(&port->stats_work, ehea_update_stats); - - init_waitqueue_head(&port->swqe_avail_wq); - init_waitqueue_head(&port->restart_wq); - - ret = register_netdev(dev); - if (ret) { - pr_err("register_netdev failed. ret=%d\n", ret); - goto out_unreg_port; - } - - ret = ehea_get_jumboframe_status(port, &jumbo); - if (ret) - netdev_err(dev, "failed determining jumbo frame status\n"); - - netdev_info(dev, "Jumbo frames are %sabled\n", - jumbo == 1 ? "en" : "dis"); - - adapter->active_ports++; - - return port; - -out_unreg_port: - ehea_unregister_port(port); - -out_free_mc_list: - kfree(port->mc_list); - -out_free_ethdev: - free_netdev(dev); - -out_err: - pr_err("setting up logical port with id=%d failed, ret=%d\n", - logical_port_id, ret); - return NULL; -} - -static void ehea_shutdown_single_port(struct ehea_port *port) -{ - struct ehea_adapter *adapter = port->adapter; - - cancel_work_sync(&port->reset_task); - cancel_delayed_work_sync(&port->stats_work); - unregister_netdev(port->netdev); - ehea_unregister_port(port); - kfree(port->mc_list); - free_netdev(port->netdev); - adapter->active_ports--; -} - -static int ehea_setup_ports(struct ehea_adapter *adapter) -{ - struct device_node *lhea_dn; - struct device_node *eth_dn; - - const u32 *dn_log_port_id; - int i = 0; - - lhea_dn = adapter->ofdev->dev.of_node; - for_each_child_of_node(lhea_dn, eth_dn) { - dn_log_port_id = of_get_property(eth_dn, "ibm,hea-port-no", - NULL); - if (!dn_log_port_id) { - pr_err("bad device node: eth_dn name=%pOF\n", eth_dn); - continue; - } - - if (ehea_add_adapter_mr(adapter)) { - pr_err("creating MR failed\n"); - of_node_put(eth_dn); - return -EIO; - } - - adapter->port[i] = ehea_setup_single_port(adapter, - *dn_log_port_id, - eth_dn); - if (adapter->port[i]) - netdev_info(adapter->port[i]->netdev, - "logical port id #%d\n", *dn_log_port_id); - else - ehea_remove_adapter_mr(adapter); - - i++; - } - return 0; -} - -static struct device_node *ehea_get_eth_dn(struct ehea_adapter *adapter, - u32 logical_port_id) -{ - struct device_node *lhea_dn; - struct device_node *eth_dn; - const u32 *dn_log_port_id; - - lhea_dn = adapter->ofdev->dev.of_node; - for_each_child_of_node(lhea_dn, eth_dn) { - dn_log_port_id = of_get_property(eth_dn, "ibm,hea-port-no", - NULL); - if (dn_log_port_id) - if (*dn_log_port_id == logical_port_id) - return eth_dn; - } - - return NULL; -} - -static ssize_t probe_port_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct ehea_adapter *adapter = dev_get_drvdata(dev); - struct ehea_port *port; - struct device_node *eth_dn = NULL; - int i; - - u32 logical_port_id; - - sscanf(buf, "%d", &logical_port_id); - - port = ehea_get_port(adapter, logical_port_id); - - if (port) { - netdev_info(port->netdev, "adding port with logical port id=%d failed: port already configured\n", - logical_port_id); - return -EINVAL; - } - - eth_dn = ehea_get_eth_dn(adapter, logical_port_id); - - if (!eth_dn) { - pr_info("no logical port with id %d found\n", logical_port_id); - return -EINVAL; - } - - if (ehea_add_adapter_mr(adapter)) { - pr_err("creating MR failed\n"); - of_node_put(eth_dn); - return -EIO; - } - - port = ehea_setup_single_port(adapter, logical_port_id, eth_dn); - - of_node_put(eth_dn); - - if (port) { - for (i = 0; i < EHEA_MAX_PORTS; i++) - if (!adapter->port[i]) { - adapter->port[i] = port; - break; - } - - netdev_info(port->netdev, "added: (logical port id=%d)\n", - logical_port_id); - } else { - ehea_remove_adapter_mr(adapter); - return -EIO; - } - - return (ssize_t) count; -} - -static ssize_t remove_port_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct ehea_adapter *adapter = dev_get_drvdata(dev); - struct ehea_port *port; - int i; - u32 logical_port_id; - - sscanf(buf, "%d", &logical_port_id); - - port = ehea_get_port(adapter, logical_port_id); - - if (port) { - netdev_info(port->netdev, "removed: (logical port id=%d)\n", - logical_port_id); - - ehea_shutdown_single_port(port); - - for (i = 0; i < EHEA_MAX_PORTS; i++) - if (adapter->port[i] == port) { - adapter->port[i] = NULL; - break; - } - } else { - pr_err("removing port with logical port id=%d failed. port not configured.\n", - logical_port_id); - return -EINVAL; - } - - ehea_remove_adapter_mr(adapter); - - return (ssize_t) count; -} - -static DEVICE_ATTR_WO(probe_port); -static DEVICE_ATTR_WO(remove_port); - -static int ehea_create_device_sysfs(struct platform_device *dev) -{ - int ret = device_create_file(&dev->dev, &dev_attr_probe_port); - if (ret) - goto out; - - ret = device_create_file(&dev->dev, &dev_attr_remove_port); - if (ret) - device_remove_file(&dev->dev, &dev_attr_probe_port); -out: - return ret; -} - -static void ehea_remove_device_sysfs(struct platform_device *dev) -{ - device_remove_file(&dev->dev, &dev_attr_probe_port); - device_remove_file(&dev->dev, &dev_attr_remove_port); -} - -static int ehea_reboot_notifier(struct notifier_block *nb, - unsigned long action, void *unused) -{ - if (action == SYS_RESTART) { - pr_info("Reboot: freeing all eHEA resources\n"); - ibmebus_unregister_driver(&ehea_driver); - } - return NOTIFY_DONE; -} - -static struct notifier_block ehea_reboot_nb = { - .notifier_call = ehea_reboot_notifier, -}; - -static int ehea_mem_notifier(struct notifier_block *nb, - unsigned long action, void *data) -{ - int ret = NOTIFY_BAD; - struct memory_notify *arg = data; - - mutex_lock(&dlpar_mem_lock); - - switch (action) { - case MEM_CANCEL_OFFLINE: - pr_info("memory offlining canceled"); - fallthrough; /* re-add canceled memory block */ - - case MEM_ONLINE: - pr_info("memory is going online"); - set_bit(__EHEA_STOP_XFER, &ehea_driver_flags); - if (ehea_add_sect_bmap(arg->start_pfn, arg->nr_pages)) - goto out_unlock; - ehea_rereg_mrs(); - break; - - case MEM_GOING_OFFLINE: - pr_info("memory is going offline"); - set_bit(__EHEA_STOP_XFER, &ehea_driver_flags); - if (ehea_rem_sect_bmap(arg->start_pfn, arg->nr_pages)) - goto out_unlock; - ehea_rereg_mrs(); - break; - - default: - break; - } - - ehea_update_firmware_handles(); - ret = NOTIFY_OK; - -out_unlock: - mutex_unlock(&dlpar_mem_lock); - return ret; -} - -static struct notifier_block ehea_mem_nb = { - .notifier_call = ehea_mem_notifier, -}; - -static void ehea_crash_handler(void) -{ - int i; - - if (ehea_fw_handles.arr) - for (i = 0; i < ehea_fw_handles.num_entries; i++) - ehea_h_free_resource(ehea_fw_handles.arr[i].adh, - ehea_fw_handles.arr[i].fwh, - FORCE_FREE); - - if (ehea_bcmc_regs.arr) - for (i = 0; i < ehea_bcmc_regs.num_entries; i++) - ehea_h_reg_dereg_bcmc(ehea_bcmc_regs.arr[i].adh, - ehea_bcmc_regs.arr[i].port_id, - ehea_bcmc_regs.arr[i].reg_type, - ehea_bcmc_regs.arr[i].macaddr, - 0, H_DEREG_BCMC); -} - -static atomic_t ehea_memory_hooks_registered; - -/* Register memory hooks on probe of first adapter */ -static int ehea_register_memory_hooks(void) -{ - int ret = 0; - - if (atomic_inc_return(&ehea_memory_hooks_registered) > 1) - return 0; - - ret = ehea_create_busmap(); - if (ret) { - pr_info("ehea_create_busmap failed\n"); - goto out; - } - - ret = register_reboot_notifier(&ehea_reboot_nb); - if (ret) { - pr_info("register_reboot_notifier failed\n"); - goto out; - } - - ret = register_memory_notifier(&ehea_mem_nb); - if (ret) { - pr_info("register_memory_notifier failed\n"); - goto out2; - } - - ret = crash_shutdown_register(ehea_crash_handler); - if (ret) { - pr_info("crash_shutdown_register failed\n"); - goto out3; - } - - return 0; - -out3: - unregister_memory_notifier(&ehea_mem_nb); -out2: - unregister_reboot_notifier(&ehea_reboot_nb); -out: - atomic_dec(&ehea_memory_hooks_registered); - return ret; -} - -static void ehea_unregister_memory_hooks(void) -{ - /* Only remove the hooks if we've registered them */ - if (atomic_read(&ehea_memory_hooks_registered) == 0) - return; - - unregister_reboot_notifier(&ehea_reboot_nb); - if (crash_shutdown_unregister(ehea_crash_handler)) - pr_info("failed unregistering crash handler\n"); - unregister_memory_notifier(&ehea_mem_nb); -} - -static int ehea_probe_adapter(struct platform_device *dev) -{ - struct ehea_adapter *adapter; - const u64 *adapter_handle; - int ret; - int i; - - ret = ehea_register_memory_hooks(); - if (ret) - return ret; - - if (!dev || !dev->dev.of_node) { - pr_err("Invalid ibmebus device probed\n"); - return -EINVAL; - } - - adapter = devm_kzalloc(&dev->dev, sizeof(*adapter), GFP_KERNEL); - if (!adapter) { - ret = -ENOMEM; - dev_err(&dev->dev, "no mem for ehea_adapter\n"); - goto out; - } - - list_add(&adapter->list, &adapter_list); - - adapter->ofdev = dev; - - adapter_handle = of_get_property(dev->dev.of_node, "ibm,hea-handle", - NULL); - if (adapter_handle) - adapter->handle = *adapter_handle; - - if (!adapter->handle) { - dev_err(&dev->dev, "failed getting handle for adapter" - " '%pOF'\n", dev->dev.of_node); - ret = -ENODEV; - goto out_free_ad; - } - - adapter->pd = EHEA_PD_ID; - - platform_set_drvdata(dev, adapter); - - - /* initialize adapter and ports */ - /* get adapter properties */ - ret = ehea_sense_adapter_attr(adapter); - if (ret) { - dev_err(&dev->dev, "sense_adapter_attr failed: %d\n", ret); - goto out_free_ad; - } - - adapter->neq = ehea_create_eq(adapter, - EHEA_NEQ, EHEA_MAX_ENTRIES_EQ, 1); - if (!adapter->neq) { - ret = -EIO; - dev_err(&dev->dev, "NEQ creation failed\n"); - goto out_free_ad; - } - - tasklet_setup(&adapter->neq_tasklet, ehea_neq_tasklet); - - ret = ehea_create_device_sysfs(dev); - if (ret) - goto out_kill_eq; - - ret = ehea_setup_ports(adapter); - if (ret) { - dev_err(&dev->dev, "setup_ports failed\n"); - goto out_rem_dev_sysfs; - } - - ret = ibmebus_request_irq(adapter->neq->attr.ist1, - ehea_interrupt_neq, 0, - "ehea_neq", adapter); - if (ret) { - dev_err(&dev->dev, "requesting NEQ IRQ failed\n"); - goto out_shutdown_ports; - } - - /* Handle any events that might be pending. */ - tasklet_hi_schedule(&adapter->neq_tasklet); - - ret = 0; - goto out; - -out_shutdown_ports: - for (i = 0; i < EHEA_MAX_PORTS; i++) - if (adapter->port[i]) { - ehea_shutdown_single_port(adapter->port[i]); - adapter->port[i] = NULL; - } - -out_rem_dev_sysfs: - ehea_remove_device_sysfs(dev); - -out_kill_eq: - ehea_destroy_eq(adapter->neq); - -out_free_ad: - list_del(&adapter->list); - -out: - ehea_update_firmware_handles(); - - return ret; -} - -static void ehea_remove(struct platform_device *dev) -{ - struct ehea_adapter *adapter = platform_get_drvdata(dev); - int i; - - for (i = 0; i < EHEA_MAX_PORTS; i++) - if (adapter->port[i]) { - ehea_shutdown_single_port(adapter->port[i]); - adapter->port[i] = NULL; - } - - ehea_remove_device_sysfs(dev); - - ibmebus_free_irq(adapter->neq->attr.ist1, adapter); - tasklet_kill(&adapter->neq_tasklet); - - ehea_destroy_eq(adapter->neq); - ehea_remove_adapter_mr(adapter); - list_del(&adapter->list); - - ehea_update_firmware_handles(); -} - -static int check_module_parm(void) -{ - int ret = 0; - - if ((rq1_entries < EHEA_MIN_ENTRIES_QP) || - (rq1_entries > EHEA_MAX_ENTRIES_RQ1)) { - pr_info("Bad parameter: rq1_entries\n"); - ret = -EINVAL; - } - if ((rq2_entries < EHEA_MIN_ENTRIES_QP) || - (rq2_entries > EHEA_MAX_ENTRIES_RQ2)) { - pr_info("Bad parameter: rq2_entries\n"); - ret = -EINVAL; - } - if ((rq3_entries < EHEA_MIN_ENTRIES_QP) || - (rq3_entries > EHEA_MAX_ENTRIES_RQ3)) { - pr_info("Bad parameter: rq3_entries\n"); - ret = -EINVAL; - } - if ((sq_entries < EHEA_MIN_ENTRIES_QP) || - (sq_entries > EHEA_MAX_ENTRIES_SQ)) { - pr_info("Bad parameter: sq_entries\n"); - ret = -EINVAL; - } - - return ret; -} - -static ssize_t capabilities_show(struct device_driver *drv, char *buf) -{ - return sprintf(buf, "%d", EHEA_CAPABILITIES); -} - -static DRIVER_ATTR_RO(capabilities); - -static int __init ehea_module_init(void) -{ - int ret; - - pr_info("IBM eHEA ethernet device driver (Release %s)\n", DRV_VERSION); - - memset(&ehea_fw_handles, 0, sizeof(ehea_fw_handles)); - memset(&ehea_bcmc_regs, 0, sizeof(ehea_bcmc_regs)); - - mutex_init(&ehea_fw_handles.lock); - spin_lock_init(&ehea_bcmc_regs.lock); - - ret = check_module_parm(); - if (ret) - goto out; - - ret = ibmebus_register_driver(&ehea_driver); - if (ret) { - pr_err("failed registering eHEA device driver on ebus\n"); - goto out; - } - - ret = driver_create_file(&ehea_driver.driver, - &driver_attr_capabilities); - if (ret) { - pr_err("failed to register capabilities attribute, ret=%d\n", - ret); - goto out2; - } - - return ret; - -out2: - ibmebus_unregister_driver(&ehea_driver); -out: - return ret; -} - -static void __exit ehea_module_exit(void) -{ - driver_remove_file(&ehea_driver.driver, &driver_attr_capabilities); - ibmebus_unregister_driver(&ehea_driver); - ehea_unregister_memory_hooks(); - kfree(ehea_fw_handles.arr); - kfree(ehea_bcmc_regs.arr); - ehea_destroy_busmap(); -} - -module_init(ehea_module_init); -module_exit(ehea_module_exit); diff --git a/drivers/net/ethernet/ibm/ehea/ehea_phyp.c b/drivers/net/ethernet/ibm/ehea/ehea_phyp.c deleted file mode 100644 index e63716e139f5..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea_phyp.c +++ /dev/null @@ -1,612 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea_phyp.c - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch - * Jan-Bernd Themann - * Thomas Klein - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include "ehea_phyp.h" - - -static inline u16 get_order_of_qentries(u16 queue_entries) -{ - u8 ld = 1; /* logarithmus dualis */ - while (((1U << ld) - 1) < queue_entries) - ld++; - return ld - 1; -} - -/* Defines for H_CALL H_ALLOC_RESOURCE */ -#define H_ALL_RES_TYPE_QP 1 -#define H_ALL_RES_TYPE_CQ 2 -#define H_ALL_RES_TYPE_EQ 3 -#define H_ALL_RES_TYPE_MR 5 -#define H_ALL_RES_TYPE_MW 6 - -static long ehea_plpar_hcall_norets(unsigned long opcode, - unsigned long arg1, - unsigned long arg2, - unsigned long arg3, - unsigned long arg4, - unsigned long arg5, - unsigned long arg6, - unsigned long arg7) -{ - long ret; - int i, sleep_msecs; - - for (i = 0; i < 5; i++) { - ret = plpar_hcall_norets(opcode, arg1, arg2, arg3, arg4, - arg5, arg6, arg7); - - if (H_IS_LONG_BUSY(ret)) { - sleep_msecs = get_longbusy_msecs(ret); - msleep_interruptible(sleep_msecs); - continue; - } - - if (ret < H_SUCCESS) - pr_err("opcode=%lx ret=%lx" - " arg1=%lx arg2=%lx arg3=%lx arg4=%lx" - " arg5=%lx arg6=%lx arg7=%lx\n", - opcode, ret, - arg1, arg2, arg3, arg4, arg5, arg6, arg7); - - return ret; - } - - return H_BUSY; -} - -static long ehea_plpar_hcall9(unsigned long opcode, - unsigned long *outs, /* array of 9 outputs */ - unsigned long arg1, - unsigned long arg2, - unsigned long arg3, - unsigned long arg4, - unsigned long arg5, - unsigned long arg6, - unsigned long arg7, - unsigned long arg8, - unsigned long arg9) -{ - long ret; - int i, sleep_msecs; - u8 cb_cat; - - for (i = 0; i < 5; i++) { - ret = plpar_hcall9(opcode, outs, - arg1, arg2, arg3, arg4, arg5, - arg6, arg7, arg8, arg9); - - if (H_IS_LONG_BUSY(ret)) { - sleep_msecs = get_longbusy_msecs(ret); - msleep_interruptible(sleep_msecs); - continue; - } - - cb_cat = EHEA_BMASK_GET(H_MEHEAPORT_CAT, arg2); - - if ((ret < H_SUCCESS) && !(((ret == H_AUTHORITY) - && (opcode == H_MODIFY_HEA_PORT)) - && (((cb_cat == H_PORT_CB4) && ((arg3 == H_PORT_CB4_JUMBO) - || (arg3 == H_PORT_CB4_SPEED))) || ((cb_cat == H_PORT_CB7) - && (arg3 == H_PORT_CB7_DUCQPN))))) - pr_err("opcode=%lx ret=%lx" - " arg1=%lx arg2=%lx arg3=%lx arg4=%lx" - " arg5=%lx arg6=%lx arg7=%lx arg8=%lx" - " arg9=%lx" - " out1=%lx out2=%lx out3=%lx out4=%lx" - " out5=%lx out6=%lx out7=%lx out8=%lx" - " out9=%lx\n", - opcode, ret, - arg1, arg2, arg3, arg4, arg5, - arg6, arg7, arg8, arg9, - outs[0], outs[1], outs[2], outs[3], outs[4], - outs[5], outs[6], outs[7], outs[8]); - return ret; - } - - return H_BUSY; -} - -u64 ehea_h_query_ehea_qp(const u64 adapter_handle, const u8 qp_category, - const u64 qp_handle, const u64 sel_mask, void *cb_addr) -{ - return ehea_plpar_hcall_norets(H_QUERY_HEA_QP, - adapter_handle, /* R4 */ - qp_category, /* R5 */ - qp_handle, /* R6 */ - sel_mask, /* R7 */ - __pa(cb_addr), /* R8 */ - 0, 0); -} - -/* input param R5 */ -#define H_ALL_RES_QP_EQPO EHEA_BMASK_IBM(9, 11) -#define H_ALL_RES_QP_QPP EHEA_BMASK_IBM(12, 12) -#define H_ALL_RES_QP_RQR EHEA_BMASK_IBM(13, 15) -#define H_ALL_RES_QP_EQEG EHEA_BMASK_IBM(16, 16) -#define H_ALL_RES_QP_LL_QP EHEA_BMASK_IBM(17, 17) -#define H_ALL_RES_QP_DMA128 EHEA_BMASK_IBM(19, 19) -#define H_ALL_RES_QP_HSM EHEA_BMASK_IBM(20, 21) -#define H_ALL_RES_QP_SIGT EHEA_BMASK_IBM(22, 23) -#define H_ALL_RES_QP_TENURE EHEA_BMASK_IBM(48, 55) -#define H_ALL_RES_QP_RES_TYP EHEA_BMASK_IBM(56, 63) - -/* input param R9 */ -#define H_ALL_RES_QP_TOKEN EHEA_BMASK_IBM(0, 31) -#define H_ALL_RES_QP_PD EHEA_BMASK_IBM(32, 63) - -/* input param R10 */ -#define H_ALL_RES_QP_MAX_SWQE EHEA_BMASK_IBM(4, 7) -#define H_ALL_RES_QP_MAX_R1WQE EHEA_BMASK_IBM(12, 15) -#define H_ALL_RES_QP_MAX_R2WQE EHEA_BMASK_IBM(20, 23) -#define H_ALL_RES_QP_MAX_R3WQE EHEA_BMASK_IBM(28, 31) -/* Max Send Scatter Gather Elements */ -#define H_ALL_RES_QP_MAX_SSGE EHEA_BMASK_IBM(37, 39) -#define H_ALL_RES_QP_MAX_R1SGE EHEA_BMASK_IBM(45, 47) -/* Max Receive SG Elements RQ1 */ -#define H_ALL_RES_QP_MAX_R2SGE EHEA_BMASK_IBM(53, 55) -#define H_ALL_RES_QP_MAX_R3SGE EHEA_BMASK_IBM(61, 63) - -/* input param R11 */ -#define H_ALL_RES_QP_SWQE_IDL EHEA_BMASK_IBM(0, 7) -/* max swqe immediate data length */ -#define H_ALL_RES_QP_PORT_NUM EHEA_BMASK_IBM(48, 63) - -/* input param R12 */ -#define H_ALL_RES_QP_TH_RQ2 EHEA_BMASK_IBM(0, 15) -/* Threshold RQ2 */ -#define H_ALL_RES_QP_TH_RQ3 EHEA_BMASK_IBM(16, 31) -/* Threshold RQ3 */ - -/* output param R6 */ -#define H_ALL_RES_QP_ACT_SWQE EHEA_BMASK_IBM(0, 15) -#define H_ALL_RES_QP_ACT_R1WQE EHEA_BMASK_IBM(16, 31) -#define H_ALL_RES_QP_ACT_R2WQE EHEA_BMASK_IBM(32, 47) -#define H_ALL_RES_QP_ACT_R3WQE EHEA_BMASK_IBM(48, 63) - -/* output param, R7 */ -#define H_ALL_RES_QP_ACT_SSGE EHEA_BMASK_IBM(0, 7) -#define H_ALL_RES_QP_ACT_R1SGE EHEA_BMASK_IBM(8, 15) -#define H_ALL_RES_QP_ACT_R2SGE EHEA_BMASK_IBM(16, 23) -#define H_ALL_RES_QP_ACT_R3SGE EHEA_BMASK_IBM(24, 31) -#define H_ALL_RES_QP_ACT_SWQE_IDL EHEA_BMASK_IBM(32, 39) - -/* output param R8,R9 */ -#define H_ALL_RES_QP_SIZE_SQ EHEA_BMASK_IBM(0, 31) -#define H_ALL_RES_QP_SIZE_RQ1 EHEA_BMASK_IBM(32, 63) -#define H_ALL_RES_QP_SIZE_RQ2 EHEA_BMASK_IBM(0, 31) -#define H_ALL_RES_QP_SIZE_RQ3 EHEA_BMASK_IBM(32, 63) - -/* output param R11,R12 */ -#define H_ALL_RES_QP_LIOBN_SQ EHEA_BMASK_IBM(0, 31) -#define H_ALL_RES_QP_LIOBN_RQ1 EHEA_BMASK_IBM(32, 63) -#define H_ALL_RES_QP_LIOBN_RQ2 EHEA_BMASK_IBM(0, 31) -#define H_ALL_RES_QP_LIOBN_RQ3 EHEA_BMASK_IBM(32, 63) - -u64 ehea_h_alloc_resource_qp(const u64 adapter_handle, - struct ehea_qp_init_attr *init_attr, const u32 pd, - u64 *qp_handle, struct h_epas *h_epas) -{ - u64 hret; - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - - u64 allocate_controls = - EHEA_BMASK_SET(H_ALL_RES_QP_EQPO, init_attr->low_lat_rq1 ? 1 : 0) - | EHEA_BMASK_SET(H_ALL_RES_QP_QPP, 0) - | EHEA_BMASK_SET(H_ALL_RES_QP_RQR, 6) /* rq1 & rq2 & rq3 */ - | EHEA_BMASK_SET(H_ALL_RES_QP_EQEG, 0) /* EQE gen. disabled */ - | EHEA_BMASK_SET(H_ALL_RES_QP_LL_QP, init_attr->low_lat_rq1) - | EHEA_BMASK_SET(H_ALL_RES_QP_DMA128, 0) - | EHEA_BMASK_SET(H_ALL_RES_QP_HSM, 0) - | EHEA_BMASK_SET(H_ALL_RES_QP_SIGT, init_attr->signalingtype) - | EHEA_BMASK_SET(H_ALL_RES_QP_RES_TYP, H_ALL_RES_TYPE_QP); - - u64 r9_reg = EHEA_BMASK_SET(H_ALL_RES_QP_PD, pd) - | EHEA_BMASK_SET(H_ALL_RES_QP_TOKEN, init_attr->qp_token); - - u64 max_r10_reg = - EHEA_BMASK_SET(H_ALL_RES_QP_MAX_SWQE, - get_order_of_qentries(init_attr->max_nr_send_wqes)) - | EHEA_BMASK_SET(H_ALL_RES_QP_MAX_R1WQE, - get_order_of_qentries(init_attr->max_nr_rwqes_rq1)) - | EHEA_BMASK_SET(H_ALL_RES_QP_MAX_R2WQE, - get_order_of_qentries(init_attr->max_nr_rwqes_rq2)) - | EHEA_BMASK_SET(H_ALL_RES_QP_MAX_R3WQE, - get_order_of_qentries(init_attr->max_nr_rwqes_rq3)) - | EHEA_BMASK_SET(H_ALL_RES_QP_MAX_SSGE, init_attr->wqe_size_enc_sq) - | EHEA_BMASK_SET(H_ALL_RES_QP_MAX_R1SGE, - init_attr->wqe_size_enc_rq1) - | EHEA_BMASK_SET(H_ALL_RES_QP_MAX_R2SGE, - init_attr->wqe_size_enc_rq2) - | EHEA_BMASK_SET(H_ALL_RES_QP_MAX_R3SGE, - init_attr->wqe_size_enc_rq3); - - u64 r11_in = - EHEA_BMASK_SET(H_ALL_RES_QP_SWQE_IDL, init_attr->swqe_imm_data_len) - | EHEA_BMASK_SET(H_ALL_RES_QP_PORT_NUM, init_attr->port_nr); - u64 threshold = - EHEA_BMASK_SET(H_ALL_RES_QP_TH_RQ2, init_attr->rq2_threshold) - | EHEA_BMASK_SET(H_ALL_RES_QP_TH_RQ3, init_attr->rq3_threshold); - - hret = ehea_plpar_hcall9(H_ALLOC_HEA_RESOURCE, - outs, - adapter_handle, /* R4 */ - allocate_controls, /* R5 */ - init_attr->send_cq_handle, /* R6 */ - init_attr->recv_cq_handle, /* R7 */ - init_attr->aff_eq_handle, /* R8 */ - r9_reg, /* R9 */ - max_r10_reg, /* R10 */ - r11_in, /* R11 */ - threshold); /* R12 */ - - *qp_handle = outs[0]; - init_attr->qp_nr = (u32)outs[1]; - - init_attr->act_nr_send_wqes = - (u16)EHEA_BMASK_GET(H_ALL_RES_QP_ACT_SWQE, outs[2]); - init_attr->act_nr_rwqes_rq1 = - (u16)EHEA_BMASK_GET(H_ALL_RES_QP_ACT_R1WQE, outs[2]); - init_attr->act_nr_rwqes_rq2 = - (u16)EHEA_BMASK_GET(H_ALL_RES_QP_ACT_R2WQE, outs[2]); - init_attr->act_nr_rwqes_rq3 = - (u16)EHEA_BMASK_GET(H_ALL_RES_QP_ACT_R3WQE, outs[2]); - - init_attr->act_wqe_size_enc_sq = init_attr->wqe_size_enc_sq; - init_attr->act_wqe_size_enc_rq1 = init_attr->wqe_size_enc_rq1; - init_attr->act_wqe_size_enc_rq2 = init_attr->wqe_size_enc_rq2; - init_attr->act_wqe_size_enc_rq3 = init_attr->wqe_size_enc_rq3; - - init_attr->nr_sq_pages = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_SIZE_SQ, outs[4]); - init_attr->nr_rq1_pages = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_SIZE_RQ1, outs[4]); - init_attr->nr_rq2_pages = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_SIZE_RQ2, outs[5]); - init_attr->nr_rq3_pages = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_SIZE_RQ3, outs[5]); - - init_attr->liobn_sq = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_LIOBN_SQ, outs[7]); - init_attr->liobn_rq1 = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_LIOBN_RQ1, outs[7]); - init_attr->liobn_rq2 = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_LIOBN_RQ2, outs[8]); - init_attr->liobn_rq3 = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_LIOBN_RQ3, outs[8]); - - if (!hret) - hcp_epas_ctor(h_epas, outs[6], outs[6]); - - return hret; -} - -u64 ehea_h_alloc_resource_cq(const u64 adapter_handle, - struct ehea_cq_attr *cq_attr, - u64 *cq_handle, struct h_epas *epas) -{ - u64 hret; - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - - hret = ehea_plpar_hcall9(H_ALLOC_HEA_RESOURCE, - outs, - adapter_handle, /* R4 */ - H_ALL_RES_TYPE_CQ, /* R5 */ - cq_attr->eq_handle, /* R6 */ - cq_attr->cq_token, /* R7 */ - cq_attr->max_nr_of_cqes, /* R8 */ - 0, 0, 0, 0); /* R9-R12 */ - - *cq_handle = outs[0]; - cq_attr->act_nr_of_cqes = outs[3]; - cq_attr->nr_pages = outs[4]; - - if (!hret) - hcp_epas_ctor(epas, outs[5], outs[6]); - - return hret; -} - -/* Defines for H_CALL H_ALLOC_RESOURCE */ -#define H_ALL_RES_TYPE_QP 1 -#define H_ALL_RES_TYPE_CQ 2 -#define H_ALL_RES_TYPE_EQ 3 -#define H_ALL_RES_TYPE_MR 5 -#define H_ALL_RES_TYPE_MW 6 - -/* input param R5 */ -#define H_ALL_RES_EQ_NEQ EHEA_BMASK_IBM(0, 0) -#define H_ALL_RES_EQ_NON_NEQ_ISN EHEA_BMASK_IBM(6, 7) -#define H_ALL_RES_EQ_INH_EQE_GEN EHEA_BMASK_IBM(16, 16) -#define H_ALL_RES_EQ_RES_TYPE EHEA_BMASK_IBM(56, 63) -/* input param R6 */ -#define H_ALL_RES_EQ_MAX_EQE EHEA_BMASK_IBM(32, 63) - -/* output param R6 */ -#define H_ALL_RES_EQ_LIOBN EHEA_BMASK_IBM(32, 63) - -/* output param R7 */ -#define H_ALL_RES_EQ_ACT_EQE EHEA_BMASK_IBM(32, 63) - -/* output param R8 */ -#define H_ALL_RES_EQ_ACT_PS EHEA_BMASK_IBM(32, 63) - -/* output param R9 */ -#define H_ALL_RES_EQ_ACT_EQ_IST_C EHEA_BMASK_IBM(30, 31) -#define H_ALL_RES_EQ_ACT_EQ_IST_1 EHEA_BMASK_IBM(40, 63) - -/* output param R10 */ -#define H_ALL_RES_EQ_ACT_EQ_IST_2 EHEA_BMASK_IBM(40, 63) - -/* output param R11 */ -#define H_ALL_RES_EQ_ACT_EQ_IST_3 EHEA_BMASK_IBM(40, 63) - -/* output param R12 */ -#define H_ALL_RES_EQ_ACT_EQ_IST_4 EHEA_BMASK_IBM(40, 63) - -u64 ehea_h_alloc_resource_eq(const u64 adapter_handle, - struct ehea_eq_attr *eq_attr, u64 *eq_handle) -{ - u64 hret, allocate_controls; - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - - /* resource type */ - allocate_controls = - EHEA_BMASK_SET(H_ALL_RES_EQ_RES_TYPE, H_ALL_RES_TYPE_EQ) - | EHEA_BMASK_SET(H_ALL_RES_EQ_NEQ, eq_attr->type ? 1 : 0) - | EHEA_BMASK_SET(H_ALL_RES_EQ_INH_EQE_GEN, !eq_attr->eqe_gen) - | EHEA_BMASK_SET(H_ALL_RES_EQ_NON_NEQ_ISN, 1); - - hret = ehea_plpar_hcall9(H_ALLOC_HEA_RESOURCE, - outs, - adapter_handle, /* R4 */ - allocate_controls, /* R5 */ - eq_attr->max_nr_of_eqes, /* R6 */ - 0, 0, 0, 0, 0, 0); /* R7-R10 */ - - *eq_handle = outs[0]; - eq_attr->act_nr_of_eqes = outs[3]; - eq_attr->nr_pages = outs[4]; - eq_attr->ist1 = outs[5]; - eq_attr->ist2 = outs[6]; - eq_attr->ist3 = outs[7]; - eq_attr->ist4 = outs[8]; - - return hret; -} - -u64 ehea_h_modify_ehea_qp(const u64 adapter_handle, const u8 cat, - const u64 qp_handle, const u64 sel_mask, - void *cb_addr, u64 *inv_attr_id, u64 *proc_mask, - u16 *out_swr, u16 *out_rwr) -{ - u64 hret; - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - - hret = ehea_plpar_hcall9(H_MODIFY_HEA_QP, - outs, - adapter_handle, /* R4 */ - (u64) cat, /* R5 */ - qp_handle, /* R6 */ - sel_mask, /* R7 */ - __pa(cb_addr), /* R8 */ - 0, 0, 0, 0); /* R9-R12 */ - - *inv_attr_id = outs[0]; - *out_swr = outs[3]; - *out_rwr = outs[4]; - *proc_mask = outs[5]; - - return hret; -} - -u64 ehea_h_register_rpage(const u64 adapter_handle, const u8 pagesize, - const u8 queue_type, const u64 resource_handle, - const u64 log_pageaddr, u64 count) -{ - u64 reg_control; - - reg_control = EHEA_BMASK_SET(H_REG_RPAGE_PAGE_SIZE, pagesize) - | EHEA_BMASK_SET(H_REG_RPAGE_QT, queue_type); - - return ehea_plpar_hcall_norets(H_REGISTER_HEA_RPAGES, - adapter_handle, /* R4 */ - reg_control, /* R5 */ - resource_handle, /* R6 */ - log_pageaddr, /* R7 */ - count, /* R8 */ - 0, 0); /* R9-R10 */ -} - -u64 ehea_h_register_smr(const u64 adapter_handle, const u64 orig_mr_handle, - const u64 vaddr_in, const u32 access_ctrl, const u32 pd, - struct ehea_mr *mr) -{ - u64 hret; - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - - hret = ehea_plpar_hcall9(H_REGISTER_SMR, - outs, - adapter_handle , /* R4 */ - orig_mr_handle, /* R5 */ - vaddr_in, /* R6 */ - (((u64)access_ctrl) << 32ULL), /* R7 */ - pd, /* R8 */ - 0, 0, 0, 0); /* R9-R12 */ - - mr->handle = outs[0]; - mr->lkey = (u32)outs[2]; - - return hret; -} - -u64 ehea_h_disable_and_get_hea(const u64 adapter_handle, const u64 qp_handle) -{ - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - - return ehea_plpar_hcall9(H_DISABLE_AND_GET_HEA, - outs, - adapter_handle, /* R4 */ - H_DISABLE_GET_EHEA_WQE_P, /* R5 */ - qp_handle, /* R6 */ - 0, 0, 0, 0, 0, 0); /* R7-R12 */ -} - -u64 ehea_h_free_resource(const u64 adapter_handle, const u64 res_handle, - u64 force_bit) -{ - return ehea_plpar_hcall_norets(H_FREE_RESOURCE, - adapter_handle, /* R4 */ - res_handle, /* R5 */ - force_bit, - 0, 0, 0, 0); /* R7-R10 */ -} - -u64 ehea_h_alloc_resource_mr(const u64 adapter_handle, const u64 vaddr, - const u64 length, const u32 access_ctrl, - const u32 pd, u64 *mr_handle, u32 *lkey) -{ - u64 hret; - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - - hret = ehea_plpar_hcall9(H_ALLOC_HEA_RESOURCE, - outs, - adapter_handle, /* R4 */ - 5, /* R5 */ - vaddr, /* R6 */ - length, /* R7 */ - (((u64) access_ctrl) << 32ULL), /* R8 */ - pd, /* R9 */ - 0, 0, 0); /* R10-R12 */ - - *mr_handle = outs[0]; - *lkey = (u32)outs[2]; - return hret; -} - -u64 ehea_h_register_rpage_mr(const u64 adapter_handle, const u64 mr_handle, - const u8 pagesize, const u8 queue_type, - const u64 log_pageaddr, const u64 count) -{ - if ((count > 1) && (log_pageaddr & ~PAGE_MASK)) { - pr_err("not on pageboundary\n"); - return H_PARAMETER; - } - - return ehea_h_register_rpage(adapter_handle, pagesize, - queue_type, mr_handle, - log_pageaddr, count); -} - -u64 ehea_h_query_ehea(const u64 adapter_handle, void *cb_addr) -{ - u64 hret, cb_logaddr; - - cb_logaddr = __pa(cb_addr); - - hret = ehea_plpar_hcall_norets(H_QUERY_HEA, - adapter_handle, /* R4 */ - cb_logaddr, /* R5 */ - 0, 0, 0, 0, 0); /* R6-R10 */ -#ifdef DEBUG - ehea_dump(cb_addr, sizeof(struct hcp_query_ehea), "hcp_query_ehea"); -#endif - return hret; -} - -u64 ehea_h_query_ehea_port(const u64 adapter_handle, const u16 port_num, - const u8 cb_cat, const u64 select_mask, - void *cb_addr) -{ - u64 port_info; - u64 cb_logaddr = __pa(cb_addr); - u64 arr_index = 0; - - port_info = EHEA_BMASK_SET(H_MEHEAPORT_CAT, cb_cat) - | EHEA_BMASK_SET(H_MEHEAPORT_PN, port_num); - - return ehea_plpar_hcall_norets(H_QUERY_HEA_PORT, - adapter_handle, /* R4 */ - port_info, /* R5 */ - select_mask, /* R6 */ - arr_index, /* R7 */ - cb_logaddr, /* R8 */ - 0, 0); /* R9-R10 */ -} - -u64 ehea_h_modify_ehea_port(const u64 adapter_handle, const u16 port_num, - const u8 cb_cat, const u64 select_mask, - void *cb_addr) -{ - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - u64 port_info; - u64 arr_index = 0; - u64 cb_logaddr = __pa(cb_addr); - - port_info = EHEA_BMASK_SET(H_MEHEAPORT_CAT, cb_cat) - | EHEA_BMASK_SET(H_MEHEAPORT_PN, port_num); -#ifdef DEBUG - ehea_dump(cb_addr, sizeof(struct hcp_ehea_port_cb0), "Before HCALL"); -#endif - return ehea_plpar_hcall9(H_MODIFY_HEA_PORT, - outs, - adapter_handle, /* R4 */ - port_info, /* R5 */ - select_mask, /* R6 */ - arr_index, /* R7 */ - cb_logaddr, /* R8 */ - 0, 0, 0, 0); /* R9-R12 */ -} - -u64 ehea_h_reg_dereg_bcmc(const u64 adapter_handle, const u16 port_num, - const u8 reg_type, const u64 mc_mac_addr, - const u16 vlan_id, const u32 hcall_id) -{ - u64 r5_port_num, r6_reg_type, r7_mc_mac_addr, r8_vlan_id; - u64 mac_addr = mc_mac_addr >> 16; - - r5_port_num = EHEA_BMASK_SET(H_REGBCMC_PN, port_num); - r6_reg_type = EHEA_BMASK_SET(H_REGBCMC_REGTYPE, reg_type); - r7_mc_mac_addr = EHEA_BMASK_SET(H_REGBCMC_MACADDR, mac_addr); - r8_vlan_id = EHEA_BMASK_SET(H_REGBCMC_VLANID, vlan_id); - - return ehea_plpar_hcall_norets(hcall_id, - adapter_handle, /* R4 */ - r5_port_num, /* R5 */ - r6_reg_type, /* R6 */ - r7_mc_mac_addr, /* R7 */ - r8_vlan_id, /* R8 */ - 0, 0); /* R9-R12 */ -} - -u64 ehea_h_reset_events(const u64 adapter_handle, const u64 neq_handle, - const u64 event_mask) -{ - return ehea_plpar_hcall_norets(H_RESET_EVENTS, - adapter_handle, /* R4 */ - neq_handle, /* R5 */ - event_mask, /* R6 */ - 0, 0, 0, 0); /* R7-R12 */ -} - -u64 ehea_h_error_data(const u64 adapter_handle, const u64 ressource_handle, - void *rblock) -{ - return ehea_plpar_hcall_norets(H_ERROR_DATA, - adapter_handle, /* R4 */ - ressource_handle, /* R5 */ - __pa(rblock), /* R6 */ - 0, 0, 0, 0); /* R7-R12 */ -} diff --git a/drivers/net/ethernet/ibm/ehea/ehea_phyp.h b/drivers/net/ethernet/ibm/ehea/ehea_phyp.h deleted file mode 100644 index e8b56c103410..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea_phyp.h +++ /dev/null @@ -1,433 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea_phyp.h - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch - * Jan-Bernd Themann - * Thomas Klein - */ - -#ifndef __EHEA_PHYP_H__ -#define __EHEA_PHYP_H__ - -#include -#include -#include "ehea.h" -#include "ehea_hw.h" - -/* Some abbreviations used here: - * - * hcp_* - structures, variables and functions releated to Hypervisor Calls - */ - -/* Number of pages which can be registered at once by H_REGISTER_HEA_RPAGES */ -#define EHEA_MAX_RPAGE 512 - -/* Notification Event Queue (NEQ) Entry bit masks */ -#define NEQE_EVENT_CODE EHEA_BMASK_IBM(2, 7) -#define NEQE_PORTNUM EHEA_BMASK_IBM(32, 47) -#define NEQE_PORT_UP EHEA_BMASK_IBM(16, 16) -#define NEQE_EXTSWITCH_PORT_UP EHEA_BMASK_IBM(17, 17) -#define NEQE_EXTSWITCH_PRIMARY EHEA_BMASK_IBM(18, 18) -#define NEQE_PLID EHEA_BMASK_IBM(16, 47) - -/* Notification Event Codes */ -#define EHEA_EC_PORTSTATE_CHG 0x30 -#define EHEA_EC_ADAPTER_MALFUNC 0x32 -#define EHEA_EC_PORT_MALFUNC 0x33 - -/* Notification Event Log Register (NELR) bit masks */ -#define NELR_PORT_MALFUNC EHEA_BMASK_IBM(61, 61) -#define NELR_ADAPTER_MALFUNC EHEA_BMASK_IBM(62, 62) -#define NELR_PORTSTATE_CHG EHEA_BMASK_IBM(63, 63) - -static inline void hcp_epas_ctor(struct h_epas *epas, u64 paddr_kernel, - u64 paddr_user) -{ - /* To support 64k pages we must round to 64k page boundary */ - epas->kernel.addr = ioremap((paddr_kernel & PAGE_MASK), PAGE_SIZE) + - (paddr_kernel & ~PAGE_MASK); - epas->user.addr = paddr_user; -} - -static inline void hcp_epas_dtor(struct h_epas *epas) -{ - if (epas->kernel.addr) - iounmap((void __iomem *)((u64)epas->kernel.addr & PAGE_MASK)); - - epas->user.addr = 0; - epas->kernel.addr = 0; -} - -struct hcp_modify_qp_cb0 { - u64 qp_ctl_reg; /* 00 */ - u32 max_swqe; /* 02 */ - u32 max_rwqe; /* 03 */ - u32 port_nb; /* 04 */ - u32 reserved0; /* 05 */ - u64 qp_aer; /* 06 */ - u64 qp_tenure; /* 08 */ -}; - -/* Hcall Query/Modify Queue Pair Control Block 0 Selection Mask Bits */ -#define H_QPCB0_ALL EHEA_BMASK_IBM(0, 5) -#define H_QPCB0_QP_CTL_REG EHEA_BMASK_IBM(0, 0) -#define H_QPCB0_MAX_SWQE EHEA_BMASK_IBM(1, 1) -#define H_QPCB0_MAX_RWQE EHEA_BMASK_IBM(2, 2) -#define H_QPCB0_PORT_NB EHEA_BMASK_IBM(3, 3) -#define H_QPCB0_QP_AER EHEA_BMASK_IBM(4, 4) -#define H_QPCB0_QP_TENURE EHEA_BMASK_IBM(5, 5) - -/* Queue Pair Control Register Status Bits */ -#define H_QP_CR_ENABLED 0x8000000000000000ULL /* QP enabled */ - /* QP States: */ -#define H_QP_CR_STATE_RESET 0x0000010000000000ULL /* Reset */ -#define H_QP_CR_STATE_INITIALIZED 0x0000020000000000ULL /* Initialized */ -#define H_QP_CR_STATE_RDY2RCV 0x0000030000000000ULL /* Ready to recv */ -#define H_QP_CR_STATE_RDY2SND 0x0000050000000000ULL /* Ready to send */ -#define H_QP_CR_STATE_ERROR 0x0000800000000000ULL /* Error */ -#define H_QP_CR_RES_STATE 0x0000007F00000000ULL /* Resultant state */ - -struct hcp_modify_qp_cb1 { - u32 qpn; /* 00 */ - u32 qp_asyn_ev_eq_nb; /* 01 */ - u64 sq_cq_handle; /* 02 */ - u64 rq_cq_handle; /* 04 */ - /* sgel = scatter gather element */ - u32 sgel_nb_sq; /* 06 */ - u32 sgel_nb_rq1; /* 07 */ - u32 sgel_nb_rq2; /* 08 */ - u32 sgel_nb_rq3; /* 09 */ -}; - -/* Hcall Query/Modify Queue Pair Control Block 1 Selection Mask Bits */ -#define H_QPCB1_ALL EHEA_BMASK_IBM(0, 7) -#define H_QPCB1_QPN EHEA_BMASK_IBM(0, 0) -#define H_QPCB1_ASYN_EV_EQ_NB EHEA_BMASK_IBM(1, 1) -#define H_QPCB1_SQ_CQ_HANDLE EHEA_BMASK_IBM(2, 2) -#define H_QPCB1_RQ_CQ_HANDLE EHEA_BMASK_IBM(3, 3) -#define H_QPCB1_SGEL_NB_SQ EHEA_BMASK_IBM(4, 4) -#define H_QPCB1_SGEL_NB_RQ1 EHEA_BMASK_IBM(5, 5) -#define H_QPCB1_SGEL_NB_RQ2 EHEA_BMASK_IBM(6, 6) -#define H_QPCB1_SGEL_NB_RQ3 EHEA_BMASK_IBM(7, 7) - -struct hcp_query_ehea { - u32 cur_num_qps; /* 00 */ - u32 cur_num_cqs; /* 01 */ - u32 cur_num_eqs; /* 02 */ - u32 cur_num_mrs; /* 03 */ - u32 auth_level; /* 04 */ - u32 max_num_qps; /* 05 */ - u32 max_num_cqs; /* 06 */ - u32 max_num_eqs; /* 07 */ - u32 max_num_mrs; /* 08 */ - u32 reserved0; /* 09 */ - u32 int_clock_freq; /* 10 */ - u32 max_num_pds; /* 11 */ - u32 max_num_addr_handles; /* 12 */ - u32 max_num_cqes; /* 13 */ - u32 max_num_wqes; /* 14 */ - u32 max_num_sgel_rq1wqe; /* 15 */ - u32 max_num_sgel_rq2wqe; /* 16 */ - u32 max_num_sgel_rq3wqe; /* 17 */ - u32 mr_page_size; /* 18 */ - u32 reserved1; /* 19 */ - u64 max_mr_size; /* 20 */ - u64 reserved2; /* 22 */ - u32 num_ports; /* 24 */ - u32 reserved3; /* 25 */ - u32 reserved4; /* 26 */ - u32 reserved5; /* 27 */ - u64 max_mc_mac; /* 28 */ - u64 ehea_cap; /* 30 */ - u32 max_isn_per_eq; /* 32 */ - u32 max_num_neq; /* 33 */ - u64 max_num_vlan_ids; /* 34 */ - u32 max_num_port_group; /* 36 */ - u32 max_num_phys_port; /* 37 */ - -}; - -/* Hcall Query/Modify Port Control Block defines */ -#define H_PORT_CB0 0 -#define H_PORT_CB1 1 -#define H_PORT_CB2 2 -#define H_PORT_CB3 3 -#define H_PORT_CB4 4 -#define H_PORT_CB5 5 -#define H_PORT_CB6 6 -#define H_PORT_CB7 7 - -struct hcp_ehea_port_cb0 { - u64 port_mac_addr; - u64 port_rc; - u64 reserved0; - u32 port_op_state; - u32 port_speed; - u32 ext_swport_op_state; - u32 neg_tpf_prpf; - u32 num_default_qps; - u32 reserved1; - u64 default_qpn_arr[16]; -}; - -/* Hcall Query/Modify Port Control Block 0 Selection Mask Bits */ -#define H_PORT_CB0_ALL EHEA_BMASK_IBM(0, 7) /* Set all bits */ -#define H_PORT_CB0_MAC EHEA_BMASK_IBM(0, 0) /* MAC address */ -#define H_PORT_CB0_PRC EHEA_BMASK_IBM(1, 1) /* Port Recv Control */ -#define H_PORT_CB0_DEFQPNARRAY EHEA_BMASK_IBM(7, 7) /* Default QPN Array */ - -/* Hcall Query Port: Returned port speed values */ -#define H_SPEED_10M_H 1 /* 10 Mbps, Half Duplex */ -#define H_SPEED_10M_F 2 /* 10 Mbps, Full Duplex */ -#define H_SPEED_100M_H 3 /* 100 Mbps, Half Duplex */ -#define H_SPEED_100M_F 4 /* 100 Mbps, Full Duplex */ -#define H_SPEED_1G_F 6 /* 1 Gbps, Full Duplex */ -#define H_SPEED_10G_F 8 /* 10 Gbps, Full Duplex */ - -/* Port Receive Control Status Bits */ -#define PXLY_RC_VALID EHEA_BMASK_IBM(49, 49) -#define PXLY_RC_VLAN_XTRACT EHEA_BMASK_IBM(50, 50) -#define PXLY_RC_TCP_6_TUPLE EHEA_BMASK_IBM(51, 51) -#define PXLY_RC_UDP_6_TUPLE EHEA_BMASK_IBM(52, 52) -#define PXLY_RC_TCP_3_TUPLE EHEA_BMASK_IBM(53, 53) -#define PXLY_RC_TCP_2_TUPLE EHEA_BMASK_IBM(54, 54) -#define PXLY_RC_LLC_SNAP EHEA_BMASK_IBM(55, 55) -#define PXLY_RC_JUMBO_FRAME EHEA_BMASK_IBM(56, 56) -#define PXLY_RC_FRAG_IP_PKT EHEA_BMASK_IBM(57, 57) -#define PXLY_RC_TCP_UDP_CHKSUM EHEA_BMASK_IBM(58, 58) -#define PXLY_RC_IP_CHKSUM EHEA_BMASK_IBM(59, 59) -#define PXLY_RC_MAC_FILTER EHEA_BMASK_IBM(60, 60) -#define PXLY_RC_UNTAG_FILTER EHEA_BMASK_IBM(61, 61) -#define PXLY_RC_VLAN_TAG_FILTER EHEA_BMASK_IBM(62, 63) - -#define PXLY_RC_VLAN_FILTER 2 -#define PXLY_RC_VLAN_PERM 0 - - -#define H_PORT_CB1_ALL 0x8000000000000000ULL - -struct hcp_ehea_port_cb1 { - u64 vlan_filter[64]; -}; - -#define H_PORT_CB2_ALL 0xFFE0000000000000ULL - -struct hcp_ehea_port_cb2 { - u64 rxo; - u64 rxucp; - u64 rxufd; - u64 rxuerr; - u64 rxftl; - u64 rxmcp; - u64 rxbcp; - u64 txo; - u64 txucp; - u64 txmcp; - u64 txbcp; -}; - -struct hcp_ehea_port_cb3 { - u64 vlan_bc_filter[64]; - u64 vlan_mc_filter[64]; - u64 vlan_un_filter[64]; - u64 port_mac_hash_array[64]; -}; - -#define H_PORT_CB4_ALL 0xF000000000000000ULL -#define H_PORT_CB4_JUMBO 0x1000000000000000ULL -#define H_PORT_CB4_SPEED 0x8000000000000000ULL - -struct hcp_ehea_port_cb4 { - u32 port_speed; - u32 pause_frame; - u32 ens_port_op_state; - u32 jumbo_frame; - u32 ens_port_wrap; -}; - -/* Hcall Query/Modify Port Control Block 5 Selection Mask Bits */ -#define H_PORT_CB5_RCU 0x0001000000000000ULL -#define PXS_RCU EHEA_BMASK_IBM(61, 63) - -struct hcp_ehea_port_cb5 { - u64 prc; /* 00 */ - u64 uaa; /* 01 */ - u64 macvc; /* 02 */ - u64 xpcsc; /* 03 */ - u64 xpcsp; /* 04 */ - u64 pcsid; /* 05 */ - u64 xpcsst; /* 06 */ - u64 pthlb; /* 07 */ - u64 pthrb; /* 08 */ - u64 pqu; /* 09 */ - u64 pqd; /* 10 */ - u64 prt; /* 11 */ - u64 wsth; /* 12 */ - u64 rcb; /* 13 */ - u64 rcm; /* 14 */ - u64 rcu; /* 15 */ - u64 macc; /* 16 */ - u64 pc; /* 17 */ - u64 pst; /* 18 */ - u64 ducqpn; /* 19 */ - u64 mcqpn; /* 20 */ - u64 mma; /* 21 */ - u64 pmc0h; /* 22 */ - u64 pmc0l; /* 23 */ - u64 lbc; /* 24 */ -}; - -#define H_PORT_CB6_ALL 0xFFFFFE7FFFFF8000ULL - -struct hcp_ehea_port_cb6 { - u64 rxo; /* 00 */ - u64 rx64; /* 01 */ - u64 rx65; /* 02 */ - u64 rx128; /* 03 */ - u64 rx256; /* 04 */ - u64 rx512; /* 05 */ - u64 rx1024; /* 06 */ - u64 rxbfcs; /* 07 */ - u64 rxime; /* 08 */ - u64 rxrle; /* 09 */ - u64 rxorle; /* 10 */ - u64 rxftl; /* 11 */ - u64 rxjab; /* 12 */ - u64 rxse; /* 13 */ - u64 rxce; /* 14 */ - u64 rxrf; /* 15 */ - u64 rxfrag; /* 16 */ - u64 rxuoc; /* 17 */ - u64 rxcpf; /* 18 */ - u64 rxsb; /* 19 */ - u64 rxfd; /* 20 */ - u64 rxoerr; /* 21 */ - u64 rxaln; /* 22 */ - u64 ducqpn; /* 23 */ - u64 reserved0; /* 24 */ - u64 rxmcp; /* 25 */ - u64 rxbcp; /* 26 */ - u64 txmcp; /* 27 */ - u64 txbcp; /* 28 */ - u64 txo; /* 29 */ - u64 tx64; /* 30 */ - u64 tx65; /* 31 */ - u64 tx128; /* 32 */ - u64 tx256; /* 33 */ - u64 tx512; /* 34 */ - u64 tx1024; /* 35 */ - u64 txbfcs; /* 36 */ - u64 txcpf; /* 37 */ - u64 txlf; /* 38 */ - u64 txrf; /* 39 */ - u64 txime; /* 40 */ - u64 txsc; /* 41 */ - u64 txmc; /* 42 */ - u64 txsqe; /* 43 */ - u64 txdef; /* 44 */ - u64 txlcol; /* 45 */ - u64 txexcol; /* 46 */ - u64 txcse; /* 47 */ - u64 txbor; /* 48 */ -}; - -#define H_PORT_CB7_DUCQPN 0x8000000000000000ULL - -struct hcp_ehea_port_cb7 { - u64 def_uc_qpn; -}; - -u64 ehea_h_query_ehea_qp(const u64 adapter_handle, - const u8 qp_category, - const u64 qp_handle, const u64 sel_mask, - void *cb_addr); - -u64 ehea_h_modify_ehea_qp(const u64 adapter_handle, - const u8 cat, - const u64 qp_handle, - const u64 sel_mask, - void *cb_addr, - u64 *inv_attr_id, - u64 *proc_mask, u16 *out_swr, u16 *out_rwr); - -u64 ehea_h_alloc_resource_eq(const u64 adapter_handle, - struct ehea_eq_attr *eq_attr, u64 *eq_handle); - -u64 ehea_h_alloc_resource_cq(const u64 adapter_handle, - struct ehea_cq_attr *cq_attr, - u64 *cq_handle, struct h_epas *epas); - -u64 ehea_h_alloc_resource_qp(const u64 adapter_handle, - struct ehea_qp_init_attr *init_attr, - const u32 pd, - u64 *qp_handle, struct h_epas *h_epas); - -#define H_REG_RPAGE_PAGE_SIZE EHEA_BMASK_IBM(48, 55) -#define H_REG_RPAGE_QT EHEA_BMASK_IBM(62, 63) - -u64 ehea_h_register_rpage(const u64 adapter_handle, - const u8 pagesize, - const u8 queue_type, - const u64 resource_handle, - const u64 log_pageaddr, u64 count); - -#define H_DISABLE_GET_EHEA_WQE_P 1 -#define H_DISABLE_GET_SQ_WQE_P 2 -#define H_DISABLE_GET_RQC 3 - -u64 ehea_h_disable_and_get_hea(const u64 adapter_handle, const u64 qp_handle); - -#define FORCE_FREE 1 -#define NORMAL_FREE 0 - -u64 ehea_h_free_resource(const u64 adapter_handle, const u64 res_handle, - u64 force_bit); - -u64 ehea_h_alloc_resource_mr(const u64 adapter_handle, const u64 vaddr, - const u64 length, const u32 access_ctrl, - const u32 pd, u64 *mr_handle, u32 *lkey); - -u64 ehea_h_register_rpage_mr(const u64 adapter_handle, const u64 mr_handle, - const u8 pagesize, const u8 queue_type, - const u64 log_pageaddr, const u64 count); - -u64 ehea_h_register_smr(const u64 adapter_handle, const u64 orig_mr_handle, - const u64 vaddr_in, const u32 access_ctrl, const u32 pd, - struct ehea_mr *mr); - -u64 ehea_h_query_ehea(const u64 adapter_handle, void *cb_addr); - -/* output param R5 */ -#define H_MEHEAPORT_CAT EHEA_BMASK_IBM(40, 47) -#define H_MEHEAPORT_PN EHEA_BMASK_IBM(48, 63) - -u64 ehea_h_query_ehea_port(const u64 adapter_handle, const u16 port_num, - const u8 cb_cat, const u64 select_mask, - void *cb_addr); - -u64 ehea_h_modify_ehea_port(const u64 adapter_handle, const u16 port_num, - const u8 cb_cat, const u64 select_mask, - void *cb_addr); - -#define H_REGBCMC_PN EHEA_BMASK_IBM(48, 63) -#define H_REGBCMC_REGTYPE EHEA_BMASK_IBM(60, 63) -#define H_REGBCMC_MACADDR EHEA_BMASK_IBM(16, 63) -#define H_REGBCMC_VLANID EHEA_BMASK_IBM(52, 63) - -u64 ehea_h_reg_dereg_bcmc(const u64 adapter_handle, const u16 port_num, - const u8 reg_type, const u64 mc_mac_addr, - const u16 vlan_id, const u32 hcall_id); - -u64 ehea_h_reset_events(const u64 adapter_handle, const u64 neq_handle, - const u64 event_mask); - -u64 ehea_h_error_data(const u64 adapter_handle, const u64 ressource_handle, - void *rblock); - -#endif /* __EHEA_PHYP_H__ */ diff --git a/drivers/net/ethernet/ibm/ehea/ehea_qmr.c b/drivers/net/ethernet/ibm/ehea/ehea_qmr.c deleted file mode 100644 index 60629a0032b2..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea_qmr.c +++ /dev/null @@ -1,999 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea_qmr.c - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch - * Jan-Bernd Themann - * Thomas Klein - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include "ehea.h" -#include "ehea_phyp.h" -#include "ehea_qmr.h" - -static struct ehea_bmap *ehea_bmap; - -static void *hw_qpageit_get_inc(struct hw_queue *queue) -{ - void *retvalue = hw_qeit_get(queue); - - queue->current_q_offset += queue->pagesize; - if (queue->current_q_offset > queue->queue_length) { - queue->current_q_offset -= queue->pagesize; - retvalue = NULL; - } else if (((u64) retvalue) & (EHEA_PAGESIZE-1)) { - pr_err("not on pageboundary\n"); - retvalue = NULL; - } - return retvalue; -} - -static int hw_queue_ctor(struct hw_queue *queue, const u32 nr_of_pages, - const u32 pagesize, const u32 qe_size) -{ - int pages_per_kpage = PAGE_SIZE / pagesize; - int i, k; - - if ((pagesize > PAGE_SIZE) || (!pages_per_kpage)) { - pr_err("pagesize conflict! kernel pagesize=%d, ehea pagesize=%d\n", - (int)PAGE_SIZE, (int)pagesize); - return -EINVAL; - } - - queue->queue_length = nr_of_pages * pagesize; - queue->queue_pages = kmalloc_array(nr_of_pages, sizeof(void *), - GFP_KERNEL); - if (!queue->queue_pages) - return -ENOMEM; - - /* - * allocate pages for queue: - * outer loop allocates whole kernel pages (page aligned) and - * inner loop divides a kernel page into smaller hea queue pages - */ - i = 0; - while (i < nr_of_pages) { - u8 *kpage = (u8 *)get_zeroed_page(GFP_KERNEL); - if (!kpage) - goto out_nomem; - for (k = 0; k < pages_per_kpage && i < nr_of_pages; k++) { - (queue->queue_pages)[i] = (struct ehea_page *)kpage; - kpage += pagesize; - i++; - } - } - - queue->current_q_offset = 0; - queue->qe_size = qe_size; - queue->pagesize = pagesize; - queue->toggle_state = 1; - - return 0; -out_nomem: - for (i = 0; i < nr_of_pages; i += pages_per_kpage) { - if (!(queue->queue_pages)[i]) - break; - free_page((unsigned long)(queue->queue_pages)[i]); - } - return -ENOMEM; -} - -static void hw_queue_dtor(struct hw_queue *queue) -{ - int pages_per_kpage; - int i, nr_pages; - - if (!queue || !queue->queue_pages) - return; - - pages_per_kpage = PAGE_SIZE / queue->pagesize; - - nr_pages = queue->queue_length / queue->pagesize; - - for (i = 0; i < nr_pages; i += pages_per_kpage) - free_page((unsigned long)(queue->queue_pages)[i]); - - kfree(queue->queue_pages); -} - -struct ehea_cq *ehea_create_cq(struct ehea_adapter *adapter, - int nr_of_cqe, u64 eq_handle, u32 cq_token) -{ - struct ehea_cq *cq; - u64 hret, rpage; - u32 counter; - int ret; - void *vpage; - - cq = kzalloc_obj(*cq); - if (!cq) - goto out_nomem; - - cq->attr.max_nr_of_cqes = nr_of_cqe; - cq->attr.cq_token = cq_token; - cq->attr.eq_handle = eq_handle; - - cq->adapter = adapter; - - hret = ehea_h_alloc_resource_cq(adapter->handle, &cq->attr, - &cq->fw_handle, &cq->epas); - if (hret != H_SUCCESS) { - pr_err("alloc_resource_cq failed\n"); - goto out_freemem; - } - - ret = hw_queue_ctor(&cq->hw_queue, cq->attr.nr_pages, - EHEA_PAGESIZE, sizeof(struct ehea_cqe)); - if (ret) - goto out_freeres; - - for (counter = 0; counter < cq->attr.nr_pages; counter++) { - vpage = hw_qpageit_get_inc(&cq->hw_queue); - if (!vpage) { - pr_err("hw_qpageit_get_inc failed\n"); - goto out_kill_hwq; - } - - rpage = __pa(vpage); - hret = ehea_h_register_rpage(adapter->handle, - 0, EHEA_CQ_REGISTER_ORIG, - cq->fw_handle, rpage, 1); - if (hret < H_SUCCESS) { - pr_err("register_rpage_cq failed ehea_cq=%p hret=%llx counter=%i act_pages=%i\n", - cq, hret, counter, cq->attr.nr_pages); - goto out_kill_hwq; - } - - if (counter == (cq->attr.nr_pages - 1)) { - vpage = hw_qpageit_get_inc(&cq->hw_queue); - - if ((hret != H_SUCCESS) || (vpage)) { - pr_err("registration of pages not complete hret=%llx\n", - hret); - goto out_kill_hwq; - } - } else { - if (hret != H_PAGE_REGISTERED) { - pr_err("CQ: registration of page failed hret=%llx\n", - hret); - goto out_kill_hwq; - } - } - } - - hw_qeit_reset(&cq->hw_queue); - ehea_reset_cq_ep(cq); - ehea_reset_cq_n1(cq); - - return cq; - -out_kill_hwq: - hw_queue_dtor(&cq->hw_queue); - -out_freeres: - ehea_h_free_resource(adapter->handle, cq->fw_handle, FORCE_FREE); - -out_freemem: - kfree(cq); - -out_nomem: - return NULL; -} - -static u64 ehea_destroy_cq_res(struct ehea_cq *cq, u64 force) -{ - u64 hret; - u64 adapter_handle = cq->adapter->handle; - - /* deregister all previous registered pages */ - hret = ehea_h_free_resource(adapter_handle, cq->fw_handle, force); - if (hret != H_SUCCESS) - return hret; - - hw_queue_dtor(&cq->hw_queue); - kfree(cq); - - return hret; -} - -int ehea_destroy_cq(struct ehea_cq *cq) -{ - u64 hret, aer, aerr; - if (!cq) - return 0; - - hcp_epas_dtor(&cq->epas); - hret = ehea_destroy_cq_res(cq, NORMAL_FREE); - if (hret == H_R_STATE) { - ehea_error_data(cq->adapter, cq->fw_handle, &aer, &aerr); - hret = ehea_destroy_cq_res(cq, FORCE_FREE); - } - - if (hret != H_SUCCESS) { - pr_err("destroy CQ failed\n"); - return -EIO; - } - - return 0; -} - -struct ehea_eq *ehea_create_eq(struct ehea_adapter *adapter, - const enum ehea_eq_type type, - const u32 max_nr_of_eqes, const u8 eqe_gen) -{ - int ret, i; - u64 hret, rpage; - void *vpage; - struct ehea_eq *eq; - - eq = kzalloc_obj(*eq); - if (!eq) - return NULL; - - eq->adapter = adapter; - eq->attr.type = type; - eq->attr.max_nr_of_eqes = max_nr_of_eqes; - eq->attr.eqe_gen = eqe_gen; - spin_lock_init(&eq->spinlock); - - hret = ehea_h_alloc_resource_eq(adapter->handle, - &eq->attr, &eq->fw_handle); - if (hret != H_SUCCESS) { - pr_err("alloc_resource_eq failed\n"); - goto out_freemem; - } - - ret = hw_queue_ctor(&eq->hw_queue, eq->attr.nr_pages, - EHEA_PAGESIZE, sizeof(struct ehea_eqe)); - if (ret) { - pr_err("can't allocate eq pages\n"); - goto out_freeres; - } - - for (i = 0; i < eq->attr.nr_pages; i++) { - vpage = hw_qpageit_get_inc(&eq->hw_queue); - if (!vpage) { - pr_err("hw_qpageit_get_inc failed\n"); - hret = H_RESOURCE; - goto out_kill_hwq; - } - - rpage = __pa(vpage); - - hret = ehea_h_register_rpage(adapter->handle, 0, - EHEA_EQ_REGISTER_ORIG, - eq->fw_handle, rpage, 1); - - if (i == (eq->attr.nr_pages - 1)) { - /* last page */ - vpage = hw_qpageit_get_inc(&eq->hw_queue); - if ((hret != H_SUCCESS) || (vpage)) - goto out_kill_hwq; - - } else { - if (hret != H_PAGE_REGISTERED) - goto out_kill_hwq; - - } - } - - hw_qeit_reset(&eq->hw_queue); - return eq; - -out_kill_hwq: - hw_queue_dtor(&eq->hw_queue); - -out_freeres: - ehea_h_free_resource(adapter->handle, eq->fw_handle, FORCE_FREE); - -out_freemem: - kfree(eq); - return NULL; -} - -struct ehea_eqe *ehea_poll_eq(struct ehea_eq *eq) -{ - struct ehea_eqe *eqe; - unsigned long flags; - - spin_lock_irqsave(&eq->spinlock, flags); - eqe = hw_eqit_eq_get_inc_valid(&eq->hw_queue); - spin_unlock_irqrestore(&eq->spinlock, flags); - - return eqe; -} - -static u64 ehea_destroy_eq_res(struct ehea_eq *eq, u64 force) -{ - u64 hret; - unsigned long flags; - - spin_lock_irqsave(&eq->spinlock, flags); - - hret = ehea_h_free_resource(eq->adapter->handle, eq->fw_handle, force); - spin_unlock_irqrestore(&eq->spinlock, flags); - - if (hret != H_SUCCESS) - return hret; - - hw_queue_dtor(&eq->hw_queue); - kfree(eq); - - return hret; -} - -int ehea_destroy_eq(struct ehea_eq *eq) -{ - u64 hret, aer, aerr; - if (!eq) - return 0; - - hcp_epas_dtor(&eq->epas); - - hret = ehea_destroy_eq_res(eq, NORMAL_FREE); - if (hret == H_R_STATE) { - ehea_error_data(eq->adapter, eq->fw_handle, &aer, &aerr); - hret = ehea_destroy_eq_res(eq, FORCE_FREE); - } - - if (hret != H_SUCCESS) { - pr_err("destroy EQ failed\n"); - return -EIO; - } - - return 0; -} - -/* allocates memory for a queue and registers pages in phyp */ -static int ehea_qp_alloc_register(struct ehea_qp *qp, struct hw_queue *hw_queue, - int nr_pages, int wqe_size, int act_nr_sges, - struct ehea_adapter *adapter, int h_call_q_selector) -{ - u64 hret, rpage; - int ret, cnt; - void *vpage; - - ret = hw_queue_ctor(hw_queue, nr_pages, EHEA_PAGESIZE, wqe_size); - if (ret) - return ret; - - for (cnt = 0; cnt < nr_pages; cnt++) { - vpage = hw_qpageit_get_inc(hw_queue); - if (!vpage) { - pr_err("hw_qpageit_get_inc failed\n"); - goto out_kill_hwq; - } - rpage = __pa(vpage); - hret = ehea_h_register_rpage(adapter->handle, - 0, h_call_q_selector, - qp->fw_handle, rpage, 1); - if (hret < H_SUCCESS) { - pr_err("register_rpage_qp failed\n"); - goto out_kill_hwq; - } - } - hw_qeit_reset(hw_queue); - return 0; - -out_kill_hwq: - hw_queue_dtor(hw_queue); - return -EIO; -} - -static inline u32 map_wqe_size(u8 wqe_enc_size) -{ - return 128 << wqe_enc_size; -} - -struct ehea_qp *ehea_create_qp(struct ehea_adapter *adapter, - u32 pd, struct ehea_qp_init_attr *init_attr) -{ - int ret; - u64 hret; - struct ehea_qp *qp; - u32 wqe_size_in_bytes_sq, wqe_size_in_bytes_rq1; - u32 wqe_size_in_bytes_rq2, wqe_size_in_bytes_rq3; - - - qp = kzalloc_obj(*qp); - if (!qp) - return NULL; - - qp->adapter = adapter; - - hret = ehea_h_alloc_resource_qp(adapter->handle, init_attr, pd, - &qp->fw_handle, &qp->epas); - if (hret != H_SUCCESS) { - pr_err("ehea_h_alloc_resource_qp failed\n"); - goto out_freemem; - } - - wqe_size_in_bytes_sq = map_wqe_size(init_attr->act_wqe_size_enc_sq); - wqe_size_in_bytes_rq1 = map_wqe_size(init_attr->act_wqe_size_enc_rq1); - wqe_size_in_bytes_rq2 = map_wqe_size(init_attr->act_wqe_size_enc_rq2); - wqe_size_in_bytes_rq3 = map_wqe_size(init_attr->act_wqe_size_enc_rq3); - - ret = ehea_qp_alloc_register(qp, &qp->hw_squeue, init_attr->nr_sq_pages, - wqe_size_in_bytes_sq, - init_attr->act_wqe_size_enc_sq, adapter, - 0); - if (ret) { - pr_err("can't register for sq ret=%x\n", ret); - goto out_freeres; - } - - ret = ehea_qp_alloc_register(qp, &qp->hw_rqueue1, - init_attr->nr_rq1_pages, - wqe_size_in_bytes_rq1, - init_attr->act_wqe_size_enc_rq1, - adapter, 1); - if (ret) { - pr_err("can't register for rq1 ret=%x\n", ret); - goto out_kill_hwsq; - } - - if (init_attr->rq_count > 1) { - ret = ehea_qp_alloc_register(qp, &qp->hw_rqueue2, - init_attr->nr_rq2_pages, - wqe_size_in_bytes_rq2, - init_attr->act_wqe_size_enc_rq2, - adapter, 2); - if (ret) { - pr_err("can't register for rq2 ret=%x\n", ret); - goto out_kill_hwr1q; - } - } - - if (init_attr->rq_count > 2) { - ret = ehea_qp_alloc_register(qp, &qp->hw_rqueue3, - init_attr->nr_rq3_pages, - wqe_size_in_bytes_rq3, - init_attr->act_wqe_size_enc_rq3, - adapter, 3); - if (ret) { - pr_err("can't register for rq3 ret=%x\n", ret); - goto out_kill_hwr2q; - } - } - - qp->init_attr = *init_attr; - - return qp; - -out_kill_hwr2q: - hw_queue_dtor(&qp->hw_rqueue2); - -out_kill_hwr1q: - hw_queue_dtor(&qp->hw_rqueue1); - -out_kill_hwsq: - hw_queue_dtor(&qp->hw_squeue); - -out_freeres: - ehea_h_disable_and_get_hea(adapter->handle, qp->fw_handle); - ehea_h_free_resource(adapter->handle, qp->fw_handle, FORCE_FREE); - -out_freemem: - kfree(qp); - return NULL; -} - -static u64 ehea_destroy_qp_res(struct ehea_qp *qp, u64 force) -{ - u64 hret; - struct ehea_qp_init_attr *qp_attr = &qp->init_attr; - - - ehea_h_disable_and_get_hea(qp->adapter->handle, qp->fw_handle); - hret = ehea_h_free_resource(qp->adapter->handle, qp->fw_handle, force); - if (hret != H_SUCCESS) - return hret; - - hw_queue_dtor(&qp->hw_squeue); - hw_queue_dtor(&qp->hw_rqueue1); - - if (qp_attr->rq_count > 1) - hw_queue_dtor(&qp->hw_rqueue2); - if (qp_attr->rq_count > 2) - hw_queue_dtor(&qp->hw_rqueue3); - kfree(qp); - - return hret; -} - -int ehea_destroy_qp(struct ehea_qp *qp) -{ - u64 hret, aer, aerr; - if (!qp) - return 0; - - hcp_epas_dtor(&qp->epas); - - hret = ehea_destroy_qp_res(qp, NORMAL_FREE); - if (hret == H_R_STATE) { - ehea_error_data(qp->adapter, qp->fw_handle, &aer, &aerr); - hret = ehea_destroy_qp_res(qp, FORCE_FREE); - } - - if (hret != H_SUCCESS) { - pr_err("destroy QP failed\n"); - return -EIO; - } - - return 0; -} - -static inline int ehea_calc_index(unsigned long i, unsigned long s) -{ - return (i >> s) & EHEA_INDEX_MASK; -} - -static inline int ehea_init_top_bmap(struct ehea_top_bmap *ehea_top_bmap, - int dir) -{ - if (!ehea_top_bmap->dir[dir]) { - ehea_top_bmap->dir[dir] = - kzalloc_obj(struct ehea_dir_bmap); - if (!ehea_top_bmap->dir[dir]) - return -ENOMEM; - } - return 0; -} - -static inline int ehea_init_bmap(struct ehea_bmap *ehea_bmap, int top, int dir) -{ - if (!ehea_bmap->top[top]) { - ehea_bmap->top[top] = - kzalloc_obj(struct ehea_top_bmap); - if (!ehea_bmap->top[top]) - return -ENOMEM; - } - return ehea_init_top_bmap(ehea_bmap->top[top], dir); -} - -static DEFINE_MUTEX(ehea_busmap_mutex); -static unsigned long ehea_mr_len; - -#define EHEA_BUSMAP_ADD_SECT 1 -#define EHEA_BUSMAP_REM_SECT 0 - -static void ehea_rebuild_busmap(void) -{ - u64 vaddr = EHEA_BUSMAP_START; - int top, dir, idx; - - for (top = 0; top < EHEA_MAP_ENTRIES; top++) { - struct ehea_top_bmap *ehea_top; - int valid_dir_entries = 0; - - if (!ehea_bmap->top[top]) - continue; - ehea_top = ehea_bmap->top[top]; - for (dir = 0; dir < EHEA_MAP_ENTRIES; dir++) { - struct ehea_dir_bmap *ehea_dir; - int valid_entries = 0; - - if (!ehea_top->dir[dir]) - continue; - valid_dir_entries++; - ehea_dir = ehea_top->dir[dir]; - for (idx = 0; idx < EHEA_MAP_ENTRIES; idx++) { - if (!ehea_dir->ent[idx]) - continue; - valid_entries++; - ehea_dir->ent[idx] = vaddr; - vaddr += EHEA_SECTSIZE; - } - if (!valid_entries) { - ehea_top->dir[dir] = NULL; - kfree(ehea_dir); - } - } - if (!valid_dir_entries) { - ehea_bmap->top[top] = NULL; - kfree(ehea_top); - } - } -} - -static int ehea_update_busmap(unsigned long pfn, unsigned long nr_pages, int add) -{ - unsigned long i, start_section, end_section; - - if (!nr_pages) - return 0; - - if (!ehea_bmap) { - ehea_bmap = kzalloc_obj(struct ehea_bmap); - if (!ehea_bmap) - return -ENOMEM; - } - - start_section = (pfn * PAGE_SIZE) / EHEA_SECTSIZE; - end_section = start_section + ((nr_pages * PAGE_SIZE) / EHEA_SECTSIZE); - /* Mark entries as valid or invalid only; address is assigned later */ - for (i = start_section; i < end_section; i++) { - u64 flag; - int top = ehea_calc_index(i, EHEA_TOP_INDEX_SHIFT); - int dir = ehea_calc_index(i, EHEA_DIR_INDEX_SHIFT); - int idx = i & EHEA_INDEX_MASK; - - if (add) { - int ret = ehea_init_bmap(ehea_bmap, top, dir); - if (ret) - return ret; - flag = 1; /* valid */ - ehea_mr_len += EHEA_SECTSIZE; - } else { - if (!ehea_bmap->top[top]) - continue; - if (!ehea_bmap->top[top]->dir[dir]) - continue; - flag = 0; /* invalid */ - ehea_mr_len -= EHEA_SECTSIZE; - } - - ehea_bmap->top[top]->dir[dir]->ent[idx] = flag; - } - ehea_rebuild_busmap(); /* Assign contiguous addresses for mr */ - return 0; -} - -int ehea_add_sect_bmap(unsigned long pfn, unsigned long nr_pages) -{ - int ret; - - mutex_lock(&ehea_busmap_mutex); - ret = ehea_update_busmap(pfn, nr_pages, EHEA_BUSMAP_ADD_SECT); - mutex_unlock(&ehea_busmap_mutex); - return ret; -} - -int ehea_rem_sect_bmap(unsigned long pfn, unsigned long nr_pages) -{ - int ret; - - mutex_lock(&ehea_busmap_mutex); - ret = ehea_update_busmap(pfn, nr_pages, EHEA_BUSMAP_REM_SECT); - mutex_unlock(&ehea_busmap_mutex); - return ret; -} - -static int ehea_is_hugepage(unsigned long pfn) -{ - if (pfn & EHEA_HUGEPAGE_PFN_MASK) - return 0; - - if (page_shift(pfn_to_page(pfn)) != EHEA_HUGEPAGESHIFT) - return 0; - - return 1; -} - -static int ehea_create_busmap_callback(unsigned long initial_pfn, - unsigned long total_nr_pages, void *arg) -{ - int ret; - unsigned long pfn, start_pfn, end_pfn, nr_pages; - - if ((total_nr_pages * PAGE_SIZE) < EHEA_HUGEPAGE_SIZE) - return ehea_update_busmap(initial_pfn, total_nr_pages, - EHEA_BUSMAP_ADD_SECT); - - /* Given chunk is >= 16GB -> check for hugepages */ - start_pfn = initial_pfn; - end_pfn = initial_pfn + total_nr_pages; - pfn = start_pfn; - - while (pfn < end_pfn) { - if (ehea_is_hugepage(pfn)) { - /* Add mem found in front of the hugepage */ - nr_pages = pfn - start_pfn; - ret = ehea_update_busmap(start_pfn, nr_pages, - EHEA_BUSMAP_ADD_SECT); - if (ret) - return ret; - - /* Skip the hugepage */ - pfn += (EHEA_HUGEPAGE_SIZE / PAGE_SIZE); - start_pfn = pfn; - } else - pfn += (EHEA_SECTSIZE / PAGE_SIZE); - } - - /* Add mem found behind the hugepage(s) */ - nr_pages = pfn - start_pfn; - return ehea_update_busmap(start_pfn, nr_pages, EHEA_BUSMAP_ADD_SECT); -} - -int ehea_create_busmap(void) -{ - int ret; - - mutex_lock(&ehea_busmap_mutex); - ehea_mr_len = 0; - ret = walk_system_ram_range(0, 1ULL << MAX_PHYSMEM_BITS, NULL, - ehea_create_busmap_callback); - mutex_unlock(&ehea_busmap_mutex); - return ret; -} - -void ehea_destroy_busmap(void) -{ - int top, dir; - mutex_lock(&ehea_busmap_mutex); - if (!ehea_bmap) - goto out_destroy; - - for (top = 0; top < EHEA_MAP_ENTRIES; top++) { - if (!ehea_bmap->top[top]) - continue; - - for (dir = 0; dir < EHEA_MAP_ENTRIES; dir++) { - if (!ehea_bmap->top[top]->dir[dir]) - continue; - - kfree(ehea_bmap->top[top]->dir[dir]); - } - - kfree(ehea_bmap->top[top]); - } - - kfree(ehea_bmap); - ehea_bmap = NULL; -out_destroy: - mutex_unlock(&ehea_busmap_mutex); -} - -u64 ehea_map_vaddr(void *caddr) -{ - int top, dir, idx; - unsigned long index, offset; - - if (!ehea_bmap) - return EHEA_INVAL_ADDR; - - index = __pa(caddr) >> SECTION_SIZE_BITS; - top = (index >> EHEA_TOP_INDEX_SHIFT) & EHEA_INDEX_MASK; - if (!ehea_bmap->top[top]) - return EHEA_INVAL_ADDR; - - dir = (index >> EHEA_DIR_INDEX_SHIFT) & EHEA_INDEX_MASK; - if (!ehea_bmap->top[top]->dir[dir]) - return EHEA_INVAL_ADDR; - - idx = index & EHEA_INDEX_MASK; - if (!ehea_bmap->top[top]->dir[dir]->ent[idx]) - return EHEA_INVAL_ADDR; - - offset = (unsigned long)caddr & (EHEA_SECTSIZE - 1); - return ehea_bmap->top[top]->dir[dir]->ent[idx] | offset; -} - -static inline void *ehea_calc_sectbase(int top, int dir, int idx) -{ - unsigned long ret = idx; - ret |= dir << EHEA_DIR_INDEX_SHIFT; - ret |= top << EHEA_TOP_INDEX_SHIFT; - return __va(ret << SECTION_SIZE_BITS); -} - -static u64 ehea_reg_mr_section(int top, int dir, int idx, u64 *pt, - struct ehea_adapter *adapter, - struct ehea_mr *mr) -{ - void *pg; - u64 j, m, hret; - unsigned long k = 0; - u64 pt_abs = __pa(pt); - - void *sectbase = ehea_calc_sectbase(top, dir, idx); - - for (j = 0; j < (EHEA_PAGES_PER_SECTION / EHEA_MAX_RPAGE); j++) { - - for (m = 0; m < EHEA_MAX_RPAGE; m++) { - pg = sectbase + ((k++) * EHEA_PAGESIZE); - pt[m] = __pa(pg); - } - hret = ehea_h_register_rpage_mr(adapter->handle, mr->handle, 0, - 0, pt_abs, EHEA_MAX_RPAGE); - - if ((hret != H_SUCCESS) && - (hret != H_PAGE_REGISTERED)) { - ehea_h_free_resource(adapter->handle, mr->handle, - FORCE_FREE); - pr_err("register_rpage_mr failed\n"); - return hret; - } - } - return hret; -} - -static u64 ehea_reg_mr_sections(int top, int dir, u64 *pt, - struct ehea_adapter *adapter, - struct ehea_mr *mr) -{ - u64 hret = H_SUCCESS; - int idx; - - for (idx = 0; idx < EHEA_MAP_ENTRIES; idx++) { - if (!ehea_bmap->top[top]->dir[dir]->ent[idx]) - continue; - - hret = ehea_reg_mr_section(top, dir, idx, pt, adapter, mr); - if ((hret != H_SUCCESS) && (hret != H_PAGE_REGISTERED)) - return hret; - } - return hret; -} - -static u64 ehea_reg_mr_dir_sections(int top, u64 *pt, - struct ehea_adapter *adapter, - struct ehea_mr *mr) -{ - u64 hret = H_SUCCESS; - int dir; - - for (dir = 0; dir < EHEA_MAP_ENTRIES; dir++) { - if (!ehea_bmap->top[top]->dir[dir]) - continue; - - hret = ehea_reg_mr_sections(top, dir, pt, adapter, mr); - if ((hret != H_SUCCESS) && (hret != H_PAGE_REGISTERED)) - return hret; - } - return hret; -} - -int ehea_reg_kernel_mr(struct ehea_adapter *adapter, struct ehea_mr *mr) -{ - int ret; - u64 *pt; - u64 hret; - u32 acc_ctrl = EHEA_MR_ACC_CTRL; - - unsigned long top; - - pt = (void *)get_zeroed_page(GFP_KERNEL); - if (!pt) { - pr_err("no mem\n"); - ret = -ENOMEM; - goto out; - } - - hret = ehea_h_alloc_resource_mr(adapter->handle, EHEA_BUSMAP_START, - ehea_mr_len, acc_ctrl, adapter->pd, - &mr->handle, &mr->lkey); - - if (hret != H_SUCCESS) { - pr_err("alloc_resource_mr failed\n"); - ret = -EIO; - goto out; - } - - if (!ehea_bmap) { - ehea_h_free_resource(adapter->handle, mr->handle, FORCE_FREE); - pr_err("no busmap available\n"); - ret = -EIO; - goto out; - } - - for (top = 0; top < EHEA_MAP_ENTRIES; top++) { - if (!ehea_bmap->top[top]) - continue; - - hret = ehea_reg_mr_dir_sections(top, pt, adapter, mr); - if((hret != H_PAGE_REGISTERED) && (hret != H_SUCCESS)) - break; - } - - if (hret != H_SUCCESS) { - ehea_h_free_resource(adapter->handle, mr->handle, FORCE_FREE); - pr_err("registering mr failed\n"); - ret = -EIO; - goto out; - } - - mr->vaddr = EHEA_BUSMAP_START; - mr->adapter = adapter; - ret = 0; -out: - free_page((unsigned long)pt); - return ret; -} - -int ehea_rem_mr(struct ehea_mr *mr) -{ - u64 hret; - - if (!mr || !mr->adapter) - return -EINVAL; - - hret = ehea_h_free_resource(mr->adapter->handle, mr->handle, - FORCE_FREE); - if (hret != H_SUCCESS) { - pr_err("destroy MR failed\n"); - return -EIO; - } - - return 0; -} - -int ehea_gen_smr(struct ehea_adapter *adapter, struct ehea_mr *old_mr, - struct ehea_mr *shared_mr) -{ - u64 hret; - - hret = ehea_h_register_smr(adapter->handle, old_mr->handle, - old_mr->vaddr, EHEA_MR_ACC_CTRL, - adapter->pd, shared_mr); - if (hret != H_SUCCESS) - return -EIO; - - shared_mr->adapter = adapter; - - return 0; -} - -static void print_error_data(u64 *data) -{ - int length; - u64 type = EHEA_BMASK_GET(ERROR_DATA_TYPE, data[2]); - u64 resource = data[1]; - - length = EHEA_BMASK_GET(ERROR_DATA_LENGTH, data[0]); - - if (length > EHEA_PAGESIZE) - length = EHEA_PAGESIZE; - - if (type == EHEA_AER_RESTYPE_QP) - pr_err("QP (resource=%llX) state: AER=0x%llX, AERR=0x%llX, port=%llX\n", - resource, data[6], data[12], data[22]); - else if (type == EHEA_AER_RESTYPE_CQ) - pr_err("CQ (resource=%llX) state: AER=0x%llX\n", - resource, data[6]); - else if (type == EHEA_AER_RESTYPE_EQ) - pr_err("EQ (resource=%llX) state: AER=0x%llX\n", - resource, data[6]); - - ehea_dump(data, length, "error data"); -} - -u64 ehea_error_data(struct ehea_adapter *adapter, u64 res_handle, - u64 *aer, u64 *aerr) -{ - unsigned long ret; - u64 *rblock; - u64 type = 0; - - rblock = (void *)get_zeroed_page(GFP_KERNEL); - if (!rblock) { - pr_err("Cannot allocate rblock memory\n"); - goto out; - } - - ret = ehea_h_error_data(adapter->handle, res_handle, rblock); - - if (ret == H_SUCCESS) { - type = EHEA_BMASK_GET(ERROR_DATA_TYPE, rblock[2]); - *aer = rblock[6]; - *aerr = rblock[12]; - print_error_data(rblock); - } else if (ret == H_R_STATE) { - pr_err("No error data available: %llX\n", res_handle); - } else - pr_err("Error data could not be fetched: %llX\n", res_handle); - - free_page((unsigned long)rblock); -out: - return type; -} diff --git a/drivers/net/ethernet/ibm/ehea/ehea_qmr.h b/drivers/net/ethernet/ibm/ehea/ehea_qmr.h deleted file mode 100644 index 7c7cccd820f7..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea_qmr.h +++ /dev/null @@ -1,390 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea_qmr.h - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch - * Jan-Bernd Themann - * Thomas Klein - */ - -#ifndef __EHEA_QMR_H__ -#define __EHEA_QMR_H__ - -#include -#include "ehea.h" -#include "ehea_hw.h" - -/* - * page size of ehea hardware queues - */ - -#define EHEA_PAGESHIFT 12 -#define EHEA_PAGESIZE (1UL << EHEA_PAGESHIFT) -#define EHEA_SECTSIZE (1UL << 24) -#define EHEA_PAGES_PER_SECTION (EHEA_SECTSIZE >> EHEA_PAGESHIFT) -#define EHEA_HUGEPAGESHIFT 34 -#define EHEA_HUGEPAGE_SIZE (1UL << EHEA_HUGEPAGESHIFT) -#define EHEA_HUGEPAGE_PFN_MASK ((EHEA_HUGEPAGE_SIZE - 1) >> PAGE_SHIFT) - -#if ((1UL << SECTION_SIZE_BITS) < EHEA_SECTSIZE) -#error eHEA module cannot work if kernel sectionsize < ehea sectionsize -#endif - -/* Some abbreviations used here: - * - * WQE - Work Queue Entry - * SWQE - Send Work Queue Entry - * RWQE - Receive Work Queue Entry - * CQE - Completion Queue Entry - * EQE - Event Queue Entry - * MR - Memory Region - */ - -/* Use of WR_ID field for EHEA */ -#define EHEA_WR_ID_COUNT EHEA_BMASK_IBM(0, 19) -#define EHEA_WR_ID_TYPE EHEA_BMASK_IBM(20, 23) -#define EHEA_SWQE2_TYPE 0x1 -#define EHEA_SWQE3_TYPE 0x2 -#define EHEA_RWQE2_TYPE 0x3 -#define EHEA_RWQE3_TYPE 0x4 -#define EHEA_WR_ID_INDEX EHEA_BMASK_IBM(24, 47) -#define EHEA_WR_ID_REFILL EHEA_BMASK_IBM(48, 63) - -struct ehea_vsgentry { - u64 vaddr; - u32 l_key; - u32 len; -}; - -/* maximum number of sg entries allowed in a WQE */ -#define EHEA_MAX_WQE_SG_ENTRIES 252 -#define SWQE2_MAX_IMM (0xD0 - 0x30) -#define SWQE3_MAX_IMM 224 - -/* tx control flags for swqe */ -#define EHEA_SWQE_CRC 0x8000 -#define EHEA_SWQE_IP_CHECKSUM 0x4000 -#define EHEA_SWQE_TCP_CHECKSUM 0x2000 -#define EHEA_SWQE_TSO 0x1000 -#define EHEA_SWQE_SIGNALLED_COMPLETION 0x0800 -#define EHEA_SWQE_VLAN_INSERT 0x0400 -#define EHEA_SWQE_IMM_DATA_PRESENT 0x0200 -#define EHEA_SWQE_DESCRIPTORS_PRESENT 0x0100 -#define EHEA_SWQE_WRAP_CTL_REC 0x0080 -#define EHEA_SWQE_WRAP_CTL_FORCE 0x0040 -#define EHEA_SWQE_BIND 0x0020 -#define EHEA_SWQE_PURGE 0x0010 - -/* sizeof(struct ehea_swqe) less the union */ -#define SWQE_HEADER_SIZE 32 - -struct ehea_swqe { - u64 wr_id; - u16 tx_control; - u16 vlan_tag; - u8 reserved1; - u8 ip_start; - u8 ip_end; - u8 immediate_data_length; - u8 tcp_offset; - u8 reserved2; - u16 reserved2b; - u8 wrap_tag; - u8 descriptors; /* number of valid descriptors in WQE */ - u16 reserved3; - u16 reserved4; - u16 mss; - u32 reserved5; - union { - /* Send WQE Format 1 */ - struct { - struct ehea_vsgentry sg_list[EHEA_MAX_WQE_SG_ENTRIES]; - } no_immediate_data; - - /* Send WQE Format 2 */ - struct { - struct ehea_vsgentry sg_entry; - /* 0x30 */ - u8 immediate_data[SWQE2_MAX_IMM]; - /* 0xd0 */ - struct ehea_vsgentry sg_list[EHEA_MAX_WQE_SG_ENTRIES-1]; - } immdata_desc __packed; - - /* Send WQE Format 3 */ - struct { - u8 immediate_data[SWQE3_MAX_IMM]; - } immdata_nodesc; - } u; -}; - -struct ehea_rwqe { - u64 wr_id; /* work request ID */ - u8 reserved1[5]; - u8 data_segments; - u16 reserved2; - u64 reserved3; - u64 reserved4; - struct ehea_vsgentry sg_list[EHEA_MAX_WQE_SG_ENTRIES]; -}; - -#define EHEA_CQE_VLAN_TAG_XTRACT 0x0400 - -#define EHEA_CQE_TYPE_RQ 0x60 -#define EHEA_CQE_STAT_ERR_MASK 0x700F -#define EHEA_CQE_STAT_FAT_ERR_MASK 0xF -#define EHEA_CQE_BLIND_CKSUM 0x8000 -#define EHEA_CQE_STAT_ERR_TCP 0x4000 -#define EHEA_CQE_STAT_ERR_IP 0x2000 -#define EHEA_CQE_STAT_ERR_CRC 0x1000 - -/* Defines which bad send cqe stati lead to a port reset */ -#define EHEA_CQE_STAT_RESET_MASK 0x0002 - -struct ehea_cqe { - u64 wr_id; /* work request ID from WQE */ - u8 type; - u8 valid; - u16 status; - u16 reserved1; - u16 num_bytes_transfered; - u16 vlan_tag; - u16 inet_checksum_value; - u8 reserved2; - u8 header_length; - u16 reserved3; - u16 page_offset; - u16 wqe_count; - u32 qp_token; - u32 timestamp; - u32 reserved4; - u64 reserved5[3]; -}; - -#define EHEA_EQE_VALID EHEA_BMASK_IBM(0, 0) -#define EHEA_EQE_IS_CQE EHEA_BMASK_IBM(1, 1) -#define EHEA_EQE_IDENTIFIER EHEA_BMASK_IBM(2, 7) -#define EHEA_EQE_QP_CQ_NUMBER EHEA_BMASK_IBM(8, 31) -#define EHEA_EQE_QP_TOKEN EHEA_BMASK_IBM(32, 63) -#define EHEA_EQE_CQ_TOKEN EHEA_BMASK_IBM(32, 63) -#define EHEA_EQE_KEY EHEA_BMASK_IBM(32, 63) -#define EHEA_EQE_PORT_NUMBER EHEA_BMASK_IBM(56, 63) -#define EHEA_EQE_EQ_NUMBER EHEA_BMASK_IBM(48, 63) -#define EHEA_EQE_SM_ID EHEA_BMASK_IBM(48, 63) -#define EHEA_EQE_SM_MECH_NUMBER EHEA_BMASK_IBM(48, 55) -#define EHEA_EQE_SM_PORT_NUMBER EHEA_BMASK_IBM(56, 63) - -#define EHEA_AER_RESTYPE_QP 0x8 -#define EHEA_AER_RESTYPE_CQ 0x4 -#define EHEA_AER_RESTYPE_EQ 0x3 - -/* Defines which affiliated errors lead to a port reset */ -#define EHEA_AER_RESET_MASK 0xFFFFFFFFFEFFFFFFULL -#define EHEA_AERR_RESET_MASK 0xFFFFFFFFFFFFFFFFULL - -struct ehea_eqe { - u64 entry; -}; - -#define ERROR_DATA_LENGTH EHEA_BMASK_IBM(52, 63) -#define ERROR_DATA_TYPE EHEA_BMASK_IBM(0, 7) - -static inline void *hw_qeit_calc(struct hw_queue *queue, u64 q_offset) -{ - struct ehea_page *current_page; - - if (q_offset >= queue->queue_length) - q_offset -= queue->queue_length; - current_page = (queue->queue_pages)[q_offset >> EHEA_PAGESHIFT]; - return ¤t_page->entries[q_offset & (EHEA_PAGESIZE - 1)]; -} - -static inline void *hw_qeit_get(struct hw_queue *queue) -{ - return hw_qeit_calc(queue, queue->current_q_offset); -} - -static inline void hw_qeit_inc(struct hw_queue *queue) -{ - queue->current_q_offset += queue->qe_size; - if (queue->current_q_offset >= queue->queue_length) { - queue->current_q_offset = 0; - /* toggle the valid flag */ - queue->toggle_state = (~queue->toggle_state) & 1; - } -} - -static inline void *hw_qeit_get_inc(struct hw_queue *queue) -{ - void *retvalue = hw_qeit_get(queue); - hw_qeit_inc(queue); - return retvalue; -} - -static inline void *hw_qeit_get_inc_valid(struct hw_queue *queue) -{ - struct ehea_cqe *retvalue = hw_qeit_get(queue); - u8 valid = retvalue->valid; - void *pref; - - if ((valid >> 7) == (queue->toggle_state & 1)) { - /* this is a good one */ - hw_qeit_inc(queue); - pref = hw_qeit_calc(queue, queue->current_q_offset); - prefetch(pref); - prefetch(pref + 128); - } else - retvalue = NULL; - return retvalue; -} - -static inline void *hw_qeit_get_valid(struct hw_queue *queue) -{ - struct ehea_cqe *retvalue = hw_qeit_get(queue); - void *pref; - u8 valid; - - pref = hw_qeit_calc(queue, queue->current_q_offset); - prefetch(pref); - prefetch(pref + 128); - prefetch(pref + 256); - valid = retvalue->valid; - if (!((valid >> 7) == (queue->toggle_state & 1))) - retvalue = NULL; - return retvalue; -} - -static inline void *hw_qeit_reset(struct hw_queue *queue) -{ - queue->current_q_offset = 0; - return hw_qeit_get(queue); -} - -static inline void *hw_qeit_eq_get_inc(struct hw_queue *queue) -{ - u64 last_entry_in_q = queue->queue_length - queue->qe_size; - void *retvalue; - - retvalue = hw_qeit_get(queue); - queue->current_q_offset += queue->qe_size; - if (queue->current_q_offset > last_entry_in_q) { - queue->current_q_offset = 0; - queue->toggle_state = (~queue->toggle_state) & 1; - } - return retvalue; -} - -static inline void *hw_eqit_eq_get_inc_valid(struct hw_queue *queue) -{ - void *retvalue = hw_qeit_get(queue); - u32 qe = *(u8 *)retvalue; - if ((qe >> 7) == (queue->toggle_state & 1)) - hw_qeit_eq_get_inc(queue); - else - retvalue = NULL; - return retvalue; -} - -static inline struct ehea_rwqe *ehea_get_next_rwqe(struct ehea_qp *qp, - int rq_nr) -{ - struct hw_queue *queue; - - if (rq_nr == 1) - queue = &qp->hw_rqueue1; - else if (rq_nr == 2) - queue = &qp->hw_rqueue2; - else - queue = &qp->hw_rqueue3; - - return hw_qeit_get_inc(queue); -} - -static inline struct ehea_swqe *ehea_get_swqe(struct ehea_qp *my_qp, - int *wqe_index) -{ - struct hw_queue *queue = &my_qp->hw_squeue; - struct ehea_swqe *wqe_p; - - *wqe_index = (queue->current_q_offset) >> (7 + EHEA_SG_SQ); - wqe_p = hw_qeit_get_inc(&my_qp->hw_squeue); - - return wqe_p; -} - -static inline void ehea_post_swqe(struct ehea_qp *my_qp, struct ehea_swqe *swqe) -{ - iosync(); - ehea_update_sqa(my_qp, 1); -} - -static inline struct ehea_cqe *ehea_poll_rq1(struct ehea_qp *qp, int *wqe_index) -{ - struct hw_queue *queue = &qp->hw_rqueue1; - - *wqe_index = (queue->current_q_offset) >> (7 + EHEA_SG_RQ1); - return hw_qeit_get_valid(queue); -} - -static inline void ehea_inc_cq(struct ehea_cq *cq) -{ - hw_qeit_inc(&cq->hw_queue); -} - -static inline void ehea_inc_rq1(struct ehea_qp *qp) -{ - hw_qeit_inc(&qp->hw_rqueue1); -} - -static inline struct ehea_cqe *ehea_poll_cq(struct ehea_cq *my_cq) -{ - return hw_qeit_get_valid(&my_cq->hw_queue); -} - -#define EHEA_CQ_REGISTER_ORIG 0 -#define EHEA_EQ_REGISTER_ORIG 0 - -enum ehea_eq_type { - EHEA_EQ = 0, /* event queue */ - EHEA_NEQ /* notification event queue */ -}; - -struct ehea_eq *ehea_create_eq(struct ehea_adapter *adapter, - enum ehea_eq_type type, - const u32 length, const u8 eqe_gen); - -int ehea_destroy_eq(struct ehea_eq *eq); - -struct ehea_eqe *ehea_poll_eq(struct ehea_eq *eq); - -struct ehea_cq *ehea_create_cq(struct ehea_adapter *adapter, int cqe, - u64 eq_handle, u32 cq_token); - -int ehea_destroy_cq(struct ehea_cq *cq); - -struct ehea_qp *ehea_create_qp(struct ehea_adapter *adapter, u32 pd, - struct ehea_qp_init_attr *init_attr); - -int ehea_destroy_qp(struct ehea_qp *qp); - -int ehea_reg_kernel_mr(struct ehea_adapter *adapter, struct ehea_mr *mr); - -int ehea_gen_smr(struct ehea_adapter *adapter, struct ehea_mr *old_mr, - struct ehea_mr *shared_mr); - -int ehea_rem_mr(struct ehea_mr *mr); - -u64 ehea_error_data(struct ehea_adapter *adapter, u64 res_handle, - u64 *aer, u64 *aerr); - -int ehea_add_sect_bmap(unsigned long pfn, unsigned long nr_pages); -int ehea_rem_sect_bmap(unsigned long pfn, unsigned long nr_pages); -int ehea_create_busmap(void); -void ehea_destroy_busmap(void); -u64 ehea_map_vaddr(void *caddr); - -#endif /* __EHEA_QMR_H__ */ From 4bbb6f5940708649b8f51ab22692c467a766518f Mon Sep 17 00:00:00 2001 From: David Christensen Date: Mon, 29 Jun 2026 16:13:43 -0500 Subject: [PATCH 0093/1433] powerpc: remove ehea driver references Follow-on cleanup after the removal of the IBM eHEA driver in commit f721e8ffa92a ("ehea: remove the ehea driver"). Remove the CONFIG_IBM_EHEA entry from ppc64_defconfig and the EXPORT_SYMBOL_GPL(walk_system_ram_range) export from arch/powerpc/mm/mem.c that was only needed by the ehea driver. Signed-off-by: David Christensen Reviewed-by: Christophe Leroy (CS GROUP) Link: https://patch.msgid.link/20260629211343.3712775-3-drc@linux.ibm.com Signed-off-by: Paolo Abeni --- arch/powerpc/configs/ppc64_defconfig | 1 - arch/powerpc/mm/mem.c | 6 ------ 2 files changed, 7 deletions(-) diff --git a/arch/powerpc/configs/ppc64_defconfig b/arch/powerpc/configs/ppc64_defconfig index f795b74602ec..1eb8e3457e8b 100644 --- a/arch/powerpc/configs/ppc64_defconfig +++ b/arch/powerpc/configs/ppc64_defconfig @@ -210,7 +210,6 @@ CONFIG_BNX2X=m CONFIG_CHELSIO_T1=m CONFIG_BE2NET=m CONFIG_IBMVETH=m -CONFIG_EHEA=m CONFIG_IBMVNIC=m CONFIG_E100=y CONFIG_E1000=y diff --git a/arch/powerpc/mm/mem.c b/arch/powerpc/mm/mem.c index 4c1afab91996..b617b69452cd 100644 --- a/arch/powerpc/mm/mem.c +++ b/arch/powerpc/mm/mem.c @@ -371,12 +371,6 @@ int devmem_is_allowed(unsigned long pfn) } #endif /* CONFIG_STRICT_DEVMEM */ -/* - * This is defined in kernel/resource.c but only powerpc needs to export it, for - * the EHEA driver. Drop this when drivers/net/ethernet/ibm/ehea is removed. - */ -EXPORT_SYMBOL_GPL(walk_system_ram_range); - #ifdef CONFIG_EXECMEM static struct execmem_info execmem_info __ro_after_init; From 8c9c5b9a689612dcb92a04a4218c975cd19f19d8 Mon Sep 17 00:00:00 2001 From: Yousef Alhouseen Date: Tue, 30 Jun 2026 12:12:16 +0200 Subject: [PATCH 0094/1433] net: usb: rtl8150: handle link status read failures set_carrier() ignores the result of the USB control transfer and tests the stack variable supplied as its receive buffer. If the device rejects or aborts the request, that variable remains uninitialized and the driver chooses an arbitrary carrier state. Leave the existing carrier state unchanged when the link status cannot be read. A transient USB error should not be treated as link loss. Reported-by: syzbot+9db6c624635564ad813c@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=9db6c624635564ad813c Suggested-by: Petko Manolov Signed-off-by: Yousef Alhouseen Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260630101216.10365-1-alhouseenyousef@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/usb/rtl8150.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/usb/rtl8150.c b/drivers/net/usb/rtl8150.c index c880c95c41a5..d51e43170e03 100644 --- a/drivers/net/usb/rtl8150.c +++ b/drivers/net/usb/rtl8150.c @@ -732,7 +732,9 @@ static void set_carrier(struct net_device *netdev) rtl8150_t *dev = netdev_priv(netdev); short tmp; - get_registers(dev, CSCR, 2, &tmp); + if (get_registers(dev, CSCR, 2, &tmp)) + return; + if (tmp & CSCR_LINK_STATUS) netif_carrier_on(netdev); else From 09cfee6a80047850581ad373cffac6034fedf0be Mon Sep 17 00:00:00 2001 From: Lei Zhu Date: Tue, 30 Jun 2026 14:54:57 +0800 Subject: [PATCH 0095/1433] ionic: Change list definition method The LIST_HEAD macro can both define a linked list and initialize it in one step. To simplify code, we replace the separate operations of linked list definition and manual initialization with the LIST_HEAD macro. Signed-off-by: Lei Zhu Reviewed-by: Brett Creeley Link: https://patch.msgid.link/20260630065457.160081-1-zhulei_szu@163.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/pensando/ionic/ionic_rx_filter.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/pensando/ionic/ionic_rx_filter.c b/drivers/net/ethernet/pensando/ionic/ionic_rx_filter.c index 528114877677..c999754afb5f 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_rx_filter.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_rx_filter.c @@ -558,18 +558,15 @@ struct sync_item { void ionic_rx_filter_sync(struct ionic_lif *lif) { struct device *dev = lif->ionic->dev; - struct list_head sync_add_list; - struct list_head sync_del_list; struct sync_item *sync_item; struct ionic_rx_filter *f; + LIST_HEAD(sync_add_list); + LIST_HEAD(sync_del_list); struct hlist_head *head; struct hlist_node *tmp; struct sync_item *spos; unsigned int i; - INIT_LIST_HEAD(&sync_add_list); - INIT_LIST_HEAD(&sync_del_list); - clear_bit(IONIC_LIF_F_FILTER_SYNC_NEEDED, lif->state); /* Copy the filters to be added and deleted From 69bf497cfa799f80cade1ce5e663fb3c58da7b85 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 30 Jun 2026 13:19:41 +0200 Subject: [PATCH 0096/1433] net: dsa: realtek: rtl83xx: Make learning optional in join/leave Mostly to make it possible to add rtl83xx support piece by piece, make the port learning callback optional in rtl83xx_port_bridge_join() and rtl83xx_port_bridge_leave(). Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260630-rtl8366rb-improvements-v2-1-05eb9d6a37f5@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/dsa/realtek/rtl83xx.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rtl83xx.c index 71124ecca92f..90843d52c5a8 100644 --- a/drivers/net/dsa/realtek/rtl83xx.c +++ b/drivers/net/dsa/realtek/rtl83xx.c @@ -356,9 +356,6 @@ int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port, if (!priv->ops->port_add_isolation) return -EOPNOTSUPP; - if (!priv->ops->port_set_learning) - return -EOPNOTSUPP; - dev_dbg(priv->dev, "bridge %d join port %d\n", bridge.num, port); /* Add this port to the isolation group of every other port @@ -396,9 +393,11 @@ int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port, goto undo_self_isolation; } - ret = priv->ops->port_set_learning(priv, port, true); - if (ret) - goto undo_efid; + if (priv->ops->port_set_learning) { + ret = priv->ops->port_set_learning(priv, port, true); + if (ret) + goto undo_efid; + } return 0; @@ -443,9 +442,6 @@ void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port, if (!priv->ops->port_remove_isolation) return; - if (!priv->ops->port_set_learning) - return; - dev_dbg(priv->dev, "bridge %d leave port %d\n", bridge.num, port); /* Remove this port from the isolation group of every other @@ -474,11 +470,13 @@ void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port, * downstream DSA ports from the isolation group. */ - ret = priv->ops->port_set_learning(priv, port, false); - if (ret) - dev_err(priv->dev, - "failed to disable learning on port %d: %pe\n", - port, ERR_PTR(ret)); + if (priv->ops->port_set_learning) { + ret = priv->ops->port_set_learning(priv, port, false); + if (ret) + dev_err(priv->dev, + "failed to disable learning on port %d: %pe\n", + port, ERR_PTR(ret)); + } /* Remove those ports from the isolation group of this port */ ret = priv->ops->port_remove_isolation(priv, port, mask); From 82ddf181448ba93b12f8d2e2b6ba7ab38e66c8b9 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 30 Jun 2026 13:19:42 +0200 Subject: [PATCH 0097/1433] net: dsa: realtek: rtl8366rb: Switch to generic port_bridge* handlers The RTL8366RB is using its own sub-standard port isolation code. Implement the required isolation helpers, use these directly in the port setup callback, and switch over to the standard port isolation code. Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260630-rtl8366rb-improvements-v2-2-05eb9d6a37f5@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/dsa/realtek/rtl8366rb.c | 108 ++++++++++------------------ 1 file changed, 36 insertions(+), 72 deletions(-) diff --git a/drivers/net/dsa/realtek/rtl8366rb.c b/drivers/net/dsa/realtek/rtl8366rb.c index 103039fe3086..8b57ef3bf03a 100644 --- a/drivers/net/dsa/realtek/rtl8366rb.c +++ b/drivers/net/dsa/realtek/rtl8366rb.c @@ -791,6 +791,35 @@ static int rtl8366rb_setup_all_leds_off(struct realtek_priv *priv) return ret; } +static int rtl8366rb_port_set_isolation(struct realtek_priv *priv, int port, + u32 mask) +{ + /* Bit 0 enables isolation so set this if we enable isolation + * any of the ports an clear it if we disable on all of them. + */ + if (mask) + mask = RTL8366RB_PORT_ISO_PORTS(mask) | RTL8366RB_PORT_ISO_EN; + + return regmap_write(priv->map, RTL8366RB_PORT_ISO(port), + mask); +} + +static int rtl8366rb_port_add_isolation(struct realtek_priv *priv, int port, + u32 mask) +{ + /* We assume isolation bit is on */ + return regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(port), + RTL8366RB_PORT_ISO_PORTS(mask), + RTL8366RB_PORT_ISO_PORTS(mask)); +} + +static int rtl8366rb_port_remove_isolation(struct realtek_priv *priv, int port, + u32 mask) +{ + return regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(port), + RTL8366RB_PORT_ISO_PORTS(mask), 0); +} + static int rtl8366rb_setup(struct dsa_switch *ds) { struct realtek_priv *priv = ds->priv; @@ -868,16 +897,13 @@ static int rtl8366rb_setup(struct dsa_switch *ds) /* Isolate all user ports so they can only send packets to itself and the CPU port */ for (i = 0; i < RTL8366RB_PORT_NUM_CPU; i++) { - ret = regmap_write(priv->map, RTL8366RB_PORT_ISO(i), - RTL8366RB_PORT_ISO_PORTS(BIT(RTL8366RB_PORT_NUM_CPU)) | - RTL8366RB_PORT_ISO_EN); + ret = rtl8366rb_port_set_isolation(priv, i, BIT(RTL8366RB_PORT_NUM_CPU)); if (ret) return ret; } /* CPU port can send packets to all ports */ - ret = regmap_write(priv->map, RTL8366RB_PORT_ISO(RTL8366RB_PORT_NUM_CPU), - RTL8366RB_PORT_ISO_PORTS(dsa_user_ports(ds)) | - RTL8366RB_PORT_ISO_EN); + ret = rtl8366rb_port_set_isolation(priv, RTL8366RB_PORT_NUM_CPU, + dsa_user_ports(ds)); if (ret) return ret; @@ -1184,70 +1210,6 @@ rtl8366rb_port_disable(struct dsa_switch *ds, int port) return; } -static int -rtl8366rb_port_bridge_join(struct dsa_switch *ds, int port, - struct dsa_bridge bridge, - bool *tx_fwd_offload, - struct netlink_ext_ack *extack) -{ - struct realtek_priv *priv = ds->priv; - unsigned int port_bitmap = 0; - int ret, i; - - /* Loop over all other ports than the current one */ - for (i = 0; i < RTL8366RB_PORT_NUM_CPU; i++) { - /* Current port handled last */ - if (i == port) - continue; - /* Not on this bridge */ - if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge)) - continue; - /* Join this port to each other port on the bridge */ - ret = regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(i), - RTL8366RB_PORT_ISO_PORTS(BIT(port)), - RTL8366RB_PORT_ISO_PORTS(BIT(port))); - if (ret) - dev_err(priv->dev, "failed to join port %d\n", port); - - port_bitmap |= BIT(i); - } - - /* Set the bits for the ports we can access */ - return regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(port), - RTL8366RB_PORT_ISO_PORTS(port_bitmap), - RTL8366RB_PORT_ISO_PORTS(port_bitmap)); -} - -static void -rtl8366rb_port_bridge_leave(struct dsa_switch *ds, int port, - struct dsa_bridge bridge) -{ - struct realtek_priv *priv = ds->priv; - unsigned int port_bitmap = 0; - int ret, i; - - /* Loop over all other ports than this one */ - for (i = 0; i < RTL8366RB_PORT_NUM_CPU; i++) { - /* Current port handled last */ - if (i == port) - continue; - /* Not on this bridge */ - if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge)) - continue; - /* Remove this port from any other port on the bridge */ - ret = regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(i), - RTL8366RB_PORT_ISO_PORTS(BIT(port)), 0); - if (ret) - dev_err(priv->dev, "failed to leave port %d\n", port); - - port_bitmap |= BIT(i); - } - - /* Clear the bits for the ports we can not access, leave ourselves */ - regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(port), - RTL8366RB_PORT_ISO_PORTS(port_bitmap), 0); -} - /** * rtl8366rb_drop_untagged() - make the switch drop untagged and C-tagged frames * @priv: SMI state container @@ -1801,8 +1763,8 @@ static const struct dsa_switch_ops rtl8366rb_switch_ops = { .get_strings = rtl8366_get_strings, .get_ethtool_stats = rtl8366_get_ethtool_stats, .get_sset_count = rtl8366_get_sset_count, - .port_bridge_join = rtl8366rb_port_bridge_join, - .port_bridge_leave = rtl8366rb_port_bridge_leave, + .port_bridge_join = rtl83xx_port_bridge_join, + .port_bridge_leave = rtl83xx_port_bridge_leave, .port_vlan_filtering = rtl8366rb_vlan_filtering, .port_vlan_add = rtl8366_vlan_add, .port_vlan_del = rtl8366_vlan_del, @@ -1830,6 +1792,8 @@ static const struct realtek_ops rtl8366rb_ops = { .is_vlan_valid = rtl8366rb_is_vlan_valid, .enable_vlan = rtl8366rb_enable_vlan, .enable_vlan4k = rtl8366rb_enable_vlan4k, + .port_add_isolation = rtl8366rb_port_add_isolation, + .port_remove_isolation = rtl8366rb_port_remove_isolation, .phy_read = rtl8366rb_phy_read, .phy_write = rtl8366rb_phy_write, }; From cc61d5d7c205502d87f720ef86de9a6819f913e7 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 30 Jun 2026 13:19:43 +0200 Subject: [PATCH 0098/1433] net: dsa: realtek: rtl8366rb: Use DSA port iterators Instead of custom loops for intializing the ports (including the CPU port) use the DSA helpers dsa_switch_for_each_port() and dsa_switch_for_each_cpu_port() following the pattern in RTL8365MB by accumulatong masks for the upstream and downstream ports. This gives us similar enough code to the RTL8365MB that we can start using more generic rtl83xx helpers. Reviewed-by: Luiz Angelo Daros de Luca Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260630-rtl8366rb-improvements-v2-3-05eb9d6a37f5@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/dsa/realtek/rtl8366rb.c | 49 ++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/drivers/net/dsa/realtek/rtl8366rb.c b/drivers/net/dsa/realtek/rtl8366rb.c index 8b57ef3bf03a..64215a0d5d6d 100644 --- a/drivers/net/dsa/realtek/rtl8366rb.c +++ b/drivers/net/dsa/realtek/rtl8366rb.c @@ -824,7 +824,10 @@ static int rtl8366rb_setup(struct dsa_switch *ds) { struct realtek_priv *priv = ds->priv; const struct rtl8366rb_jam_tbl_entry *jam_table; + u32 downports_mask = 0; struct rtl8366rb *rb; + u32 upports_mask = 0; + struct dsa_port *dp; u32 chip_ver = 0; u32 chip_id = 0; int jam_size; @@ -895,17 +898,47 @@ static int rtl8366rb_setup(struct dsa_switch *ds) if (ret) return ret; - /* Isolate all user ports so they can only send packets to itself and the CPU port */ - for (i = 0; i < RTL8366RB_PORT_NUM_CPU; i++) { - ret = rtl8366rb_port_set_isolation(priv, i, BIT(RTL8366RB_PORT_NUM_CPU)); + /* Start with all ports blocked, including unused ports */ + dsa_switch_for_each_port(dp, ds) { + /* Start with all ports completely isolated */ + ret = rtl8366rb_port_set_isolation(priv, dp->index, 0); + if (ret) + return ret; + + /* Collect CPU ports. If we support cascade switches, it should + * also include the upstream DSA ports. + */ + if (!dsa_port_is_cpu(dp)) + continue; + + upports_mask |= BIT(dp->index); + } + + /* Configure user ports */ + dsa_switch_for_each_port(dp, ds) { + if (!dsa_port_is_user(dp)) + continue; + + /* Forward only to the CPU */ + ret = rtl8366rb_port_set_isolation(priv, dp->index, upports_mask); + if (ret) + return ret; + + /* If we support cascade switches, it should also include the + * downstream DSA ports. + */ + downports_mask |= BIT(dp->index); + } + + /* Configure CPU ports. If we support cascade switches, this will also + * include DSA ports. + */ + dsa_switch_for_each_cpu_port(dp, ds) { + /* Forward to all user ports */ + ret = rtl8366rb_port_set_isolation(priv, dp->index, downports_mask); if (ret) return ret; } - /* CPU port can send packets to all ports */ - ret = rtl8366rb_port_set_isolation(priv, RTL8366RB_PORT_NUM_CPU, - dsa_user_ports(ds)); - if (ret) - return ret; /* Set up the "green ethernet" feature */ ret = rtl8366rb_jam_table(rtl8366rb_green_jam, From e058ab0c46168d5acb5ce59dbc28b62507b8e426 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 30 Jun 2026 13:19:44 +0200 Subject: [PATCH 0099/1433] net: dsa: realtek: rtl8366rb: Disable STP learning on all ports in setup When we loop over all ports in the switch .setup() callback, make sure to disable learning on all user ports. This is what is normally expected and what the RTL8365MB is doing. Move the code around to accommodate for the new call. Reviewed-by: Luiz Angelo Daros de Luca Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260630-rtl8366rb-improvements-v2-4-05eb9d6a37f5@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/dsa/realtek/rtl8366rb.c | 74 ++++++++++++++++------------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/drivers/net/dsa/realtek/rtl8366rb.c b/drivers/net/dsa/realtek/rtl8366rb.c index 64215a0d5d6d..155bf0010d5f 100644 --- a/drivers/net/dsa/realtek/rtl8366rb.c +++ b/drivers/net/dsa/realtek/rtl8366rb.c @@ -820,6 +820,40 @@ static int rtl8366rb_port_remove_isolation(struct realtek_priv *priv, int port, RTL8366RB_PORT_ISO_PORTS(mask), 0); } +static void +rtl8366rb_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) +{ + struct realtek_priv *priv = ds->priv; + u32 val; + int i; + + switch (state) { + case BR_STATE_DISABLED: + val = RTL8366RB_STP_STATE_DISABLED; + break; + case BR_STATE_BLOCKING: + case BR_STATE_LISTENING: + val = RTL8366RB_STP_STATE_BLOCKING; + break; + case BR_STATE_LEARNING: + val = RTL8366RB_STP_STATE_LEARNING; + break; + case BR_STATE_FORWARDING: + val = RTL8366RB_STP_STATE_FORWARDING; + break; + default: + dev_err(priv->dev, "unknown bridge state requested\n"); + return; + } + + /* Set the same status for the port on all the FIDs */ + for (i = 0; i < RTL8366RB_NUM_FIDS; i++) { + regmap_update_bits(priv->map, RTL8366RB_STP_STATE_BASE + i, + RTL8366RB_STP_STATE_MASK(port), + RTL8366RB_STP_STATE(port, val)); + } +} + static int rtl8366rb_setup(struct dsa_switch *ds) { struct realtek_priv *priv = ds->priv; @@ -900,6 +934,12 @@ static int rtl8366rb_setup(struct dsa_switch *ds) /* Start with all ports blocked, including unused ports */ dsa_switch_for_each_port(dp, ds) { + /* Set the initial STP state of all ports to DISABLED, otherwise + * ports will still forward frames to the CPU despite being + * administratively down by default. + */ + rtl8366rb_port_stp_state_set(ds, dp->index, BR_STATE_DISABLED); + /* Start with all ports completely isolated */ ret = rtl8366rb_port_set_isolation(priv, dp->index, 0); if (ret) @@ -1320,40 +1360,6 @@ rtl8366rb_port_bridge_flags(struct dsa_switch *ds, int port, return 0; } -static void -rtl8366rb_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) -{ - struct realtek_priv *priv = ds->priv; - u32 val; - int i; - - switch (state) { - case BR_STATE_DISABLED: - val = RTL8366RB_STP_STATE_DISABLED; - break; - case BR_STATE_BLOCKING: - case BR_STATE_LISTENING: - val = RTL8366RB_STP_STATE_BLOCKING; - break; - case BR_STATE_LEARNING: - val = RTL8366RB_STP_STATE_LEARNING; - break; - case BR_STATE_FORWARDING: - val = RTL8366RB_STP_STATE_FORWARDING; - break; - default: - dev_err(priv->dev, "unknown bridge state requested\n"); - return; - } - - /* Set the same status for the port on all the FIDs */ - for (i = 0; i < RTL8366RB_NUM_FIDS; i++) { - regmap_update_bits(priv->map, RTL8366RB_STP_STATE_BASE + i, - RTL8366RB_STP_STATE_MASK(port), - RTL8366RB_STP_STATE(port, val)); - } -} - static void rtl8366rb_port_fast_age(struct dsa_switch *ds, int port) { From b269a05961913b81753dc2f9ae87469a6815a534 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 30 Jun 2026 13:19:45 +0200 Subject: [PATCH 0100/1433] net: dsa: realtek: rtl8366rb: Switch to generic learning enablement Instead of just writing the learning disablement register in setup and a custom handling of BR_LEARNING, implement the generic RTL83xx .port_set_learning() callback for setting learning on a port, and call this in the per-port loop in .setup(). Instead of the custom rtl83366rb_port_bridge_flags() function for setting learning mode on each port, use the RTL83xx generic rtl83xx_port_bridge_flags() callback. Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260630-rtl8366rb-improvements-v2-5-05eb9d6a37f5@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/dsa/realtek/rtl8366rb.c | 43 ++++++++++++----------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/drivers/net/dsa/realtek/rtl8366rb.c b/drivers/net/dsa/realtek/rtl8366rb.c index 155bf0010d5f..d2fa8ff6a5d0 100644 --- a/drivers/net/dsa/realtek/rtl8366rb.c +++ b/drivers/net/dsa/realtek/rtl8366rb.c @@ -854,6 +854,16 @@ rtl8366rb_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) } } +static int rtl8366rb_port_set_learning(struct realtek_priv *priv, int port, + bool enable) +{ + /* Notice inverted semantics in this register: setting a bit disables + * learning instead of enabling it. + */ + return regmap_update_bits(priv->map, RTL8366RB_PORT_LEARNDIS_CTRL, + BIT(port), enable ? 0 : BIT(port)); +} + static int rtl8366rb_setup(struct dsa_switch *ds) { struct realtek_priv *priv = ds->priv; @@ -945,6 +955,11 @@ static int rtl8366rb_setup(struct dsa_switch *ds) if (ret) return ret; + /* Disable learning */ + ret = rtl8366rb_port_set_learning(priv, dp->index, false); + if (ret) + return ret; + /* Collect CPU ports. If we support cascade switches, it should * also include the upstream DSA ports. */ @@ -1037,12 +1052,6 @@ static int rtl8366rb_setup(struct dsa_switch *ds) rb->max_mtu[i] = ETH_DATA_LEN; } - /* Disable learning for all ports */ - ret = regmap_write(priv->map, RTL8366RB_PORT_LEARNDIS_CTRL, - RTL8366RB_PORT_ALL); - if (ret) - return ret; - /* Enable auto ageing for all ports */ ret = regmap_write(priv->map, RTL8366RB_SECURITY_CTRL, 0); if (ret) @@ -1341,25 +1350,6 @@ rtl8366rb_port_pre_bridge_flags(struct dsa_switch *ds, int port, return 0; } -static int -rtl8366rb_port_bridge_flags(struct dsa_switch *ds, int port, - struct switchdev_brport_flags flags, - struct netlink_ext_ack *extack) -{ - struct realtek_priv *priv = ds->priv; - int ret; - - if (flags.mask & BR_LEARNING) { - ret = regmap_update_bits(priv->map, RTL8366RB_PORT_LEARNDIS_CTRL, - BIT(port), - (flags.val & BR_LEARNING) ? 0 : BIT(port)); - if (ret) - return ret; - } - - return 0; -} - static void rtl8366rb_port_fast_age(struct dsa_switch *ds, int port) { @@ -1810,7 +1800,7 @@ static const struct dsa_switch_ops rtl8366rb_switch_ops = { .port_enable = rtl8366rb_port_enable, .port_disable = rtl8366rb_port_disable, .port_pre_bridge_flags = rtl8366rb_port_pre_bridge_flags, - .port_bridge_flags = rtl8366rb_port_bridge_flags, + .port_bridge_flags = rtl83xx_port_bridge_flags, .port_stp_state_set = rtl8366rb_port_stp_state_set, .port_fast_age = rtl8366rb_port_fast_age, .port_change_mtu = rtl8366rb_change_mtu, @@ -1833,6 +1823,7 @@ static const struct realtek_ops rtl8366rb_ops = { .enable_vlan4k = rtl8366rb_enable_vlan4k, .port_add_isolation = rtl8366rb_port_add_isolation, .port_remove_isolation = rtl8366rb_port_remove_isolation, + .port_set_learning = rtl8366rb_port_set_learning, .phy_read = rtl8366rb_phy_read, .phy_write = rtl8366rb_phy_write, }; From 49c5ad3cd51885fe9883cc641a0b5a5c3a99dbbe Mon Sep 17 00:00:00 2001 From: Ratheesh Kannoth Date: Tue, 30 Jun 2026 07:08:14 +0530 Subject: [PATCH 0101/1433] octeontx2-pf: link RQ page pools to netdev for Netlink stats page_pool_create() only registers pools with the netdev Netlink interface when pp_params.netdev is set. Set netdev in page pool params. Signed-off-by: Ratheesh Kannoth Link: https://patch.msgid.link/20260630013814.3657831-1-rkannoth@marvell.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c | 1 + drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c b/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c index dbf173196608..8e41431c7f9c 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c @@ -656,6 +656,7 @@ static int cn20k_pool_aq_init(struct otx2_nic *pfvf, u16 pool_id, pp_params.nid = NUMA_NO_NODE; pp_params.dev = pfvf->dev; pp_params.dma_dir = DMA_FROM_DEVICE; + pp_params.netdev = pfvf->netdev; pool->page_pool = page_pool_create(&pp_params); if (IS_ERR(pool->page_pool)) { netdev_err(pfvf->netdev, "Creation of page pool failed\n"); diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c index 3d253132a17f..ca73a94db794 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c @@ -1035,7 +1035,6 @@ int otx2_sq_init(struct otx2_nic *pfvf, u16 qidx, u16 sqb_aura) if (qidx > pfvf->hw.xdp_queues) otx2_attach_xsk_buff(pfvf, sq, (qidx - pfvf->hw.xdp_queues)); - chan_offset = qidx % pfvf->hw.tx_chan_cnt; err = pfvf->hw_ops->sq_aq_init(pfvf, qidx, chan_offset, sqb_aura); if (err) { @@ -1517,6 +1516,7 @@ int otx2_pool_aq_init(struct otx2_nic *pfvf, u16 pool_id, pp_params.nid = NUMA_NO_NODE; pp_params.dev = pfvf->dev; pp_params.dma_dir = DMA_FROM_DEVICE; + pp_params.netdev = pfvf->netdev; pool->page_pool = page_pool_create(&pp_params); if (IS_ERR(pool->page_pool)) { netdev_err(pfvf->netdev, "Creation of page pool failed\n"); From b8ea7da314c2efcb9c2f559ed65b7a36c869d68e Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 29 Jun 2026 18:51:37 -0700 Subject: [PATCH 0102/1433] net: dsa: qca8k: fall back to ethernet-ports node name for LEDs The device tree binding allows both "ports" and "ethernet-ports" as the container node name. Try "ethernet-ports" when "ports" is absent so that newer DTBs with the preferred name work. This matches the handling already present in qca8k-8xxx.c Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260630015137.1591152-1-rosenp@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/dsa/qca/qca8k-leds.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/dsa/qca/qca8k-leds.c b/drivers/net/dsa/qca/qca8k-leds.c index ef496e345a4e..0ada28377f46 100644 --- a/drivers/net/dsa/qca/qca8k-leds.c +++ b/drivers/net/dsa/qca/qca8k-leds.c @@ -457,6 +457,9 @@ qca8k_setup_led_ctrl(struct qca8k_priv *priv) int ret; ports = device_get_named_child_node(priv->dev, "ports"); + if (!ports) + ports = device_get_named_child_node(priv->dev, "ethernet-ports"); + if (!ports) { dev_info(priv->dev, "No ports node specified in device tree!"); return 0; From b010e2a4a9ac2bcd0db2c3a41877d59d827a8a80 Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Fri, 20 Mar 2026 16:19:39 +0100 Subject: [PATCH 0103/1433] netfilter: nfnetlink_hook: Dump nat type chains These chains are indirectly attached to the hook since they are not called for packets belonging to an established connection. Introduce NF_HOOK_OP_NAT to identify the container and dump attached entries instead of the container itself. Dump these entries with the dispatcher's priority value since their own priority merely defines ordering within the dispatcher's list. Signed-off-by: Phil Sutter Signed-off-by: Florian Westphal --- include/linux/netfilter.h | 7 +++++++ net/netfilter/nf_nat_core.c | 6 ------ net/netfilter/nf_nat_proto.c | 8 ++++++++ net/netfilter/nfnetlink_hook.c | 37 ++++++++++++++++++++++++++++++---- 4 files changed, 48 insertions(+), 10 deletions(-) diff --git a/include/linux/netfilter.h b/include/linux/netfilter.h index efbbfa770d66..e99afc1414cd 100644 --- a/include/linux/netfilter.h +++ b/include/linux/netfilter.h @@ -93,6 +93,7 @@ enum nf_hook_ops_type { NF_HOOK_OP_NF_TABLES, NF_HOOK_OP_BPF, NF_HOOK_OP_NFT_FT, + NF_HOOK_OP_NAT, }; struct nf_hook_ops { @@ -140,6 +141,12 @@ struct nf_hook_entries { */ }; +struct nf_nat_lookup_hook_priv { + struct nf_hook_entries __rcu *entries; + + struct rcu_head rcu_head; +}; + #ifdef CONFIG_NETFILTER static inline struct nf_hook_ops **nf_hook_entries_get_hook_ops(const struct nf_hook_entries *e) { diff --git a/net/netfilter/nf_nat_core.c b/net/netfilter/nf_nat_core.c index 63ff6b4d5d21..8ac326e1eb5b 100644 --- a/net/netfilter/nf_nat_core.c +++ b/net/netfilter/nf_nat_core.c @@ -39,12 +39,6 @@ static struct hlist_head *nf_nat_bysource __read_mostly; static unsigned int nf_nat_htable_size __read_mostly; static siphash_aligned_key_t nf_nat_hash_rnd; -struct nf_nat_lookup_hook_priv { - struct nf_hook_entries __rcu *entries; - - struct rcu_head rcu_head; -}; - struct nf_nat_hooks_net { struct nf_hook_ops *nat_hook_ops; unsigned int users; diff --git a/net/netfilter/nf_nat_proto.c b/net/netfilter/nf_nat_proto.c index 07f51fe75fbe..64b9bac228ea 100644 --- a/net/netfilter/nf_nat_proto.c +++ b/net/netfilter/nf_nat_proto.c @@ -770,6 +770,7 @@ static const struct nf_hook_ops nf_nat_ipv4_ops[] = { .pf = NFPROTO_IPV4, .hooknum = NF_INET_PRE_ROUTING, .priority = NF_IP_PRI_NAT_DST, + .hook_ops_type = NF_HOOK_OP_NAT, }, /* After packet filtering, change source */ { @@ -777,6 +778,7 @@ static const struct nf_hook_ops nf_nat_ipv4_ops[] = { .pf = NFPROTO_IPV4, .hooknum = NF_INET_POST_ROUTING, .priority = NF_IP_PRI_NAT_SRC, + .hook_ops_type = NF_HOOK_OP_NAT, }, /* Before packet filtering, change destination */ { @@ -784,6 +786,7 @@ static const struct nf_hook_ops nf_nat_ipv4_ops[] = { .pf = NFPROTO_IPV4, .hooknum = NF_INET_LOCAL_OUT, .priority = NF_IP_PRI_NAT_DST, + .hook_ops_type = NF_HOOK_OP_NAT, }, /* After packet filtering, change source */ { @@ -791,6 +794,7 @@ static const struct nf_hook_ops nf_nat_ipv4_ops[] = { .pf = NFPROTO_IPV4, .hooknum = NF_INET_LOCAL_IN, .priority = NF_IP_PRI_NAT_SRC, + .hook_ops_type = NF_HOOK_OP_NAT, }, }; @@ -1031,6 +1035,7 @@ static const struct nf_hook_ops nf_nat_ipv6_ops[] = { .pf = NFPROTO_IPV6, .hooknum = NF_INET_PRE_ROUTING, .priority = NF_IP6_PRI_NAT_DST, + .hook_ops_type = NF_HOOK_OP_NAT, }, /* After packet filtering, change source */ { @@ -1038,6 +1043,7 @@ static const struct nf_hook_ops nf_nat_ipv6_ops[] = { .pf = NFPROTO_IPV6, .hooknum = NF_INET_POST_ROUTING, .priority = NF_IP6_PRI_NAT_SRC, + .hook_ops_type = NF_HOOK_OP_NAT, }, /* Before packet filtering, change destination */ { @@ -1045,6 +1051,7 @@ static const struct nf_hook_ops nf_nat_ipv6_ops[] = { .pf = NFPROTO_IPV6, .hooknum = NF_INET_LOCAL_OUT, .priority = NF_IP6_PRI_NAT_DST, + .hook_ops_type = NF_HOOK_OP_NAT, }, /* After packet filtering, change source */ { @@ -1052,6 +1059,7 @@ static const struct nf_hook_ops nf_nat_ipv6_ops[] = { .pf = NFPROTO_IPV6, .hooknum = NF_INET_LOCAL_IN, .priority = NF_IP6_PRI_NAT_SRC, + .hook_ops_type = NF_HOOK_OP_NAT, }, }; diff --git a/net/netfilter/nfnetlink_hook.c b/net/netfilter/nfnetlink_hook.c index 5623c18fcd12..95005e9a6066 100644 --- a/net/netfilter/nfnetlink_hook.c +++ b/net/netfilter/nfnetlink_hook.c @@ -190,7 +190,7 @@ static int nfnl_hook_put_nft_ft_info(struct sk_buff *nlskb, static int nfnl_hook_dump_one(struct sk_buff *nlskb, const struct nfnl_dump_hook_data *ctx, - const struct nf_hook_ops *ops, + const struct nf_hook_ops *ops, int priority, int family, unsigned int seq) { u16 event = nfnl_msg_type(NFNL_SUBSYS_HOOK, NFNL_MSG_HOOK_GET); @@ -244,7 +244,7 @@ static int nfnl_hook_dump_one(struct sk_buff *nlskb, if (ret) goto nla_put_failure; - ret = nla_put_be32(nlskb, NFNLA_HOOK_PRIORITY, htonl(ops->priority)); + ret = nla_put_be32(nlskb, NFNLA_HOOK_PRIORITY, htonl(priority)); if (ret) goto nla_put_failure; @@ -337,6 +337,30 @@ nfnl_hook_entries_head(u8 pf, unsigned int hook, struct net *net, const char *de return hook_head; } +static int nfnl_hook_dump_nat(struct sk_buff *nlskb, + const struct nfnl_dump_hook_data *ctx, + const struct nf_hook_ops *ops, + int family, unsigned int seq) +{ + struct nf_nat_lookup_hook_priv *priv = ops->priv; + struct nf_hook_entries *e = rcu_dereference(priv->entries); + struct nf_hook_ops **nat_ops; + int i, err; + + if (!e) + return 0; + + nat_ops = nf_hook_entries_get_hook_ops(e); + + for (i = 0; i < e->num_hook_entries; i++) { + err = nfnl_hook_dump_one(nlskb, ctx, nat_ops[i], + ops->priority, family, seq); + if (err) + return err; + } + return 0; +} + static int nfnl_hook_dump(struct sk_buff *nlskb, struct netlink_callback *cb) { @@ -365,8 +389,13 @@ static int nfnl_hook_dump(struct sk_buff *nlskb, ops = nf_hook_entries_get_hook_ops(e); for (; i < e->num_hook_entries; i++) { - err = nfnl_hook_dump_one(nlskb, ctx, ops[i], family, - cb->nlh->nlmsg_seq); + if (ops[i]->hook_ops_type == NF_HOOK_OP_NAT) + err = nfnl_hook_dump_nat(nlskb, ctx, ops[i], family, + cb->nlh->nlmsg_seq); + else + err = nfnl_hook_dump_one(nlskb, ctx, ops[i], + ops[i]->priority, family, + cb->nlh->nlmsg_seq); if (err) break; } From 9cc4d9720d70f391dca52a07e72652f1693a6239 Mon Sep 17 00:00:00 2001 From: Ian Bridges Date: Fri, 26 Jun 2026 17:25:35 -0500 Subject: [PATCH 0104/1433] netfilter: x_tables: replace strlcat() with snprintf() In preparation for removing the deprecated strlcat() API[1], replace the strscpy()/strlcat() pairs in xt_proto_init() and xt_proto_fini() with snprintf(), which builds each /proc file name in a single call. Each name is "", where is the address-family string xt_prefix[af] and is one of the FORMAT_TABLES, FORMAT_MATCHES or FORMAT_TARGETS literals. Prepend %s to the FORMAT macros and switch to snprintf(). Link: https://github.com/KSPP/linux/issues/370 [1] Signed-off-by: Ian Bridges Signed-off-by: Florian Westphal --- net/netfilter/x_tables.c | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c index 4e6708c23922..e64116bf2637 100644 --- a/net/netfilter/x_tables.c +++ b/net/netfilter/x_tables.c @@ -1920,9 +1920,9 @@ static const struct seq_operations xt_target_seq_ops = { .show = xt_target_seq_show, }; -#define FORMAT_TABLES "_tables_names" -#define FORMAT_MATCHES "_tables_matches" -#define FORMAT_TARGETS "_tables_targets" +#define FORMAT_TABLES "%s_tables_names" +#define FORMAT_MATCHES "%s_tables_matches" +#define FORMAT_TARGETS "%s_tables_targets" #endif /* CONFIG_PROC_FS */ @@ -2033,8 +2033,7 @@ int xt_proto_init(struct net *net, u_int8_t af) root_uid = make_kuid(net->user_ns, 0); root_gid = make_kgid(net->user_ns, 0); - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_TABLES, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_TABLES, xt_prefix[af]); proc = proc_create_net_data(buf, 0440, net->proc_net, &xt_table_seq_ops, sizeof(struct seq_net_private), (void *)(unsigned long)af); @@ -2043,8 +2042,7 @@ int xt_proto_init(struct net *net, u_int8_t af) if (uid_valid(root_uid) && gid_valid(root_gid)) proc_set_user(proc, root_uid, root_gid); - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_MATCHES, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_MATCHES, xt_prefix[af]); proc = proc_create_seq_private(buf, 0440, net->proc_net, &xt_match_seq_ops, sizeof(struct nf_mttg_trav), (void *)(unsigned long)af); @@ -2053,8 +2051,7 @@ int xt_proto_init(struct net *net, u_int8_t af) if (uid_valid(root_uid) && gid_valid(root_gid)) proc_set_user(proc, root_uid, root_gid); - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_TARGETS, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_TARGETS, xt_prefix[af]); proc = proc_create_seq_private(buf, 0440, net->proc_net, &xt_target_seq_ops, sizeof(struct nf_mttg_trav), (void *)(unsigned long)af); @@ -2068,13 +2065,11 @@ int xt_proto_init(struct net *net, u_int8_t af) #ifdef CONFIG_PROC_FS out_remove_matches: - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_MATCHES, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_MATCHES, xt_prefix[af]); remove_proc_entry(buf, net->proc_net); out_remove_tables: - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_TABLES, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_TABLES, xt_prefix[af]); remove_proc_entry(buf, net->proc_net); out: return -1; @@ -2087,16 +2082,13 @@ void xt_proto_fini(struct net *net, u_int8_t af) #ifdef CONFIG_PROC_FS char buf[XT_FUNCTION_MAXNAMELEN]; - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_TABLES, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_TABLES, xt_prefix[af]); remove_proc_entry(buf, net->proc_net); - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_TARGETS, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_TARGETS, xt_prefix[af]); remove_proc_entry(buf, net->proc_net); - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_MATCHES, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_MATCHES, xt_prefix[af]); remove_proc_entry(buf, net->proc_net); #endif /*CONFIG_PROC_FS*/ } From 32b00984e002708d55b3ad3830198d3ba9126e09 Mon Sep 17 00:00:00 2001 From: Carlos Grillet Date: Thu, 25 Jun 2026 19:25:46 +0200 Subject: [PATCH 0105/1433] netfilter: replace u_int8_t and u_int16t with u8 and u16 Use preferred kernel integer type u8 instead of the POSIX u_int8_t variant. No functional change. Signed-off-by: Carlos Grillet Signed-off-by: Florian Westphal --- include/net/ip_vs.h | 2 +- net/netfilter/ipvs/ip_vs_nfct.c | 2 +- net/netfilter/nf_conntrack_amanda.c | 2 +- net/netfilter/nf_conntrack_h323_main.c | 2 +- net/netfilter/xt_TCPOPTSTRIP.c | 8 ++++---- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index 49297fec448a..ed2e9bc1bb4e 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -2123,7 +2123,7 @@ void ip_vs_update_conntrack(struct sk_buff *skb, struct ip_vs_conn *cp, int outin); int ip_vs_confirm_conntrack(struct sk_buff *skb); void ip_vs_nfct_expect_related(struct sk_buff *skb, struct nf_conn *ct, - struct ip_vs_conn *cp, u_int8_t proto, + struct ip_vs_conn *cp, u8 proto, const __be16 port, int from_rs); void ip_vs_conn_drop_conntrack(struct ip_vs_conn *cp); diff --git a/net/netfilter/ipvs/ip_vs_nfct.c b/net/netfilter/ipvs/ip_vs_nfct.c index 81974f69e5bb..347185fd0c8c 100644 --- a/net/netfilter/ipvs/ip_vs_nfct.c +++ b/net/netfilter/ipvs/ip_vs_nfct.c @@ -208,7 +208,7 @@ static void ip_vs_nfct_expect_callback(struct nf_conn *ct, * Use port 0 to expect connection from any port. */ void ip_vs_nfct_expect_related(struct sk_buff *skb, struct nf_conn *ct, - struct ip_vs_conn *cp, u_int8_t proto, + struct ip_vs_conn *cp, u8 proto, const __be16 port, int from_rs) { struct nf_conntrack_expect *exp; diff --git a/net/netfilter/nf_conntrack_amanda.c b/net/netfilter/nf_conntrack_amanda.c index ddafbdfc96dc..f10ac2c49f4b 100644 --- a/net/netfilter/nf_conntrack_amanda.c +++ b/net/netfilter/nf_conntrack_amanda.c @@ -89,7 +89,7 @@ static int amanda_help(struct sk_buff *skb, struct nf_conntrack_tuple *tuple; unsigned int dataoff, start, stop, off, i; char pbuf[sizeof("65535")], *tmp; - u_int16_t len; + u16 len; __be16 port; int ret = NF_ACCEPT; nf_nat_amanda_hook_fn *nf_nat_amanda; diff --git a/net/netfilter/nf_conntrack_h323_main.c b/net/netfilter/nf_conntrack_h323_main.c index 24931e379985..37b6314ca772 100644 --- a/net/netfilter/nf_conntrack_h323_main.c +++ b/net/netfilter/nf_conntrack_h323_main.c @@ -671,7 +671,7 @@ static int expect_h245(struct sk_buff *skb, struct nf_conn *ct, static int callforward_do_filter(struct net *net, const union nf_inet_addr *src, const union nf_inet_addr *dst, - u_int8_t family) + u8 family) { int ret = 0; diff --git a/net/netfilter/xt_TCPOPTSTRIP.c b/net/netfilter/xt_TCPOPTSTRIP.c index 93f064306901..265d21697847 100644 --- a/net/netfilter/xt_TCPOPTSTRIP.c +++ b/net/netfilter/xt_TCPOPTSTRIP.c @@ -16,7 +16,7 @@ #include #include -static inline unsigned int optlen(const u_int8_t *opt, unsigned int offset) +static inline unsigned int optlen(const u8 *opt, unsigned int offset) { /* Beware zero-length options: make finite progress */ if (opt[offset] <= TCPOPT_NOP || opt[offset+1] == 0) @@ -33,8 +33,8 @@ tcpoptstrip_mangle_packet(struct sk_buff *skb, const struct xt_tcpoptstrip_target_info *info = par->targinfo; struct tcphdr *tcph, _th; unsigned int optl, i, j; - u_int16_t n, o; - u_int8_t *opt; + u16 n, o; + u8 *opt; int tcp_hdrlen; /* This is a fragment, no TCP header is available */ @@ -97,7 +97,7 @@ tcpoptstrip_tg6(struct sk_buff *skb, const struct xt_action_param *par) { struct ipv6hdr *ipv6h = ipv6_hdr(skb); int tcphoff; - u_int8_t nexthdr; + u8 nexthdr; __be16 frag_off; nexthdr = ipv6h->nexthdr; From 1501ab0701fdd4571658e5829a2178f1af1f3917 Mon Sep 17 00:00:00 2001 From: David Laight Date: Mon, 8 Jun 2026 10:54:59 +0100 Subject: [PATCH 0106/1433] netfilter: avoid strcpy usage Replacing strcpy() with strscpy() ensures that overflow of the target buffer cannot happen. [ fw@strlen.de: cleanup. netlink policy rejects too large inputs, xt_recent validates content and length before the copy ] Signed-off-by: David Laight Signed-off-by: Florian Westphal --- net/netfilter/nfnetlink_cttimeout.c | 2 +- net/netfilter/xt_recent.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/net/netfilter/nfnetlink_cttimeout.c b/net/netfilter/nfnetlink_cttimeout.c index 170d3db860c5..66c2016f6049 100644 --- a/net/netfilter/nfnetlink_cttimeout.c +++ b/net/netfilter/nfnetlink_cttimeout.c @@ -168,7 +168,7 @@ static int cttimeout_new_timeout(struct sk_buff *skb, if (ret < 0) goto err_free_timeout_policy; - strcpy(timeout->name, nla_data(cda[CTA_TIMEOUT_NAME])); + nla_strscpy(timeout->name, cda[CTA_TIMEOUT_NAME], sizeof(timeout->name)); timeout->timeout->l3num = l3num; timeout->timeout->l4proto = l4proto; refcount_set(&timeout->timeout->refcnt, 1); diff --git a/net/netfilter/xt_recent.c b/net/netfilter/xt_recent.c index f72752fa4374..d34831ce3adf 100644 --- a/net/netfilter/xt_recent.c +++ b/net/netfilter/xt_recent.c @@ -400,7 +400,7 @@ static int recent_mt_check(const struct xt_mtchk_param *par, t->nstamps_max_mask = nstamp_mask; memcpy(&t->mask, &info->mask, sizeof(t->mask)); - strcpy(t->name, info->name); + strscpy(t->name, info->name); INIT_LIST_HEAD(&t->lru_list); for (i = 0; i < ip_list_hash_size; i++) INIT_LIST_HEAD(&t->iphash[i]); From 5efbced92ec19514528ce6b18ae8e1b755cc4552 Mon Sep 17 00:00:00 2001 From: Subasri S Date: Thu, 25 Jun 2026 19:01:24 +0530 Subject: [PATCH 0107/1433] netfilter: remove redundant null check before kvfree() kvfree() internally performs NULL check on the pointer handed to it and takes no action if it indeed is NULL. Hence there is no need for a pre-check of the memory pointer before handing it to kvfree(). Issue reported by ifnullfree.cocci Coccinelle semantic patch script. Signed-off-by: Subasri S Signed-off-by: Florian Westphal --- net/netfilter/nft_set_rbtree.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c index 018bbb6df4ce..efc25e788a1c 100644 --- a/net/netfilter/nft_set_rbtree.c +++ b/net/netfilter/nft_set_rbtree.c @@ -544,8 +544,7 @@ static int nft_array_intervals_alloc(struct nft_array *array, u32 max_intervals) if (!intervals) return -ENOMEM; - if (array->intervals) - kvfree(array->intervals); + kvfree(array->intervals); array->intervals = intervals; array->max_intervals = max_intervals; From 68fc6c6470d653fcfa7655f4f2b36ff444cb72b3 Mon Sep 17 00:00:00 2001 From: Feng Wu Date: Thu, 25 Jun 2026 04:44:26 -0400 Subject: [PATCH 0108/1433] netfilter: xt_tcpmss: add checkentry for parameter validation Add tcpmss_mt_check() that validates mss_min <= mss_max and invert <= 1. Signed-off-by: Feng Wu Signed-off-by: Florian Westphal --- net/netfilter/xt_tcpmss.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/net/netfilter/xt_tcpmss.c b/net/netfilter/xt_tcpmss.c index b9da8269161d..b08b077d7f0a 100644 --- a/net/netfilter/xt_tcpmss.c +++ b/net/netfilter/xt_tcpmss.c @@ -78,10 +78,23 @@ tcpmss_mt(const struct sk_buff *skb, struct xt_action_param *par) return false; } +static int tcpmss_mt_check(const struct xt_mtchk_param *par) +{ + const struct xt_tcpmss_match_info *info = par->matchinfo; + + if (info->mss_min > info->mss_max) + return -EINVAL; + if (info->invert > 1) + return -EINVAL; + + return 0; +} + static struct xt_match tcpmss_mt_reg[] __read_mostly = { { .name = "tcpmss", .family = NFPROTO_IPV4, + .checkentry = tcpmss_mt_check, .match = tcpmss_mt, .matchsize = sizeof(struct xt_tcpmss_match_info), .proto = IPPROTO_TCP, From 60aee97fc7f8c0ad18474b984db47210e5a7b5f2 Mon Sep 17 00:00:00 2001 From: Feng Wu Date: Thu, 25 Jun 2026 01:43:34 -0700 Subject: [PATCH 0109/1433] netfilter: xt_dscp: add checkentry for tos match The 'tos' match registered in xt_dscp.c has no .checkentry callback, allowing userspace to insert rules with a non-boolean invert field without any validation. Add tos_mt_check() that rejects invert > 1 and attach it to both the IPv4 and IPv6 'tos' match registrations. Signed-off-by: Feng Wu Signed-off-by: Florian Westphal --- net/netfilter/xt_dscp.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/net/netfilter/xt_dscp.c b/net/netfilter/xt_dscp.c index fb0169a8f9bb..878f27016e99 100644 --- a/net/netfilter/xt_dscp.c +++ b/net/netfilter/xt_dscp.c @@ -49,6 +49,16 @@ static int dscp_mt_check(const struct xt_mtchk_param *par) return 0; } +static int tos_mt_check(const struct xt_mtchk_param *par) +{ + const struct xt_tos_match_info *info = par->matchinfo; + + if (info->invert > 1) + return -EINVAL; + + return 0; +} + static bool tos_mt(const struct sk_buff *skb, struct xt_action_param *par) { const struct xt_tos_match_info *info = par->matchinfo; @@ -82,6 +92,7 @@ static struct xt_match dscp_mt_reg[] __read_mostly = { .name = "tos", .revision = 1, .family = NFPROTO_IPV4, + .checkentry = tos_mt_check, .match = tos_mt, .matchsize = sizeof(struct xt_tos_match_info), .me = THIS_MODULE, @@ -90,6 +101,7 @@ static struct xt_match dscp_mt_reg[] __read_mostly = { .name = "tos", .revision = 1, .family = NFPROTO_IPV6, + .checkentry = tos_mt_check, .match = tos_mt, .matchsize = sizeof(struct xt_tos_match_info), .me = THIS_MODULE, From 26fb502773bc472723930a59dbe561d250c45a5a Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Mon, 29 Jun 2026 14:58:21 +0200 Subject: [PATCH 0110/1433] netfilter: nf_conntrack_helper: do not hash by tuple Long time ago helpers were auto-assigned to connections based on port/protocol match. For this reason, nf_conntrack_helper still contains a full tuple. Nowadays the only relevant entries in the tuple are the l3 and l4 protocol numbers. Prepare for tuple removal and switch to hashing name and l4 protocol. l3num cannot be used because helpers can also register for "unspec" protocol. Signed-off-by: Florian Westphal --- net/netfilter/nf_conntrack_helper.c | 67 +++++++++++++---------------- 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/net/netfilter/nf_conntrack_helper.c b/net/netfilter/nf_conntrack_helper.c index 500509b17663..5ad5429352a7 100644 --- a/net/netfilter/nf_conntrack_helper.c +++ b/net/netfilter/nf_conntrack_helper.c @@ -40,12 +40,16 @@ static unsigned int nf_ct_helper_count __read_mostly; static DEFINE_MUTEX(nf_ct_nat_helpers_mutex); static struct list_head nf_ct_nat_helpers __read_mostly; -/* Stupid hash, but collision free for the default registrations of the - * helpers currently in the kernel. */ -static unsigned int helper_hash(const struct nf_conntrack_tuple *tuple) +static unsigned int helper_hash(const char *name, u8 protonum) { - return (((tuple->src.l3num << 8) | tuple->dst.protonum) ^ - (__force __u16)tuple->src.u.all) % nf_ct_helper_hsize; + static u32 seed; + u32 initval; + + get_random_once(&seed, sizeof(seed)); + + initval = seed ^ protonum; + + return jhash(name, strlen(name), initval) % nf_ct_helper_hsize; } struct nf_conntrack_helper * @@ -54,18 +58,21 @@ __nf_conntrack_helper_find(const char *name, u16 l3num, u8 protonum) struct nf_conntrack_helper *h; unsigned int i; - for (i = 0; i < nf_ct_helper_hsize; i++) { - hlist_for_each_entry_rcu(h, &nf_ct_helper_hash[i], hnode) { - if (strcmp(h->name, name)) - continue; + if (!nf_ct_helper_hash) + return NULL; - if (h->tuple.src.l3num != NFPROTO_UNSPEC && - h->tuple.src.l3num != l3num) - continue; + i = helper_hash(name, protonum); - if (h->tuple.dst.protonum == protonum) - return h; - } + hlist_for_each_entry_rcu(h, &nf_ct_helper_hash[i], hnode) { + if (strcmp(h->name, name)) + continue; + + if (h->tuple.src.l3num != NFPROTO_UNSPEC && + h->tuple.src.l3num != l3num) + continue; + + if (h->tuple.dst.protonum == protonum) + return h; } return NULL; } @@ -363,9 +370,8 @@ EXPORT_SYMBOL_GPL(nf_ct_helper_log); int __nf_conntrack_helper_register(struct nf_conntrack_helper *me) { - struct nf_conntrack_tuple_mask mask = { .src.u.all = htons(0xFFFF) }; - unsigned int h = helper_hash(&me->tuple); struct nf_conntrack_helper *cur; + unsigned int h; int ret = 0, i; BUG_ON(me->expect_class_max >= NF_CT_MAX_EXPECT_CLASSES); @@ -382,29 +388,18 @@ int __nf_conntrack_helper_register(struct nf_conntrack_helper *me) return -EINVAL; } + h = helper_hash(me->name, me->tuple.dst.protonum); mutex_lock(&nf_ct_helper_mutex); - for (i = 0; i < nf_ct_helper_hsize; i++) { - hlist_for_each_entry(cur, &nf_ct_helper_hash[i], hnode) { - if (!strcmp(cur->name, me->name) && - (cur->tuple.src.l3num == NFPROTO_UNSPEC || - cur->tuple.src.l3num == me->tuple.src.l3num) && - cur->tuple.dst.protonum == me->tuple.dst.protonum) { - ret = -EBUSY; - goto out; - } + hlist_for_each_entry(cur, &nf_ct_helper_hash[h], hnode) { + if (!strcmp(cur->name, me->name) && + (cur->tuple.src.l3num == NFPROTO_UNSPEC || + cur->tuple.src.l3num == me->tuple.src.l3num) && + cur->tuple.dst.protonum == me->tuple.dst.protonum) { + ret = -EBUSY; + goto out; } } - /* avoid unpredictable behaviour for auto_assign_helper */ - if (!(me->flags & NF_CT_HELPER_F_USERSPACE)) { - hlist_for_each_entry(cur, &nf_ct_helper_hash[h], hnode) { - if (nf_ct_tuple_src_mask_cmp(&cur->tuple, &me->tuple, - &mask)) { - ret = -EBUSY; - goto out; - } - } - } refcount_set(&me->ct_refcnt, 1); hlist_add_head_rcu(&me->hnode, &nf_ct_helper_hash[h]); nf_ct_helper_count++; From 5de6c8ad0bcccef1be55ad07d29833df69b601cf Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Mon, 29 Jun 2026 14:58:22 +0200 Subject: [PATCH 0111/1433] netfilter: conntrack: get rid of tuple in helper definitions Leftover from the days when the kernel did automatic assignment of helpers based on a pre-registered / well-known-port. This helper autoassign was removed from the kernel, so all we really need are the l3 and l4 protocol numbers. In the broadcast helper, the only remaining consumer of the port number is removed. AFAICS its not needed: The expectation is populated from the control connection reply tuple, so the src port is the original directions destination (snmp/161 for example). LLM complained about silent l3num (u16) -> nfproto (u8) truncation, so add a netlink policy validation to reject large NFPROTO values upfront. Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Florian Westphal --- include/net/netfilter/nf_conntrack_helper.h | 9 ++++----- net/netfilter/nf_conntrack_broadcast.c | 2 -- net/netfilter/nf_conntrack_helper.c | 22 +++++++++------------ net/netfilter/nf_conntrack_ovs.c | 6 +++--- net/netfilter/nfnetlink_cthelper.c | 21 ++++++++++---------- net/sched/act_ct.c | 4 ++-- 6 files changed, 29 insertions(+), 35 deletions(-) diff --git a/include/net/netfilter/nf_conntrack_helper.h b/include/net/netfilter/nf_conntrack_helper.h index c761cd8158b2..f3f0c1392e88 100644 --- a/include/net/netfilter/nf_conntrack_helper.h +++ b/include/net/netfilter/nf_conntrack_helper.h @@ -43,11 +43,10 @@ struct nf_conntrack_helper { refcount_t ct_refcnt; - /* Tuple of things we will help (compared against server response) */ - struct nf_conntrack_tuple tuple; + u8 nfproto; /* NFPROTO_*, can be NFPROTO_UNSPEC */ + u8 l4proto; /* IPPROTO_UDP/TCP */ - /* Function to call when data passes; return verdict, or -1 to - invalidate. */ + /* Function to call when data passes; return verdict */ int __rcu (*help)(struct sk_buff *skb, unsigned int protoff, struct nf_conn *ct, enum ip_conntrack_info conntrackinfo); @@ -94,7 +93,7 @@ struct nf_conntrack_helper *nf_conntrack_helper_try_module_get(const char *name, void nf_conntrack_helper_put(struct nf_conntrack_helper *helper); void nf_ct_helper_init(struct nf_conntrack_helper *helper, - u16 l3num, u16 protonum, const char *name, + u8 l3num, u16 protonum, const char *name, u16 default_port, u16 spec_port, u32 id, const struct nf_conntrack_expect_policy *exp_pol, u32 expect_class_max, diff --git a/net/netfilter/nf_conntrack_broadcast.c b/net/netfilter/nf_conntrack_broadcast.c index bf78828c7549..6ff954f1bfb8 100644 --- a/net/netfilter/nf_conntrack_broadcast.c +++ b/net/netfilter/nf_conntrack_broadcast.c @@ -66,8 +66,6 @@ int nf_conntrack_broadcast_help(struct sk_buff *skb, exp->tuple = ct->tuplehash[IP_CT_DIR_REPLY].tuple; helper = rcu_dereference(help->helper); - if (helper) - exp->tuple.src.u.udp.port = helper->tuple.src.u.udp.port; exp->mask.src.u3.ip = mask; exp->mask.src.u.udp.port = htons(0xFFFF); diff --git a/net/netfilter/nf_conntrack_helper.c b/net/netfilter/nf_conntrack_helper.c index 5ad5429352a7..b28986100db0 100644 --- a/net/netfilter/nf_conntrack_helper.c +++ b/net/netfilter/nf_conntrack_helper.c @@ -66,12 +66,9 @@ __nf_conntrack_helper_find(const char *name, u16 l3num, u8 protonum) hlist_for_each_entry_rcu(h, &nf_ct_helper_hash[i], hnode) { if (strcmp(h->name, name)) continue; - - if (h->tuple.src.l3num != NFPROTO_UNSPEC && - h->tuple.src.l3num != l3num) + if (h->nfproto != NFPROTO_UNSPEC && h->nfproto != l3num) continue; - - if (h->tuple.dst.protonum == protonum) + if (h->l4proto == protonum) return h; } return NULL; @@ -388,13 +385,13 @@ int __nf_conntrack_helper_register(struct nf_conntrack_helper *me) return -EINVAL; } - h = helper_hash(me->name, me->tuple.dst.protonum); + h = helper_hash(me->name, me->l4proto); mutex_lock(&nf_ct_helper_mutex); hlist_for_each_entry(cur, &nf_ct_helper_hash[h], hnode) { if (!strcmp(cur->name, me->name) && - (cur->tuple.src.l3num == NFPROTO_UNSPEC || - cur->tuple.src.l3num == me->tuple.src.l3num) && - cur->tuple.dst.protonum == me->tuple.dst.protonum) { + (cur->nfproto == NFPROTO_UNSPEC || + cur->nfproto == me->nfproto) && + cur->l4proto == me->l4proto) { ret = -EBUSY; goto out; } @@ -474,7 +471,7 @@ void nf_conntrack_helper_unregister(struct nf_conntrack_helper *me) EXPORT_SYMBOL_GPL(nf_conntrack_helper_unregister); void nf_ct_helper_init(struct nf_conntrack_helper *helper, - u16 l3num, u16 protonum, const char *name, + u8 l3num, u16 protonum, const char *name, u16 default_port, u16 spec_port, u32 id, const struct nf_conntrack_expect_policy *exp_pol, u32 expect_class_max, @@ -487,9 +484,8 @@ void nf_ct_helper_init(struct nf_conntrack_helper *helper, { memset(helper, 0, sizeof(*helper)); - helper->tuple.src.l3num = l3num; - helper->tuple.dst.protonum = protonum; - helper->tuple.src.u.all = htons(spec_port); + helper->nfproto = l3num; + helper->l4proto = protonum; rcu_assign_pointer(helper->help, help); helper->from_nlattr = from_nlattr; diff --git a/net/netfilter/nf_conntrack_ovs.c b/net/netfilter/nf_conntrack_ovs.c index 49d1511e9921..b4085af3ad1c 100644 --- a/net/netfilter/nf_conntrack_ovs.c +++ b/net/netfilter/nf_conntrack_ovs.c @@ -31,8 +31,8 @@ int nf_ct_helper(struct sk_buff *skb, struct nf_conn *ct, if (!helper) return NF_ACCEPT; - if (helper->tuple.src.l3num != NFPROTO_UNSPEC && - helper->tuple.src.l3num != proto) + if (helper->nfproto != NFPROTO_UNSPEC && + helper->nfproto != proto) return NF_ACCEPT; switch (proto) { @@ -60,7 +60,7 @@ int nf_ct_helper(struct sk_buff *skb, struct nf_conn *ct, return NF_DROP; } - if (helper->tuple.dst.protonum != proto) + if (helper->l4proto != proto) return NF_ACCEPT; helper_cb = rcu_dereference(helper->help); diff --git a/net/netfilter/nfnetlink_cthelper.c b/net/netfilter/nfnetlink_cthelper.c index f1460b683d7a..56655cb7fe2a 100644 --- a/net/netfilter/nfnetlink_cthelper.c +++ b/net/netfilter/nfnetlink_cthelper.c @@ -67,7 +67,7 @@ nfnl_userspace_cthelper(struct sk_buff *skb, unsigned int protoff, } static const struct nla_policy nfnl_cthelper_tuple_pol[NFCTH_TUPLE_MAX+1] = { - [NFCTH_TUPLE_L3PROTONUM] = { .type = NLA_U16, }, + [NFCTH_TUPLE_L3PROTONUM] = NLA_POLICY_MAX(NLA_BE16, NFPROTO_IPV6), [NFCTH_TUPLE_L4PROTONUM] = { .type = NLA_U8, }, }; @@ -254,7 +254,8 @@ nfnl_cthelper_create(const struct nlattr * const tb[], helper->data_len = size; helper->flags |= NF_CT_HELPER_F_USERSPACE; - memcpy(&helper->tuple, tuple, sizeof(struct nf_conntrack_tuple)); + helper->nfproto = tuple->src.l3num; + helper->l4proto = tuple->dst.protonum; helper->me = THIS_MODULE; helper->help = nfnl_userspace_cthelper; @@ -449,8 +450,8 @@ static int nfnl_cthelper_new(struct sk_buff *skb, const struct nfnl_info *info, if (strncmp(cur->name, helper_name, NF_CT_HELPER_NAME_LEN)) continue; - if ((tuple.src.l3num != cur->tuple.src.l3num || - tuple.dst.protonum != cur->tuple.dst.protonum)) + if ((tuple.src.l3num != cur->nfproto || + tuple.dst.protonum != cur->l4proto)) continue; if (info->nlh->nlmsg_flags & NLM_F_EXCL) @@ -479,10 +480,10 @@ nfnl_cthelper_dump_tuple(struct sk_buff *skb, goto nla_put_failure; if (nla_put_be16(skb, NFCTH_TUPLE_L3PROTONUM, - htons(helper->tuple.src.l3num))) + htons(helper->nfproto))) goto nla_put_failure; - if (nla_put_u8(skb, NFCTH_TUPLE_L4PROTONUM, helper->tuple.dst.protonum)) + if (nla_put_u8(skb, NFCTH_TUPLE_L4PROTONUM, helper->l4proto)) goto nla_put_failure; nla_nest_end(skb, nest_parms); @@ -661,8 +662,8 @@ static int nfnl_cthelper_get(struct sk_buff *skb, const struct nfnl_info *info, continue; if (tuple_set && - (tuple.src.l3num != cur->tuple.src.l3num || - tuple.dst.protonum != cur->tuple.dst.protonum)) + (tuple.src.l3num != cur->nfproto || + tuple.dst.protonum != cur->l4proto)) continue; skb2 = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); @@ -721,8 +722,8 @@ static int nfnl_cthelper_del(struct sk_buff *skb, const struct nfnl_info *info, continue; if (tuple_set && - (tuple.src.l3num != cur->tuple.src.l3num || - tuple.dst.protonum != cur->tuple.dst.protonum)) + (tuple.src.l3num != cur->nfproto || + tuple.dst.protonum != cur->l4proto)) continue; found = true; diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c index be535a261fa0..4ca7964e83c8 100644 --- a/net/sched/act_ct.c +++ b/net/sched/act_ct.c @@ -1527,8 +1527,8 @@ static int tcf_ct_dump_helper(struct sk_buff *skb, return 0; if (nla_put_string(skb, TCA_CT_HELPER_NAME, helper->name) || - nla_put_u8(skb, TCA_CT_HELPER_FAMILY, helper->tuple.src.l3num) || - nla_put_u8(skb, TCA_CT_HELPER_PROTO, helper->tuple.dst.protonum)) + nla_put_u8(skb, TCA_CT_HELPER_FAMILY, helper->nfproto) || + nla_put_u8(skb, TCA_CT_HELPER_PROTO, helper->l4proto)) return -1; return 0; From 78217fb2ccf9d3963dd32d86712ba42e7fd619a8 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Mon, 29 Jun 2026 14:58:23 +0200 Subject: [PATCH 0112/1433] netfilter: conntrack: remove obsolete module parameters helper autoassign was removed years ago, all the port numbers are no longer functional. Signed-off-by: Florian Westphal --- include/linux/netfilter/nf_conntrack_h323.h | 2 - include/linux/netfilter/nf_conntrack_pptp.h | 2 - include/linux/netfilter/nf_conntrack_sane.h | 2 - include/linux/netfilter/nf_conntrack_tftp.h | 2 - include/net/netfilter/nf_conntrack_helper.h | 1 - net/ipv4/netfilter/nf_nat_snmp_basic_main.c | 2 +- net/netfilter/nf_conntrack_amanda.c | 4 +- net/netfilter/nf_conntrack_ftp.c | 32 +++++---------- net/netfilter/nf_conntrack_h323_main.c | 10 ++--- net/netfilter/nf_conntrack_helper.c | 6 +-- net/netfilter/nf_conntrack_irc.c | 27 ++++--------- net/netfilter/nf_conntrack_netbios_ns.c | 2 - net/netfilter/nf_conntrack_pptp.c | 2 +- net/netfilter/nf_conntrack_sane.c | 34 +++++----------- net/netfilter/nf_conntrack_sip.c | 45 ++++++--------------- net/netfilter/nf_conntrack_snmp.c | 4 +- net/netfilter/nf_conntrack_tftp.c | 33 +++++---------- 17 files changed, 59 insertions(+), 151 deletions(-) diff --git a/include/linux/netfilter/nf_conntrack_h323.h b/include/linux/netfilter/nf_conntrack_h323.h index 81286c499325..b15f37604cde 100644 --- a/include/linux/netfilter/nf_conntrack_h323.h +++ b/include/linux/netfilter/nf_conntrack_h323.h @@ -9,8 +9,6 @@ #include #include -#define RAS_PORT 1719 -#define Q931_PORT 1720 #define H323_RTP_CHANNEL_MAX 4 /* Audio, video, FAX and other */ /* This structure exists only once per master */ diff --git a/include/linux/netfilter/nf_conntrack_pptp.h b/include/linux/netfilter/nf_conntrack_pptp.h index c3bdb4370938..c0b305ce7c3c 100644 --- a/include/linux/netfilter/nf_conntrack_pptp.h +++ b/include/linux/netfilter/nf_conntrack_pptp.h @@ -50,8 +50,6 @@ struct nf_nat_pptp { __be16 pac_call_id; /* NAT'ed PAC call id */ }; -#define PPTP_CONTROL_PORT 1723 - #define PPTP_PACKET_CONTROL 1 #define PPTP_PACKET_MGMT 2 diff --git a/include/linux/netfilter/nf_conntrack_sane.h b/include/linux/netfilter/nf_conntrack_sane.h index 46c7acd1b4a7..8501035d7335 100644 --- a/include/linux/netfilter/nf_conntrack_sane.h +++ b/include/linux/netfilter/nf_conntrack_sane.h @@ -3,8 +3,6 @@ #define _NF_CONNTRACK_SANE_H /* SANE tracking. */ -#define SANE_PORT 6566 - enum sane_state { SANE_STATE_NORMAL, SANE_STATE_START_REQUESTED, diff --git a/include/linux/netfilter/nf_conntrack_tftp.h b/include/linux/netfilter/nf_conntrack_tftp.h index 90b334bbce3c..e3d1739c557d 100644 --- a/include/linux/netfilter/nf_conntrack_tftp.h +++ b/include/linux/netfilter/nf_conntrack_tftp.h @@ -2,8 +2,6 @@ #ifndef _NF_CONNTRACK_TFTP_H #define _NF_CONNTRACK_TFTP_H -#define TFTP_PORT 69 - #include #include #include diff --git a/include/net/netfilter/nf_conntrack_helper.h b/include/net/netfilter/nf_conntrack_helper.h index f3f0c1392e88..bc5427d239f4 100644 --- a/include/net/netfilter/nf_conntrack_helper.h +++ b/include/net/netfilter/nf_conntrack_helper.h @@ -94,7 +94,6 @@ void nf_conntrack_helper_put(struct nf_conntrack_helper *helper); void nf_ct_helper_init(struct nf_conntrack_helper *helper, u8 l3num, u16 protonum, const char *name, - u16 default_port, u16 spec_port, u32 id, const struct nf_conntrack_expect_policy *exp_pol, u32 expect_class_max, int (*help)(struct sk_buff *skb, unsigned int protoff, diff --git a/net/ipv4/netfilter/nf_nat_snmp_basic_main.c b/net/ipv4/netfilter/nf_nat_snmp_basic_main.c index 0ede138dfd29..e540b86bd15b 100644 --- a/net/ipv4/netfilter/nf_nat_snmp_basic_main.c +++ b/net/ipv4/netfilter/nf_nat_snmp_basic_main.c @@ -213,7 +213,7 @@ static int __init nf_nat_snmp_basic_init(void) RCU_INIT_POINTER(nf_nat_snmp_hook, help); nf_ct_helper_init(&snmp_trap_helper, AF_INET, IPPROTO_UDP, - "snmp_trap", SNMP_TRAP_PORT, SNMP_TRAP_PORT, SNMP_TRAP_PORT, + "snmp_trap", &snmp_exp_policy, 0, help, NULL, THIS_MODULE); err = nf_conntrack_helper_register(&snmp_trap_helper, &snmp_trap_helper_ptr); diff --git a/net/netfilter/nf_conntrack_amanda.c b/net/netfilter/nf_conntrack_amanda.c index f10ac2c49f4b..06d6ec12c86d 100644 --- a/net/netfilter/nf_conntrack_amanda.c +++ b/net/netfilter/nf_conntrack_amanda.c @@ -199,10 +199,10 @@ static int __init nf_conntrack_amanda_init(void) } nf_ct_helper_init(&amanda_helper[0], AF_INET, IPPROTO_UDP, - HELPER_NAME, 10080, 10080, 10080, + HELPER_NAME, &amanda_exp_policy, 0, amanda_help, NULL, THIS_MODULE); nf_ct_helper_init(&amanda_helper[1], AF_INET6, IPPROTO_UDP, - HELPER_NAME, 10080, 10080, 10080, + HELPER_NAME, &amanda_exp_policy, 0, amanda_help, NULL, THIS_MODULE); ret = nf_conntrack_helpers_register(amanda_helper, diff --git a/net/netfilter/nf_conntrack_ftp.c b/net/netfilter/nf_conntrack_ftp.c index 0847f845613d..f3944598c172 100644 --- a/net/netfilter/nf_conntrack_ftp.c +++ b/net/netfilter/nf_conntrack_ftp.c @@ -35,11 +35,6 @@ MODULE_ALIAS("ip_conntrack_ftp"); MODULE_ALIAS_NFCT_HELPER(HELPER_NAME); static DEFINE_SPINLOCK(nf_ftp_lock); -#define MAX_PORTS 8 -static u_int16_t ports[MAX_PORTS]; -static unsigned int ports_c; -module_param_array(ports, ushort, &ports_c, 0400); - static bool loose; module_param(loose, bool, 0600); @@ -560,8 +555,8 @@ static int nf_ct_ftp_from_nlattr(struct nlattr *attr, struct nf_conn *ct) return 0; } -static struct nf_conntrack_helper ftp[MAX_PORTS * 2] __read_mostly; -static struct nf_conntrack_helper *ftp_ptr[MAX_PORTS * 2] __read_mostly; +static struct nf_conntrack_helper ftp __read_mostly; +static struct nf_conntrack_helper *ftp_ptr __read_mostly; static const struct nf_conntrack_expect_policy ftp_exp_policy = { .max_expected = 1, @@ -570,32 +565,23 @@ static const struct nf_conntrack_expect_policy ftp_exp_policy = { static void __exit nf_conntrack_ftp_fini(void) { - nf_conntrack_helpers_unregister(ftp_ptr, ports_c * 2); + nf_conntrack_helper_unregister(ftp_ptr); } static int __init nf_conntrack_ftp_init(void) { - int i, ret = 0; + int ret = 0; NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nf_ct_ftp_master)); - if (ports_c == 0) - ports[ports_c++] = FTP_PORT; - /* FIXME should be configurable whether IPv4 and IPv6 FTP connections are tracked or not - YK */ - for (i = 0; i < ports_c; i++) { - nf_ct_helper_init(&ftp[2 * i], AF_INET, IPPROTO_TCP, - HELPER_NAME, FTP_PORT, ports[i], ports[i], - &ftp_exp_policy, 0, help, - nf_ct_ftp_from_nlattr, THIS_MODULE); - nf_ct_helper_init(&ftp[2 * i + 1], AF_INET6, IPPROTO_TCP, - HELPER_NAME, FTP_PORT, ports[i], ports[i], - &ftp_exp_policy, 0, help, - nf_ct_ftp_from_nlattr, THIS_MODULE); - } + nf_ct_helper_init(&ftp, NFPROTO_UNSPEC, IPPROTO_TCP, + HELPER_NAME, + &ftp_exp_policy, 0, help, + nf_ct_ftp_from_nlattr, THIS_MODULE); - ret = nf_conntrack_helpers_register(ftp, ports_c * 2, ftp_ptr); + ret = nf_conntrack_helper_register(&ftp, &ftp_ptr); if (ret < 0) { pr_err("failed to register helpers\n"); return ret; diff --git a/net/netfilter/nf_conntrack_h323_main.c b/net/netfilter/nf_conntrack_h323_main.c index 37b6314ca772..4cb1665bba02 100644 --- a/net/netfilter/nf_conntrack_h323_main.c +++ b/net/netfilter/nf_conntrack_h323_main.c @@ -1713,19 +1713,19 @@ static int __init h323_helper_init(void) int ret; nf_ct_helper_init(&nf_conntrack_helper_ras[0], AF_INET, IPPROTO_UDP, - "RAS", RAS_PORT, RAS_PORT, RAS_PORT, + "RAS", &ras_exp_policy, 0, ras_help, NULL, THIS_MODULE); nf_ct_helper_init(&nf_conntrack_helper_ras[1], AF_INET6, IPPROTO_UDP, - "RAS", RAS_PORT, RAS_PORT, RAS_PORT, + "RAS", &ras_exp_policy, 0, ras_help, NULL, THIS_MODULE); nf_ct_helper_init(&nf_conntrack_helper_h245, AF_UNSPEC, IPPROTO_UDP, - "H.245", 0, 0, 0, + "H.245", &h245_exp_policy, 0, h245_help, NULL, THIS_MODULE); nf_ct_helper_init(&nf_conntrack_helper_q931[0], AF_INET, IPPROTO_TCP, - "Q.931", Q931_PORT, Q931_PORT, Q931_PORT, + "Q.931", &q931_exp_policy, 0, q931_help, NULL, THIS_MODULE); nf_ct_helper_init(&nf_conntrack_helper_q931[1], AF_INET6, IPPROTO_TCP, - "Q.931", Q931_PORT, Q931_PORT, Q931_PORT, + "Q.931", &q931_exp_policy, 0, q931_help, NULL, THIS_MODULE); ret = nf_conntrack_helper_register(&nf_conntrack_helper_h245, diff --git a/net/netfilter/nf_conntrack_helper.c b/net/netfilter/nf_conntrack_helper.c index b28986100db0..506c58034761 100644 --- a/net/netfilter/nf_conntrack_helper.c +++ b/net/netfilter/nf_conntrack_helper.c @@ -472,7 +472,6 @@ EXPORT_SYMBOL_GPL(nf_conntrack_helper_unregister); void nf_ct_helper_init(struct nf_conntrack_helper *helper, u8 l3num, u16 protonum, const char *name, - u16 default_port, u16 spec_port, u32 id, const struct nf_conntrack_expect_policy *exp_pol, u32 expect_class_max, int (*help)(struct sk_buff *skb, unsigned int protoff, @@ -493,10 +492,7 @@ void nf_ct_helper_init(struct nf_conntrack_helper *helper, snprintf(helper->nat_mod_name, sizeof(helper->nat_mod_name), NF_NAT_HELPER_PREFIX "%s", name); - if (spec_port == default_port) - snprintf(helper->name, sizeof(helper->name), "%s", name); - else - snprintf(helper->name, sizeof(helper->name), "%s-%u", name, id); + snprintf(helper->name, sizeof(helper->name), "%s", name); if (WARN_ON_ONCE(expect_class_max >= NF_CT_MAX_EXPECT_CLASSES)) return; diff --git a/net/netfilter/nf_conntrack_irc.c b/net/netfilter/nf_conntrack_irc.c index 193ab34db795..4e6bafe41437 100644 --- a/net/netfilter/nf_conntrack_irc.c +++ b/net/netfilter/nf_conntrack_irc.c @@ -21,9 +21,6 @@ #include #include -#define MAX_PORTS 8 -static unsigned short ports[MAX_PORTS]; -static unsigned int ports_c; static unsigned int max_dcc_channels = 8; static unsigned int dcc_timeout __read_mostly = 300; /* This is slow, but it's simple. --RR */ @@ -42,8 +39,6 @@ MODULE_LICENSE("GPL"); MODULE_ALIAS("ip_conntrack_irc"); MODULE_ALIAS_NFCT_HELPER(HELPER_NAME); -module_param_array(ports, ushort, &ports_c, 0400); -MODULE_PARM_DESC(ports, "port numbers of IRC servers"); module_param(max_dcc_channels, uint, 0400); MODULE_PARM_DESC(max_dcc_channels, "max number of expected DCC channels per " "IRC session"); @@ -254,13 +249,13 @@ static int help(struct sk_buff *skb, unsigned int protoff, return ret; } -static struct nf_conntrack_helper irc[MAX_PORTS] __read_mostly; -static struct nf_conntrack_helper *irc_ptr[MAX_PORTS] __read_mostly; +static struct nf_conntrack_helper irc __read_mostly; +static struct nf_conntrack_helper *irc_ptr __read_mostly; static struct nf_conntrack_expect_policy irc_exp_policy; static int __init nf_conntrack_irc_init(void) { - int i, ret; + int ret; nf_conntrack_helper_deprecated(HELPER_NAME); @@ -282,17 +277,11 @@ static int __init nf_conntrack_irc_init(void) if (!irc_buffer) return -ENOMEM; - /* If no port given, default to standard irc port */ - if (ports_c == 0) - ports[ports_c++] = IRC_PORT; + nf_ct_helper_init(&irc, AF_INET, IPPROTO_TCP, HELPER_NAME, + &irc_exp_policy, + 0, help, NULL, THIS_MODULE); - for (i = 0; i < ports_c; i++) { - nf_ct_helper_init(&irc[i], AF_INET, IPPROTO_TCP, HELPER_NAME, - IRC_PORT, ports[i], i, &irc_exp_policy, - 0, help, NULL, THIS_MODULE); - } - - ret = nf_conntrack_helpers_register(&irc[0], ports_c, irc_ptr); + ret = nf_conntrack_helper_register(&irc, &irc_ptr); if (ret) { pr_err("failed to register helpers\n"); kfree(irc_buffer); @@ -304,7 +293,7 @@ static int __init nf_conntrack_irc_init(void) static void __exit nf_conntrack_irc_fini(void) { - nf_conntrack_helpers_unregister(irc_ptr, ports_c); + nf_conntrack_helper_unregister(irc_ptr); kfree(irc_buffer); } diff --git a/net/netfilter/nf_conntrack_netbios_ns.c b/net/netfilter/nf_conntrack_netbios_ns.c index 89d1cf7d6512..caa2b101fa9e 100644 --- a/net/netfilter/nf_conntrack_netbios_ns.c +++ b/net/netfilter/nf_conntrack_netbios_ns.c @@ -21,7 +21,6 @@ #include #define HELPER_NAME "netbios-ns" -#define NMBD_PORT 137 MODULE_AUTHOR("Patrick McHardy "); MODULE_DESCRIPTION("NetBIOS name service broadcast connection tracking helper"); @@ -54,7 +53,6 @@ static int __init nf_conntrack_netbios_ns_init(void) exp_policy.timeout = timeout; nf_ct_helper_init(&helper, AF_INET, IPPROTO_UDP, HELPER_NAME, - NMBD_PORT, NMBD_PORT, NMBD_PORT, &exp_policy, 0, netbios_ns_help, NULL, THIS_MODULE); return nf_conntrack_helper_register(&helper, &helper_ptr); diff --git a/net/netfilter/nf_conntrack_pptp.c b/net/netfilter/nf_conntrack_pptp.c index 80fc14c87ddc..cbf32a3cb1f6 100644 --- a/net/netfilter/nf_conntrack_pptp.c +++ b/net/netfilter/nf_conntrack_pptp.c @@ -540,7 +540,7 @@ static int __init nf_conntrack_pptp_init(void) NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nf_ct_pptp_master)); nf_ct_helper_init(&pptp, AF_INET, IPPROTO_TCP, - "pptp", PPTP_CONTROL_PORT, PPTP_CONTROL_PORT, PPTP_CONTROL_PORT, + "pptp", &pptp_exp_policy, 0, conntrack_pptp_help, NULL, THIS_MODULE); pptp.destroy = gre_pptp_destroy_siblings; diff --git a/net/netfilter/nf_conntrack_sane.c b/net/netfilter/nf_conntrack_sane.c index 39085acf7a71..a0658f69d78f 100644 --- a/net/netfilter/nf_conntrack_sane.c +++ b/net/netfilter/nf_conntrack_sane.c @@ -34,11 +34,6 @@ MODULE_AUTHOR("Michal Schmidt "); MODULE_DESCRIPTION("SANE connection tracking helper"); MODULE_ALIAS_NFCT_HELPER(HELPER_NAME); -#define MAX_PORTS 8 -static u_int16_t ports[MAX_PORTS]; -static unsigned int ports_c; -module_param_array(ports, ushort, &ports_c, 0400); - struct sane_request { __be32 RPC_code; #define SANE_NET_START 7 /* RPC code */ @@ -169,8 +164,8 @@ static int help(struct sk_buff *skb, return ret; } -static struct nf_conntrack_helper sane[MAX_PORTS * 2] __read_mostly; -static struct nf_conntrack_helper *sane_ptr[MAX_PORTS * 2] __read_mostly; +static struct nf_conntrack_helper sane __read_mostly; +static struct nf_conntrack_helper *sane_ptr __read_mostly; static const struct nf_conntrack_expect_policy sane_exp_policy = { .max_expected = 1, @@ -179,32 +174,21 @@ static const struct nf_conntrack_expect_policy sane_exp_policy = { static void __exit nf_conntrack_sane_fini(void) { - nf_conntrack_helpers_unregister(sane_ptr, ports_c * 2); + nf_conntrack_helper_unregister(sane_ptr); } static int __init nf_conntrack_sane_init(void) { - int i, ret = 0; + int ret = 0; NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nf_ct_sane_master)); - if (ports_c == 0) - ports[ports_c++] = SANE_PORT; + nf_ct_helper_init(&sane, NFPROTO_UNSPEC, IPPROTO_TCP, + HELPER_NAME, + &sane_exp_policy, 0, help, NULL, + THIS_MODULE); - /* FIXME should be configurable whether IPv4 and IPv6 connections - are tracked or not - YK */ - for (i = 0; i < ports_c; i++) { - nf_ct_helper_init(&sane[2 * i], AF_INET, IPPROTO_TCP, - HELPER_NAME, SANE_PORT, ports[i], ports[i], - &sane_exp_policy, 0, help, NULL, - THIS_MODULE); - nf_ct_helper_init(&sane[2 * i + 1], AF_INET6, IPPROTO_TCP, - HELPER_NAME, SANE_PORT, ports[i], ports[i], - &sane_exp_policy, 0, help, NULL, - THIS_MODULE); - } - - ret = nf_conntrack_helpers_register(sane, ports_c * 2, sane_ptr); + ret = nf_conntrack_helper_register(&sane, &sane_ptr); if (ret < 0) { pr_err("failed to register helpers\n"); return ret; diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c index 5ec3a4a4bbd7..d0b85b8ad1e6 100644 --- a/net/netfilter/nf_conntrack_sip.c +++ b/net/netfilter/nf_conntrack_sip.c @@ -35,12 +35,6 @@ MODULE_DESCRIPTION("SIP connection tracking helper"); MODULE_ALIAS("ip_conntrack_sip"); MODULE_ALIAS_NFCT_HELPER(HELPER_NAME); -#define MAX_PORTS 8 -static unsigned short ports[MAX_PORTS]; -static unsigned int ports_c; -module_param_array(ports, ushort, &ports_c, 0400); -MODULE_PARM_DESC(ports, "port numbers of SIP servers"); - static unsigned int sip_timeout __read_mostly = SIP_TIMEOUT; module_param(sip_timeout, uint, 0600); MODULE_PARM_DESC(sip_timeout, "timeout for the master SIP session"); @@ -1764,8 +1758,8 @@ static int sip_help_udp(struct sk_buff *skb, unsigned int protoff, return process_sip_msg(skb, ct, protoff, dataoff, &dptr, &datalen); } -static struct nf_conntrack_helper sip[MAX_PORTS * 4] __read_mostly; -static struct nf_conntrack_helper *sip_ptr[MAX_PORTS * 4] __read_mostly; +static struct nf_conntrack_helper sip[2] __read_mostly; +static struct nf_conntrack_helper *sip_ptr[2] __read_mostly; static const struct nf_conntrack_expect_policy sip_exp_policy[SIP_EXPECT_MAX + 1] = { [SIP_EXPECT_SIGNALLING] = { @@ -1792,38 +1786,25 @@ static const struct nf_conntrack_expect_policy sip_exp_policy[SIP_EXPECT_MAX + 1 static void __exit nf_conntrack_sip_fini(void) { - nf_conntrack_helpers_unregister(sip_ptr, ports_c * 4); + nf_conntrack_helpers_unregister(sip_ptr, 2); } static int __init nf_conntrack_sip_init(void) { - int i, ret; + int ret; NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nf_ct_sip_master)); - if (ports_c == 0) - ports[ports_c++] = SIP_PORT; + nf_ct_helper_init(&sip[0], NFPROTO_UNSPEC, IPPROTO_UDP, + HELPER_NAME, + sip_exp_policy, SIP_EXPECT_MAX, sip_help_udp, + NULL, THIS_MODULE); + nf_ct_helper_init(&sip[1], NFPROTO_UNSPEC, IPPROTO_TCP, + HELPER_NAME, + sip_exp_policy, SIP_EXPECT_MAX, sip_help_tcp, + NULL, THIS_MODULE); - for (i = 0; i < ports_c; i++) { - nf_ct_helper_init(&sip[4 * i], AF_INET, IPPROTO_UDP, - HELPER_NAME, SIP_PORT, ports[i], i, - sip_exp_policy, SIP_EXPECT_MAX, sip_help_udp, - NULL, THIS_MODULE); - nf_ct_helper_init(&sip[4 * i + 1], AF_INET, IPPROTO_TCP, - HELPER_NAME, SIP_PORT, ports[i], i, - sip_exp_policy, SIP_EXPECT_MAX, sip_help_tcp, - NULL, THIS_MODULE); - nf_ct_helper_init(&sip[4 * i + 2], AF_INET6, IPPROTO_UDP, - HELPER_NAME, SIP_PORT, ports[i], i, - sip_exp_policy, SIP_EXPECT_MAX, sip_help_udp, - NULL, THIS_MODULE); - nf_ct_helper_init(&sip[4 * i + 3], AF_INET6, IPPROTO_TCP, - HELPER_NAME, SIP_PORT, ports[i], i, - sip_exp_policy, SIP_EXPECT_MAX, sip_help_tcp, - NULL, THIS_MODULE); - } - - ret = nf_conntrack_helpers_register(sip, ports_c * 4, sip_ptr); + ret = nf_conntrack_helpers_register(sip, 2, sip_ptr); if (ret < 0) { pr_err("failed to register helpers\n"); return ret; diff --git a/net/netfilter/nf_conntrack_snmp.c b/net/netfilter/nf_conntrack_snmp.c index b6fce5703fce..109986d5d55e 100644 --- a/net/netfilter/nf_conntrack_snmp.c +++ b/net/netfilter/nf_conntrack_snmp.c @@ -14,8 +14,6 @@ #include #include -#define SNMP_PORT 161 - MODULE_AUTHOR("Jiri Olsa "); MODULE_DESCRIPTION("SNMP service broadcast connection tracking helper"); MODULE_LICENSE("GPL"); @@ -55,7 +53,7 @@ static int __init nf_conntrack_snmp_init(void) exp_policy.timeout = timeout; nf_ct_helper_init(&helper, AF_INET, IPPROTO_UDP, - "snmp", SNMP_PORT, SNMP_PORT, SNMP_PORT, + "snmp", &exp_policy, 0, snmp_conntrack_help, NULL, THIS_MODULE); diff --git a/net/netfilter/nf_conntrack_tftp.c b/net/netfilter/nf_conntrack_tftp.c index 4393c435aa35..a69559edf9b3 100644 --- a/net/netfilter/nf_conntrack_tftp.c +++ b/net/netfilter/nf_conntrack_tftp.c @@ -26,12 +26,6 @@ MODULE_LICENSE("GPL"); MODULE_ALIAS("ip_conntrack_tftp"); MODULE_ALIAS_NFCT_HELPER(HELPER_NAME); -#define MAX_PORTS 8 -static unsigned short ports[MAX_PORTS]; -static unsigned int ports_c; -module_param_array(ports, ushort, &ports_c, 0400); -MODULE_PARM_DESC(ports, "Port numbers of TFTP servers"); - nf_nat_tftp_hook_fn __rcu *nf_nat_tftp_hook __read_mostly; EXPORT_SYMBOL_GPL(nf_nat_tftp_hook); @@ -95,8 +89,8 @@ static int tftp_help(struct sk_buff *skb, return ret; } -static struct nf_conntrack_helper tftp[MAX_PORTS * 2] __read_mostly; -static struct nf_conntrack_helper *tftp_ptr[MAX_PORTS * 2] __read_mostly; +static struct nf_conntrack_helper tftp __read_mostly; +static struct nf_conntrack_helper *tftp_ptr __read_mostly; static const struct nf_conntrack_expect_policy tftp_exp_policy = { .max_expected = 1, @@ -105,30 +99,21 @@ static const struct nf_conntrack_expect_policy tftp_exp_policy = { static void __exit nf_conntrack_tftp_fini(void) { - nf_conntrack_helpers_unregister(tftp_ptr, ports_c * 2); + nf_conntrack_helper_unregister(tftp_ptr); } static int __init nf_conntrack_tftp_init(void) { - int i, ret; + int ret; NF_CT_HELPER_BUILD_BUG_ON(0); - if (ports_c == 0) - ports[ports_c++] = TFTP_PORT; + nf_ct_helper_init(&tftp, NFPROTO_UNSPEC, IPPROTO_UDP, + HELPER_NAME, + &tftp_exp_policy, 0, tftp_help, NULL, + THIS_MODULE); - for (i = 0; i < ports_c; i++) { - nf_ct_helper_init(&tftp[2 * i], AF_INET, IPPROTO_UDP, - HELPER_NAME, TFTP_PORT, ports[i], i, - &tftp_exp_policy, 0, tftp_help, NULL, - THIS_MODULE); - nf_ct_helper_init(&tftp[2 * i + 1], AF_INET6, IPPROTO_UDP, - HELPER_NAME, TFTP_PORT, ports[i], i, - &tftp_exp_policy, 0, tftp_help, NULL, - THIS_MODULE); - } - - ret = nf_conntrack_helpers_register(tftp, ports_c * 2, tftp_ptr); + ret = nf_conntrack_helper_register(&tftp, &tftp_ptr); if (ret < 0) { pr_err("failed to register helpers\n"); return ret; From 43ae85af154b27f9a0ff602f7a01e3a7583ffdab Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Wed, 10 Jun 2026 11:02:59 +0800 Subject: [PATCH 0113/1433] netfilter: ebtables: bound num_counters like nentries in do_replace() do_replace_finish() allocates the counter buffer before it is validated: counterstmp = vmalloc_array(repl->num_counters, sizeof(*counterstmp)); do_replace() only checks num_counters against INT_MAX / sizeof(struct ebt_counter), so vmalloc_array() can be asked for up to 134217726 * 16 = 2147483616 bytes (~2 GiB). num_counters must in fact equal nentries: do_replace_finish() later rejects the request when repl->num_counters != t->private->nentries. get_counters() folds the per-CPU counters back into one entry per rule, so what userspace gets is bounded by nentries, never by nentries * nr_cpus. Apply the same upper bound used for nentries (MAX_EBT_ENTRIES) to the incoming num_counters so the over-sized allocation can no longer be requested. The allocation is still kept outside the ebt_mutex, since vmalloc() may sleep and trigger reclaim; only the bound is tightened. Signed-off-by: Jiayuan Chen Signed-off-by: Florian Westphal --- net/bridge/netfilter/ebtables.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/net/bridge/netfilter/ebtables.c b/net/bridge/netfilter/ebtables.c index f20c039e44c8..042d31278713 100644 --- a/net/bridge/netfilter/ebtables.c +++ b/net/bridge/netfilter/ebtables.c @@ -39,6 +39,8 @@ #define COUNTER_OFFSET(n) (SMP_ALIGN(n * sizeof(struct ebt_counter))) #define COUNTER_BASE(c, n, cpu) ((struct ebt_counter *)(((char *)c) + \ COUNTER_OFFSET(n) * cpu)) +#define MAX_EBT_ENTRIES (((INT_MAX - sizeof(struct ebt_table_info)) / \ + NR_CPUS - SMP_CACHE_BYTES) / sizeof(struct ebt_counter)) struct ebt_pernet { struct list_head tables; @@ -1124,10 +1126,9 @@ static int do_replace(struct net *net, sockptr_t arg, unsigned int len) return -EINVAL; /* overflow check */ - if (tmp.nentries >= ((INT_MAX - sizeof(struct ebt_table_info)) / - NR_CPUS - SMP_CACHE_BYTES) / sizeof(struct ebt_counter)) + if (tmp.nentries >= MAX_EBT_ENTRIES) return -ENOMEM; - if (tmp.num_counters >= INT_MAX / sizeof(struct ebt_counter)) + if (tmp.num_counters >= MAX_EBT_ENTRIES) return -ENOMEM; tmp.name[sizeof(tmp.name) - 1] = 0; @@ -2265,10 +2266,9 @@ static int compat_copy_ebt_replace_from_user(struct ebt_replace *repl, if (tmp.entries_size == 0) return -EINVAL; - if (tmp.nentries >= ((INT_MAX - sizeof(struct ebt_table_info)) / - NR_CPUS - SMP_CACHE_BYTES) / sizeof(struct ebt_counter)) + if (tmp.nentries >= MAX_EBT_ENTRIES) return -ENOMEM; - if (tmp.num_counters >= INT_MAX / sizeof(struct ebt_counter)) + if (tmp.num_counters >= MAX_EBT_ENTRIES) return -ENOMEM; memcpy(repl, &tmp, offsetof(struct ebt_replace, hook_entry)); From d4beefc90a66672e43fdf82b43e4b3c0b1b18c5e Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Mon, 22 Jun 2026 14:50:18 +0200 Subject: [PATCH 0114/1433] netfilter: nft_ct: support expectation creation for natted flows This feature only works for connections originating from the host and only if there no source address rewrite. Add the needed nat glue to have the expectation follow the original nat binding. Signed-off-by: Florian Westphal --- net/netfilter/nft_ct.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/net/netfilter/nft_ct.c b/net/netfilter/nft_ct.c index 03a88c77e0f0..358b9287e12e 100644 --- a/net/netfilter/nft_ct.c +++ b/net/netfilter/nft_ct.c @@ -1297,6 +1297,17 @@ static int nft_ct_expect_obj_dump(struct sk_buff *skb, return 0; } +#if IS_ENABLED(CONFIG_NF_NAT) +static void nft_ct_nat_follow_master(struct nf_conn *ct, struct nf_conntrack_expect *this) +{ + const struct nf_ct_helper_expectfn *expfn; + + expfn = nf_ct_helper_expectfn_find_by_name("nat-follow-master"); + if (expfn) + expfn->expectfn(ct, this); +} +#endif + static void nft_ct_expect_obj_eval(struct nft_object *obj, struct nft_regs *regs, const struct nft_pktinfo *pkt) @@ -1342,6 +1353,13 @@ static void nft_ct_expect_obj_eval(struct nft_object *obj, priv->l4proto, NULL, &priv->dport); exp->timeout += priv->timeout; +#if IS_ENABLED(CONFIG_NF_NAT) + if (ct->status & IPS_NAT_MASK) { + exp->saved_proto.tcp.port = priv->dport; + exp->dir = !dir; + exp->expectfn = nft_ct_nat_follow_master; + } +#endif if (nf_ct_expect_related(exp, 0) != 0) regs->verdict.code = NF_DROP; @@ -1375,6 +1393,13 @@ static struct nft_object_type nft_ct_expect_obj_type __read_mostly = { .owner = THIS_MODULE, }; +#if IS_ENABLED(CONFIG_NF_NAT) +static struct nf_ct_helper_expectfn nft_ct_nat __read_mostly = { + .name = "nft_ct-follow-master", + .expectfn = nft_ct_nat_follow_master, +}; +#endif + static int __init nft_ct_module_init(void) { int err; @@ -1400,6 +1425,9 @@ static int __init nft_ct_module_init(void) err = nft_register_obj(&nft_ct_timeout_obj_type); if (err < 0) goto err4; +#endif +#if IS_ENABLED(CONFIG_NF_NAT) + nf_ct_helper_expectfn_register(&nft_ct_nat); #endif return 0; @@ -1425,6 +1453,13 @@ static void __exit nft_ct_module_exit(void) nft_unregister_obj(&nft_ct_helper_obj_type); nft_unregister_expr(&nft_notrack_type); nft_unregister_expr(&nft_ct_type); + +#if IS_ENABLED(CONFIG_NF_NAT) + nf_ct_helper_expectfn_unregister(&nft_ct_nat); + synchronize_rcu(); + nf_ct_helper_expectfn_destroy(&nft_ct_nat); + synchronize_rcu(); +#endif } module_init(nft_ct_module_init); From b5997f911eec53790d56ca438c8ad61e872d795b Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 30 Jun 2026 07:01:26 -0700 Subject: [PATCH 0115/1433] net: add sockopt_init_user() for getsockopt conversion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a helper that initializes a user-backed sockopt_t from the (optval, optlen) __user pair passed to a getsockopt() callback. It is used by transitional __user getsockopt wrappers while the proto-layer getsockopt callbacks are converted to take a sockopt_t, and is removed once the conversion is complete. The goal is to help to convert leafs. Example: sock_common_getsockopt(... char __user *optval, int __user *optlen) → udp_getsockopt(sk, level, optname, optval__user, optlen__user) → udp_lib_getsockopt(sk, level, optname, &opt) /* needs a sockopt_t */ Signed-off-by: Breno Leitao Acked-by: Stanislav Fomichev Acked-by: Willem de Bruijn Link: https://patch.msgid.link/20260630-getsockopt_phase2-v2-1-193335f3d4d1@debian.org Signed-off-by: Paolo Abeni --- include/linux/net.h | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/include/linux/net.h b/include/linux/net.h index f268f395ce47..277188a40c72 100644 --- a/include/linux/net.h +++ b/include/linux/net.h @@ -47,6 +47,29 @@ typedef struct sockopt { int optlen; } sockopt_t; +/* + * Initialize a user-backed sockopt_t from the (optval, optlen) __user pair of + * a getsockopt() callback. Used by transitional __user getsockopt wrappers + * while the proto-layer callbacks are converted to take a sockopt_t; the + * caller writes opt->optlen back to the user optlen after the callback. + */ +static inline int sockopt_init_user(sockopt_t *opt, char __user *optval, + int __user *optlen) +{ + int len; + + if (get_user(len, optlen)) + return -EFAULT; + if (len < 0) + return -EINVAL; + + iov_iter_ubuf(&opt->iter_out, ITER_DEST, optval, len); + iov_iter_ubuf(&opt->iter_in, ITER_SOURCE, optval, len); + opt->optlen = len; + + return 0; +} + struct poll_table_struct; struct pipe_inode_info; struct inode; From f99d7065a4d45529ccfdc630c1f278da805cf59f Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 30 Jun 2026 07:01:27 -0700 Subject: [PATCH 0116/1433] udp: convert udp_lib_getsockopt to sockopt_t In preparation for converting the proto-layer getsockopt callbacks to the sockopt_t interface, switch udp_lib_getsockopt() to take a sockopt_t. The thin udp_getsockopt()/udpv6_getsockopt() wrappers keep their __user signature for now: they build a user-backed sockopt_t with sockopt_init_user(), call the helper, and write the returned length back to optlen. The helper uses copy_to_iter() instead of copy_to_user(). No functional change. Signed-off-by: Breno Leitao Acked-by: Stanislav Fomichev Acked-by: Willem de Bruijn Link: https://patch.msgid.link/20260630-getsockopt_phase2-v2-2-193335f3d4d1@debian.org Signed-off-by: Paolo Abeni --- include/net/udp.h | 2 +- net/ipv4/udp.c | 39 +++++++++++++++++++++++++++++---------- net/ipv6/udp.c | 19 ++++++++++++++++--- 3 files changed, 46 insertions(+), 14 deletions(-) diff --git a/include/net/udp.h b/include/net/udp.h index 8262e2b215b4..1fee17274745 100644 --- a/include/net/udp.h +++ b/include/net/udp.h @@ -430,7 +430,7 @@ struct sk_buff *skb_udp_tunnel_segment(struct sk_buff *skb, netdev_features_t features, bool is_ipv6); int udp_lib_getsockopt(struct sock *sk, int level, int optname, - char __user *optval, int __user *optlen); + sockopt_t *opt); int udp_lib_setsockopt(struct sock *sk, int level, int optname, sockptr_t optval, unsigned int optlen, int (*push_pending_frames)(struct sock *)); diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index 70f6cbd4ef73..59248a59358c 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -76,6 +76,7 @@ #include #include +#include #include #include #include @@ -2995,14 +2996,13 @@ static int udp_setsockopt(struct sock *sk, int level, int optname, sockptr_t opt } int udp_lib_getsockopt(struct sock *sk, int level, int optname, - char __user *optval, int __user *optlen) + sockopt_t *opt) { struct udp_sock *up = udp_sk(sk); int val, len; - if (get_user(len, optlen)) - return -EFAULT; - + len = opt->optlen; + /* keep the check so direct sockopt_t callers stay covered. */ if (len < 0) return -EINVAL; @@ -3037,9 +3037,8 @@ int udp_lib_getsockopt(struct sock *sk, int level, int optname, return -ENOPROTOOPT; } - if (put_user(len, optlen)) - return -EFAULT; - if (copy_to_user(optval, &val, len)) + opt->optlen = len; + if (copy_to_iter(&val, len, &opt->iter_out) != len) return -EFAULT; return 0; } @@ -3047,9 +3046,29 @@ int udp_lib_getsockopt(struct sock *sk, int level, int optname, static int udp_getsockopt(struct sock *sk, int level, int optname, char __user *optval, int __user *optlen) { - if (level == SOL_UDP) - return udp_lib_getsockopt(sk, level, optname, optval, optlen); - return ip_getsockopt(sk, level, optname, optval, optlen); + sockopt_t opt; + int err; + + /* + * keep the old __user pointers, until ip_getsockopt() moves + * to sockopt_t + */ + if (level != SOL_UDP) + return ip_getsockopt(sk, level, optname, optval, optlen); + + err = sockopt_init_user(&opt, optval, optlen); + if (err) + return err; + + err = udp_lib_getsockopt(sk, level, optname, &opt); + if (err) + return err; + + /* optval was written by copy_to_iter() in udp_lib_getsockopt() */ + if (put_user(opt.optlen, optlen)) + return -EFAULT; + + return 0; } /** diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c index 15e032194ecc..392e18b97045 100644 --- a/net/ipv6/udp.c +++ b/net/ipv6/udp.c @@ -1826,9 +1826,22 @@ static int udpv6_setsockopt(struct sock *sk, int level, int optname, static int udpv6_getsockopt(struct sock *sk, int level, int optname, char __user *optval, int __user *optlen) { - if (level == SOL_UDP) - return udp_lib_getsockopt(sk, level, optname, optval, optlen); - return ipv6_getsockopt(sk, level, optname, optval, optlen); + sockopt_t opt; + int err; + + if (level != SOL_UDP) + return ipv6_getsockopt(sk, level, optname, optval, optlen); + + err = sockopt_init_user(&opt, optval, optlen); + if (err) + return err; + + err = udp_lib_getsockopt(sk, level, optname, &opt); + if (err) + return err; + if (put_user(opt.optlen, optlen)) + return -EFAULT; + return 0; } From 9588d5da17ce1d52ab8356ea2d7231988d1e11fa Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 30 Jun 2026 07:01:28 -0700 Subject: [PATCH 0117/1433] ipv4: raw: convert do_raw_getsockopt to sockopt_t Continue converting the proto-layer getsockopt callbacks to the sockopt_t interface, switching do_raw_getsockopt() and its raw_geticmpfilter() helper to take a sockopt_t. The thin raw_getsockopt() wrapper keeps its __user signature for now: it builds a user-backed sockopt_t with sockopt_init_user(), calls the helper, and writes the returned length back to optlen. The helper uses copy_to_iter() instead of copy_to_user(). No functional change. Signed-off-by: Breno Leitao Acked-by: Stanislav Fomichev Acked-by: Willem de Bruijn Link: https://patch.msgid.link/20260630-getsockopt_phase2-v2-3-193335f3d4d1@debian.org Signed-off-by: Paolo Abeni --- net/ipv4/raw.c | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/net/ipv4/raw.c b/net/ipv4/raw.c index e9fbab6ad914..2aebaf8297e0 100644 --- a/net/ipv4/raw.c +++ b/net/ipv4/raw.c @@ -809,23 +809,18 @@ static int raw_seticmpfilter(struct sock *sk, sockptr_t optval, int optlen) return 0; } -static int raw_geticmpfilter(struct sock *sk, char __user *optval, int __user *optlen) +static int raw_geticmpfilter(struct sock *sk, sockopt_t *opt) { - int len, ret = -EFAULT; + int len = opt->optlen; - if (get_user(len, optlen)) - goto out; - ret = -EINVAL; if (len < 0) - goto out; + return -EINVAL; if (len > sizeof(struct icmp_filter)) len = sizeof(struct icmp_filter); - ret = -EFAULT; - if (put_user(len, optlen) || - copy_to_user(optval, &raw_sk(sk)->filter, len)) - goto out; - ret = 0; -out: return ret; + opt->optlen = len; + if (copy_to_iter(&raw_sk(sk)->filter, len, &opt->iter_out) != len) + return -EFAULT; + return 0; } static int do_raw_setsockopt(struct sock *sk, int optname, @@ -848,14 +843,13 @@ static int raw_setsockopt(struct sock *sk, int level, int optname, return do_raw_setsockopt(sk, optname, optval, optlen); } -static int do_raw_getsockopt(struct sock *sk, int optname, - char __user *optval, int __user *optlen) +static int do_raw_getsockopt(struct sock *sk, int optname, sockopt_t *opt) { if (optname == ICMP_FILTER) { if (inet_sk(sk)->inet_num != IPPROTO_ICMP) return -EOPNOTSUPP; else - return raw_geticmpfilter(sk, optval, optlen); + return raw_geticmpfilter(sk, opt); } return -ENOPROTOOPT; } @@ -863,9 +857,24 @@ static int do_raw_getsockopt(struct sock *sk, int optname, static int raw_getsockopt(struct sock *sk, int level, int optname, char __user *optval, int __user *optlen) { + sockopt_t opt; + int err; + if (level != SOL_RAW) return ip_getsockopt(sk, level, optname, optval, optlen); - return do_raw_getsockopt(sk, optname, optval, optlen); + + err = sockopt_init_user(&opt, optval, optlen); + if (err) + return err; + + err = do_raw_getsockopt(sk, optname, &opt); + if (err) + return err; + + if (put_user(opt.optlen, optlen)) + return -EFAULT; + + return 0; } static int raw_ioctl(struct sock *sk, int cmd, int *karg) From f4d5e3a5c7bc3d789b5138ef127ab27e0128e2da Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 30 Jun 2026 07:01:29 -0700 Subject: [PATCH 0118/1433] selftests: net: getsockopt_iter: add raw ICMP_FILTER coverage Exercise the raw getsockopt path now backed by sockopt_t. ICMP_FILTER returns a fixed-size struct and, unlike the int/u64 options already covered, clamps the length down to the user buffer on a short read instead of failing, so check that semantic explicitly along with the exact and oversized cases, the -EOPNOTSUPP path on a non-ICMP raw socket, and an unknown optname. Signed-off-by: Breno Leitao Acked-by: Stanislav Fomichev Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260630-getsockopt_phase2-v2-4-193335f3d4d1@debian.org Signed-off-by: Paolo Abeni --- tools/testing/selftests/net/getsockopt_iter.c | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/tools/testing/selftests/net/getsockopt_iter.c b/tools/testing/selftests/net/getsockopt_iter.c index 209569354d0e..fe5a5268bc34 100644 --- a/tools/testing/selftests/net/getsockopt_iter.c +++ b/tools/testing/selftests/net/getsockopt_iter.c @@ -11,6 +11,8 @@ * that always reports the required buffer length back via optlen, * even when the user buffer is too small to receive any group bits. * - vsock: SO_VM_SOCKETS_BUFFER_SIZE covers the u64 path. + * - raw: ICMP_FILTER covers a fixed-size struct payload that clamps + * the length down on a short buffer instead of failing. * * Author: Breno Leitao */ @@ -24,12 +26,20 @@ #include #include #include +#include +#include #include #include "kselftest_harness.h" #ifndef AF_VSOCK #define AF_VSOCK 40 #endif +#ifndef SOL_RAW +#define SOL_RAW 255 +#endif +#ifndef ICMP_FILTER +#define ICMP_FILTER 1 +#endif /* ---------- netlink ---------- */ @@ -297,4 +307,91 @@ TEST_F(vsock, connect_timeout_old_exact) ASSERT_EQ(sizeof(tv), optlen); } +/* ---------- raw (ipv4) ---------- */ + +FIXTURE(raw) +{ + int fd; +}; + +FIXTURE_SETUP(raw) +{ + struct icmp_filter filt = { .data = 0xdeadbeef }; + + self->fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + if (self->fd < 0) + SKIP(return, "SOCK_RAW/ICMP socket: %s", strerror(errno)); + + if (setsockopt(self->fd, SOL_RAW, ICMP_FILTER, &filt, sizeof(filt)) < 0) + SKIP(return, "set ICMP_FILTER: %s", strerror(errno)); +} + +FIXTURE_TEARDOWN(raw) +{ + if (self->fd >= 0) + close(self->fd); +} + +TEST_F(raw, icmpfilter_exact) +{ + struct icmp_filter filt = {}; + socklen_t optlen = sizeof(filt); + + ASSERT_EQ(0, getsockopt(self->fd, SOL_RAW, ICMP_FILTER, + &filt, &optlen)); + ASSERT_EQ(sizeof(filt), optlen); + ASSERT_EQ(0xdeadbeef, filt.data); +} + +TEST_F(raw, icmpfilter_oversize_clamped) +{ + char buf[16] = {}; + socklen_t optlen = sizeof(buf); + + ASSERT_EQ(0, getsockopt(self->fd, SOL_RAW, ICMP_FILTER, + buf, &optlen)); + ASSERT_EQ(sizeof(struct icmp_filter), optlen); +} + +/* Unlike the int/u64 options above, ICMP_FILTER clamps the length down + * to the user buffer instead of returning EINVAL: a short buffer + * succeeds and reports the truncated length back via optlen. + */ +TEST_F(raw, icmpfilter_undersize_clamped) +{ + char buf[2] = {}; + socklen_t optlen = sizeof(buf); + + ASSERT_EQ(0, getsockopt(self->fd, SOL_RAW, ICMP_FILTER, + buf, &optlen)); + ASSERT_EQ(sizeof(buf), optlen); +} + +TEST_F(raw, icmpfilter_wrong_proto) +{ + struct icmp_filter filt; + socklen_t optlen = sizeof(filt); + int fd; + + fd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); + if (fd < 0) + SKIP(return, "SOCK_RAW/UDP socket: %s", strerror(errno)); + + ASSERT_EQ(-1, getsockopt(fd, SOL_RAW, ICMP_FILTER, &filt, &optlen)); + ASSERT_EQ(EOPNOTSUPP, errno); + close(fd); +} + +TEST_F(raw, bad_optname) +{ + socklen_t optlen; + int val; + + optlen = sizeof(val); + + ASSERT_EQ(-1, getsockopt(self->fd, SOL_RAW, 0x7fff, &val, &optlen)); + ASSERT_EQ(ENOPROTOOPT, errno); + ASSERT_EQ(sizeof(val), optlen); +} + TEST_HARNESS_MAIN From 140be217df577961bea1ca36240439a463026cbd Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Tue, 30 Jun 2026 14:03:11 +0800 Subject: [PATCH 0119/1433] net: mvneta_bm: add suspend/resume support to prevent crash after resume The mvneta driver uses the hardware Buffer Manager (BM) for RX buffer allocation. During suspend, mvneta disables its clock, causing BM to lose all buffer address state. On resume, mvneta_bm_port_init() re- attaches the BM pool to the NIC, but BM hardware returns stale/garbage buffer addresses. When NAPI poll processes these buffers, DMA cache sync hits an invalid virtual address causing a kernel panic: Unable to handle kernel paging request at virtual address b0000080 PC is at v7_dma_inv_range Call trace: v7_dma_inv_range from arch_sync_dma_for_cpu+0x94/0x158 arch_sync_dma_for_cpu from __dma_sync_single_for_cpu+0xc4/0x15c __dma_sync_single_for_cpu from mvneta_rx_swbm+0x6c8/0xf48 mvneta_rx_swbm from mvneta_poll+0x6fc/0x70c mvneta_poll from __napi_poll.constprop.0+0x2c/0x1e0 __napi_poll.constprop.0 from net_rx_action+0x160/0x2c4 net_rx_action from handle_softirqs+0xd8/0x2b8 handle_softirqs from run_ksoftirqd+0x30/0x94 run_ksoftirqd from smpboot_thread_fn+0x100/0x204 smpboot_thread_fn from kthread+0xf4/0x110 kthread from ret_from_fork+0x14/0x28 Fix by adding suspend/resume callbacks to the BM driver: - suspend: drain all buffers (with DMA unmapping), free the BPPE regions, and reset pool state to FREE before stopping BM and gating the clock. - resume: enable the clock, reinitialize BM defaults, and restore pool read/write pointers and size registers. Pool allocation and buffer refill are handled by mvneta_resume() through the normal mvneta_bm_port_init() path, which sees pools as FREE and performs full initialization identical to probe. Add a device_link (DL_FLAG_AUTOREMOVE_CONSUMER) in mvneta_probe to guarantee BM resumes before mvneta and suspends after mvneta. If the link cannot be created, fall back to SW buffer management to avoid a potential crash on resume due to unordered PM transitions. Signed-off-by: Yun Zhou Link: https://patch.msgid.link/20260630060311.4072140-1-yun.zhou@windriver.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/marvell/mvneta.c | 18 ++++++ drivers/net/ethernet/marvell/mvneta_bm.c | 72 ++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c index 744d6585a949..543e566425c1 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -5678,6 +5678,24 @@ static int mvneta_probe(struct platform_device *pdev) "use SW buffer management\n"); mvneta_bm_put(pp->bm_priv); pp->bm_priv = NULL; + } else if (!device_link_add(&pdev->dev, + &pp->bm_priv->pdev->dev, + DL_FLAG_AUTOREMOVE_CONSUMER)) { + /* + * Link guarantees BM resumes before mvneta. + * Without it, BM may not be ready when + * mvneta_bm_port_init() runs on resume, + * causing stale buffer addresses and a crash. + * Fall back to SW management to be safe. + */ + dev_warn(&pdev->dev, + "failed to link to BM, use SW buffer management\n"); + mvneta_bm_pool_destroy(pp->bm_priv, + pp->pool_long, 1 << pp->id); + mvneta_bm_pool_destroy(pp->bm_priv, + pp->pool_short, 1 << pp->id); + mvneta_bm_put(pp->bm_priv); + pp->bm_priv = NULL; } } /* Set RX packet offset correction for platforms, whose diff --git a/drivers/net/ethernet/marvell/mvneta_bm.c b/drivers/net/ethernet/marvell/mvneta_bm.c index 6bb380494919..e0c693c0a910 100644 --- a/drivers/net/ethernet/marvell/mvneta_bm.c +++ b/drivers/net/ethernet/marvell/mvneta_bm.c @@ -129,6 +129,7 @@ static int mvneta_bm_pool_create(struct mvneta_bm *priv, if (!IS_ALIGNED((u32)bm_pool->virt_addr, MVNETA_BM_POOL_PTR_ALIGN)) { dma_free_coherent(&pdev->dev, size_bytes, bm_pool->virt_addr, bm_pool->phys_addr); + bm_pool->virt_addr = NULL; dev_err(&pdev->dev, "BM pool %d is not %d bytes aligned\n", bm_pool->id, MVNETA_BM_POOL_PTR_ALIGN); return -ENOMEM; @@ -139,6 +140,7 @@ static int mvneta_bm_pool_create(struct mvneta_bm *priv, if (err < 0) { dma_free_coherent(&pdev->dev, size_bytes, bm_pool->virt_addr, bm_pool->phys_addr); + bm_pool->virt_addr = NULL; return err; } @@ -477,6 +479,75 @@ static void mvneta_bm_remove(struct platform_device *pdev) clk_disable_unprepare(priv->clk); } +static int mvneta_bm_suspend(struct device *dev) +{ + struct mvneta_bm *priv = dev_get_drvdata(dev); + int i; + + /* Drain buffers and free pool resources while BM is still clocked */ + for (i = 0; i < MVNETA_BM_POOLS_NUM; i++) { + struct mvneta_bm_pool *bm_pool = &priv->bm_pools[i]; + int size_bytes; + + if (bm_pool->type == MVNETA_BM_FREE) + continue; + + mvneta_bm_bufs_free(priv, bm_pool, bm_pool->port_map); + if (bm_pool->hwbm_pool.buf_num) + dev_warn(&priv->pdev->dev, + "pool %d: %d buffers not freed\n", + bm_pool->id, bm_pool->hwbm_pool.buf_num); + + mvneta_bm_pool_disable(priv, bm_pool->id); + + if (bm_pool->virt_addr) { + size_bytes = sizeof(u32) * bm_pool->hwbm_pool.size; + dma_free_coherent(&priv->pdev->dev, size_bytes, + bm_pool->virt_addr, + bm_pool->phys_addr); + bm_pool->virt_addr = NULL; + } + /* + * Safe to destroy: device_link guarantees all mvneta ports + * have already suspended, so no hwbm_pool_add() can be in + * progress holding buf_lock. Pairs with mutex_init() in + * mvneta_bm_pool_use() on resume. + */ + mutex_destroy(&bm_pool->hwbm_pool.buf_lock); + bm_pool->type = MVNETA_BM_FREE; + } + + mvneta_bm_write(priv, MVNETA_BM_COMMAND_REG, MVNETA_BM_STOP_MASK); + clk_disable_unprepare(priv->clk); + return 0; +} + +static int mvneta_bm_resume(struct device *dev) +{ + struct mvneta_bm *priv = dev_get_drvdata(dev); + int i, err; + + err = clk_prepare_enable(priv->clk); + if (err) + return err; + + /* Reinitialize BM hardware; pools are refilled by mvneta_resume() */ + mvneta_bm_default_set(priv); + + /* Restore pool registers lost during clock gating */ + for (i = 0; i < MVNETA_BM_POOLS_NUM; i++) { + mvneta_bm_write(priv, MVNETA_BM_POOL_READ_PTR_REG(i), 0); + mvneta_bm_write(priv, MVNETA_BM_POOL_WRITE_PTR_REG(i), 0); + mvneta_bm_write(priv, MVNETA_BM_POOL_SIZE_REG(i), + priv->bm_pools[i].hwbm_pool.size); + } + + mvneta_bm_write(priv, MVNETA_BM_COMMAND_REG, MVNETA_BM_START_MASK); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mvneta_bm_pm_ops, mvneta_bm_suspend, mvneta_bm_resume); + static const struct of_device_id mvneta_bm_match[] = { { .compatible = "marvell,armada-380-neta-bm" }, { } @@ -489,6 +560,7 @@ static struct platform_driver mvneta_bm_driver = { .driver = { .name = MVNETA_BM_DRIVER_NAME, .of_match_table = mvneta_bm_match, + .pm = pm_sleep_ptr(&mvneta_bm_pm_ops), }, }; From 5ba5611ef946fc43d7e74cd050f334a9420de136 Mon Sep 17 00:00:00 2001 From: Kiran Kumar K Date: Tue, 30 Jun 2026 11:51:44 +0530 Subject: [PATCH 0120/1433] octeontx2-af: reserve 4 PKINDs for skip-size custom use The NPC block uses PKINDs to determine how incoming packets are parsed. Reserve PKINDs 46-49 (NPC_RX_SKIP_SIZE_PKIND) for configurable L2 skip-size use in the first pass, and PKINDs 50-53 (NPC_RX_CPT_SKIP_SIZE_PKIND) for the second pass where packets carry a CPT (Cryptographic Accelerator Unit) header. Add npc_set_skip_size_pkind() to program NPC_AF_PKINDX_ACTION0 for these reserved PKINDs with a user-supplied ptr_advance value representing the L2 size to skip. For the corresponding CPT PKINDs (pkind + 4), additionally configure the var_len_offset, var_len_mask, var_len_shift, and var_len_right fields so the NPC can extract the inner payload length from the CPT header. Update rvu_npc_set_parse_mode() to accept a new skip_size argument and dispatch to npc_set_skip_size_pkind() when the requested PKIND falls in the newly reserved range. Extend the npc_set_pkind mbox message struct with a skip_size field so PF/VF drivers can supply this value at run time. Advance NPC_UNRESERVED_PKIND_COUNT to NPC_RX_SKIP_SIZE_PKIND to reflect the updated reservation boundary. Signed-off-by: Kiran Kumar K Signed-off-by: Nitin Shetty J Link: https://patch.msgid.link/20260630062145.2533816-2-nshettyj@marvell.com Signed-off-by: Paolo Abeni --- .../net/ethernet/marvell/octeontx2/af/mbox.h | 1 + .../net/ethernet/marvell/octeontx2/af/npc.h | 4 +- .../net/ethernet/marvell/octeontx2/af/rvu.h | 2 +- .../ethernet/marvell/octeontx2/af/rvu_nix.c | 2 +- .../ethernet/marvell/octeontx2/af/rvu_npc.c | 43 +++++++++++++++++-- 5 files changed, 46 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h index 714e47f68d93..83f0da3a93fb 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h +++ b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h @@ -803,6 +803,7 @@ struct npc_set_pkind { */ u8 var_len_off_mask; /* Mask for length with in offset */ u8 shift_dir; /* shift direction to get length of the header at var_len_off */ + u8 skip_size; /* l2 size to skip */ }; /* NPA mbox message formats */ diff --git a/drivers/net/ethernet/marvell/octeontx2/af/npc.h b/drivers/net/ethernet/marvell/octeontx2/af/npc.h index eaed172f1606..719b3618eeb5 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/npc.h +++ b/drivers/net/ethernet/marvell/octeontx2/af/npc.h @@ -161,10 +161,12 @@ enum npc_kpu_lh_ltype { * Software assigns pkind for each incoming port such as CGX * Ethernet interfaces, LBK interfaces, etc. */ -#define NPC_UNRESERVED_PKIND_COUNT NPC_RX_CPT_HDR_PTP_PKIND +#define NPC_UNRESERVED_PKIND_COUNT NPC_RX_SKIP_SIZE_PKIND enum npc_pkind_type { NPC_RX_LBK_PKIND = 0ULL, + NPC_RX_SKIP_SIZE_PKIND = 46ULL, + NPC_RX_CPT_SKIP_SIZE_PKIND = 50ULL, NPC_RX_CPT_HDR_PTP_PKIND = 54ULL, NPC_RX_CUSTOM_PRE_L2_PKIND = 55ULL, NPC_RX_VLAN_EXDSA_PKIND = 56ULL, diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h index 7f3505ae6860..c5610f242687 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h @@ -1181,7 +1181,7 @@ void rvu_switch_enable_lbk_link(struct rvu *rvu, u16 pcifunc, bool ena); int rvu_npc_set_parse_mode(struct rvu *rvu, u16 pcifunc, u64 mode, u8 dir, u64 pkind, u8 var_len_off, u8 var_len_off_mask, - u8 shift_dir); + u8 shift_dir, u8 skip_size); int rvu_get_hwvf(struct rvu *rvu, int pcifunc); /* CN10K MCS */ diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c index 0297c7ab0614..144076e161c6 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c @@ -5392,7 +5392,7 @@ void rvu_nix_lf_teardown(struct rvu *rvu, u16 pcifunc, int blkaddr, int nixlf) /* reset HW config done for Switch headers */ rvu_npc_set_parse_mode(rvu, pcifunc, OTX2_PRIV_FLAGS_DEFAULT, - (PKIND_TX | PKIND_RX), 0, 0, 0, 0); + (PKIND_TX | PKIND_RX), 0, 0, 0, 0, 0); /* Disabling CGX and NPC config done for PTP */ if (pfvf->hw_rx_tstamp_en) { diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c index c7bc0b3a29b9..08b83de9beb4 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c @@ -4194,10 +4194,40 @@ npc_set_var_len_offset_pkind(struct rvu *rvu, u16 pcifunc, u64 pkind, return 0; } +static int npc_set_skip_size_pkind(struct rvu *rvu, u16 pcifunc, u64 pkind, + u8 skip_size) +{ + struct npc_kpu_action0 *act0; + int blkaddr; + u64 val; + + blkaddr = rvu_get_blkaddr(rvu, BLKTYPE_NPC, pcifunc); + if (blkaddr < 0) { + dev_err(rvu->dev, "%s: NPC block not implemented\n", __func__); + return -EINVAL; + } + + val = rvu_read64(rvu, blkaddr, NPC_AF_PKINDX_ACTION0(pkind)); + act0 = (struct npc_kpu_action0 *)&val; + act0->ptr_advance = skip_size; + rvu_write64(rvu, blkaddr, NPC_AF_PKINDX_ACTION0(pkind), val); + + /* Update CPT_HR new PKIND */ + val = rvu_read64(rvu, blkaddr, NPC_AF_PKINDX_ACTION0(pkind + 4)); + act0 = (struct npc_kpu_action0 *)&val; + act0->ptr_advance = (skip_size + 40); + act0->next_state = NPC_S_KPU1_CPT_HDR; + act0->var_len_offset = (skip_size + 6); + act0->var_len_mask = 0xe0; + act0->var_len_shift = 0x5; + act0->var_len_right = 0x1; + rvu_write64(rvu, blkaddr, NPC_AF_PKINDX_ACTION0(pkind + 4), val); + return 0; +} + int rvu_npc_set_parse_mode(struct rvu *rvu, u16 pcifunc, u64 mode, u8 dir, u64 pkind, u8 var_len_off, u8 var_len_off_mask, - u8 shift_dir) - + u8 shift_dir, u8 skip_size) { struct rvu_pfvf *pfvf = rvu_get_pfvf(rvu, pcifunc); int blkaddr, nixlf, rc, intf_mode; @@ -4218,6 +4248,12 @@ int rvu_npc_set_parse_mode(struct rvu *rvu, u16 pcifunc, u64 mode, u8 dir, shift_dir); if (rc) return rc; + } else if (pkind >= NPC_RX_SKIP_SIZE_PKIND && + pkind <= NPC_RX_SKIP_SIZE_PKIND + 3) { + rc = npc_set_skip_size_pkind(rvu, pcifunc, pkind, + skip_size); + if (rc) + return rc; } rxpkind = pkind; txpkind = pkind; @@ -4254,7 +4290,8 @@ int rvu_mbox_handler_npc_set_pkind(struct rvu *rvu, struct npc_set_pkind *req, { return rvu_npc_set_parse_mode(rvu, req->hdr.pcifunc, req->mode, req->dir, req->pkind, req->var_len_off, - req->var_len_off_mask, req->shift_dir); + req->var_len_off_mask, req->shift_dir, + req->skip_size); } int rvu_mbox_handler_npc_read_base_steer_rule(struct rvu *rvu, From 961db18e28d0ab6a684292e0efc99ba24377d394 Mon Sep 17 00:00:00 2001 From: Kiran Kumar K Date: Tue, 30 Jun 2026 11:51:45 +0530 Subject: [PATCH 0121/1433] octeontx2-af: Add RSS hashing support based on RoCEv2 header Add NIX_FLOW_KEY_TYPE_ROCEV2 flow key type to support RSS hashing on the RoCEv2 destination Queue Pair (QP) field, allowing RoCEv2 traffic to be distributed across receive queues. Signed-off-by: Kiran Kumar K Signed-off-by: Nitin Shetty J Link: https://patch.msgid.link/20260630062145.2533816-3-nshettyj@marvell.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/marvell/octeontx2/af/mbox.h | 1 + drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h index 83f0da3a93fb..f87cdf1b971d 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h +++ b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h @@ -1268,6 +1268,7 @@ struct nix_rss_flowkey_cfg { #define NIX_FLOW_KEY_TYPE_IPV4_PROTO BIT(21) #define NIX_FLOW_KEY_TYPE_AH BIT(22) #define NIX_FLOW_KEY_TYPE_ESP BIT(23) +#define NIX_FLOW_KEY_TYPE_ROCEV2 BIT(24) #define NIX_FLOW_KEY_TYPE_L4_DST_ONLY BIT(28) #define NIX_FLOW_KEY_TYPE_L4_SRC_ONLY BIT(29) #define NIX_FLOW_KEY_TYPE_L3_DST_ONLY BIT(30) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c index 144076e161c6..8e3bb47eb3ba 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c @@ -4305,6 +4305,13 @@ static int set_flowkey_fields(struct nix_rx_flowkey_alg *alg, u32 flow_cfg) keyoff_marker = false; } break; + case NIX_FLOW_KEY_TYPE_ROCEV2: + field->hdr_offset = 5; + field->bytesm1 = 2; /* Destination QP */ + field->ltype_mask = 0xF; + field->lid = NPC_LID_LE; + field->ltype_match = NPC_LT_LE_ROCEV2; + break; } field->ena = 1; From 7123cb442e13fb426bee8132d0420b59071a5153 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Mon, 25 May 2026 15:07:34 +0800 Subject: [PATCH 0122/1433] wifi: rtw89: fw: add first set of firmware features by version for RTL8922D The firmware features including version of command/event format are maintained by this table, which enables features by firmware version. Define the first feature set accordingly. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260525070735.27659-2-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/fw.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index d6a594b75ab2..41b48e0d73a3 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -933,6 +933,19 @@ static const struct __fw_feat_cfg fw_feat_tbl[] = { __CFG_FW_FEAT(RTL8922A, ge, 0, 35, 92, 0, TX_HISTORY_V1), __CFG_FW_FEAT(RTL8922A, ge, 0, 35, 100, 0, SER_POST_RECOVER_DMAC), __CFG_FW_FEAT(RTL8922A, ge, 0, 35, 108, 0, SIM_SER_L0L1_BY_HALT_H2C), + __CFG_FW_FEAT(RTL8922D, ge, 0, 0, 0, 0, MACID_PAUSE_SLEEP), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 75, 2, SCAN_OFFLOAD), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 75, 2, BEACON_FILTER), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 76, 0, LPS_DACK_BY_C2H_REG), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 84, 0, CRASH_TRIGGER_TYPE_1), + __CFG_FW_FEAT(RTL8922D, lt, 0, 35, 84, 0, ADDR_CAM_V0), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 87, 0, BEACON_LOSS_COUNT_V1), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 91, 0, RFK_PRE_NOTIFY_MCC_V2), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 91, 4, LPS_ML_INFO_V1), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 93, 0, NOTIFY_AP_INFO), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 100, 0, SER_POST_RECOVER_DMAC), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 104, 0, TX_HISTORY_V1), + __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 108, 0, SIM_SER_L0L1_BY_HALT_H2C), }; static void rtw89_fw_iterate_feature_cfg(struct rtw89_fw_info *fw, From 92827aaf07ceeb1db93a847c287f44f2ae7cda41 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Mon, 25 May 2026 15:07:35 +0800 Subject: [PATCH 0123/1433] wifi: rtw89: fw: support scan offload v2 for WiFi 7 chips The format of scan offload v2 is to extend fields to consider channel noise as a factor to adjust dwell time of certain channels. Leave empty for now to ignore this factor. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260525070735.27659-3-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.h | 1 + drivers/net/wireless/realtek/rtw89/fw.c | 9 +++++++-- drivers/net/wireless/realtek/rtw89/fw.h | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 5547888d7e67..4bf6fe9d3880 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -4917,6 +4917,7 @@ enum rtw89_fw_feature { RTW89_FW_FEATURE_BEACON_FILTER, RTW89_FW_FEATURE_MACID_PAUSE_SLEEP, RTW89_FW_FEATURE_SCAN_OFFLOAD_BE_V0, + RTW89_FW_FEATURE_SCAN_OFFLOAD_BE_V1, RTW89_FW_FEATURE_WOW_REASON_V1, RTW89_FW_FEATURE_GROUP(WITH_RFK_PRE_NOTIFY, RTW89_FW_FEATURE_RFK_PRE_NOTIFY_V0, diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 41b48e0d73a3..3707dc5bf4f8 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -933,6 +933,7 @@ static const struct __fw_feat_cfg fw_feat_tbl[] = { __CFG_FW_FEAT(RTL8922A, ge, 0, 35, 92, 0, TX_HISTORY_V1), __CFG_FW_FEAT(RTL8922A, ge, 0, 35, 100, 0, SER_POST_RECOVER_DMAC), __CFG_FW_FEAT(RTL8922A, ge, 0, 35, 108, 0, SIM_SER_L0L1_BY_HALT_H2C), + __CFG_FW_FEAT(RTL8922A, lt, 0, 35, 109, 1, SCAN_OFFLOAD_BE_V1), __CFG_FW_FEAT(RTL8922D, ge, 0, 0, 0, 0, MACID_PAUSE_SLEEP), __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 75, 2, SCAN_OFFLOAD), __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 75, 2, BEACON_FILTER), @@ -946,6 +947,7 @@ static const struct __fw_feat_cfg fw_feat_tbl[] = { __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 100, 0, SER_POST_RECOVER_DMAC), __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 104, 0, TX_HISTORY_V1), __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 108, 0, SIM_SER_L0L1_BY_HALT_H2C), + __CFG_FW_FEAT(RTL8922D, lt, 0, 35, 109, 1, SCAN_OFFLOAD_BE_V1), }; static void rtw89_fw_iterate_feature_cfg(struct rtw89_fw_info *fw, @@ -6842,7 +6844,10 @@ int rtw89_fw_h2c_scan_offload_be(struct rtw89_dev *rtwdev, rtw89_scan_get_6g_disabled_chan(rtwdev, option); - if (RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V0, &rtwdev->fw)) { + if (RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V1, &rtwdev->fw)) { + cfg_len = offsetofend(typeof(*h2c), w9); + scan_offload_ver = 1; + } else if (RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V0, &rtwdev->fw)) { cfg_len = offsetofend(typeof(*h2c), w8); scan_offload_ver = 0; } @@ -6921,7 +6926,7 @@ int rtw89_fw_h2c_scan_offload_be(struct rtw89_dev *rtwdev, if (scan_offload_ver == 0) goto flex_member; - h2c->w9 = le32_encode_bits(sizeof(*h2c) / sizeof(h2c->w0), + h2c->w9 = le32_encode_bits(cfg_len / sizeof(h2c->w0), RTW89_H2C_SCANOFLD_BE_W9_SIZE_CFG) | le32_encode_bits(sizeof(*macc_role) / sizeof(macc_role->w0), RTW89_H2C_SCANOFLD_BE_W9_SIZE_MACC) | diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index 20721d5209aa..6cb99144e541 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -3063,6 +3063,8 @@ struct rtw89_h2c_scanofld_be { __le32 w7; __le32 w8; __le32 w9; /* Added after SCAN_OFFLOAD_BE_V1 */ + __le32 w10; /* Added after SCAN_OFFLOAD_BE_V2 */ + __le32 w11; /* Added after SCAN_OFFLOAD_BE_V2 */ /* struct rtw89_h2c_scanofld_be_macc_role (flexible number) */ /* struct rtw89_h2c_scanofld_be_opch (flexible number) */ } __packed; From 1908534deb53a018580309be84a4f7dcc9cb1af3 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 8 Jun 2026 15:34:46 +0300 Subject: [PATCH 0124/1433] wifi: rtw89: debug: fix off by on in rtw89_ppdu_str() This > comparison should be >= to avoid an out of bounds access. Fixes: 419ed7f4a053 ("wifi: rtw89: debug: extend bb_info with TX status and PER") Signed-off-by: Dan Carpenter Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/aia25i0ds3B6QF6c@stanley.mountain --- drivers/net/wireless/realtek/rtw89/debug.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/debug.c b/drivers/net/wireless/realtek/rtw89/debug.c index 8f5af873e09f..5786120602ab 100644 --- a/drivers/net/wireless/realtek/rtw89/debug.c +++ b/drivers/net/wireless/realtek/rtw89/debug.c @@ -4348,7 +4348,7 @@ static const char *rtw89_ppdu_str(struct rtw89_dev *rtwdev, u8 type, u8 subtype) const struct rtw89_chip_info *chip = rtwdev->chip; const struct rtw89_ppdu_info *ppdu_info; - if (type > ARRAY_SIZE(rtw89_ppdu_infos)) + if (type >= ARRAY_SIZE(rtw89_ppdu_infos)) return "RSVD"; ppdu_info = &rtw89_ppdu_infos[type]; From 1b4cd55626b6ffa93d18848412afb912a4add585 Mon Sep 17 00:00:00 2001 From: William Hansen-Baird Date: Tue, 9 Jun 2026 11:53:59 +0200 Subject: [PATCH 0125/1433] wifi: rtlwifi: rtl8723be: Remove unnecessary irq save/restore in hw_init() rtl8723be hw_init() calls local_save_flags(flags) followed by local_irq_enable(). Later, local_irq_restore(flags) is called. This causes warnings from Lockdep on boot and modprobe, as local_irq_restore(flags) should only be called while irqs are disabled. The warning was introduced to detect this class of bug in [1]. With testing I found that all paths which call hw_init() have irqs already enabled for rtl8723be. Furthermore, the calls were originally added for the rtl8192ce in commit f78bccd79ba3 ("rtlwifi: rtl8192ce: Fix too long disable of IRQs") before later being added to most other rtlwifi drivers. Commit d3feae41a347 ("rtlwifi: Update power-save routines for 062814 driver") then replaces the call to spin_lock_irqsave() before hw_init(), and thus the codepath which caused irqs to be disabled in hw_init and prompted the original commit has been removed. The same irq save/restore pattern is also present in the hw_init() of rtl8192ce, rtl8723ae, rtl8188ee, rtl8192se and rtl8192cu, however I don't have the hardware to test them, so I did not include them in my changes. Tested on a Razer Blade 14 2017. Example of output from Lockdep prior to fix: raw_local_irq_restore() called with IRQs enabled ... Call Trace: rtl8723be_hw_init+0x5992/0x7220 [rtl8723be] ? static_obj+0x61/0xa0 rtl_pci_start+0x222/0x5c0 [rtl_pci] rtl_op_start+0x128/0x1a0 [rtlwifi] ? __kasan_check_read+0x11/0x20 drv_start+0x16c/0x550 [mac80211] ... irq event stamp: 887679 hardirqs last enabled at (887689): [] __up_console_sem+0x90/0xa0 hardirqs last disabled at (887698): [] __up_console_sem+0x75/0xa0 softirqs last enabled at (887670): [] __irq_exit_rcu+0x175/0x2f0 softirqs last disabled at (887649): [] __irq_exit_rcu+0x175/0x2f0 ---[ end trace 0000000000000000 ]--- [1] https://lore.kernel.org/all/20210111153707.10071-1-mark.rutland@arm.com/ Signed-off-by: William Hansen-Baird Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260609095359.2964193-1-william.hansen.baird@gmail.com --- drivers/net/wireless/realtek/rtlwifi/rtl8723be/hw.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/net/wireless/realtek/rtlwifi/rtl8723be/hw.c b/drivers/net/wireless/realtek/rtlwifi/rtl8723be/hw.c index e1f811218894..bf7b5a32adaa 100644 --- a/drivers/net/wireless/realtek/rtlwifi/rtl8723be/hw.c +++ b/drivers/net/wireless/realtek/rtlwifi/rtl8723be/hw.c @@ -1333,11 +1333,6 @@ int rtl8723be_hw_init(struct ieee80211_hw *hw) bool rtstatus = true; int err; u8 tmp_u1b; - unsigned long flags; - - /* reenable interrupts to not interfere with other devices */ - local_save_flags(flags); - local_irq_enable(); rtlhal->fw_ready = false; rtlpriv->rtlhal.being_init_adapter = true; @@ -1443,7 +1438,6 @@ int rtl8723be_hw_init(struct ieee80211_hw *hw) rtl8723be_dm_init(hw); exit: - local_irq_restore(flags); rtlpriv->rtlhal.being_init_adapter = false; return err; } From a4a2c1a1032f8254e18b44cfdbe7e2be433b758f Mon Sep 17 00:00:00 2001 From: Bitterblue Smith Date: Wed, 10 Jun 2026 16:01:35 +0300 Subject: [PATCH 0126/1433] wifi: rtw88: 8822c: Don't process RF path C in query_phy_status_page{0,1} Replace <= with < in the loops in query_phy_status_page{0,1}(). They were processing data related to RF path C, which this chip doesn't have. The only bad effect seems to be that the phy_info file in debugfs was printing unexpected values for RF path C. Signed-off-by: Bitterblue Smith Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/ee30b95f-bc68-4711-9b15-cf5fd23c3c48@gmail.com --- drivers/net/wireless/realtek/rtw88/rtw8822c.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822c.c b/drivers/net/wireless/realtek/rtw88/rtw8822c.c index 244c8026479c..80c9f0c11e5c 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8822c.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8822c.c @@ -2584,7 +2584,7 @@ static void query_phy_status_page0(struct rtw_dev *rtwdev, u8 *phy_status, pkt_stat->rx_power[RF_PATH_A] = rx_power[RF_PATH_A]; pkt_stat->rx_power[RF_PATH_B] = rx_power[RF_PATH_B]; - for (path = 0; path <= rtwdev->hal.rf_path_num; path++) { + for (path = 0; path < rtwdev->hal.rf_path_num; path++) { rssi = rtw_phy_rf_power_2_rssi(&pkt_stat->rx_power[path], 1); dm_info->rssi[path] = rssi; } @@ -2644,7 +2644,7 @@ static void query_phy_status_page1(struct rtw_dev *rtwdev, u8 *phy_status, pkt_stat->cfo_tail[RF_PATH_A] = GET_PHY_STAT_P1_CFO_TAIL_A(phy_status); pkt_stat->cfo_tail[RF_PATH_B] = GET_PHY_STAT_P1_CFO_TAIL_B(phy_status); - for (path = 0; path <= rtwdev->hal.rf_path_num; path++) { + for (path = 0; path < rtwdev->hal.rf_path_num; path++) { rssi = rtw_phy_rf_power_2_rssi(&pkt_stat->rx_power[path], 1); dm_info->rssi[path] = rssi; if (path == RF_PATH_A) { From 617b1d97617bd44319e796811cf5cd4a2cf634fc Mon Sep 17 00:00:00 2001 From: Bitterblue Smith Date: Wed, 10 Jun 2026 16:02:21 +0300 Subject: [PATCH 0127/1433] wifi: rtw88: 8822b: Don't process RF path C in query_phy_status_page1 Replace <= with < in the loop in query_phy_status_page1(). It was processing data related to RF path C, which this chip doesn't have. The only bad effect seems to be that the phy_info file in debugfs was printing unexpected values for RF path C. Signed-off-by: Bitterblue Smith Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/9c4beb36-2954-4db0-844a-74ba5eacf21b@gmail.com --- drivers/net/wireless/realtek/rtw88/rtw8822b.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822b.c b/drivers/net/wireless/realtek/rtw88/rtw8822b.c index e9e8a7f3f382..37b7a520fea0 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8822b.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8822b.c @@ -896,7 +896,7 @@ static void query_phy_status_page1(struct rtw_dev *rtwdev, u8 *phy_status, pkt_stat->cfo_tail[RF_PATH_A] = GET_PHY_STAT_P1_CFO_TAIL_A(phy_status); pkt_stat->cfo_tail[RF_PATH_B] = GET_PHY_STAT_P1_CFO_TAIL_B(phy_status); - for (path = 0; path <= rtwdev->hal.rf_path_num; path++) { + for (path = 0; path < rtwdev->hal.rf_path_num; path++) { rssi = rtw_phy_rf_power_2_rssi(&pkt_stat->rx_power[path], 1); dm_info->rssi[path] = rssi; dm_info->rx_snr[path] = pkt_stat->rx_snr[path] >> 1; From e13cd023a4cdbbb9e58ab91e857b5e45ea753f19 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Thu, 11 Jun 2026 16:20:21 +0800 Subject: [PATCH 0128/1433] wifi: rtw89: fw: correct preload field of w2 in rtw89_fw_h2c_default_cmac_tbl_be() BE_CCTL_INFO_W2_PRELOAD_ENABLE is for h2c->w2, not h2c->w1. These will cause h2c->w1 wrong overlap by w2 and w2 not initialized. Fixes: c73607b3a8ef ("wifi: rtw89: fw: add CMAC H2C command to initialize default value for RTL8922D") Signed-off-by: Wentao Guan Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260611082021.46650-1-guanwentao@uniontech.com --- drivers/net/wireless/realtek/rtw89/fw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 3707dc5bf4f8..27b52845c1dd 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -3744,7 +3744,7 @@ int rtw89_fw_h2c_default_cmac_tbl_be(struct rtw89_dev *rtwdev, le32_encode_bits(4, BE_CCTL_INFO_W1_RTS_RTY_LOWEST_RATE); h2c->m1 = cpu_to_le32(BE_CCTL_INFO_W1_ALL); - h2c->w1 = le32_encode_bits(preld, BE_CCTL_INFO_W2_PRELOAD_ENABLE); + h2c->w2 = le32_encode_bits(preld, BE_CCTL_INFO_W2_PRELOAD_ENABLE); h2c->m2 = cpu_to_le32(BE_CCTL_INFO_W2_ALL); h2c->m3 = cpu_to_le32(BE_CCTL_INFO_W3_ALL); From 2c0810030cf8a6a04d59cecff4a79d506da177c8 Mon Sep 17 00:00:00 2001 From: Panagiotis Petrakopoulos Date: Sat, 13 Jun 2026 01:30:12 +0300 Subject: [PATCH 0129/1433] wifi: rtw89: use str_enable_disable() helper Replace "enable"/"disable" strings in ternary expressions with the str_enable_disable() helper from . This covers the rfkill state log in rtw89_core_rfkill_poll() and the DPK on/off log in _dpk_onoff(). No functional change intended. Signed-off-by: Panagiotis Petrakopoulos Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260612223012.504886-1-npetrakopoulos2003@gmail.com --- drivers/net/wireless/realtek/rtw89/core.c | 2 +- drivers/net/wireless/realtek/rtw89/rtw8851b_rfk.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index 68dad6090f87..26b744dfbcf8 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -7266,7 +7266,7 @@ void rtw89_core_rfkill_poll(struct rtw89_dev *rtwdev, bool force) return; rtw89_info(rtwdev, "rfkill hardware state changed to %s\n", - blocked ? "disable" : "enable"); + str_enable_disable(!blocked)); if (blocked) set_bit(RTW89_FLAG_HW_RFKILL_STATE, rtwdev->flags); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b_rfk.c b/drivers/net/wireless/realtek/rtw89/rtw8851b_rfk.c index e574a9950a3b..a6c7f59223ef 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851b_rfk.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851b_rfk.c @@ -1874,7 +1874,7 @@ static void _dpk_onoff(struct rtw89_dev *rtwdev, enum rtw89_rf_path path, 0xf0000000, val); rtw89_debug(rtwdev, RTW89_DBG_RFK, "[DPK] S%d[%d] DPK %s !!!\n", path, - kidx, val == 0 ? "disable" : "enable"); + kidx, str_enable_disable(val)); } static void _dpk_init(struct rtw89_dev *rtwdev, enum rtw89_rf_path path) From 000e63e67a78456bed43a8d681ff83e8f36ad1af Mon Sep 17 00:00:00 2001 From: Chen Jung Ku Date: Sun, 14 Jun 2026 01:04:34 +0800 Subject: [PATCH 0130/1433] wifi: rtw88: 8822c: replace msleep() with fsleep() for DPK delays Replace msleep() with fsleep(), because msleep() may oversleep to as much as 20 ms when used for a 10 ms delay. According to the kernel documentation, fsleep() is more suitable and aligns better with modern kernel style. Signed-off-by: Chen Jung Ku Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260613170434.23645-1-ku.loong@gapp.nthu.edu.tw --- drivers/net/wireless/realtek/rtw88/rtw8822c.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822c.c b/drivers/net/wireless/realtek/rtw88/rtw8822c.c index 80c9f0c11e5c..c3220c98f08a 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8822c.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8822c.c @@ -3405,7 +3405,7 @@ static u8 rtw8822c_dpk_one_shot(struct rtw_dev *rtwdev, u8 path, u8 action) rtw_write32_mask(rtwdev, REG_DPD_CTL0, BIT(12), 0x1); rtw_write32_mask(rtwdev, REG_DPD_CTL0, BIT(12), 0x0); rtw_write32_mask(rtwdev, REG_RXSRAM_CTL, BIT_RPT_SEL, 0x0); - msleep(10); + fsleep(10000); if (!check_hw_ready(rtwdev, REG_STAT_RPT, BIT(31), 0x1)) { result = 1; rtw_dbg(rtwdev, RTW_DBG_RFK, "[DPK] one-shot over 20ms\n"); @@ -3418,7 +3418,7 @@ static u8 rtw8822c_dpk_one_shot(struct rtw_dev *rtwdev, u8 path, u8 action) dpk_cmd = rtw8822c_dpk_get_cmd(rtwdev, action, path); rtw_write32(rtwdev, REG_NCTL0, dpk_cmd); rtw_write32(rtwdev, REG_NCTL0, dpk_cmd + 1); - msleep(10); + fsleep(10000); if (!check_hw_ready(rtwdev, 0x2d9c, 0xff, 0x55)) { result = 1; rtw_dbg(rtwdev, RTW_DBG_RFK, "[DPK] one-shot over 20ms\n"); From e779df4806cd29cbcca5c9dc0a1073662c76b889 Mon Sep 17 00:00:00 2001 From: Dawei Feng Date: Wed, 17 Jun 2026 09:35:02 +0800 Subject: [PATCH 0131/1433] wifi: rtw88: pci: fix resource leak on failed NAPI setup rtw_pci_probe() allocates PCI resources through rtw_pci_setup_resource() before it sets up NAPI. If rtw_pci_napi_init() fails, the error path jumps straight to err_pci_declaim and skips rtw_pci_destroy(), leaving the PCI resources allocated by rtw_pci_setup_resource() behind. Add a dedicated cleanup label for the NAPI setup failure path so probe destroys the PCI resources. The bug was first flagged by an experimental analysis tool we are developing for kernel memory-management bugs while analyzing current mainline kernels. The tool is still under development and is not yet publicly available. Manual inspection confirms that the bug is still present in v7.1-rc7. An x86_64 allyesconfig build showed no new warnings. As we do not have a suitable rtw88 PCI board to test with, no runtime testing was able to be performed. Fixes: d0bcb10e7b94 ("wifi: rtw88: Un-embed dummy device") Cc: stable@vger.kernel.org Signed-off-by: Dawei Feng Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260617013502.114057-1-dawei.feng@seu.edu.cn --- drivers/net/wireless/realtek/rtw88/pci.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw88/pci.c b/drivers/net/wireless/realtek/rtw88/pci.c index a30467228912..69f2840fed09 100644 --- a/drivers/net/wireless/realtek/rtw88/pci.c +++ b/drivers/net/wireless/realtek/rtw88/pci.c @@ -1834,7 +1834,7 @@ int rtw_pci_probe(struct pci_dev *pdev, ret = rtw_pci_napi_init(rtwdev); if (ret) { rtw_err(rtwdev, "failed to setup NAPI\n"); - goto err_pci_declaim; + goto err_destroy_rsrc; } ret = rtw_chip_info_setup(rtwdev); @@ -1866,6 +1866,8 @@ int rtw_pci_probe(struct pci_dev *pdev, err_destroy_pci: rtw_pci_napi_deinit(rtwdev); + +err_destroy_rsrc: rtw_pci_destroy(rtwdev, pdev); err_pci_declaim: From ed4f05d9f2f42fd866f55108db8123eefcc5fb33 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Sat, 20 Jun 2026 10:56:32 +0800 Subject: [PATCH 0132/1433] wifi: rtlwifi: rtl8192du: check QoS TID before indexing tids rtl92du_tx_fill_desc() uses ieee80211_get_tid() to read the QoS TID from the 802.11 header and then uses it as an index into sta_entry->tids[]. ieee80211_get_tid() returns the low 4-bit QoS TID value, so the result can be in the range 0..15. rtlwifi only allocates MAX_TID_COUNT entries for sta_entry->tids[], and MAX_TID_COUNT is 9. A QoS TID greater than 8 therefore indexes past the aggregation state array. Keep the default RTL_AGG_STOP state for out-of-range TIDs, matching rtl92cu_tx_fill_desc(). This issue was detected by our static analysis tool and confirmed by manual audit. UBSAN validation for the same bug pattern reports an array-index-out-of-bounds access with index 10 for type 'rtl_tid_data [9]'. Fixes: 8321424134a4 ("wifi: rtlwifi: Add rtl8192du/trx.{c,h}") Cc: stable@vger.kernel.org Signed-off-by: Runyu Xiao Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260620025632.46206-1-runyu.xiao@seu.edu.cn --- drivers/net/wireless/realtek/rtlwifi/rtl8192du/trx.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtlwifi/rtl8192du/trx.c b/drivers/net/wireless/realtek/rtlwifi/rtl8192du/trx.c index 743ce0cfffe6..c608c51f1b78 100644 --- a/drivers/net/wireless/realtek/rtlwifi/rtl8192du/trx.c +++ b/drivers/net/wireless/realtek/rtlwifi/rtl8192du/trx.c @@ -106,7 +106,8 @@ void rtl92du_tx_fill_desc(struct ieee80211_hw *hw, if (sta) { sta_entry = (struct rtl_sta_info *)sta->drv_priv; tid = ieee80211_get_tid(hdr); - agg_state = sta_entry->tids[tid].agg.agg_state; + if (tid < MAX_TID_COUNT) + agg_state = sta_entry->tids[tid].agg.agg_state; ampdu_density = sta->deflink.ht_cap.ampdu_density; } From 1349ed8f104a77964a907d52f25d91fadeb9ce3b Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Mon, 22 Jun 2026 09:54:39 +0800 Subject: [PATCH 0133/1433] wifi: rtl8xxxu: 8723bu: remove reference of non-existing firmware rtl8723bu_bt.bin A report from [1] that firmware is missing in linux-firmware repository. However, there is no specific firmware for RTL8723BU for Bluetooth enabled. Remove the unnecessary reference of firmware file. [1] https://github.com/rtlwifi-linux/rtlwifi-next/issues/20 Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260622015439.9621-1-pkshih@realtek.com --- drivers/net/wireless/realtek/rtl8xxxu/8723b.c | 11 +---------- drivers/net/wireless/realtek/rtl8xxxu/core.c | 1 - 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/drivers/net/wireless/realtek/rtl8xxxu/8723b.c b/drivers/net/wireless/realtek/rtl8xxxu/8723b.c index e314ef991b38..24c6d8ae76ec 100644 --- a/drivers/net/wireless/realtek/rtl8xxxu/8723b.c +++ b/drivers/net/wireless/realtek/rtl8xxxu/8723b.c @@ -483,16 +483,7 @@ static int rtl8723bu_parse_efuse(struct rtl8xxxu_priv *priv) static int rtl8723bu_load_firmware(struct rtl8xxxu_priv *priv) { - const char *fw_name; - int ret; - - if (priv->enable_bluetooth) - fw_name = "rtlwifi/rtl8723bu_bt.bin"; - else - fw_name = "rtlwifi/rtl8723bu_nic.bin"; - - ret = rtl8xxxu_load_firmware(priv, fw_name); - return ret; + return rtl8xxxu_load_firmware(priv, "rtlwifi/rtl8723bu_nic.bin"); } static void rtl8723bu_init_phy_bb(struct rtl8xxxu_priv *priv) diff --git a/drivers/net/wireless/realtek/rtl8xxxu/core.c b/drivers/net/wireless/realtek/rtl8xxxu/core.c index 646fe76b086e..4e8a4769603c 100644 --- a/drivers/net/wireless/realtek/rtl8xxxu/core.c +++ b/drivers/net/wireless/realtek/rtl8xxxu/core.c @@ -37,7 +37,6 @@ MODULE_FIRMWARE("rtlwifi/rtl8192cufw_B.bin"); MODULE_FIRMWARE("rtlwifi/rtl8192cufw_TMSC.bin"); MODULE_FIRMWARE("rtlwifi/rtl8192eu_nic.bin"); MODULE_FIRMWARE("rtlwifi/rtl8723bu_nic.bin"); -MODULE_FIRMWARE("rtlwifi/rtl8723bu_bt.bin"); MODULE_FIRMWARE("rtlwifi/rtl8188fufw.bin"); MODULE_FIRMWARE("rtlwifi/rtl8710bufw_SMIC.bin"); MODULE_FIRMWARE("rtlwifi/rtl8710bufw_UMC.bin"); From ed51a86b787f6e1008aefc58aca3491ee079aef0 Mon Sep 17 00:00:00 2001 From: "Bitterblue Smith (S.E.A. Datentechnik GmbH)" Date: Mon, 22 Jun 2026 20:02:44 +0300 Subject: [PATCH 0134/1433] wifi: rtw88: Enable receiving control frames in monitor mode By default RTL8723D, RTL8703B, RTL8812A, RTL8821A, and RTL8814A are configured to filter out all control frames except PS-Poll, even in monitor mode. Handle FIF_CONTROL in rtw_ops_configure_filter(). When it's set, configure REG_RXFLTMAP1 to let all control frames through. When it's unset, restore the original value. Because some drivers configure REG_RXFLTMAP1 differently, keep track of its value in a new member of struct rtw_hal. Signed-off-by: Bitterblue Smith (S.E.A. Datentechnik GmbH) Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/2a52d718-9e46-47f2-84a1-d8e7b1ed89a8@gmail.com --- drivers/net/wireless/realtek/rtw88/bf.c | 6 ++++-- drivers/net/wireless/realtek/rtw88/mac80211.c | 8 +++++++- drivers/net/wireless/realtek/rtw88/main.h | 1 + drivers/net/wireless/realtek/rtw88/rtw8723x.c | 3 ++- drivers/net/wireless/realtek/rtw88/rtw8814a.c | 3 ++- drivers/net/wireless/realtek/rtw88/rtw8821c.c | 4 +++- drivers/net/wireless/realtek/rtw88/rtw8821c.h | 3 ++- drivers/net/wireless/realtek/rtw88/rtw8822b.c | 7 +++++-- drivers/net/wireless/realtek/rtw88/rtw8822c.c | 7 +++++-- drivers/net/wireless/realtek/rtw88/rtw88xxa.c | 3 ++- 10 files changed, 33 insertions(+), 12 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw88/bf.c b/drivers/net/wireless/realtek/rtw88/bf.c index 0d0ccbc7d00c..7313ecc5c82a 100644 --- a/drivers/net/wireless/realtek/rtw88/bf.c +++ b/drivers/net/wireless/realtek/rtw88/bf.c @@ -137,7 +137,8 @@ void rtw_bf_cfg_sounding(struct rtw_dev *rtwdev, struct rtw_vif *vif, rtw_write8_mask(rtwdev, REG_SND_PTCL_CTRL, BIT_MASK_BEAMFORM, RTW_SND_CTRL_SOUNDING); rtw_write8(rtwdev, REG_SND_PTCL_CTRL + 3, 0x26); - rtw_write8_clr(rtwdev, REG_RXFLTMAP1, BIT_RXFLTMAP1_BF_REPORT_POLL); + rtwdev->hal.rxfltmap1 &= ~BIT_RXFLTMAP1_BF_REPORT_POLL; + rtw_write16(rtwdev, REG_RXFLTMAP1, rtwdev->hal.rxfltmap1); rtw_write8_clr(rtwdev, REG_RXFLTMAP4, BIT_RXFLTMAP4_BF_REPORT_POLL); if (vif->net_type == RTW_NET_AP_MODE) @@ -269,7 +270,8 @@ void rtw_bf_enable_bfee_mu(struct rtw_dev *rtwdev, struct rtw_vif *vif, rtw_write16_set(rtwdev, REG_RXFLTMAP0, BIT_RXFLTMAP0_ACTIONNOACK); /* accept NDPA and BF report poll */ - rtw_write16_set(rtwdev, REG_RXFLTMAP1, BIT_RXFLTMAP1_BF); + rtwdev->hal.rxfltmap1 |= BIT_RXFLTMAP1_BF; + rtw_write16(rtwdev, REG_RXFLTMAP1, rtwdev->hal.rxfltmap1); } EXPORT_SYMBOL(rtw_bf_enable_bfee_mu); diff --git a/drivers/net/wireless/realtek/rtw88/mac80211.c b/drivers/net/wireless/realtek/rtw88/mac80211.c index 766f22d31079..b01b98d24b0a 100644 --- a/drivers/net/wireless/realtek/rtw88/mac80211.c +++ b/drivers/net/wireless/realtek/rtw88/mac80211.c @@ -281,12 +281,18 @@ static void rtw_ops_configure_filter(struct ieee80211_hw *hw, struct rtw_dev *rtwdev = hw->priv; *new_flags &= FIF_ALLMULTI | FIF_OTHER_BSS | FIF_FCSFAIL | - FIF_BCN_PRBRESP_PROMISC; + FIF_BCN_PRBRESP_PROMISC | FIF_CONTROL; mutex_lock(&rtwdev->mutex); rtw_leave_lps_deep(rtwdev); + if (changed_flags & FIF_CONTROL) { + if (*new_flags & FIF_CONTROL) + rtw_write16(rtwdev, REG_RXFLTMAP1, 0xffff); + else + rtw_write16(rtwdev, REG_RXFLTMAP1, rtwdev->hal.rxfltmap1); + } if (changed_flags & FIF_ALLMULTI) { if (*new_flags & FIF_ALLMULTI) rtwdev->hal.rcr |= BIT_AM; diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h index 9c0b746540b0..c6e981ba7986 100644 --- a/drivers/net/wireless/realtek/rtw88/main.h +++ b/drivers/net/wireless/realtek/rtw88/main.h @@ -1963,6 +1963,7 @@ struct rtw_sar { struct rtw_hal { u32 rcr; + u16 rxfltmap1; u32 chip_version; u8 cut_version; diff --git a/drivers/net/wireless/realtek/rtw88/rtw8723x.c b/drivers/net/wireless/realtek/rtw88/rtw8723x.c index 3f3e9b0c44e8..97e6f6a62c0d 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8723x.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8723x.c @@ -356,7 +356,8 @@ static int __rtw8723x_mac_init(struct rtw_dev *rtwdev) rtw_write32(rtwdev, REG_TCR, BIT_TCR_CFG); rtw_write16(rtwdev, REG_RXFLTMAP0, WLAN_RX_FILTER0); - rtw_write16(rtwdev, REG_RXFLTMAP1, WLAN_RX_FILTER1); + rtwdev->hal.rxfltmap1 = WLAN_RX_FILTER1; + rtw_write16(rtwdev, REG_RXFLTMAP1, rtwdev->hal.rxfltmap1); rtw_write16(rtwdev, REG_RXFLTMAP2, WLAN_RX_FILTER2); rtw_write32(rtwdev, REG_RCR, WLAN_RCR_CFG); diff --git a/drivers/net/wireless/realtek/rtw88/rtw8814a.c b/drivers/net/wireless/realtek/rtw88/rtw8814a.c index ca1079e12023..fa02aa299b5e 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8814a.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8814a.c @@ -400,7 +400,8 @@ static int rtw8814a_mac_init(struct rtw_dev *rtwdev) rtw_write16(rtwdev, REG_RETRY_LIMIT, 0x3030); rtw_write16(rtwdev, REG_RXFLTMAP0, 0xffff); - rtw_write16(rtwdev, REG_RXFLTMAP1, 0x0400); + rtwdev->hal.rxfltmap1 = 0x0400; + rtw_write16(rtwdev, REG_RXFLTMAP1, rtwdev->hal.rxfltmap1); rtw_write16(rtwdev, REG_RXFLTMAP2, 0xffff); rtw_write8(rtwdev, REG_MAX_AGGR_NUM, 0x36); diff --git a/drivers/net/wireless/realtek/rtw88/rtw8821c.c b/drivers/net/wireless/realtek/rtw88/rtw8821c.c index 246046da4f13..ee689fa3d87f 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8821c.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8821c.c @@ -248,7 +248,9 @@ static int rtw8821c_mac_init(struct rtw_dev *rtwdev) rtw_write8_clr(rtwdev, REG_TX_PTCL_CTRL + 1, BIT_SIFS_BK_EN >> 8); /* WMAC configuration */ - rtw_write32(rtwdev, REG_RXFLTMAP0, WLAN_RX_FILTER0); + rtw_write16(rtwdev, REG_RXFLTMAP0, WLAN_RX_FILTER0); + rtwdev->hal.rxfltmap1 = WLAN_RX_FILTER1; + rtw_write16(rtwdev, REG_RXFLTMAP1, rtwdev->hal.rxfltmap1); rtw_write16(rtwdev, REG_RXFLTMAP2, WLAN_RX_FILTER2); rtw_write32(rtwdev, REG_RCR, WLAN_RCR_CFG); rtw_write8(rtwdev, REG_RX_PKT_LIMIT, WLAN_RXPKT_MAX_SZ_512); diff --git a/drivers/net/wireless/realtek/rtw88/rtw8821c.h b/drivers/net/wireless/realtek/rtw88/rtw8821c.h index 954e93c8020d..ea85b7e73050 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8821c.h +++ b/drivers/net/wireless/realtek/rtw88/rtw8821c.h @@ -139,7 +139,8 @@ extern const struct rtw_chip_info rtw8821c_hw_spec; #define WLAN_DRV_EARLY_INT 0x04 #define WLAN_BCN_DMA_TIME 0x02 -#define WLAN_RX_FILTER0 0x0FFFFFFF +#define WLAN_RX_FILTER0 0xFFFF +#define WLAN_RX_FILTER1 0x0FFF #define WLAN_RX_FILTER2 0xFFFF #define WLAN_RCR_CFG 0xE400220E #define WLAN_RXPKT_MAX_SZ 12288 diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822b.c b/drivers/net/wireless/realtek/rtw88/rtw8822b.c index 37b7a520fea0..a56c8befa077 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8822b.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8822b.c @@ -202,7 +202,8 @@ static void rtw8822b_phy_set_param(struct rtw_dev *rtwdev) #define WLAN_DRV_EARLY_INT 0x04 #define WLAN_BCN_DMA_TIME 0x02 -#define WLAN_RX_FILTER0 0x0FFFFFFF +#define WLAN_RX_FILTER0 0xFFFF +#define WLAN_RX_FILTER1 0x0FFF #define WLAN_RX_FILTER2 0xFFFF #define WLAN_RCR_CFG 0xE400220E #define WLAN_RXPKT_MAX_SZ 12288 @@ -273,7 +274,9 @@ static int rtw8822b_mac_init(struct rtw_dev *rtwdev) rtw_write8(rtwdev, REG_BCNDMATIM, WLAN_BCN_DMA_TIME); rtw_write8_clr(rtwdev, REG_TX_PTCL_CTRL + 1, BIT_SIFS_BK_EN >> 8); /* WMAC configuration */ - rtw_write32(rtwdev, REG_RXFLTMAP0, WLAN_RX_FILTER0); + rtw_write16(rtwdev, REG_RXFLTMAP0, WLAN_RX_FILTER0); + rtwdev->hal.rxfltmap1 = WLAN_RX_FILTER1; + rtw_write16(rtwdev, REG_RXFLTMAP1, rtwdev->hal.rxfltmap1); rtw_write16(rtwdev, REG_RXFLTMAP2, WLAN_RX_FILTER2); rtw_write32(rtwdev, REG_RCR, WLAN_RCR_CFG); rtw_write8(rtwdev, REG_RX_PKT_LIMIT, WLAN_RXPKT_MAX_SZ_512); diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822c.c b/drivers/net/wireless/realtek/rtw88/rtw8822c.c index c3220c98f08a..32d9709f2c24 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8822c.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8822c.c @@ -1942,7 +1942,8 @@ static void rtw8822c_phy_set_param(struct rtw_dev *rtwdev) #define WLAN_EDCA_BE_PARAM 0x005EA42B #define WLAN_EDCA_BK_PARAM 0x0000A44F -#define WLAN_RX_FILTER0 0xFFFFFFFF +#define WLAN_RX_FILTER0 0xFFFF +#define WLAN_RX_FILTER1 0xFFFF #define WLAN_RX_FILTER2 0xFFFF #define WLAN_RCR_CFG 0xE400220E #define WLAN_RXPKT_MAX_SZ 12288 @@ -2093,7 +2094,9 @@ static int rtw8822c_mac_init(struct rtw_dev *rtwdev) rtw_write16(rtwdev, REG_EIFS, WLAN_EIFS_DUR_TUNE); rtw_write8(rtwdev, REG_NAV_CTRL + 2, WLAN_NAV_MAX); rtw_write8(rtwdev, REG_WMAC_TRXPTCL_CTL_H + 2, WLAN_BAR_ACK_TYPE); - rtw_write32(rtwdev, REG_RXFLTMAP0, WLAN_RX_FILTER0); + rtw_write16(rtwdev, REG_RXFLTMAP0, WLAN_RX_FILTER0); + rtwdev->hal.rxfltmap1 = WLAN_RX_FILTER1; + rtw_write16(rtwdev, REG_RXFLTMAP1, rtwdev->hal.rxfltmap1); rtw_write16(rtwdev, REG_RXFLTMAP2, WLAN_RX_FILTER2); rtw_write32(rtwdev, REG_RCR, WLAN_RCR_CFG); rtw_write8(rtwdev, REG_RX_PKT_LIMIT, WLAN_RXPKT_MAX_SZ_512); diff --git a/drivers/net/wireless/realtek/rtw88/rtw88xxa.c b/drivers/net/wireless/realtek/rtw88/rtw88xxa.c index 0fa943271fb6..2eaadcfec4cb 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw88xxa.c +++ b/drivers/net/wireless/realtek/rtw88/rtw88xxa.c @@ -512,7 +512,8 @@ static int rtw88xxau_init_queue_priority(struct rtw_dev *rtwdev) static void rtw88xxa_init_wmac_setting(struct rtw_dev *rtwdev) { rtw_write16(rtwdev, REG_RXFLTMAP0, 0xffff); - rtw_write16(rtwdev, REG_RXFLTMAP1, 0x0400); + rtwdev->hal.rxfltmap1 = 0x0400; + rtw_write16(rtwdev, REG_RXFLTMAP1, rtwdev->hal.rxfltmap1); rtw_write16(rtwdev, REG_RXFLTMAP2, 0xffff); rtw_write32(rtwdev, REG_MAR, 0xffffffff); From 04e91bb237a438eb562c686b5aa85445ee3df7cd Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Wed, 24 Jun 2026 11:39:32 +0800 Subject: [PATCH 0135/1433] wifi: rtw89: coex: force to exit Wi-Fi LPS while Bluetooth profile exist Wi-Fi can not reach LPS leave threshold while Wi-Fi only throughput not good & Bluetooth share bandwidth. Add logic to let force leave Wi-Fi LPS while Bluetooth profile exist. Update COEX version to 9.0.1. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260624033941.45918-2-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 87 +++++++++++++++++------ drivers/net/wireless/realtek/rtw89/core.h | 4 ++ 2 files changed, 69 insertions(+), 22 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 0f7ae572ef91..73f271a7ae7a 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -11,7 +11,7 @@ #include "ps.h" #include "reg.h" -#define RTW89_COEX_VERSION 0x09000013 +#define RTW89_COEX_VERSION 0x09000113 #define FCXDEF_STEP 50 /* MUST <= FCXMAX_STEP and match with wl fw*/ #define BTC_E2G_LIMIT_DEF 80 @@ -434,6 +434,8 @@ enum btc_b2w_scoreboard { BTC_BSCB_WLRFK = BIT(11), BTC_BSCB_BT_HILNA = BIT(13), BTC_BSCB_BT_CONNECT = BIT(16), + BTC_BSCB_PAN_ACT = BIT(28), + BTC_BSCB_HFP_ACT = BIT(29), BTC_BSCB_PATCH_CODE = BIT(30), BTC_BSCB_ALL = GENMASK(30, 0), }; @@ -2747,7 +2749,9 @@ static void _fw_set_policy(struct rtw89_dev *rtwdev, u16 policy_type, dm->tdma.rxflctrl == CXFLC_QOSNULL) btc->lps = 1; else - btc->lps = 0; + btc->lps = dm->lps_ctrl_scbd; + + dm->lps_ctrl_scbd_last = dm->lps_ctrl_scbd; if (btc->lps == 1) rtw89_set_coex_ctrl_lps(rtwdev, btc->lps); @@ -4434,7 +4438,7 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) if (dm->leak_ap && dm->tdma.leak_n > 1) _tdma_set_lek(btc, 1); - if (dm->tdma_instant_excute) { + if (dm->tdma_instant_excute || dm->lps_ctrl_change) { btc->dm.tdma.option_ctrl |= BIT(0); btc->update_policy_force = true; } @@ -5638,7 +5642,8 @@ static void _action_common(struct rtw89_dev *rtwdev) _fw_set_drv_info(rtwdev, CXDRVINFO_OSI); } } - btc->dm.tdma_instant_excute = 0; + dm->tdma_instant_excute = 0; + dm->lps_ctrl_change = false; wl->pta_reg_mac_chg = false; } @@ -7384,11 +7389,15 @@ void rtw89_coex_rfk_chk_work(struct wiphy *wiphy, struct wiphy_work *work) static void _update_bt_scbd(struct rtw89_dev *rtwdev, bool only_update) { const struct rtw89_chip_info *chip = rtwdev->chip; + const struct rtw89_btc_ver *ver = rtwdev->btc.ver; struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_bt_info *bt = &btc->cx.bt; - u32 val; - bool status_change = false; + struct rtw89_btc_wl_info *wl = &btc->cx.wl; + struct rtw89_btc_dm *dm = &rtwdev->btc.dm; + bool bt_link_change = false, lps_ctrl = false; + u32 val, any_bt_connect; + u8 mode; if (!chip->scbd) return; @@ -7403,13 +7412,26 @@ static void _update_bt_scbd(struct rtw89_dev *rtwdev, bool only_update) return; } + if (ver->fwlrole == 0) + mode = wl->role_info.link_mode; + else if (ver->fwlrole == 1) + mode = wl->role_info_v1.link_mode; + else if (ver->fwlrole == 2) + mode = wl->role_info_v2.link_mode; + else if (ver->fwlrole == 7) + mode = wl->role_info_v7.link_mode; + else if (ver->fwlrole == 8) + mode = wl->role_info_v8.link_mode; + else + return; + if (!(val & BTC_BSCB_ON)) bt->enable.now = 0; else bt->enable.now = 1; if (bt->enable.now != bt->enable.last) - status_change = true; + bt_link_change = true; /* reset bt info if bt re-enable */ if (bt->enable.now && !bt->enable.last) { @@ -7423,29 +7445,52 @@ static void _update_bt_scbd(struct rtw89_dev *rtwdev, bool only_update) bt->mbx_avl = !!(val & BTC_BSCB_ACT); if (bt->whql_test != !!(val & BTC_BSCB_WHQL)) - status_change = true; + bt_link_change = true; bt->whql_test = !!(val & BTC_BSCB_WHQL); bt->btg_type = val & BTC_BSCB_BT_S1 ? BTC_BT_BTG : BTC_BT_ALONE; bt->link_info.a2dp_desc.exist = !!(val & BTC_BSCB_A2DP_ACT); + bt->link_info.pan_desc.exist = !!(val & BTC_BSCB_PAN_ACT); + bt->link_info.hfp_desc.exist = !!(val & BTC_BSCB_HFP_ACT); bt->lna_constrain = !!(val & BTC_BSCB_BT_LNAB0) + !!(val & BTC_BSCB_BT_LNAB1) * 2 + 4; /* if rfk run 1->0 */ if (bt->rfk_info.map.run && !(val & BTC_BSCB_RFK_RUN)) - status_change = true; + bt_link_change = true; bt->rfk_info.map.run = !!(val & BTC_BSCB_RFK_RUN); bt->rfk_info.map.req = !!(val & BTC_BSCB_RFK_REQ); bt->hi_lna_rx = !!(val & BTC_BSCB_BT_HILNA); - bt->link_info.status.map.connect = !!(val & BTC_BSCB_BT_CONNECT); - if (bt->run_patch_code != !!(val & BTC_BSCB_PATCH_CODE)) - status_change = true; + any_bt_connect = !!(val & BTC_BSCB_BT_CONNECT); + + /* if connect change */ + if (bt->link_info.status.map.connect != any_bt_connect) + bt_link_change = true; + + /* if specific profile exist */ + if (((bt->link_info.a2dp_desc.exist || bt->link_info.pan_desc.exist || + bt->link_info.hfp_desc.exist) && mode == BTC_WLINK_2G_STA) || + bt->whql_test) + lps_ctrl = true; + + if (dm->lps_ctrl_scbd != lps_ctrl) { + dm->lps_ctrl_scbd = lps_ctrl; + bt_link_change = true; + dm->lps_ctrl_change = true; + } else { + dm->lps_ctrl_change = false; + } + + bt->link_info.status.map.connect = any_bt_connect; bt->run_patch_code = !!(val & BTC_BSCB_PATCH_CODE); - if (!only_update && status_change) - _run_coex(rtwdev, BTC_RSN_UPDATE_BT_SCBD); + if (bt_link_change) { + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s: bt status change!!\n", __func__); + /* TODO: Need to notify driver to update EXT-CTRL-BT-SLOT */ + } } #define BTC_BTINFO_PWR_LEN 5 @@ -7560,7 +7605,8 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) } if (wl->status.map.rf_off_pre == wl->status.map.rf_off && - wl->status.map.lps_pre == wl->status.map.lps) { + wl->status.map.lps_pre == wl->status.map.lps && + !dm->lps_ctrl_change && !dm->lps_ctrl_scbd) { if (reason == BTC_RSN_NTFY_POWEROFF || reason == BTC_RSN_NTFY_RADIO_STATE) { rtw89_debug(rtwdev, RTW89_DBG_BTC, @@ -7577,6 +7623,9 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) } } + if (reason == BTC_RSN_NTFY_INIT || reason == BTC_RSN_NTFY_RADIO_STATE) + _update_bt_scbd(rtwdev, false); + dm->freerun = false; dm->cnt_dm[BTC_DCNT_RUN]++; dm->fddt_train = BTC_FDDT_DISABLE; @@ -7597,7 +7646,7 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) goto exit; } - if (wl->status.map.rf_off || wl->status.map.lps || dm->bt_only) { + if (wl->status.map.rf_off || dm->bt_only || wl->status.map.lps) { _action_wl_off(rtwdev, mode); igno_bt = true; goto exit; @@ -7781,7 +7830,6 @@ void rtw89_btc_ntfy_init(struct rtw89_dev *rtwdev, u8 mode) _write_scbd(rtwdev, BTC_WSCB_ACTIVE | BTC_WSCB_ON | BTC_WSCB_BTLOG, true); - _update_bt_scbd(rtwdev, true); if (rtw89_mac_get_ctrl_path(rtwdev)) { rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): PTA owner warning!!\n", @@ -8339,7 +8387,6 @@ void rtw89_btc_ntfy_radio_state(struct rtw89_dev *rtwdev, enum btc_rfctrl rf_sta rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_MREG, true); val = BTC_WSCB_ACTIVE | BTC_WSCB_ON | BTC_WSCB_BTLOG; _write_scbd(rtwdev, val, true); - _update_bt_scbd(rtwdev, true); chip->ops->btc_init_cfg(rtwdev); } else { rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_ALL, false); @@ -8349,10 +8396,6 @@ void rtw89_btc_ntfy_radio_state(struct rtw89_dev *rtwdev, enum btc_rfctrl rf_sta _write_scbd(rtwdev, BTC_WSCB_ALL, false); else _write_scbd(rtwdev, BTC_WSCB_ACTIVE, false); - - if (rf_state == BTC_RFCTRL_LPS_WL_ON && - wl->status.map.lps_pre != BTC_LPS_OFF) - _update_bt_scbd(rtwdev, true); } btc->dm.cnt_dm[BTC_DCNT_BTCNT_HANG] = 0; diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 4bf6fe9d3880..920c97df4556 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -3153,6 +3153,10 @@ struct rtw89_btc_dm { u8 wl_pre_agc_rb: 2; u8 bt_select: 2; /* 0:s0, 1:s1, 2:s0 & s1, refer to enum btc_bt_index */ u8 slot_req_more: 1; + u8 lps_ctrl_scbd: 1; + + u8 lps_ctrl_scbd_last: 1; + u8 lps_ctrl_change: 1; }; struct rtw89_btc_ctrl { From ce9b6ca8f4ba8cfc111ced2c85fd749f49e95df8 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Wed, 24 Jun 2026 11:39:33 +0800 Subject: [PATCH 0136/1433] wifi: rtw89: coex: offset current BT info to BT0 for dual BT configuration In order to compatible with single/dual Bluetooth structure in one branch, offset the currently using BT info structure to BT-0. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260624033941.45918-3-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 130 +++++++++++----------- drivers/net/wireless/realtek/rtw89/core.h | 3 +- 2 files changed, 67 insertions(+), 66 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 73f271a7ae7a..f857ba247c23 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -924,7 +924,7 @@ static void _reset_btc_var(struct rtw89_dev *rtwdev, u8 type) const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; struct rtw89_btc_wl_link_info *wl_linfo; u8 i; @@ -1106,7 +1106,7 @@ static void _chk_btc_err(struct rtw89_dev *rtwdev, u8 type, u32 cnt) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_cx *cx = &btc->cx; - struct rtw89_btc_bt_info *bt = &cx->bt; + struct rtw89_btc_bt_info *bt = &cx->bt0; struct rtw89_btc_wl_info *wl = &cx->wl; struct rtw89_btc_dm *dm = &btc->dm; @@ -1276,7 +1276,7 @@ static void _update_bt_report(struct rtw89_dev *rtwdev, u8 rpt_type, u8 *pfinfo) { struct rtw89_btc *btc = &rtwdev->btc; const struct rtw89_btc_ver *ver = btc->ver; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; struct rtw89_btc_bt_a2dp_desc *a2dp = &bt_linfo->a2dp_desc; union rtw89_btc_fbtc_btver *pver = &btc->fwinfo.rpt_fbtc_btver.finfo; @@ -1417,7 +1417,7 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; union rtw89_btc_fbtc_rpt_ctrl_ver_info *prpt = NULL; union rtw89_btc_fbtc_cysta_info *pcysta = NULL; struct rtw89_btc_prpt *btc_prpt = NULL; @@ -3079,7 +3079,7 @@ static void _set_wl_rx_gain(struct rtw89_dev *rtwdev, u32 level) static void _set_bt_tx_power(struct rtw89_dev *rtwdev, u8 level) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; int ret; u8 buf; @@ -3106,7 +3106,7 @@ static void _set_bt_tx_power(struct rtw89_dev *rtwdev, u8 level) static void _set_bt_rx_gain(struct rtw89_dev *rtwdev, u8 level) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; if (btc->cx.cnt_bt[BTC_BCNT_INFOUPDATE] == 0) return; @@ -3138,7 +3138,7 @@ static void _set_rf_trx_para(struct rtw89_dev *rtwdev) const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_bt_link_info *b = &bt->link_info; struct rtw89_btc_wl_smap *wl_smap = &wl->status.map; struct rtw89_btc_rf_trx_para para; @@ -3224,7 +3224,7 @@ static void _update_btc_state_map(struct rtw89_dev *rtwdev) struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &cx->wl; - struct rtw89_btc_bt_info *bt = &cx->bt; + struct rtw89_btc_bt_info *bt = &cx->bt0; struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; if (wl->status.map.connecting || wl->status.map._4way || @@ -3251,7 +3251,7 @@ static void _set_bt_afh_info_v0(struct rtw89_dev *rtwdev) struct rtw89_btc *btc = &rtwdev->btc; const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_bt_link_info *b = &bt->link_info; struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; struct rtw89_btc_wl_role_info_v1 *wl_rinfo_v1 = &wl->role_info_v1; @@ -3421,7 +3421,7 @@ static void _set_bt_afh_info_v1(struct rtw89_dev *rtwdev) struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_wl_role_info_v8 *wl_rinfo = &wl->role_info_v8; struct rtw89_btc_wl_afh_info *wl_afh = &wl->afh_info; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_wl_rlink *rlink; u8 en = 0, ch = 0, bw = 0, buf[3] = {}; u8 i, j, link_mode; @@ -3527,7 +3527,7 @@ static bool _check_freerun(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; struct rtw89_btc_wl_role_info_v1 *wl_rinfo_v1 = &wl->role_info_v1; struct rtw89_btc_wl_role_info_v2 *wl_rinfo_v2 = &wl->role_info_v2; @@ -4000,9 +4000,9 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_fbtc_tdma *t = &dm->tdma; struct rtw89_btc_wl_role_info_v1 *wl_rinfo = &btc->cx.wl.role_info_v1; - struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt.link_info.a2dp_desc; - struct rtw89_btc_bt_hid_desc *hid = &btc->cx.bt.link_info.hid_desc; - struct rtw89_btc_bt_hfp_desc *hfp = &btc->cx.bt.link_info.hfp_desc; + struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt0.link_info.a2dp_desc; + struct rtw89_btc_bt_hid_desc *hid = &btc->cx.bt0.link_info.hid_desc; + struct rtw89_btc_bt_hfp_desc *hfp = &btc->cx.bt0.link_info.hfp_desc; struct rtw89_btc_wl_info *wl = &btc->cx.wl; u8 type, null_role; u32 tbl_w1, tbl_b1, tbl_b4; @@ -4483,7 +4483,7 @@ static void _set_ant_v0(struct rtw89_dev *rtwdev, bool force_exec, struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &cx->bt; + struct rtw89_btc_bt_info *bt = &cx->bt0; struct rtw89_btc_wl_dbcc_info *wl_dinfo = &wl->dbcc_info; u8 gnt_wl_ctrl, gnt_bt_ctrl, plt_ctrl, i, b2g = 0; bool dbcc_chg = false; @@ -4611,7 +4611,7 @@ static void _set_ant_v1(struct rtw89_dev *rtwdev, bool force_exec, { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_wl_role_info_v8 *wl_rinfo = &wl->role_info_v8; u32 ant_path_type = rtw89_get_antpath_type(phy_map, type); struct rtw89_btc_wl_dbcc_info *wl_dinfo = &wl->dbcc_info; @@ -4750,7 +4750,7 @@ static void _action_wl_off(struct rtw89_dev *rtwdev, u8 mode) if (mode == BTC_WLINK_5G) { _set_policy(rtwdev, BTC_CXP_OFF_EQ0, BTC_ACT_WL_OFF); } else if (wl->status.map.lps == BTC_LPS_RF_ON) { - if (btc->cx.bt.link_info.a2dp_desc.active) + if (btc->cx.bt0.link_info.a2dp_desc.active) _set_policy(rtwdev, BTC_CXP_OFF_BT, BTC_ACT_WL_OFF); else _set_policy(rtwdev, BTC_CXP_OFF_BWB1, BTC_ACT_WL_OFF); @@ -4790,7 +4790,7 @@ static void _action_bt_off(struct rtw89_dev *rtwdev) static void _action_bt_idle(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_link_info *b = &btc->cx.bt.link_info; + struct rtw89_btc_bt_link_info *b = &btc->cx.bt0.link_info; struct rtw89_btc_wl_info *wl = &btc->cx.wl; _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); @@ -4838,7 +4838,7 @@ static void _action_bt_hfp(struct rtw89_dev *rtwdev) if (btc->cx.wl.status.map._4way) { _set_policy(rtwdev, BTC_CXP_OFF_WL, BTC_ACT_BT_HFP); } else if (wl->status.map.traffic_dir & BIT(RTW89_TFC_UL)) { - btc->cx.bt.scan_rx_low_pri = true; + btc->cx.bt0.scan_rx_low_pri = true; _set_policy(rtwdev, BTC_CXP_OFF_BWB2, BTC_ACT_BT_HFP); } else { _set_policy(rtwdev, BTC_CXP_OFF_BWB1, BTC_ACT_BT_HFP); @@ -4858,7 +4858,7 @@ static void _action_bt_hid(struct rtw89_dev *rtwdev) const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_bt_hid_desc *hid = &bt->link_info.hid_desc; u16 policy_type = BTC_CXP_OFF_BT; @@ -4868,7 +4868,7 @@ static void _action_bt_hid(struct rtw89_dev *rtwdev) if (wl->status.map._4way) { policy_type = BTC_CXP_OFF_WL; } else if (wl->status.map.traffic_dir & BIT(RTW89_TFC_UL)) { - btc->cx.bt.scan_rx_low_pri = true; + btc->cx.bt0.scan_rx_low_pri = true; if (hid->type & BTC_HID_BLE) policy_type = BTC_CXP_OFF_BWB0; else @@ -4960,7 +4960,7 @@ static void _action_bt_a2dpsink(struct rtw89_dev *rtwdev) static void _action_bt_pan(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_link_info *bt_linfo = &btc->cx.bt.link_info; + struct rtw89_btc_bt_link_info *bt_linfo = &btc->cx.bt0.link_info; struct rtw89_btc_bt_a2dp_desc a2dp = bt_linfo->a2dp_desc; struct rtw89_btc_bt_pan_desc pan = bt_linfo->pan_desc; @@ -5172,7 +5172,7 @@ static void _set_btg_ctrl(struct rtw89_dev *rtwdev) struct rtw89_btc_wl_role_info *wl_rinfo_v0 = &wl->role_info; const struct rtw89_chip_info *chip = rtwdev->chip; const struct rtw89_btc_ver *ver = btc->ver; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_dm *dm = &btc->dm; struct _wl_rinfo_now wl_rinfo; u32 is_btg = BTC_BTGCTRL_DISABLE; @@ -5249,13 +5249,13 @@ static void _set_wl_preagc_ctrl(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_fbtc_outsrc_set_info *o_info = &btc->dm.ost_info; - struct rtw89_btc_bt_link_info *bt_linfo = &btc->cx.bt.link_info; + struct rtw89_btc_bt_link_info *bt_linfo = &btc->cx.bt0.link_info; struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_wl_role_info_v2 *rinfo_v2 = &wl->role_info_v2; struct rtw89_btc_wl_role_info_v7 *rinfo_v7 = &wl->role_info_v7; struct rtw89_btc_wl_role_info_v8 *rinfo_v8 = &wl->role_info_v8; const struct rtw89_chip_info *chip = rtwdev->chip; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_dm *dm = &btc->dm; u8 is_preagc, val, link_mode, dbcc_2g_phy; u8 role_ver = rtwdev->btc.ver->fwlrole; @@ -5285,7 +5285,7 @@ static void _set_wl_preagc_ctrl(struct rtw89_dev *rtwdev) } else if (link_mode == BTC_WLINK_5G) { is_preagc = BTC_PREAGC_DISABLE; } else if (link_mode == BTC_WLINK_NOLINK || - btc->cx.bt.link_info.profile_cnt.now == 0) { + btc->cx.bt0.link_info.profile_cnt.now == 0) { is_preagc = BTC_PREAGC_DISABLE; } else if (dm->tdma_now.type != CXTDMA_OFF && !bt_linfo->hfp_desc.exist && @@ -5435,7 +5435,7 @@ static void _set_wl_tx_limit(struct rtw89_dev *rtwdev) struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_wl_info *wl = &cx->wl; - struct rtw89_btc_bt_info *bt = &cx->bt; + struct rtw89_btc_bt_info *bt = &cx->bt0; struct rtw89_btc_bt_link_info *b = &bt->link_info; struct rtw89_btc_bt_hfp_desc *hfp = &b->hfp_desc; struct rtw89_btc_bt_hid_desc *hid = &b->hid_desc; @@ -5522,7 +5522,7 @@ static void _set_bt_rx_agc(struct rtw89_dev *rtwdev) struct rtw89_btc_wl_role_info_v2 *wl_rinfo_v2 = &wl->role_info_v2; struct rtw89_btc_wl_role_info_v7 *wl_rinfo_v7 = &wl->role_info_v7; struct rtw89_btc_wl_role_info_v8 *wl_rinfo_v8 = &wl->role_info_v8; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; bool bt_hi_lna_rx = false; u8 mode; @@ -5551,7 +5551,7 @@ static void _set_bt_rx_agc(struct rtw89_dev *rtwdev) static void _set_bt_rx_scan_pri(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; _write_scbd(rtwdev, BTC_WSCB_RXSCAN_PRI, (bool)(!!bt->scan_rx_low_pri)); } @@ -5590,7 +5590,7 @@ static void _action_common(struct rtw89_dev *rtwdev) struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_wl_role_info_v8 *rinfo_v8 = &wl->role_info_v8; struct rtw89_btc_wl_smap *wl_smap = &wl->status.map; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_dm *dm = &btc->dm; u32 bt_rom_code_id, bt_fw_ver; @@ -5650,7 +5650,7 @@ static void _action_common(struct rtw89_dev *rtwdev) static void _action_by_bt(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; struct rtw89_btc_bt_hid_desc hid = bt_linfo->hid_desc; struct rtw89_btc_bt_a2dp_desc a2dp = bt_linfo->a2dp_desc; @@ -5747,7 +5747,7 @@ static void _action_wl_25g_mcc(struct rtw89_dev *rtwdev) policy_type = BTC_CXP_OFFE_WL; else if (btc->cx.wl.status.val & btc_scanning_map.val) policy_type = BTC_CXP_OFFE_2GBWMIXB; - else if (btc->cx.bt.link_info.status.map.connect == 0) + else if (btc->cx.bt0.link_info.status.map.connect == 0) policy_type = BTC_CXP_OFFE_2GISOB; else policy_type = BTC_CXP_OFFE_2GBWISOB; @@ -5791,7 +5791,7 @@ static void _action_wl_2g_mcc(struct rtw89_dev *rtwdev) _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); if (btc->ant_type == BTC_ANT_SHARED) { /* shared-antenna */ - if (btc->cx.bt.link_info.profile_cnt.now == 0) + if (btc->cx.bt0.link_info.profile_cnt.now == 0) _set_policy(rtwdev, BTC_CXP_OFFE_DEF2, BTC_ACT_WL_2G_MCC); else @@ -5809,7 +5809,7 @@ static void _action_wl_2g_scc(struct rtw89_dev *rtwdev) _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); if (btc->ant_type == BTC_ANT_SHARED) { /* shared-antenna */ - if (btc->cx.bt.link_info.profile_cnt.now == 0) + if (btc->cx.bt0.link_info.profile_cnt.now == 0) _set_policy(rtwdev, BTC_CXP_OFFE_DEF2, BTC_ACT_WL_2G_SCC); else @@ -5824,7 +5824,7 @@ static void _action_wl_2g_scc_v1(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_wl_role_info_v1 *wl_rinfo = &wl->role_info_v1; u16 policy_type = BTC_CXP_OFF_BT; @@ -5886,7 +5886,7 @@ static void _action_wl_2g_scc_v2(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_wl_role_info_v2 *rinfo_v2 = &wl->role_info_v2; struct rtw89_btc_wl_role_info_v7 *rinfo_v7 = &wl->role_info_v7; @@ -5959,7 +5959,7 @@ static void _action_wl_2g_scc_v8(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_dm *dm = &btc->dm; u16 policy_type = BTC_CXP_OFF_BT; @@ -5987,7 +5987,7 @@ static void _action_wl_2g_ap(struct rtw89_dev *rtwdev) _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); if (btc->ant_type == BTC_ANT_SHARED) { - if (btc->cx.bt.link_info.profile_cnt.now == 0) + if (btc->cx.bt0.link_info.profile_cnt.now == 0) _set_policy(rtwdev, BTC_CXP_OFFE_DEF2, BTC_ACT_WL_2G_AP); else @@ -6004,7 +6004,7 @@ static void _action_wl_2g_go(struct rtw89_dev *rtwdev) _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); if (btc->ant_type == BTC_ANT_SHARED) { /* shared-antenna */ - if (btc->cx.bt.link_info.profile_cnt.now == 0) + if (btc->cx.bt0.link_info.profile_cnt.now == 0) _set_policy(rtwdev, BTC_CXP_OFFE_DEF2, BTC_ACT_WL_2G_GO); else @@ -6035,7 +6035,7 @@ static void _action_wl_2g_nan(struct rtw89_dev *rtwdev) _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); if (btc->ant_type == BTC_ANT_SHARED) { /* shared-antenna */ - if (btc->cx.bt.link_info.profile_cnt.now == 0) + if (btc->cx.bt0.link_info.profile_cnt.now == 0) _set_policy(rtwdev, BTC_CXP_OFFE_DEF2, BTC_ACT_WL_2G_NAN); else @@ -7352,7 +7352,7 @@ void rtw89_coex_bt_devinfo_work(struct wiphy *wiphy, struct wiphy_work *work) coex_bt_devinfo_work.work); struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_dm *dm = &rtwdev->btc.dm; - struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt.link_info.a2dp_desc; + struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt0.link_info.a2dp_desc; lockdep_assert_wiphy(wiphy); @@ -7392,7 +7392,7 @@ static void _update_bt_scbd(struct rtw89_dev *rtwdev, bool only_update) const struct rtw89_btc_ver *ver = rtwdev->btc.ver; struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_cx *cx = &btc->cx; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_dm *dm = &rtwdev->btc.dm; bool bt_link_change = false, lps_ctrl = false; @@ -7496,7 +7496,7 @@ static void _update_bt_scbd(struct rtw89_dev *rtwdev, bool only_update) #define BTC_BTINFO_PWR_LEN 5 static void _update_bt_txpwr_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) { - struct rtw89_btc_bt_info *bt = &rtwdev->btc.cx.bt; + struct rtw89_btc_bt_info *bt = &rtwdev->btc.cx.bt0; struct rtw89_btc_bt_link_info *b = &bt->link_info; if (len != BTC_BTINFO_PWR_LEN) @@ -7516,7 +7516,7 @@ static bool _chk_wl_rfk_request(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_cx *cx = &btc->cx; - struct rtw89_btc_bt_info *bt = &cx->bt; + struct rtw89_btc_bt_info *bt = &cx->bt0; _update_bt_scbd(rtwdev, true); @@ -7540,7 +7540,7 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) struct rtw89_btc_dm *dm = &rtwdev->btc.dm; struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; struct rtw89_btc_wl_role_info_v1 *wl_rinfo_v1 = &wl->role_info_v1; struct rtw89_btc_wl_role_info_v2 *wl_rinfo_v2 = &wl->role_info_v2; @@ -7657,12 +7657,12 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) goto exit; } - if (!cx->bt.enable.now && !cx->other.type) { + if (!cx->bt0.enable.now && !cx->other.type) { _action_bt_off(rtwdev); goto exit; } - if (cx->bt.whql_test) { + if (cx->bt0.whql_test) { _action_bt_whql(rtwdev); goto exit; } @@ -7925,7 +7925,7 @@ void rtw89_btc_ntfy_specific_packet(struct rtw89_dev *rtwdev, struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &cx->wl; - struct rtw89_btc_bt_link_info *b = &cx->bt.link_info; + struct rtw89_btc_bt_link_info *b = &cx->bt0.link_info; struct rtw89_btc_bt_hfp_desc *hfp = &b->hfp_desc; struct rtw89_btc_bt_hid_desc *hid = &b->hid_desc; u32 cnt; @@ -8030,7 +8030,7 @@ static u8 _update_bt_rssi_level(struct rtw89_dev *rtwdev, u8 rssi) { const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; u8 *rssi_st, rssi_th, rssi_level = 0; u8 i; @@ -8080,7 +8080,7 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_cx *cx = &btc->cx; - struct rtw89_btc_bt_info *bt = &cx->bt; + struct rtw89_btc_bt_info *bt = &cx->bt0; struct rtw89_btc_bt_link_info *b = &bt->link_info; struct rtw89_btc_bt_hfp_desc *hfp = &b->hfp_desc; struct rtw89_btc_bt_hid_desc *hid = &b->hid_desc; @@ -8851,7 +8851,7 @@ static int _show_cx_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) struct rtw89_hal *hal = &rtwdev->hal; struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_wl_info *wl = &btc->cx.wl; u32 ver_main = 0, ver_sub = 0, ver_hotfix = 0, id_branch = 0; u8 cv, rfe, iso, ant_num, ant_single_pos; @@ -9050,7 +9050,7 @@ enum btc_bt_a2dp_type { static int _show_bt_profile_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_link_info *bt_linfo = &btc->cx.bt.link_info; + struct rtw89_btc_bt_link_info *bt_linfo = &btc->cx.bt0.link_info; struct rtw89_btc_bt_hfp_desc hfp = bt_linfo->hfp_desc; struct rtw89_btc_bt_hid_desc hid = bt_linfo->hid_desc; struct rtw89_btc_bt_a2dp_desc a2dp = bt_linfo->a2dp_desc; @@ -9108,7 +9108,7 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) struct rtw89_btc *btc = &rtwdev->btc; const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_cx *cx = &btc->cx; - struct rtw89_btc_bt_info *bt = &cx->bt; + struct rtw89_btc_bt_info *bt = &cx->bt0; struct rtw89_btc_wl_info *wl = &cx->wl; u32 ver_main = FIELD_GET(GENMASK(31, 24), wl->ver_info.fw_coex); struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; @@ -9631,7 +9631,7 @@ static int _show_dm_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; char *p = buf, *end = buf + bufsz; u8 igno_bt; @@ -9853,7 +9853,7 @@ static int _show_fbtc_cysta_v2(struct rtw89_dev *rtwdev, char *buf, size_t bufsz struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_btf_fwinfo *pfwinfo = &btc->fwinfo; struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt.link_info.a2dp_desc; + struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt0.link_info.a2dp_desc; struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; struct rtw89_btc_fbtc_cysta_v2 *pcysta_le32 = NULL; union rtw89_btc_fbtc_rxflct r; @@ -9984,7 +9984,7 @@ static int _show_fbtc_cysta_v2(struct rtw89_dev *rtwdev, char *buf, size_t bufsz static int _show_fbtc_cysta_v3(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt.link_info.a2dp_desc; + struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt0.link_info.a2dp_desc; struct rtw89_btc_btf_fwinfo *pfwinfo = &btc->fwinfo; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_fbtc_a2dp_trx_stat *a2dp_trx; @@ -10123,7 +10123,7 @@ static int _show_fbtc_cysta_v3(struct rtw89_dev *rtwdev, char *buf, size_t bufsz static int _show_fbtc_cysta_v4(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt.link_info.a2dp_desc; + struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt0.link_info.a2dp_desc; struct rtw89_btc_btf_fwinfo *pfwinfo = &btc->fwinfo; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_fbtc_a2dp_trx_stat_v4 *a2dp_trx; @@ -10262,7 +10262,7 @@ static int _show_fbtc_cysta_v4(struct rtw89_dev *rtwdev, char *buf, size_t bufsz static int _show_fbtc_cysta_v5(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt.link_info.a2dp_desc; + struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt0.link_info.a2dp_desc; struct rtw89_btc_btf_fwinfo *pfwinfo = &btc->fwinfo; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_fbtc_a2dp_trx_stat_v4 *a2dp_trx; @@ -10399,7 +10399,7 @@ static int _show_fbtc_cysta_v5(struct rtw89_dev *rtwdev, char *buf, size_t bufsz static int _show_fbtc_cysta_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) { - struct rtw89_btc_bt_info *bt = &rtwdev->btc.cx.bt; + struct rtw89_btc_bt_info *bt = &rtwdev->btc.cx.bt0; struct rtw89_btc_bt_a2dp_desc *a2dp = &bt->link_info.a2dp_desc; struct rtw89_btc_btf_fwinfo *pfwinfo = &rtwdev->btc.fwinfo; struct rtw89_btc_fbtc_cysta_v7 *pcysta = NULL; @@ -10898,7 +10898,7 @@ static int _show_mreg_v1(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) struct rtw89_btc_fbtc_mreg_val_v1 *pmreg = NULL; struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_mac_ax_coex_gnt gnt_cfg = {}; struct rtw89_mac_ax_gnt gnt; char *p = buf, *end = buf + bufsz; @@ -10983,7 +10983,7 @@ static int _show_mreg_v2(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) struct rtw89_btc_fbtc_mreg_val_v2 *pmreg = NULL; struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_mac_ax_coex_gnt gnt_cfg = {}; struct rtw89_mac_ax_gnt gnt; char *p = buf, *end = buf + bufsz; @@ -11068,7 +11068,7 @@ static int _show_mreg_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &cx->wl; - struct rtw89_btc_bt_info *bt = &cx->bt; + struct rtw89_btc_bt_info *bt = &cx->bt0; struct rtw89_mac_ax_gnt *gnt = NULL; struct rtw89_btc_dm *dm = &btc->dm; char *p = buf, *end = buf + bufsz; @@ -11146,7 +11146,7 @@ static int _show_summary_v1(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_wl_info *wl = &cx->wl; - struct rtw89_btc_bt_info *bt = &cx->bt; + struct rtw89_btc_bt_info *bt = &cx->bt0; u32 cnt_sum = 0, *cnt = btc->dm.cnt_notify; char *p = buf, *end = buf + bufsz; u8 i; @@ -11255,7 +11255,7 @@ static int _show_summary_v4(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_wl_info *wl = &cx->wl; - struct rtw89_btc_bt_info *bt = &cx->bt; + struct rtw89_btc_bt_info *bt = &cx->bt0; u32 cnt_sum = 0, *cnt = btc->dm.cnt_notify; char *p = buf, *end = buf + bufsz; u8 i; @@ -11929,7 +11929,7 @@ void rtw89_coex_recognize_ver(struct rtw89_dev *rtwdev) void rtw89_btc_ntfy_preserve_bt_time(struct rtw89_dev *rtwdev, u32 ms) { - struct rtw89_btc_bt_link_info *bt_linfo = &rtwdev->btc.cx.bt.link_info; + struct rtw89_btc_bt_link_info *bt_linfo = &rtwdev->btc.cx.bt0.link_info; struct rtw89_btc_bt_a2dp_desc a2dp = bt_linfo->a2dp_desc; if (test_bit(RTW89_FLAG_SER_HANDLING, rtwdev->flags)) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 920c97df4556..83f66cdb0435 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -2262,7 +2262,8 @@ struct rtw89_btc_rf_trx_para_v9 { struct rtw89_btc_cx { struct rtw89_btc_wl_info wl; - struct rtw89_btc_bt_info bt; + struct rtw89_btc_bt_info bt0; + struct rtw89_btc_bt_info bt1; struct rtw89_btc_3rdcx_info other; u32 state_map; u32 cnt_bt[BTC_BCNT_NUM]; From 6ca62c49a679eeaad08c39f76e2c2db7f917e0aa Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Wed, 24 Jun 2026 11:39:34 +0800 Subject: [PATCH 0137/1433] wifi: rtw89: coex: Move wifi related counters to wifi info Move wifi related counters to wifi main info, it is to facilitate the after modification for dual MAC wifi structure. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260624033941.45918-4-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 80 +++++++++++------------ drivers/net/wireless/realtek/rtw89/core.h | 2 +- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index f857ba247c23..e4662e7b74e0 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -3411,7 +3411,7 @@ static void _set_bt_afh_info_v0(struct rtw89_dev *rtwdev) rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): en=%d, ch=%d, bw=%d\n", __func__, en, ch, bw); - btc->cx.cnt_wl[BTC_WCNT_CH_UPDATE]++; + wl->wcnt[BTC_WCNT_CH_UPDATE]++; } static void _set_bt_afh_info_v1(struct rtw89_dev *rtwdev) @@ -3511,7 +3511,7 @@ static void _set_bt_afh_info_v1(struct rtw89_dev *rtwdev) "[BTC], %s(): en=%d, ch=%d, bw=%d\n", __func__, en, ch, bw); - btc->cx.cnt_wl[BTC_WCNT_CH_UPDATE]++; + wl->wcnt[BTC_WCNT_CH_UPDATE]++; } } @@ -5630,7 +5630,7 @@ static void _action_common(struct rtw89_dev *rtwdev) rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], write scbd: 0x%08x\n", wl->scbd); wl->scbd_change = false; - btc->cx.cnt_wl[BTC_WCNT_SCBDUPDATE]++; + wl->wcnt[BTC_WCNT_SCBDUPDATE]++; } if (btc->ver->fcxosi) { @@ -6877,7 +6877,7 @@ static void _update_wl_info_v7(struct rtw89_dev *rtwdev, u8 rid) if (wl_rinfo->dbcc_en != rtwdev->dbcc_en) { wl_rinfo->dbcc_chg = 1; wl_rinfo->dbcc_en = rtwdev->dbcc_en; - btc->cx.cnt_wl[BTC_WCNT_DBCC_CHG]++; + wl->wcnt[BTC_WCNT_DBCC_CHG]++; } if (rtwdev->dbcc_en) { @@ -7321,7 +7321,7 @@ static void _update_wl_info_v8(struct rtw89_dev *rtwdev, u8 role_id, u8 rlink_id if (wl_rinfo->dbcc_en != dbcc_en_ori) { wl->dbcc_chg = true; - btc->cx.cnt_wl[BTC_WCNT_DBCC_CHG]++; + wl->wcnt[BTC_WCNT_DBCC_CHG]++; } } @@ -7378,7 +7378,7 @@ void rtw89_coex_rfk_chk_work(struct wiphy *wiphy, struct wiphy_work *work) if (wl->rfk_info.state != BTC_WRFK_STOP) { rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): RFK timeout\n", __func__); - cx->cnt_wl[BTC_WCNT_RFK_TIMEOUT]++; + wl->wcnt[BTC_WCNT_RFK_TIMEOUT]++; dm->error.map.wl_rfk_timeout = true; wl->rfk_info.state = BTC_WRFK_STOP; _write_scbd(rtwdev, BTC_WSCB_WLRFK, false); @@ -7520,13 +7520,13 @@ static bool _chk_wl_rfk_request(struct rtw89_dev *rtwdev) _update_bt_scbd(rtwdev, true); - cx->cnt_wl[BTC_WCNT_RFK_REQ]++; + cx->wl.wcnt[BTC_WCNT_RFK_REQ]++; if ((bt->rfk_info.map.run || bt->rfk_info.map.req) && !bt->rfk_info.map.timeout) { - cx->cnt_wl[BTC_WCNT_RFK_REJECT]++; + cx->wl.wcnt[BTC_WCNT_RFK_REJECT]++; } else { - cx->cnt_wl[BTC_WCNT_RFK_GO]++; + cx->wl.wcnt[BTC_WCNT_RFK_GO]++; return true; } return false; @@ -7934,14 +7934,14 @@ void rtw89_btc_ntfy_specific_packet(struct rtw89_dev *rtwdev, switch (pkt_type) { case PACKET_DHCP: - cnt = ++cx->cnt_wl[BTC_WCNT_DHCP]; + cnt = ++wl->wcnt[BTC_WCNT_DHCP]; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): DHCP cnt=%d\n", __func__, cnt); wl->status.map.connecting = true; delay_work = true; break; case PACKET_EAPOL: - cnt = ++cx->cnt_wl[BTC_WCNT_EAPOL]; + cnt = ++wl->wcnt[BTC_WCNT_EAPOL]; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): EAPOL cnt=%d\n", __func__, cnt); wl->status.map._4way = true; @@ -7950,7 +7950,7 @@ void rtw89_btc_ntfy_specific_packet(struct rtw89_dev *rtwdev, delay /= 2; break; case PACKET_EAPOL_END: - cnt = ++cx->cnt_wl[BTC_WCNT_EAPOL]; + cnt = ++wl->wcnt[BTC_WCNT_EAPOL]; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): EAPOL_End cnt=%d\n", __func__, cnt); @@ -7958,7 +7958,7 @@ void rtw89_btc_ntfy_specific_packet(struct rtw89_dev *rtwdev, wiphy_delayed_work_cancel(rtwdev->hw->wiphy, &rtwdev->coex_act1_work); break; case PACKET_ARP: - cnt = ++cx->cnt_wl[BTC_WCNT_ARP]; + cnt = ++wl->wcnt[BTC_WCNT_ARP]; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): ARP cnt=%d\n", __func__, cnt); return; @@ -10913,7 +10913,7 @@ static int _show_mreg_v1(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, " %-15s : WL->BT:0x%08x(cnt:%d), BT->WL:0x%08x(total:%d, bt_update:%d)\n", "[scoreboard]", wl->scbd, - cx->cnt_wl[BTC_WCNT_SCBDUPDATE], + wl->wcnt[BTC_WCNT_SCBDUPDATE], bt->scbd, cx->cnt_bt[BTC_BCNT_SCBDREAD], cx->cnt_bt[BTC_BCNT_SCBDUPDATE]); @@ -10998,7 +10998,7 @@ static int _show_mreg_v2(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, " %-15s : WL->BT:0x%08x(cnt:%d), BT->WL:0x%08x(total:%d, bt_update:%d)\n", "[scoreboard]", wl->scbd, - cx->cnt_wl[BTC_WCNT_SCBDUPDATE], + wl->wcnt[BTC_WCNT_SCBDUPDATE], bt->scbd, cx->cnt_bt[BTC_BCNT_SCBDREAD], cx->cnt_bt[BTC_BCNT_SCBDUPDATE]); @@ -11083,7 +11083,7 @@ static int _show_mreg_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, "\n\r %-15s : WL->BT:0x%08x(cnt:%d), BT->WL:0x%08x(total:%d, bt_update:%d)", "[scoreboard]", wl->scbd, - cx->cnt_wl[BTC_WCNT_SCBDUPDATE], + wl->wcnt[BTC_WCNT_SCBDUPDATE], bt->scbd, cx->cnt_bt[BTC_BCNT_SCBDREAD], cx->cnt_bt[BTC_BCNT_SCBDUPDATE]); @@ -11189,10 +11189,10 @@ static int _show_summary_v1(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, " %-15s : wl_rfk[req:%d/go:%d/reject:%d/timeout:%d]", - "[RFK]", cx->cnt_wl[BTC_WCNT_RFK_REQ], - cx->cnt_wl[BTC_WCNT_RFK_GO], - cx->cnt_wl[BTC_WCNT_RFK_REJECT], - cx->cnt_wl[BTC_WCNT_RFK_TIMEOUT]); + "[RFK]", wl->wcnt[BTC_WCNT_RFK_REQ], + wl->wcnt[BTC_WCNT_RFK_GO], + wl->wcnt[BTC_WCNT_RFK_REJECT], + wl->wcnt[BTC_WCNT_RFK_TIMEOUT]); p += scnprintf(p, end - p, ", bt_rfk[req:%d/go:%d/reject:%d/timeout:%d/fail:%d]\n", @@ -11304,10 +11304,10 @@ static int _show_summary_v4(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, " %-15s : wl_rfk[req:%d/go:%d/reject:%d/timeout:%d]", - "[RFK]", cx->cnt_wl[BTC_WCNT_RFK_REQ], - cx->cnt_wl[BTC_WCNT_RFK_GO], - cx->cnt_wl[BTC_WCNT_RFK_REJECT], - cx->cnt_wl[BTC_WCNT_RFK_TIMEOUT]); + "[RFK]", wl->wcnt[BTC_WCNT_RFK_REQ], + wl->wcnt[BTC_WCNT_RFK_GO], + wl->wcnt[BTC_WCNT_RFK_REJECT], + wl->wcnt[BTC_WCNT_RFK_TIMEOUT]); p += scnprintf(p, end - p, ", bt_rfk[req:%d/go:%d/reject:%d/timeout:%d/fail:%d]\n", @@ -11418,10 +11418,10 @@ static int _show_summary_v5(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, " %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d]", - "[RFK/LPS]", cx->cnt_wl[BTC_WCNT_RFK_REQ], - cx->cnt_wl[BTC_WCNT_RFK_GO], - cx->cnt_wl[BTC_WCNT_RFK_REJECT], - cx->cnt_wl[BTC_WCNT_RFK_TIMEOUT]); + "[RFK/LPS]", wl->wcnt[BTC_WCNT_RFK_REQ], + wl->wcnt[BTC_WCNT_RFK_GO], + wl->wcnt[BTC_WCNT_RFK_REJECT], + wl->wcnt[BTC_WCNT_RFK_TIMEOUT]); p += scnprintf(p, end - p, ", bt_rfk[req:%d]", @@ -11539,10 +11539,10 @@ static int _show_summary_v105(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, " %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d]", - "[RFK/LPS]", cx->cnt_wl[BTC_WCNT_RFK_REQ], - cx->cnt_wl[BTC_WCNT_RFK_GO], - cx->cnt_wl[BTC_WCNT_RFK_REJECT], - cx->cnt_wl[BTC_WCNT_RFK_TIMEOUT]); + "[RFK/LPS]", wl->wcnt[BTC_WCNT_RFK_REQ], + wl->wcnt[BTC_WCNT_RFK_GO], + wl->wcnt[BTC_WCNT_RFK_REJECT], + wl->wcnt[BTC_WCNT_RFK_TIMEOUT]); p += scnprintf(p, end - p, ", bt_rfk[req:%d]", @@ -11664,10 +11664,10 @@ static int _show_summary_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, "\n\r %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]", - "[RFK/LPS]", cx->cnt_wl[BTC_WCNT_RFK_REQ], - cx->cnt_wl[BTC_WCNT_RFK_GO], - cx->cnt_wl[BTC_WCNT_RFK_REJECT], - cx->cnt_wl[BTC_WCNT_RFK_TIMEOUT], + "[RFK/LPS]", wl->wcnt[BTC_WCNT_RFK_REQ], + wl->wcnt[BTC_WCNT_RFK_GO], + wl->wcnt[BTC_WCNT_RFK_REJECT], + wl->wcnt[BTC_WCNT_RFK_TIMEOUT], wl->rfk_info.proc_time); p += scnprintf(p, end - p, ", bt_rfk[req:%d]", @@ -11777,10 +11777,10 @@ static int _show_summary_v8(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, "\n\r %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]", - "[RFK/LPS]", cx->cnt_wl[BTC_WCNT_RFK_REQ], - cx->cnt_wl[BTC_WCNT_RFK_GO], - cx->cnt_wl[BTC_WCNT_RFK_REJECT], - cx->cnt_wl[BTC_WCNT_RFK_TIMEOUT], + "[RFK/LPS]", wl->wcnt[BTC_WCNT_RFK_REQ], + wl->wcnt[BTC_WCNT_RFK_GO], + wl->wcnt[BTC_WCNT_RFK_REJECT], + wl->wcnt[BTC_WCNT_RFK_TIMEOUT], wl->rfk_info.proc_time); p += scnprintf(p, end - p, ", bt_rfk[req:%d]", diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 83f66cdb0435..5bff68f53cb5 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -2078,6 +2078,7 @@ struct rtw89_btc_wl_info { bool link_mode_chg; bool dbcc_chg; u32 scbd; + u32 wcnt[BTC_WCNT_NUM]; }; struct rtw89_btc_module { @@ -2267,7 +2268,6 @@ struct rtw89_btc_cx { struct rtw89_btc_3rdcx_info other; u32 state_map; u32 cnt_bt[BTC_BCNT_NUM]; - u32 cnt_wl[BTC_WCNT_NUM]; }; struct rtw89_btc_fbtc_tdma { From 257cdb2c6e380e30bf61ed4607b46765e60c747c Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Wed, 24 Jun 2026 11:39:35 +0800 Subject: [PATCH 0138/1433] wifi: rtw89: coex: Extend bt_slot_req for dual MAC wifi This variable is for asking driver occupied Bluetooth traffic slot while wifi is running at multi-port mode. Example like station + AP. The time slot is separated by wifi driver under these wifi modes. And to ensure Bluetooth performance, Coex will advice the Bluetooth slot length to driver. And each MAC is able to run multi-port mode, so extend the variable's index. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260624033941.45918-5-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 24 +++++++++++------------ drivers/net/wireless/realtek/rtw89/coex.h | 2 +- drivers/net/wireless/realtek/rtw89/core.h | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index e4662e7b74e0..659028edccfa 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -975,8 +975,8 @@ static void _reset_btc_var(struct rtw89_dev *rtwdev, u8 type) } btc->policy_len = 0; - btc->bt_req_len = 0; - + btc->bt_req_len[RTW89_PHY_0] = 0; + btc->bt_req_len[RTW89_PHY_1] = 0; btc->dm.coex_info_map = BTC_COEX_INFO_ALL; btc->dm.wl_tx_limit.tx_time = BTC_MAX_TX_TIME_DEF; btc->dm.wl_tx_limit.tx_retry = BTC_MAX_TX_RETRY_DEF; @@ -1994,10 +1994,10 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, /* Check diff time between real BT slot and EBT/E5G slot */ if (dm->tdma_now.type == CXTDMA_OFF && dm->tdma_now.ext_ctrl == CXECTL_EXT && - btc->bt_req_len != 0) { + btc->bt_req_len[RTW89_PHY_0] != 0) { bt_slot_real = le16_to_cpu(pcysta->v3.cycle_time.tavg[CXT_BT]); - if (btc->bt_req_len > bt_slot_real) { - diff_t = btc->bt_req_len - bt_slot_real; + if (btc->bt_req_len[RTW89_PHY_0] > bt_slot_real) { + diff_t = btc->bt_req_len[RTW89_PHY_0] - bt_slot_real; _chk_btc_err(rtwdev, BTC_DCNT_BT_SLOT_DRIFT, diff_t); } } @@ -2038,11 +2038,11 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, /* Check diff time between real BT slot and EBT/E5G slot */ if (dm->tdma_now.type == CXTDMA_OFF && dm->tdma_now.ext_ctrl == CXECTL_EXT && - btc->bt_req_len != 0) { + btc->bt_req_len[RTW89_PHY_0] != 0) { bt_slot_real = le16_to_cpu(pcysta->v4.cycle_time.tavg[CXT_BT]); - if (btc->bt_req_len > bt_slot_real) { - diff_t = btc->bt_req_len - bt_slot_real; + if (btc->bt_req_len[RTW89_PHY_0] > bt_slot_real) { + diff_t = btc->bt_req_len[RTW89_PHY_0] - bt_slot_real; _chk_btc_err(rtwdev, BTC_DCNT_BT_SLOT_DRIFT, diff_t); } } @@ -2083,7 +2083,7 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, _chk_btc_err(rtwdev, BTC_DCNT_WL_SLOT_DRIFT, diff_t); /* Check diff time between real BT slot and EBT/E5G slot */ - bt_slot_set = btc->bt_req_len; + bt_slot_set = btc->bt_req_len[RTW89_PHY_0]; bt_slot_real = le16_to_cpu(pcysta->v5.cycle_time.tavg[CXT_BT]); diff_t = 0; if (dm->tdma_now.type == CXTDMA_OFF && @@ -5861,7 +5861,7 @@ static void _action_wl_2g_scc_v1(struct rtw89_dev *rtwdev) dm->wl_scc.ebt_null = 0; policy_type = BTC_CXP_OFFE_2GISOB; } else if (bt->link_info.a2dp_desc.exist && - dur < btc->bt_req_len) { + dur < btc->bt_req_len[RTW89_PHY_0]) { dm->wl_scc.ebt_null = 1; /* tx null at EBT */ policy_type = BTC_CXP_OFFE_2GBWMIXB2; } else if (bt->link_info.a2dp_desc.exist || @@ -5934,7 +5934,7 @@ static void _action_wl_2g_scc_v2(struct rtw89_dev *rtwdev) dm->wl_scc.ebt_null = 0; policy_type = BTC_CXP_OFFE_2GISOB; } else if (bt->link_info.a2dp_desc.exist && - dur < btc->bt_req_len) { + dur < btc->bt_req_len[RTW89_PHY_0]) { dm->wl_scc.ebt_null = 1; /* tx null at EBT */ policy_type = BTC_CXP_OFFE_2GBWMIXB2; } else if (bt->link_info.a2dp_desc.exist || @@ -9694,7 +9694,7 @@ static int _show_dm_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) " %-15s : wl_tx_limit[en:%d/max_t:%dus/max_retry:%d], bt_slot_reg:%d-TU, bt_scan_rx_low_pri:%d\n", "[dm_ctrl]", dm->wl_tx_limit.enable, dm->wl_tx_limit.tx_time, - dm->wl_tx_limit.tx_retry, btc->bt_req_len, + dm->wl_tx_limit.tx_retry, btc->bt_req_len[RTW89_PHY_0], bt->scan_rx_low_pri); return p - buf; diff --git a/drivers/net/wireless/realtek/rtw89/coex.h b/drivers/net/wireless/realtek/rtw89/coex.h index ea2c1e5d70f5..6ac14611607c 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.h +++ b/drivers/net/wireless/realtek/rtw89/coex.h @@ -329,7 +329,7 @@ static inline u16 rtw89_coex_query_bt_req_len(struct rtw89_dev *rtwdev, { struct rtw89_btc *btc = &rtwdev->btc; - return btc->bt_req_len; + return btc->bt_req_len[phy_idx]; } static inline u32 rtw89_get_antpath_type(u8 phy_map, u8 type) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 5bff68f53cb5..0e3110aa867a 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -3390,7 +3390,7 @@ struct rtw89_btc { struct wiphy_work dhcp_notify_work; struct wiphy_work icmp_notify_work; - u32 bt_req_len; + u32 bt_req_len[RTW89_PHY_NUM]; u8 policy[RTW89_BTC_POLICY_MAXLEN]; u8 ant_type; From 77e219a25501a0c042d9337e538a4c1a7232697c Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Wed, 24 Jun 2026 11:39:36 +0800 Subject: [PATCH 0139/1433] wifi: rtw89: coex: Move Bluetooth related counters to BT info In order to support dual Bluetooth chip, move Bluetooth counters to BT info. Because the two Bluetooth need to collect their own counters. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260624033941.45918-6-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 163 +++++++++--------- drivers/net/wireless/realtek/rtw89/core.h | 3 +- drivers/net/wireless/realtek/rtw89/rtw8852a.c | 8 +- 3 files changed, 86 insertions(+), 88 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 659028edccfa..8fa51867055b 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -1218,7 +1218,7 @@ static void _chk_btc_err(struct rtw89_dev *rtwdev, u8 type, u32 cnt) dm->error.map.slot_no_sync = false; break; case BTC_DCNT_BTTX_HANG: - cnt = cx->cnt_bt[BTC_BCNT_LOPRI_TX]; + cnt = bt->bcnt[BTC_BCNT_LOPRI_TX]; if (cnt == 0 && bt->link_info.slave_role) dm->cnt_dm[BTC_DCNT_BTTX_HANG]++; @@ -1231,10 +1231,10 @@ static void _chk_btc_err(struct rtw89_dev *rtwdev, u8 type, u32 cnt) dm->error.map.bt_tx_hang = false; break; case BTC_DCNT_BTCNT_HANG: - cnt = cx->cnt_bt[BTC_BCNT_HIPRI_RX] + - cx->cnt_bt[BTC_BCNT_HIPRI_TX] + - cx->cnt_bt[BTC_BCNT_LOPRI_RX] + - cx->cnt_bt[BTC_BCNT_LOPRI_TX]; + cnt = bt->bcnt[BTC_BCNT_HIPRI_RX] + + bt->bcnt[BTC_BCNT_HIPRI_TX] + + bt->bcnt[BTC_BCNT_LOPRI_RX] + + bt->bcnt[BTC_BCNT_LOPRI_TX]; if (cnt == 0) dm->cnt_dm[BTC_DCNT_BTCNT_HANG]++; @@ -1723,7 +1723,7 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, rtwdev->chip->ops->btc_update_bt_cnt(rtwdev); _chk_btc_err(rtwdev, BTC_DCNT_BTCNT_HANG, 0); - btc->cx.cnt_bt[BTC_BCNT_POLUT] = + bt->bcnt[BTC_BCNT_POLUT] = rtw89_mac_get_plt_cnt(rtwdev, RTW89_MAC_0); } @@ -1738,15 +1738,15 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, memcpy(&dm->gnt.band[i], &prpt->v4.gnt_val[i], sizeof(dm->gnt.band[i])); - btc->cx.cnt_bt[BTC_BCNT_HIPRI_TX] = + bt->bcnt[BTC_BCNT_HIPRI_TX] = le32_to_cpu(prpt->v4.bt_cnt[BTC_BCNT_HI_TX]); - btc->cx.cnt_bt[BTC_BCNT_HIPRI_RX] = + bt->bcnt[BTC_BCNT_HIPRI_RX] = le32_to_cpu(prpt->v4.bt_cnt[BTC_BCNT_HI_RX]); - btc->cx.cnt_bt[BTC_BCNT_LOPRI_TX] = + bt->bcnt[BTC_BCNT_LOPRI_TX] = le32_to_cpu(prpt->v4.bt_cnt[BTC_BCNT_LO_TX]); - btc->cx.cnt_bt[BTC_BCNT_LOPRI_RX] = + bt->bcnt[BTC_BCNT_LOPRI_RX] = le32_to_cpu(prpt->v4.bt_cnt[BTC_BCNT_LO_RX]); - btc->cx.cnt_bt[BTC_BCNT_POLUT] = + bt->bcnt[BTC_BCNT_POLUT] = le32_to_cpu(prpt->v4.bt_cnt[BTC_BCNT_POLLUTED]); _chk_btc_err(rtwdev, BTC_DCNT_BTCNT_HANG, 0); @@ -1770,15 +1770,15 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, memcpy(&dm->gnt.band[i], &prpt->v5.gnt_val[i][0], sizeof(dm->gnt.band[i])); - btc->cx.cnt_bt[BTC_BCNT_HIPRI_TX] = + bt->bcnt[BTC_BCNT_HIPRI_TX] = le16_to_cpu(prpt->v5.bt_cnt[BTC_BCNT_HI_TX]); - btc->cx.cnt_bt[BTC_BCNT_HIPRI_RX] = + bt->bcnt[BTC_BCNT_HIPRI_RX] = le16_to_cpu(prpt->v5.bt_cnt[BTC_BCNT_HI_RX]); - btc->cx.cnt_bt[BTC_BCNT_LOPRI_TX] = + bt->bcnt[BTC_BCNT_LOPRI_TX] = le16_to_cpu(prpt->v5.bt_cnt[BTC_BCNT_LO_TX]); - btc->cx.cnt_bt[BTC_BCNT_LOPRI_RX] = + bt->bcnt[BTC_BCNT_LOPRI_RX] = le16_to_cpu(prpt->v5.bt_cnt[BTC_BCNT_LO_RX]); - btc->cx.cnt_bt[BTC_BCNT_POLUT] = + bt->bcnt[BTC_BCNT_POLUT] = le16_to_cpu(prpt->v5.bt_cnt[BTC_BCNT_POLLUTED]); _chk_btc_err(rtwdev, BTC_DCNT_BTCNT_HANG, 0); @@ -1797,15 +1797,15 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, memcpy(&dm->gnt.band[i], &prpt->v105.gnt_val[i][0], sizeof(dm->gnt.band[i])); - btc->cx.cnt_bt[BTC_BCNT_HIPRI_TX] = + bt->bcnt[BTC_BCNT_HIPRI_TX] = le16_to_cpu(prpt->v105.bt_cnt[BTC_BCNT_HI_TX_V105]); - btc->cx.cnt_bt[BTC_BCNT_HIPRI_RX] = + bt->bcnt[BTC_BCNT_HIPRI_RX] = le16_to_cpu(prpt->v105.bt_cnt[BTC_BCNT_HI_RX_V105]); - btc->cx.cnt_bt[BTC_BCNT_LOPRI_TX] = + bt->bcnt[BTC_BCNT_LOPRI_TX] = le16_to_cpu(prpt->v105.bt_cnt[BTC_BCNT_LO_TX_V105]); - btc->cx.cnt_bt[BTC_BCNT_LOPRI_RX] = + bt->bcnt[BTC_BCNT_LOPRI_RX] = le16_to_cpu(prpt->v105.bt_cnt[BTC_BCNT_LO_RX_V105]); - btc->cx.cnt_bt[BTC_BCNT_POLUT] = + bt->bcnt[BTC_BCNT_POLUT] = le16_to_cpu(prpt->v105.bt_cnt[BTC_BCNT_POLLUTED_V105]); _chk_btc_err(rtwdev, BTC_DCNT_BTCNT_HANG, 0); @@ -1823,21 +1823,21 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, memcpy(&dm->gnt.band[i], &prpt->v7.gnt_val[i][0], sizeof(dm->gnt.band[i])); - btc->cx.cnt_bt[BTC_BCNT_HIPRI_TX] = + bt->bcnt[BTC_BCNT_HIPRI_TX] = le16_to_cpu(prpt->v7.bt_cnt[BTC_BCNT_HI_TX_V105]); - btc->cx.cnt_bt[BTC_BCNT_HIPRI_RX] = + bt->bcnt[BTC_BCNT_HIPRI_RX] = le16_to_cpu(prpt->v7.bt_cnt[BTC_BCNT_HI_RX_V105]); - btc->cx.cnt_bt[BTC_BCNT_LOPRI_TX] = + bt->bcnt[BTC_BCNT_LOPRI_TX] = le16_to_cpu(prpt->v7.bt_cnt[BTC_BCNT_LO_TX_V105]); - btc->cx.cnt_bt[BTC_BCNT_LOPRI_RX] = + bt->bcnt[BTC_BCNT_LOPRI_RX] = le16_to_cpu(prpt->v7.bt_cnt[BTC_BCNT_LO_RX_V105]); val1 = le16_to_cpu(prpt->v7.bt_cnt[BTC_BCNT_POLLUTED_V105]); - if (val1 > btc->cx.cnt_bt[BTC_BCNT_POLUT_NOW]) - val1 -= btc->cx.cnt_bt[BTC_BCNT_POLUT_NOW]; /* diff */ + if (val1 > bt->bcnt[BTC_BCNT_POLUT_NOW]) + val1 -= bt->bcnt[BTC_BCNT_POLUT_NOW]; /* diff */ - btc->cx.cnt_bt[BTC_BCNT_POLUT_DIFF] = val1; - btc->cx.cnt_bt[BTC_BCNT_POLUT_NOW] = + bt->bcnt[BTC_BCNT_POLUT_DIFF] = val1; + bt->bcnt[BTC_BCNT_POLUT_NOW] = le16_to_cpu(prpt->v7.bt_cnt[BTC_BCNT_POLLUTED_V105]); val1 = pfwinfo->event[BTF_EVNT_RPT]; @@ -1855,21 +1855,21 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, memcpy(&dm->gnt.band[i], &prpt->v8.gnt_val[i][0], sizeof(dm->gnt.band[i])); - btc->cx.cnt_bt[BTC_BCNT_HIPRI_TX] = + bt->bcnt[BTC_BCNT_HIPRI_TX] = le16_to_cpu(prpt->v8.bt_cnt[BTC_BCNT_HI_TX_V105]); - btc->cx.cnt_bt[BTC_BCNT_HIPRI_RX] = + bt->bcnt[BTC_BCNT_HIPRI_RX] = le16_to_cpu(prpt->v8.bt_cnt[BTC_BCNT_HI_RX_V105]); - btc->cx.cnt_bt[BTC_BCNT_LOPRI_TX] = + bt->bcnt[BTC_BCNT_LOPRI_TX] = le16_to_cpu(prpt->v8.bt_cnt[BTC_BCNT_LO_TX_V105]); - btc->cx.cnt_bt[BTC_BCNT_LOPRI_RX] = + bt->bcnt[BTC_BCNT_LOPRI_RX] = le16_to_cpu(prpt->v8.bt_cnt[BTC_BCNT_LO_RX_V105]); val1 = le16_to_cpu(prpt->v8.bt_cnt[BTC_BCNT_POLLUTED_V105]); - if (val1 > btc->cx.cnt_bt[BTC_BCNT_POLUT_NOW]) - val1 -= btc->cx.cnt_bt[BTC_BCNT_POLUT_NOW]; /* diff */ + if (val1 > bt->bcnt[BTC_BCNT_POLUT_NOW]) + val1 -= bt->bcnt[BTC_BCNT_POLUT_NOW]; /* diff */ - btc->cx.cnt_bt[BTC_BCNT_POLUT_DIFF] = val1; - btc->cx.cnt_bt[BTC_BCNT_POLUT_NOW] = + bt->bcnt[BTC_BCNT_POLUT_DIFF] = val1; + bt->bcnt[BTC_BCNT_POLUT_NOW] = le16_to_cpu(prpt->v8.bt_cnt[BTC_BCNT_POLLUTED_V105]); val1 = pfwinfo->event[BTF_EVNT_RPT]; @@ -3083,7 +3083,7 @@ static void _set_bt_tx_power(struct rtw89_dev *rtwdev, u8 level) int ret; u8 buf; - if (btc->cx.cnt_bt[BTC_BCNT_INFOUPDATE] == 0) + if (bt->bcnt[BTC_BCNT_INFOUPDATE] == 0) return; if (bt->rf_para.tx_pwr_freerun == level) @@ -3108,7 +3108,7 @@ static void _set_bt_rx_gain(struct rtw89_dev *rtwdev, u8 level) struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_bt_info *bt = &btc->cx.bt0; - if (btc->cx.cnt_bt[BTC_BCNT_INFOUPDATE] == 0) + if (bt->bcnt[BTC_BCNT_INFOUPDATE] == 0) return; if ((bt->rf_para.rx_gain_freerun == level || @@ -6059,7 +6059,7 @@ static u32 _read_scbd(struct rtw89_dev *rtwdev) rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], read scbd: 0x%08x\n", scbd_val); - btc->cx.cnt_bt[BTC_BCNT_SCBDREAD]++; + btc->cx.bt0.bcnt[BTC_BCNT_SCBDREAD]++; return scbd_val; } @@ -7388,18 +7388,16 @@ void rtw89_coex_rfk_chk_work(struct wiphy *wiphy, struct wiphy_work *work) static void _update_bt_scbd(struct rtw89_dev *rtwdev, bool only_update) { - const struct rtw89_chip_info *chip = rtwdev->chip; - const struct rtw89_btc_ver *ver = rtwdev->btc.ver; struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_bt_info *bt = &btc->cx.bt0; + const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_dm *dm = &rtwdev->btc.dm; bool bt_link_change = false, lps_ctrl = false; u32 val, any_bt_connect; u8 mode; - if (!chip->scbd) + if (rtwdev->chip->scbd) return; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s\n", __func__); @@ -7436,7 +7434,7 @@ static void _update_bt_scbd(struct rtw89_dev *rtwdev, bool only_update) /* reset bt info if bt re-enable */ if (bt->enable.now && !bt->enable.last) { _reset_btc_var(rtwdev, BTC_RESET_BTINFO); - cx->cnt_bt[BTC_BCNT_REENABLE]++; + bt->bcnt[BTC_BCNT_REENABLE]++; bt->enable.now = 1; } @@ -8095,7 +8093,7 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): return by bt-info duplicate!!\n", __func__); - cx->cnt_bt[BTC_BCNT_INFOSAME]++; + bt->bcnt[BTC_BCNT_INFOSAME]++; return; } @@ -8116,7 +8114,7 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) b->status.map.acl_busy = btinfo.lb2.acl_busy; b->status.map.inq_pag = btinfo.lb2.inq_pag; bt->inq_pag.now = btinfo.lb2.inq_pag; - cx->cnt_bt[BTC_BCNT_INQPAG] += !!(bt->inq_pag.now && !bt->inq_pag.last); + bt->bcnt[BTC_BCNT_INQPAG] += !!(bt->inq_pag.now && !bt->inq_pag.last); hfp->exist = btinfo.lb2.hfp; b->profile_cnt.now += (u8)hfp->exist; @@ -8131,11 +8129,11 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) /* parse raw info low-Byte3 */ btinfo.val = bt->raw_info[BTC_BTINFO_L3]; if (btinfo.lb3.retry != 0) - cx->cnt_bt[BTC_BCNT_RETRY]++; + bt->bcnt[BTC_BCNT_RETRY]++; b->cqddr = btinfo.lb3.cqddr; - cx->cnt_bt[BTC_BCNT_INQ] += !!(btinfo.lb3.inq && !bt->inq); + bt->bcnt[BTC_BCNT_INQ] += !!(btinfo.lb3.inq && !bt->inq); bt->inq = btinfo.lb3.inq; - cx->cnt_bt[BTC_BCNT_PAGE] += !!(btinfo.lb3.pag && !bt->pag); + bt->bcnt[BTC_BCNT_PAGE] += !!(btinfo.lb3.pag && !bt->pag); bt->pag = btinfo.lb3.pag; b->status.map.mesh_busy = btinfo.lb3.mesh_busy; @@ -8158,11 +8156,11 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) hid->type |= BTC_HID_RCU; } - cx->cnt_bt[BTC_BCNT_REINIT] += !!(btinfo.hb1.reinit && !bt->reinit); + bt->bcnt[BTC_BCNT_REINIT] += !!(btinfo.hb1.reinit && !bt->reinit); bt->reinit = btinfo.hb1.reinit; - cx->cnt_bt[BTC_BCNT_RELINK] += !!(btinfo.hb1.relink && !b->relink.now); + bt->bcnt[BTC_BCNT_RELINK] += !!(btinfo.hb1.relink && !b->relink.now); b->relink.now = btinfo.hb1.relink; - cx->cnt_bt[BTC_BCNT_IGNOWL] += !!(btinfo.hb1.igno_wl && !bt->igno_wl); + bt->bcnt[BTC_BCNT_IGNOWL] += !!(btinfo.hb1.igno_wl && !bt->igno_wl); bt->igno_wl = btinfo.hb1.igno_wl; if (bt->igno_wl && !cx->wl.status.map.rf_off) @@ -8170,7 +8168,7 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) bt->ble_scan_en = btinfo.hb1.ble_scan; - cx->cnt_bt[BTC_BCNT_ROLESW] += !!(btinfo.hb1.role_sw && !b->role_sw); + bt->bcnt[BTC_BCNT_ROLESW] += !!(btinfo.hb1.role_sw && !b->role_sw); b->role_sw = btinfo.hb1.role_sw; b->multi_link.now = btinfo.hb1.multi_link; @@ -8179,7 +8177,7 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) btinfo.val = bt->raw_info[BTC_BTINFO_H2]; pan->active = !!btinfo.hb2.pan_active; - cx->cnt_bt[BTC_BCNT_AFH] += !!(btinfo.hb2.afh_update && !b->afh_update); + bt->bcnt[BTC_BCNT_AFH] += !!(btinfo.hb2.afh_update && !b->afh_update); b->afh_update = btinfo.hb2.afh_update; a2dp->active = btinfo.hb2.a2dp_active; b->slave_role = btinfo.hb2.slave; @@ -8193,7 +8191,7 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) a2dp->bitpool = btinfo.hb3.a2dp_bitpool; if (b->tx_3m != (u32)btinfo.hb3.tx_3m) - cx->cnt_bt[BTC_BCNT_RATECHG]++; + bt->bcnt[BTC_BCNT_RATECHG]++; b->tx_3m = (u32)btinfo.hb3.tx_3m; a2dp->sink = btinfo.hb3.a2dp_sink; @@ -8785,6 +8783,7 @@ void rtw89_btc_c2h_handle(struct rtw89_dev *rtwdev, struct sk_buff *skb, u32 len, u8 class, u8 func) { struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_bt_info *bt = &rtwdev->btc.cx.bt0; struct rtw89_btc_btf_fwinfo *pfwinfo = &btc->fwinfo; u8 *buf = &skb->data[RTW89_C2H_HEADER_LEN]; @@ -8812,13 +8811,13 @@ void rtw89_btc_c2h_handle(struct rtw89_dev *rtwdev, struct sk_buff *skb, case BTF_EVNT_BT_INFO: rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], handle C2H BT INFO with data %8ph\n", buf); - btc->cx.cnt_bt[BTC_BCNT_INFOUPDATE]++; + bt->bcnt[BTC_BCNT_INFOUPDATE]++; _update_bt_info(rtwdev, buf, len); break; case BTF_EVNT_BT_SCBD: rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], handle C2H BT SCBD with data %8ph\n", buf); - btc->cx.cnt_bt[BTC_BCNT_SCBDUPDATE]++; + bt->bcnt[BTC_BCNT_SCBDUPDATE]++; _update_bt_scbd(rtwdev, false); break; case BTF_EVNT_BT_PSD: @@ -8836,7 +8835,7 @@ void rtw89_btc_c2h_handle(struct rtw89_dev *rtwdev, struct sk_buff *skb, btc->dm.cnt_dm[BTC_DCNT_CX_RUNINFO]++; break; case BTF_EVNT_BT_QUERY_TXPWR: - btc->cx.cnt_bt[BTC_BCNT_BTTXPWR_UPDATE]++; + bt->bcnt[BTC_BCNT_BTTXPWR_UPDATE]++; _update_bt_txpwr_info(rtwdev, buf, len); } } @@ -9187,17 +9186,17 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, " %-15s : retry:%d, relink:%d, rate_chg:%d, reinit:%d, reenable:%d, ", - "[stat_cnt]", cx->cnt_bt[BTC_BCNT_RETRY], - cx->cnt_bt[BTC_BCNT_RELINK], - cx->cnt_bt[BTC_BCNT_RATECHG], - cx->cnt_bt[BTC_BCNT_REINIT], - cx->cnt_bt[BTC_BCNT_REENABLE]); + "[stat_cnt]", bt->bcnt[BTC_BCNT_RETRY], + bt->bcnt[BTC_BCNT_RELINK], + bt->bcnt[BTC_BCNT_RATECHG], + bt->bcnt[BTC_BCNT_REINIT], + bt->bcnt[BTC_BCNT_REENABLE]); p += scnprintf(p, end - p, "role-switch:%d, afh:%d, inq_page:%d(inq:%d/page:%d), igno_wl:%d\n", - cx->cnt_bt[BTC_BCNT_ROLESW], cx->cnt_bt[BTC_BCNT_AFH], - cx->cnt_bt[BTC_BCNT_INQPAG], cx->cnt_bt[BTC_BCNT_INQ], - cx->cnt_bt[BTC_BCNT_PAGE], cx->cnt_bt[BTC_BCNT_IGNOWL]); + bt->bcnt[BTC_BCNT_ROLESW], bt->bcnt[BTC_BCNT_AFH], + bt->bcnt[BTC_BCNT_INQPAG], bt->bcnt[BTC_BCNT_INQ], + bt->bcnt[BTC_BCNT_PAGE], bt->bcnt[BTC_BCNT_IGNOWL]); p += _show_bt_profile_info(rtwdev, p, end - p); @@ -9207,16 +9206,16 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) bt->raw_info[4], bt->raw_info[5], bt->raw_info[6], bt->raw_info[7], bt->raw_info[0] == BTC_BTINFO_AUTO ? "auto" : "reply", - cx->cnt_bt[BTC_BCNT_INFOUPDATE], - cx->cnt_bt[BTC_BCNT_INFOSAME]); + bt->bcnt[BTC_BCNT_INFOUPDATE], + bt->bcnt[BTC_BCNT_INFOSAME]); p += scnprintf(p, end - p, " %-15s : Hi-rx = %d, Hi-tx = %d, Lo-rx = %d, Lo-tx = %d (bt_polut_wl_tx = %d)", - "[trx_req_cnt]", cx->cnt_bt[BTC_BCNT_HIPRI_RX], - cx->cnt_bt[BTC_BCNT_HIPRI_TX], - cx->cnt_bt[BTC_BCNT_LOPRI_RX], - cx->cnt_bt[BTC_BCNT_LOPRI_TX], - cx->cnt_bt[BTC_BCNT_POLUT]); + "[trx_req_cnt]", bt->bcnt[BTC_BCNT_HIPRI_RX], + bt->bcnt[BTC_BCNT_HIPRI_TX], + bt->bcnt[BTC_BCNT_LOPRI_RX], + bt->bcnt[BTC_BCNT_LOPRI_TX], + bt->bcnt[BTC_BCNT_POLUT]); if (!bt->scan_info_update) { rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_SCAN_INFO, true); @@ -9252,7 +9251,7 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) else rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_TX_PWR_LVL, false); - if (cx->cnt_bt[BTC_BCNT_BTTXPWR_UPDATE]) { + if (bt->bcnt[BTC_BCNT_BTTXPWR_UPDATE]) { p += scnprintf(p, end - p, " %-15s : br_index:0x%x, le_index:0x%x", "[bt_txpwr_lvl]", @@ -10896,7 +10895,6 @@ static int _show_mreg_v1(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) struct rtw89_btc_btf_fwinfo *pfwinfo = &btc->fwinfo; struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; struct rtw89_btc_fbtc_mreg_val_v1 *pmreg = NULL; - struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_mac_ax_coex_gnt gnt_cfg = {}; @@ -10914,8 +10912,8 @@ static int _show_mreg_v1(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) " %-15s : WL->BT:0x%08x(cnt:%d), BT->WL:0x%08x(total:%d, bt_update:%d)\n", "[scoreboard]", wl->scbd, wl->wcnt[BTC_WCNT_SCBDUPDATE], - bt->scbd, cx->cnt_bt[BTC_BCNT_SCBDREAD], - cx->cnt_bt[BTC_BCNT_SCBDUPDATE]); + bt->scbd, bt->bcnt[BTC_BCNT_SCBDREAD], + bt->bcnt[BTC_BCNT_SCBDUPDATE]); btc->dm.pta_owner = rtw89_mac_get_ctrl_path(rtwdev); _get_gnt(rtwdev, &gnt_cfg); @@ -10981,7 +10979,6 @@ static int _show_mreg_v2(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) struct rtw89_btc_btf_fwinfo *pfwinfo = &btc->fwinfo; struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; struct rtw89_btc_fbtc_mreg_val_v2 *pmreg = NULL; - struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_mac_ax_coex_gnt gnt_cfg = {}; @@ -10999,8 +10996,8 @@ static int _show_mreg_v2(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) " %-15s : WL->BT:0x%08x(cnt:%d), BT->WL:0x%08x(total:%d, bt_update:%d)\n", "[scoreboard]", wl->scbd, wl->wcnt[BTC_WCNT_SCBDUPDATE], - bt->scbd, cx->cnt_bt[BTC_BCNT_SCBDREAD], - cx->cnt_bt[BTC_BCNT_SCBDUPDATE]); + bt->scbd, bt->bcnt[BTC_BCNT_SCBDREAD], + bt->bcnt[BTC_BCNT_SCBDUPDATE]); btc->dm.pta_owner = rtw89_mac_get_ctrl_path(rtwdev); _get_gnt(rtwdev, &gnt_cfg); @@ -11084,8 +11081,8 @@ static int _show_mreg_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) "\n\r %-15s : WL->BT:0x%08x(cnt:%d), BT->WL:0x%08x(total:%d, bt_update:%d)", "[scoreboard]", wl->scbd, wl->wcnt[BTC_WCNT_SCBDUPDATE], - bt->scbd, cx->cnt_bt[BTC_BCNT_SCBDREAD], - cx->cnt_bt[BTC_BCNT_SCBDUPDATE]); + bt->scbd, bt->bcnt[BTC_BCNT_SCBDREAD], + bt->bcnt[BTC_BCNT_SCBDUPDATE]); /* To avoid I/O if WL LPS or power-off */ dm->pta_owner = rtw89_mac_get_ctrl_path(rtwdev); diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 0e3110aa867a..e86db8c5c087 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -2250,6 +2250,8 @@ struct rtw89_btc_bt_info { u32 scan_info_update: 1; u32 lna_constrain: 3; u32 rsvd: 17; + + u32 bcnt[BTC_BCNT_NUM]; }; struct rtw89_btc_rf_trx_para_v9 { @@ -2267,7 +2269,6 @@ struct rtw89_btc_cx { struct rtw89_btc_bt_info bt1; struct rtw89_btc_3rdcx_info other; u32 state_map; - u32 cnt_bt[BTC_BCNT_NUM]; }; struct rtw89_btc_fbtc_tdma { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852a.c b/drivers/net/wireless/realtek/rtw89/rtw8852a.c index 2c1f166e687f..94027e5b8d28 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852a.c @@ -2105,12 +2105,12 @@ void rtw8852a_btc_update_bt_cnt(struct rtw89_dev *rtwdev) return; val = rtw89_read32(rtwdev, R_AX_BT_STAST_HIGH); - cx->cnt_bt[BTC_BCNT_HIPRI_TX] = FIELD_GET(B_AX_STATIS_BT_HI_TX_MASK, val); - cx->cnt_bt[BTC_BCNT_HIPRI_RX] = FIELD_GET(B_AX_STATIS_BT_HI_RX_MASK, val); + cx->bt0.bcnt[BTC_BCNT_HIPRI_TX] = FIELD_GET(B_AX_STATIS_BT_HI_TX_MASK, val); + cx->bt0.bcnt[BTC_BCNT_HIPRI_RX] = FIELD_GET(B_AX_STATIS_BT_HI_RX_MASK, val); val = rtw89_read32(rtwdev, R_AX_BT_STAST_LOW); - cx->cnt_bt[BTC_BCNT_LOPRI_TX] = FIELD_GET(B_AX_STATIS_BT_LO_TX_1_MASK, val); - cx->cnt_bt[BTC_BCNT_LOPRI_RX] = FIELD_GET(B_AX_STATIS_BT_LO_RX_1_MASK, val); + cx->bt0.bcnt[BTC_BCNT_LOPRI_TX] = FIELD_GET(B_AX_STATIS_BT_LO_TX_1_MASK, val); + cx->bt0.bcnt[BTC_BCNT_LOPRI_RX] = FIELD_GET(B_AX_STATIS_BT_LO_RX_1_MASK, val); /* clock-gate off before reset counter*/ rtw89_write32_set(rtwdev, R_AX_BTC_CFG, B_AX_DIS_BTC_CLK_G); From 195ce7889423f1fc068490cfbaaed4eaa16a093f Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Wed, 24 Jun 2026 11:39:37 +0800 Subject: [PATCH 0140/1433] wifi: rtw89: coex: Refine third party module related coexistence The incoming chip reserved several IO ports to coexist with the other vendor's stand along chips. In order to configured them easier add the structure. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260624033941.45918-7-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 8 ++-- drivers/net/wireless/realtek/rtw89/core.h | 31 +++++++++++--- drivers/net/wireless/realtek/rtw89/rtw8922a.c | 2 +- drivers/net/wireless/realtek/rtw89/rtw8922d.c | 40 +++++++++++++++++++ 4 files changed, 71 insertions(+), 10 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 8fa51867055b..5159338960f3 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -7655,7 +7655,7 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) goto exit; } - if (!cx->bt0.enable.now && !cx->other.type) { + if (!cx->bt0.enable.now && !cx->bt_ext.func_type) { _action_bt_off(rtwdev); goto exit; } @@ -7776,7 +7776,7 @@ static void _set_init_info(struct rtw89_dev *rtwdev) dm->init_info.init_v7.wl_only = (u8)dm->wl_only; dm->init_info.init_v7.bt_only = (u8)dm->bt_only; dm->init_info.init_v7.wl_init_ok = (u8)wl->status.map.init_ok; - dm->init_info.init_v7.cx_other = btc->cx.other.type; + dm->init_info.init_v7.cx_other = btc->cx.bt_ext.func_type; dm->init_info.init_v7.wl_guard_ch = chip->afh_guard_ch; dm->init_info.init_v7.module = btc->mdinfo.md_v7; } else { @@ -7784,7 +7784,7 @@ static void _set_init_info(struct rtw89_dev *rtwdev) dm->init_info.init.bt_only = (u8)dm->bt_only; dm->init_info.init.wl_init_ok = (u8)wl->status.map.init_ok; dm->init_info.init.dbcc_en = rtwdev->dbcc_en; - dm->init_info.init.cx_other = btc->cx.other.type; + dm->init_info.init.cx_other = btc->cx.bt_ext.func_type; dm->init_info.init.wl_guard_ch = chip->afh_guard_ch; dm->init_info.init.module = btc->mdinfo.md; } @@ -8927,7 +8927,7 @@ static int _show_cx_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, "3rd_coex:%d, dbcc:%d, tx_num:%d, rx_num:%d\n", - btc->cx.other.type, rtwdev->dbcc_en, hal->tx_nss, + btc->cx.bt_ext.func_type, rtwdev->dbcc_en, hal->tx_nss, hal->rx_nss); return p - buf; diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index e86db8c5c087..1293e343d45c 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -1445,6 +1445,12 @@ enum rtw89_btc_bt_state_cnt { BTC_BCNT_NUM, }; +enum rtw89_btc_bt_rf_band { + BTC_BT_B2G = 0x0, /* 2.4GHz */ + BTC_BT_B5G = 0x1, /* 5GHz or 6GHz */ + BTC_BT_BMAX = 0x2 +}; + enum rtw89_btc_bt_profile { BTC_BT_NOPROFILE = 0, BTC_BT_HFP = BIT(0), @@ -1975,10 +1981,25 @@ struct rtw89_btc_bt_link_info { u32 rsvd: 19; }; -struct rtw89_btc_3rdcx_info { - u8 type; /* 0: none, 1:zigbee, 2:LTE */ - u8 hw_coex; - u16 rsvd; +struct rtw89_btc_extsoc_info { + u8 chip_id; + u8 max_tx_pwr; + u8 rf_band_map; + u8 ant_iso_to_wl; + + u8 link_weight[BTC_BT_BMAX]; + + u8 func_type; /* 0: none, 1:zigbee, 2:LTE */ + u8 hw_coex; /* Hard-Wire coex interface support */ + u8 pta_type; /* 0: RTK 4-wire mode, 1: 3-wire mode */ + u8 pta_req_exist; + + u32 hpta_cfg; + u32 hmbx_cfg; + u32 swout_cfg; + u32 swin_cfg; + u32 profile_map[BTC_BT_BMAX]; + u32 bcnt[BTC_BCNT_NUM]; }; struct rtw89_btc_dm_emap { @@ -2267,7 +2288,7 @@ struct rtw89_btc_cx { struct rtw89_btc_wl_info wl; struct rtw89_btc_bt_info bt0; struct rtw89_btc_bt_info bt1; - struct rtw89_btc_3rdcx_info other; + struct rtw89_btc_extsoc_info bt_ext; u32 state_map; }; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922a.c b/drivers/net/wireless/realtek/rtw89/rtw8922a.c index ad3618dfd57d..91897aeced28 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922a.c @@ -2745,7 +2745,7 @@ static void rtw8922a_btc_set_rfe(struct rtw89_dev *rtwdev) if (module->kt_ver <= 1) module->wa_type |= BTC_WA_HFP_ZB; - rtwdev->btc.cx.other.type = BTC_3CX_NONE; + rtwdev->btc.cx.bt_ext.func_type = BTC_3CX_NONE; if (module->rfe_type == 0) { rtwdev->btc.dm.error.map.rfe_type0 = true; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c index 838c26231897..888973f4ef95 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c @@ -2997,6 +2997,46 @@ static u32 rtw8922d_chan_to_rf18_val(struct rtw89_dev *rtwdev, static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev) { + union rtw89_btc_module_info *md = &rtwdev->btc.mdinfo; + struct rtw89_btc_module_v7 *module = &md->md_v7; + + module->rfe_type = rtwdev->efuse.rfe_type; + module->kt_ver = rtwdev->hal.cv; + module->bt_solo = 0; + module->switch_type = BTC_SWITCH_INTERNAL; + module->wa_type = 0; + + module->ant.type = BTC_ANT_SHARED; + module->ant.num = 2; + module->ant.isolation = 10; + module->ant.diversity = 0; + module->ant.single_pos = RF_PATH_A; + module->ant.btg_pos = RF_PATH_B; + + if (module->kt_ver <= 1) + module->wa_type |= BTC_WA_HFP_ZB; + + rtwdev->btc.cx.bt_ext.func_type = BTC_3CX_NONE; + + if (module->rfe_type == 0) { + rtwdev->btc.dm.error.map.rfe_type0 = true; + return; + } + + module->ant.num = (module->rfe_type % 2) ? 2 : 3; + + if (module->kt_ver == 0) + module->ant.num = 2; + + if (module->ant.num == 3) { + module->ant.type = BTC_ANT_DEDICATED; + module->bt_pos = BTC_BT_ALONE; + } else { + module->ant.type = BTC_ANT_SHARED; + module->bt_pos = BTC_BT_BTG; + } + rtwdev->btc.btg_pos = module->ant.btg_pos; + rtwdev->btc.ant_type = module->ant.type; } static void rtw8922d_btc_init_cfg(struct rtw89_dev *rtwdev) From ebb69df34148c3acffb46c18b76865cbddfb40cd Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Wed, 24 Jun 2026 11:39:38 +0800 Subject: [PATCH 0141/1433] wifi: rtw89: coex: Add TX/RX RF parameter format version 9 In order to support external Zigbee/Thread/Bluetooth etc module, the version 8 add the parameter for the case. And also update the related configuration function. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260624033941.45918-8-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 230 ++++++++++------ drivers/net/wireless/realtek/rtw89/core.h | 40 +-- drivers/net/wireless/realtek/rtw89/fw.c | 250 ++++++++++++++++-- drivers/net/wireless/realtek/rtw89/fw.h | 160 ++++++----- drivers/net/wireless/realtek/rtw89/rtw8851b.c | 12 +- drivers/net/wireless/realtek/rtw89/rtw8852a.c | 12 +- drivers/net/wireless/realtek/rtw89/rtw8852b.c | 12 +- .../net/wireless/realtek/rtw89/rtw8852bt.c | 12 +- drivers/net/wireless/realtek/rtw89/rtw8852c.c | 12 +- drivers/net/wireless/realtek/rtw89/rtw8922a.c | 12 +- drivers/net/wireless/realtek/rtw89/rtw8922d.c | 4 + 11 files changed, 512 insertions(+), 244 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 5159338960f3..dd9d6cbc2943 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -140,6 +140,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fwlrole = 7, .frptmap = 3, .fcxctrl = 7, .fcxinit = 7, .fwevntrptl = 1, .fwc2hfunc = 2, .drvinfo_type = 1, .info_buf = 1800, .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 8, + .fcxtrx = 0, }, {RTL8852BT, RTW89_FW_VER_CODE(0, 29, 90, 0), .fcxbtcrpt = 7, .fcxtdma = 7, .fcxslots = 7, .fcxcysta = 7, @@ -148,6 +149,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fwlrole = 7, .frptmap = 3, .fcxctrl = 7, .fcxinit = 7, .fwevntrptl = 1, .fwc2hfunc = 2, .drvinfo_type = 1, .info_buf = 1800, .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 8, + .fcxtrx = 0, }, {RTL8922A, RTW89_FW_VER_CODE(0, 35, 71, 0), .fcxbtcrpt = 8, .fcxtdma = 7, .fcxslots = 7, .fcxcysta = 7, @@ -156,6 +158,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fwlrole = 8, .frptmap = 4, .fcxctrl = 7, .fcxinit = 7, .fwevntrptl = 1, .fwc2hfunc = 3, .drvinfo_type = 2, .info_buf = 1800, .max_role_num = 6, .fcxosi = 1, .fcxmlo = 1, .bt_desired = 9, + .fcxtrx = 7, }, {RTL8922A, RTW89_FW_VER_CODE(0, 35, 63, 0), .fcxbtcrpt = 8, .fcxtdma = 7, .fcxslots = 7, .fcxcysta = 7, @@ -164,6 +167,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fwlrole = 8, .frptmap = 4, .fcxctrl = 7, .fcxinit = 7, .fwevntrptl = 1, .fwc2hfunc = 3, .drvinfo_type = 2, .info_buf = 1800, .max_role_num = 6, .fcxosi = 1, .fcxmlo = 1, .bt_desired = 9, + .fcxtrx = 0, }, {RTL8922A, RTW89_FW_VER_CODE(0, 35, 8, 0), .fcxbtcrpt = 8, .fcxtdma = 7, .fcxslots = 7, .fcxcysta = 7, @@ -172,6 +176,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fwlrole = 8, .frptmap = 3, .fcxctrl = 7, .fcxinit = 7, .fwevntrptl = 1, .fwc2hfunc = 1, .drvinfo_type = 1, .info_buf = 1800, .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, }, {RTL8851B, RTW89_FW_VER_CODE(0, 29, 29, 0), .fcxbtcrpt = 105, .fcxtdma = 3, .fcxslots = 1, .fcxcysta = 5, @@ -180,6 +185,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fwlrole = 2, .frptmap = 3, .fcxctrl = 1, .fcxinit = 0, .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1800, .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, }, {RTL8852C, RTW89_FW_VER_CODE(0, 27, 57, 0), .fcxbtcrpt = 4, .fcxtdma = 3, .fcxslots = 1, .fcxcysta = 3, @@ -188,6 +194,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fwlrole = 1, .frptmap = 3, .fcxctrl = 1, .fcxinit = 0, .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1280, .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, }, {RTL8852C, RTW89_FW_VER_CODE(0, 27, 42, 0), .fcxbtcrpt = 4, .fcxtdma = 3, .fcxslots = 1, .fcxcysta = 3, @@ -196,6 +203,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fwlrole = 1, .frptmap = 2, .fcxctrl = 1, .fcxinit = 0, .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1280, .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, }, {RTL8852C, RTW89_FW_VER_CODE(0, 27, 0, 0), .fcxbtcrpt = 4, .fcxtdma = 3, .fcxslots = 1, .fcxcysta = 3, @@ -204,6 +212,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fwlrole = 1, .frptmap = 2, .fcxctrl = 1, .fcxinit = 0, .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1280, .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, }, {RTL8852B, RTW89_FW_VER_CODE(0, 29, 122, 0), .fcxbtcrpt = 8, .fcxtdma = 7, .fcxslots = 7, .fcxcysta = 7, @@ -212,6 +221,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fwlrole = 7, .frptmap = 3, .fcxctrl = 7, .fcxinit = 7, .fwevntrptl = 1, .fwc2hfunc = 2, .drvinfo_type = 1, .info_buf = 1800, .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 8, + .fcxtrx = 0, }, {RTL8852B, RTW89_FW_VER_CODE(0, 29, 29, 0), .fcxbtcrpt = 105, .fcxtdma = 3, .fcxslots = 1, .fcxcysta = 5, @@ -220,6 +230,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fwlrole = 2, .frptmap = 3, .fcxctrl = 1, .fcxinit = 0, .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1800, .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, }, {RTL8852B, RTW89_FW_VER_CODE(0, 29, 14, 0), .fcxbtcrpt = 5, .fcxtdma = 3, .fcxslots = 1, .fcxcysta = 4, @@ -228,6 +239,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fwlrole = 1, .frptmap = 3, .fcxctrl = 1, .fcxinit = 0, .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1800, .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, }, {RTL8852B, RTW89_FW_VER_CODE(0, 27, 0, 0), .fcxbtcrpt = 4, .fcxtdma = 3, .fcxslots = 1, .fcxcysta = 3, @@ -236,6 +248,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fwlrole = 1, .frptmap = 1, .fcxctrl = 1, .fcxinit = 0, .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1280, .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, }, {RTL8852A, RTW89_FW_VER_CODE(0, 13, 37, 0), .fcxbtcrpt = 4, .fcxtdma = 3, .fcxslots = 1, .fcxcysta = 3, @@ -244,6 +257,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fwlrole = 1, .frptmap = 3, .fcxctrl = 1, .fcxinit = 0, .fwevntrptl = 0, .fwc2hfunc = 0, .drvinfo_type = 0, .info_buf = 1280, .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, }, {RTL8852A, RTW89_FW_VER_CODE(0, 13, 0, 0), .fcxbtcrpt = 1, .fcxtdma = 1, .fcxslots = 1, .fcxcysta = 2, @@ -252,6 +266,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fwlrole = 0, .frptmap = 0, .fcxctrl = 0, .fcxinit = 0, .fwevntrptl = 0, .fwc2hfunc = 0, .drvinfo_type = 0, .info_buf = 1024, .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, }, /* keep it to be the last as default entry */ @@ -262,6 +277,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fwlrole = 0, .frptmap = 0, .fcxctrl = 0, .fcxinit = 0, .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1024, .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 0, }, }; @@ -2777,9 +2793,6 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 type) { struct rtw89_btc *btc = &rtwdev->btc; const struct rtw89_btc_ver *ver = btc->ver; - struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_rf_trx_para rf_para = dm->rf_trx_para; switch (type) { case CXDRVINFO_INIT: @@ -2813,17 +2826,10 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 type) if (ver->drvinfo_type == 1) type = 3; - dm->trx_info.tx_power = u32_get_bits(rf_para.wl_tx_power, - RTW89_BTC_WL_DEF_TX_PWR); - dm->trx_info.rx_gain = u32_get_bits(rf_para.wl_rx_gain, - RTW89_BTC_WL_DEF_TX_PWR); - dm->trx_info.bt_tx_power = u32_get_bits(rf_para.bt_tx_power, - RTW89_BTC_WL_DEF_TX_PWR); - dm->trx_info.bt_rx_gain = u32_get_bits(rf_para.bt_rx_gain, - RTW89_BTC_WL_DEF_TX_PWR); - dm->trx_info.cn = wl->cn_report; - dm->trx_info.nhm = wl->nhm.pwr; - rtw89_fw_h2c_cxdrv_trx(rtwdev, type); + if (ver->fcxtrx == 7) + rtw89_fw_h2c_cxdrv_trx_v7(rtwdev, type); + else if (ver->fcxtrx == 9) + rtw89_fw_h2c_cxdrv_trx_v9(rtwdev, type); break; case CXDRVINFO_RFK: if (ver->drvinfo_type == 1) @@ -2832,6 +2838,14 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 type) rtw89_fw_h2c_cxdrv_rfk(rtwdev, type); break; case CXDRVINFO_TXPWR: + if (ver->drvinfo_type == 3) + type = 4; + + if (ver->fcxtrx == 7) + rtw89_fw_h2c_cxtxpwr_v7(rtwdev, type); + else if (ver->fcxtrx == 9) + rtw89_fw_h2c_cxtxpwr_v9(rtwdev, type); + break; case CXDRVINFO_FDDT: case CXDRVINFO_MLO: case CXDRVINFO_OSI: @@ -3025,53 +3039,84 @@ static void _set_bt_ignore_wlan_act(struct rtw89_dev *rtwdev, u8 enable) #define B_BTC_WL_TX_POWER_SIGN BIT(7) #define B_TSSI_WL_TX_POWER_SIGN BIT(8) -static void _set_wl_tx_power(struct rtw89_dev *rtwdev, u32 level) +static void _set_wl_tx_power(struct rtw89_dev *rtwdev, u32 level, u8 phy_map) { const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; + const struct rtw89_btc_ver *ver = btc->ver; + struct rtw89_btc_dm *dm = &btc->dm; + bool level_chg = false; u32 pwr_val; - if (wl->rf_para.tx_pwr_freerun == level) + if (phy_map & BIT(RTW89_PHY_0)) + level_chg = !(dm->rf_trx_para.wl_tx_power[RTW89_PHY_0] == level); + + if (phy_map & BIT(RTW89_PHY_1)) + level_chg |= !(dm->rf_trx_para.wl_tx_power[RTW89_PHY_1] == level); + + if (!level_chg && !btc->cli_h2c_cmd) return; - wl->rf_para.tx_pwr_freerun = level; - btc->dm.rf_trx_para.wl_tx_power = level; + if (phy_map & BIT(RTW89_PHY_0)) + dm->rf_trx_para.wl_tx_power[RTW89_PHY_0] = level; + if (phy_map & BIT(RTW89_PHY_1)) + dm->rf_trx_para.wl_tx_power[RTW89_PHY_1] = level; + + dm->wl_tx_pwr_phy_map = phy_map; rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): level = %d\n", - __func__, level); + "[BTC], %s(): phy_map=0x%x, wl_tx_pwr-> phy0=%d, phy1=%d\n", + __func__, phy_map, + dm->rf_trx_para.wl_tx_power[RTW89_PHY_0], + dm->rf_trx_para.wl_tx_power[RTW89_PHY_1]); - if (level == RTW89_BTC_WL_DEF_TX_PWR) { - pwr_val = WL_TX_POWER_NO_BTC_CTRL; - } else { /* only apply "force tx power" */ - pwr_val = FIELD_PREP(WL_TX_POWER_INT_PART, level); - if (pwr_val > RTW89_BTC_WL_DEF_TX_PWR) - pwr_val = RTW89_BTC_WL_DEF_TX_PWR; + if (ver->fcxtrx == 7 && chip->chip_id == RTL8922A) { + _fw_set_drv_info(rtwdev, CXDRVINFO_TXPWR); + } else if (ver->fcxtrx == 9) { + _fw_set_drv_info(rtwdev, CXDRVINFO_TXPWR); + } else { + if (level == RTW89_BTC_WL_DEF_TX_PWR) { + pwr_val = WL_TX_POWER_NO_BTC_CTRL; + } else { /* only apply "force tx power" */ + pwr_val = FIELD_PREP(WL_TX_POWER_INT_PART, level); + if (pwr_val > RTW89_BTC_WL_DEF_TX_PWR) + pwr_val = RTW89_BTC_WL_DEF_TX_PWR; - if (level & B_BTC_WL_TX_POWER_SIGN) - pwr_val |= B_TSSI_WL_TX_POWER_SIGN; - pwr_val |= WL_TX_POWER_WITH_BT; + if (level & B_BTC_WL_TX_POWER_SIGN) + pwr_val |= B_TSSI_WL_TX_POWER_SIGN; + pwr_val |= WL_TX_POWER_WITH_BT; + } + chip->ops->btc_set_wl_txpwr_ctrl(rtwdev, pwr_val); } - - chip->ops->btc_set_wl_txpwr_ctrl(rtwdev, pwr_val); } -static void _set_wl_rx_gain(struct rtw89_dev *rtwdev, u32 level) +static void _set_wl_rx_gain(struct rtw89_dev *rtwdev, u32 level, u8 phy_map) { const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; + struct rtw89_btc_dm *dm = &btc->dm; + bool level_chg = false; - if (wl->rf_para.rx_gain_freerun == level) + if (phy_map & BIT(RTW89_PHY_0)) + level_chg = !(dm->rf_trx_para.wl_rx_gain[RTW89_PHY_0] == level); + + if (phy_map & BIT(RTW89_PHY_1)) + level_chg |= !(dm->rf_trx_para.wl_rx_gain[RTW89_PHY_1] == level); + + if (!level_chg && !btc->cli_h2c_cmd) return; - wl->rf_para.rx_gain_freerun = level; - btc->dm.rf_trx_para.wl_rx_gain = level; + if (phy_map & BIT(RTW89_PHY_0)) + dm->rf_trx_para.wl_rx_gain[RTW89_PHY_0] = level; + + if (phy_map & BIT(RTW89_PHY_1)) + dm->rf_trx_para.wl_rx_gain[RTW89_PHY_1] = level; rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): level = %d\n", - __func__, level); + "[BTC], %s(): phy_map=0x%x, wl_rx_gain-> phy0=%d, phy1=%d\n", + __func__, phy_map, + dm->rf_trx_para.wl_rx_gain[RTW89_PHY_0], + dm->rf_trx_para.wl_rx_gain[RTW89_PHY_1]); chip->ops->btc_set_wl_rx_gain(rtwdev, level); } @@ -3097,7 +3142,7 @@ static void _set_bt_tx_power(struct rtw89_dev *rtwdev, u8 level) ret = _send_fw_cmd(rtwdev, BTFC_SET, SET_BT_TX_PWR, &buf, 1); if (!ret) { bt->rf_para.tx_pwr_freerun = level; - btc->dm.rf_trx_para.bt_tx_power = level; + btc->dm.rf_trx_para.bt_tx_power[BTC_BT_1ST] = level; } } @@ -3117,7 +3162,7 @@ static void _set_bt_rx_gain(struct rtw89_dev *rtwdev, u8 level) return; bt->rf_para.rx_gain_freerun = level; - btc->dm.rf_trx_para.bt_rx_gain = level; + btc->dm.rf_trx_para.bt_rx_gain[BTC_BT_1ST] = level; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): level = %d\n", @@ -3141,9 +3186,10 @@ static void _set_rf_trx_para(struct rtw89_dev *rtwdev) struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_bt_link_info *b = &bt->link_info; struct rtw89_btc_wl_smap *wl_smap = &wl->status.map; - struct rtw89_btc_rf_trx_para para; + struct rtw89_btc_rf_trx_para_v9 para; + u8 lv, link_mode = 0, i, dbcc_2g_phy = 0; + u8 ul_para_num, dl_para_num; u32 wl_stb_chg = 0; - u8 level_id = 0, link_mode = 0, i, dbcc_2g_phy = 0; if (ver->fwlrole == 0) { link_mode = wl->role_info.link_mode; @@ -3159,6 +3205,18 @@ static void _set_rf_trx_para(struct rtw89_dev *rtwdev) dbcc_2g_phy = wl->role_info_v2.dbcc_2g_phy; } + if (ver->fcxtrx == 9 && chip->rf_para_ulink_v9) { + ul_para_num = chip->rf_para_ulink_num_v9; + dl_para_num = chip->rf_para_dlink_num_v9; + } else if (ver->fcxtrx == 0 && chip->rf_para_ulink_v0) { + ul_para_num = chip->rf_para_ulink_num_v0; + dl_para_num = chip->rf_para_dlink_num_v0; + } else { + rtw89_warn(rtwdev, "[BTC]%s(), No rf_para for verseion %d\n", + __func__, ver->fcxtrx); + goto next; + } + /* decide trx_para_level */ if (btc->ant_type == BTC_ANT_SHARED) { /* fix LNA2 + TIA gain not change by GNT_BT */ @@ -3180,30 +3238,54 @@ static void _set_rf_trx_para(struct rtw89_dev *rtwdev) } } - level_id = dm->trx_para_level; - if (level_id >= chip->rf_para_dlink_num || - level_id >= chip->rf_para_ulink_num) { + lv = dm->trx_para_level; + if (lv >= ul_para_num || + lv >= dl_para_num) { rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): invalid level_id: %d\n", - __func__, level_id); + __func__, lv); return; } - if (wl->status.map.traffic_dir & BIT(RTW89_TFC_UL)) - para = chip->rf_para_ulink[level_id]; - else - para = chip->rf_para_dlink[level_id]; - - if (dm->fddt_train) { - _set_wl_rx_gain(rtwdev, 1); - _write_scbd(rtwdev, BTC_WSCB_RXGAIN, true); + if (wl->status.map.traffic_dir & BIT(RTW89_TFC_UL)) { + if (ver->fcxtrx == 9) { + para = chip->rf_para_ulink_v9[lv]; + } else { + for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) { + para.wl_tx_power[i] = chip->rf_para_ulink_v0[lv].wl_tx_power; + para.wl_rx_gain[i] = chip->rf_para_ulink_v0[lv].wl_rx_gain; + } + for (i = BTC_BT_1ST; i < BTC_ALL_BT; i++) { + para.bt_tx_power[i] = chip->rf_para_ulink_v0[lv].bt_tx_power; + para.bt_rx_gain[i] = chip->rf_para_ulink_v0[lv].bt_rx_gain; + } + } } else { - _set_wl_tx_power(rtwdev, para.wl_tx_power); - _set_wl_rx_gain(rtwdev, para.wl_rx_gain); - _set_bt_tx_power(rtwdev, para.bt_tx_power); - _set_bt_rx_gain(rtwdev, para.bt_rx_gain); + if (ver->fcxtrx == 9) { + para = chip->rf_para_dlink_v9[lv]; + } else { + for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) { + para.wl_tx_power[i] = chip->rf_para_dlink_v0[lv].wl_tx_power; + para.wl_rx_gain[i] = chip->rf_para_dlink_v0[lv].wl_rx_gain; + } + for (i = BTC_BT_1ST; i < BTC_ALL_BT; i++) { + para.bt_tx_power[i] = chip->rf_para_dlink_v0[lv].bt_tx_power; + para.bt_rx_gain[i] = chip->rf_para_dlink_v0[lv].bt_rx_gain; + } + } } + if (dm->fddt_train) { + _set_wl_rx_gain(rtwdev, 1, RTW89_PHY_0); + _write_scbd(rtwdev, BTC_WSCB_RXGAIN, true); + } else { + _set_wl_tx_power(rtwdev, para.wl_tx_power[RTW89_PHY_0], RTW89_PHY_0); + _set_wl_rx_gain(rtwdev, para.wl_rx_gain[RTW89_PHY_0], RTW89_PHY_0); + _set_bt_tx_power(rtwdev, para.bt_tx_power[BTC_BT_1ST]); + _set_bt_rx_gain(rtwdev, para.bt_rx_gain[BTC_BT_1ST]); + } + +next: if (!bt->enable.now || dm->wl_only || wl_smap->rf_off || wl_smap->lps == BTC_LPS_RF_OFF || link_mode == BTC_WLINK_5G || @@ -7836,7 +7918,7 @@ void rtw89_btc_ntfy_init(struct rtw89_dev *rtwdev, u8 mode) } _set_init_info(rtwdev); - _set_wl_tx_power(rtwdev, RTW89_BTC_WL_DEF_TX_PWR); + _set_wl_tx_power(rtwdev, RTW89_BTC_WL_DEF_TX_PWR, RTW89_PHY_0); btc_fw_set_monreg(rtwdev); rtw89_btc_fw_set_slots(rtwdev); _fw_set_drv_info(rtwdev, CXDRVINFO_INIT); @@ -9669,25 +9751,19 @@ static int _show_dm_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) (dm->wl_fw_cx_offload == BTC_CX_FW_OFFLOAD ? "" : "(Mismatch!!)")); - if (dm->rf_trx_para.wl_tx_power == 0xff) - p += scnprintf(p, end - p, - " %-15s : wl_rssi_lvl:%d, para_lvl:%d, wl_tx_pwr:orig, ", - "[trx_ctrl]", wl->rssi_level, - dm->trx_para_level); - - else - p += scnprintf(p, end - p, - " %-15s : wl_rssi_lvl:%d, para_lvl:%d, wl_tx_pwr:%d, ", - "[trx_ctrl]", wl->rssi_level, - dm->trx_para_level, - dm->rf_trx_para.wl_tx_power); + p += scnprintf(p, end - p, + " %-15s : wl[rssi_lvl:%d/para:%d/tx_pwr:[%d %d]/rx_lvl:[%d %d]/lna2:%d/stb_chg:%d]\n ", + "[dm_rf_ctrl]", + wl->rssi_level, dm->trx_para_level, + dm->rf_trx_para.wl_tx_power[RTW89_PHY_0], + dm->rf_trx_para.wl_tx_power[RTW89_PHY_1], + dm->rf_trx_para.wl_rx_gain[RTW89_PHY_0], + dm->rf_trx_para.wl_rx_gain[RTW89_PHY_1], + dm->wl_lna2, dm->wl_stb_chg); p += scnprintf(p, end - p, - "wl_rx_lvl:%d, bt_tx_pwr_dec:%d, bt_rx_lna:%d(%s-tbl), wl_btg_rx:%d\n", - dm->rf_trx_para.wl_rx_gain, - dm->rf_trx_para.bt_tx_power, - dm->rf_trx_para.bt_rx_gain, - (bt->hi_lna_rx ? "Hi" : "Ori"), dm->wl_btg_rx); + " %-15s : pre_agc:%d, btg_rx:%d\n ", + "[dm_bb_ctrl]", dm->wl_pre_agc, dm->wl_btg_rx); p += scnprintf(p, end - p, " %-15s : wl_tx_limit[en:%d/max_t:%dus/max_retry:%d], bt_slot_reg:%d-TU, bt_scan_rx_low_pri:%d\n", diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 1293e343d45c..01c7d36d8e12 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -2275,6 +2275,14 @@ struct rtw89_btc_bt_info { u32 bcnt[BTC_BCNT_NUM]; }; +#define RTW89_BTC_WL_DEF_TX_PWR GENMASK(7, 0) +struct rtw89_btc_rf_trx_para_v0 { + u32 wl_tx_power; /* absolute Tx power (dBm), 0xff-> no BTC control */ + u32 wl_rx_gain; /* rx gain table index (TBD.) */ + u8 bt_tx_power; /* decrease Tx power (dB) */ + u8 bt_rx_gain; /* LNA constrain level */ +}; + struct rtw89_btc_rf_trx_para_v9 { u32 wl_tx_power[RTW89_PHY_NUM]; /* absolute Tx power (dBm), 1's complement -5->0x85 */ u32 wl_rx_gain[RTW89_PHY_NUM]; /* rx gain table index (TBD.) */ @@ -2289,6 +2297,7 @@ struct rtw89_btc_cx { struct rtw89_btc_bt_info bt0; struct rtw89_btc_bt_info bt1; struct rtw89_btc_extsoc_info bt_ext; + struct rtw89_btc_rf_trx_para_v9 rf_para; u32 state_map; }; @@ -3068,24 +3077,18 @@ struct rtw89_btc_fbtc_btdevinfo { __le32 flush_time; } __packed; -#define RTW89_BTC_WL_DEF_TX_PWR GENMASK(7, 0) -struct rtw89_btc_rf_trx_para { - u32 wl_tx_power; /* absolute Tx power (dBm), 0xff-> no BTC control */ - u32 wl_rx_gain; /* rx gain table index (TBD.) */ - u8 bt_tx_power; /* decrease Tx power (dB) */ - u8 bt_rx_gain; /* LNA constrain level */ -}; - struct rtw89_btc_trx_info { u8 tx_lvl; u8 rx_lvl; u8 wl_rssi; u8 bt_rssi; - s8 tx_power; /* absolute Tx power (dBm), 0xff-> no BTC control */ - s8 rx_gain; /* rx gain table index (TBD.) */ - s8 bt_tx_power; /* decrease Tx power (dB) */ - s8 bt_rx_gain; /* LNA constrain level */ + s8 wl_tx_power[RTW89_PHY_NUM]; /* absolute Tx power (dBm), 0xff-> no BTC control */ + s8 wl_rx_gain[RTW89_PHY_NUM]; /* rx gain table index (TBD.) */ + s8 bt_tx_power[BTC_ALL_BT]; /* decrease Tx power (dB) */ + s8 bt_rx_gain[BTC_ALL_BT]; /* LNA constrain level */ + s8 zb_tx_power[BTC_ALL_BT]; + s8 zb_rx_gain[BTC_ALL_BT]; u8 cn; /* condition_num */ s8 nhm; @@ -3132,7 +3135,7 @@ struct rtw89_btc_dm { struct rtw89_btc_fbtc_tdma tdma_now; struct rtw89_mac_ax_coex_gnt gnt; union rtw89_btc_init_info_u init_info; /* pass to wl_fw if offload */ - struct rtw89_btc_rf_trx_para rf_trx_para; + struct rtw89_btc_rf_trx_para_v9 rf_trx_para; struct rtw89_btc_wl_tx_limit_para wl_tx_limit; struct rtw89_btc_dm_step dm_step; struct rtw89_btc_wl_scc_ctrl wl_scc; @@ -3169,6 +3172,7 @@ struct rtw89_btc_dm { u8 run_reason; u8 run_action; + u8 wl_tx_pwr_phy_map; u8 wl_pre_agc: 2; u8 wl_lna2: 1; @@ -3367,6 +3371,7 @@ struct rtw89_btc_ver { u8 fcxosi; u8 fcxmlo; u8 bt_desired; + u8 fcxtrx; }; struct rtw89_btc_btf_fwinfo { @@ -3424,6 +3429,7 @@ struct rtw89_btc { bool update_policy_force; bool lps; bool manual_ctrl; + bool cli_h2c_cmd; }; enum rtw89_btc_hmsg { @@ -4772,10 +4778,10 @@ struct rtw89_chip_info { u8 mon_reg_num; const struct rtw89_btc_fbtc_mreg *mon_reg; - u8 rf_para_ulink_num; - const struct rtw89_btc_rf_trx_para *rf_para_ulink; - u8 rf_para_dlink_num; - const struct rtw89_btc_rf_trx_para *rf_para_dlink; + const struct rtw89_btc_rf_trx_para_v0 *rf_para_ulink_v0; + const struct rtw89_btc_rf_trx_para_v0 *rf_para_dlink_v0; + u8 rf_para_ulink_num_v0; + u8 rf_para_dlink_num_v0; const struct rtw89_btc_rf_trx_para_v9 *rf_para_ulink_v9; const struct rtw89_btc_rf_trx_para_v9 *rf_para_dlink_v9; u8 rf_para_ulink_num_v9; diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 27b52845c1dd..a20bc9aa5b26 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -6318,48 +6318,158 @@ int rtw89_fw_h2c_cxdrv_ctrl_v7(struct rtw89_dev *rtwdev, u8 type) return ret; } -#define H2C_LEN_CXDRVINFO_TRX (28 + H2C_LEN_CXDRVHDR) -int rtw89_fw_h2c_cxdrv_trx(struct rtw89_dev *rtwdev, u8 type) +int rtw89_fw_h2c_cxdrv_trx_v7(struct rtw89_dev *rtwdev, u8 type) { struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_rf_trx_para_v9 rf_para = btc->dm.rf_trx_para; struct rtw89_btc_trx_info *trx = &btc->dm.trx_info; + struct rtw89_btc_wl_info *wl = &btc->cx.wl; + struct rtw89_h2c_cxtrx_v7 *h2c; + u32 len = sizeof(*h2c); struct sk_buff *skb; - u8 *cmd; int ret; + u8 i; - skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, H2C_LEN_CXDRVINFO_TRX); + for (i = 0; i < RTW89_PHY_NUM; i++) { + trx->wl_tx_power[i] = u32_get_bits(rf_para.wl_tx_power[i], + RTW89_BTC_WL_DEF_TX_PWR); + trx->wl_rx_gain[i] = u32_get_bits(rf_para.wl_rx_gain[i], + RTW89_BTC_WL_DEF_TX_PWR); + } + for (i = 0; i < BTC_ALL_BT; i++) { + trx->bt_tx_power[i] = u32_get_bits(rf_para.bt_tx_power[i], + RTW89_BTC_WL_DEF_TX_PWR); + trx->bt_rx_gain[i] = u32_get_bits(rf_para.bt_rx_gain[i], + RTW89_BTC_WL_DEF_TX_PWR); + trx->zb_tx_power[i] = u32_get_bits(rf_para.zb_tx_power[i], + RTW89_BTC_WL_DEF_TX_PWR); + trx->zb_rx_gain[i] = u32_get_bits(rf_para.zb_rx_gain[i], + RTW89_BTC_WL_DEF_TX_PWR); + } + trx->cn = wl->cn_report; + trx->nhm = wl->nhm.pwr; + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); if (!skb) { - rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_trx\n"); + rtw89_err(rtwdev, "failed to alloc skb for h2c cxtrx_v9\n"); return -ENOMEM; } - skb_put(skb, H2C_LEN_CXDRVINFO_TRX); - cmd = skb->data; + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxtrx_v7 *)skb->data; - RTW89_SET_FWCMD_CXHDR_TYPE(cmd, type); - RTW89_SET_FWCMD_CXHDR_LEN(cmd, H2C_LEN_CXDRVINFO_TRX - H2C_LEN_CXDRVHDR); + h2c->hdr.type = type; + h2c->hdr.ver = btc->ver->fcxtrx; + h2c->hdr.len = sizeof(*h2c) - H2C_LEN_CXDRVHDR_V7; - RTW89_SET_FWCMD_CXTRX_TXLV(cmd, trx->tx_lvl); - RTW89_SET_FWCMD_CXTRX_RXLV(cmd, trx->rx_lvl); - RTW89_SET_FWCMD_CXTRX_WLRSSI(cmd, trx->wl_rssi); - RTW89_SET_FWCMD_CXTRX_BTRSSI(cmd, trx->bt_rssi); - RTW89_SET_FWCMD_CXTRX_TXPWR(cmd, trx->tx_power); - RTW89_SET_FWCMD_CXTRX_RXGAIN(cmd, trx->rx_gain); - RTW89_SET_FWCMD_CXTRX_BTTXPWR(cmd, trx->bt_tx_power); - RTW89_SET_FWCMD_CXTRX_BTRXGAIN(cmd, trx->bt_rx_gain); - RTW89_SET_FWCMD_CXTRX_CN(cmd, trx->cn); - RTW89_SET_FWCMD_CXTRX_NHM(cmd, trx->nhm); - RTW89_SET_FWCMD_CXTRX_BTPROFILE(cmd, trx->bt_profile); - RTW89_SET_FWCMD_CXTRX_RSVD2(cmd, trx->rsvd2); - RTW89_SET_FWCMD_CXTRX_TXRATE(cmd, trx->tx_rate); - RTW89_SET_FWCMD_CXTRX_RXRATE(cmd, trx->rx_rate); - RTW89_SET_FWCMD_CXTRX_TXTP(cmd, trx->tx_tp); - RTW89_SET_FWCMD_CXTRX_RXTP(cmd, trx->rx_tp); - RTW89_SET_FWCMD_CXTRX_RXERRRA(cmd, trx->rx_err_ratio); + h2c->v7_u8.tx_lvl = trx->tx_lvl; + h2c->v7_u8.rx_lvl = trx->rx_lvl; + h2c->v7_u8.wl_rssi = trx->wl_rssi; + h2c->v7_u8.bt_rssi = trx->bt_rssi; + h2c->v7_u8.wl_tx_power = trx->wl_tx_power[RTW89_PHY_0]; + h2c->v7_u8.wl_rx_gain = trx->wl_rx_gain[RTW89_PHY_0]; + h2c->v7_u8.bt_tx_power = trx->bt_tx_power[BTC_BT_1ST]; + h2c->v7_u8.bt_rx_gain = trx->bt_rx_gain[BTC_BT_1ST]; + h2c->v7_u8.zb_tx_power = trx->zb_tx_power[BTC_BT_1ST]; + h2c->v7_u8.zb_rx_gain = trx->zb_rx_gain[BTC_BT_1ST]; + h2c->v7_u8.cn = trx->cn; + h2c->v7_u8.nhm = trx->nhm; + h2c->v7_u8.bt_profile = trx->bt_profile; + h2c->v7_u8.rsvd2 = trx->rsvd2; + h2c->v7_le.tx_rate = cpu_to_le16(trx->tx_rate); + h2c->v7_le.rx_rate = cpu_to_le16(trx->rx_rate); + h2c->v7_le.tx_tp = cpu_to_le32(trx->tx_tp); + h2c->v7_le.rx_tp = cpu_to_le32(trx->rx_tp); + h2c->v7_le.rx_err_ratio = cpu_to_le32(trx->rx_err_ratio); rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_OUTSRC, BTFC_SET, - SET_DRV_INFO, 0, 0, - H2C_LEN_CXDRVINFO_TRX); + SET_DRV_INFO, 0, 0, len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + +int rtw89_fw_h2c_cxdrv_trx_v9(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_rf_trx_para_v9 rf_para = btc->dm.rf_trx_para; + struct rtw89_btc_trx_info *trx = &btc->dm.trx_info; + struct rtw89_btc_wl_info *wl = &btc->cx.wl; + struct rtw89_h2c_cxtrx_v9 *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + int ret; + u8 i; + + for (i = 0; i < RTW89_PHY_NUM; i++) { + trx->wl_tx_power[i] = u32_get_bits(rf_para.wl_tx_power[i], + RTW89_BTC_WL_DEF_TX_PWR); + trx->wl_rx_gain[i] = u32_get_bits(rf_para.wl_rx_gain[i], + RTW89_BTC_WL_DEF_TX_PWR); + } + for (i = 0; i < BTC_ALL_BT; i++) { + trx->bt_tx_power[i] = u32_get_bits(rf_para.bt_tx_power[i], + RTW89_BTC_WL_DEF_TX_PWR); + trx->bt_rx_gain[i] = u32_get_bits(rf_para.bt_rx_gain[i], + RTW89_BTC_WL_DEF_TX_PWR); + trx->zb_tx_power[i] = u32_get_bits(rf_para.zb_tx_power[i], + RTW89_BTC_WL_DEF_TX_PWR); + trx->zb_rx_gain[i] = u32_get_bits(rf_para.zb_rx_gain[i], + RTW89_BTC_WL_DEF_TX_PWR); + } + trx->cn = wl->cn_report; + trx->nhm = wl->nhm.pwr; + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxtrx_v9\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxtrx_v9 *)skb->data; + + h2c->hdr.type = type; + h2c->hdr.ver = btc->ver->fcxtrx; + h2c->hdr.len = sizeof(*h2c) - H2C_LEN_CXDRVHDR_V7; + + h2c->v9_u8.tx_lvl = trx->tx_lvl; + h2c->v9_u8.rx_lvl = trx->rx_lvl; + h2c->v9_u8.wl_rssi = trx->wl_rssi; + h2c->v9_u8.bt_rssi = trx->bt_rssi; + + for (i = 0; i < RTW89_PHY_NUM; i++) { + h2c->v9_u8.wl_tx_power[i] = trx->wl_tx_power[i]; + h2c->v9_u8.wl_rx_gain[i] = trx->wl_rx_gain[i]; + } + + for (i = 0; i < BTC_ALL_BT; i++) { + h2c->v9_u8.bt_tx_power[i] = trx->bt_tx_power[i]; + h2c->v9_u8.bt_rx_gain[i] = trx->bt_rx_gain[i]; + h2c->v9_u8.zb_tx_power[i] = trx->zb_tx_power[i]; + h2c->v9_u8.zb_rx_gain[i] = trx->zb_rx_gain[i]; + } + h2c->v9_u8.cn = trx->cn; + h2c->v9_u8.nhm = trx->nhm; + h2c->v9_u8.bt_profile = trx->bt_profile; + h2c->v9_u8.rsvd2 = trx->rsvd2; + h2c->v9_le.tx_rate = cpu_to_le16(trx->tx_rate); + h2c->v9_le.rx_rate = cpu_to_le16(trx->rx_rate); + h2c->v9_le.tx_tp = cpu_to_le32(trx->tx_tp); + h2c->v9_le.rx_tp = cpu_to_le32(trx->rx_tp); + h2c->v9_le.rx_err_ratio = cpu_to_le32(trx->rx_err_ratio); + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, len); ret = rtw89_h2c_tx(rtwdev, skb, false); if (ret) { @@ -6419,6 +6529,90 @@ int rtw89_fw_h2c_cxdrv_rfk(struct rtw89_dev *rtwdev, u8 type) return ret; } +int rtw89_fw_h2c_cxtxpwr_v7(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_rf_trx_para_v9 rp = dm->rf_trx_para; + struct rtw89_h2c_cxtxpwr_v7 *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + int ret; + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_ctrl\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxtxpwr_v7 *)skb->data; + + h2c->hdr.type = type; + h2c->hdr.ver = btc->ver->fcxtrx; + h2c->hdr.len = sizeof(*h2c) - H2C_LEN_CXDRVHDR_V7; + h2c->pwr = rp.wl_tx_power[RTW89_PHY_0] & 0xff; + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + +int rtw89_fw_h2c_cxtxpwr_v9(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_rf_trx_para_v9 rp = dm->rf_trx_para; + struct rtw89_h2c_cxtxpwr_v9 *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + int ret; + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_ctrl\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxtxpwr_v9 *)skb->data; + + h2c->hdr.type = type; + h2c->hdr.ver = btc->ver->fcxtrx; + h2c->hdr.len = sizeof(*h2c) - H2C_LEN_CXDRVHDR_V7; + if (dm->wl_tx_pwr_phy_map == BIT(RTW89_PHY_1)) + h2c->pwr = rp.wl_tx_power[RTW89_PHY_1] & 0xff; + else + h2c->pwr = rp.wl_tx_power[RTW89_PHY_0] & 0xff; + h2c->band = dm->wl_tx_pwr_phy_map; + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + #define H2C_LEN_PKT_OFLD 4 int rtw89_fw_h2c_del_pkt_offload(struct rtw89_dev *rtwdev, u8 id) { diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index 6cb99144e541..79e9af3a6805 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -2499,6 +2499,76 @@ struct rtw89_h2c_cxinit { u8 rsvd1; } __packed; +struct rtw89_btc_trx_info_u8 { + u8 tx_lvl; + u8 rx_lvl; + u8 wl_rssi; + u8 bt_rssi; + + s8 wl_tx_power[RTW89_PHY_NUM]; /* absolute Tx power (dBm), 0xff-> no BTC control */ + s8 wl_rx_gain[RTW89_PHY_NUM]; /* rx gain table index (TBD.) */ + s8 bt_tx_power[BTC_ALL_BT]; /* decrease Tx power (dB) */ + s8 bt_rx_gain[BTC_ALL_BT]; /* LNA constrain level */ + s8 zb_tx_power[BTC_ALL_BT]; + s8 zb_rx_gain[BTC_ALL_BT]; + + u8 cn; /* condition_num */ + s8 nhm; + u8 bt_profile; + u8 rsvd2; +} __packed; + +struct rtw89_btc_trx_info_v7_u8 { + u8 tx_lvl; + u8 rx_lvl; + u8 wl_rssi; + u8 bt_rssi; + + s8 wl_tx_power; + s8 wl_rx_gain; + s8 bt_tx_power; + s8 bt_rx_gain; + s8 zb_tx_power; + s8 zb_rx_gain; + + u8 cn; + s8 nhm; + u8 bt_profile; + u8 rsvd2; +} __packed; + +struct rtw89_btc_trx_info_le { + __le16 tx_rate; + __le16 rx_rate; + + __le32 tx_tp; + __le32 rx_tp; + __le32 rx_err_ratio; +} __packed; + +struct rtw89_h2c_cxtrx_v9 { + struct rtw89_h2c_cxhdr_v7 hdr; + struct rtw89_btc_trx_info_u8 v9_u8; + struct rtw89_btc_trx_info_le v9_le; +} __packed; + +struct rtw89_h2c_cxtrx_v7 { + struct rtw89_h2c_cxhdr_v7 hdr; + struct rtw89_btc_trx_info_v7_u8 v7_u8; + struct rtw89_btc_trx_info_le v7_le; +} __packed; + +struct rtw89_h2c_cxtxpwr_v7 { + struct rtw89_h2c_cxhdr_v7 hdr; + u8 pwr; +} __packed; + +struct rtw89_h2c_cxtxpwr_v9 { + struct rtw89_h2c_cxhdr_v7 hdr; + u8 pwr; + u8 band; +} __packed; + #define RTW89_H2C_CXINIT_ANT_INFO_POS BIT(0) #define RTW89_H2C_CXINIT_ANT_INFO_DIVERSITY BIT(1) #define RTW89_H2C_CXINIT_ANT_INFO_BTG_POS GENMASK(3, 2) @@ -2760,91 +2830,6 @@ static inline void RTW89_SET_FWCMD_CXCTRL_TRACE_STEP(void *cmd, u32 val) le32p_replace_bits((__le32 *)((u8 *)(cmd) + 2), val, GENMASK(18, 3)); } -static inline void RTW89_SET_FWCMD_CXTRX_TXLV(void *cmd, u8 val) -{ - u8p_replace_bits((u8 *)cmd + 2, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_RXLV(void *cmd, u8 val) -{ - u8p_replace_bits((u8 *)cmd + 3, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_WLRSSI(void *cmd, u8 val) -{ - u8p_replace_bits((u8 *)cmd + 4, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_BTRSSI(void *cmd, u8 val) -{ - u8p_replace_bits((u8 *)cmd + 5, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_TXPWR(void *cmd, s8 val) -{ - u8p_replace_bits((u8 *)cmd + 6, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_RXGAIN(void *cmd, s8 val) -{ - u8p_replace_bits((u8 *)cmd + 7, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_BTTXPWR(void *cmd, s8 val) -{ - u8p_replace_bits((u8 *)cmd + 8, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_BTRXGAIN(void *cmd, s8 val) -{ - u8p_replace_bits((u8 *)cmd + 9, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_CN(void *cmd, u8 val) -{ - u8p_replace_bits((u8 *)cmd + 10, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_NHM(void *cmd, s8 val) -{ - u8p_replace_bits((u8 *)cmd + 11, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_BTPROFILE(void *cmd, u8 val) -{ - u8p_replace_bits((u8 *)cmd + 12, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_RSVD2(void *cmd, u8 val) -{ - u8p_replace_bits((u8 *)cmd + 13, val, GENMASK(7, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_TXRATE(void *cmd, u16 val) -{ - le16p_replace_bits((__le16 *)((u8 *)cmd + 14), val, GENMASK(15, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_RXRATE(void *cmd, u16 val) -{ - le16p_replace_bits((__le16 *)((u8 *)cmd + 16), val, GENMASK(15, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_TXTP(void *cmd, u32 val) -{ - le32p_replace_bits((__le32 *)((u8 *)cmd + 18), val, GENMASK(31, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_RXTP(void *cmd, u32 val) -{ - le32p_replace_bits((__le32 *)((u8 *)cmd + 22), val, GENMASK(31, 0)); -} - -static inline void RTW89_SET_FWCMD_CXTRX_RXERRRA(void *cmd, u32 val) -{ - le32p_replace_bits((__le32 *)((u8 *)cmd + 26), val, GENMASK(31, 0)); -} - static inline void RTW89_SET_FWCMD_CXRFK_STATE(void *cmd, u32 val) { le32p_replace_bits((__le32 *)((u8 *)(cmd) + 2), val, GENMASK(1, 0)); @@ -5401,8 +5386,11 @@ int rtw89_fw_h2c_cxdrv_role_v8(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_osi_info(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_ctrl(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_ctrl_v7(struct rtw89_dev *rtwdev, u8 type); -int rtw89_fw_h2c_cxdrv_trx(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxdrv_trx_v7(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxdrv_trx_v9(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_rfk(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxtxpwr_v7(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxtxpwr_v9(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_del_pkt_offload(struct rtw89_dev *rtwdev, u8 id); int rtw89_fw_h2c_add_pkt_offload(struct rtw89_dev *rtwdev, u8 *id, struct sk_buff *skb_ofld); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b.c b/drivers/net/wireless/realtek/rtw89/rtw8851b.c index 60f362593696..4caf231c6287 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851b.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851b.c @@ -355,7 +355,7 @@ static const struct rtw89_pmac_regs rtw8851b_pmac_regs = { .ampdu_crc_err_mask = B_CNT_AMPDU_RX_CRC32_ERR, }; -static const struct rtw89_btc_rf_trx_para rtw89_btc_8851b_rf_ul[] = { +static const struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8851b_rf_ul_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> for BT-connected ACI issue && BTG co-rx */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -367,7 +367,7 @@ static const struct rtw89_btc_rf_trx_para rtw89_btc_8851b_rf_ul[] = { {13, 1, 0, 7} }; -static const struct rtw89_btc_rf_trx_para rtw89_btc_8851b_rf_dl[] = { +static const struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8851b_rf_dl_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> reserved for shared-antenna */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -2730,10 +2730,10 @@ const struct rtw89_chip_info rtw8851b_chip_info = { .rssi_tol = 2, .mon_reg_num = ARRAY_SIZE(rtw89_btc_8851b_mon_reg), .mon_reg = rtw89_btc_8851b_mon_reg, - .rf_para_ulink_num = ARRAY_SIZE(rtw89_btc_8851b_rf_ul), - .rf_para_ulink = rtw89_btc_8851b_rf_ul, - .rf_para_dlink_num = ARRAY_SIZE(rtw89_btc_8851b_rf_dl), - .rf_para_dlink = rtw89_btc_8851b_rf_dl, + .rf_para_ulink_v0 = rtw89_btc_8851b_rf_ul_v0, + .rf_para_dlink_v0 = rtw89_btc_8851b_rf_dl_v0, + .rf_para_ulink_num_v0 = ARRAY_SIZE(rtw89_btc_8851b_rf_ul_v0), + .rf_para_dlink_num_v0 = ARRAY_SIZE(rtw89_btc_8851b_rf_dl_v0), .rf_para_ulink_v9 = NULL, .rf_para_dlink_v9 = NULL, .rf_para_ulink_num_v9 = 0, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852a.c b/drivers/net/wireless/realtek/rtw89/rtw8852a.c index 94027e5b8d28..78addc0aef69 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852a.c @@ -2049,7 +2049,7 @@ s8 rtw8852a_btc_get_bt_rssi(struct rtw89_dev *rtwdev, s8 val) return clamp_t(s8, val + 6, -100, 0) + 100; } -static struct rtw89_btc_rf_trx_para rtw89_btc_8852a_rf_ul[] = { +static struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8852a_rf_ul_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> for BT-connected ACI issue && BTG co-rx */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -2061,7 +2061,7 @@ static struct rtw89_btc_rf_trx_para rtw89_btc_8852a_rf_ul[] = { {13, 1, 0, 7} }; -static struct rtw89_btc_rf_trx_para rtw89_btc_8852a_rf_dl[] = { +static struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8852a_rf_dl_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> reserved for shared-antenna */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -2468,10 +2468,10 @@ const struct rtw89_chip_info rtw8852a_chip_info = { .rssi_tol = 2, .mon_reg_num = ARRAY_SIZE(rtw89_btc_8852a_mon_reg), .mon_reg = rtw89_btc_8852a_mon_reg, - .rf_para_ulink_num = ARRAY_SIZE(rtw89_btc_8852a_rf_ul), - .rf_para_ulink = rtw89_btc_8852a_rf_ul, - .rf_para_dlink_num = ARRAY_SIZE(rtw89_btc_8852a_rf_dl), - .rf_para_dlink = rtw89_btc_8852a_rf_dl, + .rf_para_ulink_v0 = rtw89_btc_8852a_rf_ul_v0, + .rf_para_dlink_v0 = rtw89_btc_8852a_rf_dl_v0, + .rf_para_ulink_num_v0 = ARRAY_SIZE(rtw89_btc_8852a_rf_ul_v0), + .rf_para_dlink_num_v0 = ARRAY_SIZE(rtw89_btc_8852a_rf_dl_v0), .rf_para_ulink_v9 = NULL, .rf_para_dlink_v9 = NULL, .rf_para_ulink_num_v9 = 0, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852b.c b/drivers/net/wireless/realtek/rtw89/rtw8852b.c index 4e7b068aaa75..debcdb2eacd6 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852b.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852b.c @@ -307,7 +307,7 @@ static const struct rtw89_pmac_regs rtw8852b_pmac_regs = { .ampdu_crc_err_mask = B_CNT_AMPDU_RX_CRC32_ERR, }; -static const struct rtw89_btc_rf_trx_para rtw89_btc_8852b_rf_ul[] = { +static const struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8852b_rf_ul_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> for BT-connected ACI issue && BTG co-rx */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -319,7 +319,7 @@ static const struct rtw89_btc_rf_trx_para rtw89_btc_8852b_rf_ul[] = { {13, 1, 0, 7} }; -static const struct rtw89_btc_rf_trx_para rtw89_btc_8852b_rf_dl[] = { +static const struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8852b_rf_dl_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> reserved for shared-antenna */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -1063,10 +1063,10 @@ const struct rtw89_chip_info rtw8852b_chip_info = { .rssi_tol = 2, .mon_reg_num = ARRAY_SIZE(rtw89_btc_8852b_mon_reg), .mon_reg = rtw89_btc_8852b_mon_reg, - .rf_para_ulink_num = ARRAY_SIZE(rtw89_btc_8852b_rf_ul), - .rf_para_ulink = rtw89_btc_8852b_rf_ul, - .rf_para_dlink_num = ARRAY_SIZE(rtw89_btc_8852b_rf_dl), - .rf_para_dlink = rtw89_btc_8852b_rf_dl, + .rf_para_ulink_v0 = rtw89_btc_8852b_rf_ul_v0, + .rf_para_dlink_v0 = rtw89_btc_8852b_rf_dl_v0, + .rf_para_ulink_num_v0 = ARRAY_SIZE(rtw89_btc_8852b_rf_ul_v0), + .rf_para_dlink_num_v0 = ARRAY_SIZE(rtw89_btc_8852b_rf_dl_v0), .rf_para_ulink_v9 = NULL, .rf_para_dlink_v9 = NULL, .rf_para_ulink_num_v9 = 0, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bt.c b/drivers/net/wireless/realtek/rtw89/rtw8852bt.c index 7fcc877f6ea0..fc8a17fb95f4 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852bt.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852bt.c @@ -250,7 +250,7 @@ static const struct rtw89_pmac_regs rtw8852bt_pmac_regs = { .ampdu_crc_err_mask = B_CNT_AMPDU_RX_CRC32_ERR, }; -static const struct rtw89_btc_rf_trx_para rtw89_btc_8852bt_rf_ul[] = { +static const struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8852bt_rf_ul_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> for BT-connected ACI issue && BTG co-rx */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -262,7 +262,7 @@ static const struct rtw89_btc_rf_trx_para rtw89_btc_8852bt_rf_ul[] = { {13, 1, 0, 7} }; -static const struct rtw89_btc_rf_trx_para rtw89_btc_8852bt_rf_dl[] = { +static const struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8852bt_rf_dl_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> reserved for shared-antenna */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -902,10 +902,10 @@ const struct rtw89_chip_info rtw8852bt_chip_info = { .rssi_tol = 2, .mon_reg_num = ARRAY_SIZE(rtw89_btc_8852bt_mon_reg), .mon_reg = rtw89_btc_8852bt_mon_reg, - .rf_para_ulink_num = ARRAY_SIZE(rtw89_btc_8852bt_rf_ul), - .rf_para_ulink = rtw89_btc_8852bt_rf_ul, - .rf_para_dlink_num = ARRAY_SIZE(rtw89_btc_8852bt_rf_dl), - .rf_para_dlink = rtw89_btc_8852bt_rf_dl, + .rf_para_ulink_v0 = rtw89_btc_8852bt_rf_ul_v0, + .rf_para_dlink_v0 = rtw89_btc_8852bt_rf_dl_v0, + .rf_para_ulink_num_v0 = ARRAY_SIZE(rtw89_btc_8852bt_rf_ul_v0), + .rf_para_dlink_num_v0 = ARRAY_SIZE(rtw89_btc_8852bt_rf_dl_v0), .rf_para_ulink_v9 = NULL, .rf_para_dlink_v9 = NULL, .rf_para_ulink_num_v9 = 0, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852c.c b/drivers/net/wireless/realtek/rtw89/rtw8852c.c index 80821885b08e..29a3c90021f3 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852c.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852c.c @@ -2863,7 +2863,7 @@ s8 rtw8852c_btc_get_bt_rssi(struct rtw89_dev *rtwdev, s8 val) return clamp_t(s8, val + 6, -100, 0) + 100; } -static const struct rtw89_btc_rf_trx_para rtw89_btc_8852c_rf_ul[] = { +static const struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8852c_rf_ul_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> for BT-connected ACI issue && BTG co-rx */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -2875,7 +2875,7 @@ static const struct rtw89_btc_rf_trx_para rtw89_btc_8852c_rf_ul[] = { {13, 1, 0, 7} }; -static const struct rtw89_btc_rf_trx_para rtw89_btc_8852c_rf_dl[] = { +static const struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8852c_rf_dl_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> reserved for shared-antenna */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -3270,10 +3270,10 @@ const struct rtw89_chip_info rtw8852c_chip_info = { .rssi_tol = 2, .mon_reg_num = ARRAY_SIZE(rtw89_btc_8852c_mon_reg), .mon_reg = rtw89_btc_8852c_mon_reg, - .rf_para_ulink_num = ARRAY_SIZE(rtw89_btc_8852c_rf_ul), - .rf_para_ulink = rtw89_btc_8852c_rf_ul, - .rf_para_dlink_num = ARRAY_SIZE(rtw89_btc_8852c_rf_dl), - .rf_para_dlink = rtw89_btc_8852c_rf_dl, + .rf_para_ulink_v0 = rtw89_btc_8852c_rf_ul_v0, + .rf_para_dlink_v0 = rtw89_btc_8852c_rf_dl_v0, + .rf_para_ulink_num_v0 = ARRAY_SIZE(rtw89_btc_8852c_rf_ul_v0), + .rf_para_dlink_num_v0 = ARRAY_SIZE(rtw89_btc_8852c_rf_dl_v0), .rf_para_ulink_v9 = NULL, .rf_para_dlink_v9 = NULL, .rf_para_ulink_num_v9 = 0, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922a.c b/drivers/net/wireless/realtek/rtw89/rtw8922a.c index 91897aeced28..6d4301661b04 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922a.c @@ -2874,7 +2874,7 @@ s8 rtw8922a_btc_get_bt_rssi(struct rtw89_dev *rtwdev, s8 val) return clamp_t(s8, val, -100, 0) + 100; } -static const struct rtw89_btc_rf_trx_para rtw89_btc_8922a_rf_ul[] = { +static const struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8922a_rf_ul_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> for BT-connected ACI issue && BTG co-rx */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -2886,7 +2886,7 @@ static const struct rtw89_btc_rf_trx_para rtw89_btc_8922a_rf_ul[] = { {13, 1, 0, 7} }; -static const struct rtw89_btc_rf_trx_para rtw89_btc_8922a_rf_dl[] = { +static const struct rtw89_btc_rf_trx_para_v0 rtw89_btc_8922a_rf_dl_v0[] = { {255, 0, 0, 7}, /* 0 -> original */ {255, 2, 0, 7}, /* 1 -> reserved for shared-antenna */ {255, 0, 0, 7}, /* 2 ->reserved for shared-antenna */ @@ -3254,10 +3254,10 @@ const struct rtw89_chip_info rtw8922a_chip_info = { .rssi_tol = 2, .mon_reg_num = ARRAY_SIZE(rtw89_btc_8922a_mon_reg), .mon_reg = rtw89_btc_8922a_mon_reg, - .rf_para_ulink_num = ARRAY_SIZE(rtw89_btc_8922a_rf_ul), - .rf_para_ulink = rtw89_btc_8922a_rf_ul, - .rf_para_dlink_num = ARRAY_SIZE(rtw89_btc_8922a_rf_dl), - .rf_para_dlink = rtw89_btc_8922a_rf_dl, + .rf_para_ulink_v0 = rtw89_btc_8922a_rf_ul_v0, + .rf_para_dlink_v0 = rtw89_btc_8922a_rf_dl_v0, + .rf_para_ulink_num_v0 = ARRAY_SIZE(rtw89_btc_8922a_rf_ul_v0), + .rf_para_dlink_num_v0 = ARRAY_SIZE(rtw89_btc_8922a_rf_dl_v0), .rf_para_ulink_v9 = NULL, .rf_para_dlink_v9 = NULL, .rf_para_ulink_num_v9 = 0, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c index 888973f4ef95..f78d6d46e65f 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c @@ -3430,6 +3430,10 @@ const struct rtw89_chip_info rtw8922d_chip_info = { .rssi_tol = 2, .mon_reg_num = ARRAY_SIZE(rtw89_btc_8922d_mon_reg), .mon_reg = rtw89_btc_8922d_mon_reg, + .rf_para_ulink_v0 = NULL, + .rf_para_dlink_v0 = NULL, + .rf_para_ulink_num_v0 = 0, + .rf_para_dlink_num_v0 = 0, .rf_para_ulink_v9 = rtw89_btc_8922d_rf_ul_v9, .rf_para_dlink_v9 = rtw89_btc_8922d_rf_dl_v9, .rf_para_ulink_num_v9 = ARRAY_SIZE(rtw89_btc_8922d_rf_ul_v9), From 600649fa9c10e517818bdf42c911fe207bbc243f Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Wed, 24 Jun 2026 11:39:39 +0800 Subject: [PATCH 0142/1433] wifi: rtw89: coex: Renaming drvinfo_type to drvinfo_ver It's more closing to the original meaning. It is defined for rearranging driver info index by firmware support version. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260624033941.45918-9-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 84 +++++++++++------------ drivers/net/wireless/realtek/rtw89/core.h | 2 +- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index dd9d6cbc2943..572eed7939e1 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -138,7 +138,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 7, .fcxbtver = 7, .fcxbtscan = 7, .fcxbtafh = 7, .fcxbtdevinfo = 7, .fwlrole = 7, .frptmap = 3, .fcxctrl = 7, .fcxinit = 7, - .fwevntrptl = 1, .fwc2hfunc = 2, .drvinfo_type = 1, .info_buf = 1800, + .fwevntrptl = 1, .fwc2hfunc = 2, .drvinfo_ver = 1, .info_buf = 1800, .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 8, .fcxtrx = 0, }, @@ -147,7 +147,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 7, .fcxbtver = 7, .fcxbtscan = 7, .fcxbtafh = 7, .fcxbtdevinfo = 7, .fwlrole = 7, .frptmap = 3, .fcxctrl = 7, .fcxinit = 7, - .fwevntrptl = 1, .fwc2hfunc = 2, .drvinfo_type = 1, .info_buf = 1800, + .fwevntrptl = 1, .fwc2hfunc = 2, .drvinfo_ver = 1, .info_buf = 1800, .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 8, .fcxtrx = 0, }, @@ -156,7 +156,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 7, .fcxbtver = 7, .fcxbtscan = 7, .fcxbtafh = 7, .fcxbtdevinfo = 7, .fwlrole = 8, .frptmap = 4, .fcxctrl = 7, .fcxinit = 7, - .fwevntrptl = 1, .fwc2hfunc = 3, .drvinfo_type = 2, .info_buf = 1800, + .fwevntrptl = 1, .fwc2hfunc = 3, .drvinfo_ver = 2, .info_buf = 1800, .max_role_num = 6, .fcxosi = 1, .fcxmlo = 1, .bt_desired = 9, .fcxtrx = 7, }, @@ -165,7 +165,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 7, .fcxbtver = 7, .fcxbtscan = 7, .fcxbtafh = 7, .fcxbtdevinfo = 7, .fwlrole = 8, .frptmap = 4, .fcxctrl = 7, .fcxinit = 7, - .fwevntrptl = 1, .fwc2hfunc = 3, .drvinfo_type = 2, .info_buf = 1800, + .fwevntrptl = 1, .fwc2hfunc = 3, .drvinfo_ver = 2, .info_buf = 1800, .max_role_num = 6, .fcxosi = 1, .fcxmlo = 1, .bt_desired = 9, .fcxtrx = 0, }, @@ -174,7 +174,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 7, .fcxbtver = 7, .fcxbtscan = 7, .fcxbtafh = 7, .fcxbtdevinfo = 7, .fwlrole = 8, .frptmap = 3, .fcxctrl = 7, .fcxinit = 7, - .fwevntrptl = 1, .fwc2hfunc = 1, .drvinfo_type = 1, .info_buf = 1800, + .fwevntrptl = 1, .fwc2hfunc = 1, .drvinfo_ver = 1, .info_buf = 1800, .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, .fcxtrx = 0, }, @@ -183,7 +183,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fcxstep = 3, .fcxnullsta = 2, .fcxmreg = 2, .fcxgpiodbg = 1, .fcxbtver = 1, .fcxbtscan = 2, .fcxbtafh = 2, .fcxbtdevinfo = 1, .fwlrole = 2, .frptmap = 3, .fcxctrl = 1, .fcxinit = 0, - .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1800, + .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_ver = 0, .info_buf = 1800, .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, .fcxtrx = 0, }, @@ -192,7 +192,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fcxstep = 3, .fcxnullsta = 2, .fcxmreg = 1, .fcxgpiodbg = 1, .fcxbtver = 1, .fcxbtscan = 1, .fcxbtafh = 2, .fcxbtdevinfo = 1, .fwlrole = 1, .frptmap = 3, .fcxctrl = 1, .fcxinit = 0, - .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1280, + .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_ver = 0, .info_buf = 1280, .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, .fcxtrx = 0, }, @@ -201,7 +201,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fcxstep = 3, .fcxnullsta = 2, .fcxmreg = 1, .fcxgpiodbg = 1, .fcxbtver = 1, .fcxbtscan = 1, .fcxbtafh = 2, .fcxbtdevinfo = 1, .fwlrole = 1, .frptmap = 2, .fcxctrl = 1, .fcxinit = 0, - .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1280, + .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_ver = 0, .info_buf = 1280, .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, .fcxtrx = 0, }, @@ -210,7 +210,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fcxstep = 3, .fcxnullsta = 2, .fcxmreg = 1, .fcxgpiodbg = 1, .fcxbtver = 1, .fcxbtscan = 1, .fcxbtafh = 1, .fcxbtdevinfo = 1, .fwlrole = 1, .frptmap = 2, .fcxctrl = 1, .fcxinit = 0, - .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1280, + .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_ver = 0, .info_buf = 1280, .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, .fcxtrx = 0, }, @@ -219,7 +219,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 7, .fcxbtver = 7, .fcxbtscan = 7, .fcxbtafh = 7, .fcxbtdevinfo = 7, .fwlrole = 7, .frptmap = 3, .fcxctrl = 7, .fcxinit = 7, - .fwevntrptl = 1, .fwc2hfunc = 2, .drvinfo_type = 1, .info_buf = 1800, + .fwevntrptl = 1, .fwc2hfunc = 2, .drvinfo_ver = 1, .info_buf = 1800, .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 8, .fcxtrx = 0, }, @@ -228,7 +228,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fcxstep = 3, .fcxnullsta = 2, .fcxmreg = 2, .fcxgpiodbg = 1, .fcxbtver = 1, .fcxbtscan = 2, .fcxbtafh = 2, .fcxbtdevinfo = 1, .fwlrole = 2, .frptmap = 3, .fcxctrl = 1, .fcxinit = 0, - .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1800, + .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_ver = 0, .info_buf = 1800, .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, .fcxtrx = 0, }, @@ -237,7 +237,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fcxstep = 3, .fcxnullsta = 2, .fcxmreg = 1, .fcxgpiodbg = 1, .fcxbtver = 1, .fcxbtscan = 1, .fcxbtafh = 2, .fcxbtdevinfo = 1, .fwlrole = 1, .frptmap = 3, .fcxctrl = 1, .fcxinit = 0, - .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1800, + .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_ver = 0, .info_buf = 1800, .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, .fcxtrx = 0, }, @@ -246,7 +246,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fcxstep = 3, .fcxnullsta = 2, .fcxmreg = 1, .fcxgpiodbg = 1, .fcxbtver = 1, .fcxbtscan = 1, .fcxbtafh = 1, .fcxbtdevinfo = 1, .fwlrole = 1, .frptmap = 1, .fcxctrl = 1, .fcxinit = 0, - .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1280, + .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_ver = 0, .info_buf = 1280, .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, .fcxtrx = 0, }, @@ -255,7 +255,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fcxstep = 3, .fcxnullsta = 2, .fcxmreg = 1, .fcxgpiodbg = 1, .fcxbtver = 1, .fcxbtscan = 1, .fcxbtafh = 2, .fcxbtdevinfo = 1, .fwlrole = 1, .frptmap = 3, .fcxctrl = 1, .fcxinit = 0, - .fwevntrptl = 0, .fwc2hfunc = 0, .drvinfo_type = 0, .info_buf = 1280, + .fwevntrptl = 0, .fwc2hfunc = 0, .drvinfo_ver = 0, .info_buf = 1280, .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, .fcxtrx = 0, }, @@ -264,7 +264,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fcxstep = 2, .fcxnullsta = 1, .fcxmreg = 1, .fcxgpiodbg = 1, .fcxbtver = 1, .fcxbtscan = 1, .fcxbtafh = 1, .fcxbtdevinfo = 1, .fwlrole = 0, .frptmap = 0, .fcxctrl = 0, .fcxinit = 0, - .fwevntrptl = 0, .fwc2hfunc = 0, .drvinfo_type = 0, .info_buf = 1024, + .fwevntrptl = 0, .fwc2hfunc = 0, .drvinfo_ver = 0, .info_buf = 1024, .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, .fcxtrx = 0, }, @@ -275,7 +275,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fcxstep = 2, .fcxnullsta = 1, .fcxmreg = 1, .fcxgpiodbg = 1, .fcxbtver = 1, .fcxbtscan = 1, .fcxbtafh = 1, .fcxbtdevinfo = 1, .fwlrole = 0, .frptmap = 0, .fcxctrl = 0, .fcxinit = 0, - .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_type = 0, .info_buf = 1024, + .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_ver = 0, .info_buf = 1024, .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, .fcxtrx = 0, }, @@ -2789,62 +2789,62 @@ static void _fw_set_policy(struct rtw89_dev *rtwdev, u16 policy_type, rtw89_set_coex_ctrl_lps(rtwdev, btc->lps); } -static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 type) +static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index) { struct rtw89_btc *btc = &rtwdev->btc; const struct rtw89_btc_ver *ver = btc->ver; - switch (type) { + switch (index) { case CXDRVINFO_INIT: if (ver->fcxinit == 7) - rtw89_fw_h2c_cxdrv_init_v7(rtwdev, type); + rtw89_fw_h2c_cxdrv_init_v7(rtwdev, index); else - rtw89_fw_h2c_cxdrv_init(rtwdev, type); + rtw89_fw_h2c_cxdrv_init(rtwdev, index); break; case CXDRVINFO_ROLE: if (ver->fwlrole == 0) - rtw89_fw_h2c_cxdrv_role(rtwdev, type); + rtw89_fw_h2c_cxdrv_role(rtwdev, index); else if (ver->fwlrole == 1) - rtw89_fw_h2c_cxdrv_role_v1(rtwdev, type); + rtw89_fw_h2c_cxdrv_role_v1(rtwdev, index); else if (ver->fwlrole == 2) - rtw89_fw_h2c_cxdrv_role_v2(rtwdev, type); + rtw89_fw_h2c_cxdrv_role_v2(rtwdev, index); else if (ver->fwlrole == 7) - rtw89_fw_h2c_cxdrv_role_v7(rtwdev, type); + rtw89_fw_h2c_cxdrv_role_v7(rtwdev, index); else if (ver->fwlrole == 8) - rtw89_fw_h2c_cxdrv_role_v8(rtwdev, type); + rtw89_fw_h2c_cxdrv_role_v8(rtwdev, index); break; case CXDRVINFO_CTRL: - if (ver->drvinfo_type == 1) - type = 2; + if (ver->drvinfo_ver == 1) + index = 2; if (ver->fcxctrl == 7) - rtw89_fw_h2c_cxdrv_ctrl_v7(rtwdev, type); + rtw89_fw_h2c_cxdrv_ctrl_v7(rtwdev, index); else - rtw89_fw_h2c_cxdrv_ctrl(rtwdev, type); + rtw89_fw_h2c_cxdrv_ctrl(rtwdev, index); break; case CXDRVINFO_TRX: - if (ver->drvinfo_type == 1) - type = 3; + if (ver->drvinfo_ver == 1) + index = 3; if (ver->fcxtrx == 7) - rtw89_fw_h2c_cxdrv_trx_v7(rtwdev, type); + rtw89_fw_h2c_cxdrv_trx_v7(rtwdev, index); else if (ver->fcxtrx == 9) - rtw89_fw_h2c_cxdrv_trx_v9(rtwdev, type); + rtw89_fw_h2c_cxdrv_trx_v9(rtwdev, index); break; case CXDRVINFO_RFK: - if (ver->drvinfo_type == 1) + if (ver->drvinfo_ver == 1) return; - rtw89_fw_h2c_cxdrv_rfk(rtwdev, type); + rtw89_fw_h2c_cxdrv_rfk(rtwdev, index); break; case CXDRVINFO_TXPWR: - if (ver->drvinfo_type == 3) - type = 4; + if (ver->drvinfo_ver == 3) + index = 4; if (ver->fcxtrx == 7) - rtw89_fw_h2c_cxtxpwr_v7(rtwdev, type); + rtw89_fw_h2c_cxtxpwr_v7(rtwdev, index); else if (ver->fcxtrx == 9) - rtw89_fw_h2c_cxtxpwr_v9(rtwdev, type); + rtw89_fw_h2c_cxtxpwr_v9(rtwdev, index); break; case CXDRVINFO_FDDT: case CXDRVINFO_MLO: @@ -2852,12 +2852,12 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 type) if (!ver->fcxosi) return; - if (ver->drvinfo_type == 2) - type = 7; + if (ver->drvinfo_ver == 2) + index = 7; else return; - rtw89_fw_h2c_cxdrv_osi_info(rtwdev, type); + rtw89_fw_h2c_cxdrv_osi_info(rtwdev, index); break; default: break; diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 01c7d36d8e12..d3a21e9ea883 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -3365,7 +3365,7 @@ struct rtw89_btc_ver { u8 fwevntrptl; u8 fwc2hfunc; - u8 drvinfo_type; + u8 drvinfo_ver; u16 info_buf; u8 max_role_num; u8 fcxosi; From 5c071a06bbba0f36806cd3ac4a8bb3403c20fd16 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Wed, 24 Jun 2026 11:39:40 +0800 Subject: [PATCH 0143/1433] wifi: rtw89: coex: Add Wi-Fi firmware 0.35.94.1 support for RTL8922D The firmware 0.35.94.1 included several new features. Wi-Fi TX power setting offload to firmware. Including dual BT / dual Wi-Fi MAC related configurations. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260624033941.45918-10-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 62 +++++++++++++++++++---- drivers/net/wireless/realtek/rtw89/core.h | 3 ++ 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 572eed7939e1..6f9bb31b5263 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -133,6 +133,15 @@ static const u32 cxtbl[] = { static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { /* firmware version must be in decreasing order for each chip */ + {RTL8922D, RTW89_FW_VER_CODE(0, 35, 94, 0), + .fcxbtcrpt = 11, .fcxtdma = 8, .fcxslots = 7, .fcxcysta = 8, + .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 8, + .fcxbtver = 8, .fcxbtscan = 8, .fcxbtafh = 8, .fcxbtdevinfo = 8, + .fwlrole = 10, .frptmap = 5, .fcxctrl = 9, .fcxinit = 10, + .fwevntrptl = 1, .fwc2hfunc = 4, .drvinfo_ver = 3, .info_buf = 1800, + .max_role_num = 6, .fcxosi = 6, .fcxmlo = 2, .bt_desired = 8, + .fcxtrx = 9, + }, {RTL8852BT, RTW89_FW_VER_CODE(0, 29, 122, 0), .fcxbtcrpt = 8, .fcxtdma = 7, .fcxslots = 7, .fcxcysta = 7, .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 7, @@ -156,7 +165,7 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 7, .fcxbtver = 7, .fcxbtscan = 7, .fcxbtafh = 7, .fcxbtdevinfo = 7, .fwlrole = 8, .frptmap = 4, .fcxctrl = 7, .fcxinit = 7, - .fwevntrptl = 1, .fwc2hfunc = 3, .drvinfo_ver = 2, .info_buf = 1800, + .fwevntrptl = 1, .fwc2hfunc = 3, .drvinfo_ver = 3, .info_buf = 1800, .max_role_num = 6, .fcxosi = 1, .fcxmlo = 1, .bt_desired = 9, .fcxtrx = 7, }, @@ -2423,6 +2432,7 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) break; case 3: case 4: + case 5: bit_map = BIT(5); break; default: @@ -2438,6 +2448,7 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) break; case 3: case 4: + case 5: bit_map = BIT(6); break; default: @@ -2451,6 +2462,7 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) break; case 3: case 4: + case 5: bit_map = BIT(7); break; default: @@ -2465,6 +2477,7 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) case 3: break; case 4: + case 5: bit_map = BIT(8); break; default: @@ -2481,6 +2494,7 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) bit_map = BIT(8); break; case 4: + case 5: bit_map = BIT(9); break; default: @@ -2488,7 +2502,10 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) } break; case RPT_EN_TEST: - bit_map = BIT(31); + if (ver->frptmap == 5) + bit_map = BIT(10); + else + bit_map = BIT(31); break; case RPT_EN_WL_ALL: switch (ver->frptmap) { @@ -2501,6 +2518,7 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) bit_map = GENMASK(2, 0) | BIT(8); break; case 4: + case 5: bit_map = GENMASK(2, 0) | BIT(9); break; default: @@ -2520,6 +2538,7 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) bit_map = GENMASK(7, 3); break; case 4: + case 5: bit_map = GENMASK(8, 3); break; default: @@ -2539,6 +2558,7 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) bit_map = GENMASK(8, 0); break; case 4: + case 5: bit_map = GENMASK(9, 0); break; default: @@ -2558,6 +2578,7 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) bit_map = GENMASK(8, 2); break; case 4: + case 5: bit_map = GENMASK(9, 2); break; default: @@ -2814,7 +2835,7 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index) rtw89_fw_h2c_cxdrv_role_v8(rtwdev, index); break; case CXDRVINFO_CTRL: - if (ver->drvinfo_ver == 1) + if (ver->drvinfo_ver != 0) index = 2; if (ver->fcxctrl == 7) @@ -2823,7 +2844,7 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index) rtw89_fw_h2c_cxdrv_ctrl(rtwdev, index); break; case CXDRVINFO_TRX: - if (ver->drvinfo_ver == 1) + if (ver->drvinfo_ver > 1) index = 3; if (ver->fcxtrx == 7) @@ -2832,7 +2853,7 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index) rtw89_fw_h2c_cxdrv_trx_v9(rtwdev, index); break; case CXDRVINFO_RFK: - if (ver->drvinfo_ver == 1) + if (ver->drvinfo_ver != 0) return; rtw89_fw_h2c_cxdrv_rfk(rtwdev, index); @@ -2847,12 +2868,26 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index) rtw89_fw_h2c_cxtxpwr_v9(rtwdev, index); break; case CXDRVINFO_FDDT: + if (ver->drvinfo_ver == 3) + index = 5; + else + return; + + rtw89_debug(rtwdev, RTW89_DBG_BTC, "drv_info FDDT index=%d\n", index); + break; case CXDRVINFO_MLO: + if (ver->drvinfo_ver == 3) + index = 6; + else + return; + + rtw89_debug(rtwdev, RTW89_DBG_BTC, "drv_info MLO index=%d\n", index); + break; case CXDRVINFO_OSI: if (!ver->fcxosi) return; - if (ver->drvinfo_ver == 2) + if (ver->drvinfo_ver > 1) index = 7; else return; @@ -8828,7 +8863,7 @@ static u8 rtw89_btc_c2h_get_index_by_ver(struct rtw89_dev *rtwdev, u8 func) return BTF_EVNT_BUF_OVERFLOW; else if (ver->fwc2hfunc == 2) return func; - else if (ver->fwc2hfunc == 3) + else if (ver->fwc2hfunc == 3 || ver->fwc2hfunc == 4) return BTF_EVNT_BUF_OVERFLOW; else return BTF_EVNT_MAX; @@ -8839,19 +8874,26 @@ static u8 rtw89_btc_c2h_get_index_by_ver(struct rtw89_dev *rtwdev, u8 func) return BTF_EVNT_C2H_LOOPBACK; else if (ver->fwc2hfunc == 2) return func; - else if (ver->fwc2hfunc == 3) + else if (ver->fwc2hfunc == 3 || ver->fwc2hfunc == 4) return BTF_EVNT_C2H_LOOPBACK; else return BTF_EVNT_MAX; case BTF_EVNT_C2H_LOOPBACK: if (ver->fwc2hfunc == 2) return func; - else if (ver->fwc2hfunc == 3) + else if (ver->fwc2hfunc == 3 || ver->fwc2hfunc == 4) return BTF_EVNT_BT_LEAUDIO_INFO; else return BTF_EVNT_MAX; case BTF_EVNT_BT_QUERY_TXPWR: - if (ver->fwc2hfunc == 3) + if (ver->fwc2hfunc == 3 || ver->fwc2hfunc == 4) + return func; + else + return BTF_EVNT_MAX; + case BTF_EVNT_ZB_INFO: + case BTF_EVNT_ZB_CH: + case BTF_EVNT_ZB_QUERY_TXPWR: + if (ver->fwc2hfunc == 4) return func; else return BTF_EVNT_MAX; diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index d3a21e9ea883..5dde620b1e5e 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -3224,6 +3224,9 @@ enum rtw89_btc_btf_fw_event { BTF_EVNT_BUF_OVERFLOW, BTF_EVNT_C2H_LOOPBACK, BTF_EVNT_BT_QUERY_TXPWR, /* fwc2hfunc > 3 */ + BTF_EVNT_ZB_INFO = 11, + BTF_EVNT_ZB_CH = 12, + BTF_EVNT_ZB_QUERY_TXPWR = 13, BTF_EVNT_MAX, }; From 9a149cf572e90157769ab3a8a3bc8cf5601dcfaa Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Wed, 24 Jun 2026 11:39:41 +0800 Subject: [PATCH 0144/1433] wifi: rtw89: coex: Add RTL8922D chip string Add string for logic using and show logs. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260624033941.45918-11-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 6f9bb31b5263..1361d4d54528 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -313,6 +313,7 @@ static u32 chip_id_to_bt_rom_code_id(u32 id) case RTL8851B: return 0x8851; case RTL8922A: + case RTL8922D: return 0x8922; default: return 0; @@ -347,6 +348,8 @@ static char *chip_id_str(u32 id) return "RTL8851B"; case RTL8922A: return "RTL8922A"; + case RTL8922D: + return "RTL8922D"; default: return "UNKNOWN"; } From 0819de0fd2906236dd38fdd89e52dcb53cd853b2 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Thu, 25 Jun 2026 14:15:36 +0800 Subject: [PATCH 0145/1433] wifi: rtw89: mac: finish active TX immediately without waiting for DMAC Currently active TX only finishes after ensuring PCIE and DMAC become idle. However, the waiting time might be long. Since the packet is already transmitted over the air, update the registers to finish active TX immediately, regardless of the PCIE/DMAC status. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260625061545.44808-2-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/mac_be.c | 3 +++ drivers/net/wireless/realtek/rtw89/reg.h | 28 +++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/mac_be.c b/drivers/net/wireless/realtek/rtw89/mac_be.c index f24c119b99f1..70e6c9d21986 100644 --- a/drivers/net/wireless/realtek/rtw89/mac_be.c +++ b/drivers/net/wireless/realtek/rtw89/mac_be.c @@ -1196,6 +1196,9 @@ static int scheduler_init_be(struct rtw89_dev *rtwdev, u8 mac_idx) rtw89_io_pack(rtwdev); + reg = rtw89_mac_reg_by_idx(rtwdev, R_BE_MISC_1, mac_idx); + rtw89_write32_set(rtwdev, reg, B_BE_EN_TX_FINISH_PRD_RESP); + if (chip->chip_id == RTL8922D) { reg = rtw89_mac_reg_by_idx(rtwdev, R_BE_SCH_EXT_CTRL, mac_idx); rtw89_write32_set(rtwdev, reg, B_BE_CWCNT_PLUS_MODE); diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h index 086ef77ebb9f..bf1c6cb0ae9c 100644 --- a/drivers/net/wireless/realtek/rtw89/reg.h +++ b/drivers/net/wireless/realtek/rtw89/reg.h @@ -6858,6 +6858,34 @@ #define B_BE_MUEDCA_EN_MASK GENMASK(1, 0) #define B_BE_MUEDCA_EN_0 BIT(0) +#define R_BE_MISC_1 0x1037C +#define R_BE_MISC_1_C1 0x1437C +#define B_BE_PPS_REMAIN_TIME_MODE BIT(31) +#define B_BE_PPS_IDLE_SORT_EN BIT(30) +#define B_BE_SR_TXOP_USE_SR_PERIOD_EN BIT(29) +#define B_BE_SCH_CCA_PIFS_CLK_GATING_DIS BIT(28) +#define B_BE_SR_TXOP_EN BIT(27) +#define B_BE_SCH_ABORT_CNT_SIFS_EN BIT(26) +#define B_BE_SCH_ABORT_CNT_TB_EN BIT(25) +#define B_BE_SCH_ABORT_CNT_CTN_EN BIT(24) +#define B_BE_SR_CCA_PER20_BITMAP_EN BIT(23) +#define B_BE_SR_CCA_S80_EN BIT(22) +#define B_BE_SR_CCA_S40_EN BIT(21) +#define B_BE_SR_CCA_S20_EN BIT(20) +#define B_BE_EN_TX_FINISH_PRD_RESP BIT(18) +#define B_BE_RESP_TX_ABORT_NON_IDLE BIT(17) +#define B_BE_RESP_TX_ABORT_QUICK_EN BIT(16) +#define B_BE_PREBKF_CHK_LINK_BUSY BIT(15) +#define B_BE_SCH_MSD_PRD_RST_EDCA_EN BIT(14) +#define B_BE_LINK_BUSY_RST_EDCA_EN_MASK GENMASK(13, 12) +#define B_BE_RX_TSFT_SYNC_BYPASS_FCS BIT(11) +#define B_BE_RX_TSFT_DIFF_THD_MASK GENMASK(10, 8) +#define B_BE_CAL_TBTT_OV_EN BIT(5) +#define B_BE_SUBBCN_MS_CNT_MODE BIT(3) +#define B_BE_CAL_ALWAYS_EN BIT(2) +#define B_BE_SIFS_TIMER_AUTO_RST_EN BIT(1) +#define B_BE_CHK_HAS_SIFS_TX_ABORT BIT(0) + #define R_BE_CTN_DRV_TXEN 0x10398 #define R_BE_CTN_DRV_TXEN_C1 0x14398 #define B_BE_CTN_TXEN_TWT_3 BIT(17) From 14dfbfeba17b98d5cb3a7e31bbff4d21e74302c9 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Thu, 25 Jun 2026 14:15:37 +0800 Subject: [PATCH 0146/1433] wifi: rtw89: mac: pass chip version to firmware Set chip version to register shared with firmware before downloading firmware, so firmware can run proper flow according to the version. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260625061545.44808-3-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/mac_be.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/mac_be.c b/drivers/net/wireless/realtek/rtw89/mac_be.c index 70e6c9d21986..4cb48cf9415a 100644 --- a/drivers/net/wireless/realtek/rtw89/mac_be.c +++ b/drivers/net/wireless/realtek/rtw89/mac_be.c @@ -641,6 +641,7 @@ static void set_cpu_en(struct rtw89_dev *rtwdev, bool include_bb) static int wcpu_on(struct rtw89_dev *rtwdev, u8 boot_reason, bool dlfw) { const struct rtw89_chip_info *chip = rtwdev->chip; + struct rtw89_hal *hal = &rtwdev->hal; u32 val32; int ret; @@ -683,6 +684,8 @@ static int wcpu_on(struct rtw89_dev *rtwdev, u8 boot_reason, bool dlfw) if (chip->chip_id != RTL8922A) rtw89_write32_set(rtwdev, R_BE_WCPU_FW_CTRL, B_BE_HOST_EXIST); + rtw89_write32_mask(rtwdev, R_BE_WCPU_FW_CTRL, + B_BE_WCPU_ROM_CUT_VAL_MASK, hal->cv + 1); rtw89_write16_mask(rtwdev, R_BE_BOOT_REASON, B_BE_BOOT_REASON_MASK, boot_reason); rtw89_write32_clr(rtwdev, R_BE_PLATFORM_ENABLE, B_BE_WCPU_EN); rtw89_write32_clr(rtwdev, R_BE_PLATFORM_ENABLE, B_BE_HOLD_AFTER_RESET); From e50c0fb7867e6b2b0714c79b8385ab6db2c5567a Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Thu, 25 Jun 2026 14:15:38 +0800 Subject: [PATCH 0147/1433] wifi: rtw89: fw: lower debug level for UDM1 debug register The UDM1 is user define message to record count of H2C command sent by driver and received by firmware. Normally, this value should be zero. Otherwise, throw a warning. For the new chip RTL8922DE, its default value is not zero, causing a warning at first time probe. Since this is a debug purpose and the value will be set to zero right after this checking, lower the debug level. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260625061545.44808-4-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/mac_be.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/mac_be.c b/drivers/net/wireless/realtek/rtw89/mac_be.c index 4cb48cf9415a..d9c93adb58ee 100644 --- a/drivers/net/wireless/realtek/rtw89/mac_be.c +++ b/drivers/net/wireless/realtek/rtw89/mac_be.c @@ -652,8 +652,10 @@ static int wcpu_on(struct rtw89_dev *rtwdev, u8 boot_reason, bool dlfw) } val32 = rtw89_read32(rtwdev, R_BE_UDM1); if (val32) { - rtw89_warn(rtwdev, "[SER] AON L2 Debug register not empty before Boot.\n"); - rtw89_warn(rtwdev, "[SER] %s: R_BE_UDM1 = 0x%x\n", __func__, val32); + rtw89_debug(rtwdev, RTW89_DBG_UNEXP, + "[SER] AON L2 Debug register not empty before Boot.\n"); + rtw89_debug(rtwdev, RTW89_DBG_UNEXP, + "[SER] %s: R_BE_UDM1 = 0x%x\n", __func__, val32); } val32 = rtw89_read32(rtwdev, R_BE_UDM2); if (val32) { From c99498b4cbd75f7e1b45e354c2108f4646c0944b Mon Sep 17 00:00:00 2001 From: Dian-Syuan Yang Date: Thu, 25 Jun 2026 14:15:39 +0800 Subject: [PATCH 0148/1433] wifi: rtw89: drop packet offload entry on H2C addition failure to avoid scan issue A special case is when C2H done ack has been completed, but the corresponding packet offload response has not actually been received, which causes the add packet offload to fail. In this state, firmware treats the entry as added, so subsequent add requests for the same id are rejected as duplicates. To recover from this, send a delete packet offload H2C command to roll back the normal state. It has been tested and verified to have no functional side effect. Signed-off-by: Dian-Syuan Yang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260625061545.44808-5-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/fw.c | 26 ++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index a20bc9aa5b26..ac8e0e034a59 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -6652,8 +6652,8 @@ int rtw89_fw_h2c_del_pkt_offload(struct rtw89_dev *rtwdev, u8 id) return 0; } -int rtw89_fw_h2c_add_pkt_offload(struct rtw89_dev *rtwdev, u8 *id, - struct sk_buff *skb_ofld) +static int __rtw89_fw_h2c_add_pkt_offload(struct rtw89_dev *rtwdev, u8 *id, + struct sk_buff *skb_ofld) { struct rtw89_wait_info *wait = &rtwdev->mac.fw_ofld_wait; struct sk_buff *skb; @@ -6695,13 +6695,33 @@ int rtw89_fw_h2c_add_pkt_offload(struct rtw89_dev *rtwdev, u8 *id, rtw89_debug(rtwdev, RTW89_DBG_FW, "failed to add pkt ofld: id %d, ret %d\n", alloc_id, ret); + /* + * Firmware may consider that it has added this entry + * successfully even though the H2C return timeout. + * Send a delete H2C command to drop it, and thus the + * next add on the same id won't be rejected as duplicate. + */ + rtw89_fw_h2c_del_pkt_offload(rtwdev, alloc_id); rtw89_core_release_bit_map(rtwdev->pkt_offload, alloc_id); - return ret; + + return -EAGAIN; } return 0; } +int rtw89_fw_h2c_add_pkt_offload(struct rtw89_dev *rtwdev, u8 *id, + struct sk_buff *skb_ofld) +{ + int ret; + + ret = __rtw89_fw_h2c_add_pkt_offload(rtwdev, id, skb_ofld); + if (ret == -EAGAIN) + ret = __rtw89_fw_h2c_add_pkt_offload(rtwdev, id, skb_ofld); + + return ret; +} + static int rtw89_fw_h2c_scan_list_offload_ax(struct rtw89_dev *rtwdev, int ch_num, struct list_head *chan_list) From b993046234fc3110f5ca3eb561492f8072ccc689 Mon Sep 17 00:00:00 2001 From: Chih-Kang Chang Date: Thu, 25 Jun 2026 14:15:40 +0800 Subject: [PATCH 0149/1433] wifi: rtw89: disable sniffer mode in RX filter when initialization for Wi-Fi 7 chips Sniffer mode is enabled by default in the RX filter on Wi-Fi 7 chips, which causes all packets to be received regardless of the ADDR_CAM lookup result. This may result in unexpected packets being received. Therefore, disable it by default. Signed-off-by: Chih-Kang Chang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260625061545.44808-6-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/mac_be.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/mac_be.c b/drivers/net/wireless/realtek/rtw89/mac_be.c index d9c93adb58ee..14f1e30066e9 100644 --- a/drivers/net/wireless/realtek/rtw89/mac_be.c +++ b/drivers/net/wireless/realtek/rtw89/mac_be.c @@ -1333,7 +1333,7 @@ static int rx_fltr_init_be(struct rtw89_dev *rtwdev, u8 mac_idx) reg = rtw89_mac_reg_by_idx(rtwdev, R_BE_RX_FLTR_OPT, mac_idx); val = B_BE_A_BC_CAM_MATCH | B_BE_A_UC_CAM_MATCH | B_BE_A_MC | - B_BE_A_BC | B_BE_A_A1_MATCH | B_BE_SNIFFER_MODE | + B_BE_A_BC | B_BE_A_A1_MATCH | u32_encode_bits(15, B_BE_UID_FILTER_MASK); rtw89_write32(rtwdev, reg, val); u32p_replace_bits(&rtwdev->hal.rx_fltr, 15, B_BE_UID_FILTER_MASK); From 0ec249ffc060cd91f3f6cefd7e7008c17bcb20b9 Mon Sep 17 00:00:00 2001 From: Chih-Kang Chang Date: Thu, 25 Jun 2026 14:15:41 +0800 Subject: [PATCH 0150/1433] wifi: rtw89: pci: disable phy error flag related to refclk On some platforms, refclk is not available up to 15 ms after entering suspend. The delayed clock cause the hardware to detect falsely error and trigger an unexpected hardware reset. Disable the phy error flag related to refclk to fix it. Signed-off-by: Chih-Kang Chang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260625061545.44808-7-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/pci.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/pci.h b/drivers/net/wireless/realtek/rtw89/pci.h index c3f2d0df5846..92c30c7f9fb2 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.h +++ b/drivers/net/wireless/realtek/rtw89/pci.h @@ -58,7 +58,7 @@ #define B_AX_DIV GENMASK(15, 14) #define RAC_SET_PPR_V1 0x31 #define RAC_ANA40 0x40 -#define PHY_ERR_IMR_DIS (BIT(9) | BIT(8) | BIT(0)) +#define PHY_ERR_IMR_DIS (BIT(9) | BIT(2) | BIT(1) | BIT(0)) #define RAC_ANA41 0x41 #define PHY_ERR_FLAG_EN BIT(6) From c1eabaaa088ddbb1b937cd339adfa4c18e93c93d Mon Sep 17 00:00:00 2001 From: Zong-Zhe Yang Date: Thu, 25 Jun 2026 14:15:42 +0800 Subject: [PATCH 0151/1433] wifi: rtw89: fw: fix link ID filling for LPS MLO common info The link ID field in H2C command of LPS MLO common info is incorrectly filled with the PHY index. Fix it with the target link ID. Fixes: 20380a039ddd ("wifi: rtw89: phy: add H2C command to send detail RX gain and link parameters for PS mode") Signed-off-by: Zong-Zhe Yang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260625061545.44808-8-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/fw.c | 4 ++-- drivers/net/wireless/realtek/rtw89/fw.h | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index ac8e0e034a59..09338466be95 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -3454,7 +3454,7 @@ int rtw89_fw_h2c_lps_ml_cmn_info_v1(struct rtw89_dev *rtwdev, h2c->rfe_type = efuse->rfe_type; h2c->rssi_main = U8_MAX; - memset(h2c->link_id, 0xfe, RTW89_BB_PS_LINK_BUF_MAX); + memset(h2c->link_id, RTW89_BB_PS_LINK_ID_SKIP, RTW89_BB_PS_LINK_BUF_MAX); rtw89_vif_for_each_link(rtwvif, rtwvif_link, link_id) { u8 phy_idx = rtwvif_link->phy_idx; @@ -3462,7 +3462,7 @@ int rtw89_fw_h2c_lps_ml_cmn_info_v1(struct rtw89_dev *rtwdev, bb = rtw89_get_bb_ctx(rtwdev, phy_idx); chan = rtw89_chan_get(rtwdev, rtwvif_link->chanctx_idx); - h2c->link_id[phy_idx] = phy_idx; + h2c->link_id[phy_idx] = link_id; h2c->central_ch[phy_idx] = chan->channel; h2c->pri_ch[phy_idx] = chan->primary_channel; h2c->band[phy_idx] = chan->band_type; diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index 79e9af3a6805..71e8554a7af7 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -2053,6 +2053,8 @@ enum rtw89_bb_link_rx_gain_table_type { RTW89_BB_PS_LINK_RX_GAIN_TAB_MAX, }; +#define RTW89_BB_PS_LINK_ID_SKIP 0xfe + enum rtw89_bb_ps_link_buf_id { RTW89_BB_PS_LINK_BUF_0 = 0x00, RTW89_BB_PS_LINK_BUF_1 = 0x01, From 76edcedda6437647ecd09b4b47593990a003b07a Mon Sep 17 00:00:00 2001 From: Chin-Yen Lee Date: Thu, 25 Jun 2026 14:15:43 +0800 Subject: [PATCH 0152/1433] wifi: rtw89: wow: use MLD address in WoWLAN ARP replies for MLO stations Currently, WoWLAN ARP replies for MLO stations use the link address in the ARP hardware address fields. As a result, peers may learn the link address from the ARP reply and use it as the destination address for subsequent traffic. Some APs may not forward frames addressed to the link address, causing connectivity issues. Use the MLD address instead when generating WoWLAN ARP replies so peers learn the correct address for MLO stations. Signed-off-by: Chin-Yen Lee Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260625061545.44808-9-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/fw.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 09338466be95..44eb71c38580 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -2982,9 +2982,9 @@ static struct sk_buff *rtw89_arp_response_get(struct rtw89_dev *rtwdev, arp_hdr->ar_pln = 4; arp_hdr->ar_op = htons(ARPOP_REPLY); - ether_addr_copy(arp_skb->sender_hw, rtwvif_link->mac_addr); + ether_addr_copy(arp_skb->sender_hw, rtwvif->mac_addr); arp_skb->sender_ip = rtwvif->ip_addr; - ether_addr_copy(arp_skb->target_hw, rtwvif_link->mac_addr); + ether_addr_copy(arp_skb->target_hw, rtwvif->mac_addr); arp_skb->target_ip = rtwvif->ip_addr; return skb; From 03a963f4aeda538acad50806e94ef25d95b78743 Mon Sep 17 00:00:00 2001 From: Chin-Yen Lee Date: Thu, 25 Jun 2026 14:15:44 +0800 Subject: [PATCH 0153/1433] wifi: rtw89: wow: add QoS control field to WoWLAN ARP response for MLO Some MLO APs expect WoWLAN ARP response frames to be transmitted as QoS data frames and may discard frames that do not contain a QoS Control field. Add a QoS Control field and use the QoS Data subtype when generating WoWLAN ARP responses for MLD vifs. Keep the existing frame format unchanged for non-MLO connections. This allows WoWLAN ARP responses to be accepted by MLO APs while preserving compatibility with legacy APs. Signed-off-by: Chin-Yen Lee Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260625061545.44808-10-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/fw.c | 35 ++++++++++++++++++------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 44eb71c38580..9d98805835d6 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -2943,6 +2943,7 @@ static struct sk_buff *rtw89_sa_query_get(struct rtw89_dev *rtwdev, static struct sk_buff *rtw89_arp_response_get(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link) { + struct ieee80211_vif *vif = rtwvif_to_vif(rtwvif_link->rtwvif); struct rtw89_vif *rtwvif = rtwvif_link->rtwvif; u8 sec_hdr_len = rtw89_wow_get_sec_hdr_len(rtwdev); struct rtw89_wow_param *rtw_wow = &rtwdev->wow; @@ -2950,26 +2951,42 @@ static struct sk_buff *rtw89_arp_response_get(struct rtw89_dev *rtwdev, struct rtw89_arp_rsp *arp_skb; struct arphdr *arp_hdr; struct sk_buff *skb; - __le16 fc; + bool with_qos; + u16 fc; - skb = dev_alloc_skb(sizeof(*hdr) + sec_hdr_len + sizeof(*arp_skb)); + with_qos = ieee80211_vif_is_mld(vif); + + rtw89_debug(rtwdev, RTW89_DBG_WOW, "[arp_reply] with qos field: %s\n", + str_yes_no(with_qos)); + + skb = dev_alloc_skb(sizeof(*hdr) + sec_hdr_len + sizeof(*arp_skb) + + (with_qos ? 2 : 0)); if (!skb) return NULL; hdr = skb_put_zero(skb, sizeof(*hdr)); - if (rtw_wow->ptk_alg) - fc = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_FCTL_TODS | - IEEE80211_FCTL_PROTECTED); - else - fc = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_FCTL_TODS); + fc = IEEE80211_FTYPE_DATA | IEEE80211_FCTL_TODS; + + if (rtw_wow->ptk_alg) + fc |= IEEE80211_FCTL_PROTECTED; + + if (with_qos) + fc |= IEEE80211_STYPE_QOS_DATA; + else + fc |= IEEE80211_STYPE_DATA; + + hdr->frame_control = cpu_to_le16(fc); - hdr->frame_control = fc; ether_addr_copy(hdr->addr1, rtwvif_link->bssid); ether_addr_copy(hdr->addr2, rtwvif_link->mac_addr); ether_addr_copy(hdr->addr3, rtwvif_link->bssid); - skb_put_zero(skb, sec_hdr_len); + if (with_qos) + skb_put_zero(skb, sizeof(__le16)); + + if (sec_hdr_len) + skb_put_zero(skb, sec_hdr_len); arp_skb = skb_put_zero(skb, sizeof(*arp_skb)); memcpy(arp_skb->llc_hdr, rfc1042_header, sizeof(rfc1042_header)); From dbff9040587e9bd6d3ee337315971957f9f76612 Mon Sep 17 00:00:00 2001 From: Chih-Kang Chang Date: Thu, 25 Jun 2026 14:15:45 +0800 Subject: [PATCH 0154/1433] wifi: rtw89: wow: only WiFi 6 chips initialize RF registers in WoWLAN mode Only the WiFi 6 chips need to initialize RF register when WoWLAN download FW for some power save issue. Applying the same initialization flow to WiFi 7 chips might trigger the error 'RF parameters exceed size. path=1, idx=1500.'. This happens because normal mode uses rtw89_phy_config_rf_reg_v1(), which skips registers with addresses below 0x100. However, WoWLAN mode uses rtw89_phy_config_rf_reg_noio(), and WiFi 7 chips do not satisfy the rtw89_chip_rf_v1() condition. As a result, more RF registers are configured, causing the size overflow error. Signed-off-by: Chih-Kang Chang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260625061545.44808-11-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/wow.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/wow.c b/drivers/net/wireless/realtek/rtw89/wow.c index 8dadd8df4fc6..a7539f91264d 100644 --- a/drivers/net/wireless/realtek/rtw89/wow.c +++ b/drivers/net/wireless/realtek/rtw89/wow.c @@ -1299,7 +1299,8 @@ static int rtw89_wow_swap_fw(struct rtw89_dev *rtwdev, bool wow) if (disable_intr_for_dlfw) rtw89_hci_enable_intr(rtwdev); - rtw89_phy_init_rf_reg(rtwdev, true); + if (chip->chip_gen == RTW89_CHIP_AX) + rtw89_phy_init_rf_reg(rtwdev, true); ret = rtw89_fw_h2c_role_maintain(rtwdev, rtwvif_link, rtwsta_link, RTW89_ROLE_FW_RESTORE); From a8cddb62c573f28eef5f887a8f3156e8ee22776a Mon Sep 17 00:00:00 2001 From: Dmitry Morgun Date: Mon, 29 Jun 2026 09:44:52 +0000 Subject: [PATCH 0155/1433] wifi: rtw89: check return values in rtw89_ops_start_ap() Several functions called in rtw89_ops_start_ap() may fail to allocate skb or fail to send H2C command to firmware, returning -ENOMEM or an error code. Their return values are ignored, so subsequent commands are executed with incorrect state. Check the return values and propagate errors. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: a52e4f2ce0f5 ("rtw89: implement ieee80211_ops::start_ap and stop_ap") Signed-off-by: Dmitry Morgun Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260629094452.8709-1-d.morgun@ispras.ru --- drivers/net/wireless/realtek/rtw89/mac80211.c | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/mac80211.c b/drivers/net/wireless/realtek/rtw89/mac80211.c index aade5c5b79e8..e381aacda667 100644 --- a/drivers/net/wireless/realtek/rtw89/mac80211.c +++ b/drivers/net/wireless/realtek/rtw89/mac80211.c @@ -826,11 +826,36 @@ static int rtw89_ops_start_ap(struct ieee80211_hw *hw, ether_addr_copy(rtwvif_link->bssid, link_conf->bssid); rtw89_cam_bssid_changed(rtwdev, rtwvif_link); - rtw89_mac_port_update(rtwdev, rtwvif_link); - rtw89_chip_h2c_assoc_cmac_tbl(rtwdev, rtwvif_link, NULL); - rtw89_fw_h2c_role_maintain(rtwdev, rtwvif_link, NULL, RTW89_ROLE_TYPE_CHANGE); - rtw89_fw_h2c_join_info(rtwdev, rtwvif_link, NULL, true); - rtw89_fw_h2c_cam(rtwdev, rtwvif_link, NULL, NULL, RTW89_ROLE_TYPE_CHANGE); + ret = rtw89_mac_port_update(rtwdev, rtwvif_link); + if (ret) { + rtw89_warn(rtwdev, "failed to update mac port\n"); + return ret; + } + + ret = rtw89_chip_h2c_assoc_cmac_tbl(rtwdev, rtwvif_link, NULL); + if (ret) { + rtw89_warn(rtwdev, "failed to send h2c cmac table\n"); + return ret; + } + + ret = rtw89_fw_h2c_role_maintain(rtwdev, rtwvif_link, NULL, RTW89_ROLE_TYPE_CHANGE); + if (ret) { + rtw89_warn(rtwdev, "failed to send h2c role info\n"); + return ret; + } + + ret = rtw89_fw_h2c_join_info(rtwdev, rtwvif_link, NULL, true); + if (ret) { + rtw89_warn(rtwdev, "failed to send h2c join info\n"); + return ret; + } + + ret = rtw89_fw_h2c_cam(rtwdev, rtwvif_link, NULL, NULL, RTW89_ROLE_TYPE_CHANGE); + if (ret) { + rtw89_warn(rtwdev, "failed to send h2c cam\n"); + return ret; + } + rtw89_chip_rfk_channel(rtwdev, rtwvif_link); if (RTW89_CHK_FW_FEATURE(NOTIFY_AP_INFO, &rtwdev->fw)) { From 2aba608a86e9b099c9af2ea70b620552dee2b628 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Tue, 30 Jun 2026 15:28:27 +0800 Subject: [PATCH 0156/1433] wifi: rtw89: fix HE extended capability length check rtw89_mac_check_he_obss_narrow_bw_ru_iter() reads extended capability byte 10, but rejects only datalen values below 10. Byte 10 requires at least 11 bytes. Require datalen >= 11 before reading data[10]. Fixes: 8d540f9d2916 ("wifi: rtw89: disable 26-tone RU HE TB PPDU transmissions") Signed-off-by: Pengpeng Hou Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/2026063009025530.2-ccfa108-0024-wifi-rtw89-fix-HE-extended--pengpeng@iscas.ac.cn --- drivers/net/wireless/realtek/rtw89/mac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/mac.c b/drivers/net/wireless/realtek/rtw89/mac.c index 8c395517bd2f..99de1b202976 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.c +++ b/drivers/net/wireless/realtek/rtw89/mac.c @@ -5167,7 +5167,7 @@ static void rtw89_mac_check_he_obss_narrow_bw_ru_iter(struct wiphy *wiphy, elem = cfg80211_find_elem(WLAN_EID_EXT_CAPABILITY, ies->data, ies->len); - if (!elem || elem->datalen < 10 || + if (!elem || elem->datalen < 11 || !(elem->data[10] & WLAN_EXT_CAPA10_OBSS_NARROW_BW_RU_TOLERANCE_SUPPORT)) *tolerated = false; rcu_read_unlock(); From 04a46c2dbf3b516c96a1d19b72917b56055ad6c1 Mon Sep 17 00:00:00 2001 From: William Hansen-Baird Date: Tue, 30 Jun 2026 16:15:51 +0200 Subject: [PATCH 0157/1433] wifi: rtlwifi: fix disabling of ASPM for RTL8723BE with AER flooding commit 77a6407c6ab2 ("wifi: rtlwifi: disable ASPM for RTL8723BE with subsystem ID 11ad:1723") adds code which sets ppsc->support_aspm to false in _rtl_pci_update_default_setting() in order to disable ASPM. This does not, however, disable ASPM. Rather, it disables driver control of ASPM, and blocks calls to rtl_pci_enable_aspm() and rtl_pci_disable_aspm(). In some cases, the pci device supplied to the probe function has ASPM enabled. The code would therefore not disable ASPM, as it means to, but rather just leave it enabled. This was discovered through testing on a Razer Blade 14 2017. Implement a new __rtl_pci_disable_aspm(hw) function which does not check ppsc->support_aspm before disabling and call it from rtl_pci_disable_aspm(). Then move the code added in the previous commit to rtl_pci_init_aspm() to allow adding a call to __rtl_pci_disable_aspm(hw). This makes sure ASPM is disabled while still disabling driver control of ASPM to block it from being enabled later. Signed-off-by: William Hansen-Baird Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260630141553.785769-2-william.hansen.baird@gmail.com --- drivers/net/wireless/realtek/rtlwifi/pci.c | 39 ++++++++++++++-------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/drivers/net/wireless/realtek/rtlwifi/pci.c b/drivers/net/wireless/realtek/rtlwifi/pci.c index 73018a0498b4..f706575f92ce 100644 --- a/drivers/net/wireless/realtek/rtlwifi/pci.c +++ b/drivers/net/wireless/realtek/rtlwifi/pci.c @@ -156,15 +156,6 @@ static void _rtl_pci_update_default_setting(struct ieee80211_hw *hw) PCI_EXP_LNKCTL_ASPM_L1 | PCI_EXP_LNKCTL_CCC)) ppsc->support_aspm = false; - /* RTL8723BE found on some ASUSTek laptops, such as F441U and - * X555UQ with subsystem ID 11ad:1723 are known to output large - * amounts of PCIe AER errors during and after boot up, causing - * heavy lags, poor network throughput, and occasional lock-ups. - */ - if (rtlpriv->rtlhal.hw_type == HARDWARE_TYPE_RTL8723BE && - (rtlpci->pdev->subsystem_vendor == 0x11ad && - rtlpci->pdev->subsystem_device == 0x1723)) - ppsc->support_aspm = false; } static bool _rtl_pci_platform_switch_device_pci_aspm( @@ -203,7 +194,7 @@ static void _rtl_pci_switch_clk_req(struct ieee80211_hw *hw, u16 value) } /*Disable RTL8192SE ASPM & Disable Pci Bridge ASPM*/ -static void rtl_pci_disable_aspm(struct ieee80211_hw *hw) +static void __rtl_pci_disable_aspm(struct ieee80211_hw *hw) { struct rtl_priv *rtlpriv = rtl_priv(hw); struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); @@ -215,9 +206,6 @@ static void rtl_pci_disable_aspm(struct ieee80211_hw *hw) u16 aspmlevel = 0; u16 tmp_u1b = 0; - if (!ppsc->support_aspm) - return; - if (pcibridge_vendor == PCI_BRIDGE_VENDOR_UNKNOWN) { rtl_dbg(rtlpriv, COMP_POWER, DBG_TRACE, "PCI(Bridge) UNKNOWN\n"); @@ -240,6 +228,16 @@ static void rtl_pci_disable_aspm(struct ieee80211_hw *hw) _rtl_pci_platform_switch_device_pci_aspm(hw, linkctrl_reg); } +static void rtl_pci_disable_aspm(struct ieee80211_hw *hw) +{ + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + if (!ppsc->support_aspm) + return; + + __rtl_pci_disable_aspm(hw); +} + /*Enable RTL8192SE ASPM & Enable Pci Bridge ASPM for *power saving We should follow the sequence to enable *RTL8192SE first then enable Pci Bridge ASPM @@ -330,10 +328,25 @@ static void rtl_pci_parse_configuration(struct pci_dev *pdev, static void rtl_pci_init_aspm(struct ieee80211_hw *hw) { + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_priv *rtlpriv = rtl_priv(hw); _rtl_pci_update_default_setting(hw); + /* + * RTL8723BE found on some ASUSTek laptops, such as F441U and + * X555UQ with subsystem ID 11ad:1723 are known to output large + * amounts of PCIe AER errors during and after boot up, causing + * heavy lags, poor network throughput, and occasional lock-ups. + */ + if (rtlpriv->rtlhal.hw_type == HARDWARE_TYPE_RTL8723BE && + (rtlpci->pdev->subsystem_vendor == 0x11ad && + rtlpci->pdev->subsystem_device == 0x1723)) { + __rtl_pci_disable_aspm(hw); + ppsc->support_aspm = false; + } + if (ppsc->reg_rfps_level & RT_RF_PS_LEVEL_ALWAYS_ASPM) { /*Always enable ASPM & Clock Req. */ rtl_pci_enable_aspm(hw); From 676e59a3825c6dff56318f0a502769853b72f223 Mon Sep 17 00:00:00 2001 From: William Hansen-Baird Date: Tue, 30 Jun 2026 16:15:52 +0200 Subject: [PATCH 0158/1433] wifi: rtlwifi: convert pci if-statement to ID table Refactor the ASUSTek quirk logic from an if-statement to a standard rtl_aspm_quirks pci_device_id table. This allows future devices with the same quirk to be added more easily while avoiding a large if-chain. Signed-off-by: William Hansen-Baird Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260630141553.785769-3-william.hansen.baird@gmail.com --- drivers/net/wireless/realtek/rtlwifi/pci.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/realtek/rtlwifi/pci.c b/drivers/net/wireless/realtek/rtlwifi/pci.c index f706575f92ce..9a9c895b0bef 100644 --- a/drivers/net/wireless/realtek/rtlwifi/pci.c +++ b/drivers/net/wireless/realtek/rtlwifi/pci.c @@ -31,6 +31,12 @@ static const u8 ac_to_hwq[] = { BK_QUEUE }; +static const struct pci_device_id rtl_aspm_quirks[] = { + /* ASUSTek F441U/X555UQ */ + { PCI_DEVICE_SUB(PCI_VENDOR_ID_REALTEK, 0xb723, 0x11ad, 0x1723) }, + {} +}; + static u8 _rtl_mac_to_hwqueue(struct ieee80211_hw *hw, struct sk_buff *skb) { struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); @@ -330,19 +336,15 @@ static void rtl_pci_init_aspm(struct ieee80211_hw *hw) { struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); - struct rtl_priv *rtlpriv = rtl_priv(hw); _rtl_pci_update_default_setting(hw); /* - * RTL8723BE found on some ASUSTek laptops, such as F441U and - * X555UQ with subsystem ID 11ad:1723 are known to output large + * Certain pci devices are known to output large * amounts of PCIe AER errors during and after boot up, causing * heavy lags, poor network throughput, and occasional lock-ups. */ - if (rtlpriv->rtlhal.hw_type == HARDWARE_TYPE_RTL8723BE && - (rtlpci->pdev->subsystem_vendor == 0x11ad && - rtlpci->pdev->subsystem_device == 0x1723)) { + if (pci_match_id(rtl_aspm_quirks, rtlpci->pdev)) { __rtl_pci_disable_aspm(hw); ppsc->support_aspm = false; } From 2b7858891b100587c10c136cf07205335a897be0 Mon Sep 17 00:00:00 2001 From: William Hansen-Baird Date: Tue, 30 Jun 2026 16:15:53 +0200 Subject: [PATCH 0159/1433] wifi: rtlwifi: disable ASPM for RTL8723BE with subsystem ID 17aa:b736 RTL8723BE outputs a large amount of PCIe AER errors during and after boot, even before probe and when driver is never loaded. This causes significant system slowdown. The errors are the same as reported by commit 77a6407c6ab2 ("wifi: rtlwifi: disable ASPM for RTL8723BE with subsystem ID 11ad:1723") Add the RTL8723BE with subsystem ID 17aa:b736 to the rtl_aspm_quirks table to stop the AER errors. AER errors can still be present prior to pci probe, as the device by default may have ASPM enabled. Testing on a Razer Blade 14 2017 which shipped from the OEM equipped with an RTL8723BE card with this subsystem ID confirms that this patch resolves the AER flood and allows the wireless card to function normally once the driver takes over. Signed-off-by: William Hansen-Baird Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260630141553.785769-4-william.hansen.baird@gmail.com --- drivers/net/wireless/realtek/rtlwifi/pci.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/realtek/rtlwifi/pci.c b/drivers/net/wireless/realtek/rtlwifi/pci.c index 9a9c895b0bef..220fb4dc7927 100644 --- a/drivers/net/wireless/realtek/rtlwifi/pci.c +++ b/drivers/net/wireless/realtek/rtlwifi/pci.c @@ -34,6 +34,8 @@ static const u8 ac_to_hwq[] = { static const struct pci_device_id rtl_aspm_quirks[] = { /* ASUSTek F441U/X555UQ */ { PCI_DEVICE_SUB(PCI_VENDOR_ID_REALTEK, 0xb723, 0x11ad, 0x1723) }, + /* Razer Blade 14 2017 */ + { PCI_DEVICE_SUB(PCI_VENDOR_ID_REALTEK, 0xb723, 0x17aa, 0xb736) }, {} }; From 2ed8d5c72488bca9666fefa942ed2dc07cc0c56a Mon Sep 17 00:00:00 2001 From: Pengfei Zhang Date: Tue, 30 Jun 2026 16:42:20 +0800 Subject: [PATCH 0160/1433] ipv4: fib: fix route re-dump in inet_dump_fib() on multi-batch dump inet_dump_fib() saves its progress in cb->args[1] as a positional index within the current hash chain. Between batches, a concurrent fib_new_table() can insert a new table at the chain head, shifting all existing entries. On resume the saved index lands on a different table, causing already-dumped tables to be re-dumped and the originally suspended table to restart from the beginning. Fix by storing tb->tb_id in cb->args[1] instead of a positional index, mirroring the fix applied to inet6_dump_fib() in commit 9facb861dc6b ("ipv6: fib6: fix NULL deref in fib6_walk_continue() on multi-batch dump"). Signed-off-by: Pengfei Zhang Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260630084220.2711025-1-zhangfeionline@gmail.com Signed-off-by: Paolo Abeni --- net/ipv4/fib_frontend.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/net/ipv4/fib_frontend.c b/net/ipv4/fib_frontend.c index 54eb72695093..a5e739d32d59 100644 --- a/net/ipv4/fib_frontend.c +++ b/net/ipv4/fib_frontend.c @@ -1038,10 +1038,11 @@ static int inet_dump_fib(struct sk_buff *skb, struct netlink_callback *cb) .dump_routes = true, .dump_exceptions = true, }; - unsigned int e = 0, s_e, h, s_h; struct hlist_head *head; int dumped = 0, err = 0; struct fib_table *tb; + unsigned int h, s_h; + u32 s_id; rcu_read_lock(); if (cb->strict_check) { @@ -1073,29 +1074,28 @@ static int inet_dump_fib(struct sk_buff *skb, struct netlink_callback *cb) } s_h = cb->args[0]; - s_e = cb->args[1]; + s_id = cb->args[1]; err = 0; - for (h = s_h; h < FIB_TABLE_HASHSZ; h++, s_e = 0) { - e = 0; + for (h = s_h; h < FIB_TABLE_HASHSZ; h++, s_id = 0) { head = &net->ipv4.fib_table_hash[h]; hlist_for_each_entry_rcu(tb, head, tb_hlist) { - if (e < s_e) - goto next; + if (s_id && tb->tb_id != s_id) + continue; + + s_id = 0; if (dumped) memset(&cb->args[2], 0, sizeof(cb->args) - 2 * sizeof(cb->args[0])); + cb->args[1] = tb->tb_id; err = fib_table_dump(tb, skb, cb, &filter); if (err < 0) goto out; dumped = 1; -next: - e++; } } out: - cb->args[1] = e; cb->args[0] = h; unlock: From 7cb8198761e627ff3a3b4770c8f147e75c4e649d Mon Sep 17 00:00:00 2001 From: Yuyang Huang Date: Tue, 30 Jun 2026 20:02:05 +0900 Subject: [PATCH 0161/1433] net: ipv4: report multicast group user count RTM_GETMULTICAST has been part of the rtnetlink ABI for a long time and already reports IPv4 multicast group membership through IFA_MULTICAST and IFA_CACHEINFO. It does not report how many consumers hold each membership, so userspace still has to parse /proc/net/igmp to get the Users column. Add IFA_MC_USERS as a u32 attribute carrying ip_mc_list::users in RTM_GETMULTICAST replies and entry-lifecycle notifications. This gives iproute2 enough information to migrate the IPv4 part of "ip maddr show" from procfs parsing to rtnetlink. Signed-off-by: Yuyang Huang Reviewed-by: Vadim Fedorenko Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260630110207.37841-2-sigefriedhyy@gmail.com Signed-off-by: Paolo Abeni --- Documentation/netlink/specs/rt-addr.yaml | 4 ++++ include/uapi/linux/if_addr.h | 1 + net/ipv4/igmp.c | 2 ++ 3 files changed, 7 insertions(+) diff --git a/Documentation/netlink/specs/rt-addr.yaml b/Documentation/netlink/specs/rt-addr.yaml index 163a106c41bb..0ecbd24c890c 100644 --- a/Documentation/netlink/specs/rt-addr.yaml +++ b/Documentation/netlink/specs/rt-addr.yaml @@ -123,6 +123,9 @@ attribute-sets: - name: proto type: u8 + - + name: mc-users + type: u32 operations: @@ -176,6 +179,7 @@ operations: value: 58 attributes: &mcaddr-attrs - multicast + - mc-users - cacheinfo dump: request: diff --git a/include/uapi/linux/if_addr.h b/include/uapi/linux/if_addr.h index aa7958b4e41d..7fb630b7fe31 100644 --- a/include/uapi/linux/if_addr.h +++ b/include/uapi/linux/if_addr.h @@ -36,6 +36,7 @@ enum { IFA_RT_PRIORITY, /* u32, priority/metric for prefix route */ IFA_TARGET_NETNSID, IFA_PROTO, /* u8, address protocol */ + IFA_MC_USERS, /* u32, multicast group users */ __IFA_MAX, }; diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c index b6337a47c141..116ce7cec80e 100644 --- a/net/ipv4/igmp.c +++ b/net/ipv4/igmp.c @@ -1473,6 +1473,7 @@ int inet_fill_ifmcaddr(struct sk_buff *skb, struct net_device *dev, ci.ifa_valid = INFINITY_LIFE_TIME; if (nla_put_in_addr(skb, IFA_MULTICAST, im->multiaddr) < 0 || + nla_put_u32(skb, IFA_MC_USERS, READ_ONCE(im->users)) < 0 || nla_put(skb, IFA_CACHEINFO, sizeof(ci), &ci) < 0) { nlmsg_cancel(skb, nlh); return -EMSGSIZE; @@ -1494,6 +1495,7 @@ static void inet_ifmcaddr_notify(struct net_device *dev, skb = nlmsg_new(NLMSG_ALIGN(sizeof(struct ifaddrmsg)) + nla_total_size(sizeof(__be32)) + + nla_total_size(sizeof(u32)) + nla_total_size(sizeof(struct ifa_cacheinfo)), GFP_KERNEL); if (!skb) From e1d0f3f0839103bc5387d2fa78eb1fd9af2d1fe1 Mon Sep 17 00:00:00 2001 From: Yuyang Huang Date: Tue, 30 Jun 2026 20:02:06 +0900 Subject: [PATCH 0162/1433] net: ipv6: report multicast group user count The previous patch added IFA_MC_USERS and emits it for IPv4 multicast groups. Add the same snapshot attribute to IPv6 RTM_GETMULTICAST replies and entry-lifecycle notifications, carrying ifmcaddr6::mca_users. This makes the multicast rtnetlink ABI symmetric across IPv4 and IPv6 and gives userspace the same user count that /proc/net/igmp6 exposes. Signed-off-by: Yuyang Huang Reviewed-by: Vadim Fedorenko Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260630110207.37841-3-sigefriedhyy@gmail.com Signed-off-by: Paolo Abeni --- net/ipv6/addrconf.c | 1 + net/ipv6/mcast.c | 1 + 2 files changed, 2 insertions(+) diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index cbe681de3818..f1fe9ede1edb 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -5264,6 +5264,7 @@ int inet6_fill_ifmcaddr(struct sk_buff *skb, put_ifaddrmsg(nlh, 128, IFA_F_PERMANENT, scope, ifindex); if (nla_put_in6_addr(skb, IFA_MULTICAST, &ifmca->mca_addr) < 0 || + nla_put_u32(skb, IFA_MC_USERS, READ_ONCE(ifmca->mca_users)) < 0 || put_cacheinfo(skb, ifmca->mca_cstamp, READ_ONCE(ifmca->mca_tstamp), INFINITY_LIFE_TIME, INFINITY_LIFE_TIME) < 0) { nlmsg_cancel(skb, nlh); diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index 04b811b3be97..774f4c72a6fa 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -908,6 +908,7 @@ static void inet6_ifmcaddr_notify(struct net_device *dev, skb = nlmsg_new(NLMSG_ALIGN(sizeof(struct ifaddrmsg)) + nla_total_size(sizeof(struct in6_addr)) + + nla_total_size(sizeof(u32)) + nla_total_size(sizeof(struct ifa_cacheinfo)), GFP_KERNEL); if (!skb) From 3373cb099e6692b219328960e1d1ea70b978f20b Mon Sep 17 00:00:00 2001 From: Yuyang Huang Date: Tue, 30 Jun 2026 20:02:07 +0900 Subject: [PATCH 0163/1433] selftests: net: check multicast group user count Extend the RTM_GETMULTICAST dump test to verify IFA_MC_USERS for both IPv4 and IPv6 multicast groups. Run each protocol test in a fresh network namespace to avoid changing host-network state or racing with unrelated multicast users. Join a fixed multicast group twice using separate sockets and check that the reported user count increases by two. Signed-off-by: Yuyang Huang Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260630110207.37841-4-sigefriedhyy@gmail.com Signed-off-by: Paolo Abeni --- tools/testing/selftests/net/rtnetlink.py | 101 ++++++++++++++++++++--- 1 file changed, 90 insertions(+), 11 deletions(-) diff --git a/tools/testing/selftests/net/rtnetlink.py b/tools/testing/selftests/net/rtnetlink.py index 3622413d793d..0c67c7c00d84 100755 --- a/tools/testing/selftests/net/rtnetlink.py +++ b/tools/testing/selftests/net/rtnetlink.py @@ -2,27 +2,106 @@ # SPDX-License-Identifier: GPL-2.0 import socket +import struct import time -from lib.py import bkg, ip, ksft_exit, ksft_run, ksft_ge, ksft_true, KsftSkipEx +from lib.py import bkg, ip, ksft_exit, ksft_run, ksft_eq, ksft_ge, ksft_true, KsftSkipEx from lib.py import CmdExitFailure, NetNS, NetNSEnter, RtnlAddrFamily IPV4_ALL_HOSTS_MULTICAST = b'\xe0\x00\x00\x01' +IPV4_TEST_MULTICAST = b'\xef\x01\x01\x01' +IPV6_TEST_MULTICAST = bytes.fromhex('ff020000000000000000000000000123') + + +def _users_for(rtnl: RtnlAddrFamily, family: int, grp: bytes, ifindex: int): + """Return mc-users for grp on ifindex, or 0 if absent.""" + + addrs = rtnl.getmulticast({"ifa-family": family}, dump=True) + matches = [addr for addr in addrs + if addr['multicast'] == grp and addr['ifa-index'] == ifindex] + if not matches: + return 0 + if 'mc-users' not in matches[0]: + return None + + return matches[0]['mc-users'] + def dump_mcaddr_check() -> None: """ - Verify that at least one interface has the IPv4 all-hosts multicast address. - At least the loopback interface should have this address. + Verify IPv4 multicast addresses and their user counts in RTM_GETMULTICAST. """ - rtnl = RtnlAddrFamily() - addresses = rtnl.getmulticast({"ifa-family": socket.AF_INET}, dump=True) + with NetNS() as ns: + with NetNSEnter(str(ns)): + ip("link set lo up") + rtnl = RtnlAddrFamily() + lo_idx = socket.if_nametoindex('lo') + addresses = rtnl.getmulticast({"ifa-family": socket.AF_INET}, dump=True) - all_host_multicasts = [ - addr for addr in addresses if addr['multicast'] == IPV4_ALL_HOSTS_MULTICAST - ] + all_host_multicasts = [ + addr for addr in addresses + if addr['multicast'] == IPV4_ALL_HOSTS_MULTICAST + ] + + ksft_ge(len(all_host_multicasts), 1, + "No interface found with the IPv4 all-hosts multicast address") + + mreq = IPV4_TEST_MULTICAST + socket.inet_aton('127.0.0.1') + before = _users_for(rtnl, socket.AF_INET, IPV4_TEST_MULTICAST, lo_idx) + if before is None: + raise KsftSkipEx("kernel does not expose IFA_MC_USERS") + + s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + s1.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) + s2.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) + + after_join = _users_for(rtnl, socket.AF_INET, + IPV4_TEST_MULTICAST, lo_idx) + if after_join is None: + raise KsftSkipEx("kernel does not expose IFA_MC_USERS") + ksft_eq(after_join - before, 2, + f"users delta != 2 after two joins " + f"(before={before}, after={after_join})") + finally: + s1.close() + s2.close() + + +def dump_mcaddr6_check() -> None: + """ + Verify IPv6 multicast addresses and their user counts in RTM_GETMULTICAST. + """ + + with NetNS() as ns: + with NetNSEnter(str(ns)): + ip("link set lo up") + rtnl = RtnlAddrFamily() + lo_idx = socket.if_nametoindex('lo') + before = _users_for(rtnl, socket.AF_INET6, + IPV6_TEST_MULTICAST, lo_idx) + if before is None: + raise KsftSkipEx("kernel does not expose IFA_MC_USERS for IPv6") + + mreq = IPV6_TEST_MULTICAST + struct.pack('=I', lo_idx) + s1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) + s2 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) + try: + s1.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq) + s2.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq) + + after_join = _users_for(rtnl, socket.AF_INET6, + IPV6_TEST_MULTICAST, lo_idx) + if after_join is None: + raise KsftSkipEx("kernel does not expose IFA_MC_USERS for IPv6") + ksft_eq(after_join - before, 2, + f"IPv6 users delta != 2 after two joins " + f"(before={before}, after={after_join})") + finally: + s1.close() + s2.close() - ksft_ge(len(all_host_multicasts), 1, - "No interface found with the IPv4 all-hosts multicast address") def ipv4_devconf_notify() -> None: """ @@ -56,7 +135,7 @@ def ipv4_devconf_notify() -> None: f"No 'forwarding on' notificiation found for interface {ifname}") def main() -> None: - ksft_run([dump_mcaddr_check, ipv4_devconf_notify]) + ksft_run([dump_mcaddr_check, dump_mcaddr6_check, ipv4_devconf_notify]) ksft_exit() if __name__ == "__main__": From df87e5c4e94e5f52020f51a071353956df814b22 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 30 Jun 2026 19:17:50 -0700 Subject: [PATCH 0164/1433] tools: ynl: pyynl: re-export the library API from the package root The public classes live in pyynl.lib, so users had to spell out from pyynl.lib import YnlFamily which I forget at least once a month. Re-export lib's API from the package __init__ so that from pyynl import YnlFamily works as well. I don't think there was a real reason not to do this? Acked-by: Jan Stancek Reviewed-by: Donald Hunter Signed-off-by: Jakub Kicinski Link: https://patch.msgid.link/20260701021751.3234681-2-kuba@kernel.org Signed-off-by: Paolo Abeni --- tools/net/ynl/pyynl/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tools/net/ynl/pyynl/__init__.py b/tools/net/ynl/pyynl/__init__.py index e69de29bb2d1..d8f59c132ab7 100644 --- a/tools/net/ynl/pyynl/__init__.py +++ b/tools/net/ynl/pyynl/__init__.py @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +""" Python YNL (YAML Netlink) library. """ + +# Re-export the public library API so it can be imported straight from the +# package, e.g. `from pyynl import YnlFamily`. +# pylint: disable=wildcard-import,unused-wildcard-import +from .lib import * +from .lib import __all__ From 10c90f1bba3ad979b77f0778e295fb974e78f0cc Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 30 Jun 2026 19:17:51 -0700 Subject: [PATCH 0165/1433] tools: ynl: pyynl: pull the --family resolution logic into the lib When packaging YNL as a system level utility we added a --family argument which auto-resolves the full spec path from a well known path in /usr/share. Spelling out full YAML spec files is at this point only done in-tree, for example in the selftests which need the very latest YAML. But the selftests have their own wrapping classes for each family so test authors aren't really bothered by having to spell the paths out. Afford the same ease of use to the Python library users. Move the path resolution from the CLI code to the library. This simplifies the pyynl use by a lot: from pyynl import YnlFamily ynl = YnlFamily(family="netdev") Unless I'm missing a trick, resolving the /usr/share path is hard enough for most users to lean towards shelling out to ynl CLI with --output-json, which is sad. The ethtool script can now use family= instead of resolving the path (the helpers are removed from cli.py so this isn't just a cleanup). Signed-off-by: Jakub Kicinski Reviewed-by: Donald Hunter Link: https://patch.msgid.link/20260701021751.3234681-3-kuba@kernel.org Signed-off-by: Paolo Abeni --- tools/net/ynl/pyynl/cli.py | 56 +++++++---------------------- tools/net/ynl/pyynl/lib/__init__.py | 3 +- tools/net/ynl/pyynl/lib/nlspec.py | 22 ++++++++++-- tools/net/ynl/pyynl/lib/specdir.py | 51 ++++++++++++++++++++++++++ tools/net/ynl/pyynl/lib/ynl.py | 19 ++++++++-- tools/net/ynl/tests/ethtool.py | 7 +--- 6 files changed, 102 insertions(+), 56 deletions(-) create mode 100644 tools/net/ynl/pyynl/lib/specdir.py diff --git a/tools/net/ynl/pyynl/cli.py b/tools/net/ynl/pyynl/cli.py index 8275a806cf73..b6a6ce12b4a7 100755 --- a/tools/net/ynl/pyynl/cli.py +++ b/tools/net/ynl/pyynl/cli.py @@ -17,9 +17,7 @@ import textwrap # pylint: disable=no-name-in-module,wrong-import-position sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix()) from lib import YnlFamily, Netlink, NlError, SpecFamily, SpecException, YnlException - -SYS_SCHEMA_DIR='/usr/share/ynl' -RELATIVE_SCHEMA_DIR='../../../../Documentation/netlink' +from lib import list_families # pylint: disable=too-few-public-methods,too-many-locals class Colors: @@ -48,30 +46,6 @@ def term_width(): """ Get terminal width in columns (80 if stdout is not a terminal) """ return shutil.get_terminal_size().columns -def schema_dir(): - """ - Return the effective schema directory, preferring in-tree before - system schema directory. - """ - script_dir = os.path.dirname(os.path.abspath(__file__)) - schema_dir_ = os.path.abspath(f"{script_dir}/{RELATIVE_SCHEMA_DIR}") - if not os.path.isdir(schema_dir_): - schema_dir_ = SYS_SCHEMA_DIR - if not os.path.isdir(schema_dir_): - raise YnlException(f"Schema directory {schema_dir_} does not exist") - return schema_dir_ - -def spec_dir(): - """ - Return the effective spec directory, relative to the effective - schema directory. - """ - spec_dir_ = schema_dir() + '/specs' - if not os.path.isdir(spec_dir_): - raise YnlException(f"Spec directory {spec_dir_} does not exist") - return spec_dir_ - - class YnlEncoder(json.JSONEncoder): """A custom encoder for emitting JSON with ynl-specific instance types""" def default(self, o): @@ -272,9 +246,8 @@ def main(): pprint.pprint(msg, width=term_width(), compact=True) if args.list_families: - for filename in sorted(os.listdir(spec_dir())): - if filename.endswith('.yaml'): - print(filename.removesuffix('.yaml')) + for family in list_families(): + print(family) return if args.no_schema: @@ -284,28 +257,23 @@ def main(): if args.json_text: attrs = json.loads(args.json_text) - if args.family: - spec = f"{spec_dir()}/{args.family}.yaml" - else: - spec = args.spec - if not os.path.isfile(spec): - raise YnlException(f"Spec file {spec} does not exist") + if args.spec and not os.path.isfile(args.spec): + raise YnlException(f"Spec file {args.spec} does not exist") + # Spec/YnlFamily will raise if both or neither spec and family are given if args.validate: + # Force validation even for installed specs (schema=True), unless the + # user explicitly picked a schema or opted out with --no-schema. + schema = True if args.schema is None else args.schema try: - SpecFamily(spec, args.schema) + SpecFamily(args.spec, schema_path=schema, family=args.family) except SpecException as error: print(error) sys.exit(1) return - if args.family: # set behaviour when using installed specs - if args.schema is None and spec.startswith(SYS_SCHEMA_DIR): - args.schema = '' # disable schema validation when installed - if args.process_unknown is None: - args.process_unknown = True - - ynl = YnlFamily(spec, args.schema, args.process_unknown, + ynl = YnlFamily(args.spec, schema=args.schema, family=args.family, + process_unknown=args.process_unknown, recv_size=args.dbg_small_recv) if args.dbg_small_recv: ynl.set_recv_dbg(True) diff --git a/tools/net/ynl/pyynl/lib/__init__.py b/tools/net/ynl/pyynl/lib/__init__.py index be741985ae4e..aa4263c8cba9 100644 --- a/tools/net/ynl/pyynl/lib/__init__.py +++ b/tools/net/ynl/pyynl/lib/__init__.py @@ -5,12 +5,13 @@ from .nlspec import SpecAttr, SpecAttrSet, SpecEnumEntry, SpecEnumSet, \ SpecFamily, SpecOperation, SpecSubMessage, SpecSubMessageFormat, \ SpecException +from .specdir import list_families from .ynl import YnlFamily, Netlink, NlError, NlPolicy, YnlException from .doc_generator import YnlDocGenerator __all__ = ["SpecAttr", "SpecAttrSet", "SpecEnumEntry", "SpecEnumSet", "SpecFamily", "SpecOperation", "SpecSubMessage", "SpecSubMessageFormat", - "SpecException", + "SpecException", "list_families", "YnlFamily", "Netlink", "NlError", "NlPolicy", "YnlException", "YnlDocGenerator"] diff --git a/tools/net/ynl/pyynl/lib/nlspec.py b/tools/net/ynl/pyynl/lib/nlspec.py index 0469a0e270d0..b4ec59814ab1 100644 --- a/tools/net/ynl/pyynl/lib/nlspec.py +++ b/tools/net/ynl/pyynl/lib/nlspec.py @@ -12,6 +12,8 @@ import importlib import os import yaml as pyyaml +from .specdir import find_spec, SYS_SCHEMA_DIR + class SpecException(Exception): """Netlink spec exception. @@ -444,7 +446,23 @@ class SpecFamily(SpecElement): except AttributeError: _yaml_loader = pyyaml.SafeLoader - def __init__(self, spec_path, schema_path=None, exclude_ops=None): + def __init__(self, spec_path=None, schema_path=None, exclude_ops=None, + family=None): + # schema_path selects how the spec is validated: + # None -- no preference: validate against the default schema, + # but trust (skip) installed specs selected by family= + # True -- always validate against the default schema + # path -- validate against this schema + # '' -- do not validate + if (spec_path is None) == (family is None): + raise ValueError("Specify exactly one of spec path or family name") + if family is not None: + spec_path = find_spec(family) + # Installed specs are assumed correct, so skip schema validation + # to save cycles unless the caller asked to validate. + if schema_path is None and spec_path.startswith(SYS_SCHEMA_DIR): + schema_path = '' + with open(spec_path, "r", encoding='utf-8') as stream: prefix = '# SPDX-License-Identifier: ' first = stream.readline().strip() @@ -465,7 +483,7 @@ class SpecFamily(SpecElement): self.proto = self.yaml.get('protocol', 'genetlink') self.msg_id_model = self.yaml['operations'].get('enum-model', 'unified') - if schema_path is None: + if schema_path is None or schema_path is True: schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml' if schema_path: with open(schema_path, "r", encoding='utf-8') as stream: diff --git a/tools/net/ynl/pyynl/lib/specdir.py b/tools/net/ynl/pyynl/lib/specdir.py new file mode 100644 index 000000000000..fcea9b9fb7b0 --- /dev/null +++ b/tools/net/ynl/pyynl/lib/specdir.py @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +""" +Locating YNL spec and schema files on disk. + +Resolves the directory holding the YAML specs (preferring an in-tree copy +over the installed system path) and maps family names to spec files. +""" + +import os + +SYS_SCHEMA_DIR='/usr/share/ynl' +RELATIVE_SCHEMA_DIR='../../../../../Documentation/netlink' + + +def schema_dir(): + """ + Return the effective schema directory, preferring in-tree before + system schema directory. + """ + script_dir = os.path.dirname(os.path.abspath(__file__)) + schema_dir_ = os.path.abspath(f"{script_dir}/{RELATIVE_SCHEMA_DIR}") + if not os.path.isdir(schema_dir_): + schema_dir_ = SYS_SCHEMA_DIR + if not os.path.isdir(schema_dir_): + raise FileNotFoundError(f"Schema directory {schema_dir_} does not exist") + return schema_dir_ + +def spec_dir(): + """ + Return the effective spec directory, relative to the effective + schema directory. + """ + spec_dir_ = schema_dir() + '/specs' + if not os.path.isdir(spec_dir_): + raise FileNotFoundError(f"Spec directory {spec_dir_} does not exist") + return spec_dir_ + + +def find_spec(family): + """ Return the path to the YAML spec file for a family by name. """ + spec = f"{spec_dir()}/{family}.yaml" + if not os.path.isfile(spec): + raise FileNotFoundError(f"Spec for family '{family}' not found at {spec}") + return spec + + +def list_families(): + """ Return the sorted names of all families with an installed spec. """ + return sorted(f.removesuffix('.yaml') + for f in os.listdir(spec_dir()) if f.endswith('.yaml')) diff --git a/tools/net/ynl/pyynl/lib/ynl.py b/tools/net/ynl/pyynl/lib/ynl.py index 092d132edec1..8682bf588e1f 100644 --- a/tools/net/ynl/pyynl/lib/ynl.py +++ b/tools/net/ynl/pyynl/lib/ynl.py @@ -661,6 +661,14 @@ class YnlFamily(SpecFamily): """ YNL family -- a Netlink interface built from a YAML spec. + The spec can be selected either by file path (def_path=) or, when it + ships in a well-known location, by family name (family="xyz"); exactly + one of the two must be given. For example: + + from pyynl import YnlFamily + + ynl = YnlFamily(family="netdev") + Primary use of the class is to execute Netlink commands: ynl.(attrs, ...) @@ -691,11 +699,16 @@ class YnlFamily(SpecFamily): ynl.get_policy(op_name, mode) -- query kernel policy for an op """ - def __init__(self, def_path, schema=None, process_unknown=False, - recv_size=0): - super().__init__(def_path, schema) + def __init__(self, def_path=None, schema=None, process_unknown=None, + recv_size=0, family=None): + super().__init__(def_path, schema, family=family) self.include_raw = False + # Specs from /usr (selected by family=) have a higher chance of being + # stale, default to ignoring unknown attrs. In-tree users, and users + # who bundle the spec need to make a conscious decision. + if process_unknown is None: + process_unknown = family is not None self.process_unknown = process_unknown try: diff --git a/tools/net/ynl/tests/ethtool.py b/tools/net/ynl/tests/ethtool.py index db3b62c652e7..0ee0c8e87686 100755 --- a/tools/net/ynl/tests/ethtool.py +++ b/tools/net/ynl/tests/ethtool.py @@ -11,12 +11,10 @@ import pathlib import pprint import sys import re -import os # pylint: disable=no-name-in-module,wrong-import-position sys.path.append(pathlib.Path(__file__).resolve().parent.parent.joinpath('pyynl').as_posix()) # pylint: disable=import-error -from cli import schema_dir, spec_dir from lib import YnlFamily @@ -173,10 +171,7 @@ def main(): args = parser.parse_args() - spec = os.path.join(spec_dir(), 'ethtool.yaml') - schema = os.path.join(schema_dir(), 'genetlink-legacy.yaml') - - ynl = YnlFamily(spec, schema, recv_size=args.dbg_small_recv) + ynl = YnlFamily(family='ethtool', recv_size=args.dbg_small_recv) if args.dbg_small_recv: ynl.set_recv_dbg(True) From ed37710d6c672561c3309dab4b86d0f18c8534ff Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 1 Jul 2026 08:22:13 +0000 Subject: [PATCH 0166/1433] macvlan: annotate data-races around vlan->mode and vlan->flags Both fields can be changed in macvlan_changelink() while being read locklessly. Add READ_ONCE()/WRITE_ONCE() annotations. Signed-off-by: Eric Dumazet Reviewed-by: Nikolay Aleksandrov Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260701082214.2974946-2-edumazet@google.com Signed-off-by: Paolo Abeni --- drivers/net/macvlan.c | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c index c40fa331836b..8b69cc9b70f9 100644 --- a/drivers/net/macvlan.c +++ b/drivers/net/macvlan.c @@ -277,7 +277,7 @@ static void macvlan_broadcast(struct sk_buff *skb, return; hash_for_each_rcu(port->vlan_hash, i, vlan, hlist) { - if (vlan->dev == src || !(vlan->mode & mode)) + if (vlan->dev == src || !(READ_ONCE(vlan->mode) & mode)) continue; hash = mc_hash(vlan, eth->h_dest); @@ -306,7 +306,7 @@ static void macvlan_multicast_rx(const struct macvlan_port *port, MACVLAN_MODE_VEPA | MACVLAN_MODE_PASSTHRU| MACVLAN_MODE_BRIDGE); - else if (src->mode == MACVLAN_MODE_VEPA) + else if (READ_ONCE(src->mode) == MACVLAN_MODE_VEPA) /* flood to everyone except source */ macvlan_broadcast(skb, port, src->dev, MACVLAN_MODE_VEPA | @@ -447,7 +447,7 @@ static bool macvlan_forward_source(struct sk_buff *skb, if (!vlan) continue; - if (vlan->flags & MACVLAN_FLAG_NODST) + if (READ_ONCE(vlan->flags) & MACVLAN_FLAG_NODST) consume = true; macvlan_forward_source_one(skb, vlan); } @@ -487,14 +487,18 @@ static rx_handler_result_t macvlan_handle_frame(struct sk_buff **pskb) return RX_HANDLER_CONSUMED; } src = macvlan_hash_lookup(port, eth->h_source); - if (src && src->mode != MACVLAN_MODE_VEPA && - src->mode != MACVLAN_MODE_BRIDGE) { - /* forward to original port. */ - vlan = src; - ret = macvlan_broadcast_one(skb, vlan, eth, 0) ?: - __netif_rx(skb); - handle_res = RX_HANDLER_CONSUMED; - goto out; + if (src) { + enum macvlan_mode mode = READ_ONCE(src->mode); + + if (mode != MACVLAN_MODE_VEPA && + mode != MACVLAN_MODE_BRIDGE) { + /* forward to original port. */ + vlan = src; + ret = macvlan_broadcast_one(skb, vlan, eth, 0) ?: + __netif_rx(skb); + handle_res = RX_HANDLER_CONSUMED; + goto out; + } } hash = mc_hash(NULL, eth->h_dest); @@ -515,7 +519,7 @@ static rx_handler_result_t macvlan_handle_frame(struct sk_buff **pskb) struct macvlan_dev, list); else vlan = macvlan_hash_lookup(port, eth->h_dest); - if (!vlan || vlan->mode == MACVLAN_MODE_SOURCE) + if (!vlan || READ_ONCE(vlan->mode) == MACVLAN_MODE_SOURCE) return RX_HANDLER_PASS; dev = vlan->dev; @@ -548,7 +552,7 @@ static int macvlan_queue_xmit(struct sk_buff *skb, struct net_device *dev) const struct macvlan_port *port = vlan->port; const struct macvlan_dev *dest; - if (vlan->mode == MACVLAN_MODE_BRIDGE) { + if (READ_ONCE(vlan->mode) == MACVLAN_MODE_BRIDGE) { const struct ethhdr *eth = skb_eth_hdr(skb); /* send to other bridge ports directly */ @@ -559,7 +563,7 @@ static int macvlan_queue_xmit(struct sk_buff *skb, struct net_device *dev) } dest = macvlan_hash_lookup(port, eth->h_dest); - if (dest && dest->mode == MACVLAN_MODE_BRIDGE) { + if (dest && READ_ONCE(dest->mode) == MACVLAN_MODE_BRIDGE) { /* send to lowerdev first for its network taps */ dev_forward_skb(vlan->lowerdev, skb); @@ -777,7 +781,7 @@ static int macvlan_set_mac_address(struct net_device *dev, void *p) if (ether_addr_equal(dev->dev_addr, addr->__data)) return 0; - if (vlan->mode == MACVLAN_MODE_PASSTHRU) { + if (READ_ONCE(vlan->mode) == MACVLAN_MODE_PASSTHRU) { macvlan_set_addr_change(vlan->port); return dev_set_mac_address(vlan->lowerdev, addr, NULL); } @@ -1645,7 +1649,7 @@ static int macvlan_changelink(struct net_device *dev, if (err < 0) return err; } - vlan->flags = flags; + WRITE_ONCE(vlan->flags, flags); } if (data && data[IFLA_MACVLAN_BC_QUEUE_LEN]) { @@ -1658,7 +1662,7 @@ static int macvlan_changelink(struct net_device *dev, vlan, nla_get_s32(data[IFLA_MACVLAN_BC_CUTOFF])); if (set_mode) - vlan->mode = mode; + WRITE_ONCE(vlan->mode, mode); if (data && data[IFLA_MACVLAN_MACADDR_MODE]) { if (vlan->mode != MACVLAN_MODE_SOURCE) return -EINVAL; From 6d728e7e286b8537422ab5b0ea1f9af9ce7b5794 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 1 Jul 2026 08:22:14 +0000 Subject: [PATCH 0167/1433] macvlan: no longer rely on RTNL in macvlan_fill_info() Add READ_ONCE()/WRITE_ONCE() annotations on vlan->mode, vlan->flags, vlan->bc_queue_len_req and port->bc_cutoff. Fill IFLA_MACVLAN_MACADDR_DATA nested attribute and compute on the fly the precise number of elements we put in it, to fill an accurate IFLA_MACVLAN_MACADDR_COUNT attribute as some user space applications could depend on its value and the attributes order. Signed-off-by: Eric Dumazet Reviewed-by: Nikolay Aleksandrov Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260701082214.2974946-3-edumazet@google.com Signed-off-by: Paolo Abeni --- drivers/net/macvlan.c | 71 +++++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c index 8b69cc9b70f9..9a4bc99dbf53 100644 --- a/drivers/net/macvlan.c +++ b/drivers/net/macvlan.c @@ -171,7 +171,7 @@ static int macvlan_hash_add_source(struct macvlan_dev *vlan, RCU_INIT_POINTER(entry->vlan, vlan); h = &port->vlan_source_hash[macvlan_eth_hash(addr)]; hlist_add_head_rcu(&entry->hlist, h); - vlan->macaddr_count++; + WRITE_ONCE(vlan->macaddr_count, vlan->macaddr_count + 1); return 0; } @@ -402,7 +402,7 @@ static void macvlan_flush_sources(struct macvlan_port *port, if (rcu_access_pointer(entry->vlan) == vlan) macvlan_hash_del_source(entry); - vlan->macaddr_count = 0; + WRITE_ONCE(vlan->macaddr_count, 0); } static void macvlan_forward_source_one(struct sk_buff *skb, @@ -874,7 +874,7 @@ static void update_port_bc_cutoff(struct macvlan_dev *vlan, int cutoff) if (vlan->port->bc_cutoff == cutoff) return; - vlan->port->bc_cutoff = cutoff; + WRITE_ONCE(vlan->port->bc_cutoff, cutoff); macvlan_recompute_bc_filter(vlan); } @@ -1427,7 +1427,7 @@ static int macvlan_changelink_sources(struct macvlan_dev *vlan, u32 mode, entry = macvlan_hash_lookup_source(vlan, addr); if (entry) { macvlan_hash_del_source(entry); - vlan->macaddr_count--; + WRITE_ONCE(vlan->macaddr_count, vlan->macaddr_count - 1); } } else if (mode == MACVLAN_MACADDR_FLUSH) { macvlan_flush_sources(vlan->port, vlan); @@ -1653,7 +1653,8 @@ static int macvlan_changelink(struct net_device *dev, } if (data && data[IFLA_MACVLAN_BC_QUEUE_LEN]) { - vlan->bc_queue_len_req = nla_get_u32(data[IFLA_MACVLAN_BC_QUEUE_LEN]); + WRITE_ONCE(vlan->bc_queue_len_req, + nla_get_u32(data[IFLA_MACVLAN_BC_QUEUE_LEN])); update_port_bc_queue_len(vlan->port); } @@ -1676,10 +1677,12 @@ static int macvlan_changelink(struct net_device *dev, static size_t macvlan_get_size_mac(const struct macvlan_dev *vlan) { - if (vlan->macaddr_count == 0) + unsigned int macaddr_count = READ_ONCE(vlan->macaddr_count); + + if (!macaddr_count) return 0; return nla_total_size(0) /* IFLA_MACVLAN_MACADDR_DATA */ - + vlan->macaddr_count * nla_total_size(sizeof(u8) * ETH_ALEN); + + macaddr_count * nla_total_size(sizeof(u8) * ETH_ALEN); } static size_t macvlan_get_size(const struct net_device *dev) @@ -1702,53 +1705,75 @@ static int macvlan_fill_info_macaddr(struct sk_buff *skb, const int i) { struct hlist_head *h = &vlan->port->vlan_source_hash[i]; - struct macvlan_source_entry *entry; + const struct macvlan_source_entry *entry; + int cnt = 0; - hlist_for_each_entry_rcu(entry, h, hlist, lockdep_rtnl_is_held()) { + hlist_for_each_entry_rcu(entry, h, hlist) { if (rcu_access_pointer(entry->vlan) != vlan) continue; if (nla_put(skb, IFLA_MACVLAN_MACADDR, ETH_ALEN, entry->addr)) - return 1; + return -EMSGSIZE; + cnt++; } - return 0; + return cnt; } static int macvlan_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct macvlan_dev *vlan = netdev_priv(dev); + const struct macvlan_dev *vlan = netdev_priv(dev); struct macvlan_port *port = vlan->port; - int i; - struct nlattr *nest; + unsigned int macaddr_count = 0; + struct nlattr *nest, *attr; + int bc_cutoff, cnt, i; - if (nla_put_u32(skb, IFLA_MACVLAN_MODE, vlan->mode)) + rcu_read_lock(); + if (nla_put_u32(skb, IFLA_MACVLAN_MODE, READ_ONCE(vlan->mode))) goto nla_put_failure; - if (nla_put_u16(skb, IFLA_MACVLAN_FLAGS, vlan->flags)) + + if (nla_put_u16(skb, IFLA_MACVLAN_FLAGS, READ_ONCE(vlan->flags))) goto nla_put_failure; - if (nla_put_u32(skb, IFLA_MACVLAN_MACADDR_COUNT, vlan->macaddr_count)) + + attr = nla_reserve(skb, IFLA_MACVLAN_MACADDR_COUNT, sizeof(u32)); + if (!attr) goto nla_put_failure; - if (vlan->macaddr_count > 0) { + + if (READ_ONCE(vlan->macaddr_count) > 0) { nest = nla_nest_start_noflag(skb, IFLA_MACVLAN_MACADDR_DATA); if (nest == NULL) goto nla_put_failure; for (i = 0; i < MACVLAN_HASH_SIZE; i++) { - if (macvlan_fill_info_macaddr(skb, vlan, i)) + cnt = macvlan_fill_info_macaddr(skb, vlan, i); + if (cnt < 0) goto nla_put_failure; + macaddr_count += cnt; } - nla_nest_end(skb, nest); + if (!macaddr_count) + nla_nest_cancel(skb, nest); + else if (nla_nest_end_safe(skb, nest) < 0) + goto nla_put_failure; } - if (nla_put_u32(skb, IFLA_MACVLAN_BC_QUEUE_LEN, vlan->bc_queue_len_req)) + *(u32 *)nla_data(attr) = macaddr_count; + + if (nla_put_u32(skb, IFLA_MACVLAN_BC_QUEUE_LEN, + READ_ONCE(vlan->bc_queue_len_req))) goto nla_put_failure; + if (nla_put_u32(skb, IFLA_MACVLAN_BC_QUEUE_LEN_USED, READ_ONCE(port->bc_queue_len_used))) goto nla_put_failure; - if (port->bc_cutoff != 1 && - nla_put_s32(skb, IFLA_MACVLAN_BC_CUTOFF, port->bc_cutoff)) + + bc_cutoff = READ_ONCE(port->bc_cutoff); + if (bc_cutoff != 1 && + nla_put_s32(skb, IFLA_MACVLAN_BC_CUTOFF, bc_cutoff)) goto nla_put_failure; + + rcu_read_unlock(); return 0; nla_put_failure: + rcu_read_unlock(); return -EMSGSIZE; } From 5a5ebefdab9d1de4a4af09555dc7359cc3c0b34f Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 1 Jul 2026 09:43:41 +0000 Subject: [PATCH 0168/1433] macsec: no longer rely on RTNL in macsec_fill_info() Add READ_ONCE()/WRITE_ONCE() annotations on fields that can be changed concurrently in macsec_changelink() and macsec_update_offload(): - secy->key_len - secy->xpn - tx_sc->encoding_sa - tx_sc->encrypt - secy->protect_frames - tx_sc->send_sci - tx_sc->end_station - tx_sc->scb - secy->replay_protect - secy->validate_frames - secy->replay_window - macsec->offload This allows macsec_fill_info() to run locklessly without RTNL. Signed-off-by: Eric Dumazet Cc: Sabrina Dubroca Cc: Andrew Lunn Reviewed-by: Kuniyuki Iwashima Reviewed-by: Sabrina Dubroca Link: https://patch.msgid.link/20260701094341.3218199-1-edumazet@google.com Signed-off-by: Paolo Abeni --- drivers/net/macsec.c | 78 +++++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/drivers/net/macsec.c b/drivers/net/macsec.c index fb009120a924..1a968596ca45 100644 --- a/drivers/net/macsec.c +++ b/drivers/net/macsec.c @@ -2636,7 +2636,7 @@ static int macsec_update_offload(struct net_device *dev, vlan_drop_rx_ctag_filter_info(dev); vlan_drop_rx_stag_filter_info(dev); } - macsec->offload = offload; + WRITE_ONCE(macsec->offload, offload); /* Add VLAN filters when enabling offload. */ if (prev_offload == MACSEC_OFFLOAD_OFF) { ret = vlan_get_rx_ctag_filter_info(dev); @@ -2666,7 +2666,7 @@ static int macsec_update_offload(struct net_device *dev, return 0; rollback_offload: - macsec->offload = prev_offload; + WRITE_ONCE(macsec->offload, prev_offload); macsec_offload(ops->mdo_del_secy, &ctx); return ret; @@ -3875,52 +3875,53 @@ static int macsec_changelink_common(struct net_device *dev, if (data[IFLA_MACSEC_ENCODING_SA]) { struct macsec_tx_sa *tx_sa; + u8 encoding_sa = nla_get_u8(data[IFLA_MACSEC_ENCODING_SA]); - tx_sc->encoding_sa = nla_get_u8(data[IFLA_MACSEC_ENCODING_SA]); - tx_sa = rtnl_dereference(tx_sc->sa[tx_sc->encoding_sa]); + WRITE_ONCE(tx_sc->encoding_sa, encoding_sa); + tx_sa = rtnl_dereference(tx_sc->sa[encoding_sa]); secy->operational = tx_sa && tx_sa->active; } if (data[IFLA_MACSEC_ENCRYPT]) - tx_sc->encrypt = !!nla_get_u8(data[IFLA_MACSEC_ENCRYPT]); + WRITE_ONCE(tx_sc->encrypt, !!nla_get_u8(data[IFLA_MACSEC_ENCRYPT])); if (data[IFLA_MACSEC_PROTECT]) - secy->protect_frames = !!nla_get_u8(data[IFLA_MACSEC_PROTECT]); + WRITE_ONCE(secy->protect_frames, !!nla_get_u8(data[IFLA_MACSEC_PROTECT])); if (data[IFLA_MACSEC_INC_SCI]) - tx_sc->send_sci = !!nla_get_u8(data[IFLA_MACSEC_INC_SCI]); + WRITE_ONCE(tx_sc->send_sci, !!nla_get_u8(data[IFLA_MACSEC_INC_SCI])); if (data[IFLA_MACSEC_ES]) - tx_sc->end_station = !!nla_get_u8(data[IFLA_MACSEC_ES]); + WRITE_ONCE(tx_sc->end_station, !!nla_get_u8(data[IFLA_MACSEC_ES])); if (data[IFLA_MACSEC_SCB]) - tx_sc->scb = !!nla_get_u8(data[IFLA_MACSEC_SCB]); + WRITE_ONCE(tx_sc->scb, !!nla_get_u8(data[IFLA_MACSEC_SCB])); if (data[IFLA_MACSEC_REPLAY_PROTECT]) - secy->replay_protect = !!nla_get_u8(data[IFLA_MACSEC_REPLAY_PROTECT]); + WRITE_ONCE(secy->replay_protect, !!nla_get_u8(data[IFLA_MACSEC_REPLAY_PROTECT])); if (data[IFLA_MACSEC_VALIDATION]) - secy->validate_frames = nla_get_u8(data[IFLA_MACSEC_VALIDATION]); + WRITE_ONCE(secy->validate_frames, nla_get_u8(data[IFLA_MACSEC_VALIDATION])); if (data[IFLA_MACSEC_CIPHER_SUITE]) { switch (nla_get_u64(data[IFLA_MACSEC_CIPHER_SUITE])) { case MACSEC_CIPHER_ID_GCM_AES_128: case MACSEC_DEFAULT_CIPHER_ID: - secy->key_len = MACSEC_GCM_AES_128_SAK_LEN; - secy->xpn = false; + WRITE_ONCE(secy->key_len, MACSEC_GCM_AES_128_SAK_LEN); + WRITE_ONCE(secy->xpn, false); break; case MACSEC_CIPHER_ID_GCM_AES_256: - secy->key_len = MACSEC_GCM_AES_256_SAK_LEN; - secy->xpn = false; + WRITE_ONCE(secy->key_len, MACSEC_GCM_AES_256_SAK_LEN); + WRITE_ONCE(secy->xpn, false); break; case MACSEC_CIPHER_ID_GCM_AES_XPN_128: - secy->key_len = MACSEC_GCM_AES_128_SAK_LEN; - secy->xpn = true; + WRITE_ONCE(secy->key_len, MACSEC_GCM_AES_128_SAK_LEN); + WRITE_ONCE(secy->xpn, true); break; case MACSEC_CIPHER_ID_GCM_AES_XPN_256: - secy->key_len = MACSEC_GCM_AES_256_SAK_LEN; - secy->xpn = true; + WRITE_ONCE(secy->key_len, MACSEC_GCM_AES_256_SAK_LEN); + WRITE_ONCE(secy->xpn, true); break; default: return -EINVAL; @@ -3928,13 +3929,14 @@ static int macsec_changelink_common(struct net_device *dev, } if (data[IFLA_MACSEC_WINDOW]) { - secy->replay_window = nla_get_u32(data[IFLA_MACSEC_WINDOW]); + u32 replay_window = nla_get_u32(data[IFLA_MACSEC_WINDOW]); /* IEEE 802.1AEbw-2013 10.7.8 - maximum replay window * for XPN cipher suites */ if (secy->xpn && - secy->replay_window > MACSEC_XPN_MAX_REPLAY_WINDOW) + replay_window > MACSEC_XPN_MAX_REPLAY_WINDOW) return -EINVAL; + WRITE_ONCE(secy->replay_window, replay_window); } return 0; @@ -4382,21 +4384,21 @@ static size_t macsec_get_size(const struct net_device *dev) static int macsec_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct macsec_tx_sc *tx_sc; - struct macsec_dev *macsec; - struct macsec_secy *secy; + const struct macsec_tx_sc *tx_sc; + const struct macsec_dev *macsec; + const struct macsec_secy *secy; u64 csid; macsec = macsec_priv(dev); secy = &macsec->secy; tx_sc = &secy->tx_sc; - switch (secy->key_len) { + switch (READ_ONCE(secy->key_len)) { case MACSEC_GCM_AES_128_SAK_LEN: - csid = secy->xpn ? MACSEC_CIPHER_ID_GCM_AES_XPN_128 : MACSEC_DEFAULT_CIPHER_ID; + csid = READ_ONCE(secy->xpn) ? MACSEC_CIPHER_ID_GCM_AES_XPN_128 : MACSEC_DEFAULT_CIPHER_ID; break; case MACSEC_GCM_AES_256_SAK_LEN: - csid = secy->xpn ? MACSEC_CIPHER_ID_GCM_AES_XPN_256 : MACSEC_CIPHER_ID_GCM_AES_256; + csid = READ_ONCE(secy->xpn) ? MACSEC_CIPHER_ID_GCM_AES_XPN_256 : MACSEC_CIPHER_ID_GCM_AES_256; break; default: goto nla_put_failure; @@ -4407,20 +4409,20 @@ static int macsec_fill_info(struct sk_buff *skb, nla_put_u8(skb, IFLA_MACSEC_ICV_LEN, secy->icv_len) || nla_put_u64_64bit(skb, IFLA_MACSEC_CIPHER_SUITE, csid, IFLA_MACSEC_PAD) || - nla_put_u8(skb, IFLA_MACSEC_ENCODING_SA, tx_sc->encoding_sa) || - nla_put_u8(skb, IFLA_MACSEC_ENCRYPT, tx_sc->encrypt) || - nla_put_u8(skb, IFLA_MACSEC_PROTECT, secy->protect_frames) || - nla_put_u8(skb, IFLA_MACSEC_INC_SCI, tx_sc->send_sci) || - nla_put_u8(skb, IFLA_MACSEC_ES, tx_sc->end_station) || - nla_put_u8(skb, IFLA_MACSEC_SCB, tx_sc->scb) || - nla_put_u8(skb, IFLA_MACSEC_REPLAY_PROTECT, secy->replay_protect) || - nla_put_u8(skb, IFLA_MACSEC_VALIDATION, secy->validate_frames) || - nla_put_u8(skb, IFLA_MACSEC_OFFLOAD, macsec->offload) || + nla_put_u8(skb, IFLA_MACSEC_ENCODING_SA, READ_ONCE(tx_sc->encoding_sa)) || + nla_put_u8(skb, IFLA_MACSEC_ENCRYPT, READ_ONCE(tx_sc->encrypt)) || + nla_put_u8(skb, IFLA_MACSEC_PROTECT, READ_ONCE(secy->protect_frames)) || + nla_put_u8(skb, IFLA_MACSEC_INC_SCI, READ_ONCE(tx_sc->send_sci)) || + nla_put_u8(skb, IFLA_MACSEC_ES, READ_ONCE(tx_sc->end_station)) || + nla_put_u8(skb, IFLA_MACSEC_SCB, READ_ONCE(tx_sc->scb)) || + nla_put_u8(skb, IFLA_MACSEC_REPLAY_PROTECT, READ_ONCE(secy->replay_protect)) || + nla_put_u8(skb, IFLA_MACSEC_VALIDATION, READ_ONCE(secy->validate_frames)) || + nla_put_u8(skb, IFLA_MACSEC_OFFLOAD, READ_ONCE(macsec->offload)) || 0) goto nla_put_failure; - if (secy->replay_protect) { - if (nla_put_u32(skb, IFLA_MACSEC_WINDOW, secy->replay_window)) + if (READ_ONCE(secy->replay_protect)) { + if (nla_put_u32(skb, IFLA_MACSEC_WINDOW, READ_ONCE(secy->replay_window))) goto nla_put_failure; } From d7261cdb9550733b9003123eabb986d80fa9155d Mon Sep 17 00:00:00 2001 From: Hao Chen Date: Tue, 30 Jun 2026 21:40:43 +0800 Subject: [PATCH 0169/1433] net: hns3: add support to query/set TX pfc_prevention_tout for ethtool with RX prevention disabled Add ethtool support to query and configure the PFC (Priority Flow Control) storm prevention timeout. When TX continuously sends PFC frames, the peer end is suppressed from sending packets. If this persists, a PFC frame storm may occur. This feature allows configuring a timeout to prevent such storms. Signed-off-by: Hao Chen Signed-off-by: Jijie Shao Link: https://patch.msgid.link/20260630134043.1532431-1-shaojijie@huawei.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/hisilicon/hns3/hnae3.h | 14 ++ .../hns3/hns3_common/hclge_comm_cmd.h | 3 + .../ethernet/hisilicon/hns3/hns3_ethtool.c | 12 ++ .../hisilicon/hns3/hns3pf/hclge_cmd.h | 9 + .../hisilicon/hns3/hns3pf/hclge_main.c | 172 ++++++++++++++++++ .../hisilicon/hns3/hns3pf/hclge_main.h | 7 + 6 files changed, 217 insertions(+) diff --git a/drivers/net/ethernet/hisilicon/hns3/hnae3.h b/drivers/net/ethernet/hisilicon/hns3/hnae3.h index a8798eecd9fb..4286af9239b0 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hnae3.h +++ b/drivers/net/ethernet/hisilicon/hns3/hnae3.h @@ -602,6 +602,10 @@ typedef int (*read_func)(struct seq_file *s, void *data); * Config wake on lan * dbg_get_read_func * Return the read func for debugfs seq file + * set_pfc_prevention_tout + * Set PFC storm prevention timeout + * get_pfc_prevention_tout + * Get PFC storm prevention timeout */ struct hnae3_ae_ops { int (*init_ae_dev)(struct hnae3_ae_dev *ae_dev); @@ -810,6 +814,8 @@ struct hnae3_ae_ops { int (*hwtstamp_set)(struct hnae3_handle *handle, struct kernel_hwtstamp_config *config, struct netlink_ext_ack *extack); + int (*set_pfc_prevention_tout)(struct hnae3_handle *handle, u16 times); + int (*get_pfc_prevention_tout)(struct hnae3_handle *handle, u16 *times); }; struct hnae3_dcb_ops { @@ -891,6 +897,14 @@ struct hnae3_roce_private_info { unsigned long state; }; +struct hnae3_pfc_storm_para { + u32 dir; + u32 enable; + u32 period_ms; + u32 times; + u32 recovery_period_ms; +}; + #define HNAE3_SUPPORT_APP_LOOPBACK BIT(0) #define HNAE3_SUPPORT_PHY_LOOPBACK BIT(1) #define HNAE3_SUPPORT_SERDES_SERIAL_LOOPBACK BIT(2) diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3_common/hclge_comm_cmd.h b/drivers/net/ethernet/hisilicon/hns3/hns3_common/hclge_comm_cmd.h index 2c2a2f1e0d7a..6dde07dde1e8 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3_common/hclge_comm_cmd.h +++ b/drivers/net/ethernet/hisilicon/hns3/hns3_common/hclge_comm_cmd.h @@ -314,6 +314,9 @@ enum hclge_opcode_type { /* Query link diagnosis info command */ HCLGE_OPC_QUERY_LINK_DIAGNOSIS = 0x702A, + + /* Config pause storm param command */ + HCLGE_OPC_CFG_PAUSE_STORM_PARA = 0x7019, }; enum hclge_comm_cmd_return_status { diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c b/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c index 442f15476af3..e7318f236315 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c @@ -1895,6 +1895,12 @@ static int hns3_get_tunable(struct net_device *netdev, case ETHTOOL_TX_COPYBREAK_BUF_SIZE: *(u32 *)data = h->kinfo.tx_spare_buf_size; break; + case ETHTOOL_PFC_PREVENTION_TOUT: + if (!h->ae_algo->ops->get_pfc_prevention_tout) + return -EOPNOTSUPP; + + ret = h->ae_algo->ops->get_pfc_prevention_tout(h, (u16 *)data); + break; default: ret = -EOPNOTSUPP; break; @@ -2020,6 +2026,12 @@ static int hns3_set_tunable(struct net_device *netdev, netdev_info(netdev, "the active tx spare buf size is %u, due to page order\n", priv->ring->tx_spare->len); + break; + case ETHTOOL_PFC_PREVENTION_TOUT: + if (!h->ae_algo->ops->set_pfc_prevention_tout) + return -EOPNOTSUPP; + + ret = h->ae_algo->ops->set_pfc_prevention_tout(h, *(u16 *)data); break; default: ret = -EOPNOTSUPP; diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_cmd.h b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_cmd.h index 4ce92ddefcde..1c029dc32ab8 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_cmd.h +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_cmd.h @@ -890,6 +890,15 @@ struct hclge_query_wol_supported_cmd { u8 rsv[20]; }; +struct hclge_pfc_storm_para_cmd { + __le32 dir; + __le32 enable; + __le32 period_ms; + __le32 times; + __le32 recovery_period_ms; + __le32 rsv; +}; + struct hclge_hw; int hclge_cmd_send(struct hclge_hw *hw, struct hclge_desc *desc, int num); #endif diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c index fc8587c80813..a08d8a35aef9 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c @@ -3524,6 +3524,122 @@ static int hclge_set_vf_link_state(struct hnae3_handle *handle, int vf, return ret; } +static int hclge_set_pfc_storm_para(struct hclge_dev *hdev, + struct hnae3_pfc_storm_para *para) +{ + struct hclge_pfc_storm_para_cmd *para_cmd; + struct hclge_desc desc; + int ret; + + if (hdev->ae_dev->dev_version < HNAE3_DEVICE_VERSION_V3) + return -EOPNOTSUPP; + + hclge_cmd_setup_basic_desc(&desc, HCLGE_OPC_CFG_PAUSE_STORM_PARA, + false); + para_cmd = (struct hclge_pfc_storm_para_cmd *)desc.data; + para_cmd->dir = cpu_to_le32(para->dir); + para_cmd->enable = cpu_to_le32(para->enable); + para_cmd->period_ms = cpu_to_le32(para->period_ms); + para_cmd->times = cpu_to_le32(para->times); + para_cmd->recovery_period_ms = cpu_to_le32(para->recovery_period_ms); + + ret = hclge_cmd_send(&hdev->hw, &desc, 1); + if (ret) + dev_err(&hdev->pdev->dev, + "failed to set pfc storm para, ret = %d\n", ret); + return ret; +} + +static int hclge_get_pfc_storm_para(struct hclge_dev *hdev, + struct hnae3_pfc_storm_para *para) +{ + struct hclge_pfc_storm_para_cmd *para_cmd; + struct hclge_desc desc; + int ret; + + if (hdev->ae_dev->dev_version < HNAE3_DEVICE_VERSION_V3) + return -EOPNOTSUPP; + + hclge_cmd_setup_basic_desc(&desc, HCLGE_OPC_CFG_PAUSE_STORM_PARA, true); + para_cmd = (struct hclge_pfc_storm_para_cmd *)desc.data; + para_cmd->dir = cpu_to_le32(para->dir); + ret = hclge_cmd_send(&hdev->hw, &desc, 1); + if (ret) { + dev_err(&hdev->pdev->dev, + "failed to get pfc storm para, ret = %d\n", ret); + return ret; + } + + para->enable = le32_to_cpu(para_cmd->enable); + para->period_ms = le32_to_cpu(para_cmd->period_ms); + para->times = le32_to_cpu(para_cmd->times); + para->recovery_period_ms = le32_to_cpu(para_cmd->recovery_period_ms); + + return 0; +} + +static int hclge_enable_pfc_storm_prevent(struct hclge_dev *hdev, + int dir, bool enable) +{ + struct hnae3_pfc_storm_para para = {0}; + int ret; + + para.dir = dir; + ret = hclge_get_pfc_storm_para(hdev, ¶); + if (ret) + return ret; + + para.enable = enable; + return hclge_set_pfc_storm_para(hdev, ¶); +} + +static int hclge_set_pfc_prevention_tout(struct hnae3_handle *h, u16 times) +{ + struct hclge_vport *vport = hclge_get_vport(h); + struct hclge_dev *hdev = vport->back; + struct hnae3_pfc_storm_para para; + int ret; + + if (times > HCLGE_MAX_PFC_PREVENTION_TOUT) { + dev_err(&hdev->pdev->dev, + "times %u should be no more than %u!\n", + times, HCLGE_MAX_PFC_PREVENTION_TOUT); + return -EINVAL; + } + + para.dir = HCLGE_DIR_TX; + ret = hclge_get_pfc_storm_para(hdev, ¶); + if (ret) + return ret; + + para.enable = times ? 1 : 0; + para.times = (u32)times; + ret = hclge_set_pfc_storm_para(hdev, ¶); + if (ret) + return ret; + + hdev->pfc_prevention_tout = times; + + return 0; +} + +static int hclge_get_pfc_prevention_tout(struct hnae3_handle *h, u16 *times) +{ + struct hclge_vport *vport = hclge_get_vport(h); + struct hclge_dev *hdev = vport->back; + struct hnae3_pfc_storm_para para; + int ret; + + para.dir = HCLGE_DIR_TX; + ret = hclge_get_pfc_storm_para(hdev, ¶); + if (ret) + return ret; + + *times = para.enable ? (u16)para.times : 0; + + return 0; +} + static void hclge_set_reset_pending(struct hclge_dev *hdev, enum hnae3_reset_type reset_type) { @@ -4317,6 +4433,26 @@ static int hclge_reset_prepare(struct hclge_dev *hdev) return hclge_reset_prepare_wait(hdev); } +static void hclge_restore_pfc_storm_prevention_tout(struct hclge_dev *hdev) +{ + struct hnae3_handle *handle = &hdev->vport[0].nic; + int ret; + + ret = hclge_enable_pfc_storm_prevent(hdev, HCLGE_DIR_RX, false); + if (ret == -EOPNOTSUPP) + return; + else if (ret) + dev_warn(&hdev->pdev->dev, + "failed to disable rx pfc storm prevent, ret = %d\n", + ret); + + ret = hclge_set_pfc_prevention_tout(handle, hdev->pfc_prevention_tout); + if (ret) + dev_warn(&hdev->pdev->dev, + "failed to set tx pfc storm prevent, ret = %d\n", + ret); +} + static int hclge_reset_rebuild(struct hclge_dev *hdev) { int ret; @@ -9278,6 +9414,32 @@ static int hclge_init_wol(struct hclge_dev *hdev) return hclge_update_wol(hdev); } +static void hclge_init_pfc_prevention_tout(struct hclge_dev *hdev) +{ + struct hnae3_handle *handle = &hdev->vport[0].nic; + u16 times; + int ret; + + ret = hclge_enable_pfc_storm_prevent(hdev, HCLGE_DIR_RX, false); + if (ret == -EOPNOTSUPP) + return; + else if (ret) + dev_warn(&hdev->pdev->dev, + "failed to disable rx pfc storm prevent, ret = %d\n", + ret); + + ret = hclge_get_pfc_prevention_tout(handle, ×); + if (ret) { + dev_warn(&hdev->pdev->dev, + "failed to get tx pfc prevention timeout, ret = %d\n", + ret); + times = HCLGE_DEFAULT_PFC_PREVENTION_TOUT; + } + + hdev->pfc_prevention_tout = times; + hdev->pfc_prevention_tout_default = times; +} + static void hclge_get_wol(struct hnae3_handle *handle, struct ethtool_wolinfo *wol) { @@ -9547,6 +9709,8 @@ static int hclge_init_ae_dev(struct hnae3_ae_dev *ae_dev) dev_warn(&pdev->dev, "failed to wake on lan init, ret = %d\n", ret); + hclge_init_pfc_prevention_tout(hdev); + ret = hclge_devlink_init(hdev); if (ret) goto err_ptp_uninit; @@ -9946,6 +10110,8 @@ static int hclge_reset_ae_dev(struct hnae3_ae_dev *ae_dev) dev_warn(&pdev->dev, "failed to update wol config, ret = %d\n", ret); + hclge_restore_pfc_storm_prevention_tout(hdev); + dev_info(&pdev->dev, "Reset done, %s driver initialization finished.\n", HCLGE_DRIVER_NAME); @@ -9977,6 +10143,10 @@ static void hclge_uninit_ae_dev(struct hnae3_ae_dev *ae_dev) hclge_config_nic_hw_error(hdev, false); hclge_config_rocee_ras_interrupt(hdev, false); + /* Restore hw default values for the next initialization */ + hclge_set_pfc_prevention_tout(&hdev->vport->nic, + hdev->pfc_prevention_tout_default); + hclge_comm_cmd_uninit(hdev->ae_dev, &hdev->hw.hw); hclge_misc_irq_uninit(hdev); hclge_devlink_uninit(hdev); @@ -10539,6 +10709,8 @@ static const struct hnae3_ae_ops hclge_ops = { .set_wol = hclge_set_wol, .hwtstamp_get = hclge_ptp_get_cfg, .hwtstamp_set = hclge_ptp_set_cfg, + .set_pfc_prevention_tout = hclge_set_pfc_prevention_tout, + .get_pfc_prevention_tout = hclge_get_pfc_prevention_tout, }; static struct hnae3_ae_algo ae_algo = { diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.h b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.h index 7419481422c3..0cee8947f6b4 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.h +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.h @@ -346,6 +346,11 @@ enum hclge_link_fail_code { #define HCLGE_LINK_STATUS_DOWN 0 #define HCLGE_LINK_STATUS_UP 1 +#define HCLGE_DIR_RX 0 +#define HCLGE_DIR_TX 1 +#define HCLGE_MAX_PFC_PREVENTION_TOUT 2000 +#define HCLGE_DEFAULT_PFC_PREVENTION_TOUT 1000 + #define HCLGE_PG_NUM 4 #define HCLGE_SCH_MODE_SP 0 #define HCLGE_SCH_MODE_DWRR 1 @@ -898,6 +903,8 @@ struct hclge_dev { u16 vf_rss_size_max; /* HW defined VF max RSS task queue */ u16 pf_rss_size_max; /* HW defined PF max RSS task queue */ u32 tx_spare_buf_size; /* HW defined TX spare buffer size */ + u16 pfc_prevention_tout; /* User config, restored after reset */ + u16 pfc_prevention_tout_default; /* HW default, to avoid stale state */ u16 fdir_pf_filter_count; /* Num of guaranteed filters for this PF */ u16 num_alloc_vport; /* Num vports this driver supports */ From 586c4dcf28eb68756d44a30cfcc9535380f07cc6 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 1 Jul 2026 12:50:16 +0000 Subject: [PATCH 0170/1433] amt: no longer rely on RTNL in amt_fill_info() Update amt_fill_info() to run under RCU read lock instead of RTNL. The AMT device configuration fields (mode, relay_port, gw_port, local_ip, discovery_ip, max_tunnels) and stream_dev pointer are initialized during device creation (amt_newlink) and are immutable. Accessing them locklessly is safe. The stream_dev net_device structure is protected from being freed by RCU. The only field that can change concurrently is amt->remote_ip, which is updated in the packet receive path (amt_advertisement_handler) and workqueue (amt_req_work). Add READ_ONCE()/WRITE_ONCE() annotations around amt->remote_ip to prevent data races. Signed-off-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260701125016.3650708-1-edumazet@google.com Signed-off-by: Paolo Abeni --- drivers/net/amt.c | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/drivers/net/amt.c b/drivers/net/amt.c index 724a8163a514..5cf97c65576f 100644 --- a/drivers/net/amt.c +++ b/drivers/net/amt.c @@ -708,11 +708,13 @@ static void amt_send_request(struct amt_dev *amt, bool v6) struct iphdr *iph; struct rtable *rt; struct flowi4 fl4; + __be32 remote_ip; struct sock *sk; u32 len; int err; rcu_read_lock(); + remote_ip = READ_ONCE(amt->remote_ip); sk = rcu_dereference(amt->sk); if (!sk) goto out; @@ -721,7 +723,7 @@ static void amt_send_request(struct amt_dev *amt, bool v6) goto out; rt = ip_route_output_ports(amt->net, &fl4, sk, - amt->remote_ip, amt->local_ip, + remote_ip, amt->local_ip, amt->gw_port, amt->relay_port, IPPROTO_UDP, 0, amt->stream_dev->ifindex); @@ -762,7 +764,7 @@ static void amt_send_request(struct amt_dev *amt, bool v6) udph->check = 0; offset = skb_transport_offset(skb); skb->csum = skb_checksum(skb, offset, skb->len - offset, 0); - udph->check = csum_tcpudp_magic(amt->local_ip, amt->remote_ip, + udph->check = csum_tcpudp_magic(amt->local_ip, remote_ip, sizeof(*udph) + sizeof(*amtrh), IPPROTO_UDP, skb->csum); @@ -773,7 +775,7 @@ static void amt_send_request(struct amt_dev *amt, bool v6) iph->tos = AMT_TOS; iph->frag_off = 0; iph->ttl = ip4_dst_hoplimit(&rt->dst); - iph->daddr = amt->remote_ip; + iph->daddr = remote_ip; iph->saddr = amt->local_ip; iph->protocol = IPPROTO_UDP; iph->tot_len = htons(len); @@ -962,7 +964,7 @@ static void amt_event_send_request(struct amt_dev *amt) amt->qi = AMT_INIT_REQ_TIMEOUT; WRITE_ONCE(amt->ready4, false); WRITE_ONCE(amt->ready6, false); - amt->remote_ip = 0; + WRITE_ONCE(amt->remote_ip, 0); amt_update_gw_status(amt, AMT_STATUS_INIT, false); amt->req_cnt = 0; amt->nonce = 0; @@ -999,6 +1001,7 @@ static bool amt_send_membership_update(struct amt_dev *amt, struct sk_buff *skb, bool v6) { + __be32 remote_ip = READ_ONCE(amt->remote_ip); struct amt_header_membership_update *amtmu; struct iphdr *iph; struct flowi4 fl4; @@ -1018,13 +1021,13 @@ static bool amt_send_membership_update(struct amt_dev *amt, skb_reset_inner_headers(skb); memset(&fl4, 0, sizeof(struct flowi4)); fl4.flowi4_oif = amt->stream_dev->ifindex; - fl4.daddr = amt->remote_ip; + fl4.daddr = remote_ip; fl4.saddr = amt->local_ip; fl4.flowi4_dscp = inet_dsfield_to_dscp(AMT_TOS); fl4.flowi4_proto = IPPROTO_UDP; rt = ip_route_output_key(amt->net, &fl4); if (IS_ERR(rt)) { - netdev_dbg(amt->dev, "no route to %pI4\n", &amt->remote_ip); + netdev_dbg(amt->dev, "no route to %pI4\n", &remote_ip); return true; } @@ -2272,8 +2275,8 @@ static bool amt_advertisement_handler(struct amt_dev *amt, struct sk_buff *skb) amt->nonce != amta->nonce) return true; - amt->remote_ip = amta->ip4; - netdev_dbg(amt->dev, "advertised remote ip = %pI4\n", &amt->remote_ip); + WRITE_ONCE(amt->remote_ip, amta->ip4); + netdev_dbg(amt->dev, "advertised remote ip = %pI4\n", &amta->ip4); mod_delayed_work(amt_wq, &amt->req_wq, 0); amt_update_gw_status(amt, AMT_STATUS_RECEIVED_ADVERTISEMENT, true); @@ -2773,6 +2776,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb) { struct amt_dev *amt; struct iphdr *iph; + __be32 remote_ip; int type; bool err; @@ -2783,6 +2787,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb) kfree_skb(skb); goto out; } + remote_ip = READ_ONCE(amt->remote_ip); skb->dev = amt->dev; iph = ip_hdr(skb); @@ -2807,7 +2812,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb) } goto out; case AMT_MSG_MULTICAST_DATA: - if (iph->saddr != amt->remote_ip) { + if (iph->saddr != remote_ip) { netdev_dbg(amt->dev, "Invalid Relay IP\n"); err = true; goto drop; @@ -2818,7 +2823,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb) else goto out; case AMT_MSG_MEMBERSHIP_QUERY: - if (iph->saddr != amt->remote_ip) { + if (iph->saddr != remote_ip) { netdev_dbg(amt->dev, "Invalid Relay IP\n"); err = true; goto drop; @@ -3000,7 +3005,7 @@ static int amt_dev_open(struct net_device *dev) return err; amt->req_cnt = 0; - amt->remote_ip = 0; + WRITE_ONCE(amt->remote_ip, 0); amt->nonce = 0; get_random_bytes(&amt->key, sizeof(siphash_key_t)); @@ -3045,7 +3050,7 @@ static int amt_dev_stop(struct net_device *dev) amt->ready4 = false; amt->ready6 = false; amt->req_cnt = 0; - amt->remote_ip = 0; + WRITE_ONCE(amt->remote_ip, 0); list_for_each_entry_safe(tunnel, tmp, &amt->tunnel_list, list) { list_del_rcu(&tunnel->list); @@ -3244,7 +3249,7 @@ static int amt_newlink(struct net_device *dev, "gateway port must not be 0"); goto err; } - amt->remote_ip = 0; + WRITE_ONCE(amt->remote_ip, 0); amt->discovery_ip = nla_get_in_addr(data[IFLA_AMT_DISCOVERY_IP]); if (ipv4_is_loopback(amt->discovery_ip) || ipv4_is_zeronet(amt->discovery_ip) || @@ -3308,8 +3313,10 @@ static size_t amt_get_size(const struct net_device *dev) static int amt_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct amt_dev *amt = netdev_priv(dev); + const struct amt_dev *amt = netdev_priv(dev); + __be32 remote_ip; + rcu_read_lock(); if (nla_put_u32(skb, IFLA_AMT_MODE, amt->mode)) goto nla_put_failure; if (nla_put_be16(skb, IFLA_AMT_RELAY_PORT, amt->relay_port)) @@ -3322,15 +3329,19 @@ static int amt_fill_info(struct sk_buff *skb, const struct net_device *dev) goto nla_put_failure; if (nla_put_in_addr(skb, IFLA_AMT_DISCOVERY_IP, amt->discovery_ip)) goto nla_put_failure; - if (amt->remote_ip) - if (nla_put_in_addr(skb, IFLA_AMT_REMOTE_IP, amt->remote_ip)) + + remote_ip = READ_ONCE(amt->remote_ip); + if (remote_ip) + if (nla_put_in_addr(skb, IFLA_AMT_REMOTE_IP, remote_ip)) goto nla_put_failure; if (nla_put_u32(skb, IFLA_AMT_MAX_TUNNELS, amt->max_tunnels)) goto nla_put_failure; + rcu_read_unlock(); return 0; nla_put_failure: + rcu_read_unlock(); return -EMSGSIZE; } From 1704cc8640702c93e79921e2dffff3cfa31f0ce5 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Tue, 30 Jun 2026 12:36:51 -0300 Subject: [PATCH 0171/1433] selftests/tc-testing: Add tests that force multiq and taprio to enqueue to child's gso_skb Add test cases to reproduce scenarios fixed recently [1] where multiqueue and taprio forced their children into enqueueing an skb to gso_skb (during peek), but failed to dequeue from gso_skb because they called the child's dequeue callback directly. This causes a desync in the child's qlen/backlog and results in an eventual null-ptr-deref (with a qfq or dualpi2 child). Test cases are the following: - Force multiq to dequeue from its child's gso_skb with qfq leaf (fb6c) - Force multiq to dequeue from its child's gso_skb with dualpi2 leaf (1922) - Force taprio to dequeue from its child's gso_skb with qfq leaf (476f) - Force taprio to dequeue from its child's gso_skb with dualpi2 leaf (0235) [1] https://lore.kernel.org/netdev/20260625-b4-disp-31bcb279-v1-0-85c40b83c529@proton.me/ Signed-off-by: Victor Nogueira Reviewed-by: Pedro Tammela Link: https://patch.msgid.link/20260630153651.249752-1-victor@mojatatu.com Signed-off-by: Paolo Abeni --- .../tc-testing/tc-tests/infra/qdiscs.json | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json b/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json index a1f97a4b606e..0cf12c50fb74 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json +++ b/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json @@ -1540,5 +1540,169 @@ "$TC qdisc del dev $DUMMY root", "$IP addr del 10.10.10.10/24 dev $DUMMY || true" ] + }, + { + "id": "fb6c", + "name": "Force multiq to dequeue from its child's gso_skb with qfq leaf", + "category": [ + "qdisc", + "tbf", + "multiq", + "qfq" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "echo \"1 1 4\" > /sys/bus/netdevsim/new_device", + "$IP link set dev $ETH up || true", + "$IP l set addr 01:02:03:04:05:06 dev $ETH || true", + "$IP n add dev $ETH 10.10.11.1 lladdr 01:02:03:04:05:06 dev $ETH || true", + "$IP addr add 10.10.11.10/24 dev $ETH || true", + "$TC qdisc add dev $ETH root handle 1: tbf rate 88bit burst 1661b peakrate 2257333 minburst 1024 limit 7b", + "$TC qdisc add dev $ETH parent 1: handle 2: multiq", + "$TC qdisc add dev $ETH parent 2:1 handle 3: qfq", + "$TC class add dev $ETH classid 3:1 parent 3: qfq maxpkt 512 weight 1", + "$TC filter add dev $ETH parent 2: protocol all prio 1 matchall action skbedit queue_mapping 0", + "$TC filter add dev $ETH parent 3: protocol all prio 1 matchall classid 3:1 action ok" + ], + "cmdUnderTest": "ping -c 1 10.10.11.1 -W0.01 -I$ETH || true", + "expExitCode": "0", + "verifyCmd": "$TC -s -j qdisc ls dev $ETH parent 1:", + "matchJSON": [ + { + "kind": "multiq", + "handle": "2:", + "bytes": 98, + "packets": 1, + "backlog": 0, + "qlen": 0 + } + ], + "teardown": [ + "$TC qdisc del dev $ETH handle 1: root", + "echo \"1\" > /sys/bus/netdevsim/del_device" + ] + }, + { + "id": "1922", + "name": "Force multiq to dequeue from its child's gso_skb with dualpi2 leaf", + "category": [ + "qdisc", + "tbf", + "multiq", + "dualpi2" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "echo \"1 1 4\" > /sys/bus/netdevsim/new_device", + "$IP link set dev $ETH up || true", + "$IP l set addr 01:02:03:04:05:06 dev $ETH || true", + "$IP n add dev $ETH 10.10.11.1 lladdr 01:02:03:04:05:06 dev $ETH || true", + "$IP addr add 10.10.11.10/24 dev $ETH || true", + "$TC qdisc add dev $ETH root handle 1: tbf rate 88bit burst 1661b peakrate 2257333 minburst 1024 limit 7b", + "$TC qdisc add dev $ETH parent 1: handle 2: multiq", + "$TC qdisc add dev $ETH parent 2:1 handle 3: dualpi2", + "$TC filter add dev $ETH parent 2: protocol ip prio 1 u32 match ip dst 10.10.11.1 action skbedit queue_mapping 0", + "$TC filter add dev $ETH parent 3: protocol ip prio 1 u32 match ip dst 10.10.11.1 classid 3:1 action ok" + ], + "cmdUnderTest": "ping -c 1 10.10.11.1 -W0.01 -I$ETH || true", + "expExitCode": "0", + "verifyCmd": "$TC -j -s qdisc ls dev $ETH handle 3:", + "matchJSON": [ + { + "kind": "dualpi2", + "handle": "3:", + "bytes": 98, + "packets": 1, + "backlog": 0, + "qlen": 0 + } + ], + "teardown": [ + "$TC qdisc del dev $ETH handle 1: root", + "echo \"1\" > /sys/bus/netdevsim/del_device" + ] + }, + { + "id": "476f", + "name": "Force taprio to dequeue from its child's gso_skb with qfq leaf", + "category": [ + "qdisc", + "tbf", + "multiq", + "qfq" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "echo \"1 1 4\" > /sys/bus/netdevsim/new_device", + "$IP link set dev $ETH up || true", + "$IP l set addr 01:02:03:04:05:06 dev $ETH || true", + "$IP n add dev $ETH 10.10.11.1 lladdr 01:02:03:04:05:06 dev $ETH || true", + "$TC qdisc add dev $ETH root handle 1: taprio num_tc 2 map 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 queues 1@0 1@1 base-time 9000000000000000000 sched-entry S 03 200000 flags 0x0 clockid CLOCK_TAI", + "$TC qdisc add dev $ETH parent 1:1 handle 3: qfq", + "$TC class add dev $ETH classid 3:1 parent 3: qfq maxpkt 512 weight 1", + "$TC filter add dev $ETH parent 3: protocol all prio 1 matchall classid 3:1 action ok" + ], + "cmdUnderTest": "ping -c 1 10.10.11.1 -W0.01 -I$ETH || true", + "expExitCode": "0", + "verifyCmd": "$TC -s -j qdisc ls dev $ETH", + "matchJSON": [ + { + "kind": "taprio", + "handle": "1:", + "bytes": 98, + "packets": 1, + "backlog": 0, + "qlen": 0 + } + ], + "teardown": [ + "$TC qdisc del dev $ETH handle 1: root", + "echo \"1\" > /sys/bus/netdevsim/del_device" + ] + }, + { + "id": "0235", + "name": "Force taprio to dequeue from its child's gso_skb with dualpi2 leaf", + "category": [ + "qdisc", + "tbf", + "taprio", + "dualpi2" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "echo \"1 1 4\" > /sys/bus/netdevsim/new_device", + "$IP link set dev $ETH up || true", + "$IP l set addr 01:02:03:04:05:06 dev $ETH || true", + "$IP n add dev $ETH 10.10.11.1 lladdr 01:02:03:04:05:06 dev $ETH || true", + "$TC qdisc add dev $ETH root handle 1: taprio num_tc 2 map 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 queues 1@0 1@1 base-time 9000000000000000000 sched-entry S 03 200000 flags 0x0 clockid CLOCK_TAI", + "$TC qdisc replace dev $ETH parent 1:1 handle 3: dualpi2", + "$TC filter add dev $ETH parent 3: protocol ip prio 1 u32 match ip dst 10.10.11.1 classid 3:1 action ok" + ], + "cmdUnderTest": "ping -c 1 10.10.11.1 -W0.01 -I$ETH || true", + "expExitCode": "0", + "verifyCmd": "$TC -j -s qdisc ls dev $ETH handle 3:", + "matchJSON": [ + { + "kind": "dualpi2", + "handle": "3:", + "bytes": 98, + "packets": 1, + "backlog": 0, + "qlen": 0 + } + ], + "teardown": [ + "$TC qdisc del dev $ETH handle 1: root", + "echo \"1\" > /sys/bus/netdevsim/del_device" + ] } ] From 25bc29ba671d0496b599149a0823b5dbb5409f94 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 12 Apr 2026 15:26:09 +0300 Subject: [PATCH 0172/1433] wifi: radiotap: add definitions for the new UHR TLVs Add the necessary definitions to create radiotap UHR TLVs for UHR sniffers. Signed-off-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260412152605.73e682d0c8c3.I5a0c858467c852b7a2a00f580bd073af29c37705@changeid Signed-off-by: Johannes Berg --- include/net/ieee80211_radiotap.h | 190 +++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) diff --git a/include/net/ieee80211_radiotap.h b/include/net/ieee80211_radiotap.h index c60867e7e43c..8bbaf77da7cf 100644 --- a/include/net/ieee80211_radiotap.h +++ b/include/net/ieee80211_radiotap.h @@ -95,6 +95,8 @@ enum ieee80211_radiotap_presence { IEEE80211_RADIOTAP_EXT = 31, IEEE80211_RADIOTAP_EHT_USIG = 33, IEEE80211_RADIOTAP_EHT = 34, + IEEE80211_RADIOTAP_UHR_ELR = 37, + IEEE80211_RADIOTAP_UHR = 38, }; /* for IEEE80211_RADIOTAP_FLAGS */ @@ -602,6 +604,194 @@ enum ieee80211_radiotap_eht_usig_tb { IEEE80211_RADIOTAP_EHT_USIG2_TB_B20_B25_TAIL = 0xfc000000, }; +/* + * ieee80211_radiotap_uhr_elr - content of UHR-ELR TLV (type 37) + * see https://www.radiotap.org/fields/UHR-ELR for details + */ +struct ieee80211_radiotap_uhr_elr { + __le32 known; + __le32 sig1, sig2, mark; +} __packed; + +enum ieee80211_radiotap_uhr_elr_known { + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_VERSION_ID = 0x00000001, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_UL_DL = 0x00000002, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_MCS = 0x00000004, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_CODING = 0x00000008, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_LENGTH = 0x00000010, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_LDPC_EXTRA_OFDM_SYM = 0x00000020, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_SIG_1_CRC = 0x00000040, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_SIG_1_TAIL = 0x00000080, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_STA_ID = 0x00000100, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_DISREGARD = 0x00000200, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_SIG_2_CRC = 0x00000400, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_SIG_2_TAIL = 0x00000800, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_SIG_1_CRC_CHECKED = 0x00001000, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_SIG_2_CRC_CHECKED = 0x00002000, + IEEE80211_RADIOTAP_UHR_ELR_KNOWN_MARK_BSS_COLOR = 0x00010000, +}; + +enum ieee80211_radiotap_uhr_elr_sig1 { + IEEE80211_RADIOTAP_UHR_ELR_SIG1_VERSION_ID = 0x00000001, + IEEE80211_RADIOTAP_UHR_ELR_SIG1_UL_DL = 0x00000002, + IEEE80211_RADIOTAP_UHR_ELR_SIG1_MCS = 0x00000004, + IEEE80211_RADIOTAP_UHR_ELR_SIG1_CODING = 0x00000008, + IEEE80211_RADIOTAP_UHR_ELR_SIG1_LENGTH = 0x00001FF0, + IEEE80211_RADIOTAP_UHR_ELR_SIG1_LDPC_EXTRA_OFDM_SYM = 0x00002000, + IEEE80211_RADIOTAP_UHR_ELR_SIG1_CRC = 0x0003C000, + IEEE80211_RADIOTAP_UHR_ELR_SIG1_TAIL = 0x00FC0000, + IEEE80211_RADIOTAP_UHR_ELR_SIG1_CRC_VALID = 0x80000000, +}; + +enum ieee80211_radiotap_uhr_elr_sig2 { + IEEE80211_RADIOTAP_UHR_ELR_SIG2_STA_ID = 0x000007FF, + IEEE80211_RADIOTAP_UHR_ELR_SIG2_DISREGARD = 0x00003800, + IEEE80211_RADIOTAP_UHR_ELR_SIG2_CRC = 0x0003C000, + IEEE80211_RADIOTAP_UHR_ELR_SIG2_TAIL = 0x00FC0000, + IEEE80211_RADIOTAP_UHR_ELR_SIG2_CRC_VALID = 0x80000000, +}; + +enum ieee80211_radiotap_uhr_elr_mark { + IEEE80211_RADIOTAP_UHR_ELR_MARK_BSS_COLOR = 0x0000003F, +}; + +/* + * ieee80211_radiotap_uhr - content of UHR TLV (type 38) + * see https://www.radiotap.org/fields/UHR for details + */ +struct ieee80211_radiotap_uhr { + __le32 known; + __le32 data[9]; + struct { + __le32 known, info; + } user[]; +} __packed; + +enum ieee80211_radiotap_uhr_known { + IEEE80211_RADIOTAP_UHR_KNOWN_SPATIAL_REUSE = 0x00000001, + IEEE80211_RADIOTAP_UHR_KNOWN_GI_LTF_SIZE = 0x00000002, + IEEE80211_RADIOTAP_UHR_KNOWN_NUMBER_OF_UHR_LTF_SYMBOLS = 0x00000004, + IEEE80211_RADIOTAP_UHR_KNOWN_LDPC_EXTRA_SYMBOL_SEGMENT = 0x00000008, + IEEE80211_RADIOTAP_UHR_KNOWN_PRE_FEC_PADDING_FACTOR = 0x00000010, + IEEE80211_RADIOTAP_UHR_KNOWN_PE_DISAMBIGUITY = 0x00000020, + IEEE80211_RADIOTAP_UHR_KNOWN_DISREGARD_OFDMA = 0x00000040, + IEEE80211_RADIOTAP_UHR_KNOWN_CRC1 = 0x00000080, + IEEE80211_RADIOTAP_UHR_KNOWN_TAIL1 = 0x00000100, + IEEE80211_RADIOTAP_UHR_KNOWN_CRC2 = 0x00000200, + IEEE80211_RADIOTAP_UHR_KNOWN_TAIL2 = 0x00000400, + IEEE80211_RADIOTAP_UHR_KNOWN_INTERFERENCE_MITIGATION = 0x00000800, + IEEE80211_RADIOTAP_UHR_KNOWN_DISREGARD_NON_OFDMA = 0x00001000, + IEEE80211_RADIOTAP_UHR_KNOWN_NUMBER_OF_NON_OFDMA_USERS = 0x00002000, + IEEE80211_RADIOTAP_UHR_KNOWN_COMMON_ENCODING_BLOCK_CRC = 0x00004000, + IEEE80211_RADIOTAP_UHR_KNOWN_COMMON_ENCODING_BLOCK_TAIL = 0x00008000, + IEEE80211_RADIOTAP_UHR_KNOWN_RU_MRU_DRU_SIZE = 0x00010000, + IEEE80211_RADIOTAP_UHR_KNOWN_RU_MRU_INDEX = 0x00020000, + IEEE80211_RADIOTAP_UHR_KNOWN_DRU_RRU_ALLOC_TB_FMT = 0x00040000, + IEEE80211_RADIOTAP_UHR_KNOWN_PRI80_CHAN_POS = 0x00080000, +}; + +enum ieee80211_radiotap_uhr_data { + /* data[0] */ + IEEE80211_RADIOTAP_UHR_DATA0_SPATIAL_REUSE = 0x0000000F, + IEEE80211_RADIOTAP_UHR_DATA0_GI_LTF_SIZE = 0x00000030, + IEEE80211_RADIOTAP_UHR_DATA0_NUMBER_OF_LTF_SYMBOLS = 0x00000700, + IEEE80211_RADIOTAP_UHR_DATA0_LDPC_EXTRA_SYMBOL_SEGMENT = 0x00000800, + IEEE80211_RADIOTAP_UHR_DATA0_PRE_FEC_PADDING_FACTOR = 0x00003000, + IEEE80211_RADIOTAP_UHR_DATA0_PE_DISAMBIGUITY = 0x00004000, + IEEE80211_RADIOTAP_UHR_DATA0_DISREGARD_OFDMA = 0x00078000, + IEEE80211_RADIOTAP_UHR_DATA0_CRC1 = 0x00780000, + IEEE80211_RADIOTAP_UHR_DATA0_TAIL1 = 0x1f800000, + /* data[1] */ + IEEE80211_RADIOTAP_UHR_DATA1_RU_MRU_DRU_SIZE = 0x0000001f, + IEEE80211_RADIOTAP_UHR_DATA1_RU_MRU_INDEX = 0x00001fe0, + IEEE80211_RADIOTAP_UHR_DATA1_RU_ALLOC_CC_1_1_1 = 0x003fe000, + IEEE80211_RADIOTAP_UHR_DATA1_RU_ALLOC_CC_1_1_1_KNOWN = 0x00400000, + IEEE80211_RADIOTAP_UHR_DATA1_PRI80_CHAN_POS = 0xc0000000, + /* data[2] */ + IEEE80211_RADIOTAP_UHR_DATA2_RU_ALLOC_CC_2_1_1 = 0x000001ff, + IEEE80211_RADIOTAP_UHR_DATA2_RU_ALLOC_CC_2_1_1_KNOWN = 0x00000200, + IEEE80211_RADIOTAP_UHR_DATA2_RU_ALLOC_CC_1_1_2 = 0x0007fc00, + IEEE80211_RADIOTAP_UHR_DATA2_RU_ALLOC_CC_1_1_2_KNOWN = 0x00080000, + IEEE80211_RADIOTAP_UHR_DATA2_RU_ALLOC_CC_2_1_2 = 0x1ff00000, + IEEE80211_RADIOTAP_UHR_DATA2_RU_ALLOC_CC_2_1_2_KNOWN = 0x20000000, + /* data[3] */ + IEEE80211_RADIOTAP_UHR_DATA3_RU_ALLOC_CC_1_2_1 = 0x000001ff, + IEEE80211_RADIOTAP_UHR_DATA3_RU_ALLOC_CC_1_2_1_KNOWN = 0x00000200, + IEEE80211_RADIOTAP_UHR_DATA3_RU_ALLOC_CC_2_2_1 = 0x0007fc00, + IEEE80211_RADIOTAP_UHR_DATA3_RU_ALLOC_CC_2_2_1_KNOWN = 0x00080000, + IEEE80211_RADIOTAP_UHR_DATA3_RU_ALLOC_CC_1_2_2 = 0x1ff00000, + IEEE80211_RADIOTAP_UHR_DATA3_RU_ALLOC_CC_1_2_2_KNOWN = 0x20000000, + /* data[4] */ + IEEE80211_RADIOTAP_UHR_DATA4_RU_ALLOC_CC_2_2_2 = 0x000001ff, + IEEE80211_RADIOTAP_UHR_DATA4_RU_ALLOC_CC_2_2_2_KNOWN = 0x00000200, + IEEE80211_RADIOTAP_UHR_DATA4_RU_ALLOC_CC_1_2_3 = 0x0007fc00, + IEEE80211_RADIOTAP_UHR_DATA4_RU_ALLOC_CC_1_2_3_KNOWN = 0x00080000, + IEEE80211_RADIOTAP_UHR_DATA4_RU_ALLOC_CC_2_2_3 = 0x1ff00000, + IEEE80211_RADIOTAP_UHR_DATA4_RU_ALLOC_CC_2_2_3_KNOWN = 0x20000000, + /* data[5] */ + IEEE80211_RADIOTAP_UHR_DATA5_RU_ALLOC_CC_1_2_4 = 0x000001ff, + IEEE80211_RADIOTAP_UHR_DATA5_RU_ALLOC_CC_1_2_4_KNOWN = 0x00000200, + IEEE80211_RADIOTAP_UHR_DATA5_RU_ALLOC_CC_2_2_4 = 0x0007fc00, + IEEE80211_RADIOTAP_UHR_DATA5_RU_ALLOC_CC_2_2_4_KNOWN = 0x00080000, + IEEE80211_RADIOTAP_UHR_DATA5_RU_ALLOC_CC_1_2_5 = 0x1ff00000, + IEEE80211_RADIOTAP_UHR_DATA5_RU_ALLOC_CC_1_2_5_KNOWN = 0x20000000, + /* data[6] */ + IEEE80211_RADIOTAP_UHR_DATA6_RU_ALLOC_CC_2_2_5 = 0x000001ff, + IEEE80211_RADIOTAP_UHR_DATA6_RU_ALLOC_CC_2_2_5_KNOWN = 0x00000200, + IEEE80211_RADIOTAP_UHR_DATA6_RU_ALLOC_CC_1_2_6 = 0x0007fc00, + IEEE80211_RADIOTAP_UHR_DATA6_RU_ALLOC_CC_1_2_6_KNOWN = 0x00080000, + IEEE80211_RADIOTAP_UHR_DATA6_RU_ALLOC_CC_2_2_6 = 0x1ff00000, + IEEE80211_RADIOTAP_UHR_DATA6_RU_ALLOC_CC_2_2_6_KNOWN = 0x20000000, + /* data[7] */ + IEEE80211_RADIOTAP_UHR_DATA7_CRC2 = 0x0000000f, + IEEE80211_RADIOTAP_UHR_DATA7_TAIL2 = 0x000003f0, + IEEE80211_RADIOTAP_UHR_DATA7_INTERFERENCE_MITIGATION = 0x00000400, + IEEE80211_RADIOTAP_UHR_DATA7_DISREGARD_NON_OFDMA = 0x00001800, + IEEE80211_RADIOTAP_UHR_DATA7_NUMBER_OF_NON_OFDMA_USERS = 0x0000e000, + IEEE80211_RADIOTAP_UHR_DATA7_COMMON_ENCODING_BLOCK_CRC = 0x000f0000, + IEEE80211_RADIOTAP_UHR_DATA7_COMMON_ENCODING_BLOCK_TAIL = 0x03f00000, + /* data[8] */ + IEEE80211_RADIOTAP_UHR_DATA8_DRU_RRU_ALLOC_TB_FMT_PS_160= 0x00000001, + IEEE80211_RADIOTAP_UHR_DATA8_DRU_RRU_ALLOC_TB_FMT_B0 = 0x00000002, + IEEE80211_RADIOTAP_UHR_DATA8_DRU_RRU_ALLOC_TB_FMT_B7_B1 = 0x000001fc, + IEEE80211_RADIOTAP_UHR_DATA8_DRU_RRU_INDICATION = 0x00000200, +}; + +enum ieee80211_radiotap_uhr_user_known { + IEEE80211_RADIOTAP_UHR_USER_KNOWN_STA_ID = 0x00000001, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_MCS = 0x00000002, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_NSS = 0x00000004, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_UEQM = 0x00000008, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_BF = 0x00000010, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_CODING = 0x00000020, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_UEQM_PATTERN = 0x00000040, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_2X_LDPC = 0x00000080, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_SPATIAL_CONFIG = 0x00000100, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_DISREGARD = 0x00000200, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_BSS_COLOR_INDICATION = 0x00000400, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_USR_ENC_BLK_CRC = 0x00000800, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_USR_ENC_BLK_TAIL = 0x00001000, + /* really 'known' but actual data */ + IEEE80211_RADIOTAP_UHR_USER_KNOWN_DATA_USR_ENC_BLK_CRC = 0x000f0000, + IEEE80211_RADIOTAP_UHR_USER_KNOWN_DATA_USR_ENC_BLK_TAIL = 0x03f00000, + /* indicates this user was captured */ + IEEE80211_RADIOTAP_UHR_USER_KNOWN_USER_CAPTURED = 0x80000000, +}; + +enum ieee80211_radiotap_uhr_user_info { + IEEE80211_RADIOTAP_UHR_USER_INFO_STA_ID = 0x000007ff, + IEEE80211_RADIOTAP_UHR_USER_INFO_MCS = 0x0000f800, + IEEE80211_RADIOTAP_UHR_USER_INFO_NSS = 0x00070000, + IEEE80211_RADIOTAP_UHR_USER_INFO_SPATIAL_CONFIG = 0x000f0000, + IEEE80211_RADIOTAP_UHR_USER_INFO_UEQM = 0x00100000, + IEEE80211_RADIOTAP_UHR_USER_INFO_DISREGARD = 0x00100000, + IEEE80211_RADIOTAP_UHR_USER_INFO_BF = 0x00200000, + IEEE80211_RADIOTAP_UHR_USER_INFO_BSS_COLOR_INDICATION = 0x00200000, + IEEE80211_RADIOTAP_UHR_USER_INFO_UEQM_PATTERN = 0x00c00000, + IEEE80211_RADIOTAP_UHR_USER_INFO_CODING = 0x01000000, + IEEE80211_RADIOTAP_UHR_USER_INFO_2X_LDPC = 0x02000000, +}; + /** * ieee80211_get_radiotap_len - get radiotap header length * @data: pointer to the header From 4ab9b637b94a3821acfcd6d6037dffe33669245a Mon Sep 17 00:00:00 2001 From: Priyansha Tiwari Date: Thu, 11 Jun 2026 11:52:22 +0530 Subject: [PATCH 0173/1433] wifi: nl80211/cfg80211: rename probe_client to probe_peer Rename NL80211_CMD_PROBE_CLIENT to NL80211_CMD_PROBE_PEER in the UAPI enum and retain NL80211_CMD_PROBE_CLIENT as a compatibility alias. Rename the .probe_client cfg80211_ops callback to .probe_peer and update all in-tree users (wil6210, mwifiex) and mac80211 so the tree continues to build after this change. Signed-off-by: Priyansha Tiwari Link: https://patch.msgid.link/20260611062225.2144241-2-pritiwa@qti.qualcomm.com Signed-off-by: Johannes Berg --- drivers/net/wireless/ath/wil6210/cfg80211.c | 8 ++++---- drivers/net/wireless/marvell/mwifiex/cfg80211.c | 8 ++++---- include/net/cfg80211.h | 6 +++--- include/uapi/linux/nl80211.h | 5 +++-- net/mac80211/cfg.c | 6 +++--- net/wireless/nl80211.c | 17 ++++++++--------- net/wireless/rdev-ops.h | 10 +++++----- net/wireless/trace.h | 2 +- 8 files changed, 31 insertions(+), 31 deletions(-) diff --git a/drivers/net/wireless/ath/wil6210/cfg80211.c b/drivers/net/wireless/ath/wil6210/cfg80211.c index d6ef92cfcbaf..a85ff2a4316b 100644 --- a/drivers/net/wireless/ath/wil6210/cfg80211.c +++ b/drivers/net/wireless/ath/wil6210/cfg80211.c @@ -2379,9 +2379,9 @@ void wil_probe_client_flush(struct wil6210_vif *vif) mutex_unlock(&vif->probe_client_mutex); } -static int wil_cfg80211_probe_client(struct wiphy *wiphy, - struct net_device *dev, - const u8 *peer, u64 *cookie) +static int wil_cfg80211_probe_peer(struct wiphy *wiphy, + struct net_device *dev, + const u8 *peer, u64 *cookie) { struct wil6210_priv *wil = wiphy_to_wil(wiphy); struct wil6210_vif *vif = ndev_to_vif(dev); @@ -2660,7 +2660,7 @@ static const struct cfg80211_ops wil_cfg80211_ops = { .add_station = wil_cfg80211_add_station, .del_station = wil_cfg80211_del_station, .change_station = wil_cfg80211_change_station, - .probe_client = wil_cfg80211_probe_client, + .probe_peer = wil_cfg80211_probe_peer, .change_bss = wil_cfg80211_change_bss, /* P2P device */ .start_p2p_device = wil_cfg80211_start_p2p_device, diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c index c9daf893472f..99d96088e364 100644 --- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c +++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c @@ -4558,9 +4558,9 @@ mwifiex_cfg80211_disassociate(struct wiphy *wiphy, } static int -mwifiex_cfg80211_probe_client(struct wiphy *wiphy, - struct net_device *dev, const u8 *peer, - u64 *cookie) +mwifiex_cfg80211_probe_peer(struct wiphy *wiphy, + struct net_device *dev, const u8 *peer, + u64 *cookie) { /* hostapd looks for NL80211_CMD_PROBE_CLIENT support; otherwise, * it requires monitor-mode support (which mwifiex doesn't support). @@ -4726,7 +4726,7 @@ int mwifiex_register_cfg80211(struct mwifiex_adapter *adapter) ops->disassoc = mwifiex_cfg80211_disassociate; ops->disconnect = NULL; ops->connect = NULL; - ops->probe_client = mwifiex_cfg80211_probe_client; + ops->probe_peer = mwifiex_cfg80211_probe_peer; } wiphy->max_scan_ssids = MWIFIEX_MAX_SSID_LIST_LENGTH; wiphy->max_scan_ie_len = MWIFIEX_MAX_VSIE_LEN; diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 8188ad200de5..549b2214e833 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -5086,7 +5086,7 @@ struct mgmt_frame_regs { * @tdls_mgmt: Transmit a TDLS management frame. * @tdls_oper: Perform a high-level TDLS operation (e.g. TDLS link setup). * - * @probe_client: probe an associated client, must return a cookie that it + * @probe_peer: probe an associated client, must return a cookie that it * later passes to cfg80211_probe_status(). * * @set_noack_map: Set the NoAck Map for the TIDs. @@ -5488,8 +5488,8 @@ struct cfg80211_ops { int (*tdls_oper)(struct wiphy *wiphy, struct net_device *dev, const u8 *peer, enum nl80211_tdls_operation oper); - int (*probe_client)(struct wiphy *wiphy, struct net_device *dev, - const u8 *peer, u64 *cookie); + int (*probe_peer)(struct wiphy *wiphy, struct net_device *dev, + const u8 *peer, u64 *cookie); int (*set_noack_map)(struct wiphy *wiphy, struct net_device *dev, diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 9998f6c0a665..d1907dd12a80 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -922,7 +922,7 @@ * and wasn't already in a 4-addr VLAN. The event will be sent similarly * to the %NL80211_CMD_UNEXPECTED_FRAME event, to the same listener. * - * @NL80211_CMD_PROBE_CLIENT: Probe an associated station on an AP interface + * @NL80211_CMD_PROBE_PEER: Probe an associated station on an AP interface * by sending a null data frame to it and reporting when the frame is * acknowledged. This is used to allow timing out inactive clients. Uses * %NL80211_ATTR_IFINDEX and %NL80211_ATTR_MAC. The command returns a @@ -1558,7 +1558,7 @@ enum nl80211_commands { NL80211_CMD_UNEXPECTED_FRAME, - NL80211_CMD_PROBE_CLIENT, + NL80211_CMD_PROBE_PEER, NL80211_CMD_REGISTER_BEACONS, @@ -1729,6 +1729,7 @@ enum nl80211_commands { #define NL80211_CMD_GET_MESH_PARAMS NL80211_CMD_GET_MESH_CONFIG #define NL80211_CMD_SET_MESH_PARAMS NL80211_CMD_SET_MESH_CONFIG #define NL80211_MESH_SETUP_VENDOR_PATH_SEL_IE NL80211_MESH_SETUP_IE +#define NL80211_CMD_PROBE_CLIENT NL80211_CMD_PROBE_PEER /** * enum nl80211_attrs - nl80211 netlink attributes diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 3b58af59f7e4..9c311c8290f7 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -4949,8 +4949,8 @@ static int ieee80211_set_rekey_data(struct wiphy *wiphy, return 0; } -static int ieee80211_probe_client(struct wiphy *wiphy, struct net_device *dev, - const u8 *peer, u64 *cookie) +static int ieee80211_probe_peer(struct wiphy *wiphy, struct net_device *dev, + const u8 *peer, u64 *cookie) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; @@ -6060,7 +6060,7 @@ const struct cfg80211_ops mac80211_config_ops = { .tdls_mgmt = ieee80211_tdls_mgmt, .tdls_channel_switch = ieee80211_tdls_channel_switch, .tdls_cancel_channel_switch = ieee80211_tdls_cancel_channel_switch, - .probe_client = ieee80211_probe_client, + .probe_peer = ieee80211_probe_peer, .set_noack_map = ieee80211_set_noack_map, #ifdef CONFIG_PM .set_wakeup = ieee80211_set_wakeup, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 53b4b3f76697..0d651a46b9d6 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -2444,7 +2444,7 @@ static int nl80211_add_commands_unsplit(struct cfg80211_registered_device *rdev, } if (rdev->wiphy.max_sched_scan_reqs) CMD(sched_scan_start, START_SCHED_SCAN); - CMD(probe_client, PROBE_CLIENT); + CMD(probe_peer, PROBE_PEER); CMD(set_noack_map, SET_NOACK_MAP); if (rdev->wiphy.flags & WIPHY_FLAG_REPORTS_OBSS) { i++; @@ -16150,8 +16150,7 @@ static int nl80211_register_unexpected_frame(struct sk_buff *skb, return 0; } -static int nl80211_probe_client(struct sk_buff *skb, - struct genl_info *info) +static int nl80211_probe_peer(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev = info->user_ptr[0]; struct net_device *dev = info->user_ptr[1]; @@ -16169,7 +16168,7 @@ static int nl80211_probe_client(struct sk_buff *skb, if (!info->attrs[NL80211_ATTR_MAC]) return -EINVAL; - if (!rdev->ops->probe_client) + if (!rdev->ops->probe_peer) return -EOPNOTSUPP; msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); @@ -16177,7 +16176,7 @@ static int nl80211_probe_client(struct sk_buff *skb, return -ENOMEM; hdr = nl80211hdr_put(msg, info->snd_portid, info->snd_seq, 0, - NL80211_CMD_PROBE_CLIENT); + NL80211_CMD_PROBE_PEER); if (!hdr) { err = -ENOBUFS; goto free_msg; @@ -16185,7 +16184,7 @@ static int nl80211_probe_client(struct sk_buff *skb, addr = nla_data(info->attrs[NL80211_ATTR_MAC]); - err = rdev_probe_client(rdev, dev, addr, &cookie); + err = rdev_probe_peer(rdev, dev, addr, &cookie); if (err) goto free_msg; @@ -20042,9 +20041,9 @@ static const struct genl_small_ops nl80211_small_ops[] = { .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV), }, { - .cmd = NL80211_CMD_PROBE_CLIENT, + .cmd = NL80211_CMD_PROBE_PEER, .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, - .doit = nl80211_probe_client, + .doit = nl80211_probe_peer, .flags = GENL_UNS_ADMIN_PERM, .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP), }, @@ -22614,7 +22613,7 @@ void cfg80211_probe_status(struct net_device *dev, const u8 *addr, if (!msg) return; - hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_PROBE_CLIENT); + hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_PROBE_PEER); if (!hdr) { nlmsg_free(msg); return; diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h index 63c26e8b1139..6c3bad8b2d6f 100644 --- a/net/wireless/rdev-ops.h +++ b/net/wireless/rdev-ops.h @@ -948,13 +948,13 @@ static inline int rdev_tdls_oper(struct cfg80211_registered_device *rdev, return ret; } -static inline int rdev_probe_client(struct cfg80211_registered_device *rdev, - struct net_device *dev, const u8 *peer, - u64 *cookie) +static inline int rdev_probe_peer(struct cfg80211_registered_device *rdev, + struct net_device *dev, const u8 *peer, + u64 *cookie) { int ret; - trace_rdev_probe_client(&rdev->wiphy, dev, peer); - ret = rdev->ops->probe_client(&rdev->wiphy, dev, peer, cookie); + trace_rdev_probe_peer(&rdev->wiphy, dev, peer); + ret = rdev->ops->probe_peer(&rdev->wiphy, dev, peer, cookie); trace_rdev_return_int_cookie(&rdev->wiphy, ret, *cookie); return ret; } diff --git a/net/wireless/trace.h b/net/wireless/trace.h index 94944f2a39a4..8c2a91b85c39 100644 --- a/net/wireless/trace.h +++ b/net/wireless/trace.h @@ -2132,7 +2132,7 @@ DECLARE_EVENT_CLASS(rdev_pmksa, WIPHY_PR_ARG, NETDEV_PR_ARG, __entry->bssid) ); -TRACE_EVENT(rdev_probe_client, +TRACE_EVENT(rdev_probe_peer, TP_PROTO(struct wiphy *wiphy, struct net_device *netdev, const u8 *peer), TP_ARGS(wiphy, netdev, peer), From 010e955c203e435d5bdba228fdc1556297d3d7ff Mon Sep 17 00:00:00 2001 From: Priyansha Tiwari Date: Thu, 11 Jun 2026 11:52:23 +0530 Subject: [PATCH 0174/1433] wifi: cfg80211/nl80211: add STA-mode peer probing Add NL80211_EXT_FEATURE_PROBE_AP to allow drivers to advertise support for probing the associated AP from STA/P2P-client mode. Extend nl80211_probe_peer() to accept STA/P2P-client interfaces when the driver advertises NL80211_EXT_FEATURE_PROBE_AP; in that case the MAC attribute must be omitted (the peer is implied by the association). Update cfg80211_probe_status() to accept an optional peer address and a link_id parameter (-1 for non-MLO), and include NL80211_ATTR_MLO_LINK_ID in the event when link_id >= 0. Update all callers. Signed-off-by: Priyansha Tiwari Link: https://patch.msgid.link/20260611062225.2144241-3-pritiwa@qti.qualcomm.com Signed-off-by: Johannes Berg --- drivers/net/wireless/ath/wil6210/cfg80211.c | 2 +- include/net/cfg80211.h | 14 +++--- include/uapi/linux/nl80211.h | 20 +++++--- net/mac80211/status.c | 2 +- net/wireless/nl80211.c | 52 ++++++++++++++------- 5 files changed, 59 insertions(+), 31 deletions(-) diff --git a/drivers/net/wireless/ath/wil6210/cfg80211.c b/drivers/net/wireless/ath/wil6210/cfg80211.c index a85ff2a4316b..5f2bd9a31faf 100644 --- a/drivers/net/wireless/ath/wil6210/cfg80211.c +++ b/drivers/net/wireless/ath/wil6210/cfg80211.c @@ -2326,7 +2326,7 @@ static void wil_probe_client_handle(struct wil6210_priv *wil, */ bool alive = (sta->status == wil_sta_connected); - cfg80211_probe_status(ndev, sta->addr, req->cookie, alive, + cfg80211_probe_status(ndev, sta->addr, req->cookie, -1, alive, 0, false, GFP_KERNEL); } diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 549b2214e833..ddefe5acc5ae 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -5086,8 +5086,8 @@ struct mgmt_frame_regs { * @tdls_mgmt: Transmit a TDLS management frame. * @tdls_oper: Perform a high-level TDLS operation (e.g. TDLS link setup). * - * @probe_peer: probe an associated client, must return a cookie that it - * later passes to cfg80211_probe_status(). + * @probe_peer: probe a connected peer (AP: STA MAC required; STA: no MAC), + * must return a cookie that is later passed to cfg80211_probe_status(). * * @set_noack_map: Set the NoAck Map for the TIDs. * @@ -9846,15 +9846,17 @@ bool cfg80211_rx_unexpected_4addr_frame(struct net_device *dev, const u8 *addr, /** * cfg80211_probe_status - notify userspace about probe status * @dev: the device the probe was sent on - * @addr: the address of the peer - * @cookie: the cookie filled in @probe_client previously + * @peer: The peer MAC address (or MLD address for MLO) or %NULL if not + * applicable (e.g. for STA/P2P-client) + * @cookie: the cookie filled in @probe_peer previously + * @link_id: The link ID on which the probe was sent (or -1 for non-MLO) * @acked: indicates whether probe was acked or not * @ack_signal: signal strength (in dBm) of the ACK frame. * @is_valid_ack_signal: indicates the ack_signal is valid or not. * @gfp: allocation flags */ -void cfg80211_probe_status(struct net_device *dev, const u8 *addr, - u64 cookie, bool acked, s32 ack_signal, +void cfg80211_probe_status(struct net_device *dev, const u8 *peer, u64 cookie, + int link_id, bool acked, s32 ack_signal, bool is_valid_ack_signal, gfp_t gfp); /** diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index d1907dd12a80..6b8071606e6f 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -922,13 +922,15 @@ * and wasn't already in a 4-addr VLAN. The event will be sent similarly * to the %NL80211_CMD_UNEXPECTED_FRAME event, to the same listener. * - * @NL80211_CMD_PROBE_PEER: Probe an associated station on an AP interface - * by sending a null data frame to it and reporting when the frame is - * acknowledged. This is used to allow timing out inactive clients. Uses - * %NL80211_ATTR_IFINDEX and %NL80211_ATTR_MAC. The command returns a - * direct reply with an %NL80211_ATTR_COOKIE that is later used to match - * up the event with the request. The event includes the same data and - * has %NL80211_ATTR_ACK set if the frame was ACKed. + * @NL80211_CMD_PROBE_PEER: Probe a connected peer by sending a null data + * frame and reporting when the frame is acknowledged. + * In AP/GO mode, %NL80211_ATTR_MAC is required to identify the client. + * In STA/P2P-client mode, %NL80211_ATTR_MAC must be omitted (the AP is + * implied); the driver must advertise %NL80211_EXT_FEATURE_PROBE_AP. + * The command returns a direct reply with an %NL80211_ATTR_COOKIE that + * is later used to match up the event with the request. The event + * includes the same data and has %NL80211_ATTR_ACK set if the frame + * was ACKed. * * @NL80211_CMD_REGISTER_BEACONS: Register this socket to receive beacons from * other BSSes when any interfaces are in AP mode. This helps implement @@ -7086,6 +7088,9 @@ enum nl80211_feature_flags { * LTF key seed via %NL80211_KEY_LTF_SEED. The seed is used to generate * secure LTF keys for secure LTF measurement sessions. * + * @NL80211_EXT_FEATURE_PROBE_AP: Driver supports probing the associated AP + * in STA mode using @NL80211_CMD_PROBE_PEER. + * * @NUM_NL80211_EXT_FEATURES: number of extended features. * @MAX_NL80211_EXT_FEATURES: highest extended feature index. */ @@ -7167,6 +7172,7 @@ enum nl80211_ext_feature_index { NL80211_EXT_FEATURE_IEEE8021X_AUTH, NL80211_EXT_FEATURE_ROC_ADDR_FILTER, NL80211_EXT_FEATURE_SET_KEY_LTF_SEED, + NL80211_EXT_FEATURE_PROBE_AP, /* add new features before the definition below */ NUM_NL80211_EXT_FEATURES, diff --git a/net/mac80211/status.c b/net/mac80211/status.c index dd1dbba06838..c3d29aed93fe 100644 --- a/net/mac80211/status.c +++ b/net/mac80211/status.c @@ -655,7 +655,7 @@ static void ieee80211_report_ack_skb(struct ieee80211_local *local, GFP_ATOMIC); else if (ieee80211_is_any_nullfunc(hdr->frame_control)) cfg80211_probe_status(sdata->dev, hdr->addr1, - cookie, acked, + cookie, -1, acked, info->status.ack_signal, is_valid_ack_signal, GFP_ATOMIC); diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 0d651a46b9d6..a62e319f6aec 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -16157,16 +16157,32 @@ static int nl80211_probe_peer(struct sk_buff *skb, struct genl_info *info) struct wireless_dev *wdev = dev->ieee80211_ptr; struct sk_buff *msg; void *hdr; - const u8 *addr; + const u8 *addr = NULL; u64 cookie; int err; - if (wdev->iftype != NL80211_IFTYPE_AP && - wdev->iftype != NL80211_IFTYPE_P2P_GO) + /* Allow in AP, STA, and their P2P counterparts */ + switch (wdev->iftype) { + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + if (!info->attrs[NL80211_ATTR_MAC]) + return -EINVAL; + addr = nla_data(info->attrs[NL80211_ATTR_MAC]); + break; + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + if (!wiphy_ext_feature_isset(&rdev->wiphy, + NL80211_EXT_FEATURE_PROBE_AP)) + return -EOPNOTSUPP; + if (!wdev->connected) + return -ENOLINK; + /* STA/P2P-client probes the currently associated AP/GO. */ + if (info->attrs[NL80211_ATTR_MAC]) + return -EINVAL; + break; + default: return -EOPNOTSUPP; - - if (!info->attrs[NL80211_ATTR_MAC]) - return -EINVAL; + } if (!rdev->ops->probe_peer) return -EOPNOTSUPP; @@ -16182,8 +16198,6 @@ static int nl80211_probe_peer(struct sk_buff *skb, struct genl_info *info) goto free_msg; } - addr = nla_data(info->attrs[NL80211_ATTR_MAC]); - err = rdev_probe_peer(rdev, dev, addr, &cookie); if (err) goto free_msg; @@ -22597,8 +22611,8 @@ void cfg80211_sta_opmode_change_notify(struct net_device *dev, const u8 *mac, } EXPORT_SYMBOL(cfg80211_sta_opmode_change_notify); -void cfg80211_probe_status(struct net_device *dev, const u8 *addr, - u64 cookie, bool acked, s32 ack_signal, +void cfg80211_probe_status(struct net_device *dev, const u8 *peer, u64 cookie, + int link_id, bool acked, s32 ack_signal, bool is_valid_ack_signal, gfp_t gfp) { struct wireless_dev *wdev = dev->ieee80211_ptr; @@ -22606,7 +22620,7 @@ void cfg80211_probe_status(struct net_device *dev, const u8 *addr, struct sk_buff *msg; void *hdr; - trace_cfg80211_probe_status(dev, addr, cookie, acked); + trace_cfg80211_probe_status(dev, peer, cookie, acked); msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp); @@ -22621,12 +22635,18 @@ void cfg80211_probe_status(struct net_device *dev, const u8 *addr, if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) || nla_put_u32(msg, NL80211_ATTR_IFINDEX, dev->ifindex) || - nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, addr) || + (peer && nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, peer)) || nla_put_u64_64bit(msg, NL80211_ATTR_COOKIE, cookie, - NL80211_ATTR_PAD) || - (acked && nla_put_flag(msg, NL80211_ATTR_ACK)) || - (is_valid_ack_signal && nla_put_s32(msg, NL80211_ATTR_ACK_SIGNAL, - ack_signal))) + NL80211_ATTR_PAD)) + goto nla_put_failure; + + if (link_id >= 0 && + nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id)) + goto nla_put_failure; + + if ((acked && nla_put_flag(msg, NL80211_ATTR_ACK)) || + (is_valid_ack_signal && + nla_put_s32(msg, NL80211_ATTR_ACK_SIGNAL, ack_signal))) goto nla_put_failure; genlmsg_end(msg, hdr); From f83378e6ce497eda7e9a7490de4e1a46459febb2 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 15 Jun 2026 09:39:48 +0200 Subject: [PATCH 0175/1433] wifi: nl80211: clarify NL80211_BAND_IFTYPE_ATTR_HE_6GHZ_CAPA content This is currently __le16, but really the whole content of the corresponding 802.11 element, which is even extensible and could, in theory, be increased in size. Clarify the docs. Link: https://patch.msgid.link/20260615093948.0f730833a6d5.I1c8c5c09dfe16b0b1dcb10d54fc030f6b1d4fc8c@changeid Signed-off-by: Johannes Berg --- include/uapi/linux/nl80211.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 6b8071606e6f..d9a8c693457f 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -4477,8 +4477,8 @@ enum nl80211_mpath_info { * capabilities IE * @NL80211_BAND_IFTYPE_ATTR_HE_CAP_PPE: HE PPE thresholds information as * defined in HE capabilities IE - * @NL80211_BAND_IFTYPE_ATTR_HE_6GHZ_CAPA: HE 6GHz band capabilities (__le16), - * given for all 6 GHz band channels + * @NL80211_BAND_IFTYPE_ATTR_HE_6GHZ_CAPA: HE 6GHz band capabilities, + * given for all 6 GHz band channels (binary, element content) * @NL80211_BAND_IFTYPE_ATTR_VENDOR_ELEMS: vendor element capabilities that are * advertised on this band/for this iftype (binary) * @NL80211_BAND_IFTYPE_ATTR_EHT_CAP_MAC: EHT MAC capabilities as in EHT From 84442442e04be58a8977fb10debcbbfc1649d962 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 19 Jun 2026 14:21:07 +0200 Subject: [PATCH 0176/1433] wifi: cfg80211: remove WIPHY_FLAG_DISABLE_WEXT There are only two drivers left setting it, but they're both also setting WIPHY_FLAG_SUPPORTS_MLO for the relevant devices, so we can now remove WIPHY_FLAG_DISABLE_WEXT. Link: https://patch.msgid.link/20260619142107.150f1bbe3b83.I9ff3d419bad54313c76fa4c3485148c122e67fb3@changeid Signed-off-by: Johannes Berg --- drivers/net/wireless/ath/ath12k/mac.c | 6 ------ drivers/net/wireless/realtek/rtw89/core.c | 3 --- include/net/cfg80211.h | 3 +-- net/wireless/wext-core.c | 6 ++---- 4 files changed, 3 insertions(+), 15 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index af354bef5c0d..9775a87b3db3 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -14871,12 +14871,6 @@ static int ath12k_mac_hw_register(struct ath12k_hw *ah) wiphy->features |= NL80211_FEATURE_TX_POWER_INSERTION; - /* MLO is not yet supported so disable Wireless Extensions for now - * to make sure ath12k users don't use it. This flag can be removed - * once WIPHY_FLAG_SUPPORTS_MLO is enabled. - */ - wiphy->flags |= WIPHY_FLAG_DISABLE_WEXT; - /* Copy over MLO related capabilities received from * WMI_SERVICE_READY_EXT2_EVENT if single_chip_mlo_supp is set. */ diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index 68dad6090f87..0f0e46cb4260 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -7432,9 +7432,6 @@ static int rtw89_core_register_hw(struct rtw89_dev *rtwdev) if (!chip->support_rnr) hw->wiphy->flags |= WIPHY_FLAG_SPLIT_SCAN_6GHZ; - if (chip->chip_gen == RTW89_CHIP_BE) - hw->wiphy->flags |= WIPHY_FLAG_DISABLE_WEXT; - if (rtwdev->support_mlo) { hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_MLO; hw->wiphy->iftype_ext_capab = rtw89_iftypes_ext_capa; diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index ddefe5acc5ae..d91533a66712 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -5690,7 +5690,6 @@ struct cfg80211_ops { * set this flag to update channels on beacon hints. * @WIPHY_FLAG_SUPPORTS_NSTR_NONPRIMARY: support connection to non-primary link * of an NSTR mobile AP MLD. - * @WIPHY_FLAG_DISABLE_WEXT: disable wireless extensions for this device */ enum wiphy_flags { WIPHY_FLAG_SUPPORTS_EXT_KEK_KCK = BIT(0), @@ -5702,7 +5701,7 @@ enum wiphy_flags { WIPHY_FLAG_4ADDR_STATION = BIT(6), WIPHY_FLAG_CONTROL_PORT_PROTOCOL = BIT(7), WIPHY_FLAG_IBSS_RSN = BIT(8), - WIPHY_FLAG_DISABLE_WEXT = BIT(9), + /* reuse bit 9 */ WIPHY_FLAG_MESH_AUTH = BIT(10), WIPHY_FLAG_SUPPORTS_EXT_KCK_32 = BIT(11), WIPHY_FLAG_SUPPORTS_NSTR_NONPRIMARY = BIT(12), diff --git a/net/wireless/wext-core.c b/net/wireless/wext-core.c index c19dece2bc6e..db77912b3994 100644 --- a/net/wireless/wext-core.c +++ b/net/wireless/wext-core.c @@ -660,8 +660,7 @@ struct iw_statistics *get_wireless_stats(struct net_device *dev) dev->ieee80211_ptr->wiphy->wext && dev->ieee80211_ptr->wiphy->wext->get_wireless_stats) { wireless_warn_cfg80211_wext(); - if (dev->ieee80211_ptr->wiphy->flags & (WIPHY_FLAG_SUPPORTS_MLO | - WIPHY_FLAG_DISABLE_WEXT)) + if (dev->ieee80211_ptr->wiphy->flags & WIPHY_FLAG_SUPPORTS_MLO) return NULL; return dev->ieee80211_ptr->wiphy->wext->get_wireless_stats(dev); } @@ -703,8 +702,7 @@ static iw_handler get_handler(struct net_device *dev, unsigned int cmd) #ifdef CONFIG_CFG80211_WEXT if (dev->ieee80211_ptr && dev->ieee80211_ptr->wiphy) { wireless_warn_cfg80211_wext(); - if (dev->ieee80211_ptr->wiphy->flags & (WIPHY_FLAG_SUPPORTS_MLO | - WIPHY_FLAG_DISABLE_WEXT)) + if (dev->ieee80211_ptr->wiphy->flags & WIPHY_FLAG_SUPPORTS_MLO) return NULL; handlers = dev->ieee80211_ptr->wiphy->wext; } From 5e20d72350df589478691310f0e1c427f35ee4aa Mon Sep 17 00:00:00 2001 From: Dhanavandhana Kannan Date: Tue, 23 Jun 2026 16:04:12 +0530 Subject: [PATCH 0177/1433] wifi: cfg80211: Avoid UNPROT_BEACON on AP interfaces Currently, an AP may receive unprotected beacons from neighbouring BSSes, which cfg80211_rx_unprot_mlme_mgmt() forwards to userspace via NL80211_CMD_UNPROT_BEACON regardless of interface type. While the kernel rate-limits these events to once per 10 seconds per wdev, in multi-BSS scenarios each AP interface maintains its own rate-limit state, increasing the number of reported events. In AP mode, hostapd has no handler for NL80211_CMD_UNPROT_BEACON and logs an unhandled event message for each occurrence, leading to excessive log noise and making it harder to identify real issues. Since an AP does not need to act on unprotected beacons from neighbouring BSSes, skip reporting this event when operating in AP mode. Signed-off-by: Dhanavandhana Kannan Link: https://patch.msgid.link/20260623103412.1578812-1-dhanavandhana.kannan@oss.qualcomm.com Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index a62e319f6aec..d738c0089990 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -20903,6 +20903,9 @@ void cfg80211_rx_unprot_mlme_mgmt(struct net_device *dev, const u8 *buf, } else if (ieee80211_is_disassoc(mgmt->frame_control)) { event.cmd = NL80211_CMD_UNPROT_DISASSOCIATE; } else if (ieee80211_is_beacon(mgmt->frame_control)) { + if (wdev->iftype == NL80211_IFTYPE_AP || + wdev->iftype == NL80211_IFTYPE_P2P_GO) + return; if (wdev->unprot_beacon_reported && elapsed_jiffies_msecs(wdev->unprot_beacon_reported) < 10000) return; From 1c29c67bc7a9962e2ef91e8c65b7fc4fa227eded Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Fri, 26 Jun 2026 16:28:57 +1000 Subject: [PATCH 0178/1433] wifi: cfg80211: introduce helper to get S1G primary width This is needed for drivers and will be needed for mac80211/cfg80211 in the future so introduce a generic accessor to retrieve the chandefs S1G primary channel width. Signed-off-by: Lachlan Hodges Link: https://patch.msgid.link/20260626063014.1275235-2-lachlan.hodges@morsemicro.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index d91533a66712..b8e9fbb89e69 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -1236,6 +1236,26 @@ ieee80211_chandef_max_power(struct cfg80211_chan_def *chandef) return chandef->chan->max_power; } +/** + * cfg80211_chandef_s1g_pri_width - return S1G primary width in MHz + * + * An S1G interface may have a primary channel width of either 1 + * or 2MHz depending on whether chandef::s1g_primary_2mhz is set. + * + * Note: There is _always_ a 1MHz primary subchannel, regardless + * of the primary width. So chandef::chan always points to this + * 1MHz primary channel. + * + * @chandef: the chandef to use + * + * Returns: width in MHz of the S1G primary channel in use + */ +static inline int +cfg80211_chandef_s1g_pri_width(struct cfg80211_chan_def *chandef) +{ + return chandef->s1g_primary_2mhz ? 2 : 1; +} + /** * cfg80211_any_usable_channels - check for usable channels * @wiphy: the wiphy to check for From cc61825060b01077da2b9796dc1047ec383e53c0 Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Fri, 26 Jun 2026 16:28:58 +1000 Subject: [PATCH 0179/1433] wifi: ieee80211: introduce generic KHZ_TO_HZ helper Useful for S1G drivers due to the increased required granularity, but may be useful for others so include it as a generic helper. Signed-off-by: Lachlan Hodges Link: https://patch.msgid.link/20260626063014.1275235-3-lachlan.hodges@morsemicro.com Signed-off-by: Johannes Berg --- include/linux/ieee80211.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h index d40484451e9a..084ad45aa2d8 100644 --- a/include/linux/ieee80211.h +++ b/include/linux/ieee80211.h @@ -2616,6 +2616,7 @@ static inline int ieee80211_get_tdls_action(struct sk_buff *skb) /* convert frequencies */ #define MHZ_TO_KHZ(freq) ((freq) * 1000) #define KHZ_TO_MHZ(freq) ((freq) / 1000) +#define KHZ_TO_HZ(x) ((x) * 1000) #define PR_KHZ(f) KHZ_TO_MHZ(f), f % 1000 #define KHZ_F "%d.%03d" From 1cb971bb9741d2358fc422f6603b68dfaf424335 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 1 Jul 2026 16:59:10 +0300 Subject: [PATCH 0180/1433] wifi: b43, b43legacy: debugfs: use kzalloc() to allocate formatting buffers b43* debugfs functions allocate 16 KiB buffers for formatting debug output text using __get_free_pages(). kzalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object and for 16 Kib allocation kzalloc() will anyway delegate it to buddy. Replace use of __get_free_pages() with kzalloc(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260701-b4-drivers-wireless-v1-1-60264cdf2efe@kernel.org Signed-off-by: Johannes Berg --- drivers/net/wireless/broadcom/b43/debugfs.c | 12 +++++------- drivers/net/wireless/broadcom/b43legacy/debugfs.c | 12 +++++------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/drivers/net/wireless/broadcom/b43/debugfs.c b/drivers/net/wireless/broadcom/b43/debugfs.c index acddae68947a..31a1ff00c1a4 100644 --- a/drivers/net/wireless/broadcom/b43/debugfs.c +++ b/drivers/net/wireless/broadcom/b43/debugfs.c @@ -495,7 +495,6 @@ static ssize_t b43_debugfs_read(struct file *file, char __user *userbuf, ssize_t ret; char *buf; const size_t bufsize = 1024 * 16; /* 16 kiB buffer */ - const size_t buforder = get_order(bufsize); int err = 0; if (!count) @@ -518,15 +517,14 @@ static ssize_t b43_debugfs_read(struct file *file, char __user *userbuf, dfile = fops_to_dfs_file(dev, dfops); if (!dfile->buffer) { - buf = (char *)__get_free_pages(GFP_KERNEL, buforder); + buf = kzalloc(bufsize, GFP_KERNEL); if (!buf) { err = -ENOMEM; goto out_unlock; } - memset(buf, 0, bufsize); ret = dfops->read(dev, buf, bufsize); if (ret <= 0) { - free_pages((unsigned long)buf, buforder); + kfree(buf); err = ret; goto out_unlock; } @@ -538,7 +536,7 @@ static ssize_t b43_debugfs_read(struct file *file, char __user *userbuf, dfile->buffer, dfile->data_len); if (*ppos >= dfile->data_len) { - free_pages((unsigned long)dfile->buffer, buforder); + kfree(dfile->buffer); dfile->buffer = NULL; dfile->data_len = 0; } @@ -577,7 +575,7 @@ static ssize_t b43_debugfs_write(struct file *file, goto out_unlock; } - buf = (char *)get_zeroed_page(GFP_KERNEL); + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) { err = -ENOMEM; goto out_unlock; @@ -591,7 +589,7 @@ static ssize_t b43_debugfs_write(struct file *file, goto out_freepage; out_freepage: - free_page((unsigned long)buf); + kfree(buf); out_unlock: mutex_unlock(&dev->wl->mutex); diff --git a/drivers/net/wireless/broadcom/b43legacy/debugfs.c b/drivers/net/wireless/broadcom/b43legacy/debugfs.c index 3ad99124d522..a04d90d7307c 100644 --- a/drivers/net/wireless/broadcom/b43legacy/debugfs.c +++ b/drivers/net/wireless/broadcom/b43legacy/debugfs.c @@ -192,7 +192,6 @@ static ssize_t b43legacy_debugfs_read(struct file *file, char __user *userbuf, ssize_t ret; char *buf; const size_t bufsize = 1024 * 16; /* 16 KiB buffer */ - const size_t buforder = get_order(bufsize); int err = 0; if (!count) @@ -215,12 +214,11 @@ static ssize_t b43legacy_debugfs_read(struct file *file, char __user *userbuf, dfile = fops_to_dfs_file(dev, dfops); if (!dfile->buffer) { - buf = (char *)__get_free_pages(GFP_KERNEL, buforder); + buf = kzalloc(bufsize, GFP_KERNEL); if (!buf) { err = -ENOMEM; goto out_unlock; } - memset(buf, 0, bufsize); if (dfops->take_irqlock) { spin_lock_irq(&dev->wl->irq_lock); ret = dfops->read(dev, buf, bufsize); @@ -228,7 +226,7 @@ static ssize_t b43legacy_debugfs_read(struct file *file, char __user *userbuf, } else ret = dfops->read(dev, buf, bufsize); if (ret <= 0) { - free_pages((unsigned long)buf, buforder); + kfree(buf); err = ret; goto out_unlock; } @@ -240,7 +238,7 @@ static ssize_t b43legacy_debugfs_read(struct file *file, char __user *userbuf, dfile->buffer, dfile->data_len); if (*ppos >= dfile->data_len) { - free_pages((unsigned long)dfile->buffer, buforder); + kfree(dfile->buffer); dfile->buffer = NULL; dfile->data_len = 0; } @@ -279,7 +277,7 @@ static ssize_t b43legacy_debugfs_write(struct file *file, goto out_unlock; } - buf = (char *)get_zeroed_page(GFP_KERNEL); + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) { err = -ENOMEM; goto out_unlock; @@ -298,7 +296,7 @@ static ssize_t b43legacy_debugfs_write(struct file *file, goto out_freepage; out_freepage: - free_page((unsigned long)buf); + kfree(buf); out_unlock: mutex_unlock(&dev->wl->mutex); From 535fd0a64f94f4a6d93e2fd1daf829663eb17e06 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 1 Jul 2026 16:59:11 +0300 Subject: [PATCH 0181/1433] wifi: libertas: debugfs: use kzalloc() to allocate formatting buffers libertas debugfs functions allocate buffers for formatting debug output text using get_zeroed_page(). These buffers can be allocated with kmalloc() as there's nothing special about them to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of get_zeroed_page() with kzalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260701-b4-drivers-wireless-v1-2-60264cdf2efe@kernel.org Signed-off-by: Johannes Berg --- .../net/wireless/marvell/libertas/debugfs.c | 39 ++++++++----------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/drivers/net/wireless/marvell/libertas/debugfs.c b/drivers/net/wireless/marvell/libertas/debugfs.c index 9ebd69134940..9428f954837a 100644 --- a/drivers/net/wireless/marvell/libertas/debugfs.c +++ b/drivers/net/wireless/marvell/libertas/debugfs.c @@ -35,8 +35,7 @@ static ssize_t lbs_dev_info(struct file *file, char __user *userbuf, { struct lbs_private *priv = file->private_data; size_t pos = 0; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *)addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); ssize_t res; if (!buf) return -ENOMEM; @@ -48,7 +47,7 @@ static ssize_t lbs_dev_info(struct file *file, char __user *userbuf, res = simple_read_from_buffer(userbuf, count, ppos, buf, pos); - free_page(addr); + kfree(buf); return res; } @@ -96,8 +95,7 @@ static ssize_t lbs_sleepparams_read(struct file *file, char __user *userbuf, ssize_t ret; size_t pos = 0; struct sleep_params sp; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *)addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; @@ -113,7 +111,7 @@ static ssize_t lbs_sleepparams_read(struct file *file, char __user *userbuf, ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos); out_unlock: - free_page(addr); + kfree(buf); return ret; } @@ -165,8 +163,7 @@ static ssize_t lbs_host_sleep_read(struct file *file, char __user *userbuf, struct lbs_private *priv = file->private_data; ssize_t ret; size_t pos = 0; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *)addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; @@ -174,7 +171,7 @@ static ssize_t lbs_host_sleep_read(struct file *file, char __user *userbuf, ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos); - free_page(addr); + kfree(buf); return ret; } @@ -228,7 +225,7 @@ static ssize_t lbs_threshold_read(uint16_t tlv_type, uint16_t event_mask, u8 freq; int events = 0; - buf = (char *)get_zeroed_page(GFP_KERNEL); + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; @@ -261,7 +258,7 @@ static ssize_t lbs_threshold_read(uint16_t tlv_type, uint16_t event_mask, kfree(subscribed); out_page: - free_page((unsigned long)buf); + kfree(buf); return ret; } @@ -436,8 +433,7 @@ static ssize_t lbs_rdmac_read(struct file *file, char __user *userbuf, struct lbs_private *priv = file->private_data; ssize_t pos = 0; int ret; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *)addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); u32 val = 0; if (!buf) @@ -450,7 +446,7 @@ static ssize_t lbs_rdmac_read(struct file *file, char __user *userbuf, priv->mac_offset, val); ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos); } - free_page(addr); + kfree(buf); return ret; } @@ -506,8 +502,7 @@ static ssize_t lbs_rdbbp_read(struct file *file, char __user *userbuf, struct lbs_private *priv = file->private_data; ssize_t pos = 0; int ret; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *)addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); u32 val; if (!buf) @@ -520,7 +515,7 @@ static ssize_t lbs_rdbbp_read(struct file *file, char __user *userbuf, priv->bbp_offset, val); ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos); } - free_page(addr); + kfree(buf); return ret; } @@ -578,8 +573,7 @@ static ssize_t lbs_rdrf_read(struct file *file, char __user *userbuf, struct lbs_private *priv = file->private_data; ssize_t pos = 0; int ret; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *)addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); u32 val; if (!buf) @@ -592,7 +586,7 @@ static ssize_t lbs_rdrf_read(struct file *file, char __user *userbuf, priv->rf_offset, val); ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos); } - free_page(addr); + kfree(buf); return ret; } @@ -812,8 +806,7 @@ static ssize_t lbs_debugfs_read(struct file *file, char __user *userbuf, char *p; int i; struct debug_data *d; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *)addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; @@ -836,7 +829,7 @@ static ssize_t lbs_debugfs_read(struct file *file, char __user *userbuf, res = simple_read_from_buffer(userbuf, count, ppos, p, pos); - free_page(addr); + kfree(buf); return res; } From 93e60f96b76fa9108bfc47ee420a6db6f36f19a2 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 1 Jul 2026 16:59:12 +0300 Subject: [PATCH 0182/1433] wifi: mwifiex: debugfs: use kzalloc() to allocate formatting buffers mwifiex debugfs functions allocate buffers for formatting debug output text using get_zeroed_page(). These buffers can be allocated with kmalloc() as there's nothing special about them to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of get_zeroed_page() with kzalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Francesco Dolcini Link: https://patch.msgid.link/20260701-b4-drivers-wireless-v1-3-60264cdf2efe@kernel.org Signed-off-by: Johannes Berg --- .../net/wireless/marvell/mwifiex/debugfs.c | 62 ++++++++----------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/drivers/net/wireless/marvell/mwifiex/debugfs.c b/drivers/net/wireless/marvell/mwifiex/debugfs.c index 9deaf59dcb62..573768b6ad91 100644 --- a/drivers/net/wireless/marvell/mwifiex/debugfs.c +++ b/drivers/net/wireless/marvell/mwifiex/debugfs.c @@ -6,6 +6,7 @@ */ #include +#include #include "main.h" #include "11n.h" @@ -67,8 +68,8 @@ mwifiex_info_read(struct file *file, char __user *ubuf, struct net_device *netdev = priv->netdev; struct netdev_hw_addr *ha; struct netdev_queue *txq; - unsigned long page = get_zeroed_page(GFP_KERNEL); - char *p = (char *) page, fmt[64]; + char *page = kzalloc(PAGE_SIZE, GFP_KERNEL); + char *p = page, fmt[64]; struct mwifiex_bss_info info; ssize_t ret; int i = 0; @@ -133,11 +134,10 @@ mwifiex_info_read(struct file *file, char __user *ubuf, } p += sprintf(p, "\n"); - ret = simple_read_from_buffer(ubuf, count, ppos, (char *) page, - (unsigned long) p - page); + ret = simple_read_from_buffer(ubuf, count, ppos, page, p - page); free_and_exit: - free_page(page); + kfree(page); return ret; } @@ -168,8 +168,8 @@ mwifiex_getlog_read(struct file *file, char __user *ubuf, { struct mwifiex_private *priv = (struct mwifiex_private *) file->private_data; - unsigned long page = get_zeroed_page(GFP_KERNEL); - char *p = (char *) page; + char *page = kzalloc(PAGE_SIZE, GFP_KERNEL); + char *p = page; ssize_t ret; struct mwifiex_ds_get_stats stats; @@ -220,11 +220,10 @@ mwifiex_getlog_read(struct file *file, char __user *ubuf, stats.bcn_miss_cnt); - ret = simple_read_from_buffer(ubuf, count, ppos, (char *) page, - (unsigned long) p - page); + ret = simple_read_from_buffer(ubuf, count, ppos, page, p - page); free_and_exit: - free_page(page); + kfree(page); return ret; } @@ -247,8 +246,8 @@ mwifiex_histogram_read(struct file *file, char __user *ubuf, ssize_t ret; struct mwifiex_histogram_data *phist_data; int i, value; - unsigned long page = get_zeroed_page(GFP_KERNEL); - char *p = (char *)page; + char *page = kzalloc(PAGE_SIZE, GFP_KERNEL); + char *p = page; if (!p) return -ENOMEM; @@ -309,11 +308,10 @@ mwifiex_histogram_read(struct file *file, char __user *ubuf, i, value); } - ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page, - (unsigned long)p - page); + ret = simple_read_from_buffer(ubuf, count, ppos, page, p - page); free_and_exit: - free_page(page); + kfree(page); return ret; } @@ -383,8 +381,8 @@ mwifiex_debug_read(struct file *file, char __user *ubuf, { struct mwifiex_private *priv = (struct mwifiex_private *) file->private_data; - unsigned long page = get_zeroed_page(GFP_KERNEL); - char *p = (char *) page; + char *page = kzalloc(PAGE_SIZE, GFP_KERNEL); + char *p = page; ssize_t ret; if (!p) @@ -396,11 +394,10 @@ mwifiex_debug_read(struct file *file, char __user *ubuf, p += mwifiex_debug_info_to_buffer(priv, p, &info); - ret = simple_read_from_buffer(ubuf, count, ppos, (char *) page, - (unsigned long) p - page); + ret = simple_read_from_buffer(ubuf, count, ppos, page, p - page); free_and_exit: - free_page(page); + kfree(page); return ret; } @@ -457,8 +454,7 @@ mwifiex_regrdwr_read(struct file *file, char __user *ubuf, { struct mwifiex_private *priv = (struct mwifiex_private *) file->private_data; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *) addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); int pos = 0, ret = 0; u32 reg_value; @@ -497,7 +493,7 @@ mwifiex_regrdwr_read(struct file *file, char __user *ubuf, ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); done: - free_page(addr); + kfree(buf); return ret; } @@ -511,8 +507,7 @@ mwifiex_debug_mask_read(struct file *file, char __user *ubuf, { struct mwifiex_private *priv = (struct mwifiex_private *)file->private_data; - unsigned long page = get_zeroed_page(GFP_KERNEL); - char *buf = (char *)page; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); size_t ret = 0; int pos = 0; @@ -523,7 +518,7 @@ mwifiex_debug_mask_read(struct file *file, char __user *ubuf, priv->adapter->debug_mask); ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); - free_page(page); + kfree(buf); return ret; } @@ -652,8 +647,7 @@ mwifiex_memrw_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) { struct mwifiex_private *priv = (void *)file->private_data; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *)addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); int ret, pos = 0; if (!buf) @@ -663,7 +657,7 @@ mwifiex_memrw_read(struct file *file, char __user *ubuf, priv->mem_rw.value); ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); - free_page(addr); + kfree(buf); return ret; } @@ -719,8 +713,7 @@ mwifiex_rdeeprom_read(struct file *file, char __user *ubuf, { struct mwifiex_private *priv = (struct mwifiex_private *) file->private_data; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *) addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); int pos, ret, i; u8 value[MAX_EEPROM_DATA]; @@ -749,7 +742,7 @@ mwifiex_rdeeprom_read(struct file *file, char __user *ubuf, done: ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); out_free: - free_page(addr); + kfree(buf); return ret; } @@ -820,8 +813,7 @@ mwifiex_hscfg_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) { struct mwifiex_private *priv = (void *)file->private_data; - unsigned long addr = get_zeroed_page(GFP_KERNEL); - char *buf = (char *)addr; + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); int pos, ret; struct mwifiex_ds_hs_cfg hscfg; @@ -836,7 +828,7 @@ mwifiex_hscfg_read(struct file *file, char __user *ubuf, ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); - free_page(addr); + kfree(buf); return ret; } From 72cdb89a4349daf94b1360abff1b27dc42e33380 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 1 Jul 2026 16:59:13 +0300 Subject: [PATCH 0183/1433] wifi: wlcore: allocate aggregation and firmware log buffers with kzalloc() wlcore_alloc_hw() uses __get_free_pages() to allocate TX aggregation and firmware log buffers used for software data staging. These buffer can be allocated with kmalloc() as there's nothing special about them to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of __get_free_pages() with kzalloc() and free_pages() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260701-b4-drivers-wireless-v1-4-60264cdf2efe@kernel.org Signed-off-by: Johannes Berg --- drivers/net/wireless/ti/wlcore/main.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/ti/wlcore/main.c b/drivers/net/wireless/ti/wlcore/main.c index be583ae331c0..5595f7a1fc0c 100644 --- a/drivers/net/wireless/ti/wlcore/main.c +++ b/drivers/net/wireless/ti/wlcore/main.c @@ -6354,7 +6354,6 @@ struct ieee80211_hw *wlcore_alloc_hw(size_t priv_size, u32 aggr_buf_size, struct ieee80211_hw *hw; struct wl1271 *wl; int i, j, ret; - unsigned int order; hw = ieee80211_alloc_hw(sizeof(*wl), &wl1271_ops); if (!hw) { @@ -6434,8 +6433,7 @@ struct ieee80211_hw *wlcore_alloc_hw(size_t priv_size, u32 aggr_buf_size, mutex_init(&wl->flush_mutex); init_completion(&wl->nvs_loading_complete); - order = get_order(aggr_buf_size); - wl->aggr_buf = (u8 *)__get_free_pages(GFP_KERNEL, order); + wl->aggr_buf = kmalloc(round_up(aggr_buf_size, PAGE_SIZE), GFP_KERNEL); if (!wl->aggr_buf) { ret = -ENOMEM; goto err_wq; @@ -6449,7 +6447,7 @@ struct ieee80211_hw *wlcore_alloc_hw(size_t priv_size, u32 aggr_buf_size, } /* Allocate one page for the FW log */ - wl->fwlog = (u8 *)get_zeroed_page(GFP_KERNEL); + wl->fwlog = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!wl->fwlog) { ret = -ENOMEM; goto err_dummy_packet; @@ -6474,13 +6472,13 @@ struct ieee80211_hw *wlcore_alloc_hw(size_t priv_size, u32 aggr_buf_size, kfree(wl->mbox); err_fwlog: - free_page((unsigned long)wl->fwlog); + kfree(wl->fwlog); err_dummy_packet: dev_kfree_skb(wl->dummy_packet); err_aggr: - free_pages((unsigned long)wl->aggr_buf, order); + kfree(wl->aggr_buf); err_wq: destroy_workqueue(wl->freezable_wq); @@ -6509,9 +6507,9 @@ int wlcore_free_hw(struct wl1271 *wl) kfree(wl->buffer_32); kfree(wl->mbox); - free_page((unsigned long)wl->fwlog); + kfree(wl->fwlog); dev_kfree_skb(wl->dummy_packet); - free_pages((unsigned long)wl->aggr_buf, get_order(wl->aggr_buf_size)); + kfree(wl->aggr_buf); wl1271_debugfs_exit(wl); From ddd23927462f34d4a32e4fab086637d400e2b777 Mon Sep 17 00:00:00 2001 From: Anas Khan Date: Thu, 2 Jul 2026 15:53:25 +0530 Subject: [PATCH 0184/1433] wifi: p54: update stale wireless wiki URLs The p54 wireless wiki links (wireless.wiki.kernel.org) return 404; the content moved to the Sphinx documentation site. Point them at the current wireless.docs.kernel.org pages. Signed-off-by: Anas Khan Acked-by: Christian Lamparter Link: https://patch.msgid.link/20260702102325.63955-1-anxkhn28@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/intersil/p54/Kconfig | 6 +++--- drivers/net/wireless/intersil/p54/fwio.c | 4 +--- drivers/net/wireless/intersil/p54/p54usb.c | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/intersil/p54/Kconfig b/drivers/net/wireless/intersil/p54/Kconfig index 003c378ed131..44b0f1a724aa 100644 --- a/drivers/net/wireless/intersil/p54/Kconfig +++ b/drivers/net/wireless/intersil/p54/Kconfig @@ -10,7 +10,7 @@ config P54_COMMON also need to be enabled in order to support any devices. These devices require softmac firmware which can be found at - + If you choose to build a module, it'll be called p54common. @@ -22,7 +22,7 @@ config P54_USB This driver is for USB isl38xx based wireless cards. These devices require softmac firmware which can be found at - + If you choose to build a module, it'll be called p54usb. @@ -36,7 +36,7 @@ config P54_PCI supported by the fullmac driver/firmware. This driver requires softmac firmware which can be found at - + If you choose to build a module, it'll be called p54pci. diff --git a/drivers/net/wireless/intersil/p54/fwio.c b/drivers/net/wireless/intersil/p54/fwio.c index 3baf8ab01e22..a3d9053f043c 100644 --- a/drivers/net/wireless/intersil/p54/fwio.c +++ b/drivers/net/wireless/intersil/p54/fwio.c @@ -131,9 +131,7 @@ int p54_parse_firmware(struct ieee80211_hw *dev, const struct firmware *fw) if (priv->fw_var < 0x500) wiphy_info(priv->hw->wiphy, - "you are using an obsolete firmware. " - "visit http://wireless.wiki.kernel.org/en/users/Drivers/p54 " - "and grab one for \"kernel >= 2.6.28\"!\n"); + "you are using an obsolete firmware. visit https://wireless.docs.kernel.org/en/latest/en/users/drivers/p54.html and grab one for \"kernel >= 2.6.28\"!\n"); if (priv->fw_var >= 0x300) { /* Firmware supports QoS, use it! */ diff --git a/drivers/net/wireless/intersil/p54/p54usb.c b/drivers/net/wireless/intersil/p54/p54usb.c index c0d3b5329f4e..b88a3dadddc0 100644 --- a/drivers/net/wireless/intersil/p54/p54usb.c +++ b/drivers/net/wireless/intersil/p54/p54usb.c @@ -36,7 +36,7 @@ static struct usb_driver p54u_driver; * Note: * * Always update our wiki's device list (located at: - * http://wireless.wiki.kernel.org/en/users/Drivers/p54/devices ), + * https://wireless.docs.kernel.org/en/latest/en/users/drivers/p54/devices.html ), * whenever you add a new device. */ From b1d0c412088e3908821ef2ec52e2c0e5e7f5a535 Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Tue, 30 Jun 2026 14:55:32 +0200 Subject: [PATCH 0185/1433] dpll: add STATE_CONNECTED_OVERRIDE pin capability Add DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE capability flag that indicates a pin can be set to connected regardless of the current DPLL device mode, overriding the active input selection. This is useful for automatic-only DPLL devices where mode cannot be switched to manual, allowing userspace to directly connect such pin from automatic mode. The capability requires STATE_CAN_CHANGE to be set as well; dpll_pin_register() warns if a driver violates this. Document the new capability in the Pin selection section of Documentation/driver-api/dpll.rst. Signed-off-by: Ivan Vecera Link: https://patch.msgid.link/20260630125536.720717-2-ivecera@redhat.com Signed-off-by: Paolo Abeni --- Documentation/driver-api/dpll.rst | 7 +++++++ Documentation/netlink/specs/dpll.yaml | 6 ++++++ drivers/dpll/dpll_core.c | 6 +++++- include/uapi/linux/dpll.h | 4 ++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst index bae14766d4f7..f83150917814 100644 --- a/Documentation/driver-api/dpll.rst +++ b/Documentation/driver-api/dpll.rst @@ -91,6 +91,13 @@ following pin states: - ``DPLL_PIN_STATE_DISCONNECTED`` - the pin shall be not considered as a valid input for automatic selection algorithm +Pins that have the ``DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE`` +capability can additionally be set to ``DPLL_PIN_STATE_CONNECTED`` in +automatic mode, overriding the active input selection. This is useful +for automatic-only DPLL devices where mode cannot be switched to manual. +When such a pin is disconnected, the device returns to automatic input +selection. + The actual hardware status of a pin is reported via the operational state (``DPLL_A_PIN_OPERSTATE``) attribute nested under the parent device: diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml index 2bf83f6732ab..526a5b2df2bd 100644 --- a/Documentation/netlink/specs/dpll.yaml +++ b/Documentation/netlink/specs/dpll.yaml @@ -252,6 +252,12 @@ definitions: - name: state-can-change doc: pin state can be changed + - + name: state-connected-override + doc: | + pin state can be set to connected regardless of current + DPLL device mode, overriding the active input selection. + Requires state-can-change to be set as well. - type: const name: phase-offset-divider diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c index 2e8690cb3c16..bb1e8650c9d5 100644 --- a/drivers/dpll/dpll_core.c +++ b/drivers/dpll/dpll_core.c @@ -884,7 +884,11 @@ dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin, WARN_ON(ops->measured_freq_get && (!dpll_device_ops(dpll)->freq_monitor_get || !dpll_device_ops(dpll)->freq_monitor_set)) || - WARN_ON(ops->supported_ffo && !ops->ffo_get)) + WARN_ON(ops->supported_ffo && !ops->ffo_get) || + WARN_ON((pin->prop.capabilities & + DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE) && + !(pin->prop.capabilities & + DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE))) return -EINVAL; mutex_lock(&dpll_lock); diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h index 55eaa82f5f98..5d7ca6a413cd 100644 --- a/include/uapi/linux/dpll.h +++ b/include/uapi/linux/dpll.h @@ -208,11 +208,15 @@ enum dpll_pin_operstate { * @DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE: pin direction can be changed * @DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE: pin priority can be changed * @DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE: pin state can be changed + * @DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE: pin state can be set to + * connected regardless of current DPLL device mode, overriding the active + * input selection. Requires state-can-change to be set as well. */ enum dpll_pin_capabilities { DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE = 1, DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE = 2, DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE = 4, + DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE = 8, }; #define DPLL_PHASE_OFFSET_DIVIDER 1000 From 0cc8348a9786727e3622f833f442e8b45e2d363b Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Tue, 30 Jun 2026 14:55:33 +0200 Subject: [PATCH 0186/1433] dpll: add DPLL_PIN_TYPE_INT_NCO pin type Add DPLL_PIN_TYPE_INT_NCO pin type for virtual pins representing the NCO mode of a DPLL. When connected as a DPLL input, the DPLL enters NCO mode where the output frequency is adjusted by the host via the PTP clock interface. Update the fractional-frequency-offset and fractional-frequency- offset-ppt attribute documentation to note that for INT_NCO pins these attributes represent the DPLL's current output frequency offset from its nominal frequency. Reviewed-by: Jiri Pirko Signed-off-by: Ivan Vecera Link: https://patch.msgid.link/20260630125536.720717-3-ivecera@redhat.com Signed-off-by: Paolo Abeni --- Documentation/netlink/specs/dpll.yaml | 13 +++++++++++++ drivers/dpll/dpll_nl.c | 2 +- include/uapi/linux/dpll.h | 4 ++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml index 526a5b2df2bd..cdc8c7b456df 100644 --- a/Documentation/netlink/specs/dpll.yaml +++ b/Documentation/netlink/specs/dpll.yaml @@ -165,6 +165,13 @@ definitions: - name: gnss doc: GNSS recovered clock + - + name: int-nco + doc: | + Device internal numerically controlled oscillator. + When connected as a DPLL input, the DPLL enters NCO mode + where the output frequency is adjusted by the host via + the PTP clock interface. render-max: true - type: enum @@ -462,6 +469,9 @@ attribute-sets: offset on the media associated with the pin. Inside the pin-parent-device nest it represents the frequency offset between the pin and its parent DPLL device. + For pins of type PIN_TYPE_INT_NCO this represents + the DPLL's current output frequency offset from its + nominal frequency. Value is in PPM (parts per million). This is a lower-precision version of fractional-frequency-offset-ppt. @@ -508,6 +518,9 @@ attribute-sets: offset on the media associated with the pin. Inside the pin-parent-device nest it represents the frequency offset between the pin and its parent DPLL device. + For pins of type PIN_TYPE_INT_NCO this represents + the DPLL's current output frequency offset from its + nominal frequency. Value is in PPT (parts per trillion, 10^-12). This is a higher-precision version of fractional-frequency-offset. diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c index ed3bbe9841ea..b1ba490e72b0 100644 --- a/drivers/dpll/dpll_nl.c +++ b/drivers/dpll/dpll_nl.c @@ -61,7 +61,7 @@ static const struct nla_policy dpll_pin_id_get_nl_policy[DPLL_A_PIN_TYPE + 1] = [DPLL_A_PIN_BOARD_LABEL] = { .type = NLA_NUL_STRING, }, [DPLL_A_PIN_PANEL_LABEL] = { .type = NLA_NUL_STRING, }, [DPLL_A_PIN_PACKAGE_LABEL] = { .type = NLA_NUL_STRING, }, - [DPLL_A_PIN_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 5), + [DPLL_A_PIN_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 6), }; /* DPLL_CMD_PIN_GET - do */ diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h index 5d7ca6a413cd..85b898b1db5e 100644 --- a/include/uapi/linux/dpll.h +++ b/include/uapi/linux/dpll.h @@ -129,6 +129,9 @@ enum dpll_type { * @DPLL_PIN_TYPE_SYNCE_ETH_PORT: ethernet port PHY's recovered clock * @DPLL_PIN_TYPE_INT_OSCILLATOR: device internal oscillator * @DPLL_PIN_TYPE_GNSS: GNSS recovered clock + * @DPLL_PIN_TYPE_INT_NCO: Device internal numerically controlled oscillator. + * When connected as a DPLL input, the DPLL enters NCO mode where the output + * frequency is adjusted by the host via the PTP clock interface. */ enum dpll_pin_type { DPLL_PIN_TYPE_MUX = 1, @@ -136,6 +139,7 @@ enum dpll_pin_type { DPLL_PIN_TYPE_SYNCE_ETH_PORT, DPLL_PIN_TYPE_INT_OSCILLATOR, DPLL_PIN_TYPE_GNSS, + DPLL_PIN_TYPE_INT_NCO, /* private: */ __DPLL_PIN_TYPE_MAX, From 2b11bde391c4a58cd006d98c5deb2538904709e2 Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Tue, 30 Jun 2026 14:55:34 +0200 Subject: [PATCH 0187/1433] dpll: zl3073x: use per-operation poll timeouts Replace the single 2s timeout in zl3073x_poll_zero_u8() with a per-caller timeout parameter. Different HW operations have different expected completion times so using per-operation timeouts improves error detection. The timeout values are based on proprietary source code provided by Microchip and own measurement. Signed-off-by: Ivan Vecera Reviewed-by: Petr Oros Link: https://patch.msgid.link/20260630125536.720717-4-ivecera@redhat.com Signed-off-by: Paolo Abeni --- drivers/dpll/zl3073x/chan.c | 6 ++++-- drivers/dpll/zl3073x/core.c | 29 +++++++++++++++++------------ drivers/dpll/zl3073x/core.h | 10 +++++++++- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c index 2fe3c3da84bb..677a920c1625 100644 --- a/drivers/dpll/zl3073x/chan.c +++ b/drivers/dpll/zl3073x/chan.c @@ -33,7 +33,8 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index) /* Read df_offset vs tracked reference */ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index), - ZL_DPLL_DF_READ_SEM); + ZL_DPLL_DF_READ_SEM, + ZL_POLL_DF_READ_TIMEOUT_US); if (rc) return rc; @@ -43,7 +44,8 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index) return rc; rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index), - ZL_DPLL_DF_READ_SEM); + ZL_DPLL_DF_READ_SEM, + ZL_POLL_DF_READ_TIMEOUT_US); if (rc) return rc; diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c index 8e6416a4741d..0b2050aa2ed9 100644 --- a/drivers/dpll/zl3073x/core.c +++ b/drivers/dpll/zl3073x/core.c @@ -311,17 +311,17 @@ int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val) * @zldev: zl3073x device pointer * @reg: register to poll (has to be 8bit register) * @mask: bit mask for polling + * @timeout_us: timeout in microseconds * * Waits for bits specified by @mask in register @reg value to be cleared * by the device. * * Returns: 0 on success, <0 on error */ -int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask) +int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, + u8 mask, unsigned int timeout_us) { - /* Register polling sleep & timeout */ -#define ZL_POLL_SLEEP_US 10 -#define ZL_POLL_TIMEOUT_US 2000000 +#define ZL_POLL_SLEEP_US 10 unsigned int val; /* Check the register is 8bit */ @@ -335,7 +335,7 @@ int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask) reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET; return regmap_read_poll_timeout(zldev->regmap, reg, val, !(val & mask), - ZL_POLL_SLEEP_US, ZL_POLL_TIMEOUT_US); + ZL_POLL_SLEEP_US, timeout_us); } int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val, @@ -354,7 +354,8 @@ int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val, return rc; /* Wait for the operation to actually finish */ - return zl3073x_poll_zero_u8(zldev, op_reg, op_val); + return zl3073x_poll_zero_u8(zldev, op_reg, op_val, + ZL_POLL_MB_TIMEOUT_US); } /** @@ -377,8 +378,8 @@ zl3073x_do_hwreg_op(struct zl3073x_dev *zldev, u8 op) return rc; /* Poll for completion - pending bit cleared */ - return zl3073x_poll_zero_u8(zldev, ZL_REG_HWREG_OP, - ZL_HWREG_OP_PENDING); + return zl3073x_poll_zero_u8(zldev, ZL_REG_HWREG_OP, ZL_HWREG_OP_PENDING, + ZL_POLL_HWREG_TIMEOUT_US); } /** @@ -609,7 +610,8 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel) * to be zero to ensure that the measured data are coherent. */ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST, - ZL_REF_PHASE_ERR_READ_RQST_RD); + ZL_REF_PHASE_ERR_READ_RQST_RD, + ZL_POLL_PHASE_ERR_TIMEOUT_US); if (rc) return rc; @@ -628,7 +630,8 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel) /* Wait for finish */ return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST, - ZL_REF_PHASE_ERR_READ_RQST_RD); + ZL_REF_PHASE_ERR_READ_RQST_RD, + ZL_POLL_PHASE_ERR_TIMEOUT_US); } /** @@ -648,7 +651,8 @@ zl3073x_ref_freq_meas_latch(struct zl3073x_dev *zldev, u8 type) /* Wait for previous measurement to finish */ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL, - ZL_REF_FREQ_MEAS_CTRL); + ZL_REF_FREQ_MEAS_CTRL, + ZL_POLL_FREQ_MEAS_TIMEOUT_US); if (rc) return rc; @@ -669,7 +673,8 @@ zl3073x_ref_freq_meas_latch(struct zl3073x_dev *zldev, u8 type) /* Wait for finish */ return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL, - ZL_REF_FREQ_MEAS_CTRL); + ZL_REF_FREQ_MEAS_CTRL, + ZL_POLL_FREQ_MEAS_TIMEOUT_US); } /** diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h index addba378b0df..6b55a05a222e 100644 --- a/drivers/dpll/zl3073x/core.h +++ b/drivers/dpll/zl3073x/core.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "chan.h" @@ -19,6 +20,12 @@ struct device; struct regmap; struct zl3073x_dpll; +/* Per-operation poll timeouts */ +#define ZL_POLL_DF_READ_TIMEOUT_US (25 * USEC_PER_MSEC) +#define ZL_POLL_FREQ_MEAS_TIMEOUT_US (50 * USEC_PER_MSEC) +#define ZL_POLL_HWREG_TIMEOUT_US (50 * USEC_PER_MSEC) +#define ZL_POLL_MB_TIMEOUT_US (30 * USEC_PER_MSEC) +#define ZL_POLL_PHASE_ERR_TIMEOUT_US (50 * USEC_PER_MSEC) enum zl3073x_flags { ZL3073X_FLAG_REF_PHASE_COMP_32_BIT, @@ -127,7 +134,8 @@ struct zl3073x_hwreg_seq_item { int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val, unsigned int mask_reg, u16 mask_val); -int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask); +int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, + u8 mask, unsigned int timeout_us); int zl3073x_read_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 *val); int zl3073x_read_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 *val); int zl3073x_read_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 *val); From 21460118d71b05bac091d8d5bcdb34439f697167 Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Tue, 30 Jun 2026 14:55:35 +0200 Subject: [PATCH 0188/1433] dpll: zl3073x: add per-DPLL serialization lock Add a per-DPLL mutex that serializes all operations on a given DPLL channel across DPLL netlink callbacks, the periodic kthread worker, and (in subsequent patches) PTP clock callbacks. All DPLL pin and device callbacks that access mutable state take the lock as the first operation. The periodic worker holds it for the entire check cycle of each channel, deferring change notifications until after the lock is released to avoid ABBA deadlock with dpll_lock. This establishes the lock ordering: dpll_lock (subsystem, outer) -> zldpll->lock (driver, inner). Move zl3073x_chan_state_update() from the per-device zl3073x_dev_chan_states_update() loop into the per-DPLL zl3073x_dpll_changes_check() so it runs under zldpll->lock. This serializes df_offset writes with all readers and eliminates the need for separate df_offset synchronization. Change pin->freq_offset from atomic64_t to plain s64 since all readers and writers are now serialized by zldpll->lock, making atomic access unnecessary. Signed-off-by: Ivan Vecera Reviewed-by: Petr Oros Link: https://patch.msgid.link/20260630125536.720717-5-ivecera@redhat.com Signed-off-by: Paolo Abeni --- drivers/dpll/zl3073x/core.c | 19 +--- drivers/dpll/zl3073x/core.h | 2 +- drivers/dpll/zl3073x/dpll.c | 189 +++++++++++++++++++++++++++--------- drivers/dpll/zl3073x/dpll.h | 2 + 4 files changed, 149 insertions(+), 63 deletions(-) diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c index 0b2050aa2ed9..7f5afaaae634 100644 --- a/drivers/dpll/zl3073x/core.c +++ b/drivers/dpll/zl3073x/core.c @@ -567,19 +567,7 @@ zl3073x_dev_ref_states_update(struct zl3073x_dev *zldev) } } -static void -zl3073x_dev_chan_states_update(struct zl3073x_dev *zldev) -{ - int i, rc; - for (i = 0; i < zldev->info->num_channels; i++) { - rc = zl3073x_chan_state_update(zldev, i); - if (rc) - dev_warn(zldev->dev, - "Failed to get DPLL%u state: %pe\n", i, - ERR_PTR(rc)); - } -} /** * zl3073x_ref_phase_offsets_update - update reference phase offsets @@ -720,9 +708,6 @@ zl3073x_dev_periodic_work(struct kthread_work *work) /* Update input references' states */ zl3073x_dev_ref_states_update(zldev); - /* Update DPLL channels' states */ - zl3073x_dev_chan_states_update(zldev); - /* Update DPLL-to-connected-ref phase offsets registers */ rc = zl3073x_ref_phase_offsets_update(zldev, -1); if (rc) @@ -732,7 +717,7 @@ zl3073x_dev_periodic_work(struct kthread_work *work) /* Update measured input reference frequencies if frequency * monitoring is enabled. */ - if (zldev->freq_monitor) { + if (READ_ONCE(zldev->freq_monitor)) { rc = zl3073x_ref_freq_meas_update(zldev); if (rc) dev_warn(zldev->dev, @@ -768,7 +753,7 @@ int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor) return rc; /* Save the new factor */ - zldev->phase_avg_factor = factor; + WRITE_ONCE(zldev->phase_avg_factor, factor); return 0; } diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h index 6b55a05a222e..78dc208f3eea 100644 --- a/drivers/dpll/zl3073x/core.h +++ b/drivers/dpll/zl3073x/core.h @@ -101,7 +101,7 @@ void zl3073x_dev_stop(struct zl3073x_dev *zldev); static inline u8 zl3073x_dev_phase_avg_factor_get(struct zl3073x_dev *zldev) { - return zldev->phase_avg_factor; + return READ_ONCE(zldev->phase_avg_factor); } int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor); diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c index 5e58ded5734d..87e8b7c86548 100644 --- a/drivers/dpll/zl3073x/dpll.c +++ b/drivers/dpll/zl3073x/dpll.c @@ -1,6 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -#include #include #include #include @@ -58,7 +57,7 @@ struct zl3073x_dpll_pin { s32 phase_gran; enum dpll_pin_operstate operstate; s64 phase_offset; - atomic64_t freq_offset; + s64 freq_offset; u32 measured_freq; }; @@ -134,6 +133,8 @@ zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin, const struct zl3073x_ref *ref; u8 ref_id; + guard(mutex)(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); ref = zl3073x_ref_state_get(zldev, ref_id); @@ -170,6 +171,8 @@ zl3073x_dpll_input_pin_esync_set(const struct dpll_pin *dpll_pin, struct zl3073x_ref ref; u8 ref_id, sync_mode; + guard(mutex)(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); ref = *zl3073x_ref_state_get(zldev, ref_id); @@ -205,6 +208,8 @@ zl3073x_dpll_input_pin_ref_sync_get(const struct dpll_pin *dpll_pin, const struct zl3073x_ref *ref; u8 ref_id, mode, pair; + guard(mutex)(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); ref = zl3073x_ref_state_get(zldev, ref_id); mode = zl3073x_ref_sync_mode_get(ref); @@ -236,6 +241,8 @@ zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin, struct zl3073x_ref ref; int rc; + guard(mutex)(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); sync_ref_id = zl3073x_input_pin_ref_get(sync_pin->id); ref = *zl3073x_ref_state_get(zldev, ref_id); @@ -299,12 +306,15 @@ zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv, struct dpll_ffo_param *ffo, struct netlink_ext_ack *extack) { + struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + guard(mutex)(&zldpll->lock); + if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE) return -ENODATA; - ffo->ffo = atomic64_read(&pin->freq_offset); + ffo->ffo = pin->freq_offset; return 0; } @@ -316,8 +326,11 @@ zl3073x_dpll_input_pin_measured_freq_get(const struct dpll_pin *dpll_pin, void *dpll_priv, u64 *measured_freq, struct netlink_ext_ack *extack) { + struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + guard(mutex)(&zldpll->lock); + *measured_freq = pin->measured_freq; *measured_freq *= DPLL_PIN_MEASURED_FREQUENCY_DIVIDER; @@ -335,6 +348,8 @@ zl3073x_dpll_input_pin_frequency_get(const struct dpll_pin *dpll_pin, struct zl3073x_dpll_pin *pin = pin_priv; u8 ref_id; + guard(mutex)(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); *frequency = zl3073x_dev_ref_freq_get(zldpll->dev, ref_id); @@ -354,6 +369,8 @@ zl3073x_dpll_input_pin_frequency_set(const struct dpll_pin *dpll_pin, struct zl3073x_ref ref; u8 ref_id; + guard(mutex)(&zldpll->lock); + /* Get reference state */ ref_id = zl3073x_input_pin_ref_get(pin->id); ref = *zl3073x_ref_state_get(zldev, ref_id); @@ -402,6 +419,8 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin, u8 conn_id, ref_id; s64 ref_phase; + guard(mutex)(&zldpll->lock); + /* Get currently connected reference */ conn_id = zl3073x_dpll_connected_ref_get(zldpll); @@ -459,6 +478,8 @@ zl3073x_dpll_input_pin_phase_adjust_get(const struct dpll_pin *dpll_pin, s64 phase_comp; u8 ref_id; + guard(mutex)(&zldpll->lock); + /* Read reference configuration */ ref_id = zl3073x_input_pin_ref_get(pin->id); ref = zl3073x_ref_state_get(zldev, ref_id); @@ -491,6 +512,8 @@ zl3073x_dpll_input_pin_phase_adjust_set(const struct dpll_pin *dpll_pin, struct zl3073x_ref ref; u8 ref_id; + guard(mutex)(&zldpll->lock); + /* Read reference configuration */ ref_id = zl3073x_input_pin_ref_get(pin->id); ref = *zl3073x_ref_state_get(zldev, ref_id); @@ -524,6 +547,8 @@ zl3073x_dpll_ref_operstate_get(struct zl3073x_dpll_pin *pin, const struct zl3073x_ref *ref; u8 ref_id; + lockdep_assert_held(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); /* Check if this pin is the currently locked reference */ @@ -557,6 +582,8 @@ zl3073x_dpll_input_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin, const struct zl3073x_chan *chan; u8 mode, ref; + guard(mutex)(&zldpll->lock); + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); ref = zl3073x_input_pin_ref_get(pin->id); mode = zl3073x_chan_mode_get(chan); @@ -590,8 +617,11 @@ zl3073x_dpll_input_pin_operstate_on_dpll_get(const struct dpll_pin *dpll_pin, enum dpll_pin_operstate *operstate, struct netlink_ext_ack *extack) { + struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + guard(mutex)(&zldpll->lock); + return zl3073x_dpll_ref_operstate_get(pin, operstate); } @@ -607,7 +637,9 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, struct zl3073x_dpll_pin *pin = pin_priv; struct zl3073x_chan chan; u8 mode, ref; - int rc; + int rc = 0; + + mutex_lock(&zldpll->lock); chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); ref = zl3073x_input_pin_ref_get(pin->id); @@ -649,13 +681,13 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, case ZL_DPLL_MODE_REFSEL_MODE_AUTO: if (state == DPLL_PIN_STATE_SELECTABLE) { if (zl3073x_chan_ref_is_selectable(&chan, ref)) - return 0; /* Pin is already selectable */ + goto unlock; /* Pin is already selectable */ /* Restore pin priority in HW */ zl3073x_chan_ref_prio_set(&chan, ref, pin->prio); } else if (state == DPLL_PIN_STATE_DISCONNECTED) { if (!zl3073x_chan_ref_is_selectable(&chan, ref)) - return 0; /* Pin is already disconnected */ + goto unlock; /* Pin is already disconnected */ /* Set pin priority to none in HW */ zl3073x_chan_ref_prio_set(&chan, ref, @@ -668,18 +700,20 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, /* In other modes we cannot change input reference */ NL_SET_ERR_MSG(extack, "Pin state cannot be changed in current mode"); - return -EOPNOTSUPP; + rc = -EOPNOTSUPP; + goto unlock; } /* Commit DPLL channel changes */ rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); - if (rc) - return rc; + goto unlock; - return 0; invalid_state: NL_SET_ERR_MSG_MOD(extack, "Invalid pin state for this device mode"); - return -EINVAL; + rc = -EINVAL; +unlock: + mutex_unlock(&zldpll->lock); + return rc; } static int @@ -687,8 +721,11 @@ zl3073x_dpll_input_pin_prio_get(const struct dpll_pin *dpll_pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, u32 *prio, struct netlink_ext_ack *extack) { + struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + guard(mutex)(&zldpll->lock); + *prio = pin->prio; return 0; @@ -705,6 +742,8 @@ zl3073x_dpll_input_pin_prio_set(const struct dpll_pin *dpll_pin, void *pin_priv, u8 ref; int rc; + guard(mutex)(&zldpll->lock); + if (prio > ZL_DPLL_REF_PRIO_MAX) return -EINVAL; @@ -740,6 +779,8 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin, u32 synth_freq, out_freq; u8 out_id; + guard(mutex)(&zldpll->lock); + out_id = zl3073x_output_pin_out_get(pin->id); out = zl3073x_out_state_get(zldev, out_id); @@ -797,6 +838,8 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, u32 synth_freq; u8 out_id; + guard(mutex)(&zldpll->lock); + out_id = zl3073x_output_pin_out_get(pin->id); out = *zl3073x_out_state_get(zldev, out_id); @@ -817,7 +860,7 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, /* If esync is being disabled just write mailbox and finish */ if (!freq) - goto write_mailbox; + return zl3073x_out_state_set(zldev, out_id, &out); /* Get attached synth frequency */ synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(&out)); @@ -834,7 +877,6 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, */ out.esync_n_width = out.div / 2; -write_mailbox: /* Commit output configuration */ return zl3073x_out_state_set(zldev, out_id, &out); } @@ -849,6 +891,8 @@ zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin, struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + guard(mutex)(&zldpll->lock); + *frequency = zl3073x_dev_output_pin_freq_get(zldpll->dev, pin->id); return 0; @@ -869,6 +913,8 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin, struct zl3073x_out out; u8 out_id; + guard(mutex)(&zldpll->lock); + out_id = zl3073x_output_pin_out_get(pin->id); out = *zl3073x_out_state_get(zldev, out_id); @@ -942,6 +988,8 @@ zl3073x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *dpll_pin, const struct zl3073x_out *out; u8 out_id; + guard(mutex)(&zldpll->lock); + out_id = zl3073x_output_pin_out_get(pin->id); out = zl3073x_out_state_get(zldev, out_id); @@ -965,6 +1013,8 @@ zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin, struct zl3073x_out out; u8 out_id; + guard(mutex)(&zldpll->lock); + out_id = zl3073x_output_pin_out_get(pin->id); out = *zl3073x_out_state_get(zldev, out_id); @@ -998,6 +1048,8 @@ zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv, u16 val; int rc; + guard(mutex)(&zldpll->lock); + rc = zl3073x_read_u16(zldev, ZL_REG_DIE_TEMP_STATUS, &val); if (rc) return rc; @@ -1009,14 +1061,13 @@ zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv, } static int -zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv, - enum dpll_lock_status *status, - enum dpll_lock_status_error *status_error, - struct netlink_ext_ack *extack) +__zl3073x_dpll_lock_status_get(struct zl3073x_dpll *zldpll, + enum dpll_lock_status *status) { - struct zl3073x_dpll *zldpll = dpll_priv; const struct zl3073x_chan *chan; + lockdep_assert_held(&zldpll->lock); + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); switch (zl3073x_chan_mode_get(chan)) { @@ -1052,6 +1103,19 @@ zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv, return 0; } +static int +zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv, + enum dpll_lock_status *status, + enum dpll_lock_status_error *status_error, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + + guard(mutex)(&zldpll->lock); + + return __zl3073x_dpll_lock_status_get(zldpll, status); +} + static int zl3073x_dpll_supported_modes_get(const struct dpll_device *dpll, void *dpll_priv, unsigned long *modes, @@ -1060,6 +1124,8 @@ zl3073x_dpll_supported_modes_get(const struct dpll_device *dpll, struct zl3073x_dpll *zldpll = dpll_priv; const struct zl3073x_chan *chan; + guard(mutex)(&zldpll->lock); + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); /* We support switching between automatic and manual mode, except in @@ -1082,6 +1148,8 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv, struct zl3073x_dpll *zldpll = dpll_priv; const struct zl3073x_chan *chan; + guard(mutex)(&zldpll->lock); + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); switch (zl3073x_chan_mode_get(chan)) { @@ -1138,8 +1206,8 @@ zl3073x_dpll_phase_offset_avg_factor_set(const struct dpll_device *dpll, return rc; } - /* The averaging factor is common for all DPLL channels so after change - * we have to send a notification for other DPLL devices. + /* The averaging factor is common for all DPLL channels so after + * change we have to send a notification for other DPLL devices. */ list_for_each_entry(item, &zldpll->dev->dplls, list) { struct dpll_device *dpll_dev = READ_ONCE(item->dpll_dev); @@ -1160,6 +1228,8 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv, u8 hw_mode, ref; int rc; + guard(mutex)(&zldpll->lock); + chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); ref = zl3073x_chan_refsel_ref_get(&chan); @@ -1221,6 +1291,8 @@ zl3073x_dpll_phase_offset_monitor_get(const struct dpll_device *dpll, { struct zl3073x_dpll *zldpll = dpll_priv; + guard(mutex)(&zldpll->lock); + if (zldpll->phase_monitor) *state = DPLL_FEATURE_STATE_ENABLE; else @@ -1237,6 +1309,8 @@ zl3073x_dpll_phase_offset_monitor_set(const struct dpll_device *dpll, { struct zl3073x_dpll *zldpll = dpll_priv; + guard(mutex)(&zldpll->lock); + zldpll->phase_monitor = (state == DPLL_FEATURE_STATE_ENABLE); return 0; @@ -1250,7 +1324,7 @@ zl3073x_dpll_freq_monitor_get(const struct dpll_device *dpll, { struct zl3073x_dpll *zldpll = dpll_priv; - if (zldpll->dev->freq_monitor) + if (READ_ONCE(zldpll->dev->freq_monitor)) *state = DPLL_FEATURE_STATE_ENABLE; else *state = DPLL_FEATURE_STATE_DISABLE; @@ -1265,13 +1339,14 @@ zl3073x_dpll_freq_monitor_set(const struct dpll_device *dpll, struct netlink_ext_ack *extack) { struct zl3073x_dpll *item, *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; - zldpll->dev->freq_monitor = (state == DPLL_FEATURE_STATE_ENABLE); + WRITE_ONCE(zldev->freq_monitor, state == DPLL_FEATURE_STATE_ENABLE); /* The frequency monitoring is common for all DPLL channels so after * change we have to send a notification for other DPLL devices. */ - list_for_each_entry(item, &zldpll->dev->dplls, list) { + list_for_each_entry(item, &zldev->dplls, list) { struct dpll_device *dpll_dev = READ_ONCE(item->dpll_dev); if (item != zldpll && dpll_dev) @@ -1697,6 +1772,8 @@ zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin) u8 ref_id; int rc; + lockdep_assert_held(&zldpll->lock); + /* No phase offset if the ref monitor reports signal errors */ ref_id = zl3073x_input_pin_ref_get(pin->id); if (!zl3073x_dev_ref_is_status_ok(zldev, ref_id)) @@ -1753,6 +1830,8 @@ zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin) const struct zl3073x_chan *chan; s64 ffo; + lockdep_assert_held(&zldpll->lock); + if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE) return false; @@ -1760,9 +1839,10 @@ zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin) ffo = mul_s64_u64_shr(zl3073x_chan_df_offset_get(chan), 244140625, 36); - if (atomic64_xchg(&pin->freq_offset, ffo) != ffo) { + if (pin->freq_offset != ffo) { dev_dbg(zldev->dev, "%s freq offset changed to: %lld\n", pin->label, ffo); + pin->freq_offset = ffo; return true; } @@ -1787,7 +1867,9 @@ zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin) u8 ref_id; u32 freq; - if (!zldpll->dev->freq_monitor) + lockdep_assert_held(&zldpll->lock); + + if (!READ_ONCE(zldpll->dev->freq_monitor)) return false; ref_id = zl3073x_input_pin_ref_get(pin->id); @@ -1817,27 +1899,37 @@ zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin) void zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll) { + DECLARE_BITMAP(changed_pins, ZL3073X_NUM_INPUT_PINS); struct zl3073x_dev *zldev = zldpll->dev; enum dpll_lock_status lock_status; struct device *dev = zldev->dev; struct zl3073x_dpll_pin *pin; + bool dev_changed = false; int rc; + bitmap_zero(changed_pins, ZL3073X_NUM_INPUT_PINS); + + mutex_lock(&zldpll->lock); + zldpll->check_count++; - /* Get current lock status for the DPLL */ - rc = zl3073x_dpll_lock_status_get(zldpll->dpll_dev, zldpll, - &lock_status, NULL, NULL); + rc = zl3073x_chan_state_update(zldev, zldpll->id); + if (rc) { + dev_err(dev, "Failed to get DPLL%u state: %pe\n", + zldpll->id, ERR_PTR(rc)); + goto unlock; + } + + rc = __zl3073x_dpll_lock_status_get(zldpll, &lock_status); if (rc) { dev_err(dev, "Failed to get DPLL%u lock status: %pe\n", zldpll->id, ERR_PTR(rc)); - return; + goto unlock; } - /* If lock status was changed then notify DPLL core */ if (zldpll->lock_status != lock_status) { zldpll->lock_status = lock_status; - dpll_device_change_ntf(zldpll->dpll_dev); + dev_changed = true; } /* Update phase offset latch registers for this DPLL if the phase @@ -1849,17 +1941,13 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll) dev_err(zldev->dev, "Failed to update phase offsets: %pe\n", ERR_PTR(rc)); - return; + goto unlock; } } list_for_each_entry(pin, &zldpll->pins, list) { enum dpll_pin_operstate operstate; - bool pin_changed = false; - /* Output pins change checks are not necessary because output - * states are constant. - */ if (!zl3073x_dpll_is_input_pin(pin)) continue; @@ -1868,31 +1956,40 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll) dev_err(dev, "Failed to get %s on DPLL%u oper state: %pe\n", pin->label, zldpll->id, ERR_PTR(rc)); - return; + goto unlock; } if (operstate != pin->operstate) { dev_dbg(dev, "%s oper state changed: %u->%u\n", pin->label, pin->operstate, operstate); pin->operstate = operstate; - pin_changed = true; + set_bit(pin->id, changed_pins); } - /* Check for phase offset, ffo, and measured freq change - * once per second. - */ if (zldpll->check_count % 2 == 0) { if (zl3073x_dpll_pin_phase_offset_check(pin)) - pin_changed = true; + set_bit(pin->id, changed_pins); if (zl3073x_dpll_pin_ffo_check(pin)) - pin_changed = true; + set_bit(pin->id, changed_pins); if (zl3073x_dpll_pin_measured_freq_check(pin)) - pin_changed = true; + set_bit(pin->id, changed_pins); } + } - if (pin_changed) +unlock: + mutex_unlock(&zldpll->lock); + + /* Send notifications outside the lock to avoid ABBA deadlock + * with dpll_lock taken by notification functions. + */ + if (dev_changed) + dpll_device_change_ntf(zldpll->dpll_dev); + + list_for_each_entry(pin, &zldpll->pins, list) { + if (zl3073x_dpll_is_input_pin(pin) && + test_bit(pin->id, changed_pins)) dpll_pin_change_ntf(pin->dpll_pin); } } @@ -1949,6 +2046,7 @@ zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch) zldpll->dev = zldev; zldpll->id = ch; + mutex_init(&zldpll->lock); INIT_LIST_HEAD(&zldpll->pins); return zldpll; @@ -1965,6 +2063,7 @@ zl3073x_dpll_free(struct zl3073x_dpll *zldpll) { WARN(zldpll->dpll_dev, "DPLL device is still registered\n"); + mutex_destroy(&zldpll->lock); kfree(zldpll); } diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h index 21adcc18e45e..9f57c944a007 100644 --- a/drivers/dpll/zl3073x/dpll.h +++ b/drivers/dpll/zl3073x/dpll.h @@ -18,6 +18,7 @@ * @ops: DPLL device operations for this instance * @dpll_dev: pointer to registered DPLL device * @tracker: tracking object for the acquired reference + * @lock: per-DPLL mutex serializing all operations * @lock_status: last saved DPLL lock status * @pins: list of pins */ @@ -30,6 +31,7 @@ struct zl3073x_dpll { struct dpll_device_ops ops; struct dpll_device *dpll_dev; dpll_tracker tracker; + struct mutex lock; enum dpll_lock_status lock_status; struct list_head pins; }; From 3553976ffe2f0ccb7f667725816e41c44f0e4729 Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Tue, 30 Jun 2026 14:55:36 +0200 Subject: [PATCH 0189/1433] dpll: zl3073x: add NCO virtual input pin Add a virtual NCO (Numerically Controlled Oscillator) input pin that lets userspace switch a DPLL channel into NCO mode. A single NCO pin is shared across all DPLL channels - each channel has its own independent connection state. The NCO pin is registered with the new DPLL_PIN_TYPE_INT_NCO type and reports DPLL_PIN_STATE_CONNECTED / DPLL_PIN_OPERSTATE_ACTIVE when the channel is in NCO mode. At NCO pin registration the following bits are configured in dpll_ctrl_x: - nco_auto_read: auto-capture tracking offset on NCO entry - tod_step_reset: apply negated ToD step accumulator on NCO exit - tie_clear: PPS DPLLs set 1 to re-align outputs on NCO exit, EEC DPLLs keep 0 to prevent an unwanted TIE write Before switching to NCO mode, dpll_df_read_x is configured with ref_ofst=0 and cmd=ACC_I so that nco_auto_read captures the accumulated I-part offset relative to the master clock. Without this, the captured df_offset would be near zero (offset relative to the input reference after lock). On NCO entry the df_offset captured by nco_auto_read is read from the register. Per the datasheet, nco_auto_read only captures a valid offset when entering NCO from reflock, auto or holdover mode; from freerun the captured value is not meaningful and df_offset is marked as ZL_DPLL_DF_OFFSET_UNKNOWN. The same sentinel is set in chan_state_update() when the channel is not locked, and both FFO consumers (NCO pin and input pin) guard against it. Disconnecting the NCO pin switches to freerun rather than holdover because holdover averaging is not updated during NCO mode. When connecting the NCO pin displaces a previously connected input pin (reflock mode), a change notification is sent for that input pin. Input reference pins are now always registered regardless of the initial DPLL mode. Previously they were skipped when the DPLL was in NCO mode, but the NCO pin provides the proper mechanism for mode transitions. Reviewed-by: Petr Oros Tested-by: Chris du Quesnay Signed-off-by: Ivan Vecera Link: https://patch.msgid.link/20260630125536.720717-6-ivecera@redhat.com Signed-off-by: Paolo Abeni --- drivers/dpll/zl3073x/chan.c | 122 +++++++++++++- drivers/dpll/zl3073x/chan.h | 48 ++++++ drivers/dpll/zl3073x/dpll.c | 308 ++++++++++++++++++++++++++++++++---- drivers/dpll/zl3073x/dpll.h | 2 + drivers/dpll/zl3073x/regs.h | 11 ++ 5 files changed, 462 insertions(+), 29 deletions(-) diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c index 677a920c1625..4ec2cf53dad4 100644 --- a/drivers/dpll/zl3073x/chan.c +++ b/drivers/dpll/zl3073x/chan.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only #include +#include #include #include #include @@ -31,7 +32,15 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index) if (rc) return rc; - /* Read df_offset vs tracked reference */ + /* Read df_offset only when locked to a reference. In NCO mode + * df_offset was captured at entry by nco_mode_set() - preserve it. + */ + if (!zl3073x_chan_is_locked(chan)) { + if (!zl3073x_chan_mode_is_nco(chan)) + chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN; + return 0; + } + rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index), ZL_DPLL_DF_READ_SEM, ZL_POLL_DF_READ_TIMEOUT_US); @@ -58,6 +67,96 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index) return 0; } +/** + * zl3073x_chan_nco_mode_set - switch DPLL channel to NCO mode + * @zldev: pointer to zl3073x_dev structure + * @index: DPLL channel index + * + * Switches the channel to NCO mode and reads the df_offset + * auto-captured by nco_auto_read directly from the register. + * No DF_READ handshake is needed as nco_auto_read populates + * the register before the mode switch completes. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index) +{ + struct zl3073x_chan *chan = &zldev->chan[index]; + u8 prev_mode, df_read; + u64 val; + int rc; + + prev_mode = zl3073x_chan_mode_get(chan); + + /* nco_auto_read captures the tracking offset at NCO entry only + * from reflock, auto or holdover mode. From freerun the captured + * value is not meaningful. + */ + if (prev_mode == ZL_DPLL_MODE_REFSEL_MODE_FREERUN) { + zl3073x_chan_mode_set(chan, ZL_DPLL_MODE_REFSEL_MODE_NCO); + + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index), + chan->mode_refsel); + if (rc) { + zl3073x_chan_mode_set(chan, prev_mode); + return rc; + } + + chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN; + return 0; + } + + /* Configure df_read for nco_auto_read: + * ref_ofst=0 - reads offset relative to master clock (not input ref) + * cmd=CMD_ACC_I - accumulated I-part covering both locked and + * holdover entry. + * + * No semaphore is set - this only configures what the df_offset + * value represents after the mode switch; nco_auto_read performs + * the actual read automatically. + */ + df_read = FIELD_PREP(ZL_DPLL_DF_READ_REF_OFST, 0) | + FIELD_PREP(ZL_DPLL_DF_READ_CMD, ZL_DPLL_DF_READ_CMD_ACC_I); + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_DF_READ(index), df_read); + if (rc) + return rc; + + /* Wait for df_read configuration to take effect before + * triggering nco_auto_read via mode switch. The worst-case + * internal register update time is 25 ms. + */ + fsleep(25000); + + zl3073x_chan_mode_set(chan, ZL_DPLL_MODE_REFSEL_MODE_NCO); + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index), + chan->mode_refsel); + if (rc) { + zl3073x_chan_mode_set(chan, prev_mode); + return rc; + } + + /* Wait for nco_auto_read to populate df_offset. The worst-case + * internal register update time is 25 ms. + */ + fsleep(25000); + + /* Read df_offset captured by nco_auto_read during mode switch. + * No DF_READ semaphore handshake needed. Mode switch already + * succeeded, so don't propagate a read failure back to userspace. + */ + rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_DF_OFFSET(index), &val); + if (rc) { + dev_warn(zldev->dev, + "Failed to read DPLL%u df_offset: %pe\n", + index, ERR_PTR(rc)); + chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN; + } else { + chan->df_offset = sign_extend64(val, 47); + } + + return 0; +} + /** * zl3073x_chan_state_fetch - fetch DPLL channel state from hardware * @zldev: pointer to zl3073x_dev structure @@ -73,6 +172,10 @@ int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index) struct zl3073x_chan *chan = &zldev->chan[index]; int rc, i; + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_CTRL(index), &chan->ctrl); + if (rc) + return rc; + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index), &chan->mode_refsel); if (rc) @@ -85,6 +188,13 @@ int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index) if (rc) return rc; + /* If firmware left the channel in NCO mode, mark df_offset as + * unknown - we cannot know whether the preconditions for a valid + * nco_auto_read capture were met. + */ + if (zl3073x_chan_mode_is_nco(chan)) + chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN; + dev_dbg(zldev->dev, "DPLL%u lock_state: %u, ho: %u, sel_state: %u, sel_ref: %u\n", index, zl3073x_chan_lock_state_get(chan), @@ -147,7 +257,15 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index, if (!memcmp(&dchan->cfg, &chan->cfg, sizeof(chan->cfg))) return 0; - /* Direct register write for mode_refsel */ + /* Direct register writes for ctrl and mode_refsel */ + if (dchan->ctrl != chan->ctrl) { + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_CTRL(index), + chan->ctrl); + if (rc) + return rc; + dchan->ctrl = chan->ctrl; + } + if (dchan->mode_refsel != chan->mode_refsel) { rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index), chan->mode_refsel); diff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h index 4353809c6912..dc9c6d95bdee 100644 --- a/drivers/dpll/zl3073x/chan.h +++ b/drivers/dpll/zl3073x/chan.h @@ -13,6 +13,7 @@ struct zl3073x_dev; /** * struct zl3073x_chan - DPLL channel state + * @ctrl: DPLL control register value * @mode_refsel: mode and reference selection register value * @ref_prio: reference priority registers (4 bits per ref, P/N packed) * @mon_status: monitor status register value @@ -21,6 +22,7 @@ struct zl3073x_dev; */ struct zl3073x_chan { struct_group(cfg, + u8 ctrl; u8 mode_refsel; u8 ref_prio[ZL3073X_NUM_REFS / 2]; ); @@ -38,6 +40,7 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index, const struct zl3073x_chan *chan); int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index); +int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index); /** * zl3073x_chan_df_offset_get - get cached df_offset vs tracked reference @@ -152,6 +155,51 @@ static inline u8 zl3073x_chan_lock_state_get(const struct zl3073x_chan *chan) return FIELD_GET(ZL_DPLL_MON_STATUS_STATE, chan->mon_status); } +/** + * zl3073x_chan_is_locked - check if channel is locked to a reference + * @chan: pointer to channel state + * + * Return: true if channel is locked, false otherwise + */ +static inline bool zl3073x_chan_is_locked(const struct zl3073x_chan *chan) +{ + u8 lock_state = zl3073x_chan_lock_state_get(chan); + return lock_state == ZL_DPLL_MON_STATUS_STATE_LOCK; +} + +/** + * zl3073x_chan_mode_is_auto - check if channel is in automatic mode + * @chan: pointer to channel state + * + * Return: true if channel is in automatic mode, false otherwise + */ +static inline bool zl3073x_chan_mode_is_auto(const struct zl3073x_chan *chan) +{ + return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_AUTO; +} + +/** + * zl3073x_chan_mode_is_nco - check if channel is in NCO mode + * @chan: pointer to channel state + * + * Return: true if channel is in NCO mode, false otherwise + */ +static inline bool zl3073x_chan_mode_is_nco(const struct zl3073x_chan *chan) +{ + return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_NCO; +} + +/** + * zl3073x_chan_mode_is_reflock - check if channel is in reflock mode + * @chan: pointer to channel state + * + * Return: true if channel is in reflock mode, false otherwise + */ +static inline bool zl3073x_chan_mode_is_reflock(const struct zl3073x_chan *chan) +{ + return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_REFLOCK; +} + /** * zl3073x_chan_is_ho_ready - check if holdover is ready * @chan: pointer to channel state diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c index 87e8b7c86548..d91f52b58eae 100644 --- a/drivers/dpll/zl3073x/dpll.c +++ b/drivers/dpll/zl3073x/dpll.c @@ -80,6 +80,18 @@ zl3073x_dpll_is_input_pin(struct zl3073x_dpll_pin *pin) return pin->dir == DPLL_PIN_DIRECTION_INPUT; } +/** + * zl3073x_dpll_is_nco_pin - check if the pin is a virtual NCO pin + * @pin: pin to check + * + * Return: true if pin is a virtual NCO pin, false otherwise. + */ +static bool +zl3073x_dpll_is_nco_pin(struct zl3073x_dpll_pin *pin) +{ + return pin->id == ZL3073X_NCO_PIN_ID; +} + /** * zl3073x_dpll_is_p_pin - check if the pin is P-pin * @pin: pin to check @@ -119,6 +131,19 @@ zl3073x_dpll_pin_get_by_ref(struct zl3073x_dpll *zldpll, u8 ref_id) return NULL; } +static struct zl3073x_dpll_pin * +zl3073x_dpll_nco_pin_get(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dpll_pin *pin; + + list_for_each_entry(pin, &zldpll->pins, list) { + if (zl3073x_dpll_is_nco_pin(pin)) + return pin; + } + + return NULL; +} + static int zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin, void *pin_priv, @@ -635,6 +660,7 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, { struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + struct zl3073x_dpll_pin *nco_pin = NULL; struct zl3073x_chan chan; u8 mode, ref; int rc = 0; @@ -666,6 +692,10 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, goto invalid_state; } break; + case ZL_DPLL_MODE_REFSEL_MODE_NCO: + if (state == DPLL_PIN_STATE_CONNECTED) + nco_pin = zl3073x_dpll_nco_pin_get(zldpll); + fallthrough; case ZL_DPLL_MODE_REFSEL_MODE_FREERUN: case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER: if (state == DPLL_PIN_STATE_CONNECTED) { @@ -713,6 +743,13 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, rc = -EINVAL; unlock: mutex_unlock(&zldpll->lock); + + /* If leaving NCO mode, notify userspace about the NCO pin + * state change - the periodic worker skips the NCO pin. + */ + if (!rc && nco_pin) + __dpll_pin_change_ntf(nco_pin->dpll_pin); + return rc; } @@ -1039,6 +1076,144 @@ zl3073x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin, return 0; } +static int +zl3073x_dpll_nco_pin_operstate_on_dpll_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_operstate *operstate, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + const struct zl3073x_chan *chan; + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (zl3073x_chan_mode_is_nco(chan)) + *operstate = DPLL_PIN_OPERSTATE_ACTIVE; + else + *operstate = DPLL_PIN_OPERSTATE_STANDBY; + + return 0; +} + +static int +zl3073x_dpll_nco_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + const struct zl3073x_chan *chan; + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (zl3073x_chan_mode_is_nco(chan)) + *state = DPLL_PIN_STATE_CONNECTED; + else + *state = DPLL_PIN_STATE_DISCONNECTED; + + return 0; +} + +static int +zl3073x_dpll_nco_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll_pin *ref_pin = NULL; + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_chan chan; + u8 ref; + int rc; + + mutex_lock(&zldpll->lock); + + chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); + + switch (state) { + case DPLL_PIN_STATE_CONNECTED: + if (zl3073x_chan_mode_is_nco(&chan)) { + mutex_unlock(&zldpll->lock); + return 0; + } + if (zl3073x_chan_mode_is_auto(&chan)) { + NL_SET_ERR_MSG(extack, + "NCO pin cannot be connected in automatic mode"); + mutex_unlock(&zldpll->lock); + return -EINVAL; + } + if (zl3073x_chan_mode_is_reflock(&chan)) { + /* Get currently connected pin */ + ref = zl3073x_chan_ref_get(&chan); + ref_pin = zl3073x_dpll_pin_get_by_ref(zldpll, ref); + } + rc = zl3073x_chan_nco_mode_set(zldpll->dev, zldpll->id); + break; + case DPLL_PIN_STATE_DISCONNECTED: + if (!zl3073x_chan_mode_is_nco(&chan)) { + mutex_unlock(&zldpll->lock); + return 0; + } + /* Switch to freerun - holdover averaging was not + * updated during NCO mode. + */ + zl3073x_chan_mode_set(&chan, + ZL_DPLL_MODE_REFSEL_MODE_FREERUN); + rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); + break; + default: + NL_SET_ERR_MSG(extack, "invalid pin state for NCO pin"); + mutex_unlock(&zldpll->lock); + return -EINVAL; + } + + mutex_unlock(&zldpll->lock); + + if (!rc && ref_pin) + __dpll_pin_change_ntf(ref_pin->dpll_pin); + + return rc; +} + +static int +zl3073x_dpll_nco_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv, + const struct dpll_device *dpll, void *dpll_priv, + struct dpll_ffo_param *ffo, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + const struct zl3073x_chan *chan; + s64 df_offset; + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (!zl3073x_chan_mode_is_nco(chan)) + return -ENODATA; + + /* Do not report FFO if a failure occurred during switching to NCO. */ + df_offset = zl3073x_chan_df_offset_get(chan); + if (df_offset == ZL_DPLL_DF_OFFSET_UNKNOWN) + return -ENODATA; + + /* dpll_df_offset register has inverted sign per datasheet: + * f_offset = f_nom * (-df_offset) / 2^48 + * NCO pin reports DPLL output offset from nominal, so negate. + * Convert to PPT: ppt = -df * 5^12 / 2^36 + */ + ffo->ffo = -mul_s64_u64_shr(df_offset, 244140625, 36); + + return 0; +} + static int zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv, s32 *temp, struct netlink_ext_ack *extack) @@ -1121,21 +1296,7 @@ zl3073x_dpll_supported_modes_get(const struct dpll_device *dpll, void *dpll_priv, unsigned long *modes, struct netlink_ext_ack *extack) { - struct zl3073x_dpll *zldpll = dpll_priv; - const struct zl3073x_chan *chan; - - guard(mutex)(&zldpll->lock); - - chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); - - /* We support switching between automatic and manual mode, except in - * a case where the DPLL channel is configured to run in NCO mode. - * In this case, report only the manual mode to which the NCO is mapped - * as the only supported one. - */ - if (zl3073x_chan_mode_get(chan) != ZL_DPLL_MODE_REFSEL_MODE_NCO) - __set_bit(DPLL_MODE_AUTOMATIC, modes); - + __set_bit(DPLL_MODE_AUTOMATIC, modes); __set_bit(DPLL_MODE_MANUAL, modes); return 0; @@ -1224,11 +1385,12 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv, enum dpll_mode mode, struct netlink_ext_ack *extack) { struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dpll_pin *nco_pin = NULL; struct zl3073x_chan chan; u8 hw_mode, ref; int rc; - guard(mutex)(&zldpll->lock); + mutex_lock(&zldpll->lock); chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); ref = zl3073x_chan_refsel_ref_get(&chan); @@ -1249,6 +1411,9 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv, else hw_mode = ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER; } else { + if (zl3073x_chan_mode_is_nco(&chan)) + nco_pin = zl3073x_dpll_nco_pin_get(zldpll); + /* We are switching from manual to automatic mode: * - if there is a valid reference selected then ensure that * it is selectable after switch to automatic mode @@ -1277,9 +1442,18 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv, if (rc) { NL_SET_ERR_MSG_MOD(extack, "failed to set reference selection mode"); + mutex_unlock(&zldpll->lock); return rc; } + mutex_unlock(&zldpll->lock); + + /* If leaving NCO mode, notify userspace about the NCO pin + * state change - the periodic worker skips the NCO pin. + */ + if (nco_pin) + __dpll_pin_change_ntf(nco_pin->dpll_pin); + return 0; } @@ -1388,6 +1562,15 @@ static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = { .state_on_dpll_get = zl3073x_dpll_output_pin_state_on_dpll_get, }; +static const struct dpll_pin_ops zl3073x_dpll_nco_pin_ops = { + .supported_ffo = BIT(DPLL_FFO_PIN_DEVICE), + .direction_get = zl3073x_dpll_pin_direction_get, + .ffo_get = zl3073x_dpll_nco_pin_ffo_get, + .operstate_on_dpll_get = zl3073x_dpll_nco_pin_operstate_on_dpll_get, + .state_on_dpll_get = zl3073x_dpll_nco_pin_state_on_dpll_get, + .state_on_dpll_set = zl3073x_dpll_nco_pin_state_on_dpll_set, +}; + static const struct dpll_device_ops zl3073x_dpll_device_ops = { .lock_status_get = zl3073x_dpll_lock_status_get, .mode_get = zl3073x_dpll_mode_get, @@ -1535,7 +1718,9 @@ zl3073x_dpll_pin_unregister(struct zl3073x_dpll_pin *pin) WARN(!pin->dpll_pin, "DPLL pin is not registered\n"); - if (zl3073x_dpll_is_input_pin(pin)) + if (zl3073x_dpll_is_nco_pin(pin)) + ops = &zl3073x_dpll_nco_pin_ops; + else if (zl3073x_dpll_is_input_pin(pin)) ops = &zl3073x_dpll_input_pin_ops; else ops = &zl3073x_dpll_output_pin_ops; @@ -1588,20 +1773,13 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll, enum dpll_pin_direction dir, u8 index) { struct zl3073x_dev *zldev = zldpll->dev; - const struct zl3073x_chan *chan; bool is_diff, is_enabled; const char *name; - chan = zl3073x_chan_state_get(zldev, zldpll->id); - if (dir == DPLL_PIN_DIRECTION_INPUT) { u8 ref_id = zl3073x_input_pin_ref_get(index); const struct zl3073x_ref *ref; - /* Skip the pin if the DPLL is running in NCO mode */ - if (zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_NCO) - return false; - name = "REF"; ref = zl3073x_ref_state_get(zldev, ref_id); is_diff = zl3073x_ref_is_diff(ref); @@ -1642,6 +1820,66 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll, return true; } +static const struct dpll_pin_properties zl3073x_dpll_nco_pin_props = { + .type = DPLL_PIN_TYPE_INT_NCO, + .package_label = "NCO", + .capabilities = DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE, +}; + +static int +zl3073x_dpll_nco_pin_register(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dpll_pin *pin; + struct zl3073x_chan chan; + int rc; + + /* Ensure that ctrl bits are configured for NCO operation: + * - nco_auto_read: auto-capture tracking offset on NCO entry + * - tod_step_reset: apply negated ToD step on NCO exit + * - tie_clear: PPS DPLLs re-align outputs on NCO exit + */ + mutex_lock(&zldpll->lock); + chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); + FIELD_MODIFY(ZL_DPLL_CTRL_NCO_AUTO_READ, &chan.ctrl, 1); + FIELD_MODIFY(ZL_DPLL_CTRL_TOD_STEP_RST, &chan.ctrl, 1); + FIELD_MODIFY(ZL_DPLL_CTRL_TIE_CLEAR, &chan.ctrl, + zldpll->type == DPLL_TYPE_PPS ? 1 : 0); + rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); + mutex_unlock(&zldpll->lock); + if (rc) + return rc; + + pin = zl3073x_dpll_pin_alloc(zldpll, DPLL_PIN_DIRECTION_INPUT, + ZL3073X_NCO_PIN_ID); + if (IS_ERR(pin)) + return PTR_ERR(pin); + + pin->dpll_pin = dpll_pin_get(zldpll->dev->clock_id, ZL3073X_NCO_PIN_ID, + THIS_MODULE, &zl3073x_dpll_nco_pin_props, + &pin->tracker); + if (IS_ERR(pin->dpll_pin)) { + rc = PTR_ERR(pin->dpll_pin); + goto err_pin_get; + } + + rc = dpll_pin_register(zldpll->dpll_dev, pin->dpll_pin, + &zl3073x_dpll_nco_pin_ops, pin); + if (rc) + goto err_register; + + list_add(&pin->list, &zldpll->pins); + + return 0; + +err_register: + dpll_pin_put(pin->dpll_pin, &pin->tracker); +err_pin_get: + pin->dpll_pin = NULL; + kfree(pin); + + return rc; +} + /** * zl3073x_dpll_pins_register - register all registerable DPLL pins * @zldpll: pointer to zl3073x_dpll structure @@ -1689,6 +1927,11 @@ zl3073x_dpll_pins_register(struct zl3073x_dpll *zldpll) list_add(&pin->list, &zldpll->pins); } + /* Register NCO virtual input pin */ + rc = zl3073x_dpll_nco_pin_register(zldpll); + if (rc) + goto error; + return 0; error: @@ -1724,8 +1967,8 @@ zl3073x_dpll_device_register(struct zl3073x_dpll *zldpll) return rc; } - rc = dpll_device_register(zldpll->dpll_dev, - zl3073x_prop_dpll_type_get(zldev, zldpll->id), + zldpll->type = zl3073x_prop_dpll_type_get(zldev, zldpll->id); + rc = dpll_device_register(zldpll->dpll_dev, zldpll->type, &zldpll->ops, zldpll); if (rc) { dpll_device_put(zldpll->dpll_dev, &zldpll->tracker); @@ -1836,6 +2079,14 @@ zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin) return false; chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (zl3073x_chan_df_offset_get(chan) == ZL_DPLL_DF_OFFSET_UNKNOWN) + return false; + + /* dpll_df_offset register has inverted sign per datasheet: + * f_offset = f_nom * (-df_offset) / 2^48 + * Input pin FFO is pin-vs-DPLL (opposite of DPLL-vs-reference), + * so the two inversions cancel out: ppt = df * 5^12 / 2^36 + */ ffo = mul_s64_u64_shr(zl3073x_chan_df_offset_get(chan), 244140625, 36); @@ -1948,7 +2199,9 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll) list_for_each_entry(pin, &zldpll->pins, list) { enum dpll_pin_operstate operstate; - if (!zl3073x_dpll_is_input_pin(pin)) + /* Only physical input pins need monitoring */ + if (!zl3073x_dpll_is_input_pin(pin) || + zl3073x_dpll_is_nco_pin(pin)) continue; rc = zl3073x_dpll_ref_operstate_get(pin, &operstate); @@ -1989,6 +2242,7 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll) list_for_each_entry(pin, &zldpll->pins, list) { if (zl3073x_dpll_is_input_pin(pin) && + !zl3073x_dpll_is_nco_pin(pin) && test_bit(pin->id, changed_pins)) dpll_pin_change_ntf(pin->dpll_pin); } diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h index 9f57c944a007..faebc402ba1b 100644 --- a/drivers/dpll/zl3073x/dpll.h +++ b/drivers/dpll/zl3073x/dpll.h @@ -19,6 +19,7 @@ * @dpll_dev: pointer to registered DPLL device * @tracker: tracking object for the acquired reference * @lock: per-DPLL mutex serializing all operations + * @type: DPLL type (PPS or EEC) * @lock_status: last saved DPLL lock status * @pins: list of pins */ @@ -32,6 +33,7 @@ struct zl3073x_dpll { struct dpll_device *dpll_dev; dpll_tracker tracker; struct mutex lock; + enum dpll_type type; enum dpll_lock_status lock_status; struct list_head pins; }; diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h index 9578f0009528..b70ead7d4495 100644 --- a/drivers/dpll/zl3073x/regs.h +++ b/drivers/dpll/zl3073x/regs.h @@ -5,6 +5,7 @@ #include #include +#include /* * Hardware limits for ZL3073x chip family @@ -17,6 +18,7 @@ #define ZL3073X_NUM_OUTPUT_PINS (ZL3073X_NUM_OUTS * 2) #define ZL3073X_NUM_PINS (ZL3073X_NUM_INPUT_PINS + \ ZL3073X_NUM_OUTPUT_PINS) +#define ZL3073X_NCO_PIN_ID ZL3073X_NUM_PINS /* * Register address structure: @@ -164,10 +166,18 @@ #define ZL_DPLL_MODE_REFSEL_MODE_NCO 4 #define ZL_DPLL_MODE_REFSEL_REF GENMASK(7, 4) +#define ZL_REG_DPLL_CTRL(_idx) \ + ZL_REG_IDX(_idx, 5, 0x05, 1, ZL3073X_MAX_CHANNELS, 4) +#define ZL_DPLL_CTRL_TIE_CLEAR BIT(0) +#define ZL_DPLL_CTRL_TOD_STEP_RST BIT(2) +#define ZL_DPLL_CTRL_NCO_AUTO_READ BIT(7) + #define ZL_REG_DPLL_DF_READ(_idx) \ ZL_REG_IDX(_idx, 5, 0x28, 1, ZL3073X_MAX_CHANNELS, 1) #define ZL_DPLL_DF_READ_SEM BIT(4) #define ZL_DPLL_DF_READ_REF_OFST BIT(3) +#define ZL_DPLL_DF_READ_CMD GENMASK(2, 0) +#define ZL_DPLL_DF_READ_CMD_ACC_I 4 #define ZL_REG_DPLL_MEAS_CTRL ZL_REG(5, 0x50, 1) #define ZL_DPLL_MEAS_CTRL_EN BIT(0) @@ -190,6 +200,7 @@ #define ZL_REG_DPLL_DF_OFFSET_4 ZL_REG(7, 0x00, 6) #define ZL_REG_DPLL_DF_OFFSET(_idx) \ ((_idx) < 4 ? ZL_REG_DPLL_DF_OFFSET_03(_idx) : ZL_REG_DPLL_DF_OFFSET_4) +#define ZL_DPLL_DF_OFFSET_UNKNOWN S64_MIN /*********************************** * Register Page 9, Synth and Output From 5c660b243435d74d17a6721cd90e9d188b231cc8 Mon Sep 17 00:00:00 2001 From: P Praneesh Date: Sun, 14 Jun 2026 10:47:31 +0530 Subject: [PATCH 0190/1433] wifi: cfg80211: Drop unused link stats handling in nl80211_send_station() Remove the link level statistics handling from nl80211_send_station() and drop the unused link_stats parameter from its signature and callers. The removed code iterated over each MLO link and attempted to send link specific station data through NL80211_ATTR_MLO_LINKS, but this logic was never used because link_stats was always false. This logic was introduced during early work on link level station statistics with the intention of reporting information for each link. Due to message size concerns when a station has multiple links, the feature was disabled behind the link_stats flag and remained unused. The link level reporting block in nl80211_send_station() is dead code and cannot support larger messages, so remove it. This cleanup also prepares for proper link level statistics reporting in nl80211_dump_station() in a later patch, where fragmentation allows safe transmission of multi link data. Also fix label indentation: the nla_put_failure label had an erroneous leading space. Signed-off-by: P Praneesh Link: https://patch.msgid.link/20260614051739.3979947-2-praneesh.p@oss.qualcomm.com Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 50 +++++------------------------------------- 1 file changed, 6 insertions(+), 44 deletions(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index d738c0089990..091deaa58204 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -8033,14 +8033,10 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, u32 seq, int flags, struct cfg80211_registered_device *rdev, struct wireless_dev *wdev, - const u8 *mac_addr, struct station_info *sinfo, - bool link_stats) + const u8 *mac_addr, struct station_info *sinfo) { void *hdr; struct nlattr *sinfoattr, *bss_param; - struct link_station_info *link_sinfo; - struct nlattr *links, *link; - int link_id; hdr = nl80211hdr_put(msg, portid, seq, flags, cmd); if (!hdr) { @@ -8258,45 +8254,11 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, goto nla_put_failure; } - if (link_stats && sinfo->valid_links) { - links = nla_nest_start(msg, NL80211_ATTR_MLO_LINKS); - if (!links) - goto nla_put_failure; - - for_each_valid_link(sinfo, link_id) { - link_sinfo = sinfo->links[link_id]; - - if (WARN_ON_ONCE(!link_sinfo)) - continue; - - if (!is_valid_ether_addr(link_sinfo->addr)) - continue; - - link = nla_nest_start(msg, link_id + 1); - if (!link) - goto nla_put_failure; - - if (nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, - link_id)) - goto nla_put_failure; - - if (nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, - link_sinfo->addr)) - goto nla_put_failure; - - if (nl80211_fill_link_station(msg, rdev, link_sinfo)) - goto nla_put_failure; - - nla_nest_end(msg, link); - } - nla_nest_end(msg, links); - } - cfg80211_sinfo_release_content(sinfo); genlmsg_end(msg, hdr); return 0; - nla_put_failure: +nla_put_failure: cfg80211_sinfo_release_content(sinfo); genlmsg_cancel(msg, hdr); return -EMSGSIZE; @@ -8549,7 +8511,7 @@ static int nl80211_dump_station(struct sk_buff *skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, NLM_F_MULTI, rdev, wdev, mac_addr, - &sinfo, false) < 0) + &sinfo) < 0) goto out; sta_idx++; @@ -8613,7 +8575,7 @@ static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info) if (nl80211_send_station(msg, NL80211_CMD_NEW_STATION, info->snd_portid, info->snd_seq, 0, - rdev, wdev, mac_addr, &sinfo, false) < 0) { + rdev, wdev, mac_addr, &sinfo) < 0) { nlmsg_free(msg); return -ENOBUFS; } @@ -21651,7 +21613,7 @@ void cfg80211_new_sta(struct wireless_dev *wdev, const u8 *mac_addr, return; if (nl80211_send_station(msg, NL80211_CMD_NEW_STATION, 0, 0, 0, - rdev, wdev, mac_addr, sinfo, false) < 0) { + rdev, wdev, mac_addr, sinfo) < 0) { nlmsg_free(msg); return; } @@ -21681,7 +21643,7 @@ void cfg80211_del_sta_sinfo(struct wireless_dev *wdev, const u8 *mac_addr, } if (nl80211_send_station(msg, NL80211_CMD_DEL_STATION, 0, 0, 0, - rdev, wdev, mac_addr, sinfo, false) < 0) { + rdev, wdev, mac_addr, sinfo) < 0) { nlmsg_free(msg); return; } From 4b40378ac20384e58b9b6f2c4b800966ec14c137 Mon Sep 17 00:00:00 2001 From: P Praneesh Date: Sun, 14 Jun 2026 10:47:32 +0530 Subject: [PATCH 0191/1433] wifi: cfg80211: Add helper to pack station-level STA_INFO Add a helper function nl80211_put_sta_info_common() to pack the station-level (aggregated) STA information into a netlink message. This prepares the code for future enhancements such as supporting fragmented link statistics in nl80211_dump_station. Signed-off-by: P Praneesh Link: https://patch.msgid.link/20260614051739.3979947-3-praneesh.p@oss.qualcomm.com Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 66 +++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 091deaa58204..912009fb9dda 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -8029,32 +8029,15 @@ static int nl80211_fill_link_station(struct sk_buff *msg, return -EMSGSIZE; } -static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, - u32 seq, int flags, - struct cfg80211_registered_device *rdev, - struct wireless_dev *wdev, - const u8 *mac_addr, struct station_info *sinfo) +static int nl80211_put_sta_info_common(struct sk_buff *msg, + struct cfg80211_registered_device *rdev, + struct station_info *sinfo) { - void *hdr; struct nlattr *sinfoattr, *bss_param; - hdr = nl80211hdr_put(msg, portid, seq, flags, cmd); - if (!hdr) { - cfg80211_sinfo_release_content(sinfo); - return -1; - } - - if ((wdev->netdev && - nla_put_u32(msg, NL80211_ATTR_IFINDEX, wdev->netdev->ifindex)) || - nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev), - NL80211_ATTR_PAD) || - nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr) || - nla_put_u32(msg, NL80211_ATTR_GENERATION, sinfo->generation)) - goto nla_put_failure; - - sinfoattr = nla_nest_start_noflag(msg, NL80211_ATTR_STA_INFO); + sinfoattr = nla_nest_start(msg, NL80211_ATTR_STA_INFO); if (!sinfoattr) - goto nla_put_failure; + return -EMSGSIZE; #define PUT_SINFO(attr, memb, type) do { \ BUILD_BUG_ON(sizeof(type) == sizeof(u64)); \ @@ -8145,8 +8128,7 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, PUT_SINFO_U64(T_OFFSET, t_offset); if (sinfo->filled & BIT_ULL(NL80211_STA_INFO_BSS_PARAM)) { - bss_param = nla_nest_start_noflag(msg, - NL80211_STA_INFO_BSS_PARAM); + bss_param = nla_nest_start(msg, NL80211_STA_INFO_BSS_PARAM); if (!bss_param) goto nla_put_failure; @@ -8188,8 +8170,7 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, struct nlattr *tidsattr; int tid; - tidsattr = nla_nest_start_noflag(msg, - NL80211_STA_INFO_TID_STATS); + tidsattr = nla_nest_start(msg, NL80211_STA_INFO_TID_STATS); if (!tidsattr) goto nla_put_failure; @@ -8202,7 +8183,7 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, if (!tidstats->filled) continue; - tidattr = nla_nest_start_noflag(msg, tid + 1); + tidattr = nla_nest_start(msg, tid + 1); if (!tidattr) goto nla_put_failure; @@ -8232,6 +8213,37 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, } nla_nest_end(msg, sinfoattr); + return 0; + +nla_put_failure: + nla_nest_cancel(msg, sinfoattr); + return -EMSGSIZE; +} + +static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, + u32 seq, int flags, + struct cfg80211_registered_device *rdev, + struct wireless_dev *wdev, + const u8 *mac_addr, struct station_info *sinfo) +{ + void *hdr; + + hdr = nl80211hdr_put(msg, portid, seq, flags, cmd); + if (!hdr) { + cfg80211_sinfo_release_content(sinfo); + return -1; + } + + if ((wdev->netdev && + nla_put_u32(msg, NL80211_ATTR_IFINDEX, wdev->netdev->ifindex)) || + nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev), + NL80211_ATTR_PAD) || + nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr) || + nla_put_u32(msg, NL80211_ATTR_GENERATION, sinfo->generation)) + goto nla_put_failure; + + if (nl80211_put_sta_info_common(msg, rdev, sinfo)) + goto nla_put_failure; if (sinfo->assoc_req_ies_len && nla_put(msg, NL80211_ATTR_IE, sinfo->assoc_req_ies_len, From 727afb05ef6322c5a430d971301adad90eb040b0 Mon Sep 17 00:00:00 2001 From: P Praneesh Date: Sun, 14 Jun 2026 10:47:33 +0530 Subject: [PATCH 0192/1433] wifi: cfg80211: Refactor nl80211_dump_station() to prepare for per-link stats Currently, nl80211_dump_station() relies on the netlink callback's generic args array (cb->args[2]) to track the station index during dumps. It also processes the entire sinfo structure and transmits it to userspace immediately in a single pass. This approach creates a bottleneck for MLO. When an MLD station has multiple active links, the aggregated station information, combined with the individual per-link statistics, can easily exceed the maximum netlink message size limits. The current monolithic dump iteration cannot pause and resume mid-station to fragment these large per-link statistics across multiple netlink messages. Introduce a stateful context structure (struct nl80211_dump_station_ctx) allocated during the dump to track the iteration state. Store the context pointer directly at cb->args[2], following the same pattern as nl80211_dump_wiphy which stores its state pointer at cb->args[0]. Move the station index (sta_idx) tracking and the sinfo payload into this context. The per-station netlink message is built inline in the loop: common header attributes are assembled directly, then nl80211_put_sta_info_common() adds the STA_INFO payload. Furthermore, move the NL80211_CMD_GET_STATION command definition from genl_small_ops to genl_ops to natively support the .done callback. Implement nl80211_dump_station_done() to ensure the newly allocated state context and its deeply allocated sinfo payload are safely freed when the dump concludes or is aborted prematurely by userspace. Note that the previous dump path used nl80211_send_station(), which included NL80211_ATTR_IE and NL80211_ATTR_RESP_IE. These attributes are not carried forward in this implementation. As documented, association response IEs (assoc_resp_ies) are only relevant at station creation time (e.g. via cfg80211_new_sta()) to notify userspace about association details, and are not expected to be part of get_station()/dump_station() callbacks. Aligning with this expectation, these IEs are intentionally omitted here. This refactoring maintains the existing netlink batching performance while laying the stateful foundation required for per-link statistics fragmentation in subsequent patches. At out_err_release, cfg80211_sinfo_release_content() frees any dynamically allocated sub-fields inside ctx->sinfo (including per-link pointers in sinfo.links[]). Without the subsequent memset, those pointers remain non-NULL in the embedded sinfo. When the dump concludes or is aborted, nl80211_dump_station_done() calls cfg80211_sinfo_release_content() a second time on the same ctx->sinfo, which would free the already-released link memory. The memset(&ctx->sinfo, 0, sizeof(ctx->sinfo)) zeroes all pointers so the second release call hits kfree(NULL), which is a harmless no-op. Signed-off-by: P Praneesh Link: https://patch.msgid.link/20260614051739.3979947-4-praneesh.p@oss.qualcomm.com Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 129 +++++++++++++++++++++++++++-------------- 1 file changed, 85 insertions(+), 44 deletions(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 912009fb9dda..ccc4341a0aea 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -8464,16 +8464,19 @@ static void cfg80211_sta_set_mld_sinfo(struct station_info *sinfo) sinfo->filled &= ~BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL_AVG); } +struct nl80211_dump_station_ctx { + int sta_idx; + u8 mac_addr[ETH_ALEN]; + struct station_info sinfo; +}; + static int nl80211_dump_station(struct sk_buff *skb, struct netlink_callback *cb) { - struct station_info sinfo; struct cfg80211_registered_device *rdev; struct wireless_dev *wdev; - u8 mac_addr[ETH_ALEN]; - int sta_idx = cb->args[2]; - bool sinfo_alloc = false; - int err, i; + struct nl80211_dump_station_ctx *ctx = (void *)cb->args[2]; + int err; err = nl80211_prepare_wdev_dump(cb, &rdev, &wdev, NULL); if (err) @@ -8481,6 +8484,15 @@ static int nl80211_dump_station(struct sk_buff *skb, /* nl80211_prepare_wdev_dump acquired it in the successful case */ __acquire(&rdev->wiphy.mtx); + if (!ctx) { + ctx = kzalloc_obj(*ctx); + if (!ctx) { + err = -ENOMEM; + goto out_err; + } + cb->args[2] = (long)ctx; + } + if (!wdev->netdev && wdev->iftype != NL80211_IFTYPE_NAN) { err = -EINVAL; goto out_err; @@ -8491,55 +8503,83 @@ static int nl80211_dump_station(struct sk_buff *skb, goto out_err; } - while (1) { - memset(&sinfo, 0, sizeof(sinfo)); + while (true) { + void *hdr; - for (i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) { - sinfo.links[i] = - kzalloc_obj(*sinfo.links[0]); - if (!sinfo.links[i]) { + memset(&ctx->sinfo, 0, sizeof(ctx->sinfo)); + for (int i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) { + ctx->sinfo.links[i] = + kzalloc_obj(*ctx->sinfo.links[0]); + if (!ctx->sinfo.links[i]) { err = -ENOMEM; - goto out_err; + goto out_err_release; } - sinfo_alloc = true; } - err = rdev_dump_station(rdev, wdev, sta_idx, - mac_addr, &sinfo); - if (err == -ENOENT) - break; + err = rdev_dump_station(rdev, wdev, ctx->sta_idx, + ctx->mac_addr, &ctx->sinfo); + if (err == -ENOENT) { + err = skb->len; + goto out_err_release; + } if (err) - goto out_err; + goto out_err_release; - if (sinfo.valid_links) - cfg80211_sta_set_mld_sinfo(&sinfo); + if (ctx->sinfo.valid_links) + cfg80211_sta_set_mld_sinfo(&ctx->sinfo); - /* reset the sinfo_alloc flag as nl80211_send_station() - * always releases sinfo - */ - sinfo_alloc = false; + hdr = nl80211hdr_put(skb, NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, NLM_F_MULTI, + NL80211_CMD_NEW_STATION); + if (!hdr) { + err = skb->len; + goto out_err_release; + } - if (nl80211_send_station(skb, NL80211_CMD_NEW_STATION, - NETLINK_CB(cb->skb).portid, - cb->nlh->nlmsg_seq, NLM_F_MULTI, - rdev, wdev, mac_addr, - &sinfo) < 0) - goto out; + if ((wdev->netdev && + nla_put_u32(skb, NL80211_ATTR_IFINDEX, + wdev->netdev->ifindex)) || + nla_put_u64_64bit(skb, NL80211_ATTR_WDEV, + wdev_id(wdev), NL80211_ATTR_PAD) || + nla_put(skb, NL80211_ATTR_MAC, ETH_ALEN, ctx->mac_addr) || + nla_put_u32(skb, NL80211_ATTR_GENERATION, + ctx->sinfo.generation)) { + genlmsg_cancel(skb, hdr); + err = skb->len; + goto out_err_release; + } - sta_idx++; + if (nl80211_put_sta_info_common(skb, rdev, &ctx->sinfo)) { + genlmsg_cancel(skb, hdr); + err = skb->len; + goto out_err_release; + } + + genlmsg_end(skb, hdr); + cfg80211_sinfo_release_content(&ctx->sinfo); + ctx->sta_idx++; } - out: - cb->args[2] = sta_idx; - err = skb->len; - out_err: - if (sinfo_alloc) - cfg80211_sinfo_release_content(&sinfo); +out_err_release: + cfg80211_sinfo_release_content(&ctx->sinfo); + memset(&ctx->sinfo, 0, sizeof(ctx->sinfo)); +out_err: wiphy_unlock(&rdev->wiphy); return err; } +static int nl80211_dump_station_done(struct netlink_callback *cb) +{ + struct nl80211_dump_station_ctx *ctx = (void *)cb->args[2]; + + if (ctx) { + cfg80211_sinfo_release_content(&ctx->sinfo); + kfree(ctx); + } + return 0; +} + static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev = info->user_ptr[0]; @@ -19517,6 +19557,14 @@ static const struct genl_ops nl80211_ops[] = { /* can be retrieved by unprivileged users */ .internal_flags = IFLAGS(NL80211_FLAG_NEED_WIPHY), }, + { + .cmd = NL80211_CMD_GET_STATION, + .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, + .doit = nl80211_get_station, + .dumpit = nl80211_dump_station, + .done = nl80211_dump_station_done, + .internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV), + }, }; static const struct genl_small_ops nl80211_small_ops[] = { @@ -19616,13 +19664,6 @@ static const struct genl_small_ops nl80211_small_ops[] = { .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_MLO_VALID_LINK_ID), }, - { - .cmd = NL80211_CMD_GET_STATION, - .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, - .doit = nl80211_get_station, - .dumpit = nl80211_dump_station, - .internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV), - }, { .cmd = NL80211_CMD_SET_STATION, .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, From dd21d1844aa096216e1be551d1ae5e57c27c837c Mon Sep 17 00:00:00 2001 From: P Praneesh Date: Sun, 14 Jun 2026 10:47:34 +0530 Subject: [PATCH 0193/1433] wifi: cfg80211: Fragment per-link station stats in nl80211_dump_station() In MLO scenarios, stations may have multiple links, each with distinct statistics. When userspace tools like iw or hostapd request station dumps, attempting to pack all per-link stats into a single netlink message can easily exceed the default 4KB buffer limit, especially when more than two links are active. This results in -EMSGSIZE errors and incomplete data delivery. To address this, fragment per-link station statistics across multiple netlink messages to ensure reliable delivery of complete MLO station information. Extend the stateful context with a two-phase dump mechanism: phase 0 (AGGREGATED) sends combined MLO-level statistics and phase 1 (PER_LINK) sends individual per-link statistics for each active link. The dump loop is structured to produce exactly one netlink message per iteration, with a common header (ifindex, wdev, mac, generation) built once and phase-specific payload added via a switch statement. This keeps header construction in one place and makes the EMSGSIZE bail-out uniform. Add a new request flag attribute, NL80211_ATTR_STA_DUMP_LINK_STATS (NLA_FLAG), for NL80211_CMD_GET_STATION dump. Userspace can set this flag to request per-link station statistics for MLO stations. Extract this flag during the first dump invocation by passing an attrbuf to nl80211_prepare_wdev_dump(); use __free(kfree) to avoid scattered manual kfree() calls. Cache the boolean in the dump context to avoid repeated parsing on subsequent invocations. Per-link messages carry a single NL80211_ATTR_MLO_LINKS nest with the link ID, link-specific MAC, and per-link NL80211_ATTR_STA_INFO payload. The link-specific validity (is_valid_ether_addr) and null pointer guard are checked in nl80211_put_link_station_payload() before any message construction begins. Also fix all nla_nest_start_noflag() calls in nl80211_fill_link_station() for nested attribute types (STA_INFO, BSS_PARAM, TID_STATS, per-tid) to use nla_nest_start() so the NLA_F_NESTED flag is set correctly. Propagate the actual return value from nl80211_put_sta_info_common() in the AGGREGATED phase rather than returning skb->len. Returning skb->len signals netlink to re-invoke the dump with the same sta_idx, causing an infinite loop when the aggregated payload is too large to fit; returning the real error code (-EMSGSIZE or otherwise) terminates the dump cleanly. Backward compatibility is seamlessly preserved for non-MLO stations. Signed-off-by: P Praneesh Link: https://patch.msgid.link/20260614051739.3979947-5-praneesh.p@oss.qualcomm.com Signed-off-by: Johannes Berg --- include/uapi/linux/nl80211.h | 19 ++++ net/wireless/nl80211.c | 172 ++++++++++++++++++++++++++++------- 2 files changed, 158 insertions(+), 33 deletions(-) diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index d9a8c693457f..020387d76412 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -3168,6 +3168,23 @@ enum nl80211_commands { * @NL80211_ATTR_NPCA_PRIMARY_FREQ: NPCA primary channel (u32) * @NL80211_ATTR_NPCA_PUNCT_BITMAP: NPCA puncturing bitmap (u32) * + * @NL80211_ATTR_STA_DUMP_LINK_STATS: Request flag for %NL80211_CMD_GET_STATION + * (dump mode only). When set on an MLD station, the dump produces two + * %NL80211_CMD_NEW_STATION messages per station per dump call: + * + * 1. An aggregated-stats message whose top-level %NL80211_ATTR_STA_INFO + * contains MLO-combined statistics (same content as a dump without + * this flag). + * + * 2. For each active link, a per-link message containing + * %NL80211_ATTR_MLO_LINKS with a single link entry. Each entry holds + * %NL80211_ATTR_MLO_LINK_ID, the link-specific %NL80211_ATTR_MAC, + * and %NL80211_ATTR_STA_INFO with per-link statistics (see + * &enum nl80211_sta_info). + * + * The aggregated message always precedes the per-link messages for the + * same station within a dump sequence. + * * @NUM_NL80211_ATTR: total number of nl80211_attrs available * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use @@ -3766,6 +3783,8 @@ enum nl80211_attrs { NL80211_ATTR_NPCA_PRIMARY_FREQ, NL80211_ATTR_NPCA_PUNCT_BITMAP, + NL80211_ATTR_STA_DUMP_LINK_STATS, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index ccc4341a0aea..be16a40132ff 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -1093,6 +1093,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = { [NL80211_ATTR_NPCA_PRIMARY_FREQ] = { .type = NLA_U32 }, [NL80211_ATTR_NPCA_PUNCT_BITMAP] = NLA_POLICY_FULL_RANGE(NLA_U32, &nl80211_punct_bitmap_range), + [NL80211_ATTR_STA_DUMP_LINK_STATS] = { .type = NLA_FLAG }, }; /* policy for the key attributes */ @@ -7870,7 +7871,7 @@ static int nl80211_fill_link_station(struct sk_buff *msg, goto nla_put_failure; \ } while (0) - link_sinfoattr = nla_nest_start_noflag(msg, NL80211_ATTR_STA_INFO); + link_sinfoattr = nla_nest_start(msg, NL80211_ATTR_STA_INFO); if (!link_sinfoattr) goto nla_put_failure; @@ -7936,8 +7937,8 @@ static int nl80211_fill_link_station(struct sk_buff *msg, PUT_LINK_SINFO(BEACON_LOSS, beacon_loss_count, u32); if (link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_BSS_PARAM)) { - bss_param = nla_nest_start_noflag(msg, - NL80211_STA_INFO_BSS_PARAM); + bss_param = nla_nest_start(msg, + NL80211_STA_INFO_BSS_PARAM); if (!bss_param) goto nla_put_failure; @@ -7979,8 +7980,7 @@ static int nl80211_fill_link_station(struct sk_buff *msg, struct nlattr *tidsattr; int tid; - tidsattr = nla_nest_start_noflag(msg, - NL80211_STA_INFO_TID_STATS); + tidsattr = nla_nest_start(msg, NL80211_STA_INFO_TID_STATS); if (!tidsattr) goto nla_put_failure; @@ -7993,7 +7993,7 @@ static int nl80211_fill_link_station(struct sk_buff *msg, if (!tidstats->filled) continue; - tidattr = nla_nest_start_noflag(msg, tid + 1); + tidattr = nla_nest_start(msg, tid + 1); if (!tidattr) goto nla_put_failure; @@ -8464,21 +8464,74 @@ static void cfg80211_sta_set_mld_sinfo(struct station_info *sinfo) sinfo->filled &= ~BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL_AVG); } +enum nl80211_dump_station_phase { + NL80211_DUMP_STA_PHASE_AGGREGATED = 0, + NL80211_DUMP_STA_PHASE_PER_LINK = 1, +}; + struct nl80211_dump_station_ctx { int sta_idx; + int link_idx; + enum nl80211_dump_station_phase phase; + bool dump_link_stats; u8 mac_addr[ETH_ALEN]; struct station_info sinfo; }; +static int nl80211_put_link_station_payload(struct sk_buff *msg, + struct cfg80211_registered_device *rdev, + struct station_info *sinfo, + int link_idx) +{ + struct link_station_info *link_sinfo = sinfo->links[link_idx]; + struct nlattr *links, *link; + + if (WARN_ON_ONCE(!link_sinfo)) + return -ENOENT; + + if (!is_valid_ether_addr(link_sinfo->addr)) + return -EADDRNOTAVAIL; + + links = nla_nest_start(msg, NL80211_ATTR_MLO_LINKS); + if (!links) + return -EMSGSIZE; + + link = nla_nest_start(msg, link_idx + 1); + if (!link) + goto nla_put_failure; + + if (nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_idx) || + nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, link_sinfo->addr)) + goto nla_put_failure; + + if (nl80211_fill_link_station(msg, rdev, link_sinfo)) + goto nla_put_failure; + + nla_nest_end(msg, link); + nla_nest_end(msg, links); + return 0; + +nla_put_failure: + nla_nest_cancel(msg, links); + return -EMSGSIZE; +} + static int nl80211_dump_station(struct sk_buff *skb, struct netlink_callback *cb) { struct cfg80211_registered_device *rdev; struct wireless_dev *wdev; struct nl80211_dump_station_ctx *ctx = (void *)cb->args[2]; + struct nlattr **attrbuf __free(kfree) = NULL; int err; - err = nl80211_prepare_wdev_dump(cb, &rdev, &wdev, NULL); + if (!ctx) { + attrbuf = kzalloc_objs(*attrbuf, NUM_NL80211_ATTR); + if (!attrbuf) + return -ENOMEM; + } + + err = nl80211_prepare_wdev_dump(cb, &rdev, &wdev, attrbuf); if (err) return err; /* nl80211_prepare_wdev_dump acquired it in the successful case */ @@ -8490,6 +8543,9 @@ static int nl80211_dump_station(struct sk_buff *skb, err = -ENOMEM; goto out_err; } + ctx->phase = NL80211_DUMP_STA_PHASE_AGGREGATED; + ctx->dump_link_stats = + !!attrbuf[NL80211_ATTR_STA_DUMP_LINK_STATS]; cb->args[2] = (long)ctx; } @@ -8505,34 +8561,53 @@ static int nl80211_dump_station(struct sk_buff *skb, while (true) { void *hdr; + int ret; - memset(&ctx->sinfo, 0, sizeof(ctx->sinfo)); - for (int i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) { - ctx->sinfo.links[i] = - kzalloc_obj(*ctx->sinfo.links[0]); - if (!ctx->sinfo.links[i]) { - err = -ENOMEM; + /* AGGREGATED phase: fetch sinfo from driver once per station */ + if (ctx->phase == NL80211_DUMP_STA_PHASE_AGGREGATED) { + memset(&ctx->sinfo, 0, sizeof(ctx->sinfo)); + for (int i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) { + ctx->sinfo.links[i] = + kzalloc_obj(*ctx->sinfo.links[0]); + if (!ctx->sinfo.links[i]) { + err = -ENOMEM; + goto out_err_release; + } + } + + err = rdev_dump_station(rdev, wdev, ctx->sta_idx, + ctx->mac_addr, &ctx->sinfo); + if (err == -ENOENT) { + err = skb->len; goto out_err_release; } + if (err) + goto out_err_release; + + if (ctx->sinfo.valid_links) + cfg80211_sta_set_mld_sinfo(&ctx->sinfo); + } else { + /* PER_LINK phase: advance to next valid link */ + while (ctx->link_idx < IEEE80211_MLD_MAX_NUM_LINKS && + !(ctx->sinfo.valid_links & BIT(ctx->link_idx))) + ctx->link_idx++; + + if (ctx->link_idx >= IEEE80211_MLD_MAX_NUM_LINKS) { + cfg80211_sinfo_release_content(&ctx->sinfo); + ctx->sta_idx++; + ctx->phase = NL80211_DUMP_STA_PHASE_AGGREGATED; + continue; + } } - err = rdev_dump_station(rdev, wdev, ctx->sta_idx, - ctx->mac_addr, &ctx->sinfo); - if (err == -ENOENT) { - err = skb->len; - goto out_err_release; - } - if (err) - goto out_err_release; - - if (ctx->sinfo.valid_links) - cfg80211_sta_set_mld_sinfo(&ctx->sinfo); - + /* Build common header for both phases */ hdr = nl80211hdr_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, NLM_F_MULTI, NL80211_CMD_NEW_STATION); if (!hdr) { err = skb->len; + if (ctx->phase == NL80211_DUMP_STA_PHASE_PER_LINK) + goto out_err; goto out_err_release; } @@ -8546,18 +8621,49 @@ static int nl80211_dump_station(struct sk_buff *skb, ctx->sinfo.generation)) { genlmsg_cancel(skb, hdr); err = skb->len; + if (ctx->phase == NL80211_DUMP_STA_PHASE_PER_LINK) + goto out_err; goto out_err_release; } - if (nl80211_put_sta_info_common(skb, rdev, &ctx->sinfo)) { - genlmsg_cancel(skb, hdr); - err = skb->len; - goto out_err_release; - } + switch (ctx->phase) { + case NL80211_DUMP_STA_PHASE_AGGREGATED: + ret = nl80211_put_sta_info_common(skb, rdev, &ctx->sinfo); + if (ret) { + genlmsg_cancel(skb, hdr); + err = ret; + goto out_err_release; + } + genlmsg_end(skb, hdr); - genlmsg_end(skb, hdr); - cfg80211_sinfo_release_content(&ctx->sinfo); - ctx->sta_idx++; + if (ctx->dump_link_stats && ctx->sinfo.valid_links) { + ctx->phase = NL80211_DUMP_STA_PHASE_PER_LINK; + ctx->link_idx = 0; + } else { + cfg80211_sinfo_release_content(&ctx->sinfo); + ctx->sta_idx++; + } + break; + + case NL80211_DUMP_STA_PHASE_PER_LINK: + ret = nl80211_put_link_station_payload(skb, rdev, + &ctx->sinfo, + ctx->link_idx); + if (ret == -EMSGSIZE) { + genlmsg_cancel(skb, hdr); + err = skb->len; + goto out_err; + } + if (ret) { + /* skip invalid link, do not abort the dump */ + genlmsg_cancel(skb, hdr); + ctx->link_idx++; + continue; + } + genlmsg_end(skb, hdr); + ctx->link_idx++; + break; + } } out_err_release: From f9202a374ec34e767185cf44f44ed2fbdf6bf053 Mon Sep 17 00:00:00 2001 From: P Praneesh Date: Sun, 14 Jun 2026 10:47:35 +0530 Subject: [PATCH 0194/1433] wifi: cfg80211: support MAC address filtering in station dump for link stats Currently, when userspace requests station information with link statistics using NL80211_CMD_GET_STATION with the NL80211_ATTR_STA_DUMP_LINK_STATS flag, the kernel uses the .doit callback (nl80211_get_station) which sends a single netlink message. For MLO stations with multiple links, the link statistics can be large and may exceed the maximum netlink message size, causing the operation to fail with -EMSGSIZE. The .dumpit callback (nl80211_dump_station) already supports fragmentation across multiple netlink messages, making it suitable for handling large link statistics. However, it currently iterates over all stations on the interface, which is inefficient when userspace only wants information about a specific station. Add support for MAC address filtering in nl80211_dump_station to allow userspace to request fragmented link statistics for a specific station. When NL80211_ATTR_MAC is present in a dump request, cache the MAC address in the dump context and use rdev_get_station() to retrieve information for only that station, instead of iterating over all stations with rdev_dump_station(). This allows userspace tools (like iw) to use NL80211_CMD_GET_STATION with NLM_F_DUMP flag to retrieve complete link statistics for a specific station across multiple netlink messages, avoiding the message size limitation. Signed-off-by: P Praneesh Link: https://patch.msgid.link/20260614051739.3979947-6-praneesh.p@oss.qualcomm.com Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index be16a40132ff..242071ad10d6 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -8474,6 +8474,8 @@ struct nl80211_dump_station_ctx { int link_idx; enum nl80211_dump_station_phase phase; bool dump_link_stats; + bool filter_mac; + u8 filter_mac_addr[ETH_ALEN]; u8 mac_addr[ETH_ALEN]; struct station_info sinfo; }; @@ -8543,10 +8545,22 @@ static int nl80211_dump_station(struct sk_buff *skb, err = -ENOMEM; goto out_err; } + cb->args[2] = (long)ctx; ctx->phase = NL80211_DUMP_STA_PHASE_AGGREGATED; ctx->dump_link_stats = !!attrbuf[NL80211_ATTR_STA_DUMP_LINK_STATS]; - cb->args[2] = (long)ctx; + if (attrbuf[NL80211_ATTR_MAC]) { + const u8 *mac = nla_data(attrbuf[NL80211_ATTR_MAC]); + + if (!is_valid_ether_addr(mac)) { + kfree(ctx); + cb->args[2] = 0; + err = -EINVAL; + goto out_err; + } + ctx->filter_mac = true; + memcpy(ctx->filter_mac_addr, mac, ETH_ALEN); + } } if (!wdev->netdev && wdev->iftype != NL80211_IFTYPE_NAN) { @@ -8554,7 +8568,12 @@ static int nl80211_dump_station(struct sk_buff *skb, goto out_err; } - if (!rdev->ops->dump_station) { + if (ctx->filter_mac) { + if (!rdev->ops->get_station) { + err = -EOPNOTSUPP; + goto out_err; + } + } else if (!rdev->ops->dump_station) { err = -EOPNOTSUPP; goto out_err; } @@ -8575,8 +8594,22 @@ static int nl80211_dump_station(struct sk_buff *skb, } } - err = rdev_dump_station(rdev, wdev, ctx->sta_idx, - ctx->mac_addr, &ctx->sinfo); + if (ctx->filter_mac) { + if (ctx->sta_idx > 0) { + err = skb->len; + goto out_err_release; + } + err = rdev_get_station(rdev, wdev, + ctx->filter_mac_addr, + &ctx->sinfo); + if (!err) + memcpy(ctx->mac_addr, + ctx->filter_mac_addr, ETH_ALEN); + } else { + err = rdev_dump_station(rdev, wdev, ctx->sta_idx, + ctx->mac_addr, + &ctx->sinfo); + } if (err == -ENOENT) { err = skb->len; goto out_err_release; From cffd0d2ed5f2603ef147fc8b625ca84b4041f5bf Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Mon, 6 Jul 2026 20:37:56 +0800 Subject: [PATCH 0195/1433] wifi: mac80211_hwsim: clean up radio rhashtable on free mac80211_hwsim_free() removes each radio from hwsim_radios before calling mac80211_hwsim_del_radio(), but leaves the matching hwsim_radios_rht entry in place until the whole table is destroyed. Other radio removal paths remove both the list entry and data->rht while holding hwsim_radio_lock, before dropping the lock and deleting the radio. Do the same here so the all-radio cleanup path follows the same object visibility ordering. This helper is used while all radios are being torn down, either after callback users have already been unregistered or while module init is unwinding, so no hwsim_radios_generation update is needed. Assisted-by: Codex:gpt-5.5 Signed-off-by: Cen Zhang Link: https://patch.msgid.link/20260706123756.343818-1-zzzccc427@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/virtual/mac80211_hwsim_main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index 0dd8a6c85953..5c59c1a8e720 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -6274,6 +6274,8 @@ static void mac80211_hwsim_free(void) struct mac80211_hwsim_data, list))) { list_del(&data->list); + rhashtable_remove_fast(&hwsim_radios_rht, &data->rht, + hwsim_rht_params); spin_unlock_bh(&hwsim_radio_lock); mac80211_hwsim_del_radio(data, wiphy_name(data->hw->wiphy), NULL); From 158438cd6ad69d6dd7d871582c38baf22169fede Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Tue, 7 Jul 2026 00:18:22 +0800 Subject: [PATCH 0196/1433] wifi: mac80211_hwsim: avoid NULL skb in stop queue drain mac80211_hwsim_stop() drops any frames left in data->pending. The loop currently checks skb_queue_empty() and then dequeues separately. That split is racy with TX status handling, which can remove a pending frame under the queue lock. If the last entry is removed after the empty check, skb_dequeue() returns NULL and the stop path passes that NULL skb to ieee80211_free_txskb(). Use skb_dequeue() as the loop condition instead. The dequeue result is the object that stop owns and frees, and a concurrent status completion that empties the queue simply makes the loop terminate. Fixes: bd18de517923 ("mac80211_hwsim: drop pending frames on stop") Assisted-by: Codex:gpt-5.5 Signed-off-by: Cen Zhang Link: https://patch.msgid.link/20260706161822.921039-1-zzzccc427@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/virtual/mac80211_hwsim_main.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index 5c59c1a8e720..f66da1a343f1 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -2303,6 +2303,7 @@ static int mac80211_hwsim_start(struct ieee80211_hw *hw) static void mac80211_hwsim_stop(struct ieee80211_hw *hw, bool suspend) { struct mac80211_hwsim_data *data = hw->priv; + struct sk_buff *skb; int i; data->started = false; @@ -2310,8 +2311,8 @@ static void mac80211_hwsim_stop(struct ieee80211_hw *hw, bool suspend) for (i = 0; i < ARRAY_SIZE(data->link_data); i++) hrtimer_cancel(&data->link_data[i].beacon_timer); - while (!skb_queue_empty(&data->pending)) - ieee80211_free_txskb(hw, skb_dequeue(&data->pending)); + while ((skb = skb_dequeue(&data->pending))) + ieee80211_free_txskb(hw, skb); wiphy_dbg(hw->wiphy, "%s\n", __func__); } From ac798f757d6475dc6fee2ec899980d6740714596 Mon Sep 17 00:00:00 2001 From: Ilan Peer Date: Mon, 6 Jul 2026 22:29:31 +0300 Subject: [PATCH 0197/1433] wifi: mac80211: Route (Re)association req/response to per-STA queue An association request or response frame is generally delivered to the driver without a TX queue object. However, with drivers that do encryption offload and couple the key with a transmit queue, this means that the frames are not being encrypted. Fix this by routing the association frames to the management TXQ. This will allow the driver to set up the required resources before transmitting the association frame, e.g., set up keys etc. Signed-off-by: Ilan Peer Reviewed-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260706222925.febcc62f485c.Iff932880e4b98232cbf6ba405fbb90d650a85381@changeid Signed-off-by: Johannes Berg --- include/linux/ieee80211.h | 11 +++++++++++ net/mac80211/ieee80211_i.h | 4 +--- net/mac80211/tx.c | 7 +++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h index 084ad45aa2d8..26e674038865 100644 --- a/include/linux/ieee80211.h +++ b/include/linux/ieee80211.h @@ -556,6 +556,17 @@ static inline bool ieee80211_is_reassoc_resp(__le16 fc) cpu_to_le16(IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_REASSOC_RESP); } +/** + * ieee80211_is_assoc - check if (Re)association request/response frame + * @fc: frame control bytes in little-endian byteorder + * Return: whether or not the frame is an (re)association request or response + */ +static inline bool ieee80211_is_assoc(__le16 fc) +{ + return ieee80211_is_assoc_req(fc) || ieee80211_is_reassoc_req(fc) || + ieee80211_is_assoc_resp(fc) || ieee80211_is_reassoc_resp(fc); +} + /** * ieee80211_is_probe_req - check if IEEE80211_FTYPE_MGMT && IEEE80211_STYPE_PROBE_REQ * @fc: frame control bytes in little-endian byteorder diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 34a9ea8b6f85..d585820245dd 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -2470,9 +2470,7 @@ void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata, static inline bool ieee80211_require_encrypted_assoc(__le16 fc, struct sta_info *sta) { - return (sta && sta->sta.epp_peer && - (ieee80211_is_assoc_req(fc) || ieee80211_is_reassoc_req(fc) || - ieee80211_is_assoc_resp(fc) || ieee80211_is_reassoc_resp(fc))); + return sta && sta->sta.epp_peer && ieee80211_is_assoc(fc); } /* sta_out needs to be checked for ERR_PTR() before using */ diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index c13b209fad47..42cfd76850b8 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -1309,10 +1309,17 @@ static struct txq_info *ieee80211_get_txq(struct ieee80211_local *local, (info->control.flags & IEEE80211_TX_CTRL_PS_RESPONSE)) return NULL; + /* + * While (re)association request/response frames are not considered + * bufferable MMPDUs, use the TXQ abstraction for the transmission of + * these frames. This is specifically useful for drivers that might + * associate other resources with the TXQ, e.g., encryption keys etc. + */ if (!(info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) && unlikely(!ieee80211_is_data_present(hdr->frame_control))) { if ((!ieee80211_is_mgmt(hdr->frame_control) || ieee80211_is_bufferable_mmpdu(skb) || + ieee80211_is_assoc(hdr->frame_control) || vif->type == NL80211_IFTYPE_STATION || vif->type == NL80211_IFTYPE_NAN || vif->type == NL80211_IFTYPE_NAN_DATA) && From 6041421dd0876356794d49db00b65bb831066ad6 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Thu, 2 Jul 2026 04:44:14 +0000 Subject: [PATCH 0198/1433] ipv4: fib: Define fib_table_hash_lock under CONFIG_IP_MULTIPLE_TABLES. When CONFIG_IP_MULTIPLE_TABLES is disabled, fib_new_table() is fib_get_table(), and no new table is created. Let's move net->ipv4.fib_table_hash_lock under CONFIG_IP_MULTIPLE_TABLES. While at it, netns_ipv4_sysctl.rst is updated. Suggested-by: Ido Schimmel Signed-off-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260702044437.591864-2-kuniyu@google.com Signed-off-by: Paolo Abeni --- Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst | 1 + include/net/netns/ipv4.h | 2 +- net/ipv4/fib_frontend.c | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst b/Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst index 6dbd97d435e9..3dc03bff739e 100644 --- a/Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst +++ b/Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst @@ -22,6 +22,7 @@ struct_mutex ra_mutex struct_fib_rules_ops* rules_ops struct_fib_table fib_main struct_fib_table fib_default +spinlock_t fib_table_hash_lock unsigned_int fib_rules_require_fldissect bool fib_has_custom_rules bool fib_has_custom_local_routes diff --git a/include/net/netns/ipv4.h b/include/net/netns/ipv4.h index 59506320558a..cb7f8bf15671 100644 --- a/include/net/netns/ipv4.h +++ b/include/net/netns/ipv4.h @@ -118,6 +118,7 @@ struct netns_ipv4 { struct fib_rules_ops *rules_ops; struct fib_table __rcu *fib_main; struct fib_table __rcu *fib_default; + spinlock_t fib_table_hash_lock; unsigned int fib_rules_require_fldissect; bool fib_has_custom_rules; #endif @@ -127,7 +128,6 @@ struct netns_ipv4 { atomic_t fib_num_tclassid_users; #endif struct hlist_head *fib_table_hash; - spinlock_t fib_table_hash_lock; struct sock *fibnl; struct hlist_head *fib_info_hash; unsigned int fib_info_hash_bits; diff --git a/net/ipv4/fib_frontend.c b/net/ipv4/fib_frontend.c index a5e739d32d59..8a3dc04e8cac 100644 --- a/net/ipv4/fib_frontend.c +++ b/net/ipv4/fib_frontend.c @@ -1584,7 +1584,10 @@ static int __net_init ip_fib_net_init(struct net *net) net->ipv4.sysctl_fib_multipath_hash_fields = FIB_MULTIPATH_HASH_FIELD_DEFAULT_MASK; #endif + +#ifdef CONFIG_IP_MULTIPLE_TABLES spin_lock_init(&net->ipv4.fib_table_hash_lock); +#endif /* Avoid false sharing : Use at least a full cache line */ size = max_t(size_t, size, L1_CACHE_BYTES); From 99c13f8020df1b06f68044ec93649b0b50f37d79 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Thu, 2 Jul 2026 04:44:15 +0000 Subject: [PATCH 0199/1433] net: fib_rules: Destroy ops->lock in fib_rules_unregister(). Commit 8e133ba99cd8 ("net: fib_rules: Add fib_rules_ops.lock.") added mutex in struct fib_rules_ops, which is initialised in fib_rules_register(). Let's add paired mutex_destroy() in fib_rules_unregister(). Signed-off-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260702044437.591864-3-kuniyu@google.com Signed-off-by: Paolo Abeni --- net/core/fib_rules.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/core/fib_rules.c b/net/core/fib_rules.c index 22e5e5e1a9c4..7df216c17c67 100644 --- a/net/core/fib_rules.c +++ b/net/core/fib_rules.c @@ -203,6 +203,7 @@ void fib_rules_unregister(struct fib_rules_ops *ops) spin_unlock(&net->rules_mod_lock); fib_rules_cleanup_ops(ops); + mutex_destroy(&ops->lock); kfree_rcu(ops, rcu); } From 3bf4c5970ca8c4bcd8312a3e9d577894e438c3a1 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Wed, 1 Jul 2026 10:32:41 +0300 Subject: [PATCH 0200/1433] devlink: Update nested instance locking comment In commit [1] a comment about nested instance locking was updated. But there's another place where this is mentioned, so update that as well. [1] commit 0061b5199d7c ("devlink: Reverse locking order for nested instances") Signed-off-by: Cosmin Ratiu Reviewed-by: Carolina Jubran Reviewed-by: Jiri Pirko Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260701073254.754518-2-tariqt@nvidia.com Signed-off-by: Paolo Abeni --- Documentation/networking/devlink/index.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/networking/devlink/index.rst b/Documentation/networking/devlink/index.rst index 32f70879ddd0..4745148fecf4 100644 --- a/Documentation/networking/devlink/index.rst +++ b/Documentation/networking/devlink/index.rst @@ -31,10 +31,10 @@ sure to respect following rules: - Lock ordering should be maintained. If driver needs to take instance lock of both nested and parent instances at the same time, devlink - instance lock of the parent instance should be taken first, only then - instance lock of the nested instance could be taken. - - Driver should use object-specific helpers to setup the nested relationship - before registering the nested devlink instance: + instance lock of the nested instance should be taken first, only then + instance lock of the parent instance could be taken. + - Driver should use object-specific helpers to setup the + nested relationship: - ``devl_nested_devlink_set()`` - called to setup devlink -> nested devlink relationship (could be used for multiple nested instances). From 9f2d908cc34e5aa118aa341480faca2314b49438 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Wed, 1 Jul 2026 10:32:42 +0300 Subject: [PATCH 0201/1433] devlink: Add a helper for getting a nested-in instance Upcoming code will need to obtain references to locked nested-in devlink instances. Add a helper to lock, reference and return the nested-in instance. Signed-off-by: Cosmin Ratiu Reviewed-by: Carolina Jubran Reviewed-by: Jiri Pirko Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260701073254.754518-3-tariqt@nvidia.com Signed-off-by: Paolo Abeni --- net/devlink/core.c | 16 ++++++++++++++++ net/devlink/devl_internal.h | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/net/devlink/core.c b/net/devlink/core.c index fe9f6a0a67d5..ee26c50b4118 100644 --- a/net/devlink/core.c +++ b/net/devlink/core.c @@ -67,6 +67,22 @@ static void __devlink_rel_put(struct devlink_rel *rel) devlink_rel_free(rel); } +struct devlink *__must_check devlink_nested_in_get_lock(struct devlink *devlink) +{ + devl_assert_locked(devlink); + if (!devlink->rel) + return NULL; + devlink = devlinks_xa_get(devlink->rel->nested_in.devlink_index); + if (!devlink) + return NULL; + devl_lock(devlink); + if (devl_is_registered(devlink)) + return devlink; + devl_unlock(devlink); + devlink_put(devlink); + return NULL; +} + static void devlink_rel_nested_in_notify_work(struct work_struct *work) { struct devlink_rel *rel = container_of(work, struct devlink_rel, diff --git a/net/devlink/devl_internal.h b/net/devlink/devl_internal.h index e4e48ee2da5a..36dff282f9b0 100644 --- a/net/devlink/devl_internal.h +++ b/net/devlink/devl_internal.h @@ -136,6 +136,10 @@ typedef void devlink_rel_notify_cb_t(struct devlink *devlink, u32 obj_index); typedef void devlink_rel_cleanup_cb_t(struct devlink *devlink, u32 obj_index, u32 rel_index); +/* Returns the locked+referenced nested-in instance or NULL. */ +struct devlink *__must_check +devlink_nested_in_get_lock(struct devlink *devlink); + void devlink_rel_nested_in_clear(u32 rel_index); int devlink_rel_nested_in_add(u32 *rel_index, u32 devlink_index, u32 obj_index, devlink_rel_notify_cb_t *notify_cb, From e48abacd6e831450f6a45dde00809ae01cf1fc85 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Wed, 1 Jul 2026 10:32:43 +0300 Subject: [PATCH 0202/1433] devlink: Migrate from info->user_ptr to info->ctx Replace deprecated info->user_ptr[0]/[1] with a typed devlink_nl_ctx struct stored in info->ctx. The struct aliases the same union memory, so the migration is safe. There are no functionality changes here. Signed-off-by: Cosmin Ratiu Reviewed-by: Jiri Pirko Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260701073254.754518-4-tariqt@nvidia.com Signed-off-by: Paolo Abeni --- net/devlink/dev.c | 16 ++++++++-------- net/devlink/devl_internal.h | 13 +++++++++++++ net/devlink/dpipe.c | 14 +++++++------- net/devlink/health.c | 12 ++++++------ net/devlink/linecard.c | 4 ++-- net/devlink/netlink.c | 8 ++++---- net/devlink/param.c | 4 ++-- net/devlink/port.c | 18 +++++++++--------- net/devlink/rate.c | 8 ++++---- net/devlink/region.c | 6 +++--- net/devlink/resource.c | 14 +++++++++----- net/devlink/sb.c | 22 +++++++++++----------- net/devlink/trap.c | 12 ++++++------ 13 files changed, 84 insertions(+), 67 deletions(-) diff --git a/net/devlink/dev.c b/net/devlink/dev.c index 57b2b8f03543..bcf001554e84 100644 --- a/net/devlink/dev.c +++ b/net/devlink/dev.c @@ -222,7 +222,7 @@ static void devlink_notify(struct devlink *devlink, enum devlink_command cmd) int devlink_nl_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct sk_buff *msg; int err; @@ -519,7 +519,7 @@ devlink_nl_reload_actions_performed_snd(struct devlink *devlink, u32 actions_per int devlink_nl_reload_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; enum devlink_reload_action action; enum devlink_reload_limit limit; struct net *dest_net = NULL; @@ -683,7 +683,7 @@ static int devlink_nl_eswitch_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_eswitch_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct sk_buff *msg; int err; @@ -704,7 +704,7 @@ int devlink_nl_eswitch_get_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_eswitch_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const struct devlink_ops *ops = devlink->ops; enum devlink_eswitch_encap_mode encap_mode; u8 inline_mode; @@ -906,7 +906,7 @@ devlink_nl_info_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_info_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct sk_buff *msg; int err; @@ -1134,7 +1134,7 @@ int devlink_nl_flash_update_doit(struct sk_buff *skb, struct genl_info *info) { struct nlattr *nla_overwrite_mask, *nla_file_name; struct devlink_flash_update_params params = {}; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const char *file_name; u32 supported_params; int ret; @@ -1302,7 +1302,7 @@ devlink_nl_selftests_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_selftests_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct sk_buff *msg; int err; @@ -1372,7 +1372,7 @@ static const struct nla_policy devlink_selftest_nl_policy[DEVLINK_ATTR_SELFTEST_ int devlink_nl_selftests_run_doit(struct sk_buff *skb, struct genl_info *info) { struct nlattr *tb[DEVLINK_ATTR_SELFTEST_ID_MAX + 1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct nlattr *attrs, *selftests; struct sk_buff *msg; void *hdr; diff --git a/net/devlink/devl_internal.h b/net/devlink/devl_internal.h index 36dff282f9b0..52c8bf359dd4 100644 --- a/net/devlink/devl_internal.h +++ b/net/devlink/devl_internal.h @@ -151,6 +151,19 @@ int devlink_rel_devlink_handle_put(struct sk_buff *msg, struct devlink *devlink, bool *msg_updated); /* Netlink */ +struct devlink_nl_ctx { + struct devlink *devlink; + struct devlink_port *devlink_port; +}; + +static inline struct devlink_nl_ctx * +devlink_nl_ctx(struct genl_info *info) +{ + BUILD_BUG_ON(sizeof(struct devlink_nl_ctx) > + sizeof_field(struct genl_info, ctx)); + return (struct devlink_nl_ctx *)info->ctx; +} + enum devlink_multicast_groups { DEVLINK_MCGRP_CONFIG, }; diff --git a/net/devlink/dpipe.c b/net/devlink/dpipe.c index c8d4a4374ae1..08c7b66fc3e8 100644 --- a/net/devlink/dpipe.c +++ b/net/devlink/dpipe.c @@ -213,7 +213,7 @@ static int devlink_dpipe_tables_fill(struct genl_info *info, struct list_head *dpipe_tables, const char *table_name) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_dpipe_table *table; struct nlattr *tables_attr; struct sk_buff *skb = NULL; @@ -290,7 +290,7 @@ static int devlink_dpipe_tables_fill(struct genl_info *info, int devlink_nl_dpipe_table_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const char *table_name = NULL; if (info->attrs[DEVLINK_ATTR_DPIPE_TABLE_NAME]) @@ -478,7 +478,7 @@ int devlink_dpipe_entry_ctx_prepare(struct devlink_dpipe_dump_ctx *dump_ctx) if (!dump_ctx->hdr) goto nla_put_failure; - devlink = dump_ctx->info->user_ptr[0]; + devlink = devlink_nl_ctx(dump_ctx->info)->devlink; if (devlink_nl_put_handle(dump_ctx->skb, devlink)) goto nla_put_failure; dump_ctx->nest = nla_nest_start_noflag(dump_ctx->skb, @@ -563,7 +563,7 @@ static int devlink_dpipe_entries_fill(struct genl_info *info, int devlink_nl_dpipe_entries_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_dpipe_table *table; const char *table_name; @@ -650,7 +650,7 @@ static int devlink_dpipe_headers_fill(struct genl_info *info, struct devlink_dpipe_headers * dpipe_headers) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct nlattr *headers_attr; struct sk_buff *skb = NULL; struct nlmsghdr *nlh; @@ -713,7 +713,7 @@ static int devlink_dpipe_headers_fill(struct genl_info *info, int devlink_nl_dpipe_headers_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; if (!devlink->dpipe_headers) return -EOPNOTSUPP; @@ -747,7 +747,7 @@ static int devlink_dpipe_table_counters_set(struct devlink *devlink, int devlink_nl_dpipe_table_counters_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const char *table_name; bool counters_enable; diff --git a/net/devlink/health.c b/net/devlink/health.c index ea7a334e939b..8ce6cd399cb7 100644 --- a/net/devlink/health.c +++ b/net/devlink/health.c @@ -358,7 +358,7 @@ devlink_health_reporter_get_from_info(struct devlink *devlink, int devlink_nl_health_reporter_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; struct sk_buff *msg; int err; @@ -456,7 +456,7 @@ int devlink_nl_health_reporter_get_dumpit(struct sk_buff *skb, int devlink_nl_health_reporter_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; reporter = devlink_health_reporter_get_from_info(devlink, info); @@ -715,7 +715,7 @@ EXPORT_SYMBOL_GPL(devlink_health_reporter_state_update); int devlink_nl_health_reporter_recover_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; reporter = devlink_health_reporter_get_from_info(devlink, info); @@ -1157,7 +1157,7 @@ static int devlink_fmsg_dumpit(struct devlink_fmsg *fmsg, struct sk_buff *skb, int devlink_nl_health_reporter_diagnose_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; struct devlink_fmsg *fmsg; int err; @@ -1252,7 +1252,7 @@ int devlink_nl_health_reporter_dump_get_dumpit(struct sk_buff *skb, int devlink_nl_health_reporter_dump_clear_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; reporter = devlink_health_reporter_get_from_info(devlink, info); @@ -1269,7 +1269,7 @@ int devlink_nl_health_reporter_dump_clear_doit(struct sk_buff *skb, int devlink_nl_health_reporter_test_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; reporter = devlink_health_reporter_get_from_info(devlink, info); diff --git a/net/devlink/linecard.c b/net/devlink/linecard.c index 8315d35cb91d..fd18f2759770 100644 --- a/net/devlink/linecard.c +++ b/net/devlink/linecard.c @@ -171,7 +171,7 @@ void devlink_linecards_notify_unregister(struct devlink *devlink) int devlink_nl_linecard_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_linecard *linecard; struct sk_buff *msg; int err; @@ -371,7 +371,7 @@ static int devlink_linecard_type_unset(struct devlink_linecard *linecard, int devlink_nl_linecard_set_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_linecard *linecard; int err; diff --git a/net/devlink/netlink.c b/net/devlink/netlink.c index ae4afc739678..f0a857e286bc 100644 --- a/net/devlink/netlink.c +++ b/net/devlink/netlink.c @@ -252,18 +252,18 @@ static int __devlink_nl_pre_doit(struct sk_buff *skb, struct genl_info *info, if (IS_ERR(devlink)) return PTR_ERR(devlink); - info->user_ptr[0] = devlink; + devlink_nl_ctx(info)->devlink = devlink; if (flags & DEVLINK_NL_FLAG_NEED_PORT) { devlink_port = devlink_port_get_from_info(devlink, info); if (IS_ERR(devlink_port)) { err = PTR_ERR(devlink_port); goto unlock; } - info->user_ptr[1] = devlink_port; + devlink_nl_ctx(info)->devlink_port = devlink_port; } else if (flags & DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT) { devlink_port = devlink_port_get_from_info(devlink, info); if (!IS_ERR(devlink_port)) - info->user_ptr[1] = devlink_port; + devlink_nl_ctx(info)->devlink_port = devlink_port; } return 0; @@ -304,7 +304,7 @@ static void __devlink_nl_post_doit(struct sk_buff *skb, struct genl_info *info, bool dev_lock = flags & DEVLINK_NL_FLAG_NEED_DEV_LOCK; struct devlink *devlink; - devlink = info->user_ptr[0]; + devlink = devlink_nl_ctx(info)->devlink; devl_dev_unlock(devlink, dev_lock); devlink_put(devlink); } diff --git a/net/devlink/param.c b/net/devlink/param.c index 3e9d2e5750c2..1cc562a6ebfd 100644 --- a/net/devlink/param.c +++ b/net/devlink/param.c @@ -627,7 +627,7 @@ devlink_param_get_from_info(struct xarray *params, struct genl_info *info) int devlink_nl_param_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_param_item *param_item; struct sk_buff *msg; int err; @@ -728,7 +728,7 @@ static int __devlink_nl_cmd_param_set_doit(struct devlink *devlink, int devlink_nl_param_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; return __devlink_nl_cmd_param_set_doit(devlink, 0, &devlink->params, info, DEVLINK_CMD_PARAM_NEW); diff --git a/net/devlink/port.c b/net/devlink/port.c index 485029d43428..c268afefaed7 100644 --- a/net/devlink/port.c +++ b/net/devlink/port.c @@ -594,7 +594,7 @@ void devlink_ports_notify_unregister(struct devlink *devlink) int devlink_nl_port_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; struct sk_buff *msg; int err; @@ -830,7 +830,7 @@ static int devlink_port_function_set(struct devlink_port *port, int devlink_nl_port_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; int err; if (info->attrs[DEVLINK_ATTR_PORT_TYPE]) { @@ -856,8 +856,8 @@ int devlink_nl_port_set_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_port_split_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; u32 count; if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_PORT_SPLIT_COUNT)) @@ -887,8 +887,8 @@ int devlink_nl_port_split_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_port_unsplit_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; if (!devlink_port->ops->port_unsplit) return -EOPNOTSUPP; @@ -899,7 +899,7 @@ int devlink_nl_port_new_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; struct devlink_port_new_attrs new_attrs = {}; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_port *devlink_port; struct sk_buff *msg; int err; @@ -961,9 +961,9 @@ int devlink_nl_port_new_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_port_del_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; if (!devlink_port->ops->port_del) return -EOPNOTSUPP; diff --git a/net/devlink/rate.c b/net/devlink/rate.c index 533d21b028a7..630441e429b3 100644 --- a/net/devlink/rate.c +++ b/net/devlink/rate.c @@ -239,7 +239,7 @@ int devlink_nl_rate_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb) int devlink_nl_rate_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_rate *devlink_rate; struct sk_buff *msg; int err; @@ -588,7 +588,7 @@ static bool devlink_rate_set_ops_supported(const struct devlink_ops *ops, int devlink_nl_rate_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_rate *devlink_rate; const struct devlink_ops *ops; int err; @@ -610,7 +610,7 @@ int devlink_nl_rate_set_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_rate_new_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_rate *rate_node; const struct devlink_ops *ops; int err; @@ -666,7 +666,7 @@ int devlink_nl_rate_new_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_rate_del_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_rate *rate_node; int err; diff --git a/net/devlink/region.c b/net/devlink/region.c index 5588e3d560b9..537779bbff07 100644 --- a/net/devlink/region.c +++ b/net/devlink/region.c @@ -469,7 +469,7 @@ static void devlink_region_snapshot_del(struct devlink_region *region, int devlink_nl_region_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_port *port = NULL; struct devlink_region *region; const char *region_name; @@ -588,7 +588,7 @@ int devlink_nl_region_get_dumpit(struct sk_buff *skb, int devlink_nl_region_del_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_snapshot *snapshot; struct devlink_port *port = NULL; struct devlink_region *region; @@ -633,7 +633,7 @@ int devlink_nl_region_del_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_region_new_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_snapshot *snapshot; struct devlink_port *port = NULL; struct nlattr *snapshot_id_attr; diff --git a/net/devlink/resource.c b/net/devlink/resource.c index 574108ccfe5d..c3cfda7ea070 100644 --- a/net/devlink/resource.c +++ b/net/devlink/resource.c @@ -117,7 +117,7 @@ devlink_resource_validate_size(struct devlink_resource *resource, u64 size, int devlink_nl_resource_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_resource *resource; u64 resource_id; u64 size; @@ -251,8 +251,9 @@ static int devlink_resource_list_fill(struct sk_buff *skb, static int devlink_resource_fill(struct genl_info *info, enum devlink_command cmd, int flags) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_nl_ctx *ctx = devlink_nl_ctx(info); + struct devlink *devlink = ctx->devlink; + struct devlink_port *devlink_port; struct devlink_resource *resource; struct list_head *resource_list; struct nlattr *resources_attr; @@ -263,6 +264,7 @@ static int devlink_resource_fill(struct genl_info *info, int i; int err; + devlink_port = ctx->devlink_port; resource_list = devlink_port ? &devlink_port->resource_list : &devlink->resource_list; resource = list_first_entry(resource_list, @@ -326,10 +328,12 @@ static int devlink_resource_fill(struct genl_info *info, int devlink_nl_resource_dump_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_nl_ctx *ctx = devlink_nl_ctx(info); + struct devlink *devlink = ctx->devlink; + struct devlink_port *devlink_port; struct list_head *resource_list; + devlink_port = ctx->devlink_port; if (info->attrs[DEVLINK_ATTR_PORT_INDEX] && !devlink_port) return -ENODEV; diff --git a/net/devlink/sb.c b/net/devlink/sb.c index 49fcbfe08f15..129bd016e302 100644 --- a/net/devlink/sb.c +++ b/net/devlink/sb.c @@ -204,7 +204,7 @@ static int devlink_nl_sb_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_sb_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_sb *devlink_sb; struct sk_buff *msg; int err; @@ -306,7 +306,7 @@ static int devlink_nl_sb_pool_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_sb_pool_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_sb *devlink_sb; struct sk_buff *msg; u16 pool_index; @@ -415,7 +415,7 @@ static int devlink_sb_pool_set(struct devlink *devlink, unsigned int sb_index, int devlink_nl_sb_pool_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; enum devlink_sb_threshold_type threshold_type; struct devlink_sb *devlink_sb; u16 pool_index; @@ -506,7 +506,7 @@ static int devlink_nl_sb_port_pool_fill(struct sk_buff *msg, int devlink_nl_sb_port_pool_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; struct devlink *devlink = devlink_port->devlink; struct devlink_sb *devlink_sb; struct sk_buff *msg; @@ -624,8 +624,8 @@ static int devlink_sb_port_pool_set(struct devlink_port *devlink_port, int devlink_nl_sb_port_pool_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_sb *devlink_sb; u16 pool_index; u32 threshold; @@ -716,7 +716,7 @@ devlink_nl_sb_tc_pool_bind_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_sb_tc_pool_bind_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; struct devlink *devlink = devlink_port->devlink; struct devlink_sb *devlink_sb; struct sk_buff *msg; @@ -864,8 +864,8 @@ static int devlink_sb_tc_pool_bind_set(struct devlink_port *devlink_port, int devlink_nl_sb_tc_pool_bind_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; enum devlink_sb_pool_type pool_type; struct devlink_sb *devlink_sb; u16 tc_index; @@ -902,7 +902,7 @@ int devlink_nl_sb_tc_pool_bind_set_doit(struct sk_buff *skb, int devlink_nl_sb_occ_snapshot_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const struct devlink_ops *ops = devlink->ops; struct devlink_sb *devlink_sb; @@ -918,7 +918,7 @@ int devlink_nl_sb_occ_snapshot_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_sb_occ_max_clear_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const struct devlink_ops *ops = devlink->ops; struct devlink_sb *devlink_sb; diff --git a/net/devlink/trap.c b/net/devlink/trap.c index 8edb31654a68..793ffc66dc11 100644 --- a/net/devlink/trap.c +++ b/net/devlink/trap.c @@ -302,7 +302,7 @@ static int devlink_nl_trap_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_trap_get_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_trap_item *trap_item; struct sk_buff *msg; int err; @@ -412,7 +412,7 @@ static int devlink_trap_action_set(struct devlink *devlink, int devlink_nl_trap_set_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_trap_item *trap_item; if (list_empty(&devlink->trap_list)) @@ -511,7 +511,7 @@ devlink_nl_trap_group_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_trap_group_get_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_trap_group_item *group_item; struct sk_buff *msg; int err; @@ -682,7 +682,7 @@ static int devlink_trap_group_set(struct devlink *devlink, int devlink_nl_trap_group_set_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_trap_group_item *group_item; bool modified = false; int err; @@ -804,7 +804,7 @@ int devlink_nl_trap_policer_get_doit(struct sk_buff *skb, { struct devlink_trap_policer_item *policer_item; struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct sk_buff *msg; int err; @@ -924,7 +924,7 @@ int devlink_nl_trap_policer_set_doit(struct sk_buff *skb, { struct devlink_trap_policer_item *policer_item; struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; if (list_empty(&devlink->trap_policer_list)) return -EOPNOTSUPP; From db078bc2b03157648ef901f4f78a23586777b184 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Wed, 1 Jul 2026 10:32:44 +0300 Subject: [PATCH 0203/1433] devlink: Decouple rate storage from associated devlink object Devlink rate leafs and nodes were stored in their respective devlink objects pointed to by devlink_rate->devlink. This patch removes that association by introducing the concept of 'rate node devlink', which is where all rates that could link to each other are stored. For now this is the same as devlink_rate->devlink. After this patch, the devlink rates stored in this devlink instance could potentially be from multiple other devlink instances. So all rate node manipulation code was updated to: - correctly compare the actual devlink object during iteration. - maybe acquire additional locks (noop for now). Signed-off-by: Cosmin Ratiu Reviewed-by: Carolina Jubran Reviewed-by: Jiri Pirko Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260701073254.754518-5-tariqt@nvidia.com Signed-off-by: Paolo Abeni --- net/devlink/rate.c | 247 ++++++++++++++++++++++++++++++++------------- 1 file changed, 176 insertions(+), 71 deletions(-) diff --git a/net/devlink/rate.c b/net/devlink/rate.c index 630441e429b3..295f4185fdfd 100644 --- a/net/devlink/rate.c +++ b/net/devlink/rate.c @@ -30,13 +30,25 @@ devlink_rate_leaf_get_from_info(struct devlink *devlink, struct genl_info *info) return devlink_rate ?: ERR_PTR(-ENODEV); } +static struct devlink *devl_rate_lock(struct devlink *devlink) +{ + return devlink; +} + +static void devl_rate_unlock(struct devlink *devlink, + struct devlink *rate_devlink) +{ +} + static struct devlink_rate * -devlink_rate_node_get_by_name(struct devlink *devlink, const char *node_name) +devlink_rate_node_get_by_name(struct devlink *rate_devlink, + struct devlink *devlink, const char *node_name) { struct devlink_rate *devlink_rate; - list_for_each_entry(devlink_rate, &devlink->rate_list, list) { - if (devlink_rate_is_node(devlink_rate) && + list_for_each_entry(devlink_rate, &rate_devlink->rate_list, list) { + if (devlink_rate->devlink == devlink && + devlink_rate_is_node(devlink_rate) && !strcmp(node_name, devlink_rate->name)) return devlink_rate; } @@ -44,7 +56,8 @@ devlink_rate_node_get_by_name(struct devlink *devlink, const char *node_name) } static struct devlink_rate * -devlink_rate_node_get_from_attrs(struct devlink *devlink, struct nlattr **attrs) +devlink_rate_node_get_from_attrs(struct devlink *rate_devlink, + struct devlink *devlink, struct nlattr **attrs) { const char *rate_node_name; size_t len; @@ -57,24 +70,30 @@ devlink_rate_node_get_from_attrs(struct devlink *devlink, struct nlattr **attrs) if (!len || strspn(rate_node_name, "0123456789") == len) return ERR_PTR(-EINVAL); - return devlink_rate_node_get_by_name(devlink, rate_node_name); + return devlink_rate_node_get_by_name(rate_devlink, devlink, + rate_node_name); } static struct devlink_rate * -devlink_rate_node_get_from_info(struct devlink *devlink, struct genl_info *info) +devlink_rate_node_get_from_info(struct devlink *rate_devlink, + struct devlink *devlink, + struct genl_info *info) { - return devlink_rate_node_get_from_attrs(devlink, info->attrs); + return devlink_rate_node_get_from_attrs(rate_devlink, devlink, + info->attrs); } static struct devlink_rate * -devlink_rate_get_from_info(struct devlink *devlink, struct genl_info *info) +devlink_rate_get_from_info(struct devlink *rate_devlink, + struct devlink *devlink, struct genl_info *info) { struct nlattr **attrs = info->attrs; if (attrs[DEVLINK_ATTR_PORT_INDEX]) return devlink_rate_leaf_get_from_info(devlink, info); else if (attrs[DEVLINK_ATTR_RATE_NODE_NAME]) - return devlink_rate_node_get_from_info(devlink, info); + return devlink_rate_node_get_from_info(rate_devlink, devlink, + info); else return ERR_PTR(-EINVAL); } @@ -190,17 +209,25 @@ static void devlink_rate_notify(struct devlink_rate *devlink_rate, void devlink_rates_notify_register(struct devlink *devlink) { struct devlink_rate *rate_node; + struct devlink *rate_devlink; - list_for_each_entry(rate_node, &devlink->rate_list, list) - devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_NEW); + rate_devlink = devl_rate_lock(devlink); + list_for_each_entry(rate_node, &rate_devlink->rate_list, list) + if (rate_node->devlink == devlink) + devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_NEW); + devl_rate_unlock(devlink, rate_devlink); } void devlink_rates_notify_unregister(struct devlink *devlink) { struct devlink_rate *rate_node; + struct devlink *rate_devlink; - list_for_each_entry_reverse(rate_node, &devlink->rate_list, list) - devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_DEL); + rate_devlink = devl_rate_lock(devlink); + list_for_each_entry_reverse(rate_node, &rate_devlink->rate_list, list) + if (rate_node->devlink == devlink) + devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_DEL); + devl_rate_unlock(devlink, rate_devlink); } static int @@ -209,17 +236,20 @@ devlink_nl_rate_get_dump_one(struct sk_buff *msg, struct devlink *devlink, { struct devlink_nl_dump_state *state = devlink_dump_state(cb); struct devlink_rate *devlink_rate; + struct devlink *rate_devlink; int idx = 0; int err = 0; - list_for_each_entry(devlink_rate, &devlink->rate_list, list) { + rate_devlink = devl_rate_lock(devlink); + list_for_each_entry(devlink_rate, &rate_devlink->rate_list, list) { enum devlink_command cmd = DEVLINK_CMD_RATE_NEW; u32 id = NETLINK_CB(cb->skb).portid; - if (idx < state->idx) { + if (idx < state->idx || devlink_rate->devlink != devlink) { idx++; continue; } + err = devlink_nl_rate_fill(msg, devlink_rate, cmd, id, cb->nlh->nlmsg_seq, flags, NULL); if (err) { @@ -228,6 +258,7 @@ devlink_nl_rate_get_dump_one(struct sk_buff *msg, struct devlink *devlink, } idx++; } + devl_rate_unlock(devlink, rate_devlink); return err; } @@ -239,28 +270,38 @@ int devlink_nl_rate_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb) int devlink_nl_rate_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = devlink_nl_ctx(info)->devlink; + struct devlink *rate_devlink, *devlink = devlink_nl_ctx(info)->devlink; struct devlink_rate *devlink_rate; struct sk_buff *msg; int err; - devlink_rate = devlink_rate_get_from_info(devlink, info); - if (IS_ERR(devlink_rate)) - return PTR_ERR(devlink_rate); + rate_devlink = devl_rate_lock(devlink); + devlink_rate = devlink_rate_get_from_info(rate_devlink, devlink, info); + if (IS_ERR(devlink_rate)) { + err = PTR_ERR(devlink_rate); + goto unlock; + } msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); - if (!msg) - return -ENOMEM; + if (!msg) { + err = -ENOMEM; + goto unlock; + } err = devlink_nl_rate_fill(msg, devlink_rate, DEVLINK_CMD_RATE_NEW, info->snd_portid, info->snd_seq, 0, info->extack); - if (err) { - nlmsg_free(msg); - return err; - } + if (err) + goto err_fill; + devl_rate_unlock(devlink, rate_devlink); return genlmsg_reply(msg, info); + +err_fill: + nlmsg_free(msg); +unlock: + devl_rate_unlock(devlink, rate_devlink); + return err; } static bool @@ -277,6 +318,7 @@ devlink_rate_is_parent_node(struct devlink_rate *devlink_rate, static int devlink_nl_rate_parent_node_set(struct devlink_rate *devlink_rate, + struct devlink *rate_devlink, struct genl_info *info, struct nlattr *nla_parent) { @@ -304,7 +346,8 @@ devlink_nl_rate_parent_node_set(struct devlink_rate *devlink_rate, refcount_dec(&parent->refcnt); devlink_rate->parent = NULL; } else if (len) { - parent = devlink_rate_node_get_by_name(devlink, parent_name); + parent = devlink_rate_node_get_by_name(rate_devlink, devlink, + parent_name); if (IS_ERR(parent)) return -ENODEV; @@ -423,6 +466,7 @@ static int devlink_nl_rate_tc_bw_set(struct devlink_rate *devlink_rate, } static int devlink_nl_rate_set(struct devlink_rate *devlink_rate, + struct devlink *rate_devlink, const struct devlink_ops *ops, struct genl_info *info) { @@ -497,7 +541,8 @@ static int devlink_nl_rate_set(struct devlink_rate *devlink_rate, */ nla_parent = attrs[DEVLINK_ATTR_RATE_PARENT_NODE_NAME]; if (nla_parent) { - err = devlink_nl_rate_parent_node_set(devlink_rate, info, + err = devlink_nl_rate_parent_node_set(devlink_rate, + rate_devlink, info, nla_parent); if (err) return err; @@ -588,29 +633,37 @@ static bool devlink_rate_set_ops_supported(const struct devlink_ops *ops, int devlink_nl_rate_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = devlink_nl_ctx(info)->devlink; + struct devlink *rate_devlink, *devlink = devlink_nl_ctx(info)->devlink; struct devlink_rate *devlink_rate; const struct devlink_ops *ops; int err; - devlink_rate = devlink_rate_get_from_info(devlink, info); - if (IS_ERR(devlink_rate)) - return PTR_ERR(devlink_rate); + rate_devlink = devl_rate_lock(devlink); + devlink_rate = devlink_rate_get_from_info(rate_devlink, devlink, info); + if (IS_ERR(devlink_rate)) { + err = PTR_ERR(devlink_rate); + goto unlock; + } ops = devlink->ops; - if (!ops || !devlink_rate_set_ops_supported(ops, info, devlink_rate->type)) - return -EOPNOTSUPP; + if (!ops || + !devlink_rate_set_ops_supported(ops, info, devlink_rate->type)) { + err = -EOPNOTSUPP; + goto unlock; + } - err = devlink_nl_rate_set(devlink_rate, ops, info); + err = devlink_nl_rate_set(devlink_rate, rate_devlink, ops, info); if (!err) devlink_rate_notify(devlink_rate, DEVLINK_CMD_RATE_NEW); +unlock: + devl_rate_unlock(devlink, rate_devlink); return err; } int devlink_nl_rate_new_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = devlink_nl_ctx(info)->devlink; + struct devlink *rate_devlink, *devlink = devlink_nl_ctx(info)->devlink; struct devlink_rate *rate_node; const struct devlink_ops *ops; int err; @@ -624,15 +677,22 @@ int devlink_nl_rate_new_doit(struct sk_buff *skb, struct genl_info *info) if (!devlink_rate_set_ops_supported(ops, info, DEVLINK_RATE_TYPE_NODE)) return -EOPNOTSUPP; - rate_node = devlink_rate_node_get_from_attrs(devlink, info->attrs); - if (!IS_ERR(rate_node)) - return -EEXIST; - else if (rate_node == ERR_PTR(-EINVAL)) - return -EINVAL; + rate_devlink = devl_rate_lock(devlink); + rate_node = devlink_rate_node_get_from_attrs(rate_devlink, devlink, + info->attrs); + if (!IS_ERR(rate_node)) { + err = -EEXIST; + goto unlock; + } else if (rate_node == ERR_PTR(-EINVAL)) { + err = -EINVAL; + goto unlock; + } rate_node = kzalloc_obj(*rate_node); - if (!rate_node) - return -ENOMEM; + if (!rate_node) { + err = -ENOMEM; + goto unlock; + } rate_node->devlink = devlink; rate_node->type = DEVLINK_RATE_TYPE_NODE; @@ -646,13 +706,14 @@ int devlink_nl_rate_new_doit(struct sk_buff *skb, struct genl_info *info) if (err) goto err_node_new; - err = devlink_nl_rate_set(rate_node, ops, info); + err = devlink_nl_rate_set(rate_node, rate_devlink, ops, info); if (err) goto err_rate_set; refcount_set(&rate_node->refcnt, 1); - list_add(&rate_node->list, &devlink->rate_list); + list_add(&rate_node->list, &rate_devlink->rate_list); devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_NEW); + devl_rate_unlock(devlink, rate_devlink); return 0; err_rate_set: @@ -661,22 +722,29 @@ int devlink_nl_rate_new_doit(struct sk_buff *skb, struct genl_info *info) kfree(rate_node->name); err_strdup: kfree(rate_node); +unlock: + devl_rate_unlock(devlink, rate_devlink); return err; } int devlink_nl_rate_del_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = devlink_nl_ctx(info)->devlink; + struct devlink *rate_devlink, *devlink = devlink_nl_ctx(info)->devlink; struct devlink_rate *rate_node; int err; - rate_node = devlink_rate_node_get_from_info(devlink, info); - if (IS_ERR(rate_node)) - return PTR_ERR(rate_node); + rate_devlink = devl_rate_lock(devlink); + rate_node = devlink_rate_node_get_from_info(rate_devlink, devlink, + info); + if (IS_ERR(rate_node)) { + err = PTR_ERR(rate_node); + goto unlock; + } if (refcount_read(&rate_node->refcnt) > 1) { NL_SET_ERR_MSG(info->extack, "Node has children. Cannot delete node."); - return -EBUSY; + err = -EBUSY; + goto unlock; } devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_DEL); @@ -687,6 +755,8 @@ int devlink_nl_rate_del_doit(struct sk_buff *skb, struct genl_info *info) list_del(&rate_node->list); kfree(rate_node->name); kfree(rate_node); +unlock: + devl_rate_unlock(devlink, rate_devlink); return err; } @@ -695,14 +765,20 @@ int devlink_rates_check(struct devlink *devlink, struct netlink_ext_ack *extack) { struct devlink_rate *devlink_rate; + struct devlink *rate_devlink; + int err = 0; - list_for_each_entry(devlink_rate, &devlink->rate_list, list) - if (!rate_filter || rate_filter(devlink_rate)) { + rate_devlink = devl_rate_lock(devlink); + list_for_each_entry(devlink_rate, &rate_devlink->rate_list, list) + if (devlink_rate->devlink == devlink && + (!rate_filter || rate_filter(devlink_rate))) { if (extack) NL_SET_ERR_MSG(extack, "Rate node(s) exists."); - return -EBUSY; + err = -EBUSY; + break; } - return 0; + devl_rate_unlock(devlink, rate_devlink); + return err; } /** @@ -719,14 +795,21 @@ devl_rate_node_create(struct devlink *devlink, void *priv, char *node_name, struct devlink_rate *parent) { struct devlink_rate *rate_node; + struct devlink *rate_devlink; - rate_node = devlink_rate_node_get_by_name(devlink, node_name); - if (!IS_ERR(rate_node)) - return ERR_PTR(-EEXIST); + rate_devlink = devl_rate_lock(devlink); + rate_node = devlink_rate_node_get_by_name(rate_devlink, devlink, + node_name); + if (!IS_ERR(rate_node)) { + rate_node = ERR_PTR(-EEXIST); + goto unlock; + } rate_node = kzalloc_obj(*rate_node); - if (!rate_node) - return ERR_PTR(-ENOMEM); + if (!rate_node) { + rate_node = ERR_PTR(-ENOMEM); + goto unlock; + } rate_node->type = DEVLINK_RATE_TYPE_NODE; rate_node->devlink = devlink; @@ -735,7 +818,8 @@ devl_rate_node_create(struct devlink *devlink, void *priv, char *node_name, rate_node->name = kstrdup(node_name, GFP_KERNEL); if (!rate_node->name) { kfree(rate_node); - return ERR_PTR(-ENOMEM); + rate_node = ERR_PTR(-ENOMEM); + goto unlock; } if (parent) { @@ -744,8 +828,10 @@ devl_rate_node_create(struct devlink *devlink, void *priv, char *node_name, } refcount_set(&rate_node->refcnt, 1); - list_add(&rate_node->list, &devlink->rate_list); + list_add(&rate_node->list, &rate_devlink->rate_list); devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_NEW); +unlock: + devl_rate_unlock(devlink, rate_devlink); return rate_node; } EXPORT_SYMBOL_GPL(devl_rate_node_create); @@ -761,10 +847,10 @@ EXPORT_SYMBOL_GPL(devl_rate_node_create); int devl_rate_leaf_create(struct devlink_port *devlink_port, void *priv, struct devlink_rate *parent) { - struct devlink *devlink = devlink_port->devlink; + struct devlink *rate_devlink, *devlink = devlink_port->devlink; struct devlink_rate *devlink_rate; - devl_assert_locked(devlink_port->devlink); + devl_assert_locked(devlink); if (WARN_ON(devlink_port->devlink_rate)) return -EBUSY; @@ -773,6 +859,7 @@ int devl_rate_leaf_create(struct devlink_port *devlink_port, void *priv, if (!devlink_rate) return -ENOMEM; + rate_devlink = devl_rate_lock(devlink); if (parent) { devlink_rate->parent = parent; refcount_inc(&devlink_rate->parent->refcnt); @@ -782,9 +869,10 @@ int devl_rate_leaf_create(struct devlink_port *devlink_port, void *priv, devlink_rate->devlink = devlink; devlink_rate->devlink_port = devlink_port; devlink_rate->priv = priv; - list_add_tail(&devlink_rate->list, &devlink->rate_list); + list_add_tail(&devlink_rate->list, &rate_devlink->rate_list); devlink_port->devlink_rate = devlink_rate; devlink_rate_notify(devlink_rate, DEVLINK_CMD_RATE_NEW); + devl_rate_unlock(devlink, rate_devlink); return 0; } @@ -800,16 +888,19 @@ EXPORT_SYMBOL_GPL(devl_rate_leaf_create); void devl_rate_leaf_destroy(struct devlink_port *devlink_port) { struct devlink_rate *devlink_rate = devlink_port->devlink_rate; + struct devlink *rate_devlink, *devlink = devlink_port->devlink; - devl_assert_locked(devlink_port->devlink); + devl_assert_locked(devlink); if (!devlink_rate) return; + rate_devlink = devl_rate_lock(devlink); devlink_rate_notify(devlink_rate, DEVLINK_CMD_RATE_DEL); if (devlink_rate->parent) refcount_dec(&devlink_rate->parent->refcnt); list_del(&devlink_rate->list); devlink_port->devlink_rate = NULL; + devl_rate_unlock(devlink, rate_devlink); kfree(devlink_rate); } EXPORT_SYMBOL_GPL(devl_rate_leaf_destroy); @@ -818,20 +909,30 @@ EXPORT_SYMBOL_GPL(devl_rate_leaf_destroy); * devl_rate_nodes_destroy - destroy all devlink rate nodes on device * @devlink: devlink instance * - * Unset parent for all rate objects and destroy all rate nodes - * on specified device. + * Unset parent for all rate objects involving this device and destroy all rate + * nodes on it. */ void devl_rate_nodes_destroy(struct devlink *devlink) { - const struct devlink_ops *ops = devlink->ops; struct devlink_rate *devlink_rate, *tmp; + const struct devlink_ops *ops; + struct devlink *rate_devlink; devl_assert_locked(devlink); + rate_devlink = devl_rate_lock(devlink); - list_for_each_entry(devlink_rate, &devlink->rate_list, list) { - if (!devlink_rate->parent) + list_for_each_entry(devlink_rate, &rate_devlink->rate_list, list) { + if (!devlink_rate->parent || + (devlink_rate->devlink != devlink && + devlink_rate->parent->devlink != devlink)) continue; + /* This could destroy rate objects on other devlinks in the + * same hierarchy under 'rate_devlink'. This is safe because + * the shared common ancestor is locked so there can be no + * other concurrent rate operations on devlink_rate->devlink. + */ + ops = devlink_rate->devlink->ops; if (devlink_rate_is_leaf(devlink_rate)) ops->rate_leaf_parent_set(devlink_rate, NULL, devlink_rate->priv, NULL, NULL); @@ -842,13 +943,17 @@ void devl_rate_nodes_destroy(struct devlink *devlink) refcount_dec(&devlink_rate->parent->refcnt); devlink_rate->parent = NULL; } - list_for_each_entry_safe(devlink_rate, tmp, &devlink->rate_list, list) { - if (devlink_rate_is_node(devlink_rate)) { + ops = devlink->ops; + list_for_each_entry_safe(devlink_rate, tmp, &rate_devlink->rate_list, + list) { + if (devlink_rate->devlink == devlink && + devlink_rate_is_node(devlink_rate)) { ops->rate_node_del(devlink_rate, devlink_rate->priv, NULL); list_del(&devlink_rate->list); kfree(devlink_rate->name); kfree(devlink_rate); } } + devl_rate_unlock(devlink, rate_devlink); } EXPORT_SYMBOL_GPL(devl_rate_nodes_destroy); From b5f90fd4580ce71aa24ac9afcf5c9b4fa8121518 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Wed, 1 Jul 2026 10:32:45 +0300 Subject: [PATCH 0204/1433] devlink: Add parent dev to devlink API Upcoming changes to the rate commands need the parent devlink specified. This change adds a nested 'parent-dev' attribute to the API and helpers to obtain and put a reference to the parent devlink instance in info->ctx. To avoid deadlocks, the parent devlink is unlocked before obtaining the main devlink instance that is the target of the request. A reference to the parent is kept until the end of the request to avoid it suddenly disappearing. This means that this reference is of limited use without additional protection. Signed-off-by: Cosmin Ratiu Reviewed-by: Carolina Jubran Reviewed-by: Jiri Pirko Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260701073254.754518-6-tariqt@nvidia.com Signed-off-by: Paolo Abeni --- Documentation/netlink/specs/devlink.yaml | 20 +++++++++++++ include/uapi/linux/devlink.h | 2 ++ net/devlink/devl_internal.h | 3 ++ net/devlink/netlink.c | 36 ++++++++++++++++++++---- 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/Documentation/netlink/specs/devlink.yaml b/Documentation/netlink/specs/devlink.yaml index 52ad1e7805d1..13d960b3abb1 100644 --- a/Documentation/netlink/specs/devlink.yaml +++ b/Documentation/netlink/specs/devlink.yaml @@ -895,6 +895,16 @@ attribute-sets: resource-dump response. Bit 0 (dev) selects device-level resources; bit 1 (port) selects port-level resources. When absent all classes are returned. + - + name: parent-dev + type: nest + nested-attributes: dl-parent-dev + doc: | + Identifies the devlink instance which owns the parent rate node. + Used with rate-set and rate-new to parent a rate object to a node on + a different devlink instance, enabling cross-device rate scheduling. + When absent, the parent node is resolved on the same instance. + - name: dl-dev-stats subset-of: devlink @@ -1317,6 +1327,16 @@ attribute-sets: Specifies the bandwidth share assigned to the Traffic Class. The bandwidth for the traffic class is determined in proportion to the sum of the shares of all configured classes. + - + name: dl-parent-dev + subset-of: devlink + attributes: + - + name: bus-name + - + name: dev-name + - + name: index operations: enum-model: directional diff --git a/include/uapi/linux/devlink.h b/include/uapi/linux/devlink.h index ca713bcc47b9..a6801feb7744 100644 --- a/include/uapi/linux/devlink.h +++ b/include/uapi/linux/devlink.h @@ -648,6 +648,8 @@ enum devlink_attr { DEVLINK_ATTR_INDEX, /* uint */ DEVLINK_ATTR_RESOURCE_SCOPE_MASK, /* u32 */ + DEVLINK_ATTR_PARENT_DEV, /* nested */ + /* Add new attributes above here, update the spec in * Documentation/netlink/specs/devlink.yaml and re-generate * net/devlink/netlink_gen.c. diff --git a/net/devlink/devl_internal.h b/net/devlink/devl_internal.h index 52c8bf359dd4..cdf894ba5a9d 100644 --- a/net/devlink/devl_internal.h +++ b/net/devlink/devl_internal.h @@ -154,6 +154,7 @@ int devlink_rel_devlink_handle_put(struct sk_buff *msg, struct devlink *devlink, struct devlink_nl_ctx { struct devlink *devlink; struct devlink_port *devlink_port; + struct devlink *parent_devlink; }; static inline struct devlink_nl_ctx * @@ -197,6 +198,8 @@ typedef int devlink_nl_dump_one_func_t(struct sk_buff *msg, struct devlink * devlink_get_from_attrs_lock(struct net *net, struct nlattr **attrs, bool dev_lock); +struct devlink * +devlink_get_parent_from_attrs_lock(struct net *net, struct nlattr **attrs); int devlink_nl_dumpit(struct sk_buff *msg, struct netlink_callback *cb, devlink_nl_dump_one_func_t *dump_one); diff --git a/net/devlink/netlink.c b/net/devlink/netlink.c index f0a857e286bc..5a057dc86b0f 100644 --- a/net/devlink/netlink.c +++ b/net/devlink/netlink.c @@ -12,6 +12,7 @@ #define DEVLINK_NL_FLAG_NEED_PORT BIT(0) #define DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT BIT(1) #define DEVLINK_NL_FLAG_NEED_DEV_LOCK BIT(2) +#define DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV BIT(3) static const struct genl_multicast_group devlink_nl_mcgrps[] = { [DEVLINK_MCGRP_CONFIG] = { .name = DEVLINK_GENL_MCGRP_CONFIG_NAME }, @@ -239,19 +240,39 @@ devlink_get_from_attrs_lock(struct net *net, struct nlattr **attrs, return ERR_PTR(-ENODEV); } +struct devlink * +devlink_get_parent_from_attrs_lock(struct net *net, struct nlattr **attrs) +{ + return ERR_PTR(-EOPNOTSUPP); +} + static int __devlink_nl_pre_doit(struct sk_buff *skb, struct genl_info *info, u8 flags) { + bool parent_dev = flags & DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV; bool dev_lock = flags & DEVLINK_NL_FLAG_NEED_DEV_LOCK; + struct devlink *devlink, *parent_devlink = NULL; + struct net *net = genl_info_net(info); + struct nlattr **attrs = info->attrs; struct devlink_port *devlink_port; - struct devlink *devlink; int err; - devlink = devlink_get_from_attrs_lock(genl_info_net(info), info->attrs, - dev_lock); - if (IS_ERR(devlink)) - return PTR_ERR(devlink); + if (parent_dev && attrs[DEVLINK_ATTR_PARENT_DEV]) { + parent_devlink = devlink_get_parent_from_attrs_lock(net, attrs); + if (IS_ERR(parent_devlink)) + return PTR_ERR(parent_devlink); + devlink_nl_ctx(info)->parent_devlink = parent_devlink; + /* Drop the parent devlink lock but don't release the reference. + * This will keep it alive until the end of the request. + */ + devl_unlock(parent_devlink); + } + devlink = devlink_get_from_attrs_lock(net, attrs, dev_lock); + if (IS_ERR(devlink)) { + err = PTR_ERR(devlink); + goto parent_put; + } devlink_nl_ctx(info)->devlink = devlink; if (flags & DEVLINK_NL_FLAG_NEED_PORT) { devlink_port = devlink_port_get_from_info(devlink, info); @@ -270,6 +291,9 @@ static int __devlink_nl_pre_doit(struct sk_buff *skb, struct genl_info *info, unlock: devl_dev_unlock(devlink, dev_lock); devlink_put(devlink); +parent_put: + if (parent_dev && parent_devlink) + devlink_put(parent_devlink); return err; } @@ -307,6 +331,8 @@ static void __devlink_nl_post_doit(struct sk_buff *skb, struct genl_info *info, devlink = devlink_nl_ctx(info)->devlink; devl_dev_unlock(devlink, dev_lock); devlink_put(devlink); + if (devlink_nl_ctx(info)->parent_devlink) + devlink_put(devlink_nl_ctx(info)->parent_devlink); } void devlink_nl_post_doit(const struct genl_split_ops *ops, From 58132b6fc4a58441d2b99c65a3548f6875cd52d0 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Wed, 1 Jul 2026 10:32:46 +0300 Subject: [PATCH 0205/1433] devlink: Allow parent dev for rate-set and rate-new Currently, a devlink rate's parent device is assumed to be the same as the one where the devlink rate is created. This patch changes that to allow rate commands to accept an additional argument that specifies the parent dev. This will allow devlink rate groups with leafs from other devices. Example of the new usage with ynl: Creating a group on pci/0000:08:00.1 with a parent to an already existing pci/0000:08:00.1/group1: ./tools/net/ynl/pyynl/cli.py --spec \ Documentation/netlink/specs/devlink.yaml --do rate-new --json '{ "bus-name": "pci", "dev-name": "0000:08:00.1", "rate-node-name": "group2", "rate-parent-node-name": "group1", "parent-dev": { "bus-name": "pci", "dev-name": "0000:08:00.1" } }' Setting the parent of leaf node pci/0000:08:00.1/65537 to pci/0000:08:00.0/group1: ./tools/net/ynl/pyynl/cli.py --spec \ Documentation/netlink/specs/devlink.yaml --do rate-set --json '{ "bus-name": "pci", "dev-name": "0000:08:00.1", "port-index": 65537, "parent-dev": { "bus-name": "pci", "dev-name": "0000:08:00.0" }, "rate-parent-node-name": "group1" }' Signed-off-by: Cosmin Ratiu Reviewed-by: Carolina Jubran Reviewed-by: Jiri Pirko Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260701073254.754518-7-tariqt@nvidia.com Signed-off-by: Paolo Abeni --- Documentation/netlink/specs/devlink.yaml | 10 +++--- net/devlink/netlink.c | 40 +++++++++++++++++++++++- net/devlink/netlink_gen.c | 24 +++++++++----- net/devlink/netlink_gen.h | 8 +++++ net/devlink/rate.c | 4 ++- 5 files changed, 72 insertions(+), 14 deletions(-) diff --git a/Documentation/netlink/specs/devlink.yaml b/Documentation/netlink/specs/devlink.yaml index 13d960b3abb1..38b1190f3d26 100644 --- a/Documentation/netlink/specs/devlink.yaml +++ b/Documentation/netlink/specs/devlink.yaml @@ -2309,8 +2309,8 @@ operations: dont-validate: [strict] flags: [admin-perm] do: - pre: devlink-nl-pre-doit - post: devlink-nl-post-doit + pre: devlink-nl-pre-doit-parent-dev-optional + post: devlink-nl-post-doit-parent-dev-optional request: attributes: - bus-name @@ -2323,6 +2323,7 @@ operations: - rate-tx-weight - rate-parent-node-name - rate-tc-bws + - parent-dev - name: rate-new @@ -2331,8 +2332,8 @@ operations: dont-validate: [strict] flags: [admin-perm] do: - pre: devlink-nl-pre-doit - post: devlink-nl-post-doit + pre: devlink-nl-pre-doit-parent-dev-optional + post: devlink-nl-post-doit-parent-dev-optional request: attributes: - bus-name @@ -2345,6 +2346,7 @@ operations: - rate-tx-weight - rate-parent-node-name - rate-tc-bws + - parent-dev - name: rate-del diff --git a/net/devlink/netlink.c b/net/devlink/netlink.c index 5a057dc86b0f..300580c1a217 100644 --- a/net/devlink/netlink.c +++ b/net/devlink/netlink.c @@ -243,7 +243,29 @@ devlink_get_from_attrs_lock(struct net *net, struct nlattr **attrs, struct devlink * devlink_get_parent_from_attrs_lock(struct net *net, struct nlattr **attrs) { - return ERR_PTR(-EOPNOTSUPP); + unsigned int maxtype = ARRAY_SIZE(devlink_dl_parent_dev_nl_policy) - 1; + struct devlink *devlink; + struct nlattr **tb; + int err; + + if (!attrs[DEVLINK_ATTR_PARENT_DEV]) + return ERR_PTR(-EINVAL); + + tb = kcalloc(maxtype + 1, sizeof(*tb), GFP_KERNEL); + if (!tb) + return ERR_PTR(-ENOMEM); + + err = nla_parse_nested(tb, maxtype, attrs[DEVLINK_ATTR_PARENT_DEV], + devlink_dl_parent_dev_nl_policy, NULL); + if (err) + goto out; + + devlink = devlink_get_from_attrs_lock(net, tb, false); + kfree(tb); + return devlink; +out: + kfree(tb); + return ERR_PTR(err); } static int __devlink_nl_pre_doit(struct sk_buff *skb, struct genl_info *info, @@ -322,6 +344,14 @@ int devlink_nl_pre_doit_port_optional(const struct genl_split_ops *ops, return __devlink_nl_pre_doit(skb, info, DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT); } +int devlink_nl_pre_doit_parent_dev_optional(const struct genl_split_ops *ops, + struct sk_buff *skb, + struct genl_info *info) +{ + return __devlink_nl_pre_doit(skb, info, + DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV); +} + static void __devlink_nl_post_doit(struct sk_buff *skb, struct genl_info *info, u8 flags) { @@ -348,6 +378,14 @@ devlink_nl_post_doit_dev_lock(const struct genl_split_ops *ops, __devlink_nl_post_doit(skb, info, DEVLINK_NL_FLAG_NEED_DEV_LOCK); } +void +devlink_nl_post_doit_parent_dev_optional(const struct genl_split_ops *ops, + struct sk_buff *skb, + struct genl_info *info) +{ + __devlink_nl_post_doit(skb, info, DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV); +} + static int devlink_nl_inst_single_dumpit(struct sk_buff *msg, struct netlink_callback *cb, int flags, devlink_nl_dump_one_func_t *dump_one, diff --git a/net/devlink/netlink_gen.c b/net/devlink/netlink_gen.c index f52b0c2b19ed..dec00133178d 100644 --- a/net/devlink/netlink_gen.c +++ b/net/devlink/netlink_gen.c @@ -46,6 +46,12 @@ devlink_attr_param_type_validate(const struct nlattr *attr, } /* Common nested types */ +const struct nla_policy devlink_dl_parent_dev_nl_policy[DEVLINK_ATTR_INDEX + 1] = { + [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING, }, + [DEVLINK_ATTR_INDEX] = NLA_POLICY_FULL_RANGE(NLA_UINT, &devlink_attr_index_range), +}; + const struct nla_policy devlink_dl_port_function_nl_policy[DEVLINK_PORT_FN_ATTR_CAPS + 1] = { [DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR] = { .type = NLA_BINARY, }, [DEVLINK_PORT_FN_ATTR_STATE] = NLA_POLICY_MAX(NLA_U8, 1), @@ -608,7 +614,7 @@ static const struct nla_policy devlink_rate_get_dump_nl_policy[DEVLINK_ATTR_INDE }; /* DEVLINK_CMD_RATE_SET - do */ -static const struct nla_policy devlink_rate_set_nl_policy[DEVLINK_ATTR_INDEX + 1] = { +static const struct nla_policy devlink_rate_set_nl_policy[DEVLINK_ATTR_PARENT_DEV + 1] = { [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_INDEX] = NLA_POLICY_FULL_RANGE(NLA_UINT, &devlink_attr_index_range), @@ -619,10 +625,11 @@ static const struct nla_policy devlink_rate_set_nl_policy[DEVLINK_ATTR_INDEX + 1 [DEVLINK_ATTR_RATE_TX_WEIGHT] = { .type = NLA_U32, }, [DEVLINK_ATTR_RATE_PARENT_NODE_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_RATE_TC_BWS] = NLA_POLICY_NESTED(devlink_dl_rate_tc_bws_nl_policy), + [DEVLINK_ATTR_PARENT_DEV] = NLA_POLICY_NESTED(devlink_dl_parent_dev_nl_policy), }; /* DEVLINK_CMD_RATE_NEW - do */ -static const struct nla_policy devlink_rate_new_nl_policy[DEVLINK_ATTR_INDEX + 1] = { +static const struct nla_policy devlink_rate_new_nl_policy[DEVLINK_ATTR_PARENT_DEV + 1] = { [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_INDEX] = NLA_POLICY_FULL_RANGE(NLA_UINT, &devlink_attr_index_range), @@ -633,6 +640,7 @@ static const struct nla_policy devlink_rate_new_nl_policy[DEVLINK_ATTR_INDEX + 1 [DEVLINK_ATTR_RATE_TX_WEIGHT] = { .type = NLA_U32, }, [DEVLINK_ATTR_RATE_PARENT_NODE_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_RATE_TC_BWS] = NLA_POLICY_NESTED(devlink_dl_rate_tc_bws_nl_policy), + [DEVLINK_ATTR_PARENT_DEV] = NLA_POLICY_NESTED(devlink_dl_parent_dev_nl_policy), }; /* DEVLINK_CMD_RATE_DEL - do */ @@ -1290,21 +1298,21 @@ const struct genl_split_ops devlink_nl_ops[75] = { { .cmd = DEVLINK_CMD_RATE_SET, .validate = GENL_DONT_VALIDATE_STRICT, - .pre_doit = devlink_nl_pre_doit, + .pre_doit = devlink_nl_pre_doit_parent_dev_optional, .doit = devlink_nl_rate_set_doit, - .post_doit = devlink_nl_post_doit, + .post_doit = devlink_nl_post_doit_parent_dev_optional, .policy = devlink_rate_set_nl_policy, - .maxattr = DEVLINK_ATTR_INDEX, + .maxattr = DEVLINK_ATTR_PARENT_DEV, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, { .cmd = DEVLINK_CMD_RATE_NEW, .validate = GENL_DONT_VALIDATE_STRICT, - .pre_doit = devlink_nl_pre_doit, + .pre_doit = devlink_nl_pre_doit_parent_dev_optional, .doit = devlink_nl_rate_new_doit, - .post_doit = devlink_nl_post_doit, + .post_doit = devlink_nl_post_doit_parent_dev_optional, .policy = devlink_rate_new_nl_policy, - .maxattr = DEVLINK_ATTR_INDEX, + .maxattr = DEVLINK_ATTR_PARENT_DEV, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, { diff --git a/net/devlink/netlink_gen.h b/net/devlink/netlink_gen.h index 20034b0929a8..a70e0e4769aa 100644 --- a/net/devlink/netlink_gen.h +++ b/net/devlink/netlink_gen.h @@ -13,6 +13,7 @@ #include /* Common nested types */ +extern const struct nla_policy devlink_dl_parent_dev_nl_policy[DEVLINK_ATTR_INDEX + 1]; extern const struct nla_policy devlink_dl_port_function_nl_policy[DEVLINK_PORT_FN_ATTR_CAPS + 1]; extern const struct nla_policy devlink_dl_rate_tc_bws_nl_policy[DEVLINK_RATE_TC_ATTR_BW + 1]; extern const struct nla_policy devlink_dl_selftest_id_nl_policy[DEVLINK_ATTR_SELFTEST_ID_FLASH + 1]; @@ -29,12 +30,19 @@ int devlink_nl_pre_doit_port_optional(const struct genl_split_ops *ops, struct genl_info *info); int devlink_nl_pre_doit_dev_lock(const struct genl_split_ops *ops, struct sk_buff *skb, struct genl_info *info); +int devlink_nl_pre_doit_parent_dev_optional(const struct genl_split_ops *ops, + struct sk_buff *skb, + struct genl_info *info); void devlink_nl_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb, struct genl_info *info); void devlink_nl_post_doit_dev_lock(const struct genl_split_ops *ops, struct sk_buff *skb, struct genl_info *info); +void +devlink_nl_post_doit_parent_dev_optional(const struct genl_split_ops *ops, + struct sk_buff *skb, + struct genl_info *info); int devlink_nl_get_doit(struct sk_buff *skb, struct genl_info *info); int devlink_nl_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb); diff --git a/net/devlink/rate.c b/net/devlink/rate.c index 295f4185fdfd..78a59d79c2ea 100644 --- a/net/devlink/rate.c +++ b/net/devlink/rate.c @@ -663,9 +663,11 @@ int devlink_nl_rate_set_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_rate_new_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *rate_devlink, *devlink = devlink_nl_ctx(info)->devlink; + struct devlink_nl_ctx *ctx = devlink_nl_ctx(info); + struct devlink *devlink = ctx->devlink; struct devlink_rate *rate_node; const struct devlink_ops *ops; + struct devlink *rate_devlink; int err; ops = devlink->ops; From 6bbd1bce3099eec42cb3e90099f5f9910c0dc84f Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Wed, 1 Jul 2026 10:32:47 +0300 Subject: [PATCH 0206/1433] devlink: Allow rate node parents from other devlinks This commit makes use of the building blocks previously added to implement cross-device rate nodes. A new 'supported_cross_device_rate_nodes' bool is added to devlink_ops which lets drivers advertise support for cross-device rate objects. If enabled and if there is a common shared devlink instance, then: - all rate objects will be stored in the top-most common nested instance and - rate objects can have parents from other devices sharing the same common instance. Storing rates in the common shared ancestor is safe, because it is reference counted by its nested devlink instances, so it's guaranteed to outlive them. Furthermore, the shared devlink infra guarantees a given nested devlink hierarchy is managed by the same driver. The parent devlink from info->ctx is not locked, so none of its mutable fields can be used. But parent setting only requires comparing devlink pointer comparisons. Additionally, since the shared devlink is locked, other rate operations cannot concurrently happen. Signed-off-by: Cosmin Ratiu Reviewed-by: Carolina Jubran Reviewed-by: Jiri Pirko Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260701073254.754518-8-tariqt@nvidia.com Signed-off-by: Paolo Abeni --- .../networking/devlink/devlink-port.rst | 2 + include/net/devlink.h | 9 ++ net/devlink/core.c | 4 +- net/devlink/rate.c | 86 +++++++++++++++++-- 4 files changed, 92 insertions(+), 9 deletions(-) diff --git a/Documentation/networking/devlink/devlink-port.rst b/Documentation/networking/devlink/devlink-port.rst index 9374ebe70f48..18aca77006d5 100644 --- a/Documentation/networking/devlink/devlink-port.rst +++ b/Documentation/networking/devlink/devlink-port.rst @@ -420,6 +420,8 @@ API allows to configure following rate object's parameters: Parent node name. Parent node rate limits are considered as additional limits to all node children limits. ``tx_max`` is an upper limit for children. ``tx_share`` is a total bandwidth distributed among children. + If the device supports cross-function scheduling, the parent can be from a + different function of the same underlying device. ``tc_bw`` Allow users to set the bandwidth allocation per traffic class on rate diff --git a/include/net/devlink.h b/include/net/devlink.h index dd546dbd57cf..ffe1ad5fb70b 100644 --- a/include/net/devlink.h +++ b/include/net/devlink.h @@ -1594,6 +1594,15 @@ struct devlink_ops { struct devlink_rate *parent, void *priv_child, void *priv_parent, struct netlink_ext_ack *extack); + /* Indicates if cross-device rate nodes are supported. + * This also requires a shared common ancestor object all devices that + * could share rate nodes are nested in. + * If enabled, rate operations may be called on an instance with only + * the common ancestor lock held and *without that instance lock held*. + * It is the driver's responsibility to ensure proper serialization + * with other operations. + */ + bool supported_cross_device_rate_nodes; /** * selftests_check() - queries if selftest is supported * @devlink: devlink instance diff --git a/net/devlink/core.c b/net/devlink/core.c index ee26c50b4118..c53a42e17a58 100644 --- a/net/devlink/core.c +++ b/net/devlink/core.c @@ -534,6 +534,9 @@ void devlink_free(struct devlink *devlink) { ASSERT_DEVLINK_NOT_REGISTERED(devlink); + devl_lock(devlink); + WARN_ON(devlink_rates_check(devlink, NULL, NULL)); + devl_unlock(devlink); devlink_rel_put(devlink); WARN_ON(!list_empty(&devlink->trap_policer_list)); @@ -544,7 +547,6 @@ void devlink_free(struct devlink *devlink) WARN_ON(!list_empty(&devlink->resource_list)); WARN_ON(!list_empty(&devlink->dpipe_table_list)); WARN_ON(!list_empty(&devlink->sb_list)); - WARN_ON(devlink_rates_check(devlink, NULL, NULL)); WARN_ON(!list_empty(&devlink->linecard_list)); WARN_ON(!xa_empty(&devlink->ports)); diff --git a/net/devlink/rate.c b/net/devlink/rate.c index 78a59d79c2ea..e727c8b8b33e 100644 --- a/net/devlink/rate.c +++ b/net/devlink/rate.c @@ -30,14 +30,42 @@ devlink_rate_leaf_get_from_info(struct devlink *devlink, struct genl_info *info) return devlink_rate ?: ERR_PTR(-ENODEV); } +/* Repeatedly walks the nested devlink chain while cross device rate nodes are + * supported and finds the topmost instance where rates should be stored. + * That instance is locked, referenced and returned. + * When cross device rate nodes aren't supported the original devlink instance + * is returned. + */ static struct devlink *devl_rate_lock(struct devlink *devlink) { - return devlink; + struct devlink *rate_devlink = devlink, *parent; + + devl_assert_locked(devlink); + + while (rate_devlink->ops && + rate_devlink->ops->supported_cross_device_rate_nodes) { + parent = devlink_nested_in_get_lock(rate_devlink); + if (!parent) + break; + if (rate_devlink != devlink) { + /* Unlock intermediate instances. */ + devl_unlock(rate_devlink); + devlink_put(rate_devlink); + } + rate_devlink = parent; + } + return rate_devlink; } +/* Unlocks and puts 'rate devlink' if different than 'devlink'. */ static void devl_rate_unlock(struct devlink *devlink, struct devlink *rate_devlink) { + if (devlink == rate_devlink) + return; + + devl_unlock(rate_devlink); + devlink_put(rate_devlink); } static struct devlink_rate * @@ -121,6 +149,25 @@ static int devlink_rate_put_tc_bws(struct sk_buff *msg, u32 *tc_bw) return -EMSGSIZE; } +static int devlink_nl_rate_parent_fill(struct sk_buff *msg, + struct devlink_rate *devlink_rate) +{ + struct devlink_rate *parent = devlink_rate->parent; + struct devlink *devlink = parent->devlink; + + if (nla_put_string(msg, DEVLINK_ATTR_RATE_PARENT_NODE_NAME, + parent->name)) + return -EMSGSIZE; + + if (devlink != devlink_rate->devlink && + devlink_nl_put_nested_handle(msg, + devlink_net(devlink_rate->devlink), + devlink, DEVLINK_ATTR_PARENT_DEV)) + return -EMSGSIZE; + + return 0; +} + static int devlink_nl_rate_fill(struct sk_buff *msg, struct devlink_rate *devlink_rate, enum devlink_command cmd, u32 portid, u32 seq, @@ -165,10 +212,9 @@ static int devlink_nl_rate_fill(struct sk_buff *msg, devlink_rate->tx_weight)) goto nla_put_failure; - if (devlink_rate->parent) - if (nla_put_string(msg, DEVLINK_ATTR_RATE_PARENT_NODE_NAME, - devlink_rate->parent->name)) - goto nla_put_failure; + if (devlink_rate->parent && + devlink_nl_rate_parent_fill(msg, devlink_rate)) + goto nla_put_failure; if (devlink_rate_put_tc_bws(msg, devlink_rate->tc_bw)) goto nla_put_failure; @@ -322,13 +368,14 @@ devlink_nl_rate_parent_node_set(struct devlink_rate *devlink_rate, struct genl_info *info, struct nlattr *nla_parent) { - struct devlink *devlink = devlink_rate->devlink; + struct devlink *devlink = devlink_rate->devlink, *parent_devlink; const char *parent_name = nla_data(nla_parent); const struct devlink_ops *ops = devlink->ops; size_t len = strlen(parent_name); struct devlink_rate *parent; int err = -EOPNOTSUPP; + parent_devlink = devlink_nl_ctx(info)->parent_devlink ? : devlink; parent = devlink_rate->parent; if (parent && !len) { @@ -346,7 +393,13 @@ devlink_nl_rate_parent_node_set(struct devlink_rate *devlink_rate, refcount_dec(&parent->refcnt); devlink_rate->parent = NULL; } else if (len) { - parent = devlink_rate_node_get_by_name(rate_devlink, devlink, + /* parent_devlink (when different than devlink) isn't locked, + * but the rate node devlink instance is, so nobody from the + * same group of devices sharing rates could change the used + * fields or unregister the parent. + */ + parent = devlink_rate_node_get_by_name(rate_devlink, + parent_devlink, parent_name); if (IS_ERR(parent)) return -ENODEV; @@ -633,9 +686,11 @@ static bool devlink_rate_set_ops_supported(const struct devlink_ops *ops, int devlink_nl_rate_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *rate_devlink, *devlink = devlink_nl_ctx(info)->devlink; + struct devlink_nl_ctx *ctx = devlink_nl_ctx(info); + struct devlink *devlink = ctx->devlink; struct devlink_rate *devlink_rate; const struct devlink_ops *ops; + struct devlink *rate_devlink; int err; rate_devlink = devl_rate_lock(devlink); @@ -652,6 +707,14 @@ int devlink_nl_rate_set_doit(struct sk_buff *skb, struct genl_info *info) goto unlock; } + if (ctx->parent_devlink && ctx->parent_devlink != devlink && + !ops->supported_cross_device_rate_nodes) { + NL_SET_ERR_MSG(info->extack, + "Cross-device rate parents aren't supported"); + err = -EOPNOTSUPP; + goto unlock; + } + err = devlink_nl_rate_set(devlink_rate, rate_devlink, ops, info); if (!err) @@ -679,6 +742,13 @@ int devlink_nl_rate_new_doit(struct sk_buff *skb, struct genl_info *info) if (!devlink_rate_set_ops_supported(ops, info, DEVLINK_RATE_TYPE_NODE)) return -EOPNOTSUPP; + if (ctx->parent_devlink && ctx->parent_devlink != devlink && + !ops->supported_cross_device_rate_nodes) { + NL_SET_ERR_MSG(info->extack, + "Cross-device rate parents aren't supported"); + return -EOPNOTSUPP; + } + rate_devlink = devl_rate_lock(devlink); rate_node = devlink_rate_node_get_from_attrs(rate_devlink, devlink, info->attrs); From f8128b13df6630823fddabfd8ec0639c1920f49a Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Wed, 1 Jul 2026 10:32:48 +0300 Subject: [PATCH 0207/1433] net/mlx5: qos: Use mlx5_lag_query_bond_speed to query LAG speed Previously, the master device of the uplink netdev was queried for its maximum link speed from the QoS layer, requiring the uplink_netdev mutex and possibly the RTNL (if the call originated from the TC matchall layer). Acquiring these locks here is risky, as lock cycles could form. The locking for the QoS layer is about to change, so to avoid issues, replace the code querying the LAG's max link speed with the existing infrastructure added in commit [1]. This simplifies this part and avoids potential lock cycles. One caveat is that there's a new edge case, when the bond device is not fully formed to represent the LAG device, the speed isn't calculated and is left at 0. This now handled explicitly. [1] commit f0b2fde98065 ("net/mlx5: Add support for querying bond speed") Signed-off-by: Cosmin Ratiu Reviewed-by: Carolina Jubran Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260701073254.754518-9-tariqt@nvidia.com Signed-off-by: Paolo Abeni --- .../net/ethernet/mellanox/mlx5/core/esw/qos.c | 36 ++++--------------- 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c index faccc60fc93a..d04fda4b3778 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c @@ -1489,41 +1489,16 @@ static int esw_qos_node_enable_tc_arbitration(struct mlx5_esw_sched_node *node, return err; } -static u32 mlx5_esw_qos_lag_link_speed_get(struct mlx5_core_dev *mdev, - bool take_rtnl) -{ - struct ethtool_link_ksettings lksettings; - struct net_device *slave, *master; - u32 speed = SPEED_UNKNOWN; - - slave = mlx5_uplink_netdev_get(mdev); - if (!slave) - goto out; - - if (take_rtnl) - rtnl_lock(); - master = netdev_master_upper_dev_get(slave); - if (master && !__ethtool_get_link_ksettings(master, &lksettings)) - speed = lksettings.base.speed; - if (take_rtnl) - rtnl_unlock(); - -out: - mlx5_uplink_netdev_put(mdev, slave); - return speed; -} - static int mlx5_esw_qos_max_link_speed_get(struct mlx5_core_dev *mdev, u32 *link_speed_max, - bool take_rtnl, struct netlink_ext_ack *extack) { int err; - if (!mlx5_lag_is_active(mdev)) + if (!mlx5_lag_is_active(mdev) || + mlx5_lag_query_bond_speed(mdev, link_speed_max) < 0 || + *link_speed_max == 0) goto skip_lag; - *link_speed_max = mlx5_esw_qos_lag_link_speed_get(mdev, take_rtnl); - if (*link_speed_max != (u32)SPEED_UNKNOWN) return 0; @@ -1560,7 +1535,8 @@ int mlx5_esw_qos_modify_vport_rate(struct mlx5_eswitch *esw, u16 vport_num, u32 return PTR_ERR(vport); if (rate_mbps) { - err = mlx5_esw_qos_max_link_speed_get(esw->dev, &link_speed_max, false, NULL); + err = mlx5_esw_qos_max_link_speed_get(esw->dev, &link_speed_max, + NULL); if (err) return err; @@ -1598,7 +1574,7 @@ static int esw_qos_devlink_rate_to_mbps(struct mlx5_core_dev *mdev, const char * return -EINVAL; } - err = mlx5_esw_qos_max_link_speed_get(mdev, &link_speed_max, true, extack); + err = mlx5_esw_qos_max_link_speed_get(mdev, &link_speed_max, extack); if (err) return err; From 89a0881183d1abe308d659e3d0011588ae7fc99c Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Wed, 1 Jul 2026 10:32:49 +0300 Subject: [PATCH 0208/1433] net/mlx5: qos: Refactor vport QoS cleanup Qos cleanup is a complex affair, because of the two modes of operation (legacy and switchdev). Leaf QoS is removed: 1. In legacy mode by esw_vport_cleanup() -> mlx5_esw_qos_vport_disable() 2. In switchdev mode by mlx5_esw_offloads_devlink_port_unregister() -> mlx5_esw_qos_vport_update_parent(). A little later in the same flow, the calls in 1 happen but they are noops. Zooming out a bit, from both mlx5_eswitch_disable_locked() and mlx5_eswitch_disable_sriov() the leaves are destroyed before the nodes, which is the reverse of what should be. For SFs there's no devl_rate_nodes_destroy() call to unparent the affected leaf. Sanitize all of this by: 1. Destroying nodes before leaves in both legacy and switchdev mode. 2. Only removing vport qos from esw_vport_cleanup(), reachable from both legacy and switchdev and also reachable by SF removal. 3. Unexpose mlx5_esw_qos_vport_update_parent(), which becomes internal to qos. 4. Remove the WARN in mlx5_esw_qos_vport_disable(). This also takes care of a theoretical corner case, when mlx5_esw_qos_vport_update_parent() tried to reattach the vport to the original parent on failure, which can fail as well, leaving the vport in a broken state. Signed-off-by: Cosmin Ratiu Reviewed-by: Carolina Jubran Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260701073254.754518-10-tariqt@nvidia.com Signed-off-by: Paolo Abeni --- .../mellanox/mlx5/core/esw/devlink_port.c | 1 - .../net/ethernet/mellanox/mlx5/core/esw/qos.c | 14 ++++---------- .../net/ethernet/mellanox/mlx5/core/eswitch.c | 19 ++++++++++--------- .../net/ethernet/mellanox/mlx5/core/eswitch.h | 2 -- 4 files changed, 14 insertions(+), 22 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c index 6e50311faa27..8c27a33f9d7b 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c @@ -268,7 +268,6 @@ void mlx5_esw_offloads_devlink_port_unregister(struct mlx5_vport *vport) dl_port = vport->dl_port; mlx5_esw_devlink_port_res_unregister(&dl_port->dl_port); - mlx5_esw_qos_vport_update_parent(vport, NULL, NULL); devl_rate_leaf_destroy(&dl_port->dl_port); devl_port_unregister(&dl_port->dl_port); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c index d04fda4b3778..204f47c99142 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c @@ -1139,18 +1139,10 @@ static void mlx5_esw_qos_vport_disable_locked(struct mlx5_vport *vport) void mlx5_esw_qos_vport_disable(struct mlx5_vport *vport) { struct mlx5_eswitch *esw = vport->dev->priv.eswitch; - struct mlx5_esw_sched_node *parent; lockdep_assert_held(&esw->state_lock); esw_qos_lock(esw); - if (!vport->qos.sched_node) - goto unlock; - - parent = vport->qos.sched_node->parent; - WARN(parent, "Disabling QoS on port before detaching it from node"); - mlx5_esw_qos_vport_disable_locked(vport); -unlock: esw_qos_unlock(esw); } @@ -1866,8 +1858,10 @@ int mlx5_esw_devlink_rate_node_del(struct devlink_rate *rate_node, void *priv, return 0; } -int mlx5_esw_qos_vport_update_parent(struct mlx5_vport *vport, struct mlx5_esw_sched_node *parent, - struct netlink_ext_ack *extack) +static int +mlx5_esw_qos_vport_update_parent(struct mlx5_vport *vport, + struct mlx5_esw_sched_node *parent, + struct netlink_ext_ack *extack) { struct mlx5_eswitch *esw = vport->dev->priv.eswitch; int err = 0; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c index a0e2ca87b8d8..b67f15a8f766 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c @@ -1990,6 +1990,13 @@ void mlx5_eswitch_disable_sriov(struct mlx5_eswitch *esw, bool clear_vf) esw->esw_funcs.num_vfs, esw->esw_funcs.num_ec_vfs, esw->enabled_vports); mlx5_eswitch_invalidate_wq(esw); + + if (esw->mode == MLX5_ESWITCH_OFFLOADS) { + struct devlink *devlink = priv_to_devlink(esw->dev); + + devl_rate_nodes_destroy(devlink); + } + mlx5_esw_reps_block(esw); if (!mlx5_core_is_ecpf(esw->dev)) { @@ -2003,12 +2010,6 @@ void mlx5_eswitch_disable_sriov(struct mlx5_eswitch *esw, bool clear_vf) } mlx5_esw_reps_unblock(esw); - - if (esw->mode == MLX5_ESWITCH_OFFLOADS) { - struct devlink *devlink = priv_to_devlink(esw->dev); - - devl_rate_nodes_destroy(devlink); - } /* Destroy legacy fdb when disabling sriov in legacy mode. */ if (esw->mode == MLX5_ESWITCH_LEGACY) mlx5_eswitch_disable_locked(esw); @@ -2039,6 +2040,9 @@ void mlx5_eswitch_disable_locked(struct mlx5_eswitch *esw) esw->mode == MLX5_ESWITCH_LEGACY ? "LEGACY" : "OFFLOADS", esw->esw_funcs.num_vfs, esw->esw_funcs.num_ec_vfs, esw->enabled_vports); + if (esw->mode == MLX5_ESWITCH_OFFLOADS) + devl_rate_nodes_destroy(devlink); + if (esw->fdb_table.flags & MLX5_ESW_FDB_CREATED) { esw->fdb_table.flags &= ~MLX5_ESW_FDB_CREATED; if (esw->mode == MLX5_ESWITCH_OFFLOADS) @@ -2047,9 +2051,6 @@ void mlx5_eswitch_disable_locked(struct mlx5_eswitch *esw) esw_legacy_disable(esw); mlx5_esw_acls_ns_cleanup(esw); } - - if (esw->mode == MLX5_ESWITCH_OFFLOADS) - devl_rate_nodes_destroy(devlink); } void mlx5_eswitch_disable(struct mlx5_eswitch *esw) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h index fea72b1dedab..140343f2b913 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h @@ -482,8 +482,6 @@ int mlx5_eswitch_set_vport_trust(struct mlx5_eswitch *esw, u16 vport_num, bool setting); int mlx5_eswitch_set_vport_rate(struct mlx5_eswitch *esw, u16 vport, u32 max_rate, u32 min_rate); -int mlx5_esw_qos_vport_update_parent(struct mlx5_vport *vport, struct mlx5_esw_sched_node *node, - struct netlink_ext_ack *extack); int mlx5_eswitch_set_vepa(struct mlx5_eswitch *esw, u8 setting); int mlx5_eswitch_get_vepa(struct mlx5_eswitch *esw, u8 *setting); int mlx5_eswitch_get_vport_config(struct mlx5_eswitch *esw, From 22d32def3ced2c51f97323476940658a0fb21855 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Wed, 1 Jul 2026 10:32:50 +0300 Subject: [PATCH 0209/1433] net/mlx5: qos: Model the root node in the scheduling hierarchy In commit [1] the concept of the root node in the qos hierarchy was removed due to a bug with how tx_share worked. The side effect is that in many places, there are now corner cases related to parent handling. However, since that change, support for tc_bw was added and now, with upcoming cross-esw support, the code is about to become even more complicated, increasing the number of such corner cases. Bring back the concept of the root node, to which all esw vports and nodes are connected to. This benefits multiple operations which can assume there's always a valid parent and don't have to do ternary gymnastics to determine the correct esw to talk to. As side effect, there's no longer a need to store the groups in the qos domain, since normalization can simply iterate over all children of the root node. Normalization gets simplified as a result. There should be no functionality changes as a result of this change. [1] commit 330f0f6713a3 ("net/mlx5: Remove default QoS group and attach vports directly to root TSAR") Signed-off-by: Cosmin Ratiu Reviewed-by: Carolina Jubran Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260701073254.754518-11-tariqt@nvidia.com Signed-off-by: Paolo Abeni --- .../net/ethernet/mellanox/mlx5/core/esw/qos.c | 208 ++++++++---------- .../net/ethernet/mellanox/mlx5/core/eswitch.h | 3 +- 2 files changed, 90 insertions(+), 121 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c index 204f47c99142..49c8ec0dac9a 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c @@ -15,8 +15,6 @@ struct mlx5_qos_domain { /* Serializes access to all qos changes in the qos domain. */ struct mutex lock; - /* List of all mlx5_esw_sched_nodes. */ - struct list_head nodes; }; static void esw_qos_lock(struct mlx5_eswitch *esw) @@ -43,7 +41,6 @@ static struct mlx5_qos_domain *esw_qos_domain_alloc(void) return NULL; mutex_init(&qos_domain->lock); - INIT_LIST_HEAD(&qos_domain->nodes); return qos_domain; } @@ -62,6 +59,7 @@ static void esw_qos_domain_release(struct mlx5_eswitch *esw) } enum sched_node_type { + SCHED_NODE_TYPE_ROOT, SCHED_NODE_TYPE_VPORTS_TSAR, SCHED_NODE_TYPE_VPORT, SCHED_NODE_TYPE_TC_ARBITER_TSAR, @@ -106,18 +104,6 @@ struct mlx5_esw_sched_node { u32 tc_bw[DEVLINK_RATE_TCS_MAX]; }; -static void esw_qos_node_attach_to_parent(struct mlx5_esw_sched_node *node) -{ - if (!node->parent) { - /* Root children are assigned a depth level of 2. */ - node->level = 2; - list_add_tail(&node->entry, &node->esw->qos.domain->nodes); - } else { - node->level = node->parent->level + 1; - list_add_tail(&node->entry, &node->parent->children); - } -} - static int esw_qos_num_tcs(struct mlx5_core_dev *dev) { int num_tcs = mlx5_max_tc(dev) + 1; @@ -125,14 +111,14 @@ static int esw_qos_num_tcs(struct mlx5_core_dev *dev) return num_tcs < DEVLINK_RATE_TCS_MAX ? num_tcs : DEVLINK_RATE_TCS_MAX; } -static void -esw_qos_node_set_parent(struct mlx5_esw_sched_node *node, struct mlx5_esw_sched_node *parent) +static void esw_qos_node_set_parent(struct mlx5_esw_sched_node *node, + struct mlx5_esw_sched_node *parent) { - list_del_init(&node->entry); node->parent = parent; - if (parent) - node->esw = parent->esw; - esw_qos_node_attach_to_parent(node); + node->esw = parent->esw; + node->level = parent->level + 1; + list_del(&node->entry); + list_add_tail(&node->entry, &parent->children); } static void esw_qos_nodes_set_parent(struct list_head *nodes, @@ -321,22 +307,19 @@ static int esw_qos_create_rate_limit_element(struct mlx5_esw_sched_node *node, return esw_qos_node_create_sched_element(node, sched_ctx, extack); } -static u32 esw_qos_calculate_min_rate_divider(struct mlx5_eswitch *esw, - struct mlx5_esw_sched_node *parent) +static u32 +esw_qos_calculate_min_rate_divider(struct mlx5_esw_sched_node *parent) { - struct list_head *nodes = parent ? &parent->children : &esw->qos.domain->nodes; - u32 fw_max_bw_share = MLX5_CAP_QOS(esw->dev, max_tsar_bw_share); + u32 fw_max_bw_share = MLX5_CAP_QOS(parent->esw->dev, max_tsar_bw_share); struct mlx5_esw_sched_node *node; u32 max_guarantee = 0; /* Find max min_rate across all nodes. * This will correspond to fw_max_bw_share in the final bw_share calculation. */ - list_for_each_entry(node, nodes, entry) { - if (node->esw == esw && node->ix != esw->qos.root_tsar_ix && - node->min_rate > max_guarantee) + list_for_each_entry(node, &parent->children, entry) + if (node->min_rate > max_guarantee) max_guarantee = node->min_rate; - } if (max_guarantee) return max_t(u32, max_guarantee / fw_max_bw_share, 1); @@ -368,18 +351,13 @@ static void esw_qos_update_sched_node_bw_share(struct mlx5_esw_sched_node *node, esw_qos_sched_elem_config(node, node->max_rate, bw_share, extack); } -static void esw_qos_normalize_min_rate(struct mlx5_eswitch *esw, - struct mlx5_esw_sched_node *parent, +static void esw_qos_normalize_min_rate(struct mlx5_esw_sched_node *parent, struct netlink_ext_ack *extack) { - struct list_head *nodes = parent ? &parent->children : &esw->qos.domain->nodes; - u32 divider = esw_qos_calculate_min_rate_divider(esw, parent); + u32 divider = esw_qos_calculate_min_rate_divider(parent); struct mlx5_esw_sched_node *node; - list_for_each_entry(node, nodes, entry) { - if (node->esw != esw || node->ix == esw->qos.root_tsar_ix) - continue; - + list_for_each_entry(node, &parent->children, entry) { /* Vports TC TSARs don't have a minimum rate configured, * so there's no need to update the bw_share on them. */ @@ -391,7 +369,7 @@ static void esw_qos_normalize_min_rate(struct mlx5_eswitch *esw, if (list_empty(&node->children)) continue; - esw_qos_normalize_min_rate(node->esw, node, extack); + esw_qos_normalize_min_rate(node, extack); } } @@ -412,14 +390,11 @@ static u32 esw_qos_calculate_tc_bw_divider(u32 *tc_bw) static int esw_qos_set_node_min_rate(struct mlx5_esw_sched_node *node, u32 min_rate, struct netlink_ext_ack *extack) { - struct mlx5_eswitch *esw = node->esw; - if (min_rate == node->min_rate) return 0; node->min_rate = min_rate; - esw_qos_normalize_min_rate(esw, node->parent, extack); - + esw_qos_normalize_min_rate(node->parent, extack); return 0; } @@ -472,8 +447,7 @@ esw_qos_vport_create_sched_element(struct mlx5_esw_sched_node *vport_node, SCHEDULING_CONTEXT_ELEMENT_TYPE_VPORT); attr = MLX5_ADDR_OF(scheduling_context, sched_ctx, element_attributes); MLX5_SET(vport_element, attr, vport_number, vport_node->vport->vport); - MLX5_SET(scheduling_context, sched_ctx, parent_element_id, - parent ? parent->ix : vport_node->esw->qos.root_tsar_ix); + MLX5_SET(scheduling_context, sched_ctx, parent_element_id, parent->ix); MLX5_SET(scheduling_context, sched_ctx, max_average_bw, vport_node->max_rate); @@ -513,7 +487,7 @@ esw_qos_vport_tc_create_sched_element(struct mlx5_esw_sched_node *vport_tc_node, } static struct mlx5_esw_sched_node * -__esw_qos_alloc_node(struct mlx5_eswitch *esw, u32 tsar_ix, enum sched_node_type type, +__esw_qos_alloc_node(u32 tsar_ix, enum sched_node_type type, struct mlx5_esw_sched_node *parent) { struct mlx5_esw_sched_node *node; @@ -522,20 +496,12 @@ __esw_qos_alloc_node(struct mlx5_eswitch *esw, u32 tsar_ix, enum sched_node_type if (!node) return NULL; - node->esw = esw; node->ix = tsar_ix; node->type = type; - node->parent = parent; INIT_LIST_HEAD(&node->children); - esw_qos_node_attach_to_parent(node); - if (!parent) { - /* The caller is responsible for inserting the node into the - * parent list if necessary. This function can also be used with - * a NULL parent, which doesn't necessarily indicate that it - * refers to the root scheduling element. - */ - list_del_init(&node->entry); - } + INIT_LIST_HEAD(&node->entry); + if (parent) + esw_qos_node_set_parent(node, parent); return node; } @@ -570,7 +536,7 @@ static int esw_qos_create_vports_tc_node(struct mlx5_esw_sched_node *parent, SCHEDULING_HIERARCHY_E_SWITCH)) return -EOPNOTSUPP; - vports_tc_node = __esw_qos_alloc_node(parent->esw, 0, + vports_tc_node = __esw_qos_alloc_node(0, SCHED_NODE_TYPE_VPORTS_TC_TSAR, parent); if (!vports_tc_node) { @@ -665,7 +631,6 @@ static int esw_qos_create_tc_arbiter_sched_elem( struct netlink_ext_ack *extack) { u32 tsar_ctx[MLX5_ST_SZ_DW(scheduling_context)] = {}; - u32 tsar_parent_ix; void *attr; if (!mlx5_qos_tsar_type_supported(tc_arbiter_node->esw->dev, @@ -678,10 +643,8 @@ static int esw_qos_create_tc_arbiter_sched_elem( attr = MLX5_ADDR_OF(scheduling_context, tsar_ctx, element_attributes); MLX5_SET(tsar_element, attr, tsar_type, TSAR_ELEMENT_TSAR_TYPE_TC_ARB); - tsar_parent_ix = tc_arbiter_node->parent ? tc_arbiter_node->parent->ix : - tc_arbiter_node->esw->qos.root_tsar_ix; MLX5_SET(scheduling_context, tsar_ctx, parent_element_id, - tsar_parent_ix); + tc_arbiter_node->parent->ix); MLX5_SET(scheduling_context, tsar_ctx, element_type, SCHEDULING_CONTEXT_ELEMENT_TYPE_TSAR); MLX5_SET(scheduling_context, tsar_ctx, max_average_bw, @@ -694,37 +657,36 @@ static int esw_qos_create_tc_arbiter_sched_elem( } static struct mlx5_esw_sched_node * -__esw_qos_create_vports_sched_node(struct mlx5_eswitch *esw, struct mlx5_esw_sched_node *parent, +__esw_qos_create_vports_sched_node(struct mlx5_esw_sched_node *parent, struct netlink_ext_ack *extack) { struct mlx5_esw_sched_node *node; - u32 tsar_ix; int err; + u32 ix; - err = esw_qos_create_node_sched_elem(esw->dev, esw->qos.root_tsar_ix, 0, - 0, &tsar_ix); + err = esw_qos_create_node_sched_elem(parent->esw->dev, parent->ix, 0, 0, + &ix); if (err) { NL_SET_ERR_MSG_MOD(extack, "E-Switch create TSAR for node failed"); return ERR_PTR(err); } - node = __esw_qos_alloc_node(esw, tsar_ix, SCHED_NODE_TYPE_VPORTS_TSAR, parent); + node = __esw_qos_alloc_node(ix, SCHED_NODE_TYPE_VPORTS_TSAR, parent); if (!node) { NL_SET_ERR_MSG_MOD(extack, "E-Switch alloc node failed"); err = -ENOMEM; goto err_alloc_node; } - list_add_tail(&node->entry, &esw->qos.domain->nodes); - esw_qos_normalize_min_rate(esw, NULL, extack); - trace_mlx5_esw_node_qos_create(esw->dev, node, node->ix); + esw_qos_normalize_min_rate(parent, extack); + trace_mlx5_esw_node_qos_create(parent->esw->dev, node, node->ix); return node; err_alloc_node: - if (mlx5_destroy_scheduling_element_cmd(esw->dev, + if (mlx5_destroy_scheduling_element_cmd(parent->esw->dev, SCHEDULING_HIERARCHY_E_SWITCH, - tsar_ix)) + ix)) NL_SET_ERR_MSG_MOD(extack, "E-Switch destroy TSAR for node failed"); return ERR_PTR(err); } @@ -746,7 +708,7 @@ esw_qos_create_vports_sched_node(struct mlx5_eswitch *esw, struct netlink_ext_ac if (err) return ERR_PTR(err); - node = __esw_qos_create_vports_sched_node(esw, NULL, extack); + node = __esw_qos_create_vports_sched_node(esw->qos.root, extack); if (IS_ERR(node)) esw_qos_put(esw); @@ -762,38 +724,47 @@ static void __esw_qos_destroy_node(struct mlx5_esw_sched_node *node, struct netl trace_mlx5_esw_node_qos_destroy(esw->dev, node, node->ix); esw_qos_destroy_node(node, extack); - esw_qos_normalize_min_rate(esw, NULL, extack); + esw_qos_normalize_min_rate(esw->qos.root, extack); } static int esw_qos_create(struct mlx5_eswitch *esw, struct netlink_ext_ack *extack) { struct mlx5_core_dev *dev = esw->dev; + struct mlx5_esw_sched_node *root; + u32 root_ix; int err; if (!MLX5_CAP_GEN(dev, qos) || !MLX5_CAP_QOS(dev, esw_scheduling)) return -EOPNOTSUPP; - err = esw_qos_create_node_sched_elem(esw->dev, 0, 0, 0, - &esw->qos.root_tsar_ix); + err = esw_qos_create_node_sched_elem(esw->dev, 0, 0, 0, &root_ix); if (err) { esw_warn(dev, "E-Switch create root TSAR failed (%d)\n", err); return err; } + root = __esw_qos_alloc_node(root_ix, SCHED_NODE_TYPE_ROOT, NULL); + if (!root) { + esw_warn(dev, "E-Switch create root node failed\n"); + err = -ENOMEM; + goto out_err; + } + root->esw = esw; + root->level = 1; + esw->qos.root = root; refcount_set(&esw->qos.refcnt, 1); return 0; +out_err: + mlx5_destroy_scheduling_element_cmd(dev, SCHEDULING_HIERARCHY_E_SWITCH, + root_ix); + return err; } static void esw_qos_destroy(struct mlx5_eswitch *esw) { - int err; - - err = mlx5_destroy_scheduling_element_cmd(esw->dev, - SCHEDULING_HIERARCHY_E_SWITCH, - esw->qos.root_tsar_ix); - if (err) - esw_warn(esw->dev, "E-Switch destroy root TSAR failed (%d)\n", err); + esw_qos_destroy_node(esw->qos.root, NULL); + esw->qos.root = NULL; } static int esw_qos_get(struct mlx5_eswitch *esw, struct netlink_ext_ack *extack) @@ -866,8 +837,7 @@ esw_qos_create_vport_tc_sched_node(struct mlx5_vport *vport, u8 tc = vports_tc_node->tc; int err; - vport_tc_node = __esw_qos_alloc_node(vport_node->esw, 0, - SCHED_NODE_TYPE_VPORT_TC, + vport_tc_node = __esw_qos_alloc_node(0, SCHED_NODE_TYPE_VPORT_TC, vports_tc_node); if (!vport_tc_node) return -ENOMEM; @@ -959,7 +929,7 @@ esw_qos_vport_tc_enable(struct mlx5_vport *vport, enum sched_node_type type, /* Increase the parent's level by 2 to account for both the * TC arbiter and the vports TC scheduling element. */ - new_level = (parent ? parent->level : 2) + 2; + new_level = parent->level + 2; max_level = 1 << MLX5_CAP_QOS(vport_node->esw->dev, log_esw_max_sched_depth); if (new_level > max_level) { @@ -997,7 +967,7 @@ esw_qos_vport_tc_enable(struct mlx5_vport *vport, enum sched_node_type type, err_sched_nodes: if (type == SCHED_NODE_TYPE_RATE_LIMITER) { esw_qos_node_destroy_sched_element(vport_node, NULL); - esw_qos_node_attach_to_parent(vport_node); + esw_qos_node_set_parent(vport_node, vport_node->parent); } else { esw_qos_tc_arbiter_scheduling_teardown(vport_node, NULL); } @@ -1055,7 +1025,7 @@ static void esw_qos_vport_disable(struct mlx5_vport *vport, struct netlink_ext_a vport_node->bw_share = 0; memset(vport_node->tc_bw, 0, sizeof(vport_node->tc_bw)); list_del_init(&vport_node->entry); - esw_qos_normalize_min_rate(vport_node->esw, vport_node->parent, extack); + esw_qos_normalize_min_rate(vport_node->parent, extack); trace_mlx5_esw_vport_qos_destroy(vport_node->esw->dev, vport); } @@ -1068,7 +1038,7 @@ static int esw_qos_vport_enable(struct mlx5_vport *vport, struct mlx5_esw_sched_node *vport_node = vport->qos.sched_node; int err; - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport_node->esw); esw_qos_node_set_parent(vport_node, parent); if (type == SCHED_NODE_TYPE_VPORT) @@ -1079,7 +1049,7 @@ static int esw_qos_vport_enable(struct mlx5_vport *vport, return err; vport_node->type = type; - esw_qos_normalize_min_rate(vport_node->esw, parent, extack); + esw_qos_normalize_min_rate(parent, extack); trace_mlx5_esw_vport_qos_create(vport->dev, vport, vport_node->max_rate, vport_node->bw_share); @@ -1092,7 +1062,6 @@ static int mlx5_esw_qos_vport_enable(struct mlx5_vport *vport, enum sched_node_t { struct mlx5_eswitch *esw = vport->dev->priv.eswitch; struct mlx5_esw_sched_node *sched_node; - struct mlx5_eswitch *parent_esw; int err; esw_assert_qos_lock_held(esw); @@ -1100,14 +1069,13 @@ static int mlx5_esw_qos_vport_enable(struct mlx5_vport *vport, enum sched_node_t if (err) return err; - parent_esw = parent ? parent->esw : esw; - sched_node = __esw_qos_alloc_node(parent_esw, 0, type, parent); + if (!parent) + parent = esw->qos.root; + sched_node = __esw_qos_alloc_node(0, type, parent); if (!sched_node) { esw_qos_put(esw); return -ENOMEM; } - if (!parent) - list_add_tail(&sched_node->entry, &esw->qos.domain->nodes); sched_node->max_rate = max_rate; sched_node->min_rate = min_rate; @@ -1279,10 +1247,9 @@ static int esw_qos_vport_update_parent(struct mlx5_vport *vport, struct mlx5_esw /* Set vport QoS type based on parent node type if different from * default QoS; otherwise, use the vport's current QoS type. */ - if (parent && parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) + if (parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) type = SCHED_NODE_TYPE_RATE_LIMITER; - else if (curr_parent && - curr_parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) + else if (curr_parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) type = SCHED_NODE_TYPE_VPORT; else type = vport->qos.sched_node->type; @@ -1311,11 +1278,9 @@ static int esw_qos_switch_tc_arbiter_node_to_vports( struct mlx5_esw_sched_node *node, struct netlink_ext_ack *extack) { - u32 parent_tsar_ix = node->parent ? - node->parent->ix : node->esw->qos.root_tsar_ix; int err; - err = esw_qos_create_node_sched_elem(node->esw->dev, parent_tsar_ix, + err = esw_qos_create_node_sched_elem(node->esw->dev, node->parent->ix, node->max_rate, node->bw_share, &node->ix); if (err) { @@ -1370,8 +1335,8 @@ esw_qos_move_node(struct mlx5_esw_sched_node *curr_node) { struct mlx5_esw_sched_node *new_node; - new_node = __esw_qos_alloc_node(curr_node->esw, curr_node->ix, - curr_node->type, NULL); + new_node = __esw_qos_alloc_node(curr_node->ix, curr_node->type, + curr_node->parent); if (!new_node) return ERR_PTR(-ENOMEM); @@ -1595,9 +1560,8 @@ static bool esw_qos_vport_validate_unsupported_tc_bw(struct mlx5_vport *vport, u32 *tc_bw) { struct mlx5_esw_sched_node *node = vport->qos.sched_node; - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; - - esw = (node && node->parent) ? node->parent->esw : esw; + struct mlx5_eswitch *esw = node ? + node->parent->esw : vport->dev->priv.eswitch; return esw_qos_validate_unsupported_tc_bw(esw, tc_bw); } @@ -1622,8 +1586,9 @@ static void esw_vport_qos_prune_empty(struct mlx5_vport *vport) if (!vport_node) return; - if (vport_node->parent || vport_node->max_rate || - vport_node->min_rate || !esw_qos_tc_bw_disabled(vport_node->tc_bw)) + if (vport_node->parent != vport_node->esw->qos.root || + vport_node->max_rate || vport_node->min_rate || + !esw_qos_tc_bw_disabled(vport_node->tc_bw)) return; mlx5_esw_qos_vport_disable_locked(vport); @@ -1880,7 +1845,9 @@ mlx5_esw_qos_vport_update_parent(struct mlx5_vport *vport, err = mlx5_esw_qos_vport_enable(vport, type, parent, 0, 0, extack); } else if (vport->qos.sched_node) { - err = esw_qos_vport_update_parent(vport, parent, extack); + err = esw_qos_vport_update_parent(vport, + parent ? : esw->qos.root, + extack); } esw_qos_unlock(esw); return err; @@ -1928,7 +1895,7 @@ mlx5_esw_qos_node_validate_set_parent(struct mlx5_esw_sched_node *node, { u8 new_level, max_level; - if (parent && parent->esw != node->esw) { + if (parent->esw != node->esw) { NL_SET_ERR_MSG_MOD(extack, "Cannot assign node to another E-Switch"); return -EOPNOTSUPP; @@ -1940,13 +1907,13 @@ mlx5_esw_qos_node_validate_set_parent(struct mlx5_esw_sched_node *node, return -EOPNOTSUPP; } - if (parent && parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) { + if (parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) { NL_SET_ERR_MSG_MOD(extack, "Cannot attach a node to a parent with TC bandwidth configured"); return -EOPNOTSUPP; } - new_level = parent ? parent->level + 1 : 2; + new_level = parent->level + 1; if (node->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) { /* Increase by one to account for the vports TC scheduling * element. @@ -1997,14 +1964,12 @@ static int esw_qos_vports_node_update_parent(struct mlx5_esw_sched_node *node, { struct mlx5_esw_sched_node *curr_parent = node->parent; struct mlx5_eswitch *esw = node->esw; - u32 parent_ix; int err; - parent_ix = parent ? parent->ix : node->esw->qos.root_tsar_ix; mlx5_destroy_scheduling_element_cmd(esw->dev, SCHEDULING_HIERARCHY_E_SWITCH, node->ix); - err = esw_qos_create_node_sched_elem(esw->dev, parent_ix, + err = esw_qos_create_node_sched_elem(esw->dev, parent->ix, node->max_rate, 0, &node->ix); if (err) { NL_SET_ERR_MSG_MOD(extack, @@ -2031,12 +1996,15 @@ static int mlx5_esw_qos_node_update_parent(struct mlx5_esw_sched_node *node, struct mlx5_eswitch *esw = node->esw; int err; - err = mlx5_esw_qos_node_validate_set_parent(node, parent, extack); - if (err) - return err; - esw_qos_lock(esw); curr_parent = node->parent; + if (!parent) + parent = esw->qos.root; + + err = mlx5_esw_qos_node_validate_set_parent(node, parent, extack); + if (err) + goto out; + if (node->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) { err = esw_qos_tc_arbiter_node_update_parent(node, parent, extack); @@ -2047,8 +2015,8 @@ static int mlx5_esw_qos_node_update_parent(struct mlx5_esw_sched_node *node, if (err) goto out; - esw_qos_normalize_min_rate(esw, curr_parent, extack); - esw_qos_normalize_min_rate(esw, parent, extack); + esw_qos_normalize_min_rate(curr_parent, extack); + esw_qos_normalize_min_rate(parent, extack); out: esw_qos_unlock(esw); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h index 140343f2b913..10c4eacd43b4 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h @@ -415,8 +415,9 @@ struct mlx5_eswitch { struct { /* Initially 0, meaning no QoS users and QoS is disabled. */ refcount_t refcnt; - u32 root_tsar_ix; struct mlx5_qos_domain *domain; + /* The root node of the hierarchy. */ + struct mlx5_esw_sched_node *root; } qos; struct mlx5_esw_bridge_offloads *br_offloads; From 450ed6b182de3a56cc5877464ddabe9938e7ccfa Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Wed, 1 Jul 2026 10:32:51 +0300 Subject: [PATCH 0210/1433] net/mlx5: qos: Remove qos domains and use shd E-Switch QoS domains were added with the intention of eventually implementing shared qos domains to support cross-esw scheduling in the previous approach ([1]), but they are no longer necessary in the new approach. Remove QoS domains and switch to using the shd lock for protecting against concurrent QoS modifications. Enable the supported_cross_device_rate_nodes devlink ops attribute so that all calls originating from devlink rate acquire the shd lock. Only the additional entry points into QoS need to acquire the shd lock. The wrinkle is that since shd can be NULL (e.g. on older HW without serial number available), there needs to be a fallback locking mechanism. The devlink instance lock cannot be used, as some code paths into QoS (get, set & modify vport rate) happen with RTNL held, and the existing devlink -> RTNL order prevents devlink lock usage there. The other two options are either esw->state_lock or a new lock as fallback when shd is NULL. This patch adds esw->state_lock, which implies: - 3 new lock/unlock helper pairs to acquire/release the missing lock: - esw_qos_{,un}lock: acquire/release esw->state_lock when shd is NULL. - esw_qos_shd_{,un}lock: when esw->state_lock is already held. - esw_qos_devlink_{,un}lock: when shd is already held. - esw_assert_qos_lock_held now asserts esw->state_lock is held when shd is NULL. Use the corresponding lock/unlock function in all places where either shd or state_lock would need to be acquired. Document all of this trickery next to esw_assert_qos_lock_held. Enabling supported_cross_device_rate_nodes now is safe, because mlx5_esw_qos_vport_update_parent rejects cross-esw parent updates. This will change in the next patch. [1] https://lore.kernel.org/netdev/20250213180134.323929-1-tariqt@nvidia.com/ Signed-off-by: Cosmin Ratiu Reviewed-by: Carolina Jubran Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260701073254.754518-12-tariqt@nvidia.com Signed-off-by: Paolo Abeni --- .../net/ethernet/mellanox/mlx5/core/devlink.c | 1 + .../net/ethernet/mellanox/mlx5/core/esw/qos.c | 245 ++++++++---------- .../net/ethernet/mellanox/mlx5/core/esw/qos.h | 3 - .../net/ethernet/mellanox/mlx5/core/eswitch.c | 8 - .../net/ethernet/mellanox/mlx5/core/eswitch.h | 13 +- 5 files changed, 120 insertions(+), 150 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/devlink.c b/drivers/net/ethernet/mellanox/mlx5/core/devlink.c index c31e05529fc4..b9026cc64383 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/devlink.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/devlink.c @@ -383,6 +383,7 @@ static const struct devlink_ops mlx5_devlink_ops = { .rate_node_del = mlx5_esw_devlink_rate_node_del, .rate_leaf_parent_set = mlx5_esw_devlink_rate_leaf_parent_set, .rate_node_parent_set = mlx5_esw_devlink_rate_node_parent_set, + .supported_cross_device_rate_nodes = true, #endif #ifdef CONFIG_MLX5_SF_MANAGER .port_new = mlx5_devlink_sf_port_new, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c index 49c8ec0dac9a..80a28596349b 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c @@ -11,53 +11,6 @@ /* Minimum supported BW share value by the HW is 1 Mbit/sec */ #define MLX5_MIN_BW_SHARE 1 -/* Holds rate nodes associated with an E-Switch. */ -struct mlx5_qos_domain { - /* Serializes access to all qos changes in the qos domain. */ - struct mutex lock; -}; - -static void esw_qos_lock(struct mlx5_eswitch *esw) -{ - mutex_lock(&esw->qos.domain->lock); -} - -static void esw_qos_unlock(struct mlx5_eswitch *esw) -{ - mutex_unlock(&esw->qos.domain->lock); -} - -static void esw_assert_qos_lock_held(struct mlx5_eswitch *esw) -{ - lockdep_assert_held(&esw->qos.domain->lock); -} - -static struct mlx5_qos_domain *esw_qos_domain_alloc(void) -{ - struct mlx5_qos_domain *qos_domain; - - qos_domain = kzalloc_obj(*qos_domain); - if (!qos_domain) - return NULL; - - mutex_init(&qos_domain->lock); - - return qos_domain; -} - -static int esw_qos_domain_init(struct mlx5_eswitch *esw) -{ - esw->qos.domain = esw_qos_domain_alloc(); - - return esw->qos.domain ? 0 : -ENOMEM; -} - -static void esw_qos_domain_release(struct mlx5_eswitch *esw) -{ - kfree(esw->qos.domain); - esw->qos.domain = NULL; -} - enum sched_node_type { SCHED_NODE_TYPE_ROOT, SCHED_NODE_TYPE_VPORTS_TSAR, @@ -104,6 +57,65 @@ struct mlx5_esw_sched_node { u32 tc_bw[DEVLINK_RATE_TCS_MAX]; }; +/* Locking notes: + * QoS changes are normally protected by the shd lock. But on older HW shd + * might not be created at all, so there needs to be a fallback serialization + * mechanism. This is esw->state_lock. + * Callers into QoS hold a combination of RTNL, devlink instance lock and + * esw->state_lock. Devlink rate ops additionally hold the shd lock if it + * exists. + * - VF rate ops use esw_qos_lock/esw_qos_unlock. + * - callers with esw->state_lock held use esw_qos_shd_lock/esw_qos_shd_unlock. + * - devlink callers use esw_qos_devlink_lock/esw_qos_devlink_unlock. + */ +static void esw_assert_qos_lock_held(struct mlx5_core_dev *dev) +{ + if (dev->shd) + devl_assert_locked(dev->shd); + else + lockdep_assert_held(&dev->priv.eswitch->state_lock); +} + +static void esw_qos_lock(struct mlx5_core_dev *dev) +{ + if (dev->shd) + devl_lock(dev->shd); + else + mutex_lock(&dev->priv.eswitch->state_lock); +} + +static void esw_qos_unlock(struct mlx5_core_dev *dev) +{ + if (dev->shd) + devl_unlock(dev->shd); + else + mutex_unlock(&dev->priv.eswitch->state_lock); +} + +static void esw_qos_shd_lock(struct mlx5_core_dev *dev) +{ + if (dev->shd) + devl_lock(dev->shd); +} + +static void esw_qos_shd_unlock(struct mlx5_core_dev *dev) +{ + if (dev->shd) + devl_unlock(dev->shd); +} + +static void esw_qos_devlink_lock(struct mlx5_core_dev *dev) +{ + if (!dev->shd) + mutex_lock(&dev->priv.eswitch->state_lock); +} + +static void esw_qos_devlink_unlock(struct mlx5_core_dev *dev) +{ + if (!dev->shd) + mutex_unlock(&dev->priv.eswitch->state_lock); +} + static int esw_qos_num_tcs(struct mlx5_core_dev *dev) { int num_tcs = mlx5_max_tc(dev) + 1; @@ -700,7 +712,7 @@ esw_qos_create_vports_sched_node(struct mlx5_eswitch *esw, struct netlink_ext_ac struct mlx5_esw_sched_node *node; int err; - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(esw->dev); if (!MLX5_CAP_QOS(esw->dev, log_esw_max_sched_depth)) return ERR_PTR(-EOPNOTSUPP); @@ -771,7 +783,7 @@ static int esw_qos_get(struct mlx5_eswitch *esw, struct netlink_ext_ack *extack) { int err = 0; - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(esw->dev); if (!refcount_inc_not_zero(&esw->qos.refcnt)) { /* esw_qos_create() set refcount to 1 only on success. * No need to decrement on failure. @@ -784,7 +796,7 @@ static int esw_qos_get(struct mlx5_eswitch *esw, struct netlink_ext_ack *extack) static void esw_qos_put(struct mlx5_eswitch *esw) { - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(esw->dev); if (refcount_dec_and_test(&esw->qos.refcnt)) esw_qos_destroy(esw); } @@ -940,7 +952,7 @@ esw_qos_vport_tc_enable(struct mlx5_vport *vport, enum sched_node_type type, } } - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); if (type == SCHED_NODE_TYPE_RATE_LIMITER) err = esw_qos_create_rate_limit_element(vport_node, extack); @@ -1038,7 +1050,7 @@ static int esw_qos_vport_enable(struct mlx5_vport *vport, struct mlx5_esw_sched_node *vport_node = vport->qos.sched_node; int err; - esw_assert_qos_lock_held(vport_node->esw); + esw_assert_qos_lock_held(vport->dev); esw_qos_node_set_parent(vport_node, parent); if (type == SCHED_NODE_TYPE_VPORT) @@ -1064,7 +1076,7 @@ static int mlx5_esw_qos_vport_enable(struct mlx5_vport *vport, enum sched_node_t struct mlx5_esw_sched_node *sched_node; int err; - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(vport->dev); err = esw_qos_get(esw, extack); if (err) return err; @@ -1093,15 +1105,13 @@ static int mlx5_esw_qos_vport_enable(struct mlx5_vport *vport, enum sched_node_t static void mlx5_esw_qos_vport_disable_locked(struct mlx5_vport *vport) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; - - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(vport->dev); if (!vport->qos.sched_node) return; esw_qos_vport_disable(vport, NULL); mlx5_esw_qos_vport_qos_free(vport); - esw_qos_put(esw); + esw_qos_put(vport->dev->priv.eswitch); } void mlx5_esw_qos_vport_disable(struct mlx5_vport *vport) @@ -1109,9 +1119,9 @@ void mlx5_esw_qos_vport_disable(struct mlx5_vport *vport) struct mlx5_eswitch *esw = vport->dev->priv.eswitch; lockdep_assert_held(&esw->state_lock); - esw_qos_lock(esw); + esw_qos_shd_lock(vport->dev); mlx5_esw_qos_vport_disable_locked(vport); - esw_qos_unlock(esw); + esw_qos_shd_unlock(vport->dev); } static int mlx5_esw_qos_set_vport_max_rate(struct mlx5_vport *vport, u32 max_rate, @@ -1119,7 +1129,7 @@ static int mlx5_esw_qos_set_vport_max_rate(struct mlx5_vport *vport, u32 max_rat { struct mlx5_esw_sched_node *vport_node = vport->qos.sched_node; - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); if (!vport_node) return mlx5_esw_qos_vport_enable(vport, SCHED_NODE_TYPE_VPORT, NULL, max_rate, 0, @@ -1134,7 +1144,7 @@ static int mlx5_esw_qos_set_vport_min_rate(struct mlx5_vport *vport, u32 min_rat { struct mlx5_esw_sched_node *vport_node = vport->qos.sched_node; - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); if (!vport_node) return mlx5_esw_qos_vport_enable(vport, SCHED_NODE_TYPE_VPORT, NULL, 0, min_rate, @@ -1147,29 +1157,27 @@ static int mlx5_esw_qos_set_vport_min_rate(struct mlx5_vport *vport, u32 min_rat int mlx5_esw_qos_set_vport_rate(struct mlx5_vport *vport, u32 max_rate, u32 min_rate) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; int err; - esw_qos_lock(esw); + esw_qos_lock(vport->dev); err = mlx5_esw_qos_set_vport_min_rate(vport, min_rate, NULL); if (!err) err = mlx5_esw_qos_set_vport_max_rate(vport, max_rate, NULL); - esw_qos_unlock(esw); + esw_qos_unlock(vport->dev); return err; } bool mlx5_esw_qos_get_vport_rate(struct mlx5_vport *vport, u32 *max_rate, u32 *min_rate) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; bool enabled; - esw_qos_lock(esw); + esw_qos_shd_lock(vport->dev); enabled = !!vport->qos.sched_node; if (enabled) { *max_rate = vport->qos.sched_node->max_rate; *min_rate = vport->qos.sched_node->min_rate; } - esw_qos_unlock(esw); + esw_qos_shd_unlock(vport->dev); return enabled; } @@ -1205,7 +1213,7 @@ static int esw_qos_vport_update(struct mlx5_vport *vport, u32 curr_tc_bw[DEVLINK_RATE_TCS_MAX] = {0}; int err; - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); if (curr_type == type && curr_parent == parent) return 0; @@ -1235,11 +1243,10 @@ static int esw_qos_vport_update(struct mlx5_vport *vport, static int esw_qos_vport_update_parent(struct mlx5_vport *vport, struct mlx5_esw_sched_node *parent, struct netlink_ext_ack *extack) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; struct mlx5_esw_sched_node *curr_parent; enum sched_node_type type; - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(vport->dev); curr_parent = vport->qos.sched_node->parent; if (curr_parent == parent) return 0; @@ -1503,9 +1510,9 @@ int mlx5_esw_qos_modify_vport_rate(struct mlx5_eswitch *esw, u16 vport_num, u32 return err; } - esw_qos_lock(esw); + esw_qos_lock(vport->dev); err = mlx5_esw_qos_set_vport_max_rate(vport, rate_mbps, NULL); - esw_qos_unlock(esw); + esw_qos_unlock(vport->dev); return err; } @@ -1582,7 +1589,7 @@ static void esw_vport_qos_prune_empty(struct mlx5_vport *vport) { struct mlx5_esw_sched_node *vport_node = vport->qos.sched_node; - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); if (!vport_node) return; @@ -1594,44 +1601,26 @@ static void esw_vport_qos_prune_empty(struct mlx5_vport *vport) mlx5_esw_qos_vport_disable_locked(vport); } -int mlx5_esw_qos_init(struct mlx5_eswitch *esw) -{ - if (esw->qos.domain) - return 0; /* Nothing to change. */ - - return esw_qos_domain_init(esw); -} - -void mlx5_esw_qos_cleanup(struct mlx5_eswitch *esw) -{ - if (esw->qos.domain) - esw_qos_domain_release(esw); -} - /* Eswitch devlink rate API */ int mlx5_esw_devlink_rate_leaf_tx_share_set(struct devlink_rate *rate_leaf, void *priv, u64 tx_share, struct netlink_ext_ack *extack) { struct mlx5_vport *vport = priv; - struct mlx5_eswitch *esw; int err; - esw = vport->dev->priv.eswitch; - if (!mlx5_esw_allowed(esw)) + if (!mlx5_esw_allowed(vport->dev->priv.eswitch)) return -EPERM; err = esw_qos_devlink_rate_to_mbps(vport->dev, "tx_share", &tx_share, extack); if (err) return err; - esw_qos_lock(esw); + esw_qos_devlink_lock(vport->dev); err = mlx5_esw_qos_set_vport_min_rate(vport, tx_share, extack); - if (err) - goto out; - esw_vport_qos_prune_empty(vport); -out: - esw_qos_unlock(esw); + if (!err) + esw_vport_qos_prune_empty(vport); + esw_qos_devlink_unlock(vport->dev); return err; } @@ -1639,24 +1628,20 @@ int mlx5_esw_devlink_rate_leaf_tx_max_set(struct devlink_rate *rate_leaf, void * u64 tx_max, struct netlink_ext_ack *extack) { struct mlx5_vport *vport = priv; - struct mlx5_eswitch *esw; int err; - esw = vport->dev->priv.eswitch; - if (!mlx5_esw_allowed(esw)) + if (!mlx5_esw_allowed(vport->dev->priv.eswitch)) return -EPERM; err = esw_qos_devlink_rate_to_mbps(vport->dev, "tx_max", &tx_max, extack); if (err) return err; - esw_qos_lock(esw); + esw_qos_devlink_lock(vport->dev); err = mlx5_esw_qos_set_vport_max_rate(vport, tx_max, extack); - if (err) - goto out; - esw_vport_qos_prune_empty(vport); -out: - esw_qos_unlock(esw); + if (!err) + esw_vport_qos_prune_empty(vport); + esw_qos_devlink_unlock(vport->dev); return err; } @@ -1667,16 +1652,14 @@ int mlx5_esw_devlink_rate_leaf_tc_bw_set(struct devlink_rate *rate_leaf, { struct mlx5_esw_sched_node *vport_node; struct mlx5_vport *vport = priv; - struct mlx5_eswitch *esw; bool disable; int err = 0; - esw = vport->dev->priv.eswitch; - if (!mlx5_esw_allowed(esw)) + if (!mlx5_esw_allowed(vport->dev->priv.eswitch)) return -EPERM; disable = esw_qos_tc_bw_disabled(tc_bw); - esw_qos_lock(esw); + esw_qos_devlink_lock(vport->dev); if (!esw_qos_vport_validate_unsupported_tc_bw(vport, tc_bw)) { NL_SET_ERR_MSG_MOD(extack, @@ -1710,7 +1693,7 @@ int mlx5_esw_devlink_rate_leaf_tc_bw_set(struct devlink_rate *rate_leaf, if (!err) esw_qos_set_tc_arbiter_bw_shares(vport_node, tc_bw, extack); unlock: - esw_qos_unlock(esw); + esw_qos_devlink_unlock(vport->dev); return err; } @@ -1720,18 +1703,17 @@ int mlx5_esw_devlink_rate_node_tc_bw_set(struct devlink_rate *rate_node, struct netlink_ext_ack *extack) { struct mlx5_esw_sched_node *node = priv; - struct mlx5_eswitch *esw = node->esw; bool disable; int err; - if (!esw_qos_validate_unsupported_tc_bw(esw, tc_bw)) { + if (!esw_qos_validate_unsupported_tc_bw(node->esw, tc_bw)) { NL_SET_ERR_MSG_MOD(extack, "E-Switch traffic classes number is not supported"); return -EOPNOTSUPP; } disable = esw_qos_tc_bw_disabled(tc_bw); - esw_qos_lock(esw); + esw_qos_devlink_lock(node->esw->dev); if (disable) { err = esw_qos_node_disable_tc_arbitration(node, extack); goto unlock; @@ -1741,7 +1723,7 @@ int mlx5_esw_devlink_rate_node_tc_bw_set(struct devlink_rate *rate_node, if (!err) esw_qos_set_tc_arbiter_bw_shares(node, tc_bw, extack); unlock: - esw_qos_unlock(esw); + esw_qos_devlink_unlock(node->esw->dev); return err; } @@ -1756,9 +1738,9 @@ int mlx5_esw_devlink_rate_node_tx_share_set(struct devlink_rate *rate_node, void if (err) return err; - esw_qos_lock(esw); + esw_qos_devlink_lock(esw->dev); err = esw_qos_set_node_min_rate(node, tx_share, extack); - esw_qos_unlock(esw); + esw_qos_devlink_unlock(esw->dev); return err; } @@ -1773,9 +1755,9 @@ int mlx5_esw_devlink_rate_node_tx_max_set(struct devlink_rate *rate_node, void * if (err) return err; - esw_qos_lock(esw); + esw_qos_devlink_lock(esw->dev); err = esw_qos_sched_elem_config(node, tx_max, node->bw_share, extack); - esw_qos_unlock(esw); + esw_qos_devlink_unlock(esw->dev); return err; } @@ -1790,7 +1772,7 @@ int mlx5_esw_devlink_rate_node_new(struct devlink_rate *rate_node, void **priv, if (IS_ERR(esw)) return PTR_ERR(esw); - esw_qos_lock(esw); + esw_qos_devlink_lock(esw->dev); if (esw->mode != MLX5_ESWITCH_OFFLOADS) { NL_SET_ERR_MSG_MOD(extack, "Rate node creation supported only in switchdev mode"); @@ -1803,10 +1785,9 @@ int mlx5_esw_devlink_rate_node_new(struct devlink_rate *rate_node, void **priv, err = PTR_ERR(node); goto unlock; } - *priv = node; unlock: - esw_qos_unlock(esw); + esw_qos_devlink_unlock(esw->dev); return err; } @@ -1816,10 +1797,11 @@ int mlx5_esw_devlink_rate_node_del(struct devlink_rate *rate_node, void *priv, struct mlx5_esw_sched_node *node = priv; struct mlx5_eswitch *esw = node->esw; - esw_qos_lock(esw); + esw_qos_devlink_lock(esw->dev); __esw_qos_destroy_node(node, extack); esw_qos_put(esw); - esw_qos_unlock(esw); + esw_qos_devlink_unlock(esw->dev); + return 0; } @@ -1836,7 +1818,6 @@ mlx5_esw_qos_vport_update_parent(struct mlx5_vport *vport, return -EOPNOTSUPP; } - esw_qos_lock(esw); if (!vport->qos.sched_node && parent) { enum sched_node_type type; @@ -1849,7 +1830,7 @@ mlx5_esw_qos_vport_update_parent(struct mlx5_vport *vport, parent ? : esw->qos.root, extack); } - esw_qos_unlock(esw); + return err; } @@ -1862,14 +1843,11 @@ int mlx5_esw_devlink_rate_leaf_parent_set(struct devlink_rate *devlink_rate, struct mlx5_vport *vport = priv; int err; + esw_qos_devlink_lock(vport->dev); err = mlx5_esw_qos_vport_update_parent(vport, node, extack); - if (!err) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; - - esw_qos_lock(esw); + if (!err) esw_vport_qos_prune_empty(vport); - esw_qos_unlock(esw); - } + esw_qos_devlink_unlock(vport->dev); return err; } @@ -1996,7 +1974,7 @@ static int mlx5_esw_qos_node_update_parent(struct mlx5_esw_sched_node *node, struct mlx5_eswitch *esw = node->esw; int err; - esw_qos_lock(esw); + esw_qos_devlink_lock(esw->dev); curr_parent = node->parent; if (!parent) parent = esw->qos.root; @@ -2019,8 +1997,7 @@ static int mlx5_esw_qos_node_update_parent(struct mlx5_esw_sched_node *node, esw_qos_normalize_min_rate(parent, extack); out: - esw_qos_unlock(esw); - + esw_qos_devlink_unlock(esw->dev); return err; } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.h b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.h index 0a50982b0e27..f275e850d2c9 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.h @@ -6,9 +6,6 @@ #ifdef CONFIG_MLX5_ESWITCH -int mlx5_esw_qos_init(struct mlx5_eswitch *esw); -void mlx5_esw_qos_cleanup(struct mlx5_eswitch *esw); - int mlx5_esw_qos_set_vport_rate(struct mlx5_vport *evport, u32 max_rate, u32 min_rate); bool mlx5_esw_qos_get_vport_rate(struct mlx5_vport *vport, u32 *max_rate, u32 *min_rate); void mlx5_esw_qos_vport_disable(struct mlx5_vport *vport); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c index b67f15a8f766..b6e2c153b4f7 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c @@ -1885,10 +1885,6 @@ int mlx5_eswitch_enable_locked(struct mlx5_eswitch *esw, int num_vfs) MLX5_NB_INIT(&esw->nb, eswitch_vport_event, NIC_VPORT_CHANGE); mlx5_eq_notifier_register(esw->dev, &esw->nb); - err = mlx5_esw_qos_init(esw); - if (err) - goto err_esw_init; - if (esw->mode == MLX5_ESWITCH_LEGACY) { err = esw_legacy_enable(esw); } else { @@ -2555,9 +2551,6 @@ int mlx5_eswitch_init(struct mlx5_core_dev *dev) goto reps_err; esw->mode = MLX5_ESWITCH_LEGACY; - err = mlx5_esw_qos_init(esw); - if (err) - goto reps_err; mutex_init(&esw->offloads.encap_tbl_lock); hash_init(esw->offloads.encap_tbl); @@ -2612,7 +2605,6 @@ void mlx5_eswitch_cleanup(struct mlx5_eswitch *esw) mlx5_eswitch_invalidate_wq(esw); destroy_workqueue(esw->work_queue); - mlx5_esw_qos_cleanup(esw); WARN_ON(refcount_read(&esw->qos.refcnt)); mutex_destroy(&esw->state_lock); WARN_ON(!xa_empty(&esw->offloads.vhca_map)); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h index 10c4eacd43b4..c655f6e8da1c 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h @@ -234,8 +234,10 @@ struct mlx5_vport { struct mlx5_vport_info info; - /* Protected with the E-Switch qos domain lock. The Vport QoS can - * either be disabled (sched_node is NULL) or in one of three states: + /* Protected by either the shared devlink (dev->shd) lock or by + * esw->state_lock. See esw_assert_qos_lock_held() for more details. + * The Vport QoS can either be disabled (sched_node is NULL) or in one + * of three states: * 1. Regular QoS (sched_node is a vport node). * 2. TC QoS enabled on the vport (sched_node is a TC arbiter). * 3. TC QoS enabled on the vport's parent node @@ -382,7 +384,6 @@ enum { }; struct dentry; -struct mlx5_qos_domain; struct mlx5_eswitch { struct mlx5_core_dev *dev; @@ -411,11 +412,13 @@ struct mlx5_eswitch { atomic64_t user_count; wait_queue_head_t work_queue_wait; - /* Protected with the E-Switch qos domain lock. */ + /* QoS changes are serialized by either the shared devlink (dev->shd) + * lock or by esw->state_lock. See esw_assert_qos_lock_held() for more + * details. + */ struct { /* Initially 0, meaning no QoS users and QoS is disabled. */ refcount_t refcnt; - struct mlx5_qos_domain *domain; /* The root node of the hierarchy. */ struct mlx5_esw_sched_node *root; } qos; From 2bc38232047c051e1f1cfc9299b0231a607d3adc Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Wed, 1 Jul 2026 10:32:52 +0300 Subject: [PATCH 0211/1433] net/mlx5: qos: Support cross-device tx scheduling Up to now, rate groups could only contain vports from the same E-Switch. This patch relaxes that restriction if the device supports it (HCA_CAP.esw_cross_esw_sched == true) and the right conditions are met: - Link Aggregation (LAG) is enabled. - The E-Switches are from the same shared devlink device. Signed-off-by: Cosmin Ratiu Reviewed-by: Carolina Jubran Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260701073254.754518-13-tariqt@nvidia.com Signed-off-by: Paolo Abeni --- .../net/ethernet/mellanox/mlx5/core/esw/qos.c | 120 +++++++++++++----- 1 file changed, 85 insertions(+), 35 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c index 80a28596349b..0d20f51b9702 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c @@ -45,7 +45,9 @@ struct mlx5_esw_sched_node { enum sched_node_type type; /* The eswitch this node belongs to. */ struct mlx5_eswitch *esw; - /* The children nodes of this node, empty list for leaf nodes. */ + /* The children nodes of this node, empty list for leaf nodes. + * Can be from multiple E-Switches. + */ struct list_head children; /* Valid only if this node is associated with a vport. */ struct mlx5_vport *vport; @@ -447,6 +449,7 @@ esw_qos_vport_create_sched_element(struct mlx5_esw_sched_node *vport_node, struct mlx5_esw_sched_node *parent = vport_node->parent; u32 sched_ctx[MLX5_ST_SZ_DW(scheduling_context)] = {}; struct mlx5_core_dev *dev = vport_node->esw->dev; + struct mlx5_vport *vport = vport_node->vport; void *attr; if (!mlx5_qos_element_type_supported( @@ -458,10 +461,17 @@ esw_qos_vport_create_sched_element(struct mlx5_esw_sched_node *vport_node, MLX5_SET(scheduling_context, sched_ctx, element_type, SCHEDULING_CONTEXT_ELEMENT_TYPE_VPORT); attr = MLX5_ADDR_OF(scheduling_context, sched_ctx, element_attributes); - MLX5_SET(vport_element, attr, vport_number, vport_node->vport->vport); + MLX5_SET(vport_element, attr, vport_number, vport->vport); MLX5_SET(scheduling_context, sched_ctx, parent_element_id, parent->ix); MLX5_SET(scheduling_context, sched_ctx, max_average_bw, vport_node->max_rate); + if (vport->dev != dev) { + /* The port is assigned to a node on another eswitch. */ + MLX5_SET(vport_element, attr, eswitch_owner_vhca_id_valid, + true); + MLX5_SET(vport_element, attr, eswitch_owner_vhca_id, + MLX5_CAP_GEN(vport->dev, vhca_id)); + } return esw_qos_node_create_sched_element(vport_node, sched_ctx, extack); } @@ -473,6 +483,7 @@ esw_qos_vport_tc_create_sched_element(struct mlx5_esw_sched_node *vport_tc_node, { u32 sched_ctx[MLX5_ST_SZ_DW(scheduling_context)] = {}; struct mlx5_core_dev *dev = vport_tc_node->esw->dev; + struct mlx5_vport *vport = vport_tc_node->vport; void *attr; if (!mlx5_qos_element_type_supported( @@ -484,8 +495,7 @@ esw_qos_vport_tc_create_sched_element(struct mlx5_esw_sched_node *vport_tc_node, MLX5_SET(scheduling_context, sched_ctx, element_type, SCHEDULING_CONTEXT_ELEMENT_TYPE_VPORT_TC); attr = MLX5_ADDR_OF(scheduling_context, sched_ctx, element_attributes); - MLX5_SET(vport_tc_element, attr, vport_number, - vport_tc_node->vport->vport); + MLX5_SET(vport_tc_element, attr, vport_number, vport->vport); MLX5_SET(vport_tc_element, attr, traffic_class, vport_tc_node->tc); MLX5_SET(scheduling_context, sched_ctx, max_bw_obj_id, rate_limit_elem_ix); @@ -493,6 +503,13 @@ esw_qos_vport_tc_create_sched_element(struct mlx5_esw_sched_node *vport_tc_node, vport_tc_node->parent->ix); MLX5_SET(scheduling_context, sched_ctx, bw_share, vport_tc_node->bw_share); + if (vport->dev != dev) { + /* The port is assigned to a node on another eswitch. */ + MLX5_SET(vport_tc_element, attr, eswitch_owner_vhca_id_valid, + true); + MLX5_SET(vport_tc_element, attr, eswitch_owner_vhca_id, + MLX5_CAP_GEN(vport->dev, vhca_id)); + } return esw_qos_node_create_sched_element(vport_tc_node, sched_ctx, extack); @@ -1062,8 +1079,9 @@ static int esw_qos_vport_enable(struct mlx5_vport *vport, vport_node->type = type; esw_qos_normalize_min_rate(parent, extack); - trace_mlx5_esw_vport_qos_create(vport->dev, vport, vport_node->max_rate, - vport_node->bw_share); + trace_mlx5_esw_vport_qos_create(vport_node->esw->dev, vport, + vport_node->bw_share, + vport_node->max_rate); return 0; } @@ -1202,6 +1220,28 @@ static int esw_qos_vport_tc_check_type(enum sched_node_type curr_type, return 0; } +static bool esw_qos_validate_unsupported_tc_bw(struct mlx5_eswitch *esw, + u32 *tc_bw) +{ + int i, num_tcs = esw_qos_num_tcs(esw->dev); + + for (i = num_tcs; i < DEVLINK_RATE_TCS_MAX; i++) + if (tc_bw[i]) + return false; + + return true; +} + +static bool esw_qos_vport_validate_unsupported_tc_bw(struct mlx5_vport *vport, + u32 *tc_bw) +{ + struct mlx5_esw_sched_node *node = vport->qos.sched_node; + struct mlx5_eswitch *esw = node ? + node->parent->esw : vport->dev->priv.eswitch; + + return esw_qos_validate_unsupported_tc_bw(esw, tc_bw); +} + static int esw_qos_vport_update(struct mlx5_vport *vport, enum sched_node_type type, struct mlx5_esw_sched_node *parent, @@ -1221,8 +1261,15 @@ static int esw_qos_vport_update(struct mlx5_vport *vport, if (err) return err; - if (curr_type == SCHED_NODE_TYPE_TC_ARBITER_TSAR && curr_type == type) + if (curr_type == SCHED_NODE_TYPE_TC_ARBITER_TSAR && curr_type == type) { esw_qos_tc_arbiter_get_bw_shares(vport_node, curr_tc_bw); + if (!esw_qos_validate_unsupported_tc_bw(parent->esw, + curr_tc_bw)) { + NL_SET_ERR_MSG_MOD(extack, + "Unsupported traffic classes on the new device"); + return -EOPNOTSUPP; + } + } esw_qos_vport_disable(vport, extack); @@ -1550,29 +1597,6 @@ static int esw_qos_devlink_rate_to_mbps(struct mlx5_core_dev *mdev, const char * return 0; } -static bool esw_qos_validate_unsupported_tc_bw(struct mlx5_eswitch *esw, - u32 *tc_bw) -{ - int i, num_tcs = esw_qos_num_tcs(esw->dev); - - for (i = num_tcs; i < DEVLINK_RATE_TCS_MAX; i++) { - if (tc_bw[i]) - return false; - } - - return true; -} - -static bool esw_qos_vport_validate_unsupported_tc_bw(struct mlx5_vport *vport, - u32 *tc_bw) -{ - struct mlx5_esw_sched_node *node = vport->qos.sched_node; - struct mlx5_eswitch *esw = node ? - node->parent->esw : vport->dev->priv.eswitch; - - return esw_qos_validate_unsupported_tc_bw(esw, tc_bw); -} - static bool esw_qos_tc_bw_disabled(u32 *tc_bw) { int i; @@ -1805,18 +1829,44 @@ int mlx5_esw_devlink_rate_node_del(struct devlink_rate *rate_node, void *priv, return 0; } +static int +mlx5_esw_validate_cross_esw_scheduling(struct mlx5_eswitch *esw, + struct mlx5_esw_sched_node *parent, + struct netlink_ext_ack *extack) +{ + if (!parent || esw == parent->esw) + return 0; + + if (!MLX5_CAP_QOS(esw->dev, esw_cross_esw_sched)) { + NL_SET_ERR_MSG_MOD(extack, + "Cross E-Switch scheduling is not supported"); + return -EOPNOTSUPP; + } + if (!esw->dev->shd || esw->dev->shd != parent->esw->dev->shd) { + NL_SET_ERR_MSG_MOD(extack, + "Cannot add vport to a parent belonging to a different device"); + return -EOPNOTSUPP; + } + if (!mlx5_lag_is_active(esw->dev)) { + NL_SET_ERR_MSG_MOD(extack, + "Cross E-Switch scheduling requires LAG to be activated"); + return -EOPNOTSUPP; + } + + return 0; +} + static int mlx5_esw_qos_vport_update_parent(struct mlx5_vport *vport, struct mlx5_esw_sched_node *parent, struct netlink_ext_ack *extack) { struct mlx5_eswitch *esw = vport->dev->priv.eswitch; - int err = 0; + int err; - if (parent && parent->esw != esw) { - NL_SET_ERR_MSG_MOD(extack, "Cross E-Switch scheduling is not supported"); - return -EOPNOTSUPP; - } + err = mlx5_esw_validate_cross_esw_scheduling(esw, parent, extack); + if (err) + return err; if (!vport->qos.sched_node && parent) { enum sched_node_type type; From b2aa7390967ee71cc7d40b849a242c27b59391ba Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Wed, 1 Jul 2026 10:32:53 +0300 Subject: [PATCH 0212/1433] selftests: drv-net: Add test for cross-esw rate scheduling Adds a Python selftest using the YNL devlink API to verify the devlink rate ops. The test requires a bond device given in the config as NETIF containing two PFs. Test setup will then create 1 VF on each PF and verify the various rate commands. ./devlink_rate_cross_esw.py TAP version 13 1..3 ok 1 devlink_rate_cross_esw.test_same_esw_parent ok 2 devlink_rate_cross_esw.test_cross_esw_parent ok 3 devlink_rate_cross_esw.test_tx_rates_on_cross_esw Tests will be skipped when the preconditions aren't met, when the devlink API is too old or when the devices don't appear to support cross-esw scheduling (detected via EOPNOTSUPP). Signed-off-by: Cosmin Ratiu Reviewed-by: Carolina Jubran Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260701073254.754518-14-tariqt@nvidia.com Signed-off-by: Paolo Abeni --- .../testing/selftests/drivers/net/hw/Makefile | 1 + .../drivers/net/hw/devlink_rate_cross_esw.py | 296 ++++++++++++++++++ 2 files changed, 297 insertions(+) create mode 100755 tools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile index fd0535a96d84..234db5c2c90c 100644 --- a/tools/testing/selftests/drivers/net/hw/Makefile +++ b/tools/testing/selftests/drivers/net/hw/Makefile @@ -20,6 +20,7 @@ TEST_GEN_FILES := \ TEST_PROGS = \ csum.py \ devlink_port_split.py \ + devlink_rate_cross_esw.py \ devlink_rate_tc_bw.py \ devmem.py \ ethtool.sh \ diff --git a/tools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py b/tools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py new file mode 100755 index 000000000000..4416f024cb76 --- /dev/null +++ b/tools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py @@ -0,0 +1,296 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +""" +Devlink Rate Cross-eswitch Scheduling Test Suite +================================================== + +Control-plane tests for cross-eswitch TX scheduling via devlink-rate. +Validates that VFs from different PFs on the same chip can share +rate groups using the cross-device parent-dev attribute. + +Preconditions: +- NETIF points to a bond device with exactly two interfaces. +- the interfaces must be two PFs from different devices sharing the same chip. +- (for mlx5): the two interfaces are in switchdev mode and configured in a LAG: + - devlink dev eswitch set $DEV1 mode switchdev + - devlink dev eswitch set $DEV2 mode switchdev + - devlink dev param set $DEV1 name esw_multiport value 1 cmode runtime + - devlink dev param set $DEV2 name esw_multiport value 1 cmode runtime +- test cases will be skipped if: + - the number of interfaces in the bond device is != 2. + - the kernel doesn't support devlink rates. + - the devlink API doesn't support cross-device parents (ENODEV). + - cross-esw rate scheduling returns EOPNOTSUPP. +""" + +import errno +import glob +import os +import time + +from lib.py import ksft_pr, ksft_eq, ksft_run, ksft_exit +from lib.py import KsftSkipEx, KsftFailEx +from lib.py import NetDrvEnv, DevlinkFamily +from lib.py import NlError +from lib.py import cmd, defer, ip, tool + + +# --- Discovery and setup --- + + +def get_bond_slaves(bond_ifname): + """Returns sorted list of slave netdev names for a bond.""" + pattern = f"/sys/class/net/{bond_ifname}/lower_*" + lowers = glob.glob(pattern) + if not lowers: + raise KsftSkipEx(f"No bond slaves for {bond_ifname}") + slaves = [] + for path in sorted(lowers): + name = os.path.basename(path) + if name.startswith("lower_"): + name = name[len("lower_"):] + slaves.append(name) + return slaves + + +def discover_pfs(cfg): + """Discovers both PFs from bond slaves.""" + slaves = get_bond_slaves(cfg.ifname) + if len(slaves) != 2: + raise KsftSkipEx(f"Need 2 bond slaves, found {len(slaves)}") + + pf0, pf1 = slaves[0], slaves[1] + ksft_pr(f"PF0: {pf0} PF1: {pf1}") + return pf0, pf1 + + +def get_pci_addr(ifname): + """Resolves PCI address for a network interface.""" + return os.path.basename(os.path.realpath(f"/sys/class/net/{ifname}/device")) + + +def get_vf_port_index(pf_pci): + """Finds devlink port-index for vf0 under pf_pci.""" + ports = tool("devlink", "port show", json=True)["port"] + for port_name, props in ports.items(): + if port_name.startswith(f"pci/{pf_pci}/") and props.get("vfnum") == 0: + return int(port_name.split("/")[-1]) + raise KsftSkipEx(f"VF port not found for {pf_pci}") + + +def cleanup_esw(pf): + """Removes VFs if created by tests.""" + cmd(f"echo 0 > /sys/class/net/{pf}/device/sriov_numvfs", shell=True, fail=False) + + +def setup_esw(pf): + """Creates 1 VF on 'pf'.""" + path = f"/sys/class/net/{pf}/device/sriov_numvfs" + cmd(f"echo 0 > {path}", shell=True) + cmd(f"echo 1 > {path}", shell=True) + defer(cleanup_esw, pf) + time.sleep(2) + + vf_dir = f"/sys/class/net/{pf}/device/virtfn0/net" + entries = os.listdir(vf_dir) if os.path.isdir(vf_dir) else [] + if not entries: + raise KsftSkipEx(f"VF not found for {pf}") + ip(f"link set dev {entries[0]} up") + + pf_pci = get_pci_addr(pf) + vf_idx = get_vf_port_index(pf_pci) + ksft_pr(f"Created VF {vf_idx} on PF {pf} ({pf_pci})") + return pf_pci, vf_idx + + +# --- Rate operation helpers --- + + +def rate_new(devnl, dev_pci, node_name, **kwargs): + """Creates rate node.""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "rate-node-name": node_name, + } + params.update(kwargs) + try: + devnl.rate_new(params) + except NlError as e: + if e.error == errno.EOPNOTSUPP: + raise KsftSkipEx("rate_new not supported") from e + raise KsftFailEx("rate_new failed") from e + + +def rate_get(devnl, dev_pci, node_name): + """Gets rate node.""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "rate-node-name": node_name, + } + return devnl.rate_get(params) + + +def rate_get_leaf(devnl, dev_pci, port_index): + """Gets rate leaf (VF).""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "port-index": port_index, + } + return devnl.rate_get(params) + + +def rate_del(devnl, dev_pci, node_name): + """Deletes rate node.""" + devnl.rate_del({ + "bus-name": "pci", + "dev-name": dev_pci, + "rate-node-name": node_name, + }) + + +def rate_set_leaf(devnl, dev_pci, port_index, **kwargs): + """Sets rate attributes on a leaf (VF).""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "port-index": port_index, + } + params.update(kwargs) + try: + devnl.rate_set(params) + except NlError as e: + if e.error == errno.EOPNOTSUPP: + raise KsftSkipEx("rate_set not supported") from e + raise KsftFailEx("rate_set failed") from e + + +def rate_set_leaf_parent(devnl, dev_pci, port_index, + parent_name, parent_dev_pci=None): + """Sets a leaf's parent, optionally cross-esw.""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "port-index": port_index, + "rate-parent-node-name": parent_name, + } + if parent_dev_pci: + params["parent-dev"] = { + "bus-name": "pci", + "dev-name": parent_dev_pci, + } + try: + devnl.rate_set(params) + except NlError as e: + if e.error == errno.EOPNOTSUPP: + raise KsftSkipEx("rate_set not supported") from e + if parent_dev_pci and e.error == errno.ENODEV: + raise KsftSkipEx("Cross-esw scheduling not supported") from e + raise KsftFailEx("rate_set failed") from e + + +def rate_clear_leaf_parent(devnl, dev_pci, port_index): + """Clears a leaf's parent.""" + rate_set_leaf_parent(devnl, dev_pci, port_index, "") + + +def rate_set_node(devnl, dev_pci, node_name, **kwargs): + """Sets rate attributes on a node.""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "rate-node-name": node_name, + } + params.update(kwargs) + devnl.rate_set(params) + + +# --- Test cases --- + + +def test_same_esw_parent(cfg): + """Assigns PF0's VF to PF0's group (same esw baseline).""" + pf0, _ = discover_pfs(cfg) + pf0_pci, vf0_idx = setup_esw(pf0) + + rate_new(cfg.devnl, pf0_pci, "group0") + defer(rate_del, cfg.devnl, pf0_pci, "group0") + ksft_pr("rate-new succeeded") + + rate_set_leaf_parent(cfg.devnl, pf0_pci, vf0_idx, "group0") + defer(rate_clear_leaf_parent, cfg.devnl, pf0_pci, vf0_idx) + + ksft_pr("Same-esw parent assignment succeeded") + + +def test_cross_esw_parent(cfg): + """Sets cross-esw parent, then clear it.""" + pf0, pf1 = discover_pfs(cfg) + pf0_pci, _ = setup_esw(pf0) + pf1_pci, vf1_idx = setup_esw(pf1) + + rate_new(cfg.devnl, pf0_pci, "group1") + defer(rate_del, cfg.devnl, pf0_pci, "group1") + ksft_pr("rate-new succeeded") + + rate_set_leaf_parent(cfg.devnl, pf1_pci, vf1_idx, + "group1", parent_dev_pci=pf0_pci) + defer(rate_clear_leaf_parent, cfg.devnl, pf1_pci, vf1_idx) + + ksft_pr("Cross-esw parent set and clear succeeded") + + +def test_tx_rates_on_cross_esw(cfg): + """Sets tx_max on group and tx_share on leaves in a cross-esw setup.""" + pf0, pf1 = discover_pfs(cfg) + pf0_pci, vf0_idx = setup_esw(pf0) + pf1_pci, vf1_idx = setup_esw(pf1) + + rate_new(cfg.devnl, pf0_pci, "group2", **{"rate-tx-max": 10000000}) + defer(rate_del, cfg.devnl, pf0_pci, "group2") + ksft_pr("rate-new succeeded") + + rate_set_leaf_parent(cfg.devnl, pf1_pci, vf1_idx, + "group2", parent_dev_pci=pf0_pci) + defer(rate_clear_leaf_parent, cfg.devnl, pf1_pci, vf1_idx) + ksft_pr("set parent cross-esw succeeded") + + rate_set_leaf_parent(cfg.devnl, pf0_pci, vf0_idx, "group2") + defer(rate_clear_leaf_parent, cfg.devnl, pf0_pci, vf0_idx) + ksft_pr("set parent same esw succeeded") + + rate_set_leaf(cfg.devnl, pf0_pci, vf0_idx, **{"rate-tx-share": 1000000}) + rate = rate_get_leaf(cfg.devnl, pf0_pci, vf0_idx) + ksft_eq(rate["rate-tx-share"], 1000000) + rate_set_leaf(cfg.devnl, pf1_pci, vf1_idx, **{"rate-tx-share": 2000000}) + rate = rate_get_leaf(cfg.devnl, pf1_pci, vf1_idx) + ksft_eq(rate["rate-tx-share"], 2000000) + rate_set_node(cfg.devnl, pf0_pci, "group2", **{"rate-tx-max": 250000000}) + rate = rate_get(cfg.devnl, pf0_pci, "group2") + ksft_eq(rate["rate-tx-max"], 250000000) + + ksft_pr("tx_max and tx_share set on cross-esw group") + + +def main() -> None: + """Main function.""" + + with NetDrvEnv(__file__, nsim_test=False) as cfg: + cfg.devnl = DevlinkFamily() + + ksft_run( + cases=[ + test_same_esw_parent, + test_cross_esw_parent, + test_tx_rates_on_cross_esw, + ], + args=(cfg,), + ) + ksft_exit() + + +if __name__ == "__main__": + main() From 403ac520e893a901ab1801aa6924bf694c8223a8 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Wed, 1 Jul 2026 10:32:54 +0300 Subject: [PATCH 0213/1433] net/mlx5: Document devlink rates It seems rates were not documented in the mlx5-specific file, so add examples on how to limit VFs and groups and also provide an example of the intended way to achieve cross-esw scheduling. Signed-off-by: Cosmin Ratiu Reviewed-by: Carolina Jubran Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260701073254.754518-15-tariqt@nvidia.com Signed-off-by: Paolo Abeni --- Documentation/networking/devlink/mlx5.rst | 33 +++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Documentation/networking/devlink/mlx5.rst b/Documentation/networking/devlink/mlx5.rst index 4bba4d780a4a..cf1dffa67669 100644 --- a/Documentation/networking/devlink/mlx5.rst +++ b/Documentation/networking/devlink/mlx5.rst @@ -419,3 +419,36 @@ User commands examples: .. note:: This command can run over all interfaces such as PF/VF and representor ports. + +Rates +===== + +mlx5 devices can limit transmission of individual VFs or a group of them via +the devlink-rate API in switchdev mode. + +User commands examples: + +- Print the existing rates:: + + $ devlink port function rate show + +- Set a max tx limit on traffic from VF0:: + + $ devlink port function rate set pci/0000:82:00.0/1 tx_max 10Gbit + +- Create a rate group with a max tx limit and add two VFs to it:: + + $ devlink port function rate add pci/0000:82:00.0/group1 tx_max 10Gbit + $ devlink port function rate set pci/0000:82:00.0/1 parent group1 + $ devlink port function rate set pci/0000:82:00.0/2 parent group1 + +- Same scenario, with a min guarantee of 20% of the bandwidth for the first VF:: + + $ devlink port function rate add pci/0000:82:00.0/group1 tx_max 10Gbit + $ devlink port function rate set pci/0000:82:00.0/1 parent group1 tx_share 2Gbit + $ devlink port function rate set pci/0000:82:00.0/2 parent group1 + +- Cross-device scheduling:: + + $ devlink port function rate add pci/0000:82:00.0/group1 tx_max 10Gbit + $ devlink port function rate set pci/0000:82:00.1/32769 parent pci/0000:82:00.0/group1 From e0421c6fd39d9e775fef85faab82bae7b49d6c8f Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 2 Jul 2026 11:49:09 +0200 Subject: [PATCH 0214/1433] net: ethernet: qualcomm: Unconstify function arguments passed by value There is no benefit in marking "const" a pass-by-value (not a pointer) function argument, because it is passed as a copy on the stack. No code readability improvements, no additional compiler-time safety for misuse. Drop such redundant "const". Signed-off-by: Krzysztof Kozlowski Reviewed-by: Luo Jie Link: https://patch.msgid.link/20260702094908.79859-3-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/qualcomm/ppe/ppe_config.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/qualcomm/ppe/ppe_config.c b/drivers/net/ethernet/qualcomm/ppe/ppe_config.c index e9a0e22907a6..94f69c077949 100644 --- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.c +++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.c @@ -1380,7 +1380,7 @@ int ppe_ring_queue_map_set(struct ppe_device *ppe_dev, int ring_id, u32 *queue_m } static int ppe_config_bm_threshold(struct ppe_device *ppe_dev, int bm_port_id, - const struct ppe_bm_port_config port_cfg) + struct ppe_bm_port_config port_cfg) { u32 reg, val, bm_fc_val[2]; int ret; @@ -1586,7 +1586,7 @@ static int ppe_config_qm(struct ppe_device *ppe_dev) } static int ppe_node_scheduler_config(struct ppe_device *ppe_dev, - const struct ppe_scheduler_port_config config) + struct ppe_scheduler_port_config config) { struct ppe_scheduler_cfg sch_cfg; int ret, i; From cefd16657c1d9008259e7f91ea3f3d67a933fece Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 2 Jul 2026 11:49:10 +0200 Subject: [PATCH 0215/1433] net: ethernet: qualcomm: Constify "queue_map" in ppe_ring_queue_map_set() "queue_map" is a pointer to "u32" and is not modified by the ppe_ring_queue_map_set() function, thus can be made a pointer to const to indicate that function is treating the pointed value read-only. This in general makes the code easier to follow and a bit safer. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Luo Jie Link: https://patch.msgid.link/20260702094908.79859-4-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/qualcomm/ppe/ppe_config.c | 3 ++- drivers/net/ethernet/qualcomm/ppe/ppe_config.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/qualcomm/ppe/ppe_config.c b/drivers/net/ethernet/qualcomm/ppe/ppe_config.c index 94f69c077949..125b73be92b1 100644 --- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.c +++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.c @@ -1367,7 +1367,8 @@ int ppe_rss_hash_config_set(struct ppe_device *ppe_dev, int mode, * * Return: 0 on success, negative error code on failure. */ -int ppe_ring_queue_map_set(struct ppe_device *ppe_dev, int ring_id, u32 *queue_map) +int ppe_ring_queue_map_set(struct ppe_device *ppe_dev, int ring_id, + const u32 *queue_map) { u32 reg, queue_bitmap_val[PPE_RING_TO_QUEUE_BITMAP_WORD_CNT]; diff --git a/drivers/net/ethernet/qualcomm/ppe/ppe_config.h b/drivers/net/ethernet/qualcomm/ppe/ppe_config.h index 4bb45ca40144..60493e51e0a4 100644 --- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.h +++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.h @@ -313,5 +313,5 @@ int ppe_rss_hash_config_set(struct ppe_device *ppe_dev, int mode, struct ppe_rss_hash_cfg hash_cfg); int ppe_ring_queue_map_set(struct ppe_device *ppe_dev, int ring_id, - u32 *queue_map); + const u32 *queue_map); #endif From 5326fefb9fe8e7014f5d7246fa10ff0db7a968a3 Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Thu, 2 Jul 2026 15:41:45 -0700 Subject: [PATCH 0216/1433] net: hold instance lock around NETDEV_DOWN/GOING_DOWN Mirror what call_netdevice_register_net_notifiers does but for the teardown. Cover only DOWN and GOING_DOWN. UNREGISTER is still unlocked because of the SW devices using dev_xxx methods. Signed-off-by: Stanislav Fomichev Link: https://patch.msgid.link/20260702224150.3730033-2-sdf@fomichev.me Signed-off-by: Paolo Abeni --- net/core/dev.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/core/dev.c b/net/core/dev.c index 4b3d5cfdf6e0..9d49493f4fb5 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -1912,9 +1912,11 @@ static void call_netdevice_unregister_notifiers(struct notifier_block *nb, struct net_device *dev) { if (dev->flags & IFF_UP) { + netdev_lock_ops(dev); call_netdevice_notifier(nb, NETDEV_GOING_DOWN, dev); call_netdevice_notifier(nb, NETDEV_DOWN, dev); + netdev_unlock_ops(dev); } call_netdevice_notifier(nb, NETDEV_UNREGISTER, dev); } From cefa18fab29cf0dc99f6e7aa10ee332826f305c0 Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Thu, 2 Jul 2026 15:41:46 -0700 Subject: [PATCH 0217/1433] net: dsa: hold instance lock on close-on-shutdown paths netif_close_many will soon assert ops lock (for locked DOWN/GOING_DOWN). Update dsa_switch_shutdown to manually grab and release the ops lock. Signed-off-by: Stanislav Fomichev Link: https://patch.msgid.link/20260702224150.3730033-3-sdf@fomichev.me Signed-off-by: Paolo Abeni --- net/dsa/dsa.c | 20 +++++++++++++++++--- net/dsa/user.c | 19 +++++++++++++++++-- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c index 9cb732f6b1e3..da53a666d4b8 100644 --- a/net/dsa/dsa.c +++ b/net/dsa/dsa.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "conduit.h" @@ -1620,10 +1621,23 @@ void dsa_switch_shutdown(struct dsa_switch *ds) rtnl_lock(); - dsa_switch_for_each_cpu_port(dp, ds) - list_add(&dp->conduit->close_list, &close_list); + dsa_switch_for_each_cpu_port(dp, ds) { + if (!(dp->conduit->flags & IFF_UP)) + continue; + list_add_tail(&dp->conduit->close_list, &close_list); + netdev_lock_ops(dp->conduit); + } - netif_close_many(&close_list, true); + netif_close_many(&close_list, false); + + while (!list_empty(&close_list)) { + struct net_device *conduit; + + conduit = list_first_entry(&close_list, struct net_device, + close_list); + netdev_unlock_ops(conduit); + list_del_init(&conduit->close_list); + } dsa_switch_for_each_user_port(dp, ds) { conduit = dsa_port_to_conduit(dp); diff --git a/net/dsa/user.c b/net/dsa/user.c index 072fa76972cc..03c7af6abe18 100644 --- a/net/dsa/user.c +++ b/net/dsa/user.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -3599,10 +3600,24 @@ static int dsa_user_netdevice_event(struct notifier_block *nb, if (dp->cpu_dp != cpu_dp) continue; - list_add(&dp->user->close_list, &close_list); + if (!(dp->user->flags & IFF_UP)) + continue; + + list_add_tail(&dp->user->close_list, &close_list); + netdev_lock_ops(dp->user); } - netif_close_many(&close_list, true); + netif_close_many(&close_list, false); + + while (!list_empty(&close_list)) { + struct net_device *user_dev; + + user_dev = list_first_entry(&close_list, + struct net_device, + close_list); + netdev_unlock_ops(user_dev); + list_del_init(&user_dev->close_list); + } return NOTIFY_OK; } From 6034bc4febc98801097e5c416a7fef8ccf883507 Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Thu, 2 Jul 2026 15:41:47 -0700 Subject: [PATCH 0218/1433] net: mtk_eth_soc: hold instance lock around DMA-device-swap close netif_close_many will soon assert ops lock (for locked DOWN/GOING_DOWN). Update mtk_eth_set_dma_device to manually grab and release the ops lock. Signed-off-by: Stanislav Fomichev Link: https://patch.msgid.link/20260702224150.3730033-4-sdf@fomichev.me Signed-off-by: Paolo Abeni --- drivers/net/ethernet/mediatek/mtk_eth_soc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c index 5d291e50a47b..fe7610c42e5d 100644 --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -5030,10 +5031,14 @@ void mtk_eth_set_dma_device(struct mtk_eth *eth, struct device *dma_dev) continue; list_add_tail(&dev->close_list, &dev_list); + netdev_lock_ops(dev); } netif_close_many(&dev_list, false); + list_for_each_entry(dev, &dev_list, close_list) + netdev_unlock_ops(dev); + eth->dma_dev = dma_dev; list_for_each_entry_safe(dev, tmp, &dev_list, close_list) { From b8d2262b966a068dc567e72d493230f4ba62cc4a Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Thu, 2 Jul 2026 15:41:48 -0700 Subject: [PATCH 0219/1433] net: rtnetlink: take instance lock inside rtnl_configure_link rtnl_configure_link calls __dev_change_flags() and __dev_notify_flags, both need the instance lock. rtnl_newlink_create grabs it but stacked devices do not. Move the lock inside rtnl_configure_link. Signed-off-by: Stanislav Fomichev Link: https://patch.msgid.link/20260702224150.3730033-5-sdf@fomichev.me Signed-off-by: Paolo Abeni --- net/core/rtnetlink.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index 12aa3aa1688b..1b7d6f6b8b68 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -3660,14 +3660,16 @@ int rtnl_configure_link(struct net_device *dev, const struct ifinfomsg *ifm, u32 portid, const struct nlmsghdr *nlh) { unsigned int old_flags, changed; - int err; + int err = 0; + + netdev_lock_ops(dev); old_flags = dev->flags; if (ifm && (ifm->ifi_flags || ifm->ifi_change)) { err = __dev_change_flags(dev, rtnl_dev_combine_flags(dev, ifm), NULL); if (err < 0) - return err; + goto out; } changed = old_flags ^ dev->flags; @@ -3677,7 +3679,10 @@ int rtnl_configure_link(struct net_device *dev, const struct ifinfomsg *ifm, } __dev_notify_flags(dev, old_flags, changed, portid, nlh); - return 0; + +out: + netdev_unlock_ops(dev); + return err; } EXPORT_SYMBOL(rtnl_configure_link); @@ -3918,22 +3923,20 @@ static int rtnl_newlink_create(struct sk_buff *skb, struct ifinfomsg *ifm, goto out; } - netdev_lock_ops(dev); - err = rtnl_configure_link(dev, ifm, portid, nlh); if (err < 0) goto out_unregister; if (tb[IFLA_MASTER]) { + netdev_lock_ops(dev); err = do_set_master(dev, nla_get_u32(tb[IFLA_MASTER]), extack); + netdev_unlock_ops(dev); if (err) goto out_unregister; } - netdev_unlock_ops(dev); out: return err; out_unregister: - netdev_unlock_ops(dev); if (ops->newlink) { LIST_HEAD(list_kill); From f540caaaca8d23a6bd3a8c424cd808037840b4af Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Thu, 2 Jul 2026 15:41:49 -0700 Subject: [PATCH 0220/1433] net: require instance lock for NETDEV_DOWN/GOING_DOWN notifiers Sprinkle a few asserts about ops lock: netif_close_many and __dev_notify_flags should now consistently run under the lock Signed-off-by: Stanislav Fomichev Link: https://patch.msgid.link/20260702224150.3730033-6-sdf@fomichev.me Signed-off-by: Paolo Abeni --- Documentation/networking/netdevices.rst | 2 ++ net/core/dev.c | 3 +++ net/core/lock_debug.c | 4 ++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Documentation/networking/netdevices.rst b/Documentation/networking/netdevices.rst index d2a238f8cc8b..1bb68a73bb67 100644 --- a/Documentation/networking/netdevices.rst +++ b/Documentation/networking/netdevices.rst @@ -421,6 +421,8 @@ running under the lock: * ``NETDEV_CHANGENAME`` * ``NETDEV_REGISTER`` * ``NETDEV_UP`` +* ``NETDEV_DOWN`` +* ``NETDEV_GOING_DOWN`` The following notifiers are running without the lock: * ``NETDEV_UNREGISTER`` diff --git a/net/core/dev.c b/net/core/dev.c index 9d49493f4fb5..714d05283500 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -1802,6 +1802,7 @@ void netif_close_many(struct list_head *head, bool unlink) __dev_close_many(head); list_for_each_entry_safe(dev, tmp, head, close_list) { + netdev_assert_locked_ops_compat(dev); rtmsg_ifinfo(RTM_NEWLINK, dev, IFF_UP | IFF_RUNNING, GFP_KERNEL, 0, NULL); call_netdevice_notifiers(NETDEV_DOWN, dev); if (unlink) @@ -9787,6 +9788,8 @@ void __dev_notify_flags(struct net_device *dev, unsigned int old_flags, { unsigned int changes = dev->flags ^ old_flags; + netdev_assert_locked_ops_compat(dev); + if (gchanges) rtmsg_ifinfo(RTM_NEWLINK, dev, gchanges, GFP_ATOMIC, portid, nlh); diff --git a/net/core/lock_debug.c b/net/core/lock_debug.c index 8a81c5430705..abc4c00728b1 100644 --- a/net/core/lock_debug.c +++ b/net/core/lock_debug.c @@ -24,15 +24,15 @@ int netdev_debug_event(struct notifier_block *nb, unsigned long event, case NETDEV_CHANGE: case NETDEV_REGISTER: case NETDEV_UP: + case NETDEV_DOWN: + case NETDEV_GOING_DOWN: netdev_assert_locked_ops_compat(dev); fallthrough; - case NETDEV_DOWN: case NETDEV_REBOOT: case NETDEV_UNREGISTER: case NETDEV_CHANGEMTU: case NETDEV_CHANGEADDR: case NETDEV_PRE_CHANGEADDR: - case NETDEV_GOING_DOWN: case NETDEV_FEAT_CHANGE: case NETDEV_BONDING_FAILOVER: case NETDEV_PRE_UP: From 538d89fd914610852a6fb20b823ba70566153d46 Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Thu, 2 Jul 2026 15:41:50 -0700 Subject: [PATCH 0221/1433] net: document NETDEV_UNREGISTER unlocked rationale The lock-state table marks UNREGISTER as unlocked without saying why. Add a short note that many handlers release the lowers via dev_close(). Signed-off-by: Stanislav Fomichev Link: https://patch.msgid.link/20260702224150.3730033-7-sdf@fomichev.me Signed-off-by: Paolo Abeni --- Documentation/networking/netdevices.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/networking/netdevices.rst b/Documentation/networking/netdevices.rst index 1bb68a73bb67..db71d4283032 100644 --- a/Documentation/networking/netdevices.rst +++ b/Documentation/networking/netdevices.rst @@ -427,6 +427,11 @@ running under the lock: The following notifiers are running without the lock: * ``NETDEV_UNREGISTER`` +Many SW devices (uppers) catch their lower's ``NETDEV_UNREGISTER`` +events and may interact with them via ``dev_*()`` handlers, which take +the instance lock. Until we convert these devices to ``netif_*()`` variants, +``NETDEV_UNREGISTER`` stays unlocked. + There are no clear expectations for the remaining notifiers. Notifiers not on the list may run with or without the instance lock, potentially even invoking the same notifier type with and without the lock from different code paths. From 037d3e9a44e52dba5508a84095db8d90f343628c Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Wed, 18 Mar 2026 16:19:05 +1100 Subject: [PATCH 0222/1433] mmc: sdio: add Morse Micro vendor ids Add the Morse Micro mm81x series vendor ids. Acked-by: Ulf Hansson Signed-off-by: Lachlan Hodges --- include/linux/mmc/sdio_ids.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/linux/mmc/sdio_ids.h b/include/linux/mmc/sdio_ids.h index 0685dd717e85..bbffad9ae88e 100644 --- a/include/linux/mmc/sdio_ids.h +++ b/include/linux/mmc/sdio_ids.h @@ -117,6 +117,9 @@ #define SDIO_VENDOR_ID_MICROCHIP_WILC 0x0296 #define SDIO_DEVICE_ID_MICROCHIP_WILC1000 0x5347 +#define SDIO_VENDOR_ID_MORSEMICRO 0x325b +#define SDIO_DEVICE_ID_MORSEMICRO_MM8108 0x0809 + #define SDIO_VENDOR_ID_NXP 0x0471 #define SDIO_DEVICE_ID_NXP_IW61X 0x0205 From b1906cea00b021acea22460225401d5b27bc7c36 Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Tue, 7 Jul 2026 11:21:24 +1000 Subject: [PATCH 0223/1433] wifi: mm81x: add mm81x Wi-Fi HaLow driver mm81x is the first Wi-Fi HaLow driver to support the Morse Micro mm81x chip family via USB and SDIO. S1G support in the kernel is only new, and as a result this driver has been scoped to be simple and only support station and AP interface. The Wi-Fi specific features only cover the minimum required for basic use such as powersave, aggregation, rate control and so on. The driver will be extended into the future as S1G support for operations such as ACS, channel switching and so on are added into the wireless stack. The driver has been build tested on a long list of architectures and compilers via Intels LKP. The driver currently supports IEEE80211-2024 US channels only, with AU 2020 also available. In order for this to be expanded additional non-trivial kernel work is required which will begin once the driver is upstream. The driver has had many authors who are listed below in alphabetical order: Co-developed-by: Andrew Pope Signed-off-by: Andrew Pope Co-developed-by: Arien Judge Signed-off-by: Arien Judge Co-developed-by: Ayman Grais Signed-off-by: Ayman Grais Co-developed-by: Bassem Dawood Signed-off-by: Bassem Dawood Co-developed-by: Chetan Mistry Signed-off-by: Chetan Mistry Co-developed-by: Dan Callaghan Signed-off-by: Dan Callaghan Co-developed-by: James Herbert Signed-off-by: James Herbert Co-developed-by: Sahand Maleki Signed-off-by: Sahand Maleki Co-developed-by: Simon Wadsworth Signed-off-by: Simon Wadsworth Signed-off-by: Lachlan Hodges --- MAINTAINERS | 8 + drivers/net/wireless/Kconfig | 1 + drivers/net/wireless/Makefile | 1 + drivers/net/wireless/morsemicro/Kconfig | 15 + drivers/net/wireless/morsemicro/Makefile | 2 + drivers/net/wireless/morsemicro/mm81x/Kconfig | 24 + .../net/wireless/morsemicro/mm81x/Makefile | 21 + drivers/net/wireless/morsemicro/mm81x/bus.h | 99 + .../net/wireless/morsemicro/mm81x/command.c | 563 ++++ .../net/wireless/morsemicro/mm81x/command.h | 85 + .../wireless/morsemicro/mm81x/command_defs.h | 1658 +++++++++++ drivers/net/wireless/morsemicro/mm81x/core.c | 138 + drivers/net/wireless/morsemicro/mm81x/core.h | 456 +++ drivers/net/wireless/morsemicro/mm81x/fw.c | 752 +++++ drivers/net/wireless/morsemicro/mm81x/fw.h | 143 + drivers/net/wireless/morsemicro/mm81x/hif.h | 117 + drivers/net/wireless/morsemicro/mm81x/hw.c | 367 +++ drivers/net/wireless/morsemicro/mm81x/hw.h | 159 ++ drivers/net/wireless/morsemicro/mm81x/mac.c | 2443 +++++++++++++++++ drivers/net/wireless/morsemicro/mm81x/mac.h | 63 + drivers/net/wireless/morsemicro/mm81x/mmrc.c | 1354 +++++++++ drivers/net/wireless/morsemicro/mm81x/mmrc.h | 193 ++ drivers/net/wireless/morsemicro/mm81x/ps.c | 120 + drivers/net/wireless/morsemicro/mm81x/ps.h | 22 + .../net/wireless/morsemicro/mm81x/rate_code.h | 177 ++ drivers/net/wireless/morsemicro/mm81x/rc.c | 494 ++++ drivers/net/wireless/morsemicro/mm81x/rc.h | 51 + drivers/net/wireless/morsemicro/mm81x/sdio.c | 613 +++++ drivers/net/wireless/morsemicro/mm81x/skbq.c | 1064 +++++++ drivers/net/wireless/morsemicro/mm81x/skbq.h | 218 ++ drivers/net/wireless/morsemicro/mm81x/usb.c | 943 +++++++ drivers/net/wireless/morsemicro/mm81x/yaps.c | 704 +++++ drivers/net/wireless/morsemicro/mm81x/yaps.h | 77 + .../net/wireless/morsemicro/mm81x/yaps_hw.c | 702 +++++ .../net/wireless/morsemicro/mm81x/yaps_hw.h | 52 + 35 files changed, 13899 insertions(+) create mode 100644 drivers/net/wireless/morsemicro/Kconfig create mode 100644 drivers/net/wireless/morsemicro/Makefile create mode 100644 drivers/net/wireless/morsemicro/mm81x/Kconfig create mode 100644 drivers/net/wireless/morsemicro/mm81x/Makefile create mode 100644 drivers/net/wireless/morsemicro/mm81x/bus.h create mode 100644 drivers/net/wireless/morsemicro/mm81x/command.c create mode 100644 drivers/net/wireless/morsemicro/mm81x/command.h create mode 100644 drivers/net/wireless/morsemicro/mm81x/command_defs.h create mode 100644 drivers/net/wireless/morsemicro/mm81x/core.c create mode 100644 drivers/net/wireless/morsemicro/mm81x/core.h create mode 100644 drivers/net/wireless/morsemicro/mm81x/fw.c create mode 100644 drivers/net/wireless/morsemicro/mm81x/fw.h create mode 100644 drivers/net/wireless/morsemicro/mm81x/hif.h create mode 100644 drivers/net/wireless/morsemicro/mm81x/hw.c create mode 100644 drivers/net/wireless/morsemicro/mm81x/hw.h create mode 100644 drivers/net/wireless/morsemicro/mm81x/mac.c create mode 100644 drivers/net/wireless/morsemicro/mm81x/mac.h create mode 100644 drivers/net/wireless/morsemicro/mm81x/mmrc.c create mode 100644 drivers/net/wireless/morsemicro/mm81x/mmrc.h create mode 100644 drivers/net/wireless/morsemicro/mm81x/ps.c create mode 100644 drivers/net/wireless/morsemicro/mm81x/ps.h create mode 100644 drivers/net/wireless/morsemicro/mm81x/rate_code.h create mode 100644 drivers/net/wireless/morsemicro/mm81x/rc.c create mode 100644 drivers/net/wireless/morsemicro/mm81x/rc.h create mode 100644 drivers/net/wireless/morsemicro/mm81x/sdio.c create mode 100644 drivers/net/wireless/morsemicro/mm81x/skbq.c create mode 100644 drivers/net/wireless/morsemicro/mm81x/skbq.h create mode 100644 drivers/net/wireless/morsemicro/mm81x/usb.c create mode 100644 drivers/net/wireless/morsemicro/mm81x/yaps.c create mode 100644 drivers/net/wireless/morsemicro/mm81x/yaps.h create mode 100644 drivers/net/wireless/morsemicro/mm81x/yaps_hw.c create mode 100644 drivers/net/wireless/morsemicro/mm81x/yaps_hw.h diff --git a/MAINTAINERS b/MAINTAINERS index 52f1a55eca99..edd1674d1074 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18202,6 +18202,14 @@ F: drivers/regulator/mpq7920.c F: drivers/regulator/mpq7920.h F: include/linux/mfd/mp2629.h +MORSE MICRO MM81X WIRELESS DRIVER +M: Lachlan Hodges +M: Dan Callaghan +R: Arien Judge +L: linux-wireless@vger.kernel.org +S: Supported +F: drivers/net/wireless/morsemicro/mm81x/ + MOST(R) TECHNOLOGY DRIVER M: Parthiban Veerasooran M: Christian Gromm diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig index c6599594dc99..baddadf9ec3c 100644 --- a/drivers/net/wireless/Kconfig +++ b/drivers/net/wireless/Kconfig @@ -27,6 +27,7 @@ source "drivers/net/wireless/intersil/Kconfig" source "drivers/net/wireless/marvell/Kconfig" source "drivers/net/wireless/mediatek/Kconfig" source "drivers/net/wireless/microchip/Kconfig" +source "drivers/net/wireless/morsemicro/Kconfig" source "drivers/net/wireless/purelifi/Kconfig" source "drivers/net/wireless/ralink/Kconfig" source "drivers/net/wireless/realtek/Kconfig" diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile index e1c4141c6004..d74f817b37de 100644 --- a/drivers/net/wireless/Makefile +++ b/drivers/net/wireless/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_WLAN_VENDOR_INTERSIL) += intersil/ obj-$(CONFIG_WLAN_VENDOR_MARVELL) += marvell/ obj-$(CONFIG_WLAN_VENDOR_MEDIATEK) += mediatek/ obj-$(CONFIG_WLAN_VENDOR_MICROCHIP) += microchip/ +obj-$(CONFIG_WLAN_VENDOR_MORSEMICRO) += morsemicro/ obj-$(CONFIG_WLAN_VENDOR_PURELIFI) += purelifi/ obj-$(CONFIG_WLAN_VENDOR_QUANTENNA) += quantenna/ obj-$(CONFIG_WLAN_VENDOR_RALINK) += ralink/ diff --git a/drivers/net/wireless/morsemicro/Kconfig b/drivers/net/wireless/morsemicro/Kconfig new file mode 100644 index 000000000000..cb0653c77d87 --- /dev/null +++ b/drivers/net/wireless/morsemicro/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only +config WLAN_VENDOR_MORSEMICRO + bool "Morse Micro devices" + default y + help + If you have a wireless card belonging to this class, say Y. + + Note that the answer to this question doesn't directly affect the + kernel: saying N will just cause the configurator to skip all the + questions about these cards. If you say Y, you will be asked for + your specific card in the following questions. + +if WLAN_VENDOR_MORSEMICRO +source "drivers/net/wireless/morsemicro/mm81x/Kconfig" +endif # WLAN_VENDOR_MORSEMICRO diff --git a/drivers/net/wireless/morsemicro/Makefile b/drivers/net/wireless/morsemicro/Makefile new file mode 100644 index 000000000000..5b2670f7d171 --- /dev/null +++ b/drivers/net/wireless/morsemicro/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_MM81X) += mm81x/ diff --git a/drivers/net/wireless/morsemicro/mm81x/Kconfig b/drivers/net/wireless/morsemicro/mm81x/Kconfig new file mode 100644 index 000000000000..33cdcc0df4de --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/Kconfig @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0 + +config MM81X + tristate "Morse Micro MM81x wireless devices" + depends on MAC80211 + select FW_LOADER + select CRC7 + help + This module adds support for wireless devices based + on Morse Micro MM81xx chipsets. + +config MM81X_USB + tristate "Morse Micro MM81x USB support" + depends on MM81X && USB + help + This module adds support for the USB interface of + devices using the Morse Micro MM81x chipset. + +config MM81X_SDIO + tristate "Morse Micro MM81x SDIO support" + depends on MM81X && MMC + help + This module adds support for the SDIO interface of + devices using the Morse Micro MM81x chipset. diff --git a/drivers/net/wireless/morsemicro/mm81x/Makefile b/drivers/net/wireless/morsemicro/mm81x/Makefile new file mode 100644 index 000000000000..0d494fda1412 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/Makefile @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_MM81X) += mm81x_core.o + +mm81x_core-y += core.o +mm81x_core-y += mac.o +mm81x_core-y += hw.o +mm81x_core-y += fw.o +mm81x_core-y += command.o +mm81x_core-y += ps.o +mm81x_core-y += skbq.o +mm81x_core-y += yaps_hw.o +mm81x_core-y += yaps.o +mm81x_core-y += rc.o +mm81x_core-y += mmrc.o + +obj-$(CONFIG_MM81X_USB) += mm81x_usb.o +mm81x_usb-y += usb.o + +obj-$(CONFIG_MM81X_SDIO) += mm81x_sdio.o +mm81x_sdio-y += sdio.o diff --git a/drivers/net/wireless/morsemicro/mm81x/bus.h b/drivers/net/wireless/morsemicro/mm81x/bus.h new file mode 100644 index 000000000000..d2ccabc037fb --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/bus.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_BUS_H_ +#define _MM81X_BUS_H_ + +#include +#include "core.h" + +enum mm81x_bus_type { + MM81X_BUS_TYPE_USB, + MM81X_BUS_TYPE_SDIO, +}; + +struct mm81x_bus_ops { + int (*dm_read)(struct mm81x *mors, u32 addr, u8 *data, int len); + int (*dm_write)(struct mm81x *mors, u32 addr, const u8 *data, int len); + int (*reg32_read)(struct mm81x *mors, u32 addr, u32 *data); + int (*reg32_write)(struct mm81x *mors, u32 addr, u32 data); + int (*digital_reset)(struct mm81x *mors); + void (*set_bus_enable)(struct mm81x *mors, bool enable); + void (*config_burst_mode)(struct mm81x *mors, bool enable_burst); + void (*claim)(struct mm81x *mors); + void (*set_irq)(struct mm81x *mors, bool enable); + void (*release)(struct mm81x *mors); + unsigned int bulk_alignment; +}; + +/* + * Default TX alignment for buses which don't care. mac80211 will give us + * SKBs aligned to the 2 byte boundary, so 2 is effectively a noop. + */ +#define MM81X_BUS_DEFAULT_BULK_ALIGNMENT (2) + +/* mm81x_dm_read - len must be rounded up to the nearest 4-byte boundary */ +static inline int mm81x_dm_read(struct mm81x *mors, u32 addr, u8 *data, int len) +{ + return mors->bus_ops->dm_read(mors, addr, data, len); +} + +static inline int mm81x_dm_write(struct mm81x *mors, u32 addr, const u8 *data, + int len) +{ + return mors->bus_ops->dm_write(mors, addr, data, len); +} + +static inline int mm81x_reg32_read(struct mm81x *mors, u32 addr, u32 *data) +{ + return mors->bus_ops->reg32_read(mors, addr, data); +} + +static inline int mm81x_reg32_write(struct mm81x *mors, u32 addr, u32 data) +{ + return mors->bus_ops->reg32_write(mors, addr, data); +} + +static inline int mm81x_bus_digital_reset(struct mm81x *mors) +{ + if (mors->bus_ops->digital_reset) + return mors->bus_ops->digital_reset(mors); + + return 0; +} + +static inline void mm81x_set_bus_enable(struct mm81x *mors, bool enable) +{ + mors->bus_ops->set_bus_enable(mors, enable); +} + +static inline void mm81x_bus_config_burst_mode(struct mm81x *mors, + bool enable_burst) +{ + if (mors->bus_ops->config_burst_mode) + mors->bus_ops->config_burst_mode(mors, enable_burst); +} + +static inline void mm81x_claim_bus(struct mm81x *mors) +{ + mors->bus_ops->claim(mors); +} + +static inline void mm81x_bus_set_irq(struct mm81x *mors, bool enable) +{ + mors->bus_ops->set_irq(mors, enable); +} + +static inline void mm81x_release_bus(struct mm81x *mors) +{ + mors->bus_ops->release(mors); +} + +static inline unsigned int mm81x_bus_get_alignment(struct mm81x *mors) +{ + return mors->bus_ops->bulk_alignment; +} + +#endif /* !_MM81X_BUS_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/command.c b/drivers/net/wireless/morsemicro/mm81x/command.c new file mode 100644 index 000000000000..afb7ee9bb236 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/command.c @@ -0,0 +1,563 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#include +#include +#include +#include + +#include "command.h" +#include "mac.h" +#include "ps.h" +#include "hif.h" + +#define MM_MAX_COMMAND_RETRY 2 +#define HOST_CMD_DEFAULT_TIMEOUT_MS 600 +#define HOST_CMD_POWERSAVE_TIMEOUT_MS 2000 + +#define INIT_CMD_HDR(_req, _cmd, _vif_id) \ + ((struct host_cmd_header){ \ + .message_id = cpu_to_le16(_cmd), \ + .len = cpu_to_le16(sizeof(_req) - sizeof((_req).hdr)), \ + .vif_id = cpu_to_le16(_vif_id), \ + }) + +struct host_cmd_resp_cb { + int ret; + u32 length; + struct host_cmd_resp *dest_resp; +}; + +static int mm81x_cmd_tx(struct mm81x *mors, struct host_cmd_resp *resp, + struct host_cmd_req *req, u32 length, u32 timeout) +{ + int cmd_len; + int ret = 0; + u16 host_id; + int retry = 0; + unsigned long wait_ret = 0; + struct sk_buff *skb; + struct mm81x_skbq *cmd_q = mm81x_hif_get_tx_cmd_queue(mors); + struct host_cmd_resp_cb *resp_cb; + DECLARE_COMPLETION_ONSTACK(cmd_comp); + + BUILD_BUG_ON(sizeof(struct host_cmd_resp_cb) > + IEEE80211_TX_INFO_DRIVER_DATA_SIZE); + + cmd_len = sizeof(*req) + le16_to_cpu(req->hdr.len); + req->hdr.flags = cpu_to_le16(HOST_CMD_TYPE_REQ); + + mutex_lock(&mors->cmd_wait); + mors->cmd_seq++; + if (mors->cmd_seq > HOST_CMD_HOST_ID_SEQ_MAX) + mors->cmd_seq = 1; + host_id = mors->cmd_seq << HOST_CMD_HOST_ID_SEQ_SHIFT; + + mm81x_ps_disable(mors); + + do { + req->hdr.host_id = cpu_to_le16(host_id | retry); + + skb = mm81x_skbq_alloc_skb(cmd_q, cmd_len); + if (!skb) { + ret = -ENOMEM; + break; + } + + memcpy(skb->data, req, cmd_len); + resp_cb = (struct host_cmd_resp_cb *)IEEE80211_SKB_CB(skb) + ->driver_data; + resp_cb->length = length; + resp_cb->dest_resp = resp; + + dev_dbg(mors->dev, "CMD 0x%04x:%04x", + le16_to_cpu(req->hdr.message_id), + le16_to_cpu(req->hdr.host_id)); + + mutex_lock(&mors->cmd_lock); + mors->cmd_comp = &cmd_comp; + if (retry > 0) + reinit_completion(&cmd_comp); + timeout = timeout ? timeout : HOST_CMD_DEFAULT_TIMEOUT_MS; + ret = mm81x_skbq_skb_tx(cmd_q, &skb, NULL, + MM81X_SKB_CHAN_COMMAND); + mutex_unlock(&mors->cmd_lock); + + if (ret) { + dev_err(mors->dev, "mm81x_skbq_tx fail: %d", ret); + break; + } + + wait_ret = wait_for_completion_timeout( + &cmd_comp, msecs_to_jiffies(timeout)); + mutex_lock(&mors->cmd_lock); + mors->cmd_comp = NULL; + + if (!wait_ret) { + dev_err(mors->dev, + "Try:%d Command %04x:%04x timeout after %u ms", + retry, le16_to_cpu(req->hdr.message_id), + le16_to_cpu(req->hdr.host_id), timeout); + ret = -ETIMEDOUT; + } else { + ret = (length && resp) ? le32_to_cpu(resp->status) : + resp_cb->ret; + if (ret > 0 || ret < -MAX_ERRNO) + ret = -EIO; + + dev_dbg(mors->dev, "Command 0x%04x:%04x status 0x%08x", + le16_to_cpu(req->hdr.message_id), + le16_to_cpu(req->hdr.host_id), ret); + if (ret) { + dev_err(mors->dev, + "Command 0x%04x:%04x error %d", + le16_to_cpu(req->hdr.message_id), + le16_to_cpu(req->hdr.host_id), ret); + } + } + /* Free the command request */ + spin_lock_bh(&cmd_q->lock); + mm81x_skbq_skb_finish(cmd_q, skb, NULL); + spin_unlock_bh(&cmd_q->lock); + mutex_unlock(&mors->cmd_lock); + + retry++; + } while ((ret == -ETIMEDOUT) && retry < MM_MAX_COMMAND_RETRY); + + mm81x_ps_enable(mors); + mutex_unlock(&mors->cmd_wait); + + if (ret == -ETIMEDOUT) { + dev_err(mors->dev, "Command %02x:%02x timed out", + le16_to_cpu(req->hdr.message_id), + le16_to_cpu(req->hdr.host_id)); + } else if (ret != 0) { + dev_err(mors->dev, + "Command %02x:%02x failed with rc %d (0x%x)\n", + le16_to_cpu(req->hdr.message_id), + le16_to_cpu(req->hdr.host_id), ret, ret); + } + + return ret; +} + +int mm81x_cmd_resp_process(struct mm81x *mors, struct sk_buff *skb) +{ + int length, ret = -ESRCH; /* No such process */ + struct mm81x_skbq *cmd_q = mm81x_hif_get_tx_cmd_queue(mors); + struct host_cmd_resp *src_resp = (struct host_cmd_resp *)(skb->data); + struct sk_buff *cmd_skb = NULL; + struct host_cmd_resp_cb *resp_cb; + struct host_cmd_resp *dest_resp; + struct host_cmd_req *req; + u16 message_id = 0; + u16 host_id = 0; + u16 resp_message_id = le16_to_cpu(src_resp->hdr.message_id); + u16 resp_host_id = le16_to_cpu(src_resp->hdr.host_id); + bool is_late_response = false; + + dev_dbg(mors->dev, "EVT 0x%04x:0x%04x", resp_message_id, resp_host_id); + + if (!HOST_CMD_IS_RESP(src_resp)) { + ret = mm81x_mac_event_recv(mors, skb); + goto exit_free; + } + + mutex_lock(&mors->cmd_lock); + + cmd_skb = mm81x_skbq_tx_pending(cmd_q); + if (cmd_skb) { + mm81x_skbq_pull_hdr_post_tx(cmd_skb); + req = (struct host_cmd_req *)cmd_skb->data; + message_id = le16_to_cpu(req->hdr.message_id); + host_id = le16_to_cpu(req->hdr.host_id); + } + + /* + * If there is no pending command or the sequence ID does not match, + * this is a late response for a timed out command which has been + * cleaned up, so just free up the response. If a command was retried, + * the response may be from the retry or from the original command + * (late response) but not from both because the firmware will silently + * drop a retry if it received the initial request. So a mismatched + * retry counter is treated as a matched command and response. + */ + if (!cmd_skb || message_id != resp_message_id || + (host_id & HOST_CMD_HOST_ID_SEQ_MASK) != + (resp_host_id & HOST_CMD_HOST_ID_SEQ_MASK)) { + dev_err(mors->dev, + "Late response for timed out req 0x%04x:%04x have 0x%04x:%04x 0x%04x", + resp_message_id, resp_host_id, message_id, host_id, + mors->cmd_seq); + is_late_response = true; + goto exit; + } + if ((host_id & HOST_CMD_HOST_ID_RETRY_MASK) != + (resp_host_id & HOST_CMD_HOST_ID_RETRY_MASK)) + dev_dbg(mors->dev, + "Command retry mismatch 0x%04x:%04x 0x%04x:%04x", + message_id, host_id, resp_message_id, resp_host_id); + + resp_cb = (struct host_cmd_resp_cb *)IEEE80211_SKB_CB(cmd_skb) + ->driver_data; + length = resp_cb->length; + dest_resp = resp_cb->dest_resp; + if (length >= sizeof(struct host_cmd_resp) && dest_resp) { + ret = 0; + length = min_t(int, length, + le16_to_cpu(src_resp->hdr.len) + + sizeof(struct host_cmd_header)); + memcpy(dest_resp, src_resp, length); + } else { + ret = le32_to_cpu(src_resp->status); + } + + resp_cb->ret = ret; + +exit: + if (cmd_skb && !is_late_response) { + /* Complete if not already timed out */ + if (mors->cmd_comp) + complete(mors->cmd_comp); + } + + mutex_unlock(&mors->cmd_lock); +exit_free: + dev_kfree_skb(skb); + return 0; +} + +int mm81x_cmd_sta_state(struct mm81x *mors, struct mm81x_vif *mors_vif, u16 aid, + struct ieee80211_sta *sta, + enum ieee80211_sta_state state) +{ + struct host_cmd_req_set_sta_state req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_STA_STATE, + mors_vif->id), + .aid = cpu_to_le16(aid), + .state = cpu_to_le16(state), + .uapsd_queues = sta->uapsd_queues, + }; + + memcpy(req.sta_addr, sta->addr, sizeof(req.sta_addr)); + + return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0); +} + +int mm81x_cmd_add_if(struct mm81x *mors, u16 *vif_id, const u8 *addr, + enum nl80211_iftype type) +{ + int ret; + struct host_cmd_req_add_interface req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_ADD_INTERFACE, 0), + }; + struct host_cmd_resp_add_interface resp; + + switch (type) { + case NL80211_IFTYPE_STATION: + req.interface_type = cpu_to_le32(HOST_CMD_INTERFACE_TYPE_STA); + break; + case NL80211_IFTYPE_AP: + req.interface_type = cpu_to_le32(HOST_CMD_INTERFACE_TYPE_AP); + break; + default: + return -EOPNOTSUPP; + } + + memcpy(req.addr.octet, addr, sizeof(req.addr.octet)); + + ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp, + (struct host_cmd_req *)&req, sizeof(resp), 0); + if (!ret) + *vif_id = le16_to_cpu(resp.hdr.vif_id); + + return ret; +} + +int mm81x_cmd_get_capabilities(struct mm81x *mors, u16 vif_id, + struct mm81x_fw_caps *capabilities) +{ + int ret; + int i; + struct host_cmd_req_get_capabilities req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_CAPABILITIES, vif_id), + }; + struct host_cmd_resp_get_capabilities rsp; + + ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&rsp, + (struct host_cmd_req *)&req, sizeof(rsp), 0); + if (ret) + return ret; + + capabilities->ampdu_mss = rsp.capabilities.ampdu_mss; + capabilities->mm81x_mmss_offset = rsp.morse_mmss_offset; + capabilities->beamformee_sts_capability = + rsp.capabilities.beamformee_sts_capability; + capabilities->maximum_ampdu_length_exponent = + rsp.capabilities.maximum_ampdu_length_exponent; + capabilities->number_sounding_dimensions = + rsp.capabilities.number_sounding_dimensions; + for (i = 0; i < FW_CAPABILITIES_FLAGS_WIDTH; i++) + capabilities->flags[i] = le32_to_cpu(rsp.capabilities.flags[i]); + + return ret; +} + +int mm81x_cmd_get_max_txpower(struct mm81x *mors, s32 *out_power_mbm) +{ + int ret; + struct host_cmd_req_get_max_txpower req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_MAX_TXPOWER, 0), + }; + struct host_cmd_resp_get_max_txpower resp; + + ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp, + (struct host_cmd_req *)&req, sizeof(resp), 0); + if (!ret) + *out_power_mbm = QDBM_TO_MBM(le32_to_cpu(resp.power_qdbm)); + + return ret; +} + +int mm81x_cmd_hw_scan(struct mm81x *mors, struct mm81x_hw_scan_params *params, + bool store) +{ + int ret; + struct host_cmd_req_hw_scan *req; + size_t cmd_size; + u8 *buf; + u32 flags = 0; + + cmd_size = mm81x_hw_scan_h_get_cmd_size(params); + cmd_size = ROUND_BYTES_TO_WORD(cmd_size); + + req = kzalloc(cmd_size, GFP_KERNEL); + if (!req) + return -ENOMEM; + + buf = req->variable; + + if (store) + flags = HOST_CMD_HW_SCAN_FLAGS_STORE; + else if (params->operation == MM81X_HW_SCAN_OP_START) + flags |= HOST_CMD_HW_SCAN_FLAGS_START; + else if (params->operation == MM81X_HW_SCAN_OP_STOP) + flags |= HOST_CMD_HW_SCAN_FLAGS_ABORT; + + flags |= HOST_CMD_HW_SCAN_FLAGS_1MHZ_PROBES; + + if (params->operation == MM81X_HW_SCAN_OP_START) { + req->dwell_time_ms = cpu_to_le32(params->dwell_time_ms); + buf = mm81x_hw_scan_h_insert_tlvs(params, buf); + } + + req->flags = cpu_to_le32(flags); + req->hdr = INIT_CMD_HDR((*req), HOST_CMD_ID_HW_SCAN, 0); + req->hdr.len = cpu_to_le16((u16)((buf - (u8 *)req) - sizeof(req->hdr))); + ret = mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)req, 0, 0); + kfree(req); + + return ret; +} + +int mm81x_cmd_set_txpower(struct mm81x *mors, s32 *out_power_mbm, + int txpower_mbm) +{ + int ret; + struct host_cmd_req_set_txpower req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_TXPOWER, 0), + .power_qdbm = cpu_to_le32(MBM_TO_QDBM(txpower_mbm)), + }; + struct host_cmd_resp_set_txpower resp; + + ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp, + (struct host_cmd_req *)&req, sizeof(resp), 0); + if (!ret) + *out_power_mbm = QDBM_TO_MBM(le32_to_cpu(resp.power_qdbm)); + + return ret; +} + +int mm81x_cmd_set_channel(struct mm81x *mors, u32 op_chan_freq_hz, + u8 pri_1mhz_chan_idx, u8 op_bw_mhz, u8 pri_bw_mhz, + s32 *power_mbm) +{ + int ret; + struct host_cmd_req_set_channel req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_CHANNEL, 0), + .op_chan_freq_hz = cpu_to_le32(op_chan_freq_hz), + .op_bw_mhz = op_bw_mhz, + .pri_bw_mhz = pri_bw_mhz, + .pri_1mhz_chan_idx = pri_1mhz_chan_idx, + .dot11_mode = HOST_CMD_DOT11_PROTO_MODE_AH, + }; + struct host_cmd_resp_set_channel resp; + + ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp, + (struct host_cmd_req *)&req, sizeof(resp), 0); + if (!ret) + *power_mbm = QDBM_TO_MBM(le32_to_cpu(resp.power_qdbm)); + + return ret; +} + +int mm81x_cmd_disable_key(struct mm81x *mors, struct mm81x_vif *mors_vif, + u16 aid, struct ieee80211_key_conf *key) +{ + struct host_cmd_req_disable_key req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_DISABLE_KEY, mors_vif->id), + .aid = cpu_to_le32(aid), + .key_idx = key->hw_key_idx, + .key_type = + cpu_to_le32((key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ? + HOST_CMD_TEMPORAL_KEY_TYPE_PTK : + HOST_CMD_TEMPORAL_KEY_TYPE_GTK), + }; + + return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0); +} + +int mm81x_cmd_install_key(struct mm81x *mors, struct mm81x_vif *mors_vif, + u16 aid, struct ieee80211_key_conf *key, + enum host_cmd_key_cipher cipher, + enum host_cmd_aes_key_len length) +{ + int ret; + struct host_cmd_req_install_key req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_INSTALL_KEY, mors_vif->id), + .pn = cpu_to_le64(atomic64_read(&key->tx_pn)), + .aid = cpu_to_le32(aid), + .cipher = cipher, + .key_length = length, + .key_idx = key->keyidx, + .key_type = (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ? + HOST_CMD_TEMPORAL_KEY_TYPE_PTK : + HOST_CMD_TEMPORAL_KEY_TYPE_GTK, + }; + struct host_cmd_resp_install_key resp; + + if (key->keylen > sizeof(req.key)) + return -EINVAL; + + memcpy(req.key, key->key, key->keylen); + + ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp, + (struct host_cmd_req *)&req, sizeof(resp), 0); + if (!ret) { + key->hw_key_idx = resp.key_idx; + dev_dbg(mors->dev, "Installed key @ hw index: %d", + resp.key_idx); + } + + return ret; +} + +int mm81x_cmd_cfg_multicast_filter(struct mm81x *mors, + struct mm81x_vif *mors_vif) +{ + struct host_cmd_req_mcast_filter *req; + struct mcast_filter *filter = mors->mcast_filter; + u16 filter_list_len = sizeof(filter->addr_list[0]) * filter->count; + u16 alloc_len = filter_list_len + sizeof(*req); + int ret = 0; + + req = kzalloc(alloc_len, GFP_KERNEL); + if (!req) + return -ENOMEM; + + req->hdr = INIT_CMD_HDR((*req), HOST_CMD_ID_MCAST_FILTER, mors_vif->id); + req->hdr.len = cpu_to_le16(alloc_len - sizeof(req->hdr)); + req->count = filter->count; + memcpy(req->hw_addr, filter->addr_list, filter_list_len); + + ret = mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)req, 0, 0); + kfree(req); + return ret; +} + +int mm81x_cmd_cfg_bss(struct mm81x *mors, u16 vif_id, u16 beacon_int, + u16 dtim_period, u32 cssid) +{ + struct host_cmd_req_bss_config req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_BSS_CONFIG, vif_id), + .beacon_interval_tu = cpu_to_le16(beacon_int), + .cssid = cpu_to_le32(cssid), + .dtim_period = cpu_to_le16(dtim_period), + }; + + return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0); +} + +int mm81x_cmd_config_beacon_timer(struct mm81x *mors, void *mm81x_vif, + bool enabled) +{ + struct mm81x_vif *vif = mm81x_vif; + struct host_cmd_req_bss_beacon_config req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_BSS_BEACON_CONFIG, + vif->id), + .enable = enabled, + }; + + return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0); +} + +int mm81x_cmd_set_ps(struct mm81x *mors, bool enabled) +{ + struct host_cmd_req_config_ps req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_CONFIG_PS, 0), + .enabled = (u8)enabled, + }; + + return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, + HOST_CMD_POWERSAVE_TIMEOUT_MS); +} + +int mm81x_cmd_cfg_qos(struct mm81x *mors, struct mm81x_queue_params *params) +{ + struct host_cmd_req_set_qos_params req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_QOS_PARAMS, 0), + .uapsd = params->uapsd, + .queue_idx = params->aci, + .aifs_slot_count = params->aifs, + .contention_window_min = cpu_to_le16(params->cw_min), + .contention_window_max = cpu_to_le16(params->cw_max), + .max_txop_usec = cpu_to_le32(params->txop), + }; + + return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0); +} + +int mm81x_cmd_rm_if(struct mm81x *mors, u16 vif_id) +{ + struct host_cmd_req_remove_interface req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_REMOVE_INTERFACE, vif_id), + }; + + return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0); +} + +int mm81x_cmd_set_frag_threshold(struct mm81x *mors, u32 frag_threshold) +{ + struct host_cmd_req_get_set_generic_param req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_SET_GENERIC_PARAM, 0), + .param_id = cpu_to_le32(HOST_CMD_PARAM_ID_FRAGMENT_THRESHOLD), + .action = cpu_to_le32(HOST_CMD_PARAM_ACTION_SET), + .value = cpu_to_le32(frag_threshold), + }; + + return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0); +} + +int mm81x_cmd_get_disabled_channels( + struct mm81x *mors, struct host_cmd_resp_get_disabled_channels *resp, + uint resp_len) +{ + struct host_cmd_req req = { + .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_DISABLED_CHANNELS, 0), + }; + + return mm81x_cmd_tx(mors, (struct host_cmd_resp *)resp, &req, resp_len, + 0); +} diff --git a/drivers/net/wireless/morsemicro/mm81x/command.h b/drivers/net/wireless/morsemicro/mm81x/command.h new file mode 100644 index 000000000000..0ea796f1d878 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/command.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_COMMAND_H_ +#define _MM81X_COMMAND_H_ + +#include +#include +#include "core.h" +#include "command_defs.h" + +#define HOST_CMD_IS_REQ(cmd) (le16_to_cpu((cmd)->hdr.flags) & HOST_CMD_TYPE_REQ) +#define HOST_CMD_IS_RESP(cmd) \ + (le16_to_cpu((cmd)->hdr.flags) & HOST_CMD_TYPE_RESP) +#define HOST_CMD_IS_EVT(cmd) (le16_to_cpu((cmd)->hdr.flags) & HOST_CMD_TYPE_EVT) + +struct mm81x_queue_params; + +enum mm81x_cmd_return_code { + MM81X_RET_SUCCESS = 0, + MM81X_RET_EPERM = -1, + MM81X_RET_ENOMEM = -12, + MM81X_RET_CMD_NOT_HANDLED = -32757, +}; + +#define HOST_CMD_HOST_ID_SEQ_MAX 0xFFF +#define HOST_CMD_HOST_ID_RETRY_MASK 0x000F +#define HOST_CMD_HOST_ID_SEQ_SHIFT 4 +#define HOST_CMD_HOST_ID_SEQ_MASK 0xFFF0 + +struct host_cmd_req { + struct host_cmd_header hdr; + u8 data[]; +} __packed; + +struct host_cmd_resp { + struct host_cmd_header hdr; + __le32 status; + u8 data[]; +} __packed; + +struct host_cmd_event { + struct host_cmd_header hdr; + u8 data[]; +} __packed; + +int mm81x_cmd_resp_process(struct mm81x *mors, struct sk_buff *skb); +int mm81x_cmd_add_if(struct mm81x *mors, u16 *vif_id, const u8 *addr, + enum nl80211_iftype type); +int mm81x_cmd_get_capabilities(struct mm81x *mors, u16 vif_id, + struct mm81x_fw_caps *capabilities); +int mm81x_cmd_cfg_qos(struct mm81x *mors, struct mm81x_queue_params *params); +int mm81x_cmd_config_beacon_timer(struct mm81x *mors, void *mm81x_vif, + bool enabled); +int mm81x_cmd_cfg_bss(struct mm81x *mors, u16 vif_id, u16 beacon_int, + u16 dtim_period, u32 cssid); +int mm81x_cmd_set_channel(struct mm81x *mors, u32 op_chan_freq_hz, + u8 pri_1mhz_chan_idx, u8 op_bw_mhz, u8 pri_bw_mhz, + s32 *power_mbm); +int mm81x_cmd_get_max_txpower(struct mm81x *mors, s32 *out_power_mbm); +int mm81x_cmd_set_txpower(struct mm81x *mors, s32 *out_power_mbm, + int txpower_mbm); +int mm81x_cmd_hw_scan(struct mm81x *mors, struct mm81x_hw_scan_params *params, + bool store); +int mm81x_cmd_set_ps(struct mm81x *mors, bool enabled); +int mm81x_cmd_cfg_multicast_filter(struct mm81x *mors, + struct mm81x_vif *mors_vif); +int mm81x_cmd_sta_state(struct mm81x *mors, struct mm81x_vif *mors_vif, u16 aid, + struct ieee80211_sta *sta, + enum ieee80211_sta_state state); +int mm81x_cmd_install_key(struct mm81x *mors, struct mm81x_vif *mors_vif, + u16 aid, struct ieee80211_key_conf *key, + enum host_cmd_key_cipher cipher, + enum host_cmd_aes_key_len length); +int mm81x_cmd_disable_key(struct mm81x *mors, struct mm81x_vif *mors_vif, + u16 aid, struct ieee80211_key_conf *key); +int mm81x_cmd_rm_if(struct mm81x *mors, u16 vif_id); +int mm81x_cmd_set_frag_threshold(struct mm81x *mors, u32 frag_threshold); +int mm81x_cmd_get_disabled_channels( + struct mm81x *mors, struct host_cmd_resp_get_disabled_channels *resp, + uint resp_len); + +#endif /* !_MM81X_COMMAND_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/command_defs.h b/drivers/net/wireless/morsemicro/mm81x/command_defs.h new file mode 100644 index 000000000000..91a4ac09ad80 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/command_defs.h @@ -0,0 +1,1658 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#ifndef _MM81X_COMMAND_DEFS_H_ +#define _MM81X_COMMAND_DEFS_H_ + +#include + +#define __sle16 __le16 +#define __sle32 __le32 +#define __sle64 __le64 + +#define HOST_CMD_SEMVER_MAJOR 56 +#define HOST_CMD_SEMVER_MINOR 17 +#define HOST_CMD_SEMVER_PATCH 0 + +#define HOST_CMD_TYPE_REQ BIT(0) +#define HOST_CMD_TYPE_RESP BIT(1) +#define HOST_CMD_TYPE_EVT BIT(2) + +#define HOST_CMD_SSID_MAX_LEN 32 +#define HOST_CMD_MAC_ADDR_LEN 6 + +enum host_cmd_id { + HOST_CMD_ID_SET_CHANNEL = 0x0001, + HOST_CMD_ID_GET_CHANNEL = 0x001D, + HOST_CMD_ID_GET_CHANNEL_FULL = 0x0013, + HOST_CMD_ID_GET_CHANNEL_DTIM = 0x001C, + HOST_CMD_ID_GET_VERSION = 0x0002, + HOST_CMD_ID_SET_TXPOWER = 0x0003, + HOST_CMD_ID_GET_MAX_TXPOWER = 0x0024, + HOST_CMD_ID_ADD_INTERFACE = 0x0004, + HOST_CMD_ID_REMOVE_INTERFACE = 0x0005, + HOST_CMD_ID_BSS_CONFIG = 0x0006, + HOST_CMD_ID_SCAN_CONFIG = 0x0010, + HOST_CMD_ID_SET_QOS_PARAMS = 0x0011, + HOST_CMD_ID_GET_QOS_PARAMS = 0x0012, + HOST_CMD_ID_SET_STA_STATE = 0x0014, + HOST_CMD_ID_SET_BSS_COLOR = 0x0015, + HOST_CMD_ID_CONFIG_PS = 0x0016, + HOST_CMD_ID_HEALTH_CHECK = 0x0019, + HOST_CMD_ID_CTS_SELF_PS = 0x001A, + HOST_CMD_ID_DTIM_CHANNEL_ENABLE = 0x001B, + HOST_CMD_ID_ARP_OFFLOAD = 0x0020, + HOST_CMD_ID_SET_LONG_SLEEP_CONFIG = 0x0021, + HOST_CMD_ID_SET_DUTY_CYCLE = 0x0022, + HOST_CMD_ID_GET_DUTY_CYCLE = 0x0023, + HOST_CMD_ID_GET_CAPABILITIES = 0x0025, + HOST_CMD_ID_TWT_AGREEMENT_INSTALL = 0x0026, + HOST_CMD_ID_TWT_AGREEMENT_VALIDATE = 0x0036, + HOST_CMD_ID_TWT_AGREEMENT_REMOVE = 0x0027, + HOST_CMD_ID_GET_TSF = 0x0028, + HOST_CMD_ID_MAC_ADDR = 0x0029, + HOST_CMD_ID_MPSW_CONFIG = 0x0030, + HOST_CMD_ID_INSTALL_KEY = 0x000A, + HOST_CMD_ID_DISABLE_KEY = 0x000B, + HOST_CMD_ID_DHCP_OFFLOAD = 0x0032, + HOST_CMD_ID_SET_KEEP_ALIVE_OFFLOAD = 0x0033, + HOST_CMD_ID_UPDATE_OUI_FILTER = 0x0034, + HOST_CMD_ID_IBSS_CONFIG = 0x0035, + HOST_CMD_ID_OCS = 0x0038, + HOST_CMD_ID_MESH_CONFIG = 0x0039, + HOST_CMD_ID_SET_OFFSET_TSF = 0x003A, + HOST_CMD_ID_GET_CHANNEL_USAGE = 0x003B, + HOST_CMD_ID_MCAST_FILTER = 0x003C, + HOST_CMD_ID_BSS_BEACON_CONFIG = 0x003D, + HOST_CMD_ID_UAPSD_CONFIG = 0x0040, + HOST_CMD_ID_PAGE_SLICING_CONFIG = 0x0043, + HOST_CMD_ID_HW_SCAN = 0x0044, + HOST_CMD_ID_SET_WHITELIST = 0x0045, + HOST_CMD_ID_ARP_PERIODIC_REFRESH = 0x0046, + HOST_CMD_ID_SET_TCP_KEEPALIVE = 0x0047, + HOST_CMD_ID_FORCE_POWER_MODE = 0x0048, + HOST_CMD_ID_LI_SLEEP = 0x0049, + HOST_CMD_ID_GET_DISABLED_CHANNELS = 0x004A, + HOST_CMD_ID_SET_CQM_RSSI = 0x004F, + HOST_CMD_ID_GET_APF_CAPABILITIES = 0x0050, + HOST_CMD_ID_READ_WRITE_APF = 0x0051, + HOST_CMD_ID_BSSID_SET = 0x0052, + HOST_CMD_ID_BEACON_OFFLOAD = 0x0053, + HOST_CMD_ID_PROBE_RESPONSE_OFFLOAD = 0x0054, + HOST_CMD_ID_HOST_STATS_LOG = 0x2007, + HOST_CMD_ID_HOST_STATS_RESET = 0x2008, + HOST_CMD_ID_MAC_STATS_LOG = 0x200C, + HOST_CMD_ID_MAC_STATS_RESET = 0x200D, + HOST_CMD_ID_UPHY_STATS_LOG = 0x200E, + HOST_CMD_ID_UPHY_STATS_RESET = 0x200F, + HOST_CMD_ID_SET_STA_TYPE = 0xA000, + HOST_CMD_ID_SET_ENC_MODE = 0xA001, + HOST_CMD_ID_TEST_BA = 0xA002, + HOST_CMD_ID_SET_LISTEN_INTERVAL = 0xA003, + HOST_CMD_ID_SET_AMPDU = 0xA004, + HOST_CMD_ID_COREDUMP = 0xA006, + HOST_CMD_ID_SET_S1G_OP_CLASS = 0xA007, + HOST_CMD_ID_SEND_WAKE_ACTION_FRAME = 0xA008, + HOST_CMD_ID_VENDOR_IE_CONFIG = 0xA009, + HOST_CMD_ID_SET_TWT_CONF = 0xA010, + HOST_CMD_ID_GET_AVAILABLE_CHANNELS = 0xA011, + HOST_CMD_ID_SET_ECSA_S1G_INFO = 0xA012, + HOST_CMD_ID_GET_HW_VERSION = 0xA013, + HOST_CMD_ID_CAC = 0xA014, + HOST_CMD_ID_DRIVER_SET_DUTY_CYCLE = 0xA015, + HOST_CMD_ID_OCS_DRIVER = 0xA017, + HOST_CMD_ID_MBSSID = 0xA016, + HOST_CMD_ID_SET_MESH_CONFIG = 0xA018, + HOST_CMD_ID_SET_MCBA_CONF = 0xA019, + HOST_CMD_ID_DYNAMIC_PEERING_CONFIG = 0xA020, + HOST_CMD_ID_CONFIG_RAW = 0xA021, + HOST_CMD_ID_CONFIG_BSS_STATS = 0xA022, + HOST_CMD_ID_GET_RSSI = 0x1002, + HOST_CMD_ID_SET_IFS = 0x1003, + HOST_CMD_ID_SET_FEM_SETTINGS = 0x1005, + HOST_CMD_ID_SET_TXOP = 0x1008, + HOST_CMD_ID_SET_CONTROL_RESPONSE = 0x1009, + HOST_CMD_ID_SET_PERIODIC_CAL = 0x100A, + HOST_CMD_ID_SET_BCN_RSSI_THRESHOLD = 0x100B, + HOST_CMD_ID_SET_TX_PKT_LIFETIME_USECS = 0x100C, + HOST_CMD_ID_SET_PHYSM_WATCHDOG = 0x100D, + HOST_CMD_ID_TX_POLAR = 0x100E, + HOST_CMD_ID_EVT_STA_STATE = 0x4001, + HOST_CMD_ID_EVT_BEACON_LOSS = 0x4002, + HOST_CMD_ID_EVT_SIG_FIELD_ERROR = 0x4003, + HOST_CMD_ID_EVT_UMAC_TRAFFIC_CONTROL = 0x4004, + HOST_CMD_ID_EVT_DHCP_LEASE_UPDATE = 0x4005, + HOST_CMD_ID_EVT_OCS_DONE = 0x4006, + HOST_CMD_ID_EVT_HW_SCAN_DONE = 0x4011, + HOST_CMD_ID_EVT_CHANNEL_USAGE = 0x4012, + HOST_CMD_ID_EVT_CONNECTION_LOSS = 0x4013, + HOST_CMD_ID_EVT_SCHED_SCAN_RESULTS = 0x4014, + HOST_CMD_ID_EVT_CQM_RSSI_NOTIFY = 0x4015, + HOST_CMD_ID_EVT_SCAN_DONE = 0x4007, + HOST_CMD_ID_EVT_SCAN_RESULT = 0x4008, + HOST_CMD_ID_EVT_CONNECTED = 0x4009, + HOST_CMD_ID_EVT_DISCONNECTED = 0x4010, + HOST_CMD_ID_EVT_BEACON_FILTER_MATCH = 0x4016, + HOST_CMD_ID_SET_CAPABILITIES = 0x8118, + HOST_CMD_ID_SET_TRANSMISSION_RATE = 0x8009, + HOST_CMD_ID_FORCE_ASSERT = 0x800E, + HOST_CMD_ID_GET_SET_GENERIC_PARAM = 0x003E, +}; + +struct host_cmd_mac_addr { + u8 octet[HOST_CMD_MAC_ADDR_LEN]; +}; + +enum host_cmd_ocs_subcmd { + HOST_CMD_OCS_SUBCMD_CONFIG = 1, + HOST_CMD_OCS_SUBCMD_STATUS = 2, +}; + +enum host_cmd_headless_cfg_option { + HOST_CMD_HEADLESS_CFG_OPTION_KEEP_IFACES = BIT(0), + HOST_CMD_HEADLESS_CFG_OPTION_BUFFER_RX = BIT(1), + HOST_CMD_HEADLESS_CFG_OPTION_NOTIFY_ON_ANY_RX = BIT(2), +}; + +struct host_cmd_header { + __le16 flags; + __le16 message_id; + __le16 len; + __le16 host_id; + __le16 vif_id; + __le16 pad; +}; + +#define HOST_CMD_CHANNEL_BW_NOT_SET 0xFF +#define HOST_CMD_CHANNEL_IDX_NOT_SET 0xFF +#define HOST_CMD_CHANNEL_FREQ_NOT_SET 0xFFFFFFFF + +enum host_cmd_dot11_proto_mode { + HOST_CMD_DOT11_PROTO_MODE_AH = 0, +}; + +struct host_cmd_req_set_channel { + struct host_cmd_header hdr; + __le32 op_chan_freq_hz; + u8 op_bw_mhz; + u8 pri_bw_mhz; + u8 pri_1mhz_chan_idx; + u8 dot11_mode; + u8 __deprecated_reg_tx_power_set; + u8 is_off_channel; +} __packed; + +struct host_cmd_resp_set_channel { + struct host_cmd_header hdr; + __le32 status; + __sle32 power_qdbm; +} __packed; + +struct host_cmd_req_get_channel { + struct host_cmd_header hdr; +} __packed; + +struct host_cmd_resp_get_channel { + struct host_cmd_header hdr; + __le32 status; + __le32 op_chan_freq_hz; + u8 op_chan_bw_mhz; + u8 pri_chan_bw_mhz; + u8 pri_1mhz_chan_idx; +} __packed; + +#define HOST_CMD_MAX_VERSION_LEN 128 + +struct host_cmd_req_get_version { + struct host_cmd_header hdr; +} __packed; + +struct host_cmd_resp_get_version { + struct host_cmd_header hdr; + __le32 status; + __sle32 length; + u8 version[]; +} __packed; + +struct host_cmd_req_set_txpower { + struct host_cmd_header hdr; + __sle32 power_qdbm; +} __packed; + +struct host_cmd_resp_set_txpower { + struct host_cmd_header hdr; + __le32 status; + __sle32 power_qdbm; +} __packed; + +struct host_cmd_req_get_max_txpower { + struct host_cmd_header hdr; +} __packed; + +struct host_cmd_resp_get_max_txpower { + struct host_cmd_header hdr; + __le32 status; + __sle32 power_qdbm; +} __packed; + +enum host_cmd_interface_type { + HOST_CMD_INTERFACE_TYPE_INVALID = 0, + HOST_CMD_INTERFACE_TYPE_STA = 1, + HOST_CMD_INTERFACE_TYPE_AP = 2, + HOST_CMD_INTERFACE_TYPE_MON = 3, + HOST_CMD_INTERFACE_TYPE_ADHOC = 4, + HOST_CMD_INTERFACE_TYPE_MESH = 5, + HOST_CMD_INTERFACE_TYPE_LAST = HOST_CMD_INTERFACE_TYPE_MESH, +}; + +struct host_cmd_req_add_interface { + struct host_cmd_header hdr; + struct host_cmd_mac_addr addr; + __le32 interface_type; +} __packed; + +struct host_cmd_resp_add_interface { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_remove_interface { + struct host_cmd_header hdr; +} __packed; + +struct host_cmd_resp_remove_interface { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_bss_config { + struct host_cmd_header hdr; + __le16 beacon_interval_tu; + __le16 dtim_period; + u8 __padding[2]; + __le32 cssid; +} __packed; + +struct host_cmd_resp_bss_config { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_scan_config { + struct host_cmd_header hdr; + u8 enabled; + u8 is_survey; +} __packed; + +struct host_cmd_resp_scan_config { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_qos_params { + struct host_cmd_header hdr; + u8 uapsd; + u8 queue_idx; + u8 aifs_slot_count; + __le16 contention_window_min; + __le16 contention_window_max; + __le32 max_txop_usec; +} __packed; + +struct host_cmd_resp_set_qos_params { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_get_qos_params { + struct host_cmd_header hdr; + u8 queue_idx; +} __packed; + +struct host_cmd_resp_get_qos_params { + struct host_cmd_header hdr; + __le32 status; + u8 aifs_slot_count; + __le16 contention_window_min; + __le16 contention_window_max; + __le32 max_txop_usec; +} __packed; + +struct host_cmd_req_set_sta_state { + struct host_cmd_header hdr; + u8 sta_addr[HOST_CMD_MAC_ADDR_LEN]; + __le16 aid; + __le16 state; + u8 uapsd_queues; + __le32 flags; +} __packed; + +struct host_cmd_resp_set_sta_state { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_bss_color { + struct host_cmd_header hdr; + u8 bss_color; +} __packed; + +struct host_cmd_resp_set_bss_color { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_config_ps { + struct host_cmd_header hdr; + u8 enabled; + u8 dynamic_ps_offload; +} __packed; + +struct host_cmd_resp_config_ps { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_health_check { + struct host_cmd_header hdr; +} __packed; + +struct host_cmd_resp_health_check { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_cts_self_ps { + struct host_cmd_header hdr; + u8 enable; +} __packed; + +struct host_cmd_resp_cts_self_ps { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_dtim_channel_enable { + struct host_cmd_header hdr; + u8 enable; +} __packed; + +struct host_cmd_resp_dtim_channel_enable { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +#define HOST_CMD_ARP_OFFLOAD_MAX_IP_ADDRESSES 4 + +struct host_cmd_req_arp_offload { + struct host_cmd_header hdr; + __be32 ip_table[HOST_CMD_ARP_OFFLOAD_MAX_IP_ADDRESSES]; +} __packed; + +struct host_cmd_resp_arp_offload { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_long_sleep_config { + struct host_cmd_header hdr; + u8 enabled; +} __packed; + +struct host_cmd_resp_set_long_sleep_config { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +#define HOST_CMD_DUTY_CYCLE_SET_CFG_DUTY_CYCLE BIT(0) +#define HOST_CMD_DUTY_CYCLE_SET_CFG_OMIT_CONTROL_RESP BIT(1) +#define HOST_CMD_DUTY_CYCLE_SET_CFG_EXT BIT(2) +#define HOST_CMD_DUTY_CYCLE_SET_CFG_BURST_RECORD_UNIT BIT(3) + +enum host_cmd_duty_cycle_mode { + HOST_CMD_DUTY_CYCLE_MODE_SPREAD = 0, + HOST_CMD_DUTY_CYCLE_MODE_BURST = 1, + HOST_CMD_DUTY_CYCLE_MODE_LAST = HOST_CMD_DUTY_CYCLE_MODE_BURST, +}; + +struct host_cmd_duty_cycle_configuration { + u8 omit_control_responses; + __le32 duty_cycle; +} __packed; + +struct host_cmd_duty_cycle_set_configuration_ext { + __le32 burst_record_unit_us; + u8 mode; +} __packed; + +struct host_cmd_duty_cycle_configuration_ext { + __le32 airtime_remaining_us; + __le32 burst_window_duration_us; + struct host_cmd_duty_cycle_set_configuration_ext set; +} __packed; + +struct host_cmd_req_set_duty_cycle { + struct host_cmd_header hdr; + struct host_cmd_duty_cycle_configuration config; + u8 set_cfgs; + struct host_cmd_duty_cycle_set_configuration_ext config_ext; +} __packed; + +struct host_cmd_resp_get_duty_cycle { + struct host_cmd_header hdr; + __le32 status; + struct host_cmd_duty_cycle_configuration config; + struct host_cmd_duty_cycle_configuration_ext config_ext; +} __packed; + +#define HOST_CMD_SET_S1G_CAP_FLAGS BIT(0) +#define HOST_CMD_SET_S1G_CAP_AMPDU_MSS BIT(1) +#define HOST_CMD_SET_S1G_CAP_BEAM_STS BIT(2) +#define HOST_CMD_SET_S1G_CAP_NUM_SOUND_DIMS BIT(3) +#define HOST_CMD_SET_S1G_CAP_MAX_AMPDU_LEXP BIT(4) +#define HOST_CMD_SET_MORSE_CAP_MMSS_OFFSET BIT(5) +#define HOST_CMD_S1G_CAPABILITY_FLAGS_WIDTH 4 + +struct host_cmd_mm_capabilities { + __le32 flags[HOST_CMD_S1G_CAPABILITY_FLAGS_WIDTH]; + u8 ampdu_mss; + u8 beamformee_sts_capability; + u8 number_sounding_dimensions; + u8 maximum_ampdu_length_exponent; +} __packed; + +struct host_cmd_req_get_capabilities { + struct host_cmd_header hdr; +} __packed; + +struct host_cmd_resp_get_capabilities { + struct host_cmd_header hdr; + __le32 status; + struct host_cmd_mm_capabilities capabilities; + u8 morse_mmss_offset; +} __packed; + +#define HOST_CMD_DOT11_TWT_AGREEMENT_MAX_LEN 20 + +struct host_cmd_req_twt_agreement_install { + struct host_cmd_header hdr; + u8 flow_id; + u8 agreement_len; + u8 agreement[HOST_CMD_DOT11_TWT_AGREEMENT_MAX_LEN]; +} __packed; + +struct host_cmd_resp_twt_agreement_install { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_twt_agreement_validate { + struct host_cmd_header hdr; + u8 flow_id; + u8 agreement_len; + u8 agreement[HOST_CMD_DOT11_TWT_AGREEMENT_MAX_LEN]; +} __packed; + +struct host_cmd_resp_twt_agreement_validate { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_twt_agreement_remove { + struct host_cmd_header hdr; + u8 flow_id; +} __packed; + +struct host_cmd_req_get_tsf { + struct host_cmd_header hdr; +} __packed; + +struct host_cmd_resp_get_tsf { + struct host_cmd_header hdr; + __le32 status; + __le64 now_tsf; + __le64 now_chip_ts; +} __packed; + +struct host_cmd_req_mac_addr { + struct host_cmd_header hdr; + u8 write; + u8 octet[HOST_CMD_MAC_ADDR_LEN]; +} __packed; + +struct host_cmd_resp_mac_addr { + struct host_cmd_header hdr; + __le32 status; + u8 octet[HOST_CMD_MAC_ADDR_LEN]; +} __packed; + +#define HOST_CMD_SET_MPSW_CFG_AIRTIME_BOUNDS BIT(0) +#define HOST_CMD_SET_MPSW_CFG_PKT_SPC_WIN_LEN BIT(1) +#define HOST_CMD_SET_MPSW_CFG_ENABLED BIT(2) + +struct host_cmd_mpsw_configuration { + __le32 airtime_max_us; + __le32 airtime_min_us; + __le32 packet_space_window_length_us; + u8 enable; +} __packed; + +struct host_cmd_req_mpsw_config { + struct host_cmd_header hdr; + struct host_cmd_mpsw_configuration config; + u8 set_cfgs; +} __packed; + +struct host_cmd_resp_mpsw_config { + struct host_cmd_header hdr; + __le32 status; + struct host_cmd_mpsw_configuration config; +} __packed; + +#define HOST_CMD_MAX_KEY_LEN 32 + +enum host_cmd_key_cipher { + HOST_CMD_KEY_CIPHER_INVALID = 0, + HOST_CMD_KEY_CIPHER_AES_CCM = 1, + HOST_CMD_KEY_CIPHER_AES_GCM = 2, + HOST_CMD_KEY_CIPHER_AES_CMAC = 3, + HOST_CMD_KEY_CIPHER_AES_GMAC = 4, + HOST_CMD_KEY_CIPHER_LAST = HOST_CMD_KEY_CIPHER_AES_GMAC, +}; + +enum host_cmd_aes_key_len { + HOST_CMD_AES_KEY_LEN_INVALID = 0, + HOST_CMD_AES_KEY_LEN_LENGTH_128 = 1, + HOST_CMD_AES_KEY_LEN_LENGTH_256 = 2, + HOST_CMD_AES_KEY_LEN_LENGTH_LAST = HOST_CMD_AES_KEY_LEN_LENGTH_256, +}; + +enum host_cmd_temporal_key_type { + HOST_CMD_TEMPORAL_KEY_TYPE_INVALID = 0, + HOST_CMD_TEMPORAL_KEY_TYPE_GTK = 1, + HOST_CMD_TEMPORAL_KEY_TYPE_PTK = 2, + HOST_CMD_TEMPORAL_KEY_TYPE_IGTK = 3, + HOST_CMD_TEMPORAL_KEY_TYPE_LAST = HOST_CMD_TEMPORAL_KEY_TYPE_IGTK, +}; + +struct host_cmd_req_install_key { + struct host_cmd_header hdr; + __le64 pn; + __le32 aid; + u8 key_idx; + u8 cipher; + u8 key_length; + u8 key_type; + u8 __padding[2]; + u8 key[HOST_CMD_MAX_KEY_LEN]; +} __packed; + +struct host_cmd_resp_install_key { + struct host_cmd_header hdr; + __le32 status; + u8 key_idx; +} __packed; + +struct host_cmd_req_disable_key { + struct host_cmd_header hdr; + __le32 key_type; + __le32 aid; + u8 key_idx; +} __packed; + +struct host_cmd_resp_disable_key { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +enum host_cmd_dhcp_opcode { + HOST_CMD_DHCP_OPCODE_ENABLE = 0, + HOST_CMD_DHCP_OPCODE_DO_DISCOVERY = 1, + HOST_CMD_DHCP_OPCODE_GET_LEASE = 2, + HOST_CMD_DHCP_OPCODE_CLEAR_LEASE = 3, + HOST_CMD_DHCP_OPCODE_RENEW_LEASE = 4, + HOST_CMD_DHCP_OPCODE_REBIND_LEASE = 5, + HOST_CMD_DHCP_OPCODE_SEND_LEASE_UPDATE = 6, +}; + +enum host_cmd_dhcp_retcode { + HOST_CMD_DHCP_RETCODE_SUCCESS = 0, + HOST_CMD_DHCP_RETCODE_NOT_ENABLED = 1, + HOST_CMD_DHCP_RETCODE_ALREADY_ENABLED = 2, + HOST_CMD_DHCP_RETCODE_NO_LEASE = 3, + HOST_CMD_DHCP_RETCODE_HAVE_LEASE = 4, + HOST_CMD_DHCP_RETCODE_BUSY = 5, + HOST_CMD_DHCP_RETCODE_BAD_VIF = 6, +}; + +struct host_cmd_req_dhcp_offload { + struct host_cmd_header hdr; + __le32 opcode; +} __packed; + +struct host_cmd_resp_dhcp_offload { + struct host_cmd_header hdr; + __le32 status; + __le32 retcode; + __le32 my_ip; + __le32 netmask; + __le32 router; + __le32 dns; +} __packed; + +struct host_cmd_req_set_keep_alive_offload { + struct host_cmd_header hdr; + __le16 bss_max_idle_period; + u8 interpret_as_11ah; +} __packed; + +#define HOST_CMD_MAX_OUI_FILTERS 5 +#define HOST_CMD_OUI_SIZE 3 +#define HOST_CMD_MAX_OUI_FILTER_ARRAY_SIZE 15 + +struct host_cmd_req_update_oui_filter { + struct host_cmd_header hdr; + u8 n_ouis; + u8 ouis[HOST_CMD_MAX_OUI_FILTERS][HOST_CMD_OUI_SIZE]; +} __packed; + +enum host_cmd_ibss_config_opcode { + HOST_CMD_IBSS_CONFIG_OPCODE_CREATE = 0, + HOST_CMD_IBSS_CONFIG_OPCODE_JOIN = 1, + HOST_CMD_IBSS_CONFIG_OPCODE_STOP = 2, +}; + +struct host_cmd_req_ibss_config { + struct host_cmd_header hdr; + u8 ibss_bssid[HOST_CMD_MAC_ADDR_LEN]; + u8 ibss_cfg_opcode; + u8 ibss_probe_filtering; +} __packed; + +enum host_cmd_ocs_type { + HOST_CMD_OCS_TYPE_QNULL = 0, + HOST_CMD_OCS_TYPE_RAW = 1, +}; + +struct host_cmd_ocs_config_req { + __le32 op_channel_freq_hz; + u8 op_channel_bw_mhz; + u8 pri_channel_bw_mhz; + u8 pri_1mhz_channel_index; + __le16 aid; + u8 type; +} __packed; + +struct host_cmd_ocs_status_resp { + u8 running; +} __packed; + +struct host_cmd_req_ocs { + struct host_cmd_header hdr; + __le32 subcmd; + union { + u8 opaque[0]; + struct host_cmd_ocs_config_req config; + }; +} __packed; + +struct host_cmd_resp_ocs { + struct host_cmd_header hdr; + __le32 status; + __le32 subcmd; + union { + u8 opaque[0]; + struct host_cmd_ocs_status_resp ocs_status; + }; +} __packed; + +enum host_cmd_mesh_config_opcode { + HOST_CMD_MESH_CONFIG_OPCODE_START = 0, + HOST_CMD_MESH_CONFIG_OPCODE_STOP = 1, +}; + +struct host_cmd_req_mesh_config { + struct host_cmd_header hdr; + u8 mesh_cfg_opcode; + u8 enable_beaconing; + u8 mbca_config; + u8 min_beacon_gap_ms; + __le16 mbss_start_scan_duration_ms; + __le16 tbtt_adj_timer_interval_ms; +} __packed; + +struct host_cmd_req_set_offset_tsf { + struct host_cmd_header hdr; + __sle64 offset_tsf; +} __packed; + +struct host_cmd_req_get_channel_usage { + struct host_cmd_header hdr; +} __packed; + +struct host_cmd_resp_get_channel_usage { + struct host_cmd_header hdr; + __le32 status; + __le64 time_listen; + __le64 busy_time; + __le32 freq_hz; + s8 noise; + u8 bw_mhz; +} __packed; + +#define HOST_CMD_MAX_MCAST_FILTERS 12 + +struct host_cmd_req_mcast_filter { + struct host_cmd_header hdr; + u8 count; + __le32 hw_addr[]; +} __packed; + +struct host_cmd_req_bss_beacon_config { + struct host_cmd_header hdr; + u8 enable; +} __packed; + +struct host_cmd_resp_bss_beacon_config { + struct host_cmd_header hdr; + __le32 status; + __le16 interface_id; +} __packed; + +struct host_cmd_req_uapsd_config { + struct host_cmd_header hdr; + u8 auto_trigger_enabled; + __le32 auto_trigger_timeout; +} __packed; + +struct host_cmd_resp_uapsd_config { + struct host_cmd_header hdr; + __le32 status; + u8 auto_trigger_enabled; +} __packed; + +struct host_cmd_req_page_slicing_config { + struct host_cmd_header hdr; + u8 enable; +} __packed; + +#define HOST_CMD_HW_SCAN_FLAGS_START BIT(0) +#define HOST_CMD_HW_SCAN_FLAGS_ABORT BIT(1) +#define HOST_CMD_HW_SCAN_FLAGS_SURVEY BIT(2) +#define HOST_CMD_HW_SCAN_FLAGS_STORE BIT(3) +#define HOST_CMD_HW_SCAN_FLAGS_1MHZ_PROBES BIT(4) +#define HOST_CMD_HW_SCAN_FLAGS_SCHED_START BIT(5) +#define HOST_CMD_HW_SCAN_FLAGS_SCHED_STOP BIT(6) +#define HOST_CMD_HW_SCAN_FLAGS_PROBE_ON_DOZE_BEACON BIT(7) + +enum host_cmd_hw_scan_tlv_tag { + HOST_CMD_HW_SCAN_TLV_TAG_PAD = 0, + HOST_CMD_HW_SCAN_TLV_TAG_PROBE_REQ = 1, + HOST_CMD_HW_SCAN_TLV_TAG_CHAN_LIST = 2, + HOST_CMD_HW_SCAN_TLV_TAG_POWER_LIST = 3, + HOST_CMD_HW_SCAN_TLV_TAG_DWELL_ON_HOME = 4, + HOST_CMD_HW_SCAN_TLV_TAG_SCHED = 5, + HOST_CMD_HW_SCAN_TLV_TAG_FILTER = 6, + HOST_CMD_HW_SCAN_TLV_TAG_SCHED_PARAMS = 7, +}; + +struct host_cmd_hw_scan_tlv { + __le16 tag; + __le16 len; + u8 value[]; +} __packed; + +struct host_cmd_req_hw_scan { + struct host_cmd_header hdr; + __le32 flags; + __le32 dwell_time_ms; + u8 variable[]; +} __packed; + +#define HOST_CMD_WHITELIST_FLAGS_CLEAR BIT(0) + +struct host_cmd_req_set_whitelist { + struct host_cmd_header hdr; + u8 flags; + u8 ip_protocol; + __be16 llc_protocol; + __be32 src_ip; + __be32 dest_ip; + __be32 netmask; + __be16 src_port; + __be16 dest_port; +} __packed; + +struct host_cmd_arp_periodic_params { + __le32 refresh_period_s; + __le32 destination_ip; + u8 send_as_garp; +} __packed; + +struct host_cmd_req_arp_periodic_refresh { + struct host_cmd_header hdr; + struct host_cmd_arp_periodic_params config; +} __packed; + +#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_PERIOD BIT(0) +#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_RETRY_COUNT BIT(1) +#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_RETRY_INTERVAL BIT(2) +#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_SRC_IP_ADDR BIT(3) +#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_DEST_IP_ADDR BIT(4) +#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_SRC_PORT BIT(5) +#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_DEST_PORT BIT(6) + +struct host_cmd_req_set_tcp_keepalive { + struct host_cmd_header hdr; + u8 enabled; + u8 retry_count; + u8 retry_interval_s; + u8 set_cfgs; + __be32 src_ip; + __be32 dest_ip; + __be16 src_port; + __be16 dest_port; + __le16 period_s; +} __packed; + +enum host_cmd_power_mode { + HOST_CMD_POWER_MODE_SNOOZE = 0, + HOST_CMD_POWER_MODE_DEEP_SLEEP = 1, + HOST_CMD_POWER_MODE_HIBERNATE = 2, +}; + +struct host_cmd_req_force_power_mode { + struct host_cmd_header hdr; + __le32 mode; +} __packed; + +struct host_cmd_req_li_sleep { + struct host_cmd_header hdr; + __le32 listen_interval; +} __packed; + +struct host_cmd_disabled_channel_entry { + __le16 freq_100khz; + u8 bw_mhz; +} __packed; + +struct host_cmd_resp_get_disabled_channels { + struct host_cmd_header hdr; + __le32 status; + __le32 n_channels; + struct host_cmd_disabled_channel_entry channels[]; +} __packed; + +struct host_cmd_req_set_cqm_rssi { + struct host_cmd_header hdr; + __sle32 threshold; + __le32 hysteresis; +} __packed; + +struct host_cmd_req_get_apf_capabilities { + struct host_cmd_header hdr; +} __packed; + +struct host_cmd_resp_get_apf_capabilities { + struct host_cmd_header hdr; + __le32 status; + __le32 max_length; + u8 version; +} __packed; + +struct host_cmd_req_read_write_apf { + struct host_cmd_header hdr; + __le32 offset; + __le16 program_length; + u8 write; + u8 program[]; +} __packed; + +struct host_cmd_resp_read_write_apf { + struct host_cmd_header hdr; + __le32 status; + __le16 program_length; + u8 program[]; +} __packed; + +struct host_cmd_req_bssid_set { + struct host_cmd_header hdr; + struct host_cmd_mac_addr bssid; +} __packed; + +#define HOST_CMD_BEACON_OFFLOAD_FLAGS_START BIT(0) +#define HOST_CMD_BEACON_OFFLOAD_FLAGS_STOP BIT(1) +#define HOST_CMD_BEACON_OFFLOAD_CSSID_LEN 4 + +enum host_cmd_beacon_offload_tlv_tag { + HOST_CMD_BEACON_OFFLOAD_TLV_TAG_DTIM_CNT = 0, + HOST_CMD_BEACON_OFFLOAD_TLV_TAG_FRAME_CTRL = 1, + HOST_CMD_BEACON_OFFLOAD_TLV_TAG_CHANGE_SEQ = 2, + HOST_CMD_BEACON_OFFLOAD_TLV_TAG_CSSID = 3, + HOST_CMD_BEACON_OFFLOAD_TLV_TAG_IES = 4, + HOST_CMD_BEACON_OFFLOAD_TLV_TAG_TX_INFO = 5, +}; + +struct host_cmd_beacon_offload_tlv_hdr { + __le16 tag; + __le16 len; +} __packed; + +struct host_cmd_beacon_offload_tlv_generic { + struct host_cmd_beacon_offload_tlv_hdr hdr; + u8 value[]; +} __packed; + +struct host_cmd_beacon_offload_tlv_dtim_cnt { + struct host_cmd_beacon_offload_tlv_hdr hdr; + __le16 dtim_cnt; +} __packed; + +struct host_cmd_beacon_offload_tlv_frame_ctrl { + struct host_cmd_beacon_offload_tlv_hdr hdr; + u8 frame_ctrl[2]; +} __packed; + +struct host_cmd_beacon_offload_tlv_change_seq { + struct host_cmd_beacon_offload_tlv_hdr hdr; + __le16 change_seq; +} __packed; + +struct host_cmd_beacon_offload_tlv_tx_info { + struct host_cmd_beacon_offload_tlv_hdr hdr; + u8 bw_mhz; +} __packed; + +struct host_cmd_beacon_offload_tlv_cssid { + struct host_cmd_beacon_offload_tlv_hdr hdr; + u8 cssid[HOST_CMD_BEACON_OFFLOAD_CSSID_LEN]; +} __packed; + +struct host_cmd_beacon_offload_tlv_ies { + struct host_cmd_beacon_offload_tlv_hdr hdr; + u8 buf[]; +} __packed; + +struct host_cmd_req_beacon_offload { + struct host_cmd_header hdr; + __le32 flags; + u8 variable[]; +} __packed; + +struct host_cmd_resp_beacon_offload { + struct host_cmd_header hdr; + __le32 status; + __le16 dtim_count; +} __packed; + +struct host_cmd_req_probe_response_offload { + struct host_cmd_header hdr; + u8 enable; + __le16 probe_resp_len; + u8 probe_resp_buf[]; +} __packed; + +struct host_cmd_resp_probe_response_offload { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_sta_type { + struct host_cmd_header hdr; + u8 sta_type; +} __packed; + +struct host_cmd_req_set_enc_mode { + struct host_cmd_header hdr; + u8 enc_mode; +} __packed; + +struct host_cmd_req_test_ba { + struct host_cmd_header hdr; + u8 addr[HOST_CMD_MAC_ADDR_LEN]; + u8 start; + u8 tx; + __le32 tid; +} __packed; + +struct host_cmd_req_set_listen_interval { + struct host_cmd_header hdr; + __le16 listen_interval; +} __packed; + +struct host_cmd_req_set_ampdu { + struct host_cmd_header hdr; + u8 ampdu_enabled; +} __packed; + +struct host_cmd_req_set_s1g_op_class { + struct host_cmd_header hdr; + u8 opclass; + u8 prim_opclass; +} __packed; + +struct host_cmd_req_send_wake_action_frame { + struct host_cmd_header hdr; + u8 dest_addr[HOST_CMD_MAC_ADDR_LEN]; + __le32 payload_size; + u8 payload[]; +} __packed; + +#define HOST_CMD_MAX_VENDOR_IE_LENGTH 255 +#define HOST_CMD_VENDOR_IE_TYPE_FLAG_BEACON BIT(0) +#define HOST_CMD_VENDOR_IE_TYPE_FLAG_PROBE_REQ BIT(1) +#define HOST_CMD_VENDOR_IE_TYPE_FLAG_PROBE_RESP BIT(2) +#define HOST_CMD_VENDOR_IE_TYPE_FLAG_ASSOC_REQ BIT(3) +#define HOST_CMD_VENDOR_IE_TYPE_FLAG_ASSOC_RESP BIT(4) + +enum host_cmd_vendor_ie_op { + HOST_CMD_VENDOR_IE_OP_ADD_ELEMENT = 0, + HOST_CMD_VENDOR_IE_OP_CLEAR_ELEMENTS = 1, + HOST_CMD_VENDOR_IE_OP_ADD_FILTER = 2, + HOST_CMD_VENDOR_IE_OP_CLEAR_FILTERS = 3, + HOST_CMD_VENDOR_IE_OP_INVALID = U16_MAX, +}; + +struct host_cmd_req_vendor_ie_config { + struct host_cmd_header hdr; + __le16 opcode; + __le16 mgmt_type_mask; + u8 data[HOST_CMD_MAX_VENDOR_IE_LENGTH]; +} __packed; + +struct host_cmd_resp_vendor_ie_config { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +enum host_cmd_twt_conf_op { + HOST_CMD_TWT_CONF_OP_CONFIGURE = 0, + HOST_CMD_TWT_CONF_OP_FORCE_INSTALL_AGREEMENT = 1, + HOST_CMD_TWT_CONF_OP_REMOVE_AGREEMENT = 2, + HOST_CMD_TWT_CONF_OP_CONFIGURE_EXPLICIT = 3, +}; + +struct host_cmd_explicit_twt_wake_interval { + __le16 wake_interval_mantissa; + u8 wake_interval_exponent; + u8 __padding[5]; +} __packed; + +union host_cmd_wake_interval { + __le64 wake_interval_us; + struct host_cmd_explicit_twt_wake_interval explicit_twt; +} __packed; + +struct host_cmd_req_set_twt_conf { + struct host_cmd_header hdr; + u8 opcode; + u8 flow_id; + __le64 target_wake_time; + union host_cmd_wake_interval wake_interval; + __le32 wake_duration_us; + u8 twt_setup_command; + u8 __padding[3]; +} __packed; + +#define HOST_CMD_MAX_AVAILABLE_CHANNELS 255 + +struct host_cmd_channel_info { + __le32 frequency_khz; + u8 channel_5g; + u8 channel_s1g; + u8 bandwidth_mhz; +} __packed; + +struct host_cmd_resp_get_available_channels { + struct host_cmd_header hdr; + __le32 status; + __le32 num_channels; + struct host_cmd_channel_info channels[HOST_CMD_MAX_AVAILABLE_CHANNELS]; +} __packed; + +#define HOST_CMD_S1G_CAP0_S1G_LONG BIT(0) +#define HOST_CMD_S1G_CAP0_SGI_1MHZ BIT(1) +#define HOST_CMD_S1G_CAP0_SGI_2MHZ BIT(2) +#define HOST_CMD_S1G_CAP0_SGI_4MHZ BIT(3) +#define HOST_CMD_S1G_CAP0_SGI_8MHZ BIT(4) +#define HOST_CMD_S1G_CAP0_SGI_16MHZ BIT(5) + +struct host_cmd_req_set_ecsa_s1g_info { + struct host_cmd_header hdr; + __le32 operating_channel_freq_hz; + u8 opclass; + u8 primary_channel_bw_mhz; + u8 prim_1mhz_ch_idx; + u8 operating_channel_bw_mhz; + u8 prim_opclass; + u8 s1g_cap0; + u8 s1g_cap1; + u8 s1g_cap2; + u8 s1g_cap3; +} __packed; + +struct host_cmd_resp_get_hw_version { + struct host_cmd_header hdr; + __le32 status; + u8 hw_version[64]; +} __packed; + +#define HOST_CMD_CAC_CFG_CHANGE_RULE_MAX 8 +#define HOST_CMD_CAC_CFG_ARFS_MAX 99 +#define HOST_CMD_CAC_CFG_CHANGE_MAX 99 +#define HOST_CMD_CAC_CFG_CHANGE_STEP 5 + +enum host_cmd_cac_op { + HOST_CMD_CAC_OP_DISABLE = 0, + HOST_CMD_CAC_OP_ENABLE = 1, + HOST_CMD_CAC_OP_CFG_GET = 2, + HOST_CMD_CAC_OP_CFG_SET = 3, +}; + +struct host_cmd_cac_change_rule { + __le16 arfs; + __sle16 threshold_change; +} __packed; + +struct host_cmd_req_cac { + struct host_cmd_header hdr; + u8 opcode; + u8 rule_tot; + struct host_cmd_cac_change_rule rule[HOST_CMD_CAC_CFG_CHANGE_RULE_MAX]; +} __packed; + +struct host_cmd_resp_cac { + struct host_cmd_header hdr; + __le32 status; + u8 rule_tot; + struct host_cmd_cac_change_rule rule[HOST_CMD_CAC_CFG_CHANGE_RULE_MAX]; +} __packed; + +struct host_cmd_ocs_driver_req { + __le32 op_channel_freq_hz; + u8 op_channel_bw_mhz; + u8 pri_channel_bw_mhz; + u8 pri_1mhz_channel_index; +} __packed; + +struct host_cmd_ocs_driver_resp { + u8 running; +} __packed; + +struct host_cmd_req_ocs_driver { + struct host_cmd_header hdr; + __le32 subcmd; + union { + u8 opaque[0]; + struct host_cmd_ocs_driver_req config; + }; +} __packed; + +struct host_cmd_resp_ocs_driver { + struct host_cmd_header hdr; + __le32 status; + __le32 subcmd; + union { + u8 opaque[0]; + struct host_cmd_ocs_driver_resp ocs_status; + }; +} __packed; + +#define HOST_CMD_IFNAMSIZ 16 + +struct host_cmd_req_mbssid { + struct host_cmd_header hdr; + u8 max_bssid_indicator; + s8 transmitter_iface[HOST_CMD_IFNAMSIZ]; +} __packed; + +#define HOST_CMD_MESH_ID_LEN_MAX 32 +#define HOST_CMD_MESH_BEACONLESS_MODE_DISABLE 0 +#define HOST_CMD_MESH_BEACONLESS_MODE_ENABLE 1 +#define HOST_CMD_MESH_PEER_LINKS_MIN 0 +#define HOST_CMD_MESH_PEER_LINKS_MAX 10 + +struct host_cmd_req_set_mesh_config { + struct host_cmd_header hdr; + u8 mesh_id_len; + u8 mesh_id[HOST_CMD_MESH_ID_LEN_MAX]; + u8 mesh_beaconless_mode; + u8 max_plinks; +} __packed; + +struct host_cmd_req_set_mcba_conf { + struct host_cmd_header hdr; + u8 mbca_config; + u8 beacon_timing_report_interval; + u8 min_beacon_gap_ms; + __le16 mbss_start_scan_duration_ms; + __le16 tbtt_adj_interval_ms; +} __packed; + +struct host_cmd_req_dynamic_peering_config { + struct host_cmd_header hdr; + u8 enabled; + u8 rssi_margin; + __le32 blacklist_timeout; +} __packed; + +#define HOST_CMD_CFG_RAW_FLAG_ENABLE BIT(0) +#define HOST_CMD_CFG_RAW_FLAG_DELETE BIT(1) +#define HOST_CMD_CFG_RAW_FLAG_UPDATE BIT(2) +#define HOST_CMD_CFG_RAW_FLAG_DYNAMIC BIT(3) +#define HOST_CMD_RAW_RESERVED_AID_DCS 2008 +#define HOST_CMD_RAW_RESERVED_AID_DOWNLINK 2009 + +enum host_cmd_raw_tlv_tag { + HOST_CMD_RAW_TLV_TAG_SLOT_DEF = 0, + HOST_CMD_RAW_TLV_TAG_GROUP = 1, + HOST_CMD_RAW_TLV_TAG_START_TIME = 2, + HOST_CMD_RAW_TLV_TAG_PRAW = 3, + HOST_CMD_RAW_TLV_TAG_BCN_SPREAD = 4, + HOST_CMD_RAW_TLV_TAG_DYN_GLOBAL = 5, + HOST_CMD_RAW_TLV_TAG_DYN_CONFIG = 6, + HOST_CMD_RAW_TLV_TAG_LAST = 7, +}; + +struct host_cmd_raw_tlv_slot_def { + u8 tag; + __le32 raw_duration_us; + u8 num_slots; + u8 cross_slot_bleed; +} __packed; + +struct host_cmd_raw_tlv_group { + u8 tag; + __le16 aid_start; + __le16 aid_end; +} __packed; + +struct host_cmd_raw_tlv_start_time { + u8 tag; + __le32 start_time_us; +} __packed; + +struct host_cmd_raw_tlv_praw { + u8 tag; + u8 periodicity; + u8 validity; + u8 start_offset; + u8 refresh_on_expiry; +} __packed; + +struct host_cmd_raw_tlv_bcn_spread { + u8 tag; + __le16 max_spread; + __le16 nominal_sta_per_bcn; +} __packed; + +struct host_cmd_raw_tlv_dyn_global { + u8 tag; + __le16 num_configs; + __le16 num_bcn_indexes; +} __packed; + +struct host_cmd_raw_tlv_dyn_config { + u8 tag; + __le16 id; + __le16 index; + __le16 len; + u8 variable[]; +} __packed; + +union host_cmd_raw_tlvs { + u8 tag; + struct host_cmd_raw_tlv_slot_def slot_def; + struct host_cmd_raw_tlv_group group; + struct host_cmd_raw_tlv_start_time start_time; + struct host_cmd_raw_tlv_praw praw; + struct host_cmd_raw_tlv_bcn_spread bcn_spread; + struct host_cmd_raw_tlv_dyn_global dyn_global; + struct host_cmd_raw_tlv_dyn_config dyn_config; +} __packed; + +struct host_cmd_req_config_raw { + struct host_cmd_header hdr; + __le32 flags; + __le16 id; + u8 variable[]; +} __packed; + +struct host_cmd_req_config_bss_stats { + struct host_cmd_header hdr; + u8 enable; + __le32 monitor_window_ms; +} __packed; + +struct host_cmd_req_get_rssi { + struct host_cmd_header hdr; +} __packed; + +struct host_cmd_resp_get_rssi { + struct host_cmd_header hdr; + __le32 status; + __sle32 rssi0; + __sle32 rssi1; + __sle32 rssi2; + __sle32 rssi3; + __sle32 rssi4; + __sle32 rssi5; + __sle32 rssi6; + __sle32 rssi7; +} __packed; + +#define HOST_CMD_SET_IFS_MIN_USECS 160 + +struct host_cmd_req_set_ifs { + struct host_cmd_header hdr; + __le32 period_usecs; +} __packed; + +struct host_cmd_resp_set_ifs { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_fem_settings { + struct host_cmd_header hdr; + __le32 tx_antenna; + __le32 rx_antenna; + __le32 lna_enabled; + __le32 pa_enabled; +} __packed; + +struct host_cmd_resp_set_fem_settings { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_txop { + struct host_cmd_header hdr; + u8 min_packet_count; +} __packed; + +struct host_cmd_resp_set_txop { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_control_response { + struct host_cmd_header hdr; + u8 direction; + u8 control_response_1mhz_en; +} __packed; + +struct host_cmd_resp_set_control_response { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_periodic_cal { + struct host_cmd_header hdr; + __le32 periodic_cal_en_mask; +} __packed; + +struct host_cmd_resp_set_periodic_cal { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_bcn_rssi_threshold { + struct host_cmd_header hdr; + u8 threshold_db; +} __packed; + +struct host_cmd_resp_set_bcn_rssi_threshold { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_tx_pkt_lifetime_usecs { + struct host_cmd_header hdr; + __le32 lifetime_usecs; +} __packed; + +struct host_cmd_resp_set_tx_pkt_lifetime_usecs { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_physm_watchdog { + struct host_cmd_header hdr; + u8 physm_watchdog_en; +} __packed; + +struct host_cmd_req_tx_polar { + struct host_cmd_header hdr; + u8 enable; +} __packed; + +struct host_cmd_evt_sta_state { + struct host_cmd_header hdr; + u8 sta_addr[HOST_CMD_MAC_ADDR_LEN]; + __le16 aid; + __le16 state; +} __packed; + +struct host_cmd_evt_beacon_loss { + struct host_cmd_header hdr; + __le32 num_bcns; +} __packed; + +struct host_cmd_evt_sig_field_error { + struct host_cmd_header hdr; + __le64 start_timestamp; + __le64 end_timestamp; +} __packed; + +#define HOST_CMD_UMAC_TRAFFIC_CONTROL_SOURCE_TWT BIT(0) +#define HOST_CMD_UMAC_TRAFFIC_CONTROL_SOURCE_DUTY_CYCLE BIT(1) + +struct host_cmd_evt_umac_traffic_control { + struct host_cmd_header hdr; + u8 pause_data_traffic; + __le32 sources; +} __packed; + +struct host_cmd_evt_dhcp_lease_update { + struct host_cmd_header hdr; + __le32 my_ip; + __le32 netmask; + __le32 router; + __le32 dns; +} __packed; + +struct host_cmd_evt_ocs_done { + struct host_cmd_header hdr; + __le64 time_listen; + __le64 time_rx; + s8 noise; + u8 metric; +} __packed; + +struct host_cmd_evt_hw_scan_done { + struct host_cmd_header hdr; + u8 aborted; +} __packed; + +struct host_cmd_evt_channel_usage { + struct host_cmd_header hdr; + __le64 time_listen; + __le64 busy_time; + __le32 freq_hz; + u8 noise; + u8 bw_mhz; +} __packed; + +enum host_cmd_connection_loss_reason { + HOST_CMD_CONNECTION_LOSS_REASON_TSF_RESET = 0, +}; + +struct host_cmd_evt_connection_loss { + struct host_cmd_header hdr; + __le32 reason; +} __packed; + +struct host_cmd_evt_sched_scan_results { + struct host_cmd_header hdr; +} __packed; + +enum host_cmd_cqm_rssi_threshold_event { + HOST_CMD_CQM_RSSI_THRESHOLD_EVENT_LOW = 0, + HOST_CMD_CQM_RSSI_THRESHOLD_EVENT_HIGH = 1, +}; + +struct host_cmd_evt_cqm_rssi_notify { + struct host_cmd_header hdr; + __sle16 rssi; + __le16 event; +} __packed; + +struct host_cmd_evt_scan_done { + struct host_cmd_header hdr; + u8 aborted; +} __packed; + +enum host_cmd_scan_result_frame { + HOST_CMD_SCAN_RESULT_FRAME_UNKNOWN = 0, + HOST_CMD_SCAN_RESULT_FRAME_BEACON = 1, + HOST_CMD_SCAN_RESULT_FRAME_PROBE_RESPONSE = 2, +}; + +struct host_cmd_evt_scan_result { + struct host_cmd_header hdr; + __le32 channel_freq_hz; + u8 bw_mhz; + u8 frame_type; + __sle16 rssi; + u8 bssid[HOST_CMD_MAC_ADDR_LEN]; + __le16 beacon_interval; + __le16 capability_info; + __le64 tsf; + __le16 ies_len; + u8 ies[]; +} __packed; + +struct host_cmd_evt_connected { + struct host_cmd_header hdr; + u8 bssid[HOST_CMD_MAC_ADDR_LEN]; + __sle16 rssi; + u8 padding_0[8]; + __le16 assoc_resp_ies_len; + u8 assoc_resp_ies[]; +} __packed; + +struct host_cmd_evt_beacon_filter_match { + struct host_cmd_header hdr; + u8 padding_0[4]; + __le32 ies_len; + u8 ies[]; +} __packed; + +struct host_cmd_req_set_capabilities { + struct host_cmd_header hdr; + struct host_cmd_mm_capabilities capabilities; + u8 set_caps; + u8 morse_mmss_offset; +} __packed; + +struct host_cmd_resp_set_capabilities { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +struct host_cmd_req_set_transmission_rate { + struct host_cmd_header hdr; + __sle32 mcs_index; + __sle32 bandwidth_mhz; + __sle32 tx_80211ah_format; + s8 use_traveling_pilots; + s8 use_sgi; + u8 enabled; + s8 nss_idx; + s8 use_ldpc; + s8 use_stbc; +} __packed; + +struct host_cmd_resp_set_transmission_rate { + struct host_cmd_header hdr; + __le32 status; +} __packed; + +enum host_cmd_hart_id { + HOST_CMD_HART_ID_HOST = 0, + HOST_CMD_HART_ID_MAC = 1, + HOST_CMD_HART_ID_UPHY = 2, + HOST_CMD_HART_ID_LPHY = 3, +}; + +struct host_cmd_req_force_assert { + struct host_cmd_header hdr; + __le32 hart_id; +} __packed; + +#define HOST_CMD_HOST_BLOCK_TX_FRAMES BIT(0) +#define HOST_CMD_HOST_BLOCK_TX_CMD BIT(1) + +enum host_cmd_param_action { + HOST_CMD_PARAM_ACTION_SET = 0, + HOST_CMD_PARAM_ACTION_GET = 1, + HOST_CMD_PARAM_ACTION_LAST = 2, +}; + +enum host_cmd_slow_clock_mode { + HOST_CMD_SLOW_CLOCK_MODE_AUTO = 0, + HOST_CMD_SLOW_CLOCK_MODE_INTERNAL = 1, +}; + +enum host_cmd_param_id { + HOST_CMD_PARAM_ID_MAX_TRAFFIC_DELIVERY_WAIT_US = 0, + HOST_CMD_PARAM_ID_EXTRA_ACK_TIMEOUT_ADJUST_US = 1, + HOST_CMD_PARAM_ID_TX_STATUS_FLUSH_WATERMARK = 2, + HOST_CMD_PARAM_ID_TX_STATUS_FLUSH_MIN_AMPDU_SIZE = 3, + HOST_CMD_PARAM_ID_POWERSAVE_TYPE = 4, + HOST_CMD_PARAM_ID_SNOOZE_DURATION_ADJUST_US = 5, + HOST_CMD_PARAM_ID_TX_BLOCK = 6, + HOST_CMD_PARAM_ID_FORCED_SNOOZE_PERIOD_US = 7, + HOST_CMD_PARAM_ID_WAKE_ACTION_GPIO = 8, + HOST_CMD_PARAM_ID_WAKE_ACTION_GPIO_PULSE_MS = 9, + HOST_CMD_PARAM_ID_CONNECTION_MONITOR_GPIO = 10, + HOST_CMD_PARAM_ID_INPUT_TRIGGER_GPIO = 11, + HOST_CMD_PARAM_ID_INPUT_TRIGGER_MODE = 12, + HOST_CMD_PARAM_ID_COUNTRY = 13, + HOST_CMD_PARAM_ID_RTS_THRESHOLD = 14, + HOST_CMD_PARAM_ID_HOST_TX_BLOCK = 15, + HOST_CMD_PARAM_ID_MEM_RETENTION_CODE = 16, + HOST_CMD_PARAM_ID_NON_TIM_MODE = 17, + HOST_CMD_PARAM_ID_DYNAMIC_PS_TIMEOUT_MS = 18, + HOST_CMD_PARAM_ID_HOME_CHANNEL_DWELL_MS = 19, + HOST_CMD_PARAM_ID_SLOW_CLOCK_MODE = 20, + HOST_CMD_PARAM_ID_FRAGMENT_THRESHOLD = 21, + HOST_CMD_PARAM_ID_BEACON_LOSS_COUNT = 22, + HOST_CMD_PARAM_ID_AP_POWER_SAVE = 23, + HOST_CMD_PARAM_ID_BEACON_OFFLOAD = 24, + HOST_CMD_PARAM_ID_PROBE_RESP_OFFLOAD = 25, + HOST_CMD_PARAM_ID_BSS_MAX_AWAY_DURATION = 26, + HOST_CMD_PARAM_ID_DEFAULT_ACTIVE_SCAN_DWELL_MS = 27, + HOST_CMD_PARAM_ID_CTS_TO_SELF = 28, + HOST_CMD_PARAM_ID_CHANNELIZATION = 29, + HOST_CMD_PARAM_ID_LAST = 30, +}; + +struct host_cmd_req_get_set_generic_param { + struct host_cmd_header hdr; + __le32 param_id; + __le32 action; + __le32 flags; + __le32 value; +} __packed; + +struct host_cmd_resp_get_set_generic_param { + struct host_cmd_header hdr; + __le32 status; + __le32 flags; + __le32 value; +} __packed; + +#endif diff --git a/drivers/net/wireless/morsemicro/mm81x/core.c b/drivers/net/wireless/morsemicro/mm81x/core.c new file mode 100644 index 000000000000..5c51d69c4fb4 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/core.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include +#include "core.h" +#include "bus.h" +#include "hif.h" +#include "mac.h" + +static int mm81x_core_attach_regs(struct mm81x *mors) +{ + int ret = 0; + + mm81x_claim_bus(mors); + ret = mm81x_reg32_read(mors, MM8108_REG_CHIP_ID, &mors->chip_id); + mm81x_release_bus(mors); + + if (ret < 0) { + dev_err(mors->dev, "failed to read chip id %d", ret); + return ret; + } + + switch (mors->chip_id) { + case (CHIP_ID_MM8108): + mors->regs = &mm8108_regs; + mors->hif.ops = &mm81x_yaps_ops; + break; + default: + return -ENODEV; + } + + return ret; +} + +static void mm81x_core_init_mac_addr(struct mm81x *mors) +{ + int ret = mm81x_hw_otp_get_mac_addr(mors); + + if (ret || !is_valid_ether_addr(mors->macaddr)) + eth_random_addr(mors->macaddr); +} + +char *mm81x_core_get_fw_path(u32 chip_id, u32 fw_ver) +{ + const char *fw_base; + + switch (chip_id) { + case CHIP_ID_MM8108: + fw_base = MM8108_FW_BASE; + break; + default: + return NULL; + } + + return kasprintf(GFP_KERNEL, MM81X_FW_DIR "/v%u/%s" MM81X_FW_EXT, + fw_ver, fw_base); +} +EXPORT_SYMBOL_GPL(mm81x_core_get_fw_path); + +struct mm81x *mm81x_core_alloc(size_t priv_size, struct device *dev) +{ + return mm81x_mac_alloc(priv_size, dev); +} +EXPORT_SYMBOL_GPL(mm81x_core_alloc); + +int mm81x_core_init(struct mm81x *mors) +{ + int ret; + + set_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags); + set_bit(MM81X_STATE_RELOAD_FW_AFTER_START, &mors->state_flags); + + mm81x_core_init_mac_addr(mors); + + ret = mm81x_core_attach_regs(mors); + if (ret) + return ret; + + mors->chip_wq = create_singlethread_workqueue("chip_wq"); + if (!mors->chip_wq) + return -ENOMEM; + + mors->net_wq = create_singlethread_workqueue("net_wq"); + if (!mors->net_wq) { + ret = -ENOMEM; + goto err_chip_wq; + } + + ret = mm81x_hif_init(mors); + if (ret) + goto err_wqs; + + return 0; + +err_wqs: + flush_workqueue(mors->net_wq); + destroy_workqueue(mors->net_wq); + +err_chip_wq: + flush_workqueue(mors->chip_wq); + destroy_workqueue(mors->chip_wq); + + return ret; +} +EXPORT_SYMBOL_GPL(mm81x_core_init); + +int mm81x_core_register(struct mm81x *mors) +{ + return mm81x_mac_register(mors); +} +EXPORT_SYMBOL_GPL(mm81x_core_register); + +void mm81x_core_unregister(struct mm81x *mors) +{ + mm81x_mac_unregister(mors); +} +EXPORT_SYMBOL_GPL(mm81x_core_unregister); + +void mm81x_core_deinit(struct mm81x *mors) +{ + mm81x_hif_finish(mors); + flush_workqueue(mors->net_wq); + destroy_workqueue(mors->net_wq); + flush_workqueue(mors->chip_wq); + destroy_workqueue(mors->chip_wq); +} +EXPORT_SYMBOL_GPL(mm81x_core_deinit); + +void mm81x_core_free(struct mm81x *mors) +{ + mm81x_mac_free(mors); +} +EXPORT_SYMBOL_GPL(mm81x_core_free); + +MODULE_AUTHOR("Morse Micro"); +MODULE_DESCRIPTION("Driver support for Morse Micro MM81X core"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/wireless/morsemicro/mm81x/core.h b/drivers/net/wireless/morsemicro/mm81x/core.h new file mode 100644 index 000000000000..2fd4b4786e77 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/core.h @@ -0,0 +1,456 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_CORE_H_ +#define _MM81X_CORE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "yaps.h" +#include "yaps_hw.h" +#include "hw.h" +#include "fw.h" +#include "rc.h" + +#define MM81X_DRIVER_SEMVER_MAJOR 56 +#define MM81X_DRIVER_SEMVER_MINOR 3 +#define MM81X_DRIVER_SEMVER_PATCH 0 + +#define MM81X_SEMVER_GET_MAJOR(x) (((x) >> 22) & 0x3FF) +#define MM81X_SEMVER_GET_MINOR(x) (((x) >> 10) & 0xFFF) +#define MM81X_SEMVER_GET_PATCH(x) ((x) & 0x3FF) + +#define DRV_VERSION __stringify(MM81X_VERSION) + +#define MM8108_FW_BASE "mm8108" + +#define BCF_SIZE_MAX 48 + +#define KHZ100_TO_MHZ(x) ((x) / 10) +#define KHZ100_TO_KHZ(freq) ((freq) * 100) +#define KHZ100_TO_HZ(freq) ((freq) * 100000) + +#define QDBM_TO_MBM(gain) (((gain) * 100) >> 2) +#define MBM_TO_QDBM(gain) (((gain) << 2) / 100) +#define QDBM_TO_DBM(gain) ((gain) / 4) + +#define BPS_TO_KBPS(x) ((x) / 1000) + +#define NSS_IDX_TO_NSS(x) ((x) + 1) +#define NSS_TO_NSS_IDX(x) ((x) - 1) + +#define ROUND_BYTES_TO_WORD(_nbytes) \ + (((_nbytes) + 3) & ~((typeof(_nbytes))0x03)) + +struct mm81x_bus_ops; +struct mm81x_hif_ops; + +#define MM81X_CAPS_MAX_FW_VAL (128) + +/* Max number of interfaces */ +#define MM81X_MAX_IF (2) + +enum mm81x_caps_flags { + MM81X_CAPS_FW_START = 0, + MM81X_CAPS_2MHZ = MM81X_CAPS_FW_START, + MM81X_CAPS_4MHZ, + MM81X_CAPS_8MHZ, + MM81X_CAPS_16MHZ, + MM81X_CAPS_SGI, + MM81X_CAPS_S1G_LONG, + MM81X_CAPS_TRAVELING_PILOT_ONE_STREAM, + MM81X_CAPS_TRAVELING_PILOT_TWO_STREAM, + MM81X_CAPS_MU_BEAMFORMEE, + MM81X_CAPS_MU_BEAMFORMER, + MM81X_CAPS_RD_RESPONDER, + MM81X_CAPS_STA_TYPE_SENSOR, + MM81X_CAPS_STA_TYPE_NON_SENSOR, + MM81X_CAPS_GROUP_AID, + MM81X_CAPS_NON_TIM, + MM81X_CAPS_TIM_ADE, + MM81X_CAPS_BAT, + MM81X_CAPS_DYNAMIC_AID, + MM81X_CAPS_UPLINK_SYNC, + MM81X_CAPS_FLOW_CONTROL, + MM81X_CAPS_AMPDU, + MM81X_CAPS_AMSDU, + MM81X_CAPS_1MHZ_CONTROL_RESPONSE_PREAMBLE, + MM81X_CAPS_PAGE_SLICING, + MM81X_CAPS_RAW, + MM81X_CAPS_MCS8, + MM81X_CAPS_MCS9, + MM81X_CAPS_ASYMMETRIC_BA_SUPPORT, + MM81X_CAPS_DAC, + MM81X_CAPS_CAC, + MM81X_CAPS_TXOP_SHARING_IMPLICIT_ACK, + MM81X_CAPS_NDP_PSPOLL, + MM81X_CAPS_FRAGMENT_BA, + MM81X_CAPS_OBSS_MITIGATION, + MM81X_CAPS_TMP_PS_MODE_SWITCH, + MM81X_CAPS_SECTOR_TRAINING, + MM81X_CAPS_UNSOLICIT_DYNAMIC_AID, + MM81X_CAPS_NDP_BEAMFORMING_REPORT, + MM81X_CAPS_MCS_NEGOTIATION, + MM81X_CAPS_DUPLICATE_1MHZ, + MM81X_CAPS_TACK_AS_PSPOLL, + MM81X_CAPS_PV1, + MM81X_CAPS_TWT_RESPONDER, + MM81X_CAPS_TWT_REQUESTER, + MM81X_CAPS_BDT, + MM81X_CAPS_TWT_GROUPING, + MM81X_CAPS_LINK_ADAPTATION_WO_NDP_CMAC, + MM81X_CAPS_LONG_MPDU, + MM81X_CAPS_TXOP_SECTORIZATION, + MM81X_CAPS_GROUP_SECTORIZATION, + MM81X_CAPS_HTC_VHT, + MM81X_CAPS_HTC_VHT_MFB, + MM81X_CAPS_HTC_VHT_MRQ, + MM81X_CAPS_2SS, + MM81X_CAPS_3SS, + MM81X_CAPS_4SS, + MM81X_CAPS_SU_BEAMFORMEE, + MM81X_CAPS_SU_BEAMFORMER, + MM81X_CAPS_RX_STBC, + MM81X_CAPS_TX_STBC, + MM81X_CAPS_RX_LDPC, + MM81X_CAPS_HW_FRAGMENT, + + MM81X_CAPS_FW_END = MM81X_CAPS_MAX_FW_VAL, + MM81X_CAPS_LAST = MM81X_CAPS_FW_END, +}; + +struct mm81x_fw_caps { + u32 flags[FW_CAPABILITIES_FLAGS_WIDTH]; + u8 ampdu_mss; + u8 beamformee_sts_capability; + u8 number_sounding_dimensions; + u8 maximum_ampdu_length_exponent; + u8 mm81x_mmss_offset; +}; + +#define MM81X_FW_SUPP(MM81X_CAPS, CAPABILITY) \ + mm81x_caps_supported(MM81X_CAPS, MM81X_CAPS_##CAPABILITY) + +static inline bool mm81x_caps_supported(struct mm81x_fw_caps *caps, + enum mm81x_caps_flags flag) +{ + const unsigned long *flags_ptr = (unsigned long *)caps->flags; + + return test_bit(flag, flags_ptr); +} + +struct mm81x_ps { + u32 wakers; + bool enable; + bool suspended; + /* PS state lock */ + struct mutex lock; + struct delayed_work delayed_eval_work; +}; + +enum mm81x_page_aci { + MM81X_ACI_BE = 0, + MM81X_ACI_BK = 1, + MM81X_ACI_VI = 2, + MM81X_ACI_VO = 3, +}; + +enum mm81x_qos_tid_up_index { + MM81X_QOS_TID_UP_BK = 1, + MM81X_QOS_TID_UP_XX = 2, + MM81X_QOS_TID_UP_BE = 0, + MM81X_QOS_TID_UP_EE = 3, + MM81X_QOS_TID_UP_CL = 4, + MM81X_QOS_TID_UP_VI = 5, + MM81X_QOS_TID_UP_VO = 6, + MM81X_QOS_TID_UP_NC = 7, + + MM81X_QOS_TID_UP_LOWEST = MM81X_QOS_TID_UP_BK, + MM81X_QOS_TID_UP_HIGHEST = MM81X_QOS_TID_UP_NC +}; + +struct mm81x_sw_version { + u8 major; + u8 minor; + u8 patch; +}; + +struct mm81x_sta { + const struct ieee80211_vif *vif; + u8 addr[ETH_ALEN]; + enum ieee80211_sta_state state; + bool tid_tx[IEEE80211_NUM_TIDS]; + bool tid_start_tx[IEEE80211_NUM_TIDS]; + u8 tid_params[IEEE80211_NUM_TIDS]; + int max_bw_mhz; + struct mm81x_rc_sta rc; + struct mmrc_rate last_sta_tx_rate; + s16 avg_rssi; + bool tx_ps_filter_en; +}; + +struct mm81x_vif { + struct mm81x *mors; + u16 id; + + union { + struct { + bool is_assoc; + } sta; + struct { + u32 num_stas; + struct work_struct beacon_work; + } ap; + } u; +}; + +struct mm81x_stale_tx_status { + /* Stale Tx lock */ + spinlock_t lock; + struct timer_list timer; +}; + +struct mcast_filter { + u8 count; + /* + * Integer representation of the last four bytes of a multicast MAC + * address. The first two bytes are always 0x0100 (IPv4) or 0x3333 + * (IPv6). + */ + __le32 addr_list[]; +}; + +enum mm81x_hw_scan_op { + MM81X_HW_SCAN_OP_START, + MM81X_HW_SCAN_OP_STOP, +}; + +struct mm81x_hw_scan_params { + struct ieee80211_hw *hw; + + /* vif which initiated the scan */ + struct ieee80211_vif *vif; + bool has_directed_ssid; + u32 dwell_time_ms; + u32 dwell_on_home_ms; + enum mm81x_hw_scan_op operation; + bool store; + struct sk_buff *probe_req; + u16 num_chans; + u16 allocated_chans; + + struct { + struct ieee80211_channel *channel; + /* Index into @ref powers_qdbm for the power of this channel */ + u8 power_idx; + } *channels; + + s32 *powers_qdbm; + u8 n_powers; +}; + +enum mm81x_hw_scan_state { + HW_SCAN_STATE_IDLE, + HW_SCAN_STATE_RUNNING, + HW_SCAN_STATE_ABORTING, +}; + +struct mm81x_hw_scan { + enum mm81x_hw_scan_state state; + struct completion scan_done; + struct mm81x_hw_scan_params *params; + struct delayed_work timeout; + u32 home_dwell_ms; +}; + +enum mm81x_hif_event_flags { + MM81X_HIF_EVT_RX_PEND, + MM81X_HIF_EVT_PAGE_RETURN_PEND, + MM81X_HIF_EVT_TX_COMMAND_PEND, + MM81X_HIF_EVT_TX_BEACON_PEND, + MM81X_HIF_EVT_TX_MGMT_PEND, + MM81X_HIF_EVT_TX_DATA_PEND, + MM81X_HIF_EVT_TX_PACKET_FREED_UP_PEND, + MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND, + MM81X_HIF_EVT_DATA_TRAFFIC_RESUME_PEND, + MM81X_HIF_EVT_UPDATE_HW_CLOCK_REFERENCE, +}; + +enum mm81x_state_flags { + MM81X_STATE_CHIP_UNRESPONSIVE, + MM81X_STATE_DATA_QS_STOPPED, + MM81X_STATE_DATA_TX_STOPPED, + MM81X_STATE_REGDOM_SET_BY_USER, + MM81X_STATE_REGDOM_SET_BY_OTP, + MM81X_STATE_RELOAD_FW_AFTER_START, + MM81X_STATE_HOST_TO_CHIP_TX_BLOCKED, + MM81X_STATE_HOST_TO_CHIP_CMD_BLOCKED, +}; + +#define MM81X_COUNTRY_LEN (3) +#define INVALID_VIF_INDEX 0xFF + +struct mm81x { + u32 chip_id; + u32 host_table_ptr; + + /* Refer to @enum mm81x_bus_type */ + u32 bus_type; + u32 bcf_address; + + /* + * Parsed from the release tag, which should be in the format + * 'rel___'. If the tag is not in this format + * then corresponding version field will be 0. + */ + struct mm81x_sw_version sw_ver; + u8 macaddr[ETH_ALEN]; + u8 country[MM81X_COUNTRY_LEN]; + + /* Mask of type @enum host_table_firmware_flags */ + u32 fw_flags; + u32 fw_major; + struct mm81x_fw_caps fw_caps; + bool started; + bool chip_was_reset; + struct wiphy *wiphy; + struct mm81x_hw_scan hw_scan; + struct ieee80211_hw *hw; + struct device *dev; + + struct ieee80211_vif __rcu *vifs[MM81X_MAX_IF]; + + /* @mm81x_state_flags */ + unsigned long state_flags; + + u16 cmd_seq; + struct completion *cmd_comp; + /* Serialises commands */ + struct mutex cmd_lock; + + /* Serialises command completion */ + struct mutex cmd_wait; + + const struct mm81x_regs *regs; + + struct { + union { + struct mm81x_yaps yaps; + } u; + const struct mm81x_hif_ops *ops; + /* See @enum mm81x_hif_event_flags for values */ + unsigned long event_flags; + bool validate_skb_checksum; + } hif; + + struct workqueue_struct *chip_wq; + struct work_struct hif_work; + struct work_struct usb_irq_work; + struct mm81x_stale_tx_status stale_status; + bool config_ps; + struct mm81x_ps ps; + + /* Tx power in mBm received from the FW before association */ + s32 tx_power_mbm; + s32 tx_max_power_mbm; + + const struct mm81x_bus_ops *bus_ops; + struct mm81x_rc mrc; + int rts_threshold; + struct workqueue_struct *net_wq; + struct work_struct tx_stale_work; + wait_queue_head_t tx_empty_waitq; + + struct cfg80211_chan_def chandef; + struct mcast_filter *mcast_filter; + atomic_t num_bcn_vifs; + unsigned long beacon_irqs_enabled; + u8 drv_priv[] __aligned(sizeof(void *)); +}; + +/* Map from mac80211 queue to Morse ACI value for page metadata */ +static inline u8 map_mac80211q_2_mm81x_aci(u16 mac80211queue) +{ + switch (mac80211queue) { + case IEEE80211_AC_VO: + return MM81X_ACI_VO; + case IEEE80211_AC_VI: + return MM81X_ACI_VI; + case IEEE80211_AC_BK: + return MM81X_ACI_BK; + default: + return MM81X_ACI_BE; + } +} + +static inline enum mm81x_page_aci +dot11_tid_to_ac(enum mm81x_qos_tid_up_index tid) +{ + switch (tid) { + case MM81X_QOS_TID_UP_BK: + case MM81X_QOS_TID_UP_XX: + return MM81X_ACI_BK; + case MM81X_QOS_TID_UP_CL: + case MM81X_QOS_TID_UP_VI: + return MM81X_ACI_VI; + case MM81X_QOS_TID_UP_VO: + case MM81X_QOS_TID_UP_NC: + return MM81X_ACI_VO; + case MM81X_QOS_TID_UP_BE: + case MM81X_QOS_TID_UP_EE: + default: + return MM81X_ACI_BE; + } +} + +static inline bool mm81x_is_data_tx_allowed(struct mm81x *mors) +{ + return !test_bit(MM81X_STATE_DATA_TX_STOPPED, &mors->state_flags) && + !test_bit(MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND, + &mors->hif.event_flags); +} + +static inline struct ieee80211_vif * +mm81x_vif_to_ieee80211_vif(struct mm81x_vif *mors_vif) +{ + return container_of((void *)mors_vif, struct ieee80211_vif, drv_priv); +} + +static inline struct mm81x_vif * +ieee80211_vif_to_mors_vif(struct ieee80211_vif *vif) +{ + return (struct mm81x_vif *)vif->drv_priv; +} + +static inline struct mm81x *mm81x_vif_to_mors(struct mm81x_vif *mors_vif) +{ + return mors_vif->mors; +} + +static inline u32 mm81x_generate_cssid(const u8 *ssid, u8 len) +{ + return ~crc32(~0, ssid, len); +} + +int mm81x_beacon_init(struct mm81x_vif *mors_vif); +void mm81x_beacon_finish(struct mm81x_vif *mors_vif); +void mm81x_beacon_irq_handle(struct mm81x *mors, u32 status); +char *mm81x_core_get_fw_path(u32 chip_id, u32 fw_ver); +struct mm81x *mm81x_core_alloc(size_t priv_size, struct device *dev); +int mm81x_core_init(struct mm81x *mors); +int mm81x_core_register(struct mm81x *mors); +void mm81x_core_unregister(struct mm81x *mors); +void mm81x_core_deinit(struct mm81x *mors); +void mm81x_core_free(struct mm81x *mors); + +#endif /* !_MM81X_MM81X_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/fw.c b/drivers/net/wireless/morsemicro/mm81x/fw.c new file mode 100644 index 000000000000..d6d2ad086c32 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/fw.c @@ -0,0 +1,752 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fw.h" +#include "mac.h" +#include "bus.h" + +/* + * Maximum wait time (microseconds) for firmware to boot (for host table + * pointer to be available) + */ +#define HOST_TABLE_PTR_POLL_TIMEOUT_US 1200000 +#define HOST_TABLE_PTR_POLL_PERIOD_US 10000 + +/* Number of times to attempt flashing FW */ +#define FW_FLASH_ATTEMPT_COUNT 3 + +static int mm81x_fw_get_header(const u8 *data, Elf32_Ehdr *ehdr) +{ + const struct mm81x_elf32_ehdr *p = + (const struct mm81x_elf32_ehdr *)data; + + /* Magic check */ + if (p->e_ident[EI_MAG0] != ELFMAG0 || p->e_ident[EI_MAG1] != ELFMAG1 || + p->e_ident[EI_MAG2] != ELFMAG2 || p->e_ident[EI_MAG3] != ELFMAG3) + return -EINVAL; + + /* elf32 and little endian */ + if (p->e_ident[EI_DATA] != ELFDATA2LSB || + p->e_ident[EI_CLASS] != ELFCLASS32) + return -EINVAL; + + ehdr->e_phoff = le32_to_cpu(p->e_phoff); + ehdr->e_phentsize = le16_to_cpu(p->e_phentsize); + ehdr->e_phnum = le16_to_cpu(p->e_phnum); + ehdr->e_shoff = le32_to_cpu(p->e_shoff); + ehdr->e_shentsize = le16_to_cpu(p->e_shentsize); + ehdr->e_shnum = le16_to_cpu(p->e_shnum); + ehdr->e_shstrndx = le16_to_cpu(p->e_shstrndx); + ehdr->e_entry = le32_to_cpu(p->e_entry); + + return 0; +} + +static void mm81x_fw_parse_info(struct mm81x *mors, const u8 *data, int length) +{ + const struct mm81x_fw_info_tlv *tlv = + (const struct mm81x_fw_info_tlv *)data; + + while ((u8 *)tlv < (data + length)) { + switch (le16_to_cpu(tlv->type)) { + case MM81X_FW_INFO_TLV_BCF_ADDR: + mors->bcf_address = get_unaligned_le32(tlv->val); + break; + default: + break; + } + tlv = (const struct mm81x_fw_info_tlv *)((u8 *)tlv + + le16_to_cpu( + tlv->length) + + sizeof(*tlv)); + } +} + +static int mm81x_fw_get_section_header(const u8 *data, Elf32_Ehdr *ehdr, + Elf32_Shdr *shdr, int i) +{ + const struct mm81x_elf32_shdr *p = + (void *)(data + ehdr->e_shoff + (i * ehdr->e_shentsize)); + + shdr->sh_name = le32_to_cpu(p->sh_name); + shdr->sh_type = le32_to_cpu(p->sh_type); + shdr->sh_offset = le32_to_cpu(p->sh_offset); + shdr->sh_addr = le32_to_cpu(p->sh_addr); + shdr->sh_size = le32_to_cpu(p->sh_size); + shdr->sh_flags = le32_to_cpu(p->sh_flags); + + return 0; +} + +static int mm81x_fw_set_boot_addr(struct mm81x *mors, uint32_t addr) +{ + int status; + + dev_dbg(mors->dev, "Overwriting boot address to 0x%x", addr); + mm81x_claim_bus(mors); + status = mm81x_reg32_write(mors, MM81X_REG_BOOT_ADDR(mors), addr); + mm81x_release_bus(mors); + return status; +} + +static int mm81x_fw_load_fw(struct mm81x *mors, const struct firmware *fw) +{ + int i; + int ret = 0; + Elf32_Ehdr ehdr; + Elf32_Phdr phdr; + Elf32_Shdr shdr; + Elf32_Shdr sh_strtab; + const char *sh_strs; + + u8 *fw_buf = devm_kmalloc(mors->dev, ROUND_BYTES_TO_WORD(fw->size), + GFP_KERNEL); + + if (!fw_buf) + return -ENOMEM; + + if (mm81x_fw_get_header(fw->data, &ehdr)) { + dev_err(mors->dev, "Wrong file format"); + return -EINVAL; + } + + if (mm81x_fw_get_section_header(fw->data, &ehdr, &sh_strtab, + ehdr.e_shstrndx)) { + dev_err(mors->dev, "Invalid firmware. Missing string table"); + return -ENOENT; + } + + sh_strs = (const char *)fw->data + sh_strtab.sh_offset; + + for (i = 0; i < ehdr.e_phnum; i++) { + int status; + int address; + const struct mm81x_elf32_phdr *p = + (void *)(fw->data + ehdr.e_phoff + + i * ehdr.e_phentsize); + + phdr.p_type = le32_to_cpu(p->p_type); + phdr.p_offset = le32_to_cpu(p->p_offset); + phdr.p_paddr = le32_to_cpu(p->p_paddr); + phdr.p_filesz = le32_to_cpu(p->p_filesz); + phdr.p_memsz = le32_to_cpu(p->p_memsz); + + address = phdr.p_paddr; + + if (phdr.p_type != PT_LOAD || !phdr.p_memsz) + continue; + + if (phdr.p_filesz && phdr.p_offset && + (phdr.p_offset + phdr.p_filesz) < fw->size) { + u32 padded_size = ROUND_BYTES_TO_WORD(phdr.p_filesz); + + memcpy(fw_buf, fw->data + phdr.p_offset, padded_size); + /* Set padding to 0xff */ + memset(fw_buf + phdr.p_filesz, 0xff, + padded_size - phdr.p_filesz); + mm81x_claim_bus(mors); + status = mm81x_dm_write(mors, address, fw_buf, + padded_size); + mm81x_release_bus(mors); + if (status) { + ret = -EIO; + break; + } + } + } + + for (i = 0; i < ehdr.e_shnum; i++) { + if (mm81x_fw_get_section_header(fw->data, &ehdr, &shdr, i)) + continue; + + /* This is the firmware info. Parse it */ + if (!strncmp(sh_strs + shdr.sh_name, ".fw_info", + sizeof(".fw_info"))) + mm81x_fw_parse_info(mors, fw->data + shdr.sh_offset, + shdr.sh_size); + } + + if (ehdr.e_entry) + ret = mm81x_fw_set_boot_addr(mors, ehdr.e_entry); + + devm_kfree(mors->dev, fw_buf); + return ret; +} + +static int __mm81x_fw_load_bcf(struct mm81x *mors, unsigned int addr, + const void *src, size_t src_len, u8 *scratch, + size_t scratch_cap) +{ + size_t rounded = ROUND_BYTES_TO_WORD(src_len); + int st; + + if (rounded > scratch_cap) + return -EINVAL; + if (rounded > BCF_DATABASE_SIZE) + return -EFBIG; + + memcpy(scratch, src, src_len); + if (rounded > src_len) + memset(scratch + src_len, 0xff, rounded - src_len); + + mm81x_claim_bus(mors); + st = mm81x_dm_write(mors, addr, scratch, rounded); + mm81x_release_bus(mors); + + return st ? -EIO : 0; +} + +static int mm81x_fw_load_bcf(struct mm81x *mors, const struct firmware *bcf, + unsigned int bcf_address) +{ + int i, ret = 0; + size_t reg_prefix_len, cfg_len_rounded = 0, reg_len_rounded; + Elf32_Ehdr ehdr; + Elf32_Shdr shdr, sh_strtab; + const char *sh_strs, *reg_prefix = ".regdom_", *reg_src; + size_t reg_len; + u8 *bcf_buf; + + bcf_buf = devm_kmalloc(mors->dev, ROUND_BYTES_TO_WORD(bcf->size), + GFP_KERNEL); + if (!bcf_buf) + return -ENOMEM; + + if (mm81x_fw_get_header(bcf->data, &ehdr)) { + dev_err(mors->dev, "Wrong file format"); + ret = -EINVAL; + goto out_free; + } + + if (mm81x_fw_get_section_header(bcf->data, &ehdr, &sh_strtab, + ehdr.e_shstrndx)) { + dev_err(mors->dev, "Invalid BCF - missing string table"); + ret = -ENOENT; + goto out_free; + } + + sh_strs = (const char *)bcf->data + sh_strtab.sh_offset; + reg_prefix_len = strlen(reg_prefix); + + for (i = 0; i < ehdr.e_shnum; i++) { + if (mm81x_fw_get_section_header(bcf->data, &ehdr, &shdr, i)) + continue; + if (strcmp(sh_strs + shdr.sh_name, ".board_config")) + continue; + + cfg_len_rounded = ROUND_BYTES_TO_WORD(shdr.sh_size); + dev_dbg(mors->dev, + "Write BCF board_config - addr 0x%x size %zu", + bcf_address, cfg_len_rounded); + + ret = __mm81x_fw_load_bcf(mors, bcf_address, + bcf->data + shdr.sh_offset, + shdr.sh_size, bcf_buf, + ROUND_BYTES_TO_WORD(bcf->size)); + if (ret) + goto out_free; + + bcf_address += cfg_len_rounded; + break; + } + + ret = -EINVAL; + for (; i < ehdr.e_shnum; i++) { + if (mm81x_fw_get_section_header(bcf->data, &ehdr, &shdr, i)) + continue; + if (strncmp(sh_strs + shdr.sh_name, reg_prefix, reg_prefix_len)) + continue; + if (strncmp(sh_strs + shdr.sh_name + reg_prefix_len, + mors->country, 2)) + continue; + + reg_src = bcf->data + shdr.sh_offset; + reg_len = shdr.sh_size; + dev_dbg(mors->dev, "Write BCF %s - addr 0x%x size %zu", + sh_strs + shdr.sh_name, bcf_address, + ROUND_BYTES_TO_WORD(reg_len)); + ret = 0; + break; + } + + if (ret) + goto out_free; + + reg_len_rounded = ROUND_BYTES_TO_WORD(reg_len); + if ((cfg_len_rounded + reg_len_rounded) > BCF_DATABASE_SIZE) { + ret = -EFBIG; + goto out_free; + } + + ret = __mm81x_fw_load_bcf(mors, bcf_address, reg_src, reg_len, bcf_buf, + ROUND_BYTES_TO_WORD(bcf->size)); + +out_free: + devm_kfree(mors->dev, bcf_buf); + return ret; +} + +static void mm81x_fw_clear_aon(struct mm81x *mors) +{ + int idx; + u8 count = MM81X_REG_AON_COUNT(mors); + u32 address = MM81X_REG_AON_ADDR(mors); + + for (idx = 0; idx < count; idx++, address += 4) { + if (mors->bus_type == MM81X_BUS_TYPE_USB && idx == 0) + /* Keep the USB power domain enabled in AON. */ + mm81x_reg32_write(mors, address, + MM81X_REG_AON_USB_RESET(mors)); + else + /* clear AON */ + mm81x_reg32_write(mors, address, 0x0); + } + + mm81x_hw_toggle_aon_latch(mors); +} + +static void mm81x_fw_trigger(struct mm81x *mors) +{ + const unsigned int wait_after_msi_trigger_ms = 1; + + mm81x_claim_bus(mors); + /* + * If not coming from a full reset, some AON flags may be latched. + * Make sure to clear any hanging AON bits (can affect booting). + */ + mm81x_fw_clear_aon(mors); + + if (MM81X_REG_CLK_CTRL(mors)) + mm81x_reg32_write(mors, MM81X_REG_CLK_CTRL(mors), + MM81X_REG_CLK_CTRL_VALUE(mors)); + + mm81x_reg32_write(mors, MM81X_REG_MSI(mors), + MM81X_REG_MSI_HOST_INT(mors)); + mm81x_release_bus(mors); + + /* Give the chip a chance to boot */ + mdelay(wait_after_msi_trigger_ms); +} + +static int mm81x_fw_verify_magic(struct mm81x *mors) +{ + int ret = 0; + int magic = ~MM81X_REG_HOST_MAGIC_VALUE(mors); + + mm81x_claim_bus(mors); + mm81x_reg32_read(mors, + mors->host_table_ptr + + offsetof(struct host_table, magic_number), + &magic); + + if (magic != MM81X_REG_HOST_MAGIC_VALUE(mors)) { + dev_err(mors->dev, "FW magic mismatch 0x%08x:0x%08x", + MM81X_REG_HOST_MAGIC_VALUE(mors), magic); + ret = -EIO; + } + + mm81x_release_bus(mors); + return ret; +} + +static int mm81x_fw_get_flags(struct mm81x *mors) +{ + int ret = 0; + int fw_flags = 0; + + mm81x_claim_bus(mors); + ret = mm81x_reg32_read(mors, + mors->host_table_ptr + + offsetof(struct host_table, fw_flags), + &fw_flags); + mors->fw_flags = fw_flags; + mm81x_release_bus(mors); + + return ret; +} + +static int mm81x_fw_check_compatibility(struct mm81x *mors) +{ + int ret = 0; + u32 fw_version; + u32 major; + u32 minor; + u32 patch; + + mm81x_claim_bus(mors); + ret = mm81x_reg32_read(mors, + mors->host_table_ptr + + offsetof(struct host_table, + fw_version_number), + &fw_version); + mm81x_release_bus(mors); + + major = MM81X_SEMVER_GET_MAJOR(fw_version); + minor = MM81X_SEMVER_GET_MINOR(fw_version); + patch = MM81X_SEMVER_GET_PATCH(fw_version); + + /* Firmware on device must match the firmware file we requested */ + if (ret == 0 && major != mors->fw_major) { + dev_err(mors->dev, + "Incompatible FW version: (Requested) v%u, (Chip) %d.%d.%d\n", + mors->fw_major, major, minor, patch); + ret = -EPERM; + } else if (ret == 0 && major != HOST_CMD_SEMVER_MAJOR) { + dev_warn( + mors->dev, + "Running FW v%d.%d.%d, driver supports up to v%d, some features might not be supported", + major, minor, patch, HOST_CMD_SEMVER_MAJOR); + } else if (ret == 0 && minor != HOST_CMD_SEMVER_MINOR) { + dev_warn( + mors->dev, + "FW version mismatch, some features might not be supported: (Driver) %d.%d.%d, (Chip) %d.%d.%d", + HOST_CMD_SEMVER_MAJOR, HOST_CMD_SEMVER_MINOR, + HOST_CMD_SEMVER_PATCH, major, minor, patch); + } + + return ret; +} + +static int mm81x_fw_invalidate_host_ptr(struct mm81x *mors) +{ + int ret; + + mors->host_table_ptr = 0; + mm81x_claim_bus(mors); + ret = mm81x_reg32_write(mors, MM81X_REG_HOST_MANIFEST_PTR(mors), 0); + mm81x_release_bus(mors); + return ret; +} + +static int mm81x_fw_get_host_table_ptr(struct mm81x *mors) +{ + int ret, err; + + mm81x_claim_bus(mors); + ret = read_poll_timeout(mm81x_reg32_read, err, + err || mors->host_table_ptr, + HOST_TABLE_PTR_POLL_PERIOD_US, + HOST_TABLE_PTR_POLL_TIMEOUT_US, false, mors, + MM81X_REG_HOST_MANIFEST_PTR(mors), + &mors->host_table_ptr); + mm81x_release_bus(mors); + + return ret ? ret : err; +} + +static int mm81x_fw_read_ext_host_table(struct mm81x *mors, + struct ext_host_tbl **ext_host_table) +{ + int ret = 0; + u32 host_tbl_ptr = mors->host_table_ptr; + u32 ext_host_tbl_ptr; + u32 ext_host_tbl_ptr_addr = + host_tbl_ptr + offsetof(struct host_table, ext_host_tbl_addr); + u32 ext_host_tbl_len; + u32 ext_host_tbl_len_ptr_addr; + struct ext_host_tbl *host_tbl = NULL; + + mm81x_claim_bus(mors); + ret = mm81x_reg32_read(mors, ext_host_tbl_ptr_addr, &ext_host_tbl_ptr); + if (ret) + goto exit; + + if (!ext_host_tbl_ptr) { + ret = -ENXIO; + goto exit; + } + + ext_host_tbl_len_ptr_addr = + ext_host_tbl_ptr + + offsetof(struct ext_host_tbl, ext_host_tbl_length); + + ret = mm81x_reg32_read(mors, ext_host_tbl_len_ptr_addr, + &ext_host_tbl_len); + if (ret) + goto exit; + + ext_host_tbl_len = ROUND_BYTES_TO_WORD(ext_host_tbl_len); + if (WARN_ON(ext_host_tbl_len == 0 || ext_host_tbl_len > INT_MAX)) { + ret = -EINVAL; + goto exit; + } + + host_tbl = kmalloc(ext_host_tbl_len, GFP_KERNEL); + if (!host_tbl) { + ret = -ENOMEM; + goto exit; + } + + ret = mm81x_dm_read(mors, ext_host_tbl_ptr, (u8 *)host_tbl, + (int)ext_host_tbl_len); + if (ret) + goto exit; + + mm81x_release_bus(mors); + *ext_host_table = host_tbl; + return ret; + +exit: + mm81x_release_bus(mors); + kfree(host_tbl); + return ret; +} + +static void mm81x_fw_update_capabilities(struct mm81x *mors, + struct ext_host_tbl_s1g_caps *caps) +{ + int i; + + for (i = 0; i < FW_CAPABILITIES_FLAGS_WIDTH; i++) { + mors->fw_caps.flags[i] = le32_to_cpu(caps->flags[i]); + dev_dbg(mors->dev, "Firmware Manifest Flags%d: 0x%x", i, + le32_to_cpu(caps->flags[i])); + } + mors->fw_caps.ampdu_mss = caps->ampdu_mss; + mors->fw_caps.mm81x_mmss_offset = caps->mm81x_mmss_offset; + mors->fw_caps.beamformee_sts_capability = + caps->beamformee_sts_capability; + mors->fw_caps.maximum_ampdu_length_exponent = + caps->maximum_ampdu_length; + mors->fw_caps.number_sounding_dimensions = + caps->number_sounding_dimensions; + + dev_dbg(mors->dev, "\tAMPDU Minimum start spacing: %u", + caps->ampdu_mss); + dev_dbg(mors->dev, "\tMorse Minimum Start Spacing offset: %u", + caps->mm81x_mmss_offset); + dev_dbg(mors->dev, "\tBeamformee STS Capability: %u", + caps->beamformee_sts_capability); + dev_dbg(mors->dev, "\tNumber of Sounding Dimensions: %u", + caps->number_sounding_dimensions); + dev_dbg(mors->dev, "\tMaximum AMPDU Length Exponent: %u", + caps->maximum_ampdu_length); +} + +static void mm81x_fw_update_validate_skb_checksum( + struct mm81x *mors, + struct ext_host_tbl_insert_skb_checksum *validate_checksum) +{ + mors->hif.validate_skb_checksum = + validate_checksum->insert_and_validate_checksum; + dev_dbg(mors->dev, "Validate checksum inserted by fw %s", + str_enabled_disabled(mors->hif.validate_skb_checksum)); +} + +int mm81x_fw_parse_ext_host_tbl(struct mm81x *mors) +{ + int ret; + u8 *head; + u8 *end; + struct ext_host_tbl *ext_host_table = NULL; + + ret = mm81x_fw_read_ext_host_table(mors, &ext_host_table); + if (ret || !ext_host_table) + goto exit; + + /* Parse the TLVs */ + head = ext_host_table->ext_host_table_data_tlvs; + end = ((u8 *)ext_host_table) + + le32_to_cpu(ext_host_table->ext_host_tbl_length); + + while (head < end) { + struct ext_host_tbl_tlv_hdr *hdr = + (struct ext_host_tbl_tlv_hdr *)head; + + switch (le16_to_cpu(hdr->tag)) { + case MM81X_FW_HOST_TABLE_TAG_S1G_CAPABILITIES: + mm81x_fw_update_capabilities( + mors, (struct ext_host_tbl_s1g_caps *)hdr); + break; + + case MM81X_FW_HOST_TABLE_TAG_INSERT_SKB_CHECKSUM: + mm81x_fw_update_validate_skb_checksum( + mors, + (struct ext_host_tbl_insert_skb_checksum *)hdr); + break; + + case MM81X_FW_HOST_TABLE_TAG_YAPS_TABLE: + mm81x_yaps_hw_read_table( + mors, &((struct ext_host_tbl_yaps_table *)hdr) + ->yaps_table); + break; + default: + break; + } + + head += le16_to_cpu(hdr->length); + if (!hdr->length) + break; + } + + kfree(ext_host_table); + return ret; +exit: + dev_err(mors->dev, "failed to parse ext host table %d", ret); + return ret; +} + +static int __mm81x_fw_flash(struct mm81x *mors, const struct firmware *fw, + const struct firmware *bcf, bool reset) +{ + int ret; + + if (reset || !mors->chip_was_reset) { + ret = mm81x_hw_digital_reset(mors); + if (ret) + return ret; + } + + mm81x_hw_pre_firmware_ndr_hook(mors); + + ret = mm81x_fw_invalidate_host_ptr(mors); + if (ret) + return ret; + + ret = mm81x_fw_load_fw(mors, fw); + if (ret) + return ret; + + ret = mm81x_fw_load_bcf(mors, bcf, mors->bcf_address); + if (ret) + return ret; + + mm81x_fw_trigger(mors); + mm81x_hw_post_firmware_ndr_hook(mors); + + ret = mm81x_fw_get_host_table_ptr(mors); + if (ret) + return ret; + + ret = mm81x_fw_verify_magic(mors); + if (ret) + return ret; + + return mm81x_fw_check_compatibility(mors); +} + +static int mm81x_fw_flash(struct mm81x *mors, const struct firmware *fw, + const struct firmware *bcf, bool reset) +{ + int ret; + int retries = FW_FLASH_ATTEMPT_COUNT; + + while (retries--) { + ret = __mm81x_fw_flash(mors, fw, bcf, reset); + if (!ret) + return 0; + + mors->chip_was_reset = false; + } + + return ret; +} + +static uint32_t binary_crc(const struct firmware *fw) +{ + return ~crc32_le(~0, (unsigned char const *)fw->data, fw->size) & + 0xffffffff; +} + +static int mm81x_fw_request(struct mm81x *mors, const struct firmware **fw) +{ + int ret = -ENOENT; + int ver; + char *fw_path; + + for (ver = MM81X_FW_VER_MAX; ver >= MM81X_FW_VER_MIN; ver--) { + fw_path = mm81x_core_get_fw_path(mors->chip_id, ver); + if (!fw_path) + return -ENOMEM; + + ret = firmware_request_nowarn(fw, fw_path, mors->dev); + if (!ret) { + dev_info( + mors->dev, + "Loaded firmware from %s, size %zu, crc32 0x%08x\n", + fw_path, (*fw)->size, binary_crc(*fw)); + mors->fw_major = ver; + } + + kfree(fw_path); + if (!ret) + return 0; + } + + dev_err(mors->dev, "no firmware found (tried v%d down to v%d): %d\n", + MM81X_FW_VER_MAX, MM81X_FW_VER_MIN, ret); + return ret; +} + +int mm81x_fw_init(struct mm81x *mors, bool reset) +{ + int ret; + int board_id; + char *bcf_path = NULL; + const struct firmware *fw = NULL; + const struct firmware *bcf = NULL; + + board_id = mm81x_hw_otp_get_board_type(mors); + + if (!mm81x_hw_otp_valid_board_type(board_id)) { + dev_err(mors->dev, + "OTP not set, unable to determine BCF to use"); + ret = -EINVAL; + goto out; + } + + dev_dbg(mors->dev, "Using board type 0x%04x from OTP", board_id); + + ret = mm81x_fw_request(mors, &fw); + if (ret) + goto out; + + bcf_path = kasprintf(GFP_KERNEL, + MM81X_FW_DIR + "/v%u/bcf_boardtype_%04x" MM81X_FW_EXT, + mors->fw_major, board_id); + if (!bcf_path) { + ret = -ENOMEM; + goto out; + } + + ret = request_firmware(&bcf, bcf_path, mors->dev); + if (ret) { + if (ret == -ENOENT) + dev_err(mors->dev, "BCF %s not found\n", bcf_path); + goto out; + } + + dev_info(mors->dev, "Loaded BCF from %s, size %zu, crc32 0x%08x\n", + bcf_path, bcf->size, binary_crc(bcf)); + + ret = mm81x_fw_flash(mors, fw, bcf, reset); + if (ret) { + dev_err(mors->dev, "failed to flash firmware: %d", ret); + goto out; + } + + ret = mm81x_fw_get_flags(mors); + +out: + release_firmware(fw); + release_firmware(bcf); + kfree(bcf_path); + + if (ret) + dev_err(mors->dev, "failed to init firmware: %d", ret); + else + dev_dbg(mors->dev, "firmware initialised"); + + return ret; +} diff --git a/drivers/net/wireless/morsemicro/mm81x/fw.h b/drivers/net/wireless/morsemicro/mm81x/fw.h new file mode 100644 index 000000000000..b5f4c0e5f998 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/fw.h @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_FW_H_ +#define _MM81X_FW_H_ + +#include +#include +#include +#include "command_defs.h" +#include "yaps_hw.h" + +#define BCF_DATABASE_SIZE (1024) +#define MM81X_FW_DIR "morsemicro/mm81x" +#define MM81X_FW_EXT ".bin" + +#define MM81X_FW_VER_MAX HOST_CMD_SEMVER_MAJOR +#define MM81X_FW_VER_MIN 56 + +/* FW_CAPABILITIES_FLAGS_WIDTH = ceil(MM81X_CAPS_MAX_HW_LEN / 32) */ +#define FW_CAPABILITIES_FLAGS_WIDTH (4) + +struct mm81x_elf32_ehdr { + unsigned char e_ident[EI_NIDENT]; + __le16 e_type; + __le16 e_machine; + __le32 e_version; + __le32 e_entry; + __le32 e_phoff; + __le32 e_shoff; + __le32 e_flags; + __le16 e_ehsize; + __le16 e_phentsize; + __le16 e_phnum; + __le16 e_shentsize; + __le16 e_shnum; + __le16 e_shstrndx; +} __packed; + +struct mm81x_elf32_shdr { + __le32 sh_name; + __le32 sh_type; + __le32 sh_flags; + __le32 sh_addr; + __le32 sh_offset; + __le32 sh_size; + __le32 sh_link; + __le32 sh_info; + __le32 sh_addralign; + __le32 sh_entsize; +} __packed; + +struct mm81x_elf32_phdr { + __le32 p_type; + __le32 p_offset; + __le32 p_vaddr; + __le32 p_paddr; + __le32 p_filesz; + __le32 p_memsz; + __le32 p_flags; + __le32 p_align; +} __packed; + +enum mm81x_fw_info_tlv_type { + MM81X_FW_INFO_TLV_BCF_ADDR = 1, +}; + +struct mm81x_fw_info_tlv { + __le16 type; + __le16 length; + u8 val[]; +} __packed; + +enum mm81x_fw_ext_host_tbl_tag { + /* The S1G capability tag */ + MM81X_FW_HOST_TABLE_TAG_S1G_CAPABILITIES = 0, + MM81X_FW_HOST_TABLE_TAG_PAGER_BYPASS_TX_STATUS = 1, + MM81X_FW_HOST_TABLE_TAG_INSERT_SKB_CHECKSUM = 2, + MM81X_FW_HOST_TABLE_TAG_YAPS_TABLE = 3, + MM81X_FW_HOST_TABLE_TAG_PAGER_PKT_MEMORY = 4, + MM81X_FW_HOST_TABLE_TAG_PAGER_BYPASS_CMD_RESP = 5, +}; + +struct ext_host_tbl_tlv_hdr { + /* The tag used to identify which capability this represents */ + __le16 tag; + /* The length of the capability structure including this header */ + __le16 length; +} __packed; + +struct ext_host_tbl_s1g_caps { + struct ext_host_tbl_tlv_hdr header; + __le32 flags[FW_CAPABILITIES_FLAGS_WIDTH]; + /* + * The minimum A-MPDU start spacing required by firmware. + * Value | Description + * ------|------------ + * 0 | No restriction + * 1 | 1/4 us + * 2 | 1/2 us + * 3 | 1 us + * 4 | 2 us + * 5 | 4 us + * 6 | 8 us + * 7 | 16 us + */ + u8 ampdu_mss; + u8 beamformee_sts_capability; + u8 number_sounding_dimensions; + /* + * The maximum A-MPDU length. This is the exponent value such that + * (2^(13 + exponent) - 1) is the length + */ + u8 maximum_ampdu_length; + /* + * Offset to apply to the specification's MMSS table to signal further + * minimum MPDU start spacing. + */ + u8 mm81x_mmss_offset; +} __packed; + +struct ext_host_tbl_insert_skb_checksum { + struct ext_host_tbl_tlv_hdr header; + u8 insert_and_validate_checksum; +}; + +struct ext_host_tbl_yaps_table { + struct ext_host_tbl_tlv_hdr header; + struct mm81x_yaps_hw_table yaps_table; +} __packed; + +struct ext_host_tbl { + __le32 ext_host_tbl_length; + u8 dev_mac_addr[6]; + u8 ext_host_table_data_tlvs[]; +} __packed; + +int mm81x_fw_init(struct mm81x *mors, bool reset); +int mm81x_fw_parse_ext_host_tbl(struct mm81x *mors); + +#endif /* !_MM81X_FW_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/hif.h b/drivers/net/wireless/morsemicro/mm81x/hif.h new file mode 100644 index 000000000000..e3d23423049a --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/hif.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_HIF_H_ +#define _MM81X_HIF_H_ + +#include "core.h" + +struct mm81x_skbq; + +#define MM81X_HIF_BYPASS_TX_STATUS_IRQ_NUM (15) +#define MM81X_HIF_BYPASS_CMD_RESP_IRQ_NUM (29) +#define MM81X_HIF_IRQ_BYPASS_TX_STATUS_AVAILABLE \ + BIT(MM81X_HIF_BYPASS_TX_STATUS_IRQ_NUM) +#define MM81X_HIF_IRQ_BYPASS_CMD_RESP_AVAILABLE \ + BIT(MM81X_HIF_BYPASS_CMD_RESP_IRQ_NUM) + +/* Hardware IF interrupt mask. We may use any interrupts in this range */ +#define MM81X_HIF_IRQ_MASK_ALL \ + (GENMASK(13, 0) | MM81X_HIF_IRQ_BYPASS_TX_STATUS_AVAILABLE | \ + MM81X_HIF_IRQ_BYPASS_CMD_RESP_AVAILABLE) + +enum mm81x_hif_flags { + MM81X_HIF_FLAGS_DIR_TO_HOST = BIT(0), + MM81X_HIF_FLAGS_DIR_TO_CHIP = BIT(1), + MM81X_HIF_FLAGS_COMMAND = BIT(2), + MM81X_HIF_FLAGS_BEACON = BIT(3), + MM81X_HIF_FLAGS_DATA = BIT(4) +}; + +struct mm81x_hif_ops { + int (*init)(struct mm81x *mors); + void (*flush_tx_data)(struct mm81x *mors); + void (*flush_cmds)(struct mm81x *mors); + void (*finish)(struct mm81x *mors); + void (*skbq_get_tx_qs)(struct mm81x *mors, struct mm81x_skbq **qs, + int *num_qs); + struct mm81x_skbq *(*get_tx_cmd_queue)(struct mm81x *mors); + struct mm81x_skbq *(*get_tx_beacon_queue)(struct mm81x *mors); + struct mm81x_skbq *(*get_tx_mgmt_queue)(struct mm81x *mors); + struct mm81x_skbq *(*get_tx_data_queue)(struct mm81x *mors, int aci); + int (*handle_irq)(struct mm81x *mors, u32 status); + int (*get_tx_buffered_count)(struct mm81x *mors); + int (*get_tx_status_pending_count)(struct mm81x *mors); +}; + +static inline void mm81x_hif_clear_events(struct mm81x *mors) +{ + mors->hif.event_flags = 0; +} + +static inline int mm81x_hif_init(struct mm81x *mors) +{ + return mors->hif.ops->init(mors); +} + +static inline void mm81x_hif_flush_tx_data(struct mm81x *mors) +{ + mors->hif.ops->flush_tx_data(mors); +} + +static inline void mm81x_hif_flush_cmds(struct mm81x *mors) +{ + mors->hif.ops->flush_cmds(mors); +} + +static inline void mm81x_hif_finish(struct mm81x *mors) +{ + mors->hif.ops->finish(mors); +} + +static inline void mm81x_hif_skbq_get_tx_qs(struct mm81x *mors, + struct mm81x_skbq **qs, int *num_qs) +{ + mors->hif.ops->skbq_get_tx_qs(mors, qs, num_qs); +} + +static inline struct mm81x_skbq *mm81x_hif_get_tx_cmd_queue(struct mm81x *mors) +{ + return mors->hif.ops->get_tx_cmd_queue(mors); +} + +static inline struct mm81x_skbq * +mm81x_hif_get_tx_beacon_queue(struct mm81x *mors) +{ + return mors->hif.ops->get_tx_beacon_queue(mors); +} + +static inline struct mm81x_skbq *mm81x_hif_get_tx_mgmt_queue(struct mm81x *mors) +{ + return mors->hif.ops->get_tx_mgmt_queue(mors); +} + +static inline struct mm81x_skbq *mm81x_hif_get_tx_data_queue(struct mm81x *mors, + int aci) +{ + return mors->hif.ops->get_tx_data_queue(mors, aci); +} + +static inline int mm81x_hif_handle_irq(struct mm81x *mors, u32 status) +{ + return mors->hif.ops->handle_irq(mors, status); +} + +static inline int mm81x_hif_get_tx_buffered_count(struct mm81x *mors) +{ + return mors->hif.ops->get_tx_buffered_count(mors); +} + +static inline int mm81x_hif_get_tx_status_pending_count(struct mm81x *mors) +{ + return mors->hif.ops->get_tx_status_pending_count(mors); +} + +#endif /* _MM81X_HIF_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/hw.c b/drivers/net/wireless/morsemicro/mm81x/hw.c new file mode 100644 index 000000000000..9293f4094db1 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/hw.c @@ -0,0 +1,367 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include +#include +#include +#include +#include +#include +#include "hif.h" +#include "mac.h" +#include "bus.h" +#include "core.h" +#include "fw.h" +#include "yaps.h" + +#define MM8108_REG_HOST_MAGIC_VALUE 0xDEADBEEF +#define MM8108_REG_RESET_VALUE 0xDEAD + +#define MM8108_REG_SDIO_DEVICE_ADDR 0x0000207C + +#define MM8108_REG_SDIO_DEVICE_BURST_OFFSET 9 +#define MM8108_REG_TRGR_BASE 0x00003c00 +#define MM8108_REG_INT_BASE 0x00003c50 +#define MM8108_REG_MSI_ADDRESS 0x00004100 +#define MM8108_REG_MSI_VALUE 0x1 +#define MM8108_REG_MANIFEST_PTR_ADDRESS 0x00002d40 +#define MM8108_REG_APPS_BOOT_ADDR 0x00002084 +#define MM8108_REG_RESET 0x000020AC +#define MM8108_REG_AON_COUNT 2 + +#define MM8108_REG_AON_ADDR 0x00002114 +#define MM8108_REG_AON_LATCH_ADDR 0x00405020 +#define MM8108_REG_AON_LATCH_MASK 0x1 +#define MM8108_REG_AON_RESET_USB_VALUE 0x8 +#define MM8108_APPS_MAC_DMEM_ADDR_START 0x00100000 + +#define MM8108_REG_RC_CLK_POWER_OFF_ADDR 0x00405020 +#define MM8108_REG_RC_CLK_POWER_OFF_MASK 0x00000040 +#define MM8108_SLOW_RC_POWER_ON_DELAY_MS 2 + +#define MM8108_RESET_DELAY_TIME_MS 400 + +#define MM8108_REG_OTPCTRL_PLDO 0x00004014 +#define MM8108_REG_OTPCTRL_PENVDD2 0x00004010 +#define MM8108_REG_OTPCTRL_PDSTB 0x00004018 +#define MM8108_REG_OTPCTRL_PTM 0x0000401c +#define MM8108_REG_OTPCTRL_PCE 0x00004020 +#define MM8108_REG_OTPCTRL_PA 0x00004034 +#define MM8108_REG_OTPCTRL_PECCRDB 0x00004048 +#define MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START 0x0000400c +#define MM8108_REG_OTPCTRL_PDOUT 0x00004040 + +#define MM81X_OTP_MAC_ADDR_2_BANK_NUM 27 +#define MM81X_OTP_MAC_ADDR_1_BANK_NUM 26 +#define MM81X_OTP_MAC_ADDR_1_MASK GENMASK(31, 16) +#define MM81X_OTP_BOARD_TYPE_BANK_NUM 26 +#define MM81X_OTP_BOARD_TYPE_MASK GENMASK(15, 0) + +#define MM810X_BOARD_TYPE_MAX_VALUE (MM81X_OTP_BOARD_TYPE_MASK - 1) + +static void mm81x_hw_otp_power_up(struct mm81x *mors) +{ + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PENVDD2, 1); + udelay(2); + + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PLDO, 1); + usleep_range(10, 20); + + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PDSTB, 1); + udelay(3); +} + +static void mm81x_hw_otp_power_down(struct mm81x *mors) +{ + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PDSTB, 0); + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PLDO, 0); + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PENVDD2, 0); +} + +static void mm81x_hw_otp_read_enable(struct mm81x *mors) +{ + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PTM, 0); + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PCE, 1); + usleep_range(10, 20); +} + +static void mm81x_hw_otp_read_disable(struct mm81x *mors) +{ + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PCE, 0); + udelay(1); +} + +static int mm81x_hw_otp_read(struct mm81x *mors, u8 bank_num, u32 *buf, + u8 ignore_ecc) +{ + u32 auto_rd_start_tmp; + u32 auto_rd_start = 1; + int i; + + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PA, bank_num); + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PECCRDB, ignore_ecc); + + mm81x_reg32_read(mors, MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START, + &auto_rd_start_tmp); + auto_rd_start_tmp &= 0xfffffffe; + + mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START, + auto_rd_start | auto_rd_start_tmp); + + /* Attempt reading up to 5 times. */ + for (i = 0; i < 5 && auto_rd_start; i++) { + usleep_range(15, 20); + mm81x_reg32_read(mors, MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START, + &auto_rd_start_tmp); + auto_rd_start = auto_rd_start_tmp & 0x1; + } + + if (i == 5) + return -EIO; + + mm81x_reg32_read(mors, MM8108_REG_OTPCTRL_PDOUT, buf); + + return 0; +} + +int mm81x_hw_otp_get_board_type(struct mm81x *mors) +{ + int board_type = 0; + u32 otp_word = 0; + int ret; + + mm81x_claim_bus(mors); + mm81x_hw_otp_power_up(mors); + mm81x_hw_otp_read_enable(mors); + + ret = mm81x_hw_otp_read(mors, MM81X_OTP_BOARD_TYPE_BANK_NUM, &otp_word, + 1); + + mm81x_hw_otp_read_disable(mors); + mm81x_hw_otp_power_down(mors); + mm81x_release_bus(mors); + + if (ret) + return -EINVAL; + + board_type = otp_word & MM81X_OTP_BOARD_TYPE_MASK; + + return board_type; +} + +bool mm81x_hw_otp_valid_board_type(u32 board_type) +{ + return board_type > 0 && board_type < MM810X_BOARD_TYPE_MAX_VALUE; +} + +int mm81x_hw_otp_get_mac_addr(struct mm81x *mors) +{ + u32 mac1 = 0; + u32 mac2 = 0; + int ret = 0; + + mm81x_claim_bus(mors); + mm81x_hw_otp_power_up(mors); + mm81x_hw_otp_read_enable(mors); + + ret = mm81x_hw_otp_read(mors, MM81X_OTP_MAC_ADDR_1_BANK_NUM, &mac1, 1); + if (ret) + goto exit; + + ret = mm81x_hw_otp_read(mors, MM81X_OTP_MAC_ADDR_2_BANK_NUM, &mac2, 1); + if (ret) + goto exit; + + put_unaligned_le16((mac1 & MM81X_OTP_MAC_ADDR_1_MASK) >> 16, + &mors->macaddr[0]); + put_unaligned_le32(mac2, &mors->macaddr[2]); + +exit: + mm81x_hw_otp_read_disable(mors); + mm81x_hw_otp_power_down(mors); + mm81x_release_bus(mors); + + return ret; +} + +void mm81x_hw_irq_enable(struct mm81x *mors, u32 irq, bool enable) +{ + u32 irq_en, irq_en_addr = irq < 32 ? MM81X_REG_INT1_EN(mors) : + MM81X_REG_INT2_EN(mors); + u32 irq_clr_addr = irq < 32 ? MM81X_REG_INT1_CLR(mors) : + MM81X_REG_INT2_CLR(mors); + u32 mask = irq < 32 ? (1 << irq) : (1 << (irq - 32)); + + mm81x_claim_bus(mors); + mm81x_reg32_read(mors, irq_en_addr, &irq_en); + if (enable) + irq_en |= (mask); + else + irq_en &= ~(mask); + mm81x_reg32_write(mors, irq_clr_addr, mask); + mm81x_reg32_write(mors, irq_en_addr, irq_en); + mm81x_release_bus(mors); +} + +int mm81x_hw_irq_handle(struct mm81x *mors) +{ + u32 status1 = 0; + + mm81x_reg32_read(mors, MM81X_REG_INT1_STS(mors), &status1); + + if (status1 & MM81X_HIF_IRQ_MASK_ALL) + mm81x_hif_handle_irq(mors, status1); + + if (status1 & MM81X_INT_BEACON_VIF_MASK_ALL) + mm81x_mac_beacon_irq_handle(mors, status1); + + mm81x_reg32_write(mors, MM81X_REG_INT1_CLR(mors), status1); + + return status1 ? 1 : 0; +} +EXPORT_SYMBOL_GPL(mm81x_hw_irq_handle); + +void mm81x_hw_irq_clear(struct mm81x *mors) +{ + mm81x_claim_bus(mors); + mm81x_reg32_write(mors, MM81X_REG_INT1_CLR(mors), 0xFFFFFFFF); + mm81x_reg32_write(mors, MM81X_REG_INT2_CLR(mors), 0xFFFFFFFF); + mm81x_release_bus(mors); +} + +void mm81x_hw_toggle_aon_latch(struct mm81x *mors) +{ + u32 address = MM81X_REG_AON_LATCH_ADDR(mors); + u32 mask = MM81X_REG_AON_LATCH_MASK(mors); + u32 latch; + + mm81x_reg32_read(mors, address, &latch); + mm81x_reg32_write(mors, address, latch & ~(mask)); + mdelay(5); + mm81x_reg32_write(mors, address, latch | mask); + mdelay(5); + mm81x_reg32_write(mors, address, latch & ~(mask)); + mdelay(5); +} + +void mm81x_hw_enable_stop_notifications(struct mm81x *mors, bool enable) +{ + mm81x_hw_irq_enable(mors, MM81X_INT_HW_STOP_NOTIFICATION_NUM, enable); +} + +void mm81x_hw_enable_burst_mode(struct mm81x *mors, const u8 burst_mode) +{ + u32 reg32_value; + + mm81x_claim_bus(mors); + if (mm81x_reg32_read(mors, MM8108_REG_SDIO_DEVICE_ADDR, ®32_value)) + goto end; + + reg32_value &= ~(u32)(SDIO_WORD_BURST_MASK + << MM8108_REG_SDIO_DEVICE_BURST_OFFSET); + reg32_value |= (u32)(burst_mode << MM8108_REG_SDIO_DEVICE_BURST_OFFSET); + + dev_dbg(mors->dev, + "Setting Burst mode to %d Writing 0x%08X to the register", + burst_mode, reg32_value); + + if (mm81x_reg32_write(mors, MM8108_REG_SDIO_DEVICE_ADDR, reg32_value)) + goto end; + +end: + mm81x_release_bus(mors); +} +EXPORT_SYMBOL_GPL(mm81x_hw_enable_burst_mode); + +static int mm81x_hw_enable_internal_slow_clock(struct mm81x *mors) +{ + u32 rc_clock_reg_value; + int ret = 0; + + dev_dbg(mors->dev, "Enabling internal slow clock"); + + ret = mm81x_reg32_read(mors, MM8108_REG_RC_CLK_POWER_OFF_ADDR, + &rc_clock_reg_value); + if (ret) + goto exit; + + rc_clock_reg_value &= ~MM8108_REG_RC_CLK_POWER_OFF_MASK; + ret = mm81x_reg32_write(mors, MM8108_REG_RC_CLK_POWER_OFF_ADDR, + rc_clock_reg_value); + if (ret) + goto exit; + + mm81x_hw_toggle_aon_latch(mors); + + /* Wait for the clock to turn on and settle */ + mdelay(MM8108_SLOW_RC_POWER_ON_DELAY_MS); +exit: + return ret; +} + +int mm81x_hw_digital_reset(struct mm81x *mors) +{ + int ret = 0; + + mm81x_claim_bus(mors); + + /* This should be the first step in digital reset, do not reorder */ + ret = mm81x_hw_enable_internal_slow_clock(mors); + if (ret) + goto exit; + + if (mors->bus_type == MM81X_BUS_TYPE_USB) { + ret = mm81x_bus_digital_reset(mors); + goto usb_done; + } + + if (MM81X_REG_RESET(mors) != 0) + ret = mm81x_reg32_write(mors, MM81X_REG_RESET(mors), + MM81X_REG_RESET_VALUE(mors)); + +usb_done: + msleep(MM8108_RESET_DELAY_TIME_MS); +exit: + mm81x_release_bus(mors); + + if (!ret) + mors->chip_was_reset = true; + + return ret; +} + +void mm81x_hw_pre_firmware_ndr_hook(struct mm81x *mors) +{ + /* We need disable bursting for firmware download/init procedure */ + mm81x_bus_config_burst_mode(mors, false); +} + +void mm81x_hw_post_firmware_ndr_hook(struct mm81x *mors) +{ + /* We are safe here to re-enable bursting again, if supported */ + mm81x_bus_config_burst_mode(mors, true); +} + +const struct mm81x_regs mm8108_regs = { + .chip_id_address = MM8108_REG_CHIP_ID, + .irq_base_address = MM8108_REG_INT_BASE, + .trgr_base_address = MM8108_REG_TRGR_BASE, + .cpu_reset_address = MM8108_REG_RESET, + .cpu_reset_value = MM8108_REG_RESET_VALUE, + .manifest_ptr_address = MM8108_REG_MANIFEST_PTR_ADDRESS, + .msi_address = MM8108_REG_MSI_ADDRESS, + .msi_value = MM8108_REG_MSI_VALUE, + .magic_num_value = MM8108_REG_HOST_MAGIC_VALUE, + .early_clk_ctrl_value = 0, + .pager_base_address = MM8108_APPS_MAC_DMEM_ADDR_START, + .aon_latch = MM8108_REG_AON_LATCH_ADDR, + .aon_latch_mask = MM8108_REG_AON_LATCH_MASK, + .aon_reset_usb_value = MM8108_REG_AON_RESET_USB_VALUE, + .aon = MM8108_REG_AON_ADDR, + .aon_count = MM8108_REG_AON_COUNT, + .boot_address = MM8108_REG_APPS_BOOT_ADDR, +}; + +MODULE_FIRMWARE(MM81X_FW_DIR "/v" __stringify(MM81X_FW_VER_MAX) "/" + MM8108_FW_BASE MM81X_FW_EXT); diff --git a/drivers/net/wireless/morsemicro/mm81x/hw.h b/drivers/net/wireless/morsemicro/mm81x/hw.h new file mode 100644 index 000000000000..178db64861d0 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/hw.h @@ -0,0 +1,159 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_HW_H_ +#define _MM81X_HW_H_ + +#include +#include "core.h" +#include "command_defs.h" + +/* This should be at a fixed location for a family of chipset */ +#define MM8108_REG_CHIP_ID 0x00002d20 + +#define MM81X_SDIO_RW_ADDR_BOUNDARY_MASK ((u32)0xFFFF0000) + +#define MM81X_CONFIG_ACCESS_1BYTE 0 +#define MM81X_CONFIG_ACCESS_2BYTE 1 +#define MM81X_CONFIG_ACCESS_4BYTE 2 + +#define MM81X_REG_TRGR_BASE(mors) ((mors)->regs->trgr_base_address) +#define MM81X_REG_TRGR1_STS(mors) (MM81X_REG_TRGR_BASE(mors) + 0x00) +#define MM81X_REG_TRGR1_SET(mors) (MM81X_REG_TRGR_BASE(mors) + 0x04) +#define MM81X_REG_TRGR1_CLR(mors) (MM81X_REG_TRGR_BASE(mors) + 0x08) +#define MM81X_REG_TRGR1_EN(mors) (MM81X_REG_TRGR_BASE(mors) + 0x0C) +#define MM81X_REG_TRGR2_STS(mors) (MM81X_REG_TRGR_BASE(mors) + 0x10) +#define MM81X_REG_TRGR2_SET(mors) (MM81X_REG_TRGR_BASE(mors) + 0x14) +#define MM81X_REG_TRGR2_CLR(mors) (MM81X_REG_TRGR_BASE(mors) + 0x18) +#define MM81X_REG_TRGR2_EN(mors) (MM81X_REG_TRGR_BASE(mors) + 0x1C) + +#define MM81X_REG_INT_BASE(mors) ((mors)->regs->irq_base_address) +#define MM81X_REG_INT1_STS(mors) (MM81X_REG_INT_BASE(mors) + 0x00) +#define MM81X_REG_INT1_SET(mors) (MM81X_REG_INT_BASE(mors) + 0x04) +#define MM81X_REG_INT1_CLR(mors) (MM81X_REG_INT_BASE(mors) + 0x08) +#define MM81X_REG_INT1_EN(mors) (MM81X_REG_INT_BASE(mors) + 0x0C) +#define MM81X_REG_INT2_STS(mors) (MM81X_REG_INT_BASE(mors) + 0x10) +#define MM81X_REG_INT2_SET(mors) (MM81X_REG_INT_BASE(mors) + 0x14) +#define MM81X_REG_INT2_CLR(mors) (MM81X_REG_INT_BASE(mors) + 0x18) +#define MM81X_REG_INT2_EN(mors) (MM81X_REG_INT_BASE(mors) + 0x1C) + +#define MM81X_REG_CHIP_ID(mors) ((mors)->regs->chip_id_address) + +#define MM81X_REG_MSI(mors) ((mors)->regs->msi_address) +#define MM81X_REG_MSI_HOST_INT(mors) ((mors)->regs->msi_value) + +#define MM81X_REG_HOST_MAGIC_VALUE(mors) ((mors)->regs->magic_num_value) + +#define MM81X_REG_RESET(mors) ((mors)->regs->cpu_reset_address) +#define MM81X_REG_RESET_VALUE(mors) ((mors)->regs->cpu_reset_value) + +#define MM81X_REG_HOST_MANIFEST_PTR(mors) ((mors)->regs->manifest_ptr_address) + +#define MM81X_REG_EARLY_CLK_CTRL_VALUE(mors) \ + ((mors)->regs->early_clk_ctrl_value) + +#define MM81X_REG_CLK_CTRL(mors) ((mors)->regs->clk_ctrl_address) +#define MM81X_REG_CLK_CTRL_VALUE(mors) ((mors)->regs->clk_ctrl_value) + +#define MM81X_REG_BOOT_ADDR(mors) ((mors)->regs->boot_address) +#define MM81X_REG_BOOT_ADDR_VALUE(mors) ((mors)->regs->boot_value) + +#define MM81X_REG_AON_ADDR(mors) ((mors)->regs->aon) +#define MM81X_REG_AON_COUNT(mors) ((mors)->regs->aon_count) +#define MM81X_REG_AON_LATCH_ADDR(mors) ((mors)->regs->aon_latch) +#define MM81X_REG_AON_LATCH_MASK(mors) ((mors)->regs->aon_latch_mask) +#define MM81X_REG_AON_USB_RESET(mors) ((mors)->regs->aon_reset_usb_value) + +/* Bit 17 to 24 reserved for the beacon VIF 0 to 7 interrupts */ +#define MM81X_INT_BEACON_VIF_MASK_ALL (GENMASK(24, 17)) +#define MM81X_INT_BEACON_BASE_NUM (17) + +/* PV0 NDP probe interrupts (VIF 0 and 1). */ +#define MM81X_INT_NDP_PROBE_REQ_PV0_VIF_MASK_ALL (GENMASK(26, 25)) +#define MM81X_INT_NDP_PROBE_REQ_PV0_BASE_NUM (25) + +/* Bit 27 Chip to Host stop notify */ +#define MM81X_INT_HW_STOP_NOTIFICATION_NUM (27) +#define MM81X_INT_HW_STOP_NOTIFICATION BIT(MM81X_INT_HW_STOP_NOTIFICATION_NUM) + +/* Chip IDs */ +#define CHIP_ID_MM8108 0x809 + +/* + * Minimum time we must wait between attempting to reload the HW after a + * stop notification + */ +#define HW_RELOAD_AFTER_STOP_WINDOW 5 + +enum host_table_firmware_flags { + MM81X_FW_FLAGS_SUPPORT_S1G = BIT(0), + MM81X_FW_FLAGS_BUSY_ACTIVE_LOW = BIT(1), + MM81X_FW_FLAGS_REPORTS_TX_BEACON_COMPLETION = BIT(2), + MM81X_FW_FLAGS_SUPPORT_HW_SCAN = BIT(3), + MM81X_FW_FLAGS_SUPPORT_CHIP_HALT_IRQ = BIT(4), +}; + +struct host_table { + __le32 magic_number; + __le32 fw_version_number; + __le32 host_flags; + __le32 fw_flags; + __le32 memcmd_cmd_addr; + __le32 memcmd_resp_addr; + __le32 ext_host_tbl_addr; +} __packed; + +struct mm81x_regs { + u32 chip_id_address; + u32 irq_base_address; + u32 trgr_base_address; + u32 cpu_reset_address; + u32 cpu_reset_value; + u32 msi_address; + u32 msi_value; + u32 manifest_ptr_address; + u32 magic_num_value; + u32 clk_ctrl_address; + u32 clk_ctrl_value; + u32 early_clk_ctrl_value; + u32 boot_address; + u32 boot_value; + u32 pager_base_address; + u32 aon_latch; + u32 aon_latch_mask; + u32 aon_reset_usb_value; + u32 aon; + u8 aon_count; +}; + +int mm81x_hw_otp_get_board_type(struct mm81x *mors); +bool mm81x_hw_otp_valid_board_type(u32 board_type); +int mm81x_hw_otp_get_mac_addr(struct mm81x *mors); + +void mm81x_hw_irq_enable(struct mm81x *mors, u32 irq, bool enable); +int mm81x_hw_irq_handle(struct mm81x *mors); +void mm81x_hw_irq_clear(struct mm81x *mors); +void mm81x_hw_toggle_aon_latch(struct mm81x *mors); +void mm81x_hw_enable_burst_mode(struct mm81x *mors, const u8 burst_mode); +int mm81x_hw_digital_reset(struct mm81x *mors); +void mm81x_hw_pre_firmware_ndr_hook(struct mm81x *mors); +void mm81x_hw_post_firmware_ndr_hook(struct mm81x *mors); + +enum sdio_burst_mode { + SDIO_WORD_BURST_DISABLE = + 0, /* Intentionally duplicate to make it clear it's disabled */ + SDIO_WORD_BURST_SIZE_0 = 0, /* 000: no bursting (single 32bit word) */ + SDIO_WORD_BURST_SIZE_2 = 1, /* 001: bursts of 2 words */ + SDIO_WORD_BURST_SIZE_4 = 2, /* 010: bursts of 4 words */ + SDIO_WORD_BURST_SIZE_8 = 3, /* 011: bursts of 8 words */ + SDIO_WORD_BURST_SIZE_16 = 4, /* 100: bursts of 16 words */ + SDIO_WORD_BURST_MASK = 7, +}; + +extern const struct mm81x_regs mm8108_regs; + +void mm81x_hw_enable_stop_notifications(struct mm81x *mors, bool enable); + +#endif /* !_MM81X_HW_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/mac.c b/drivers/net/wireless/morsemicro/mm81x/mac.c new file mode 100644 index 000000000000..392dae5d7ce9 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/mac.c @@ -0,0 +1,2443 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include "core.h" +#include +#include +#include +#include +#include +#include +#include "hif.h" +#include "mac.h" +#include "bus.h" +#include "ps.h" +#include "rc.h" + +/* + * Arbitrary size limit for the filter command address list, to ensure that + * the command does not exceed page/MTU size. This will be far greater than + * the number of filters supported by the firmware. + */ +#define MCAST_FILTER_COUNT_MAX (1024 / sizeof(filter->addr_list[0])) + +/* Calculate average RSSI for Rx status */ +#define CALC_AVG_RSSI(_avg, _sample) ((((_avg) * 9 + (_sample)) / 10)) + +/* + * When automatically trying MCS0 before MCS10, this is how many + * MCS0 attempts to make + */ +#define MCS0_BEFORE_MCS10_COUNT (1) + +/* Maximum TX power (default) */ +#define MAX_TX_POWER_MBM (2200) + +/* + * Since S1G runs at 1/10th the clockrate of VHT, the worst-case + * transmission time is significantly longer then that of non-S1G + * PHYs. + */ +#define MM81X_FLUSH_TIMEOUT (16 * HZ) + +/* Default queue count */ +#define MM81X_HW_QUEUE_COUNT (4) + +/* Max rates per skb */ +#define MM81X_HW_MAX_RATES (4) + +/* Max reported rates */ +#define MM81X_HW_MAX_REPORT_RATES (4) + +/* Max rate attempts */ +#define MM81X_HW_MAX_RATE_TRIES (1) + +/* Max sk pacing shift */ +#define MM81X_HW_TX_SK_PACING_SHIFT (3) + +/* NSS/MCS map values */ +#define MM81X_NSS_MCS_BYTE_0 0xfe /* 1SS */ +#define MM81X_NSS_MCS_BYTE_1 0x00 +#define MM81X_NSS_MCS_BYTE_2 0xfc /* 1SS */ +#define MM81X_NSS_MCS_BYTE_3 0x01 +#define MM81X_NSS_MCS_BYTE_4 0x00 + +/* HW restart delay time before terminating hardware IF work items */ +#define MM81X_HW_RESTART_DELAY_MS 20 + +/* clang-format off */ + +/* mm81x chips do not support 16MHz */ +#define CHANS1G(channel, frequency, offset, chan_flags) \ +{ \ + .band = NL80211_BAND_S1GHZ, \ + .center_freq = (frequency), \ + .freq_offset = (offset), \ + .hw_value = (channel), \ + .flags = ((chan_flags) | IEEE80211_CHAN_NO_16MHZ), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + +static struct ieee80211_channel mors_s1ghz_channels[] = { + CHANS1G(1, 902, 500, IEEE80211_CHAN_S1G_NO_PRIMARY), + CHANS1G(3, 903, 500, 0), + CHANS1G(5, 904, 500, 0), + CHANS1G(7, 905, 500, 0), + CHANS1G(9, 906, 500, 0), + CHANS1G(11, 907, 500, 0), + CHANS1G(13, 908, 500, 0), + CHANS1G(15, 909, 500, 0), + CHANS1G(17, 910, 500, 0), + CHANS1G(19, 911, 500, 0), + CHANS1G(21, 912, 500, 0), + CHANS1G(23, 913, 500, 0), + CHANS1G(25, 914, 500, 0), + CHANS1G(27, 915, 500, 0), + CHANS1G(29, 916, 500, 0), + CHANS1G(31, 917, 500, 0), + CHANS1G(33, 918, 500, 0), + CHANS1G(35, 919, 500, 0), + CHANS1G(37, 920, 500, 0), + CHANS1G(39, 921, 500, 0), + CHANS1G(41, 922, 500, 0), + CHANS1G(43, 923, 500, 0), + CHANS1G(45, 924, 500, 0), + CHANS1G(47, 925, 500, 0), + CHANS1G(49, 926, 500, 0), + CHANS1G(51, 927, 500, IEEE80211_CHAN_S1G_NO_PRIMARY), +}; + +/* clang-format on */ + +static struct ieee80211_supported_band mors_band_s1ghz = { + .band = NL80211_BAND_S1GHZ, + .s1g_cap.s1g = true, + .channels = mors_s1ghz_channels, + .n_channels = ARRAY_SIZE(mors_s1ghz_channels), + .bitrates = NULL, + .n_bitrates = 0, + .s1g_cap.cap[4] = 0x80 /* STA type sensor only for AP & STA */ +}; + +static struct ieee80211_iface_limit mors_if_limits[] = { + { + .max = MM81X_MAX_IF, + .types = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP), + }, +}; + +static struct ieee80211_iface_combination mors_if_combs[] = { + { + .limits = mors_if_limits, + .n_limits = ARRAY_SIZE(mors_if_limits), + .max_interfaces = MM81X_MAX_IF, + .num_different_channels = 1, + }, +}; + +/* Convert from a time in time units (1024us) to us */ +#define MM81X_TU_TO_US(x) ((x) * 1024UL) + +/* Convert from a time in time units (1024us) to ms */ +#define MM81X_TU_TO_MS(x) (MM81X_TU_TO_US(x) / 1000UL) + +/* Default time to dwell on a scan channel */ +#define MM81X_HWSCAN_DEFAULT_DWELL_TIME_MS (30) + +/* Default time to dwell on a scan channel for passive scan */ +#define MM81X_HWSCAN_DEFAULT_PASSIVE_DWELL_TIME_MS (110) + +/* Default time to dwell on home channel, in between scan channels */ +#define MM81X_HWSCAN_DEFAULT_DWELL_ON_HOME_MS (200) + +/* Typical time it takes to send the probe */ +#define MM81X_HWSCAN_PROBE_DELAY_MS (30) + +/* A margin to account for event/command processing */ +#define MM81X_HWSCAN_TIMEOUT_OVERHEAD_MS (2000) + +/* Scan channel frequency mask */ +#define HW_SCAN_CH_LIST_FREQ_KHZ GENMASK(19, 0) + +/* + * Scan channel bandwidth mask. + * Encoded as: 0 = 1MHz, 1 = 2MHz, 2 = 4MHz, 3 = 8MHz + */ +#define HW_SCAN_CH_LIST_OP_BW GENMASK(21, 20) + +/* + * Scan channel primary channel width. + * Encoded as: 0 = 1MHz, 1 = 2MHz + */ +#define HW_SCAN_CH_LIST_PRIM_CH_WIDTH BIT(22) + +/* Index into power_list for tx power of channel */ +#define HW_SCAN_CH_LIST_PWR_LIST_IDX GENMASK(31, 26) + +struct hw_scan_tlv_hdr { + __le16 tag; + __le16 len; +} __packed; + +struct hw_scan_tlv_channel_list { + struct hw_scan_tlv_hdr hdr; + __le32 channels[]; +} __packed; + +struct hw_scan_tlv_power_list { + struct hw_scan_tlv_hdr hdr; + s32 tx_power_qdbm[]; +} __packed; + +struct hw_scan_tlv_probe_req { + struct hw_scan_tlv_hdr hdr; + /* Probe request frame template (including SSIDs) */ + u8 buf[]; +} __packed; + +struct hw_scan_tlv_dwell_on_home { + struct hw_scan_tlv_hdr hdr; + /* Time to dwell on home between scan channels */ + __le32 home_dwell_time_ms; +} __packed; + +#define DOT11AH_BA_MAX_MPDU_PER_AMPDU (32) + +/* wiphy scan params */ +#define MM81X_MAX_SCAN_IE_LEN 512 +#define MM81X_MAX_SCAN_SSIDS 1 +#define MM81X_MAX_REMAIN_ON_CHAN_DURATION 10000 + +static bool mm81x_reg_h_cc_equal(const char *cc1, const char *cc2) +{ + return (cc1[0] == cc2[0]) && (cc1[1] == cc2[1]); +} + +static bool mm81x_tx_h_pkt_over_rts_threshold(struct mm81x *mors, + struct ieee80211_tx_info *info, + struct sk_buff *skb) +{ + u8 ccmp_len; + + if (!info->control.hw_key) + return ((skb->len + FCS_LEN) > mors->rts_threshold); + + if (info->control.hw_key->keylen == 32) + ccmp_len = + IEEE80211_CCMP_256_HDR_LEN + IEEE80211_CCMP_256_MIC_LEN; + else if (info->control.hw_key->keylen == 16) + ccmp_len = IEEE80211_CCMP_HDR_LEN + IEEE80211_CCMP_MIC_LEN; + else + ccmp_len = 0; + + return ((skb->len + FCS_LEN + ccmp_len) > mors->rts_threshold); +} + +static bool mm81x_tx_h_ps_filtered_for_sta(struct mm81x *mors, + struct sk_buff *skb, + struct ieee80211_sta *sta) +{ + struct mm81x_sta *mors_sta; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + if (!sta) + return false; + + mors_sta = (struct mm81x_sta *)sta->drv_priv; + + if (!mors_sta->tx_ps_filter_en) + return false; + + dev_dbg(mors->dev, "Frame for sta[%pM] PS filtered", mors_sta->addr); + + info->flags |= IEEE80211_TX_STAT_TX_FILTERED; + info->flags &= ~IEEE80211_TX_CTL_AMPDU; + + ieee80211_tx_status_skb(mors->hw, skb); + return true; +} + +static void mm81x_mac_check_fw_disabled_chans(struct ieee80211_hw *hw) +{ + int ret = 0; + u32 i; + struct mm81x *mors = hw->priv; + struct host_cmd_resp_get_disabled_channels *resp; + u32 resp_len = sizeof(struct host_cmd_disabled_channel_entry) * + ARRAY_SIZE(mors_s1ghz_channels) + + sizeof(*resp); + + resp = kzalloc(resp_len, GFP_KERNEL); + if (!resp) { + ret = -ENOMEM; + goto out; + } + + ret = mm81x_cmd_get_disabled_channels(mors, resp, resp_len); + if (ret) + goto out; + + for (i = 0; i < ARRAY_SIZE(mors_s1ghz_channels); i++) { + struct ieee80211_channel *ch = &mors_s1ghz_channels[i]; + + if (ch->flags & IEEE80211_CHAN_DISABLED) + continue; + + ch->flags &= ~IEEE80211_CHAN_S1G_NO_PRIMARY; + } + + for (i = 0; i < le32_to_cpu(resp->n_channels); i++) { + struct ieee80211_channel *ch; + struct host_cmd_disabled_channel_entry *entry = + &resp->channels[i]; + + if (entry->bw_mhz != 1) + continue; + + ch = ieee80211_get_channel_khz( + hw->wiphy, + KHZ100_TO_KHZ(le16_to_cpu(entry->freq_100khz))); + if (!ch) + continue; + + ch->flags |= IEEE80211_CHAN_S1G_NO_PRIMARY; + dev_dbg(mors->dev, "set NO_PRIMARY on %u KHz", + ieee80211_channel_to_khz(ch)); + } + +out: + if (ret) + dev_err(mors->dev, "failed to set disabled primary channels"); + + kfree(resp); +} + +static int mm81x_mac_ops_start(struct ieee80211_hw *hw) +{ + struct mm81x *mors = hw->priv; + + mors->started = true; + return 0; +} + +static int mm81x_tx_h_get_max_bw(struct mm81x *mors) +{ + return MM81X_FW_SUPP(&mors->fw_caps, 8MHZ) ? 8 : + MM81X_FW_SUPP(&mors->fw_caps, 4MHZ) ? 4 : + MM81X_FW_SUPP(&mors->fw_caps, 2MHZ) ? 2 : + 1; +} + +static void mm81x_mac_caps_init(struct mm81x *mors) +{ + struct mm81x_fw_caps *fw_caps = &mors->fw_caps; + struct ieee80211_sta_s1g_cap *s1g = &mors_band_s1ghz.s1g_cap; + +#define __FW_CAP_N(_n, _cap, _bit) \ + do { \ + if (MM81X_FW_SUPP(fw_caps, _cap)) \ + s1g->cap[_n] |= (_bit); \ + } while (0) + +#define FW_CAP0(_cap, _bit) __FW_CAP_N(0, _cap, _bit) +#define FW_CAP3(_cap, _bit) __FW_CAP_N(3, _cap, _bit) +#define FW_CAP5(_cap, _bit) __FW_CAP_N(5, _cap, _bit) +#define FW_CAP6(_cap, _bit) __FW_CAP_N(6, _cap, _bit) +#define FW_CAP7(_cap, _bit) __FW_CAP_N(7, _cap, _bit) +#define FW_CAP8(_cap, _bit) __FW_CAP_N(8, _cap, _bit) +#define FW_CAP9(_cap, _bit) __FW_CAP_N(9, _cap, _bit) + + FW_CAP0(S1G_LONG, S1G_CAP0_S1G_LONG); + + s1g->cap[0] |= S1G_CAP0_SGI_1MHZ; + if (MM81X_FW_SUPP(fw_caps, SGI)) { + FW_CAP0(2MHZ, S1G_CAP0_SGI_2MHZ); + FW_CAP0(4MHZ, S1G_CAP0_SGI_4MHZ); + FW_CAP0(8MHZ, S1G_CAP0_SGI_8MHZ); + } + + if (MM81X_FW_SUPP(fw_caps, 8MHZ)) + s1g->cap[0] |= S1G_SUPP_CH_WIDTH_8; + else if (MM81X_FW_SUPP(fw_caps, 4MHZ)) + s1g->cap[0] |= S1G_SUPP_CH_WIDTH_4; + else if (MM81X_FW_SUPP(fw_caps, 2MHZ)) + s1g->cap[0] |= S1G_SUPP_CH_WIDTH_2; + + FW_CAP3(RD_RESPONDER, S1G_CAP3_RD_RESPONDER); + FW_CAP3(LONG_MPDU, S1G_CAP3_MAX_MPDU_LEN); + + FW_CAP5(AMSDU, S1G_CAP5_AMSDU); + FW_CAP5(AMPDU, S1G_CAP5_AMPDU); + FW_CAP5(ASYMMETRIC_BA_SUPPORT, S1G_CAP5_ASYMMETRIC_BA); + FW_CAP5(FLOW_CONTROL, S1G_CAP5_FLOW_CONTROL); + + FW_CAP6(OBSS_MITIGATION, S1G_CAP6_OBSS_MITIGATION); + FW_CAP6(FRAGMENT_BA, S1G_CAP6_FRAGMENT_BA); + FW_CAP6(NDP_PSPOLL, S1G_CAP6_NDP_PS_POLL); + FW_CAP6(TXOP_SHARING_IMPLICIT_ACK, S1G_CAP6_TXOP_SHARING_IMP_ACK); + FW_CAP6(HTC_VHT_MFB, S1G_CAP6_VHT_LINK_ADAPT); + + FW_CAP7(TACK_AS_PSPOLL, S1G_CAP7_TACK_AS_PS_POLL); + FW_CAP7(DUPLICATE_1MHZ, S1G_CAP7_DUP_1MHZ); + FW_CAP7(MCS_NEGOTIATION, S1G_CAP7_MCS_NEGOTIATION); + FW_CAP7(1MHZ_CONTROL_RESPONSE_PREAMBLE, + S1G_CAP7_1MHZ_CTL_RESPONSE_PREAMBLE); + FW_CAP7(SECTOR_TRAINING, S1G_CAP7_SECTOR_TRAINING_OPERATION); + FW_CAP7(TMP_PS_MODE_SWITCH, S1G_CAP7_TEMP_PS_MODE_SWITCH); + + FW_CAP8(BDT, S1G_CAP8_BDT); + + FW_CAP9(LINK_ADAPTATION_WO_NDP_CMAC, + S1G_CAP9_LINK_ADAPT_PER_CONTROL_RESPONSE); + + /* 1SS MCS 9 for Rx / Tx map */ + s1g->nss_mcs[0] = MM81X_NSS_MCS_BYTE_0; + s1g->nss_mcs[1] = MM81X_NSS_MCS_BYTE_1; + s1g->nss_mcs[2] = MM81X_NSS_MCS_BYTE_2; + s1g->nss_mcs[3] = MM81X_NSS_MCS_BYTE_3; + s1g->nss_mcs[4] = MM81X_NSS_MCS_BYTE_4; + +#undef FW_CAP0 +#undef FW_CAP3 +#undef FW_CAP5 +#undef FW_CAP6 +#undef FW_CAP7 +#undef FW_CAP8 +#undef FW_CAP9 +#undef __FW_CAP_N +} + +static void mm81x_mac_beacon_irq_enable(struct mm81x_vif *mors_vif, bool enable) +{ + struct mm81x *mors = mm81x_vif_to_mors(mors_vif); + u8 beacon_irq_num = MM81X_INT_BEACON_BASE_NUM + mors_vif->id; + + enable ? set_bit(beacon_irq_num, &mors->beacon_irqs_enabled) : + clear_bit(beacon_irq_num, &mors->beacon_irqs_enabled); + + mm81x_hw_irq_enable(mors, beacon_irq_num, enable); +} + +static void mm81x_beacon_h_fill_tx_info(struct mm81x *mors, + struct mm81x_skb_tx_info *tx_info, + struct mm81x_vif *mors_vif, + int tx_bw_mhz) +{ + enum dot11_bandwidth bw_idx = + mm81x_ratecode_bw_mhz_to_bw_index(tx_bw_mhz); + enum mm81x_rate_preamble pream = MM81X_RATE_PREAMBLE_S1G_SHORT; + + tx_info->flags |= + cpu_to_le32(MM81X_TX_CONF_FLAGS_VIF_ID_SET(mors_vif->id)); + + if (bw_idx == DOT11_BANDWIDTH_1MHZ) + pream = MM81X_RATE_PREAMBLE_S1G_1M; + + tx_info->rates[0].count = 1; + tx_info->rates[1].count = 0; + tx_info->rates[0].mm81x_ratecode = + mm81x_ratecode_init(bw_idx, 0, 0, pream); + + if (mors->fw_flags & MM81X_FW_FLAGS_REPORTS_TX_BEACON_COMPLETION) + tx_info->flags |= + cpu_to_le32(MM81X_TX_CONF_FLAGS_IMMEDIATE_REPORT); +} + +static void mm81x_mac_beacon_work(struct work_struct *work) +{ + struct mm81x_vif *mors_vif = + from_work(mors_vif, work, u.ap.beacon_work); + struct mm81x *mors = mm81x_vif_to_mors(mors_vif); + struct mm81x_skbq *mq; + struct sk_buff *beacon; + struct ieee80211_vif *vif = mm81x_vif_to_ieee80211_vif(mors_vif); + struct mm81x_skb_tx_info tx_info = { 0 }; + int num_bcn_vifs = atomic_read(&mors->num_bcn_vifs); + + mq = mm81x_hif_get_tx_beacon_queue(mors); + if (!mq) { + dev_err(mors->dev, "no matching beacon Q found"); + return; + } + + if (mm81x_skbq_count(mq) >= num_bcn_vifs) { + dev_err(mors->dev, + "previous beacon not consumed, dropping req [id:%d]", + mors_vif->id); + return; + } + + beacon = ieee80211_beacon_get(mors->hw, vif, false); + if (!beacon) + return; + + mm81x_beacon_h_fill_tx_info(mors, &tx_info, mors_vif, + cfg80211_chandef_s1g_pri_width(&mors->chandef)); + mm81x_skbq_skb_tx(mq, &beacon, &tx_info, MM81X_SKB_CHAN_BEACON); +} + +void mm81x_mac_beacon_irq_handle(struct mm81x *mors, u32 status) +{ + int vif_id; + unsigned long masked_status = (status & mors->beacon_irqs_enabled) >> + MM81X_INT_BEACON_BASE_NUM; + + guard(rcu)(); + for_each_set_bit(vif_id, &masked_status, MM81X_MAX_IF) { + struct mm81x_vif *mors_vif; + struct ieee80211_vif *vif; + + vif = mm81x_rcu_dereference_vif_id(mors, vif_id, true); + if (vif) { + mors_vif = ieee80211_vif_to_mors_vif(vif); + queue_work(system_bh_wq, &mors_vif->u.ap.beacon_work); + } + } +} + +static void mm81x_mac_beacon_init(struct mm81x_vif *mors_vif) +{ + struct mm81x *mors = mm81x_vif_to_mors(mors_vif); + + INIT_WORK(&mors_vif->u.ap.beacon_work, mm81x_mac_beacon_work); + mm81x_mac_beacon_irq_enable(mors_vif, true); + atomic_inc(&mors->num_bcn_vifs); +} + +static struct hw_scan_tlv_hdr mm81x_hw_scan_h_pack_tlv_hdr(u16 tag, u16 len) +{ + struct hw_scan_tlv_hdr hdr = { .tag = cpu_to_le16(tag), + .len = cpu_to_le16(len) }; + return hdr; +} + +static __le32 mm81x_hw_scan_h_pack_channel(struct ieee80211_channel *chan, + u8 pwr_idx) +{ + __le32 packed = 0; + u32 freq_khz = ieee80211_channel_to_khz(chan); + + packed |= le32_encode_bits(freq_khz, HW_SCAN_CH_LIST_FREQ_KHZ); + packed |= le32_encode_bits(mm81x_ratecode_bw_mhz_to_bw_index(1), + HW_SCAN_CH_LIST_OP_BW); + packed |= le32_encode_bits(mm81x_ratecode_bw_mhz_to_bw_index(1), + HW_SCAN_CH_LIST_PRIM_CH_WIDTH); + packed |= le32_encode_bits(pwr_idx, HW_SCAN_CH_LIST_PWR_LIST_IDX); + + return packed; +} + +static u8 * +mm81x_hw_scan_h_add_channel_list_tlv(u8 *buf, + struct mm81x_hw_scan_params *params) +{ + int i; + struct hw_scan_tlv_channel_list *ch_list = + (struct hw_scan_tlv_channel_list *)buf; + + ch_list->hdr = mm81x_hw_scan_h_pack_tlv_hdr( + HOST_CMD_HW_SCAN_TLV_TAG_CHAN_LIST, + params->num_chans * sizeof(ch_list->channels[0])); + + for (i = 0; i < params->num_chans; i++) { + struct ieee80211_channel *chan = params->channels[i].channel; + + ch_list->channels[i] = mm81x_hw_scan_h_pack_channel( + chan, params->channels[i].power_idx); + } + + return (u8 *)&ch_list->channels[i]; +} + +static u8 * +mm81x_hw_scan_h_add_power_list_tlv(u8 *buf, struct mm81x_hw_scan_params *params) +{ + int i; + struct hw_scan_tlv_power_list *pwr_list = + (struct hw_scan_tlv_power_list *)buf; + size_t size = sizeof(pwr_list->tx_power_qdbm[0]) * params->n_powers; + + pwr_list->hdr = mm81x_hw_scan_h_pack_tlv_hdr( + HOST_CMD_HW_SCAN_TLV_TAG_POWER_LIST, size); + + for (i = 0; i < params->n_powers; i++) + pwr_list->tx_power_qdbm[i] = params->powers_qdbm[i]; + + return (u8 *)&pwr_list->tx_power_qdbm[i]; +} + +static u8 * +mm81x_hw_scan_h_add_probe_req_tlv(u8 *buf, struct mm81x_hw_scan_params *params) +{ + struct sk_buff *skb = params->probe_req; + struct hw_scan_tlv_probe_req *probe_req = + (struct hw_scan_tlv_probe_req *)buf; + + probe_req->hdr = mm81x_hw_scan_h_pack_tlv_hdr( + HOST_CMD_HW_SCAN_TLV_TAG_PROBE_REQ, skb->len); + memcpy(probe_req->buf, skb->data, skb->len); + + return buf + sizeof(*probe_req) + skb->len; +} + +static u8 * +mm81x_hw_scan_h_insert_dwell_time_tlv(u8 *buf, + struct mm81x_hw_scan_params *params) +{ + struct hw_scan_tlv_dwell_on_home *dwell = + (struct hw_scan_tlv_dwell_on_home *)buf; + + dwell->hdr = mm81x_hw_scan_h_pack_tlv_hdr( + HOST_CMD_HW_SCAN_TLV_TAG_DWELL_ON_HOME, + sizeof(*dwell) - sizeof(dwell->hdr)); + dwell->home_dwell_time_ms = cpu_to_le32(params->dwell_on_home_ms); + + return buf + sizeof(*dwell); +} + +static int __mm81x_hw_scan_h_init_probe_req(struct mm81x_hw_scan_params *params, + u8 *ssid, u8 ssid_len, + struct ieee80211_scan_ies *ies) +{ + u8 *pos; + struct sk_buff *probe_req; + struct ieee80211_tx_info *info; + u16 ies_len = ies->len[NL80211_BAND_S1GHZ] + ies->common_ie_len; + + probe_req = ieee80211_probereq_get(params->hw, params->vif->addr, ssid, + ssid_len, ies_len); + if (!probe_req) + return -ENOMEM; + + pos = skb_put(probe_req, ies_len); + memcpy(pos, ies->common_ies, ies->common_ie_len); + pos += ies->common_ie_len; + memcpy(pos, ies->ies[NL80211_BAND_S1GHZ], ies->len[NL80211_BAND_S1GHZ]); + + info = IEEE80211_SKB_CB(probe_req); + info->control.vif = params->vif; + params->probe_req = probe_req; + + return 0; +} + +static void mm81x_hw_scan_h_init_ssid(struct mm81x *mors, + struct cfg80211_ssid *ssids, int n_ssids, + u8 **out_ssid, u8 *out_ssid_len) +{ + *out_ssid = NULL; + *out_ssid_len = 0; + + if (n_ssids > 0) { + if (n_ssids > 1) { + dev_warn( + mors->dev, + "Multiple SSIDs found when only one supported. Using the first only."); + } + *out_ssid_len = ssids[0].ssid_len; + *out_ssid = ssids[0].ssid; + } +} + +static int +mm81x_hw_scan_h_init_probe_req(struct mm81x_hw_scan_params *params, + struct ieee80211_scan_request *scan_req) +{ + struct mm81x *mors = params->hw->priv; + struct cfg80211_scan_request *req = &scan_req->req; + struct ieee80211_scan_ies *ies = &scan_req->ies; + u8 ssid_len = 0; + u8 *ssid = NULL; + + mm81x_hw_scan_h_init_ssid(mors, req->ssids, req->n_ssids, &ssid, + &ssid_len); + + return __mm81x_hw_scan_h_init_probe_req(params, ssid, ssid_len, ies); +} + +static bool +mm81x_hw_scan_h_is_chan_present(const struct mm81x_hw_scan_params *params, + const struct ieee80211_channel *chan) +{ + int channel; + + for (channel = 0; channel < params->num_chans; channel++) { + if (params->channels[channel].channel == chan) + return true; + } + + return false; +} + +static int mm81x_hw_scan_h_insert_chan(struct mm81x_hw_scan_params *params, + struct ieee80211_channel *chan) +{ + if (!params->channels) + return -EFAULT; + + if (!chan) + return -EFAULT; + + if (params->num_chans >= params->allocated_chans) + return -ENOMEM; + + if (mm81x_hw_scan_h_is_chan_present(params, chan)) + return 0; + + params->channels[params->num_chans].channel = chan; + params->num_chans++; + return 0; +} + +static int mm81x_hw_scan_h_init_chan_list(struct mm81x_hw_scan_params *params, + struct ieee80211_channel **chans, + u32 n_channels) +{ + int i, j; + int num_pwrs_coarse = 0; + int last_pwr = INT_MIN; + int chans_to_allocate = 0; + + for (i = 0; i < n_channels; i++) + if (chans[i]) + chans_to_allocate++; + + params->num_chans = 0; + params->allocated_chans = 0; + params->channels = kcalloc(chans_to_allocate, sizeof(*params->channels), + GFP_KERNEL); + if (!params->channels) + return -ENOMEM; + + params->allocated_chans = chans_to_allocate; + + for (i = 0; i < n_channels; i++) + if (chans[i]) + mm81x_hw_scan_h_insert_chan(params, chans[i]); + + /* + * Calculate a rough estimate of number of different channel + * powers required + */ + for (i = 0; i < params->num_chans; i++) { + if (chans[i]->max_reg_power != last_pwr) { + last_pwr = chans[i]->max_reg_power; + num_pwrs_coarse++; + } + } + + params->powers_qdbm = kmalloc_array( + num_pwrs_coarse, sizeof(*params->powers_qdbm), GFP_KERNEL); + if (!params->powers_qdbm) + return -ENOMEM; + + params->n_powers = 0; + + for (i = 0; i < params->num_chans; i++) { + s32 power_qdbm = + MBM_TO_QDBM(DBM_TO_MBM(chans[i]->max_reg_power)); + + /* Try and find the power in the list */ + for (j = 0; j < params->n_powers; j++) + if (params->powers_qdbm[j] == power_qdbm) + break; + + /* Reached the end of the list - add the new power option */ + if (j == params->n_powers) { + params->powers_qdbm[j] = power_qdbm; + params->n_powers++; + if (params->n_powers > num_pwrs_coarse) { + WARN_ON(1); + return -EFAULT; + } + } + + /* Give the index of the power level to the channel */ + params->channels[i].power_idx = j; + } + return 0; +} + +static void mm81x_hw_scan_h_clean_params(struct mm81x_hw_scan_params *params) +{ + if (params->probe_req) + dev_kfree_skb_any(params->probe_req); + kfree(params->channels); + kfree(params->powers_qdbm); + + params->num_chans = 0; + params->allocated_chans = 0; +} + +size_t mm81x_hw_scan_h_get_cmd_size(struct mm81x_hw_scan_params *params) +{ + struct hw_scan_tlv_channel_list *ch_list; + struct hw_scan_tlv_power_list *pwr_list; + struct hw_scan_tlv_probe_req *probe_req; + struct hw_scan_tlv_dwell_on_home *dwell; + struct host_cmd_req_hw_scan *req; + size_t cmd_size = sizeof(*req); + + /* No TLVs if simple abort command */ + if (params->operation != MM81X_HW_SCAN_OP_START) + return cmd_size; + + cmd_size += struct_size(ch_list, channels, params->num_chans); + cmd_size += struct_size(pwr_list, tx_power_qdbm, params->n_powers); + + if (params->probe_req) + cmd_size += struct_size(probe_req, buf, params->probe_req->len); + if (params->dwell_on_home_ms) + cmd_size += sizeof(*dwell); + + return cmd_size; +} + +u8 *mm81x_hw_scan_h_insert_tlvs(struct mm81x_hw_scan_params *params, u8 *buf) +{ + buf = mm81x_hw_scan_h_add_channel_list_tlv(buf, params); + buf = mm81x_hw_scan_h_add_power_list_tlv(buf, params); + + if (params->dwell_on_home_ms) + buf = mm81x_hw_scan_h_insert_dwell_time_tlv(buf, params); + if (params->probe_req) + buf = mm81x_hw_scan_h_add_probe_req_tlv(buf, params); + + return buf; +} + +static u32 mm81x_hw_scan_h_get_dwell_on_home(struct mm81x *mors, + struct ieee80211_vif *vif) +{ + if (vif->type == NL80211_IFTYPE_STATION && vif->cfg.assoc) + return mors->hw_scan.home_dwell_ms; + return 0; +} + +static struct mm81x_hw_scan_params * +__mm81x_hw_scan_h_init_params(struct mm81x *mors) +{ + struct mm81x_hw_scan_params *params = mors->hw_scan.params; + + if (!params) { + params = kzalloc_obj(*params, GFP_KERNEL); + if (params) + mors->hw_scan.params = params; + } else { + mm81x_hw_scan_h_clean_params(params); + memset(params, 0, sizeof(*params)); + } + + return params; +} + +static int mm81x_hw_scan_h_init_params(struct mm81x *mors, + struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_scan_request *req) +{ + struct mm81x_hw_scan_params *params = mors->hw_scan.params; + + params = __mm81x_hw_scan_h_init_params(mors); + if (!params) { + mors->hw_scan.state = HW_SCAN_STATE_IDLE; + return -ENOMEM; + } + + params->hw = hw; + params->vif = vif; + params->has_directed_ssid = (req->ssids && req->ssids[0].ssid_len > 0); + params->operation = MM81X_HW_SCAN_OP_START; + params->dwell_on_home_ms = mm81x_hw_scan_h_get_dwell_on_home(mors, vif); + + if (req->duration) + params->dwell_time_ms = MM81X_TU_TO_MS(req->duration); + else if (req->n_ssids == 0) + params->dwell_time_ms = + MM81X_HWSCAN_DEFAULT_PASSIVE_DWELL_TIME_MS; + else + params->dwell_time_ms = MM81X_HWSCAN_DEFAULT_DWELL_TIME_MS; + + return 0; +} + +static u32 mm81x_hw_scan_h_calc_timeout(struct mm81x_hw_scan_params *params) +{ + u32 ret = 0; + + ret = params->dwell_time_ms + params->dwell_on_home_ms; + if (params->probe_req) + ret += MM81X_HWSCAN_PROBE_DELAY_MS; + + ret *= params->num_chans; + ret += MM81X_HWSCAN_TIMEOUT_OVERHEAD_MS; + + return ret; +} + +static int mm81x_mac_ops_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_scan_request *hw_req) +{ + int ret = 0; + struct mm81x *mors = hw->priv; + struct cfg80211_scan_request *req = &hw_req->req; + struct mm81x_hw_scan_params *params; + struct ieee80211_channel **chans = hw_req->req.channels; + + dev_dbg(mors->dev, "state %d", mors->hw_scan.state); + + if (!mors->started) { + dev_warn(mors->dev, "device not ready"); + ret = -ENODEV; + goto exit; + } + + switch (mors->hw_scan.state) { + case HW_SCAN_STATE_IDLE: + mors->hw_scan.state = HW_SCAN_STATE_RUNNING; + reinit_completion(&mors->hw_scan.scan_done); + break; + case HW_SCAN_STATE_RUNNING: + case HW_SCAN_STATE_ABORTING: + ret = -EBUSY; + goto exit; + } + + ret = mm81x_hw_scan_h_init_params(mors, hw, vif, req); + if (ret) + goto exit; + + params = mors->hw_scan.params; + + ret = mm81x_hw_scan_h_init_chan_list(params, chans, + hw_req->req.n_channels); + if (ret) + goto exit; + + /* Only init the probe request template if this is an active scan */ + if (req->n_ssids > 0) { + ret = mm81x_hw_scan_h_init_probe_req(params, hw_req); + if (ret) { + dev_err(mors->dev, "Failed to init probe req %d", ret); + goto exit; + } + } + + ret = mm81x_cmd_hw_scan(mors, params, false); + if (ret) { + mors->hw_scan.state = HW_SCAN_STATE_IDLE; + goto exit; + } + + ieee80211_queue_delayed_work( + mors->hw, &mors->hw_scan.timeout, + msecs_to_jiffies(mm81x_hw_scan_h_calc_timeout(params))); +exit: + return ret; +} + +static void mm81x_hw_scan_abort(struct mm81x *mors) +{ + int ret; + struct mm81x_hw_scan_params params = { 0 }; + + switch (mors->hw_scan.state) { + case HW_SCAN_STATE_IDLE: + case HW_SCAN_STATE_ABORTING: + /* scan not running */ + return; + case HW_SCAN_STATE_RUNNING: + mors->hw_scan.state = HW_SCAN_STATE_ABORTING; + break; + } + + params.operation = MM81X_HW_SCAN_OP_STOP; + + ret = mm81x_cmd_hw_scan(mors, ¶ms, false); + + if (ret || !mors->started || + !wait_for_completion_timeout(&mors->hw_scan.scan_done, 1 * HZ)) { + /* + * We may have lost the event on the bus, the chip could be + * wedged, or the cmd failed for another reason. Nevertheless, + * we should call the done event so mac80211 knows to unblock + * itself. + */ + struct cfg80211_scan_info info = { .aborted = true }; + + ieee80211_scan_completed(mors->hw, &info); + mors->hw_scan.state = HW_SCAN_STATE_IDLE; + } +} + +static void mm81x_mac_ops_cancel_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mm81x *mors = hw->priv; + + cancel_delayed_work_sync(&mors->hw_scan.timeout); + mm81x_hw_scan_abort(mors); +} + +static void mm81x_mac_hw_scan_done_event(struct ieee80211_hw *hw) +{ + struct mm81x *mors = hw->priv; + struct cfg80211_scan_info info = { 0 }; + + dev_dbg(mors->dev, "completing hw scan"); + + switch (mors->hw_scan.state) { + case HW_SCAN_STATE_IDLE: + /* Scan has already been stopped. Just continue */ + goto exit; + case HW_SCAN_STATE_RUNNING: + case HW_SCAN_STATE_ABORTING: + info.aborted = (mors->hw_scan.state == HW_SCAN_STATE_ABORTING); + mors->hw_scan.state = HW_SCAN_STATE_IDLE; + } + + ieee80211_scan_completed(mors->hw, &info); +exit: + complete(&mors->hw_scan.scan_done); + cancel_delayed_work_sync(&mors->hw_scan.timeout); +} + +static void mm81x_mac_hw_scan_timeout_work(struct work_struct *work) +{ + struct mm81x *mors = + container_of(work, struct mm81x, hw_scan.timeout.work); + + dev_err(mors->dev, "hw scan timed out, aborting"); + mm81x_hw_scan_abort(mors); +} + +static void mm81x_mac_hw_scan_init(struct mm81x *mors) +{ + mors->hw_scan.state = HW_SCAN_STATE_IDLE; + mors->hw_scan.params = NULL; + mors->hw_scan.home_dwell_ms = MM81X_HWSCAN_DEFAULT_DWELL_ON_HOME_MS; + + init_completion(&mors->hw_scan.scan_done); + INIT_DELAYED_WORK(&mors->hw_scan.timeout, + mm81x_mac_hw_scan_timeout_work); +} + +static void mm81x_mac_hw_scan_destroy(struct mm81x *mors) +{ + cancel_delayed_work_sync(&mors->hw_scan.timeout); + if (mors->hw_scan.params) + mm81x_hw_scan_h_clean_params(mors->hw_scan.params); + kfree(mors->hw_scan.params); + mors->hw_scan.params = NULL; +} + +static void mm81x_mac_hw_scan_finish(struct mm81x *mors) +{ + struct cfg80211_scan_info info = { + .aborted = true, + }; + + if (mors->hw_scan.state == HW_SCAN_STATE_IDLE) + return; + + ieee80211_scan_completed(mors->hw, &info); + complete(&mors->hw_scan.scan_done); + mors->hw_scan.state = HW_SCAN_STATE_IDLE; + cancel_delayed_work_sync(&mors->hw_scan.timeout); +} + +int mm81x_mac_event_recv(struct mm81x *mors, struct sk_buff *skb) +{ + struct host_cmd_event *event = (struct host_cmd_event *)(skb->data); + u16 event_id = le16_to_cpu(event->hdr.message_id); + u16 event_iid = le16_to_cpu(event->hdr.host_id); + u16 vif_id = le16_to_cpu(event->hdr.vif_id); + struct ieee80211_vif *vif; + + if (!HOST_CMD_IS_EVT(event) || event_iid != 0) + return -EINVAL; + + switch (event_id) { + case HOST_CMD_ID_EVT_HW_SCAN_DONE: + dev_dbg(mors->dev, + "Event: HOST_CMD_ID_EVT_HW_SCAN_DONE Received."); + mm81x_mac_hw_scan_done_event(mors->hw); + break; + case HOST_CMD_ID_EVT_BEACON_LOSS: + dev_dbg(mors->dev, + "Event: HOST_CMD_ID_EVT_BEACON_LOSS Received"); + scoped_guard(rcu) { + vif = mm81x_rcu_dereference_vif_id(mors, vif_id, true); + if (vif) + ieee80211_beacon_loss(vif); + } + break; + default: + break; + } + + return 0; +} + +static void mm81x_tx_h_apply_mcs10(struct mm81x *mors, + struct mm81x_skb_tx_info *tx_info) +{ + u8 i; + u8 j; + int mcs0_first_idx = -1; + int mcs0_last_idx = -1; + + /* Find out where our first and last MCS0 entries are. */ + for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { + enum dot11_bandwidth bw_idx = mm81x_ratecode_bw_index_get( + tx_info->rates[i].mm81x_ratecode); + + if (bw_idx == DOT11_BANDWIDTH_1MHZ) { + mcs0_last_idx = i; + if (mcs0_first_idx == -1) + mcs0_first_idx = i; + } + + /* + * If the count is 0 then we are at the end of the table. + * Break to allow us to reuse i indicating the end of the + * table. + */ + if (tx_info->rates[i].count == 0) + break; + } + + /* If there aren't any MCS0 (at 1MHz) entries we are done. */ + if (mcs0_first_idx < 0) + return; + + /* + * If we are in MCS10_MODE_AUTO add MCS10 counts to the table if they + * will fit. There should be three cases: + * + * - There is one MSC0 entry and the table is full -> do nothing + * - There is one MSC0 entry and the table has space -> adjust MSC0 + * down and add MCS 10 + * - There are multiple MCS0 entries -> replace entries after the first + * with MCS 10 + */ + /* Case 3 - replace additional entries. */ + if (mcs0_last_idx > mcs0_first_idx) { + for (j = mcs0_first_idx + 1; j < i; j++) { + enum dot11_bandwidth bw_idx = + mm81x_ratecode_bw_index_get( + tx_info->rates[j].mm81x_ratecode); + u8 mcs_index = mm81x_ratecode_mcs_index_get( + tx_info->rates[j].mm81x_ratecode); + if (mcs_index == 0 && bw_idx == DOT11_BANDWIDTH_1MHZ) { + mm81x_ratecode_mcs_index_set( + &tx_info->rates[j].mm81x_ratecode, 10); + } + } + /* Case 2 - add additional MCS10 entry. */ + } else if (mcs0_last_idx == mcs0_first_idx && + i < (IEEE80211_TX_MAX_RATES)) { + int pre_mcs10_mcs0_count = + min_t(u8, tx_info->rates[mcs0_last_idx].count, + MCS0_BEFORE_MCS10_COUNT); + int mcs10_count = tx_info->rates[mcs0_last_idx].count - + pre_mcs10_mcs0_count; + + /* + * If there were less retries than our desired minimum MCS0 we + * don't add MCS10 retries. + */ + if (mcs10_count > 0) { + /* Use the same flags for MCS10 as MCS0. */ + tx_info->rates[i].mm81x_ratecode = + tx_info->rates[mcs0_last_idx].mm81x_ratecode; + mm81x_ratecode_mcs_index_set( + &tx_info->rates[i].mm81x_ratecode, 10); + tx_info->rates[mcs0_last_idx].count = + pre_mcs10_mcs0_count; + tx_info->rates[i].count = mcs10_count; + } + } +} + +void mm81x_tx_h_check_aggr(struct ieee80211_sta *pubsta, struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct mm81x_sta *mors_sta = (struct mm81x_sta *)pubsta->drv_priv; + u8 tid = ieee80211_get_tid(hdr); + + /* we are already aggregating */ + if (mors_sta->tid_tx[tid] || mors_sta->tid_start_tx[tid]) + return; + + if (mors_sta->state < IEEE80211_STA_AUTHORIZED) + return; + + if (skb_get_queue_mapping(skb) == IEEE80211_AC_VO) + return; + + if (unlikely(!ieee80211_is_data_qos(hdr->frame_control))) + return; + + if (unlikely(skb->protocol == cpu_to_be16(ETH_P_PAE))) + return; + + mors_sta->tid_start_tx[tid] = true; + ieee80211_start_tx_ba_session(pubsta, tid, 0); +} + +int mm81x_tx_h_get_attempts(struct mm81x *mors, + struct mm81x_skb_tx_status *tx_sts) +{ + int attempts = 0; + int i; + int count = min_t(int, MM81X_SKB_MAX_RATES, IEEE80211_TX_MAX_RATES); + + for (i = 0; i < count; i++) { + if (tx_sts->rates[i].count > 0) + attempts += tx_sts->rates[i].count; + else + break; + } + + return attempts; +} + +static void mm81x_tx_h_fill_info(struct mm81x *mors, + struct mm81x_skb_tx_info *tx_info, + struct sk_buff *skb, struct ieee80211_vif *vif, + int tx_bw_mhz, struct ieee80211_sta *sta) +{ + int i; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct mm81x_vif *mors_vif = ieee80211_vif_to_mors_vif(vif); + struct mm81x_sta *mors_sta = NULL; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + int op_bw_mhz = cfg80211_chandef_get_width(&mors->chandef); + u8 tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK; + bool rts_allowed = op_bw_mhz < 8; + + if (sta) + mors_sta = (struct mm81x_sta *)sta->drv_priv; + + rts_allowed &= mm81x_tx_h_pkt_over_rts_threshold(mors, info, skb); + + mm81x_rc_sta_fill_tx_rates(mors, tx_info, skb, sta, tx_bw_mhz, + rts_allowed); + + for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { + if (rts_allowed) + mm81x_ratecode_enable_rts( + &tx_info->rates[i].mm81x_ratecode); + + if (info->control.rates[i].flags & IEEE80211_TX_RC_SHORT_GI) + mm81x_ratecode_enable_sgi( + &tx_info->rates[i].mm81x_ratecode); + } + + /* Apply change of MCS0 to MCS10 if required. */ + mm81x_tx_h_apply_mcs10(mors, tx_info); + + tx_info->flags |= + cpu_to_le32(MM81X_TX_CONF_FLAGS_VIF_ID_SET(mors_vif->id)); + + if (info->flags & IEEE80211_TX_CTL_AMPDU) + tx_info->flags |= cpu_to_le32(MM81X_TX_CONF_FLAGS_CTL_AMPDU); + + if (info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM) + tx_info->flags |= + cpu_to_le32(MM81X_TX_CONF_FLAGS_SEND_AFTER_DTIM); + + if (info->flags & IEEE80211_TX_CTL_NO_PS_BUFFER) { + tx_info->flags |= cpu_to_le32(MM81X_TX_CONF_NO_PS_BUFFER); + + if (info->flags & IEEE80211_TX_STATUS_EOSP) + tx_info->flags |= cpu_to_le32( + MM81X_TX_CONF_FLAGS_IMMEDIATE_REPORT); + } else if (ieee80211_is_mgmt(hdr->frame_control) && + !ieee80211_is_bufferable_mmpdu(skb)) { + tx_info->flags |= cpu_to_le32(MM81X_TX_CONF_NO_PS_BUFFER); + } + + if (info->control.hw_key) { + tx_info->flags |= cpu_to_le32(MM81X_TX_CONF_FLAGS_HW_ENCRYPT); + tx_info->flags |= cpu_to_le32(MM81X_TX_CONF_FLAGS_KEY_IDX_SET( + info->control.hw_key->hw_key_idx)); + } + + tx_info->tid = tid; + if (mors_sta) { + tx_info->tid_params = mors_sta->tid_params[tid]; + + if (info->flags & IEEE80211_TX_CTL_CLEAR_PS_FILT) { + if (mors_sta->tx_ps_filter_en) + dev_dbg(mors->dev, + "TX ps filter cleared sta[%pM]", + mors_sta->addr); + mors_sta->tx_ps_filter_en = false; + } + } +} + +static void mm81x_mac_ops_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct mm81x *mors = hw->priv; + struct mm81x_skbq *mq = NULL; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_vif *vif = info->control.vif; + struct mm81x_skb_tx_info tx_info = { 0 }; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + bool is_mgmt = ieee80211_is_mgmt(hdr->frame_control); + int tx_bw_mhz = cfg80211_chandef_get_width(&mors->chandef); + struct ieee80211_sta *sta = control->sta; + int max_tx_bw = 0, sta_max_bw_mhz = 0; + + if (sta) { + struct mm81x_sta *mors_sta = (struct mm81x_sta *)sta->drv_priv; + + sta_max_bw_mhz = mors_sta->max_bw_mhz; + } + + max_tx_bw = mm81x_tx_h_get_max_bw(mors); + tx_bw_mhz = min(max_tx_bw, tx_bw_mhz); + + if (is_mgmt) + tx_bw_mhz = cfg80211_chandef_s1g_pri_width(&mors->chandef); + if (sta_max_bw_mhz) + tx_bw_mhz = min(tx_bw_mhz, sta_max_bw_mhz); + if (ieee80211_is_probe_resp(hdr->frame_control)) + tx_bw_mhz = 1; + + mm81x_tx_h_fill_info(mors, &tx_info, skb, vif, tx_bw_mhz, sta); + + if (mm81x_tx_h_ps_filtered_for_sta(mors, skb, sta)) + return; + + if (is_mgmt) + mq = mm81x_hif_get_tx_mgmt_queue(mors); + else + mq = mm81x_hif_get_tx_data_queue(mors, + dot11_tid_to_ac(tx_info.tid)); + + mm81x_skbq_skb_tx(mq, &skb, &tx_info, + (is_mgmt) ? MM81X_SKB_CHAN_MGMT : + MM81X_SKB_CHAN_DATA); +} + +static void mm81x_mac_ops_stop(struct ieee80211_hw *hw, bool suspend) +{ + struct mm81x *mors = hw->priv; + + mors->started = false; +} + +static void mm81x_mac_beacon_finish(struct mm81x_vif *mors_vif) +{ + struct mm81x *mors = mm81x_vif_to_mors(mors_vif); + + mm81x_mac_beacon_irq_enable(mors_vif, false); + cancel_work_sync(&mors_vif->u.ap.beacon_work); + /* + * Side effect of the restarting required when + * reacting to regdom changes... + */ + atomic_add_unless(&mors->num_bcn_vifs, -1, 0); +} + +static void mm81x_mac_ops_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + int ret; + struct mm81x *mors = hw->priv; + struct mm81x_vif *mors_vif = (struct mm81x_vif *)vif->drv_priv; + + ret = mm81x_cmd_rm_if(mors, mors_vif->id); + if (ret) + dev_err(mors->dev, "mm81x_cmd_rm_if failed %d", ret); + + RCU_INIT_POINTER(mors->vifs[mors_vif->id], NULL); +} + +static s32 mm81x_mac_get_max_txpower(struct mm81x *mors) +{ + int ret; + s32 power_mbm; + + /* Retrieve maximum TX power the chip can transmit */ + ret = mm81x_cmd_get_max_txpower(mors, &power_mbm); + if (ret) { + dev_err(mors->dev, "using default tx max power %d mBm", + MAX_TX_POWER_MBM); + return MAX_TX_POWER_MBM; + } + + dev_dbg(mors->dev, "Max tx power detected %d mBm", power_mbm); + return power_mbm; +} + +static s32 mm81x_mac_set_txpower(struct mm81x *mors, s32 power_mbm) +{ + int ret; + s32 out_power_mbm; + + if (mors->tx_max_power_mbm == INT_MAX) + mors->tx_max_power_mbm = mm81x_mac_get_max_txpower(mors); + + power_mbm = min(power_mbm, mors->tx_max_power_mbm); + if (power_mbm == mors->tx_power_mbm) + return mors->tx_power_mbm; + + ret = mm81x_cmd_set_txpower(mors, &out_power_mbm, power_mbm); + if (ret) { + dev_err(mors->dev, "failed, power %d mBm ret %d", power_mbm, + ret); + return mors->tx_power_mbm; + } + + if (out_power_mbm != mors->tx_power_mbm) { + dev_dbg(mors->dev, "%d -> %d mBm", mors->tx_power_mbm, + out_power_mbm); + mors->tx_power_mbm = out_power_mbm; + } + + return mors->tx_power_mbm; +} + +static int mm81x_mac_set_channel(struct mm81x *mors, u32 op_chan_freq_hz, + u8 pri_1mhz_chan_idx, u8 op_bw_mhz, + u8 pri_bw_mhz) +{ + int ret; + + ret = mm81x_cmd_set_channel(mors, op_chan_freq_hz, pri_1mhz_chan_idx, + op_bw_mhz, pri_bw_mhz, &mors->tx_power_mbm); + if (ret) { + dev_err(mors->dev, "mm81x_cmd_set_channel() failed, ret %d", + ret); + return ret; + } + + mm81x_mac_set_txpower(mors, mors->tx_power_mbm); + return 0; +} + +static u8 mm81x_mac_pri_chan_to_index(const struct cfg80211_chan_def *chandef) +{ + u32 bw_mhz = cfg80211_chandef_get_width(chandef); + u32 op_center_khz = ieee80211_chandef_to_khz(chandef); + u32 first_1mhz_center_khz = op_center_khz - (bw_mhz * 500) + 500; + u32 pri_1mhz_khz = ieee80211_channel_to_khz(chandef->chan); + + return (pri_1mhz_khz - first_1mhz_center_khz) / 1000; +} + +static int mm81x_mac_ops_change_channel(struct ieee80211_hw *hw, + struct cfg80211_chan_def *chandef) +{ + int ret; + struct mm81x *mors = hw->priv; + u64 freq_hz = KHZ_TO_HZ(ieee80211_chandef_to_khz(chandef)); + u8 op_bw_mhz = cfg80211_chandef_get_width(chandef); + u8 pri_1mhz_idx = mm81x_mac_pri_chan_to_index(chandef); + int pri_chan_width_mhz = cfg80211_chandef_s1g_pri_width(chandef); + + dev_dbg(mors->dev, "ch: freq=%llu Hz bw=%u pri_idx=%d pri_bw=%d", + freq_hz, op_bw_mhz, pri_1mhz_idx, pri_chan_width_mhz); + + ret = mm81x_mac_set_channel(mors, freq_hz, (u8)pri_1mhz_idx, op_bw_mhz, + pri_chan_width_mhz); + if (ret) + return ret; + + memcpy(&mors->chandef, chandef, sizeof(mors->chandef)); + return 0; +} + +static int mm81x_mac_ops_config(struct ieee80211_hw *hw, int radio_idx, + u32 changed) +{ + int ret; + struct mm81x *mors = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + struct ieee80211_channel *channel = conf->chandef.chan; + + if (!mors->started) + return 0; + + if (changed & IEEE80211_CONF_CHANGE_CHANNEL) { + ret = mm81x_mac_ops_change_channel(hw, &conf->chandef); + if (ret < 0) + return ret; + } + + if ((changed & IEEE80211_CONF_CHANGE_POWER) && + !(changed & IEEE80211_CONF_CHANGE_CHANNEL) && + !(conf->flags & IEEE80211_CONF_MONITOR)) { + s32 power_mbm = DBM_TO_MBM(conf->power_level); + + power_mbm = min(DBM_TO_MBM(channel->max_reg_power), power_mbm); + power_mbm = mm81x_mac_set_txpower(mors, power_mbm); + conf->power_level = MBM_TO_DBM(power_mbm); + } + + return 0; +} + +static int mm81x_mac_ops_get_txpower(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + unsigned int link_id, int *dbm) +{ + struct mm81x *mors = hw->priv; + struct ieee80211_chanctx_conf *chanctx_conf; + struct cfg80211_chan_def *chandef = &vif->bss_conf.chanreq.oper; + + scoped_guard(rcu) { + chanctx_conf = rcu_access_pointer(vif->bss_conf.chanctx_conf); + if (!chanctx_conf || + !cfg80211_chandef_identical(chandef, &chanctx_conf->def)) + return -ENODATA; + } + + *dbm = MBM_TO_DBM(mors->tx_power_mbm); + return 0; +} + +static void mm81x_mac_config_ps(struct mm81x *mors, struct ieee80211_vif *vif) +{ + bool en_ps = vif->cfg.ps; + + if (vif->type == NL80211_IFTYPE_AP || !mors->ps.enable) + return; + + if (mors->config_ps == en_ps) + return; + + dev_dbg(mors->dev, "change powersave mode: %d (current %d)", en_ps, + mors->config_ps); + + mors->config_ps = en_ps; + + if (en_ps) { + mm81x_cmd_set_ps(mors, true); + mm81x_ps_enable(mors); + } else { + mm81x_ps_disable(mors); + mm81x_cmd_set_ps(mors, false); + } +} + +static void mm81x_mac_ops_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, + u64 changed) +{ + int ret; + struct mm81x *mors = hw->priv; + struct mm81x_vif *mors_vif = (struct mm81x_vif *)vif->drv_priv; + + if (changed & BSS_CHANGED_PS) + mm81x_mac_config_ps(mors, vif); + + if (changed & BSS_CHANGED_BEACON_ENABLED) { + mm81x_cmd_config_beacon_timer(mors, mors_vif, + info->enable_beacon); + + if (!info->enable_beacon) + mm81x_mac_beacon_finish(mors_vif); + } + + if (changed & BSS_CHANGED_BEACON_INT || changed & BSS_CHANGED_SSID) { + ret = mm81x_cmd_cfg_bss(mors, mors_vif->id, info->beacon_int, + info->dtim_period, + mm81x_vif_generate_cssid(vif)); + if (ret) + dev_err(mors->dev, "mm81x_cmd_cfg_bss failed %d", ret); + } +} + +static u64 mm81x_mac_ops_prepare_multicast(struct ieee80211_hw *hw, + struct netdev_hw_addr_list *mc_list) +{ + struct mm81x *mors = hw->priv; + struct mcast_filter *filter; + struct netdev_hw_addr *addr; + u16 addr_count = netdev_hw_addr_list_count(mc_list); + u16 len = sizeof(*filter) + addr_count * sizeof(filter->addr_list[0]); + + filter = kzalloc(len, GFP_ATOMIC); + if (!filter) + return 0; + + if (addr_count > MCAST_FILTER_COUNT_MAX) { + dev_warn( + mors->dev, + "Multicast filtering disabled - too many groups (%d) > %u", + addr_count, (u16)MCAST_FILTER_COUNT_MAX); + filter->count = 0; + } else { + netdev_hw_addr_list_for_each(addr, mc_list) { + dev_dbg(mors->dev, "mcast whitelist (%d): %pM", + filter->count, addr->addr); + filter->addr_list[filter->count++] = + mac2le32(addr->addr); + } + } + + return (u64)(unsigned long)filter; +} + +static void mm81x_mac_ops_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast) +{ + struct mm81x *mors = hw->priv; + struct mcast_filter *cmd = (void *)(unsigned long)multicast; + struct mm81x_vif *mors_vif = NULL; + struct ieee80211_vif *vif = NULL; + int vif_id = 0; + int ret = 0; + + if (!cmd) + goto out; + + kfree(mors->mcast_filter); + mors->mcast_filter = cmd; + + for (vif_id = 0; vif_id < ARRAY_SIZE(mors->vifs); vif_id++) { + vif = mm81x_rcu_dereference_vif_id(mors, vif_id, false); + if (!vif) + continue; + + mors_vif = ieee80211_vif_to_mors_vif(vif); + + ret = mm81x_cmd_cfg_multicast_filter(mors, mors_vif); + if (!ret) + continue; + + dev_err(mors->dev, "Multicast filtering failed - rc=%d", ret); + mors->mcast_filter = NULL; + kfree(cmd); + break; + } + +out: + *total_flags &= 0; +} + +static int mm81x_mac_ops_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + unsigned int link_id, u16 ac, + const struct ieee80211_tx_queue_params *params) +{ + int ret; + struct mm81x *mors = hw->priv; + struct mm81x_queue_params mqp; + + mqp.aci = map_mac80211q_2_mm81x_aci(ac); + mqp.aifs = params->aifs; + mqp.cw_max = params->cw_max; + mqp.cw_min = params->cw_min; + mqp.uapsd = params->uapsd; + mqp.txop = params->txop << 5; + + dev_dbg(mors->dev, "queue:%d txop:%d cw_min:%d cw_max:%d aifs:%d", + mqp.aci, mqp.txop, mqp.cw_min, mqp.cw_max, mqp.aifs); + + ret = mm81x_cmd_cfg_qos(mors, &mqp); + if (ret) + dev_dbg(mors->dev, "mm81x_cmd_cfg_qos failed %d", ret); + return ret; +} + +static int mm81x_mac_ops_sta_state(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + enum ieee80211_sta_state old_state, + enum ieee80211_sta_state new_state) +{ + u16 aid; + int ret; + struct mm81x *mors = hw->priv; + struct mm81x_vif *mors_vif = (struct mm81x_vif *)vif->drv_priv; + struct mm81x_sta *mors_sta = (struct mm81x_sta *)sta->drv_priv; + + /* Ignore both NOTEXIST to NONE and NONE to NOTEXIST */ + if ((old_state == IEEE80211_STA_NOTEXIST && + new_state == IEEE80211_STA_NONE) || + (old_state == IEEE80211_STA_NONE && + new_state == IEEE80211_STA_NOTEXIST)) + return 0; + + if (vif->type == NL80211_IFTYPE_STATION) + aid = vif->cfg.aid; + else + aid = sta->aid; + + ret = mm81x_cmd_sta_state(mors, mors_vif, aid, sta, new_state); + if (ret < 0) + goto exit; + + ether_addr_copy(mors_sta->addr, sta->addr); + mors_sta->state = new_state; + + if (new_state > old_state && new_state == IEEE80211_STA_ASSOC) { + if (vif->type == NL80211_IFTYPE_AP) + mors_vif->u.ap.num_stas++; + else if (vif->type == NL80211_IFTYPE_STATION) + mors_vif->u.sta.is_assoc = true; + } + + if (new_state < old_state && new_state == IEEE80211_STA_NONE) { + if (vif->type == NL80211_IFTYPE_AP) + mors_vif->u.ap.num_stas--; + else if (vif->type == NL80211_IFTYPE_STATION) + mors_vif->u.sta.is_assoc = false; + } + +exit: + /* + * Always update our mmrc sta state even on failure to ensure + * we don't hold a dangling sta on error + */ + mm81x_rc_sta_state_check(mors, vif, sta, old_state, new_state); + return new_state < old_state ? 0 : ret; +} + +static int mm81x_mac_ops_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_ampdu_params *params) +{ + u16 tid = params->tid; + struct mm81x *mors = hw->priv; + struct ieee80211_sta *sta = params->sta; + struct mm81x_sta *mors_sta = (struct mm81x_sta *)sta->drv_priv; + u16 buf_size = + min_t(u16, params->buf_size, DOT11AH_BA_MAX_MPDU_PER_AMPDU); + + switch (params->action) { + case IEEE80211_AMPDU_TX_START: + dev_dbg(mors->dev, "%pM.%d A-MPDU TX start", mors_sta->addr, + tid); + ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + case IEEE80211_AMPDU_TX_STOP_CONT: + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + dev_dbg(mors->dev, "%pM.%d A-MPDU TX flush", mors_sta->addr, + tid); + mors_sta->tid_start_tx[tid] = false; + mors_sta->tid_tx[tid] = false; + mors_sta->tid_params[tid] = 0; + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + dev_dbg(mors->dev, "%pM.%d A-MPDU TX oper", mors_sta->addr, + tid); + mors_sta->tid_tx[tid] = true; + if (!buf_size) { + dev_err(mors->dev, "%pM.%d A-MPDU Invalid buf size", + mors_sta->addr, tid); + break; + } + mors_sta->tid_params[tid] = + u8_encode_bits(buf_size - 1, + TX_INFO_TID_PARAMS_MAX_REORDER_BUF) | + u8_encode_bits(1, TX_INFO_TID_PARAMS_AMPDU_ENABLED) | + u8_encode_bits(params->amsdu, + TX_INFO_TID_PARAMS_AMSDU_SUPPORTED); + break; + default: + break; + } + + return 0; +} + +static int mm81x_mac_ops_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + u16 aid; + int ret = -EOPNOTSUPP; + struct mm81x *mors = hw->priv; + struct mm81x_vif *mors_vif = (struct mm81x_vif *)vif->drv_priv; + enum host_cmd_key_cipher cipher; + enum host_cmd_aes_key_len length; + + if (vif->type == NL80211_IFTYPE_STATION) { + aid = vif->cfg.aid; + } else if (sta) { + aid = sta->aid; + } else { + /* Is a group key - AID is unused */ + WARN_ON(key->flags & IEEE80211_KEY_FLAG_PAIRWISE); + aid = 0; + } + + switch (cmd) { + case SET_KEY: { + switch (key->cipher) { + case WLAN_CIPHER_SUITE_CCMP: + case WLAN_CIPHER_SUITE_CCMP_256: + cipher = HOST_CMD_KEY_CIPHER_AES_CCM; + break; + case WLAN_CIPHER_SUITE_GCMP: + case WLAN_CIPHER_SUITE_GCMP_256: + cipher = HOST_CMD_KEY_CIPHER_AES_GCM; + break; + default: + /* Cipher suite currently not supported */ + ret = -EOPNOTSUPP; + goto exit; + } + + switch (key->keylen) { + case 16: + length = HOST_CMD_AES_KEY_LEN_LENGTH_128; + break; + case 32: + length = HOST_CMD_AES_KEY_LEN_LENGTH_256; + break; + default: + /* Key length not supported */ + ret = -EOPNOTSUPP; + goto exit; + } + + ret = mm81x_cmd_install_key(mors, mors_vif, aid, key, cipher, + length); + break; + } + case DISABLE_KEY: + ret = mm81x_cmd_disable_key(mors, mors_vif, aid, key); + if (ret) { + /* Must return 0 */ + dev_warn(mors->dev, "Failed to remove key"); + ret = 0; + } + break; + default: + WARN_ON(1); + } + + if (ret) { + dev_dbg(mors->dev, "Falling back to software crypto"); + ret = 1; + } + +exit: + return ret; +} + +static int mm81x_mac_set_frag_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 value) +{ + struct mm81x *mors = hw->priv; + + return mm81x_cmd_set_frag_threshold(mors, value); +} + +static u8 mm81x_rx_h_rc_bw_to_rx_bw(__le32 ratecode) +{ + enum dot11_bandwidth bw = mm81x_ratecode_bw_index_get(ratecode); + + switch (bw) { + case DOT11_BANDWIDTH_1MHZ: + return RATE_INFO_BW_1; + case DOT11_BANDWIDTH_2MHZ: + return RATE_INFO_BW_2; + case DOT11_BANDWIDTH_4MHZ: + return RATE_INFO_BW_4; + case DOT11_BANDWIDTH_8MHZ: + return RATE_INFO_BW_8; + default: + return RATE_INFO_BW_1; + } +} + +static void mm81x_rx_h_fill_status(struct mm81x *mors, + struct mm81x_skb_rx_status *hdr_rx_status, + struct ieee80211_rx_status *rx_status, + struct sk_buff *skb) +{ + u32 flags = le32_to_cpu(hdr_rx_status->flags); + u16 freq_100khz = le16_to_cpu(hdr_rx_status->freq_100khz); + __le32 ratecode = hdr_rx_status->mm81x_ratecode; + + rx_status->signal = le16_to_cpu(hdr_rx_status->rssi); + rx_status->encoding = RX_ENC_S1G; + rx_status->band = NL80211_BAND_S1GHZ; + rx_status->freq = KHZ100_TO_MHZ(freq_100khz); + rx_status->freq_offset = (freq_100khz % 10) ? 1 : 0; + rx_status->nss = NSS_IDX_TO_NSS(mm81x_ratecode_nss_index_get(ratecode)); + + if (flags & MM81X_RX_STATUS_FLAGS_DECRYPTED) + rx_status->flag |= RX_FLAG_DECRYPTED; + + rx_status->rate_idx = mm81x_ratecode_mcs_index_get(ratecode); + rx_status->bw = mm81x_rx_h_rc_bw_to_rx_bw(ratecode); + + if (mm81x_ratecode_sgi_get(ratecode)) + rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI; +} + +static void mm81x_rx_h_update_sta(struct ieee80211_vif *vif, + struct ieee80211_hdr *hdr, + struct ieee80211_rx_status *rx_status) +{ + struct ieee80211_sta *sta; + struct mm81x_sta *msta; + u8 *lookup = ieee80211_is_s1g_beacon(hdr->frame_control) ? hdr->addr1 : + hdr->addr2; + + lockdep_assert_in_rcu_read_lock(); + + sta = ieee80211_find_sta(vif, lookup); + if (!sta) + return; + + msta = (void *)sta->drv_priv; + if (msta->avg_rssi) { + msta->avg_rssi = + CALC_AVG_RSSI(msta->avg_rssi, rx_status->signal); + } else { + msta->avg_rssi = rx_status->signal; + } +} + +static struct ieee80211_vif * +mm81x_rx_h_skb_get_vif(struct mm81x *mors, struct sk_buff *skb, + struct mm81x_skb_rx_status *hdr_rx_status) +{ + u8 vif_id = u32_get_bits(le32_to_cpu(hdr_rx_status->flags), + MM81X_RX_STATUS_FLAGS_VIF_ID); + + lockdep_assert_in_rcu_read_lock(); + + if (vif_id == INVALID_VIF_INDEX) + return NULL; + + return mm81x_rcu_dereference_vif_id(mors, vif_id, true); +} + +void mm81x_mac_rx_skb(struct mm81x *mors, struct sk_buff *skb, + struct mm81x_skb_rx_status *hdr_rx_status) +{ + struct ieee80211_vif *vif; + struct ieee80211_hw *hw = mors->hw; + struct ieee80211_rx_status rx_status; + struct ieee80211_hdr *hdr = (void *)skb->data; + + memset(&rx_status, 0, sizeof(rx_status)); + + if (!mors->started || !skb->data || !skb->len) { + dev_kfree_skb_any(skb); + return; + } + + mm81x_rx_h_fill_status(mors, hdr_rx_status, &rx_status, skb); + + scoped_guard(rcu) { + vif = mm81x_rx_h_skb_get_vif(mors, skb, hdr_rx_status); + if (!vif) + goto rx; + + mm81x_rx_h_update_sta(vif, hdr, &rx_status); + } + +rx: + memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status)); + ieee80211_rx_ni(hw, skb); +} + +static void mm81x_mac_flush_queues(struct mm81x *mors) +{ + /* + * No need to call mm81x_skbq_stop_tx_queues as mac80211 + * has already cancelled each queue prior to calling .flush() + */ + mm81x_skbq_data_traffic_pause(mors); + + flush_work(&mors->hif_work); + flush_work(&mors->tx_stale_work); + + mm81x_hif_clear_events(mors); + mm81x_hif_flush_tx_data(mors); + mm81x_hif_flush_cmds(mors); + + /* Re-enable data, not that there will be any */ + mm81x_skbq_data_traffic_resume(mors); +} + +static bool mm81x_mac_has_tx_pending(struct mm81x *mors) +{ + struct mm81x_skbq *mgmt_q = mm81x_hif_get_tx_mgmt_queue(mors); + struct mm81x_skbq *tx_qs; + int num_qs, i; + + mm81x_hif_skbq_get_tx_qs(mors, &tx_qs, &num_qs); + for (i = 0; i < num_qs; i++) + if (mm81x_skbq_count(&tx_qs[i]) || + mm81x_skbq_pending_count(&tx_qs[i])) + return true; + + if (mm81x_skbq_count(mgmt_q) || mm81x_skbq_pending_count(mgmt_q)) + return true; + + return false; +} + +static void mm81x_mac_wait_queues(struct mm81x *mors) +{ + if (!wait_event_timeout(mors->tx_empty_waitq, + !mm81x_mac_has_tx_pending(mors), + MM81X_FLUSH_TIMEOUT)) + dev_warn(mors->dev, "Unable to empty queues before timeout"); +} + +static void mm81x_mac_ops_flush(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u32 queues, + bool drop) +{ + struct mm81x *mors = hw->priv; + + /* We don't support IEEE80211_HW_QUEUE_CONTROL so flush all queues */ + if (drop) + mm81x_mac_flush_queues(mors); + else + mm81x_mac_wait_queues(mors); +} + +static int mm81x_mac_ops_set_rts_threshold(struct ieee80211_hw *hw, + int radio_idx, u32 value) +{ + struct mm81x *mors = hw->priv; + + mors->rts_threshold = value; + return 0; +} + +static void mm81x_mac_ops_sta_statistics(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct station_info *sinfo) +{ + struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv; + struct mm81x *mors = hw->priv; + const struct mmrc_table *tb = msta->rc.tb; + struct mmrc_rate rate; + + if (!tb || tb->best_tp.rate == MMRC_MCS_UNUSED) { + sinfo->filled &= ~BIT_ULL(NL80211_STA_INFO_TX_BITRATE); + return; + } + + rate = tb->best_tp; + sinfo->txrate.mcs = rate.rate; + sinfo->txrate.nss = NSS_IDX_TO_NSS(rate.ss); + sinfo->txrate.flags = RATE_INFO_FLAGS_S1G_MCS; + switch (rate.bw) { + case MMRC_BW_1MHZ: + sinfo->txrate.bw = RATE_INFO_BW_1; + break; + case MMRC_BW_2MHZ: + sinfo->txrate.bw = RATE_INFO_BW_2; + break; + case MMRC_BW_4MHZ: + sinfo->txrate.bw = RATE_INFO_BW_4; + break; + case MMRC_BW_8MHZ: + sinfo->txrate.bw = RATE_INFO_BW_8; + break; + default: + break; + } + + if (rate.guard == MMRC_GUARD_SHORT) + sinfo->txrate.flags |= (RATE_INFO_FLAGS_SHORT_GI); + + dev_dbg(mors->dev, "mcs: %d, bw: %d, flag: 0x%x", rate.rate, rate.bw, + sinfo->txrate.flags); + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE); +} + +static u32 mm81x_get_expected_throughput(struct ieee80211_hw *hw, + struct ieee80211_sta *sta) +{ + struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv; + struct mm81x *mors = hw->priv; + const struct mmrc_table *tb = msta->rc.tb; + struct mmrc_rate rate; + u32 tput; + + if (!tb || tb->best_tp.rate == MMRC_MCS_UNUSED) + return 0; + + rate = tb->best_tp; + tput = BPS_TO_KBPS(mmrc_calculate_theoretical_throughput(rate)); + dev_dbg(mors->dev, "Throughput: MCS: %d, BW: %d, GI: %d -> %u", + rate.rate, 1 << rate.bw, rate.guard, tput); + + return tput; +} + +static void mm81x_mac_restart_cleanup_iter(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + if (vif->type == NL80211_IFTYPE_AP) + mm81x_mac_beacon_finish((struct mm81x_vif *)vif->drv_priv); +} + +static void mm81x_mac_restart_cleanup(struct mm81x *mors) +{ + ieee80211_iterate_active_interfaces(mors->hw, + IEEE80211_IFACE_ITER_NORMAL, + mm81x_mac_restart_cleanup_iter, + NULL); + mm81x_mac_hw_scan_finish(mors); +} + +static int mm81x_mac_restart(struct mm81x *mors) +{ + int ret; + u32 chip_id; + + mors->started = false; + mm81x_ps_disable(mors); + mm81x_bus_set_irq(mors, false); + mm81x_hw_irq_clear(mors); + ieee80211_stop_queues(mors->hw); + + set_bit(MM81X_STATE_DATA_TX_STOPPED, &mors->state_flags); + set_bit(MM81X_STATE_DATA_QS_STOPPED, &mors->state_flags); + + /* Allow time for in-transit tx/rx packets to settle */ + mdelay(MM81X_HW_RESTART_DELAY_MS); + flush_work(&mors->hif_work); + flush_work(&mors->tx_stale_work); + mm81x_hif_clear_events(mors); + mm81x_hif_flush_tx_data(mors); + mm81x_hif_flush_cmds(mors); + + mm81x_claim_bus(mors); + ret = mm81x_reg32_read(mors, MM81X_REG_CHIP_ID(mors), &chip_id); + mm81x_release_bus(mors); + + if (ret < 0) { + dev_err(mors->dev, "Failed to access HW: %d", ret); + goto exit; + } + + mm81x_mac_restart_cleanup(mors); + + ret = mm81x_fw_init(mors, true); + if (ret < 0) { + dev_err(mors->dev, "Failed to init firmware: %d", ret); + goto exit; + } + + mm81x_hw_irq_enable(mors, MM81X_INT_HW_STOP_NOTIFICATION_NUM, true); + + ret = mm81x_fw_parse_ext_host_tbl(mors); + if (ret) { + dev_err(mors->dev, "failed to parse extended host table: %d", + ret); + goto exit; + } + + mm81x_mac_caps_init(mors); + + mm81x_bus_set_irq(mors, true); + clear_bit(MM81X_STATE_DATA_TX_STOPPED, &mors->state_flags); + clear_bit(MM81X_STATE_DATA_QS_STOPPED, &mors->state_flags); + clear_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags); + clear_bit(MM81X_STATE_RELOAD_FW_AFTER_START, &mors->state_flags); + mm81x_mac_check_fw_disabled_chans(mors->hw); + ieee80211_restart_hw(mors->hw); + +exit: + mm81x_ps_enable(mors); + return ret; +} + +static int mm81x_mac_ops_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + int ret = 0; + struct mm81x *mors = hw->priv; + struct mm81x_vif *mors_vif = (struct mm81x_vif *)vif->drv_priv; + + if (test_bit(MM81X_STATE_RELOAD_FW_AFTER_START, &mors->state_flags)) { + dev_info(mors->dev, "Restarting chip with regdom: %s", + mors->country); + + ret = mm81x_mac_restart(mors); + if (ret) { + dev_err(mors->dev, "Failed to restart chip"); + return ret; + } + + /* + * mac_restart will trigger ieee80211_hw_restart and + * add_interface will re-enter. just exit here instead. + */ + return 0; + } + + vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER; + mors_vif->mors = mors; + + ret = mm81x_cmd_add_if(mors, &mors_vif->id, vif->addr, vif->type); + if (ret) { + dev_err(mors->dev, "mm81x_cmd_add_if failed %d", ret); + return ret; + } + + if (mors_vif->id >= ARRAY_SIZE(mors->vifs)) { + dev_err(mors->dev, "vif_id is too large %u", mors_vif->id); + ret = -EOPNOTSUPP; + return ret; + } + + if (mors_vif->id != (mors_vif->id & MM81X_TX_CONF_FLAGS_VIF_ID_MASK)) { + dev_err(mors->dev, "invalid vif_id %u", mors_vif->id); + ret = -EOPNOTSUPP; + return ret; + } + + rcu_assign_pointer(mors->vifs[mors_vif->id], vif); + + if (vif->type == NL80211_IFTYPE_AP) + mm81x_mac_beacon_init(mors_vif); + + ret = mm81x_cmd_get_capabilities(mors, mors_vif->id, &mors->fw_caps); + if (ret) { + dev_err(mors->dev, + "mm81x_cmd_get_capabilities failed for vif %d", + mors_vif->id); + return ret; + } + + ieee80211_wake_queues(mors->hw); + return ret; +} + +static const struct ieee80211_ops mm81x_ops = { + .start = mm81x_mac_ops_start, + .stop = mm81x_mac_ops_stop, + .config = mm81x_mac_ops_config, + .wake_tx_queue = ieee80211_handle_wake_tx_queue, + .tx = mm81x_mac_ops_tx, + .add_interface = mm81x_mac_ops_add_interface, + .remove_interface = mm81x_mac_ops_remove_interface, + .configure_filter = mm81x_mac_ops_configure_filter, + .sta_state = mm81x_mac_ops_sta_state, + .flush = mm81x_mac_ops_flush, + .set_frag_threshold = mm81x_mac_set_frag_threshold, + .set_rts_threshold = mm81x_mac_ops_set_rts_threshold, + .sta_statistics = mm81x_mac_ops_sta_statistics, + .get_expected_throughput = mm81x_get_expected_throughput, + .hw_scan = mm81x_mac_ops_hw_scan, + .cancel_hw_scan = mm81x_mac_ops_cancel_hw_scan, + .get_txpower = mm81x_mac_ops_get_txpower, + .bss_info_changed = mm81x_mac_ops_bss_info_changed, + .prepare_multicast = mm81x_mac_ops_prepare_multicast, + .conf_tx = mm81x_mac_ops_conf_tx, + .ampdu_action = mm81x_mac_ops_ampdu_action, + .set_key = mm81x_mac_ops_set_key, + .add_chanctx = ieee80211_emulate_add_chanctx, + .remove_chanctx = ieee80211_emulate_remove_chanctx, + .change_chanctx = ieee80211_emulate_change_chanctx, + .switch_vif_chanctx = ieee80211_emulate_switch_vif_chanctx, +}; + +static void mm81x_reg_notifier(struct wiphy *wiphy, + struct regulatory_request *request) +{ + int ret; + struct mm81x *mors = wiphy_to_ieee80211_hw(wiphy)->priv; + + if (mm81x_reg_h_cc_equal(request->alpha2, "00") || + mm81x_reg_h_cc_equal(request->alpha2, mors->country)) + return; + + memcpy(mors->country, request->alpha2, sizeof(mors->country)); + + ret = mm81x_mac_restart(mors); + if (ret) + dev_err(mors->dev, "Failed to restart chip: %d", ret); +} + +static void mm81x_mac_config_hw(struct mm81x *mors) +{ + int i; + struct ieee80211_hw *hw = mors->hw; + struct wiphy *wiphy; + + for (i = 0; i < NUM_NL80211_BANDS; i++) + hw->wiphy->bands[i] = NULL; + + hw->wiphy->bands[NL80211_BAND_S1GHZ] = &mors_band_s1ghz; + hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_STATION); + hw->wiphy->reg_notifier = mm81x_reg_notifier; + hw->queues = MM81X_HW_QUEUE_COUNT; + hw->max_rates = MM81X_HW_MAX_RATES; + hw->max_report_rates = MM81X_HW_MAX_REPORT_RATES; + hw->max_rate_tries = MM81X_HW_MAX_RATE_TRIES; + hw->tx_sk_pacing_shift = MM81X_HW_TX_SK_PACING_SHIFT; + hw->vif_data_size = sizeof(struct mm81x_vif); + hw->sta_data_size = sizeof(struct mm81x_sta); + hw->extra_tx_headroom = + sizeof(struct mm81x_skb_hdr) + mm81x_bus_get_alignment(mors); + + mors->wiphy = hw->wiphy; + + ieee80211_hw_set(hw, SIGNAL_DBM); + ieee80211_hw_set(hw, MFP_CAPABLE); + ieee80211_hw_set(hw, REPORTS_TX_ACK_STATUS); + ieee80211_hw_set(hw, AMPDU_AGGREGATION); + ieee80211_hw_set(hw, HOST_BROADCAST_PS_BUFFERING); + ieee80211_hw_set(hw, HAS_RATE_CONTROL); + ieee80211_hw_set(hw, SUPPORTS_PS); + ieee80211_hw_set(hw, NEED_DTIM_BEFORE_ASSOC); + ieee80211_hw_set(hw, PS_NULLFUNC_STACK); + ieee80211_hw_set(hw, SUPPORTS_TX_FRAG); + ieee80211_hw_set(hw, SUPPORTS_NDP_BLOCKACK); + + SET_IEEE80211_PERM_ADDR(hw, mors->macaddr); + + wiphy = mors->wiphy; + + wiphy->flags |= WIPHY_FLAG_AP_UAPSD; + wiphy->flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT; + + if (!mors->ps.enable) + wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; + + wiphy->features |= NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE | + NL80211_FEATURE_TX_POWER_INSERTION; + + wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_AIRTIME_FAIRNESS); + wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SET_SCAN_DWELL); + + wiphy->iface_combinations = mors_if_combs; + wiphy->n_iface_combinations = ARRAY_SIZE(mors_if_combs); + wiphy->max_scan_ie_len = MM81X_MAX_SCAN_IE_LEN; + wiphy->max_scan_ssids = MM81X_MAX_SCAN_SSIDS; + wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; + wiphy->max_remain_on_channel_duration = + MM81X_MAX_REMAIN_ON_CHAN_DURATION; +} + +static void mm81x_stale_tx_status_timer(struct timer_list *t) +{ + struct mm81x *mors = timer_container_of(mors, t, stale_status.timer); + + spin_lock_bh(&mors->stale_status.lock); + if (mm81x_hif_get_tx_status_pending_count(mors)) + queue_work(mors->net_wq, &mors->tx_stale_work); + spin_unlock_bh(&mors->stale_status.lock); +} + +static void mm81x_stale_tx_status_timer_finish(struct mm81x *mors) +{ + timer_delete_sync_try(&mors->stale_status.timer); +} + +static void mm81x_mac_stale_tx_status_timer_init(struct mm81x *mors) +{ + spin_lock_init(&mors->stale_status.lock); + timer_setup(&mors->stale_status.timer, mm81x_stale_tx_status_timer, 0); +} + +int mm81x_mac_register(struct mm81x *mors) +{ + int ret; + struct ieee80211_hw *hw = mors->hw; + + mors->tx_power_mbm = INT_MAX; + mors->tx_max_power_mbm = INT_MAX; + mors->rts_threshold = IEEE80211_MAX_RTS_THRESHOLD; + + ret = mm81x_ps_init(mors); + if (ret) + return ret; + + mm81x_mac_config_hw(mors); + mm81x_mac_hw_scan_init(mors); + mm81x_mac_stale_tx_status_timer_init(mors); + + ret = ieee80211_register_hw(hw); + if (ret) { + dev_err(mors->dev, "ieee80211_register_hw failed %d", ret); + mm81x_mac_unregister(mors); + return ret; + } + + mm81x_rc_init(mors); + + /* + * At this stage, we know bus and pager system interrupts are enabled. + * Trigger the receive workqueue to drain any incoming chip-to-host + * pending packets been pushed in the period between the firmware + * initialization and interrupts being enabled. + */ + set_bit(MM81X_HIF_EVT_RX_PEND, &mors->hif.event_flags); + queue_work(mors->chip_wq, &mors->hif_work); + + return ret; +} + +void mm81x_mac_unregister(struct mm81x *mors) +{ + mm81x_ps_disable(mors); + mm81x_rc_deinit(mors); + mm81x_mac_hw_scan_destroy(mors); + + ieee80211_stop_queues(mors->hw); + ieee80211_unregister_hw(mors->hw); + + mm81x_hif_flush_tx_data(mors); + mm81x_hif_flush_cmds(mors); + mm81x_stale_tx_status_timer_finish(mors); + mm81x_ps_finish(mors); + + kfree(mors->mcast_filter); +} + +struct mm81x *mm81x_mac_alloc(size_t priv_size, struct device *dev) +{ + struct ieee80211_hw *hw; + struct mm81x *mors; + + hw = ieee80211_alloc_hw(sizeof(*mors) + priv_size, &mm81x_ops); + if (!hw) { + dev_err(dev, "ieee80211_alloc_hw failed\r\n"); + return NULL; + } + + SET_IEEE80211_DEV(hw, dev); + memset(hw->priv, 0, sizeof(*mors)); + + mors = hw->priv; + mors->hw = hw; + mors->dev = dev; + mutex_init(&mors->cmd_lock); + mutex_init(&mors->cmd_wait); + init_waitqueue_head(&mors->tx_empty_waitq); + + return mors; +} + +void mm81x_mac_free(struct mm81x *mors) +{ + ieee80211_free_hw(mors->hw); +} diff --git a/drivers/net/wireless/morsemicro/mm81x/mac.h b/drivers/net/wireless/morsemicro/mm81x/mac.h new file mode 100644 index 000000000000..c540471f274e --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/mac.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_MAC_H_ +#define _MM81X_MAC_H_ + +#include "core.h" +#include "command.h" + +struct mm81x_queue_params { + u8 uapsd; + u8 aci; + u8 aifs; + u16 cw_min; + u16 cw_max; + u32 txop; +}; + +static inline u32 mm81x_vif_generate_cssid(struct ieee80211_vif *vif) +{ + return mm81x_generate_cssid(vif->cfg.ssid, vif->cfg.ssid_len); +} + +/* + * Build a little-endian word from the last four octets of a MAC address; + * the first two octets are dropped. + */ +static inline __le32 mac2le32(const unsigned char *addr) +{ + return cpu_to_le32(((u32)(addr[2]) << 24) | ((u32)(addr[3]) << 16) | + ((u32)(addr[4]) << 8) | ((u32)(addr[5]))); +} + +static inline struct ieee80211_vif * +mm81x_rcu_dereference_vif_id(struct mm81x *mors, u8 vif_id, bool rcu) +{ + if (WARN_ON(vif_id >= ARRAY_SIZE(mors->vifs))) + return NULL; + + if (rcu) + return rcu_dereference(mors->vifs[vif_id]); + + return rcu_dereference_protected(mors->vifs[vif_id], + lockdep_is_held(&mors->hw->wiphy->mtx)); +} + +int mm81x_tx_h_get_attempts(struct mm81x *mors, + struct mm81x_skb_tx_status *tx_sts); +struct mm81x *mm81x_mac_alloc(size_t priv_size, struct device *dev); +int mm81x_mac_register(struct mm81x *mors); +void mm81x_mac_free(struct mm81x *mors); +void mm81x_mac_unregister(struct mm81x *mors); +int mm81x_mac_event_recv(struct mm81x *mors, struct sk_buff *skb); +void mm81x_mac_rx_skb(struct mm81x *mors, struct sk_buff *skb, + struct mm81x_skb_rx_status *hdr_rx_status); +void mm81x_mac_beacon_irq_handle(struct mm81x *mors, u32 status); + +u8 *mm81x_hw_scan_h_insert_tlvs(struct mm81x_hw_scan_params *params, u8 *buf); +size_t mm81x_hw_scan_h_get_cmd_size(struct mm81x_hw_scan_params *params); +void mm81x_tx_h_check_aggr(struct ieee80211_sta *pubsta, struct sk_buff *skb); +#endif /* !_MM81X_MAC_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/mmrc.c b/drivers/net/wireless/morsemicro/mm81x/mmrc.c new file mode 100644 index 000000000000..fe7e4f501d6c --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/mmrc.c @@ -0,0 +1,1354 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include "mmrc.h" + +/* + * The default packet size in bits used for calculated throughput of a given + * rate + */ +#define DEFAULT_PACKET_SIZE_BITS 9600 + +/* + * The default packet size in bytes used for calculating retries for a given + * rate + */ +#define DEFAULT_PACKET_SIZE_BYTES 1200 + +/* The sample frequencies at different stages */ +#define LOOKAROUND_RATE_INIT 5 +#define LOOKAROUND_RATE_NORMAL 50 +#define LOOKAROUND_RATE_STABLE 100 + +/* The thresholds for stability stages */ +#define STABILITY_CNT_THRESHOLD_INIT 20 +#define STABILITY_CNT_THRESHOLD_NORMAL 50 +#define STABILITY_CNT_THRESHOLD_STABLE 100 + +/* The backoff step size for the counter */ +#define STABILITY_BACKOFF_STEP 2 + +/* + * The packet success threshold for attempting slower lookaround rates + */ +/* + * Force a look around if there haven't been any for this number of cycles + */ +#define LOOKAROUND_MAX_RC_CYCLES 5 + +/* + * Number of attempts for each lookaround rate within at most two RC cycles + * if there are enough packets + */ +#define LOOKAROUND_RATE_ATTEMPTS 4 + +/* + * Limit the number of times we try to pick a theoretically better rate to + * sample. Necessary so we don't stall the CPU, due to constantly picking worse + * rates. + */ +#define LOOKAROUND_FAIL_MAX 200 + +/* + * Initial and reset probability per rate in the table + * Changing this value will have a severe implication on the current heuristic + * It could mean that some rates will have better probability throughput even + * with no edivence and so will cause unexpected changes in the rate table + */ +#define RATE_INIT_PROBABILITY 0 + +/* + * The lowest number of MPDUs within acknowledged AMPDUs that can be used for + * rate stats + */ +#define AMPDU_STATS_MIN 2 + +/* + * The lowest number of stats to be used for processing in NORMAL lookaround + * mode + */ +#define STATS_MIN_NORMAL 2 + +/* + * The lowest number of stats to be used for processing in INIT lookaround + * mode + */ +#define STATS_MIN_INIT 1 + +/* The lowest probability value considered for recognising a dip */ +#define PROBABILITY_DIP_MIN 20 + +/* The lowest probability value for recovering from a dip */ +#define PROBABILITY_DIP_RECOVERY_MIN 40 + +/* + * The time cap on rate allocation for multiple attempts. If a single attempt + * exceeds this window, no additional attempts will be generated + */ +#define MAX_WINDOW_ATTEMPT_TIME 4000 + +/* The time window for all rates in rate table */ +#define RATE_WINDOW_MICROSECONDS 24000 + +/* + * EWMA is the alpha coefficient in the exponential weighting moving average + * filter used for probability updates. + * + * Y[n] = X[n] * (100 - EWMA) + (Y[n-1] * EWMA) + * ------------------------------------- + * 100 + * + */ +#define EWMA 75 + +/* + * Evidence scaling to allow for one decimal place. Needed for low + * throughput, otherwise the history decays in a single cycle. + */ +#define EVIDENCE_SCALE 5 + +/* + * Evidence maximum to ensure history doesn't decay too slowly when + * there is a lot of historical data. + */ +#define EVIDENCE_MAX 100 + +/* + * This fixed point conversion multiplies a value by one and shifts it + * accordingly to account for the fixed point shifting at the return of a + * function + */ +#define FP_8_MULT_1 256 + +/* Fixed point conversion for 2.1 * 2^8 used for 4MHz symbol multiplication */ +#define FP_8_4MHZ 537 + +/* Fixed point conversion for 4.5 * 2^8 used for 8MHz symbol multiplication */ +#define FP_8_8MHZ 1152 + +/* Fixed point conversion for 9.0 * 2^8 used for 16MHz symbol multiplication */ +#define FP_8_16MHZ 2301 + +/* + * Fixed point conversion for 3.6 * 2^8 used for long guard symbol tx time + * multiplication + */ +#define FP_8_LONG_GUARD_SYMBOL_TIME 1024 + +/* + * Fixed point conversion for 4.0 * 2^8 used for short guard symbol tx time + * multiplication + */ +#define FP_8_SHORT_GUARD_SYMBOL_TIME 921 + +/* + * Shift value to shift back our FP conversions + */ +#define FP_8_SHIFT 8 + +/* + * Limit to count of consecutive variations in one direction + */ +#define MAX_VARIATION_DIRECTION 5 + +/* + * Threshold for considering consecutive variation direction as variation + * or not + */ +#define VARIATION_DIRECTION_THRESHOLD 3 + +/* EWMA percentage value for averaging the best rate probability variation */ +#define VARIATION_EWMA 95 + +/* Percentage variation regarded as minor */ +#define MINOR_VARIATION_THRESHOLD 1 + +/* Percentage variation regarded as moderate */ +#define MODERATE_VARIATION_THRESHOLD 3 + +/* Percentage variation regarded as significant */ +#define SIGNIFICANT_VARIATION_THRESHOLD 5 + +/* If the best rate changes twice in this number of cycles, it is unstable */ +#define BEST_RATE_UNSTABLE_THRESHOLD 4 + +/* + * Once the best rate is unchanged for this number of cycles it has + * converged + */ +#define BEST_RATE_CONVERGED_THRESHOLD 10 + +/* RSSI threshold for short range */ +#define MMRC_SHORT_RANGE_RSSI_LIMIT -70 + +/* RSSI threshold for mid range */ +#define MMRC_MID_RANGE_RSSI_LIMIT -85 + +#define MMRC_MAX_BW(bw_caps) \ + (((bw_caps) & BIT(MMRC_BW_16MHZ)) ? MMRC_BW_16MHZ : \ + ((bw_caps) & BIT(MMRC_BW_8MHZ)) ? MMRC_BW_8MHZ : \ + ((bw_caps) & BIT(MMRC_BW_4MHZ)) ? MMRC_BW_4MHZ : \ + ((bw_caps) & BIT(MMRC_BW_2MHZ)) ? MMRC_BW_2MHZ : \ + MMRC_BW_1MHZ) + +/* + * This table stores the number of bits per symbols used for MCS0-MCS9 based + * on 20MHz and 1SS + */ +static const u32 sym_table[10] = { 24, 36, 48, 72, 96, 144, 192, 216, 256, 288 }; + +/* + * Calculate which bit is the nth bit set in an integer based flag. + */ +static u8 nth_bit(u16 in, u16 index) +{ + u32 i; + u8 count = 0; + + for (i = 0; count != index + 1; i++) { + if (((1u << i) & in) != 0) + count++; + } + + return i - 1; +} + +/* + * Calculate the input bit's index among all the set bits in an integer + * based flag. + */ +static u16 bit_index(u16 in, u32 bit_pos) +{ + u16 i; + u16 index = 0; + + for (i = 0; i != bit_pos + 1; i++) { + if (((1u << i) & in) != 0) + index++; + } + + if (index == 0) { + /* Could not match bit pos to caps */ + return 0; + } + + return index - 1; +} + +static u16 rows_from_sta_caps(struct mmrc_sta_capabilities *caps) +{ + u16 rows = 0; + u8 n_rates = hweight_long(caps->rates); + + /* Taking MCS10 into account as it is relevant for 1 MHz entries */ + if (caps->rates & BIT(MMRC_MCS10)) { + n_rates -= 1; + rows = 2; + } + + rows += (hweight_long(caps->bandwidth) * n_rates * + hweight_long(caps->guard) * + hweight_long(caps->spatial_streams)); + + return rows; +} + +static void rate_update_index(struct mmrc_table *tb, struct mmrc_rate *rate) +{ + u16 index = 0; + /* Information about our rates */ + u16 bw = hweight_long(tb->caps.bandwidth); + u16 streams = hweight_long(tb->caps.spatial_streams); + u16 guard = hweight_long(tb->caps.guard); + u16 rows = rows_from_sta_caps(&tb->caps); + + index = bit_index(tb->caps.guard, rate->guard) + + bit_index(tb->caps.bandwidth, rate->bw) * guard + + bit_index(tb->caps.spatial_streams, rate->ss) * guard * bw + + bit_index(tb->caps.rates, rate->rate) * bw * streams * guard; + + if (index >= rows) + index = 0; + + rate->index = index; +} + +static struct mmrc_rate get_rate_row(struct mmrc_table *tb, u16 index) +{ + struct mmrc_rate rate; + u16 ss_index; + + /* Information about our rates */ + u16 mcs = hweight_long(tb->caps.rates); + u16 bw = hweight_long(tb->caps.bandwidth); + u16 streams = hweight_long(tb->caps.spatial_streams); + u16 guard = hweight_long(tb->caps.guard); + u16 total_caps = mcs * bw * streams * guard; + + /* Find our MCS */ + u16 rows = total_caps / mcs; + u16 mcs_index = index / rows; + u16 mcs_modulo = index % rows; + + mcs = nth_bit(tb->caps.rates, mcs_index); + + /* Find our spatial stream */ + rows = rows / streams; + streams = nth_bit(tb->caps.spatial_streams, mcs_modulo / rows); + + /* Find our bandwidth */ + ss_index = index % rows; + rows = rows / bw; + bw = nth_bit(tb->caps.bandwidth, ss_index / rows); + + /* Find our guard */ + guard = nth_bit(tb->caps.guard, index % guard); + + /* Add range checks to keep scan-build happy */ + if (bw >= MMRC_BW_MAX) + bw = MMRC_BW_1MHZ; + + if (guard >= MMRC_GUARD_MAX) + guard = MMRC_GUARD_LONG; + + /* Validate guard against capability */ + if (guard == MMRC_GUARD_SHORT && + !(tb->caps.sgi_per_bw & SGI_PER_BW(bw))) + guard = MMRC_GUARD_LONG; + + /* Create our rate row and send it */ + rate.bw = MMRC_BW_TO_BITFIELD(bw); + rate.ss = MMRC_SS_TO_BITFIELD(streams); + rate.rate = MMRC_RATE_TO_BITFIELD(mcs); + rate.guard = MMRC_GUARD_TO_BITFIELD(guard); + rate.attempts = 0; + rate.flags = 0; + + /* Update index as bw or guard may have changed */ + rate_update_index(tb, &rate); + + return rate; +} + +size_t mmrc_memory_required_for_caps(struct mmrc_sta_capabilities *caps) +{ + return sizeof(struct mmrc_table) + + rows_from_sta_caps(caps) * sizeof(struct mmrc_stats_table); +} + +static u32 calculate_bits_per_symbol(struct mmrc_rate *rate) +{ + u32 bps; + + /* If MCS10 is selected we return 2*MCS0 Symbols */ + if (rate->rate == MMRC_MCS10) + return 6; + + /* Confirm that the rate is valid for the sym_table lookup */ + if (rate->rate >= MMRC_MCS_UNUSED) { + pr_err("%s: Invalid MCS rate %d for sym_table lookup\n", + __func__, rate->rate); + return 1; + } + + /* + * Coversion from 20MHz as in sym_table to: + * 40MHz == x 2.1 + * 80MHz == x 4.5 + * 160MHz == x 9.0 + */ + bps = sym_table[rate->rate]; + switch (rate->bw) { + case (MMRC_BW_4MHZ): + bps *= FP_8_4MHZ; + break; + case (MMRC_BW_8MHZ): + bps *= FP_8_8MHZ; + break; + case (MMRC_BW_16MHZ): + bps *= FP_8_16MHZ; + break; + case (MMRC_BW_1MHZ): + bps = sym_table[rate->rate] * 24 / 52; + bps *= FP_8_MULT_1; + break; + case (MMRC_BW_2MHZ): + case (MMRC_BW_MAX): + default: + bps *= FP_8_MULT_1; + break; + } + /* SS + 1 because mmrc_spatial_stream starts at 0 */ + return ((rate->ss + 1) * bps) >> FP_8_SHIFT; +} + +static u32 get_tx_time(struct mmrc_rate *rate) +{ + u32 tx = 0; + u32 n_sym; + u32 avg_bits; + + /* Calculate tx time based on a default packet size */ + avg_bits = DEFAULT_PACKET_SIZE_BITS; + + /* Number of bits per symbol for this rate */ + n_sym = calculate_bits_per_symbol(rate); + + /* In case of bad calcuation/parameter use lowest value */ + n_sym = n_sym == 0 ? sym_table[0] : n_sym; + + /* number of symbols in default packet size */ + n_sym = avg_bits / n_sym; + + /* tx is time to transmit average packet in us */ + switch (rate->guard) { + case (MMRC_GUARD_LONG): + tx = n_sym * FP_8_LONG_GUARD_SYMBOL_TIME; + break; + case (MMRC_GUARD_SHORT): + tx = n_sym * FP_8_SHORT_GUARD_SYMBOL_TIME; + break; + default: + return 0; + } + + return (tx * 10) >> FP_8_SHIFT; +} + +u32 mmrc_calculate_theoretical_throughput(struct mmrc_rate rate) +{ + static const u32 s1g_tpt_lgi[4][11] = { + { 300, 600, 900, 1200, 1800, 2400, 2700, 3000, 3600, 4000, + 150 }, + { 650, 1300, 1950, 2600, 3900, 5200, 5850, 6500, 7800, 0, 0 }, + { 1350, 2700, 4050, 5400, 8100, 10800, 12150, 13500, 16200, + 18000, 0 }, + { 2925, 5850, 8775, 11700, 17550, 23400, 26325, 29250, 35100, + 39000, 0 }, + }; + + static const u32 s1g_tpt_sgi[4][11] = { + { 333, 666, 1000, 1333, 2000, 2666, 3000, 3333, 4000, 4444, + 166 }, + { 722, 1444, 2166, 2888, 4333, 5777, 6500, 7222, 8666, 0, 0 }, + { 1500, 3000, 4500, 6000, 9000, 12000, 13500, 15000, 18000, + 20000, 0 }, + { 3250, 6500, 9750, 13000, 19500, 26000, 29250, 32500, 39000, + 43333, 0 }, + }; + + if (rate.guard) + return s1g_tpt_sgi[rate.bw][rate.rate] * 1000 * (rate.ss + 1); + + return s1g_tpt_lgi[rate.bw][rate.rate] * 1000 * (rate.ss + 1); +} + +static u32 calculate_throughput(struct mmrc_table *tb, u8 index) +{ + struct mmrc_rate rate = get_rate_row(tb, index); + + /* + * Avoid the overflow (observed for 8MHz MCS9 rate: 43333) by dividing + * first before multiplying. Should not experience any loss of + * precision as the throughput is already multiplied by 1000 in + * mmrc_calculate_theoretical_throughput (returned as bits/sec) + */ + if (tb->table[rate.index].prob < 10) + return 0; + else if (rate.index == tb->best_tp.index && tb->interference_likely) + /* + * Assist the best rate by increasing the probability by the + * averaged variation + */ + return (mmrc_calculate_theoretical_throughput(rate) / 100) * + (tb->table[rate.index].prob + tb->probability_variation); + else + return (mmrc_calculate_theoretical_throughput(rate) / 100) * + tb->table[rate.index].prob; +} + +static bool validate_rate(struct mmrc_table *tb, struct mmrc_rate *rate) +{ + if (rate->rate == MMRC_MCS10 && + (rate->bw != MMRC_BW_1MHZ || rate->ss != MMRC_SPATIAL_STREAM_1)) { + /* + * 802.11ah does not support MCS10 with BW that is not 1MHz or + * not 1 spatial stream. + */ + return false; + } + + if (rate->rate == MMRC_MCS9 && rate->bw == MMRC_BW_2MHZ && + rate->ss != MMRC_SPATIAL_STREAM_3) { + /* + * 802.11ah does not support MCS9 at 2MHz for 1, 2 or 4 spatial + * streams + */ + return false; + } + + if (rate->guard == MMRC_GUARD_SHORT && + !(tb->caps.sgi_per_bw & SGI_PER_BW(rate->bw))) + return false; + + return true; +} + +static u16 find_baseline_index(struct mmrc_table *tb) +{ + u32 i, theoretical_tp, min_theoretical_tp; + u16 row_count = rows_from_sta_caps(&tb->caps); + u16 min_theoretical_tp_index = 0; + struct mmrc_rate rate; + + if (tb->caps.rates & BIT(MMRC_MCS10)) + return 0; + + min_theoretical_tp = + mmrc_calculate_theoretical_throughput(get_rate_row(tb, 0)); + for (i = 0; i < row_count; i++) { + rate = get_rate_row(tb, i); + if (!validate_rate(tb, &rate)) + continue; + + theoretical_tp = mmrc_calculate_theoretical_throughput(rate); + if (min_theoretical_tp > theoretical_tp) { + min_theoretical_tp = theoretical_tp; + min_theoretical_tp_index = rate.index; + } + } + + return min_theoretical_tp_index; +} + +/* + * Fill out the remaining rates to be used once the best rate is selected. + * Normally the retry rates are one MCS lower than the previous, however in + * unconverged mode we limit the 3 respective retry rates to MCS 4, 2 and 0 + * respectively. The last retry rate is always MCS 0 + */ +static void mmrc_fill_retry_rates(struct mmrc_table *tb) +{ + tb->second_tp = tb->best_tp; + if (tb->second_tp.rate != MMRC_MCS0) { + tb->second_tp.rate--; + if (tb->unconverged && tb->second_tp.rate > MMRC_MCS4) + tb->second_tp.rate = MMRC_MCS4; + rate_update_index(tb, &tb->second_tp); + } else if (tb->second_tp.bw > MMRC_BW_1MHZ) { + tb->second_tp.bw--; + rate_update_index(tb, &tb->second_tp); + } + + tb->best_prob = tb->second_tp; + if (tb->best_prob.rate != MMRC_MCS0) { + tb->best_prob.rate--; + if (tb->unconverged && tb->best_prob.rate > MMRC_MCS2) + tb->best_prob.rate = MMRC_MCS2; + rate_update_index(tb, &tb->best_prob); + } else if (tb->best_prob.bw > MMRC_BW_1MHZ) { + tb->best_prob.bw--; + rate_update_index(tb, &tb->best_prob); + } + + tb->baseline = tb->best_prob; + if (tb->baseline.rate != MMRC_MCS0) { + tb->baseline.rate = MMRC_MCS0; + rate_update_index(tb, &tb->baseline); + } else if (tb->baseline.bw > MMRC_BW_1MHZ) { + tb->baseline.bw--; + rate_update_index(tb, &tb->baseline); + } +} + +/* + * Updates the mmrc_table with the appropriate rate priority based on the + * latest update statistics + */ +static void generate_table_priority(struct mmrc_table *tb, u32 new_stats) +{ + u16 i; + u16 best_row = tb->best_tp.index; + u16 prev_best_row = best_row; + u8 prev_best_rate = tb->best_tp.rate; + u16 second_best_row = tb->second_tp.index; + u32 best_tp = calculate_throughput(tb, best_row); + u32 second_best_tp = calculate_throughput(tb, second_best_row); + u32 last_nonzero_prob = 0; + struct mmrc_rate tmp; + u32 tmp_tp; + + /* Use fixed rate if set */ + if (tb->fixed_rate.rate != MMRC_MCS_UNUSED) { + tb->best_tp = tb->fixed_rate; + tb->second_tp = tb->fixed_rate; + tb->best_prob = tb->fixed_rate; + return; + } + + for (i = 0; i < rows_from_sta_caps(&tb->caps); i++) { + tmp = get_rate_row(tb, i); + if (!validate_rate(tb, &tmp)) + continue; + + if (tb->table[tmp.index].evidence == 0) + continue; + + /* + * Besides better throughput, also consider this rate better if + * lower rates had worse probability. That indicates the rate + * itself is not the problem. Only do the probability check for + * rates up to the previous best rate. + */ + tmp_tp = calculate_throughput(tb, tmp.index); + + if (tmp_tp > best_tp || + (tb->table[tmp.index].max_throughput <= + tb->table[prev_best_row].max_throughput && + tb->table[tmp.index].prob >= + PROBABILITY_DIP_RECOVERY_MIN && + tb->table[tmp.index].prob > + tb->table[last_nonzero_prob].prob)) { + second_best_row = best_row; + second_best_tp = best_tp; + + best_tp = tmp_tp; + best_row = tmp.index; + } else if (tmp_tp > second_best_tp && best_row != tmp.index) { + second_best_tp = tmp_tp; + second_best_row = tmp.index; + } + + if (tb->table[tmp.index].prob >= PROBABILITY_DIP_MIN && + tb->table[tmp.index].max_throughput >= + tb->table[last_nonzero_prob].max_throughput) + last_nonzero_prob = tmp.index; + } + + /* Only update rates and stability when there are new statistics */ + if (!new_stats) + return; + + tb->best_tp = get_rate_row(tb, best_row); + if (best_tp == 0 && tb->best_tp.rate > MMRC_MCS0) { + /* Drop one rate, as the best throughput is zero */ + tb->best_tp.rate--; + rate_update_index(tb, &tb->best_tp); + } + tb->second_tp = get_rate_row(tb, second_best_row); + mmrc_fill_retry_rates(tb); + + if (tb->best_tp.rate > MMRC_MCS1 && prev_best_row == best_row) { + /* Increase the counter when the best rate is not changed */ + tb->stability_cnt++; + } else if (tb->stability_cnt > STABILITY_BACKOFF_STEP) { + /* Back off the counter when there is a new best rate */ + tb->stability_cnt -= STABILITY_BACKOFF_STEP; + } else { + tb->stability_cnt = 0; + } + + if (prev_best_row != best_row) { + s8 latest_best_rate_diff = prev_best_rate - tb->best_tp.rate; + u8 total_abs_best_rate_diff = + abs(tb->best_rate_diff[0] + tb->best_rate_diff[1] + + latest_best_rate_diff); + + if (!tb->interference_likely) { + tb->probability_variation = 0; + if (!tb->unconverged && + tb->best_rate_cycle_count <= + BEST_RATE_UNSTABLE_THRESHOLD && + total_abs_best_rate_diff >= 2) { + /* + * Best rate has changed twice in a few cycles + * and moved at least 2 MCSs from where it was + * 3 best rate changes ago + */ + tb->unconverged = true; + tb->newly_unconverged = true; + } + } + if (tb->unconverged && !tb->newly_unconverged && + total_abs_best_rate_diff < 2) { + /* + * Best rate has been relatively stable (not moved more + * than 1 MCS after the last 3 rate changes), go back + * to converged + */ + tb->unconverged = false; + } + tb->probability_variation_direction = 0; + tb->best_rate_cycle_count = 0; + tb->best_rate_diff[0] = tb->best_rate_diff[1]; + tb->best_rate_diff[1] = latest_best_rate_diff; + } else { + tb->best_rate_cycle_count++; + if (tb->unconverged && !tb->newly_unconverged && + tb->best_rate_cycle_count >= + BEST_RATE_CONVERGED_THRESHOLD) { + /* + * Best rate has been stable for a while, go back to + * converged + */ + tb->unconverged = false; + } + } + + if (tb->newly_unconverged) + tb->newly_unconverged = false; +} + +static u32 calculate_attempt_time(struct mmrc_rate *rate, size_t size) +{ + u32 time; + + time = get_tx_time(rate); + + if (size > DEFAULT_PACKET_SIZE_BYTES) + time = (time * ((size * 1000) / DEFAULT_PACKET_SIZE_BYTES)) / + 1000; + else + time = (time * 1000) / + ((DEFAULT_PACKET_SIZE_BYTES * 1000) / size); + + return time; +} + +u32 mmrc_calculate_rate_tx_time(struct mmrc_rate *rate, size_t size) +{ + u8 i; + u32 total_time = 0; + + for (i = 0; i < rate->attempts; i++) + total_time += calculate_attempt_time(rate, size); + + return total_time; +} + +/* + * Calculates the appropriate amount of additional attempts to make based on + * packet size and theoretical throughput. + */ +static void calculate_remaining_attempts(struct mmrc_table *tb, + struct mmrc_rate_table *rate, + s32 *rem_time, size_t size) +{ + size_t i; + + if (*rem_time <= 0) + return; + + for (i = 0; i < MMRC_MAX_CHAIN_LENGTH; i++) { + u32 attempt_time; + u32 attempt; + + if (rate->rates[i].rate == MMRC_MCS_UNUSED) + break; + + /* + * The attempts for these rates were calculated in the initial + * attempt allocation + */ + if (tb->table[rate->rates[i].index].prob < 20) + continue; + + if (i == 0 && (calculate_throughput(tb, rate->rates[i].index) < + calculate_throughput(tb, tb->best_prob.index))) + continue; + + attempt_time = calculate_attempt_time(&rate->rates[i], size); + if (!attempt_time) + continue; + + attempt = (*rem_time / tb->caps.max_rates) / attempt_time; + attempt += rate->rates[i].attempts; + + rate->rates[i].attempts = MMRC_ATTEMPTS_TO_BITFIELD( + attempt > MMRC_MAX_CHAIN_ATTEMPTS ? + MMRC_MAX_CHAIN_ATTEMPTS : + attempt); + } +} + +/* Allocate initial attempts to all rates in a rate table */ +static void allocate_initial_attempts(struct mmrc_rate_table *rate, + s32 *rem_time, size_t size) +{ + u32 i; + + for (i = 0; i < MMRC_MAX_CHAIN_LENGTH; i++) { + u32 attempt_time; + + if (rate->rates[i].rate == MMRC_MCS_UNUSED) + break; + + attempt_time = calculate_attempt_time(&rate->rates[i], size); + + /* + * if the time for a single attempt is very long, lets just + * try once + */ + if (attempt_time > MAX_WINDOW_ATTEMPT_TIME) { + *rem_time -= attempt_time; + rate->rates[i].attempts = MMRC_ATTEMPTS_TO_BITFIELD(1); + } else { + *rem_time -= attempt_time * 2; + rate->rates[i].attempts = MMRC_ATTEMPTS_TO_BITFIELD(2); + } + } +} + +void mmrc_get_rates(struct mmrc_table *tb, struct mmrc_rate_table *out, + size_t size) +{ + u8 i; + u16 random_index; + struct mmrc_rate random; + struct mmrc_rate lookaround0 = tb->best_tp; + struct mmrc_rate lookaround1 = tb->second_tp; + bool is_lookaround; + int lookaround_index = -1; + int best_index = 0; + int random_tp = 0; + int best_tp; + int lookaround_fail_count; + bool try_current_lookaround = false; + + s32 rem_time = RATE_WINDOW_MICROSECONDS; + + memset(out, 0, sizeof(*out)); + + tb->lookaround_cnt = (tb->lookaround_cnt + 1) % tb->lookaround_wrap; + /* + * Look around if the counter wraps or there has been no look around + * for a number of rate control cycles. + */ + is_lookaround = (tb->fixed_rate.rate == MMRC_MCS_UNUSED) && + ((tb->lookaround_cnt == 0) || + ((tb->last_lookaround_cycle + + LOOKAROUND_MAX_RC_CYCLES) <= tb->cycle_cnt)); + + /* Also skip sampling if we don't yet have data for our best rate */ + if (tb->table[tb->best_tp.index].evidence == 0) + is_lookaround = false; + + if (tb->lookaround_wrap != LOOKAROUND_RATE_STABLE) { + if (tb->stability_cnt >= tb->stability_cnt_threshold) { + tb->lookaround_wrap = LOOKAROUND_RATE_STABLE; + tb->stability_cnt_threshold = + STABILITY_CNT_THRESHOLD_STABLE; + tb->stability_cnt = STABILITY_CNT_THRESHOLD_STABLE * 2; + is_lookaround = false; + } + } else if (tb->stability_cnt < tb->stability_cnt_threshold) { + tb->stability_cnt_threshold = STABILITY_CNT_THRESHOLD_NORMAL; + tb->lookaround_wrap = LOOKAROUND_RATE_NORMAL; + tb->stability_cnt = 0; + } + + /* Look around only when the fixed rate is not set */ + if (is_lookaround) { + tb->total_lookaround++; + tb->forced_lookaround = + (tb->forced_lookaround + 1) % LOOKAROUND_RATE_NORMAL; + tb->last_lookaround_cycle = tb->cycle_cnt; + + if (tb->current_lookaround_rate_attempts < + LOOKAROUND_RATE_ATTEMPTS) + try_current_lookaround = true; + + best_tp = calculate_throughput(tb, tb->best_tp.index); + + for (lookaround_fail_count = 0; + lookaround_fail_count < LOOKAROUND_FAIL_MAX; + lookaround_fail_count++) { + if (try_current_lookaround) { + random_index = + tb->current_lookaround_rate_index; + try_current_lookaround = false; + } else { + random_index = get_random_u32_below( + rows_from_sta_caps(&tb->caps)); + } + random = get_rate_row(tb, random_index); + + if (!validate_rate(tb, &random)) + continue; + + if (random.rate == MMRC_MCS10) + continue; + + if (tb->table[random_index].evidence > 0) + random_tp = + calculate_throughput(tb, random_index); + else + random_tp = + mmrc_calculate_theoretical_throughput( + random); + + /* + * Skip rates that can only be worse than the current + * best + */ + if (random_tp <= best_tp) + continue; + + /* + * Force looking up the rate no more that one MCS. + * It will avoid looking for rates with very low + * success rate. In case of better environment + * conditions MMRC will collect enough statistics to + * climb up the rates one by one. + */ + if (random.rate > tb->best_tp.rate + 1 || + random.bw > tb->best_tp.bw + 1 || + (random.rate > tb->best_tp.rate && + random.bw > tb->best_tp.bw)) + continue; + + if (tb->current_lookaround_rate_index == random_index) { + tb->current_lookaround_rate_attempts++; + } else { + tb->current_lookaround_rate_attempts = 0; + tb->current_lookaround_rate_index = + random_index; + } + + break; + } + + if (lookaround_fail_count >= LOOKAROUND_FAIL_MAX) { + is_lookaround = false; + tb->current_lookaround_rate_index = tb->best_tp.index; + } else { + lookaround0 = random; + lookaround1 = tb->best_tp; + lookaround_index = 0; + best_index = 1; + } + } + + if (tb->caps.max_rates == 1) { + out->rates[0] = (is_lookaround) ? lookaround0 : tb->best_tp; + out->rates[1].rate = MMRC_MCS_UNUSED; + out->rates[2].rate = MMRC_MCS_UNUSED; + out->rates[3].rate = MMRC_MCS_UNUSED; + } else if (tb->caps.max_rates == 2) { + out->rates[0] = (is_lookaround) ? lookaround0 : tb->best_tp; + out->rates[1] = (is_lookaround) ? lookaround1 : tb->best_prob; + out->rates[2].rate = MMRC_MCS_UNUSED; + out->rates[3].rate = MMRC_MCS_UNUSED; + } else if (tb->caps.max_rates == 3) { + out->rates[0] = (is_lookaround) ? lookaround0 : tb->best_tp; + out->rates[1] = (is_lookaround) ? lookaround1 : tb->second_tp; + out->rates[2] = tb->best_prob; + out->rates[3].rate = MMRC_MCS_UNUSED; + } else { + out->rates[0] = (is_lookaround) ? lookaround0 : tb->best_tp; + out->rates[1] = (is_lookaround) ? lookaround1 : tb->second_tp; + out->rates[2] = tb->best_prob; + out->rates[3] = tb->baseline; + } + + /* For fallback rates, set RTS/CTS */ + for (i = 1; i < MMRC_MAX_CHAIN_LENGTH; i++) + out->rates[i].flags |= BIT(MMRC_FLAGS_CTS_RTS); + + /* Allocate initial attempts for rate */ + allocate_initial_attempts(out, &rem_time, size); + + /* Calculate and allocate remaining attempts */ + calculate_remaining_attempts(tb, out, &rem_time, size); + + /* Enforce limits on each attempts */ + for (i = 0; i < MMRC_MAX_CHAIN_LENGTH; i++) { + if (out->rates[i].rate != MMRC_MCS_UNUSED) { + out->rates[i].attempts = + out->rates[i].attempts == 0 ? + MMRC_ATTEMPTS_TO_BITFIELD( + MMRC_MIN_CHAIN_ATTEMPTS) : + out->rates[i].attempts; + out->rates[i].attempts = + out->rates[i].attempts > + MMRC_MAX_CHAIN_ATTEMPTS ? + MMRC_ATTEMPTS_TO_BITFIELD( + MMRC_MAX_CHAIN_ATTEMPTS) : + out->rates[i].attempts; + if (i == lookaround_index && + tb->lookaround_wrap != LOOKAROUND_RATE_INIT) + out->rates[i].attempts = + MMRC_ATTEMPTS_TO_BITFIELD(1); + } + } + + /* + * Give the best rate at least 2 attempts to keep peak throughput + * unless it is too low + */ + if (out->rates[best_index].attempts == 1 && + out->rates[best_index].rate > MMRC_MCS1) + out->rates[best_index].attempts = MMRC_ATTEMPTS_TO_BITFIELD(2); + else if (out->rates[best_index].rate <= MMRC_MCS1) + out->rates[best_index].attempts = 1; +} + +static u32 calc_ewma_average(u32 avg, u32 latest, u32 weight) +{ + WARN_ON_ONCE(!(weight <= 100)); + + if (avg == 0) + return latest; + + return ((latest * (100 - weight)) + (avg * weight)) / 100; +} + +static void mmrc_process_variation(struct mmrc_table *tb, u16 current_success, + u32 index) +{ + u32 current_variation; + + /* + * Only process probability variation for the best rate. It is likely + * the only rate to have enough data to see the variation and its + * statistics are more affected because they are usually collected over + * the full period. + */ + if (index != tb->best_tp.index) + return; + + if (current_success == 0) { + if (!tb->unconverged) { + /* + * Best rate is failing completely, go to unconverged + * mode + */ + tb->unconverged = true; + tb->newly_unconverged = true; + } + return; + } + + if (tb->table[index].prob == 0) + return; + + /* Don't process variation while converging after association */ + if (tb->lookaround_wrap == LOOKAROUND_RATE_INIT) + return; + + current_variation = abs(current_success - tb->table[index].prob); + + /* Calculate the EWMA of the probability variation */ + tb->probability_variation = calc_ewma_average( + tb->probability_variation, current_variation, VARIATION_EWMA); + + /* + * Process the variation direction to distinguish converged and + * unconverged scenarios + */ + if (tb->probability_variation >= MODERATE_VARIATION_THRESHOLD || + tb->interference_likely) { + if ((current_success - tb->table[index].prob) * + tb->probability_variation_direction < + 0) + tb->probability_variation_direction = 0; + else if (current_success > tb->table[index].prob) + tb->probability_variation_direction = + min(tb->probability_variation_direction + 1, + MAX_VARIATION_DIRECTION); + else if (current_success < tb->table[index].prob) + tb->probability_variation_direction = + max(tb->probability_variation_direction - 1, + -MAX_VARIATION_DIRECTION); + } + + if (tb->best_rate_cycle_count > VARIATION_DIRECTION_THRESHOLD && + tb->probability_variation >= SIGNIFICANT_VARIATION_THRESHOLD) { + /* + * Only enter interference mode if the best rate is stable for + * enough cycles to determine the direction is random and not + * in one direction only + */ + if (abs(tb->probability_variation_direction) <= + VARIATION_DIRECTION_THRESHOLD && + !tb->interference_likely) { + tb->interference_likely = true; + } + } else if (tb->interference_likely && + (tb->probability_variation <= MINOR_VARIATION_THRESHOLD || + abs(tb->probability_variation_direction) == + MAX_VARIATION_DIRECTION)) { + /* + * Exit interference mode if the variability drops or the + * direction stops being random + */ + tb->interference_likely = false; + } +} + +void mmrc_update(struct mmrc_table *tb) +{ + u32 i; + u16 this_success; + u32 scale; + u32 scaled_ewma; + u32 new_stats = 0; + u32 attempts_for_stats; + u32 success_for_stats; + u32 min_stats; + u32 throughput; + u32 evidence_sent; + + tb->cycle_cnt++; + + /* Allow less minimum stats when converging */ + if (tb->lookaround_wrap != LOOKAROUND_RATE_INIT) + min_stats = STATS_MIN_NORMAL; + else + min_stats = STATS_MIN_INIT; + + for (i = 0; i < rows_from_sta_caps(&tb->caps); i++) { + /* This algorithm is keeping track of the amount of evidence, + * being packets that have been recently sent at this rate. + * This value is smoothed with an EWMA function over time and + * used to update the probability of a rate succeeding + * dynamically. This method allows MMRC to react timely if a + * new rate is used that hasn't been used recently + */ + + /* Necessary to prevent a divide by 0 */ + if (tb->table[i].evidence == 0) + scale = 0; + else + scale = ((tb->table[i].evidence * 2) * 100) / + ((tb->table[i].sent * EVIDENCE_SCALE) + + tb->table[i].evidence); + + /* Restrict scale to appropriate values */ + if (scale > 100) + scale = 100; + + scaled_ewma = scale * EWMA / 100; + + /* + * Only count new packets for evidence if we will process + * them + */ + evidence_sent = + tb->table[i].sent >= min_stats ? tb->table[i].sent : 0; + tb->table[i].evidence = calc_ewma_average( + tb->table[i].evidence, evidence_sent * EVIDENCE_SCALE, + scaled_ewma); + + if (tb->table[i].evidence > EVIDENCE_MAX) + tb->table[i].evidence = EVIDENCE_MAX; + + /* Try to use statistics from acknowledged AMPDUs first */ + attempts_for_stats = tb->table[i].back_mpdu_success + + tb->table[i].back_mpdu_failure; + success_for_stats = tb->table[i].back_mpdu_success; + + /* + * Use the full statistics if rates are not converged or there + * were no AMPDUs for this rate or the remaining attempts are + * less than half of what we have from AMPDUs. + */ + if (!tb->table[i].have_sent_ampdus || tb->unconverged || + attempts_for_stats < AMPDU_STATS_MIN || + (tb->table[i].sent - attempts_for_stats < + attempts_for_stats / 2)) { + attempts_for_stats = tb->table[i].sent; + success_for_stats = tb->table[i].sent_success; + } + + if (attempts_for_stats >= min_stats || + (attempts_for_stats > 0 && tb->table[i].prob > 0)) { + new_stats = 1; + this_success = + (100 * success_for_stats) / attempts_for_stats; + + if (scaled_ewma) + mmrc_process_variation(tb, this_success, i); + + tb->table[i].prob = calc_ewma_average( + tb->table[i].prob, this_success, scaled_ewma); + + /* Clear our sent statistics and update totals */ + tb->table[i].total_sent += tb->table[i].sent; + tb->table[i].sent = 0; + + tb->table[i].total_success += tb->table[i].sent_success; + tb->table[i].sent_success = 0; + + tb->table[i].back_mpdu_failure = 0; + tb->table[i].back_mpdu_success = 0; + tb->table[i].have_sent_ampdus = false; + } + + throughput = calculate_throughput(tb, i); + if (tb->table[i].max_throughput < throughput) + tb->table[i].max_throughput = throughput; + + /* + * Reset the running average windows if reached collector + * limits + */ + if (tb->table[i].sum_throughput > (0xFFFFFFFF - throughput)) { + tb->table[i].sum_throughput /= + tb->table[i].avg_throughput_counter; + tb->table[i].avg_throughput_counter = 1; + } + /* Update the sum and counter so it will be possible later to + * calculate the running average throughput + */ + tb->table[i].sum_throughput += throughput; + tb->table[i].avg_throughput_counter++; + } + + generate_table_priority(tb, new_stats); + + /* + * Switch to faster lookaround mode if rates drop low at very low + * bandwidth or we are in unconverged mode. Switching at low bandwidth + * and rate is to help recover quickly from rates where we would need + * to fragment standard MTU size packets. + */ + if (tb->lookaround_wrap != LOOKAROUND_RATE_INIT && + (tb->unconverged || (tb->best_tp.bw == MMRC_BW_1MHZ && + tb->best_tp.rate <= MMRC_MCS2))) { + tb->lookaround_cnt = 0; + tb->lookaround_wrap = LOOKAROUND_RATE_INIT; + tb->stability_cnt_threshold = STABILITY_CNT_THRESHOLD_INIT; + } + + /* + * If it is unlikely we can do the lookaround attempts in two RC cycles + * choose a new rate + */ + if (tb->current_lookaround_rate_attempts <= + (LOOKAROUND_RATE_ATTEMPTS / 2)) + tb->current_lookaround_rate_attempts = LOOKAROUND_RATE_ATTEMPTS; +} + +void mmrc_feedback(struct mmrc_table *tb, struct mmrc_rate_table *rates, + s32 retry_count, bool was_aggregated) +{ + s32 ind = retry_count; + u32 i; + + for (i = 0; i < MMRC_MAX_CHAIN_LENGTH; i++) { + rate_update_index(tb, &rates->rates[i]); + tb->table[rates->rates[i].index].have_sent_ampdus |= + was_aggregated; + + if ((s32)rates->rates[i].attempts < ind) { + ind = ind - rates->rates[i].attempts; + tb->table[rates->rates[i].index].sent += + rates->rates[i].attempts; + if (was_aggregated) { + tb->table[rates->rates[i].index] + .back_mpdu_failure += + rates->rates[i].attempts; + } + } else { + tb->table[rates->rates[i].index].sent += ind; + tb->table[rates->rates[i].index].sent_success += 1; + if (was_aggregated) { + tb->table[rates->rates[i].index] + .back_mpdu_success += 1; + tb->table[rates->rates[i].index] + .back_mpdu_failure += + ind > 1 ? ind - 1 : 0; + } + return; + } + } +} + +/* + * Chooses a reasonable starting rate based on range (gathered from + * RSSI measurements) or bandwidth. Then fills out the 3 retry rates + * so a full set of rates is available. + */ +static void mmrc_init_rates(struct mmrc_table *tb, s8 rssi) +{ + tb->best_tp.bw = MMRC_MAX_BW(tb->caps.bandwidth); + if (tb->caps.sgi_per_bw & SGI_PER_BW(tb->best_tp.bw)) + tb->best_tp.guard = MMRC_GUARD_TO_BITFIELD(MMRC_GUARD_SHORT); + else + tb->best_tp.guard = MMRC_GUARD_TO_BITFIELD(MMRC_GUARD_LONG); + tb->best_tp.rate = MMRC_RATE_TO_BITFIELD(MMRC_MCS0); + + if (rssi >= MMRC_SHORT_RANGE_RSSI_LIMIT) + tb->best_tp.rate = MMRC_RATE_TO_BITFIELD(MMRC_MCS7); + else if (rssi < MMRC_SHORT_RANGE_RSSI_LIMIT && + rssi >= MMRC_MID_RANGE_RSSI_LIMIT) + tb->best_tp.rate = MMRC_RATE_TO_BITFIELD(MMRC_MCS3); + else if (tb->best_tp.bw == MMRC_BW_1MHZ || + tb->best_tp.bw == MMRC_BW_2MHZ) + /* + * To compensate for slow feedback when running with 1 and 2 + * MHz bandwidth, we start from MCS3 which will correspond to + * reasonable feedback and will avoid resetting the rate table + * evidence. + */ + tb->best_tp.rate = MMRC_RATE_TO_BITFIELD(MMRC_MCS3); + + tb->best_tp.ss = MMRC_SS_TO_BITFIELD(MMRC_SPATIAL_STREAM_1); + rate_update_index(tb, &tb->best_tp); + /* Init every rate in case they are needed to set the retry rates */ + tb->second_tp = tb->best_tp; + tb->best_prob = tb->best_tp; + tb->baseline = tb->best_tp; + mmrc_fill_retry_rates(tb); +} + +void mmrc_sta_init(struct mmrc_table *tb, struct mmrc_sta_capabilities *caps, + s8 rssi) +{ + u32 i; + u16 row_count = rows_from_sta_caps(caps); + + memset(tb, 0, mmrc_memory_required_for_caps(caps)); + memcpy(&tb->caps, caps, sizeof(tb->caps)); + + for (i = 0; i < row_count; i++) { + tb->table[i].prob = RATE_INIT_PROBABILITY; + tb->table[i].evidence = 0; + tb->table[i].sum_throughput = 0; + tb->table[i].avg_throughput_counter = 0; + tb->table[i].max_throughput = 0; + } + + tb->fixed_rate.rate = MMRC_MCS_UNUSED; + tb->cycle_cnt = 0; + tb->last_lookaround_cycle = 0; + tb->lookaround_cnt = 0; + tb->lookaround_wrap = LOOKAROUND_RATE_INIT; + tb->unconverged = true; + tb->newly_unconverged = true; + tb->stability_cnt_threshold = STABILITY_CNT_THRESHOLD_INIT; + tb->baseline = get_rate_row(tb, find_baseline_index(tb)); + mmrc_init_rates(tb, rssi); +} + +bool mmrc_set_fixed_rate(struct mmrc_table *tb, struct mmrc_rate fixed_rate) +{ + bool caps_support_rate = true; + + /* Do not accept rate which does not support the STA capabilities */ + if ((BIT(fixed_rate.rate) & tb->caps.rates) == 0 || + (BIT(fixed_rate.bw) & tb->caps.bandwidth) == 0 || + (BIT(fixed_rate.ss) & tb->caps.spatial_streams) == 0 || + (BIT(fixed_rate.guard) & tb->caps.guard) == 0) + caps_support_rate = false; + + if (validate_rate(tb, &fixed_rate) && caps_support_rate) { + tb->fixed_rate = fixed_rate; + rate_update_index(tb, &tb->fixed_rate); + return true; + } + + return false; +} diff --git a/drivers/net/wireless/morsemicro/mm81x/mmrc.h b/drivers/net/wireless/morsemicro/mm81x/mmrc.h new file mode 100644 index 000000000000..a4c7d941ad55 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/mmrc.h @@ -0,0 +1,193 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_MMRC_H_ +#define _MM81X_MMRC_H_ + +#include +#include +#include +#include +#include +#include + +/* The max length of a retry chain for a single packet transmission */ +#define MMRC_MAX_CHAIN_LENGTH 4 + +/* Rate minimum allowed attempts */ +#define MMRC_MIN_CHAIN_ATTEMPTS 1 + +/* Rate upper limit for attempts */ +#define MMRC_MAX_CHAIN_ATTEMPTS 2 + +/* The frequency of MMRC stat table updates */ +#define MMRC_UPDATE_FREQUENCY_MS 100 + +enum mmrc_flags { + MMRC_FLAGS_CTS_RTS, +}; + +enum mmrc_mcs_rate { + MMRC_MCS0, + MMRC_MCS1, + MMRC_MCS2, + MMRC_MCS3, + MMRC_MCS4, + MMRC_MCS5, + MMRC_MCS6, + MMRC_MCS7, + MMRC_MCS8, + MMRC_MCS9, + MMRC_MCS10, + MMRC_MCS_UNUSED, +}; + +enum mmrc_bw { + MMRC_BW_1MHZ = 0, + MMRC_BW_2MHZ = 1, + MMRC_BW_4MHZ = 2, + MMRC_BW_8MHZ = 3, + MMRC_BW_16MHZ = 4, + MMRC_BW_MAX = 5, +}; + +enum mmrc_spatial_stream { + MMRC_SPATIAL_STREAM_1 = 0, + MMRC_SPATIAL_STREAM_2 = 1, + MMRC_SPATIAL_STREAM_3 = 2, + MMRC_SPATIAL_STREAM_4 = 3, + MMRC_SPATIAL_STREAM_MAX, +}; + +enum mmrc_guard { + MMRC_GUARD_LONG = 0, + MMRC_GUARD_SHORT = 1, + MMRC_GUARD_MAX, +}; + +#define MMRC_RATE_TO_BITFIELD(x) ((x) & 0xF) +#define MMRC_ATTEMPTS_TO_BITFIELD(x) ((x) & 0x7) +#define MMRC_GUARD_TO_BITFIELD(x) ((x) & 0x1) +#define MMRC_SS_TO_BITFIELD(x) ((x) & 0x3) +#define MMRC_BW_TO_BITFIELD(x) ((x) & 0x7) +#define MMRC_FLAGS_TO_BITFIELD(x) ((x) & 0x7) + +struct mmrc_rate { + u8 rate : 4; + u8 attempts : 3; + u8 guard : 1; + u8 ss : 2; + u8 bw : 3; + u8 flags : 3; + u16 index; +}; + +struct mmrc_rate_table { + struct mmrc_rate rates[MMRC_MAX_CHAIN_LENGTH]; +}; + +#define SGI_PER_BW(bw) (1 << (bw)) + +struct mmrc_sta_capabilities { + u8 max_rates : 3; + u8 max_retries : 3; + u8 bandwidth : 5; + u8 spatial_streams : 4; + u16 rates : 11; + u8 guard : 2; + u8 sta_flags : 4; + u8 sgi_per_bw : 5; +}; + +struct mmrc_stats_table { + u32 avg_throughput_counter; + u32 sum_throughput; + u32 max_throughput; + u16 sent; + u16 sent_success; + u16 back_mpdu_success; + u16 back_mpdu_failure; + u32 total_sent; + u32 total_success; + u16 evidence; + u8 prob; + bool have_sent_ampdus; +}; + +struct mmrc_table { + struct mmrc_sta_capabilities caps; + struct mmrc_rate best_tp; + struct mmrc_rate second_tp; + struct mmrc_rate baseline; + struct mmrc_rate best_prob; + struct mmrc_rate fixed_rate; + u32 cycle_cnt; + u32 last_lookaround_cycle; + u8 lookaround_cnt; + + /* The ratio of using normal rate and sampling */ + u8 lookaround_wrap; + + /* + * A counter that is used to determine when we should force a + * lookaround. Should be a portion of the above lookaround with + * less constraints + */ + u8 forced_lookaround; + + u8 current_lookaround_rate_attempts; + u16 current_lookaround_rate_index; + u32 total_lookaround; + + /* + * A counter to detect if the current best rate is optimal + * and may slow down sample frequency. + */ + u32 stability_cnt; + + u32 stability_cnt_threshold; + u8 probability_variation; + + /* The difference in MCS from each of the last 2 rate changes */ + s8 best_rate_diff[2]; + + /* Indication of random versus consistently one-sided variation */ + s8 probability_variation_direction; + + /* Has rate control detected possible interference */ + bool interference_likely; + + /* Has rate control detected the best rate is no longer converged */ + bool unconverged; + + /* Is rate control just entering unconverged state */ + bool newly_unconverged; + + /* + * Number of rate control cycles the best rate has remained + * unchanged + */ + s32 best_rate_cycle_count; + + /* + * The probability table for the STA. This MUST always be the last + * element in the struct. + */ + struct mmrc_stats_table table[]; +}; + +void mmrc_sta_init(struct mmrc_table *tb, struct mmrc_sta_capabilities *caps, + s8 rssi); +size_t mmrc_memory_required_for_caps(struct mmrc_sta_capabilities *caps); +void mmrc_get_rates(struct mmrc_table *tb, struct mmrc_rate_table *out, + size_t size); +void mmrc_feedback(struct mmrc_table *tb, struct mmrc_rate_table *rates, + s32 retry_count, bool was_aggregated); +void mmrc_update(struct mmrc_table *tb); +bool mmrc_set_fixed_rate(struct mmrc_table *tb, struct mmrc_rate fixed_rate); +u32 mmrc_calculate_theoretical_throughput(struct mmrc_rate rate); +u32 mmrc_calculate_rate_tx_time(struct mmrc_rate *rate, size_t size); + +#endif /* _MMRC_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/ps.c b/drivers/net/wireless/morsemicro/mm81x/ps.c new file mode 100644 index 000000000000..ab67823452ee --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/ps.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include +#include +#include +#include "hif.h" +#include "skbq.h" +#include "mac.h" +#include "bus.h" +#include "ps.h" + +static void mm81x_ps_wakeup(struct mm81x_ps *mps) +{ + struct mm81x *mors = container_of(mps, struct mm81x, ps); + + if (!mps->enable || !mps->suspended) + return; + + mm81x_set_bus_enable(mors, true); + mps->suspended = false; +} + +static void mm81x_ps_sleep(struct mm81x_ps *mps) +{ + struct mm81x *mors = container_of(mps, struct mm81x, ps); + + if (!mps->enable || mps->suspended) + return; + + mps->suspended = true; + mm81x_set_bus_enable(mors, false); +} + +static void mm81x_ps_evaluate(struct mm81x_ps *mps) +{ + struct mm81x *mors = container_of(mps, struct mm81x, ps); + bool needs_wake = false; + unsigned long flags_on_entry = + (mors->hif.event_flags & + ~BIT(MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND)); + + if (!mps->enable) + return; + + needs_wake = (mps->wakers > 0); + needs_wake |= (flags_on_entry > 0); + needs_wake |= (mm81x_hif_get_tx_buffered_count(mors) > 0); + + if (needs_wake) { + mm81x_ps_wakeup(mps); + return; + } + + mm81x_ps_sleep(mps); +} + +static void mm81x_ps_evaluate_work(struct work_struct *work) +{ + struct mm81x_ps *mps = + container_of(work, struct mm81x_ps, delayed_eval_work.work); + + if (mps->enable) { + mutex_lock(&mps->lock); + mm81x_ps_evaluate(mps); + mutex_unlock(&mps->lock); + } +} + +void mm81x_ps_enable(struct mm81x *mors) +{ + struct mm81x_ps *mps = &mors->ps; + + if (mps->enable) { + mutex_lock(&mps->lock); + if (mps->wakers == 0) { + WARN_ON_ONCE(1); + } else { + mps->wakers--; + mm81x_ps_evaluate(mps); + } + mutex_unlock(&mps->lock); + } +} + +void mm81x_ps_disable(struct mm81x *mors) +{ + struct mm81x_ps *mps = &mors->ps; + + if (mps->enable) { + mutex_lock(&mps->lock); + mps->wakers++; + mm81x_ps_evaluate(mps); + mutex_unlock(&mps->lock); + } +} + +int mm81x_ps_init(struct mm81x *mors) +{ + struct mm81x_ps *mps = &mors->ps; + + mps->enable = (mors->bus_type == MM81X_BUS_TYPE_USB); + mps->suspended = true; + mps->wakers = 1; /* we default to being on */ + mutex_init(&mps->lock); + INIT_DELAYED_WORK(&mps->delayed_eval_work, mm81x_ps_evaluate_work); + + return 0; +} + +void mm81x_ps_finish(struct mm81x *mors) +{ + struct mm81x_ps *mps = &mors->ps; + + if (mps->enable) { + mps->enable = false; + cancel_delayed_work_sync(&mps->delayed_eval_work); + } +} diff --git a/drivers/net/wireless/morsemicro/mm81x/ps.h b/drivers/net/wireless/morsemicro/mm81x/ps.h new file mode 100644 index 000000000000..0b59bb4145ab --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/ps.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_PS_H_ +#define _MM81X_PS_H_ + +#include "core.h" + +/* This should be nominally <= the dynamic ps timeout */ +#define NETWORK_BUS_TIMEOUT_MS (90) + +/* The default period of time to wait to re-evaluate powersave */ +#define DEFAULT_BUS_TIMEOUT_MS (50) + +void mm81x_ps_disable(struct mm81x *mors); +void mm81x_ps_enable(struct mm81x *mors); +int mm81x_ps_init(struct mm81x *mors); +void mm81x_ps_finish(struct mm81x *mors); + +#endif /* !_MM81X_PS_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/rate_code.h b/drivers/net/wireless/morsemicro/mm81x/rate_code.h new file mode 100644 index 000000000000..c60fcb9447c4 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/rate_code.h @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_RATE_CODE_H_ +#define _MM81X_RATE_CODE_H_ + +#include + +enum dot11_bandwidth { + DOT11_BANDWIDTH_1MHZ = 0, + DOT11_BANDWIDTH_2MHZ = 1, + DOT11_BANDWIDTH_4MHZ = 2, + DOT11_BANDWIDTH_8MHZ = 3, + DOT11_BANDWIDTH_16MHZ = 4, + + DOT11_MAX_BANDWIDTH = DOT11_BANDWIDTH_16MHZ, + DOT11_INVALID_BANDWIDTH = 5 +}; + +enum mm81x_rate_preamble { + /* S1G LONG format (with SIG-A and SIG-B) */ + MM81X_RATE_PREAMBLE_S1G_LONG = 0, + /* This is the most common format used */ + MM81X_RATE_PREAMBLE_S1G_SHORT = 1, + /* S1G 1M format */ + MM81X_RATE_PREAMBLE_S1G_1M = 2, + + MM81X_RATE_MAX_PREAMBLE = MM81X_RATE_PREAMBLE_S1G_1M, + MM81X_RATE_INVALID_PREAMBLE = 7 +}; + +typedef __le32 mm81x_rate_code_t; + +#define MM81X_RATECODE_PREAMBLE (0x0000000F) +#define MM81X_RATECODE_MCS_INDEX (0x000000F0) +#define MM81X_RATECODE_NSS_INDEX (0x00000700) +#define MM81X_RATECODE_BW_INDEX (0x00003800) +#define MM81X_RATECODE_RTS_FLAG (0x00010000) +#define MM81X_RATECODE_SHORT_GI_FLAG (0x00040000) +#define MM81X_RATECODE_DUP_BW_INDEX (0x01C00000) + +static inline enum mm81x_rate_preamble +mm81x_ratecode_preamble_get(mm81x_rate_code_t rc) +{ + return (enum mm81x_rate_preamble)( + le32_get_bits(rc, MM81X_RATECODE_PREAMBLE)); +} + +static inline u8 mm81x_ratecode_mcs_index_get(mm81x_rate_code_t rc) +{ + return le32_get_bits(rc, MM81X_RATECODE_MCS_INDEX); +} + +static inline u8 mm81x_ratecode_nss_index_get(mm81x_rate_code_t rc) +{ + return le32_get_bits(rc, MM81X_RATECODE_NSS_INDEX); +} + +static inline enum dot11_bandwidth +mm81x_ratecode_bw_index_get(mm81x_rate_code_t rc) +{ + return (enum dot11_bandwidth)( + le32_get_bits(rc, MM81X_RATECODE_BW_INDEX)); +} + +static inline bool mm81x_ratecode_rts_get(mm81x_rate_code_t rc) +{ + return le32_get_bits(rc, MM81X_RATECODE_RTS_FLAG); +} + +static inline bool mm81x_ratecode_sgi_get(mm81x_rate_code_t rc) +{ + return le32_get_bits(rc, MM81X_RATECODE_SHORT_GI_FLAG); +} + +static inline enum dot11_bandwidth +mm81x_ratecode_dup_bw_index_get(mm81x_rate_code_t rc) +{ + return (enum dot11_bandwidth)( + le32_get_bits(rc, MM81X_RATECODE_DUP_BW_INDEX)); +} + +#define MM81X_RATECODE_INIT(bw_idx, nss_idx, mcs_idx, preamble) \ + (le32_encode_bits((bw_idx), MM81X_RATECODE_BW_INDEX) | \ + le32_encode_bits((nss_idx), MM81X_RATECODE_NSS_INDEX) | \ + le32_encode_bits((mcs_idx), MM81X_RATECODE_MCS_INDEX) | \ + le32_encode_bits((preamble), MM81X_RATECODE_PREAMBLE)) + +static inline mm81x_rate_code_t +mm81x_ratecode_init(enum dot11_bandwidth bw_index, u32 nss_index, u32 mcs_index, + enum mm81x_rate_preamble preamble) +{ + return MM81X_RATECODE_INIT(bw_index, nss_index, mcs_index, preamble); +} + +static inline void +mm81x_ratecode_preamble_set(mm81x_rate_code_t *rc, + enum mm81x_rate_preamble preamble) +{ + *rc = (*rc & cpu_to_le32(~MM81X_RATECODE_PREAMBLE)) | + le32_encode_bits(preamble, MM81X_RATECODE_PREAMBLE); +} + +static inline void mm81x_ratecode_mcs_index_set(mm81x_rate_code_t *rc, + u32 mcs_index) +{ + *rc = (*rc & cpu_to_le32(~MM81X_RATECODE_MCS_INDEX)) | + le32_encode_bits(mcs_index, MM81X_RATECODE_MCS_INDEX); +} + +static inline void mm81x_ratecode_nss_index_set(mm81x_rate_code_t *rc, + u32 nss_index) +{ + *rc = (*rc & cpu_to_le32(~MM81X_RATECODE_NSS_INDEX)) | + le32_encode_bits(nss_index, MM81X_RATECODE_NSS_INDEX); +} + +static inline void mm81x_ratecode_bw_index_set(mm81x_rate_code_t *rc, + enum dot11_bandwidth bw_index) +{ + *rc = (*rc & cpu_to_le32(~MM81X_RATECODE_BW_INDEX)) | + le32_encode_bits(bw_index, MM81X_RATECODE_BW_INDEX); +} + +static inline void +mm81x_ratecode_update_s1g_bw_preamble(mm81x_rate_code_t *rc, + enum dot11_bandwidth bw_index) +{ + enum mm81x_rate_preamble pream = MM81X_RATE_PREAMBLE_S1G_SHORT; + + if (bw_index == DOT11_BANDWIDTH_1MHZ) + pream = MM81X_RATE_PREAMBLE_S1G_1M; + + mm81x_ratecode_preamble_set(rc, pream); + mm81x_ratecode_bw_index_set(rc, bw_index); +} + +static inline void +mm81x_ratecode_dup_bw_index_set(mm81x_rate_code_t *rc, + enum dot11_bandwidth dup_bw_index) +{ + *rc = (*rc & cpu_to_le32(~MM81X_RATECODE_DUP_BW_INDEX)) | + le32_encode_bits(dup_bw_index, MM81X_RATECODE_DUP_BW_INDEX); +} + +static inline void mm81x_ratecode_enable_rts(mm81x_rate_code_t *rc) +{ + *rc |= cpu_to_le32(MM81X_RATECODE_RTS_FLAG); +} + +static inline void mm81x_ratecode_enable_sgi(mm81x_rate_code_t *rc) +{ + *rc |= cpu_to_le32(MM81X_RATECODE_SHORT_GI_FLAG); +} + +static inline enum dot11_bandwidth mm81x_ratecode_bw_mhz_to_bw_index(u8 bw_mhz) +{ + return ((bw_mhz == 1) ? DOT11_BANDWIDTH_1MHZ : + (bw_mhz == 2) ? DOT11_BANDWIDTH_2MHZ : + (bw_mhz == 4) ? DOT11_BANDWIDTH_4MHZ : + (bw_mhz == 8) ? DOT11_BANDWIDTH_8MHZ : + DOT11_BANDWIDTH_2MHZ); +} + +static inline u8 +mm81x_ratecode_bw_index_to_s1g_bw_mhz(enum dot11_bandwidth bw_idx) +{ + return ((bw_idx == DOT11_BANDWIDTH_1MHZ) ? 1 : + (bw_idx == DOT11_BANDWIDTH_2MHZ) ? 2 : + (bw_idx == DOT11_BANDWIDTH_4MHZ) ? 4 : + (bw_idx == DOT11_BANDWIDTH_8MHZ) ? 8 : + 2); +} + +#endif diff --git a/drivers/net/wireless/morsemicro/mm81x/rc.c b/drivers/net/wireless/morsemicro/mm81x/rc.c new file mode 100644 index 000000000000..04aff66de4bd --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/rc.c @@ -0,0 +1,494 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include +#include +#include "core.h" +#include "mac.h" +#include "bus.h" +#include "rc.h" + +#define MM81X_RC_BW_TO_MMRC_BW(X) \ + (((X) == 1) ? MMRC_BW_1MHZ : \ + ((X) == 2) ? MMRC_BW_2MHZ : \ + ((X) == 4) ? MMRC_BW_4MHZ : \ + ((X) == 8) ? MMRC_BW_8MHZ : \ + MMRC_BW_2MHZ) + +static void mm81x_rc_work(struct work_struct *work) +{ + struct mm81x_rc *mrc = container_of(work, struct mm81x_rc, work); + struct list_head *pos; + + spin_lock_bh(&mrc->lock); + + list_for_each(pos, &mrc->stas) { + struct mm81x_rc_sta *mrc_sta = + container_of(pos, struct mm81x_rc_sta, list); + unsigned long now = jiffies; + + mrc_sta->last_update = now; + + mmrc_update(mrc_sta->tb); + } + + spin_unlock_bh(&mrc->lock); + + mod_timer(&mrc->timer, jiffies + msecs_to_jiffies(100)); +} + +static void mm81x_rc_timer(struct timer_list *t) +{ + struct mm81x_rc *mrc = timer_container_of(mrc, t, timer); + struct mm81x *mors = mrc->mors; + + queue_work(mors->net_wq, &mors->mrc.work); +} + +void mm81x_rc_init(struct mm81x *mors) +{ + INIT_LIST_HEAD(&mors->mrc.stas); + spin_lock_init(&mors->mrc.lock); + + INIT_WORK(&mors->mrc.work, mm81x_rc_work); + timer_setup(&mors->mrc.timer, mm81x_rc_timer, 0); + + mors->mrc.mors = mors; + mod_timer(&mors->mrc.timer, jiffies + msecs_to_jiffies(100)); +} + +void mm81x_rc_deinit(struct mm81x *mors) +{ + cancel_work_sync(&mors->mrc.work); + timer_delete_sync_try(&mors->mrc.timer); +} + +static void mm81x_rc_sta_config_guard_per_bw(struct ieee80211_sta *sta, + struct mmrc_sta_capabilities *caps) +{ + caps->guard = BIT(MMRC_GUARD_LONG); + + if (caps->bandwidth & BIT(MMRC_BW_1MHZ)) { + caps->sgi_per_bw |= SGI_PER_BW(MMRC_BW_1MHZ); + caps->guard |= BIT(MMRC_GUARD_SHORT); + } + + if (caps->bandwidth & BIT(MMRC_BW_2MHZ)) { + caps->sgi_per_bw |= SGI_PER_BW(MMRC_BW_2MHZ); + caps->guard |= BIT(MMRC_GUARD_SHORT); + } + + if (caps->bandwidth & BIT(MMRC_BW_4MHZ)) { + caps->sgi_per_bw |= SGI_PER_BW(MMRC_BW_4MHZ); + caps->guard |= BIT(MMRC_GUARD_SHORT); + } + + if (caps->bandwidth & BIT(MMRC_BW_8MHZ)) { + caps->sgi_per_bw |= SGI_PER_BW(MMRC_BW_8MHZ); + caps->guard |= BIT(MMRC_GUARD_SHORT); + } +} + +static void mm81x_rc_sta_add_s1g_sta_caps(struct mm81x *mors, + struct mmrc_sta_capabilities *caps, + struct ieee80211_sta_s1g_cap *s1g_cap) +{ + int nss_idx = 0; + u8 rx_mcs = s1g_cap->nss_mcs[0] & 0x3; /* 1SS */ + u8 tx_mcs = (s1g_cap->nss_mcs[2] >> 1) & 0x3; /* 1SS */ + u8 mcs = min(rx_mcs, tx_mcs); + + switch (mcs) { + case IEEE80211_VHT_MCS_SUPPORT_0_9: /* VHT 9 -> S1G 9 */ + caps->rates |= BIT(MMRC_MCS9) | BIT(MMRC_MCS8); + fallthrough; + case IEEE80211_VHT_MCS_SUPPORT_0_8: /* VHT 8 -> S1G 7 */ + caps->rates |= BIT(MMRC_MCS7) | BIT(MMRC_MCS6) | + BIT(MMRC_MCS5) | BIT(MMRC_MCS4) | BIT(MMRC_MCS3); + fallthrough; + case IEEE80211_VHT_MCS_SUPPORT_0_7: /* VHT 7 -> S1G 2 */ + caps->rates |= BIT(MMRC_MCS2) | BIT(MMRC_MCS1) | + BIT(MMRC_MCS0) | BIT(MMRC_MCS10); + caps->spatial_streams |= (BIT(nss_idx) & 0x0F); + break; + + default: + dev_warn(mors->dev, "Invalid MCS encoding 0x%02x for stream %d", + mcs, nss_idx); + } +} + +int mm81x_rc_sta_add(struct mm81x *mors, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct ieee80211_sta_s1g_cap *s1g_cap = &sta->deflink.s1g_cap; + struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv; + struct mmrc_sta_capabilities caps; + int oper_bw_mhz = cfg80211_chandef_get_width(&mors->chandef); + size_t table_mem_size; + struct mmrc_table *tb; + + memset(&caps, 0, sizeof(caps)); + + mm81x_rc_sta_add_s1g_sta_caps(mors, &caps, s1g_cap); + + /* Configure STA for support up to 8MHZ */ + while (oper_bw_mhz > 0) { + caps.bandwidth |= BIT(MM81X_RC_BW_TO_MMRC_BW(oper_bw_mhz)); + oper_bw_mhz >>= 1; + } + + /* Configure STA for short and long guard */ + mm81x_rc_sta_config_guard_per_bw(sta, &caps); + + /* Set max rates */ + if (mors->hw->max_rates > 0 && + mors->hw->max_rates < IEEE80211_TX_MAX_RATES) + caps.max_rates = mors->hw->max_rates; + else + caps.max_rates = IEEE80211_TX_MAX_RATES; + + /* Set max reties */ + if (mors->hw->max_rate_tries >= MMRC_MIN_CHAIN_ATTEMPTS && + mors->hw->max_rate_tries < MMRC_MAX_CHAIN_ATTEMPTS) + caps.max_retries = mors->hw->max_rate_tries; + else + caps.max_retries = MMRC_MAX_CHAIN_ATTEMPTS; + + WARN_ON(msta->rc.tb); + table_mem_size = mmrc_memory_required_for_caps(&caps); + tb = kzalloc(table_mem_size, GFP_KERNEL); + if (!tb) + return -ENOMEM; + + /* Initialise the STA rate control table */ + mmrc_sta_init(tb, &caps, msta->avg_rssi); + + spin_lock_bh(&mors->mrc.lock); + kfree(msta->rc.tb); + msta->rc.tb = tb; + list_add(&msta->rc.list, &mors->mrc.stas); + msta->rc.last_update = jiffies; + spin_unlock_bh(&mors->mrc.lock); + + return 0; +} + +void mm81x_rc_sta_remove(struct mm81x *mors, struct ieee80211_sta *sta) +{ + struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv; + + spin_lock_bh(&mors->mrc.lock); + if (msta->rc.tb) { + list_del_init(&msta->rc.list); + kfree(msta->rc.tb); + msta->rc.tb = NULL; + } + spin_unlock_bh(&mors->mrc.lock); +} + +static void mm81x_rc_sta_fill_basic_rates(struct mm81x_skb_tx_info *tx_info, + struct ieee80211_tx_info *info, + int tx_bw) +{ + int i; + enum dot11_bandwidth bw_idx = mm81x_ratecode_bw_mhz_to_bw_index(tx_bw); + enum mm81x_rate_preamble pream = MM81X_RATE_PREAMBLE_S1G_SHORT; + + mm81x_ratecode_mcs_index_set(&tx_info->rates[0].mm81x_ratecode, 0); + mm81x_ratecode_nss_index_set(&tx_info->rates[0].mm81x_ratecode, + NSS_TO_NSS_IDX(1)); + mm81x_ratecode_bw_index_set(&tx_info->rates[0].mm81x_ratecode, bw_idx); + if (bw_idx == DOT11_BANDWIDTH_1MHZ) + pream = MM81X_RATE_PREAMBLE_S1G_1M; + mm81x_ratecode_preamble_set(&tx_info->rates[0].mm81x_ratecode, pream); + tx_info->rates[0].count = 4; + + for (i = 1; i < IEEE80211_TX_MAX_RATES; i++) + tx_info->rates[i].count = 0; + + info->control.rates[0].idx = 0; + info->control.rates[0].count = tx_info->rates[0].count; + info->control.rates[0].flags = 0; + info->control.rates[1].idx = -1; +} + +static int mm81x_rc_sta_get_rates(struct mm81x *mors, struct mm81x_sta *msta, + struct mmrc_rate_table *rates, size_t size) +{ + int ret = -ENOENT; + struct list_head *pos; + + spin_lock_bh(&mors->mrc.lock); + list_for_each(pos, &mors->mrc.stas) { + struct mm81x_rc_sta *mrc_sta = + list_entry(pos, struct mm81x_rc_sta, list); + + if (&msta->rc == mrc_sta) { + ret = 0; + mmrc_get_rates(msta->rc.tb, rates, size); + break; + } + } + spin_unlock_bh(&mors->mrc.lock); + + return ret; +} + +static bool mm81x_rc_use_basic_rates(struct ieee80211_sta *sta, + struct sk_buff *skb, + struct ieee80211_hdr *hdr) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + if (!sta) + return true; + + if (ieee80211_is_qos_nullfunc(hdr->frame_control) || + ieee80211_is_nullfunc(hdr->frame_control)) + return true; + + if (!ieee80211_is_data_qos(hdr->frame_control)) + return true; + + /* Use basic rates for EAPOL exchanges or when instructed */ + if (unlikely((skb->protocol == cpu_to_be16(ETH_P_PAE) || + info->flags & IEEE80211_TX_CTL_USE_MINRATE))) + return true; + + return false; +} + +void mm81x_rc_sta_fill_tx_rates(struct mm81x *mors, + struct mm81x_skb_tx_info *tx_info, + struct sk_buff *skb, struct ieee80211_sta *sta, + int tx_bw, bool rts_allowed) +{ + int ret, i; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct mm81x_sta *msta; + struct mmrc_rate_table rates; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + BUILD_BUG_ON((MMRC_BW_1MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_1MHZ || + MMRC_BW_2MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_2MHZ || + MMRC_BW_4MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_4MHZ || + MMRC_BW_16MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_16MHZ)); + + memset(&info->control.rates, 0, sizeof(info->control.rates)); + memset(&info->status.rates, 0, sizeof(info->status.rates)); + mm81x_rc_sta_fill_basic_rates(tx_info, info, tx_bw); + + /* Use basic rates for non data packets */ + if (mm81x_rc_use_basic_rates(sta, skb, hdr)) + return; + + msta = (struct mm81x_sta *)sta->drv_priv; + if (!msta) + return; + + ret = mm81x_rc_sta_get_rates(mors, msta, &rates, skb->len); + if (ret != 0) + return; + + for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { + info->control.rates[i].flags = 0; + if (rates.rates[i].rate != MMRC_MCS_UNUSED) { + u8 mcs = rates.rates[i].rate; + u8 nss_index = rates.rates[i].ss; + enum dot11_bandwidth bw_idx = + (enum dot11_bandwidth)rates.rates[i].bw; + enum mm81x_rate_preamble pream = + MM81X_RATE_PREAMBLE_S1G_SHORT; + + mm81x_ratecode_bw_index_set( + &tx_info->rates[i].mm81x_ratecode, bw_idx); + mm81x_ratecode_mcs_index_set( + &tx_info->rates[i].mm81x_ratecode, mcs); + mm81x_ratecode_nss_index_set( + &tx_info->rates[i].mm81x_ratecode, nss_index); + if (bw_idx == DOT11_BANDWIDTH_1MHZ) + pream = MM81X_RATE_PREAMBLE_S1G_1M; + mm81x_ratecode_preamble_set( + &tx_info->rates[i].mm81x_ratecode, pream); + tx_info->rates[i].count = rates.rates[i].attempts; + + if (rts_allowed && + (rates.rates[i].flags & BIT(MMRC_FLAGS_CTS_RTS))) { + mm81x_ratecode_enable_rts( + &tx_info->rates[i].mm81x_ratecode); + info->control.rates[i].flags |= + IEEE80211_TX_RC_USE_RTS_CTS; + } + + if (rates.rates[i].guard == MMRC_GUARD_SHORT) { + mm81x_ratecode_enable_sgi( + &tx_info->rates[i].mm81x_ratecode); + info->control.rates[i].flags |= + IEEE80211_TX_RC_SHORT_GI; + } + + /* Update skb tx_info */ + info->control.rates[i].idx = rates.rates[i].rate; + info->control.rates[i].count = rates.rates[i].attempts; + } else { + info->control.rates[i].idx = -1; + info->control.rates[i].count = 0; + tx_info->rates[i].count = 0; + } + } +} + +static void mm81x_rc_sta_set_rates(struct mm81x *mors, struct mm81x_sta *msta, + struct mmrc_rate_table *rates, int attempts, + bool was_aggregated) +{ + struct list_head *pos; + + spin_lock_bh(&mors->mrc.lock); + list_for_each(pos, &mors->mrc.stas) { + struct mm81x_rc_sta *mrc_sta = + list_entry(pos, struct mm81x_rc_sta, list); + + if (&msta->rc == mrc_sta) { + mmrc_feedback(msta->rc.tb, rates, attempts, + was_aggregated); + break; + } + } + spin_unlock_bh(&mors->mrc.lock); +} + +void mm81x_rc_sta_feedback_rates(struct mm81x *mors, struct sk_buff *skb, + struct ieee80211_sta *sta, + struct mm81x_skb_tx_status *tx_sts, + int attempts) +{ + int i; + u32 tx_airtime = 0; + struct mmrc_rate_table rates; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb); + struct ieee80211_tx_rate *r = &txi->status.rates[0]; + int count = min_t(int, MM81X_SKB_MAX_RATES, IEEE80211_TX_MAX_RATES); + struct mm81x_sta *msta = msta = (struct mm81x_sta *)sta->drv_priv; + + /* Don't update rate info if basic rates were used */ + if (mm81x_rc_use_basic_rates(sta, skb, hdr)) + goto exit; + + if (attempts <= 0) + /* Did we really send the packet? */ + goto exit; + + for (i = 0; i < count; i++) { + rates.rates[i].rate = mm81x_ratecode_mcs_index_get( + tx_sts->rates[i].mm81x_ratecode); + rates.rates[i].ss = mm81x_ratecode_nss_index_get( + tx_sts->rates[i].mm81x_ratecode); + rates.rates[i].guard = + mm81x_ratecode_sgi_get(tx_sts->rates[i].mm81x_ratecode); + rates.rates[i].bw = mm81x_ratecode_bw_index_get( + tx_sts->rates[i].mm81x_ratecode); + rates.rates[i].flags = + mm81x_ratecode_rts_get(tx_sts->rates[i].mm81x_ratecode); + rates.rates[i].attempts = tx_sts->rates[i].count; + + tx_airtime += + mmrc_calculate_rate_tx_time(&rates.rates[i], skb->len); + } + + if (msta) { + /* + * Save the rate information. This will be used to update + * station's tx rate stats + */ + msta->last_sta_tx_rate.bw = rates.rates[0].bw; + msta->last_sta_tx_rate.rate = rates.rates[0].rate; + msta->last_sta_tx_rate.ss = rates.rates[0].ss; + msta->last_sta_tx_rate.guard = rates.rates[0].guard; + } + + mm81x_rc_sta_set_rates(mors, msta, &rates, attempts, + !!(le32_to_cpu(tx_sts->flags) & + MM81X_TX_STATUS_WAS_AGGREGATED)); + + ieee80211_sta_register_airtime(sta, tx_sts->tid, tx_airtime, 0); + +exit: + ieee80211_tx_info_clear_status(txi); + + if (!(le32_to_cpu(tx_sts->flags) & MM81X_TX_STATUS_FLAGS_NO_ACK) && + !(txi->flags & IEEE80211_TX_CTL_NO_ACK)) + txi->flags |= IEEE80211_TX_STAT_ACK; + + if (le32_to_cpu(tx_sts->flags) & MM81X_TX_STATUS_FLAGS_PS_FILTERED) { + txi->flags |= IEEE80211_TX_STAT_TX_FILTERED; + + /* + * Clear TX CTL AMPDU flag so that this frame gets rescheduled + * in ieee80211_handle_filtered_frame(). This flag will get set + * again by mac80211's tx path on rescheduling. + */ + txi->flags &= ~IEEE80211_TX_CTL_AMPDU; + if (msta) { + if (!msta->tx_ps_filter_en) + dev_dbg(mors->dev, "TX ps filter set sta[%pM]", + msta->addr); + msta->tx_ps_filter_en = true; + } + } + + for (i = 0; i < count; i++) { + if (tx_sts->rates[i].count > 0) { + r[i].count = tx_sts->rates[i].count; + r[i].flags |= IEEE80211_TX_RC_MCS; + } else { + r[i].idx = -1; + } + } + + /* single packet per A-MPDU (for now) */ + if (txi->flags & IEEE80211_TX_CTL_AMPDU) { + txi->flags |= IEEE80211_TX_STAT_AMPDU; + txi->status.ampdu_len = 1; + txi->status.ampdu_ack_len = + txi->flags & IEEE80211_TX_STAT_ACK ? 1 : 0; + } + + /* + * Inform mac80211 that the SP (elicited by a PS-Poll or u-APSD) is + * over + */ + if (sta && (txi->flags & IEEE80211_TX_STATUS_EOSP)) { + txi->flags &= ~IEEE80211_TX_STATUS_EOSP; + ieee80211_sta_eosp(sta); + } +} + +void mm81x_rc_sta_state_check(struct mm81x *mors, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + enum ieee80211_sta_state old_state, + enum ieee80211_sta_state new_state) +{ + struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv; + + /* Add to Morse RC STA list */ + if (old_state < new_state && new_state == IEEE80211_STA_ASSOC) { + /* Newly associated, add to RC */ + mm81x_rc_sta_add(mors, vif, sta); + } else if (old_state > new_state && (old_state == IEEE80211_STA_ASSOC || + old_state == IEEE80211_STA_AUTH)) { + /* Lost or failed association; remove from list */ + mm81x_rc_sta_remove(mors, sta); + } else if (old_state < new_state && old_state == IEEE80211_STA_NONE && + msta->rc.list.prev) { + /* + * Special case for driver warning issue causing a sta to be + * left on the list + */ + dev_dbg(mors->dev, "Remove stale sta from rc list"); + mm81x_rc_sta_remove(mors, sta); + } +} diff --git a/drivers/net/wireless/morsemicro/mm81x/rc.h b/drivers/net/wireless/morsemicro/mm81x/rc.h new file mode 100644 index 000000000000..53f129024408 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/rc.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_RC_H_ +#define _MM81X_RC_H_ + +#include +#include +#include "core.h" +#include "mmrc.h" + +struct mm81x_vif; + +#define INIT_MAX_RATES_NUM 4 + +struct mm81x_rc { + /* Serialise rate control queue manipulation and timer functions */ + spinlock_t lock; + struct list_head stas; + struct timer_list timer; + struct work_struct work; + struct mm81x *mors; +}; + +struct mm81x_rc_sta { + struct mmrc_table *tb; + struct list_head list; + unsigned long last_update; +}; + +void mm81x_rc_init(struct mm81x *mors); +void mm81x_rc_deinit(struct mm81x *mors); +int mm81x_rc_sta_add(struct mm81x *mors, struct ieee80211_vif *vif, + struct ieee80211_sta *sta); +void mm81x_rc_sta_remove(struct mm81x *mors, struct ieee80211_sta *sta); +void mm81x_rc_sta_fill_tx_rates(struct mm81x *mors, + struct mm81x_skb_tx_info *tx_info, + struct sk_buff *skb, struct ieee80211_sta *sta, + int tx_bw, bool rts_allowed); +void mm81x_rc_sta_feedback_rates(struct mm81x *mors, struct sk_buff *skb, + struct ieee80211_sta *sta, + struct mm81x_skb_tx_status *tx_sts, + int tx_attempts); +void mm81x_rc_sta_state_check(struct mm81x *mors, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + enum ieee80211_sta_state old_state, + enum ieee80211_sta_state new_state); + +#endif /* !_MM81X_RC_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/sdio.c b/drivers/net/wireless/morsemicro/mm81x/sdio.c new file mode 100644 index 000000000000..96fce187dd35 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/sdio.c @@ -0,0 +1,613 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hw.h" +#include "core.h" +#include "bus.h" +#include "mac.h" +#include "fw.h" +#include "hif.h" + +/* + * Value to indicate that the base address for bulk/register + * read/writes has yet to be set + */ +#define MM81X_SDIO_BASE_ADDR_UNSET 0xFFFFFFFF + +#define MM81X_SDIO_ALIGNMENT (8) + +#define MM81X_SDIO_REG_ADDRESS_BASE 0x10000 +#define MM81X_SDIO_REG_ADDRESS_WINDOW_0 MM81X_SDIO_REG_ADDRESS_BASE +#define MM81X_SDIO_REG_ADDRESS_WINDOW_1 (MM81X_SDIO_REG_ADDRESS_BASE + 1) +#define MM81X_SDIO_REG_ADDRESS_CONFIG (MM81X_SDIO_REG_ADDRESS_BASE + 2) + +struct mm81x_sdio { + bool enabled; + u32 bulk_addr_base; + u32 register_addr_base; + struct sdio_func *func; + const struct sdio_device_id *id; +}; + +static void irq_handler(struct sdio_func *func1) +{ + struct sdio_func *func = func1->card->sdio_func[1]; + struct mm81x *mors = sdio_get_drvdata(func); + + mm81x_hw_irq_handle(mors); +} + +static int mm81x_sdio_enable_irq(struct mm81x_sdio *sdio) +{ + int ret; + struct sdio_func *func = sdio->func; + struct sdio_func *func1 = func->card->sdio_func[0]; + struct mm81x *mors = sdio_get_drvdata(func); + + sdio_claim_host(func); + ret = sdio_claim_irq(func1, irq_handler); + if (ret) + dev_err(mors->dev, "Failed to enable sdio irq: %d\n", ret); + + sdio_release_host(func); + return ret; +} + +static void mm81x_sdio_disable_irq(struct mm81x_sdio *sdio) +{ + struct sdio_func *func = sdio->func; + struct sdio_func *func1 = func->card->sdio_func[0]; + + sdio_claim_host(func); + sdio_release_irq(func1); + sdio_release_host(func); +} + +static void mm81x_sdio_set_irq(struct mm81x *mors, bool enable) +{ + struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv; + + if (enable) + mm81x_sdio_enable_irq(sdio); + else + mm81x_sdio_disable_irq(sdio); +} + +static u32 mm81x_sdio_calculate_base_address(u32 address, u8 access) +{ + return (address & MM81X_SDIO_RW_ADDR_BOUNDARY_MASK) | (access & 0x3); +} + +static void mm81x_sdio_reset_base_address(struct mm81x_sdio *sdio) +{ + sdio->bulk_addr_base = MM81X_SDIO_BASE_ADDR_UNSET; + sdio->register_addr_base = MM81X_SDIO_BASE_ADDR_UNSET; +} + +static int mm81x_sdio_set_func_address_base(struct mm81x_sdio *sdio, + struct sdio_func *func, u32 address, + u8 access) +{ + int ret = 0; + int retries = 0; + static const int max_retries = 3; + struct sdio_func *func2 = sdio->func; + struct mm81x *mors = sdio_get_drvdata(sdio->func); + s32 calculated_addr_base = + mm81x_sdio_calculate_base_address(address, access); + u32 *current_addr_base = func == func2 ? &sdio->bulk_addr_base : + &sdio->register_addr_base; + + if ((*current_addr_base) == calculated_addr_base && + *current_addr_base != MM81X_SDIO_BASE_ADDR_UNSET) + return ret; + +retry: + sdio_writeb(func, (u8)u32_get_bits(address, GENMASK(23, 16)), + MM81X_SDIO_REG_ADDRESS_WINDOW_0, &ret); + if (ret) + goto err; + + sdio_writeb(func, (u8)u32_get_bits(address, GENMASK(31, 24)), + MM81X_SDIO_REG_ADDRESS_WINDOW_1, &ret); + if (ret) + goto err; + + sdio_writeb(func, access & 0x3, MM81X_SDIO_REG_ADDRESS_CONFIG, &ret); + if (ret) + goto err; + + *current_addr_base = calculated_addr_base; + if (retries) + dev_dbg(mors->dev, "%s succeeded after %d retries\n", __func__, + retries); + + return ret; +err: + retries++; + if (ret == -ETIMEDOUT && retries <= max_retries) { + dev_dbg(mors->dev, "%s failed (%d), retrying (%d/%d)\n", + __func__, ret, retries, max_retries); + goto retry; + } + + *current_addr_base = MM81X_SDIO_BASE_ADDR_UNSET; + return ret; +} + +static int mm81x_sdio_mem_write_block(struct mm81x_sdio *sdio, u32 address, + u8 *data, ssize_t size) +{ + int ret; + struct sdio_func *func2 = sdio->func; + struct mm81x *mors = sdio_get_drvdata(sdio->func); + + mm81x_sdio_set_func_address_base(sdio, func2, address, + MM81X_CONFIG_ACCESS_4BYTE); + if (unlikely(!IS_ALIGNED((uintptr_t)data, + mors->bus_ops->bulk_alignment))) { + ret = -EBADE; + goto exit; + } + + address &= 0x0000FFFF; /* remove base and keep offset */ + ret = sdio_memcpy_toio(func2, address, data, size); + if (ret) + goto exit; + + ret = size; +exit: + return ret; +} + +static int mm81x_sdio_mem_write_byte(struct mm81x_sdio *sdio, u32 address, + u8 *data, ssize_t size) +{ + int i, ret; + struct sdio_func *func1 = sdio->func->card->sdio_func[0]; + + mm81x_sdio_set_func_address_base(sdio, func1, address, + MM81X_CONFIG_ACCESS_1BYTE); + + address &= 0x0000FFFF; /* remove base and keep offset */ + for (i = 0; i < size; i++) { + sdio_writeb(func1, data[i], address + i, (int *)&ret); + if (ret) + goto exit; + } + + ret = size; +exit: + return ret; +} + +static void mm81x_sdio_claim_host(struct mm81x *mors) +{ + struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv; + struct sdio_func *func = sdio->func; + + sdio_claim_host(func); +} + +static void mm81x_sdio_release_host(struct mm81x *mors) +{ + struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv; + struct sdio_func *func = sdio->func; + + sdio_release_host(func); +} + +static int mm81x_sdio_mem_read_block(struct mm81x_sdio *sdio, u32 address, + u8 *data, ssize_t size) +{ + int ret; + struct sdio_func *func2 = sdio->func; + struct mm81x *mors = sdio_get_drvdata(sdio->func); + + mm81x_sdio_set_func_address_base(sdio, func2, address, + MM81X_CONFIG_ACCESS_4BYTE); + if (unlikely(!IS_ALIGNED((uintptr_t)data, + mors->bus_ops->bulk_alignment))) { + ret = -EBADE; + goto exit; + } + + address &= 0x0000FFFF; /* remove base and keep offset */ + ret = sdio_memcpy_fromio(func2, data, address, size); + if (ret) + goto exit; + + /* + * Observed sometimes that SDIO read repeats the first 4-bytes + * word twice, overwriting second word (hence, tail will be + * overwritten with 'sync' byte). When this happens, reading + * will fetch the correct word. NB: if repeated again, pass it + * anyway and upper layers will handle it + */ + + if (size >= 8 && memcmp(data, data + 4, 4) == 0) + sdio_memcpy_fromio(func2, data, address, 8); + + ret = size; +exit: + return ret; +} + +static int mm81x_sdio_mem_read_byte(struct mm81x_sdio *sdio, u32 address, + u8 *data, ssize_t size) +{ + int i, ret; + struct sdio_func *func1 = sdio->func->card->sdio_func[0]; + + mm81x_sdio_set_func_address_base(sdio, func1, address, + MM81X_CONFIG_ACCESS_1BYTE); + + address &= 0x0000FFFF; /* remove base and keep offset */ + for (i = 0; i < size; i++) { + data[i] = sdio_readb(func1, address + i, (int *)&ret); + if (ret) + goto exit; + } + + ret = size; +exit: + return ret; +} + +static int mm81x_sdio_dm_write(struct mm81x *mors, u32 address, const u8 *data, + int len) +{ + int ret = 0; + int block_len, byte_len; + struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv; + int remaining = len; + int offset = 0; + + if (remaining > 0 && address & 0x3) { + len = 4 - (address & 0x3); + ret = mm81x_sdio_mem_write_byte(sdio, address, (u8 *)data, len); + if (ret != len) + return -EIO; + + offset += len; + remaining -= len; + } + + while ((remaining) > 0) { + /* + * We can only write up to the end of a single window in + * each write operation. + */ + u32 window_end = (address + offset) | + ~MM81X_SDIO_RW_ADDR_BOUNDARY_MASK; + + len = min(remaining, (int)(window_end + 1 - address - offset)); + block_len = len & ~0x3; + byte_len = len & 0x3; + + if (block_len) { + ret = mm81x_sdio_mem_write_block(sdio, address + offset, + (u8 *)(data + offset), + block_len); + if (ret != block_len) + return -EIO; + + offset += block_len; + } + + if (byte_len) { + ret = mm81x_sdio_mem_write_byte(sdio, address + offset, + (u8 *)(data + offset), + byte_len); + if (ret != byte_len) + return -EIO; + + offset += byte_len; + } + + remaining -= len; + } + + return 0; +} + +static int mm81x_sdio_dm_read(struct mm81x *mors, u32 address, u8 *data, + int len) +{ + int ret = 0; + int block_len, byte_len; + struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv; + int remaining = len; + int offset = 0; + + if (remaining > 0 && address & 0x3) { + len = 4 - (address & 0x3); + ret = mm81x_sdio_mem_read_byte(sdio, address, data, len); + if (ret != len) + return -EIO; + + offset += len; + remaining -= len; + } + + while (remaining > 0) { + /* + * We can only read up to the end of a single window in + * each read operation. + */ + u32 window_end = (address + offset) | + ~MM81X_SDIO_RW_ADDR_BOUNDARY_MASK; + + len = min(remaining, (int)(window_end + 1 - address - offset)); + block_len = len & ~0x3; + byte_len = len & 0x3; + + if (block_len) { + ret = mm81x_sdio_mem_read_block(sdio, address + offset, + data + offset, + block_len); + if (ret != block_len) + return -EIO; + + offset += block_len; + } + + if (byte_len) { + ret = mm81x_sdio_mem_read_byte(sdio, address + offset, + data + offset, byte_len); + if (ret != byte_len) + return -EIO; + + offset += byte_len; + } + + remaining -= len; + } + + return 0; +} + +static int mm81x_sdio_reg32_write(struct mm81x *mors, u32 address, u32 val) +{ + ssize_t ret = 0; + u32 original_address = address; + struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv; + struct sdio_func *func1 = sdio->func->card->sdio_func[0]; + + mm81x_sdio_set_func_address_base(sdio, func1, address, + MM81X_CONFIG_ACCESS_4BYTE); + + address &= 0x0000FFFF; + sdio_writel(func1, (__force u32)cpu_to_le32(val), + (__force u32)cpu_to_le32(address), (int *)&ret); + if (ret) + goto error; + + return 0; + +error: + if (original_address == MM81X_REG_RESET(mors) && + val == MM81X_REG_RESET_VALUE(mors)) { + dev_dbg(mors->dev, + "SDIO reset detected, invalidating base addr\n"); + mm81x_sdio_reset_base_address(sdio); + } + + return -EIO; +} + +static int mm81x_sdio_reg32_read(struct mm81x *mors, u32 address, u32 *val) +{ + u32 value; + ssize_t ret = 0; + struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv; + struct sdio_func *func1 = sdio->func->card->sdio_func[0]; + + mm81x_sdio_set_func_address_base(sdio, func1, address, + MM81X_CONFIG_ACCESS_4BYTE); + + address &= 0x0000FFFF; + value = sdio_readl(func1, (__force u32)cpu_to_le32(address), + (int *)&ret); + if (ret) + return ret; + + *val = le32_to_cpup((__le32 *)&value); + return 0; +} + +static void mm81x_sdio_bus_enable(struct mm81x *mors, bool enable) +{ + struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv; + struct sdio_func *func = sdio->func; + struct mmc_host *host = func->card->host; + + sdio_claim_host(func); + + if (enable) { + /* + * No need to do anything special to re-enable the sdio bus. + * This will happen automatically when a read/write is + * attempted and sdio->bulk_addr_base == 0. + */ + sdio->enabled = true; + host->ops->enable_sdio_irq(host, 1); + dev_dbg(mors->dev, "%s: enabling bus\n", __func__); + } else { + host->ops->enable_sdio_irq(host, 0); + mm81x_sdio_reset_base_address(sdio); + sdio->enabled = false; + dev_dbg(mors->dev, "%s: disabling bus\n", __func__); + } + + sdio_release_host(func); +} + +static void mm81x_sdio_reset(struct sdio_func *func) +{ + sdio_claim_host(func); + sdio_disable_func(func); + sdio_release_host(func); + + mdelay(20); + + sdio_claim_host(func); + sdio_disable_func(func); + mmc_hw_reset(func->card); + sdio_enable_func(func); + sdio_release_host(func); +} + +static void mm81x_sdio_config_burst_mode(struct mm81x *mors, bool enable_burst) +{ + u8 burst_mode = (enable_burst) ? SDIO_WORD_BURST_SIZE_16 : + SDIO_WORD_BURST_DISABLE; + + mm81x_hw_enable_burst_mode(mors, burst_mode); +} + +static const struct mm81x_bus_ops mm81x_sdio_ops = { + .dm_read = mm81x_sdio_dm_read, + .dm_write = mm81x_sdio_dm_write, + .reg32_read = mm81x_sdio_reg32_read, + .reg32_write = mm81x_sdio_reg32_write, + .set_bus_enable = mm81x_sdio_bus_enable, + .claim = mm81x_sdio_claim_host, + .release = mm81x_sdio_release_host, + .config_burst_mode = mm81x_sdio_config_burst_mode, + .set_irq = mm81x_sdio_set_irq, + .bulk_alignment = MM81X_SDIO_ALIGNMENT +}; + +static int mm81x_sdio_enable(struct mm81x_sdio *sdio) +{ + int ret; + struct sdio_func *func = sdio->func; + struct mm81x *mors = sdio_get_drvdata(func); + + sdio_claim_host(func); + ret = sdio_enable_func(func); + if (ret) + dev_err(mors->dev, "sdio_enable_func failed: %d\n", ret); + sdio_release_host(func); + return ret; +} + +static void mm81x_sdio_release(struct mm81x_sdio *sdio) +{ + struct sdio_func *func = sdio->func; + + sdio_claim_host(func); + sdio_disable_func(func); + sdio_release_host(func); +} + +static int mm81x_sdio_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + int ret = 0; + struct mm81x *mors = NULL; + struct mm81x_sdio *sdio; + struct device *dev = &func->dev; + + if (func->num == 1) + return 0; + + if (func->num != 2) + return -ENODEV; + + mors = mm81x_core_alloc(sizeof(*sdio), dev); + if (!mors) + return -ENOMEM; + + mors->bus_ops = &mm81x_sdio_ops; + mors->bus_type = MM81X_BUS_TYPE_SDIO; + + sdio = (struct mm81x_sdio *)mors->drv_priv; + sdio->func = func; + sdio->id = id; + sdio->enabled = true; + mm81x_sdio_reset_base_address(sdio); + + sdio_set_drvdata(func, mors); + + ret = mm81x_sdio_enable(sdio); + if (ret) + goto err_core_free; + + mm81x_sdio_config_burst_mode(mors, true); + + ret = mm81x_core_init(mors); + if (ret) + goto err_sdio_release; + + ret = mm81x_sdio_enable_irq(sdio); + if (ret) + goto err_core_deinit; + + ret = mm81x_core_register(mors); + if (ret) + goto err_disable_irq; + + return 0; + +err_disable_irq: + mm81x_sdio_disable_irq(sdio); +err_core_deinit: + mm81x_core_deinit(mors); +err_sdio_release: + mm81x_sdio_release(sdio); +err_core_free: + mm81x_core_free(mors); + return ret; +} + +static void mm81x_sdio_remove(struct sdio_func *func) +{ + struct mm81x *mors = sdio_get_drvdata(func); + struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv; + + if (!mors) + return; + + mm81x_core_unregister(mors); + mm81x_sdio_disable_irq(sdio); + mm81x_core_deinit(mors); + mm81x_sdio_release(sdio); + mm81x_sdio_reset(func); + mm81x_core_free(mors); + sdio_set_drvdata(func, NULL); +} + +static const struct sdio_device_id mm81x_sdio_devices[] = { + { SDIO_DEVICE(SDIO_VENDOR_ID_MORSEMICRO, + SDIO_DEVICE_ID_MORSEMICRO_MM8108) }, + {}, +}; + +MODULE_DEVICE_TABLE(sdio, mm81x_sdio_devices); + +static struct sdio_driver mm81x_sdio_driver = { + .name = "mm81x_sdio", + .id_table = mm81x_sdio_devices, + .probe = mm81x_sdio_probe, + .remove = mm81x_sdio_remove, +}; + +module_sdio_driver(mm81x_sdio_driver); + +MODULE_AUTHOR("Morse Micro"); +MODULE_DESCRIPTION("Driver support for Morse Micro MM81X SDIO devices"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/wireless/morsemicro/mm81x/skbq.c b/drivers/net/wireless/morsemicro/mm81x/skbq.c new file mode 100644 index 000000000000..25655bd56d14 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/skbq.c @@ -0,0 +1,1064 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include +#include +#include +#include +#include +#include +#include "hif.h" +#include "skbq.h" +#include "mac.h" +#include "command.h" +#include "bus.h" + +/* Returns number of bytes needed to word align */ +#define BYTES_NEEDED_TO_WORD_ALIGN(bytes) \ + ((bytes) & 0x3 ? (4 - ((bytes) & 0x3)) : 0) + +/* Rounds down to the nearest word boundary */ +#define ROUND_DOWN_TO_WORD(bytes) \ + (BYTES_NEEDED_TO_WORD_ALIGN(bytes) ? \ + bytes - (4 - BYTES_NEEDED_TO_WORD_ALIGN(bytes)) : \ + bytes) + +#define MM81X_SKBQ_MAX_TXQ_LEN 32 +#define MM81X_SKBQ_TX_QUEUED_LIFETIME_MS 1000 +#define MM81X_SKBQ_TX_STATUS_LIFETIME_MS (15 * 1000) + +/* Returns padding needed to align x up to a 4-byte boundary */ +#define MM81X_PAD4(x) (((x) & 0x3) ? (4 - ((x) & 0x3)) : 0) + +struct mm81x_tx_status_priv { + /* + * Time (jiffies) at which this packet has spent too long the pending + * queue, waiting for status notification from the firmware, and + * should be considered lost. + */ + unsigned long tx_status_expiry; +}; + +static struct mm81x_tx_status_priv * +__mm81x_skbq_tx_status_priv(struct sk_buff *skb) +{ + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + + BUILD_BUG_ON(sizeof(struct mm81x_tx_status_priv) > + sizeof(tx_info->status.status_driver_data)); + return (struct mm81x_tx_status_priv *)&tx_info->status + .status_driver_data[0]; +} + +static bool __mm81x_skbq_has_pending_tx_skb_timed_out(struct sk_buff *skb) +{ + struct mm81x_tx_status_priv *info = __mm81x_skbq_tx_status_priv(skb); + + /* If our timestamp value is in the past then we have timed out. */ + return time_is_before_jiffies(info->tx_status_expiry); +} + +static u32 __mm81x_skbq_size(const struct mm81x_skbq *mq) +{ + return mq->skbq_size; +} + +static u32 __mm81x_skbq_space(const struct mm81x_skbq *mq) +{ + return MM81X_SKBQ_SIZE - __mm81x_skbq_size(mq); +} + +static bool __mm81x_skbq_over_threshold(struct mm81x_skbq *mq) +{ + return skb_queue_len(&mq->skbq) >= MM81X_SKBQ_MAX_TXQ_LEN; +} + +static bool __mm81x_skbq_under_threshold(struct mm81x_skbq *mq) +{ + return skb_queue_len(&mq->skbq) < (MM81X_SKBQ_MAX_TXQ_LEN - 2); +} + +static void __mm81x_skbq_unlink(struct mm81x_skbq *mq, + struct sk_buff_head *queue, struct sk_buff *skb) +{ + if (queue == &mq->skbq) { + WARN_ON(skb->len > mq->skbq_size); + mq->skbq_size -= min(skb->len, mq->skbq_size); + } + + __skb_unlink(skb, queue); +} + +static int __mm81x_skbq_put(struct mm81x_skbq *mq, struct sk_buff_head *queue, + struct sk_buff *skb, bool queue_at_head, + struct sk_buff *queue_before) +{ + /* Limit the size of the Tx queue, but not the pending queue */ + if (queue == &mq->skbq) { + if (skb->len > __mm81x_skbq_space(mq)) + return -ENOMEM; + + mq->skbq_size += skb->len; + } + + if (queue_before) + __skb_queue_before(queue, queue_before, skb); + else if (queue_at_head) + __skb_queue_head(queue, skb); + else + __skb_queue_tail(queue, skb); + + return 0; +} + +static void __mm81x_skbq_pkt_id(struct mm81x_skbq *mq, struct sk_buff *skb) +{ + struct mm81x_skb_hdr *hdr = (struct mm81x_skb_hdr *)skb->data; + + hdr->tx_info.pkt_id = cpu_to_le32(mq->pkt_seq++); +} + +static struct mm81x_skbq * +__mm81x_skbq_tx_status_to_skbq(struct mm81x *mors, + const struct mm81x_skb_tx_status *tx_sts) +{ + int aci; + struct mm81x_skbq *mq = NULL; + + switch (tx_sts->channel) { + case MM81X_SKB_CHAN_DATA: + case MM81X_SKB_CHAN_DATA_NOACK: + aci = dot11_tid_to_ac(tx_sts->tid); + mq = mm81x_hif_get_tx_data_queue(mors, aci); + break; + case MM81X_SKB_CHAN_MGMT: + mq = mm81x_hif_get_tx_mgmt_queue(mors); + break; + case MM81X_SKB_CHAN_BEACON: + mq = mm81x_hif_get_tx_beacon_queue(mors); + break; + default: + dev_err(mors->dev, + "unexpected channel on reported tx status [%d]", + tx_sts->channel); + } + + return mq; +} + +void mm81x_skbq_pull_hdr_post_tx(struct sk_buff *skb) +{ + skb_pull(skb, sizeof(struct mm81x_skb_hdr) + + ((struct mm81x_skb_hdr *)skb->data)->offset); +} + +static void mm81x_skbq_insert_pending(struct mm81x_skbq *mq, + struct sk_buff *skb, __le32 insertion_id) +{ + struct sk_buff *pfirst, *pnext; + struct mm81x_skb_hdr *mhdr; + struct sk_buff *tail = skb_peek_tail(&mq->skbq); + + __mm81x_skbq_unlink(mq, &mq->pending, skb); + + if (!tail) { + __mm81x_skbq_put(mq, &mq->skbq, skb, false, NULL); + return; + } + + /* Check if it should just be inserted on to the end */ + mhdr = (struct mm81x_skb_hdr *)tail->data; + WARN_ON(insertion_id == mhdr->tx_info.pkt_id); + if (le32_to_cpu(insertion_id) >= le32_to_cpu(mhdr->tx_info.pkt_id)) { + __mm81x_skbq_put(mq, &mq->skbq, skb, false, NULL); + return; + } + + /* Otherwise, re-insert to correct spot in skbq */ + skb_queue_walk_safe(&mq->skbq, pfirst, pnext) { + mhdr = (struct mm81x_skb_hdr *)pfirst->data; + + WARN_ON(insertion_id == mhdr->tx_info.pkt_id); + if (le32_to_cpu(insertion_id) <= + le32_to_cpu(mhdr->tx_info.pkt_id)) { + __mm81x_skbq_put(mq, &mq->skbq, skb, false, pfirst); + return; + } + } + + WARN_ON_ONCE(1); +} + +static void mm81x_skbq_sta_eosp(struct mm81x *mors, struct sk_buff *skb) +{ + struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb); + struct ieee80211_vif *vif = txi->control.vif; + + mm81x_skbq_pull_hdr_post_tx(skb); + + /* + * If this frame is the last frame in a PS-Poll or u-APSD SP, + * then mac80211 must be informed that the SP is now over. + */ + if (txi->flags & IEEE80211_TX_STATUS_EOSP) { + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct ieee80211_sta *sta; + + scoped_guard(rcu) { + sta = ieee80211_find_sta(vif, hdr->addr1); + if (sta) + ieee80211_sta_eosp(sta); + } + } +} + +static void __mm81x_skbq_drop_pending_skb(struct mm81x_skbq *mq, + struct sk_buff *skb) +{ + __mm81x_skbq_unlink(mq, &mq->pending, skb); + mm81x_skbq_sta_eosp(mq->mors, skb); + ieee80211_free_txskb(mq->mors->hw, skb); +} + +static bool mm81x_tx_h_is_ps_filtered(struct mm81x_skbq *mq, + struct sk_buff *skb, + struct mm81x_skb_tx_status *tx_sts) +{ + struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb); + struct ieee80211_vif *vif = txi->control.vif; + + WARN_ON_ONCE(!(le32_to_cpu(tx_sts->flags) & + MM81X_TX_STATUS_FLAGS_PS_FILTERED)); + + if (vif->type == NL80211_IFTYPE_AP) { + __mm81x_skbq_drop_pending_skb(mq, skb); + return true; + } + + if (vif->type == NL80211_IFTYPE_STATION) { + mm81x_skbq_insert_pending(mq, skb, tx_sts->pkt_id); + return true; + } + + return false; +} + +/* + * Get a pending frame by its ID. This will also drop frames with + * older packet ids that are in the list + */ +static struct sk_buff *__mm81x_skbq_get_pending_by_id(struct mm81x *mors, + struct mm81x_skbq *mq, + u32 pkt_id) +{ + struct sk_buff *pfirst, *pnext; + struct sk_buff *ret = NULL; + + /* Move sent packets to pending list waiting for feedback */ + skb_queue_walk_safe(&mq->pending, pfirst, pnext) { + struct mm81x_skb_hdr *hdr = + (struct mm81x_skb_hdr *)pfirst->data; + + if (le32_to_cpu(hdr->tx_info.pkt_id) == pkt_id) { + ret = pfirst; + break; + + } else if (le32_to_cpu(hdr->tx_info.pkt_id) < pkt_id && + __mm81x_skbq_has_pending_tx_skb_timed_out(pfirst)) { + __mm81x_skbq_drop_pending_skb(mq, pfirst); + } + } + + return ret; +} + +static void mm81x_skbq_check_tx_empty(struct mm81x *mors, struct mm81x_skbq *mq) +{ + lockdep_assert_held(&mq->lock); + + if (mq->flags & MM81X_HIF_FLAGS_BEACON) + return; + + if (skb_queue_len(&mq->skbq) + skb_queue_len(&mq->pending) == 0) + wake_up(&mq->mors->tx_empty_waitq); +} + +static void __mm81x_skbq_tx_status_process(struct mm81x *mors, + struct mm81x_skbq *mq, + struct mm81x_skb_tx_status *tx_sts) +{ + struct sk_buff *skb; + + lockdep_assert_held(&mq->lock); + + skb = __mm81x_skbq_get_pending_by_id(mors, mq, + le32_to_cpu(tx_sts->pkt_id)); + if (!skb) { + dev_dbg(mors->dev, + "No pending pkt match found [pktid:%d chan:%d]", + tx_sts->pkt_id, tx_sts->channel); + goto out; + } + + if (le32_to_cpu(tx_sts->flags) & MM81X_TX_STATUS_PAGE_INVALID) { + __mm81x_skbq_drop_pending_skb(mq, skb); + goto out; + } + + if (le32_to_cpu(tx_sts->flags) & MM81X_TX_STATUS_FLAGS_PS_FILTERED && + mm81x_tx_h_is_ps_filtered(mq, skb, tx_sts)) + /* Has been consumed by mm81x_tx_h_is_ps_filtered */ + goto out; + + mm81x_skbq_pull_hdr_post_tx(skb); + mm81x_skbq_skb_finish(mq, skb, tx_sts); + +out: + mm81x_skbq_check_tx_empty(mors, mq); +} + +static void mm81x_skbq_tx_status_process(struct mm81x *mors, + struct sk_buff *skb) +{ + int i; + struct mm81x_skb_tx_status *tx_sts = + (struct mm81x_skb_tx_status *)skb->data; + int count = skb->len / sizeof(*tx_sts); + + for (i = 0; i < count; tx_sts++, i++) { + struct mm81x_skbq *mq = + __mm81x_skbq_tx_status_to_skbq(mors, tx_sts); + + if (mq) { + spin_lock_bh(&mq->lock); + __mm81x_skbq_tx_status_process(mors, mq, tx_sts); + spin_unlock_bh(&mq->lock); + } + } + + if (mors->ps.enable && !mors->ps.suspended && + (mm81x_hif_get_tx_buffered_count(mors) == 0)) { + /* Evaluate ps, check if it was gated on a pending tx status */ + queue_delayed_work(mors->chip_wq, &mors->ps.delayed_eval_work, + 0); + } +} + +static void mm81x_skbq_dispatch_work(struct work_struct *dispatch_work) +{ + struct mm81x_skbq *mq = + container_of(dispatch_work, struct mm81x_skbq, dispatch_work); + struct mm81x *mors = mq->mors; + struct mm81x_skb_hdr *hdr; + struct sk_buff_head skbq; + struct sk_buff *pfirst, *pnext; + u8 channel; + + __skb_queue_head_init(&skbq); + + mm81x_skbq_deq_num_skb(mq, &skbq, mm81x_skbq_count(mq)); + + skb_queue_walk_safe(&skbq, pfirst, pnext) { + __skb_unlink(pfirst, &skbq); + /* Header endianness has already be adjusted */ + hdr = (struct mm81x_skb_hdr *)pfirst->data; + channel = hdr->channel; + /* Remove mm81x header and padding */ + __skb_pull(pfirst, sizeof(*hdr) + hdr->offset); + + switch (channel) { + case MM81X_SKB_CHAN_COMMAND: + mm81x_cmd_resp_process(mors, pfirst); + break; + case MM81X_SKB_CHAN_TX_STATUS: + mm81x_skbq_tx_status_process(mors, pfirst); + dev_kfree_skb_any(pfirst); + break; + default: + mm81x_mac_rx_skb(mors, pfirst, &hdr->rx_status); + break; + } + } + + if (mm81x_skbq_count(mq)) + queue_work(mors->net_wq, &mq->dispatch_work); +} + +int mm81x_skbq_put(struct mm81x_skbq *mq, struct sk_buff *skb) +{ + int ret; + + spin_lock_bh(&mq->lock); + ret = __mm81x_skbq_put(mq, &mq->skbq, skb, false, NULL); + spin_unlock_bh(&mq->lock); + return ret; +} + +static void mm81x_skbq_set_queued_tx_skb_expiry(struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb); + + if (ieee80211_is_probe_req(hdr->frame_control) || + ieee80211_is_probe_resp(hdr->frame_control) || + ieee80211_is_auth(hdr->frame_control)) { + txi->control.enqueue_time = (u32)jiffies; + } else { + txi->control.enqueue_time = 0; + } +} + +static bool mm81x_skbq_has_queued_tx_skb_expired(struct sk_buff *skb) +{ + struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb); + + if (txi->control.enqueue_time > 0) { + u32 expiry_time = + txi->control.enqueue_time + + msecs_to_jiffies(MM81X_SKBQ_TX_QUEUED_LIFETIME_MS); + + return (s32)((u32)jiffies - expiry_time) > 0; + } + + return false; +} + +/* + * Drop selected frames (those with an expiry time set) that could not + * be sent within a reasonable timeframe due to congestion. These would + * only be rejected or ignored by the peer, so are only contributing to + * the problem. + */ +void mm81x_skbq_purge_aged(struct mm81x *mors, struct mm81x_skbq *mq) +{ + struct sk_buff *pfirst; + struct sk_buff *pnext; + + spin_lock_bh(&mq->lock); + skb_queue_walk_safe(&mq->skbq, pfirst, pnext) { + if (!mm81x_skbq_has_queued_tx_skb_expired(pfirst)) + break; + __mm81x_skbq_unlink(mq, &mq->skbq, pfirst); + ieee80211_free_txskb(mors->hw, pfirst); + } + + spin_unlock_bh(&mq->lock); +} + +void mm81x_skbq_purge(struct mm81x_skbq *mq, struct sk_buff_head *skbq) +{ + struct sk_buff *skb; + + spin_lock_bh(&mq->lock); + while ((skb = __skb_dequeue(skbq))) + dev_kfree_skb_any(skb); + spin_unlock_bh(&mq->lock); +} + +void mm81x_skbq_enq(struct mm81x_skbq *mq, struct sk_buff_head *skbq) +{ + int size; + struct sk_buff *pfirst, *pnext; + + spin_lock_bh(&mq->lock); + size = __mm81x_skbq_space(mq); + skb_queue_walk_safe(skbq, pfirst, pnext) { + if (pfirst->len > size) + break; + __skb_unlink(pfirst, skbq); + __mm81x_skbq_put(mq, &mq->skbq, pfirst, false, NULL); + size -= pfirst->len; + } + + spin_unlock_bh(&mq->lock); +} + +int mm81x_skbq_deq_num_skb(struct mm81x_skbq *mq, struct sk_buff_head *skbq, + int num_skb) +{ + int count = 0; + struct sk_buff *pfirst, *pnext; + + spin_lock_bh(&mq->lock); + skb_queue_walk_safe(&mq->skbq, pfirst, pnext) { + if (count >= num_skb) + break; + __mm81x_skbq_unlink(mq, &mq->skbq, pfirst); + __skb_queue_tail(skbq, pfirst); + ++count; + } + + spin_unlock_bh(&mq->lock); + return count; +} + +void mm81x_skbq_enq_prepend(struct mm81x_skbq *mq, struct sk_buff_head *skbq) +{ + int size; + struct sk_buff *pfirst, *pnext; + + spin_lock_bh(&mq->lock); + size = __mm81x_skbq_space(mq); + + /* + * We are doing a reverse walk here to ensure the order remains the + * same. This means the last member of the queue goes in, on top of + * the queue first and gets pushed down as more members get added to + * the top of the queue. + */ + skb_queue_reverse_walk_safe(skbq, pfirst, pnext) { + if (pfirst->len > size) + break; + __skb_unlink(pfirst, skbq); + __mm81x_skbq_put(mq, &mq->skbq, pfirst, true, NULL); + size -= pfirst->len; + } + + spin_unlock_bh(&mq->lock); +} + +static void mm81x_skbq_stop_tx_queues(struct mm81x *mors) +{ + int queue; + + if (!mors->started) + return; + for (queue = IEEE80211_AC_VO; queue <= IEEE80211_AC_BK; queue++) + ieee80211_stop_queue(mors->hw, queue); + + set_bit(MM81X_STATE_DATA_QS_STOPPED, &mors->state_flags); +} + +/* Wake all Tx queues if all queues are below threshold */ +void mm81x_skbq_may_wake_tx_queues(struct mm81x *mors) +{ + int queue; + struct mm81x_skbq *qs; + int num_qs; + bool could_wake; + + if (!mors->started) + return; + + could_wake = true; + mm81x_hif_skbq_get_tx_qs(mors, &qs, &num_qs); + for (queue = 0; queue < num_qs; queue++) { + struct mm81x_skbq *mq = &qs[queue]; + + if (!could_wake) + break; + + spin_lock_bh(&mq->lock); + could_wake &= (__mm81x_skbq_under_threshold(mq)); + spin_unlock_bh(&mq->lock); + } + + if (!could_wake) + return; + + for (queue = IEEE80211_AC_VO; queue <= IEEE80211_AC_BK; queue++) + ieee80211_wake_queue(mors->hw, queue); + + clear_bit(MM81X_STATE_DATA_QS_STOPPED, &mors->state_flags); +} + +static int mm81x_skbq_tx(struct mm81x_skbq *mq, struct sk_buff *skb, u8 channel) +{ + int rc; + bool mq_over_threshold; + struct mm81x *mors = mq->mors; + + spin_lock_bh(&mq->lock); + rc = __mm81x_skbq_put(mq, &mq->skbq, skb, false, NULL); + if (rc) { + dev_err(mors->dev, "skb put chan %d failed (%d)", channel, rc); + if (channel == MM81X_SKB_CHAN_DATA) { + u16 queue = skb_get_queue_mapping(skb); + + dev_err(mors->dev, "skb put queue %d status %d", queue, + ieee80211_queue_stopped(mors->hw, queue)); + } + } + + /* Fill packet ID in TX info */ + __mm81x_skbq_pkt_id(mq, skb); + + mq_over_threshold = __mm81x_skbq_over_threshold(mq); + spin_unlock_bh(&mq->lock); + + /* For data packets stop queues */ + if (channel == MM81X_SKB_CHAN_DATA && mq_over_threshold) + mm81x_skbq_stop_tx_queues(mors); + + switch (channel) { + case MM81X_SKB_CHAN_DATA: + case MM81X_SKB_CHAN_DATA_NOACK: + if (mm81x_is_data_tx_allowed(mors)) { + set_bit(MM81X_HIF_EVT_TX_DATA_PEND, + &mors->hif.event_flags); + queue_work(mors->chip_wq, &mors->hif_work); + } + break; + case MM81X_SKB_CHAN_MGMT: + set_bit(MM81X_HIF_EVT_TX_MGMT_PEND, &mors->hif.event_flags); + queue_work(mors->chip_wq, &mors->hif_work); + break; + case MM81X_SKB_CHAN_BEACON: + set_bit(MM81X_HIF_EVT_TX_BEACON_PEND, &mors->hif.event_flags); + queue_work(mors->chip_wq, &mors->hif_work); + break; + case MM81X_SKB_CHAN_COMMAND: + set_bit(MM81X_HIF_EVT_TX_COMMAND_PEND, &mors->hif.event_flags); + queue_work(mors->chip_wq, &mors->hif_work); + break; + default: + dev_err(mors->dev, "Invalid skb channel: %d", channel); + break; + } + + return rc; +} + +static void __mm81x_skbq_tx_move_to_pending(struct mm81x_skbq *mq, + struct sk_buff *skb) +{ + struct mm81x_tx_status_priv *pend_info = + __mm81x_skbq_tx_status_priv(skb); + + pend_info->tx_status_expiry = + jiffies + msecs_to_jiffies(MM81X_SKBQ_TX_STATUS_LIFETIME_MS); + __mm81x_skbq_put(mq, &mq->pending, skb, false, NULL); +} + +void mm81x_skbq_tx_complete(struct mm81x_skbq *mq, struct sk_buff_head *skbq) +{ + bool skb_awaits_tx_status = false; + struct mm81x *mors = mq->mors; + struct sk_buff *pfirst, *pnext; + struct sk_buff *peek = skb_peek(skbq); + struct mm81x_skb_hdr *hdr; + const bool fw_reports_bcn_tx_status = + mors->fw_flags & MM81X_FW_FLAGS_REPORTS_TX_BEACON_COMPLETION; + + if (!peek) + return; + + /* Move sent packets to pending list waiting for feedback */ + spin_lock_bh(&mq->lock); + skb_queue_walk_safe(skbq, pfirst, pnext) { + __skb_unlink(pfirst, skbq); + hdr = (struct mm81x_skb_hdr *)pfirst->data; + /* + * If firmware doesn't give status on beacons just free + * them, otherwise queue and wait for response. + */ + switch (hdr->channel) { + case MM81X_SKB_CHAN_BEACON: + if (fw_reports_bcn_tx_status) { + __mm81x_skbq_tx_move_to_pending(mq, pfirst); + skb_awaits_tx_status = true; + break; + } + /* + * If the FW doesn't give statuses on beacon's, + * then mark them as done. + */ + mm81x_skbq_pull_hdr_post_tx(pfirst); + dev_kfree_skb_any(pfirst); + break; + default: + if (le32_to_cpu(hdr->tx_info.flags) & + MM81X_TX_STATUS_FLAGS_NO_REPORT) { + dev_kfree_skb_any(pfirst); + } else { + /* + * skb has been given to the chip. Store the + * time and queue the skb onto the pending + * queue while we wait for the tx_status. + */ + __mm81x_skbq_tx_move_to_pending(mq, pfirst); + skb_awaits_tx_status = true; + } + break; + } + } + spin_unlock_bh(&mq->lock); + + if (skb_awaits_tx_status) { + spin_lock_bh(&mors->stale_status.lock); + mod_timer(&mors->stale_status.timer, + jiffies + msecs_to_jiffies( + MM81X_SKBQ_TX_STATUS_LIFETIME_MS)); + spin_unlock_bh(&mors->stale_status.lock); + } +} + +/* Returns the first skb in the pending list. */ +struct sk_buff *mm81x_skbq_tx_pending(struct mm81x_skbq *mq) +{ + struct sk_buff *pfirst; + + spin_lock_bh(&mq->lock); + pfirst = skb_peek(&mq->pending); + spin_unlock_bh(&mq->lock); + return pfirst; +} + +int mm81x_skbq_check_for_stale_tx(struct mm81x *mors, struct mm81x_skbq *mq) +{ + int flushed = 0; + struct sk_buff *pfirst; + struct sk_buff *pnext; + + if (!skb_queue_len(&mq->pending)) + return 0; + + /* Move sent packets to pending list waiting for feedback */ + spin_lock_bh(&mq->lock); + skb_queue_walk_safe(&mq->pending, pfirst, pnext) { + struct mm81x_skb_hdr *hdr = + (struct mm81x_skb_hdr *)pfirst->data; + + if (__mm81x_skbq_has_pending_tx_skb_timed_out(pfirst)) { + dev_dbg(mors->dev, "TX skb timed out [id:%d,chan:%d]", + hdr->tx_info.pkt_id, hdr->channel); + + __mm81x_skbq_drop_pending_skb(mq, pfirst); + flushed++; + } + } + + if (flushed) + mm81x_skbq_check_tx_empty(mors, mq); + + spin_unlock_bh(&mq->lock); + return flushed; +} + +/* Remove commands from pending (or skbq if not sent) */ +static void __skbq_cmd_finish(struct mm81x_skbq *mq, struct sk_buff *skb) +{ + struct mm81x *mors = mq->mors; + + if (skb_queue_len(&mq->pending)) { + __mm81x_skbq_unlink(mq, &mq->pending, skb); + dev_kfree_skb(skb); + } else if (skb_queue_len(&mq->skbq)) { + /* Command was probably timed out before being sent */ + dev_dbg(mors->dev, + "Command pending queue empty. Removing from SKBQ."); + __mm81x_skbq_unlink(mq, &mq->skbq, skb); + dev_kfree_skb(skb); + } else { + dev_dbg(mors->dev, "Command Q not found"); + } +} + +struct mm81x_update_sta_iter_data { + struct mm81x *mors; + struct sk_buff *skb; + struct mm81x_skb_tx_status *tx_sts; + int tx_attempts; + bool updated; +}; + +static void mm81x_tx_h_update_sta_iter(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct mm81x_update_sta_iter_data *iter = data; + struct ieee80211_hdr *hdr; + struct ieee80211_sta *sta; + + if (iter->updated || !iter->skb || !iter->skb->data) + return; + + hdr = (struct ieee80211_hdr *)iter->skb->data; + + /* + * Note that each iteration via + * ieee80211_iterate_active_interfaces_atomic is under an RCU critical + * section so there is no need for a local critical section within here + * when looking up the station. + */ + sta = ieee80211_find_sta(vif, hdr->addr1); + if (!sta) + return; + + mm81x_rc_sta_feedback_rates(iter->mors, iter->skb, sta, iter->tx_sts, + iter->tx_attempts); + mm81x_tx_h_check_aggr(sta, iter->skb); + + /* + * In situations with multiple virtual interfaces, finish iteration + * once we have found our STA to prevent further iteration. + */ + iter->updated = true; +} + +/* TX status/Response received remove packet from pending TX finish */ +static void __skbq_data_tx_finish(struct mm81x_skbq *mq, struct sk_buff *skb, + struct mm81x_skb_tx_status *tx_sts) +{ + struct mm81x *mors = mq->mors; + struct mm81x_update_sta_iter_data iter = {}; + + __mm81x_skbq_unlink(mq, &mq->pending, skb); + iter.mors = mors; + iter.skb = skb; + iter.tx_sts = tx_sts; + iter.tx_attempts = mm81x_tx_h_get_attempts(mors, tx_sts); + + ieee80211_iterate_active_interfaces_atomic(mors->hw, + IEEE80211_IFACE_ITER_NORMAL, + mm81x_tx_h_update_sta_iter, + &iter); + + ieee80211_tx_status_skb(mors->hw, skb); +} + +void mm81x_skbq_skb_finish(struct mm81x_skbq *mq, struct sk_buff *skb, + struct mm81x_skb_tx_status *tx_sts) +{ + if (mq->flags & MM81X_HIF_FLAGS_COMMAND) + __skbq_cmd_finish(mq, skb); + else + __skbq_data_tx_finish(mq, skb, tx_sts); +} + +void mm81x_skbq_tx_flush(struct mm81x_skbq *mq) +{ + struct sk_buff *pfirst, *pnext; + + spin_lock_bh(&mq->lock); + skb_queue_walk_safe(&mq->pending, pfirst, pnext) { + __mm81x_skbq_unlink(mq, &mq->pending, pfirst); + ieee80211_free_txskb(mq->mors->hw, pfirst); + } + + skb_queue_walk_safe(&mq->skbq, pfirst, pnext) { + __mm81x_skbq_unlink(mq, &mq->skbq, pfirst); + ieee80211_free_txskb(mq->mors->hw, pfirst); + } + spin_unlock_bh(&mq->lock); +} + +void mm81x_skbq_init(struct mm81x *mors, struct mm81x_skbq *mq, u16 flags) +{ + spin_lock_init(&mq->lock); + __skb_queue_head_init(&mq->skbq); + __skb_queue_head_init(&mq->pending); + mq->mors = mors; + mq->skbq_size = 0; + mq->flags = flags; + mq->pkt_seq = 0; + if (flags & MM81X_HIF_FLAGS_DIR_TO_HOST) + INIT_WORK(&mq->dispatch_work, mm81x_skbq_dispatch_work); +} + +void mm81x_skbq_finish(struct mm81x_skbq *mq) +{ + if (mq->skbq_size > 0) + dev_dbg(mq->mors->dev, + "Purging a non empty MorseQ. Dropping data!"); + + /* Clean up link to hif */ + if (mq->flags & MM81X_HIF_FLAGS_DIR_TO_HOST) + cancel_work_sync(&mq->dispatch_work); + mm81x_skbq_purge(mq, &mq->skbq); + mm81x_skbq_purge(mq, &mq->pending); + mq->skbq_size = 0; +} + +u32 mm81x_skbq_size(struct mm81x_skbq *mq) +{ + u32 count; + + spin_lock_bh(&mq->lock); + count = __mm81x_skbq_size(mq); + spin_unlock_bh(&mq->lock); + return count; +} + +u32 mm81x_skbq_count(struct mm81x_skbq *mq) +{ + u32 count = 0; + + spin_lock_bh(&mq->lock); + count += skb_queue_len(&mq->skbq); + spin_unlock_bh(&mq->lock); + return count; +} + +u32 mm81x_skbq_pending_count(struct mm81x_skbq *mq) +{ + u32 count; + + spin_lock_bh(&mq->lock); + count = skb_queue_len(&mq->pending); + spin_unlock_bh(&mq->lock); + return count; +} + +u32 mm81x_skbq_count_tx_ready(struct mm81x_skbq *mq) +{ + struct mm81x *mors = mq->mors; + + if (!mm81x_is_data_tx_allowed(mors)) + return 0; + + return mm81x_skbq_count(mq); +} + +u32 mm81x_skbq_space(struct mm81x_skbq *mq) +{ + u32 space; + + spin_lock_bh(&mq->lock); + space = __mm81x_skbq_space(mq); + spin_unlock_bh(&mq->lock); + + return space; +} + +struct sk_buff *mm81x_skbq_alloc_skb(struct mm81x_skbq *mq, unsigned int length) +{ + struct sk_buff *skb; + int tx_headroom = sizeof(struct mm81x_skb_hdr) + + mm81x_bus_get_alignment(mq->mors); + int skb_len = tx_headroom + length + MM81X_PAD4(length); + + skb = dev_alloc_skb(skb_len); + if (!skb) + return NULL; + + skb_reserve(skb, tx_headroom); + skb_put(skb, length); + return skb; +} + +static int mm81x_skb_tx_h_validate_channel(const struct mm81x *mors, u8 channel) +{ + if (channel == MM81X_SKB_CHAN_COMMAND) { + if (test_bit(MM81X_STATE_HOST_TO_CHIP_CMD_BLOCKED, + &mors->state_flags)) + return -EPERM; + } else { + if (test_bit(MM81X_STATE_HOST_TO_CHIP_TX_BLOCKED, + &mors->state_flags)) + return -EPERM; + } + + return 0; +} + +int mm81x_skbq_skb_tx(struct mm81x_skbq *mq, struct sk_buff **skb_orig, + struct mm81x_skb_tx_info *tx_info, u8 channel) +{ + int ret; + struct mm81x_skb_hdr hdr; + struct mm81x *mors = mq->mors; + size_t end_of_skb_pad; + struct sk_buff *skb = *skb_orig; + u8 *aligned_head, *data; + + if (test_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags)) { + dev_kfree_skb_any(skb); + return -ENODEV; + } + + ret = mm81x_skb_tx_h_validate_channel(mors, channel); + if (ret) { + dev_kfree_skb_any(skb); + return ret; + } + + mm81x_skbq_set_queued_tx_skb_expiry(skb); + + data = skb->data; + aligned_head = PTR_ALIGN_DOWN((data - sizeof(hdr)), + mm81x_bus_get_alignment(mors)); + hdr.sync = MM81X_SKB_HEADER_SYNC; + hdr.channel = channel; + hdr.len = cpu_to_le16(skb->len); + hdr.offset = data - (aligned_head + sizeof(hdr)); + hdr.checksum_upper = 0; + hdr.checksum_lower = 0; + if (tx_info) + memcpy(&hdr.tx_info, tx_info, sizeof(*tx_info)); + else + memset(&hdr.tx_info, 0, sizeof(hdr.tx_info)); + + skb_push(skb, data - aligned_head); + memcpy(skb->data, &hdr, sizeof(hdr)); + + end_of_skb_pad = MM81X_PAD4(skb->len); + if (end_of_skb_pad && skb_pad(skb, end_of_skb_pad)) + return -EINVAL; + + ret = mm81x_skbq_tx(mq, skb, channel); + if (ret) { + dev_err(mors->dev, "mm81x_skbq_tx fail: %d", ret); + dev_kfree_skb_any(skb); + } + + return ret; +} + +void mm81x_skbq_data_traffic_pause(struct mm81x *mors) +{ + set_bit(MM81X_STATE_DATA_TX_STOPPED, &mors->state_flags); + /* power-save requirements will be re-evaluated by the caller */ +} + +void mm81x_skbq_data_traffic_resume(struct mm81x *mors) +{ + clear_bit(MM81X_STATE_DATA_TX_STOPPED, &mors->state_flags); + + /* Set the TX_DATA_PEND bit. This will kick the transmission path to + * send any frames pending in the TX buffers, and wake the mac80211 + * data Qs if they were previously stopped. + */ + set_bit(MM81X_HIF_EVT_TX_DATA_PEND, &mors->hif.event_flags); +} + +bool mm81x_skbq_validate_checksum(u8 *data) +{ + int i; + u32 xor = 0; + struct mm81x_skb_hdr *skb_hdr = (struct mm81x_skb_hdr *)data; + struct ieee80211_hdr *hdr = + (struct ieee80211_hdr *)(data + sizeof(*skb_hdr)); + u16 len = le16_to_cpu(skb_hdr->len) + sizeof(*skb_hdr); + u32 *data_to_xor = (u32 *)data; + u32 header_xor = (le16_to_cpu(skb_hdr->checksum_upper) << 8) | + (skb_hdr->checksum_lower); + + /* + * For data frames the calculate the xor for skb header, mac header + * and ccmp header. For all other channel the xor is calculated for + * the full skb. + */ + if (skb_hdr->channel == MM81X_SKB_CHAN_DATA && + (ieee80211_is_data(hdr->frame_control) || + ieee80211_is_data_qos(hdr->frame_control))) { + u16 data_len = sizeof(*skb_hdr) + + sizeof(struct ieee80211_qos_hdr) + + IEEE80211_CCMP_HDR_LEN; + + len = min(len, data_len); + len = ROUND_DOWN_TO_WORD(len); + } + + skb_hdr->checksum_upper = 0; + skb_hdr->checksum_lower = 0; + + for (i = 0; i < len; i += 4) { + xor ^= *data_to_xor; + data_to_xor++; + } + + xor &= 0x00FFFFFF; + + return xor == header_xor; +} diff --git a/drivers/net/wireless/morsemicro/mm81x/skbq.h b/drivers/net/wireless/morsemicro/mm81x/skbq.h new file mode 100644 index 000000000000..9930493141cf --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/skbq.h @@ -0,0 +1,218 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_SKBQ_H_ +#define _MM81X_SKBQ_H_ + +#include +#include +#include "rate_code.h" + +/* Sync value of skb header to indicate a valid skb */ +#define MM81X_SKB_HEADER_SYNC (0xAA) +/* Sync value indicating that the chip owns this skb */ +#define MM81X_SKB_HEADER_CHIP_OWNED_SYNC (0xBB) + +enum mm81x_tx_status_and_conf_flags { + MM81X_TX_STATUS_FLAGS_NO_ACK = BIT(0), + MM81X_TX_STATUS_FLAGS_NO_REPORT = BIT(1), + MM81X_TX_CONF_FLAGS_CTL_AMPDU = BIT(2), + MM81X_TX_CONF_FLAGS_HW_ENCRYPT = BIT(3), + MM81X_TX_CONF_FLAGS_VIF_ID = (BIT(4) | BIT(5) | BIT(6) | BIT(7) | + BIT(8) | BIT(9) | BIT(10) | BIT(11)), + MM81X_TX_CONF_FLAGS_KEY_IDX = (BIT(12) | BIT(13) | BIT(14)), + MM81X_TX_STATUS_FLAGS_PS_FILTERED = (BIT(15)), + MM81X_TX_CONF_IGNORE_TWT = (BIT(16)), + MM81X_TX_STATUS_PAGE_INVALID = (BIT(17)), + MM81X_TX_CONF_NO_PS_BUFFER = (BIT(18)), + MM81X_TX_STATUS_DUTY_CYCLE_CANT_SEND = (BIT(19)), + MM81X_TX_CONF_HAS_PV1_BPN_IN_BODY = (BIT(21)), + MM81X_TX_CONF_FLAGS_SEND_AFTER_DTIM = (BIT(22)), + MM81X_TX_STATUS_WAS_AGGREGATED = (BIT(23)), + MM81X_TX_CONF_FLAGS_FULLMAC_REPORT = BIT(24), + MM81X_TX_CONF_FLAGS_IMMEDIATE_REPORT = (BIT(31)) +}; + +/* Getter and setter macros for vif id */ +#define MM81X_TX_CONF_FLAGS_VIF_ID_MASK (0xFF) +#define MM81X_TX_CONF_FLAGS_VIF_ID_SET(x) \ + (((x) & MM81X_TX_CONF_FLAGS_VIF_ID_MASK) << 4) +#define MM81X_TX_CONF_FLAGS_VIF_ID_GET(x) \ + (((x) & MM81X_TX_CONF_FLAGS_VIF_ID) >> 4) + +/* Getter and setter macros for key index */ +#define MM81X_TX_CONF_FLAGS_KEY_IDX_SET(x) (((x) & 0x07) << 12) +#define MM81X_TX_CONF_FLAGS_KEY_IDX_GET(x) \ + (((x) & MM81X_TX_CONF_FLAGS_KEY_IDX) >> 12) + +enum mm81x_rx_status_flags { + MM81X_RX_STATUS_FLAGS_ERROR = BIT(0), + MM81X_RX_STATUS_FLAGS_DECRYPTED = BIT(1), + MM81X_RX_STATUS_FLAGS_FCS_INCLUDED = BIT(2), + MM81X_RX_STATUS_FLAGS_EOF = BIT(3), + MM81X_RX_STATUS_FLAGS_AMPDU = BIT(4), + MM81X_RX_STATUS_FLAGS_NDP = BIT(7), + MM81X_RX_STATUS_FLAGS_UPLINK = BIT(8), + MM81X_RX_STATUS_FLAGS_RI = (BIT(9) | BIT(10)), + MM81X_RX_STATUS_FLAGS_NDP_TYPE = (BIT(11) | BIT(12) | BIT(13)), + MM81X_RX_STATUS_FLAGS_CRC_ERROR = BIT(14), + MM81X_RX_STATUS_FLAGS_VIF_ID = GENMASK(24, 17), +}; + +/* Getter and Setter macros for vif id */ +#define MM81X_RX_STATUS_FLAGS_VIF_ID_MASK (0xFF) +#define MM81X_RX_STATUS_FLAGS_VIF_ID_SET(x) \ + (((x) & MM81X_RX_STATUS_FLAGS_VIF_ID_MASK) << 17) +#define MM81X_RX_STATUS_FLAGS_VIF_ID_GET(x) \ + (((x) & MM81X_RX_STATUS_FLAGS_VIF_ID) >> 17) +#define MM81X_RX_STATUS_FLAGS_VIF_ID_CLEAR(x) \ + ((x) & ~(MM81X_RX_STATUS_FLAGS_VIF_ID_MASK << 17)) + +/* Getter macro for guard interval */ +#define MM81X_RX_STATUS_FLAGS_UPL_IND_GET(x) \ + (((x) & MM81X_RX_STATUS_FLAGS_UPLINK) >> 8) + +/* Getter macro for response indication */ +#define MM81X_RX_STATUS_FLAGS_RI_GET(x) (((x) & MM81X_RX_STATUS_FLAGS_RI) >> 9) + +/* Getter macro for NDP type */ +#define MM81X_RX_STATUS_FLAGS_NDP_TYPE_GET(x) \ + (((x) & MM81X_RX_STATUS_FLAGS_NDP_TYPE) >> 11) + +enum mm81x_skb_channel { + MM81X_SKB_CHAN_DATA = 0x0, + MM81X_SKB_CHAN_NDP_FRAMES = 0x1, + MM81X_SKB_CHAN_DATA_NOACK = 0x2, + MM81X_SKB_CHAN_BEACON = 0x3, + MM81X_SKB_CHAN_MGMT = 0x4, + MM81X_SKB_CHAN_INTERNAL_CRIT_BEACON = 0x80, + MM81X_SKB_CHAN_COMMAND = 0xFE, + MM81X_SKB_CHAN_TX_STATUS = 0xFF +}; + +#define MM81X_SKB_MAX_RATES (4) + +struct mm81x_skb_rate_info { + mm81x_rate_code_t mm81x_ratecode; + u8 count; +} __packed; + +struct mm81x_skb_tx_status { + __le32 flags; + __le32 pkt_id; + u8 tid; + u8 channel; + __le16 ampdu_info; + struct mm81x_skb_rate_info rates[MM81X_SKB_MAX_RATES]; +} __packed; + +#define MM81X_TXSTS_AMPDU_INFO_GET_TAG(x) (((x) >> 10) & 0x3F) +#define MM81X_TXSTS_AMPDU_INFO_GET_LEN(x) (((x) >> 5) & 0x1F) +#define MM81X_TXSTS_AMPDU_INFO_GET_SUC(x) ((x) & 0x1F) + +struct mm81x_skb_tx_info { + __le32 flags; + __le32 pkt_id; + u8 tid; + u8 tid_params; + u8 mmss_params; + u8 padding[1]; + struct mm81x_skb_rate_info rates[MM81X_SKB_MAX_RATES]; +} __packed; + +#define TX_INFO_TID_PARAMS_MAX_REORDER_BUF 0x1f +#define TX_INFO_TID_PARAMS_AMPDU_ENABLED 0x20 +#define TX_INFO_TID_PARAMS_AMSDU_SUPPORTED 0x40 +#define TX_INFO_TID_PARAMS_USE_LEGACY_BA 0x80 + +/* Bitmap for MMSS (Minimum MPDU start spacing) parameters + * +-----------+-----------+ + * | Morse | MMSS set | + * | MMSS | by S1G cap| + * | offset | IE | + * |-----------|-----------| + * |b7|b6|b5|b4|b3|b2|b1|b0| + */ +#define TX_INFO_MMSS_PARAMS_MMSS_MASK GENMASK(3, 0) +#define TX_INFO_MMSS_PARAMS_MMSS_OFFSET_START 4 +#define TX_INFO_MMSS_PARAMS_MMSS_OFFSET_MASK GENMASK(7, 4) +#define TX_INFO_MMSS_PARAMS_SET_MMSS(x) ((x) & TX_INFO_MMSS_PARAMS_MMSS_MASK) +#define TX_INFO_MMSS_PARAMS_SET_MMSS_OFFSET(x) \ + (((x) << TX_INFO_MMSS_PARAMS_MMSS_OFFSET_START) & \ + TX_INFO_MMSS_PARAMS_MMSS_OFFSET_MASK) + +struct mm81x_skb_rx_status { + __le32 flags; + mm81x_rate_code_t mm81x_ratecode; + __le16 rssi; + __le16 freq_100khz; + u8 bss_color; + s8 noise_dbm; + /** Padding for word alignment */ + u8 padding[2]; + __le64 rx_timestamp_us; +} __packed; + +struct mm81x_skb_hdr { + u8 sync; + u8 channel; + __le16 len; + u8 offset; + u8 checksum_lower; + __le16 checksum_upper; + union { + struct mm81x_skb_tx_info tx_info; + struct mm81x_skb_tx_status tx_status; + struct mm81x_skb_rx_status rx_status; + }; +} __packed; + +#define MM81X_SKBQ_SIZE (4 * 128 * 1024) + +struct mm81x; + +struct mm81x_skbq { + struct mm81x *mors; + u32 pkt_seq; /* SKB sequence used in tx_status */ + u16 flags; + u32 skbq_size; /* current off loaded size */ + spinlock_t lock; + struct sk_buff_head skbq; + struct sk_buff_head pending; /* packets sent pending feedback */ + struct work_struct dispatch_work; +}; + +void mm81x_skbq_purge(struct mm81x_skbq *mq, struct sk_buff_head *skbq); +void mm81x_skbq_purge_aged(struct mm81x *mors, struct mm81x_skbq *mq); +u32 mm81x_skbq_space(struct mm81x_skbq *mq); +u32 mm81x_skbq_size(struct mm81x_skbq *mq); +int mm81x_skbq_deq_num_skb(struct mm81x_skbq *mq, struct sk_buff_head *skbq, + int num_skb); +struct sk_buff *mm81x_skbq_alloc_skb(struct mm81x_skbq *mq, + unsigned int length); +int mm81x_skbq_skb_tx(struct mm81x_skbq *mq, struct sk_buff **skb, + struct mm81x_skb_tx_info *tx_info, u8 channel); +int mm81x_skbq_put(struct mm81x_skbq *mq, struct sk_buff *skb); +void mm81x_skbq_enq(struct mm81x_skbq *mq, struct sk_buff_head *skbq); +void mm81x_skbq_enq_prepend(struct mm81x_skbq *mq, struct sk_buff_head *skbq); +void mm81x_skbq_tx_complete(struct mm81x_skbq *mq, struct sk_buff_head *skbq); +struct sk_buff *mm81x_skbq_tx_pending(struct mm81x_skbq *mq); +void mm81x_skbq_init(struct mm81x *mors, struct mm81x_skbq *mq, u16 flags); +void mm81x_skbq_finish(struct mm81x_skbq *mq); +void mm81x_skbq_pull_hdr_post_tx(struct sk_buff *skb); +void mm81x_skbq_mon_dump(struct mm81x *mors, struct seq_file *file); +void mm81x_skbq_skb_finish(struct mm81x_skbq *mq, struct sk_buff *skb, + struct mm81x_skb_tx_status *tx_sts); +void mm81x_skbq_tx_flush(struct mm81x_skbq *mq); +int mm81x_skbq_check_for_stale_tx(struct mm81x *mors, struct mm81x_skbq *mq); +void mm81x_skbq_may_wake_tx_queues(struct mm81x *mors); +u32 mm81x_skbq_count_tx_ready(struct mm81x_skbq *mq); +u32 mm81x_skbq_count(struct mm81x_skbq *mq); +u32 mm81x_skbq_pending_count(struct mm81x_skbq *mq); +void mm81x_skbq_data_traffic_pause(struct mm81x *mors); +void mm81x_skbq_data_traffic_resume(struct mm81x *mors); +bool mm81x_skbq_validate_checksum(u8 *data); + +#endif /* !_MM81X_SKBQ_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/usb.c b/drivers/net/wireless/morsemicro/mm81x/usb.c new file mode 100644 index 000000000000..ec94936b157b --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/usb.c @@ -0,0 +1,943 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include +#include +#include +#include "hif.h" +#include "bus.h" +#include "mac.h" +#include "core.h" + +/* + * URB timeout in milliseconds. If an URB does not complete within this + * time, it will be killed. This timeout needs to account for USB suspendand + * resume occurring before the URB can be transferred, and it also needs to + * account for transferring USB_MAX_TRANSFER_SIZE bytes over a potentially + * slow, congested USB Full Speed link. + */ +#define URB_TIMEOUT_MS 250 + +/* High speed USB 2^(4-1) * 125usec = 1msec */ +#define MM81X_USB_INTERRUPT_INTERVAL 4 + +/* Max bytes per USB read/write */ +#define USB_MAX_TRANSFER_SIZE (16 * 1024) + +/* INT EP buffer size */ +#define MM81X_EP_INT_BUFFER_SIZE 8 + +/* Morse vendor IDs*/ +#define MM81X_VENDOR_ID 0x325b +#define MM81X_MM810X_PRODUCT_ID 0x8100 + +/* Power management runtime auto-suspend delay value in milliseconds */ +#define PM_RUNTIME_AUTOSUSPEND_DELAY_MS 100 + +enum mm81x_usb_endpoints { + MM81X_EP_CMD = 0, + MM81X_EP_INT, + MM81X_EP_MEM_RD, + MM81X_EP_MEM_WR, + MM81X_EP_REG_RD, + MM81X_EP_REG_WR, + MM81X_EP_EP_MAX, +}; + +struct mm81x_usb_endpoint { + unsigned char *buffer; + struct urb *urb; + __u8 addr; + int size; +}; + +enum mm81x_usb_flags { MM81X_USB_FLAG_ATTACHED, MM81X_USB_FLAG_SUSPENDED }; + +struct mm81x_usb { + struct usb_device *udev; + struct usb_interface *interface; + struct mm81x_usb_endpoint endpoints[MM81X_EP_EP_MAX]; + int errors; + + /* serialise USB device struct */ + struct mutex lock; + + /* serialise USB bus access */ + struct mutex bus_lock; + + bool ongoing_cmd; + bool ongoing_rw; + wait_queue_head_t rw_in_wait; + unsigned long flags; +}; + +enum mm81x_usb_command_direction { + MM81X_USB_WRITE = 0x00, + MM81X_USB_READ = 0x80, + MM81X_USB_RESET = 0x02, +}; + +struct mm81x_usb_command { + __le32 dir; /* Next BULK direction */ + __le32 address; /* Next BULK address */ + __le32 length; /* Next BULK size */ +}; + +static const struct usb_device_id mm81x_usb_table[] = { + { USB_DEVICE(MM81X_VENDOR_ID, MM81X_MM810X_PRODUCT_ID) }, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, mm81x_usb_table); + +static void mm81x_usb_irq_work(struct work_struct *work) +{ + struct mm81x *mors = container_of(work, struct mm81x, usb_irq_work); + + mm81x_claim_bus(mors); + mm81x_hw_irq_handle(mors); + mm81x_release_bus(mors); +} + +static bool mm81x_usb_urb_status_is_disconnect(const struct urb *urb) +{ + return ((urb->status == -EPROTO) || (urb->status == -EILSEQ) || + (urb->status == -ETIME) || (urb->status == -EPIPE)); +} + +static void mm81x_usb_int_handler(struct urb *urb) +{ + int ret; + struct mm81x *mors = urb->context; + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + + if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags)) + return; + + if (urb->status) { + if (mm81x_usb_urb_status_is_disconnect(urb)) { + clear_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags); + set_bit(MM81X_STATE_CHIP_UNRESPONSIVE, + &mors->state_flags); + dev_dbg(mors->dev, + "USB sudden disconnect detected in %s", + __func__); + return; + } + + if (!(urb->status == -ENOENT || urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) + dev_err(mors->dev, "- nonzero read status received: %d", + urb->status); + } + + ret = usb_submit_urb(urb, GFP_ATOMIC); + + /* usb_kill_urb has been called */ + if (ret == -EPERM) + return; + else if (ret) + dev_err(mors->dev, "error: resubmit urb %p err code %d", urb, + ret); + + queue_work(mors->chip_wq, &mors->usb_irq_work); +} + +static int mm81x_usb_int_enable(struct mm81x *mors) +{ + int ret = 0; + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + struct urb *urb; + + if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags)) + return -ENODEV; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + ret = -ENOMEM; + goto out; + } + + musb->endpoints[MM81X_EP_INT].urb = urb; + + musb->endpoints[MM81X_EP_INT].buffer = + usb_alloc_coherent(musb->udev, MM81X_EP_INT_BUFFER_SIZE, + GFP_KERNEL, &urb->transfer_dma); + if (!musb->endpoints[MM81X_EP_INT].buffer) { + dev_err(mors->dev, "couldn't allocate transfer_buffer"); + ret = -ENOMEM; + goto error_set_urb_null; + } + + usb_fill_int_urb( + musb->endpoints[MM81X_EP_INT].urb, musb->udev, + usb_rcvintpipe(musb->udev, musb->endpoints[MM81X_EP_INT].addr), + musb->endpoints[MM81X_EP_INT].buffer, MM81X_EP_INT_BUFFER_SIZE, + mm81x_usb_int_handler, mors, MM81X_USB_INTERRUPT_INTERVAL); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret) { + dev_err(mors->dev, "Couldn't submit urb. Error number %d", ret); + goto error; + } + + return 0; + +error: + usb_free_coherent(musb->udev, MM81X_EP_INT_BUFFER_SIZE, + musb->endpoints[MM81X_EP_INT].buffer, + urb->transfer_dma); +error_set_urb_null: + musb->endpoints[MM81X_EP_INT].urb = NULL; + usb_free_urb(urb); +out: + return ret; +} + +static void mm81x_usb_int_stop(struct mm81x *mors) +{ + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + + usb_kill_urb(musb->endpoints[MM81X_EP_INT].urb); + cancel_work_sync(&mors->usb_irq_work); +} + +static void mm81x_usb_cmd_callback(struct urb *urb) +{ + struct mm81x *mors = urb->context; + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + + /* sync/async unlink faults aren't errors */ + if (urb->status) { + if (!(urb->status == -ENOENT || urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) + dev_err(mors->dev, + "nonzero write bulk status received: %d", + urb->status); + + musb->errors = urb->status; + } + + musb->ongoing_cmd = false; + wake_up(&musb->rw_in_wait); +} + +static int mm81x_usb_cmd(struct mm81x_usb *musb, + const struct mm81x_usb_command *cmd) +{ + int retval = 0; + struct mm81x *mors = usb_get_intfdata(musb->interface); + struct mm81x_usb_endpoint *ep = &musb->endpoints[MM81X_EP_CMD]; + size_t writesize = sizeof(*cmd); + + if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags)) + return -ENODEV; + + memcpy(ep->buffer, cmd, writesize); + + usb_fill_bulk_urb(ep->urb, musb->udev, + usb_sndbulkpipe(musb->udev, ep->addr), ep->buffer, + writesize, mm81x_usb_cmd_callback, mors); + ep->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + musb->ongoing_cmd = true; + + retval = usb_submit_urb(ep->urb, GFP_KERNEL); + if (retval) { + dev_err(mors->dev, "- failed submitting write urb, error %d", + retval); + + goto error; + } + + retval = wait_event_interruptible_timeout( + musb->rw_in_wait, (!musb->ongoing_cmd), + msecs_to_jiffies(URB_TIMEOUT_MS)); + if (retval < 0) { + dev_err(mors->dev, "error waiting for urb %d", retval); + goto error; + } else if (retval == 0) { + dev_err(mors->dev, "timed out waiting for urb"); + usb_kill_urb(ep->urb); + retval = -ETIMEDOUT; + goto error; + } + + musb->ongoing_cmd = false; + return writesize; + +error: + musb->ongoing_cmd = false; + return retval; +} + +static int mm81x_usb_ndr_reset(struct mm81x *mors) +{ + int ret; + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + struct mm81x_usb_command cmd; + + mutex_lock(&musb->lock); + + musb->ongoing_rw = true; + musb->errors = 0; + + cmd.dir = cpu_to_le32(MM81X_USB_RESET); + cmd.address = cpu_to_le32(0); + cmd.length = cpu_to_le32(0); + + ret = mm81x_usb_cmd(musb, &cmd); + if (ret < 0) + dev_err(mors->dev, "mm81x_usb_cmd (MM81X_USB_RESET) error %d\n", + ret); + else + ret = 0; + + musb->ongoing_rw = false; + mutex_unlock(&musb->lock); + return ret; +} + +static void mm81x_usb_mem_rw_callback(struct urb *urb) +{ + struct mm81x *mors = urb->context; + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + + /* sync/async unlink faults aren't errors */ + if (urb->status) { + if (!(urb->status == -ENOENT || urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) + dev_err(mors->dev, + "nonzero write bulk status received: %d", + urb->status); + + musb->errors = urb->status; + } + + musb->ongoing_rw = false; + wake_up(&musb->rw_in_wait); +} + +static int mm81x_usb_mem_read(struct mm81x_usb *musb, u32 address, u8 *data, + ssize_t size) +{ + int ret; + struct mm81x_usb_command cmd; + struct mm81x *mors = usb_get_intfdata(musb->interface); + + if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags)) + return -ENODEV; + + mutex_lock(&musb->lock); + + musb->ongoing_rw = true; + musb->errors = 0; + + /* Send command ahead to prepare for Tokens */ + cmd.dir = cpu_to_le32(MM81X_USB_READ); + cmd.address = cpu_to_le32(address); + cmd.length = cpu_to_le32(size); + + ret = mm81x_usb_cmd(musb, &cmd); + if (ret < 0) { + dev_err(mors->dev, "mm81x_usb_cmd error %d", ret); + goto error; + } + + /* Let's be fast push the next URB, don't wait until command is done */ + usb_fill_bulk_urb( + musb->endpoints[MM81X_EP_MEM_RD].urb, musb->udev, + usb_rcvbulkpipe(musb->udev, + musb->endpoints[MM81X_EP_MEM_RD].addr), + musb->endpoints[MM81X_EP_MEM_RD].buffer, size, + mm81x_usb_mem_rw_callback, mors); + + ret = usb_submit_urb(musb->endpoints[MM81X_EP_MEM_RD].urb, GFP_ATOMIC); + if (ret < 0) { + dev_err(mors->dev, "failed submitting read urb, error %d", ret); + ret = (ret == -ENOMEM) ? ret : -EIO; + goto error; + } + + ret = wait_event_interruptible_timeout( + musb->rw_in_wait, (!musb->ongoing_rw), + msecs_to_jiffies(URB_TIMEOUT_MS)); + if (ret < 0) { + dev_err(mors->dev, "wait_event_interruptible: error %d", ret); + goto error; + } else if (ret == 0) { + /* Timed out. */ + usb_kill_urb(musb->endpoints[MM81X_EP_MEM_RD].urb); + } + + if (musb->errors) { + ret = musb->errors; + dev_err(mors->dev, "mem read error %d", ret); + goto error; + } + + memcpy(data, musb->endpoints[MM81X_EP_MEM_RD].buffer, size); + ret = size; + +error: + musb->ongoing_rw = false; + mutex_unlock(&musb->lock); + + return ret; +} + +static int mm81x_usb_mem_write(struct mm81x_usb *musb, u32 address, u8 *data, + ssize_t size) +{ + int ret; + struct mm81x_usb_command cmd; + struct mm81x *mors = usb_get_intfdata(musb->interface); + + if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags)) + return -ENODEV; + + mutex_lock(&musb->lock); + + musb->ongoing_rw = true; + musb->errors = 0; + + /* Send command ahead to prepare for Tokens */ + cmd.dir = cpu_to_le32(MM81X_USB_WRITE); + cmd.address = cpu_to_le32(address); + cmd.length = cpu_to_le32(size); + ret = mm81x_usb_cmd(musb, &cmd); + if (ret < 0) { + dev_err(mors->dev, "mm81x_usb_mem_read error %d", ret); + goto error; + } + + memcpy(musb->endpoints[MM81X_EP_MEM_WR].buffer, data, size); + + /* prepare a read */ + usb_fill_bulk_urb( + musb->endpoints[MM81X_EP_MEM_WR].urb, musb->udev, + usb_sndbulkpipe(musb->udev, + musb->endpoints[MM81X_EP_MEM_WR].addr), + musb->endpoints[MM81X_EP_MEM_WR].buffer, size, + mm81x_usb_mem_rw_callback, mors); + + ret = usb_submit_urb(musb->endpoints[MM81X_EP_MEM_WR].urb, GFP_ATOMIC); + if (ret < 0) { + dev_err(mors->dev, "- failed submitting write urb, error %d", + ret); + ret = (ret == -ENOMEM) ? ret : -EIO; + goto error; + } + + ret = wait_event_interruptible_timeout( + musb->rw_in_wait, (!musb->ongoing_rw), + msecs_to_jiffies(URB_TIMEOUT_MS)); + if (ret < 0) { + dev_err(mors->dev, "error %d", ret); + goto error; + } else if (ret == 0) { + /* Timed out. */ + usb_kill_urb(musb->endpoints[MM81X_EP_MEM_WR].urb); + } + + if (musb->errors) { + ret = musb->errors; + dev_err(mors->dev, "error %d", ret); + goto error; + } + + ret = size; + +error: + musb->ongoing_rw = false; + mutex_unlock(&musb->lock); + return ret; +} + +static int mm81x_usb_dm_read(struct mm81x *mors, u32 address, u8 *data, int len) +{ + ssize_t offset = 0; + int ret; + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + + while (offset < len) { + ret = mm81x_usb_mem_read(musb, address + offset, + (u8 *)(data + offset), + min((ssize_t)(len - offset), + (ssize_t)USB_MAX_TRANSFER_SIZE)); + if (ret < 0) { + dev_err(mors->dev, "%s failed (errno=%d)", __func__, + ret); + return ret; + } + + offset += ret; + } + + return 0; +} + +static int mm81x_usb_dm_write(struct mm81x *mors, u32 address, const u8 *data, + int len) +{ + ssize_t offset = 0; + int ret; + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + + while (offset < len) { + ret = mm81x_usb_mem_write(musb, address + offset, + (u8 *)(data + offset), + min((ssize_t)(len - offset), + (ssize_t)USB_MAX_TRANSFER_SIZE)); + if (ret < 0) { + dev_err(mors->dev, "%s failed (errno=%d)", __func__, + ret); + return ret; + } + + offset += ret; + } + + return 0; +} + +static int mm81x_usb_reg32_read(struct mm81x *mors, u32 address, u32 *val) +{ + int ret = 0; + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + + ret = mm81x_usb_mem_read(musb, address, (u8 *)val, sizeof(*val)); + if (ret == sizeof(*val)) { + *val = le32_to_cpup((__le32 *)val); + return 0; + } + + dev_err(mors->dev, "usb reg32 read failed %d", ret); + return ret; +} + +static int mm81x_usb_reg32_write(struct mm81x *mors, u32 address, u32 val) +{ + int ret = 0; + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + __le32 val_le = cpu_to_le32(val); + + ret = mm81x_usb_mem_write(musb, address, (u8 *)&val_le, sizeof(val_le)); + if (ret == sizeof(val_le)) + return 0; + + dev_err(mors->dev, "usb reg32 write failed %d", ret); + return ret; +} + +static void mm81x_usb_bus_enable(struct mm81x *mors, bool enable) +{ + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + + if (enable) + usb_autopm_get_interface(musb->interface); + else + usb_autopm_put_interface(musb->interface); +} + +static void mm81x_usb_claim_bus(struct mm81x *mors) +{ + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + + mutex_lock(&musb->bus_lock); +} + +static void mm81x_usb_release_bus(struct mm81x *mors) +{ + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + + mutex_unlock(&musb->bus_lock); +} + +static void mm81x_usb_set_irq(struct mm81x *mors, bool enable) +{ +} + +static const struct mm81x_bus_ops mm81x_usb_ops = { + .dm_read = mm81x_usb_dm_read, + .dm_write = mm81x_usb_dm_write, + .reg32_read = mm81x_usb_reg32_read, + .reg32_write = mm81x_usb_reg32_write, + .digital_reset = mm81x_usb_ndr_reset, + .set_bus_enable = mm81x_usb_bus_enable, + .claim = mm81x_usb_claim_bus, + .release = mm81x_usb_release_bus, + .set_irq = mm81x_usb_set_irq, + .bulk_alignment = MM81X_BUS_DEFAULT_BULK_ALIGNMENT, +}; + +static int mm81x_usb_detect_endpoints(struct mm81x *mors, + const struct usb_interface *intf) +{ + int ret; + unsigned int i; + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + struct usb_endpoint_descriptor *ep_desc; + struct usb_host_interface *intf_desc = intf->cur_altsetting; + + for (i = 0; i < intf_desc->desc.bNumEndpoints; i++) { + ep_desc = &intf_desc->endpoint[i].desc; + + if (usb_endpoint_is_bulk_in(ep_desc)) { + if (!musb->endpoints[MM81X_EP_MEM_RD].addr) { + musb->endpoints[MM81X_EP_MEM_RD].addr = + usb_endpoint_num(ep_desc); + musb->endpoints[MM81X_EP_MEM_RD].size = + usb_endpoint_maxp(ep_desc); + } else if (!musb->endpoints[MM81X_EP_REG_RD].addr) { + musb->endpoints[MM81X_EP_REG_RD].addr = + usb_endpoint_num(ep_desc); + musb->endpoints[MM81X_EP_REG_RD].size = + usb_endpoint_maxp(ep_desc); + } + } else if (usb_endpoint_is_bulk_out(ep_desc)) { + if (!musb->endpoints[MM81X_EP_MEM_WR].addr) { + musb->endpoints[MM81X_EP_MEM_WR].addr = + usb_endpoint_num(ep_desc); + musb->endpoints[MM81X_EP_MEM_WR].size = + usb_endpoint_maxp(ep_desc); + } else if (!musb->endpoints[MM81X_EP_REG_WR].addr) { + musb->endpoints[MM81X_EP_REG_WR].addr = + usb_endpoint_num(ep_desc); + musb->endpoints[MM81X_EP_REG_WR].size = + usb_endpoint_maxp(ep_desc); + } + } else if (usb_endpoint_is_int_in(ep_desc)) { + musb->endpoints[MM81X_EP_INT].addr = + usb_endpoint_num(ep_desc); + musb->endpoints[MM81X_EP_INT].size = + usb_endpoint_maxp(ep_desc); + } + } + + dev_dbg(mors->dev, "\tMemory Endpoint IN %s detected: %u size %u", + musb->endpoints[MM81X_EP_MEM_RD].addr ? "" : "not", + musb->endpoints[MM81X_EP_MEM_RD].addr, + musb->endpoints[MM81X_EP_MEM_RD].size); + dev_dbg(mors->dev, "\tMemory Endpoint OUT %s detected: %u size %u", + musb->endpoints[MM81X_EP_MEM_WR].addr ? "" : "not", + musb->endpoints[MM81X_EP_MEM_WR].addr, + musb->endpoints[MM81X_EP_MEM_WR].size); + dev_dbg(mors->dev, "\tRegister Endpoint IN %s detected: %u", + musb->endpoints[MM81X_EP_REG_RD].addr ? "" : "not", + musb->endpoints[MM81X_EP_REG_RD].addr); + dev_dbg(mors->dev, "\tRegister Endpoint OUT %s detected: %u", + musb->endpoints[MM81X_EP_REG_WR].addr ? "" : "not", + musb->endpoints[MM81X_EP_REG_WR].addr); + dev_dbg(mors->dev, "\tStats IN endpoint %s detected: %u", + musb->endpoints[MM81X_EP_INT].addr ? "" : "not", + musb->endpoints[MM81X_EP_INT].addr); + + /* Verify we have an IN and OUT */ + if (!(musb->endpoints[MM81X_EP_MEM_RD].addr && + musb->endpoints[MM81X_EP_MEM_WR].addr)) + return -ENODEV; + + /* Verify the stats MM81X_EP_INT is detected */ + if (!musb->endpoints[MM81X_EP_INT].addr) + return -ENODEV; + + /* Verify minimum interrupt status read */ + if (musb->endpoints[MM81X_EP_INT].size < 8) + return -ENODEV; + + musb->endpoints[MM81X_EP_CMD].urb = usb_alloc_urb(0, GFP_KERNEL); + if (!musb->endpoints[MM81X_EP_CMD].urb) { + ret = -ENOMEM; + goto err_ep; + } + + musb->endpoints[MM81X_EP_MEM_RD].urb = usb_alloc_urb(0, GFP_KERNEL); + if (!musb->endpoints[MM81X_EP_MEM_RD].urb) { + ret = -ENOMEM; + goto err_ep; + } + + musb->endpoints[MM81X_EP_MEM_WR].urb = usb_alloc_urb(0, GFP_KERNEL); + if (!musb->endpoints[MM81X_EP_MEM_WR].urb) { + ret = -ENOMEM; + goto err_ep; + } + + musb->endpoints[MM81X_EP_MEM_RD].buffer = + kmalloc(USB_MAX_TRANSFER_SIZE, GFP_KERNEL); + if (!musb->endpoints[MM81X_EP_MEM_RD].buffer) { + ret = -ENOMEM; + goto err_ep; + } + + musb->endpoints[MM81X_EP_MEM_WR].buffer = + kmalloc(USB_MAX_TRANSFER_SIZE, GFP_KERNEL); + if (!musb->endpoints[MM81X_EP_MEM_WR].buffer) { + ret = -ENOMEM; + goto err_ep; + } + + musb->endpoints[MM81X_EP_CMD].buffer = usb_alloc_coherent( + musb->udev, sizeof(struct mm81x_usb_command), GFP_KERNEL, + &musb->endpoints[MM81X_EP_CMD].urb->transfer_dma); + + if (!musb->endpoints[MM81X_EP_CMD].buffer) { + ret = -ENOMEM; + goto err_ep; + } + + /* Assign command to memory out end point */ + musb->endpoints[MM81X_EP_CMD].addr = + musb->endpoints[MM81X_EP_MEM_WR].addr; + musb->endpoints[MM81X_EP_CMD].size = + musb->endpoints[MM81X_EP_MEM_WR].size; + + return 0; + +err_ep: + if (musb->endpoints[MM81X_EP_CMD].urb && + musb->endpoints[MM81X_EP_CMD].buffer) + usb_free_coherent( + musb->udev, sizeof(struct mm81x_usb_command), + musb->endpoints[MM81X_EP_CMD].buffer, + musb->endpoints[MM81X_EP_CMD].urb->transfer_dma); + usb_free_urb(musb->endpoints[MM81X_EP_MEM_RD].urb); + usb_free_urb(musb->endpoints[MM81X_EP_CMD].urb); + usb_free_urb(musb->endpoints[MM81X_EP_MEM_WR].urb); + kfree(musb->endpoints[MM81X_EP_MEM_RD].buffer); + kfree(musb->endpoints[MM81X_EP_MEM_WR].buffer); + + return ret; +} + +static void mm81x_urb_cleanup(struct mm81x *mors) +{ + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT]; + struct mm81x_usb_endpoint *rd_ep = &musb->endpoints[MM81X_EP_MEM_RD]; + struct mm81x_usb_endpoint *wr_ep = &musb->endpoints[MM81X_EP_MEM_WR]; + struct mm81x_usb_endpoint *cmd_ep = &musb->endpoints[MM81X_EP_CMD]; + + usb_kill_urb(rd_ep->urb); + usb_kill_urb(wr_ep->urb); + usb_kill_urb(cmd_ep->urb); + + if (int_ep->urb) + usb_free_coherent(musb->udev, MM81X_EP_INT_BUFFER_SIZE, + int_ep->buffer, int_ep->urb->transfer_dma); + + if (cmd_ep->urb) + usb_free_coherent(musb->udev, sizeof(struct mm81x_usb_command), + cmd_ep->buffer, cmd_ep->urb->transfer_dma); + + kfree(wr_ep->buffer); + kfree(rd_ep->buffer); + + usb_free_urb(int_ep->urb); + usb_free_urb(wr_ep->urb); + usb_free_urb(rd_ep->urb); + usb_free_urb(cmd_ep->urb); +} + +static int mm81x_usb_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + int ret; + struct mm81x *mors; + struct mm81x_usb *musb; + + mors = mm81x_core_alloc(sizeof(*musb), &interface->dev); + if (!mors) + return -ENOMEM; + + mors->bus_ops = &mm81x_usb_ops; + mors->bus_type = MM81X_BUS_TYPE_USB; + + musb = (struct mm81x_usb *)mors->drv_priv; + musb->udev = usb_get_dev(interface_to_usbdev(interface)); + musb->interface = usb_get_intf(interface); + + mutex_init(&musb->lock); + mutex_init(&musb->bus_lock); + init_waitqueue_head(&musb->rw_in_wait); + usb_set_intfdata(interface, mors); + + ret = mm81x_usb_detect_endpoints(mors, interface); + if (ret < 0) + goto err_core_free; + + set_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags); + + ret = mm81x_core_init(mors); + if (ret) + goto err_urb_cleanup; + + INIT_WORK(&mors->usb_irq_work, mm81x_usb_irq_work); + + ret = mm81x_usb_int_enable(mors); + if (ret) + goto err_core_deinit; + + ret = mm81x_core_register(mors); + if (ret) + goto err_usb_int_stop; + + /* USB requires remote wakeup functionality for suspend */ + clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags); + musb->interface->needs_remote_wakeup = 1; + usb_enable_autosuspend(musb->udev); + pm_runtime_set_autosuspend_delay(&musb->udev->dev, + PM_RUNTIME_AUTOSUSPEND_DELAY_MS); + + usb_autopm_get_interface(interface); + return 0; + +err_usb_int_stop: + mm81x_usb_int_stop(mors); +err_core_deinit: + mm81x_core_deinit(mors); +err_urb_cleanup: + mm81x_urb_cleanup(mors); +err_core_free: + mm81x_core_free(mors); + usb_put_intf(interface); + usb_put_dev(interface_to_usbdev(interface)); + return ret; +} + +static void mm81x_usb_disconnect(struct usb_interface *interface) +{ + struct mm81x *mors = usb_get_intfdata(interface); + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + int minor = interface->minor; + struct usb_device *udev = interface_to_usbdev(interface); + + if (udev->state == USB_STATE_NOTATTACHED) { + clear_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags); + set_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags); + dev_dbg(mors->dev, "USB suddenly unplugged"); + } + + usb_disable_autosuspend(udev); + + if (test_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags)) { + dev_dbg(mors->dev, "USB was suspended: release locks"); + mm81x_usb_release_bus(mors); + mutex_unlock(&musb->lock); + } + + clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags); + + mm81x_core_unregister(mors); + mm81x_usb_int_stop(mors); + mm81x_core_deinit(mors); + mm81x_urb_cleanup(mors); + mm81x_core_free(mors); + + usb_autopm_put_interface(interface); + usb_set_intfdata(interface, NULL); + dev_info(&interface->dev, "USB Morse #%d now disconnected", minor); + usb_put_intf(interface); + usb_put_dev(udev); +} + +static int mm81x_usb_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct mm81x *mors = usb_get_intfdata(intf); + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT]; + struct mm81x_usb_endpoint *rd_ep = &musb->endpoints[MM81X_EP_MEM_RD]; + struct mm81x_usb_endpoint *wr_ep = &musb->endpoints[MM81X_EP_MEM_WR]; + struct mm81x_usb_endpoint *cmd_ep = &musb->endpoints[MM81X_EP_CMD]; + + if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags)) + return -ENODEV; + + usb_kill_urb(int_ep->urb); + usb_kill_urb(rd_ep->urb); + usb_kill_urb(wr_ep->urb); + usb_kill_urb(cmd_ep->urb); + + /* Locking the bus. No USB communication after this point */ + mm81x_usb_claim_bus(mors); + mutex_lock(&musb->lock); + + set_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags); + return 0; +} + +static int mm81x_usb_resume(struct usb_interface *intf) +{ + struct mm81x *mors = usb_get_intfdata(intf); + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + int ret; + struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT]; + + if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags)) + return -ENODEV; + + ret = usb_submit_urb(int_ep->urb, GFP_KERNEL); + if (ret) + dev_err(mors->dev, "Couldn't submit urb. Error number %d", ret); + + mm81x_usb_release_bus(mors); + mutex_unlock(&musb->lock); + + clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags); + return 0; +} + +static int mm81x_usb_reset_resume(struct usb_interface *intf) +{ + struct mm81x *mors = usb_get_intfdata(intf); + struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv; + int ret; + struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT]; + + if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags)) + return -ENODEV; + + ret = usb_submit_urb(int_ep->urb, GFP_KERNEL); + if (ret) + dev_err(mors->dev, "Couldn't submit urb. Error number %d", ret); + + mm81x_usb_release_bus(mors); + mutex_unlock(&musb->lock); + + clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags); + + return 0; +} + +static int mm81x_usb_pre_reset(struct usb_interface *intf) +{ + return 0; +} + +static int mm81x_usb_post_reset(struct usb_interface *intf) +{ + return 0; +} + +static struct usb_driver mm81x_usb_driver = { + .name = "mm81x_usb", + .probe = mm81x_usb_probe, + .disconnect = mm81x_usb_disconnect, + .suspend = mm81x_usb_suspend, + .resume = mm81x_usb_resume, + .reset_resume = mm81x_usb_reset_resume, + .pre_reset = mm81x_usb_pre_reset, + .post_reset = mm81x_usb_post_reset, + .id_table = mm81x_usb_table, + .supports_autosuspend = 1, + .soft_unbind = 1, +}; + +module_usb_driver(mm81x_usb_driver); + +MODULE_AUTHOR("Morse Micro"); +MODULE_DESCRIPTION("Driver support for Morse Micro MM81X USB devices"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/wireless/morsemicro/mm81x/yaps.c b/drivers/net/wireless/morsemicro/mm81x/yaps.c new file mode 100644 index 000000000000..bdadb822bf9a --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/yaps.c @@ -0,0 +1,704 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include +#include +#include +#include +#include +#include "hif.h" +#include "ps.h" +#include "bus.h" +#include "command.h" +#include "skbq.h" + +/* This is a fail safe timeout */ +#define CHIP_FULL_RECOVERY_TIMEOUT_MS 30 + +/* Defined as the max number of MPDUs per AMPDU */ +#define MAX_PKTS_PER_TX_TXN 16 +#define MAX_PKTS_PER_RX_TXN 32 + +static int mm81x_yaps_alloc_pkt_buffers(struct mm81x_yaps *yaps) +{ + yaps->hw.to_chip_pkts = kcalloc(MAX_PKTS_PER_TX_TXN, + sizeof(*yaps->hw.to_chip_pkts), + GFP_KERNEL); + if (!yaps->hw.to_chip_pkts) + return -ENOMEM; + + yaps->hw.from_chip_pkts = kcalloc(MAX_PKTS_PER_RX_TXN, + sizeof(*yaps->hw.from_chip_pkts), + GFP_KERNEL); + if (!yaps->hw.from_chip_pkts) { + kfree(yaps->hw.to_chip_pkts); + yaps->hw.to_chip_pkts = NULL; + return -ENOMEM; + } + + return 0; +} + +static void mm81x_yaps_free_pkt_buffers(struct mm81x_yaps *yaps) +{ + kfree(yaps->hw.from_chip_pkts); + yaps->hw.from_chip_pkts = NULL; + kfree(yaps->hw.to_chip_pkts); + yaps->hw.to_chip_pkts = NULL; +} + +static int mm81x_yaps_write_pkts(struct mm81x_yaps *yaps, + struct mm81x_yaps_pkt *pkts, int num_pkts, + int *num_pkts_sent) +{ + return yaps->ops->write_pkts(yaps, pkts, num_pkts, num_pkts_sent); +} + +static int mm81x_yaps_read_pkts(struct mm81x_yaps *yaps, + struct mm81x_yaps_pkt *pkts, int num_pkts_max, + int *num_pkts_received) +{ + return yaps->ops->read_pkts(yaps, pkts, num_pkts_max, + num_pkts_received); +} + +static int mm81x_yaps_update_status(struct mm81x_yaps *yaps) +{ + return yaps->ops->update_status(yaps); +} + +/* Mappings between sk_buff, skbq and yaps */ +static struct mm81x_skbq *mm81x_yaps_tc_q_from_aci(struct mm81x *mors, int aci) +{ + struct mm81x_yaps *yaps = &mors->hif.u.yaps; + + if (aci >= ARRAY_SIZE(yaps->data_tx_qs)) + return NULL; + return &yaps->data_tx_qs[aci]; +} + +static void mm81x_yaps_get_tx_qs(struct mm81x *mors, struct mm81x_skbq **qs, + int *num_qs) +{ + *qs = mors->hif.u.yaps.data_tx_qs; + *num_qs = YAPS_TX_SKBQ_MAX; +} + +static struct mm81x_skbq *mm81x_yaps_get_bcn_tc_q(struct mm81x *mors) +{ + return &mors->hif.u.yaps.beacon_q; +} + +static struct mm81x_skbq *mm81x_yaps_get_mgmt_tc_q(struct mm81x *mors) +{ + return &mors->hif.u.yaps.mgmt_q; +} + +static struct mm81x_skbq *mm81x_yaps_get_tx_cmd_queue(struct mm81x *mors) +{ + return &mors->hif.u.yaps.cmd_q; +} + +static int mm81x_yaps_irq_handler(struct mm81x *mors, u32 status) +{ + if (status & BIT(MM81X_INT_YAPS_FC_PKT_WAITING_IRQN)) + set_bit(MM81X_HIF_EVT_RX_PEND, &mors->hif.event_flags); + + if (status & BIT(MM81X_INT_YAPS_FC_PACKET_FREED_UP_IRQN)) { + timer_delete_sync_try(&mors->hif.u.yaps.chip_queue_full.timer); + set_bit(MM81X_HIF_EVT_TX_PACKET_FREED_UP_PEND, + &mors->hif.event_flags); + } + + queue_work(mors->chip_wq, &mors->hif_work); + return 0; +} + +const struct mm81x_hif_ops mm81x_yaps_ops = { + .init = mm81x_yaps_init, + .flush_tx_data = mm81x_yaps_flush_tx_data, + .flush_cmds = mm81x_yaps_flush_cmds, + .get_tx_status_pending_count = mm81x_yaps_get_tx_status_pending_count, + .get_tx_buffered_count = mm81x_yaps_get_tx_buffered_count, + .finish = mm81x_yaps_finish, + .skbq_get_tx_qs = mm81x_yaps_get_tx_qs, + .get_tx_beacon_queue = mm81x_yaps_get_bcn_tc_q, + .get_tx_mgmt_queue = mm81x_yaps_get_mgmt_tc_q, + .get_tx_cmd_queue = mm81x_yaps_get_tx_cmd_queue, + .get_tx_data_queue = mm81x_yaps_tc_q_from_aci, + .handle_irq = mm81x_yaps_irq_handler +}; + +static int mm81x_yaps_read_pkt(struct mm81x_yaps *yaps, struct sk_buff *skb) +{ + struct mm81x *mors = yaps->mors; + struct sk_buff_head skbq; + struct mm81x_skbq *mq = NULL; + struct mm81x_skb_hdr *hdr; + int skb_bytes_remaining; + int skb_len; + int ret = 0; + + if (!skb) { + ret = -EINVAL; + goto exit_return_page; + } + + __skb_queue_head_init(&skbq); + + hdr = (struct mm81x_skb_hdr *)skb->data; + if (hdr->sync != MM81X_SKB_HEADER_SYNC) { + dev_err(mors->dev, "sync value error [0xAA:%d], hdr.len %d", + hdr->sync, hdr->len); + ret = -EIO; + goto exit_return_page; + } + + if (yaps->mors->hif.validate_skb_checksum && + !mm81x_skbq_validate_checksum(skb->data)) { + dev_dbg(yaps->mors->dev, + "SKB checksum is invalid hdr:[c:%02X s:%02X len:%d]", + hdr->channel, hdr->sync, hdr->len); + + if (hdr->channel != MM81X_SKB_CHAN_TX_STATUS) { + ret = -EIO; + goto exit; + } + } + + switch (hdr->channel) { + case MM81X_SKB_CHAN_DATA: + case MM81X_SKB_CHAN_NDP_FRAMES: + case MM81X_SKB_CHAN_TX_STATUS: + case MM81X_SKB_CHAN_DATA_NOACK: + case MM81X_SKB_CHAN_BEACON: + case MM81X_SKB_CHAN_MGMT: + mq = &yaps->data_rx_q; + break; + case MM81X_SKB_CHAN_COMMAND: + mq = &yaps->cmd_resp_q; + break; + default: + dev_err(mors->dev, "channel value error [%d]", hdr->channel); + ret = -EIO; + goto exit_return_page; + } + + skb_len = sizeof(*hdr) + hdr->offset + le16_to_cpu(hdr->len); + skb_bytes_remaining = mm81x_skbq_space(mq); + + if (skb_len > skb_bytes_remaining) { + dev_err(mors->dev, + "Page will not fit in SKBQ, dropping - len %d remain %d", + skb_len, skb_bytes_remaining); + ret = -ENOMEM; + /* Queue work to clear backlog */ + queue_work(mors->net_wq, &mq->dispatch_work); + goto exit_return_page; + } + + skb_trim(skb, skb_len); + __skb_queue_tail(&skbq, skb); + + if (skb_queue_len(&skbq)) + mm81x_skbq_enq(mq, &skbq); + + /* push packets up in a different context */ + queue_work(mors->net_wq, &mq->dispatch_work); + + goto exit; + +exit_return_page: + if (ret && mq) { + dev_err(mors->dev, "failed %d", ret); + mm81x_skbq_purge(mq, &skbq); + goto exit; + } + +exit: + if (ret && skb) + dev_kfree_skb(skb); + + return ret; +} + +static int mm81x_yaps_tx(struct mm81x_yaps *yaps, struct mm81x_skbq *mq) +{ + int i; + int ret = 0; + int num_skbs = 0; + int tc_pkt_idx = 0; + int num_pkts_sent = 0; + struct sk_buff *skb; + struct sk_buff_head skbq_to_send; + struct sk_buff_head skbq_sent; + struct sk_buff_head skbq_failed; + struct sk_buff *pfirst, *pnext; + struct mm81x *mors = yaps->mors; + struct mm81x_skb_hdr *hdr; + + /* Check there is something on the queue */ + spin_lock_bh(&mq->lock); + skb = skb_peek(&mq->skbq); + spin_unlock_bh(&mq->lock); + if (!skb) + return 0; + + __skb_queue_head_init(&skbq_to_send); + __skb_queue_head_init(&skbq_sent); + __skb_queue_head_init(&skbq_failed); + + if (mq == &yaps->cmd_q) + /* Purge timed-out commands (this should not happen) */ + mm81x_skbq_purge(mq, &mq->pending); + else if (mq == &yaps->mgmt_q && skb_queue_len(&mq->skbq) > 0) + /* + * Purge old mgmt frames that have not been sent due to + * congestion + */ + mm81x_skbq_purge_aged(mors, mq); + + num_skbs = + mm81x_skbq_deq_num_skb(mq, &skbq_to_send, MAX_PKTS_PER_TX_TXN); + + skb_queue_walk_safe(&skbq_to_send, pfirst, pnext) { + enum mm81x_yaps_to_chip_q tc_queue; + + hdr = (struct mm81x_skb_hdr *)pfirst->data; + switch (hdr->channel) { + case MM81X_SKB_CHAN_COMMAND: + tc_queue = MM81X_YAPS_CMD_Q; + break; + case MM81X_SKB_CHAN_BEACON: + tc_queue = MM81X_YAPS_BEACON_Q; + break; + case MM81X_SKB_CHAN_MGMT: + tc_queue = MM81X_YAPS_MGMT_Q; + break; + default: + tc_queue = MM81X_YAPS_TX_Q; + break; + } + yaps->hw.to_chip_pkts[tc_pkt_idx].tc_queue = tc_queue; + yaps->hw.to_chip_pkts[tc_pkt_idx].skb = pfirst; + tc_pkt_idx++; + } + + /* Send queued packets to chip */ + ret = mm81x_yaps_update_status(yaps); + if (ret) + return ret; + + ret = mm81x_yaps_write_pkts(yaps, yaps->hw.to_chip_pkts, tc_pkt_idx, + &num_pkts_sent); + + /* Move sent packets to done queue */ + for (i = 0; i < num_pkts_sent; ++i) { + pfirst = __skb_dequeue(&skbq_to_send); + __skb_queue_tail(&skbq_sent, pfirst); + } + + for (i = num_pkts_sent; i < num_skbs; ++i) { + pfirst = __skb_dequeue(&skbq_to_send); + __skb_queue_tail(&skbq_failed, pfirst); + } + + if (skb_queue_len(&skbq_failed) > 0) { + mm81x_skbq_enq_prepend(mq, &skbq_failed); + + /* queue full, can't requeue */ + if (skb_queue_len(&skbq_failed) > 0) { + dev_warn(mors->dev, + "can't requeue failed pkts, purging"); + __skb_queue_purge(&skbq_failed); + } + } + + if (skb_queue_len(&skbq_sent) > 0) + mm81x_skbq_tx_complete(mq, &skbq_sent); + + return ret; +} + +/* Returns true if there are TX data pages waiting to be sent */ +static bool mm81x_yaps_tx_data_handler(struct mm81x_yaps *yaps) +{ + s16 aci; + u32 count = 0; + struct mm81x *mors = yaps->mors; + + for (aci = MM81X_ACI_VO; aci >= 0; aci--) { + struct mm81x_skbq *data_q = mm81x_yaps_tc_q_from_aci(mors, aci); + + if (!mm81x_is_data_tx_allowed(mors)) + break; + + yaps->chip_queue_full.is_full = mm81x_yaps_tx(yaps, data_q); + count += mm81x_skbq_count(data_q); + + if (yaps->chip_queue_full.is_full) + break; + + if (aci == MM81X_ACI_BE) + break; + } + + /* + * Data has potentially been transmitted from the data SKBQs. + * If the mac80211 TX data Qs were previously stopped, now would + * be a good time to check if they can be started again. + */ + mm81x_skbq_may_wake_tx_queues(mors); + + return (count > 0) && mm81x_is_data_tx_allowed(mors); +} + +/* Returns true if there are commands waiting to be sent */ +static bool mm81x_yaps_tx_cmd_handler(struct mm81x_yaps *yaps) +{ + struct mm81x_skbq *cmd_q = &yaps->cmd_q; + + mm81x_yaps_tx(yaps, cmd_q); + + return mm81x_skbq_count(cmd_q) > 0; +} + +static bool mm81x_yaps_tx_beacon_handler(struct mm81x_yaps *yaps) +{ + struct mm81x_skbq *beacon_q = &yaps->beacon_q; + + mm81x_yaps_tx(yaps, beacon_q); + + return mm81x_skbq_count(beacon_q) > 0; +} + +static bool mm81x_yaps_tx_mgmt_handler(struct mm81x_yaps *yaps) +{ + struct mm81x_skbq *mgmt_q = &yaps->mgmt_q; + + mm81x_yaps_tx(yaps, mgmt_q); + + return mm81x_skbq_count(mgmt_q) > 0; +} + +/* Returns true if there are populated RX pages left in the device */ +static bool mm81x_yaps_rx_handler(struct mm81x_yaps *yaps) +{ + int ret = 0; + int i; + int num_pks_received; + + ret = mm81x_yaps_update_status(yaps); + if (ret) + goto exit; + + ret = mm81x_yaps_read_pkts(yaps, yaps->hw.from_chip_pkts, + MAX_PKTS_PER_RX_TXN, &num_pks_received); + if (ret && ret != -EAGAIN) { + dev_err(yaps->mors->dev, "YAPS read_pkts fail: %d", ret); + goto exit; + } + + for (i = 0; i < num_pks_received; ++i) { + mm81x_yaps_read_pkt(yaps, yaps->hw.from_chip_pkts[i].skb); + yaps->hw.from_chip_pkts[i].skb = NULL; + } + +exit: + if (ret == -ENOMEM || ret == -EAGAIN) + return true; + else + return false; +} + +void mm81x_yaps_stale_tx_work(struct work_struct *work) +{ + int i; + int flushed = 0; + struct mm81x *mors = container_of(work, struct mm81x, tx_stale_work); + struct mm81x_yaps *yaps; + + yaps = &mors->hif.u.yaps; + flushed += mm81x_skbq_check_for_stale_tx(mors, &yaps->beacon_q); + flushed += mm81x_skbq_check_for_stale_tx(mors, &yaps->mgmt_q); + + for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++) + flushed += mm81x_skbq_check_for_stale_tx(mors, + &yaps->data_tx_qs[i]); + + if (!flushed) + return; + + dev_dbg(mors->dev, "Flushed %d stale TX SKBs", flushed); + + if (mors->ps.enable && !mors->ps.suspended && + (mm81x_yaps_get_tx_buffered_count(mors) == 0)) { + /* Evaluate ps to check if it was gated on a stale tx status */ + queue_delayed_work(mors->chip_wq, &mors->ps.delayed_eval_work, + 0); + } +} + +void mm81x_yaps_work(struct work_struct *work) +{ + struct mm81x *mors = container_of(work, struct mm81x, hif_work); + unsigned long *flags = &mors->hif.event_flags; + struct mm81x_yaps *yaps = &mors->hif.u.yaps; + + if (test_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags)) + return; + + if (!*flags) + return; + + /* Disable power save in case it is running */ + mm81x_ps_disable(mors); + mm81x_claim_bus(mors); + + /* + * Handle any populated RX pages from chip first to + * avoid dropping pkts due to full on-chip buffers. + * Check if all pages were removed, set event flags if not. + */ + if (test_and_clear_bit(MM81X_HIF_EVT_RX_PEND, flags)) { + if (mm81x_yaps_rx_handler(yaps)) + set_bit(MM81X_HIF_EVT_RX_PEND, flags); + } + + /* TX any commands before considering data */ + if (test_and_clear_bit(MM81X_HIF_EVT_TX_COMMAND_PEND, flags)) { + if (mm81x_yaps_tx_cmd_handler(yaps)) + set_bit(MM81X_HIF_EVT_TX_COMMAND_PEND, flags); + } + + /* TX beacons before considering mgmt/data */ + if (test_and_clear_bit(MM81X_HIF_EVT_TX_BEACON_PEND, flags)) { + if (mm81x_yaps_tx_beacon_handler(yaps)) + set_bit(MM81X_HIF_EVT_TX_BEACON_PEND, flags); + } + + /* TX mgmt before considering data */ + if (test_and_clear_bit(MM81X_HIF_EVT_TX_MGMT_PEND, flags)) { + if (mm81x_yaps_tx_mgmt_handler(yaps)) + set_bit(MM81X_HIF_EVT_TX_MGMT_PEND, flags); + } + + /* Pause TX data Qs */ + if (test_and_clear_bit(MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND, flags)) { + test_and_clear_bit(MM81X_HIF_EVT_DATA_TRAFFIC_RESUME_PEND, + flags); + mm81x_skbq_data_traffic_pause(mors); + } + + /* Resume TX data Qs */ + if (test_and_clear_bit(MM81X_HIF_EVT_DATA_TRAFFIC_RESUME_PEND, flags)) + mm81x_skbq_data_traffic_resume(mors); + + /* Handle chip queue status */ + if (test_and_clear_bit(MM81X_HIF_EVT_TX_PACKET_FREED_UP_PEND, flags)) + yaps->chip_queue_full.is_full = false; + + /* Check to see if the queue is full or + * long enough has past since the queue was full + */ + if (yaps->chip_queue_full.is_full && + time_before(jiffies, yaps->chip_queue_full.retry_expiry)) + goto exit; + + /* Finally TX any data */ + if (test_and_clear_bit(MM81X_HIF_EVT_TX_DATA_PEND, flags)) { + if (mm81x_yaps_tx_data_handler(yaps)) + set_bit(MM81X_HIF_EVT_TX_DATA_PEND, flags); + + if (yaps->chip_queue_full.is_full) { + yaps->chip_queue_full.retry_expiry = + jiffies + + msecs_to_jiffies(CHIP_FULL_RECOVERY_TIMEOUT_MS); + mod_timer(&yaps->chip_queue_full.timer, + yaps->chip_queue_full.retry_expiry); + } + } + +exit: + + /* Disable power save in case it is running */ + mm81x_release_bus(mors); + mm81x_ps_enable(mors); + + /* Don't requeue work if we are shutting down. */ + if (yaps->finish) + return; + /* + * Evaluate all events except MM81X_HIF_EVT_TX_DATA_PEND in case data + * tx queue is full + */ + if ((*flags) & ~(1 << MM81X_HIF_EVT_TX_DATA_PEND)) + queue_work(mors->chip_wq, &mors->hif_work); + /* + * if data tx queue is not full and the work hasn't been queued let's + * queue it + */ + else if (!yaps->chip_queue_full.is_full && *flags) + queue_work(mors->chip_wq, &mors->hif_work); +} + +int mm81x_yaps_get_tx_status_pending_count(struct mm81x *mors) +{ + int i = 0; + int count = 0; + struct mm81x_yaps *yaps; + + yaps = &mors->hif.u.yaps; + count += skb_queue_len(&yaps->beacon_q.pending); + count += skb_queue_len(&yaps->mgmt_q.pending); + count += skb_queue_len(&yaps->cmd_q.pending); + + for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++) + count += skb_queue_len(&yaps->data_tx_qs[i].pending); + + return count; +} + +int mm81x_yaps_get_tx_buffered_count(struct mm81x *mors) +{ + int i = 0; + int count = 0; + struct mm81x_yaps *yaps; + + yaps = &mors->hif.u.yaps; + count += skb_queue_len(&yaps->beacon_q.skbq) + + skb_queue_len(&yaps->beacon_q.pending); + count += skb_queue_len(&yaps->mgmt_q.skbq) + + skb_queue_len(&yaps->mgmt_q.pending); + count += skb_queue_len(&yaps->cmd_q.skbq) + + skb_queue_len(&yaps->cmd_q.pending); + + for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++) + count += mm81x_skbq_count_tx_ready(&yaps->data_tx_qs[i]) + + skb_queue_len(&yaps->data_tx_qs[i].pending); + + return count; +} + +static void mm81x_yaps_tx_q_full_timer(struct timer_list *t) +{ + struct mm81x_yaps *yaps = + timer_container_of(yaps, t, chip_queue_full.timer); + + queue_work(yaps->mors->chip_wq, &yaps->mors->hif_work); +} + +static void mm81x_yaps_q_chip_full_timer_init(struct mm81x_yaps *yaps) +{ + timer_setup(&yaps->chip_queue_full.timer, mm81x_yaps_tx_q_full_timer, + 0); +} + +static void mm81x_yaps_q_chip_full_timer_finish(struct mm81x_yaps *yaps) +{ + timer_delete_sync_try(&yaps->chip_queue_full.timer); +} + +int mm81x_yaps_init(struct mm81x *mors) +{ + int i, ret; + struct mm81x_yaps *yaps; + + ret = mm81x_yaps_hw_init(mors); + if (ret) { + dev_err(mors->dev, "mm81x_yaps_hw_init failed %d", ret); + return ret; + } + + yaps = &mors->hif.u.yaps; + yaps->mors = mors; + + mm81x_claim_bus(mors); + + ret = mm81x_yaps_alloc_pkt_buffers(yaps); + if (ret) { + dev_err(mors->dev, "Failed to allocate YAPS packet buffers: %d", + ret); + mm81x_yaps_hw_finish(mors); + mm81x_release_bus(mors); + return ret; + } + + /* YAPS is bi-directional */ + mm81x_skbq_init(mors, &yaps->data_rx_q, + MM81X_HIF_FLAGS_DATA | MM81X_HIF_FLAGS_DIR_TO_HOST); + mm81x_skbq_init(mors, &yaps->beacon_q, + MM81X_HIF_FLAGS_DATA | MM81X_HIF_FLAGS_DIR_TO_HOST); + mm81x_skbq_init(mors, &yaps->mgmt_q, + MM81X_HIF_FLAGS_DATA | MM81X_HIF_FLAGS_DIR_TO_HOST); + + for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++) { + mm81x_skbq_init(mors, &yaps->data_tx_qs[i], + MM81X_HIF_FLAGS_DATA | + MM81X_HIF_FLAGS_DIR_TO_CHIP); + } + + mm81x_skbq_init(mors, &yaps->cmd_q, + MM81X_HIF_FLAGS_COMMAND | MM81X_HIF_FLAGS_DIR_TO_CHIP); + mm81x_skbq_init(mors, &yaps->cmd_resp_q, + MM81X_HIF_FLAGS_COMMAND | MM81X_HIF_FLAGS_DIR_TO_HOST); + + mm81x_yaps_q_chip_full_timer_init(yaps); + INIT_WORK(&mors->hif_work, mm81x_yaps_work); + INIT_WORK(&mors->tx_stale_work, mm81x_yaps_stale_tx_work); + mm81x_release_bus(mors); + mm81x_hw_enable_stop_notifications(mors, true); + return 0; +} + +void mm81x_yaps_finish(struct mm81x *mors) +{ + int i; + struct mm81x_yaps *yaps; + + mm81x_yaps_hw_enable_irqs(mors, false); + + yaps = &mors->hif.u.yaps; + yaps->finish = true; + + mm81x_skbq_finish(&yaps->data_rx_q); + mm81x_skbq_finish(&yaps->beacon_q); + mm81x_skbq_finish(&yaps->mgmt_q); + + for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++) + mm81x_skbq_finish(&yaps->data_tx_qs[i]); + + mm81x_skbq_finish(&yaps->cmd_q); + mm81x_skbq_finish(&yaps->cmd_resp_q); + + mm81x_yaps_q_chip_full_timer_finish(yaps); + + cancel_work_sync(&mors->hif_work); + cancel_work_sync(&mors->tx_stale_work); + + mm81x_yaps_free_pkt_buffers(yaps); + mm81x_yaps_hw_finish(mors); +} + +void mm81x_yaps_flush_tx_data(struct mm81x *mors) +{ + int i; + struct mm81x_yaps *yaps = &mors->hif.u.yaps; + + mm81x_skbq_tx_flush(&yaps->beacon_q); + mm81x_skbq_tx_flush(&yaps->mgmt_q); + + for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++) + mm81x_skbq_tx_flush(&yaps->data_tx_qs[i]); +} + +void mm81x_yaps_flush_cmds(struct mm81x *mors) +{ + struct mm81x_yaps *yaps = &mors->hif.u.yaps; + + if (yaps->flags & MM81X_HIF_FLAGS_COMMAND) { + mm81x_skbq_finish(&yaps->cmd_q); + mm81x_skbq_finish(&yaps->cmd_resp_q); + } +} diff --git a/drivers/net/wireless/morsemicro/mm81x/yaps.h b/drivers/net/wireless/morsemicro/mm81x/yaps.h new file mode 100644 index 000000000000..2b2bb5f6e399 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/yaps.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_YAPS_H_ +#define _MM81X_YAPS_H_ + +#include +#include +#include "skbq.h" + +#define YAPS_TX_SKBQ_MAX 4 + +struct mm81x_hif_ops; +extern const struct mm81x_hif_ops mm81x_yaps_ops; + +enum mm81x_yaps_to_chip_q { + MM81X_YAPS_TX_Q = 0, + MM81X_YAPS_CMD_Q, + MM81X_YAPS_BEACON_Q, + MM81X_YAPS_MGMT_Q, + /* Keep this last */ + MM81X_YAPS_NUM_TC_Q +}; + +struct mm81x_yaps_pkt { + struct sk_buff *skb; + enum mm81x_yaps_to_chip_q tc_queue; +}; + +struct mm81x_yaps { + struct mm81x *mors; + struct mm81x_yaps_hw_aux_data *aux_data; + const struct mm81x_yaps_ops *ops; + u8 flags; + struct { + struct mm81x_yaps_pkt *to_chip_pkts; + struct mm81x_yaps_pkt *from_chip_pkts; + } hw; + + /* Chip interface is stopping, new work should not be enqueued. */ + bool finish; + + struct mm81x_skbq data_tx_qs[YAPS_TX_SKBQ_MAX]; + struct mm81x_skbq beacon_q; + struct mm81x_skbq mgmt_q; + struct mm81x_skbq data_rx_q; + struct mm81x_skbq cmd_q; + struct mm81x_skbq cmd_resp_q; + + struct { + struct timer_list timer; + unsigned long retry_expiry; + bool is_full; + } chip_queue_full; +}; + +struct mm81x_yaps_ops { + int (*write_pkts)(struct mm81x_yaps *yaps, struct mm81x_yaps_pkt *pkts, + int num_pkts, int *num_pkts_sent); + int (*read_pkts)(struct mm81x_yaps *yaps, struct mm81x_yaps_pkt *pkts, + int num_pkts_max, int *num_pkts_received); + int (*update_status)(struct mm81x_yaps *yaps); +}; + +int mm81x_yaps_init(struct mm81x *mors); +void mm81x_yaps_show(struct mm81x_yaps *yaps, struct seq_file *file); +void mm81x_yaps_finish(struct mm81x *mors); +void mm81x_yaps_flush_tx_data(struct mm81x *mors); +void mm81x_yaps_flush_cmds(struct mm81x *mors); +void mm81x_yaps_work(struct work_struct *work); +void mm81x_yaps_stale_tx_work(struct work_struct *work); +int mm81x_yaps_get_tx_status_pending_count(struct mm81x *mors); +int mm81x_yaps_get_tx_buffered_count(struct mm81x *mors); + +#endif /* !_MM81X_YAPS_H_ */ diff --git a/drivers/net/wireless/morsemicro/mm81x/yaps_hw.c b/drivers/net/wireless/morsemicro/mm81x/yaps_hw.c new file mode 100644 index 000000000000..3d641d23c35b --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/yaps_hw.c @@ -0,0 +1,702 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2026 Morse Micro + */ +#include "yaps_hw.h" +#include "bus.h" +#include "hif.h" +#include "yaps.h" + +#define YAPS_HW_WINDOW_SIZE_BYTES 32768 +#define YAPS_MAX_PKT_SIZE_BYTES 16128 +#define YAPS_METADATA_PAGE_COUNT 1 + +#define YAPS_PHANDLE_CORRUPTION_WAR_EXTRA_PAGE 1 + +#define YAPS_PAGE_SIZE 256 + +/* Calculate padding required for yaps transaction */ +#define YAPS_CALC_PADDING(_bytes) ((_bytes) & 0x3 ? (4 - ((_bytes) & 0x3)) : 0) + +#define YAPS_RESERVED_PAGE_SIZE 256 + +/* + * Yaps data stream delimiter is a 32 bit word with the following fields: + * + * pkt_size (14 bits) - Packet size not including delimiter or padding + * pool_id (3 bits) - Pool that pages should be allocated from. + * padding (2 bits) - Padding required to bring packet to word (4 byte) + * irq (1 bit ) - Raise a PKT_IRQ on the YDS this is sent to + * reserved (5 bits) - Reserved, must write as 0 + * crc (7 bits) - YAPS CRC + */ + +/* Packet size not including delimiter or padding */ +#define YAPS_DELIM_GET_PKT_SIZE(_delim) \ + (((_delim) & 0x3FFF) - YAPS_RESERVED_PAGE_SIZE) +#define YAPS_DELIM_SET_PKT_SIZE(_pkt_size) \ + (((_pkt_size) & 0x3FFF) + YAPS_RESERVED_PAGE_SIZE) +#define YAPS_DELIM_GET_PHANDLE_SIZE(_delim) (((_delim) & 0x3FFF)) + +/* Pool that pages should be allocated from. */ +#define YAPS_DELIM_SET_POOL_ID(_pool_id) (((_pool_id) & 0x7) << 14) + +/* Padding required to bring packet to word (4 byte) boundary */ +#define YAPS_DELIM_GET_PADDING(_delim) (((_delim) >> 17) & 0x3) +#define YAPS_DELIM_SET_PADDING(_padding) (((_padding) & 0x3) << 17) + +/* Raise a PKT_IRQ on the YDS this is sent to */ +#define YAPS_DELIM_SET_IRQ(_irq) (((_irq) & 0x1) << 19) + +/* YAPS CRC */ +#define YAPS_DELIM_GET_CRC(_delim) (((_delim) >> 25) & 0x7F) +#define YAPS_DELIM_SET_CRC(_crc) (((_crc) & 0x7F) << 25) + +struct mm81x_yaps_status_regs { + /* Allocation pools */ + u32 tc_tx_pool_num_pages; + u32 tc_cmd_pool_num_pages; + u32 tc_beacon_pool_num_pages; + u32 tc_mgmt_pool_num_pages; + u32 fc_rx_pool_num_pages; + u32 fc_resp_pool_num_pages; + u32 fc_tx_sts_pool_num_pages; + u32 fc_aux_pool_num_pages; + u32 tc_tx_num_pkts; + u32 tc_cmd_num_pkts; + u32 tc_beacon_num_pkts; + u32 tc_mgmt_num_pkts; + u32 fc_num_pkts; + u32 fc_done_num_pkts; + u32 fc_rx_bytes_in_queue; + u32 tc_delim_crc_fail_detected; + u32 fc_host_ysl_status; + u32 lock; +} __packed __aligned(8); + +struct mm81x_yaps_hw_status_regs { + __le32 tc_tx_pool_num_pages; + __le32 tc_cmd_pool_num_pages; + __le32 tc_beacon_pool_num_pages; + __le32 tc_mgmt_pool_num_pages; + __le32 fc_rx_pool_num_pages; + __le32 fc_resp_pool_num_pages; + __le32 fc_tx_sts_pool_num_pages; + __le32 fc_aux_pool_num_pages; + __le32 tc_tx_num_pkts; + __le32 tc_cmd_num_pkts; + __le32 tc_beacon_num_pkts; + __le32 tc_mgmt_num_pkts; + __le32 fc_num_pkts; + __le32 fc_done_num_pkts; + __le32 fc_rx_bytes_in_queue; + __le32 tc_delim_crc_fail_detected; + __le32 fc_host_ysl_status; + __le32 lock; +} __packed __aligned(8); + +struct mm81x_yaps_hw_aux_data { + unsigned long access_lock; + + u32 yds_addr; + u32 ysl_addr; + u32 status_regs_addr; + + /* Alloc pool sizes */ + u16 tc_tx_pool_size; + u16 tc_cmd_pool_size; + u8 tc_beacon_pool_size; + u8 tc_mgmt_pool_size; + u8 fc_rx_pool_size; + u8 fc_resp_pool_size; + u8 fc_tx_sts_pool_size; + u8 fc_aux_pool_size; + + /* To chip/from chip queue sizes */ + u8 tc_tx_q_size; + u8 tc_cmd_q_size; + u8 tc_beacon_q_size; + u8 tc_mgmt_q_size; + u8 fc_q_size; + u8 fc_done_q_size; + + u16 reserved_yaps_page_size; + + /* Buffers to/from chip to support large contiguous reads/writes */ + char *to_chip_buffer; + char *from_chip_buffer; + + /* status registers in host endian */ + struct mm81x_yaps_status_regs status_regs; + + /* DMA target buffer in firmware endian */ + struct mm81x_yaps_hw_status_regs hw_status_regs; +}; + +static int mm81x_yaps_hw_lock(struct mm81x_yaps *yaps) +{ + if (test_and_set_bit_lock(0, &yaps->aux_data->access_lock)) + return -1; + return 0; +} + +static void mm81x_yaps_hw_unlock(struct mm81x_yaps *yaps) +{ + clear_bit_unlock(0, &yaps->aux_data->access_lock); +} + +static void +mm81x_yaps_hw_fill_aux_data_from_hw_tbl(struct mm81x_yaps_hw_aux_data *a, + struct mm81x_yaps_hw_table *t) +{ + a->ysl_addr = __le32_to_cpu(t->ysl_addr); + a->yds_addr = __le32_to_cpu(t->yds_addr); + a->status_regs_addr = __le32_to_cpu(t->status_regs_addr); + a->tc_tx_pool_size = __le16_to_cpu(t->tc_tx_pool_size); + a->fc_rx_pool_size = __le16_to_cpu(t->fc_rx_pool_size); + a->tc_cmd_pool_size = t->tc_cmd_pool_size; + a->tc_beacon_pool_size = t->tc_beacon_pool_size; + a->tc_mgmt_pool_size = t->tc_mgmt_pool_size; + a->fc_resp_pool_size = t->fc_resp_pool_size; + a->fc_tx_sts_pool_size = t->fc_tx_sts_pool_size; + a->fc_aux_pool_size = t->fc_aux_pool_size; + a->tc_tx_q_size = t->tc_tx_q_size; + a->tc_cmd_q_size = t->tc_cmd_q_size; + a->tc_beacon_q_size = t->tc_beacon_q_size; + a->tc_mgmt_q_size = t->tc_mgmt_q_size; + a->fc_q_size = t->fc_q_size; + a->fc_done_q_size = t->fc_done_q_size; + a->reserved_yaps_page_size = le16_to_cpu(t->yaps_reserved_page_size); +} + +static u8 mm81x_yaps_hw_crc(u32 word) +{ + u8 crc = 0; + u8 byte; + int i; + + /* Mask to look at only non-CRC bits */ + word &= 0x1ffffff; + + for (i = 0; i < 4; i++) { + byte = (word >> 24) & 0xff; + crc = crc7_be(crc, &byte, 1); + word <<= 8; + } + + return crc >> 1; +} + +static u32 mm81x_write_pkts_h_build_delim(struct mm81x_yaps *yaps, + unsigned int size, u8 pool_id, + bool irq) +{ + u32 delim = 0; + + delim |= YAPS_DELIM_SET_PKT_SIZE(size); + delim |= YAPS_DELIM_SET_PADDING(YAPS_CALC_PADDING(size)); + delim |= YAPS_DELIM_SET_POOL_ID(pool_id); + delim |= YAPS_DELIM_SET_IRQ(irq); + delim |= YAPS_DELIM_SET_CRC(mm81x_yaps_hw_crc(delim)); + return delim; +} + +void mm81x_yaps_hw_enable_irqs(struct mm81x *mors, bool enable) +{ + mm81x_hw_irq_enable(mors, MM81X_INT_YAPS_FC_PKT_WAITING_IRQN, enable); + mm81x_hw_irq_enable(mors, MM81X_INT_YAPS_FC_PACKET_FREED_UP_IRQN, + enable); +} + +void mm81x_yaps_hw_read_table(struct mm81x *mors, + struct mm81x_yaps_hw_table *tbl_ptr) +{ + mm81x_yaps_hw_fill_aux_data_from_hw_tbl(mors->hif.u.yaps.aux_data, + tbl_ptr); + mm81x_yaps_hw_enable_irqs(mors, true); +} + +static unsigned int mm81x_write_pkts_h_pages_required(struct mm81x_yaps *yaps, + unsigned int size_bytes) +{ + /* Always account for the first metadata page */ + return DIV_ROUND_UP(size_bytes + + yaps->aux_data->reserved_yaps_page_size, + YAPS_PAGE_SIZE) + + YAPS_METADATA_PAGE_COUNT + + YAPS_PHANDLE_CORRUPTION_WAR_EXTRA_PAGE; +} + +/* + * Checks if a single pkt will fit in the chip using the pool/alloc holding + * information from the last status register read. + */ +static bool mm81x_write_pkts_h_will_fit(struct mm81x_yaps *yaps, + struct mm81x_yaps_pkt *pkt, bool update) +{ + bool will_fit = true; + const int pages_required = + mm81x_write_pkts_h_pages_required(yaps, pkt->skb->len); + int *pool_pages_avail = NULL; + int *pkts_in_queue = NULL; + int queue_pkts_avail = 0; + + switch (pkt->tc_queue) { + case MM81X_YAPS_TX_Q: + pool_pages_avail = + &yaps->aux_data->status_regs.tc_tx_pool_num_pages; + pkts_in_queue = &yaps->aux_data->status_regs.tc_tx_num_pkts; + queue_pkts_avail = + yaps->aux_data->tc_tx_q_size - *pkts_in_queue; + break; + case MM81X_YAPS_CMD_Q: + pool_pages_avail = + &yaps->aux_data->status_regs.tc_cmd_pool_num_pages; + pkts_in_queue = &yaps->aux_data->status_regs.tc_cmd_num_pkts; + queue_pkts_avail = + yaps->aux_data->tc_cmd_q_size - *pkts_in_queue; + break; + case MM81X_YAPS_BEACON_Q: + pool_pages_avail = + &yaps->aux_data->status_regs.tc_beacon_pool_num_pages; + pkts_in_queue = &yaps->aux_data->status_regs.tc_beacon_num_pkts; + queue_pkts_avail = + yaps->aux_data->tc_beacon_q_size - *pkts_in_queue; + break; + case MM81X_YAPS_MGMT_Q: + pool_pages_avail = + &yaps->aux_data->status_regs.tc_mgmt_pool_num_pages; + pkts_in_queue = &yaps->aux_data->status_regs.tc_mgmt_num_pkts; + queue_pkts_avail = + yaps->aux_data->tc_mgmt_q_size - *pkts_in_queue; + break; + default: + dev_err(yaps->mors->dev, "yaps invalid tc queue"); + return false; + } + + WARN_ON(queue_pkts_avail < 0); + + if (pages_required > *pool_pages_avail) + will_fit = false; + + if (queue_pkts_avail == 0) + will_fit = false; + + if (will_fit && update) { + *pool_pages_avail -= pages_required; + *pkts_in_queue += 1; + } + + return will_fit; +} + +static int mm81x_write_pkts_h_err_check(struct mm81x_yaps *yaps, + struct mm81x_yaps_pkt *pkt) +{ + if (pkt->skb->len + yaps->aux_data->reserved_yaps_page_size > + YAPS_MAX_PKT_SIZE_BYTES) + return -EMSGSIZE; + if (pkt->tc_queue >= MM81X_YAPS_NUM_TC_Q) + return -EINVAL; + if (!mm81x_write_pkts_h_will_fit(yaps, pkt, true)) + return -EAGAIN; + + return 0; +} + +static int mm81x_yaps_hw_write_pkts(struct mm81x_yaps *yaps, + struct mm81x_yaps_pkt *pkts, int num_pkts, + int *num_pkts_sent) +{ + int ret = 0; + int i; + u32 delim = 0; + int tx_len; + int batch_txn_len = 0; + int pkts_pending = 0; + bool delim_irq = false; + char *to_chip_buffer_aligned = + PTR_ALIGN(yaps->aux_data->to_chip_buffer, + mm81x_bus_get_alignment(yaps->mors)); + char *write_buf = to_chip_buffer_aligned; + + ret = mm81x_yaps_hw_lock(yaps); + if (ret) { + dev_dbg(yaps->mors->dev, "yaps lock failed %d", ret); + return ret; + } + + *num_pkts_sent = 0; + + /* Check packet conditions */ + ret = mm81x_write_pkts_h_err_check(yaps, &pkts[0]); + if (ret) + goto exit; + + /* Batch packets into larger transactions */ + for (i = 0; i < num_pkts; ++i) { + u32 pkt_size = + pkts[i].skb->len + YAPS_CALC_PADDING(pkts[i].skb->len); + tx_len = pkt_size + sizeof(delim); + + /* + * Send when we have reached window size, don't split pkt over + * boundary + */ + if ((batch_txn_len + tx_len) > YAPS_HW_WINDOW_SIZE_BYTES) { + ret = mm81x_dm_write(yaps->mors, + yaps->aux_data->yds_addr, + to_chip_buffer_aligned, + batch_txn_len); + + batch_txn_len = 0; + if (ret) + goto exit; + write_buf = to_chip_buffer_aligned; + *num_pkts_sent += pkts_pending; + pkts_pending = 0; + } + + if ((i + 1) == num_pkts) { + /* The last packet in the queue has IRQ set */ + delim_irq = true; + } else { + /* + * Since this is not the last packet, we can check for + * the next one. In case of errors in the next packet + * set the IRQ + */ + ret = mm81x_write_pkts_h_err_check(yaps, &pkts[i + 1]); + if (ret) + delim_irq = true; + } + + /* Build stream header*/ + delim = mm81x_write_pkts_h_build_delim( + yaps, pkt_size, pkts[i].tc_queue, delim_irq); + *((__le32 *)write_buf) = cpu_to_le32(delim); + memcpy(write_buf + sizeof(delim), pkts[i].skb->data, + pkts[i].skb->len); + + write_buf += tx_len; + batch_txn_len += tx_len; + pkts_pending++; + + if (ret) + goto exit; + } + +exit: + if (batch_txn_len > 0) { + ret = mm81x_dm_write(yaps->mors, yaps->aux_data->yds_addr, + to_chip_buffer_aligned, batch_txn_len); + *num_pkts_sent += pkts_pending; + } + + mm81x_yaps_hw_unlock(yaps); + return ret; +} + +static bool mm81x_read_pkts_h_is_valid_delim(u32 delim) +{ + u8 calc_crc = mm81x_yaps_hw_crc(delim); + int pkt_size = YAPS_DELIM_GET_PHANDLE_SIZE(delim); + int padding = YAPS_DELIM_GET_PADDING(delim); + + if (calc_crc != YAPS_DELIM_GET_CRC(delim)) + return false; + + if (pkt_size == 0) + return false; + + if ((pkt_size + padding) > YAPS_MAX_PKT_SIZE_BYTES) + return false; + + /* Pkt length + padding should not require more padding */ + if (YAPS_CALC_PADDING(pkt_size) != padding) + return false; + + return true; +} + +static int mm81x_read_pkts_h_bytes_remaining(struct mm81x_yaps *yaps) +{ + u32 bytes_in_queue = yaps->aux_data->status_regs.fc_rx_bytes_in_queue; + u32 delim_overhead = + yaps->aux_data->status_regs.fc_num_pkts * sizeof(u32); + u32 reserved_bytes = yaps->aux_data->status_regs.fc_num_pkts * + yaps->aux_data->reserved_yaps_page_size; + + if (WARN_ON(bytes_in_queue > INT_MAX) || + WARN_ON(delim_overhead > INT_MAX) || + WARN_ON(reserved_bytes > INT_MAX)) + return -EIO; + + return (int)bytes_in_queue; +} + +static int mm81x_yaps_hw_read_pkts(struct mm81x_yaps *yaps, + struct mm81x_yaps_pkt *pkts, + int num_pkts_max, int *num_pkts_received) +{ + int ret; + int i = 0; + char *from_chip_buffer_aligned = + PTR_ALIGN(yaps->aux_data->from_chip_buffer, + mm81x_bus_get_alignment(yaps->mors)); + char *read_ptr = from_chip_buffer_aligned; + int bytes_remaining = mm81x_read_pkts_h_bytes_remaining(yaps); + bool again = false; + + *num_pkts_received = 0; + + if (num_pkts_max == 0 || bytes_remaining == 0) + return 0; + if (bytes_remaining < 0) + return bytes_remaining; + + if (bytes_remaining > YAPS_HW_WINDOW_SIZE_BYTES) { + bytes_remaining = YAPS_HW_WINDOW_SIZE_BYTES; + again = true; + } + + /* + * This is more coarse-grained than it needs to be - once the data + * is read into a local buffer the lock can be released, however + * access to from_chip_buffer will need to be protected with its + * own lock + */ + ret = mm81x_yaps_hw_lock(yaps); + if (ret) { + dev_dbg(yaps->mors->dev, "yaps lock failed %d", ret); + return ret; + } + + /* Read all available packets to the buffer */ + ret = mm81x_dm_read(yaps->mors, yaps->aux_data->ysl_addr, + from_chip_buffer_aligned, bytes_remaining); + + if (ret) + goto exit; + + /* Split serialised packets from buffer */ + while (i < num_pkts_max && bytes_remaining > 0) { + u32 delim; + int total_len; + int pkt_size; + + delim = le32_to_cpu(*((__le32 *)read_ptr)); + read_ptr += sizeof(delim); + bytes_remaining -= sizeof(delim); + + /* End of stream */ + if (!delim) + break; + + if (!mm81x_read_pkts_h_is_valid_delim(delim)) { + /* + * This will start a hunt for a valid delimiter. Given + * the CRC is only 7 bit it's possible to find an + * invalid block with a valid delimiter, leading to + * desynchronisation. + */ + dev_warn(yaps->mors->dev, "yaps invalid delim"); + break; + } + + /* Total length in chip */ + pkt_size = YAPS_DELIM_GET_PKT_SIZE(delim); + total_len = pkt_size + YAPS_DELIM_GET_PADDING(delim); + + if (pkts[i].skb) + dev_err(yaps->mors->dev, "yaps packet leak"); + + /* SKB doesn't want padding */ + pkts[i].skb = dev_alloc_skb(pkt_size); + if (!pkts[i].skb) { + ret = -ENOMEM; + dev_err(yaps->mors->dev, "yaps no mem for skb"); + goto exit; + } + skb_put(pkts[i].skb, pkt_size); + + if (total_len <= bytes_remaining) { + memcpy(pkts[i].skb->data, read_ptr, pkt_size); + read_ptr += total_len; + bytes_remaining -= total_len; + } else { + const int read_overhang_len = + total_len - bytes_remaining; + const int pkt_overhang_len = pkt_size - bytes_remaining; + + memcpy(pkts[i].skb->data, read_ptr, bytes_remaining); + read_ptr = from_chip_buffer_aligned; + + ret = mm81x_dm_read( + yaps->mors, + /* Offset by 4 to avoid retry logic */ + yaps->aux_data->ysl_addr + 4, read_ptr, + read_overhang_len); + + if (ret) + goto exit; + + memcpy(pkts[i].skb->data + bytes_remaining, read_ptr, + pkt_overhang_len); + read_ptr += read_overhang_len; + bytes_remaining = 0; + } + + *num_pkts_received += 1; + i++; + } + + if (again) + ret = -EAGAIN; + +exit: + mm81x_yaps_hw_unlock(yaps); + return ret; +} + +static int mm81x_yaps_hw_update_status(struct mm81x_yaps *yaps) +{ + int ret; + int tc_total_pkt_count; + unsigned long reg_read_timeout; + struct mm81x_yaps_status_regs *r = &yaps->aux_data->status_regs; + struct mm81x_yaps_hw_status_regs *hw_r = &yaps->aux_data->hw_status_regs; + + ret = mm81x_yaps_hw_lock(yaps); + if (ret) { + dev_dbg(yaps->mors->dev, "yaps lock failed %d", ret); + return ret; + } + + reg_read_timeout = jiffies + msecs_to_jiffies(100); + do { + if (time_after(jiffies, reg_read_timeout)) { + dev_err(yaps->mors->dev, + "timed out reading status registers: %d", ret); + ret = -ETIMEDOUT; + break; + } + + ret = mm81x_dm_read(yaps->mors, + yaps->aux_data->status_regs_addr, + (u8 *)hw_r, sizeof(*hw_r)); + } while (!ret && le32_to_cpu(hw_r->lock)); + + if (ret) { + if (ret != -ENODEV) { + dev_err(yaps->mors->dev, + "error reading yaps status registers: %d", ret); + } + goto exit_unlock; + } + + r->tc_tx_pool_num_pages = le32_to_cpu(hw_r->tc_tx_pool_num_pages); + r->tc_cmd_pool_num_pages = le32_to_cpu(hw_r->tc_cmd_pool_num_pages); + r->tc_beacon_pool_num_pages = le32_to_cpu(hw_r->tc_beacon_pool_num_pages); + r->tc_mgmt_pool_num_pages = le32_to_cpu(hw_r->tc_mgmt_pool_num_pages); + r->fc_rx_pool_num_pages = le32_to_cpu(hw_r->fc_rx_pool_num_pages); + r->fc_resp_pool_num_pages = le32_to_cpu(hw_r->fc_resp_pool_num_pages); + r->fc_tx_sts_pool_num_pages = le32_to_cpu(hw_r->fc_tx_sts_pool_num_pages); + r->fc_aux_pool_num_pages = le32_to_cpu(hw_r->fc_aux_pool_num_pages); + r->tc_tx_num_pkts = le32_to_cpu(hw_r->tc_tx_num_pkts); + r->tc_cmd_num_pkts = le32_to_cpu(hw_r->tc_cmd_num_pkts); + r->tc_beacon_num_pkts = le32_to_cpu(hw_r->tc_beacon_num_pkts); + r->tc_mgmt_num_pkts = le32_to_cpu(hw_r->tc_mgmt_num_pkts); + r->fc_num_pkts = le32_to_cpu(hw_r->fc_num_pkts); + r->fc_done_num_pkts = le32_to_cpu(hw_r->fc_done_num_pkts); + r->fc_rx_bytes_in_queue = le32_to_cpu(hw_r->fc_rx_bytes_in_queue); + r->tc_delim_crc_fail_detected = le32_to_cpu(hw_r->tc_delim_crc_fail_detected); + r->lock = le32_to_cpu(hw_r->lock); + r->fc_host_ysl_status = le32_to_cpu(hw_r->fc_host_ysl_status); + + tc_total_pkt_count = r->tc_tx_num_pkts + r->tc_cmd_num_pkts + + r->tc_beacon_num_pkts + r->tc_mgmt_num_pkts; + + if (r->tc_delim_crc_fail_detected) { + /* + * Host and chip have become desynchronised. This can happen if + * the chip crashes during a YAPS transaction. We cannot + * recover from this. + */ + dev_err(yaps->mors->dev, + "to-chip yaps delimiter CRC fail, pkt_count=%d", + tc_total_pkt_count); + ret = -EIO; + } + + if (mm81x_read_pkts_h_bytes_remaining(yaps)) + set_bit(MM81X_HIF_EVT_RX_PEND, &yaps->mors->hif.event_flags); + +exit_unlock: + mm81x_yaps_hw_unlock(yaps); + return ret; +} + +static const struct mm81x_yaps_ops mm81x_yaps_hw_ops = { + .write_pkts = mm81x_yaps_hw_write_pkts, + .read_pkts = mm81x_yaps_hw_read_pkts, + .update_status = mm81x_yaps_hw_update_status, +}; + +int mm81x_yaps_hw_init(struct mm81x *mors) +{ + int ret = 0; + struct mm81x_yaps *yaps = NULL; + int aux_data_len = sizeof(struct mm81x_yaps_hw_aux_data); + int alignment = mm81x_bus_get_alignment(mors); + + yaps = &mors->hif.u.yaps; + yaps->aux_data = kzalloc(aux_data_len, GFP_KERNEL); + if (!yaps->aux_data) { + ret = -ENOMEM; + goto err_exit; + } + + yaps->aux_data->to_chip_buffer = + kzalloc(YAPS_HW_WINDOW_SIZE_BYTES + alignment - 1, GFP_KERNEL); + if (!yaps->aux_data->to_chip_buffer) { + ret = -ENOMEM; + goto err_exit; + } + + yaps->aux_data->from_chip_buffer = + kzalloc(YAPS_HW_WINDOW_SIZE_BYTES + alignment - 1, GFP_KERNEL); + if (!yaps->aux_data->from_chip_buffer) { + ret = -ENOMEM; + goto err_exit; + } + + if (!IS_ALIGNED((uintptr_t)&yaps->aux_data->status_regs, alignment)) { + dev_warn(mors->dev, + "Status registers are not aligned to %d bytes", + alignment); + } + + yaps->ops = &mm81x_yaps_hw_ops; + return ret; + +err_exit: + mm81x_yaps_hw_finish(mors); + return ret; +} + +void mm81x_yaps_hw_finish(struct mm81x *mors) +{ + struct mm81x_yaps *yaps; + + yaps = &mors->hif.u.yaps; + if (yaps->aux_data) { + kfree(yaps->aux_data->from_chip_buffer); + yaps->aux_data->from_chip_buffer = NULL; + kfree(yaps->aux_data->to_chip_buffer); + yaps->aux_data->to_chip_buffer = NULL; + kfree(yaps->aux_data); + yaps->aux_data = NULL; + } +} diff --git a/drivers/net/wireless/morsemicro/mm81x/yaps_hw.h b/drivers/net/wireless/morsemicro/mm81x/yaps_hw.h new file mode 100644 index 000000000000..89e15375aabc --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/yaps_hw.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2026 Morse Micro + */ + +#ifndef _MM81X_YAPS_HW_H_ +#define _MM81X_YAPS_HW_H_ + +#include +#include + +#define MM81X_INT_YAPS_FC_PKT_WAITING_IRQN 0 +#define MM81X_INT_YAPS_FC_PACKET_FREED_UP_IRQN 1 + +struct mm81x_yaps_hw_table { + /* NOTE: We need these padding bytes for yaps to work */ + u8 padding[4]; + __le32 ysl_addr; + __le32 yds_addr; + __le32 status_regs_addr; + + /* Alloc pool sizes */ + __le16 tc_tx_pool_size; + __le16 fc_rx_pool_size; + u8 tc_cmd_pool_size; + u8 tc_beacon_pool_size; + u8 tc_mgmt_pool_size; + u8 fc_resp_pool_size; + u8 fc_tx_sts_pool_size; + u8 fc_aux_pool_size; + + /* To chip/from chip queue sizes */ + u8 tc_tx_q_size; + u8 tc_cmd_q_size; + u8 tc_beacon_q_size; + u8 tc_mgmt_q_size; + u8 fc_q_size; + u8 fc_done_q_size; + + __le16 yaps_reserved_page_size; + __le16 reserved_unused; +} __packed; + +struct mm81x; + +void mm81x_yaps_hw_enable_irqs(struct mm81x *mors, bool enable); +int mm81x_yaps_hw_init(struct mm81x *mors); +void mm81x_yaps_hw_finish(struct mm81x *mors); +void mm81x_yaps_hw_read_table(struct mm81x *mors, + struct mm81x_yaps_hw_table *tbl_ptr); + +#endif /* !_MM81X_YAPS_HW_H_ */ From a211b03fee2c87704b38db08b039bd2c597a2eea Mon Sep 17 00:00:00 2001 From: Minxi Hou Date: Thu, 2 Jul 2026 03:49:26 -0400 Subject: [PATCH 0224/1433] selftests/net/openvswitch: add output truncation test Add test_trunc exercising the OVS_ACTION_ATTR_TRUNC action. The test verifies truncation limits in four steps: reject trunc(1) and trunc(13) which are below ETH_HLEN, confirm normal forwarding works, apply trunc(14) which truncates packets to the Ethernet header and verify ping fails, then restore normal forwarding and verify recovery. The kernel requires max_len >= ETH_HLEN (14 bytes). trunc(14) sets OVS_CB(skb)->cutlen so pskb_trim strips the IP payload at output time; the receiver drops the runt frame and no echo reply is generated. Signed-off-by: Minxi Hou Reviewed-by: Aaron Conole Link: https://patch.msgid.link/20260702074926.1174810-1-houminxi@gmail.com Signed-off-by: Paolo Abeni --- .../selftests/net/openvswitch/openvswitch.sh | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/tools/testing/selftests/net/openvswitch/openvswitch.sh b/tools/testing/selftests/net/openvswitch/openvswitch.sh index 2954245129a2..f75ee723415a 100755 --- a/tools/testing/selftests/net/openvswitch/openvswitch.sh +++ b/tools/testing/selftests/net/openvswitch/openvswitch.sh @@ -32,6 +32,7 @@ tests=" dec_ttl ttl: dec_ttl decrements IP TTL flow_set flow-set: Flow modify action_set set: SET action rewrites fields + trunc trunc: output truncation psample psample: Sampling packets with psample" info() { @@ -443,6 +444,92 @@ test_action_set() { return 0 } +# trunc test +# - trunc(14): truncate to ETH_HLEN, strips IP payload, ping fails +# - trunc(1) and trunc(13): kernel rejects below ETH_HLEN (EINVAL) +# - restore normal forwarding and verify recovery +test_trunc() { + sbx_add "test_trunc" || return $? + ovs_add_dp "test_trunc" trunctest || return 1 + + info "create namespaces" + for ns in client server; do + ovs_add_netns_and_veths "test_trunc" "trunctest" \ + "$ns" "${ns:0:1}0" "${ns:0:1}1" || return 1 + done + + ip netns exec client ip addr add 10.0.0.1/24 dev c1 + ip netns exec client ip link set c1 up + ip netns exec server ip addr add 10.0.0.2/24 dev s1 + ip netns exec server ip link set s1 up + + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0800),ipv4()' \ + '2' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(2),eth(),eth_type(0x0800),ipv4()' \ + '1' || return 1 + + info "verify connectivity without truncation" + ovs_sbx "test_trunc" ip netns exec client \ + ping -c 1 -W 2 10.0.0.2 || return 1 + + # trunc below ETH_HLEN must be rejected by the kernel + info "verify trunc(1) is rejected" + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0800),ipv4()' \ + 'trunc(1),2' &> /dev/null \ + && { info "trunc(1) should be rejected"; return 1; } + + info "verify trunc(13) is rejected" + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0800),ipv4()' \ + 'trunc(13),2' &> /dev/null \ + && { info "trunc(13) should be rejected"; return 1; } + + ovs_del_flows "test_trunc" trunctest + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1 + + info "add trunc(14) forwarding flow" + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0800),ipv4()' \ + 'trunc(14),2' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(2),eth(),eth_type(0x0800),ipv4()' \ + '1' || return 1 + + info "verify ping fails with trunc(14)" + ovs_sbx "test_trunc" ip netns exec client \ + ping -c 1 -W 2 10.0.0.2 >/dev/null 2>&1 \ + && { info "ping should fail with trunc(14)" + return 1; } + + ovs_del_flows "test_trunc" trunctest + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0800),ipv4()' \ + '2' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(2),eth(),eth_type(0x0800),ipv4()' \ + '1' || return 1 + + info "verify connectivity restored" + ovs_sbx "test_trunc" ip netns exec client \ + ping -c 1 -W 2 10.0.0.2 || return 1 + + return 0 +} + # psample test # - use psample to observe packets test_psample() { From 0be5c3f0fbef3679f50f345b9237b8f9ea5de4e9 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Wed, 1 Jul 2026 20:39:25 +0800 Subject: [PATCH 0225/1433] gtp: annotate PDP lookups under RTNL The GTP PDP lookup helpers are shared by RCU-protected data and report paths and RTNL-protected control paths such as gtp_genl_new_pdp(). The helpers walk RCU hlists, but they do not currently pass the RTNL condition for the control-path lookups. Pass lockdep_rtnl_is_held() to the PDP hlist iterators. Existing RCU-reader callers remain valid because the RCU-list macros also accept an active RCU read-side section; the added condition only documents the non-RCU protection already used by RTNL control paths. This was found by our static analysis tool and then manually reviewed against the current tree. The dynamic triage evidence is a target-matched CONFIG_PROVE_RCU_LIST warning; the change is limited to documenting the existing protection contract. This is a lockdep annotation cleanup. It does not change PDP lifetime or hash updates. Signed-off-by: Runyu Xiao Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260701123925.3193089-1-runyu.xiao@seu.edu.cn Signed-off-by: Paolo Abeni --- drivers/net/gtp.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c index a60ef32b35b8..4ad9528322c4 100644 --- a/drivers/net/gtp.c +++ b/drivers/net/gtp.c @@ -151,7 +151,8 @@ static struct pdp_ctx *gtp0_pdp_find(struct gtp_dev *gtp, u64 tid, u16 family) head = >p->tid_hash[gtp0_hashfn(tid) % gtp->hash_size]; - hlist_for_each_entry_rcu(pdp, head, hlist_tid) { + hlist_for_each_entry_rcu(pdp, head, hlist_tid, + lockdep_rtnl_is_held()) { if (pdp->af == family && pdp->gtp_version == GTP_V0 && pdp->u.v0.tid == tid) @@ -168,7 +169,8 @@ static struct pdp_ctx *gtp1_pdp_find(struct gtp_dev *gtp, u32 tid, u16 family) head = >p->tid_hash[gtp1u_hashfn(tid) % gtp->hash_size]; - hlist_for_each_entry_rcu(pdp, head, hlist_tid) { + hlist_for_each_entry_rcu(pdp, head, hlist_tid, + lockdep_rtnl_is_held()) { if (pdp->af == family && pdp->gtp_version == GTP_V1 && pdp->u.v1.i_tei == tid) @@ -185,7 +187,8 @@ static struct pdp_ctx *ipv4_pdp_find(struct gtp_dev *gtp, __be32 ms_addr) head = >p->addr_hash[ipv4_hashfn(ms_addr) % gtp->hash_size]; - hlist_for_each_entry_rcu(pdp, head, hlist_addr) { + hlist_for_each_entry_rcu(pdp, head, hlist_addr, + lockdep_rtnl_is_held()) { if (pdp->af == AF_INET && pdp->ms.addr.s_addr == ms_addr) return pdp; @@ -220,7 +223,8 @@ static struct pdp_ctx *ipv6_pdp_find(struct gtp_dev *gtp, head = >p->addr_hash[ipv6_hashfn(ms_addr) % gtp->hash_size]; - hlist_for_each_entry_rcu(pdp, head, hlist_addr) { + hlist_for_each_entry_rcu(pdp, head, hlist_addr, + lockdep_rtnl_is_held()) { if (pdp->af == AF_INET6 && ipv6_pdp_addr_equal(&pdp->ms.addr6, ms_addr)) return pdp; From 4fa0619d039f561dd2207055cf21a10abaeebe0e Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Wed, 1 Jul 2026 20:40:17 +0800 Subject: [PATCH 0226/1433] net: rmnet: annotate endpoint lookup under RTNL rmnet_get_endpoint() is shared by packet receive paths and RTNL-protected control paths. The receive paths already run under RCU/BH context through the RX handler, while the control paths reach rmnet_get_endpoint() after obtaining the rmnet port with rmnet_get_port_rtnl(). The helper walks port->muxed_ep[] with hlist_for_each_entry_rcu(). Pass lockdep_rtnl_is_held() as the non-RCU protection condition so CONFIG_PROVE_RCU_LIST can see the RTNL-protected control-path calls while preserving the existing RCU-reader behavior for data paths. This was found by our static analysis tool and then manually reviewed against the current tree. The dynamic triage evidence is a target-matched CONFIG_PROVE_RCU_LIST warning; the change is limited to documenting the existing protection contract. This is a lockdep annotation cleanup. It does not change endpoint lifetime or hash updates. Signed-off-by: Runyu Xiao Reviewed-by: Subash Abhinov Kasiviswanathan Link: https://patch.msgid.link/20260701124017.3205729-1-runyu.xiao@seu.edu.cn Signed-off-by: Paolo Abeni --- drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c index 78d4df55740a..bed6f63facf2 100644 --- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c +++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c @@ -423,7 +423,8 @@ struct rmnet_endpoint *rmnet_get_endpoint(struct rmnet_port *port, u8 mux_id) { struct rmnet_endpoint *ep; - hlist_for_each_entry_rcu(ep, &port->muxed_ep[mux_id], hlnode) { + hlist_for_each_entry_rcu(ep, &port->muxed_ep[mux_id], hlnode, + lockdep_rtnl_is_held()) { if (ep->mux_id == mux_id) return ep; } From 28a236c54c9ae648fda9cc961e2343c70b8b3b49 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 1 Jul 2026 16:57:18 +0300 Subject: [PATCH 0227/1433] bnx2x: use kzalloc() to allocate mac filtering list bnx2x_mcast_enqueue_cmd() allocates memory for mac filtering list using __get_free_pages(). This memory can be allocated with kzalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of __get_free_page() with kzalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260701-b4-drivers-ethernet-v1-1-58776615db6e@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/broadcom/bnx2x/bnx2x_sp.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_sp.c b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_sp.c index 07a908a2c72f..d560524d317d 100644 --- a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_sp.c +++ b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_sp.c @@ -26,6 +26,7 @@ #include #include #include +#include #include "bnx2x.h" #include "bnx2x_cmn.h" #include "bnx2x_sp.h" @@ -2664,7 +2665,7 @@ static void bnx2x_free_groups(struct list_head *mcast_group_list) struct bnx2x_mcast_elem_group, mcast_group_link); list_del(¤t_mcast_group->mcast_group_link); - free_page((unsigned long)current_mcast_group); + kfree(current_mcast_group); } } @@ -2713,8 +2714,7 @@ static int bnx2x_mcast_enqueue_cmd(struct bnx2x *bp, total_elems = BNX2X_MCAST_BINS_NUM; } while (total_elems > 0) { - elem_group = (struct bnx2x_mcast_elem_group *) - __get_free_page(GFP_ATOMIC | __GFP_ZERO); + elem_group = kzalloc(PAGE_SIZE, GFP_ATOMIC); if (!elem_group) { bnx2x_free_groups(&new_cmd->group_head); kfree(new_cmd); From 764f2a0c6d1e5bc8f7c2438e0dd6572a322f8762 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 1 Jul 2026 16:57:19 +0300 Subject: [PATCH 0228/1433] ice: use kzalloc() to allocate staging buffer for reading from GNSS ice_gnss_read() uses get_zeroed_page() to allocate a staging buffer for reading GNSS module data via I2C bus. This buffer can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of get_zeroed_page() with kzalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Przemek Kitszel Reviewed-by: Aleksandr Loktionov Link: https://patch.msgid.link/20260701-b4-drivers-ethernet-v1-2-58776615db6e@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/intel/ice/ice_gnss.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_gnss.c b/drivers/net/ethernet/intel/ice/ice_gnss.c index 8fd954f1ebd6..7d21c3417b0b 100644 --- a/drivers/net/ethernet/intel/ice/ice_gnss.c +++ b/drivers/net/ethernet/intel/ice/ice_gnss.c @@ -2,6 +2,7 @@ /* Copyright (C) 2021-2022, Intel Corporation. */ #include "ice.h" +#include #include "ice_lib.h" /** @@ -124,7 +125,7 @@ static void ice_gnss_read(struct kthread_work *work) data_len = min_t(typeof(data_len), data_len, PAGE_SIZE); - buf = (char *)get_zeroed_page(GFP_KERNEL); + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) { err = -ENOMEM; goto requeue; @@ -151,7 +152,7 @@ static void ice_gnss_read(struct kthread_work *work) count, i); delay = ICE_GNSS_TIMER_DELAY_TIME; free_buf: - free_page((unsigned long)buf); + kfree(buf); requeue: kthread_queue_delayed_work(gnss->kworker, &gnss->read_work, delay); if (err) From 50bfc5eac4cb9bfe5b4bedb5c55d21fe8bd1235c Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 1 Jul 2026 16:57:20 +0300 Subject: [PATCH 0229/1433] sfc/siena: use kmalloc() to allocate logging buffer efx_siena_mcdi_init() allocates a logging buffer for MCDI firmware communication diagnostics. This buffer can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of __get_free_page() with kmalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Edward Cree Link: https://patch.msgid.link/20260701-b4-drivers-ethernet-v1-3-58776615db6e@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/sfc/siena/mcdi.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/sfc/siena/mcdi.c b/drivers/net/ethernet/sfc/siena/mcdi.c index 4d0d6bd5d3d1..048c1e6017c0 100644 --- a/drivers/net/ethernet/sfc/siena/mcdi.c +++ b/drivers/net/ethernet/sfc/siena/mcdi.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "net_driver.h" #include "nic.h" #include "io.h" @@ -73,7 +74,7 @@ int efx_siena_mcdi_init(struct efx_nic *efx) mcdi->efx = efx; #ifdef CONFIG_SFC_SIENA_MCDI_LOGGING /* consuming code assumes buffer is page-sized */ - mcdi->logging_buffer = (char *)__get_free_page(GFP_KERNEL); + mcdi->logging_buffer = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!mcdi->logging_buffer) goto fail1; mcdi->logging_enabled = efx_siena_mcdi_logging_default; @@ -116,7 +117,7 @@ int efx_siena_mcdi_init(struct efx_nic *efx) return 0; fail2: #ifdef CONFIG_SFC_SIENA_MCDI_LOGGING - free_page((unsigned long)mcdi->logging_buffer); + kfree(mcdi->logging_buffer); fail1: #endif kfree(efx->mcdi); @@ -142,7 +143,7 @@ void efx_siena_mcdi_fini(struct efx_nic *efx) return; #ifdef CONFIG_SFC_SIENA_MCDI_LOGGING - free_page((unsigned long)efx->mcdi->iface.logging_buffer); + kfree(efx->mcdi->iface.logging_buffer); #endif kfree(efx->mcdi); From e8cfc70720ea9c48150235321a4e8831e4735e8f Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 1 Jul 2026 16:57:21 +0300 Subject: [PATCH 0230/1433] sfc: use kmalloc() to allocate logging buffer efx_mcdi_init() allocates a logging buffer for MCDI firmware communication diagnostics. This buffer can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of __get_free_page() with kmalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Edward Cree Link: https://patch.msgid.link/20260701-b4-drivers-ethernet-v1-4-58776615db6e@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/sfc/mcdi.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/sfc/mcdi.c b/drivers/net/ethernet/sfc/mcdi.c index e65db9b70724..b806d3d90c42 100644 --- a/drivers/net/ethernet/sfc/mcdi.c +++ b/drivers/net/ethernet/sfc/mcdi.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "net_driver.h" #include "nic.h" #include "io.h" @@ -71,7 +72,7 @@ int efx_mcdi_init(struct efx_nic *efx) mcdi->efx = efx; #ifdef CONFIG_SFC_MCDI_LOGGING /* consuming code assumes buffer is page-sized */ - mcdi->logging_buffer = (char *)__get_free_page(GFP_KERNEL); + mcdi->logging_buffer = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!mcdi->logging_buffer) goto fail1; mcdi->logging_enabled = mcdi_logging_default; @@ -112,7 +113,7 @@ int efx_mcdi_init(struct efx_nic *efx) return 0; fail2: #ifdef CONFIG_SFC_MCDI_LOGGING - free_page((unsigned long)mcdi->logging_buffer); + kfree(mcdi->logging_buffer); fail1: #endif kfree(efx->mcdi); @@ -138,7 +139,7 @@ void efx_mcdi_fini(struct efx_nic *efx) return; #ifdef CONFIG_SFC_MCDI_LOGGING - free_page((unsigned long)efx->mcdi->iface.logging_buffer); + kfree(efx->mcdi->iface.logging_buffer); #endif kfree(efx->mcdi); From 15604320877e09d84dd5f1d79ce138bf2263f2a1 Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Thu, 2 Jul 2026 11:07:23 +0200 Subject: [PATCH 0231/1433] net: dsa: microchip: split ksz8_change_mtu() Even among the ksz8 family, there are big differences in the MTU change procedure between KSZ87xx and KSZ88xx (KSZ8463 is like KSZ88xx here). Since we have 3 separate dsa_switch_ops for what constitutes "KSZ8", we can split those procedures into separate functions. Signed-off-by: Vladimir Oltean Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260702-clean-ksz-4th-v1-1-93441e695fa4@bootlin.com Signed-off-by: Paolo Abeni --- drivers/net/dsa/microchip/ksz8.c | 49 +++++++++++++------------------- 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index 586916570a84..c351179f6a60 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -158,10 +158,17 @@ static int ksz8_reset_switch(struct ksz_device *dev) return 0; } -static int ksz8863_change_mtu(struct ksz_device *dev, int frame_size) +static int ksz88xx_change_mtu(struct dsa_switch *ds, int port, int mtu) { + struct ksz_device *dev = ds->priv; + int frame_size; u8 ctrl2 = 0; + if (!dsa_is_cpu_port(dev->ds, port)) + return 0; + + frame_size = mtu + VLAN_ETH_HLEN + ETH_FCS_LEN; + if (frame_size <= KSZ8_LEGAL_PACKET_SIZE) ctrl2 |= KSZ8863_LEGAL_PACKET_ENABLE; else if (frame_size > KSZ8863_NORMAL_PACKET_SIZE) @@ -171,11 +178,18 @@ static int ksz8863_change_mtu(struct ksz_device *dev, int frame_size) KSZ8863_HUGE_PACKET_ENABLE, ctrl2); } -static int ksz8795_change_mtu(struct ksz_device *dev, int frame_size) +static int ksz87xx_change_mtu(struct dsa_switch *ds, int port, int mtu) { + struct ksz_device *dev = ds->priv; u8 ctrl1 = 0, ctrl2 = 0; + u16 frame_size; int ret; + if (!dsa_is_cpu_port(dev->ds, port)) + return 0; + + frame_size = mtu + VLAN_ETH_HLEN + ETH_FCS_LEN; + if (frame_size > KSZ8_LEGAL_PACKET_SIZE) ctrl2 |= SW_LEGAL_PACKET_DISABLE; if (frame_size > KSZ8863_NORMAL_PACKET_SIZE) @@ -188,31 +202,6 @@ static int ksz8795_change_mtu(struct ksz_device *dev, int frame_size) return ksz_rmw8(dev, REG_SW_CTRL_2, SW_LEGAL_PACKET_DISABLE, ctrl2); } -static int ksz8_change_mtu(struct dsa_switch *ds, int port, int mtu) -{ - struct ksz_device *dev = ds->priv; - u16 frame_size; - - if (!dsa_is_cpu_port(dev->ds, port)) - return 0; - - frame_size = mtu + VLAN_ETH_HLEN + ETH_FCS_LEN; - - switch (dev->chip_id) { - case KSZ8795_CHIP_ID: - case KSZ8794_CHIP_ID: - case KSZ8765_CHIP_ID: - return ksz8795_change_mtu(dev, frame_size); - case KSZ8463_CHIP_ID: - case KSZ88X3_CHIP_ID: - case KSZ8864_CHIP_ID: - case KSZ8895_CHIP_ID: - return ksz8863_change_mtu(dev, frame_size); - } - - return -EOPNOTSUPP; -} - static int ksz8_port_queue_split(struct ksz_device *dev, int port, int queues) { u8 mask_4q, mask_2q; @@ -2551,7 +2540,7 @@ const struct dsa_switch_ops ksz8463_switch_ops = { .port_mirror_del = ksz8_port_mirror_del, .get_stats64 = ksz_get_stats64, .get_pause_stats = ksz_get_pause_stats, - .port_change_mtu = ksz8_change_mtu, + .port_change_mtu = ksz88xx_change_mtu, .port_max_mtu = ksz_max_mtu, .suspend = ksz_suspend, .resume = ksz_resume, @@ -2601,7 +2590,7 @@ const struct dsa_switch_ops ksz87xx_switch_ops = { .port_mirror_del = ksz8_port_mirror_del, .get_stats64 = ksz_get_stats64, .get_pause_stats = ksz_get_pause_stats, - .port_change_mtu = ksz8_change_mtu, + .port_change_mtu = ksz87xx_change_mtu, .port_max_mtu = ksz_max_mtu, .suspend = ksz_suspend, .resume = ksz_resume, @@ -2652,7 +2641,7 @@ const struct dsa_switch_ops ksz88xx_switch_ops = { .port_mirror_del = ksz8_port_mirror_del, .get_stats64 = ksz_get_stats64, .get_pause_stats = ksz_get_pause_stats, - .port_change_mtu = ksz8_change_mtu, + .port_change_mtu = ksz88xx_change_mtu, .port_max_mtu = ksz_max_mtu, .get_wol = ksz_get_wol, .set_wol = ksz_set_wol, From 18a856b236800efb3cfc321e51580e00c4a6e497 Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Thu, 2 Jul 2026 11:07:24 +0200 Subject: [PATCH 0232/1433] net: dsa: microchip: split port_max_mtu() implementation ksz_max_mtu() is a bit cluttered. It would be good for developers and reviewers if they didn't need to look at a common function for hardware they likely don't have, and which is vastly different, when they are interested in only a specific chip. Benefit from the fact that all families listed here have their own dsa_switch_ops, and provide separate implementations for the port_max_mtu() method. Signed-off-by: Vladimir Oltean Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260702-clean-ksz-4th-v1-2-93441e695fa4@bootlin.com Signed-off-by: Paolo Abeni --- drivers/net/dsa/microchip/ksz8.c | 16 ++++++++--- drivers/net/dsa/microchip/ksz9477.c | 7 ++++- drivers/net/dsa/microchip/ksz9477.h | 1 + drivers/net/dsa/microchip/ksz_common.c | 34 ------------------------ drivers/net/dsa/microchip/ksz_common.h | 2 -- drivers/net/dsa/microchip/lan937x_main.c | 2 +- 6 files changed, 21 insertions(+), 41 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index c351179f6a60..2b4720c4c87e 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -202,6 +202,16 @@ static int ksz87xx_change_mtu(struct dsa_switch *ds, int port, int mtu) return ksz_rmw8(dev, REG_SW_CTRL_2, SW_LEGAL_PACKET_DISABLE, ctrl2); } +static int ksz87xx_max_mtu(struct dsa_switch *ds, int port) +{ + return KSZ8795_HUGE_PACKET_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; +} + +static int ksz88xx_max_mtu(struct dsa_switch *ds, int port) +{ + return KSZ8863_HUGE_PACKET_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; +} + static int ksz8_port_queue_split(struct ksz_device *dev, int port, int queues) { u8 mask_4q, mask_2q; @@ -2541,7 +2551,7 @@ const struct dsa_switch_ops ksz8463_switch_ops = { .get_stats64 = ksz_get_stats64, .get_pause_stats = ksz_get_pause_stats, .port_change_mtu = ksz88xx_change_mtu, - .port_max_mtu = ksz_max_mtu, + .port_max_mtu = ksz88xx_max_mtu, .suspend = ksz_suspend, .resume = ksz_resume, .get_ts_info = ksz_get_ts_info, @@ -2591,7 +2601,7 @@ const struct dsa_switch_ops ksz87xx_switch_ops = { .get_stats64 = ksz_get_stats64, .get_pause_stats = ksz_get_pause_stats, .port_change_mtu = ksz87xx_change_mtu, - .port_max_mtu = ksz_max_mtu, + .port_max_mtu = ksz87xx_max_mtu, .suspend = ksz_suspend, .resume = ksz_resume, .get_ts_info = ksz_get_ts_info, @@ -2642,7 +2652,7 @@ const struct dsa_switch_ops ksz88xx_switch_ops = { .get_stats64 = ksz_get_stats64, .get_pause_stats = ksz_get_pause_stats, .port_change_mtu = ksz88xx_change_mtu, - .port_max_mtu = ksz_max_mtu, + .port_max_mtu = ksz88xx_max_mtu, .get_wol = ksz_get_wol, .set_wol = ksz_set_wol, .suspend = ksz_suspend, diff --git a/drivers/net/dsa/microchip/ksz9477.c b/drivers/net/dsa/microchip/ksz9477.c index f3f0c98dfb5a..0b08d2175f17 100644 --- a/drivers/net/dsa/microchip/ksz9477.c +++ b/drivers/net/dsa/microchip/ksz9477.c @@ -60,6 +60,11 @@ static int ksz9477_change_mtu(struct dsa_switch *ds, int port, int mtu) REG_SW_MTU_MASK, frame_size); } +int ksz9477_max_mtu(struct dsa_switch *ds, int port) +{ + return KSZ9477_MAX_FRAME_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; +} + static int ksz9477_wait_vlan_ctrl_ready(struct ksz_device *dev) { unsigned int val; @@ -2098,7 +2103,7 @@ const struct dsa_switch_ops ksz9477_switch_ops = { .get_stats64 = ksz_get_stats64, .get_pause_stats = ksz_get_pause_stats, .port_change_mtu = ksz9477_change_mtu, - .port_max_mtu = ksz_max_mtu, + .port_max_mtu = ksz9477_max_mtu, .get_wol = ksz_get_wol, .set_wol = ksz_set_wol, .suspend = ksz_suspend, diff --git a/drivers/net/dsa/microchip/ksz9477.h b/drivers/net/dsa/microchip/ksz9477.h index 92a1d889224d..25b74d0af6c5 100644 --- a/drivers/net/dsa/microchip/ksz9477.h +++ b/drivers/net/dsa/microchip/ksz9477.h @@ -21,6 +21,7 @@ void ksz9477_freeze_mib(struct ksz_device *dev, int port, bool freeze); void ksz9477_port_init_cnt(struct ksz_device *dev, int port); int ksz9477_port_vlan_filtering(struct dsa_switch *ds, int port, bool flag, struct netlink_ext_ack *extack); +int ksz9477_max_mtu(struct dsa_switch *ds, int port); int ksz9477_port_vlan_add(struct dsa_switch *ds, int port, const struct switchdev_obj_port_vlan *vlan, struct netlink_ext_ack *extack); diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index d1726778bb48..686bdbfe8b6a 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -2984,40 +2984,6 @@ int ksz_port_bridge_flags(struct dsa_switch *ds, int port, return 0; } -int ksz_max_mtu(struct dsa_switch *ds, int port) -{ - struct ksz_device *dev = ds->priv; - - switch (dev->chip_id) { - case KSZ8795_CHIP_ID: - case KSZ8794_CHIP_ID: - case KSZ8765_CHIP_ID: - return KSZ8795_HUGE_PACKET_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; - case KSZ8463_CHIP_ID: - case KSZ88X3_CHIP_ID: - case KSZ8864_CHIP_ID: - case KSZ8895_CHIP_ID: - return KSZ8863_HUGE_PACKET_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; - case KSZ8563_CHIP_ID: - case KSZ8567_CHIP_ID: - case KSZ9477_CHIP_ID: - case KSZ9563_CHIP_ID: - case KSZ9567_CHIP_ID: - case KSZ9893_CHIP_ID: - case KSZ9896_CHIP_ID: - case KSZ9897_CHIP_ID: - case LAN9370_CHIP_ID: - case LAN9371_CHIP_ID: - case LAN9372_CHIP_ID: - case LAN9373_CHIP_ID: - case LAN9374_CHIP_ID: - case LAN9646_CHIP_ID: - return KSZ9477_MAX_FRAME_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; - } - - return -EOPNOTSUPP; -} - int ksz_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_keee *e) { diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index b4a5673ba365..f276f2452684 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -443,8 +443,6 @@ void ksz_phylink_mac_link_down(struct phylink_config *config, unsigned int mode, phy_interface_t interface); -int ksz_max_mtu(struct dsa_switch *ds, int port); - int ksz_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_keee *e); diff --git a/drivers/net/dsa/microchip/lan937x_main.c b/drivers/net/dsa/microchip/lan937x_main.c index 8eb5337b0c10..f060fbc4c4f4 100644 --- a/drivers/net/dsa/microchip/lan937x_main.c +++ b/drivers/net/dsa/microchip/lan937x_main.c @@ -983,7 +983,7 @@ const struct dsa_switch_ops lan937x_switch_ops = { .get_stats64 = ksz_get_stats64, .get_pause_stats = ksz_get_pause_stats, .port_change_mtu = lan937x_change_mtu, - .port_max_mtu = ksz_max_mtu, + .port_max_mtu = ksz9477_max_mtu, .suspend = ksz_suspend, .resume = ksz_resume, .get_ts_info = ksz_get_ts_info, From caf5d68b51e61bfdd8aaa8d5aea6f5f24d76fca5 Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Thu, 2 Jul 2026 11:07:25 +0200 Subject: [PATCH 0233/1433] net: dsa: microchip: make ksz_is_port_mac_global_usable() static ksz_is_port_mac_global_usable() is exposed in ksz_common.h while it's only used internally. Make ksz_is_port_mac_global_usable() static. Move its definition above its first call. Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260702-clean-ksz-4th-v1-3-93441e695fa4@bootlin.com Signed-off-by: Paolo Abeni --- drivers/net/dsa/microchip/ksz_common.c | 56 +++++++++++++------------- drivers/net/dsa/microchip/ksz_common.h | 1 - 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index 686bdbfe8b6a..be7738ae1f2a 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -3670,6 +3670,34 @@ int ksz_handle_wake_reason(struct ksz_device *dev, int port) pme_status); } +/** + * ksz_is_port_mac_global_usable - Check if the MAC address on a given port + * can be used as a global address. + * @ds: Pointer to the DSA switch structure. + * @port: The port number on which the MAC address is to be checked. + * + * This function examines the MAC address set on the specified port and + * determines if it can be used as a global address for the switch. + * + * Return: true if the port's MAC address can be used as a global address, false + * otherwise. + */ +static bool ksz_is_port_mac_global_usable(struct dsa_switch *ds, int port) +{ + struct net_device *user = dsa_to_port(ds, port)->user; + const unsigned char *addr = user->dev_addr; + struct ksz_switch_macaddr *switch_macaddr; + struct ksz_device *dev = ds->priv; + + ASSERT_RTNL(); + + switch_macaddr = dev->switch_macaddr; + if (switch_macaddr && !ether_addr_equal(switch_macaddr->addr, addr)) + return false; + + return true; +} + /** * ksz_get_wol - Get Wake-on-LAN settings for a specified port. * @ds: The dsa_switch structure. @@ -3863,34 +3891,6 @@ int ksz_port_set_mac_address(struct dsa_switch *ds, int port, return 0; } -/** - * ksz_is_port_mac_global_usable - Check if the MAC address on a given port - * can be used as a global address. - * @ds: Pointer to the DSA switch structure. - * @port: The port number on which the MAC address is to be checked. - * - * This function examines the MAC address set on the specified port and - * determines if it can be used as a global address for the switch. - * - * Return: true if the port's MAC address can be used as a global address, false - * otherwise. - */ -bool ksz_is_port_mac_global_usable(struct dsa_switch *ds, int port) -{ - struct net_device *user = dsa_to_port(ds, port)->user; - const unsigned char *addr = user->dev_addr; - struct ksz_switch_macaddr *switch_macaddr; - struct ksz_device *dev = ds->priv; - - ASSERT_RTNL(); - - switch_macaddr = dev->switch_macaddr; - if (switch_macaddr && !ether_addr_equal(switch_macaddr->addr, addr)) - return false; - - return true; -} - /** * ksz_switch_macaddr_get - Program the switch's MAC address register. * @ds: DSA switch instance. diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index f276f2452684..f367d6f96fa2 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -393,7 +393,6 @@ int ksz_switch_resume(struct device *dev); void ksz_teardown(struct dsa_switch *ds); void ksz_init_mib_timer(struct ksz_device *dev); -bool ksz_is_port_mac_global_usable(struct dsa_switch *ds, int port); void ksz_r_mib_stats64(struct ksz_device *dev, int port); void ksz88xx_r_mib_stats64(struct ksz_device *dev, int port); void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state); From a7e806f11f37cc4d6a85cdbba568ed8d911be86f Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Thu, 2 Jul 2026 11:07:26 +0200 Subject: [PATCH 0234/1433] net: dsa: microchip: move ksz88xx stats handling in ksz8.c ksz88xx_r_mib_stats64() is defined in ksz_common while it's clearly ksz88xx-specific. Move its definition in ksz8.c Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260702-clean-ksz-4th-v1-4-93441e695fa4@bootlin.com Signed-off-by: Paolo Abeni --- drivers/net/dsa/microchip/ksz8.c | 86 ++++++++++++++++++++++++++ drivers/net/dsa/microchip/ksz_common.c | 86 -------------------------- drivers/net/dsa/microchip/ksz_common.h | 1 - 3 files changed, 86 insertions(+), 87 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index 2b4720c4c87e..ad7c7a6e1d31 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -36,6 +36,43 @@ #include "ksz8_reg.h" #include "ksz8.h" +struct ksz88xx_stats_raw { + u64 rx; + u64 rx_hi; + u64 rx_undersize; + u64 rx_fragments; + u64 rx_oversize; + u64 rx_jabbers; + u64 rx_symbol_err; + u64 rx_crc_err; + u64 rx_align_err; + u64 rx_mac_ctrl; + u64 rx_pause; + u64 rx_bcast; + u64 rx_mcast; + u64 rx_ucast; + u64 rx_64_or_less; + u64 rx_65_127; + u64 rx_128_255; + u64 rx_256_511; + u64 rx_512_1023; + u64 rx_1024_1522; + u64 tx; + u64 tx_hi; + u64 tx_late_col; + u64 tx_pause; + u64 tx_bcast; + u64 tx_mcast; + u64 tx_ucast; + u64 tx_deferred; + u64 tx_total_col; + u64 tx_exc_col; + u64 tx_single_col; + u64 tx_mult_col; + u64 rx_discards; + u64 tx_discards; +}; + static void ksz_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool set) { ksz_rmw8(dev, addr, bits, set ? bits : 0); @@ -2052,6 +2089,55 @@ static int ksz8_enable_stp_addr(struct ksz_device *dev) return ksz8_w_sta_mac_table(dev, 0, &alu); } +static void ksz88xx_r_mib_stats64(struct ksz_device *dev, int port) +{ + struct ethtool_pause_stats *pstats; + struct rtnl_link_stats64 *stats; + struct ksz88xx_stats_raw *raw; + struct ksz_port_mib *mib; + + mib = &dev->ports[port].mib; + stats = &mib->stats64; + pstats = &mib->pause_stats; + raw = (struct ksz88xx_stats_raw *)mib->counters; + + spin_lock(&mib->stats64_lock); + + stats->rx_packets = raw->rx_bcast + raw->rx_mcast + raw->rx_ucast + + raw->rx_pause; + stats->tx_packets = raw->tx_bcast + raw->tx_mcast + raw->tx_ucast + + raw->tx_pause; + + /* HW counters are counting bytes + FCS which is not acceptable + * for rtnl_link_stats64 interface + */ + stats->rx_bytes = raw->rx + raw->rx_hi - stats->rx_packets * ETH_FCS_LEN; + stats->tx_bytes = raw->tx + raw->tx_hi - stats->tx_packets * ETH_FCS_LEN; + + stats->rx_length_errors = raw->rx_undersize + raw->rx_fragments + + raw->rx_oversize; + + stats->rx_crc_errors = raw->rx_crc_err; + stats->rx_frame_errors = raw->rx_align_err; + stats->rx_dropped = raw->rx_discards; + stats->rx_errors = stats->rx_length_errors + stats->rx_crc_errors + + stats->rx_frame_errors + stats->rx_dropped; + + stats->tx_window_errors = raw->tx_late_col; + stats->tx_fifo_errors = raw->tx_discards; + stats->tx_aborted_errors = raw->tx_exc_col; + stats->tx_errors = stats->tx_window_errors + stats->tx_fifo_errors + + stats->tx_aborted_errors; + + stats->multicast = raw->rx_mcast; + stats->collisions = raw->tx_total_col; + + pstats->tx_pause_frames = raw->tx_pause; + pstats->rx_pause_frames = raw->rx_pause; + + spin_unlock(&mib->stats64_lock); +} + static int ksz8_setup(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index be7738ae1f2a..56dd4c27b182 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -76,43 +76,6 @@ struct ksz_stats_raw { u64 tx_discards; }; -struct ksz88xx_stats_raw { - u64 rx; - u64 rx_hi; - u64 rx_undersize; - u64 rx_fragments; - u64 rx_oversize; - u64 rx_jabbers; - u64 rx_symbol_err; - u64 rx_crc_err; - u64 rx_align_err; - u64 rx_mac_ctrl; - u64 rx_pause; - u64 rx_bcast; - u64 rx_mcast; - u64 rx_ucast; - u64 rx_64_or_less; - u64 rx_65_127; - u64 rx_128_255; - u64 rx_256_511; - u64 rx_512_1023; - u64 rx_1024_1522; - u64 tx; - u64 tx_hi; - u64 tx_late_col; - u64 tx_pause; - u64 tx_bcast; - u64 tx_mcast; - u64 tx_ucast; - u64 tx_deferred; - u64 tx_total_col; - u64 tx_exc_col; - u64 tx_single_col; - u64 tx_mult_col; - u64 rx_discards; - u64 tx_discards; -}; - static const struct ksz_mib_names ksz88xx_mib_names[] = { { 0x00, "rx" }, { 0x01, "rx_hi" }, @@ -2063,55 +2026,6 @@ void ksz_r_mib_stats64(struct ksz_device *dev, int port) } } -void ksz88xx_r_mib_stats64(struct ksz_device *dev, int port) -{ - struct ethtool_pause_stats *pstats; - struct rtnl_link_stats64 *stats; - struct ksz88xx_stats_raw *raw; - struct ksz_port_mib *mib; - - mib = &dev->ports[port].mib; - stats = &mib->stats64; - pstats = &mib->pause_stats; - raw = (struct ksz88xx_stats_raw *)mib->counters; - - spin_lock(&mib->stats64_lock); - - stats->rx_packets = raw->rx_bcast + raw->rx_mcast + raw->rx_ucast + - raw->rx_pause; - stats->tx_packets = raw->tx_bcast + raw->tx_mcast + raw->tx_ucast + - raw->tx_pause; - - /* HW counters are counting bytes + FCS which is not acceptable - * for rtnl_link_stats64 interface - */ - stats->rx_bytes = raw->rx + raw->rx_hi - stats->rx_packets * ETH_FCS_LEN; - stats->tx_bytes = raw->tx + raw->tx_hi - stats->tx_packets * ETH_FCS_LEN; - - stats->rx_length_errors = raw->rx_undersize + raw->rx_fragments + - raw->rx_oversize; - - stats->rx_crc_errors = raw->rx_crc_err; - stats->rx_frame_errors = raw->rx_align_err; - stats->rx_dropped = raw->rx_discards; - stats->rx_errors = stats->rx_length_errors + stats->rx_crc_errors + - stats->rx_frame_errors + stats->rx_dropped; - - stats->tx_window_errors = raw->tx_late_col; - stats->tx_fifo_errors = raw->tx_discards; - stats->tx_aborted_errors = raw->tx_exc_col; - stats->tx_errors = stats->tx_window_errors + stats->tx_fifo_errors + - stats->tx_aborted_errors; - - stats->multicast = raw->rx_mcast; - stats->collisions = raw->tx_total_col; - - pstats->tx_pause_frames = raw->tx_pause; - pstats->rx_pause_frames = raw->rx_pause; - - spin_unlock(&mib->stats64_lock); -} - void ksz_get_stats64(struct dsa_switch *ds, int port, struct rtnl_link_stats64 *s) { diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index f367d6f96fa2..347787ef1f00 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -394,7 +394,6 @@ void ksz_teardown(struct dsa_switch *ds); void ksz_init_mib_timer(struct ksz_device *dev); void ksz_r_mib_stats64(struct ksz_device *dev, int port); -void ksz88xx_r_mib_stats64(struct ksz_device *dev, int port); void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state); bool ksz_get_gbit(struct ksz_device *dev, int port); phy_interface_t ksz_get_xmii(struct ksz_device *dev, int port, bool gbit); From 7a83196e901258764aa53b52ee4d90b4b1d905be Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Thu, 2 Jul 2026 11:07:27 +0200 Subject: [PATCH 0235/1433] net: dsa: microchip: move ksz_get_gbit() and ksz_get_xmii() to ksz9477.c ksz_get_gbit() and ksz_get_xmii() are defined in the ksz_common while they are only used by the ksz9477 driver. Move their definition into ksz9477.c Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260702-clean-ksz-4th-v1-5-93441e695fa4@bootlin.com Signed-off-by: Paolo Abeni --- drivers/net/dsa/microchip/ksz9477.c | 56 +++++++++++++++++++++++++- drivers/net/dsa/microchip/ksz_common.c | 51 ----------------------- drivers/net/dsa/microchip/ksz_common.h | 2 - 3 files changed, 54 insertions(+), 55 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz9477.c b/drivers/net/dsa/microchip/ksz9477.c index 0b08d2175f17..ad6748905e08 100644 --- a/drivers/net/dsa/microchip/ksz9477.c +++ b/drivers/net/dsa/microchip/ksz9477.c @@ -1181,6 +1181,58 @@ void ksz9477_port_mirror_del(struct dsa_switch *ds, int port, PORT_MIRROR_SNIFFER, false); } +static bool ksz9477_get_gbit(struct ksz_device *dev, int port) +{ + const u8 *bitval = dev->info->xmii_ctrl1; + const u16 *regs = dev->info->regs; + bool gbit = false; + u8 data8; + bool val; + + ksz_pread8(dev, port, regs[P_XMII_CTRL_1], &data8); + + val = FIELD_GET(P_GMII_1GBIT_M, data8); + + if (val == bitval[P_GMII_1GBIT]) + gbit = true; + + return gbit; +} + +static phy_interface_t ksz9477_get_xmii(struct ksz_device *dev, int port, + bool gbit) +{ + const u8 *bitval = dev->info->xmii_ctrl1; + const u16 *regs = dev->info->regs; + phy_interface_t interface; + u8 data8; + u8 val; + + ksz_pread8(dev, port, regs[P_XMII_CTRL_1], &data8); + + val = FIELD_GET(P_MII_SEL_M, data8); + + if (val == bitval[P_MII_SEL]) { + if (gbit) + interface = PHY_INTERFACE_MODE_GMII; + else + interface = PHY_INTERFACE_MODE_MII; + } else if (val == bitval[P_RMII_SEL]) { + interface = PHY_INTERFACE_MODE_RMII; + } else { + interface = PHY_INTERFACE_MODE_RGMII; + if (data8 & P_RGMII_ID_EG_ENABLE) + interface = PHY_INTERFACE_MODE_RGMII_TXID; + if (data8 & P_RGMII_ID_IG_ENABLE) { + interface = PHY_INTERFACE_MODE_RGMII_RXID; + if (data8 & P_RGMII_ID_EG_ENABLE) + interface = PHY_INTERFACE_MODE_RGMII_ID; + } + } + + return interface; +} + static phy_interface_t ksz9477_get_interface(struct ksz_device *dev, int port) { phy_interface_t interface; @@ -1189,9 +1241,9 @@ static phy_interface_t ksz9477_get_interface(struct ksz_device *dev, int port) if (dev->info->internal_phy[port]) return PHY_INTERFACE_MODE_NA; - gbit = ksz_get_gbit(dev, port); + gbit = ksz9477_get_gbit(dev, port); - interface = ksz_get_xmii(dev, port, gbit); + interface = ksz9477_get_xmii(dev, port, gbit); return interface; } diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index 56dd4c27b182..92fcbd9605c7 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -2966,39 +2966,6 @@ void ksz_set_xmii(struct ksz_device *dev, int port, phy_interface_t interface) ksz_pwrite8(dev, port, regs[P_XMII_CTRL_1], data8); } -phy_interface_t ksz_get_xmii(struct ksz_device *dev, int port, bool gbit) -{ - const u8 *bitval = dev->info->xmii_ctrl1; - const u16 *regs = dev->info->regs; - phy_interface_t interface; - u8 data8; - u8 val; - - ksz_pread8(dev, port, regs[P_XMII_CTRL_1], &data8); - - val = FIELD_GET(P_MII_SEL_M, data8); - - if (val == bitval[P_MII_SEL]) { - if (gbit) - interface = PHY_INTERFACE_MODE_GMII; - else - interface = PHY_INTERFACE_MODE_MII; - } else if (val == bitval[P_RMII_SEL]) { - interface = PHY_INTERFACE_MODE_RMII; - } else { - interface = PHY_INTERFACE_MODE_RGMII; - if (data8 & P_RGMII_ID_EG_ENABLE) - interface = PHY_INTERFACE_MODE_RGMII_TXID; - if (data8 & P_RGMII_ID_IG_ENABLE) { - interface = PHY_INTERFACE_MODE_RGMII_RXID; - if (data8 & P_RGMII_ID_EG_ENABLE) - interface = PHY_INTERFACE_MODE_RGMII_ID; - } - } - - return interface; -} - bool ksz_phylink_need_config(struct phylink_config *config, unsigned int mode) { @@ -3034,24 +3001,6 @@ void ksz_phylink_mac_config(struct phylink_config *config, ksz_set_xmii(dev, port, state->interface); } -bool ksz_get_gbit(struct ksz_device *dev, int port) -{ - const u8 *bitval = dev->info->xmii_ctrl1; - const u16 *regs = dev->info->regs; - bool gbit = false; - u8 data8; - bool val; - - ksz_pread8(dev, port, regs[P_XMII_CTRL_1], &data8); - - val = FIELD_GET(P_GMII_1GBIT_M, data8); - - if (val == bitval[P_GMII_1GBIT]) - gbit = true; - - return gbit; -} - static int ksz_switch_detect(struct ksz_device *dev) { u8 id1, id2, id4; diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index 347787ef1f00..2c7716d8cf28 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -395,8 +395,6 @@ void ksz_teardown(struct dsa_switch *ds); void ksz_init_mib_timer(struct ksz_device *dev); void ksz_r_mib_stats64(struct ksz_device *dev, int port); void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state); -bool ksz_get_gbit(struct ksz_device *dev, int port); -phy_interface_t ksz_get_xmii(struct ksz_device *dev, int port, bool gbit); extern const struct ksz_chip_data ksz_switch_chips[]; int ksz_switch_macaddr_get(struct dsa_switch *ds, int port, struct netlink_ext_ack *extack); From ab4b571d44fbc849de7a7f57c232ff074c323704 Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Thu, 2 Jul 2026 11:07:28 +0200 Subject: [PATCH 0236/1433] net: dsa: microchip: move KSZ9477 errata handling to ksz9477.c The KSZ9477 PHY errata is handled from the common ksz_r_mib_stat64(). This errata clearly belongs to the KSZ9477 family so it should be handled from the ksz9477-specific portion of the driver. Create a ksz9477-specific r_mib_stat64() implementation that handles this errata. Remove the errata handling from the common ksz_r_mib_stat64(). Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260702-clean-ksz-4th-v1-6-93441e695fa4@bootlin.com Signed-off-by: Paolo Abeni --- drivers/net/dsa/microchip/ksz9477.c | 24 ++++++++++++-- drivers/net/dsa/microchip/ksz9477.h | 2 -- drivers/net/dsa/microchip/ksz_common.c | 46 -------------------------- drivers/net/dsa/microchip/ksz_common.h | 39 ++++++++++++++++++++++ 4 files changed, 60 insertions(+), 51 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz9477.c b/drivers/net/dsa/microchip/ksz9477.c index ad6748905e08..a831eb884ce4 100644 --- a/drivers/net/dsa/microchip/ksz9477.c +++ b/drivers/net/dsa/microchip/ksz9477.c @@ -483,8 +483,8 @@ static int ksz9477_half_duplex_monitor(struct ksz_device *dev, int port, return ret; } -int ksz9477_errata_monitor(struct ksz_device *dev, int port, - u64 tx_late_col) +static int ksz9477_errata_monitor(struct ksz_device *dev, int port, + u64 tx_late_col) { u8 status; int ret; @@ -502,6 +502,24 @@ int ksz9477_errata_monitor(struct ksz_device *dev, int port, return ret; } +static void ksz9477_r_mib_stats64(struct ksz_device *dev, int port) +{ + struct ksz_stats_raw *raw; + struct ksz_port_mib *mib; + int ret; + + ksz_r_mib_stats64(dev, port); + + if (dev->info->phy_errata_9477 && !ksz_is_sgmii_port(dev, port)) { + mib = &dev->ports[port].mib; + raw = (struct ksz_stats_raw *)mib->counters; + + ret = ksz9477_errata_monitor(dev, port, raw->tx_late_col); + if (ret) + dev_err(dev->dev, "Failed to monitor transmission halt\n"); + } +}; + void ksz9477_port_init_cnt(struct ksz_device *dev, int port) { struct ksz_port_mib *mib = &dev->ports[port].mib; @@ -2109,7 +2127,7 @@ const struct ksz_dev_ops ksz9477_dev_ops = { .cfg_port_member = ksz9477_cfg_port_member, .r_mib_cnt = ksz9477_r_mib_cnt, .r_mib_pkt = ksz9477_r_mib_pkt, - .r_mib_stat64 = ksz_r_mib_stats64, + .r_mib_stat64 = ksz9477_r_mib_stats64, .freeze_mib = ksz9477_freeze_mib, .port_init_cnt = ksz9477_port_init_cnt, .pme_write8 = ksz_write8, diff --git a/drivers/net/dsa/microchip/ksz9477.h b/drivers/net/dsa/microchip/ksz9477.h index 25b74d0af6c5..962174a922a0 100644 --- a/drivers/net/dsa/microchip/ksz9477.h +++ b/drivers/net/dsa/microchip/ksz9477.h @@ -32,8 +32,6 @@ int ksz9477_port_mirror_add(struct dsa_switch *ds, int port, bool ingress, struct netlink_ext_ack *extack); void ksz9477_port_mirror_del(struct dsa_switch *ds, int port, struct dsa_mall_mirror_tc_entry *mirror); -int ksz9477_errata_monitor(struct ksz_device *dev, int port, - u64 tx_late_col); int ksz9477_fdb_dump(struct dsa_switch *ds, int port, dsa_fdb_dump_cb_t *cb, void *data); int ksz9477_fdb_add(struct dsa_switch *ds, int port, diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index 92fcbd9605c7..b54aea92b67d 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -37,45 +37,6 @@ #define MIB_COUNTER_NUM 0x20 -struct ksz_stats_raw { - u64 rx_hi; - u64 rx_undersize; - u64 rx_fragments; - u64 rx_oversize; - u64 rx_jabbers; - u64 rx_symbol_err; - u64 rx_crc_err; - u64 rx_align_err; - u64 rx_mac_ctrl; - u64 rx_pause; - u64 rx_bcast; - u64 rx_mcast; - u64 rx_ucast; - u64 rx_64_or_less; - u64 rx_65_127; - u64 rx_128_255; - u64 rx_256_511; - u64 rx_512_1023; - u64 rx_1024_1522; - u64 rx_1523_2000; - u64 rx_2001; - u64 tx_hi; - u64 tx_late_col; - u64 tx_pause; - u64 tx_bcast; - u64 tx_mcast; - u64 tx_ucast; - u64 tx_deferred; - u64 tx_total_col; - u64 tx_exc_col; - u64 tx_single_col; - u64 tx_mult_col; - u64 rx_total; - u64 tx_total; - u64 rx_discards; - u64 tx_discards; -}; - static const struct ksz_mib_names ksz88xx_mib_names[] = { { 0x00, "rx" }, { 0x01, "rx_hi" }, @@ -1976,7 +1937,6 @@ void ksz_r_mib_stats64(struct ksz_device *dev, int port) struct rtnl_link_stats64 *stats; struct ksz_stats_raw *raw; struct ksz_port_mib *mib; - int ret; mib = &dev->ports[port].mib; stats = &mib->stats64; @@ -2018,12 +1978,6 @@ void ksz_r_mib_stats64(struct ksz_device *dev, int port) pstats->rx_pause_frames = raw->rx_pause; spin_unlock(&mib->stats64_lock); - - if (dev->info->phy_errata_9477 && !ksz_is_sgmii_port(dev, port)) { - ret = ksz9477_errata_monitor(dev, port, raw->tx_late_col); - if (ret) - dev_err(dev->dev, "Failed to monitor transmission halt\n"); - } } void ksz_get_stats64(struct dsa_switch *ds, int port, diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index 2c7716d8cf28..245329b6e0e9 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -40,6 +40,45 @@ struct vlan_table { u32 table[3]; }; +struct ksz_stats_raw { + u64 rx_hi; + u64 rx_undersize; + u64 rx_fragments; + u64 rx_oversize; + u64 rx_jabbers; + u64 rx_symbol_err; + u64 rx_crc_err; + u64 rx_align_err; + u64 rx_mac_ctrl; + u64 rx_pause; + u64 rx_bcast; + u64 rx_mcast; + u64 rx_ucast; + u64 rx_64_or_less; + u64 rx_65_127; + u64 rx_128_255; + u64 rx_256_511; + u64 rx_512_1023; + u64 rx_1024_1522; + u64 rx_1523_2000; + u64 rx_2001; + u64 tx_hi; + u64 tx_late_col; + u64 tx_pause; + u64 tx_bcast; + u64 tx_mcast; + u64 tx_ucast; + u64 tx_deferred; + u64 tx_total_col; + u64 tx_exc_col; + u64 tx_single_col; + u64 tx_mult_col; + u64 rx_total; + u64 tx_total; + u64 rx_discards; + u64 tx_discards; +}; + struct ksz_port_mib { struct mutex cnt_mutex; /* structure access */ u8 cnt_ptr; From 610106257cd7cd1ba7dc014796f515a7bd3a480f Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Thu, 2 Jul 2026 11:07:29 +0200 Subject: [PATCH 0237/1433] net: dsa: microchip: handle KSZ8-specific tc setup in ksz8.c The setup of QDISC_ETS isn't the same for the KSZ8 switches than for the other switches. It leads to is_ksz8() branches in the common code. Move the KSZ8-specific portions into ksz8.c by creating two setup_tc() functions, one dedicated to the KSZ87xx family that only handles the TC_SETUP_QDISC_CBS case, one for the rest of the KSZ8 that handles both TC_SETUP_QDISC_CBS and TC_SETUP_QDISC_ETS cases. It remains some is_kszXXXX() branches because inside the ksz88xx family, only the ksz88x3 switches support QDISC_ETS. Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260702-clean-ksz-4th-v1-7-93441e695fa4@bootlin.com Signed-off-by: Paolo Abeni --- drivers/net/dsa/microchip/ksz8.c | 159 ++++++++++++++++++++++++- drivers/net/dsa/microchip/ksz_common.c | 124 ++----------------- drivers/net/dsa/microchip/ksz_common.h | 7 ++ 3 files changed, 171 insertions(+), 119 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index ad7c7a6e1d31..472cc62ea747 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -1782,6 +1782,159 @@ static void ksz8_port_mirror_del(struct dsa_switch *ds, int port, PORT_MIRROR_SNIFFER, false); } +static u8 ksz8463_tc_ctrl(int port, int queue) +{ + u8 reg; + + reg = 0xC8 + port * 4; + reg += ((3 - queue) / 2) * 2; + reg++; + reg -= (queue & 1); + return reg; +} + +/** + * ksz88x3_tc_ets_add - Configure ETS (Enhanced Transmission Selection) + * for a port on KSZ88x3 switch + * @dev: Pointer to the KSZ switch device structure + * @port: Port number to configure + * @p: Pointer to offload replace parameters describing ETS bands and mapping + * + * The KSZ88x3 supports two scheduling modes: Strict Priority and + * Weighted Fair Queuing (WFQ). Both modes have fixed behavior: + * - No configurable queue-to-priority mapping + * - No weight adjustment in WFQ mode + * + * This function configures the switch to use strict priority mode by + * clearing the WFQ enable bit for all queues associated with ETS bands. + * If strict priority is not explicitly requested, the switch will default + * to WFQ mode. + * + * Return: 0 on success, or a negative error code on failure + */ +static int ksz88x3_tc_ets_add(struct ksz_device *dev, int port, + struct tc_ets_qopt_offload_replace_params *p) +{ + int ret, band; + + /* Only strict priority mode is supported for now. + * WFQ is implicitly enabled when strict mode is disabled. + */ + for (band = 0; band < p->bands; band++) { + int queue = ksz_ets_band_to_queue(p, band); + u8 reg; + + /* Calculate TXQ Split Control register address for this + * port/queue + */ + reg = KSZ8873_TXQ_SPLIT_CTRL_REG(port, queue); + if (ksz_is_ksz8463(dev)) + reg = ksz8463_tc_ctrl(port, queue); + + /* Clear WFQ enable bit to select strict priority scheduling */ + ret = ksz_rmw8(dev, reg, KSZ8873_TXQ_WFQ_ENABLE, 0); + if (ret) + return ret; + } + + return 0; +} + +/** + * ksz88x3_tc_ets_del - Reset ETS (Enhanced Transmission Selection) config + * for a port on KSZ88x3 switch + * @dev: Pointer to the KSZ switch device structure + * @port: Port number to reset + * + * The KSZ88x3 supports only fixed scheduling modes: Strict Priority or + * Weighted Fair Queuing (WFQ), with no reconfiguration of weights or + * queue mapping. This function resets the port’s scheduling mode to + * the default, which is WFQ, by enabling the WFQ bit for all queues. + * + * Return: 0 on success, or a negative error code on failure + */ +static int ksz88x3_tc_ets_del(struct ksz_device *dev, int port) +{ + int ret, queue; + + /* Iterate over all transmit queues for this port */ + for (queue = 0; queue < dev->info->num_tx_queues; queue++) { + u8 reg; + + /* Calculate TXQ Split Control register address for this + * port/queue + */ + reg = KSZ8873_TXQ_SPLIT_CTRL_REG(port, queue); + if (ksz_is_ksz8463(dev)) + reg = ksz8463_tc_ctrl(port, queue); + + /* Set WFQ enable bit to revert back to default scheduling + * mode + */ + ret = ksz_rmw8(dev, reg, KSZ8873_TXQ_WFQ_ENABLE, + KSZ8873_TXQ_WFQ_ENABLE); + if (ret) + return ret; + } + + return 0; +} + +static int ksz8_tc_setup_qdisc_ets(struct dsa_switch *ds, int port, + struct tc_ets_qopt_offload *qopt) +{ + struct ksz_device *dev = ds->priv; + int ret; + + if (!(ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev))) + return -EOPNOTSUPP; + + if (qopt->parent != TC_H_ROOT) { + dev_err(dev->dev, "Parent should be \"root\"\n"); + return -EOPNOTSUPP; + } + + switch (qopt->command) { + case TC_ETS_REPLACE: + ret = ksz_tc_ets_validate(dev, port, &qopt->replace_params); + if (ret) + return ret; + + return ksz88x3_tc_ets_add(dev, port, &qopt->replace_params); + case TC_ETS_DESTROY: + return ksz88x3_tc_ets_del(dev, port); + case TC_ETS_STATS: + case TC_ETS_GRAFT: + return -EOPNOTSUPP; + } + + return -EOPNOTSUPP; +} + +static int ksz87xx_setup_tc(struct dsa_switch *ds, int port, + enum tc_setup_type type, void *type_data) +{ + switch (type) { + case TC_SETUP_QDISC_CBS: + return ksz_setup_tc_cbs(ds, port, type_data); + default: + return -EOPNOTSUPP; + } +} + +static int ksz8_setup_tc(struct dsa_switch *ds, int port, + enum tc_setup_type type, void *type_data) +{ + switch (type) { + case TC_SETUP_QDISC_CBS: + return ksz_setup_tc_cbs(ds, port, type_data); + case TC_SETUP_QDISC_ETS: + return ksz8_tc_setup_qdisc_ets(ds, port, type_data); + default: + return -EOPNOTSUPP; + } +} + static void ksz8795_cpu_interface_select(struct ksz_device *dev, int port) { struct ksz_port *p = &dev->ports[port]; @@ -2645,7 +2798,7 @@ const struct dsa_switch_ops ksz8463_switch_ops = { .port_hwtstamp_set = ksz_hwtstamp_set, .port_txtstamp = ksz_port_txtstamp, .port_rxtstamp = ksz_port_rxtstamp, - .port_setup_tc = ksz_setup_tc, + .port_setup_tc = ksz8_setup_tc, .port_get_default_prio = ksz_port_get_default_prio, .port_set_default_prio = ksz_port_set_default_prio, .port_get_dscp_prio = ksz_port_get_dscp_prio, @@ -2695,7 +2848,7 @@ const struct dsa_switch_ops ksz87xx_switch_ops = { .port_hwtstamp_set = ksz_hwtstamp_set, .port_txtstamp = ksz_port_txtstamp, .port_rxtstamp = ksz_port_rxtstamp, - .port_setup_tc = ksz_setup_tc, + .port_setup_tc = ksz87xx_setup_tc, .port_get_default_prio = ksz_port_get_default_prio, .port_set_default_prio = ksz_port_set_default_prio, .port_get_dscp_prio = ksz_port_get_dscp_prio, @@ -2748,7 +2901,7 @@ const struct dsa_switch_ops ksz88xx_switch_ops = { .port_hwtstamp_set = ksz_hwtstamp_set, .port_txtstamp = ksz_port_txtstamp, .port_rxtstamp = ksz_port_rxtstamp, - .port_setup_tc = ksz_setup_tc, + .port_setup_tc = ksz8_setup_tc, .port_get_default_prio = ksz_port_get_default_prio, .port_set_default_prio = ksz_port_set_default_prio, .port_get_dscp_prio = ksz_port_get_dscp_prio, diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index b54aea92b67d..2846041d97a5 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -3097,8 +3097,8 @@ static int ksz_setup_tc_mode(struct ksz_device *dev, int port, u8 scheduler, FIELD_PREP(MTI_SHAPING_M, shaper)); } -static int ksz_setup_tc_cbs(struct dsa_switch *ds, int port, - struct tc_cbs_qopt_offload *qopt) +int ksz_setup_tc_cbs(struct dsa_switch *ds, int port, + struct tc_cbs_qopt_offload *qopt) { struct ksz_device *dev = ds->priv; int ret; @@ -3163,8 +3163,8 @@ static int ksz_disable_egress_rate_limit(struct ksz_device *dev, int port) return 0; } -static int ksz_ets_band_to_queue(struct tc_ets_qopt_offload_replace_params *p, - int band) +int ksz_ets_band_to_queue(struct tc_ets_qopt_offload_replace_params *p, + int band) { /* Compared to queues, bands prioritize packets differently. In strict * priority mode, the lowest priority is assigned to Queue 0 while the @@ -3173,104 +3173,6 @@ static int ksz_ets_band_to_queue(struct tc_ets_qopt_offload_replace_params *p, return p->bands - 1 - band; } -static u8 ksz8463_tc_ctrl(int port, int queue) -{ - u8 reg; - - reg = 0xC8 + port * 4; - reg += ((3 - queue) / 2) * 2; - reg++; - reg -= (queue & 1); - return reg; -} - -/** - * ksz88x3_tc_ets_add - Configure ETS (Enhanced Transmission Selection) - * for a port on KSZ88x3 switch - * @dev: Pointer to the KSZ switch device structure - * @port: Port number to configure - * @p: Pointer to offload replace parameters describing ETS bands and mapping - * - * The KSZ88x3 supports two scheduling modes: Strict Priority and - * Weighted Fair Queuing (WFQ). Both modes have fixed behavior: - * - No configurable queue-to-priority mapping - * - No weight adjustment in WFQ mode - * - * This function configures the switch to use strict priority mode by - * clearing the WFQ enable bit for all queues associated with ETS bands. - * If strict priority is not explicitly requested, the switch will default - * to WFQ mode. - * - * Return: 0 on success, or a negative error code on failure - */ -static int ksz88x3_tc_ets_add(struct ksz_device *dev, int port, - struct tc_ets_qopt_offload_replace_params *p) -{ - int ret, band; - - /* Only strict priority mode is supported for now. - * WFQ is implicitly enabled when strict mode is disabled. - */ - for (band = 0; band < p->bands; band++) { - int queue = ksz_ets_band_to_queue(p, band); - u8 reg; - - /* Calculate TXQ Split Control register address for this - * port/queue - */ - reg = KSZ8873_TXQ_SPLIT_CTRL_REG(port, queue); - if (ksz_is_ksz8463(dev)) - reg = ksz8463_tc_ctrl(port, queue); - - /* Clear WFQ enable bit to select strict priority scheduling */ - ret = ksz_rmw8(dev, reg, KSZ8873_TXQ_WFQ_ENABLE, 0); - if (ret) - return ret; - } - - return 0; -} - -/** - * ksz88x3_tc_ets_del - Reset ETS (Enhanced Transmission Selection) config - * for a port on KSZ88x3 switch - * @dev: Pointer to the KSZ switch device structure - * @port: Port number to reset - * - * The KSZ88x3 supports only fixed scheduling modes: Strict Priority or - * Weighted Fair Queuing (WFQ), with no reconfiguration of weights or - * queue mapping. This function resets the port’s scheduling mode to - * the default, which is WFQ, by enabling the WFQ bit for all queues. - * - * Return: 0 on success, or a negative error code on failure - */ -static int ksz88x3_tc_ets_del(struct ksz_device *dev, int port) -{ - int ret, queue; - - /* Iterate over all transmit queues for this port */ - for (queue = 0; queue < dev->info->num_tx_queues; queue++) { - u8 reg; - - /* Calculate TXQ Split Control register address for this - * port/queue - */ - reg = KSZ8873_TXQ_SPLIT_CTRL_REG(port, queue); - if (ksz_is_ksz8463(dev)) - reg = ksz8463_tc_ctrl(port, queue); - - /* Set WFQ enable bit to revert back to default scheduling - * mode - */ - ret = ksz_rmw8(dev, reg, KSZ8873_TXQ_WFQ_ENABLE, - KSZ8873_TXQ_WFQ_ENABLE); - if (ret) - return ret; - } - - return 0; -} - static int ksz_queue_set_strict(struct ksz_device *dev, int port, int queue) { int ret; @@ -3363,8 +3265,8 @@ static int ksz_tc_ets_del(struct ksz_device *dev, int port) return ksz9477_set_default_prio_queue_mapping(dev, port); } -static int ksz_tc_ets_validate(struct ksz_device *dev, int port, - struct tc_ets_qopt_offload_replace_params *p) +int ksz_tc_ets_validate(struct ksz_device *dev, int port, + struct tc_ets_qopt_offload_replace_params *p) { int band; @@ -3405,9 +3307,6 @@ static int ksz_tc_setup_qdisc_ets(struct dsa_switch *ds, int port, struct ksz_device *dev = ds->priv; int ret; - if (is_ksz8(dev) && !(ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev))) - return -EOPNOTSUPP; - if (qopt->parent != TC_H_ROOT) { dev_err(dev->dev, "Parent should be \"root\"\n"); return -EOPNOTSUPP; @@ -3419,16 +3318,9 @@ static int ksz_tc_setup_qdisc_ets(struct dsa_switch *ds, int port, if (ret) return ret; - if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) - return ksz88x3_tc_ets_add(dev, port, - &qopt->replace_params); - else - return ksz_tc_ets_add(dev, port, &qopt->replace_params); + return ksz_tc_ets_add(dev, port, &qopt->replace_params); case TC_ETS_DESTROY: - if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) - return ksz88x3_tc_ets_del(dev, port); - else - return ksz_tc_ets_del(dev, port); + return ksz_tc_ets_del(dev, port); case TC_ETS_STATS: case TC_ETS_GRAFT: return -EOPNOTSUPP; diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index 245329b6e0e9..7aa4a69d06ef 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -481,6 +482,12 @@ void ksz_phylink_mac_link_down(struct phylink_config *config, int ksz_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_keee *e); +int ksz_ets_band_to_queue(struct tc_ets_qopt_offload_replace_params *p, + int band); +int ksz_setup_tc_cbs(struct dsa_switch *ds, int port, + struct tc_cbs_qopt_offload *qopt); +int ksz_tc_ets_validate(struct ksz_device *dev, int port, + struct tc_ets_qopt_offload_replace_params *p); int ksz_setup_tc(struct dsa_switch *ds, int port, enum tc_setup_type type, void *type_data); From e8de229a62ec3d004fb72246f77085f0d15c9068 Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Thu, 2 Jul 2026 11:07:30 +0200 Subject: [PATCH 0238/1433] net: dsa: microchip: move ksz9477_set_default_prio_queue_mapping() to ksz9477.c ksz9477_set_default_prio_queue_mapping() dictates a KSZ9477-specific behavior but is defined in the common section of the code. Move its definition to the KSZ9477-specific area. Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260702-clean-ksz-4th-v1-8-93441e695fa4@bootlin.com Signed-off-by: Paolo Abeni --- drivers/net/dsa/microchip/ksz9477.c | 23 +++++++++++++++++++++++ drivers/net/dsa/microchip/ksz9477.h | 1 + drivers/net/dsa/microchip/ksz_common.c | 22 ---------------------- drivers/net/dsa/microchip/ksz_common.h | 1 - 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz9477.c b/drivers/net/dsa/microchip/ksz9477.c index a831eb884ce4..691b9b18c707 100644 --- a/drivers/net/dsa/microchip/ksz9477.c +++ b/drivers/net/dsa/microchip/ksz9477.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "ksz9477_reg.h" @@ -1410,6 +1411,28 @@ static void ksz9477_port_setup(struct ksz_device *dev, int port, bool cpu_port) ksz_pwrite8(dev, port, regs[REG_PORT_PME_CTRL], 0); } +int ksz9477_set_default_prio_queue_mapping(struct ksz_device *dev, int port) +{ + u32 queue_map = 0; + int ipm; + + for (ipm = 0; ipm < dev->info->num_ipms; ipm++) { + int queue; + + /* Traffic Type (TT) is corresponding to the Internal Priority + * Map (IPM) in the switch. Traffic Class (TC) is + * corresponding to the queue in the switch. + */ + queue = ieee8021q_tt_to_tc(ipm, dev->info->num_tx_queues); + if (queue < 0) + return queue; + + queue_map |= queue << (ipm * KSZ9477_PORT_TC_MAP_S); + } + + return ksz_pwrite32(dev, port, KSZ9477_PORT_MRI_TC_MAP__4, queue_map); +} + static int ksz9477_dsa_port_setup(struct dsa_switch *ds, int port) { struct ksz_device *dev = ds->priv; diff --git a/drivers/net/dsa/microchip/ksz9477.h b/drivers/net/dsa/microchip/ksz9477.h index 962174a922a0..a84c000000e6 100644 --- a/drivers/net/dsa/microchip/ksz9477.h +++ b/drivers/net/dsa/microchip/ksz9477.h @@ -44,6 +44,7 @@ int ksz9477_mdb_del(struct dsa_switch *ds, int port, const struct switchdev_obj_port_mdb *mdb, struct dsa_db db); int ksz9477_enable_stp_addr(struct ksz_device *dev); void ksz9477_port_queue_split(struct ksz_device *dev, int port); +int ksz9477_set_default_prio_queue_mapping(struct ksz_device *dev, int port); int ksz9477_port_acl_init(struct ksz_device *dev, int port); void ksz9477_port_acl_free(struct ksz_device *dev, int port); diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index 2846041d97a5..4f6f5526c26b 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -2753,28 +2753,6 @@ void ksz_port_bridge_leave(struct dsa_switch *ds, int port, */ } -int ksz9477_set_default_prio_queue_mapping(struct ksz_device *dev, int port) -{ - u32 queue_map = 0; - int ipm; - - for (ipm = 0; ipm < dev->info->num_ipms; ipm++) { - int queue; - - /* Traffic Type (TT) is corresponding to the Internal Priority - * Map (IPM) in the switch. Traffic Class (TC) is - * corresponding to the queue in the switch. - */ - queue = ieee8021q_tt_to_tc(ipm, dev->info->num_tx_queues); - if (queue < 0) - return queue; - - queue_map |= queue << (ipm * KSZ9477_PORT_TC_MAP_S); - } - - return ksz_pwrite32(dev, port, KSZ9477_PORT_MRI_TC_MAP__4, queue_map); -} - void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) { struct ksz_device *dev = ds->priv; diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index 7aa4a69d06ef..029080838237 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -512,7 +512,6 @@ int ksz_pirq_setup(struct ksz_device *dev, u8 p); int ksz_girq_setup(struct ksz_device *dev); void ksz_irq_free(struct ksz_irq *kirq); int ksz_parse_drive_strength(struct ksz_device *dev); -int ksz9477_set_default_prio_queue_mapping(struct ksz_device *dev, int port); /* Common register access functions */ static inline struct regmap *ksz_regmap_8(struct ksz_device *dev) From da466bf62b01fdbbe4d2abeacec654a6e42b9e36 Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Thu, 2 Jul 2026 11:07:31 +0200 Subject: [PATCH 0239/1433] net: dsa: microchip: rename ksz9477_drive_strength_write() ksz9477_drive_strength_write() isn't used for the KSZ9477-family only. It's also used for the KSZ87xx chip variants. This function name is misleading. Rename it ksz_drive_strength_write(). Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260702-clean-ksz-4th-v1-9-93441e695fa4@bootlin.com Signed-off-by: Paolo Abeni --- drivers/net/dsa/microchip/ksz_common.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index 4f6f5526c26b..15ed139564cb 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -3838,8 +3838,8 @@ static void ksz_drive_strength_error(struct ksz_device *dev, } /** - * ksz9477_drive_strength_write() - Set the drive strength for specific KSZ9477 - * chip variants. + * ksz_drive_strength_write() - Set the drive strength for specific KSZ9477 + * and the KSZ87xx chip variants. * @dev: ksz device * @props: Array of drive strength properties to be applied * @num_props: Number of properties in the array @@ -3850,9 +3850,9 @@ static void ksz_drive_strength_error(struct ksz_device *dev, * * Return: 0 on successful configuration, a negative error code on failure. */ -static int ksz9477_drive_strength_write(struct ksz_device *dev, - struct ksz_driver_strength_prop *props, - int num_props) +static int ksz_drive_strength_write(struct ksz_device *dev, + struct ksz_driver_strength_prop *props, + int num_props) { size_t array_size = ARRAY_SIZE(ksz9477_drive_strengths); int i, ret, reg; @@ -3998,8 +3998,8 @@ int ksz_parse_drive_strength(struct ksz_device *dev) case KSZ9896_CHIP_ID: case KSZ9897_CHIP_ID: case LAN9646_CHIP_ID: - return ksz9477_drive_strength_write(dev, of_props, - ARRAY_SIZE(of_props)); + return ksz_drive_strength_write(dev, of_props, + ARRAY_SIZE(of_props)); default: for (i = 0; i < ARRAY_SIZE(of_props); i++) { if (of_props[i].value == -1) From 90c676dd4bb61e63a5e27522d32799b5667021ad Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Thu, 2 Jul 2026 11:07:32 +0200 Subject: [PATCH 0240/1433] net: dsa: microchip: move the drive strength config out of the common section The drive strength configuration is done during the setup of all switches through a common function that then has specific behavior depending on the switch identity. Split the common configuration in two functions: one is dedicated to the KSZ8 family, the other is dedicated to the KSZ9477 family. Remove the drive strength configuration from the lan937x_setup since the LAN937x family doesn't support it. Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260702-clean-ksz-4th-v1-10-93441e695fa4@bootlin.com Signed-off-by: Paolo Abeni --- drivers/net/dsa/microchip/ksz8.c | 127 ++++++++++++++++- drivers/net/dsa/microchip/ksz9477.c | 54 ++++++- drivers/net/dsa/microchip/ksz_common.c | 171 ++--------------------- drivers/net/dsa/microchip/ksz_common.h | 32 ++++- drivers/net/dsa/microchip/lan937x_main.c | 4 - 5 files changed, 218 insertions(+), 170 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index 472cc62ea747..c4c769028a20 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -36,6 +36,15 @@ #include "ksz8_reg.h" #include "ksz8.h" +/* ksz88x3_drive_strengths - Drive strength mapping for KSZ8863, KSZ8873, .. + * variants. + * This values are documented in KSZ8873 and KSZ8863 datasheets. + */ +static const struct ksz_drive_strength ksz88x3_drive_strengths[] = { + { 0, 8000 }, + { KSZ8873_DRIVE_STRENGTH_16MA, 16000 }, +}; + struct ksz88xx_stats_raw { u64 rx; u64 rx_hi; @@ -2291,6 +2300,122 @@ static void ksz88xx_r_mib_stats64(struct ksz_device *dev, int port) spin_unlock(&mib->stats64_lock); } +/** + * ksz88x3_drive_strength_write() - Set the drive strength configuration for + * KSZ8863 compatible chip variants. + * @dev: ksz device + * @props: Array of drive strength properties to be set + * @num_props: Number of properties in the array + * + * This function applies the specified drive strength settings to KSZ88X3 chip + * variants (KSZ8873, KSZ8863). + * It ensures the configurations align with what the chip variant supports and + * warns or errors out on unsupported settings. + * + * Return: 0 on success, error code otherwise + */ +static int ksz88x3_drive_strength_write(struct ksz_device *dev, + struct ksz_driver_strength_prop *props, + int num_props) +{ + size_t array_size = ARRAY_SIZE(ksz88x3_drive_strengths); + int microamp; + int i, ret; + + for (i = 0; i < num_props; i++) { + if (props[i].value == -1 || i == KSZ_DRIVER_STRENGTH_IO) + continue; + + dev_warn(dev->dev, "%s is not supported by this chip variant\n", + props[i].name); + } + + microamp = props[KSZ_DRIVER_STRENGTH_IO].value; + ret = ksz_drive_strength_to_reg(ksz88x3_drive_strengths, array_size, + microamp); + if (ret < 0) { + ksz_drive_strength_error(dev, ksz88x3_drive_strengths, + array_size, microamp); + return ret; + } + + return ksz_rmw8(dev, KSZ8873_REG_GLOBAL_CTRL_12, + KSZ8873_DRIVE_STRENGTH_16MA, ret); +} + +/** + * ksz8_parse_drive_strength() - Extract and apply drive strength configurations + * from device tree properties. + * @dev: ksz device + * + * This function reads the specified drive strength properties from the + * device tree, validates against the supported chip variants, and sets + * them accordingly. An error should be critical here, as the drive strength + * settings are crucial for EMI compliance. + * + * Return: 0 on success, error code otherwise + */ +static int ksz8_parse_drive_strength(struct ksz_device *dev) +{ + struct ksz_driver_strength_prop of_props[] = { + [KSZ_DRIVER_STRENGTH_HI] = { + .name = "microchip,hi-drive-strength-microamp", + .offset = SW_HI_SPEED_DRIVE_STRENGTH_S, + .value = -1, + }, + [KSZ_DRIVER_STRENGTH_LO] = { + .name = "microchip,lo-drive-strength-microamp", + .offset = SW_LO_SPEED_DRIVE_STRENGTH_S, + .value = -1, + }, + [KSZ_DRIVER_STRENGTH_IO] = { + .name = "microchip,io-drive-strength-microamp", + .offset = 0, /* don't care */ + .value = -1, + }, + }; + struct device_node *np = dev->dev->of_node; + bool have_any_prop = false; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(of_props); i++) { + ret = of_property_read_u32(np, of_props[i].name, + &of_props[i].value); + if (ret && ret != -EINVAL) + dev_warn(dev->dev, "Failed to read %s\n", + of_props[i].name); + if (ret) + continue; + + have_any_prop = true; + } + + if (!have_any_prop) + return 0; + + switch (dev->chip_id) { + case KSZ88X3_CHIP_ID: + return ksz88x3_drive_strength_write(dev, of_props, + ARRAY_SIZE(of_props)); + case KSZ8795_CHIP_ID: + case KSZ8794_CHIP_ID: + case KSZ8765_CHIP_ID: + return ksz_drive_strength_write(dev, of_props, + ARRAY_SIZE(of_props)); + default: + /* KSZ8864, KSZ8895 */ + for (i = 0; i < ARRAY_SIZE(of_props); i++) { + if (of_props[i].value == -1) + continue; + + dev_warn(dev->dev, "%s is not supported by this chip variant\n", + of_props[i].name); + } + } + + return 0; +} + static int ksz8_setup(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; @@ -2313,7 +2438,7 @@ static int ksz8_setup(struct dsa_switch *ds) return ret; } - ret = ksz_parse_drive_strength(dev); + ret = ksz8_parse_drive_strength(dev); if (ret) return ret; diff --git a/drivers/net/dsa/microchip/ksz9477.c b/drivers/net/dsa/microchip/ksz9477.c index 691b9b18c707..3ee995545c57 100644 --- a/drivers/net/dsa/microchip/ksz9477.c +++ b/drivers/net/dsa/microchip/ksz9477.c @@ -1615,6 +1615,58 @@ int ksz9477_enable_stp_addr(struct ksz_device *dev) return 0; } +/** + * ksz9477_parse_drive_strength() - Extract and apply drive strength + * configurations from device tree properties. + * @dev: ksz device + * + * This function reads the specified drive strength properties from the + * device tree, validates against the supported chip variants, and sets + * them accordingly. An error should be critical here, as the drive strength + * settings are crucial for EMI compliance. + * + * Return: 0 on success, error code otherwise + */ +static int ksz9477_parse_drive_strength(struct ksz_device *dev) +{ + struct ksz_driver_strength_prop of_props[] = { + [KSZ_DRIVER_STRENGTH_HI] = { + .name = "microchip,hi-drive-strength-microamp", + .offset = SW_HI_SPEED_DRIVE_STRENGTH_S, + .value = -1, + }, + [KSZ_DRIVER_STRENGTH_LO] = { + .name = "microchip,lo-drive-strength-microamp", + .offset = SW_LO_SPEED_DRIVE_STRENGTH_S, + .value = -1, + }, + [KSZ_DRIVER_STRENGTH_IO] = { + .name = "microchip,io-drive-strength-microamp", + .offset = 0, /* don't care */ + .value = -1, + }, + }; + struct device_node *np = dev->dev->of_node; + bool have_any_prop = false; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(of_props); i++) { + ret = of_property_read_u32(np, of_props[i].name, + &of_props[i].value); + if (ret && ret != -EINVAL) + dev_warn(dev->dev, "Failed to read %s\n", + of_props[i].name); + if (ret) + continue; + + have_any_prop = true; + } + + if (!have_any_prop) + return 0; + + return ksz_drive_strength_write(dev, of_props, ARRAY_SIZE(of_props)); +} static int ksz9477_setup(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; @@ -1637,7 +1689,7 @@ static int ksz9477_setup(struct dsa_switch *ds) return ret; } - ret = ksz_parse_drive_strength(dev); + ret = ksz9477_parse_drive_strength(dev); if (ret) return ret; diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index 15ed139564cb..67ab6ddb9e53 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -113,28 +113,6 @@ static const struct ksz_mib_names ksz9477_mib_names[] = { { 0x83, "tx_discards" }, }; -struct ksz_driver_strength_prop { - const char *name; - int offset; - int value; -}; - -enum ksz_driver_strength_type { - KSZ_DRIVER_STRENGTH_HI, - KSZ_DRIVER_STRENGTH_LO, - KSZ_DRIVER_STRENGTH_IO, -}; - -/** - * struct ksz_drive_strength - drive strength mapping - * @reg_val: register value - * @microamp: microamp value - */ -struct ksz_drive_strength { - u32 reg_val; - u32 microamp; -}; - /* ksz9477_drive_strengths - Drive strength mapping for KSZ9477 variants * * This values are not documented in KSZ9477 variants but confirmed by @@ -170,15 +148,6 @@ static const struct ksz_drive_strength ksz9477_drive_strengths[] = { { SW_DRIVE_STRENGTH_28MA, 28000 }, }; -/* ksz88x3_drive_strengths - Drive strength mapping for KSZ8863, KSZ8873, .. - * variants. - * This values are documented in KSZ8873 and KSZ8863 datasheets. - */ -static const struct ksz_drive_strength ksz88x3_drive_strengths[] = { - { 0, 8000 }, - { KSZ8873_DRIVE_STRENGTH_16MA, 16000 }, -}; - /** * ksz_phylink_mac_disable_tx_lpi() - Callback to signal LPI support (Dummy) * @config: phylink config structure @@ -3785,8 +3754,8 @@ static void ksz_parse_rgmii_delay(struct ksz_device *dev, int port_num, * Returns: If found, the corresponding register value for that drive strength * is returned. Otherwise, -EINVAL is returned indicating an invalid value. */ -static int ksz_drive_strength_to_reg(const struct ksz_drive_strength *array, - size_t array_size, int microamp) +int ksz_drive_strength_to_reg(const struct ksz_drive_strength *array, + size_t array_size, int microamp) { int i; @@ -3809,9 +3778,9 @@ static int ksz_drive_strength_to_reg(const struct ksz_drive_strength *array, * is detected. It lists out all the supported drive strength values for * reference in the error message. */ -static void ksz_drive_strength_error(struct ksz_device *dev, - const struct ksz_drive_strength *array, - size_t array_size, int microamp) +void ksz_drive_strength_error(struct ksz_device *dev, + const struct ksz_drive_strength *array, + size_t array_size, int microamp) { char supported_values[100]; size_t remaining_size; @@ -3850,9 +3819,9 @@ static void ksz_drive_strength_error(struct ksz_device *dev, * * Return: 0 on successful configuration, a negative error code on failure. */ -static int ksz_drive_strength_write(struct ksz_device *dev, - struct ksz_driver_strength_prop *props, - int num_props) +int ksz_drive_strength_write(struct ksz_device *dev, + struct ksz_driver_strength_prop *props, + int num_props) { size_t array_size = ARRAY_SIZE(ksz9477_drive_strengths); int i, ret, reg; @@ -3889,130 +3858,6 @@ static int ksz_drive_strength_write(struct ksz_device *dev, return ksz_rmw8(dev, reg, mask, val); } -/** - * ksz88x3_drive_strength_write() - Set the drive strength configuration for - * KSZ8863 compatible chip variants. - * @dev: ksz device - * @props: Array of drive strength properties to be set - * @num_props: Number of properties in the array - * - * This function applies the specified drive strength settings to KSZ88X3 chip - * variants (KSZ8873, KSZ8863). - * It ensures the configurations align with what the chip variant supports and - * warns or errors out on unsupported settings. - * - * Return: 0 on success, error code otherwise - */ -static int ksz88x3_drive_strength_write(struct ksz_device *dev, - struct ksz_driver_strength_prop *props, - int num_props) -{ - size_t array_size = ARRAY_SIZE(ksz88x3_drive_strengths); - int microamp; - int i, ret; - - for (i = 0; i < num_props; i++) { - if (props[i].value == -1 || i == KSZ_DRIVER_STRENGTH_IO) - continue; - - dev_warn(dev->dev, "%s is not supported by this chip variant\n", - props[i].name); - } - - microamp = props[KSZ_DRIVER_STRENGTH_IO].value; - ret = ksz_drive_strength_to_reg(ksz88x3_drive_strengths, array_size, - microamp); - if (ret < 0) { - ksz_drive_strength_error(dev, ksz88x3_drive_strengths, - array_size, microamp); - return ret; - } - - return ksz_rmw8(dev, KSZ8873_REG_GLOBAL_CTRL_12, - KSZ8873_DRIVE_STRENGTH_16MA, ret); -} - -/** - * ksz_parse_drive_strength() - Extract and apply drive strength configurations - * from device tree properties. - * @dev: ksz device - * - * This function reads the specified drive strength properties from the - * device tree, validates against the supported chip variants, and sets - * them accordingly. An error should be critical here, as the drive strength - * settings are crucial for EMI compliance. - * - * Return: 0 on success, error code otherwise - */ -int ksz_parse_drive_strength(struct ksz_device *dev) -{ - struct ksz_driver_strength_prop of_props[] = { - [KSZ_DRIVER_STRENGTH_HI] = { - .name = "microchip,hi-drive-strength-microamp", - .offset = SW_HI_SPEED_DRIVE_STRENGTH_S, - .value = -1, - }, - [KSZ_DRIVER_STRENGTH_LO] = { - .name = "microchip,lo-drive-strength-microamp", - .offset = SW_LO_SPEED_DRIVE_STRENGTH_S, - .value = -1, - }, - [KSZ_DRIVER_STRENGTH_IO] = { - .name = "microchip,io-drive-strength-microamp", - .offset = 0, /* don't care */ - .value = -1, - }, - }; - struct device_node *np = dev->dev->of_node; - bool have_any_prop = false; - int i, ret; - - for (i = 0; i < ARRAY_SIZE(of_props); i++) { - ret = of_property_read_u32(np, of_props[i].name, - &of_props[i].value); - if (ret && ret != -EINVAL) - dev_warn(dev->dev, "Failed to read %s\n", - of_props[i].name); - if (ret) - continue; - - have_any_prop = true; - } - - if (!have_any_prop) - return 0; - - switch (dev->chip_id) { - case KSZ88X3_CHIP_ID: - return ksz88x3_drive_strength_write(dev, of_props, - ARRAY_SIZE(of_props)); - case KSZ8795_CHIP_ID: - case KSZ8794_CHIP_ID: - case KSZ8765_CHIP_ID: - case KSZ8563_CHIP_ID: - case KSZ8567_CHIP_ID: - case KSZ9477_CHIP_ID: - case KSZ9563_CHIP_ID: - case KSZ9567_CHIP_ID: - case KSZ9893_CHIP_ID: - case KSZ9896_CHIP_ID: - case KSZ9897_CHIP_ID: - case LAN9646_CHIP_ID: - return ksz_drive_strength_write(dev, of_props, - ARRAY_SIZE(of_props)); - default: - for (i = 0; i < ARRAY_SIZE(of_props); i++) { - if (of_props[i].value == -1) - continue; - - dev_warn(dev->dev, "%s is not supported by this chip variant\n", - of_props[i].name); - } - } - - return 0; -} - static int ksz8463_configure_straps_spi(struct ksz_device *dev) { struct pinctrl *pinctrl; diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index 029080838237..acaf70e6f393 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -511,7 +511,37 @@ int ksz_mdio_register(struct ksz_device *dev); int ksz_pirq_setup(struct ksz_device *dev, u8 p); int ksz_girq_setup(struct ksz_device *dev); void ksz_irq_free(struct ksz_irq *kirq); -int ksz_parse_drive_strength(struct ksz_device *dev); + +struct ksz_driver_strength_prop { + const char *name; + int offset; + int value; +}; + +enum ksz_driver_strength_type { + KSZ_DRIVER_STRENGTH_HI, + KSZ_DRIVER_STRENGTH_LO, + KSZ_DRIVER_STRENGTH_IO, +}; + +/** + * struct ksz_drive_strength - drive strength mapping + * @reg_val: register value + * @microamp: microamp value + */ +struct ksz_drive_strength { + u32 reg_val; + u32 microamp; +}; + +void ksz_drive_strength_error(struct ksz_device *dev, + const struct ksz_drive_strength *array, + size_t array_size, int microamp); +int ksz_drive_strength_to_reg(const struct ksz_drive_strength *array, + size_t array_size, int microamp); +int ksz_drive_strength_write(struct ksz_device *dev, + struct ksz_driver_strength_prop *props, + int num_props); /* Common register access functions */ static inline struct regmap *ksz_regmap_8(struct ksz_device *dev) diff --git a/drivers/net/dsa/microchip/lan937x_main.c b/drivers/net/dsa/microchip/lan937x_main.c index f060fbc4c4f4..86ce3a86705f 100644 --- a/drivers/net/dsa/microchip/lan937x_main.c +++ b/drivers/net/dsa/microchip/lan937x_main.c @@ -787,10 +787,6 @@ static int lan937x_setup(struct dsa_switch *ds) return ret; } - ret = ksz_parse_drive_strength(dev); - if (ret) - return ret; - /* set broadcast storm protection 10% rate */ storm_mask = BROADCAST_STORM_RATE; storm_rate = (BROADCAST_STORM_VALUE * BROADCAST_STORM_PROT_RATE) / 100; From f6ec46b7e2b227499200fb071752ea653f145f3d Mon Sep 17 00:00:00 2001 From: Moshe Shemesh Date: Thu, 2 Jul 2026 14:17:25 +0300 Subject: [PATCH 0241/1433] devlink: print controller prefix for non-zero controller The controller prefix (c) in phys_port_name is currently restricted to external host controllers. This layout sufficed when DPUs only had a single local controller and one or more external host controllers. However, newer devices can have multiple controllers within the DPU itself, even within a single host environment. To support these topologies, allow drivers to report the controller number regardless of the "external" flag status. Any non-zero controller number will now be explicitly reported, even for single-host or local DPU controllers. Existing ports with controller=0 are unaffected. Update documentation and kdoc to clarify that a non-zero controller number does not require the external flag to be set. Signed-off-by: Moshe Shemesh Reviewed-by: Parav Pandit Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260702111726.816985-2-tariqt@nvidia.com Signed-off-by: Paolo Abeni --- Documentation/networking/devlink/devlink-port.rst | 9 +++++++++ include/net/devlink.h | 6 +++--- net/devlink/port.c | 6 +++--- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Documentation/networking/devlink/devlink-port.rst b/Documentation/networking/devlink/devlink-port.rst index 18aca77006d5..fe2cfee3e2a6 100644 --- a/Documentation/networking/devlink/devlink-port.rst +++ b/Documentation/networking/devlink/devlink-port.rst @@ -107,6 +107,15 @@ doesn't have the eswitch. Local controller (identified by controller number = 0) has the eswitch. The Devlink instance on the local controller has eswitch devlink ports for both the controllers. +A non-zero controller number may also be used for ports that are not external. +For example, a SmartNIC may have additional local PCI physical functions +that are managed by the eswitch but are not on an external host. These +ports use a non-zero controller number to distinguish them from the eswitch +manager's own functions, while the external flag remains unset. + +The ``phys_port_name`` includes the controller prefix (``c``) +whenever the controller number is non-zero, regardless of the external flag. + Function configuration ====================== diff --git a/include/net/devlink.h b/include/net/devlink.h index ffe1ad5fb70b..4830aba4087a 100644 --- a/include/net/devlink.h +++ b/include/net/devlink.h @@ -36,7 +36,7 @@ struct devlink_port_phys_attrs { * struct devlink_port_pci_pf_attrs - devlink port's PCI PF attributes * @controller: Associated controller number * @pf: associated PCI function number for the devlink port instance - * @external: when set, indicates if a port is for an external controller + * @external: when set, indicates if a port is for an external host controller. */ struct devlink_port_pci_pf_attrs { u32 controller; @@ -50,7 +50,7 @@ struct devlink_port_pci_pf_attrs { * @pf: associated PCI function number for the devlink port instance * @vf: associated PCI VF number of a PF for the devlink port instance; * VF number starts from 0 for the first PCI virtual function - * @external: when set, indicates if a port is for an external controller + * @external: when set, indicates if a port is for an external host controller. */ struct devlink_port_pci_vf_attrs { u32 controller; @@ -64,7 +64,7 @@ struct devlink_port_pci_vf_attrs { * @controller: Associated controller number * @sf: associated SF number of a PF for the devlink port instance * @pf: associated PCI function number for the devlink port instance - * @external: when set, indicates if a port is for an external controller + * @external: when set, indicates if a port is for an external host controller. */ struct devlink_port_pci_sf_attrs { u32 controller; diff --git a/net/devlink/port.c b/net/devlink/port.c index c268afefaed7..dc82cac68e7d 100644 --- a/net/devlink/port.c +++ b/net/devlink/port.c @@ -1529,7 +1529,7 @@ static int __devlink_port_phys_port_name_get(struct devlink_port *devlink_port, WARN_ON(1); return -EINVAL; case DEVLINK_PORT_FLAVOUR_PCI_PF: - if (attrs->pci_pf.external) { + if (attrs->pci_pf.external || attrs->pci_pf.controller) { n = snprintf(name, len, "c%u", attrs->pci_pf.controller); if (n >= len) return -EINVAL; @@ -1539,7 +1539,7 @@ static int __devlink_port_phys_port_name_get(struct devlink_port *devlink_port, n = snprintf(name, len, "pf%u", attrs->pci_pf.pf); break; case DEVLINK_PORT_FLAVOUR_PCI_VF: - if (attrs->pci_vf.external) { + if (attrs->pci_vf.external || attrs->pci_vf.controller) { n = snprintf(name, len, "c%u", attrs->pci_vf.controller); if (n >= len) return -EINVAL; @@ -1550,7 +1550,7 @@ static int __devlink_port_phys_port_name_get(struct devlink_port *devlink_port, attrs->pci_vf.pf, attrs->pci_vf.vf); break; case DEVLINK_PORT_FLAVOUR_PCI_SF: - if (attrs->pci_sf.external) { + if (attrs->pci_sf.external || attrs->pci_sf.controller) { n = snprintf(name, len, "c%u", attrs->pci_sf.controller); if (n >= len) return -EINVAL; From a49ea2e042af96a7f028ef0972f03589df134eba Mon Sep 17 00:00:00 2001 From: Moshe Shemesh Date: Thu, 2 Jul 2026 14:17:26 +0300 Subject: [PATCH 0242/1433] net/mlx5: Set satellite PF devlink ports as non-external Satellite PFs are local to the DPU and are not on an external host. Set their devlink port external attribute to false to reflect this. For satellite PF SFs, distinguish them from host PF SFs by comparing the SF controller number against the host PF controller (hpf_host_number + 1). Only SFs whose controller matches the host PF are marked external, since their PF resides on an external host. Signed-off-by: Moshe Shemesh Reviewed-by: Parav Pandit Reviewed-by: Shay Drori Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260702111726.816985-3-tariqt@nvidia.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c index 8c27a33f9d7b..36b00a856bc2 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c @@ -74,7 +74,7 @@ static void mlx5_esw_offloads_pf_vf_devlink_port_attrs_set(struct mlx5_eswitch * memcpy(dl_port->attrs.switch_id.id, ppid.id, ppid.id_len); dl_port->attrs.switch_id.id_len = ppid.id_len; devlink_port_attrs_pci_pf_set(dl_port, controller_num, pfnum, - true); + false); } } @@ -134,13 +134,16 @@ static void mlx5_esw_offloads_sf_devlink_port_attrs_set(struct mlx5_eswitch *esw { struct mlx5_core_dev *dev = esw->dev; struct netdev_phys_item_id ppid = {}; + u32 hpf_ctrl; u16 pfnum; pfnum = mlx5_esw_sf_controller_to_pfnum(dev, controller); + hpf_ctrl = mlx5_esw_get_hpf_host_number(dev) + 1; mlx5_esw_get_port_parent_id(dev, &ppid); memcpy(dl_port->attrs.switch_id.id, &ppid.id[0], ppid.id_len); dl_port->attrs.switch_id.id_len = ppid.id_len; - devlink_port_attrs_pci_sf_set(dl_port, controller, pfnum, sfnum, !!controller); + devlink_port_attrs_pci_sf_set(dl_port, controller, pfnum, sfnum, + controller == hpf_ctrl); } int mlx5_esw_offloads_sf_devlink_port_init(struct mlx5_eswitch *esw, struct mlx5_vport *vport, From 432f4bab1adaaa7d572bc61216aeebb04aec27a4 Mon Sep 17 00:00:00 2001 From: Nimrod Oren Date: Thu, 2 Jul 2026 09:23:47 +0300 Subject: [PATCH 0243/1433] selftests: drv-net: allow switching env IP version NetDrvEpEnv picks a single IP version at init time, preferring IPv6 when both are configured. Add NetDrvEpEnv.set_ipver() to reselect the IP version and recompute the derived address fields. Reviewed-by: Carolina Jubran Reviewed-by: Dragos Tatulea Signed-off-by: Nimrod Oren Link: https://patch.msgid.link/20260702062348.2123960-2-noren@nvidia.com Signed-off-by: Paolo Abeni --- .../selftests/drivers/net/lib/py/env.py | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py index e4ab99b905b1..e4acf3d8333f 100644 --- a/tools/testing/selftests/drivers/net/lib/py/env.py +++ b/tools/testing/selftests/drivers/net/lib/py/env.py @@ -159,13 +159,7 @@ class NetDrvEpEnv(NetDrvEnvBase): self.remote = Remote(kind, args, src_path) - self.addr_ipver = "6" if self.addr_v["6"] else "4" - self.addr = self.addr_v[self.addr_ipver] - self.remote_addr = self.remote_addr_v[self.addr_ipver] - - # Bracketed addresses, some commands need IPv6 to be inside [] - self.baddr = f"[{self.addr_v['6']}]" if self.addr_v["6"] else self.addr_v["4"] - self.remote_baddr = f"[{self.remote_addr_v['6']}]" if self.remote_addr_v["6"] else self.remote_addr_v["4"] + self.set_ipver("6" if self.addr_v["6"] else "4") self.ifname = self.dev['ifname'] self.ifindex = self.dev['ifindex'] @@ -252,6 +246,25 @@ class NetDrvEpEnv(NetDrvEnvBase): if not self.addr_v[ipver] or not self.remote_addr_v[ipver]: raise KsftSkipEx(f"Test requires IPv{ipver} connectivity") + def set_ipver(self, ipver): + """ + Modify the IP version used by the generic address fields. + """ + if ipver == getattr(self, "addr_ipver", None): + return + + self.require_ipver(ipver) + + self.addr_ipver = ipver + self.addr = self.addr_v[ipver] + self.remote_addr = self.remote_addr_v[ipver] + + # Bracketed addresses, some commands need IPv6 to be inside [] + self.baddr = (f"[{self.addr_v['6']}]" if ipver == "6" + else self.addr_v["4"]) + self.remote_baddr = (f"[{self.remote_addr_v['6']}]" if ipver == "6" + else self.remote_addr_v["4"]) + def require_nsim(self, nsim_test=True): """Require or exclude netdevsim for this test""" if nsim_test and self._ns is None: From 47467501cb88d65f5f3b6eee1cf0b6704b6c43e1 Mon Sep 17 00:00:00 2001 From: Nimrod Oren Date: Thu, 2 Jul 2026 09:23:48 +0300 Subject: [PATCH 0244/1433] selftests: drv-net: xdp: run with both IP versions Parameterize test cases by IP version using @ksft_variants. Set the environment's IP version at the start of each case using a new local helper, which also handles restoring the original value via defer(). The last case is left unparameterized because it does not send traffic or exercise IP-version-specific code. While here, fix an int vs str comparison bug `if cfg.addr_ipver == 4`. Suggested-by: Jakub Kicinski Reviewed-by: Carolina Jubran Reviewed-by: Dragos Tatulea Signed-off-by: Nimrod Oren Link: https://patch.msgid.link/20260702062348.2123960-3-noren@nvidia.com Signed-off-by: Paolo Abeni --- tools/testing/selftests/drivers/net/xdp.py | 94 ++++++++++++++++++---- 1 file changed, 77 insertions(+), 17 deletions(-) diff --git a/tools/testing/selftests/drivers/net/xdp.py b/tools/testing/selftests/drivers/net/xdp.py index 2ad5932299e8..0369929f3c51 100755 --- a/tools/testing/selftests/drivers/net/xdp.py +++ b/tools/testing/selftests/drivers/net/xdp.py @@ -172,25 +172,45 @@ def _test_pass(cfg, bpf_info, msg_sz): ksft_eq(stats[XDPStats.RX.value], stats[XDPStats.PASS.value], "RX and PASS stats mismatch") -def test_xdp_native_pass_sb(cfg): +_ipvers = [ + KsftNamedVariant("ipv4", "4"), + KsftNamedVariant("ipv6", "6"), +] + + +def _set_ipver_defer_restore(cfg, ipver): + old_ipver = cfg.addr_ipver + cfg.set_ipver(ipver) + defer(cfg.set_ipver, old_ipver) + + +@ksft_variants(_ipvers) +def test_xdp_native_pass_sb(cfg, ipver): """ Tests the XDP_PASS action for single buffer case. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500) _test_pass(cfg, bpf_info, 256) -def test_xdp_native_pass_mb(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_pass_mb(cfg, ipver): """ Tests the XDP_PASS action for a multi-buff size. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", "xdp.frags", 9000) _test_pass(cfg, bpf_info, 8000) @@ -219,25 +239,33 @@ def _test_drop(cfg, bpf_info, msg_sz): ksft_eq(stats[XDPStats.RX.value], stats[XDPStats.DROP.value], "RX and DROP stats mismatch") -def test_xdp_native_drop_sb(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_drop_sb(cfg, ipver): """ Tests the XDP_DROP action for a signle-buff case. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500) _test_drop(cfg, bpf_info, 256) -def test_xdp_native_drop_mb(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_drop_mb(cfg, ipver): """ Tests the XDP_DROP action for a multi-buff case. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", "xdp.frags", 9000) _test_drop(cfg, bpf_info, 8000) @@ -287,13 +315,17 @@ def _test_xdp_native_tx(cfg, bpf_info, payload_lens): ksft_eq(stats[XDPStats.TX.value], expected_pkts, "TX stats mismatch") -def test_xdp_native_tx_sb(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_tx_sb(cfg, ipver): """ Tests the XDP_TX action for a single-buff case. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500) # Ensure there's enough room for an ETH / IP / UDP header @@ -302,13 +334,17 @@ def test_xdp_native_tx_sb(cfg): _test_xdp_native_tx(cfg, bpf_info, [0, 1500 // 2, 1500 - pkt_hdr_len]) -def test_xdp_native_tx_mb(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_tx_mb(cfg, ipver): """ Tests the XDP_TX action for a multi-buff case. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", "xdp.frags", 9000) # The first packet ensures we exercise the fragmented code path. @@ -447,13 +483,17 @@ def _test_xdp_native_tail_adjst(cfg, pkt_sz_lst, offset_lst): return {"status": "pass"} -def test_xdp_native_adjst_tail_grow_data(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_adjst_tail_grow_data(cfg, ipver): """ Tests the XDP tail adjustment by growing packet data. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + pkt_sz_lst = [512, 1024, 2048] offset_lst = [1, 16, 32, 64, 128, 256] res = _test_xdp_native_tail_adjst( @@ -465,13 +505,17 @@ def test_xdp_native_adjst_tail_grow_data(cfg): _validate_res(res, offset_lst, pkt_sz_lst) -def test_xdp_native_adjst_tail_shrnk_data(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_adjst_tail_shrnk_data(cfg, ipver): """ Tests the XDP tail adjustment by shrinking packet data. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + pkt_sz_lst = [512, 1024, 2048] offset_lst = [-16, -32, -64, -128, -256] res = _test_xdp_native_tail_adjst( @@ -535,7 +579,7 @@ def _test_xdp_native_head_adjst(cfg, prog, pkt_sz_lst, offset_lst): # after we eat into it. We send large-enough packets, but if HDS # is enabled head will only contain headers. Don't try to eat # more than 28 bytes (UDPv4 + eth hdr left: (14 + 20 + 8) - 14) - l2_cut_off = 28 if cfg.addr_ipver == 4 else 48 + l2_cut_off = 28 if cfg.addr_ipver == "4" else 48 if pkt_sz > hds_thresh and offset > l2_cut_off: ksft_pr( f"Failed run: pkt_sz ({pkt_sz}) > HDS threshold ({hds_thresh}) and " @@ -579,18 +623,22 @@ def _test_xdp_native_head_adjst(cfg, prog, pkt_sz_lst, offset_lst): return {"status": "pass"} -def test_xdp_native_adjst_head_grow_data(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_adjst_head_grow_data(cfg, ipver): """ Tests the XDP headroom growth support. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). This function sets up the packet size and offset lists, then calls the _test_xdp_native_head_adjst_mb function to perform the actual test. The test is passed if the headroom is successfully extended for given packet sizes and offsets. """ + _set_ipver_defer_restore(cfg, ipver) + pkt_sz_lst = [512, 1024, 2048] # Negative values result in headroom shrinking, resulting in growing of payload @@ -600,18 +648,22 @@ def test_xdp_native_adjst_head_grow_data(cfg): _validate_res(res, offset_lst, pkt_sz_lst) -def test_xdp_native_adjst_head_shrnk_data(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_adjst_head_shrnk_data(cfg, ipver): """ Tests the XDP headroom shrinking support. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). This function sets up the packet size and offset lists, then calls the _test_xdp_native_head_adjst_mb function to perform the actual test. The test is passed if the headroom is successfully shrunk for given packet sizes and offsets. """ + _set_ipver_defer_restore(cfg, ipver) + pkt_sz_lst = [512, 1024, 2048] # Positive values result in headroom growing, resulting in shrinking of payload @@ -621,12 +673,19 @@ def test_xdp_native_adjst_head_shrnk_data(cfg): _validate_res(res, offset_lst, pkt_sz_lst) -@ksft_variants([ - KsftNamedVariant("pass", XDPAction.PASS), - KsftNamedVariant("drop", XDPAction.DROP), - KsftNamedVariant("tx", XDPAction.TX), -]) -def test_xdp_native_qstats(cfg, act): +def _qstats_variants(): + actions = [ + ("pass", XDPAction.PASS), + ("drop", XDPAction.DROP), + ("tx", XDPAction.TX), + ] + for ipver in ["4", "6"]: + for name, act in actions: + yield KsftNamedVariant(f"{name}_ipv{ipver}", act, ipver) + + +@ksft_variants(_qstats_variants()) +def test_xdp_native_qstats(cfg, act, ipver): """ Send 1000 messages. Expect XDP action specified in @act. Make sure the packets were counted to interface level qstats @@ -634,6 +693,7 @@ def test_xdp_native_qstats(cfg, act): """ cfg.require_cmd("socat") + _set_ipver_defer_restore(cfg, ipver) bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500) prog_info = _load_xdp_prog(cfg, bpf_info) From 155c68aef2397f8c5d72ef10acf48ae159bf1869 Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Thu, 2 Jul 2026 08:04:20 +0200 Subject: [PATCH 0245/1433] ppp/ppp_{async,synctty}: drop unused {a,}syncppp::bytes_{sent,rcvd} The bytes_sent and bytes_rcvd members of structs asyncppp and syncppp are not used. Drop them. Signed-off-by: Jiri Slaby (SUSE) Cc: Andrew Lunn Cc: "David S. Miller" Cc: Eric Dumazet Cc: Jakub Kicinski Cc: Paolo Abeni Link: https://patch.msgid.link/20260702060420.95023-1-jirislaby@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ppp/ppp_async.c | 2 -- drivers/net/ppp/ppp_synctty.c | 2 -- 2 files changed, 4 deletions(-) diff --git a/drivers/net/ppp/ppp_async.c b/drivers/net/ppp/ppp_async.c index 93a7b0f6c4e7..583426d06381 100644 --- a/drivers/net/ppp/ppp_async.c +++ b/drivers/net/ppp/ppp_async.c @@ -49,8 +49,6 @@ struct asyncppp { unsigned long xmit_flags; u32 xaccm[8]; u32 raccm; - unsigned int bytes_sent; - unsigned int bytes_rcvd; struct sk_buff *tpkt; int tpkt_pos; diff --git a/drivers/net/ppp/ppp_synctty.c b/drivers/net/ppp/ppp_synctty.c index b7f243b416f8..0b1bd1635c39 100644 --- a/drivers/net/ppp/ppp_synctty.c +++ b/drivers/net/ppp/ppp_synctty.c @@ -59,8 +59,6 @@ struct syncppp { unsigned long xmit_flags; u32 xaccm[8]; u32 raccm; - unsigned int bytes_sent; - unsigned int bytes_rcvd; struct sk_buff *tpkt; unsigned long last_xmit; From dc4d1a7615a72d8671350ea8131cd86576869ab8 Mon Sep 17 00:00:00 2001 From: Jeff Chen Date: Tue, 28 Apr 2026 22:28:48 +0800 Subject: [PATCH 0246/1433] mmc: core: add NXP IW61x base ID and block size quirk The NXP IW61x series SDIO chipset identifies itself with a base card ID (0x0204) during the initial MMC bus scan, while the specific WLAN function reports a different ID (0x0205). To ensure that the MMC_QUIRK_BLKSZ_FOR_BYTE_MODE quirk is correctly inherited by all SDIO functions (including Wi-Fi), it must be attached to the base card ID at the core level. Add the SDIO_DEVICE_ID_NXP_IW61X_BASE definition and apply the required fixup in the SDIO quirk table. Signed-off-by: Jeff Chen --- drivers/mmc/core/quirks.h | 3 +++ include/linux/mmc/sdio_ids.h | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/mmc/core/quirks.h b/drivers/mmc/core/quirks.h index 940549d3b95d..ae3ece89d0aa 100644 --- a/drivers/mmc/core/quirks.h +++ b/drivers/mmc/core/quirks.h @@ -208,6 +208,9 @@ static const struct mmc_fixup __maybe_unused sdio_fixup_methods[] = { SDIO_FIXUP(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_8887_F0, add_limit_rate_quirk, 150000000), + SDIO_FIXUP(SDIO_VENDOR_ID_NXP, SDIO_DEVICE_ID_NXP_IW61X_BASE, + add_quirk, MMC_QUIRK_BLKSZ_FOR_BYTE_MODE), + END_FIXUP }; diff --git a/include/linux/mmc/sdio_ids.h b/include/linux/mmc/sdio_ids.h index 0685dd717e85..7dac5428afe0 100644 --- a/include/linux/mmc/sdio_ids.h +++ b/include/linux/mmc/sdio_ids.h @@ -118,6 +118,7 @@ #define SDIO_DEVICE_ID_MICROCHIP_WILC1000 0x5347 #define SDIO_VENDOR_ID_NXP 0x0471 +#define SDIO_DEVICE_ID_NXP_IW61X_BASE 0x0204 #define SDIO_DEVICE_ID_NXP_IW61X 0x0205 #define SDIO_VENDOR_ID_REALTEK 0x024c From 433f482add31b8f81c79fcafa739a5e7ab71daf2 Mon Sep 17 00:00:00 2001 From: Haiyang Zhang Date: Thu, 2 Jul 2026 15:01:04 -0700 Subject: [PATCH 0247/1433] net: mana: Add Interrupt Moderation support Add Static and Dynamic Interrupt Moderation (DIM) support for Rx and Tx. Update queue creation procedure with new data struct with the related settings. Add functions to collect stat for DIM, and workers to update DIM data and settings. Update ethtool handler to get/set the moderation settings from a user. To avoid detach/re-attach ops, ring DIM doorbell to change settings at run time. By default, adaptive-rx/tx (DIM) are enabled if supported by HW. Signed-off-by: Haiyang Zhang Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260702220123.815018-1-haiyangz@linux.microsoft.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/microsoft/Kconfig | 1 + .../net/ethernet/microsoft/mana/gdma_main.c | 29 +++ drivers/net/ethernet/microsoft/mana/mana_en.c | 175 ++++++++++++++++++ .../ethernet/microsoft/mana/mana_ethtool.c | 167 ++++++++++++++++- include/net/mana/gdma.h | 24 ++- include/net/mana/mana.h | 54 ++++++ 6 files changed, 441 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/microsoft/Kconfig b/drivers/net/ethernet/microsoft/Kconfig index 3f36ee6a8ece..e9be18c92ca5 100644 --- a/drivers/net/ethernet/microsoft/Kconfig +++ b/drivers/net/ethernet/microsoft/Kconfig @@ -21,6 +21,7 @@ config MICROSOFT_MANA depends on X86_64 || (ARM64 && !CPU_BIG_ENDIAN) depends on PCI_HYPERV select AUXILIARY_BUS + select DIMLIB select PAGE_POOL select NET_SHAPER help diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c index e8b7ffb47eb9..aef3b77229c1 100644 --- a/drivers/net/ethernet/microsoft/mana/gdma_main.c +++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* Copyright (c) 2021, Microsoft Corporation. */ +#include #include #include #include @@ -466,6 +467,7 @@ static int mana_gd_disable_queue(struct gdma_queue *queue) #define DOORBELL_OFFSET_RQ 0x400 #define DOORBELL_OFFSET_CQ 0x800 #define DOORBELL_OFFSET_EQ 0xFF8 +#define DOORBELL_OFFSET_DIM 0x820 static void mana_gd_ring_doorbell(struct gdma_context *gc, u32 db_index, enum gdma_queue_type q_type, u32 qid, @@ -506,6 +508,16 @@ static void mana_gd_ring_doorbell(struct gdma_context *gc, u32 db_index, addr += DOORBELL_OFFSET_SQ; break; + case GDMA_DIM: + e.dim.id = qid; + e.dim.mod_usec = FIELD_GET(MANA_INTR_MODR_USEC_MAX, tail_ptr); + e.dim.mod_usec_vld = !!(tail_ptr & MANA_INTR_MODR_USEC_VLD); + e.dim.mod_comps = FIELD_GET(MANA_INTR_MODR_COMP_MASK, tail_ptr); + e.dim.mod_comps_vld = num_req; + + addr += DOORBELL_OFFSET_DIM; + break; + default: WARN_ON(1); return; @@ -540,6 +552,23 @@ void mana_gd_ring_cq(struct gdma_queue *cq, u8 arm_bit) } EXPORT_SYMBOL_NS(mana_gd_ring_cq, "NET_MANA"); +void mana_gd_ring_dim(struct gdma_queue *cq, u32 mod_usec, bool mod_usec_vld, + u32 mod_comps, bool mod_comps_vld) +{ + struct gdma_context *gc = cq->gdma_dev->gdma_context; + u32 dim_val; + + /* Convert the DIM values to doorbell parameters */ + dim_val = FIELD_PREP(MANA_INTR_MODR_USEC_MAX, mod_usec) | + FIELD_PREP(MANA_INTR_MODR_COMP_MASK, mod_comps); + if (mod_usec_vld) + dim_val |= MANA_INTR_MODR_USEC_VLD; + + mana_gd_ring_doorbell(gc, cq->gdma_dev->doorbell, GDMA_DIM, cq->id, + dim_val, mod_comps_vld); +} +EXPORT_SYMBOL_NS(mana_gd_ring_dim, "NET_MANA"); + #define MANA_SERVICE_PERIOD 10 static void mana_serv_rescan(struct pci_dev *pdev) diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index 7438ea6b3f26..5ce0b96c50f6 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -1591,6 +1591,15 @@ int mana_create_wq_obj(struct mana_port_context *apc, mana_gd_init_req_hdr(&req.hdr, MANA_CREATE_WQ_OBJ, sizeof(req), sizeof(resp)); + + /* Our driver uses different message versions for request and + * response in this case. + * Our firmware is forward compatible with newer message versions, so + * the old firmware still properly handles this message, just the new + * feature fields are ignored, and queue creation will be successful. + */ + req.hdr.req.msg_version = GDMA_MESSAGE_V3; + req.hdr.resp.msg_version = GDMA_MESSAGE_V2; req.vport = vport; req.wq_type = wq_type; req.wq_gdma_region = wq_spec->gdma_region; @@ -1599,6 +1608,9 @@ int mana_create_wq_obj(struct mana_port_context *apc, req.cq_size = cq_spec->queue_size; req.cq_moderation_ctx_id = cq_spec->modr_ctx_id; req.cq_parent_qid = cq_spec->attached_eq; + req.req_cq_moderation = cq_spec->req_cq_moderation; + req.cq_moderation_comp = cq_spec->cq_moderation_comp; + req.cq_moderation_usec = cq_spec->cq_moderation_usec; err = mana_send_request(apc->ac, &req, sizeof(req), &resp, sizeof(resp)); @@ -1856,6 +1868,7 @@ static void mana_poll_tx_cq(struct mana_cq *cq) struct gdma_posted_wqe_info *wqe_info; unsigned int pkt_transmitted = 0; unsigned int wqe_unit_cnt = 0; + unsigned int tx_bytes = 0; struct mana_txq *txq = cq->txq; struct mana_port_context *apc; struct netdev_queue *net_txq; @@ -1937,6 +1950,8 @@ static void mana_poll_tx_cq(struct mana_cq *cq) mana_unmap_skb(skb, apc); + tx_bytes += skb->len; + napi_consume_skb(skb, cq->budget); pkt_transmitted++; @@ -1967,6 +1982,10 @@ static void mana_poll_tx_cq(struct mana_cq *cq) if (atomic_sub_return(pkt_transmitted, &txq->pending_sends) < 0) WARN_ON_ONCE(1); + /* Feed DIM with the completion rate observed here, in NAPI context. */ + cq->tx_dim_pkts += pkt_transmitted; + cq->tx_dim_bytes += tx_bytes; + cq->work_done = pkt_transmitted; } @@ -2318,6 +2337,117 @@ static void mana_poll_rx_cq(struct mana_cq *cq) xdp_do_flush(); } +static void mana_rx_dim_work(struct work_struct *work) +{ + struct dim *dim = container_of(work, struct dim, work); + struct dim_cq_moder cur_moder; + struct mana_cq *cq; + + cur_moder = net_dim_get_rx_moderation(dim->mode, dim->profile_ix); + cq = container_of(dim, struct mana_cq, dim); + + cur_moder.usec = min_t(u16, cur_moder.usec, MANA_INTR_MODR_USEC_MAX); + cur_moder.pkts = min_t(u16, cur_moder.pkts, MANA_INTR_MODR_COMP_MAX); + + mana_gd_ring_dim(cq->gdma_cq, cur_moder.usec, true, + cur_moder.pkts, true); + + dim->state = DIM_START_MEASURE; +} + +static void mana_tx_dim_work(struct work_struct *work) +{ + struct dim *dim = container_of(work, struct dim, work); + struct dim_cq_moder cur_moder; + struct mana_cq *cq; + + cur_moder = net_dim_get_tx_moderation(dim->mode, dim->profile_ix); + cq = container_of(dim, struct mana_cq, dim); + + cur_moder.usec = min_t(u16, cur_moder.usec, MANA_INTR_MODR_USEC_MAX); + cur_moder.pkts = min_t(u16, cur_moder.pkts, MANA_INTR_MODR_COMP_MAX); + + mana_gd_ring_dim(cq->gdma_cq, cur_moder.usec, true, + cur_moder.pkts, true); + + dim->state = DIM_START_MEASURE; +} + +/* The caller must update apc->rx/tx_dim_enabled before disabling and + * after enabling. And synchronize_net() before draining the DIM work, + * so that NAPI cannot observe a stale flag. + */ +void mana_dim_change(struct mana_cq *cq, bool enable) +{ + bool is_rx = cq->type == MANA_CQ_TYPE_RX; + struct mana_port_context *apc; + work_func_t work_func; + u32 usec, comp; + + if (is_rx) { + apc = netdev_priv(cq->rxq->ndev); + usec = apc->intr_modr_rx_usec; + comp = apc->intr_modr_rx_comp; + work_func = mana_rx_dim_work; + } else { + apc = netdev_priv(cq->txq->ndev); + usec = apc->intr_modr_tx_usec; + comp = apc->intr_modr_tx_comp; + work_func = mana_tx_dim_work; + } + + /* On enable, zero the DIM state so net_dim() starts measuring from + * scratch. + * On disable, drain any pending DIM work and restore the static + * moderation values. + */ + if (enable) { + memset(&cq->dim, 0, sizeof(cq->dim)); + cq->dim.mode = DIM_CQ_PERIOD_MODE_START_FROM_EQE; + INIT_WORK(&cq->dim.work, work_func); + } else { + cancel_work_sync(&cq->dim.work); + mana_gd_ring_dim(cq->gdma_cq, usec, true, comp, true); + } +} + +static void mana_update_rx_dim(struct mana_cq *cq) +{ + struct mana_port_context *apc = netdev_priv(cq->rxq->ndev); + struct dim_sample dim_sample = {}; + struct mana_rxq *rxq = cq->rxq; + + /* Pairs with smp_store_release() in mana_set_coalesce(): observing the + * enable flag set guarantees the DIM (re)initialization is visible. + */ + if (!smp_load_acquire(&apc->rx_dim_enabled)) + return; + + dim_update_sample(READ_ONCE(cq->dim_event_ctr), rxq->stats.packets, + rxq->stats.bytes, &dim_sample); + net_dim(&cq->dim, &dim_sample); +} + +static void mana_update_tx_dim(struct mana_cq *cq) +{ + struct mana_port_context *apc = netdev_priv(cq->txq->ndev); + struct dim_sample dim_sample = {}; + + /* Pairs with smp_store_release() in mana_set_coalesce(): observing the + * enable flag set guarantees the DIM (re)initialization is visible. + */ + if (!smp_load_acquire(&apc->tx_dim_enabled)) + return; + + /* cq->tx_dim_pkts/bytes are accumulated in mana_poll_tx_cq(), in the + * same NAPI context as this read, so they track the hardware + * completion rate and need no u64_stats_sync protection. + */ + dim_update_sample(READ_ONCE(cq->dim_event_ctr), cq->tx_dim_pkts, + cq->tx_dim_bytes, &dim_sample); + net_dim(&cq->dim, &dim_sample); +} + static int mana_cq_handler(void *context, struct gdma_queue *gdma_queue) { struct mana_cq *cq = context; @@ -2336,6 +2466,15 @@ static int mana_cq_handler(void *context, struct gdma_queue *gdma_queue) if (w < cq->budget) { mana_gd_ring_cq(gdma_queue, SET_ARM_BIT); cq->work_done_since_doorbell = 0; + + /* Update DIM before napi_complete_done() to prevent running + * net_dim() concurrently. + */ + if (cq->type == MANA_CQ_TYPE_RX) + mana_update_rx_dim(cq); + else + mana_update_tx_dim(cq); + napi_complete_done(&cq->napi, w); } else if (cq->work_done_since_doorbell >= (cq->gdma_cq->queue_size / COMP_ENTRY_SIZE) * 4) { @@ -2368,6 +2507,7 @@ static void mana_schedule_napi(void *context, struct gdma_queue *gdma_queue) { struct mana_cq *cq = context; + WRITE_ONCE(cq->dim_event_ctr, cq->dim_event_ctr + 1); napi_schedule_irqoff(&cq->napi); } @@ -2410,6 +2550,7 @@ static void mana_destroy_txq(struct mana_port_context *apc) if (apc->tx_qp[i]->txq.napi_initialized) { napi_synchronize(napi); napi_disable_locked(napi); + cancel_work_sync(&apc->tx_qp[i]->tx_cq.dim.work); netif_napi_del_locked(napi); apc->tx_qp[i]->txq.napi_initialized = false; } @@ -2543,6 +2684,11 @@ static int mana_create_txq(struct mana_port_context *apc, cq_spec.modr_ctx_id = 0; cq_spec.attached_eq = cq->gdma_cq->cq.parent->id; + /* DIM setting can be changed at runtime */ + cq_spec.req_cq_moderation = true; + cq_spec.cq_moderation_usec = apc->intr_modr_tx_usec; + cq_spec.cq_moderation_comp = apc->intr_modr_tx_comp; + err = mana_create_wq_obj(apc, apc->port_handle, GDMA_SQ, &wq_spec, &cq_spec, &apc->tx_qp[i]->tx_object); @@ -2573,6 +2719,13 @@ static int mana_create_txq(struct mana_port_context *apc, set_bit(NAPI_STATE_NO_BUSY_POLL, &cq->napi.state); netif_napi_add_locked(net, &cq->napi, mana_poll); + + /* Initialize the DIM work before enabling NAPI, so that a poll + * cannot reach net_dim() with an uninitialized cq->dim.work. + */ + INIT_WORK(&cq->dim.work, mana_tx_dim_work); + cq->dim.mode = DIM_CQ_PERIOD_MODE_START_FROM_EQE; + napi_enable_locked(&cq->napi); txq->napi_initialized = true; @@ -2610,6 +2763,7 @@ static void mana_destroy_rxq(struct mana_port_context *apc, napi_synchronize(napi); napi_disable_locked(napi); + cancel_work_sync(&rxq->rx_cq.dim.work); netif_napi_del_locked(napi); } @@ -2848,6 +3002,11 @@ static struct mana_rxq *mana_create_rxq(struct mana_port_context *apc, cq_spec.modr_ctx_id = 0; cq_spec.attached_eq = cq->gdma_cq->cq.parent->id; + /* DIM setting can be changed at runtime */ + cq_spec.req_cq_moderation = true; + cq_spec.cq_moderation_usec = apc->intr_modr_rx_usec; + cq_spec.cq_moderation_comp = apc->intr_modr_rx_comp; + err = mana_create_wq_obj(apc, apc->port_handle, GDMA_RQ, &wq_spec, &cq_spec, &rxq->rxobj); if (err) @@ -2880,6 +3039,12 @@ static struct mana_rxq *mana_create_rxq(struct mana_port_context *apc, WARN_ON(xdp_rxq_info_reg_mem_model(&rxq->xdp_rxq, MEM_TYPE_PAGE_POOL, rxq->page_pool)); + /* Initialize the DIM work before enabling NAPI, so that a poll + * cannot reach net_dim() with an uninitialized cq->dim.work. + */ + INIT_WORK(&cq->dim.work, mana_rx_dim_work); + cq->dim.mode = DIM_CQ_PERIOD_MODE_START_FROM_EQE; + napi_enable_locked(&cq->napi); mana_gd_ring_cq(cq->gdma_cq, SET_ARM_BIT); @@ -3546,6 +3711,16 @@ static int mana_probe_port(struct mana_context *ac, int port_idx, apc->link_cfg_error = 1; apc->cqe_coalescing_enable = 0; + /* Initialize interrupt moderation settings if supported by HW */ + if (gc->pf_cap_flags1 & GDMA_PF_CAP_FLAG_1_DYN_INTERRUPT_MODERATION) { + apc->intr_modr_rx_usec = MANA_INTR_MODR_USEC_DEF; + apc->intr_modr_rx_comp = MANA_INTR_MODR_COMP_DEF; + apc->intr_modr_tx_usec = MANA_INTR_MODR_USEC_DEF; + apc->intr_modr_tx_comp = MANA_INTR_MODR_COMP_DEF; + apc->rx_dim_enabled = MANA_ADAPTIVE_RX_DEF; + apc->tx_dim_enabled = MANA_ADAPTIVE_TX_DEF; + } + mutex_init(&apc->vport_mutex); apc->vport_use_count = 0; diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c index 881df597d7f9..9e31e2595ae3 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c +++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c @@ -419,6 +419,15 @@ static int mana_get_coalesce(struct net_device *ndev, !kernel_coal->rx_cqe_nsecs) kernel_coal->rx_cqe_nsecs = MANA_RX_CQE_NSEC_DEF; + ec->rx_coalesce_usecs = apc->intr_modr_rx_usec; + ec->rx_max_coalesced_frames = apc->intr_modr_rx_comp; + + ec->tx_coalesce_usecs = apc->intr_modr_tx_usec; + ec->tx_max_coalesced_frames = apc->intr_modr_tx_comp; + + ec->use_adaptive_rx_coalesce = apc->rx_dim_enabled; + ec->use_adaptive_tx_coalesce = apc->tx_dim_enabled; + return 0; } @@ -428,9 +437,34 @@ static int mana_set_coalesce(struct net_device *ndev, struct netlink_ext_ack *extack) { struct mana_port_context *apc = netdev_priv(ndev); - u8 saved_cqe_coalescing_enable; + struct { + u16 intr_modr_rx_usec; + u16 intr_modr_rx_comp; + u16 intr_modr_tx_usec; + u16 intr_modr_tx_comp; + u8 cqe_coalescing_enable; + bool rx_dim_enabled; + bool tx_dim_enabled; + } saved; + bool modr_changed = false; + bool dim_changed = false; + struct gdma_context *gc; int err; + gc = apc->ac->gdma_dev->gdma_context; + + /* Both static and dynamic interrupt moderation (DIM) rely on the + * same HW capability advertised by the PF. + */ + if ((ec->use_adaptive_rx_coalesce || ec->use_adaptive_tx_coalesce || + ec->rx_coalesce_usecs || ec->tx_coalesce_usecs || + ec->rx_max_coalesced_frames || ec->tx_max_coalesced_frames) && + !(gc->pf_cap_flags1 & GDMA_PF_CAP_FLAG_1_DYN_INTERRUPT_MODERATION)) { + NL_SET_ERR_MSG(extack, + "Interrupt Moderation is not supported by HW"); + return -EOPNOTSUPP; + } + if (kernel_coal->rx_cqe_frames != 1 && kernel_coal->rx_cqe_frames != MANA_RXCOMP_OOB_NUM_PPI) { NL_SET_ERR_MSG_FMT(extack, @@ -440,18 +474,129 @@ static int mana_set_coalesce(struct net_device *ndev, return -EINVAL; } - saved_cqe_coalescing_enable = apc->cqe_coalescing_enable; + if (ec->rx_coalesce_usecs > MANA_INTR_MODR_USEC_MAX || + ec->tx_coalesce_usecs > MANA_INTR_MODR_USEC_MAX) { + NL_SET_ERR_MSG_FMT(extack, + "coalesce usecs must be <= %lu", + MANA_INTR_MODR_USEC_MAX); + return -EINVAL; + } + + if (ec->rx_max_coalesced_frames > MANA_INTR_MODR_COMP_MAX || + ec->tx_max_coalesced_frames > MANA_INTR_MODR_COMP_MAX) { + NL_SET_ERR_MSG_FMT(extack, + "coalesce frames must be <= %lu", + MANA_INTR_MODR_COMP_MAX); + return -EINVAL; + } + + if (ec->rx_coalesce_usecs != apc->intr_modr_rx_usec || + ec->rx_max_coalesced_frames != apc->intr_modr_rx_comp || + ec->tx_coalesce_usecs != apc->intr_modr_tx_usec || + ec->tx_max_coalesced_frames != apc->intr_modr_tx_comp) + modr_changed = true; + + saved.intr_modr_rx_usec = apc->intr_modr_rx_usec; + saved.intr_modr_rx_comp = apc->intr_modr_rx_comp; + saved.intr_modr_tx_usec = apc->intr_modr_tx_usec; + saved.intr_modr_tx_comp = apc->intr_modr_tx_comp; + + apc->intr_modr_rx_usec = ec->rx_coalesce_usecs; + apc->intr_modr_rx_comp = ec->rx_max_coalesced_frames; + apc->intr_modr_tx_usec = ec->tx_coalesce_usecs; + apc->intr_modr_tx_comp = ec->tx_max_coalesced_frames; + + if (!!ec->use_adaptive_rx_coalesce != apc->rx_dim_enabled || + !!ec->use_adaptive_tx_coalesce != apc->tx_dim_enabled) + dim_changed = true; + + saved.rx_dim_enabled = apc->rx_dim_enabled; + saved.tx_dim_enabled = apc->tx_dim_enabled; + + saved.cqe_coalescing_enable = apc->cqe_coalescing_enable; apc->cqe_coalescing_enable = kernel_coal->rx_cqe_frames == MANA_RXCOMP_OOB_NUM_PPI; - if (!apc->port_is_up) + if (!apc->port_is_up) { + WRITE_ONCE(apc->rx_dim_enabled, !!ec->use_adaptive_rx_coalesce); + WRITE_ONCE(apc->tx_dim_enabled, !!ec->use_adaptive_tx_coalesce); return 0; + } - err = mana_config_rss(apc, TRI_STATE_TRUE, false, false); - if (err) - apc->cqe_coalescing_enable = saved_cqe_coalescing_enable; + if (apc->cqe_coalescing_enable != saved.cqe_coalescing_enable) { + /* CQE coalescing setting is applied via RSS configuration. */ + err = mana_config_rss(apc, TRI_STATE_TRUE, false, false); + if (err) { + netdev_err(ndev, "Change CQE coalescing failed: %d\n", + err); + apc->cqe_coalescing_enable = + saved.cqe_coalescing_enable; + apc->intr_modr_rx_usec = saved.intr_modr_rx_usec; + apc->intr_modr_rx_comp = saved.intr_modr_rx_comp; + apc->intr_modr_tx_usec = saved.intr_modr_tx_usec; + apc->intr_modr_tx_comp = saved.intr_modr_tx_comp; + return err; + } + } - return err; + if (modr_changed || dim_changed) { + bool new_rx_dim = !!ec->use_adaptive_rx_coalesce; + bool new_tx_dim = !!ec->use_adaptive_tx_coalesce; + bool disable_rx_dim = saved.rx_dim_enabled && !new_rx_dim; + bool disable_tx_dim = saved.tx_dim_enabled && !new_tx_dim; + bool enable_rx_dim = !saved.rx_dim_enabled && new_rx_dim; + bool enable_tx_dim = !saved.tx_dim_enabled && new_tx_dim; + int q; + + /* On disable: clear the per-port flag first and + * synchronize_net() so any in-flight NAPI poll observes + * the new value and will not schedule further DIM work; + * then drain pending work and restore the static + * moderation values. + */ + if (disable_rx_dim) + WRITE_ONCE(apc->rx_dim_enabled, false); + if (disable_tx_dim) + WRITE_ONCE(apc->tx_dim_enabled, false); + if (disable_rx_dim || disable_tx_dim) + synchronize_net(); + + for (q = 0; q < apc->num_queues; q++) { + struct mana_cq *rx_cq = &apc->rxqs[q]->rx_cq; + struct mana_cq *tx_cq = &apc->tx_qp[q]->tx_cq; + + if (disable_rx_dim) + mana_dim_change(rx_cq, false); + else if (enable_rx_dim) + mana_dim_change(rx_cq, true); + else if (!new_rx_dim && modr_changed) + mana_gd_ring_dim(rx_cq->gdma_cq, + apc->intr_modr_rx_usec, true, + apc->intr_modr_rx_comp, true); + + if (disable_tx_dim) + mana_dim_change(tx_cq, false); + else if (enable_tx_dim) + mana_dim_change(tx_cq, true); + else if (!new_tx_dim && modr_changed) + mana_gd_ring_dim(tx_cq->gdma_cq, + apc->intr_modr_tx_usec, true, + apc->intr_modr_tx_comp, true); + } + + /* Publish the enable flag with release semantics so a + * concurrent NAPI poll that observes it set also sees the DIM + * (re)init done by mana_dim_change() above. + */ + if (enable_rx_dim) + /* pairs with smp_load_acquire() in mana_update_rx_dim() */ + smp_store_release(&apc->rx_dim_enabled, true); + if (enable_tx_dim) + /* pairs with smp_load_acquire() in mana_update_tx_dim() */ + smp_store_release(&apc->tx_dim_enabled, true); + } + + return 0; } /* mana_set_channels - change the number of queues on a port @@ -595,7 +740,13 @@ static int mana_get_link_ksettings(struct net_device *ndev, } const struct ethtool_ops mana_ethtool_ops = { - .supported_coalesce_params = ETHTOOL_COALESCE_RX_CQE_FRAMES, + .supported_coalesce_params = ETHTOOL_COALESCE_RX_CQE_FRAMES | + ETHTOOL_COALESCE_RX_USECS | + ETHTOOL_COALESCE_RX_MAX_FRAMES | + ETHTOOL_COALESCE_TX_USECS | + ETHTOOL_COALESCE_TX_MAX_FRAMES | + ETHTOOL_COALESCE_USE_ADAPTIVE_RX | + ETHTOOL_COALESCE_USE_ADAPTIVE_TX, .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM | ETHTOOL_OP_NEEDS_RTNL_GLINK, diff --git a/include/net/mana/gdma.h b/include/net/mana/gdma.h index 0c395917b214..8529cef0d7c4 100644 --- a/include/net/mana/gdma.h +++ b/include/net/mana/gdma.h @@ -47,6 +47,7 @@ enum gdma_queue_type { GDMA_RQ, GDMA_CQ, GDMA_EQ, + GDMA_DIM, }; enum gdma_work_request_flags { @@ -126,6 +127,17 @@ union gdma_doorbell_entry { u64 tail_ptr : 31; u64 arm : 1; } eq; + + struct { + u64 id : 24; + u64 reserved : 8; + u64 mod_usec : 10; + u64 reserve1 : 5; + u64 mod_usec_vld : 1; + u64 mod_comps : 8; + u64 reserve2 : 7; + u64 mod_comps_vld: 1; + } dim; }; /* HW DATA */ struct gdma_msg_hdr { @@ -502,6 +514,9 @@ void mana_gd_ring_cq(struct gdma_queue *cq, u8 arm_bit); int mana_schedule_serv_work(struct gdma_context *gc, enum gdma_eqe_type type); +void mana_gd_ring_dim(struct gdma_queue *cq, u32 mod_usec, bool mod_usec_vld, + u32 mod_comps, bool mod_comps_vld); + struct gdma_wqe { u32 reserved :24; u32 last_vbytes :8; @@ -650,6 +665,9 @@ enum { /* Driver supports self recovery on Hardware Channel timeouts */ #define GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECOVERY BIT(25) +/* Driver supports dynamic interrupt moderation - DIM */ +#define GDMA_DRV_CAP_FLAG_1_DYN_INTERRUPT_MODERATION BIT(28) + #define GDMA_DRV_CAP_FLAGS1 \ (GDMA_DRV_CAP_FLAG_1_EQ_SHARING_MULTI_VPORT | \ GDMA_DRV_CAP_FLAG_1_NAPI_WKDONE_FIX | \ @@ -665,7 +683,8 @@ enum { GDMA_DRV_CAP_FLAG_1_PROBE_RECOVERY | \ GDMA_DRV_CAP_FLAG_1_HANDLE_STALL_SQ_RECOVERY | \ GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECOVERY | \ - GDMA_DRV_CAP_FLAG_1_EQ_MSI_UNSHARE_MULTI_VPORT) + GDMA_DRV_CAP_FLAG_1_EQ_MSI_UNSHARE_MULTI_VPORT | \ + GDMA_DRV_CAP_FLAG_1_DYN_INTERRUPT_MODERATION) #define GDMA_DRV_CAP_FLAGS2 0 @@ -701,6 +720,9 @@ struct gdma_verify_ver_req { u8 os_ver_str4[128]; }; /* HW DATA */ +/* HW supports dynamic interrupt moderation - DIM */ +#define GDMA_PF_CAP_FLAG_1_DYN_INTERRUPT_MODERATION BIT(15) + struct gdma_verify_ver_resp { struct gdma_resp_hdr hdr; u64 gdma_protocol_ver; diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h index 13c87baf018e..48f4445aa87a 100644 --- a/include/net/mana/mana.h +++ b/include/net/mana/mana.h @@ -4,6 +4,7 @@ #ifndef _MANA_H #define _MANA_H +#include #include #include @@ -64,6 +65,19 @@ enum TRI_STATE { /* Maximum number of packets per coalesced CQE */ #define MANA_RXCOMP_OOB_NUM_PPI 4 +/* Default/max interrupt moderation settings */ +#define MANA_INTR_MODR_USEC_DEF 0 +#define MANA_INTR_MODR_COMP_DEF 0 + +#define MANA_ADAPTIVE_RX_DEF true +#define MANA_ADAPTIVE_TX_DEF true + +/* DIM doorbell value field layout */ +#define MANA_INTR_MODR_USEC_MAX GENMASK(9, 0) +#define MANA_INTR_MODR_USEC_VLD BIT(15) +#define MANA_INTR_MODR_COMP_MAX GENMASK(7, 0) +#define MANA_INTR_MODR_COMP_MASK GENMASK(23, 16) + /* Update this count whenever the respective structures are changed */ #define MANA_STATS_RX_COUNT (6 + MANA_RXCOMP_OOB_NUM_PPI - 1) #define MANA_STATS_TX_COUNT 11 @@ -297,6 +311,17 @@ struct mana_cq { int work_done; int work_done_since_doorbell; int budget; + + /* DIM - Dynamic Interrupt Moderation */ + struct dim dim; + u16 dim_event_ctr; + + /* Cumulative TX completions fed to DIM. Updated and read only in + * NAPI context (mana_poll_tx_cq() / mana_update_tx_dim()), so they + * measure the hardware completion rate and need no u64_stats_sync. + */ + u64 tx_dim_pkts; + u64 tx_dim_bytes; }; struct mana_recv_buf_oob { @@ -573,6 +598,15 @@ struct mana_port_context { u8 cqe_coalescing_enable; u32 cqe_coalescing_timeout_ns; + /* Interrupt moderation settings */ + u16 intr_modr_rx_usec; + u16 intr_modr_rx_comp; + u16 intr_modr_tx_usec; + u16 intr_modr_tx_comp; + + bool rx_dim_enabled; + bool tx_dim_enabled; + struct mana_ethtool_stats eth_stats; struct mana_ethtool_phy_stats phy_stats; @@ -598,6 +632,8 @@ int mana_alloc_queues(struct net_device *ndev); int mana_attach(struct net_device *ndev); int mana_detach(struct net_device *ndev, bool from_close); +void mana_dim_change(struct mana_cq *cq, bool enable); + int mana_probe(struct gdma_dev *gd, bool resuming); void mana_remove(struct gdma_dev *gd, bool suspending); @@ -633,6 +669,9 @@ struct mana_obj_spec { u32 queue_size; u32 attached_eq; u32 modr_ctx_id; + u8 req_cq_moderation; + u16 cq_moderation_comp; + u16 cq_moderation_usec; }; enum mana_command_code { @@ -764,6 +803,15 @@ struct mana_create_wqobj_req { u32 cq_size; u32 cq_moderation_ctx_id; u32 cq_parent_qid; + + /* V2 */ + u8 allow_rqwqe_chain; + + /* V3 */ + u8 req_cq_moderation; + u16 cq_moderation_comp; + u16 cq_moderation_usec; + u8 reserved2[2]; }; /* HW DATA */ struct mana_create_wqobj_resp { @@ -771,6 +819,12 @@ struct mana_create_wqobj_resp { u32 wq_id; u32 cq_id; mana_handle_t wq_obj; + + /* V2 */ + u16 cq_moderation_comp; + u16 cq_moderation_usec; + u8 cq_moderation_enabled; + u8 reserved1[3]; }; /* HW DATA */ /* Destroy WQ Object */ From 6d86ce0da0d5631721c142ea9bb5499cc129b347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Mon, 6 Jul 2026 11:29:19 +0200 Subject: [PATCH 0248/1433] net: phy: Drop #inclusion of from MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit itself doesn't use any of the device id structures. The files #including use a variety of them: $ git grep -l '' | xargs grep --color -E '\<(acpi_device_id|amba_id|ap_device_id|apr_device_id|auxiliary_device_id|bcma_device_id|ccw_device_id|cdx_device_id|coreboot_device_id|css_device_id|dfl_device_id|dmi_(device|system)_id|eisa_device_id|fsl_mc_device_id|hda_device_id|hid_device_id|hv_vmbus_device_id|i2c_device_id|i3c_device_id|ieee1394_device_id|input_device_id|ipack_device_id|isapnp_device_id|ishtp_device_id|mcb_device_id|mdio_device_id|mei_cl_device_id|mhi_device_id|mips_cdmm_device_id|of_device_id|parisc_device_id|pci_device_id|pci_epf_device_id|pcmcia_device_id|platform_device_id|pnp_(card_)?device_id|rio_device_id|rpmsg_device_id|sdio_device_id|sdw_device_id|serio_device_id|slim_device_id|spi_device_id|spmi_device_id|ssam_device_id|ssb_device_id|tb_service_id|tee_client_device_id|typec_device_id|ulpi_device_id|usb_device_id|vchiq_device_id|virtio_device_id|wmi_device_id|x86_(cpu|device)_id|zorro_device_id|cpu_feature)\>' ... but none of them relies on 's #include. is a bad header mixing many different device id structures and thus is an unfortunate dependency because if e.g. struct wmi_device_id is modified all users of need to be recompiled despite none of them using struct wmi_device_id. So drop the unused header. Signed-off-by: Uwe Kleine-König (The Capable Hub) Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/ca270a534d0f230a939a3fb4a661808b35d6436d.1783329817.git.u.kleine-koenig@baylibre.com Signed-off-by: Paolo Abeni --- include/linux/mdio.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/linux/mdio.h b/include/linux/mdio.h index f4f9d9609448..300805e66592 100644 --- a/include/linux/mdio.h +++ b/include/linux/mdio.h @@ -8,7 +8,6 @@ #include #include -#include struct gpio_desc; struct mii_bus; From 0e74441edefce4cae60f572ca7da70fa9eabc168 Mon Sep 17 00:00:00 2001 From: Paritosh Potukuchi Date: Fri, 3 Jul 2026 12:55:12 +0000 Subject: [PATCH 0249/1433] net : bonding : Remove TODO comment about retrying setting the MAC As correctly pointed out by Jay Vosburgh, "This comment dates to sometime before git, when it was common for network device drivers to lack the ability to change the MAC while the interface is up. To the best of my knowledge, that isn't a issue today." Based on the discussion in the RFC linked below, I am removing the TODO. Link to the RFC: https://lore.kernel.org/netdev/2001256.1782860341@famine/T/#t Signed-off-by: Paritosh Potukuchi Link: https://patch.msgid.link/20260703125513.694324-1-paritosh.potukuchi@amd.com Signed-off-by: Paolo Abeni --- drivers/net/bonding/bond_main.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index e044fc733b8c..e1bec8164221 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -4857,12 +4857,6 @@ static int bond_set_mac_address(struct net_device *bond_dev, void *addr) __func__, slave); res = dev_set_mac_address(slave->dev, addr, NULL); if (res) { - /* TODO: consider downing the slave - * and retry ? - * User should expect communications - * breakage anyway until ARP finish - * updating, so... - */ slave_dbg(bond_dev, slave->dev, "%s: err %d\n", __func__, res); goto unwind; From fe3e786ef4eb6e47d2901f568a27bd920477bbe9 Mon Sep 17 00:00:00 2001 From: Zinc Lim Date: Thu, 2 Jul 2026 09:49:31 -0700 Subject: [PATCH 0250/1433] selftests: drv-net: rss_ctx: Add retries to test_rss_context_overlap to reduce flakes Similar to commit 690043b95c18 ("selftests: drv-net: rss: Add retries to test_rss_key_indir to reduce flakes"), implement the retry mechanism for test_rss_context_overlap. This gives the test more attempts to distribute the flow evenly, as the chance of flow skewing to one queue is high. Example failures: # Check failed 5288 < 7000 traffic on main context (1/2): [2727, 2561, 8961, 6648] not ok 1 rss_ctx.test_rss_context_overlap # Check failed 6710 < 7000 traffic on main context (2/2): [9280, 5217, 5358, 1352] not ok 1 rss_ctx.test_rss_context_overlap Ran test_rss_context_overlap and test_rss_context_overlap2 over 1,000 consecutive runs with no failures. Signed-off-by: Zinc Lim Link: https://patch.msgid.link/20260702164932.2832916-1-limzhineng2@gmail.com Signed-off-by: Paolo Abeni --- tools/testing/selftests/drivers/net/hw/rss_ctx.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/drivers/net/hw/rss_ctx.py b/tools/testing/selftests/drivers/net/hw/rss_ctx.py index f36f76d6ca59..5b25fa89c629 100755 --- a/tools/testing/selftests/drivers/net/hw/rss_ctx.py +++ b/tools/testing/selftests/drivers/net/hw/rss_ctx.py @@ -651,9 +651,14 @@ def test_rss_context_overlap(cfg, other_ctx=0): ntuple = defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}") # Test the main context - cnts = _get_rx_cnts(cfg) - GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000) - cnts = _get_rx_cnts(cfg, prev=cnts) + attempts = 3 + for attempt in range(attempts): + cnts = _get_rx_cnts(cfg) + GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000) + cnts = _get_rx_cnts(cfg, prev=cnts) + if sum(cnts[:2]) >= 7000 and sum(cnts[2:4]) >= 7000: + break + ksft_pr(f"Skewed queue distribution, attempt {attempt + 1}/{attempts}: " + str(cnts)) ksft_ge(sum(cnts[ :4]), 20000, "traffic on main context: " + str(cnts)) ksft_ge(sum(cnts[ :2]), 7000, "traffic on main context (1/2): " + str(cnts)) From 832255a6df491b6bfa41e6458d097f269b680d77 Mon Sep 17 00:00:00 2001 From: Praveen Rajendran Date: Fri, 3 Jul 2026 20:01:30 +0530 Subject: [PATCH 0251/1433] net: qed: Fix spelling typo in qed_dcbx.c comment Correct a minor spelling error inside a comment block of the QLogic Core module where "successfully" was misspelled as "successfuly". Signed-off-by: Praveen Rajendran Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260703143130.3685-1-praveenrajendran2009@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/qlogic/qed/qed_dcbx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/qlogic/qed/qed_dcbx.c b/drivers/net/ethernet/qlogic/qed/qed_dcbx.c index 3a5c25026858..19c2b870feed 100644 --- a/drivers/net/ethernet/qlogic/qed/qed_dcbx.c +++ b/drivers/net/ethernet/qlogic/qed/qed_dcbx.c @@ -639,7 +639,7 @@ qed_dcbx_get_operational_params(struct qed_hwfn *p_hwfn, flags = p_hwfn->p_dcbx_info->operational.flags; /* If DCBx version is non zero, then negotiation - * was successfuly performed + * was successfully performed */ p_operational = ¶ms->operational; enabled = !!(QED_MFW_GET_FIELD(flags, DCBX_CONFIG_VERSION) != From d5d7554052f3ac45c75d8e526ded2a70a7593929 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sat, 4 Jul 2026 14:10:11 +0200 Subject: [PATCH 0252/1433] net: chelsio: cxgb4: Use str_plural() in mem_intr_handler() Replace the manual ternary "s" pluralization with str_plural() to simplify the code. Signed-off-by: Thorsten Blum Reviewed-by: Potnuri Bharat Teja Link: https://patch.msgid.link/20260704121010.201016-3-thorsten.blum@linux.dev Signed-off-by: Paolo Abeni --- drivers/net/ethernet/chelsio/cxgb4/t4_hw.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c b/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c index 6871127427fa..29d7b786e51e 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c +++ b/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c @@ -33,6 +33,7 @@ */ #include +#include #include "cxgb4.h" #include "t4_regs.h" #include "t4_values.h" @@ -4867,7 +4868,7 @@ static void mem_intr_handler(struct adapter *adapter, int idx) if (printk_ratelimit()) dev_warn(adapter->pdev_dev, "%u %s correctable ECC data error%s\n", - cnt, name[idx], cnt > 1 ? "s" : ""); + cnt, name[idx], str_plural(cnt)); } if (v & ECC_UE_INT_CAUSE_F) dev_alert(adapter->pdev_dev, From 23dad2d088dfc82cae1f5a936f8ff7ffebb38dd9 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 6 Jul 2026 16:35:17 +0000 Subject: [PATCH 0253/1433] tun: no longer rely on RTNL in tun_fill_info() Update tun_fill_info() to read device configuration fields (flags, owner, group, numqueues, numdisabled) locklessly using READ_ONCE(). Annotate all writes to these fields in the control paths with WRITE_ONCE() to prevent data races, as these fields can be modified concurrently via ioctls (TUNSETPERSIST, TUNSETOWNER, TUNSETGROUP, TUNSETIFF) or queue attaching/detaching. Signed-off-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260706163517.2415530-1-edumazet@google.com Signed-off-by: Paolo Abeni --- drivers/net/tun.c | 60 ++++++++++++++++++++++-------------------- drivers/net/tun_vnet.h | 8 +++--- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/drivers/net/tun.c b/drivers/net/tun.c index ffbe6f13fb1f..9e2761887896 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -532,7 +532,7 @@ static void tun_disable_queue(struct tun_struct *tun, struct tun_file *tfile) { tfile->detached = tun; list_add_tail(&tfile->next, &tun->disabled); - ++tun->numdisabled; + WRITE_ONCE(tun->numdisabled, tun->numdisabled + 1); } static struct tun_struct *tun_enable_queue(struct tun_file *tfile) @@ -541,7 +541,7 @@ static struct tun_struct *tun_enable_queue(struct tun_file *tfile) tfile->detached = NULL; list_del_init(&tfile->next); - --tun->numdisabled; + WRITE_ONCE(tun->numdisabled, tun->numdisabled - 1); return tun; } @@ -600,7 +600,7 @@ static void __tun_detach(struct tun_file *tfile, bool clean) rcu_assign_pointer(tun->tfiles[tun->numqueues - 1], NULL); - --tun->numqueues; + WRITE_ONCE(tun->numqueues, tun->numqueues - 1); if (clean) { RCU_INIT_POINTER(tfile->tun, NULL); sock_put(&tfile->sk); @@ -663,7 +663,7 @@ static void tun_detach_all(struct net_device *dev) tfile->socket.sk->sk_shutdown = RCV_SHUTDOWN; tfile->socket.sk->sk_data_ready(tfile->socket.sk); RCU_INIT_POINTER(tfile->tun, NULL); - --tun->numqueues; + WRITE_ONCE(tun->numqueues, tun->numqueues - 1); } list_for_each_entry(tfile, &tun->disabled, next) { tfile->socket.sk->sk_shutdown = RCV_SHUTDOWN; @@ -786,7 +786,7 @@ static int tun_attach(struct tun_struct *tun, struct file *file, if (publish_tun) rcu_assign_pointer(tfile->tun, tun); rcu_assign_pointer(tun->tfiles[tun->numqueues], tfile); - tun->numqueues++; + WRITE_ONCE(tun->numqueues, tun->numqueues + 1); tun_set_real_num_queues(tun); out: return err; @@ -2370,32 +2370,36 @@ static size_t tun_get_size(const struct net_device *dev) static int tun_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct tun_struct *tun = netdev_priv(dev); + const struct tun_struct *tun = netdev_priv(dev); + unsigned int flags = READ_ONCE(tun->flags); + kuid_t owner = READ_ONCE(tun->owner); + kgid_t group = READ_ONCE(tun->group); - if (nla_put_u8(skb, IFLA_TUN_TYPE, tun->flags & TUN_TYPE_MASK)) + if (nla_put_u8(skb, IFLA_TUN_TYPE, flags & TUN_TYPE_MASK)) goto nla_put_failure; - if (uid_valid(tun->owner) && + if (uid_valid(owner) && nla_put_u32(skb, IFLA_TUN_OWNER, - from_kuid_munged(current_user_ns(), tun->owner))) + from_kuid_munged(current_user_ns(), owner))) goto nla_put_failure; - if (gid_valid(tun->group) && + if (gid_valid(group) && nla_put_u32(skb, IFLA_TUN_GROUP, - from_kgid_munged(current_user_ns(), tun->group))) + from_kgid_munged(current_user_ns(), group))) goto nla_put_failure; - if (nla_put_u8(skb, IFLA_TUN_PI, !(tun->flags & IFF_NO_PI))) + if (nla_put_u8(skb, IFLA_TUN_PI, !(flags & IFF_NO_PI))) goto nla_put_failure; - if (nla_put_u8(skb, IFLA_TUN_VNET_HDR, !!(tun->flags & IFF_VNET_HDR))) + if (nla_put_u8(skb, IFLA_TUN_VNET_HDR, !!(flags & IFF_VNET_HDR))) goto nla_put_failure; - if (nla_put_u8(skb, IFLA_TUN_PERSIST, !!(tun->flags & IFF_PERSIST))) + if (nla_put_u8(skb, IFLA_TUN_PERSIST, !!(flags & IFF_PERSIST))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_TUN_MULTI_QUEUE, - !!(tun->flags & IFF_MULTI_QUEUE))) + !!(flags & IFF_MULTI_QUEUE))) goto nla_put_failure; - if (tun->flags & IFF_MULTI_QUEUE) { - if (nla_put_u32(skb, IFLA_TUN_NUM_QUEUES, tun->numqueues)) + if (flags & IFF_MULTI_QUEUE) { + if (nla_put_u32(skb, IFLA_TUN_NUM_QUEUES, + READ_ONCE(tun->numqueues))) goto nla_put_failure; if (nla_put_u32(skb, IFLA_TUN_NUM_DISABLED_QUEUES, - tun->numdisabled)) + READ_ONCE(tun->numdisabled))) goto nla_put_failure; } @@ -2814,8 +2818,8 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) return 0; } - tun->flags = (tun->flags & ~TUN_FEATURES) | - (ifr->ifr_flags & TUN_FEATURES); + WRITE_ONCE(tun->flags, (tun->flags & ~TUN_FEATURES) | + (ifr->ifr_flags & TUN_FEATURES)); netdev_state_change(dev); } else { @@ -3213,13 +3217,13 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd, /* Disable/Enable persist mode. Keep an extra reference to the * module to prevent the module being unprobed. */ - if (arg && !(tun->flags & IFF_PERSIST)) { - tun->flags |= IFF_PERSIST; + if (arg && !(READ_ONCE(tun->flags) & IFF_PERSIST)) { + WRITE_ONCE(tun->flags, READ_ONCE(tun->flags) | IFF_PERSIST); __module_get(THIS_MODULE); do_notify = true; } - if (!arg && (tun->flags & IFF_PERSIST)) { - tun->flags &= ~IFF_PERSIST; + if (!arg && (READ_ONCE(tun->flags) & IFF_PERSIST)) { + WRITE_ONCE(tun->flags, READ_ONCE(tun->flags) & ~IFF_PERSIST); module_put(THIS_MODULE); do_notify = true; } @@ -3235,10 +3239,10 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd, ret = -EINVAL; break; } - tun->owner = owner; + WRITE_ONCE(tun->owner, owner); do_notify = true; netif_info(tun, drv, tun->dev, "owner set to %u\n", - from_kuid(&init_user_ns, tun->owner)); + from_kuid(&init_user_ns, owner)); break; case TUNSETGROUP: @@ -3248,10 +3252,10 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd, ret = -EINVAL; break; } - tun->group = group; + WRITE_ONCE(tun->group, group); do_notify = true; netif_info(tun, drv, tun->dev, "group set to %u\n", - from_kgid(&init_user_ns, tun->group)); + from_kgid(&init_user_ns, group)); break; case TUNSETLINK: diff --git a/drivers/net/tun_vnet.h b/drivers/net/tun_vnet.h index fa5cab9d3e55..f4c652b1fa44 100644 --- a/drivers/net/tun_vnet.h +++ b/drivers/net/tun_vnet.h @@ -40,9 +40,9 @@ static inline long tun_set_vnet_be(unsigned int *flags, int __user *argp) return -EFAULT; if (be) - *flags |= TUN_VNET_BE; + WRITE_ONCE(*flags, *flags | TUN_VNET_BE); else - *flags &= ~TUN_VNET_BE; + WRITE_ONCE(*flags, *flags & ~TUN_VNET_BE); return 0; } @@ -93,9 +93,9 @@ static inline long tun_vnet_ioctl(int *vnet_hdr_sz, unsigned int *flags, if (get_user(s, sp)) return -EFAULT; if (s) - *flags |= TUN_VNET_LE; + WRITE_ONCE(*flags, *flags | TUN_VNET_LE); else - *flags &= ~TUN_VNET_LE; + WRITE_ONCE(*flags, *flags & ~TUN_VNET_LE); return 0; case TUNGETVNETBE: From 5ecbbb179e0c737eb1360a877508766efb16f010 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 3 Jul 2026 00:09:12 +0000 Subject: [PATCH 0254/1433] rtnetlink: Lock sock_net(skb->sk) in rtnl_newlink(). There are a few cases where rtnl_net_lock() is not properly held in rtnl_newlink(). When either of IFLA_NET_NS_PID / IFLA_NET_NS_FD / IFLA_TARGET_NETNSID is specified but IFLA_LINK_NETNSID is not, sock_net(skb->sk) is used as link_net in rtnl_newlink_link_net(). In addition, the do_setlink() path uses sock_net(skb->sk) and one from the three netns attributes while rtnl_link_get_net_capable() returns only one of four. Let's add sock_net(skb->sk) to rtnl_nets in rtnl_newlink(). Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260703001009.1572444-2-kuniyu@google.com Signed-off-by: Paolo Abeni --- net/core/rtnetlink.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index 1b7d6f6b8b68..a5811b427680 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -282,10 +282,11 @@ static int rtnl_net_cmp_locks(const struct net *net_a, const struct net *net_b) #endif struct rtnl_nets { - /* ->newlink() needs to freeze 3 netns at most; - * 2 for the new device, 1 for its peer. + /* ->newlink() needs to freeze 4 netns at most; + * 2 for the new device, 1 for its peer, 1 for + * an existing device (do_setlink() path). */ - struct net *net[3]; + struct net *net[4]; unsigned char len; }; @@ -4158,6 +4159,8 @@ static int rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh, } } + rtnl_nets_add(&rtnl_nets, get_net(sock_net(skb->sk))); + rtnl_nets_lock(&rtnl_nets); ret = __rtnl_newlink(skb, nlh, ops, tgt_net, link_net, peer_net, tbs, data, extack); rtnl_nets_unlock(&rtnl_nets); From 49c26d4bf1daa8ada5a2015646adbf7b1b716b8b Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 3 Jul 2026 00:09:13 +0000 Subject: [PATCH 0255/1433] rtnetlink: Call unregister_netdevice_many() only once in rtnl_link_unregister(). When rtnl_link_unregister() is called during module unload, it calls __rtnl_kill_links() for every netns. __rtnl_kill_links() collects all devices of the unloaded module and passes them to unregister_netdevice_many(). Let's move unregister_netdevice_many() to rtnl_link_unregister() to unregister all devices across netns in a single batch. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260703001009.1572444-3-kuniyu@google.com Signed-off-by: Paolo Abeni --- net/core/rtnetlink.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index a5811b427680..c4528f5349a3 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -637,16 +637,15 @@ int rtnl_link_register(struct rtnl_link_ops *ops) } EXPORT_SYMBOL_GPL(rtnl_link_register); -static void __rtnl_kill_links(struct net *net, struct rtnl_link_ops *ops) +static void __rtnl_kill_links(struct net *net, struct rtnl_link_ops *ops, + struct list_head *dev_kill_list) { struct net_device *dev; - LIST_HEAD(list_kill); for_each_netdev(net, dev) { if (dev->rtnl_link_ops == ops) - ops->dellink(dev, &list_kill); + ops->dellink(dev, dev_kill_list); } - unregister_netdevice_many(&list_kill); } /* Return with the rtnl_lock held when there are no network @@ -677,6 +676,7 @@ static void rtnl_lock_unregistering_all(void) */ void rtnl_link_unregister(struct rtnl_link_ops *ops) { + LIST_HEAD(dev_kill_list); struct net *net; mutex_lock(&link_ops_mutex); @@ -691,7 +691,9 @@ void rtnl_link_unregister(struct rtnl_link_ops *ops) rtnl_lock_unregistering_all(); for_each_net(net) - __rtnl_kill_links(net, ops); + __rtnl_kill_links(net, ops, &dev_kill_list); + + unregister_netdevice_many(&dev_kill_list); rtnl_unlock(); up_write(&pernet_ops_rwsem); From c6cfaf97837e18d50e65185d36b4a9d860f62a4a Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 3 Jul 2026 00:09:14 +0000 Subject: [PATCH 0256/1433] rtnetlink: Add per-netns rtnl_work. The biggest blocker to per-netns RTNL is netdev unregistration. It starts within a single netns (e.g., during a device lookup or netns dismantle), but it can eventually involve multiple namespaces, such as when upper ipvlan devices reside in different netns. This prevents us from acquiring multiple rtnl_net_lock()s beforehand. When we encounter such a cross-netns device, we must delegate the unregistration to the work of the netns where the device actually resides. Let's add per-netns rtnl_work to support the deferred netdev unregistration. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260703001009.1572444-4-kuniyu@google.com Signed-off-by: Paolo Abeni --- include/linux/rtnetlink.h | 8 ++++++++ include/net/net_namespace.h | 1 + net/core/net_namespace.c | 1 + net/core/rtnetlink.c | 26 ++++++++++++++++++++++++++ 4 files changed, 36 insertions(+) diff --git a/include/linux/rtnetlink.h b/include/linux/rtnetlink.h index ea39dd23a197..95729339e7a5 100644 --- a/include/linux/rtnetlink.h +++ b/include/linux/rtnetlink.h @@ -115,6 +115,10 @@ bool rtnl_net_is_locked(struct net *net); bool lockdep_rtnl_net_is_held(struct net *net); +void rtnl_net_queue_work(struct net *net); +void rtnl_net_flush_workqueue(void); +void rtnl_net_work_func(struct work_struct *work); + #define rcu_dereference_rtnl_net(net, p) \ rcu_dereference_check(p, lockdep_rtnl_net_is_held(net)) #define rtnl_net_dereference(net, p) \ @@ -150,6 +154,10 @@ static inline void ASSERT_RTNL_NET(struct net *net) ASSERT_RTNL(); } +static inline void rtnl_net_flush_workqueue(void) +{ +} + #define rcu_dereference_rtnl_net(net, p) \ rcu_dereference_rtnl(p) #define rtnl_net_dereference(net, p) \ diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h index 80de5e98a66d..a989019af5f7 100644 --- a/include/net/net_namespace.h +++ b/include/net/net_namespace.h @@ -197,6 +197,7 @@ struct net { #ifdef CONFIG_DEBUG_NET_SMALL_RTNL /* Move to a better place when the config guard is removed. */ struct mutex rtnl_mutex; + struct work_struct rtnl_work; #endif #if IS_ENABLED(CONFIG_VSOCKETS) struct netns_vsock vsock; diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c index d9dafe24f57e..d1aeff9de580 100644 --- a/net/core/net_namespace.c +++ b/net/core/net_namespace.c @@ -422,6 +422,7 @@ static __net_init int preinit_net(struct net *net, struct user_namespace *user_n #ifdef CONFIG_DEBUG_NET_SMALL_RTNL mutex_init(&net->rtnl_mutex); lock_set_cmp_fn(&net->rtnl_mutex, rtnl_net_lock_cmp_fn, NULL); + INIT_WORK(&net->rtnl_work, rtnl_net_work_func); #endif INIT_LIST_HEAD(&net->ptype_all); diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index c4528f5349a3..608908b3feec 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -273,6 +273,26 @@ bool lockdep_rtnl_net_is_held(struct net *net) return lockdep_rtnl_is_held() && lockdep_is_held(&net->rtnl_mutex); } EXPORT_SYMBOL(lockdep_rtnl_net_is_held); + +static struct workqueue_struct *rtnl_net_wq; + +void rtnl_net_queue_work(struct net *net) +{ + queue_work(rtnl_net_wq, &net->rtnl_work); +} + +void rtnl_net_flush_workqueue(void) +{ + flush_workqueue(rtnl_net_wq); +} + +void rtnl_net_work_func(struct work_struct *work) +{ + struct net *net = container_of(work, struct net, rtnl_work); + + rtnl_net_lock(net); + rtnl_net_unlock(net); +} #else static int rtnl_net_cmp_locks(const struct net *net_a, const struct net *net_b) { @@ -7229,4 +7249,10 @@ void __init rtnetlink_init(void) register_netdevice_notifier(&rtnetlink_dev_notifier); rtnl_register_many(rtnetlink_rtnl_msg_handlers); + +#ifdef CONFIG_DEBUG_NET_SMALL_RTNL + rtnl_net_wq = create_workqueue("rtnl_net"); + if (!rtnl_net_wq) + panic("Could not create rtnl_net workq"); +#endif } From 2b12ec25784954134df6cf0972a28f14dffdad50 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 3 Jul 2026 00:09:15 +0000 Subject: [PATCH 0257/1433] net: Wrap default_device_exit_net() with __rtnl_net_lock(). default_device_exit_net() could call dev_change_net_namespace() to move devices from a dying netns to init_net. Let's hold the two netns __rtnl_net_lock() around it. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260703001009.1572444-5-kuniyu@google.com Signed-off-by: Paolo Abeni --- net/core/dev.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/net/core/dev.c b/net/core/dev.c index 714d05283500..9db053b355ef 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -13039,7 +13039,7 @@ static void __net_exit default_device_exit_net(struct net *net) * Push all migratable network devices back to the * initial network namespace */ - ASSERT_RTNL(); + for_each_netdev_safe(net, dev, aux) { int err; char fb_name[IFNAMSIZ]; @@ -13082,11 +13082,19 @@ static void __net_exit default_device_exit_batch(struct list_head *net_list) LIST_HEAD(dev_kill_list); rtnl_lock(); + + __rtnl_net_lock(&init_net); + list_for_each_entry(net, net_list, exit_list) { + __rtnl_net_lock(net); default_device_exit_net(net); + __rtnl_net_unlock(net); + cond_resched(); } + __rtnl_net_unlock(&init_net); + list_for_each_entry(net, net_list, exit_list) { for_each_netdev_reverse(net, dev) { if (dev->rtnl_link_ops && dev->rtnl_link_ops->dellink) From 0fa296dd520eb388e18b97dc553926f82a09cc1d Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 3 Jul 2026 00:09:16 +0000 Subject: [PATCH 0258/1433] net: Hold __rtnl_net_lock() in netdev_wait_allrefs_any(). Currently, netdev_run_todo() processes pending devices from multiple namespaces in a batch. To expand the per-netns RTNL coverage for NETDEV_UNREGISTER, let's acquire __rtnl_net_lock() in netdev_wait_allrefs_any(). Note that netdev_run_todo() itself will need to be namespacified before RTNL is removed. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260703001009.1572444-6-kuniyu@google.com Signed-off-by: Paolo Abeni --- net/core/dev.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/net/core/dev.c b/net/core/dev.c index 9db053b355ef..cc77f41452ec 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -11613,8 +11613,13 @@ static struct net_device *netdev_wait_allrefs_any(struct list_head *list) rtnl_lock(); /* Rebroadcast unregister notification */ - list_for_each_entry(dev, list, todo_list) + list_for_each_entry(dev, list, todo_list) { + struct net *net = dev_net(dev); + + __rtnl_net_lock(net); call_netdevice_notifiers(NETDEV_UNREGISTER, dev); + __rtnl_net_unlock(net); + } __rtnl_unlock(); rcu_barrier(); From af3634d4ac652cd93cb97dd2ad2f01536c2cebc7 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 3 Jul 2026 00:09:17 +0000 Subject: [PATCH 0259/1433] net: Add per-netns netdev unregistration infra. When we need to unregister a netdev in a different netns, we will delegate its unregistration to per-netns work. There are three types of such cross-netns devices: 1. Paired devices (e.g., netkit, veth, vxcan) -> Unregistering one device also deletes its peer, which may reside in another netns. 2. Tunnel devices (e.g., bareudp, geneve, etc) -> Destroying a netns removes devices in another netns if their backend sockets reside in the dying netns 3. Stacked devices (e.g., ipvlan, macvlan, etc) -> Removing the lower device also removes multiple upper devices, each of which may reside in different namespaces. In these cases, we will use unregister_netdevice_queue_net() to queue such potential cross-netns devices for destruction. Each driver must not call both unregister_netdevice_queue_net() and unregister_netdevice_queue() for the same device. See the subsequent veth/bareudp/ipvlan patches for how they avoid double queueing. unregister_netdevice_queue_net() takes net and dev. If dev resides in the net, it simply calls unregister_netdevice_queue(). If dev_net(dev) is different from the net, it enqueues the device to dev_net(dev)->dev_unreg_head and schedules the per-netns work. When __rtnl_net_unlock() is called from the per-netns work (or another thread already holding the lock), unregister_netdevice_many_net() collects the queued devices and calls unregister_netdevice_many() to perform the actual unregistration. During netns dismantle, rtnl_net_flush_workqueue() is called at the end of default_device_exit_batch() to ensure that cross-netns devices in the other alive netns are unregistered. Once RTNL is removed, a device could be moved to another netns while being queued to net->dev_unreg_head. __dev_change_net_namespace() handles this race by acquiring net->dev_unreg_lock of both the old and new netns after dev_set_net() and moving the device between their dev_unreg_head lists. Since dev_set_net() and unregister_netdevice_queue_net() are synchronised by netdev_lock(), the device is either queued to the old netns's dev_unreg_head and then moved, or queued directly to the new netns. Note that unregister_netdevice_move_net() does not need to call rtnl_net_queue_work() because __dev_change_net_namespace() is (supposed to be) called with rtnl_net_lock(). (Not all callers hold it yet, but the race does not happen until all callers are converted and RTNL is removed.) Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260703001009.1572444-7-kuniyu@google.com Signed-off-by: Paolo Abeni --- include/linux/netdevice.h | 18 ++++++++ include/net/net_namespace.h | 2 + net/core/dev.c | 91 +++++++++++++++++++++++++++++++++++++ net/core/net_namespace.c | 2 + net/core/rtnetlink.c | 4 ++ 5 files changed, 117 insertions(+) diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 9981d637f8b5..108d8d7ea75b 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -1845,6 +1845,8 @@ enum netdev_reg_state { * @napi_list: List entry used for polling NAPI devices * @unreg_list: List entry when we are unregistering the * device; see the function unregister_netdev + * @unreg_list_net:List entry when we are unregistering the cross-netns + * device; see the function unregister_netdevice_queue_net() * @close_list: List entry used when we are closing the device * @ptype_all: Device-specific packet handlers for all protocols * @ptype_specific: Device-specific, protocol-specific packet handlers @@ -2241,6 +2243,9 @@ struct net_device { struct list_head dev_list; struct list_head napi_list; struct list_head unreg_list; +#ifdef CONFIG_DEBUG_NET_SMALL_RTNL + struct list_head unreg_list_net; +#endif struct list_head close_list; struct list_head ptype_all; @@ -3472,6 +3477,19 @@ static inline void unregister_netdevice(struct net_device *dev) unregister_netdevice_queue(dev, NULL); } +#ifdef CONFIG_DEBUG_NET_SMALL_RTNL +void unregister_netdevice_queue_net(struct net *net, struct net_device *dev, + struct list_head *head); +void unregister_netdevice_many_net(struct net *net); +#else +static inline void unregister_netdevice_queue_net(struct net *net, + struct net_device *dev, + struct list_head *head) +{ + unregister_netdevice_queue(dev, head); +} +#endif + int netdev_refcnt_read(const struct net_device *dev); void free_netdev(struct net_device *dev); diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h index a989019af5f7..501af1999fe8 100644 --- a/include/net/net_namespace.h +++ b/include/net/net_namespace.h @@ -198,6 +198,8 @@ struct net { /* Move to a better place when the config guard is removed. */ struct mutex rtnl_mutex; struct work_struct rtnl_work; + struct list_head dev_unreg_head; + spinlock_t dev_unreg_lock; #endif #if IS_ENABLED(CONFIG_VSOCKETS) struct netns_vsock vsock; diff --git a/net/core/dev.c b/net/core/dev.c index cc77f41452ec..7ec9af75d143 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -12097,6 +12097,9 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name, INIT_LIST_HEAD(&dev->napi_list); INIT_LIST_HEAD(&dev->unreg_list); +#ifdef CONFIG_DEBUG_NET_SMALL_RTNL + INIT_LIST_HEAD(&dev->unreg_list_net); +#endif INIT_LIST_HEAD(&dev->close_list); INIT_LIST_HEAD(&dev->link_watch_list); INIT_LIST_HEAD(&dev->adj_list.upper); @@ -12314,6 +12317,10 @@ void unregister_netdevice_queue(struct net_device *dev, struct list_head *head) { ASSERT_RTNL(); +#ifdef CONFIG_DEBUG_NET_SMALL_RTNL + DEBUG_NET_WARN_ON_ONCE(!list_empty(&dev->unreg_list_net)); +#endif + if (head) { list_move_tail(&dev->unreg_list, head); } else { @@ -12490,6 +12497,16 @@ void unregister_netdevice_many_notify(struct list_head *head, synchronize_net(); list_for_each_entry(dev, head, unreg_list) { +#ifdef CONFIG_DEBUG_NET_SMALL_RTNL + struct net *net = dev_net(dev); + + /* spin_lock() can be moved outside of the loop + * once the per-netns RTNL conversion completes. + */ + spin_lock(&net->dev_unreg_lock); + list_del(&dev->unreg_list_net); + spin_unlock(&net->dev_unreg_lock); +#endif netdev_put(dev, &dev->dev_registered_tracker); net_set_todo(dev); cnt++; @@ -12512,6 +12529,74 @@ void unregister_netdevice_many(struct list_head *head) } EXPORT_SYMBOL(unregister_netdevice_many); +#ifdef CONFIG_DEBUG_NET_SMALL_RTNL +void unregister_netdevice_queue_net(struct net *net, struct net_device *dev, + struct list_head *head) +{ + netdev_lock(dev); + + if (net_eq(dev_net(dev), net)) { + netdev_unlock(dev); + unregister_netdevice_queue(dev, head); + return; + } + + net = dev_net(dev); + + spin_lock(&net->dev_unreg_lock); + + DEBUG_NET_WARN_ON_ONCE(!list_empty(&dev->unreg_list)); + DEBUG_NET_WARN_ON_ONCE(!list_empty(&dev->unreg_list_net)); + + list_add_tail(&dev->unreg_list_net, &net->dev_unreg_head); + rtnl_net_queue_work(net); + + spin_unlock(&net->dev_unreg_lock); + + netdev_unlock(dev); +} +EXPORT_SYMBOL(unregister_netdevice_queue_net); + +static void unregister_netdevice_move_net(struct net *net_old, + struct net *net, + struct net_device *dev) +{ + if (net_old > net) { + spin_lock(&net->dev_unreg_lock); + spin_lock_nested(&net_old->dev_unreg_lock, SINGLE_DEPTH_NESTING); + } else { + spin_lock(&net_old->dev_unreg_lock); + spin_lock_nested(&net->dev_unreg_lock, SINGLE_DEPTH_NESTING); + } + + if (!list_empty(&dev->unreg_list_net)) { + list_del(&dev->unreg_list_net); + list_add_tail(&dev->unreg_list_net, &net->dev_unreg_head); + } + + spin_unlock(&net_old->dev_unreg_lock); + spin_unlock(&net->dev_unreg_lock); +} + +void unregister_netdevice_many_net(struct net *net) +{ + struct net_device *dev, *tmp; + LIST_HEAD(unreg_head_net); + LIST_HEAD(unreg_head); + + spin_lock(&net->dev_unreg_lock); + list_splice_init(&net->dev_unreg_head, &unreg_head_net); + spin_unlock(&net->dev_unreg_lock); + + list_for_each_entry_safe(dev, tmp, &unreg_head_net, unreg_list_net) { + list_del_init(&dev->unreg_list_net); + list_add_tail(&dev->unreg_list, &unreg_head); + } + + unregister_netdevice_many(&unreg_head); +} +#endif + /** * unregister_netdev - remove device from the kernel * @dev: device @@ -12668,6 +12753,10 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net, netdev_unlock(dev); dev->ifindex = new_ifindex; +#ifdef CONFIG_DEBUG_NET_SMALL_RTNL + unregister_netdevice_move_net(net_old, net, dev); +#endif + if (new_name[0]) { /* Rename the netdev to prepared name */ write_seqlock_bh(&netdev_rename_lock); @@ -13110,6 +13199,8 @@ static void __net_exit default_device_exit_batch(struct list_head *net_list) } unregister_netdevice_many(&dev_kill_list); rtnl_unlock(); + + rtnl_net_flush_workqueue(); } static struct pernet_operations __net_initdata default_device_ops = { diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c index d1aeff9de580..578b48cf5318 100644 --- a/net/core/net_namespace.c +++ b/net/core/net_namespace.c @@ -423,6 +423,8 @@ static __net_init int preinit_net(struct net *net, struct user_namespace *user_n mutex_init(&net->rtnl_mutex); lock_set_cmp_fn(&net->rtnl_mutex, rtnl_net_lock_cmp_fn, NULL); INIT_WORK(&net->rtnl_work, rtnl_net_work_func); + INIT_LIST_HEAD(&net->dev_unreg_head); + spin_lock_init(&net->dev_unreg_lock); #endif INIT_LIST_HEAD(&net->ptype_all); diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index 608908b3feec..cf2b7697a6e7 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -197,6 +197,7 @@ void __rtnl_net_unlock(struct net *net) { ASSERT_RTNL(); + unregister_netdevice_many_net(net); mutex_unlock(&net->rtnl_mutex); } EXPORT_SYMBOL(__rtnl_net_unlock); @@ -290,6 +291,9 @@ void rtnl_net_work_func(struct work_struct *work) { struct net *net = container_of(work, struct net, rtnl_work); + if (list_empty(&net->dev_unreg_head)) + return; + rtnl_net_lock(net); rtnl_net_unlock(net); } From d0008553a70a306c10ca2a596f449971d0579d50 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 3 Jul 2026 00:09:18 +0000 Subject: [PATCH 0260/1433] net: Call unregister_netdevice_many() per netns. For per-netns device unregistration, the list passed to unregister_netdevice_many() must contain devices from a single netns only (once all callers are converted). Let's move collected devices in the following functions to net->dev_unreg_head and let __rtnl_net_unlock() pass them to unregister_netdevice_many(). * default_device_exit_batch() * ops_exit_rtnl_list() * __rtnl_kill_links() This allows incremental conversion of each driver to support per-netns device unregistration without affecting the normal kernel where CONFIG_DEBUG_NET_SMALL_RTNL is disabled. Note that this change unbatches synchronize_rcu() etc in unregister_netdevice_many(), but we can later split it into multiple stages to batch them again. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260703001009.1572444-8-kuniyu@google.com Signed-off-by: Paolo Abeni --- include/linux/netdevice.h | 6 ++++++ net/core/dev.c | 27 +++++++++++++++++++++++++++ net/core/net_namespace.c | 1 + net/core/rtnetlink.c | 6 +++++- 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 108d8d7ea75b..8db25b79573e 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -3481,6 +3481,7 @@ static inline void unregister_netdevice(struct net_device *dev) void unregister_netdevice_queue_net(struct net *net, struct net_device *dev, struct list_head *head); void unregister_netdevice_many_net(struct net *net); +void unregister_netdevice_queue_many_net(struct net *net, struct list_head *head); #else static inline void unregister_netdevice_queue_net(struct net *net, struct net_device *dev, @@ -3488,6 +3489,11 @@ static inline void unregister_netdevice_queue_net(struct net *net, { unregister_netdevice_queue(dev, head); } + +static inline void unregister_netdevice_queue_many_net(struct net *net, + struct list_head *head) +{ +} #endif int netdev_refcnt_read(const struct net_device *dev); diff --git a/net/core/dev.c b/net/core/dev.c index 7ec9af75d143..7c21bc0a1e34 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -12557,6 +12557,28 @@ void unregister_netdevice_queue_net(struct net *net, struct net_device *dev, } EXPORT_SYMBOL(unregister_netdevice_queue_net); +void unregister_netdevice_queue_many_net(struct net *net, struct list_head *head) +{ + struct net_device *dev, *tmp; + + spin_lock(&net->dev_unreg_lock); + list_for_each_entry_safe(dev, tmp, head, unreg_list) { + /* Once all cross-netns unregister_netdevice_queue() is + * converted to _net() (or for debugging), remove this check. + */ + if (!net_eq(dev_net(dev), net)) + continue; + + DEBUG_NET_WARN_ONCE(!net_eq(dev_net(dev), net), + "%s was unregistered from a different netns.\n", + dev->name); + + list_del_init(&dev->unreg_list); + list_move_tail(&dev->unreg_list_net, &net->dev_unreg_head); + } + spin_unlock(&net->dev_unreg_lock); +} + static void unregister_netdevice_move_net(struct net *net_old, struct net *net, struct net_device *dev) @@ -13190,12 +13212,17 @@ static void __net_exit default_device_exit_batch(struct list_head *net_list) __rtnl_net_unlock(&init_net); list_for_each_entry(net, net_list, exit_list) { + __rtnl_net_lock(net); + for_each_netdev_reverse(net, dev) { if (dev->rtnl_link_ops && dev->rtnl_link_ops->dellink) dev->rtnl_link_ops->dellink(dev, &dev_kill_list); else unregister_netdevice_queue(dev, &dev_kill_list); } + + unregister_netdevice_queue_many_net(net, &dev_kill_list); + __rtnl_net_unlock(net); } unregister_netdevice_many(&dev_kill_list); rtnl_unlock(); diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c index 578b48cf5318..a91d2b58aadd 100644 --- a/net/core/net_namespace.c +++ b/net/core/net_namespace.c @@ -181,6 +181,7 @@ static void ops_exit_rtnl_list(const struct list_head *ops_list, ops->exit_rtnl(net, &dev_kill_list); } + unregister_netdevice_queue_many_net(net, &dev_kill_list); __rtnl_net_unlock(net); } diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index cf2b7697a6e7..31c65a545a10 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -714,8 +714,12 @@ void rtnl_link_unregister(struct rtnl_link_ops *ops) down_write(&pernet_ops_rwsem); rtnl_lock_unregistering_all(); - for_each_net(net) + for_each_net(net) { + __rtnl_net_lock(net); __rtnl_kill_links(net, ops, &dev_kill_list); + unregister_netdevice_queue_many_net(net, &dev_kill_list); + __rtnl_net_unlock(net); + } unregister_netdevice_many(&dev_kill_list); From d7fda2c776b2a969b9d78c5ad00e30824df43add Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 3 Jul 2026 00:09:19 +0000 Subject: [PATCH 0261/1433] veth: Support per-netns device unregistration. Currently, veth_dellink() unregisters both local and peer devices synchronously under RTNL. Once RTNL is removed, it can be called concurrently from different netns. Let's use xchg() and unregister_netdevice_queue_net() to support per-netns device unregistration. This way, each device is queued for destruction only once by the winner of the race. Note that the extra netdev_hold() ensures that @peer obtained by the first xchg() is not freed during the subsequent access to netdev_priv(peer). The 2nd xchg() overwrites @dev to balance the refcount. Tested: 1. Create two veth pairs (veth1-2, veth3-4) between two netns (ns1 & ns2). # ip netns add ns1 # ip netns add ns2 # ip -n ns1 link add veth1 type veth peer veth2 netns ns2 # ip -n ns1 link add veth3 type veth peer veth4 netns ns2 2. Run bpftrace to check if the same process does NOT unregister the paired veth devices # bpftrace -e '#include kprobe:free_netdev { $dev = (struct net_device *)arg0; printf("PID: %d | DEV: %s%s\n", pid, $dev->name, kstack()); }' 3. Remove veth2 in ns2 and check bpftrace output # ip -n ns2 link del veth2 PID: 2194 | DEV: veth2 free_netdev+5 netdev_run_todo+4798 rtnl_dellink+1507 rtnetlink_rcv_msg+1791 ... PID: 448 | DEV: veth1 free_netdev+5 netdev_run_todo+4798 process_scheduled_works+2538 ... 4. Remove ns2 (thus veth4) and check bpftrace output # ip netns del ns2 PID: 571 | DEV: veth4 free_netdev+5 netdev_run_todo+4798 default_device_exit_batch+2271 ops_undo_list+993 cleanup_net+1122 process_scheduled_works+2538 ... PID: 441 | DEV: veth3 free_netdev+5 netdev_run_todo+4798 process_scheduled_works+2538 ... Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260703001009.1572444-9-kuniyu@google.com Signed-off-by: Paolo Abeni --- drivers/net/veth.c | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/drivers/net/veth.c b/drivers/net/veth.c index 1c5142149175..8170bf33ccf9 100644 --- a/drivers/net/veth.c +++ b/drivers/net/veth.c @@ -77,6 +77,7 @@ struct veth_priv { struct bpf_prog *_xdp_prog; struct veth_rq *rq; unsigned int requested_headroom; + netdevice_tracker peer_tracker; }; struct veth_xdp_tx_bq { @@ -1901,15 +1902,17 @@ static int veth_newlink(struct net_device *dev, priv = netdev_priv(dev); rcu_assign_pointer(priv->peer, peer); + netdev_hold(peer, &priv->peer_tracker, GFP_KERNEL); err = veth_init_queues(dev, tb); if (err) goto err_queues; priv = netdev_priv(peer); rcu_assign_pointer(priv->peer, dev); + netdev_hold(dev, &priv->peer_tracker, GFP_KERNEL); err = veth_init_queues(peer, tb); if (err) - goto err_queues; + goto err_peer_queues; veth_disable_gro(dev); /* update XDP supported features */ @@ -1918,7 +1921,11 @@ static int veth_newlink(struct net_device *dev, return 0; +err_peer_queues: + netdev_put(dev, &priv->peer_tracker); + priv = netdev_priv(dev); err_queues: + netdev_put(peer, &priv->peer_tracker); unregister_netdevice(dev); err_register_dev: /* nothing to do */ @@ -1933,24 +1940,25 @@ static int veth_newlink(struct net_device *dev, static void veth_dellink(struct net_device *dev, struct list_head *head) { - struct veth_priv *priv; + netdevice_tracker *peer_tracker; struct net_device *peer; + struct veth_priv *priv; priv = netdev_priv(dev); - peer = rtnl_dereference(priv->peer); + peer_tracker = &priv->peer_tracker; + peer = unrcu_pointer(xchg(&priv->peer, NULL)); + if (!peer) + return; - /* Note : dellink() is called from default_device_exit_batch(), - * before a rcu_synchronize() point. The devices are guaranteed - * not being freed before one RCU grace period. - */ - RCU_INIT_POINTER(priv->peer, NULL); unregister_netdevice_queue(dev, head); - if (peer) { - priv = netdev_priv(peer); - RCU_INIT_POINTER(priv->peer, NULL); - unregister_netdevice_queue(peer, head); - } + priv = netdev_priv(peer); + dev = unrcu_pointer(xchg(&priv->peer, NULL)); + if (dev) + unregister_netdevice_queue_net(dev_net(dev), peer, head); + + netdev_put(peer, peer_tracker); + netdev_put(dev, &priv->peer_tracker); } static const struct nla_policy veth_policy[VETH_INFO_MAX + 1] = { From a278ea7ba32a948c90da54caef9193b54652540d Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 3 Jul 2026 00:09:20 +0000 Subject: [PATCH 0262/1433] bareudp: Protect bareudp_list with mutex. struct bareudp_dev.net is the netns where the backend bareudp socket resides. struct bareudp_dev is linked to the bareudp_net.bareudp_list of the socket's netns. During netns dismantle or module unload, bareudp_exit_rtnl_net() iterates the list and queues devices for destruction regardless of the devices' netns. Thus, once RTNL is removed, the list can be modified concurrently from different netns due to device removal. Let's protect it with per-netns mutex. bareudp_newlink() is still protected by rtnl_net_lock()s, so acquiring bn->lock twice in bareudp_find_dev() and bareudp_configure() is not a problem. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260703001009.1572444-10-kuniyu@google.com Signed-off-by: Paolo Abeni --- drivers/net/bareudp.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/drivers/net/bareudp.c b/drivers/net/bareudp.c index 5ef841c85526..7dedf4867e7b 100644 --- a/drivers/net/bareudp.c +++ b/drivers/net/bareudp.c @@ -36,6 +36,7 @@ static unsigned int bareudp_net_id; struct bareudp_net { struct list_head bareudp_list; + struct mutex lock; }; struct bareudp_conf { @@ -636,10 +637,15 @@ static struct bareudp_dev *bareudp_find_dev(struct bareudp_net *bn, { struct bareudp_dev *bareudp, *t = NULL; + mutex_lock(&bn->lock); + list_for_each_entry(bareudp, &bn->bareudp_list, next) { if (conf->port == bareudp->port) t = bareudp; } + + mutex_unlock(&bn->lock); + return t; } @@ -675,7 +681,10 @@ static int bareudp_configure(struct net *net, struct net_device *dev, if (err) return err; + mutex_lock(&bn->lock); list_add(&bareudp->next, &bn->bareudp_list); + mutex_unlock(&bn->lock); + return 0; } @@ -692,7 +701,7 @@ static int bareudp_link_config(struct net_device *dev, return 0; } -static void bareudp_dellink(struct net_device *dev, struct list_head *head) +static void __bareudp_dellink(struct net_device *dev, struct list_head *head) { struct bareudp_dev *bareudp = netdev_priv(dev); @@ -700,6 +709,18 @@ static void bareudp_dellink(struct net_device *dev, struct list_head *head) unregister_netdevice_queue(dev, head); } +static void bareudp_dellink(struct net_device *dev, struct list_head *head) +{ + struct bareudp_dev *bareudp = netdev_priv(dev); + struct bareudp_net *bn; + + bn = net_generic(bareudp->net, bareudp_net_id); + + mutex_lock(&bn->lock); + __bareudp_dellink(dev, head); + mutex_unlock(&bn->lock); +} + static int bareudp_newlink(struct net_device *dev, struct rtnl_newlink_params *params, struct netlink_ext_ack *extack) @@ -776,6 +797,8 @@ static __net_init int bareudp_init_net(struct net *net) struct bareudp_net *bn = net_generic(net, bareudp_net_id); INIT_LIST_HEAD(&bn->bareudp_list); + mutex_init(&bn->lock); + return 0; } @@ -785,8 +808,12 @@ static void __net_exit bareudp_exit_rtnl_net(struct net *net, struct bareudp_net *bn = net_generic(net, bareudp_net_id); struct bareudp_dev *bareudp, *next; + mutex_lock(&bn->lock); + list_for_each_entry_safe(bareudp, next, &bn->bareudp_list, next) - bareudp_dellink(bareudp->dev, dev_kill_list); + __bareudp_dellink(bareudp->dev, dev_kill_list); + + mutex_unlock(&bn->lock); } static struct pernet_operations bareudp_net_ops = { From f1de92507a91ea99831461721714c44fa39ddd77 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 3 Jul 2026 00:09:21 +0000 Subject: [PATCH 0263/1433] bareudp: Support per-netns netdev unregistration. bareudp_exit_rtnl_net() iterates bareudp devices whose sockets are in the dying netns and queues them for destruction. So the devices may reside in different netns. Let's use unregister_netdevice_queue_net() to support per-netns device unregistration. list_del() is changed to list_del_init() to avoid queueing the same device twice. Even after bareudp_exit_rtnl_net() queues a cross-netns bareudp device, bareudp_dellink() could be called concurrently for it (once RTNL is removed). In such a case, __rtnl_net_unlock() will perform the unregistration. Note that bareudp uses register_pernet_subsys() instead of _device(), so default_device_exit_batch() guarantees that the async per-netns works are flushed before ->exit(). Tested: 1. Create bareudp device across two netns. # ip netns add ns1 # ip netns add ns2 # ip -n ns1 link add bareudp0 link-netns ns2 type bareudp \ dstport 9292 ethertype ipv4 2. Run bpftrace to check that bareudp_uninit() is called between ->exit_rtnl() and ->exit(). # bpftrace -e '#include kprobe:bareudp_uninit { $dev = (struct net_device *)arg0; printf("PID: %d | DEV: %s%s\n", pid, $dev->name, kstack()); } kprobe:bareudp_exit_rtnl_net, kprobe:bareudp_exit_net { printf("PID: %d%s\n", pid, kstack()); }' 3. Remove the netns where the bareudp socket resides # ip netns del ns2 Now, we can see bareudp0 is unregistered by per-netns work instead of cleanup_net() and it finishes before ->exit() to avoid WARN_ON_ONCE(!list_empty(&bn->bareudp_list)) there. PID: 576 bareudp_exit_rtnl_net+5 ops_undo_list+702 cleanup_net+1122 process_scheduled_works+2538 ... PID: 470 | DEV: bareudp0 bareudp_uninit+5 unregister_netdevice_many_notify+7129 unregister_netdevice_many_net+1050 rtnl_net_work_func+136 process_scheduled_works+2538 ... PID: 576 bareudp_exit_net+5 ops_undo_list+1064 cleanup_net+1122 process_scheduled_works+2538 Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260703001009.1572444-11-kuniyu@google.com Signed-off-by: Paolo Abeni --- drivers/net/bareudp.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/drivers/net/bareudp.c b/drivers/net/bareudp.c index 7dedf4867e7b..c3b5ed52d877 100644 --- a/drivers/net/bareudp.c +++ b/drivers/net/bareudp.c @@ -701,12 +701,13 @@ static int bareudp_link_config(struct net_device *dev, return 0; } -static void __bareudp_dellink(struct net_device *dev, struct list_head *head) +static void __bareudp_dellink(struct net *net, struct net_device *dev, + struct list_head *head) { struct bareudp_dev *bareudp = netdev_priv(dev); - list_del(&bareudp->next); - unregister_netdevice_queue(dev, head); + list_del_init(&bareudp->next); + unregister_netdevice_queue_net(net, dev, head); } static void bareudp_dellink(struct net_device *dev, struct list_head *head) @@ -717,7 +718,8 @@ static void bareudp_dellink(struct net_device *dev, struct list_head *head) bn = net_generic(bareudp->net, bareudp_net_id); mutex_lock(&bn->lock); - __bareudp_dellink(dev, head); + if (!list_empty(&bareudp->next)) + __bareudp_dellink(dev_net(dev), dev, head); mutex_unlock(&bn->lock); } @@ -811,14 +813,22 @@ static void __net_exit bareudp_exit_rtnl_net(struct net *net, mutex_lock(&bn->lock); list_for_each_entry_safe(bareudp, next, &bn->bareudp_list, next) - __bareudp_dellink(bareudp->dev, dev_kill_list); + __bareudp_dellink(net, bareudp->dev, dev_kill_list); mutex_unlock(&bn->lock); } +static void __net_exit bareudp_exit_net(struct net *net) +{ + struct bareudp_net *bn = net_generic(net, bareudp_net_id); + + WARN_ON_ONCE(!list_empty(&bn->bareudp_list)); +} + static struct pernet_operations bareudp_net_ops = { .init = bareudp_init_net, .exit_rtnl = bareudp_exit_rtnl_net, + .exit = bareudp_exit_net, .id = &bareudp_net_id, .size = sizeof(struct bareudp_net), }; From acb351b5a899a45400daa154258d970077658848 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 3 Jul 2026 00:09:22 +0000 Subject: [PATCH 0264/1433] ipvlan: Convert ipvl_port.count to refcount_t. struct ipvl_port is shared between a lower device and its upper ipvlan devices. While each upper device can always access ipvl_port safely via ipvlan_dev.port, the lower device relies on RTNL to access it via net_device.rx_handler_data. Once RTNL is removed, the lower device cannot read ipvl_port safely in ipvlan_device_event() because the port could be freed concurrently and net_device.rx_handler_data is set to NULL if the last ipvlan device in another namespace is unregistered. Let's convert ipvl_port.count to refcount_t and use RCU along with refcount_inc_not_zero() in ipvlan_device_event(). netdev_put() in ipvlan_port_destroy() is also moved down after cancel_work_sync(), which is the last user of port->dev. Note that ipvlan->port is now set in ipvlan_init() so that it can be used in ipvlan_uninit(), instead of ipvlan_port_get_rtnl() (rtnl_dereference()). Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260703001009.1572444-12-kuniyu@google.com Signed-off-by: Paolo Abeni --- drivers/net/ipvlan/ipvlan.h | 2 +- drivers/net/ipvlan/ipvlan_main.c | 75 ++++++++++++++++++++++---------- 2 files changed, 52 insertions(+), 25 deletions(-) diff --git a/drivers/net/ipvlan/ipvlan.h b/drivers/net/ipvlan/ipvlan.h index 80f84fc87008..78f9107fa752 100644 --- a/drivers/net/ipvlan/ipvlan.h +++ b/drivers/net/ipvlan/ipvlan.h @@ -96,7 +96,7 @@ struct ipvl_port { u16 dev_id_start; struct work_struct wq; struct sk_buff_head backlog; - int count; + refcount_t count; struct ida ida; netdevice_tracker dev_tracker; }; diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c index ed46439a9f4e..b4906a8d24ef 100644 --- a/drivers/net/ipvlan/ipvlan_main.c +++ b/drivers/net/ipvlan/ipvlan_main.c @@ -86,6 +86,7 @@ static int ipvlan_port_create(struct net_device *dev) goto err; netdev_hold(dev, &port->dev_tracker, GFP_KERNEL); + return 0; err: @@ -93,16 +94,18 @@ static int ipvlan_port_create(struct net_device *dev) return err; } -static void ipvlan_port_destroy(struct net_device *dev) +static void ipvlan_port_destroy(struct ipvl_port *port) { - struct ipvl_port *port = ipvlan_port_get_rtnl(dev); + struct net_device *dev = port->dev; struct sk_buff *skb; - netdev_put(dev, &port->dev_tracker); if (port->mode == IPVLAN_MODE_L3S) ipvlan_l3s_unregister(port); + netdev_rx_handler_unregister(dev); cancel_work_sync(&port->wq); + netdev_put(dev, &port->dev_tracker); + while ((skb = __skb_dequeue(&port->backlog)) != NULL) { dev_put(skb->dev); kfree_skb(skb); @@ -111,6 +114,27 @@ static void ipvlan_port_destroy(struct net_device *dev) kfree(port); } +static void ipvlan_port_put(struct ipvl_port *port) +{ + if (refcount_dec_and_test(&port->count)) + ipvlan_port_destroy(port); +} + +static struct ipvl_port *ipvlan_port_get(struct net_device *dev) +{ + struct ipvl_port *port = NULL; + + rcu_read_lock(); + if (netif_is_ipvlan_port(dev)) { + port = ipvlan_port_get_rcu(dev); + if (!refcount_inc_not_zero(&port->count)) + port = NULL; + } + rcu_read_unlock(); + + return port; +} + #define IPVLAN_ALWAYS_ON_OFLOADS \ (NETIF_F_SG | NETIF_F_HW_CSUM | \ NETIF_F_GSO_ROBUST | NETIF_F_GSO_SOFTWARE | NETIF_F_GSO_ENCAP_ALL) @@ -159,24 +183,24 @@ static int ipvlan_init(struct net_device *dev) free_percpu(ipvlan->pcpu_stats); return err; } + port = ipvlan_port_get_rtnl(phy_dev); + refcount_set(&port->count, 1); + } else { + port = ipvlan_port_get_rtnl(phy_dev); + refcount_inc(&port->count); } - port = ipvlan_port_get_rtnl(phy_dev); - port->count += 1; + + ipvlan->port = port; + return 0; } static void ipvlan_uninit(struct net_device *dev) { struct ipvl_dev *ipvlan = netdev_priv(dev); - struct net_device *phy_dev = ipvlan->phy_dev; - struct ipvl_port *port; free_percpu(ipvlan->pcpu_stats); - - port = ipvlan_port_get_rtnl(phy_dev); - port->count -= 1; - if (!port->count) - ipvlan_port_destroy(port->dev); + ipvlan_port_put(ipvlan->port); } static int ipvlan_open(struct net_device *dev) @@ -594,9 +618,7 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params, if (err < 0) return err; - /* ipvlan_init() would have created the port, if required */ - port = ipvlan_port_get_rtnl(phy_dev); - ipvlan->port = port; + port = ipvlan->port; /* If the port-id base is at the MAX value, then wrap it around and * begin from 0x1 again. This may be due to a busy system where lots @@ -729,14 +751,13 @@ static int ipvlan_device_event(struct notifier_block *unused, struct netdev_notifier_pre_changeaddr_info *prechaddr_info; struct net_device *dev = netdev_notifier_info_to_dev(ptr); struct ipvl_dev *ipvlan, *next; + int err, ret = NOTIFY_DONE; struct ipvl_port *port; LIST_HEAD(lst_kill); - int err; - if (!netif_is_ipvlan_port(dev)) - return NOTIFY_DONE; - - port = ipvlan_port_get_rtnl(dev); + port = ipvlan_port_get(dev); + if (!port) + return ret; switch (event) { case NETDEV_UP: @@ -788,8 +809,10 @@ static int ipvlan_device_event(struct notifier_block *unused, err = netif_pre_changeaddr_notify(ipvlan->dev, prechaddr_info->dev_addr, extack); - if (err) - return notifier_from_errno(err); + if (err) { + ret = notifier_from_errno(err); + break; + } } break; @@ -802,7 +825,8 @@ static int ipvlan_device_event(struct notifier_block *unused, case NETDEV_PRE_TYPE_CHANGE: /* Forbid underlying device to change its type. */ - return NOTIFY_BAD; + ret = NOTIFY_BAD; + break; case NETDEV_NOTIFY_PEERS: case NETDEV_BONDING_FAILOVER: @@ -810,7 +834,10 @@ static int ipvlan_device_event(struct notifier_block *unused, list_for_each_entry(ipvlan, &port->ipvlans, pnode) call_netdevice_notifiers(event, ipvlan->dev); } - return NOTIFY_DONE; + + ipvlan_port_put(port); + + return ret; } /* the caller must held the addrs lock */ From aabbdb8c76d7b912d9a6bb2b1e835eba54a53a8d Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 3 Jul 2026 00:09:23 +0000 Subject: [PATCH 0265/1433] ipvlan: Synchronise ipvlan_init() and ipvlan_uninit() for the same lower dev. ipvlan_uninit() for the last ipvlan device resets the lower device's rx_handler_data to NULL. Once RTNL is removed, ipvlan_init() would race with ipvlan_uninit(), which could leak a newly allocated ipvl_port. ipvlan_init() ipvlan_uninit() | |- if (refcount_dec_and_test(old_port)) ... |- ipvlan_port_destroy(old_port) | ' |- refcount_inc_not_zero(old_port) <-- fails |- ipvlan_port_create(phy_dev) . |- new_port = kzalloc() | |- phy_dev->rx_handler_data = new_port |- phy_dev->rx_handler_data = NULL ... `- kfree(old_port); Let's synchronise the two by holding the lower device's netdev_lock(). Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260703001009.1572444-13-kuniyu@google.com Signed-off-by: Paolo Abeni --- drivers/net/ipvlan/ipvlan_main.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c index b4906a8d24ef..7adad781e9b5 100644 --- a/drivers/net/ipvlan/ipvlan_main.c +++ b/drivers/net/ipvlan/ipvlan_main.c @@ -177,9 +177,12 @@ static int ipvlan_init(struct net_device *dev) if (!ipvlan->pcpu_stats) return -ENOMEM; + netdev_lock(phy_dev); + if (!netif_is_ipvlan_port(phy_dev)) { err = ipvlan_port_create(phy_dev); if (err < 0) { + netdev_unlock(phy_dev); free_percpu(ipvlan->pcpu_stats); return err; } @@ -190,6 +193,8 @@ static int ipvlan_init(struct net_device *dev) refcount_inc(&port->count); } + netdev_unlock(phy_dev); + ipvlan->port = port; return 0; @@ -198,9 +203,19 @@ static int ipvlan_init(struct net_device *dev) static void ipvlan_uninit(struct net_device *dev) { struct ipvl_dev *ipvlan = netdev_priv(dev); + netdevice_tracker dev_tracker; + struct net_device *phy_dev; free_percpu(ipvlan->pcpu_stats); + + phy_dev = ipvlan->phy_dev; + netdev_hold(phy_dev, &dev_tracker, GFP_KERNEL); + netdev_lock(phy_dev); + ipvlan_port_put(ipvlan->port); + + netdev_unlock(phy_dev); + netdev_put(phy_dev, &dev_tracker); } static int ipvlan_open(struct net_device *dev) From 35add1093e2fe62b755ef69b211d15b58ab915ab Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 3 Jul 2026 00:09:24 +0000 Subject: [PATCH 0266/1433] ipvlan: Protect ipvl_port.ipvlans with mutex. struct ipvl_port is shared between a lower device and its upper ipvlan devices. All upper devices are linked to ipvl_port.ipvlans. Once RTNL is removed, the list can be modified concurrently from different netns due to device removal. Let's protect it with a per-port mutex. NETDEV_PRECHANGEUPPER and NETDEV_CHANGEUPPER are explicitly skipped to avoid deadlock for netdev_upper_dev_unlink() called from NETDEV_UNREGISTER. Note that __ipvtap_dellink_ptr is added for CONFIG_IPVLAN=y but CONFIG_TAP=m and CONFIG_IPVTAP=m. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260703001009.1572444-14-kuniyu@google.com Signed-off-by: Paolo Abeni --- drivers/net/ipvlan/ipvlan.h | 8 +++++- drivers/net/ipvlan/ipvlan_main.c | 49 ++++++++++++++++++++++++++++---- drivers/net/ipvlan/ipvtap.c | 23 ++++++++++++--- 3 files changed, 70 insertions(+), 10 deletions(-) diff --git a/drivers/net/ipvlan/ipvlan.h b/drivers/net/ipvlan/ipvlan.h index 78f9107fa752..9d3835c14e5e 100644 --- a/drivers/net/ipvlan/ipvlan.h +++ b/drivers/net/ipvlan/ipvlan.h @@ -91,6 +91,7 @@ struct ipvl_port { struct hlist_head hlhead[IPVLAN_HASH_SIZE]; spinlock_t addrs_lock; /* guards hash-table and addrs */ struct list_head ipvlans; + struct mutex pnodes_lock; u16 mode; u16 flags; u16 dev_id_start; @@ -168,7 +169,7 @@ void ipvlan_count_rx(const struct ipvl_dev *ipvlan, unsigned int len, bool success, bool mcast); int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params, struct netlink_ext_ack *extack); -void ipvlan_link_delete(struct net_device *dev, struct list_head *head); +void __ipvlan_link_delete(struct net_device *dev, struct list_head *head); void ipvlan_link_setup(struct net_device *dev); int ipvlan_link_register(struct rtnl_link_ops *ops); #ifdef CONFIG_IPVLAN_L3S @@ -207,4 +208,9 @@ static inline bool netif_is_ipvlan_port(const struct net_device *dev) return rcu_access_pointer(dev->rx_handler) == ipvlan_handle_frame; } +#if IS_ENABLED(CONFIG_IPVTAP) +extern void (*__ipvtap_dellink_ptr)(struct net_device *dev, + struct list_head *head); +#endif + #endif /* __IPVLAN_H */ diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c index 7adad781e9b5..6d7479a8a9c6 100644 --- a/drivers/net/ipvlan/ipvlan_main.c +++ b/drivers/net/ipvlan/ipvlan_main.c @@ -7,6 +7,12 @@ #include "ipvlan.h" +#if IS_ENABLED(CONFIG_IPVTAP) +void (*__ipvtap_dellink_ptr)(struct net_device *dev, + struct list_head *head); +EXPORT_SYMBOL(__ipvtap_dellink_ptr); +#endif + static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval, struct netlink_ext_ack *extack) { @@ -16,6 +22,8 @@ static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval, ASSERT_RTNL(); if (port->mode != nval) { + mutex_lock(&port->pnodes_lock); + list_for_each_entry(ipvlan, &port->ipvlans, pnode) { flags = ipvlan->dev->flags; if (nval == IPVLAN_MODE_L3 || nval == IPVLAN_MODE_L3S) { @@ -40,6 +48,8 @@ static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval, ipvlan_l3s_unregister(port); } port->mode = nval; + + mutex_unlock(&port->pnodes_lock); } return 0; @@ -56,6 +66,8 @@ static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval, NULL); } + mutex_unlock(&port->pnodes_lock); + return err; } @@ -76,6 +88,7 @@ static int ipvlan_port_create(struct net_device *dev) INIT_HLIST_HEAD(&port->hlhead[idx]); spin_lock_init(&port->addrs_lock); + mutex_init(&port->pnodes_lock); skb_queue_head_init(&port->backlog); INIT_WORK(&port->wq, ipvlan_process_multicast); ida_init(&port->ida); @@ -676,7 +689,10 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params, if (err) goto unlink_netdev; + mutex_lock(&port->pnodes_lock); list_add_tail_rcu(&ipvlan->pnode, &port->ipvlans); + mutex_unlock(&port->pnodes_lock); + netif_stacked_transfer_operstate(phy_dev, dev); return 0; @@ -690,7 +706,7 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params, } EXPORT_SYMBOL_GPL(ipvlan_link_new); -void ipvlan_link_delete(struct net_device *dev, struct list_head *head) +void __ipvlan_link_delete(struct net_device *dev, struct list_head *head) { struct ipvl_dev *ipvlan = netdev_priv(dev); struct ipvl_addr *addr, *next; @@ -708,7 +724,16 @@ void ipvlan_link_delete(struct net_device *dev, struct list_head *head) unregister_netdevice_queue(dev, head); netdev_upper_dev_unlink(ipvlan->phy_dev, dev); } -EXPORT_SYMBOL_GPL(ipvlan_link_delete); +EXPORT_SYMBOL(__ipvlan_link_delete); + +static void ipvlan_link_delete(struct net_device *dev, struct list_head *head) +{ + struct ipvl_dev *ipvlan = netdev_priv(dev); + + mutex_lock(&ipvlan->port->pnodes_lock); + __ipvlan_link_delete(dev, head); + mutex_unlock(&ipvlan->port->pnodes_lock); +} void ipvlan_link_setup(struct net_device *dev) { @@ -770,10 +795,16 @@ static int ipvlan_device_event(struct notifier_block *unused, struct ipvl_port *port; LIST_HEAD(lst_kill); + if (event == NETDEV_PRECHANGEUPPER || + event == NETDEV_CHANGEUPPER) + return ret; + port = ipvlan_port_get(dev); if (!port) return ret; + mutex_lock(&port->pnodes_lock); + switch (event) { case NETDEV_UP: case NETDEV_DOWN: @@ -800,9 +831,15 @@ static int ipvlan_device_event(struct notifier_block *unused, if (dev->reg_state != NETREG_UNREGISTERING) break; - list_for_each_entry_safe(ipvlan, next, &port->ipvlans, pnode) - ipvlan->dev->rtnl_link_ops->dellink(ipvlan->dev, - &lst_kill); + list_for_each_entry_safe(ipvlan, next, &port->ipvlans, pnode) { +#if IS_ENABLED(CONFIG_IPVTAP) + if (ipvlan->dev->rtnl_link_ops != &ipvlan_link_ops) + __ipvtap_dellink_ptr(ipvlan->dev, &lst_kill); + else +#endif + __ipvlan_link_delete(ipvlan->dev, &lst_kill); + } + unregister_netdevice_many(&lst_kill); break; @@ -850,6 +887,8 @@ static int ipvlan_device_event(struct notifier_block *unused, call_netdevice_notifiers(event, ipvlan->dev); } + mutex_unlock(&port->pnodes_lock); + ipvlan_port_put(port); return ret; diff --git a/drivers/net/ipvlan/ipvtap.c b/drivers/net/ipvlan/ipvtap.c index 2d6bbddd1edd..99eaa29057b4 100644 --- a/drivers/net/ipvlan/ipvtap.c +++ b/drivers/net/ipvlan/ipvtap.c @@ -109,14 +109,24 @@ static int ipvtap_newlink(struct net_device *dev, return err; } +static void __ipvtap_dellink(struct net_device *dev, struct list_head *head) +{ + struct ipvtap_dev *vlantap = netdev_priv(dev); + + netdev_rx_handler_unregister(dev); + tap_del_queues(&vlantap->tap); + __ipvlan_link_delete(dev, head); +} + static void ipvtap_dellink(struct net_device *dev, struct list_head *head) { - struct ipvtap_dev *vlan = netdev_priv(dev); + struct ipvtap_dev *vlantap = netdev_priv(dev); + struct ipvl_port *port = vlantap->vlan.port; - netdev_rx_handler_unregister(dev); - tap_del_queues(&vlan->tap); - ipvlan_link_delete(dev, head); + mutex_lock(&port->pnodes_lock); + __ipvtap_dellink(dev, head); + mutex_unlock(&port->pnodes_lock); } static void ipvtap_setup(struct net_device *dev) @@ -198,6 +208,8 @@ static int __init ipvtap_init(void) { int err; + __ipvtap_dellink_ptr = __ipvtap_dellink; + err = tap_create_cdev(&ipvtap_cdev, &ipvtap_major, "ipvtap", THIS_MODULE); if (err) @@ -224,6 +236,8 @@ static int __init ipvtap_init(void) out2: tap_destroy_cdev(ipvtap_major, &ipvtap_cdev); out1: + __ipvtap_dellink_ptr = NULL; + return err; } module_init(ipvtap_init); @@ -234,6 +248,7 @@ static void __exit ipvtap_exit(void) unregister_netdevice_notifier(&ipvtap_notifier_block); class_unregister(&ipvtap_class); tap_destroy_cdev(ipvtap_major, &ipvtap_cdev); + __ipvtap_dellink_ptr = NULL; } module_exit(ipvtap_exit); MODULE_ALIAS_RTNL_LINK("ipvtap"); From 00a40d809207a61f0762488aa5ce72e941b367ce Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 3 Jul 2026 00:09:25 +0000 Subject: [PATCH 0267/1433] ipvlan: Support per-netns netdev unregistration. When a lower device is unregistered, its upper ipvlan devices must also be unregistered. However, these upper devices may reside in different netns than the lower device. Let's use unregister_netdevice_queue_net() to support per-netns device unregistration for ipvlan. The new dying flag in struct ipvl_dev is used to avoid a race that ipvlan_link_delete() is called while its lower device is being removed in ipvlan_device_event(). If dying is true in ipvlan_link_delete(), the ipvlan device is already destructed but not yet unregistered. In this case, unregistration will be done in __rtnl_net_unlock() of the ->dellink() caller. Tested: 1. Create veth in ns1 and two ipvlan devices in ns2 and ns3. # ip netns add ns1 # ip netns add ns2 # ip netns add ns3 # ip -n ns1 link add veth0 type veth peer veth1 # ip -n ns2 link add ipvl2 link veth0 link-netns ns1 type ipvlan mode l2 # ip -n ns3 link add ipvl3 link veth0 link-netns ns1 type ipvlan mode l2 2. Run bpftrace to check that veth is unregistered first but wait ipvlan to be unregistered # bpftrace -e '#include kprobe:ipvlan_uninit, kprobe:veth_dellink, kprobe:free_netdev { $dev = (struct net_device *)arg0; printf("PID: %d | DEV: %s%s\n", pid, $dev->name, kstack()); }' 3. Remove the lower veth0 in ns1. # ip -n ns1 link del veth0 We can see that veth0 is freed after unregistering ipvl2 and ipvl3 in per-netns work because ipvl_port holds refcount of veth0. PID: 2010 | DEV: veth0 veth_dellink+5 rtnl_dellink+1213 rtnetlink_rcv_msg+1791 ... PID: 440 | DEV: ipvl2 ipvlan_uninit+5 unregister_netdevice_many_notify+7129 unregister_netdevice_many_net+1050 rtnl_net_work_func+136 process_scheduled_works+2538 ... PID: 440 | DEV: ipvl2 free_netdev+5 netdev_run_todo+4798 process_scheduled_works+2538 ... PID: 440 | DEV: ipvl3 ipvlan_uninit+5 unregister_netdevice_many_notify+7129 unregister_netdevice_many_net+1050 rtnl_net_work_func+136 process_scheduled_works+2538 ... PID: 2010 | DEV: veth0 free_netdev+5 netdev_run_todo+4798 rtnl_dellink+1507 rtnetlink_rcv_msg+1791 ... PID: 440 | DEV: ipvl3 free_netdev+5 netdev_run_todo+4798 process_scheduled_works+2538 ... Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260703001009.1572444-15-kuniyu@google.com Signed-off-by: Paolo Abeni --- drivers/net/ipvlan/ipvlan.h | 6 ++++-- drivers/net/ipvlan/ipvlan_main.c | 22 ++++++++++++++-------- drivers/net/ipvlan/ipvtap.c | 8 +++++--- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/drivers/net/ipvlan/ipvlan.h b/drivers/net/ipvlan/ipvlan.h index 9d3835c14e5e..8d05ad480438 100644 --- a/drivers/net/ipvlan/ipvlan.h +++ b/drivers/net/ipvlan/ipvlan.h @@ -69,6 +69,7 @@ struct ipvl_dev { DECLARE_BITMAP(mac_filters, IPVLAN_MAC_FILTER_SIZE); netdev_features_t sfeatures; u32 msg_enable; + bool dying; }; struct ipvl_addr { @@ -169,7 +170,8 @@ void ipvlan_count_rx(const struct ipvl_dev *ipvlan, unsigned int len, bool success, bool mcast); int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params, struct netlink_ext_ack *extack); -void __ipvlan_link_delete(struct net_device *dev, struct list_head *head); +void __ipvlan_link_delete(struct net *net, struct net_device *dev, + struct list_head *head); void ipvlan_link_setup(struct net_device *dev); int ipvlan_link_register(struct rtnl_link_ops *ops); #ifdef CONFIG_IPVLAN_L3S @@ -209,7 +211,7 @@ static inline bool netif_is_ipvlan_port(const struct net_device *dev) } #if IS_ENABLED(CONFIG_IPVTAP) -extern void (*__ipvtap_dellink_ptr)(struct net_device *dev, +extern void (*__ipvtap_dellink_ptr)(struct net *net, struct net_device *dev, struct list_head *head); #endif diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c index 6d7479a8a9c6..ee46a55f73d1 100644 --- a/drivers/net/ipvlan/ipvlan_main.c +++ b/drivers/net/ipvlan/ipvlan_main.c @@ -8,7 +8,7 @@ #include "ipvlan.h" #if IS_ENABLED(CONFIG_IPVTAP) -void (*__ipvtap_dellink_ptr)(struct net_device *dev, +void (*__ipvtap_dellink_ptr)(struct net *net, struct net_device *dev, struct list_head *head); EXPORT_SYMBOL(__ipvtap_dellink_ptr); #endif @@ -706,7 +706,8 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params, } EXPORT_SYMBOL_GPL(ipvlan_link_new); -void __ipvlan_link_delete(struct net_device *dev, struct list_head *head) +void __ipvlan_link_delete(struct net *net, struct net_device *dev, + struct list_head *head) { struct ipvl_dev *ipvlan = netdev_priv(dev); struct ipvl_addr *addr, *next; @@ -721,7 +722,7 @@ void __ipvlan_link_delete(struct net_device *dev, struct list_head *head) ida_free(&ipvlan->port->ida, dev->dev_id); list_del_rcu(&ipvlan->pnode); - unregister_netdevice_queue(dev, head); + unregister_netdevice_queue_net(net, dev, head); netdev_upper_dev_unlink(ipvlan->phy_dev, dev); } EXPORT_SYMBOL(__ipvlan_link_delete); @@ -731,7 +732,8 @@ static void ipvlan_link_delete(struct net_device *dev, struct list_head *head) struct ipvl_dev *ipvlan = netdev_priv(dev); mutex_lock(&ipvlan->port->pnodes_lock); - __ipvlan_link_delete(dev, head); + if (!ipvlan->dying) + __ipvlan_link_delete(dev_net(dev), dev, head); mutex_unlock(&ipvlan->port->pnodes_lock); } @@ -827,22 +829,26 @@ static int ipvlan_device_event(struct notifier_block *unused, ipvlan_migrate_l3s_hook(oldnet, newnet); break; } - case NETDEV_UNREGISTER: + case NETDEV_UNREGISTER: { + struct net *net = dev_net(dev); + if (dev->reg_state != NETREG_UNREGISTERING) break; list_for_each_entry_safe(ipvlan, next, &port->ipvlans, pnode) { + ipvlan->dying = true; + #if IS_ENABLED(CONFIG_IPVTAP) if (ipvlan->dev->rtnl_link_ops != &ipvlan_link_ops) - __ipvtap_dellink_ptr(ipvlan->dev, &lst_kill); + __ipvtap_dellink_ptr(net, ipvlan->dev, &lst_kill); else #endif - __ipvlan_link_delete(ipvlan->dev, &lst_kill); + __ipvlan_link_delete(net, ipvlan->dev, &lst_kill); } unregister_netdevice_many(&lst_kill); break; - + } case NETDEV_FEAT_CHANGE: list_for_each_entry(ipvlan, &port->ipvlans, pnode) { netif_inherit_tso_max(ipvlan->dev, dev); diff --git a/drivers/net/ipvlan/ipvtap.c b/drivers/net/ipvlan/ipvtap.c index 99eaa29057b4..66c949d94261 100644 --- a/drivers/net/ipvlan/ipvtap.c +++ b/drivers/net/ipvlan/ipvtap.c @@ -109,13 +109,14 @@ static int ipvtap_newlink(struct net_device *dev, return err; } -static void __ipvtap_dellink(struct net_device *dev, struct list_head *head) +static void __ipvtap_dellink(struct net *net, struct net_device *dev, + struct list_head *head) { struct ipvtap_dev *vlantap = netdev_priv(dev); netdev_rx_handler_unregister(dev); tap_del_queues(&vlantap->tap); - __ipvlan_link_delete(dev, head); + __ipvlan_link_delete(net, dev, head); } static void ipvtap_dellink(struct net_device *dev, @@ -125,7 +126,8 @@ static void ipvtap_dellink(struct net_device *dev, struct ipvl_port *port = vlantap->vlan.port; mutex_lock(&port->pnodes_lock); - __ipvtap_dellink(dev, head); + if (!vlantap->vlan.dying) + __ipvtap_dellink(dev_net(dev), dev, head); mutex_unlock(&port->pnodes_lock); } From f6f3b36c15ed44de1fbb44e645e4fae8c4a4453e Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sun, 5 Jul 2026 18:41:59 +0200 Subject: [PATCH 0268/1433] net: ethernet: qualcomm: remove unneeded 'fast_io' parameter in regmap_config When using MMIO with regmap, fast_io is implied. No need to set it again. Signed-off-by: Wolfram Sang Reviewed-by: Luo Jie Link: https://patch.msgid.link/20260705164208.2184-2-wsa+renesas@sang-engineering.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/qualcomm/ppe/ppe.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/ethernet/qualcomm/ppe/ppe.c b/drivers/net/ethernet/qualcomm/ppe/ppe.c index be747510d947..3c301e609d3e 100644 --- a/drivers/net/ethernet/qualcomm/ppe/ppe.c +++ b/drivers/net/ethernet/qualcomm/ppe/ppe.c @@ -106,7 +106,6 @@ static const struct regmap_config regmap_config_ipq9574 = { .rd_table = &ppe_reg_table, .wr_table = &ppe_reg_table, .max_register = 0xbef800, - .fast_io = true, }; static int ppe_clock_init_and_reset(struct ppe_device *ppe_dev) From 0b79ae7b09a86ff2b925b3ec738dc18ba073d924 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Mon, 6 Jul 2026 10:54:37 +0800 Subject: [PATCH 0269/1433] wifi: rtw89: coex: Add Init info version 10 The version 10 Init info add I/O offload type & variable Bluetooth function (EX: Zigbee/Thread...etc) into the structure definition. Firmware need to synchronize these information to do corresponding setting. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260706025445.18428-2-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 58 +++++++++++++++++-- drivers/net/wireless/realtek/rtw89/coex.h | 6 ++ drivers/net/wireless/realtek/rtw89/core.h | 54 ++++++++++++++++- drivers/net/wireless/realtek/rtw89/fw.c | 41 +++++++++++++ drivers/net/wireless/realtek/rtw89/fw.h | 6 ++ drivers/net/wireless/realtek/rtw89/rtw8851b.c | 2 - drivers/net/wireless/realtek/rtw89/rtw8852a.c | 1 - .../wireless/realtek/rtw89/rtw8852b_common.c | 1 - drivers/net/wireless/realtek/rtw89/rtw8852c.c | 1 - drivers/net/wireless/realtek/rtw89/rtw8922a.c | 1 - 10 files changed, 159 insertions(+), 12 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 1361d4d54528..43de238c18f8 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -2822,6 +2822,8 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index) case CXDRVINFO_INIT: if (ver->fcxinit == 7) rtw89_fw_h2c_cxdrv_init_v7(rtwdev, index); + else if (ver->fcxinit == 10) + rtw89_fw_h2c_cxdrv_init_v10(rtwdev, index); else rtw89_fw_h2c_cxdrv_init(rtwdev, index); break; @@ -7884,15 +7886,30 @@ void rtw89_btc_ntfy_poweroff(struct rtw89_dev *rtwdev) btc->cx.wl.status.map.rf_off_pre = btc->cx.wl.status.map.rf_off; } +#define BTC_PLATFORM_LITTLE_ENDIAN 0 static void _set_init_info(struct rtw89_dev *rtwdev) { const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - if (ver->fcxinit == 7) { + if (ver->fcxinit == 10) { + dm->init_info.init_v10.init_mode = wl->coex_mode; + dm->init_info.init_v10.wl_init_ok = wl->status.map.init_ok; + dm->init_info.init_v10.endian_type = BTC_PLATFORM_LITTLE_ENDIAN; + + dm->init_info.init_v10.module = btc->mdinfo.md_v10; + + dm->init_info.init_v10.bt0_function = cx->bt0.func_type; + dm->init_info.init_v10.bt1_function = cx->bt1.func_type; + dm->init_info.init_v10.bt2_function = cx->bt_ext.func_type; + + dm->init_info.init_v10.pta_mode = RTW89_MAC_AX_COEX_RTK_MODE; + dm->init_info.init_v10.pta_direction = RTW89_MAC_AX_COEX_INNER; + } else if (ver->fcxinit == 7) { dm->init_info.init_v7.wl_only = (u8)dm->wl_only; dm->init_info.init_v7.bt_only = (u8)dm->bt_only; dm->init_info.init_v7.wl_init_ok = (u8)wl->status.map.init_ok; @@ -7908,6 +7925,12 @@ static void _set_init_info(struct rtw89_dev *rtwdev) dm->init_info.init.wl_guard_ch = chip->afh_guard_ch; dm->init_info.init.module = btc->mdinfo.md; } + + _fw_set_drv_info(rtwdev, CXDRVINFO_INIT); + _fw_set_drv_info(rtwdev, CXDRVINFO_CTRL); + rtw89_btc_fw_set_slots(rtwdev); + btc_fw_set_monreg(rtwdev); + _set_wl_tx_power(rtwdev, RTW89_BTC_WL_DEF_TX_PWR, RTW89_PHY_0); } void rtw89_btc_ntfy_init(struct rtw89_dev *rtwdev, u8 mode) @@ -7918,13 +7941,18 @@ void rtw89_btc_ntfy_init(struct rtw89_dev *rtwdev, u8 mode) const struct rtw89_chip_info *chip = rtwdev->chip; const struct rtw89_btc_ver *ver = btc->ver; + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(): Init %s !!\n", __func__, + chip_id_str(chip->chip_id)); + _reset_btc_var(rtwdev, BTC_RESET_ALL); btc->dm.run_reason = BTC_RSN_NONE; btc->dm.run_action = BTC_ACT_NONE; - if (ver->fcxctrl == 7) + if (ver->fcxctrl >= 7) btc->ctrl.ctrl_v7.igno_bt = true; else btc->ctrl.ctrl.igno_bt = true; + wl->status.map.init_ok = true; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): mode=%d\n", __func__, mode); @@ -7934,9 +7962,11 @@ void rtw89_btc_ntfy_init(struct rtw89_dev *rtwdev, u8 mode) dm->wl_only = mode == BTC_MODE_WL ? 1 : 0; dm->bt_only = mode == BTC_MODE_BT ? 1 : 0; wl->status.map.rf_off = mode == BTC_MODE_WLOFF ? 1 : 0; - - chip->ops->btc_set_rfe(rtwdev); - chip->ops->btc_init_cfg(rtwdev); + dm->vid = rtwdev->custid; + if (ver->fcxctrl >= 7) + btc->ctrl.ctrl_v7.always_freerun = mode == BTC_MODE_COTX; + else + btc->ctrl.ctrl.always_freerun = mode == BTC_MODE_COTX; if (!wl->status.map.init_ok) { rtw89_debug(rtwdev, RTW89_DBG_BTC, @@ -7946,8 +7976,26 @@ void rtw89_btc_ntfy_init(struct rtw89_dev *rtwdev, u8 mode) return; } + if (rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) { + btc->cx.bt1.enable.now = 1; + btc->cx.bt1.run_patch_code = 1; + } + + if (rtwdev->chip->para_ver & BTC_FEAT_H2C_MACRO) { + btc->cx.bt0.enable.now = 1; + btc->cx.bt0.run_patch_code = 1; + btc->io_oflld_type = BTC_IO_OFLD_BTC_H2C; + } else { + btc->io_oflld_type = BTC_IO_OFLD_NO_SUPPORT; + _update_bt_scbd(rtwdev, true); + } + + chip->ops->btc_set_rfe(rtwdev); + chip->ops->btc_init_cfg(rtwdev); + _write_scbd(rtwdev, BTC_WSCB_ACTIVE | BTC_WSCB_ON | BTC_WSCB_BTLOG, true); + if (rtw89_mac_get_ctrl_path(rtwdev)) { rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): PTA owner warning!!\n", diff --git a/drivers/net/wireless/realtek/rtw89/coex.h b/drivers/net/wireless/realtek/rtw89/coex.h index 6ac14611607c..259c6e2c0e3c 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.h +++ b/drivers/net/wireless/realtek/rtw89/coex.h @@ -16,6 +16,8 @@ enum btc_mode { BTC_MODE_WL, BTC_MODE_BT, BTC_MODE_WLOFF, + BTC_MODE_COTX, + BTC_MODE_MECHANISM_INIT, BTC_MODE_MAX }; @@ -212,6 +214,10 @@ enum btc_chip_feature { BTC_FEAT_NEW_BBAPI_FLOW = BIT(3), /* new btg_ctrl/pre_agc_ctrl */ BTC_FEAT_MLO_SUPPORT = BIT(4), BTC_FEAT_H2C_MACRO = BIT(5), + BTC_FEAT_DUAL_BT = BIT(6), + BTC_FEAT_BT_6G = BIT(7), + BTC_FEAT_MULTI_PTA = BIT(8), + BTC_FEAT_DUAL_BTGA = BIT(9) /* the future A-Die */ }; enum btc_wl_mode { diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 5dde620b1e5e..1e72c9b9f3b7 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -1451,6 +1451,12 @@ enum rtw89_btc_bt_rf_band { BTC_BT_BMAX = 0x2 }; +enum rtw89_btc_io_offload_type { + BTC_IO_OFLD_NO_SUPPORT = 0, + BTC_IO_OFLD_MAC_API = 1, + BTC_IO_OFLD_BTC_H2C = 2 +}; + enum rtw89_btc_bt_profile { BTC_BT_NOPROFILE = 0, BTC_BT_HFP = BIT(0), @@ -1483,6 +1489,19 @@ struct rtw89_btc_ant_info_v7 { u8 rsvd; } __packed; +struct rtw89_btc_ant_info_v10 { + u8 type; /* shared, dedicated(non-shared) */ + u8 num; /* antenna count */ + u8 isolation; /* Ant-Iso between WL/BT */ + u8 single_pos; /* wifi 1ss-1ant at 0:S0 or 1:S1 */ + + u8 stream_cnt; /* spatial_stream count: Tx[7:4], Rx[3:0] */ + u8 btg_pos; /* BT0 btg-circuit at 0:WL-S0/1:WL-S1 */ + u8 btg1_pos; /* BT1 btg-circuit at 0:WL-S0/1:WL-S1 */ + u8 func[5]; /* function at 1~5 Ant refer to enum btc_bt_func_type */ + u8 ant_xmap[2][4]; +} __packed; + enum rtw89_tfc_dir { RTW89_TFC_UL, RTW89_TFC_DL, @@ -2129,9 +2148,24 @@ struct rtw89_btc_module_v7 { struct rtw89_btc_ant_info_v7 ant; } __packed; +struct rtw89_btc_module_v10 { + u8 rfe_type; + u8 wa_type; /* Refer to enum btc_wa_type */ + u8 kt_ver; + u8 kt_ver_adie; + + u8 bt0_pos; /* wl-end view: get from efuse, must compare bt.btg_type*/ + u8 bt0_sw_type; /* BT Ant-switch: None(non-share), Int(BTG), Ext(SPDT)*/ + u8 bt1_pos; /* BTC_BT_ALONE or BTC_BT_BTG */ + u8 bt1_sw_type; + + struct rtw89_btc_ant_info_v10 ant; +} __packed; + union rtw89_btc_module_info { struct rtw89_btc_module md; struct rtw89_btc_module_v7 md_v7; + struct rtw89_btc_module_v10 md_v10; }; #define RTW89_BTC_DM_MAXSTEP 30 @@ -2170,9 +2204,24 @@ struct rtw89_btc_init_info_v7 { struct rtw89_btc_module_v7 module; } __packed; +struct rtw89_btc_init_info_v10 { + u8 endian_type; /* 0: little-endian, 1:big-endian */ + u8 init_mode; /* refer to enum BTC_MODE_xxx */ + u8 wl_init_ok; + u8 bt0_function; + + u8 bt1_function; + u8 bt2_function; + u8 pta_mode; + u8 pta_direction; + + struct rtw89_btc_module_v10 module; +}; + union rtw89_btc_init_info_u { struct rtw89_btc_init_info init; struct rtw89_btc_init_info_v7 init_v7; + struct rtw89_btc_init_info_v10 init_v10; }; struct rtw89_btc_wl_tx_limit_para { @@ -2253,6 +2302,7 @@ struct rtw89_btc_bt_info { u8 raw_info[BTC_BTINFO_MAX]; /* raw bt info from mailbox */ u8 txpwr_info[BTC_BTINFO_MAX]; u8 rssi_level; + u8 func_type; u32 scbd; u32 feature; @@ -3173,6 +3223,7 @@ struct rtw89_btc_dm { u8 run_reason; u8 run_action; u8 wl_tx_pwr_phy_map; + u8 vid; u8 wl_pre_agc: 2; u8 wl_lna2: 1; @@ -3203,7 +3254,7 @@ struct rtw89_btc_ctrl_v7 { union rtw89_btc_ctrl_list { struct rtw89_btc_ctrl ctrl; - struct rtw89_btc_ctrl_v7 ctrl_v7; + struct rtw89_btc_ctrl_v7 ctrl_v7; /* ver 8, 9 is the same */ }; struct rtw89_btc_dbg { @@ -3425,6 +3476,7 @@ struct rtw89_btc { u8 policy[RTW89_BTC_POLICY_MAXLEN]; u8 ant_type; u8 btg_pos; + u8 io_oflld_type; u16 policy_len; u16 policy_type; u32 hubmsg_cnt; diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 9d98805835d6..b97c6e9c18bc 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -5863,6 +5863,47 @@ int rtw89_fw_h2c_cxdrv_init_v7(struct rtw89_dev *rtwdev, u8 type) return ret; } +int rtw89_fw_h2c_cxdrv_init_v10(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_init_info_v10 *init_info = &dm->init_info.init_v10; + struct rtw89_h2c_cxinit_v10 *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + int ret; + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_init_v10\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxinit_v10 *)skb->data; + + h2c->hdr.type = type; + h2c->hdr.ver = btc->ver->fcxinit; + h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; + h2c->init = *init_info; + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, + len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + #define PORT_DATA_OFFSET 4 #define H2C_LEN_CXDRVINFO_ROLE_DBCC_LEN 12 #define H2C_LEN_CXDRVINFO_ROLE_SIZE(max_role_num) \ diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index 71e8554a7af7..de8b77de8705 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -2592,6 +2592,11 @@ struct rtw89_h2c_cxinit_v7 { struct rtw89_btc_init_info_v7 init; } __packed; +struct rtw89_h2c_cxinit_v10 { + struct rtw89_h2c_cxhdr_v7 hdr; + struct rtw89_btc_init_info_v10 init; +} __packed; + static inline void RTW89_SET_FWCMD_CXROLE_CONNECT_CNT(void *cmd, u8 val) { u8p_replace_bits((u8 *)(cmd) + 2, val, GENMASK(7, 0)); @@ -5380,6 +5385,7 @@ int rtw89_fw_h2c_tx_history(struct rtw89_dev *rtwdev, u16 mac_id); int rtw89_fw_h2c_drv_ctrl_fw(struct rtw89_dev *rtwdev); int rtw89_fw_h2c_cxdrv_init(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_init_v7(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxdrv_init_v10(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role_v1(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role_v2(struct rtw89_dev *rtwdev, u8 type); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b.c b/drivers/net/wireless/realtek/rtw89/rtw8851b.c index 4caf231c6287..a1a63588cb90 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851b.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851b.c @@ -2277,8 +2277,6 @@ static void rtw8851b_btc_init_cfg(struct rtw89_dev *rtwdev) /* enable BT counter 0xda40[16,2] = 2b'11 */ rtw89_write32_set(rtwdev, R_AX_CSR_MODE, B_AX_BT_CNT_RST | B_AX_STATIS_BT_EN); - - btc->cx.wl.status.map.init_ok = true; } static diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852a.c b/drivers/net/wireless/realtek/rtw89/rtw8852a.c index 78addc0aef69..055c67a07cea 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852a.c @@ -1945,7 +1945,6 @@ static void rtw8852a_btc_init_cfg(struct rtw89_dev *rtwdev) /* enable BT counter 0xda40[16,2] = 2b'11 */ rtw89_write32_set(rtwdev, R_AX_CSR_MODE, B_AX_BT_CNT_RST | B_AX_STATIS_BT_EN); - btc->cx.wl.status.map.init_ok = true; } static diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852b_common.c b/drivers/net/wireless/realtek/rtw89/rtw8852b_common.c index df5fbae50ff5..7d409a64869f 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852b_common.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852b_common.c @@ -1838,7 +1838,6 @@ static void __rtw8852bx_btc_init_cfg(struct rtw89_dev *rtwdev) /* enable BT counter 0xda40[16,2] = 2b'11 */ rtw89_write32_set(rtwdev, R_AX_CSR_MODE, B_AX_BT_CNT_RST | B_AX_STATIS_BT_EN); - btc->cx.wl.status.map.init_ok = true; } static diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852c.c b/drivers/net/wireless/realtek/rtw89/rtw8852c.c index 29a3c90021f3..9e630b897986 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852c.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852c.c @@ -2761,7 +2761,6 @@ static void rtw8852c_btc_init_cfg(struct rtw89_dev *rtwdev) rtw89_write32_set(rtwdev, R_AX_BT_CNT_CFG, B_AX_BT_CNT_EN | B_AX_BT_CNT_RST_V1); - btc->cx.wl.status.map.init_ok = true; } static diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922a.c b/drivers/net/wireless/realtek/rtw89/rtw8922a.c index 6d4301661b04..382034eb27d0 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922a.c @@ -2828,7 +2828,6 @@ static void rtw8922a_btc_init_cfg(struct rtw89_dev *rtwdev) rtw89_write32(rtwdev, R_BTC_ZB_COEX_TBL_1, 0xda5a5a5a); rtw89_write32(rtwdev, R_BTC_ZB_BREAK_TBL, 0xf0ffffff); - btc->cx.wl.status.map.init_ok = true; } static void From c595e0a0958c5b700eed0a91ce0bf25290587f0f Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Mon, 6 Jul 2026 10:54:38 +0800 Subject: [PATCH 0270/1433] wifi: rtw89: coex: add rtw89_btc_init() entry for initialization once Separate these two type of initialize entry. Because Wi-Fi power save leaving will also call the initializing, but don't have to reset all the stored variables. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260706025445.18428-3-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 35 ++++++++++++++--------- drivers/net/wireless/realtek/rtw89/coex.h | 1 + drivers/net/wireless/realtek/rtw89/core.c | 1 + 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 43de238c18f8..196bec751070 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -7858,6 +7858,28 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) _action_common(rtwdev); } +void rtw89_btc_init(struct rtw89_dev *rtwdev) +{ + const struct rtw89_chip_info *chip = rtwdev->chip; + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_wl_info *wl = &btc->cx.wl; + const struct rtw89_btc_ver *ver = btc->ver; + + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(): Init %s !!\n", __func__, + chip_id_str(chip->chip_id)); + + _reset_btc_var(rtwdev, BTC_RESET_ALL); + + btc->dm.run_reason = BTC_RSN_NONE; + btc->dm.run_action = BTC_ACT_NONE; + if (ver->fcxctrl >= 7) + btc->ctrl.ctrl_v7.igno_bt = true; + else + btc->ctrl.ctrl.igno_bt = true; + wl->status.map.init_ok = true; +} + void rtw89_btc_ntfy_poweron(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; @@ -7941,19 +7963,6 @@ void rtw89_btc_ntfy_init(struct rtw89_dev *rtwdev, u8 mode) const struct rtw89_chip_info *chip = rtwdev->chip; const struct rtw89_btc_ver *ver = btc->ver; - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): Init %s !!\n", __func__, - chip_id_str(chip->chip_id)); - - _reset_btc_var(rtwdev, BTC_RESET_ALL); - btc->dm.run_reason = BTC_RSN_NONE; - btc->dm.run_action = BTC_ACT_NONE; - if (ver->fcxctrl >= 7) - btc->ctrl.ctrl_v7.igno_bt = true; - else - btc->ctrl.ctrl.igno_bt = true; - wl->status.map.init_ok = true; - rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): mode=%d\n", __func__, mode); diff --git a/drivers/net/wireless/realtek/rtw89/coex.h b/drivers/net/wireless/realtek/rtw89/coex.h index 259c6e2c0e3c..fb151f68eb64 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.h +++ b/drivers/net/wireless/realtek/rtw89/coex.h @@ -272,6 +272,7 @@ enum btc_wl_gpio_debug { BTC_DBG_USER_DEF = 31, }; +void rtw89_btc_init(struct rtw89_dev *rtwdev); void rtw89_btc_ntfy_poweron(struct rtw89_dev *rtwdev); void rtw89_btc_ntfy_poweroff(struct rtw89_dev *rtwdev); void rtw89_btc_ntfy_init(struct rtw89_dev *rtwdev, u8 mode); diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index 26b744dfbcf8..85aeb9e90812 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -7488,6 +7488,7 @@ static int rtw89_core_register_hw(struct rtw89_dev *rtwdev) } rtw89_rfkill_polling_init(rtwdev); + rtw89_btc_init(rtwdev); return 0; From 2c5af470819ba6c8a996c297d2beb06987215f8f Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Mon, 6 Jul 2026 10:54:39 +0800 Subject: [PATCH 0271/1433] wifi: rtw89: coex: Update TDMA descriptor for dual MAC The mechanism needs information to know which MAC is coexisting with which Bluetooth and when to enable TDMA with which MAC. So change an variable to describe the binding target. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260706025445.18428-4-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 15 ++++++++------- drivers/net/wireless/realtek/rtw89/core.h | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 196bec751070..14cc62cf399d 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -2288,9 +2288,9 @@ static void _append_tdma(struct rtw89_dev *rtwdev) } rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): type:%d, rxflctrl=%d, txpause=%d, wtgle_n=%d, leak_n=%d, ext_ctrl=%d\n", + "[BTC], %s(): type:%d, rxflctrl=%d, txflctrl=%d, bind=%d, leak_n=%d, ext_ctrl=%d\n", __func__, dm->tdma.type, dm->tdma.rxflctrl, - dm->tdma.txpause, dm->tdma.wtgle_n, dm->tdma.leak_n, + dm->tdma.txflctrl, dm->tdma.bind, dm->tdma.leak_n, dm->tdma.ext_ctrl); } @@ -3733,7 +3733,8 @@ static bool _check_freerun(struct rtw89_dev *rtwdev) #define _tdma_set_flctrl(btc, flc) ({(btc)->dm.tdma.rxflctrl = flc; }) #define _tdma_set_flctrl_role(btc, role) ({(btc)->dm.tdma.rxflctrl_role = role; }) -#define _tdma_set_tog(btc, wtg) ({(btc)->dm.tdma.wtgle_n = wtg; }) +#define _tdma_set_rxflctrl(btc, rxflc) ({(btc)->dm.tdma.rxflctrl = rxflc; }) +#define _tdma_set_txflctrl(btc, txflc) ({(btc)->dm.tdma.txflctrl = txflc; }) #define _tdma_set_lek(btc, lek) ({(btc)->dm.tdma.leak_n = lek; }) struct btc_btinfo_lb2 { @@ -9967,13 +9968,13 @@ static int _show_fbtc_tdma(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, " %-15s : ", "[tdma_policy]"); p += scnprintf(p, end - p, - "type:%d, rx_flow_ctrl:%d, tx_pause:%d, ", + "type:%d, rx_flow_ctrl:%d, txflctrl:%d, ", (u32)t->type, - t->rxflctrl, t->txpause); + t->rxflctrl, t->txflctrl); p += scnprintf(p, end - p, - "wl_toggle_n:%d, leak_n:%d, ext_ctrl:%d, ", - t->wtgle_n, t->leak_n, t->ext_ctrl); + "bind:%d, leak_n:%d, ext_ctrl:%d, ", + t->bind, t->leak_n, t->ext_ctrl); p += scnprintf(p, end - p, "policy_type:%d", diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 1e72c9b9f3b7..a0f6929873ab 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -2354,8 +2354,8 @@ struct rtw89_btc_cx { struct rtw89_btc_fbtc_tdma { u8 type; /* btc_ver::fcxtdma */ u8 rxflctrl; - u8 txpause; - u8 wtgle_n; + u8 txflctrl; + u8 bind; u8 leak_n; u8 ext_ctrl; u8 rxflctrl_role; From 2b497ba92abe3f31a66643e5d05a7b27a1de0c56 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Mon, 6 Jul 2026 10:54:40 +0800 Subject: [PATCH 0272/1433] wifi: rtw89: coex: Add Bluetooth binding for Bluetooth TX power setting Dual Bluetooth the each of Bluetooth may use different TX power by their condition. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260706025445.18428-5-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 65 ++++++++++++++++++----- drivers/net/wireless/realtek/rtw89/core.h | 15 ++++-- drivers/net/wireless/realtek/rtw89/fw.h | 3 ++ 3 files changed, 67 insertions(+), 16 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 14cc62cf399d..2080253559a7 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -3078,6 +3078,7 @@ static void _set_bt_ignore_wlan_act(struct rtw89_dev *rtwdev, u8 enable) #define WL_TX_POWER_FRA_PART GENMASK(1, 0) #define B_BTC_WL_TX_POWER_SIGN BIT(7) #define B_TSSI_WL_TX_POWER_SIGN BIT(8) +#define SET_RF_PARA_AX_LEN 1 static void _set_wl_tx_power(struct rtw89_dev *rtwdev, u32 level, u8 phy_map) { @@ -3161,28 +3162,64 @@ static void _set_wl_rx_gain(struct rtw89_dev *rtwdev, u32 level, u8 phy_map) chip->ops->btc_set_wl_rx_gain(rtwdev, level); } -static void _set_bt_tx_power(struct rtw89_dev *rtwdev, u8 level) +static void _set_bt_tx_power(struct rtw89_dev *rtwdev, bool force_exec, u8 bid, + u8 rf_band, u8 level) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_bt_info *bt = &btc->cx.bt0; - int ret; - u8 buf; + u8 h2c_func = SET_BT_TX_PWR; + u8 i, id_start, id_stop; + u8 buf[2] = {}; + u8 len = sizeof(*buf); - if (bt->bcnt[BTC_BCNT_INFOUPDATE] == 0) + if (bt->bcnt[BTC_BCNT_INFOUPDATE] == 0 || !rf_band) return; if (bt->rf_para.tx_pwr_freerun == level) return; - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): level = %d\n", - __func__, level); + if (bid == BTC_ALL_BT) { + id_start = BTC_BT_1ST; + id_stop = BTC_BT_2ND; + } else { + id_start = bid; + id_stop = bid; + } - buf = (s8)(-level); - ret = _send_fw_cmd(rtwdev, BTFC_SET, SET_BT_TX_PWR, &buf, 1); - if (!ret) { - bt->rf_para.tx_pwr_freerun = level; - btc->dm.rf_trx_para.bt_tx_power[BTC_BT_1ST] = level; + for (i = id_start; i <= id_stop; i++) { + if (i == BTC_BT_2ND) { + if (!(rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT)) + continue; + + bt = &btc->cx.bt1; + h2c_func |= BT_H2C_FUNC_BT2ND; + } + + buf[0] = (s8)(-level); + buf[1] = rf_band; /* bit-map: bit1->5GHz/6Ghz, bit0->2.4GHz */ + + if (!force_exec && !btc->cli_h2c_cmd) { + if (rf_band == RTW89_BAND_2G && + bt->tx_power_now == level) + continue; + else if (rf_band != RTW89_BAND_2G && + bt->tx_power_now_6g == level) + continue; + } + + if (rtwdev->chip->chip_gen == RTW89_CHIP_AX) + len = SET_RF_PARA_AX_LEN; + + if (_send_fw_cmd(rtwdev, BTFC_SET, h2c_func, buf, len)) { + btc->dm.rf_trx_para.bt_tx_power[i] = level; + if (rf_band == RTW89_BAND_2G) + bt->tx_power_now = level; + else + bt->tx_power_now_6g = level; + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(): bt%d_tx_power_level = %d\n", + __func__, i, level); + } } } @@ -3229,6 +3266,8 @@ static void _set_rf_trx_para(struct rtw89_dev *rtwdev) struct rtw89_btc_rf_trx_para_v9 para; u8 lv, link_mode = 0, i, dbcc_2g_phy = 0; u8 ul_para_num, dl_para_num; + u8 rf_band = RTW89_BAND_2G; + u8 bid = BTC_BT_1ST; u32 wl_stb_chg = 0; if (ver->fwlrole == 0) { @@ -3321,7 +3360,7 @@ static void _set_rf_trx_para(struct rtw89_dev *rtwdev) } else { _set_wl_tx_power(rtwdev, para.wl_tx_power[RTW89_PHY_0], RTW89_PHY_0); _set_wl_rx_gain(rtwdev, para.wl_rx_gain[RTW89_PHY_0], RTW89_PHY_0); - _set_bt_tx_power(rtwdev, para.bt_tx_power[BTC_BT_1ST]); + _set_bt_tx_power(rtwdev, true, bid, rf_band, para.bt_tx_power[BTC_BT_1ST]); _set_bt_rx_gain(rtwdev, para.bt_rx_gain[BTC_BT_1ST]); } diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index a0f6929873ab..3f77707e2733 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -2302,7 +2302,18 @@ struct rtw89_btc_bt_info { u8 raw_info[BTC_BTINFO_MAX]; /* raw bt info from mailbox */ u8 txpwr_info[BTC_BTINFO_MAX]; u8 rssi_level; + u8 rf_band_map; u8 func_type; + u8 tx_power_now; + u8 tx_power_now_6g; + + u8 fw_ver_mismatch: 1; + u8 band_56G_support: 1; + u8 hi_lna_rx: 1; + u8 lna_constrain: 3; + u8 hi_lna_rx_6g: 1; + u8 lna_constrain_6g: 3; + u8 rsvd: 6; u32 scbd; u32 feature; @@ -2316,11 +2327,9 @@ struct rtw89_btc_bt_info { u32 inq: 1; u32 pag: 1; u32 run_patch_code: 1; - u32 hi_lna_rx: 1; u32 scan_rx_low_pri: 1; u32 scan_info_update: 1; - u32 lna_constrain: 3; - u32 rsvd: 17; + u32 rsvd1: 22; u32 bcnt[BTC_BCNT_NUM]; }; diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index de8b77de8705..a6f3b28b9e33 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -2337,6 +2337,9 @@ struct rtw89_h2c_arp_offload { #define RTW89_H2C_ARP_OFFLOAD_W0_PKT_ID GENMASK(31, 24) #define RTW89_H2C_ARP_OFFLOAD_W1_CONTENT GENMASK(31, 0) +#define BT_H2C_FUNC_BT2ND 0x80 +#define BT_C2H_FUNC_BT2ND 0x80 + enum rtw89_btc_btf_h2c_class { BTFC_SET = 0x10, BTFC_GET = 0x11, From fe8f6ddb9095caa156983f920922d5f8b9b25e1c Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Mon, 6 Jul 2026 10:54:41 +0800 Subject: [PATCH 0273/1433] wifi: rtw89: coex: Add Bluetooth binding for Bluetooth RX gain setting Dual Bluetooth the each of Bluetooth may use different RX gain by their condition. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260706025445.18428-6-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 86 ++++++++++++++++++----- 1 file changed, 68 insertions(+), 18 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 2080253559a7..ff3c05101ab3 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -730,6 +730,7 @@ enum btc_w2b_scoreboard { BTC_WSCB_SCAN = BIT(2), BTC_WSCB_UNDERTEST = BIT(3), BTC_WSCB_RXGAIN = BIT(4), + BTC_WSCB_5GHICH = BIT(6), BTC_WSCB_WLBUSY = BIT(7), BTC_WSCB_EXTFEM = BIT(8), BTC_WSCB_TDMA = BIT(9), @@ -738,6 +739,9 @@ enum btc_w2b_scoreboard { BTC_WSCB_RXSCAN_PRI = BIT(12), BTC_WSCB_BT_HILNA = BIT(13), BTC_WSCB_BTLOG = BIT(14), + BTC_WSCB_CTCODE = BIT(15), + BTC_WSCB_RXGAIN_56G = BIT(16), + BTC_WSCB_BT_HILNA_56G = BIT(17), BTC_WSCB_ALL = GENMASK(23, 0), }; @@ -3224,33 +3228,79 @@ static void _set_bt_tx_power(struct rtw89_dev *rtwdev, bool force_exec, u8 bid, } #define BTC_BT_RX_NORMAL_LVL 7 - -static void _set_bt_rx_gain(struct rtw89_dev *rtwdev, u8 level) +static void _set_bt_rx_gain(struct rtw89_dev *rtwdev, bool force_exec, u8 bid, + u8 rf_band, u8 level) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_info *bt = &btc->cx.bt0; + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_bt_info *bt = &cx->bt0; + u8 h2c_func = SET_BT_LNA_CONSTRAIN; + u8 i, id_start, id_stop; + bool state = false; + u32 scbd_bit = 0; + u8 buf[2] = {}; + u8 len = sizeof(*buf); - if (bt->bcnt[BTC_BCNT_INFOUPDATE] == 0) + if (bt->bcnt[BTC_BCNT_INFOUPDATE] == 0 || !rf_band) return; - if ((bt->rf_para.rx_gain_freerun == level || - level > BTC_BT_RX_NORMAL_LVL) && - (!rtwdev->chip->scbd || bt->lna_constrain == level)) + if (bt->rf_para.rx_gain_freerun == level || + level > BTC_BT_RX_NORMAL_LVL || !rtwdev->chip->scbd || + bid > BTC_ALL_BT) return; - bt->rf_para.rx_gain_freerun = level; - btc->dm.rf_trx_para.bt_rx_gain[BTC_BT_1ST] = level; + if (bid == BTC_ALL_BT) { + id_start = BTC_BT_1ST; + id_stop = BTC_BT_2ND; + } else { + id_start = bid; + id_stop = bid; + } - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): level = %d\n", - __func__, level); - - if (level == BTC_BT_RX_NORMAL_LVL) - _write_scbd(rtwdev, BTC_WSCB_RXGAIN, false); + if (rf_band == RTW89_BAND_2G) + scbd_bit |= BTC_WSCB_RXGAIN; else - _write_scbd(rtwdev, BTC_WSCB_RXGAIN, true); + scbd_bit |= BTC_WSCB_RXGAIN_56G; + + for (i = id_start; i <= id_stop; i++) { + if (i == BTC_BT_2ND) { + if (!(rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT)) + continue; + + bt = &cx->bt1; + h2c_func |= BT_H2C_FUNC_BT2ND; + } + + /* return if same setup */ + if (!force_exec && !btc->cli_h2c_cmd) { + if (rf_band == RTW89_BAND_2G && + bt->lna_constrain == level) + continue; + else if (rf_band != RTW89_BAND_2G && + bt->lna_constrain_6g == level) + continue; + } + + buf[0] = level; + buf[1] = rf_band; + + if (rtwdev->chip->chip_gen == RTW89_CHIP_AX) + len = SET_RF_PARA_AX_LEN; + + if (_send_fw_cmd(rtwdev, BTFC_SET, h2c_func, buf, len)) { + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(): bt%d_rx_gain_level = %d\n", + __func__, i, level); + } + + btc->dm.rf_trx_para.bt_rx_gain[i] = level; + + if (buf[0] != BTC_BT_RX_NORMAL_LVL) + state = true; + + _write_scbd(rtwdev, scbd_bit, state); + } - _send_fw_cmd(rtwdev, BTFC_SET, SET_BT_LNA_CONSTRAIN, &level, sizeof(level)); } static void _set_rf_trx_para(struct rtw89_dev *rtwdev) @@ -3361,7 +3411,7 @@ static void _set_rf_trx_para(struct rtw89_dev *rtwdev) _set_wl_tx_power(rtwdev, para.wl_tx_power[RTW89_PHY_0], RTW89_PHY_0); _set_wl_rx_gain(rtwdev, para.wl_rx_gain[RTW89_PHY_0], RTW89_PHY_0); _set_bt_tx_power(rtwdev, true, bid, rf_band, para.bt_tx_power[BTC_BT_1ST]); - _set_bt_rx_gain(rtwdev, para.bt_rx_gain[BTC_BT_1ST]); + _set_bt_rx_gain(rtwdev, true, bid, rf_band, para.bt_rx_gain[BTC_BT_1ST]); } next: From 564dd7a9504767bbf1ed9c855f334f042ee4ae7d Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Mon, 6 Jul 2026 10:54:42 +0800 Subject: [PATCH 0274/1433] wifi: rtw89: coex: Add WiFi/Bluetooth adapter binding info To bind Wi-Fi/Bluetooth with which adapter, in which band. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260706025445.18428-7-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 309 ++++++++++++++++++++-- drivers/net/wireless/realtek/rtw89/core.h | 123 +++++++-- 2 files changed, 397 insertions(+), 35 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index ff3c05101ab3..f2dea0e8c8f2 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -3349,7 +3349,7 @@ static void _set_rf_trx_para(struct rtw89_dev *rtwdev) /* decide trx_para_level */ if (btc->ant_type == BTC_ANT_SHARED) { /* fix LNA2 + TIA gain not change by GNT_BT */ - if ((btc->dm.wl_btg_rx && b->profile_cnt.now != 0) || + if ((btc->dm.wl_btg_rx && b->link_cnt.now != 0) || dm->bt_only == 1) dm->trx_para_level = 1; /* for better BT ACI issue */ else @@ -3357,7 +3357,7 @@ static void _set_rf_trx_para(struct rtw89_dev *rtwdev) } else { /* non-shared antenna */ dm->trx_para_level = 5; /* modify trx_para if WK 2.4G-STA-DL + bt link */ - if (b->profile_cnt.now != 0 && + if (b->link_cnt.now != 0 && link_mode == BTC_WLINK_2G_STA && wl->status.map.traffic_dir & BIT(RTW89_TFC_UL)) { /* uplink */ if (wl->rssi_level == 4 && bt->rssi_level > 2) @@ -3606,7 +3606,7 @@ static void _set_bt_afh_info_v0(struct rtw89_dev *rtwdev) if (wl->afh_info.en == en && wl->afh_info.ch == ch && wl->afh_info.bw == bw && - b->profile_cnt.last == b->profile_cnt.now) { + b->link_cnt.last == b->link_cnt.now) { rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): return because no change!\n", __func__); @@ -3777,7 +3777,7 @@ static bool _check_freerun(struct rtw89_dev *rtwdev) return true; } - if (bt_linfo->profile_cnt.now == 0) { + if (bt_linfo->link_cnt.now == 0) { btc->dm.trx_para_level = 5; return true; } @@ -5497,7 +5497,7 @@ static void _set_wl_preagc_ctrl(struct rtw89_dev *rtwdev) } else if (link_mode == BTC_WLINK_5G) { is_preagc = BTC_PREAGC_DISABLE; } else if (link_mode == BTC_WLINK_NOLINK || - btc->cx.bt0.link_info.profile_cnt.now == 0) { + btc->cx.bt0.link_info.link_cnt.now == 0) { is_preagc = BTC_PREAGC_DISABLE; } else if (dm->tdma_now.type != CXTDMA_OFF && !bt_linfo->hfp_desc.exist && @@ -5683,7 +5683,7 @@ static void _set_wl_tx_limit(struct rtw89_dev *rtwdev) else igno_bt = btc->ctrl.ctrl.igno_bt; - if (btc->dm.freerun || igno_bt || b->profile_cnt.now == 0 || + if (btc->dm.freerun || igno_bt || b->link_cnt.now == 0 || mode == BTC_WLINK_5G || mode == BTC_WLINK_NOLINK) { enable = 0; tx_time = BTC_MAX_TX_TIME_DEF; @@ -6003,7 +6003,7 @@ static void _action_wl_2g_mcc(struct rtw89_dev *rtwdev) _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); if (btc->ant_type == BTC_ANT_SHARED) { /* shared-antenna */ - if (btc->cx.bt0.link_info.profile_cnt.now == 0) + if (btc->cx.bt0.link_info.link_cnt.now == 0) _set_policy(rtwdev, BTC_CXP_OFFE_DEF2, BTC_ACT_WL_2G_MCC); else @@ -6021,7 +6021,7 @@ static void _action_wl_2g_scc(struct rtw89_dev *rtwdev) _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); if (btc->ant_type == BTC_ANT_SHARED) { /* shared-antenna */ - if (btc->cx.bt0.link_info.profile_cnt.now == 0) + if (btc->cx.bt0.link_info.link_cnt.now == 0) _set_policy(rtwdev, BTC_CXP_OFFE_DEF2, BTC_ACT_WL_2G_SCC); else @@ -6199,7 +6199,7 @@ static void _action_wl_2g_ap(struct rtw89_dev *rtwdev) _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); if (btc->ant_type == BTC_ANT_SHARED) { - if (btc->cx.bt0.link_info.profile_cnt.now == 0) + if (btc->cx.bt0.link_info.link_cnt.now == 0) _set_policy(rtwdev, BTC_CXP_OFFE_DEF2, BTC_ACT_WL_2G_AP); else @@ -6216,7 +6216,7 @@ static void _action_wl_2g_go(struct rtw89_dev *rtwdev) _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); if (btc->ant_type == BTC_ANT_SHARED) { /* shared-antenna */ - if (btc->cx.bt0.link_info.profile_cnt.now == 0) + if (btc->cx.bt0.link_info.link_cnt.now == 0) _set_policy(rtwdev, BTC_CXP_OFFE_DEF2, BTC_ACT_WL_2G_GO); else @@ -6247,7 +6247,7 @@ static void _action_wl_2g_nan(struct rtw89_dev *rtwdev) _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); if (btc->ant_type == BTC_ANT_SHARED) { /* shared-antenna */ - if (btc->cx.bt0.link_info.profile_cnt.now == 0) + if (btc->cx.bt0.link_info.link_cnt.now == 0) _set_policy(rtwdev, BTC_CXP_OFFE_DEF2, BTC_ACT_WL_2G_NAN); else @@ -7742,6 +7742,273 @@ static bool _chk_wl_rfk_request(struct rtw89_dev *rtwdev) return false; } +static void _set_bind_info(struct rtw89_btc *btc, u8 type) +{ + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_wl_info *wl = &cx->wl; + struct rtw89_btc_bt_info *bt = &cx->bt0; + struct rtw89_btc_bt_link_info *b; + struct rtw89_btc_bind_info *bd; + u8 path_hwb[BTC_RF_NUM] = {RTW89_PHY_0, RTW89_PHY_1}; + u8 link_weight[BTC_ALL_BT_EZL][BTC_BT_BMAX]; + u8 map[BTC_RF_NUM][BTC_ALL_BT_EZL]; + u8 b2g_score = 0, b5g_score = 0; + u8 i, j, rf_band_map, thres; + u8 weight[BTC_BT_BMAX] = {}; + + if (type == BTC_MECH_TDD) { + bd = &dm->tdd_bind; + memcpy(map, dm->tdd_map, sizeof(map)); + thres = 30; /* BT profile score threshold for TDMA */ + } else { + bd = &dm->fdd_bind; + memcpy(map, dm->fdd_map, sizeof(map)); + thres = 5; + } + + memset(bd, 0, sizeof(*bd)); + /* compare BT 2GHz/5GHz profile by link-weighting */ + for (i = 0; i < BTC_BT_BMAX; i++) { + link_weight[BTC_BT_1ST][i] = cx->bt0.link_weight[i]; + link_weight[BTC_BT_2ND][i] = cx->bt1.link_weight[i]; + link_weight[BTC_BT_EXT][i] = cx->bt_ext.link_weight[i]; + } + + /* + * get 2GHz/5GHz link weight score by "band-overlap" map + * link_weight from _update_bt_link_cnt(), + * if link_weight >30 --> TDMA-required + */ + for (j = 0; j < BTC_ALL_BT_EZL; j++) { + rf_band_map = map[BTC_RF_S0][j] | map[BTC_RF_S1][j]; + if (rf_band_map & BIT(RTW89_BAND_2G)) + b2g_score += link_weight[j][BTC_BT_B2G]; + if (rf_band_map & BIT(RTW89_BAND_5G)) + b5g_score += link_weight[j][BTC_BT_B5G]; + } + + /* rf-band bound by comparing link weight */ + if (b2g_score == 0 && b5g_score == 0) /* no-rf-band overlap */ + bd->rf_band = 0; + else if (b5g_score > b2g_score) + bd->rf_band = BIT(BTC_BT_B5G); + else + bd->rf_band = BIT(BTC_BT_B2G); + + /* + * if MR_WTYPE_MLD2L1R_NONMLD, FW will change 2+0/0+2/1+1 + * Therefore, BTC_MLO_RF_xxx is not real-time state. + * In this case, RF_S0->HWB0, RF_S1->HWB1 + * If link-mode change by _ntfy_generic(BTC_GNTFY_MRCX_INFO) + * HW-Band is decided by wl->mlo_info.mrcx_act_hwb_map + */ + if (wl->mlo_info.wtype == RTW89_MR_WTYPE_MLD2L1R_NONMLD) { + /* TODO: Should patched WiFi mode & WiFi role patch */ + } else { + /* + * Dual-RF_band(HWB)TDMA if BT-profile is TDMA-type at both + * 2GHz and 5GHz. + * ex: 1+1: HWB0 5GHz + BT0 5GHz (BIS, HDT) + * && HWB1 2.4GHz + BT1 2.4GHz(PAN) + * for TDD: the link score must > 30 (from _bt_link_cnt) + * for FDD: the link score must > 5 (bt enable) + */ + if ((wl->mlo_info.rf_combination == BTC_MLO_RF_1_PLUS_1 || + wl->mlo_info.rf_combination == BTC_MLO_RF_2_PLUS_2) && + (b2g_score >= thres && b5g_score >= thres)) { + bd->rf_band = BIT(BTC_BT_B2G) | BIT(BTC_BT_B5G); + } + + if (wl->mlo_info.rf_combination == BTC_MLO_RF_2_PLUS_0) + path_hwb[BTC_RF_S1] = RTW89_PHY_0; /* 2+0 RF-S0/1->HWB0 */ + else if (wl->mlo_info.rf_combination == BTC_MLO_RF_0_PLUS_2) + path_hwb[BTC_RF_S0] = RTW89_PHY_1; /* 2+0 RF-S0/1->HWB1 */ + } + + /* Get HWB-sel and BT-sel by rf-band-binding */ + for (i = 0; i < BTC_RF_NUM; i++) { + for (j = 0; j < BTC_ALL_BT_EZL; j++) { + if (!(map[i][j] & bd->rf_band)) /* no-overlap */ + continue; + + bd->wl_hwb_sel |= BIT(path_hwb[i]); + bd->bt_sel |= BIT(j); + } + } + + /* TODO: Should patched WiFi mode & WiFi role patch */ + + /* update Bind-BT status map for BT0/BT1 */ + for (i = BTC_BT_1ST; i < BTC_ALL_BT_EZL; i++) { + if (!(bd->bt_sel & BIT(i))) + continue; + + for (j = BTC_BT_B2G; j <= BTC_BT_B5G; j++) { + if (!(bd->rf_band & BIT(j))) + continue; + + if (i == BTC_BT_EXT) { + bd->bt_profile |= cx->bt_ext.profile_map[j]; + weight[j] = cx->bt_ext.link_weight[j]; + if (weight[j] > bd->bt_link_weight) /* max */ + bd->bt_link_weight = weight[j]; + continue; + } else if (i == BTC_BT_1ST) { + bt = &cx->bt0; + } else { + bt = &cx->bt1; + } + + if (j == BTC_BT_B2G) + b = &bt->link_info; + else + b = &bt->link_info_56g; + + bd->bt_profile |= b->status.map.profile_map; + bd->bt_smap.a2dp_active |= b->a2dp_desc.active; + bd->bt_smap.a2dp_sink |= b->a2dp_desc.sink; + bd->bt_smap.pan_active |= b->pan_desc.active; + bd->bt_smap.connect |= b->status.map.connect; + bd->bt_smap.hid_cnt += (u8)b->hid_desc.pair_cnt; + bd->bt_smap.hid_type |= b->hid_desc.type; + bd->bt_smap.cis_cnt += (u8)b->leaudio_desc.cis_cnt; + bd->bt_smap.link_cnt += b->link_cnt.now; + bd->bt_smap.inq_page |= b->status.map.inq_pag; + bd->bt_smap.page |= b->pag; + + if (bt->link_weight[j] > bd->bt_link_weight) /* max */ + bd->bt_link_weight = bt->link_weight[j]; + + if (b->slave_role) + bd->bt_smap.slave_role = b->slave_role; + + if (b->a2dp_desc.vendor_id != 0) + bd->bt_smap.a2dp_vendor_id = + b->a2dp_desc.vendor_id; + } + } + + if (bd->bt_profile & BTC_BT_HFP) + bd->bt_smap.hfp_exist = 1; + if (bd->bt_profile & BTC_BT_HID) + bd->bt_smap.hid_exist = 1; + if (bd->bt_profile & BTC_BT_A2DP) + bd->bt_smap.a2dp_exist = 1; + if (bd->bt_profile & BTC_BT_PAN) + bd->bt_smap.pan_exist = 1; + if (bd->bt_profile & BTC_BT_BIS) + bd->bt_smap.bis_exist = 1; + if (bd->bt_profile & BTC_BT_CIS) + bd->bt_smap.cis_exist = 1; + if (bd->bt_profile & BTC_BT_THREAD) + bd->bt_smap.thread_exist = 1; + if (bd->bt_profile & BTC_BT_ULL) + bd->bt_smap.ull_exist = 1; +} + +#define _bind_is_btonly 0x7 +static void _set_coex_binding(struct rtw89_btc *btc) +{ + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_extsoc_info *bt2 = &cx->bt_ext; + struct rtw89_btc_bt_info *bt0 = &cx->bt0; + struct rtw89_btc_bt_info *bt1 = &cx->bt1; + struct rtw89_btc_wl_info *wl = &cx->wl; + struct rtw89_btc_dm *dm = &btc->dm; + u8 path_hwb[BTC_RF_NUM] = {RTW89_PHY_0, RTW89_PHY_1}; + u8 wl_rf_band[RTW89_BAND_NUM] = {}; + u8 i, j, val = 0; + + /* + * sit_xmap(Space-Interaction) = ant_xmap | xtk_xmap + * 1: WL-BT space-interference, always 1 if BTG/BTA/SPDT = 1 + * if dedicated-ant, it may be 1 if BT-Tx is bigger than WL-Rx(xtk_xmap) + * + * ant_xmap(ANT-Division-Multiplexing) + * ==> dedicated-ant->0, BTG/BTA/SPDT->1 + * + * xtk_xmap(Cross-talk map)-> calculate WL/BT interference by SIR + * ==> 1: interference, 0: no-interference + * + * fdm_map(Frequency-Division-Multiplexing): WL/BT RF_Band overlap-map + * 2bit-map: bit[1]:5GHz/6GHz, bit[0]:2.4GHz + */ + for (i = 0; i < RTW89_PHY_NUM; i++) { + if (wl->rf_band_map[i] & BIT(RTW89_BAND_2G)) + wl_rf_band[i] |= BIT(RTW89_BAND_2G); + + if (wl->rf_band_map[i] & (BIT(RTW89_BAND_5G) | BIT(RTW89_BAND_6G))) + wl_rf_band[i] |= BIT(RTW89_BAND_5G); + + dm->fit_xmap[i][BTC_BT_1ST] = wl_rf_band[i] & bt0->rf_band_map; + dm->fit_xmap[i][BTC_BT_2ND] = wl_rf_band[i] & bt1->rf_band_map; + dm->fit_xmap[i][BTC_BT_EXT] = wl_rf_band[i] & bt2->rf_band_map; + } + + /* + * if MR_WTYPE_MLD2L1R_NONMLD, FW will change 2+0/0+2/1+1 + * Therefore, BTC_MLO_RF_xxx is not real-time state. + * if link mode change by _ntfy_generic(BTC_GNTFY_MRCX_INFO) + * the HW-BAND is decided by wl->mlo_info.mrcx_act_hwb_map + * In this case, BTC_RF_S0->HWB0, BTC_RF_S1->HWB1 + */ + if (wl->mlo_info.wtype == RTW89_MR_WTYPE_MLD2L1R_NONMLD) { + if (wl->role_info.link_mode != BTC_WLINK_2G_MCC && + wl->role_info.link_mode != BTC_WLINK_25G_MCC && + wl->role_info.link_mode != BTC_WLINK_25G_DBCC) {/* mode chg */ + if (wl->mlo_info.mrcx_act_hwb_map == BIT(RTW89_PHY_1)) + path_hwb[BTC_RF_S0] = RTW89_PHY_1;/* S0/1->HWB1 */ + else + path_hwb[BTC_RF_S1] = RTW89_PHY_0;/* S0/1->HWB0 */ + } + } else { + if (wl->mlo_info.rf_combination == BTC_MLO_RF_2_PLUS_0) + path_hwb[BTC_RF_S1] = RTW89_PHY_0; /* 2+0 RF-S0/1->HWB0 */ + else if (wl->mlo_info.rf_combination == BTC_MLO_RF_0_PLUS_2) + path_hwb[BTC_RF_S0] = RTW89_PHY_1; /* 2+0 RF-S0/1->HWB1 */ + } + + /* + * tdd_map = sit_xmap * fdm_map, 1: WL-RF-Sx vs. BTx take TDD-Action + * fdd_map =(!sit_xmap) * fdm_map, 1: WL-RF-Sx vs.BTx take FDD-Action + * co-rx map = ant_xmap * fdm_map 1:WL/BT co-rx (for halbb-btg-ctrl) + * sit_xmap,ant_xmap = 0 or 1, so tdd/fdd use multiplication (*) + */ + + for (i = 0; i < BTC_RF_NUM; i++) + for (j = 0; j < BTC_ALL_BT_EZL; j++) { + dm->tdd_map[i][j] = dm->sit_xmap[i][j] * + dm->fit_xmap[path_hwb[i]][j]; + dm->fdd_map[i][j] = !dm->sit_xmap[i][j] * + dm->fit_xmap[path_hwb[i]][j]; + dm->corx_map[i][j] = dm->ant_xmap[i][j] * + dm->fit_xmap[path_hwb[i]][j]; + } + + /* TDD-Binding */ + _set_bind_info(btc, BTC_MECH_TDD); + + /* FDD-Binding */ + _set_bind_info(btc, BTC_MECH_FDD); + + dm->out_of_band = !dm->tdd_bind.rf_band && !dm->fdd_bind.rf_band; + dm->fdd_en = !!dm->fdd_bind.rf_band; + dm->tdd_en = !!dm->tdd_bind.rf_band; + + /* set BT on/off state for GNT_WL Combined-MUX control */ + if (bt0->enable.now) + val |= BIT(0); + + if (bt1->enable.now) + val |= BIT(1); + + if (bt2->func_type) + val |= BIT(2); + + dm->ost_info.bt_enable_state = dm->bt_only ? _bind_is_btonly : val; +} + static void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) { @@ -7842,6 +8109,8 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) bt->scan_rx_low_pri = false; igno_bt = false; + _set_coex_binding(btc); + dm->freerun_chk = _check_freerun(rtwdev); /* check if meet freerun */ if (always_freerun) { @@ -8370,8 +8639,8 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) "[BTC], %s(): bt_info[2]=0x%02x\n", __func__, bt->raw_info[2]); - b->profile_cnt.last = b->profile_cnt.now; - b->profile_cnt.now = 0; + b->link_cnt.last = b->link_cnt.now; + b->link_cnt.now = 0; hid->type = 0; /* parse raw info low-Byte2 */ @@ -8384,13 +8653,13 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) bt->bcnt[BTC_BCNT_INQPAG] += !!(bt->inq_pag.now && !bt->inq_pag.last); hfp->exist = btinfo.lb2.hfp; - b->profile_cnt.now += (u8)hfp->exist; + b->link_cnt.now += (u8)hfp->exist; hid->exist = btinfo.lb2.hid; - b->profile_cnt.now += (u8)hid->exist; + b->link_cnt.now += (u8)hid->exist; a2dp->exist = btinfo.lb2.a2dp; - b->profile_cnt.now += (u8)a2dp->exist; + b->link_cnt.now += (u8)a2dp->exist; pan->exist = btinfo.lb2.pan; - b->profile_cnt.now += (u8)pan->exist; + b->link_cnt.now += (u8)pan->exist; btc->dm.trx_info.bt_profile = u32_get_bits(btinfo.val, BT_PROFILE_PROTOCOL_MASK); /* parse raw info low-Byte3 */ @@ -9418,7 +9687,7 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, " %-15s : profile:%s%s%s%s%s ", "[profile]", - (bt_linfo->profile_cnt.now == 0) ? "None," : "", + (bt_linfo->link_cnt.now == 0) ? "None," : "", bt_linfo->hfp_desc.exist ? "HFP," : "", bt_linfo->hid_desc.exist ? "HID," : "", bt_linfo->a2dp_desc.exist ? @@ -9520,7 +9789,7 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, "\n"); } - if (ver_main >= 9 && bt_linfo->profile_cnt.now) + if (ver_main >= 9 && bt_linfo->link_cnt.now) rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_TX_PWR_LVL, true); else rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_TX_PWR_LVL, false); @@ -9542,7 +9811,7 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) } p += scnprintf(p, end - p, "\n"); - if (bt_linfo->profile_cnt.now || bt_linfo->status.map.ble_connect) + if (bt_linfo->link_cnt.now || bt_linfo->status.map.ble_connect) rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_AFH_MAP, true); else rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_AFH_MAP, false); diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 3f77707e2733..21bdf229723c 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -1445,6 +1445,11 @@ enum rtw89_btc_bt_state_cnt { BTC_BCNT_NUM, }; +enum rtw89_btc_bt_mech_type { + BTC_MECH_TDD = 0, + BTC_MECH_FDD = 1, +}; + enum rtw89_btc_bt_rf_band { BTC_BT_B2G = 0x0, /* 2.4GHz */ BTC_BT_B5G = 0x1, /* 5GHz or 6GHz */ @@ -1463,6 +1468,12 @@ enum rtw89_btc_bt_profile { BTC_BT_HID = BIT(1), BTC_BT_A2DP = BIT(2), BTC_BT_PAN = BIT(3), + BTC_BT_BIS = BIT(4), + BTC_BT_CIS = BIT(5), + BTC_BT_THREAD = BIT(6), + BTC_BT_ULL = BIT(7), + BTC_BT_LEGACY = 0xf, + BTC_BT_FULL = 0x3f, BTC_PROFILE_MAX = 4, }; @@ -1688,16 +1699,16 @@ struct rtw89_btc_bt_ver_info { }; struct rtw89_btc_bool_sta_chg { - u32 now: 1; - u32 last: 1; - u32 remain: 1; - u32 srvd: 29; + u8 now: 1; + u8 last: 1; + u8 remain: 1; + u8 srvd: 5; }; struct rtw89_btc_u8_sta_chg { u8 now; u8 last; - u8 remain; + u8 chg; u8 rsvd; }; @@ -1956,6 +1967,8 @@ struct rtw89_btc_bt_smap { u32 sco_busy: 1; u32 mesh_busy: 1; u32 inq_pag: 1; + u32 profile_map: 8; + u32 rsvd: 18; }; union rtw89_btc_bt_state_map { @@ -1974,14 +1987,30 @@ struct rtw89_btc_bt_txpwr_desc { u8 le_gain_index; }; +struct rtw89_btc_bt_leaudio_desc { + u32 bis_exist: 1; + u32 bis_exist_last: 1; + u32 cis_exist: 1; + u32 cis_exist_last: 1; + u32 bis_cnt: 3; + u32 cis_cnt: 3; + u32 rssi: 8; + u32 bis_cnt_last: 3; + u32 cis_cnt_last: 3; + u32 rsvd: 8; + + u16 diff_t; +}; + struct rtw89_btc_bt_link_info { - struct rtw89_btc_u8_sta_chg profile_cnt; + struct rtw89_btc_u8_sta_chg link_cnt; struct rtw89_btc_bool_sta_chg multi_link; struct rtw89_btc_bool_sta_chg relink; struct rtw89_btc_bt_hfp_desc hfp_desc; struct rtw89_btc_bt_hid_desc hid_desc; struct rtw89_btc_bt_a2dp_desc a2dp_desc; struct rtw89_btc_bt_pan_desc pan_desc; + struct rtw89_btc_bt_leaudio_desc leaudio_desc; union rtw89_btc_bt_state_map status; struct rtw89_btc_bt_txpwr_desc bt_txpwr_desc; @@ -1990,14 +2019,59 @@ struct rtw89_btc_bt_link_info { u8 rssi_state[BTC_BT_RSSI_THMAX]; u8 afh_map[BTC_BT_AFH_GROUP]; u8 afh_map_le[BTC_BT_AFH_LE_GROUP]; + u8 rssi; - u32 role_sw: 1; - u32 slave_role: 1; - u32 afh_update: 1; - u32 cqddr: 1; - u32 rssi: 8; - u32 tx_3m: 1; - u32 rsvd: 19; + u8 role_sw: 1; + u8 slave_role: 1; + u8 afh_update: 1; + u8 cqddr: 1; + u8 tx_3m: 1; + u8 inq: 1; + u8 pag: 1; + u8 igno_wl: 1; + + u8 ble_scan_en: 1; + u8 reinit: 1; + u8 rsvd: 6; +}; + +struct rtw89_btc_bind_bt_status { + u8 a2dp_active: 1; + u8 a2dp_sink: 1; + u8 pan_active: 1; + u8 connect: 1; + u8 inq_page: 1; + u8 multi_link: 1; + u8 slave_role: 1; + u8 page: 1; + + u8 hfp_exist: 1; + u8 hid_exist: 1; + u8 a2dp_exist: 1; + u8 pan_exist: 1; + u8 bis_exist: 1; + u8 cis_exist: 1; + u8 thread_exist: 1; + u8 ull_exist: 1; + + u8 hid_cnt; + u8 hid_type; + u8 cis_cnt; + u8 link_cnt; + + u16 a2dp_vendor_id; +}; + +struct rtw89_btc_bind_info { + u8 wl_hwb_sel; /* map */ + u8 wl_link_mode; + u8 wl_bg_mode; + u8 rf_band; /* map, 0: no any rf-band bind */ + u8 bt_sel; /* map */ + u8 bt_link_weight; /* select the highest weight between bt/rf-band */ + + u32 bt_profile; /* map */ + struct rtw89_btc_bind_bt_status bt_smap; }; struct rtw89_btc_extsoc_info { @@ -2105,6 +2179,7 @@ struct rtw89_btc_wl_info { u8 coex_mode; u8 pta_req_mac; u8 bt_polut_type[RTW89_PHY_NUM]; /* BT polluted WL-Tx type for phy0/1 */ + u8 rf_band_map[RTW89_PHY_NUM]; /* rf_band bit-map */ bool is_5g_hi_channel; bool go_client_exist; @@ -2291,6 +2366,7 @@ union rtw89_btc_fbtc_btscan { struct rtw89_btc_bt_info { struct rtw89_btc_bt_link_info link_info; + struct rtw89_btc_bt_link_info link_info_56g; struct rtw89_btc_bt_scan_info_v1 scan_info_v1[BTC_SCAN_MAX1]; struct rtw89_btc_bt_scan_info_v2 scan_info_v2[CXSCAN_MAX]; struct rtw89_btc_bt_ver_info ver_info; @@ -2301,6 +2377,7 @@ struct rtw89_btc_bt_info { u8 raw_info[BTC_BTINFO_MAX]; /* raw bt info from mailbox */ u8 txpwr_info[BTC_BTINFO_MAX]; + u8 link_weight[BTC_BT_BMAX]; /* Link Weight for RF-band/HWB selection */ u8 rssi_level; u8 rf_band_map; u8 func_type; @@ -3178,6 +3255,7 @@ struct rtw89_btc_fbtc_outsrc_set_info { u8 pta_req_hw_band; u8 rf_gbt_source; + u8 bt_enable_state; } __packed; union rtw89_btc_fbtc_slot_u { @@ -3200,8 +3278,20 @@ struct rtw89_btc_dm { struct rtw89_btc_wl_scc_ctrl wl_scc; struct rtw89_btc_trx_info trx_info; union rtw89_btc_dm_error_map error; + struct rtw89_btc_bind_info tdd_bind; + struct rtw89_btc_bind_info fdd_bind; u32 cnt_dm[BTC_DCNT_NUM]; u32 cnt_notify[BTC_NCNT_NUM]; + u8 ant_xmap[BTC_RF_NUM][BTC_ALL_BT_EZL]; /* WL-BT ANT interact-map */ + u8 xtk_xmap[BTC_RF_NUM][BTC_ALL_BT_EZL]; /* 1: If RSSI<(BT-Pin -SIR) */ + u8 sit_xmap[BTC_RF_NUM][BTC_ALL_BT_EZL]; /* WL-BT space interact-map */ + u8 fit_xmap[RTW89_PHY_NUM][BTC_ALL_BT_EZL]; /* HWB-BT freq interact-map */ + u8 tdd_map[BTC_RF_NUM][BTC_ALL_BT_EZL]; /* WL-BT tdd-map */ + u8 fdd_map[BTC_RF_NUM][BTC_ALL_BT_EZL]; /* WL-BT fdd-map */ + u8 corx_map[BTC_RF_NUM][BTC_ALL_BT_EZL]; /* WL-BT Co-Rx */ + + u8 sit_xmap_last[BTC_RF_NUM][BTC_ALL_BT_EZL]; + u8 fit_xmap_last[RTW89_PHY_NUM][BTC_ALL_BT_EZL]; u32 update_slot_map; u32 set_ant_path; @@ -3239,9 +3329,12 @@ struct rtw89_btc_dm { u8 freerun_chk: 1; u8 wl_pre_agc_rb: 2; u8 bt_select: 2; /* 0:s0, 1:s1, 2:s0 & s1, refer to enum btc_bt_index */ - u8 slot_req_more: 1; - u8 lps_ctrl_scbd: 1; + u8 slot_req_more: 1; + u8 out_of_band: 1; + u8 fdd_en: 1; + u8 tdd_en: 1; + u8 lps_ctrl_scbd: 1; u8 lps_ctrl_scbd_last: 1; u8 lps_ctrl_change: 1; }; From 3244261af91596da1f17adc0812e42cb7fea9983 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Mon, 6 Jul 2026 10:54:43 +0800 Subject: [PATCH 0275/1433] wifi: rtw89: coex: Add TDMA binding for dual MAC Because the two MAC should have their own individual using, they will need different TDMA mechanism. This patch will bind TDMA with MAC index, and also the corresponding antenna, hardware grant signal setting. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260706025445.18428-8-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 381 +++++++++------------- drivers/net/wireless/realtek/rtw89/core.h | 11 +- 2 files changed, 158 insertions(+), 234 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index f2dea0e8c8f2..c1c3638be26d 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -494,7 +494,7 @@ enum btc_ant_phase { BTC_ANT_FREERUN, BTC_ANT_WRFK, BTC_ANT_WRFK2, - BTC_ANT_BRFK, + BTC_ANT_PTA, /* for Multi-PTA, each hw-band has its own PTA */ BTC_ANT_MAX }; @@ -3894,6 +3894,41 @@ static void _set_policy(struct rtw89_dev *rtwdev, u16 policy_type, _fw_set_policy(rtwdev, policy_type, action); } +static void _set_tdma_bind(struct rtw89_dev *rtwdev, bool tdma_on) +{ + struct rtw89_btc_dm *dm = &rtwdev->btc.dm; + struct rtw89_btc_bind_info *bind; + u8 null_role = RTW89_WIFI_ROLE_STATION; + + if (dm->tdd_en) + bind = &dm->tdd_bind; /* tdd = 1 && fdd = 0 or 1 */ + else + bind = &dm->fdd_bind; /* tdd = 0 && fdd = 1 */ + + /* notify BT TDMA on/off by scoreboard for ACL/Scan schedule */ + _write_scbd(rtwdev, BTC_WSCB_TDMA, tdma_on); + + /* + * set hwb/bt bind to TDMA policy parameter + * FW-BTC will setup related hwbx vs. BTx coex tables by slot toggle + * ex: HWb0 + BT0 + BT1, HWB0-BT0/HWB0-BT1 coex by WL/BT slot toggle + */ + dm->tdma.bind = ((bind->bt_sel & 0xf) << 4) + (bind->wl_hwb_sel & 0x3); + if (dm->tdd_en) + dm->tdma.bind |= BIT(2); + if (dm->fdd_en) + dm->tdma.bind |= BIT(3); + if (rtwdev->btc.ver->fcxtdma != 8) + dm->tdma.bind |= 0; + + /* set Null-tx role for 2 HW-BAND TDMA (MLMR) */ + if (!dm->eslot_ctrl.en && dm->tdma.rxflctrl && + (bind->wl_hwb_sel == (BIT(RTW89_PHY_1) | BIT(RTW89_PHY_0)))) { + null_role = (null_role << 4) + null_role; + _tdma_set_flctrl_role(&rtwdev->btc, null_role); + } +} + #define BTC_B1_MAX 250 /* unit ms */ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) { @@ -3903,6 +3938,7 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) struct rtw89_btc_fbtc_slot *s = dm->slot.v1; u8 type; u32 tbl_w1, tbl_b1, tbl_b4; + bool tdma_on = false; if (btc->ant_type == BTC_ANT_SHARED) { if (btc->cx.wl.status.map._4way) @@ -3928,7 +3964,7 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) btc->update_policy_force = true; break; case BTC_CXP_OFF: /* TDMA off */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, false); + tdma_on = false; *t = t_def[CXTD_OFF]; s[CXST_OFF] = s_def[CXST_OFF]; @@ -3963,7 +3999,7 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_OFFB: /* TDMA off + beacon protect */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, false); + tdma_on = false; *t = t_def[CXTD_OFF_B2]; s[CXST_OFF] = s_def[CXST_OFF]; switch (policy_type) { @@ -3974,7 +4010,7 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) break; case BTC_CXP_OFFE: /* TDMA off + beacon protect + Ext_control */ btc->bt_req_en = true; - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_OFF_EXT]; switch (policy_type) { case BTC_CXP_OFFE_DEF: @@ -3992,7 +4028,7 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_FIX: /* TDMA Fix-Slot */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_FIX]; switch (policy_type) { case BTC_CXP_FIX_TD3030: @@ -4048,7 +4084,7 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_PFIX: /* PS-TDMA Fix-Slot */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_PFIX]; if (btc->cx.wl.role_info.role_map.role.ap) _tdma_set_flctrl(btc, CXFLC_QOSNULL); @@ -4081,7 +4117,7 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_AUTO: /* TDMA Auto-Slot */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_AUTO]; switch (policy_type) { case BTC_CXP_AUTO_TD50B1: @@ -4105,7 +4141,7 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_PAUTO: /* PS-TDMA Auto-Slot */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_PAUTO]; switch (policy_type) { case BTC_CXP_PAUTO_TD50B1: @@ -4129,7 +4165,7 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_AUTO2: /* TDMA Auto-Slot2 */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_AUTO2]; switch (policy_type) { case BTC_CXP_AUTO2_TD3050: @@ -4166,7 +4202,7 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_PAUTO2: /* PS-TDMA Auto-Slot2 */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_PAUTO2]; switch (policy_type) { case BTC_CXP_PAUTO2_TD3050: @@ -4203,6 +4239,7 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) } break; } + _set_tdma_bind(rtwdev, tdma_on); } EXPORT_SYMBOL(rtw89_btc_set_policy); @@ -4211,14 +4248,14 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_fbtc_tdma *t = &dm->tdma; - struct rtw89_btc_wl_role_info_v1 *wl_rinfo = &btc->cx.wl.role_info_v1; struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt0.link_info.a2dp_desc; struct rtw89_btc_bt_hid_desc *hid = &btc->cx.bt0.link_info.hid_desc; struct rtw89_btc_bt_hfp_desc *hfp = &btc->cx.bt0.link_info.hfp_desc; struct rtw89_btc_wl_info *wl = &btc->cx.wl; u8 type, null_role; u32 tbl_w1, tbl_b1, tbl_b4; - u16 dur_2; + bool tdma_on = false; + u16 dur_1 = 0, dur_2; if (wl->status.map.lps) { _slot_set_le(btc, CXST_E2G, s_def[CXST_E2G].dur, @@ -4272,7 +4309,7 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) _slot_set_tbl(btc, CXST_OFF, cxtbl[2]); break; case BTC_CXP_OFF: /* TDMA off */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, false); + tdma_on = false; *t = t_def[CXTD_OFF]; _slot_set_le(btc, CXST_OFF, s_def[CXST_OFF].dur, s_def[CXST_OFF].cxtbl, s_def[CXST_OFF].cxtype); @@ -4324,7 +4361,7 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_OFFB: /* TDMA off + beacon protect */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, false); + tdma_on = false; *t = t_def[CXTD_OFF_B2]; _slot_set_le(btc, CXST_OFF, s_def[CXST_OFF].dur, s_def[CXST_OFF].cxtbl, s_def[CXST_OFF].cxtype); @@ -4338,24 +4375,35 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_OFFE: /* TDMA off + beacon protect + Ext_control */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_OFF_EXT]; /* To avoid wl-s0 tx break by hid/hfp tx */ if (hid->exist || hfp->exist) tbl_w1 = cxtbl[16]; + if (dm->eslot_ctrl.en) { + null_role = u8_encode_bits(dm->eslot_ctrl.nulltx_role1, 0x0f) | + u8_encode_bits(dm->eslot_ctrl.nulltx_role2, 0xf0); + _tdma_set_flctrl_role(btc, null_role); + _tdma_set_rxflctrl(btc, 1); + _tdma_set_txflctrl(btc, 1); + /* Set Null-Tx time-tick by E2G duration */ + dm->eslot_ctrl.nulltx_pre_time = 5; /* TODO: BIS COEX */ + dur_1 = dm->eslot_ctrl.nulltx_pre_time; + } + dur_2 = dm->e2g_slot_limit; switch (policy_type) { case BTC_CXP_OFFE_2GBWISOB: /* for normal-case */ - _slot_set(btc, CXST_E2G, 5, tbl_w1, SLOT_ISO); + _slot_set(btc, CXST_E2G, dur_1, tbl_w1, SLOT_ISO); _slot_set_le(btc, CXST_EBT, s_def[CXST_EBT].dur, s_def[CXST_EBT].cxtbl, s_def[CXST_EBT].cxtype); _slot_set_dur(btc, CXST_EBT, dur_2); break; case BTC_CXP_OFFE_2GISOB: /* for bt no-link */ - _slot_set(btc, CXST_E2G, 5, cxtbl[1], SLOT_ISO); + _slot_set(btc, CXST_E2G, dur_1, cxtbl[1], SLOT_ISO); _slot_set_le(btc, CXST_EBT, s_def[CXST_EBT].dur, s_def[CXST_EBT].cxtbl, s_def[CXST_EBT].cxtype); _slot_set_dur(btc, CXST_EBT, dur_2); @@ -4383,16 +4431,16 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) break; case BTC_CXP_OFFE_2GBWMIXB: if (a2dp->exist) - _slot_set(btc, CXST_E2G, 5, cxtbl[2], SLOT_MIX); + _slot_set(btc, CXST_E2G, dur_1, cxtbl[2], SLOT_MIX); else - _slot_set(btc, CXST_E2G, 5, tbl_w1, SLOT_MIX); + _slot_set(btc, CXST_E2G, dur_1, tbl_w1, SLOT_MIX); _slot_set_le(btc, CXST_EBT, cpu_to_le16(40), s_def[CXST_EBT].cxtbl, s_def[CXST_EBT].cxtype); _slot_set_dur(btc, CXST_EBT, dur_2); break; case BTC_CXP_OFFE_WL: /* for 4-way */ - _slot_set(btc, CXST_E2G, 5, cxtbl[1], SLOT_MIX); - _slot_set(btc, CXST_EBT, 5, cxtbl[1], SLOT_MIX); + _slot_set(btc, CXST_E2G, dur_1, cxtbl[1], SLOT_MIX); + _slot_set(btc, CXST_EBT, dur_1, cxtbl[1], SLOT_MIX); break; default: break; @@ -4403,7 +4451,7 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) s_def[CXST_OFF].cxtbl, s_def[CXST_OFF].cxtype); break; case BTC_CXP_FIX: /* TDMA Fix-Slot */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_FIX]; switch (policy_type) { @@ -4462,7 +4510,7 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_PFIX: /* PS-TDMA Fix-Slot */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_PFIX]; switch (policy_type) { @@ -4501,7 +4549,7 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_AUTO: /* TDMA Auto-Slot */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_AUTO]; switch (policy_type) { @@ -4528,7 +4576,7 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_PAUTO: /* PS-TDMA Auto-Slot */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_PAUTO]; switch (policy_type) { @@ -4555,7 +4603,7 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_AUTO2: /* TDMA Auto-Slot2 */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_AUTO2]; switch (policy_type) { @@ -4597,7 +4645,7 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) } break; case BTC_CXP_PAUTO2: /* PS-TDMA Auto-Slot2 */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, true); + tdma_on = true; *t = t_def[CXTD_PAUTO2]; switch (policy_type) { @@ -4640,20 +4688,22 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) break; } - if (wl_rinfo->link_mode == BTC_WLINK_2G_SCC && dm->tdma.rxflctrl) { - null_role = FIELD_PREP(0x0f, dm->wl_scc.null_role1) | - FIELD_PREP(0xf0, dm->wl_scc.null_role2); - _tdma_set_flctrl_role(btc, null_role); - } - /* enter leak_slot after each null-1 */ if (dm->leak_ap && dm->tdma.leak_n > 1) _tdma_set_lek(btc, 1); - if (dm->tdma_instant_excute || dm->lps_ctrl_change) { + if (dm->tdma_instant_excute || + dm->error.map.tdma_no_sync || + dm->error.map.slot_no_sync || + dm->lps_ctrl_change) { btc->dm.tdma.option_ctrl |= BIT(0); btc->update_policy_force = true; } + + if (btc->cli_h2c_cmd) + btc->update_policy_force = true; + + _set_tdma_bind(rtwdev, tdma_on); } EXPORT_SYMBOL(rtw89_btc_set_policy_v1); @@ -4697,20 +4747,29 @@ static void _set_ant_v0(struct rtw89_dev *rtwdev, bool force_exec, struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_bt_info *bt = &cx->bt0; struct rtw89_btc_wl_dbcc_info *wl_dinfo = &wl->dbcc_info; - u8 gnt_wl_ctrl, gnt_bt_ctrl, plt_ctrl, i, b2g = 0; - bool dbcc_chg = false; + u8 gwl, gwl0, gwl1, gbt, plt_ctrl, i, dbcc_2g_phy, b2g = 0; + bool dbcc_chg = false, dbcc_en = false; u32 ant_path_type; ant_path_type = ((phy_map << 8) + type); - if (btc->ver->fwlrole == 1) + if (btc->ver->fwlrole == 1) { dbcc_chg = wl->role_info_v1.dbcc_chg; - else if (btc->ver->fwlrole == 2) + dbcc_en = wl->role_info_v1.dbcc_en; + dbcc_2g_phy = wl->role_info_v1.dbcc_2g_phy; + } else if (btc->ver->fwlrole == 2) { dbcc_chg = wl->role_info_v2.dbcc_chg; - else if (btc->ver->fwlrole == 7) + dbcc_en = wl->role_info_v2.dbcc_en; + dbcc_2g_phy = wl->role_info_v2.dbcc_2g_phy; + } else if (btc->ver->fwlrole == 7) { dbcc_chg = wl->role_info_v7.dbcc_chg; - else if (btc->ver->fwlrole == 8) + dbcc_en = wl->role_info_v7.dbcc_en; + dbcc_2g_phy = wl->role_info_v7.dbcc_2g_phy; + } else if (btc->ver->fwlrole == 8) { dbcc_chg = wl->role_info_v8.dbcc_chg; + dbcc_en = wl->role_info_v8.dbcc_en; + dbcc_2g_phy = wl->role_info_v8.dbcc_2g_phy; + } if (btc->dm.run_reason == BTC_RSN_NTFY_POWEROFF || btc->dm.run_reason == BTC_RSN_NTFY_RADIO_STATE || @@ -4768,14 +4827,14 @@ static void _set_ant_v0(struct rtw89_dev *rtwdev, bool force_exec, for (i = 0; i < RTW89_PHY_NUM; i++) { b2g = (wl_dinfo->real_band[i] == RTW89_BAND_2G); - gnt_wl_ctrl = b2g ? BTC_GNT_HW : BTC_GNT_SW_HI; - gnt_bt_ctrl = b2g ? BTC_GNT_HW : BTC_GNT_SW_HI; + gwl = b2g ? BTC_GNT_HW : BTC_GNT_SW_HI; + gbt = b2g ? BTC_GNT_HW : BTC_GNT_SW_HI; /* BT should control by GNT_BT if WL_2G at S0 */ if (i == 1 && wl_dinfo->real_band[0] == RTW89_BAND_2G && wl_dinfo->real_band[1] == RTW89_BAND_5G) - gnt_bt_ctrl = BTC_GNT_HW; - _set_gnt(rtwdev, BIT(i), gnt_wl_ctrl, gnt_bt_ctrl); + gbt = BTC_GNT_HW; + _set_gnt(rtwdev, BIT(i), gwl, gbt); plt_ctrl = b2g ? BTC_PLT_BT : BTC_PLT_NONE; _set_bt_plut(rtwdev, BIT(i), plt_ctrl, plt_ctrl); @@ -4808,12 +4867,27 @@ static void _set_ant_v0(struct rtw89_dev *rtwdev, bool force_exec, _set_gnt(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_SW_LO); _set_bt_plut(rtwdev, phy_map, BTC_PLT_NONE, BTC_PLT_NONE); break; - case BTC_ANT_BRFK: - rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_BT); - _set_gnt(rtwdev, phy_map, BTC_GNT_SW_LO, BTC_GNT_SW_HI); - _set_bt_plut(rtwdev, phy_map, BTC_PLT_NONE, BTC_PLT_NONE); - break; + case BTC_ANT_PTA: default: + gbt = BTC_GNT_HW; + if ((rtwdev->chip->para_ver & BTC_FEAT_MULTI_PTA) || + !dbcc_en) { + gwl = BTC_GNT_HW; + _set_gnt(rtwdev, BTC_PHY_ALL, gwl, gbt); + } else { + /* for DBCC Only-1-PTA */ + if (dbcc_2g_phy == RTW89_PHY_0) { + gwl0 = BTC_GNT_HW; + gwl1 = BTC_GNT_SW_HI; + } else { + gwl0 = BTC_GNT_SW_HI; + gwl1 = BTC_GNT_HW; + } + _set_gnt(rtwdev, BTC_PHY_0, gwl0, gbt); + _set_gnt(rtwdev, BTC_PHY_1, gwl1, gbt); + } + rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_WL); + _set_bt_plut(rtwdev, phy_map, BTC_PLT_NONE, BTC_PLT_NONE); break; } } @@ -4828,7 +4902,7 @@ static void _set_ant_v1(struct rtw89_dev *rtwdev, bool force_exec, u32 ant_path_type = rtw89_get_antpath_type(phy_map, type); struct rtw89_btc_wl_dbcc_info *wl_dinfo = &wl->dbcc_info; struct rtw89_btc_dm *dm = &btc->dm; - u8 gwl = BTC_GNT_HW; + u8 gwl = BTC_GNT_HW, gwl0, gwl1, gbt; if (btc->dm.run_reason == BTC_RSN_NTFY_POWEROFF || btc->dm.run_reason == BTC_RSN_NTFY_RADIO_STATE || @@ -4913,8 +4987,26 @@ static void _set_ant_v1(struct rtw89_dev *rtwdev, bool force_exec, _set_gnt_v1(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_SW_LO, BTC_WLACT_SW_HI); /* no BT-Tx */ break; + case BTC_ANT_PTA: default: - return; + gbt = BTC_GNT_HW; + if ((rtwdev->chip->para_ver & BTC_FEAT_MULTI_PTA) || + !wl_rinfo->dbcc_en) { + gwl = BTC_GNT_HW; + _set_gnt_v1(rtwdev, BTC_PHY_ALL, gwl, gbt, BTC_WLACT_HW); + } else { + /* for DBCC Only-1-PTA */ + if (wl_rinfo->dbcc_2g_phy == RTW89_PHY_0) { + gwl0 = BTC_GNT_HW; + gwl1 = BTC_GNT_SW_HI; + } else { + gwl0 = BTC_GNT_SW_HI; + gwl1 = BTC_GNT_HW; + } + _set_gnt_v1(rtwdev, BTC_PHY_0, gwl0, gbt, BTC_WLACT_HW); + _set_gnt_v1(rtwdev, BTC_PHY_1, gwl1, gbt, BTC_WLACT_HW); + } + break; } _set_bt_plut(rtwdev, phy_map, BTC_PLT_GNT_WL, BTC_PLT_GNT_WL); @@ -6017,178 +6109,15 @@ static void _action_wl_2g_mcc(struct rtw89_dev *rtwdev) static void _action_wl_2g_scc(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - - _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); - - if (btc->ant_type == BTC_ANT_SHARED) { /* shared-antenna */ - if (btc->cx.bt0.link_info.link_cnt.now == 0) - _set_policy(rtwdev, - BTC_CXP_OFFE_DEF2, BTC_ACT_WL_2G_SCC); - else - _set_policy(rtwdev, - BTC_CXP_OFFE_DEF, BTC_ACT_WL_2G_SCC); - } else { /* dedicated-antenna */ - _set_policy(rtwdev, BTC_CXP_OFF_EQ0, BTC_ACT_WL_2G_SCC); - } -} - -static void _action_wl_2g_scc_v1(struct rtw89_dev *rtwdev) -{ - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_btc_wl_role_info_v1 *wl_rinfo = &wl->role_info_v1; - u16 policy_type = BTC_CXP_OFF_BT; - u32 dur; + u16 policy_type; - if (btc->ant_type == BTC_ANT_DEDICATED) { - policy_type = BTC_CXP_OFF_EQ0; - } else { - /* shared-antenna */ - switch (wl_rinfo->mrole_type) { - case BTC_WLMROLE_STA_GC: - dm->wl_scc.null_role1 = RTW89_WIFI_ROLE_STATION; - dm->wl_scc.null_role2 = RTW89_WIFI_ROLE_P2P_CLIENT; - dm->wl_scc.ebt_null = 0; /* no ext-slot-control */ - _action_by_bt(rtwdev); - return; - case BTC_WLMROLE_STA_STA: - dm->wl_scc.null_role1 = RTW89_WIFI_ROLE_STATION; - dm->wl_scc.null_role2 = RTW89_WIFI_ROLE_STATION; - dm->wl_scc.ebt_null = 0; /* no ext-slot-control */ - _action_by_bt(rtwdev); - return; - case BTC_WLMROLE_STA_GC_NOA: - case BTC_WLMROLE_STA_GO: - case BTC_WLMROLE_STA_GO_NOA: - dm->wl_scc.null_role1 = RTW89_WIFI_ROLE_STATION; - dm->wl_scc.null_role2 = RTW89_WIFI_ROLE_NONE; - dur = wl_rinfo->mrole_noa_duration; + if (!dm->tdd_bind.bt_smap.connect) + policy_type = BTC_CXP_OFFE_2GISOB; + else + policy_type = BTC_CXP_OFFE_2GBWISOB; - if (wl->status.map._4way) { - dm->wl_scc.ebt_null = 0; - policy_type = BTC_CXP_OFFE_WL; - } else if (bt->link_info.status.map.connect == 0) { - dm->wl_scc.ebt_null = 0; - policy_type = BTC_CXP_OFFE_2GISOB; - } else if (bt->link_info.a2dp_desc.exist && - dur < btc->bt_req_len[RTW89_PHY_0]) { - dm->wl_scc.ebt_null = 1; /* tx null at EBT */ - policy_type = BTC_CXP_OFFE_2GBWMIXB2; - } else if (bt->link_info.a2dp_desc.exist || - bt->link_info.pan_desc.exist) { - dm->wl_scc.ebt_null = 1; /* tx null at EBT */ - policy_type = BTC_CXP_OFFE_2GBWISOB; - } else { - dm->wl_scc.ebt_null = 0; - policy_type = BTC_CXP_OFFE_2GBWISOB; - } - break; - default: - break; - } - } - - _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); - _set_policy(rtwdev, policy_type, BTC_ACT_WL_2G_SCC); -} - -static void _action_wl_2g_scc_v2(struct rtw89_dev *rtwdev) -{ - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt0; - struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_btc_wl_role_info_v2 *rinfo_v2 = &wl->role_info_v2; - struct rtw89_btc_wl_role_info_v7 *rinfo_v7 = &wl->role_info_v7; - u32 dur, mrole_type, mrole_noa_duration; - u16 policy_type = BTC_CXP_OFF_BT; - - if (btc->ver->fwlrole == 2) { - mrole_type = rinfo_v2->mrole_type; - mrole_noa_duration = rinfo_v2->mrole_noa_duration; - } else if (btc->ver->fwlrole == 7) { - mrole_type = rinfo_v7->mrole_type; - mrole_noa_duration = rinfo_v7->mrole_noa_duration; - } else { - return; - } - - if (btc->ant_type == BTC_ANT_DEDICATED) { - policy_type = BTC_CXP_OFF_EQ0; - } else { - /* shared-antenna */ - switch (mrole_type) { - case BTC_WLMROLE_STA_GC: - dm->wl_scc.null_role1 = RTW89_WIFI_ROLE_STATION; - dm->wl_scc.null_role2 = RTW89_WIFI_ROLE_P2P_CLIENT; - dm->wl_scc.ebt_null = 0; /* no ext-slot-control */ - _action_by_bt(rtwdev); - return; - case BTC_WLMROLE_STA_STA: - dm->wl_scc.null_role1 = RTW89_WIFI_ROLE_STATION; - dm->wl_scc.null_role2 = RTW89_WIFI_ROLE_STATION; - dm->wl_scc.ebt_null = 0; /* no ext-slot-control */ - _action_by_bt(rtwdev); - return; - case BTC_WLMROLE_STA_GC_NOA: - case BTC_WLMROLE_STA_GO: - case BTC_WLMROLE_STA_GO_NOA: - dm->wl_scc.null_role1 = RTW89_WIFI_ROLE_STATION; - dm->wl_scc.null_role2 = RTW89_WIFI_ROLE_NONE; - dur = mrole_noa_duration; - - if (wl->status.map._4way) { - dm->wl_scc.ebt_null = 0; - policy_type = BTC_CXP_OFFE_WL; - } else if (bt->link_info.status.map.connect == 0) { - dm->wl_scc.ebt_null = 0; - policy_type = BTC_CXP_OFFE_2GISOB; - } else if (bt->link_info.a2dp_desc.exist && - dur < btc->bt_req_len[RTW89_PHY_0]) { - dm->wl_scc.ebt_null = 1; /* tx null at EBT */ - policy_type = BTC_CXP_OFFE_2GBWMIXB2; - } else if (bt->link_info.a2dp_desc.exist || - bt->link_info.pan_desc.exist) { - dm->wl_scc.ebt_null = 1; /* tx null at EBT */ - policy_type = BTC_CXP_OFFE_2GBWISOB; - } else { - dm->wl_scc.ebt_null = 0; - policy_type = BTC_CXP_OFFE_2GBWISOB; - } - break; - default: - break; - } - } - - _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); - _set_policy(rtwdev, policy_type, BTC_ACT_WL_2G_SCC); -} - -static void _action_wl_2g_scc_v8(struct rtw89_dev *rtwdev) -{ - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt0; - struct rtw89_btc_dm *dm = &btc->dm; - u16 policy_type = BTC_CXP_OFF_BT; - - if (btc->ant_type == BTC_ANT_SHARED) { - if (wl->status.map._4way) - policy_type = BTC_CXP_OFFE_WL; - else if (bt->link_info.status.map.connect == 0) - policy_type = BTC_CXP_OFFE_2GISOB; - else - policy_type = BTC_CXP_OFFE_2GBWISOB; - } else { - policy_type = BTC_CXP_OFF_EQ0; - } - - dm->e2g_slot_limit = BTC_E2G_LIMIT_DEF; - - _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); + _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_PTA); _set_policy(rtwdev, policy_type, BTC_ACT_WL_2G_SCC); } @@ -8180,14 +8109,7 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) break; case BTC_WLINK_2G_SCC: bt->scan_rx_low_pri = true; - if (ver->fwlrole == 0) - _action_wl_2g_scc(rtwdev); - else if (ver->fwlrole == 1) - _action_wl_2g_scc_v1(rtwdev); - else if (ver->fwlrole == 2 || ver->fwlrole == 7) - _action_wl_2g_scc_v2(rtwdev); - else if (ver->fwlrole == 8) - _action_wl_2g_scc_v8(rtwdev); + _action_wl_2g_scc(rtwdev); break; case BTC_WLINK_2G_MCC: bt->scan_rx_low_pri = true; @@ -10115,7 +10037,8 @@ static const char *id_to_ant(u32 id) CASE_BTC_ANTPATH_STR(W25G); CASE_BTC_ANTPATH_STR(FREERUN); CASE_BTC_ANTPATH_STR(WRFK); - CASE_BTC_ANTPATH_STR(BRFK); + CASE_BTC_ANTPATH_STR(WRFK2); + CASE_BTC_ANTPATH_STR(PTA); CASE_BTC_ANTPATH_STR(MAX); default: return "unknown"; diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 21bdf229723c..eb814425f536 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -1832,10 +1832,11 @@ struct rtw89_btc_wl_role_info_bpos { u16 nan: 1; }; -struct rtw89_btc_wl_scc_ctrl { - u8 null_role1; - u8 null_role2; - u8 ebt_null; /* if tx null at EBT slot */ +struct rtw89_btc_eslot_ctrl { + u8 en; /* 1: toggle tx-flow-ctrl (null 0/1), tx-pause by Ext-slot */ + u8 nulltx_role1; + u8 nulltx_role2; + u8 nulltx_pre_time; /* null-tx time prior to EBT-start (from E2G-end) */ }; union rtw89_btc_wl_role_info_map { @@ -3275,7 +3276,7 @@ struct rtw89_btc_dm { struct rtw89_btc_rf_trx_para_v9 rf_trx_para; struct rtw89_btc_wl_tx_limit_para wl_tx_limit; struct rtw89_btc_dm_step dm_step; - struct rtw89_btc_wl_scc_ctrl wl_scc; + struct rtw89_btc_eslot_ctrl eslot_ctrl; struct rtw89_btc_trx_info trx_info; union rtw89_btc_dm_error_map error; struct rtw89_btc_bind_info tdd_bind; From 36f90091ee57deae8eef55e891f07458f08127eb Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Mon, 6 Jul 2026 10:54:44 +0800 Subject: [PATCH 0276/1433] wifi: rtw89: coex: Update scoreboard related logic for dual Bluetooth Update WiFi status to each Bluetooth & collect status from the two Bluetooth adapter by non-stop power zone register. To correct the meaning of variable, redefine the naming for the variables. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260706025445.18428-9-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 543 ++++++++++++++-------- drivers/net/wireless/realtek/rtw89/coex.h | 9 + drivers/net/wireless/realtek/rtw89/core.c | 4 +- drivers/net/wireless/realtek/rtw89/core.h | 33 +- 4 files changed, 379 insertions(+), 210 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index c1c3638be26d..9e51906b1ccc 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -461,7 +461,14 @@ enum btc_b2w_scoreboard { BTC_BSCB_BT_LNAB1 = BIT(10), BTC_BSCB_WLRFK = BIT(11), BTC_BSCB_BT_HILNA = BIT(13), + BTC_BSCB_BT_HILNA_56G = BIT(14), + BTC_BSCB_CTCODE = BIT(15), BTC_BSCB_BT_CONNECT = BIT(16), + BTC_BSCB_BT_CONNECT_56G = BIT(17), + BTC_BSCB_BT_LNAB0_56G = BIT(18), + BTC_BSCB_BT_LNAB1_56G = BIT(19), + BTC_BSCB_BT_15DOT4 = BIT(24), + BTC_BSCB_BT_PROTECT = BIT(27), BTC_BSCB_PAN_ACT = BIT(28), BTC_BSCB_HFP_ACT = BIT(29), BTC_BSCB_PATCH_CODE = BIT(30), @@ -808,6 +815,8 @@ enum btc_reset_module { BTC_RESET_CXDM = BIT(0) | BIT(1), BTC_RESET_BTINFO = BIT(3), BTC_RESET_MDINFO = BIT(4), + BTC_RESET_BT_PSD_DM = BIT(5), + BTC_RESET_BTINFO2 = BIT(6), BTC_RESET_ALL = GENMASK(7, 0), }; @@ -905,8 +914,9 @@ enum btc_reason_and_action { static void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason); -static void _write_scbd(struct rtw89_dev *rtwdev, u32 val, bool state); -static void _update_bt_scbd(struct rtw89_dev *rtwdev, bool only_update); +static void _write_scbd(struct rtw89_dev *rtwdev, u8 bid, u32 val, bool state); +static u8 _sned_h2c_w2bscbd(struct rtw89_dev *rtwdev, bool force_exec, u8 bid); +static void _update_bt_scbd(struct rtw89_dev *rtwdev, u8 bid); static int _send_fw_cmd(struct rtw89_dev *rtwdev, u8 h2c_class, u8 h2c_func, void *param, u16 len) @@ -1272,11 +1282,6 @@ static void _chk_btc_err(struct rtw89_dev *rtwdev, u8 type, u32 cnt) dm->cnt_dm[BTC_DCNT_BTCNT_HANG]++; else dm->cnt_dm[BTC_DCNT_BTCNT_HANG] = 0; - - if ((dm->cnt_dm[BTC_DCNT_BTCNT_HANG] >= BTC_CHK_HANG_MAX && - bt->enable.now) || (!dm->cnt_dm[BTC_DCNT_BTCNT_HANG] && - !bt->enable.now)) - _update_bt_scbd(rtwdev, false); break; case BTC_DCNT_WL_SLOT_DRIFT: if (cnt >= BTC_CHK_WLSLOT_DRIFT_MAX) @@ -1755,7 +1760,7 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, rtwdev->chip->ops->btc_update_bt_cnt(rtwdev); _chk_btc_err(rtwdev, BTC_DCNT_BTCNT_HANG, 0); - bt->bcnt[BTC_BCNT_POLUT] = + bt->bcnt[BTC_BCNT_POLLUTED] = rtw89_mac_get_plt_cnt(rtwdev, RTW89_MAC_0); } @@ -1778,7 +1783,7 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, le32_to_cpu(prpt->v4.bt_cnt[BTC_BCNT_LO_TX]); bt->bcnt[BTC_BCNT_LOPRI_RX] = le32_to_cpu(prpt->v4.bt_cnt[BTC_BCNT_LO_RX]); - bt->bcnt[BTC_BCNT_POLUT] = + bt->bcnt[BTC_BCNT_POLLUTED] = le32_to_cpu(prpt->v4.bt_cnt[BTC_BCNT_POLLUTED]); _chk_btc_err(rtwdev, BTC_DCNT_BTCNT_HANG, 0); @@ -1810,7 +1815,7 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, le16_to_cpu(prpt->v5.bt_cnt[BTC_BCNT_LO_TX]); bt->bcnt[BTC_BCNT_LOPRI_RX] = le16_to_cpu(prpt->v5.bt_cnt[BTC_BCNT_LO_RX]); - bt->bcnt[BTC_BCNT_POLUT] = + bt->bcnt[BTC_BCNT_POLLUTED] = le16_to_cpu(prpt->v5.bt_cnt[BTC_BCNT_POLLUTED]); _chk_btc_err(rtwdev, BTC_DCNT_BTCNT_HANG, 0); @@ -1837,7 +1842,7 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, le16_to_cpu(prpt->v105.bt_cnt[BTC_BCNT_LO_TX_V105]); bt->bcnt[BTC_BCNT_LOPRI_RX] = le16_to_cpu(prpt->v105.bt_cnt[BTC_BCNT_LO_RX_V105]); - bt->bcnt[BTC_BCNT_POLUT] = + bt->bcnt[BTC_BCNT_POLLUTED] = le16_to_cpu(prpt->v105.bt_cnt[BTC_BCNT_POLLUTED_V105]); _chk_btc_err(rtwdev, BTC_DCNT_BTCNT_HANG, 0); @@ -2791,14 +2796,14 @@ static void _fw_set_policy(struct rtw89_dev *rtwdev, u16 policy_type, if (dm->tdma.rxflctrl == CXFLC_NULLP || dm->tdma.rxflctrl == CXFLC_QOSNULL) - btc->lps = 1; + btc->btc_ctrl_lps = 1; else - btc->lps = dm->lps_ctrl_scbd; + btc->btc_ctrl_lps = dm->lps_ctrl_scbd; dm->lps_ctrl_scbd_last = dm->lps_ctrl_scbd; - if (btc->lps == 1) - rtw89_set_coex_ctrl_lps(rtwdev, btc->lps); + if (btc->btc_ctrl_lps == 1) + rtw89_set_coex_ctrl_lps(rtwdev, btc->btc_ctrl_lps); ret = _send_fw_cmd(rtwdev, BTFC_SET, SET_CX_POLICY, btc->policy, btc->policy_len); @@ -2813,8 +2818,8 @@ static void _fw_set_policy(struct rtw89_dev *rtwdev, u16 policy_type, if (btc->update_policy_force) btc->update_policy_force = false; - if (btc->lps == 0) - rtw89_set_coex_ctrl_lps(rtwdev, btc->lps); + if (btc->btc_ctrl_lps == 0) + rtw89_set_coex_ctrl_lps(rtwdev, btc->btc_ctrl_lps); } static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index) @@ -2913,21 +2918,35 @@ void btc_fw_event(struct rtw89_dev *rtwdev, u8 evt_id, void *data, u32 len) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_btf_fwinfo *pfwinfo = &btc->fwinfo; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; + u8 i; - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): evt_id:%d len:%d\n", - __func__, evt_id, len); + _parse_btc_report(rtwdev, pfwinfo, data, len); - if (!len || !data) + if (!rtwdev->chip->scbd) return; - switch (evt_id) { - case BTF_EVNT_RPT: - _parse_btc_report(rtwdev, pfwinfo, data, len); - break; - default: - break; + if (!btc->dm.scbd_b2w_update) + return; /* skip if init or no-update */ + + for (i = BTC_BT_1ST; i <= BTC_BT_2ND; i++) { + bt = (i == BTC_BT_1ST) ? &btc->cx.bt0 : &btc->cx.bt1; + + if (bt->scbd_c2h != bt->scbd_rb) { + /* if btx b2w scbd non-sync */ + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s() bt%d:c2h->0x%08x, rb->0x%08x\n", + __func__, i, bt->scbd_c2h, bt->scbd_rb); + bt->scbd_c2h = bt->scbd_rb; + _update_bt_scbd(rtwdev, i); + btc->dm.scbd_b2w_update = false; + } } + + if (!btc->dm.scbd_b2w_update) + _run_coex(rtwdev, BTC_RSN_UPDATE_BT_SCBD); + else + btc->dm.scbd_b2w_update = 0; } static void _set_gnt(struct rtw89_dev *rtwdev, u8 phy_map, u8 wl_state, u8 bt_state) @@ -3298,7 +3317,7 @@ static void _set_bt_rx_gain(struct rtw89_dev *rtwdev, bool force_exec, u8 bid, if (buf[0] != BTC_BT_RX_NORMAL_LVL) state = true; - _write_scbd(rtwdev, scbd_bit, state); + _write_scbd(rtwdev, bid, scbd_bit, state); } } @@ -3406,7 +3425,7 @@ static void _set_rf_trx_para(struct rtw89_dev *rtwdev) if (dm->fddt_train) { _set_wl_rx_gain(rtwdev, 1, RTW89_PHY_0); - _write_scbd(rtwdev, BTC_WSCB_RXGAIN, true); + _write_scbd(rtwdev, bid, BTC_WSCB_RXGAIN, true); } else { _set_wl_tx_power(rtwdev, para.wl_tx_power[RTW89_PHY_0], RTW89_PHY_0); _set_wl_rx_gain(rtwdev, para.wl_rx_gain[RTW89_PHY_0], RTW89_PHY_0); @@ -3899,14 +3918,22 @@ static void _set_tdma_bind(struct rtw89_dev *rtwdev, bool tdma_on) struct rtw89_btc_dm *dm = &rtwdev->btc.dm; struct rtw89_btc_bind_info *bind; u8 null_role = RTW89_WIFI_ROLE_STATION; + u8 bt_sel; if (dm->tdd_en) bind = &dm->tdd_bind; /* tdd = 1 && fdd = 0 or 1 */ else bind = &dm->fdd_bind; /* tdd = 0 && fdd = 1 */ + if (bind->bt_sel >= (BIT(BTC_BT_1ST) | BIT(BTC_BT_2ND))) + bt_sel = BTC_ALL_BT; + else if (bind->bt_sel == BIT(BTC_BT_2ND)) + bt_sel = BTC_BT_2ND; + else + bt_sel = BTC_BT_1ST; + /* notify BT TDMA on/off by scoreboard for ACL/Scan schedule */ - _write_scbd(rtwdev, BTC_WSCB_TDMA, tdma_on); + _write_scbd(rtwdev, bt_sel, BTC_WSCB_TDMA, tdma_on); /* * set hwb/bt bind to TDMA policy parameter @@ -5849,7 +5876,7 @@ static void _set_bt_rx_agc(struct rtw89_dev *rtwdev) if (bt_hi_lna_rx == bt->hi_lna_rx) return; - _write_scbd(rtwdev, BTC_WSCB_BT_HILNA, bt_hi_lna_rx); + _write_scbd(rtwdev, BTC_BT_1ST, BTC_WSCB_BT_HILNA, bt_hi_lna_rx); } static void _set_bt_rx_scan_pri(struct rtw89_dev *rtwdev) @@ -5857,7 +5884,8 @@ static void _set_bt_rx_scan_pri(struct rtw89_dev *rtwdev) struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_bt_info *bt = &btc->cx.bt0; - _write_scbd(rtwdev, BTC_WSCB_RXSCAN_PRI, (bool)(!!bt->scan_rx_low_pri)); + _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_RXSCAN_PRI, + (bool)(!!bt->scan_rx_low_pri)); } static void _wl_req_mac(struct rtw89_dev *rtwdev, u8 mac) @@ -5897,6 +5925,7 @@ static void _action_common(struct rtw89_dev *rtwdev) struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_dm *dm = &btc->dm; u32 bt_rom_code_id, bt_fw_ver; + u8 i; if (btc->ver->fwlrole == 8) _wl_req_mac(rtwdev, rinfo_v8->pta_req_band); @@ -5929,13 +5958,8 @@ static void _action_common(struct rtw89_dev *rtwdev) rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_MREG, 1); } - if (wl->scbd_change) { - rtw89_mac_cfg_sb(rtwdev, wl->scbd); - rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], write scbd: 0x%08x\n", - wl->scbd); - wl->scbd_change = false; - wl->wcnt[BTC_WCNT_SCBDUPDATE]++; - } + for (i = BTC_BT_1ST; i <= BTC_BT_2ND; i++) + _sned_h2c_w2bscbd(rtwdev, false, i); if (btc->ver->fcxosi) { if (memcmp(&dm->ost_info_last, &dm->ost_info, @@ -6187,42 +6211,97 @@ static void _action_wl_2g_nan(struct rtw89_dev *rtwdev) } } -static u32 _read_scbd(struct rtw89_dev *rtwdev) +static u8 _sned_h2c_w2bscbd(struct rtw89_dev *rtwdev, bool force_exec, u8 bid) { - const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; - u32 scbd_val = 0; + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_wl_info *wl = &cx->wl; + u8 cnt_idx = BTC_WCNT_SCBDUPDATE; + u8 h2c_func = SET_IOFLD_SCBD; + u8 buf[4]; - if (!chip->scbd) - return 0; + if (bid == BTC_BT_2ND) { + if (!(rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT)) + return 0; - scbd_val = rtw89_mac_get_sb(rtwdev); - rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], read scbd: 0x%08x\n", - scbd_val); + h2c_func |= BT_H2C_FUNC_BT2ND; + cnt_idx = BTC_WCNT_SCBDUPDATE2; + } - btc->cx.bt0.bcnt[BTC_BCNT_SCBDREAD]++; - return scbd_val; + if (wl->scbd_chg[bid] || force_exec) { + /* Add delay to avoid BT FW loss information */ + + btc_dw2b(buf, 0, (wl->scbd[bid] | BIT(31))); /* trig */ + if (rtwdev->chip->para_ver & BTC_FEAT_H2C_MACRO) { + if (!_send_fw_cmd(rtwdev, BTFC_SET, + h2c_func, buf, sizeof(buf))) + return 0; + } else { + rtw89_mac_cfg_sb(rtwdev, wl->scbd[bid]); + } + wl->scbd_chg[bid] = 0; + wl->wcnt[cnt_idx]++; + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s() write BT%d scbd:0x%08x\n", + __func__, bid, wl->scbd[bid]); + } + return 1; } -static void _write_scbd(struct rtw89_dev *rtwdev, u32 val, bool state) +static void _write_scbd(struct rtw89_dev *rtwdev, u8 bid, u32 val, bool state) { const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - u32 scbd_val = 0; - u8 force_exec = false; + struct rtw89_btc_dm *dm = &btc->dm; + u8 force_exec, id, id_start, id_stop; + u32 scbd = 0; if (!chip->scbd) return; - scbd_val = state ? wl->scbd | val : wl->scbd & ~val; + if (bid == BTC_ALL_BT) { + id_start = BTC_BT_1ST; + id_stop = BTC_BT_2ND; + } else { + id_start = bid; + id_stop = bid; + } - if (val & BTC_WSCB_ACTIVE || val & BTC_WSCB_ON) - force_exec = true; + for (id = id_start; id <= id_stop; id++) { + force_exec = false; + if (!(rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) && + id == BTC_BT_2ND) + continue; - if (scbd_val != wl->scbd || force_exec) { - wl->scbd = scbd_val; - wl->scbd_change = true; + if (state) + scbd = wl->scbd[id] | val; + else + scbd = wl->scbd[id] & (~val); + + if (val & BTC_WSCB_CTCODE || + val & BTC_WSCB_RXGAIN || + val & BTC_WSCB_RXGAIN_56G) { /* instant exec */ + force_exec = true; + wl->scbd[id] = scbd; + dm->scbd_write_instant = 1; + if (rtwdev->chip->para_ver & BTC_FEAT_H2C_MACRO) + _sned_h2c_w2bscbd(rtwdev, force_exec, id); + else + rtw89_mac_cfg_sb(rtwdev, wl->scbd[id]); + dm->scbd_write_instant = 0; + } else if ((val & BTC_WSCB_ACTIVE || + val & BTC_WSCB_ON || + val & BTC_WSCB_WLRFK) || + scbd != wl->scbd[id]) { + /* + * Just update wl->scbd[] and set wl->scbd_chg[], + * moved "Write scoreboard I/O" to _action_common() + * _write_scbd will be executed if run_coex() + */ + wl->scbd[id] = scbd; + wl->scbd_chg[id] = 1; + } } } @@ -7522,35 +7601,28 @@ void rtw89_coex_rfk_chk_work(struct wiphy *wiphy, struct wiphy_work *work) wl->wcnt[BTC_WCNT_RFK_TIMEOUT]++; dm->error.map.wl_rfk_timeout = true; wl->rfk_info.state = BTC_WRFK_STOP; - _write_scbd(rtwdev, BTC_WSCB_WLRFK, false); + _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_WLRFK, false); _run_coex(rtwdev, BTC_RSN_RFK_CHK_WORK); } } -static void _update_bt_scbd(struct rtw89_dev *rtwdev, bool only_update) +static void _update_bt_scbd(struct rtw89_dev *rtwdev, u8 bid) { + struct rtw89_btc_bt_link_info *bt_2g, *bt_56g; struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_info *bt = &btc->cx.bt0; const struct rtw89_btc_ver *ver = btc->ver; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_dm *dm = &rtwdev->btc.dm; - bool bt_link_change = false, lps_ctrl = false; - u32 val, any_bt_connect; - u8 mode; + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_wl_info *wl = &cx->wl; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_bt_info *bt; + u32 val, any_bt_connect, any_bt_6g_connect = 0; + u8 id, id_start, id_stop, mode; + bool bt_link_change = false; + bool lps_ctrl = false; - if (rtwdev->chip->scbd) + if (!rtwdev->chip->scbd || bid > BTC_ALL_BT) return; - rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s\n", __func__); - - val = _read_scbd(rtwdev); - if (val == BTC_SCB_INV_VALUE) { - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): return by invalid scbd value\n", - __func__); - return; - } - if (ver->fwlrole == 0) mode = wl->role_info.link_mode; else if (ver->fwlrole == 1) @@ -7564,66 +7636,112 @@ static void _update_bt_scbd(struct rtw89_dev *rtwdev, bool only_update) else return; - if (!(val & BTC_BSCB_ON)) - bt->enable.now = 0; - else - bt->enable.now = 1; - - if (bt->enable.now != bt->enable.last) - bt_link_change = true; - - /* reset bt info if bt re-enable */ - if (bt->enable.now && !bt->enable.last) { - _reset_btc_var(rtwdev, BTC_RESET_BTINFO); - bt->bcnt[BTC_BCNT_REENABLE]++; - bt->enable.now = 1; - } - - bt->enable.last = bt->enable.now; - bt->scbd = val; - bt->mbx_avl = !!(val & BTC_BSCB_ACT); - - if (bt->whql_test != !!(val & BTC_BSCB_WHQL)) - bt_link_change = true; - - bt->whql_test = !!(val & BTC_BSCB_WHQL); - bt->btg_type = val & BTC_BSCB_BT_S1 ? BTC_BT_BTG : BTC_BT_ALONE; - bt->link_info.a2dp_desc.exist = !!(val & BTC_BSCB_A2DP_ACT); - bt->link_info.pan_desc.exist = !!(val & BTC_BSCB_PAN_ACT); - bt->link_info.hfp_desc.exist = !!(val & BTC_BSCB_HFP_ACT); - - bt->lna_constrain = !!(val & BTC_BSCB_BT_LNAB0) + - !!(val & BTC_BSCB_BT_LNAB1) * 2 + 4; - - /* if rfk run 1->0 */ - if (bt->rfk_info.map.run && !(val & BTC_BSCB_RFK_RUN)) - bt_link_change = true; - - bt->rfk_info.map.run = !!(val & BTC_BSCB_RFK_RUN); - bt->rfk_info.map.req = !!(val & BTC_BSCB_RFK_REQ); - bt->hi_lna_rx = !!(val & BTC_BSCB_BT_HILNA); - any_bt_connect = !!(val & BTC_BSCB_BT_CONNECT); - - /* if connect change */ - if (bt->link_info.status.map.connect != any_bt_connect) - bt_link_change = true; - - /* if specific profile exist */ - if (((bt->link_info.a2dp_desc.exist || bt->link_info.pan_desc.exist || - bt->link_info.hfp_desc.exist) && mode == BTC_WLINK_2G_STA) || - bt->whql_test) - lps_ctrl = true; - - if (dm->lps_ctrl_scbd != lps_ctrl) { - dm->lps_ctrl_scbd = lps_ctrl; - bt_link_change = true; - dm->lps_ctrl_change = true; + if (bid == BTC_ALL_BT) { + id_start = BTC_BT_1ST; + id_stop = BTC_BT_2ND; } else { - dm->lps_ctrl_change = false; + id_start = bid; + id_stop = bid; } - bt->link_info.status.map.connect = any_bt_connect; - bt->run_patch_code = !!(val & BTC_BSCB_PATCH_CODE); + dm->lps_ctrl_change = false; + rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s()\n", __func__); + + for (id = id_start; id <= id_stop; id++) { + bt = (id == BTC_BT_1ST) ? &cx->bt0 : &cx->bt1; + bt_2g = &bt->link_info; + bt_56g = &bt->link_info_56g; + + if (!(rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) && id == BTC_BT_2ND) + break; + + val = bt->scbd_c2h; + + if (val == 0xffffffff) { + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s return by invalid scbd value\n", + __func__); + return; + } + + if (!(val & BTC_BSCB_ON)) + bt->enable.now = 0; + else + bt->enable.now = 1; + + if (bt->enable.now != bt->enable.last) + bt_link_change = true; + + /* reset bt info if bt re-enable */ + if (bt->enable.now && !bt->enable.last) { + if (id == BTC_BT_1ST) + _reset_btc_var(rtwdev, BTC_RESET_BTINFO); + else + _reset_btc_var(rtwdev, BTC_RESET_BTINFO2); + + bt->bcnt[BTC_BCNT_REENABLE]++; + bt->enable.now = 1; + bt->rf_band_map = BIT(RTW89_BAND_2G); + bt->link_weight[BTC_BT_B2G] = 5; /* no-profile */ + } + bt->enable.last = bt->enable.now; + + /* if BT will disconnect and adopt protect plan */ + if ((val & BTC_BSCB_BT_PROTECT) && + !(bt->scbd & BTC_BSCB_BT_PROTECT)) + bt->bcnt[BTC_BCNT_PROTECT]++; + + bt->mbx_avl = !!(val & BTC_BSCB_ACT); + if (bt->whql_test != !!(val & BTC_BSCB_WHQL)) + bt_link_change = true; + + bt->whql_test = !!(val & BTC_BSCB_WHQL); + bt->btg_type = (val & BTC_BSCB_BT_S1 ? BTC_BT_BTG : BTC_BT_ALONE); + + bt_2g->a2dp_desc.exist = !!(val & BTC_BSCB_A2DP_ACT); + bt_2g->pan_desc.exist = !!(val & BTC_BSCB_PAN_ACT); + bt_2g->hfp_desc.exist = !!(val & BTC_BSCB_HFP_ACT); + + bt->lna_constrain = 4 + !!(val & BTC_BSCB_BT_LNAB0) + + !!(val & BTC_BSCB_BT_LNAB1) * 2; + + if (rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) { + if (val & BTC_BSCB_BT_15DOT4) + bt->func_type |= (BTC_BTF_THREAD | BTC_BTF_ZB); + else + bt->func_type &= ~(BTC_BTF_THREAD | BTC_BTF_ZB); + } + + bt->hi_lna_rx = !!(val & BTC_BSCB_BT_HILNA); + any_bt_connect = !!(val & BTC_BSCB_BT_CONNECT); + + /* if connect change */ + if (bt_2g->status.map.connect != any_bt_connect || + bt_56g->status.map.connect != any_bt_6g_connect) { + bt_link_change = true; + bt_2g->status.map.connect = any_bt_connect; + bt_56g->status.map.connect = any_bt_6g_connect; + } + + /* if specific profile exist */ + if (((bt->link_info.a2dp_desc.exist || + bt->link_info.pan_desc.exist || + bt->link_info.hfp_desc.exist) && + mode == BTC_WLINK_2G_STA) || + bt->whql_test) + lps_ctrl = true; + + if (dm->lps_ctrl_scbd != lps_ctrl) { + dm->lps_ctrl_scbd = lps_ctrl; + bt_link_change = true; + dm->lps_ctrl_change = true; + } else { + dm->lps_ctrl_change = false; + } + + bt->run_patch_code = !!(val & BTC_BSCB_PATCH_CODE); + bt->scbd = val; + } if (bt_link_change) { rtw89_debug(rtwdev, RTW89_DBG_BTC, @@ -7651,26 +7769,6 @@ static void _update_bt_txpwr_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) memcpy(&b->bt_txpwr_desc, &buf[2], sizeof(b->bt_txpwr_desc)); } -static bool _chk_wl_rfk_request(struct rtw89_dev *rtwdev) -{ - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_cx *cx = &btc->cx; - struct rtw89_btc_bt_info *bt = &cx->bt0; - - _update_bt_scbd(rtwdev, true); - - cx->wl.wcnt[BTC_WCNT_RFK_REQ]++; - - if ((bt->rfk_info.map.run || bt->rfk_info.map.req) && - !bt->rfk_info.map.timeout) { - cx->wl.wcnt[BTC_WCNT_RFK_REJECT]++; - } else { - cx->wl.wcnt[BTC_WCNT_RFK_GO]++; - return true; - } - return false; -} - static void _set_bind_info(struct rtw89_btc *btc, u8 type) { struct rtw89_btc_cx *cx = &btc->cx; @@ -8181,7 +8279,7 @@ void rtw89_btc_ntfy_poweroff(struct rtw89_dev *rtwdev) btc->cx.wl.status.map.busy = 0; wl->status.map.lps = BTC_LPS_OFF; - _write_scbd(rtwdev, BTC_WSCB_ALL, false); + _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_ALL, false); _run_coex(rtwdev, BTC_RSN_NTFY_POWEROFF); rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_ALL, 0); @@ -8266,24 +8364,22 @@ void rtw89_btc_ntfy_init(struct rtw89_dev *rtwdev, u8 mode) return; } + btc->cx.bt0.enable.now = 1; + btc->cx.bt0.run_patch_code = 1; if (rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) { btc->cx.bt1.enable.now = 1; btc->cx.bt1.run_patch_code = 1; } - if (rtwdev->chip->para_ver & BTC_FEAT_H2C_MACRO) { - btc->cx.bt0.enable.now = 1; - btc->cx.bt0.run_patch_code = 1; + if (rtwdev->chip->para_ver & BTC_FEAT_H2C_MACRO) btc->io_oflld_type = BTC_IO_OFLD_BTC_H2C; - } else { + else btc->io_oflld_type = BTC_IO_OFLD_NO_SUPPORT; - _update_bt_scbd(rtwdev, true); - } chip->ops->btc_set_rfe(rtwdev); chip->ops->btc_init_cfg(rtwdev); - _write_scbd(rtwdev, + _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_ACTIVE | BTC_WSCB_ON | BTC_WSCB_BTLOG, true); if (rtw89_mac_get_ctrl_path(rtwdev)) { @@ -8842,16 +8938,16 @@ void rtw89_btc_ntfy_radio_state(struct rtw89_dev *rtwdev, enum btc_rfctrl rf_sta if (rf_state == BTC_RFCTRL_WL_ON) { rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_MREG, true); val = BTC_WSCB_ACTIVE | BTC_WSCB_ON | BTC_WSCB_BTLOG; - _write_scbd(rtwdev, val, true); + _write_scbd(rtwdev, BTC_ALL_BT, val, true); chip->ops->btc_init_cfg(rtwdev); } else { rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_ALL, false); if (rf_state == BTC_RFCTRL_FW_CTRL) - _write_scbd(rtwdev, BTC_WSCB_ACTIVE, false); + _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_ACTIVE, false); else if (rf_state == BTC_RFCTRL_WL_OFF) - _write_scbd(rtwdev, BTC_WSCB_ALL, false); + _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_ALL, false); else - _write_scbd(rtwdev, BTC_WSCB_ACTIVE, false); + _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_ACTIVE, false); } btc->dm.cnt_dm[BTC_DCNT_BTCNT_HANG] = 0; @@ -8883,27 +8979,26 @@ static bool _ntfy_wl_rfk(struct rtw89_dev *rtwdev, u8 phy_path, switch (state) { case BTC_WRFK_START: - result = _chk_wl_rfk_request(rtwdev); - wl->rfk_info.state = result ? BTC_WRFK_START : BTC_WRFK_STOP; - - _write_scbd(rtwdev, BTC_WSCB_WLRFK, result); + result = BTC_WRFK_ALLOW; + wl->rfk_info.state = BTC_WRFK_START; + btc->cx.wl.wcnt[BTC_WCNT_RFK_REQ]++; + btc->cx.wl.wcnt[BTC_WCNT_RFK_GO]++; btc->dm.cnt_notify[BTC_NCNT_WL_RFK]++; + + _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_WLRFK, true); break; case BTC_WRFK_ONESHOT_START: case BTC_WRFK_ONESHOT_STOP: - if (wl->rfk_info.state == BTC_WRFK_STOP) { - result = BTC_WRFK_REJECT; - } else { - result = BTC_WRFK_ALLOW; - wl->rfk_info.state = state; - } + wl->rfk_info.state = state; + if (type != BTC_WRFKT_RXDCK) + return BTC_WRFK_ALLOW; break; case BTC_WRFK_STOP: result = BTC_WRFK_ALLOW; wl->rfk_info.state = BTC_WRFK_STOP; - _write_scbd(rtwdev, BTC_WSCB_WLRFK, false); + _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_WLRFK, false); wiphy_delayed_work_cancel(rtwdev->hw->wiphy, &rtwdev->coex_rfk_chk_work); break; default: @@ -8927,7 +9022,7 @@ static bool _ntfy_wl_rfk(struct rtw89_dev *rtwdev, u8 phy_path, "[BTC], %s()_finish: rfk_cnt=%d, result=%d\n", __func__, btc->dm.cnt_notify[BTC_NCNT_WL_RFK], result); - return result == BTC_WRFK_ALLOW; + return result; } void rtw89_btc_ntfy_wl_rfk(struct rtw89_dev *rtwdev, u8 phy_map, @@ -9160,7 +9255,7 @@ void rtw89_btc_ntfy_wl_sta(struct rtw89_dev *rtwdev) rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): busy=%d\n", __func__, !!wl->status.map.busy); - _write_scbd(rtwdev, BTC_WSCB_WLBUSY, (!!wl->status.map.busy)); + _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_WLBUSY, (!!wl->status.map.busy)); if (data.is_traffic_change) _fw_set_drv_info(rtwdev, CXDRVINFO_ROLE); @@ -9251,6 +9346,7 @@ void rtw89_btc_c2h_handle(struct rtw89_dev *rtwdev, struct sk_buff *skb, struct rtw89_btc_bt_info *bt = &rtwdev->btc.cx.bt0; struct rtw89_btc_btf_fwinfo *pfwinfo = &btc->fwinfo; u8 *buf = &skb->data[RTW89_C2H_HEADER_LEN]; + u8 bid = BTC_BT_1ST; len -= RTW89_C2H_HEADER_LEN; @@ -9261,6 +9357,12 @@ void rtw89_btc_c2h_handle(struct rtw89_dev *rtwdev, struct sk_buff *skb, if (class != BTFC_FW_EVENT) return; + if (func & BT_C2H_FUNC_BT2ND) { + bid = BTC_BT_2ND; + func &= ~BT_C2H_FUNC_BT2ND; + bt = &btc->cx.bt1; + } + func = rtw89_btc_c2h_get_index_by_ver(rtwdev, func); pfwinfo->cnt_c2h++; @@ -9280,10 +9382,15 @@ void rtw89_btc_c2h_handle(struct rtw89_dev *rtwdev, struct sk_buff *skb, _update_bt_info(rtwdev, buf, len); break; case BTF_EVNT_BT_SCBD: - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], handle C2H BT SCBD with data %8ph\n", buf); bt->bcnt[BTC_BCNT_SCBDUPDATE]++; - _update_bt_scbd(rtwdev, false); + bt->scbd_c2h = ((buf[3] << 24) | (buf[2] << 16) | + (buf[1] << 8) | (buf[0])); + bt->scbd_rb = bt->scbd_c2h; + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], handle C2H BT%d SCBD with data 0x%08x\n", + bid, bt->scbd_c2h); + _update_bt_scbd(rtwdev, bid); + _run_coex(rtwdev, BTC_RSN_UPDATE_BT_SCBD); break; case BTF_EVNT_BT_PSD: break; @@ -9300,7 +9407,7 @@ void rtw89_btc_c2h_handle(struct rtw89_dev *rtwdev, struct sk_buff *skb, btc->dm.cnt_dm[BTC_DCNT_CX_RUNINFO]++; break; case BTF_EVNT_BT_QUERY_TXPWR: - bt->bcnt[BTC_BCNT_BTTXPWR_UPDATE]++; + bt->bcnt[BTC_BCNT_TXPWR_UPDATE]++; _update_bt_txpwr_info(rtwdev, buf, len); } } @@ -9680,7 +9787,7 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) bt->bcnt[BTC_BCNT_HIPRI_TX], bt->bcnt[BTC_BCNT_LOPRI_RX], bt->bcnt[BTC_BCNT_LOPRI_TX], - bt->bcnt[BTC_BCNT_POLUT]); + bt->bcnt[BTC_BCNT_POLLUTED]); if (!bt->scan_info_update) { rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_SCAN_INFO, true); @@ -9716,7 +9823,7 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) else rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_TX_PWR_LVL, false); - if (bt->bcnt[BTC_BCNT_BTTXPWR_UPDATE]) { + if (bt->bcnt[BTC_BCNT_TXPWR_UPDATE]) { p += scnprintf(p, end - p, " %-15s : br_index:0x%x, le_index:0x%x", "[bt_txpwr_lvl]", @@ -10127,7 +10234,7 @@ static int _show_dm_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, " %-15s : wl_only:%d, bt_only:%d, igno_bt:%d, free_run:%d, wl_ps_ctrl:%d, wl_mimo_ps:%d, ", "[dm_flag]", dm->wl_only, dm->bt_only, igno_bt, - dm->freerun, btc->lps, dm->wl_mimo_ps); + dm->freerun, btc->btc_ctrl_lps, dm->wl_mimo_ps); p += scnprintf(p, end - p, "leak_ap:%d, fw_offload:%s%s\n", dm->leak_ap, @@ -11353,10 +11460,11 @@ static int _show_mreg_v1(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_btf_fwinfo *pfwinfo = &btc->fwinfo; - struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; struct rtw89_btc_fbtc_mreg_val_v1 *pmreg = NULL; + struct rtw89_btc_bt_info *bt0 = &btc->cx.bt0; + struct rtw89_btc_bt_info *bt1 = &btc->cx.bt1; + struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_mac_ax_coex_gnt gnt_cfg = {}; struct rtw89_mac_ax_gnt gnt; char *p = buf, *end = buf + bufsz; @@ -11369,11 +11477,20 @@ static int _show_mreg_v1(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, "========== [HW Status] ==========\n"); p += scnprintf(p, end - p, - " %-15s : WL->BT:0x%08x(cnt:%d), BT->WL:0x%08x(total:%d, bt_update:%d)\n", - "[scoreboard]", wl->scbd, + " %-15s : WL->BT0:0x%08x(cnt:%d), BT0->WL:0x%08x(total:%d, bt_update:%d)\n", + "[scoreboard]", wl->scbd[BTC_BT_1ST], wl->wcnt[BTC_WCNT_SCBDUPDATE], - bt->scbd, bt->bcnt[BTC_BCNT_SCBDREAD], - bt->bcnt[BTC_BCNT_SCBDUPDATE]); + bt0->scbd, bt0->bcnt[BTC_BCNT_SCBDREAD], + bt0->bcnt[BTC_BCNT_SCBDUPDATE]); + + if (rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) { + p += scnprintf(p, end - p, + " %-15s : WL->BT1:0x%08x(cnt:%d), BT1->WL:0x%08x(total:%d, bt_update:%d)\n", + "[scoreboard]", wl->scbd[BTC_BT_2ND], + wl->wcnt[BTC_WCNT_SCBDUPDATE2], + bt1->scbd, bt1->bcnt[BTC_BCNT_SCBDREAD], + bt1->bcnt[BTC_BCNT_SCBDUPDATE]); + } btc->dm.pta_owner = rtw89_mac_get_ctrl_path(rtwdev); _get_gnt(rtwdev, &gnt_cfg); @@ -11437,10 +11554,11 @@ static int _show_mreg_v2(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_btf_fwinfo *pfwinfo = &btc->fwinfo; - struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; struct rtw89_btc_fbtc_mreg_val_v2 *pmreg = NULL; + struct rtw89_btc_bt_info *bt0 = &btc->cx.bt0; + struct rtw89_btc_bt_info *bt1 = &btc->cx.bt1; + struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_mac_ax_coex_gnt gnt_cfg = {}; struct rtw89_mac_ax_gnt gnt; char *p = buf, *end = buf + bufsz; @@ -11453,11 +11571,20 @@ static int _show_mreg_v2(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, "========== [HW Status] ==========\n"); p += scnprintf(p, end - p, - " %-15s : WL->BT:0x%08x(cnt:%d), BT->WL:0x%08x(total:%d, bt_update:%d)\n", - "[scoreboard]", wl->scbd, + " %-15s : WL->BT0:0x%08x(cnt:%d), BT0->WL:0x%08x(total:%d, bt_update:%d)\n", + "[scoreboard]", wl->scbd[BTC_BT_1ST], wl->wcnt[BTC_WCNT_SCBDUPDATE], - bt->scbd, bt->bcnt[BTC_BCNT_SCBDREAD], - bt->bcnt[BTC_BCNT_SCBDUPDATE]); + bt0->scbd, bt0->bcnt[BTC_BCNT_SCBDREAD], + bt0->bcnt[BTC_BCNT_SCBDUPDATE]); + + if (rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) { + p += scnprintf(p, end - p, + " %-15s : WL->BT1:0x%08x(cnt:%d), BT1->WL:0x%08x(total:%d, bt_update:%d)\n", + "[scoreboard]", wl->scbd[BTC_BT_2ND], + wl->wcnt[BTC_WCNT_SCBDUPDATE2], + bt1->scbd, bt1->bcnt[BTC_BCNT_SCBDREAD], + bt1->bcnt[BTC_BCNT_SCBDUPDATE]); + } btc->dm.pta_owner = rtw89_mac_get_ctrl_path(rtwdev); _get_gnt(rtwdev, &gnt_cfg); @@ -11524,8 +11651,9 @@ static int _show_mreg_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) struct rtw89_btc_fbtc_mreg_val_v7 *pmreg = NULL; struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_bt_info *bt0 = &cx->bt0; + struct rtw89_btc_bt_info *bt1 = &cx->bt1; struct rtw89_btc_wl_info *wl = &cx->wl; - struct rtw89_btc_bt_info *bt = &cx->bt0; struct rtw89_mac_ax_gnt *gnt = NULL; struct rtw89_btc_dm *dm = &btc->dm; char *p = buf, *end = buf + bufsz; @@ -11538,11 +11666,20 @@ static int _show_mreg_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, "\n\r========== [HW Status] =========="); p += scnprintf(p, end - p, - "\n\r %-15s : WL->BT:0x%08x(cnt:%d), BT->WL:0x%08x(total:%d, bt_update:%d)", - "[scoreboard]", wl->scbd, + " %-15s : WL->BT0:0x%08x(cnt:%d), BT0->WL:0x%08x(total:%d, bt_update:%d)\n", + "[scoreboard]", wl->scbd[BTC_BT_1ST], wl->wcnt[BTC_WCNT_SCBDUPDATE], - bt->scbd, bt->bcnt[BTC_BCNT_SCBDREAD], - bt->bcnt[BTC_BCNT_SCBDUPDATE]); + bt0->scbd, bt0->bcnt[BTC_BCNT_SCBDREAD], + bt0->bcnt[BTC_BCNT_SCBDUPDATE]); + + if (rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) { + p += scnprintf(p, end - p, + " %-15s : WL->BT1:0x%08x(cnt:%d), BT1->WL:0x%08x(total:%d, bt_update:%d)\n", + "[scoreboard]", wl->scbd[BTC_BT_2ND], + wl->wcnt[BTC_WCNT_SCBDUPDATE2], + bt1->scbd, bt1->bcnt[BTC_BCNT_SCBDREAD], + bt1->bcnt[BTC_BCNT_SCBDUPDATE]); + } /* To avoid I/O if WL LPS or power-off */ dm->pta_owner = rtw89_mac_get_ctrl_path(rtwdev); diff --git a/drivers/net/wireless/realtek/rtw89/coex.h b/drivers/net/wireless/realtek/rtw89/coex.h index fb151f68eb64..74027ca4eccc 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.h +++ b/drivers/net/wireless/realtek/rtw89/coex.h @@ -391,4 +391,13 @@ void _slot_set_tbl(struct rtw89_btc *btc, u8 sid, u32 tbl) btc->dm.slot.v7[sid].cxtbl = cpu_to_le32(tbl); } +static inline +void btc_dw2b(u8 *buf, size_t idx, u32 val) +{ + buf[idx] = u32_get_bits(val, MASKBYTE0); + buf[idx + 1] = u32_get_bits(val, MASKBYTE1); + buf[idx + 2] = u32_get_bits(val, MASKBYTE2); + buf[idx + 3] = u32_get_bits(val, MASKBYTE3); +} + #endif diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index 85aeb9e90812..b3376fadf593 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -5417,7 +5417,7 @@ static void rtw89_track_ps_work(struct wiphy *wiphy, struct wiphy_work *work) if (rtwdev->scanning) return; - if (rtwdev->lps_enabled && !rtwdev->btc.lps) + if (rtwdev->lps_enabled && !rtwdev->btc.btc_ctrl_lps) rtw89_enter_lps_track(rtwdev, RTW89_TFC_INTERVAL_100MS); } @@ -5465,7 +5465,7 @@ static void rtw89_track_work(struct wiphy *wiphy, struct wiphy_work *work) rtw89_core_rfkill_poll(rtwdev, false); rtw89_core_mlo_track(rtwdev); - if (rtwdev->lps_enabled && !rtwdev->btc.lps) + if (rtwdev->lps_enabled && !rtwdev->btc.btc_ctrl_lps) rtw89_enter_lps_track(rtwdev, RTW89_TFC_INTERVAL_2SEC); } diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index eb814425f536..8646a13bfd79 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -1415,6 +1415,7 @@ enum rtw89_btc_wl_state_cnt { BTC_WCNT_RX_ERR_LAST, BTC_WCNT_RX_ERR_LAST2S, BTC_WCNT_RX_LAST, + BTC_WCNT_SCBDUPDATE2, BTC_WCNT_NUM }; @@ -1431,17 +1432,25 @@ enum rtw89_btc_bt_state_cnt { BTC_BCNT_ROLESW, BTC_BCNT_AFH, BTC_BCNT_INFOUPDATE, + BTC_BCNT_LEAUDIO_INFOUPDATE, BTC_BCNT_INFOSAME, + BTC_BCNT_LEAUDIO_INFOSAME, BTC_BCNT_SCBDUPDATE, BTC_BCNT_HIPRI_TX, BTC_BCNT_HIPRI_RX, BTC_BCNT_LOPRI_TX, BTC_BCNT_LOPRI_RX, - BTC_BCNT_POLUT, BTC_BCNT_POLUT_NOW, BTC_BCNT_POLUT_DIFF, BTC_BCNT_RATECHG, - BTC_BCNT_BTTXPWR_UPDATE, + BTC_BCNT_AFH_CONFLICT, + BTC_BCNT_AFH_LE_CONFLICT, + BTC_BCNT_AFH_UPDATE, + BTC_BCNT_AFH_LE_UPDATE, + BTC_BCNT_AFH_CHN, + BTC_BCNT_AFH_LE_CHN, + BTC_BCNT_TXPWR_UPDATE, + BTC_BCNT_PROTECT, BTC_BCNT_NUM, }; @@ -2188,12 +2197,13 @@ struct rtw89_btc_wl_info { bool pta_reg_mac_chg; bool bg_mode; bool he_mode; - bool scbd_change; + bool scbd_chg[BTC_ALL_BT]; bool fw_ver_mismatch; bool client_cnt_inc_2g; bool link_mode_chg; bool dbcc_chg; - u32 scbd; + u32 scbd[BTC_ALL_BT]; + u32 scbd_rb[BTC_ALL_BT]; u32 wcnt[BTC_WCNT_NUM]; }; @@ -2323,6 +2333,15 @@ enum rtw89_btc_ble_scan_type { CXSCAN_MAX }; +enum rtw89_btc_bt_func_type { + BTC_BTF_NONE = 0, + BTC_BTF_BT = BIT(0), + BTC_BTF_ZB = BIT(1), + BTC_BTF_THREAD = BIT(2), + BTC_BTF_24GPRO = BIT(3), /* 2.4GHz Proprietary */ + BTC_BTF_ULL = BIT(4), +}; + #define RTW89_BTC_BTC_SCAN_V1_FLAG_ENABLE BIT(0) #define RTW89_BTC_BTC_SCAN_V1_FLAG_INTERLACE BIT(1) @@ -2394,6 +2413,8 @@ struct rtw89_btc_bt_info { u8 rsvd: 6; u32 scbd; + u32 scbd_rb; + u32 scbd_c2h; u32 feature; u32 mbx_avl: 1; @@ -3338,6 +3359,8 @@ struct rtw89_btc_dm { u8 lps_ctrl_scbd: 1; u8 lps_ctrl_scbd_last: 1; u8 lps_ctrl_change: 1; + u8 scbd_write_instant; + bool scbd_b2w_update; }; struct rtw89_btc_ctrl { @@ -3585,7 +3608,7 @@ struct rtw89_btc { u32 hubmsg_cnt; bool bt_req_en; bool update_policy_force; - bool lps; + bool btc_ctrl_lps; bool manual_ctrl; bool cli_h2c_cmd; }; From d01bcd34dd98d74c38a6c3e8bdbd94c00ee86a8f Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Mon, 6 Jul 2026 10:54:45 +0800 Subject: [PATCH 0277/1433] wifi: rtw89: coex: Add Co-RX logic Co-RX means Wi-Fi & Bluetooth can be able to RX in the same time. This patch is for judging the Wi-Fi/Bluetooth condition could be Co-RX or not, and how to set the gain and power. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260706025445.18428-10-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 69 +++++++++++++++++++++++ drivers/net/wireless/realtek/rtw89/core.h | 1 + 2 files changed, 70 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 9e51906b1ccc..989344e93ab7 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -3319,7 +3319,74 @@ static void _set_bt_rx_gain(struct rtw89_dev *rtwdev, bool force_exec, u8 bid, _write_scbd(rtwdev, bid, scbd_bit, state); } +} +static void _set_bt_corx_table(struct rtw89_dev *rtwdev, bool en) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_bt_info *bt = &cx->bt0; + struct rtw89_btc_dm *dm = &btc->dm; + u8 is_24g, is_56g, i; + u32 scbd_bit; + + /* + * true: bt use Hi-LNA rx gain table (f/e/3/2) in -3x~-9xdBm for co-rx + * false: bt use original rx gain table (f/b/7/3/2) + */ + + if (btc->dm.fdd_bind.bt_sel == BIT(BTC_BT_EXT)) + return; + + for (i = BTC_BT_1ST; i <= BTC_BT_2ND; i++) { + scbd_bit = 0; + if (i == BTC_BT_2ND) { + if (!(rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT)) + continue; + bt = &cx->bt1; + } + + is_24g = (dm->corx_map[BTC_RF_S0][i] || + dm->corx_map[BTC_RF_S1][i]) & BIT(RTW89_BAND_2G); + is_56g = (dm->corx_map[BTC_RF_S0][i] || + dm->corx_map[BTC_RF_S1][i]) & BIT(RTW89_BAND_5G); + if (is_24g) + scbd_bit |= BTC_WSCB_BT_HILNA; + if (is_56g) + scbd_bit |= BTC_WSCB_BT_HILNA_56G; + + if ((is_24g && (en != (!!bt->hi_lna_rx))) || + (is_56g && (en != (!!bt->hi_lna_rx_6g)))) + _write_scbd(rtwdev, i, scbd_bit, en); + } +} + +static void _set_rf_trx_para_v9(struct rtw89_dev *rtwdev) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_wl_smap *wl_smap = &cx->wl.status.map; + struct rtw89_btc_dm *dm = &btc->dm; + bool bt_2r = false; + u8 wl_stb_chg = 0; + + if (wl_smap->rf_off || wl_smap->lps == BTC_LPS_RF_OFF) + return; + + /* must call after _set_halbb_btg_ctrl() */ + if (dm->tdd_bind.wl_link_mode != BTC_WLINK_NOLINK) { + wl_stb_chg = 1; + if (dm->wl_btg_rx) + bt_2r = true; + } + + _set_bt_corx_table(rtwdev, bt_2r); + + if (wl_stb_chg != dm->wl_stb_chg) { + dm->wl_stb_chg = wl_stb_chg; + dm->ost_info.wl_btg_standby_chg = wl_stb_chg; + rtwdev->chip->ops->btc_wl_s1_standby(rtwdev, dm->wl_stb_chg); + } } static void _set_rf_trx_para(struct rtw89_dev *rtwdev) @@ -3356,6 +3423,8 @@ static void _set_rf_trx_para(struct rtw89_dev *rtwdev) if (ver->fcxtrx == 9 && chip->rf_para_ulink_v9) { ul_para_num = chip->rf_para_ulink_num_v9; dl_para_num = chip->rf_para_dlink_num_v9; + _set_rf_trx_para_v9(rtwdev); + return; } else if (ver->fcxtrx == 0 && chip->rf_para_ulink_v0) { ul_para_num = chip->rf_para_ulink_num_v0; dl_para_num = chip->rf_para_dlink_num_v0; diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 8646a13bfd79..524b4974040c 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -3278,6 +3278,7 @@ struct rtw89_btc_fbtc_outsrc_set_info { u8 pta_req_hw_band; u8 rf_gbt_source; u8 bt_enable_state; + u8 wl_btg_standby_chg; } __packed; union rtw89_btc_fbtc_slot_u { From be8aedb68c9645d5b76f262f7efc7e6a5581bc9a Mon Sep 17 00:00:00 2001 From: Eric Huang Date: Tue, 7 Jul 2026 17:10:42 +0800 Subject: [PATCH 0278/1433] wifi: rtw89: 8922d: remove CCK bandwidth compensation Remove the 40MHz bandwidth compensation from CCK efuse gain calculation. The design no longer requires the +3dB compensation for 40MHz channels. Signed-off-by: Eric Huang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260707091056.42771-2-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/rtw8922d.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c index f78d6d46e65f..625f8d675f08 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c @@ -1634,10 +1634,6 @@ static void rtw8922d_calc_rx_gain_normal_cck(struct rtw89_dev *rtwdev, s8 rx_gain_offset; rx_gain_offset = -rtw8922d_get_rx_gain_by_chan(rtwdev, chan, path, true); - - if (chan->band_width == RTW89_CHANNEL_WIDTH_40) - rx_gain_offset += (3 << 2); /* compensate RPL loss of 3dB */ - calc->cck_mean_gain_bias = (rx_gain_offset & 0x3) << 1; calc->cck_rpl_ofst = (rx_gain_offset >> 2) + gain->cck_rpl_base[phy_idx]; } From daa3fda5aeb9a8dc8a3f961dcbca8c740f59be07 Mon Sep 17 00:00:00 2001 From: Eric Huang Date: Tue, 7 Jul 2026 17:10:43 +0800 Subject: [PATCH 0279/1433] wifi: rtw89: 8922d: dynamic adjust channel smoothing Add support for path difference based channel smoothing for RTL8922D chip. This feature measures the ratio of NDP frames and uses a moving average filter to decide whether to enable beamforming channel smoothing. Tone index selection is dynamically adjusted based on bandwidth and link mode (HE/EHT vs VHT). The feature is only enabled for RTL8922D_CID7090 variant. Signed-off-by: Eric Huang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260707091056.42771-3-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.c | 8 +++ drivers/net/wireless/realtek/rtw89/core.h | 21 ++++++ drivers/net/wireless/realtek/rtw89/phy.c | 5 ++ drivers/net/wireless/realtek/rtw89/reg.h | 24 +++++++ drivers/net/wireless/realtek/rtw89/rtw8851b.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8852a.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8852b.c | 1 + .../net/wireless/realtek/rtw89/rtw8852bt.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8852c.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8922a.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8922d.c | 68 +++++++++++++++++++ 11 files changed, 132 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index b3376fadf593..5d35f13a1ea6 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -5806,6 +5806,7 @@ int rtw89_core_sta_link_assoc(struct rtw89_dev *rtwdev, rtwsta_link); const struct rtw89_chan *chan = rtw89_chan_get(rtwdev, rtwvif_link->chanctx_idx); + struct rtw89_bb_ctx *bb = rtw89_get_bb_ctx(rtwdev, rtwvif_link->phy_idx); struct ieee80211_link_sta *link_sta; int ret; @@ -5858,6 +5859,7 @@ int rtw89_core_sta_link_assoc(struct rtw89_dev *rtwdev, if (vif->type == NL80211_IFTYPE_STATION && !sta->tdls) { struct ieee80211_bss_conf *bss_conf; + u8 link_mode = 0; rcu_read_lock(); @@ -5865,6 +5867,12 @@ int rtw89_core_sta_link_assoc(struct rtw89_dev *rtwdev, link_sta = rtw89_sta_rcu_dereference_link(rtwsta_link, true); rtwsta_link->er_cap = rtw89_sta_link_can_er(rtwdev, bss_conf, link_sta); + if (link_sta->he_cap.has_he || link_sta->eht_cap.has_eht) + link_mode = 2; + else if (link_sta->vht_cap.vht_supported) + link_mode = 1; + bb->path_diff.link_mode = link_mode; + rcu_read_unlock(); rtw89_btc_ntfy_role_info(rtwdev, rtwvif_link, rtwsta_link, diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 524b4974040c..72aaef321d61 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -14,6 +14,7 @@ #include struct rtw89_dev; +struct rtw89_bb_ctx; struct rtw89_pci_info; struct rtw89_usb_info; struct rtw89_mac_gen_def; @@ -4127,6 +4128,8 @@ struct rtw89_chip_ops { enum rtw89_rf_path path, enum rtw89_phy_idx phy_idx, struct rtw89_phy_calc_efuse_gain *calc); + void (*path_diff_update)(struct rtw89_dev *rtwdev, + struct rtw89_bb_ctx *bb); int (*pwr_on_func)(struct rtw89_dev *rtwdev); int (*pwr_off_func)(struct rtw89_dev *rtwdev); void (*query_rxdesc)(struct rtw89_dev *rtwdev, @@ -5683,6 +5686,14 @@ struct rtw89_beacon_stat { }; DECLARE_EWMA(thermal, 4, 4); +DECLARE_EWMA(path_diff, 4, 2); + +struct rtw89_phy_path_diff { + struct ewma_path_diff avg; + u8 raw; + bool bf_smo_en; + u8 link_mode; +}; #define RTW89_TX_RATE_NR 40 struct rtw89_phy_stat { @@ -6741,6 +6752,7 @@ struct rtw89_dev { struct rtw89_pmac_stat_info pmac_stat; struct rtw89_tx_stat_info tx_stat; struct rtw89_diag_bb diag; + struct rtw89_phy_path_diff path_diff; } bbs[RTW89_PHY_NUM]; struct wiphy_delayed_work track_work; @@ -7852,6 +7864,15 @@ static inline void rtw89_chip_power_trim(struct rtw89_dev *rtwdev) chip->ops->power_trim(rtwdev); } +static inline void rtw89_chip_path_diff_update(struct rtw89_dev *rtwdev, + struct rtw89_bb_ctx *bb) +{ + const struct rtw89_chip_info *chip = rtwdev->chip; + + if (chip->ops->path_diff_update) + chip->ops->path_diff_update(rtwdev, bb); +} + static inline void __rtw89_chip_init_txpwr_unit(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx) { diff --git a/drivers/net/wireless/realtek/rtw89/phy.c b/drivers/net/wireless/realtek/rtw89/phy.c index 759be4dab42b..981c7f02271a 100644 --- a/drivers/net/wireless/realtek/rtw89/phy.c +++ b/drivers/net/wireless/realtek/rtw89/phy.c @@ -5842,6 +5842,9 @@ static void rtw89_phy_stat_init(struct rtw89_dev *rtwdev) memset(&bb->last_pkt_stat, 0, sizeof(bb->last_pkt_stat)); ewma_rssi_init(&bb->bcn_rssi); + bb->path_diff.raw = 0; + ewma_path_diff_init(&bb->path_diff.avg); + bb->path_diff.bf_smo_en = false; } rtwdev->hal.thermal_prot_lv = 0; @@ -6190,6 +6193,8 @@ void rtw89_phy_stat_track(struct rtw89_dev *rtwdev) rtw89_for_each_active_bb(rtwdev, bb) { bb->last_pkt_stat = bb->cur_pkt_stat; memset(&bb->cur_pkt_stat, 0, sizeof(bb->cur_pkt_stat)); + + rtw89_chip_path_diff_update(rtwdev, bb); } } diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h index bf1c6cb0ae9c..1ff788c24eec 100644 --- a/drivers/net/wireless/realtek/rtw89/reg.h +++ b/drivers/net/wireless/realtek/rtw89/reg.h @@ -10822,6 +10822,13 @@ #define B_TXINFO_PATH_MB_BE4 BIT(19) #define R_SHAPER_COEFF_BE4 0x20CBC #define B_SHAPER_COEFF_BE4 BIT(19) + +#define R_CL_MODE_CNT_BE4 0x20DE0 +#define B_CL_MODE_NDP_CNT_PHY0_BE4 GENMASK(31, 24) +#define B_CL_MODE_NDP_CNT_PHY1_BE4 GENMASK(15, 8) +#define B_CL_MODE_CL_CNT_PHY0_BE4 GENMASK(23, 16) +#define B_CL_MODE_CL_CNT_PHY1_BE4 GENMASK(7, 0) + #define R_IFS_T1_AVG_BE4 0x20EDC #define B_IFS_T1_AVG_BE4 GENMASK(15, 0) #define B_IFS_T2_AVG_BE4 GENMASK(31, 16) @@ -10940,6 +10947,14 @@ #define R_GAIN_BIAS_BE4 0x260A0 #define B_GAIN_BIAS_BW20_BE4 GENMASK(11, 6) #define B_GAIN_BIAS_BW40_BE4 GENMASK(17, 12) + +#define R_BF_SMO_PDP_LMT_EHT_BE4 0x26568 +#define B_BF_SMO_PDP_LMT_EHT_BE4 BIT(30) +#define R_BF_SMO_PDP_LMT_HE_BE4 0x26568 +#define B_BF_SMO_PDP_LMT_HE_BE4 BIT(31) +#define R_BF_SMO_PDP_LMT_VHT_BE4 0x2656C +#define B_BF_SMO_PDP_LMT_VHT_BE4 BIT(31) + #define R_AWGN_DET_BE4 0x2668C #define B_AWGN_DET_BE4 GENMASK(17, 9) #define R_CSI_WGT_BE4 0x26770 @@ -10981,6 +10996,15 @@ #define R_BSS_CLR_VLD_BE4 0x26920 #define B_BSS_CLR_VLD_BE4 BIT(2) +#define R_CL_MODE_TRIG_BE4 0x26F44 +#define B_CL_MODE_TRIG_BE4 BIT(31) +#define R_SELECTED_TONE_IDX_BE4 0x26F4C +#define B_SELECTED_TONE_IDX_BE4 GENMASK(11, 0) +#define R_OS_TRIG_BY_SW_BE4 0x26F50 +#define B_OS_TRIG_BY_SW_BE4 BIT(30) +#define R_OS_TRIG_SOURCE_BE4 0x26F6C +#define B_OS_TRIG_SOURCE_BE4 BIT(0) + #define R_SW_SI_DATA_BE4 0x2CF4C #define B_SW_SI_READ_DATA_BE4 GENMASK(19, 0) #define B_SW_SI_W_BUSY_BE4 BIT(24) diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b.c b/drivers/net/wireless/realtek/rtw89/rtw8851b.c index a1a63588cb90..95437ba9b675 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851b.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851b.c @@ -2578,6 +2578,7 @@ static const struct rtw89_chip_ops rtw8851b_chip_ops = { .set_txpwr_ul_tb_offset = rtw8851b_set_txpwr_ul_tb_offset, .digital_pwr_comp = NULL, .calc_rx_gain_normal = NULL, + .path_diff_update = NULL, .pwr_on_func = rtw8851b_pwr_on_func, .pwr_off_func = rtw8851b_pwr_off_func, .query_rxdesc = rtw89_core_query_rxdesc, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852a.c b/drivers/net/wireless/realtek/rtw89/rtw8852a.c index 055c67a07cea..6aa726efb7f6 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852a.c @@ -2325,6 +2325,7 @@ static const struct rtw89_chip_ops rtw8852a_chip_ops = { .set_txpwr_ul_tb_offset = rtw8852a_set_txpwr_ul_tb_offset, .digital_pwr_comp = NULL, .calc_rx_gain_normal = NULL, + .path_diff_update = NULL, .pwr_on_func = NULL, .pwr_off_func = NULL, .query_rxdesc = rtw89_core_query_rxdesc, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852b.c b/drivers/net/wireless/realtek/rtw89/rtw8852b.c index debcdb2eacd6..a8638024b54a 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852b.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852b.c @@ -907,6 +907,7 @@ static const struct rtw89_chip_ops rtw8852b_chip_ops = { .set_txpwr_ul_tb_offset = rtw8852bx_set_txpwr_ul_tb_offset, .digital_pwr_comp = NULL, .calc_rx_gain_normal = NULL, + .path_diff_update = NULL, .pwr_on_func = rtw8852b_pwr_on_func, .pwr_off_func = rtw8852b_pwr_off_func, .query_rxdesc = rtw89_core_query_rxdesc, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bt.c b/drivers/net/wireless/realtek/rtw89/rtw8852bt.c index fc8a17fb95f4..e68f73827fa5 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852bt.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852bt.c @@ -753,6 +753,7 @@ static const struct rtw89_chip_ops rtw8852bt_chip_ops = { .set_txpwr_ul_tb_offset = rtw8852bx_set_txpwr_ul_tb_offset, .digital_pwr_comp = NULL, .calc_rx_gain_normal = NULL, + .path_diff_update = NULL, .pwr_on_func = rtw8852bt_pwr_on_func, .pwr_off_func = rtw8852bt_pwr_off_func, .query_rxdesc = rtw89_core_query_rxdesc, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852c.c b/drivers/net/wireless/realtek/rtw89/rtw8852c.c index 9e630b897986..a82373198356 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852c.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852c.c @@ -3124,6 +3124,7 @@ static const struct rtw89_chip_ops rtw8852c_chip_ops = { .set_txpwr_ul_tb_offset = rtw8852c_set_txpwr_ul_tb_offset, .digital_pwr_comp = NULL, .calc_rx_gain_normal = NULL, + .path_diff_update = NULL, .pwr_on_func = rtw8852c_pwr_on_func, .pwr_off_func = rtw8852c_pwr_off_func, .query_rxdesc = rtw89_core_query_rxdesc, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922a.c b/drivers/net/wireless/realtek/rtw89/rtw8922a.c index 382034eb27d0..fe87b4929ddc 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922a.c @@ -3109,6 +3109,7 @@ static const struct rtw89_chip_ops rtw8922a_chip_ops = { .set_txpwr_ul_tb_offset = NULL, .digital_pwr_comp = rtw8922a_digital_pwr_comp, .calc_rx_gain_normal = NULL, + .path_diff_update = NULL, .pwr_on_func = rtw8922a_pwr_on_func, .pwr_off_func = rtw8922a_pwr_off_func, .query_rxdesc = rtw89_core_query_rxdesc_v2, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c index 625f8d675f08..805dd96e61e6 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c @@ -1716,6 +1716,73 @@ static void rtw8922d_calc_rx_gain_normal(struct rtw89_dev *rtwdev, rtw8922d_calc_rx_gain_normal_cck(rtwdev, chan, path, phy_idx, calc); } +static void rtw8922d_path_diff_update(struct rtw89_dev *rtwdev, + struct rtw89_bb_ctx *bb) +{ +#define BF_SMOOTH_TH 80 + static const u32 path_diff_cnt_mask[] = {0xff0000, 0xff}; + static const u32 ndp_cnt_mask[] = {0xff000000, 0xff00}; + static const u16 he_eht_sel_tone[4] = {27, 54, 56, 56}; + static const u16 vht_sel_tone[4] = {11, 25, 25, 25}; + struct rtw89_hal *hal = &rtwdev->hal; + struct rtw89_entity_conf conf; + const struct rtw89_chan *chan; + u8 phy_idx = bb->phy_idx; + u32 path_diff_cnt; + u16 sel_tone = 0; + bool bf_smo_lmt; + u32 ndp_cnt; + + if (hal->cid != RTL8922D_CID7090) + return; + + ndp_cnt = rtw89_phy_read32_mask(rtwdev, R_CL_MODE_CNT_BE4, ndp_cnt_mask[phy_idx]); + path_diff_cnt = rtw89_phy_read32_mask(rtwdev, R_CL_MODE_CNT_BE4, + path_diff_cnt_mask[phy_idx]); + + bb->path_diff.raw = clamp(phy_div(path_diff_cnt * 100, ndp_cnt), 0, 255); + + if (ndp_cnt == 0) + ewma_path_diff_init(&bb->path_diff.avg); + else + ewma_path_diff_add(&bb->path_diff.avg, bb->path_diff.raw); + + rtw89_phy_write32_mask(rtwdev, R_CL_MODE_TRIG_BE4, B_CL_MODE_TRIG_BE4, 1); + rtw89_phy_write32_mask(rtwdev, R_CL_MODE_TRIG_BE4, B_CL_MODE_TRIG_BE4, 0); + + bf_smo_lmt = ewma_path_diff_read(&bb->path_diff.avg) < BF_SMOOTH_TH; + bb->path_diff.bf_smo_en = !bf_smo_lmt; + + rtw89_phy_write32_mask(rtwdev, R_BF_SMO_PDP_LMT_EHT_BE4, + B_BF_SMO_PDP_LMT_EHT_BE4, bf_smo_lmt); + rtw89_phy_write32_mask(rtwdev, R_BF_SMO_PDP_LMT_HE_BE4, + B_BF_SMO_PDP_LMT_HE_BE4, bf_smo_lmt); + rtw89_phy_write32_mask(rtwdev, R_BF_SMO_PDP_LMT_VHT_BE4, + B_BF_SMO_PDP_LMT_VHT_BE4, bf_smo_lmt); + + rtw89_phy_write32_mask(rtwdev, R_OS_TRIG_BY_SW_BE4, + B_OS_TRIG_BY_SW_BE4, !bf_smo_lmt); + rtw89_phy_write32_mask(rtwdev, R_OS_TRIG_SOURCE_BE4, + B_OS_TRIG_SOURCE_BE4, !bf_smo_lmt); + + rtw89_entity_get_conf(rtwdev, &conf); + chan = conf.chans[phy_idx]; + if (chan->band_width <= RTW89_CHANNEL_WIDTH_160) { + if (bb->path_diff.link_mode >= 2) + sel_tone = he_eht_sel_tone[chan->band_width]; + else if (bb->path_diff.link_mode == 1) + sel_tone = vht_sel_tone[chan->band_width]; + rtw89_phy_write32_mask(rtwdev, R_SELECTED_TONE_IDX_BE4, + B_SELECTED_TONE_IDX_BE4, sel_tone); + } + + rtw89_debug(rtwdev, RTW89_DBG_PHY_TRACK, + "[PATH_DIFF] raw=%d%%, ma=%ld%%, bf_smo_en=%d, tone=%d\n", + bb->path_diff.raw, + ewma_path_diff_read(&bb->path_diff.avg), + bb->path_diff.bf_smo_en, sel_tone); +} + static void rtw8922d_set_cck_parameters(struct rtw89_dev *rtwdev, const struct rtw89_chan *chan, enum rtw89_phy_idx phy_idx) @@ -3288,6 +3355,7 @@ static const struct rtw89_chip_ops rtw8922d_chip_ops = { .set_txpwr_ul_tb_offset = NULL, .digital_pwr_comp = rtw8922d_digital_pwr_comp, .calc_rx_gain_normal = rtw8922d_calc_rx_gain_normal, + .path_diff_update = rtw8922d_path_diff_update, .pwr_on_func = rtw8922d_pwr_on_func, .pwr_off_func = rtw8922d_pwr_off_func, .query_rxdesc = rtw89_core_query_rxdesc_v3, From 8da4883c81ae69e3abc86328b256c828748d035f Mon Sep 17 00:00:00 2001 From: Eric Huang Date: Tue, 7 Jul 2026 17:10:44 +0800 Subject: [PATCH 0280/1433] wifi: rtw89: 8922d: fix EMLSR BB switch sequence for MLO mode transition Assert BB reset in the intermediate "switch to 1+1" step of the EMLSR switch sequence for all three MLO mode transitions by updating the B_EMLSR_SWITCH_BE4 intermediate value from 0xAFFF to 0x3BAB. Without the BB reset in this step, the baseband can be left in an inconsistent state before settling into the final MLO configuration. Signed-off-by: Eric Huang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260707091056.42771-4-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/reg.h | 1 + drivers/net/wireless/realtek/rtw89/rtw8922d.c | 45 ++++++++----------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h index 1ff788c24eec..3908f9729736 100644 --- a/drivers/net/wireless/realtek/rtw89/reg.h +++ b/drivers/net/wireless/realtek/rtw89/reg.h @@ -10782,6 +10782,7 @@ #define B_SYS_DBCC_24G_BAND_SEL_BE4 BIT(1) #define R_EMLSR_SWITCH_BE4 0x20044 #define B_EMLSR_SWITCH_BE4 GENMASK(27, 12) +#define B_EMLSR_CLR_FORCE_BE4 GENMASK(20, 19) #define B_EMLSR_BB_CLK_BE4 GENMASK(31, 30) #define R_CHINFO_SEG_BE4 0x200B4 #define B_CHINFO_SEG_LEN_BE4 GENMASK(12, 10) diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c index 805dd96e61e6..212917db7154 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c @@ -2309,17 +2309,16 @@ static void rtw8922d_digital_pwr_comp(struct rtw89_dev *rtwdev, } } -static int rtw8922d_ctrl_mlo(struct rtw89_dev *rtwdev, enum rtw89_mlo_dbcc_mode mode, - bool pwr_comp) +static void rtw8922d_ctrl_mlo_mode_core(struct rtw89_dev *rtwdev, + enum rtw89_mlo_dbcc_mode mode) { - u32 reg0, reg1; - u8 cck_phy_idx; + rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_CLR_FORCE_BE4, 0x3); if (mode == MLO_2_PLUS_0_1RF) { rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xBBBB); udelay(1); rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_BB_CLK_BE4, 0x3); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xAFFF); + rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0x3BAB); rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xEBAD); udelay(1); @@ -2329,7 +2328,7 @@ static int rtw8922d_ctrl_mlo(struct rtw89_dev *rtwdev, enum rtw89_mlo_dbcc_mode rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xBBBB); udelay(1); rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_BB_CLK_BE4, 0x3); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xAFFF); + rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0x3BAB); udelay(1); rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xEFFF); @@ -2339,7 +2338,7 @@ static int rtw8922d_ctrl_mlo(struct rtw89_dev *rtwdev, enum rtw89_mlo_dbcc_mode rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xBBBB); udelay(1); rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_BB_CLK_BE4, 0x3); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xAFFF); + rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0x3BAB); udelay(1); rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_BB_CLK_BE4, 0x0); rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0x3AAB); @@ -2351,6 +2350,15 @@ static int rtw8922d_ctrl_mlo(struct rtw89_dev *rtwdev, enum rtw89_mlo_dbcc_mode rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_BB_CLK_BE4, 0x0); rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0x0); } +} + +static int rtw8922d_ctrl_mlo(struct rtw89_dev *rtwdev, enum rtw89_mlo_dbcc_mode mode, + bool pwr_comp) +{ + u32 reg0, reg1; + u8 cck_phy_idx; + + rtw8922d_ctrl_mlo_mode_core(rtwdev, mode); if (pwr_comp) rtw8922d_digital_pwr_comp(rtwdev, RTW89_PHY_0); @@ -2474,25 +2482,10 @@ static void rtw8922d_pre_set_channel_bb(struct rtw89_dev *rtwdev, rtw89_phy_write32_mask(rtwdev, R_SYS_DBCC_BE4, B_SYS_DBCC_BE4, 0x0); - if (phy_idx == RTW89_PHY_0) { - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xBBBB); - fsleep(1); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_BB_CLK_BE4, 0x3); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xAFFF); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xEBAD); - fsleep(1); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_BB_CLK_BE4, 0x0); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xEAAD); - } else { - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xBBBB); - fsleep(1); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_BB_CLK_BE4, 0x3); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xAFFF); - fsleep(1); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xEFFF); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_BB_CLK_BE4, 0x0); - rtw89_phy_write32_mask(rtwdev, R_EMLSR_SWITCH_BE4, B_EMLSR_SWITCH_BE4, 0xEEFF); - } + if (phy_idx == RTW89_PHY_0) + rtw8922d_ctrl_mlo_mode_core(rtwdev, MLO_2_PLUS_0_1RF); + else + rtw8922d_ctrl_mlo_mode_core(rtwdev, MLO_0_PLUS_2_1RF); fsleep(1); } From 852927114f6ef37530952b3dba063d24e60b38d9 Mon Sep 17 00:00:00 2001 From: Eric Huang Date: Tue, 7 Jul 2026 17:10:45 +0800 Subject: [PATCH 0281/1433] wifi: rtw89: phy: fix bandedge primary channel for 2.4GHz 40MHz and 6GHz Correct 2.4GHz bandwidth 40MHz bandedge check: pri_ch 5/9 instead of 3/11. Remove stale 6GHz bandedge case; no restricted band borders 6GHz range. Signed-off-by: Eric Huang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260707091056.42771-5-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/phy_be.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/phy_be.c b/drivers/net/wireless/realtek/rtw89/phy_be.c index 99263355e2f1..ac4ce30445b3 100644 --- a/drivers/net/wireless/realtek/rtw89/phy_be.c +++ b/drivers/net/wireless/realtek/rtw89/phy_be.c @@ -623,7 +623,7 @@ static u32 rtw89_phy_bb_wrap_be_bandedge_decision(struct rtw89_dev *rtwdev, case RTW89_BAND_2G: if (pri_ch == 1 || pri_ch == 13) val = BIT(1) | BIT(0); - else if (pri_ch == 3 || pri_ch == 11) + else if (pri_ch == 5 || pri_ch == 9) val = BIT(1); break; case RTW89_BAND_5G: @@ -637,8 +637,6 @@ static u32 rtw89_phy_bb_wrap_be_bandedge_decision(struct rtw89_dev *rtwdev, val = BIT(3); break; case RTW89_BAND_6G: - if (pri_ch == 233) - val = BIT(0); break; } From 390f58e29de3a3ddb05dd739a5fb12ed973d9088 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 7 Jul 2026 17:10:46 +0800 Subject: [PATCH 0282/1433] wifi: rtw89: 8922d: set TX compensation by format v2 The total number of TX compensation registers is 22, including 7 base and 15 ones according to operating channel. The v0 is 22 ones per channel. To reduce array size, v1 treats 7 base as common part across all conditions. However, we can fine tune the base part to yield better performance, so divide base into two sets according to NSS. Summarize dimensions as follows base (7) vals(15) v0 nss * band * path nss * band * path v1 1 nss * band * path v2 nss nss * band * path Currently, v1 and v2 can present in a file, but only one fw element with a specific format will be used for specific RFE type. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260707091056.42771-6-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/rtw8922d.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c index 212917db7154..e8267173bd4c 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c @@ -2249,6 +2249,12 @@ static void rtw8922d_set_digital_pwr_comp(struct rtw89_dev *rtwdev, struct { __le32 vals[DIGITAL_PWR_COMP_VALS_NUM]; } sets[2][RTW89_TX_COMP_BAND_NR][BB_PATH_NUM_8922D]; + } *pwr_comp_v1; + const struct { + __le32 base[2][DIGITAL_PWR_COMP_BASE_NUM]; + struct { + __le32 vals[DIGITAL_PWR_COMP_VALS_NUM]; + } sets[2][RTW89_TX_COMP_BAND_NR][BB_PATH_NUM_8922D]; } *pwr_comp; struct rtw89_fw_elm_info *elm_info = &rtwdev->fw.elm_info; const struct rtw89_fw_element_hdr *txcomp_elm = elm_info->tx_comp; @@ -2259,8 +2265,12 @@ static void rtw8922d_set_digital_pwr_comp(struct rtw89_dev *rtwdev, if (sizeof(*pwr_comp) == le32_to_cpu(txcomp_elm->size)) { pwr_comp = (const void *)txcomp_elm->u.common.contents; - comp_base = &pwr_comp->base; + comp_base = &pwr_comp->base[nss]; comp_vals = &pwr_comp->sets[nss][chan->tx_comp_band][path].vals; + } else if (sizeof(*pwr_comp_v1) == le32_to_cpu(txcomp_elm->size)) { + pwr_comp_v1 = (const void *)txcomp_elm->u.common.contents; + comp_base = &pwr_comp_v1->base; + comp_vals = &pwr_comp_v1->sets[nss][chan->tx_comp_band][path].vals; } else if (sizeof(*pwr_comp_v0) == le32_to_cpu(txcomp_elm->size)) { pwr_comp_v0 = (const void *)txcomp_elm->u.common.contents; comp_base = &pwr_comp_v0->sets[nss][chan->tx_comp_band][path].base; From d193bdc73396110a1d803c51d6e99ee2b3e42c15 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 7 Jul 2026 17:10:47 +0800 Subject: [PATCH 0283/1433] wifi: rtw89: efuse: no need to export rtw89_efuse_read_ecv_be() The consumer of rtw89_efuse_read_ecv_be() is in the same ko. No need to export it. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260707091056.42771-7-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/efuse_be.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/efuse_be.c b/drivers/net/wireless/realtek/rtw89/efuse_be.c index 70c1b8be662e..a716ad54fce5 100644 --- a/drivers/net/wireless/realtek/rtw89/efuse_be.c +++ b/drivers/net/wireless/realtek/rtw89/efuse_be.c @@ -537,4 +537,3 @@ int rtw89_efuse_read_ecv_be(struct rtw89_dev *rtwdev) return 0; } -EXPORT_SYMBOL(rtw89_efuse_read_ecv_be); From 4260edb3b1a6da71b8cd362f57a9ac4329eb538c Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 7 Jul 2026 17:10:48 +0800 Subject: [PATCH 0284/1433] wifi: rtw89: efuse: read thermal calibration value for RTL8922D The thermal calibration value programmed in efuse is the offset to adjust thermal value read from hardware, so that output will be accurate. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260707091056.42771-8-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.h | 2 ++ drivers/net/wireless/realtek/rtw89/efuse.h | 6 ++++ drivers/net/wireless/realtek/rtw89/efuse_be.c | 29 +++++++++++++++++++ drivers/net/wireless/realtek/rtw89/mac.c | 2 ++ drivers/net/wireless/realtek/rtw89/mac.h | 11 +++++++ drivers/net/wireless/realtek/rtw89/mac_be.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8922d.c | 2 ++ 7 files changed, 53 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 72aaef321d61..f467299b6363 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -6063,6 +6063,8 @@ struct rtw89_power_trim_info { u8 pa_bias_trim[RF_PATH_MAX]; u8 pad_bias_trim[RF_PATH_MAX]; u8 vco_trim[RF_PATH_MAX]; + + s16 thermal_k; }; enum rtw89_regd_func { diff --git a/drivers/net/wireless/realtek/rtw89/efuse.h b/drivers/net/wireless/realtek/rtw89/efuse.h index a14a9dfed8e8..c6415da749a3 100644 --- a/drivers/net/wireless/realtek/rtw89/efuse.h +++ b/drivers/net/wireless/realtek/rtw89/efuse.h @@ -16,6 +16,11 @@ #define EF_CV_MASK GENMASK(7, 4) #define EF_CV_INV 15 +#define EFUSE_THERMAL_K_OFFSET_BE 0x17CC +#define EFUSE_THERMAL_K_VALID_BE BIT(9) +#define EFUSE_THERMAL_K_SIGN_BE BIT(8) +#define EFUSE_THERMAL_K_VAL_BE GENMASK(7, 0) + struct rtw89_efuse_block_cfg { u32 offset; u32 size; @@ -32,5 +37,6 @@ int rtw89_efuse_recognize_mss_info_v1(struct rtw89_dev *rtwdev, u8 b1, u8 b2); int rtw89_efuse_read_fw_secure_ax(struct rtw89_dev *rtwdev); int rtw89_efuse_read_fw_secure_be(struct rtw89_dev *rtwdev); int rtw89_efuse_read_ecv_be(struct rtw89_dev *rtwdev); +int rtw89_efuse_read_thermal_k_be(struct rtw89_dev *rtwdev); #endif diff --git a/drivers/net/wireless/realtek/rtw89/efuse_be.c b/drivers/net/wireless/realtek/rtw89/efuse_be.c index a716ad54fce5..67036c89883f 100644 --- a/drivers/net/wireless/realtek/rtw89/efuse_be.c +++ b/drivers/net/wireless/realtek/rtw89/efuse_be.c @@ -537,3 +537,32 @@ int rtw89_efuse_read_ecv_be(struct rtw89_dev *rtwdev) return 0; } + +int rtw89_efuse_read_thermal_k_be(struct rtw89_dev *rtwdev) +{ + struct rtw89_power_trim_info *info = &rtwdev->pwr_trim; + u32 dump_addr = EFUSE_THERMAL_K_OFFSET_BE; + u8 buff[4]; /* efuse access must be multiple of 4 bytes in size */ + bool no_k; + u16 val16; + int ret; + + ret = rtw89_dump_physical_efuse_map_be(rtwdev, buff, dump_addr, 4, false); + if (ret) + return ret; + + val16 = buff[0] | buff[1] << 8; + + no_k = !!u16_get_bits(val16, EFUSE_THERMAL_K_VALID_BE); + if (no_k) { + info->thermal_k = 0; + return -ENOENT; + } + + info->thermal_k = u16_get_bits(val16, EFUSE_THERMAL_K_VAL_BE); + + if (u16_get_bits(val16, EFUSE_THERMAL_K_SIGN_BE)) + info->thermal_k *= -1; + + return 0; +} diff --git a/drivers/net/wireless/realtek/rtw89/mac.c b/drivers/net/wireless/realtek/rtw89/mac.c index 99de1b202976..3d3f683046a7 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.c +++ b/drivers/net/wireless/realtek/rtw89/mac.c @@ -1558,6 +1558,7 @@ static int rtw89_mac_power_switch(struct rtw89_dev *rtwdev, bool on) if (!test_bit(RTW89_FLAG_PROBE_DONE, rtwdev->flags)) { rtw89_mac_efuse_read_ecv(rtwdev); mac->efuse_read_fw_secure(rtwdev); + rtw89_mac_efuse_read_thermal_k(rtwdev); } set_bit(RTW89_FLAG_POWERON, rtwdev->flags); @@ -7500,6 +7501,7 @@ const struct rtw89_mac_gen_def rtw89_mac_gen_ax = { .cnv_efuse_state = rtw89_cnv_efuse_state_ax, .efuse_read_fw_secure = rtw89_efuse_read_fw_secure_ax, .efuse_read_ecv = NULL, + .efuse_read_thermal_k = NULL, .cfg_plt = rtw89_mac_cfg_plt_ax, .get_plt_cnt = rtw89_mac_get_plt_cnt_ax, diff --git a/drivers/net/wireless/realtek/rtw89/mac.h b/drivers/net/wireless/realtek/rtw89/mac.h index 539061fc15e8..7256d64ea07f 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.h +++ b/drivers/net/wireless/realtek/rtw89/mac.h @@ -1132,6 +1132,7 @@ struct rtw89_mac_gen_def { int (*cnv_efuse_state)(struct rtw89_dev *rtwdev, bool idle); int (*efuse_read_fw_secure)(struct rtw89_dev *rtwdev); int (*efuse_read_ecv)(struct rtw89_dev *rtwdev); + int (*efuse_read_thermal_k)(struct rtw89_dev *rtwdev); int (*cfg_plt)(struct rtw89_dev *rtwdev, struct rtw89_mac_ax_plt *plt); u16 (*get_plt_cnt)(struct rtw89_dev *rtwdev, u8 band); @@ -1735,6 +1736,16 @@ static inline int rtw89_mac_efuse_read_ecv(struct rtw89_dev *rtwdev) return mac->efuse_read_ecv(rtwdev); } +static inline int rtw89_mac_efuse_read_thermal_k(struct rtw89_dev *rtwdev) +{ + const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; + + if (!mac->efuse_read_thermal_k) + return -ENOENT; + + return mac->efuse_read_thermal_k(rtwdev); +} + static inline void rtw89_mac_fwdl_preconfig(struct rtw89_dev *rtwdev) { diff --git a/drivers/net/wireless/realtek/rtw89/mac_be.c b/drivers/net/wireless/realtek/rtw89/mac_be.c index 14f1e30066e9..077ddf4f77c5 100644 --- a/drivers/net/wireless/realtek/rtw89/mac_be.c +++ b/drivers/net/wireless/realtek/rtw89/mac_be.c @@ -3312,6 +3312,7 @@ const struct rtw89_mac_gen_def rtw89_mac_gen_be = { .cnv_efuse_state = rtw89_cnv_efuse_state_be, .efuse_read_fw_secure = rtw89_efuse_read_fw_secure_be, .efuse_read_ecv = rtw89_efuse_read_ecv_be, + .efuse_read_thermal_k = rtw89_efuse_read_thermal_k_be, .cfg_plt = rtw89_mac_cfg_plt_be, .get_plt_cnt = rtw89_mac_get_plt_cnt_be, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c index e8267173bd4c..8b91c552a309 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c @@ -3006,6 +3006,7 @@ static void rtw8922d_bb_cfg_txrx_path(struct rtw89_dev *rtwdev) static u8 rtw8922d_get_thermal(struct rtw89_dev *rtwdev, enum rtw89_rf_path rf_path) { + struct rtw89_power_trim_info *info = &rtwdev->pwr_trim; u8 val; rtw89_phy_write32_mask(rtwdev, R_TC_EN_BE4, B_TC_EN_BE4, 0x1); @@ -3015,6 +3016,7 @@ static u8 rtw8922d_get_thermal(struct rtw89_dev *rtwdev, enum rtw89_rf_path rf_p fsleep(100); val = rtw89_phy_read32_mask(rtwdev, R_TC_VAL_BE4, B_TC_VAL_BE4); + val = clamp_t(int, val + info->thermal_k, 0, 255); return val; } From 4e199cd6661457e426e0f10a63c553c6c50b5262 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 7 Jul 2026 17:10:49 +0800 Subject: [PATCH 0285/1433] wifi: rtw89: 8922d: read default digital voltage calibration values In order to lower temperature when WiFi is running, reduce the digital voltage for the purpose. The calibration values of digital voltage are programmed in efuse. One is for normal use, which driver stores it into hal->thermal_prot_vmax. The other is the minimum voltage, which is stored into hal->thermal_prot_vmin. These two values define a range of supported voltage, which latter patch uses them to adjust voltage depends on thermal value (temperature). Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260707091056.42771-9-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.h | 7 ++ drivers/net/wireless/realtek/rtw89/efuse.h | 1 + drivers/net/wireless/realtek/rtw89/efuse_be.c | 87 +++++++++++++++++++ drivers/net/wireless/realtek/rtw89/mac.c | 2 + drivers/net/wireless/realtek/rtw89/mac.h | 11 +++ drivers/net/wireless/realtek/rtw89/mac_be.c | 1 + drivers/net/wireless/realtek/rtw89/reg.h | 17 ++++ drivers/net/wireless/realtek/rtw89/rtw8922d.c | 24 +++++ 8 files changed, 150 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index f467299b6363..d457da2a21fe 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -3840,6 +3840,8 @@ struct rtw89_sta_link { struct rtw89_efuse { bool valid; bool power_k_valid; + bool vcore_valid; + bool dswr_valid; u8 xtal_cap; u8 addr[ETH_ALEN]; u8 rfe_type; @@ -3849,6 +3851,8 @@ struct rtw89_efuse { u8 bt_setting_3; u8 sn[RTW89_EFUSE_SN_LEN]; u8 uuid[RTW89_EFUSE_UUID_LEN]; + u8 vcore_vmax_reduce; + u8 dswr_vmin; }; struct rtw89_phy_rate_pattern { @@ -5561,6 +5565,9 @@ struct rtw89_hal { u8 thermal_prot_th; u8 thermal_prot_lv; /* 0 ~ RTW89_THERMAL_PROT_LV_MAX */ + u8 thermal_prot_vmax; + u8 thermal_prot_vmin; + u8 fixed_dig_pd_th; /* v = (X(dBm) + 102)/2 */ s8 fixed_dig_cck_pd_th; /* dBm */ }; diff --git a/drivers/net/wireless/realtek/rtw89/efuse.h b/drivers/net/wireless/realtek/rtw89/efuse.h index c6415da749a3..4eb629273ab9 100644 --- a/drivers/net/wireless/realtek/rtw89/efuse.h +++ b/drivers/net/wireless/realtek/rtw89/efuse.h @@ -38,5 +38,6 @@ int rtw89_efuse_read_fw_secure_ax(struct rtw89_dev *rtwdev); int rtw89_efuse_read_fw_secure_be(struct rtw89_dev *rtwdev); int rtw89_efuse_read_ecv_be(struct rtw89_dev *rtwdev); int rtw89_efuse_read_thermal_k_be(struct rtw89_dev *rtwdev); +int rtw89_efuse_read_pwr_data_be(struct rtw89_dev *rtwdev); #endif diff --git a/drivers/net/wireless/realtek/rtw89/efuse_be.c b/drivers/net/wireless/realtek/rtw89/efuse_be.c index 67036c89883f..825274bd7c41 100644 --- a/drivers/net/wireless/realtek/rtw89/efuse_be.c +++ b/drivers/net/wireless/realtek/rtw89/efuse_be.c @@ -16,6 +16,15 @@ #define EFUSE_SEC_BE_START 0x1580 #define EFUSE_SEC_BE_SIZE 4 +#define EFUSE_VCORE_PWR_BE 0x17D6 +#define EFUSE_VCORE_PWR_BE_VALID BIT(12) +#define EFUSE_VCORE_PWR_BE_VAL GENMASK(11, 0) +#define SWR_DIG_MIN 860 +#define EFUSE_DIG_K_STEP 0xC +#define EFUSE_DSWR_V0_86_BE 0x17DA +#define EFUSE_DSWR_V0_86_BE_VALID BIT(5) +#define EFUSE_DSWR_V0_86_BE_VAL GENMASK(4, 0) + static const u32 sb_sel_mgn[SB_SEL_MGN_MAX_SIZE] = { 0x8000100, 0xC000180 }; @@ -392,6 +401,17 @@ int rtw89_parse_efuse_map_be(struct rtw89_dev *rtwdev) goto out_free; } + if (rtwdev->chip->chip_id != RTL8922D) + goto out_free; + + ret = rtw89_parse_logical_efuse_block_be(rtwdev, phy_map, phy_size, + RTW89_EFUSE_BLOCK_SYS); + if (ret) { + rtw89_warn(rtwdev, "failed to parse efuse logic block %d\n", + RTW89_EFUSE_BLOCK_SYS); + goto out_free; + } + out_free: kfree(dav_phy_map); kfree(phy_map); @@ -566,3 +586,70 @@ int rtw89_efuse_read_thermal_k_be(struct rtw89_dev *rtwdev) return 0; } + +static int rtw89_efuse_read_pwr_data_vcore_be(struct rtw89_dev *rtwdev) +{ + struct rtw89_efuse *efuse = &rtwdev->efuse; + u32 dump_addr; + u8 buff[4]; /* efuse access must 4 bytes align */ + u16 val16; + int ret; + + dump_addr = ALIGN_DOWN(EFUSE_VCORE_PWR_BE, 4); + + ret = rtw89_dump_physical_efuse_map_be(rtwdev, buff, dump_addr, 4, false); + if (ret) + return ret; + + val16 = buff[EFUSE_VCORE_PWR_BE - dump_addr] | + buff[EFUSE_VCORE_PWR_BE - dump_addr + 1] << 8; + + if (val16 & EFUSE_VCORE_PWR_BE_VALID) + return 0; + + efuse->vcore_valid = true; + efuse->vcore_vmax_reduce = + (u16_get_bits(val16, EFUSE_VCORE_PWR_BE_VAL) - SWR_DIG_MIN) / + EFUSE_DIG_K_STEP; + + return 0; +} + +static int rtw89_efuse_read_pwr_data_dswr_be(struct rtw89_dev *rtwdev) +{ + struct rtw89_efuse *efuse = &rtwdev->efuse; + u32 dump_addr; + u8 buff[4]; /* efuse access must 4 bytes align */ + u8 val8; + int ret; + + dump_addr = ALIGN_DOWN(EFUSE_DSWR_V0_86_BE, 4); + + ret = rtw89_dump_physical_efuse_map_be(rtwdev, buff, dump_addr, 4, false); + if (ret) + return ret; + + val8 = buff[EFUSE_DSWR_V0_86_BE - dump_addr]; + + if (val8 & EFUSE_DSWR_V0_86_BE_VALID) + return 0; + + efuse->dswr_valid = true; + efuse->dswr_vmin = u8_get_bits(val8, EFUSE_DSWR_V0_86_BE_VAL); + + return 0; +} + +int rtw89_efuse_read_pwr_data_be(struct rtw89_dev *rtwdev) +{ + int ret; + + if (rtwdev->chip->chip_id != RTL8922D) + return 0; + + ret = rtw89_efuse_read_pwr_data_vcore_be(rtwdev); + if (ret) + return ret; + + return rtw89_efuse_read_pwr_data_dswr_be(rtwdev); +} diff --git a/drivers/net/wireless/realtek/rtw89/mac.c b/drivers/net/wireless/realtek/rtw89/mac.c index 3d3f683046a7..f5e55e6be119 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.c +++ b/drivers/net/wireless/realtek/rtw89/mac.c @@ -1559,6 +1559,7 @@ static int rtw89_mac_power_switch(struct rtw89_dev *rtwdev, bool on) rtw89_mac_efuse_read_ecv(rtwdev); mac->efuse_read_fw_secure(rtwdev); rtw89_mac_efuse_read_thermal_k(rtwdev); + rtw89_mac_efuse_read_pwr_data(rtwdev); } set_bit(RTW89_FLAG_POWERON, rtwdev->flags); @@ -7502,6 +7503,7 @@ const struct rtw89_mac_gen_def rtw89_mac_gen_ax = { .efuse_read_fw_secure = rtw89_efuse_read_fw_secure_ax, .efuse_read_ecv = NULL, .efuse_read_thermal_k = NULL, + .efuse_read_pwr_data = NULL, .cfg_plt = rtw89_mac_cfg_plt_ax, .get_plt_cnt = rtw89_mac_get_plt_cnt_ax, diff --git a/drivers/net/wireless/realtek/rtw89/mac.h b/drivers/net/wireless/realtek/rtw89/mac.h index 7256d64ea07f..8bbe49492d4c 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.h +++ b/drivers/net/wireless/realtek/rtw89/mac.h @@ -1133,6 +1133,7 @@ struct rtw89_mac_gen_def { int (*efuse_read_fw_secure)(struct rtw89_dev *rtwdev); int (*efuse_read_ecv)(struct rtw89_dev *rtwdev); int (*efuse_read_thermal_k)(struct rtw89_dev *rtwdev); + int (*efuse_read_pwr_data)(struct rtw89_dev *rtwdev); int (*cfg_plt)(struct rtw89_dev *rtwdev, struct rtw89_mac_ax_plt *plt); u16 (*get_plt_cnt)(struct rtw89_dev *rtwdev, u8 band); @@ -1746,6 +1747,16 @@ static inline int rtw89_mac_efuse_read_thermal_k(struct rtw89_dev *rtwdev) return mac->efuse_read_thermal_k(rtwdev); } +static inline int rtw89_mac_efuse_read_pwr_data(struct rtw89_dev *rtwdev) +{ + const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; + + if (!mac->efuse_read_pwr_data) + return -ENOENT; + + return mac->efuse_read_pwr_data(rtwdev); +} + static inline void rtw89_mac_fwdl_preconfig(struct rtw89_dev *rtwdev) { diff --git a/drivers/net/wireless/realtek/rtw89/mac_be.c b/drivers/net/wireless/realtek/rtw89/mac_be.c index 077ddf4f77c5..0fe317929b1b 100644 --- a/drivers/net/wireless/realtek/rtw89/mac_be.c +++ b/drivers/net/wireless/realtek/rtw89/mac_be.c @@ -3313,6 +3313,7 @@ const struct rtw89_mac_gen_def rtw89_mac_gen_be = { .efuse_read_fw_secure = rtw89_efuse_read_fw_secure_be, .efuse_read_ecv = rtw89_efuse_read_ecv_be, .efuse_read_thermal_k = rtw89_efuse_read_thermal_k_be, + .efuse_read_pwr_data = rtw89_efuse_read_pwr_data_be, .cfg_plt = rtw89_mac_cfg_plt_be, .get_plt_cnt = rtw89_mac_get_plt_cnt_be, diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h index 3908f9729736..72f7b80fe1fb 100644 --- a/drivers/net/wireless/realtek/rtw89/reg.h +++ b/drivers/net/wireless/realtek/rtw89/reg.h @@ -4573,6 +4573,23 @@ #define R_BE_UDM2 0x01F8 #define B_BE_UDM2_EPC_RA_MASK GENMASK(31, 0) +#define R_BE_SPS_DIG_ON_CTRL0 0x0200 +#define B_BE_PFMCMP_IQ BIT(31) +#define B_BE_FREQ_MASK GENMASK(30, 27) +#define B_BE_OFF_END_SEL BIT(26) +#define B_BE_POW_MINOFF_L BIT(25) +#define B_BE_REG_BYPASS_L BIT(24) +#define B_BE_VREFPFM_L_MASK GENMASK(23, 19) +#define B_BE_REG_ZCDC_H_MASK GENMASK(18, 17) +#define B_BE_FORCE_ZCD_BIAS BIT(16) +#define B_BE_ZCD_SDZ_L_MASK GENMASK(15, 14) +#define B_BE_ISAW_MASK GENMASK(13, 10) +#define B_BE_SS_DIVSEL_MASK GENMASK(9, 8) +#define B_BE_REG_BG_H BIT(7) +#define B_BE_FPWM_L1 BIT(6) +#define B_BE_POW_ZCD_L BIT(5) +#define B_BE_PWMTUNE_MASK GENMASK(4, 0) + #define R_BE_SPS_DIG_ON_CTRL1 0x0204 #define B_BE_SN_N_L_MASK GENMASK(31, 28) #define B_BE_SP_N_L_MASK GENMASK(27, 24) diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c index 8b91c552a309..a6693c8413d5 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c @@ -881,6 +881,28 @@ static int rtw8922d_read_efuse_rf(struct rtw89_dev *rtwdev, u8 *log_map) return 0; } +static int rtw8922d_read_sys(struct rtw89_dev *rtwdev, u8 *log_map) +{ + struct rtw89_efuse *efuse = &rtwdev->efuse; + struct rtw89_hal *hal = &rtwdev->hal; + u16 digk; + u8 vmin; + + digk = log_map[0x200] | log_map[0x201] << 8; + hal->thermal_prot_vmax = u16_get_bits(digk, B_BE_PWMTUNE_MASK); + + if (efuse->dswr_valid) + vmin = efuse->dswr_vmin; + else if (efuse->vcore_valid) + vmin = hal->thermal_prot_vmax - efuse->vcore_vmax_reduce; + else + vmin = hal->thermal_prot_vmax - 3; + + hal->thermal_prot_vmin = vmin; + + return 0; +} + static int rtw8922d_read_efuse(struct rtw89_dev *rtwdev, u8 *log_map, enum rtw89_efuse_block block) { @@ -891,6 +913,8 @@ static int rtw8922d_read_efuse(struct rtw89_dev *rtwdev, u8 *log_map, return rtw8922d_read_efuse_usb(rtwdev, log_map); case RTW89_EFUSE_BLOCK_RF: return rtw8922d_read_efuse_rf(rtwdev, log_map); + case RTW89_EFUSE_BLOCK_SYS: + return rtw8922d_read_sys(rtwdev, log_map); default: return 0; } From 47ad03f1eca5ddbc44562b9a4add5003fdca34c7 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 7 Jul 2026 17:10:50 +0800 Subject: [PATCH 0286/1433] wifi: rtw89: add thermal protect by digital voltage reduction The temperature is rising when WiFi does high throughput, and reduce voltage is a way to ease temperature. The existing method to do thermal protect is to reduce TX duty, which can affect throughput obviously. Before doing reduce TX duty (with -20 thermal value offset), driver does voltage reduction first, which doesn't affect throughput but lower power consumption. The voltage gram of register is 0.01 voltage, and the suggested maximum reduction is 6 grams, which driver defines 6 as maximum voltage level. When thermal value is over threshold, driver will try to adjust level and corresponding register value. It should be adjust grams one by one with additional 50 ms delay to ensure hardware work properly. A note that the voltage must reset to normal value before entering power save mode, otherwise WiFi card might get lost. Since it takes 50 ms delay for each gram to adjust voltage, we stop to enter power save if protect level is not zero. By the way, adjust thermal threshold to 0xB4 as desired. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260707091056.42771-10-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.c | 9 +++++ drivers/net/wireless/realtek/rtw89/core.h | 4 ++ drivers/net/wireless/realtek/rtw89/debug.c | 1 + drivers/net/wireless/realtek/rtw89/mac.c | 3 ++ drivers/net/wireless/realtek/rtw89/mac.h | 24 ++++++++++++ drivers/net/wireless/realtek/rtw89/mac_be.c | 38 +++++++++++++++++++ drivers/net/wireless/realtek/rtw89/phy.c | 27 +++++++++++++ drivers/net/wireless/realtek/rtw89/rtw8922d.c | 2 +- 8 files changed, 107 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index 5d35f13a1ea6..0343cd1a0ee1 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -5236,9 +5236,18 @@ static bool rtw89_traffic_stats_track(struct rtw89_dev *rtwdev) static void rtw89_enter_lps_track(struct rtw89_dev *rtwdev, enum rtw89_tfc_interval interval) { + struct rtw89_hal *hal = &rtwdev->hal; struct ieee80211_vif *vif; struct rtw89_vif *rtwvif; + /* + * If vcore level is set, temperature is high and voltage is low. As + * entering power save must reset voltage to default, avoid power save + * until vcore decreases to zero resulting from temperature becomes low. + */ + if (hal->thermal_prot_vlv) + return; + rtw89_for_each_rtwvif(rtwdev, rtwvif) { if (rtwvif->tdls_peer) continue; diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index d457da2a21fe..174349c6bc58 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -5523,10 +5523,13 @@ enum rtw89_dm_type { RTW89_DM_HW_SCAN, RTW89_DM_INACTIVE_PS, RTW89_DM_DIG_PD, + RTW89_DM_VCORE, }; #define RTW89_THERMAL_PROT_LV_MAX 5 #define RTW89_THERMAL_PROT_STEP 5 /* -5% for each level */ +#define RTW89_THERMAL_PROT_VLV_MAX 6 +#define RTW89_THERMAL_PROT_VLV_TH_OFFSET 20 struct rtw89_hal { u32 rx_fltr; @@ -5567,6 +5570,7 @@ struct rtw89_hal { u8 thermal_prot_vmax; u8 thermal_prot_vmin; + u8 thermal_prot_vlv; /* 0 ~ RTW89_THERMAL_PROT_VLV_MAX (6) */ u8 fixed_dig_pd_th; /* v = (X(dBm) + 102)/2 */ s8 fixed_dig_cck_pd_th; /* dBm */ diff --git a/drivers/net/wireless/realtek/rtw89/debug.c b/drivers/net/wireless/realtek/rtw89/debug.c index 5786120602ab..e42dc5707576 100644 --- a/drivers/net/wireless/realtek/rtw89/debug.c +++ b/drivers/net/wireless/realtek/rtw89/debug.c @@ -4778,6 +4778,7 @@ static const struct rtw89_disabled_dm_info { DM_INFO(HW_SCAN), DM_INFO(INACTIVE_PS), DM_INFO(DIG_PD), + DM_INFO(VCORE), }; static ssize_t diff --git a/drivers/net/wireless/realtek/rtw89/mac.c b/drivers/net/wireless/realtek/rtw89/mac.c index f5e55e6be119..6e3da5e4a1b3 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.c +++ b/drivers/net/wireless/realtek/rtw89/mac.c @@ -1602,6 +1602,8 @@ int rtw89_mac_pwr_on(struct rtw89_dev *rtwdev) void rtw89_mac_pwr_off(struct rtw89_dev *rtwdev) { + rtw89_mac_set_vcore_reset(rtwdev); + rtw89_mac_power_switch(rtwdev, false); } @@ -7477,6 +7479,7 @@ const struct rtw89_mac_gen_def rtw89_mac_gen_ax = { .cfg_ppdu_status = rtw89_mac_cfg_ppdu_status_ax, .cfg_phy_rpt = NULL, .set_edcca_mode = NULL, + .set_vcore_cfg = NULL, .dle_mix_cfg = dle_mix_cfg_ax, .chk_dle_rdy = chk_dle_rdy_ax, diff --git a/drivers/net/wireless/realtek/rtw89/mac.h b/drivers/net/wireless/realtek/rtw89/mac.h index 8bbe49492d4c..f85c14ca0f35 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.h +++ b/drivers/net/wireless/realtek/rtw89/mac.h @@ -1100,6 +1100,7 @@ struct rtw89_mac_gen_def { int (*cfg_ppdu_status)(struct rtw89_dev *rtwdev, u8 mac_idx, bool enable); void (*cfg_phy_rpt)(struct rtw89_dev *rtwdev, u8 mac_idx, bool enable); void (*set_edcca_mode)(struct rtw89_dev *rtwdev, u8 mac_idx, bool normal); + void (*set_vcore_cfg)(struct rtw89_dev *rtwdev, u8 vlv); int (*dle_mix_cfg)(struct rtw89_dev *rtwdev, const struct rtw89_dle_mem *cfg); int (*chk_dle_rdy)(struct rtw89_dev *rtwdev, bool wde_or_ple); @@ -1459,6 +1460,29 @@ void rtw89_mac_set_edcca_mode(struct rtw89_dev *rtwdev, u8 mac_idx, bool normal) mac->set_edcca_mode(rtwdev, mac_idx, normal); } +static inline +void rtw89_mac_set_vcore_cfg(struct rtw89_dev *rtwdev, u8 vlv) +{ + const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; + + if (!mac->set_vcore_cfg) + return; + + mac->set_vcore_cfg(rtwdev, vlv); +} + +static inline +void rtw89_mac_set_vcore_reset(struct rtw89_dev *rtwdev) +{ + struct rtw89_hal *hal = &rtwdev->hal; + + if (hal->thermal_prot_vlv == 0) + return; + + hal->thermal_prot_vlv = 0; + rtw89_mac_set_vcore_cfg(rtwdev, 0); +} + static inline void rtw89_mac_set_edcca_mode_bands(struct rtw89_dev *rtwdev, bool normal) { diff --git a/drivers/net/wireless/realtek/rtw89/mac_be.c b/drivers/net/wireless/realtek/rtw89/mac_be.c index 0fe317929b1b..4bdf20b7ba6d 100644 --- a/drivers/net/wireless/realtek/rtw89/mac_be.c +++ b/drivers/net/wireless/realtek/rtw89/mac_be.c @@ -2676,6 +2676,43 @@ void rtw89_mac_set_edcca_mode_be(struct rtw89_dev *rtwdev, u8 mac_idx, bool norm } } +static +void rtw89_mac_set_vcore_cfg_be(struct rtw89_dev *rtwdev, u8 vlv) +{ + struct rtw89_hal *hal = &rtwdev->hal; + u32 val32; + u8 target; + u8 vpwm; + int i; + + if (rtwdev->chip->chip_id != RTL8922D) + return; + + target = clamp(hal->thermal_prot_vmax - vlv, + hal->thermal_prot_vmin, hal->thermal_prot_vmax); + + val32 = rtw89_read32(rtwdev, R_BE_SPS_DIG_ON_CTRL0); + vpwm = u32_get_bits(val32, B_BE_PWMTUNE_MASK); + val32 &= ~B_BE_PWMTUNE_MASK; + + if (vpwm == target) + return; + + for (i = 0; i < RTW89_THERMAL_PROT_VLV_MAX; i++) { + if (vpwm > target) + vpwm--; + else + vpwm++; + + rtw89_write32(rtwdev, R_BE_SPS_DIG_ON_CTRL0, val32 | vpwm); + + if (vpwm == target) + break; + + mdelay(50); + } +} + static int rtw89_mac_cfg_ppdu_status_be(struct rtw89_dev *rtwdev, u8 mac_idx, bool enable) { @@ -3287,6 +3324,7 @@ const struct rtw89_mac_gen_def rtw89_mac_gen_be = { .cfg_ppdu_status = rtw89_mac_cfg_ppdu_status_be, .cfg_phy_rpt = rtw89_mac_cfg_phy_rpt_be, .set_edcca_mode = rtw89_mac_set_edcca_mode_be, + .set_vcore_cfg = rtw89_mac_set_vcore_cfg_be, .dle_mix_cfg = dle_mix_cfg_be, .chk_dle_rdy = chk_dle_rdy_be, diff --git a/drivers/net/wireless/realtek/rtw89/phy.c b/drivers/net/wireless/realtek/rtw89/phy.c index 981c7f02271a..dcd54be58a33 100644 --- a/drivers/net/wireless/realtek/rtw89/phy.c +++ b/drivers/net/wireless/realtek/rtw89/phy.c @@ -5718,6 +5718,32 @@ static void rtw89_phy_antdiv_init(struct rtw89_dev *rtwdev) rtw89_phy_antdiv_reg_init(rtwdev); } +static void rtw89_phy_thermal_protect_vcore(struct rtw89_dev *rtwdev) +{ + struct rtw89_phy_stat *phystat = &rtwdev->phystat; + struct rtw89_hal *hal = &rtwdev->hal; + bool vcore_enabled = !!hal->thermal_prot_vmax; + u8 th_max = phystat->last_thermal_max; + u8 vlv = hal->thermal_prot_vlv; + u8 prot_th; + + if (!vcore_enabled || (hal->disabled_dm_bitmap & BIT(RTW89_DM_VCORE))) + return; + + prot_th = hal->thermal_prot_th - RTW89_THERMAL_PROT_VLV_TH_OFFSET; + if (th_max > prot_th && vlv < RTW89_THERMAL_PROT_VLV_MAX) + vlv++; + else if (th_max < prot_th - 2 && vlv > 0) + vlv--; + else + return; + + rtw89_debug(rtwdev, RTW89_DBG_RFK_TRACK, "thermal protection vlv=%d\n", vlv); + + hal->thermal_prot_vlv = vlv; + rtw89_mac_set_vcore_cfg(rtwdev, vlv); +} + static void rtw89_phy_thermal_protect(struct rtw89_dev *rtwdev) { struct rtw89_phy_stat *phystat = &rtwdev->phystat; @@ -6186,6 +6212,7 @@ void rtw89_phy_stat_track(struct rtw89_dev *rtwdev) struct rtw89_bb_ctx *bb; rtw89_phy_stat_thermal_update(rtwdev); + rtw89_phy_thermal_protect_vcore(rtwdev); rtw89_phy_thermal_protect(rtwdev); rtw89_phy_stat_rssi_update(rtwdev); rtw89_phy_stat_update(rtwdev); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c index a6693c8413d5..36b9b529195d 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c @@ -3451,7 +3451,7 @@ const struct rtw89_chip_info rtw8922d_chip_info = { .wde_qempty_acq_grpnum = 8, .wde_qempty_mgq_grpsel = 8, .rf_base_addr = {0x3e000, 0x3f000}, - .thermal_th = {0xac, 0xad}, + .thermal_th = {0xac, 0xb4}, .pwr_on_seq = NULL, .pwr_off_seq = NULL, .bb_table = NULL, From b7911466b6dbba6df7c282044b65eaa8700584cb Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 7 Jul 2026 17:10:51 +0800 Subject: [PATCH 0287/1433] wifi: rtw89: 8922d: set ANA CLK enter to 500KHz To power save, change the clock of ANA hardware from 12MHz to 500KHz. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260707091056.42771-11-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/mac.h | 1 + drivers/net/wireless/realtek/rtw89/rtw8922d.c | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/mac.h b/drivers/net/wireless/realtek/rtw89/mac.h index f85c14ca0f35..a5f1694af91a 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.h +++ b/drivers/net/wireless/realtek/rtw89/mac.h @@ -1680,6 +1680,7 @@ enum rtw89_mac_xtal_si_offset { XTAL_SI_PWR_CUT = 0x10, #define XTAL_SI_SMALL_PWR_CUT BIT(0) #define XTAL_SI_BIG_PWR_CUT BIT(1) + XTAL_SI_AONLDO_CTRL = 0x10, XTAL_SI_XTAL_DRV = 0x15, #define XTAL_SI_DRV_LATCH BIT(4) XTAL_SI_XTAL_PLL = 0x16, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c index 36b9b529195d..8ca07aeb377a 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c @@ -535,6 +535,19 @@ static int rtw8922d_pwr_on_func(struct rtw89_dev *rtwdev) rtw89_write32_set(rtwdev, R_BE_SYS_PW_CTRL, B_BE_EN_WLON); rtw89_write32_set(rtwdev, R_BE_WLRESUME_CTRL, B_BE_LPSROP_CMAC0 | B_BE_LPSROP_CMAC1); + + if (hal->aid == RTL8922D_AID7102) { + ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_AONLDO_CTRL, 0, 0x20); + if (ret) + return ret; + + udelay(1); + + ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_AONLDO_CTRL, 0, 0x40); + if (ret) + return ret; + } + rtw89_write32_set(rtwdev, R_BE_SYS_PW_CTRL, B_BE_APFN_ONMAC); ret = read_poll_timeout(rtw89_read32, val32, !(val32 & B_BE_APFN_ONMAC), From 79afed9426ce4cc1bf6807dfadda081124ca2c4a Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 7 Jul 2026 17:10:52 +0800 Subject: [PATCH 0288/1433] wifi: rtw89: 8922d: update scaling factor for RX path Update the per-MCS calibration values of RX scaling factors on RX path (1R or 2R) for BCC and LDPC coding schemes. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260707091056.42771-12-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/reg.h | 5 ++++ drivers/net/wireless/realtek/rtw89/rtw8922d.c | 26 +++++++++++-------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h index 72f7b80fe1fb..2caefb929199 100644 --- a/drivers/net/wireless/realtek/rtw89/reg.h +++ b/drivers/net/wireless/realtek/rtw89/reg.h @@ -11002,6 +11002,11 @@ #define R_RX_LDPC02_BE4 0x26834 #define B_RX_LDPC10_BE4 GENMASK(17, 12) #define B_RX_LDPC11_BE4 GENMASK(23, 18) +#define B_RX_LDPC12_BE4 GENMASK(29, 24) +#define R_RX_LDPC03_BE4 0x26838 +#define B_RX_LDPC13_BE4 GENMASK(5, 0) +#define B_RX_LDPC02_BE4 GENMASK(23, 18) +#define B_RX_LDPC03_BE4 GENMASK(29, 24) #define R_RX_LDPC00_BE4 0x2683C #define B_RX_LDPC04_BE4 GENMASK(5, 0) #define B_RX_LDPC05_BE4 GENMASK(11, 6) diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c index 8ca07aeb377a..768434db14c6 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c @@ -2230,38 +2230,42 @@ static int rtw8922d_ctrl_rx_path_tmac(struct rtw89_dev *rtwdev, rtw89_phy_write32_idx(rtwdev, R_RXCH_BCC1_BE4, B_RXCH_MCS6_BE4, 3, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RXCH_BCC1_BE4, B_RXCH_MCS7_BE4, 7, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RXCH_BCC1_BE4, B_RXCH_MCS8_BE4, 2, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RXCH_BCC1_BE4, B_RXCH_MCS9_BE4, 2, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_AWGN00_BE4, B_RX_AWGN04_BE4, 4, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_AWGN00_BE4, B_RX_AWGN07_BE4, 2, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_AWGN01_BE4, B_RX_AWGN09_BE4, 0, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_AWGN02_BE4, B_RX_AWGN11_BE4, 1, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC04_BE4, 8, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC05_BE4, 5, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC06_BE4, 3, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC07_BE4, 5, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC08_BE4, 1, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RX_LDPC01_BE4, B_RX_LDPC09_BE4, 2, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RX_LDPC02_BE4, B_RX_LDPC10_BE4, 4, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RX_LDPC02_BE4, B_RX_LDPC11_BE4, 2, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC03_BE4, B_RX_LDPC02_BE4, 0x6, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC03_BE4, B_RX_LDPC03_BE4, 0xf, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC04_BE4, 0x10, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC05_BE4, 0xa, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC06_BE4, 0x8, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC07_BE4, 0x14, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC01_BE4, B_RX_LDPC09_BE4, 0x14, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC02_BE4, B_RX_LDPC10_BE4, 0xc, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC02_BE4, B_RX_LDPC11_BE4, 0xb, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC02_BE4, B_RX_LDPC12_BE4, 0x14, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC03_BE4, B_RX_LDPC13_BE4, 0x2d, phy_idx); } else { rtw89_phy_write32_idx(rtwdev, R_RXCH_BCC0_BE4, B_RXCH_MCS4_BE4, 13, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RXCH_BCC1_BE4, B_RXCH_MCS5_BE4, 15, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RXCH_BCC1_BE4, B_RXCH_MCS6_BE4, 6, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RXCH_BCC1_BE4, B_RXCH_MCS7_BE4, 15, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RXCH_BCC1_BE4, B_RXCH_MCS8_BE4, 4, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RXCH_BCC1_BE4, B_RXCH_MCS9_BE4, 15, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_AWGN00_BE4, B_RX_AWGN04_BE4, 9, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_AWGN00_BE4, B_RX_AWGN07_BE4, 3, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_AWGN01_BE4, B_RX_AWGN09_BE4, 1, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_AWGN02_BE4, B_RX_AWGN11_BE4, 0, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC03_BE4, B_RX_LDPC02_BE4, 0x3, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC03_BE4, B_RX_LDPC03_BE4, 0x9, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC04_BE4, 9, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC05_BE4, 8, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC06_BE4, 6, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC07_BE4, 16, phy_idx); - rtw89_phy_write32_idx(rtwdev, R_RX_LDPC00_BE4, B_RX_LDPC08_BE4, 4, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_LDPC01_BE4, B_RX_LDPC09_BE4, 9, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_LDPC02_BE4, B_RX_LDPC10_BE4, 9, phy_idx); rtw89_phy_write32_idx(rtwdev, R_RX_LDPC02_BE4, B_RX_LDPC11_BE4, 7, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC02_BE4, B_RX_LDPC12_BE4, 0x17, phy_idx); + rtw89_phy_write32_idx(rtwdev, R_RX_LDPC03_BE4, B_RX_LDPC13_BE4, 0x1a, phy_idx); } return 0; From 9bf6bd6ed5accb57544d04ded911a2ef1642d48f Mon Sep 17 00:00:00 2001 From: Chih-Kang Chang Date: Tue, 7 Jul 2026 17:10:53 +0800 Subject: [PATCH 0289/1433] wifi: rtw89: 8852a: fix RSSI report when average beacon RSSI is not ready 8852A uses the average beacon RSSI to smooth the RSSI. However, before the average beacon RSSI is available, the RSSI should use the PPDU status RSSI of the received packet to avoid reporting the RSSI as -110 dBm. Fixes: f0f3bf4b370c ("wifi: rtw89: 8852a: report average RSSI to avoid unnecessary scanning") Signed-off-by: Chih-Kang Chang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260707091056.42771-13-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/rtw8852a.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852a.c b/drivers/net/wireless/realtek/rtw89/rtw8852a.c index 6aa726efb7f6..78e07276fb2e 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852a.c @@ -2211,7 +2211,7 @@ static void rtw8852a_query_ppdu(struct rtw89_dev *rtwdev, u8 raw; if (!status->signal) { - if (phy_ppdu->to_self) + if (phy_ppdu->to_self && ewma_rssi_read(&bb->bcn_rssi)) raw = ewma_rssi_read(&bb->bcn_rssi); else raw = max(rx_power[RF_PATH_A], rx_power[RF_PATH_B]); From 235fa79d756106028b92505e78bd281698d9fb44 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 7 Jul 2026 17:10:54 +0800 Subject: [PATCH 0290/1433] wifi: rtw89: phy: add NCTL check for WiFi 7 chips Initialize NCTL hardware and check the state before downloading NCTL. Otherwise, the following RF calibrations relying on NCTL may fail. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260707091056.42771-14-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/phy.c | 1 + drivers/net/wireless/realtek/rtw89/phy.h | 6 +++ drivers/net/wireless/realtek/rtw89/phy_be.c | 41 +++++++++++++++++++++ drivers/net/wireless/realtek/rtw89/reg.h | 6 +++ 4 files changed, 54 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/phy.c b/drivers/net/wireless/realtek/rtw89/phy.c index dcd54be58a33..30d5741da87e 100644 --- a/drivers/net/wireless/realtek/rtw89/phy.c +++ b/drivers/net/wireless/realtek/rtw89/phy.c @@ -9010,6 +9010,7 @@ const struct rtw89_phy_gen_def rtw89_phy_gen_ax = { .physts = &rtw89_physts_regs_ax, .cfo = &rtw89_cfo_regs_ax, .bb_wrap = NULL, + .nctl = NULL, .phy0_phy1_offset = rtw89_phy0_phy1_offset_ax, .config_bb_gain = rtw89_phy_config_bb_gain_ax, .preinit_rf_nctl = rtw89_phy_preinit_rf_nctl_ax, diff --git a/drivers/net/wireless/realtek/rtw89/phy.h b/drivers/net/wireless/realtek/rtw89/phy.h index 532232892831..e31034377b54 100644 --- a/drivers/net/wireless/realtek/rtw89/phy.h +++ b/drivers/net/wireless/realtek/rtw89/phy.h @@ -502,6 +502,11 @@ struct rtw89_bb_wrap_regs { u32 pwr_macid_path; }; +struct rtw89_nctl_regs { + u32 cfg; + u32 rw; +}; + enum rtw89_bandwidth_section_num_ax { RTW89_BW20_SEC_NUM_AX = 8, RTW89_BW40_SEC_NUM_AX = 4, @@ -679,6 +684,7 @@ struct rtw89_phy_gen_def { const struct rtw89_physts_regs *physts; const struct rtw89_cfo_regs *cfo; const struct rtw89_bb_wrap_regs *bb_wrap; + const struct rtw89_nctl_regs *nctl; u32 (*phy0_phy1_offset)(struct rtw89_dev *rtwdev, u32 addr); void (*config_bb_gain)(struct rtw89_dev *rtwdev, const struct rtw89_reg2_def *reg, diff --git a/drivers/net/wireless/realtek/rtw89/phy_be.c b/drivers/net/wireless/realtek/rtw89/phy_be.c index ac4ce30445b3..e471409a4b8f 100644 --- a/drivers/net/wireless/realtek/rtw89/phy_be.c +++ b/drivers/net/wireless/realtek/rtw89/phy_be.c @@ -225,6 +225,16 @@ static const struct rtw89_bb_wrap_regs rtw89_bb_wrap_regs_be_v1 = { .pwr_macid_path = R_BE_PWR_MACID_PATH_BASE_V1, }; +static const struct rtw89_nctl_regs rtw89_nctl_regs_be = { + .cfg = R_NCTL_CFG, + .rw = R_NCTL_RW, +}; + +static const struct rtw89_nctl_regs rtw89_nctl_regs_be_v1 = { + .cfg = R_NCTL_CFG_BE4, + .rw = R_NCTL_RW_BE4, +}; + static u32 rtw89_phy0_phy1_offset_be(struct rtw89_dev *rtwdev, u32 addr) { u32 phy_page = addr >> 8; @@ -440,6 +450,28 @@ static void rtw89_phy_config_bb_gain_be(struct rtw89_dev *rtwdev, } } +static void rtw89_phy_preinit_rf_nctl_check_be(struct rtw89_dev *rtwdev) +{ + const struct rtw89_phy_gen_def *phy = rtwdev->chip->phy_def; + const struct rtw89_nctl_regs *nctl = phy->nctl; + int poll; + u32 val; + + rtw89_phy_write32(rtwdev, nctl->cfg, B_NCTL_CHK_EN); + + for (poll = 0; poll < 10000; poll++) { + rtw89_phy_write32(rtwdev, nctl->rw, B_NCTL_CHECK); + + fsleep(10); + + val = rtw89_phy_read32(rtwdev, nctl->rw); + if (val == B_NCTL_CHECK) + return; + } + + rtw89_warn(rtwdev, "NCTL INIT check 0x%x[2]TIMEOUT!\n", nctl->rw); +} + static void rtw89_phy_preinit_rf_nctl_be(struct rtw89_dev *rtwdev) { rtw89_phy_write32_mask(rtwdev, R_GOTX_IQKDPK_C0, B_GOTX_IQKDPK, 0x3); @@ -456,6 +488,11 @@ static void rtw89_phy_preinit_rf_nctl_be(struct rtw89_dev *rtwdev) rtw89_phy_write32_mask(rtwdev, R_IQK_DPK_RST_C1, B_IQK_DPK_RST, 0x1); rtw89_phy_write32_mask(rtwdev, R_TXRFC_C1, B_TXRFC_RST, 0x1); } + + rtw89_phy_write32_mask(rtwdev, R_TX_COLLISION_T2R_ST, B_RX_CFIR_PATH_EN, 0x1); + rtw89_phy_write32_mask(rtwdev, R_TX_COLLISION_T2R_ST, B_RX_CFIR_PATH, 0x3); + + rtw89_phy_preinit_rf_nctl_check_be(rtwdev); } static void rtw89_phy_preinit_rf_nctl_be_v1(struct rtw89_dev *rtwdev) @@ -466,6 +503,8 @@ static void rtw89_phy_preinit_rf_nctl_be_v1(struct rtw89_dev *rtwdev) rtw89_phy_write32_mask(rtwdev, R_IQK_DPK_RST_BE4, B_IQK_DPK_RST, 0x1); rtw89_phy_write32_mask(rtwdev, R_IQK_DPK_PRST_BE4, B_IQK_DPK_PRST, 0x1); rtw89_phy_write32_mask(rtwdev, R_IQK_DPK_PRST_C1_BE4, B_IQK_DPK_PRST, 0x1); + + rtw89_phy_preinit_rf_nctl_check_be(rtwdev); } static u32 rtw89_phy_bb_wrap_flush_addr(struct rtw89_dev *rtwdev, u32 addr) @@ -1905,6 +1944,7 @@ const struct rtw89_phy_gen_def rtw89_phy_gen_be = { .physts = &rtw89_physts_regs_be, .cfo = &rtw89_cfo_regs_be, .bb_wrap = &rtw89_bb_wrap_regs_be, + .nctl = &rtw89_nctl_regs_be, .phy0_phy1_offset = rtw89_phy0_phy1_offset_be, .config_bb_gain = rtw89_phy_config_bb_gain_be, .preinit_rf_nctl = rtw89_phy_preinit_rf_nctl_be, @@ -1930,6 +1970,7 @@ const struct rtw89_phy_gen_def rtw89_phy_gen_be_v1 = { .physts = &rtw89_physts_regs_be_v1, .cfo = &rtw89_cfo_regs_be_v1, .bb_wrap = &rtw89_bb_wrap_regs_be_v1, + .nctl = &rtw89_nctl_regs_be_v1, .phy0_phy1_offset = rtw89_phy0_phy1_offset_be_v1, .config_bb_gain = rtw89_phy_config_bb_gain_be, .preinit_rf_nctl = rtw89_phy_preinit_rf_nctl_be_v1, diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h index 2caefb929199..4d2117de798b 100644 --- a/drivers/net/wireless/realtek/rtw89/reg.h +++ b/drivers/net/wireless/realtek/rtw89/reg.h @@ -9022,6 +9022,8 @@ #define B_IQK_DPK_RST BIT(0) #define R_TX_COLLISION_T2R_ST 0x0C70 #define B_TX_COLLISION_T2R_ST_M GENMASK(25, 20) +#define B_RX_CFIR_PATH_EN BIT(9) +#define B_RX_CFIR_PATH GENMASK(6, 5) #define B_TXRX_FORCE_VAL GENMASK(9, 0) #define R_TXGATING 0x0C74 #define B_TXGATING_EN BIT(4) @@ -10115,6 +10117,8 @@ #define R_S1_DACKQ8 0x7E98 #define B_S1_DACKQ8_K GENMASK(15, 8) #define R_NCTL_CFG 0x8000 +#define R_NCTL_CFG_BE4 0x38000 +#define B_NCTL_CHK_EN BIT(3) #define B_NCTL_CFG_SPAGE GENMASK(2, 1) #define R_NCTL_RPT 0x8008 #define B_NCTL_RPT_FLG BIT(26) @@ -10148,6 +10152,8 @@ #define R_KIP_MOD 0x8078 #define B_KIP_MOD GENMASK(19, 0) #define R_NCTL_RW 0x8080 +#define R_NCTL_RW_BE4 0x38080 +#define B_NCTL_CHECK BIT(2) #define R_KIP_SYSCFG 0x8088 #define R_KIP_CLK 0x808C #define R_DPK_IDL 0x809C From 5c925d7722c674fe4d0819f2bb176d144a3eaae2 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 7 Jul 2026 17:10:55 +0800 Subject: [PATCH 0291/1433] wifi: rtw89: unify access struct of TX power track tables There are two struct to access TX power track tables. One is to access driver built-in tables, and the other one is to access the tables in fw element. As we are going to remove the built-in tables from driver, unify to use the struct as fw element style. The precedence is to use tables in fw element if present, and then built-in tables. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260707091056.42771-15-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/phy.h | 19 ---- .../net/wireless/realtek/rtw89/rtw8851b_rfk.c | 20 ++-- .../wireless/realtek/rtw89/rtw8851b_table.c | 15 +-- .../wireless/realtek/rtw89/rtw8851b_table.h | 2 +- .../net/wireless/realtek/rtw89/rtw8852a_rfk.c | 36 ++++--- .../wireless/realtek/rtw89/rtw8852a_table.c | 27 ++--- .../wireless/realtek/rtw89/rtw8852a_table.h | 2 +- .../net/wireless/realtek/rtw89/rtw8852b_rfk.c | 36 ++++--- .../wireless/realtek/rtw89/rtw8852b_table.c | 27 ++--- .../wireless/realtek/rtw89/rtw8852b_table.h | 2 +- .../net/wireless/realtek/rtw89/rtw8852c_rfk.c | 101 +++++++----------- .../wireless/realtek/rtw89/rtw8852c_table.c | 35 +++--- .../wireless/realtek/rtw89/rtw8852c_table.h | 2 +- 13 files changed, 146 insertions(+), 178 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/phy.h b/drivers/net/wireless/realtek/rtw89/phy.h index e31034377b54..b0c82bac72f0 100644 --- a/drivers/net/wireless/realtek/rtw89/phy.h +++ b/drivers/net/wireless/realtek/rtw89/phy.h @@ -353,25 +353,6 @@ struct rtw89_txpwr_byrate_cfg { u32 data; }; -struct rtw89_txpwr_track_cfg { - const s8 (*delta_swingidx_6gb_n)[DELTA_SWINGIDX_SIZE]; - const s8 (*delta_swingidx_6gb_p)[DELTA_SWINGIDX_SIZE]; - const s8 (*delta_swingidx_6ga_n)[DELTA_SWINGIDX_SIZE]; - const s8 (*delta_swingidx_6ga_p)[DELTA_SWINGIDX_SIZE]; - const s8 (*delta_swingidx_5gb_n)[DELTA_SWINGIDX_SIZE]; - const s8 (*delta_swingidx_5gb_p)[DELTA_SWINGIDX_SIZE]; - const s8 (*delta_swingidx_5ga_n)[DELTA_SWINGIDX_SIZE]; - const s8 (*delta_swingidx_5ga_p)[DELTA_SWINGIDX_SIZE]; - const s8 *delta_swingidx_2gb_n; - const s8 *delta_swingidx_2gb_p; - const s8 *delta_swingidx_2ga_n; - const s8 *delta_swingidx_2ga_p; - const s8 *delta_swingidx_2g_cck_b_n; - const s8 *delta_swingidx_2g_cck_b_p; - const s8 *delta_swingidx_2g_cck_a_n; - const s8 *delta_swingidx_2g_cck_a_p; -}; - struct rtw89_phy_dig_gain_cfg { const struct rtw89_reg_def *table; u8 size; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b_rfk.c b/drivers/net/wireless/realtek/rtw89/rtw8851b_rfk.c index a6c7f59223ef..c63f0e6364da 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851b_rfk.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851b_rfk.c @@ -2823,6 +2823,7 @@ static void _tssi_set_tmeter_tbl(struct rtw89_dev *rtwdev, enum rtw89_phy_idx ph } \ __val; \ }) + const struct rtw89_fw_txpwr_track_cfg *trk = rtwdev->fw.elm_info.txpwr_trk; struct rtw89_tssi_info *tssi_info = &rtwdev->tssi; u8 ch = chan->channel; u8 subband = chan->subband_type; @@ -2833,23 +2834,26 @@ static void _tssi_set_tmeter_tbl(struct rtw89_dev *rtwdev, enum rtw89_phy_idx ph u32 tmp = 0; u8 i, j; + if (!trk) + trk = &rtw89_8851b_trk_cfg; + switch (subband) { default: case RTW89_CH_2G: - thm_up_a = rtw89_8851b_trk_cfg.delta_swingidx_2ga_p; - thm_down_a = rtw89_8851b_trk_cfg.delta_swingidx_2ga_n; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_P][0]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_N][0]; break; case RTW89_CH_5G_BAND_1: - thm_up_a = rtw89_8851b_trk_cfg.delta_swingidx_5ga_p[0]; - thm_down_a = rtw89_8851b_trk_cfg.delta_swingidx_5ga_n[0]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][0]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][0]; break; case RTW89_CH_5G_BAND_3: - thm_up_a = rtw89_8851b_trk_cfg.delta_swingidx_5ga_p[1]; - thm_down_a = rtw89_8851b_trk_cfg.delta_swingidx_5ga_n[1]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][1]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][1]; break; case RTW89_CH_5G_BAND_4: - thm_up_a = rtw89_8851b_trk_cfg.delta_swingidx_5ga_p[2]; - thm_down_a = rtw89_8851b_trk_cfg.delta_swingidx_5ga_n[2]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][2]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][2]; break; } diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b_table.c b/drivers/net/wireless/realtek/rtw89/rtw8851b_table.c index a9c309c245c3..b8105f6e94f1 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851b_table.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851b_table.c @@ -2,6 +2,7 @@ /* Copyright(c) 2022-2023 Realtek Corporation */ +#include "fw.h" #include "phy.h" #include "reg.h" #include "rtw8851b_table.h" @@ -14866,13 +14867,13 @@ const struct rtw89_txpwr_table rtw89_8851b_byr_table_type2 = { .load = rtw89_phy_load_txpwr_byrate, }; -const struct rtw89_txpwr_track_cfg rtw89_8851b_trk_cfg = { - .delta_swingidx_5ga_n = _txpwr_track_delta_swingidx_5ga_n, - .delta_swingidx_5ga_p = _txpwr_track_delta_swingidx_5ga_p, - .delta_swingidx_2ga_n = _txpwr_track_delta_swingidx_2ga_n, - .delta_swingidx_2ga_p = _txpwr_track_delta_swingidx_2ga_p, - .delta_swingidx_2g_cck_a_n = _txpwr_track_delta_swingidx_2g_cck_a_n, - .delta_swingidx_2g_cck_a_p = _txpwr_track_delta_swingidx_2g_cck_a_p, +const struct rtw89_fw_txpwr_track_cfg rtw89_8851b_trk_cfg = { + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N] = _txpwr_track_delta_swingidx_5ga_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P] = _txpwr_track_delta_swingidx_5ga_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_N] = &_txpwr_track_delta_swingidx_2ga_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_P] = &_txpwr_track_delta_swingidx_2ga_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_A_N] = &_txpwr_track_delta_swingidx_2g_cck_a_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_A_P] = &_txpwr_track_delta_swingidx_2g_cck_a_p, }; const struct rtw89_rfe_parms rtw89_8851b_dflt_parms = { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b_table.h b/drivers/net/wireless/realtek/rtw89/rtw8851b_table.h index d8cf545d40a0..a73c0a6f03df 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851b_table.h +++ b/drivers/net/wireless/realtek/rtw89/rtw8851b_table.h @@ -11,7 +11,7 @@ extern const struct rtw89_phy_table rtw89_8851b_phy_bb_table; extern const struct rtw89_phy_table rtw89_8851b_phy_bb_gain_table; extern const struct rtw89_phy_table rtw89_8851b_phy_radioa_table; extern const struct rtw89_phy_table rtw89_8851b_phy_nctl_table; -extern const struct rtw89_txpwr_track_cfg rtw89_8851b_trk_cfg; +extern const struct rtw89_fw_txpwr_track_cfg rtw89_8851b_trk_cfg; extern const struct rtw89_rfe_parms rtw89_8851b_dflt_parms; extern const struct rtw89_rfe_parms_conf rtw89_8851b_rfe_parms_conf[]; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852a_rfk.c b/drivers/net/wireless/realtek/rtw89/rtw8852a_rfk.c index 8679b21fd3fd..609cc300f24e 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852a_rfk.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852a_rfk.c @@ -2910,6 +2910,7 @@ static void _tssi_set_tmeter_tbl(struct rtw89_dev *rtwdev, enum rtw89_phy_idx ph } \ __val; \ }) + const struct rtw89_fw_txpwr_track_cfg *trk = rtwdev->fw.elm_info.txpwr_trk; struct rtw89_tssi_info *tssi_info = &rtwdev->tssi; u8 ch = chan->channel; u8 subband = chan->subband_type; @@ -2922,31 +2923,34 @@ static void _tssi_set_tmeter_tbl(struct rtw89_dev *rtwdev, enum rtw89_phy_idx ph u32 tmp = 0; u8 i, j; + if (!trk) + trk = &rtw89_8852a_trk_cfg; + switch (subband) { default: case RTW89_CH_2G: - thm_up_a = rtw89_8852a_trk_cfg.delta_swingidx_2ga_p; - thm_down_a = rtw89_8852a_trk_cfg.delta_swingidx_2ga_n; - thm_up_b = rtw89_8852a_trk_cfg.delta_swingidx_2gb_p; - thm_down_b = rtw89_8852a_trk_cfg.delta_swingidx_2gb_n; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_P][0]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_N][0]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_P][0]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_N][0]; break; case RTW89_CH_5G_BAND_1: - thm_up_a = rtw89_8852a_trk_cfg.delta_swingidx_5ga_p[0]; - thm_down_a = rtw89_8852a_trk_cfg.delta_swingidx_5ga_n[0]; - thm_up_b = rtw89_8852a_trk_cfg.delta_swingidx_5gb_p[0]; - thm_down_b = rtw89_8852a_trk_cfg.delta_swingidx_5gb_n[0]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][0]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][0]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][0]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][0]; break; case RTW89_CH_5G_BAND_3: - thm_up_a = rtw89_8852a_trk_cfg.delta_swingidx_5ga_p[1]; - thm_down_a = rtw89_8852a_trk_cfg.delta_swingidx_5ga_n[1]; - thm_up_b = rtw89_8852a_trk_cfg.delta_swingidx_5gb_p[1]; - thm_down_b = rtw89_8852a_trk_cfg.delta_swingidx_5gb_n[1]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][1]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][1]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][1]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][1]; break; case RTW89_CH_5G_BAND_4: - thm_up_a = rtw89_8852a_trk_cfg.delta_swingidx_5ga_p[2]; - thm_down_a = rtw89_8852a_trk_cfg.delta_swingidx_5ga_n[2]; - thm_up_b = rtw89_8852a_trk_cfg.delta_swingidx_5gb_p[2]; - thm_down_b = rtw89_8852a_trk_cfg.delta_swingidx_5gb_n[2]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][2]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][2]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][2]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][2]; break; } diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852a_table.c b/drivers/net/wireless/realtek/rtw89/rtw8852a_table.c index ffdeb3801991..f1bcef64a91f 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852a_table.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852a_table.c @@ -2,6 +2,7 @@ /* Copyright(c) 2019-2020 Realtek Corporation */ +#include "fw.h" #include "phy.h" #include "reg.h" #include "rtw8852a_table.h" @@ -50983,19 +50984,19 @@ const struct rtw89_txpwr_table rtw89_8852a_byr_table = { .load = rtw89_phy_load_txpwr_byrate, }; -const struct rtw89_txpwr_track_cfg rtw89_8852a_trk_cfg = { - .delta_swingidx_5gb_n = _txpwr_track_delta_swingidx_5gb_n, - .delta_swingidx_5gb_p = _txpwr_track_delta_swingidx_5gb_p, - .delta_swingidx_5ga_n = _txpwr_track_delta_swingidx_5ga_n, - .delta_swingidx_5ga_p = _txpwr_track_delta_swingidx_5ga_p, - .delta_swingidx_2gb_n = _txpwr_track_delta_swingidx_2gb_n, - .delta_swingidx_2gb_p = _txpwr_track_delta_swingidx_2gb_p, - .delta_swingidx_2ga_n = _txpwr_track_delta_swingidx_2ga_n, - .delta_swingidx_2ga_p = _txpwr_track_delta_swingidx_2ga_p, - .delta_swingidx_2g_cck_b_n = _txpwr_track_delta_swingidx_2g_cck_b_n, - .delta_swingidx_2g_cck_b_p = _txpwr_track_delta_swingidx_2g_cck_b_p, - .delta_swingidx_2g_cck_a_n = _txpwr_track_delta_swingidx_2g_cck_a_n, - .delta_swingidx_2g_cck_a_p = _txpwr_track_delta_swingidx_2g_cck_a_p, +const struct rtw89_fw_txpwr_track_cfg rtw89_8852a_trk_cfg = { + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N] = _txpwr_track_delta_swingidx_5gb_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P] = _txpwr_track_delta_swingidx_5gb_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N] = _txpwr_track_delta_swingidx_5ga_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P] = _txpwr_track_delta_swingidx_5ga_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_N] = &_txpwr_track_delta_swingidx_2gb_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_P] = &_txpwr_track_delta_swingidx_2gb_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_N] = &_txpwr_track_delta_swingidx_2ga_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_P] = &_txpwr_track_delta_swingidx_2ga_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_B_N] = &_txpwr_track_delta_swingidx_2g_cck_b_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_B_P] = &_txpwr_track_delta_swingidx_2g_cck_b_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_A_N] = &_txpwr_track_delta_swingidx_2g_cck_a_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_A_P] = &_txpwr_track_delta_swingidx_2g_cck_a_p, }; const struct rtw89_rfe_parms rtw89_8852a_dflt_parms = { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852a_table.h b/drivers/net/wireless/realtek/rtw89/rtw8852a_table.h index 58fe8575c1c9..501a8248d1ce 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852a_table.h +++ b/drivers/net/wireless/realtek/rtw89/rtw8852a_table.h @@ -11,7 +11,7 @@ extern const struct rtw89_phy_table rtw89_8852a_phy_bb_table; extern const struct rtw89_phy_table rtw89_8852a_phy_radioa_table; extern const struct rtw89_phy_table rtw89_8852a_phy_radiob_table; extern const struct rtw89_phy_table rtw89_8852a_phy_nctl_table; -extern const struct rtw89_txpwr_track_cfg rtw89_8852a_trk_cfg; +extern const struct rtw89_fw_txpwr_track_cfg rtw89_8852a_trk_cfg; extern const struct rtw89_rfe_parms rtw89_8852a_dflt_parms; #endif diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852b_rfk.c b/drivers/net/wireless/realtek/rtw89/rtw8852b_rfk.c index 5cfacc10e7c8..29a82b1c4457 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852b_rfk.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852b_rfk.c @@ -2784,6 +2784,7 @@ static void _tssi_set_tmeter_tbl(struct rtw89_dev *rtwdev, enum rtw89_phy_idx ph } \ __val; \ }) + const struct rtw89_fw_txpwr_track_cfg *trk = rtwdev->fw.elm_info.txpwr_trk; struct rtw89_tssi_info *tssi_info = &rtwdev->tssi; u8 ch = chan->channel; u8 subband = chan->subband_type; @@ -2796,31 +2797,34 @@ static void _tssi_set_tmeter_tbl(struct rtw89_dev *rtwdev, enum rtw89_phy_idx ph u32 tmp = 0; u8 i, j; + if (!trk) + trk = &rtw89_8852b_trk_cfg; + switch (subband) { default: case RTW89_CH_2G: - thm_up_a = rtw89_8852b_trk_cfg.delta_swingidx_2ga_p; - thm_down_a = rtw89_8852b_trk_cfg.delta_swingidx_2ga_n; - thm_up_b = rtw89_8852b_trk_cfg.delta_swingidx_2gb_p; - thm_down_b = rtw89_8852b_trk_cfg.delta_swingidx_2gb_n; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_P][0]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_N][0]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_P][0]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_N][0]; break; case RTW89_CH_5G_BAND_1: - thm_up_a = rtw89_8852b_trk_cfg.delta_swingidx_5ga_p[0]; - thm_down_a = rtw89_8852b_trk_cfg.delta_swingidx_5ga_n[0]; - thm_up_b = rtw89_8852b_trk_cfg.delta_swingidx_5gb_p[0]; - thm_down_b = rtw89_8852b_trk_cfg.delta_swingidx_5gb_n[0]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][0]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][0]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][0]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][0]; break; case RTW89_CH_5G_BAND_3: - thm_up_a = rtw89_8852b_trk_cfg.delta_swingidx_5ga_p[1]; - thm_down_a = rtw89_8852b_trk_cfg.delta_swingidx_5ga_n[1]; - thm_up_b = rtw89_8852b_trk_cfg.delta_swingidx_5gb_p[1]; - thm_down_b = rtw89_8852b_trk_cfg.delta_swingidx_5gb_n[1]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][1]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][1]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][1]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][1]; break; case RTW89_CH_5G_BAND_4: - thm_up_a = rtw89_8852b_trk_cfg.delta_swingidx_5ga_p[2]; - thm_down_a = rtw89_8852b_trk_cfg.delta_swingidx_5ga_n[2]; - thm_up_b = rtw89_8852b_trk_cfg.delta_swingidx_5gb_p[2]; - thm_down_b = rtw89_8852b_trk_cfg.delta_swingidx_5gb_n[2]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][2]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][2]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][2]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][2]; break; } diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852b_table.c b/drivers/net/wireless/realtek/rtw89/rtw8852b_table.c index 07945d06dc59..96b18e9095b3 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852b_table.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852b_table.c @@ -2,6 +2,7 @@ /* Copyright(c) 2019-2020 Realtek Corporation */ +#include "fw.h" #include "phy.h" #include "reg.h" #include "rtw8852b_table.h" @@ -22895,19 +22896,19 @@ const struct rtw89_txpwr_table rtw89_8852b_byr_table = { .load = rtw89_phy_load_txpwr_byrate, }; -const struct rtw89_txpwr_track_cfg rtw89_8852b_trk_cfg = { - .delta_swingidx_5gb_n = _txpwr_track_delta_swingidx_5gb_n, - .delta_swingidx_5gb_p = _txpwr_track_delta_swingidx_5gb_p, - .delta_swingidx_5ga_n = _txpwr_track_delta_swingidx_5ga_n, - .delta_swingidx_5ga_p = _txpwr_track_delta_swingidx_5ga_p, - .delta_swingidx_2gb_n = _txpwr_track_delta_swingidx_2gb_n, - .delta_swingidx_2gb_p = _txpwr_track_delta_swingidx_2gb_p, - .delta_swingidx_2ga_n = _txpwr_track_delta_swingidx_2ga_n, - .delta_swingidx_2ga_p = _txpwr_track_delta_swingidx_2ga_p, - .delta_swingidx_2g_cck_b_n = _txpwr_track_delta_swingidx_2g_cck_b_n, - .delta_swingidx_2g_cck_b_p = _txpwr_track_delta_swingidx_2g_cck_b_p, - .delta_swingidx_2g_cck_a_n = _txpwr_track_delta_swingidx_2g_cck_a_n, - .delta_swingidx_2g_cck_a_p = _txpwr_track_delta_swingidx_2g_cck_a_p, +const struct rtw89_fw_txpwr_track_cfg rtw89_8852b_trk_cfg = { + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N] = _txpwr_track_delta_swingidx_5gb_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P] = _txpwr_track_delta_swingidx_5gb_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N] = _txpwr_track_delta_swingidx_5ga_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P] = _txpwr_track_delta_swingidx_5ga_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_N] = &_txpwr_track_delta_swingidx_2gb_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_P] = &_txpwr_track_delta_swingidx_2gb_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_N] = &_txpwr_track_delta_swingidx_2ga_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_P] = &_txpwr_track_delta_swingidx_2ga_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_B_N] = &_txpwr_track_delta_swingidx_2g_cck_b_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_B_P] = &_txpwr_track_delta_swingidx_2g_cck_b_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_A_N] = &_txpwr_track_delta_swingidx_2g_cck_a_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_A_P] = &_txpwr_track_delta_swingidx_2g_cck_a_p, }; const struct rtw89_rfe_parms rtw89_8852b_dflt_parms = { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852b_table.h b/drivers/net/wireless/realtek/rtw89/rtw8852b_table.h index da6c90e2ba93..d30a1fb43e96 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852b_table.h +++ b/drivers/net/wireless/realtek/rtw89/rtw8852b_table.h @@ -12,7 +12,7 @@ extern const struct rtw89_phy_table rtw89_8852b_phy_bb_gain_table; extern const struct rtw89_phy_table rtw89_8852b_phy_radioa_table; extern const struct rtw89_phy_table rtw89_8852b_phy_radiob_table; extern const struct rtw89_phy_table rtw89_8852b_phy_nctl_table; -extern const struct rtw89_txpwr_track_cfg rtw89_8852b_trk_cfg; +extern const struct rtw89_fw_txpwr_track_cfg rtw89_8852b_trk_cfg; extern const struct rtw89_rfe_parms rtw89_8852b_dflt_parms; #endif diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852c_rfk.c b/drivers/net/wireless/realtek/rtw89/rtw8852c_rfk.c index cbee484dee30..8d03a4645947 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852c_rfk.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852c_rfk.c @@ -2992,7 +2992,7 @@ static void _tssi_set_tmeter_tbl(struct rtw89_dev *rtwdev, enum rtw89_phy_idx ph } \ __val; \ }) - struct rtw89_fw_txpwr_track_cfg *trk = rtwdev->fw.elm_info.txpwr_trk; + const struct rtw89_fw_txpwr_track_cfg *trk = rtwdev->fw.elm_info.txpwr_trk; struct rtw89_tssi_info *tssi_info = &rtwdev->tssi; u8 ch = chan->channel; u8 subband = chan->subband_type; @@ -3005,91 +3005,62 @@ static void _tssi_set_tmeter_tbl(struct rtw89_dev *rtwdev, enum rtw89_phy_idx ph u32 tmp = 0; u8 i, j; + if (!trk) + trk = &rtw89_8852c_trk_cfg; + switch (subband) { default: case RTW89_CH_2G: - thm_up_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_P][0] : - rtw89_8852c_trk_cfg.delta_swingidx_2ga_p; - thm_down_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_N][0] : - rtw89_8852c_trk_cfg.delta_swingidx_2ga_n; - thm_up_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_P][0] : - rtw89_8852c_trk_cfg.delta_swingidx_2gb_p; - thm_down_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_N][0] : - rtw89_8852c_trk_cfg.delta_swingidx_2gb_n; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_P][0]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_N][0]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_P][0]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_N][0]; break; case RTW89_CH_5G_BAND_1: - thm_up_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][0] : - rtw89_8852c_trk_cfg.delta_swingidx_5ga_p[0]; - thm_down_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][0] : - rtw89_8852c_trk_cfg.delta_swingidx_5ga_n[0]; - thm_up_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][0] : - rtw89_8852c_trk_cfg.delta_swingidx_5gb_p[0]; - thm_down_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][0] : - rtw89_8852c_trk_cfg.delta_swingidx_5gb_n[0]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][0]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][0]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][0]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][0]; break; case RTW89_CH_5G_BAND_3: - thm_up_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][1] : - rtw89_8852c_trk_cfg.delta_swingidx_5ga_p[1]; - thm_down_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][1] : - rtw89_8852c_trk_cfg.delta_swingidx_5ga_n[1]; - thm_up_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][1] : - rtw89_8852c_trk_cfg.delta_swingidx_5gb_p[1]; - thm_down_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][1] : - rtw89_8852c_trk_cfg.delta_swingidx_5gb_n[1]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][1]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][1]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][1]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][1]; break; case RTW89_CH_5G_BAND_4: - thm_up_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][2] : - rtw89_8852c_trk_cfg.delta_swingidx_5ga_p[2]; - thm_down_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][2] : - rtw89_8852c_trk_cfg.delta_swingidx_5ga_n[2]; - thm_up_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][2] : - rtw89_8852c_trk_cfg.delta_swingidx_5gb_p[2]; - thm_down_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][2] : - rtw89_8852c_trk_cfg.delta_swingidx_5gb_n[2]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P][2]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N][2]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P][2]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N][2]; break; case RTW89_CH_6G_BAND_IDX0: case RTW89_CH_6G_BAND_IDX1: - thm_up_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_P][0] : - rtw89_8852c_trk_cfg.delta_swingidx_6ga_p[0]; - thm_down_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_N][0] : - rtw89_8852c_trk_cfg.delta_swingidx_6ga_n[0]; - thm_up_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_P][0] : - rtw89_8852c_trk_cfg.delta_swingidx_6gb_p[0]; - thm_down_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_N][0] : - rtw89_8852c_trk_cfg.delta_swingidx_6gb_n[0]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_P][0]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_N][0]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_P][0]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_N][0]; break; case RTW89_CH_6G_BAND_IDX2: case RTW89_CH_6G_BAND_IDX3: - thm_up_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_P][1] : - rtw89_8852c_trk_cfg.delta_swingidx_6ga_p[1]; - thm_down_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_N][1] : - rtw89_8852c_trk_cfg.delta_swingidx_6ga_n[1]; - thm_up_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_P][1] : - rtw89_8852c_trk_cfg.delta_swingidx_6gb_p[1]; - thm_down_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_N][1] : - rtw89_8852c_trk_cfg.delta_swingidx_6gb_n[1]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_P][1]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_N][1]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_P][1]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_N][1]; break; case RTW89_CH_6G_BAND_IDX4: case RTW89_CH_6G_BAND_IDX5: - thm_up_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_P][2] : - rtw89_8852c_trk_cfg.delta_swingidx_6ga_p[2]; - thm_down_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_N][2] : - rtw89_8852c_trk_cfg.delta_swingidx_6ga_n[2]; - thm_up_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_P][2] : - rtw89_8852c_trk_cfg.delta_swingidx_6gb_p[2]; - thm_down_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_N][2] : - rtw89_8852c_trk_cfg.delta_swingidx_6gb_n[2]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_P][2]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_N][2]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_P][2]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_N][2]; break; case RTW89_CH_6G_BAND_IDX6: case RTW89_CH_6G_BAND_IDX7: - thm_up_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_P][3] : - rtw89_8852c_trk_cfg.delta_swingidx_6ga_p[3]; - thm_down_a = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_N][3] : - rtw89_8852c_trk_cfg.delta_swingidx_6ga_n[3]; - thm_up_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_P][3] : - rtw89_8852c_trk_cfg.delta_swingidx_6gb_p[3]; - thm_down_b = trk ? trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_N][3] : - rtw89_8852c_trk_cfg.delta_swingidx_6gb_n[3]; + thm_up_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_P][3]; + thm_down_a = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_N][3]; + thm_up_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_P][3]; + thm_down_b = trk->delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_N][3]; break; } diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852c_table.c b/drivers/net/wireless/realtek/rtw89/rtw8852c_table.c index 24c390b6f3d3..b4cf497e8524 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852c_table.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852c_table.c @@ -2,6 +2,7 @@ /* Copyright(c) 2019-2022 Realtek Corporation */ +#include "fw.h" #include "phy.h" #include "reg.h" #include "rtw8852c_table.h" @@ -57109,23 +57110,23 @@ const struct rtw89_txpwr_table rtw89_8852c_byr_table = { .load = rtw89_phy_load_txpwr_byrate, }; -const struct rtw89_txpwr_track_cfg rtw89_8852c_trk_cfg = { - .delta_swingidx_6gb_n = _txpwr_track_delta_swingidx_6gb_n, - .delta_swingidx_6gb_p = _txpwr_track_delta_swingidx_6gb_p, - .delta_swingidx_6ga_n = _txpwr_track_delta_swingidx_6ga_n, - .delta_swingidx_6ga_p = _txpwr_track_delta_swingidx_6ga_p, - .delta_swingidx_5gb_n = _txpwr_track_delta_swingidx_5gb_n, - .delta_swingidx_5gb_p = _txpwr_track_delta_swingidx_5gb_p, - .delta_swingidx_5ga_n = _txpwr_track_delta_swingidx_5ga_n, - .delta_swingidx_5ga_p = _txpwr_track_delta_swingidx_5ga_p, - .delta_swingidx_2gb_n = _txpwr_track_delta_swingidx_2gb_n, - .delta_swingidx_2gb_p = _txpwr_track_delta_swingidx_2gb_p, - .delta_swingidx_2ga_n = _txpwr_track_delta_swingidx_2ga_n, - .delta_swingidx_2ga_p = _txpwr_track_delta_swingidx_2ga_p, - .delta_swingidx_2g_cck_b_n = _txpwr_track_delta_swingidx_2g_cck_b_n, - .delta_swingidx_2g_cck_b_p = _txpwr_track_delta_swingidx_2g_cck_b_p, - .delta_swingidx_2g_cck_a_n = _txpwr_track_delta_swingidx_2g_cck_a_n, - .delta_swingidx_2g_cck_a_p = _txpwr_track_delta_swingidx_2g_cck_a_p, +const struct rtw89_fw_txpwr_track_cfg rtw89_8852c_trk_cfg = { + .delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_N] = _txpwr_track_delta_swingidx_6gb_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_6GB_P] = _txpwr_track_delta_swingidx_6gb_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_N] = _txpwr_track_delta_swingidx_6ga_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_6GA_P] = _txpwr_track_delta_swingidx_6ga_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_N] = _txpwr_track_delta_swingidx_5gb_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GB_P] = _txpwr_track_delta_swingidx_5gb_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_N] = _txpwr_track_delta_swingidx_5ga_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_5GA_P] = _txpwr_track_delta_swingidx_5ga_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_N] = &_txpwr_track_delta_swingidx_2gb_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GB_P] = &_txpwr_track_delta_swingidx_2gb_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_N] = &_txpwr_track_delta_swingidx_2ga_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2GA_P] = &_txpwr_track_delta_swingidx_2ga_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_B_N] = &_txpwr_track_delta_swingidx_2g_cck_b_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_B_P] = &_txpwr_track_delta_swingidx_2g_cck_b_p, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_A_N] = &_txpwr_track_delta_swingidx_2g_cck_a_n, + .delta[RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_A_P] = &_txpwr_track_delta_swingidx_2g_cck_a_p, }; const struct rtw89_phy_tssi_dbw_table rtw89_8852c_tssi_dbw_table = { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852c_table.h b/drivers/net/wireless/realtek/rtw89/rtw8852c_table.h index 7c9f3ecdc4e7..69c95f35b535 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852c_table.h +++ b/drivers/net/wireless/realtek/rtw89/rtw8852c_table.h @@ -13,7 +13,7 @@ extern const struct rtw89_phy_table rtw89_8852c_phy_radioa_table; extern const struct rtw89_phy_table rtw89_8852c_phy_radiob_table; extern const struct rtw89_phy_table rtw89_8852c_phy_nctl_table; extern const struct rtw89_phy_tssi_dbw_table rtw89_8852c_tssi_dbw_table; -extern const struct rtw89_txpwr_track_cfg rtw89_8852c_trk_cfg; +extern const struct rtw89_fw_txpwr_track_cfg rtw89_8852c_trk_cfg; extern const struct rtw89_rfe_parms rtw89_8852c_dflt_parms; #endif From 56d32cdc6040440b08edfd5d7262250a721233f8 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 7 Jul 2026 17:10:56 +0800 Subject: [PATCH 0292/1433] wifi: rtw89: set needed firmware elements for early chips transition The early chips including RTL8852A, RTL8851B, RTL8852B and RTL8852C have driver built-in tables, which are not preferred. New firmware is prepared with corresponding tables in firmware elements, so we can start to transition. After this patch, old firmware is still usable, but add a prompt text for users to update firmware. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260707091056.42771-16-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/fw.c | 10 +++++++ drivers/net/wireless/realtek/rtw89/fw.h | 27 ++++++++++++++++--- drivers/net/wireless/realtek/rtw89/rtw8851b.c | 3 ++- drivers/net/wireless/realtek/rtw89/rtw8852a.c | 3 ++- drivers/net/wireless/realtek/rtw89/rtw8852b.c | 3 ++- drivers/net/wireless/realtek/rtw89/rtw8852c.c | 3 ++- 6 files changed, 41 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index b97c6e9c18bc..0e7168605850 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -1562,6 +1562,7 @@ int rtw89_fw_recognize_elements(struct rtw89_dev *rtwdev) u32 unrecognized_elements = chip->needed_fw_elms; const struct rtw89_fw_element_handler *handler; const struct rtw89_fw_element_hdr *hdr; + bool transition; u32 elm_size; u32 elem_id; u32 offset; @@ -1569,6 +1570,9 @@ int rtw89_fw_recognize_elements(struct rtw89_dev *rtwdev) BUILD_BUG_ON(sizeof(chip->needed_fw_elms) * 8 < RTW89_FW_ELEMENT_ID_NUM); + transition = !!((chip->needed_fw_elms & BIT(__RTW89_FW_ELEMENT_ID_INTL_TRANSITION))); + unrecognized_elements &= ~BIT(__RTW89_FW_ELEMENT_ID_INTL_TRANSITION); + offset = rtw89_mfw_get_size(rtwdev); offset = ALIGN(offset, RTW89_FW_ELEMENT_ALIGN); if (offset == 0) @@ -1608,6 +1612,12 @@ int rtw89_fw_recognize_elements(struct rtw89_dev *rtwdev) } if (unrecognized_elements) { + if (transition) { + rtw89_info(rtwdev, "NOTE: This firmware is going to be obsolete!\n" + "Please download the latest firmware from https://gitlab.com/kernel-firmware/linux-firmware.git\n"); + return 0; + } + rtw89_err(rtwdev, "Firmware elements 0x%08x are unrecognized\n", unrecognized_elements); return -ENOENT; diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index a6f3b28b9e33..af126d15a1fb 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -4354,31 +4354,50 @@ enum rtw89_fw_element_id { RTW89_FW_ELEMENT_ID_DIAG_MAC = 28, RTW89_FW_ELEMENT_ID_TX_COMP = 29, + __RTW89_FW_ELEMENT_ID_INTL_TRANSITION, RTW89_FW_ELEMENT_ID_NUM, }; +#define BITS_OF_RTW89_TXPWR_FW_ELEMENTS_TX_SHAPE \ + (BIT(RTW89_FW_ELEMENT_ID_TX_SHAPE_LMT) | \ + BIT(RTW89_FW_ELEMENT_ID_TX_SHAPE_LMT_RU)) + #define BITS_OF_RTW89_TXPWR_FW_ELEMENTS_NO_6GHZ \ (BIT(RTW89_FW_ELEMENT_ID_TXPWR_BYRATE) | \ BIT(RTW89_FW_ELEMENT_ID_TXPWR_LMT_2GHZ) | \ BIT(RTW89_FW_ELEMENT_ID_TXPWR_LMT_5GHZ) | \ BIT(RTW89_FW_ELEMENT_ID_TXPWR_LMT_RU_2GHZ) | \ BIT(RTW89_FW_ELEMENT_ID_TXPWR_LMT_RU_5GHZ) | \ - BIT(RTW89_FW_ELEMENT_ID_TX_SHAPE_LMT) | \ - BIT(RTW89_FW_ELEMENT_ID_TX_SHAPE_LMT_RU)) + BITS_OF_RTW89_TXPWR_FW_ELEMENTS_TX_SHAPE) #define BITS_OF_RTW89_TXPWR_FW_ELEMENTS \ (BITS_OF_RTW89_TXPWR_FW_ELEMENTS_NO_6GHZ | \ BIT(RTW89_FW_ELEMENT_ID_TXPWR_LMT_6GHZ) | \ BIT(RTW89_FW_ELEMENT_ID_TXPWR_LMT_RU_6GHZ)) -#define RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_NO_6GHZ \ +#define RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_BASE \ (BIT(RTW89_FW_ELEMENT_ID_BB_REG) | \ BIT(RTW89_FW_ELEMENT_ID_RADIO_A) | \ BIT(RTW89_FW_ELEMENT_ID_RADIO_B) | \ BIT(RTW89_FW_ELEMENT_ID_RF_NCTL) | \ - BIT(RTW89_FW_ELEMENT_ID_TXPWR_TRK) | \ + BIT(RTW89_FW_ELEMENT_ID_TXPWR_TRK)) + +#define RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_NO_6GHZ \ + (RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_BASE | \ BITS_OF_RTW89_TXPWR_FW_ELEMENTS_NO_6GHZ) +#define RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS \ + (RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_BASE | \ + BITS_OF_RTW89_TXPWR_FW_ELEMENTS) + +#define RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_RTL8852A \ + (RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_NO_6GHZ & \ + ~BITS_OF_RTW89_TXPWR_FW_ELEMENTS_TX_SHAPE) + +#define RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_RTL8851B \ + (RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_NO_6GHZ & \ + ~BIT(RTW89_FW_ELEMENT_ID_RADIO_B)) + #define RTW89_BE_GEN_DEF_NEEDED_FW_ELEMENTS_BASE \ (BIT(RTW89_FW_ELEMENT_ID_BB_REG) | \ BIT(RTW89_FW_ELEMENT_ID_RADIO_A) | \ diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b.c b/drivers/net/wireless/realtek/rtw89/rtw8851b.c index 95437ba9b675..50480f72c96d 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851b.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851b.c @@ -2635,7 +2635,8 @@ const struct rtw89_chip_info rtw8851b_chip_info = { }, .try_ce_fw = true, .bbmcu_nr = 0, - .needed_fw_elms = 0, + .needed_fw_elms = RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_RTL8851B | + BIT(__RTW89_FW_ELEMENT_ID_INTL_TRANSITION), .fw_blacklist = NULL, .fifo_size = 196608, .small_fifo_size = true, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852a.c b/drivers/net/wireless/realtek/rtw89/rtw8852a.c index 78e07276fb2e..0c7794742650 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852a.c @@ -2373,7 +2373,8 @@ const struct rtw89_chip_info rtw8852a_chip_info = { }, .try_ce_fw = false, .bbmcu_nr = 0, - .needed_fw_elms = 0, + .needed_fw_elms = RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_RTL8852A | + BIT(__RTW89_FW_ELEMENT_ID_INTL_TRANSITION), .fw_blacklist = NULL, .fifo_size = 458752, .small_fifo_size = false, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852b.c b/drivers/net/wireless/realtek/rtw89/rtw8852b.c index a8638024b54a..5904127cc836 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852b.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852b.c @@ -968,7 +968,8 @@ const struct rtw89_chip_info rtw8852b_chip_info = { }, .try_ce_fw = true, .bbmcu_nr = 0, - .needed_fw_elms = 0, + .needed_fw_elms = RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS_NO_6GHZ | + BIT(__RTW89_FW_ELEMENT_ID_INTL_TRANSITION), .fw_blacklist = &rtw89_fw_blacklist_default, .fifo_size = 196608, .small_fifo_size = true, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852c.c b/drivers/net/wireless/realtek/rtw89/rtw8852c.c index a82373198356..968666280c89 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852c.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852c.c @@ -3172,7 +3172,8 @@ const struct rtw89_chip_info rtw8852c_chip_info = { }, .try_ce_fw = false, .bbmcu_nr = 0, - .needed_fw_elms = 0, + .needed_fw_elms = RTW89_AX_GEN_DEF_NEEDED_FW_ELEMENTS | + BIT(__RTW89_FW_ELEMENT_ID_INTL_TRANSITION), .fw_blacklist = &rtw89_fw_blacklist_default, .fifo_size = 458752, .small_fifo_size = false, From 6c080026ecc17eecb103f8927c64ea73a74bb818 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Tue, 30 Jun 2026 03:31:17 +0000 Subject: [PATCH 0293/1433] wifi: rtl8xxxu: fix use-after-free from rx_urb_wq on stop rtl8xxxu arms rx_urb_wq from the RX completion path: rtl8xxxu_rx_complete() hands the URB to rtl8xxxu_queue_rx_urb(), which queues it on rx_urb_pending_list and, once the list grows past RTL8XXXU_RX_URB_PENDING_WATER, schedules rx_urb_wq. The worker rtl8xxxu_rx_urb_work() drains rx_urb_pending_list, recovers priv through container_of, and resubmits each URB through rtl8xxxu_submit_rx_urb(), which anchors it on rx_anchor and dereferences priv->udev. rtl8xxxu_stop() cancels the sibling work items (c2hcmd_work, ra_watchdog, update_beacon_work) but never cancels rx_urb_wq, so a worker armed during the last burst of RX traffic can run rtl8xxxu_rx_urb_work() after rtl8xxxu_disconnect() has called ieee80211_free_hw(), which frees priv, producing a use-after-free. The window opens under active RX traffic (pending count above the watermark) followed by a disconnect. There are two teardown races to close: * rtl8xxxu_queue_rx_urb() decided whether to enqueue under rx_urb_lock but called schedule_work() after dropping the lock. A completion that observed shutdown == false and released the lock could then call schedule_work() after rtl8xxxu_stop() had set shutdown and cancel_work_sync() had already returned, arming the worker to run after the teardown. Move schedule_work() under the same !shutdown branch so the arming decision is atomic with the shutdown check. * rtl8xxxu_rx_urb_work() anchors every URB it drained back onto rx_anchor through rtl8xxxu_submit_rx_urb(). A worker still running when usb_kill_anchored_urbs(&priv->rx_anchor) returned would submit a URB that escaped the kill. In rtl8xxxu_stop(), call cancel_work_sync(&priv->rx_urb_wq) before the kill so the worker is drained first. After priv->shutdown is set under rx_urb_lock, completions can no longer queue rx_urb_wq. cancel_work_sync() then drains the last queued or running worker, and the following usb_kill_anchored_urbs() kills the URBs it may have submitted. rtl8xxxu_disconnect() is covered because ieee80211_unregister_hw() guarantees .stop() runs for a live interface before ieee80211_free_hw() frees priv. The probe error path needs no cancel: rx_urb_wq is INIT_WORK()'d there but cannot have been scheduled, since no URB is submitted before ieee80211_register_hw() succeeds. This bug was found by static analysis. Fixes: 26f1fad29ad9 ("New driver: rtl8xxxu (mac80211)") Cc: stable@vger.kernel.org Signed-off-by: Fan Wu Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260630033117.3377-1-fanwu01@zju.edu.cn --- drivers/net/wireless/realtek/rtl8xxxu/core.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/realtek/rtl8xxxu/core.c b/drivers/net/wireless/realtek/rtl8xxxu/core.c index 4e8a4769603c..bddbd0990de7 100644 --- a/drivers/net/wireless/realtek/rtl8xxxu/core.c +++ b/drivers/net/wireless/realtek/rtl8xxxu/core.c @@ -5837,14 +5837,19 @@ static void rtl8xxxu_queue_rx_urb(struct rtl8xxxu_priv *priv, { struct sk_buff *skb; unsigned long flags; - int pending = 0; spin_lock_irqsave(&priv->rx_urb_lock, flags); if (!priv->shutdown) { list_add_tail(&rx_urb->list, &priv->rx_urb_pending_list); priv->rx_urb_pending_count++; - pending = priv->rx_urb_pending_count; + /* + * Arm the worker under rx_urb_lock so this is atomic with the + * shutdown check: moving it out of the lock would let a + * completion arm the work after rtl8xxxu_stop() canceled it. + */ + if (priv->rx_urb_pending_count > RTL8XXXU_RX_URB_PENDING_WATER) + schedule_work(&priv->rx_urb_wq); } else { skb = (struct sk_buff *)rx_urb->urb.context; dev_kfree_skb_irq(skb); @@ -5852,9 +5857,6 @@ static void rtl8xxxu_queue_rx_urb(struct rtl8xxxu_priv *priv, } spin_unlock_irqrestore(&priv->rx_urb_lock, flags); - - if (pending > RTL8XXXU_RX_URB_PENDING_WATER) - schedule_work(&priv->rx_urb_wq); } static void rtl8xxxu_rx_urb_work(struct work_struct *work) @@ -7506,6 +7508,13 @@ static void rtl8xxxu_stop(struct ieee80211_hw *hw, bool suspend) priv->shutdown = true; spin_unlock_irqrestore(&priv->rx_urb_lock, flags); + /* + * Cancel before killing rx_anchor: the worker re-anchors every URB + * it drained via rtl8xxxu_submit_rx_urb(), so a worker still running + * after the kill could submit a URB that escapes it. + */ + cancel_work_sync(&priv->rx_urb_wq); + usb_kill_anchored_urbs(&priv->rx_anchor); usb_kill_anchored_urbs(&priv->tx_anchor); if (priv->usb_interrupts) From 0ba1b366185b770440bf4eee3c4a929bc3cfc0e9 Mon Sep 17 00:00:00 2001 From: Ben Greear Date: Sat, 14 Feb 2026 11:05:09 -0800 Subject: [PATCH 0294/1433] wifi: iwlwifi: Clean dangling pointer in tx path If iwl_txq_gen2_build_tfd fails, we return -1, which will cause calling code to dispose of the skb one way or another. Remove any reference to that skb from the txq entries so that nothing will try to access it later. Signed-off-by: Ben Greear Link: https://patch.msgid.link/20260214190509.2098565-1-greearb@candelatech.com Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/tx-gen2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/tx-gen2.c b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/tx-gen2.c index bda9f807321e..27f4973791d0 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/tx-gen2.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/tx-gen2.c @@ -773,6 +773,8 @@ int iwl_txq_gen2_tx(struct iwl_trans *trans, struct sk_buff *skb, tfd = iwl_txq_gen2_build_tfd(trans, txq, dev_cmd, skb, out_meta); if (!tfd) { + txq->entries[idx].skb = NULL; + txq->entries[idx].cmd = NULL; spin_unlock(&txq->lock); return -1; } From d6061f5f1318a0fc116fa442bea299095d211ad4 Mon Sep 17 00:00:00 2001 From: Praveen Rajendran Date: Fri, 3 Jul 2026 19:37:57 +0530 Subject: [PATCH 0295/1433] wifi: iwlwifi: fw: Fix spelling typo in error-dump.h Correct a minor grammatical error inside the kernel-doc comments of error-dump.h where "configuration" was misspelled as "configuraiton". Signed-off-by: Praveen Rajendran Link: https://patch.msgid.link/20260703140757.3372-1-praveenrajendran2009@gmail.com Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/error-dump.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/error-dump.h b/drivers/net/wireless/intel/iwlwifi/fw/error-dump.h index 07f1240df866..692bb17dfc33 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/error-dump.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/error-dump.h @@ -352,7 +352,7 @@ struct iwl_fw_ini_error_dump_register { * struct iwl_fw_ini_dump_cfg_name - configuration name * @image_type: image type the configuration is related to * @cfg_name_len: length of the configuration name - * @cfg_name: name of the configuraiton + * @cfg_name: name of the configuration */ struct iwl_fw_ini_dump_cfg_name { __le32 image_type; From d418509383b0c884b70814ae85d3ef105a63b940 Mon Sep 17 00:00:00 2001 From: Sumit Garg Date: Thu, 2 Jul 2026 17:28:28 +0530 Subject: [PATCH 0296/1433] wifi: ath12k: Switch to generic PAS TZ APIs Switch ath12k client driver over to generic PAS TZ APIs. Generic PAS TZ service allows to support multiple TZ implementation backends like QTEE based SCM PAS service, OP-TEE based PAS service and any further future TZ backend service. Acked-by: Jeff Johnson Signed-off-by: Sumit Garg Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260702115835.167602-13-sumit.garg@kernel.org Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/Kconfig | 2 +- drivers/net/wireless/ath/ath12k/ahb.c | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/Kconfig b/drivers/net/wireless/ath/ath12k/Kconfig index 4a2b240f967a..0d5d1c55bfc1 100644 --- a/drivers/net/wireless/ath/ath12k/Kconfig +++ b/drivers/net/wireless/ath/ath12k/Kconfig @@ -18,7 +18,7 @@ config ATH12K_AHB bool "Qualcomm ath12k AHB support" depends on ATH12K && REMOTEPROC select QCOM_MDT_LOADER - select QCOM_SCM + select QCOM_PAS help Enable support for Ath12k AHB bus chipsets, example IPQ5332. diff --git a/drivers/net/wireless/ath/ath12k/ahb.c b/drivers/net/wireless/ath/ath12k/ahb.c index 4912172e106e..4944ea7855ca 100644 --- a/drivers/net/wireless/ath/ath12k/ahb.c +++ b/drivers/net/wireless/ath/ath12k/ahb.c @@ -5,7 +5,7 @@ */ #include -#include +#include #include #include #include @@ -419,7 +419,7 @@ static int ath12k_ahb_power_up(struct ath12k_base *ab) if (ab_ahb->scm_auth_enabled) { /* Authenticate FW image using peripheral ID */ - ret = qcom_scm_pas_auth_and_reset(pasid); + ret = qcom_pas_auth_and_reset(pasid); if (ret) { ath12k_err(ab, "failed to boot the remote processor %d\n", ret); goto err_fw2; @@ -484,10 +484,10 @@ static void ath12k_ahb_power_down(struct ath12k_base *ab, bool is_suspend) pasid = (u32_encode_bits(ab_ahb->userpd_id, ATH12K_USERPD_ID_MASK)) | ATH12K_AHB_UPD_SWID; /* Release the firmware */ - ret = qcom_scm_pas_shutdown(pasid); + ret = qcom_pas_shutdown(pasid); if (ret) - ath12k_err(ab, "scm pas shutdown failed for userPD%d\n", - ab_ahb->userpd_id); + ath12k_err(ab, "PAS shutdown failed for userPD%d: %d\n", + ab_ahb->userpd_id, ret); } } From f066e1a93703c5be0fd905109d00587541711c97 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 29 Jun 2026 15:01:17 +0800 Subject: [PATCH 0297/1433] wifi: ath12k: fix dp_link_peer dangling references on AP vdev rollback ath12k_mac_vdev_create() for an AP vdev creates the bss self-peer via ath12k_peer_create(), which finishes by calling ath12k_dp_link_peer_assign() to publish the dp_link_peer in the dp_hw->dp_peers[peerid_index] RCU table, in the dp_peer's link_peers[] array, and in the per-addr rhashtable. If a step after ath12k_peer_create() fails the function jumps to err_peer_del, which open-codes a WMI peer_delete and waits for the unmap / delete_resp events. The wait_for_peer_delete_done() path relies on ath12k_dp_link_peer_unmap_event() freeing the dp_link_peer when the unmap arrives, but err_peer_del never calls ath12k_dp_link_peer_unassign() first. The published references in the dp_hw RCU table, dp_peer->link_peers[] and the rhashtable are left pointing at the dp_link_peer that unmap_event then frees, producing dangling pointers and use-after-free on subsequent lookups. Replace the open-coded sequence with a call to ath12k_peer_delete(), which already does ath12k_dp_link_peer_unassign() before sending the WMI command. This drops the published references before the dp_link_peer is freed, in the same order as the normal teardown path in ath12k_mac_remove_link_interface(). Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Fixes: 5525f12fa671 ("wifi: ath12k: Attach and detach ath12k_dp_link_peer to ath12k_dp_peer") Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260629-ath12k-mlo-peer-delete-race-v2-1-362b25590d19@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 16339469c24c..3b6720b7f3bd 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -10568,22 +10568,8 @@ int ath12k_mac_vdev_create(struct ath12k *ar, struct ath12k_link_vif *arvif) err_peer_del: if (ahvif->vdev_type == WMI_VDEV_TYPE_AP) { - reinit_completion(&ar->peer_delete_done); - - ret = ath12k_wmi_send_peer_delete_cmd(ar, arvif->bssid, - arvif->vdev_id); - if (ret) { - ath12k_warn(ar->ab, "failed to delete peer vdev_id %d addr %pM\n", - arvif->vdev_id, arvif->bssid); - goto err_dp_peer_del; - } - - ret = ath12k_wait_for_peer_delete_done(ar, arvif->vdev_id, - arvif->bssid); - if (ret) - goto err_dp_peer_del; - - ar->num_peers--; + /* ignore return value: propagate the original error */ + ath12k_peer_delete(ar, arvif->vdev_id, arvif->bssid); } err_dp_peer_del: From 01cc0f59aac3574b6a7b02493a76bc8ed0aa758f Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 29 Jun 2026 15:01:18 +0800 Subject: [PATCH 0298/1433] wifi: ath12k: fix MLO peer delete race ath12k_peer_mlo_link_peers_delete() sends WMI peer_delete for every link before waiting for any peer_unmap / peer_delete_resp event. The shared per-radio completion ar->peer_delete_done could not disambiguate which peer a response was for: every call to ath12k_peer_delete_send() did reinit_completion(&ar->peer_delete_done), so when an event for the first link arrived between two sends it raised the count to 1 and the second send promptly cleared it; the wait for the second link then timed out with Timeout in receiving peer delete response Replace the shared completion with a per-radio waiter list, with each pending ath12k_peer_delete() caller queueing an ath12k_peer_delete_wait carrying its (vdev_id, addr) and a private struct completion. ath12k_peer_delete_resp_event() matches the response against the list under ar->data_lock and signals the matching waiter. Also correct the endian conversion in ath12k_peer_delete_resp_event() logging, and add the missing \n in some logging. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Fixes: 8e6f8bc28603 ("wifi: ath12k: Add MLO station state change handling") Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260629-ath12k-mlo-peer-delete-race-v2-2-362b25590d19@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.c | 2 +- drivers/net/wireless/ath/ath12k/core.h | 5 +- drivers/net/wireless/ath/ath12k/mac.c | 2 +- drivers/net/wireless/ath/ath12k/peer.c | 132 ++++++++++++++++++++----- drivers/net/wireless/ath/ath12k/peer.h | 14 ++- drivers/net/wireless/ath/ath12k/wmi.c | 16 +-- 6 files changed, 132 insertions(+), 39 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index 0e7c732f8222..fb599caa3aab 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -1527,7 +1527,7 @@ static void ath12k_core_pre_reconfigure_recovery(struct ath12k_base *ab) complete_all(&ar->scan.completed); complete(&ar->scan.on_channel); complete(&ar->peer_assoc_done); - complete(&ar->peer_delete_done); + ath12k_peer_delete_wait_flush(ar); complete(&ar->install_key_done); complete(&ar->vdev_setup_done); complete(&ar->vdev_delete_done); diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index fc5127b5c1a3..1436ff4316e7 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -665,7 +665,8 @@ struct ath12k { /* protects the radio specific data like debug stats, ppdu_stats_info stats, * vdev_stop_status info, scan data, ath12k_sta info, ath12k_link_vif info, - * channel context data, survey info, test mode data, regd_channel_update_queue. + * channel context data, survey info, test mode data, regd_channel_update_queue, + * peer_delete_waits. */ spinlock_t data_lock; @@ -687,7 +688,7 @@ struct ath12k { u8 radio_idx; struct completion peer_assoc_done; - struct completion peer_delete_done; + struct list_head peer_delete_waits; int install_key_status; struct completion install_key_done; diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 3b6720b7f3bd..6667019a0049 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -15047,11 +15047,11 @@ static void ath12k_mac_setup(struct ath12k *ar) spin_lock_init(&ar->dp.ppdu_list_lock); INIT_LIST_HEAD(&ar->arvifs); INIT_LIST_HEAD(&ar->dp.ppdu_stats_info); + INIT_LIST_HEAD(&ar->peer_delete_waits); init_completion(&ar->vdev_setup_done); init_completion(&ar->vdev_delete_done); init_completion(&ar->peer_assoc_done); - init_completion(&ar->peer_delete_done); init_completion(&ar->install_key_done); init_completion(&ar->bss_survey_done); init_completion(&ar->scan.started); diff --git a/drivers/net/wireless/ath/ath12k/peer.c b/drivers/net/wireless/ath/ath12k/peer.c index c222bdaa333c..5084e8c42a3f 100644 --- a/drivers/net/wireless/ath/ath12k/peer.c +++ b/drivers/net/wireless/ath/ath12k/peer.c @@ -9,6 +9,55 @@ #include "debug.h" #include "debugfs.h" +static void ath12k_peer_delete_wait_register(struct ath12k *ar, + struct ath12k_peer_delete_wait *wait, + u32 vdev_id, const u8 *addr) +{ + wait->vdev_id = vdev_id; + ether_addr_copy(wait->addr, addr); + init_completion(&wait->done); + + spin_lock_bh(&ar->data_lock); + list_add(&wait->list, &ar->peer_delete_waits); + spin_unlock_bh(&ar->data_lock); +} + +static void ath12k_peer_delete_wait_unregister(struct ath12k *ar, + struct ath12k_peer_delete_wait *wait) +{ + spin_lock_bh(&ar->data_lock); + list_del(&wait->list); + spin_unlock_bh(&ar->data_lock); +} + +void ath12k_peer_delete_resp_signal(struct ath12k *ar, u32 vdev_id, const u8 *addr) +{ + struct ath12k_peer_delete_wait *wait; + + guard(spinlock_bh)(&ar->data_lock); + + list_for_each_entry(wait, &ar->peer_delete_waits, list) { + if (wait->vdev_id == vdev_id && + ether_addr_equal(wait->addr, addr)) { + complete(&wait->done); + return; + } + } + + ath12k_warn(ar->ab, "failed to find link peer with vdev id %u addr %pM\n", + vdev_id, addr); +} + +void ath12k_peer_delete_wait_flush(struct ath12k *ar) +{ + struct ath12k_peer_delete_wait *wait; + + spin_lock_bh(&ar->data_lock); + list_for_each_entry(wait, &ar->peer_delete_waits, list) + complete(&wait->done); + spin_unlock_bh(&ar->data_lock); +} + static int ath12k_wait_for_dp_link_peer_common(struct ath12k_base *ab, int vdev_id, const u8 *addr, bool expect_mapped) { @@ -62,20 +111,19 @@ static int ath12k_wait_for_peer_deleted(struct ath12k *ar, int vdev_id, const u8 return ath12k_wait_for_dp_link_peer_common(ar->ab, vdev_id, addr, false); } -int ath12k_wait_for_peer_delete_done(struct ath12k *ar, u32 vdev_id, - const u8 *addr) +int ath12k_wait_for_peer_delete_done(struct ath12k *ar, + struct ath12k_peer_delete_wait *wait) { - int ret; unsigned long time_left; + int ret; - ret = ath12k_wait_for_peer_deleted(ar, vdev_id, addr); + ret = ath12k_wait_for_peer_deleted(ar, wait->vdev_id, wait->addr); if (ret) { - ath12k_warn(ar->ab, "failed wait for peer deleted"); + ath12k_warn(ar->ab, "failed wait for peer deleted\n"); return ret; } - time_left = wait_for_completion_timeout(&ar->peer_delete_done, - 3 * HZ); + time_left = wait_for_completion_timeout(&wait->done, 3 * HZ); if (time_left == 0) { ath12k_warn(ar->ab, "Timeout in receiving peer delete response\n"); return -ETIMEDOUT; @@ -91,8 +139,6 @@ static int ath12k_peer_delete_send(struct ath12k *ar, u32 vdev_id, const u8 *add lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy); - reinit_completion(&ar->peer_delete_done); - ret = ath12k_wmi_send_peer_delete_cmd(ar, addr, vdev_id); if (ret) { ath12k_warn(ab, @@ -106,6 +152,7 @@ static int ath12k_peer_delete_send(struct ath12k *ar, u32 vdev_id, const u8 *add int ath12k_peer_delete(struct ath12k *ar, u32 vdev_id, u8 *addr) { + struct ath12k_peer_delete_wait wait; int ret; lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy); @@ -114,17 +161,25 @@ int ath12k_peer_delete(struct ath12k *ar, u32 vdev_id, u8 *addr) &(ath12k_ar_to_ah(ar)->dp_hw), vdev_id, addr, ar->hw_link_id); + /* + * Register the stack waiter before sending so the resp_event for + * this peer cannot arrive while no waiter is queued. + */ + ath12k_peer_delete_wait_register(ar, &wait, vdev_id, addr); + ret = ath12k_peer_delete_send(ar, vdev_id, addr); if (ret) - return ret; + goto out; - ret = ath12k_wait_for_peer_delete_done(ar, vdev_id, addr); + ret = ath12k_wait_for_peer_delete_done(ar, &wait); if (ret) - return ret; + goto out; ar->num_peers--; - return 0; +out: + ath12k_peer_delete_wait_unregister(ar, &wait); + return ret; } static int ath12k_wait_for_peer_created(struct ath12k *ar, int vdev_id, const u8 *addr) @@ -184,22 +239,26 @@ int ath12k_peer_create(struct ath12k *ar, struct ath12k_link_vif *arvif, peer = ath12k_dp_link_peer_find_by_vdev_and_addr(dp, arg->vdev_id, arg->peer_addr); if (!peer) { + struct ath12k_peer_delete_wait wait; + spin_unlock_bh(&dp->dp_lock); ath12k_warn(ar->ab, "failed to find peer %pM on vdev %i after creation\n", arg->peer_addr, arg->vdev_id); - reinit_completion(&ar->peer_delete_done); + ath12k_peer_delete_wait_register(ar, &wait, arg->vdev_id, + arg->peer_addr); ret = ath12k_wmi_send_peer_delete_cmd(ar, arg->peer_addr, arg->vdev_id); if (ret) { ath12k_warn(ar->ab, "failed to delete peer vdev_id %d addr %pM\n", arg->vdev_id, arg->peer_addr); + ath12k_peer_delete_wait_unregister(ar, &wait); return ret; } - ret = ath12k_wait_for_peer_delete_done(ar, arg->vdev_id, - arg->peer_addr); + ret = ath12k_wait_for_peer_delete_done(ar, &wait); + ath12k_peer_delete_wait_unregister(ar, &wait); if (ret) return ret; @@ -283,13 +342,14 @@ u16 ath12k_peer_ml_alloc(struct ath12k_hw *ah) int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_sta *ahsta) { + DECLARE_BITMAP(registered, IEEE80211_MLD_MAX_NUM_LINKS); struct ieee80211_sta *sta = ath12k_ahsta_to_sta(ahsta); struct ath12k_hw *ah = ahvif->ah; struct ath12k_link_vif *arvif; struct ath12k_link_sta *arsta; + int ret, err_ret = 0; unsigned long links; struct ath12k *ar; - int ret, err_ret = 0; u8 link_id; lockdep_assert_wiphy(ah->hw->wiphy); @@ -297,8 +357,19 @@ int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_st if (!sta->mlo) return -EINVAL; - /* FW expects delete of all link peers at once before waiting for reception - * of peer unmap or delete responses + struct ath12k_peer_delete_wait *waits __free(kfree) = + kzalloc_objs(*waits, IEEE80211_MLD_MAX_NUM_LINKS); + if (!waits) + return -ENOMEM; + + bitmap_zero(registered, IEEE80211_MLD_MAX_NUM_LINKS); + + /* + * Firmware expects delete of all link peers at once before waiting + * for reception of peer unmap or delete responses. Phase 1 registers + * a per-link stack waiter and sends WMI peer delete for every + * link; the resp_event handler matches each response to its + * (vdev_id, addr) waiter on ar->peer_delete_waits. */ links = ahsta->links_map; for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { @@ -318,29 +389,36 @@ int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_st arvif->vdev_id, arsta->addr, ar->hw_link_id); + ath12k_peer_delete_wait_register(ar, &waits[link_id], + arvif->vdev_id, arsta->addr); + ret = ath12k_peer_delete_send(ar, arvif->vdev_id, arsta->addr); if (ret) { ath12k_warn(ar->ab, "failed to delete peer vdev_id %d addr %pM ret %d\n", arvif->vdev_id, arsta->addr, ret); err_ret = ret; + ath12k_peer_delete_wait_unregister(ar, &waits[link_id]); continue; } + + set_bit(link_id, registered); } - /* Ensure all link peers are deleted and unmapped */ + /* + * Phase 2: wait for unmap + delete_resp on each registered link + * and tear down the waiter. + */ links = ahsta->links_map; for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { + if (!test_bit(link_id, registered)) + continue; + arvif = wiphy_dereference(ah->hw->wiphy, ahvif->link[link_id]); - arsta = wiphy_dereference(ah->hw->wiphy, ahsta->link[link_id]); - if (!arvif || !arsta) - continue; - ar = arvif->ar; - if (!ar) - continue; - ret = ath12k_wait_for_peer_delete_done(ar, arvif->vdev_id, arsta->addr); + ret = ath12k_wait_for_peer_delete_done(ar, &waits[link_id]); + ath12k_peer_delete_wait_unregister(ar, &waits[link_id]); if (ret) { err_ret = ret; continue; diff --git a/drivers/net/wireless/ath/ath12k/peer.h b/drivers/net/wireless/ath/ath12k/peer.h index 49d89796bc46..9343944c2b8e 100644 --- a/drivers/net/wireless/ath/ath12k/peer.h +++ b/drivers/net/wireless/ath/ath12k/peer.h @@ -9,13 +9,23 @@ #include "dp_peer.h" +struct ath12k_peer_delete_wait { + struct list_head list; + u32 vdev_id; + u8 addr[ETH_ALEN]; + struct completion done; +}; + +void ath12k_peer_delete_resp_signal(struct ath12k *ar, u32 vdev_id, const u8 *addr); +void ath12k_peer_delete_wait_flush(struct ath12k *ar); + void ath12k_peer_cleanup(struct ath12k *ar, u32 vdev_id); int ath12k_peer_delete(struct ath12k *ar, u32 vdev_id, u8 *addr); int ath12k_peer_create(struct ath12k *ar, struct ath12k_link_vif *arvif, struct ieee80211_sta *sta, struct ath12k_wmi_peer_create_arg *arg); -int ath12k_wait_for_peer_delete_done(struct ath12k *ar, u32 vdev_id, - const u8 *addr); +int ath12k_wait_for_peer_delete_done(struct ath12k *ar, + struct ath12k_peer_delete_wait *wait); int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_sta *ahsta); struct ath12k_ml_peer *ath12k_peer_ml_find(struct ath12k_hw *ah, const u8 *addr); diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index ad739bffcf88..da14cfda0528 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -7095,25 +7095,29 @@ static void ath12k_peer_delete_resp_event(struct ath12k_base *ab, struct sk_buff { struct wmi_peer_delete_resp_event peer_del_resp; struct ath12k *ar; + u32 vdev_id; if (ath12k_pull_peer_del_resp_ev(ab, skb, &peer_del_resp) != 0) { - ath12k_warn(ab, "failed to extract peer delete resp"); + ath12k_warn(ab, "failed to extract peer delete resp\n"); return; } + vdev_id = le32_to_cpu(peer_del_resp.vdev_id); + rcu_read_lock(); - ar = ath12k_mac_get_ar_by_vdev_id(ab, le32_to_cpu(peer_del_resp.vdev_id)); + ar = ath12k_mac_get_ar_by_vdev_id(ab, vdev_id); if (!ar) { - ath12k_warn(ab, "invalid vdev id in peer delete resp ev %d", - peer_del_resp.vdev_id); + ath12k_warn(ab, "invalid vdev id in peer delete resp ev %d\n", + vdev_id); rcu_read_unlock(); return; } - complete(&ar->peer_delete_done); + ath12k_peer_delete_resp_signal(ar, vdev_id, + peer_del_resp.peer_macaddr.addr); rcu_read_unlock(); ath12k_dbg(ab, ATH12K_DBG_WMI, "peer delete resp for vdev id %d addr %pM\n", - peer_del_resp.vdev_id, peer_del_resp.peer_macaddr.addr); + vdev_id, peer_del_resp.peer_macaddr.addr); } static void ath12k_vdev_delete_resp_event(struct ath12k_base *ab, From 6385fd09a399805e82e50b228f1af318316aaeb0 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Sat, 11 Jul 2026 08:31:10 -0700 Subject: [PATCH 0299/1433] wifi: ath12k: Fix ath12k_wifi7_mac_op_tx() style issues Commit e47d6c9bb416 ("wifi: ath12k: Advertise multicast Ethernet encapsulation offload support") introduced a few style issues. ath12k-check reports: drivers/net/wireless/ath/ath12k/wifi7/hw.c:1042: line length of 92 exceeds 90 columns And automated review did not like one if/else that did not use braces for a single statement that also included a block comment. Fix these issues. Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260711-ath12k_wifi7_mac_op_tx-line-length-v1-1-10e4899b98ef@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wifi7/hw.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c index e5bf9d218104..d54c2a6d83b2 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c @@ -1031,18 +1031,22 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, if (cb_flags & ATH12K_SKB_HW_80211_ENCAP) { /* - * skb->data may be modified for the iova_mask devices. - * It is better to use skb_copy() for such devices - * to avoid any potential skb corruption related issues. + * skb->data may be modified for the + * iova_mask devices. It is better to + * use skb_copy() for such devices to + * avoid any potential skb corruption + * related issues. */ - if (tmp_dp->hw_params->iova_mask) + if (tmp_dp->hw_params->iova_mask) { msdu_copied = skb_copy(skb, GFP_ATOMIC); - else + } else { /* - * ath12k_wifi7_dp_tx() should treat cloned HW-encap - * Ethernet multicast frames as read-only. + * ath12k_wifi7_dp_tx() should + * treat cloned HW-encap Ethernet + * multicast frames as read-only. */ msdu_copied = skb_clone(skb, GFP_ATOMIC); + } if (!msdu_copied) { ath12k_err(ar->ab, "skb copy/clone failure link_id 0x%X vdevid 0x%X\n", From 6b31c3451b12cfc5e86a41898014cb62d2a377b1 Mon Sep 17 00:00:00 2001 From: Thiraviyam Mariyappan Date: Mon, 22 Jun 2026 11:53:24 +0530 Subject: [PATCH 0300/1433] wifi: ath12k: Reduce RX SRNG interrupt timer threshold to 200us Currently when RX traffic is low or intermittent, the RX SRNG interrupt mitigation logic defers packet processing for up to 500us via HAL_SRNG_INT_TIMER_THRESHOLD_RX. This causes excessive RX servicing delay, leading to increased end-to-end latency and degraded TCP performance in low-concurrency scenarios. In single-client single-stream TCP tests using 5G EHT160 (NSS 2x2) mode, throughput drops to ~400 Mbps DL and UL instead of the expected ~600 Mbps. In addition, UDP UL end-to-end latency measured in 5G VHT80 (NSS 4x4) mode increases by up to ~48% (~570us versus ~270us) across frame sizes from 76 to 1518 bytes in uplink and bidirectional traffic, indicating delayed RX servicing under sparse traffic conditions. To address this issue, reduce the RX SRNG interrupt timer threshold from 500us to 200us so that received packets are serviced more promptly under low-rate and intermittent RX traffic. With this change, single-client single-stream TCP throughput in EHT160 is restored to expected levels ~600 Mbps TCP DL/UL and UDP UL end-to-end latency in VHT80 returns to baseline values ~270us across all tested frame sizes. Under high RX load, no throughput regression is observed, as RX rings are already serviced frequently. The primary implication is a modest increase in RX interrupt frequency under low traffic, with no observed functional, stability, or performance regressions on tested platforms. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01181-QCAHKSWPL_SILICONZ-1 Signed-off-by: Thiraviyam Mariyappan Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260622062324.758533-1-thiraviyam.mariyappan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/hal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/hal.h b/drivers/net/wireless/ath/ath12k/hal.h index ba2c22fb2982..3a874db7968e 100644 --- a/drivers/net/wireless/ath/ath12k/hal.h +++ b/drivers/net/wireless/ath/ath12k/hal.h @@ -1024,7 +1024,7 @@ enum hal_wbm_rel_desc_type { /* Interrupt mitigation - timer threshold in us */ #define HAL_SRNG_INT_TIMER_THRESHOLD_TX 1000 -#define HAL_SRNG_INT_TIMER_THRESHOLD_RX 500 +#define HAL_SRNG_INT_TIMER_THRESHOLD_RX 200 #define HAL_SRNG_INT_TIMER_THRESHOLD_OTHER 256 enum hal_srng_mac_type { From 9eb29fd47595e8128775f8ac57ca671238cb798a Mon Sep 17 00:00:00 2001 From: Wei Zhang Date: Sun, 28 Jun 2026 23:15:28 -0700 Subject: [PATCH 0301/1433] wifi: ath12k: fix rx_mpdu_start layout for QCC2072 QCC2072's rx_mpdu_start TLV has a different field layout from QCN9274. Reusing struct rx_mpdu_start_qcn9274 in hal_rx_desc_qcc2072 causes the RX datapath to read the wrong offsets for info2, info4, pn[] and phy_ppdu_id, producing corrupted sequence number, PN, ppdu_id and mpdu-info flags (encrypted, fragment, addr2/addr4 valid). Add a dedicated struct rx_mpdu_start_qcc2072 that matches the actual hardware descriptor layout, and use it in hal_rx_desc_qcc2072. Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00188-QCACOLSWPL_V1_TO_SILICONZ-1 Fixes: 28badc78142e ("wifi: ath12k: add HAL descriptor and ops for QCC2072") Signed-off-by: Wei Zhang Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260629061529.1993932-1-wei.zhang@oss.qualcomm.com Signed-off-by: Jeff Johnson --- .../wireless/ath/ath12k/wifi7/hal_rx_desc.h | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_rx_desc.h b/drivers/net/wireless/ath/ath12k/wifi7/hal_rx_desc.h index 0d19a9cbb68c..6d69851e529d 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_rx_desc.h +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_rx_desc.h @@ -140,6 +140,38 @@ struct rx_mpdu_start_qcn9274 { __le32 res1; } __packed; +struct rx_mpdu_start_qcc2072 { + __le32 info0; + __le32 info2; + __le32 reo_queue_desc_lo; + __le32 info1; + __le32 pn[4]; + __le32 info4; + __le32 peer_meta_data; + __le16 ast_index; + __le16 sw_peer_id; + __le16 info3; + __le16 phy_ppdu_id; + __le32 info5; + __le32 info6; + __le16 frame_ctrl; + __le16 duration; + u8 addr1[ETH_ALEN]; + u8 addr2[ETH_ALEN]; + u8 addr3[ETH_ALEN]; + __le16 seq_ctrl; + u8 addr4[ETH_ALEN]; + __le16 qos_ctrl; + __le32 ht_ctrl; + __le32 info7; + __le32 res0; + __le32 res1; + __le32 res2; + __le32 info8; + __le32 res3; + __le32 res4; +} __packed; + #define QCN9274_MPDU_START_SELECT_MPDU_START_TAG BIT(0) #define QCN9274_MPDU_START_SELECT_INFO0_REO_QUEUE_DESC_LO BIT(1) #define QCN9274_MPDU_START_SELECT_INFO1_PN_31_0 BIT(2) @@ -1492,7 +1524,7 @@ struct hal_rx_desc_qcc2072 { struct rx_msdu_end_qcn9274 msdu_end; u8 rx_padding0[RX_BE_PADDING0_BYTES]; __le32 mpdu_start_tag; - struct rx_mpdu_start_qcn9274 mpdu_start; + struct rx_mpdu_start_qcc2072 mpdu_start; struct rx_pkt_hdr_tlv_qcc2072 pkt_hdr_tlv; u8 msdu_payload[]; }; From b93c1dc446847165fd4cafc3d4d1c67ee6c6e987 Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Wed, 1 Jul 2026 09:46:11 +0530 Subject: [PATCH 0302/1433] wifi: ath12k: add QMI capability negotiation for dynamic memory mode On AHB platforms, firmware operates in two modes: fixed-memory mode where firmware uses hardcoded addresses for memory regions such as BDF and does not request HOST_DDR memory from the host, and dynamic-memory mode where firmware expects the host to provide memory addresses including HOST_DDR after the Q6 read-only region and relies on host allocation for all memory types. Introduce QMI capability negotiation to support both modes. Add a new QMI PHY capability flag dynamic_ddr_support which is advertised by firmware to indicate it supports dynamic memory mode. When the host detects this capability, set the dynamic_mem_support flag in the host capability message to signal the host is ready to provide dynamic memory allocation. This triggers firmware to send the HOST_DDR memory request and use the host-provided address. For backward compatibility, if firmware doesn't advertise dynamic_ddr_support, the firmware continues to operate in fixed-memory mode where firmware uses predefined addresses. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Tested-on: IPQ5332 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Signed-off-by: Aaradhana Sahu Link: https://patch.msgid.link/20260701041611.3077185-1-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/qmi.c | 49 +++++++++++++++++++++++++-- drivers/net/wireless/ath/ath12k/qmi.h | 8 +++-- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index 1f3efcc16ac3..cabdd544bbc7 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -506,6 +506,24 @@ static const struct qmi_elem_info qmi_wlanfw_host_cap_req_msg_v01_ei[] = { .offset = offsetof(struct qmi_wlanfw_host_cap_req_msg_v01, feature_list), }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x33, + .offset = offsetof(struct qmi_wlanfw_host_cap_req_msg_v01, + dynamic_mem_support_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x33, + .offset = offsetof(struct qmi_wlanfw_host_cap_req_msg_v01, + dynamic_mem_support), + }, { .data_type = QMI_EOTI, .array_type = NO_ARRAY, @@ -602,6 +620,24 @@ static const struct qmi_elem_info qmi_wlanfw_phy_cap_resp_msg_v01_ei[] = { .offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01, single_chip_mlo_support), }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x17, + .offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01, + dynamic_ddr_support_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x17, + .offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01, + dynamic_ddr_support), + }, { .data_type = QMI_EOTI, .array_type = NO_ARRAY, @@ -2254,6 +2290,11 @@ int ath12k_qmi_host_cap_send(struct ath12k_base *ab) if (ret < 0) goto out; + if (ab->qmi.dynamic_ddr_support) { + req.dynamic_mem_support_valid = 1; + req.dynamic_mem_support = 1; + } + ret = qmi_txn_init(&ab->qmi.handle, &txn, qmi_wlanfw_host_cap_resp_msg_v01_ei, &resp); if (ret < 0) @@ -2325,11 +2366,15 @@ static void ath12k_qmi_phy_cap_send(struct ath12k_base *ab) ab->qmi.num_radios = resp.num_phy; + if (resp.dynamic_ddr_support_valid) + ab->qmi.dynamic_ddr_support = resp.dynamic_ddr_support; + ath12k_dbg(ab, ATH12K_DBG_QMI, - "phy capability resp valid %u single_chip_mlo_support %u valid %u num_phy %u valid %u board_id %u\n", + "phy capability resp valid %u single_chip_mlo_support %u valid %u num_phy %u valid %u board_id %u dynamic_ddr_valid %u dynamic_ddr_support %u\n", resp.single_chip_mlo_support_valid, resp.single_chip_mlo_support, resp.num_phy_valid, resp.num_phy, - resp.board_id_valid, resp.board_id); + resp.board_id_valid, resp.board_id, resp.dynamic_ddr_support_valid, + resp.dynamic_ddr_support); return; diff --git a/drivers/net/wireless/ath/ath12k/qmi.h b/drivers/net/wireless/ath/ath12k/qmi.h index 80a9b42a2548..cbe5be30053a 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.h +++ b/drivers/net/wireless/ath/ath12k/qmi.h @@ -153,9 +153,10 @@ struct ath12k_qmi { struct m3_mem_region aux_uc_mem; unsigned int service_ins_id; struct dev_mem_info dev_mem[ATH12K_QMI_WLFW_MAX_DEV_MEM_NUM_V01]; + u8 dynamic_ddr_support; }; -#define QMI_WLANFW_HOST_CAP_REQ_MSG_V01_MAX_LEN 261 +#define QMI_WLANFW_HOST_CAP_REQ_MSG_V01_MAX_LEN 265 #define QMI_WLANFW_HOST_CAP_REQ_V01 0x0034 #define QMI_WLFW_MAX_NUM_GPIO_V01 32 #define QMI_WLANFW_MAX_PLATFORM_NAME_LEN_V01 64 @@ -253,7 +254,8 @@ struct qmi_wlanfw_host_cap_req_msg_v01 { struct wlfw_host_mlo_chip_info_s_v01 mlo_chip_info[QMI_WLFW_MAX_NUM_MLO_CHIPS_V01]; u8 feature_list_valid; u64 feature_list; - + u8 dynamic_mem_support_valid; + u8 dynamic_mem_support; }; struct qmi_wlanfw_host_cap_resp_msg_v01 { @@ -274,6 +276,8 @@ struct qmi_wlanfw_phy_cap_resp_msg_v01 { u32 board_id; u8 single_chip_mlo_support_valid; u8 single_chip_mlo_support; + u8 dynamic_ddr_support_valid; + u8 dynamic_ddr_support; }; #define QMI_WLANFW_IND_REGISTER_REQ_MSG_V01_MAX_LEN 54 From 12b09e478aa7459b7893a695ef77682202f2da83 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Wed, 1 Jul 2026 09:49:13 +0800 Subject: [PATCH 0303/1433] wifi: ath11k: cap out-of-range rx MCS instead of leaving bogus rate ath11k can receive HT/VHT/HE frames whose reported MCS is above the maximum that can be expressed in the corresponding mac80211 rate space (e.g. an HE frame reported with MCS 12, while HE tops out at MCS 11). The frame itself is valid and decodes correctly, but for such a frame ath11k_dp_rx_h_rate() leaves rx_status->rate_idx set to the out-of-range value and never assigns rx_status->encoding, so it stays RX_ENC_LEGACY from the ath11k_dp_rx_h_ppdu() initialization. Once that frame reaches mac80211 it trips the rate sanity check and the frame is dropped with a splat: ath11k_pci 0000:03:00.0: Received with invalid mcs in HE mode 12 WARNING: CPU: 0 PID: 0 at net/mac80211/rx.c:5433 ieee80211_rx_list+0xb0a/0xe90 [mac80211] Dropping the frame would discard otherwise valid data, so instead cap the reported MCS to the maximum the rate space can express and deliver the frame. Set rx_status->encoding before the range check and assign rate_idx from the capped value, so a frame with an out-of-range MCS no longer leaves partial or bogus rate metadata behind. Also downgrade the logging level since they are not treated as invalid frames now. The only loss is that such a frame is reported as the capped MCS in the rx rate statistics. Tested-on: WCN6855 hw2.1 PCI WLAN.HSP.1.1-03125-QCAHSPSWPL_V1_V2_SILICONZ_LITE-3.6510.41 Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices") Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260701-ath11k-invalid-he-mcs-v1-1-7d963080c079@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/dp_rx.c | 32 ++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/dp_rx.c b/drivers/net/wireless/ath/ath11k/dp_rx.c index 9e90d8e3f155..896d30181754 100644 --- a/drivers/net/wireless/ath/ath11k/dp_rx.c +++ b/drivers/net/wireless/ath/ath11k/dp_rx.c @@ -2334,10 +2334,10 @@ static void ath11k_dp_rx_h_rate(struct ath11k *ar, struct hal_rx_desc *rx_desc, case RX_MSDU_START_PKT_TYPE_11N: rx_status->encoding = RX_ENC_HT; if (rate_mcs > ATH11K_HT_MCS_MAX) { - ath11k_warn(ar->ab, - "Received with invalid mcs in HT mode %d\n", - rate_mcs); - break; + ath11k_dbg(ar->ab, ATH11K_DBG_DP_RX, + "Received HT frame with out-of-range mcs %d, capping to %d\n", + rate_mcs, ATH11K_HT_MCS_MAX); + rate_mcs = ATH11K_HT_MCS_MAX; } rx_status->rate_idx = rate_mcs + (8 * (nss - 1)); if (sgi) @@ -2346,13 +2346,13 @@ static void ath11k_dp_rx_h_rate(struct ath11k *ar, struct hal_rx_desc *rx_desc, break; case RX_MSDU_START_PKT_TYPE_11AC: rx_status->encoding = RX_ENC_VHT; - rx_status->rate_idx = rate_mcs; if (rate_mcs > ATH11K_VHT_MCS_MAX) { - ath11k_warn(ar->ab, - "Received with invalid mcs in VHT mode %d\n", - rate_mcs); - break; + ath11k_dbg(ar->ab, ATH11K_DBG_DP_RX, + "Received VHT frame with out-of-range mcs %d, capping to %d\n", + rate_mcs, ATH11K_VHT_MCS_MAX); + rate_mcs = ATH11K_VHT_MCS_MAX; } + rx_status->rate_idx = rate_mcs; rx_status->nss = nss; if (sgi) rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI; @@ -2362,14 +2362,14 @@ static void ath11k_dp_rx_h_rate(struct ath11k *ar, struct hal_rx_desc *rx_desc, rx_status->enc_flags |= RX_ENC_FLAG_LDPC; break; case RX_MSDU_START_PKT_TYPE_11AX: - rx_status->rate_idx = rate_mcs; - if (rate_mcs > ATH11K_HE_MCS_MAX) { - ath11k_warn(ar->ab, - "Received with invalid mcs in HE mode %d\n", - rate_mcs); - break; - } rx_status->encoding = RX_ENC_HE; + if (rate_mcs > ATH11K_HE_MCS_MAX) { + ath11k_dbg(ar->ab, ATH11K_DBG_DP_RX, + "Received HE frame with out-of-range mcs %d, capping to %d\n", + rate_mcs, ATH11K_HE_MCS_MAX); + rate_mcs = ATH11K_HE_MCS_MAX; + } + rx_status->rate_idx = rate_mcs; rx_status->nss = nss; rx_status->he_gi = ath11k_mac_he_gi_to_nl80211_he_gi(sgi); rx_status->bw = ath11k_mac_bw_to_mac80211_bw(bw); From 67105abd6195a685a84dcb8a5daf54a1f4bfdb60 Mon Sep 17 00:00:00 2001 From: Dawei Feng Date: Wed, 24 Jun 2026 16:44:04 +0800 Subject: [PATCH 0304/1433] wifi: iwlwifi: dvm: fix memory leak in iwl_op_mode_dvm_start() In iwl_op_mode_dvm_start(), jumping to out_free_eeprom currently bypasses the out_free_eeprom_blob label. Consequently, error paths triggered after successfully parsing the EEPROM free priv->nvm_data but leak priv->eeprom_blob. Fix this memory leak by reordering the error handling labels so that out_free_eeprom falls through to out_free_eeprom_blob. The bug was first flagged by an experimental analysis tool we are developing for kernel memory-management bugs while analyzing v6.13-rc1. The tool is still under development and is not yet publicly available. Manual inspection confirms that the bug is still present in v7.1-rc6. An x86_64 allyesconfig build showed no new warnings. As we do not have supported Intel DVM wireless hardware and firmware to test with, no runtime testing was able to be performed. Cc: stable@vger.kernel.org Signed-off-by: Dawei Feng Link: https://patch.msgid.link/20260624084404.570703-1-dawei.feng@seu.edu.cn Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/dvm/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/main.c b/drivers/net/wireless/intel/iwlwifi/dvm/main.c index ca5a8140908a..6bd5b6d84b2a 100644 --- a/drivers/net/wireless/intel/iwlwifi/dvm/main.c +++ b/drivers/net/wireless/intel/iwlwifi/dvm/main.c @@ -1511,10 +1511,10 @@ static struct iwl_op_mode *iwl_op_mode_dvm_start(struct iwl_trans *trans, priv->workqueue = NULL; out_uninit_drv: iwl_uninit_drv(priv); -out_free_eeprom_blob: - kfree(priv->eeprom_blob); out_free_eeprom: kfree(priv->nvm_data); +out_free_eeprom_blob: + kfree(priv->eeprom_blob); out_leave_trans: iwl_trans_op_mode_leave(priv->trans); out_free_hw: From 94280e6e0c69a5327e377803732f494d8b5a74cd Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Wed, 1 Apr 2026 11:05:55 +0800 Subject: [PATCH 0305/1433] iwlwifi: dvm: add missing cleaup for on error path In iwlagn_tx_agg_start(), call iwlagn_dealloc_agg_txq() to clear bit on error path. Signed-off-by: Haoxiang Li Link: https://patch.msgid.link/20260401030555.541685-1-lihaoxiang@isrc.iscas.ac.cn Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/dvm/tx.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/tx.c b/drivers/net/wireless/intel/iwlwifi/dvm/tx.c index a7806776a51e..1a0167f67c7b 100644 --- a/drivers/net/wireless/intel/iwlwifi/dvm/tx.c +++ b/drivers/net/wireless/intel/iwlwifi/dvm/tx.c @@ -604,8 +604,10 @@ int iwlagn_tx_agg_start(struct iwl_priv *priv, struct ieee80211_vif *vif, } ret = iwl_sta_tx_modify_enable_tid(priv, sta_id, tid); - if (ret) + if (ret) { + iwlagn_dealloc_agg_txq(priv, txq_id); return ret; + } spin_lock_bh(&priv->sta_lock); tid_data = &priv->tid_data[sta_id][tid]; From 73b01e57ed3e3d6102c0cbcb21f62086c5429437 Mon Sep 17 00:00:00 2001 From: Jeff Chen Date: Fri, 5 Jun 2026 10:33:35 +0800 Subject: [PATCH 0306/1433] wifi: nxp: add nxpwifi driver for IW61x Add support for the NXP IW61x wireless devices. The nxpwifi driver implements a full-MAC design and integrates with cfg80211 for configuration and control, supporting both station (STA) and access point (AP) modes. The driver provides a firmware-based command/event interface using TLV messages, with the core handling command processing, event dispatching, and device lifecycle management. A SDIO transport layer is implemented to support IW61x devices. Key features include: - 802.11n/ac/ax (HT/VHT/HE) capability support - Scan, association, and connection management - Data path handling for TX/RX, including aggregation and reorder - WMM QoS support and traffic prioritization - 802.11h (DFS/TPC) support for regulatory compliance - cfg80211 integration for STA and AP operations - Debugfs and ethtool support - Wake-on-LAN support The driver translates cfg80211 configuration into firmware commands and implements required data path processing in software where needed. Signed-off-by: Jeff Chen --- MAINTAINERS | 7 + drivers/net/wireless/Kconfig | 1 + drivers/net/wireless/Makefile | 1 + drivers/net/wireless/nxp/Kconfig | 17 + drivers/net/wireless/nxp/Makefile | 3 + drivers/net/wireless/nxp/nxpwifi/11ac.c | 280 ++ drivers/net/wireless/nxp/nxpwifi/11ac.h | 33 + drivers/net/wireless/nxp/nxpwifi/11ax.c | 594 +++ drivers/net/wireless/nxp/nxpwifi/11ax.h | 73 + drivers/net/wireless/nxp/nxpwifi/11h.c | 339 ++ drivers/net/wireless/nxp/nxpwifi/11n.c | 837 ++++ drivers/net/wireless/nxp/nxpwifi/11n.h | 158 + drivers/net/wireless/nxp/nxpwifi/11n_aggr.c | 251 ++ drivers/net/wireless/nxp/nxpwifi/11n_aggr.h | 21 + .../net/wireless/nxp/nxpwifi/11n_rxreorder.c | 826 ++++ .../net/wireless/nxp/nxpwifi/11n_rxreorder.h | 71 + drivers/net/wireless/nxp/nxpwifi/Kconfig | 22 + drivers/net/wireless/nxp/nxpwifi/Makefile | 39 + drivers/net/wireless/nxp/nxpwifi/cfg.h | 1019 +++++ drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 3931 +++++++++++++++++ drivers/net/wireless/nxp/nxpwifi/cfg80211.h | 18 + drivers/net/wireless/nxp/nxpwifi/cfp.c | 478 ++ drivers/net/wireless/nxp/nxpwifi/cmdevt.c | 1310 ++++++ drivers/net/wireless/nxp/nxpwifi/cmdevt.h | 122 + drivers/net/wireless/nxp/nxpwifi/debugfs.c | 1094 +++++ drivers/net/wireless/nxp/nxpwifi/ethtool.c | 58 + drivers/net/wireless/nxp/nxpwifi/fw.h | 2475 +++++++++++ drivers/net/wireless/nxp/nxpwifi/ie.c | 480 ++ drivers/net/wireless/nxp/nxpwifi/init.c | 607 +++ drivers/net/wireless/nxp/nxpwifi/join.c | 787 ++++ drivers/net/wireless/nxp/nxpwifi/main.c | 1673 +++++++ drivers/net/wireless/nxp/nxpwifi/main.h | 1429 ++++++ drivers/net/wireless/nxp/nxpwifi/scan.c | 2695 +++++++++++ drivers/net/wireless/nxp/nxpwifi/sdio.c | 2327 ++++++++++ drivers/net/wireless/nxp/nxpwifi/sdio.h | 340 ++ drivers/net/wireless/nxp/nxpwifi/sta_cfg.c | 1165 +++++ drivers/net/wireless/nxp/nxpwifi/sta_cmd.c | 3383 ++++++++++++++ drivers/net/wireless/nxp/nxpwifi/sta_event.c | 862 ++++ drivers/net/wireless/nxp/nxpwifi/sta_rx.c | 242 + drivers/net/wireless/nxp/nxpwifi/sta_tx.c | 190 + drivers/net/wireless/nxp/nxpwifi/txrx.c | 352 ++ drivers/net/wireless/nxp/nxpwifi/uap_cmd.c | 1256 ++++++ drivers/net/wireless/nxp/nxpwifi/uap_event.c | 488 ++ drivers/net/wireless/nxp/nxpwifi/uap_txrx.c | 478 ++ drivers/net/wireless/nxp/nxpwifi/util.c | 1381 ++++++ drivers/net/wireless/nxp/nxpwifi/util.h | 155 + drivers/net/wireless/nxp/nxpwifi/wmm.c | 1318 ++++++ drivers/net/wireless/nxp/nxpwifi/wmm.h | 77 + 48 files changed, 35763 insertions(+) create mode 100644 drivers/net/wireless/nxp/Kconfig create mode 100644 drivers/net/wireless/nxp/Makefile create mode 100644 drivers/net/wireless/nxp/nxpwifi/11ac.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/11ac.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/11ax.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/11ax.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/11h.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n_aggr.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n_aggr.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/Kconfig create mode 100644 drivers/net/wireless/nxp/nxpwifi/Makefile create mode 100644 drivers/net/wireless/nxp/nxpwifi/cfg.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/cfg80211.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/cfg80211.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/cfp.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/cmdevt.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/cmdevt.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/debugfs.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/ethtool.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/fw.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/ie.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/init.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/join.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/main.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/main.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/scan.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/sdio.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/sdio.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_cfg.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_cmd.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_event.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_rx.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_tx.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/txrx.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/uap_cmd.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/uap_event.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/uap_txrx.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/util.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/util.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/wmm.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/wmm.h diff --git a/MAINTAINERS b/MAINTAINERS index 52f1a55eca99..fc8f9e38b6ba 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19536,6 +19536,13 @@ S: Maintained F: Documentation/devicetree/bindings/ptp/nxp,ptp-netc.yaml F: drivers/ptp/ptp_netc.c +NXP NXPWIFI WIRELESS DRIVER +M: Jeff Chen +R: Francesco Dolcini +L: linux-wireless@vger.kernel.org +S: Maintained +F: drivers/net/wireless/nxp/nxpwifi + NXP PF5300/PF5301/PF5302 PMIC REGULATOR DEVICE DRIVER M: Woodrow Douglass S: Maintained diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig index c6599594dc99..4d7b81182925 100644 --- a/drivers/net/wireless/Kconfig +++ b/drivers/net/wireless/Kconfig @@ -27,6 +27,7 @@ source "drivers/net/wireless/intersil/Kconfig" source "drivers/net/wireless/marvell/Kconfig" source "drivers/net/wireless/mediatek/Kconfig" source "drivers/net/wireless/microchip/Kconfig" +source "drivers/net/wireless/nxp/Kconfig" source "drivers/net/wireless/purelifi/Kconfig" source "drivers/net/wireless/ralink/Kconfig" source "drivers/net/wireless/realtek/Kconfig" diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile index e1c4141c6004..0c6b3cc719db 100644 --- a/drivers/net/wireless/Makefile +++ b/drivers/net/wireless/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_WLAN_VENDOR_INTERSIL) += intersil/ obj-$(CONFIG_WLAN_VENDOR_MARVELL) += marvell/ obj-$(CONFIG_WLAN_VENDOR_MEDIATEK) += mediatek/ obj-$(CONFIG_WLAN_VENDOR_MICROCHIP) += microchip/ +obj-$(CONFIG_WLAN_VENDOR_NXP) += nxp/ obj-$(CONFIG_WLAN_VENDOR_PURELIFI) += purelifi/ obj-$(CONFIG_WLAN_VENDOR_QUANTENNA) += quantenna/ obj-$(CONFIG_WLAN_VENDOR_RALINK) += ralink/ diff --git a/drivers/net/wireless/nxp/Kconfig b/drivers/net/wireless/nxp/Kconfig new file mode 100644 index 000000000000..68b32d4536e5 --- /dev/null +++ b/drivers/net/wireless/nxp/Kconfig @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-only +config WLAN_VENDOR_NXP + bool "NXP devices" + default y + help + If you have a wireless card belonging to this class, say Y. + + Note that the answer to this question doesn't directly affect the + kernel: saying N will just cause the configurator to skip all the + questions about these cards. If you say Y, you will be asked for + your specific card in the following questions. + +if WLAN_VENDOR_NXP + +source "drivers/net/wireless/nxp/nxpwifi/Kconfig" + +endif # WLAN_VENDOR_NXP diff --git a/drivers/net/wireless/nxp/Makefile b/drivers/net/wireless/nxp/Makefile new file mode 100644 index 000000000000..27b41a0afdd2 --- /dev/null +++ b/drivers/net/wireless/nxp/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_NXPWIFI) += nxpwifi/ diff --git a/drivers/net/wireless/nxp/nxpwifi/11ac.c b/drivers/net/wireless/nxp/nxpwifi/11ac.c new file mode 100644 index 000000000000..117d06c35401 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11ac.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi 802.11ac helpers + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "fw.h" +#include "main.h" +#include "11ac.h" + +/* Map VHT MCS/NSS to highest data rate (Mbps), long GI. */ +static const u16 max_rate_lgi_80MHZ[8][3] = { + {0x124, 0x15F, 0x186}, /* NSS = 1 */ + {0x249, 0x2BE, 0x30C}, /* NSS = 2 */ + {0x36D, 0x41D, 0x492}, /* NSS = 3 */ + {0x492, 0x57C, 0x618}, /* NSS = 4 */ + {0x5B6, 0x6DB, 0x79E}, /* NSS = 5 */ + {0x6DB, 0x83A, 0x0}, /* NSS = 6 */ + {0x7FF, 0x999, 0xAAA}, /* NSS = 7 */ + {0x924, 0xAF8, 0xC30} /* NSS = 8 */ +}; + +static const u16 max_rate_lgi_160MHZ[8][3] = { + {0x249, 0x2BE, 0x30C}, /* NSS = 1 */ + {0x492, 0x57C, 0x618}, /* NSS = 2 */ + {0x6DB, 0x83A, 0x0}, /* NSS = 3 */ + {0x924, 0xAF8, 0xC30}, /* NSS = 4 */ + {0xB6D, 0xDB6, 0xF3C}, /* NSS = 5 */ + {0xDB6, 0x1074, 0x1248}, /* NSS = 6 */ + {0xFFF, 0x1332, 0x1554}, /* NSS = 7 */ + {0x1248, 0x15F0, 0x1860} /* NSS = 8 */ +}; + +/* Convert 2-bit MCS map to highest long-GI VHT data rate. */ +static u16 +nxpwifi_convert_mcsmap_to_maxrate(struct nxpwifi_private *priv, + u16 bands, u16 mcs_map) +{ + u8 i, nss, mcs; + u16 max_rate = 0; + u32 usr_vht_cap_info = 0; + struct nxpwifi_adapter *adapter = priv->adapter; + + if (bands & BAND_AAC) + usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_a; + else + usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_bg; + + /* Find max supported NSS. */ + nss = 1; + for (i = 1; i <= 8; i++) { + mcs = GET_VHTNSSMCS(mcs_map, i); + if (mcs < IEEE80211_VHT_MCS_NOT_SUPPORTED) + nss = i; + } + mcs = GET_VHTNSSMCS(mcs_map, nss); + + /* If not supported, fall back to 0-9. */ + if (mcs == IEEE80211_VHT_MCS_NOT_SUPPORTED) + mcs = IEEE80211_VHT_MCS_SUPPORT_0_9; + + if (u32_get_bits(usr_vht_cap_info, IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK)) { + /* Support 160 MHz. */ + max_rate = max_rate_lgi_160MHZ[nss - 1][mcs]; + if (!max_rate) + /* MCS9 not supported in NSS6. */ + max_rate = max_rate_lgi_160MHZ[nss - 1][mcs - 1]; + } else { + max_rate = max_rate_lgi_80MHZ[nss - 1][mcs]; + if (!max_rate) + /* MCS9 not supported in NSS3. */ + max_rate = max_rate_lgi_80MHZ[nss - 1][mcs - 1]; + } + + return max_rate; +} + +static void +nxpwifi_fill_vht_cap_info(struct nxpwifi_private *priv, + struct ieee80211_vht_cap *vht_cap, u16 bands) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (bands & BAND_A) + vht_cap->vht_cap_info = + cpu_to_le32(adapter->usr_dot_11ac_dev_cap_a); + else + vht_cap->vht_cap_info = + cpu_to_le32(adapter->usr_dot_11ac_dev_cap_bg); +} + +void +nxpwifi_fill_vht_cap_tlv(struct nxpwifi_private *priv, + struct ieee80211_vht_cap *vht_cap, u16 bands) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u16 mcs_map_user, mcs_map_resp, mcs_map_result; + u16 mcs_user, mcs_resp, nss, tmp; + + /* Fill VHT capability info. */ + nxpwifi_fill_vht_cap_info(priv, vht_cap, bands); + + /* RX MCS set: min(user, AP). */ + mcs_map_user = GET_DEVRXMCSMAP(adapter->usr_dot_11ac_mcs_support); + mcs_map_resp = le16_to_cpu(vht_cap->supp_mcs.rx_mcs_map); + mcs_map_result = 0; + + for (nss = 1; nss <= 8; nss++) { + mcs_user = GET_VHTNSSMCS(mcs_map_user, nss); + mcs_resp = GET_VHTNSSMCS(mcs_map_resp, nss); + + if (mcs_user == IEEE80211_VHT_MCS_NOT_SUPPORTED || + mcs_resp == IEEE80211_VHT_MCS_NOT_SUPPORTED) + SET_VHTNSSMCS(mcs_map_result, nss, + IEEE80211_VHT_MCS_NOT_SUPPORTED); + else + SET_VHTNSSMCS(mcs_map_result, nss, + min(mcs_user, mcs_resp)); + } + + vht_cap->supp_mcs.rx_mcs_map = cpu_to_le16(mcs_map_result); + + tmp = nxpwifi_convert_mcsmap_to_maxrate(priv, bands, mcs_map_result); + vht_cap->supp_mcs.rx_highest = cpu_to_le16(tmp); + + /* TX MCS set: min(user, AP). */ + mcs_map_user = GET_DEVTXMCSMAP(adapter->usr_dot_11ac_mcs_support); + mcs_map_resp = le16_to_cpu(vht_cap->supp_mcs.tx_mcs_map); + mcs_map_result = 0; + + for (nss = 1; nss <= 8; nss++) { + mcs_user = GET_VHTNSSMCS(mcs_map_user, nss); + mcs_resp = GET_VHTNSSMCS(mcs_map_resp, nss); + if (mcs_user == IEEE80211_VHT_MCS_NOT_SUPPORTED || + mcs_resp == IEEE80211_VHT_MCS_NOT_SUPPORTED) + SET_VHTNSSMCS(mcs_map_result, nss, + IEEE80211_VHT_MCS_NOT_SUPPORTED); + else + SET_VHTNSSMCS(mcs_map_result, nss, + min(mcs_user, mcs_resp)); + } + + vht_cap->supp_mcs.tx_mcs_map = cpu_to_le16(mcs_map_result); + + tmp = nxpwifi_convert_mcsmap_to_maxrate(priv, bands, mcs_map_result); + vht_cap->supp_mcs.tx_highest = cpu_to_le16(tmp); +} + +int nxpwifi_cmd_append_11ac_tlv(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, + u8 **buffer) +{ + struct nxpwifi_ie_types_vhtcap *vht_cap; + struct nxpwifi_ie_types_oper_mode_ntf *oper_ntf; + struct ieee_types_oper_mode_ntf *ieee_oper_ntf; + struct nxpwifi_ie_types_vht_oper *vht_op; + struct nxpwifi_adapter *adapter = priv->adapter; + u8 supp_chwd_set; + u32 usr_vht_cap_info; + int ret_len = 0; + + if (bss_desc->bss_band & BAND_A) + usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_a; + else + usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_bg; + + /* VHT Capabilities element. */ + if (bss_desc->bcn_vht_cap) { + vht_cap = (struct nxpwifi_ie_types_vhtcap *)*buffer; + memset(vht_cap, 0, sizeof(*vht_cap)); + vht_cap->header.type = cpu_to_le16(WLAN_EID_VHT_CAPABILITY); + vht_cap->header.len = + cpu_to_le16(sizeof(struct ieee80211_vht_cap)); + memcpy((u8 *)vht_cap + sizeof(struct nxpwifi_ie_types_header), + (u8 *)bss_desc->bcn_vht_cap, + le16_to_cpu(vht_cap->header.len)); + + nxpwifi_fill_vht_cap_tlv(priv, &vht_cap->vht_cap, + bss_desc->bss_band); + *buffer += sizeof(*vht_cap); + ret_len += sizeof(*vht_cap); + } + + /* VHT Operation element. */ + if (bss_desc->bcn_vht_oper) { + if (priv->bss_mode == NL80211_IFTYPE_STATION) { + vht_op = (struct nxpwifi_ie_types_vht_oper *)*buffer; + memset(vht_op, 0, sizeof(*vht_op)); + vht_op->header.type = + cpu_to_le16(WLAN_EID_VHT_OPERATION); + vht_op->header.len = cpu_to_le16(sizeof(*vht_op) - + sizeof(struct nxpwifi_ie_types_header)); + memcpy((u8 *)vht_op + + sizeof(struct nxpwifi_ie_types_header), + (u8 *)bss_desc->bcn_vht_oper, + le16_to_cpu(vht_op->header.len)); + + /* Negotiate channel width; keep peer's center freq. */ + supp_chwd_set = u32_get_bits(usr_vht_cap_info, + IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK); + + switch (supp_chwd_set) { + case 0: + vht_op->chan_width = + min_t(u8, IEEE80211_VHT_CHANWIDTH_80MHZ, + bss_desc->bcn_vht_oper->chan_width); + break; + case 1: + vht_op->chan_width = + min_t(u8, IEEE80211_VHT_CHANWIDTH_160MHZ, + bss_desc->bcn_vht_oper->chan_width); + break; + case 2: + vht_op->chan_width = + min_t(u8, IEEE80211_VHT_CHANWIDTH_80P80MHZ, + bss_desc->bcn_vht_oper->chan_width); + break; + default: + vht_op->chan_width = + IEEE80211_VHT_CHANWIDTH_USE_HT; + break; + } + + *buffer += sizeof(*vht_op); + ret_len += sizeof(*vht_op); + } + } + + /* Operating Mode Notification element. */ + if (bss_desc->oper_mode) { + ieee_oper_ntf = bss_desc->oper_mode; + oper_ntf = (void *)*buffer; + memset(oper_ntf, 0, sizeof(*oper_ntf)); + oper_ntf->header.type = cpu_to_le16(WLAN_EID_OPMODE_NOTIF); + oper_ntf->header.len = cpu_to_le16(sizeof(u8)); + oper_ntf->oper_mode = ieee_oper_ntf->oper_mode; + *buffer += sizeof(*oper_ntf); + ret_len += sizeof(*oper_ntf); + } + + return ret_len; +} + +int nxpwifi_cmd_11ac_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_11ac_vht_cfg *cfg) +{ + struct host_cmd_11ac_vht_cfg *vhtcfg = &cmd->params.vht_cfg; + + cmd->command = cpu_to_le16(HOST_CMD_11AC_CFG); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_11ac_vht_cfg) + + S_DS_GEN); + vhtcfg->action = cpu_to_le16(cmd_action); + vhtcfg->band_config = cfg->band_config; + vhtcfg->misc_config = cfg->misc_config; + vhtcfg->cap_info = cpu_to_le32(cfg->cap_info); + vhtcfg->mcs_tx_set = cpu_to_le32(cfg->mcs_tx_set); + vhtcfg->mcs_rx_set = cpu_to_le32(cfg->mcs_rx_set); + + return 0; +} + +/* Initialize BlockAck parameters for 11ac. */ +void nxpwifi_set_11ac_ba_params(struct nxpwifi_private *priv) +{ + priv->add_ba_param.timeout = NXPWIFI_DEFAULT_BLOCK_ACK_TIMEOUT; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + priv->add_ba_param.tx_win_size = + NXPWIFI_11AC_UAP_AMPDU_DEF_TXWINSIZE; + priv->add_ba_param.rx_win_size = + NXPWIFI_11AC_UAP_AMPDU_DEF_RXWINSIZE; + } else { + priv->add_ba_param.tx_win_size = + NXPWIFI_11AC_STA_AMPDU_DEF_TXWINSIZE; + priv->add_ba_param.rx_win_size = + NXPWIFI_11AC_STA_AMPDU_DEF_RXWINSIZE; + } +} diff --git a/drivers/net/wireless/nxp/nxpwifi/11ac.h b/drivers/net/wireless/nxp/nxpwifi/11ac.h new file mode 100644 index 000000000000..edc01b35d5b8 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11ac.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * nxpwifi: 802.11ac (VHT) definitions + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_11AC_H_ +#define _NXPWIFI_11AC_H_ + +#define VHT_CFG_2GHZ BIT(0) +#define VHT_CFG_5GHZ BIT(1) + +enum vht_cfg_misc_config { + VHT_CAP_TX_OPERATION = 1, + VHT_CAP_ASSOCIATION, + VHT_CAP_UAP_ONLY +}; + +#define DEFAULT_VHT_MCS_SET 0xfffe +#define DISABLE_VHT_MCS_SET 0xffff + +#define VHT_BW_80_160_80P80 BIT(2) + +int nxpwifi_cmd_append_11ac_tlv(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, + u8 **buffer); +int nxpwifi_cmd_11ac_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_11ac_vht_cfg *cfg); +void nxpwifi_fill_vht_cap_tlv(struct nxpwifi_private *priv, + struct ieee80211_vht_cap *vht_cap, u16 bands); +#endif /* _NXPWIFI_11AC_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/11ax.c b/drivers/net/wireless/nxp/nxpwifi/11ax.c new file mode 100644 index 000000000000..cc47c435eb70 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11ax.c @@ -0,0 +1,594 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* nxpwifi: 802.11ax (HE) support + * Copyright (C) 2011-2024 NXP + */ + +#include "cfg.h" +#include "fw.h" +#include "main.h" +#include "11ax.h" + +void nxpwifi_update_11ax_cap(struct nxpwifi_adapter *adapter, + struct hw_spec_extension *hw_he_cap) +{ + struct nxpwifi_private *priv; + struct nxpwifi_ie_types_he_cap *he_cap = NULL; + struct nxpwifi_ie_types_he_cap *user_he_cap = NULL; + u8 header_len = sizeof(struct nxpwifi_ie_types_header); + u16 data_len = le16_to_cpu(hw_he_cap->header.len); + bool he_cap_2g = false; + int i; + + if ((data_len + header_len) > sizeof(adapter->hw_he_cap)) { + nxpwifi_dbg(adapter, ERROR, + "hw_he_cap too big, len=%d\n", + data_len); + return; + } + + he_cap = (struct nxpwifi_ie_types_he_cap *)hw_he_cap; + + if (he_cap->he_phy_cap[0] & + (AX_2G_40MHZ_SUPPORT | AX_2G_20MHZ_SUPPORT)) { + adapter->hw_2g_he_cap_len = data_len + header_len; + memcpy(adapter->hw_2g_he_cap, (u8 *)hw_he_cap, + adapter->hw_2g_he_cap_len); + adapter->fw_bands |= BAND_GAX; + he_cap_2g = true; + nxpwifi_dbg_dump(adapter, CMD_D, "2.4G HE capability element ", + adapter->hw_2g_he_cap, + adapter->hw_2g_he_cap_len); + } else { + adapter->hw_he_cap_len = data_len + header_len; + memcpy(adapter->hw_he_cap, (u8 *)hw_he_cap, + adapter->hw_he_cap_len); + adapter->fw_bands |= BAND_AAX; + nxpwifi_dbg_dump(adapter, CMD_D, "5G HE capability element ", + adapter->hw_he_cap, + adapter->hw_he_cap_len); + } + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + + if (he_cap_2g) { + priv->user_2g_he_cap_len = adapter->hw_2g_he_cap_len; + memcpy(priv->user_2g_he_cap, adapter->hw_2g_he_cap, + sizeof(adapter->hw_2g_he_cap)); + user_he_cap = (struct nxpwifi_ie_types_he_cap *) + priv->user_2g_he_cap; + } else { + priv->user_he_cap_len = adapter->hw_he_cap_len; + memcpy(priv->user_he_cap, adapter->hw_he_cap, + sizeof(adapter->hw_he_cap)); + user_he_cap = (struct nxpwifi_ie_types_he_cap *) + priv->user_he_cap; + } + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) + user_he_cap->he_mac_cap[0] &= + ~HE_MAC_CAP_TWT_RESP_SUPPORT; + else + user_he_cap->he_mac_cap[0] &= + ~HE_MAC_CAP_TWT_REQ_SUPPORT; + } + + adapter->is_hw_11ax_capable = true; +} + +bool nxpwifi_11ax_bandconfig_allowed(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + u16 bss_band = bss_desc->bss_band; + + if (bss_desc->disable_11n) + return false; + + if (bss_band & BAND_G) + return (priv->config_bands & BAND_GAX); + else if (bss_band & BAND_A) + return (priv->config_bands & BAND_AAX); + + return false; +} + +int nxpwifi_fill_he_cap_tlv(struct nxpwifi_private *priv, + struct nxpwifi_ie_types_he_cap *he_cap, + u16 bands) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ie_types_he_cap *hw_he_cap = NULL; + u16 rx_nss, tx_nss; + u8 nss; + u16 cfg_value; + u16 hw_value; + int ret_len; + + if (bands & BAND_A) { + memcpy(he_cap, priv->user_he_cap, priv->user_he_cap_len); + hw_he_cap = (struct nxpwifi_ie_types_he_cap *)adapter->hw_he_cap; + ret_len = priv->user_he_cap_len; + } else { + memcpy(he_cap, priv->user_2g_he_cap, priv->user_2g_he_cap_len); + hw_he_cap = (struct nxpwifi_ie_types_he_cap *)adapter->hw_2g_he_cap; + ret_len = priv->user_2g_he_cap_len; + } + + if (bands & BAND_A) { + rx_nss = GET_RXMCSSUPP(adapter->user_htstream >> 8); + tx_nss = GET_TXMCSSUPP(adapter->user_htstream >> 8) & 0x0f; + } else { + rx_nss = GET_RXMCSSUPP(adapter->user_htstream); + tx_nss = GET_TXMCSSUPP(adapter->user_htstream) & 0x0f; + } + + for (nss = 1; nss <= 8; nss++) { + cfg_value = nxpwifi_get_he_nss_mcs(he_cap->rx_mcs_80, nss); + hw_value = nxpwifi_get_he_nss_mcs(hw_he_cap->rx_mcs_80, nss); + if (rx_nss != 0 && nss > rx_nss) + cfg_value = NO_NSS_SUPPORT; + if (hw_value == NO_NSS_SUPPORT || cfg_value == NO_NSS_SUPPORT) + nxpwifi_set_he_nss_mcs(&he_cap->rx_mcs_80, nss, + NO_NSS_SUPPORT); + else + nxpwifi_set_he_nss_mcs(&he_cap->rx_mcs_80, nss, + min(cfg_value, hw_value)); + } + + for (nss = 1; nss <= 8; nss++) { + cfg_value = nxpwifi_get_he_nss_mcs(he_cap->tx_mcs_80, nss); + hw_value = nxpwifi_get_he_nss_mcs(hw_he_cap->tx_mcs_80, nss); + if (tx_nss != 0 && nss > tx_nss) + cfg_value = NO_NSS_SUPPORT; + if (hw_value == NO_NSS_SUPPORT || cfg_value == NO_NSS_SUPPORT) + nxpwifi_set_he_nss_mcs(&he_cap->tx_mcs_80, nss, + NO_NSS_SUPPORT); + else + nxpwifi_set_he_nss_mcs(&he_cap->tx_mcs_80, nss, + min(cfg_value, hw_value)); + } + + return ret_len; +} + +int nxpwifi_cmd_append_11ax_tlv(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, + u8 **buffer) +{ + struct nxpwifi_ie_types_he_cap *he_cap = NULL; + int ret_len; + + if (!bss_desc->bcn_he_cap) + return -EOPNOTSUPP; + + he_cap = (struct nxpwifi_ie_types_he_cap *)*buffer; + ret_len = nxpwifi_fill_he_cap_tlv(priv, he_cap, bss_desc->bss_band); + *buffer += ret_len; + + return ret_len; +} + +int nxpwifi_cmd_11ax_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_11ax_he_cfg *ax_cfg) +{ + struct host_cmd_11ax_cfg *he_cfg = &cmd->params.ax_cfg; + u16 cmd_size; + struct nxpwifi_ie_types_header *header; + + cmd->command = cpu_to_le16(HOST_CMD_11AX_CFG); + cmd_size = sizeof(struct host_cmd_11ax_cfg) + S_DS_GEN; + + he_cfg->action = cpu_to_le16(cmd_action); + he_cfg->band_config = ax_cfg->band; + + if (ax_cfg->he_cap_cfg.len && + ax_cfg->he_cap_cfg.ext_id == WLAN_EID_EXT_HE_CAPABILITY) { + header = (struct nxpwifi_ie_types_header *)he_cfg->tlv; + header->type = cpu_to_le16(ax_cfg->he_cap_cfg.id); + header->len = cpu_to_le16(ax_cfg->he_cap_cfg.len); + memcpy(he_cfg->tlv + sizeof(*header), + &ax_cfg->he_cap_cfg.ext_id, + ax_cfg->he_cap_cfg.len); + cmd_size += (sizeof(*header) + ax_cfg->he_cap_cfg.len); + } + + cmd->size = cpu_to_le16(cmd_size); + + return 0; +} + +int nxpwifi_ret_11ax_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + struct nxpwifi_11ax_he_cfg *ax_cfg) +{ + struct host_cmd_11ax_cfg *he_cfg = &resp->params.ax_cfg; + struct nxpwifi_ie_types_header *header; + u16 left_len, tlv_type, tlv_len; + u8 ext_id; + struct nxpwifi_11ax_he_cap_cfg *he_cap = &ax_cfg->he_cap_cfg; + + left_len = le16_to_cpu(resp->size) - sizeof(*he_cfg) - S_DS_GEN; + header = (struct nxpwifi_ie_types_header *)he_cfg->tlv; + + while (left_len > sizeof(*header)) { + tlv_type = le16_to_cpu(header->type); + tlv_len = le16_to_cpu(header->len); + + if (tlv_type == TLV_TYPE_EXTENSION_ID) { + ext_id = *((u8 *)header + sizeof(*header) + 1); + if (ext_id == WLAN_EID_EXT_HE_CAPABILITY) { + he_cap->id = tlv_type; + he_cap->len = tlv_len; + memcpy((u8 *)&he_cap->ext_id, + (u8 *)header + sizeof(*header) + 1, + tlv_len); + if (he_cfg->band_config & BIT(1)) { + memcpy(priv->user_he_cap, + (u8 *)header, + sizeof(*header) + tlv_len); + priv->user_he_cap_len = + sizeof(*header) + tlv_len; + } else { + memcpy(priv->user_2g_he_cap, + (u8 *)header, + sizeof(*header) + tlv_len); + priv->user_2g_he_cap_len = + sizeof(*header) + tlv_len; + } + } + } + + left_len -= (sizeof(*header) + tlv_len); + header = (struct nxpwifi_ie_types_header *)((u8 *)header + + sizeof(*header) + + tlv_len); + } + + return 0; +} + +int nxpwifi_cmd_11ax_cmd(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_11ax_cmd_cfg *ax_cmd) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_11ax_cmd *he_cmd = &cmd->params.ax_cmd; + u16 cmd_size; + struct nxpwifi_11ax_sr_cmd *sr_cmd; + struct nxpwifi_ie_types_data *tlv; + struct nxpwifi_11ax_beam_cmd *beam_cmd; + struct nxpwifi_11ax_htc_cmd *htc_cmd; + struct nxpwifi_11ax_txomi_cmd *txmoi_cmd; + struct nxpwifi_11ax_toltime_cmd *toltime_cmd; + struct nxpwifi_11ax_txop_cmd *txop_cmd; + struct nxpwifi_11ax_set_bsrp_cmd *set_bsrp_cmd; + struct nxpwifi_11ax_llde_cmd *llde_cmd; + + cmd->command = cpu_to_le16(HOST_CMD_11AX_CMD); + cmd_size = sizeof(struct host_cmd_11ax_cmd) + S_DS_GEN; + + he_cmd->action = cpu_to_le16(cmd_action); + he_cmd->sub_id = cpu_to_le16(ax_cmd->sub_id); + + switch (ax_cmd->sub_command) { + case NXPWIFI_11AXCMD_SR_SUBID: + sr_cmd = (struct nxpwifi_11ax_sr_cmd *)&ax_cmd->param; + + tlv = (struct nxpwifi_ie_types_data *)he_cmd->val; + tlv->header.type = cpu_to_le16(sr_cmd->type); + tlv->header.len = cpu_to_le16(sr_cmd->len); + memcpy(tlv->data, sr_cmd->param.obss_pd_offset.offset, + sr_cmd->len); + cmd_size += (sizeof(tlv->header) + sr_cmd->len); + break; + case NXPWIFI_11AXCMD_BEAM_SUBID: + beam_cmd = (struct nxpwifi_11ax_beam_cmd *)&ax_cmd->param; + + he_cmd->val[0] = beam_cmd->value; + cmd_size += sizeof(*beam_cmd); + break; + case NXPWIFI_11AXCMD_HTC_SUBID: + htc_cmd = (struct nxpwifi_11ax_htc_cmd *)&ax_cmd->param; + + he_cmd->val[0] = htc_cmd->value; + cmd_size += sizeof(*htc_cmd); + break; + case NXPWIFI_11AXCMD_TXOMI_SUBID: + txmoi_cmd = (struct nxpwifi_11ax_txomi_cmd *)&ax_cmd->param; + + memcpy((void *)he_cmd->val, txmoi_cmd, sizeof(*txmoi_cmd)); + cmd_size += sizeof(*txmoi_cmd); + break; + case NXPWIFI_11AXCMD_OBSS_TOLTIME_SUBID: + toltime_cmd = (struct nxpwifi_11ax_toltime_cmd *)&ax_cmd->param; + + memcpy(he_cmd->val, &toltime_cmd->tol_time, + sizeof(toltime_cmd->tol_time)); + cmd_size += sizeof(*toltime_cmd); + break; + case NXPWIFI_11AXCMD_TXOPRTS_SUBID: + txop_cmd = (struct nxpwifi_11ax_txop_cmd *)&ax_cmd->param; + + memcpy(he_cmd->val, &txop_cmd->rts_thres, + sizeof(txop_cmd->rts_thres)); + cmd_size += sizeof(*txop_cmd); + break; + case NXPWIFI_11AXCMD_SET_BSRP_SUBID: + set_bsrp_cmd = (struct nxpwifi_11ax_set_bsrp_cmd *)&ax_cmd->param; + + he_cmd->val[0] = set_bsrp_cmd->value; + cmd_size += sizeof(*set_bsrp_cmd); + break; + case NXPWIFI_11AXCMD_LLDE_SUBID: + llde_cmd = (struct nxpwifi_11ax_llde_cmd *)&ax_cmd->param; + + memcpy((void *)he_cmd->val, llde_cmd, sizeof(*llde_cmd)); + cmd_size += sizeof(*llde_cmd); + break; + default: + nxpwifi_dbg(adapter, ERROR, + "%s: Unknown sub command: %d\n", + __func__, ax_cmd->sub_command); + return -EINVAL; + } + + cmd->size = cpu_to_le16(cmd_size); + + return 0; +} + +int nxpwifi_ret_11ax_cmd(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + struct nxpwifi_11ax_cmd_cfg *ax_cmd) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_11ax_cmd *he_cmd = &resp->params.ax_cmd; + struct nxpwifi_ie_types_data *tlv; + + ax_cmd->sub_id = le16_to_cpu(he_cmd->sub_id); + + switch (ax_cmd->sub_command) { + case NXPWIFI_11AXCMD_SR_SUBID: + tlv = (struct nxpwifi_ie_types_data *)he_cmd->val; + memcpy(ax_cmd->param.sr_cfg.param.obss_pd_offset.offset, + tlv->data, + ax_cmd->param.sr_cfg.len); + break; + case NXPWIFI_11AXCMD_BEAM_SUBID: + ax_cmd->param.beam_cfg.value = *he_cmd->val; + break; + case NXPWIFI_11AXCMD_HTC_SUBID: + ax_cmd->param.htc_cfg.value = *he_cmd->val; + break; + case NXPWIFI_11AXCMD_TXOMI_SUBID: + memcpy(&ax_cmd->param.txomi_cfg, + he_cmd->val, sizeof(ax_cmd->param.txomi_cfg)); + break; + case NXPWIFI_11AXCMD_OBSS_TOLTIME_SUBID: + memcpy(&ax_cmd->param.toltime_cfg.tol_time, + he_cmd->val, sizeof(ax_cmd->param.toltime_cfg)); + break; + case NXPWIFI_11AXCMD_TXOPRTS_SUBID: + memcpy(&ax_cmd->param.txop_cfg.rts_thres, + he_cmd->val, sizeof(ax_cmd->param.txop_cfg)); + break; + case NXPWIFI_11AXCMD_SET_BSRP_SUBID: + ax_cmd->param.setbsrp_cfg.value = *he_cmd->val; + break; + case NXPWIFI_11AXCMD_LLDE_SUBID: + memcpy(&ax_cmd->param.llde_cfg, + he_cmd->val, sizeof(ax_cmd->param.llde_cfg)); + break; + default: + nxpwifi_dbg(adapter, ERROR, + "%s: Unknown sub command: %d\n", + __func__, ax_cmd->sub_command); + return -EINVAL; + } + + return 0; +} + +static u8 nxpwifi_is_ap_11ax_twt_supported(struct nxpwifi_bssdescriptor *bss_desc) +{ + struct element *ext_cap; + + if (!bss_desc->bcn_he_cap) + return false; + if (!(bss_desc->bcn_he_cap->mac_cap_info[0] & HE_MAC_CAP_TWT_RESP_SUPPORT)) + return false; + if (!bss_desc->bcn_ext_cap) + return false; + ext_cap = (struct element *)bss_desc->bcn_ext_cap; + + if (!(ext_cap->data[9] & WLAN_EXT_CAPA10_TWT_RESPONDER_SUPPORT)) + return false; + return true; +} + +bool nxpwifi_is_11ax_twt_supported(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + struct nxpwifi_ie_types_he_cap *user_he_cap; + struct nxpwifi_ie_types_he_cap *hw_he_cap; + + if (bss_desc && (!nxpwifi_is_ap_11ax_twt_supported(bss_desc))) { + nxpwifi_dbg(priv->adapter, MSG, + "AP don't support twt feature\n"); + return false; + } + + if (bss_desc->bss_band & BAND_A) { + hw_he_cap = (struct nxpwifi_ie_types_he_cap *) + priv->adapter->hw_he_cap; + user_he_cap = (struct nxpwifi_ie_types_he_cap *) + priv->user_he_cap; + } else { + hw_he_cap = (struct nxpwifi_ie_types_he_cap *) + priv->adapter->hw_2g_he_cap; + user_he_cap = (struct nxpwifi_ie_types_he_cap *) + priv->user_2g_he_cap; + } + + if (!(hw_he_cap->he_mac_cap[0] & HE_MAC_CAP_TWT_REQ_SUPPORT)) { + nxpwifi_dbg(priv->adapter, MSG, + "FW don't support TWT\n"); + return false; + } + + if (!(user_he_cap->he_mac_cap[0] & HE_MAC_CAP_TWT_REQ_SUPPORT)) { + nxpwifi_dbg(priv->adapter, MSG, + "USER HE_MAC_CAP don't support TWT\n"); + return false; + } + + return true; +} + +u8 nxpwifi_is_sta_11ax_twt_req_supported(struct nxpwifi_private *priv) +{ + struct nxpwifi_ie_types_he_cap *user_he_cap; + u8 ret = 0; + + if (ISSUPP_11AXENABLED(priv->adapter->fw_cap_ext) && + (priv->config_bands & BAND_GAX || priv->config_bands & BAND_AAX)) { + if (priv->config_bands & BAND_AAX) + user_he_cap = (struct nxpwifi_ie_types_he_cap *)priv->user_he_cap; + else + user_he_cap = (struct nxpwifi_ie_types_he_cap *)priv->user_2g_he_cap; + ret = user_he_cap->he_mac_cap[0] & HE_MAC_CAP_TWT_REQ_SUPPORT; + } + + return ret; +} + +int nxpwifi_cmd_twt_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_twt_cfg *twt_cfg) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_twt_cfg *twt_cfg_cmd = &cmd->params.twt_cfg; + struct nxpwifi_twt_setup *twt_setup; + struct nxpwifi_twt_teardown *twt_teardown; + struct nxpwifi_twt_report *twt_report; + struct nxpwifi_twt_information *twt_information; + struct nxpwifi_btwt_ap_config *btwt_ap_config; + u8 i; + u16 cmd_size; + + cmd->command = cpu_to_le16(HOST_CMD_TWT_CFG); + cmd_size = sizeof(struct host_cmd_twt_cfg) + S_DS_GEN; + + twt_cfg_cmd->action = cpu_to_le16(cmd_action); + twt_cfg_cmd->sub_id = cpu_to_le16(twt_cfg->sub_id); + + switch (twt_cfg->sub_id) { + case NXPWIFI_11AX_TWT_SETUP_SUBID: + twt_setup = (struct nxpwifi_twt_setup *) + twt_cfg_cmd->val; + + memset(twt_setup, 0x00, sizeof(struct nxpwifi_twt_setup)); + twt_setup->implicit = twt_cfg->param.twt_setup.implicit; + twt_setup->announced = twt_cfg->param.twt_setup.announced; + twt_setup->trigger_enabled = twt_cfg->param.twt_setup.trigger_enabled; + twt_setup->twt_info_disabled = twt_cfg->param.twt_setup.twt_info_disabled; + twt_setup->negotiation_type = twt_cfg->param.twt_setup.negotiation_type; + twt_setup->twt_wakeup_duration = + twt_cfg->param.twt_setup.twt_wakeup_duration; + twt_setup->flow_identifier = twt_cfg->param.twt_setup.flow_identifier; + twt_setup->hard_constraint = twt_cfg->param.twt_setup.hard_constraint; + twt_setup->twt_exponent = twt_cfg->param.twt_setup.twt_exponent; + twt_setup->twt_mantissa = twt_cfg->param.twt_setup.twt_mantissa; + twt_setup->twt_request = twt_cfg->param.twt_setup.twt_request; + twt_setup->bcn_miss_threshold = twt_cfg->param.twt_setup.bcn_miss_threshold; + cmd_size += sizeof(struct nxpwifi_twt_setup); + break; + case NXPWIFI_11AX_TWT_TEARDOWN_SUBID: + twt_teardown = (struct nxpwifi_twt_teardown *) + twt_cfg_cmd->val; + memset(twt_teardown, 0x00, + sizeof(struct nxpwifi_twt_teardown)); + twt_teardown->flow_identifier = + twt_cfg->param.twt_teardown.flow_identifier; + twt_teardown->negotiation_type = + twt_cfg->param.twt_teardown.negotiation_type; + twt_teardown->teardown_all_twt = + twt_cfg->param.twt_teardown.teardown_all_twt; + cmd_size += sizeof(struct nxpwifi_twt_teardown); + break; + case NXPWIFI_11AX_TWT_REPORT_SUBID: + twt_report = (struct nxpwifi_twt_report *) + twt_cfg_cmd->val; + memset(twt_report, 0x00, sizeof(struct nxpwifi_twt_report)); + twt_report->type = twt_cfg->param.twt_report.type; + cmd_size += sizeof(struct nxpwifi_twt_report); + break; + case NXPWIFI_11AX_TWT_INFORMATION_SUBID: + twt_information = (struct nxpwifi_twt_information *) + twt_cfg_cmd->val; + memset(twt_information, 0x00, + sizeof(struct nxpwifi_twt_information)); + twt_information->flow_identifier = + twt_cfg->param.twt_information.flow_identifier; + twt_information->suspend_duration = + twt_cfg->param.twt_information.suspend_duration; + cmd_size += sizeof(struct nxpwifi_twt_information); + break; + case NXPWIFI_11AX_BTWT_AP_CONFIG_SUBID: + btwt_ap_config = (struct nxpwifi_btwt_ap_config *) + twt_cfg_cmd->val; + memset(btwt_ap_config, 0x00, + sizeof(struct nxpwifi_btwt_ap_config)); + btwt_ap_config->ap_bcast_bet_sta_wait = + twt_cfg->param.btwt_ap_config.ap_bcast_bet_sta_wait; + btwt_ap_config->ap_bcast_offset = + twt_cfg->param.btwt_ap_config.ap_bcast_offset; + btwt_ap_config->bcast_twtli = + twt_cfg->param.btwt_ap_config.bcast_twtli; + btwt_ap_config->count = + twt_cfg->param.btwt_ap_config.count; + for (i = 0; i < BTWT_AGREEMENT_MAX; i++) { + btwt_ap_config->btwt_sets[i].btwt_id = + twt_cfg->param.btwt_ap_config.btwt_sets[i].btwt_id; + btwt_ap_config->btwt_sets[i].ap_bcast_mantissa = + twt_cfg->param.btwt_ap_config.btwt_sets[i].ap_bcast_mantissa; + btwt_ap_config->btwt_sets[i].ap_bcast_exponent = + twt_cfg->param.btwt_ap_config.btwt_sets[i].ap_bcast_exponent; + btwt_ap_config->btwt_sets[i].nominalwake = + twt_cfg->param.btwt_ap_config.btwt_sets[i].nominalwake; + } + + cmd_size += sizeof(struct nxpwifi_btwt_ap_config); + break; + default: + nxpwifi_dbg(adapter, ERROR, + "Unknown sub id: %d\n", twt_cfg->sub_id); + return -EINVAL; + } + + cmd->size = cpu_to_le16(cmd_size); + + return 0; +} + +int nxpwifi_ret_twt_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + struct nxpwifi_twt_cfg *twt_cfg) +{ + struct host_cmd_twt_cfg *twt_cfg_cmd = &resp->params.twt_cfg; + u16 action; + + action = le16_to_cpu(twt_cfg_cmd->action); + twt_cfg->sub_id = le16_to_cpu(twt_cfg_cmd->sub_id); + + if (action == HOST_ACT_GEN_GET && + twt_cfg->sub_id == NXPWIFI_11AX_TWT_REPORT_SUBID) { + struct nxpwifi_twt_report *twt_report = + (struct nxpwifi_twt_report *)twt_cfg_cmd->val; + + memcpy(&twt_cfg->param.twt_report, twt_report, sizeof(struct nxpwifi_twt_report)); + } + + return 0; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/11ax.h b/drivers/net/wireless/nxp/nxpwifi/11ax.h new file mode 100644 index 000000000000..2eda69f19763 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11ax.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * nxpwifi: 802.11ax support + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_11AX_H_ +#define _NXPWIFI_11AX_H_ + +/* device support 2.4G 40MHZ */ +#define AX_2G_40MHZ_SUPPORT BIT(1) +/* device support 2.4G 242 tone RUs */ +#define AX_2G_20MHZ_SUPPORT BIT(5) + +/* Get HE MCS map code for n spatial streams (0..3). */ +static inline u16 +nxpwifi_get_he_nss_mcs(__le16 mcs_map_set, int nss) { + return ((le16_to_cpu(mcs_map_set) >> (2 * (nss - 1))) & 0x3); +} + +static inline void +nxpwifi_set_he_nss_mcs(__le16 *mcs_map_set, int nss, int value) { + u16 temp; + + temp = le16_to_cpu(*mcs_map_set); + temp |= ((value & 0x3) << (2 * (nss - 1))); + *mcs_map_set = cpu_to_le16(temp); +} + +bool nxpwifi_is_11ax_twt_supported(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc); + +void nxpwifi_update_11ax_cap(struct nxpwifi_adapter *adapter, + struct hw_spec_extension *hw_he_cap); + +bool nxpwifi_11ax_bandconfig_allowed(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc); + +int nxpwifi_cmd_append_11ax_tlv(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, + u8 **buffer); + +int nxpwifi_fill_he_cap_tlv(struct nxpwifi_private *priv, + struct nxpwifi_ie_types_he_cap *he_cap, + u16 bands); +int nxpwifi_cmd_11ax_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_11ax_he_cfg *ax_cfg); + +int nxpwifi_ret_11ax_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + struct nxpwifi_11ax_he_cfg *ax_cfg); + +int nxpwifi_cmd_11ax_cmd(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_11ax_cmd_cfg *ax_cmd); + +int nxpwifi_ret_11ax_cmd(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + struct nxpwifi_11ax_cmd_cfg *ax_cmd); + +int nxpwifi_cmd_twt_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_twt_cfg *twt_cfg); + +int nxpwifi_ret_twt_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + struct nxpwifi_twt_cfg *twt_cfg); + +u8 nxpwifi_is_sta_11ax_twt_req_supported(struct nxpwifi_private *priv); + +#endif /* _NXPWIFI_11AX_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/11h.c b/drivers/net/wireless/nxp/nxpwifi/11h.c new file mode 100644 index 000000000000..058c319ff910 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11h.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: 802.11h helpers + * + * Copyright 2011-2024 NXP + */ + +#include "main.h" +#include "cmdevt.h" +#include "fw.h" +#include "cfg80211.h" + +void nxpwifi_init_11h_params(struct nxpwifi_private *priv) +{ + priv->state_11h.is_11h_enabled = true; + priv->state_11h.is_11h_active = false; +} + +int nxpwifi_is_11h_active(struct nxpwifi_private *priv) +{ + return priv->state_11h.is_11h_active; +} + +/* appends 11h info to a buffer while joining an infrastructure BSS */ +static void +nxpwifi_11h_process_infra_join(struct nxpwifi_private *priv, u8 **buffer, + struct nxpwifi_bssdescriptor *bss_desc) +{ + struct nxpwifi_ie_types_header *ie_header; + struct nxpwifi_ie_types_pwr_capability *cap; + struct nxpwifi_ie_types_local_pwr_constraint *constraint; + struct ieee80211_supported_band *sband; + u8 radio_type; + int i; + + if (!buffer || !(*buffer)) + return; + + radio_type = nxpwifi_band_to_radio_type((u8)bss_desc->bss_band); + sband = priv->wdev.wiphy->bands[radio_type]; + + cap = (struct nxpwifi_ie_types_pwr_capability *)*buffer; + cap->header.type = cpu_to_le16(WLAN_EID_PWR_CAPABILITY); + cap->header.len = cpu_to_le16(2); + cap->min_pwr = 0; + cap->max_pwr = 0; + *buffer += sizeof(*cap); + + constraint = (struct nxpwifi_ie_types_local_pwr_constraint *)*buffer; + constraint->header.type = cpu_to_le16(WLAN_EID_PWR_CONSTRAINT); + constraint->header.len = cpu_to_le16(2); + constraint->chan = bss_desc->channel; + constraint->constraint = bss_desc->local_constraint; + *buffer += sizeof(*constraint); + + ie_header = (struct nxpwifi_ie_types_header *)*buffer; + ie_header->type = cpu_to_le16(TLV_TYPE_PASSTHROUGH); + ie_header->len = cpu_to_le16(2 * sband->n_channels + 2); + *buffer += sizeof(*ie_header); + *(*buffer)++ = WLAN_EID_SUPPORTED_CHANNELS; + *(*buffer)++ = 2 * sband->n_channels; + for (i = 0; i < sband->n_channels; i++) { + u32 center_freq; + + center_freq = sband->channels[i].center_freq; + *(*buffer)++ = ieee80211_frequency_to_channel(center_freq); + *(*buffer)++ = 1; /* one channel in the subband */ + } +} + +/* Enable or disable the 11h extensions in the firmware */ +int nxpwifi_11h_activate(struct nxpwifi_private *priv, bool flag) +{ + u32 enable = flag; + + /* enable master mode radar detection on AP interface */ + if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) && enable) + enable |= NXPWIFI_MASTER_RADAR_DET_MASK; + + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_SNMP_MIB, + HOST_ACT_GEN_SET, DOT11H_I, &enable, true); +} + +/* + * Process TLV buffer for a pending BSS join. Enable 11h in firmware when the + * network advertises spectrum management, and add required TLVs based on the + * BSS's 11h capability. + */ +void nxpwifi_11h_process_join(struct nxpwifi_private *priv, u8 **buffer, + struct nxpwifi_bssdescriptor *bss_desc) +{ + if (bss_desc->sensed_11h) { + /* Activate 11h functions in firmware, turns on capability bit */ + nxpwifi_11h_activate(priv, true); + priv->state_11h.is_11h_active = true; + bss_desc->cap_info_bitmap |= WLAN_CAPABILITY_SPECTRUM_MGMT; + nxpwifi_11h_process_infra_join(priv, buffer, bss_desc); + } else { + /* Deactivate 11h functions in the firmware */ + nxpwifi_11h_activate(priv, false); + priv->state_11h.is_11h_active = false; + bss_desc->cap_info_bitmap &= ~WLAN_CAPABILITY_SPECTRUM_MGMT; + } +} + +/* + * DFS CAC work function. This delayed work emits CAC finished event for cfg80211 + * if CAC was started earlier + */ +void nxpwifi_dfs_cac_work(struct wiphy *wiphy, struct wiphy_work *work) +{ + struct cfg80211_chan_def chandef; + struct wiphy_delayed_work *delayed_work = + container_of(work, struct wiphy_delayed_work, work); + struct nxpwifi_private *priv = container_of(delayed_work, + struct nxpwifi_private, + dfs_cac_work); + + chandef = priv->dfs_chandef; + if (priv->wdev.links[0].cac_started) { + nxpwifi_dbg(priv->adapter, MSG, + "CAC timer finished; No radar detected\n"); + cfg80211_cac_event(priv->netdev, &chandef, + NL80211_RADAR_CAC_FINISHED, + GFP_KERNEL, 0); + } +} + +/* prepares channel report request command to FW for starting radar detection */ +int nxpwifi_cmd_issue_chan_report_request(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + void *data_buf) +{ + struct host_cmd_ds_chan_rpt_req *cr_req = &cmd->params.chan_rpt_req; + struct nxpwifi_radar_params *radar_params = (void *)data_buf; + u16 size; + + cmd->command = cpu_to_le16(HOST_CMD_CHAN_REPORT_REQUEST); + size = S_DS_GEN; + + cr_req->chan_desc.start_freq = cpu_to_le16(NXPWIFI_A_BAND_START_FREQ); + nxpwifi_convert_chan_to_band_cfg(priv, + &cr_req->chan_desc.band_cfg, + radar_params->chandef); + cr_req->chan_desc.chan_num = radar_params->chandef->chan->hw_value; + cr_req->msec_dwell_time = cpu_to_le32(radar_params->cac_time_ms); + size += sizeof(*cr_req); + + if (radar_params->cac_time_ms) { + struct nxpwifi_ie_types_chan_rpt_data *rpt; + + rpt = (struct nxpwifi_ie_types_chan_rpt_data *)((u8 *)cmd + size); + rpt->header.type = cpu_to_le16(TLV_TYPE_CHANRPT_11H_BASIC); + rpt->header.len = cpu_to_le16(sizeof(u8)); + rpt->meas_rpt_map = 1 << MEAS_RPT_MAP_RADAR_SHIFT_BIT; + size += sizeof(*rpt); + + nxpwifi_dbg(priv->adapter, MSG, + "11h: issuing DFS Radar check for channel=%d\n", + radar_params->chandef->chan->hw_value); + } else { + nxpwifi_dbg(priv->adapter, MSG, "cancelling CAC\n"); + } + + cmd->size = cpu_to_le16(size); + + return 0; +} + +int nxpwifi_stop_radar_detection(struct nxpwifi_private *priv, + struct cfg80211_chan_def *chandef) +{ + struct nxpwifi_radar_params radar_params; + + memset(&radar_params, 0, sizeof(struct nxpwifi_radar_params)); + radar_params.chandef = chandef; + radar_params.cac_time_ms = 0; + + return nxpwifi_send_cmd(priv, HOST_CMD_CHAN_REPORT_REQUEST, + HOST_ACT_GEN_SET, 0, &radar_params, true); +} + +/* Abort ongoing CAC when stopping AP operations or during unload */ +void nxpwifi_abort_cac(struct nxpwifi_private *priv) +{ + if (priv->wdev.links[0].cac_started) { + if (nxpwifi_stop_radar_detection(priv, &priv->dfs_chandef)) + nxpwifi_dbg(priv->adapter, ERROR, + "failed to stop CAC in FW\n"); + nxpwifi_dbg(priv->adapter, MSG, + "Aborting delayed work for CAC.\n"); + wiphy_delayed_work_cancel(priv->adapter->wiphy, &priv->dfs_cac_work); + cfg80211_cac_event(priv->netdev, &priv->dfs_chandef, + NL80211_RADAR_CAC_ABORTED, GFP_KERNEL, 0); + } +} + +/* + * handles channel report event from FW during CAC period. If radar is detected + * during CAC, driver indicates the same to cfg80211 and also cancels ongoing + * delayed work + */ +int nxpwifi_11h_handle_chanrpt_ready(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct host_cmd_ds_chan_rpt_event *rpt_event; + struct nxpwifi_ie_types_chan_rpt_data *rpt; + u16 event_len, tlv_len; + + rpt_event = (void *)(skb->data + sizeof(u32)); + event_len = skb->len - (sizeof(struct host_cmd_ds_chan_rpt_event) + + sizeof(u32)); + + if (le32_to_cpu(rpt_event->result) != HOST_RESULT_OK) { + nxpwifi_dbg(priv->adapter, ERROR, + "Error in channel report event\n"); + return -EINVAL; + } + + while (event_len >= sizeof(struct nxpwifi_ie_types_header)) { + rpt = (void *)&rpt_event->tlvbuf; + tlv_len = le16_to_cpu(rpt->header.len); + + switch (le16_to_cpu(rpt->header.type)) { + case TLV_TYPE_CHANRPT_11H_BASIC: + if (rpt->meas_rpt_map & MEAS_RPT_MAP_RADAR_MASK) { + nxpwifi_dbg(priv->adapter, MSG, + "RADAR Detected on channel %d!\n", + priv->dfs_chandef.chan->hw_value); + + wiphy_delayed_work_cancel(priv->adapter->wiphy, + &priv->dfs_cac_work); + cfg80211_cac_event(priv->netdev, + &priv->dfs_chandef, + NL80211_RADAR_CAC_ABORTED, + GFP_KERNEL, 0); + cfg80211_radar_event(priv->adapter->wiphy, + &priv->dfs_chandef, + GFP_KERNEL); + } + break; + default: + break; + } + + event_len -= (tlv_len + sizeof(rpt->header)); + } + + return 0; +} + +/* Handler for radar detected event from FW */ +int nxpwifi_11h_handle_radar_detected(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_radar_det_event *rdr_event; + + rdr_event = (void *)(skb->data + sizeof(u32)); + + nxpwifi_dbg(priv->adapter, MSG, + "radar detected; indicating kernel\n"); + + if (priv->wdev.links[0].cac_started) { + if (nxpwifi_stop_radar_detection(priv, &priv->dfs_chandef)) + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to stop CAC in FW\n"); + wiphy_delayed_work_cancel(priv->adapter->wiphy, &priv->dfs_cac_work); + cfg80211_cac_event(priv->netdev, &priv->dfs_chandef, + NL80211_RADAR_CAC_ABORTED, GFP_KERNEL, 0); + } + cfg80211_radar_event(priv->adapter->wiphy, &priv->dfs_chandef, + GFP_KERNEL); + nxpwifi_dbg(priv->adapter, MSG, "regdomain: %d\n", + rdr_event->reg_domain); + nxpwifi_dbg(priv->adapter, MSG, "radar detection type: %d\n", + rdr_event->det_type); + + return 0; +} + +/* + * work function for channel switch handling. takes care of updating new channel + * definitin to bss config structure, restart AP and indicate channel switch + * success to cfg80211 + */ +void nxpwifi_dfs_chan_sw_work(struct wiphy *wiphy, struct wiphy_work *work) +{ + struct nxpwifi_uap_bss_param *bss_cfg; + struct wiphy_delayed_work *delayed_work = + container_of(work, struct wiphy_delayed_work, work); + struct nxpwifi_private *priv = container_of(delayed_work, + struct nxpwifi_private, + dfs_chan_sw_work); + struct nxpwifi_adapter *adapter = priv->adapter; + + if (nxpwifi_del_mgmt_ies(priv)) + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to delete mgmt IEs!\n"); + + bss_cfg = &priv->bss_cfg; + if (!bss_cfg->beacon_period) { + nxpwifi_dbg(adapter, ERROR, + "channel switch: AP already stopped\n"); + return; + } + + if (nxpwifi_send_cmd(priv, HOST_CMD_UAP_BSS_STOP, + HOST_ACT_GEN_SET, 0, NULL, true)) { + nxpwifi_dbg(adapter, ERROR, + "channel switch: Failed to stop the BSS\n"); + return; + } + + if (nxpwifi_cfg80211_change_beacon(adapter->wiphy, priv->netdev, + &priv->ap_update_info)) { + nxpwifi_dbg(adapter, ERROR, + "channel switch: Failed to set beacon\n"); + return; + } + + nxpwifi_uap_set_channel(priv, bss_cfg, priv->dfs_chandef); + + if (nxpwifi_config_start_uap(priv, bss_cfg)) { + nxpwifi_dbg(adapter, ERROR, + "Failed to start AP after channel switch\n"); + return; + } + + nxpwifi_dbg(adapter, MSG, + "indicating channel switch completion to kernel\n"); + + cfg80211_ch_switch_notify(priv->netdev, &priv->dfs_chandef, 0); + + if (priv->uap_stop_tx) { + netif_carrier_on(priv->netdev); + nxpwifi_wake_up_net_dev_queue(priv->netdev, adapter); + priv->uap_stop_tx = false; + } +} diff --git a/drivers/net/wireless/nxp/nxpwifi/11n.c b/drivers/net/wireless/nxp/nxpwifi/11n.c new file mode 100644 index 000000000000..e46c5053d509 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11n.c @@ -0,0 +1,837 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi 802.11n helpers + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" +#include "11ax.h" + +/* + * Fills HT capability information field, AMPDU Parameters field, HT extended + * capability field, and supported MCS set fields. + * + * HT capability information field, AMPDU Parameters field, supported MCS set + * fields are retrieved from cfg80211 stack + * + * RD responder bit to set to clear in the extended capability header. + */ +int nxpwifi_fill_cap_info(struct nxpwifi_private *priv, u8 radio_type, + struct ieee80211_ht_cap *ht_cap) +{ + u16 ht_cap_info; + u16 bcn_ht_cap = le16_to_cpu(ht_cap->cap_info); + u16 ht_ext_cap = le16_to_cpu(ht_cap->extended_ht_cap_info); + struct ieee80211_supported_band *sband = + priv->wdev.wiphy->bands[radio_type]; + + if (WARN_ON_ONCE(!sband)) { + nxpwifi_dbg(priv->adapter, ERROR, "Invalid radio type!\n"); + return -EINVAL; + } + + ht_cap->ampdu_params_info = + (AMPDU_FACTOR_64K & IEEE80211_HT_AMPDU_PARM_FACTOR) | + ((priv->adapter->hw_mpdu_density << + IEEE80211_HT_AMPDU_PARM_DENSITY_SHIFT) & + IEEE80211_HT_AMPDU_PARM_DENSITY); + + memcpy((u8 *)&ht_cap->mcs, &sband->ht_cap.mcs, + sizeof(sband->ht_cap.mcs)); + + if (priv->bss_mode == NL80211_IFTYPE_STATION || + (sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 && + priv->adapter->sec_chan_offset != IEEE80211_HT_PARAM_CHA_SEC_NONE)) + /* Set MCS32 for infra mode or ad-hoc mode with 40MHz support */ + SETHT_MCS32(ht_cap->mcs.rx_mask); + + /* Clear RD responder bit */ + ht_ext_cap &= ~IEEE80211_HT_EXT_CAP_RD_RESPONDER; + + ht_cap_info = sband->ht_cap.cap; + if (bcn_ht_cap) { + if (!(bcn_ht_cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40)) + ht_cap_info &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40; + if (!(bcn_ht_cap & IEEE80211_HT_CAP_SGI_40)) + ht_cap_info &= ~IEEE80211_HT_CAP_SGI_40; + if (!(bcn_ht_cap & IEEE80211_HT_CAP_40MHZ_INTOLERANT)) + ht_cap_info &= ~IEEE80211_HT_CAP_40MHZ_INTOLERANT; + } + ht_cap->cap_info = cpu_to_le16(ht_cap_info); + ht_cap->extended_ht_cap_info = cpu_to_le16(ht_ext_cap); + + if (ISSUPP_BEAMFORMING(priv->adapter->hw_dot_11n_dev_cap)) + ht_cap->tx_BF_cap_info = cpu_to_le32(NXPWIFI_DEF_11N_TX_BF_CAP); + + return 0; +} + +/* Return BA stream entry that matches the requested status. */ +static struct nxpwifi_tx_ba_stream_tbl * +nxpwifi_get_ba_status(struct nxpwifi_private *priv, int tid, + enum nxpwifi_ba_status ba_status) +{ + struct nxpwifi_tx_ba_stream_tbl *tx_ba_tsr_tbl, *found = NULL; + + guard(rcu)(); + list_for_each_entry_rcu(tx_ba_tsr_tbl, &priv->tx_ba_stream_tbl_ptr[tid], list) { + if (tx_ba_tsr_tbl->ba_status == ba_status) { + found = tx_ba_tsr_tbl; + break; + } + } + return found; +} + +/* Handle DELBA command response (recreate or continue ADDBA as needed). */ +int nxpwifi_ret_11n_delba(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + int tid; + struct nxpwifi_tx_ba_stream_tbl *tx_ba_tbl; + struct host_cmd_ds_11n_delba *del_ba = &resp->params.del_ba; + u16 del_ba_param_set = le16_to_cpu(del_ba->del_ba_param_set); + + tid = del_ba_param_set >> DELBA_TID_POS; + if (del_ba->del_result == BA_RESULT_SUCCESS) { + nxpwifi_del_ba_tbl(priv, tid, del_ba->peer_mac_addr, + TYPE_DELBA_SENT, + INITIATOR_BIT(del_ba_param_set)); + + tx_ba_tbl = nxpwifi_get_ba_status(priv, tid, BA_SETUP_INPROGRESS); + if (tx_ba_tbl) + nxpwifi_send_addba(priv, tx_ba_tbl->tid, + tx_ba_tbl->ra); + } else { + /* + * In case of failure, recreate the deleted stream in case + * we initiated the DELBA + */ + if (!INITIATOR_BIT(del_ba_param_set)) + return 0; + + nxpwifi_create_ba_tbl(priv, del_ba->peer_mac_addr, tid, + BA_SETUP_INPROGRESS); + + tx_ba_tbl = nxpwifi_get_ba_status(priv, tid, BA_SETUP_INPROGRESS); + + if (tx_ba_tbl) + nxpwifi_del_ba_tbl(priv, tx_ba_tbl->tid, tx_ba_tbl->ra, + TYPE_DELBA_SENT, true); + } + + return 0; +} + +/* Handle ADDBA response; delete BA stream on failure. */ +int nxpwifi_ret_11n_addba_req(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + int tid, tid_down; + struct host_cmd_ds_11n_addba_rsp *add_ba_rsp = &resp->params.add_ba_rsp; + struct nxpwifi_tx_ba_stream_tbl *tx_ba_tbl; + struct nxpwifi_ra_list_tbl *ra_list; + u16 block_ack_param_set = le16_to_cpu(add_ba_rsp->block_ack_param_set); + + add_ba_rsp->ssn = cpu_to_le16((le16_to_cpu(add_ba_rsp->ssn)) + & SSN_MASK); + + tid = u16_get_bits(block_ack_param_set, IEEE80211_ADDBA_PARAM_TID_MASK); + + tid_down = nxpwifi_wmm_downgrade_tid(priv, tid); + ra_list = nxpwifi_wmm_get_ralist_node(priv, tid_down, + add_ba_rsp->peer_mac_addr); + if (le16_to_cpu(add_ba_rsp->status_code) != BA_RESULT_SUCCESS) { + if (ra_list) { + ra_list->ba_status = BA_SETUP_NONE; + ra_list->amsdu_in_ampdu = false; + } + nxpwifi_del_ba_tbl(priv, tid, add_ba_rsp->peer_mac_addr, + TYPE_DELBA_SENT, true); + if (add_ba_rsp->add_rsp_result != BA_RESULT_TIMEOUT) + priv->aggr_prio_tbl[tid].ampdu_ap = + BA_STREAM_NOT_ALLOWED; + return 0; + } + + guard(rcu)(); + tx_ba_tbl = nxpwifi_get_ba_tbl(priv, tid, add_ba_rsp->peer_mac_addr); + if (tx_ba_tbl) { + nxpwifi_dbg(priv->adapter, EVENT, "info: BA stream complete\n"); + tx_ba_tbl->ba_status = BA_SETUP_COMPLETE; + if ((block_ack_param_set & IEEE80211_ADDBA_PARAM_AMSDU_MASK) && + priv->add_ba_param.tx_amsdu && + priv->aggr_prio_tbl[tid].amsdu != BA_STREAM_NOT_ALLOWED) + tx_ba_tbl->amsdu = true; + else + tx_ba_tbl->amsdu = false; + if (ra_list) { + ra_list->amsdu_in_ampdu = tx_ba_tbl->amsdu; + ra_list->ba_status = BA_SETUP_COMPLETE; + } + } else { + nxpwifi_dbg(priv->adapter, ERROR, "BA stream not created\n"); + } + + return 0; +} + +/* + * Reconfigure Tx buffer command. + * Set command ID/action/size; set Tx buffer size on SET; ensure little-endian. + */ +int nxpwifi_cmd_recfg_tx_buf(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, int cmd_action, + u16 *buf_size) +{ + struct host_cmd_ds_txbuf_cfg *tx_buf = &cmd->params.tx_buf; + u16 action = (u16)cmd_action; + + cmd->command = cpu_to_le16(HOST_CMD_RECONFIGURE_TX_BUFF); + cmd->size = + cpu_to_le16(sizeof(struct host_cmd_ds_txbuf_cfg) + S_DS_GEN); + tx_buf->action = cpu_to_le16(action); + switch (action) { + case HOST_ACT_GEN_SET: + nxpwifi_dbg(priv->adapter, CMD, + "cmd: set tx_buf=%d\n", *buf_size); + tx_buf->buff_size = cpu_to_le16(*buf_size); + break; + case HOST_ACT_GEN_GET: + default: + tx_buf->buff_size = 0; + break; + } + return 0; +} + +/* + * AMSDU aggregation control command. + * Set ID/action/size; set AMSDU params on SET; ensure little-endian. + */ +int nxpwifi_cmd_amsdu_aggr_ctrl(struct host_cmd_ds_command *cmd, + int cmd_action, + struct nxpwifi_ds_11n_amsdu_aggr_ctrl *aa_ctrl) +{ + struct host_cmd_ds_amsdu_aggr_ctrl *amsdu_ctrl = + &cmd->params.amsdu_aggr_ctrl; + u16 action = (u16)cmd_action; + + cmd->command = cpu_to_le16(HOST_CMD_AMSDU_AGGR_CTRL); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_amsdu_aggr_ctrl) + + S_DS_GEN); + amsdu_ctrl->action = cpu_to_le16(action); + switch (action) { + case HOST_ACT_GEN_SET: + amsdu_ctrl->enable = cpu_to_le16(aa_ctrl->enable); + amsdu_ctrl->curr_buf_size = 0; + break; + case HOST_ACT_GEN_GET: + default: + amsdu_ctrl->curr_buf_size = 0; + break; + } + return 0; +} + +/* + * 11n configuration command. + * Set action, HT Tx capability/info, and misc config when 11ac HW is present. + */ +int nxpwifi_cmd_11n_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_ds_11n_tx_cfg *txcfg) +{ + struct host_cmd_ds_11n_cfg *htcfg = &cmd->params.htcfg; + + cmd->command = cpu_to_le16(HOST_CMD_11N_CFG); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_11n_cfg) + S_DS_GEN); + htcfg->action = cpu_to_le16(cmd_action); + htcfg->ht_tx_cap = cpu_to_le16(txcfg->tx_htcap); + htcfg->ht_tx_info = cpu_to_le16(txcfg->tx_htinfo); + + if (priv->adapter->is_hw_11ac_capable) + htcfg->misc_config = cpu_to_le16(txcfg->misc_config); + + return 0; +} + +/* + * Append 11n TLVs to the caller-owned buffer. + * Caller allocates space; no size checks here. + * May add: HT Cap, HT Operation + channel list, 20/40 BSS Coexistence, + * and Extended Capabilities (HS2/TWT bits when applicable). + */ +int +nxpwifi_cmd_append_11n_tlv(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, + u8 **buffer) +{ + struct nxpwifi_ie_types_htcap *ht_cap; + struct nxpwifi_ie_types_chan_list_param_set *chan_list; + struct nxpwifi_chan_scan_param_set *chan_param; + struct nxpwifi_ie_types_2040bssco *bss_co_2040; + struct nxpwifi_ie_types_extcap *ext_cap; + int ret_len = 0; + struct ieee80211_supported_band *sband; + struct element *hdr; + u8 radio_type; + + if (!buffer || !*buffer) + return ret_len; + + radio_type = nxpwifi_band_to_radio_type((u8)bss_desc->bss_band); + sband = priv->wdev.wiphy->bands[radio_type]; + + if (bss_desc->bcn_ht_cap) { + ht_cap = (struct nxpwifi_ie_types_htcap *)*buffer; + memset(ht_cap, 0, sizeof(struct nxpwifi_ie_types_htcap)); + ht_cap->header.type = cpu_to_le16(WLAN_EID_HT_CAPABILITY); + ht_cap->header.len = + cpu_to_le16(sizeof(struct ieee80211_ht_cap)); + memcpy((u8 *)ht_cap + sizeof(struct nxpwifi_ie_types_header), + (u8 *)bss_desc->bcn_ht_cap, + le16_to_cpu(ht_cap->header.len)); + + nxpwifi_fill_cap_info(priv, radio_type, &ht_cap->ht_cap); + /* Update HT40 capability from current channel. */ + if (bss_desc->bcn_ht_oper) { + u8 ht_param = bss_desc->bcn_ht_oper->ht_param; + u8 radio = + nxpwifi_band_to_radio_type(bss_desc->bss_band); + int freq = + ieee80211_channel_to_frequency(bss_desc->channel, + radio); + struct ieee80211_channel *chan = + ieee80211_get_channel(priv->adapter->wiphy, freq); + + switch (ht_param & IEEE80211_HT_PARAM_CHA_SEC_OFFSET) { + case IEEE80211_HT_PARAM_CHA_SEC_ABOVE: + if (chan->flags & IEEE80211_CHAN_NO_HT40PLUS) { + ht_cap->ht_cap.cap_info &= + cpu_to_le16 + (~IEEE80211_HT_CAP_SUP_WIDTH_20_40); + ht_cap->ht_cap.cap_info &= + cpu_to_le16(~IEEE80211_HT_CAP_SGI_40); + } + break; + case IEEE80211_HT_PARAM_CHA_SEC_BELOW: + if (chan->flags & IEEE80211_CHAN_NO_HT40MINUS) { + ht_cap->ht_cap.cap_info &= + cpu_to_le16 + (~IEEE80211_HT_CAP_SUP_WIDTH_20_40); + ht_cap->ht_cap.cap_info &= + cpu_to_le16(~IEEE80211_HT_CAP_SGI_40); + } + break; + } + } + + *buffer += sizeof(struct nxpwifi_ie_types_htcap); + ret_len += sizeof(struct nxpwifi_ie_types_htcap); + } + + if (bss_desc->bcn_ht_oper) { + chan_list = + (struct nxpwifi_ie_types_chan_list_param_set *)*buffer; + chan_param = chan_list->chan_scan_param; + memset(chan_list, 0, struct_size(chan_list, chan_scan_param, 1)); + chan_list->header.type = cpu_to_le16(TLV_TYPE_CHANLIST); + chan_list->header.len = cpu_to_le16(sizeof(*chan_param)); + chan_param->chan_number = bss_desc->bcn_ht_oper->primary_chan; + chan_param->band_cfg = + nxpwifi_band_to_radio_type((u8)bss_desc->bss_band); + + if (ISSUPP_11ACENABLED(priv->adapter->fw_cap_info) && + bss_desc->bcn_vht_oper && + bss_desc->bcn_vht_oper->chan_width == + IEEE80211_VHT_CHANWIDTH_80MHZ) { + SET_SECONDARYCHAN(chan_param->band_cfg, + (bss_desc->bcn_ht_oper->ht_param & + IEEE80211_HT_PARAM_CHA_SEC_OFFSET)); + chan_param->band_cfg |= + ((CHAN_BW_80MHZ << + BAND_CFG_CHAN_WIDTH_SHIFT_BIT) & + BAND_CFG_CHAN_WIDTH_MASK); + } else if (sband->ht_cap.cap & + IEEE80211_HT_CAP_SUP_WIDTH_20_40 && + bss_desc->bcn_ht_oper->ht_param & + IEEE80211_HT_PARAM_CHAN_WIDTH_ANY) { + SET_SECONDARYCHAN(chan_param->band_cfg, + (bss_desc->bcn_ht_oper->ht_param & + IEEE80211_HT_PARAM_CHA_SEC_OFFSET)); + chan_param->band_cfg |= + ((CHAN_BW_40MHZ << + BAND_CFG_CHAN_WIDTH_SHIFT_BIT) & + BAND_CFG_CHAN_WIDTH_MASK); + } + + *buffer += struct_size(chan_list, chan_scan_param, 1); + ret_len += struct_size(chan_list, chan_scan_param, 1); + } + + if (bss_desc->bcn_bss_co_2040) { + bss_co_2040 = (struct nxpwifi_ie_types_2040bssco *)*buffer; + memset(bss_co_2040, 0, + sizeof(struct nxpwifi_ie_types_2040bssco)); + bss_co_2040->header.type = cpu_to_le16(WLAN_EID_BSS_COEX_2040); + bss_co_2040->header.len = + cpu_to_le16(sizeof(bss_co_2040->bss_co_2040)); + + memcpy((u8 *)bss_co_2040 + + sizeof(struct nxpwifi_ie_types_header), + bss_desc->bcn_bss_co_2040 + + sizeof(struct element), + le16_to_cpu(bss_co_2040->header.len)); + + *buffer += sizeof(struct nxpwifi_ie_types_2040bssco); + ret_len += sizeof(struct nxpwifi_ie_types_2040bssco); + } + + if (bss_desc->bcn_ext_cap) { + u8 *ext_capab; + + hdr = (void *)bss_desc->bcn_ext_cap; + + ext_capab = (u8 *)cfg80211_find_ie(WLAN_EID_EXT_CAPABILITY, priv->gen_ie_buf, + priv->gen_ie_buf_len); + if (ext_capab) { + ext_capab += 2; + } else { + ext_cap = (struct nxpwifi_ie_types_extcap *)*buffer; + memset(ext_cap, 0, sizeof(struct nxpwifi_ie_types_extcap) + hdr->datalen); + ext_cap->header.type = cpu_to_le16(WLAN_EID_EXT_CAPABILITY); + ext_cap->header.len = cpu_to_le16(hdr->datalen); + ext_capab = ext_cap->ext_capab; + *buffer += sizeof(struct nxpwifi_ie_types_extcap) + hdr->datalen; + ret_len += sizeof(struct nxpwifi_ie_types_extcap) + hdr->datalen; + } + + if (hdr->datalen > 3 && + ext_capab[3] & WLAN_EXT_CAPA4_INTERWORKING_ENABLED) + priv->hs2_enabled = true; + else + priv->hs2_enabled = false; + + if (nxpwifi_is_11ax_twt_supported(priv, bss_desc)) + ext_capab[9] |= + WLAN_EXT_CAPA10_TWT_REQUESTER_SUPPORT; + } + return ret_len; +} + +/* Check if pointer is a valid Tx BA stream entry. */ +static bool +nxpwifi_is_tx_ba_stream_ptr_valid(struct nxpwifi_private *priv, + struct nxpwifi_tx_ba_stream_tbl *tx_tbl_ptr) +{ + struct nxpwifi_tx_ba_stream_tbl *tx_ba_tsr_tbl; + bool ret = false; + int tid; + + tid = tx_tbl_ptr->tid; + guard(rcu)(); + list_for_each_entry_rcu(tx_ba_tsr_tbl, &priv->tx_ba_stream_tbl_ptr[tid], list) { + if (tx_ba_tsr_tbl == tx_tbl_ptr) { + ret = true; + break; + } + } + return ret; +} + +/* Delete a Tx BA stream entry (after validating pointer). */ +void +nxpwifi_11n_delete_tx_ba_stream_tbl_entry(struct nxpwifi_private *priv, + struct nxpwifi_tx_ba_stream_tbl *tbl) +{ + if (!tbl && nxpwifi_is_tx_ba_stream_ptr_valid(priv, tbl)) + return; + + nxpwifi_dbg(priv->adapter, INFO, + "info: tx_ba_tsr_tbl %p\n", tbl); + + list_del_rcu(&tbl->list); + kfree_rcu(tbl, rcu); +} + +/* Delete all entries in Tx BA stream table. */ +void nxpwifi_11n_delete_all_tx_ba_stream_tbl(struct nxpwifi_private *priv) +{ + int i; + struct nxpwifi_tx_ba_stream_tbl *del_tbl_ptr, *tmp_node; + + for (i = 0; i < MAX_NUM_TID; i++) { + spin_lock_bh(&priv->tx_ba_stream_tbl_lock[i]); + list_for_each_entry_safe(del_tbl_ptr, tmp_node, + &priv->tx_ba_stream_tbl_ptr[i], list) + nxpwifi_11n_delete_tx_ba_stream_tbl_entry(priv, del_tbl_ptr); + spin_unlock_bh(&priv->tx_ba_stream_tbl_lock[i]); + + INIT_LIST_HEAD(&priv->tx_ba_stream_tbl_ptr[i]); + + priv->aggr_prio_tbl[i].ampdu_ap = + priv->aggr_prio_tbl[i].ampdu_user; + } +} + +/* Return BA stream entry for given RA/TID. */ +struct nxpwifi_tx_ba_stream_tbl * +nxpwifi_get_ba_tbl(struct nxpwifi_private *priv, int tid, u8 *ra) +{ + struct nxpwifi_tx_ba_stream_tbl *tx_ba_tsr_tbl = NULL; + + list_for_each_entry_rcu(tx_ba_tsr_tbl, &priv->tx_ba_stream_tbl_ptr[tid], list) { + if (ether_addr_equal_unaligned(tx_ba_tsr_tbl->ra, ra) && + tx_ba_tsr_tbl->tid == tid) + return tx_ba_tsr_tbl; + } + return NULL; +} + +/* Create Tx BA stream entry for given RA/TID. */ +void nxpwifi_create_ba_tbl(struct nxpwifi_private *priv, u8 *ra, int tid, + enum nxpwifi_ba_status ba_status) +{ + struct nxpwifi_tx_ba_stream_tbl *new_node; + struct nxpwifi_ra_list_tbl *ra_list; + int tid_down; + struct nxpwifi_tx_ba_stream_tbl *tx_ba_tbl; + + guard(rcu)(); + tx_ba_tbl = nxpwifi_get_ba_tbl(priv, tid, ra); + + if (!tx_ba_tbl) { + new_node = kzalloc_obj(*new_node, GFP_ATOMIC); + if (!new_node) + return; + + tid_down = nxpwifi_wmm_downgrade_tid(priv, tid); + ra_list = nxpwifi_wmm_get_ralist_node(priv, tid_down, ra); + if (ra_list) { + ra_list->ba_status = ba_status; + ra_list->amsdu_in_ampdu = false; + } + INIT_LIST_HEAD(&new_node->list); + + new_node->tid = tid; + new_node->ba_status = ba_status; + memcpy(new_node->ra, ra, ETH_ALEN); + + spin_lock_bh(&priv->tx_ba_stream_tbl_lock[tid]); + list_add_tail_rcu(&new_node->list, &priv->tx_ba_stream_tbl_ptr[tid]); + spin_unlock_bh(&priv->tx_ba_stream_tbl_lock[tid]); + } +} + +/* Send ADDBA request to the given TID/RA. */ +int nxpwifi_send_addba(struct nxpwifi_private *priv, int tid, u8 *peer_mac) +{ + struct host_cmd_ds_11n_addba_req add_ba_req; + u32 tx_win_size = priv->add_ba_param.tx_win_size; + static u8 dialog_tok; + u16 block_ack_param_set; + + nxpwifi_dbg(priv->adapter, CMD, "cmd: %s: tid %d\n", __func__, tid); + + memset(&add_ba_req, 0, sizeof(add_ba_req)); + + block_ack_param_set = (u16)((tid << BLOCKACKPARAM_TID_POS) | + tx_win_size << BLOCKACKPARAM_WINSIZE_POS | + IMMEDIATE_BLOCK_ACK); + + /* enable AMSDU inside AMPDU */ + if (priv->add_ba_param.tx_amsdu && + priv->aggr_prio_tbl[tid].amsdu != BA_STREAM_NOT_ALLOWED) + block_ack_param_set |= IEEE80211_ADDBA_PARAM_AMSDU_MASK; + + add_ba_req.block_ack_param_set = cpu_to_le16(block_ack_param_set); + add_ba_req.block_ack_tmo = cpu_to_le16((u16)priv->add_ba_param.timeout); + + ++dialog_tok; + + if (dialog_tok == 0) + dialog_tok = 1; + + add_ba_req.dialog_token = dialog_tok; + memcpy(&add_ba_req.peer_mac_addr, peer_mac, ETH_ALEN); + + /* We don't wait for the response of this command */ + return nxpwifi_send_cmd(priv, HOST_CMD_11N_ADDBA_REQ, + 0, 0, &add_ba_req, false); +} + +/* Send DELBA request to the given TID/RA. */ +int nxpwifi_send_delba(struct nxpwifi_private *priv, int tid, u8 *peer_mac, + int initiator) +{ + struct host_cmd_ds_11n_delba delba; + u16 del_ba_param_set; + + memset(&delba, 0, sizeof(delba)); + + del_ba_param_set = tid << DELBA_TID_POS; + + if (initiator) + del_ba_param_set |= IEEE80211_DELBA_PARAM_INITIATOR_MASK; + else + del_ba_param_set &= ~IEEE80211_DELBA_PARAM_INITIATOR_MASK; + + delba.del_ba_param_set = cpu_to_le16(del_ba_param_set); + memcpy(&delba.peer_mac_addr, peer_mac, ETH_ALEN); + + /* We don't wait for the response of this command */ + return nxpwifi_send_cmd(priv, HOST_CMD_11N_DELBA, + HOST_ACT_GEN_SET, 0, &delba, false); +} + +/* Send DELBA to specific TID. */ +void nxpwifi_11n_delba(struct nxpwifi_private *priv, int tid) +{ + struct nxpwifi_rx_reorder_tbl *rx_reor_tbl_ptr; + u8 ta[ETH_ALEN]; + bool found = false; + + rcu_read_lock(); + list_for_each_entry_rcu(rx_reor_tbl_ptr, &priv->rx_reorder_tbl_ptr[tid], list) { + if (rx_reor_tbl_ptr->tid == tid) { + memcpy(ta, rx_reor_tbl_ptr->ta, ETH_ALEN); + found = true; + break; + } + } + rcu_read_unlock(); + + if (found) { + nxpwifi_dbg(priv->adapter, INFO, + "Send delba to tid=%d, %pM\n", tid, ta); + nxpwifi_send_delba(priv, tid, ta, 0); + } +} + +/* Handle DELBA event; remove BA stream. */ +void nxpwifi_11n_delete_ba_stream(struct nxpwifi_private *priv, u8 *del_ba) +{ + struct host_cmd_ds_11n_delba *cmd_del_ba = + (struct host_cmd_ds_11n_delba *)del_ba; + u16 del_ba_param_set = le16_to_cpu(cmd_del_ba->del_ba_param_set); + int tid; + + tid = del_ba_param_set >> DELBA_TID_POS; + + nxpwifi_del_ba_tbl(priv, tid, cmd_del_ba->peer_mac_addr, + TYPE_DELBA_RECEIVE, INITIATOR_BIT(del_ba_param_set)); +} + +/* Retrieve Rx reordering table. */ +int nxpwifi_get_rx_reorder_tbl(struct nxpwifi_private *priv, + struct nxpwifi_ds_rx_reorder_tbl *buf) +{ + int i, j; + struct nxpwifi_ds_rx_reorder_tbl *rx_reo_tbl = buf; + struct nxpwifi_rx_reorder_tbl *rx_reorder_tbl_ptr; + int count = 0; + + guard(rcu)(); + for (j = 0; j < MAX_NUM_TID; j++) { + list_for_each_entry_rcu(rx_reorder_tbl_ptr, + &priv->rx_reorder_tbl_ptr[j], + list) { + rx_reo_tbl->tid = (u16)rx_reorder_tbl_ptr->tid; + memcpy(rx_reo_tbl->ta, rx_reorder_tbl_ptr->ta, ETH_ALEN); + rx_reo_tbl->start_win = rx_reorder_tbl_ptr->start_win; + rx_reo_tbl->win_size = rx_reorder_tbl_ptr->win_size; + for (i = 0; i < rx_reorder_tbl_ptr->win_size; ++i) { + if (rx_reorder_tbl_ptr->rx_reorder_ptr[i]) + rx_reo_tbl->buffer[i] = true; + else + rx_reo_tbl->buffer[i] = false; + } + rx_reo_tbl++; + count++; + + if (count >= NXPWIFI_MAX_RX_BASTREAM_SUPPORTED) + return count; + } + } + + return count; +} + +/* Retrieve Tx BA stream table. */ +int nxpwifi_get_tx_ba_stream_tbl(struct nxpwifi_private *priv, + struct nxpwifi_ds_tx_ba_stream_tbl *buf) +{ + struct nxpwifi_tx_ba_stream_tbl *tx_ba_tsr_tbl; + struct nxpwifi_ds_tx_ba_stream_tbl *rx_reo_tbl = buf; + int count = 0; + int i; + + guard(rcu)(); + for (i = 0; i < MAX_NUM_TID; i++) { + list_for_each_entry_rcu(tx_ba_tsr_tbl, &priv->tx_ba_stream_tbl_ptr[i], list) { + rx_reo_tbl->tid = (u16)tx_ba_tsr_tbl->tid; + nxpwifi_dbg(priv->adapter, DATA, "data: %s tid=%d\n", + __func__, rx_reo_tbl->tid); + memcpy(rx_reo_tbl->ra, tx_ba_tsr_tbl->ra, ETH_ALEN); + rx_reo_tbl->amsdu = tx_ba_tsr_tbl->amsdu; + rx_reo_tbl++; + count++; + if (count >= NXPWIFI_MAX_TX_BASTREAM_SUPPORTED) + return count; + } + } + + return count; +} + +/* Delete Tx BA stream entry by RA. */ +void nxpwifi_del_tx_ba_stream_tbl_by_ra(struct nxpwifi_private *priv, u8 *ra) +{ + struct nxpwifi_tx_ba_stream_tbl *tbl; + int i; + + if (!ra) + return; + + for (i = 0; i < MAX_NUM_TID; i++) { + spin_lock_bh(&priv->tx_ba_stream_tbl_lock[i]); + list_for_each_entry_rcu(tbl, &priv->tx_ba_stream_tbl_ptr[i], list) + if (!memcmp(tbl->ra, ra, ETH_ALEN)) + nxpwifi_11n_delete_tx_ba_stream_tbl_entry(priv, tbl); + + spin_unlock_bh(&priv->tx_ba_stream_tbl_lock[i]); + } +} + +/* Initialize BlockAck parameters. */ +void nxpwifi_set_ba_params(struct nxpwifi_private *priv) +{ + priv->add_ba_param.timeout = NXPWIFI_DEFAULT_BLOCK_ACK_TIMEOUT; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + priv->add_ba_param.tx_win_size = + NXPWIFI_UAP_AMPDU_DEF_TXWINSIZE; + priv->add_ba_param.rx_win_size = + NXPWIFI_UAP_AMPDU_DEF_RXWINSIZE; + } else { + priv->add_ba_param.tx_win_size = + NXPWIFI_STA_AMPDU_DEF_TXWINSIZE; + priv->add_ba_param.rx_win_size = + NXPWIFI_STA_AMPDU_DEF_RXWINSIZE; + } + + priv->add_ba_param.tx_amsdu = true; + priv->add_ba_param.rx_amsdu = true; +} + +u8 nxpwifi_get_sec_chan_offset(int chan) +{ + u8 sec_offset; + + switch (chan) { + case 36: + case 44: + case 52: + case 60: + case 100: + case 108: + case 116: + case 124: + case 132: + case 140: + case 149: + case 157: + case 173: + sec_offset = IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + break; + case 40: + case 48: + case 56: + case 64: + case 104: + case 112: + case 120: + case 128: + case 136: + case 144: + case 153: + case 161: + case 169: + case 177: + sec_offset = IEEE80211_HT_PARAM_CHA_SEC_BELOW; + break; + case 165: + default: + sec_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE; + break; + } + + return sec_offset; +} + +/* Send DELBA to entries in the Tx BA stream table. */ +static void +nxpwifi_send_delba_txbastream_tbl(struct nxpwifi_private *priv, u8 tid) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_tx_ba_stream_tbl *tx_ba_stream_tbl_ptr; + + guard(rcu)(); + list_for_each_entry_rcu(tx_ba_stream_tbl_ptr, + &priv->tx_ba_stream_tbl_ptr[tid], list) { + if (tx_ba_stream_tbl_ptr->ba_status == BA_SETUP_COMPLETE) { + if (tid == tx_ba_stream_tbl_ptr->tid) { + nxpwifi_dbg(adapter, INFO, + "Tx:Send delba to tid=%d, %pM\n", tid, + tx_ba_stream_tbl_ptr->ra); + nxpwifi_send_delba(priv, + tx_ba_stream_tbl_ptr->tid, + tx_ba_stream_tbl_ptr->ra, 1); + break; + } + } + } +} + +/* + * Update tx_win_size for all interfaces and send DELBA when it changes. + */ +void nxpwifi_update_ampdu_txwinsize(struct nxpwifi_adapter *adapter) +{ + u8 i, j; + u32 tx_win_size; + struct nxpwifi_private *priv; + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + tx_win_size = priv->add_ba_param.tx_win_size; + + if (priv->bss_type == NXPWIFI_BSS_TYPE_STA) + priv->add_ba_param.tx_win_size = + NXPWIFI_STA_AMPDU_DEF_TXWINSIZE; + + if (priv->bss_type == NXPWIFI_BSS_TYPE_UAP) + priv->add_ba_param.tx_win_size = + NXPWIFI_UAP_AMPDU_DEF_TXWINSIZE; + + if (adapter->coex_win_size) { + if (adapter->coex_tx_win_size) + priv->add_ba_param.tx_win_size = + adapter->coex_tx_win_size; + } + + if (tx_win_size != priv->add_ba_param.tx_win_size) { + if (!priv->media_connected) + continue; + for (j = 0; j < MAX_NUM_TID; j++) + nxpwifi_send_delba_txbastream_tbl(priv, j); + } + } +} diff --git a/drivers/net/wireless/nxp/nxpwifi/11n.h b/drivers/net/wireless/nxp/nxpwifi/11n.h new file mode 100644 index 000000000000..039c45993f07 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11n.h @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * nxpwifi: 802.11n support + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_11N_H_ +#define _NXPWIFI_11N_H_ + +#include "11n_aggr.h" +#include "11n_rxreorder.h" +#include "wmm.h" + +int nxpwifi_ret_11n_delba(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp); +int nxpwifi_ret_11n_addba_req(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp); +int nxpwifi_cmd_11n_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_ds_11n_tx_cfg *txcfg); +int nxpwifi_cmd_append_11n_tlv(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, + u8 **buffer); +int nxpwifi_fill_cap_info(struct nxpwifi_private *priv, u8 radio_type, + struct ieee80211_ht_cap *ht_cap); +int nxpwifi_set_get_11n_htcap_cfg(struct nxpwifi_private *priv, + u16 action, int *htcap_cfg); +void nxpwifi_11n_delete_tx_ba_stream_tbl_entry(struct nxpwifi_private *priv, + struct nxpwifi_tx_ba_stream_tbl + *tx_tbl); +void nxpwifi_11n_delete_all_tx_ba_stream_tbl(struct nxpwifi_private *priv); +struct nxpwifi_tx_ba_stream_tbl *nxpwifi_get_ba_tbl(struct nxpwifi_private + *priv, int tid, u8 *ra); +void nxpwifi_create_ba_tbl(struct nxpwifi_private *priv, u8 *ra, int tid, + enum nxpwifi_ba_status ba_status); +int nxpwifi_send_addba(struct nxpwifi_private *priv, int tid, u8 *peer_mac); +int nxpwifi_send_delba(struct nxpwifi_private *priv, int tid, u8 *peer_mac, + int initiator); +void nxpwifi_11n_delete_ba_stream(struct nxpwifi_private *priv, u8 *del_ba); +int nxpwifi_get_rx_reorder_tbl(struct nxpwifi_private *priv, + struct nxpwifi_ds_rx_reorder_tbl *buf); +int nxpwifi_get_tx_ba_stream_tbl(struct nxpwifi_private *priv, + struct nxpwifi_ds_tx_ba_stream_tbl *buf); +int nxpwifi_cmd_recfg_tx_buf(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + int cmd_action, u16 *buf_size); +int nxpwifi_cmd_amsdu_aggr_ctrl(struct host_cmd_ds_command *cmd, + int cmd_action, + struct nxpwifi_ds_11n_amsdu_aggr_ctrl *aa_ctrl); +void nxpwifi_del_tx_ba_stream_tbl_by_ra(struct nxpwifi_private *priv, u8 *ra); +u8 nxpwifi_get_sec_chan_offset(int chan); + +static inline bool +nxpwifi_is_station_ampdu_allowed(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ptr, int tid) +{ + struct nxpwifi_sta_node *node; + + guard(rcu)(); + node = nxpwifi_get_sta_entry(priv, ptr->ra); + if (unlikely(!node)) + return false; + + if (node->ampdu_sta[tid] == BA_STREAM_NOT_ALLOWED) + return false; + + return true; +} + +/* Check if AMPDU is allowed for the given TID. */ +static inline bool +nxpwifi_is_ampdu_allowed(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ptr, int tid) +{ + if (is_broadcast_ether_addr(ptr->ra)) + return false; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) + return nxpwifi_is_station_ampdu_allowed(priv, ptr, tid); + + return priv->aggr_prio_tbl[tid].ampdu_ap != BA_STREAM_NOT_ALLOWED; +} + +/* Check if AMSDU is allowed for the given TID. */ +static inline bool +nxpwifi_is_amsdu_allowed(struct nxpwifi_private *priv, int tid) +{ + bool amsdu_enabled = priv->aggr_prio_tbl[tid].amsdu != BA_STREAM_NOT_ALLOWED; + bool rate_ok = priv->is_data_rate_auto || !(priv->bitmap_rates[2] & 0x03); + + return amsdu_enabled && rate_ok; +} + +/* Check if there is available space for a new BA stream. */ +static inline bool +nxpwifi_space_avail_for_new_ba_stream(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + u8 i, j; + size_t ba_stream_num = 0; + size_t ba_stream_max = NXPWIFI_MAX_TX_BASTREAM_SUPPORTED; + + if (adapter->fw_api_ver == NXPWIFI_FW_V15) { + ba_stream_max = GETSUPP_TXBASTREAMS(adapter->hw_dot_11n_dev_cap); + if (!ba_stream_max) + ba_stream_max = NXPWIFI_MAX_TX_BASTREAM_SUPPORTED; + } + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + for (j = 0; j < MAX_NUM_TID; j++) + ba_stream_num += list_count_nodes(&priv->tx_ba_stream_tbl_ptr[j]); + } + + return ba_stream_num < ba_stream_max; +} + +/* Find the Tx BA stream to delete and return its TID and RA. */ +static inline bool +nxpwifi_find_stream_to_delete(struct nxpwifi_private *priv, int ptr_tid, + int *ptid, u8 *ra) +{ + int search_tid = priv->aggr_prio_tbl[ptr_tid].ampdu_user; + bool found = false; + struct nxpwifi_tx_ba_stream_tbl *tx_tbl; + int candidate_tid; + + spin_lock_bh(&priv->tx_ba_stream_tbl_lock[ptr_tid]); + + list_for_each_entry(tx_tbl, &priv->tx_ba_stream_tbl_ptr[ptr_tid], list) { + candidate_tid = priv->aggr_prio_tbl[tx_tbl->tid].ampdu_user; + + if (search_tid > candidate_tid) { + search_tid = candidate_tid; + *ptid = tx_tbl->tid; + memcpy(ra, tx_tbl->ra, ETH_ALEN); + found = true; + } + } + + spin_unlock_bh(&priv->tx_ba_stream_tbl_lock[ptr_tid]); + + return found; +} + +/* Check whether the associated station is 11n enabled. */ +static inline int nxpwifi_is_sta_11n_enabled(struct nxpwifi_private *priv, + struct nxpwifi_sta_node *node) +{ + if (!node || (priv->bss_role == NXPWIFI_BSS_ROLE_UAP && + !priv->ap_11n_enabled)) + return 0; + + return node->is_11n_enabled; +} + +#endif /* !_NXPWIFI_11N_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c new file mode 100644 index 000000000000..be7080f2a6ce --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: 802.11n Aggregation + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" +#include "11n_aggr.h" + +/* + * Build an AMSDU subframe for aggregation, with fields + * (DA | SA | Length | SNAP header | MSDU), and compute padding + * to align the subframe to a 4-byte boundary. + */ +static int nxpwifi_11n_form_amsdu_pkt(struct sk_buff *skb_aggr, + struct sk_buff *skb_src, int *pad) + +{ + int dt_offset; + struct rfc_1042_hdr snap = { + 0xaa, /* LLC DSAP */ + 0xaa, /* LLC SSAP */ + 0x03, /* LLC CTRL */ + {0x00, 0x00, 0x00}, /* SNAP OUI */ + 0x0000 /* SNAP type */ + /* This field will be overwritten later with ethertype */ + }; + struct tx_packet_hdr *tx_header; + + tx_header = skb_put(skb_aggr, sizeof(*tx_header)); + + /* Copy DA and SA */ + dt_offset = 2 * ETH_ALEN; + memcpy(&tx_header->eth803_hdr, skb_src->data, dt_offset); + + /* Copy SNAP header */ + snap.snap_type = ((struct ethhdr *)skb_src->data)->h_proto; + + dt_offset += sizeof(__be16); + + memcpy(&tx_header->rfc1042_hdr, &snap, sizeof(struct rfc_1042_hdr)); + + skb_pull(skb_src, dt_offset); + + /* Update Length field */ + tx_header->eth803_hdr.h_proto = htons(skb_src->len + LLC_SNAP_LEN); + + /* Add payload */ + skb_put_data(skb_aggr, skb_src->data, skb_src->len); + + /* Add padding for new MSDU to start from 4 byte boundary */ + *pad = (4 - ((unsigned long)skb_aggr->tail & 0x3)) % 4; + + return skb_aggr->len + *pad; +} + +/* + * Adds TxPD to AMSDU header. Each AMSDU packet will contain one TxPD at the + * beginning, followed by multiple AMSDU subframes + */ +static void +nxpwifi_11n_form_amsdu_txpd(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct txpd *local_tx_pd; + + skb_push(skb, sizeof(*local_tx_pd)); + + local_tx_pd = (struct txpd *)skb->data; + memset(local_tx_pd, 0, sizeof(struct txpd)); + + /* Original priority has been overwritten */ + local_tx_pd->priority = (u8)skb->priority; + local_tx_pd->pkt_delay_2ms = + nxpwifi_wmm_compute_drv_pkt_delay(priv, skb); + local_tx_pd->bss_num = priv->bss_num; + local_tx_pd->bss_type = priv->bss_type; + /* Always zero as the data is followed by struct txpd */ + local_tx_pd->tx_pkt_offset = cpu_to_le16(sizeof(struct txpd)); + local_tx_pd->tx_pkt_type = cpu_to_le16(PKT_TYPE_AMSDU); + local_tx_pd->tx_pkt_length = cpu_to_le16(skb->len - + sizeof(*local_tx_pd)); + + if (local_tx_pd->tx_control == 0) + /* TxCtrl set by user or default */ + local_tx_pd->tx_control = cpu_to_le32(priv->pkt_tx_ctrl); + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA && + priv->adapter->pps_uapsd_mode) { + if (nxpwifi_check_last_packet_indication(priv)) { + priv->adapter->tx_lock_flag = true; + local_tx_pd->flags = + NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET; + } + } +} + +/* + * Build an aggregated MSDU packet by encapsulating buffers from the RA + * list as AMSDU subframes and concatenating them. A TxPD is prepended + * before transmission to form the final AMSDU packet. + */ +int +nxpwifi_11n_aggregate_pkt(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *pra_list, + int ptrindex) + __releases(&priv->wmm.ra_list_spinlock) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct sk_buff *skb_aggr, *skb_src; + struct nxpwifi_txinfo *tx_info_aggr, *tx_info_src; + int pad = 0, aggr_num = 0, ret; + struct nxpwifi_tx_param tx_param; + struct txpd *ptx_pd = NULL; + int headroom = adapter->intf_hdr_len; + + skb_src = skb_peek(&pra_list->skb_head); + if (!skb_src) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + return 0; + } + + tx_info_src = NXPWIFI_SKB_TXCB(skb_src); + skb_aggr = nxpwifi_alloc_dma_align_buf(adapter->tx_buf_size, + GFP_ATOMIC); + if (!skb_aggr) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + return -ENOMEM; + } + + /* + * skb_aggr->data already 64 byte align, just reserve bus interface + * header and txpd. + */ + skb_reserve(skb_aggr, headroom + sizeof(struct txpd)); + tx_info_aggr = NXPWIFI_SKB_TXCB(skb_aggr); + + memset(tx_info_aggr, 0, sizeof(*tx_info_aggr)); + tx_info_aggr->bss_type = tx_info_src->bss_type; + tx_info_aggr->bss_num = tx_info_src->bss_num; + + tx_info_aggr->flags |= NXPWIFI_BUF_FLAG_AGGR_PKT; + skb_aggr->priority = skb_src->priority; + skb_aggr->tstamp = skb_src->tstamp; + + do { + /* Check if AMSDU can accommodate this MSDU */ + if ((skb_aggr->len + skb_src->len + LLC_SNAP_LEN) > + adapter->tx_buf_size) + break; + + skb_src = skb_dequeue(&pra_list->skb_head); + pra_list->total_pkt_count--; + atomic_dec(&priv->wmm.tx_pkts_queued); + aggr_num++; + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_11n_form_amsdu_pkt(skb_aggr, skb_src, &pad); + + nxpwifi_write_data_complete(adapter, skb_src, 0, 0); + + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + if (!nxpwifi_is_ralist_valid(priv, pra_list, ptrindex)) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + return -ENOENT; + } + + if (skb_tailroom(skb_aggr) < pad) { + pad = 0; + break; + } + skb_put(skb_aggr, pad); + + skb_src = skb_peek(&pra_list->skb_head); + + } while (skb_src); + + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + + /* Last AMSDU packet does not need padding */ + skb_trim(skb_aggr, skb_aggr->len - pad); + + /* Form AMSDU */ + nxpwifi_11n_form_amsdu_txpd(priv, skb_aggr); + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) + ptx_pd = (struct txpd *)skb_aggr->data; + + skb_push(skb_aggr, headroom); + tx_info_aggr->aggr_num = aggr_num * 2; + if (adapter->data_sent || adapter->tx_lock_flag) { + atomic_add(aggr_num * 2, &adapter->tx_queued); + skb_queue_tail(&adapter->tx_data_q, skb_aggr); + return 0; + } + + if (skb_src) + tx_param.next_pkt_len = skb_src->len + sizeof(struct txpd); + else + tx_param.next_pkt_len = 0; + + ret = adapter->if_ops.host_to_card(adapter, NXPWIFI_TYPE_DATA, + skb_aggr, &tx_param); + + switch (ret) { + case -EBUSY: + spin_lock_bh(&priv->wmm.ra_list_spinlock); + if (!nxpwifi_is_ralist_valid(priv, pra_list, ptrindex)) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_write_data_complete(adapter, skb_aggr, 1, -1); + return -EINVAL; + } + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA && + adapter->pps_uapsd_mode && adapter->tx_lock_flag) { + priv->adapter->tx_lock_flag = false; + if (ptx_pd) + ptx_pd->flags = 0; + } + + skb_queue_tail(&pra_list->skb_head, skb_aggr); + + pra_list->total_pkt_count++; + + atomic_inc(&priv->wmm.tx_pkts_queued); + + tx_info_aggr->flags |= NXPWIFI_BUF_FLAG_REQUEUED_PKT; + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_dbg(adapter, ERROR, "data: -EBUSY is returned\n"); + break; + case -EINPROGRESS: + break; + case 0: + nxpwifi_write_data_complete(adapter, skb_aggr, 1, ret); + break; + default: + nxpwifi_dbg(adapter, ERROR, "%s: host_to_card failed: %#x\n", + __func__, ret); + adapter->dbg.num_tx_host_to_card_failure++; + nxpwifi_write_data_complete(adapter, skb_aggr, 1, ret); + break; + } + if (ret != -EBUSY) + nxpwifi_rotate_priolists(priv, pra_list, ptrindex); + + return 0; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_aggr.h b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.h new file mode 100644 index 000000000000..be9f0f8f4e48 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * NXP Wireless LAN device driver: 802.11n Aggregation + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_11N_AGGR_H_ +#define _NXPWIFI_11N_AGGR_H_ + +#define PKT_TYPE_AMSDU 0xE6 +#define MIN_NUM_AMSDU 2 + +int nxpwifi_11n_deaggregate_pkt(struct nxpwifi_private *priv, + struct sk_buff *skb); +int nxpwifi_11n_aggregate_pkt(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ptr, + int ptr_index) + __releases(&priv->wmm.ra_list_spinlock); + +#endif /* !_NXPWIFI_11N_AGGR_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c new file mode 100644 index 000000000000..c5819f89b08c --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c @@ -0,0 +1,826 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: 802.11n RX Re-ordering + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" +#include "11n_rxreorder.h" +/* Dispatch A-MSDU to stack. */ +static int nxpwifi_11n_dispatch_amsdu_pkt(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct rxpd *local_rx_pd = (struct rxpd *)(skb->data); + int ret; + + if (le16_to_cpu(local_rx_pd->rx_pkt_type) == PKT_TYPE_AMSDU) { + struct sk_buff_head list; + struct sk_buff *rx_skb; + + __skb_queue_head_init(&list); + + skb_pull(skb, le16_to_cpu(local_rx_pd->rx_pkt_offset)); + skb_trim(skb, le16_to_cpu(local_rx_pd->rx_pkt_length)); + + ieee80211_amsdu_to_8023s(skb, &list, priv->curr_addr, + priv->wdev.iftype, 0, NULL, NULL, false); + + while (!skb_queue_empty(&list)) { + rx_skb = __skb_dequeue(&list); + + if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP) + ret = nxpwifi_uap_recv_packet(priv, rx_skb); + else + ret = nxpwifi_recv_packet(priv, rx_skb); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "Rx of A-MSDU failed"); + } + return 0; + } + + return -EINVAL; +} + +/* Process RX packet and forward to stack. */ +static int nxpwifi_11n_dispatch_pkt(struct nxpwifi_private *priv, + struct sk_buff *payload) +{ + int ret; + + if (!payload) { + nxpwifi_dbg(priv->adapter, INFO, "info: fw drop data\n"); + return 0; + } + + ret = nxpwifi_11n_dispatch_amsdu_pkt(priv, payload); + if (!ret) + return 0; + + if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP) + return nxpwifi_handle_uap_rx_forward(priv, payload); + + return nxpwifi_process_rx_packet(priv, payload); +} + +/* Dispatch packets up to start_win. */ +static void +nxpwifi_11n_dispatch_pkt_until_start_win(struct nxpwifi_private *priv, + struct nxpwifi_rx_reorder_tbl *tbl, + int start_win) +{ + struct sk_buff_head list; + struct sk_buff *skb; + int pkt_to_send, i, tid; + + tid = tbl->tid; + __skb_queue_head_init(&list); + spin_lock_bh(&priv->rx_reorder_tbl_lock[tid]); + + pkt_to_send = (start_win > tbl->start_win) ? + min((start_win - tbl->start_win), tbl->win_size) : + tbl->win_size; + + for (i = 0; i < pkt_to_send; ++i) { + if (tbl->rx_reorder_ptr[i]) { + skb = tbl->rx_reorder_ptr[i]; + __skb_queue_tail(&list, skb); + tbl->rx_reorder_ptr[i] = NULL; + } + } + + /* Simulate circular buffer via rotation. */ + for (i = 0; i < tbl->win_size - pkt_to_send; ++i) { + tbl->rx_reorder_ptr[i] = tbl->rx_reorder_ptr[pkt_to_send + i]; + tbl->rx_reorder_ptr[pkt_to_send + i] = NULL; + } + + tbl->start_win = start_win; + spin_unlock_bh(&priv->rx_reorder_tbl_lock[tid]); + + while ((skb = __skb_dequeue(&list))) + nxpwifi_11n_dispatch_pkt(priv, skb); +} + +/* Dispatch packets until a hole is found. */ +static void +nxpwifi_11n_scan_and_dispatch(struct nxpwifi_private *priv, + struct nxpwifi_rx_reorder_tbl *tbl) +{ + struct sk_buff_head list; + struct sk_buff *skb; + int i, j, xchg, tid; + + tid = tbl->tid; + __skb_queue_head_init(&list); + spin_lock_bh(&priv->rx_reorder_tbl_lock[tid]); + + for (i = 0; i < tbl->win_size; ++i) { + if (!tbl->rx_reorder_ptr[i]) + break; + skb = tbl->rx_reorder_ptr[i]; + __skb_queue_tail(&list, skb); + tbl->rx_reorder_ptr[i] = NULL; + } + + /* Simulate circular buffer via rotation. */ + if (i > 0) { + xchg = tbl->win_size - i; + for (j = 0; j < xchg; ++j) { + tbl->rx_reorder_ptr[j] = tbl->rx_reorder_ptr[i + j]; + tbl->rx_reorder_ptr[i + j] = NULL; + } + } + tbl->start_win = (tbl->start_win + i) & (MAX_TID_VALUE - 1); + + spin_unlock_bh(&priv->rx_reorder_tbl_lock[tid]); + + while ((skb = __skb_dequeue(&list))) + nxpwifi_11n_dispatch_pkt(priv, skb); +} + +/* Delete RX reorder entry and flush pending packets. */ +static void +nxpwifi_del_rx_reorder_entry(struct nxpwifi_private *priv, + struct nxpwifi_rx_reorder_tbl *tbl) +{ + int start_win, tid; + + if (!tbl) + return; + + tid = tbl->tid; + + atomic_set(&priv->adapter->rx_ba_teardown_pending, 1); + flush_workqueue(priv->adapter->rx_workqueue); + + start_win = (tbl->start_win + tbl->win_size) & (MAX_TID_VALUE - 1); + nxpwifi_11n_dispatch_pkt_until_start_win(priv, tbl, start_win); + + timer_delete_sync(&tbl->timer_context.timer); + tbl->timer_context.timer_is_set = false; + + spin_lock_bh(&priv->rx_reorder_tbl_lock[tid]); + list_del_rcu(&tbl->list); + spin_unlock_bh(&priv->rx_reorder_tbl_lock[tid]); + + kfree(tbl->rx_reorder_ptr); + kfree_rcu(tbl, rcu); + + atomic_set(&priv->adapter->rx_ba_teardown_pending, 0); +} + +/* Lookup RX reorder entry by TID/TA. */ +struct nxpwifi_rx_reorder_tbl * +nxpwifi_11n_get_rx_reorder_tbl(struct nxpwifi_private *priv, int tid, u8 *ta) +{ + struct nxpwifi_rx_reorder_tbl *tbl, *found = NULL; + + guard(rcu)(); + + list_for_each_entry_rcu(tbl, &priv->rx_reorder_tbl_ptr[tid], list) { + if (!memcmp(tbl->ta, ta, ETH_ALEN) && tbl->tid == tid) { + found = tbl; + break; + } + } + + return found; +} + +/* Delete RX reorder entries by TA. */ +void nxpwifi_11n_del_rx_reorder_tbl_by_ta(struct nxpwifi_private *priv, u8 *ta) +{ + struct nxpwifi_rx_reorder_tbl *tbl, *tmp; + LIST_HEAD(to_delete); + int i; + + if (!ta) + return; + + for (i = 0; i < MAX_NUM_TID; i++) { + guard(rcu)(); + list_for_each_entry_rcu(tbl, &priv->rx_reorder_tbl_ptr[i], list) { + if (!memcmp(tbl->ta, ta, ETH_ALEN)) { + INIT_LIST_HEAD(&tbl->tmp_list); + list_add_tail(&tbl->tmp_list, &to_delete); + } + } + + list_for_each_entry_safe(tbl, tmp, &to_delete, tmp_list) + nxpwifi_del_rx_reorder_entry(priv, tbl); + + INIT_LIST_HEAD(&to_delete); + } +} + +/* Find last buffered sequence index. */ +static int +nxpwifi_11n_find_last_seq_num(struct reorder_tmr_cnxt *ctx) +{ + struct nxpwifi_rx_reorder_tbl *rx_reorder_tbl_ptr = ctx->ptr; + int i; + + guard(rcu)(); + for (i = rx_reorder_tbl_ptr->win_size - 1; i >= 0; --i) { + if (rx_reorder_tbl_ptr->rx_reorder_ptr[i]) + return i; + } + + return -EINVAL; +} + +/* Flush and dispatch buffered packets on timer. */ +static void +nxpwifi_flush_data(struct timer_list *t) +{ + struct reorder_tmr_cnxt *ctx = + timer_container_of(ctx, t, timer); + int start_win, seq_num; + + ctx->timer_is_set = false; + seq_num = nxpwifi_11n_find_last_seq_num(ctx); + + if (seq_num < 0) + return; + + nxpwifi_dbg(ctx->priv->adapter, INFO, "info: flush data %d\n", seq_num); + start_win = (ctx->ptr->start_win + seq_num + 1) & (MAX_TID_VALUE - 1); + nxpwifi_11n_dispatch_pkt_until_start_win(ctx->priv, ctx->ptr, + start_win); +} + +/* Create RX reorder entry (TID/TA, SSN, winsize, timer). */ +static void +nxpwifi_11n_create_rx_reorder_tbl(struct nxpwifi_private *priv, u8 *ta, + int tid, int win_size, int seq_num) +{ + int i; + struct nxpwifi_rx_reorder_tbl *tbl, *new_node; + u16 last_seq = 0; + struct nxpwifi_sta_node *node; + + /* Existing TID/TA: flush and move window to SSN. */ + tbl = nxpwifi_11n_get_rx_reorder_tbl(priv, tid, ta); + if (tbl) { + nxpwifi_11n_dispatch_pkt_until_start_win(priv, tbl, seq_num); + return; + } + /* if !tbl then create one */ + new_node = kzalloc_obj(*new_node, GFP_KERNEL); + if (!new_node) + return; + + INIT_LIST_HEAD(&new_node->list); + new_node->tid = tid; + memcpy(new_node->ta, ta, ETH_ALEN); + new_node->start_win = seq_num; + new_node->init_win = seq_num; + new_node->flags = 0; + + if (nxpwifi_queuing_ra_based(priv)) { + if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP) { + guard(rcu)(); + node = nxpwifi_get_sta_entry(priv, ta); + if (node) + last_seq = node->rx_seq[tid]; + } + } else { + guard(rcu)(); + node = nxpwifi_get_sta_entry(priv, ta); + if (node) + last_seq = node->rx_seq[tid]; + else + last_seq = priv->rx_seq[tid]; + } + + nxpwifi_dbg(priv->adapter, INFO, + "info: last_seq=%d start_win=%d\n", + last_seq, new_node->start_win); + + if (last_seq != NXPWIFI_DEF_11N_RX_SEQ_NUM && + last_seq >= new_node->start_win) { + new_node->start_win = last_seq + 1; + new_node->flags |= RXREOR_INIT_WINDOW_SHIFT; + } + + new_node->win_size = win_size; + + new_node->rx_reorder_ptr = kcalloc(win_size, sizeof(void *), + GFP_KERNEL); + if (!new_node->rx_reorder_ptr) { + kfree(new_node); + nxpwifi_dbg(priv->adapter, ERROR, + "%s: failed to alloc reorder_ptr\n", __func__); + return; + } + + new_node->timer_context.ptr = new_node; + new_node->timer_context.priv = priv; + new_node->timer_context.timer_is_set = false; + + timer_setup(&new_node->timer_context.timer, nxpwifi_flush_data, 0); + + for (i = 0; i < win_size; ++i) + new_node->rx_reorder_ptr[i] = NULL; + + spin_lock_bh(&priv->rx_reorder_tbl_lock[tid]); + list_add_tail_rcu(&new_node->list, &priv->rx_reorder_tbl_ptr[tid]); + spin_unlock_bh(&priv->rx_reorder_tbl_lock[tid]); +} + +static void +nxpwifi_11n_rxreorder_timer_restart(struct nxpwifi_rx_reorder_tbl *tbl) +{ + u32 min_flush_time; + + if (tbl->win_size >= NXPWIFI_BA_WIN_SIZE_32) + min_flush_time = MIN_FLUSH_TIMER_15_MS; + else + min_flush_time = MIN_FLUSH_TIMER_MS; + + mod_timer(&tbl->timer_context.timer, + jiffies + msecs_to_jiffies(min_flush_time * tbl->win_size)); + + tbl->timer_context.timer_is_set = true; +} + +/* Prepare ADDBA request. */ +int nxpwifi_cmd_11n_addba_req(struct host_cmd_ds_command *cmd, void *data_buf) +{ + struct host_cmd_ds_11n_addba_req *add_ba_req = &cmd->params.add_ba_req; + + cmd->command = cpu_to_le16(HOST_CMD_11N_ADDBA_REQ); + cmd->size = cpu_to_le16(sizeof(*add_ba_req) + S_DS_GEN); + memcpy(add_ba_req, data_buf, sizeof(*add_ba_req)); + + return 0; +} + +/* Prepare ADDBA response and create RX reorder table. */ +int nxpwifi_cmd_11n_addba_rsp_gen(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + struct host_cmd_ds_11n_addba_req + *cmd_addba_req) +{ + struct host_cmd_ds_11n_addba_rsp *add_ba_rsp = &cmd->params.add_ba_rsp; + u32 rx_win_size = priv->add_ba_param.rx_win_size; + u8 tid; + int win_size; + u16 block_ack_param_set; + + cmd->command = cpu_to_le16(HOST_CMD_11N_ADDBA_RSP); + cmd->size = cpu_to_le16(sizeof(*add_ba_rsp) + S_DS_GEN); + + memcpy(add_ba_rsp->peer_mac_addr, cmd_addba_req->peer_mac_addr, + ETH_ALEN); + add_ba_rsp->dialog_token = cmd_addba_req->dialog_token; + add_ba_rsp->block_ack_tmo = cmd_addba_req->block_ack_tmo; + add_ba_rsp->ssn = cmd_addba_req->ssn; + + block_ack_param_set = le16_to_cpu(cmd_addba_req->block_ack_param_set); + tid = (block_ack_param_set & IEEE80211_ADDBA_PARAM_TID_MASK) + >> BLOCKACKPARAM_TID_POS; + add_ba_rsp->status_code = cpu_to_le16(ADDBA_RSP_STATUS_ACCEPT); + block_ack_param_set &= ~IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK; + + /* If we don't support AMSDU inside AMPDU, reset the bit */ + if (!priv->add_ba_param.rx_amsdu || + priv->aggr_prio_tbl[tid].amsdu == BA_STREAM_NOT_ALLOWED) + block_ack_param_set &= ~IEEE80211_ADDBA_PARAM_AMSDU_MASK; + block_ack_param_set |= rx_win_size << BLOCKACKPARAM_WINSIZE_POS; + add_ba_rsp->block_ack_param_set = cpu_to_le16(block_ack_param_set); + win_size = (le16_to_cpu(add_ba_rsp->block_ack_param_set) + & IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK) + >> BLOCKACKPARAM_WINSIZE_POS; + cmd_addba_req->block_ack_param_set = cpu_to_le16(block_ack_param_set); + + nxpwifi_11n_create_rx_reorder_tbl(priv, cmd_addba_req->peer_mac_addr, + tid, win_size, + le16_to_cpu(cmd_addba_req->ssn)); + return 0; +} + +/* Prepare DELBA command. */ +int nxpwifi_cmd_11n_delba(struct host_cmd_ds_command *cmd, void *data_buf) +{ + struct host_cmd_ds_11n_delba *del_ba = &cmd->params.del_ba; + + cmd->command = cpu_to_le16(HOST_CMD_11N_DELBA); + cmd->size = cpu_to_le16(sizeof(*del_ba) + S_DS_GEN); + memcpy(del_ba, data_buf, sizeof(*del_ba)); + + return 0; +} + +/* Decide and perform RX reordering for a packet. */ +int nxpwifi_11n_rx_reorder_pkt(struct nxpwifi_private *priv, + u16 seq_num, u16 tid, + u8 *ta, u8 pkt_type, void *payload) +{ + struct nxpwifi_rx_reorder_tbl *tbl; + int prev_start_win, start_win, end_win, win_size; + u16 pkt_index; + bool init_window_shift = false; + int ret = 0; + + tbl = nxpwifi_11n_get_rx_reorder_tbl(priv, tid, ta); + if (!tbl) { + if (pkt_type != PKT_TYPE_BAR) + nxpwifi_11n_dispatch_pkt(priv, payload); + return ret; + } + + if (pkt_type == PKT_TYPE_AMSDU && !tbl->amsdu) { + nxpwifi_11n_dispatch_pkt(priv, payload); + return ret; + } + + start_win = tbl->start_win; + prev_start_win = start_win; + win_size = tbl->win_size; + end_win = ((start_win + win_size) - 1) & (MAX_TID_VALUE - 1); + if (tbl->flags & RXREOR_INIT_WINDOW_SHIFT) { + init_window_shift = true; + tbl->flags &= ~RXREOR_INIT_WINDOW_SHIFT; + } + + if (tbl->flags & RXREOR_FORCE_NO_DROP) { + nxpwifi_dbg(priv->adapter, INFO, + "RXREOR_FORCE_NO_DROP when HS is activated\n"); + tbl->flags &= ~RXREOR_FORCE_NO_DROP; + } else if (init_window_shift && seq_num < start_win && + seq_num >= tbl->init_win) { + nxpwifi_dbg(priv->adapter, INFO, + "Sender TID sequence number reset %d->%d for SSN %d\n", + start_win, seq_num, tbl->init_win); + start_win = seq_num; + tbl->start_win = start_win; + end_win = ((start_win + win_size) - 1) & (MAX_TID_VALUE - 1); + } else { + /* Drop packet if seq_num < start_win. */ + if ((start_win + TWOPOW11) > (MAX_TID_VALUE - 1)) { + if (seq_num >= ((start_win + TWOPOW11) & + (MAX_TID_VALUE - 1)) && + seq_num < start_win) { + ret = -EINVAL; + goto done; + } + } else if ((seq_num < start_win) || + (seq_num >= (start_win + TWOPOW11))) { + ret = -EINVAL; + goto done; + } + } + + /* Adjust seq_num for BAR (WinStart = seq_num). */ + if (pkt_type == PKT_TYPE_BAR) + seq_num = ((seq_num + win_size) - 1) & (MAX_TID_VALUE - 1); + + if ((end_win < start_win && + seq_num < start_win && seq_num > end_win) || + (end_win > start_win && (seq_num > end_win || + seq_num < start_win))) { + end_win = seq_num; + if (((end_win - win_size) + 1) >= 0) + start_win = (end_win - win_size) + 1; + else + start_win = (MAX_TID_VALUE - (win_size - end_win)) + 1; + nxpwifi_11n_dispatch_pkt_until_start_win(priv, tbl, start_win); + } + + if (pkt_type != PKT_TYPE_BAR) { + if (seq_num >= start_win) + pkt_index = seq_num - start_win; + else + pkt_index = (seq_num + MAX_TID_VALUE) - start_win; + + if (tbl->rx_reorder_ptr[pkt_index]) { + ret = -EINVAL; + goto done; + } + + tbl->rx_reorder_ptr[pkt_index] = payload; + } + + /* Dispatch sequentially until a hole; update start_win. */ + nxpwifi_11n_scan_and_dispatch(priv, tbl); + +done: + if (!tbl->timer_context.timer_is_set || + prev_start_win != tbl->start_win) + nxpwifi_11n_rxreorder_timer_restart(tbl); + return ret; +} + +/* Delete BA entry for TID/TA. */ +void +nxpwifi_del_ba_tbl(struct nxpwifi_private *priv, int tid, u8 *peer_mac, + u8 type, int initiator) +{ + struct nxpwifi_rx_reorder_tbl *tbl; + struct nxpwifi_tx_ba_stream_tbl *ptx_tbl; + struct nxpwifi_ra_list_tbl *ra_list; + u8 cleanup_rx_reorder_tbl; + int tid_down; + + if (type == TYPE_DELBA_RECEIVE) + cleanup_rx_reorder_tbl = (initiator) ? true : false; + else + cleanup_rx_reorder_tbl = (initiator) ? false : true; + + nxpwifi_dbg(priv->adapter, EVENT, "event: DELBA: %pM tid=%d initiator=%d\n", + peer_mac, tid, initiator); + + if (cleanup_rx_reorder_tbl) { + tbl = nxpwifi_11n_get_rx_reorder_tbl(priv, tid, peer_mac); + if (!tbl) { + nxpwifi_dbg(priv->adapter, EVENT, + "event: TID, TA not found in table\n"); + return; + } + nxpwifi_del_rx_reorder_entry(priv, tbl); + } else { + guard(rcu)(); + ptx_tbl = nxpwifi_get_ba_tbl(priv, tid, peer_mac); + + if (!ptx_tbl) { + nxpwifi_dbg(priv->adapter, EVENT, + "event: TID, RA not found in table\n"); + return; + } + + tid_down = nxpwifi_wmm_downgrade_tid(priv, tid); + ra_list = nxpwifi_wmm_get_ralist_node(priv, tid_down, peer_mac); + if (ra_list) { + ra_list->amsdu_in_ampdu = false; + ra_list->ba_status = BA_SETUP_NONE; + } + spin_lock_bh(&priv->tx_ba_stream_tbl_lock[tid]); + nxpwifi_11n_delete_tx_ba_stream_tbl_entry(priv, ptx_tbl); + spin_unlock_bh(&priv->tx_ba_stream_tbl_lock[tid]); + } +} + +/* Handle ADDBA response. */ +int nxpwifi_ret_11n_addba_resp(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + struct host_cmd_ds_11n_addba_rsp *add_ba_rsp = &resp->params.add_ba_rsp; + int tid, win_size; + struct nxpwifi_rx_reorder_tbl *tbl; + u16 block_ack_param_set; + + block_ack_param_set = le16_to_cpu(add_ba_rsp->block_ack_param_set); + + tid = (block_ack_param_set & IEEE80211_ADDBA_PARAM_TID_MASK) + >> BLOCKACKPARAM_TID_POS; + /* Check if we had rejected the ADDBA, if yes then do not create the stream */ + if (le16_to_cpu(add_ba_rsp->status_code) != BA_RESULT_SUCCESS) { + nxpwifi_dbg(priv->adapter, ERROR, "ADDBA RSP: failed %pM tid=%d)\n", + add_ba_rsp->peer_mac_addr, tid); + + tbl = nxpwifi_11n_get_rx_reorder_tbl(priv, tid, + add_ba_rsp->peer_mac_addr); + if (tbl) + nxpwifi_del_rx_reorder_entry(priv, tbl); + + return 0; + } + + win_size = (block_ack_param_set & IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK) + >> BLOCKACKPARAM_WINSIZE_POS; + + tbl = nxpwifi_11n_get_rx_reorder_tbl(priv, tid, + add_ba_rsp->peer_mac_addr); + if (tbl) { + if ((block_ack_param_set & IEEE80211_ADDBA_PARAM_AMSDU_MASK) && + priv->add_ba_param.rx_amsdu && + priv->aggr_prio_tbl[tid].amsdu != BA_STREAM_NOT_ALLOWED) + tbl->amsdu = true; + else + tbl->amsdu = false; + } + + nxpwifi_dbg(priv->adapter, CMD, + "cmd: ADDBA RSP: %pM tid=%d ssn=%d win_size=%d\n", + add_ba_rsp->peer_mac_addr, tid, add_ba_rsp->ssn, win_size); + + return 0; +} + +/* Handle BA stream timeout: send DELBA. */ +void nxpwifi_11n_ba_stream_timeout(struct nxpwifi_private *priv, + struct host_cmd_ds_11n_batimeout *event) +{ + struct host_cmd_ds_11n_delba delba; + + memset(&delba, 0, sizeof(struct host_cmd_ds_11n_delba)); + memcpy(delba.peer_mac_addr, event->peer_mac_addr, ETH_ALEN); + + delba.del_ba_param_set |= + cpu_to_le16((u16)event->tid << DELBA_TID_POS); + delba.del_ba_param_set |= + cpu_to_le16((u16)event->origninator << DELBA_INITIATOR_POS); + delba.reason_code = cpu_to_le16(WLAN_REASON_QSTA_TIMEOUT); + nxpwifi_send_cmd(priv, HOST_CMD_11N_DELBA, 0, 0, &delba, false); +} + +/* Cleanup all RX reorder entries. */ +void nxpwifi_11n_cleanup_reorder_tbl(struct nxpwifi_private *priv) +{ + struct nxpwifi_rx_reorder_tbl *del_tbl_ptr, *tmp_node; + LIST_HEAD(to_delete_list); + int i; + + for (i = 0; i < MAX_NUM_TID; i++) { + spin_lock_bh(&priv->rx_reorder_tbl_lock[i]); + list_splice_init(&priv->rx_reorder_tbl_ptr[i], &to_delete_list); + spin_unlock_bh(&priv->rx_reorder_tbl_lock[i]); + + list_for_each_entry_safe(del_tbl_ptr, tmp_node, &to_delete_list, list) + nxpwifi_del_rx_reorder_entry(priv, del_tbl_ptr); + + INIT_LIST_HEAD(&to_delete_list); + } + + nxpwifi_reset_11n_rx_seq_num(priv); +} + +/* Update flags for all RX reorder tables. */ +void nxpwifi_update_rxreor_flags(struct nxpwifi_adapter *adapter, u8 flags) +{ + struct nxpwifi_private *priv; + struct nxpwifi_rx_reorder_tbl *tbl; + int i, j; + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + + for (j = 0; j < MAX_NUM_TID; j++) { + spin_lock_bh(&priv->rx_reorder_tbl_lock[j]); + list_for_each_entry_rcu(tbl, &priv->rx_reorder_tbl_ptr[j], list) + tbl->flags = flags; + spin_unlock_bh(&priv->rx_reorder_tbl_lock[j]); + } + } +} + +/* Update RX window size based on coex flag. */ +static void nxpwifi_update_ampdu_rxwinsize(struct nxpwifi_adapter *adapter, + bool coex_flag) +{ + u8 i, j; + u32 rx_win_size; + struct nxpwifi_private *priv; + + nxpwifi_dbg(adapter, INFO, "Update rxwinsize %d\n", coex_flag); + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + rx_win_size = priv->add_ba_param.rx_win_size; + if (coex_flag) { + if (priv->bss_type == NXPWIFI_BSS_TYPE_STA) + priv->add_ba_param.rx_win_size = + NXPWIFI_STA_COEX_AMPDU_DEF_RXWINSIZE; + if (priv->bss_type == NXPWIFI_BSS_TYPE_UAP) + priv->add_ba_param.rx_win_size = + NXPWIFI_UAP_COEX_AMPDU_DEF_RXWINSIZE; + } else { + if (priv->bss_type == NXPWIFI_BSS_TYPE_STA) + priv->add_ba_param.rx_win_size = + NXPWIFI_STA_AMPDU_DEF_RXWINSIZE; + if (priv->bss_type == NXPWIFI_BSS_TYPE_UAP) + priv->add_ba_param.rx_win_size = + NXPWIFI_UAP_AMPDU_DEF_RXWINSIZE; + } + + if (adapter->coex_win_size && adapter->coex_rx_win_size) + priv->add_ba_param.rx_win_size = + adapter->coex_rx_win_size; + + if (rx_win_size != priv->add_ba_param.rx_win_size) { + if (!priv->media_connected) + continue; + for (j = 0; j < MAX_NUM_TID; j++) + nxpwifi_11n_delba(priv, j); + } + } +} + +/* Check coex for RX BA. */ +void nxpwifi_coex_ampdu_rxwinsize(struct nxpwifi_adapter *adapter) +{ + u8 i; + struct nxpwifi_private *priv; + u8 count = 0; + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) { + if (priv->media_connected) + count++; + } + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + if (priv->bss_started) + count++; + } + if (count >= NXPWIFI_BSS_COEX_COUNT) + break; + } + if (count >= NXPWIFI_BSS_COEX_COUNT) + nxpwifi_update_ampdu_rxwinsize(adapter, true); + else + nxpwifi_update_ampdu_rxwinsize(adapter, false); +} + +/* Handle RXBA sync event. */ +void nxpwifi_11n_rxba_sync_event(struct nxpwifi_private *priv, + u8 *event_buf, u16 len) +{ + struct nxpwifi_ie_types_rxba_sync *tlv_rxba = (void *)event_buf; + u16 tlv_type, tlv_len; + struct nxpwifi_rx_reorder_tbl *rx_reor_tbl_ptr; + u8 i, j; + u16 seq_num, tlv_seq_num, tlv_bitmap_len; + int tlv_buf_left = len; + int ret; + u8 *tmp; + + nxpwifi_dbg_dump(priv->adapter, EVT_D, "RXBA_SYNC event:", + event_buf, len); + while (tlv_buf_left > sizeof(*tlv_rxba)) { + tlv_type = le16_to_cpu(tlv_rxba->header.type); + tlv_len = le16_to_cpu(tlv_rxba->header.len); + if (size_add(sizeof(tlv_rxba->header), tlv_len) > tlv_buf_left) { + nxpwifi_dbg(priv->adapter, WARN, + "TLV size (%zu) overflows event_buf buf_left=%d\n", + size_add(sizeof(tlv_rxba->header), tlv_len), + tlv_buf_left); + return; + } + + if (tlv_type != TLV_TYPE_RXBA_SYNC) { + nxpwifi_dbg(priv->adapter, ERROR, + "Wrong TLV id=0x%x\n", tlv_type); + return; + } + + tlv_seq_num = le16_to_cpu(tlv_rxba->seq_num); + tlv_bitmap_len = le16_to_cpu(tlv_rxba->bitmap_len); + if (size_add(sizeof(*tlv_rxba), tlv_bitmap_len) > tlv_buf_left) { + nxpwifi_dbg(priv->adapter, WARN, + "TLV size (%zu) overflows event_buf buf_left=%d\n", + size_add(sizeof(*tlv_rxba), tlv_bitmap_len), + tlv_buf_left); + return; + } + + nxpwifi_dbg(priv->adapter, INFO, + "%pM tid=%d seq_num=%d bitmap_len=%d\n", + tlv_rxba->mac, tlv_rxba->tid, tlv_seq_num, + tlv_bitmap_len); + + rx_reor_tbl_ptr = + nxpwifi_11n_get_rx_reorder_tbl(priv, tlv_rxba->tid, + tlv_rxba->mac); + if (!rx_reor_tbl_ptr) { + nxpwifi_dbg(priv->adapter, ERROR, + "Can not find rx_reorder_tbl!"); + return; + } + + for (i = 0; i < tlv_bitmap_len; i++) { + for (j = 0 ; j < 8; j++) { + if (tlv_rxba->bitmap[i] & (1 << j)) { + seq_num = (MAX_TID_VALUE - 1) & + (tlv_seq_num + i * 8 + j); + + nxpwifi_dbg(priv->adapter, ERROR, + "drop packet,seq=%d\n", + seq_num); + + ret = nxpwifi_11n_rx_reorder_pkt + (priv, seq_num, tlv_rxba->tid, + tlv_rxba->mac, 0, NULL); + + if (ret) + nxpwifi_dbg(priv->adapter, + ERROR, + "Fail to drop packet"); + } + } + } + + tlv_buf_left -= (sizeof(tlv_rxba->header) + tlv_len); + tmp = (u8 *)tlv_rxba + sizeof(tlv_rxba->header) + tlv_len; + tlv_rxba = (struct nxpwifi_ie_types_rxba_sync *)tmp; + } +} diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.h b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.h new file mode 100644 index 000000000000..db95d9db5d1f --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * NXP Wireless LAN device driver: 802.11n RX Re-ordering + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_11N_RXREORDER_H_ +#define _NXPWIFI_11N_RXREORDER_H_ + +#define MIN_FLUSH_TIMER_MS 50 +#define MIN_FLUSH_TIMER_15_MS 15 +#define NXPWIFI_BA_WIN_SIZE_32 32 + +#define PKT_TYPE_BAR 0xE7 +#define MAX_TID_VALUE (2 << 11) +#define TWOPOW11 (2 << 10) + +#define BLOCKACKPARAM_TID_POS 2 +#define BLOCKACKPARAM_WINSIZE_POS 6 +#define DELBA_TID_POS 12 +#define DELBA_INITIATOR_POS 11 +#define TYPE_DELBA_SENT 1 +#define TYPE_DELBA_RECEIVE 2 +#define IMMEDIATE_BLOCK_ACK 0x2 + +#define ADDBA_RSP_STATUS_ACCEPT 0 + +#define NXPWIFI_DEF_11N_RX_SEQ_NUM 0xffff +#define BA_SETUP_MAX_PACKET_THRESHOLD 16 +#define BA_SETUP_PACKET_OFFSET 16 + +enum nxpwifi_rxreor_flags { + RXREOR_FORCE_NO_DROP = 1 << 0, + RXREOR_INIT_WINDOW_SHIFT = 1 << 1, +}; + +static inline void nxpwifi_reset_11n_rx_seq_num(struct nxpwifi_private *priv) +{ + memset(priv->rx_seq, 0xff, sizeof(priv->rx_seq)); +} + +int nxpwifi_11n_rx_reorder_pkt(struct nxpwifi_private *priv, + u16 seq_num, + u16 tid, u8 *ta, + u8 pkttype, void *payload); +void nxpwifi_del_ba_tbl(struct nxpwifi_private *priv, int tid, + u8 *peer_mac, u8 type, int initiator); +void nxpwifi_11n_ba_stream_timeout(struct nxpwifi_private *priv, + struct host_cmd_ds_11n_batimeout *event); +int nxpwifi_ret_11n_addba_resp(struct nxpwifi_private *priv, + struct host_cmd_ds_command + *resp); +int nxpwifi_cmd_11n_delba(struct host_cmd_ds_command *cmd, + void *data_buf); +int nxpwifi_cmd_11n_addba_rsp_gen(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + struct host_cmd_ds_11n_addba_req + *cmd_addba_req); +int nxpwifi_cmd_11n_addba_req(struct host_cmd_ds_command *cmd, + void *data_buf); +void nxpwifi_11n_cleanup_reorder_tbl(struct nxpwifi_private *priv); +struct nxpwifi_rx_reorder_tbl * +nxpwifi_11n_get_rxreorder_tbl(struct nxpwifi_private *priv, int tid, u8 *ta); +struct nxpwifi_rx_reorder_tbl * +nxpwifi_11n_get_rx_reorder_tbl(struct nxpwifi_private *priv, int tid, u8 *ta); +void nxpwifi_11n_del_rx_reorder_tbl_by_ta(struct nxpwifi_private *priv, u8 *ta); +void nxpwifi_update_rxreor_flags(struct nxpwifi_adapter *adapter, u8 flags); +void nxpwifi_11n_rxba_sync_event(struct nxpwifi_private *priv, + u8 *event_buf, u16 len); +#endif /* _NXPWIFI_11N_RXREORDER_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/Kconfig b/drivers/net/wireless/nxp/nxpwifi/Kconfig new file mode 100644 index 000000000000..3637068574b8 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/Kconfig @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-2.0-only +config NXPWIFI + tristate "NXP WiFi Driver" + depends on CFG80211 + help + This adds support for wireless adapters based on NXP + 802.11n/ac chipsets. + + If you choose to build it as a module, it will be called + nxpwifi. + +config NXPWIFI_SDIO + tristate "NXP WiFi Driver for IW61x" + depends on NXPWIFI && MMC + select FW_LOADER + select WANT_DEV_COREDUMP + help + This adds support for wireless adapters based on NXP + IW61x interface. + + If you choose to build it as a module, it will be called + nxpwifi_sdio. diff --git a/drivers/net/wireless/nxp/nxpwifi/Makefile b/drivers/net/wireless/nxp/nxpwifi/Makefile new file mode 100644 index 000000000000..8f581429f28d --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/Makefile @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright 2011-2020 NXP +# + + +nxpwifi-y += main.o +nxpwifi-y += init.o +nxpwifi-y += cfp.o +nxpwifi-y += cmdevt.o +nxpwifi-y += util.o +nxpwifi-y += txrx.o +nxpwifi-y += wmm.o +nxpwifi-y += 11n.o +nxpwifi-y += 11ac.o +nxpwifi-y += 11ax.o +nxpwifi-y += 11n_aggr.o +nxpwifi-y += 11n_rxreorder.o +nxpwifi-y += scan.o +nxpwifi-y += join.o +nxpwifi-y += sta_cfg.o +nxpwifi-y += sta_cmd.o +nxpwifi-y += uap_cmd.o +nxpwifi-y += ie.o +nxpwifi-y += sta_event.o +nxpwifi-y += uap_event.o +nxpwifi-y += sta_tx.o +nxpwifi-y += sta_rx.o +nxpwifi-y += uap_txrx.o +nxpwifi-y += cfg80211.o +nxpwifi-y += ethtool.o +nxpwifi-y += 11h.o +nxpwifi-$(CONFIG_DEBUG_FS) += debugfs.o +obj-$(CONFIG_NXPWIFI) += nxpwifi.o + +nxpwifi_sdio-y += sdio.o +obj-$(CONFIG_NXPWIFI_SDIO) += nxpwifi_sdio.o + +ccflags-y += -D__CHECK_ENDIAN diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg.h b/drivers/net/wireless/nxp/nxpwifi/cfg.h new file mode 100644 index 000000000000..8627a3372978 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/cfg.h @@ -0,0 +1,1019 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * NXP Wireless LAN device driver: ioctl data structures & APIs + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_CFG_H_ +#define _NXPWIFI_CFG_H_ + +#include +#include +#include +#include +#include + +#define NUM_WEP_KEYS 4 + +#define NXPWIFI_BSS_COEX_COUNT 2 +#define NXPWIFI_MAX_BSS_NUM (3) + +#define NXPWIFI_MAX_CSA_COUNTERS 5 + +#define NXPWIFI_DMA_ALIGN_SZ 64 +#define NXPWIFI_RX_HEADROOM 64 +#define MAX_TXPD_SZ 32 +#define INTF_HDR_ALIGN 4 +/* special FW 4 address management header */ +#define NXPWIFI_MIN_DATA_HEADER_LEN (NXPWIFI_DMA_ALIGN_SZ + INTF_HDR_ALIGN + \ + MAX_TXPD_SZ) + +#define NXPWIFI_MGMT_FRAME_HEADER_SIZE 8 /* sizeof(pkt_type) + * + sizeof(tx_control) + */ + +#define FRMCTL_LEN 2 +#define DURATION_LEN 2 +#define SEQCTL_LEN 2 +#define NXPWIFI_MGMT_HEADER_LEN (FRMCTL_LEN + FRMCTL_LEN + ETH_ALEN + \ + ETH_ALEN + ETH_ALEN + SEQCTL_LEN + ETH_ALEN) + +#define AUTH_ALG_LEN 2 +#define AUTH_TRANSACTION_LEN 2 +#define AUTH_STATUS_LEN 2 +#define NXPWIFI_AUTH_BODY_LEN (AUTH_ALG_LEN + AUTH_TRANSACTION_LEN + \ + AUTH_STATUS_LEN) + +#define HOST_MLME_AUTH_PENDING BIT(0) +#define HOST_MLME_AUTH_DONE BIT(1) + +#define HOST_MLME_MGMT_MASK (BIT(IEEE80211_STYPE_AUTH >> 4) | \ + BIT(IEEE80211_STYPE_DEAUTH >> 4) | \ + BIT(IEEE80211_STYPE_DISASSOC >> 4)) + +#define AUTH_TX_DEFAULT_WAIT_TIME 2400 + +#define WLAN_AUTH_NONE 0xFFFF + +#define NXPWIFI_MAX_TX_BASTREAM_SUPPORTED 2 +#define NXPWIFI_MAX_RX_BASTREAM_SUPPORTED 16 + +#define NXPWIFI_STA_AMPDU_DEF_TXWINSIZE 64 +#define NXPWIFI_STA_AMPDU_DEF_RXWINSIZE 64 +#define NXPWIFI_STA_COEX_AMPDU_DEF_RXWINSIZE 16 + +#define NXPWIFI_UAP_AMPDU_DEF_TXWINSIZE 32 + +#define NXPWIFI_UAP_COEX_AMPDU_DEF_RXWINSIZE 16 + +#define NXPWIFI_UAP_AMPDU_DEF_RXWINSIZE 16 +#define NXPWIFI_11AC_STA_AMPDU_DEF_TXWINSIZE 64 +#define NXPWIFI_11AC_STA_AMPDU_DEF_RXWINSIZE 64 +#define NXPWIFI_11AC_UAP_AMPDU_DEF_TXWINSIZE 64 +#define NXPWIFI_11AC_UAP_AMPDU_DEF_RXWINSIZE 64 + +#define NXPWIFI_DEFAULT_BLOCK_ACK_TIMEOUT 0xffff + +#define NXPWIFI_RATE_BITMAP_MCS0 32 + +#define NXPWIFI_RX_DATA_BUF_SIZE (4 * 1024) +#define NXPWIFI_RX_CMD_BUF_SIZE (2 * 1024) + +#define NXPWIFI_BEACON_PERIOD_MAX (4000) +#define NXPWIFI_BEACON_PERIOD_MIN (50) +#define NXPWIFI_INVALID_BEACON_PERIOD (NXPWIFI_BEACON_PERIOD_MAX + 1) +#define NXPWIFI_MAX_DTIM_PERIOD (100) +#define NXPWIFI_MIN_DTIM_PERIOD (1) +#define NXPWIFI_INVALID_DTIM_PERIOD (NXPWIFI_MAX_DTIM_PERIOD + 1) +#define NXPWIFI_RTS_THRESHOLD_MIN (0) +#define NXPWIFI_RTS_THRESHOLD_MAX (2347) +#define NXPWIFI_INVALID_RTS (NXPWIFI_RTS_THRESHOLD_MAX + 1) +#define NXPWIFI_FRAG_THRESHOLD_MIN (256) +#define NXPWIFI_FRAG_THRESHOLD_MAX (2346) +#define NXPWIFI_INVALID_FRAG (NXPWIFI_FRAG_THRESHOLD_MAX + 1) +#define NXPWIFI_RETRY_LIMIT_MAX 14 +#define NXPWIFI_INVALID_RETRY_LIMI (NXPWIFI_RETRY_LIMIT_MAX + 1) + +enum nxpwifi_bcast_ssid_ctl { + /* Hide SSID in beacons (SSID length = 0) */ + NXPWIFI_BCAST_SSID_HIDE_LEN_ZERO = 0, + /* Do not hide SSID (normal broadcast) */ + NXPWIFI_BCAST_SSID_VISIBLE, + /* Hide SSID, clear SSID content (ASCII 0), + * but keep the original SSID length + */ + NXPWIFI_BCAST_SSID_HIDE_LEN_RETAIN, +}; + +enum nxpwifi_radio_ctl { + NXPWIFI_RADIO_CTL_DISABLE = 0, + NXPWIFI_RADIO_CTL_ENABLE, + __NXPWIFI_RADIO_CTL_MAX, +}; + +static inline bool nxpwifi_radio_ctl_valid(enum nxpwifi_radio_ctl v) +{ + return v < __NXPWIFI_RADIO_CTL_MAX; +} + +#define NXPWIFI_WMM_VERSION 0x01 +#define NXPWIFI_WMM_SUBTYPE 0x01 + +#define NXPWIFI_SDIO_BLOCK_SIZE 256 + +#define NXPWIFI_BUF_FLAG_REQUEUED_PKT BIT(0) +#define NXPWIFI_BUF_FLAG_BRIDGED_PKT BIT(1) +#define NXPWIFI_BUF_FLAG_EAPOL_TX_STATUS BIT(3) +#define NXPWIFI_BUF_FLAG_ACTION_TX_STATUS BIT(4) +#define NXPWIFI_BUF_FLAG_AGGR_PKT BIT(5) + +#define NXPWIFI_BRIDGED_PKTS_THR_HIGH 1024 +#define NXPWIFI_BRIDGED_PKTS_THR_LOW 128 + +/* 54M rates, index from 0 to 11 */ +#define NXPWIFI_RATE_INDEX_MCS0 12 +/* 12-27=MCS0-15(BW20) */ +#define NXPWIFI_BW20_MCS_NUM 15 + +/* Rate index for OFDM 0 */ +#define NXPWIFI_RATE_INDEX_OFDM0 4 + +#define NXPWIFI_MAX_STA_NUM 3 +#define NXPWIFI_MAX_UAP_NUM 3 + +#define NXPWIFI_A_BAND_START_FREQ 5000 + +/* SDIO Aggr data packet special info */ +#define SDIO_MAX_AGGR_BUF_SIZE (256 * 255) +#define BLOCK_NUMBER_OFFSET 15 +#define SDIO_HEADER_OFFSET 28 + +#define NXPWIFI_SIZE_4K 0x4000 +#define NXPWIFI_EXT_CAPAB_IE_LEN 10 + +enum nxpwifi_bss_type { + NXPWIFI_BSS_TYPE_STA = 0, + NXPWIFI_BSS_TYPE_UAP = 1, + NXPWIFI_BSS_TYPE_ANY = 0xff, +}; + +enum nxpwifi_bss_role { + NXPWIFI_BSS_ROLE_STA = 0, + NXPWIFI_BSS_ROLE_UAP = 1, + NXPWIFI_BSS_ROLE_ANY = 0xff, +}; + +#define BSS_ROLE_BIT_MASK BIT(0) + +#define GET_BSS_ROLE(priv) ((priv)->bss_role & BSS_ROLE_BIT_MASK) + +enum nxpwifi_data_frame_type { + NXPWIFI_DATA_FRAME_TYPE_ETH_II = 0, + NXPWIFI_DATA_FRAME_TYPE_802_11, +}; + +struct nxpwifi_fw_image { + u8 *helper_buf; + u32 helper_len; + u8 *fw_buf; + u32 fw_len; +}; + +struct nxpwifi_802_11_ssid { + u32 ssid_len; + u8 ssid[IEEE80211_MAX_SSID_LEN]; +}; + +struct nxpwifi_wait_queue { + wait_queue_head_t wait; + int status; +}; + +struct nxpwifi_rxinfo { + struct sk_buff *parent; + u8 bss_num; + u8 bss_type; + u8 use_count; + u8 buf_type; + u16 pkt_len; +}; + +struct nxpwifi_txinfo { + u8 flags; + u8 bss_num; + u8 bss_type; + u8 aggr_num; + u32 pkt_len; + u8 ack_frame_id; + u64 cookie; +}; + +enum nxpwifi_wmm_ac_e { + WMM_AC_BK, + WMM_AC_BE, + WMM_AC_VI, + WMM_AC_VO +} __packed; + +struct nxpwifi_types_wmm_info { + u8 oui[4]; + u8 subtype; + u8 version; + u8 qos_info; + u8 reserved; + struct ieee80211_wmm_ac_param ac[IEEE80211_NUM_ACS]; +} __packed; + +struct nxpwifi_arp_eth_header { + struct arphdr hdr; + u8 ar_sha[ETH_ALEN]; + u8 ar_sip[4]; + u8 ar_tha[ETH_ALEN]; + u8 ar_tip[4]; +} __packed; + +struct nxpwifi_chan_stats { + u8 chan_num; + u8 bandcfg; + u8 flags; + s8 noise; + u16 total_bss; + u16 cca_scan_dur; + u16 cca_busy_dur; +} __packed; + +#define NXPWIFI_HIST_MAX_SAMPLES 1048576 +#define NXPWIFI_MAX_RX_RATES 44 +#define NXPWIFI_MAX_AC_RX_RATES 74 +#define NXPWIFI_MAX_SNR 256 +#define NXPWIFI_MAX_NOISE_FLR 256 +#define NXPWIFI_MAX_SIG_STRENGTH 256 + +struct nxpwifi_histogram_data { + atomic_t rx_rate[NXPWIFI_MAX_AC_RX_RATES]; + atomic_t snr[NXPWIFI_MAX_SNR]; + atomic_t noise_flr[NXPWIFI_MAX_NOISE_FLR]; + atomic_t sig_str[NXPWIFI_MAX_SIG_STRENGTH]; + atomic_t num_samples; +}; + +struct nxpwifi_iface_comb { + u8 sta_intf; + u8 uap_intf; +}; + +struct nxpwifi_radar_params { + struct cfg80211_chan_def *chandef; + u32 cac_time_ms; +} __packed; + +struct nxpwifi_11h_intf_state { + bool is_11h_enabled; + bool is_11h_active; +} __packed; + +#define NXPWIFI_FW_DUMP_IDX 0xff +#define NXPWIFI_FW_DUMP_MAX_MEMSIZE 0x160000 +#define NXPWIFI_DRV_INFO_IDX 20 +#define FW_DUMP_MAX_NAME_LEN 8 +#define FW_DUMP_HOST_READY 0xEE +#define FW_DUMP_DONE 0xFF +#define FW_DUMP_READ_DONE 0xFE + +/* Channel bandwidth */ +#define CHANNEL_BW_20MHZ 0 +#define CHANNEL_BW_40MHZ_ABOVE 1 +#define CHANNEL_BW_40MHZ_BELOW 3 +/* secondary channel is 80MHz bandwidth for 11ac */ +#define CHANNEL_BW_80MHZ 4 +#define CHANNEL_BW_160MHZ 5 + +struct memory_type_mapping { + u8 mem_name[FW_DUMP_MAX_NAME_LEN]; + u8 *mem_ptr; + u32 mem_size; + u8 done_flag; +}; + +enum rdwr_status { + RDWR_STATUS_SUCCESS = 0, + RDWR_STATUS_FAILURE = 1, + RDWR_STATUS_DONE = 2 +}; + +enum nxpwifi_chan_band { + BAND_2GHZ = 0, + BAND_5GHZ, + BAND_6GHZ, + BAND_4GHZ, +}; + +enum nxpwifi_chan_width { + CHAN_BW_20MHZ = 0, + CHAN_BW_10MHZ, + CHAN_BW_40MHZ, + CHAN_BW_80MHZ, + CHAN_BW_8080MHZ, + CHAN_BW_160MHZ, + CHAN_BW_5MHZ, +}; + +enum { + NXPWIFI_SCAN_TYPE_UNCHANGED = 0, + NXPWIFI_SCAN_TYPE_ACTIVE, + NXPWIFI_SCAN_TYPE_PASSIVE +}; + +#define NXPWIFI_PROMISC_MODE 1 +#define NXPWIFI_MULTICAST_MODE 2 +#define NXPWIFI_ALL_MULTI_MODE 4 +#define NXPWIFI_MAX_MULTICAST_LIST_SIZE 32 + +struct nxpwifi_multicast_list { + u32 mode; + u32 num_multicast_addr; + u8 mac_list[NXPWIFI_MAX_MULTICAST_LIST_SIZE][ETH_ALEN]; +}; + +struct nxpwifi_chan_freq { + u32 channel; + u32 freq; +}; + +struct nxpwifi_ssid_bssid { + struct cfg80211_ssid ssid; + u8 bssid[ETH_ALEN]; +}; + +enum { + BAND_B = 1, + BAND_G = 2, + BAND_A = 4, + BAND_GN = 8, + BAND_AN = 16, + BAND_GAC = 32, + BAND_AAC = 64, + BAND_GAX = 256, + BAND_AAX = 512, +}; + +#define NXPWIFI_WPA_PASSHPHRASE_LEN 64 +struct wpa_param { + u8 pairwise_cipher_wpa; + u8 pairwise_cipher_wpa2; + u8 group_cipher; + u32 length; + u8 passphrase[NXPWIFI_WPA_PASSHPHRASE_LEN]; +}; + +struct wep_key { + u8 key_index; + u8 is_default; + u16 length; + u8 key[WLAN_KEY_LEN_WEP104]; +}; + +#define KEY_MGMT_ON_HOST 0x03 +#define NXPWIFI_AUTH_MODE_AUTO 0xFF +#define BAND_CONFIG_BG 0x00 +#define BAND_CONFIG_A 0x01 +#define NXPWIFI_SEC_CHAN_BELOW 0x03 +#define NXPWIFI_SEC_CHAN_ABOVE 0x01 +#define NXPWIFI_SUPPORTED_RATES 14 +#define NXPWIFI_SUPPORTED_RATES_EXT 32 +#define NXPWIFI_PRIO_BK 2 +#define NXPWIFI_PRIO_VI 5 +#define NXPWIFI_SUPPORTED_CHANNELS 2 +#define NXPWIFI_OPERATING_CLASSES 16 + +struct nxpwifi_uap_bss_param { + u8 mac_addr[ETH_ALEN]; + u8 channel; + u8 band_cfg; + u16 rts_threshold; + u16 frag_threshold; + u8 retry_limit; + struct nxpwifi_802_11_ssid ssid; + u8 bcast_ssid_ctl; + u8 radio_ctl; + u8 dtim_period; + u16 beacon_period; + u16 auth_mode; + u16 protocol; + u16 key_mgmt; + u16 key_mgmt_operation; + struct wpa_param wpa_cfg; + struct wep_key wep_cfg[NUM_WEP_KEYS]; + struct ieee80211_ht_cap ht_cap; + struct ieee80211_vht_cap vht_cap; + u8 rates[NXPWIFI_SUPPORTED_RATES]; + u32 sta_ao_timer; + u32 ps_sta_ao_timer; + u8 power_constraint; + struct ieee80211_wmm_param_ie wmm_element; +}; + +struct nxpwifi_ds_get_stats { + u32 mcast_tx_frame; + u32 failed; + u32 retry; + u32 multi_retry; + u32 frame_dup; + u32 rts_success; + u32 rts_failure; + u32 ack_failure; + u32 rx_frag; + u32 mcast_rx_frame; + u32 fcs_error; + u32 tx_frame; + u32 wep_icv_error[4]; + u32 bcn_rcv_cnt; + u32 bcn_miss_cnt; +}; + +#define NXPWIFI_MAX_VER_STR_LEN 128 + +struct nxpwifi_ver_ext { + u32 version_str_sel; + char version_str[NXPWIFI_MAX_VER_STR_LEN]; +}; + +struct nxpwifi_bss_info { + u32 bss_mode; + struct cfg80211_ssid ssid; + u32 bss_chan; + u8 country_code[3]; + u32 media_connected; + u32 max_power_level; + u32 min_power_level; + signed int bcn_nf_last; + u32 wep_status; + u32 is_hs_configured; + u32 is_deep_sleep; + u8 bssid[ETH_ALEN]; +}; + +struct nxpwifi_sta_info { + u8 peer_mac[ETH_ALEN]; + struct station_parameters *params; +}; + +#define MAX_NUM_TID 8 + +#define MAX_RX_WINSIZE 64 + +struct nxpwifi_ds_rx_reorder_tbl { + u16 tid; + u8 ta[ETH_ALEN]; + u32 start_win; + u32 win_size; + u32 buffer[MAX_RX_WINSIZE]; +}; + +struct nxpwifi_ds_tx_ba_stream_tbl { + u16 tid; + u8 ra[ETH_ALEN]; + u8 amsdu; +}; + +#define DBG_CMD_NUM 5 +#define NXPWIFI_DBG_SDIO_MP_NUM 10 + +struct nxpwifi_debug_info { + unsigned int debug_mask; + u32 int_counter; + u32 packets_out[MAX_NUM_TID]; + u32 tx_buf_size; + u32 curr_tx_buf_size; + u32 tx_tbl_num; + struct nxpwifi_ds_tx_ba_stream_tbl + tx_tbl[NXPWIFI_MAX_TX_BASTREAM_SUPPORTED]; + u32 rx_tbl_num; + struct nxpwifi_ds_rx_reorder_tbl rx_tbl + [NXPWIFI_MAX_RX_BASTREAM_SUPPORTED]; + u16 ps_mode; + u32 ps_state; + u8 is_deep_sleep; + u8 pm_wakeup_card_req; + u32 pm_wakeup_fw_try; + u8 is_hs_configured; + u8 hs_activated; + u32 num_cmd_host_to_card_failure; + u32 num_cmd_sleep_cfm_host_to_card_failure; + u32 num_tx_host_to_card_failure; + u32 num_event_deauth; + u32 num_event_disassoc; + u32 num_event_link_lost; + u32 num_cmd_deauth; + u32 num_cmd_assoc_success; + u32 num_cmd_assoc_failure; + u32 num_tx_timeout; + u8 is_cmd_timedout; + u16 timeout_cmd_id; + u16 timeout_cmd_act; + u16 last_cmd_id[DBG_CMD_NUM]; + u16 last_cmd_act[DBG_CMD_NUM]; + u16 last_cmd_index; + u16 last_cmd_resp_id[DBG_CMD_NUM]; + u16 last_cmd_resp_index; + u16 last_event[DBG_CMD_NUM]; + u16 last_event_index; + u8 data_sent; + u8 cmd_sent; + u8 cmd_resp_received; + u8 event_received; + u32 last_mp_wr_bitmap[NXPWIFI_DBG_SDIO_MP_NUM]; + u32 last_mp_wr_ports[NXPWIFI_DBG_SDIO_MP_NUM]; + u32 last_mp_wr_len[NXPWIFI_DBG_SDIO_MP_NUM]; + u32 last_mp_curr_wr_port[NXPWIFI_DBG_SDIO_MP_NUM]; + u8 last_sdio_mp_index; +}; + +#define NXPWIFI_KEY_INDEX_UNICAST 0x40000000 +#define PN_LEN 16 + +struct nxpwifi_ds_encrypt_key { + u32 key_disable; + u32 key_index; + u32 key_len; + u32 key_cipher; + u8 key_material[WLAN_MAX_KEY_LEN]; + u8 mac_addr[ETH_ALEN]; + u8 pn[PN_LEN]; /* packet number */ + u8 pn_len; + u8 is_igtk_key; + u8 is_current_wep_key; + u8 is_rx_seq_valid; + u8 is_igtk_def_key; +}; + +struct nxpwifi_power_cfg { + u32 is_power_auto; + u32 is_power_fixed; + u32 power_level; +}; + +struct nxpwifi_ds_hs_cfg { + u32 is_invoke_hostcmd; + /* + * Bit0: non-unicast data + * Bit1: unicast data + * Bit2: mac events + * Bit3: magic packet + */ + u32 conditions; + u32 gpio; + u32 gap; +}; + +struct nxpwifi_ds_wakeup_reason { + u16 hs_wakeup_reason; +}; + +#define DEEP_SLEEP_ON 1 +#define DEEP_SLEEP_OFF 0 +#define DEEP_SLEEP_IDLE_TIME 100 +#define PS_MODE_AUTO 1 + +struct nxpwifi_ds_auto_ds { + u16 auto_ds; + u16 idle_time; +}; + +struct nxpwifi_ds_pm_cfg { + union { + u32 ps_mode; + struct nxpwifi_ds_hs_cfg hs_cfg; + struct nxpwifi_ds_auto_ds auto_deep_sleep; + u32 sleep_period; + } param; +}; + +struct nxpwifi_11ac_vht_cfg { + u8 band_config; + u8 misc_config; + u32 cap_info; + u32 mcs_tx_set; + u32 mcs_rx_set; +}; + +struct nxpwifi_ds_11n_tx_cfg { + u16 tx_htcap; + u16 tx_htinfo; + u16 misc_config; /* Needed for 802.11AC cards only */ +}; + +struct nxpwifi_ds_11n_amsdu_aggr_ctrl { + u16 enable; + u16 curr_buf_size; +}; + +struct nxpwifi_ds_ant_cfg { + u32 tx_ant; + u32 rx_ant; +}; + +#define NXPWIFI_NUM_OF_CMD_BUFFER 50 +#define NXPWIFI_SIZE_OF_CMD_BUFFER 2048 + +enum { + NXPWIFI_IE_TYPE_GEN_IE = 0, + NXPWIFI_IE_TYPE_ARP_FILTER, +}; + +enum { + NXPWIFI_REG_MAC = 1, + NXPWIFI_REG_BBP, + NXPWIFI_REG_RF, + NXPWIFI_REG_PMIC, + NXPWIFI_REG_CAU, +}; + +struct nxpwifi_ds_reg_rw { + u32 type; + u32 offset; + u32 value; +}; + +#define MAX_EEPROM_DATA 256 + +struct nxpwifi_ds_read_eeprom { + u16 offset; + u16 byte_count; + u8 value[MAX_EEPROM_DATA]; +}; + +struct nxpwifi_ds_mem_rw { + u32 addr; + u32 value; +}; + +#define IEEE_MAX_IE_SIZE 256 + +#define NXPWIFI_IE_HDR_SIZE (sizeof(struct nxpwifi_ie) - IEEE_MAX_IE_SIZE) + +struct nxpwifi_ds_misc_gen_ie { + u32 type; + u32 len; + u8 ie_data[IEEE_MAX_IE_SIZE]; +}; + +struct nxpwifi_ds_misc_cmd { + u32 len; + u8 cmd[NXPWIFI_SIZE_OF_CMD_BUFFER]; +}; + +#define BITMASK_BCN_RSSI_LOW BIT(0) +#define BITMASK_BCN_RSSI_HIGH BIT(4) + +enum subsc_evt_rssi_state { + EVENT_HANDLED, + RSSI_LOW_RECVD, + RSSI_HIGH_RECVD +}; + +struct subsc_evt_cfg { + u8 abs_value; + u8 evt_freq; +}; + +struct nxpwifi_ds_misc_subsc_evt { + u16 action; + u16 events; + struct subsc_evt_cfg bcn_l_rssi_cfg; + struct subsc_evt_cfg bcn_h_rssi_cfg; +}; + +#define NXPWIFI_MEF_MAX_BYTESEQ 6 /* non-adjustable */ +#define NXPWIFI_MEF_MAX_FILTERS 10 + +struct nxpwifi_mef_filter { + u16 repeat; + u16 offset; + s8 byte_seq[NXPWIFI_MEF_MAX_BYTESEQ + 1]; + u8 filt_type; + u8 filt_action; +}; + +struct nxpwifi_mef_entry { + u8 mode; + u8 action; + struct nxpwifi_mef_filter filter[NXPWIFI_MEF_MAX_FILTERS]; +}; + +struct nxpwifi_ds_mef_cfg { + u32 criteria; + u16 num_entries; + struct nxpwifi_mef_entry *mef_entry; +}; + +#define NXPWIFI_MAX_VSIE_LEN (256) +#define NXPWIFI_MAX_VSIE_NUM (8) +#define NXPWIFI_VSIE_MASK_CLEAR 0x00 +#define NXPWIFI_VSIE_MASK_SCAN 0x01 +#define NXPWIFI_VSIE_MASK_ASSOC 0x02 +#define NXPWIFI_VSIE_MASK_BGSCAN 0x08 + +enum { + NXPWIFI_FUNC_INIT = 1, + NXPWIFI_FUNC_SHUTDOWN, +}; + +enum COALESCE_OPERATION { + RECV_FILTER_MATCH_TYPE_EQ = 0x80, + RECV_FILTER_MATCH_TYPE_NE, +}; + +enum COALESCE_PACKET_TYPE { + PACKET_TYPE_UNICAST = 1, + PACKET_TYPE_MULTICAST = 2, + PACKET_TYPE_BROADCAST = 3 +}; + +#define NXPWIFI_COALESCE_MAX_RULES 8 +#define NXPWIFI_COALESCE_MAX_BYTESEQ 4 /* non-adjustable */ +#define NXPWIFI_COALESCE_MAX_FILTERS 4 +#define NXPWIFI_MAX_COALESCING_DELAY 100 /* in msecs */ + +struct filt_field_param { + u8 operation; + u8 operand_len; + u16 offset; + u8 operand_byte_stream[NXPWIFI_COALESCE_MAX_BYTESEQ]; +}; + +struct nxpwifi_coalesce_rule { + u16 max_coalescing_delay; + u8 num_of_fields; + u8 pkt_type; + struct filt_field_param params[NXPWIFI_COALESCE_MAX_FILTERS]; +}; + +struct nxpwifi_ds_coalesce_cfg { + u16 num_of_rules; + struct nxpwifi_coalesce_rule rule[NXPWIFI_COALESCE_MAX_RULES]; +}; + +struct nxpwifi_11ax_he_cap_cfg { + u16 id; + u16 len; + u8 ext_id; + struct ieee80211_he_cap_elem cap_elem; + u8 he_txrx_mcs_support[4]; + u8 val[28]; +}; + +#define HE_CAP_MAX_SIZE 54 + +struct nxpwifi_11ax_he_cfg { + u8 band; + union { + struct nxpwifi_11ax_he_cap_cfg he_cap_cfg; + u8 data[HE_CAP_MAX_SIZE]; + }; +}; + +#define NXPWIFI_11AXCMD_CFG_ID_SR_OBSS_PD_OFFSET 1 +#define NXPWIFI_11AXCMD_CFG_ID_SR_ENABLE 2 +#define NXPWIFI_11AXCMD_CFG_ID_BEAM_CHANGE 3 +#define NXPWIFI_11AXCMD_CFG_ID_HTC_ENABLE 4 +#define NXPWIFI_11AXCMD_CFG_ID_TXOP_RTS 5 +#define NXPWIFI_11AXCMD_CFG_ID_TX_OMI 6 +#define NXPWIFI_11AXCMD_CFG_ID_OBSSNBRU_TOLTIME 7 +#define NXPWIFI_11AXCMD_CFG_ID_SET_BSRP 8 +#define NXPWIFI_11AXCMD_CFG_ID_LLDE 9 + +#define NXPWIFI_11AXCMD_SR_SUBID 0x102 +#define NXPWIFI_11AXCMD_BEAM_SUBID 0x103 +#define NXPWIFI_11AXCMD_HTC_SUBID 0x104 +#define NXPWIFI_11AXCMD_TXOMI_SUBID 0x105 +#define NXPWIFI_11AXCMD_OBSS_TOLTIME_SUBID 0x106 +#define NXPWIFI_11AXCMD_TXOPRTS_SUBID 0x108 +#define NXPWIFI_11AXCMD_SET_BSRP_SUBID 0x109 +#define NXPWIFI_11AXCMD_LLDE_SUBID 0x110 + +#define NXPWIFI_11AX_TWT_SETUP_SUBID 0x114 +#define NXPWIFI_11AX_TWT_TEARDOWN_SUBID 0x115 +#define NXPWIFI_11AX_TWT_REPORT_SUBID 0x116 +#define NXPWIFI_11AX_TWT_INFORMATION_SUBID 0x119 +#define NXPWIFI_11AX_BTWT_AP_CONFIG_SUBID 0x120 +#define BTWT_AGREEMENT_MAX 5 + +struct nxpwifi_11axcmdcfg_obss_pd_offset { + /* */ + u8 offset[2]; +}; + +struct nxpwifi_11axcmdcfg_sr_control { + /* 1 enable, 0 disable */ + u8 control; +}; + +struct nxpwifi_11ax_sr_cmd { + /* type */ + u16 type; + /* length of TLV */ + u16 len; + /* value */ + union { + struct nxpwifi_11axcmdcfg_obss_pd_offset obss_pd_offset; + struct nxpwifi_11axcmdcfg_sr_control sr_control; + } param; +}; + +struct nxpwifi_11ax_beam_cmd { + /* command value: 1 is disable, 0 is enable */ + u8 value; +}; + +struct nxpwifi_11ax_htc_cmd { + /* command value: 1 is enable, 0 is disable */ + u8 value; +}; + +struct nxpwifi_11ax_txomi_cmd { + /* 11ax spec 9.2.4.6a.2 OM Control 12 bits. Bit 0 to bit 11 */ + u16 omi; + /* + * tx option + * 0: send OMI in QoS NULL; 1: send OMI in QoS data; 0xFF: set OMI in + * both + */ + u8 tx_option; + /* + * if OMI is sent in QoS data, specify the number of consecutive data + * packets containing the OMI + */ + u8 num_data_pkts; +}; + +struct nxpwifi_11ax_toltime_cmd { + /* OBSS Narrow Bandwidth RU Tolerance Time */ + u32 tol_time; +}; + +struct nxpwifi_11ax_txop_cmd { + /* + * Two byte rts threshold value of which only 10 bits, bit 0 to bit 9 + * are valid + */ + u16 rts_thres; +}; + +struct nxpwifi_11ax_set_bsrp_cmd { + /* command value: 1 is enable, 0 is disable */ + u8 value; +}; + +struct nxpwifi_11ax_llde_cmd { + /* Uplink LLDE: enable=1,disable=0 */ + u8 llde; + /* operation mode: default=0,carplay=1,gameplay=2 */ + u8 mode; + /* trigger frame rate: auto=0xff */ + u8 fixrate; + /* cap airtime limit index: auto=0xff */ + u8 trigger_limit; + /* cap peak UL rate */ + u8 peak_ul_rate; + /* Downlink LLDE: enable=1,disable=0 */ + u8 dl_llde; + /* Set trigger frame interval(us): auto=0 */ + u16 poll_interval; + /* Set TxOp duration */ + u16 tx_op_duration; + /* for other configurations */ + u16 llde_ctrl; + u16 mu_rts_successcnt; + u16 mu_rts_failcnt; + u16 basic_trigger_successcnt; + u16 basic_trigger_failcnt; + u16 tbppdu_nullcnt; + u16 tbppdu_datacnt; +}; + +struct nxpwifi_11ax_cmd_cfg { + u32 sub_command; + u32 sub_id; + union { + struct nxpwifi_11ax_sr_cmd sr_cfg; + struct nxpwifi_11ax_beam_cmd beam_cfg; + struct nxpwifi_11ax_htc_cmd htc_cfg; + struct nxpwifi_11ax_txomi_cmd txomi_cfg; + struct nxpwifi_11ax_toltime_cmd toltime_cfg; + struct nxpwifi_11ax_txop_cmd txop_cfg; + struct nxpwifi_11ax_set_bsrp_cmd setbsrp_cfg; + struct nxpwifi_11ax_llde_cmd llde_cfg; + } param; +}; + +struct nxpwifi_twt_setup { + /** Implicit, 0: TWT session is explicit, 1: Session is implicit */ + u8 implicit; + /** Announced, 0: Unannounced, 1: Announced TWT */ + u8 announced; + /** Trigger Enabled, 0: Non-Trigger enabled, 1: Trigger enabled TWT */ + u8 trigger_enabled; + /** TWT Information Disabled, 0: TWT info enabled, 1: TWT info disabled */ + u8 twt_info_disabled; + /* + * Negotiation Type, 0: Future Individual TWT SP start time, 1: + * Next Wake TBTT time + */ + u8 negotiation_type; + /* + * TWT Wakeup Duration, time after which the TWT requesting STA can + * transition to doze state + */ + u8 twt_wakeup_duration; + /** Flow Identifier. Range: [0-7]*/ + u8 flow_identifier; + /* + * Hard Constraint, 0: FW can tweak the TWT setup parameters if it is + * rejected by AP. + * 1: Firmware should not tweak any parameters. + */ + u8 hard_constraint; + /** TWT Exponent, Range: [0-63] */ + u8 twt_exponent; + /** TWT Mantissa Range: [0-sizeof(UINT16)] */ + __le16 twt_mantissa; + /** TWT Request Type, 0: REQUEST_TWT, 1: SUGGEST_TWT*/ + u8 twt_request; + /** TWT Setup State. Set to 0 by driver, filled by FW in response*/ + u8 twt_setup_state; + /** TWT link lost timeout threshold */ + __le16 bcn_miss_threshold; +} __packed; + +struct nxpwifi_twt_teardown { + /** TWT Flow Identifier. Range: [0-7] */ + u8 flow_identifier; + /* + * Negotiation Type. 0: Future Individual TWT SP start time, 1: Next + * Wake TBTT time + */ + u8 negotiation_type; + /** Tear down all TWT. 1: To teardown all TWT, 0 otherwise */ + u8 teardown_all_twt; + /** TWT Teardown State. Set to 0 by driver, filled by FW in response */ + u8 twt_teardown_state; + /** Reserved, set to 0. */ + u8 reserved[3]; +} __packed; + +#define NXPWIFI_BTWT_REPORT_LEN 9 +#define NXPWIFI_BTWT_REPORT_MAX_NUM 4 +struct nxpwifi_twt_report { + /** TWT report type, 0: BTWT id */ + u8 type; + /** TWT report length of value in data */ + u8 length; + u8 reserve[2]; + /** TWT report payload for FW response to fill */ + u8 data[NXPWIFI_BTWT_REPORT_LEN * NXPWIFI_BTWT_REPORT_MAX_NUM]; +} __packed; + +struct nxpwifi_twt_information { + /** TWT Flow Identifier. Range: [0-7] */ + u8 flow_identifier; + /* + * Suspend Duration. Range: [0-UINT32_MAX] + * 0:Suspend forever; + * Else:Suspend agreement for specific duration in milli seconds, + * after than resume the agreement and enter SP immediately + */ + __le32 suspend_duration; + /** TWT Information State. Set to 0 by driver, filled by FW in response */ + u8 twt_information_state; +} __packed; + +struct btwt_set { + u8 btwt_id; + __le16 ap_bcast_mantissa; + u8 ap_bcast_exponent; + u8 nominalwake; +} __packed; + +#define BTWT_AGREEMENT_MAX 5 +struct nxpwifi_btwt_ap_config { + u8 ap_bcast_bet_sta_wait; + __le16 ap_bcast_offset; + u8 bcast_twtli; + u8 count; + struct btwt_set btwt_sets[BTWT_AGREEMENT_MAX]; +} __packed; + +struct nxpwifi_twt_cfg { + u16 action; + u16 sub_id; + union { + struct nxpwifi_twt_setup twt_setup; + struct nxpwifi_twt_teardown twt_teardown; + struct nxpwifi_twt_report twt_report; + struct nxpwifi_twt_information twt_information; + struct nxpwifi_btwt_ap_config btwt_ap_config; + } param; +}; +#endif /* !_NXPWIFI_CFG_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c new file mode 100644 index 000000000000..4f9e20f72811 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -0,0 +1,3931 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: cfg80211 support + * + * Copyright 2011-2024 NXP + */ + +#include "cfg80211.h" +#include "main.h" +#include "cmdevt.h" +#include "11n.h" +#include "wmm.h" + +static const struct ieee80211_iface_limit nxpwifi_ap_sta_limits[] = { + { + .max = NXPWIFI_MAX_BSS_NUM, + .types = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_MONITOR), + }, +}; + +static const struct ieee80211_iface_combination +nxpwifi_iface_comb_ap_sta = { + .limits = nxpwifi_ap_sta_limits, + .num_different_channels = 1, + .n_limits = ARRAY_SIZE(nxpwifi_ap_sta_limits), + .max_interfaces = NXPWIFI_MAX_BSS_NUM, + .beacon_int_infra_match = true, + .radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) | + BIT(NL80211_CHAN_WIDTH_20) | + BIT(NL80211_CHAN_WIDTH_40), +}; + +static const struct ieee80211_iface_combination +nxpwifi_iface_comb_ap_sta_vht = { + .limits = nxpwifi_ap_sta_limits, + .num_different_channels = 1, + .n_limits = ARRAY_SIZE(nxpwifi_ap_sta_limits), + .max_interfaces = NXPWIFI_MAX_BSS_NUM, + .beacon_int_infra_match = true, + .radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) | + BIT(NL80211_CHAN_WIDTH_20) | + BIT(NL80211_CHAN_WIDTH_40) | + BIT(NL80211_CHAN_WIDTH_80), +}; + +/* Map nl80211 channel types to secondary channel offsets */ +u8 nxpwifi_chan_type_to_sec_chan_offset(enum nl80211_channel_type chan_type) +{ + switch (chan_type) { + case NL80211_CHAN_NO_HT: + case NL80211_CHAN_HT20: + return IEEE80211_HT_PARAM_CHA_SEC_NONE; + case NL80211_CHAN_HT40PLUS: + return IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + case NL80211_CHAN_HT40MINUS: + return IEEE80211_HT_PARAM_CHA_SEC_BELOW; + default: + return IEEE80211_HT_PARAM_CHA_SEC_NONE; + } +} + +/* Map IEEE HT secondary‑channel type to nl80211 channel type */ +u8 nxpwifi_get_chan_type(struct nxpwifi_private *priv) +{ + struct nxpwifi_channel_band channel_band; + int ret; + + ret = nxpwifi_get_chan_info(priv, &channel_band); + + if (!ret) { + switch (channel_band.band_config.chan_width) { + case CHAN_BW_20MHZ: + if (IS_11N_ENABLED(priv)) + return NL80211_CHAN_HT20; + else + return NL80211_CHAN_NO_HT; + case CHAN_BW_40MHZ: + if (channel_band.band_config.chan2_offset == + IEEE80211_HT_PARAM_CHA_SEC_ABOVE) + return NL80211_CHAN_HT40PLUS; + else + return NL80211_CHAN_HT40MINUS; + default: + return NL80211_CHAN_HT20; + } + } + + return NL80211_CHAN_HT20; +} + +/* Retrieve the driver private data from the wiphy */ +static void *nxpwifi_cfg80211_get_adapter(struct wiphy *wiphy) +{ + return (void *)(*(unsigned long *)wiphy_priv(wiphy)); +} + +/* cfg80211 operation handler to delete a network key. */ +static int +nxpwifi_cfg80211_del_key(struct wiphy *wiphy, struct wireless_dev *wdev, + int link_id, u8 key_index, bool pairwise, + const u8 *mac_addr) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + static const u8 bc_mac[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + const u8 *peer_mac = pairwise ? mac_addr : bc_mac; + int ret; + + ret = nxpwifi_set_encode(priv, NULL, NULL, 0, key_index, peer_mac, 1); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "crypto keys deleted failed %d\n", ret); + else + nxpwifi_dbg(priv->adapter, INFO, "info: crypto keys deleted\n"); + + return ret; +} + +/* Build an skb containing a management frame */ +static void +nxpwifi_form_mgmt_frame(struct sk_buff *skb, const u8 *buf, size_t len) +{ + u8 addr[ETH_ALEN]; + u16 pkt_len; + u32 tx_control = 0, pkt_type = PKT_TYPE_MGMT; + + eth_broadcast_addr(addr); + pkt_len = len + ETH_ALEN; + + skb_reserve(skb, NXPWIFI_MIN_DATA_HEADER_LEN + + NXPWIFI_MGMT_FRAME_HEADER_SIZE + sizeof(pkt_len)); + memcpy(skb_push(skb, sizeof(pkt_len)), &pkt_len, sizeof(pkt_len)); + + memcpy(skb_push(skb, sizeof(tx_control)), + &tx_control, sizeof(tx_control)); + + memcpy(skb_push(skb, sizeof(pkt_type)), &pkt_type, sizeof(pkt_type)); + + /* Add packet data and address4 */ + skb_put_data(skb, buf, sizeof(struct ieee80211_hdr_3addr)); + skb_put_data(skb, addr, ETH_ALEN); + skb_put_data(skb, buf + sizeof(struct ieee80211_hdr_3addr), + len - sizeof(struct ieee80211_hdr_3addr)); + + skb->priority = TC_PRIO_BESTEFFORT; + __net_timestamp(skb); +} + +/* cfg80211 operation handler to transmit a management frame. */ +static int +nxpwifi_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, + struct cfg80211_mgmt_tx_params *params, u64 *cookie) +{ + const u8 *buf = params->buf; + size_t len = params->len; + struct sk_buff *skb; + u16 pkt_len; + const struct ieee80211_mgmt *mgmt; + struct nxpwifi_txinfo *tx_info; + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + + if (!buf || !len) { + nxpwifi_dbg(priv->adapter, ERROR, "invalid buffer and length\n"); + return -EINVAL; + } + + mgmt = (const struct ieee80211_mgmt *)buf; + if (GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_STA && + ieee80211_is_probe_resp(mgmt->frame_control)) { + /* Offloaded probe responses; skip TX in AP/GO mode */ + nxpwifi_dbg(priv->adapter, INFO, + "info: skip to send probe resp in AP or GO mode\n"); + return 0; + } + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + if (ieee80211_is_auth(mgmt->frame_control)) + nxpwifi_dbg(priv->adapter, MSG, + "auth: send auth to %pM\n", mgmt->da); + if (ieee80211_is_deauth(mgmt->frame_control)) + nxpwifi_dbg(priv->adapter, MSG, + "auth: send deauth to %pM\n", mgmt->da); + if (ieee80211_is_disassoc(mgmt->frame_control)) + nxpwifi_dbg(priv->adapter, MSG, + "assoc: send disassoc to %pM\n", mgmt->da); + if (ieee80211_is_assoc_resp(mgmt->frame_control)) + nxpwifi_dbg(priv->adapter, MSG, + "assoc: send assoc resp to %pM\n", + mgmt->da); + if (ieee80211_is_reassoc_resp(mgmt->frame_control)) + nxpwifi_dbg(priv->adapter, MSG, + "assoc: send reassoc resp to %pM\n", + mgmt->da); + } + + pkt_len = len + ETH_ALEN; + skb = dev_alloc_skb(NXPWIFI_MIN_DATA_HEADER_LEN + + NXPWIFI_MGMT_FRAME_HEADER_SIZE + + pkt_len + sizeof(pkt_len)); + + if (!skb) { + nxpwifi_dbg(priv->adapter, ERROR, + "allocate skb failed for management frame\n"); + return -ENOMEM; + } + + tx_info = NXPWIFI_SKB_TXCB(skb); + memset(tx_info, 0, sizeof(*tx_info)); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + tx_info->pkt_len = pkt_len; + + nxpwifi_form_mgmt_frame(skb, buf, len); + *cookie = nxpwifi_roc_cookie(priv->adapter); + + if (ieee80211_is_action(mgmt->frame_control)) + skb = nxpwifi_clone_skb_for_tx_status(priv, + skb, + NXPWIFI_BUF_FLAG_ACTION_TX_STATUS, cookie); + else + cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, true, + GFP_ATOMIC); + + nxpwifi_queue_tx_pkt(priv, skb); + + nxpwifi_dbg(priv->adapter, INFO, "info: management frame transmitted\n"); + return 0; +} + +/* cfg80211 operation handler to register a mgmt frame. */ +static void +nxpwifi_cfg80211_update_mgmt_frame_registrations(struct wiphy *wiphy, + struct wireless_dev *wdev, + struct mgmt_frame_regs *upd) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + u32 mask = upd->interface_stypes; + + if (mask != priv->mgmt_frame_mask) { + priv->mgmt_frame_mask = mask; + if (priv->host_mlme_reg && + GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_UAP) + priv->mgmt_frame_mask |= HOST_MLME_MGMT_MASK; + + nxpwifi_mgmt_frame_reg(priv, priv->mgmt_frame_mask); + + nxpwifi_dbg(priv->adapter, INFO, "info: mgmt frame registered\n"); + } +} + +/* cfg80211 operation handler to remain on channel. */ +static int +nxpwifi_cfg80211_remain_on_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, + struct ieee80211_channel *chan, + unsigned int duration, u64 *cookie, + const u8 *rx_addr) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + + if (!chan || !cookie) { + nxpwifi_dbg(adapter, ERROR, "Invalid parameter for ROC\n"); + return -EINVAL; + } + + if (priv->roc_cfg.cookie) { + nxpwifi_dbg(adapter, INFO, + "info: ongoing ROC, cookie = 0x%llx\n", + priv->roc_cfg.cookie); + return -EBUSY; + } + + ret = nxpwifi_remain_on_chan_cfg(priv, HOST_ACT_GEN_SET, chan, + duration); + + if (!ret) { + *cookie = nxpwifi_roc_cookie(adapter); + priv->roc_cfg.cookie = *cookie; + priv->roc_cfg.chan = *chan; + + cfg80211_ready_on_channel(wdev, *cookie, chan, + duration, GFP_ATOMIC); + + nxpwifi_dbg(adapter, INFO, + "info: ROC, cookie = 0x%llx\n", *cookie); + } + + return ret; +} + +/* cfg80211 operation handler to cancel remain on channel. */ +static int +nxpwifi_cfg80211_cancel_remain_on_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, u64 cookie) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + int ret; + + if (cookie != priv->roc_cfg.cookie) + return -ENOENT; + + ret = nxpwifi_remain_on_chan_cfg(priv, HOST_ACT_GEN_REMOVE, + &priv->roc_cfg.chan, 0); + + if (!ret) { + cfg80211_remain_on_channel_expired(wdev, cookie, + &priv->roc_cfg.chan, + GFP_ATOMIC); + + memset(&priv->roc_cfg, 0, sizeof(struct nxpwifi_roc_cfg)); + + nxpwifi_dbg(priv->adapter, INFO, + "info: cancel ROC, cookie = 0x%llx\n", cookie); + } + + return ret; +} + +/* cfg80211 operation handler to set Tx power. */ +static int +nxpwifi_cfg80211_set_tx_power(struct wiphy *wiphy, + struct wireless_dev *wdev, + int radio_idx, + enum nl80211_tx_power_setting type, + int mbm) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv; + struct nxpwifi_power_cfg power_cfg; + int dbm = MBM_TO_DBM(mbm); + + switch (type) { + case NL80211_TX_POWER_FIXED: + power_cfg.is_power_auto = 0; + power_cfg.is_power_fixed = 1; + power_cfg.power_level = dbm; + break; + case NL80211_TX_POWER_LIMITED: + power_cfg.is_power_auto = 0; + power_cfg.is_power_fixed = 0; + power_cfg.power_level = dbm; + break; + case NL80211_TX_POWER_AUTOMATIC: + power_cfg.is_power_auto = 1; + break; + } + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + + return nxpwifi_set_tx_power(priv, &power_cfg); +} + +/* cfg80211 operation handler to get Tx power. */ +static int +nxpwifi_cfg80211_get_tx_power(struct wiphy *wiphy, + struct wireless_dev *wdev, + int radio_idx, + unsigned int link_id, + int *dbm) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + int ret = nxpwifi_get_tx_pwr(priv); + + if (ret < 0) + return ret; + + *dbm = priv->tx_power_level; + + return 0; +} + +/* + * cfg80211 handler for setting IEEE 802.11 Power Save mode. + * + * The 'timeout' parameter is not supported and is ignored. + */ +static int +nxpwifi_cfg80211_set_power_mgmt(struct wiphy *wiphy, + struct net_device *dev, + bool enabled, int timeout) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + u32 ps_mode; + + if (timeout) + nxpwifi_dbg(priv->adapter, INFO, + "info: ignore timeout value for IEEE Power Save\n"); + + ps_mode = enabled; + + return nxpwifi_drv_set_power(priv, &ps_mode); +} + +/* + * cfg80211 handler for setting the default WEP key. + * + * Ignored if WEP is not enabled. + */ +static int +nxpwifi_cfg80211_set_default_key(struct wiphy *wiphy, struct net_device *netdev, + int link_id, u8 key_index, bool unicast, + bool multicast) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(netdev); + int ret = 0; + + /* Return if WEP key not configured */ + if (!priv->sec_info.wep_enabled) + return 0; + + if (priv->bss_type == NXPWIFI_BSS_TYPE_UAP) { + priv->wep_key_curr_index = key_index; + } else { + ret = nxpwifi_set_encode(priv, NULL, NULL, 0, key_index, + NULL, 0); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "failed to set default Tx key index\n"); + } + + return ret; +} + +/* cfg80211 handler for adding an 802.11 encryption key. */ +static int +nxpwifi_cfg80211_add_key(struct wiphy *wiphy, struct wireless_dev *wdev, + int link_id, u8 key_index, bool pairwise, + const u8 *mac_addr, struct key_params *params) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_wep_key *wep_key; + u8 bc_mac[ETH_ALEN]; + const u8 *peer_mac; + int ret; + + eth_broadcast_addr(bc_mac); + peer_mac = pairwise ? mac_addr : bc_mac; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP && + (params->cipher == WLAN_CIPHER_SUITE_WEP40 || + params->cipher == WLAN_CIPHER_SUITE_WEP104)) { + if (params->key && params->key_len) { + wep_key = &priv->wep_key[key_index]; + memset(wep_key, 0, sizeof(struct nxpwifi_wep_key)); + memcpy(wep_key->key_material, params->key, + params->key_len); + wep_key->key_index = key_index; + wep_key->key_length = params->key_len; + priv->sec_info.wep_enabled = 1; + } + return 0; + } + + ret = nxpwifi_set_encode(priv, params, params->key, params->key_len, + key_index, peer_mac, 0); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "failed to add crypto keys\n"); + + return ret; +} + +/* cfg80211 handler for setting the default management key. */ +static int +nxpwifi_cfg80211_set_default_mgmt_key(struct wiphy *wiphy, + struct wireless_dev *wdev, + int link_id, + u8 key_index) +{ + return 0; +} + +/* + * Sends regulatory domain information to the firmware. + * + * Includes: + * - Country code + * - Sub-band definitions (first channel, channel count, max TX power) + */ +int nxpwifi_send_domain_info_cmd_fw(struct wiphy *wiphy, enum nl80211_band band) +{ + u8 no_of_triplet = 0; + struct ieee80211_country_ie_triplet *t; + u8 no_of_parsed_chan = 0; + u8 first_chan = 0, next_chan = 0, max_pwr = 0; + u8 i, flag = 0; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch; + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv; + struct nxpwifi_802_11d_domain_reg *domain_info = &adapter->domain_reg; + int ret; + + domain_info->dfs_region = adapter->dfs_region; + + /* Set country code */ + domain_info->country_code[0] = adapter->country_code[0]; + domain_info->country_code[1] = adapter->country_code[1]; + domain_info->country_code[2] = ' '; + + if (!wiphy->bands[band]) { + nxpwifi_dbg(adapter, ERROR, + "11D: setting domain info in FW\n"); + return -EINVAL; + } + + sband = wiphy->bands[band]; + + for (i = 0; i < sband->n_channels ; i++) { + ch = &sband->channels[i]; + if (ch->flags & IEEE80211_CHAN_DISABLED) + continue; + + if (!flag) { + flag = 1; + first_chan = (u32)ch->hw_value; + next_chan = first_chan; + max_pwr = ch->max_power; + no_of_parsed_chan = 1; + continue; + } + + if (ch->hw_value == next_chan + 1 && + ch->max_power == max_pwr) { + next_chan++; + no_of_parsed_chan++; + } else { + t = &domain_info->triplet[no_of_triplet]; + t->chans.first_channel = first_chan; + t->chans.num_channels = no_of_parsed_chan; + t->chans.max_power = max_pwr; + no_of_triplet++; + first_chan = (u32)ch->hw_value; + next_chan = first_chan; + max_pwr = ch->max_power; + no_of_parsed_chan = 1; + } + } + + if (flag) { + t = &domain_info->triplet[no_of_triplet]; + t->chans.first_channel = first_chan; + t->chans.num_channels = no_of_parsed_chan; + t->chans.max_power = max_pwr; + no_of_triplet++; + } + + domain_info->no_of_triplet = no_of_triplet; + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + + ret = nxpwifi_apply_regdomain(priv); + + if (ret) + nxpwifi_dbg(adapter, INFO, + "11D: failed to set domain info in FW\n"); + + return ret; +} + +static void nxpwifi_reg_apply_radar_flags(struct wiphy *wiphy) +{ + struct ieee80211_supported_band *sband; + struct ieee80211_channel *chan; + unsigned int i; + + if (!wiphy->bands[NL80211_BAND_5GHZ]) + return; + sband = wiphy->bands[NL80211_BAND_5GHZ]; + + for (i = 0; i < sband->n_channels; i++) { + chan = &sband->channels[i]; + if ((!(chan->flags & IEEE80211_CHAN_DISABLED)) && + (chan->flags & IEEE80211_CHAN_RADAR)) + chan->flags |= IEEE80211_CHAN_NO_IR; + } +} + +/* + * cfg80211 regulatory domain change callback. + * + * Invoked when the regulatory domain is updated by: + * - the driver + * - the system core + * - the user + * - a received Country IE + */ +static void nxpwifi_reg_notifier(struct wiphy *wiphy, + struct regulatory_request *request) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + + nxpwifi_dbg(adapter, INFO, + "info: cfg80211 regulatory domain callback for %c%c\n", + request->alpha2[0], request->alpha2[1]); + nxpwifi_reg_apply_radar_flags(wiphy); + + switch (request->initiator) { + case NL80211_REGDOM_SET_BY_DRIVER: + case NL80211_REGDOM_SET_BY_CORE: + case NL80211_REGDOM_SET_BY_USER: + case NL80211_REGDOM_SET_BY_COUNTRY_IE: + break; + default: + nxpwifi_dbg(adapter, ERROR, + "unknown regdom initiator: %d\n", + request->initiator); + return; + } + + /* Skip world/unchanged regulatory domains. */ + if (strncmp(request->alpha2, "00", 2) && + strncmp(request->alpha2, adapter->country_code, + sizeof(request->alpha2))) { + memcpy(adapter->country_code, request->alpha2, + sizeof(request->alpha2)); + adapter->dfs_region = request->dfs_region; + nxpwifi_send_domain_info_cmd_fw(wiphy, NL80211_BAND_2GHZ); + if (adapter->fw_bands & BAND_A) + nxpwifi_send_domain_info_cmd_fw(wiphy, + NL80211_BAND_5GHZ); + } +} + +/* + * cfg80211 op: set wiphy parameters. + * Updates RTS/fragmentation thresholds and retry limits based on 'changed' + * flags. + */ +static int +nxpwifi_cfg80211_set_wiphy_params(struct wiphy *wiphy, int radio_idx, u32 changed) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv; + struct nxpwifi_uap_bss_param *bss_cfg; + int ret = 0; + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + + switch (priv->bss_role) { + case NXPWIFI_BSS_ROLE_UAP: + bss_cfg = kzalloc_obj(*bss_cfg, GFP_KERNEL); + if (!bss_cfg) { + ret = -ENOMEM; + break; + } + + nxpwifi_set_sys_config_invalid_data(bss_cfg); + + if (changed & WIPHY_PARAM_RTS_THRESHOLD) + bss_cfg->rts_threshold = wiphy->rts_threshold; + if (changed & WIPHY_PARAM_FRAG_THRESHOLD) + bss_cfg->frag_threshold = wiphy->frag_threshold; + if (changed & WIPHY_PARAM_RETRY_LONG) + bss_cfg->retry_limit = wiphy->retry_long; + + ret = nxpwifi_set_uap_sys_cfg(priv, bss_cfg); + + kfree(bss_cfg); + if (ret) + nxpwifi_dbg(adapter, ERROR, + "Failed to set wiphy phy params\n"); + break; + + case NXPWIFI_BSS_ROLE_STA: + if (changed & WIPHY_PARAM_RTS_THRESHOLD) { + ret = nxpwifi_set_rts(priv, + wiphy->rts_threshold); + if (ret) + break; + } + if (changed & WIPHY_PARAM_FRAG_THRESHOLD) + ret = nxpwifi_set_frag(priv, + wiphy->frag_threshold); + break; + } + + return ret; +} + +static int nxpwifi_deinit_priv_params(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret = 0; + + if (priv->mgmt_frame_mask) { + priv->mgmt_frame_mask = 0; + ret = nxpwifi_mgmt_frame_reg(priv, priv->mgmt_frame_mask); + + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "could not unregister mgmt frame rx\n"); + return ret; + } + priv->host_mlme_reg = false; + + } + + nxpwifi_deauthenticate(priv, NULL); + + atomic_set(&adapter->iface_changing, 1); + flush_workqueue(adapter->workqueue); + flush_workqueue(adapter->rx_workqueue); + nxpwifi_free_priv(priv); + priv->wdev.iftype = NL80211_IFTYPE_UNSPECIFIED; + priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; + priv->sec_info.authentication_mode = NL80211_AUTHTYPE_OPEN_SYSTEM; + + return ret; +} + +static int +nxpwifi_init_new_priv_params(struct nxpwifi_private *priv, + struct net_device *dev, + enum nl80211_iftype type) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_init_priv(priv); + + priv->bss_mode = type; + priv->wdev.iftype = type; + + nxpwifi_init_priv_params(priv, priv->netdev); + priv->bss_started = 0; + + switch (type) { + case NL80211_IFTYPE_STATION: + priv->bss_role = NXPWIFI_BSS_ROLE_STA; + break; + case NL80211_IFTYPE_AP: + priv->bss_role = NXPWIFI_BSS_ROLE_UAP; + break; + default: + nxpwifi_dbg(adapter, ERROR, + "%s: changing to %d not supported\n", + dev->name, type); + return -EOPNOTSUPP; + } + + priv->bss_num = nxpwifi_get_unused_bss_num(adapter, priv->bss_type); + + flush_workqueue(adapter->workqueue); + atomic_set(&adapter->iface_changing, 0); + + nxpwifi_set_mac_address(priv, dev, false, NULL); + + return 0; +} + +static bool +is_vif_type_change_allowed(struct nxpwifi_adapter *adapter, + enum nl80211_iftype old_iftype, + enum nl80211_iftype new_iftype) +{ + switch (old_iftype) { + case NL80211_IFTYPE_STATION: + switch (new_iftype) { + case NL80211_IFTYPE_AP: + return adapter->curr_iface_comb.uap_intf != + adapter->iface_limit.uap_intf; + default: + return false; + } + + case NL80211_IFTYPE_AP: + switch (new_iftype) { + case NL80211_IFTYPE_STATION: + return adapter->curr_iface_comb.sta_intf != + adapter->iface_limit.sta_intf; + default: + return false; + } + + default: + break; + } + + return false; +} + +static void +update_vif_type_counter(struct nxpwifi_adapter *adapter, + enum nl80211_iftype iftype, + int change) +{ + switch (iftype) { + case NL80211_IFTYPE_UNSPECIFIED: + case NL80211_IFTYPE_STATION: + adapter->curr_iface_comb.sta_intf += change; + break; + case NL80211_IFTYPE_AP: + adapter->curr_iface_comb.uap_intf += change; + break; + case NL80211_IFTYPE_MONITOR: + break; + default: + nxpwifi_dbg(adapter, ERROR, + "%s: Unsupported iftype passed: %d\n", + __func__, iftype); + break; + } +} + +static int +nxpwifi_change_vif_to_sta(struct net_device *dev, + enum nl80211_iftype curr_iftype, + enum nl80211_iftype type, + struct vif_params *params) +{ + struct nxpwifi_private *priv; + struct nxpwifi_adapter *adapter; + int ret; + + priv = nxpwifi_netdev_get_priv(dev); + + if (!priv) + return -EINVAL; + + adapter = priv->adapter; + + nxpwifi_dbg(adapter, INFO, + "%s: changing role to station\n", dev->name); + + ret = nxpwifi_deinit_priv_params(priv); + if (ret) + goto done; + ret = nxpwifi_init_new_priv_params(priv, dev, type); + if (ret) + goto done; + + update_vif_type_counter(adapter, curr_iftype, -1); + update_vif_type_counter(adapter, type, 1); + dev->ieee80211_ptr->iftype = type; + + if (nxpwifi_set_bss_mode(priv)) + return -1; + + if (ret) + goto done; + + ret = nxpwifi_sta_init_cmd(priv, false, false); + +done: + return ret; +} + +static int +nxpwifi_change_vif_to_ap(struct net_device *dev, + enum nl80211_iftype curr_iftype, + enum nl80211_iftype type, + struct vif_params *params) +{ + struct nxpwifi_private *priv; + struct nxpwifi_adapter *adapter; + int ret; + + priv = nxpwifi_netdev_get_priv(dev); + + if (!priv) + return -EINVAL; + + adapter = priv->adapter; + + nxpwifi_dbg(adapter, INFO, + "%s: changing role to AP\n", dev->name); + + ret = nxpwifi_deinit_priv_params(priv); + if (ret) + goto done; + + ret = nxpwifi_init_new_priv_params(priv, dev, type); + if (ret) + goto done; + + update_vif_type_counter(adapter, curr_iftype, -1); + update_vif_type_counter(adapter, type, 1); + dev->ieee80211_ptr->iftype = type; + + if (nxpwifi_set_bss_mode(priv)) + return -1; + + if (ret) + goto done; + + ret = nxpwifi_sta_init_cmd(priv, false, false); + +done: + return ret; +} + +/* cfg80211 operation handler to change interface type. */ +static int +nxpwifi_cfg80211_change_virtual_intf(struct wiphy *wiphy, + struct net_device *dev, + enum nl80211_iftype type, + struct vif_params *params) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + enum nl80211_iftype curr_iftype = dev->ieee80211_ptr->iftype; + + if (priv->scan_request) { + nxpwifi_dbg(priv->adapter, ERROR, + "change virtual interface: scan in process\n"); + return -EBUSY; + } + + if (type == NL80211_IFTYPE_UNSPECIFIED) { + nxpwifi_dbg(priv->adapter, INFO, + "%s: no new type specified, keeping old type %d\n", + dev->name, curr_iftype); + return 0; + } + + if (curr_iftype == type) { + nxpwifi_dbg(priv->adapter, INFO, + "%s: interface already is of type %d\n", + dev->name, curr_iftype); + return 0; + } + + if (!is_vif_type_change_allowed(priv->adapter, curr_iftype, type)) { + nxpwifi_dbg(priv->adapter, ERROR, + "%s: change from type %d to %d is not allowed\n", + dev->name, curr_iftype, type); + return -EOPNOTSUPP; + } + + switch (curr_iftype) { + case NL80211_IFTYPE_STATION: + switch (type) { + case NL80211_IFTYPE_AP: + return nxpwifi_change_vif_to_ap(dev, curr_iftype, type, + params); + default: + goto errnotsupp; + } + + case NL80211_IFTYPE_AP: + switch (type) { + case NL80211_IFTYPE_STATION: + return nxpwifi_change_vif_to_sta(dev, curr_iftype, + type, params); + break; + default: + goto errnotsupp; + } + + default: + goto errnotsupp; + } + + return 0; + +errnotsupp: + nxpwifi_dbg(priv->adapter, ERROR, + "unsupported interface type transition: %d to %d\n", + curr_iftype, type); + return -EOPNOTSUPP; +} + +#define RATE_FORMAT_LG 0 +#define RATE_FORMAT_HT 1 +#define RATE_FORMAT_VHT 2 +#define RATE_FORMAT_HE 3 + +static void +nxpwifi_parse_htinfo(struct nxpwifi_private *priv, u8 rateinfo, u8 htinfo, + struct rate_info *rate) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u8 rate_format; + u8 he_dcm; + u8 stbc; + u8 gi; + u8 bw; + /* Bitrates in multiples of 100kb/s. */ + static const int legacy_rates[] = { + [0] = 10, + [1] = 20, + [2] = 55, + [3] = 110, + [4] = 60, /* NXPWIFI_RATE_INDEX_OFDM0 */ + [5] = 60, + [6] = 90, + [7] = 120, + [8] = 180, + [9] = 240, + [10] = 360, + [11] = 480, + [12] = 540, + }; + + rate_format = htinfo & 0x3; + + switch (rate_format) { + case RATE_FORMAT_LG: + if (rateinfo < ARRAY_SIZE(legacy_rates)) + rate->legacy = legacy_rates[rateinfo]; + break; + case RATE_FORMAT_HT: + rate->mcs = rateinfo; + rate->flags |= RATE_INFO_FLAGS_MCS; + break; + case RATE_FORMAT_VHT: + rate->mcs = rateinfo & 0xF; + rate->flags |= RATE_INFO_FLAGS_VHT_MCS; + break; + case RATE_FORMAT_HE: + rate->mcs = rateinfo & 0xF; + rate->flags |= RATE_INFO_FLAGS_HE_MCS; + he_dcm = 0; /* ToDo: ext_rate_info */ + gi = (htinfo & BIT(4)) >> 4 | + (htinfo & BIT(7)) >> 6; + stbc = (htinfo & BIT(5)) >> 5; + if (gi > 3) { + nxpwifi_dbg(adapter, ERROR, "Invalid gi value\n"); + break; + } + if (gi == 3 && stbc && he_dcm) { + gi = 0; + stbc = 0; + he_dcm = 0; + } + if (gi > 0) + gi -= 1; + rate->he_gi = gi; + rate->he_dcm = he_dcm; + break; + } + + bw = (htinfo & 0xC) >> 2; + + switch (bw) { + case 0: + rate->bw = RATE_INFO_BW_20; + break; + case 1: + rate->bw = RATE_INFO_BW_40; + break; + case 2: + rate->bw = RATE_INFO_BW_80; + break; + case 3: + rate->bw = RATE_INFO_BW_160; + break; + } + + if (rate_format != RATE_FORMAT_HE && (htinfo & BIT(4))) + rate->flags |= RATE_INFO_FLAGS_SHORT_GI; + + if ((rateinfo >> 4) == 1) + rate->nss = 2; + else + rate->nss = 1; +} + +/* + * Dump station statistics into station_info. + * Includes bytes/packets counters, signal level, and TX/RX rates. + */ +static int +nxpwifi_dump_station_info(struct nxpwifi_private *priv, + struct nxpwifi_sta_node *node, + struct station_info *sinfo) +{ + u32 rate; + int ret; + + sinfo->filled = BIT_ULL(NL80211_STA_INFO_RX_BYTES) | + BIT_ULL(NL80211_STA_INFO_TX_BYTES) | + BIT_ULL(NL80211_STA_INFO_RX_PACKETS) | + BIT_ULL(NL80211_STA_INFO_TX_PACKETS) | + BIT_ULL(NL80211_STA_INFO_TX_BITRATE) | + BIT_ULL(NL80211_STA_INFO_SIGNAL) | + BIT_ULL(NL80211_STA_INFO_SIGNAL_AVG); + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + if (!node) + return -ENOENT; + + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_INACTIVE_TIME) | + BIT_ULL(NL80211_STA_INFO_TX_FAILED); + sinfo->inactive_time = + jiffies_to_msecs(jiffies - node->stats.last_rx); + + sinfo->signal = node->stats.rssi; + sinfo->signal_avg = node->stats.rssi; + sinfo->rx_bytes = node->stats.rx_bytes; + sinfo->tx_bytes = node->stats.tx_bytes; + sinfo->rx_packets = node->stats.rx_packets; + sinfo->tx_packets = node->stats.tx_packets; + sinfo->tx_failed = node->stats.tx_failed; + + nxpwifi_parse_htinfo(priv, priv->tx_rate, + node->stats.last_tx_htinfo, + &sinfo->txrate); + sinfo->txrate.legacy = node->stats.last_tx_rate * 5; + + return 0; + } + + /* Get signal information from the firmware */ + ret = nxpwifi_get_rssi_info(priv); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "failed to get signal information\n"); + goto done; + } + + ret = nxpwifi_drv_get_data_rate(priv, &rate); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "getting data rate error\n"); + goto done; + } + + /* Retrieve DTIM period value from firmware. */ + nxpwifi_get_802_11_snmp_mib(priv, DTIM_PERIOD_I, &priv->dtim_period); + + nxpwifi_parse_htinfo(priv, priv->tx_rate, priv->tx_htinfo, + &sinfo->txrate); + + sinfo->signal_avg = priv->bcn_rssi_avg; + sinfo->rx_bytes = priv->stats.rx_bytes; + sinfo->tx_bytes = priv->stats.tx_bytes; + sinfo->rx_packets = priv->stats.rx_packets; + sinfo->tx_packets = priv->stats.tx_packets; + sinfo->signal = priv->bcn_rssi_avg; + /* Convert bitrate from 500 kb/s units to 100 kb/s units. */ + sinfo->txrate.legacy = rate * 5; + + sinfo->filled |= BIT(NL80211_STA_INFO_RX_BITRATE); + nxpwifi_parse_htinfo(priv, priv->rxpd_rate, priv->rxpd_htinfo, + &sinfo->rxrate); + + if (priv->bss_mode == NL80211_IFTYPE_STATION) { + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_BSS_PARAM); + sinfo->bss_param.flags = 0; + if (priv->curr_bss_params.bss_descriptor.cap_info_bitmap & + WLAN_CAPABILITY_SHORT_PREAMBLE) + sinfo->bss_param.flags |= + BSS_PARAM_FLAGS_SHORT_PREAMBLE; + if (priv->curr_bss_params.bss_descriptor.cap_info_bitmap & + WLAN_CAPABILITY_SHORT_SLOT_TIME) + sinfo->bss_param.flags |= + BSS_PARAM_FLAGS_SHORT_SLOT_TIME; + sinfo->bss_param.dtim_period = priv->dtim_period; + sinfo->bss_param.beacon_interval = + priv->curr_bss_params.bss_descriptor.beacon_period; + } + +done: + return ret; +} + +/* + * cfg80211 op: get station information. + * Works only when connected and fills station_info with current stats. + */ +static int +nxpwifi_cfg80211_get_station(struct wiphy *wiphy, struct wireless_dev *wdev, + const u8 *mac, struct station_info *sinfo) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_sta_node *node; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) { + if (!priv->media_connected || + memcmp(mac, priv->cfg_bssid, ETH_ALEN)) + return -ENOENT; + node = NULL; + } else { + rcu_read_lock(); + node = nxpwifi_get_sta_entry(priv, mac); + rcu_read_unlock(); + } + + return nxpwifi_dump_station_info(priv, node, sinfo); +} + +/* cfg80211 operation handler to dump station information. */ +static int +nxpwifi_cfg80211_dump_station(struct wiphy *wiphy, struct wireless_dev *wdev, + int idx, u8 *mac, struct station_info *sinfo) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_sta_node *node; + struct nxpwifi_sta_node *found = NULL; + int i; + + if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) && + priv->media_connected && idx == 0) { + ether_addr_copy(mac, priv->cfg_bssid); + return nxpwifi_dump_station_info(priv, NULL, sinfo); + } else if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + nxpwifi_ap_get_sta_list(priv); + + i = 0; + rcu_read_lock(); + list_for_each_entry_rcu(node, &priv->sta_list, list) { + if (i++ != idx) + continue; + found = node; + break; + } + rcu_read_unlock(); + + if (found) { + ether_addr_copy(mac, node->mac_addr); + return nxpwifi_dump_station_info(priv, node, sinfo); + } + } + + return -ENOENT; +} + +static int +nxpwifi_cfg80211_dump_survey(struct wiphy *wiphy, struct net_device *dev, + int idx, struct survey_info *survey) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_chan_stats *pchan_stats = priv->adapter->chan_stats; + enum nl80211_band band; + u8 chan_num; + + nxpwifi_dbg(priv->adapter, DUMP, "dump_survey idx=%d\n", idx); + + memset(survey, 0, sizeof(struct survey_info)); + + if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) && + priv->media_connected && idx == 0) { + u8 curr_bss_band = priv->curr_bss_params.band; + u32 chan = priv->curr_bss_params.bss_descriptor.channel; + + band = nxpwifi_band_to_radio_type(curr_bss_band); + survey->channel = ieee80211_get_channel + (wiphy, + ieee80211_channel_to_frequency(chan, band)); + + if (priv->bcn_nf_last) { + survey->filled = SURVEY_INFO_NOISE_DBM; + survey->noise = priv->bcn_nf_last; + } + return 0; + } + + if (idx >= priv->adapter->num_in_chan_stats) + return -ENOENT; + + if (!pchan_stats[idx].cca_scan_dur) + return 0; + + band = pchan_stats[idx].bandcfg; + chan_num = pchan_stats[idx].chan_num; + survey->channel = ieee80211_get_channel + (wiphy, + ieee80211_channel_to_frequency(chan_num, band)); + survey->filled = SURVEY_INFO_NOISE_DBM | + SURVEY_INFO_TIME | + SURVEY_INFO_TIME_BUSY; + survey->noise = pchan_stats[idx].noise; + survey->time = pchan_stats[idx].cca_scan_dur; + survey->time_busy = pchan_stats[idx].cca_busy_dur; + + return 0; +} + +/* Supported rates to be advertised to the cfg80211 */ +static struct ieee80211_rate nxpwifi_rates[] = { + {.bitrate = 10, .hw_value = 2, }, + {.bitrate = 20, .hw_value = 4, }, + {.bitrate = 55, .hw_value = 11, }, + {.bitrate = 110, .hw_value = 22, }, + {.bitrate = 60, .hw_value = 12, }, + {.bitrate = 90, .hw_value = 18, }, + {.bitrate = 120, .hw_value = 24, }, + {.bitrate = 180, .hw_value = 36, }, + {.bitrate = 240, .hw_value = 48, }, + {.bitrate = 360, .hw_value = 72, }, + {.bitrate = 480, .hw_value = 96, }, + {.bitrate = 540, .hw_value = 108, }, +}; + +/* Channel definitions to be advertised to cfg80211 */ +static struct ieee80211_channel nxpwifi_channels_2ghz[] = { + {.center_freq = 2412, .hw_value = 1, }, + {.center_freq = 2417, .hw_value = 2, }, + {.center_freq = 2422, .hw_value = 3, }, + {.center_freq = 2427, .hw_value = 4, }, + {.center_freq = 2432, .hw_value = 5, }, + {.center_freq = 2437, .hw_value = 6, }, + {.center_freq = 2442, .hw_value = 7, }, + {.center_freq = 2447, .hw_value = 8, }, + {.center_freq = 2452, .hw_value = 9, }, + {.center_freq = 2457, .hw_value = 10, }, + {.center_freq = 2462, .hw_value = 11, }, + {.center_freq = 2467, .hw_value = 12, }, + {.center_freq = 2472, .hw_value = 13, }, + {.center_freq = 2484, .hw_value = 14, }, +}; + +static struct ieee80211_supported_band nxpwifi_band_2ghz = { + .band = NL80211_BAND_2GHZ, + .channels = nxpwifi_channels_2ghz, + .n_channels = ARRAY_SIZE(nxpwifi_channels_2ghz), + .bitrates = nxpwifi_rates, + .n_bitrates = ARRAY_SIZE(nxpwifi_rates), +}; + +static struct ieee80211_channel nxpwifi_channels_5ghz[] = { + {.center_freq = 5040, .hw_value = 8, }, + {.center_freq = 5060, .hw_value = 12, }, + {.center_freq = 5080, .hw_value = 16, }, + {.center_freq = 5170, .hw_value = 34, }, + {.center_freq = 5190, .hw_value = 38, }, + {.center_freq = 5210, .hw_value = 42, }, + {.center_freq = 5230, .hw_value = 46, }, + {.center_freq = 5180, .hw_value = 36, }, + {.center_freq = 5200, .hw_value = 40, }, + {.center_freq = 5220, .hw_value = 44, }, + {.center_freq = 5240, .hw_value = 48, }, + {.center_freq = 5260, .hw_value = 52, }, + {.center_freq = 5280, .hw_value = 56, }, + {.center_freq = 5300, .hw_value = 60, }, + {.center_freq = 5320, .hw_value = 64, }, + {.center_freq = 5500, .hw_value = 100, }, + {.center_freq = 5520, .hw_value = 104, }, + {.center_freq = 5540, .hw_value = 108, }, + {.center_freq = 5560, .hw_value = 112, }, + {.center_freq = 5580, .hw_value = 116, }, + {.center_freq = 5600, .hw_value = 120, }, + {.center_freq = 5620, .hw_value = 124, }, + {.center_freq = 5640, .hw_value = 128, }, + {.center_freq = 5660, .hw_value = 132, }, + {.center_freq = 5680, .hw_value = 136, }, + {.center_freq = 5700, .hw_value = 140, }, + {.center_freq = 5745, .hw_value = 149, }, + {.center_freq = 5765, .hw_value = 153, }, + {.center_freq = 5785, .hw_value = 157, }, + {.center_freq = 5805, .hw_value = 161, }, + {.center_freq = 5825, .hw_value = 165, }, +}; + +static struct ieee80211_supported_band nxpwifi_band_5ghz = { + .band = NL80211_BAND_5GHZ, + .channels = nxpwifi_channels_5ghz, + .n_channels = ARRAY_SIZE(nxpwifi_channels_5ghz), + .bitrates = nxpwifi_rates + 4, + .n_bitrates = ARRAY_SIZE(nxpwifi_rates) - 4, +}; + +/* Supported crypto cipher suits to be advertised to cfg80211 */ +static const u32 nxpwifi_cipher_suites[] = { + WLAN_CIPHER_SUITE_WEP40, + WLAN_CIPHER_SUITE_WEP104, + WLAN_CIPHER_SUITE_TKIP, + WLAN_CIPHER_SUITE_CCMP, + WLAN_CIPHER_SUITE_SMS4, + WLAN_CIPHER_SUITE_AES_CMAC, + WLAN_CIPHER_SUITE_GCMP_256, + WLAN_CIPHER_SUITE_CCMP_256, + WLAN_CIPHER_SUITE_BIP_GMAC_256, + WLAN_CIPHER_SUITE_BIP_CMAC_256, +}; + +/* Supported mgmt frame types to be advertised to cfg80211 */ +static const struct ieee80211_txrx_stypes +nxpwifi_mgmt_stypes[NUM_NL80211_IFTYPES] = { + [NL80211_IFTYPE_STATION] = { + .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_RESP >> 4), + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4), + }, + [NL80211_IFTYPE_AP] = { + .tx = 0xffff, + .rx = BIT(IEEE80211_STYPE_ASSOC_REQ >> 4) | + BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) | + BIT(IEEE80211_STYPE_DISASSOC >> 4) | + BIT(IEEE80211_STYPE_AUTH >> 4) | + BIT(IEEE80211_STYPE_DEAUTH >> 4) | + BIT(IEEE80211_STYPE_ACTION >> 4), + }, +}; + +/* + * cfg80211 op: set bitrate mask. + * Converts cfg80211 bitrate selections into firmware bitmap format. + */ +static int +nxpwifi_cfg80211_set_bitrate_mask(struct wiphy *wiphy, + struct net_device *dev, + unsigned int link_id, + const u8 *peer, + const struct cfg80211_bitrate_mask *mask) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + u16 bitmap_rates[MAX_BITMAP_RATES_SIZE]; + enum nl80211_band band; + struct nxpwifi_adapter *adapter = priv->adapter; + + if (!priv->media_connected) { + nxpwifi_dbg(adapter, ERROR, + "Can not set Tx data rate in disconnected state\n"); + return -EINVAL; + } + + band = nxpwifi_band_to_radio_type(priv->curr_bss_params.band); + + memset(bitmap_rates, 0, sizeof(bitmap_rates)); + + /* Fill HR/DSSS legacy rates (2.4 GHz only). */ + if (band == NL80211_BAND_2GHZ) + bitmap_rates[0] = mask->control[band].legacy & 0x000f; + + /* Fill OFDM legacy rates. */ + if (band == NL80211_BAND_2GHZ) + bitmap_rates[1] = (mask->control[band].legacy & 0x0ff0) >> 4; + else + bitmap_rates[1] = mask->control[band].legacy; + + /* Fill HT MCS bitmap (1x1 or 2x2 depending on hardware). */ + bitmap_rates[2] = mask->control[band].ht_mcs[0]; + if (adapter->hw_dev_mcs_support == HT_STREAM_2X2) + bitmap_rates[2] |= mask->control[band].ht_mcs[1] << 8; + + /* Fill VHT MCS bitmap if supported by firmware. */ + if (adapter->fw_api_ver == NXPWIFI_FW_V15) { + bitmap_rates[10] = mask->control[band].vht_mcs[0]; + if (adapter->hw_dev_mcs_support == HT_STREAM_2X2) + bitmap_rates[11] = mask->control[band].vht_mcs[1]; + } + + return nxpwifi_set_tx_rate(priv, bitmap_rates); +} + +/* + * cfg80211 op: configure connection-quality monitoring. + * Subscribes or unsubscribes HIGH_RSSI and LOW_RSSI events to firmware. + */ +static int nxpwifi_cfg80211_set_cqm_rssi_config(struct wiphy *wiphy, + struct net_device *dev, + s32 rssi_thold, u32 rssi_hyst) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_ds_misc_subsc_evt subsc_evt; + int ret = 0; + + priv->cqm_rssi_thold = rssi_thold; + priv->cqm_rssi_hyst = rssi_hyst; + + memset(&subsc_evt, 0x00, sizeof(struct nxpwifi_ds_misc_subsc_evt)); + subsc_evt.events = BITMASK_BCN_RSSI_LOW | BITMASK_BCN_RSSI_HIGH; + + /* Subscribe/unsubscribe low and high rssi events */ + if (rssi_thold && rssi_hyst) { + subsc_evt.action = HOST_ACT_BITWISE_SET; + subsc_evt.bcn_l_rssi_cfg.abs_value = abs(rssi_thold); + subsc_evt.bcn_h_rssi_cfg.abs_value = abs(rssi_thold); + subsc_evt.bcn_l_rssi_cfg.evt_freq = 1; + subsc_evt.bcn_h_rssi_cfg.evt_freq = 1; + ret = nxpwifi_802_11_subscribe_event(priv, &subsc_evt); + } else { + subsc_evt.action = HOST_ACT_BITWISE_CLR; + ret = nxpwifi_802_11_subscribe_event(priv, &subsc_evt); + } + + return ret; +} + +/* + * cfg80211 operation handler for change_beacon. + * Function retrieves and sets modified management IEs to FW. + */ +int nxpwifi_cfg80211_change_beacon(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_ap_update *params) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_adapter *adapter = priv->adapter; + struct cfg80211_beacon_data *data = ¶ms->beacon; + int ret; + + nxpwifi_cancel_scan(adapter); + + if (GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_UAP) { + nxpwifi_dbg(priv->adapter, ERROR, + "%s: bss_type mismatched\n", __func__); + return -EINVAL; + } + + ret = nxpwifi_set_mgmt_ies(priv, data); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "%s: setting mgmt ies failed\n", __func__); + + return ret; +} + +/* + * cfg80211 operation handler for del_station. + * Function deauthenticates station which value is provided in mac parameter. + * If mac is NULL/broadcast, all stations in associated station list are + * deauthenticated. If bss is not started or there are no stations in + * associated stations list, no action is taken. + */ +static int +nxpwifi_cfg80211_del_station(struct wiphy *wiphy, struct wireless_dev *wdev, + struct station_del_parameters *params) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_sta_node *sta_node; + u8 deauth_mac[ETH_ALEN]; + int ret = 0; + + if (!priv->bss_started && priv->wdev.links[0].cac_started) { + nxpwifi_dbg(priv->adapter, INFO, "%s: abort CAC!\n", __func__); + nxpwifi_abort_cac(priv); + } + + if (list_empty(&priv->sta_list) || !priv->bss_started) + return 0; + + if (!params->mac || is_broadcast_ether_addr(params->mac)) + return 0; + + nxpwifi_dbg(priv->adapter, INFO, "%s: mac address %pM\n", + __func__, params->mac); + + eth_zero_addr(deauth_mac); + + sta_node = nxpwifi_get_sta_entry(priv, params->mac); + if (sta_node) + ether_addr_copy(deauth_mac, params->mac); + + if (is_valid_ether_addr(deauth_mac)) { + ret = nxpwifi_uap_sta_deauth(priv, deauth_mac); + nxpwifi_del_sta_entry(priv, deauth_mac); + } + return ret; +} + +static int +nxpwifi_cfg80211_set_antenna(struct wiphy *wiphy, int radio_idx, u32 tx_ant, u32 rx_ant) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv = nxpwifi_get_priv(adapter, + NXPWIFI_BSS_ROLE_ANY); + struct nxpwifi_ds_ant_cfg ant_cfg; + + if (!tx_ant || !rx_ant) + return -EOPNOTSUPP; + + if (adapter->hw_dev_mcs_support != HT_STREAM_2X2) { + /* + * Not a MIMO chip. User should provide specific antenna number + * for Tx/Rx path or enable all antennas for diversity + */ + if (tx_ant != rx_ant) + return -EOPNOTSUPP; + + if ((tx_ant & (tx_ant - 1)) && + (tx_ant != BIT(adapter->number_of_antenna) - 1)) + return -EOPNOTSUPP; + + if ((tx_ant == BIT(adapter->number_of_antenna) - 1) && + priv->adapter->number_of_antenna > 1) { + tx_ant = RF_ANTENNA_AUTO; + rx_ant = RF_ANTENNA_AUTO; + } + } else { + struct ieee80211_sta_ht_cap *ht_info; + int rx_mcs_supp; + enum nl80211_band band; + + if ((tx_ant == 0x1 && rx_ant == 0x1)) { + adapter->user_dev_mcs_support = HT_STREAM_1X1; + if (adapter->is_hw_11ac_capable) + adapter->usr_dot_11ac_mcs_support = + NXPWIFI_11AC_MCS_MAP_1X1; + } else { + adapter->user_dev_mcs_support = HT_STREAM_2X2; + if (adapter->is_hw_11ac_capable) + adapter->usr_dot_11ac_mcs_support = + NXPWIFI_11AC_MCS_MAP_2X2; + } + + for (band = 0; band < NUM_NL80211_BANDS; band++) { + if (!adapter->wiphy->bands[band]) + continue; + + ht_info = &adapter->wiphy->bands[band]->ht_cap; + rx_mcs_supp = + GET_RXMCSSUPP(adapter->user_dev_mcs_support); + memset(&ht_info->mcs, 0, adapter->number_of_antenna); + memset(&ht_info->mcs, 0xff, rx_mcs_supp); + } + } + + ant_cfg.tx_ant = tx_ant; + ant_cfg.rx_ant = rx_ant; + + return nxpwifi_set_rf_antenna(priv, &ant_cfg); +} + +static int +nxpwifi_cfg80211_get_antenna(struct wiphy *wiphy, int radio_idx, u32 *tx_ant, u32 *rx_ant) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv = nxpwifi_get_priv(adapter, + NXPWIFI_BSS_ROLE_ANY); + int ret; + + ret = nxpwifi_get_rf_antenna(priv, tx_ant, rx_ant); + + return ret; +} + +/* + * cfg80211 op: stop AP. + * Stops the BSS running on the uAP interface. + */ +static int nxpwifi_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *dev, + unsigned int link_id) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + int ret; + + nxpwifi_abort_cac(priv); + + if (nxpwifi_del_mgmt_ies(priv)) + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to delete mgmt IEs!\n"); + + priv->ap_11n_enabled = 0; + memset(&priv->bss_cfg, 0, sizeof(priv->bss_cfg)); + + ret = nxpwifi_ap_stop_bss(priv); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to stop the BSS\n"); + goto done; + } + + ret = nxpwifi_ap_sys_reset(priv); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to reset BSS\n"); + goto done; + } + + netif_carrier_off(priv->netdev); + nxpwifi_stop_net_dev_queue(priv->netdev, priv->adapter); + + if (atomic_dec_and_test(&priv->adapter->uap_count)) { + priv->adapter->chandef_valid = false; + memset(&priv->adapter->chandef, 0, sizeof(priv->adapter->chandef)); + } + +done: + return ret; +} + +/* + * cfg80211 op: start AP. + * Applies beacon/DTIM/SSID/security settings to the uAP configuration and + * starts the BSS. + */ +static int nxpwifi_cfg80211_start_ap(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_ap_settings *params) +{ + struct nxpwifi_uap_bss_param *bss_cfg; + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_adapter *adapter = priv->adapter; + struct cfg80211_chan_def use_chandef; + bool is_first_uap = false; + int ret; + + /* + * Adapter is the HW channel owner (single PHY). + * All UAP interfaces on the same adapter must share + * the same RF channel. + */ + use_chandef = params->chandef; + + if (adapter->chandef_valid) { + if (!cfg80211_chandef_identical(&adapter->chandef, + ¶ms->chandef)) { + nxpwifi_dbg(adapter, INFO, + "UAP already running on channel %d, ignore requested channel %d\n", + adapter->chandef.chan->hw_value, + params->chandef.chan->hw_value); + } + use_chandef = adapter->chandef; + } else { + is_first_uap = true; + } + + if (GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_UAP) + return -EINVAL; + + if (!nxpwifi_is_channel_setting_allowable(priv, params->chandef.chan)) + return -EOPNOTSUPP; + + bss_cfg = kzalloc_obj(*bss_cfg, GFP_KERNEL); + if (!bss_cfg) + return -ENOMEM; + + nxpwifi_set_sys_config_invalid_data(bss_cfg); + + memcpy(bss_cfg->mac_addr, priv->curr_addr, ETH_ALEN); + + if (params->beacon_interval) + bss_cfg->beacon_period = params->beacon_interval; + if (params->dtim_period) + bss_cfg->dtim_period = params->dtim_period; + + if (params->ssid && params->ssid_len) { + memcpy(bss_cfg->ssid.ssid, params->ssid, params->ssid_len); + bss_cfg->ssid.ssid_len = params->ssid_len; + } + if (params->inactivity_timeout > 0) { + /* sta_ao_timer/ps_sta_ao_timer is in unit of 100ms */ + bss_cfg->sta_ao_timer = 10 * params->inactivity_timeout; + bss_cfg->ps_sta_ao_timer = 10 * params->inactivity_timeout; + } + + /* Default: SSID is visible */ + bss_cfg->bcast_ssid_ctl = NXPWIFI_BCAST_SSID_VISIBLE; + + switch (params->hidden_ssid) { + case NL80211_HIDDEN_SSID_NOT_IN_USE: + bss_cfg->bcast_ssid_ctl = NXPWIFI_BCAST_SSID_VISIBLE; + break; + case NL80211_HIDDEN_SSID_ZERO_LEN: + bss_cfg->bcast_ssid_ctl = NXPWIFI_BCAST_SSID_HIDE_LEN_ZERO; + break; + case NL80211_HIDDEN_SSID_ZERO_CONTENTS: + bss_cfg->bcast_ssid_ctl = NXPWIFI_BCAST_SSID_HIDE_LEN_RETAIN; + break; + } + + nxpwifi_uap_set_channel(priv, bss_cfg, use_chandef); + nxpwifi_set_uap_rates(bss_cfg, params); + + ret = nxpwifi_set_secure_params(priv, bss_cfg, params); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "Failed to parse security parameters!\n"); + goto done; + } + + nxpwifi_set_ht_params(priv, bss_cfg, params); + + if (adapter->is_hw_11ac_capable) { + nxpwifi_set_vht_params(priv, bss_cfg, params); + nxpwifi_set_vht_width(priv, use_chandef.width, + priv->ap_11ac_enabled); + } + + if (priv->ap_11ac_enabled) + nxpwifi_set_11ac_ba_params(priv); + else + nxpwifi_set_ba_params(priv); + + if (adapter->is_hw_11ax_capable) { + priv->ap_11ax_enabled = + nxpwifi_check_11ax_capability(priv, bss_cfg, params); + if (priv->ap_11ax_enabled) + nxpwifi_set_11ax_status(priv, bss_cfg, params); + } + + nxpwifi_set_wmm_params(priv, bss_cfg, params); + + if (nxpwifi_is_11h_active(priv)) + nxpwifi_set_tpc_params(priv, bss_cfg, params); + + if (nxpwifi_is_11h_active(priv) && + !cfg80211_chandef_dfs_required(wiphy, ¶ms->chandef, + priv->bss_mode)) { + nxpwifi_dbg(priv->adapter, INFO, + "Disable 11h extensions in FW\n"); + ret = nxpwifi_11h_activate(priv, false); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to disable 11h extensions!!"); + goto done; + } + priv->state_11h.is_11h_active = false; + } + + nxpwifi_config_uap_11d(priv, ¶ms->beacon); + + ret = nxpwifi_set_mgmt_ies(priv, ¶ms->beacon); + if (ret) + goto done; + + ret = nxpwifi_config_start_uap(priv, bss_cfg); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to start AP\n"); + goto done; + } + + /* First UAP records adapter-level HW channel */ + if (is_first_uap) { + adapter->chandef = use_chandef; + adapter->chandef_valid = true; + } + atomic_inc(&adapter->uap_count); + netif_carrier_on(priv->netdev); + nxpwifi_wake_up_net_dev_queue(priv->netdev, priv->adapter); + + memcpy(&priv->bss_cfg, bss_cfg, sizeof(priv->bss_cfg)); + +done: + kfree(bss_cfg); + return ret; +} + +/* + * cfg80211 op: handle scan request. + * Issues a firmware scan using the requested parameters and reports the + * results. + */ +static int +nxpwifi_cfg80211_scan(struct wiphy *wiphy, + struct cfg80211_scan_request *request) +{ + struct net_device *dev = request->wdev->netdev; + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + int i, offset, ret; + struct ieee80211_channel *chan; + struct element *ie; + struct nxpwifi_user_scan_cfg *user_scan_cfg; + u8 mac_addr[ETH_ALEN]; + + nxpwifi_dbg(priv->adapter, CMD, + "info: received scan request on %s\n", dev->name); + + /* + * Block scan requests during active scanning or scan cleanup. + * Prevents new scans when the interface is disabled or teardown is in + * progress. + */ + if (priv->scan_request || priv->scan_aborting) { + nxpwifi_dbg(priv->adapter, WARN, + "cmd: Scan already in process..\n"); + return -EBUSY; + } + + if (!priv->wdev.connected && priv->scan_block) + priv->scan_block = false; + + if (!nxpwifi_stop_bg_scan(priv)) + cfg80211_sched_scan_stopped_locked(priv->wdev.wiphy, 0); + + user_scan_cfg = kzalloc_obj(*user_scan_cfg, GFP_KERNEL); + if (!user_scan_cfg) + return -ENOMEM; + + priv->scan_request = request; + + if (request->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) { + get_random_mask_addr(mac_addr, request->mac_addr, + request->mac_addr_mask); + ether_addr_copy(request->mac_addr, mac_addr); + ether_addr_copy(user_scan_cfg->random_mac, mac_addr); + } + + user_scan_cfg->num_ssids = request->n_ssids; + user_scan_cfg->ssid_list = request->ssids; + + if (request->ie && request->ie_len) { + offset = 0; + for (i = 0; i < NXPWIFI_MAX_VSIE_NUM; i++) { + if (priv->vs_ie[i].mask != NXPWIFI_VSIE_MASK_CLEAR) + continue; + priv->vs_ie[i].mask = NXPWIFI_VSIE_MASK_SCAN; + ie = (struct element *)(request->ie + offset); + memcpy(&priv->vs_ie[i].ie, ie, + sizeof(*ie) + ie->datalen); + offset += sizeof(*ie) + ie->datalen; + + if (offset >= request->ie_len) + break; + } + } + + for (i = 0; i < min_t(u32, request->n_channels, + NXPWIFI_USER_SCAN_CHAN_MAX); i++) { + chan = request->channels[i]; + user_scan_cfg->chan_list[i].chan_number = chan->hw_value; + user_scan_cfg->chan_list[i].radio_type = chan->band; + + if ((chan->flags & IEEE80211_CHAN_NO_IR) || !request->n_ssids) + user_scan_cfg->chan_list[i].scan_type = + NXPWIFI_SCAN_TYPE_PASSIVE; + else + user_scan_cfg->chan_list[i].scan_type = + NXPWIFI_SCAN_TYPE_ACTIVE; + + user_scan_cfg->chan_list[i].scan_time = 0; + } + + if (priv->adapter->scan_chan_gap_enabled && + nxpwifi_is_any_intf_active(priv)) + user_scan_cfg->scan_chan_gap = + priv->adapter->scan_chan_gap_time; + + ret = nxpwifi_scan_networks(priv, user_scan_cfg); + kfree(user_scan_cfg); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "scan failed: %d\n", ret); + priv->scan_aborting = false; + priv->scan_request = NULL; + return ret; + } + + if (request->ie && request->ie_len) { + for (i = 0; i < NXPWIFI_MAX_VSIE_NUM; i++) { + if (priv->vs_ie[i].mask == NXPWIFI_VSIE_MASK_SCAN) { + priv->vs_ie[i].mask = NXPWIFI_VSIE_MASK_CLEAR; + memset(&priv->vs_ie[i].ie, 0, + NXPWIFI_MAX_VSIE_LEN); + } + } + } + return 0; +} + +/* + * cfg80211 sched_scan_start handler. + * + * Send a bgscan configuration request to the firmware based on the + * scheduled scan parameters. On success, the firmware later issues a + * BGSCAN_REPORT event, after which the driver should query the firmware + * for scan results. + */ +static int +nxpwifi_cfg80211_sched_scan_start(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_sched_scan_request *request) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + int i, offset; + struct ieee80211_channel *chan; + struct nxpwifi_bg_scan_cfg *bgscan_cfg; + struct element *ie; + int ret; + + if (!request || (!request->n_ssids && !request->n_match_sets)) { + wiphy_err(wiphy, "%s : Invalid Sched_scan parameters", + __func__); + return -EINVAL; + } + + wiphy_info(wiphy, "sched_scan start : n_ssids=%d n_match_sets=%d ", + request->n_ssids, request->n_match_sets); + wiphy_info(wiphy, "n_channels=%d interval=%d ie_len=%d\n", + request->n_channels, request->scan_plans->interval, + (int)request->ie_len); + + bgscan_cfg = kzalloc_obj(*bgscan_cfg, GFP_KERNEL); + if (!bgscan_cfg) + return -ENOMEM; + + if (priv->scan_request || priv->scan_aborting) + bgscan_cfg->start_later = true; + + bgscan_cfg->num_ssids = request->n_match_sets; + bgscan_cfg->ssid_list = request->match_sets; + + if (request->ie && request->ie_len) { + offset = 0; + for (i = 0; i < NXPWIFI_MAX_VSIE_NUM; i++) { + if (priv->vs_ie[i].mask != NXPWIFI_VSIE_MASK_CLEAR) + continue; + priv->vs_ie[i].mask = NXPWIFI_VSIE_MASK_BGSCAN; + ie = (struct element *)(request->ie + offset); + memcpy(&priv->vs_ie[i].ie, ie, + sizeof(*ie) + ie->datalen); + offset += sizeof(*ie) + ie->datalen; + + if (offset >= request->ie_len) + break; + } + } + + for (i = 0; i < min_t(u32, request->n_channels, + NXPWIFI_BG_SCAN_CHAN_MAX); i++) { + chan = request->channels[i]; + bgscan_cfg->chan_list[i].chan_number = chan->hw_value; + bgscan_cfg->chan_list[i].radio_type = chan->band; + + if ((chan->flags & IEEE80211_CHAN_NO_IR) || !request->n_ssids) + bgscan_cfg->chan_list[i].scan_type = + NXPWIFI_SCAN_TYPE_PASSIVE; + else + bgscan_cfg->chan_list[i].scan_type = + NXPWIFI_SCAN_TYPE_ACTIVE; + + bgscan_cfg->chan_list[i].scan_time = 0; + } + + bgscan_cfg->chan_per_scan = min_t(u32, request->n_channels, + NXPWIFI_BG_SCAN_CHAN_MAX); + + /* Minimum scan cycle duration: 15 seconds */ + bgscan_cfg->scan_interval = (request->scan_plans->interval > + NXPWIFI_BGSCAN_INTERVAL) ? + request->scan_plans->interval : + NXPWIFI_BGSCAN_INTERVAL; + + bgscan_cfg->repeat_count = NXPWIFI_BGSCAN_REPEAT_COUNT; + bgscan_cfg->report_condition = NXPWIFI_BGSCAN_SSID_MATCH | + NXPWIFI_BGSCAN_WAIT_ALL_CHAN_DONE; + bgscan_cfg->bss_type = NXPWIFI_BSS_MODE_INFRA; + bgscan_cfg->action = NXPWIFI_BGSCAN_ACT_SET; + bgscan_cfg->enable = true; + if (request->min_rssi_thold != NL80211_SCAN_RSSI_THOLD_OFF) { + bgscan_cfg->report_condition |= NXPWIFI_BGSCAN_SSID_RSSI_MATCH; + bgscan_cfg->rssi_threshold = request->min_rssi_thold; + } + + ret = nxpwifi_bg_scan_config(priv, bgscan_cfg); + + if (!ret) + priv->sched_scanning = true; + + kfree(bgscan_cfg); + return ret; +} + +/* + * cfg80211 sched_scan_stop handler. + * + * Send a bgscan configuration command to disable the previous + * background scan settings in the firmware. + */ +static int nxpwifi_cfg80211_sched_scan_stop(struct wiphy *wiphy, + struct net_device *dev, u64 reqid) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + + wiphy_info(wiphy, "sched scan stop!"); + return nxpwifi_stop_bg_scan(priv); +} + +/* + * Set default cfg80211 HT capabilities. + */ +static void +nxpwifi_setup_ht_caps(struct nxpwifi_private *priv, + struct ieee80211_sta_ht_cap *ht_info) +{ + int rx_mcs_supp; + struct ieee80211_mcs_info mcs_set; + u8 *mcs = (u8 *)&mcs_set; + struct nxpwifi_adapter *adapter = priv->adapter; + + ht_info->ht_supported = true; + ht_info->ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; + ht_info->ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE; + + memset(&ht_info->mcs, 0, sizeof(ht_info->mcs)); + + /* Fill HT capability information */ + if (ISSUPP_CHANWIDTH40(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; + else + ht_info->cap &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40; + + if (ISSUPP_SHORTGI20(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_SGI_20; + else + ht_info->cap &= ~IEEE80211_HT_CAP_SGI_20; + + if (ISSUPP_SHORTGI40(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_SGI_40; + else + ht_info->cap &= ~IEEE80211_HT_CAP_SGI_40; + + if (adapter->user_dev_mcs_support == HT_STREAM_2X2) + ht_info->cap |= 2 << IEEE80211_HT_CAP_RX_STBC_SHIFT; + else + ht_info->cap |= 1 << IEEE80211_HT_CAP_RX_STBC_SHIFT; + + if (ISSUPP_TXSTBC(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_TX_STBC; + else + ht_info->cap &= ~IEEE80211_HT_CAP_TX_STBC; + + if (ISSUPP_GREENFIELD(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_GRN_FLD; + else + ht_info->cap &= ~IEEE80211_HT_CAP_GRN_FLD; + + if (ISENABLED_40MHZ_INTOLERANT(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_40MHZ_INTOLERANT; + else + ht_info->cap &= ~IEEE80211_HT_CAP_40MHZ_INTOLERANT; + + if (ISSUPP_RXLDPC(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_LDPC_CODING; + else + ht_info->cap &= ~IEEE80211_HT_CAP_LDPC_CODING; + + ht_info->cap &= ~IEEE80211_HT_CAP_MAX_AMSDU; + ht_info->cap |= IEEE80211_HT_CAP_SM_PS; + + rx_mcs_supp = GET_RXMCSSUPP(adapter->user_dev_mcs_support); + /* Set MCS for 1x1/2x2 */ + memset(mcs, 0xff, rx_mcs_supp); + /* Clear all the other values */ + memset(&mcs[rx_mcs_supp], 0, + sizeof(struct ieee80211_mcs_info) - rx_mcs_supp); + if (priv->bss_mode == NL80211_IFTYPE_STATION || + ISSUPP_CHANWIDTH40(adapter->hw_dot_11n_dev_cap)) + /* Set MCS32 for infra mode or ad-hoc mode with 40MHz support */ + SETHT_MCS32(mcs_set.rx_mask); + + memcpy((u8 *)&ht_info->mcs, mcs, sizeof(struct ieee80211_mcs_info)); + + ht_info->mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; +} + +static void +nxpwifi_setup_vht_caps(struct nxpwifi_private *priv, + struct ieee80211_sta_vht_cap *vht_info) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + vht_info->vht_supported = true; + + vht_info->cap = adapter->hw_dot_11ac_dev_cap; + /* Update MCS support for VHT */ + vht_info->vht_mcs.rx_mcs_map = + cpu_to_le16(adapter->hw_dot_11ac_mcs_support & 0xFFFF); + vht_info->vht_mcs.rx_highest = 0; + vht_info->vht_mcs.tx_mcs_map = + cpu_to_le16(adapter->hw_dot_11ac_mcs_support >> 16); + vht_info->vht_mcs.tx_highest = 0; +} + +/* + * 5 GHz HE capability masks for UAP mode. + * + * MAC: TWT requester/respondor, broadcast TWT, OMI control. + * + * PHY: 40/80 MHz width, puncturing, LDPC, NDP 4xLTF, STBC, + * Doppler, DCM, SU BF/BFe, STS, sounding dims, extended + * range, PPE present, 4xLTF 0.8us GI, Rx 1024-QAM. + */ +#define UAP_HE_MAC_CAP0_MASK (IEEE80211_HE_MAC_CAP0_TWT_REQ | \ + IEEE80211_HE_MAC_CAP0_TWT_RES) + +#define UAP_HE_MAC_CAP1_MASK 0 +#define UAP_HE_MAC_CAP2_MASK IEEE80211_HE_MAC_CAP2_BCAST_TWT +#define UAP_HE_MAC_CAP3_MASK IEEE80211_HE_MAC_CAP3_OMI_CONTROL +#define UAP_HE_MAC_CAP4_MASK 0 +#define UAP_HE_MAC_CAP5_MASK 0 + +#define UAP_HE_PHY_CAP0_MASK IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G +#define UAP_HE_PHY_CAP1_MASK (IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD | \ + IEEE80211_HE_PHY_CAP1_PREAMBLE_PUNC_RX_80MHZ_ONLY_SECOND_20MHZ | \ + IEEE80211_HE_PHY_CAP1_PREAMBLE_PUNC_RX_80MHZ_ONLY_SECOND_40MHZ) +#define UAP_HE_PHY_CAP2_MASK (IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US | \ + IEEE80211_HE_PHY_CAP2_STBC_TX_UNDER_80MHZ | \ + IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ | \ + IEEE80211_HE_PHY_CAP2_DOPPLER_TX | \ + IEEE80211_HE_PHY_CAP2_DOPPLER_RX) +#define UAP_HE_PHY_CAP3_MASK (IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_TX_BPSK | \ + IEEE80211_HE_PHY_CAP3_DCM_MAX_TX_NSS_1 | \ + IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_BPSK | \ + IEEE80211_HE_PHY_CAP3_DCM_MAX_RX_NSS_1 | \ + IEEE80211_HE_PHY_CAP3_SU_BEAMFORMER) +#define UAP_HE_PHY_CAP4_MASK (IEEE80211_HE_PHY_CAP4_SU_BEAMFORMEE | \ + IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_8) +#define UAP_HE_PHY_CAP5_MASK IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_UNDER_80MHZ_2 +#define UAP_HE_PHY_CAP6_MASK (IEEE80211_HE_PHY_CAP6_PARTIAL_BW_EXT_RANGE | \ + IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT) +#define UAP_HE_PHY_CAP7_MASK (IEEE80211_HE_PHY_CAP7_HE_SU_MU_PPDU_4XLTF_AND_08_US_GI | \ + IEEE80211_HE_PHY_CAP7_MAX_NC_1) +#define UAP_HE_PHY_CAP8_MASK 0 +#define UAP_HE_PHY_CAP9_MASK IEEE80211_HE_PHY_CAP9_RX_1024_QAM_LESS_THAN_242_TONE_RU +#define UAP_HE_PHY_CAP10_MASK 0 + +/* + * 2.4 GHz HE capability masks for UAP mode. + * + * MAC: HTC HE, OMI control (no UL OFDMA). + * PHY: 40 MHz, LDPC, NDP 4xLTF, STBC, Doppler, DCM, + * SU BF/BFe, STS/sounding dims, extended range, + * PPE present, 4xLTF 0.8us GI, Rx 1024-QAM. + */ +#define UAP_HE_2G_MAC_CAP0_MASK 0x00 +#define UAP_HE_2G_MAC_CAP1_MASK 0x00 +#define UAP_HE_2G_MAC_CAP2_MASK 0x00 +#define UAP_HE_2G_MAC_CAP3_MASK IEEE80211_HE_MAC_CAP3_OMI_CONTROL +#define UAP_HE_2G_MAC_CAP4_MASK 0x00 +#define UAP_HE_2G_MAC_CAP5_MASK 0x00 + +#define UAP_HE_2G_PHY_CAP0_MASK IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G +#define UAP_HE_2G_PHY_CAP1_MASK IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD +#define UAP_HE_2G_PHY_CAP2_MASK (IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US | \ + IEEE80211_HE_PHY_CAP2_STBC_TX_UNDER_80MHZ | \ + IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ | \ + IEEE80211_HE_PHY_CAP2_DOPPLER_TX | \ + IEEE80211_HE_PHY_CAP2_DOPPLER_RX) +#define UAP_HE_2G_PHY_CAP3_MASK (IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_TX_BPSK | \ + IEEE80211_HE_PHY_CAP3_DCM_MAX_TX_NSS_1 | \ + IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_BPSK | \ + IEEE80211_HE_PHY_CAP3_DCM_MAX_RX_NSS_1 | \ + IEEE80211_HE_PHY_CAP3_SU_BEAMFORMER) +#define UAP_HE_2G_PHY_CAP4_MASK (IEEE80211_HE_PHY_CAP4_SU_BEAMFORMEE | \ + IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_8) +#define UAP_HE_2G_PHY_CAP5_MASK IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_UNDER_80MHZ_2 +#define UAP_HE_2G_PHY_CAP6_MASK (IEEE80211_HE_PHY_CAP6_PARTIAL_BW_EXT_RANGE | \ + IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT) +#define UAP_HE_2G_PHY_CAP7_MASK (IEEE80211_HE_PHY_CAP7_HE_SU_MU_PPDU_4XLTF_AND_08_US_GI | \ + IEEE80211_HE_PHY_CAP7_MAX_NC_1) +#define UAP_HE_2G_PHY_CAP8_MASK 0x00 +#define UAP_HE_2G_PHY_CAP9_MASK IEEE80211_HE_PHY_CAP9_RX_1024_QAM_LESS_THAN_242_TONE_RU +#define UAP_HE_2G_PHY_CAP10_MASK 0x00 +#define HE_CAP_FIX_SIZE 22 + +static void +nxpwifi_update_11ax_ie(u8 band, + struct nxpwifi_11ax_he_cap_cfg *he_cap_cfg) +{ + if (band == BAND_A) { + he_cap_cfg->cap_elem.mac_cap_info[0] &= UAP_HE_MAC_CAP0_MASK; + he_cap_cfg->cap_elem.mac_cap_info[1] &= UAP_HE_MAC_CAP1_MASK; + he_cap_cfg->cap_elem.mac_cap_info[2] &= UAP_HE_MAC_CAP2_MASK; + he_cap_cfg->cap_elem.mac_cap_info[3] &= UAP_HE_MAC_CAP3_MASK; + he_cap_cfg->cap_elem.mac_cap_info[4] &= UAP_HE_MAC_CAP4_MASK; + he_cap_cfg->cap_elem.mac_cap_info[5] &= UAP_HE_MAC_CAP5_MASK; + he_cap_cfg->cap_elem.phy_cap_info[0] &= UAP_HE_PHY_CAP0_MASK; + he_cap_cfg->cap_elem.phy_cap_info[1] &= UAP_HE_PHY_CAP1_MASK; + he_cap_cfg->cap_elem.phy_cap_info[2] &= UAP_HE_PHY_CAP2_MASK; + he_cap_cfg->cap_elem.phy_cap_info[3] &= UAP_HE_PHY_CAP3_MASK; + he_cap_cfg->cap_elem.phy_cap_info[4] &= UAP_HE_PHY_CAP4_MASK; + he_cap_cfg->cap_elem.phy_cap_info[5] &= UAP_HE_PHY_CAP5_MASK; + he_cap_cfg->cap_elem.phy_cap_info[6] &= UAP_HE_PHY_CAP6_MASK; + he_cap_cfg->cap_elem.phy_cap_info[7] &= UAP_HE_PHY_CAP7_MASK; + he_cap_cfg->cap_elem.phy_cap_info[8] &= UAP_HE_PHY_CAP8_MASK; + he_cap_cfg->cap_elem.phy_cap_info[9] &= UAP_HE_PHY_CAP9_MASK; + he_cap_cfg->cap_elem.phy_cap_info[10] &= UAP_HE_PHY_CAP10_MASK; + } else { + he_cap_cfg->cap_elem.mac_cap_info[0] &= UAP_HE_2G_MAC_CAP0_MASK; + he_cap_cfg->cap_elem.mac_cap_info[1] &= UAP_HE_2G_MAC_CAP1_MASK; + he_cap_cfg->cap_elem.mac_cap_info[2] &= UAP_HE_2G_MAC_CAP2_MASK; + he_cap_cfg->cap_elem.mac_cap_info[3] &= UAP_HE_2G_MAC_CAP3_MASK; + he_cap_cfg->cap_elem.mac_cap_info[4] &= UAP_HE_2G_MAC_CAP4_MASK; + he_cap_cfg->cap_elem.mac_cap_info[5] &= UAP_HE_2G_MAC_CAP5_MASK; + he_cap_cfg->cap_elem.phy_cap_info[0] &= UAP_HE_2G_PHY_CAP0_MASK; + he_cap_cfg->cap_elem.phy_cap_info[1] &= UAP_HE_2G_PHY_CAP1_MASK; + he_cap_cfg->cap_elem.phy_cap_info[2] &= UAP_HE_2G_PHY_CAP2_MASK; + he_cap_cfg->cap_elem.phy_cap_info[3] &= UAP_HE_2G_PHY_CAP3_MASK; + he_cap_cfg->cap_elem.phy_cap_info[4] &= UAP_HE_2G_PHY_CAP4_MASK; + he_cap_cfg->cap_elem.phy_cap_info[5] &= UAP_HE_2G_PHY_CAP5_MASK; + he_cap_cfg->cap_elem.phy_cap_info[6] &= UAP_HE_2G_PHY_CAP6_MASK; + he_cap_cfg->cap_elem.phy_cap_info[7] &= UAP_HE_2G_PHY_CAP7_MASK; + he_cap_cfg->cap_elem.phy_cap_info[8] &= UAP_HE_2G_PHY_CAP8_MASK; + he_cap_cfg->cap_elem.phy_cap_info[9] &= UAP_HE_2G_PHY_CAP9_MASK; + he_cap_cfg->cap_elem.phy_cap_info[10] &= UAP_HE_2G_PHY_CAP10_MASK; + } +} + +static void +nxpwifi_setup_he_caps(struct nxpwifi_private *priv, + struct ieee80211_supported_band *band) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct ieee80211_sband_iftype_data *iftype_data; + struct nxpwifi_11ax_he_cap_cfg he_cap_cfg; + u8 hw_he_cap_len; + u8 extra_mcs_size; + int ppe_threshold_len; + + if (band->band == NL80211_BAND_5GHZ) { + hw_he_cap_len = adapter->hw_he_cap_len; + memcpy(&he_cap_cfg, adapter->hw_he_cap, hw_he_cap_len); + nxpwifi_update_11ax_ie(BAND_A, &he_cap_cfg); + } else { + hw_he_cap_len = adapter->hw_2g_he_cap_len; + memcpy(&he_cap_cfg, adapter->hw_2g_he_cap, hw_he_cap_len); + nxpwifi_update_11ax_ie(BAND_G, &he_cap_cfg); + } + + if (!hw_he_cap_len) + return; + + iftype_data = kmalloc_obj(*iftype_data, GFP_KERNEL); + if (!iftype_data) + return; + memset(iftype_data, 0, sizeof(*iftype_data)); + + iftype_data->types_mask = + BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP); + iftype_data->he_cap.has_he = true; + + memcpy(iftype_data->he_cap.he_cap_elem.mac_cap_info, + he_cap_cfg.cap_elem.mac_cap_info, + sizeof(he_cap_cfg.cap_elem.mac_cap_info)); + memcpy(iftype_data->he_cap.he_cap_elem.phy_cap_info, + he_cap_cfg.cap_elem.phy_cap_info, + sizeof(he_cap_cfg.cap_elem.phy_cap_info)); + memset(&iftype_data->he_cap.he_mcs_nss_supp, + 0xff, + sizeof(iftype_data->he_cap.he_mcs_nss_supp)); + memcpy(&iftype_data->he_cap.he_mcs_nss_supp, + he_cap_cfg.he_txrx_mcs_support, + sizeof(he_cap_cfg.he_txrx_mcs_support)); + + extra_mcs_size = 0; + /* Add 160 MHz MCS/NSS if supported */ + if (he_cap_cfg.cap_elem.phy_cap_info[0] & BIT(3)) + extra_mcs_size += 4; + /* Add 80+80 MHz MCS/NSS if supported */ + if (he_cap_cfg.cap_elem.phy_cap_info[0] & BIT(4)) + extra_mcs_size += 4; + if (extra_mcs_size) + memcpy((u8 *)&iftype_data->he_cap.he_mcs_nss_supp.rx_mcs_160, + he_cap_cfg.val, extra_mcs_size); + + /* Parse PPE thresholds when present */ + ppe_threshold_len = he_cap_cfg.len - HE_CAP_FIX_SIZE - extra_mcs_size; + if (he_cap_cfg.cap_elem.phy_cap_info[6] & BIT(7) && ppe_threshold_len) { + memcpy(iftype_data->he_cap.ppe_thres, + &he_cap_cfg.val[extra_mcs_size], + ppe_threshold_len); + } else { + iftype_data->he_cap.he_cap_elem.phy_cap_info[6] &= BIT(7); + } + + _ieee80211_set_sband_iftype_data(band, iftype_data, 1); +} + +/* create a new virtual interface with the given name and name assign type */ +struct wireless_dev *nxpwifi_add_virtual_intf(struct wiphy *wiphy, + const char *name, + unsigned char name_assign_type, + enum nl80211_iftype type, + struct vif_params *params) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv; + struct net_device *dev; + void *mdev_priv; + int ret; + + if (!adapter) + return ERR_PTR(-EFAULT); + + switch (type) { + case NL80211_IFTYPE_UNSPECIFIED: + case NL80211_IFTYPE_STATION: + if (adapter->curr_iface_comb.sta_intf == + adapter->iface_limit.sta_intf) { + nxpwifi_dbg(adapter, ERROR, + "cannot create multiple sta ifaces\n"); + return ERR_PTR(-EINVAL); + } + + priv = nxpwifi_get_unused_priv_by_bss_type + (adapter, NXPWIFI_BSS_TYPE_STA); + if (!priv) { + nxpwifi_dbg(adapter, ERROR, + "could not get free private struct\n"); + return ERR_PTR(-EFAULT); + } + + priv->wdev.wiphy = wiphy; + priv->wdev.iftype = NL80211_IFTYPE_STATION; + + if (type == NL80211_IFTYPE_UNSPECIFIED) + priv->bss_mode = NL80211_IFTYPE_STATION; + else + priv->bss_mode = type; + + priv->bss_type = NXPWIFI_BSS_TYPE_STA; + priv->frame_type = NXPWIFI_DATA_FRAME_TYPE_ETH_II; + priv->bss_priority = 0; + priv->bss_role = NXPWIFI_BSS_ROLE_STA; + + break; + case NL80211_IFTYPE_AP: + if (adapter->curr_iface_comb.uap_intf == + adapter->iface_limit.uap_intf) { + nxpwifi_dbg(adapter, ERROR, + "cannot create multiple AP ifaces\n"); + return ERR_PTR(-EINVAL); + } + + priv = nxpwifi_get_unused_priv_by_bss_type + (adapter, NXPWIFI_BSS_TYPE_UAP); + if (!priv) { + nxpwifi_dbg(adapter, ERROR, + "could not get free private struct\n"); + return ERR_PTR(-EFAULT); + } + + priv->wdev.wiphy = wiphy; + priv->wdev.iftype = NL80211_IFTYPE_AP; + + priv->bss_type = NXPWIFI_BSS_TYPE_UAP; + priv->frame_type = NXPWIFI_DATA_FRAME_TYPE_ETH_II; + priv->bss_priority = 0; + priv->bss_role = NXPWIFI_BSS_ROLE_UAP; + priv->bss_started = 0; + priv->bss_mode = type; + + break; + case NL80211_IFTYPE_MONITOR: + priv = nxpwifi_get_unused_priv_by_bss_type + (adapter, NXPWIFI_BSS_TYPE_UAP); + if (!priv) { + nxpwifi_dbg(adapter, ERROR, + "could not get free private struct\n"); + return ERR_PTR(-EFAULT); + } + priv->wdev.wiphy = wiphy; + priv->wdev.iftype = NL80211_IFTYPE_MONITOR; + + priv->bss_type = NXPWIFI_BSS_TYPE_UAP; + priv->frame_type = NXPWIFI_DATA_FRAME_TYPE_ETH_II; + priv->bss_priority = 0; + priv->bss_started = 0; + priv->bss_mode = type; + + break; + default: + nxpwifi_dbg(adapter, ERROR, "type not supported\n"); + return ERR_PTR(-EINVAL); + } + + dev = alloc_netdev_mqs(sizeof(struct nxpwifi_private *), name, + name_assign_type, ether_setup, + IEEE80211_NUM_ACS, 1); + if (!dev) { + nxpwifi_dbg(adapter, ERROR, + "no memory available for netdevice\n"); + ret = -ENOMEM; + goto err_alloc_netdev; + } + + nxpwifi_init_priv_params(priv, dev); + + priv->netdev = dev; + + nxpwifi_set_mac_address(priv, dev, false, NULL); + + if (type != NL80211_IFTYPE_MONITOR) { + ret = nxpwifi_set_bss_mode(priv); + + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "%s: err_set_bss_mode\n", __func__); + goto err_set_bss_mode; + } + } + + ret = nxpwifi_sta_init_cmd(priv, false, false); + if (ret) + goto err_sta_init; + + dev_net_set(dev, wiphy_net(wiphy)); + dev->ieee80211_ptr = &priv->wdev; + dev->ieee80211_ptr->iftype = priv->bss_mode; + SET_NETDEV_DEV(dev, wiphy_dev(wiphy)); + + dev->flags |= IFF_BROADCAST | IFF_MULTICAST; + dev->watchdog_timeo = NXPWIFI_DEFAULT_WATCHDOG_TIMEOUT; + dev->needed_headroom = NXPWIFI_MIN_DATA_HEADER_LEN; + dev->ethtool_ops = &nxpwifi_ethtool_ops; + + mdev_priv = netdev_priv(dev); + *((unsigned long *)mdev_priv) = (unsigned long)priv; + + if (type == NL80211_IFTYPE_MONITOR) + dev->type = ARPHRD_IEEE80211_RADIOTAP; + + SET_NETDEV_DEV(dev, adapter->dev); + + wiphy_work_init(&priv->reset_conn_state_work, nxpwifi_reset_conn_state_work); + + wiphy_delayed_work_init(&priv->dfs_cac_work, nxpwifi_dfs_cac_work); + + wiphy_delayed_work_init(&priv->dfs_chan_sw_work, nxpwifi_dfs_chan_sw_work); + + /* Register network device */ + if (cfg80211_register_netdevice(dev)) { + nxpwifi_dbg(adapter, ERROR, "cannot register network device\n"); + ret = -EFAULT; + goto err_reg_netdev; + } + + nxpwifi_dbg(adapter, INFO, + "info: %s: NXP 802.11 Adapter\n", dev->name); + +#ifdef CONFIG_DEBUG_FS + nxpwifi_dev_debugfs_init(priv); +#endif + + update_vif_type_counter(adapter, type, 1); + + return &priv->wdev; + +err_reg_netdev: + free_netdev(dev); + priv->netdev = NULL; +err_sta_init: +err_set_bss_mode: +err_alloc_netdev: + memset(&priv->wdev, 0, sizeof(priv->wdev)); + priv->wdev.iftype = NL80211_IFTYPE_UNSPECIFIED; + priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(nxpwifi_add_virtual_intf); + +/* del_virtual_intf: remove the virtual interface determined by dev */ +int nxpwifi_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_adapter *adapter = priv->adapter; + struct sk_buff *skb, *tmp; + +#ifdef CONFIG_DEBUG_FS + nxpwifi_dev_debugfs_remove(priv); +#endif + if (priv->bss_mode == NL80211_IFTYPE_MONITOR) { + struct nxpwifi_802_11_net_monitor netmon_cfg; + + memset(&netmon_cfg, 0, sizeof(struct nxpwifi_802_11_net_monitor)); + nxpwifi_config_monitor_mode(priv, &netmon_cfg); + } + + if (priv->sched_scanning) + priv->sched_scanning = false; + + nxpwifi_stop_net_dev_queue(priv->netdev, adapter); + + skb_queue_walk_safe(&priv->bypass_txq, skb, tmp) { + skb_unlink(skb, &priv->bypass_txq); + nxpwifi_write_data_complete(priv->adapter, skb, 0, -1); + } + + netif_carrier_off(priv->netdev); + + if (wdev->netdev->reg_state == NETREG_REGISTERED) + cfg80211_unregister_netdevice(wdev->netdev); + + /* Clear the priv in adapter */ + priv->netdev = NULL; + + update_vif_type_counter(adapter, priv->bss_mode, -1); + + priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA || + GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) + kfree(priv->hist_data); + + return 0; +} +EXPORT_SYMBOL_GPL(nxpwifi_del_virtual_intf); + +static bool +nxpwifi_is_pattern_supported(struct cfg80211_pkt_pattern *pat, s8 *byte_seq, + u8 max_byte_seq) +{ + int j, k, valid_byte_cnt = 0; + bool dont_care_byte = false; + + for (j = 0; j < DIV_ROUND_UP(pat->pattern_len, 8); j++) { + for (k = 0; k < 8; k++) { + if (pat->mask[j] & 1 << k) { + memcpy(byte_seq + valid_byte_cnt, + &pat->pattern[j * 8 + k], 1); + valid_byte_cnt++; + if (dont_care_byte) + return false; + } else { + if (valid_byte_cnt) + dont_care_byte = true; + } + + /* wildcard bytes record as the offset before the valid byte */ + if (!valid_byte_cnt && !dont_care_byte) + pat->pkt_offset++; + + if (valid_byte_cnt > max_byte_seq) + return false; + } + } + + byte_seq[max_byte_seq] = valid_byte_cnt; + + return true; +} + +#ifdef CONFIG_PM +static void nxpwifi_set_auto_arp_mef_entry(struct nxpwifi_private *priv, + struct nxpwifi_mef_entry *mef_entry) +{ + int i, filt_num = 0, num_ipv4 = 0; + struct in_device *in_dev; + struct in_ifaddr *ifa; + __be32 ips[NXPWIFI_MAX_SUPPORTED_IPADDR]; + struct nxpwifi_adapter *adapter = priv->adapter; + + mef_entry->mode = MEF_MODE_HOST_SLEEP; + mef_entry->action = MEF_ACTION_AUTO_ARP; + + /* Enable ARP offload feature */ + memset(ips, 0, sizeof(ips)); + for (i = 0; i < adapter->priv_num; i++) { + if (adapter->priv[i]->netdev) { + in_dev = __in_dev_get_rtnl(adapter->priv[i]->netdev); + if (!in_dev) + continue; + ifa = rtnl_dereference(in_dev->ifa_list); + if (!ifa || !ifa->ifa_local) + continue; + ips[i] = ifa->ifa_local; + num_ipv4++; + } + } + + for (i = 0; i < num_ipv4; i++) { + if (!ips[i]) + continue; + mef_entry->filter[filt_num].repeat = 1; + memcpy(mef_entry->filter[filt_num].byte_seq, + (u8 *)&ips[i], sizeof(ips[i])); + mef_entry->filter[filt_num].byte_seq[NXPWIFI_MEF_MAX_BYTESEQ] = + sizeof(ips[i]); + mef_entry->filter[filt_num].offset = 46; + mef_entry->filter[filt_num].filt_type = TYPE_EQ; + if (filt_num) { + mef_entry->filter[filt_num].filt_action = + TYPE_OR; + } + filt_num++; + } + + mef_entry->filter[filt_num].repeat = 1; + mef_entry->filter[filt_num].byte_seq[0] = 0x08; + mef_entry->filter[filt_num].byte_seq[1] = 0x06; + mef_entry->filter[filt_num].byte_seq[NXPWIFI_MEF_MAX_BYTESEQ] = 2; + mef_entry->filter[filt_num].offset = 20; + mef_entry->filter[filt_num].filt_type = TYPE_EQ; + mef_entry->filter[filt_num].filt_action = TYPE_AND; +} + +static int nxpwifi_set_wowlan_mef_entry(struct nxpwifi_private *priv, + struct nxpwifi_ds_mef_cfg *mef_cfg, + struct nxpwifi_mef_entry *mef_entry, + struct cfg80211_wowlan *wowlan) +{ + int i, filt_num = 0, ret = 0; + bool first_pat = true; + u8 byte_seq[NXPWIFI_MEF_MAX_BYTESEQ + 1]; + + mef_entry->mode = MEF_MODE_HOST_SLEEP; + mef_entry->action = MEF_ACTION_ALLOW_AND_WAKEUP_HOST; + + for (i = 0; i < wowlan->n_patterns; i++) { + memset(byte_seq, 0, sizeof(byte_seq)); + if (!nxpwifi_is_pattern_supported + (&wowlan->patterns[i], byte_seq, + NXPWIFI_MEF_MAX_BYTESEQ)) { + nxpwifi_dbg(priv->adapter, ERROR, + "Pattern not supported\n"); + return -EOPNOTSUPP; + } + + if (!wowlan->patterns[i].pkt_offset) { + if (is_unicast_ether_addr(byte_seq) && + byte_seq[NXPWIFI_MEF_MAX_BYTESEQ] == 1) { + mef_cfg->criteria |= NXPWIFI_CRITERIA_UNICAST; + continue; + } else if (is_broadcast_ether_addr(byte_seq)) { + mef_cfg->criteria |= NXPWIFI_CRITERIA_BROADCAST; + continue; + } else if ((!memcmp(byte_seq, "\x33\x33", 2) && + (byte_seq[NXPWIFI_MEF_MAX_BYTESEQ] == 2)) || + (!memcmp(byte_seq, "\x01\x00\x5e", 3) && + (byte_seq[NXPWIFI_MEF_MAX_BYTESEQ] == 3))) { + mef_cfg->criteria |= NXPWIFI_CRITERIA_MULTICAST; + continue; + } + } + mef_entry->filter[filt_num].repeat = 1; + mef_entry->filter[filt_num].offset = + wowlan->patterns[i].pkt_offset; + memcpy(mef_entry->filter[filt_num].byte_seq, byte_seq, + sizeof(byte_seq)); + mef_entry->filter[filt_num].filt_type = TYPE_EQ; + + if (first_pat) { + first_pat = false; + nxpwifi_dbg(priv->adapter, INFO, "Wake on patterns\n"); + } else { + mef_entry->filter[filt_num].filt_action = TYPE_AND; + } + + filt_num++; + } + + if (wowlan->magic_pkt) { + mef_cfg->criteria |= NXPWIFI_CRITERIA_UNICAST; + mef_entry->filter[filt_num].repeat = 16; + memcpy(mef_entry->filter[filt_num].byte_seq, priv->curr_addr, + ETH_ALEN); + mef_entry->filter[filt_num].byte_seq[NXPWIFI_MEF_MAX_BYTESEQ] = + ETH_ALEN; + mef_entry->filter[filt_num].offset = 28; + mef_entry->filter[filt_num].filt_type = TYPE_EQ; + if (filt_num) + mef_entry->filter[filt_num].filt_action = TYPE_OR; + + filt_num++; + mef_entry->filter[filt_num].repeat = 16; + memcpy(mef_entry->filter[filt_num].byte_seq, priv->curr_addr, + ETH_ALEN); + mef_entry->filter[filt_num].byte_seq[NXPWIFI_MEF_MAX_BYTESEQ] = + ETH_ALEN; + mef_entry->filter[filt_num].offset = 56; + mef_entry->filter[filt_num].filt_type = TYPE_EQ; + mef_entry->filter[filt_num].filt_action = TYPE_OR; + nxpwifi_dbg(priv->adapter, INFO, "Wake on magic packet\n"); + } + return ret; +} + +static int nxpwifi_set_mef_filter(struct nxpwifi_private *priv, + struct cfg80211_wowlan *wowlan) +{ + int ret = 0, num_entries = 1; + struct nxpwifi_ds_mef_cfg mef_cfg; + struct nxpwifi_mef_entry *mef_entry; + + if (wowlan->n_patterns || wowlan->magic_pkt) + num_entries++; + + mef_entry = kzalloc_objs(*mef_entry, num_entries, GFP_KERNEL); + if (!mef_entry) + return -ENOMEM; + + memset(&mef_cfg, 0, sizeof(mef_cfg)); + mef_cfg.criteria |= NXPWIFI_CRITERIA_BROADCAST | + NXPWIFI_CRITERIA_UNICAST; + mef_cfg.num_entries = num_entries; + mef_cfg.mef_entry = mef_entry; + + nxpwifi_set_auto_arp_mef_entry(priv, &mef_entry[0]); + + if (wowlan->n_patterns || wowlan->magic_pkt) { + ret = nxpwifi_set_wowlan_mef_entry(priv, &mef_cfg, + &mef_entry[1], wowlan); + if (ret) + goto done; + } + + if (!mef_cfg.criteria) + mef_cfg.criteria = NXPWIFI_CRITERIA_BROADCAST | + NXPWIFI_CRITERIA_UNICAST | + NXPWIFI_CRITERIA_MULTICAST; + + ret = nxpwifi_mef_cfg(priv, &mef_cfg); + +done: + kfree(mef_entry); + return ret; +} + +static int nxpwifi_cfg80211_suspend(struct wiphy *wiphy, + struct cfg80211_wowlan *wowlan) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_ds_hs_cfg hs_cfg; + int i, ret = 0, retry_num = 10; + struct nxpwifi_private *priv; + struct nxpwifi_private *sta_priv = + nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA); + + adapter->wowlan_enabled = false; + + sta_priv->scan_aborting = true; + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + nxpwifi_abort_cac(priv); + } + + nxpwifi_cancel_all_pending_cmd(adapter); + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (priv->netdev) + netif_device_detach(priv->netdev); + } + + for (i = 0; i < retry_num; i++) { + if (!nxpwifi_wmm_lists_empty(adapter) || + !nxpwifi_bypass_txlist_empty(adapter) || + !skb_queue_empty(&adapter->tx_data_q)) + usleep_range(10000, 15000); + else + break; + } + + if (!wowlan) { + nxpwifi_dbg(adapter, INFO, + "None of the WOWLAN triggers enabled\n"); + ret = 0; + goto done; + } + + if (!sta_priv->media_connected && !wowlan->nd_config) { + nxpwifi_dbg(adapter, ERROR, + "Can not configure WOWLAN in disconnected state\n"); + ret = 0; + goto done; + } + + ret = nxpwifi_set_mef_filter(sta_priv, wowlan); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "Failed to set MEF filter\n"); + goto done; + } + + memset(&hs_cfg, 0, sizeof(hs_cfg)); + hs_cfg.conditions = le32_to_cpu(adapter->hs_cfg.conditions); + + if (wowlan->nd_config) { + nxpwifi_dbg(adapter, INFO, "Wake on net detect\n"); + hs_cfg.conditions |= HS_CFG_COND_MAC_EVENT; + nxpwifi_cfg80211_sched_scan_start(wiphy, sta_priv->netdev, + wowlan->nd_config); + } + + if (wowlan->disconnect) { + hs_cfg.conditions |= HS_CFG_COND_MAC_EVENT; + nxpwifi_dbg(sta_priv->adapter, INFO, "Wake on device disconnect\n"); + } + + hs_cfg.is_invoke_hostcmd = false; + hs_cfg.gpio = adapter->hs_cfg.gpio; + hs_cfg.gap = adapter->hs_cfg.gap; + ret = nxpwifi_set_hs_params(sta_priv, HOST_ACT_GEN_SET, + NXPWIFI_SYNC_CMD, &hs_cfg); + if (ret) + nxpwifi_dbg(adapter, ERROR, "Failed to set HS params\n"); + + adapter->wowlan_enabled = true; + +done: + sta_priv->scan_aborting = false; + return ret; +} + +static int nxpwifi_cfg80211_resume(struct wiphy *wiphy) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv; + struct nxpwifi_ds_wakeup_reason wakeup_reason; + struct cfg80211_wowlan_wakeup wakeup_report; + int i; + bool report_wakeup_reason = true; + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (priv->netdev) + netif_device_attach(priv->netdev); + } + + if (!wiphy->wowlan_config) + goto done; + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA); + nxpwifi_get_wakeup_reason(priv, HOST_ACT_GEN_GET, NXPWIFI_SYNC_CMD, + &wakeup_reason); + memset(&wakeup_report, 0, sizeof(struct cfg80211_wowlan_wakeup)); + + wakeup_report.pattern_idx = -1; + + switch (wakeup_reason.hs_wakeup_reason) { + case NO_HSWAKEUP_REASON: + break; + case BCAST_DATA_MATCHED: + break; + case MCAST_DATA_MATCHED: + break; + case UCAST_DATA_MATCHED: + break; + case MASKTABLE_EVENT_MATCHED: + break; + case NON_MASKABLE_EVENT_MATCHED: + if (wiphy->wowlan_config->disconnect) + wakeup_report.disconnect = true; + if (wiphy->wowlan_config->nd_config) + wakeup_report.net_detect = adapter->nd_info; + break; + case NON_MASKABLE_CONDITION_MATCHED: + break; + case MAGIC_PATTERN_MATCHED: + if (wiphy->wowlan_config->magic_pkt) + wakeup_report.magic_pkt = true; + if (wiphy->wowlan_config->n_patterns) + wakeup_report.pattern_idx = 1; + break; + case GTK_REKEY_FAILURE: + if (wiphy->wowlan_config->gtk_rekey_failure) + wakeup_report.gtk_rekey_failure = true; + break; + default: + report_wakeup_reason = false; + break; + } + + if (report_wakeup_reason) + cfg80211_report_wowlan_wakeup(&priv->wdev, &wakeup_report, + GFP_KERNEL); + +done: + if (adapter->nd_info) { + for (i = 0 ; i < adapter->nd_info->n_matches ; i++) + kfree(adapter->nd_info->matches[i]); + kfree(adapter->nd_info); + adapter->nd_info = NULL; + } + + return 0; +} + +static void nxpwifi_cfg80211_set_wakeup(struct wiphy *wiphy, + bool enabled) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + + device_set_wakeup_enable(adapter->dev, enabled); +} +#endif + +static int nxpwifi_get_coalesce_pkt_type(u8 *byte_seq) +{ + if ((byte_seq[0] & 0x01) && + byte_seq[NXPWIFI_COALESCE_MAX_BYTESEQ] == 1) + return PACKET_TYPE_UNICAST; + else if (is_broadcast_ether_addr(byte_seq)) + return PACKET_TYPE_BROADCAST; + else if ((!memcmp(byte_seq, "\x33\x33", 2) && + byte_seq[NXPWIFI_COALESCE_MAX_BYTESEQ] == 2) || + (!memcmp(byte_seq, "\x01\x00\x5e", 3) && + byte_seq[NXPWIFI_COALESCE_MAX_BYTESEQ] == 3)) + return PACKET_TYPE_MULTICAST; + + return 0; +} + +static int +nxpwifi_fill_coalesce_rule_info(struct nxpwifi_private *priv, + struct cfg80211_coalesce_rules *crule, + struct nxpwifi_coalesce_rule *mrule) +{ + u8 byte_seq[NXPWIFI_COALESCE_MAX_BYTESEQ + 1]; + struct filt_field_param *param; + int i; + + mrule->max_coalescing_delay = crule->delay; + + param = mrule->params; + + for (i = 0; i < crule->n_patterns; i++) { + memset(byte_seq, 0, sizeof(byte_seq)); + if (!nxpwifi_is_pattern_supported(&crule->patterns[i], + byte_seq, + NXPWIFI_COALESCE_MAX_BYTESEQ)) { + nxpwifi_dbg(priv->adapter, ERROR, + "Pattern not supported\n"); + return -EOPNOTSUPP; + } + + if (!crule->patterns[i].pkt_offset) { + u8 pkt_type; + + pkt_type = nxpwifi_get_coalesce_pkt_type(byte_seq); + if (pkt_type && mrule->pkt_type) { + nxpwifi_dbg(priv->adapter, ERROR, + "Multiple packet types not allowed\n"); + return -EOPNOTSUPP; + } else if (pkt_type) { + mrule->pkt_type = pkt_type; + continue; + } + } + + if (crule->condition == NL80211_COALESCE_CONDITION_MATCH) + param->operation = RECV_FILTER_MATCH_TYPE_EQ; + else + param->operation = RECV_FILTER_MATCH_TYPE_NE; + + param->operand_len = byte_seq[NXPWIFI_COALESCE_MAX_BYTESEQ]; + memcpy(param->operand_byte_stream, byte_seq, + param->operand_len); + param->offset = crule->patterns[i].pkt_offset; + param++; + + mrule->num_of_fields++; + } + + if (!mrule->pkt_type) { + nxpwifi_dbg(priv->adapter, ERROR, + "Packet type can not be determined\n"); + return -EOPNOTSUPP; + } + + return 0; +} + +static int nxpwifi_cfg80211_set_coalesce(struct wiphy *wiphy, + struct cfg80211_coalesce *coalesce) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + int i, ret; + struct nxpwifi_ds_coalesce_cfg coalesce_cfg; + struct nxpwifi_private *priv = + nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA); + + memset(&coalesce_cfg, 0, sizeof(coalesce_cfg)); + + if (!coalesce) + return nxpwifi_coalesce_cfg(priv, &coalesce_cfg); + + coalesce_cfg.num_of_rules = coalesce->n_rules; + for (i = 0; i < coalesce->n_rules; i++) { + ret = nxpwifi_fill_coalesce_rule_info(priv, &coalesce->rules[i], + &coalesce_cfg.rule[i]); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "Recheck the patterns provided for rule %d\n", + i + 1); + return ret; + } + } + + return nxpwifi_coalesce_cfg(priv, &coalesce_cfg); +} + +static int +nxpwifi_cfg80211_uap_add_station(struct nxpwifi_private *priv, const u8 *mac, + struct station_parameters *params) +{ + struct nxpwifi_sta_info add_sta; + int ret; + + memcpy(add_sta.peer_mac, mac, ETH_ALEN); + add_sta.params = params; + + ret = nxpwifi_add_new_station(priv, &add_sta); + + return ret; +} + +static int +nxpwifi_cfg80211_add_station(struct wiphy *wiphy, struct wireless_dev *wdev, + const u8 *mac, struct station_parameters *params) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + int ret = -EOPNOTSUPP; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) + ret = nxpwifi_cfg80211_uap_add_station(priv, mac, params); + + return ret; +} + +static int +nxpwifi_cfg80211_channel_switch(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_csa_settings *params) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + int chsw_msec; + int ret; + + if (priv->adapter->scan_processing) { + nxpwifi_dbg(priv->adapter, ERROR, + "radar detection: scan in process...\n"); + return -EBUSY; + } + + if (priv->wdev.links[0].cac_started) + return -EBUSY; + + if (cfg80211_chandef_identical(¶ms->chandef, + &priv->dfs_chandef)) + return -EINVAL; + + if (params->block_tx) { + netif_carrier_off(priv->netdev); + nxpwifi_stop_net_dev_queue(priv->netdev, priv->adapter); + priv->uap_stop_tx = true; + } + + ret = nxpwifi_del_mgmt_ies(priv); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to delete mgmt IEs!\n"); + + ret = nxpwifi_set_mgmt_ies(priv, ¶ms->beacon_csa); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "%s: setting mgmt ies failed\n", __func__); + goto done; + } + + memcpy(&priv->dfs_chandef, ¶ms->chandef, sizeof(priv->dfs_chandef)); + memcpy(&priv->ap_update_info.beacon, ¶ms->beacon_after, + sizeof(priv->ap_update_info.beacon)); + + chsw_msec = max(params->count * priv->bss_cfg.beacon_period, 100); + + nxpwifi_queue_delayed_wiphy_work(priv->adapter, + &priv->dfs_chan_sw_work, + msecs_to_jiffies(chsw_msec)); + +done: + return ret; +} + +static int nxpwifi_cfg80211_get_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, + unsigned int link_id, + struct cfg80211_chan_def *chandef) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_bssdescriptor *curr_bss; + struct ieee80211_channel *chan; + enum nl80211_channel_type chan_type; + enum nl80211_band band; + int freq; + int ret = -ENODATA; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP && + cfg80211_chandef_valid(&priv->bss_chandef)) { + *chandef = priv->bss_chandef; + ret = 0; + } else if (priv->media_connected) { + curr_bss = &priv->curr_bss_params.bss_descriptor; + band = nxpwifi_band_to_radio_type(priv->curr_bss_params.band); + freq = ieee80211_channel_to_frequency(curr_bss->channel, band); + chan = ieee80211_get_channel(wiphy, freq); + + if (priv->ht_param_present) { + chan_type = nxpwifi_get_chan_type(priv); + cfg80211_chandef_create(chandef, chan, chan_type); + } else { + cfg80211_chandef_create(chandef, chan, + NL80211_CHAN_NO_HT); + } + ret = 0; + } + + return ret; +} + +#ifdef CONFIG_NL80211_TESTMODE + +enum nxpwifi_tm_attr { + __NXPWIFI_TM_ATTR_INVALID = 0, + NXPWIFI_TM_ATTR_CMD = 1, + NXPWIFI_TM_ATTR_DATA = 2, + + /* keep last */ + __NXPWIFI_TM_ATTR_AFTER_LAST, + NXPWIFI_TM_ATTR_MAX = __NXPWIFI_TM_ATTR_AFTER_LAST - 1, +}; + +static const struct nla_policy nxpwifi_tm_policy[NXPWIFI_TM_ATTR_MAX + 1] = { + [NXPWIFI_TM_ATTR_CMD] = { .type = NLA_U32 }, + [NXPWIFI_TM_ATTR_DATA] = { .type = NLA_BINARY, + .len = NXPWIFI_SIZE_OF_CMD_BUFFER }, +}; + +enum nxpwifi_tm_command { + NXPWIFI_TM_CMD_HOSTCMD = 0, +}; + +static int nxpwifi_tm_cmd(struct wiphy *wiphy, struct wireless_dev *wdev, + void *data, int len) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_ds_misc_cmd *hostcmd; + struct nlattr *tb[NXPWIFI_TM_ATTR_MAX + 1]; + struct sk_buff *skb; + int err; + + if (!priv) + return -EINVAL; + + err = nla_parse_deprecated(tb, NXPWIFI_TM_ATTR_MAX, data, len, + nxpwifi_tm_policy, NULL); + if (err) + return err; + + if (!tb[NXPWIFI_TM_ATTR_CMD]) + return -EINVAL; + + switch (nla_get_u32(tb[NXPWIFI_TM_ATTR_CMD])) { + case NXPWIFI_TM_CMD_HOSTCMD: + if (!tb[NXPWIFI_TM_ATTR_DATA]) + return -EINVAL; + + hostcmd = kzalloc_obj(*hostcmd, GFP_KERNEL); + if (!hostcmd) + return -ENOMEM; + + hostcmd->len = nla_len(tb[NXPWIFI_TM_ATTR_DATA]); + memcpy(hostcmd->cmd, nla_data(tb[NXPWIFI_TM_ATTR_DATA]), + hostcmd->len); + + if (nxpwifi_hostcmd(priv, hostcmd)) { + nxpwifi_dbg(priv->adapter, ERROR, "Failed to process hostcmd\n"); + kfree(hostcmd); + return -EFAULT; + } + + /* process hostcmd response*/ + skb = cfg80211_testmode_alloc_reply_skb(wiphy, hostcmd->len); + if (!skb) { + kfree(hostcmd); + return -ENOMEM; + } + err = nla_put(skb, NXPWIFI_TM_ATTR_DATA, + hostcmd->len, hostcmd->cmd); + if (err) { + kfree(hostcmd); + kfree_skb(skb); + return -EMSGSIZE; + } + + err = cfg80211_testmode_reply(skb); + kfree(hostcmd); + return err; + default: + return -EOPNOTSUPP; + } +} +#endif + +static int +nxpwifi_cfg80211_start_radar_detection(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_chan_def *chandef, + u32 cac_time_ms, int link_id) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_radar_params radar_params; + int ret; + + if (priv->adapter->scan_processing) { + nxpwifi_dbg(priv->adapter, ERROR, + "radar detection: scan already in process...\n"); + return -EBUSY; + } + + if (!nxpwifi_is_11h_active(priv)) { + nxpwifi_dbg(priv->adapter, INFO, + "Enable 11h extensions in FW\n"); + if (nxpwifi_11h_activate(priv, true)) { + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to activate 11h extensions!!"); + return -EPERM; + } + priv->state_11h.is_11h_active = true; + } + + memset(&radar_params, 0, sizeof(struct nxpwifi_radar_params)); + radar_params.chandef = chandef; + radar_params.cac_time_ms = cac_time_ms; + + memcpy(&priv->dfs_chandef, chandef, sizeof(priv->dfs_chandef)); + + ret = nxpwifi_chan_report_request(priv, &radar_params); + if (!ret) + nxpwifi_queue_delayed_wiphy_work(priv->adapter, + &priv->dfs_cac_work, + msecs_to_jiffies(cac_time_ms)); + + return ret; +} + +static int +nxpwifi_cfg80211_change_station(struct wiphy *wiphy, struct wireless_dev *wdev, + const u8 *mac, + struct station_parameters *params) +{ + return 0; +} + +static int +nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_auth_request *req) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_adapter *adapter = priv->adapter; + struct sk_buff *skb; + u16 pkt_len, auth_alg; + int ret; + struct ieee80211_mgmt *mgmt; + struct nxpwifi_txinfo *tx_info; + u8 trans = 1, status_code = 0; + u8 *varptr = NULL; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + nxpwifi_dbg(adapter, ERROR, "Interface role is AP\n"); + return -EINVAL; + } + + if (priv->wdev.iftype != NL80211_IFTYPE_STATION) { + nxpwifi_dbg(adapter, ERROR, + "Interface type is not correct (type %d)\n", + priv->wdev.iftype); + return -EINVAL; + } + + if (!nxpwifi_is_channel_setting_allowable(priv, req->bss->channel)) + return -EOPNOTSUPP; + + if (priv->auth_alg != WLAN_AUTH_SAE && + (priv->auth_flag & HOST_MLME_AUTH_PENDING)) { + nxpwifi_dbg(adapter, ERROR, "Pending auth on going\n"); + return -EBUSY; + } + + if (!priv->host_mlme_reg) { + priv->host_mlme_reg = true; + priv->mgmt_frame_mask |= HOST_MLME_MGMT_MASK; + nxpwifi_mgmt_frame_reg(priv, priv->mgmt_frame_mask); + } + + switch (req->auth_type) { + case NL80211_AUTHTYPE_OPEN_SYSTEM: + auth_alg = WLAN_AUTH_OPEN; + break; + case NL80211_AUTHTYPE_SHARED_KEY: + auth_alg = WLAN_AUTH_SHARED_KEY; + break; + case NL80211_AUTHTYPE_FT: + auth_alg = WLAN_AUTH_FT; + break; + case NL80211_AUTHTYPE_NETWORK_EAP: + auth_alg = WLAN_AUTH_LEAP; + break; + case NL80211_AUTHTYPE_SAE: + auth_alg = WLAN_AUTH_SAE; + break; + default: + nxpwifi_dbg(adapter, ERROR, + "unsupported auth type=%d\n", req->auth_type); + return -EOPNOTSUPP; + } + + if (!(priv->auth_flag & HOST_MLME_AUTH_PENDING)) { + ret = nxpwifi_remain_on_chan_cfg(priv, HOST_ACT_GEN_SET, + req->bss->channel, + AUTH_TX_DEFAULT_WAIT_TIME); + + if (!ret) { + priv->roc_cfg.cookie = + nxpwifi_roc_cookie(adapter); + priv->roc_cfg.chan = *req->bss->channel; + } else { + return -EPERM; + } + } + + priv->sec_info.authentication_mode = auth_alg; + + nxpwifi_cancel_scan(adapter); + + pkt_len = (u16)req->ie_len + req->auth_data_len + + NXPWIFI_MGMT_HEADER_LEN + NXPWIFI_AUTH_BODY_LEN; + + if (req->auth_data_len >= 4) + pkt_len -= 4; + + mgmt = kzalloc(pkt_len, GFP_KERNEL); + + skb = dev_alloc_skb(NXPWIFI_MIN_DATA_HEADER_LEN + + NXPWIFI_MGMT_FRAME_HEADER_SIZE + + pkt_len + sizeof(pkt_len)); + if (!skb) { + nxpwifi_dbg(adapter, ERROR, + "allocate skb failed for management frame\n"); + return -ENOMEM; + } + + tx_info = NXPWIFI_SKB_TXCB(skb); + memset(tx_info, 0, sizeof(*tx_info)); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + tx_info->pkt_len = pkt_len; + + memcpy(mgmt->da, req->bss->bssid, ETH_ALEN); + memcpy(mgmt->sa, priv->curr_addr, ETH_ALEN); + memcpy(mgmt->bssid, req->bss->bssid, ETH_ALEN); + mgmt->frame_control = + cpu_to_le16(IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_AUTH); + + if (req->auth_data_len >= 4) { + if (req->auth_type == NL80211_AUTHTYPE_SAE) { + __le16 *pos = (__le16 *)req->auth_data; + + trans = le16_to_cpu(pos[0]); + status_code = le16_to_cpu(pos[1]); + } + memcpy((u8 *)(&mgmt->u.auth.variable), req->auth_data + 4, + req->auth_data_len - 4); + varptr = (u8 *)&mgmt->u.auth.variable + + (req->auth_data_len - 4); + } + + mgmt->u.auth.auth_alg = cpu_to_le16(auth_alg); + mgmt->u.auth.auth_transaction = cpu_to_le16(trans); + mgmt->u.auth.status_code = cpu_to_le16(status_code); + + if (req->ie && req->ie_len) { + if (!varptr) + varptr = (u8 *)&mgmt->u.auth.variable; + memcpy((u8 *)varptr, req->ie, req->ie_len); + } + + nxpwifi_form_mgmt_frame(skb, (const u8 *)mgmt, pkt_len); + kfree(mgmt); + priv->auth_flag = HOST_MLME_AUTH_PENDING; + priv->auth_alg = auth_alg; + skb->priority = WMM_HIGHEST_PRIORITY; + __net_timestamp(skb); + + nxpwifi_dbg(adapter, MSG, + "auth: send authentication to %pM\n", req->bss->bssid); + + nxpwifi_queue_tx_pkt(priv, skb); + + return 0; +} + +static int +nxpwifi_cfg80211_associate(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_assoc_request *req) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + struct cfg80211_ssid req_ssid; + const u8 *ssid_ie; + + if (GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_STA) { + nxpwifi_dbg(adapter, ERROR, + "%s: reject infra assoc request in non-STA role\n", + dev->name); + return -EINVAL; + } + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags) || + test_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags)) { + nxpwifi_dbg(adapter, ERROR, + "%s: Ignore association.\t" + "Card removed or FW in bad state\n", + dev->name); + return -EPERM; + } + + if (priv->auth_alg == WLAN_AUTH_SAE) + priv->auth_flag = HOST_MLME_AUTH_DONE; + + if (priv->auth_flag && !(priv->auth_flag & HOST_MLME_AUTH_DONE)) + return -EBUSY; + + if (priv->roc_cfg.cookie) { + ret = nxpwifi_remain_on_chan_cfg(priv, HOST_ACT_GEN_REMOVE, + &priv->roc_cfg.chan, 0); + if (!ret) + memset(&priv->roc_cfg, 0, + sizeof(struct nxpwifi_roc_cfg)); + else + return ret; + } + + if (!nxpwifi_stop_bg_scan(priv)) + cfg80211_sched_scan_stopped_locked(priv->wdev.wiphy, 0); + + memset(&req_ssid, 0, sizeof(struct cfg80211_ssid)); + rcu_read_lock(); + ssid_ie = ieee80211_bss_get_ie(req->bss, WLAN_EID_SSID); + + if (!ssid_ie) + goto ssid_err; + + req_ssid.ssid_len = ssid_ie[1]; + if (req_ssid.ssid_len > IEEE80211_MAX_SSID_LEN) { + nxpwifi_dbg(adapter, ERROR, "invalid SSID - aborting\n"); + goto ssid_err; + } + + memcpy(req_ssid.ssid, ssid_ie + 2, req_ssid.ssid_len); + if (!req_ssid.ssid_len || req_ssid.ssid[0] < 0x20) { + nxpwifi_dbg(adapter, ERROR, "invalid SSID - aborting\n"); + goto ssid_err; + } + rcu_read_unlock(); + + /* + * As this is new association, clear locally stored + * keys and security related flags + */ + priv->sec_info.wpa_enabled = false; + priv->sec_info.wpa2_enabled = false; + priv->wep_key_curr_index = 0; + priv->sec_info.encryption_mode = 0; + priv->sec_info.is_authtype_auto = 0; + ret = nxpwifi_set_encode(priv, NULL, NULL, 0, 0, NULL, 1); + + if (req->crypto.n_ciphers_pairwise) + priv->sec_info.encryption_mode = + req->crypto.ciphers_pairwise[0]; + + if (req->crypto.cipher_group) + priv->sec_info.encryption_mode = req->crypto.cipher_group; + + if (req->ie) + ret = nxpwifi_set_gen_ie(priv, req->ie, req->ie_len); + + memcpy(priv->cfg_bssid, req->bss->bssid, ETH_ALEN); + + nxpwifi_dbg(adapter, MSG, + "assoc: send association to %pM\n", req->bss->bssid); + + cfg80211_ref_bss(adapter->wiphy, req->bss); + + ret = nxpwifi_bss_start(priv, req->bss, &req_ssid); + + if (ret) { + priv->auth_flag = 0; + priv->auth_alg = WLAN_AUTH_NONE; + eth_zero_addr(priv->cfg_bssid); + } + + if (ret >= 0) { + if (priv->assoc_rsp_size) { + priv->req_bss = req->bss; + adapter->assoc_resp_received = true; + nxpwifi_queue_wiphy_work(adapter, + &adapter->host_mlme_work); + } + ret = 0; + } + + cfg80211_put_bss(priv->adapter->wiphy, req->bss); + + return ret; + +ssid_err: + + rcu_read_unlock(); + return -EINVAL; +} + +static int +nxpwifi_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *dev, + u16 reason_code) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + int ret; + + if (!nxpwifi_stop_bg_scan(priv)) + cfg80211_sched_scan_stopped_locked(priv->wdev.wiphy, 0); + + ret = nxpwifi_deauthenticate(priv, NULL); + if (!ret) { + eth_zero_addr(priv->cfg_bssid); + priv->hs2_enabled = false; + } + + return ret; +} + +static int +nxpwifi_cfg80211_deauthenticate(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_deauth_request *req) +{ + return nxpwifi_cfg80211_disconnect(wiphy, dev, req->reason_code); +} + +static int +nxpwifi_cfg80211_disassociate(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_disassoc_request *req) +{ + return nxpwifi_cfg80211_disconnect(wiphy, dev, req->reason_code); +} + +static int +nxpwifi_cfg80211_probe_peer(struct wiphy *wiphy, + struct net_device *dev, const u8 *peer, + u64 *cookie) +{ + /* + * hostapd looks for NL80211_CMD_PROBE_CLIENT support; otherwise, + * it requires monitor-mode support (which mwifiex doesn't support). + * Provide fake probe_peer support to work around this. + */ + return -EOPNOTSUPP; +} + +/* station cfg80211 operations */ +static const struct cfg80211_ops nxpwifi_cfg80211_ops = { + .add_virtual_intf = nxpwifi_add_virtual_intf, + .del_virtual_intf = nxpwifi_del_virtual_intf, + .change_virtual_intf = nxpwifi_cfg80211_change_virtual_intf, + .scan = nxpwifi_cfg80211_scan, + .auth = nxpwifi_cfg80211_authenticate, + .assoc = nxpwifi_cfg80211_associate, + .deauth = nxpwifi_cfg80211_deauthenticate, + .disassoc = nxpwifi_cfg80211_disassociate, + .probe_peer = nxpwifi_cfg80211_probe_peer, + .get_station = nxpwifi_cfg80211_get_station, + .dump_station = nxpwifi_cfg80211_dump_station, + .dump_survey = nxpwifi_cfg80211_dump_survey, + .set_wiphy_params = nxpwifi_cfg80211_set_wiphy_params, + .add_key = nxpwifi_cfg80211_add_key, + .del_key = nxpwifi_cfg80211_del_key, + .set_default_mgmt_key = nxpwifi_cfg80211_set_default_mgmt_key, + .mgmt_tx = nxpwifi_cfg80211_mgmt_tx, + .update_mgmt_frame_registrations = + nxpwifi_cfg80211_update_mgmt_frame_registrations, + .remain_on_channel = nxpwifi_cfg80211_remain_on_channel, + .cancel_remain_on_channel = nxpwifi_cfg80211_cancel_remain_on_channel, + .set_default_key = nxpwifi_cfg80211_set_default_key, + .set_power_mgmt = nxpwifi_cfg80211_set_power_mgmt, + .set_tx_power = nxpwifi_cfg80211_set_tx_power, + .get_tx_power = nxpwifi_cfg80211_get_tx_power, + .set_bitrate_mask = nxpwifi_cfg80211_set_bitrate_mask, + .start_ap = nxpwifi_cfg80211_start_ap, + .stop_ap = nxpwifi_cfg80211_stop_ap, + .change_beacon = nxpwifi_cfg80211_change_beacon, + .set_cqm_rssi_config = nxpwifi_cfg80211_set_cqm_rssi_config, + .set_antenna = nxpwifi_cfg80211_set_antenna, + .get_antenna = nxpwifi_cfg80211_get_antenna, + .del_station = nxpwifi_cfg80211_del_station, + .sched_scan_start = nxpwifi_cfg80211_sched_scan_start, + .sched_scan_stop = nxpwifi_cfg80211_sched_scan_stop, + .change_station = nxpwifi_cfg80211_change_station, +#ifdef CONFIG_PM + .suspend = nxpwifi_cfg80211_suspend, + .resume = nxpwifi_cfg80211_resume, + .set_wakeup = nxpwifi_cfg80211_set_wakeup, +#endif + .set_coalesce = nxpwifi_cfg80211_set_coalesce, + .add_station = nxpwifi_cfg80211_add_station, + CFG80211_TESTMODE_CMD(nxpwifi_tm_cmd) + .get_channel = nxpwifi_cfg80211_get_channel, + .start_radar_detection = nxpwifi_cfg80211_start_radar_detection, + .channel_switch = nxpwifi_cfg80211_channel_switch, +}; + +#ifdef CONFIG_PM +static const struct wiphy_wowlan_support nxpwifi_wowlan_support = { + .flags = WIPHY_WOWLAN_MAGIC_PKT | WIPHY_WOWLAN_DISCONNECT | + WIPHY_WOWLAN_NET_DETECT | WIPHY_WOWLAN_SUPPORTS_GTK_REKEY | + WIPHY_WOWLAN_GTK_REKEY_FAILURE, + .n_patterns = NXPWIFI_MEF_MAX_FILTERS, + .pattern_min_len = 1, + .pattern_max_len = NXPWIFI_MAX_PATTERN_LEN, + .max_pkt_offset = NXPWIFI_MAX_OFFSET_LEN, + .max_nd_match_sets = NXPWIFI_MAX_ND_MATCH_SETS, +}; + +static const struct wiphy_wowlan_support nxpwifi_wowlan_support_no_gtk = { + .flags = WIPHY_WOWLAN_MAGIC_PKT | WIPHY_WOWLAN_DISCONNECT | + WIPHY_WOWLAN_NET_DETECT, + .n_patterns = NXPWIFI_MEF_MAX_FILTERS, + .pattern_min_len = 1, + .pattern_max_len = NXPWIFI_MAX_PATTERN_LEN, + .max_pkt_offset = NXPWIFI_MAX_OFFSET_LEN, + .max_nd_match_sets = NXPWIFI_MAX_ND_MATCH_SETS, +}; +#endif + +static const struct wiphy_coalesce_support nxpwifi_coalesce_support = { + .n_rules = NXPWIFI_COALESCE_MAX_RULES, + .max_delay = NXPWIFI_MAX_COALESCING_DELAY, + .n_patterns = NXPWIFI_COALESCE_MAX_FILTERS, + .pattern_min_len = 1, + .pattern_max_len = NXPWIFI_MAX_PATTERN_LEN, + .max_pkt_offset = NXPWIFI_MAX_OFFSET_LEN, +}; + +int nxpwifi_init_channel_scan_gap(struct nxpwifi_adapter *adapter) +{ + u32 n_channels_bg, n_channels_a = 0; + + n_channels_bg = nxpwifi_band_2ghz.n_channels; + + if (adapter->fw_bands & BAND_A) + n_channels_a = nxpwifi_band_5ghz.n_channels; + + /* + * allocate twice the number total channels, since the driver issues an + * additional active scan request for hidden SSIDs on passive channels. + */ + adapter->num_in_chan_stats = 2 * (n_channels_bg + n_channels_a); + adapter->chan_stats = vmalloc(array_size(sizeof(*adapter->chan_stats), + adapter->num_in_chan_stats)); + + if (!adapter->chan_stats) + return -ENOMEM; + + return 0; +} + +/* + * Register the device with cfg80211. + * + * Create and initialize the wiphy, fill in defaults and handlers, + * then register it with the cfg80211 subsystem. + */ +int nxpwifi_register_cfg80211(struct nxpwifi_adapter *adapter) +{ + int ret; + void *wdev_priv; + struct wiphy *wiphy; + struct nxpwifi_private *priv = adapter->priv[NXPWIFI_BSS_TYPE_STA]; + struct ieee80211_sta_ht_cap *ht_cap; + struct ieee80211_sta_vht_cap *vht_cap; + u8 *country_code; + u32 thr, retry; + + /* create a new wiphy for use with cfg80211 */ + wiphy = wiphy_new(&nxpwifi_cfg80211_ops, + sizeof(struct nxpwifi_adapter *)); + if (!wiphy) { + nxpwifi_dbg(adapter, ERROR, + "%s: creating new wiphy\n", __func__); + return -ENOMEM; + } + + wiphy->max_scan_ssids = NXPWIFI_MAX_SSID_LIST_LENGTH; + wiphy->max_scan_ie_len = NXPWIFI_MAX_VSIE_LEN; + + wiphy->mgmt_stypes = nxpwifi_mgmt_stypes; + wiphy->max_remain_on_channel_duration = 5000; + wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_MONITOR); + + wiphy->max_num_akm_suites = CFG80211_MAX_NUM_AKM_SUITES; + + wiphy->bands[NL80211_BAND_2GHZ] = + devm_kmemdup(adapter->dev, &nxpwifi_band_2ghz, + sizeof(nxpwifi_band_2ghz), GFP_KERNEL); + if (!wiphy->bands[NL80211_BAND_2GHZ]) { + ret = -ENOMEM; + goto err; + } + + if (adapter->fw_bands & BAND_A) { + wiphy->bands[NL80211_BAND_5GHZ] = + devm_kmemdup(adapter->dev, &nxpwifi_band_5ghz, + sizeof(nxpwifi_band_5ghz), GFP_KERNEL); + if (!wiphy->bands[NL80211_BAND_5GHZ]) { + ret = -ENOMEM; + goto err; + } + } else { + wiphy->bands[NL80211_BAND_5GHZ] = NULL; + } + + ht_cap = &wiphy->bands[NL80211_BAND_2GHZ]->ht_cap; + nxpwifi_setup_ht_caps(priv, ht_cap); + + if (adapter->is_hw_11ac_capable) { + vht_cap = &wiphy->bands[NL80211_BAND_2GHZ]->vht_cap; + nxpwifi_setup_vht_caps(priv, vht_cap); + } + + if (adapter->is_hw_11ax_capable) + nxpwifi_setup_he_caps(priv, wiphy->bands[NL80211_BAND_2GHZ]); + + if (adapter->fw_bands & BAND_A) { + ht_cap = &wiphy->bands[NL80211_BAND_5GHZ]->ht_cap; + nxpwifi_setup_ht_caps(priv, ht_cap); + + if (adapter->is_hw_11ac_capable) { + vht_cap = &wiphy->bands[NL80211_BAND_5GHZ]->vht_cap; + nxpwifi_setup_vht_caps(priv, vht_cap); + } + + if (adapter->is_hw_11ax_capable) + nxpwifi_setup_he_caps(priv, wiphy->bands[NL80211_BAND_5GHZ]); + } + + if (adapter->is_hw_11ac_capable) + wiphy->iface_combinations = &nxpwifi_iface_comb_ap_sta_vht; + else + wiphy->iface_combinations = &nxpwifi_iface_comb_ap_sta; + wiphy->n_iface_combinations = 1; + + wiphy->max_ap_assoc_sta = adapter->max_sta_conn; + + /* Initialize cipher suits */ + wiphy->cipher_suites = nxpwifi_cipher_suites; + wiphy->n_cipher_suites = ARRAY_SIZE(nxpwifi_cipher_suites); + + if (adapter->regd) { + wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG | + REGULATORY_DISABLE_BEACON_HINTS | + REGULATORY_COUNTRY_IE_IGNORE; + wiphy_apply_custom_regulatory(wiphy, adapter->regd); + } + + ether_addr_copy(wiphy->perm_addr, adapter->perm_addr); + wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; + wiphy->flags |= WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD | + WIPHY_FLAG_AP_UAPSD | + WIPHY_FLAG_REPORTS_OBSS | + WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL | + WIPHY_FLAG_HAS_CHANNEL_SWITCH | + WIPHY_FLAG_NETNS_OK | + WIPHY_FLAG_PS_ON_BY_DEFAULT; + wiphy->max_num_csa_counters = NXPWIFI_MAX_CSA_COUNTERS; + +#ifdef CONFIG_PM + if (ISSUPP_FIRMWARE_SUPPLICANT(priv->adapter->fw_cap_info)) + wiphy->wowlan = &nxpwifi_wowlan_support; + else + wiphy->wowlan = &nxpwifi_wowlan_support_no_gtk; +#endif + + wiphy->coalesce = &nxpwifi_coalesce_support; + + wiphy->probe_resp_offload = NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS | + NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2; + + wiphy->max_sched_scan_reqs = 1; + wiphy->max_sched_scan_ssids = NXPWIFI_MAX_SSID_LIST_LENGTH; + wiphy->max_sched_scan_ie_len = NXPWIFI_MAX_VSIE_LEN; + wiphy->max_match_sets = NXPWIFI_MAX_SSID_LIST_LENGTH; + + wiphy->available_antennas_tx = BIT(adapter->number_of_antenna) - 1; + wiphy->available_antennas_rx = BIT(adapter->number_of_antenna) - 1; + + wiphy->features |= NL80211_FEATURE_SAE | + NL80211_FEATURE_INACTIVITY_TIMER | + NL80211_FEATURE_LOW_PRIORITY_SCAN | + NL80211_FEATURE_NEED_OBSS_SCAN; + + if (ISSUPP_RANDOM_MAC(adapter->fw_cap_info)) + wiphy->features |= NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR | + NL80211_FEATURE_SCHED_SCAN_RANDOM_MAC_ADDR | + NL80211_FEATURE_ND_RANDOM_MAC_ADDR; + + if (adapter->fw_api_ver == NXPWIFI_FW_V15) + wiphy->features |= NL80211_FEATURE_SK_TX_STATUS; + + /* Reserve space for nxpwifi specific private data for BSS */ + wiphy->bss_priv_size = sizeof(struct nxpwifi_bss_priv); + + wiphy->reg_notifier = nxpwifi_reg_notifier; + + /* Set struct nxpwifi_adapter pointer in wiphy_priv */ + wdev_priv = wiphy_priv(wiphy); + *(unsigned long *)wdev_priv = (unsigned long)adapter; + + set_wiphy_dev(wiphy, priv->adapter->dev); + + ret = wiphy_register(wiphy); + if (ret < 0) { + nxpwifi_dbg(adapter, ERROR, + "%s: wiphy_register failed: %d\n", __func__, ret); + goto err; + } + + if (!adapter->regd) { + if (adapter->region_code == 0x00) { + nxpwifi_dbg(adapter, WARN, + "Ignore world regulatory domain\n"); + } else { + wiphy->regulatory_flags |= + REGULATORY_DISABLE_BEACON_HINTS | + REGULATORY_COUNTRY_IE_IGNORE; + country_code = + nxpwifi_11d_code_2_region(adapter->region_code); + if (country_code && + regulatory_hint(wiphy, country_code)) + nxpwifi_dbg(priv->adapter, ERROR, + "regulatory_hint() failed\n"); + } + } + + nxpwifi_get_802_11_snmp_mib(priv, FRAG_THRESH_I, &thr); + wiphy->frag_threshold = thr; + nxpwifi_get_802_11_snmp_mib(priv, RTS_THRESH_I, &thr); + wiphy->rts_threshold = thr; + nxpwifi_get_802_11_snmp_mib(priv, SHORT_RETRY_LIM_I, &retry); + wiphy->retry_short = (u8)retry; + nxpwifi_get_802_11_snmp_mib(priv, LONG_RETRY_LIM_I, &retry); + wiphy->retry_long = (u8)retry; + + adapter->wiphy = wiphy; + return ret; + +err: + wiphy_free(wiphy); + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.h b/drivers/net/wireless/nxp/nxpwifi/cfg80211.h new file mode 100644 index 000000000000..3a9a204df195 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * nxpwifi: cfg80211 support + * + * Copyright 2011-2024 NXP + */ + +#ifndef __NXPWIFI_CFG80211__ +#define __NXPWIFI_CFG80211__ + +#include "main.h" + +int nxpwifi_register_cfg80211(struct nxpwifi_adapter *adapter); + +int nxpwifi_cfg80211_change_beacon(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_ap_update *params); +#endif diff --git a/drivers/net/wireless/nxp/nxpwifi/cfp.c b/drivers/net/wireless/nxp/nxpwifi/cfp.c new file mode 100644 index 000000000000..aec1d5014810 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/cfp.c @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: Channel, Frequency and Power + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cfg80211.h" + +/* 100mW */ +#define NXPWIFI_TX_PWR_DEFAULT 20 +/* 100mW */ +#define NXPWIFI_TX_PWR_US_DEFAULT 20 +/* 50mW */ +#define NXPWIFI_TX_PWR_JP_DEFAULT 16 +/* 100mW */ +#define NXPWIFI_TX_PWR_FR_100MW 20 +/* 10mW */ +#define NXPWIFI_TX_PWR_FR_10MW 10 +/* 100mW */ +#define NXPWIFI_TX_PWR_EMEA_DEFAULT 20 + +static u8 supported_rates_a[A_SUPPORTED_RATES] = { 0x0c, 0x12, 0x18, 0x24, + 0xb0, 0x48, 0x60, 0x6c, 0 }; +static u16 nxpwifi_data_rates[NXPWIFI_SUPPORTED_RATES_EXT] = { 0x02, 0x04, + 0x0B, 0x16, 0x00, 0x0C, 0x12, 0x18, + 0x24, 0x30, 0x48, 0x60, 0x6C, 0x90, + 0x0D, 0x1A, 0x27, 0x34, 0x4E, 0x68, + 0x75, 0x82, 0x0C, 0x1B, 0x36, 0x51, + 0x6C, 0xA2, 0xD8, 0xF3, 0x10E, 0x00 }; + +static u8 supported_rates_b[B_SUPPORTED_RATES] = { 0x02, 0x04, 0x0b, 0x16, 0 }; + +static u8 supported_rates_g[G_SUPPORTED_RATES] = { 0x0c, 0x12, 0x18, 0x24, + 0x30, 0x48, 0x60, 0x6c, 0 }; + +static u8 supported_rates_bg[BG_SUPPORTED_RATES] = { 0x02, 0x04, 0x0b, 0x0c, + 0x12, 0x16, 0x18, 0x24, 0x30, 0x48, + 0x60, 0x6c, 0 }; + +/* mcs_rate: first 8 entries for 1x1; all 16 for 2x2. */ +static const u16 mcs_rate[4][16] = { + /* LGI 40M */ + { 0x1b, 0x36, 0x51, 0x6c, 0xa2, 0xd8, 0xf3, 0x10e, + 0x36, 0x6c, 0xa2, 0xd8, 0x144, 0x1b0, 0x1e6, 0x21c }, + + /* SGI 40M */ + { 0x1e, 0x3c, 0x5a, 0x78, 0xb4, 0xf0, 0x10e, 0x12c, + 0x3c, 0x78, 0xb4, 0xf0, 0x168, 0x1e0, 0x21c, 0x258 }, + + /* LGI 20M */ + { 0x0d, 0x1a, 0x27, 0x34, 0x4e, 0x68, 0x75, 0x82, + 0x1a, 0x34, 0x4e, 0x68, 0x9c, 0xd0, 0xea, 0x104 }, + + /* SGI 20M */ + { 0x0e, 0x1c, 0x2b, 0x39, 0x56, 0x73, 0x82, 0x90, + 0x1c, 0x39, 0x56, 0x73, 0xad, 0xe7, 0x104, 0x120 } +}; + +/* AC rates */ +static const u16 ac_mcs_rate_nss1[8][10] = { + /* LG 160M */ + { 0x75, 0xEA, 0x15F, 0x1D4, 0x2BE, 0x3A8, 0x41D, + 0x492, 0x57C, 0x618 }, + + /* SG 160M */ + { 0x82, 0x104, 0x186, 0x208, 0x30C, 0x410, 0x492, + 0x514, 0x618, 0x6C6 }, + + /* LG 80M */ + { 0x3B, 0x75, 0xB0, 0xEA, 0x15F, 0x1D4, 0x20F, + 0x249, 0x2BE, 0x30C }, + + /* SG 80M */ + { 0x41, 0x82, 0xC3, 0x104, 0x186, 0x208, 0x249, + 0x28A, 0x30C, 0x363 }, + + /* LG 40M */ + { 0x1B, 0x36, 0x51, 0x6C, 0xA2, 0xD8, 0xF3, + 0x10E, 0x144, 0x168 }, + + /* SG 40M */ + { 0x1E, 0x3C, 0x5A, 0x78, 0xB4, 0xF0, 0x10E, + 0x12C, 0x168, 0x190 }, + + /* LG 20M */ + { 0xD, 0x1A, 0x27, 0x34, 0x4E, 0x68, 0x75, 0x82, 0x9C, 0x00 }, + + /* SG 20M */ + { 0xF, 0x1D, 0x2C, 0x3A, 0x57, 0x74, 0x82, 0x91, 0xAE, 0x00 }, +}; + +/* NSS2 note: the value in the table is 2 multiplier of the actual rate */ +static const u16 ac_mcs_rate_nss2[8][10] = { + /* LG 160M */ + { 0xEA, 0x1D4, 0x2BE, 0x3A8, 0x57C, 0x750, 0x83A, + 0x924, 0xAF8, 0xC30 }, + + /* SG 160M */ + { 0x104, 0x208, 0x30C, 0x410, 0x618, 0x820, 0x924, + 0xA28, 0xC30, 0xD8B }, + + /* LG 80M */ + { 0x75, 0xEA, 0x15F, 0x1D4, 0x2BE, 0x3A8, 0x41D, + 0x492, 0x57C, 0x618 }, + + /* SG 80M */ + { 0x82, 0x104, 0x186, 0x208, 0x30C, 0x410, 0x492, + 0x514, 0x618, 0x6C6 }, + + /* LG 40M */ + { 0x36, 0x6C, 0xA2, 0xD8, 0x144, 0x1B0, 0x1E6, + 0x21C, 0x288, 0x2D0 }, + + /* SG 40M */ + { 0x3C, 0x78, 0xB4, 0xF0, 0x168, 0x1E0, 0x21C, + 0x258, 0x2D0, 0x320 }, + + /* LG 20M */ + { 0x1A, 0x34, 0x4A, 0x68, 0x9C, 0xD0, 0xEA, 0x104, + 0x138, 0x00 }, + + /* SG 20M */ + { 0x1D, 0x3A, 0x57, 0x74, 0xAE, 0xE6, 0x104, 0x121, + 0x15B, 0x00 }, +}; + +struct region_code_mapping { + u8 code; + u8 region[IEEE80211_COUNTRY_STRING_LEN]; +}; + +static struct region_code_mapping region_code_mapping_t[] = { + { 0x10, "US " }, /* US FCC */ + { 0x20, "CA " }, /* IC Canada */ + { 0x30, "FR " }, /* France */ + { 0x31, "ES " }, /* Spain */ + { 0x32, "FR " }, /* France */ + { 0x40, "JP " }, /* Japan */ + { 0x41, "JP " }, /* Japan */ + { 0x50, "CN " }, /* China */ +}; + +/* Convert 11d country code to region string. */ +u8 *nxpwifi_11d_code_2_region(u8 code) +{ + u8 i; + + /* Look for code in mapping table */ + for (i = 0; i < ARRAY_SIZE(region_code_mapping_t); i++) + if (region_code_mapping_t[i].code == code) + return region_code_mapping_t[i].region; + + return NULL; +} + +/* Map supported rate index to AC/VHT data rate. */ +u32 nxpwifi_index_to_acs_data_rate(struct nxpwifi_private *priv, + u8 index, u8 ht_info) +{ + u32 rate = 0; + u8 mcs_index = 0; + u8 bw = 0; + u8 gi = 0; + + if ((ht_info & 0x3) == NXPWIFI_RATE_FORMAT_VHT) { + mcs_index = min(index & 0xF, 9); + + /* 20M: bw=0, 40M: bw=1, 80M: bw=2, 160M: bw=3 */ + bw = (ht_info & 0xC) >> 2; + + /* LGI: gi =0, SGI: gi = 1 */ + gi = (ht_info & 0x10) >> 4; + + if ((index >> 4) == 1) /* NSS = 2 */ + rate = ac_mcs_rate_nss2[2 * (3 - bw) + gi][mcs_index]; + else /* NSS = 1 */ + rate = ac_mcs_rate_nss1[2 * (3 - bw) + gi][mcs_index]; + } else if ((ht_info & 0x3) == NXPWIFI_RATE_FORMAT_HT) { + /* 20M: bw=0, 40M: bw=1 */ + bw = (ht_info & 0xC) >> 2; + + /* LGI: gi =0, SGI: gi = 1 */ + gi = (ht_info & 0x10) >> 4; + + if (index == NXPWIFI_RATE_BITMAP_MCS0) { + if (gi == 1) + rate = 0x0D; /* MCS 32 SGI rate */ + else + rate = 0x0C; /* MCS 32 LGI rate */ + } else if (index < 16) { + if (bw == 1 || bw == 0) + rate = mcs_rate[2 * (1 - bw) + gi][index]; + else + rate = nxpwifi_data_rates[0]; + } else { + rate = nxpwifi_data_rates[0]; + } + } else { + /* 11n non-HT rates */ + if (index >= NXPWIFI_SUPPORTED_RATES_EXT) + index = 0; + rate = nxpwifi_data_rates[index]; + } + + return rate; +} + +/* Map supported rate index to data rate. */ +u32 nxpwifi_index_to_data_rate(struct nxpwifi_private *priv, + u8 index, u8 ht_info) +{ + u32 mcs_num_supp = + (priv->adapter->user_dev_mcs_support == HT_STREAM_2X2) ? 16 : 8; + u32 rate; + + if (priv->adapter->is_hw_11ac_capable) + return nxpwifi_index_to_acs_data_rate(priv, index, ht_info); + + if (ht_info & BIT(0)) { + if (index == NXPWIFI_RATE_BITMAP_MCS0) { + if (ht_info & BIT(2)) + rate = 0x0D; /* MCS 32 SGI rate */ + else + rate = 0x0C; /* MCS 32 LGI rate */ + } else if (index < mcs_num_supp) { + if (ht_info & BIT(1)) { + if (ht_info & BIT(2)) + /* SGI, 40M */ + rate = mcs_rate[1][index]; + else + /* LGI, 40M */ + rate = mcs_rate[0][index]; + } else { + if (ht_info & BIT(2)) + /* SGI, 20M */ + rate = mcs_rate[3][index]; + else + /* LGI, 20M */ + rate = mcs_rate[2][index]; + } + } else { + rate = nxpwifi_data_rates[0]; + } + } else { + if (index >= NXPWIFI_SUPPORTED_RATES_EXT) + index = 0; + rate = nxpwifi_data_rates[index]; + } + return rate; +} + +/* Return current active data rates (depends on connection). */ +u32 nxpwifi_get_active_data_rates(struct nxpwifi_private *priv, u8 *rates) +{ + if (!priv->media_connected) + return nxpwifi_get_supported_rates(priv, rates); + else + return nxpwifi_copy_rates(rates, 0, + priv->curr_bss_params.data_rates, + priv->curr_bss_params.num_of_rates); +} + +/* Find Channel/Frequency/Power by band and channel or frequency. */ +struct nxpwifi_chan_freq_power * +nxpwifi_get_cfp(struct nxpwifi_private *priv, u8 band, u16 channel, u32 freq) +{ + struct nxpwifi_chan_freq_power *cfp = NULL; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch = NULL; + int i; + + if (!channel && !freq) + return cfp; + + if (nxpwifi_band_to_radio_type(band) == HOST_SCAN_RADIO_TYPE_BG) + sband = priv->wdev.wiphy->bands[NL80211_BAND_2GHZ]; + else + sband = priv->wdev.wiphy->bands[NL80211_BAND_5GHZ]; + + if (!sband) { + nxpwifi_dbg(priv->adapter, ERROR, + "%s: cannot find cfp by band %d\n", + __func__, band); + return cfp; + } + + for (i = 0; i < sband->n_channels; i++) { + ch = &sband->channels[i]; + + if (ch->flags & IEEE80211_CHAN_DISABLED) + continue; + + if (freq) { + if (ch->center_freq == freq) + break; + } else { + /* Find by valid channel. */ + if (ch->hw_value == channel || + channel == FIRST_VALID_CHANNEL) + break; + } + } + if (i == sband->n_channels) { + nxpwifi_dbg(priv->adapter, WARN, + "%s: cannot find cfp by band %d\t" + "& channel=%d freq=%d\n", + __func__, band, channel, freq); + } else { + if (!ch) + return cfp; + + priv->cfp.channel = ch->hw_value; + priv->cfp.freq = ch->center_freq; + priv->cfp.max_tx_power = ch->max_power; + cfp = &priv->cfp; + } + + return cfp; +} + +/* Return true if data rate is set to auto. */ +u8 +nxpwifi_is_rate_auto(struct nxpwifi_private *priv) +{ + u32 i; + int rate_num = 0; + + for (i = 0; i < ARRAY_SIZE(priv->bitmap_rates); i++) + if (priv->bitmap_rates[i]) + rate_num++; + + if (rate_num > 1) + return true; + else + return false; +} + +/* Extract supported rates from cfg80211_scan_request bitmask. */ +u32 nxpwifi_get_rates_from_cfg80211(struct nxpwifi_private *priv, + u8 *rates, u8 radio_type) +{ + struct wiphy *wiphy = priv->adapter->wiphy; + struct cfg80211_scan_request *request = priv->scan_request; + u32 num_rates, rate_mask; + struct ieee80211_supported_band *sband; + int i; + + if (radio_type) { + sband = wiphy->bands[NL80211_BAND_5GHZ]; + if (WARN_ON_ONCE(!sband)) + return 0; + rate_mask = request->rates[NL80211_BAND_5GHZ]; + } else { + sband = wiphy->bands[NL80211_BAND_2GHZ]; + if (WARN_ON_ONCE(!sband)) + return 0; + rate_mask = request->rates[NL80211_BAND_2GHZ]; + } + + num_rates = 0; + for (i = 0; i < sband->n_bitrates; i++) { + if ((BIT(i) & rate_mask) == 0) + continue; /* skip rate */ + rates[num_rates++] = (u8)(sband->bitrates[i].bitrate / 5); + } + + return num_rates; +} + +/* Convert config_bands to B/G/A band */ +static u16 nxpwifi_convert_config_bands(u16 config_bands) +{ + u16 bands = 0; + + if (config_bands & BAND_B) + bands |= BAND_B; + if (config_bands & BAND_G || config_bands & BAND_GN || + config_bands & BAND_GAC || config_bands & BAND_GAX) + bands |= BAND_G; + if (config_bands & BAND_A || config_bands & BAND_AN || + config_bands & BAND_AAC || config_bands & BAND_AAX) + bands |= BAND_A; + + return bands; +} + +/* Get supported rates in infrastructure (STA/P2P client) mode. */ +u32 nxpwifi_get_supported_rates(struct nxpwifi_private *priv, u8 *rates) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u32 k = 0; + u16 bands = 0; + + bands = nxpwifi_convert_config_bands(adapter->fw_bands); + + if (priv->bss_mode == NL80211_IFTYPE_STATION) { + if (bands == BAND_B) { + /* B only */ + nxpwifi_dbg(adapter, INFO, "info: infra band=%d\t" + "supported_rates_b\n", + priv->config_bands); + k = nxpwifi_copy_rates(rates, k, supported_rates_b, + sizeof(supported_rates_b)); + } else if (bands == BAND_G) { + /* G only */ + nxpwifi_dbg(adapter, INFO, "info: infra band=%d\t" + "supported_rates_g\n", + priv->config_bands); + k = nxpwifi_copy_rates(rates, k, supported_rates_g, + sizeof(supported_rates_g)); + } else if (bands & (BAND_B | BAND_G)) { + /* BG only */ + nxpwifi_dbg(adapter, INFO, "info: infra band=%d\t" + "supported_rates_bg\n", + priv->config_bands); + k = nxpwifi_copy_rates(rates, k, supported_rates_bg, + sizeof(supported_rates_bg)); + } else if (bands & BAND_A) { + /* support A */ + nxpwifi_dbg(adapter, INFO, "info: infra band=%d\t" + "supported_rates_a\n", + priv->config_bands); + k = nxpwifi_copy_rates(rates, k, supported_rates_a, + sizeof(supported_rates_a)); + } + } + + return k; +} + +u8 nxpwifi_adjust_data_rate(struct nxpwifi_private *priv, + u8 rx_rate, u8 rate_info) +{ + u8 rate_index = 0; + + /* HT40 */ + if ((rate_info & BIT(0)) && (rate_info & BIT(1))) + rate_index = NXPWIFI_RATE_INDEX_MCS0 + + NXPWIFI_BW20_MCS_NUM + rx_rate; + else if (rate_info & BIT(0)) /* HT20 */ + rate_index = NXPWIFI_RATE_INDEX_MCS0 + rx_rate; + else + rate_index = (rx_rate > NXPWIFI_RATE_INDEX_OFDM0) ? + rx_rate - 1 : rx_rate; + + if (rate_index >= NXPWIFI_MAX_AC_RX_RATES) + rate_index = NXPWIFI_MAX_AC_RX_RATES - 1; + + return rate_index; +} + +/* Check if the given region code is a valid NXP-defined region code. + * Valid codes are defined by the FW v18 region enum in IEEE_types.h: + * 0x00 (World), 0x10 (US/FCC), 0x20 (Canada/IC), 0x30 (ETSI), + * 0x31 (Spain), 0x32 (France), 0x40 (Japan), 0x41 (Japan1), 0x50 (China) + */ +bool nxpwifi_is_valid_region_code(enum nxpwifi_region_code code) +{ + switch (code) { + case NXPWIFI_REGION_WORLD: + case NXPWIFI_REGION_FCC: + case NXPWIFI_REGION_IC: + case NXPWIFI_REGION_ETSI: + case NXPWIFI_REGION_SPAIN: + case NXPWIFI_REGION_FRANCE: + case NXPWIFI_REGION_JAPAN: + case NXPWIFI_REGION_JAPAN1: + case NXPWIFI_REGION_CHINA: + return true; + default: + return false; + } +} diff --git a/drivers/net/wireless/nxp/nxpwifi/cmdevt.c b/drivers/net/wireless/nxp/nxpwifi/cmdevt.c new file mode 100644 index 000000000000..4eb17ada5db8 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/cmdevt.c @@ -0,0 +1,1310 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: commands and events + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" + +static void nxpwifi_cancel_pending_ioctl(struct nxpwifi_adapter *adapter); + +/* Initialize command node; set defaults; buffers are supplied by caller. */ +static void +nxpwifi_init_cmd_node(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node, + u32 cmd_no, void *data_buf, bool sync) +{ + cmd_node->priv = priv; + cmd_node->cmd_no = cmd_no; + + if (sync) { + cmd_node->wait_q_enabled = true; + cmd_node->cmd_wait_q_woken = false; + cmd_node->condition = &cmd_node->cmd_wait_q_woken; + } + cmd_node->data_buf = data_buf; + cmd_node->cmd_skb = cmd_node->skb; + cmd_node->cmd_resp = NULL; +} + +/* Get a free command node from cmd_free_q; return NULL if none. */ +static struct cmd_ctrl_node * +nxpwifi_get_cmd_node(struct nxpwifi_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_node; + + spin_lock_bh(&adapter->cmd_free_q_lock); + if (list_empty(&adapter->cmd_free_q)) { + nxpwifi_dbg(adapter, ERROR, + "GET_CMD_NODE: cmd node not available\n"); + spin_unlock_bh(&adapter->cmd_free_q_lock); + return NULL; + } + cmd_node = list_first_entry(&adapter->cmd_free_q, + struct cmd_ctrl_node, list); + list_del(&cmd_node->list); + spin_unlock_bh(&adapter->cmd_free_q_lock); + + return cmd_node; +} + +/* Reset cmd node state; trim cmd skb; complete and clear resp_skb if present. */ +static void +nxpwifi_clean_cmd_node(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node) +{ + cmd_node->cmd_no = 0; + cmd_node->cmd_flag = 0; + cmd_node->data_buf = NULL; + cmd_node->wait_q_enabled = false; + + if (cmd_node->cmd_skb) + skb_trim(cmd_node->cmd_skb, 0); + + if (cmd_node->resp_skb) { + adapter->if_ops.cmdrsp_complete(adapter, cmd_node->resp_skb); + cmd_node->resp_skb = NULL; + } +} + +/* Optionally complete waiters, clean the node, and add it back to cmd_free_q. */ +static void +nxpwifi_insert_cmd_to_free_q(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node) +{ + if (!cmd_node) + return; + + if (cmd_node->wait_q_enabled) + nxpwifi_complete_cmd(adapter, cmd_node); + /* Clean the node */ + nxpwifi_clean_cmd_node(adapter, cmd_node); + + /* Insert node into cmd_free_q */ + spin_lock_bh(&adapter->cmd_free_q_lock); + list_add_tail(&cmd_node->list, &adapter->cmd_free_q); + spin_unlock_bh(&adapter->cmd_free_q_lock); +} + +/* Reuse a command node. */ +void nxpwifi_recycle_cmd_node(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node) +{ + struct host_cmd_ds_command *host_cmd = (void *)cmd_node->cmd_skb->data; + + nxpwifi_insert_cmd_to_free_q(adapter, cmd_node); + + atomic_dec(&adapter->cmd_pending); + nxpwifi_dbg(adapter, CMD, + "cmd: FREE_CMD: cmd=%#x, cmd_pending=%d\n", + le16_to_cpu(host_cmd->command), + atomic_read(&adapter->cmd_pending)); +} + +/* Copy host command (userspace-provided) into the driver cmd buffer. */ +static int nxpwifi_cmd_host_cmd(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node) +{ + struct host_cmd_ds_command *cmd; + struct nxpwifi_ds_misc_cmd *pcmd_ptr; + + cmd = (struct host_cmd_ds_command *)cmd_node->skb->data; + pcmd_ptr = (struct nxpwifi_ds_misc_cmd *)cmd_node->data_buf; + + /* Copy the HOST command to command buffer */ + memcpy(cmd, pcmd_ptr->cmd, pcmd_ptr->len); + nxpwifi_dbg(priv->adapter, CMD, + "cmd: host cmd size = %d\n", pcmd_ptr->len); + return 0; +} + +/* Send prepared command to FW: set seq no, adjust skb length, log, start timer. */ +static int nxpwifi_dnld_cmd_to_fw(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + struct host_cmd_ds_command *host_cmd; + u16 cmd_code; + u16 cmd_size; + + if (!adapter || !cmd_node) + return -EINVAL; + + host_cmd = (struct host_cmd_ds_command *)(cmd_node->cmd_skb->data); + + /* Sanity test */ + if (host_cmd->size == 0) { + nxpwifi_dbg(adapter, ERROR, + "DNLD_CMD: host_cmd is null\t" + "or cmd size is 0, not sending\n"); + if (cmd_node->wait_q_enabled) + adapter->cmd_wait_q.status = -1; + nxpwifi_recycle_cmd_node(adapter, cmd_node); + return -EINVAL; + } + + cmd_code = le16_to_cpu(host_cmd->command); + cmd_node->cmd_no = cmd_code; + cmd_size = le16_to_cpu(host_cmd->size); + + if (adapter->hw_status == NXPWIFI_HW_STATUS_RESET && + cmd_code != HOST_CMD_FUNC_SHUTDOWN && + cmd_code != HOST_CMD_FUNC_INIT) { + nxpwifi_dbg(adapter, ERROR, + "DNLD_CMD: FW in reset state, ignore cmd %#x\n", + cmd_code); + nxpwifi_recycle_cmd_node(adapter, cmd_node); + nxpwifi_queue_work(adapter, &adapter->main_work); + return -EPERM; + } + + /* Set command sequence number */ + adapter->seq_num++; + host_cmd->seq_num = cpu_to_le16(HOST_SET_SEQ_NO_BSS_INFO + (adapter->seq_num, + cmd_node->priv->bss_num, + cmd_node->priv->bss_type)); + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->curr_cmd = cmd_node; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + /* Adjust skb length */ + if (cmd_node->cmd_skb->len > cmd_size) + /* + * cmd_size is less than sizeof(struct host_cmd_ds_command). + * Trim off the unused portion. + */ + skb_trim(cmd_node->cmd_skb, cmd_size); + else if (cmd_node->cmd_skb->len < cmd_size) + /* + * cmd_size is larger than sizeof(struct host_cmd_ds_command) + * because we have appended custom element TLV. Increase skb length + * accordingly. + */ + skb_put(cmd_node->cmd_skb, cmd_size - cmd_node->cmd_skb->len); + + nxpwifi_dbg(adapter, CMD, + "cmd: DNLD_CMD: %#x, act %#x, len %d, seqno %#x\n", + cmd_code, + get_unaligned_le16((u8 *)host_cmd + S_DS_GEN), + cmd_size, le16_to_cpu(host_cmd->seq_num)); + nxpwifi_dbg_dump(adapter, CMD_D, "cmd buffer:", host_cmd, cmd_size); + + skb_push(cmd_node->cmd_skb, adapter->intf_hdr_len); + ret = adapter->if_ops.host_to_card(adapter, NXPWIFI_TYPE_CMD, + cmd_node->cmd_skb, NULL); + skb_pull(cmd_node->cmd_skb, adapter->intf_hdr_len); + + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "DNLD_CMD: host to card failed\n"); + if (cmd_node->wait_q_enabled) + adapter->cmd_wait_q.status = -1; + nxpwifi_recycle_cmd_node(adapter, adapter->curr_cmd); + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->curr_cmd = NULL; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + adapter->dbg.num_cmd_host_to_card_failure++; + return ret; + } + + /* Save the last command id and action to debug log */ + adapter->dbg.last_cmd_index = + (adapter->dbg.last_cmd_index + 1) % DBG_CMD_NUM; + adapter->dbg.last_cmd_id[adapter->dbg.last_cmd_index] = cmd_code; + adapter->dbg.last_cmd_act[adapter->dbg.last_cmd_index] = + get_unaligned_le16((u8 *)host_cmd + S_DS_GEN); + + /* + * Setup the timer after transmit command, except that specific + * command might not have command response. + */ + if (cmd_code != HOST_CMD_FW_DUMP_EVENT) + mod_timer(&adapter->cmd_timer, + jiffies + msecs_to_jiffies(NXPWIFI_TIMER_10S)); + + /* Clear BSS_NO_BITS from HOST */ + cmd_code &= HOST_CMD_ID_MASK; + + return 0; +} + +/* Send sleep-confirm command to FW; set seq no; resp may be skipped when resp_ctrl=0. */ +static int nxpwifi_dnld_sleep_confirm_cmd(struct nxpwifi_adapter *adapter) +{ + int ret; + struct nxpwifi_private *priv; + struct nxpwifi_opt_sleep_confirm *sleep_cfm_buf = + (struct nxpwifi_opt_sleep_confirm *) + adapter->sleep_cfm->data; + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + + adapter->seq_num++; + sleep_cfm_buf->seq_num = + cpu_to_le16(HOST_SET_SEQ_NO_BSS_INFO + (adapter->seq_num, priv->bss_num, + priv->bss_type)); + + nxpwifi_dbg(adapter, CMD, + "cmd: DNLD_CMD: %#x, act %#x, len %d, seqno %#x\n", + le16_to_cpu(sleep_cfm_buf->command), + le16_to_cpu(sleep_cfm_buf->action), + le16_to_cpu(sleep_cfm_buf->size), + le16_to_cpu(sleep_cfm_buf->seq_num)); + nxpwifi_dbg_dump(adapter, CMD_D, "SLEEP_CFM buffer: ", sleep_cfm_buf, + le16_to_cpu(sleep_cfm_buf->size)); + + skb_push(adapter->sleep_cfm, adapter->intf_hdr_len); + ret = adapter->if_ops.host_to_card(adapter, NXPWIFI_TYPE_CMD, + adapter->sleep_cfm, NULL); + skb_pull(adapter->sleep_cfm, adapter->intf_hdr_len); + + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SLEEP_CFM: failed\n"); + adapter->dbg.num_cmd_sleep_cfm_host_to_card_failure++; + return ret; + } + + if (!le16_to_cpu(sleep_cfm_buf->resp_ctrl)) + /* Response is not needed for sleep confirm command */ + adapter->ps_state = PS_STATE_SLEEP; + else + adapter->ps_state = PS_STATE_SLEEP_CFM; + + if (!le16_to_cpu(sleep_cfm_buf->resp_ctrl) && + (test_bit(NXPWIFI_IS_HS_CONFIGURED, &adapter->work_flags) && + !adapter->sleep_period.period)) { + adapter->pm_wakeup_card_req = true; + nxpwifi_hs_activated_event(nxpwifi_get_priv + (adapter, NXPWIFI_BSS_ROLE_ANY), true); + } + + return ret; +} + +/* Allocate cmd pool and link all nodes to cmd_free_q (used/returned by cmds). */ +int nxpwifi_alloc_cmd_buffer(struct nxpwifi_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_array; + u32 i; + + /* Allocate and initialize struct cmd_ctrl_node */ + cmd_array = kzalloc_objs(struct cmd_ctrl_node, + NXPWIFI_NUM_OF_CMD_BUFFER, GFP_KERNEL); + if (!cmd_array) + return -ENOMEM; + + adapter->cmd_pool = cmd_array; + + /* Allocate and initialize command buffers */ + for (i = 0; i < NXPWIFI_NUM_OF_CMD_BUFFER; i++) { + cmd_array[i].skb = dev_alloc_skb(NXPWIFI_SIZE_OF_CMD_BUFFER); + if (!cmd_array[i].skb) + return -ENOMEM; + } + + for (i = 0; i < NXPWIFI_NUM_OF_CMD_BUFFER; i++) + nxpwifi_insert_cmd_to_free_q(adapter, &cmd_array[i]); + + return 0; +} + +/* Free cmd pool; release any remaining resp skbs. */ +void nxpwifi_free_cmd_buffer(struct nxpwifi_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_array; + u32 i; + + /* Need to check if cmd pool is allocated or not */ + if (!adapter->cmd_pool) { + nxpwifi_dbg(adapter, FATAL, + "info: FREE_CMD_BUF: cmd_pool is null\n"); + return; + } + + cmd_array = adapter->cmd_pool; + + /* Release shared memory buffers */ + for (i = 0; i < NXPWIFI_NUM_OF_CMD_BUFFER; i++) { + if (cmd_array[i].skb) { + nxpwifi_dbg(adapter, CMD, + "cmd: free cmd buffer %d\n", i); + dev_kfree_skb_any(cmd_array[i].skb); + } + if (!cmd_array[i].resp_skb) + continue; + + dev_kfree_skb_any(cmd_array[i].resp_skb); + } + /* Release struct cmd_ctrl_node */ + if (adapter->cmd_pool) { + nxpwifi_dbg(adapter, CMD, + "cmd: free cmd pool\n"); + kfree(adapter->cmd_pool); + adapter->cmd_pool = NULL; + } +} + +/* + * Handle FW event: select per-BSS priv, fill rxinfo, dispatch to STA/UAP handler, complete. + */ +int nxpwifi_process_event(struct nxpwifi_adapter *adapter) +{ + int ret, i; + struct nxpwifi_private *priv = + nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + struct sk_buff *skb = adapter->event_skb; + u32 eventcause; + struct nxpwifi_rxinfo *rx_info; + + if ((adapter->event_cause & EVENT_ID_MASK) == EVENT_RADAR_DETECTED) { + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (nxpwifi_is_11h_active(priv)) { + adapter->event_cause |= + ((priv->bss_num & 0xff) << 16) | + ((priv->bss_type & 0xff) << 24); + break; + } + } + } + + eventcause = adapter->event_cause; + + /* Save the last event to debug log */ + adapter->dbg.last_event_index = + (adapter->dbg.last_event_index + 1) % DBG_CMD_NUM; + adapter->dbg.last_event[adapter->dbg.last_event_index] = + (u16)eventcause; + + /* Get BSS number and corresponding priv */ + priv = nxpwifi_get_priv_by_id(adapter, EVENT_GET_BSS_NUM(eventcause), + EVENT_GET_BSS_TYPE(eventcause)); + if (!priv) + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + + /* Clear BSS_NO_BITS from event */ + eventcause &= EVENT_ID_MASK; + adapter->event_cause = eventcause; + + if (skb) { + rx_info = NXPWIFI_SKB_RXCB(skb); + memset(rx_info, 0, sizeof(*rx_info)); + rx_info->bss_num = priv->bss_num; + rx_info->bss_type = priv->bss_type; + nxpwifi_dbg_dump(adapter, EVT_D, "Event Buf:", + skb->data, skb->len); + } + + nxpwifi_dbg(adapter, EVENT, "EVENT: cause: %#x\n", eventcause); + + if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP) + ret = nxpwifi_process_uap_event(priv); + else + ret = nxpwifi_process_sta_event(priv); + + adapter->event_cause = 0; + adapter->event_skb = NULL; + adapter->if_ops.event_complete(adapter, skb); + + return ret; +} + +/* + * Prepare and queue a command: sanity checks, get node, init, fill, and enqueue/dispatch. + */ +int nxpwifi_send_cmd(struct nxpwifi_private *priv, u16 cmd_no, + u16 cmd_action, u32 cmd_oid, void *data_buf, bool sync) +{ + int ret; + struct nxpwifi_adapter *adapter = priv->adapter; + struct cmd_ctrl_node *cmd_node; + + if (!adapter) { + pr_err("PREP_CMD: adapter is NULL\n"); + return -EINVAL; + } + + if (test_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags)) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: device in suspended state\n"); + return -EPERM; + } + + if (test_bit(NXPWIFI_IS_HS_ENABLING, &adapter->work_flags) && + cmd_no != HOST_CMD_802_11_HS_CFG_ENH) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: host entering sleep state\n"); + return -EPERM; + } + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags)) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: card is removed\n"); + return -EPERM; + } + + if (test_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags)) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: FW is in bad state\n"); + return -EPERM; + } + + if (adapter->hw_status == NXPWIFI_HW_STATUS_RESET) { + if (cmd_no != HOST_CMD_FUNC_INIT) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: FW in reset state\n"); + return -EPERM; + } + } + + if (priv->adapter->hs_activated_manually && + cmd_no != HOST_CMD_802_11_HS_CFG_ENH) { + nxpwifi_cancel_hs(priv, NXPWIFI_ASYNC_CMD); + priv->adapter->hs_activated_manually = false; + } + + /* Get a new command node */ + cmd_node = nxpwifi_get_cmd_node(adapter); + + if (!cmd_node) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: no free cmd node\n"); + return -ENOMEM; + } + + /* Initialize the command node */ + nxpwifi_init_cmd_node(priv, cmd_node, cmd_no, data_buf, sync); + + if (!cmd_node->cmd_skb) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: no free cmd buf\n"); + return -ENOMEM; + } + + skb_put_zero(cmd_node->cmd_skb, sizeof(struct host_cmd_ds_command)); + + /* Prepare command */ + if (cmd_no) { + switch (cmd_no) { + case HOST_CMD_UAP_SYS_CONFIG: + case HOST_CMD_UAP_BSS_START: + case HOST_CMD_UAP_BSS_STOP: + case HOST_CMD_UAP_STA_DEAUTH: + case HOST_CMD_APCMD_SYS_RESET: + case HOST_CMD_APCMD_STA_LIST: + case HOST_CMD_CHAN_REPORT_REQUEST: + case HOST_CMD_ADD_NEW_STATION: + ret = nxpwifi_uap_prepare_cmd(priv, cmd_node, + cmd_action, cmd_oid); + break; + default: + ret = nxpwifi_sta_prepare_cmd(priv, cmd_node, + cmd_action, cmd_oid); + break; + } + } else { + ret = nxpwifi_cmd_host_cmd(priv, cmd_node); + cmd_node->cmd_flag |= CMD_F_HOSTCMD; + } + + /* Return error, since the command preparation failed */ + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: cmd %#x preparation failed\n", + cmd_no); + nxpwifi_insert_cmd_to_free_q(adapter, cmd_node); + return ret; + } + + /* Send command */ + if (cmd_no == HOST_CMD_802_11_SCAN || + cmd_no == HOST_CMD_802_11_SCAN_EXT) { + nxpwifi_queue_scan_cmd(priv, cmd_node); + } else { + nxpwifi_insert_cmd_to_pending_q(adapter, cmd_node); + nxpwifi_queue_work(adapter, &adapter->main_work); + if (cmd_node->wait_q_enabled) + ret = nxpwifi_wait_queue_complete(adapter, cmd_node); + } + + return ret; +} + +/* Queue command to cmd_pending_q; EXIT_PS and HS_ACTIVATE go to the head. */ +void +nxpwifi_insert_cmd_to_pending_q(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node) +{ + struct host_cmd_ds_command *host_cmd = NULL; + u16 command; + bool add_tail = true; + + host_cmd = (struct host_cmd_ds_command *)(cmd_node->cmd_skb->data); + if (!host_cmd) { + nxpwifi_dbg(adapter, ERROR, "QUEUE_CMD: host_cmd is NULL\n"); + return; + } + + command = le16_to_cpu(host_cmd->command); + + /* Exit_PS command needs to be queued in the header always. */ + if (command == HOST_CMD_802_11_PS_MODE_ENH) { + struct host_cmd_ds_802_11_ps_mode_enh *pm = + &host_cmd->params.psmode_enh; + if ((le16_to_cpu(pm->action) == DIS_PS) || + (le16_to_cpu(pm->action) == DIS_AUTO_PS)) { + if (adapter->ps_state != PS_STATE_AWAKE) + add_tail = false; + } + } + + /* Same with exit host sleep cmd, luckily that can't happen at the same time as EXIT_PS */ + if (command == HOST_CMD_802_11_HS_CFG_ENH) { + struct host_cmd_ds_802_11_hs_cfg_enh *hs_cfg = + &host_cmd->params.opt_hs_cfg; + + if (le16_to_cpu(hs_cfg->action) == HS_ACTIVATE) + add_tail = false; + } + + spin_lock_bh(&adapter->cmd_pending_q_lock); + if (add_tail) + list_add_tail(&cmd_node->list, &adapter->cmd_pending_q); + else + list_add(&cmd_node->list, &adapter->cmd_pending_q); + spin_unlock_bh(&adapter->cmd_pending_q_lock); + + atomic_inc(&adapter->cmd_pending); + nxpwifi_dbg(adapter, CMD, + "cmd: QUEUE_CMD: cmd=%#x, cmd_pending=%d\n", + command, atomic_read(&adapter->cmd_pending)); +} + +/* Dequeue next cmd and download to FW; if HS active (except HS_CFG), deactivate it. */ +int nxpwifi_exec_next_cmd(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + struct cmd_ctrl_node *cmd_node; + int ret = 0; + struct host_cmd_ds_command *host_cmd; + + /* Check if already in processing */ + if (adapter->curr_cmd) { + nxpwifi_dbg(adapter, FATAL, + "EXEC_NEXT_CMD: cmd in processing\n"); + return -EBUSY; + } + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + /* Check if any command is pending */ + spin_lock_bh(&adapter->cmd_pending_q_lock); + if (list_empty(&adapter->cmd_pending_q)) { + spin_unlock_bh(&adapter->cmd_pending_q_lock); + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + return -ENODATA; + } + cmd_node = list_first_entry(&adapter->cmd_pending_q, + struct cmd_ctrl_node, list); + + host_cmd = (struct host_cmd_ds_command *)(cmd_node->cmd_skb->data); + priv = cmd_node->priv; + + if (adapter->ps_state != PS_STATE_AWAKE) { + nxpwifi_dbg(adapter, ERROR, + "%s: cannot send cmd in sleep state,\t" + "this should not happen\n", __func__); + spin_unlock_bh(&adapter->cmd_pending_q_lock); + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + return ret; + } + + list_del(&cmd_node->list); + spin_unlock_bh(&adapter->cmd_pending_q_lock); + + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + ret = nxpwifi_dnld_cmd_to_fw(priv, cmd_node); + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + /* + * Any command sent to the firmware when host is in sleep + * mode should de-configure host sleep. We should skip the + * host sleep configuration command itself though + */ + if (priv && host_cmd->command != + cpu_to_le16(HOST_CMD_802_11_HS_CFG_ENH)) { + if (adapter->hs_activated) { + clear_bit(NXPWIFI_IS_HS_CONFIGURED, + &adapter->work_flags); + nxpwifi_hs_activated_event(priv, false); + } + } + + return ret; +} + +static void +nxpwifi_process_cmdresp_error(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_ps_mode_enh *pm; + + nxpwifi_dbg(adapter, ERROR, + "CMD_RESP: cmd %#x error, result=%#x\n", + resp->command, resp->result); + + if (adapter->curr_cmd->wait_q_enabled) + adapter->cmd_wait_q.status = -1; + + switch (le16_to_cpu(resp->command)) { + case HOST_CMD_802_11_PS_MODE_ENH: + pm = &resp->params.psmode_enh; + nxpwifi_dbg(adapter, ERROR, + "PS_MODE_ENH cmd failed: result=0x%x action=0x%X\n", + resp->result, le16_to_cpu(pm->action)); + break; + case HOST_CMD_802_11_SCAN: + case HOST_CMD_802_11_SCAN_EXT: + nxpwifi_cancel_scan(adapter); + break; + + case HOST_CMD_MAC_CONTROL: + break; + + case HOST_CMD_SDIO_SP_RX_AGGR_CFG: + nxpwifi_dbg(adapter, MSG, + "SDIO RX single-port aggregation Not support\n"); + break; + + default: + break; + } + /* Handling errors here */ + nxpwifi_recycle_cmd_node(adapter, adapter->curr_cmd); + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->curr_cmd = NULL; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); +} + +/* Handle command response: validate, cancel timer, dispatch, set status, recycle node. */ +int nxpwifi_process_cmdresp(struct nxpwifi_adapter *adapter) +{ + struct host_cmd_ds_command *resp; + struct nxpwifi_private *priv = + nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + int ret = 0; + u16 orig_cmdresp_no; + u16 cmdresp_no; + u16 cmdresp_result; + + if (!adapter->curr_cmd || !adapter->curr_cmd->resp_skb) { + resp = (struct host_cmd_ds_command *)adapter->upld_buf; + nxpwifi_dbg(adapter, ERROR, + "CMD_RESP: NULL curr_cmd, %#x\n", + le16_to_cpu(resp->command)); + return -EINVAL; + } + + resp = (struct host_cmd_ds_command *)adapter->curr_cmd->resp_skb->data; + orig_cmdresp_no = le16_to_cpu(resp->command); + cmdresp_no = (orig_cmdresp_no & HOST_CMD_ID_MASK); + + if (adapter->curr_cmd->cmd_no != cmdresp_no) { + nxpwifi_dbg(adapter, ERROR, + "cmdresp error: cmd=0x%x cmd_resp=0x%x\n", + adapter->curr_cmd->cmd_no, cmdresp_no); + return -EINVAL; + } + /* Now we got response from FW, cancel the command timer */ + timer_delete_sync(&adapter->cmd_timer); + clear_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags); + + if (adapter->curr_cmd->cmd_flag & CMD_F_HOSTCMD) { + /* Copy original response back to response buffer */ + struct nxpwifi_ds_misc_cmd *hostcmd; + u16 size = le16_to_cpu(resp->size); + + nxpwifi_dbg(adapter, INFO, + "info: host cmd resp size = %d\n", size); + size = min_t(u16, size, NXPWIFI_SIZE_OF_CMD_BUFFER); + if (adapter->curr_cmd->data_buf) { + hostcmd = adapter->curr_cmd->data_buf; + hostcmd->len = size; + memcpy(hostcmd->cmd, resp, size); + } + } + + /* Get BSS number and corresponding priv */ + priv = nxpwifi_get_priv_by_id + (adapter, HOST_GET_BSS_NO(le16_to_cpu(resp->seq_num)), + HOST_GET_BSS_TYPE(le16_to_cpu(resp->seq_num))); + if (!priv) + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + /* Clear RET_BIT from HOST */ + resp->command = cpu_to_le16(orig_cmdresp_no & HOST_CMD_ID_MASK); + + cmdresp_no = le16_to_cpu(resp->command); + cmdresp_result = le16_to_cpu(resp->result); + + /* Save the last command response to debug log */ + adapter->dbg.last_cmd_resp_index = + (adapter->dbg.last_cmd_resp_index + 1) % DBG_CMD_NUM; + adapter->dbg.last_cmd_resp_id[adapter->dbg.last_cmd_resp_index] = + orig_cmdresp_no; + + nxpwifi_dbg(adapter, CMD, + "cmd: CMD_RESP: 0x%x, result %d, len %d, seqno 0x%x\n", + orig_cmdresp_no, cmdresp_result, + le16_to_cpu(resp->size), le16_to_cpu(resp->seq_num)); + nxpwifi_dbg_dump(adapter, CMD_D, "CMD_RESP buffer:", resp, + le16_to_cpu(resp->size)); + + if (!(orig_cmdresp_no & HOST_RET_BIT)) { + nxpwifi_dbg(adapter, ERROR, "CMD_RESP: invalid cmd resp\n"); + if (adapter->curr_cmd->wait_q_enabled) + adapter->cmd_wait_q.status = -1; + + nxpwifi_recycle_cmd_node(adapter, adapter->curr_cmd); + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->curr_cmd = NULL; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + return -EINVAL; + } + + if (adapter->curr_cmd->cmd_flag & CMD_F_HOSTCMD) { + adapter->curr_cmd->cmd_flag &= ~CMD_F_HOSTCMD; + if (cmdresp_result == HOST_RESULT_OK && + cmdresp_no == HOST_CMD_802_11_HS_CFG_ENH) + ret = nxpwifi_ret_802_11_hs_cfg(priv, resp); + } else { + if (resp->result != HOST_RESULT_OK) { + nxpwifi_process_cmdresp_error(priv, resp); + return -EFAULT; + } + if (adapter->curr_cmd->cmd_resp) { + void *data_buf = adapter->curr_cmd->data_buf; + + ret = adapter->curr_cmd->cmd_resp(priv, resp, + cmdresp_no, + data_buf); + } + } + + if (adapter->curr_cmd) { + if (adapter->curr_cmd->wait_q_enabled) + adapter->cmd_wait_q.status = ret; + + nxpwifi_recycle_cmd_node(adapter, adapter->curr_cmd); + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->curr_cmd = NULL; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + } + + return ret; +} + +void nxpwifi_process_assoc_resp(struct nxpwifi_adapter *adapter) +{ + struct cfg80211_rx_assoc_resp_data assoc_resp = { + .uapsd_queues = -1, + }; + struct nxpwifi_private *priv = + nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA); + + if (priv->assoc_rsp_size) { + assoc_resp.links[0].bss = priv->req_bss; + assoc_resp.buf = priv->assoc_rsp_buf; + assoc_resp.len = priv->assoc_rsp_size; + cfg80211_rx_assoc_resp(priv->netdev, + &assoc_resp); + priv->assoc_rsp_size = 0; + } +} + +/* + * Command timeout handler: mark timed out, cancel pending IOCTL, dump/reset device if provided. + */ +void +nxpwifi_cmd_timeout_func(struct timer_list *t) +{ + struct nxpwifi_adapter *adapter = timer_container_of(adapter, t, cmd_timer); + struct cmd_ctrl_node *cmd_node; + + set_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags); + if (!adapter->curr_cmd) { + nxpwifi_dbg(adapter, ERROR, + "cmd: empty curr_cmd\n"); + return; + } + cmd_node = adapter->curr_cmd; + if (cmd_node) { + adapter->dbg.timeout_cmd_id = + adapter->dbg.last_cmd_id[adapter->dbg.last_cmd_index]; + adapter->dbg.timeout_cmd_act = + adapter->dbg.last_cmd_act[adapter->dbg.last_cmd_index]; + nxpwifi_dbg(adapter, MSG, + "%s: Timeout cmd id = %#x, act = %#x\n", __func__, + adapter->dbg.timeout_cmd_id, + adapter->dbg.timeout_cmd_act); + + nxpwifi_dbg(adapter, MSG, + "num_data_h2c_failure = %d\n", + adapter->dbg.num_tx_host_to_card_failure); + nxpwifi_dbg(adapter, MSG, + "num_cmd_h2c_failure = %d\n", + adapter->dbg.num_cmd_host_to_card_failure); + + nxpwifi_dbg(adapter, MSG, + "is_cmd_timedout = %d\n", + test_bit(NXPWIFI_IS_CMD_TIMEDOUT, + &adapter->work_flags)); + nxpwifi_dbg(adapter, MSG, + "num_tx_timeout = %d\n", + adapter->dbg.num_tx_timeout); + + nxpwifi_dbg(adapter, MSG, + "last_cmd_index = %d\n", + adapter->dbg.last_cmd_index); + nxpwifi_dbg(adapter, MSG, + "last_cmd_id: %*ph\n", + (int)sizeof(adapter->dbg.last_cmd_id), + adapter->dbg.last_cmd_id); + nxpwifi_dbg(adapter, MSG, + "last_cmd_act: %*ph\n", + (int)sizeof(adapter->dbg.last_cmd_act), + adapter->dbg.last_cmd_act); + + nxpwifi_dbg(adapter, MSG, + "last_cmd_resp_index = %d\n", + adapter->dbg.last_cmd_resp_index); + nxpwifi_dbg(adapter, MSG, + "last_cmd_resp_id: %*ph\n", + (int)sizeof(adapter->dbg.last_cmd_resp_id), + adapter->dbg.last_cmd_resp_id); + + nxpwifi_dbg(adapter, MSG, + "last_event_index = %d\n", + adapter->dbg.last_event_index); + nxpwifi_dbg(adapter, MSG, + "last_event: %*ph\n", + (int)sizeof(adapter->dbg.last_event), + adapter->dbg.last_event); + + nxpwifi_dbg(adapter, MSG, + "data_sent=%d cmd_sent=%d\n", + adapter->data_sent, adapter->cmd_sent); + + nxpwifi_dbg(adapter, MSG, + "ps_mode=%d ps_state=%d\n", + adapter->ps_mode, adapter->ps_state); + + if (cmd_node->wait_q_enabled) { + adapter->cmd_wait_q.status = -ETIMEDOUT; + nxpwifi_cancel_pending_ioctl(adapter); + } + } + + if (adapter->if_ops.device_dump) + adapter->if_ops.device_dump(adapter); + + if (adapter->if_ops.card_reset) + adapter->if_ops.card_reset(adapter); +} + +void +nxpwifi_cancel_pending_scan_cmd(struct nxpwifi_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_node = NULL, *tmp_node; + + /* Cancel all pending scan command */ + spin_lock_bh(&adapter->scan_pending_q_lock); + list_for_each_entry_safe(cmd_node, tmp_node, + &adapter->scan_pending_q, list) { + list_del(&cmd_node->list); + cmd_node->wait_q_enabled = false; + nxpwifi_insert_cmd_to_free_q(adapter, cmd_node); + } + spin_unlock_bh(&adapter->scan_pending_q_lock); +} + +/* + * Cancel current cmd (if waiting), all pending cmds, and pending scan cmds; complete with error. + */ +void +nxpwifi_cancel_all_pending_cmd(struct nxpwifi_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_node = NULL, *tmp_node; + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + /* Cancel current cmd */ + if (adapter->curr_cmd && adapter->curr_cmd->wait_q_enabled) { + adapter->cmd_wait_q.status = -1; + nxpwifi_complete_cmd(adapter, adapter->curr_cmd); + adapter->curr_cmd->wait_q_enabled = false; + /* no recycle probably wait for response */ + } + /* Cancel all pending command */ + spin_lock_bh(&adapter->cmd_pending_q_lock); + list_for_each_entry_safe(cmd_node, tmp_node, + &adapter->cmd_pending_q, list) { + list_del(&cmd_node->list); + + if (cmd_node->wait_q_enabled) + adapter->cmd_wait_q.status = -1; + nxpwifi_recycle_cmd_node(adapter, cmd_node); + } + spin_unlock_bh(&adapter->cmd_pending_q_lock); + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + nxpwifi_cancel_scan(adapter); +} + +/* Cancel current/pending commands for the pending IOCTL; also cancel scan cmds. */ +static void +nxpwifi_cancel_pending_ioctl(struct nxpwifi_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_node = NULL; + + if (adapter->curr_cmd && + adapter->curr_cmd->wait_q_enabled) { + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + cmd_node = adapter->curr_cmd; + /* + * Be careful when setting curr_cmd = NULL: + * nxpwifi_process_cmdresp expects a non-NULL pointer. + * This is safe here because only cmd_timeout calls this path + * and no response is expected at that point. + */ + adapter->curr_cmd = NULL; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + nxpwifi_recycle_cmd_node(adapter, cmd_node); + } + + nxpwifi_cancel_scan(adapter); +} + +/* If no cmd/event/tx is pending, send sleep-confirm to FW; otherwise defer. */ +void +nxpwifi_check_ps_cond(struct nxpwifi_adapter *adapter) +{ + if (!adapter->cmd_sent && !atomic_read(&adapter->tx_hw_pending) && + !adapter->curr_cmd && !IS_CARD_RX_RCVD(adapter)) + nxpwifi_dnld_sleep_confirm_cmd(adapter); + else + nxpwifi_dbg(adapter, CMD, + "cmd: Delay Sleep Confirm (%s%s%s%s)\n", + (adapter->cmd_sent) ? "D" : "", + atomic_read(&adapter->tx_hw_pending) ? "T" : "", + (adapter->curr_cmd) ? "C" : "", + (IS_CARD_RX_RCVD(adapter)) ? "R" : ""); +} + +/* Generate HS activated/deactivated event for userspace; update flags and wake waiters. */ +void +nxpwifi_hs_activated_event(struct nxpwifi_private *priv, u8 activated) +{ + if (activated) { + if (test_bit(NXPWIFI_IS_HS_CONFIGURED, + &priv->adapter->work_flags)) { + priv->adapter->hs_activated = true; + nxpwifi_update_rxreor_flags(priv->adapter, + RXREOR_FORCE_NO_DROP); + nxpwifi_dbg(priv->adapter, EVENT, + "event: hs_activated\n"); + priv->adapter->hs_activate_wait_q_woken = true; + wake_up_interruptible(&priv->adapter->hs_activate_wait_q); + } else { + nxpwifi_dbg(priv->adapter, EVENT, + "event: HS not configured\n"); + } + } else { + nxpwifi_dbg(priv->adapter, EVENT, + "event: hs_deactivated\n"); + priv->adapter->hs_activated = false; + } +} + +/* Handle HS_CFG response: update HS configured/activated flags and emit HS events. */ +int nxpwifi_ret_802_11_hs_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_hs_cfg_enh *phs_cfg = + &resp->params.opt_hs_cfg; + u32 conditions = le32_to_cpu(phs_cfg->params.hs_config.conditions); + + if (phs_cfg->action == cpu_to_le16(HS_ACTIVATE)) { + nxpwifi_hs_activated_event(priv, true); + goto done; + } else { + nxpwifi_dbg(adapter, CMD, + "cmd: CMD_RESP: HS_CFG cmd reply\t" + " result=%#x, conditions=0x%x gpio=0x%x gap=0x%x\n", + resp->result, conditions, + phs_cfg->params.hs_config.gpio, + phs_cfg->params.hs_config.gap); + } + if (conditions != HS_CFG_CANCEL) { + set_bit(NXPWIFI_IS_HS_CONFIGURED, &adapter->work_flags); + } else { + clear_bit(NXPWIFI_IS_HS_CONFIGURED, &adapter->work_flags); + if (adapter->hs_activated) + nxpwifi_hs_activated_event(priv, false); + } + +done: + return 0; +} + +/* On power-up interrupt, wake device and cancel HS if armed; clear flags and notify. */ +void +nxpwifi_process_hs_config(struct nxpwifi_adapter *adapter) +{ + nxpwifi_dbg(adapter, INFO, + "info: %s: auto cancelling host sleep\t" + "since there is interrupt from the firmware\n", + __func__); + + adapter->if_ops.wakeup(adapter); + + if (adapter->hs_activated_manually) { + nxpwifi_cancel_hs(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY), + NXPWIFI_ASYNC_CMD); + adapter->hs_activated_manually = false; + } + + adapter->hs_activated = false; + clear_bit(NXPWIFI_IS_HS_CONFIGURED, &adapter->work_flags); + clear_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags); + nxpwifi_hs_activated_event(nxpwifi_get_priv(adapter, + NXPWIFI_BSS_ROLE_ANY), + false); +} +EXPORT_SYMBOL_GPL(nxpwifi_process_hs_config); + +/* Handle sleep-confirm response; set ps_state and hs activation accordingly. */ +void +nxpwifi_process_sleep_confirm_resp(struct nxpwifi_adapter *adapter, + u8 *pbuf, u32 upld_len) +{ + struct host_cmd_ds_command *cmd = (struct host_cmd_ds_command *)pbuf; + u16 result = le16_to_cpu(cmd->result); + u16 command = le16_to_cpu(cmd->command); + u16 seq_num = le16_to_cpu(cmd->seq_num); + + if (!upld_len) { + nxpwifi_dbg(adapter, ERROR, + "%s: cmd size is 0\n", __func__); + return; + } + + nxpwifi_dbg(adapter, CMD, + "cmd: CMD_RESP: 0x%x, result %d, len %d, seqno 0x%x\n", + command, result, le16_to_cpu(cmd->size), seq_num); + + /* Update sequence number */ + seq_num = HOST_GET_SEQ_NO(seq_num); + /* Clear RET_BIT from HOST */ + command &= HOST_CMD_ID_MASK; + + if (command != HOST_CMD_802_11_PS_MODE_ENH) { + nxpwifi_dbg(adapter, ERROR, + "%s: rcvd unexpected resp for cmd %#x, result = %x\n", + __func__, command, result); + return; + } + + if (result) { + nxpwifi_dbg(adapter, ERROR, + "%s: sleep confirm cmd failed\n", + __func__); + adapter->pm_wakeup_card_req = false; + adapter->ps_state = PS_STATE_AWAKE; + return; + } + adapter->pm_wakeup_card_req = true; + if (test_bit(NXPWIFI_IS_HS_CONFIGURED, &adapter->work_flags)) + nxpwifi_hs_activated_event(nxpwifi_get_priv + (adapter, NXPWIFI_BSS_ROLE_ANY), + true); + adapter->ps_state = PS_STATE_SLEEP; + cmd->command = cpu_to_le16(command); + cmd->seq_num = cpu_to_le16(seq_num); +} +EXPORT_SYMBOL_GPL(nxpwifi_process_sleep_confirm_resp); + +int nxpwifi_mgmt_frame_reg(struct nxpwifi_private *priv, u32 mask) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_MGMT_FRAME_REG, HOST_ACT_GEN_SET, + 0, &mask, false); +} + +int nxpwifi_set_uap_sys_cfg(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *cfg) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_UAP_SYS_CONFIG, HOST_ACT_GEN_SET, + UAP_BSS_PARAMS_I, cfg, false); +} + +int nxpwifi_set_rts(struct nxpwifi_private *priv, u32 rts_thr) +{ + if (rts_thr < NXPWIFI_RTS_THRESHOLD_MIN || rts_thr > NXPWIFI_RTS_THRESHOLD_MAX) + rts_thr = NXPWIFI_RTS_THRESHOLD_MAX; + + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_SNMP_MIB, + HOST_ACT_GEN_SET, RTS_THRESH_I, &rts_thr, true); +} + +int nxpwifi_set_frag(struct nxpwifi_private *priv, u32 frag_thr) +{ + if (frag_thr < NXPWIFI_FRAG_THRESHOLD_MIN || + frag_thr > NXPWIFI_FRAG_THRESHOLD_MAX) + frag_thr = NXPWIFI_FRAG_THRESHOLD_MAX; + + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_SNMP_MIB, + HOST_ACT_GEN_SET, FRAG_THRESH_I, &frag_thr, + true); +} + +int nxpwifi_set_bss_mode(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_SET_BSS_MODE, HOST_ACT_GEN_SET, + 0, NULL, true); +} + +int nxpwifi_config_monitor_mode(struct nxpwifi_private *priv, + struct nxpwifi_802_11_net_monitor *cfg) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_NET_MONITOR, + HOST_ACT_GEN_SET, 0, cfg, true); +} + +int nxpwifi_get_tx_pwr(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_RF_TX_PWR, HOST_ACT_GEN_GET, 0, + NULL, true); +} + +int nxpwifi_apply_regdomain(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11D_DOMAIN_INFO, HOST_ACT_GEN_SET, + 0, NULL, false); +} + +int nxpwifi_get_rssi_info(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_RSSI_INFO, HOST_ACT_GEN_GET, 0, + NULL, true); +} + +int nxpwifi_get_802_11_snmp_mib(struct nxpwifi_private *priv, u16 oid, void *value) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_SNMP_MIB, HOST_ACT_GEN_GET, + oid, value, true); +} + +int nxpwifi_set_rf_antenna(struct nxpwifi_private *priv, void *antcfg) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_RF_ANTENNA, HOST_ACT_GEN_SET, 0, antcfg, + true); +} + +int nxpwifi_get_rf_antenna(struct nxpwifi_private *priv, u32 *tx_ant, u32 *rx_ant) +{ + int ret; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_RF_ANTENNA, HOST_ACT_GEN_GET, 0, NULL, true); + + if (!ret) { + *tx_ant = priv->tx_ant; + *rx_ant = priv->rx_ant; + } + + return ret; +} + +int nxpwifi_ap_stop_bss(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_UAP_BSS_STOP, HOST_ACT_GEN_SET, + 0, NULL, true); +} + +int nxpwifi_ap_sys_reset(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_APCMD_SYS_RESET, + HOST_ACT_GEN_SET, 0, NULL, true); +} + +int nxpwifi_ap_get_sta_list(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_APCMD_STA_LIST, HOST_ACT_GEN_GET, + 0, NULL, true); +} + +int nxpwifi_set_tx_rate(struct nxpwifi_private *priv, void *bitmap_rates) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_TX_RATE_CFG, HOST_ACT_GEN_SET, 0, + bitmap_rates, true); +} + +int nxpwifi_802_11_subscribe_event(struct nxpwifi_private *priv, + struct nxpwifi_ds_misc_subsc_evt *subsc_evt) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_SUBSCRIBE_EVENT, 0, 0, subsc_evt, + true); +} + +int nxpwifi_uap_sta_deauth(struct nxpwifi_private *priv, u8 *mac) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_UAP_STA_DEAUTH, HOST_ACT_GEN_SET, 0, mac, true); +} + +int nxpwifi_bg_scan_config(struct nxpwifi_private *priv, void *bg_scan_cfg) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_BG_SCAN_CONFIG, HOST_ACT_GEN_SET, + 0, bg_scan_cfg, true); +} + +int nxpwifi_mef_cfg(struct nxpwifi_private *priv, void *mef_cfg) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_MEF_CFG, HOST_ACT_GEN_SET, 0, mef_cfg, + true); +} + +int nxpwifi_coalesce_cfg(struct nxpwifi_private *priv, void *coalesce_cfg) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_COALESCE_CFG, HOST_ACT_GEN_SET, 0, + coalesce_cfg, true); +} + +int nxpwifi_add_new_station(struct nxpwifi_private *priv, void *add_sta) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_ADD_NEW_STATION, HOST_ACT_ADD_STA, 0, + add_sta, true); +} + +int nxpwifi_hostcmd(struct nxpwifi_private *priv, struct nxpwifi_ds_misc_cmd *hostcmd) +{ + return nxpwifi_send_cmd(priv, 0, 0, 0, hostcmd, true); +} + +int nxpwifi_chan_report_request(struct nxpwifi_private *priv, void *radar_params) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_CHAN_REPORT_REQUEST, HOST_ACT_GEN_SET, 0, + radar_params, true); +} diff --git a/drivers/net/wireless/nxp/nxpwifi/cmdevt.h b/drivers/net/wireless/nxp/nxpwifi/cmdevt.h new file mode 100644 index 000000000000..6112760b697e --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/cmdevt.h @@ -0,0 +1,122 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * nxpwifi: commands and events + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_CMD_EVT_H_ +#define _NXPWIFI_CMD_EVT_H_ + +struct nxpwifi_cmd_entry { + u16 cmd_no; + int (*prepare_cmd)(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type); + int (*cmd_resp)(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf); +}; + +struct nxpwifi_evt_entry { + u32 event_cause; + int (*event_handler)(struct nxpwifi_private *priv); +}; + +static inline int +nxpwifi_cmd_fill_head_only(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(cmd_no); + cmd->size = cpu_to_le16(S_DS_GEN); + + return 0; +} + +int nxpwifi_send_cmd(struct nxpwifi_private *priv, u16 cmd_no, + u16 cmd_action, u32 cmd_oid, void *data_buf, bool sync); +int nxpwifi_sta_prepare_cmd(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node, + u16 cmd_action, u32 cmd_oid); +int nxpwifi_sta_init_cmd(struct nxpwifi_private *priv, u8 first_sta, bool init); +int nxpwifi_uap_prepare_cmd(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node, + u16 cmd_action, u32 type); +int nxpwifi_set_secure_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_config, + struct cfg80211_ap_settings *params); +void nxpwifi_set_ht_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void nxpwifi_set_vht_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void nxpwifi_set_tpc_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void nxpwifi_set_uap_rates(struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void nxpwifi_set_vht_width(struct nxpwifi_private *priv, + enum nl80211_chan_width width, + bool ap_11ac_disable); +bool nxpwifi_check_11ax_capability(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +int nxpwifi_set_11ax_status(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void nxpwifi_set_sys_config_invalid_data(struct nxpwifi_uap_bss_param *config); +void nxpwifi_set_wmm_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void nxpwifi_config_uap_11d(struct nxpwifi_private *priv, + struct cfg80211_beacon_data *beacon_data); +void nxpwifi_uap_set_channel(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_chan_def chandef); +int nxpwifi_config_start_uap(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg); +int nxpwifi_process_event(struct nxpwifi_adapter *adapter); +int nxpwifi_process_sta_event(struct nxpwifi_private *priv); +int nxpwifi_process_uap_event(struct nxpwifi_private *priv); +void nxpwifi_reset_connect_state(struct nxpwifi_private *priv, u16 reason, + bool from_ap); +void nxpwifi_process_multi_chan_event(struct nxpwifi_private *priv, + struct sk_buff *event_skb); +void nxpwifi_process_tx_pause_event(struct nxpwifi_private *priv, + struct sk_buff *event); +void nxpwifi_bt_coex_wlan_param_update_event(struct nxpwifi_private *priv, + struct sk_buff *event_skb); +int nxpwifi_mgmt_frame_reg(struct nxpwifi_private *priv, u32 mask); +int nxpwifi_set_uap_sys_cfg(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *cfg); +int nxpwifi_set_rts(struct nxpwifi_private *priv, u32 rts_thr); +int nxpwifi_set_frag(struct nxpwifi_private *priv, u32 frag_thr); +int nxpwifi_set_bss_mode(struct nxpwifi_private *priv); +int nxpwifi_config_monitor_mode(struct nxpwifi_private *priv, + struct nxpwifi_802_11_net_monitor *cfg); +int nxpwifi_apply_regdomain(struct nxpwifi_private *priv); +int nxpwifi_get_tx_pwr(struct nxpwifi_private *priv); +int nxpwifi_get_rssi_info(struct nxpwifi_private *priv); +int nxpwifi_get_802_11_snmp_mib(struct nxpwifi_private *priv, u16 oid, void *value); +int nxpwifi_set_rf_antenna(struct nxpwifi_private *priv, void *antcfg); +int nxpwifi_get_rf_antenna(struct nxpwifi_private *priv, u32 *tx_ant, u32 *rx_ant); +int nxpwifi_ap_stop_bss(struct nxpwifi_private *priv); +int nxpwifi_ap_sys_reset(struct nxpwifi_private *priv); +int nxpwifi_cfg80211_deinit_p2p(struct nxpwifi_private *priv); +int nxpwifi_ap_get_sta_list(struct nxpwifi_private *priv); +int nxpwifi_set_tx_rate(struct nxpwifi_private *priv, void *bitmap_rates); +int nxpwifi_802_11_subscribe_event(struct nxpwifi_private *priv, + struct nxpwifi_ds_misc_subsc_evt *subsc_evt); +int nxpwifi_uap_sta_deauth(struct nxpwifi_private *priv, u8 *mac); +int nxpwifi_bg_scan_config(struct nxpwifi_private *priv, void *bg_scan_cfg); +int nxpwifi_mef_cfg(struct nxpwifi_private *priv, void *mef_cfg); +int nxpwifi_coalesce_cfg(struct nxpwifi_private *priv, void *coalesce_cfg); +int nxpwifi_add_new_station(struct nxpwifi_private *priv, void *add_sta); +int nxpwifi_hostcmd(struct nxpwifi_private *priv, struct nxpwifi_ds_misc_cmd *hostcmd); +int nxpwifi_chan_report_request(struct nxpwifi_private *priv, void *radar_params); +#endif /* !_NXPWIFI_CMD_EVT_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/debugfs.c b/drivers/net/wireless/nxp/nxpwifi/debugfs.c new file mode 100644 index 000000000000..ccaf0eae37e3 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/debugfs.c @@ -0,0 +1,1094 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: debugfs + * + * Copyright 2011-2024 NXP + */ + +#include + +#include "main.h" +#include "cmdevt.h" +#include "11n.h" + +static struct dentry *nxpwifi_dfs_dir; + +static char *bss_modes[] = { + "UNSPECIFIED", + "ADHOC", + "STATION", + "AP", + "AP_VLAN", + "WDS", + "MONITOR", + "MESH_POINT", + "P2P_CLIENT", + "P2P_GO", + "P2P_DEVICE", +}; + +/* + * debugfs "info" read handler: dump driver name/version, interface, BSS mode, + * link state, MAC, counters; STA adds SSID/BSSID/channel/country/region and + * multicast list. + */ +static ssize_t +nxpwifi_info_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + struct net_device *netdev = priv->netdev; + struct netdev_hw_addr *ha; + struct netdev_queue *txq; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page, fmt[64]; + struct nxpwifi_bss_info info; + ssize_t ret; + int i = 0; + + if (!p) + return -ENOMEM; + + memset(&info, 0, sizeof(info)); + ret = nxpwifi_get_bss_info(priv, &info); + if (ret) + goto free_and_exit; + + nxpwifi_drv_get_driver_version(priv->adapter, fmt, sizeof(fmt) - 1); + + nxpwifi_get_ver_ext(priv, 0); + + p += sprintf(p, "driver_name = "); + p += sprintf(p, "\"nxpwifi\"\n"); + p += sprintf(p, "driver_version = %s", fmt); + p += sprintf(p, "\nverext = %s", priv->version_str); + p += sprintf(p, "\ninterface_name=\"%s\"\n", netdev->name); + + if (info.bss_mode >= ARRAY_SIZE(bss_modes)) + p += sprintf(p, "bss_mode=\"%d\"\n", info.bss_mode); + else + p += sprintf(p, "bss_mode=\"%s\"\n", bss_modes[info.bss_mode]); + + p += sprintf(p, "media_state=\"%s\"\n", + (!priv->media_connected ? "Disconnected" : "Connected")); + p += sprintf(p, "mac_address=\"%pM\"\n", netdev->dev_addr); + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) { + p += sprintf(p, "multicast_count=\"%d\"\n", + netdev_mc_count(netdev)); + p += sprintf(p, "essid=\"%.*s\"\n", info.ssid.ssid_len, + info.ssid.ssid); + p += sprintf(p, "bssid=\"%pM\"\n", info.bssid); + p += sprintf(p, "channel=\"%d\"\n", (int)info.bss_chan); + p += sprintf(p, "country_code = \"%s\"\n", info.country_code); + p += sprintf(p, "region_code=\"0x%x\"\n", + priv->adapter->region_code); + + netdev_for_each_mc_addr(ha, netdev) + p += sprintf(p, "multicast_address[%d]=\"%pM\"\n", + i++, ha->addr); + } + + p += sprintf(p, "num_tx_bytes = %lu\n", priv->stats.tx_bytes); + p += sprintf(p, "num_rx_bytes = %lu\n", priv->stats.rx_bytes); + p += sprintf(p, "num_tx_pkts = %lu\n", priv->stats.tx_packets); + p += sprintf(p, "num_rx_pkts = %lu\n", priv->stats.rx_packets); + p += sprintf(p, "num_tx_pkts_dropped = %lu\n", priv->stats.tx_dropped); + p += sprintf(p, "num_rx_pkts_dropped = %lu\n", priv->stats.rx_dropped); + p += sprintf(p, "num_tx_pkts_err = %lu\n", priv->stats.tx_errors); + p += sprintf(p, "num_rx_pkts_err = %lu\n", priv->stats.rx_errors); + p += sprintf(p, "carrier %s\n", ((netif_carrier_ok(priv->netdev)) + ? "on" : "off")); + p += sprintf(p, "tx queue"); + for (i = 0; i < netdev->num_tx_queues; i++) { + txq = netdev_get_tx_queue(netdev, i); + p += sprintf(p, " %d:%s", i, netif_tx_queue_stopped(txq) ? + "stopped" : "started"); + } + p += sprintf(p, "\n"); + + ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page, + (unsigned long)p - page); + +free_and_exit: + free_page(page); + return ret; +} + +/* + * debugfs "getlog" read handler: dump firmware/802.11 counters (retry, RTS/ACK, dup, + * frag, mcast, FCS, beacon stats). + */ +static ssize_t +nxpwifi_getlog_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + ssize_t ret; + struct nxpwifi_ds_get_stats stats; + + if (!p) + return -ENOMEM; + + memset(&stats, 0, sizeof(stats)); + ret = nxpwifi_get_stats_info(priv, &stats); + if (ret) + goto free_and_exit; + + p += sprintf(p, "\n" + "mcasttxframe %u\n" + "failed %u\n" + "retry %u\n" + "multiretry %u\n" + "framedup %u\n" + "rtssuccess %u\n" + "rtsfailure %u\n" + "ackfailure %u\n" + "rxfrag %u\n" + "mcastrxframe %u\n" + "fcserror %u\n" + "txframe %u\n" + "wepicverrcnt-1 %u\n" + "wepicverrcnt-2 %u\n" + "wepicverrcnt-3 %u\n" + "wepicverrcnt-4 %u\n" + "bcn_rcv_cnt %u\n" + "bcn_miss_cnt %u\n", + stats.mcast_tx_frame, + stats.failed, + stats.retry, + stats.multi_retry, + stats.frame_dup, + stats.rts_success, + stats.rts_failure, + stats.ack_failure, + stats.rx_frag, + stats.mcast_rx_frame, + stats.fcs_error, + stats.tx_frame, + stats.wep_icv_error[0], + stats.wep_icv_error[1], + stats.wep_icv_error[2], + stats.wep_icv_error[3], + stats.bcn_rcv_cnt, + stats.bcn_miss_cnt); + + ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page, + (unsigned long)p - page); + +free_and_exit: + free_page(page); + return ret; +} + +/* + * debugfs "histogram" read handler: report sample count and per-rate/SNR/noise + * floor/signal strength histograms. + */ +static ssize_t +nxpwifi_histogram_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + ssize_t ret; + struct nxpwifi_histogram_data *phist_data; + int i, value; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + + if (!p) + return -ENOMEM; + + if (!priv || !priv->hist_data) { + ret = -EFAULT; + goto free_and_exit; + } + + phist_data = priv->hist_data; + + p += sprintf(p, "\n" + "total samples = %d\n", + atomic_read(&phist_data->num_samples)); + + p += sprintf(p, + "rx rates (in Mbps): 0=1M 1=2M 2=5.5M 3=11M 4=6M 5=9M 6=12M\n" + "7=18M 8=24M 9=36M 10=48M 11=54M 12-27=MCS0-15(BW20) 28-43=MCS0-15(BW40)\n"); + + if (ISSUPP_11ACENABLED(priv->adapter->fw_cap_info)) { + p += sprintf(p, + "44-53=MCS0-9(VHT:BW20) 54-63=MCS0-9(VHT:BW40) 64-73=MCS0-9(VHT:BW80)\n\n"); + } else { + p += sprintf(p, "\n"); + } + + for (i = 0; i < NXPWIFI_MAX_RX_RATES; i++) { + value = atomic_read(&phist_data->rx_rate[i]); + if (value) + p += sprintf(p, "rx_rate[%02d] = %d\n", i, value); + } + + if (ISSUPP_11ACENABLED(priv->adapter->fw_cap_info)) { + for (i = NXPWIFI_MAX_RX_RATES; i < NXPWIFI_MAX_AC_RX_RATES; + i++) { + value = atomic_read(&phist_data->rx_rate[i]); + if (value) + p += sprintf(p, "rx_rate[%02d] = %d\n", + i, value); + } + } + + for (i = 0; i < NXPWIFI_MAX_SNR; i++) { + value = atomic_read(&phist_data->snr[i]); + if (value) + p += sprintf(p, "snr[%02ddB] = %d\n", i, value); + } + for (i = 0; i < NXPWIFI_MAX_NOISE_FLR; i++) { + value = atomic_read(&phist_data->noise_flr[i]); + if (value) + p += sprintf(p, "noise_flr[%02ddBm] = %d\n", + (int)(i - 128), value); + } + for (i = 0; i < NXPWIFI_MAX_SIG_STRENGTH; i++) { + value = atomic_read(&phist_data->sig_str[i]); + if (value) + p += sprintf(p, "sig_strength[-%02ddBm] = %d\n", + i, value); + } + + ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page, + (unsigned long)p - page); + +free_and_exit: + free_page(page); + return ret; +} + +static ssize_t +nxpwifi_histogram_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = (void *)file->private_data; + + if (priv && priv->hist_data) + nxpwifi_hist_data_reset(priv); + return 0; +} + +static struct nxpwifi_debug_info info; + +/* debugfs "debug" read handler: dump adapter debug info and BA/reorder tables. */ +static ssize_t +nxpwifi_debug_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + ssize_t ret; + + if (!p) + return -ENOMEM; + + ret = nxpwifi_get_debug_info(priv, &info); + if (ret) + goto free_and_exit; + + p += nxpwifi_debug_info_to_buffer(priv, p, &info); + + ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page, + (unsigned long)p - page); + +free_and_exit: + free_page(page); + return ret; +} + +static u32 saved_reg_type, saved_reg_offset, saved_reg_value; + +/* + * debugfs "regrdwr" write handler: parse and store for + * readback/IO. + */ +static ssize_t +nxpwifi_regrdwr_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + char *buf; + int ret; + u32 reg_type = 0, reg_offset = 0, reg_value = UINT_MAX; + int rv; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + rv = sscanf(buf, "%u %x %x", ®_type, ®_offset, ®_value); + + if (rv != 3) { + ret = -EINVAL; + goto done; + } + + if (reg_type == 0 || reg_offset == 0) { + ret = -EINVAL; + goto done; + } else { + saved_reg_type = reg_type; + saved_reg_offset = reg_offset; + saved_reg_value = reg_value; + ret = count; + } +done: + kfree(buf); + return ret; +} + +/* + * debugfs "regrdwr" read handler: perform pending register read/write and return + * . + */ +static ssize_t +nxpwifi_regrdwr_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + int pos = 0, ret = 0; + u32 reg_value; + + if (!buf) + return -ENOMEM; + + if (!saved_reg_type) { + /* No command has been given */ + pos += snprintf(buf, PAGE_SIZE, "0"); + goto done; + } + /* Set command has been given */ + if (saved_reg_value != UINT_MAX) { + ret = nxpwifi_reg_write(priv, saved_reg_type, saved_reg_offset, + saved_reg_value); + + pos += snprintf(buf, PAGE_SIZE, "%u 0x%x 0x%x\n", + saved_reg_type, saved_reg_offset, + saved_reg_value); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); + + goto done; + } + /* Get command has been given */ + ret = nxpwifi_reg_read(priv, saved_reg_type, + saved_reg_offset, ®_value); + if (ret) { + ret = -EINVAL; + goto done; + } + + pos += snprintf(buf, PAGE_SIZE, "%u 0x%x 0x%x\n", saved_reg_type, + saved_reg_offset, reg_value); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); + +done: + free_page(addr); + return ret; +} + +/* debugfs "debug_mask" read handler: show driver debug mask. */ + +static ssize_t +nxpwifi_debug_mask_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)page; + size_t ret = 0; + int pos = 0; + + if (!buf) + return -ENOMEM; + + pos += snprintf(buf, PAGE_SIZE, "debug mask=0x%08x\n", + priv->adapter->debug_mask); + ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); + + free_page(page); + return ret; +} + +/* debugfs "debug_mask" write handler: set driver debug mask. */ + +static ssize_t +nxpwifi_debug_mask_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret; + unsigned long debug_mask; + struct nxpwifi_private *priv = (void *)file->private_data; + char *buf; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + if (kstrtoul(buf, 0, &debug_mask)) { + ret = -EINVAL; + goto done; + } + + priv->adapter->debug_mask = debug_mask; + ret = count; +done: + kfree(buf); + return ret; +} + +/* debugfs "verext" write handler: select extended version string. */ +static ssize_t +nxpwifi_verext_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret; + u32 versionstrsel; + struct nxpwifi_private *priv = (void *)file->private_data; + + ret = kstrtou32_from_user(ubuf, count, 10, &versionstrsel); + if (ret) + return ret; + + priv->versionstrsel = versionstrsel; + + return count; +} + +/* debugfs "verext" read handler: show extended version string. */ +static ssize_t +nxpwifi_verext_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + char buf[256]; + int ret; + + nxpwifi_get_ver_ext(priv, priv->versionstrsel); + ret = snprintf(buf, sizeof(buf), "version string: %s\n", + priv->version_str); + + return simple_read_from_buffer(ubuf, count, ppos, buf, ret); +} + +/* debugfs "memrw" write handler: read/write firmware memory (addr, value). */ +static ssize_t +nxpwifi_memrw_write(struct file *file, const char __user *ubuf, size_t count, + loff_t *ppos) +{ + int ret; + char cmd; + struct nxpwifi_ds_mem_rw mem_rw; + u16 cmd_action; + struct nxpwifi_private *priv = (void *)file->private_data; + char *buf; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + ret = sscanf(buf, "%c %x %x", &cmd, &mem_rw.addr, &mem_rw.value); + if (ret != 3) { + ret = -EINVAL; + goto done; + } + + if ((cmd == 'r') || (cmd == 'R')) { + cmd_action = HOST_ACT_GEN_GET; + mem_rw.value = 0; + } else if ((cmd == 'w') || (cmd == 'W')) { + cmd_action = HOST_ACT_GEN_SET; + } else { + ret = -EINVAL; + goto done; + } + + memcpy(&priv->mem_rw, &mem_rw, sizeof(mem_rw)); + ret = nxpwifi_send_cmd(priv, HOST_CMD_MEM_ACCESS, cmd_action, 0, + &mem_rw, true); + if (!ret) + ret = count; + +done: + kfree(buf); + return ret; +} + +/* debugfs "memrw" read handler: show last memory access result. */ +static ssize_t +nxpwifi_memrw_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = (void *)file->private_data; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + int ret, pos = 0; + + if (!buf) + return -ENOMEM; + + pos += snprintf(buf, PAGE_SIZE, "0x%x 0x%x\n", priv->mem_rw.addr, + priv->mem_rw.value); + ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); + + free_page(addr); + return ret; +} + +static u32 saved_offset = -1, saved_bytes = -1; + +/* debugfs "rdeeprom" write handler: set EEPROM offset/length to read. */ +static ssize_t +nxpwifi_rdeeprom_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + char *buf; + int ret = 0; + int offset = -1, bytes = -1; + int rv; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + rv = sscanf(buf, "%d %d", &offset, &bytes); + + if (rv != 2) { + ret = -EINVAL; + goto done; + } + + if (offset == -1 || bytes == -1) { + ret = -EINVAL; + goto done; + } else { + saved_offset = offset; + saved_bytes = bytes; + ret = count; + } +done: + kfree(buf); + return ret; +} + +/* debugfs "rdeeprom" read handler: dump EEPROM bytes from saved offset/length. */ +static ssize_t +nxpwifi_rdeeprom_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + int pos, ret, i; + u8 value[MAX_EEPROM_DATA]; + + if (!buf) + return -ENOMEM; + + if (saved_offset == -1) { + /* No command has been given */ + pos = snprintf(buf, PAGE_SIZE, "0"); + goto done; + } + + /* Get command has been given */ + ret = nxpwifi_eeprom_read(priv, (u16)saved_offset, + (u16)saved_bytes, value); + if (ret) { + ret = -EINVAL; + goto out_free; + } + + pos = snprintf(buf, PAGE_SIZE, "%d %d ", saved_offset, saved_bytes); + + for (i = 0; i < saved_bytes; i++) + pos += scnprintf(buf + pos, PAGE_SIZE - pos, "%d ", value[i]); + +done: + ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); +out_free: + free_page(addr); + return ret; +} + +/* + * debugfs "hscfg" write handler: configure host-sleep (conditions/gpio/gap) or + * cancel. + */ +static ssize_t +nxpwifi_hscfg_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = (void *)file->private_data; + char *buf; + int ret, arg_num; + struct nxpwifi_ds_hs_cfg hscfg; + int conditions = HS_CFG_COND_DEF; + u32 gpio = HS_CFG_GPIO_DEF, gap = HS_CFG_GAP_DEF; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + arg_num = sscanf(buf, "%d %x %x", &conditions, &gpio, &gap); + + memset(&hscfg, 0, sizeof(struct nxpwifi_ds_hs_cfg)); + + if (arg_num > 3) { + nxpwifi_dbg(priv->adapter, ERROR, + "Too many arguments\n"); + ret = -EINVAL; + goto done; + } + + if (arg_num >= 1 && arg_num < 3) + nxpwifi_set_hs_params(priv, HOST_ACT_GEN_GET, + NXPWIFI_SYNC_CMD, &hscfg); + + if (arg_num) { + if (conditions == HS_CFG_CANCEL) { + nxpwifi_cancel_hs(priv, NXPWIFI_ASYNC_CMD); + ret = count; + goto done; + } + hscfg.conditions = conditions; + } + if (arg_num >= 2) + hscfg.gpio = gpio; + if (arg_num == 3) + hscfg.gap = gap; + + hscfg.is_invoke_hostcmd = false; + nxpwifi_set_hs_params(priv, HOST_ACT_GEN_SET, + NXPWIFI_SYNC_CMD, &hscfg); + + nxpwifi_enable_hs(priv->adapter); + clear_bit(NXPWIFI_IS_HS_ENABLING, &priv->adapter->work_flags); + ret = count; +done: + kfree(buf); + return ret; +} + +/* debugfs "hscfg" read handler: show current host-sleep configuration. */ +static ssize_t +nxpwifi_hscfg_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = (void *)file->private_data; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + int pos, ret; + struct nxpwifi_ds_hs_cfg hscfg; + + if (!buf) + return -ENOMEM; + + nxpwifi_set_hs_params(priv, HOST_ACT_GEN_GET, + NXPWIFI_SYNC_CMD, &hscfg); + + pos = snprintf(buf, PAGE_SIZE, "%u 0x%x 0x%x\n", hscfg.conditions, + hscfg.gpio, hscfg.gap); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); + + free_page(addr); + return ret; +} + +static ssize_t +nxpwifi_timeshare_coex_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = file->private_data; + char buf[3]; + bool timeshare_coex; + int ret; + unsigned int len; + + if (priv->adapter->fw_api_ver != NXPWIFI_FW_V15) + return -EOPNOTSUPP; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_ROBUST_COEX, + HOST_ACT_GEN_GET, 0, ×hare_coex, true); + if (ret) + return ret; + + len = sprintf(buf, "%d\n", timeshare_coex); + return simple_read_from_buffer(ubuf, count, ppos, buf, len); +} + +static ssize_t +nxpwifi_timeshare_coex_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + bool timeshare_coex; + struct nxpwifi_private *priv = file->private_data; + int ret; + + if (priv->adapter->fw_api_ver != NXPWIFI_FW_V15) + return -EOPNOTSUPP; + + ret = kstrtobool_from_user(ubuf, count, ×hare_coex); + if (ret) + return ret; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_ROBUST_COEX, + HOST_ACT_GEN_SET, 0, ×hare_coex, true); + if (ret) + return ret; + else + return count; +} + +static ssize_t +nxpwifi_reset_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = file->private_data; + struct nxpwifi_adapter *adapter = priv->adapter; + bool result; + int rc; + + rc = kstrtobool_from_user(ubuf, count, &result); + if (rc) + return rc; + + if (!result) + return -EINVAL; + + if (adapter->if_ops.card_reset) { + nxpwifi_dbg(adapter, INFO, "Resetting per request\n"); + adapter->if_ops.card_reset(adapter); + } + + return count; +} + +static ssize_t +nxpwifi_fake_radar_detect_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = file->private_data; + struct nxpwifi_adapter *adapter = priv->adapter; + bool result; + int rc; + + rc = kstrtobool_from_user(ubuf, count, &result); + if (rc) + return rc; + + if (!result) + return -EINVAL; + + if (priv->wdev.links[0].cac_started) { + nxpwifi_dbg(adapter, MSG, + "Generate fake radar detected during CAC\n"); + if (nxpwifi_stop_radar_detection(priv, &priv->dfs_chandef)) + nxpwifi_dbg(adapter, ERROR, + "Failed to stop CAC in FW\n"); + wiphy_delayed_work_cancel(priv->adapter->wiphy, &priv->dfs_cac_work); + cfg80211_cac_event(priv->netdev, &priv->dfs_chandef, + NL80211_RADAR_CAC_ABORTED, GFP_KERNEL, 0); + cfg80211_radar_event(adapter->wiphy, &priv->dfs_chandef, + GFP_KERNEL); + } else { + if (priv->bss_chandef.chan->dfs_cac_ms) { + nxpwifi_dbg(adapter, MSG, + "Generate fake radar detected\n"); + cfg80211_radar_event(adapter->wiphy, + &priv->dfs_chandef, + GFP_KERNEL); + } + } + + return count; +} + +static ssize_t +nxpwifi_netmon_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret; + struct nxpwifi_802_11_net_monitor netmon_cfg; + struct nxpwifi_private *priv = (void *)file->private_data; + char *buf; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + memset(&netmon_cfg, 0, sizeof(struct nxpwifi_802_11_net_monitor)); + ret = sscanf(buf, "%u %u %u %u %u", + &netmon_cfg.enable_net_mon, + &netmon_cfg.filter_flag, + &netmon_cfg.band, + &netmon_cfg.channel, + &netmon_cfg.chan_bandwidth); + + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_NET_MONITOR, + HOST_ACT_GEN_SET, 0, &netmon_cfg, true); + + if (!ret) + ret = count; + + kfree(buf); + return ret; +} + +static ssize_t +nxpwifi_twt_setup_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret; + struct nxpwifi_twt_cfg twt_cfg; + struct nxpwifi_private *priv = (void *)file->private_data; + char *buf; + u16 twt_mantissa, bcn_miss_threshold; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + ret = sscanf(buf, "%hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hu %hhu %hu", + &twt_cfg.param.twt_setup.implicit, + &twt_cfg.param.twt_setup.announced, + &twt_cfg.param.twt_setup.trigger_enabled, + &twt_cfg.param.twt_setup.twt_info_disabled, + &twt_cfg.param.twt_setup.negotiation_type, + &twt_cfg.param.twt_setup.twt_wakeup_duration, + &twt_cfg.param.twt_setup.flow_identifier, + &twt_cfg.param.twt_setup.hard_constraint, + &twt_cfg.param.twt_setup.twt_exponent, + &twt_mantissa, + &twt_cfg.param.twt_setup.twt_request, + &bcn_miss_threshold); + + twt_cfg.param.twt_setup.twt_mantissa = cpu_to_le16(twt_mantissa); + twt_cfg.param.twt_setup.bcn_miss_threshold = cpu_to_le16(bcn_miss_threshold); + twt_cfg.sub_id = NXPWIFI_11AX_TWT_SETUP_SUBID; + ret = nxpwifi_send_cmd(priv, HOST_CMD_TWT_CFG, HOST_ACT_GEN_SET, 0, + &twt_cfg, true); + if (!ret) + ret = count; + + kfree(buf); + return ret; +} + +static ssize_t +nxpwifi_twt_teardown_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret; + struct nxpwifi_twt_cfg twt_cfg; + struct nxpwifi_private *priv = (void *)file->private_data; + char *buf; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + ret = sscanf(buf, "%hhu %hhu %hhu", + &twt_cfg.param.twt_teardown.flow_identifier, + &twt_cfg.param.twt_teardown.negotiation_type, + &twt_cfg.param.twt_teardown.teardown_all_twt); + + twt_cfg.sub_id = NXPWIFI_11AX_TWT_TEARDOWN_SUBID; + ret = nxpwifi_send_cmd(priv, HOST_CMD_TWT_CFG, HOST_ACT_GEN_SET, 0, + &twt_cfg, true); + + if (!ret) + ret = count; + + kfree(buf); + return ret; +} + +static ssize_t +nxpwifi_twt_report_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + ssize_t ret; + struct nxpwifi_twt_cfg twt_cfg; + u8 num, i, j; + + if (!p) + return -ENOMEM; + + twt_cfg.sub_id = NXPWIFI_11AX_TWT_REPORT_SUBID; + ret = nxpwifi_send_cmd(priv, HOST_CMD_TWT_CFG, HOST_ACT_GEN_GET, 0, + &twt_cfg, true); + if (ret) + goto done; + num = twt_cfg.param.twt_report.length / NXPWIFI_BTWT_REPORT_LEN; + num = num <= NXPWIFI_BTWT_REPORT_MAX_NUM ? num : NXPWIFI_BTWT_REPORT_MAX_NUM; + p += sprintf(p, "\ntwt_report len %hhu, num %hhu, twt_report_info:\n", + twt_cfg.param.twt_report.length, num); + for (i = 0; i < num; i++) { + p += sprintf(p, "id[%hu]:\r\n", i); + for (j = 0; j < NXPWIFI_BTWT_REPORT_LEN; j++) { + p += sprintf(p, + " 0x%02x", + twt_cfg.param.twt_report.data[i * NXPWIFI_BTWT_REPORT_LEN + j]); + } + p += sprintf(p, "\r\n"); + } + + ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page, + (unsigned long)p - page); + +done: + free_page(page); + return ret; +} + +static ssize_t +nxpwifi_twt_information_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret; + struct nxpwifi_twt_cfg twt_cfg; + struct nxpwifi_private *priv = (void *)file->private_data; + char *buf; + u32 suspend_duration; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + ret = sscanf(buf, "%hhu %u", + &twt_cfg.param.twt_information.flow_identifier, &suspend_duration); + twt_cfg.param.twt_information.suspend_duration = cpu_to_le32(suspend_duration); + + twt_cfg.sub_id = NXPWIFI_11AX_TWT_INFORMATION_SUBID; + ret = nxpwifi_send_cmd(priv, HOST_CMD_TWT_CFG, HOST_ACT_GEN_SET, 0, + &twt_cfg, true); + + if (!ret) + ret = count; + + kfree(buf); + return ret; +} + +#define NXPWIFI_DFS_ADD_FILE(name) debugfs_create_file(#name, 0644, \ + priv->dfs_dev_dir, priv, \ + &nxpwifi_dfs_##name##_fops) + +#define NXPWIFI_DFS_FILE_OPS(name) \ +static const struct file_operations nxpwifi_dfs_##name##_fops = { \ + .read = nxpwifi_##name##_read, \ + .write = nxpwifi_##name##_write, \ + .open = simple_open, \ +} + +#define NXPWIFI_DFS_FILE_READ_OPS(name) \ +static const struct file_operations nxpwifi_dfs_##name##_fops = { \ + .read = nxpwifi_##name##_read, \ + .open = simple_open, \ +} + +#define NXPWIFI_DFS_FILE_WRITE_OPS(name) \ +static const struct file_operations nxpwifi_dfs_##name##_fops = { \ + .write = nxpwifi_##name##_write, \ + .open = simple_open, \ +} + +NXPWIFI_DFS_FILE_READ_OPS(info); +NXPWIFI_DFS_FILE_READ_OPS(debug); +NXPWIFI_DFS_FILE_READ_OPS(getlog); +NXPWIFI_DFS_FILE_OPS(regrdwr); +NXPWIFI_DFS_FILE_OPS(rdeeprom); +NXPWIFI_DFS_FILE_OPS(memrw); +NXPWIFI_DFS_FILE_OPS(hscfg); +NXPWIFI_DFS_FILE_OPS(histogram); +NXPWIFI_DFS_FILE_OPS(debug_mask); +NXPWIFI_DFS_FILE_OPS(timeshare_coex); +NXPWIFI_DFS_FILE_WRITE_OPS(reset); +NXPWIFI_DFS_FILE_WRITE_OPS(fake_radar_detect); +NXPWIFI_DFS_FILE_OPS(verext); +NXPWIFI_DFS_FILE_WRITE_OPS(netmon); +NXPWIFI_DFS_FILE_WRITE_OPS(twt_setup); +NXPWIFI_DFS_FILE_WRITE_OPS(twt_teardown); +NXPWIFI_DFS_FILE_READ_OPS(twt_report); +NXPWIFI_DFS_FILE_WRITE_OPS(twt_information); + +/* Create per-netdev debugfs directory and files. */ +void +nxpwifi_dev_debugfs_init(struct nxpwifi_private *priv) +{ + if (!nxpwifi_dfs_dir || !priv) + return; + + priv->dfs_dev_dir = debugfs_create_dir(priv->netdev->name, + nxpwifi_dfs_dir); + + NXPWIFI_DFS_ADD_FILE(info); + NXPWIFI_DFS_ADD_FILE(debug); + NXPWIFI_DFS_ADD_FILE(getlog); + NXPWIFI_DFS_ADD_FILE(regrdwr); + NXPWIFI_DFS_ADD_FILE(rdeeprom); + + NXPWIFI_DFS_ADD_FILE(memrw); + NXPWIFI_DFS_ADD_FILE(hscfg); + NXPWIFI_DFS_ADD_FILE(histogram); + NXPWIFI_DFS_ADD_FILE(debug_mask); + NXPWIFI_DFS_ADD_FILE(timeshare_coex); + NXPWIFI_DFS_ADD_FILE(reset); + NXPWIFI_DFS_ADD_FILE(fake_radar_detect); + NXPWIFI_DFS_ADD_FILE(verext); + NXPWIFI_DFS_ADD_FILE(netmon); + NXPWIFI_DFS_ADD_FILE(twt_setup); + NXPWIFI_DFS_ADD_FILE(twt_teardown); + NXPWIFI_DFS_ADD_FILE(twt_report); + NXPWIFI_DFS_ADD_FILE(twt_information); +} + +/* Remove per-netdev debugfs directory and files. */ +void +nxpwifi_dev_debugfs_remove(struct nxpwifi_private *priv) +{ + if (!priv) + return; + + debugfs_remove_recursive(priv->dfs_dev_dir); +} + +/* Create top-level debugfs directory. */ +void +nxpwifi_debugfs_init(void) +{ + if (!nxpwifi_dfs_dir) + nxpwifi_dfs_dir = debugfs_create_dir("nxpwifi", NULL); +} + +/* Remove top-level debugfs directory. */ +void +nxpwifi_debugfs_remove(void) +{ + debugfs_remove(nxpwifi_dfs_dir); +} diff --git a/drivers/net/wireless/nxp/nxpwifi/ethtool.c b/drivers/net/wireless/nxp/nxpwifi/ethtool.c new file mode 100644 index 000000000000..aabb635afcf5 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/ethtool.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: ethtool + * + * Copyright 2011-2024 NXP + */ + +#include "main.h" + +static void nxpwifi_ethtool_get_wol(struct net_device *dev, + struct ethtool_wolinfo *wol) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + u32 conditions = le32_to_cpu(priv->adapter->hs_cfg.conditions); + + wol->supported = WAKE_UCAST | WAKE_MCAST | WAKE_BCAST | WAKE_PHY; + + if (conditions == HS_CFG_COND_DEF) + return; + + if (conditions & HS_CFG_COND_UNICAST_DATA) + wol->wolopts |= WAKE_UCAST; + if (conditions & HS_CFG_COND_MULTICAST_DATA) + wol->wolopts |= WAKE_MCAST; + if (conditions & HS_CFG_COND_BROADCAST_DATA) + wol->wolopts |= WAKE_BCAST; + if (conditions & HS_CFG_COND_MAC_EVENT) + wol->wolopts |= WAKE_PHY; +} + +static int nxpwifi_ethtool_set_wol(struct net_device *dev, + struct ethtool_wolinfo *wol) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + u32 conditions = 0; + + if (wol->wolopts & ~(WAKE_UCAST | WAKE_MCAST | WAKE_BCAST | WAKE_PHY)) + return -EOPNOTSUPP; + + if (wol->wolopts & WAKE_UCAST) + conditions |= HS_CFG_COND_UNICAST_DATA; + if (wol->wolopts & WAKE_MCAST) + conditions |= HS_CFG_COND_MULTICAST_DATA; + if (wol->wolopts & WAKE_BCAST) + conditions |= HS_CFG_COND_BROADCAST_DATA; + if (wol->wolopts & WAKE_PHY) + conditions |= HS_CFG_COND_MAC_EVENT; + if (wol->wolopts == 0) + conditions |= HS_CFG_COND_DEF; + priv->adapter->hs_cfg.conditions = cpu_to_le32(conditions); + + return 0; +} + +const struct ethtool_ops nxpwifi_ethtool_ops = { + .get_wol = nxpwifi_ethtool_get_wol, + .set_wol = nxpwifi_ethtool_set_wol, +}; diff --git a/drivers/net/wireless/nxp/nxpwifi/fw.h b/drivers/net/wireless/nxp/nxpwifi/fw.h new file mode 100644 index 000000000000..188110b020cf --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/fw.h @@ -0,0 +1,2475 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * nxpwifi: Firmware-specific macros and structures + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_FW_H_ +#define _NXPWIFI_FW_H_ + +#include + +#define INTF_HEADER_LEN 4 + +struct rfc_1042_hdr { + u8 llc_dsap; + u8 llc_ssap; + u8 llc_ctrl; + u8 snap_oui[3]; + __be16 snap_type; +} __packed; + +struct rx_packet_hdr { + struct ethhdr eth803_hdr; + struct rfc_1042_hdr rfc1042_hdr; +} __packed; + +struct tx_packet_hdr { + struct ethhdr eth803_hdr; + struct rfc_1042_hdr rfc1042_hdr; +} __packed; + +struct nxpwifi_fw_header { + __le32 dnld_cmd; + __le32 base_addr; + __le32 data_length; + __le32 crc; +} __packed; + +struct nxpwifi_fw_data { + struct nxpwifi_fw_header header; + __le32 seq_num; + u8 data[]; +} __packed; + +struct nxpwifi_fw_dump_header { + __le16 seq_num; + __le16 reserved; + __le16 type; + __le16 len; +} __packed; + +#define FW_DUMP_INFO_ENDED 0x0002 + +#define NXPWIFI_FW_DNLD_CMD_1 0x1 +#define NXPWIFI_FW_DNLD_CMD_5 0x5 +#define NXPWIFI_FW_DNLD_CMD_6 0x6 +#define NXPWIFI_FW_DNLD_CMD_7 0x7 + +#define B_SUPPORTED_RATES 5 +#define G_SUPPORTED_RATES 9 +#define BG_SUPPORTED_RATES 13 +#define A_SUPPORTED_RATES 9 +#define HOSTCMD_SUPPORTED_RATES 14 +#define N_SUPPORTED_RATES 3 +#define ALL_802_11_BANDS \ + (BAND_A | BAND_B | BAND_G | BAND_GN | BAND_AN | BAND_AAC | BAND_GAC) +#define FW_MULTI_BANDS_SUPPORT \ + (BIT(8) | BIT(9) | BIT(10) | BIT(11) | BIT(12) | BIT(13)) +#define IS_SUPPORT_MULTI_BANDS(adapter) \ + ((adapter)->fw_cap_info & FW_MULTI_BANDS_SUPPORT) + +/* + * Map fw_cap_info for default bands: shift 11ac flags so bits + * 11:GN, 12:AN, 13:GAC, 14:AAC match driver layout after >>8. + */ +#define GET_FW_DEFAULT_BANDS(adapter) ({\ + typeof(adapter) (_adapter) = adapter; \ + (((((_adapter->fw_cap_info & 0x3000) << 1) | \ + (_adapter->fw_cap_info & ~0xF000)) \ + >> 8) & \ + ALL_802_11_BANDS); \ + }) + +#define HOST_WEP_KEY_INDEX_MASK 0x3fff + +#define KEY_INFO_ENABLED 0x01 +enum KEY_TYPE_ID { + KEY_TYPE_ID_WEP = 0, + KEY_TYPE_ID_TKIP, + KEY_TYPE_ID_AES, + KEY_TYPE_ID_WAPI, + KEY_TYPE_ID_AES_CMAC, + KEY_TYPE_ID_GCMP, + KEY_TYPE_ID_GCMP_256, + KEY_TYPE_ID_CCMP_256, + KEY_TYPE_ID_BIP_GMAC_128, + KEY_TYPE_ID_BIP_GMAC_256, +}; + +#define WPA_PN_SIZE 8 +#define KEY_PARAMS_FIXED_LEN 10 +#define KEY_INDEX_MASK 0xf +#define KEY_API_VER_MAJOR_V2 2 + +#define KEY_MCAST BIT(0) +#define KEY_UNICAST BIT(1) +#define KEY_ENABLED BIT(2) +#define KEY_DEFAULT BIT(3) +#define KEY_TX_KEY BIT(4) +#define KEY_RX_KEY BIT(5) +#define KEY_IGTK BIT(10) + +#define MAX_POLL_TRIES 10000 +#define MAX_FIRMWARE_POLL_TRIES 300 + +#define FIRMWARE_READY_SDIO 0xfedc +#define FIRMWARE_READY_PCIE 0xfedcba00 + +#define NXPWIFI_COEX_MODE_TIMESHARE 0x01 +#define NXPWIFI_COEX_MODE_SPATIAL 0x82 + +enum nxpwifi_usb_ep { + NXPWIFI_USB_EP_CMD_EVENT = 1, + NXPWIFI_USB_EP_DATA = 2, + NXPWIFI_USB_EP_DATA_CH2 = 3, +}; + +enum NXPWIFI_802_11_PRIVACY_FILTER { + NXPWIFI_802_11_PRIV_FILTER_ACCEPT_ALL, + NXPWIFI_802_11_PRIV_FILTER_8021X_WEP +}; + +#define CAL_SNR(RSSI, NF) ((s16)((s16)(RSSI) - (s16)(NF))) +#define CAL_RSSI(SNR, NF) ((s16)((s16)(SNR) + (s16)(NF))) + +#define UAP_BSS_PARAMS_I 0 +#define UAP_CUSTOM_IE_I 1 +#define NXPWIFI_AUTO_IDX_MASK 0xffff +#define NXPWIFI_DELETE_MASK 0x0000 +#define MGMT_MASK_ASSOC_REQ 0x01 +#define MGMT_MASK_REASSOC_REQ 0x04 +#define MGMT_MASK_ASSOC_RESP 0x02 +#define MGMT_MASK_REASSOC_RESP 0x08 +#define MGMT_MASK_PROBE_REQ 0x10 +#define MGMT_MASK_PROBE_RESP 0x20 +#define MGMT_MASK_BEACON 0x100 + +#define TLV_TYPE_UAP_SSID 0x0000 +#define TLV_TYPE_UAP_RATES 0x0001 +#define TLV_TYPE_PWR_CONSTRAINT 0x0020 +#define TLV_TYPE_HT_CAPABILITY 0x002d +#define TLV_TYPE_EXTENSION_ID 0x00ff + +#define PROPRIETARY_TLV_BASE_ID 0x0100 +#define TLV_TYPE_KEY_MATERIAL (PROPRIETARY_TLV_BASE_ID + 0) +#define TLV_TYPE_CHANLIST (PROPRIETARY_TLV_BASE_ID + 1) +#define TLV_TYPE_NUMPROBES (PROPRIETARY_TLV_BASE_ID + 2) +#define TLV_TYPE_RSSI_LOW (PROPRIETARY_TLV_BASE_ID + 4) +#define TLV_TYPE_PASSTHROUGH (PROPRIETARY_TLV_BASE_ID + 10) +#define TLV_TYPE_WMMQSTATUS (PROPRIETARY_TLV_BASE_ID + 16) +#define TLV_TYPE_WILDCARDSSID (PROPRIETARY_TLV_BASE_ID + 18) +#define TLV_TYPE_TSFTIMESTAMP (PROPRIETARY_TLV_BASE_ID + 19) +#define TLV_TYPE_RSSI_HIGH (PROPRIETARY_TLV_BASE_ID + 22) +#define TLV_TYPE_BGSCAN_START_LATER (PROPRIETARY_TLV_BASE_ID + 30) +#define TLV_TYPE_AUTH_TYPE (PROPRIETARY_TLV_BASE_ID + 31) +#define TLV_TYPE_STA_MAC_ADDR (PROPRIETARY_TLV_BASE_ID + 32) +#define TLV_TYPE_BSSID (PROPRIETARY_TLV_BASE_ID + 35) +#define TLV_TYPE_CHANNELBANDLIST (PROPRIETARY_TLV_BASE_ID + 42) +#define TLV_TYPE_UAP_MAC_ADDRESS (PROPRIETARY_TLV_BASE_ID + 43) +#define TLV_TYPE_UAP_BEACON_PERIOD (PROPRIETARY_TLV_BASE_ID + 44) +#define TLV_TYPE_UAP_DTIM_PERIOD (PROPRIETARY_TLV_BASE_ID + 45) +#define TLV_TYPE_UAP_BCAST_SSID (PROPRIETARY_TLV_BASE_ID + 48) +#define TLV_TYPE_UAP_PREAMBLE_CTL (PROPRIETARY_TLV_BASE_ID + 49) +#define TLV_TYPE_UAP_RTS_THRESHOLD (PROPRIETARY_TLV_BASE_ID + 51) +#define TLV_TYPE_UAP_AO_TIMER (PROPRIETARY_TLV_BASE_ID + 57) +#define TLV_TYPE_UAP_WEP_KEY (PROPRIETARY_TLV_BASE_ID + 59) +#define TLV_TYPE_UAP_WPA_PASSPHRASE (PROPRIETARY_TLV_BASE_ID + 60) +#define TLV_TYPE_UAP_ENCRY_PROTOCOL (PROPRIETARY_TLV_BASE_ID + 64) +#define TLV_TYPE_UAP_AKMP (PROPRIETARY_TLV_BASE_ID + 65) +#define TLV_TYPE_UAP_FRAG_THRESHOLD (PROPRIETARY_TLV_BASE_ID + 70) +#define TLV_TYPE_RATE_DROP_CONTROL (PROPRIETARY_TLV_BASE_ID + 82) +#define TLV_TYPE_RATE_SCOPE (PROPRIETARY_TLV_BASE_ID + 83) +#define TLV_TYPE_POWER_GROUP (PROPRIETARY_TLV_BASE_ID + 84) +#define TLV_TYPE_BSS_SCAN_RSP (PROPRIETARY_TLV_BASE_ID + 86) +#define TLV_TYPE_BSS_SCAN_INFO (PROPRIETARY_TLV_BASE_ID + 87) +#define TLV_TYPE_CHANRPT_11H_BASIC (PROPRIETARY_TLV_BASE_ID + 91) +#define TLV_TYPE_UAP_RETRY_LIMIT (PROPRIETARY_TLV_BASE_ID + 93) +#define TLV_TYPE_ROBUST_COEX (PROPRIETARY_TLV_BASE_ID + 96) +#define TLV_TYPE_UAP_MGMT_FRAME (PROPRIETARY_TLV_BASE_ID + 104) +#define TLV_TYPE_MGMT_IE (PROPRIETARY_TLV_BASE_ID + 105) +#define TLV_TYPE_AUTO_DS_PARAM (PROPRIETARY_TLV_BASE_ID + 113) +#define TLV_TYPE_PS_PARAM (PROPRIETARY_TLV_BASE_ID + 114) +#define TLV_TYPE_UAP_PS_AO_TIMER (PROPRIETARY_TLV_BASE_ID + 123) +#define TLV_TYPE_PWK_CIPHER (PROPRIETARY_TLV_BASE_ID + 145) +#define TLV_TYPE_GWK_CIPHER (PROPRIETARY_TLV_BASE_ID + 146) +#define TLV_TYPE_TX_PAUSE (PROPRIETARY_TLV_BASE_ID + 148) +#define TLV_TYPE_RXBA_SYNC (PROPRIETARY_TLV_BASE_ID + 153) +#define TLV_TYPE_COALESCE_RULE (PROPRIETARY_TLV_BASE_ID + 154) +#define TLV_TYPE_KEY_PARAM_V2 (PROPRIETARY_TLV_BASE_ID + 156) +#define TLV_TYPE_REGION_DOMAIN_CODE (PROPRIETARY_TLV_BASE_ID + 171) +#define TLV_TYPE_REPEAT_COUNT (PROPRIETARY_TLV_BASE_ID + 176) +#define TLV_TYPE_PS_PARAMS_IN_HS (PROPRIETARY_TLV_BASE_ID + 181) +#define TLV_TYPE_MULTI_CHAN_INFO (PROPRIETARY_TLV_BASE_ID + 183) +#define TLV_TYPE_MC_GROUP_INFO (PROPRIETARY_TLV_BASE_ID + 184) +#define TLV_TYPE_SCAN_CHANNEL_GAP (PROPRIETARY_TLV_BASE_ID + 197) +#define TLV_TYPE_API_REV (PROPRIETARY_TLV_BASE_ID + 199) +#define TLV_TYPE_CHANNEL_STATS (PROPRIETARY_TLV_BASE_ID + 198) +#define TLV_BTCOEX_WL_AGGR_WINSIZE (PROPRIETARY_TLV_BASE_ID + 202) +#define TLV_BTCOEX_WL_SCANTIME (PROPRIETARY_TLV_BASE_ID + 203) +#define TLV_TYPE_BSS_MODE (PROPRIETARY_TLV_BASE_ID + 206) +#define TLV_TYPE_RANDOM_MAC (PROPRIETARY_TLV_BASE_ID + 236) +#define TLV_TYPE_CHAN_ATTR_CFG (PROPRIETARY_TLV_BASE_ID + 237) +#define TLV_TYPE_MAX_CONN (PROPRIETARY_TLV_BASE_ID + 279) +#define TLV_TYPE_HOST_MLME (PROPRIETARY_TLV_BASE_ID + 307) +#define TLV_TYPE_UAP_STA_FLAGS (PROPRIETARY_TLV_BASE_ID + 313) +#define TLV_TYPE_FW_CAP_INFO (PROPRIETARY_TLV_BASE_ID + 318) +#define TLV_TYPE_AX_ENABLE_SR (PROPRIETARY_TLV_BASE_ID + 322) +#define TLV_TYPE_AX_OBSS_PD_OFFSET (PROPRIETARY_TLV_BASE_ID + 323) +#define TLV_TYPE_SAE_PWE_MODE (PROPRIETARY_TLV_BASE_ID + 339) +#define TLV_TYPE_6E_INBAND_FRAMES (PROPRIETARY_TLV_BASE_ID + 345) +#define TLV_TYPE_SECURE_BOOT_UUID (PROPRIETARY_TLV_BASE_ID + 348) + +#define NXPWIFI_TX_DATA_BUF_SIZE_2K 2048 + +#define SSN_MASK 0xfff0 + +#define BA_RESULT_SUCCESS 0x0 +#define BA_RESULT_TIMEOUT 0x2 + +#define IS_BASTREAM_SETUP(ptr) ((ptr)->ba_status) + +#define BA_STREAM_NOT_ALLOWED 0xff + +#define IS_11N_ENABLED(priv) ({ \ + typeof(priv) (_priv) = priv; \ + (((_priv)->config_bands & BAND_GN || \ + (_priv)->config_bands & BAND_AN) && \ + (_priv)->curr_bss_params.bss_descriptor.bcn_ht_cap && \ + !(_priv)->curr_bss_params.bss_descriptor.disable_11n); \ + }) +#define INITIATOR_BIT(del_ba_param_set) (((del_ba_param_set) &\ + BIT(DELBA_INITIATOR_POS)) >> DELBA_INITIATOR_POS) + +#define NXPWIFI_TX_DATA_BUF_SIZE_4K 4096 +#define NXPWIFI_TX_DATA_BUF_SIZE_8K 8192 +#define NXPWIFI_TX_DATA_BUF_SIZE_12K 12288 + +#define ISSUPP_11NENABLED(fw_cap_info) ((fw_cap_info) & BIT(11)) +#define ISSUPP_DRCS_ENABLED(fw_cap_info) ((fw_cap_info) & BIT(15)) +#define ISSUPP_SDIO_SPA_ENABLED(fw_cap_info) ((fw_cap_info) & BIT(16)) +#define ISSUPP_RANDOM_MAC(fw_cap_info) ((fw_cap_info) & BIT(27)) +#define ISSUPP_FIRMWARE_SUPPLICANT(fw_cap_info) ((fw_cap_info) & BIT(21)) + +#define NXPWIFI_DEF_HT_CAP (IEEE80211_HT_CAP_DSSSCCK40 | \ + (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT) | \ + IEEE80211_HT_CAP_SM_PS) + +#define NXPWIFI_DEF_11N_TX_BF_CAP 0x09E1E008 + +#define NXPWIFI_DEF_AMPDU IEEE80211_HT_AMPDU_PARM_FACTOR + +#define RXPD_FLAG_EXTRA_HEADER BIT(1) +/* channel number at bit 5-13 */ +#define RXPD_CHAN_MASK 0x3FE0 +/* DCM at bit 16 */ +#define RXPD_DCM_MASK 0x10000 + +/* + * dot11n dev_cap bits: 17:20/40MHz, 23:SGI20, 24:SGI40, 25:TXSTBC, + * 26:RXSTBC, 29:Greenfield. + */ +#define ISSUPP_CHANWIDTH40(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(17)) +#define ISSUPP_SHORTGI20(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(23)) +#define ISSUPP_SHORTGI40(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(24)) +#define ISSUPP_TXSTBC(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(25)) +#define ISSUPP_RXSTBC(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(26)) +#define ISSUPP_GREENFIELD(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(29)) +#define ISENABLED_40MHZ_INTOLERANT(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(8)) +#define ISSUPP_RXLDPC(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(22)) +#define ISSUPP_BEAMFORMING(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(30)) +#define ISALLOWED_CHANWIDTH40(ht_param) ((ht_param) & BIT(2)) +#define GETSUPP_TXBASTREAMS(dot_11n_dev_cap) (((dot_11n_dev_cap) >> 18) & 0xF) + +/* AMPDU factor size */ +#define AMPDU_FACTOR_64K 0x03 +/* hw_dev_cap : MPDU DENSITY */ +#define GET_MPDU_DENSITY(hw_dev_cap) ((hw_dev_cap) & 0x7) + +/* httxcfg bits: 1:20/40, 4:GF, 5:SGI20, 6:SGI40. */ +#define NXPWIFI_FW_DEF_HTTXCFG (BIT(1) | BIT(4) | BIT(5) | BIT(6)) + +/* 11ac MCS map (1x1): stream0 supports 0-9, others not supported. */ +#define NXPWIFI_11AC_MCS_MAP_1X1 0xfffefffe + +/* 11ac MCS map (2x2): stream0/1 support 0-9, others not supported. */ +#define NXPWIFI_11AC_MCS_MAP_2X2 0xfffafffa + +#define GET_TXMCSSUPP(dev_mcs_supported) ((dev_mcs_supported) >> 4) +#define GET_RXMCSSUPP(dev_mcs_supported) ((dev_mcs_supported) & 0x0f) +#define SETHT_MCS32(x) (x[4] |= 1) +#define HT_STREAM_1X1 0x11 +#define HT_STREAM_2X2 0x22 + +#define SET_SECONDARYCHAN(radio_type, sec_chan) \ + ((radio_type) |= ((sec_chan) << 4)) + +#define LLC_SNAP_LEN 8 + +/* HW_SPEC fw_cap_info */ + +#define ISSUPP_11ACENABLED(fw_cap_info) ((fw_cap_info) & BIT(13)) +#define NO_NSS_SUPPORT 0x3 +#define GET_VHTNSSMCS(mcs_mapset, nss) \ + (((mcs_mapset) >> (2 * ((nss) - 1))) & 0x3) +#define SET_VHTNSSMCS(mcs_mapset, nss, value) \ + ((mcs_mapset) |= ((value) & 0x3) << (2 * ((nss) - 1))) +#define GET_DEVTXMCSMAP(dev_mcs_map) ((dev_mcs_map) >> 16) +#define GET_DEVRXMCSMAP(dev_mcs_map) ((dev_mcs_map) & 0xFFFF) + +/* Clear SU/MU beamformer/beamformee and sounding dimension bits. */ +#define NXPWIFI_DEF_11AC_CAP_BF_RESET_MASK \ + (IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE | \ + IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE | \ + IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE | \ + IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_MASK) + +#define MOD_CLASS_HR_DSSS 0x03 +#define MOD_CLASS_OFDM 0x07 +#define MOD_CLASS_HT 0x08 +#define HT_BW_20 0 +#define HT_BW_40 1 + +#define DFS_CHAN_MOVE_TIME 10000 + +#define ISSUPP_11AXENABLED(fw_cap_ext) ((fw_cap_ext) & BIT(7)) + +#define HOST_CMD_GET_HW_SPEC 0x0003 +#define HOST_CMD_802_11_SCAN 0x0006 +#define HOST_CMD_802_11_GET_LOG 0x000b +#define HOST_CMD_MAC_MULTICAST_ADR 0x0010 +#define HOST_CMD_802_11_ASSOCIATE 0x0012 +#define HOST_CMD_802_11_SNMP_MIB 0x0016 +#define HOST_CMD_MAC_REG_ACCESS 0x0019 +#define HOST_CMD_BBP_REG_ACCESS 0x001a +#define HOST_CMD_RF_REG_ACCESS 0x001b +#define HOST_CMD_RF_TX_PWR 0x001e +#define HOST_CMD_RF_ANTENNA 0x0020 +#define HOST_CMD_802_11_DEAUTHENTICATE 0x0024 +#define HOST_CMD_MAC_CONTROL 0x0028 +#define HOST_CMD_802_11_MAC_ADDRESS 0x004D +#define HOST_CMD_802_11_EEPROM_ACCESS 0x0059 +#define HOST_CMD_802_11D_DOMAIN_INFO 0x005b +#define HOST_CMD_802_11_KEY_MATERIAL 0x005e +#define HOST_CMD_802_11_BG_SCAN_CONFIG 0x006b +#define HOST_CMD_802_11_BG_SCAN_QUERY 0x006c +#define HOST_CMD_WMM_GET_STATUS 0x0071 +#define HOST_CMD_802_11_SUBSCRIBE_EVENT 0x0075 +#define HOST_CMD_802_11_TX_RATE_QUERY 0x007f +#define HOST_CMD_MEM_ACCESS 0x0086 +#define HOST_CMD_CFG_DATA 0x008f +#define HOST_CMD_VERSION_EXT 0x0097 +#define HOST_CMD_MEF_CFG 0x009a +#define HOST_CMD_RSSI_INFO 0x00a4 +#define HOST_CMD_FUNC_INIT 0x00a9 +#define HOST_CMD_FUNC_SHUTDOWN 0x00aa +#define HOST_CMD_PMIC_REG_ACCESS 0x00ad +#define HOST_CMD_APCMD_SYS_RESET 0x00af +#define HOST_CMD_UAP_SYS_CONFIG 0x00b0 +#define HOST_CMD_UAP_BSS_START 0x00b1 +#define HOST_CMD_UAP_BSS_STOP 0x00b2 +#define HOST_CMD_APCMD_STA_LIST 0x00b3 +#define HOST_CMD_UAP_STA_DEAUTH 0x00b5 +#define HOST_CMD_11N_CFG 0x00cd +#define HOST_CMD_11N_ADDBA_REQ 0x00ce +#define HOST_CMD_11N_ADDBA_RSP 0x00cf +#define HOST_CMD_11N_DELBA 0x00d0 +#define HOST_CMD_TXPWR_CFG 0x00d1 +#define HOST_CMD_TX_RATE_CFG 0x00d6 +#define HOST_CMD_RECONFIGURE_TX_BUFF 0x00d9 +#define HOST_CMD_CHAN_REPORT_REQUEST 0x00dd +#define HOST_CMD_AMSDU_AGGR_CTRL 0x00df +#define HOST_CMD_ROBUST_COEX 0x00e0 +#define HOST_CMD_802_11_PS_MODE_ENH 0x00e4 +#define HOST_CMD_802_11_HS_CFG_ENH 0x00e5 +#define HOST_CMD_CAU_REG_ACCESS 0x00ed +#define HOST_CMD_SET_BSS_MODE 0x00f7 +#define HOST_CMD_PCIE_DESC_DETAILS 0x00fa +#define HOST_CMD_802_11_NET_MONITOR 0x0102 +#define HOST_CMD_802_11_SCAN_EXT 0x0107 +#define HOST_CMD_COALESCE_CFG 0x010a +#define HOST_CMD_MGMT_FRAME_REG 0x010c +#define HOST_CMD_REMAIN_ON_CHAN 0x010d +#define HOST_CMD_GTK_REKEY_OFFLOAD_CFG 0x010f +#define HOST_CMD_11AC_CFG 0x0112 +#define HOST_CMD_HS_WAKEUP_REASON 0x0116 +#define HOST_CMD_MC_POLICY 0x0121 +#define HOST_CMD_FW_DUMP_EVENT 0x0125 +#define HOST_CMD_SDIO_SP_RX_AGGR_CFG 0x0223 +#define HOST_CMD_STA_CONFIGURE 0x023f +#define HOST_CMD_VDLL 0x0240 +#define HOST_CMD_CHAN_REGION_CFG 0x0242 +#define HOST_CMD_PACKET_AGGR_CTRL 0x0251 +#define HOST_CMD_ADD_NEW_STATION 0x025f +#define HOST_CMD_11AX_CFG 0x0266 +#define HOST_CMD_11AX_CMD 0x026d +#define HOST_CMD_TWT_CFG 0x0270 + +#define PROTOCOL_NO_SECURITY 0x01 +#define PROTOCOL_STATIC_WEP 0x02 +#define PROTOCOL_WPA 0x08 +#define PROTOCOL_WPA2 0x20 +#define PROTOCOL_WPA2_MIXED 0x28 +#define PROTOCOL_EAP 0x40 +#define KEY_MGMT_EAP 0x01 +#define KEY_MGMT_PSK 0x02 +#define KEY_MGMT_NONE 0x04 +#define KEY_MGMT_PSK_SHA256 0x100 +#define KEY_MGMT_OWE 0x200 +#define KEY_MGMT_SAE 0x400 +#define CIPHER_TKIP 0x04 +#define CIPHER_AES_CCMP 0x08 +#define VALID_CIPHER_BITMAP 0x0c + +enum ENH_PS_MODES { + EN_PS = 1, + DIS_PS = 2, + EN_AUTO_DS = 3, + DIS_AUTO_DS = 4, + SLEEP_CONFIRM = 5, + GET_PS = 0, + EN_AUTO_PS = 0xff, + DIS_AUTO_PS = 0xfe, +}; + +enum nxpwifi_channel_flags { + NXPWIFI_CHANNEL_PASSIVE = BIT(0), + NXPWIFI_CHANNEL_DFS = BIT(1), + NXPWIFI_CHANNEL_NOHT40 = BIT(2), + NXPWIFI_CHANNEL_NOHT80 = BIT(3), + NXPWIFI_CHANNEL_DISABLED = BIT(7), +}; + +#define HOST_RET_BIT 0x8000 +#define HOST_ACT_GEN_GET 0x0000 +#define HOST_ACT_GEN_SET 0x0001 +#define HOST_ACT_GEN_REMOVE 0x0004 +#define HOST_ACT_BITWISE_SET 0x0002 +#define HOST_ACT_BITWISE_CLR 0x0003 +#define HOST_RESULT_OK 0x0000 +#define HOST_ACT_MAC_RX_ON BIT(0) +#define HOST_ACT_MAC_TX_ON BIT(1) +#define HOST_ACT_MAC_WEP_ENABLE BIT(3) +#define HOST_ACT_MAC_ETHERNETII_ENABLE BIT(4) +#define HOST_ACT_MAC_PROMISCUOUS_ENABLE BIT(7) +#define HOST_ACT_MAC_ALL_MULTICAST_ENABLE BIT(8) +#define HOST_ACT_MAC_DYNAMIC_BW_ENABLE BIT(16) + +#define HOST_BSS_MODE_IBSS 0x0002 +#define HOST_BSS_MODE_ANY 0x0003 + +#define HOST_SCAN_RADIO_TYPE_BG 0 +#define HOST_SCAN_RADIO_TYPE_A 1 + +#define HS_CFG_CANCEL 0xffffffff +#define HS_CFG_COND_DEF 0x00000000 +#define HS_CFG_GPIO_DEF 0xff +#define HS_CFG_GAP_DEF 0xff +#define HS_CFG_COND_BROADCAST_DATA 0x00000001 +#define HS_CFG_COND_UNICAST_DATA 0x00000002 +#define HS_CFG_COND_MAC_EVENT 0x00000004 +#define HS_CFG_COND_MULTICAST_DATA 0x00000008 + +#define CONNECT_ERR_AUTH_ERR_STA_FAILURE 0xFFFB +#define CONNECT_ERR_ASSOC_ERR_TIMEOUT 0xFFFC +#define CONNECT_ERR_ASSOC_ERR_AUTH_REFUSED 0xFFFD +#define CONNECT_ERR_AUTH_MSG_UNHANDLED 0xFFFE +#define CONNECT_ERR_STA_FAILURE 0xFFFF + +#define CMD_F_HOSTCMD BIT(0) + +#define HOST_CMD_ID_MASK 0x0fff + +#define HOST_SEQ_NUM_MASK 0x00ff + +#define HOST_BSS_NUM_MASK 0x0f00 + +#define HOST_BSS_TYPE_MASK 0xf000 + +#define HOST_ACT_SET_RX 0x0001 +#define HOST_ACT_SET_TX 0x0002 +#define HOST_ACT_SET_BOTH 0x0003 +#define HOST_ACT_GET_RX 0x0004 +#define HOST_ACT_GET_TX 0x0008 +#define HOST_ACT_GET_BOTH 0x000c + +#define HOST_ACT_REMOVE_STA 0x0 +#define HOST_ACT_ADD_STA 0x1 + +#define RF_ANTENNA_AUTO 0xFFFF + +#define HOST_SET_SEQ_NO_BSS_INFO(seq, num, type) \ + ((((seq) & 0x00ff) | \ + (((num) & 0x000f) << 8)) | \ + (((type) & 0x000f) << 12)) + +#define HOST_GET_SEQ_NO(seq) \ + ((seq) & HOST_SEQ_NUM_MASK) + +#define HOST_GET_BSS_NO(seq) \ + (((seq) & HOST_BSS_NUM_MASK) >> 8) + +#define HOST_GET_BSS_TYPE(seq) \ + (((seq) & HOST_BSS_TYPE_MASK) >> 12) + +#define EVENT_DUMMY_HOST_WAKEUP_SIGNAL 0x00000001 +#define EVENT_LINK_LOST 0x00000003 +#define EVENT_LINK_SENSED 0x00000004 +#define EVENT_MIB_CHANGED 0x00000006 +#define EVENT_INIT_DONE 0x00000007 +#define EVENT_DEAUTHENTICATED 0x00000008 +#define EVENT_DISASSOCIATED 0x00000009 +#define EVENT_PS_AWAKE 0x0000000a +#define EVENT_PS_SLEEP 0x0000000b +#define EVENT_MIC_ERR_MULTICAST 0x0000000d +#define EVENT_MIC_ERR_UNICAST 0x0000000e +#define EVENT_DEEP_SLEEP_AWAKE 0x00000010 +#define EVENT_WMM_STATUS_CHANGE 0x00000017 +#define EVENT_BG_SCAN_REPORT 0x00000018 +#define EVENT_RSSI_LOW 0x00000019 +#define EVENT_SNR_LOW 0x0000001a +#define EVENT_MAX_FAIL 0x0000001b +#define EVENT_RSSI_HIGH 0x0000001c +#define EVENT_SNR_HIGH 0x0000001d +#define EVENT_DATA_RSSI_LOW 0x00000024 +#define EVENT_DATA_SNR_LOW 0x00000025 +#define EVENT_DATA_RSSI_HIGH 0x00000026 +#define EVENT_DATA_SNR_HIGH 0x00000027 +#define EVENT_LINK_QUALITY 0x00000028 +#define EVENT_PORT_RELEASE 0x0000002b +#define EVENT_UAP_STA_DEAUTH 0x0000002c +#define EVENT_UAP_STA_ASSOC 0x0000002d +#define EVENT_UAP_BSS_START 0x0000002e +#define EVENT_PRE_BEACON_LOST 0x00000031 +#define EVENT_ADDBA 0x00000033 +#define EVENT_DELBA 0x00000034 +#define EVENT_BA_STREAM_TIEMOUT 0x00000037 +#define EVENT_AMSDU_AGGR_CTRL 0x00000042 +#define EVENT_UAP_BSS_IDLE 0x00000043 +#define EVENT_UAP_BSS_ACTIVE 0x00000044 +#define EVENT_WEP_ICV_ERR 0x00000046 +#define EVENT_HS_ACT_REQ 0x00000047 +#define EVENT_BW_CHANGE 0x00000048 +#define EVENT_UAP_MIC_COUNTERMEASURES 0x0000004c +#define EVENT_HOSTWAKE_STAIE 0x0000004d +#define EVENT_CHANNEL_SWITCH_ANN 0x00000050 +#define EVENT_RADAR_DETECTED 0x00000053 +#define EVENT_CHANNEL_REPORT_RDY 0x00000054 +#define EVENT_TX_DATA_PAUSE 0x00000055 +#define EVENT_EXT_SCAN_REPORT 0x00000058 +#define EVENT_RXBA_SYNC 0x00000059 +#define EVENT_REMAIN_ON_CHAN_EXPIRED 0x0000005f +#define EVENT_UNKNOWN_DEBUG 0x00000063 +#define EVENT_BG_SCAN_STOPPED 0x00000065 +#define EVENT_MULTI_CHAN_INFO 0x0000006a +#define EVENT_FW_DUMP_INFO 0x00000073 +#define EVENT_TX_STATUS_REPORT 0x00000074 +#define EVENT_BT_COEX_WLAN_PARA_CHANGE 0X00000076 +#define EVENT_VDLL_IND 0x00000081 + +#define EVENT_ID_MASK 0xffff +#define BSS_NUM_MASK 0xf + +#define EVENT_GET_BSS_NUM(event_cause) \ + (((event_cause) >> 16) & BSS_NUM_MASK) + +#define EVENT_GET_BSS_TYPE(event_cause) \ + (((event_cause) >> 24) & 0x00ff) + +#define NXPWIFI_MAX_PATTERN_LEN 40 +#define NXPWIFI_MAX_OFFSET_LEN 100 +#define NXPWIFI_MAX_ND_MATCH_SETS 10 + +#define STACK_NBYTES 100 +#define TYPE_DNUM 1 +#define TYPE_BYTESEQ 2 +#define MAX_OPERAND 0x40 +#define TYPE_EQ (MAX_OPERAND + 1) +#define TYPE_EQ_DNUM (MAX_OPERAND + 2) +#define TYPE_EQ_BIT (MAX_OPERAND + 3) +#define TYPE_AND (MAX_OPERAND + 4) +#define TYPE_OR (MAX_OPERAND + 5) +#define MEF_MODE_HOST_SLEEP 1 +#define MEF_ACTION_ALLOW_AND_WAKEUP_HOST 3 +#define MEF_ACTION_AUTO_ARP 0x10 +#define NXPWIFI_CRITERIA_BROADCAST BIT(0) +#define NXPWIFI_CRITERIA_UNICAST BIT(1) +#define NXPWIFI_CRITERIA_MULTICAST BIT(3) +#define NXPWIFI_MAX_SUPPORTED_IPADDR 4 + +#define NXPWIFI_DEF_CS_UNIT_TIME 2 +#define NXPWIFI_DEF_CS_THR_OTHERLINK 10 +#define NXPWIFI_DEF_THR_DIRECTLINK 0 +#define NXPWIFI_DEF_CS_TIME 10 +#define NXPWIFI_DEF_CS_TIMEOUT 16 +#define NXPWIFI_DEF_CS_REG_CLASS 12 +#define NXPWIFI_DEF_CS_PERIODICITY 1 + +#define NXPWIFI_FW_V15 15 + +#define NXPWIFI_MASTER_RADAR_DET_MASK BIT(1) + +struct nxpwifi_ie_types_header { + __le16 type; + __le16 len; +} __packed; + +struct nxpwifi_ie_types_data { + struct nxpwifi_ie_types_header header; + u8 data[]; +} __packed; + +/* Generic TLV wrapper for firmware data */ +struct nxpwifi_tlv { + __le16 type; + __le16 len; + u8 data[]; +} __packed; + +#define NXPWIFI_TxPD_POWER_MGMT_NULL_PACKET 0x01 +#define NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET 0x08 +#define NXPWIFI_TXPD_FLAGS_REQ_TX_STATUS 0x20 + +enum HS_WAKEUP_REASON { + NO_HSWAKEUP_REASON = 0, + BCAST_DATA_MATCHED, + MCAST_DATA_MATCHED, + UCAST_DATA_MATCHED, + MASKTABLE_EVENT_MATCHED, + NON_MASKABLE_EVENT_MATCHED, + NON_MASKABLE_CONDITION_MATCHED, + MAGIC_PATTERN_MATCHED, + CONTROL_FRAME_MATCHED, + MANAGEMENT_FRAME_MATCHED, + GTK_REKEY_FAILURE, + RESERVED +}; + +struct txpd { + u8 bss_type; + u8 bss_num; + __le16 tx_pkt_length; + __le16 tx_pkt_offset; + __le16 tx_pkt_type; + __le32 tx_control; + u8 priority; + u8 flags; + u8 pkt_delay_2ms; + u8 reserved1[2]; + u8 tx_token_id; + u8 reserved[2]; +} __packed; + +struct rxpd { + u8 bss_type; + u8 bss_num; + __le16 rx_pkt_length; + __le16 rx_pkt_offset; + __le16 rx_pkt_type; + __le16 seq_num; + u8 priority; + u8 rx_rate; + s8 snr; + s8 nf; + /* + * rate_info bit definition (FW encoded) + * + * [1:0] format + * 00 = legacy + * 01 = HT + * 10 = VHT + * 11 = HE + * + * [3:2] bandwidth + * 00 = 20 MHz + * 01 = 40 MHz + * 10 = 80 MHz + * 11 = 160 MHz + * + * [4] GI (HT/VHT) / HE GI LSB + * HT/VHT: + * 0 = LGI + * 1 = SGI + * + * HE: + * used as GI[0] + * + * [5] STBC + * 0 = no STBC + * 1 = STBC enabled + * + * [6] LDPC + * 0 = BCC + * 1 = LDPC + * + * [7] HE GI MSB + * + * HE GI encoding (combined from bit7:bit4): + * GI[1:0] = {bit7, bit4} + * + * 00 = 0.8 us + * 01 = 1.6 us + * 10 = 3.2 us + * 11 = reserved / undefined + */ + u8 rate_info; + u8 reserved[3]; + u8 flags; + u8 antenna; + /* toa_tod_tstamps: [31:0] ToA, [63:32] ToD (ns). */ + __le64 toa_tod_tstamps; + /* rx info */ + __le32 rx_info; + /* Reserved */ + u8 reserved3[8]; + u8 ta_mac[6]; + u8 reserved4[2]; +} __packed; + +struct radiotap_timestamp { + /* device timestamp */ + u64 device_timestamp; + /* accuracy */ + u16 accuracy; + /* + * unit: + * 0 milliseconds, + * 1 microseconds, + * 2 nanoseconds, + * 3-15 reserved + */ + u8 unit : 4; + /* + * position: + * 0 first bit (or symbol containing it) of MPDU - matches TSFT field + * 1 signal acquisition at start of PLCP + * 2 end of PPDU + * 3 end of MPDU (after FCS) + * 4-14 reserved + * 15 unknown or vendor/OOB defined + */ + u8 position : 4; + /* + * flags + * 0x01 32-bit counter (high 32 bits are unused) + * 0x02 accuracy known + * 0xFC reserved + */ + u8 flags; +} __packed; + +struct rxpd_extra_info { + /* flags */ + u8 flags; + /* channel.flags */ + u16 channel_flags; + /* mcs.known */ + u8 mcs_known; + /* mcs.flags */ + u8 mcs_flags; + /* vht/he sig1 */ + u32 vht_he_sig1; + /* vht/he sig2 */ + u32 vht_he_sig2; + /* HE user idx */ + u32 user_idx; + /** timestamp */ + struct radiotap_timestamp timestamp; + /** PLCP CRC Failed */ + u8 plcp_crc_failed; + u8 rssi_dbm_a; + u8 rssi_dbm_b; +} __packed; + +struct uap_txpd { + u8 bss_type; + u8 bss_num; + __le16 tx_pkt_length; + __le16 tx_pkt_offset; + __le16 tx_pkt_type; + __le32 tx_control; + u8 priority; + u8 flags; + u8 pkt_delay_2ms; + u8 reserved1[2]; + u8 tx_token_id; + u8 reserved[2]; +} __packed; + +struct uap_rxpd { + u8 bss_type; + u8 bss_num; + __le16 rx_pkt_length; + __le16 rx_pkt_offset; + __le16 rx_pkt_type; + __le16 seq_num; + u8 priority; + u8 rx_rate; + s8 snr; + s8 nf; + u8 ht_info; + u8 reserved[3]; + u8 flags; +} __packed; + +struct nxpwifi_auth { + __le16 auth_alg; + __le16 auth_transaction; + __le16 status_code; + /* possibly followed by Challenge text */ + u8 variable[]; +} __packed; + +struct nxpwifi_ieee80211_mgmt { + __le16 frame_control; + __le16 duration; + u8 da[ETH_ALEN]; + u8 sa[ETH_ALEN]; + u8 bssid[ETH_ALEN]; + __le16 seq_ctrl; + u8 addr4[ETH_ALEN]; + struct nxpwifi_auth auth; +} __packed; + +struct nxpwifi_fw_chan_stats { + u8 chan_num; + u8 bandcfg; + u8 flags; + s8 noise; + __le16 total_bss; + __le16 cca_scan_dur; + __le16 cca_busy_dur; +} __packed; + +enum nxpwifi_chan_scan_mode_bitmasks { + NXPWIFI_PASSIVE_SCAN = BIT(0), + NXPWIFI_DISABLE_CHAN_FILT = BIT(1), + NXPWIFI_HIDDEN_SSID_REPORT = BIT(4), +}; + +struct nxpwifi_chan_scan_param_set { + u8 band_cfg; + u8 chan_number; + u8 chan_scan_mode_bmap; + __le16 min_scan_time; + __le16 max_scan_time; +} __packed; + +struct nxpwifi_ie_types_chan_list_param_set { + struct nxpwifi_ie_types_header header; + struct nxpwifi_chan_scan_param_set chan_scan_param[]; +} __packed; + +struct nxpwifi_ie_types_rxba_sync { + struct nxpwifi_ie_types_header header; + u8 mac[ETH_ALEN]; + u8 tid; + u8 reserved; + __le16 seq_num; + __le16 bitmap_len; + u8 bitmap[]; +} __packed; + +struct chan_band_param_set { + u8 radio_type; + u8 chan_number; +}; + +struct nxpwifi_ie_types_chan_band_list_param_set { + struct nxpwifi_ie_types_header header; + struct chan_band_param_set chan_band_param[]; +} __packed; + +struct nxpwifi_ie_types_rates_param_set { + struct nxpwifi_ie_types_header header; + u8 rates[]; +} __packed; + +struct nxpwifi_ie_types_ssid_param_set { + struct nxpwifi_ie_types_header header; + u8 ssid[]; +} __packed; + +struct nxpwifi_ie_types_host_mlme { + struct nxpwifi_ie_types_header header; + u8 host_mlme; +} __packed; + +struct nxpwifi_ie_types_num_probes { + struct nxpwifi_ie_types_header header; + __le16 num_probes; +} __packed; + +struct nxpwifi_ie_types_repeat_count { + struct nxpwifi_ie_types_header header; + __le16 repeat_count; +} __packed; + +struct nxpwifi_ie_types_min_rssi_threshold { + struct nxpwifi_ie_types_header header; + __le16 rssi_threshold; +} __packed; + +struct nxpwifi_ie_types_bgscan_start_later { + struct nxpwifi_ie_types_header header; + __le16 start_later; +} __packed; + +struct nxpwifi_ie_types_scan_chan_gap { + struct nxpwifi_ie_types_header header; + /* time gap in TUs to be used between two consecutive channels scan */ + __le16 chan_gap; +} __packed; + +struct nxpwifi_ie_types_random_mac { + struct nxpwifi_ie_types_header header; + u8 mac[ETH_ALEN]; +} __packed; + +struct nxpwifi_ietypes_chanstats { + struct nxpwifi_ie_types_header header; + struct nxpwifi_fw_chan_stats chanstats[]; +} __packed; + +struct nxpwifi_ie_types_wildcard_ssid_params { + struct nxpwifi_ie_types_header header; + u8 max_ssid_length; + u8 ssid[]; +} __packed; + +#define TSF_DATA_SIZE 8 +struct nxpwifi_ie_types_tsf_timestamp { + struct nxpwifi_ie_types_header header; + u8 tsf_data[]; +} __packed; + +struct nxpwifi_cf_param_set { + u8 cfp_cnt; + u8 cfp_period; + __le16 cfp_max_duration; + __le16 cfp_duration_remaining; +} __packed; + +struct nxpwifi_ibss_param_set { + __le16 atim_window; +} __packed; + +struct nxpwifi_ie_types_ss_param_set { + struct nxpwifi_ie_types_header header; + union { + struct nxpwifi_cf_param_set cf_param_set[1]; + struct nxpwifi_ibss_param_set ibss_param_set[1]; + } cf_ibss; +} __packed; + +struct nxpwifi_fh_param_set { + __le16 dwell_time; + u8 hop_set; + u8 hop_pattern; + u8 hop_index; +} __packed; + +struct nxpwifi_ds_param_set { + u8 current_chan; +} __packed; + +struct nxpwifi_ie_types_phy_param_set { + struct nxpwifi_ie_types_header header; + union { + struct nxpwifi_fh_param_set fh_param_set[1]; + struct nxpwifi_ds_param_set ds_param_set[1]; + } fh_ds; +} __packed; + +struct nxpwifi_ie_types_auth_type { + struct nxpwifi_ie_types_header header; + __le16 auth_type; +} __packed; + +struct nxpwifi_ie_types_vendor_param_set { + struct nxpwifi_ie_types_header header; + u8 ie[NXPWIFI_MAX_VSIE_LEN]; +}; + +#define NXPWIFI_AUTHTYPE_SAE 6 + +struct nxpwifi_ie_types_sae_pwe_mode { + struct nxpwifi_ie_types_header header; + u8 pwe[]; +} __packed; + +struct nxpwifi_ie_types_rsn_param_set { + struct nxpwifi_ie_types_header header; + u8 rsn_ie[]; +} __packed; + +#define KEYPARAMSET_FIXED_LEN 6 + +#define IGTK_PN_LEN 8 + +struct nxpwifi_cmac_param { + u8 ipn[IGTK_PN_LEN]; + u8 key[WLAN_KEY_LEN_AES_CMAC]; +} __packed; + +struct nxpwifi_wep_param { + __le16 key_len; + u8 key[WLAN_KEY_LEN_WEP104]; +} __packed; + +struct nxpwifi_tkip_param { + u8 pn[WPA_PN_SIZE]; + __le16 key_len; + u8 key[WLAN_KEY_LEN_TKIP]; +} __packed; + +struct nxpwifi_aes_param { + u8 pn[WPA_PN_SIZE]; + __le16 key_len; + u8 key[WLAN_KEY_LEN_CCMP_256]; +} __packed; + +struct nxpwifi_cmac_aes_param { + u8 ipn[IGTK_PN_LEN]; + __le16 key_len; + u8 key[WLAN_KEY_LEN_AES_CMAC]; +} __packed; + +struct nxpwifi_gmac_aes_param { + u8 ipn[IGTK_PN_LEN]; + __le16 key_len; + u8 key[WLAN_KEY_LEN_BIP_GMAC_256]; +} __packed; + +struct nxpwifi_ie_type_key_param_set { + __le16 type; + __le16 len; + u8 mac_addr[ETH_ALEN]; + u8 key_idx; + u8 key_type; + __le16 key_info; + union { + struct nxpwifi_wep_param wep; + struct nxpwifi_tkip_param tkip; + struct nxpwifi_aes_param aes; + struct nxpwifi_cmac_aes_param cmac_aes; + struct nxpwifi_gmac_aes_param gmac_aes; + } key_params; +} __packed; + +struct host_cmd_ds_802_11_key_material { + __le16 action; + struct nxpwifi_ie_type_key_param_set key_param_set; +} __packed; + +struct host_cmd_ds_gen { + __le16 command; + __le16 size; + __le16 seq_num; + __le16 result; +}; + +#define S_DS_GEN sizeof(struct host_cmd_ds_gen) + +enum sleep_resp_ctrl { + RESP_NOT_NEEDED = 0, + RESP_NEEDED, +}; + +struct nxpwifi_ps_param { + __le16 null_pkt_interval; + __le16 multiple_dtims; + __le16 bcn_miss_timeout; + __le16 local_listen_interval; + __le16 reserved; + __le16 mode; + __le16 delay_to_ps; +} __packed; + +#define HS_DEF_WAKE_INTERVAL 100 +#define HS_DEF_INACTIVITY_TIMEOUT 50 + +struct nxpwifi_ps_param_in_hs { + struct nxpwifi_ie_types_header header; + __le32 hs_wake_int; + __le32 hs_inact_timeout; +} __packed; + +#define BITMAP_AUTO_DS 0x01 +#define BITMAP_STA_PS 0x10 + +struct nxpwifi_ie_types_auto_ds_param { + struct nxpwifi_ie_types_header header; + __le16 deep_sleep_timeout; +} __packed; + +struct nxpwifi_ie_types_ps_param { + struct nxpwifi_ie_types_header header; + struct nxpwifi_ps_param param; +} __packed; + +struct host_cmd_ds_802_11_ps_mode_enh { + __le16 action; + + union { + struct nxpwifi_ps_param opt_ps; + __le16 ps_bitmap; + } params; +} __packed; + +enum API_VER_ID { + KEY_API_VER_ID = 1, + FW_API_VER_ID = 2, + UAP_FW_API_VER_ID = 3, + CHANRPT_API_VER_ID = 4, + FW_HOTFIX_VER_ID = 5, +}; + +struct hw_spec_api_rev { + struct nxpwifi_ie_types_header header; + __le16 api_id; + u8 major_ver; + u8 minor_ver; +} __packed; + +struct hw_spec_max_conn { + struct nxpwifi_ie_types_header header; + u8 reserved; + u8 max_sta_conn; +} __packed; + +struct hw_spec_extension { + struct nxpwifi_ie_types_header header; + u8 ext_id; + u8 tlv[]; +} __packed; + +/* HE MAC Capabilities Information field BIT 1 for TWT Req */ +#define HE_MAC_CAP_TWT_REQ_SUPPORT BIT(1) +/* HE MAC Capabilities Information field BIT 2 for TWT Resp*/ +#define HE_MAC_CAP_TWT_RESP_SUPPORT BIT(2) + +struct nxpwifi_ie_types_he_cap { + struct nxpwifi_ie_types_header header; + u8 ext_id; + u8 he_mac_cap[6]; + u8 he_phy_cap[11]; + __le16 rx_mcs_80; + __le16 tx_mcs_80; + __le16 rx_mcs_160; + __le16 tx_mcs_160; + __le16 rx_mcs_80p80; + __le16 tx_mcs_80p80; + u8 val[20]; +} __packed; + +struct nxpwifi_ie_types_he_op { + struct nxpwifi_ie_types_header header; + u8 ext_id; + __le16 he_op_param1; + u8 he_op_param2; + u8 bss_color_info; + __le16 basic_he_mcs_nss; + u8 option[9]; +} __packed; + +struct hw_spec_secure_boot_uuid { + struct nxpwifi_ie_types_header header; + __le64 uuid_lo; + __le64 uuid_hi; +} __packed; + +struct hw_spec_fw_cap_info { + struct nxpwifi_ie_types_header header; + __le32 fw_cap_info; + __le32 fw_cap_ext; +} __packed; + +/* NXP proprietary region codes reported by firmware via GET_HW_SPEC. + * These values are stored in the device OTP/calibration data and + * correspond to regulatory domains used for channel/power table selection. + */ +enum nxpwifi_region_code { + NXPWIFI_REGION_WORLD = 0x00, + NXPWIFI_REGION_FCC = 0x10, /* US, Canada-like */ + NXPWIFI_REGION_IC = 0x20, /* Canada */ + NXPWIFI_REGION_ETSI = 0x30, /* Europe */ + NXPWIFI_REGION_SPAIN = 0x31, + NXPWIFI_REGION_FRANCE = 0x32, + NXPWIFI_REGION_JAPAN = 0x40, + NXPWIFI_REGION_JAPAN1 = 0x41, + NXPWIFI_REGION_CHINA = 0x50, +}; + +struct host_cmd_ds_get_hw_spec { + __le16 hw_if_version; + __le16 version; + __le16 reserved; + __le16 num_of_mcast_adr; + u8 permanent_addr[ETH_ALEN]; + __le16 region_code; + __le16 number_of_antenna; + __le32 fw_release_number; + __le32 hw_dev_cap; + __le32 reserved_1; + __le32 reserved_2; + __le32 fw_cap_info; + __le32 dot_11n_dev_cap; + u8 dev_mcs_support; + __le16 mp_end_port; /* SDIO only, reserved for other interfaces */ + __le16 mgmt_buf_count; /* mgmt element buffer count */ + __le32 reserved_3; + __le32 reserved_4; + __le32 dot_11ac_dev_cap; + __le32 dot_11ac_mcs_support; + u8 tlv[]; +} __packed; + +struct host_cmd_ds_802_11_rssi_info { + __le16 action; + __le16 ndata; + __le16 nbcn; + __le16 reserved[9]; + long long reserved_1; +} __packed; + +struct host_cmd_ds_802_11_rssi_info_rsp { + __le16 action; + __le16 ndata; + __le16 nbcn; + __le16 data_rssi_last; + __le16 data_nf_last; + __le16 data_rssi_avg; + __le16 data_nf_avg; + __le16 bcn_rssi_last; + __le16 bcn_nf_last; + __le16 bcn_rssi_avg; + __le16 bcn_nf_avg; + long long tsf_bcn; +} __packed; + +struct host_cmd_ds_802_11_mac_address { + __le16 action; + u8 mac_addr[ETH_ALEN]; +} __packed; + +struct host_cmd_ds_mac_control { + __le32 action; +}; + +struct host_cmd_ds_mac_multicast_adr { + __le16 action; + __le16 num_of_adrs; + u8 mac_list[NXPWIFI_MAX_MULTICAST_LIST_SIZE][ETH_ALEN]; +} __packed; + +struct host_cmd_ds_802_11_deauthenticate { + u8 mac_addr[ETH_ALEN]; + __le16 reason_code; +} __packed; + +struct host_cmd_ds_802_11_associate { + u8 peer_sta_addr[ETH_ALEN]; + __le16 cap_info_bitmap; + __le16 listen_interval; + __le16 beacon_period; + u8 dtim_period; +} __packed; + +struct ieee_types_assoc_rsp { + __le16 cap_info_bitmap; + __le16 status_code; + __le16 a_id; + u8 ie_buffer[]; +} __packed; + +struct host_cmd_ds_802_11_associate_rsp { + struct ieee_types_assoc_rsp assoc_rsp; +} __packed; + +struct ieee_types_cf_param_set { + u8 element_id; + u8 len; + u8 cfp_cnt; + u8 cfp_period; + __le16 cfp_max_duration; + __le16 cfp_duration_remaining; +} __packed; + +struct ieee_types_fh_param_set { + u8 element_id; + u8 len; + __le16 dwell_time; + u8 hop_set; + u8 hop_pattern; + u8 hop_index; +} __packed; + +struct ieee_types_ds_param_set { + u8 element_id; + u8 len; + u8 current_chan; +} __packed; + +union ieee_types_phy_param_set { + struct ieee_types_fh_param_set fh_param_set; + struct ieee_types_ds_param_set ds_param_set; +} __packed; + +struct ieee_types_oper_mode_ntf { + u8 element_id; + u8 len; + u8 oper_mode; +} __packed; + +struct host_cmd_ds_802_11_get_log { + __le32 mcast_tx_frame; + __le32 failed; + __le32 retry; + __le32 multi_retry; + __le32 frame_dup; + __le32 rts_success; + __le32 rts_failure; + __le32 ack_failure; + __le32 rx_frag; + __le32 mcast_rx_frame; + __le32 fcs_error; + __le32 tx_frame; + __le32 reserved; + __le32 wep_icv_err_cnt[4]; + __le32 bcn_rcv_cnt; + __le32 bcn_miss_cnt; +} __packed; + +/* Enumeration for rate format */ +enum nxpwifi_rate_format { + NXPWIFI_RATE_FORMAT_LG = 0, + NXPWIFI_RATE_FORMAT_HT, + NXPWIFI_RATE_FORMAT_VHT, + NXPWIFI_RATE_FORMAT_HE, + NXPWIFI_RATE_FORMAT_AUTO = 0xFF, +}; + +struct host_cmd_ds_tx_rate_query { + u8 tx_rate; + /* + * Tx Rate Info: For 802.11 AC cards + * + * [Bit 0-1] tx rate format: LG = 0, HT = 1, VHT = 2 + * [Bit 2-3] HT/VHT Bandwidth: BW20 = 0, BW40 = 1, BW80 = 2, BW160 = 3 + * [Bit 4] HT/VHT Guard Interval: LGI = 0, SGI = 1 + * + * For non-802.11 AC cards + * Ht Info [Bit 0] RxRate format: LG=0, HT=1 + * [Bit 1] HT Bandwidth: BW20 = 0, BW40 = 1 + * [Bit 2] HT Guard Interval: LGI = 0, SGI = 1 + */ + u8 ht_info; +} __packed; + +struct nxpwifi_tx_pause_tlv { + struct nxpwifi_ie_types_header header; + u8 peermac[ETH_ALEN]; + u8 tx_pause; + u8 pkt_cnt; +} __packed; + +enum host_sleep_action { + HS_CONFIGURE = 0x0001, + HS_ACTIVATE = 0x0002, +}; + +struct nxpwifi_hs_config_param { + __le32 conditions; + u8 gpio; + u8 gap; +} __packed; + +struct hs_activate_param { + __le16 resp_ctrl; +} __packed; + +struct host_cmd_ds_802_11_hs_cfg_enh { + __le16 action; + + union { + struct nxpwifi_hs_config_param hs_config; + struct hs_activate_param hs_activate; + } params; +} __packed; + +enum SNMP_MIB_INDEX { + OP_RATE_SET_I = 1, + DTIM_PERIOD_I = 3, + RTS_THRESH_I = 5, + SHORT_RETRY_LIM_I = 6, + LONG_RETRY_LIM_I = 7, + FRAG_THRESH_I = 8, + DOT11D_I = 9, + DOT11H_I = 10, +}; + +enum nxpwifi_assocmd_failurepoint { + NXPWIFI_ASSOC_CMD_SUCCESS = 0, + NXPWIFI_ASSOC_CMD_FAILURE_ASSOC, + NXPWIFI_ASSOC_CMD_FAILURE_AUTH, + NXPWIFI_ASSOC_CMD_FAILURE_JOIN +}; + +#define MAX_SNMP_BUF_SIZE 128 + +struct host_cmd_ds_802_11_snmp_mib { + __le16 query_type; + __le16 oid; + __le16 buf_size; + u8 value[]; +} __packed; + +struct nxpwifi_rate_scope { + __le16 type; + __le16 length; + __le16 hr_dsss_rate_bitmap; + __le16 ofdm_rate_bitmap; + __le16 ht_mcs_rate_bitmap[8]; + __le16 vht_mcs_rate_bitmap[8]; +} __packed; + +struct nxpwifi_rate_drop_pattern { + __le16 type; + __le16 length; + __le32 rate_drop_mode; +} __packed; + +struct host_cmd_ds_tx_rate_cfg { + __le16 action; + __le16 cfg_index; +} __packed; + +struct nxpwifi_power_group { + u8 modulation_class; + u8 first_rate_code; + u8 last_rate_code; + s8 power_step; + s8 power_min; + s8 power_max; + u8 ht_bandwidth; + u8 reserved; +} __packed; + +struct nxpwifi_types_power_group { + __le16 type; + __le16 length; +} __packed; + +struct host_cmd_ds_txpwr_cfg { + __le16 action; + __le16 cfg_index; + __le32 mode; +} __packed; + +struct host_cmd_ds_rf_tx_pwr { + __le16 action; + __le16 cur_level; + u8 max_power; + u8 min_power; +} __packed; + +struct host_cmd_ds_rf_ant_mimo { + __le16 action_tx; + __le16 tx_ant_mode; + __le16 action_rx; + __le16 rx_ant_mode; +} __packed; + +struct host_cmd_ds_rf_ant_siso { + __le16 action; + __le16 ant_mode; +} __packed; + +#define BAND_CFG_CHAN_BAND_MASK 0x03 +#define BAND_CFG_CHAN_BAND_SHIFT_BIT 0 +#define BAND_CFG_CHAN_WIDTH_MASK 0x0C +#define BAND_CFG_CHAN_WIDTH_SHIFT_BIT 2 +#define BAND_CFG_CHAN2_OFFSET_MASK 0x30 +#define BAND_CFG_CHAN2_SHIFT_BIT 4 + +struct nxpwifi_chan_desc { + __le16 start_freq; + u8 band_cfg; + u8 chan_num; +} __packed; + +struct host_cmd_ds_chan_rpt_req { + struct nxpwifi_chan_desc chan_desc; + __le32 msec_dwell_time; +} __packed; + +struct host_cmd_ds_chan_rpt_event { + __le32 result; + __le64 start_tsf; + __le32 duration; + u8 tlvbuf[]; +} __packed; + +struct host_cmd_sdio_sp_rx_aggr_cfg { + u8 action; + u8 enable; + __le16 block_size; +} __packed; + +struct nxpwifi_fixed_bcn_param { + __le64 timestamp; + __le16 beacon_period; + __le16 cap_info_bitmap; +} __packed; + +struct nxpwifi_event_scan_result { + __le16 event_id; + u8 bss_index; + u8 bss_type; + u8 more_event; + u8 reserved[3]; + __le16 buf_size; + u8 num_of_set; +} __packed; + +struct tx_status_event { + u8 packet_type; + u8 tx_token_id; + u8 status; +} __packed; + +#define NXPWIFI_USER_SCAN_CHAN_MAX 50 + +#define NXPWIFI_MAX_SSID_LIST_LENGTH 10 + +struct nxpwifi_scan_cmd_config { + /* BSS mode to be sent in the firmware command */ + u8 bss_mode; + + /* Specific BSSID used to filter scan results in the firmware */ + u8 specific_bssid[ETH_ALEN]; + + /* Length of TLVs sent in command starting at tlvBuffer */ + u32 tlv_buf_len; + + /* + * SSID TLV(s) and ChanList TLVs to be sent in the firmware command + * + * TLV_TYPE_CHANLIST, nxpwifi_ie_types_chan_list_param_set + * WLAN_EID_SSID, nxpwifi_ie_types_ssid_param_set + */ + u8 tlv_buf[]; /* SSID TLV(s) and ChanList TLVs are stored here */ +} __packed; + +struct nxpwifi_user_scan_chan { + u8 chan_number; + u8 radio_type; + u8 scan_type; + u8 reserved; + u32 scan_time; +} __packed; + +struct nxpwifi_user_scan_cfg { + /* BSS mode to be sent in the firmware command */ + u8 bss_mode; + /* Configure the number of probe requests for active chan scans */ + u8 num_probes; + u8 reserved; + /* BSSID filter sent in the firmware command to limit the results */ + u8 specific_bssid[ETH_ALEN]; + /* SSID filter list used in the firmware to limit the scan results */ + struct cfg80211_ssid *ssid_list; + u8 num_ssids; + /* Variable number (fixed maximum) of channels to scan up */ + struct nxpwifi_user_scan_chan chan_list[NXPWIFI_USER_SCAN_CHAN_MAX]; + u16 scan_chan_gap; + u8 random_mac[ETH_ALEN]; +} __packed; + +#define NXPWIFI_BG_SCAN_CHAN_MAX 38 +#define NXPWIFI_BSS_MODE_INFRA 1 +#define NXPWIFI_BGSCAN_ACT_GET 0x0000 +#define NXPWIFI_BGSCAN_ACT_SET 0x0001 +#define NXPWIFI_BGSCAN_ACT_SET_ALL 0xff01 +/** ssid match */ +#define NXPWIFI_BGSCAN_SSID_MATCH 0x0001 +/** ssid match and RSSI exceeded */ +#define NXPWIFI_BGSCAN_SSID_RSSI_MATCH 0x0004 +/**wait for all channel scan to complete to report scan result*/ +#define NXPWIFI_BGSCAN_WAIT_ALL_CHAN_DONE 0x80000000 + +struct nxpwifi_bg_scan_cfg { + u16 action; + u8 enable; + u8 bss_type; + u8 chan_per_scan; + u32 scan_interval; + u32 report_condition; + u8 num_probes; + u8 rssi_threshold; + u8 snr_threshold; + u16 repeat_count; + u16 start_later; + struct cfg80211_match_set *ssid_list; + u8 num_ssids; + struct nxpwifi_user_scan_chan chan_list[NXPWIFI_BG_SCAN_CHAN_MAX]; + u16 scan_chan_gap; +} __packed; + +struct ie_body { + u8 grp_key_oui[4]; + u8 ptk_cnt[2]; + u8 ptk_body[4]; +} __packed; + +struct host_cmd_ds_802_11_scan { + u8 bss_mode; + u8 bssid[ETH_ALEN]; + u8 tlv_buffer[]; +} __packed; + +struct host_cmd_ds_802_11_scan_rsp { + __le16 bss_descript_size; + u8 number_of_sets; + u8 bss_desc_and_tlv_buffer[]; +} __packed; + +struct host_cmd_ds_802_11_scan_ext { + u32 reserved; + u8 tlv_buffer[]; +} __packed; + +struct nxpwifi_ie_types_bss_mode { + struct nxpwifi_ie_types_header header; + u8 bss_mode; +} __packed; + +struct nxpwifi_ie_types_scan_rsp { + struct nxpwifi_ie_types_header header; + u8 bssid[ETH_ALEN]; + u8 frame_body[]; +} __packed; + +struct nxpwifi_ie_types_scan_inf { + struct nxpwifi_ie_types_header header; + __le16 rssi; + __le16 anpi; + u8 cca_busy_fraction; + u8 radio_type; + u8 channel; + u8 reserved; + __le64 tsf; +} __packed; + +struct host_cmd_ds_802_11_bg_scan_config { + __le16 action; + u8 enable; + u8 bss_type; + u8 chan_per_scan; + u8 reserved; + __le16 reserved1; + __le32 scan_interval; + __le32 reserved2; + __le32 report_condition; + __le16 reserved3; + u8 tlv[]; +} __packed; + +struct host_cmd_ds_802_11_bg_scan_query { + u8 flush; +} __packed; + +struct host_cmd_ds_802_11_bg_scan_query_rsp { + __le32 report_condition; + struct host_cmd_ds_802_11_scan_rsp scan_resp; +} __packed; + +struct nxpwifi_ietypes_domain_code { + struct nxpwifi_ie_types_header header; + u8 domain_code; + u8 reserved; +} __packed; + +struct nxpwifi_ietypes_domain_param_set { + struct nxpwifi_ie_types_header header; + u8 country_code[IEEE80211_COUNTRY_STRING_LEN]; + struct ieee80211_country_ie_triplet triplet[]; +} __packed; + +struct host_cmd_ds_802_11d_domain_info { + __le16 action; + struct nxpwifi_ietypes_domain_param_set domain; +} __packed; + +struct host_cmd_ds_802_11d_domain_info_rsp { + __le16 action; + struct nxpwifi_ietypes_domain_param_set domain; +} __packed; + +struct host_cmd_ds_11n_addba_req { + u8 add_req_result; + u8 peer_mac_addr[ETH_ALEN]; + u8 dialog_token; + __le16 block_ack_param_set; + __le16 block_ack_tmo; + __le16 ssn; +} __packed; + +struct host_cmd_ds_11n_addba_rsp { + u8 add_rsp_result; + u8 peer_mac_addr[ETH_ALEN]; + u8 dialog_token; + __le16 status_code; + __le16 block_ack_param_set; + __le16 block_ack_tmo; + __le16 ssn; +} __packed; + +struct host_cmd_ds_11n_delba { + u8 del_result; + u8 peer_mac_addr[ETH_ALEN]; + __le16 del_ba_param_set; + __le16 reason_code; + u8 reserved; +} __packed; + +struct host_cmd_ds_11n_batimeout { + u8 tid; + u8 peer_mac_addr[ETH_ALEN]; + u8 origninator; +} __packed; + +struct host_cmd_ds_11n_cfg { + __le16 action; + __le16 ht_tx_cap; + __le16 ht_tx_info; + __le16 misc_config; /* Needed for 802.11AC cards only */ +} __packed; + +struct host_cmd_ds_txbuf_cfg { + __le16 action; + __le16 buff_size; + __le16 mp_end_port; /* SDIO only, reserved for other interfaces */ + __le16 reserved3; +} __packed; + +struct host_cmd_ds_amsdu_aggr_ctrl { + __le16 action; + __le16 enable; + __le16 curr_buf_size; +} __packed; + +struct host_cmd_ds_sta_deauth { + u8 mac[ETH_ALEN]; + __le16 reason; +} __packed; + +struct nxpwifi_ie_types_sta_info { + struct nxpwifi_ie_types_header header; + u8 mac[ETH_ALEN]; + u8 power_mfg_status; + s8 rssi; +}; + +struct host_cmd_ds_sta_list { + __le16 sta_count; + u8 tlv[]; +} __packed; + +struct nxpwifi_ie_types_pwr_capability { + struct nxpwifi_ie_types_header header; + s8 min_pwr; + s8 max_pwr; +}; + +struct nxpwifi_ie_types_local_pwr_constraint { + struct nxpwifi_ie_types_header header; + u8 chan; + u8 constraint; +}; + +struct nxpwifi_ie_types_wmm_param_set { + struct nxpwifi_ie_types_header header; + u8 wmm_ie[]; +} __packed; + +struct nxpwifi_ie_types_mgmt_frame { + struct nxpwifi_ie_types_header header; + __le16 frame_control; + u8 frame_contents[]; +}; + +struct nxpwifi_ie_types_wmm_queue_status { + struct nxpwifi_ie_types_header header; + u8 queue_index; + u8 disabled; + __le16 medium_time; + u8 flow_required; + u8 flow_created; + u32 reserved; +}; + +struct ieee_types_wmm_info { + /* + * WMM Info element - Vendor Specific Header: + * element_id [221/0xdd] + * Len [7] + * Oui [00:50:f2] + * OuiType [2] + * OuiSubType [0] + * Version [1] + */ + struct ieee80211_vendor_ie vend_hdr; + u8 oui_subtype; + u8 version; + + u8 qos_info_bitmap; +} __packed; + +struct host_cmd_ds_wmm_get_status { + u8 queue_status_tlv[sizeof(struct nxpwifi_ie_types_wmm_queue_status) * + IEEE80211_NUM_ACS]; + u8 wmm_param_tlv[sizeof(struct ieee80211_wmm_param_ie) + 2]; +} __packed; + +struct nxpwifi_wmm_ac_status { + u8 disabled; + u8 flow_required; + u8 flow_created; +}; + +struct nxpwifi_ie_types_htcap { + struct nxpwifi_ie_types_header header; + struct ieee80211_ht_cap ht_cap; +} __packed; + +struct nxpwifi_ie_types_vhtcap { + struct nxpwifi_ie_types_header header; + struct ieee80211_vht_cap vht_cap; +} __packed; + +struct nxpwifi_ie_types_aid { + struct nxpwifi_ie_types_header header; + __le16 aid; +} __packed; + +struct nxpwifi_ie_types_oper_mode_ntf { + struct nxpwifi_ie_types_header header; + u8 oper_mode; +} __packed; + +/* VHT Operations element */ +struct nxpwifi_ie_types_vht_oper { + struct nxpwifi_ie_types_header header; + u8 chan_width; + u8 chan_center_freq_1; + u8 chan_center_freq_2; + /* Basic MCS set map, each 2 bits stands for a NSS */ + __le16 basic_mcs_map; +} __packed; + +struct nxpwifi_ie_types_wmmcap { + struct nxpwifi_ie_types_header header; + struct nxpwifi_types_wmm_info wmm_info; +} __packed; + +struct nxpwifi_ie_types_htinfo { + struct nxpwifi_ie_types_header header; + struct ieee80211_ht_operation ht_oper; +} __packed; + +struct nxpwifi_ie_types_2040bssco { + struct nxpwifi_ie_types_header header; + u8 bss_co_2040; +} __packed; + +struct nxpwifi_ie_types_extcap { + struct nxpwifi_ie_types_header header; + u8 ext_capab[]; +} __packed; + +struct host_cmd_ds_mem_access { + __le16 action; + __le16 reserved; + __le32 addr; + __le32 value; +} __packed; + +struct nxpwifi_ie_types_qos_info { + struct nxpwifi_ie_types_header header; + u8 qos_info; +} __packed; + +struct host_cmd_ds_mac_reg_access { + __le16 action; + __le16 offset; + __le32 value; +} __packed; + +struct host_cmd_ds_bbp_reg_access { + __le16 action; + __le16 offset; + u8 value; + u8 reserved[3]; +} __packed; + +struct host_cmd_ds_rf_reg_access { + __le16 action; + __le16 offset; + u8 value; + u8 reserved[3]; +} __packed; + +struct host_cmd_ds_pmic_reg_access { + __le16 action; + __le16 offset; + u8 value; + u8 reserved[3]; +} __packed; + +struct host_cmd_ds_802_11_eeprom_access { + __le16 action; + + __le16 offset; + __le16 byte_count; + u8 value; +} __packed; + +struct nxpwifi_assoc_event { + u8 sta_addr[ETH_ALEN]; + __le16 type; + __le16 len; + __le16 frame_control; + __le16 cap_info; + __le16 listen_interval; + u8 data[]; +} __packed; + +struct host_cmd_ds_sys_config { + __le16 action; + u8 tlv[]; +}; + +struct host_cmd_11ac_vht_cfg { + __le16 action; + u8 band_config; + u8 misc_config; + __le32 cap_info; + __le32 mcs_tx_set; + __le32 mcs_rx_set; +} __packed; + +struct host_cmd_tlv_akmp { + struct nxpwifi_ie_types_header header; + __le16 key_mgmt; + __le16 key_mgmt_operation; +} __packed; + +struct host_cmd_tlv_pwk_cipher { + struct nxpwifi_ie_types_header header; + __le16 proto; + u8 cipher; + u8 reserved; +} __packed; + +struct host_cmd_tlv_gwk_cipher { + struct nxpwifi_ie_types_header header; + u8 cipher; + u8 reserved; +} __packed; + +struct host_cmd_tlv_passphrase { + struct nxpwifi_ie_types_header header; + u8 passphrase[]; +} __packed; + +struct host_cmd_tlv_wep_key { + struct nxpwifi_ie_types_header header; + u8 key_index; + u8 is_default; + u8 key[]; +}; + +struct host_cmd_tlv_auth_type { + struct nxpwifi_ie_types_header header; + u8 auth_type; + u8 pwe_derivation; + u8 transition_disable; +} __packed; + +struct host_cmd_tlv_encrypt_protocol { + struct nxpwifi_ie_types_header header; + __le16 proto; +} __packed; + +struct host_cmd_tlv_ssid { + struct nxpwifi_ie_types_header header; + u8 ssid[]; +} __packed; + +struct host_cmd_tlv_rates { + struct nxpwifi_ie_types_header header; + u8 rates[]; +} __packed; + +struct nxpwifi_ie_types_bssid_list { + struct nxpwifi_ie_types_header header; + u8 bssid[ETH_ALEN]; +} __packed; + +struct host_cmd_tlv_bcast_ssid { + struct nxpwifi_ie_types_header header; + u8 bcast_ctl; +} __packed; + +struct host_cmd_tlv_beacon_period { + struct nxpwifi_ie_types_header header; + __le16 period; +} __packed; + +struct host_cmd_tlv_dtim_period { + struct nxpwifi_ie_types_header header; + u8 period; +} __packed; + +struct host_cmd_tlv_frag_threshold { + struct nxpwifi_ie_types_header header; + __le16 frag_thr; +} __packed; + +struct host_cmd_tlv_rts_threshold { + struct nxpwifi_ie_types_header header; + __le16 rts_thr; +} __packed; + +struct host_cmd_tlv_retry_limit { + struct nxpwifi_ie_types_header header; + u8 limit; +} __packed; + +struct host_cmd_tlv_mac_addr { + struct nxpwifi_ie_types_header header; + u8 mac_addr[ETH_ALEN]; +} __packed; + +struct host_cmd_tlv_channel_band { + struct nxpwifi_ie_types_header header; + u8 band_config; + u8 channel; +} __packed; + +struct host_cmd_tlv_ageout_timer { + struct nxpwifi_ie_types_header header; + __le32 sta_ao_timer; +} __packed; + +struct host_cmd_tlv_power_constraint { + struct nxpwifi_ie_types_header header; + u8 constraint; +} __packed; + +struct nxpwifi_ie_types_btcoex_scan_time { + struct nxpwifi_ie_types_header header; + u8 coex_scan; + u8 reserved; + __le16 min_scan_time; + __le16 max_scan_time; +} __packed; + +struct nxpwifi_ie_types_btcoex_aggr_win_size { + struct nxpwifi_ie_types_header header; + u8 coex_win_size; + u8 tx_win_size; + u8 rx_win_size; + u8 reserved; +} __packed; + +struct nxpwifi_ie_types_robust_coex { + struct nxpwifi_ie_types_header header; + __le32 mode; +} __packed; + +#define NXPWIFI_VERSION_STR_LENGTH 128 + +struct host_cmd_ds_version_ext { + u8 version_str_sel; + char version_str[NXPWIFI_VERSION_STR_LENGTH]; +} __packed; + +struct host_cmd_ds_mgmt_frame_reg { + __le16 action; + __le32 mask; +} __packed; + +struct host_cmd_ds_remain_on_chan { + __le16 action; + u8 status; + u8 reserved; + u8 band_cfg; + u8 channel; + __le32 duration; +} __packed; + +struct host_cmd_ds_802_11_ibss_status { + __le16 action; + __le16 enable; + u8 bssid[ETH_ALEN]; + __le16 beacon_interval; + __le16 atim_window; + __le16 use_g_rate_protect; +} __packed; + +struct nxpwifi_fw_mef_entry { + u8 mode; + u8 action; + __le16 exprsize; + u8 expr[]; +} __packed; + +struct host_cmd_ds_mef_cfg { + __le32 criteria; + __le16 num_entries; + u8 mef_entry_data[]; +} __packed; + +#define CONNECTION_TYPE_INFRA 0 +#define CONNECTION_TYPE_AP 2 + +struct host_cmd_ds_set_bss_mode { + u8 con_type; +} __packed; + +struct host_cmd_ds_pcie_details { + /* TX buffer descriptor ring address */ + __le32 txbd_addr_lo; + __le32 txbd_addr_hi; + /* TX buffer descriptor ring count */ + __le32 txbd_count; + + /* RX buffer descriptor ring address */ + __le32 rxbd_addr_lo; + __le32 rxbd_addr_hi; + /* RX buffer descriptor ring count */ + __le32 rxbd_count; + + /* Event buffer descriptor ring address */ + __le32 evtbd_addr_lo; + __le32 evtbd_addr_hi; + /* Event buffer descriptor ring count */ + __le32 evtbd_count; + + /* Sleep cookie buffer physical address */ + __le32 sleep_cookie_addr_lo; + __le32 sleep_cookie_addr_hi; +} __packed; + +struct nxpwifi_ie_types_rssi_threshold { + struct nxpwifi_ie_types_header header; + u8 abs_value; + u8 evt_freq; +} __packed; + +#define NXPWIFI_DFS_REC_HDR_LEN 8 +#define NXPWIFI_DFS_REC_HDR_NUM 10 +#define NXPWIFI_BIN_COUNTER_LEN 7 + +struct nxpwifi_radar_det_event { + __le32 detect_count; + u8 reg_domain; /*1=fcc, 2=etsi, 3=mic*/ + u8 det_type; /*0=none, 1=pw(chirp), 2=pri(radar)*/ + __le16 pw_chirp_type; + u8 pw_chirp_idx; + u8 pw_value; + u8 pri_radar_type; + u8 pri_bincnt; + u8 bin_counter[NXPWIFI_BIN_COUNTER_LEN]; + u8 num_dfs_records; + u8 dfs_record_hdr[NXPWIFI_DFS_REC_HDR_NUM][NXPWIFI_DFS_REC_HDR_LEN]; + __le32 passed; +} __packed; + +struct nxpwifi_ie_types_multi_chan_info { + struct nxpwifi_ie_types_header header; + __le16 status; + u8 tlv_buffer[]; +} __packed; + +struct nxpwifi_ie_types_mc_group_info { + struct nxpwifi_ie_types_header header; + u8 chan_group_id; + u8 chan_buf_weight; + u8 band_config; + u8 chan_num; + __le32 chan_time; + __le32 reserved; + union { + u8 sdio_func_num; + u8 usb_ep_num; + } hid_num; + u8 intf_num; + u8 bss_type_numlist[]; +} __packed; + +#define MEAS_RPT_MAP_RADAR_MASK 0x08 +#define MEAS_RPT_MAP_RADAR_SHIFT_BIT 3 + +struct nxpwifi_ie_types_chan_rpt_data { + struct nxpwifi_ie_types_header header; + u8 meas_rpt_map; +} __packed; + +struct host_cmd_ds_802_11_subsc_evt { + __le16 action; + __le16 events; +} __packed; + +struct chan_switch_result { + u8 cur_chan; + u8 status; + u8 reason; +} __packed; + +struct nxpwifi_ie { + __le16 ie_index; + __le16 mgmt_subtype_mask; + __le16 ie_length; + u8 ie_buffer[IEEE_MAX_IE_SIZE]; +} __packed; + +#define MAX_MGMT_IE_INDEX 16 +struct nxpwifi_ie_list { + __le16 type; + __le16 len; + struct nxpwifi_ie ie_list[MAX_MGMT_IE_INDEX]; +} __packed; + +struct coalesce_filt_field_param { + u8 operation; + u8 operand_len; + __le16 offset; + u8 operand_byte_stream[4]; +}; + +struct coalesce_receive_filt_rule { + struct nxpwifi_ie_types_header header; + u8 num_of_fields; + u8 pkt_type; + __le16 max_coalescing_delay; + struct coalesce_filt_field_param params[]; +} __packed; + +struct host_cmd_ds_coalesce_cfg { + __le16 action; + __le16 num_of_rules; + u8 rule_data[]; +} __packed; + +struct host_cmd_ds_multi_chan_policy { + __le16 action; + __le16 policy; +} __packed; + +struct host_cmd_ds_robust_coex { + __le16 action; + __le16 reserved; +} __packed; + +struct host_cmd_ds_wakeup_reason { + __le16 wakeup_reason; +} __packed; + +struct host_cmd_ds_gtk_rekey_params { + __le16 action; + u8 kck[NL80211_KCK_LEN]; + u8 kek[NL80211_KEK_LEN]; + __le32 replay_ctr_low; + __le32 replay_ctr_high; +} __packed; + +struct host_cmd_ds_chan_region_cfg { + __le16 action; +} __packed; + +struct host_cmd_ds_pkt_aggr_ctrl { + __le16 action; + __le16 enable; + __le16 tx_aggr_max_size; + __le16 tx_aggr_max_num; + __le16 tx_aggr_align; +} __packed; + +struct host_cmd_ds_sta_configure { + __le16 action; + u8 tlv_buffer[]; +} __packed; + +struct nxpwifi_ie_types_sta_flag { + struct nxpwifi_ie_types_header header; + __le32 sta_flags; +} __packed; + +struct host_cmd_ds_add_station { + __le16 action; + __le16 aid; + u8 peer_mac[ETH_ALEN]; + __le32 listen_interval; + __le16 cap_info; + u8 tlv[]; +} __packed; + +struct host_cmd_11ax_cfg { + __le16 action; + u8 band_config; + u8 tlv[]; +} __packed; + +struct host_cmd_11ax_cmd { + __le16 action; + __le16 sub_id; + u8 val[]; +} __packed; + +struct nxpwifi_802_11_net_monitor { + u32 enable_net_mon; + u32 filter_flag; + u32 band; + u32 channel; + u32 chan_bandwidth; +}; + +struct band_config { + /* Band: 00=2.4, 01=5, 10=6 GHz */ + u8 chan_band : 2; + /* Width: 00=20, 10=40, 11=80 MHz */ + u8 chan_width : 2; + /* Sec offset: 00=None, 01=Above, 11=Below */ + u8 chan_2O_ffset : 2; + /* Chan sel: 00=manual, 01=ACS, 02=Adoption */ + u8 scan_mode : 2; +} __packed; + +struct chan_band_param { + struct band_config band_cfg; + u8 chan_number; +} __packed; + +struct nxpwifi_ie_types_chan_band_list { + struct nxpwifi_ie_types_header header; + struct chan_band_param chan_band_param[]; +} __packed; + +struct host_cmd_ds_802_11_net_monitor { + __le16 action; + __le16 enable_net_mon; + __le16 filter_flag; + struct nxpwifi_ie_types_chan_band_list monitor_chan; +} __packed; + +struct host_cmd_twt_cfg { + __le16 action; + __le16 sub_id; + u8 val[]; +} __packed; + +struct host_cmd_ds_command { + __le16 command; + __le16 size; + __le16 seq_num; + __le16 result; + union { + struct host_cmd_ds_get_hw_spec hw_spec; + struct host_cmd_ds_mac_control mac_ctrl; + struct host_cmd_ds_802_11_mac_address mac_addr; + struct host_cmd_ds_mac_multicast_adr mc_addr; + struct host_cmd_ds_802_11_get_log get_log; + struct host_cmd_ds_802_11_rssi_info rssi_info; + struct host_cmd_ds_802_11_rssi_info_rsp rssi_info_rsp; + struct host_cmd_ds_802_11_snmp_mib smib; + struct host_cmd_ds_tx_rate_query tx_rate; + struct host_cmd_ds_tx_rate_cfg tx_rate_cfg; + struct host_cmd_ds_txpwr_cfg txp_cfg; + struct host_cmd_ds_rf_tx_pwr txp; + struct host_cmd_ds_rf_ant_mimo ant_mimo; + struct host_cmd_ds_rf_ant_siso ant_siso; + struct host_cmd_ds_802_11_ps_mode_enh psmode_enh; + struct host_cmd_ds_802_11_hs_cfg_enh opt_hs_cfg; + struct host_cmd_ds_802_11_scan scan; + struct host_cmd_ds_802_11_scan_ext ext_scan; + struct host_cmd_ds_802_11_scan_rsp scan_resp; + struct host_cmd_ds_802_11_bg_scan_config bg_scan_config; + struct host_cmd_ds_802_11_bg_scan_query bg_scan_query; + struct host_cmd_ds_802_11_bg_scan_query_rsp bg_scan_query_resp; + struct host_cmd_ds_802_11_associate associate; + struct host_cmd_ds_802_11_associate_rsp associate_rsp; + struct host_cmd_ds_802_11_deauthenticate deauth; + struct host_cmd_ds_802_11d_domain_info domain_info; + struct host_cmd_ds_802_11d_domain_info_rsp domain_info_resp; + struct host_cmd_ds_11n_addba_req add_ba_req; + struct host_cmd_ds_11n_addba_rsp add_ba_rsp; + struct host_cmd_ds_11n_delba del_ba; + struct host_cmd_ds_txbuf_cfg tx_buf; + struct host_cmd_ds_amsdu_aggr_ctrl amsdu_aggr_ctrl; + struct host_cmd_ds_11n_cfg htcfg; + struct host_cmd_ds_wmm_get_status get_wmm_status; + struct host_cmd_ds_802_11_key_material key_material; + struct host_cmd_ds_version_ext verext; + struct host_cmd_ds_mgmt_frame_reg reg_mask; + struct host_cmd_ds_remain_on_chan roc_cfg; + struct host_cmd_ds_802_11_ibss_status ibss_coalescing; + struct host_cmd_ds_mef_cfg mef_cfg; + struct host_cmd_ds_mem_access mem; + struct host_cmd_ds_mac_reg_access mac_reg; + struct host_cmd_ds_bbp_reg_access bbp_reg; + struct host_cmd_ds_rf_reg_access rf_reg; + struct host_cmd_ds_pmic_reg_access pmic_reg; + struct host_cmd_ds_set_bss_mode bss_mode; + struct host_cmd_ds_pcie_details pcie_host_spec; + struct host_cmd_ds_802_11_eeprom_access eeprom; + struct host_cmd_ds_802_11_subsc_evt subsc_evt; + struct host_cmd_ds_sys_config uap_sys_config; + struct host_cmd_ds_sta_deauth sta_deauth; + struct host_cmd_ds_sta_list sta_list; + struct host_cmd_11ac_vht_cfg vht_cfg; + struct host_cmd_ds_coalesce_cfg coalesce_cfg; + struct host_cmd_ds_chan_rpt_req chan_rpt_req; + struct host_cmd_sdio_sp_rx_aggr_cfg sdio_rx_aggr_cfg; + struct host_cmd_ds_multi_chan_policy mc_policy; + struct host_cmd_ds_robust_coex coex; + struct host_cmd_ds_wakeup_reason hs_wakeup_reason; + struct host_cmd_ds_gtk_rekey_params rekey; + struct host_cmd_ds_chan_region_cfg reg_cfg; + struct host_cmd_ds_pkt_aggr_ctrl pkt_aggr_ctrl; + struct host_cmd_ds_sta_configure sta_cfg; + struct host_cmd_ds_add_station sta_info; + struct host_cmd_11ax_cfg ax_cfg; + struct host_cmd_11ax_cmd ax_cmd; + struct host_cmd_ds_802_11_net_monitor net_mon; + struct host_cmd_twt_cfg twt_cfg; + } params; +} __packed; + +struct nxpwifi_opt_sleep_confirm { + __le16 command; + __le16 size; + __le16 seq_num; + __le16 result; + __le16 action; + __le16 resp_ctrl; +} __packed; + +#define VDLL_IND_TYPE_REQ 0 +#define VDLL_IND_TYPE_OFFSET 1 +#define VDLL_IND_TYPE_ERR_SIG 2 +#define VDLL_IND_TYPE_ERR_ID 3 +#define VDLL_IND_TYPE_SEC_ERR_ID 4 +#define VDLL_IND_TYPE_INTF_RESET 5 + +struct vdll_ind_event { + __le16 type; + __le16 vdll_id; + __le32 offset; + __le16 block_len; +} __packed; +#endif /* !_NXPWIFI_FW_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/ie.c b/drivers/net/wireless/nxp/nxpwifi/ie.c new file mode 100644 index 000000000000..158755c0c905 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/ie.c @@ -0,0 +1,480 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: management element handling - set/delete elements. + * + * Copyright 2011-2024 NXP + */ + +#include "main.h" +#include "cmdevt.h" + +/* Return true if the IE index is used by another interface. */ +static bool +nxpwifi_ie_index_used_by_other_intf(struct nxpwifi_private *priv, u16 idx) +{ + int i; + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ie *ie; + + for (i = 0; i < adapter->priv_num; i++) { + if (adapter->priv[i] != priv) { + ie = &adapter->priv[i]->mgmt_ie[idx]; + if (ie->mgmt_subtype_mask && ie->ie_length) + return true; + } + } + + return false; +} + +/* Pick an unused IE index for a new element. */ +static int +nxpwifi_ie_get_autoidx(struct nxpwifi_private *priv, u16 subtype_mask, + struct nxpwifi_ie *ie, u16 *index) +{ + u16 mask, len, i; + + for (i = 0; i < priv->adapter->max_mgmt_ie_index; i++) { + mask = le16_to_cpu(priv->mgmt_ie[i].mgmt_subtype_mask); + len = le16_to_cpu(ie->ie_length); + + if (mask == NXPWIFI_AUTO_IDX_MASK) + continue; + + if (mask == subtype_mask) { + if (len > IEEE_MAX_IE_SIZE) + continue; + + *index = i; + return 0; + } + + if (!priv->mgmt_ie[i].ie_length) { + if (nxpwifi_ie_index_used_by_other_intf(priv, i)) + continue; + + *index = i; + return 0; + } + } + + return -ENOENT; +} + +/* Build IE list and resolve AUTO index before sending to FW. */ +static int +nxpwifi_update_autoindex_ies(struct nxpwifi_private *priv, + struct nxpwifi_ie_list *ie_list) +{ + u16 travel_len, index, mask; + s16 input_len, tlv_len; + struct nxpwifi_ie *ie; + u8 *tmp; + + input_len = le16_to_cpu(ie_list->len); + travel_len = sizeof(struct nxpwifi_ie_types_header); + + ie_list->len = 0; + + while (input_len >= sizeof(struct nxpwifi_ie_types_header)) { + ie = (struct nxpwifi_ie *)(((u8 *)ie_list) + travel_len); + tlv_len = le16_to_cpu(ie->ie_length); + travel_len += tlv_len + NXPWIFI_IE_HDR_SIZE; + + if (input_len < tlv_len + NXPWIFI_IE_HDR_SIZE) + return -EINVAL; + index = le16_to_cpu(ie->ie_index); + mask = le16_to_cpu(ie->mgmt_subtype_mask); + + if (index == NXPWIFI_AUTO_IDX_MASK) { + /* automatic addition */ + if (nxpwifi_ie_get_autoidx(priv, mask, ie, &index)) + return -ENOENT; + if (index == NXPWIFI_AUTO_IDX_MASK) + return -EINVAL; + + tmp = (u8 *)&priv->mgmt_ie[index].ie_buffer; + memcpy(tmp, &ie->ie_buffer, le16_to_cpu(ie->ie_length)); + priv->mgmt_ie[index].ie_length = ie->ie_length; + priv->mgmt_ie[index].ie_index = cpu_to_le16(index); + priv->mgmt_ie[index].mgmt_subtype_mask = + cpu_to_le16(mask); + + ie->ie_index = cpu_to_le16(index); + } else { + if (mask != NXPWIFI_DELETE_MASK) + return -EINVAL; + /* + * Check if this index is being used on any + * other interface. + */ + if (nxpwifi_ie_index_used_by_other_intf(priv, index)) + return -EPERM; + + ie->ie_length = 0; + memcpy(&priv->mgmt_ie[index], ie, + sizeof(struct nxpwifi_ie)); + } + + le16_unaligned_add_cpu + (&ie_list->len, + le16_to_cpu(priv->mgmt_ie[index].ie_length) + + NXPWIFI_IE_HDR_SIZE); + input_len -= tlv_len + NXPWIFI_IE_HDR_SIZE; + } + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) + return nxpwifi_send_cmd(priv, HOST_CMD_UAP_SYS_CONFIG, + HOST_ACT_GEN_SET, + UAP_CUSTOM_IE_I, ie_list, true); + + return 0; +} + +/* Pack beacon/probe/assoc IEs into one list and update auto-assigned indices. */ +static int +nxpwifi_update_uap_custom_ie(struct nxpwifi_private *priv, + struct nxpwifi_ie *beacon_ie, u16 *beacon_idx, + struct nxpwifi_ie *pr_ie, u16 *probe_idx, + struct nxpwifi_ie *ar_ie, u16 *assoc_idx) +{ + struct nxpwifi_ie_list *ap_custom_ie; + u8 *pos; + u16 len; + int ret; + + ap_custom_ie = kzalloc_obj(*ap_custom_ie, GFP_KERNEL); + if (!ap_custom_ie) + return -ENOMEM; + + ap_custom_ie->type = cpu_to_le16(TLV_TYPE_MGMT_IE); + pos = (u8 *)ap_custom_ie->ie_list; + + if (beacon_ie) { + len = sizeof(struct nxpwifi_ie) - IEEE_MAX_IE_SIZE + + le16_to_cpu(beacon_ie->ie_length); + memcpy(pos, beacon_ie, len); + pos += len; + le16_unaligned_add_cpu(&ap_custom_ie->len, len); + } + if (pr_ie) { + len = sizeof(struct nxpwifi_ie) - IEEE_MAX_IE_SIZE + + le16_to_cpu(pr_ie->ie_length); + memcpy(pos, pr_ie, len); + pos += len; + le16_unaligned_add_cpu(&ap_custom_ie->len, len); + } + if (ar_ie) { + len = sizeof(struct nxpwifi_ie) - IEEE_MAX_IE_SIZE + + le16_to_cpu(ar_ie->ie_length); + memcpy(pos, ar_ie, len); + pos += len; + le16_unaligned_add_cpu(&ap_custom_ie->len, len); + } + + ret = nxpwifi_update_autoindex_ies(priv, ap_custom_ie); + + pos = (u8 *)(&ap_custom_ie->ie_list[0].ie_index); + if (beacon_ie && *beacon_idx == NXPWIFI_AUTO_IDX_MASK) { + /* save beacon element index after auto-indexing */ + *beacon_idx = le16_to_cpu(ap_custom_ie->ie_list[0].ie_index); + len = sizeof(*beacon_ie) - IEEE_MAX_IE_SIZE + + le16_to_cpu(beacon_ie->ie_length); + pos += len; + } + if (pr_ie && le16_to_cpu(pr_ie->ie_index) == NXPWIFI_AUTO_IDX_MASK) { + /* save probe resp element index after auto-indexing */ + *probe_idx = *((u16 *)pos); + len = sizeof(*pr_ie) - IEEE_MAX_IE_SIZE + + le16_to_cpu(pr_ie->ie_length); + pos += len; + } + if (ar_ie && le16_to_cpu(ar_ie->ie_index) == NXPWIFI_AUTO_IDX_MASK) + /* save assoc resp element index after auto-indexing */ + *assoc_idx = *((u16 *)pos); + + kfree(ap_custom_ie); + return ret; +} + +/* Append vendor IE (if present) into nxpwifi_ie, allocating as needed. */ +static int nxpwifi_update_vs_ie(const u8 *ies, int ies_len, + struct nxpwifi_ie **ie_ptr, u16 mask, + unsigned int oui, u8 oui_type) +{ + struct element *vs_ie; + struct nxpwifi_ie *ie = *ie_ptr; + const u8 *vendor_ie; + + vendor_ie = cfg80211_find_vendor_ie(oui, oui_type, ies, ies_len); + if (vendor_ie) { + if (!*ie_ptr) { + *ie_ptr = kzalloc_obj(struct nxpwifi_ie, GFP_KERNEL); + if (!*ie_ptr) + return -ENOMEM; + ie = *ie_ptr; + } + + vs_ie = (struct element *)vendor_ie; + if (le16_to_cpu(ie->ie_length) + vs_ie->datalen + 2 > + IEEE_MAX_IE_SIZE) + return -EINVAL; + memcpy(ie->ie_buffer + le16_to_cpu(ie->ie_length), + vs_ie, vs_ie->datalen + 2); + le16_unaligned_add_cpu(&ie->ie_length, vs_ie->datalen + 2); + ie->mgmt_subtype_mask = cpu_to_le16(mask); + ie->ie_index = cpu_to_le16(NXPWIFI_AUTO_IDX_MASK); + } + + *ie_ptr = ie; + return 0; +} + +/* Parse beacon/probe/assoc IEs from cfg80211 and push them to FW. */ +static int nxpwifi_set_mgmt_beacon_data_ies(struct nxpwifi_private *priv, + struct cfg80211_beacon_data *data) +{ + struct nxpwifi_ie *beacon_ie = NULL, *pr_ie = NULL, *ar_ie = NULL; + u16 beacon_idx = NXPWIFI_AUTO_IDX_MASK, pr_idx = NXPWIFI_AUTO_IDX_MASK; + u16 ar_idx = NXPWIFI_AUTO_IDX_MASK; + int ret = 0; + + if (data->beacon_ies && data->beacon_ies_len) { + nxpwifi_update_vs_ie(data->beacon_ies, data->beacon_ies_len, + &beacon_ie, MGMT_MASK_BEACON, + WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WPS); + nxpwifi_update_vs_ie(data->beacon_ies, data->beacon_ies_len, + &beacon_ie, MGMT_MASK_BEACON, + WLAN_OUI_WFA, WLAN_OUI_TYPE_WFA_P2P); + } + + if (data->proberesp_ies && data->proberesp_ies_len) { + nxpwifi_update_vs_ie(data->proberesp_ies, + data->proberesp_ies_len, &pr_ie, + MGMT_MASK_PROBE_RESP, WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WPS); + nxpwifi_update_vs_ie(data->proberesp_ies, + data->proberesp_ies_len, &pr_ie, + MGMT_MASK_PROBE_RESP, + WLAN_OUI_WFA, WLAN_OUI_TYPE_WFA_P2P); + } + + if (data->assocresp_ies && data->assocresp_ies_len) { + nxpwifi_update_vs_ie(data->assocresp_ies, + data->assocresp_ies_len, &ar_ie, + MGMT_MASK_ASSOC_RESP | + MGMT_MASK_REASSOC_RESP, + WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WPS); + nxpwifi_update_vs_ie(data->assocresp_ies, + data->assocresp_ies_len, &ar_ie, + MGMT_MASK_ASSOC_RESP | + MGMT_MASK_REASSOC_RESP, WLAN_OUI_WFA, + WLAN_OUI_TYPE_WFA_P2P); + } + + if (beacon_ie || pr_ie || ar_ie) { + ret = nxpwifi_update_uap_custom_ie(priv, beacon_ie, + &beacon_idx, pr_ie, + &pr_idx, ar_ie, &ar_idx); + if (ret) + goto done; + } + + priv->beacon_idx = beacon_idx; + priv->proberesp_idx = pr_idx; + priv->assocresp_idx = ar_idx; + +done: + kfree(beacon_ie); + kfree(pr_ie); + kfree(ar_ie); + + return ret; +} + +/* Parse head/tail IEs from cfg80211_beacon_data and send them to FW. */ +static int nxpwifi_uap_parse_tail_ies(struct nxpwifi_private *priv, + struct cfg80211_beacon_data *info) +{ + struct nxpwifi_ie *gen_ie; + struct element *hdr; + struct ieee80211_vendor_ie *vendorhdr; + u16 gen_idx = NXPWIFI_AUTO_IDX_MASK, ie_len = 0; + int left_len, parsed_len = 0; + unsigned int token_len; + int ret = 0; + + if (!info->tail || !info->tail_len) + return 0; + + gen_ie = kzalloc_obj(*gen_ie, GFP_KERNEL); + if (!gen_ie) + return -ENOMEM; + + left_len = info->tail_len; + + /* Skip IEs generated by FW from bss configuration to avoid duplicates. */ + while (left_len > sizeof(struct element)) { + hdr = (void *)(info->tail + parsed_len); + token_len = hdr->datalen + sizeof(struct element); + if (token_len > left_len) { + ret = -EINVAL; + goto done; + } + + switch (hdr->id) { + case WLAN_EID_SSID: + case WLAN_EID_SUPP_RATES: + case WLAN_EID_COUNTRY: + case WLAN_EID_PWR_CONSTRAINT: + case WLAN_EID_ERP_INFO: + case WLAN_EID_EXT_SUPP_RATES: + case WLAN_EID_HT_CAPABILITY: + case WLAN_EID_HT_OPERATION: + case WLAN_EID_VHT_CAPABILITY: + break; + case WLAN_EID_VENDOR_SPECIFIC: + /* Skip only Microsoft WMM element */ + if (cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WMM, + (const u8 *)hdr, + token_len)) + break; + fallthrough; + default: + if (ie_len + token_len > IEEE_MAX_IE_SIZE) { + ret = -EINVAL; + goto done; + } + memcpy(gen_ie->ie_buffer + ie_len, hdr, token_len); + ie_len += token_len; + break; + } + left_len -= token_len; + parsed_len += token_len; + } + + /* + * parse only WPA vendor element from tail, WMM element is configured by + * bss_config command + */ + vendorhdr = (void *)cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WPA, + info->tail, info->tail_len); + if (vendorhdr) { + token_len = vendorhdr->len + sizeof(struct element); + if (ie_len + token_len > IEEE_MAX_IE_SIZE) { + ret = -EINVAL; + goto done; + } + memcpy(gen_ie->ie_buffer + ie_len, vendorhdr, token_len); + ie_len += token_len; + } + + if (!ie_len) + goto done; + + gen_ie->ie_index = cpu_to_le16(gen_idx); + gen_ie->mgmt_subtype_mask = cpu_to_le16(MGMT_MASK_BEACON | + MGMT_MASK_PROBE_RESP | + MGMT_MASK_ASSOC_RESP); + gen_ie->ie_length = cpu_to_le16(ie_len); + + ret = nxpwifi_update_uap_custom_ie(priv, gen_ie, &gen_idx, NULL, + NULL, NULL, NULL); + + if (ret) + goto done; + + priv->gen_idx = gen_idx; + + done: + kfree(gen_ie); + return ret; +} + +/* Parse head/tail/beacon/probe/assoc IEs and program the FW. */ +int nxpwifi_set_mgmt_ies(struct nxpwifi_private *priv, + struct cfg80211_beacon_data *info) +{ + int ret; + + ret = nxpwifi_uap_parse_tail_ies(priv, info); + + if (ret) + return ret; + + return nxpwifi_set_mgmt_beacon_data_ies(priv, info); +} + +/* Remove previously set management IEs. */ +int nxpwifi_del_mgmt_ies(struct nxpwifi_private *priv) +{ + struct nxpwifi_ie *beacon_ie = NULL, *pr_ie = NULL; + struct nxpwifi_ie *ar_ie = NULL, *gen_ie = NULL; + int ret = 0; + + if (priv->gen_idx != NXPWIFI_AUTO_IDX_MASK) { + gen_ie = kmalloc_obj(*gen_ie, GFP_KERNEL); + if (!gen_ie) + return -ENOMEM; + + gen_ie->ie_index = cpu_to_le16(priv->gen_idx); + gen_ie->mgmt_subtype_mask = cpu_to_le16(NXPWIFI_DELETE_MASK); + gen_ie->ie_length = 0; + ret = nxpwifi_update_uap_custom_ie(priv, gen_ie, &priv->gen_idx, + NULL, &priv->proberesp_idx, + NULL, &priv->assocresp_idx); + if (ret) + goto done; + + priv->gen_idx = NXPWIFI_AUTO_IDX_MASK; + } + + if (priv->beacon_idx != NXPWIFI_AUTO_IDX_MASK) { + beacon_ie = kmalloc_obj(*beacon_ie, GFP_KERNEL); + if (!beacon_ie) { + ret = -ENOMEM; + goto done; + } + beacon_ie->ie_index = cpu_to_le16(priv->beacon_idx); + beacon_ie->mgmt_subtype_mask = cpu_to_le16(NXPWIFI_DELETE_MASK); + beacon_ie->ie_length = 0; + } + if (priv->proberesp_idx != NXPWIFI_AUTO_IDX_MASK) { + pr_ie = kmalloc_obj(*pr_ie, GFP_KERNEL); + if (!pr_ie) { + ret = -ENOMEM; + goto done; + } + pr_ie->ie_index = cpu_to_le16(priv->proberesp_idx); + pr_ie->mgmt_subtype_mask = cpu_to_le16(NXPWIFI_DELETE_MASK); + pr_ie->ie_length = 0; + } + if (priv->assocresp_idx != NXPWIFI_AUTO_IDX_MASK) { + ar_ie = kmalloc_obj(*ar_ie, GFP_KERNEL); + if (!ar_ie) { + ret = -ENOMEM; + goto done; + } + ar_ie->ie_index = cpu_to_le16(priv->assocresp_idx); + ar_ie->mgmt_subtype_mask = cpu_to_le16(NXPWIFI_DELETE_MASK); + ar_ie->ie_length = 0; + } + + if (beacon_ie || pr_ie || ar_ie) + ret = nxpwifi_update_uap_custom_ie(priv, + beacon_ie, &priv->beacon_idx, + pr_ie, &priv->proberesp_idx, + ar_ie, &priv->assocresp_idx); + +done: + kfree(gen_ie); + kfree(beacon_ie); + kfree(pr_ie); + kfree(ar_ie); + + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/init.c b/drivers/net/wireless/nxp/nxpwifi/init.c new file mode 100644 index 000000000000..b128fc9fe31a --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/init.c @@ -0,0 +1,607 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: HW/FW initialization + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" + +/* Add a BSS priority node to the adapter list. */ +static int nxpwifi_add_bss_prio_tbl(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_bss_prio_node *bss_prio; + struct nxpwifi_bss_prio_tbl *tbl = adapter->bss_prio_tbl; + + bss_prio = kzalloc_obj(*bss_prio, GFP_KERNEL); + if (!bss_prio) + return -ENOMEM; + + bss_prio->priv = priv; + INIT_LIST_HEAD(&bss_prio->list); + + spin_lock_bh(&tbl[priv->bss_priority].bss_prio_lock); + list_add_tail(&bss_prio->list, &tbl[priv->bss_priority].bss_prio_head); + spin_unlock_bh(&tbl[priv->bss_priority].bss_prio_lock); + + return 0; +} + +static void wakeup_timer_fn(struct timer_list *t) +{ + struct nxpwifi_adapter *adapter = timer_container_of(adapter, t, wakeup_timer); + + nxpwifi_dbg(adapter, ERROR, "Firmware wakeup failed\n"); + adapter->hw_status = NXPWIFI_HW_STATUS_RESET; + nxpwifi_cancel_all_pending_cmd(adapter); + + if (adapter->if_ops.card_reset) + adapter->if_ops.card_reset(adapter); +} + +/* Initialize priv defaults and lists. */ +int nxpwifi_init_priv(struct nxpwifi_private *priv) +{ + u32 i; + + priv->media_connected = false; + eth_broadcast_addr(priv->curr_addr); + priv->port_open = false; + priv->usb_port = NXPWIFI_USB_EP_DATA; + priv->pkt_tx_ctrl = 0; + priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; + priv->data_rate = 0; /* Initially indicate the rate as auto */ + priv->is_data_rate_auto = true; + priv->bcn_avg_factor = DEFAULT_BCN_AVG_FACTOR; + priv->data_avg_factor = DEFAULT_DATA_AVG_FACTOR; + + priv->auth_flag = 0; + priv->auth_alg = WLAN_AUTH_NONE; + + priv->sec_info.wep_enabled = 0; + priv->sec_info.authentication_mode = NL80211_AUTHTYPE_OPEN_SYSTEM; + priv->sec_info.encryption_mode = 0; + for (i = 0; i < ARRAY_SIZE(priv->wep_key); i++) + memset(&priv->wep_key[i], 0, sizeof(struct nxpwifi_wep_key)); + priv->wep_key_curr_index = 0; + priv->curr_pkt_filter = HOST_ACT_MAC_DYNAMIC_BW_ENABLE | + HOST_ACT_MAC_RX_ON | HOST_ACT_MAC_TX_ON | + HOST_ACT_MAC_ETHERNETII_ENABLE; + + priv->beacon_period = 100; /* beacon interval */ + priv->attempted_bss_desc = NULL; + memset(&priv->curr_bss_params, 0, sizeof(priv->curr_bss_params)); + priv->listen_interval = NXPWIFI_DEFAULT_LISTEN_INTERVAL; + + memset(&priv->prev_ssid, 0, sizeof(priv->prev_ssid)); + memset(&priv->prev_bssid, 0, sizeof(priv->prev_bssid)); + memset(&priv->assoc_rsp_buf, 0, sizeof(priv->assoc_rsp_buf)); + priv->assoc_rsp_size = 0; + priv->atim_window = 0; + priv->tx_power_level = 0; + priv->max_tx_power_level = 0; + priv->min_tx_power_level = 0; + priv->tx_ant = 0; + priv->rx_ant = 0; + priv->tx_rate = 0; + priv->rxpd_htinfo = 0; + priv->rxpd_rate = 0; + priv->rate_bitmap = 0; + priv->data_rssi_last = 0; + priv->data_rssi_avg = 0; + priv->data_nf_avg = 0; + priv->data_nf_last = 0; + priv->bcn_rssi_last = 0; + priv->bcn_rssi_avg = 0; + priv->bcn_nf_avg = 0; + priv->bcn_nf_last = 0; + memset(&priv->wpa_ie, 0, sizeof(priv->wpa_ie)); + memset(&priv->aes_key, 0, sizeof(priv->aes_key)); + priv->wpa_ie_len = 0; + priv->wpa_is_gtk_set = false; + + memset(&priv->assoc_tlv_buf, 0, sizeof(priv->assoc_tlv_buf)); + priv->assoc_tlv_buf_len = 0; + memset(&priv->wps, 0, sizeof(priv->wps)); + memset(&priv->gen_ie_buf, 0, sizeof(priv->gen_ie_buf)); + priv->gen_ie_buf_len = 0; + memset(priv->vs_ie, 0, sizeof(priv->vs_ie)); + + priv->wmm_required = true; + priv->wmm_enabled = false; + priv->wmm_qosinfo = 0; + priv->curr_bcn_buf = NULL; + priv->curr_bcn_size = 0; + priv->wps_ie = NULL; + priv->wps_ie_len = 0; + priv->ap_11n_enabled = 0; + memset(&priv->roc_cfg, 0, sizeof(priv->roc_cfg)); + + priv->scan_block = false; + + priv->csa_chan = 0; + priv->csa_expire_time = 0; + priv->del_list_idx = 0; + priv->hs2_enabled = false; + nxpwifi_wmm_init_tos_to_tid_inv(priv); + + nxpwifi_init_11h_params(priv); + + return nxpwifi_add_bss_prio_tbl(priv); +} + +/* Allocate command buffer and sleep-confirm skb. */ +static int nxpwifi_allocate_adapter(struct nxpwifi_adapter *adapter) +{ + int ret; + + /* Allocate command buffer */ + ret = nxpwifi_alloc_cmd_buffer(adapter); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "%s: failed to alloc cmd buffer\n", + __func__); + return ret; + } + + adapter->sleep_cfm = + dev_alloc_skb(sizeof(struct nxpwifi_opt_sleep_confirm) + + INTF_HEADER_LEN); + + if (!adapter->sleep_cfm) { + nxpwifi_dbg(adapter, ERROR, + "%s: failed to alloc sleep cfm\t" + " cmd buffer\n", __func__); + return -ENOMEM; + } + skb_reserve(adapter->sleep_cfm, INTF_HEADER_LEN); + + return 0; +} + +/* Initialize adapter defaults and WMM parameters. */ +static void nxpwifi_init_adapter(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_opt_sleep_confirm *sleep_cfm_buf = NULL; + + skb_put(adapter->sleep_cfm, sizeof(struct nxpwifi_opt_sleep_confirm)); + + adapter->cmd_sent = false; + adapter->data_sent = true; + + adapter->intf_hdr_len = INTF_HEADER_LEN; + + adapter->cmd_resp_received = false; + adapter->event_received = false; + adapter->data_received = false; + adapter->assoc_resp_received = false; + adapter->priv_link_lost = NULL; + adapter->host_mlme_link_lost = false; + + clear_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags); + + adapter->hw_status = NXPWIFI_HW_STATUS_INITIALIZING; + + adapter->ps_mode = NXPWIFI_802_11_POWER_MODE_CAM; + adapter->ps_state = PS_STATE_AWAKE; + adapter->need_to_wakeup = false; + + adapter->scan_mode = HOST_BSS_MODE_ANY; + adapter->specific_scan_time = NXPWIFI_SPECIFIC_SCAN_CHAN_TIME; + adapter->active_scan_time = NXPWIFI_ACTIVE_SCAN_CHAN_TIME; + adapter->passive_scan_time = NXPWIFI_PASSIVE_SCAN_CHAN_TIME; + adapter->scan_chan_gap_time = NXPWIFI_DEF_SCAN_CHAN_GAP_TIME; + + adapter->scan_probes = 1; + + adapter->multiple_dtim = 1; + + /* default value in firmware will be used */ + adapter->local_listen_interval = 0; + + adapter->is_deep_sleep = false; + + adapter->delay_null_pkt = false; + adapter->delay_to_ps = 1000; + adapter->enhanced_ps_mode = PS_MODE_AUTO; + + /* Disable NULL Pkg generation by default */ + adapter->gen_null_pkt = false; + /* Disable pps/uapsd mode by default */ + adapter->pps_uapsd_mode = false; + adapter->pm_wakeup_card_req = false; + + adapter->pm_wakeup_fw_try = false; + + adapter->curr_tx_buf_size = NXPWIFI_TX_DATA_BUF_SIZE_2K; + + clear_bit(NXPWIFI_IS_HS_CONFIGURED, &adapter->work_flags); + adapter->hs_cfg.conditions = cpu_to_le32(HS_CFG_COND_DEF); + adapter->hs_cfg.gpio = HS_CFG_GPIO_DEF; + adapter->hs_cfg.gap = HS_CFG_GAP_DEF; + adapter->hs_activated = false; + + memset(adapter->event_body, 0, sizeof(adapter->event_body)); + adapter->hw_dot_11n_dev_cap = 0; + adapter->hw_dev_mcs_support = 0; + adapter->sec_chan_offset = 0; + + nxpwifi_wmm_init(adapter); + atomic_set(&adapter->tx_hw_pending, 0); + + sleep_cfm_buf = (struct nxpwifi_opt_sleep_confirm *) + adapter->sleep_cfm->data; + memset(sleep_cfm_buf, 0, adapter->sleep_cfm->len); + sleep_cfm_buf->command = cpu_to_le16(HOST_CMD_802_11_PS_MODE_ENH); + sleep_cfm_buf->size = cpu_to_le16(adapter->sleep_cfm->len); + sleep_cfm_buf->result = 0; + sleep_cfm_buf->action = cpu_to_le16(SLEEP_CONFIRM); + sleep_cfm_buf->resp_ctrl = cpu_to_le16(RESP_NEEDED); + + memset(&adapter->sleep_period, 0, sizeof(adapter->sleep_period)); + adapter->tx_lock_flag = false; + adapter->null_pkt_interval = 0; + adapter->fw_bands = 0; + adapter->fw_release_number = 0; + adapter->fw_cap_info = 0; + memset(&adapter->upld_buf, 0, sizeof(adapter->upld_buf)); + adapter->event_cause = 0; + adapter->region_code = 0; + adapter->bcn_miss_time_out = DEFAULT_BCN_MISS_TIMEOUT; + memset(&adapter->arp_filter, 0, sizeof(adapter->arp_filter)); + adapter->arp_filter_size = 0; + adapter->max_mgmt_ie_index = MAX_MGMT_IE_INDEX; + adapter->key_api_major_ver = 0; + adapter->key_api_minor_ver = 0; + eth_broadcast_addr(adapter->perm_addr); + adapter->iface_limit.sta_intf = NXPWIFI_MAX_STA_NUM; + adapter->iface_limit.uap_intf = NXPWIFI_MAX_UAP_NUM; + adapter->active_scan_triggered = false; + timer_setup(&adapter->wakeup_timer, wakeup_timer_fn, 0); + adapter->devdump_len = 0; + memset(&adapter->vdll_ctrl, 0, sizeof(adapter->vdll_ctrl)); + adapter->vdll_ctrl.skb = dev_alloc_skb(NXPWIFI_SIZE_OF_CMD_BUFFER); + atomic_set(&adapter->iface_changing, 0); +} + +/* Update trans_start for each Tx queue. */ +void nxpwifi_set_trans_start(struct net_device *dev) +{ + int i; + + for (i = 0; i < dev->num_tx_queues; i++) + txq_trans_cond_update(netdev_get_tx_queue(dev, i)); + + netif_trans_update(dev); +} + +/* Wake all netdev Tx queues. */ +void nxpwifi_wake_up_net_dev_queue(struct net_device *netdev, + struct nxpwifi_adapter *adapter) +{ + spin_lock_bh(&adapter->queue_lock); + netif_tx_wake_all_queues(netdev); + spin_unlock_bh(&adapter->queue_lock); +} + +/* Stop all netdev Tx queues. */ +void nxpwifi_stop_net_dev_queue(struct net_device *netdev, + struct nxpwifi_adapter *adapter) +{ + spin_lock_bh(&adapter->queue_lock); + netif_tx_stop_all_queues(netdev); + spin_unlock_bh(&adapter->queue_lock); +} + +/* Invalidate list heads. */ +static void nxpwifi_invalidate_lists(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + s32 i, j; + + list_del(&adapter->cmd_free_q); + list_del(&adapter->cmd_pending_q); + list_del(&adapter->scan_pending_q); + + for (i = 0; i < adapter->priv_num; i++) + list_del(&adapter->bss_prio_tbl[i].bss_prio_head); + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + for (j = 0; j < MAX_NUM_TID; ++j) { + list_del(&priv->wmm.tid_tbl_ptr[j].ra_list); + list_del(&priv->tx_ba_stream_tbl_ptr[j]); + list_del(&priv->rx_reorder_tbl_ptr[j]); + } + list_del(&priv->sta_list); + } +} + +/* Cancel pending work, stop timers, and free adapter buffers. */ +static void +nxpwifi_adapter_cleanup(struct nxpwifi_adapter *adapter) +{ + timer_delete(&adapter->wakeup_timer); + nxpwifi_cancel_all_pending_cmd(adapter); + wake_up_interruptible(&adapter->cmd_wait_q.wait); + wake_up_interruptible(&adapter->hs_activate_wait_q); + if (adapter->vdll_ctrl.vdll_mem) { + vfree(adapter->vdll_ctrl.vdll_mem); + adapter->vdll_ctrl.vdll_mem = NULL; + adapter->vdll_ctrl.vdll_len = 0; + } + if (adapter->vdll_ctrl.skb) { + dev_kfree_skb_any(adapter->vdll_ctrl.skb); + adapter->vdll_ctrl.skb = NULL; + } +} + +void nxpwifi_free_cmd_buffers(struct nxpwifi_adapter *adapter) +{ + nxpwifi_invalidate_lists(adapter); + + /* Free command buffer */ + nxpwifi_dbg(adapter, INFO, "info: free cmd buffer\n"); + nxpwifi_free_cmd_buffer(adapter); + + if (adapter->sleep_cfm) + dev_kfree_skb_any(adapter->sleep_cfm); +} + +/* Initialize locks and list heads. */ +void nxpwifi_init_lock_list(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + s32 i, j; + + spin_lock_init(&adapter->int_lock); + spin_lock_init(&adapter->nxpwifi_cmd_lock); + spin_lock_init(&adapter->queue_lock); + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + spin_lock_init(&priv->wmm.ra_list_spinlock); + spin_lock_init(&priv->curr_bcn_buf_lock); + spin_lock_init(&priv->sta_list_spinlock); + } + + /* Initialize cmd_free_q */ + INIT_LIST_HEAD(&adapter->cmd_free_q); + /* Initialize cmd_pending_q */ + INIT_LIST_HEAD(&adapter->cmd_pending_q); + /* Initialize scan_pending_q */ + INIT_LIST_HEAD(&adapter->scan_pending_q); + + spin_lock_init(&adapter->cmd_free_q_lock); + spin_lock_init(&adapter->cmd_pending_q_lock); + spin_lock_init(&adapter->scan_pending_q_lock); + + skb_queue_head_init(&adapter->rx_mlme_q); + skb_queue_head_init(&adapter->rx_data_q); + skb_queue_head_init(&adapter->tx_data_q); + + for (i = 0; i < adapter->priv_num; ++i) { + INIT_LIST_HEAD(&adapter->bss_prio_tbl[i].bss_prio_head); + spin_lock_init(&adapter->bss_prio_tbl[i].bss_prio_lock); + } + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + for (j = 0; j < MAX_NUM_TID; ++j) { + INIT_LIST_HEAD(&priv->wmm.tid_tbl_ptr[j].ra_list); + INIT_LIST_HEAD(&priv->tx_ba_stream_tbl_ptr[j]); + INIT_LIST_HEAD(&priv->rx_reorder_tbl_ptr[j]); + spin_lock_init(&priv->tx_ba_stream_tbl_lock[j]); + spin_lock_init(&priv->rx_reorder_tbl_lock[j]); + } + INIT_LIST_HEAD(&priv->sta_list); + skb_queue_head_init(&priv->bypass_txq); + + spin_lock_init(&priv->ack_status_lock); + xa_init_flags(&priv->ack_status_frames, XA_FLAGS_ALLOC); + } +} + +/* Init firmware: alloc resources, init adapter/privs, send STA init. */ +int nxpwifi_init_fw(struct nxpwifi_adapter *adapter) +{ + int ret; + struct nxpwifi_private *priv; + u8 i; + bool first_sta = true; + + adapter->hw_status = NXPWIFI_HW_STATUS_INITIALIZING; + + /* Allocate memory for member of adapter structure */ + ret = nxpwifi_allocate_adapter(adapter); + if (ret) + return ret; + + /* Initialize adapter structure */ + nxpwifi_init_adapter(adapter); + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + + /* Initialize private structure */ + ret = nxpwifi_init_priv(priv); + if (ret) + return ret; + } + + for (i = 0; i < adapter->priv_num; i++) { + ret = nxpwifi_sta_init_cmd(adapter->priv[i], + first_sta, true); + if (ret) + return ret; + + first_sta = false; + } + spin_lock_bh(&adapter->cmd_pending_q_lock); + WARN_ON(!list_empty(&adapter->cmd_pending_q)); + spin_unlock_bh(&adapter->cmd_pending_q_lock); + adapter->hw_status = NXPWIFI_HW_STATUS_READY; + + return 0; +} + +/* Remove all BSS priority nodes for this priv. */ +static void nxpwifi_delete_bss_prio_tbl(struct nxpwifi_private *priv) +{ + int i; + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_bss_prio_node *bssprio_node, *tmp_node; + struct list_head *head; + spinlock_t *lock; /* bss priority lock */ + + for (i = 0; i < adapter->priv_num; ++i) { + head = &adapter->bss_prio_tbl[i].bss_prio_head; + lock = &adapter->bss_prio_tbl[i].bss_prio_lock; + nxpwifi_dbg(adapter, INFO, + "info: delete BSS priority table,\t" + "bss_type = %d, bss_num = %d, i = %d,\t" + "head = %p\n", + priv->bss_type, priv->bss_num, i, head); + + { + spin_lock_bh(lock); + list_for_each_entry_safe(bssprio_node, tmp_node, head, + list) { + if (bssprio_node->priv == priv) { + nxpwifi_dbg(adapter, INFO, + "info: Delete\t" + "node %p, next = %p\n", + bssprio_node, tmp_node); + list_del(&bssprio_node->list); + kfree(bssprio_node); + } + } + spin_unlock_bh(lock); + } + } +} + +/* Free per-priv resources and BSS priority entries. */ +void nxpwifi_free_priv(struct nxpwifi_private *priv) +{ + nxpwifi_clean_txrx(priv); + nxpwifi_delete_bss_prio_tbl(priv); + nxpwifi_free_curr_bcn(priv); +} + +/* Shutdown driver: stop work, drain queues, free resources. */ +void +nxpwifi_shutdown_drv(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + s32 i; + struct sk_buff *skb; + + /* nxpwifi already shutdown */ + if (adapter->hw_status == NXPWIFI_HW_STATUS_NOT_READY) + return; + + /* cancel current command */ + if (adapter->curr_cmd) { + nxpwifi_dbg(adapter, WARN, + "curr_cmd is still in processing\n"); + timer_delete_sync(&adapter->cmd_timer); + nxpwifi_recycle_cmd_node(adapter, adapter->curr_cmd); + adapter->curr_cmd = NULL; + } + + /* shut down nxpwifi */ + nxpwifi_dbg(adapter, MSG, + "info: shutdown nxpwifi...\n"); + + /* Clean up Tx/Rx queues and delete BSS priority table */ + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + + nxpwifi_abort_cac(priv); + nxpwifi_free_priv(priv); + } + + atomic_set(&adapter->tx_queued, 0); + while ((skb = skb_dequeue(&adapter->tx_data_q))) + nxpwifi_write_data_complete(adapter, skb, 0, 0); + + while ((skb = skb_dequeue(&adapter->rx_mlme_q))) + dev_kfree_skb_any(skb); + + while ((skb = skb_dequeue(&adapter->rx_data_q))) { + struct nxpwifi_rxinfo *rx_info = NXPWIFI_SKB_RXCB(skb); + + atomic_dec(&adapter->rx_pending); + priv = adapter->priv[rx_info->bss_num]; + if (priv) + priv->stats.rx_dropped++; + + dev_kfree_skb_any(skb); + } + + nxpwifi_adapter_cleanup(adapter); + + adapter->hw_status = NXPWIFI_HW_STATUS_NOT_READY; +} + +/* Download FW if needed; check winner and wait until ready. */ +int nxpwifi_dnld_fw(struct nxpwifi_adapter *adapter, + struct nxpwifi_fw_image *pmfw) +{ + int ret; + u32 poll_num = 1; + + /* check if firmware is already running */ + ret = adapter->if_ops.check_fw_status(adapter, poll_num); + if (!ret) { + nxpwifi_dbg(adapter, MSG, + "WLAN FW already running! Skip FW dnld\n"); + return 0; + } + + /* check if we are the winner for downloading FW */ + if (adapter->if_ops.check_winner_status) { + adapter->winner = 0; + ret = adapter->if_ops.check_winner_status(adapter); + + poll_num = MAX_FIRMWARE_POLL_TRIES; + if (ret) { + nxpwifi_dbg(adapter, MSG, + "WLAN read winner status failed!\n"); + return ret; + } + + if (!adapter->winner) { + nxpwifi_dbg(adapter, MSG, + "WLAN is not the winner! Skip FW dnld\n"); + goto poll_fw; + } + } + + if (pmfw) { + /* Download firmware with helper */ + ret = adapter->if_ops.prog_fw(adapter, pmfw); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "prog_fw failed ret=%#x\n", ret); + return ret; + } + } + +poll_fw: + /* Check if the firmware is downloaded successfully or not */ + ret = adapter->if_ops.check_fw_status(adapter, poll_num); + if (ret) + nxpwifi_dbg(adapter, ERROR, + "FW failed to be active in time\n"); + + return ret; +} +EXPORT_SYMBOL_GPL(nxpwifi_dnld_fw); diff --git a/drivers/net/wireless/nxp/nxpwifi/join.c b/drivers/net/wireless/nxp/nxpwifi/join.c new file mode 100644 index 000000000000..359006e2c391 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/join.c @@ -0,0 +1,787 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: association and ad-hoc start/join + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" +#include "11ac.h" +#include "11ax.h" + +#define CAPINFO_MASK (~(BIT(15) | BIT(14) | BIT(12) | BIT(11) | BIT(9))) + +/* Append generic IE as pass-through TLV for join */ +static int +nxpwifi_cmd_append_generic_ie(struct nxpwifi_private *priv, u8 **buffer) +{ + int ret_len = 0; + struct nxpwifi_ie_types_header ie_header; + + /* Null Checks */ + if (!buffer) + return 0; + if (!(*buffer)) + return 0; + + /* + * If there is a generic element buffer setup, append it to the return + * parameter buffer pointer. + */ + if (priv->gen_ie_buf_len) { + nxpwifi_dbg(priv->adapter, INFO, + "info: %s: append generic element len %d to %p\n", + __func__, priv->gen_ie_buf_len, *buffer); + + /* Wrap the generic element buffer with a pass through TLV type */ + ie_header.type = cpu_to_le16(TLV_TYPE_PASSTHROUGH); + ie_header.len = cpu_to_le16(priv->gen_ie_buf_len); + memcpy(*buffer, &ie_header, sizeof(ie_header)); + + /* + * Increment the return size and the return buffer pointer + * param + */ + *buffer += sizeof(ie_header); + ret_len += sizeof(ie_header); + + /* + * Copy the generic element buffer to the output buffer, advance + * pointer + */ + memcpy(*buffer, priv->gen_ie_buf, priv->gen_ie_buf_len); + + /* + * Increment the return size and the return buffer pointer + * param + */ + *buffer += priv->gen_ie_buf_len; + ret_len += priv->gen_ie_buf_len; + + /* Reset the generic element buffer */ + priv->gen_ie_buf_len = 0; + } + + /* return the length appended to the buffer */ + return ret_len; +} + +/* Append TSF timestamp (AP TSF and local RX TSF) for reassoc */ +static int +nxpwifi_cmd_append_tsf_tlv(struct nxpwifi_private *priv, u8 **buffer, + struct nxpwifi_bssdescriptor *bss_desc) +{ + struct nxpwifi_ie_types_tsf_timestamp tsf_tlv; + __le64 tsf_val; + + /* Null Checks */ + if (!buffer) + return 0; + if (!*buffer) + return 0; + + memset(&tsf_tlv, 0x00, sizeof(struct nxpwifi_ie_types_tsf_timestamp)); + + tsf_tlv.header.type = cpu_to_le16(TLV_TYPE_TSFTIMESTAMP); + tsf_tlv.header.len = cpu_to_le16(2 * sizeof(tsf_val)); + + memcpy(*buffer, &tsf_tlv, sizeof(tsf_tlv.header)); + *buffer += sizeof(tsf_tlv.header); + + /* TSF at the time when beacon/probe_response was received */ + tsf_val = cpu_to_le64(bss_desc->fw_tsf); + memcpy(*buffer, &tsf_val, sizeof(tsf_val)); + *buffer += sizeof(tsf_val); + + tsf_val = cpu_to_le64(bss_desc->timestamp); + + nxpwifi_dbg(priv->adapter, INFO, + "info: %s: TSF offset calc: %016llx - %016llx\n", + __func__, bss_desc->timestamp, bss_desc->fw_tsf); + + memcpy(*buffer, &tsf_val, sizeof(tsf_val)); + *buffer += sizeof(tsf_val); + + return sizeof(tsf_tlv.header) + (2 * sizeof(tsf_val)); +} + +/* Compute intersection of two rate sets; rate1 updated in-place */ +static int nxpwifi_get_common_rates(struct nxpwifi_private *priv, u8 *rate1, + u32 rate1_size, u8 *rate2, u32 rate2_size) +{ + int ret; + u8 *ptr = rate1, *tmp; + u32 i, j; + + tmp = kmemdup(rate1, rate1_size, GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + memset(rate1, 0, rate1_size); + + for (i = 0; i < rate2_size && rate2[i]; i++) { + for (j = 0; j < rate1_size && tmp[j]; j++) { + /* + * Check common rate, excluding the bit for + * basic rate + */ + if ((rate2[i] & 0x7F) == (tmp[j] & 0x7F)) { + *rate1++ = tmp[j]; + break; + } + } + } + + nxpwifi_dbg(priv->adapter, INFO, "info: Tx data rate set to %#x\n", + priv->data_rate); + + if (!priv->is_data_rate_auto) { + while (*ptr) { + if ((*ptr & 0x7f) == priv->data_rate) { + ret = 0; + goto done; + } + ptr++; + } + nxpwifi_dbg(priv->adapter, ERROR, + "previously set fixed data rate %#x\t" + "is not compatible with the network\n", + priv->data_rate); + + ret = -EPERM; + goto done; + } + + ret = 0; +done: + kfree(tmp); + return ret; +} + +/* Build common rates from BSS descriptor into out_rates */ +static int +nxpwifi_setup_rates_from_bssdesc(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, + u8 *out_rates, u32 *out_rates_size) +{ + u8 card_rates[NXPWIFI_SUPPORTED_RATES]; + u32 card_rates_size; + int ret; + + /* Copy AP supported rates */ + memcpy(out_rates, bss_desc->supported_rates, NXPWIFI_SUPPORTED_RATES); + /* Get the STA supported rates */ + card_rates_size = nxpwifi_get_active_data_rates(priv, card_rates); + /* Get the common rates between AP and STA supported rates */ + ret = nxpwifi_get_common_rates(priv, out_rates, NXPWIFI_SUPPORTED_RATES, + card_rates, card_rates_size); + if (ret) { + *out_rates_size = 0; + nxpwifi_dbg(priv->adapter, ERROR, + "%s: cannot get common rates\n", + __func__); + } else { + *out_rates_size = + min_t(size_t, strlen(out_rates), NXPWIFI_SUPPORTED_RATES); + } + + return ret; +} + +/* Append WPS IE as pass-through TLV for join */ +static int +nxpwifi_cmd_append_wps_ie(struct nxpwifi_private *priv, u8 **buffer) +{ + int ret_len = 0; + struct nxpwifi_ie_types_header ie_header; + + if (!buffer || !*buffer) + return 0; + + /* + * If there is a wps element buffer setup, append it to the return + * parameter buffer pointer. + */ + if (priv->wps_ie_len) { + nxpwifi_dbg(priv->adapter, CMD, + "cmd: append wps element %d to %p\n", + priv->wps_ie_len, *buffer); + + /* Wrap the generic element buffer with a pass through TLV type */ + ie_header.type = cpu_to_le16(TLV_TYPE_PASSTHROUGH); + ie_header.len = cpu_to_le16(priv->wps_ie_len); + memcpy(*buffer, &ie_header, sizeof(ie_header)); + *buffer += sizeof(ie_header); + ret_len += sizeof(ie_header); + + memcpy(*buffer, priv->wps_ie, priv->wps_ie_len); + *buffer += priv->wps_ie_len; + ret_len += priv->wps_ie_len; + } + + kfree(priv->wps_ie); + priv->wps_ie_len = 0; + return ret_len; +} + +/* Append WPA/WPA2 RSN IE TLV */ +static int nxpwifi_append_rsn_ie_wpa_wpa2(struct nxpwifi_private *priv, + u8 **buffer) +{ + struct nxpwifi_ie_types_rsn_param_set *rsn_ie_tlv; + int rsn_ie_len; + + if (!buffer || !(*buffer)) + return 0; + + rsn_ie_tlv = (struct nxpwifi_ie_types_rsn_param_set *)(*buffer); + rsn_ie_tlv->header.type = cpu_to_le16((u16)priv->wpa_ie[0]); + rsn_ie_tlv->header.type = + cpu_to_le16(le16_to_cpu(rsn_ie_tlv->header.type) & 0x00FF); + rsn_ie_tlv->header.len = cpu_to_le16((u16)priv->wpa_ie[1]); + rsn_ie_tlv->header.len = cpu_to_le16(le16_to_cpu(rsn_ie_tlv->header.len) + & 0x00FF); + if (le16_to_cpu(rsn_ie_tlv->header.len) <= (sizeof(priv->wpa_ie) - 2)) + memcpy(rsn_ie_tlv->rsn_ie, &priv->wpa_ie[2], + le16_to_cpu(rsn_ie_tlv->header.len)); + else + return -ENOMEM; + + rsn_ie_len = sizeof(rsn_ie_tlv->header) + + le16_to_cpu(rsn_ie_tlv->header.len); + *buffer += rsn_ie_len; + + return rsn_ie_len; +} + +/* Build 802.11 association command and required TLVs */ +int nxpwifi_cmd_802_11_associate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + struct nxpwifi_bssdescriptor *bss_desc) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_associate *assoc = &cmd->params.associate; + struct nxpwifi_ie_types_host_mlme *host_mlme_tlv; + struct nxpwifi_ie_types_ssid_param_set *ssid_tlv; + struct nxpwifi_ie_types_phy_param_set *phy_tlv; + struct nxpwifi_ie_types_ss_param_set *ss_tlv; + struct nxpwifi_ie_types_rates_param_set *rates_tlv; + struct nxpwifi_ie_types_auth_type *auth_tlv; + struct nxpwifi_ie_types_sae_pwe_mode *sae_pwe_tlv; + struct nxpwifi_ie_types_chan_list_param_set *chan_tlv; + u8 rates[NXPWIFI_SUPPORTED_RATES]; + u32 rates_size; + u16 tmp_cap; + u8 *pos; + int rsn_ie_len = 0; + int ret; + + pos = (u8 *)assoc; + + cmd->command = cpu_to_le16(HOST_CMD_802_11_ASSOCIATE); + + /* Save so we know which BSS Desc to use in the response handler */ + priv->attempted_bss_desc = bss_desc; + + memcpy(assoc->peer_sta_addr, + bss_desc->mac_address, sizeof(assoc->peer_sta_addr)); + pos += sizeof(assoc->peer_sta_addr); + + /* Set the listen interval */ + assoc->listen_interval = cpu_to_le16(priv->listen_interval); + /* Set the beacon period */ + assoc->beacon_period = cpu_to_le16(bss_desc->beacon_period); + + pos += sizeof(assoc->cap_info_bitmap); + pos += sizeof(assoc->listen_interval); + pos += sizeof(assoc->beacon_period); + pos += sizeof(assoc->dtim_period); + + host_mlme_tlv = (struct nxpwifi_ie_types_host_mlme *)pos; + host_mlme_tlv->header.type = cpu_to_le16(TLV_TYPE_HOST_MLME); + host_mlme_tlv->header.len = cpu_to_le16(sizeof(host_mlme_tlv->host_mlme)); + host_mlme_tlv->host_mlme = 1; + pos += sizeof(host_mlme_tlv->header) + sizeof(host_mlme_tlv->host_mlme); + + ssid_tlv = (struct nxpwifi_ie_types_ssid_param_set *)pos; + ssid_tlv->header.type = cpu_to_le16(WLAN_EID_SSID); + ssid_tlv->header.len = cpu_to_le16((u16)bss_desc->ssid.ssid_len); + memcpy(ssid_tlv->ssid, bss_desc->ssid.ssid, + le16_to_cpu(ssid_tlv->header.len)); + pos += sizeof(ssid_tlv->header) + le16_to_cpu(ssid_tlv->header.len); + + phy_tlv = (struct nxpwifi_ie_types_phy_param_set *)pos; + phy_tlv->header.type = cpu_to_le16(WLAN_EID_DS_PARAMS); + phy_tlv->header.len = cpu_to_le16(sizeof(phy_tlv->fh_ds.ds_param_set)); + memcpy(&phy_tlv->fh_ds.ds_param_set, + &bss_desc->phy_param_set.ds_param_set.current_chan, + sizeof(phy_tlv->fh_ds.ds_param_set)); + pos += sizeof(phy_tlv->header) + le16_to_cpu(phy_tlv->header.len); + + ss_tlv = (struct nxpwifi_ie_types_ss_param_set *)pos; + ss_tlv->header.type = cpu_to_le16(WLAN_EID_CF_PARAMS); + ss_tlv->header.len = cpu_to_le16(sizeof(ss_tlv->cf_ibss.cf_param_set)); + pos += sizeof(ss_tlv->header) + le16_to_cpu(ss_tlv->header.len); + + /* Get the common rates supported between the driver and the BSS Desc */ + ret = nxpwifi_setup_rates_from_bssdesc(priv, bss_desc, + rates, &rates_size); + if (ret) + return ret; + + /* Save the data rates into Current BSS state structure */ + priv->curr_bss_params.num_of_rates = rates_size; + memcpy(&priv->curr_bss_params.data_rates, rates, rates_size); + + /* Setup the Rates TLV in the association command */ + rates_tlv = (struct nxpwifi_ie_types_rates_param_set *)pos; + rates_tlv->header.type = cpu_to_le16(WLAN_EID_SUPP_RATES); + rates_tlv->header.len = cpu_to_le16((u16)rates_size); + memcpy(rates_tlv->rates, rates, rates_size); + pos += sizeof(rates_tlv->header) + rates_size; + nxpwifi_dbg(adapter, INFO, "info: ASSOC_CMD: rates size = %d\n", + rates_size); + + /* Add the Authentication type */ + auth_tlv = (struct nxpwifi_ie_types_auth_type *)pos; + auth_tlv->header.type = cpu_to_le16(TLV_TYPE_AUTH_TYPE); + auth_tlv->header.len = cpu_to_le16(sizeof(auth_tlv->auth_type)); + if (priv->sec_info.wep_enabled) + auth_tlv->auth_type = + cpu_to_le16((u16)priv->sec_info.authentication_mode); + else + auth_tlv->auth_type = cpu_to_le16(NL80211_AUTHTYPE_OPEN_SYSTEM); + + pos += sizeof(auth_tlv->header) + le16_to_cpu(auth_tlv->header.len); + + if (priv->sec_info.authentication_mode == WLAN_AUTH_SAE) { + auth_tlv->auth_type = cpu_to_le16(NXPWIFI_AUTHTYPE_SAE); + if (bss_desc->bcn_rsnx_ie && + bss_desc->bcn_rsnx_ie->datalen && + (bss_desc->bcn_rsnx_ie->data[0] & + WLAN_RSNX_CAPA_SAE_H2E)) { + sae_pwe_tlv = + (struct nxpwifi_ie_types_sae_pwe_mode *)pos; + sae_pwe_tlv->header.type = + cpu_to_le16(TLV_TYPE_SAE_PWE_MODE); + sae_pwe_tlv->header.len = + cpu_to_le16(sizeof(sae_pwe_tlv->pwe[0])); + sae_pwe_tlv->pwe[0] = bss_desc->bcn_rsnx_ie->data[0]; + pos += sizeof(sae_pwe_tlv->header) + + sizeof(sae_pwe_tlv->pwe[0]); + } + } + + if (IS_SUPPORT_MULTI_BANDS(adapter) && + !(ISSUPP_11NENABLED(adapter->fw_cap_info) && + !bss_desc->disable_11n && + (priv->config_bands & BAND_GN || + priv->config_bands & BAND_AN) && + bss_desc->bcn_ht_cap)) { + /* + * Append a channel TLV for the channel the attempted AP was + * found on + */ + chan_tlv = (struct nxpwifi_ie_types_chan_list_param_set *)pos; + chan_tlv->header.type = cpu_to_le16(TLV_TYPE_CHANLIST); + chan_tlv->header.len = + cpu_to_le16(sizeof(struct nxpwifi_chan_scan_param_set)); + + memset(chan_tlv->chan_scan_param, 0x00, + sizeof(struct nxpwifi_chan_scan_param_set)); + chan_tlv->chan_scan_param[0].chan_number = + (bss_desc->phy_param_set.ds_param_set.current_chan); + nxpwifi_dbg(adapter, INFO, "info: Assoc: TLV Chan = %d\n", + chan_tlv->chan_scan_param[0].chan_number); + + chan_tlv->chan_scan_param[0].band_cfg = + nxpwifi_band_to_radio_type((u8)bss_desc->bss_band); + + nxpwifi_dbg(adapter, INFO, "info: Assoc: TLV Band = %d\n", + chan_tlv->chan_scan_param[0].band_cfg); + pos += sizeof(chan_tlv->header) + + sizeof(struct nxpwifi_chan_scan_param_set); + } + + if (!priv->wps.session_enable) { + if (priv->sec_info.wpa_enabled || priv->sec_info.wpa2_enabled) + rsn_ie_len = nxpwifi_append_rsn_ie_wpa_wpa2(priv, &pos); + + if (rsn_ie_len == -ENOMEM) + return -ENOMEM; + } + + if (ISSUPP_11NENABLED(adapter->fw_cap_info) && + !bss_desc->disable_11n && + (priv->config_bands & BAND_GN || + priv->config_bands & BAND_AN)) + nxpwifi_cmd_append_11n_tlv(priv, bss_desc, &pos); + + if (ISSUPP_11ACENABLED(adapter->fw_cap_info) && + !bss_desc->disable_11n && !bss_desc->disable_11ac && + (priv->config_bands & BAND_GAC || + priv->config_bands & BAND_AAC)) + nxpwifi_cmd_append_11ac_tlv(priv, bss_desc, &pos); + + if (ISSUPP_11AXENABLED(adapter->fw_cap_ext) && + nxpwifi_11ax_bandconfig_allowed(priv, bss_desc)) + nxpwifi_cmd_append_11ax_tlv(priv, bss_desc, &pos); + + /* Append vendor specific element TLV */ + nxpwifi_cmd_append_vsie_tlv(priv, NXPWIFI_VSIE_MASK_ASSOC, &pos); + + nxpwifi_wmm_process_association_req(priv, &pos, &bss_desc->wmm_ie, + bss_desc->bcn_ht_cap); + + if (priv->wps.session_enable && priv->wps_ie_len) + nxpwifi_cmd_append_wps_ie(priv, &pos); + + nxpwifi_cmd_append_generic_ie(priv, &pos); + + nxpwifi_cmd_append_tsf_tlv(priv, &pos, bss_desc); + + nxpwifi_11h_process_join(priv, &pos, bss_desc); + + cmd->size = cpu_to_le16((u16)(pos - (u8 *)assoc) + S_DS_GEN); + + /* Set the Capability info at last */ + tmp_cap = bss_desc->cap_info_bitmap; + + if (priv->config_bands == BAND_B) + tmp_cap &= ~WLAN_CAPABILITY_SHORT_SLOT_TIME; + + tmp_cap &= CAPINFO_MASK; + nxpwifi_dbg(adapter, INFO, + "info: ASSOC_CMD: tmp_cap=%4X CAPINFO_MASK=%4lX\n", + tmp_cap, CAPINFO_MASK); + assoc->cap_info_bitmap = cpu_to_le16(tmp_cap); + + return ret; +} + +static const char *assoc_failure_reason_to_str(u16 cap_info) +{ + switch (cap_info) { + case CONNECT_ERR_AUTH_ERR_STA_FAILURE: + return "CONNECT_ERR_AUTH_ERR_STA_FAILURE"; + case CONNECT_ERR_AUTH_MSG_UNHANDLED: + return "CONNECT_ERR_AUTH_MSG_UNHANDLED"; + case CONNECT_ERR_ASSOC_ERR_TIMEOUT: + return "CONNECT_ERR_ASSOC_ERR_TIMEOUT"; + case CONNECT_ERR_ASSOC_ERR_AUTH_REFUSED: + return "CONNECT_ERR_ASSOC_ERR_AUTH_REFUSED"; + case CONNECT_ERR_STA_FAILURE: + return "CONNECT_ERR_STA_FAILURE"; + } + + return "Unknown connect failure"; +} + +/* + * Handle association command response. + * Parse cap_info/status_code/AID and copy IEs, update connection state, + * WMM and HT/HE parameters, queues and filters. On failure, return the + * IEEE status code or a mapped timeout error. + */ +int nxpwifi_ret_802_11_associate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret = 0; + struct ieee_types_assoc_rsp *assoc_rsp; + struct nxpwifi_bssdescriptor *bss_desc; + bool enable_data = true; + u16 cap_info, status_code, aid; + const u8 *ie_ptr; + struct ieee80211_ht_operation *assoc_resp_ht_oper; + struct ieee80211_mgmt *hdr; + + if (!priv->attempted_bss_desc) { + nxpwifi_dbg(adapter, ERROR, + "%s: failed, association terminated by host\n", + __func__); + goto done; + } + + hdr = (struct ieee80211_mgmt *)&resp->params; + if (!memcmp(hdr->bssid, priv->attempted_bss_desc->mac_address, + ETH_ALEN)) + assoc_rsp = (struct ieee_types_assoc_rsp *)&hdr->u.assoc_resp; + else + assoc_rsp = (struct ieee_types_assoc_rsp *)&resp->params; + + cap_info = le16_to_cpu(assoc_rsp->cap_info_bitmap); + status_code = le16_to_cpu(assoc_rsp->status_code); + aid = le16_to_cpu(assoc_rsp->a_id); + + if ((aid & (BIT(15) | BIT(14))) != (BIT(15) | BIT(14))) + nxpwifi_dbg(adapter, ERROR, + "invalid AID value 0x%x; bits 15:14 not set\n", aid); + + aid &= ~(BIT(15) | BIT(14)); + + priv->assoc_rsp_size = min(le16_to_cpu(resp->size) - S_DS_GEN, + sizeof(priv->assoc_rsp_buf)); + + assoc_rsp->a_id = cpu_to_le16(aid); + memcpy(priv->assoc_rsp_buf, &resp->params, priv->assoc_rsp_size); + + if (status_code) { + adapter->dbg.num_cmd_assoc_failure++; + nxpwifi_dbg(adapter, ERROR, + "ASSOC_RESP: failed,\t" + "status code=%d err=%#x a_id=%#x\n", + status_code, cap_info, + le16_to_cpu(assoc_rsp->a_id)); + + nxpwifi_dbg(adapter, ERROR, "assoc failure: reason %s\n", + assoc_failure_reason_to_str(cap_info)); + if (cap_info == CONNECT_ERR_ASSOC_ERR_TIMEOUT) { + if (status_code == NXPWIFI_ASSOC_CMD_FAILURE_AUTH) { + ret = WLAN_STATUS_AUTH_TIMEOUT; + nxpwifi_dbg(adapter, ERROR, + "ASSOC_RESP: AUTH timeout\n"); + } else { + ret = WLAN_STATUS_UNSPECIFIED_FAILURE; + nxpwifi_dbg(adapter, ERROR, + "ASSOC_RESP: UNSPECIFIED failure\n"); + } + + priv->assoc_rsp_size = 0; + } else { + ret = status_code; + } + + goto done; + } + + /* Send a Media Connected event, according to the Spec */ + priv->media_connected = true; + + adapter->ps_state = PS_STATE_AWAKE; + adapter->pps_uapsd_mode = false; + adapter->tx_lock_flag = false; + + /* Set the attempted BSSID Index to current */ + bss_desc = priv->attempted_bss_desc; + + nxpwifi_dbg(adapter, INFO, "info: ASSOC_RESP: %s\n", + bss_desc->ssid.ssid); + + /* Make a copy of current BSSID descriptor */ + memcpy(&priv->curr_bss_params.bss_descriptor, + bss_desc, sizeof(struct nxpwifi_bssdescriptor)); + + /* Update curr_bss_params */ + priv->curr_bss_params.bss_descriptor.channel = + bss_desc->phy_param_set.ds_param_set.current_chan; + + priv->curr_bss_params.band = (u8)bss_desc->bss_band; + + if (bss_desc->wmm_ie.element_id == WLAN_EID_VENDOR_SPECIFIC) + priv->curr_bss_params.wmm_enabled = true; + else + priv->curr_bss_params.wmm_enabled = false; + + if ((priv->wmm_required || bss_desc->bcn_ht_cap) && + priv->curr_bss_params.wmm_enabled) + priv->wmm_enabled = true; + else + priv->wmm_enabled = false; + + priv->curr_bss_params.wmm_uapsd_enabled = false; + + priv->curr_bss_params.wmm_uapsd_enabled = priv->wmm_enabled && + (bss_desc->wmm_ie.qos_info & IEEE80211_WMM_IE_AP_QOSINFO_UAPSD); + + /* Store the bandwidth information from assoc response */ + ie_ptr = cfg80211_find_ie(WLAN_EID_HT_OPERATION, assoc_rsp->ie_buffer, + priv->assoc_rsp_size + - sizeof(struct ieee_types_assoc_rsp)); + if (ie_ptr) { + assoc_resp_ht_oper = (struct ieee80211_ht_operation *)(ie_ptr + + sizeof(struct element)); + priv->assoc_resp_ht_param = assoc_resp_ht_oper->ht_param; + priv->ht_param_present = true; + } else { + priv->ht_param_present = false; + } + + nxpwifi_dbg(adapter, INFO, + "info: ASSOC_RESP: curr_pkt_filter is %#x\n", + priv->curr_pkt_filter); + if (priv->sec_info.wpa_enabled || priv->sec_info.wpa2_enabled) + priv->wpa_is_gtk_set = false; + + if (priv->wmm_enabled) { + /* Don't re-enable carrier until we get the WMM_GET_STATUS event */ + enable_data = false; + } else { + /* Since WMM is not enabled, setup the queues with the defaults */ + nxpwifi_wmm_setup_queue_priorities(priv, NULL); + nxpwifi_wmm_setup_ac_downgrade(priv); + } + + if (enable_data) + nxpwifi_dbg(adapter, INFO, + "info: post association, re-enabling data flow\n"); + + /* Reset SNR/NF/RSSI values */ + priv->data_rssi_last = 0; + priv->data_nf_last = 0; + priv->data_rssi_avg = 0; + priv->data_nf_avg = 0; + priv->bcn_rssi_last = 0; + priv->bcn_nf_last = 0; + priv->bcn_rssi_avg = 0; + priv->bcn_nf_avg = 0; + priv->rxpd_rate = 0; + priv->rxpd_htinfo = 0; + + nxpwifi_save_curr_bcn(priv); + + adapter->dbg.num_cmd_assoc_success++; + + nxpwifi_dbg(adapter, MSG, "assoc: associated with %pM\n", + priv->attempted_bss_desc->mac_address); + + /* Add the ra_list here for infra mode as there will be only 1 ra always */ + nxpwifi_ralist_add(priv, + priv->curr_bss_params.bss_descriptor.mac_address); + + netif_carrier_on(priv->netdev); + nxpwifi_wake_up_net_dev_queue(priv->netdev, adapter); + + if (priv->sec_info.wpa_enabled || priv->sec_info.wpa2_enabled) + priv->scan_block = true; + else + priv->port_open = true; + +done: + /* Need to indicate IOCTL complete */ + if (adapter->curr_cmd->wait_q_enabled) { + if (ret) + adapter->cmd_wait_q.status = -1; + else + adapter->cmd_wait_q.status = 0; + } + + return ret; +} + +/* Associate to the specified BSS (STA only) */ +int nxpwifi_associate(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + /* + * Return error if the adapter is not STA role or table entry + * is not marked as infra. + */ + if ((GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_STA) || + bss_desc->bss_mode != NL80211_IFTYPE_STATION) + return -EINVAL; + + if (ISSUPP_11ACENABLED(priv->adapter->fw_cap_info) && + !bss_desc->disable_11n && !bss_desc->disable_11ac && + priv->config_bands & BAND_AAC) + nxpwifi_set_11ac_ba_params(priv); + else + nxpwifi_set_ba_params(priv); + + /* + * Clear any past association response stored for application + * retrieval + */ + priv->assoc_rsp_size = 0; + + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_ASSOCIATE, + HOST_ACT_GEN_SET, 0, bss_desc, true); +} + +/* Send deauth to disconnect from an infrastructure BSS */ +static int nxpwifi_deauthenticate_infra(struct nxpwifi_private *priv, u8 *mac) +{ + u8 mac_address[ETH_ALEN]; + int ret; + + if (!mac || is_zero_ether_addr(mac)) + memcpy(mac_address, + priv->curr_bss_params.bss_descriptor.mac_address, + ETH_ALEN); + else + memcpy(mac_address, mac, ETH_ALEN); + + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_DEAUTHENTICATE, + HOST_ACT_GEN_SET, 0, mac_address, true); + + return ret; +} + +/* Disconnect from the current BSS (STA/P2P/AP) */ +int nxpwifi_deauthenticate(struct nxpwifi_private *priv, u8 *mac) +{ + int ret = 0; + + if (!priv->media_connected) + return 0; + + priv->auth_flag = 0; + priv->auth_alg = WLAN_AUTH_NONE; + priv->host_mlme_reg = false; + priv->mgmt_frame_mask = 0; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_MGMT_FRAME_REG, + HOST_ACT_GEN_SET, 0, + &priv->mgmt_frame_mask, false); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "could not unregister mgmt frame rx\n"); + return ret; + } + + switch (priv->bss_mode) { + case NL80211_IFTYPE_STATION: + ret = nxpwifi_deauthenticate_infra(priv, mac); + if (ret) + cfg80211_disconnected(priv->netdev, 0, NULL, 0, + true, GFP_KERNEL); + break; + case NL80211_IFTYPE_AP: + ret = nxpwifi_send_cmd(priv, HOST_CMD_UAP_BSS_STOP, + HOST_ACT_GEN_SET, 0, NULL, true); + break; + default: + break; + } + + return ret; +} + +/* Deauthenticate/disconnect from all BSS. */ +void nxpwifi_deauthenticate_all(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + int i; + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + nxpwifi_deauthenticate(priv, NULL); + } +} +EXPORT_SYMBOL_GPL(nxpwifi_deauthenticate_all); + +/* Convert band to radio type used in channel TLV. */ +u8 nxpwifi_band_to_radio_type(u16 config_bands) +{ + if (config_bands & BAND_A || config_bands & BAND_AN || + config_bands & BAND_AAC || config_bands & BAND_AAX) + return HOST_SCAN_RADIO_TYPE_A; + + return HOST_SCAN_RADIO_TYPE_BG; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/main.c b/drivers/net/wireless/nxp/nxpwifi/main.c new file mode 100644 index 000000000000..4e01f45f3a00 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/main.c @@ -0,0 +1,1673 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: major functions + * + * Copyright 2011-2024 NXP + */ + +#include + +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "cfg80211.h" +#include "11n.h" + +#define VERSION "1.0" + +static unsigned int debug_mask = NXPWIFI_DEFAULT_DEBUG_MASK; + +char nxpwifi_driver_version[] = "nxpwifi " VERSION " (%s) "; + +const u16 nxpwifi_1d_to_wmm_queue[8] = { 1, 0, 0, 1, 2, 2, 3, 3 }; + +/* Optional RF calibration data file */ +static const char *cal_data_name = "nxp/cal_data.conf"; + +/* Register device; init adapter/privs/if_ops/locks; cleanup on fail. */ +static struct nxpwifi_adapter *nxpwifi_register(void *card, struct device *dev, + struct nxpwifi_if_ops *if_ops) +{ + struct nxpwifi_adapter *adapter; + int ret = 0; + int i; + + adapter = kzalloc_obj(*adapter, GFP_KERNEL); + if (!adapter) + return ERR_PTR(-ENOMEM); + + adapter->dev = dev; + adapter->card = card; + + /* Save interface specific operations in adapter */ + memmove(&adapter->if_ops, if_ops, sizeof(struct nxpwifi_if_ops)); + adapter->debug_mask = debug_mask; + + /* card specific initialization has been deferred until now .. */ + if (adapter->if_ops.init_if) { + ret = adapter->if_ops.init_if(adapter); + if (ret) + goto error; + } + + adapter->priv_num = 0; + + for (i = 0; i < NXPWIFI_MAX_BSS_NUM; i++) { + /* Allocate memory for private structure */ + adapter->priv[i] = + kzalloc_obj(struct nxpwifi_private, GFP_KERNEL); + if (!adapter->priv[i]) { + ret = -ENOMEM; + goto error; + } + + adapter->priv[i]->adapter = adapter; + adapter->priv_num++; + } + nxpwifi_init_lock_list(adapter); + + timer_setup(&adapter->cmd_timer, nxpwifi_cmd_timeout_func, 0); + + if (ret) + return ERR_PTR(ret); + else + return adapter; + +error: + nxpwifi_dbg(adapter, ERROR, + "info: leave %s with error\n", __func__); + + for (i = 0; i < adapter->priv_num; i++) + kfree(adapter->priv[i]); + + kfree(adapter); + + return ERR_PTR(ret); +} + +/* Unregister device; free timers, beacons, privs, nd_info, adapter. */ +static void nxpwifi_unregister(struct nxpwifi_adapter *adapter) +{ + s32 i; + + if (adapter->if_ops.cleanup_if) + adapter->if_ops.cleanup_if(adapter); + + timer_delete_sync(&adapter->cmd_timer); + + /* Free private structures */ + for (i = 0; i < adapter->priv_num; i++) { + nxpwifi_free_curr_bcn(adapter->priv[i]); + kfree(adapter->priv[i]); + } + + if (adapter->nd_info) { + for (i = 0 ; i < adapter->nd_info->n_matches ; i++) + kfree(adapter->nd_info->matches[i]); + kfree(adapter->nd_info); + adapter->nd_info = NULL; + } + + kfree(adapter->regd); + + kfree(adapter); +} + +static void nxpwifi_queue_rx_work(struct nxpwifi_adapter *adapter) +{ + queue_work(adapter->rx_workqueue, &adapter->rx_work); +} + +static void nxpwifi_process_rx(struct nxpwifi_adapter *adapter) +{ + struct sk_buff *skb; + struct nxpwifi_rxinfo *rx_info; + + if (atomic_read(&adapter->iface_changing) || + atomic_read(&adapter->rx_ba_teardown_pending)) + return; + + /* Check for Rx data */ + while ((skb = skb_dequeue(&adapter->rx_data_q))) { + atomic_dec(&adapter->rx_pending); + if (adapter->delay_main_work && + (atomic_read(&adapter->rx_pending) < LOW_RX_PENDING)) { + adapter->delay_main_work = false; + nxpwifi_queue_work(adapter, &adapter->main_work); + } + rx_info = NXPWIFI_SKB_RXCB(skb); + if (rx_info->buf_type == NXPWIFI_TYPE_AGGR_DATA) { + if (adapter->if_ops.deaggr_pkt) + adapter->if_ops.deaggr_pkt(adapter, skb); + dev_kfree_skb_any(skb); + } else { + nxpwifi_handle_rx_packet(adapter, skb); + } + } +} + +static void maybe_quirk_fw_disable_ds(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA); + struct nxpwifi_ver_ext ver_ext; + + if (test_and_set_bit(NXPWIFI_IS_REQUESTING_FW_VEREXT, &adapter->work_flags)) + return; + + memset(&ver_ext, 0, sizeof(ver_ext)); + ver_ext.version_str_sel = 1; + if (nxpwifi_send_cmd(priv, HOST_CMD_VERSION_EXT, + HOST_ACT_GEN_GET, 0, &ver_ext, false)) { + nxpwifi_dbg(priv->adapter, MSG, + "Checking hardware revision failed.\n"); + } +} + +static void nxpwifi_handle_irq_status(struct nxpwifi_adapter *adapter, u8 istat) +{ + if (adapter->hs_activated) + nxpwifi_process_hs_config(adapter); + if (adapter->if_ops.process_int_status) + adapter->if_ops.process_int_status(adapter, istat); +} + +static bool nxpwifi_drain_tx(struct nxpwifi_adapter *adapter) +{ + bool ret = false; + + if ((adapter->scan_chan_gap_enabled || !adapter->scan_processing) && + !adapter->data_sent && !skb_queue_empty(&adapter->tx_data_q)) { + if (adapter->hs_activated_manually) { + nxpwifi_cancel_hs(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY), + NXPWIFI_ASYNC_CMD); + adapter->hs_activated_manually = false; + } + + nxpwifi_process_tx_queue(adapter); + if (adapter->hs_activated) { + clear_bit(NXPWIFI_IS_HS_CONFIGURED, + &adapter->work_flags); + nxpwifi_hs_activated_event + (nxpwifi_get_priv + (adapter, NXPWIFI_BSS_ROLE_ANY), + false); + } + ret = true; + } + + if ((adapter->scan_chan_gap_enabled || + !adapter->scan_processing) && + !adapter->data_sent && + !nxpwifi_bypass_txlist_empty(adapter)) { + if (adapter->hs_activated_manually) { + nxpwifi_cancel_hs(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY), + NXPWIFI_ASYNC_CMD); + adapter->hs_activated_manually = false; + } + nxpwifi_process_bypass_tx(adapter); + if (adapter->hs_activated) { + clear_bit(NXPWIFI_IS_HS_CONFIGURED, + &adapter->work_flags); + nxpwifi_hs_activated_event + (nxpwifi_get_priv + (adapter, NXPWIFI_BSS_ROLE_ANY), + false); + } + ret = true; + } + + if ((adapter->scan_chan_gap_enabled || + !adapter->scan_processing) && + !adapter->data_sent && !nxpwifi_wmm_lists_empty(adapter)) { + if (adapter->hs_activated_manually) { + nxpwifi_cancel_hs(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY), + NXPWIFI_ASYNC_CMD); + adapter->hs_activated_manually = false; + } + + nxpwifi_wmm_process_tx(adapter); + if (adapter->hs_activated) { + clear_bit(NXPWIFI_IS_HS_CONFIGURED, + &adapter->work_flags); + nxpwifi_hs_activated_event + (nxpwifi_get_priv + (adapter, NXPWIFI_BSS_ROLE_ANY), + false); + } + ret = true; + } + + return ret; +} + +static bool nxpwifi_handle_rx(struct nxpwifi_adapter *adapter) +{ + if (adapter->rx_work_enabled && adapter->data_received) { + nxpwifi_queue_rx_work(adapter); + return true; + } + + return false; +} + +static bool nxpwifi_handle_cmd_response(struct nxpwifi_adapter *adapter) +{ + /* Check for Cmd Resp */ + if (adapter->cmd_resp_received) { + adapter->cmd_resp_received = false; + nxpwifi_process_cmdresp(adapter); + return true; + } + + return false; +} + +static bool nxpwifi_handle_events(struct nxpwifi_adapter *adapter) +{ + if (adapter->event_received) { + adapter->event_received = false; + nxpwifi_process_event(adapter); + return true; + } + + return false; +} + +static bool nxpwifi_tx_has_pending(struct nxpwifi_adapter *adapter) +{ + return !skb_queue_empty(&adapter->tx_data_q) || + !nxpwifi_bypass_txlist_empty(adapter) || + !nxpwifi_wmm_lists_empty(adapter); +} + +static bool nxpwifi_cmd_has_pending(struct nxpwifi_adapter *adapter) +{ + return !list_empty(&adapter->cmd_pending_q); +} + +static bool nxpwifi_events_has_pending(struct nxpwifi_adapter *adapter) +{ + return adapter->event_received; +} + +static bool nxpwifi_should_wakeup_card(struct nxpwifi_adapter *adapter) +{ + if (adapter->ps_state != PS_STATE_SLEEP) + return false; + + if (!adapter->pm_wakeup_card_req || adapter->pm_wakeup_fw_try) + return false; + + return nxpwifi_is_command_pending(adapter) || nxpwifi_tx_has_pending(adapter); +} + +static bool nxpwifi_should_exit_main_loop(struct nxpwifi_adapter *adapter) +{ + if (adapter->pm_wakeup_fw_try) + return true; + + if (adapter->ps_state == PS_STATE_PRE_SLEEP) + nxpwifi_check_ps_cond(adapter); + + if (adapter->ps_state != PS_STATE_AWAKE) + return true; + + if (adapter->tx_lock_flag) + return true; + + if ((!adapter->scan_chan_gap_enabled && adapter->scan_processing) || + adapter->data_sent || !nxpwifi_tx_has_pending(adapter)) { + if (adapter->cmd_sent || adapter->curr_cmd || + !nxpwifi_is_command_pending(adapter)) + return true; + } + + return false; +} + +static void nxpwifi_wakeup_card(struct nxpwifi_adapter *adapter) +{ + adapter->pm_wakeup_fw_try = true; + mod_timer(&adapter->wakeup_timer, jiffies + (HZ * 3)); + adapter->if_ops.wakeup(adapter); +} + +static void nxpwifi_handle_vdll_download(struct nxpwifi_adapter *adapter) +{ + if (!adapter->cmd_sent && adapter->vdll_ctrl.pending_block) { + struct vdll_dnld_ctrl *ctrl = &adapter->vdll_ctrl; + + nxpwifi_download_vdll_block(adapter, ctrl->pending_block, + ctrl->pending_block_len); + ctrl->pending_block = NULL; + } +} + +static void nxpwifi_finish_delayed_null_pkt(struct nxpwifi_adapter *adapter) +{ + if (!adapter->delay_null_pkt) + return; + + if (adapter->cmd_sent || adapter->curr_cmd || nxpwifi_is_command_pending(adapter)) + return; + + if (nxpwifi_tx_has_pending(adapter)) + return; + + if (!nxpwifi_send_null_packet(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA), + NXPWIFI_TxPD_POWER_MGMT_NULL_PACKET | + NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET)) { + adapter->delay_null_pkt = false; + adapter->ps_state = PS_STATE_SLEEP; + } +} + +static bool nxpwifi_pump_command(struct nxpwifi_adapter *adapter) +{ + if (!adapter->cmd_sent && !adapter->curr_cmd) { + if (!nxpwifi_exec_next_cmd(adapter)) + return true; + } + + return false; +} + +/* Main loop: IRQ/RX/CMD/EVENT; wake card; TX; PS null; exit if idle. */ +void nxpwifi_main_process(struct nxpwifi_adapter *adapter) +{ + unsigned long flags; + + /* Check if virtual interface changing */ + if (atomic_read(&adapter->iface_changing)) { + nxpwifi_dbg(adapter, + INFO, "main_process skipped due to iface_changing"); + return; + } + + for (;;) { + bool did_work = false; + u8 istat = 0; + + if (adapter->hw_status == NXPWIFI_HW_STATUS_NOT_READY) + break; + + /* + * For non-USB interfaces, If we process interrupts first, it + * would increase RX pending even further. Avoid this by + * checking if rx_pending has crossed high threshold and + * schedule rx work queue and then process interrupts. + * For USB interface, there are no interrupts. We already have + * HIGH_RX_PENDING check in usb.c + */ + if (atomic_read(&adapter->rx_pending) >= HIGH_RX_PENDING) { + adapter->delay_main_work = true; + nxpwifi_queue_rx_work(adapter); + break; + } + + /* + * Snapshot-and-clear the interrupt status. + * + * Take the same lock as the producer (nxpwifi_sdio_interrupt()) uses + * when OR-ing new bits into adapter->int_status. We atomically grab + * what has accumulated and clear it, so this consumer owns this batch. + */ + spin_lock_irqsave(&adapter->int_lock, flags); + istat = adapter->int_status; + adapter->int_status = 0; + spin_unlock_irqrestore(&adapter->int_lock, flags); + + /* Handle pending interrupt if any */ + if (istat) { + nxpwifi_handle_irq_status(adapter, istat); + did_work = true; + } + + did_work |= nxpwifi_handle_rx(adapter); + + if (nxpwifi_should_wakeup_card(adapter)) { + nxpwifi_wakeup_card(adapter); + continue; + } + + if (IS_CARD_RX_RCVD(adapter)) { + /* Card has responded, clear wakeup state and update power state */ + adapter->data_received = false; + adapter->pm_wakeup_fw_try = false; + timer_delete(&adapter->wakeup_timer); + if (adapter->ps_state == PS_STATE_SLEEP) + adapter->ps_state = PS_STATE_AWAKE; + } else { + if (nxpwifi_should_exit_main_loop(adapter)) + break; + } + + did_work |= nxpwifi_handle_events(adapter); + + did_work |= nxpwifi_handle_cmd_response(adapter); + + /* Check if we need to confirm Sleep Request received previously */ + if (adapter->ps_state == PS_STATE_PRE_SLEEP) + nxpwifi_check_ps_cond(adapter); + + /* + * The ps_state may have been changed during processing of + * Sleep Request event. + */ + if (adapter->ps_state != PS_STATE_AWAKE) + continue; + + if (adapter->tx_lock_flag) + continue; + + nxpwifi_handle_vdll_download(adapter); + + did_work |= nxpwifi_pump_command(adapter); + + did_work |= nxpwifi_drain_tx(adapter); + + /* + * Attempt to send delayed null packet. + * If successful, firmware will enter sleep and ps_state will be updated. + * We check ps_state here to determine if main loop can safely exit. + */ + nxpwifi_finish_delayed_null_pkt(adapter); + + if (adapter->ps_state == PS_STATE_SLEEP) + break; + /* + * Step 3) Cooperative preemption point. + * cond_resched() yields ONLY if need_resched() is set. Placing it + * BEFORE the final check improves fairness: it lets ksdioirqd (RT/FIFO) + * or other producers run and set new int_status bits. Immediately + * after we return here, we perform the final "net cast" (Step 4) to + * decide if we should continue or return. + */ + + cond_resched(); + + /* + * Step 4) Exit decision with lost-kick closure. + * + * We consider exiting ONLY when this round did no real work. + * Rationale: + * - If did_work == true: we will loop anyway; at Step 1 we will + * re-snapshot int_status, so there's no need to re-check now. + * - If did_work == false: we appear idle and may return. But during + * our execution window, a producer may have just set int_status and + * queue_work(); since this work is still running, queue_work() + * returns false (no second instance queued). If we return now, + * we'd leave unprocessed status with no pending work => lost-kick. + * + * Therefore, perform a single final check: if *anything* is pending, + * continue looping; otherwise, break and return. + */ + if (!did_work) { + bool more = false; + unsigned long flags; + /* 4a) New IRQ bits raced in while we were running? */ + spin_lock_irqsave(&adapter->int_lock, flags); + more |= adapter->int_status != 0; + spin_unlock_irqrestore(&adapter->int_lock, flags); + /* 4b) Any other sources still pending? (driver-specific) */ + more |= nxpwifi_tx_has_pending(adapter); + more |= nxpwifi_cmd_has_pending(adapter); + more |= nxpwifi_events_has_pending(adapter); + + if (!more) + break; /* Truly quiescent now: safe to return. */ + /* else: loop back to Step 1 to consume what just arrived. */ + } + /* If did_work == true, we loop unconditionally and re-snapshot. */ + }; +} + +/* Free adapter via nxpwifi_unregister(). */ +static void nxpwifi_free_adapter(struct nxpwifi_adapter *adapter) +{ + if (!adapter) { + pr_err("%s: adapter is NULL\n", __func__); + return; + } + + nxpwifi_unregister(adapter); + pr_debug("info: %s: free adapter\n", __func__); +} + +/* Destroy main and RX workqueues. */ +static void nxpwifi_terminate_workqueue(struct nxpwifi_adapter *adapter) +{ + if (adapter->workqueue) { + destroy_workqueue(adapter->workqueue); + adapter->workqueue = NULL; + } + + if (adapter->rx_workqueue) { + destroy_workqueue(adapter->rx_workqueue); + adapter->rx_workqueue = NULL; + } +} + +/* FW bring-up: download, enable IRQ, init FW; cfg80211+ifaces; cleanup. */ +static int _nxpwifi_fw_dpc(const struct firmware *firmware, void *context) +{ + int ret = 0; + char fmt[64]; + struct nxpwifi_adapter *adapter = context; + struct nxpwifi_fw_image fw; + bool init_failed = false; + struct wireless_dev *wdev; + struct completion *fw_done = adapter->fw_done; + + if (!firmware) { + nxpwifi_dbg(adapter, ERROR, + "Failed to get firmware %s\n", adapter->fw_name); + ret = -EINVAL; + goto err_dnld_fw; + } + + memset(&fw, 0, sizeof(struct nxpwifi_fw_image)); + adapter->firmware = firmware; + fw.fw_buf = (u8 *)adapter->firmware->data; + fw.fw_len = adapter->firmware->size; + + if (adapter->if_ops.dnld_fw) + ret = adapter->if_ops.dnld_fw(adapter, &fw); + else + ret = nxpwifi_dnld_fw(adapter, &fw); + + if (ret) + goto err_dnld_fw; + + nxpwifi_dbg(adapter, MSG, "WLAN FW is active\n"); + + /* Load optional calibration data */ + ret = request_firmware(&adapter->cal_data, cal_data_name, adapter->dev); + if (ret) { + nxpwifi_dbg(adapter, INFO, "no %s, using default cal\n", + cal_data_name); + adapter->cal_data = NULL; + } + + /* enable host interrupt after fw dnld is successful */ + if (adapter->if_ops.enable_int) { + ret = adapter->if_ops.enable_int(adapter); + if (ret) + goto err_dnld_fw; + } + + ret = nxpwifi_init_fw(adapter); + if (ret) + goto err_init_fw; + + maybe_quirk_fw_disable_ds(adapter); + + if (!adapter->wiphy) { + if (nxpwifi_register_cfg80211(adapter)) { + nxpwifi_dbg(adapter, ERROR, + "cannot register with cfg80211\n"); + goto err_init_fw; + } + } + + if (nxpwifi_init_channel_scan_gap(adapter)) { + nxpwifi_dbg(adapter, ERROR, + "could not init channel stats table\n"); + goto err_init_chan_scan; + } + + rtnl_lock(); + /* Create station interface by default */ + wdev = nxpwifi_add_virtual_intf(adapter->wiphy, "mlan%d", NET_NAME_ENUM, + NL80211_IFTYPE_STATION, NULL); + if (IS_ERR(wdev)) { + nxpwifi_dbg(adapter, ERROR, + "cannot create default STA interface\n"); + rtnl_unlock(); + goto err_add_intf; + } + + wdev = nxpwifi_add_virtual_intf(adapter->wiphy, "uap%d", NET_NAME_ENUM, + NL80211_IFTYPE_AP, NULL); + if (IS_ERR(wdev)) { + nxpwifi_dbg(adapter, ERROR, + "cannot create AP interface\n"); + rtnl_unlock(); + goto err_add_intf; + } + + rtnl_unlock(); + + nxpwifi_drv_get_driver_version(adapter, fmt, sizeof(fmt) - 1); + nxpwifi_dbg(adapter, MSG, "driver_version = %s\n", fmt); + adapter->is_up = true; + goto done; + +err_add_intf: + vfree(adapter->chan_stats); +err_init_chan_scan: + wiphy_unregister(adapter->wiphy); + wiphy_free(adapter->wiphy); +err_init_fw: + if (adapter->if_ops.disable_int) + adapter->if_ops.disable_int(adapter); +err_dnld_fw: + nxpwifi_dbg(adapter, ERROR, + "info: %s: unregister device\n", __func__); + if (adapter->if_ops.unregister_dev) + adapter->if_ops.unregister_dev(adapter); + + set_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags); + nxpwifi_terminate_workqueue(adapter); + + if (adapter->hw_status == NXPWIFI_HW_STATUS_READY) { + pr_debug("info: %s: shutdown nxpwifi\n", __func__); + nxpwifi_shutdown_drv(adapter); + nxpwifi_free_cmd_buffers(adapter); + } + + init_failed = true; +done: + if (adapter->cal_data) { + release_firmware(adapter->cal_data); + adapter->cal_data = NULL; + } + if (adapter->firmware) { + release_firmware(adapter->firmware); + adapter->firmware = NULL; + } + if (init_failed) + nxpwifi_free_adapter(adapter); + + /* Tell all current and future waiters we're finished */ + complete_all(fw_done); + + return ret; +} + +static void nxpwifi_fw_dpc(const struct firmware *firmware, void *context) +{ + _nxpwifi_fw_dpc(firmware, context); +} + +/* Request firmware (sync/async) and start HW init. */ +static int nxpwifi_init_hw_fw(struct nxpwifi_adapter *adapter, + bool req_fw_nowait) +{ + int ret; + + if (req_fw_nowait) { + ret = request_firmware_nowait(THIS_MODULE, 1, adapter->fw_name, + adapter->dev, GFP_KERNEL, adapter, + nxpwifi_fw_dpc); + } else { + ret = request_firmware(&adapter->firmware, + adapter->fw_name, + adapter->dev); + } + + if (ret < 0) + nxpwifi_dbg(adapter, ERROR, "request_firmware%s error %d\n", + req_fw_nowait ? "_nowait" : "", ret); + return ret; +} + +/* ndo_open: bring carrier down. */ +static int +nxpwifi_open(struct net_device *dev) +{ + netif_carrier_off(dev); + + return 0; +} + +/* ndo_stop: abort scan/sched-scan if running. */ +static int +nxpwifi_close(struct net_device *dev) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + + if (priv->scan_request) { + struct cfg80211_scan_info info = { + .aborted = true, + }; + + nxpwifi_dbg(priv->adapter, INFO, + "aborting scan on ndo_stop\n"); + cfg80211_scan_done(priv->scan_request, &info); + priv->scan_request = NULL; + priv->scan_aborting = true; + } + + if (priv->sched_scanning) { + nxpwifi_dbg(priv->adapter, INFO, + "aborting bgscan on ndo_stop\n"); + nxpwifi_stop_bg_scan(priv); + cfg80211_sched_scan_stopped(priv->wdev.wiphy, 0); + } + + return 0; +} + +static bool +nxpwifi_bypass_tx_queue(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct ethhdr *eth_hdr = (struct ethhdr *)skb->data; + + if (eth_hdr->h_proto == htons(ETH_P_PAE) || + nxpwifi_is_skb_mgmt_frame(skb)) { + nxpwifi_dbg(priv->adapter, DATA, + "bypass txqueue; eth type %#x, mgmt %d\n", + ntohs(eth_hdr->h_proto), + nxpwifi_is_skb_mgmt_frame(skb)); + if (eth_hdr->h_proto == htons(ETH_P_PAE)) + nxpwifi_dbg(priv->adapter, MSG, + "key: send EAPOL to %pM\n", + eth_hdr->h_dest); + return true; + } + + return false; +} + +/* Queue SKB (WMM or bypass) and schedule main work. */ +void nxpwifi_queue_tx_pkt(struct nxpwifi_private *priv, struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct netdev_queue *txq; + int index = nxpwifi_1d_to_wmm_queue[skb->priority]; + + if (atomic_inc_return(&priv->wmm_tx_pending[index]) >= MAX_TX_PENDING) { + txq = netdev_get_tx_queue(priv->netdev, index); + if (!netif_tx_queue_stopped(txq)) { + netif_tx_stop_queue(txq); + nxpwifi_dbg(adapter, DATA, + "stop queue: %d\n", index); + } + } + + if (nxpwifi_bypass_tx_queue(priv, skb)) { + atomic_inc(&adapter->tx_pending); + atomic_inc(&adapter->bypass_tx_pending); + nxpwifi_wmm_add_buf_bypass_txqueue(priv, skb); + } else { + atomic_inc(&adapter->tx_pending); + nxpwifi_wmm_add_buf_txqueue(priv, skb); + } + + nxpwifi_queue_work(adapter, &adapter->main_work); +} + +struct sk_buff * +nxpwifi_clone_skb_for_tx_status(struct nxpwifi_private *priv, + struct sk_buff *skb, u8 flag, u64 *cookie) +{ + struct sk_buff *orig_skb = skb; + struct nxpwifi_txinfo *tx_info, *orig_tx_info; + u32 id32 = 0; + int ret; + + skb = skb_clone(skb, GFP_ATOMIC); + if (skb) { + spin_lock_bh(&priv->ack_status_lock); + /* + * Use XArray to allocate IDs in the range 1..0x0F. + * Limit ensures the allocated token ID is always within this + * range. + */ + ret = xa_alloc(&priv->ack_status_frames, &id32, orig_skb, + XA_LIMIT(1, 0x0f), GFP_ATOMIC); + spin_unlock_bh(&priv->ack_status_lock); + + if (ret == 0) { + tx_info = NXPWIFI_SKB_TXCB(skb); + tx_info->ack_frame_id = id32; + tx_info->flags |= flag; + orig_tx_info = NXPWIFI_SKB_TXCB(orig_skb); + orig_tx_info->ack_frame_id = id32; + orig_tx_info->flags |= flag; + + if (flag == NXPWIFI_BUF_FLAG_ACTION_TX_STATUS && cookie) + orig_tx_info->cookie = *cookie; + + } else if (skb_shared(skb)) { + kfree_skb(orig_skb); + } else { + kfree_skb(skb); + skb = orig_skb; + } + } else { + /* couldn't clone -- lose tx status ... */ + skb = orig_skb; + } + + return skb; +} + +/* ndo_start_xmit: fix headroom, fill TXCB, timestamp, enqueue. */ +static netdev_tx_t +nxpwifi_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct sk_buff *new_skb; + struct nxpwifi_txinfo *tx_info; + bool multicast; + + nxpwifi_dbg(priv->adapter, DATA, + "data: %lu BSS(%d-%d): Data <= kernel\n", + jiffies, priv->bss_type, priv->bss_num); + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &priv->adapter->work_flags)) { + kfree_skb(skb); + priv->stats.tx_dropped++; + return 0; + } + if (!skb->len || skb->len > ETH_FRAME_LEN) { + nxpwifi_dbg(priv->adapter, ERROR, + "Tx: bad skb len %d\n", skb->len); + kfree_skb(skb); + priv->stats.tx_dropped++; + return 0; + } + if (skb_headroom(skb) < NXPWIFI_MIN_DATA_HEADER_LEN) { + nxpwifi_dbg(priv->adapter, DATA, + "data: Tx: insufficient skb headroom %d\n", + skb_headroom(skb)); + /* Insufficient skb headroom - allocate a new skb */ + new_skb = + skb_realloc_headroom(skb, NXPWIFI_MIN_DATA_HEADER_LEN); + if (unlikely(!new_skb)) { + nxpwifi_dbg(priv->adapter, ERROR, + "Tx: cannot alloca new_skb\n"); + kfree_skb(skb); + priv->stats.tx_dropped++; + return 0; + } + kfree_skb(skb); + skb = new_skb; + nxpwifi_dbg(priv->adapter, INFO, + "info: new skb headroomd %d\n", + skb_headroom(skb)); + } + + tx_info = NXPWIFI_SKB_TXCB(skb); + memset(tx_info, 0, sizeof(*tx_info)); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + tx_info->pkt_len = skb->len; + + multicast = is_multicast_ether_addr(skb->data); + + if (unlikely(!multicast && sk_requests_wifi_status(skb->sk) && + priv->adapter->fw_api_ver == NXPWIFI_FW_V15)) + skb = nxpwifi_clone_skb_for_tx_status(priv, + skb, + NXPWIFI_BUF_FLAG_EAPOL_TX_STATUS, NULL); + + /* + * Record the current time the packet was queued; used to + * determine the amount of time the packet was queued in + * the driver before it was sent to the firmware. + * The delay is then sent along with the packet to the + * firmware for aggregate delay calculation for stats and + * MSDU lifetime expiry. + */ + __net_timestamp(skb); + + nxpwifi_queue_tx_pkt(priv, skb); + + return 0; +} + +int nxpwifi_set_mac_address(struct nxpwifi_private *priv, + struct net_device *dev, bool external, + u8 *new_mac) +{ + int ret; + u64 mac_addr, old_mac_addr; + + old_mac_addr = ether_addr_to_u64(priv->curr_addr); + + if (external) { + mac_addr = ether_addr_to_u64(new_mac); + } else { + /* Internal mac address change */ + if (priv->bss_type == NXPWIFI_BSS_TYPE_ANY) + return -EOPNOTSUPP; + + mac_addr = old_mac_addr; + + if (priv->adapter->priv[0] != priv) { + /* Set mac address based on bss_type/bss_num */ + mac_addr ^= BIT_ULL(priv->bss_type + 8); + mac_addr += priv->bss_num; + } + } + + u64_to_ether_addr(mac_addr, priv->curr_addr); + + /* Send request to firmware */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_MAC_ADDRESS, + HOST_ACT_GEN_SET, 0, NULL, true); + + if (ret) { + u64_to_ether_addr(old_mac_addr, priv->curr_addr); + nxpwifi_dbg(priv->adapter, ERROR, + "set mac address failed: ret=%d\n", ret); + return ret; + } + + eth_hw_addr_set(dev, priv->curr_addr); + return 0; +} + +/* ndo_set_mac_address: set MAC via firmware. */ +static int +nxpwifi_ndo_set_mac_address(struct net_device *dev, void *addr) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct sockaddr *hw_addr = addr; + + return nxpwifi_set_mac_address(priv, dev, true, hw_addr->sa_data); +} + +/* ndo_set_rx_mode: promisc/allmulti or program multicast. */ +static void nxpwifi_set_multicast_list(struct net_device *dev) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_multicast_list mcast_list; + + if (dev->flags & IFF_PROMISC) { + mcast_list.mode = NXPWIFI_PROMISC_MODE; + } else if (dev->flags & IFF_ALLMULTI || + netdev_mc_count(dev) > NXPWIFI_MAX_MULTICAST_LIST_SIZE) { + mcast_list.mode = NXPWIFI_ALL_MULTI_MODE; + } else { + mcast_list.mode = NXPWIFI_MULTICAST_MODE; + mcast_list.num_multicast_addr = + nxpwifi_copy_mcast_addr(&mcast_list, dev); + } + nxpwifi_request_set_multicast_list(priv, &mcast_list); +} + +/* ndo_tx_timeout: account; reset card on threshold. */ +static void +nxpwifi_tx_timeout(struct net_device *dev, unsigned int txqueue) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + + priv->num_tx_timeout++; + priv->tx_timeout_cnt++; + nxpwifi_dbg(priv->adapter, ERROR, + "%lu : Tx timeout(#%d), bss_type-num = %d-%d\n", + jiffies, priv->tx_timeout_cnt, priv->bss_type, + priv->bss_num); + nxpwifi_set_trans_start(dev); + + if (priv->tx_timeout_cnt > TX_TIMEOUT_THRESHOLD && + priv->adapter->if_ops.card_reset) { + nxpwifi_dbg(priv->adapter, ERROR, + "tx_timeout_cnt exceeds threshold.\t" + "Triggering card reset!\n"); + priv->adapter->if_ops.card_reset(priv->adapter); + } +} + +void nxpwifi_upload_device_dump(struct nxpwifi_adapter *adapter) +{ + /* + * Dump all the memory data into single file, a userspace script will + * be used to split all the memory data to multiple files + */ + nxpwifi_dbg(adapter, MSG, + "== nxpwifi dump information to /sys/class/devcoredump start\n"); + dev_coredumpv(adapter->dev, adapter->devdump_data, adapter->devdump_len, + GFP_KERNEL); + nxpwifi_dbg(adapter, MSG, + "== nxpwifi dump information to /sys/class/devcoredump end\n"); + + /* + * Device dump data will be freed in device coredump release function + * after 5 min. Here reset adapter->devdump_data and ->devdump_len + * to avoid it been accidentally reused. + */ + adapter->devdump_data = NULL; + adapter->devdump_len = 0; +} +EXPORT_SYMBOL_GPL(nxpwifi_upload_device_dump); + +void nxpwifi_drv_info_dump(struct nxpwifi_adapter *adapter) +{ + char *p; + char drv_version[64]; + struct sdio_mmc_card *sdio_card; + struct nxpwifi_private *priv; + int i, idx; + struct netdev_queue *txq; + struct nxpwifi_debug_info *debug_info; + + nxpwifi_dbg(adapter, MSG, "===nxpwifi driverinfo dump start===\n"); + + p = adapter->devdump_data; + strscpy(p, "========Start dump driverinfo========\n", NXPWIFI_FW_DUMP_SIZE); + p += strlen("========Start dump driverinfo========\n"); + p += sprintf(p, "driver_name = "); + p += sprintf(p, "\"nxpwifi\"\n"); + + nxpwifi_drv_get_driver_version(adapter, drv_version, + sizeof(drv_version) - 1); + p += sprintf(p, "driver_version = %s\n", drv_version); + + p += sprintf(p, "tx_pending = %d\n", + atomic_read(&adapter->tx_pending)); + p += sprintf(p, "rx_pending = %d\n", + atomic_read(&adapter->rx_pending)); + + if (adapter->iface_type == NXPWIFI_SDIO) { + sdio_card = (struct sdio_mmc_card *)adapter->card; + p += sprintf(p, "\nmp_rd_bitmap=0x%x curr_rd_port=0x%x\n", + sdio_card->mp_rd_bitmap, sdio_card->curr_rd_port); + p += sprintf(p, "mp_wr_bitmap=0x%x curr_wr_port=0x%x\n", + sdio_card->mp_wr_bitmap, sdio_card->curr_wr_port); + } + + for (i = 0; i < adapter->priv_num; i++) { + if (!adapter->priv[i]->netdev) + continue; + priv = adapter->priv[i]; + p += sprintf(p, "\n[interface : \"%s\"]\n", + priv->netdev->name); + p += sprintf(p, "wmm_tx_pending[0] = %d\n", + atomic_read(&priv->wmm_tx_pending[0])); + p += sprintf(p, "wmm_tx_pending[1] = %d\n", + atomic_read(&priv->wmm_tx_pending[1])); + p += sprintf(p, "wmm_tx_pending[2] = %d\n", + atomic_read(&priv->wmm_tx_pending[2])); + p += sprintf(p, "wmm_tx_pending[3] = %d\n", + atomic_read(&priv->wmm_tx_pending[3])); + p += sprintf(p, "media_state=\"%s\"\n", !priv->media_connected ? + "Disconnected" : "Connected"); + p += sprintf(p, "carrier %s\n", (netif_carrier_ok(priv->netdev) + ? "on" : "off")); + for (idx = 0; idx < priv->netdev->num_tx_queues; idx++) { + txq = netdev_get_tx_queue(priv->netdev, idx); + p += sprintf(p, "tx queue %d:%s ", idx, + netif_tx_queue_stopped(txq) ? + "stopped" : "started"); + } + p += sprintf(p, "\n%s: num_tx_timeout = %d\n", + priv->netdev->name, priv->num_tx_timeout); + } + + if (adapter->iface_type == NXPWIFI_SDIO) { + p += sprintf(p, "\n=== %s register dump===\n", "SDIO"); + if (adapter->if_ops.reg_dump) + p += adapter->if_ops.reg_dump(adapter, p); + } + p += sprintf(p, "\n=== more debug information\n"); + debug_info = kzalloc_obj(*debug_info, GFP_KERNEL); + if (debug_info) { + for (i = 0; i < adapter->priv_num; i++) { + if (!adapter->priv[i]->netdev) + continue; + priv = adapter->priv[i]; + nxpwifi_get_debug_info(priv, debug_info); + p += nxpwifi_debug_info_to_buffer(priv, p, debug_info); + break; + } + kfree(debug_info); + } + + p += sprintf(p, "\n========End dump========\n"); + nxpwifi_dbg(adapter, MSG, "===nxpwifi driverinfo dump end===\n"); + adapter->devdump_len = p - (char *)adapter->devdump_data; +} +EXPORT_SYMBOL_GPL(nxpwifi_drv_info_dump); + +void nxpwifi_prepare_fw_dump_info(struct nxpwifi_adapter *adapter) +{ + u8 idx; + char *fw_dump_ptr; + u32 dump_len = 0; + + for (idx = 0; idx < adapter->num_mem_types; idx++) { + struct memory_type_mapping *entry = + &adapter->mem_type_mapping_tbl[idx]; + + if (entry->mem_ptr) { + dump_len += (strlen("========Start dump ") + + strlen(entry->mem_name) + + strlen("========\n") + + (entry->mem_size + 1) + + strlen("\n========End dump========\n")); + } + } + + if (dump_len + 1 + adapter->devdump_len > NXPWIFI_FW_DUMP_SIZE) { + /* Realloc in case buffer overflow */ + fw_dump_ptr = vzalloc(dump_len + 1 + adapter->devdump_len); + nxpwifi_dbg(adapter, MSG, "Realloc device dump data.\n"); + if (!fw_dump_ptr) { + vfree(adapter->devdump_data); + nxpwifi_dbg(adapter, ERROR, + "vzalloc devdump data failure!\n"); + return; + } + + memmove(fw_dump_ptr, adapter->devdump_data, + adapter->devdump_len); + vfree(adapter->devdump_data); + adapter->devdump_data = fw_dump_ptr; + } + + fw_dump_ptr = (char *)adapter->devdump_data + adapter->devdump_len; + + for (idx = 0; idx < adapter->num_mem_types; idx++) { + struct memory_type_mapping *entry = + &adapter->mem_type_mapping_tbl[idx]; + + if (entry->mem_ptr) { + fw_dump_ptr += sprintf(fw_dump_ptr, "========Start dump "); + fw_dump_ptr += sprintf(fw_dump_ptr, "%s", entry->mem_name); + fw_dump_ptr += sprintf(fw_dump_ptr, "========\n"); + memcpy(fw_dump_ptr, entry->mem_ptr, entry->mem_size); + fw_dump_ptr += entry->mem_size; + fw_dump_ptr += sprintf(fw_dump_ptr, "\n========End dump========\n"); + } + } + + adapter->devdump_len = fw_dump_ptr - (char *)adapter->devdump_data; + + for (idx = 0; idx < adapter->num_mem_types; idx++) { + struct memory_type_mapping *entry = + &adapter->mem_type_mapping_tbl[idx]; + + vfree(entry->mem_ptr); + entry->mem_ptr = NULL; + entry->mem_size = 0; + } +} +EXPORT_SYMBOL_GPL(nxpwifi_prepare_fw_dump_info); + +/* ndo_get_stats: return netdev stats. */ +static struct net_device_stats *nxpwifi_get_stats(struct net_device *dev) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + + return &priv->stats; +} + +static u16 +nxpwifi_netdev_select_wmm_queue(struct net_device *dev, struct sk_buff *skb, + struct net_device *sb_dev) +{ + skb->priority = cfg80211_classify8021d(skb, NULL); + return nxpwifi_1d_to_wmm_queue[skb->priority]; +} + +/* Network device handlers */ +static const struct net_device_ops nxpwifi_netdev_ops = { + .ndo_open = nxpwifi_open, + .ndo_stop = nxpwifi_close, + .ndo_start_xmit = nxpwifi_hard_start_xmit, + .ndo_set_mac_address = nxpwifi_ndo_set_mac_address, + .ndo_validate_addr = eth_validate_addr, + .ndo_tx_timeout = nxpwifi_tx_timeout, + .ndo_get_stats = nxpwifi_get_stats, + .ndo_set_rx_mode = nxpwifi_set_multicast_list, + .ndo_select_queue = nxpwifi_netdev_select_wmm_queue, +}; + +/* Init per-interface defaults: ops, addrs, mgmt IEs, stats. */ +void nxpwifi_init_priv_params(struct nxpwifi_private *priv, + struct net_device *dev) +{ + dev->netdev_ops = &nxpwifi_netdev_ops; + dev->needs_free_netdev = true; + /* Initialize private structure */ + priv->current_key_index = 0; + priv->media_connected = false; + memset(priv->mgmt_ie, 0, + sizeof(struct nxpwifi_ie) * MAX_MGMT_IE_INDEX); + priv->beacon_idx = NXPWIFI_AUTO_IDX_MASK; + priv->proberesp_idx = NXPWIFI_AUTO_IDX_MASK; + priv->assocresp_idx = NXPWIFI_AUTO_IDX_MASK; + priv->gen_idx = NXPWIFI_AUTO_IDX_MASK; + priv->num_tx_timeout = 0; + if (is_valid_ether_addr(dev->dev_addr)) + ether_addr_copy(priv->curr_addr, dev->dev_addr); + else + ether_addr_copy(priv->curr_addr, priv->adapter->perm_addr); + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA || + GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + priv->hist_data = kmalloc_obj(*priv->hist_data, GFP_KERNEL); + if (priv->hist_data) + nxpwifi_hist_data_reset(priv); + } +} + +/* Return true if any command is pending. */ +int nxpwifi_is_command_pending(struct nxpwifi_adapter *adapter) +{ + int is_cmd_pend_q_empty; + + spin_lock_bh(&adapter->cmd_pending_q_lock); + is_cmd_pend_q_empty = list_empty(&adapter->cmd_pending_q); + spin_unlock_bh(&adapter->cmd_pending_q_lock); + + return !is_cmd_pend_q_empty; +} + +/* Host MLME work: deliver RX; handle assoc/link-loss. */ +static void nxpwifi_host_mlme_work(struct wiphy *wiphy, struct wiphy_work *work) +{ + struct nxpwifi_adapter *adapter = + container_of(work, struct nxpwifi_adapter, host_mlme_work); + struct sk_buff *skb; + struct nxpwifi_rxinfo *rx_info; + struct nxpwifi_private *priv; + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags)) + return; + + while ((skb = skb_dequeue(&adapter->rx_mlme_q))) { + rx_info = NXPWIFI_SKB_RXCB(skb); + priv = adapter->priv[rx_info->bss_num]; + cfg80211_rx_mlme_mgmt(priv->netdev, + skb->data, + rx_info->pkt_len); + } + + /* Check for host mlme disconnection */ + if (adapter->host_mlme_link_lost) { + if (adapter->priv_link_lost) { + nxpwifi_reset_connect_state(adapter->priv_link_lost, + WLAN_REASON_DEAUTH_LEAVING, + true); + adapter->priv_link_lost = NULL; + } + adapter->host_mlme_link_lost = false; + } + + /* Check for host mlme Assoc Resp */ + if (adapter->assoc_resp_received) { + nxpwifi_process_assoc_resp(adapter); + adapter->assoc_resp_received = false; + } +} + +/* RX work: process RX queue. */ +static void nxpwifi_rx_work(struct work_struct *work) +{ + struct nxpwifi_adapter *adapter = + container_of(work, struct nxpwifi_adapter, rx_work); + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags)) + return; + nxpwifi_process_rx(adapter); +} + +/* Main work: run nxpwifi_main_process(). */ +static void nxpwifi_main_work(struct work_struct *work) +{ + struct nxpwifi_adapter *adapter = + container_of(work, struct nxpwifi_adapter, main_work); + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags)) + return; + nxpwifi_main_process(adapter); +} + +/* Teardown: disable IRQs, stop queues, shutdown, remove ifaces, unreg. */ +static void nxpwifi_uninit_sw(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + int i; + + /* + * We can no longer handle interrupts once we start doing the teardown + * below. + */ + if (adapter->if_ops.disable_int) + adapter->if_ops.disable_int(adapter); + + set_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags); + nxpwifi_terminate_workqueue(adapter); + adapter->int_status = 0; + + /* Stop data */ + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (priv->netdev) { + nxpwifi_stop_net_dev_queue(priv->netdev, adapter); + netif_carrier_off(priv->netdev); + netif_device_detach(priv->netdev); + } + } + + nxpwifi_dbg(adapter, CMD, "cmd: calling nxpwifi_shutdown_drv...\n"); + nxpwifi_shutdown_drv(adapter); + nxpwifi_dbg(adapter, CMD, "cmd: nxpwifi_shutdown_drv done\n"); + + if (atomic_read(&adapter->rx_pending) || + atomic_read(&adapter->tx_pending) || + atomic_read(&adapter->cmd_pending)) { + nxpwifi_dbg(adapter, ERROR, + "rx_pending=%d, tx_pending=%d,\t" + "cmd_pending=%d\n", + atomic_read(&adapter->rx_pending), + atomic_read(&adapter->tx_pending), + atomic_read(&adapter->cmd_pending)); + } + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + rtnl_lock(); + if (priv->netdev && + priv->wdev.iftype != NL80211_IFTYPE_UNSPECIFIED) { + /* + * Close the netdev now, because if we do it later, the + * netdev notifiers will need to acquire the wiphy lock + * again --> deadlock. + */ + dev_close(priv->wdev.netdev); + wiphy_lock(adapter->wiphy); + nxpwifi_del_virtual_intf(adapter->wiphy, &priv->wdev); + wiphy_unlock(adapter->wiphy); + } + rtnl_unlock(); + } + + wiphy_unregister(adapter->wiphy); + wiphy_free(adapter->wiphy); + adapter->wiphy = NULL; + + vfree(adapter->chan_stats); + nxpwifi_free_cmd_buffers(adapter); +} + +/* Shut down SW/FW and mark device down. */ +void nxpwifi_shutdown_sw(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + + if (!adapter) + return; + + wait_for_completion(adapter->fw_done); + /* Caller should ensure we aren't suspending while this happens */ + reinit_completion(adapter->fw_done); + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + nxpwifi_deauthenticate(priv, NULL); + + nxpwifi_init_shutdown_fw(priv, NXPWIFI_FUNC_SHUTDOWN); + + nxpwifi_uninit_sw(adapter); + adapter->is_up = false; +} +EXPORT_SYMBOL_GPL(nxpwifi_shutdown_sw); + +/* Re-init adapter SW and bring device up. */ +int +nxpwifi_reinit_sw(struct nxpwifi_adapter *adapter) +{ + int ret = 0; + + nxpwifi_init_lock_list(adapter); + if (adapter->if_ops.up_dev) + adapter->if_ops.up_dev(adapter); + + adapter->hw_status = NXPWIFI_HW_STATUS_INITIALIZING; + clear_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags); + clear_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags); + adapter->hs_activated = false; + clear_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags); + init_waitqueue_head(&adapter->hs_activate_wait_q); + init_waitqueue_head(&adapter->cmd_wait_q.wait); + adapter->cmd_wait_q.status = 0; + adapter->scan_wait_q_woken = false; + + if (num_possible_cpus() > 1) + adapter->rx_work_enabled = true; + + adapter->workqueue = + alloc_workqueue("NXPWIFI_WORK_QUEUE", + WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, 0); + if (!adapter->workqueue) { + ret = -ENOMEM; + goto err_kmalloc; + } + + INIT_WORK(&adapter->main_work, nxpwifi_main_work); + + if (adapter->rx_work_enabled) { + adapter->rx_workqueue = alloc_workqueue("NXPWIFI_RX_WORK_QUEUE", + WQ_HIGHPRI | + WQ_MEM_RECLAIM | + WQ_UNBOUND, 0); + if (!adapter->rx_workqueue) { + ret = -ENOMEM; + goto err_kmalloc; + } + INIT_WORK(&adapter->rx_work, nxpwifi_rx_work); + } + + wiphy_work_init(&adapter->host_mlme_work, nxpwifi_host_mlme_work); + + /* + * Register the device. Fill up the private data structure with + * relevant information from the card. Some code extracted from + * nxpwifi_register_dev() + */ + nxpwifi_dbg(adapter, INFO, "%s, nxpwifi_init_hw_fw()...\n", __func__); + + ret = nxpwifi_init_hw_fw(adapter, false); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "%s: firmware init failed\n", __func__); + goto err_init_fw; + } + + /* _nxpwifi_fw_dpc() does its own cleanup */ + ret = _nxpwifi_fw_dpc(adapter->firmware, adapter); + if (ret) { + pr_err("Failed to bring up adapter: %d\n", ret); + return ret; + } + nxpwifi_dbg(adapter, INFO, "%s, successful\n", __func__); + + return ret; + +err_init_fw: + nxpwifi_dbg(adapter, ERROR, "info: %s: unregister device\n", __func__); + if (adapter->if_ops.unregister_dev) + adapter->if_ops.unregister_dev(adapter); + +err_kmalloc: + set_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags); + nxpwifi_terminate_workqueue(adapter); + if (adapter->hw_status == NXPWIFI_HW_STATUS_READY) { + nxpwifi_dbg(adapter, ERROR, + "info: %s: shutdown nxpwifi\n", __func__); + nxpwifi_shutdown_drv(adapter); + nxpwifi_free_cmd_buffers(adapter); + } + + complete_all(adapter->fw_done); + nxpwifi_dbg(adapter, INFO, "%s, error\n", __func__); + + return ret; +} +EXPORT_SYMBOL_GPL(nxpwifi_reinit_sw); + +/* Add card: register adapter, workqueues, device; request FW (async). */ +int +nxpwifi_add_card(void *card, struct completion *fw_done, + struct nxpwifi_if_ops *if_ops, u8 iface_type, + struct device *dev) +{ + struct nxpwifi_adapter *adapter; + int ret = 0; + + adapter = nxpwifi_register(card, dev, if_ops); + if (IS_ERR(adapter)) { + ret = PTR_ERR(adapter); + pr_err("%s: adapter register failed %d\n", __func__, ret); + goto err_init_sw; + } + + adapter->iface_type = iface_type; + adapter->fw_done = fw_done; + + adapter->hw_status = NXPWIFI_HW_STATUS_INITIALIZING; + clear_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags); + clear_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags); + adapter->hs_activated = false; + init_waitqueue_head(&adapter->hs_activate_wait_q); + init_waitqueue_head(&adapter->cmd_wait_q.wait); + adapter->cmd_wait_q.status = 0; + adapter->scan_wait_q_woken = false; + + if (num_possible_cpus() > 1) + adapter->rx_work_enabled = true; + + adapter->workqueue = + alloc_workqueue("NXPWIFI_WORK_QUEUE", + WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, 0); + if (!adapter->workqueue) { + ret = -ENOMEM; + goto err_kmalloc; + } + + INIT_WORK(&adapter->main_work, nxpwifi_main_work); + + if (adapter->rx_work_enabled) { + adapter->rx_workqueue = alloc_workqueue("NXPWIFI_RX_WORK_QUEUE", + WQ_HIGHPRI | + WQ_MEM_RECLAIM | + WQ_UNBOUND, 0); + if (!adapter->rx_workqueue) { + ret = -ENOMEM; + goto err_kmalloc; + } + + INIT_WORK(&adapter->rx_work, nxpwifi_rx_work); + } + + wiphy_work_init(&adapter->host_mlme_work, nxpwifi_host_mlme_work); + + /* + * Register the device. Fill up the private data structure with relevant + * information from the card. + */ + ret = adapter->if_ops.register_dev(adapter); + if (ret) { + pr_err("%s: failed to register nxpwifi device\n", __func__); + goto err_registerdev; + } + + ret = nxpwifi_init_hw_fw(adapter, true); + if (ret) { + pr_err("%s: firmware init failed\n", __func__); + goto err_init_fw; + } + + return ret; + +err_init_fw: + pr_debug("info: %s: unregister device\n", __func__); + if (adapter->if_ops.unregister_dev) + adapter->if_ops.unregister_dev(adapter); +err_registerdev: + set_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags); + +err_kmalloc: + nxpwifi_terminate_workqueue(adapter); + + if (adapter->hw_status == NXPWIFI_HW_STATUS_READY) { + pr_debug("info: %s: shutdown nxpwifi\n", __func__); + nxpwifi_shutdown_drv(adapter); + nxpwifi_free_cmd_buffers(adapter); + } + + nxpwifi_free_adapter(adapter); + +err_init_sw: + + return ret; +} +EXPORT_SYMBOL_GPL(nxpwifi_add_card); + +/* Remove card: teardown SW, unregister device, free adapter. */ +void nxpwifi_remove_card(struct nxpwifi_adapter *adapter) +{ + if (!adapter) + return; + + if (adapter->is_up) + nxpwifi_uninit_sw(adapter); + + /* Unregister device */ + nxpwifi_dbg(adapter, INFO, + "info: unregister device\n"); + if (adapter->if_ops.unregister_dev) + adapter->if_ops.unregister_dev(adapter); + /* Free adapter structure */ + nxpwifi_dbg(adapter, INFO, + "info: free adapter\n"); + nxpwifi_free_adapter(adapter); +} +EXPORT_SYMBOL_GPL(nxpwifi_remove_card); + +void _nxpwifi_dbg(const struct nxpwifi_adapter *adapter, int mask, + const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + if (!(adapter->debug_mask & mask)) + return; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + if (adapter->dev) + dev_info(adapter->dev, "%pV", &vaf); + else + pr_info("%pV", &vaf); + + va_end(args); +} +EXPORT_SYMBOL_GPL(_nxpwifi_dbg); + +/* Module init: init debugfs if enabled. */ +static int +nxpwifi_init_module(void) +{ +#ifdef CONFIG_DEBUG_FS + nxpwifi_debugfs_init(); +#endif + return 0; +} + +/* Module exit: remove debugfs if enabled. */ +static void +nxpwifi_cleanup_module(void) +{ +#ifdef CONFIG_DEBUG_FS + nxpwifi_debugfs_remove(); +#endif +} + +module_init(nxpwifi_init_module); +module_exit(nxpwifi_cleanup_module); + +MODULE_AUTHOR("NXP International Ltd."); +MODULE_DESCRIPTION("NXP WiFi Driver version " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/nxp/nxpwifi/main.h b/drivers/net/wireless/nxp/nxpwifi/main.h new file mode 100644 index 000000000000..4abf80771be2 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/main.h @@ -0,0 +1,1429 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * nxpwifi: main data structures and prototypes + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_MAIN_H_ +#define _NXPWIFI_MAIN_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "sdio.h" + +extern char nxpwifi_driver_version[]; + +struct nxpwifi_adapter; +struct nxpwifi_private; + +/* command type */ +enum { + NXPWIFI_ASYNC_CMD, + NXPWIFI_SYNC_CMD +}; + +#define NXPWIFI_MAX_AP 64 + +#define NXPWIFI_MAX_PKTS_TXQ 16 + +#define NXPWIFI_DEFAULT_WATCHDOG_TIMEOUT (5 * HZ) + +#define NXPWIFI_TIMER_10S 10000 +#define NXPWIFI_TIMER_1S 1000 + +#define MAX_TX_PENDING 400 +#define LOW_TX_PENDING 380 + +#define HIGH_RX_PENDING 50 +#define LOW_RX_PENDING 20 + +#define NXPWIFI_UPLD_SIZE (2312) + +#define MAX_EVENT_SIZE 2048 + +#define NXPWIFI_FW_DUMP_SIZE (2 * 1024 * 1024) + +#define ARP_FILTER_MAX_BUF_SIZE 68 + +#define NXPWIFI_KEY_BUFFER_SIZE 16 +#define NXPWIFI_DEFAULT_LISTEN_INTERVAL 10 +#define NXPWIFI_MAX_REGION_CODE 9 + +#define DEFAULT_BCN_AVG_FACTOR 8 +#define DEFAULT_DATA_AVG_FACTOR 8 + +#define FIRST_VALID_CHANNEL 0xff + +#define DEFAULT_BCN_MISS_TIMEOUT 5 + +#define MAX_SCAN_BEACON_BUFFER 8000 + +#define SCAN_BEACON_ENTRY_PAD 6 + +#define NXPWIFI_PASSIVE_SCAN_CHAN_TIME 110 +#define NXPWIFI_ACTIVE_SCAN_CHAN_TIME 40 +#define NXPWIFI_SPECIFIC_SCAN_CHAN_TIME 40 +#define NXPWIFI_DEF_SCAN_CHAN_GAP_TIME 50 + +#define SCAN_RSSI(RSSI) (0x100 - ((u8)(RSSI))) + +#define NXPWIFI_MAX_TOTAL_SCAN_TIME (NXPWIFI_TIMER_10S - NXPWIFI_TIMER_1S) + +#define WPA_GTK_OUI_OFFSET 2 +#define RSN_GTK_OUI_OFFSET 2 + +#define NXPWIFI_OUI_NOT_PRESENT 0 +#define NXPWIFI_OUI_PRESENT 1 + +#define PKT_TYPE_MGMT 0xE5 +#define PKT_TYPE_802DOT11 0x05 +/* check if any data / resp / event is received from card */ +#define IS_CARD_RX_RCVD(adapter) ({ \ + typeof(adapter) (_adapter) = adapter; \ + ((_adapter)->cmd_resp_received || \ + (_adapter)->event_received || \ + (_adapter)->data_received); \ + }) + +#define NXPWIFI_TYPE_DATA 0 +#define NXPWIFI_TYPE_CMD 1 +#define NXPWIFI_TYPE_EVENT 3 +#define NXPWIFI_TYPE_VDLL 4 +#define NXPWIFI_TYPE_AGGR_DATA 10 + +#define MAX_BITMAP_RATES_SIZE 18 + +#define MAX_CHANNEL_BAND_BG 14 +#define MAX_CHANNEL_BAND_A 165 + +#define MAX_FREQUENCY_BAND_BG 2484 + +#define NXPWIFI_EVENT_HEADER_LEN 4 +#define NXPWIFI_UAP_EVENT_EXTRA_HEADER 2 + +#define NXPWIFI_TYPE_LEN 4 +#define NXPWIFI_USB_TYPE_CMD 0xF00DFACE +#define NXPWIFI_USB_TYPE_DATA 0xBEADC0DE +#define NXPWIFI_USB_TYPE_EVENT 0xBEEFFACE + +/* tx_timeout threshold to trigger card reset */ +#define TX_TIMEOUT_THRESHOLD 6 + +#define NXPWIFI_DRV_INFO_SIZE_MAX 0x40000 + +/* address alignment helper */ +#define NXPWIFI_ALIGN_ADDR(p, a) ({ \ + typeof(a) (_a) = a; \ + (((long)(p) + (_a) - 1) & ~((_a) - 1)); \ + }) + +#define NXPWIFI_MAC_LOCAL_ADMIN_BIT 41 + +/* bit helper */ +#define MBIT(x) (((u32)1) << (x)) + +/* enum nxpwifi_debug_level - nxp wifi debug level */ +enum NXPWIFI_DEBUG_LEVEL { + NXPWIFI_DBG_MSG = 0x00000001, + NXPWIFI_DBG_FATAL = 0x00000002, + NXPWIFI_DBG_ERROR = 0x00000004, + NXPWIFI_DBG_DATA = 0x00000008, + NXPWIFI_DBG_CMD = 0x00000010, + NXPWIFI_DBG_EVENT = 0x00000020, + NXPWIFI_DBG_INTR = 0x00000040, + NXPWIFI_DBG_IOCTL = 0x00000080, + NXPWIFI_DBG_MPA_D = 0x00008000, + NXPWIFI_DBG_DAT_D = 0x00010000, + NXPWIFI_DBG_CMD_D = 0x00020000, + NXPWIFI_DBG_EVT_D = 0x00040000, + NXPWIFI_DBG_FW_D = 0x00080000, + NXPWIFI_DBG_IF_D = 0x00100000, + NXPWIFI_DBG_ENTRY = 0x10000000, + NXPWIFI_DBG_WARN = 0x20000000, + NXPWIFI_DBG_INFO = 0x40000000, + NXPWIFI_DBG_DUMP = 0x80000000, + NXPWIFI_DBG_ANY = 0xffffffff +}; + +#define NXPWIFI_DEFAULT_DEBUG_MASK (NXPWIFI_DBG_MSG | \ + NXPWIFI_DBG_FATAL | \ + NXPWIFI_DBG_ERROR) + +__printf(3, 4) +void _nxpwifi_dbg(const struct nxpwifi_adapter *adapter, int mask, + const char *fmt, ...); +#define nxpwifi_dbg(adapter, mask, fmt, ...) \ + _nxpwifi_dbg(adapter, NXPWIFI_DBG_##mask, fmt, ##__VA_ARGS__) + +#define DEBUG_DUMP_DATA_MAX_LEN 128 +#define nxpwifi_dbg_dump(adapter, dbg_mask, str, buf, len) \ +do { \ + if ((adapter)->debug_mask & NXPWIFI_DBG_##dbg_mask) \ + print_hex_dump(KERN_DEBUG, str, \ + DUMP_PREFIX_OFFSET, 16, 1, \ + buf, len, false); \ +} while (0) + +/* Min BGSCAN interval 15 second */ +#define NXPWIFI_BGSCAN_INTERVAL 15000 +/* bgscan interval (ms) and default repeat count */ +#define NXPWIFI_BGSCAN_REPEAT_COUNT 6 + +struct nxpwifi_dbg { + u32 num_cmd_host_to_card_failure; + u32 num_cmd_sleep_cfm_host_to_card_failure; + u32 num_tx_host_to_card_failure; + u32 num_event_deauth; + u32 num_event_disassoc; + u32 num_event_link_lost; + u32 num_cmd_deauth; + u32 num_cmd_assoc_success; + u32 num_cmd_assoc_failure; + u32 num_tx_timeout; + u16 timeout_cmd_id; + u16 timeout_cmd_act; + u16 last_cmd_id[DBG_CMD_NUM]; + u16 last_cmd_act[DBG_CMD_NUM]; + u16 last_cmd_index; + u16 last_cmd_resp_id[DBG_CMD_NUM]; + u16 last_cmd_resp_index; + u16 last_event[DBG_CMD_NUM]; + u16 last_event_index; + u32 last_mp_wr_bitmap[NXPWIFI_DBG_SDIO_MP_NUM]; + u32 last_mp_wr_ports[NXPWIFI_DBG_SDIO_MP_NUM]; + u32 last_mp_wr_len[NXPWIFI_DBG_SDIO_MP_NUM]; + u32 last_mp_curr_wr_port[NXPWIFI_DBG_SDIO_MP_NUM]; + u8 last_sdio_mp_index; +}; + +enum NXPWIFI_HARDWARE_STATUS { + NXPWIFI_HW_STATUS_READY, + NXPWIFI_HW_STATUS_INITIALIZING, + NXPWIFI_HW_STATUS_RESET, + NXPWIFI_HW_STATUS_NOT_READY +}; + +enum NXPWIFI_802_11_POWER_MODE { + NXPWIFI_802_11_POWER_MODE_CAM, + NXPWIFI_802_11_POWER_MODE_PSP +}; + +struct nxpwifi_tx_param { + u32 next_pkt_len; +}; + +enum NXPWIFI_PS_STATE { + PS_STATE_AWAKE, + PS_STATE_PRE_SLEEP, + PS_STATE_SLEEP_CFM, + PS_STATE_SLEEP +}; + +enum nxpwifi_iface_type { + NXPWIFI_SDIO +}; + +struct nxpwifi_add_ba_param { + u32 tx_win_size; + u32 rx_win_size; + u32 timeout; + u8 tx_amsdu; + u8 rx_amsdu; +}; + +struct nxpwifi_tx_aggr { + u8 ampdu_user; + u8 ampdu_ap; + u8 amsdu; +}; + +enum nxpwifi_ba_status { + BA_SETUP_NONE = 0, + BA_SETUP_INPROGRESS, + BA_SETUP_COMPLETE +}; + +struct nxpwifi_ra_list_tbl { + struct list_head list; + struct sk_buff_head skb_head; + u8 ra[ETH_ALEN]; + u32 is_11n_enabled; + u16 max_amsdu; + u16 ba_pkt_count; + u8 ba_packet_thr; + enum nxpwifi_ba_status ba_status; + u8 amsdu_in_ampdu; + u16 total_pkt_count; + bool tx_paused; +}; + +struct nxpwifi_tid_tbl { + struct list_head ra_list; +}; + +#define WMM_HIGHEST_PRIORITY 7 +#define HIGH_PRIO_TID 7 +#define LOW_PRIO_TID 0 +#define NO_PKT_PRIO_TID -1 +#define NXPWIFI_WMM_DRV_DELAY_MAX 510 + +struct nxpwifi_wmm_desc { + struct nxpwifi_tid_tbl tid_tbl_ptr[MAX_NUM_TID]; + u32 packets_out[MAX_NUM_TID]; + u32 pkts_paused[MAX_NUM_TID]; + /* protects ra_list */ + spinlock_t ra_list_spinlock; + struct nxpwifi_wmm_ac_status ac_status[IEEE80211_NUM_ACS]; + enum nxpwifi_wmm_ac_e ac_down_graded_vals[IEEE80211_NUM_ACS]; + u32 drv_pkt_delay_max; + u8 queue_priority[IEEE80211_NUM_ACS]; + u32 user_pri_pkt_tx_ctrl[WMM_HIGHEST_PRIORITY + 1]; /* UP: 0 to 7 */ + /* number of queued TX packets */ + atomic_t tx_pkts_queued; + /* highest priority currently queued */ + atomic_t highest_queued_prio; +}; + +struct nxpwifi_802_11_security { + u8 wpa_enabled; + u8 wpa2_enabled; + u8 wep_enabled; + u32 authentication_mode; + u8 is_authtype_auto; + u32 encryption_mode; +}; + +struct ieee_types_vendor_specific { + struct ieee80211_vendor_ie vend_hdr; + u8 data[IEEE_MAX_IE_SIZE - sizeof(struct ieee80211_vendor_ie)]; +} __packed; + +struct nxpwifi_bssdescriptor { + u8 mac_address[ETH_ALEN]; + struct cfg80211_ssid ssid; + u32 privacy; + s32 rssi; + u32 channel; + u32 freq; + u16 beacon_period; + u8 erp_flags; + u32 bss_mode; + u8 supported_rates[NXPWIFI_SUPPORTED_RATES]; + u8 data_rates[NXPWIFI_SUPPORTED_RATES]; + u16 bss_band; + u64 fw_tsf; + u64 timestamp; + union ieee_types_phy_param_set phy_param_set; + struct ieee_types_cf_param_set cf_param_set; + u16 cap_info_bitmap; + struct ieee80211_wmm_param_ie wmm_ie; + u8 disable_11n; + struct ieee80211_ht_cap *bcn_ht_cap; + u16 ht_cap_offset; + struct ieee80211_ht_operation *bcn_ht_oper; + u16 ht_info_offset; + u8 *bcn_bss_co_2040; + u16 bss_co_2040_offset; + u8 *bcn_ext_cap; + u16 ext_cap_offset; + struct ieee80211_vht_cap *bcn_vht_cap; + u16 vht_cap_offset; + struct ieee80211_vht_operation *bcn_vht_oper; + u16 vht_info_offset; + struct ieee_types_oper_mode_ntf *oper_mode; + u16 oper_mode_offset; + u8 disable_11ac; + struct ieee80211_he_cap_elem *bcn_he_cap; + u16 he_cap_offset; + struct ieee80211_he_operation *bcn_he_oper; + u16 he_info_offset; + u8 disable_11ax; + struct ieee_types_vendor_specific *bcn_wpa_ie; + u16 wpa_offset; + struct element *bcn_rsn_ie; + u16 rsn_offset; + struct element *bcn_rsnx_ie; + u16 rsnx_offset; + u8 *beacon_buf; + u32 beacon_buf_size; + u8 sensed_11h; + u8 local_constraint; + u8 chan_sw_ie_present; +}; + +struct nxpwifi_current_bss_params { + struct nxpwifi_bssdescriptor bss_descriptor; + bool wmm_enabled; + bool wmm_uapsd_enabled; + u8 band; + u32 num_of_rates; + u8 data_rates[NXPWIFI_SUPPORTED_RATES]; +}; + +struct nxpwifi_sleep_period { + u16 period; + u16 reserved; +}; + +struct nxpwifi_wep_key { + u32 length; + u32 key_index; + u32 key_length; + u8 key_material[NXPWIFI_KEY_BUFFER_SIZE]; +}; + +#define MAX_REGION_CHANNEL_NUM 2 + +struct nxpwifi_chan_freq_power { + u16 channel; + u32 freq; + u16 max_tx_power; + u8 unsupported; +}; + +enum state_11d_t { + DISABLE_11D = 0, + ENABLE_11D = 1, +}; + +#define NXPWIFI_MAX_TRIPLET_802_11D 83 + +struct nxpwifi_802_11d_domain_reg { + u8 dfs_region; + u8 country_code[IEEE80211_COUNTRY_STRING_LEN]; + u8 no_of_triplet; + struct ieee80211_country_ie_triplet + triplet[NXPWIFI_MAX_TRIPLET_802_11D]; +}; + +struct nxpwifi_vendor_spec_cfg_ie { + u16 mask; + u16 flag; + u8 ie[NXPWIFI_MAX_VSIE_LEN]; +}; + +struct wps { + u8 session_enable; +}; + +struct nxpwifi_roc_cfg { + u64 cookie; + struct ieee80211_channel chan; +}; + +enum nxpwifi_iface_work_flags { + NXPWIFI_IFACE_WORK_DEVICE_DUMP, + NXPWIFI_IFACE_WORK_CARD_RESET, +}; + +enum nxpwifi_adapter_work_flags { + NXPWIFI_SURPRISE_REMOVED, + NXPWIFI_IS_CMD_TIMEDOUT, + NXPWIFI_IS_SUSPENDED, + NXPWIFI_IS_HS_CONFIGURED, + NXPWIFI_IS_HS_ENABLING, + NXPWIFI_IS_REQUESTING_FW_VEREXT, +}; + +struct nxpwifi_band_config { + u8 chan_band:2; + u8 chan_width:2; + u8 chan2_offset:2; + u8 scan_mode:2; +} __packed; + +struct nxpwifi_channel_band { + struct nxpwifi_band_config band_config; + u8 channel; +}; + +struct nxpwifi_private { + struct nxpwifi_adapter *adapter; + u8 bss_type; + u8 bss_role; + u8 bss_priority; + u8 bss_num; + u8 bss_started; + u8 auth_flag; + u16 auth_alg; + u8 frame_type; + u8 curr_addr[ETH_ALEN]; + u8 media_connected; + u8 port_open; + u8 usb_port; + u32 num_tx_timeout; + /* track consecutive timeout */ + u8 tx_timeout_cnt; + struct net_device *netdev; + struct net_device_stats stats; + u32 curr_pkt_filter; + u32 bss_mode; + u32 pkt_tx_ctrl; + u16 tx_power_level; + u8 max_tx_power_level; + u8 min_tx_power_level; + u32 tx_ant; + u32 rx_ant; + u8 tx_rate; + u8 tx_htinfo; + u8 rxpd_htinfo; + u8 rxpd_rate; + u16 rate_bitmap; + u16 bitmap_rates[MAX_BITMAP_RATES_SIZE]; + u32 data_rate; + u8 is_data_rate_auto; + u16 bcn_avg_factor; + u16 data_avg_factor; + s16 data_rssi_last; + s16 data_nf_last; + s16 data_rssi_avg; + s16 data_nf_avg; + s16 bcn_rssi_last; + s16 bcn_nf_last; + s16 bcn_rssi_avg; + s16 bcn_nf_avg; + struct nxpwifi_bssdescriptor *attempted_bss_desc; + struct cfg80211_ssid prev_ssid; + u8 prev_bssid[ETH_ALEN]; + struct nxpwifi_current_bss_params curr_bss_params; + u16 beacon_period; + u8 dtim_period; + u16 listen_interval; + u16 atim_window; + struct nxpwifi_802_11_security sec_info; + struct nxpwifi_wep_key wep_key[NUM_WEP_KEYS]; + u16 wep_key_curr_index; + u8 wpa_ie[256]; + u16 wpa_ie_len; + u8 wpa_is_gtk_set; + struct host_cmd_ds_802_11_key_material aes_key; + u8 *wps_ie; + u16 wps_ie_len; + u8 wmm_required; + bool wmm_enabled; + u8 wmm_qosinfo; + struct nxpwifi_wmm_desc wmm; + atomic_t wmm_tx_pending[IEEE80211_NUM_ACS]; + struct list_head sta_list; + /* spin lock for associated station list */ + spinlock_t sta_list_spinlock; + struct list_head tx_ba_stream_tbl_ptr[MAX_NUM_TID]; + /* spin lock for tx_ba_stream_tbl_ptr queue */ + struct spinlock tx_ba_stream_tbl_lock[MAX_NUM_TID]; + struct nxpwifi_tx_aggr aggr_prio_tbl[MAX_NUM_TID]; + struct nxpwifi_add_ba_param add_ba_param; + u16 rx_seq[MAX_NUM_TID]; + u8 tos_to_tid_inv[MAX_NUM_TID]; + struct list_head rx_reorder_tbl_ptr[MAX_NUM_TID]; + /* spin lock for rx_reorder_tbl_ptr queue */ + struct spinlock rx_reorder_tbl_lock[MAX_NUM_TID]; +#define NXPWIFI_ASSOC_RSP_BUF_SIZE 500 + u8 assoc_rsp_buf[NXPWIFI_ASSOC_RSP_BUF_SIZE]; + u32 assoc_rsp_size; + struct cfg80211_bss *req_bss; + +#define NXPWIFI_GENIE_BUF_SIZE 256 + u8 gen_ie_buf[NXPWIFI_GENIE_BUF_SIZE]; + u8 gen_ie_buf_len; + + struct nxpwifi_vendor_spec_cfg_ie vs_ie[NXPWIFI_MAX_VSIE_NUM]; + +#define NXPWIFI_ASSOC_TLV_BUF_SIZE 256 + u8 assoc_tlv_buf[NXPWIFI_ASSOC_TLV_BUF_SIZE]; + u8 assoc_tlv_buf_len; + + u8 *curr_bcn_buf; + u32 curr_bcn_size; + /* spin lock for beacon buffer */ + spinlock_t curr_bcn_buf_lock; + struct wireless_dev wdev; + struct nxpwifi_chan_freq_power cfp; + u32 versionstrsel; + char version_str[NXPWIFI_VERSION_STR_LENGTH]; +#ifdef CONFIG_DEBUG_FS + struct dentry *dfs_dev_dir; +#endif + u16 current_key_index; + struct cfg80211_scan_request *scan_request; + u8 cfg_bssid[6]; + struct wps wps; + u8 scan_block; + s32 cqm_rssi_thold; + u32 cqm_rssi_hyst; + u8 subsc_evt_rssi_state; + struct nxpwifi_ds_misc_subsc_evt async_subsc_evt_storage; + struct nxpwifi_ie mgmt_ie[MAX_MGMT_IE_INDEX]; + u16 beacon_idx; + u16 proberesp_idx; + u16 assocresp_idx; + u16 gen_idx; + u8 ap_11n_enabled; + u8 ap_11ac_enabled; + u8 ap_11ax_enabled; + u16 config_bands; + /* 11AX */ + u8 user_he_cap_len; + u8 user_he_cap[HE_CAP_MAX_SIZE]; + u8 user_2g_he_cap_len; + u8 user_2g_he_cap[HE_CAP_MAX_SIZE]; + bool host_mlme_reg; + u32 mgmt_frame_mask; + struct nxpwifi_roc_cfg roc_cfg; + bool scan_aborting; + u8 sched_scanning; + u8 csa_chan; + unsigned long csa_expire_time; + u8 del_list_idx; + bool hs2_enabled; + struct nxpwifi_uap_bss_param bss_cfg; + struct cfg80211_chan_def bss_chandef; + struct station_parameters *sta_params; + struct xarray ack_status_frames; + /* spin lock for ack status */ + spinlock_t ack_status_lock; + /** rx histogram data */ + struct nxpwifi_histogram_data *hist_data; + struct cfg80211_chan_def dfs_chandef; + struct wiphy_work reset_conn_state_work; + struct wiphy_delayed_work dfs_cac_work; + struct wiphy_delayed_work dfs_chan_sw_work; + bool uap_stop_tx; + struct cfg80211_ap_update ap_update_info; + struct nxpwifi_11h_intf_state state_11h; + struct nxpwifi_ds_mem_rw mem_rw; + struct sk_buff_head bypass_txq; + struct nxpwifi_user_scan_chan hidden_chan[NXPWIFI_USER_SCAN_CHAN_MAX]; + u8 assoc_resp_ht_param; + bool ht_param_present; + u16 last_deauth_reason; +}; + +struct nxpwifi_tx_ba_stream_tbl { + struct list_head list; + struct rcu_head rcu; + int tid; + u8 ra[ETH_ALEN]; + enum nxpwifi_ba_status ba_status; + u8 amsdu; +}; + +struct nxpwifi_rx_reorder_tbl; + +struct reorder_tmr_cnxt { + struct timer_list timer; + struct nxpwifi_rx_reorder_tbl *ptr; + struct nxpwifi_private *priv; + u8 timer_is_set; +}; + +struct nxpwifi_rx_reorder_tbl { + struct list_head list; + struct list_head tmp_list; + struct rcu_head rcu; + int tid; + u8 ta[ETH_ALEN]; + int init_win; + int start_win; + int win_size; + void **rx_reorder_ptr; + struct reorder_tmr_cnxt timer_context; + u8 amsdu; + u8 flags; +}; + +struct nxpwifi_bss_prio_node { + struct list_head list; + struct nxpwifi_private *priv; +}; + +struct nxpwifi_bss_prio_tbl { + struct list_head bss_prio_head; + spinlock_t bss_prio_lock; /* protects BSS priority */ + struct nxpwifi_bss_prio_node *bss_prio_cur; +}; + +struct cmd_ctrl_node { + struct list_head list; + struct nxpwifi_private *priv; + u32 cmd_no; + u32 cmd_flag; + struct sk_buff *cmd_skb; + struct sk_buff *resp_skb; + void *data_buf; + u32 wait_q_enabled; + struct sk_buff *skb; + u8 *condition; + u8 cmd_wait_q_woken; + int (*cmd_resp)(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf); +}; + +struct nxpwifi_bss_priv { + u16 band; + u64 fw_tsf; +}; + +struct nxpwifi_station_stats { + u64 last_rx; + s8 rssi; + u64 rx_bytes; + u64 tx_bytes; + u32 rx_packets; + u32 tx_packets; + u32 tx_failed; + u8 last_tx_rate; + u8 last_tx_htinfo; +}; + +/*AP - side structure tracking associated STA info */ +struct nxpwifi_sta_node { + struct list_head list; + struct rcu_head rcu; + u8 mac_addr[ETH_ALEN]; + u8 is_wmm_enabled; + u8 is_11n_enabled; + u8 is_11ac_enabled; + u8 is_11ax_enabled; + u8 ampdu_sta[MAX_NUM_TID]; + u16 rx_seq[MAX_NUM_TID]; + u16 max_amsdu; + struct nxpwifi_station_stats stats; + u8 tx_pause; +}; + +#define NXPWIFI_TYPE_AGGR_DATA_V2 11 +#define NXPWIFI_BUS_AGGR_MODE_LEN_V2 (2) +#define NXPWIFI_BUS_AGGR_MAX_LEN 16000 +#define NXPWIFI_BUS_AGGR_MAX_NUM 10 +struct bus_aggr_params { + u16 enable; + u16 mode; + u16 tx_aggr_max_size; + u16 tx_aggr_max_num; + u16 tx_aggr_align; +}; + +struct vdll_dnld_ctrl { + u8 *pending_block; + u16 pending_block_len; + u8 *vdll_mem; + u32 vdll_len; + struct sk_buff *skb; +}; + +struct nxpwifi_if_ops { + int (*init_if)(struct nxpwifi_adapter *adapter); + void (*cleanup_if)(struct nxpwifi_adapter *adapter); + int (*check_fw_status)(struct nxpwifi_adapter *adapter, u32 poll_num); + int (*check_winner_status)(struct nxpwifi_adapter *adapter); + int (*prog_fw)(struct nxpwifi_adapter *adapter, + struct nxpwifi_fw_image *fw); + int (*register_dev)(struct nxpwifi_adapter *adapter); + void (*unregister_dev)(struct nxpwifi_adapter *adapter); + int (*enable_int)(struct nxpwifi_adapter *adapter); + void (*disable_int)(struct nxpwifi_adapter *adapter); + int (*process_int_status)(struct nxpwifi_adapter *adapter, u8 istat); + int (*host_to_card)(struct nxpwifi_adapter *adapter, u8 type, + struct sk_buff *skb, + struct nxpwifi_tx_param *tx_param); + int (*wakeup)(struct nxpwifi_adapter *adapter); + int (*wakeup_complete)(struct nxpwifi_adapter *adapter); + + /* interface-specific operations */ + void (*update_mp_end_port)(struct nxpwifi_adapter *adapter, u16 port); + void (*cleanup_mpa_buf)(struct nxpwifi_adapter *adapter); + int (*cmdrsp_complete)(struct nxpwifi_adapter *adapter, + struct sk_buff *skb); + int (*event_complete)(struct nxpwifi_adapter *adapter, + struct sk_buff *skb); + int (*dnld_fw)(struct nxpwifi_adapter *adapter, + struct nxpwifi_fw_image *fw); + void (*card_reset)(struct nxpwifi_adapter *adapter); + int (*reg_dump)(struct nxpwifi_adapter *adapter, char *drv_buf); + void (*device_dump)(struct nxpwifi_adapter *adapter); + void (*deaggr_pkt)(struct nxpwifi_adapter *adapter, + struct sk_buff *skb); + void (*up_dev)(struct nxpwifi_adapter *adapter); +}; + +#define NXPWIFI_DEFAULT_REGION_CODE NXPWIFI_REGION_FCC + +struct nxpwifi_adapter { + u8 iface_type; + unsigned int debug_mask; + struct nxpwifi_iface_comb iface_limit; + struct nxpwifi_iface_comb curr_iface_comb; + struct nxpwifi_private *priv[NXPWIFI_MAX_BSS_NUM]; + u8 priv_num; + const struct firmware *firmware; + char fw_name[32]; + int winner; + struct device *dev; + struct wiphy *wiphy; + u8 perm_addr[ETH_ALEN]; + unsigned long work_flags; + u32 fw_release_number; + u8 intf_hdr_len; + void *card; + struct nxpwifi_if_ops if_ops; + atomic_t bypass_tx_pending; + atomic_t rx_pending; + atomic_t tx_pending; + atomic_t cmd_pending; + atomic_t tx_hw_pending; + struct workqueue_struct *workqueue; + struct work_struct main_work; + struct workqueue_struct *rx_workqueue; + struct work_struct rx_work; + struct wiphy_work host_mlme_work; + bool rx_work_enabled; + bool rx_processing; + bool delay_main_work; + atomic_t rx_ba_teardown_pending; + atomic_t iface_changing; + struct nxpwifi_bss_prio_tbl bss_prio_tbl[NXPWIFI_MAX_BSS_NUM]; + u32 nxpwifi_processing; + u16 tx_buf_size; + u16 curr_tx_buf_size; + /* SDIO single port rx aggregation capability */ + bool host_disable_sdio_rx_aggr; + bool sdio_rx_aggr_enable; + u16 sdio_rx_block_size; + u32 ioport; + enum NXPWIFI_HARDWARE_STATUS hw_status; + u16 number_of_antenna; + u32 fw_cap_info; + u32 fw_cap_ext; + u16 user_htstream; + u64 uuid_lo; + u64 uuid_hi; + /* interrupt lock */ + spinlock_t int_lock; + u8 int_status; + u32 event_cause; + struct sk_buff *event_skb; + u8 upld_buf[NXPWIFI_UPLD_SIZE]; + u8 data_sent; + u8 cmd_sent; + u8 cmd_resp_received; + bool event_received; + u8 data_received; + u8 assoc_resp_received; + struct nxpwifi_private *priv_link_lost; + u8 host_mlme_link_lost; + u16 seq_num; + struct cmd_ctrl_node *cmd_pool; + struct cmd_ctrl_node *curr_cmd; + /* spin lock for command */ + spinlock_t nxpwifi_cmd_lock; + struct timer_list cmd_timer; + struct list_head cmd_free_q; + spinlock_t cmd_free_q_lock; /* protects cmd_free_q */ + struct list_head cmd_pending_q; + spinlock_t cmd_pending_q_lock; /* protects cmd_pending_q */ + struct list_head scan_pending_q; + spinlock_t scan_pending_q_lock; /* protects scan_pending_q */ + struct sk_buff_head tx_data_q; + atomic_t tx_queued; + u32 scan_processing; + enum nxpwifi_region_code region_code; + struct nxpwifi_802_11d_domain_reg domain_reg; + u16 scan_probes; + u32 scan_mode; + u16 specific_scan_time; + u16 active_scan_time; + u16 passive_scan_time; + u16 scan_chan_gap_time; + u16 fw_bands; + u8 tx_lock_flag; + struct nxpwifi_sleep_period sleep_period; + u16 ps_mode; + u32 ps_state; + u8 need_to_wakeup; + u16 multiple_dtim; + u16 local_listen_interval; + u16 null_pkt_interval; + struct sk_buff *sleep_cfm; + u16 bcn_miss_time_out; + u8 is_deep_sleep; + u8 delay_null_pkt; + u16 delay_to_ps; + u16 enhanced_ps_mode; + u8 pm_wakeup_card_req; + u16 gen_null_pkt; + u16 pps_uapsd_mode; + u32 pm_wakeup_fw_try; + struct timer_list wakeup_timer; + struct nxpwifi_hs_config_param hs_cfg; + u8 hs_activated; + u8 hs_activated_manually; + u16 hs_activate_wait_q_woken; + wait_queue_head_t hs_activate_wait_q; + u8 event_body[MAX_EVENT_SIZE]; + u32 hw_dot_11n_dev_cap; + u8 hw_dev_mcs_support; + u8 hw_mpdu_density; + u8 user_dev_mcs_support; + u8 sec_chan_offset; + struct nxpwifi_dbg dbg; + u8 arp_filter[ARP_FILTER_MAX_BUF_SIZE]; + u32 arp_filter_size; + struct nxpwifi_wait_queue cmd_wait_q; + u8 scan_wait_q_woken; + spinlock_t queue_lock; /* protects TX queues */ + u8 dfs_region; + u8 country_code[IEEE80211_COUNTRY_STRING_LEN]; + u16 max_mgmt_ie_index; + const struct firmware *cal_data; + /* 11AC capability fields */ + u32 is_hw_11ac_capable; + u32 hw_dot_11ac_dev_cap; + u32 hw_dot_11ac_mcs_support; + u32 usr_dot_11ac_dev_cap_bg; + u32 usr_dot_11ac_dev_cap_a; + u32 usr_dot_11ac_mcs_support; + /* 11AX capability fields */ + u8 is_hw_11ax_capable; + u8 hw_he_cap_len; + u8 hw_he_cap[HE_CAP_MAX_SIZE]; + u8 hw_2g_he_cap_len; + u8 hw_2g_he_cap[HE_CAP_MAX_SIZE]; + atomic_t pending_bridged_pkts; + struct completion *fw_done; /* FW init completion */ + bool is_up; + bool ext_scan; + u8 fw_api_ver; + u8 fw_hotfix_ver; + u8 key_api_major_ver, key_api_minor_ver; + u8 max_sta_conn; + struct memory_type_mapping *mem_type_mapping_tbl; + u8 num_mem_types; + bool scan_chan_gap_enabled; + struct sk_buff_head rx_mlme_q; + struct sk_buff_head rx_data_q; + struct nxpwifi_chan_stats *chan_stats; + u32 num_in_chan_stats; + int survey_idx; + u8 coex_scan; + u8 coex_min_scan_time; + u8 coex_max_scan_time; + u8 coex_win_size; + u8 coex_tx_win_size; + u8 coex_rx_win_size; + u8 active_scan_triggered; + bool usb_mc_status; + bool usb_mc_setup; + struct cfg80211_wowlan_nd_info *nd_info; + struct ieee80211_regdomain *regd; + /* Aggregation parameters*/ + struct bus_aggr_params bus_aggr; + void *devdump_data; /* device dump storage */ + int devdump_len; /* device dump length */ + bool ignore_btcoex_events; + struct vdll_dnld_ctrl vdll_ctrl; + u64 roc_cookie_counter; + u32 enable_net_mon; + bool wowlan_enabled; + bool chandef_valid; + struct cfg80211_chan_def chandef; + atomic_t uap_count; +}; + +void nxpwifi_process_tx_queue(struct nxpwifi_adapter *adapter); + +void nxpwifi_init_lock_list(struct nxpwifi_adapter *adapter); + +void nxpwifi_set_trans_start(struct net_device *dev); + +void nxpwifi_stop_net_dev_queue(struct net_device *netdev, + struct nxpwifi_adapter *adapter); + +void nxpwifi_wake_up_net_dev_queue(struct net_device *netdev, + struct nxpwifi_adapter *adapter); + +int nxpwifi_init_priv(struct nxpwifi_private *priv); +void nxpwifi_free_priv(struct nxpwifi_private *priv); + +int nxpwifi_init_fw(struct nxpwifi_adapter *adapter); + +void nxpwifi_shutdown_drv(struct nxpwifi_adapter *adapter); + +int nxpwifi_dnld_fw(struct nxpwifi_adapter *adapter, + struct nxpwifi_fw_image *fw); + +int nxpwifi_recv_packet(struct nxpwifi_private *priv, struct sk_buff *skb); +int nxpwifi_uap_recv_packet(struct nxpwifi_private *priv, + struct sk_buff *skb); + +void nxpwifi_host_mlme_disconnect(struct nxpwifi_private *priv, + u16 reason_code, u8 *sa); + +int nxpwifi_process_mgmt_packet(struct nxpwifi_private *priv, + struct sk_buff *skb); +int nxpwifi_recv_packet_to_monif(struct nxpwifi_private *priv, + struct sk_buff *skb); +int nxpwifi_complete_cmd(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node); + +void nxpwifi_cmd_timeout_func(struct timer_list *t); + +int nxpwifi_get_debug_info(struct nxpwifi_private *priv, + struct nxpwifi_debug_info *info); + +int nxpwifi_alloc_cmd_buffer(struct nxpwifi_adapter *adapter); +void nxpwifi_free_cmd_buffer(struct nxpwifi_adapter *adapter); +void nxpwifi_free_cmd_buffers(struct nxpwifi_adapter *adapter); +void nxpwifi_cancel_all_pending_cmd(struct nxpwifi_adapter *adapter); +void nxpwifi_cancel_pending_scan_cmd(struct nxpwifi_adapter *adapter); +void nxpwifi_cancel_scan(struct nxpwifi_adapter *adapter); + +void nxpwifi_recycle_cmd_node(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node); + +void nxpwifi_insert_cmd_to_pending_q(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node); + +int nxpwifi_exec_next_cmd(struct nxpwifi_adapter *adapter); +int nxpwifi_process_cmdresp(struct nxpwifi_adapter *adapter); +void nxpwifi_process_assoc_resp(struct nxpwifi_adapter *adapter); +int nxpwifi_handle_rx_packet(struct nxpwifi_adapter *adapter, + struct sk_buff *skb); +int nxpwifi_process_tx(struct nxpwifi_private *priv, struct sk_buff *skb, + struct nxpwifi_tx_param *tx_param); +int nxpwifi_send_null_packet(struct nxpwifi_private *priv, u8 flags); +int nxpwifi_write_data_complete(struct nxpwifi_adapter *adapter, + struct sk_buff *skb, int aggr, int status); +void nxpwifi_clean_txrx(struct nxpwifi_private *priv); +u8 nxpwifi_check_last_packet_indication(struct nxpwifi_private *priv); +void nxpwifi_check_ps_cond(struct nxpwifi_adapter *adapter); +void nxpwifi_process_sleep_confirm_resp(struct nxpwifi_adapter *adapter, + u8 *pbuf, u32 upld_len); +void nxpwifi_process_hs_config(struct nxpwifi_adapter *adapter); +void nxpwifi_hs_activated_event(struct nxpwifi_private *priv, + u8 activated); +int nxpwifi_set_hs_params(struct nxpwifi_private *priv, u16 action, + int cmd_type, struct nxpwifi_ds_hs_cfg *hs_cfg); +int nxpwifi_ret_802_11_hs_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp); +int nxpwifi_process_rx_packet(struct nxpwifi_private *priv, + struct sk_buff *skb); +int nxpwifi_process_sta_rx_packet(struct nxpwifi_private *priv, + struct sk_buff *skb); +int nxpwifi_process_uap_rx_packet(struct nxpwifi_private *priv, + struct sk_buff *skb); +int nxpwifi_handle_uap_rx_forward(struct nxpwifi_private *priv, + struct sk_buff *skb); +void nxpwifi_delete_all_station_list(struct nxpwifi_private *priv); +void nxpwifi_wmm_del_peer_ra_list(struct nxpwifi_private *priv, + const u8 *ra_addr); +void nxpwifi_process_sta_txpd(struct nxpwifi_private *priv, + struct sk_buff *skb); +void nxpwifi_process_uap_txpd(struct nxpwifi_private *priv, + struct sk_buff *skb); +int nxpwifi_cmd_802_11_scan(struct host_cmd_ds_command *cmd, + struct nxpwifi_scan_cmd_config *scan_cfg); +void nxpwifi_queue_scan_cmd(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node); +int nxpwifi_ret_802_11_scan(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp); +int nxpwifi_associate(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc); +int nxpwifi_cmd_802_11_associate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + struct nxpwifi_bssdescriptor *bss_desc); +int nxpwifi_ret_802_11_associate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp); +u8 nxpwifi_band_to_radio_type(u16 config_bands); +int nxpwifi_deauthenticate(struct nxpwifi_private *priv, u8 *mac); +void nxpwifi_deauthenticate_all(struct nxpwifi_adapter *adapter); +int nxpwifi_cmd_802_11_bg_scan_query(struct host_cmd_ds_command *cmd); +struct nxpwifi_chan_freq_power *nxpwifi_get_cfp(struct nxpwifi_private *priv, + u8 band, u16 channel, u32 freq); +u32 nxpwifi_index_to_data_rate(struct nxpwifi_private *priv, + u8 index, u8 ht_info); +u32 nxpwifi_index_to_acs_data_rate(struct nxpwifi_private *priv, + u8 index, u8 ht_info); +int nxpwifi_cmd_append_vsie_tlv(struct nxpwifi_private *priv, u16 vsie_mask, + u8 **buffer); +u32 nxpwifi_get_active_data_rates(struct nxpwifi_private *priv, + u8 *rates); +u32 nxpwifi_get_supported_rates(struct nxpwifi_private *priv, u8 *rates); +u32 nxpwifi_get_rates_from_cfg80211(struct nxpwifi_private *priv, + u8 *rates, u8 radio_type); +u8 nxpwifi_is_rate_auto(struct nxpwifi_private *priv); +void nxpwifi_save_curr_bcn(struct nxpwifi_private *priv); +void nxpwifi_free_curr_bcn(struct nxpwifi_private *priv); +int nxpwifi_is_command_pending(struct nxpwifi_adapter *adapter); +void nxpwifi_init_priv_params(struct nxpwifi_private *priv, + struct net_device *dev); +void nxpwifi_set_ba_params(struct nxpwifi_private *priv); +void nxpwifi_update_ampdu_txwinsize(struct nxpwifi_adapter *pmadapter); +void nxpwifi_set_11ac_ba_params(struct nxpwifi_private *priv); +int nxpwifi_cmd_802_11_scan_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + void *data_buf); +int nxpwifi_ret_802_11_scan_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp); +int nxpwifi_handle_event_ext_scan_report(struct nxpwifi_private *priv, + void *buf); +int nxpwifi_cmd_802_11_bg_scan_config(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + void *data_buf); +int nxpwifi_stop_bg_scan(struct nxpwifi_private *priv); + +/* check if RA-based queuing */ +static inline u8 +nxpwifi_queuing_ra_based(struct nxpwifi_private *priv) +{ + /* In STA mode DA==RA; subject to future revision */ + if (priv->bss_mode == NL80211_IFTYPE_STATION && + (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA)) + return false; + + return true; +} + +/* copy rates from src to dest */ +static inline u32 +nxpwifi_copy_rates(u8 *dest, u32 pos, u8 *src, int len) +{ + int i; + + for (i = 0; i < len && src[i]; i++, pos++) { + if (pos >= NXPWIFI_SUPPORTED_RATES) + break; + dest[pos] = src[i]; + } + + return pos; +} + +/* return priv matching the given BSS type and number */ +static inline struct nxpwifi_private * +nxpwifi_get_priv_by_id(struct nxpwifi_adapter *adapter, + u8 bss_num, u8 bss_type) +{ + int i; + + for (i = 0; i < adapter->priv_num; i++) { + if (adapter->priv[i]->bss_mode == + NL80211_IFTYPE_UNSPECIFIED) + continue; + if (adapter->priv[i]->bss_num == bss_num && + adapter->priv[i]->bss_type == bss_type) + break; + } + return ((i < adapter->priv_num) ? adapter->priv[i] : NULL); +} + +/* return first priv matching BSS role */ +static inline struct nxpwifi_private * +nxpwifi_get_priv(struct nxpwifi_adapter *adapter, + enum nxpwifi_bss_role bss_role) +{ + int i; + + for (i = 0; i < adapter->priv_num; i++) { + if (bss_role == NXPWIFI_BSS_ROLE_ANY || + GET_BSS_ROLE(adapter->priv[i]) == bss_role) + break; + } + + return ((i < adapter->priv_num) ? adapter->priv[i] : NULL); +} + +/* find unused BSS number for new interface */ +static inline u8 +nxpwifi_get_unused_bss_num(struct nxpwifi_adapter *adapter, u8 bss_type) +{ + u8 i, j; + int index[NXPWIFI_MAX_BSS_NUM]; + + memset(index, 0, sizeof(index)); + for (i = 0; i < adapter->priv_num; i++) + if (adapter->priv[i]->bss_type == bss_type && + !(adapter->priv[i]->bss_mode == + NL80211_IFTYPE_UNSPECIFIED)) { + index[adapter->priv[i]->bss_num] = 1; + } + for (j = 0; j < NXPWIFI_MAX_BSS_NUM; j++) + if (!index[j]) + return j; + return -ENOENT; +} + +/* return unused private entry for requested bss type */ +static inline struct nxpwifi_private * +nxpwifi_get_unused_priv_by_bss_type(struct nxpwifi_adapter *adapter, + u8 bss_type) +{ + u8 i; + + for (i = 0; i < adapter->priv_num; i++) + if (adapter->priv[i]->bss_mode == + NL80211_IFTYPE_UNSPECIFIED) { + adapter->priv[i]->bss_num = + nxpwifi_get_unused_bss_num(adapter, bss_type); + break; + } + + return ((i < adapter->priv_num) ? adapter->priv[i] : NULL); +} + +/* return private structure attached to netdev */ +static inline struct nxpwifi_private * +nxpwifi_netdev_get_priv(struct net_device *dev) +{ + return (struct nxpwifi_private *)(*(unsigned long *)netdev_priv(dev)); +} + +/* return true if skb contains a management frame */ +static inline bool nxpwifi_is_skb_mgmt_frame(struct sk_buff *skb) +{ + return (get_unaligned_le32(skb->data) == PKT_TYPE_MGMT); +} + +/* channel closed by CSA */ +static inline u8 +nxpwifi_11h_get_csa_closed_channel(struct nxpwifi_private *priv) +{ + if (!priv->csa_chan) + return 0; + + /* clear CSA if DFS switch timeout expired */ + if (time_after(jiffies, priv->csa_expire_time)) { + priv->csa_chan = 0; + priv->csa_expire_time = 0; + } + + return priv->csa_chan; +} + +static inline u8 nxpwifi_is_any_intf_active(struct nxpwifi_private *priv) +{ + struct nxpwifi_private *priv_tmp; + int i; + + for (i = 0; i < priv->adapter->priv_num; i++) { + priv_tmp = priv->adapter->priv[i]; + if ((GET_BSS_ROLE(priv_tmp) == NXPWIFI_BSS_ROLE_UAP && + priv_tmp->bss_started) || + (GET_BSS_ROLE(priv_tmp) == NXPWIFI_BSS_ROLE_STA && + priv_tmp->media_connected)) + return 1; + } + + return 0; +} + +int nxpwifi_init_shutdown_fw(struct nxpwifi_private *priv, + u32 func_init_shutdown); + +int nxpwifi_add_card(void *card, struct completion *fw_done, + struct nxpwifi_if_ops *if_ops, u8 iface_type, + struct device *dev); +void nxpwifi_remove_card(struct nxpwifi_adapter *adapter); + +void nxpwifi_get_version(struct nxpwifi_adapter *adapter, char *version, + int maxlen); +int +nxpwifi_request_set_multicast_list(struct nxpwifi_private *priv, + struct nxpwifi_multicast_list *mcast_list); +int nxpwifi_copy_mcast_addr(struct nxpwifi_multicast_list *mlist, + struct net_device *dev); +int nxpwifi_wait_queue_complete(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_queued); +int nxpwifi_bss_start(struct nxpwifi_private *priv, struct cfg80211_bss *bss, + struct cfg80211_ssid *req_ssid); +int nxpwifi_cancel_hs(struct nxpwifi_private *priv, int cmd_type); +bool nxpwifi_enable_hs(struct nxpwifi_adapter *adapter); +int nxpwifi_disable_auto_ds(struct nxpwifi_private *priv); +int nxpwifi_drv_get_data_rate(struct nxpwifi_private *priv, u32 *rate); + +int nxpwifi_scan_networks(struct nxpwifi_private *priv, + const struct nxpwifi_user_scan_cfg *user_scan_in); +int nxpwifi_set_radio(struct nxpwifi_private *priv, u8 option); + +int nxpwifi_set_encode(struct nxpwifi_private *priv, struct key_params *kp, + const u8 *key, int key_len, u8 key_index, + const u8 *mac_addr, int disable); + +int nxpwifi_set_gen_ie(struct nxpwifi_private *priv, const u8 *ie, int ie_len); + +int nxpwifi_get_ver_ext(struct nxpwifi_private *priv, u32 version_str_sel); + +int nxpwifi_remain_on_chan_cfg(struct nxpwifi_private *priv, u16 action, + struct ieee80211_channel *chan, + unsigned int duration); + +int nxpwifi_get_stats_info(struct nxpwifi_private *priv, + struct nxpwifi_ds_get_stats *log); + +int nxpwifi_reg_write(struct nxpwifi_private *priv, u32 reg_type, + u32 reg_offset, u32 reg_value); + +int nxpwifi_reg_read(struct nxpwifi_private *priv, u32 reg_type, + u32 reg_offset, u32 *value); + +int nxpwifi_eeprom_read(struct nxpwifi_private *priv, u16 offset, u16 bytes, + u8 *value); + +int nxpwifi_set_11n_httx_cfg(struct nxpwifi_private *priv, int data); + +int nxpwifi_get_11n_httx_cfg(struct nxpwifi_private *priv, int *data); + +int nxpwifi_set_tx_rate_cfg(struct nxpwifi_private *priv, int tx_rate_index); + +int nxpwifi_get_tx_rate_cfg(struct nxpwifi_private *priv, int *tx_rate_index); + +int nxpwifi_drv_set_power(struct nxpwifi_private *priv, u32 *ps_mode); + +int nxpwifi_drv_get_driver_version(struct nxpwifi_adapter *adapter, + char *version, int max_len); + +int nxpwifi_set_tx_power(struct nxpwifi_private *priv, + struct nxpwifi_power_cfg *power_cfg); + +void nxpwifi_main_process(struct nxpwifi_adapter *adapter); + +void nxpwifi_queue_tx_pkt(struct nxpwifi_private *priv, struct sk_buff *skb); + +int nxpwifi_get_bss_info(struct nxpwifi_private *priv, + struct nxpwifi_bss_info *info); +int nxpwifi_fill_new_bss_desc(struct nxpwifi_private *priv, + struct cfg80211_bss *bss, + struct nxpwifi_bssdescriptor *bss_desc); +int nxpwifi_update_bss_desc_with_ie(struct nxpwifi_adapter *adapter, + struct nxpwifi_bssdescriptor *bss_entry); +int nxpwifi_check_network_compatibility(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc); + +u8 nxpwifi_chan_type_to_sec_chan_offset(enum nl80211_channel_type chan_type); +u8 nxpwifi_get_chan_type(struct nxpwifi_private *priv); + +struct wireless_dev *nxpwifi_add_virtual_intf(struct wiphy *wiphy, + const char *name, + unsigned char name_assign_type, + enum nl80211_iftype type, + struct vif_params *params); +int nxpwifi_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev); + +int nxpwifi_add_wowlan_magic_pkt_filter(struct nxpwifi_adapter *adapter); + +int nxpwifi_set_mgmt_ies(struct nxpwifi_private *priv, + struct cfg80211_beacon_data *data); +int nxpwifi_del_mgmt_ies(struct nxpwifi_private *priv); +u8 *nxpwifi_11d_code_2_region(u8 code); +void nxpwifi_init_11h_params(struct nxpwifi_private *priv); +int nxpwifi_is_11h_active(struct nxpwifi_private *priv); +int nxpwifi_11h_activate(struct nxpwifi_private *priv, bool flag); +void nxpwifi_11h_process_join(struct nxpwifi_private *priv, u8 **buffer, + struct nxpwifi_bssdescriptor *bss_desc); +int nxpwifi_11h_handle_event_chanswann(struct nxpwifi_private *priv); + +extern const struct ethtool_ops nxpwifi_ethtool_ops; + +void nxpwifi_del_all_sta_list(struct nxpwifi_private *priv); +void nxpwifi_del_sta_entry(struct nxpwifi_private *priv, const u8 *mac); +void +nxpwifi_set_sta_ht_cap(struct nxpwifi_private *priv, const u8 *ies, + int ies_len, struct nxpwifi_sta_node *node); +struct nxpwifi_sta_node * +nxpwifi_add_sta_entry(struct nxpwifi_private *priv, const u8 *mac); +struct nxpwifi_sta_node * +nxpwifi_get_sta_entry(struct nxpwifi_private *priv, const u8 *mac); +struct nxpwifi_sta_node * +nxpwifi_get_sta_entry_rcu(struct nxpwifi_private *priv, const u8 *mac); +int nxpwifi_init_channel_scan_gap(struct nxpwifi_adapter *adapter); + +int nxpwifi_cmd_issue_chan_report_request(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + void *data_buf); +int nxpwifi_11h_handle_chanrpt_ready(struct nxpwifi_private *priv, + struct sk_buff *skb); + +void nxpwifi_parse_tx_status_event(struct nxpwifi_private *priv, + void *event_body); + +struct sk_buff * +nxpwifi_clone_skb_for_tx_status(struct nxpwifi_private *priv, + struct sk_buff *skb, u8 flag, u64 *cookie); +void nxpwifi_reset_conn_state_work(struct wiphy *wiphy, struct wiphy_work *work); +void nxpwifi_dfs_cac_work(struct wiphy *wiphy, struct wiphy_work *work); +void nxpwifi_dfs_chan_sw_work(struct wiphy *wiphy, struct wiphy_work *work); +void nxpwifi_abort_cac(struct nxpwifi_private *priv); +int nxpwifi_stop_radar_detection(struct nxpwifi_private *priv, + struct cfg80211_chan_def *chandef); +int nxpwifi_11h_handle_radar_detected(struct nxpwifi_private *priv, + struct sk_buff *skb); + +void nxpwifi_hist_data_set(struct nxpwifi_private *priv, u8 rx_rate, s8 snr, + s8 nflr); +void nxpwifi_hist_data_reset(struct nxpwifi_private *priv); +void nxpwifi_hist_data_add(struct nxpwifi_private *priv, + u8 rx_rate, s8 snr, s8 nflr); +u8 nxpwifi_adjust_data_rate(struct nxpwifi_private *priv, + u8 rx_rate, u8 ht_info); + +void nxpwifi_drv_info_dump(struct nxpwifi_adapter *adapter); +void nxpwifi_prepare_fw_dump_info(struct nxpwifi_adapter *adapter); +void nxpwifi_upload_device_dump(struct nxpwifi_adapter *adapter); +void *nxpwifi_alloc_dma_align_buf(int rx_len, gfp_t flags); +void nxpwifi_fw_dump_event(struct nxpwifi_private *priv); +int nxpwifi_get_wakeup_reason(struct nxpwifi_private *priv, u16 action, + int cmd_type, + struct nxpwifi_ds_wakeup_reason *wakeup_reason); +int nxpwifi_get_chan_info(struct nxpwifi_private *priv, + struct nxpwifi_channel_band *channel_band); +void nxpwifi_coex_ampdu_rxwinsize(struct nxpwifi_adapter *adapter); +void nxpwifi_11n_delba(struct nxpwifi_private *priv, int tid); +int nxpwifi_send_domain_info_cmd_fw(struct wiphy *wiphy, enum nl80211_band band); +int nxpwifi_set_mac_address(struct nxpwifi_private *priv, + struct net_device *dev, + bool external, u8 *new_mac); +void nxpwifi_devdump_tmo_func(unsigned long function_context); + +#ifdef CONFIG_DEBUG_FS +void nxpwifi_debugfs_init(void); +void nxpwifi_debugfs_remove(void); + +void nxpwifi_dev_debugfs_init(struct nxpwifi_private *priv); +void nxpwifi_dev_debugfs_remove(struct nxpwifi_private *priv); +#endif +int nxpwifi_reinit_sw(struct nxpwifi_adapter *adapter); +void nxpwifi_shutdown_sw(struct nxpwifi_adapter *adapter); +bool nxpwifi_is_valid_region_code(enum nxpwifi_region_code code); +#endif /* !_NXPWIFI_MAIN_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/scan.c b/drivers/net/wireless/nxp/nxpwifi/scan.c new file mode 100644 index 000000000000..bb3ce2b6f4b9 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/scan.c @@ -0,0 +1,2695 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: scan ioctl and command handling + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "11n.h" +#include "11ac.h" +#include "11ax.h" +#include "cfg80211.h" + +/* The maximum number of channels the firmware can scan per command */ +#define NXPWIFI_MAX_CHANNELS_PER_SPECIFIC_SCAN 14 + +#define NXPWIFI_DEF_CHANNELS_PER_SCAN_CMD 4 + +/* Memory needed to store a max sized Channel List TLV for a firmware scan */ +#define CHAN_TLV_MAX_SIZE (sizeof(struct nxpwifi_ie_types_header) \ + + (NXPWIFI_MAX_CHANNELS_PER_SPECIFIC_SCAN \ + * sizeof(struct nxpwifi_chan_scan_param_set))) + +/* Memory needed to store supported rate */ +#define RATE_TLV_MAX_SIZE (sizeof(struct nxpwifi_ie_types_rates_param_set) \ + + HOSTCMD_SUPPORTED_RATES) + +/* Memory needed to store a max number/size WildCard SSID TLV for a firmware scan */ +#define WILDCARD_SSID_TLV_MAX_SIZE \ + (NXPWIFI_MAX_SSID_LIST_LENGTH * \ + (sizeof(struct nxpwifi_ie_types_wildcard_ssid_params) \ + + IEEE80211_MAX_SSID_LEN)) + +/* Maximum memory needed for a nxpwifi_scan_cmd_config with all TLVs at max */ +#define MAX_SCAN_CFG_ALLOC (sizeof(struct nxpwifi_scan_cmd_config) \ + + sizeof(struct nxpwifi_ie_types_num_probes) \ + + sizeof(struct nxpwifi_ie_types_htcap) \ + + sizeof(struct nxpwifi_ie_types_vhtcap) \ + + sizeof(struct nxpwifi_ie_types_he_cap) \ + + CHAN_TLV_MAX_SIZE \ + + RATE_TLV_MAX_SIZE \ + + WILDCARD_SSID_TLV_MAX_SIZE) + +union nxpwifi_scan_cmd_config_tlv { + /* Scan configuration (variable length) */ + struct nxpwifi_scan_cmd_config config; + /* Max allocated block */ + u8 config_alloc_buf[MAX_SCAN_CFG_ALLOC]; +}; + +#define NXPWIFI_WPA_CIPHER_SUITE_TKIP SUITE(WLAN_OUI_MICROSOFT, 2) +#define NXPWIFI_WPA_CIPHER_SUITE_CCMP SUITE(WLAN_OUI_MICROSOFT, 4) + +static void +_dbg_security_flags(int log_level, const char *func, const char *desc, + struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + _nxpwifi_dbg(priv->adapter, log_level, + "info: %s: %s:\twpa_ie=%#x wpa2_ie=%#x WEP=%s WPA=%s WPA2=%s\tEncMode=%#x privacy=%#x\n", + func, desc, + bss_desc->bcn_wpa_ie ? + bss_desc->bcn_wpa_ie->vend_hdr.element_id : 0, + bss_desc->bcn_rsn_ie ? + bss_desc->bcn_rsn_ie->id : 0, + priv->sec_info.wep_enabled ? "e" : "d", + priv->sec_info.wpa_enabled ? "e" : "d", + priv->sec_info.wpa2_enabled ? "e" : "d", + priv->sec_info.encryption_mode, + bss_desc->privacy); +} + +#define dbg_security_flags(mask, desc, priv, bss_desc) \ + _dbg_security_flags(NXPWIFI_DBG_##mask, __func__, desc, priv, bss_desc) + +/* Parse a WPA/RSN element and check whether its PTK list contains the OUI */ +static u8 +nxpwifi_search_oui_in_ie(struct ie_body *iebody, u8 *oui) +{ + u8 count; + + count = iebody->ptk_cnt[0]; + + /* + * PTK may contain multiple OUIs; iterate through the list and compare + * each one + */ + while (count) { + if (!memcmp(iebody->ptk_body, oui, sizeof(iebody->ptk_body))) + return NXPWIFI_OUI_PRESENT; + + --count; + if (count) + iebody = (struct ie_body *)((u8 *)iebody + + sizeof(iebody->ptk_body)); + } + + pr_debug("info: %s: OUI is not found in PTK\n", __func__); + return NXPWIFI_OUI_NOT_PRESENT; +} + +/* Check whether the RSN IE is present and if its PTK list contains the OUI */ +static u8 +nxpwifi_is_rsn_oui_present(struct nxpwifi_bssdescriptor *bss_desc, + u32 cipher) +{ + struct ie_body *iebody; + u8 ret = NXPWIFI_OUI_NOT_PRESENT; + __be32 oui = cpu_to_be32(cipher); + + if (bss_desc->bcn_rsn_ie) { + iebody = (struct ie_body *) + (((u8 *)bss_desc->bcn_rsn_ie->data) + + RSN_GTK_OUI_OFFSET); + ret = nxpwifi_search_oui_in_ie(iebody, (u8 *)&oui); + if (ret) + return ret; + } + return ret; +} + +/* Check if the WPA IE exists and whether its PTK list contains the OUI */ +static u8 +nxpwifi_is_wpa_oui_present(struct nxpwifi_bssdescriptor *bss_desc, u32 cipher) +{ + struct ie_body *iebody; + u8 ret = NXPWIFI_OUI_NOT_PRESENT; + __be32 oui = cpu_to_be32(cipher); + + if (bss_desc->bcn_wpa_ie) { + iebody = (struct ie_body *)((u8 *)bss_desc->bcn_wpa_ie->data + + WPA_GTK_OUI_OFFSET); + ret = nxpwifi_search_oui_in_ie(iebody, (u8 *)&oui); + if (ret) + return ret; + } + return ret; +} + +/* Check whether both driver and BSS operate with no security */ +static bool +nxpwifi_is_bss_no_sec(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + if (!priv->sec_info.wep_enabled && !priv->sec_info.wpa_enabled && + !priv->sec_info.wpa2_enabled && + !bss_desc->bcn_rsn_ie && + !bss_desc->bcn_wpa_ie && + !priv->sec_info.encryption_mode && !bss_desc->privacy) { + return true; + } + return false; +} + +/* Check whether static WEP is enabled and the BSS privacy setting matches */ +static bool +nxpwifi_is_bss_static_wep(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + if (priv->sec_info.wep_enabled && !priv->sec_info.wpa_enabled && + !priv->sec_info.wpa2_enabled && bss_desc->privacy) { + return true; + } + return false; +} + +/* Check whether WPA is enabled and the BSS contains a WPA IE */ +static bool +nxpwifi_is_bss_wpa(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + if (!priv->sec_info.wep_enabled && priv->sec_info.wpa_enabled && + !priv->sec_info.wpa2_enabled && + bss_desc->bcn_wpa_ie) { + dbg_security_flags(INFO, "WPA", priv, bss_desc); + return true; + } + return false; +} + +/* Check whether WPA2 is enabled and the BSS includes an RSN IE */ +static bool +nxpwifi_is_bss_wpa2(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + if (!priv->sec_info.wep_enabled && !priv->sec_info.wpa_enabled && + priv->sec_info.wpa2_enabled && + bss_desc->bcn_rsn_ie) { + /* + * Some APs (e.g., WRT54G) may omit the privacy bit even when + * using WPA2 + */ + dbg_security_flags(ERROR, "WPA2", priv, bss_desc); + return true; + } + return false; +} + +/* Check dynamic WEP: enabled in driver, privacy set, and no WPA/RSN IE present */ +static bool +nxpwifi_is_bss_dynamic_wep(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + if (!priv->sec_info.wep_enabled && !priv->sec_info.wpa_enabled && + !priv->sec_info.wpa2_enabled && + !bss_desc->bcn_wpa_ie && + !bss_desc->bcn_rsn_ie && + priv->sec_info.encryption_mode && bss_desc->privacy) { + dbg_security_flags(INFO, "dynamic", priv, bss_desc); + return true; + } + return false; +} + +/* + * Check whether a scanned network is compatible with the driver's security + * configuration. The decision considers WEP, WPA, WPA2, privacy settings, + * and whether HT must be disabled when required (e.g., no AES). + * + * General rules: + * - Open networks: always compatible. + * - WPA-only: compatible; HT disabled if AES is not supported. + * - WPA2-only: compatible; HT disabled if AES is not supported. + * - Static WEP: compatible; HT disabled. + * - Dynamic WEP: compatible when privacy is enabled. + * + * Note: Compatibility is not enforced during roaming except for security mode. + */ +static int +nxpwifi_is_network_compatible(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, u32 mode) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + bss_desc->disable_11n = false; + + /* Skip compatibility checks while roaming */ + if (priv->media_connected && + priv->bss_mode == NL80211_IFTYPE_STATION && + bss_desc->bss_mode == NL80211_IFTYPE_STATION) + return 0; + + if (priv->wps.session_enable) { + nxpwifi_dbg(adapter, IOCTL, + "info: return success directly in WPS period\n"); + return 0; + } + + if (bss_desc->chan_sw_ie_present) { + nxpwifi_dbg(adapter, INFO, + "Don't connect to AP with WLAN_EID_CHANNEL_SWITCH\n"); + return -EPERM; + } + + if (bss_desc->bss_mode == mode) { + if (nxpwifi_is_bss_no_sec(priv, bss_desc)) { + return 0; + } else if (nxpwifi_is_bss_static_wep(priv, bss_desc)) { + nxpwifi_dbg(adapter, INFO, + "info: Disable 11n in WEP mode.\n"); + bss_desc->disable_11n = true; + return 0; + } else if (nxpwifi_is_bss_wpa(priv, bss_desc)) { + if (((priv->config_bands & BAND_GN || + priv->config_bands & BAND_AN) && + bss_desc->bcn_ht_cap) && + !nxpwifi_is_wpa_oui_present(bss_desc, + NXPWIFI_WPA_CIPHER_SUITE_CCMP)) { + if (nxpwifi_is_wpa_oui_present + (bss_desc, NXPWIFI_WPA_CIPHER_SUITE_TKIP)) { + nxpwifi_dbg(adapter, INFO, + "info: Disable 11n if AES\t" + "is not supported by AP\n"); + bss_desc->disable_11n = true; + } else { + return -EINVAL; + } + } + return 0; + } else if (nxpwifi_is_bss_wpa2(priv, bss_desc)) { + if (((priv->config_bands & BAND_GN || + priv->config_bands & BAND_AN) && + bss_desc->bcn_ht_cap) && + !nxpwifi_is_rsn_oui_present(bss_desc, + WLAN_CIPHER_SUITE_CCMP)) { + if (nxpwifi_is_rsn_oui_present + (bss_desc, WLAN_CIPHER_SUITE_TKIP)) { + nxpwifi_dbg(adapter, INFO, + "info: Disable 11n if AES\t" + "is not supported by AP\n"); + bss_desc->disable_11n = true; + } else if (nxpwifi_is_rsn_oui_present + (bss_desc, WLAN_CIPHER_SUITE_GCMP_256) || + nxpwifi_is_rsn_oui_present + (bss_desc, WLAN_CIPHER_SUITE_CCMP_256)) { + return 0; + } else { + return -EINVAL; + } + } + return 0; + } else if (nxpwifi_is_bss_dynamic_wep(priv, bss_desc)) { + return 0; + } + + /* Security mismatch */ + dbg_security_flags(ERROR, "failed", priv, bss_desc); + return -EINVAL; + } + + return -EINVAL; +} + +/* + * Build the channel list for scanning based on region and band settings. + * Used when a scan request does not specify its own channel list. + */ +static int +nxpwifi_scan_create_channel_list(struct nxpwifi_private *priv, + const struct nxpwifi_user_scan_cfg + *user_scan_in, + struct nxpwifi_chan_scan_param_set + *scan_chan_list, + u8 filtered_scan) +{ + enum nl80211_band band; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch; + struct nxpwifi_adapter *adapter = priv->adapter; + int chan_idx = 0, i; + u16 scan_time = 0; + + if (user_scan_in) + scan_time = (u16)user_scan_in->chan_list[0].scan_time; + + for (band = 0; (band < NUM_NL80211_BANDS) ; band++) { + if (!priv->wdev.wiphy->bands[band]) + continue; + + sband = priv->wdev.wiphy->bands[band]; + + for (i = 0; (i < sband->n_channels) ; i++) { + ch = &sband->channels[i]; + if (ch->flags & IEEE80211_CHAN_DISABLED) + continue; + scan_chan_list[chan_idx].band_cfg = band; + + if (scan_time) + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(scan_time); + else if ((ch->flags & IEEE80211_CHAN_NO_IR) || + (ch->flags & IEEE80211_CHAN_RADAR)) + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(adapter->passive_scan_time); + else + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(adapter->active_scan_time); + + if (ch->flags & IEEE80211_CHAN_NO_IR) + scan_chan_list[chan_idx].chan_scan_mode_bmap |= + (NXPWIFI_PASSIVE_SCAN | NXPWIFI_HIDDEN_SSID_REPORT); + else + scan_chan_list[chan_idx].chan_scan_mode_bmap &= + ~NXPWIFI_PASSIVE_SCAN; + + scan_chan_list[chan_idx].chan_number = (u32)ch->hw_value; + scan_chan_list[chan_idx].chan_scan_mode_bmap |= + NXPWIFI_DISABLE_CHAN_FILT; + + if (filtered_scan && + !((ch->flags & IEEE80211_CHAN_NO_IR) || + (ch->flags & IEEE80211_CHAN_RADAR))) + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(adapter->specific_scan_time); + + chan_idx++; + } + } + return chan_idx; +} + +/* + * Build the channel-list TLV for bgscan based on region and band settings. + */ +static int +nxpwifi_bgscan_create_channel_list(struct nxpwifi_private *priv, + const struct nxpwifi_bg_scan_cfg + *bgscan_cfg_in, + struct nxpwifi_chan_scan_param_set + *scan_chan_list) +{ + enum nl80211_band band; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch; + struct nxpwifi_adapter *adapter = priv->adapter; + int chan_idx = 0, i; + u16 scan_time = 0, specific_scan_time = adapter->specific_scan_time; + + if (bgscan_cfg_in) + scan_time = (u16)bgscan_cfg_in->chan_list[0].scan_time; + + for (band = 0; (band < NUM_NL80211_BANDS); band++) { + if (!priv->wdev.wiphy->bands[band]) + continue; + + sband = priv->wdev.wiphy->bands[band]; + + for (i = 0; (i < sband->n_channels) ; i++) { + ch = &sband->channels[i]; + if (ch->flags & IEEE80211_CHAN_DISABLED) + continue; + scan_chan_list[chan_idx].band_cfg = band; + + if (scan_time) + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(scan_time); + else if (ch->flags & IEEE80211_CHAN_NO_IR) + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(adapter->passive_scan_time); + else + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(specific_scan_time); + + if (ch->flags & IEEE80211_CHAN_NO_IR) + scan_chan_list[chan_idx].chan_scan_mode_bmap |= + NXPWIFI_PASSIVE_SCAN; + else + scan_chan_list[chan_idx].chan_scan_mode_bmap &= + ~NXPWIFI_PASSIVE_SCAN; + + scan_chan_list[chan_idx].chan_number = (u32)ch->hw_value; + chan_idx++; + } + } + return chan_idx; +} + +/* Append the rate TLV to the scan configuration command */ +static int +nxpwifi_append_rate_tlv(struct nxpwifi_private *priv, + struct nxpwifi_scan_cmd_config *scan_cfg_out, + u8 radio) +{ + struct nxpwifi_ie_types_rates_param_set *rates_tlv; + u8 rates[NXPWIFI_SUPPORTED_RATES], *tlv_pos; + u32 rates_size; + + memset(rates, 0, sizeof(rates)); + + tlv_pos = (u8 *)scan_cfg_out->tlv_buf + scan_cfg_out->tlv_buf_len; + + if (priv->scan_request) + rates_size = nxpwifi_get_rates_from_cfg80211(priv, rates, + radio); + else + rates_size = nxpwifi_get_supported_rates(priv, rates); + + nxpwifi_dbg(priv->adapter, CMD, + "info: SCAN_CMD: Rates size = %d\n", + rates_size); + rates_tlv = (struct nxpwifi_ie_types_rates_param_set *)tlv_pos; + rates_tlv->header.type = cpu_to_le16(WLAN_EID_SUPP_RATES); + rates_tlv->header.len = cpu_to_le16((u16)rates_size); + memcpy(rates_tlv->rates, rates, rates_size); + scan_cfg_out->tlv_buf_len += sizeof(rates_tlv->header) + rates_size; + + return rates_size; +} + +/* + * Build and send multiple scan commands by chunking channel TLVs per scan + * limit. + */ +static int +nxpwifi_scan_channel_list(struct nxpwifi_private *priv, + u32 max_chan_per_scan, u8 filtered_scan, + struct nxpwifi_scan_cmd_config *scan_cfg_out, + struct nxpwifi_ie_types_chan_list_param_set *tlv_o, + struct nxpwifi_chan_scan_param_set *scan_chan_list) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret = 0; + struct nxpwifi_chan_scan_param_set *tmp_chan_list; + u32 tlv_idx, rates_size, cmd_no; + u32 total_scan_time; + u32 done_early; + u8 radio_type; + + if (!scan_cfg_out || !tlv_o || !scan_chan_list) { + nxpwifi_dbg(priv->adapter, ERROR, + "info: Scan: Null detect: %p, %p, %p\n", + scan_cfg_out, tlv_o, scan_chan_list); + return -EINVAL; + } + + /* Check csa channel expiry before preparing scan list */ + nxpwifi_11h_get_csa_closed_channel(priv); + + tlv_o->header.type = cpu_to_le16(TLV_TYPE_CHANLIST); + + tmp_chan_list = scan_chan_list; + + /* + * Iterate through the channel list and send a firmware scan command for + * each group of max_chan_per_scan channels, or individually for + * channels 1, 6, and 11 when configured. + */ + while (tmp_chan_list->chan_number) { + tlv_idx = 0; + total_scan_time = 0; + radio_type = 0; + tlv_o->header.len = 0; + done_early = false; + + /* + * Build the channel TLV for the scan command. Continue adding + * channel TLVs until one of the following conditions is met: + * - tlv_idx reaches the maximum allowed per scan command + * - the next channel is 0 (end of the desired channel list) + * - done_early is set (used for per-channel scanning of 1, 6, + * and 11) + */ + while (tlv_idx < max_chan_per_scan && + tmp_chan_list->chan_number && !done_early) { + if (tmp_chan_list->chan_number == priv->csa_chan) { + tmp_chan_list++; + continue; + } + + radio_type = tmp_chan_list->band_cfg; + nxpwifi_dbg(priv->adapter, INFO, + "info: Scan: Chan(%3d), Band(%d),\t" + "Mode(%d, %d), Dur(%d)\n", + tmp_chan_list->chan_number, + tmp_chan_list->band_cfg, + tmp_chan_list->chan_scan_mode_bmap + & NXPWIFI_PASSIVE_SCAN, + (tmp_chan_list->chan_scan_mode_bmap + & NXPWIFI_DISABLE_CHAN_FILT) >> 1, + le16_to_cpu(tmp_chan_list->max_scan_time)); + + /* Copy the current channel TLV into the command being prepared */ + memcpy(&tlv_o->chan_scan_param[tlv_idx], tmp_chan_list, + sizeof(*tlv_o->chan_scan_param)); + + /* + * Increment the TLV header length by the size + * appended + */ + le16_unaligned_add_cpu(&tlv_o->header.len, + sizeof(*tlv_o->chan_scan_param)); + + /* + * The tlv buffer length is set to the number of bytes + * of the between the channel tlv pointer and the start + * of the tlv buffer. This compensates for any TLVs + * that were appended before the channel list. + */ + scan_cfg_out->tlv_buf_len = + (u32)((u8 *)tlv_o - scan_cfg_out->tlv_buf); + + scan_cfg_out->tlv_buf_len += + (sizeof(tlv_o->header) + + le16_to_cpu(tlv_o->header.len)); + + /* Advance the index for the channel TLV being constructed. */ + tlv_idx++; + + /* Count the total scan time per command */ + total_scan_time += + le16_to_cpu(tmp_chan_list->max_scan_time); + + done_early = false; + + /* + * Stop the loop if the current channel is one of 1, 6, + * or 11 and no SSID or BSSID filter is applied. + */ + if (!filtered_scan && + (tmp_chan_list->chan_number == 1 || + tmp_chan_list->chan_number == 6 || + tmp_chan_list->chan_number == 11)) + done_early = true; + + /* Advance the tmp pointer to the next channel to be scanned. */ + tmp_chan_list++; + + /* + * Stop the loop if the next channel is one of 1, 6, + * or 11. This causes that channel to be scanned alone + * in the next iteration. + */ + if (!filtered_scan && + (tmp_chan_list->chan_number == 1 || + tmp_chan_list->chan_number == 6 || + tmp_chan_list->chan_number == 11)) + done_early = true; + } + + /* Ensure the total scan time does not exceed the scan-command timeout. */ + if (total_scan_time > NXPWIFI_MAX_TOTAL_SCAN_TIME) { + nxpwifi_dbg(priv->adapter, ERROR, + "total scan time %dms\t" + "is over limit (%dms), scan skipped\n", + total_scan_time, + NXPWIFI_MAX_TOTAL_SCAN_TIME); + ret = -EINVAL; + break; + } + + rates_size = nxpwifi_append_rate_tlv(priv, scan_cfg_out, + radio_type); + + if (priv->adapter->ext_scan) + cmd_no = HOST_CMD_802_11_SCAN_EXT; + else + cmd_no = HOST_CMD_802_11_SCAN; + + ret = nxpwifi_send_cmd(priv, cmd_no, HOST_ACT_GEN_SET, + 0, scan_cfg_out, false); + + /* + * The rate element is updated for each scan command, but the + * same starting pointer is reused, so the previous rate element + * in scan_cfg_out->buf is overwritten. + */ + scan_cfg_out->tlv_buf_len -= + sizeof(struct nxpwifi_ie_types_header) + rates_size; + + if (ret) { + nxpwifi_cancel_pending_scan_cmd(adapter); + break; + } + } + + return ret; +} + +/* + * Build final scan config from user params, disabling missing filters and using + * defaults. + */ +static void +nxpwifi_config_scan(struct nxpwifi_private *priv, + const struct nxpwifi_user_scan_cfg *user_scan_in, + struct nxpwifi_scan_cmd_config *scan_cfg_out, + struct nxpwifi_ie_types_chan_list_param_set **chan_list_out, + struct nxpwifi_chan_scan_param_set *scan_chan_list, + u8 *max_chan_per_scan, u8 *filtered_scan, + u8 *scan_current_only) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ie_types_num_probes *num_probes_tlv; + struct nxpwifi_ie_types_scan_chan_gap *chan_gap_tlv; + struct nxpwifi_ie_types_random_mac *random_mac_tlv; + struct nxpwifi_ie_types_wildcard_ssid_params *wildcard_ssid_tlv; + struct nxpwifi_ie_types_bssid_list *bssid_tlv; + struct nxpwifi_ie_types_extcap *ext_cap; + u8 *ext_capab = NULL; + u8 *tlv_pos; + u32 num_probes; + u32 ssid_len; + u32 chan_idx; + u32 scan_time; + u32 scan_type; + u16 scan_dur; + u8 channel; + u8 radio_type; + int i, vsid; + u8 ssid_filter; + struct nxpwifi_ie_types_htcap *ht_cap; + struct nxpwifi_ie_types_bss_mode *bss_mode; + struct nxpwifi_ie_types_vhtcap *vht_cap; + struct nxpwifi_ie_types_he_cap *he_cap; + + /* + * tlv_buf_len is recalculated for each scan command. TLVs added in this + * routine are preserved because the send routine appends channel TLVs + * at chan_list_out. The difference between chan_list_out and the start + * of the TLV buffer determines the size of the TLVs added here. + */ + scan_cfg_out->tlv_buf_len = 0; + + /* + * Running TLV pointer. It is assigned to chan_list_out at the end of + * the function so later routines know where channel TLVs can be + * appended in the command buffer. + */ + tlv_pos = scan_cfg_out->tlv_buf; + + /* + * Initialize the scan as un-filtered; the flag is later set to TRUE + * below if a SSID or BSSID filter is sent in the command + */ + *filtered_scan = false; + + /* + * Initialize the scan as not being only on the current channel. If + * the channel list is customized, only contains one channel, and is + * the active channel, this is set true and data flow is not halted. + */ + *scan_current_only = false; + + if (user_scan_in) { + u8 tmpaddr[ETH_ALEN]; + + /* + * Default the ssid_filter flag to TRUE, set false under + * certain wildcard conditions and qualified by the existence + * of an SSID list before marking the scan as filtered + */ + ssid_filter = true; + + /* + * Set the BSS type scan filter, use Adapter setting if + * unset + */ + scan_cfg_out->bss_mode = + (u8)(user_scan_in->bss_mode ?: adapter->scan_mode); + + /* + * Set the number of probes to send, use Adapter setting + * if unset + */ + num_probes = user_scan_in->num_probes ?: adapter->scan_probes; + + /* + * Set the BSSID filter to the incoming configuration, + * if non-zero. If not set, it will remain disabled + * (all zeros). + */ + memcpy(scan_cfg_out->specific_bssid, + user_scan_in->specific_bssid, + sizeof(scan_cfg_out->specific_bssid)); + + memcpy(tmpaddr, scan_cfg_out->specific_bssid, ETH_ALEN); + + if (adapter->ext_scan && + !is_zero_ether_addr(tmpaddr)) { + bssid_tlv = + (struct nxpwifi_ie_types_bssid_list *)tlv_pos; + bssid_tlv->header.type = cpu_to_le16(TLV_TYPE_BSSID); + bssid_tlv->header.len = cpu_to_le16(ETH_ALEN); + memcpy(bssid_tlv->bssid, user_scan_in->specific_bssid, + ETH_ALEN); + tlv_pos += sizeof(struct nxpwifi_ie_types_bssid_list); + } + + for (i = 0; i < user_scan_in->num_ssids; i++) { + ssid_len = user_scan_in->ssid_list[i].ssid_len; + + wildcard_ssid_tlv = + (struct nxpwifi_ie_types_wildcard_ssid_params *) + tlv_pos; + wildcard_ssid_tlv->header.type = + cpu_to_le16(TLV_TYPE_WILDCARDSSID); + wildcard_ssid_tlv->header.len = + cpu_to_le16((u16)(ssid_len + sizeof(u8))); + + /* + * max_ssid_length = 0 tells firmware to perform + * specific scan for the SSID filled, whereas + * max_ssid_length = IEEE80211_MAX_SSID_LEN is for + * wildcard scan. + */ + if (ssid_len) + wildcard_ssid_tlv->max_ssid_length = 0; + else + wildcard_ssid_tlv->max_ssid_length = + IEEE80211_MAX_SSID_LEN; + + if (!memcmp(user_scan_in->ssid_list[i].ssid, + "DIRECT-", 7)) + wildcard_ssid_tlv->max_ssid_length = 0xfe; + + memcpy(wildcard_ssid_tlv->ssid, + user_scan_in->ssid_list[i].ssid, ssid_len); + + tlv_pos += (sizeof(wildcard_ssid_tlv->header) + + le16_to_cpu(wildcard_ssid_tlv->header.len)); + + nxpwifi_dbg(adapter, INFO, + "info: scan: ssid[%d]: %s, %d\n", + i, wildcard_ssid_tlv->ssid, + wildcard_ssid_tlv->max_ssid_length); + + /* + * Empty wildcard ssid with a maxlen will match many or + * potentially all SSIDs (maxlen == 32), therefore do + * not treat the scan as + * filtered. + */ + if (!ssid_len && wildcard_ssid_tlv->max_ssid_length) + ssid_filter = false; + } + + /* + * The default number of channels sent in the command is low to + * ensure the response buffer from the firmware does not + * truncate scan results. That is not an issue with an SSID + * or BSSID filter applied to the scan results in the firmware. + */ + memcpy(tmpaddr, scan_cfg_out->specific_bssid, ETH_ALEN); + if ((i && ssid_filter) || + !is_zero_ether_addr(tmpaddr)) + *filtered_scan = true; + + if (user_scan_in->scan_chan_gap) { + nxpwifi_dbg(adapter, INFO, + "info: scan: channel gap = %d\n", + user_scan_in->scan_chan_gap); + *max_chan_per_scan = + NXPWIFI_MAX_CHANNELS_PER_SPECIFIC_SCAN; + + chan_gap_tlv = (void *)tlv_pos; + chan_gap_tlv->header.type = + cpu_to_le16(TLV_TYPE_SCAN_CHANNEL_GAP); + chan_gap_tlv->header.len = + cpu_to_le16(sizeof(chan_gap_tlv->chan_gap)); + chan_gap_tlv->chan_gap = + cpu_to_le16((user_scan_in->scan_chan_gap)); + tlv_pos += + sizeof(struct nxpwifi_ie_types_scan_chan_gap); + } + + if (!is_zero_ether_addr(user_scan_in->random_mac)) { + random_mac_tlv = (void *)tlv_pos; + random_mac_tlv->header.type = + cpu_to_le16(TLV_TYPE_RANDOM_MAC); + random_mac_tlv->header.len = + cpu_to_le16(sizeof(random_mac_tlv->mac)); + ether_addr_copy(random_mac_tlv->mac, + user_scan_in->random_mac); + tlv_pos += + sizeof(struct nxpwifi_ie_types_random_mac); + } + } else { + scan_cfg_out->bss_mode = (u8)adapter->scan_mode; + num_probes = adapter->scan_probes; + } + + /* + * If a specific BSSID or SSID is used, the number of channels in the + * scan command will be increased to the absolute maximum. + */ + if (*filtered_scan) { + *max_chan_per_scan = NXPWIFI_MAX_CHANNELS_PER_SPECIFIC_SCAN; + } else { + if (!priv->media_connected) + *max_chan_per_scan = NXPWIFI_DEF_CHANNELS_PER_SCAN_CMD; + else + *max_chan_per_scan = + NXPWIFI_DEF_CHANNELS_PER_SCAN_CMD / 2; + } + + if (adapter->ext_scan) { + bss_mode = (struct nxpwifi_ie_types_bss_mode *)tlv_pos; + bss_mode->header.type = cpu_to_le16(TLV_TYPE_BSS_MODE); + bss_mode->header.len = cpu_to_le16(sizeof(bss_mode->bss_mode)); + bss_mode->bss_mode = scan_cfg_out->bss_mode; + tlv_pos += sizeof(bss_mode->header) + + le16_to_cpu(bss_mode->header.len); + } + + /* + * If the input config or adapter has the number of Probes set, + * add tlv + */ + if (num_probes) { + nxpwifi_dbg(adapter, INFO, + "info: scan: num_probes = %d\n", + num_probes); + + num_probes_tlv = (struct nxpwifi_ie_types_num_probes *)tlv_pos; + num_probes_tlv->header.type = cpu_to_le16(TLV_TYPE_NUMPROBES); + num_probes_tlv->header.len = + cpu_to_le16(sizeof(num_probes_tlv->num_probes)); + num_probes_tlv->num_probes = cpu_to_le16((u16)num_probes); + + tlv_pos += sizeof(num_probes_tlv->header) + + le16_to_cpu(num_probes_tlv->header.len); + } + + if (ISSUPP_11NENABLED(priv->adapter->fw_cap_info) && + (priv->config_bands & BAND_GN || + priv->config_bands & BAND_AN)) { + ht_cap = (struct nxpwifi_ie_types_htcap *)tlv_pos; + memset(ht_cap, 0, sizeof(struct nxpwifi_ie_types_htcap)); + ht_cap->header.type = cpu_to_le16(WLAN_EID_HT_CAPABILITY); + ht_cap->header.len = + cpu_to_le16(sizeof(struct ieee80211_ht_cap)); + radio_type = + nxpwifi_band_to_radio_type(priv->config_bands); + nxpwifi_fill_cap_info(priv, radio_type, &ht_cap->ht_cap); + tlv_pos += sizeof(struct nxpwifi_ie_types_htcap); + } + + if (ISSUPP_11ACENABLED(adapter->fw_cap_info) && + (priv->config_bands & BAND_AAC)) { + vht_cap = (struct nxpwifi_ie_types_vhtcap *)tlv_pos; + memset(vht_cap, 0, sizeof(struct nxpwifi_ie_types_vhtcap)); + vht_cap->header.type = cpu_to_le16(WLAN_EID_VHT_CAPABILITY); + vht_cap->header.len = cpu_to_le16(sizeof(struct ieee80211_vht_cap)); + nxpwifi_fill_vht_cap_tlv(priv, &vht_cap->vht_cap, priv->config_bands); + tlv_pos += sizeof(*vht_cap); + } + + if (ISSUPP_11AXENABLED(adapter->fw_cap_ext) && + (priv->config_bands & BAND_GAX || + priv->config_bands & BAND_AAX)) { + he_cap = (struct nxpwifi_ie_types_he_cap *)tlv_pos; + memset(he_cap, 0, sizeof(struct nxpwifi_ie_types_he_cap)); + tlv_pos += nxpwifi_fill_he_cap_tlv(priv, he_cap, priv->config_bands); + } + + if (nxpwifi_is_sta_11ax_twt_req_supported(priv)) { + for (vsid = 0; vsid < NXPWIFI_MAX_VSIE_NUM; vsid++) { + if (priv->vs_ie[vsid].mask & NXPWIFI_VSIE_MASK_SCAN) { + ext_capab = (u8 *)cfg80211_find_ie(WLAN_EID_EXT_CAPABILITY, + priv->vs_ie[vsid].ie, + sizeof(priv->vs_ie[vsid].ie)); + break; + } + } + + if (ext_capab) { + ext_capab += 2; + } else { + ext_cap = (struct nxpwifi_ie_types_extcap *)tlv_pos; + memset(ext_cap, 0, sizeof(struct nxpwifi_ie_types_extcap) + + NXPWIFI_EXT_CAPAB_IE_LEN); + ext_cap->header.type = cpu_to_le16(WLAN_EID_EXT_CAPABILITY); + ext_cap->header.len = cpu_to_le16(NXPWIFI_EXT_CAPAB_IE_LEN); + ext_capab = ext_cap->ext_capab; + tlv_pos += sizeof(struct nxpwifi_ie_types_extcap) + + le16_to_cpu(ext_cap->header.len); + } + + ext_capab[9] |= WLAN_EXT_CAPA10_TWT_REQUESTER_SUPPORT; + } + + /* Append vendor specific element TLV */ + nxpwifi_cmd_append_vsie_tlv(priv, NXPWIFI_VSIE_MASK_SCAN, &tlv_pos); + + /* + * Set the channel TLV output pointer to the end of the newly added TLVs + * (SSID, num_probes). Channel TLVs for each scan will be appended after + * these, preserving previously added TLVs. + */ + *chan_list_out = + (struct nxpwifi_ie_types_chan_list_param_set *)tlv_pos; + + if (user_scan_in && user_scan_in->chan_list[0].chan_number) { + nxpwifi_dbg(adapter, INFO, + "info: Scan: Using supplied channel list\n"); + + for (chan_idx = 0; + chan_idx < NXPWIFI_USER_SCAN_CHAN_MAX && + user_scan_in->chan_list[chan_idx].chan_number; + chan_idx++) { + channel = user_scan_in->chan_list[chan_idx].chan_number; + scan_chan_list[chan_idx].chan_number = channel; + + radio_type = + user_scan_in->chan_list[chan_idx].radio_type; + scan_chan_list[chan_idx].band_cfg = radio_type; + + scan_type = user_scan_in->chan_list[chan_idx].scan_type; + + if (scan_type == NXPWIFI_SCAN_TYPE_PASSIVE) + scan_chan_list[chan_idx].chan_scan_mode_bmap |= + (NXPWIFI_PASSIVE_SCAN | + NXPWIFI_HIDDEN_SSID_REPORT); + else + scan_chan_list[chan_idx].chan_scan_mode_bmap &= + ~NXPWIFI_PASSIVE_SCAN; + + scan_chan_list[chan_idx].chan_scan_mode_bmap |= + NXPWIFI_DISABLE_CHAN_FILT; + + scan_time = user_scan_in->chan_list[chan_idx].scan_time; + + if (scan_time) { + scan_dur = (u16)scan_time; + } else { + if (scan_type == NXPWIFI_SCAN_TYPE_PASSIVE) + scan_dur = adapter->passive_scan_time; + else if (*filtered_scan) + scan_dur = adapter->specific_scan_time; + else + scan_dur = adapter->active_scan_time; + } + + scan_chan_list[chan_idx].min_scan_time = + cpu_to_le16(scan_dur); + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(scan_dur); + } + + /* Check if we are only scanning the current channel */ + if (chan_idx == 1 && + user_scan_in->chan_list[0].chan_number == + priv->curr_bss_params.bss_descriptor.channel) { + *scan_current_only = true; + nxpwifi_dbg(adapter, INFO, + "info: Scan: Scanning current channel only\n"); + } + } else { + nxpwifi_dbg(adapter, INFO, + "info: Scan: Creating full region channel list\n"); + nxpwifi_scan_create_channel_list(priv, user_scan_in, + scan_chan_list, + *filtered_scan); + } +} + +/* Parse the beacon buffer and update the BSS descriptor fields. */ +int nxpwifi_update_bss_desc_with_ie(struct nxpwifi_adapter *adapter, + struct nxpwifi_bssdescriptor *bss_entry) +{ + u8 element_id; + u16 elem_size = sizeof(struct element); + struct ieee_types_fh_param_set *fh_param_set; + struct ieee_types_ds_param_set *ds_param_set; + struct ieee_types_cf_param_set *cf_param_set; + u8 *current_ptr; + u8 *rate; + u8 element_len; + u16 total_ie_len; + u8 bytes_to_copy; + u8 rate_size; + u8 found_data_rate_ie; + u32 bytes_left; + struct ieee_types_vendor_specific *vendor_ie; + const u8 wpa_oui[4] = { 0x00, 0x50, 0xf2, 0x01 }; + const u8 wmm_oui[4] = { 0x00, 0x50, 0xf2, 0x02 }; + struct element *elem; + + found_data_rate_ie = false; + rate_size = 0; + current_ptr = bss_entry->beacon_buf; + bytes_left = bss_entry->beacon_buf_size; + + /* Process variable element */ + while (bytes_left >= 2) { + element_id = *current_ptr; + element_len = *(current_ptr + 1); + total_ie_len = element_len + elem_size; + + if (bytes_left < total_ie_len) { + nxpwifi_dbg(adapter, ERROR, + "err: InterpretIE: in processing\t" + "element, bytes left < element length\n"); + return -EINVAL; + } + switch (element_id) { + case WLAN_EID_SSID: + if (element_len > IEEE80211_MAX_SSID_LEN) + return -EINVAL; + bss_entry->ssid.ssid_len = element_len; + memcpy(bss_entry->ssid.ssid, (current_ptr + 2), + element_len); + nxpwifi_dbg(adapter, INFO, + "info: InterpretIE: ssid: %-32s\n", + bss_entry->ssid.ssid); + break; + + case WLAN_EID_SUPP_RATES: + if (element_len > NXPWIFI_SUPPORTED_RATES) + return -EINVAL; + memcpy(bss_entry->data_rates, current_ptr + 2, + element_len); + memcpy(bss_entry->supported_rates, current_ptr + 2, + element_len); + rate_size = element_len; + found_data_rate_ie = true; + break; + + case WLAN_EID_FH_PARAMS: + if (total_ie_len < sizeof(*fh_param_set)) + return -EINVAL; + fh_param_set = + (struct ieee_types_fh_param_set *)current_ptr; + memcpy(&bss_entry->phy_param_set.fh_param_set, + fh_param_set, + sizeof(struct ieee_types_fh_param_set)); + break; + + case WLAN_EID_DS_PARAMS: + if (total_ie_len < sizeof(*ds_param_set)) + return -EINVAL; + ds_param_set = + (struct ieee_types_ds_param_set *)current_ptr; + + bss_entry->channel = ds_param_set->current_chan; + + memcpy(&bss_entry->phy_param_set.ds_param_set, + ds_param_set, + sizeof(struct ieee_types_ds_param_set)); + break; + + case WLAN_EID_CF_PARAMS: + if (total_ie_len < sizeof(*cf_param_set)) + return -EINVAL; + cf_param_set = + (struct ieee_types_cf_param_set *)current_ptr; + memcpy(&bss_entry->cf_param_set, + cf_param_set, + sizeof(struct ieee_types_cf_param_set)); + break; + + case WLAN_EID_ERP_INFO: + if (!element_len) + return -EINVAL; + bss_entry->erp_flags = *(current_ptr + 2); + break; + + case WLAN_EID_PWR_CONSTRAINT: + if (!element_len) + return -EINVAL; + bss_entry->local_constraint = *(current_ptr + 2); + bss_entry->sensed_11h = true; + break; + + case WLAN_EID_CHANNEL_SWITCH: + bss_entry->chan_sw_ie_present = true; + fallthrough; + case WLAN_EID_PWR_CAPABILITY: + case WLAN_EID_TPC_REPORT: + case WLAN_EID_QUIET: + bss_entry->sensed_11h = true; + break; + + case WLAN_EID_EXT_SUPP_RATES: + /* + * Only process extended supported rate + * if data rate is already found. + * Data rate element should come before + * extended supported rate element + */ + if (found_data_rate_ie) { + if ((element_len + rate_size) > + NXPWIFI_SUPPORTED_RATES) + bytes_to_copy = + (NXPWIFI_SUPPORTED_RATES - + rate_size); + else + bytes_to_copy = element_len; + + rate = (u8 *)bss_entry->data_rates; + rate += rate_size; + memcpy(rate, current_ptr + 2, bytes_to_copy); + + rate = (u8 *)bss_entry->supported_rates; + rate += rate_size; + memcpy(rate, current_ptr + 2, bytes_to_copy); + } + break; + + case WLAN_EID_VENDOR_SPECIFIC: + vendor_ie = (struct ieee_types_vendor_specific *) + current_ptr; + + /* 802.11 requires at least 3-byte OUI. */ + if (element_len < sizeof(vendor_ie->vend_hdr.oui)) + return -EINVAL; + + /* Not long enough for a match? Skip it. */ + if (element_len < sizeof(wpa_oui)) + break; + + if (!memcmp(&vendor_ie->vend_hdr.oui, wpa_oui, + sizeof(wpa_oui))) { + bss_entry->bcn_wpa_ie = + (struct ieee_types_vendor_specific *) + current_ptr; + bss_entry->wpa_offset = + (u16)(current_ptr - + bss_entry->beacon_buf); + } else if (!memcmp(&vendor_ie->vend_hdr.oui, wmm_oui, + sizeof(wmm_oui))) { + if (total_ie_len == + sizeof(struct ieee80211_wmm_param_ie) || + total_ie_len == + sizeof(struct ieee_types_wmm_info)) + /* + * Only accept and copy the WMM element if + * it matches the size expected for the + * WMM Info element or the WMM Parameter element. + */ + memcpy((u8 *)&bss_entry->wmm_ie, + current_ptr, total_ie_len); + } + break; + case WLAN_EID_RSN: + bss_entry->bcn_rsn_ie = + (struct element *)current_ptr; + bss_entry->rsn_offset = + (u16)(current_ptr - bss_entry->beacon_buf); + break; + case WLAN_EID_RSNX: + bss_entry->bcn_rsnx_ie = + (struct element *)current_ptr; + bss_entry->rsnx_offset = + (u16)(current_ptr - bss_entry->beacon_buf); + break; + case WLAN_EID_HT_CAPABILITY: + bss_entry->bcn_ht_cap = + (struct ieee80211_ht_cap *)(current_ptr + + elem_size); + bss_entry->ht_cap_offset = + (u16)(current_ptr + elem_size - + bss_entry->beacon_buf); + break; + case WLAN_EID_HT_OPERATION: + bss_entry->bcn_ht_oper = + (struct ieee80211_ht_operation *)(current_ptr + + elem_size); + bss_entry->ht_info_offset = + (u16)(current_ptr + elem_size - + bss_entry->beacon_buf); + break; + case WLAN_EID_VHT_CAPABILITY: + bss_entry->disable_11ac = false; + bss_entry->bcn_vht_cap = (void *)(current_ptr + + elem_size); + bss_entry->vht_cap_offset = + (u16)((u8 *)bss_entry->bcn_vht_cap - + bss_entry->beacon_buf); + break; + case WLAN_EID_VHT_OPERATION: + bss_entry->bcn_vht_oper = + (void *)(current_ptr + elem_size); + bss_entry->vht_info_offset = + (u16)((u8 *)bss_entry->bcn_vht_oper - + bss_entry->beacon_buf); + break; + case WLAN_EID_BSS_COEX_2040: + bss_entry->bcn_bss_co_2040 = current_ptr; + bss_entry->bss_co_2040_offset = + (u16)(current_ptr - bss_entry->beacon_buf); + break; + case WLAN_EID_EXT_CAPABILITY: + bss_entry->bcn_ext_cap = current_ptr; + bss_entry->ext_cap_offset = + (u16)(current_ptr - bss_entry->beacon_buf); + break; + case WLAN_EID_OPMODE_NOTIF: + bss_entry->oper_mode = (void *)current_ptr; + bss_entry->oper_mode_offset = + (u16)(current_ptr - bss_entry->beacon_buf); + break; + case WLAN_EID_EXTENSION: + elem = (struct element *)current_ptr; + + switch (elem->data[0]) { + case WLAN_EID_EXT_HE_CAPABILITY: + bss_entry->disable_11ax = false; + bss_entry->bcn_he_cap = + (void *)(current_ptr + elem_size + 1); + bss_entry->he_cap_offset = + (u16)((u8 *)bss_entry->bcn_he_cap - + bss_entry->beacon_buf); + break; + case WLAN_EID_EXT_HE_OPERATION: + bss_entry->bcn_he_oper = + (void *)(current_ptr + elem_size + 1); + bss_entry->he_info_offset = + (u16)((u8 *)bss_entry->bcn_he_oper - + bss_entry->beacon_buf); + break; + default: + break; + } + break; + default: + break; + } + + current_ptr += total_ie_len; + bytes_left -= total_ie_len; + + } /* while (bytes_left > 2) */ + return 0; +} + +/* Convert the radio-type scan parameter to the join command's band config. */ +static u8 +nxpwifi_radio_type_to_band(u8 radio_type) +{ + switch (radio_type) { + case HOST_SCAN_RADIO_TYPE_A: + return BAND_A; + case HOST_SCAN_RADIO_TYPE_BG: + default: + return BAND_G; + } +} + +/* Internal helper to start a scan using the given configuration. */ +int nxpwifi_scan_networks(struct nxpwifi_private *priv, + const struct nxpwifi_user_scan_cfg *user_scan_in) +{ + int ret; + struct nxpwifi_adapter *adapter = priv->adapter; + struct cmd_ctrl_node *cmd_node; + union nxpwifi_scan_cmd_config_tlv *scan_cfg_out; + struct nxpwifi_ie_types_chan_list_param_set *chan_list_out; + struct nxpwifi_chan_scan_param_set *scan_chan_list; + u8 filtered_scan; + u8 scan_current_chan_only; + u8 max_chan_per_scan; + + if (adapter->scan_processing) { + nxpwifi_dbg(adapter, WARN, + "cmd: Scan already in process...\n"); + return -EBUSY; + } + + if (priv->scan_block) { + nxpwifi_dbg(adapter, WARN, + "cmd: Scan is blocked during association...\n"); + return -EBUSY; + } + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags) || + test_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags)) { + nxpwifi_dbg(adapter, ERROR, + "Ignore scan. Card removed or firmware in bad state\n"); + return -EPERM; + } + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->scan_processing = true; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + scan_cfg_out = kzalloc_obj(union nxpwifi_scan_cmd_config_tlv, + GFP_KERNEL); + if (!scan_cfg_out) { + ret = -ENOMEM; + goto done; + } + + scan_chan_list = kzalloc_objs(struct nxpwifi_chan_scan_param_set, + NXPWIFI_USER_SCAN_CHAN_MAX, GFP_KERNEL); + if (!scan_chan_list) { + kfree(scan_cfg_out); + ret = -ENOMEM; + goto done; + } + + nxpwifi_config_scan(priv, user_scan_in, &scan_cfg_out->config, + &chan_list_out, scan_chan_list, &max_chan_per_scan, + &filtered_scan, &scan_current_chan_only); + + ret = nxpwifi_scan_channel_list(priv, max_chan_per_scan, filtered_scan, + &scan_cfg_out->config, chan_list_out, + scan_chan_list); + + /* Get scan command from scan_pending_q and put to cmd_pending_q */ + if (!ret) { + spin_lock_bh(&adapter->scan_pending_q_lock); + if (!list_empty(&adapter->scan_pending_q)) { + cmd_node = list_first_entry(&adapter->scan_pending_q, + struct cmd_ctrl_node, list); + list_del(&cmd_node->list); + spin_unlock_bh(&adapter->scan_pending_q_lock); + nxpwifi_insert_cmd_to_pending_q(adapter, cmd_node); + nxpwifi_queue_work(adapter, &adapter->main_work); + + /* Perform internal scan synchronously */ + if (!priv->scan_request) { + nxpwifi_dbg(adapter, INFO, + "wait internal scan\n"); + nxpwifi_wait_queue_complete(adapter, cmd_node); + } + } else { + spin_unlock_bh(&adapter->scan_pending_q_lock); + } + } + + kfree(scan_cfg_out); + kfree(scan_chan_list); +done: + if (ret) { + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->scan_processing = false; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + } + return ret; +} + +/* + * Build the firmware scan command from the given configuration, including + * fixed fields and TLVs, and set the command ID, size, and endianness. + */ +int nxpwifi_cmd_802_11_scan(struct host_cmd_ds_command *cmd, + struct nxpwifi_scan_cmd_config *scan_cfg) +{ + struct host_cmd_ds_802_11_scan *scan_cmd = &cmd->params.scan; + + /* Set fixed field variables in scan command */ + scan_cmd->bss_mode = scan_cfg->bss_mode; + memcpy(scan_cmd->bssid, scan_cfg->specific_bssid, + sizeof(scan_cmd->bssid)); + memcpy(scan_cmd->tlv_buffer, scan_cfg->tlv_buf, scan_cfg->tlv_buf_len); + + cmd->command = cpu_to_le16(HOST_CMD_802_11_SCAN); + + /* Size is equal to the sizeof(fixed portions) + the TLV len + header */ + cmd->size = cpu_to_le16((u16)(sizeof(scan_cmd->bss_mode) + + sizeof(scan_cmd->bssid) + + scan_cfg->tlv_buf_len + S_DS_GEN)); + + return 0; +} + +/* Check compatibility of the requested network with current driver settings. */ +int nxpwifi_check_network_compatibility(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + int ret = 0; + + if (!bss_desc) + return -EINVAL; + + if ((nxpwifi_get_cfp(priv, (u8)bss_desc->bss_band, + (u16)bss_desc->channel, 0))) { + switch (priv->bss_mode) { + case NL80211_IFTYPE_STATION: + ret = nxpwifi_is_network_compatible(priv, bss_desc, + priv->bss_mode); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "Incompatible network settings\n"); + break; + default: + ret = 0; + } + } + + return ret; +} + +/* Check if the SSID length is zero or all bytes are zero. */ +static bool nxpwifi_is_hidden_ssid(struct cfg80211_ssid *ssid) +{ + int idx; + + for (idx = 0; idx < ssid->ssid_len; idx++) { + if (ssid->ssid[idx]) + return false; + } + + return true; +} + +/* Find hidden SSIDs on passive channels and save those channels for active scan. */ +static int nxpwifi_save_hidden_ssid_channels(struct nxpwifi_private *priv, + struct cfg80211_bss *bss) +{ + struct nxpwifi_bssdescriptor *bss_desc; + int ret; + int chid; + + /* Allocate and fill new bss descriptor */ + bss_desc = kzalloc_obj(*bss_desc, GFP_KERNEL); + if (!bss_desc) + return -ENOMEM; + + ret = nxpwifi_fill_new_bss_desc(priv, bss, bss_desc); + if (ret) + goto done; + + if (nxpwifi_is_hidden_ssid(&bss_desc->ssid)) { + nxpwifi_dbg(priv->adapter, INFO, "found hidden SSID\n"); + for (chid = 0 ; chid < NXPWIFI_USER_SCAN_CHAN_MAX; chid++) { + if (priv->hidden_chan[chid].chan_number == + bss->channel->hw_value) + break; + + if (!priv->hidden_chan[chid].chan_number) { + priv->hidden_chan[chid].chan_number = + bss->channel->hw_value; + priv->hidden_chan[chid].radio_type = + bss->channel->band; + priv->hidden_chan[chid].scan_type = + NXPWIFI_SCAN_TYPE_ACTIVE; + break; + } + } + } + +done: + /* Free beacon_ie allocated by nxpwifi_fill_new_bss_desc(). */ + kfree(bss_desc->beacon_buf); + kfree(bss_desc); + return ret; +} + +static int nxpwifi_update_curr_bss_params(struct nxpwifi_private *priv, + struct cfg80211_bss *bss) +{ + struct nxpwifi_bssdescriptor *bss_desc; + int ret; + + /* Allocate and fill new bss descriptor */ + bss_desc = kzalloc_obj(*bss_desc, GFP_KERNEL); + if (!bss_desc) + return -ENOMEM; + + ret = nxpwifi_fill_new_bss_desc(priv, bss, bss_desc); + if (ret) + goto done; + + ret = nxpwifi_check_network_compatibility(priv, bss_desc); + if (ret) + goto done; + + spin_lock_bh(&priv->curr_bcn_buf_lock); + /* Make a copy of current BSSID descriptor */ + memcpy(&priv->curr_bss_params.bss_descriptor, bss_desc, + sizeof(priv->curr_bss_params.bss_descriptor)); + + /* beacon_ie will be copied to its own buffer in nxpwifi_save_curr_bcn(). */ + nxpwifi_save_curr_bcn(priv); + spin_unlock_bh(&priv->curr_bcn_buf_lock); + +done: + /* Free beacon_ie allocated by nxpwifi_fill_new_bss_desc(). */ + kfree(bss_desc->beacon_buf); + kfree(bss_desc); + return ret; +} + +static int +nxpwifi_parse_single_response_buf(struct nxpwifi_private *priv, u8 **bss_info, + u32 *bytes_left, u64 fw_tsf, const u8 *radio_type, + bool ext_scan, s32 rssi_val) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_chan_freq_power *cfp; + struct cfg80211_bss *bss; + u8 bssid[ETH_ALEN]; + s32 rssi; + const u8 *ie_buf; + size_t ie_len; + u16 channel = 0; + u16 beacon_size = 0; + u32 curr_bcn_bytes; + u32 freq; + u16 beacon_period; + u16 cap_info_bitmap; + u8 *current_ptr; + u64 timestamp; + struct nxpwifi_fixed_bcn_param *bcn_param; + struct nxpwifi_bss_priv *bss_priv; + + if (*bytes_left >= sizeof(beacon_size)) { + /* Extract & convert beacon size from command buffer */ + beacon_size = get_unaligned_le16((*bss_info)); + *bytes_left -= sizeof(beacon_size); + *bss_info += sizeof(beacon_size); + } + + if (!beacon_size || beacon_size > *bytes_left) { + *bss_info += *bytes_left; + *bytes_left = 0; + return -EINVAL; + } + + /* + * Initialize the current working beacon pointer for this BSS + * iteration + */ + current_ptr = *bss_info; + + /* Advance the return beacon pointer past the current beacon */ + *bss_info += beacon_size; + *bytes_left -= beacon_size; + + curr_bcn_bytes = beacon_size; + + /* + * First 5 fields are bssid, RSSI(for legacy scan only), + * time stamp, beacon interval, and capability information + */ + if (curr_bcn_bytes < ETH_ALEN + sizeof(u8) + + sizeof(struct nxpwifi_fixed_bcn_param)) { + nxpwifi_dbg(adapter, ERROR, + "InterpretIE: not enough bytes left\n"); + return -EINVAL; + } + + memcpy(bssid, current_ptr, ETH_ALEN); + current_ptr += ETH_ALEN; + curr_bcn_bytes -= ETH_ALEN; + + if (!ext_scan) { + rssi = (s32)*current_ptr; + rssi = (-rssi) * 100; /* Convert dBm to mBm */ + current_ptr += sizeof(u8); + curr_bcn_bytes -= sizeof(u8); + nxpwifi_dbg(adapter, INFO, + "info: InterpretIE: RSSI=%d\n", rssi); + } else { + rssi = rssi_val; + } + + bcn_param = (struct nxpwifi_fixed_bcn_param *)current_ptr; + current_ptr += sizeof(*bcn_param); + curr_bcn_bytes -= sizeof(*bcn_param); + + timestamp = le64_to_cpu(bcn_param->timestamp); + beacon_period = le16_to_cpu(bcn_param->beacon_period); + + cap_info_bitmap = le16_to_cpu(bcn_param->cap_info_bitmap); + nxpwifi_dbg(adapter, INFO, + "info: InterpretIE: capabilities=0x%X\n", + cap_info_bitmap); + + /* Rest of the current buffer are element's */ + ie_buf = current_ptr; + ie_len = curr_bcn_bytes; + nxpwifi_dbg(adapter, INFO, + "info: InterpretIE: IELength for this AP = %d\n", + curr_bcn_bytes); + + while (curr_bcn_bytes >= sizeof(struct element)) { + u8 element_id, element_len; + + element_id = *current_ptr; + element_len = *(current_ptr + 1); + if (curr_bcn_bytes < element_len + + sizeof(struct element)) { + nxpwifi_dbg(adapter, ERROR, + "%s: bytes left < element length\n", __func__); + return -EFAULT; + } + if (element_id == WLAN_EID_DS_PARAMS) { + channel = *(current_ptr + + sizeof(struct element)); + break; + } + + current_ptr += element_len + sizeof(struct element); + curr_bcn_bytes -= element_len + + sizeof(struct element); + } + + if (channel) { + struct ieee80211_channel *chan; + struct nxpwifi_bssdescriptor *bss_desc; + u8 band; + + /* Skip entry if on csa closed channel */ + if (channel == priv->csa_chan) { + nxpwifi_dbg(adapter, WARN, + "Dropping entry on csa closed channel\n"); + return 0; + } + + band = BAND_G; + if (radio_type) + band = nxpwifi_radio_type_to_band(*radio_type & + (BIT(0) | BIT(1))); + + cfp = nxpwifi_get_cfp(priv, band, channel, 0); + + freq = cfp ? cfp->freq : 0; + + chan = ieee80211_get_channel(priv->wdev.wiphy, freq); + + if (chan && !(chan->flags & IEEE80211_CHAN_DISABLED)) { + bss = cfg80211_inform_bss(priv->wdev.wiphy, chan, + CFG80211_BSS_FTYPE_UNKNOWN, + bssid, timestamp, + cap_info_bitmap, + beacon_period, + ie_buf, ie_len, rssi, + GFP_ATOMIC); + if (bss) { + bss_priv = (struct nxpwifi_bss_priv *)bss->priv; + bss_priv->band = band; + bss_priv->fw_tsf = fw_tsf; + bss_desc = + &priv->curr_bss_params.bss_descriptor; + if (priv->media_connected && + !memcmp(bssid, bss_desc->mac_address, + ETH_ALEN)) + nxpwifi_update_curr_bss_params(priv, + bss); + + if ((chan->flags & IEEE80211_CHAN_RADAR) || + (chan->flags & IEEE80211_CHAN_NO_IR)) { + nxpwifi_dbg(adapter, INFO, + "radar or passive channel %d\n", + channel); + nxpwifi_save_hidden_ssid_channels(priv, + bss); + } + + cfg80211_put_bss(priv->wdev.wiphy, bss); + } + } + } else { + nxpwifi_dbg(adapter, WARN, "missing BSS channel element\n"); + } + + return 0; +} + +static void nxpwifi_complete_scan(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->survey_idx = 0; + if (adapter->curr_cmd->wait_q_enabled) { + adapter->cmd_wait_q.status = 0; + if (!priv->scan_request) { + nxpwifi_dbg(adapter, INFO, + "complete internal scan\n"); + nxpwifi_complete_cmd(adapter, adapter->curr_cmd); + } + } +} + +/* Find hidden SSIDs on passive channels and run active scans on them. */ +static int +nxpwifi_active_scan_req_for_passive_chan(struct nxpwifi_private *priv) +{ + int ret; + struct nxpwifi_adapter *adapter = priv->adapter; + u8 id = 0; + struct nxpwifi_user_scan_cfg *user_scan_cfg; + + if (adapter->active_scan_triggered || !priv->scan_request || + priv->scan_aborting) { + adapter->active_scan_triggered = false; + return 0; + } + + if (!priv->hidden_chan[0].chan_number) { + nxpwifi_dbg(adapter, INFO, "No BSS with hidden SSID found on DFS channels\n"); + return 0; + } + user_scan_cfg = kzalloc_obj(*user_scan_cfg, GFP_KERNEL); + + if (!user_scan_cfg) + return -ENOMEM; + + for (id = 0; id < NXPWIFI_USER_SCAN_CHAN_MAX; id++) { + if (!priv->hidden_chan[id].chan_number) + break; + memcpy(&user_scan_cfg->chan_list[id], + &priv->hidden_chan[id], + sizeof(struct nxpwifi_user_scan_chan)); + } + + adapter->active_scan_triggered = true; + if (priv->scan_request->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) + ether_addr_copy(user_scan_cfg->random_mac, + priv->scan_request->mac_addr); + user_scan_cfg->num_ssids = priv->scan_request->n_ssids; + user_scan_cfg->ssid_list = priv->scan_request->ssids; + + ret = nxpwifi_scan_networks(priv, user_scan_cfg); + kfree(user_scan_cfg); + + memset(&priv->hidden_chan, 0, sizeof(priv->hidden_chan)); + + if (ret) + nxpwifi_dbg(adapter, ERROR, "scan failed: %d\n", ret); + + return ret; +} + +static void nxpwifi_check_next_scan_command(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct cmd_ctrl_node *cmd_node; + + spin_lock_bh(&adapter->scan_pending_q_lock); + if (list_empty(&adapter->scan_pending_q)) { + spin_unlock_bh(&adapter->scan_pending_q_lock); + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->scan_processing = false; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + nxpwifi_active_scan_req_for_passive_chan(priv); + + if (!adapter->ext_scan) + nxpwifi_complete_scan(priv); + + if (priv->scan_request) { + struct cfg80211_scan_info info = { + .aborted = false, + }; + + nxpwifi_dbg(adapter, INFO, + "info: notifying scan done\n"); + cfg80211_scan_done(priv->scan_request, &info); + priv->scan_request = NULL; + priv->scan_aborting = false; + } else { + priv->scan_aborting = false; + nxpwifi_dbg(adapter, INFO, + "info: scan already aborted\n"); + } + } else if ((priv->scan_aborting && !priv->scan_request) || + priv->scan_block) { + spin_unlock_bh(&adapter->scan_pending_q_lock); + + nxpwifi_cancel_pending_scan_cmd(adapter); + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->scan_processing = false; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + if (!adapter->active_scan_triggered) { + if (priv->scan_request) { + struct cfg80211_scan_info info = { + .aborted = true, + }; + + nxpwifi_dbg(adapter, INFO, + "info: aborting scan\n"); + cfg80211_scan_done(priv->scan_request, &info); + priv->scan_request = NULL; + priv->scan_aborting = false; + } else { + priv->scan_aborting = false; + nxpwifi_dbg(adapter, INFO, + "info: scan already aborted\n"); + } + } + } else { + /* Move a scan command from scan_pending_q to cmd_pending_q. */ + cmd_node = list_first_entry(&adapter->scan_pending_q, + struct cmd_ctrl_node, list); + list_del(&cmd_node->list); + spin_unlock_bh(&adapter->scan_pending_q_lock); + nxpwifi_insert_cmd_to_pending_q(adapter, cmd_node); + } +} + +void nxpwifi_cancel_scan(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + int i; + + nxpwifi_cancel_pending_scan_cmd(adapter); + + if (adapter->scan_processing) { + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->scan_processing = false; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (priv->scan_request) { + struct cfg80211_scan_info info = { + .aborted = true, + }; + + nxpwifi_dbg(adapter, INFO, + "info: aborting scan\n"); + cfg80211_scan_done(priv->scan_request, &info); + priv->scan_request = NULL; + priv->scan_aborting = false; + } + } + } +} + +/* + * Handle the scan command response. + * + * The scan response buffer has the following layout: + * + * ------------------------------------------------------------- + * | Header (4 * t_u16): standard command response header | + * ------------------------------------------------------------- + * | BufSize (t_u16): size of the BSS description data | + * ------------------------------------------------------------- + * | NumOfSet (t_u8): number of returned BSS descriptions | + * ------------------------------------------------------------- + * | BSS description data (variable, size = BufSize) | + * ------------------------------------------------------------- + * | TLV data (variable, size = cmd_size - fixed fields) | + * ------------------------------------------------------------- + */ +int nxpwifi_ret_802_11_scan(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + int ret = 0; + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_scan_rsp *scan_rsp; + u8 *tlv_data; + const struct nxpwifi_ie_types_tsf_timestamp *tsf_tlv; + u8 *bss_info; + u32 scan_resp_size; + u32 bytes_left; + u32 idx; + u32 tlv_buf_size; + const struct nxpwifi_ie_types_chan_band_list_param_set *chan_band_tlv; + const struct chan_band_param_set *chan_band; + u8 is_bgscan_resp; + __le64 fw_tsf = 0; + const u8 *radio_type; + struct cfg80211_wowlan_nd_match *pmatch; + struct cfg80211_sched_scan_request *nd_config = NULL; + + is_bgscan_resp = (le16_to_cpu(resp->command) + == HOST_CMD_802_11_BG_SCAN_QUERY); + if (is_bgscan_resp) + scan_rsp = &resp->params.bg_scan_query_resp.scan_resp; + else + scan_rsp = &resp->params.scan_resp; + + if (scan_rsp->number_of_sets > NXPWIFI_MAX_AP) { + nxpwifi_dbg(adapter, ERROR, + "SCAN_RESP: too many AP returned (%d)\n", + scan_rsp->number_of_sets); + ret = -EINVAL; + goto check_next_scan; + } + + /* Check csa channel expiry before parsing scan response */ + nxpwifi_11h_get_csa_closed_channel(priv); + + bytes_left = le16_to_cpu(scan_rsp->bss_descript_size); + nxpwifi_dbg(adapter, INFO, + "info: SCAN_RESP: bss_descript_size %d\n", + bytes_left); + + scan_resp_size = le16_to_cpu(resp->size); + + nxpwifi_dbg(adapter, INFO, + "info: SCAN_RESP: returned %d APs before parsing\n", + scan_rsp->number_of_sets); + + bss_info = scan_rsp->bss_desc_and_tlv_buffer; + + /* + * TLV buffer size = scan_resp_size minus the fixed fields, BSS + * description data, and the command response header (S_DS_GEN). + */ + tlv_buf_size = scan_resp_size - (bytes_left + + sizeof(scan_rsp->bss_descript_size) + + sizeof(scan_rsp->number_of_sets) + + S_DS_GEN); + + tlv_data = (scan_rsp->bss_desc_and_tlv_buffer + + bytes_left); + + /* Find timestamp TLV */ + { + const struct nxpwifi_tlv *t; + + t = nxpwifi_find_tlv(TLV_TYPE_TSFTIMESTAMP, tlv_data, tlv_buf_size); + tsf_tlv = (const struct nxpwifi_ie_types_tsf_timestamp *)t; + } + + /* Find channel-band list TLV */ + { + const struct nxpwifi_tlv *t; + + t = nxpwifi_find_tlv(TLV_TYPE_CHANNELBANDLIST, tlv_data, + tlv_buf_size); + chan_band_tlv = + (const struct nxpwifi_ie_types_chan_band_list_param_set *)t; + } + +#ifdef CONFIG_PM + if (priv->wdev.wiphy->wowlan_config) + nd_config = priv->wdev.wiphy->wowlan_config->nd_config; +#endif + + if (nd_config) { + adapter->nd_info = + kzalloc_flex(*adapter->nd_info, matches, + scan_rsp->number_of_sets, GFP_ATOMIC); + + if (adapter->nd_info) + adapter->nd_info->n_matches = scan_rsp->number_of_sets; + } + + for (idx = 0; idx < scan_rsp->number_of_sets && bytes_left; idx++) { + /* + * If a TSF TLV is present, save its TSF value in fw_tsf. This + * is the firmware TSF at the time the beacon or probe response + * was received. + */ + if (tsf_tlv) + memcpy(&fw_tsf, &tsf_tlv->tsf_data[idx * TSF_DATA_SIZE], + sizeof(fw_tsf)); + + if (chan_band_tlv) { + chan_band = &chan_band_tlv->chan_band_param[idx]; + radio_type = &chan_band->radio_type; + } else { + radio_type = NULL; + } + + if (chan_band_tlv && adapter->nd_info) { + adapter->nd_info->matches[idx] = + kzalloc(sizeof(*pmatch) + sizeof(u32), + GFP_ATOMIC); + + pmatch = adapter->nd_info->matches[idx]; + + if (pmatch) { + pmatch->n_channels = 1; + pmatch->channels[0] = chan_band->chan_number; + } + } + + ret = nxpwifi_parse_single_response_buf(priv, &bss_info, + &bytes_left, + le64_to_cpu(fw_tsf), + radio_type, false, 0); + if (ret) + goto check_next_scan; + } + +check_next_scan: + nxpwifi_check_next_scan_command(priv); + return ret; +} + +/* + * Prepare the extended scan command using the provided scan configuration + * and build the structure to be sent to firmware. + */ +int nxpwifi_cmd_802_11_scan_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + void *data_buf) +{ + struct host_cmd_ds_802_11_scan_ext *ext_scan = &cmd->params.ext_scan; + struct nxpwifi_scan_cmd_config *scan_cfg = data_buf; + + memcpy(ext_scan->tlv_buffer, scan_cfg->tlv_buf, scan_cfg->tlv_buf_len); + + cmd->command = cpu_to_le16(HOST_CMD_802_11_SCAN_EXT); + + /* Size is equal to the sizeof(fixed portions) + the TLV len + header */ + cmd->size = cpu_to_le16((u16)(sizeof(ext_scan->reserved) + + scan_cfg->tlv_buf_len + S_DS_GEN)); + + return 0; +} + +/* Prepare the background scan config command to send to firmware. */ +int nxpwifi_cmd_802_11_bg_scan_config(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + void *data_buf) +{ + struct host_cmd_ds_802_11_bg_scan_config *bgscan_config = + &cmd->params.bg_scan_config; + struct nxpwifi_bg_scan_cfg *bgscan_cfg_in = data_buf; + u8 *tlv_pos = bgscan_config->tlv; + u8 num_probes; + u32 ssid_len, chan_idx, scan_time, scan_type, scan_dur, chan_num; + int i; + struct nxpwifi_ie_types_num_probes *num_probes_tlv; + struct nxpwifi_ie_types_repeat_count *repeat_count_tlv; + struct nxpwifi_ie_types_min_rssi_threshold *rssi_threshold_tlv; + struct nxpwifi_ie_types_bgscan_start_later *start_later_tlv; + struct nxpwifi_ie_types_wildcard_ssid_params *wildcard_ssid_tlv; + struct nxpwifi_ie_types_chan_list_param_set *tlv_l; + struct nxpwifi_chan_scan_param_set *temp_chan; + + cmd->command = cpu_to_le16(HOST_CMD_802_11_BG_SCAN_CONFIG); + cmd->size = cpu_to_le16(sizeof(*bgscan_config) + S_DS_GEN); + + bgscan_config->action = cpu_to_le16(bgscan_cfg_in->action); + bgscan_config->enable = bgscan_cfg_in->enable; + bgscan_config->bss_type = bgscan_cfg_in->bss_type; + bgscan_config->scan_interval = + cpu_to_le32(bgscan_cfg_in->scan_interval); + bgscan_config->report_condition = + cpu_to_le32(bgscan_cfg_in->report_condition); + + /* stop sched scan */ + if (!bgscan_config->enable) + return 0; + + bgscan_config->chan_per_scan = bgscan_cfg_in->chan_per_scan; + + num_probes = (bgscan_cfg_in->num_probes ? + bgscan_cfg_in->num_probes : priv->adapter->scan_probes); + + if (num_probes) { + num_probes_tlv = (struct nxpwifi_ie_types_num_probes *)tlv_pos; + num_probes_tlv->header.type = cpu_to_le16(TLV_TYPE_NUMPROBES); + num_probes_tlv->header.len = + cpu_to_le16(sizeof(num_probes_tlv->num_probes)); + num_probes_tlv->num_probes = cpu_to_le16((u16)num_probes); + + tlv_pos += sizeof(num_probes_tlv->header) + + le16_to_cpu(num_probes_tlv->header.len); + } + + if (bgscan_cfg_in->repeat_count) { + repeat_count_tlv = + (struct nxpwifi_ie_types_repeat_count *)tlv_pos; + repeat_count_tlv->header.type = + cpu_to_le16(TLV_TYPE_REPEAT_COUNT); + repeat_count_tlv->header.len = + cpu_to_le16(sizeof(repeat_count_tlv->repeat_count)); + repeat_count_tlv->repeat_count = + cpu_to_le16(bgscan_cfg_in->repeat_count); + + tlv_pos += sizeof(repeat_count_tlv->header) + + le16_to_cpu(repeat_count_tlv->header.len); + } + + if (bgscan_cfg_in->rssi_threshold) { + rssi_threshold_tlv = + (struct nxpwifi_ie_types_min_rssi_threshold *)tlv_pos; + rssi_threshold_tlv->header.type = + cpu_to_le16(TLV_TYPE_RSSI_LOW); + rssi_threshold_tlv->header.len = + cpu_to_le16(sizeof(rssi_threshold_tlv->rssi_threshold)); + rssi_threshold_tlv->rssi_threshold = + cpu_to_le16(bgscan_cfg_in->rssi_threshold); + + tlv_pos += sizeof(rssi_threshold_tlv->header) + + le16_to_cpu(rssi_threshold_tlv->header.len); + } + + for (i = 0; i < bgscan_cfg_in->num_ssids; i++) { + ssid_len = bgscan_cfg_in->ssid_list[i].ssid.ssid_len; + + wildcard_ssid_tlv = + (struct nxpwifi_ie_types_wildcard_ssid_params *)tlv_pos; + wildcard_ssid_tlv->header.type = + cpu_to_le16(TLV_TYPE_WILDCARDSSID); + wildcard_ssid_tlv->header.len = + cpu_to_le16((u16)(ssid_len + sizeof(u8))); + + /* + * max_ssid_length = 0 tells firmware to scan only for the given + * SSID. max_ssid_length = IEEE80211_MAX_SSID_LEN triggers a + * wildcard scan. + */ + if (ssid_len) + wildcard_ssid_tlv->max_ssid_length = 0; + else + wildcard_ssid_tlv->max_ssid_length = + IEEE80211_MAX_SSID_LEN; + + memcpy(wildcard_ssid_tlv->ssid, + bgscan_cfg_in->ssid_list[i].ssid.ssid, ssid_len); + + tlv_pos += (sizeof(wildcard_ssid_tlv->header) + + le16_to_cpu(wildcard_ssid_tlv->header.len)); + } + + tlv_l = (struct nxpwifi_ie_types_chan_list_param_set *)tlv_pos; + + if (bgscan_cfg_in->chan_list[0].chan_number) { + nxpwifi_dbg(priv->adapter, INFO, "info: bgscan: Using supplied channel list\n"); + + tlv_l->header.type = cpu_to_le16(TLV_TYPE_CHANLIST); + + for (chan_idx = 0; + chan_idx < NXPWIFI_BG_SCAN_CHAN_MAX && + bgscan_cfg_in->chan_list[chan_idx].chan_number; + chan_idx++) { + temp_chan = &tlv_l->chan_scan_param[chan_idx]; + + /* Increment the TLV header length by size appended */ + le16_unaligned_add_cpu(&tlv_l->header.len, + sizeof(*tlv_l->chan_scan_param)); + + temp_chan->chan_number = + bgscan_cfg_in->chan_list[chan_idx].chan_number; + temp_chan->band_cfg = + bgscan_cfg_in->chan_list[chan_idx].radio_type; + + scan_type = + bgscan_cfg_in->chan_list[chan_idx].scan_type; + + if (scan_type == NXPWIFI_SCAN_TYPE_PASSIVE) + temp_chan->chan_scan_mode_bmap |= + NXPWIFI_PASSIVE_SCAN; + else + temp_chan->chan_scan_mode_bmap &= + ~NXPWIFI_PASSIVE_SCAN; + + scan_time = bgscan_cfg_in->chan_list[chan_idx].scan_time; + + if (scan_time) { + scan_dur = (u16)scan_time; + } else { + scan_dur = (scan_type == + NXPWIFI_SCAN_TYPE_PASSIVE) ? + priv->adapter->passive_scan_time : + priv->adapter->specific_scan_time; + } + + temp_chan->min_scan_time = cpu_to_le16(scan_dur); + temp_chan->max_scan_time = cpu_to_le16(scan_dur); + } + } else { + nxpwifi_dbg(priv->adapter, INFO, + "info: bgscan: Creating full region channel list\n"); + chan_num = + nxpwifi_bgscan_create_channel_list + (priv, bgscan_cfg_in, + tlv_l->chan_scan_param); + le16_unaligned_add_cpu(&tlv_l->header.len, + chan_num * + sizeof(*tlv_l->chan_scan_param)); + } + + tlv_pos += (sizeof(tlv_l->header) + + le16_to_cpu(tlv_l->header.len)); + + if (bgscan_cfg_in->start_later) { + start_later_tlv = + (struct nxpwifi_ie_types_bgscan_start_later *)tlv_pos; + start_later_tlv->header.type = + cpu_to_le16(TLV_TYPE_BGSCAN_START_LATER); + start_later_tlv->header.len = + cpu_to_le16(sizeof(start_later_tlv->start_later)); + start_later_tlv->start_later = + cpu_to_le16(bgscan_cfg_in->start_later); + + tlv_pos += sizeof(start_later_tlv->header) + + le16_to_cpu(start_later_tlv->header.len); + } + + /* Append vendor specific element TLV */ + nxpwifi_cmd_append_vsie_tlv(priv, NXPWIFI_VSIE_MASK_BGSCAN, &tlv_pos); + + le16_unaligned_add_cpu(&cmd->size, tlv_pos - bgscan_config->tlv); + + return 0; +} + +int nxpwifi_stop_bg_scan(struct nxpwifi_private *priv) +{ + struct nxpwifi_bg_scan_cfg *bgscan_cfg; + int ret; + + if (!priv->sched_scanning) { + nxpwifi_dbg(priv->adapter, MSG, "bgscan already stopped!\n"); + return 0; + } + + bgscan_cfg = kzalloc_obj(*bgscan_cfg, GFP_KERNEL); + if (!bgscan_cfg) + return -ENOMEM; + + bgscan_cfg->bss_type = NXPWIFI_BSS_MODE_INFRA; + bgscan_cfg->action = NXPWIFI_BGSCAN_ACT_SET; + bgscan_cfg->enable = false; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_BG_SCAN_CONFIG, + HOST_ACT_GEN_SET, 0, bgscan_cfg, true); + if (!ret) + priv->sched_scanning = false; + + kfree(bgscan_cfg); + return ret; +} + +static void +nxpwifi_update_chan_statistics(struct nxpwifi_private *priv, + struct nxpwifi_ietypes_chanstats *tlv_stat) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u8 i, num_chan; + struct nxpwifi_fw_chan_stats *fw_chan_stats; + struct nxpwifi_chan_stats chan_stats; + + fw_chan_stats = (void *)((u8 *)tlv_stat + + sizeof(struct nxpwifi_ie_types_header)); + num_chan = le16_to_cpu(tlv_stat->header.len) / + sizeof(struct nxpwifi_chan_stats); + + for (i = 0 ; i < num_chan; i++) { + if (adapter->survey_idx >= adapter->num_in_chan_stats) { + nxpwifi_dbg(adapter, WARN, + "FW reported too many channel results (max %d)\n", + adapter->num_in_chan_stats); + return; + } + chan_stats.chan_num = fw_chan_stats->chan_num; + chan_stats.bandcfg = fw_chan_stats->bandcfg; + chan_stats.flags = fw_chan_stats->flags; + chan_stats.noise = fw_chan_stats->noise; + chan_stats.total_bss = le16_to_cpu(fw_chan_stats->total_bss); + chan_stats.cca_scan_dur = + le16_to_cpu(fw_chan_stats->cca_scan_dur); + chan_stats.cca_busy_dur = + le16_to_cpu(fw_chan_stats->cca_busy_dur); + nxpwifi_dbg(adapter, INFO, + "chan=%d, noise=%d, total_network=%d scan_duration=%d, busy_duration=%d\n", + chan_stats.chan_num, + chan_stats.noise, + chan_stats.total_bss, + chan_stats.cca_scan_dur, + chan_stats.cca_busy_dur); + memcpy(&adapter->chan_stats[adapter->survey_idx++], &chan_stats, + sizeof(struct nxpwifi_chan_stats)); + fw_chan_stats++; + } +} + +/* Handle the extended scan command response. */ +int nxpwifi_ret_802_11_scan_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_scan_ext *ext_scan_resp; + struct nxpwifi_ie_types_header *tlv; + struct nxpwifi_ietypes_chanstats *tlv_stat; + u16 buf_left, type, len; + + struct host_cmd_ds_command *cmd_ptr; + struct cmd_ctrl_node *cmd_node; + bool complete_scan = false; + + nxpwifi_dbg(adapter, INFO, "info: EXT scan returns successfully\n"); + + ext_scan_resp = &resp->params.ext_scan; + + tlv = (void *)ext_scan_resp->tlv_buffer; + buf_left = le16_to_cpu(resp->size) - (sizeof(*ext_scan_resp) + S_DS_GEN); + + while (buf_left >= sizeof(struct nxpwifi_ie_types_header)) { + type = le16_to_cpu(tlv->type); + len = le16_to_cpu(tlv->len); + + if (buf_left < (sizeof(struct nxpwifi_ie_types_header) + len)) { + nxpwifi_dbg(adapter, ERROR, + "error processing scan response TLVs"); + break; + } + + switch (type) { + case TLV_TYPE_CHANNEL_STATS: + tlv_stat = (void *)tlv; + nxpwifi_update_chan_statistics(priv, tlv_stat); + break; + default: + break; + } + + buf_left -= len + sizeof(struct nxpwifi_ie_types_header); + tlv = (void *)((u8 *)tlv + len + + sizeof(struct nxpwifi_ie_types_header)); + } + + spin_lock_bh(&adapter->cmd_pending_q_lock); + spin_lock_bh(&adapter->scan_pending_q_lock); + if (list_empty(&adapter->scan_pending_q)) { + complete_scan = true; + list_for_each_entry(cmd_node, &adapter->cmd_pending_q, list) { + cmd_ptr = (void *)cmd_node->cmd_skb->data; + if (le16_to_cpu(cmd_ptr->command) == + HOST_CMD_802_11_SCAN_EXT) { + nxpwifi_dbg(adapter, INFO, + "Scan pending in command pending list"); + complete_scan = false; + break; + } + } + } + spin_unlock_bh(&adapter->scan_pending_q_lock); + spin_unlock_bh(&adapter->cmd_pending_q_lock); + + if (complete_scan) + nxpwifi_complete_scan(priv); + + return 0; +} + +/* + * Handle the extended scan report event: parse the results and notify + * cfg80211. + */ +int nxpwifi_handle_event_ext_scan_report(struct nxpwifi_private *priv, + void *buf) +{ + int ret = 0; + struct nxpwifi_adapter *adapter = priv->adapter; + u8 *bss_info; + u32 bytes_left, bytes_left_for_tlv, idx; + u16 type, len; + struct nxpwifi_ie_types_data *tlv; + struct nxpwifi_ie_types_scan_rsp *scan_rsp_tlv; + struct nxpwifi_ie_types_scan_inf *scan_info_tlv; + u8 *radio_type; + u64 fw_tsf = 0; + s32 rssi = 0; + struct nxpwifi_event_scan_result *event_scan = buf; + u8 num_of_set = event_scan->num_of_set; + u8 *scan_resp = buf + sizeof(struct nxpwifi_event_scan_result); + u16 scan_resp_size = le16_to_cpu(event_scan->buf_size); + + if (num_of_set > NXPWIFI_MAX_AP) { + nxpwifi_dbg(adapter, ERROR, + "EXT_SCAN: Invalid number of AP returned (%d)!!\n", + num_of_set); + ret = -EINVAL; + goto check_next_scan; + } + + bytes_left = scan_resp_size; + nxpwifi_dbg(adapter, INFO, + "EXT_SCAN: size %d, returned %d APs...", + scan_resp_size, num_of_set); + nxpwifi_dbg_dump(adapter, CMD_D, "EXT_SCAN buffer:", buf, + scan_resp_size + + sizeof(struct nxpwifi_event_scan_result)); + + tlv = (struct nxpwifi_ie_types_data *)scan_resp; + + for (idx = 0; idx < num_of_set && bytes_left; idx++) { + type = le16_to_cpu(tlv->header.type); + len = le16_to_cpu(tlv->header.len); + if (bytes_left < sizeof(struct nxpwifi_ie_types_header) + len) { + nxpwifi_dbg(adapter, ERROR, + "EXT_SCAN: Error bytes left < TLV length\n"); + break; + } + scan_rsp_tlv = NULL; + scan_info_tlv = NULL; + bytes_left_for_tlv = bytes_left; + + /* + * BSS response TLV with beacon or probe response buffer + * at the initial position of each descriptor + */ + if (type != TLV_TYPE_BSS_SCAN_RSP) + break; + + bss_info = (u8 *)tlv; + scan_rsp_tlv = (struct nxpwifi_ie_types_scan_rsp *)tlv; + tlv = (struct nxpwifi_ie_types_data *)(tlv->data + len); + bytes_left_for_tlv -= + (len + sizeof(struct nxpwifi_ie_types_header)); + + while (bytes_left_for_tlv >= + sizeof(struct nxpwifi_ie_types_header) && + le16_to_cpu(tlv->header.type) != TLV_TYPE_BSS_SCAN_RSP) { + type = le16_to_cpu(tlv->header.type); + len = le16_to_cpu(tlv->header.len); + if (bytes_left_for_tlv < + sizeof(struct nxpwifi_ie_types_header) + len) { + nxpwifi_dbg(adapter, ERROR, + "EXT_SCAN: Error in processing TLV,\t" + "bytes left < TLV length\n"); + scan_rsp_tlv = NULL; + bytes_left_for_tlv = 0; + continue; + } + switch (type) { + case TLV_TYPE_BSS_SCAN_INFO: + scan_info_tlv = + (struct nxpwifi_ie_types_scan_inf *)tlv; + if (len != + sizeof(struct nxpwifi_ie_types_scan_inf) - + sizeof(struct nxpwifi_ie_types_header)) { + bytes_left_for_tlv = 0; + continue; + } + break; + default: + break; + } + tlv = (struct nxpwifi_ie_types_data *)(tlv->data + len); + bytes_left -= + (len + sizeof(struct nxpwifi_ie_types_header)); + bytes_left_for_tlv -= + (len + sizeof(struct nxpwifi_ie_types_header)); + } + + if (!scan_rsp_tlv) + break; + + /* + * Advance pointer to the beacon buffer length and + * update the bytes count so that the function + * wlan_interpret_bss_desc_with_ie() can handle the + * scan buffer withut any change + */ + bss_info += sizeof(u16); + bytes_left -= sizeof(u16); + + if (scan_info_tlv) { + rssi = (s32)(s16)(le16_to_cpu(scan_info_tlv->rssi)); + rssi *= 100; /* Convert dBm to mBm */ + nxpwifi_dbg(adapter, INFO, + "info: InterpretIE: RSSI=%d\n", rssi); + fw_tsf = le64_to_cpu(scan_info_tlv->tsf); + radio_type = &scan_info_tlv->radio_type; + } else { + radio_type = NULL; + } + ret = nxpwifi_parse_single_response_buf(priv, &bss_info, + &bytes_left, fw_tsf, + radio_type, true, rssi); + if (ret) + goto check_next_scan; + } + +check_next_scan: + if (!event_scan->more_event) + nxpwifi_check_next_scan_command(priv); + + return ret; +} + +/* + * Prepare the background scan query command. Sets the command ID, size, + * flush parameter, and fixes endianness. + */ +int nxpwifi_cmd_802_11_bg_scan_query(struct host_cmd_ds_command *cmd) +{ + struct host_cmd_ds_802_11_bg_scan_query *bg_query = + &cmd->params.bg_scan_query; + + cmd->command = cpu_to_le16(HOST_CMD_802_11_BG_SCAN_QUERY); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_bg_scan_query) + + S_DS_GEN); + + bg_query->flush = 1; + + return 0; +} + +/* Insert a scan command node into the scan_pending_q. */ +void +nxpwifi_queue_scan_cmd(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + cmd_node->wait_q_enabled = true; + cmd_node->condition = &adapter->scan_wait_q_woken; + spin_lock_bh(&adapter->scan_pending_q_lock); + list_add_tail(&cmd_node->list, &adapter->scan_pending_q); + spin_unlock_bh(&adapter->scan_pending_q_lock); +} + +/* Append a vendor-specific element TLV to the buffer. */ +int +nxpwifi_cmd_append_vsie_tlv(struct nxpwifi_private *priv, + u16 vsie_mask, u8 **buffer) +{ + int id, ret_len = 0; + struct nxpwifi_ie_types_vendor_param_set *vs_param_set; + + if (!buffer) + return 0; + if (!(*buffer)) + return 0; + + /* + * Traverse through the saved vendor specific element array and append + * the selected(scan/assoc) element as TLV to the command + */ + for (id = 0; id < NXPWIFI_MAX_VSIE_NUM; id++) { + if (priv->vs_ie[id].mask & vsie_mask) { + vs_param_set = + (struct nxpwifi_ie_types_vendor_param_set *) + *buffer; + vs_param_set->header.type = + cpu_to_le16(TLV_TYPE_PASSTHROUGH); + vs_param_set->header.len = + cpu_to_le16((((u16)priv->vs_ie[id].ie[1]) + & 0x00FF) + 2); + if (le16_to_cpu(vs_param_set->header.len) > + NXPWIFI_MAX_VSIE_LEN) { + nxpwifi_dbg(priv->adapter, ERROR, + "Invalid param length!\n"); + break; + } + + memcpy(vs_param_set->ie, priv->vs_ie[id].ie, + le16_to_cpu(vs_param_set->header.len)); + *buffer += le16_to_cpu(vs_param_set->header.len) + + sizeof(struct nxpwifi_ie_types_header); + ret_len += le16_to_cpu(vs_param_set->header.len) + + sizeof(struct nxpwifi_ie_types_header); + } + } + return ret_len; +} + +/* + * Save the beacon buffer of the current BSS descriptor. + * + * The buffer is preserved so it can be restored when the current SSID's + * beacon is missing, such as when: + * - the SSID was not found in the latest scan, or + * - the SSID was the last entry in the scan table and was overwritten. + */ +void +nxpwifi_save_curr_bcn(struct nxpwifi_private *priv) +{ + struct nxpwifi_bssdescriptor *curr_bss = + &priv->curr_bss_params.bss_descriptor; + + if (!curr_bss->beacon_buf_size) + return; + + /* allocate beacon buffer at 1st time; or if it's size has changed */ + if (!priv->curr_bcn_buf || + priv->curr_bcn_size != curr_bss->beacon_buf_size) { + priv->curr_bcn_size = curr_bss->beacon_buf_size; + + kfree(priv->curr_bcn_buf); + priv->curr_bcn_buf = kmalloc(curr_bss->beacon_buf_size, + GFP_ATOMIC); + if (!priv->curr_bcn_buf) + return; + } + + memcpy(priv->curr_bcn_buf, curr_bss->beacon_buf, + curr_bss->beacon_buf_size); + nxpwifi_dbg(priv->adapter, INFO, + "info: current beacon saved %d\n", + priv->curr_bcn_size); + + curr_bss->beacon_buf = priv->curr_bcn_buf; + + /* adjust the pointers in the current BSS descriptor */ + if (curr_bss->bcn_wpa_ie) + curr_bss->bcn_wpa_ie = + (struct ieee_types_vendor_specific *) + (curr_bss->beacon_buf + + curr_bss->wpa_offset); + + if (curr_bss->bcn_rsn_ie) + curr_bss->bcn_rsn_ie = + (struct element *)(curr_bss->beacon_buf + + curr_bss->rsn_offset); + + if (curr_bss->bcn_ht_cap) + curr_bss->bcn_ht_cap = (struct ieee80211_ht_cap *) + (curr_bss->beacon_buf + + curr_bss->ht_cap_offset); + + if (curr_bss->bcn_ht_oper) + curr_bss->bcn_ht_oper = (struct ieee80211_ht_operation *) + (curr_bss->beacon_buf + + curr_bss->ht_info_offset); + + if (curr_bss->bcn_vht_cap) + curr_bss->bcn_vht_cap = (void *)(curr_bss->beacon_buf + + curr_bss->vht_cap_offset); + + if (curr_bss->bcn_vht_oper) + curr_bss->bcn_vht_oper = (void *)(curr_bss->beacon_buf + + curr_bss->vht_info_offset); + + if (curr_bss->bcn_he_cap) + curr_bss->bcn_he_cap = (void *)(curr_bss->beacon_buf + + curr_bss->he_cap_offset); + + if (curr_bss->bcn_he_oper) + curr_bss->bcn_he_oper = (void *)(curr_bss->beacon_buf + + curr_bss->he_info_offset); + + if (curr_bss->bcn_bss_co_2040) + curr_bss->bcn_bss_co_2040 = + (curr_bss->beacon_buf + curr_bss->bss_co_2040_offset); + + if (curr_bss->bcn_ext_cap) + curr_bss->bcn_ext_cap = curr_bss->beacon_buf + + curr_bss->ext_cap_offset; + + if (curr_bss->oper_mode) + curr_bss->oper_mode = (void *)(curr_bss->beacon_buf + + curr_bss->oper_mode_offset); +} + +/* Free the beacon buffer in the current BSS descriptor. */ +void +nxpwifi_free_curr_bcn(struct nxpwifi_private *priv) +{ + kfree(priv->curr_bcn_buf); + priv->curr_bcn_buf = NULL; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/sdio.c b/drivers/net/wireless/nxp/nxpwifi/sdio.c new file mode 100644 index 000000000000..d8536354f093 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/sdio.c @@ -0,0 +1,2327 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: SDIO specific handling + * + * Copyright 2011-2024 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" +#include "sdio.h" + +#define SDIO_VERSION "1.0" + +/* Process deferred SDIO work items. */ +static void nxpwifi_sdio_work(struct work_struct *work); + +static struct nxpwifi_if_ops sdio_ops; + +static const struct nxpwifi_sdio_card_reg nxpwifi_reg_iw61x = { + .start_rd_port = 0, + .start_wr_port = 0, + .base_0_reg = 0xF8, + .base_1_reg = 0xF9, + .poll_reg = 0x5C, + .host_int_enable = UP_LD_HOST_INT_MASK | DN_LD_HOST_INT_MASK | + CMD_PORT_UPLD_INT_MASK | CMD_PORT_DNLD_INT_MASK, + .host_int_rsr_reg = 0x4, + .host_int_status_reg = 0x0C, + .host_int_mask_reg = 0x08, + .host_strap_reg = 0xF4, + .host_strap_mask = 0x01, + .host_strap_value = 0x00, + .status_reg_0 = 0xE8, + .status_reg_1 = 0xE9, + .sdio_int_mask = 0xff, + .data_port_mask = 0xffffffff, + .io_port_0_reg = 0xE4, + .io_port_1_reg = 0xE5, + .io_port_2_reg = 0xE6, + .max_mp_regs = 196, + .rd_bitmap_l = 0x10, + .rd_bitmap_u = 0x11, + .rd_bitmap_1l = 0x12, + .rd_bitmap_1u = 0x13, + .wr_bitmap_l = 0x14, + .wr_bitmap_u = 0x15, + .wr_bitmap_1l = 0x16, + .wr_bitmap_1u = 0x17, + .rd_len_p0_l = 0x18, + .rd_len_p0_u = 0x19, + .card_misc_cfg_reg = 0xd8, + .card_cfg_2_1_reg = 0xd9, + .cmd_rd_len_0 = 0xc0, + .cmd_rd_len_1 = 0xc1, + .cmd_rd_len_2 = 0xc2, + .cmd_rd_len_3 = 0xc3, + .cmd_cfg_0 = 0xc4, + .cmd_cfg_1 = 0xc5, + .cmd_cfg_2 = 0xc6, + .cmd_cfg_3 = 0xc7, + .fw_dump_host_ready = 0xcc, + .fw_dump_ctrl = 0xf9, + .fw_dump_start = 0xf1, + .fw_dump_end = 0xf8, + .func1_dump_reg_start = 0x10, + .func1_dump_reg_end = 0x17, + .func1_scratch_reg = 0xE8, + .func1_spec_reg_num = 13, + .func1_spec_reg_table = {0x08, 0x58, 0x5C, 0x5D, 0x60, + 0x61, 0x62, 0x64, 0x65, 0x66, + 0x68, 0x69, 0x6a}, +}; + +static const struct nxpwifi_sdio_device nxpwifi_sdio_iw61x = { + .firmware = IW61X_SDIO_FW_NAME, + .reg = &nxpwifi_reg_iw61x, + .max_ports = 32, + .mp_agg_pkt_limit = 16, + .tx_buf_size = NXPWIFI_TX_DATA_BUF_SIZE_4K, + .mp_tx_agg_buf_size = NXPWIFI_MP_AGGR_BSIZE_MAX, + .mp_rx_agg_buf_size = NXPWIFI_MP_AGGR_BSIZE_MAX, + .can_dump_fw = true, + .fw_dump_enh = true, + .can_ext_scan = true, +}; + +static struct memory_type_mapping generic_mem_type_map[] = { + {"DUMP", NULL, 0, 0xDD}, +}; + +static struct memory_type_mapping mem_type_mapping_tbl[] = { + {"ITCM", NULL, 0, 0xF0}, + {"DTCM", NULL, 0, 0xF1}, + {"SQRAM", NULL, 0, 0xF2}, + {"APU", NULL, 0, 0xF3}, + {"CIU", NULL, 0, 0xF4}, + {"ICU", NULL, 0, 0xF5}, + {"MAC", NULL, 0, 0xF6}, + {"EXT7", NULL, 0, 0xF7}, + {"EXT8", NULL, 0, 0xF8}, + {"EXT9", NULL, 0, 0xF9}, + {"EXT10", NULL, 0, 0xFA}, + {"EXT11", NULL, 0, 0xFB}, + {"EXT12", NULL, 0, 0xFC}, + {"EXT13", NULL, 0, 0xFD}, + {"EXTLAST", NULL, 0, 0xFE}, +}; + +/* Bind the SDIO function and start device registration. */ +static int +nxpwifi_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id) +{ + int ret; + struct sdio_mmc_card *card = NULL; + + card = devm_kzalloc(&func->dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + init_completion(&card->fw_done); + + card->func = func; + + if (id->driver_data) { + struct nxpwifi_sdio_device *data = (void *)id->driver_data; + + card->firmware = data->firmware; + card->firmware_sdiouart = data->firmware_sdiouart; + card->reg = data->reg; + card->max_ports = data->max_ports; + card->mp_agg_pkt_limit = data->mp_agg_pkt_limit; + card->tx_buf_size = data->tx_buf_size; + card->mp_tx_agg_buf_size = data->mp_tx_agg_buf_size; + card->mp_rx_agg_buf_size = data->mp_rx_agg_buf_size; + card->can_dump_fw = data->can_dump_fw; + card->fw_dump_enh = data->fw_dump_enh; + card->can_ext_scan = data->can_ext_scan; + INIT_WORK(&card->work, nxpwifi_sdio_work); + } + + sdio_claim_host(func); + ret = sdio_enable_func(func); + sdio_release_host(func); + + if (ret) { + dev_err(&func->dev, "failed to enable function\n"); + return ret; + } + + ret = nxpwifi_add_card(card, &card->fw_done, &sdio_ops, + NXPWIFI_SDIO, &func->dev); + if (ret) { + dev_err(&func->dev, "add card failed\n"); + goto err_disable; + } + + return 0; + +err_disable: + sdio_claim_host(func); + sdio_disable_func(func); + sdio_release_host(func); + + return ret; +} + +/* Resume the SDIO function and cancel host sleep. */ +static int nxpwifi_sdio_resume(struct device *dev) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + struct sdio_mmc_card *card; + struct nxpwifi_adapter *adapter; + + card = sdio_get_drvdata(func); + + if (unlikely(!card || !card->adapter)) { + dev_dbg(dev, "resume: %s not ready\n", !card ? "card" : "adapter"); + return -ENODEV; + } + + adapter = card->adapter; + + if (!test_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags)) + return 0; + + clear_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags); + + /* Disable Host Sleep */ + nxpwifi_cancel_hs(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA), + NXPWIFI_SYNC_CMD); + + return 0; +} + +static int +nxpwifi_write_reg_locked(struct sdio_func *func, u32 reg, u8 data) +{ + int ret; + + sdio_writeb(func, data, reg, &ret); + return ret; +} + +static int +nxpwifi_write_reg(struct nxpwifi_adapter *adapter, u32 reg, u8 data) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + + sdio_claim_host(card->func); + ret = nxpwifi_write_reg_locked(card->func, reg, data); + sdio_release_host(card->func); + + return ret; +} + +static int +nxpwifi_read_reg(struct nxpwifi_adapter *adapter, u32 reg, u8 *data) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + u8 val; + + sdio_claim_host(card->func); + val = sdio_readb(card->func, reg, &ret); + sdio_release_host(card->func); + + *data = val; + + return ret; +} + +static int +nxpwifi_write_data_sync(struct nxpwifi_adapter *adapter, + u8 *buffer, u32 pkt_len, u32 port) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + u8 blk_mode = + (port & NXPWIFI_SDIO_BYTE_MODE_MASK) ? BYTE_MODE : BLOCK_MODE; + u32 blk_size = (blk_mode == BLOCK_MODE) ? NXPWIFI_SDIO_BLOCK_SIZE : 1; + u32 blk_cnt = + (blk_mode == + BLOCK_MODE) ? (pkt_len / + NXPWIFI_SDIO_BLOCK_SIZE) : pkt_len; + u32 ioport = (port & NXPWIFI_SDIO_IO_PORT_MASK); + + if (test_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags)) { + nxpwifi_dbg(adapter, ERROR, + "%s: not allowed while suspended\n", __func__); + return -EPERM; + } + + sdio_claim_host(card->func); + + ret = sdio_writesb(card->func, ioport, buffer, blk_cnt * blk_size); + + sdio_release_host(card->func); + + return ret; +} + +static int nxpwifi_read_data_sync(struct nxpwifi_adapter *adapter, u8 *buffer, + u32 len, u32 port, u8 claim) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + u8 blk_mode = (port & NXPWIFI_SDIO_BYTE_MODE_MASK) ? BYTE_MODE + : BLOCK_MODE; + u32 blk_size = (blk_mode == BLOCK_MODE) ? NXPWIFI_SDIO_BLOCK_SIZE : 1; + u32 blk_cnt = (blk_mode == BLOCK_MODE) ? (len / NXPWIFI_SDIO_BLOCK_SIZE) + : len; + u32 ioport = (port & NXPWIFI_SDIO_IO_PORT_MASK); + + if (claim) + sdio_claim_host(card->func); + + ret = sdio_readsb(card->func, buffer, ioport, blk_cnt * blk_size); + + if (claim) + sdio_release_host(card->func); + + return ret; +} + +static int +nxpwifi_sdio_read_fw_status(struct nxpwifi_adapter *adapter, u16 *dat) +{ + struct sdio_mmc_card *card = adapter->card; + const struct nxpwifi_sdio_card_reg *reg = card->reg; + u8 fws0, fws1; + int ret; + + ret = nxpwifi_read_reg(adapter, reg->status_reg_0, &fws0); + if (ret) + return ret; + + ret = nxpwifi_read_reg(adapter, reg->status_reg_1, &fws1); + if (ret) + return ret; + + *dat = (u16)((fws1 << 8) | fws0); + return ret; +} + +static int nxpwifi_check_fw_status(struct nxpwifi_adapter *adapter, + u32 poll_num) +{ + int ret = 0; + u16 firmware_stat = 0; + + unsigned int timeout_us = poll_num * 100000; /* 100 ms * poll_num */ + /* + * Poll every 100 ms until firmware reports FIRMWARE_READY_SDIO. + * On timeout, read_poll_timeout() returns -ETIMEDOUT. + */ + ret = read_poll_timeout(nxpwifi_sdio_read_fw_status, ret, + (!ret && firmware_stat == FIRMWARE_READY_SDIO), + 100000, timeout_us, true, /* sleep */ + adapter, &firmware_stat); + + /* FW may appear ready; wait a bit to avoid early races. */ + if (firmware_stat == FIRMWARE_READY_SDIO) + msleep(100); + + return ret; +} + +static int nxpwifi_check_winner_status(struct nxpwifi_adapter *adapter) +{ + int ret; + u8 winner = 0; + struct sdio_mmc_card *card = adapter->card; + + ret = nxpwifi_read_reg(adapter, card->reg->status_reg_0, &winner); + if (ret) + return ret; + + if (winner) + adapter->winner = 0; + else + adapter->winner = 1; + + return ret; +} + +/* Remove the SDIO function and tear down the adapter. */ +static void +nxpwifi_sdio_remove(struct sdio_func *func) +{ + struct sdio_mmc_card *card; + struct nxpwifi_adapter *adapter; + struct nxpwifi_private *priv; + int ret = 0; + u16 firmware_stat; + + card = sdio_get_drvdata(func); + if (!card) + return; + + wait_for_completion(&card->fw_done); + + adapter = card->adapter; + if (!adapter || !adapter->priv_num) + return; + + ret = nxpwifi_sdio_read_fw_status(adapter, &firmware_stat); + if (!ret && firmware_stat == FIRMWARE_READY_SDIO) { + nxpwifi_deauthenticate_all(adapter); + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + nxpwifi_disable_auto_ds(priv); + nxpwifi_init_shutdown_fw(priv, NXPWIFI_FUNC_SHUTDOWN); + } + + nxpwifi_remove_card(adapter); +} + +/* Suspend the SDIO function while keeping SDIO power. */ +static int nxpwifi_sdio_suspend(struct device *dev) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + struct sdio_mmc_card *card; + struct nxpwifi_adapter *adapter; + mmc_pm_flag_t caps; + unsigned long flags = 0; + int ret = 0; + + caps = sdio_get_host_pm_caps(func); + + if (!(caps & MMC_PM_KEEP_POWER)) { + /* host lacks keep-power capability */ + dev_warn(dev, "suspend: host does not support MMC_PM_KEEP_POWER\n"); + return -EOPNOTSUPP; + } + + card = sdio_get_drvdata(func); + + if (!card) { + dev_warn(dev, "suspend: card not ready\n"); + return -ENODEV; + } + + /* Might still be loading firmware */ + wait_for_completion(&card->fw_done); + + adapter = card->adapter; + if (!adapter) { + dev_warn(dev, "suspend: adapter not ready\n"); + return -ENODEV; + } + + /* Enable the Host Sleep */ + if (!nxpwifi_enable_hs(adapter)) { + nxpwifi_dbg(adapter, ERROR, "suspend: enable host sleep failed\n"); + clear_bit(NXPWIFI_IS_HS_ENABLING, &adapter->work_flags); + return -ETIMEDOUT; + } + + flags |= MMC_PM_KEEP_POWER; + + if (adapter->wowlan_enabled && (caps & MMC_PM_WAKE_SDIO_IRQ)) + flags |= MMC_PM_WAKE_SDIO_IRQ; + + ret = sdio_set_host_pm_flags(func, flags); + + /* Indicate device suspended */ + set_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags); + clear_bit(NXPWIFI_IS_HS_ENABLING, &adapter->work_flags); + + return ret; +} + +static void nxpwifi_sdio_coredump(struct device *dev) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + struct sdio_mmc_card *card; + + card = sdio_get_drvdata(func); + if (!test_and_set_bit(NXPWIFI_IFACE_WORK_DEVICE_DUMP, + &card->work_flags)) + nxpwifi_queue_work(card->adapter, &card->work); +} + +/* WLAN IDs */ +static const struct sdio_device_id nxpwifi_ids[] = { + {SDIO_DEVICE(SDIO_VENDOR_ID_NXP, SDIO_DEVICE_ID_NXP_IW61X), + .driver_data = (unsigned long)&nxpwifi_sdio_iw61x}, + {}, +}; + +MODULE_DEVICE_TABLE(sdio, nxpwifi_ids); + +static const struct dev_pm_ops nxpwifi_sdio_pm_ops = { + .suspend = nxpwifi_sdio_suspend, + .resume = nxpwifi_sdio_resume, +}; + +static struct sdio_driver nxpwifi_sdio = { + .name = "nxpwifi_sdio", + .id_table = nxpwifi_ids, + .probe = nxpwifi_sdio_probe, + .remove = nxpwifi_sdio_remove, + .drv = { + .coredump = nxpwifi_sdio_coredump, + .pm = &nxpwifi_sdio_pm_ops, + } +}; + +static int nxpwifi_pm_wakeup_card(struct nxpwifi_adapter *adapter) +{ + nxpwifi_dbg(adapter, EVENT, "event: wakeup device...\n"); + + return nxpwifi_write_reg(adapter, CONFIGURATION_REG, HOST_POWER_UP); +} + +static int nxpwifi_pm_wakeup_card_complete(struct nxpwifi_adapter *adapter) +{ + nxpwifi_dbg(adapter, EVENT, "cmd: wakeup device completed\n"); + + return nxpwifi_write_reg(adapter, CONFIGURATION_REG, 0); +} + +/* SDIO wrapper for firmware download (claims host). */ +static int nxpwifi_sdio_dnld_fw(struct nxpwifi_adapter *adapter, + struct nxpwifi_fw_image *fw) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + + sdio_claim_host(card->func); + ret = nxpwifi_dnld_fw(adapter, fw); + sdio_release_host(card->func); + + return ret; +} + +static int nxpwifi_init_sdio_new_mode(struct nxpwifi_adapter *adapter) +{ + u8 reg; + struct sdio_mmc_card *card = adapter->card; + int ret; + + adapter->ioport = MEM_PORT; + + /* enable sdio new mode */ + ret = nxpwifi_read_reg(adapter, card->reg->card_cfg_2_1_reg, ®); + if (ret) + return ret; + ret = nxpwifi_write_reg(adapter, card->reg->card_cfg_2_1_reg, + reg | CMD53_NEW_MODE); + if (ret) + return ret; + + /* Configure cmd port and enable reading rx length from the register */ + ret = nxpwifi_read_reg(adapter, card->reg->cmd_cfg_0, ®); + if (ret) + return ret; + ret = nxpwifi_write_reg(adapter, card->reg->cmd_cfg_0, + reg | CMD_PORT_RD_LEN_EN); + if (ret) + return ret; + + /* + * Enable Dnld/Upld ready auto reset for cmd port after cmd53 is + * completed + */ + ret = nxpwifi_read_reg(adapter, card->reg->cmd_cfg_1, ®); + if (ret) + return ret; + ret = nxpwifi_write_reg(adapter, card->reg->cmd_cfg_1, + reg | CMD_PORT_AUTO_EN); + + return ret; +} + +/* Initialize SDIO IO ports and host-int behavior. */ +static int nxpwifi_init_sdio_ioport(struct nxpwifi_adapter *adapter) +{ + u8 reg; + struct sdio_mmc_card *card = adapter->card; + int ret; + + ret = nxpwifi_init_sdio_new_mode(adapter); + if (ret) + return ret; + + /* Set Host interrupt reset to read to clear */ + ret = nxpwifi_read_reg(adapter, card->reg->host_int_rsr_reg, ®); + if (ret) + return ret; + ret = nxpwifi_write_reg(adapter, card->reg->host_int_rsr_reg, + reg | card->reg->sdio_int_mask); + if (ret) + return ret; + + /* Dnld/Upld ready set to auto reset */ + ret = nxpwifi_read_reg(adapter, card->reg->card_misc_cfg_reg, ®); + if (ret) + return ret; + ret = nxpwifi_write_reg(adapter, card->reg->card_misc_cfg_reg, + reg | AUTO_RE_ENABLE_INT); + + return ret; +} + +static int nxpwifi_write_data_to_card(struct nxpwifi_adapter *adapter, + u8 *payload, u32 pkt_len, u32 port) +{ + u32 i = 0; + int ret; + + do { + ret = nxpwifi_write_data_sync(adapter, payload, pkt_len, port); + if (ret) { + i++; + nxpwifi_dbg(adapter, ERROR, "host_to_card, write iomem\t" + "(%d) failed: %d\n", i, ret); + if (nxpwifi_write_reg(adapter, CONFIGURATION_REG, 0x04)) + nxpwifi_dbg(adapter, ERROR, "write CFG reg failed\n"); + + if (i > MAX_WRITE_IOMEM_RETRY) + return ret; + } + } while (ret); + + return ret; +} + +static int nxpwifi_get_rd_port(struct nxpwifi_adapter *adapter, u8 *port) +{ + struct sdio_mmc_card *card = adapter->card; + const struct nxpwifi_sdio_card_reg *reg = card->reg; + u32 rd_bitmap = card->mp_rd_bitmap; + + if (!(rd_bitmap & reg->data_port_mask)) + return -EINVAL; + + if (!(card->mp_rd_bitmap & (1 << card->curr_rd_port))) + return -EINVAL; + + /* We are now handling the SDIO data ports */ + card->mp_rd_bitmap &= (u32)(~(1 << card->curr_rd_port)); + *port = card->curr_rd_port; + + if (++card->curr_rd_port == card->max_ports) + card->curr_rd_port = reg->start_rd_port; + + return 0; +} + +static int nxpwifi_get_wr_port_data(struct nxpwifi_adapter *adapter, u32 *port) +{ + struct sdio_mmc_card *card = adapter->card; + const struct nxpwifi_sdio_card_reg *reg = card->reg; + u32 wr_bitmap = card->mp_wr_bitmap; + + if (!(wr_bitmap & card->mp_data_port_mask)) { + adapter->data_sent = true; + return -EBUSY; + } + + if (card->mp_wr_bitmap & (1 << card->curr_wr_port)) { + card->mp_wr_bitmap &= (u32)(~(1 << card->curr_wr_port)); + *port = card->curr_wr_port; + if (++card->curr_wr_port == card->mp_end_port) + card->curr_wr_port = reg->start_wr_port; + } else { + adapter->data_sent = true; + return -EBUSY; + } + + return 0; +} + +static int +nxpwifi_sdio_poll_card_status(struct nxpwifi_adapter *adapter, u8 bits) +{ + struct sdio_mmc_card *card = adapter->card; + u32 tries; + u8 cs; + int ret; + + for (tries = 0; tries < MAX_POLL_TRIES; tries++) { + ret = nxpwifi_read_reg(adapter, card->reg->poll_reg, &cs); + if (ret) + break; + else if ((cs & bits) == bits) + return 0; + + usleep_range(10, 20); + } + + nxpwifi_dbg(adapter, ERROR, "poll card status failed, tries = %d\n", tries); + + return ret; +} + +/* Disable SDIO host interrupt and release IRQ. */ +static void nxpwifi_sdio_disable_host_int(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + struct sdio_func *func = card->func; + + sdio_claim_host(func); + nxpwifi_write_reg_locked(func, card->reg->host_int_mask_reg, 0); + sdio_release_irq(func); + sdio_release_host(func); +} + +static void nxpwifi_interrupt_status(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + u8 sdio_ireg; + unsigned long flags; + + if (nxpwifi_read_data_sync(adapter, card->mp_regs, + card->reg->max_mp_regs, + REG_PORT | NXPWIFI_SDIO_BYTE_MODE_MASK, 0)) { + nxpwifi_dbg(adapter, ERROR, "read mp_regs failed\n"); + return; + } + + sdio_ireg = card->mp_regs[card->reg->host_int_status_reg]; + if (sdio_ireg) { + nxpwifi_dbg(adapter, INTR, "intr: sdio_ireg = %#x\n", sdio_ireg); + spin_lock_irqsave(&adapter->int_lock, flags); + adapter->int_status |= sdio_ireg; + spin_unlock_irqrestore(&adapter->int_lock, flags); + } +} + +/* SDIO IRQ handler: snapshot status and schedule main work. */ +static void +nxpwifi_sdio_interrupt(struct sdio_func *func) +{ + struct nxpwifi_adapter *adapter; + struct sdio_mmc_card *card; + + card = sdio_get_drvdata(func); + + if (!card || !card->adapter) { + /* device-scoped error logging (rate-limited to avoid flood) */ + dev_err_ratelimited(&func->dev, "interrupt: missing card/adapter\n"); + return; + } + + adapter = card->adapter; + + if (!adapter->pps_uapsd_mode && adapter->ps_state == PS_STATE_SLEEP) + adapter->ps_state = PS_STATE_AWAKE; + + nxpwifi_interrupt_status(adapter); + nxpwifi_queue_work(adapter, &adapter->main_work); +} + +/* Enable SDIO host interrupt and claim IRQ. */ +static int nxpwifi_sdio_enable_host_int(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + struct sdio_func *func = card->func; + int ret; + + sdio_claim_host(func); + + /* Request the SDIO IRQ */ + ret = sdio_claim_irq(func, nxpwifi_sdio_interrupt); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "claim irq failed: ret=%d\n", ret); + goto done; + } + + /* Simply write the mask to the register */ + ret = nxpwifi_write_reg_locked(func, card->reg->host_int_mask_reg, + card->reg->host_int_enable); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "enable host interrupt failed\n"); + sdio_release_irq(func); + } + +done: + sdio_release_host(func); + return ret; +} + +static int nxpwifi_sdio_card_to_host(struct nxpwifi_adapter *adapter, + u32 *type, u8 *buffer, + u32 npayload, u32 ioport) +{ + int ret; + u32 nb; + + if (!buffer) + return -EINVAL; + + ret = nxpwifi_read_data_sync(adapter, buffer, npayload, ioport, 1); + + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "read iomem failed (ioport=%#x, len=%u): %d", + ioport, npayload, ret); + + return ret; + } + + nb = get_unaligned_le16((buffer)); + if (nb > npayload) { + nxpwifi_dbg(adapter, ERROR, + "invalid packet len: nb=%u > npayload=%u (ioport=%#x)", + nb, npayload, ioport); + return -EINVAL; + } + + *type = get_unaligned_le16((buffer + 2)); + + return ret; +} + +/* Download firmware using the helper protocol. */ +static int nxpwifi_prog_fw_w_helper(struct nxpwifi_adapter *adapter, + struct nxpwifi_fw_image *fw) +{ + struct sdio_mmc_card *card = adapter->card; + const struct nxpwifi_sdio_card_reg *reg = card->reg; + int ret; + u8 *firmware = fw->fw_buf; + u32 firmware_len = fw->fw_len; + u32 offset = 0; + u8 base0, base1; + u8 *fwbuf; + u16 len = 0; + u32 txlen, tx_blocks = 0, tries; + u32 i = 0; + + if (!firmware_len) { + nxpwifi_dbg(adapter, ERROR, + "firmware image not found! Terminating download\n"); + return -EINVAL; + } + + /* Assume that the allocated buffer is 8-byte aligned */ + fwbuf = kzalloc(NXPWIFI_UPLD_SIZE, GFP_KERNEL); + if (!fwbuf) + return -ENOMEM; + + sdio_claim_host(card->func); + + /* Perform firmware data transfer */ + do { + /* + * The host polls for the DN_LD_CARD_RDY and CARD_IO_READY + * bits + */ + ret = nxpwifi_sdio_poll_card_status(adapter, CARD_IO_READY | + DN_LD_CARD_RDY); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "FW download with helper:\t" + "poll status timeout @ %d\n", offset); + goto done; + } + + /* More data? */ + if (offset >= firmware_len) + break; + + for (tries = 0; tries < MAX_POLL_TRIES; tries++) { + ret = nxpwifi_read_reg(adapter, reg->base_0_reg, + &base0); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "dev BASE0 register read failed:\t" + "base0=%#04X(%d). Terminating dnld\n", + base0, base0); + goto done; + } + ret = nxpwifi_read_reg(adapter, reg->base_1_reg, + &base1); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "dev BASE1 register read failed:\t" + "base1=%#04X(%d). Terminating dnld\n", + base1, base1); + goto done; + } + len = (u16)(((base1 & 0xff) << 8) | (base0 & 0xff)); + + if (len) + break; + + usleep_range(10, 20); + } + + if (!len) { + break; + } else if (len > NXPWIFI_UPLD_SIZE) { + nxpwifi_dbg(adapter, ERROR, + "FW dnld failed @ %d, invalid length %d\n", + offset, len); + ret = -EINVAL; + goto done; + } + + txlen = len; + + if (len & BIT(0)) { + i++; + if (i > MAX_WRITE_IOMEM_RETRY) { + nxpwifi_dbg(adapter, ERROR, + "FW dnld failed @ %d, over max retry\n", + offset); + ret = -EIO; + goto done; + } + nxpwifi_dbg(adapter, ERROR, + "CRC indicated by the helper:\t" + "len = 0x%04X, txlen = %d\n", len, txlen); + len &= ~BIT(0); + /* Setting this to 0 to resend from same offset */ + txlen = 0; + } else { + i = 0; + + /* + * Set blocksize to transfer - checking for last + * block + */ + if (firmware_len - offset < txlen) + txlen = firmware_len - offset; + + tx_blocks = (txlen + NXPWIFI_SDIO_BLOCK_SIZE - 1) + / NXPWIFI_SDIO_BLOCK_SIZE; + + /* Copy payload to buffer */ + memcpy(fwbuf, &firmware[offset], txlen); + } + + ret = nxpwifi_write_data_sync(adapter, fwbuf, tx_blocks * + NXPWIFI_SDIO_BLOCK_SIZE, + adapter->ioport); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "FW download, write iomem (%d) failed @ %d\n", + i, offset); + if (nxpwifi_write_reg(adapter, CONFIGURATION_REG, 0x04)) + nxpwifi_dbg(adapter, ERROR, "write CFG reg failed\n"); + + goto done; + } + + offset += txlen; + } while (true); + + nxpwifi_dbg(adapter, MSG, "FW download complete (%u bytes)\n", offset); + + ret = 0; +done: + sdio_release_host(card->func); + kfree(fwbuf); + return ret; +} + +/* Deaggregate an SDIO RX aggregation packet. */ +static void nxpwifi_deaggr_sdio_pkt(struct nxpwifi_adapter *adapter, + struct sk_buff *skb) +{ + u32 total_pkt_len, pkt_len; + struct sk_buff *skb_deaggr; + u16 blk_size; + u8 blk_num; + u8 *data; + + data = skb->data; + total_pkt_len = skb->len; + + while (total_pkt_len >= (SDIO_HEADER_OFFSET + adapter->intf_hdr_len)) { + if (total_pkt_len < adapter->sdio_rx_block_size) + break; + blk_num = *(data + BLOCK_NUMBER_OFFSET); + blk_size = adapter->sdio_rx_block_size * blk_num; + if (blk_size > total_pkt_len) { + nxpwifi_dbg(adapter, ERROR, + "%s: error in blk_size,\t" + "blk_num=%d, blk_size=%d, total_pkt_len=%d\n", + __func__, blk_num, blk_size, total_pkt_len); + break; + } + pkt_len = get_unaligned_le16((data + + SDIO_HEADER_OFFSET)); + if ((pkt_len + SDIO_HEADER_OFFSET) > blk_size) { + nxpwifi_dbg(adapter, ERROR, + "%s: error in pkt_len,\t" + "pkt_len=%d, blk_size=%d\n", + __func__, pkt_len, blk_size); + break; + } + + skb_deaggr = nxpwifi_alloc_dma_align_buf(pkt_len, GFP_KERNEL); + if (!skb_deaggr) + break; + skb_put(skb_deaggr, pkt_len); + memcpy(skb_deaggr->data, data + SDIO_HEADER_OFFSET, pkt_len); + skb_pull(skb_deaggr, adapter->intf_hdr_len); + + nxpwifi_handle_rx_packet(adapter, skb_deaggr); + data += blk_size; + total_pkt_len -= blk_size; + } +} + +static void nxpwifi_decode_rx_packet(struct nxpwifi_adapter *adapter, + struct sk_buff *skb, u32 upld_typ) +{ + u8 *cmd_buf; + u16 pkt_len; + struct nxpwifi_rxinfo *rx_info; + + pkt_len = get_unaligned_le16(skb->data); + + if (upld_typ != NXPWIFI_TYPE_AGGR_DATA) { + skb_trim(skb, pkt_len); + skb_pull(skb, adapter->intf_hdr_len); + } + + switch (upld_typ) { + case NXPWIFI_TYPE_AGGR_DATA: + nxpwifi_dbg(adapter, DATA, + "Rx Aggr Data packet\n"); + rx_info = NXPWIFI_SKB_RXCB(skb); + rx_info->buf_type = NXPWIFI_TYPE_AGGR_DATA; + if (adapter->rx_work_enabled) { + skb_queue_tail(&adapter->rx_data_q, skb); + atomic_inc(&adapter->rx_pending); + adapter->data_received = true; + } else { + /* Deaggregate an SDIO RX aggregation packet. */ + nxpwifi_deaggr_sdio_pkt(adapter, skb); + dev_kfree_skb_any(skb); + } + break; + + case NXPWIFI_TYPE_DATA: + nxpwifi_dbg(adapter, DATA, "Rx Data packet\n"); + if (adapter->rx_work_enabled) { + skb_queue_tail(&adapter->rx_data_q, skb); + adapter->data_received = true; + atomic_inc(&adapter->rx_pending); + } else { + nxpwifi_handle_rx_packet(adapter, skb); + } + break; + + case NXPWIFI_TYPE_CMD: + nxpwifi_dbg(adapter, CMD, "Rx Cmd Response\n"); + /* take care of curr_cmd = NULL case */ + if (!adapter->curr_cmd) { + cmd_buf = adapter->upld_buf; + + if (adapter->ps_state == PS_STATE_SLEEP_CFM) + nxpwifi_process_sleep_confirm_resp(adapter, + skb->data, + skb->len); + + memcpy(cmd_buf, skb->data, + min_t(u32, NXPWIFI_SIZE_OF_CMD_BUFFER, + skb->len)); + + dev_kfree_skb_any(skb); + } else { + adapter->cmd_resp_received = true; + adapter->curr_cmd->resp_skb = skb; + } + break; + + case NXPWIFI_TYPE_EVENT: + nxpwifi_dbg(adapter, EVENT, "Rx Event\n"); + adapter->event_cause = get_unaligned_le32(skb->data); + + if (skb->len > NXPWIFI_EVENT_HEADER_LEN) { + u32 body_len = min_t(u32, skb->len - NXPWIFI_EVENT_HEADER_LEN, + MAX_EVENT_SIZE); + memcpy(adapter->event_body, skb->data + NXPWIFI_EVENT_HEADER_LEN, + body_len); + } + + /* event cause has been saved to adapter->event_cause */ + adapter->event_received = true; + adapter->event_skb = skb; + + break; + + default: + nxpwifi_dbg(adapter, ERROR, "unknown upload type %#x\n", upld_typ); + dev_kfree_skb_any(skb); + break; + } +} + +/* Receive path with SDIO multi-port aggregation. */ +static int nxpwifi_sdio_card_to_host_mp_aggr(struct nxpwifi_adapter *adapter, + u16 rx_len, u8 port) +{ + struct sdio_mmc_card *card = adapter->card; + s32 f_do_rx_aggr = 0; + s32 f_do_rx_cur = 0; + s32 f_aggr_cur = 0; + s32 f_post_aggr_cur = 0; + struct sk_buff *skb_deaggr; + struct sk_buff *skb = NULL; + u32 pkt_len, pkt_type, mport, pind; + u8 *curr_ptr; + int ret = 0; + + if (!card->mpa_rx.enabled) { + nxpwifi_dbg(adapter, WARN, "rx aggregation disabled\n"); + f_do_rx_cur = 1; + goto rx_curr_single; + } + + if (card->mp_rd_bitmap & card->reg->data_port_mask) { + /* Some more data RX pending */ + + if (MP_RX_AGGR_IN_PROGRESS(card)) { + if (MP_RX_AGGR_BUF_HAS_ROOM(card, rx_len)) { + f_aggr_cur = 1; + } else { + /* No room in Aggr buf, do rx aggr now */ + f_do_rx_aggr = 1; + f_post_aggr_cur = 1; + } + } else { + /* Rx aggr not in progress */ + f_aggr_cur = 1; + } + + } else { + /* No more data RX pending */ + + if (MP_RX_AGGR_IN_PROGRESS(card)) { + f_do_rx_aggr = 1; + if (MP_RX_AGGR_BUF_HAS_ROOM(card, rx_len)) + f_aggr_cur = 1; + else + /* No room in Aggr buf, do rx aggr now */ + f_do_rx_cur = 1; + } else { + f_do_rx_cur = 1; + } + } + + if (f_aggr_cur) { + /* Curr pkt can be aggregated */ + mp_rx_aggr_setup(card, rx_len, port); + + if (MP_RX_AGGR_PKT_LIMIT_REACHED(card) || + mp_rx_aggr_port_limit_reached(card)) { + /* No more pkts allowed in Aggr buf, rx it */ + f_do_rx_aggr = 1; + } + } + + if (f_do_rx_aggr) { + u32 port_count; + int i; + + /* do aggr RX now */ + for (i = 0, port_count = 0; i < card->max_ports; i++) + if (card->mpa_rx.ports & BIT(i)) + port_count++; + + /* + * Reading data from "start_port + 0" to "start_port + + * port_count -1", so decrease the count by 1 + */ + port_count--; + mport = (adapter->ioport | SDIO_MPA_ADDR_BASE | + (port_count << 8)) + card->mpa_rx.start_port; + + if (card->mpa_rx.pkt_cnt == 1) + mport = adapter->ioport + card->mpa_rx.start_port; + + ret = nxpwifi_read_data_sync(adapter, card->mpa_rx.buf, + card->mpa_rx.buf_len, mport, 1); + if (ret) + goto error; + + curr_ptr = card->mpa_rx.buf; + + for (pind = 0; pind < card->mpa_rx.pkt_cnt; pind++) { + u32 *len_arr = card->mpa_rx.len_arr; + + /* get curr PKT len & type */ + pkt_len = get_unaligned_le16(&curr_ptr[0]); + pkt_type = get_unaligned_le16(&curr_ptr[2]); + + /* copy pkt to deaggr buf */ + skb_deaggr = nxpwifi_alloc_dma_align_buf(len_arr[pind], + GFP_KERNEL); + if (!skb_deaggr) { + nxpwifi_dbg(adapter, ERROR, "skb allocation failure\t" + "drop pkt len=%d type=%d\n", + pkt_len, pkt_type); + curr_ptr += len_arr[pind]; + continue; + } + + skb_put(skb_deaggr, len_arr[pind]); + + if ((pkt_type == NXPWIFI_TYPE_DATA || + (pkt_type == NXPWIFI_TYPE_AGGR_DATA && + adapter->sdio_rx_aggr_enable)) && + pkt_len <= len_arr[pind]) { + memcpy(skb_deaggr->data, curr_ptr, pkt_len); + + skb_trim(skb_deaggr, pkt_len); + + nxpwifi_decode_rx_packet(adapter, skb_deaggr, + pkt_type); + } else { + nxpwifi_dbg(adapter, ERROR, + "drop wrong aggr pkt:\t" + "sdio_single_port_rx_aggr=%d\t" + "type=%d len=%d max_len=%d\n", + adapter->sdio_rx_aggr_enable, + pkt_type, pkt_len, len_arr[pind]); + dev_kfree_skb_any(skb_deaggr); + } + curr_ptr += len_arr[pind]; + } + MP_RX_AGGR_BUF_RESET(card); + } + +rx_curr_single: + if (f_do_rx_cur) { + skb = nxpwifi_alloc_dma_align_buf(rx_len, GFP_KERNEL); + if (!skb) { + nxpwifi_dbg(adapter, ERROR, + "single skb allocated fail,\t" + "drop pkt port=%d len=%d\n", port, rx_len); + ret = nxpwifi_sdio_card_to_host(adapter, &pkt_type, + card->mpa_rx.buf, + rx_len, + adapter->ioport + port); + if (ret) + goto error; + return 0; + } + + skb_put(skb, rx_len); + + ret = nxpwifi_sdio_card_to_host(adapter, &pkt_type, + skb->data, skb->len, + adapter->ioport + port); + if (ret) + goto error; + if (!adapter->sdio_rx_aggr_enable && + pkt_type == NXPWIFI_TYPE_AGGR_DATA) { + nxpwifi_dbg(adapter, ERROR, "drop wrong pkt type %d\t" + "current SDIO RX Aggr not enabled\n", + pkt_type); + dev_kfree_skb_any(skb); + return 0; + } + + nxpwifi_decode_rx_packet(adapter, skb, pkt_type); + } + if (f_post_aggr_cur) /* Curr pkt can be aggregated */ + mp_rx_aggr_setup(card, rx_len, port); + + return 0; +error: + if (MP_RX_AGGR_IN_PROGRESS(card)) + MP_RX_AGGR_BUF_RESET(card); + + if (f_do_rx_cur && skb) /* Single transfer pending. Free curr buff also */ + dev_kfree_skb_any(skb); + + return ret; +} + +static int nxpwifi_process_int_status(struct nxpwifi_adapter *adapter, u8 sdio_ireg) +{ + struct sdio_mmc_card *card = adapter->card; + const struct nxpwifi_sdio_card_reg *reg = card->reg; + int ret = 0; + struct sk_buff *skb; + u8 port; + u32 len_reg_l, len_reg_u; + u32 rx_blocks; + u16 rx_len; + u32 bitmap; + u8 cr; + + if (!sdio_ireg) + return ret; + + if (sdio_ireg & DN_LD_CMD_PORT_HOST_INT_STATUS && adapter->cmd_sent) + adapter->cmd_sent = false; + + if (sdio_ireg & UP_LD_CMD_PORT_HOST_INT_STATUS) { + u32 pkt_type; + + /* read the len of control packet */ + rx_len = card->mp_regs[reg->cmd_rd_len_1] << 8; + rx_len |= (u16)card->mp_regs[reg->cmd_rd_len_0]; + rx_blocks = DIV_ROUND_UP(rx_len, NXPWIFI_SDIO_BLOCK_SIZE); + if (rx_len <= adapter->intf_hdr_len || + (rx_blocks * NXPWIFI_SDIO_BLOCK_SIZE) > + NXPWIFI_RX_DATA_BUF_SIZE) + return -EINVAL; + rx_len = (u16)(rx_blocks * NXPWIFI_SDIO_BLOCK_SIZE); + + skb = nxpwifi_alloc_dma_align_buf(rx_len, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + skb_put(skb, rx_len); + + ret = nxpwifi_sdio_card_to_host(adapter, &pkt_type, skb->data, + skb->len, adapter->ioport | + CMD_PORT_SLCT); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "failed to card_to_host"); + dev_kfree_skb_any(skb); + goto term_cmd; + } + + if (pkt_type != NXPWIFI_TYPE_CMD && + pkt_type != NXPWIFI_TYPE_EVENT) + nxpwifi_dbg(adapter, ERROR, "Received wrong packet on cmd port"); + + nxpwifi_decode_rx_packet(adapter, skb, pkt_type); + } + + if (sdio_ireg & DN_LD_HOST_INT_STATUS) { + bitmap = (u32)card->mp_regs[reg->wr_bitmap_l]; + bitmap |= ((u32)card->mp_regs[reg->wr_bitmap_u]) << 8; + bitmap |= ((u32)card->mp_regs[reg->wr_bitmap_1l]) << 16; + bitmap |= ((u32)card->mp_regs[reg->wr_bitmap_1u]) << 24; + card->mp_wr_bitmap = bitmap; + + nxpwifi_dbg(adapter, INTR, "intr: wr_bitmap=0x%x\n", card->mp_wr_bitmap); + if (adapter->data_sent && + (card->mp_wr_bitmap & card->mp_data_port_mask)) { + nxpwifi_dbg(adapter, INTR, "Tx DONE\n"); + adapter->data_sent = false; + } + } + + nxpwifi_dbg(adapter, INTR, "cmd_sent=%d data_sent=%d\n", + adapter->cmd_sent, adapter->data_sent); + if (sdio_ireg & UP_LD_HOST_INT_STATUS) { + bitmap = (u32)card->mp_regs[reg->rd_bitmap_l]; + bitmap |= ((u32)card->mp_regs[reg->rd_bitmap_u]) << 8; + bitmap |= ((u32)card->mp_regs[reg->rd_bitmap_1l]) << 16; + bitmap |= ((u32)card->mp_regs[reg->rd_bitmap_1u]) << 24; + card->mp_rd_bitmap = bitmap; + nxpwifi_dbg(adapter, INTR, "intr: rd_bitmap=0x%x\n", card->mp_rd_bitmap); + + while (true) { + ret = nxpwifi_get_rd_port(adapter, &port); + + if (ret) + break; + + len_reg_l = reg->rd_len_p0_l + (port << 1); + len_reg_u = reg->rd_len_p0_u + (port << 1); + rx_len = ((u16)card->mp_regs[len_reg_u]) << 8; + rx_len |= (u16)card->mp_regs[len_reg_l]; + rx_blocks = + (rx_len + NXPWIFI_SDIO_BLOCK_SIZE - + 1) / NXPWIFI_SDIO_BLOCK_SIZE; + if (rx_len <= adapter->intf_hdr_len || + (card->mpa_rx.enabled && + ((rx_blocks * NXPWIFI_SDIO_BLOCK_SIZE) > + card->mpa_rx.buf_size))) { + nxpwifi_dbg(adapter, ERROR, "invalid rx_len=%d\n", rx_len); + return -EINVAL; + } + + rx_len = (u16)(rx_blocks * NXPWIFI_SDIO_BLOCK_SIZE); + + ret = nxpwifi_sdio_card_to_host_mp_aggr(adapter, rx_len, + port); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "card_to_host_mpa failed: int status=%#x\n", + sdio_ireg); + goto term_cmd; + } + } + } + + return 0; + +term_cmd: + /* terminate cmd */ + if (nxpwifi_read_reg(adapter, CONFIGURATION_REG, &cr)) + nxpwifi_dbg(adapter, ERROR, "read CFG reg failed\n"); + else + nxpwifi_dbg(adapter, INFO, "info: CFG reg val = %d\n", cr); + + if (nxpwifi_write_reg(adapter, CONFIGURATION_REG, (cr | 0x04))) + nxpwifi_dbg(adapter, ERROR, "write CFG reg failed\n"); + else + nxpwifi_dbg(adapter, INFO, "info: write success\n"); + + if (nxpwifi_read_reg(adapter, CONFIGURATION_REG, &cr)) + nxpwifi_dbg(adapter, ERROR, "read CFG reg failed\n"); + else + nxpwifi_dbg(adapter, INFO, "info: CFG reg val =%x\n", cr); + + return ret; +} + +/* Transmit using SDIO multi-port aggregation. */ +static int nxpwifi_host_to_card_mp_aggr(struct nxpwifi_adapter *adapter, + u8 *payload, u32 pkt_len, u32 port, + u32 next_pkt_len) +{ + struct sdio_mmc_card *card = adapter->card; + int ret = 0; + s32 f_send_aggr_buf = 0; + s32 f_send_cur_buf = 0; + s32 f_precopy_cur_buf = 0; + s32 f_postcopy_cur_buf = 0; + u32 mport; + int index; + + if (!card->mpa_tx.enabled || port == CMD_PORT_SLCT) { + nxpwifi_dbg(adapter, WARN, "tx aggregation disabled\n"); + f_send_cur_buf = 1; + goto tx_curr_single; + } + + if (next_pkt_len) { + /* More pkt in TX queue */ + + if (MP_TX_AGGR_IN_PROGRESS(card)) { + if (MP_TX_AGGR_BUF_HAS_ROOM(card, pkt_len)) { + f_precopy_cur_buf = 1; + + if (!(card->mp_wr_bitmap & + (1 << card->curr_wr_port)) || + !MP_TX_AGGR_BUF_HAS_ROOM + (card, pkt_len + next_pkt_len)) + f_send_aggr_buf = 1; + } else { + /* No room in Aggr buf, send it */ + f_send_aggr_buf = 1; + + if (!(card->mp_wr_bitmap & + (1 << card->curr_wr_port))) + f_send_cur_buf = 1; + else + f_postcopy_cur_buf = 1; + } + } else { + if (MP_TX_AGGR_BUF_HAS_ROOM(card, pkt_len) && + (card->mp_wr_bitmap & (1 << card->curr_wr_port))) + f_precopy_cur_buf = 1; + else + f_send_cur_buf = 1; + } + } else { + /* Last pkt in TX queue */ + + if (MP_TX_AGGR_IN_PROGRESS(card)) { + /* some packs in Aggr buf already */ + f_send_aggr_buf = 1; + + if (MP_TX_AGGR_BUF_HAS_ROOM(card, pkt_len)) + f_precopy_cur_buf = 1; + else + /* No room in Aggr buf, send it */ + f_send_cur_buf = 1; + } else { + f_send_cur_buf = 1; + } + } + + if (f_precopy_cur_buf) { + MP_TX_AGGR_BUF_PUT(card, payload, pkt_len, port); + + if (MP_TX_AGGR_PKT_LIMIT_REACHED(card) || + mp_tx_aggr_port_limit_reached(card)) + /* No more pkts allowed in Aggr buf, send it */ + f_send_aggr_buf = 1; + } + + if (f_send_aggr_buf) { + u32 port_count; + int i; + + for (i = 0, port_count = 0; i < card->max_ports; i++) + if (card->mpa_tx.ports & BIT(i)) + port_count++; + + /* + * Writing data from "start_port + 0" to "start_port + + * port_count -1", so decrease the count by 1 + */ + port_count--; + mport = (adapter->ioport | SDIO_MPA_ADDR_BASE | + (port_count << 8)) + card->mpa_tx.start_port; + + if (card->mpa_tx.pkt_cnt == 1) + mport = adapter->ioport + card->mpa_tx.start_port; + + ret = nxpwifi_write_data_to_card(adapter, card->mpa_tx.buf, + card->mpa_tx.buf_len, mport); + + /* Save the last multi port tx aggregation info to debug log */ + index = adapter->dbg.last_sdio_mp_index; + index = (index + 1) % NXPWIFI_DBG_SDIO_MP_NUM; + adapter->dbg.last_sdio_mp_index = index; + adapter->dbg.last_mp_wr_ports[index] = mport; + adapter->dbg.last_mp_wr_bitmap[index] = card->mp_wr_bitmap; + adapter->dbg.last_mp_wr_len[index] = card->mpa_tx.buf_len; + adapter->dbg.last_mp_curr_wr_port[index] = card->curr_wr_port; + + MP_TX_AGGR_BUF_RESET(card); + } + +tx_curr_single: + if (f_send_cur_buf) + ret = nxpwifi_write_data_to_card(adapter, payload, pkt_len, + adapter->ioport + port); + + if (f_postcopy_cur_buf) + MP_TX_AGGR_BUF_PUT(card, payload, pkt_len, port); + + return ret; +} + +static int nxpwifi_sdio_host_to_card(struct nxpwifi_adapter *adapter, + u8 type, struct sk_buff *skb, + struct nxpwifi_tx_param *tx_param) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + u32 buf_block_len; + u32 blk_size; + u32 port; + u8 *payload = (u8 *)skb->data; + u32 pkt_len = skb->len; + + /* Allocate buffer and copy payload */ + blk_size = NXPWIFI_SDIO_BLOCK_SIZE; + buf_block_len = (pkt_len + blk_size - 1) / blk_size; + put_unaligned_le16((u16)pkt_len, payload + 0); + put_unaligned_le16((u16)type, payload + 2); + + /* + * This is SDIO specific header + * u16 length, + * u16 type (NXPWIFI_TYPE_DATA = 0, NXPWIFI_TYPE_CMD = 1, + * NXPWIFI_TYPE_EVENT = 3) + */ + if (type == NXPWIFI_TYPE_DATA) { + ret = nxpwifi_get_wr_port_data(adapter, &port); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "no wr_port available\n"); + return ret; + } + } else { + adapter->cmd_sent = true; + + if (pkt_len <= adapter->intf_hdr_len || + pkt_len > NXPWIFI_UPLD_SIZE) { + nxpwifi_dbg(adapter, ERROR, + "invalid upld pkt_len=%u (hdr_len=%u, max=%u)\n", + pkt_len, adapter->intf_hdr_len, NXPWIFI_UPLD_SIZE); + return -EINVAL; + } + + port = CMD_PORT_SLCT; + } + + /* Transfer data to card */ + pkt_len = buf_block_len * blk_size; + + if (tx_param) + ret = nxpwifi_host_to_card_mp_aggr(adapter, payload, pkt_len, + port, tx_param->next_pkt_len + ); + else + ret = nxpwifi_host_to_card_mp_aggr(adapter, payload, pkt_len, + port, 0); + + if (ret) { + if (type == NXPWIFI_TYPE_CMD || + type == NXPWIFI_TYPE_VDLL) + adapter->cmd_sent = false; + if (type == NXPWIFI_TYPE_DATA) { + adapter->data_sent = false; + /* restore curr_wr_port in error cases */ + card->curr_wr_port = port; + card->mp_wr_bitmap |= (u32)(1 << card->curr_wr_port); + } + } else { + if (type == NXPWIFI_TYPE_DATA) { + if (!(card->mp_wr_bitmap & (1 << card->curr_wr_port))) + adapter->data_sent = true; + else + adapter->data_sent = false; + } + } + + return ret; +} + +static int nxpwifi_alloc_sdio_mpa_buffers(struct nxpwifi_adapter *adapter, + u32 mpa_tx_buf_size, + u32 mpa_rx_buf_size) +{ + struct sdio_mmc_card *card = adapter->card; + u32 rx_buf_size; + int ret = 0; + + card->mpa_tx.buf = kzalloc(mpa_tx_buf_size, GFP_KERNEL); + if (!card->mpa_tx.buf) { + ret = -ENOMEM; + goto error; + } + + card->mpa_tx.buf_size = mpa_tx_buf_size; + + rx_buf_size = max_t(u32, mpa_rx_buf_size, + (u32)SDIO_MAX_AGGR_BUF_SIZE); + card->mpa_rx.buf = kzalloc(rx_buf_size, GFP_KERNEL); + if (!card->mpa_rx.buf) { + ret = -ENOMEM; + goto error; + } + + card->mpa_rx.buf_size = rx_buf_size; + +error: + if (ret) { + kfree(card->mpa_tx.buf); + kfree(card->mpa_rx.buf); + card->mpa_tx.buf_size = 0; + card->mpa_rx.buf_size = 0; + card->mpa_tx.buf = NULL; + card->mpa_rx.buf = NULL; + } + + return ret; +} + +static void +nxpwifi_unregister_dev(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + if (adapter->card) { + card->adapter = NULL; + sdio_claim_host(card->func); + sdio_disable_func(card->func); + sdio_release_host(card->func); + } +} + +static int nxpwifi_register_dev(struct nxpwifi_adapter *adapter) +{ + int ret; + struct sdio_mmc_card *card = adapter->card; + struct sdio_func *func = card->func; + const char *firmware = card->firmware; + + /* save adapter pointer in card */ + card->adapter = adapter; + adapter->tx_buf_size = card->tx_buf_size; + + sdio_claim_host(func); + + /* Set block size */ + ret = sdio_set_block_size(card->func, NXPWIFI_SDIO_BLOCK_SIZE); + sdio_release_host(func); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "cannot set SDIO block size\n"); + return ret; + } + + /* + * Select correct firmware (sdsd or sdiouart) firmware based on the strapping + * option + */ + if (card->firmware_sdiouart) { + u8 val; + + nxpwifi_read_reg(adapter, card->reg->host_strap_reg, &val); + if ((val & card->reg->host_strap_mask) == card->reg->host_strap_value) + firmware = card->firmware_sdiouart; + } + strscpy(adapter->fw_name, firmware, sizeof(adapter->fw_name)); + + if (card->fw_dump_enh) { + adapter->mem_type_mapping_tbl = generic_mem_type_map; + adapter->num_mem_types = 1; + } else { + adapter->mem_type_mapping_tbl = mem_type_mapping_tbl; + adapter->num_mem_types = ARRAY_SIZE(mem_type_mapping_tbl); + } + + return 0; +} + +static int nxpwifi_init_sdio(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + const struct nxpwifi_sdio_card_reg *reg = card->reg; + int ret; + u8 sdio_ireg; + + sdio_set_drvdata(card->func, card); + + /* + * Read the host_int_status_reg for ACK the first interrupt got + * from the bootloader. If we don't do this we get a interrupt + * as soon as we register the irq. + */ + nxpwifi_read_reg(adapter, card->reg->host_int_status_reg, &sdio_ireg); + + /* Get SDIO ioport */ + if (nxpwifi_init_sdio_ioport(adapter)) + return -EIO; + + /* Initialize SDIO variables in card */ + card->mp_rd_bitmap = 0; + card->mp_wr_bitmap = 0; + card->curr_rd_port = reg->start_rd_port; + card->curr_wr_port = reg->start_wr_port; + + card->mp_data_port_mask = reg->data_port_mask; + + card->mpa_tx.buf_len = 0; + card->mpa_tx.pkt_cnt = 0; + card->mpa_tx.start_port = 0; + + card->mpa_tx.enabled = 1; + card->mpa_tx.pkt_aggr_limit = card->mp_agg_pkt_limit; + + card->mpa_rx.buf_len = 0; + card->mpa_rx.pkt_cnt = 0; + card->mpa_rx.start_port = 0; + + card->mpa_rx.enabled = 1; + card->mpa_rx.pkt_aggr_limit = card->mp_agg_pkt_limit; + + /* Allocate buffers for SDIO MP-A */ + card->mp_regs = devm_kzalloc(&card->func->dev, reg->max_mp_regs, GFP_KERNEL); + + if (!card->mp_regs) + return -ENOMEM; + + card->mpa_rx.len_arr = + devm_kcalloc(&card->func->dev, card->mp_agg_pkt_limit, + sizeof(*card->mpa_rx.len_arr), GFP_KERNEL); + + if (!card->mpa_rx.len_arr) + return -ENOMEM; + + ret = nxpwifi_alloc_sdio_mpa_buffers(adapter, + card->mp_tx_agg_buf_size, + card->mp_rx_agg_buf_size); + + /* Allocate 32k MPA Tx/Rx buffers if 64k memory allocation fails */ + if (ret && (card->mp_tx_agg_buf_size == NXPWIFI_MP_AGGR_BSIZE_MAX || + card->mp_rx_agg_buf_size == NXPWIFI_MP_AGGR_BSIZE_MAX)) { + /* Disable rx single port aggregation */ + adapter->host_disable_sdio_rx_aggr = true; + + ret = nxpwifi_alloc_sdio_mpa_buffers(adapter, + NXPWIFI_MP_AGGR_BSIZE_32K, + NXPWIFI_MP_AGGR_BSIZE_32K); + if (ret) { + /* Disable multi port aggregation */ + card->mpa_tx.enabled = 0; + card->mpa_rx.enabled = 0; + } + } + + adapter->ext_scan = card->can_ext_scan; + return ret; +} + +static void nxpwifi_cleanup_mpa_buf(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + MP_TX_AGGR_BUF_RESET(card); + MP_RX_AGGR_BUF_RESET(card); +} + +static void nxpwifi_cleanup_sdio(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + cancel_work_sync(&card->work); + + kfree(card->mpa_tx.buf); + kfree(card->mpa_rx.buf); +} + +static void +nxpwifi_update_mp_end_port(struct nxpwifi_adapter *adapter, u16 port) +{ + struct sdio_mmc_card *card = adapter->card; + const struct nxpwifi_sdio_card_reg *reg = card->reg; + int i; + + card->mp_end_port = port; + + card->mp_data_port_mask = reg->data_port_mask; + + if (reg->start_wr_port) { + for (i = 1; i <= card->max_ports - card->mp_end_port; i++) + card->mp_data_port_mask &= + ~(1 << (card->max_ports - i)); + } + + card->curr_wr_port = reg->start_wr_port; +} + +/* Perform an SDIO card reset in workqueue context. */ +static void nxpwifi_sdio_card_reset_work(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + struct sdio_func *func = card->func; + int ret; + + /* Prepare the adapter for the reset. */ + nxpwifi_shutdown_sw(adapter); + clear_bit(NXPWIFI_IFACE_WORK_DEVICE_DUMP, &card->work_flags); + clear_bit(NXPWIFI_IFACE_WORK_CARD_RESET, &card->work_flags); + + /* Run a HW reset of the SDIO interface. */ + sdio_claim_host(func); + ret = mmc_hw_reset(func->card); + sdio_release_host(func); + + switch (ret) { + case 1: + nxpwifi_dbg(adapter, MSG, "SDIO HW reset asynchronous\n"); + complete_all(adapter->fw_done); + break; + case 0: + ret = nxpwifi_reinit_sw(adapter); + if (ret) + dev_err(&func->dev, "reinit failed: %d\n", ret); + break; + default: + dev_err(&func->dev, "SDIO HW reset failed: %d\n", ret); + break; + } +} + +static enum +rdwr_status nxpwifi_sdio_rdwr_firmware(struct nxpwifi_adapter *adapter, + u8 doneflag) +{ + struct sdio_mmc_card *card = adapter->card; + int ret, tries; + u8 ctrl_data = 0; + + sdio_writeb(card->func, card->reg->fw_dump_host_ready, + card->reg->fw_dump_ctrl, &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO Write ERR\n"); + return RDWR_STATUS_FAILURE; + } + for (tries = 0; tries < MAX_POLL_TRIES; tries++) { + ctrl_data = sdio_readb(card->func, card->reg->fw_dump_ctrl, + &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO read err\n"); + return RDWR_STATUS_FAILURE; + } + if (ctrl_data == FW_DUMP_DONE) + break; + if (doneflag && ctrl_data == doneflag) + return RDWR_STATUS_DONE; + if (ctrl_data != card->reg->fw_dump_host_ready) { + nxpwifi_dbg(adapter, WARN, + "The ctrl reg was changed, re-try again\n"); + sdio_writeb(card->func, card->reg->fw_dump_host_ready, + card->reg->fw_dump_ctrl, &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO write err\n"); + return RDWR_STATUS_FAILURE; + } + } + usleep_range(100, 200); + } + if (ctrl_data == card->reg->fw_dump_host_ready) { + nxpwifi_dbg(adapter, ERROR, "Fail to pull ctrl_data\n"); + return RDWR_STATUS_FAILURE; + } + + return RDWR_STATUS_SUCCESS; +} + +/* Dump firmware memories for post-mortem analysis. */ +static void nxpwifi_sdio_fw_dump(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + int ret = 0; + unsigned int reg, reg_start, reg_end; + u8 *dbg_ptr, *end_ptr, dump_num, idx, i, read_reg, doneflag = 0; + enum rdwr_status stat; + u32 memory_size; + + if (!card->can_dump_fw) + return; + + for (idx = 0; idx < ARRAY_SIZE(mem_type_mapping_tbl); idx++) { + struct memory_type_mapping *entry = &mem_type_mapping_tbl[idx]; + + if (entry->mem_ptr) { + vfree(entry->mem_ptr); + entry->mem_ptr = NULL; + } + entry->mem_size = 0; + } + + nxpwifi_pm_wakeup_card(adapter); + sdio_claim_host(card->func); + + nxpwifi_dbg(adapter, MSG, "== nxpwifi firmware dump start ==\n"); + + stat = nxpwifi_sdio_rdwr_firmware(adapter, doneflag); + if (stat == RDWR_STATUS_FAILURE) + goto done; + + reg = card->reg->fw_dump_start; + /* Read the number of the memories which will dump */ + dump_num = sdio_readb(card->func, reg, &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO read memory length err\n"); + goto done; + } + + /* Read the length of every memory which will dump */ + for (idx = 0; idx < dump_num; idx++) { + struct memory_type_mapping *entry = &mem_type_mapping_tbl[idx]; + + stat = nxpwifi_sdio_rdwr_firmware(adapter, doneflag); + if (stat == RDWR_STATUS_FAILURE) + goto done; + + memory_size = 0; + reg = card->reg->fw_dump_start; + for (i = 0; i < 4; i++) { + read_reg = sdio_readb(card->func, reg, &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO read err\n"); + goto done; + } + memory_size |= (read_reg << i * 8); + reg++; + } + + if (memory_size == 0) { + nxpwifi_dbg(adapter, DUMP, "Firmware dump Finished!\n"); + ret = nxpwifi_write_reg(adapter, + card->reg->fw_dump_ctrl, + FW_DUMP_READ_DONE); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO write err\n"); + return; + } + break; + } + + nxpwifi_dbg(adapter, DUMP, + "%s_SIZE=0x%x\n", entry->mem_name, memory_size); + entry->mem_ptr = vmalloc(memory_size + 1); + entry->mem_size = memory_size; + if (!entry->mem_ptr) + goto done; + dbg_ptr = entry->mem_ptr; + end_ptr = dbg_ptr + memory_size; + + doneflag = entry->done_flag; + nxpwifi_dbg(adapter, DUMP, "Start %s output, please wait...\n", + entry->mem_name); + + do { + stat = nxpwifi_sdio_rdwr_firmware(adapter, doneflag); + if (stat == RDWR_STATUS_FAILURE) + goto done; + + reg_start = card->reg->fw_dump_start; + reg_end = card->reg->fw_dump_end; + for (reg = reg_start; reg <= reg_end; reg++) { + *dbg_ptr = sdio_readb(card->func, reg, &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO read err\n"); + goto done; + } + if (dbg_ptr < end_ptr) + dbg_ptr++; + else + nxpwifi_dbg(adapter, ERROR, "Allocated buf not enough\n"); + } + + if (stat != RDWR_STATUS_DONE) + continue; + + nxpwifi_dbg(adapter, DUMP, "%s done: size=0x%tx\n", + entry->mem_name, dbg_ptr - entry->mem_ptr); + break; + } while (1); + } + nxpwifi_dbg(adapter, MSG, "== nxpwifi firmware dump end ==\n"); + +done: + sdio_release_host(card->func); +} + +/* Generic firmware dump flow for enhanced devices. */ +static void nxpwifi_sdio_generic_fw_dump(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + struct memory_type_mapping *entry = &generic_mem_type_map[0]; + unsigned int reg, reg_start, reg_end; + u8 start_flag = 0, done_flag = 0; + u8 *dbg_ptr, *end_ptr; + enum rdwr_status stat; + int ret = -EPERM, tries; + + if (!card->fw_dump_enh) + return; + + if (entry->mem_ptr) { + vfree(entry->mem_ptr); + entry->mem_ptr = NULL; + } + entry->mem_size = 0; + + nxpwifi_pm_wakeup_card(adapter); + sdio_claim_host(card->func); + + nxpwifi_dbg(adapter, MSG, "== nxpwifi firmware dump start ==\n"); + + stat = nxpwifi_sdio_rdwr_firmware(adapter, done_flag); + if (stat == RDWR_STATUS_FAILURE) + goto done; + + reg_start = card->reg->fw_dump_start; + reg_end = card->reg->fw_dump_end; + for (reg = reg_start; reg <= reg_end; reg++) { + for (tries = 0; tries < MAX_POLL_TRIES; tries++) { + start_flag = sdio_readb(card->func, reg, &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO read err\n"); + goto done; + } + if (start_flag == 0) + break; + if (tries == MAX_POLL_TRIES) { + nxpwifi_dbg(adapter, ERROR, "FW not ready to dump\n"); + ret = -EPERM; + goto done; + } + } + usleep_range(100, 200); + } + + entry->mem_ptr = vmalloc(0xf0000 + 1); + if (!entry->mem_ptr) { + ret = -ENOMEM; + goto done; + } + dbg_ptr = entry->mem_ptr; + entry->mem_size = 0xf0000; + end_ptr = dbg_ptr + entry->mem_size; + + done_flag = entry->done_flag; + nxpwifi_dbg(adapter, DUMP, + "Start %s output, please wait...\n", entry->mem_name); + + while (true) { + stat = nxpwifi_sdio_rdwr_firmware(adapter, done_flag); + if (stat == RDWR_STATUS_FAILURE) + goto done; + for (reg = reg_start; reg <= reg_end; reg++) { + *dbg_ptr = sdio_readb(card->func, reg, &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO read err\n"); + goto done; + } + dbg_ptr++; + if (dbg_ptr >= end_ptr) { + u8 *tmp_ptr; + + tmp_ptr = vmalloc(entry->mem_size + 0x4000 + 1); + if (!tmp_ptr) + goto done; + + memcpy(tmp_ptr, entry->mem_ptr, + entry->mem_size); + vfree(entry->mem_ptr); + entry->mem_ptr = tmp_ptr; + tmp_ptr = NULL; + dbg_ptr = entry->mem_ptr + entry->mem_size; + entry->mem_size += 0x4000; + end_ptr = entry->mem_ptr + entry->mem_size; + } + } + if (stat == RDWR_STATUS_DONE) { + entry->mem_size = dbg_ptr - entry->mem_ptr; + nxpwifi_dbg(adapter, DUMP, "dump %s done size=0x%x\n", + entry->mem_name, entry->mem_size); + ret = 0; + break; + } + } + nxpwifi_dbg(adapter, MSG, "== nxpwifi firmware dump end ==\n"); + +done: + if (ret) { + nxpwifi_dbg(adapter, ERROR, "firmware dump failed\n"); + if (entry->mem_ptr) { + vfree(entry->mem_ptr); + entry->mem_ptr = NULL; + } + entry->mem_size = 0; + } + sdio_release_host(card->func); +} + +/* Build and upload consolidated device dump. */ +static void nxpwifi_sdio_device_dump_work(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + adapter->devdump_data = vzalloc(NXPWIFI_FW_DUMP_SIZE); + if (!adapter->devdump_data) + return; + + nxpwifi_drv_info_dump(adapter); + + /* Generic firmware dump flow for enhanced devices. */ + if (card->fw_dump_enh) + nxpwifi_sdio_generic_fw_dump(adapter); + /* Dump firmware memories for post-mortem analysis. */ + else + nxpwifi_sdio_fw_dump(adapter); + + nxpwifi_prepare_fw_dump_info(adapter); + nxpwifi_upload_device_dump(adapter); +} + +/* Process deferred SDIO work items. */ +static void nxpwifi_sdio_work(struct work_struct *work) +{ + struct sdio_mmc_card *card = + container_of(work, struct sdio_mmc_card, work); + + /* Build and upload consolidated device dump. */ + if (test_and_clear_bit(NXPWIFI_IFACE_WORK_DEVICE_DUMP, + &card->work_flags)) + nxpwifi_sdio_device_dump_work(card->adapter); + + /* Perform an SDIO card reset in workqueue context. */ + if (test_and_clear_bit(NXPWIFI_IFACE_WORK_CARD_RESET, + &card->work_flags)) + nxpwifi_sdio_card_reset_work(card->adapter); +} + +/* Schedule SDIO card reset. */ +static void nxpwifi_sdio_card_reset(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + if (!test_and_set_bit(NXPWIFI_IFACE_WORK_CARD_RESET, &card->work_flags)) + nxpwifi_queue_work(adapter, &card->work); +} + +static void nxpwifi_sdio_device_dump(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + if (!test_and_set_bit(NXPWIFI_IFACE_WORK_DEVICE_DUMP, + &card->work_flags)) + nxpwifi_queue_work(adapter, &card->work); +} + +/* Dump SDIO function and scratch registers into drv_buf. */ +static int +nxpwifi_sdio_reg_dump(struct nxpwifi_adapter *adapter, char *drv_buf) +{ + char *p = drv_buf; + struct sdio_mmc_card *cardp = adapter->card; + int ret = 0; + u8 count, func, data, index = 0, size = 0; + u8 reg, reg_start, reg_end; + char buf[256], *ptr; + + if (!p) + return 0; + + nxpwifi_dbg(adapter, MSG, "SDIO register dump start\n"); + + nxpwifi_pm_wakeup_card(adapter); + + sdio_claim_host(cardp->func); + + for (count = 0; count < 5; count++) { + memset(buf, 0, sizeof(buf)); + ptr = buf; + + switch (count) { + case 0: + /* Read the registers of SDIO function0 */ + func = count; + reg_start = 0; + reg_end = 9; + break; + case 1: + /* Read the registers of SDIO function1 */ + func = count; + reg_start = cardp->reg->func1_dump_reg_start; + reg_end = cardp->reg->func1_dump_reg_end; + break; + case 2: + index = 0; + func = 1; + reg_start = cardp->reg->func1_spec_reg_table[index++]; + size = cardp->reg->func1_spec_reg_num; + reg_end = cardp->reg->func1_spec_reg_table[size - 1]; + break; + default: + /* Read the scratch registers of SDIO function1 */ + if (count == 4) + msleep(100); + func = 1; + reg_start = cardp->reg->func1_scratch_reg; + reg_end = reg_start + NXPWIFI_SDIO_SCRATCH_SIZE; + } + + if (count != 2) + ptr += scnprintf(ptr, sizeof(buf) - (ptr - buf), + "SDIO Func%d (%#x-%#x): ", func, reg_start, + reg_end); + else + ptr += scnprintf(ptr, sizeof(buf) - (ptr - buf), + "SDIO Func%d: ", func); + + for (reg = reg_start; reg <= reg_end;) { + if (func == 0) + data = sdio_f0_readb(cardp->func, reg, &ret); + else + data = sdio_readb(cardp->func, reg, &ret); + + if (count == 2) + ptr += scnprintf(ptr, sizeof(buf) - (ptr - buf), "(%#x) ", reg); + if (!ret) { + ptr += scnprintf(ptr, sizeof(buf) - (ptr - buf), "%02x ", data); + } else { + ptr += scnprintf(ptr, sizeof(buf) - (ptr - buf), "ERR"); + break; + } + + if (count == 2 && reg < reg_end) + reg = cardp->reg->func1_spec_reg_table[index++]; + else + reg++; + } + + nxpwifi_dbg(adapter, MSG, "%s\n", buf); + p += sprintf(p, "%s\n", buf); + } + + sdio_release_host(cardp->func); + + nxpwifi_dbg(adapter, MSG, "SDIO register dump end\n"); + + return p - drv_buf; +} + +static void nxpwifi_sdio_up_dev(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + u8 sdio_ireg; + int ret = 0; + + sdio_claim_host(card->func); + ret = sdio_enable_func(card->func); + + if (ret) + nxpwifi_dbg(adapter, ERROR, "sdio_enable_func failed: %d\n", ret); + + ret = sdio_set_block_size(card->func, NXPWIFI_SDIO_BLOCK_SIZE); + + if (ret) + nxpwifi_dbg(adapter, ERROR, "sdio_set_block_size failed: %d\n", ret); + + sdio_release_host(card->func); + + /* + * tx_buf_size might be changed to 3584 by firmware during + * data transfer, we will reset to default size. + */ + adapter->tx_buf_size = card->tx_buf_size; + + /* + * Read the host_int_status_reg for ACK the first interrupt got + * from the bootloader. If we don't do this we get a interrupt + * as soon as we register the irq. + */ + nxpwifi_read_reg(adapter, card->reg->host_int_status_reg, &sdio_ireg); + + if (nxpwifi_init_sdio_ioport(adapter)) + nxpwifi_dbg(adapter, ERROR, "error enabling SDIO port\n"); +} + +static struct nxpwifi_if_ops sdio_ops = { + .init_if = nxpwifi_init_sdio, + .cleanup_if = nxpwifi_cleanup_sdio, + .check_fw_status = nxpwifi_check_fw_status, + .check_winner_status = nxpwifi_check_winner_status, + .prog_fw = nxpwifi_prog_fw_w_helper, + .register_dev = nxpwifi_register_dev, + .unregister_dev = nxpwifi_unregister_dev, + .enable_int = nxpwifi_sdio_enable_host_int, + .disable_int = nxpwifi_sdio_disable_host_int, + .process_int_status = nxpwifi_process_int_status, + .host_to_card = nxpwifi_sdio_host_to_card, + .wakeup = nxpwifi_pm_wakeup_card, + .wakeup_complete = nxpwifi_pm_wakeup_card_complete, + + /* SDIO specific */ + .update_mp_end_port = nxpwifi_update_mp_end_port, + .cleanup_mpa_buf = nxpwifi_cleanup_mpa_buf, + .cmdrsp_complete = nxpwifi_sdio_cmdrsp_complete, + .event_complete = nxpwifi_sdio_event_complete, + .dnld_fw = nxpwifi_sdio_dnld_fw, + .card_reset = nxpwifi_sdio_card_reset, + .reg_dump = nxpwifi_sdio_reg_dump, + .device_dump = nxpwifi_sdio_device_dump, + .deaggr_pkt = nxpwifi_deaggr_sdio_pkt, + .up_dev = nxpwifi_sdio_up_dev, +}; + +module_sdio_driver(nxpwifi_sdio); + +MODULE_AUTHOR("NXP International Ltd."); +MODULE_DESCRIPTION("NXP WiFi SDIO Driver version " SDIO_VERSION); +MODULE_VERSION(SDIO_VERSION); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(IW61X_SDIO_FW_NAME); diff --git a/drivers/net/wireless/nxp/nxpwifi/sdio.h b/drivers/net/wireless/nxp/nxpwifi/sdio.h new file mode 100644 index 000000000000..de5c884a5b14 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/sdio.h @@ -0,0 +1,340 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * NXP Wireless LAN device driver: SDIO specific definitions + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_SDIO_H +#define _NXPWIFI_SDIO_H + +#include "main.h" + +#define IW61X_SDIO_FW_NAME "nxp/sd_w61x_v1.bin.se" + +#define BLOCK_MODE 1 +#define BYTE_MODE 0 + +#define NXPWIFI_SDIO_IO_PORT_MASK 0xfffff + +#define NXPWIFI_SDIO_BYTE_MODE_MASK 0x80000000 + +#define NXPWIFI_MAX_FUNC2_REG_NUM 13 +#define NXPWIFI_SDIO_SCRATCH_SIZE 10 + +#define SDIO_MPA_ADDR_BASE 0x1000 + +#define CMD_PORT_UPLD_INT_MASK (0x1U << 6) +#define CMD_PORT_DNLD_INT_MASK (0x1U << 7) +#define HOST_TERM_CMD53 (0x1U << 2) +#define REG_PORT 0 +#define MEM_PORT 0x10000 + +#define CMD53_NEW_MODE (0x1U << 0) +#define CMD_PORT_RD_LEN_EN (0x1U << 2) +#define CMD_PORT_AUTO_EN (0x1U << 0) +#define CMD_PORT_SLCT 0x8000 +#define UP_LD_CMD_PORT_HOST_INT_STATUS (0x40U) +#define DN_LD_CMD_PORT_HOST_INT_STATUS (0x80U) + +#define NXPWIFI_MP_AGGR_BSIZE_32K (32768) +/* we leave one block of 256 bytes for DMA alignment*/ +#define NXPWIFI_MP_AGGR_BSIZE_MAX (65280) + +/* Misc. Config Register : Auto Re-enable interrupts */ +#define AUTO_RE_ENABLE_INT BIT(4) + +/* Host Control Registers : Configuration */ +#define CONFIGURATION_REG 0x00 +/* Host Control Registers : Host power up */ +#define HOST_POWER_UP (0x1U << 1) + +/* Host Control Registers : Upload host interrupt mask */ +#define UP_LD_HOST_INT_MASK (0x1U) +/* Host Control Registers : Download host interrupt mask */ +#define DN_LD_HOST_INT_MASK (0x2U) + +/* Host Control Registers : Upload host interrupt status */ +#define UP_LD_HOST_INT_STATUS (0x1U) +/* Host Control Registers : Download host interrupt status */ +#define DN_LD_HOST_INT_STATUS (0x2U) + +/* Host Control Registers : Host interrupt status */ +#define CARD_INT_STATUS_REG 0x28 + +/* Card Control Registers : Card I/O ready */ +#define CARD_IO_READY (0x1U << 3) +/* Card Control Registers : Download card ready */ +#define DN_LD_CARD_RDY (0x1U << 0) + +/* Max retry number of CMD53 write */ +#define MAX_WRITE_IOMEM_RETRY 2 + +/* SDIO Tx aggregation in progress ? */ +#define MP_TX_AGGR_IN_PROGRESS(a) ((a)->mpa_tx.pkt_cnt > 0) + +/* SDIO Tx aggregation buffer room for next packet ? */ +#define MP_TX_AGGR_BUF_HAS_ROOM(a, len) ({ \ + typeof(a) (_a) = a; \ + (((_a)->mpa_tx.buf_len + (len)) <= (_a)->mpa_tx.buf_size); \ + }) + +/* Copy current packet (SDIO Tx aggregation buffer) to SDIO buffer */ +#define MP_TX_AGGR_BUF_PUT(a, payload, pkt_len, port) do { \ + typeof(a) (_a) = (a); \ + typeof(pkt_len) (_pkt_len) = pkt_len; \ + typeof(port) (_port) = port; \ + memmove(&(_a)->mpa_tx.buf[(_a)->mpa_tx.buf_len], \ + payload, (_pkt_len)); \ + (_a)->mpa_tx.buf_len += (_pkt_len); \ + if (!(_a)->mpa_tx.pkt_cnt) \ + (_a)->mpa_tx.start_port = (_port); \ + if ((_a)->mpa_tx.start_port <= (_port)) \ + (_a)->mpa_tx.ports |= (1 << ((_a)->mpa_tx.pkt_cnt)); \ + else \ + (_a)->mpa_tx.ports |= (1 << ((_a)->mpa_tx.pkt_cnt + 1 + \ + ((_a)->max_ports - \ + (_a)->mp_end_port))); \ + (_a)->mpa_tx.pkt_cnt++; \ +} while (0) + +/* SDIO Tx aggregation limit ? */ +#define MP_TX_AGGR_PKT_LIMIT_REACHED(a) ({ \ + typeof(a) (_a) = a; \ + ((_a)->mpa_tx.pkt_cnt == (_a)->mpa_tx.pkt_aggr_limit); \ + }) + +/* Reset SDIO Tx aggregation buffer parameters */ +#define MP_TX_AGGR_BUF_RESET(a) do { \ + typeof(a) (_a) = (a); \ + (_a)->mpa_tx.pkt_cnt = 0; \ + (_a)->mpa_tx.buf_len = 0; \ + (_a)->mpa_tx.ports = 0; \ + (_a)->mpa_tx.start_port = 0; \ +} while (0) + +/* SDIO Rx aggregation limit ? */ +#define MP_RX_AGGR_PKT_LIMIT_REACHED(a) ({ \ + typeof(a) (_a) = a; \ + ((_a)->mpa_rx.pkt_cnt == (_a)->mpa_rx.pkt_aggr_limit); \ + }) + +/* SDIO Rx aggregation in progress ? */ +#define MP_RX_AGGR_IN_PROGRESS(a) ((a)->mpa_rx.pkt_cnt > 0) + +/* SDIO Rx aggregation buffer room for next packet ? */ +#define MP_RX_AGGR_BUF_HAS_ROOM(a, rx_len) ({ \ + typeof(a) (_a) = a; \ + ((((_a)->mpa_rx.buf_len + (rx_len))) <= (_a)->mpa_rx.buf_size); \ + }) + +/* Reset SDIO Rx aggregation buffer parameters */ +#define MP_RX_AGGR_BUF_RESET(a) do { \ + typeof(a) (_a) = (a); \ + (_a)->mpa_rx.pkt_cnt = 0; \ + (_a)->mpa_rx.buf_len = 0; \ + (_a)->mpa_rx.ports = 0; \ + (_a)->mpa_rx.start_port = 0; \ +} while (0) + +/* data structure for SDIO MPA TX */ +struct nxpwifi_sdio_mpa_tx { + /* multiport tx aggregation buffer pointer */ + u8 *buf; + u32 buf_len; + u32 pkt_cnt; + u32 ports; + u16 start_port; + u8 enabled; + u32 buf_size; + u32 pkt_aggr_limit; +}; + +struct nxpwifi_sdio_mpa_rx { + u8 *buf; + u32 buf_len; + u32 pkt_cnt; + u32 ports; + u16 start_port; + u32 *len_arr; + u8 enabled; + u32 buf_size; + u32 pkt_aggr_limit; +}; + +int nxpwifi_bus_register(void); +void nxpwifi_bus_unregister(void); + +struct nxpwifi_sdio_card_reg { + u8 start_rd_port; + u8 start_wr_port; + u8 base_0_reg; + u8 base_1_reg; + u8 poll_reg; + u8 host_int_enable; + u8 host_int_rsr_reg; + u8 host_int_status_reg; + u8 host_int_mask_reg; + u8 host_strap_reg; + u8 host_strap_mask; + u8 host_strap_value; + u8 status_reg_0; + u8 status_reg_1; + u8 sdio_int_mask; + u32 data_port_mask; + u8 io_port_0_reg; + u8 io_port_1_reg; + u8 io_port_2_reg; + u8 max_mp_regs; + u8 rd_bitmap_l; + u8 rd_bitmap_u; + u8 rd_bitmap_1l; + u8 rd_bitmap_1u; + u8 wr_bitmap_l; + u8 wr_bitmap_u; + u8 wr_bitmap_1l; + u8 wr_bitmap_1u; + u8 rd_len_p0_l; + u8 rd_len_p0_u; + u8 card_misc_cfg_reg; + u8 card_cfg_2_1_reg; + u8 cmd_rd_len_0; + u8 cmd_rd_len_1; + u8 cmd_rd_len_2; + u8 cmd_rd_len_3; + u8 cmd_cfg_0; + u8 cmd_cfg_1; + u8 cmd_cfg_2; + u8 cmd_cfg_3; + u8 fw_dump_host_ready; + u8 fw_dump_ctrl; + u8 fw_dump_start; + u8 fw_dump_end; + u8 func1_dump_reg_start; + u8 func1_dump_reg_end; + u8 func1_scratch_reg; + u8 func1_spec_reg_num; + u8 func1_spec_reg_table[NXPWIFI_MAX_FUNC2_REG_NUM]; +}; + +struct sdio_mmc_card { + struct sdio_func *func; + struct nxpwifi_adapter *adapter; + + struct completion fw_done; + const char *firmware; + const char *firmware_sdiouart; + const struct nxpwifi_sdio_card_reg *reg; + u8 max_ports; + u8 mp_agg_pkt_limit; + u16 tx_buf_size; + u32 mp_tx_agg_buf_size; + u32 mp_rx_agg_buf_size; + + u32 mp_rd_bitmap; + u32 mp_wr_bitmap; + + u16 mp_end_port; + u32 mp_data_port_mask; + + u8 curr_rd_port; + u8 curr_wr_port; + + u8 *mp_regs; + bool can_dump_fw; + bool fw_dump_enh; + bool can_ext_scan; + + struct nxpwifi_sdio_mpa_tx mpa_tx; + struct nxpwifi_sdio_mpa_rx mpa_rx; + + struct work_struct work; + unsigned long work_flags; +}; + +struct nxpwifi_sdio_device { + const char *firmware; + const char *firmware_sdiouart; + const struct nxpwifi_sdio_card_reg *reg; + u8 max_ports; + u8 mp_agg_pkt_limit; + u16 tx_buf_size; + u32 mp_tx_agg_buf_size; + u32 mp_rx_agg_buf_size; + bool can_dump_fw; + bool fw_dump_enh; + bool can_ext_scan; +}; + +/* .cmdrsp_complete handler + */ +static inline int nxpwifi_sdio_cmdrsp_complete(struct nxpwifi_adapter *adapter, + struct sk_buff *skb) +{ + dev_kfree_skb_any(skb); + return 0; +} + +/* .event_complete handler + */ +static inline int nxpwifi_sdio_event_complete(struct nxpwifi_adapter *adapter, + struct sk_buff *skb) +{ + dev_kfree_skb_any(skb); + return 0; +} + +static inline bool +mp_rx_aggr_port_limit_reached(struct sdio_mmc_card *card) +{ + u8 tmp; + + if (card->curr_rd_port < card->mpa_rx.start_port) { + tmp = card->mp_end_port >> 1; + + if (((card->max_ports - card->mpa_rx.start_port) + + card->curr_rd_port) >= tmp) + return true; + } + + if ((card->curr_rd_port - card->mpa_rx.start_port) >= + (card->mp_end_port >> 1)) + return true; + + return false; +} + +static inline bool +mp_tx_aggr_port_limit_reached(struct sdio_mmc_card *card) +{ + u16 tmp; + + if (card->curr_wr_port < card->mpa_tx.start_port) { + tmp = card->mp_end_port >> 1; + + if (((card->max_ports - card->mpa_tx.start_port) + + card->curr_wr_port) >= tmp) + return true; + } + + if ((card->curr_wr_port - card->mpa_tx.start_port) >= + (card->mp_end_port >> 1)) + return true; + + return false; +} + +/* Prepare to copy current packet from card to SDIO Rx aggregation buffer */ +static inline void mp_rx_aggr_setup(struct sdio_mmc_card *card, + u16 rx_len, u8 port) +{ + card->mpa_rx.buf_len += rx_len; + + if (!card->mpa_rx.pkt_cnt) + card->mpa_rx.start_port = port; + + card->mpa_rx.ports |= (1 << port); + card->mpa_rx.len_arr[card->mpa_rx.pkt_cnt] = rx_len; + card->mpa_rx.pkt_cnt++; +} +#endif /* _NXPWIFI_SDIO_H */ diff --git a/drivers/net/wireless/nxp/nxpwifi/sta_cfg.c b/drivers/net/wireless/nxp/nxpwifi/sta_cfg.c new file mode 100644 index 000000000000..502c96dc4016 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/sta_cfg.c @@ -0,0 +1,1165 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: functions for station ioctl + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" +#include "cfg80211.h" + +static int disconnect_on_suspend; + +/* Copies the multicast address list from device to driver */ +int nxpwifi_copy_mcast_addr(struct nxpwifi_multicast_list *mlist, + struct net_device *dev) +{ + int i = 0; + struct netdev_hw_addr *ha; + + netdev_for_each_mc_addr(ha, dev) + memcpy(&mlist->mac_list[i++], ha->addr, ETH_ALEN); + + return i; +} + +/* Wait queue completion handler */ +int nxpwifi_wait_queue_complete(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_queued) +{ + int status; + + /* Wait for completion */ + status = wait_event_interruptible_timeout(adapter->cmd_wait_q.wait, + *cmd_queued->condition, + (12 * HZ)); + if (status <= 0) { + if (status == 0) + status = -ETIMEDOUT; + nxpwifi_dbg(adapter, ERROR, "cmd_wait_q terminated: %d\n", + status); + nxpwifi_cancel_all_pending_cmd(adapter); + return status; + } + + status = adapter->cmd_wait_q.status; + adapter->cmd_wait_q.status = 0; + + return status; +} + +/* Set multicast list by issuing the proper firmware command */ +int +nxpwifi_request_set_multicast_list(struct nxpwifi_private *priv, + struct nxpwifi_multicast_list *mcast_list) +{ + int ret = 0; + u16 old_pkt_filter; + + old_pkt_filter = priv->curr_pkt_filter; + + if (mcast_list->mode == NXPWIFI_PROMISC_MODE) { + nxpwifi_dbg(priv->adapter, INFO, + "info: Enable Promiscuous mode\n"); + priv->curr_pkt_filter |= HOST_ACT_MAC_PROMISCUOUS_ENABLE; + priv->curr_pkt_filter &= + ~HOST_ACT_MAC_ALL_MULTICAST_ENABLE; + } else { + /* Multicast */ + priv->curr_pkt_filter &= ~HOST_ACT_MAC_PROMISCUOUS_ENABLE; + if (mcast_list->mode == NXPWIFI_ALL_MULTI_MODE) { + nxpwifi_dbg(priv->adapter, INFO, + "info: Enabling All Multicast!\n"); + priv->curr_pkt_filter |= + HOST_ACT_MAC_ALL_MULTICAST_ENABLE; + } else { + priv->curr_pkt_filter &= + ~HOST_ACT_MAC_ALL_MULTICAST_ENABLE; + nxpwifi_dbg(priv->adapter, INFO, + "info: Set multicast list=%d\n", + mcast_list->num_multicast_addr); + /* Send multicast addresses to firmware */ + ret = nxpwifi_send_cmd(priv, + HOST_CMD_MAC_MULTICAST_ADR, + HOST_ACT_GEN_SET, 0, + mcast_list, false); + } + } + nxpwifi_dbg(priv->adapter, INFO, + "info: old_pkt_filter=%#x, curr_pkt_filter=%#x\n", + old_pkt_filter, priv->curr_pkt_filter); + if (old_pkt_filter != priv->curr_pkt_filter) { + ret = nxpwifi_send_cmd(priv, HOST_CMD_MAC_CONTROL, + HOST_ACT_GEN_SET, + 0, &priv->curr_pkt_filter, false); + } + + return ret; +} + +/* Fill BSS descriptor from cfg80211_bss */ +int nxpwifi_fill_new_bss_desc(struct nxpwifi_private *priv, + struct cfg80211_bss *bss, + struct nxpwifi_bssdescriptor *bss_desc) +{ + u8 *beacon_ie; + size_t beacon_ie_len; + struct nxpwifi_bss_priv *bss_priv = (void *)bss->priv; + const struct cfg80211_bss_ies *ies; + + rcu_read_lock(); + ies = rcu_dereference(bss->ies); + beacon_ie = kmemdup(ies->data, ies->len, GFP_ATOMIC); + beacon_ie_len = ies->len; + bss_desc->timestamp = ies->tsf; + rcu_read_unlock(); + + if (!beacon_ie) { + nxpwifi_dbg(priv->adapter, ERROR, + " failed to alloc beacon_ie\n"); + return -ENOMEM; + } + + memcpy(bss_desc->mac_address, bss->bssid, ETH_ALEN); + bss_desc->rssi = bss->signal; + /* The caller of this function will free beacon_ie */ + bss_desc->beacon_buf = beacon_ie; + bss_desc->beacon_buf_size = beacon_ie_len; + bss_desc->beacon_period = bss->beacon_interval; + bss_desc->cap_info_bitmap = bss->capability; + bss_desc->bss_band = bss_priv->band; + bss_desc->fw_tsf = bss_priv->fw_tsf; + if (bss_desc->cap_info_bitmap & WLAN_CAPABILITY_PRIVACY) { + nxpwifi_dbg(priv->adapter, INFO, + "info: InterpretIE: AP WEP enabled\n"); + bss_desc->privacy = NXPWIFI_802_11_PRIV_FILTER_8021X_WEP; + } else { + bss_desc->privacy = NXPWIFI_802_11_PRIV_FILTER_ACCEPT_ALL; + } + bss_desc->bss_mode = NL80211_IFTYPE_STATION; + + /* Disable 11ac by default */ + bss_desc->disable_11ac = true; + /* Disable 11ax by default */ + bss_desc->disable_11ax = true; + + if (bss_desc->cap_info_bitmap & WLAN_CAPABILITY_SPECTRUM_MGMT) + bss_desc->sensed_11h = true; + + return nxpwifi_update_bss_desc_with_ie(priv->adapter, bss_desc); +} + +static int nxpwifi_process_country_ie(struct nxpwifi_private *priv, + struct cfg80211_bss *bss) +{ + const u8 *country_ie; + u8 country_ie_len; + struct nxpwifi_802_11d_domain_reg *domain_info = + &priv->adapter->domain_reg; + int ret; + + rcu_read_lock(); + country_ie = ieee80211_bss_get_ie(bss, WLAN_EID_COUNTRY); + if (!country_ie) { + rcu_read_unlock(); + return 0; + } + + country_ie_len = country_ie[1]; + if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN) { + rcu_read_unlock(); + return 0; + } + + if (!strncmp(priv->adapter->country_code, &country_ie[2], 2)) { + rcu_read_unlock(); + nxpwifi_dbg(priv->adapter, INFO, + "11D: skip setting domain info in FW\n"); + return 0; + } + + if (country_ie_len > + (IEEE80211_COUNTRY_STRING_LEN + NXPWIFI_MAX_TRIPLET_802_11D)) { + rcu_read_unlock(); + nxpwifi_dbg(priv->adapter, ERROR, + "11D: country_ie_len overflow!, deauth AP\n"); + return -EINVAL; + } + + memcpy(priv->adapter->country_code, &country_ie[2], 2); + + domain_info->country_code[0] = country_ie[2]; + domain_info->country_code[1] = country_ie[3]; + domain_info->country_code[2] = ' '; + + country_ie_len -= IEEE80211_COUNTRY_STRING_LEN; + + domain_info->no_of_triplet = + country_ie_len / sizeof(struct ieee80211_country_ie_triplet); + + memcpy((u8 *)domain_info->triplet, + &country_ie[2] + IEEE80211_COUNTRY_STRING_LEN, country_ie_len); + + rcu_read_unlock(); + + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11D_DOMAIN_INFO, + HOST_ACT_GEN_SET, 0, NULL, false); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "11D: setting domain info in FW fail\n"); + + return ret; +} + +/* In infra mode, an deauthentication is performed first */ +int nxpwifi_bss_start(struct nxpwifi_private *priv, struct cfg80211_bss *bss, + struct cfg80211_ssid *req_ssid) +{ + int ret; + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_bssdescriptor *bss_desc = NULL; + u16 config_bands; + + priv->scan_block = false; + + if (adapter->region_code == 0x00 && + nxpwifi_process_country_ie(priv, bss)) + return -EINVAL; + + /* Allocate and fill new bss descriptor */ + bss_desc = kzalloc_obj(*bss_desc, GFP_KERNEL); + if (!bss_desc) + return -ENOMEM; + + ret = nxpwifi_fill_new_bss_desc(priv, bss, bss_desc); + if (ret) + goto done; + + if (nxpwifi_band_to_radio_type(bss_desc->bss_band) == + HOST_SCAN_RADIO_TYPE_BG) { + config_bands = BAND_B | BAND_G | BAND_GN; + if (adapter->fw_bands & BAND_GAC) + config_bands |= BAND_GAC; + if (adapter->fw_bands & BAND_GAX) + config_bands |= BAND_GAX; + } else { + config_bands = BAND_A | BAND_AN; + if (adapter->fw_bands & BAND_AAC) + config_bands |= BAND_AAC; + if (adapter->fw_bands & BAND_AAX) + config_bands |= BAND_AAX; + } + + if (!((config_bands | adapter->fw_bands) & ~adapter->fw_bands)) + priv->config_bands = config_bands; + + ret = nxpwifi_check_network_compatibility(priv, bss_desc); + if (ret) + goto done; + + if (nxpwifi_11h_get_csa_closed_channel(priv) == (u8)bss_desc->channel) { + nxpwifi_dbg(adapter, ERROR, + "Attempt to reconnect on csa closed chan(%d)\n", + bss_desc->channel); + ret = -EINVAL; + goto done; + } + + nxpwifi_stop_net_dev_queue(priv->netdev, adapter); + netif_carrier_off(priv->netdev); + + /* Clear any past association response stored for application retrieval */ + priv->assoc_rsp_size = 0; + ret = nxpwifi_associate(priv, bss_desc); + + /* + * If auth type is auto and association fails using open mode, try to connect + * using shared mode + */ + if (ret == WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG && + priv->sec_info.is_authtype_auto && + priv->sec_info.wep_enabled) { + priv->sec_info.authentication_mode = + NL80211_AUTHTYPE_SHARED_KEY; + ret = nxpwifi_associate(priv, bss_desc); + } + +done: + /* beacon_ie buffer was allocated in function nxpwifi_fill_new_bss_desc() */ + if (bss_desc) + kfree(bss_desc->beacon_buf); + kfree(bss_desc); + + if (ret < 0) + priv->attempted_bss_desc = NULL; + + return ret; +} + +/* IOCTL request handler to set host sleep configuration */ +int nxpwifi_set_hs_params(struct nxpwifi_private *priv, u16 action, + int cmd_type, struct nxpwifi_ds_hs_cfg *hs_cfg) + +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int status = 0; + u32 prev_cond = 0; + + if (!hs_cfg) + return -ENOMEM; + + switch (action) { + case HOST_ACT_GEN_SET: + if (adapter->pps_uapsd_mode) { + nxpwifi_dbg(adapter, INFO, + "info: Host Sleep IOCTL\t" + "is blocked in UAPSD/PPS mode\n"); + status = -EPERM; + break; + } + if (hs_cfg->is_invoke_hostcmd) { + if (hs_cfg->conditions == HS_CFG_CANCEL) { + if (!test_bit(NXPWIFI_IS_HS_CONFIGURED, + &adapter->work_flags)) + /* Already cancelled */ + break; + /* Save previous condition */ + prev_cond = le32_to_cpu(adapter->hs_cfg + .conditions); + adapter->hs_cfg.conditions = + cpu_to_le32(hs_cfg->conditions); + } else if (hs_cfg->conditions) { + adapter->hs_cfg.conditions = + cpu_to_le32(hs_cfg->conditions); + adapter->hs_cfg.gpio = (u8)hs_cfg->gpio; + if (hs_cfg->gap) + adapter->hs_cfg.gap = (u8)hs_cfg->gap; + } else if (adapter->hs_cfg.conditions == + cpu_to_le32(HS_CFG_CANCEL)) { + status = -EINVAL; + break; + } + + status = nxpwifi_send_cmd(priv, + HOST_CMD_802_11_HS_CFG_ENH, + HOST_ACT_GEN_SET, 0, + &adapter->hs_cfg, + cmd_type == NXPWIFI_SYNC_CMD); + + if (hs_cfg->conditions == HS_CFG_CANCEL) + /* Restore previous condition */ + adapter->hs_cfg.conditions = + cpu_to_le32(prev_cond); + } else { + adapter->hs_cfg.conditions = + cpu_to_le32(hs_cfg->conditions); + adapter->hs_cfg.gpio = (u8)hs_cfg->gpio; + adapter->hs_cfg.gap = (u8)hs_cfg->gap; + } + break; + case HOST_ACT_GEN_GET: + hs_cfg->conditions = le32_to_cpu(adapter->hs_cfg.conditions); + hs_cfg->gpio = adapter->hs_cfg.gpio; + hs_cfg->gap = adapter->hs_cfg.gap; + break; + default: + status = -EINVAL; + break; + } + + return status; +} + +/* Sends IOCTL request to cancel the existing Host Sleep configuration */ +int nxpwifi_cancel_hs(struct nxpwifi_private *priv, int cmd_type) +{ + struct nxpwifi_ds_hs_cfg hscfg; + + hscfg.conditions = HS_CFG_CANCEL; + hscfg.is_invoke_hostcmd = true; + + return nxpwifi_set_hs_params(priv, HOST_ACT_GEN_SET, + cmd_type, &hscfg); +} +EXPORT_SYMBOL_GPL(nxpwifi_cancel_hs); + +/* Sends IOCTL request to cancel the existing Host Sleep configuration */ +bool nxpwifi_enable_hs(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_ds_hs_cfg hscfg; + struct nxpwifi_private *priv; + int i; + + if (disconnect_on_suspend) { + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + nxpwifi_deauthenticate(priv, NULL); + } + } + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA); + + if (priv && priv->sched_scanning) { +#ifdef CONFIG_PM + if (priv->wdev.wiphy->wowlan_config && + !priv->wdev.wiphy->wowlan_config->nd_config) { +#endif + nxpwifi_dbg(adapter, CMD, "aborting bgscan!\n"); + nxpwifi_stop_bg_scan(priv); + cfg80211_sched_scan_stopped(priv->wdev.wiphy, 0); +#ifdef CONFIG_PM + } +#endif + } + + if (adapter->hs_activated) { + nxpwifi_dbg(adapter, CMD, + "cmd: HS Already activated\n"); + return true; + } + + adapter->hs_activate_wait_q_woken = false; + + memset(&hscfg, 0, sizeof(hscfg)); + hscfg.is_invoke_hostcmd = true; + + set_bit(NXPWIFI_IS_HS_ENABLING, &adapter->work_flags); + nxpwifi_cancel_all_pending_cmd(adapter); + + if (nxpwifi_set_hs_params(nxpwifi_get_priv(adapter, + NXPWIFI_BSS_ROLE_STA), + HOST_ACT_GEN_SET, NXPWIFI_SYNC_CMD, + &hscfg)) { + nxpwifi_dbg(adapter, ERROR, + "IOCTL request HS enable failed\n"); + return false; + } + + if (wait_event_interruptible_timeout(adapter->hs_activate_wait_q, + adapter->hs_activate_wait_q_woken, + (10 * HZ)) <= 0) { + nxpwifi_dbg(adapter, ERROR, + "hs_activate_wait_q terminated\n"); + return false; + } + + return true; +} +EXPORT_SYMBOL_GPL(nxpwifi_enable_hs); + +/* IOCTL request handler to get BSS information */ +int nxpwifi_get_bss_info(struct nxpwifi_private *priv, + struct nxpwifi_bss_info *info) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_bssdescriptor *bss_desc; + + if (!info) + return -EINVAL; + + bss_desc = &priv->curr_bss_params.bss_descriptor; + + info->bss_mode = priv->bss_mode; + + memcpy(&info->ssid, &bss_desc->ssid, sizeof(struct cfg80211_ssid)); + + memcpy(&info->bssid, &bss_desc->mac_address, ETH_ALEN); + + info->bss_chan = bss_desc->channel; + + memcpy(info->country_code, adapter->country_code, + IEEE80211_COUNTRY_STRING_LEN); + + info->media_connected = priv->media_connected; + + info->max_power_level = priv->max_tx_power_level; + info->min_power_level = priv->min_tx_power_level; + + info->bcn_nf_last = priv->bcn_nf_last; + + if (priv->sec_info.wep_enabled) + info->wep_status = true; + else + info->wep_status = false; + + info->is_hs_configured = test_bit(NXPWIFI_IS_HS_CONFIGURED, + &adapter->work_flags); + info->is_deep_sleep = adapter->is_deep_sleep; + + return 0; +} + +/* The function disables auto deep sleep mode */ +int nxpwifi_disable_auto_ds(struct nxpwifi_private *priv) +{ + struct nxpwifi_ds_auto_ds auto_ds = { + .auto_ds = DEEP_SLEEP_OFF, + }; + + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_PS_MODE_ENH, + DIS_AUTO_PS, BITMAP_AUTO_DS, &auto_ds, true); +} +EXPORT_SYMBOL_GPL(nxpwifi_disable_auto_ds); + +/* Sends IOCTL request to get the data rate */ +int nxpwifi_drv_get_data_rate(struct nxpwifi_private *priv, u32 *rate) +{ + int ret; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_TX_RATE_QUERY, + HOST_ACT_GEN_GET, 0, NULL, true); + + if (!ret) { + if (priv->is_data_rate_auto) + *rate = nxpwifi_index_to_data_rate(priv, priv->tx_rate, + priv->tx_htinfo); + else + *rate = priv->data_rate; + } + + return ret; +} + +/* IOCTL request handler to set tx power configuration */ +int nxpwifi_set_tx_power(struct nxpwifi_private *priv, + struct nxpwifi_power_cfg *power_cfg) +{ + int ret; + struct host_cmd_ds_txpwr_cfg *txp_cfg; + struct nxpwifi_types_power_group *pg_tlv; + struct nxpwifi_power_group *pg; + u8 *buf; + u16 dbm = 0; + + if (!power_cfg->is_power_auto) { + dbm = (u16)power_cfg->power_level; + if (dbm < priv->min_tx_power_level || + dbm > priv->max_tx_power_level) { + nxpwifi_dbg(priv->adapter, ERROR, + "txpower value %d dBm\t" + "is out of range (%d dBm-%d dBm)\n", + dbm, priv->min_tx_power_level, + priv->max_tx_power_level); + return -EINVAL; + } + } + buf = kzalloc(NXPWIFI_SIZE_OF_CMD_BUFFER, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + txp_cfg = (struct host_cmd_ds_txpwr_cfg *)buf; + txp_cfg->action = cpu_to_le16(HOST_ACT_GEN_SET); + if (!power_cfg->is_power_auto) { + u16 dbm_min = power_cfg->is_power_fixed ? + dbm : priv->min_tx_power_level; + + txp_cfg->mode = cpu_to_le32(1); + pg_tlv = (struct nxpwifi_types_power_group *) + (buf + sizeof(struct host_cmd_ds_txpwr_cfg)); + pg_tlv->type = cpu_to_le16(TLV_TYPE_POWER_GROUP); + pg_tlv->length = + cpu_to_le16(4 * sizeof(struct nxpwifi_power_group)); + pg = (struct nxpwifi_power_group *) + (buf + sizeof(struct host_cmd_ds_txpwr_cfg) + + sizeof(struct nxpwifi_types_power_group)); + /* Power group for modulation class HR/DSSS */ + pg->first_rate_code = 0x00; + pg->last_rate_code = 0x03; + pg->modulation_class = MOD_CLASS_HR_DSSS; + pg->power_step = 0; + pg->power_min = (s8)dbm_min; + pg->power_max = (s8)dbm; + pg++; + /* Power group for modulation class OFDM */ + pg->first_rate_code = 0x00; + pg->last_rate_code = 0x07; + pg->modulation_class = MOD_CLASS_OFDM; + pg->power_step = 0; + pg->power_min = (s8)dbm_min; + pg->power_max = (s8)dbm; + pg++; + /* Power group for modulation class HTBW20 */ + pg->first_rate_code = 0x00; + pg->last_rate_code = 0x20; + pg->modulation_class = MOD_CLASS_HT; + pg->power_step = 0; + pg->power_min = (s8)dbm_min; + pg->power_max = (s8)dbm; + pg->ht_bandwidth = HT_BW_20; + pg++; + /* Power group for modulation class HTBW40 */ + pg->first_rate_code = 0x00; + pg->last_rate_code = 0x20; + pg->modulation_class = MOD_CLASS_HT; + pg->power_step = 0; + pg->power_min = (s8)dbm_min; + pg->power_max = (s8)dbm; + pg->ht_bandwidth = HT_BW_40; + } + ret = nxpwifi_send_cmd(priv, HOST_CMD_TXPWR_CFG, + HOST_ACT_GEN_SET, 0, buf, true); + + kfree(buf); + return ret; +} + +/* IOCTL request handler to get power save mode */ +int nxpwifi_drv_set_power(struct nxpwifi_private *priv, u32 *ps_mode) +{ + int ret; + struct nxpwifi_adapter *adapter = priv->adapter; + u16 sub_cmd; + + if (*ps_mode) + adapter->ps_mode = NXPWIFI_802_11_POWER_MODE_PSP; + else + adapter->ps_mode = NXPWIFI_802_11_POWER_MODE_CAM; + sub_cmd = (*ps_mode) ? EN_AUTO_PS : DIS_AUTO_PS; + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_PS_MODE_ENH, + sub_cmd, BITMAP_STA_PS, NULL, true); + if (!ret && sub_cmd == DIS_AUTO_PS) + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_PS_MODE_ENH, + GET_PS, 0, NULL, false); + + return ret; +} + +/* IOCTL request handler to set/reset WPA element */ +static int nxpwifi_set_wpa_ie(struct nxpwifi_private *priv, + u8 *ie_data_ptr, u16 ie_len) +{ + if (ie_len) { + if (ie_len > sizeof(priv->wpa_ie)) { + nxpwifi_dbg(priv->adapter, ERROR, + "failed to copy WPA element, too big\n"); + return -EINVAL; + } + memcpy(priv->wpa_ie, ie_data_ptr, ie_len); + priv->wpa_ie_len = ie_len; + nxpwifi_dbg(priv->adapter, CMD, + "cmd: Set WPA element len=%d element=%#x\n", + priv->wpa_ie_len, priv->wpa_ie[0]); + + if (priv->wpa_ie[0] == WLAN_EID_VENDOR_SPECIFIC) { + priv->sec_info.wpa_enabled = true; + } else if (priv->wpa_ie[0] == WLAN_EID_RSN) { + priv->sec_info.wpa2_enabled = true; + } else { + priv->sec_info.wpa_enabled = false; + priv->sec_info.wpa2_enabled = false; + } + } else { + memset(priv->wpa_ie, 0, sizeof(priv->wpa_ie)); + priv->wpa_ie_len = 0; + nxpwifi_dbg(priv->adapter, INFO, + "info: reset WPA element len=%d element=%#x\n", + priv->wpa_ie_len, priv->wpa_ie[0]); + priv->sec_info.wpa_enabled = false; + priv->sec_info.wpa2_enabled = false; + } + + return 0; +} + +/* IOCTL request handler to set/reset WPS element */ +static int nxpwifi_set_wps_ie(struct nxpwifi_private *priv, + u8 *ie_data_ptr, u16 ie_len) +{ + if (ie_len) { + if (ie_len > NXPWIFI_MAX_VSIE_LEN) { + nxpwifi_dbg(priv->adapter, ERROR, + "info: failed to copy WPS element, too big\n"); + return -EINVAL; + } + + priv->wps_ie = kzalloc(NXPWIFI_MAX_VSIE_LEN, GFP_KERNEL); + if (!priv->wps_ie) + return -ENOMEM; + + memcpy(priv->wps_ie, ie_data_ptr, ie_len); + priv->wps_ie_len = ie_len; + nxpwifi_dbg(priv->adapter, CMD, + "cmd: Set WPS element len=%d element=%#x\n", + priv->wps_ie_len, priv->wps_ie[0]); + } else { + kfree(priv->wps_ie); + priv->wps_ie_len = ie_len; + nxpwifi_dbg(priv->adapter, INFO, + "info: Reset WPS element len=%d\n", priv->wps_ie_len); + } + return 0; +} + +/* IOCTL request handler to set WEP network key */ +static int +nxpwifi_sec_ioctl_set_wep_key(struct nxpwifi_private *priv, + struct nxpwifi_ds_encrypt_key *encrypt_key) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + struct nxpwifi_wep_key *wep_key; + int index; + + if (priv->wep_key_curr_index >= NUM_WEP_KEYS) + priv->wep_key_curr_index = 0; + wep_key = &priv->wep_key[priv->wep_key_curr_index]; + index = encrypt_key->key_index; + if (encrypt_key->key_disable) { + priv->sec_info.wep_enabled = 0; + } else if (!encrypt_key->key_len) { + /* Copy the required key as the current key */ + wep_key = &priv->wep_key[index]; + if (!wep_key->key_length) { + nxpwifi_dbg(adapter, ERROR, + "key not set, so cannot enable it\n"); + return -EINVAL; + } + + memcpy(encrypt_key->key_material, + wep_key->key_material, wep_key->key_length); + encrypt_key->key_len = wep_key->key_length; + + priv->wep_key_curr_index = (u16)index; + priv->sec_info.wep_enabled = 1; + } else { + wep_key = &priv->wep_key[index]; + memset(wep_key, 0, sizeof(struct nxpwifi_wep_key)); + /* Copy the key in the driver */ + memcpy(wep_key->key_material, + encrypt_key->key_material, + encrypt_key->key_len); + wep_key->key_index = index; + wep_key->key_length = encrypt_key->key_len; + priv->sec_info.wep_enabled = 1; + } + if (wep_key->key_length) { + void *enc_key; + + if (encrypt_key->key_disable) { + memset(&priv->wep_key[index], 0, + sizeof(struct nxpwifi_wep_key)); + goto done; + } + + enc_key = encrypt_key; + + /* Send request to firmware */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_KEY_MATERIAL, + HOST_ACT_GEN_SET, 0, enc_key, false); + if (ret) + return ret; + } + +done: + if (priv->sec_info.wep_enabled) + priv->curr_pkt_filter |= HOST_ACT_MAC_WEP_ENABLE; + else + priv->curr_pkt_filter &= ~HOST_ACT_MAC_WEP_ENABLE; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_MAC_CONTROL, + HOST_ACT_GEN_SET, 0, + &priv->curr_pkt_filter, true); + + return ret; +} + +/* IOCTL request handler to set WPA key */ +static int +nxpwifi_sec_ioctl_set_wpa_key(struct nxpwifi_private *priv, + struct nxpwifi_ds_encrypt_key *encrypt_key) +{ + int ret; + u8 remove_key = false; + + /* Current driver only supports key length of up to 32 bytes */ + if (encrypt_key->key_len > WLAN_MAX_KEY_LEN) { + nxpwifi_dbg(priv->adapter, ERROR, + "key length too long\n"); + return -EINVAL; + } + + if (!encrypt_key->key_index) + encrypt_key->key_index = NXPWIFI_KEY_INDEX_UNICAST; + + if (remove_key) + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_KEY_MATERIAL, + HOST_ACT_GEN_SET, + !KEY_INFO_ENABLED, encrypt_key, true); + else + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_KEY_MATERIAL, + HOST_ACT_GEN_SET, + KEY_INFO_ENABLED, encrypt_key, true); + + return ret; +} + +/* IOCTL request handler to set/get network keys */ +static int +nxpwifi_sec_ioctl_encrypt_key(struct nxpwifi_private *priv, + struct nxpwifi_ds_encrypt_key *encrypt_key) +{ + int status; + + if (encrypt_key->key_len > WLAN_KEY_LEN_WEP104) + status = nxpwifi_sec_ioctl_set_wpa_key(priv, encrypt_key); + else + status = nxpwifi_sec_ioctl_set_wep_key(priv, encrypt_key); + + return status; +} + +/* Return driver version string */ +int +nxpwifi_drv_get_driver_version(struct nxpwifi_adapter *adapter, char *version, + int max_len) +{ + union { + __le32 l; + u8 c[4]; + } ver; + char fw_ver[32]; + + ver.l = cpu_to_le32(adapter->fw_release_number); + sprintf(fw_ver, "%u.%u.%u.p%u.%u", ver.c[2], ver.c[1], + ver.c[0], ver.c[3], adapter->fw_hotfix_ver); + + snprintf(version, max_len, nxpwifi_driver_version, fw_ver); + + nxpwifi_dbg(adapter, MSG, "info: NXPWIFI VERSION: %s\n", version); + + return 0; +} + +/* Sends IOCTL request to set encoding parameters */ +int nxpwifi_set_encode(struct nxpwifi_private *priv, struct key_params *kp, + const u8 *key, int key_len, u8 key_index, + const u8 *mac_addr, int disable) +{ + struct nxpwifi_ds_encrypt_key encrypt_key; + + memset(&encrypt_key, 0, sizeof(encrypt_key)); + encrypt_key.key_len = key_len; + encrypt_key.key_index = key_index; + + if (kp) { + encrypt_key.key_cipher = kp->cipher; + if (kp->cipher == WLAN_CIPHER_SUITE_AES_CMAC || + kp->cipher == WLAN_CIPHER_SUITE_BIP_GMAC_256) + encrypt_key.is_igtk_key = true; + } + + if (!disable) { + if (key_len) + memcpy(encrypt_key.key_material, key, key_len); + else + encrypt_key.is_current_wep_key = true; + + if (mac_addr) + memcpy(encrypt_key.mac_addr, mac_addr, ETH_ALEN); + if (kp && kp->seq && kp->seq_len) { + memcpy(encrypt_key.pn, kp->seq, kp->seq_len); + encrypt_key.pn_len = kp->seq_len; + encrypt_key.is_rx_seq_valid = true; + } + } else { + encrypt_key.key_disable = true; + if (mac_addr) + memcpy(encrypt_key.mac_addr, mac_addr, ETH_ALEN); + } + + return nxpwifi_sec_ioctl_encrypt_key(priv, &encrypt_key); +} + +/* Sends IOCTL request to get extended version */ +int +nxpwifi_get_ver_ext(struct nxpwifi_private *priv, u32 version_str_sel) +{ + struct nxpwifi_ver_ext ver_ext; + + memset(&ver_ext, 0, sizeof(ver_ext)); + ver_ext.version_str_sel = version_str_sel; + + return nxpwifi_send_cmd(priv, HOST_CMD_VERSION_EXT, + HOST_ACT_GEN_GET, 0, &ver_ext, true); +} + +int +nxpwifi_remain_on_chan_cfg(struct nxpwifi_private *priv, u16 action, + struct ieee80211_channel *chan, + unsigned int duration) +{ + struct host_cmd_ds_remain_on_chan roc_cfg; + u8 sc; + int ret; + + memset(&roc_cfg, 0, sizeof(roc_cfg)); + roc_cfg.action = cpu_to_le16(action); + if (action == HOST_ACT_GEN_SET) { + roc_cfg.band_cfg = chan->band; + sc = nxpwifi_chan_type_to_sec_chan_offset(NL80211_CHAN_NO_HT); + roc_cfg.band_cfg |= (sc << 2); + + roc_cfg.channel = + ieee80211_frequency_to_channel(chan->center_freq); + roc_cfg.duration = cpu_to_le32(duration); + } + ret = nxpwifi_send_cmd(priv, HOST_CMD_REMAIN_ON_CHAN, + action, 0, &roc_cfg, true); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "failed to remain on channel\n"); + return ret; + } + + return roc_cfg.status; +} + +/* Sends IOCTL request to get statistics information */ +int +nxpwifi_get_stats_info(struct nxpwifi_private *priv, + struct nxpwifi_ds_get_stats *log) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_GET_LOG, + HOST_ACT_GEN_GET, 0, log, true); +} + +/* IOCTL request handler to read/write register */ +static int nxpwifi_reg_mem_ioctl_reg_rw(struct nxpwifi_private *priv, + struct nxpwifi_ds_reg_rw *reg_rw, + u16 action) +{ + u16 cmd_no; + + switch (reg_rw->type) { + case NXPWIFI_REG_MAC: + cmd_no = HOST_CMD_MAC_REG_ACCESS; + break; + case NXPWIFI_REG_BBP: + cmd_no = HOST_CMD_BBP_REG_ACCESS; + break; + case NXPWIFI_REG_RF: + cmd_no = HOST_CMD_RF_REG_ACCESS; + break; + case NXPWIFI_REG_PMIC: + cmd_no = HOST_CMD_PMIC_REG_ACCESS; + break; + case NXPWIFI_REG_CAU: + cmd_no = HOST_CMD_CAU_REG_ACCESS; + break; + default: + return -EINVAL; + } + + return nxpwifi_send_cmd(priv, cmd_no, action, 0, reg_rw, true); +} + +/* Sends IOCTL request to write to a register */ +int +nxpwifi_reg_write(struct nxpwifi_private *priv, u32 reg_type, + u32 reg_offset, u32 reg_value) +{ + struct nxpwifi_ds_reg_rw reg_rw; + + reg_rw.type = reg_type; + reg_rw.offset = reg_offset; + reg_rw.value = reg_value; + + return nxpwifi_reg_mem_ioctl_reg_rw(priv, ®_rw, HOST_ACT_GEN_SET); +} + +/* Sends IOCTL request to read from a register */ +int +nxpwifi_reg_read(struct nxpwifi_private *priv, u32 reg_type, + u32 reg_offset, u32 *value) +{ + int ret; + struct nxpwifi_ds_reg_rw reg_rw; + + reg_rw.type = reg_type; + reg_rw.offset = reg_offset; + ret = nxpwifi_reg_mem_ioctl_reg_rw(priv, ®_rw, HOST_ACT_GEN_GET); + + if (!ret) + *value = reg_rw.value; + + return ret; +} + +/* Sends IOCTL request to read from EEPROM */ +int +nxpwifi_eeprom_read(struct nxpwifi_private *priv, u16 offset, u16 bytes, + u8 *value) +{ + int ret; + struct nxpwifi_ds_read_eeprom rd_eeprom; + + rd_eeprom.offset = offset; + rd_eeprom.byte_count = bytes; + + /* Send request to firmware */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_EEPROM_ACCESS, + HOST_ACT_GEN_GET, 0, &rd_eeprom, true); + + if (!ret) + memcpy(value, rd_eeprom.value, + min((u16)MAX_EEPROM_DATA, rd_eeprom.byte_count)); + return ret; +} + +/* Set generic IE(s); handle WPA/WPS specially */ +static int +nxpwifi_set_gen_ie_helper(struct nxpwifi_private *priv, u8 *ie_data_ptr, + u16 ie_len) +{ + struct ieee80211_vendor_ie *pvendor_ie; + static const u8 wpa_oui[] = { 0x00, 0x50, 0xf2, 0x01 }; + static const u8 wps_oui[] = { 0x00, 0x50, 0xf2, 0x04 }; + u16 unparsed_len = ie_len, cur_ie_len; + + /* If the passed length is zero, reset the buffer */ + if (!ie_len) { + priv->gen_ie_buf_len = 0; + priv->wps.session_enable = false; + return 0; + } else if (!ie_data_ptr || + ie_len <= sizeof(struct element)) { + return -EINVAL; + } + pvendor_ie = (struct ieee80211_vendor_ie *)ie_data_ptr; + + while (pvendor_ie) { + cur_ie_len = pvendor_ie->len + sizeof(struct element); + + if (pvendor_ie->element_id == WLAN_EID_RSN) { + /* element is a WPA/WPA2 element so call set_wpa function */ + nxpwifi_set_wpa_ie(priv, (u8 *)pvendor_ie, cur_ie_len); + priv->wps.session_enable = false; + goto next_ie; + } + + if (pvendor_ie->element_id == WLAN_EID_VENDOR_SPECIFIC) { + /* Test to see if it is a WPA element, if not, then it is a gen element */ + if (!memcmp(&pvendor_ie->oui, wpa_oui, + sizeof(wpa_oui))) { + /* element is a WPA/WPA2 element so call set_wpa function */ + nxpwifi_set_wpa_ie(priv, (u8 *)pvendor_ie, + cur_ie_len); + priv->wps.session_enable = false; + goto next_ie; + } + + if (!memcmp(&pvendor_ie->oui, wps_oui, + sizeof(wps_oui))) { + /* + * Test to see if it is a WPS element, if so, enable wps session + * flag + */ + priv->wps.session_enable = true; + nxpwifi_dbg(priv->adapter, MSG, + "WPS Session Enabled.\n"); + nxpwifi_set_wps_ie(priv, (u8 *)pvendor_ie, + cur_ie_len); + goto next_ie; + } + } + + /* + * Verify that the passed length is not larger than the available space + * remaining in the buffer + */ + if (cur_ie_len < + (sizeof(priv->gen_ie_buf) - priv->gen_ie_buf_len)) { + /* Append the passed data to the end of the genIeBuffer */ + memcpy(priv->gen_ie_buf + priv->gen_ie_buf_len, + (u8 *)pvendor_ie, cur_ie_len); + /* Increment the stored buffer length by the size passed */ + priv->gen_ie_buf_len += cur_ie_len; + } + +next_ie: + unparsed_len -= cur_ie_len; + + if (unparsed_len <= sizeof(struct element)) + pvendor_ie = NULL; + else + pvendor_ie = (struct ieee80211_vendor_ie *) + (((u8 *)pvendor_ie) + cur_ie_len); + } + + return 0; +} + +/* IOCTL request handler to set/get generic element */ +static int nxpwifi_misc_ioctl_gen_ie(struct nxpwifi_private *priv, + struct nxpwifi_ds_misc_gen_ie *gen_ie, + u16 action) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + switch (gen_ie->type) { + case NXPWIFI_IE_TYPE_GEN_IE: + if (action == HOST_ACT_GEN_GET) { + gen_ie->len = priv->wpa_ie_len; + memcpy(gen_ie->ie_data, priv->wpa_ie, gen_ie->len); + } else { + nxpwifi_set_gen_ie_helper(priv, gen_ie->ie_data, + (u16)gen_ie->len); + } + break; + case NXPWIFI_IE_TYPE_ARP_FILTER: + memset(adapter->arp_filter, 0, sizeof(adapter->arp_filter)); + if (gen_ie->len > ARP_FILTER_MAX_BUF_SIZE) { + adapter->arp_filter_size = 0; + nxpwifi_dbg(adapter, ERROR, + "invalid ARP filter size\n"); + return -EINVAL; + } + memcpy(adapter->arp_filter, gen_ie->ie_data, gen_ie->len); + adapter->arp_filter_size = gen_ie->len; + break; + default: + nxpwifi_dbg(adapter, ERROR, "invalid element type\n"); + return -EINVAL; + } + return 0; +} + +/* Sends IOCTL request to set a generic element */ +int +nxpwifi_set_gen_ie(struct nxpwifi_private *priv, const u8 *ie, int ie_len) +{ + struct nxpwifi_ds_misc_gen_ie gen_ie; + + if (ie_len > IEEE_MAX_IE_SIZE) + return -EFAULT; + + gen_ie.type = NXPWIFI_IE_TYPE_GEN_IE; + gen_ie.len = ie_len; + memcpy(gen_ie.ie_data, ie, ie_len); + + return nxpwifi_misc_ioctl_gen_ie(priv, &gen_ie, HOST_ACT_GEN_SET); +} + +/* Get Host Sleep wakeup reason */ +int nxpwifi_get_wakeup_reason(struct nxpwifi_private *priv, u16 action, + int cmd_type, + struct nxpwifi_ds_wakeup_reason *wakeup_reason) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_HS_WAKEUP_REASON, + HOST_ACT_GEN_GET, 0, wakeup_reason, + cmd_type == NXPWIFI_SYNC_CMD); +} + +int nxpwifi_get_chan_info(struct nxpwifi_private *priv, + struct nxpwifi_channel_band *channel_band) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_STA_CONFIGURE, + HOST_ACT_GEN_GET, 0, channel_band, + NXPWIFI_SYNC_CMD); +} diff --git a/drivers/net/wireless/nxp/nxpwifi/sta_cmd.c b/drivers/net/wireless/nxp/nxpwifi/sta_cmd.c new file mode 100644 index 000000000000..0b140e84916f --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/sta_cmd.c @@ -0,0 +1,3383 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: station command handling + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" +#include "11ac.h" +#include "11ax.h" + +static bool disable_auto_ds; + +static int +nxpwifi_cmd_sta_get_hw_spec(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_get_hw_spec *hw_spec = &cmd->params.hw_spec; + + cmd->command = cpu_to_le16(HOST_CMD_GET_HW_SPEC); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_get_hw_spec) + + S_DS_GEN); + memcpy(hw_spec->permanent_addr, priv->curr_addr, ETH_ALEN); + + return 0; +} + +static int +nxpwifi_ret_sta_get_hw_spec(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_get_hw_spec *hw_spec = &resp->params.hw_spec; + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ie_types_header *tlv; + struct hw_spec_api_rev *api_rev; + struct hw_spec_max_conn *max_conn; + struct hw_spec_extension *hw_he_cap; + struct hw_spec_fw_cap_info *fw_cap; + struct hw_spec_secure_boot_uuid *sb_uuid; + u16 resp_size, api_id; + int i, left_len, parsed_len = 0; + + adapter->fw_cap_info = le32_to_cpu(hw_spec->fw_cap_info); + + if (IS_SUPPORT_MULTI_BANDS(adapter)) + adapter->fw_bands = GET_FW_DEFAULT_BANDS(adapter); + else + adapter->fw_bands = BAND_B; + + if ((adapter->fw_bands & BAND_A) && (adapter->fw_bands & BAND_GN)) + adapter->fw_bands |= BAND_AN; + if (!(adapter->fw_bands & BAND_G) && (adapter->fw_bands & BAND_GN)) + adapter->fw_bands &= ~BAND_GN; + + adapter->fw_release_number = le32_to_cpu(hw_spec->fw_release_number); + adapter->fw_api_ver = (adapter->fw_release_number >> 16) & 0xff; + adapter->number_of_antenna = + le16_to_cpu(hw_spec->number_of_antenna) & 0xf; + + if (le32_to_cpu(hw_spec->dot_11ac_dev_cap)) { + adapter->is_hw_11ac_capable = true; + + /* Copy 11AC cap */ + adapter->hw_dot_11ac_dev_cap = + le32_to_cpu(hw_spec->dot_11ac_dev_cap); + adapter->usr_dot_11ac_dev_cap_bg = adapter->hw_dot_11ac_dev_cap + & ~NXPWIFI_DEF_11AC_CAP_BF_RESET_MASK; + adapter->usr_dot_11ac_dev_cap_a = adapter->hw_dot_11ac_dev_cap + & ~NXPWIFI_DEF_11AC_CAP_BF_RESET_MASK; + + /* Copy 11AC mcs */ + adapter->hw_dot_11ac_mcs_support = + le32_to_cpu(hw_spec->dot_11ac_mcs_support); + adapter->usr_dot_11ac_mcs_support = + adapter->hw_dot_11ac_mcs_support; + } else { + adapter->is_hw_11ac_capable = false; + } + + resp_size = le16_to_cpu(resp->size) - S_DS_GEN; + if (resp_size > sizeof(struct host_cmd_ds_get_hw_spec)) { + /* we have variable HW SPEC information */ + left_len = resp_size - sizeof(struct host_cmd_ds_get_hw_spec); + while (left_len > sizeof(struct nxpwifi_ie_types_header)) { + tlv = (void *)&hw_spec->tlv + parsed_len; + switch (le16_to_cpu(tlv->type)) { + case TLV_TYPE_API_REV: + api_rev = (struct hw_spec_api_rev *)tlv; + api_id = le16_to_cpu(api_rev->api_id); + switch (api_id) { + case KEY_API_VER_ID: + adapter->key_api_major_ver = + api_rev->major_ver; + adapter->key_api_minor_ver = + api_rev->minor_ver; + nxpwifi_dbg(adapter, INFO, + "key_api v%d.%d\n", + adapter->key_api_major_ver, + adapter->key_api_minor_ver); + break; + case FW_API_VER_ID: + adapter->fw_api_ver = + api_rev->major_ver; + nxpwifi_dbg(adapter, MSG, + "Firmware api version %d.%d\n", + adapter->fw_api_ver, + api_rev->minor_ver); + break; + case UAP_FW_API_VER_ID: + nxpwifi_dbg(adapter, INFO, + "uAP api version %d.%d\n", + api_rev->major_ver, + api_rev->minor_ver); + break; + case CHANRPT_API_VER_ID: + nxpwifi_dbg(adapter, INFO, + "channel report api version %d.%d\n", + api_rev->major_ver, + api_rev->minor_ver); + break; + case FW_HOTFIX_VER_ID: + adapter->fw_hotfix_ver = + api_rev->major_ver; + nxpwifi_dbg(adapter, INFO, + "Firmware hotfix version %d\n", + api_rev->major_ver); + break; + default: + nxpwifi_dbg(adapter, FATAL, + "Unknown api_id: %d\n", + api_id); + break; + } + break; + case TLV_TYPE_MAX_CONN: + max_conn = (struct hw_spec_max_conn *)tlv; + adapter->max_sta_conn = max_conn->max_sta_conn; + nxpwifi_dbg(adapter, INFO, + "max sta connections: %u\n", + adapter->max_sta_conn); + break; + case TLV_TYPE_EXTENSION_ID: + hw_he_cap = (struct hw_spec_extension *)tlv; + if (hw_he_cap->ext_id == + WLAN_EID_EXT_HE_CAPABILITY) + nxpwifi_update_11ax_cap(adapter, hw_he_cap); + break; + case TLV_TYPE_FW_CAP_INFO: + fw_cap = (struct hw_spec_fw_cap_info *)tlv; + adapter->fw_cap_info = + le32_to_cpu(fw_cap->fw_cap_info); + adapter->fw_cap_ext = + le32_to_cpu(fw_cap->fw_cap_ext); + nxpwifi_dbg(adapter, INFO, + "fw_cap_info:%#x fw_cap_ext:%#x\n", + adapter->fw_cap_info, + adapter->fw_cap_ext); + break; + case TLV_TYPE_SECURE_BOOT_UUID: + sb_uuid = (struct hw_spec_secure_boot_uuid *)tlv; + adapter->uuid_lo = + le64_to_cpu(sb_uuid->uuid_lo); + adapter->uuid_hi = + le64_to_cpu(sb_uuid->uuid_hi); + nxpwifi_dbg(adapter, INFO, + "uuid: %#llx%#llx\n", + adapter->uuid_lo, adapter->uuid_hi); + break; + default: + nxpwifi_dbg(adapter, FATAL, + "Unknown GET_HW_SPEC TLV type: %#x\n", + le16_to_cpu(tlv->type)); + break; + } + parsed_len += le16_to_cpu(tlv->len) + + sizeof(struct nxpwifi_ie_types_header); + left_len -= le16_to_cpu(tlv->len) + + sizeof(struct nxpwifi_ie_types_header); + } + } + + if (adapter->key_api_major_ver < KEY_API_VER_MAJOR_V2) + return -EOPNOTSUPP; + + nxpwifi_dbg(adapter, INFO, + "info: GET_HW_SPEC: fw_release_number- %#x\n", + adapter->fw_release_number); + nxpwifi_dbg(adapter, INFO, + "info: GET_HW_SPEC: permanent addr: %pM\n", + hw_spec->permanent_addr); + nxpwifi_dbg(adapter, INFO, + "info: GET_HW_SPEC: hw_if_version=%#x version=%#x\n", + le16_to_cpu(hw_spec->hw_if_version), + le16_to_cpu(hw_spec->version)); + + ether_addr_copy(priv->adapter->perm_addr, hw_spec->permanent_addr); + adapter->region_code = le16_to_cpu(hw_spec->region_code); + + /* If it's unidentified region code, use the default (USA/FCC) */ + if (!nxpwifi_is_valid_region_code(adapter->region_code)) { + nxpwifi_dbg(adapter, WARN, + "cmd: unknown region code %#x, use default (USA/%#x)\n", + adapter->region_code, NXPWIFI_DEFAULT_REGION_CODE); + adapter->region_code = NXPWIFI_DEFAULT_REGION_CODE; + } + + adapter->hw_dot_11n_dev_cap = le32_to_cpu(hw_spec->dot_11n_dev_cap); + adapter->hw_dev_mcs_support = hw_spec->dev_mcs_support; + adapter->hw_mpdu_density = GET_MPDU_DENSITY(le32_to_cpu(hw_spec->hw_dev_cap)); + adapter->user_dev_mcs_support = adapter->hw_dev_mcs_support; + adapter->user_htstream = adapter->hw_dev_mcs_support; + if (adapter->fw_bands & BAND_A) + adapter->user_htstream |= (adapter->user_htstream << 8); + + if (adapter->if_ops.update_mp_end_port) { + u16 mp_end_port; + + mp_end_port = le16_to_cpu(hw_spec->mp_end_port); + adapter->if_ops.update_mp_end_port(adapter, mp_end_port); + } + + if (adapter->fw_api_ver == NXPWIFI_FW_V15) + adapter->scan_chan_gap_enabled = true; + + for (i = 0; i < adapter->priv_num; i++) + adapter->priv[i]->config_bands = adapter->fw_bands; + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_scan(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_802_11_scan(cmd, data_buf); +} + +static int +nxpwifi_ret_sta_802_11_scan(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + + ret = nxpwifi_ret_802_11_scan(priv, resp); + adapter->curr_cmd->wait_q_enabled = false; + + return ret; +} + +static int +nxpwifi_cmd_sta_802_11_get_log(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(HOST_CMD_802_11_GET_LOG); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_get_log) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_get_log(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11_get_log *get_log = + &resp->params.get_log; + struct nxpwifi_ds_get_stats *stats = + (struct nxpwifi_ds_get_stats *)data_buf; + + if (stats) { + stats->mcast_tx_frame = le32_to_cpu(get_log->mcast_tx_frame); + stats->failed = le32_to_cpu(get_log->failed); + stats->retry = le32_to_cpu(get_log->retry); + stats->multi_retry = le32_to_cpu(get_log->multi_retry); + stats->frame_dup = le32_to_cpu(get_log->frame_dup); + stats->rts_success = le32_to_cpu(get_log->rts_success); + stats->rts_failure = le32_to_cpu(get_log->rts_failure); + stats->ack_failure = le32_to_cpu(get_log->ack_failure); + stats->rx_frag = le32_to_cpu(get_log->rx_frag); + stats->mcast_rx_frame = le32_to_cpu(get_log->mcast_rx_frame); + stats->fcs_error = le32_to_cpu(get_log->fcs_error); + stats->tx_frame = le32_to_cpu(get_log->tx_frame); + stats->wep_icv_error[0] = + le32_to_cpu(get_log->wep_icv_err_cnt[0]); + stats->wep_icv_error[1] = + le32_to_cpu(get_log->wep_icv_err_cnt[1]); + stats->wep_icv_error[2] = + le32_to_cpu(get_log->wep_icv_err_cnt[2]); + stats->wep_icv_error[3] = + le32_to_cpu(get_log->wep_icv_err_cnt[3]); + stats->bcn_rcv_cnt = le32_to_cpu(get_log->bcn_rcv_cnt); + stats->bcn_miss_cnt = le32_to_cpu(get_log->bcn_miss_cnt); + } + + return 0; +} + +static int +nxpwifi_cmd_sta_mac_multicast_adr(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_mac_multicast_adr *mcast_addr = &cmd->params.mc_addr; + struct nxpwifi_multicast_list *mcast_list = + (struct nxpwifi_multicast_list *)data_buf; + + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_mac_multicast_adr) + + S_DS_GEN); + cmd->command = cpu_to_le16(HOST_CMD_MAC_MULTICAST_ADR); + + mcast_addr->action = cpu_to_le16(cmd_action); + mcast_addr->num_of_adrs = + cpu_to_le16((u16)mcast_list->num_multicast_addr); + memcpy(mcast_addr->mac_list, mcast_list->mac_list, + mcast_list->num_multicast_addr * ETH_ALEN); + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_associate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_802_11_associate(priv, cmd, data_buf); +} + +static int +nxpwifi_ret_sta_802_11_associate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_802_11_associate(priv, resp); +} + +static int +nxpwifi_cmd_sta_802_11_snmp_mib(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_802_11_snmp_mib *snmp_mib = &cmd->params.smib; + u16 *ul_temp = (u16 *)data_buf; + + nxpwifi_dbg(priv->adapter, CMD, + "cmd: SNMP_CMD: cmd_oid = 0x%x\n", cmd_type); + cmd->command = cpu_to_le16(HOST_CMD_802_11_SNMP_MIB); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_snmp_mib) + + S_DS_GEN); + + snmp_mib->oid = cpu_to_le16((u16)cmd_type); + if (cmd_action == HOST_ACT_GEN_GET) { + snmp_mib->query_type = cpu_to_le16(HOST_ACT_GEN_GET); + snmp_mib->buf_size = cpu_to_le16(MAX_SNMP_BUF_SIZE); + le16_unaligned_add_cpu(&cmd->size, MAX_SNMP_BUF_SIZE); + } else if (cmd_action == HOST_ACT_GEN_SET) { + snmp_mib->query_type = cpu_to_le16(HOST_ACT_GEN_SET); + snmp_mib->buf_size = cpu_to_le16(sizeof(u16)); + put_unaligned_le16(*ul_temp, snmp_mib->value); + le16_unaligned_add_cpu(&cmd->size, sizeof(u16)); + } + + nxpwifi_dbg(priv->adapter, CMD, + "cmd: SNMP_CMD: Action=0x%x, OID=0x%x,\t" + "OIDSize=0x%x, Value=0x%x\n", + cmd_action, cmd_type, le16_to_cpu(snmp_mib->buf_size), + get_unaligned_le16(snmp_mib->value)); + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_snmp_mib(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11_snmp_mib *smib = &resp->params.smib; + u16 oid = le16_to_cpu(smib->oid); + u16 query_type = le16_to_cpu(smib->query_type); + u32 ul_temp; + + nxpwifi_dbg(priv->adapter, INFO, + "info: SNMP_RESP: oid value = %#x,\t" + "query_type = %#x, buf size = %#x\n", + oid, query_type, le16_to_cpu(smib->buf_size)); + if (query_type == HOST_ACT_GEN_GET) { + ul_temp = get_unaligned_le16(smib->value); + if (data_buf) + *(u32 *)data_buf = ul_temp; + switch (oid) { + case FRAG_THRESH_I: + nxpwifi_dbg(priv->adapter, INFO, + "info: SNMP_RESP: FragThsd =%u\n", + ul_temp); + break; + case RTS_THRESH_I: + nxpwifi_dbg(priv->adapter, INFO, + "info: SNMP_RESP: RTSThsd =%u\n", + ul_temp); + break; + case SHORT_RETRY_LIM_I: + nxpwifi_dbg(priv->adapter, INFO, + "info: SNMP_RESP: TxRetryCount=%u\n", + ul_temp); + break; + case DTIM_PERIOD_I: + nxpwifi_dbg(priv->adapter, INFO, + "info: SNMP_RESP: DTIM period=%u\n", + ul_temp); + break; + default: + break; + } + } + + return 0; +} + +static int nxpwifi_cmd_sta_reg_access(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_ds_reg_rw *reg_rw = data_buf; + + cmd->command = cpu_to_le16(cmd_no); + + switch (cmd_no) { + case HOST_CMD_MAC_REG_ACCESS: + { + struct host_cmd_ds_mac_reg_access *mac_reg; + + cmd->size = cpu_to_le16(sizeof(*mac_reg) + S_DS_GEN); + mac_reg = &cmd->params.mac_reg; + mac_reg->action = cpu_to_le16(cmd_action); + mac_reg->offset = cpu_to_le16((u16)reg_rw->offset); + mac_reg->value = cpu_to_le32(reg_rw->value); + break; + } + case HOST_CMD_BBP_REG_ACCESS: + { + struct host_cmd_ds_bbp_reg_access *bbp_reg; + + cmd->size = cpu_to_le16(sizeof(*bbp_reg) + S_DS_GEN); + bbp_reg = &cmd->params.bbp_reg; + bbp_reg->action = cpu_to_le16(cmd_action); + bbp_reg->offset = cpu_to_le16((u16)reg_rw->offset); + bbp_reg->value = (u8)reg_rw->value; + break; + } + case HOST_CMD_RF_REG_ACCESS: + { + struct host_cmd_ds_rf_reg_access *rf_reg; + + cmd->size = cpu_to_le16(sizeof(*rf_reg) + S_DS_GEN); + rf_reg = &cmd->params.rf_reg; + rf_reg->action = cpu_to_le16(cmd_action); + rf_reg->offset = cpu_to_le16((u16)reg_rw->offset); + rf_reg->value = (u8)reg_rw->value; + break; + } + case HOST_CMD_PMIC_REG_ACCESS: + { + struct host_cmd_ds_pmic_reg_access *pmic_reg; + + cmd->size = cpu_to_le16(sizeof(*pmic_reg) + S_DS_GEN); + pmic_reg = &cmd->params.pmic_reg; + pmic_reg->action = cpu_to_le16(cmd_action); + pmic_reg->offset = cpu_to_le16((u16)reg_rw->offset); + pmic_reg->value = (u8)reg_rw->value; + break; + } + case HOST_CMD_CAU_REG_ACCESS: + { + struct host_cmd_ds_rf_reg_access *cau_reg; + + cmd->size = cpu_to_le16(sizeof(*cau_reg) + S_DS_GEN); + cau_reg = &cmd->params.rf_reg; + cau_reg->action = cpu_to_le16(cmd_action); + cau_reg->offset = cpu_to_le16((u16)reg_rw->offset); + cau_reg->value = (u8)reg_rw->value; + break; + } + case HOST_CMD_802_11_EEPROM_ACCESS: + { + struct nxpwifi_ds_read_eeprom *rd_eeprom = data_buf; + struct host_cmd_ds_802_11_eeprom_access *cmd_eeprom = + &cmd->params.eeprom; + + cmd->size = cpu_to_le16(sizeof(*cmd_eeprom) + S_DS_GEN); + cmd_eeprom->action = cpu_to_le16(cmd_action); + cmd_eeprom->offset = cpu_to_le16(rd_eeprom->offset); + cmd_eeprom->byte_count = cpu_to_le16(rd_eeprom->byte_count); + cmd_eeprom->value = 0; + break; + } + default: + return -EINVAL; + } + + return 0; +} + +static int +nxpwifi_ret_sta_reg_access(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_ds_reg_rw *reg_rw; + struct nxpwifi_ds_read_eeprom *eeprom; + union reg { + struct host_cmd_ds_mac_reg_access *mac; + struct host_cmd_ds_bbp_reg_access *bbp; + struct host_cmd_ds_rf_reg_access *rf; + struct host_cmd_ds_pmic_reg_access *pmic; + struct host_cmd_ds_802_11_eeprom_access *eeprom; + } r; + + if (!data_buf) + return 0; + + reg_rw = data_buf; + eeprom = data_buf; + switch (cmdresp_no) { + case HOST_CMD_MAC_REG_ACCESS: + r.mac = &resp->params.mac_reg; + reg_rw->offset = (u32)le16_to_cpu(r.mac->offset); + reg_rw->value = le32_to_cpu(r.mac->value); + break; + case HOST_CMD_BBP_REG_ACCESS: + r.bbp = &resp->params.bbp_reg; + reg_rw->offset = (u32)le16_to_cpu(r.bbp->offset); + reg_rw->value = (u32)r.bbp->value; + break; + + case HOST_CMD_RF_REG_ACCESS: + r.rf = &resp->params.rf_reg; + reg_rw->offset = (u32)le16_to_cpu(r.rf->offset); + reg_rw->value = (u32)r.bbp->value; + break; + case HOST_CMD_PMIC_REG_ACCESS: + r.pmic = &resp->params.pmic_reg; + reg_rw->offset = (u32)le16_to_cpu(r.pmic->offset); + reg_rw->value = (u32)r.pmic->value; + break; + case HOST_CMD_CAU_REG_ACCESS: + r.rf = &resp->params.rf_reg; + reg_rw->offset = (u32)le16_to_cpu(r.rf->offset); + reg_rw->value = (u32)r.rf->value; + break; + case HOST_CMD_802_11_EEPROM_ACCESS: + r.eeprom = &resp->params.eeprom; + pr_debug("info: EEPROM read len=%x\n", + le16_to_cpu(r.eeprom->byte_count)); + if (eeprom->byte_count < le16_to_cpu(r.eeprom->byte_count)) { + eeprom->byte_count = 0; + pr_debug("info: EEPROM read length is too big\n"); + return -ENOMEM; + } + eeprom->offset = le16_to_cpu(r.eeprom->offset); + eeprom->byte_count = le16_to_cpu(r.eeprom->byte_count); + if (eeprom->byte_count > 0) + memcpy(&eeprom->value, &r.eeprom->value, + min((u16)MAX_EEPROM_DATA, eeprom->byte_count)); + break; + default: + return -EINVAL; + } + return 0; +} + +static int +nxpwifi_cmd_sta_rf_tx_pwr(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_rf_tx_pwr *txp = &cmd->params.txp; + + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_rf_tx_pwr) + + S_DS_GEN); + cmd->command = cpu_to_le16(HOST_CMD_RF_TX_PWR); + txp->action = cpu_to_le16(cmd_action); + + return 0; +} + +static int +nxpwifi_ret_sta_rf_tx_pwr(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_rf_tx_pwr *txp = &resp->params.txp; + u16 action = le16_to_cpu(txp->action); + + priv->tx_power_level = le16_to_cpu(txp->cur_level); + + if (action == HOST_ACT_GEN_GET) { + priv->max_tx_power_level = txp->max_power; + priv->min_tx_power_level = txp->min_power; + } + + nxpwifi_dbg(priv->adapter, INFO, + "Current TxPower Level=%d, Max Power=%d, Min Power=%d\n", + priv->tx_power_level, priv->max_tx_power_level, + priv->min_tx_power_level); + + return 0; +} + +static int +nxpwifi_cmd_sta_rf_antenna(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_rf_ant_mimo *ant_mimo = &cmd->params.ant_mimo; + struct host_cmd_ds_rf_ant_siso *ant_siso = &cmd->params.ant_siso; + struct nxpwifi_ds_ant_cfg *ant_cfg = + (struct nxpwifi_ds_ant_cfg *)data_buf; + + cmd->command = cpu_to_le16(HOST_CMD_RF_ANTENNA); + + switch (cmd_action) { + case HOST_ACT_GEN_SET: + if (priv->adapter->hw_dev_mcs_support == HT_STREAM_2X2) { + cmd->size = cpu_to_le16(sizeof(struct + host_cmd_ds_rf_ant_mimo) + + S_DS_GEN); + ant_mimo->action_tx = cpu_to_le16(HOST_ACT_SET_TX); + ant_mimo->tx_ant_mode = + cpu_to_le16((u16)ant_cfg->tx_ant); + ant_mimo->action_rx = cpu_to_le16(HOST_ACT_SET_RX); + ant_mimo->rx_ant_mode = + cpu_to_le16((u16)ant_cfg->rx_ant); + } else { + cmd->size = cpu_to_le16(sizeof(struct + host_cmd_ds_rf_ant_siso) + + S_DS_GEN); + ant_siso->action = cpu_to_le16(HOST_ACT_SET_BOTH); + ant_siso->ant_mode = cpu_to_le16((u16)ant_cfg->tx_ant); + } + break; + case HOST_ACT_GEN_GET: + if (priv->adapter->hw_dev_mcs_support == HT_STREAM_2X2) { + cmd->size = cpu_to_le16(sizeof(struct + host_cmd_ds_rf_ant_mimo) + + S_DS_GEN); + ant_mimo->action_tx = cpu_to_le16(HOST_ACT_GET_TX); + ant_mimo->action_rx = cpu_to_le16(HOST_ACT_GET_RX); + } else { + cmd->size = cpu_to_le16(sizeof(struct + host_cmd_ds_rf_ant_siso) + + S_DS_GEN); + ant_siso->action = cpu_to_le16(HOST_ACT_GET_BOTH); + } + break; + } + return 0; +} + +static int +nxpwifi_ret_sta_rf_antenna(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_rf_ant_mimo *ant_mimo = &resp->params.ant_mimo; + struct host_cmd_ds_rf_ant_siso *ant_siso = &resp->params.ant_siso; + struct nxpwifi_adapter *adapter = priv->adapter; + + if (adapter->hw_dev_mcs_support == HT_STREAM_2X2) { + priv->tx_ant = le16_to_cpu(ant_mimo->tx_ant_mode); + priv->rx_ant = le16_to_cpu(ant_mimo->rx_ant_mode); + nxpwifi_dbg(adapter, INFO, + "RF_ANT_RESP: Tx action = 0x%x, Tx Mode = 0x%04x\t" + "Rx action = 0x%x, Rx Mode = 0x%04x\n", + le16_to_cpu(ant_mimo->action_tx), + le16_to_cpu(ant_mimo->tx_ant_mode), + le16_to_cpu(ant_mimo->action_rx), + le16_to_cpu(ant_mimo->rx_ant_mode)); + } else { + priv->tx_ant = le16_to_cpu(ant_siso->ant_mode); + priv->rx_ant = le16_to_cpu(ant_siso->ant_mode); + nxpwifi_dbg(adapter, INFO, + "RF_ANT_RESP: action = 0x%x, Mode = 0x%04x\n", + le16_to_cpu(ant_siso->action), + le16_to_cpu(ant_siso->ant_mode)); + } + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_deauthenticate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_802_11_deauthenticate *deauth = &cmd->params.deauth; + u8 *mac = (u8 *)data_buf; + + cmd->command = cpu_to_le16(HOST_CMD_802_11_DEAUTHENTICATE); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_deauthenticate) + + S_DS_GEN); + + /* Set AP MAC address */ + memcpy(deauth->mac_addr, mac, ETH_ALEN); + + nxpwifi_dbg(priv->adapter, CMD, "cmd: Deauth: %pM\n", deauth->mac_addr); + + deauth->reason_code = cpu_to_le16(WLAN_REASON_DEAUTH_LEAVING); + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_deauthenticate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->dbg.num_cmd_deauth++; + if (!memcmp(resp->params.deauth.mac_addr, + &priv->curr_bss_params.bss_descriptor.mac_address, + sizeof(resp->params.deauth.mac_addr))) + nxpwifi_reset_connect_state(priv, WLAN_REASON_DEAUTH_LEAVING, + false); + + return 0; +} + +static int +nxpwifi_cmd_sta_mac_control(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_mac_control *mac_ctrl = &cmd->params.mac_ctrl; + u32 *action = (u32 *)data_buf; + + if (cmd_action != HOST_ACT_GEN_SET) { + nxpwifi_dbg(priv->adapter, ERROR, + "mac_control: only support set cmd\n"); + return -EINVAL; + } + + cmd->command = cpu_to_le16(HOST_CMD_MAC_CONTROL); + cmd->size = + cpu_to_le16(sizeof(struct host_cmd_ds_mac_control) + S_DS_GEN); + mac_ctrl->action = cpu_to_le32(*action); + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_mac_address(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(HOST_CMD_802_11_MAC_ADDRESS); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_mac_address) + + S_DS_GEN); + cmd->result = 0; + + cmd->params.mac_addr.action = cpu_to_le16(cmd_action); + + if (cmd_action == HOST_ACT_GEN_SET) + memcpy(cmd->params.mac_addr.mac_addr, priv->curr_addr, + ETH_ALEN); + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_mac_address(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11_mac_address *cmd_mac_addr; + + cmd_mac_addr = &resp->params.mac_addr; + + memcpy(priv->curr_addr, cmd_mac_addr->mac_addr, ETH_ALEN); + + nxpwifi_dbg(priv->adapter, INFO, + "info: set mac address: %pM\n", priv->curr_addr); + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11d_domain_info(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11d_domain_info *domain_info = + &cmd->params.domain_info; + struct nxpwifi_ietypes_domain_param_set *domain = + &domain_info->domain; + struct nxpwifi_ietypes_domain_code *domain_code; + u8 no_of_triplet = adapter->domain_reg.no_of_triplet; + int triplet_size; + + nxpwifi_dbg(adapter, INFO, + "info: 11D: no_of_triplet=0x%x\n", no_of_triplet); + + cmd->command = cpu_to_le16(HOST_CMD_802_11D_DOMAIN_INFO); + cmd->size = cpu_to_le16(S_DS_GEN); + domain_info->action = cpu_to_le16(cmd_action); + le16_unaligned_add_cpu(&cmd->size, sizeof(domain_info->action)); + + if (cmd_action == HOST_ACT_GEN_GET) + return 0; + + triplet_size = no_of_triplet * + sizeof(struct ieee80211_country_ie_triplet); + + domain->header.type = cpu_to_le16(WLAN_EID_COUNTRY); + domain->header.len = + cpu_to_le16(sizeof(domain->country_code) + triplet_size); + memcpy(domain->country_code, adapter->domain_reg.country_code, + sizeof(domain->country_code)); + if (no_of_triplet) + memcpy(domain->triplet, adapter->domain_reg.triplet, + triplet_size); + le16_unaligned_add_cpu(&cmd->size, sizeof(*domain) + triplet_size); + + domain_code = (struct nxpwifi_ietypes_domain_code *)((u8 *)cmd + + le16_to_cpu(cmd->size)); + domain_code->header.type = cpu_to_le16(TLV_TYPE_REGION_DOMAIN_CODE); + domain_code->header.len = + cpu_to_le16(sizeof(*domain_code) - + sizeof(struct nxpwifi_ie_types_header)); + le16_unaligned_add_cpu(&cmd->size, sizeof(*domain_code)); + + return 0; +} + +static int +nxpwifi_ret_sta_802_11d_domain_info(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11d_domain_info_rsp *domain_info = + &resp->params.domain_info_resp; + struct nxpwifi_ietypes_domain_param_set *domain = &domain_info->domain; + u16 action = le16_to_cpu(domain_info->action); + u8 no_of_triplet; + + no_of_triplet = (u8)((le16_to_cpu(domain->header.len) + - IEEE80211_COUNTRY_STRING_LEN) + / sizeof(struct ieee80211_country_ie_triplet)); + + nxpwifi_dbg(priv->adapter, INFO, + "info: 11D Domain Info Resp: no_of_triplet=%d\n", + no_of_triplet); + + if (no_of_triplet > NXPWIFI_MAX_TRIPLET_802_11D) { + nxpwifi_dbg(priv->adapter, FATAL, + "11D: invalid number of triplets %d returned\n", + no_of_triplet); + return -EINVAL; + } + + switch (action) { + case HOST_ACT_GEN_SET: /* Proc Set Action */ + break; + case HOST_ACT_GEN_GET: + break; + default: + nxpwifi_dbg(priv->adapter, ERROR, + "11D: invalid action:%d\n", domain_info->action); + return -EINVAL; + } + + return 0; +} + +static int nxpwifi_set_aes_key(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + struct nxpwifi_ds_encrypt_key *enc_key, + struct host_cmd_ds_802_11_key_material *km) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u16 size, len = KEY_PARAMS_FIXED_LEN; + u8 key_type, key_type_igtk; + + if (enc_key->key_len == WLAN_KEY_LEN_CCMP) { + key_type = KEY_TYPE_ID_AES; + key_type_igtk = KEY_TYPE_ID_AES_CMAC; + } else { + key_type = KEY_TYPE_ID_GCMP_256; + key_type_igtk = KEY_TYPE_ID_BIP_GMAC_256; + } + + if (enc_key->is_igtk_key) { + km->key_param_set.key_info &= cpu_to_le16(~KEY_MCAST); + km->key_param_set.key_info |= cpu_to_le16(KEY_IGTK); + km->key_param_set.key_type = key_type_igtk; + if (enc_key->key_len == WLAN_KEY_LEN_CCMP) { + nxpwifi_dbg(adapter, INFO, + "%s: Set CMAC AES Key\n", __func__); + if (enc_key->is_rx_seq_valid) + memcpy(km->key_param_set.key_params.cmac_aes.ipn, + enc_key->pn, enc_key->pn_len); + km->key_param_set.key_params.cmac_aes.key_len = + cpu_to_le16(enc_key->key_len); + memcpy(km->key_param_set.key_params.cmac_aes.key, + enc_key->key_material, enc_key->key_len); + len += sizeof(struct nxpwifi_cmac_aes_param); + } else { + nxpwifi_dbg(adapter, INFO, + "%s: Set GMAC AES Key\n", __func__); + if (enc_key->is_rx_seq_valid) + memcpy(km->key_param_set.key_params.gmac_aes.ipn, + enc_key->pn, enc_key->pn_len); + km->key_param_set.key_params.gmac_aes.key_len = + cpu_to_le16(enc_key->key_len); + memcpy(km->key_param_set.key_params.gmac_aes.key, + enc_key->key_material, enc_key->key_len); + len += sizeof(struct nxpwifi_gmac_aes_param); + } + } else if (enc_key->is_igtk_def_key) { + nxpwifi_dbg(adapter, INFO, + "%s: Set CMAC default Key index\n", __func__); + km->key_param_set.key_type = key_type_igtk; + km->key_param_set.key_idx = enc_key->key_index & KEY_INDEX_MASK; + } else { + nxpwifi_dbg(adapter, INFO, + "%s: Set AES Key\n", __func__); + if (enc_key->is_rx_seq_valid) + memcpy(km->key_param_set.key_params.aes.pn, + enc_key->pn, enc_key->pn_len); + km->key_param_set.key_type = key_type; + km->key_param_set.key_params.aes.key_len = + cpu_to_le16(enc_key->key_len); + memcpy(km->key_param_set.key_params.aes.key, + enc_key->key_material, enc_key->key_len); + len += sizeof(struct nxpwifi_aes_param); + } + + km->key_param_set.len = cpu_to_le16(len); + size = len + sizeof(struct nxpwifi_ie_types_header) + + sizeof(km->action) + S_DS_GEN; + cmd->size = cpu_to_le16(size); + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_key_material(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ds_encrypt_key *enc_key = + (struct nxpwifi_ds_encrypt_key *)data_buf; + u8 *mac = enc_key->mac_addr; + u16 key_info, len = KEY_PARAMS_FIXED_LEN; + struct host_cmd_ds_802_11_key_material *km = + &cmd->params.key_material; + + cmd->command = cpu_to_le16(HOST_CMD_802_11_KEY_MATERIAL); + km->action = cpu_to_le16(cmd_action); + + if (cmd_action == HOST_ACT_GEN_GET) { + nxpwifi_dbg(adapter, INFO, "%s: Get key\n", __func__); + km->key_param_set.key_idx = + enc_key->key_index & KEY_INDEX_MASK; + km->key_param_set.type = cpu_to_le16(TLV_TYPE_KEY_PARAM_V2); + km->key_param_set.len = cpu_to_le16(KEY_PARAMS_FIXED_LEN); + ether_addr_copy(km->key_param_set.mac_addr, mac); + + if (enc_key->key_index & NXPWIFI_KEY_INDEX_UNICAST) + key_info = KEY_UNICAST; + else + key_info = KEY_MCAST; + + if (enc_key->is_igtk_key) + key_info |= KEY_IGTK; + + km->key_param_set.key_info = cpu_to_le16(key_info); + + cmd->size = cpu_to_le16(sizeof(struct nxpwifi_ie_types_header) + + S_DS_GEN + KEY_PARAMS_FIXED_LEN + + sizeof(km->action)); + return 0; + } + + memset(&km->key_param_set, 0, + sizeof(struct nxpwifi_ie_type_key_param_set)); + + if (enc_key->key_disable) { + nxpwifi_dbg(adapter, INFO, "%s: Remove key\n", __func__); + km->action = cpu_to_le16(HOST_ACT_GEN_REMOVE); + km->key_param_set.type = cpu_to_le16(TLV_TYPE_KEY_PARAM_V2); + km->key_param_set.len = cpu_to_le16(KEY_PARAMS_FIXED_LEN); + km->key_param_set.key_idx = enc_key->key_index & KEY_INDEX_MASK; + key_info = KEY_MCAST | KEY_UNICAST; + km->key_param_set.key_info = cpu_to_le16(key_info); + ether_addr_copy(km->key_param_set.mac_addr, mac); + cmd->size = cpu_to_le16(sizeof(struct nxpwifi_ie_types_header) + + S_DS_GEN + KEY_PARAMS_FIXED_LEN + + sizeof(km->action)); + return 0; + } + + km->action = cpu_to_le16(HOST_ACT_GEN_SET); + km->key_param_set.key_idx = enc_key->key_index & KEY_INDEX_MASK; + km->key_param_set.type = cpu_to_le16(TLV_TYPE_KEY_PARAM_V2); + key_info = KEY_ENABLED; + ether_addr_copy(km->key_param_set.mac_addr, mac); + + if (enc_key->key_len <= WLAN_KEY_LEN_WEP104) { + nxpwifi_dbg(adapter, INFO, "%s: Set WEP Key\n", __func__); + len += sizeof(struct nxpwifi_wep_param); + km->key_param_set.len = cpu_to_le16(len); + km->key_param_set.key_type = KEY_TYPE_ID_WEP; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + key_info |= KEY_MCAST | KEY_UNICAST; + } else { + if (enc_key->is_current_wep_key) { + key_info |= KEY_MCAST | KEY_UNICAST; + if (km->key_param_set.key_idx == + (priv->wep_key_curr_index & KEY_INDEX_MASK)) + key_info |= KEY_DEFAULT; + } else { + if (is_broadcast_ether_addr(mac)) + key_info |= KEY_MCAST; + else + key_info |= KEY_UNICAST | KEY_DEFAULT; + } + } + km->key_param_set.key_info = cpu_to_le16(key_info); + + km->key_param_set.key_params.wep.key_len = + cpu_to_le16(enc_key->key_len); + memcpy(km->key_param_set.key_params.wep.key, + enc_key->key_material, enc_key->key_len); + + cmd->size = cpu_to_le16(sizeof(struct nxpwifi_ie_types_header) + + len + sizeof(km->action) + S_DS_GEN); + return 0; + } + + if (is_broadcast_ether_addr(mac)) + key_info |= KEY_MCAST | KEY_RX_KEY; + else + key_info |= KEY_UNICAST | KEY_TX_KEY | KEY_RX_KEY; + + /* Enable default key for WPA/WPA2 */ + if (!priv->wpa_is_gtk_set) + key_info |= KEY_DEFAULT; + + km->key_param_set.key_info = cpu_to_le16(key_info); + + if (enc_key->key_cipher != WLAN_CIPHER_SUITE_TKIP && + enc_key->key_len >= WLAN_KEY_LEN_CCMP) + return nxpwifi_set_aes_key(priv, cmd, enc_key, km); + + if (enc_key->key_len == WLAN_KEY_LEN_TKIP) { + nxpwifi_dbg(adapter, INFO, + "%s: Set TKIP Key\n", __func__); + if (enc_key->is_rx_seq_valid) + memcpy(km->key_param_set.key_params.tkip.pn, + enc_key->pn, enc_key->pn_len); + km->key_param_set.key_type = KEY_TYPE_ID_TKIP; + km->key_param_set.key_params.tkip.key_len = + cpu_to_le16(enc_key->key_len); + memcpy(km->key_param_set.key_params.tkip.key, + enc_key->key_material, enc_key->key_len); + + len += sizeof(struct nxpwifi_tkip_param); + km->key_param_set.len = cpu_to_le16(len); + cmd->size = cpu_to_le16(sizeof(struct nxpwifi_ie_types_header) + + len + sizeof(km->action) + S_DS_GEN); + } + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_key_material(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11_key_material *key; + int len; + + key = &resp->params.key_material; + + len = le16_to_cpu(key->key_param_set.key_params.aes.key_len); + if (len > sizeof(key->key_param_set.key_params.aes.key)) + return -EINVAL; + + if (le16_to_cpu(key->action) == HOST_ACT_GEN_SET) { + if ((le16_to_cpu(key->key_param_set.key_info) & KEY_MCAST)) { + nxpwifi_dbg(priv->adapter, INFO, + "info: key: GTK is set\n"); + priv->wpa_is_gtk_set = true; + priv->scan_block = false; + priv->port_open = true; + } + } + + if (key->key_param_set.key_type != KEY_TYPE_ID_AES && + key->key_param_set.key_type != KEY_TYPE_ID_GCMP_256) + return 0; + + memset(priv->aes_key.key_param_set.key_params.aes.key, 0, + sizeof(key->key_param_set.key_params.aes.key)); + priv->aes_key.key_param_set.key_params.aes.key_len = cpu_to_le16(len); + memcpy(priv->aes_key.key_param_set.key_params.aes.key, + key->key_param_set.key_params.aes.key, len); + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_bg_scan_config(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_802_11_bg_scan_config(priv, cmd, data_buf); +} + +static int +nxpwifi_cmd_sta_802_11_bg_scan_query(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_802_11_bg_scan_query(cmd); +} + +static int +nxpwifi_ret_sta_802_11_bg_scan_query(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + + ret = nxpwifi_ret_802_11_scan(priv, resp); + cfg80211_sched_scan_results(priv->wdev.wiphy, 0); + nxpwifi_dbg(adapter, CMD, + "info: CMD_RESP: BG_SCAN result is ready!\n"); + + return ret; +} + +static int +nxpwifi_cmd_sta_wmm_get_status(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(HOST_CMD_WMM_GET_STATUS); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_wmm_get_status) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_ret_sta_wmm_get_status(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_wmm_get_status(priv, resp); +} + +static int +nxpwifi_cmd_sta_802_11_subsc_evt(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_802_11_subsc_evt *subsc_evt = &cmd->params.subsc_evt; + struct nxpwifi_ds_misc_subsc_evt *subsc_evt_cfg = + (struct nxpwifi_ds_misc_subsc_evt *)data_buf; + struct nxpwifi_ie_types_rssi_threshold *rssi_tlv; + u16 event_bitmap; + u8 *pos; + + cmd->command = cpu_to_le16(HOST_CMD_802_11_SUBSCRIBE_EVENT); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_subsc_evt) + + S_DS_GEN); + + subsc_evt->action = cpu_to_le16(subsc_evt_cfg->action); + nxpwifi_dbg(priv->adapter, CMD, + "cmd: action: %d\n", subsc_evt_cfg->action); + + /* For query requests, no configuration TLV structures are to be added. */ + if (subsc_evt_cfg->action == HOST_ACT_GEN_GET) + return 0; + + subsc_evt->events = cpu_to_le16(subsc_evt_cfg->events); + + event_bitmap = subsc_evt_cfg->events; + nxpwifi_dbg(priv->adapter, CMD, "cmd: event bitmap : %16x\n", + event_bitmap); + + if ((subsc_evt_cfg->action == HOST_ACT_BITWISE_CLR || + subsc_evt_cfg->action == HOST_ACT_BITWISE_SET) && + event_bitmap == 0) { + nxpwifi_dbg(priv->adapter, ERROR, + "Error: No event specified\t" + "for bitwise action type\n"); + return -EINVAL; + } + + /* + * Append TLV structures for each of the specified events for + * subscribing or re-configuring. This is not required for + * bitwise unsubscribing request. + */ + if (subsc_evt_cfg->action == HOST_ACT_BITWISE_CLR) + return 0; + + pos = ((u8 *)subsc_evt) + + sizeof(struct host_cmd_ds_802_11_subsc_evt); + + if (event_bitmap & BITMASK_BCN_RSSI_LOW) { + rssi_tlv = (struct nxpwifi_ie_types_rssi_threshold *)pos; + + rssi_tlv->header.type = cpu_to_le16(TLV_TYPE_RSSI_LOW); + rssi_tlv->header.len = + cpu_to_le16(sizeof(struct nxpwifi_ie_types_rssi_threshold) - + sizeof(struct nxpwifi_ie_types_header)); + rssi_tlv->abs_value = subsc_evt_cfg->bcn_l_rssi_cfg.abs_value; + rssi_tlv->evt_freq = subsc_evt_cfg->bcn_l_rssi_cfg.evt_freq; + + nxpwifi_dbg(priv->adapter, EVENT, + "Cfg Beacon Low Rssi event,\t" + "RSSI:-%d dBm, Freq:%d\n", + subsc_evt_cfg->bcn_l_rssi_cfg.abs_value, + subsc_evt_cfg->bcn_l_rssi_cfg.evt_freq); + + pos += sizeof(struct nxpwifi_ie_types_rssi_threshold); + le16_unaligned_add_cpu + (&cmd->size, + sizeof(struct nxpwifi_ie_types_rssi_threshold)); + } + + if (event_bitmap & BITMASK_BCN_RSSI_HIGH) { + rssi_tlv = (struct nxpwifi_ie_types_rssi_threshold *)pos; + + rssi_tlv->header.type = cpu_to_le16(TLV_TYPE_RSSI_HIGH); + rssi_tlv->header.len = + cpu_to_le16(sizeof(struct nxpwifi_ie_types_rssi_threshold) - + sizeof(struct nxpwifi_ie_types_header)); + rssi_tlv->abs_value = subsc_evt_cfg->bcn_h_rssi_cfg.abs_value; + rssi_tlv->evt_freq = subsc_evt_cfg->bcn_h_rssi_cfg.evt_freq; + + nxpwifi_dbg(priv->adapter, EVENT, + "Cfg Beacon High Rssi event,\t" + "RSSI:-%d dBm, Freq:%d\n", + subsc_evt_cfg->bcn_h_rssi_cfg.abs_value, + subsc_evt_cfg->bcn_h_rssi_cfg.evt_freq); + + pos += sizeof(struct nxpwifi_ie_types_rssi_threshold); + le16_unaligned_add_cpu + (&cmd->size, + sizeof(struct nxpwifi_ie_types_rssi_threshold)); + } + + return 0; +} + +static int +nxpwifi_ret_sta_subsc_evt(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11_subsc_evt *cmd_sub_event = + &resp->params.subsc_evt; + + /* + * For every subscribe event command (Get/Set/Clear), FW reports the current + * set of subscribed events + */ + nxpwifi_dbg(priv->adapter, EVENT, + "Bitmap of currently subscribed events: %16x\n", + le16_to_cpu(cmd_sub_event->events)); + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_tx_rate_query(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(HOST_CMD_802_11_TX_RATE_QUERY); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_tx_rate_query) + + S_DS_GEN); + priv->tx_rate = 0; + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_tx_rate_query(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + priv->tx_rate = resp->params.tx_rate.tx_rate; + priv->tx_htinfo = resp->params.tx_rate.ht_info; + if (!priv->is_data_rate_auto) + priv->data_rate = + nxpwifi_index_to_data_rate(priv, priv->tx_rate, + priv->tx_htinfo); + + return 0; +} + +static int +nxpwifi_cmd_sta_mem_access(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_ds_mem_rw *mem_rw = + (struct nxpwifi_ds_mem_rw *)data_buf; + struct host_cmd_ds_mem_access *mem_access = (void *)&cmd->params.mem; + + cmd->command = cpu_to_le16(HOST_CMD_MEM_ACCESS); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_mem_access) + + S_DS_GEN); + + mem_access->action = cpu_to_le16(cmd_action); + mem_access->addr = cpu_to_le32(mem_rw->addr); + mem_access->value = cpu_to_le32(mem_rw->value); + + return 0; +} + +static int +nxpwifi_ret_sta_mem_access(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_mem_access *mem = (void *)&resp->params.mem; + + priv->mem_rw.addr = le32_to_cpu(mem->addr); + priv->mem_rw.value = le32_to_cpu(mem->value); + + return 0; +} + +static u32 nxpwifi_parse_cal_cfg(u8 *src, size_t len, u8 *dst) +{ + u8 *s = src, *d = dst; + + while (s - src < len) { + if (*s && (isspace(*s) || *s == '\t')) { + s++; + continue; + } + if (isxdigit(*s)) { + if (kstrtou8(s, 16, d)) + return 0; + d++; + s += 2; + } else { + s++; + } + } + + return d - dst; +} + +static int +nxpwifi_cmd_sta_cfg_data(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u32 len; + u8 *data = (u8 *)cmd + S_DS_GEN; + + if (adapter->cal_data->data && adapter->cal_data->size > 0) { + len = nxpwifi_parse_cal_cfg((u8 *)adapter->cal_data->data, + adapter->cal_data->size, data); + nxpwifi_dbg(adapter, INFO, + "download cfg_data from config file\n"); + } else { + return -EINVAL; + } + + cmd->command = cpu_to_le16(HOST_CMD_CFG_DATA); + cmd->size = cpu_to_le16(S_DS_GEN + len); + + return 0; +} + +static int +nxpwifi_ret_sta_cfg_data(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + if (resp->result != HOST_RESULT_OK) { + nxpwifi_dbg(priv->adapter, ERROR, "Cal data cmd resp failed\n"); + return -EINVAL; + } + + return 0; +} + +static int +nxpwifi_cmd_sta_ver_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(cmd_no); + cmd->params.verext.version_str_sel = + (u8)(get_unaligned((u32 *)data_buf)); + memcpy(&cmd->params, data_buf, sizeof(struct host_cmd_ds_version_ext)); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_version_ext) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_ret_sta_ver_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_version_ext *ver_ext = &resp->params.verext; + struct host_cmd_ds_version_ext *version_ext = + (struct host_cmd_ds_version_ext *)data_buf; + + if (test_and_clear_bit(NXPWIFI_IS_REQUESTING_FW_VEREXT, &priv->adapter->work_flags)) { + if (strncmp(ver_ext->version_str, "ChipRev:20, BB:9b(10.00), RF:40(21)", + NXPWIFI_VERSION_STR_LENGTH) == 0) { + struct nxpwifi_ds_auto_ds auto_ds = { + .auto_ds = DEEP_SLEEP_OFF, + }; + + nxpwifi_dbg(priv->adapter, MSG, + "Bad HW revision detected, disabling deep sleep\n"); + + if (nxpwifi_send_cmd(priv, HOST_CMD_802_11_PS_MODE_ENH, + DIS_AUTO_PS, BITMAP_AUTO_DS, &auto_ds, false)) { + nxpwifi_dbg(priv->adapter, MSG, + "Disabling deep sleep failed.\n"); + } + } + + return 0; + } + + if (version_ext) { + version_ext->version_str_sel = ver_ext->version_str_sel; + memcpy(version_ext->version_str, ver_ext->version_str, + NXPWIFI_VERSION_STR_LENGTH); + memcpy(priv->version_str, ver_ext->version_str, + NXPWIFI_VERSION_STR_LENGTH); + + /* Ensure the version string from the firmware is 0-terminated */ + priv->version_str[NXPWIFI_VERSION_STR_LENGTH - 1] = '\0'; + } + return 0; +} + +static int +nxpwifi_cmd_append_rpn_expression(struct nxpwifi_private *priv, + struct nxpwifi_mef_entry *mef_entry, + u8 **buffer) +{ + struct nxpwifi_mef_filter *filter = mef_entry->filter; + int i, byte_len; + u8 *stack_ptr = *buffer; + + for (i = 0; i < NXPWIFI_MEF_MAX_FILTERS; i++) { + filter = &mef_entry->filter[i]; + if (!filter->filt_type) + break; + put_unaligned_le32((u32)filter->repeat, stack_ptr); + stack_ptr += 4; + *stack_ptr = TYPE_DNUM; + stack_ptr += 1; + + byte_len = filter->byte_seq[NXPWIFI_MEF_MAX_BYTESEQ]; + memcpy(stack_ptr, filter->byte_seq, byte_len); + stack_ptr += byte_len; + *stack_ptr = byte_len; + stack_ptr += 1; + *stack_ptr = TYPE_BYTESEQ; + stack_ptr += 1; + put_unaligned_le32((u32)filter->offset, stack_ptr); + stack_ptr += 4; + *stack_ptr = TYPE_DNUM; + stack_ptr += 1; + + *stack_ptr = filter->filt_type; + stack_ptr += 1; + + if (filter->filt_action) { + *stack_ptr = filter->filt_action; + stack_ptr += 1; + } + + if (stack_ptr - *buffer > STACK_NBYTES) + return -ENOMEM; + } + + *buffer = stack_ptr; + return 0; +} + +static int +nxpwifi_cmd_sta_mef_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_mef_cfg *mef_cfg = &cmd->params.mef_cfg; + struct nxpwifi_ds_mef_cfg *mef = + (struct nxpwifi_ds_mef_cfg *)data_buf; + struct nxpwifi_fw_mef_entry *mef_entry = NULL; + u8 *pos = (u8 *)mef_cfg; + u16 i; + int ret = 0; + + cmd->command = cpu_to_le16(HOST_CMD_MEF_CFG); + + mef_cfg->criteria = cpu_to_le32(mef->criteria); + mef_cfg->num_entries = cpu_to_le16(mef->num_entries); + pos += sizeof(*mef_cfg); + + for (i = 0; i < mef->num_entries; i++) { + mef_entry = (struct nxpwifi_fw_mef_entry *)pos; + mef_entry->mode = mef->mef_entry[i].mode; + mef_entry->action = mef->mef_entry[i].action; + pos += sizeof(*mef_entry); + + ret = nxpwifi_cmd_append_rpn_expression(priv, + &mef->mef_entry[i], + &pos); + if (ret) + return ret; + + mef_entry->exprsize = + cpu_to_le16(pos - mef_entry->expr); + } + cmd->size = cpu_to_le16((u16)(pos - (u8 *)mef_cfg) + S_DS_GEN); + + return ret; +} + +static int +nxpwifi_cmd_sta_802_11_rssi_info(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(HOST_CMD_RSSI_INFO); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_rssi_info) + + S_DS_GEN); + cmd->params.rssi_info.action = cpu_to_le16(cmd_action); + cmd->params.rssi_info.ndata = cpu_to_le16(priv->data_avg_factor); + cmd->params.rssi_info.nbcn = cpu_to_le16(priv->bcn_avg_factor); + + /* Reset SNR/NF/RSSI values in private structure */ + priv->data_rssi_last = 0; + priv->data_nf_last = 0; + priv->data_rssi_avg = 0; + priv->data_nf_avg = 0; + priv->bcn_rssi_last = 0; + priv->bcn_nf_last = 0; + priv->bcn_rssi_avg = 0; + priv->bcn_nf_avg = 0; + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_rssi_info(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11_rssi_info_rsp *rssi_info_rsp = + &resp->params.rssi_info_rsp; + struct nxpwifi_ds_misc_subsc_evt *subsc_evt = + &priv->async_subsc_evt_storage; + + priv->data_rssi_last = le16_to_cpu(rssi_info_rsp->data_rssi_last); + priv->data_nf_last = le16_to_cpu(rssi_info_rsp->data_nf_last); + + priv->data_rssi_avg = le16_to_cpu(rssi_info_rsp->data_rssi_avg); + priv->data_nf_avg = le16_to_cpu(rssi_info_rsp->data_nf_avg); + + priv->bcn_rssi_last = le16_to_cpu(rssi_info_rsp->bcn_rssi_last); + priv->bcn_nf_last = le16_to_cpu(rssi_info_rsp->bcn_nf_last); + + priv->bcn_rssi_avg = le16_to_cpu(rssi_info_rsp->bcn_rssi_avg); + priv->bcn_nf_avg = le16_to_cpu(rssi_info_rsp->bcn_nf_avg); + + if (priv->subsc_evt_rssi_state == EVENT_HANDLED) + return 0; + + memset(subsc_evt, 0x00, sizeof(struct nxpwifi_ds_misc_subsc_evt)); + + /* Resubscribe low and high rssi events with new thresholds */ + subsc_evt->events = BITMASK_BCN_RSSI_LOW | BITMASK_BCN_RSSI_HIGH; + subsc_evt->action = HOST_ACT_BITWISE_SET; + if (priv->subsc_evt_rssi_state == RSSI_LOW_RECVD) { + subsc_evt->bcn_l_rssi_cfg.abs_value = abs(priv->bcn_rssi_avg - + priv->cqm_rssi_hyst); + subsc_evt->bcn_h_rssi_cfg.abs_value = abs(priv->cqm_rssi_thold); + } else if (priv->subsc_evt_rssi_state == RSSI_HIGH_RECVD) { + subsc_evt->bcn_l_rssi_cfg.abs_value = abs(priv->cqm_rssi_thold); + subsc_evt->bcn_h_rssi_cfg.abs_value = abs(priv->bcn_rssi_avg + + priv->cqm_rssi_hyst); + } + subsc_evt->bcn_l_rssi_cfg.evt_freq = 1; + subsc_evt->bcn_h_rssi_cfg.evt_freq = 1; + + priv->subsc_evt_rssi_state = EVENT_HANDLED; + + nxpwifi_send_cmd(priv, HOST_CMD_802_11_SUBSCRIBE_EVENT, + 0, 0, subsc_evt, false); + + return 0; +} + +static int +nxpwifi_cmd_sta_func_init(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + if (priv->adapter->hw_status == NXPWIFI_HW_STATUS_RESET) + priv->adapter->hw_status = NXPWIFI_HW_STATUS_READY; + cmd->command = cpu_to_le16(cmd_no); + cmd->size = cpu_to_le16(S_DS_GEN); + + return 0; +} + +static int +nxpwifi_cmd_sta_func_shutdown(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + priv->adapter->hw_status = NXPWIFI_HW_STATUS_RESET; + cmd->command = cpu_to_le16(cmd_no); + cmd->size = cpu_to_le16(S_DS_GEN); + + return 0; +} + +static int +nxpwifi_cmd_sta_11n_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_11n_cfg(priv, cmd, cmd_action, data_buf); +} + +static int +nxpwifi_cmd_sta_11n_addba_req(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_11n_addba_req(cmd, data_buf); +} + +static int +nxpwifi_ret_sta_11n_addba_req(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_11n_addba_req(priv, resp); +} + +static int +nxpwifi_cmd_sta_11n_addba_rsp(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_11n_addba_rsp_gen(priv, cmd, data_buf); +} + +static int +nxpwifi_ret_sta_11n_addba_rsp(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_11n_addba_resp(priv, resp); +} + +static int +nxpwifi_cmd_sta_11n_delba(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_11n_delba(cmd, data_buf); +} + +static int +nxpwifi_ret_sta_11n_delba(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_11n_delba(priv, resp); +} + +static int +nxpwifi_cmd_sta_tx_power_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_types_power_group *pg_tlv; + struct host_cmd_ds_txpwr_cfg *cmd_txp_cfg = &cmd->params.txp_cfg; + struct host_cmd_ds_txpwr_cfg *txp = + (struct host_cmd_ds_txpwr_cfg *)data_buf; + + cmd->command = cpu_to_le16(HOST_CMD_TXPWR_CFG); + cmd->size = + cpu_to_le16(S_DS_GEN + sizeof(struct host_cmd_ds_txpwr_cfg)); + switch (cmd_action) { + case HOST_ACT_GEN_SET: + if (txp->mode) { + pg_tlv = (struct nxpwifi_types_power_group + *)((unsigned long)txp + + sizeof(struct host_cmd_ds_txpwr_cfg)); + memmove(cmd_txp_cfg, txp, + sizeof(struct host_cmd_ds_txpwr_cfg) + + sizeof(struct nxpwifi_types_power_group) + + le16_to_cpu(pg_tlv->length)); + + pg_tlv = (struct nxpwifi_types_power_group *)((u8 *) + cmd_txp_cfg + + sizeof(struct host_cmd_ds_txpwr_cfg)); + cmd->size = cpu_to_le16(le16_to_cpu(cmd->size) + + sizeof(struct nxpwifi_types_power_group) + + le16_to_cpu(pg_tlv->length)); + } else { + memmove(cmd_txp_cfg, txp, sizeof(*txp)); + } + cmd_txp_cfg->action = cpu_to_le16(cmd_action); + break; + case HOST_ACT_GEN_GET: + cmd_txp_cfg->action = cpu_to_le16(cmd_action); + break; + } + + return 0; +} + +static int nxpwifi_get_power_level(struct nxpwifi_private *priv, void *data_buf) +{ + int length, max_power = -1, min_power = -1; + struct nxpwifi_types_power_group *pg_tlv_hdr; + struct nxpwifi_power_group *pg; + + if (!data_buf) + return -ENOMEM; + + pg_tlv_hdr = (struct nxpwifi_types_power_group *)((u8 *)data_buf); + pg = (struct nxpwifi_power_group *) + ((u8 *)pg_tlv_hdr + sizeof(struct nxpwifi_types_power_group)); + length = le16_to_cpu(pg_tlv_hdr->length); + + /* At least one structure required to update power */ + if (length < sizeof(struct nxpwifi_power_group)) + return 0; + + max_power = pg->power_max; + min_power = pg->power_min; + length -= sizeof(struct nxpwifi_power_group); + + while (length >= sizeof(struct nxpwifi_power_group)) { + pg++; + if (max_power < pg->power_max) + max_power = pg->power_max; + + if (min_power > pg->power_min) + min_power = pg->power_min; + + length -= sizeof(struct nxpwifi_power_group); + } + priv->min_tx_power_level = (u8)min_power; + priv->max_tx_power_level = (u8)max_power; + + return 0; +} + +static int +nxpwifi_ret_sta_tx_power_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_txpwr_cfg *txp_cfg = &resp->params.txp_cfg; + struct nxpwifi_types_power_group *pg_tlv_hdr; + struct nxpwifi_power_group *pg; + u16 action = le16_to_cpu(txp_cfg->action); + u16 tlv_buf_left; + + pg_tlv_hdr = (struct nxpwifi_types_power_group *) + ((u8 *)txp_cfg + + sizeof(struct host_cmd_ds_txpwr_cfg)); + + pg = (struct nxpwifi_power_group *) + ((u8 *)pg_tlv_hdr + + sizeof(struct nxpwifi_types_power_group)); + + tlv_buf_left = le16_to_cpu(resp->size) - S_DS_GEN - sizeof(*txp_cfg); + if (tlv_buf_left < + le16_to_cpu(pg_tlv_hdr->length) + sizeof(*pg_tlv_hdr)) + return 0; + + switch (action) { + case HOST_ACT_GEN_GET: + if (adapter->hw_status == NXPWIFI_HW_STATUS_INITIALIZING) + nxpwifi_get_power_level(priv, pg_tlv_hdr); + + priv->tx_power_level = (u16)pg->power_min; + break; + + case HOST_ACT_GEN_SET: + if (!le32_to_cpu(txp_cfg->mode)) + break; + + if (pg->power_max == pg->power_min) + priv->tx_power_level = (u16)pg->power_min; + break; + default: + nxpwifi_dbg(adapter, ERROR, + "CMD_RESP: unknown cmd action %d\n", + action); + return 0; + } + nxpwifi_dbg(adapter, INFO, + "info: Current TxPower Level = %d, Max Power=%d, Min Power=%d\n", + priv->tx_power_level, priv->max_tx_power_level, + priv->min_tx_power_level); + + return 0; +} + +static int +nxpwifi_cmd_sta_tx_rate_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_tx_rate_cfg *rate_cfg = &cmd->params.tx_rate_cfg; + u16 *pbitmap_rates = (u16 *)data_buf; + struct nxpwifi_rate_scope *rate_scope; + struct nxpwifi_rate_drop_pattern *rate_drop; + u32 i; + + cmd->command = cpu_to_le16(HOST_CMD_TX_RATE_CFG); + + rate_cfg->action = cpu_to_le16(cmd_action); + rate_cfg->cfg_index = 0; + + rate_scope = (struct nxpwifi_rate_scope *)((u8 *)rate_cfg + + sizeof(struct host_cmd_ds_tx_rate_cfg)); + rate_scope->type = cpu_to_le16(TLV_TYPE_RATE_SCOPE); + rate_scope->length = cpu_to_le16 + (sizeof(*rate_scope) - sizeof(struct nxpwifi_ie_types_header)); + if (pbitmap_rates) { + rate_scope->hr_dsss_rate_bitmap = cpu_to_le16(pbitmap_rates[0]); + rate_scope->ofdm_rate_bitmap = cpu_to_le16(pbitmap_rates[1]); + for (i = 0; i < ARRAY_SIZE(rate_scope->ht_mcs_rate_bitmap); i++) + rate_scope->ht_mcs_rate_bitmap[i] = + cpu_to_le16(pbitmap_rates[2 + i]); + if (priv->adapter->fw_api_ver == NXPWIFI_FW_V15) { + for (i = 0; + i < ARRAY_SIZE(rate_scope->vht_mcs_rate_bitmap); + i++) + rate_scope->vht_mcs_rate_bitmap[i] = + cpu_to_le16(pbitmap_rates[10 + i]); + } + } else { + rate_scope->hr_dsss_rate_bitmap = + cpu_to_le16(priv->bitmap_rates[0]); + rate_scope->ofdm_rate_bitmap = + cpu_to_le16(priv->bitmap_rates[1]); + for (i = 0; i < ARRAY_SIZE(rate_scope->ht_mcs_rate_bitmap); i++) + rate_scope->ht_mcs_rate_bitmap[i] = + cpu_to_le16(priv->bitmap_rates[2 + i]); + if (priv->adapter->fw_api_ver == NXPWIFI_FW_V15) { + for (i = 0; + i < ARRAY_SIZE(rate_scope->vht_mcs_rate_bitmap); + i++) + rate_scope->vht_mcs_rate_bitmap[i] = + cpu_to_le16(priv->bitmap_rates[10 + i]); + } + } + + rate_drop = (struct nxpwifi_rate_drop_pattern *)((u8 *)rate_scope + + sizeof(struct nxpwifi_rate_scope)); + rate_drop->type = cpu_to_le16(TLV_TYPE_RATE_DROP_CONTROL); + rate_drop->length = cpu_to_le16(sizeof(rate_drop->rate_drop_mode)); + rate_drop->rate_drop_mode = 0; + + cmd->size = + cpu_to_le16(S_DS_GEN + sizeof(struct host_cmd_ds_tx_rate_cfg) + + sizeof(struct nxpwifi_rate_scope) + + sizeof(struct nxpwifi_rate_drop_pattern)); + + return 0; +} + +static void nxpwifi_ret_rate_scope(struct nxpwifi_private *priv, u8 *tlv_buf) +{ + struct nxpwifi_rate_scope *rate_scope; + int i; + + rate_scope = (struct nxpwifi_rate_scope *)tlv_buf; + priv->bitmap_rates[0] = + le16_to_cpu(rate_scope->hr_dsss_rate_bitmap); + priv->bitmap_rates[1] = + le16_to_cpu(rate_scope->ofdm_rate_bitmap); + for (i = 0; i < ARRAY_SIZE(rate_scope->ht_mcs_rate_bitmap); i++) + priv->bitmap_rates[2 + i] = + le16_to_cpu(rate_scope->ht_mcs_rate_bitmap[i]); + + if (priv->adapter->fw_api_ver == NXPWIFI_FW_V15) { + for (i = 0; i < ARRAY_SIZE(rate_scope->vht_mcs_rate_bitmap); + i++) + priv->bitmap_rates[10 + i] = + le16_to_cpu(rate_scope->vht_mcs_rate_bitmap[i]); + } +} + +static int +nxpwifi_ret_sta_tx_rate_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_tx_rate_cfg *rate_cfg = &resp->params.tx_rate_cfg; + struct nxpwifi_ie_types_header *head; + u16 tlv, tlv_buf_len, tlv_buf_left; + u8 *tlv_buf; + + tlv_buf = ((u8 *)rate_cfg) + sizeof(struct host_cmd_ds_tx_rate_cfg); + tlv_buf_left = le16_to_cpu(resp->size) - S_DS_GEN - sizeof(*rate_cfg); + + while (tlv_buf_left >= sizeof(*head)) { + head = (struct nxpwifi_ie_types_header *)tlv_buf; + tlv = le16_to_cpu(head->type); + tlv_buf_len = le16_to_cpu(head->len); + + if (tlv_buf_left < (sizeof(*head) + tlv_buf_len)) + break; + + switch (tlv) { + case TLV_TYPE_RATE_SCOPE: + nxpwifi_ret_rate_scope(priv, tlv_buf); + break; + /* Add RATE_DROP tlv here */ + } + + tlv_buf += (sizeof(*head) + tlv_buf_len); + tlv_buf_left -= (sizeof(*head) + tlv_buf_len); + } + + priv->is_data_rate_auto = nxpwifi_is_rate_auto(priv); + + if (priv->is_data_rate_auto) + priv->data_rate = 0; + else + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_TX_RATE_QUERY, + HOST_ACT_GEN_GET, 0, NULL, false); + + return 0; +} + +static int +nxpwifi_cmd_sta_reconfigure_rx_buff(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_recfg_tx_buf(priv, cmd, cmd_action, data_buf); +} + +static int +nxpwifi_ret_sta_reconfigure_rx_buff(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (0xffff != (u16)le16_to_cpu(resp->params.tx_buf.buff_size)) { + adapter->tx_buf_size = + (u16)le16_to_cpu(resp->params.tx_buf.buff_size); + adapter->tx_buf_size = + (adapter->tx_buf_size / NXPWIFI_SDIO_BLOCK_SIZE) * + NXPWIFI_SDIO_BLOCK_SIZE; + adapter->curr_tx_buf_size = adapter->tx_buf_size; + nxpwifi_dbg(adapter, CMD, "cmd: curr_tx_buf_size=%d\n", + adapter->curr_tx_buf_size); + + if (adapter->if_ops.update_mp_end_port) { + u16 mp_end_port; + + mp_end_port = + le16_to_cpu(resp->params.tx_buf.mp_end_port); + adapter->if_ops.update_mp_end_port(adapter, + mp_end_port); + } + } + + return 0; +} + +static int +nxpwifi_cmd_sta_chan_report_request(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_issue_chan_report_request(priv, cmd, data_buf); +} + +static int +nxpwifi_cmd_sta_amsdu_aggr_ctrl(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_amsdu_aggr_ctrl(cmd, cmd_action, data_buf); +} + +static int +nxpwifi_cmd_sta_robust_coex(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_robust_coex *coex = &cmd->params.coex; + bool *is_timeshare = (bool *)data_buf; + struct nxpwifi_ie_types_robust_coex *coex_tlv; + + cmd->command = cpu_to_le16(HOST_CMD_ROBUST_COEX); + cmd->size = cpu_to_le16(sizeof(*coex) + sizeof(*coex_tlv) + S_DS_GEN); + + coex->action = cpu_to_le16(cmd_action); + coex_tlv = (struct nxpwifi_ie_types_robust_coex *) + ((u8 *)coex + sizeof(*coex)); + coex_tlv->header.type = cpu_to_le16(TLV_TYPE_ROBUST_COEX); + coex_tlv->header.len = cpu_to_le16(sizeof(coex_tlv->mode)); + + if (coex->action == HOST_ACT_GEN_GET) + return 0; + + if (*is_timeshare) + coex_tlv->mode = cpu_to_le32(NXPWIFI_COEX_MODE_TIMESHARE); + else + coex_tlv->mode = cpu_to_le32(NXPWIFI_COEX_MODE_SPATIAL); + + return 0; +} + +static int +nxpwifi_ret_sta_robust_coex(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_robust_coex *coex = &resp->params.coex; + bool *is_timeshare = (bool *)data_buf; + struct nxpwifi_ie_types_robust_coex *coex_tlv; + u16 action = le16_to_cpu(coex->action); + u32 mode; + + coex_tlv = (struct nxpwifi_ie_types_robust_coex + *)((u8 *)coex + sizeof(struct host_cmd_ds_robust_coex)); + if (action == HOST_ACT_GEN_GET) { + mode = le32_to_cpu(coex_tlv->mode); + if (mode == NXPWIFI_COEX_MODE_TIMESHARE) + *is_timeshare = true; + else + *is_timeshare = false; + } + + return 0; +} + +static int +nxpwifi_cmd_sta_enh_power_mode(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_802_11_ps_mode_enh *psmode_enh = + &cmd->params.psmode_enh; + u16 ps_bitmap = (u16)cmd_type; + struct nxpwifi_ds_auto_ds *auto_ds = + (struct nxpwifi_ds_auto_ds *)data_buf; + u8 *tlv; + u16 cmd_size = 0; + + cmd->command = cpu_to_le16(HOST_CMD_802_11_PS_MODE_ENH); + if (cmd_action == DIS_AUTO_PS) { + psmode_enh->action = cpu_to_le16(DIS_AUTO_PS); + psmode_enh->params.ps_bitmap = cpu_to_le16(ps_bitmap); + cmd->size = cpu_to_le16(S_DS_GEN + sizeof(psmode_enh->action) + + sizeof(psmode_enh->params.ps_bitmap)); + } else if (cmd_action == GET_PS) { + psmode_enh->action = cpu_to_le16(GET_PS); + psmode_enh->params.ps_bitmap = cpu_to_le16(ps_bitmap); + cmd->size = cpu_to_le16(S_DS_GEN + sizeof(psmode_enh->action) + + sizeof(psmode_enh->params.ps_bitmap)); + } else if (cmd_action == EN_AUTO_PS) { + psmode_enh->action = cpu_to_le16(EN_AUTO_PS); + psmode_enh->params.ps_bitmap = cpu_to_le16(ps_bitmap); + cmd_size = S_DS_GEN + sizeof(psmode_enh->action) + + sizeof(psmode_enh->params.ps_bitmap); + tlv = (u8 *)cmd + cmd_size; + if (ps_bitmap & BITMAP_STA_PS) { + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ie_types_ps_param *ps_tlv = + (struct nxpwifi_ie_types_ps_param *)tlv; + struct nxpwifi_ps_param *ps_mode = &ps_tlv->param; + + ps_tlv->header.type = cpu_to_le16(TLV_TYPE_PS_PARAM); + ps_tlv->header.len = cpu_to_le16(sizeof(*ps_tlv) - + sizeof(struct nxpwifi_ie_types_header)); + cmd_size += sizeof(*ps_tlv); + tlv += sizeof(*ps_tlv); + nxpwifi_dbg(priv->adapter, CMD, + "cmd: PS Command: Enter PS\n"); + ps_mode->null_pkt_interval = + cpu_to_le16(adapter->null_pkt_interval); + ps_mode->multiple_dtims = + cpu_to_le16(adapter->multiple_dtim); + ps_mode->bcn_miss_timeout = + cpu_to_le16(adapter->bcn_miss_time_out); + ps_mode->local_listen_interval = + cpu_to_le16(adapter->local_listen_interval); + ps_mode->delay_to_ps = + cpu_to_le16(adapter->delay_to_ps); + ps_mode->mode = cpu_to_le16(adapter->enhanced_ps_mode); + } + if (ps_bitmap & BITMAP_AUTO_DS) { + struct nxpwifi_ie_types_auto_ds_param *auto_ds_tlv = + (struct nxpwifi_ie_types_auto_ds_param *)tlv; + u16 idletime = 0; + + auto_ds_tlv->header.type = + cpu_to_le16(TLV_TYPE_AUTO_DS_PARAM); + auto_ds_tlv->header.len = + cpu_to_le16(sizeof(*auto_ds_tlv) - + sizeof(struct nxpwifi_ie_types_header)); + cmd_size += sizeof(*auto_ds_tlv); + tlv += sizeof(*auto_ds_tlv); + if (auto_ds) + idletime = auto_ds->idle_time; + nxpwifi_dbg(priv->adapter, CMD, + "cmd: PS Command: Enter Auto Deep Sleep\n"); + auto_ds_tlv->deep_sleep_timeout = cpu_to_le16(idletime); + } + cmd->size = cpu_to_le16(cmd_size); + } + return 0; +} + +static int +nxpwifi_ret_sta_enh_power_mode(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_ps_mode_enh *ps_mode = + &resp->params.psmode_enh; + struct nxpwifi_ds_pm_cfg *pm_cfg = + (struct nxpwifi_ds_pm_cfg *)data_buf; + u16 action = le16_to_cpu(ps_mode->action); + u16 ps_bitmap = le16_to_cpu(ps_mode->params.ps_bitmap); + u16 auto_ps_bitmap = + le16_to_cpu(ps_mode->params.ps_bitmap); + + nxpwifi_dbg(adapter, INFO, + "info: %s: PS_MODE cmd reply result=%#x action=%#X\n", + __func__, resp->result, action); + if (action == EN_AUTO_PS) { + if (auto_ps_bitmap & BITMAP_AUTO_DS) { + nxpwifi_dbg(adapter, CMD, + "cmd: Enabled auto deep sleep\n"); + priv->adapter->is_deep_sleep = true; + } + if (auto_ps_bitmap & BITMAP_STA_PS) { + nxpwifi_dbg(adapter, CMD, + "cmd: Enabled STA power save\n"); + if (adapter->sleep_period.period) + nxpwifi_dbg(adapter, CMD, + "cmd: set to uapsd/pps mode\n"); + } + } else if (action == DIS_AUTO_PS) { + if (ps_bitmap & BITMAP_AUTO_DS) { + priv->adapter->is_deep_sleep = false; + nxpwifi_dbg(adapter, CMD, + "cmd: Disabled auto deep sleep\n"); + } + if (ps_bitmap & BITMAP_STA_PS) { + nxpwifi_dbg(adapter, CMD, + "cmd: Disabled STA power save\n"); + if (adapter->sleep_period.period) { + adapter->delay_null_pkt = false; + adapter->tx_lock_flag = false; + adapter->pps_uapsd_mode = false; + } + } + } else if (action == GET_PS) { + if (ps_bitmap & BITMAP_STA_PS) + adapter->ps_mode = NXPWIFI_802_11_POWER_MODE_PSP; + else + adapter->ps_mode = NXPWIFI_802_11_POWER_MODE_CAM; + + nxpwifi_dbg(adapter, CMD, + "cmd: ps_bitmap=%#x\n", ps_bitmap); + + if (pm_cfg) { + /* This section is for get power save mode */ + if (ps_bitmap & BITMAP_STA_PS) + pm_cfg->param.ps_mode = 1; + else + pm_cfg->param.ps_mode = 0; + } + } + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_hs_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_hs_cfg_enh *hs_cfg = &cmd->params.opt_hs_cfg; + struct nxpwifi_hs_config_param *hscfg_param = + (struct nxpwifi_hs_config_param *)data_buf; + u8 *tlv = (u8 *)hs_cfg + sizeof(struct host_cmd_ds_802_11_hs_cfg_enh); + struct nxpwifi_ps_param_in_hs *psparam_tlv = NULL; + bool hs_activate = false; + u16 size; + + if (!hscfg_param) + /* New Activate command */ + hs_activate = true; + cmd->command = cpu_to_le16(HOST_CMD_802_11_HS_CFG_ENH); + + if (!hs_activate && + hscfg_param->conditions != cpu_to_le32(HS_CFG_CANCEL) && + (adapter->arp_filter_size > 0 && + adapter->arp_filter_size <= ARP_FILTER_MAX_BUF_SIZE)) { + nxpwifi_dbg(adapter, CMD, + "cmd: Attach %d bytes ArpFilter to HSCfg cmd\n", + adapter->arp_filter_size); + memcpy(((u8 *)hs_cfg) + + sizeof(struct host_cmd_ds_802_11_hs_cfg_enh), + adapter->arp_filter, adapter->arp_filter_size); + size = adapter->arp_filter_size + + sizeof(struct host_cmd_ds_802_11_hs_cfg_enh) + + S_DS_GEN; + tlv = (u8 *)hs_cfg + + sizeof(struct host_cmd_ds_802_11_hs_cfg_enh) + + adapter->arp_filter_size; + } else { + size = S_DS_GEN + sizeof(struct host_cmd_ds_802_11_hs_cfg_enh); + } + if (hs_activate) { + hs_cfg->action = cpu_to_le16(HS_ACTIVATE); + hs_cfg->params.hs_activate.resp_ctrl = cpu_to_le16(RESP_NEEDED); + + adapter->hs_activated_manually = true; + nxpwifi_dbg(priv->adapter, CMD, + "cmd: Activating host sleep manually\n"); + } else { + hs_cfg->action = cpu_to_le16(HS_CONFIGURE); + hs_cfg->params.hs_config.conditions = hscfg_param->conditions; + hs_cfg->params.hs_config.gpio = hscfg_param->gpio; + hs_cfg->params.hs_config.gap = hscfg_param->gap; + + size += sizeof(struct nxpwifi_ps_param_in_hs); + psparam_tlv = (struct nxpwifi_ps_param_in_hs *)tlv; + psparam_tlv->header.type = + cpu_to_le16(TLV_TYPE_PS_PARAMS_IN_HS); + psparam_tlv->header.len = + cpu_to_le16(sizeof(struct nxpwifi_ps_param_in_hs) + - sizeof(struct nxpwifi_ie_types_header)); + psparam_tlv->hs_wake_int = cpu_to_le32(HS_DEF_WAKE_INTERVAL); + psparam_tlv->hs_inact_timeout = + cpu_to_le32(HS_DEF_INACTIVITY_TIMEOUT); + + nxpwifi_dbg(adapter, CMD, + "cmd: HS_CFG_CMD: condition:0x%x gpio:0x%x gap:0x%x\n", + hs_cfg->params.hs_config.conditions, + hs_cfg->params.hs_config.gpio, + hs_cfg->params.hs_config.gap); + } + cmd->size = cpu_to_le16(size); + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_hs_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_802_11_hs_cfg(priv, resp); +} + +static int +nxpwifi_cmd_sta_set_bss_mode(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(cmd_no); + if (priv->bss_mode == NL80211_IFTYPE_STATION) + cmd->params.bss_mode.con_type = CONNECTION_TYPE_INFRA; + else if (priv->bss_mode == NL80211_IFTYPE_AP) + cmd->params.bss_mode.con_type = CONNECTION_TYPE_AP; + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_set_bss_mode) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_net_monitor(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_802_11_net_monitor *net_mon; + struct host_cmd_ds_802_11_net_monitor *cmd_net_mon = + &cmd->params.net_mon; + struct chan_band_param *chan_band = NULL; + u8 sec_chan_offset = 0; + u32 bw_offset = 0; + + net_mon = (struct nxpwifi_802_11_net_monitor *)data_buf; + + cmd->size = cpu_to_le16(S_DS_GEN + + sizeof(struct host_cmd_ds_802_11_net_monitor) + + sizeof(struct chan_band_param)); + cmd->command = cpu_to_le16(cmd_no); + cmd_net_mon->action = cpu_to_le16(cmd_action); + + if (cmd_action == HOST_ACT_GEN_SET) { + if (net_mon->enable_net_mon) { + cmd_net_mon->enable_net_mon = cpu_to_le16(0x1); + cmd_net_mon->filter_flag = cpu_to_le16((u16) + net_mon->filter_flag); + } + + if (net_mon->enable_net_mon && net_mon->channel) { + chan_band = &cmd_net_mon->monitor_chan.chan_band_param[0]; + cmd_net_mon->monitor_chan.header.type = + cpu_to_le16(TLV_TYPE_CHANNELBANDLIST); + cmd_net_mon->monitor_chan.header.len = + cpu_to_le16(sizeof(struct chan_band_param)); + chan_band->chan_number = (u8)net_mon->channel; + chan_band->band_cfg.chan_band = + nxpwifi_band_to_radio_type((u16)net_mon->band); + + if (net_mon->band & BAND_GN || + net_mon->band & BAND_AN || + net_mon->band & BAND_GAC || + net_mon->band & BAND_AAC) { + bw_offset = net_mon->chan_bandwidth; + if (bw_offset == CHANNEL_BW_40MHZ_ABOVE) { + chan_band->band_cfg.chan_2O_ffset = + NXPWIFI_SEC_CHAN_ABOVE; + chan_band->band_cfg.chan_width = + CHAN_BW_40MHZ; + } else if (bw_offset == CHANNEL_BW_40MHZ_BELOW) { + chan_band->band_cfg.chan_2O_ffset = + NXPWIFI_SEC_CHAN_BELOW; + chan_band->band_cfg.chan_width = + CHAN_BW_40MHZ; + } else if (bw_offset == CHANNEL_BW_80MHZ) { + sec_chan_offset = + nxpwifi_get_sec_chan_offset(net_mon->channel); + if (sec_chan_offset == NXPWIFI_SEC_CHAN_ABOVE) + chan_band->band_cfg.chan_2O_ffset = + NXPWIFI_SEC_CHAN_ABOVE; + else if (sec_chan_offset == NXPWIFI_SEC_CHAN_BELOW) + chan_band->band_cfg.chan_2O_ffset = + NXPWIFI_SEC_CHAN_BELOW; + chan_band->band_cfg.chan_width = CHAN_BW_80MHZ; + } + } + } + } + return 0; +} + +static int +nxpwifi_ret_sta_802_11_net_monitor(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11_net_monitor *cmd_net_mon = &resp->params.net_mon; + + nxpwifi_dbg(priv->adapter, CMD, + "cmd: NET_MONITOR_CMD: action: %d, enable: %d, flag: %d ch: %d band: %d bw: %d offset: %d\n", + le16_to_cpu(cmd_net_mon->action), + le16_to_cpu(cmd_net_mon->enable_net_mon), + le16_to_cpu(cmd_net_mon->filter_flag), + cmd_net_mon->monitor_chan.chan_band_param[0].chan_number, + cmd_net_mon->monitor_chan.chan_band_param[0].band_cfg.chan_band, + cmd_net_mon->monitor_chan.chan_band_param[0].band_cfg.chan_width, + cmd_net_mon->monitor_chan.chan_band_param[0].band_cfg.chan_2O_ffset); + priv->adapter->enable_net_mon = le16_to_cpu(cmd_net_mon->enable_net_mon); + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_scan_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_802_11_scan_ext(priv, cmd, data_buf); +} + +static int +nxpwifi_ret_sta_802_11_scan_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + + ret = nxpwifi_ret_802_11_scan_ext(priv, resp); + adapter->curr_cmd->wait_q_enabled = false; + + return ret; +} + +static int +nxpwifi_cmd_sta_coalesce_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_coalesce_cfg *coalesce_cfg = + &cmd->params.coalesce_cfg; + struct nxpwifi_ds_coalesce_cfg *cfg = + (struct nxpwifi_ds_coalesce_cfg *)data_buf; + struct coalesce_filt_field_param *param; + u16 cnt, idx, length; + struct coalesce_receive_filt_rule *rule; + + cmd->command = cpu_to_le16(HOST_CMD_COALESCE_CFG); + cmd->size = cpu_to_le16(S_DS_GEN); + + coalesce_cfg->action = cpu_to_le16(cmd_action); + coalesce_cfg->num_of_rules = cpu_to_le16(cfg->num_of_rules); + rule = (void *)coalesce_cfg->rule_data; + + for (cnt = 0; cnt < cfg->num_of_rules; cnt++) { + rule->header.type = cpu_to_le16(TLV_TYPE_COALESCE_RULE); + rule->max_coalescing_delay = + cpu_to_le16(cfg->rule[cnt].max_coalescing_delay); + rule->pkt_type = cfg->rule[cnt].pkt_type; + rule->num_of_fields = cfg->rule[cnt].num_of_fields; + + length = 0; + + param = rule->params; + for (idx = 0; idx < cfg->rule[cnt].num_of_fields; idx++) { + param->operation = cfg->rule[cnt].params[idx].operation; + param->operand_len = + cfg->rule[cnt].params[idx].operand_len; + param->offset = + cpu_to_le16(cfg->rule[cnt].params[idx].offset); + memcpy(param->operand_byte_stream, + cfg->rule[cnt].params[idx].operand_byte_stream, + param->operand_len); + + length += sizeof(struct coalesce_filt_field_param); + + param++; + } + + /* + * Total rule length is sizeof max_coalescing_delay(u16), + * num_of_fields(u8), pkt_type(u8) and total length of the all + * params + */ + rule->header.len = cpu_to_le16(length + sizeof(u16) + + sizeof(u8) + sizeof(u8)); + + /* Add the rule length to the command size */ + le16_unaligned_add_cpu(&cmd->size, + le16_to_cpu(rule->header.len) + + sizeof(struct nxpwifi_ie_types_header)); + + rule = (void *)((u8 *)rule->params + length); + } + + /* Add sizeof action, num_of_rules to total command length */ + le16_unaligned_add_cpu(&cmd->size, sizeof(u16) + sizeof(u16)); + + return 0; +} + +static int +nxpwifi_cmd_sta_mgmt_frame_reg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(cmd_no); + cmd->params.reg_mask.action = cpu_to_le16(cmd_action); + cmd->params.reg_mask.mask = + cpu_to_le32(get_unaligned((u32 *)data_buf)); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_mgmt_frame_reg) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_cmd_sta_remain_on_chan(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(cmd_no); + memcpy(&cmd->params, data_buf, + sizeof(struct host_cmd_ds_remain_on_chan)); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_remain_on_chan) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_ret_sta_remain_on_chan(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_remain_on_chan *resp_cfg = &resp->params.roc_cfg; + struct host_cmd_ds_remain_on_chan *roc_cfg = + (struct host_cmd_ds_remain_on_chan *)data_buf; + + if (roc_cfg) + memcpy(roc_cfg, resp_cfg, sizeof(*roc_cfg)); + + return 0; +} + +static int +nxpwifi_cmd_sta_gtk_rekey_offload(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_gtk_rekey_params *rekey = &cmd->params.rekey; + struct cfg80211_gtk_rekey_data *data = + (struct cfg80211_gtk_rekey_data *)data_buf; + u64 rekey_ctr; + + cmd->command = cpu_to_le16(HOST_CMD_GTK_REKEY_OFFLOAD_CFG); + cmd->size = cpu_to_le16(sizeof(*rekey) + S_DS_GEN); + + rekey->action = cpu_to_le16(cmd_action); + if (cmd_action == HOST_ACT_GEN_SET) { + memcpy(rekey->kek, data->kek, NL80211_KEK_LEN); + memcpy(rekey->kck, data->kck, NL80211_KCK_LEN); + rekey_ctr = be64_to_cpup((__be64 *)data->replay_ctr); + rekey->replay_ctr_low = cpu_to_le32((u32)rekey_ctr); + rekey->replay_ctr_high = + cpu_to_le32((u32)((u64)rekey_ctr >> 32)); + } + + return 0; +} + +static int +nxpwifi_cmd_sta_11ac_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_11ac_cfg(priv, cmd, cmd_action, data_buf); +} + +static int +nxpwifi_cmd_sta_hs_wakeup_reason(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(HOST_CMD_HS_WAKEUP_REASON); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_wakeup_reason) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_ret_sta_hs_wakeup_reason(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_wakeup_reason *wakeup_reason = + (struct host_cmd_ds_wakeup_reason *)data_buf; + wakeup_reason->wakeup_reason = + resp->params.hs_wakeup_reason.wakeup_reason; + + return 0; +} + +static int +nxpwifi_cmd_sta_mc_policy(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_multi_chan_policy *mc_pol = &cmd->params.mc_policy; + const u16 *drcs_info = data_buf; + + mc_pol->action = cpu_to_le16(cmd_action); + mc_pol->policy = cpu_to_le16(*drcs_info); + cmd->command = cpu_to_le16(HOST_CMD_MC_POLICY); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_multi_chan_policy) + + S_DS_GEN); + return 0; +} + +static int +nxpwifi_cmd_sta_sdio_rx_aggr_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_sdio_sp_rx_aggr_cfg *cfg = + &cmd->params.sdio_rx_aggr_cfg; + + cmd->command = cpu_to_le16(HOST_CMD_SDIO_SP_RX_AGGR_CFG); + cmd->size = + cpu_to_le16(sizeof(struct host_cmd_sdio_sp_rx_aggr_cfg) + + S_DS_GEN); + cfg->action = cmd_action; + if (cmd_action == HOST_ACT_GEN_SET) + cfg->enable = *(u8 *)data_buf; + + return 0; +} + +static int +nxpwifi_ret_sta_sdio_rx_aggr_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_sdio_sp_rx_aggr_cfg *cfg = + &resp->params.sdio_rx_aggr_cfg; + + adapter->sdio_rx_aggr_enable = cfg->enable; + adapter->sdio_rx_block_size = le16_to_cpu(cfg->block_size); + + return 0; +} + +static int +nxpwifi_cmd_sta_get_chan_info(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_sta_configure *sta_cfg_cmd = &cmd->params.sta_cfg; + struct host_cmd_tlv_channel_band *tlv_band_channel = + (struct host_cmd_tlv_channel_band *)sta_cfg_cmd->tlv_buffer; + + cmd->command = cpu_to_le16(HOST_CMD_STA_CONFIGURE); + cmd->size = cpu_to_le16(sizeof(*sta_cfg_cmd) + + sizeof(*tlv_band_channel) + S_DS_GEN); + sta_cfg_cmd->action = cpu_to_le16(cmd_action); + memset(tlv_band_channel, 0, sizeof(*tlv_band_channel)); + tlv_band_channel->header.type = cpu_to_le16(TLV_TYPE_CHANNELBANDLIST); + tlv_band_channel->header.len = cpu_to_le16(sizeof(*tlv_band_channel) - + sizeof(struct nxpwifi_ie_types_header)); + + return 0; +} + +static int +nxpwifi_ret_sta_get_chan_info(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_sta_configure *sta_cfg_cmd = &resp->params.sta_cfg; + struct nxpwifi_channel_band *channel_band = + (struct nxpwifi_channel_band *)data_buf; + struct host_cmd_tlv_channel_band *tlv_band_channel; + + tlv_band_channel = + (struct host_cmd_tlv_channel_band *)sta_cfg_cmd->tlv_buffer; + memcpy(&channel_band->band_config, &tlv_band_channel->band_config, + sizeof(struct nxpwifi_band_config)); + channel_band->channel = tlv_band_channel->channel; + + return 0; +} + +static int +nxpwifi_cmd_sta_chan_region_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_chan_region_cfg *reg = &cmd->params.reg_cfg; + + cmd->command = cpu_to_le16(HOST_CMD_CHAN_REGION_CFG); + cmd->size = cpu_to_le16(sizeof(*reg) + S_DS_GEN); + + if (cmd_action == HOST_ACT_GEN_GET) + reg->action = cpu_to_le16(cmd_action); + + return 0; +} + +static struct ieee80211_regdomain * +nxpwifi_create_custom_regdomain(struct nxpwifi_private *priv, + u8 *buf, u16 buf_len) +{ + u16 num_chan = buf_len / 2; + struct ieee80211_regdomain *regd; + struct ieee80211_reg_rule *rule; + bool new_rule; + int idx, freq, prev_freq = 0; + u32 bw, prev_bw = 0; + u8 chflags, prev_chflags = 0, valid_rules = 0; + + if (WARN_ON_ONCE(num_chan > NL80211_MAX_SUPP_REG_RULES)) + return ERR_PTR(-EINVAL); + + regd = kzalloc_flex(*regd, reg_rules, num_chan, GFP_KERNEL); + if (!regd) + return ERR_PTR(-ENOMEM); + + for (idx = 0; idx < num_chan; idx++) { + u8 chan; + enum nl80211_band band; + + chan = *buf++; + if (!chan) { + kfree(regd); + return NULL; + } + chflags = *buf++; + band = (chan <= 14) ? NL80211_BAND_2GHZ : NL80211_BAND_5GHZ; + freq = ieee80211_channel_to_frequency(chan, band); + new_rule = false; + + if (chflags & NXPWIFI_CHANNEL_DISABLED) + continue; + + if (band == NL80211_BAND_5GHZ) { + if (!(chflags & NXPWIFI_CHANNEL_NOHT80)) + bw = MHZ_TO_KHZ(80); + else if (!(chflags & NXPWIFI_CHANNEL_NOHT40)) + bw = MHZ_TO_KHZ(40); + else + bw = MHZ_TO_KHZ(20); + } else { + if (!(chflags & NXPWIFI_CHANNEL_NOHT40)) + bw = MHZ_TO_KHZ(40); + else + bw = MHZ_TO_KHZ(20); + } + + if (idx == 0 || prev_chflags != chflags || prev_bw != bw || + freq - prev_freq > 20) { + valid_rules++; + new_rule = true; + } + + rule = ®d->reg_rules[valid_rules - 1]; + + rule->freq_range.end_freq_khz = MHZ_TO_KHZ(freq + 10); + + prev_chflags = chflags; + prev_freq = freq; + prev_bw = bw; + + if (!new_rule) + continue; + + rule->freq_range.start_freq_khz = MHZ_TO_KHZ(freq - 10); + rule->power_rule.max_eirp = DBM_TO_MBM(19); + + if (chflags & NXPWIFI_CHANNEL_PASSIVE) + rule->flags = NL80211_RRF_NO_IR; + + if (chflags & NXPWIFI_CHANNEL_DFS) + rule->flags = NL80211_RRF_DFS; + + rule->freq_range.max_bandwidth_khz = bw; + } + + regd->n_reg_rules = valid_rules; + regd->alpha2[0] = '9'; + regd->alpha2[1] = '9'; + + return regd; +} + +static int +nxpwifi_ret_sta_chan_region_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_chan_region_cfg *reg = &resp->params.reg_cfg; + u16 action = le16_to_cpu(reg->action); + u16 tlv, tlv_buf_len, tlv_buf_left; + struct nxpwifi_ie_types_header *head; + struct ieee80211_regdomain *regd; + u8 *tlv_buf; + + if (action != HOST_ACT_GEN_GET) + return 0; + + tlv_buf = (u8 *)reg + sizeof(*reg); + tlv_buf_left = le16_to_cpu(resp->size) - S_DS_GEN - sizeof(*reg); + + while (tlv_buf_left >= sizeof(*head)) { + head = (struct nxpwifi_ie_types_header *)tlv_buf; + tlv = le16_to_cpu(head->type); + tlv_buf_len = le16_to_cpu(head->len); + + if (tlv_buf_left < (sizeof(*head) + tlv_buf_len)) + break; + + switch (tlv) { + case TLV_TYPE_CHAN_ATTR_CFG: + nxpwifi_dbg_dump(priv->adapter, CMD_D, "CHAN:", + (u8 *)head + sizeof(*head), + tlv_buf_len); + regd = nxpwifi_create_custom_regdomain(priv, (u8 *)head + + sizeof(*head), + tlv_buf_len); + if (!IS_ERR(regd)) + priv->adapter->regd = regd; + break; + } + + tlv_buf += (sizeof(*head) + tlv_buf_len); + tlv_buf_left -= (sizeof(*head) + tlv_buf_len); + } + + return 0; +} + +static int +nxpwifi_cmd_sta_pkt_aggr_ctrl(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(cmd_no); + cmd->params.pkt_aggr_ctrl.action = cpu_to_le16(cmd_action); + cmd->params.pkt_aggr_ctrl.enable = cpu_to_le16(*(u16 *)data_buf); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_pkt_aggr_ctrl) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_ret_sta_pkt_aggr_ctrl(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_pkt_aggr_ctrl *pkt_aggr_ctrl = + &resp->params.pkt_aggr_ctrl; + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->bus_aggr.enable = le16_to_cpu(pkt_aggr_ctrl->enable); + if (adapter->bus_aggr.enable) + adapter->intf_hdr_len = INTF_HEADER_LEN; + adapter->bus_aggr.mode = NXPWIFI_BUS_AGGR_MODE_LEN_V2; + adapter->bus_aggr.tx_aggr_max_size = + le16_to_cpu(pkt_aggr_ctrl->tx_aggr_max_size); + adapter->bus_aggr.tx_aggr_max_num = + le16_to_cpu(pkt_aggr_ctrl->tx_aggr_max_num); + adapter->bus_aggr.tx_aggr_align = + le16_to_cpu(pkt_aggr_ctrl->tx_aggr_align); + + return 0; +} + +static int +nxpwifi_cmd_sta_11ax_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_11ax_cfg(priv, cmd, cmd_action, data_buf); +} + +static int +nxpwifi_ret_sta_11ax_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_11ax_cfg(priv, resp, data_buf); +} + +static int +nxpwifi_cmd_sta_11ax_cmd(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_11ax_cmd(priv, cmd, cmd_action, data_buf); +} + +static int +nxpwifi_ret_sta_11ax_cmd(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_11ax_cmd(priv, resp, data_buf); +} + +static int +nxpwifi_cmd_sta_twt_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_twt_cfg(priv, cmd, cmd_action, data_buf); +} + +static int +nxpwifi_ret_sta_twt_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_twt_cfg(priv, resp, data_buf); +} + +static const struct nxpwifi_cmd_entry cmd_table_sta[] = { + {.cmd_no = HOST_CMD_GET_HW_SPEC, + .prepare_cmd = nxpwifi_cmd_sta_get_hw_spec, + .cmd_resp = nxpwifi_ret_sta_get_hw_spec}, + {.cmd_no = HOST_CMD_802_11_SCAN, + .prepare_cmd = nxpwifi_cmd_sta_802_11_scan, + .cmd_resp = nxpwifi_ret_sta_802_11_scan}, + {.cmd_no = HOST_CMD_802_11_GET_LOG, + .prepare_cmd = nxpwifi_cmd_sta_802_11_get_log, + .cmd_resp = nxpwifi_ret_sta_802_11_get_log}, + {.cmd_no = HOST_CMD_MAC_MULTICAST_ADR, + .prepare_cmd = nxpwifi_cmd_sta_mac_multicast_adr, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_802_11_ASSOCIATE, + .prepare_cmd = nxpwifi_cmd_sta_802_11_associate, + .cmd_resp = nxpwifi_ret_sta_802_11_associate}, + {.cmd_no = HOST_CMD_802_11_SNMP_MIB, + .prepare_cmd = nxpwifi_cmd_sta_802_11_snmp_mib, + .cmd_resp = nxpwifi_ret_sta_802_11_snmp_mib}, + {.cmd_no = HOST_CMD_MAC_REG_ACCESS, + .prepare_cmd = nxpwifi_cmd_sta_reg_access, + .cmd_resp = nxpwifi_ret_sta_reg_access}, + {.cmd_no = HOST_CMD_BBP_REG_ACCESS, + .prepare_cmd = nxpwifi_cmd_sta_reg_access, + .cmd_resp = nxpwifi_ret_sta_reg_access}, + {.cmd_no = HOST_CMD_RF_REG_ACCESS, + .prepare_cmd = nxpwifi_cmd_sta_reg_access, + .cmd_resp = nxpwifi_ret_sta_reg_access}, + {.cmd_no = HOST_CMD_RF_TX_PWR, + .prepare_cmd = nxpwifi_cmd_sta_rf_tx_pwr, + .cmd_resp = nxpwifi_ret_sta_rf_tx_pwr}, + {.cmd_no = HOST_CMD_RF_ANTENNA, + .prepare_cmd = nxpwifi_cmd_sta_rf_antenna, + .cmd_resp = nxpwifi_ret_sta_rf_antenna}, + {.cmd_no = HOST_CMD_802_11_DEAUTHENTICATE, + .prepare_cmd = nxpwifi_cmd_sta_802_11_deauthenticate, + .cmd_resp = nxpwifi_ret_sta_802_11_deauthenticate}, + {.cmd_no = HOST_CMD_MAC_CONTROL, + .prepare_cmd = nxpwifi_cmd_sta_mac_control, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_802_11_MAC_ADDRESS, + .prepare_cmd = nxpwifi_cmd_sta_802_11_mac_address, + .cmd_resp = nxpwifi_ret_sta_802_11_mac_address}, + {.cmd_no = HOST_CMD_802_11_EEPROM_ACCESS, + .prepare_cmd = nxpwifi_cmd_sta_reg_access, + .cmd_resp = nxpwifi_ret_sta_reg_access}, + {.cmd_no = HOST_CMD_802_11D_DOMAIN_INFO, + .prepare_cmd = nxpwifi_cmd_sta_802_11d_domain_info, + .cmd_resp = nxpwifi_ret_sta_802_11d_domain_info}, + {.cmd_no = HOST_CMD_802_11_KEY_MATERIAL, + .prepare_cmd = nxpwifi_cmd_sta_802_11_key_material, + .cmd_resp = nxpwifi_ret_sta_802_11_key_material}, + {.cmd_no = HOST_CMD_802_11_BG_SCAN_CONFIG, + .prepare_cmd = nxpwifi_cmd_sta_802_11_bg_scan_config, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_802_11_BG_SCAN_QUERY, + .prepare_cmd = nxpwifi_cmd_sta_802_11_bg_scan_query, + .cmd_resp = nxpwifi_ret_sta_802_11_bg_scan_query}, + {.cmd_no = HOST_CMD_WMM_GET_STATUS, + .prepare_cmd = nxpwifi_cmd_sta_wmm_get_status, + .cmd_resp = nxpwifi_ret_sta_wmm_get_status}, + {.cmd_no = HOST_CMD_802_11_SUBSCRIBE_EVENT, + .prepare_cmd = nxpwifi_cmd_sta_802_11_subsc_evt, + .cmd_resp = nxpwifi_ret_sta_subsc_evt}, + {.cmd_no = HOST_CMD_802_11_TX_RATE_QUERY, + .prepare_cmd = nxpwifi_cmd_sta_802_11_tx_rate_query, + .cmd_resp = nxpwifi_ret_sta_802_11_tx_rate_query}, + {.cmd_no = HOST_CMD_MEM_ACCESS, + .prepare_cmd = nxpwifi_cmd_sta_mem_access, + .cmd_resp = nxpwifi_ret_sta_mem_access}, + {.cmd_no = HOST_CMD_CFG_DATA, + .prepare_cmd = nxpwifi_cmd_sta_cfg_data, + .cmd_resp = nxpwifi_ret_sta_cfg_data}, + {.cmd_no = HOST_CMD_VERSION_EXT, + .prepare_cmd = nxpwifi_cmd_sta_ver_ext, + .cmd_resp = nxpwifi_ret_sta_ver_ext}, + {.cmd_no = HOST_CMD_MEF_CFG, + .prepare_cmd = nxpwifi_cmd_sta_mef_cfg, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_RSSI_INFO, + .prepare_cmd = nxpwifi_cmd_sta_802_11_rssi_info, + .cmd_resp = nxpwifi_ret_sta_802_11_rssi_info}, + {.cmd_no = HOST_CMD_FUNC_INIT, + .prepare_cmd = nxpwifi_cmd_sta_func_init, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_FUNC_SHUTDOWN, + .prepare_cmd = nxpwifi_cmd_sta_func_shutdown, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_PMIC_REG_ACCESS, + .prepare_cmd = nxpwifi_cmd_sta_reg_access, + .cmd_resp = nxpwifi_ret_sta_reg_access}, + {.cmd_no = HOST_CMD_11N_CFG, + .prepare_cmd = nxpwifi_cmd_sta_11n_cfg, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_11N_ADDBA_REQ, + .prepare_cmd = nxpwifi_cmd_sta_11n_addba_req, + .cmd_resp = nxpwifi_ret_sta_11n_addba_req}, + {.cmd_no = HOST_CMD_11N_ADDBA_RSP, + .prepare_cmd = nxpwifi_cmd_sta_11n_addba_rsp, + .cmd_resp = nxpwifi_ret_sta_11n_addba_rsp}, + {.cmd_no = HOST_CMD_11N_DELBA, + .prepare_cmd = nxpwifi_cmd_sta_11n_delba, + .cmd_resp = nxpwifi_ret_sta_11n_delba}, + {.cmd_no = HOST_CMD_TXPWR_CFG, + .prepare_cmd = nxpwifi_cmd_sta_tx_power_cfg, + .cmd_resp = nxpwifi_ret_sta_tx_power_cfg}, + {.cmd_no = HOST_CMD_TX_RATE_CFG, + .prepare_cmd = nxpwifi_cmd_sta_tx_rate_cfg, + .cmd_resp = nxpwifi_ret_sta_tx_rate_cfg}, + {.cmd_no = HOST_CMD_RECONFIGURE_TX_BUFF, + .prepare_cmd = nxpwifi_cmd_sta_reconfigure_rx_buff, + .cmd_resp = nxpwifi_ret_sta_reconfigure_rx_buff}, + {.cmd_no = HOST_CMD_CHAN_REPORT_REQUEST, + .prepare_cmd = nxpwifi_cmd_sta_chan_report_request, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_AMSDU_AGGR_CTRL, + .prepare_cmd = nxpwifi_cmd_sta_amsdu_aggr_ctrl, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_ROBUST_COEX, + .prepare_cmd = nxpwifi_cmd_sta_robust_coex, + .cmd_resp = nxpwifi_ret_sta_robust_coex}, + {.cmd_no = HOST_CMD_802_11_PS_MODE_ENH, + .prepare_cmd = nxpwifi_cmd_sta_enh_power_mode, + .cmd_resp = nxpwifi_ret_sta_enh_power_mode}, + {.cmd_no = HOST_CMD_802_11_HS_CFG_ENH, + .prepare_cmd = nxpwifi_cmd_sta_802_11_hs_cfg, + .cmd_resp = nxpwifi_ret_sta_802_11_hs_cfg}, + {.cmd_no = HOST_CMD_CAU_REG_ACCESS, + .prepare_cmd = nxpwifi_cmd_sta_reg_access, + .cmd_resp = nxpwifi_ret_sta_reg_access}, + {.cmd_no = HOST_CMD_SET_BSS_MODE, + .prepare_cmd = nxpwifi_cmd_sta_set_bss_mode, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_802_11_NET_MONITOR, + .prepare_cmd = nxpwifi_cmd_sta_802_11_net_monitor, + .cmd_resp = nxpwifi_ret_sta_802_11_net_monitor}, + {.cmd_no = HOST_CMD_802_11_SCAN_EXT, + .prepare_cmd = nxpwifi_cmd_sta_802_11_scan_ext, + .cmd_resp = nxpwifi_ret_sta_802_11_scan_ext}, + {.cmd_no = HOST_CMD_COALESCE_CFG, + .prepare_cmd = nxpwifi_cmd_sta_coalesce_cfg, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_MGMT_FRAME_REG, + .prepare_cmd = nxpwifi_cmd_sta_mgmt_frame_reg, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_REMAIN_ON_CHAN, + .prepare_cmd = nxpwifi_cmd_sta_remain_on_chan, + .cmd_resp = nxpwifi_ret_sta_remain_on_chan}, + {.cmd_no = HOST_CMD_GTK_REKEY_OFFLOAD_CFG, + .prepare_cmd = nxpwifi_cmd_sta_gtk_rekey_offload, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_11AC_CFG, + .prepare_cmd = nxpwifi_cmd_sta_11ac_cfg, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_HS_WAKEUP_REASON, + .prepare_cmd = nxpwifi_cmd_sta_hs_wakeup_reason, + .cmd_resp = nxpwifi_ret_sta_hs_wakeup_reason}, + {.cmd_no = HOST_CMD_MC_POLICY, + .prepare_cmd = nxpwifi_cmd_sta_mc_policy, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_FW_DUMP_EVENT, + .prepare_cmd = nxpwifi_cmd_fill_head_only, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_SDIO_SP_RX_AGGR_CFG, + .prepare_cmd = nxpwifi_cmd_sta_sdio_rx_aggr_cfg, + .cmd_resp = nxpwifi_ret_sta_sdio_rx_aggr_cfg}, + {.cmd_no = HOST_CMD_STA_CONFIGURE, + .prepare_cmd = nxpwifi_cmd_sta_get_chan_info, + .cmd_resp = nxpwifi_ret_sta_get_chan_info}, + {.cmd_no = HOST_CMD_CHAN_REGION_CFG, + .prepare_cmd = nxpwifi_cmd_sta_chan_region_cfg, + .cmd_resp = nxpwifi_ret_sta_chan_region_cfg}, + {.cmd_no = HOST_CMD_PACKET_AGGR_CTRL, + .prepare_cmd = nxpwifi_cmd_sta_pkt_aggr_ctrl, + .cmd_resp = nxpwifi_ret_sta_pkt_aggr_ctrl}, + {.cmd_no = HOST_CMD_11AX_CFG, + .prepare_cmd = nxpwifi_cmd_sta_11ax_cfg, + .cmd_resp = nxpwifi_ret_sta_11ax_cfg}, + {.cmd_no = HOST_CMD_11AX_CMD, + .prepare_cmd = nxpwifi_cmd_sta_11ax_cmd, + .cmd_resp = nxpwifi_ret_sta_11ax_cmd}, + {.cmd_no = HOST_CMD_TWT_CFG, + .prepare_cmd = nxpwifi_cmd_sta_twt_cfg, + .cmd_resp = nxpwifi_ret_sta_twt_cfg}, +}; + +/* + * Prepare a command before sending it to firmware by invoking the + * appropriate handler based on the command ID. + */ +int nxpwifi_sta_prepare_cmd(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node, + u16 cmd_action, u32 cmd_oid) + +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u16 cmd_no = cmd_node->cmd_no; + struct host_cmd_ds_command *cmd = + (struct host_cmd_ds_command *)cmd_node->skb->data; + void *data_buf = cmd_node->data_buf; + int i, ret = -EINVAL; + + for (i = 0; i < ARRAY_SIZE(cmd_table_sta); i++) { + if (cmd_no == cmd_table_sta[i].cmd_no) { + if (cmd_table_sta[i].prepare_cmd) + ret = cmd_table_sta[i].prepare_cmd(priv, cmd, + cmd_no, + data_buf, + cmd_action, + cmd_oid); + cmd_node->cmd_resp = cmd_table_sta[i].cmd_resp; + break; + } + } + + if (i == ARRAY_SIZE(cmd_table_sta)) + nxpwifi_dbg(adapter, ERROR, + "%s: unknown command: %#x\n", + __func__, cmd_no); + else + nxpwifi_dbg(adapter, CMD, + "%s: command: %#x\n", + __func__, cmd_no); + + return ret; +} + +/* + * Initialize firmware after download or during virtual interface + * reinitialization to bring the device to a working state. + */ +int nxpwifi_sta_init_cmd(struct nxpwifi_private *priv, u8 first_sta, bool init) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + struct nxpwifi_ds_11n_amsdu_aggr_ctrl amsdu_aggr_ctrl; + struct nxpwifi_ds_auto_ds auto_ds; + enum state_11d_t state_11d; + struct nxpwifi_ds_11n_tx_cfg tx_cfg; + u8 sdio_sp_rx_aggr_enable; + + if (first_sta) { + ret = nxpwifi_send_cmd(priv, HOST_CMD_FUNC_INIT, + HOST_ACT_GEN_SET, 0, NULL, true); + if (ret) + return ret; + + if (adapter->cal_data) + nxpwifi_send_cmd(priv, HOST_CMD_CFG_DATA, + HOST_ACT_GEN_SET, 0, NULL, true); + + /* Read MAC address from HW */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_GET_HW_SPEC, + HOST_ACT_GEN_GET, 0, NULL, true); + if (ret) + return ret; + + /* * Set SDIO Single Port RX Aggr Info */ + if (priv->adapter->iface_type == NXPWIFI_SDIO && + ISSUPP_SDIO_SPA_ENABLED(priv->adapter->fw_cap_info) && + !priv->adapter->host_disable_sdio_rx_aggr) { + sdio_sp_rx_aggr_enable = true; + ret = nxpwifi_send_cmd(priv, + HOST_CMD_SDIO_SP_RX_AGGR_CFG, + HOST_ACT_GEN_SET, 0, + &sdio_sp_rx_aggr_enable, + true); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "error while enabling SP aggregation..disable it"); + adapter->sdio_rx_aggr_enable = false; + } + } + + /* Reconfigure tx buf size */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_RECONFIGURE_TX_BUFF, + HOST_ACT_GEN_SET, 0, + &priv->adapter->tx_buf_size, true); + if (ret) + return ret; + + if (priv->bss_type != NXPWIFI_BSS_TYPE_UAP) { + /* Enable IEEE PS by default */ + priv->adapter->ps_mode = NXPWIFI_802_11_POWER_MODE_PSP; + ret = nxpwifi_send_cmd(priv, + HOST_CMD_802_11_PS_MODE_ENH, + EN_AUTO_PS, BITMAP_STA_PS, NULL, + true); + if (ret) + return ret; + } + + nxpwifi_send_cmd(priv, HOST_CMD_CHAN_REGION_CFG, + HOST_ACT_GEN_GET, 0, NULL, true); + } + + /* get tx rate */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_TX_RATE_CFG, + HOST_ACT_GEN_GET, 0, NULL, true); + if (ret) + return ret; + priv->data_rate = 0; + + /* get tx power */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_RF_TX_PWR, + HOST_ACT_GEN_GET, 0, NULL, true); + if (ret) + return ret; + + memset(&amsdu_aggr_ctrl, 0, sizeof(amsdu_aggr_ctrl)); + amsdu_aggr_ctrl.enable = true; + /* Send request to firmware */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_AMSDU_AGGR_CTRL, + HOST_ACT_GEN_SET, 0, + &amsdu_aggr_ctrl, true); + if (ret) + return ret; + /* MAC Control must be the last command in init_fw */ + /* set MAC Control */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_MAC_CONTROL, + HOST_ACT_GEN_SET, 0, + &priv->curr_pkt_filter, true); + if (ret) + return ret; + + if (!disable_auto_ds && first_sta && + priv->bss_type != NXPWIFI_BSS_TYPE_UAP) { + /* Enable auto deep sleep */ + auto_ds.auto_ds = DEEP_SLEEP_ON; + auto_ds.idle_time = DEEP_SLEEP_IDLE_TIME; + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_PS_MODE_ENH, + EN_AUTO_PS, BITMAP_AUTO_DS, + &auto_ds, true); + if (ret) + return ret; + } + + if (priv->bss_type != NXPWIFI_BSS_TYPE_UAP) { + /* Send cmd to FW to enable/disable 11D function */ + state_11d = ENABLE_11D; + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_SNMP_MIB, + HOST_ACT_GEN_SET, DOT11D_I, + &state_11d, true); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "11D: failed to enable 11D\n"); + } + + /* + * Send cmd to FW to configure 11n specific configuration + * (Short GI, Channel BW, Green field support etc.) for transmit + */ + tx_cfg.tx_htcap = NXPWIFI_FW_DEF_HTTXCFG; + ret = nxpwifi_send_cmd(priv, HOST_CMD_11N_CFG, + HOST_ACT_GEN_SET, 0, &tx_cfg, true); + + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/sta_event.c b/drivers/net/wireless/nxp/nxpwifi/sta_event.c new file mode 100644 index 000000000000..355064b1d8f7 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/sta_event.c @@ -0,0 +1,862 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: station event handling + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" + +static int +nxpwifi_sta_event_link_lost(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->dbg.num_event_link_lost++; + if (priv->media_connected) { + adapter->priv_link_lost = priv; + adapter->host_mlme_link_lost = true; + nxpwifi_queue_wiphy_work(adapter, + &adapter->host_mlme_work); + } + + return 0; +} + +static int +nxpwifi_sta_event_link_sensed(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + netif_carrier_on(priv->netdev); + nxpwifi_wake_up_net_dev_queue(priv->netdev, adapter); + + return 0; +} + +static int +nxpwifi_sta_event_deauthenticated(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (priv->wps.session_enable) { + nxpwifi_dbg(adapter, INFO, + "info: receive deauth event in wps session\n"); + } else { + adapter->dbg.num_event_deauth++; + if (priv->media_connected) { + priv->last_deauth_reason = + get_unaligned_le16(priv->adapter->event_body); + nxpwifi_queue_wiphy_work(priv->adapter, + &priv->reset_conn_state_work); + } + } + + return 0; +} + +static int +nxpwifi_sta_event_disassociated(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (priv->wps.session_enable) { + nxpwifi_dbg(adapter, INFO, + "info: receive disassoc event in wps session\n"); + } else { + adapter->dbg.num_event_disassoc++; + if (priv->media_connected) { + priv->last_deauth_reason = + get_unaligned_le16(priv->adapter->event_body); + nxpwifi_queue_wiphy_work(priv->adapter, + &priv->reset_conn_state_work); + } + } + + return 0; +} + +static int +nxpwifi_sta_event_ps_awake(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (!adapter->pps_uapsd_mode && + priv->port_open && + priv->media_connected && adapter->sleep_period.period) { + adapter->pps_uapsd_mode = true; + nxpwifi_dbg(adapter, EVENT, + "event: PPS/UAPSD mode activated\n"); + } + adapter->tx_lock_flag = false; + if (adapter->pps_uapsd_mode && adapter->gen_null_pkt) { + if (nxpwifi_check_last_packet_indication(priv)) { + if (adapter->data_sent) { + adapter->ps_state = PS_STATE_AWAKE; + adapter->pm_wakeup_card_req = false; + adapter->pm_wakeup_fw_try = false; + timer_delete(&adapter->wakeup_timer); + } else { + if (!nxpwifi_send_null_packet + (priv, + NXPWIFI_TxPD_POWER_MGMT_NULL_PACKET | + NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET)) + adapter->ps_state = PS_STATE_SLEEP; + } + + return 0; + } + } + + adapter->ps_state = PS_STATE_AWAKE; + adapter->pm_wakeup_card_req = false; + adapter->pm_wakeup_fw_try = false; + timer_delete(&adapter->wakeup_timer); + + return 0; +} + +static int +nxpwifi_sta_event_ps_sleep(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->ps_state = PS_STATE_PRE_SLEEP; + nxpwifi_check_ps_cond(adapter); + + return 0; +} + +static int +nxpwifi_sta_event_mic_err_multicast(struct nxpwifi_private *priv) +{ + cfg80211_michael_mic_failure(priv->netdev, priv->cfg_bssid, + NL80211_KEYTYPE_GROUP, + -1, NULL, GFP_KERNEL); + + return 0; +} + +static int +nxpwifi_sta_event_mic_err_unicast(struct nxpwifi_private *priv) +{ + cfg80211_michael_mic_failure(priv->netdev, priv->cfg_bssid, + NL80211_KEYTYPE_PAIRWISE, + -1, NULL, GFP_KERNEL); + + return 0; +} + +static int +nxpwifi_sta_event_deep_sleep_awake(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->if_ops.wakeup_complete(adapter); + if (adapter->is_deep_sleep) + adapter->is_deep_sleep = false; + + return 0; +} + +static int +nxpwifi_sta_event_wmm_status_change(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_WMM_GET_STATUS, + 0, 0, NULL, false); +} + +static int +nxpwifi_sta_event_bs_scan_report(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_BG_SCAN_QUERY, + HOST_ACT_GEN_GET, 0, NULL, false); +} + +static int +nxpwifi_sta_event_rssi_low(struct nxpwifi_private *priv) +{ + cfg80211_cqm_rssi_notify(priv->netdev, + NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW, + 0, GFP_KERNEL); + priv->subsc_evt_rssi_state = RSSI_LOW_RECVD; + + return nxpwifi_send_cmd(priv, HOST_CMD_RSSI_INFO, + HOST_ACT_GEN_GET, 0, NULL, false); +} + +static int +nxpwifi_sta_event_rssi_high(struct nxpwifi_private *priv) +{ + cfg80211_cqm_rssi_notify(priv->netdev, + NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH, + 0, GFP_KERNEL); + priv->subsc_evt_rssi_state = RSSI_HIGH_RECVD; + + return nxpwifi_send_cmd(priv, HOST_CMD_RSSI_INFO, + HOST_ACT_GEN_GET, 0, NULL, false); +} + +static int +nxpwifi_sta_event_port_release(struct nxpwifi_private *priv) +{ + priv->port_open = true; + + return 0; +} + +static int +nxpwifi_sta_event_addba(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + return nxpwifi_send_cmd(priv, HOST_CMD_11N_ADDBA_RSP, + HOST_ACT_GEN_SET, 0, + adapter->event_body, false); +} + +static int +nxpwifi_sta_event_delba(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_11n_delete_ba_stream(priv, adapter->event_body); + + return 0; +} + +static int +nxpwifi_sta_event_bs_stream_timeout(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_11n_batimeout *event = + (struct host_cmd_ds_11n_batimeout *)adapter->event_body; + + nxpwifi_11n_ba_stream_timeout(priv, event); + + return 0; +} + +static int +nxpwifi_sta_event_amsdu_aggr_ctrl(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u16 ctrl; + + ctrl = get_unaligned_le16(adapter->event_body); + adapter->tx_buf_size = min_t(u16, adapter->curr_tx_buf_size, ctrl); + + return 0; +} + +static int +nxpwifi_sta_event_hs_act_req(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_HS_CFG_ENH, + 0, 0, NULL, false); +} + +static int +nxpwifi_sta_event_channel_switch_ann(struct nxpwifi_private *priv) +{ + struct nxpwifi_bssdescriptor *bss_desc; + + bss_desc = &priv->curr_bss_params.bss_descriptor; + priv->csa_expire_time = jiffies + msecs_to_jiffies(DFS_CHAN_MOVE_TIME); + priv->csa_chan = bss_desc->channel; + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_DEAUTHENTICATE, + HOST_ACT_GEN_SET, 0, + bss_desc->mac_address, false); +} + +static int +nxpwifi_sta_event_radar_detected(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + return nxpwifi_11h_handle_radar_detected(priv, adapter->event_skb); +} + +static int +nxpwifi_sta_event_channel_report_rdy(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + return nxpwifi_11h_handle_chanrpt_ready(priv, adapter->event_skb); +} + +static int +nxpwifi_sta_event_tx_data_pause(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_process_tx_pause_event(priv, adapter->event_skb); + + return 0; +} + +static int +nxpwifi_sta_event_ext_scan_report(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + void *buf = adapter->event_skb->data; + int ret = 0; + + /* + * We intend to skip this event during suspend, but handle + * it in interface disabled case + */ + if (adapter->ext_scan && (!priv->scan_aborting || + !netif_running(priv->netdev))) + ret = nxpwifi_handle_event_ext_scan_report(priv, buf); + + return ret; +} + +static int +nxpwifi_sta_event_rxba_sync(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_11n_rxba_sync_event(priv, adapter->event_body, + adapter->event_skb->len - + sizeof(adapter->event_cause)); + + return 0; +} + +static int +nxpwifi_sta_event_remain_on_chan_expired(struct nxpwifi_private *priv) +{ + if (priv->auth_flag & HOST_MLME_AUTH_PENDING) { + priv->auth_flag = 0; + priv->auth_alg = WLAN_AUTH_NONE; + } else { + cfg80211_remain_on_channel_expired(&priv->wdev, + priv->roc_cfg.cookie, + &priv->roc_cfg.chan, + GFP_ATOMIC); + } + + memset(&priv->roc_cfg, 0x00, sizeof(struct nxpwifi_roc_cfg)); + + return 0; +} + +static int +nxpwifi_sta_event_bg_scan_stopped(struct nxpwifi_private *priv) +{ + cfg80211_sched_scan_stopped(priv->wdev.wiphy, 0); + if (priv->sched_scanning) + priv->sched_scanning = false; + + return 0; +} + +static int +nxpwifi_sta_event_multi_chan_info(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_process_multi_chan_event(priv, adapter->event_skb); + + return 0; +} + +static int +nxpwifi_sta_event_tx_status_report(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_parse_tx_status_event(priv, adapter->event_body); + + return 0; +} + +static int +nxpwifi_sta_event_bt_coex_wlan_para_change(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (!adapter->ignore_btcoex_events) + nxpwifi_bt_coex_wlan_param_update_event(priv, + adapter->event_skb); + + return 0; +} + +static int +nxpwifi_sta_event_vdll_ind(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + return nxpwifi_process_vdll_event(priv, adapter->event_skb); +} + +static const struct nxpwifi_evt_entry evt_table_sta[] = { + {.event_cause = EVENT_LINK_LOST, + .event_handler = nxpwifi_sta_event_link_lost}, + {.event_cause = EVENT_LINK_SENSED, + .event_handler = nxpwifi_sta_event_link_sensed}, + {.event_cause = EVENT_DEAUTHENTICATED, + .event_handler = nxpwifi_sta_event_deauthenticated}, + {.event_cause = EVENT_DISASSOCIATED, + .event_handler = nxpwifi_sta_event_disassociated}, + {.event_cause = EVENT_PS_AWAKE, + .event_handler = nxpwifi_sta_event_ps_awake}, + {.event_cause = EVENT_PS_SLEEP, + .event_handler = nxpwifi_sta_event_ps_sleep}, + {.event_cause = EVENT_MIC_ERR_MULTICAST, + .event_handler = nxpwifi_sta_event_mic_err_multicast}, + {.event_cause = EVENT_MIC_ERR_UNICAST, + .event_handler = nxpwifi_sta_event_mic_err_unicast}, + {.event_cause = EVENT_DEEP_SLEEP_AWAKE, + .event_handler = nxpwifi_sta_event_deep_sleep_awake}, + {.event_cause = EVENT_WMM_STATUS_CHANGE, + .event_handler = nxpwifi_sta_event_wmm_status_change}, + {.event_cause = EVENT_BG_SCAN_REPORT, + .event_handler = nxpwifi_sta_event_bs_scan_report}, + {.event_cause = EVENT_RSSI_LOW, + .event_handler = nxpwifi_sta_event_rssi_low}, + {.event_cause = EVENT_RSSI_HIGH, + .event_handler = nxpwifi_sta_event_rssi_high}, + {.event_cause = EVENT_PORT_RELEASE, + .event_handler = nxpwifi_sta_event_port_release}, + {.event_cause = EVENT_ADDBA, + .event_handler = nxpwifi_sta_event_addba}, + {.event_cause = EVENT_DELBA, + .event_handler = nxpwifi_sta_event_delba}, + {.event_cause = EVENT_BA_STREAM_TIEMOUT, + .event_handler = nxpwifi_sta_event_bs_stream_timeout}, + {.event_cause = EVENT_AMSDU_AGGR_CTRL, + .event_handler = nxpwifi_sta_event_amsdu_aggr_ctrl}, + {.event_cause = EVENT_HS_ACT_REQ, + .event_handler = nxpwifi_sta_event_hs_act_req}, + {.event_cause = EVENT_CHANNEL_SWITCH_ANN, + .event_handler = nxpwifi_sta_event_channel_switch_ann}, + {.event_cause = EVENT_RADAR_DETECTED, + .event_handler = nxpwifi_sta_event_radar_detected}, + {.event_cause = EVENT_CHANNEL_REPORT_RDY, + .event_handler = nxpwifi_sta_event_channel_report_rdy}, + {.event_cause = EVENT_TX_DATA_PAUSE, + .event_handler = nxpwifi_sta_event_tx_data_pause}, + {.event_cause = EVENT_EXT_SCAN_REPORT, + .event_handler = nxpwifi_sta_event_ext_scan_report}, + {.event_cause = EVENT_RXBA_SYNC, + .event_handler = nxpwifi_sta_event_rxba_sync}, + {.event_cause = EVENT_REMAIN_ON_CHAN_EXPIRED, + .event_handler = nxpwifi_sta_event_remain_on_chan_expired}, + {.event_cause = EVENT_BG_SCAN_STOPPED, + .event_handler = nxpwifi_sta_event_bg_scan_stopped}, + {.event_cause = EVENT_MULTI_CHAN_INFO, + .event_handler = nxpwifi_sta_event_multi_chan_info}, + {.event_cause = EVENT_TX_STATUS_REPORT, + .event_handler = nxpwifi_sta_event_tx_status_report}, + {.event_cause = EVENT_BT_COEX_WLAN_PARA_CHANGE, + .event_handler = nxpwifi_sta_event_bt_coex_wlan_para_change}, + {.event_cause = EVENT_VDLL_IND, + .event_handler = nxpwifi_sta_event_vdll_ind}, + {.event_cause = EVENT_DUMMY_HOST_WAKEUP_SIGNAL, + .event_handler = NULL}, + {.event_cause = EVENT_MIB_CHANGED, + .event_handler = NULL}, + {.event_cause = EVENT_INIT_DONE, + .event_handler = NULL}, + {.event_cause = EVENT_SNR_LOW, + .event_handler = NULL}, + {.event_cause = EVENT_MAX_FAIL, + .event_handler = NULL}, + {.event_cause = EVENT_SNR_HIGH, + .event_handler = NULL}, + {.event_cause = EVENT_DATA_RSSI_LOW, + .event_handler = NULL}, + {.event_cause = EVENT_DATA_SNR_LOW, + .event_handler = NULL}, + {.event_cause = EVENT_DATA_RSSI_HIGH, + .event_handler = NULL}, + {.event_cause = EVENT_DATA_SNR_HIGH, + .event_handler = NULL}, + {.event_cause = EVENT_LINK_QUALITY, + .event_handler = NULL}, + {.event_cause = EVENT_PRE_BEACON_LOST, + .event_handler = NULL}, + {.event_cause = EVENT_WEP_ICV_ERR, + .event_handler = NULL}, + {.event_cause = EVENT_BW_CHANGE, + .event_handler = NULL}, + {.event_cause = EVENT_HOSTWAKE_STAIE, + .event_handler = NULL}, + {.event_cause = EVENT_UNKNOWN_DEBUG, + .event_handler = NULL}, +}; + +static void nxpwifi_process_uap_tx_pause(struct nxpwifi_private *priv, + struct nxpwifi_ie_types_header *tlv) +{ + struct nxpwifi_tx_pause_tlv *tp; + struct nxpwifi_sta_node *sta_ptr; + + tp = (void *)tlv; + nxpwifi_dbg(priv->adapter, EVENT, + "uap tx_pause: %pM pause=%d, pkts=%d\n", + tp->peermac, tp->tx_pause, + tp->pkt_cnt); + + if (ether_addr_equal(tp->peermac, priv->netdev->dev_addr)) { + if (tp->tx_pause) + priv->port_open = false; + else + priv->port_open = true; + } else if (is_multicast_ether_addr(tp->peermac)) { + nxpwifi_update_ralist_tx_pause(priv, tp->peermac, tp->tx_pause); + } else { + rcu_read_lock(); + sta_ptr = nxpwifi_get_sta_entry(priv, tp->peermac); + if (sta_ptr && sta_ptr->tx_pause != tp->tx_pause) { + sta_ptr->tx_pause = tp->tx_pause; + nxpwifi_update_ralist_tx_pause(priv, tp->peermac, + tp->tx_pause); + } + rcu_read_unlock(); + } +} + +static void nxpwifi_process_sta_tx_pause(struct nxpwifi_private *priv, + struct nxpwifi_ie_types_header *tlv) +{ + struct nxpwifi_tx_pause_tlv *tp; + + tp = (void *)tlv; + nxpwifi_dbg(priv->adapter, EVENT, + "sta tx_pause: %pM pause=%d, pkts=%d\n", + tp->peermac, tp->tx_pause, + tp->pkt_cnt); + + if (ether_addr_equal(tp->peermac, priv->cfg_bssid)) { + if (tp->tx_pause) + priv->port_open = false; + else + priv->port_open = true; + } +} + +/* + * Reset connection state after a firmware-triggered disconnect. + * Clears link state, queues, RSSI/SNR and security settings, + * saves previous SSID/BSSID for possible reassociation, + * and notifies cfg80211. + */ +void nxpwifi_reset_connect_state(struct nxpwifi_private *priv, u16 reason_code, + bool from_ap) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (!priv->media_connected) + return; + + nxpwifi_dbg(adapter, INFO, + "info: handles disconnect event\n"); + + priv->media_connected = false; + + priv->auth_flag = 0; + priv->auth_alg = WLAN_AUTH_NONE; + + priv->scan_block = false; + priv->port_open = false; + + /* Free Tx and Rx packets, report disconnect to upper layer */ + nxpwifi_clean_txrx(priv); + + /* Reset SNR/NF/RSSI values */ + priv->data_rssi_last = 0; + priv->data_nf_last = 0; + priv->data_rssi_avg = 0; + priv->data_nf_avg = 0; + priv->bcn_rssi_last = 0; + priv->bcn_nf_last = 0; + priv->bcn_rssi_avg = 0; + priv->bcn_nf_avg = 0; + priv->rxpd_rate = 0; + priv->rxpd_htinfo = 0; + priv->sec_info.wpa_enabled = false; + priv->sec_info.wpa2_enabled = false; + priv->wpa_ie_len = 0; + + priv->sec_info.encryption_mode = 0; + + /* Enable auto data rate */ + priv->is_data_rate_auto = true; + priv->data_rate = 0; + + priv->assoc_resp_ht_param = 0; + priv->ht_param_present = false; + + if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA || + GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) && priv->hist_data) + nxpwifi_hist_data_reset(priv); + + /* + * Memorize the previous SSID and BSSID so + * it could be used for re-assoc + */ + + nxpwifi_dbg(adapter, INFO, + "info: previous SSID=%s, SSID len=%u\n", + priv->prev_ssid.ssid, priv->prev_ssid.ssid_len); + + nxpwifi_dbg(adapter, INFO, + "info: current SSID=%s, SSID len=%u\n", + priv->curr_bss_params.bss_descriptor.ssid.ssid, + priv->curr_bss_params.bss_descriptor.ssid.ssid_len); + + memcpy(&priv->prev_ssid, + &priv->curr_bss_params.bss_descriptor.ssid, + sizeof(struct cfg80211_ssid)); + + memcpy(priv->prev_bssid, + priv->curr_bss_params.bss_descriptor.mac_address, ETH_ALEN); + + /* Need to erase the current SSID and BSSID info */ + memset(&priv->curr_bss_params, 0x00, sizeof(priv->curr_bss_params)); + + adapter->tx_lock_flag = false; + adapter->pps_uapsd_mode = false; + + if (test_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags) && + adapter->curr_cmd) + return; + + priv->media_connected = false; + nxpwifi_dbg(adapter, MSG, + "info: successfully disconnected from %pM: reason code %d\n", + priv->cfg_bssid, reason_code); + + if (priv->bss_mode == NL80211_IFTYPE_STATION) { + if (adapter->host_mlme_link_lost) + nxpwifi_host_mlme_disconnect(adapter->priv_link_lost, + reason_code, NULL); + else + cfg80211_disconnected(priv->netdev, reason_code, NULL, + 0, !from_ap, GFP_KERNEL); + } + eth_zero_addr(priv->cfg_bssid); + + nxpwifi_stop_net_dev_queue(priv->netdev, adapter); + netif_carrier_off(priv->netdev); + + if (!ISSUPP_FIRMWARE_SUPPLICANT(priv->adapter->fw_cap_info)) + return; + + nxpwifi_send_cmd(priv, HOST_CMD_GTK_REKEY_OFFLOAD_CFG, + HOST_ACT_GEN_REMOVE, 0, NULL, false); +} + +void nxpwifi_reset_conn_state_work(struct wiphy *wiphy, struct wiphy_work *work) +{ + struct nxpwifi_private *priv = container_of(work, + struct nxpwifi_private, + reset_conn_state_work); + + nxpwifi_reset_connect_state(priv, priv->last_deauth_reason, true); +} + +void nxpwifi_process_multi_chan_event(struct nxpwifi_private *priv, + struct sk_buff *event_skb) +{ + struct nxpwifi_ie_types_multi_chan_info *chan_info; + struct nxpwifi_ie_types_mc_group_info *grp_info; + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ie_types_header *tlv; + u16 tlv_buf_left, tlv_type, tlv_len; + int intf_num, bss_type, bss_num, i; + struct nxpwifi_private *intf_priv; + + tlv_buf_left = event_skb->len - sizeof(u32); + chan_info = (void *)event_skb->data + sizeof(u32); + + if (le16_to_cpu(chan_info->header.type) != TLV_TYPE_MULTI_CHAN_INFO || + tlv_buf_left < sizeof(struct nxpwifi_ie_types_multi_chan_info)) { + nxpwifi_dbg(adapter, ERROR, + "unknown TLV in chan_info event\n"); + return; + } + + adapter->usb_mc_status = le16_to_cpu(chan_info->status); + nxpwifi_dbg(adapter, EVENT, "multi chan operation %s\n", + adapter->usb_mc_status ? "started" : "over"); + + tlv_buf_left -= sizeof(struct nxpwifi_ie_types_multi_chan_info); + tlv = (struct nxpwifi_ie_types_header *)chan_info->tlv_buffer; + + while (tlv_buf_left >= (int)sizeof(struct nxpwifi_ie_types_header)) { + tlv_type = le16_to_cpu(tlv->type); + tlv_len = le16_to_cpu(tlv->len); + if ((sizeof(struct nxpwifi_ie_types_header) + tlv_len) > + tlv_buf_left) { + nxpwifi_dbg(adapter, ERROR, "wrong tlv: tlvLen=%d,\t" + "tlvBufLeft=%d\n", tlv_len, tlv_buf_left); + break; + } + if (tlv_type != TLV_TYPE_MC_GROUP_INFO) { + nxpwifi_dbg(adapter, ERROR, "wrong tlv type: 0x%x\n", + tlv_type); + break; + } + + grp_info = (struct nxpwifi_ie_types_mc_group_info *)tlv; + intf_num = grp_info->intf_num; + for (i = 0; i < intf_num; i++) { + bss_type = grp_info->bss_type_numlist[i] >> 4; + bss_num = grp_info->bss_type_numlist[i] & BSS_NUM_MASK; + intf_priv = nxpwifi_get_priv_by_id(adapter, bss_num, + bss_type); + if (!intf_priv) { + nxpwifi_dbg(adapter, ERROR, + "Invalid bss_type bss_num\t" + "in multi channel event\n"); + continue; + } + } + + tlv_buf_left -= sizeof(struct nxpwifi_ie_types_header) + + tlv_len; + tlv = (void *)((u8 *)tlv + tlv_len + + sizeof(struct nxpwifi_ie_types_header)); + } +} + +void nxpwifi_process_tx_pause_event(struct nxpwifi_private *priv, + struct sk_buff *event_skb) +{ + struct nxpwifi_ie_types_header *tlv; + u16 tlv_type, tlv_len; + int tlv_buf_left; + + if (!priv->media_connected) { + nxpwifi_dbg(priv->adapter, ERROR, + "tx_pause event while disconnected; bss_role=%d\n", + priv->bss_role); + return; + } + + tlv_buf_left = event_skb->len - sizeof(u32); + tlv = (void *)event_skb->data + sizeof(u32); + + while (tlv_buf_left >= (int)sizeof(struct nxpwifi_ie_types_header)) { + tlv_type = le16_to_cpu(tlv->type); + tlv_len = le16_to_cpu(tlv->len); + if ((sizeof(struct nxpwifi_ie_types_header) + tlv_len) > + tlv_buf_left) { + nxpwifi_dbg(priv->adapter, ERROR, + "wrong tlv: tlvLen=%d, tlvBufLeft=%d\n", + tlv_len, tlv_buf_left); + break; + } + if (tlv_type == TLV_TYPE_TX_PAUSE) { + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) + nxpwifi_process_sta_tx_pause(priv, tlv); + else + nxpwifi_process_uap_tx_pause(priv, tlv); + } + + tlv_buf_left -= sizeof(struct nxpwifi_ie_types_header) + + tlv_len; + tlv = (void *)((u8 *)tlv + tlv_len + + sizeof(struct nxpwifi_ie_types_header)); + } +} + +/* + * Handle BT coexistence event. Parse TLVs and update + * coexistence aggregation window and scan timing parameters. + */ +void nxpwifi_bt_coex_wlan_param_update_event(struct nxpwifi_private *priv, + struct sk_buff *event_skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ie_types_header *tlv; + struct nxpwifi_ie_types_btcoex_aggr_win_size *winsizetlv; + struct nxpwifi_ie_types_btcoex_scan_time *scantlv; + s32 len = event_skb->len - sizeof(u32); + u8 *cur_ptr = event_skb->data + sizeof(u32); + u16 tlv_type, tlv_len; + + while (len >= sizeof(struct nxpwifi_ie_types_header)) { + tlv = (struct nxpwifi_ie_types_header *)cur_ptr; + tlv_len = le16_to_cpu(tlv->len); + tlv_type = le16_to_cpu(tlv->type); + + if ((tlv_len + sizeof(struct nxpwifi_ie_types_header)) > len) + break; + switch (tlv_type) { + case TLV_BTCOEX_WL_AGGR_WINSIZE: + winsizetlv = + (struct nxpwifi_ie_types_btcoex_aggr_win_size *)tlv; + adapter->coex_win_size = winsizetlv->coex_win_size; + adapter->coex_tx_win_size = + winsizetlv->tx_win_size; + adapter->coex_rx_win_size = + winsizetlv->rx_win_size; + nxpwifi_coex_ampdu_rxwinsize(adapter); + nxpwifi_update_ampdu_txwinsize(adapter); + break; + + case TLV_BTCOEX_WL_SCANTIME: + scantlv = + (struct nxpwifi_ie_types_btcoex_scan_time *)tlv; + adapter->coex_scan = scantlv->coex_scan; + adapter->coex_min_scan_time = le16_to_cpu(scantlv->min_scan_time); + adapter->coex_max_scan_time = le16_to_cpu(scantlv->max_scan_time); + break; + + default: + break; + } + + len -= tlv_len + sizeof(struct nxpwifi_ie_types_header); + cur_ptr += tlv_len + + sizeof(struct nxpwifi_ie_types_header); + } + + nxpwifi_dbg(adapter, INFO, "coex_scan=%d min_scan=%d coex_win=%d, tx_win=%d rx_win=%d\n", + adapter->coex_scan, adapter->coex_min_scan_time, + adapter->coex_win_size, adapter->coex_tx_win_size, + adapter->coex_rx_win_size); +} + +/* + * Dispatch station firmware event based on event_cause. + * Looks up the handler in the station event table and invokes it. + */ +int nxpwifi_process_sta_event(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u32 eventcause = adapter->event_cause; + int evt, ret = 0; + + for (evt = 0; evt < ARRAY_SIZE(evt_table_sta); evt++) { + if (eventcause == evt_table_sta[evt].event_cause) { + if (evt_table_sta[evt].event_handler) + ret = evt_table_sta[evt].event_handler(priv); + break; + } + } + + if (evt == ARRAY_SIZE(evt_table_sta)) + nxpwifi_dbg(adapter, EVENT, + "%s: unknown event id: %#x\n", + __func__, eventcause); + else + nxpwifi_dbg(adapter, EVENT, + "%s: event id: %#x\n", + __func__, eventcause); + + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/sta_rx.c b/drivers/net/wireless/nxp/nxpwifi/sta_rx.c new file mode 100644 index 000000000000..d951d21eb41c --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/sta_rx.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: station RX data handling + * + * Copyright 2011-2024 NXP + */ + +#include +#include +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "11n_aggr.h" +#include "11n_rxreorder.h" + +/* + * Drop gratuitous IPv4 ARP and IPv6 neighbour advertisements when + * source and destination addresses are identical. + */ +static bool +nxpwifi_discard_gratuitous_arp(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + const struct nxpwifi_arp_eth_header *arp; + struct ethhdr *eth; + struct ipv6hdr *ipv6; + struct icmp6hdr *icmpv6; + + eth = (struct ethhdr *)skb->data; + switch (ntohs(eth->h_proto)) { + case ETH_P_ARP: + arp = (void *)(skb->data + sizeof(struct ethhdr)); + if (arp->hdr.ar_op == htons(ARPOP_REPLY) || + arp->hdr.ar_op == htons(ARPOP_REQUEST)) { + if (!memcmp(arp->ar_sip, arp->ar_tip, 4)) + return true; + } + break; + case ETH_P_IPV6: + ipv6 = (void *)(skb->data + sizeof(struct ethhdr)); + icmpv6 = (void *)(skb->data + sizeof(struct ethhdr) + + sizeof(struct ipv6hdr)); + if (icmpv6->icmp6_type == NDISC_NEIGHBOUR_ADVERTISEMENT) { + if (!memcmp(&ipv6->saddr, &ipv6->daddr, + sizeof(struct in6_addr))) + return true; + } + break; + default: + break; + } + + return false; +} + +/* + * Process a received data packet. + * Convert 802.2/LLC/SNAP to Ethernet II when appropriate, trim the + * rxpd and extra headers, optionally drop gratuitous ARP/NA, cache + * RX rate for unicast, then deliver to the stack. + */ +int nxpwifi_process_rx_packet(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + int ret; + struct rx_packet_hdr *rx_pkt_hdr; + struct rxpd *local_rx_pd; + int hdr_chop; + struct ethhdr *eth; + u16 rx_pkt_off; + u8 adj_rx_rate = 0; + + local_rx_pd = (struct rxpd *)(skb->data); + + rx_pkt_off = le16_to_cpu(local_rx_pd->rx_pkt_offset); + rx_pkt_hdr = (void *)local_rx_pd + rx_pkt_off; + + if (sizeof(rx_pkt_hdr->eth803_hdr) + sizeof(rfc1042_header) + + rx_pkt_off > skb->len) { + priv->stats.rx_dropped++; + dev_kfree_skb_any(skb); + return -EINVAL; + } + + if (sizeof(*rx_pkt_hdr) + rx_pkt_off <= skb->len && + ((!memcmp(&rx_pkt_hdr->rfc1042_hdr, bridge_tunnel_header, + sizeof(bridge_tunnel_header))) || + (!memcmp(&rx_pkt_hdr->rfc1042_hdr, rfc1042_header, + sizeof(rfc1042_header)) && + rx_pkt_hdr->rfc1042_hdr.snap_type != htons(ETH_P_AARP) && + rx_pkt_hdr->rfc1042_hdr.snap_type != htons(ETH_P_IPX)))) { + /* + * Replace the 803 header and rfc1042 header (llc/snap) with an + * EthernetII header, keep the src/dst and snap_type + * (ethertype). + * The firmware only passes up SNAP frames converting + * all RX Data from 802.11 to 802.2/LLC/SNAP frames. + * To create the Ethernet II, just move the src, dst address + * right before the snap_type. + */ + eth = (struct ethhdr *) + ((u8 *)&rx_pkt_hdr->eth803_hdr + + sizeof(rx_pkt_hdr->eth803_hdr) + + sizeof(rx_pkt_hdr->rfc1042_hdr) + - sizeof(rx_pkt_hdr->eth803_hdr.h_dest) + - sizeof(rx_pkt_hdr->eth803_hdr.h_source) + - sizeof(rx_pkt_hdr->rfc1042_hdr.snap_type)); + + memcpy(eth->h_source, rx_pkt_hdr->eth803_hdr.h_source, + sizeof(eth->h_source)); + memcpy(eth->h_dest, rx_pkt_hdr->eth803_hdr.h_dest, + sizeof(eth->h_dest)); + + /* + * Chop off the rxpd + the excess memory from the 802.2/llc/snap + * header that was removed. + */ + hdr_chop = (u8 *)eth - (u8 *)local_rx_pd; + } else { + /* Chop off the rxpd */ + hdr_chop = (u8 *)&rx_pkt_hdr->eth803_hdr - (u8 *)local_rx_pd; + } + + /* + * Chop off the leading header bytes so the it points to the start of + * either the reconstructed EthII frame or the 802.2/llc/snap frame + */ + skb_pull(skb, hdr_chop); + + if (priv->hs2_enabled && + nxpwifi_discard_gratuitous_arp(priv, skb)) { + nxpwifi_dbg(priv->adapter, INFO, "Bypassed Gratuitous ARP\n"); + dev_kfree_skb_any(skb); + return 0; + } + + /* Only stash RX bitrate for unicast packets. */ + if (likely(!is_multicast_ether_addr(rx_pkt_hdr->eth803_hdr.h_dest))) { + priv->rxpd_rate = local_rx_pd->rx_rate; + priv->rxpd_htinfo = local_rx_pd->rate_info; + } + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA || + GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + adj_rx_rate = nxpwifi_adjust_data_rate(priv, + local_rx_pd->rx_rate, + local_rx_pd->rate_info); + nxpwifi_hist_data_add(priv, adj_rx_rate, local_rx_pd->snr, + local_rx_pd->nf); + } + + ret = nxpwifi_recv_packet(priv, skb); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "recv packet failed\n"); + + return ret; +} + +/* + * Process a received buffer on the STA path. + * Validate RxPD and lengths, handle monitor/mgmt frames, fast-path + * non-unicast frames, and feed unicast data into 11n reorder/BA logic. + */ +int nxpwifi_process_sta_rx_packet(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret = 0; + struct rxpd *local_rx_pd; + struct rx_packet_hdr *rx_pkt_hdr; + u8 ta[ETH_ALEN]; + u16 rx_pkt_type, rx_pkt_offset, rx_pkt_length, seq_num; + + local_rx_pd = (struct rxpd *)(skb->data); + rx_pkt_type = le16_to_cpu(local_rx_pd->rx_pkt_type); + rx_pkt_offset = le16_to_cpu(local_rx_pd->rx_pkt_offset); + rx_pkt_length = le16_to_cpu(local_rx_pd->rx_pkt_length); + seq_num = le16_to_cpu(local_rx_pd->seq_num); + + rx_pkt_hdr = (void *)local_rx_pd + rx_pkt_offset; + + if ((rx_pkt_offset + rx_pkt_length) > skb->len || + sizeof(rx_pkt_hdr->eth803_hdr) + rx_pkt_offset > skb->len) { + nxpwifi_dbg(adapter, ERROR, + "wrong rx packet: len=%d, rx_pkt_offset=%d, rx_pkt_length=%d\n", + skb->len, rx_pkt_offset, rx_pkt_length); + priv->stats.rx_dropped++; + dev_kfree_skb_any(skb); + return ret; + } + + if (priv->adapter->enable_net_mon && rx_pkt_type == PKT_TYPE_802DOT11) { + ret = nxpwifi_recv_packet_to_monif(priv, skb); + if (ret) + dev_kfree_skb_any(skb); + return ret; + } + + if (rx_pkt_type == PKT_TYPE_MGMT) { + ret = nxpwifi_process_mgmt_packet(priv, skb); + if (ret && (ret != -EINPROGRESS)) + nxpwifi_dbg(adapter, DATA, "Rx of mgmt packet failed"); + if (ret != -EINPROGRESS) + dev_kfree_skb_any(skb); + return ret; + } + + /* + * If the packet is not an unicast packet then send the packet + * directly to os. Don't pass thru rx reordering + */ + if (!IS_11N_ENABLED(priv) || + !ether_addr_equal_unaligned(priv->curr_addr, + rx_pkt_hdr->eth803_hdr.h_dest)) { + nxpwifi_process_rx_packet(priv, skb); + return ret; + } + + if (nxpwifi_queuing_ra_based(priv)) { + memcpy(ta, rx_pkt_hdr->eth803_hdr.h_source, ETH_ALEN); + } else { + if (rx_pkt_type != PKT_TYPE_BAR && + local_rx_pd->priority < MAX_NUM_TID) + priv->rx_seq[local_rx_pd->priority] = seq_num; + memcpy(ta, priv->curr_bss_params.bss_descriptor.mac_address, + ETH_ALEN); + } + + /* Reorder and send to OS */ + ret = nxpwifi_11n_rx_reorder_pkt(priv, seq_num, local_rx_pd->priority, + ta, (u8)rx_pkt_type, skb); + + if (ret || rx_pkt_type == PKT_TYPE_BAR) + dev_kfree_skb_any(skb); + + if (ret) + priv->stats.rx_dropped++; + + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/sta_tx.c b/drivers/net/wireless/nxp/nxpwifi/sta_tx.c new file mode 100644 index 000000000000..10f963bc4e00 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/sta_tx.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: station TX data handling + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" + +/* + * Fill TxPD for TX packets by inserting it before payload and setting required fields. + */ +void nxpwifi_process_sta_txpd(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct txpd *local_tx_pd; + struct nxpwifi_txinfo *tx_info = NXPWIFI_SKB_TXCB(skb); + unsigned int pad; + u16 pkt_type, pkt_length, pkt_offset; + int hroom = adapter->intf_hdr_len; + u32 tx_control; + + pkt_type = nxpwifi_is_skb_mgmt_frame(skb) ? PKT_TYPE_MGMT : 0; + + pad = ((uintptr_t)skb->data - (sizeof(*local_tx_pd) + hroom)) & + (NXPWIFI_DMA_ALIGN_SZ - 1); + skb_push(skb, sizeof(*local_tx_pd) + pad); + + local_tx_pd = (struct txpd *)skb->data; + memset(local_tx_pd, 0, sizeof(struct txpd)); + local_tx_pd->bss_num = priv->bss_num; + local_tx_pd->bss_type = priv->bss_type; + + pkt_length = (u16)(skb->len - (sizeof(struct txpd) + pad)); + if (pkt_type == PKT_TYPE_MGMT) + pkt_length -= NXPWIFI_MGMT_FRAME_HEADER_SIZE; + local_tx_pd->tx_pkt_length = cpu_to_le16(pkt_length); + + local_tx_pd->priority = (u8)skb->priority; + local_tx_pd->pkt_delay_2ms = + nxpwifi_wmm_compute_drv_pkt_delay(priv, skb); + + if (tx_info->flags & NXPWIFI_BUF_FLAG_EAPOL_TX_STATUS || + tx_info->flags & NXPWIFI_BUF_FLAG_ACTION_TX_STATUS) { + local_tx_pd->tx_token_id = tx_info->ack_frame_id; + local_tx_pd->flags |= NXPWIFI_TXPD_FLAGS_REQ_TX_STATUS; + } + + if (local_tx_pd->priority < + ARRAY_SIZE(priv->wmm.user_pri_pkt_tx_ctrl)) { + /* + * Set the priority specific tx_control field, setting of 0 will + * cause the default value to be used later in this function + */ + tx_control = + priv->wmm.user_pri_pkt_tx_ctrl[local_tx_pd->priority]; + local_tx_pd->tx_control = cpu_to_le32(tx_control); + } + + if (adapter->pps_uapsd_mode) { + if (nxpwifi_check_last_packet_indication(priv)) { + adapter->tx_lock_flag = true; + local_tx_pd->flags = + NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET; + } + } + + /* Offset of actual data */ + pkt_offset = sizeof(struct txpd) + pad; + if (pkt_type == PKT_TYPE_MGMT) { + /* Set the packet type and add header for management frame */ + local_tx_pd->tx_pkt_type = cpu_to_le16(pkt_type); + pkt_offset += NXPWIFI_MGMT_FRAME_HEADER_SIZE; + } + + local_tx_pd->tx_pkt_offset = cpu_to_le16(pkt_offset); + + /* make space for adapter->intf_hdr_len */ + skb_push(skb, hroom); + + if (!local_tx_pd->tx_control) + /* TxCtrl set by user or default */ + local_tx_pd->tx_control = cpu_to_le32(priv->pkt_tx_ctrl); +} + +/* Send a NULL-data frame with TxPD at highest priority. */ +int nxpwifi_send_null_packet(struct nxpwifi_private *priv, u8 flags) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct txpd *local_tx_pd; + struct nxpwifi_tx_param tx_param; +/* sizeof(struct txpd) + Interface specific header */ +#define NULL_PACKET_HDR 64 + u32 data_len = NULL_PACKET_HDR; + struct sk_buff *skb; + int ret; + struct nxpwifi_txinfo *tx_info = NULL; + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags)) + return -EPERM; + + if (!priv->media_connected) + return -EPERM; + + if (adapter->data_sent) + return -EBUSY; + + skb = dev_alloc_skb(data_len); + if (!skb) + return -ENOMEM; + + tx_info = NXPWIFI_SKB_TXCB(skb); + memset(tx_info, 0, sizeof(*tx_info)); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + tx_info->pkt_len = data_len - + (sizeof(struct txpd) + adapter->intf_hdr_len); + skb_reserve(skb, sizeof(struct txpd) + adapter->intf_hdr_len); + skb_push(skb, sizeof(struct txpd)); + + local_tx_pd = (struct txpd *)skb->data; + local_tx_pd->tx_control = cpu_to_le32(priv->pkt_tx_ctrl); + local_tx_pd->flags = flags; + local_tx_pd->priority = WMM_HIGHEST_PRIORITY; + local_tx_pd->tx_pkt_offset = cpu_to_le16(sizeof(struct txpd)); + local_tx_pd->bss_num = priv->bss_num; + local_tx_pd->bss_type = priv->bss_type; + + skb_push(skb, adapter->intf_hdr_len); + tx_param.next_pkt_len = 0; + ret = adapter->if_ops.host_to_card(adapter, NXPWIFI_TYPE_DATA, + skb, &tx_param); + + switch (ret) { + case -EBUSY: + dev_kfree_skb_any(skb); + nxpwifi_dbg(adapter, ERROR, + "%s: host_to_card failed: ret=%d\n", + __func__, ret); + adapter->dbg.num_tx_host_to_card_failure++; + break; + case 0: + dev_kfree_skb_any(skb); + nxpwifi_dbg(adapter, DATA, + "data: %s: host_to_card succeeded\n", + __func__); + adapter->tx_lock_flag = true; + break; + case -EINPROGRESS: + adapter->tx_lock_flag = true; + break; + default: + dev_kfree_skb_any(skb); + nxpwifi_dbg(adapter, ERROR, + "%s: host_to_card failed: ret=%d\n", + __func__, ret); + adapter->dbg.num_tx_host_to_card_failure++; + break; + } + + return ret; +} + +/* Check whether a last‑packet indication needs to be sent. */ +u8 nxpwifi_check_last_packet_indication(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u8 ret = false; + + if (!adapter->sleep_period.period) + return ret; + if (nxpwifi_wmm_lists_empty(adapter)) + ret = true; + + if (ret && !adapter->cmd_sent && !adapter->curr_cmd && + !nxpwifi_is_command_pending(adapter)) { + adapter->delay_null_pkt = false; + ret = true; + } else { + ret = false; + adapter->delay_null_pkt = true; + } + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/txrx.c b/drivers/net/wireless/nxp/nxpwifi/txrx.c new file mode 100644 index 000000000000..6e8b49138e57 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/txrx.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: generic TX/RX data handling + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" + +/* + * Parse the RxPD, select the target interface, and dispatch the packet for + * handling. + */ +int nxpwifi_handle_rx_packet(struct nxpwifi_adapter *adapter, + struct sk_buff *skb) +{ + struct nxpwifi_private *priv = + nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + struct rxpd *local_rx_pd; + struct nxpwifi_rxinfo *rx_info = NXPWIFI_SKB_RXCB(skb); + int ret; + + local_rx_pd = (struct rxpd *)(skb->data); + /* Get the BSS number from rxpd, get corresponding priv */ + priv = nxpwifi_get_priv_by_id(adapter, local_rx_pd->bss_num & + BSS_NUM_MASK, local_rx_pd->bss_type); + if (!priv) + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + + if (!priv) { + nxpwifi_dbg(adapter, ERROR, + "data: priv not found. Drop RX packet\n"); + dev_kfree_skb_any(skb); + return -EINVAL; + } + + nxpwifi_dbg_dump(adapter, DAT_D, "rx pkt:", skb->data, + min_t(size_t, skb->len, DEBUG_DUMP_DATA_MAX_LEN)); + + memset(rx_info, 0, sizeof(*rx_info)); + rx_info->bss_num = priv->bss_num; + rx_info->bss_type = priv->bss_type; + + if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP) + ret = nxpwifi_process_uap_rx_packet(priv, skb); + else + ret = nxpwifi_process_sta_rx_packet(priv, skb); + + return ret; +} +EXPORT_SYMBOL_GPL(nxpwifi_handle_rx_packet); + +/* + * Add TxPD, validate, send the packet to firmware, then run completion + * callback. + */ +int nxpwifi_process_tx(struct nxpwifi_private *priv, struct sk_buff *skb, + struct nxpwifi_tx_param *tx_param) +{ + int hroom, ret; + struct nxpwifi_adapter *adapter = priv->adapter; + struct txpd *local_tx_pd = NULL; + struct nxpwifi_sta_node *dest_node; + struct ethhdr *hdr = (void *)skb->data; + + if (unlikely(!skb->len || + skb_headroom(skb) < NXPWIFI_MIN_DATA_HEADER_LEN)) { + ret = -EINVAL; + goto out; + } + + hroom = adapter->intf_hdr_len; + + if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP) { + rcu_read_lock(); + dest_node = nxpwifi_get_sta_entry(priv, hdr->h_dest); + if (dest_node) { + dest_node->stats.tx_bytes += skb->len; + dest_node->stats.tx_packets++; + } + rcu_read_unlock(); + nxpwifi_process_uap_txpd(priv, skb); + } else { + nxpwifi_process_sta_txpd(priv, skb); + } + + if ((adapter->data_sent || adapter->tx_lock_flag)) { + skb_queue_tail(&adapter->tx_data_q, skb); + atomic_inc(&adapter->tx_queued); + return 0; + } + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) + local_tx_pd = (struct txpd *)(skb->data + hroom); + ret = adapter->if_ops.host_to_card(adapter, + NXPWIFI_TYPE_DATA, + skb, tx_param); + nxpwifi_dbg_dump(adapter, DAT_D, "tx pkt:", skb->data, + min_t(size_t, skb->len, DEBUG_DUMP_DATA_MAX_LEN)); + +out: + switch (ret) { + case -ENOSR: + nxpwifi_dbg(adapter, DATA, "data: -ENOSR is returned\n"); + break; + case -EBUSY: + if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) && + adapter->pps_uapsd_mode && adapter->tx_lock_flag) { + priv->adapter->tx_lock_flag = false; + if (local_tx_pd) + local_tx_pd->flags = 0; + } + nxpwifi_dbg(adapter, ERROR, "data: -EBUSY is returned\n"); + break; + case -EINPROGRESS: + break; + case -EINVAL: + nxpwifi_dbg(adapter, ERROR, + "malformed skb (length: %u, headroom: %u)\n", + skb->len, skb_headroom(skb)); + fallthrough; + case 0: + nxpwifi_write_data_complete(adapter, skb, 0, ret); + break; + default: + nxpwifi_dbg(adapter, ERROR, + "nxpwifi_write_data_async failed: 0x%X\n", + ret); + adapter->dbg.num_tx_host_to_card_failure++; + nxpwifi_write_data_complete(adapter, skb, 0, ret); + break; + } + + return ret; +} + +static int nxpwifi_host_to_card(struct nxpwifi_adapter *adapter, + struct sk_buff *skb, + struct nxpwifi_tx_param *tx_param) +{ + struct txpd *local_tx_pd = NULL; + u8 *head_ptr = skb->data; + int ret = 0; + struct nxpwifi_private *priv; + struct nxpwifi_txinfo *tx_info; + + tx_info = NXPWIFI_SKB_TXCB(skb); + priv = nxpwifi_get_priv_by_id(adapter, tx_info->bss_num, + tx_info->bss_type); + if (!priv) { + nxpwifi_dbg(adapter, ERROR, + "data: priv not found. Drop TX packet\n"); + adapter->dbg.num_tx_host_to_card_failure++; + nxpwifi_write_data_complete(adapter, skb, 0, 0); + return ret; + } + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) + local_tx_pd = (struct txpd *)(head_ptr + adapter->intf_hdr_len); + + ret = adapter->if_ops.host_to_card(adapter, + NXPWIFI_TYPE_DATA, + skb, tx_param); + + switch (ret) { + case -ENOSR: + nxpwifi_dbg(adapter, ERROR, "data: -ENOSR is returned\n"); + break; + case -EBUSY: + if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) && + adapter->pps_uapsd_mode && + adapter->tx_lock_flag) { + priv->adapter->tx_lock_flag = false; + if (local_tx_pd) + local_tx_pd->flags = 0; + } + skb_queue_head(&adapter->tx_data_q, skb); + if (tx_info->flags & NXPWIFI_BUF_FLAG_AGGR_PKT) + atomic_add(tx_info->aggr_num, &adapter->tx_queued); + else + atomic_inc(&adapter->tx_queued); + nxpwifi_dbg(adapter, ERROR, "data: -EBUSY is returned\n"); + break; + case -EINPROGRESS: + break; + case 0: + nxpwifi_write_data_complete(adapter, skb, 0, ret); + break; + default: + nxpwifi_dbg(adapter, ERROR, + "nxpwifi_write_data_async failed: 0x%X\n", ret); + adapter->dbg.num_tx_host_to_card_failure++; + nxpwifi_write_data_complete(adapter, skb, 0, ret); + break; + } + return ret; +} + +static int +nxpwifi_dequeue_tx_queue(struct nxpwifi_adapter *adapter) +{ + struct sk_buff *skb, *skb_next; + struct nxpwifi_txinfo *tx_info; + struct nxpwifi_tx_param tx_param; + + skb = skb_dequeue(&adapter->tx_data_q); + if (!skb) + return -ENOMEM; + + tx_info = NXPWIFI_SKB_TXCB(skb); + if (tx_info->flags & NXPWIFI_BUF_FLAG_AGGR_PKT) + atomic_sub(tx_info->aggr_num, &adapter->tx_queued); + else + atomic_dec(&adapter->tx_queued); + + if (!skb_queue_empty(&adapter->tx_data_q)) + skb_next = skb_peek(&adapter->tx_data_q); + else + skb_next = NULL; + tx_param.next_pkt_len = ((skb_next) ? skb_next->len : 0); + if (!tx_param.next_pkt_len) { + if (!nxpwifi_wmm_lists_empty(adapter)) + tx_param.next_pkt_len = 1; + } + return nxpwifi_host_to_card(adapter, skb, &tx_param); +} + +void +nxpwifi_process_tx_queue(struct nxpwifi_adapter *adapter) +{ + do { + if (adapter->data_sent || adapter->tx_lock_flag) + break; + if (nxpwifi_dequeue_tx_queue(adapter)) + break; + } while (!skb_queue_empty(&adapter->tx_data_q)); +} + +/* + * Packet send completion callback handler. + * + * It either frees the buffer directly or forwards it to another + * completion callback which checks conditions, updates statistics, + * wakes up stalled traffic queue if required, and then frees the buffer. + */ +int nxpwifi_write_data_complete(struct nxpwifi_adapter *adapter, + struct sk_buff *skb, int aggr, int status) +{ + struct nxpwifi_private *priv; + struct nxpwifi_txinfo *tx_info; + struct netdev_queue *txq; + int index; + + if (!skb) + return 0; + + tx_info = NXPWIFI_SKB_TXCB(skb); + priv = nxpwifi_get_priv_by_id(adapter, tx_info->bss_num, + tx_info->bss_type); + if (!priv) + goto done; + + nxpwifi_set_trans_start(priv->netdev); + + if (tx_info->flags & NXPWIFI_BUF_FLAG_BRIDGED_PKT) + atomic_dec_return(&adapter->pending_bridged_pkts); + + if (tx_info->flags & NXPWIFI_BUF_FLAG_AGGR_PKT) + goto done; + + if (!status) { + priv->stats.tx_packets++; + priv->stats.tx_bytes += tx_info->pkt_len; + if (priv->tx_timeout_cnt) + priv->tx_timeout_cnt = 0; + } else { + priv->stats.tx_errors++; + } + + if (aggr) + /* For skb_aggr, do not wake up tx queue */ + goto done; + + atomic_dec(&adapter->tx_pending); + + index = nxpwifi_1d_to_wmm_queue[skb->priority]; + if (atomic_dec_return(&priv->wmm_tx_pending[index]) < LOW_TX_PENDING) { + txq = netdev_get_tx_queue(priv->netdev, index); + if (netif_tx_queue_stopped(txq)) { + netif_tx_wake_queue(txq); + nxpwifi_dbg(adapter, DATA, "wake queue: %d\n", index); + } + } +done: + dev_kfree_skb_any(skb); + + return 0; +} +EXPORT_SYMBOL_GPL(nxpwifi_write_data_complete); + +void nxpwifi_parse_tx_status_event(struct nxpwifi_private *priv, + void *event_body) +{ + struct tx_status_event *tx_status = (void *)priv->adapter->event_body; + struct sk_buff *ack_skb; + struct nxpwifi_txinfo *tx_info; + + if (!tx_status->tx_token_id) + return; + + spin_lock_bh(&priv->ack_status_lock); + ack_skb = xa_erase(&priv->ack_status_frames, tx_status->tx_token_id); + spin_unlock_bh(&priv->ack_status_lock); + + if (ack_skb) { + tx_info = NXPWIFI_SKB_TXCB(ack_skb); + + if (tx_info->flags & NXPWIFI_BUF_FLAG_EAPOL_TX_STATUS) { + /* consumes ack_skb */ + skb_complete_wifi_ack(ack_skb, !tx_status->status); + } else { + /* Remove broadcast address which was added by driver */ + memmove(ack_skb->data + + sizeof(struct ieee80211_hdr_3addr) + + NXPWIFI_MGMT_FRAME_HEADER_SIZE + sizeof(u16), + ack_skb->data + + sizeof(struct ieee80211_hdr_3addr) + + NXPWIFI_MGMT_FRAME_HEADER_SIZE + sizeof(u16) + + ETH_ALEN, ack_skb->len - + (sizeof(struct ieee80211_hdr_3addr) + + NXPWIFI_MGMT_FRAME_HEADER_SIZE + sizeof(u16) + + ETH_ALEN)); + ack_skb->len = ack_skb->len - ETH_ALEN; + /* + * Remove driver's proprietary header including 2 bytes + * of packet length and pass actual management frame buffer + * to cfg80211. + */ + cfg80211_mgmt_tx_status(&priv->wdev, tx_info->cookie, + ack_skb->data + + NXPWIFI_MGMT_FRAME_HEADER_SIZE + + sizeof(u16), ack_skb->len - + (NXPWIFI_MGMT_FRAME_HEADER_SIZE + + sizeof(u16)), + !tx_status->status, GFP_ATOMIC); + dev_kfree_skb_any(ack_skb); + } + } +} diff --git a/drivers/net/wireless/nxp/nxpwifi/uap_cmd.c b/drivers/net/wireless/nxp/nxpwifi/uap_cmd.c new file mode 100644 index 000000000000..04551847643f --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/uap_cmd.c @@ -0,0 +1,1256 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: AP specific command handling + * + * Copyright 2011-2024 NXP + */ + +#include "main.h" +#include "cmdevt.h" +#include "11n.h" +#include "11ac.h" +#include "11ax.h" + +/* Parse BSS params and append WPA/WPA2 TLVs to the command buffer. */ +static void +nxpwifi_uap_bss_wpa(u8 **tlv_buf, void *cmd_buf, u16 *param_size) +{ + struct host_cmd_tlv_pwk_cipher *pwk_cipher; + struct host_cmd_tlv_gwk_cipher *gwk_cipher; + struct host_cmd_tlv_passphrase *passphrase; + struct host_cmd_tlv_akmp *tlv_akmp; + struct nxpwifi_uap_bss_param *bss_cfg = cmd_buf; + u16 cmd_size = *param_size; + u8 *tlv = *tlv_buf; + + tlv_akmp = (struct host_cmd_tlv_akmp *)tlv; + tlv_akmp->header.type = cpu_to_le16(TLV_TYPE_UAP_AKMP); + tlv_akmp->header.len = cpu_to_le16(sizeof(struct host_cmd_tlv_akmp) - + sizeof(struct nxpwifi_ie_types_header)); + tlv_akmp->key_mgmt_operation = cpu_to_le16(bss_cfg->key_mgmt_operation); + tlv_akmp->key_mgmt = cpu_to_le16(bss_cfg->key_mgmt); + cmd_size += sizeof(struct host_cmd_tlv_akmp); + tlv += sizeof(struct host_cmd_tlv_akmp); + + if (bss_cfg->wpa_cfg.pairwise_cipher_wpa & VALID_CIPHER_BITMAP) { + pwk_cipher = (struct host_cmd_tlv_pwk_cipher *)tlv; + pwk_cipher->header.type = cpu_to_le16(TLV_TYPE_PWK_CIPHER); + pwk_cipher->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_pwk_cipher) - + sizeof(struct nxpwifi_ie_types_header)); + pwk_cipher->proto = cpu_to_le16(PROTOCOL_WPA); + pwk_cipher->cipher = bss_cfg->wpa_cfg.pairwise_cipher_wpa; + cmd_size += sizeof(struct host_cmd_tlv_pwk_cipher); + tlv += sizeof(struct host_cmd_tlv_pwk_cipher); + } + + if (bss_cfg->wpa_cfg.pairwise_cipher_wpa2 & VALID_CIPHER_BITMAP) { + pwk_cipher = (struct host_cmd_tlv_pwk_cipher *)tlv; + pwk_cipher->header.type = cpu_to_le16(TLV_TYPE_PWK_CIPHER); + pwk_cipher->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_pwk_cipher) - + sizeof(struct nxpwifi_ie_types_header)); + pwk_cipher->proto = cpu_to_le16(PROTOCOL_WPA2); + pwk_cipher->cipher = bss_cfg->wpa_cfg.pairwise_cipher_wpa2; + cmd_size += sizeof(struct host_cmd_tlv_pwk_cipher); + tlv += sizeof(struct host_cmd_tlv_pwk_cipher); + } + + if (bss_cfg->wpa_cfg.group_cipher & VALID_CIPHER_BITMAP) { + gwk_cipher = (struct host_cmd_tlv_gwk_cipher *)tlv; + gwk_cipher->header.type = cpu_to_le16(TLV_TYPE_GWK_CIPHER); + gwk_cipher->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_gwk_cipher) - + sizeof(struct nxpwifi_ie_types_header)); + gwk_cipher->cipher = bss_cfg->wpa_cfg.group_cipher; + cmd_size += sizeof(struct host_cmd_tlv_gwk_cipher); + tlv += sizeof(struct host_cmd_tlv_gwk_cipher); + } + + if (bss_cfg->wpa_cfg.length) { + passphrase = (struct host_cmd_tlv_passphrase *)tlv; + passphrase->header.type = + cpu_to_le16(TLV_TYPE_UAP_WPA_PASSPHRASE); + passphrase->header.len = cpu_to_le16(bss_cfg->wpa_cfg.length); + memcpy(passphrase->passphrase, bss_cfg->wpa_cfg.passphrase, + bss_cfg->wpa_cfg.length); + cmd_size += sizeof(struct nxpwifi_ie_types_header) + + bss_cfg->wpa_cfg.length; + tlv += sizeof(struct nxpwifi_ie_types_header) + + bss_cfg->wpa_cfg.length; + } + + *param_size = cmd_size; + *tlv_buf = tlv; +} + +/* Parse BSS params and append WEP TLVs to the command buffer. */ +static void +nxpwifi_uap_bss_wep(u8 **tlv_buf, void *cmd_buf, u16 *param_size) +{ + struct host_cmd_tlv_wep_key *wep_key; + u16 cmd_size = *param_size; + int i; + u8 *tlv = *tlv_buf; + struct nxpwifi_uap_bss_param *bss_cfg = cmd_buf; + + for (i = 0; i < NUM_WEP_KEYS; i++) { + if (bss_cfg->wep_cfg[i].length && + (bss_cfg->wep_cfg[i].length == WLAN_KEY_LEN_WEP40 || + bss_cfg->wep_cfg[i].length == WLAN_KEY_LEN_WEP104)) { + wep_key = (struct host_cmd_tlv_wep_key *)tlv; + wep_key->header.type = + cpu_to_le16(TLV_TYPE_UAP_WEP_KEY); + wep_key->header.len = + cpu_to_le16(bss_cfg->wep_cfg[i].length + 2); + wep_key->key_index = bss_cfg->wep_cfg[i].key_index; + wep_key->is_default = bss_cfg->wep_cfg[i].is_default; + memcpy(wep_key->key, bss_cfg->wep_cfg[i].key, + bss_cfg->wep_cfg[i].length); + cmd_size += sizeof(struct nxpwifi_ie_types_header) + 2 + + bss_cfg->wep_cfg[i].length; + tlv += sizeof(struct nxpwifi_ie_types_header) + 2 + + bss_cfg->wep_cfg[i].length; + } + } + + *param_size = cmd_size; + *tlv_buf = tlv; +} + +/* Parse BSS params and append TLVs to the command buffer. */ +static int nxpwifi_uap_bss_param_prepare(struct nxpwifi_private *priv, u8 *tlv, + void *cmd_buf, u16 *param_size) +{ + struct host_cmd_tlv_mac_addr *mac_tlv; + struct host_cmd_tlv_dtim_period *dtim_period; + struct host_cmd_tlv_beacon_period *beacon_period; + struct host_cmd_tlv_ssid *ssid; + struct host_cmd_tlv_bcast_ssid *bcast_ssid; + struct host_cmd_tlv_channel_band *chan_band; + struct host_cmd_tlv_frag_threshold *frag_threshold; + struct host_cmd_tlv_rts_threshold *rts_threshold; + struct host_cmd_tlv_retry_limit *retry_limit; + struct host_cmd_tlv_encrypt_protocol *encrypt_protocol; + struct host_cmd_tlv_auth_type *auth_type; + struct host_cmd_tlv_rates *tlv_rates; + struct host_cmd_tlv_ageout_timer *ao_timer, *ps_ao_timer; + struct host_cmd_tlv_power_constraint *pwr_ct; + struct nxpwifi_ie_types_htcap *htcap; + struct nxpwifi_uap_bss_param *bss_cfg = cmd_buf; + int i; + u16 cmd_size = *param_size; + + mac_tlv = (struct host_cmd_tlv_mac_addr *)tlv; + mac_tlv->header.type = cpu_to_le16(TLV_TYPE_UAP_MAC_ADDRESS); + mac_tlv->header.len = cpu_to_le16(ETH_ALEN); + memcpy(mac_tlv->mac_addr, bss_cfg->mac_addr, ETH_ALEN); + cmd_size += sizeof(struct host_cmd_tlv_mac_addr); + tlv += sizeof(struct host_cmd_tlv_mac_addr); + + if (bss_cfg->ssid.ssid_len) { + ssid = (struct host_cmd_tlv_ssid *)tlv; + ssid->header.type = cpu_to_le16(TLV_TYPE_UAP_SSID); + ssid->header.len = cpu_to_le16((u16)bss_cfg->ssid.ssid_len); + memcpy(ssid->ssid, bss_cfg->ssid.ssid, bss_cfg->ssid.ssid_len); + cmd_size += sizeof(struct nxpwifi_ie_types_header) + + bss_cfg->ssid.ssid_len; + tlv += sizeof(struct nxpwifi_ie_types_header) + + bss_cfg->ssid.ssid_len; + + bcast_ssid = (struct host_cmd_tlv_bcast_ssid *)tlv; + bcast_ssid->header.type = cpu_to_le16(TLV_TYPE_UAP_BCAST_SSID); + bcast_ssid->header.len = + cpu_to_le16(sizeof(bcast_ssid->bcast_ctl)); + bcast_ssid->bcast_ctl = bss_cfg->bcast_ssid_ctl; + cmd_size += sizeof(struct host_cmd_tlv_bcast_ssid); + tlv += sizeof(struct host_cmd_tlv_bcast_ssid); + } + if (bss_cfg->rates[0]) { + tlv_rates = (struct host_cmd_tlv_rates *)tlv; + tlv_rates->header.type = cpu_to_le16(TLV_TYPE_UAP_RATES); + + for (i = 0; i < NXPWIFI_SUPPORTED_RATES && bss_cfg->rates[i]; + i++) + tlv_rates->rates[i] = bss_cfg->rates[i]; + + tlv_rates->header.len = cpu_to_le16(i); + cmd_size += sizeof(struct host_cmd_tlv_rates) + i; + tlv += sizeof(struct host_cmd_tlv_rates) + i; + } + if (bss_cfg->channel && + (((bss_cfg->band_cfg & BIT(0)) == BAND_CONFIG_BG && + bss_cfg->channel <= MAX_CHANNEL_BAND_BG) || + ((bss_cfg->band_cfg & BIT(0)) == BAND_CONFIG_A && + bss_cfg->channel <= MAX_CHANNEL_BAND_A))) { + chan_band = (struct host_cmd_tlv_channel_band *)tlv; + chan_band->header.type = cpu_to_le16(TLV_TYPE_CHANNELBANDLIST); + chan_band->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_channel_band) - + sizeof(struct nxpwifi_ie_types_header)); + chan_band->band_config = bss_cfg->band_cfg; + chan_band->channel = bss_cfg->channel; + cmd_size += sizeof(struct host_cmd_tlv_channel_band); + tlv += sizeof(struct host_cmd_tlv_channel_band); + } + if (bss_cfg->beacon_period >= NXPWIFI_BEACON_PERIOD_MIN && + bss_cfg->beacon_period <= NXPWIFI_BEACON_PERIOD_MAX) { + beacon_period = (struct host_cmd_tlv_beacon_period *)tlv; + beacon_period->header.type = + cpu_to_le16(TLV_TYPE_UAP_BEACON_PERIOD); + beacon_period->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_beacon_period) - + sizeof(struct nxpwifi_ie_types_header)); + beacon_period->period = cpu_to_le16(bss_cfg->beacon_period); + cmd_size += sizeof(struct host_cmd_tlv_beacon_period); + tlv += sizeof(struct host_cmd_tlv_beacon_period); + } + if (bss_cfg->dtim_period >= NXPWIFI_MIN_DTIM_PERIOD && + bss_cfg->dtim_period <= NXPWIFI_MAX_DTIM_PERIOD) { + dtim_period = (struct host_cmd_tlv_dtim_period *)tlv; + dtim_period->header.type = + cpu_to_le16(TLV_TYPE_UAP_DTIM_PERIOD); + dtim_period->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_dtim_period) - + sizeof(struct nxpwifi_ie_types_header)); + dtim_period->period = bss_cfg->dtim_period; + cmd_size += sizeof(struct host_cmd_tlv_dtim_period); + tlv += sizeof(struct host_cmd_tlv_dtim_period); + } + if (bss_cfg->rts_threshold <= NXPWIFI_RTS_THRESHOLD_MAX) { + rts_threshold = (struct host_cmd_tlv_rts_threshold *)tlv; + rts_threshold->header.type = + cpu_to_le16(TLV_TYPE_UAP_RTS_THRESHOLD); + rts_threshold->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_rts_threshold) - + sizeof(struct nxpwifi_ie_types_header)); + rts_threshold->rts_thr = cpu_to_le16(bss_cfg->rts_threshold); + cmd_size += sizeof(struct host_cmd_tlv_frag_threshold); + tlv += sizeof(struct host_cmd_tlv_frag_threshold); + } + if (bss_cfg->frag_threshold >= NXPWIFI_FRAG_THRESHOLD_MIN && + bss_cfg->frag_threshold <= NXPWIFI_FRAG_THRESHOLD_MAX) { + frag_threshold = (struct host_cmd_tlv_frag_threshold *)tlv; + frag_threshold->header.type = + cpu_to_le16(TLV_TYPE_UAP_FRAG_THRESHOLD); + frag_threshold->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_frag_threshold) - + sizeof(struct nxpwifi_ie_types_header)); + frag_threshold->frag_thr = cpu_to_le16(bss_cfg->frag_threshold); + cmd_size += sizeof(struct host_cmd_tlv_frag_threshold); + tlv += sizeof(struct host_cmd_tlv_frag_threshold); + } + if (bss_cfg->retry_limit <= NXPWIFI_RETRY_LIMIT_MAX) { + retry_limit = (struct host_cmd_tlv_retry_limit *)tlv; + retry_limit->header.type = + cpu_to_le16(TLV_TYPE_UAP_RETRY_LIMIT); + retry_limit->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_retry_limit) - + sizeof(struct nxpwifi_ie_types_header)); + retry_limit->limit = (u8)bss_cfg->retry_limit; + cmd_size += sizeof(struct host_cmd_tlv_retry_limit); + tlv += sizeof(struct host_cmd_tlv_retry_limit); + } + if ((bss_cfg->protocol & PROTOCOL_WPA) || + (bss_cfg->protocol & PROTOCOL_WPA2) || + (bss_cfg->protocol & PROTOCOL_EAP)) + nxpwifi_uap_bss_wpa(&tlv, cmd_buf, &cmd_size); + else + nxpwifi_uap_bss_wep(&tlv, cmd_buf, &cmd_size); + + if (bss_cfg->auth_mode <= WLAN_AUTH_SHARED_KEY || + bss_cfg->auth_mode == NXPWIFI_AUTH_MODE_AUTO) { + auth_type = (struct host_cmd_tlv_auth_type *)tlv; + auth_type->header.type = cpu_to_le16(TLV_TYPE_AUTH_TYPE); + auth_type->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_auth_type) - + sizeof(struct nxpwifi_ie_types_header)); + auth_type->auth_type = (u8)bss_cfg->auth_mode; + auth_type->pwe_derivation = 0; + auth_type->transition_disable = 0; + cmd_size += sizeof(struct host_cmd_tlv_auth_type); + tlv += sizeof(struct host_cmd_tlv_auth_type); + } + if (bss_cfg->protocol) { + encrypt_protocol = (struct host_cmd_tlv_encrypt_protocol *)tlv; + encrypt_protocol->header.type = + cpu_to_le16(TLV_TYPE_UAP_ENCRY_PROTOCOL); + encrypt_protocol->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_encrypt_protocol) + - sizeof(struct nxpwifi_ie_types_header)); + encrypt_protocol->proto = cpu_to_le16(bss_cfg->protocol); + cmd_size += sizeof(struct host_cmd_tlv_encrypt_protocol); + tlv += sizeof(struct host_cmd_tlv_encrypt_protocol); + } + + if (bss_cfg->ht_cap.cap_info) { + htcap = (struct nxpwifi_ie_types_htcap *)tlv; + htcap->header.type = cpu_to_le16(WLAN_EID_HT_CAPABILITY); + htcap->header.len = + cpu_to_le16(sizeof(struct ieee80211_ht_cap)); + htcap->ht_cap.cap_info = bss_cfg->ht_cap.cap_info; + htcap->ht_cap.ampdu_params_info = + bss_cfg->ht_cap.ampdu_params_info; + memcpy(&htcap->ht_cap.mcs, &bss_cfg->ht_cap.mcs, + sizeof(struct ieee80211_mcs_info)); + htcap->ht_cap.extended_ht_cap_info = + bss_cfg->ht_cap.extended_ht_cap_info; + htcap->ht_cap.tx_BF_cap_info = bss_cfg->ht_cap.tx_BF_cap_info; + htcap->ht_cap.antenna_selection_info = + bss_cfg->ht_cap.antenna_selection_info; + cmd_size += sizeof(struct nxpwifi_ie_types_htcap); + tlv += sizeof(struct nxpwifi_ie_types_htcap); + } + + if (priv->wmm_enabled) { + struct nxpwifi_ie_types_wmmcap *wmm_cap; + struct nxpwifi_types_wmm_info *fw_wmm; + const struct ieee80211_wmm_param_ie *ie; + + wmm_cap = (struct nxpwifi_ie_types_wmmcap *)tlv; + fw_wmm = &wmm_cap->wmm_info; + ie = &bss_cfg->wmm_element; + + wmm_cap->header.type = cpu_to_le16(WLAN_EID_VENDOR_SPECIFIC); + wmm_cap->header.len = + cpu_to_le16(sizeof(struct nxpwifi_types_wmm_info)); + + /* Map 802.11 WMM IE fields to FW WMM TLV payload */ + fw_wmm->oui[0] = ie->oui[0]; + fw_wmm->oui[1] = ie->oui[1]; + fw_wmm->oui[2] = ie->oui[2]; + fw_wmm->oui[3] = ie->oui_type; + + fw_wmm->subtype = ie->oui_subtype; + fw_wmm->version = ie->version; + fw_wmm->qos_info = ie->qos_info; + fw_wmm->reserved = ie->reserved; + + memcpy(fw_wmm->ac, ie->ac, sizeof(fw_wmm->ac)); + + cmd_size += sizeof(*wmm_cap); + tlv += sizeof(*wmm_cap); + } + + if (bss_cfg->sta_ao_timer) { + ao_timer = (struct host_cmd_tlv_ageout_timer *)tlv; + ao_timer->header.type = cpu_to_le16(TLV_TYPE_UAP_AO_TIMER); + ao_timer->header.len = cpu_to_le16(sizeof(*ao_timer) - + sizeof(struct nxpwifi_ie_types_header)); + ao_timer->sta_ao_timer = cpu_to_le32(bss_cfg->sta_ao_timer); + cmd_size += sizeof(*ao_timer); + tlv += sizeof(*ao_timer); + } + + if (bss_cfg->power_constraint) { + pwr_ct = (void *)tlv; + pwr_ct->header.type = cpu_to_le16(TLV_TYPE_PWR_CONSTRAINT); + pwr_ct->header.len = cpu_to_le16(sizeof(u8)); + pwr_ct->constraint = bss_cfg->power_constraint; + cmd_size += sizeof(*pwr_ct); + tlv += sizeof(*pwr_ct); + } + + if (bss_cfg->ps_sta_ao_timer) { + ps_ao_timer = (struct host_cmd_tlv_ageout_timer *)tlv; + ps_ao_timer->header.type = + cpu_to_le16(TLV_TYPE_UAP_PS_AO_TIMER); + ps_ao_timer->header.len = cpu_to_le16(sizeof(*ps_ao_timer) - + sizeof(struct nxpwifi_ie_types_header)); + ps_ao_timer->sta_ao_timer = + cpu_to_le32(bss_cfg->ps_sta_ao_timer); + cmd_size += sizeof(*ps_ao_timer); + tlv += sizeof(*ps_ao_timer); + } + + *param_size = cmd_size; + + return 0; +} + +/* Parse custom IEs and write them to the command buffer. */ +static int nxpwifi_uap_custom_ie_prepare(u8 *tlv, void *cmd_buf, u16 *ie_size) +{ + struct nxpwifi_ie_list *ap_ie = cmd_buf; + struct nxpwifi_ie_types_header *tlv_ie = (void *)tlv; + + if (!ap_ie || !ap_ie->len) + return -EINVAL; + + *ie_size += le16_to_cpu(ap_ie->len) + + sizeof(struct nxpwifi_ie_types_header); + + tlv_ie->type = cpu_to_le16(TLV_TYPE_MGMT_IE); + tlv_ie->len = ap_ie->len; + tlv += sizeof(struct nxpwifi_ie_types_header); + + memcpy(tlv, ap_ie->ie_list, le16_to_cpu(ap_ie->len)); + + return 0; +} + +static int +nxpwifi_cmd_uap_sys_config(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + u8 *tlv; + u16 cmd_size, param_size, ie_size; + struct host_cmd_ds_sys_config *sys_cfg; + int ret = 0; + + cmd->command = cpu_to_le16(HOST_CMD_UAP_SYS_CONFIG); + cmd_size = (u16)(sizeof(struct host_cmd_ds_sys_config) + S_DS_GEN); + sys_cfg = &cmd->params.uap_sys_config; + sys_cfg->action = cpu_to_le16(cmd_action); + tlv = sys_cfg->tlv; + + switch (cmd_type) { + case UAP_BSS_PARAMS_I: + param_size = cmd_size; + ret = nxpwifi_uap_bss_param_prepare(priv, tlv, data_buf, ¶m_size); + if (ret) + return ret; + cmd->size = cpu_to_le16(param_size); + break; + case UAP_CUSTOM_IE_I: + ie_size = cmd_size; + ret = nxpwifi_uap_custom_ie_prepare(tlv, data_buf, &ie_size); + if (ret) + return ret; + cmd->size = cpu_to_le16(ie_size); + break; + default: + return -EINVAL; + } + + return ret; +} + +static int +nxpwifi_cmd_uap_bss_start(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_ie_types_host_mlme *tlv; + int size; + + cmd->command = cpu_to_le16(HOST_CMD_UAP_BSS_START); + size = S_DS_GEN; + + tlv = (struct nxpwifi_ie_types_host_mlme *)((u8 *)cmd + size); + tlv->header.type = cpu_to_le16(TLV_TYPE_HOST_MLME); + tlv->header.len = cpu_to_le16(sizeof(tlv->host_mlme)); + tlv->host_mlme = 1; + size += sizeof(struct nxpwifi_ie_types_host_mlme); + + cmd->size = cpu_to_le16(size); + + return 0; +} + +static int +nxpwifi_ret_uap_bss_start(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->tx_lock_flag = false; + adapter->pps_uapsd_mode = false; + adapter->delay_null_pkt = false; + priv->bss_started = 1; + + return 0; +} + +static int +nxpwifi_ret_uap_bss_stop(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + priv->bss_started = 0; + + return 0; +} + +static int nxpwifi_ret_apcmd_sta_list(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, void *data_buf) +{ + struct host_cmd_ds_sta_list *sta_list = &resp->params.sta_list; + struct nxpwifi_ie_types_sta_info *sta_info; + struct nxpwifi_sta_node *sta_node; + u16 sta_count; + u32 resp_size; + u32 base; + u32 required_size; + int i; + + resp_size = le16_to_cpu(resp->size); + sta_count = le16_to_cpu(sta_list->sta_count); + + /* End of fixed fields before sta_list.tlv[] */ + base = offsetofend(struct host_cmd_ds_command, params.sta_list.sta_count); + + /* At least fixed fields must be present */ + if (resp_size < base) + return -EINVAL; + + required_size = base + sta_count * sizeof(*sta_info); + + /* Verify firmware did not claim more entries than the buffer holds */ + if (resp_size < required_size) + return -EINVAL; + + sta_info = (void *)sta_list->tlv; + + rcu_read_lock(); + for (i = 0; i < sta_count; i++) { + sta_node = nxpwifi_get_sta_entry(priv, sta_info->mac); + if (unlikely(!sta_node)) { + sta_info++; + continue; + } + + sta_node->stats.rssi = sta_info->rssi; + sta_info++; + } + rcu_read_unlock(); + + return 0; +} + +/* Build AP deauth command for the given MAC address. */ +static int nxpwifi_cmd_uap_sta_deauth(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_sta_deauth *sta_deauth = &cmd->params.sta_deauth; + u8 *mac = (u8 *)data_buf; + + cmd->command = cpu_to_le16(HOST_CMD_UAP_STA_DEAUTH); + memcpy(sta_deauth->mac, mac, ETH_ALEN); + sta_deauth->reason = cpu_to_le16(WLAN_REASON_DEAUTH_LEAVING); + + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_sta_deauth) + + S_DS_GEN); + return 0; +} + +static int +nxpwifi_cmd_uap_chan_report_request(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_issue_chan_report_request(priv, cmd, data_buf); +} + +/* Build AP add-station command. */ +static int +nxpwifi_cmd_uap_add_new_station(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_add_station *new_sta = &cmd->params.sta_info; + struct nxpwifi_sta_info *add_sta = (struct nxpwifi_sta_info *)data_buf; + struct station_parameters *params = add_sta->params; + struct nxpwifi_sta_node *sta_ptr; + u16 cmd_size; + u8 *pos, *cmd_end; + u16 tlv_len; + struct nxpwifi_ie_types_sta_flag *sta_flag; + int i; + + cmd->command = cpu_to_le16(HOST_CMD_ADD_NEW_STATION); + new_sta->action = cpu_to_le16(cmd_action); + cmd_size = sizeof(struct host_cmd_ds_add_station) + S_DS_GEN; + + if (cmd_action == HOST_ACT_ADD_STA) + sta_ptr = nxpwifi_add_sta_entry(priv, add_sta->peer_mac); + else + sta_ptr = nxpwifi_get_sta_entry_rcu(priv, add_sta->peer_mac); + + if (!sta_ptr) + return -EINVAL; + + memcpy(new_sta->peer_mac, add_sta->peer_mac, ETH_ALEN); + + if (cmd_action == HOST_ACT_REMOVE_STA) { + cmd->size = cpu_to_le16(cmd_size); + return 0; + } + + new_sta->aid = cpu_to_le16(params->aid); + new_sta->listen_interval = cpu_to_le32(params->listen_interval); + new_sta->cap_info = cpu_to_le16(params->capability); + + pos = new_sta->tlv; + cmd_end = (u8 *)cmd; + cmd_end += (NXPWIFI_SIZE_OF_CMD_BUFFER - 1); + + if (params->sta_flags_set & NL80211_STA_FLAG_WME) + sta_ptr->is_wmm_enabled = 1; + sta_flag = (struct nxpwifi_ie_types_sta_flag *)pos; + sta_flag->header.type = cpu_to_le16(TLV_TYPE_UAP_STA_FLAGS); + sta_flag->header.len = cpu_to_le16(sizeof(__le32)); + sta_flag->sta_flags = cpu_to_le32(params->sta_flags_set); + pos += sizeof(struct nxpwifi_ie_types_sta_flag); + cmd_size += sizeof(struct nxpwifi_ie_types_sta_flag); + + if (params->ext_capab_len) { + u8 *data = (u8 *)params->ext_capab; + u16 len = params->ext_capab_len; + + tlv_len = nxpwifi_append_data_tlv(WLAN_EID_EXT_CAPABILITY, + data, len, pos, cmd_end); + if (!tlv_len) + return -EINVAL; + pos += tlv_len; + cmd_size += tlv_len; + } + + if (params->link_sta_params.supported_rates_len) { + u8 *data = (u8 *)params->link_sta_params.supported_rates; + u16 len = params->link_sta_params.supported_rates_len; + + tlv_len = nxpwifi_append_data_tlv(WLAN_EID_SUPP_RATES, + data, len, pos, cmd_end); + if (!tlv_len) + return -EINVAL; + pos += tlv_len; + cmd_size += tlv_len; + } + + if (params->uapsd_queues || params->max_sp) { + u8 qos_capability = params->uapsd_queues | (params->max_sp << 5); + u8 *data = &qos_capability; + u16 len = sizeof(u8); + + tlv_len = nxpwifi_append_data_tlv(WLAN_EID_QOS_CAPA, + data, len, pos, cmd_end); + if (!tlv_len) + return -EINVAL; + pos += tlv_len; + cmd_size += tlv_len; + sta_ptr->is_wmm_enabled = 1; + } + + if (params->link_sta_params.ht_capa) { + u8 *data = (u8 *)params->link_sta_params.ht_capa; + u16 len = sizeof(struct ieee80211_ht_cap); + + tlv_len = nxpwifi_append_data_tlv(WLAN_EID_HT_CAPABILITY, + data, len, pos, cmd_end); + if (!tlv_len) + return -EINVAL; + pos += tlv_len; + cmd_size += tlv_len; + sta_ptr->is_11n_enabled = 1; + sta_ptr->max_amsdu = + le16_to_cpu(params->link_sta_params.ht_capa->cap_info) & + IEEE80211_HT_CAP_MAX_AMSDU ? + NXPWIFI_TX_DATA_BUF_SIZE_8K : + NXPWIFI_TX_DATA_BUF_SIZE_4K; + } + + if (params->link_sta_params.vht_capa) { + u8 *data = (u8 *)params->link_sta_params.vht_capa; + u16 len = sizeof(struct ieee80211_vht_cap); + + tlv_len = nxpwifi_append_data_tlv(WLAN_EID_VHT_CAPABILITY, + data, len, pos, cmd_end); + if (!tlv_len) + return -EINVAL; + pos += tlv_len; + cmd_size += tlv_len; + sta_ptr->is_11ac_enabled = 1; + } + + if (params->link_sta_params.opmode_notif_used) { + u8 *data = ¶ms->link_sta_params.opmode_notif; + u16 len = sizeof(u8); + + tlv_len = nxpwifi_append_data_tlv(WLAN_EID_OPMODE_NOTIF, + data, len, pos, cmd_end); + if (!tlv_len) + return -EINVAL; + pos += tlv_len; + cmd_size += tlv_len; + } + + if (params->link_sta_params.he_capa_len) { + u8 *data = (u8 *)params->link_sta_params.he_capa; + u16 len = params->link_sta_params.he_capa_len; + + tlv_len = nxpwifi_append_data_tlv(WLAN_EID_EXT_HE_CAPABILITY, + data, len, pos, cmd_end); + if (!tlv_len) + return -EINVAL; + pos += tlv_len; + cmd_size += tlv_len; + sta_ptr->is_11ax_enabled = 1; + } + + for (i = 0; i < MAX_NUM_TID; i++) { + if (sta_ptr->is_11n_enabled || sta_ptr->is_11ax_enabled) + sta_ptr->ampdu_sta[i] = + priv->aggr_prio_tbl[i].ampdu_user; + else + sta_ptr->ampdu_sta[i] = BA_STREAM_NOT_ALLOWED; + } + + memset(sta_ptr->rx_seq, 0xff, sizeof(sta_ptr->rx_seq)); + + cmd->size = cpu_to_le16(cmd_size); + + return 0; +} + +static const struct nxpwifi_cmd_entry cmd_table_uap[] = { + {.cmd_no = HOST_CMD_APCMD_SYS_RESET, + .prepare_cmd = nxpwifi_cmd_fill_head_only, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_UAP_SYS_CONFIG, + .prepare_cmd = nxpwifi_cmd_uap_sys_config, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_UAP_BSS_START, + .prepare_cmd = nxpwifi_cmd_uap_bss_start, + .cmd_resp = nxpwifi_ret_uap_bss_start}, + {.cmd_no = HOST_CMD_UAP_BSS_STOP, + .prepare_cmd = nxpwifi_cmd_fill_head_only, + .cmd_resp = nxpwifi_ret_uap_bss_stop}, + {.cmd_no = HOST_CMD_APCMD_STA_LIST, + .prepare_cmd = nxpwifi_cmd_fill_head_only, + .cmd_resp = nxpwifi_ret_apcmd_sta_list}, + {.cmd_no = HOST_CMD_UAP_STA_DEAUTH, + .prepare_cmd = nxpwifi_cmd_uap_sta_deauth, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_CHAN_REPORT_REQUEST, + .prepare_cmd = nxpwifi_cmd_uap_chan_report_request, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_ADD_NEW_STATION, + .prepare_cmd = nxpwifi_cmd_uap_add_new_station, + .cmd_resp = NULL}, +}; + +/* Prepare AP commands and dispatch to per-cmd builders before sending to firmware. */ +int nxpwifi_uap_prepare_cmd(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node, + u16 cmd_action, u32 type) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u16 cmd_no = cmd_node->cmd_no; + struct host_cmd_ds_command *cmd = + (struct host_cmd_ds_command *)cmd_node->skb->data; + void *data_buf = cmd_node->data_buf; + int i, ret = -EINVAL; + + for (i = 0; i < ARRAY_SIZE(cmd_table_uap); i++) { + if (cmd_no == cmd_table_uap[i].cmd_no) { + if (cmd_table_uap[i].prepare_cmd) + ret = cmd_table_uap[i].prepare_cmd(priv, cmd, + cmd_no, + data_buf, + cmd_action, + type); + cmd_node->cmd_resp = cmd_table_uap[i].cmd_resp; + break; + } + } + + if (i == ARRAY_SIZE(cmd_table_uap)) + nxpwifi_dbg(adapter, ERROR, + "%s: unknown command: %#x\n", + __func__, cmd_no); + else + nxpwifi_dbg(adapter, CMD, + "%s: command: %#x\n", + __func__, cmd_no); + + return ret; +} + +/* Translate cfg80211_ap_settings security into bss_config for firmware. */ +int nxpwifi_set_secure_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_config, + struct cfg80211_ap_settings *params) +{ + int i; + struct nxpwifi_wep_key wep_key; + + if (!params->privacy) { + bss_config->protocol = PROTOCOL_NO_SECURITY; + bss_config->key_mgmt = KEY_MGMT_NONE; + bss_config->wpa_cfg.length = 0; + priv->sec_info.wep_enabled = 0; + priv->sec_info.wpa_enabled = 0; + priv->sec_info.wpa2_enabled = 0; + + return 0; + } + + switch (params->auth_type) { + case NL80211_AUTHTYPE_OPEN_SYSTEM: + bss_config->auth_mode = WLAN_AUTH_OPEN; + break; + case NL80211_AUTHTYPE_SHARED_KEY: + bss_config->auth_mode = WLAN_AUTH_SHARED_KEY; + break; + case NL80211_AUTHTYPE_NETWORK_EAP: + bss_config->auth_mode = WLAN_AUTH_LEAP; + break; + default: + bss_config->auth_mode = NXPWIFI_AUTH_MODE_AUTO; + break; + } + + bss_config->key_mgmt_operation |= KEY_MGMT_ON_HOST; + + bss_config->protocol = 0; + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_1) + bss_config->protocol |= PROTOCOL_WPA; + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_2) + bss_config->protocol |= PROTOCOL_WPA2; + + bss_config->key_mgmt = 0; + for (i = 0; i < params->crypto.n_akm_suites; i++) { + switch (params->crypto.akm_suites[i]) { + case WLAN_AKM_SUITE_8021X: + bss_config->key_mgmt |= KEY_MGMT_EAP; + break; + case WLAN_AKM_SUITE_PSK: + bss_config->key_mgmt |= KEY_MGMT_PSK; + break; + case WLAN_AKM_SUITE_PSK_SHA256: + bss_config->key_mgmt |= KEY_MGMT_PSK_SHA256; + break; + case WLAN_AKM_SUITE_OWE: + bss_config->key_mgmt |= KEY_MGMT_OWE; + break; + case WLAN_AKM_SUITE_SAE: + bss_config->key_mgmt |= KEY_MGMT_SAE; + break; + default: + break; + } + } + + for (i = 0; i < params->crypto.n_ciphers_pairwise; i++) { + switch (params->crypto.ciphers_pairwise[i]) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + break; + case WLAN_CIPHER_SUITE_TKIP: + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_1) + bss_config->wpa_cfg.pairwise_cipher_wpa |= + CIPHER_TKIP; + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_2) + bss_config->wpa_cfg.pairwise_cipher_wpa2 |= + CIPHER_TKIP; + break; + case WLAN_CIPHER_SUITE_CCMP: + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_1) + bss_config->wpa_cfg.pairwise_cipher_wpa |= + CIPHER_AES_CCMP; + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_2) + bss_config->wpa_cfg.pairwise_cipher_wpa2 |= + CIPHER_AES_CCMP; + break; + default: + break; + } + } + + switch (params->crypto.cipher_group) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + if (priv->sec_info.wep_enabled) { + bss_config->protocol = PROTOCOL_STATIC_WEP; + bss_config->key_mgmt = KEY_MGMT_NONE; + bss_config->wpa_cfg.length = 0; + + for (i = 0; i < NUM_WEP_KEYS; i++) { + wep_key = priv->wep_key[i]; + bss_config->wep_cfg[i].key_index = i; + + if (priv->wep_key_curr_index == i) + bss_config->wep_cfg[i].is_default = 1; + else + bss_config->wep_cfg[i].is_default = 0; + + bss_config->wep_cfg[i].length = + wep_key.key_length; + memcpy(&bss_config->wep_cfg[i].key, + &wep_key.key_material, + wep_key.key_length); + } + } + break; + case WLAN_CIPHER_SUITE_TKIP: + bss_config->wpa_cfg.group_cipher = CIPHER_TKIP; + break; + case WLAN_CIPHER_SUITE_CCMP: + bss_config->wpa_cfg.group_cipher = CIPHER_AES_CCMP; + break; + default: + break; + } + + return 0; +} + +/* Update 11n HT params from beacon and fill bss_config. */ +void +nxpwifi_set_ht_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + const u8 *ht_ie; + + if (!ISSUPP_11NENABLED(priv->adapter->fw_cap_info)) + return; + + ht_ie = cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, params->beacon.tail, + params->beacon.tail_len); + if (ht_ie) { + memcpy(&bss_cfg->ht_cap, ht_ie + 2, + sizeof(struct ieee80211_ht_cap)); + if (ISSUPP_BEAMFORMING(priv->adapter->hw_dot_11n_dev_cap)) + bss_cfg->ht_cap.tx_BF_cap_info = + cpu_to_le32(NXPWIFI_DEF_11N_TX_BF_CAP); + priv->ap_11n_enabled = 1; + } else { + memset(&bss_cfg->ht_cap, 0, sizeof(struct ieee80211_ht_cap)); + bss_cfg->ht_cap.cap_info = cpu_to_le16(NXPWIFI_DEF_HT_CAP); + bss_cfg->ht_cap.ampdu_params_info = NXPWIFI_DEF_AMPDU; + } +} + +/* Update 11ac VHT params from beacon and fill bss_config. */ +void nxpwifi_set_vht_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + const u8 *vht_ie; + + vht_ie = cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY, params->beacon.tail, + params->beacon.tail_len); + if (vht_ie) { + memcpy(&bss_cfg->vht_cap, vht_ie + 2, + sizeof(struct ieee80211_vht_cap)); + priv->ap_11ac_enabled = 1; + } else { + priv->ap_11ac_enabled = 0; + } +} + +/* Extract TPC request from beacon and set power_constraint. */ +void nxpwifi_set_tpc_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + const u8 *tpc_ie; + + tpc_ie = cfg80211_find_ie(WLAN_EID_TPC_REQUEST, params->beacon.tail, + params->beacon.tail_len); + if (tpc_ie) + bss_cfg->power_constraint = *(tpc_ie + 2); + else + bss_cfg->power_constraint = 0; +} + +/* Enable VHT only when VHT IE is present; otherwise disable VHT. */ +void nxpwifi_set_vht_width(struct nxpwifi_private *priv, + enum nl80211_chan_width width, + bool ap_11ac_enable) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_11ac_vht_cfg vht_cfg; + + vht_cfg.band_config = VHT_CFG_5GHZ; + vht_cfg.cap_info = adapter->hw_dot_11ac_dev_cap; + + if (!ap_11ac_enable) { + vht_cfg.mcs_tx_set = DISABLE_VHT_MCS_SET; + vht_cfg.mcs_rx_set = DISABLE_VHT_MCS_SET; + } else { + vht_cfg.mcs_tx_set = DEFAULT_VHT_MCS_SET; + vht_cfg.mcs_rx_set = DEFAULT_VHT_MCS_SET; + } + + vht_cfg.misc_config = VHT_CAP_UAP_ONLY; + + if (ap_11ac_enable && width >= NL80211_CHAN_WIDTH_80) + vht_cfg.misc_config |= VHT_BW_80_160_80P80; + + nxpwifi_send_cmd(priv, HOST_CMD_11AC_CFG, + HOST_ACT_GEN_SET, 0, &vht_cfg, true); +} + +bool nxpwifi_check_11ax_capability(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u8 band = bss_cfg->band_cfg & BAND_CFG_CHAN_BAND_MASK; + + if (band == BAND_2GHZ && + !(adapter->fw_bands & BAND_GAX)) + return false; + + if (band == BAND_5GHZ && + !(adapter->fw_bands & BAND_AAX)) + return false; + + if (params->he_cap) + return true; + else + return false; +} + +int nxpwifi_set_11ax_status(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + struct nxpwifi_11ax_he_cfg ax_cfg; + u8 band = bss_cfg->band_cfg & BAND_CFG_CHAN_BAND_MASK; + const struct element *he_cap; + int ret; + + if (band == BAND_2GHZ) + ax_cfg.band = BIT(0); + else if (band == BAND_5GHZ) + ax_cfg.band = BIT(1); + else + return -EINVAL; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_11AX_CFG, + HOST_ACT_GEN_GET, 0, &ax_cfg, true); + if (ret) + return ret; + + he_cap = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_CAPABILITY, + params->beacon.tail, + params->beacon.tail_len); + + if (he_cap) { + ax_cfg.he_cap_cfg.id = he_cap->id; + ax_cfg.he_cap_cfg.len = he_cap->datalen; + if (params->twt_responder == 0) { + struct nxpwifi_11ax_he_cap_cfg *he_cap_cfg = + (struct nxpwifi_11ax_he_cap_cfg *)he_cap; + + he_cap_cfg->cap_elem.mac_cap_info[0] &= + ~HE_MAC_CAP_TWT_RESP_SUPPORT; + } + memcpy(ax_cfg.data + 4, + he_cap->data, + he_cap->datalen); + } else { + /* disable */ + if (ax_cfg.he_cap_cfg.len && + ax_cfg.he_cap_cfg.ext_id == WLAN_EID_EXT_HE_CAPABILITY) { + memset(ax_cfg.he_cap_cfg.he_txrx_mcs_support, 0xff, + sizeof(ax_cfg.he_cap_cfg.he_txrx_mcs_support)); + } + } + + return nxpwifi_send_cmd(priv, HOST_CMD_11AX_CFG, + HOST_ACT_GEN_SET, 0, &ax_cfg, true); +} + +/* Copy supported rates from beacon into bss_config. */ +void +nxpwifi_set_uap_rates(struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + struct element *rate_ie; + int var_offset = offsetof(struct ieee80211_mgmt, u.beacon.variable); + const u8 *var_pos = params->beacon.head + var_offset; + int len = params->beacon.head_len - var_offset; + u8 rate_len = 0; + + rate_ie = (void *)cfg80211_find_ie(WLAN_EID_SUPP_RATES, var_pos, len); + if (rate_ie) { + if (rate_ie->datalen > NXPWIFI_SUPPORTED_RATES) + return; + memcpy(bss_cfg->rates, rate_ie + 1, rate_ie->datalen); + rate_len = rate_ie->datalen; + } + + rate_ie = (void *)cfg80211_find_ie(WLAN_EID_EXT_SUPP_RATES, + params->beacon.tail, + params->beacon.tail_len); + if (rate_ie) { + if (rate_ie->datalen > NXPWIFI_SUPPORTED_RATES - rate_len) + return; + memcpy(bss_cfg->rates + rate_len, + rate_ie + 1, rate_ie->datalen); + } +} + +/* + * Initialize bss_config fields to sentinel values. + * Fields left with sentinel values are treated as unset and will not be + * included in the corresponding firmware command. + */ +void nxpwifi_set_sys_config_invalid_data(struct nxpwifi_uap_bss_param *config) +{ + config->radio_ctl = __NXPWIFI_RADIO_CTL_MAX; + config->dtim_period = NXPWIFI_INVALID_DTIM_PERIOD; + config->beacon_period = NXPWIFI_INVALID_BEACON_PERIOD; + config->auth_mode = NXPWIFI_AUTH_MODE_AUTO; + config->rts_threshold = NXPWIFI_INVALID_RTS; + config->frag_threshold = NXPWIFI_INVALID_FRAG; + config->retry_limit = NXPWIFI_INVALID_RETRY_LIMI; +} + +/* Parse WMM params from cfg80211_ap_settings and update bss_config. */ +void +nxpwifi_set_wmm_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + const u8 *vendor_ie; + const struct ieee80211_wmm_param_ie *wmm; + u8 ie_len; + + vendor_ie = cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WMM, + params->beacon.tail, + params->beacon.tail_len); + if (!vendor_ie) + goto no_wmm; + + ie_len = vendor_ie[1]; + + if (ie_len < sizeof(struct ieee80211_wmm_param_ie) - 2) + goto no_wmm; + + wmm = (const struct ieee80211_wmm_param_ie *)vendor_ie; + + if (memcmp(wmm->oui, "\x00\x50\xf2", 3)) + goto no_wmm; + + if (wmm->oui_type != WLAN_OUI_TYPE_MICROSOFT_WMM) + goto no_wmm; + + if (wmm->oui_subtype != 1) + goto no_wmm; + + /* Only WMM version 1 is supported */ + if (wmm->version != 1) + goto no_wmm; + + memcpy(&bss_cfg->wmm_element, wmm, + sizeof(struct ieee80211_wmm_param_ie)); + + priv->wmm_enabled = true; + return; + +no_wmm: + memset(&bss_cfg->wmm_element, 0, sizeof(bss_cfg->wmm_element)); + priv->wmm_enabled = false; +} + +/* Enable 11d when country IE is present. */ +void nxpwifi_config_uap_11d(struct nxpwifi_private *priv, + struct cfg80211_beacon_data *beacon_data) +{ + enum state_11d_t state_11d; + const u8 *country_ie; + + country_ie = cfg80211_find_ie(WLAN_EID_COUNTRY, beacon_data->tail, + beacon_data->tail_len); + if (country_ie) { + /* Send cmd to FW to enable 11D function */ + state_11d = ENABLE_11D; + if (nxpwifi_send_cmd(priv, HOST_CMD_802_11_SNMP_MIB, + HOST_ACT_GEN_SET, DOT11D_I, + &state_11d, true)) { + nxpwifi_dbg(priv->adapter, ERROR, + "11D: failed to enable 11D\n"); + } + } +} + +void nxpwifi_uap_set_channel(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_chan_def chandef) +{ + u8 config_bands = 0, old_bands = priv->config_bands; + + priv->bss_chandef = chandef; + + bss_cfg->channel = + ieee80211_frequency_to_channel(chandef.chan->center_freq); + + nxpwifi_convert_chan_to_band_cfg(priv, &bss_cfg->band_cfg, &chandef); + + /* Set appropriate bands */ + if (chandef.chan->band == NL80211_BAND_2GHZ) { + config_bands = BAND_B | BAND_G; + if (chandef.width > NL80211_CHAN_WIDTH_20_NOHT) + config_bands |= BAND_GN | BAND_GAX; + } else { + config_bands = BAND_A; + if (chandef.width > NL80211_CHAN_WIDTH_20_NOHT) + config_bands |= BAND_AN; + if (chandef.width > NL80211_CHAN_WIDTH_40) + config_bands |= BAND_AAC | BAND_AAX; + } + + priv->config_bands = config_bands; + + if (old_bands != config_bands) { + if (nxpwifi_band_to_radio_type(priv->config_bands) == + HOST_SCAN_RADIO_TYPE_BG) + nxpwifi_send_domain_info_cmd_fw(priv->adapter->wiphy, + NL80211_BAND_2GHZ); + else + nxpwifi_send_domain_info_cmd_fw(priv->adapter->wiphy, + NL80211_BAND_5GHZ); + } +} + +int nxpwifi_config_start_uap(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg) +{ + int ret; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_UAP_SYS_CONFIG, + HOST_ACT_GEN_SET, + UAP_BSS_PARAMS_I, bss_cfg, true); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to set AP configuration\n"); + return ret; + } + + ret = nxpwifi_send_cmd(priv, HOST_CMD_UAP_BSS_START, + HOST_ACT_GEN_SET, 0, NULL, true); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to start the BSS\n"); + return ret; + } + + if (priv->sec_info.wep_enabled) + priv->curr_pkt_filter |= HOST_ACT_MAC_WEP_ENABLE; + else + priv->curr_pkt_filter &= ~HOST_ACT_MAC_WEP_ENABLE; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_MAC_CONTROL, + HOST_ACT_GEN_SET, 0, + &priv->curr_pkt_filter, true); + + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/uap_event.c b/drivers/net/wireless/nxp/nxpwifi/uap_event.c new file mode 100644 index 000000000000..ed8e24ae9c0a --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/uap_event.c @@ -0,0 +1,488 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: AP event handling + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "main.h" +#include "cmdevt.h" +#include "11n.h" + +#define NXPWIFI_BSS_START_EVT_FIX_SIZE 12 + +static int +nxpwifi_uap_event_ps_awake(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (!adapter->pps_uapsd_mode && + priv->media_connected && adapter->sleep_period.period) { + adapter->pps_uapsd_mode = true; + nxpwifi_dbg(adapter, EVENT, + "event: PPS/UAPSD mode activated\n"); + } + adapter->tx_lock_flag = false; + if (adapter->pps_uapsd_mode && adapter->gen_null_pkt) { + if (nxpwifi_check_last_packet_indication(priv)) { + if (adapter->data_sent) { + adapter->ps_state = PS_STATE_AWAKE; + adapter->pm_wakeup_card_req = false; + adapter->pm_wakeup_fw_try = false; + } else { + if (!nxpwifi_send_null_packet + (priv, + NXPWIFI_TxPD_POWER_MGMT_NULL_PACKET | + NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET)) + adapter->ps_state = PS_STATE_SLEEP; + } + + return 0; + } + } + + adapter->ps_state = PS_STATE_AWAKE; + adapter->pm_wakeup_card_req = false; + adapter->pm_wakeup_fw_try = false; + + return 0; +} + +static int +nxpwifi_uap_event_ps_sleep(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->ps_state = PS_STATE_PRE_SLEEP; + nxpwifi_check_ps_cond(adapter); + + return 0; +} + +static int +nxpwifi_uap_event_sta_deauth(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u8 *deauth_mac; + + deauth_mac = adapter->event_body + + NXPWIFI_UAP_EVENT_EXTRA_HEADER; + cfg80211_del_sta(priv->netdev->ieee80211_ptr, deauth_mac, GFP_KERNEL); + + if (priv->ap_11n_enabled) { + nxpwifi_11n_del_rx_reorder_tbl_by_ta(priv, deauth_mac); + nxpwifi_del_tx_ba_stream_tbl_by_ra(priv, deauth_mac); + } + nxpwifi_wmm_del_peer_ra_list(priv, deauth_mac); + + return 0; +} + +static int +nxpwifi_uap_event_sta_assoc(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct station_info *sinfo; + struct nxpwifi_assoc_event *event; + struct nxpwifi_sta_node *node; + int len, i; + + sinfo = kzalloc_obj(*sinfo, GFP_KERNEL); + if (!sinfo) + return -ENOMEM; + + event = (struct nxpwifi_assoc_event *) + (adapter->event_body + NXPWIFI_UAP_EVENT_EXTRA_HEADER); + if (le16_to_cpu(event->type) == TLV_TYPE_UAP_MGMT_FRAME) { + len = -1; + + if (ieee80211_is_assoc_req(event->frame_control)) + len = 0; + else if (ieee80211_is_reassoc_req(event->frame_control)) + /* + * There will be ETH_ALEN bytes of + * current_ap_addr before the re-assoc ies. + */ + len = ETH_ALEN; + + if (len != -1) { + sinfo->assoc_req_ies = &event->data[len]; + len = (u8 *)sinfo->assoc_req_ies - + (u8 *)&event->frame_control; + sinfo->assoc_req_ies_len = + le16_to_cpu(event->len) - (u16)len; + } + } + cfg80211_new_sta(priv->netdev->ieee80211_ptr, event->sta_addr, sinfo, + GFP_KERNEL); + + node = nxpwifi_add_sta_entry(priv, event->sta_addr); + if (!node) { + nxpwifi_dbg(adapter, ERROR, + "could not create station entry!\n"); + kfree(sinfo); + return -ENOENT; + } + + if (!priv->ap_11n_enabled) { + kfree(sinfo); + return 0; + } + + nxpwifi_set_sta_ht_cap(priv, sinfo->assoc_req_ies, + sinfo->assoc_req_ies_len, node); + + for (i = 0; i < MAX_NUM_TID; i++) { + if (node->is_11n_enabled || node->is_11ax_enabled) + node->ampdu_sta[i] = + priv->aggr_prio_tbl[i].ampdu_user; + else + node->ampdu_sta[i] = BA_STREAM_NOT_ALLOWED; + } + memset(node->rx_seq, 0xff, sizeof(node->rx_seq)); + kfree(sinfo); + + return 0; +} + +static int +nxpwifi_check_uap_capabilities(struct nxpwifi_private *priv, + struct sk_buff *event) +{ + int evt_len; + u8 *curr; + u16 tlv_len; + struct nxpwifi_ie_types_data *tlv_hdr; + struct ieee80211_wmm_param_ie *wmm_param_ie = NULL; + int mask = IEEE80211_WMM_IE_AP_QOSINFO_PARAM_SET_CNT_MASK; + + priv->wmm_enabled = false; + skb_pull(event, NXPWIFI_BSS_START_EVT_FIX_SIZE); + evt_len = event->len; + curr = event->data; + + nxpwifi_dbg_dump(priv->adapter, EVT_D, "uap capabilities:", + event->data, event->len); + + skb_push(event, NXPWIFI_BSS_START_EVT_FIX_SIZE); + + while ((evt_len >= sizeof(tlv_hdr->header))) { + tlv_hdr = (struct nxpwifi_ie_types_data *)curr; + tlv_len = le16_to_cpu(tlv_hdr->header.len); + + if (evt_len < tlv_len + sizeof(tlv_hdr->header)) + break; + + switch (le16_to_cpu(tlv_hdr->header.type)) { + case WLAN_EID_HT_CAPABILITY: + priv->ap_11n_enabled = true; + break; + + case WLAN_EID_VHT_CAPABILITY: + priv->ap_11ac_enabled = true; + break; + + case WLAN_EID_VENDOR_SPECIFIC: + /* + * Point the regular IEEE element 2 bytes into the NXP element + * and setup the IEEE element type and length byte fields + */ + wmm_param_ie = (void *)(curr + 2); + wmm_param_ie->len = (u8)tlv_len; + wmm_param_ie->element_id = + WLAN_EID_VENDOR_SPECIFIC; + nxpwifi_dbg(priv->adapter, EVENT, + "info: check uap capabilities:\t" + "wmm parameter set count: %d\n", + wmm_param_ie->qos_info & mask); + + nxpwifi_wmm_setup_ac_downgrade(priv); + priv->wmm_enabled = true; + nxpwifi_wmm_setup_queue_priorities(priv, wmm_param_ie); + break; + + default: + break; + } + + curr += (tlv_len + sizeof(tlv_hdr->header)); + evt_len -= (tlv_len + sizeof(tlv_hdr->header)); + } + + return 0; +} + +static int +nxpwifi_uap_event_bss_start(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + priv->port_open = false; + eth_hw_addr_set(priv->netdev, adapter->event_body + 2); + if (priv->hist_data) + nxpwifi_hist_data_reset(priv); + return nxpwifi_check_uap_capabilities(priv, adapter->event_skb); +} + +static int +nxpwifi_uap_event_addba(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (priv->media_connected) + nxpwifi_send_cmd(priv, HOST_CMD_11N_ADDBA_RSP, + HOST_ACT_GEN_SET, 0, + adapter->event_body, false); + + return 0; +} + +static int +nxpwifi_uap_event_delba(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (priv->media_connected) + nxpwifi_11n_delete_ba_stream(priv, adapter->event_body); + + return 0; +} + +static int +nxpwifi_uap_event_ba_stream_timeout(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_11n_batimeout *ba_timeout; + + if (priv->media_connected) { + ba_timeout = (void *)adapter->event_body; + nxpwifi_11n_ba_stream_timeout(priv, ba_timeout); + } + + return 0; +} + +static int +nxpwifi_uap_event_amsdu_aggr_ctrl(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u16 ctrl; + + ctrl = get_unaligned_le16(adapter->event_body); + nxpwifi_dbg(adapter, EVENT, + "event: AMSDU_AGGR_CTRL %d\n", ctrl); + + if (priv->media_connected) { + adapter->tx_buf_size = + min_t(u16, adapter->curr_tx_buf_size, ctrl); + nxpwifi_dbg(adapter, EVENT, + "event: tx_buf_size %d\n", + adapter->tx_buf_size); + } + + return 0; +} + +static int +nxpwifi_uap_event_bss_idle(struct nxpwifi_private *priv) +{ + priv->media_connected = false; + priv->port_open = false; + nxpwifi_clean_txrx(priv); + nxpwifi_del_all_sta_list(priv); + + return 0; +} + +static int +nxpwifi_uap_event_bss_active(struct nxpwifi_private *priv) +{ + priv->media_connected = true; + priv->port_open = true; + + return 0; +} + +static int +nxpwifi_uap_event_mic_countermeasures(struct nxpwifi_private *priv) +{ + /* For future development */ + + return 0; +} + +static int +nxpwifi_uap_event_radar_detected(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + return nxpwifi_11h_handle_radar_detected(priv, adapter->event_skb); +} + +static int +nxpwifi_uap_event_channel_report_rdy(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + return nxpwifi_11h_handle_chanrpt_ready(priv, adapter->event_skb); +} + +static int +nxpwifi_uap_event_tx_data_pause(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_process_tx_pause_event(priv, adapter->event_skb); + + return 0; +} + +static int +nxpwifi_uap_event_ext_scan_report(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + void *buf = adapter->event_skb->data; + int ret = 0; + + if (adapter->ext_scan) + ret = nxpwifi_handle_event_ext_scan_report(priv, buf); + + return ret; +} + +static int +nxpwifi_uap_event_rxba_sync(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_11n_rxba_sync_event(priv, adapter->event_body, + adapter->event_skb->len - + sizeof(adapter->event_cause)); + + return 0; +} + +static int +nxpwifi_uap_event_remain_on_chan_expired(struct nxpwifi_private *priv) +{ + cfg80211_remain_on_channel_expired(&priv->wdev, + priv->roc_cfg.cookie, + &priv->roc_cfg.chan, + GFP_ATOMIC); + memset(&priv->roc_cfg, 0x00, sizeof(struct nxpwifi_roc_cfg)); + + return 0; +} + +static int +nxpwifi_uap_event_multi_chan_info(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_process_multi_chan_event(priv, adapter->event_skb); + + return 0; +} + +static int +nxpwifi_uap_event_tx_status_report(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_parse_tx_status_event(priv, adapter->event_body); + + return 0; +} + +static int +nxpwifi_uap_event_bt_coex_wlan_para_change(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_bt_coex_wlan_param_update_event(priv, adapter->event_skb); + + return 0; +} + +static int +nxpwifi_uap_event_vdll_ind(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + return nxpwifi_process_vdll_event(priv, adapter->event_skb); +} + +static const struct nxpwifi_evt_entry evt_table_uap[] = { + {.event_cause = EVENT_PS_AWAKE, + .event_handler = nxpwifi_uap_event_ps_awake}, + {.event_cause = EVENT_PS_SLEEP, + .event_handler = nxpwifi_uap_event_ps_sleep}, + {.event_cause = EVENT_UAP_STA_DEAUTH, + .event_handler = nxpwifi_uap_event_sta_deauth}, + {.event_cause = EVENT_UAP_STA_ASSOC, + .event_handler = nxpwifi_uap_event_sta_assoc}, + {.event_cause = EVENT_UAP_BSS_START, + .event_handler = nxpwifi_uap_event_bss_start}, + {.event_cause = EVENT_ADDBA, + .event_handler = nxpwifi_uap_event_addba}, + {.event_cause = EVENT_DELBA, + .event_handler = nxpwifi_uap_event_delba}, + {.event_cause = EVENT_BA_STREAM_TIEMOUT, + .event_handler = nxpwifi_uap_event_ba_stream_timeout}, + {.event_cause = EVENT_AMSDU_AGGR_CTRL, + .event_handler = nxpwifi_uap_event_amsdu_aggr_ctrl}, + {.event_cause = EVENT_UAP_BSS_IDLE, + .event_handler = nxpwifi_uap_event_bss_idle}, + {.event_cause = EVENT_UAP_BSS_ACTIVE, + .event_handler = nxpwifi_uap_event_bss_active}, + {.event_cause = EVENT_UAP_MIC_COUNTERMEASURES, + .event_handler = nxpwifi_uap_event_mic_countermeasures}, + {.event_cause = EVENT_RADAR_DETECTED, + .event_handler = nxpwifi_uap_event_radar_detected}, + {.event_cause = EVENT_CHANNEL_REPORT_RDY, + .event_handler = nxpwifi_uap_event_channel_report_rdy}, + {.event_cause = EVENT_TX_DATA_PAUSE, + .event_handler = nxpwifi_uap_event_tx_data_pause}, + {.event_cause = EVENT_EXT_SCAN_REPORT, + .event_handler = nxpwifi_uap_event_ext_scan_report}, + {.event_cause = EVENT_RXBA_SYNC, + .event_handler = nxpwifi_uap_event_rxba_sync}, + {.event_cause = EVENT_REMAIN_ON_CHAN_EXPIRED, + .event_handler = nxpwifi_uap_event_remain_on_chan_expired}, + {.event_cause = EVENT_MULTI_CHAN_INFO, + .event_handler = nxpwifi_uap_event_multi_chan_info}, + {.event_cause = EVENT_TX_STATUS_REPORT, + .event_handler = nxpwifi_uap_event_tx_status_report}, + {.event_cause = EVENT_BT_COEX_WLAN_PARA_CHANGE, + .event_handler = nxpwifi_uap_event_bt_coex_wlan_para_change}, + {.event_cause = EVENT_VDLL_IND, + .event_handler = nxpwifi_uap_event_vdll_ind}, +}; + +/* Handle AP‑interface events by dispatching them to event‑specific routines. */ +int nxpwifi_process_uap_event(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u32 eventcause = adapter->event_cause; + int evt, ret = 0; + + for (evt = 0; evt < ARRAY_SIZE(evt_table_uap); evt++) { + if (eventcause == evt_table_uap[evt].event_cause) { + if (evt_table_uap[evt].event_handler) + ret = evt_table_uap[evt].event_handler(priv); + break; + } + } + + if (evt == ARRAY_SIZE(evt_table_uap)) + nxpwifi_dbg(adapter, EVENT, + "%s: unknown event id: %#x\n", + __func__, eventcause); + else + nxpwifi_dbg(adapter, EVENT, + "%s: event id: %#x\n", + __func__, eventcause); + + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/uap_txrx.c b/drivers/net/wireless/nxp/nxpwifi/uap_txrx.c new file mode 100644 index 000000000000..f3d24bf861ca --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/uap_txrx.c @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: AP TX and RX data handling + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "main.h" +#include "wmm.h" +#include "11n_aggr.h" +#include "11n_rxreorder.h" + +/* + * Drop bridged pkts from RA list until pending <= low threshold; return true if + * any. + */ +static bool +nxpwifi_uap_del_tx_pkts_in_ralist(struct nxpwifi_private *priv, + struct list_head *ra_list_head, + int tid) +{ + struct nxpwifi_ra_list_tbl *ra_list; + struct sk_buff *skb, *tmp; + bool pkt_deleted = false; + struct nxpwifi_txinfo *tx_info; + struct nxpwifi_adapter *adapter = priv->adapter; + + list_for_each_entry(ra_list, ra_list_head, list) { + if (skb_queue_empty(&ra_list->skb_head)) + continue; + + skb_queue_walk_safe(&ra_list->skb_head, skb, tmp) { + tx_info = NXPWIFI_SKB_TXCB(skb); + if (tx_info->flags & NXPWIFI_BUF_FLAG_BRIDGED_PKT) { + __skb_unlink(skb, &ra_list->skb_head); + nxpwifi_write_data_complete(adapter, skb, 0, + -1); + if (ra_list->tx_paused) + priv->wmm.pkts_paused[tid]--; + else + atomic_dec(&priv->wmm.tx_pkts_queued); + pkt_deleted = true; + } + if ((atomic_read(&adapter->pending_bridged_pkts) <= + NXPWIFI_BRIDGED_PKTS_THR_LOW)) + break; + } + } + + return pkt_deleted; +} + +/* Delete bridged pkts from one RA list; rotate index to keep fairness. */ +static void nxpwifi_uap_cleanup_tx_queues(struct nxpwifi_private *priv) +{ + struct list_head *ra_list; + int i; + + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + for (i = 0; i < MAX_NUM_TID; i++, priv->del_list_idx++) { + if (priv->del_list_idx == MAX_NUM_TID) + priv->del_list_idx = 0; + ra_list = &priv->wmm.tid_tbl_ptr[priv->del_list_idx].ra_list; + if (nxpwifi_uap_del_tx_pkts_in_ralist(priv, ra_list, i)) { + priv->del_list_idx++; + break; + } + } + + spin_unlock_bh(&priv->wmm.ra_list_spinlock); +} + +static void +nxpwifi_uap_queue_bridged_pkt(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct uap_rxpd *uap_rx_pd; + struct rx_packet_hdr *rx_pkt_hdr; + struct sk_buff *new_skb; + struct nxpwifi_txinfo *tx_info; + int hdr_chop; + struct ethhdr *p_ethhdr; + struct nxpwifi_sta_node *src_node; + int index; + + uap_rx_pd = (struct uap_rxpd *)(skb->data); + rx_pkt_hdr = (void *)uap_rx_pd + le16_to_cpu(uap_rx_pd->rx_pkt_offset); + + if ((atomic_read(&adapter->pending_bridged_pkts) >= + NXPWIFI_BRIDGED_PKTS_THR_HIGH)) { + nxpwifi_dbg(adapter, ERROR, + "Tx: Bridge packet limit reached. Drop packet!\n"); + kfree_skb(skb); + nxpwifi_uap_cleanup_tx_queues(priv); + return; + } + + if (sizeof(*rx_pkt_hdr) + + le16_to_cpu(uap_rx_pd->rx_pkt_offset) > skb->len) { + priv->stats.rx_dropped++; + dev_kfree_skb_any(skb); + return; + } + + if ((!memcmp(&rx_pkt_hdr->rfc1042_hdr, bridge_tunnel_header, + sizeof(bridge_tunnel_header))) || + (!memcmp(&rx_pkt_hdr->rfc1042_hdr, rfc1042_header, + sizeof(rfc1042_header)) && + rx_pkt_hdr->rfc1042_hdr.snap_type != htons(ETH_P_AARP) && + rx_pkt_hdr->rfc1042_hdr.snap_type != htons(ETH_P_IPX))) { + /* + * Replace the 803 header and rfc1042 header (llc/snap) with + * an Ethernet II header, keep the src/dst and snap_type + * (ethertype). + * + * The firmware only passes up SNAP frames converting all RX + * data from 802.11 to 802.2/LLC/SNAP frames. + * + * To create the Ethernet II, just move the src, dst address + * right before the snap_type. + */ + p_ethhdr = (struct ethhdr *) + ((u8 *)(&rx_pkt_hdr->eth803_hdr) + + sizeof(rx_pkt_hdr->eth803_hdr) + + sizeof(rx_pkt_hdr->rfc1042_hdr) + - sizeof(rx_pkt_hdr->eth803_hdr.h_dest) + - sizeof(rx_pkt_hdr->eth803_hdr.h_source) + - sizeof(rx_pkt_hdr->rfc1042_hdr.snap_type)); + memcpy(p_ethhdr->h_source, rx_pkt_hdr->eth803_hdr.h_source, + sizeof(p_ethhdr->h_source)); + memcpy(p_ethhdr->h_dest, rx_pkt_hdr->eth803_hdr.h_dest, + sizeof(p_ethhdr->h_dest)); + /* + * Chop off the rxpd + the excess memory from + * 802.2/llc/snap header that was removed. + */ + hdr_chop = (u8 *)p_ethhdr - (u8 *)uap_rx_pd; + } else { + /* Chop off the rxpd */ + hdr_chop = (u8 *)&rx_pkt_hdr->eth803_hdr - (u8 *)uap_rx_pd; + } + + /* + * Chop off the leading header bytes so that it points + * to the start of either the reconstructed EthII frame + * or the 802.2/llc/snap frame. + */ + skb_pull(skb, hdr_chop); + + if (skb_headroom(skb) < NXPWIFI_MIN_DATA_HEADER_LEN) { + nxpwifi_dbg(adapter, ERROR, + "data: Tx: insufficient skb headroom %d\n", + skb_headroom(skb)); + /* Insufficient skb headroom - allocate a new skb */ + new_skb = + skb_realloc_headroom(skb, NXPWIFI_MIN_DATA_HEADER_LEN); + if (unlikely(!new_skb)) { + nxpwifi_dbg(adapter, ERROR, + "Tx: cannot allocate new_skb\n"); + kfree_skb(skb); + priv->stats.tx_dropped++; + return; + } + + kfree_skb(skb); + skb = new_skb; + nxpwifi_dbg(adapter, INFO, + "info: new skb headroom %d\n", + skb_headroom(skb)); + } + + tx_info = NXPWIFI_SKB_TXCB(skb); + memset(tx_info, 0, sizeof(*tx_info)); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + tx_info->flags |= NXPWIFI_BUF_FLAG_BRIDGED_PKT; + + rcu_read_lock(); + src_node = nxpwifi_get_sta_entry(priv, rx_pkt_hdr->eth803_hdr.h_source); + if (src_node) { + src_node->stats.last_rx = jiffies; + src_node->stats.rx_bytes += skb->len; + src_node->stats.rx_packets++; + src_node->stats.last_tx_rate = uap_rx_pd->rx_rate; + src_node->stats.last_tx_htinfo = uap_rx_pd->ht_info; + } + rcu_read_unlock(); + + if (is_unicast_ether_addr(rx_pkt_hdr->eth803_hdr.h_dest)) { + /* + * Update bridge packet statistics as the + * packet is not going to kernel/upper layer. + */ + priv->stats.rx_bytes += skb->len; + priv->stats.rx_packets++; + + /* + * Sending bridge packet to TX queue, so save the packet + * length in TXCB to update statistics in TX complete. + */ + tx_info->pkt_len = skb->len; + } + + __net_timestamp(skb); + + index = nxpwifi_1d_to_wmm_queue[skb->priority]; + atomic_inc(&priv->wmm_tx_pending[index]); + nxpwifi_wmm_add_buf_txqueue(priv, skb); + atomic_inc(&adapter->tx_pending); + atomic_inc(&adapter->pending_bridged_pkts); + + nxpwifi_queue_work(adapter, &adapter->main_work); +} + +/* AP fwd: mcast/bcast -> up + bridge; unicast -> bridge if RA assoc, else up. */ +int nxpwifi_handle_uap_rx_forward(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct uap_rxpd *uap_rx_pd; + struct rx_packet_hdr *rx_pkt_hdr; + u8 ra[ETH_ALEN]; + struct sk_buff *skb_uap; + struct nxpwifi_sta_node *node; + + uap_rx_pd = (struct uap_rxpd *)(skb->data); + rx_pkt_hdr = (void *)uap_rx_pd + le16_to_cpu(uap_rx_pd->rx_pkt_offset); + + /* don't do packet forwarding in disconnected state */ + if (!priv->media_connected) { + nxpwifi_dbg(adapter, ERROR, + "drop packet in disconnected state.\n"); + dev_kfree_skb_any(skb); + return 0; + } + + memcpy(ra, rx_pkt_hdr->eth803_hdr.h_dest, ETH_ALEN); + + if (is_multicast_ether_addr(ra)) { + skb_uap = skb_copy(skb, GFP_ATOMIC); + if (likely(skb_uap)) { + nxpwifi_uap_queue_bridged_pkt(priv, skb_uap); + } else { + nxpwifi_dbg(adapter, ERROR, + "failed to copy skb for uAP\n"); + priv->stats.rx_dropped++; + dev_kfree_skb_any(skb); + return -ENOMEM; + } + } else { + node = nxpwifi_get_sta_entry_rcu(priv, ra); + if (node) { + /* Requeue Intra-BSS packet */ + nxpwifi_uap_queue_bridged_pkt(priv, skb); + return 0; + } + } + + /* Forward unicat/Inter-BSS packets to kernel. */ + return nxpwifi_process_rx_packet(priv, skb); +} + +int nxpwifi_uap_recv_packet(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_sta_node *src_node, *dst_node; + struct ethhdr *p_ethhdr; + struct sk_buff *skb_uap; + struct nxpwifi_txinfo *tx_info; + + if (!skb) + return -ENOMEM; + + p_ethhdr = (void *)skb->data; + rcu_read_lock(); + src_node = nxpwifi_get_sta_entry(priv, p_ethhdr->h_source); + if (src_node) { + src_node->stats.last_rx = jiffies; + src_node->stats.rx_bytes += skb->len; + src_node->stats.rx_packets++; + } + dst_node = nxpwifi_get_sta_entry(priv, p_ethhdr->h_dest); + rcu_read_unlock(); + + if (is_multicast_ether_addr(p_ethhdr->h_dest) || dst_node) { + if (skb_headroom(skb) < NXPWIFI_MIN_DATA_HEADER_LEN) + skb_uap = + skb_realloc_headroom(skb, NXPWIFI_MIN_DATA_HEADER_LEN); + else + skb_uap = skb_copy(skb, GFP_ATOMIC); + + if (likely(skb_uap)) { + tx_info = NXPWIFI_SKB_TXCB(skb_uap); + memset(tx_info, 0, sizeof(*tx_info)); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + tx_info->flags |= NXPWIFI_BUF_FLAG_BRIDGED_PKT; + __net_timestamp(skb_uap); + nxpwifi_wmm_add_buf_txqueue(priv, skb_uap); + atomic_inc(&adapter->tx_pending); + atomic_inc(&adapter->pending_bridged_pkts); + if ((atomic_read(&adapter->pending_bridged_pkts) >= + NXPWIFI_BRIDGED_PKTS_THR_HIGH)) { + nxpwifi_dbg(adapter, ERROR, + "Tx: Bridge packet limit reached. Drop packet!\n"); + nxpwifi_uap_cleanup_tx_queues(priv); + } + + } else { + nxpwifi_dbg(adapter, ERROR, "failed to allocate skb_uap"); + } + + nxpwifi_queue_work(adapter, &adapter->main_work); + /* Don't forward Intra-BSS unicast packet to upper layer*/ + + if (dst_node) + return 0; + } + + skb->dev = priv->netdev; + skb->protocol = eth_type_trans(skb, priv->netdev); + skb->ip_summed = CHECKSUM_NONE; + + /* Forward multicast/broadcast packet to upper layer*/ + netif_rx(skb); + return 0; +} + +/* Process AP RX: check RxPD/len, handle mgmt or 11n reorder/AMSDU, then forward. */ +int nxpwifi_process_uap_rx_packet(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + struct uap_rxpd *uap_rx_pd; + struct rx_packet_hdr *rx_pkt_hdr; + u16 rx_pkt_type; + u8 ta[ETH_ALEN], pkt_type; + struct nxpwifi_sta_node *node; + + uap_rx_pd = (struct uap_rxpd *)(skb->data); + rx_pkt_type = le16_to_cpu(uap_rx_pd->rx_pkt_type); + rx_pkt_hdr = (void *)uap_rx_pd + le16_to_cpu(uap_rx_pd->rx_pkt_offset); + + if (le16_to_cpu(uap_rx_pd->rx_pkt_offset) + + sizeof(rx_pkt_hdr->eth803_hdr) > skb->len) { + nxpwifi_dbg(adapter, ERROR, + "wrong rx packet for struct ethhdr: len=%d, offset=%d\n", + skb->len, le16_to_cpu(uap_rx_pd->rx_pkt_offset)); + priv->stats.rx_dropped++; + dev_kfree_skb_any(skb); + return 0; + } + + ether_addr_copy(ta, rx_pkt_hdr->eth803_hdr.h_source); + + if ((le16_to_cpu(uap_rx_pd->rx_pkt_offset) + + le16_to_cpu(uap_rx_pd->rx_pkt_length)) > (u16)skb->len) { + nxpwifi_dbg(adapter, ERROR, + "wrong rx packet: len=%d, offset=%d, length=%d\n", + skb->len, le16_to_cpu(uap_rx_pd->rx_pkt_offset), + le16_to_cpu(uap_rx_pd->rx_pkt_length)); + priv->stats.rx_dropped++; + rcu_read_lock(); + node = nxpwifi_get_sta_entry(priv, ta); + if (node) + node->stats.tx_failed++; + rcu_read_unlock(); + + dev_kfree_skb_any(skb); + return 0; + } + + if (rx_pkt_type == PKT_TYPE_MGMT) { + ret = nxpwifi_process_mgmt_packet(priv, skb); + if (ret && (ret != -EINPROGRESS)) + nxpwifi_dbg(adapter, DATA, "Rx of mgmt packet failed"); + if (ret != -EINPROGRESS) + dev_kfree_skb_any(skb); + return ret; + } + + if (rx_pkt_type != PKT_TYPE_BAR && uap_rx_pd->priority < MAX_NUM_TID) { + rcu_read_lock(); + node = nxpwifi_get_sta_entry(priv, ta); + if (node) + node->rx_seq[uap_rx_pd->priority] = + le16_to_cpu(uap_rx_pd->seq_num); + rcu_read_unlock(); + } + + if (!priv->ap_11n_enabled || + (!nxpwifi_11n_get_rx_reorder_tbl(priv, uap_rx_pd->priority, ta) && + (le16_to_cpu(uap_rx_pd->rx_pkt_type) != PKT_TYPE_AMSDU))) { + ret = nxpwifi_handle_uap_rx_forward(priv, skb); + return ret; + } + + /* Reorder and send to kernel */ + pkt_type = (u8)le16_to_cpu(uap_rx_pd->rx_pkt_type); + ret = nxpwifi_11n_rx_reorder_pkt(priv, le16_to_cpu(uap_rx_pd->seq_num), + uap_rx_pd->priority, ta, pkt_type, skb); + + if (ret || rx_pkt_type == PKT_TYPE_BAR) + dev_kfree_skb_any(skb); + + if (ret) + priv->stats.rx_dropped++; + + return ret; +} + +/* + * Build TxPD for AP TX: push aligned TxPD; set bss, len/off, prio, delay, txctl, + * flags. + */ +void nxpwifi_process_uap_txpd(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct uap_txpd *txpd; + struct nxpwifi_txinfo *tx_info = NXPWIFI_SKB_TXCB(skb); + int pad; + u16 pkt_type, pkt_offset; + int hroom = adapter->intf_hdr_len; + + pkt_type = nxpwifi_is_skb_mgmt_frame(skb) ? PKT_TYPE_MGMT : 0; + + pad = ((uintptr_t)skb->data - (sizeof(*txpd) + hroom)) & + (NXPWIFI_DMA_ALIGN_SZ - 1); + + skb_push(skb, sizeof(*txpd) + pad); + + txpd = (struct uap_txpd *)skb->data; + memset(txpd, 0, sizeof(*txpd)); + txpd->bss_num = priv->bss_num; + txpd->bss_type = priv->bss_type; + txpd->tx_pkt_length = cpu_to_le16((u16)(skb->len - (sizeof(*txpd) + + pad))); + txpd->priority = (u8)skb->priority; + + txpd->pkt_delay_2ms = nxpwifi_wmm_compute_drv_pkt_delay(priv, skb); + + if (tx_info->flags & NXPWIFI_BUF_FLAG_EAPOL_TX_STATUS || + tx_info->flags & NXPWIFI_BUF_FLAG_ACTION_TX_STATUS) { + txpd->tx_token_id = tx_info->ack_frame_id; + txpd->flags |= NXPWIFI_TXPD_FLAGS_REQ_TX_STATUS; + } + + if (txpd->priority < ARRAY_SIZE(priv->wmm.user_pri_pkt_tx_ctrl)) + /* + * Set the priority specific tx_control field, setting of 0 will + * cause the default value to be used later in this function. + */ + txpd->tx_control = + cpu_to_le32(priv->wmm.user_pri_pkt_tx_ctrl[txpd->priority]); + + /* Offset of actual data */ + pkt_offset = sizeof(*txpd) + pad; + if (pkt_type == PKT_TYPE_MGMT) { + /* Set the packet type and add header for management frame */ + txpd->tx_pkt_type = cpu_to_le16(pkt_type); + pkt_offset += NXPWIFI_MGMT_FRAME_HEADER_SIZE; + } + + txpd->tx_pkt_offset = cpu_to_le16(pkt_offset); + + /* make space for adapter->intf_hdr_len */ + skb_push(skb, hroom); + + if (!txpd->tx_control) + /* TxCtrl set by user or default */ + txpd->tx_control = cpu_to_le32(priv->pkt_tx_ctrl); +} diff --git a/drivers/net/wireless/nxp/nxpwifi/util.c b/drivers/net/wireless/nxp/nxpwifi/util.c new file mode 100644 index 000000000000..29ef031f8ec9 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/util.c @@ -0,0 +1,1381 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: utility functions + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" +#include +#include +#include +#include +#include + +#define RX_RATE_FORMAT_MASK GENMASK(1, 0) +#define RX_RATE_BW_MASK GENMASK(3, 2) +#define RX_RATE_GI_MASK BIT(4) +#define RX_RATE_STBC_MASK BIT(5) +#define RX_RATE_LDPC_MASK BIT(6) + +static struct nxpwifi_debug_data items[] = { + {"debug_mask", item_size(debug_mask), + item_addr(debug_mask), 1}, + {"int_counter", item_size(int_counter), + item_addr(int_counter), 1}, + {"wmm_ac_vo", item_size(packets_out[WMM_AC_VO]), + item_addr(packets_out[WMM_AC_VO]), 1}, + {"wmm_ac_vi", item_size(packets_out[WMM_AC_VI]), + item_addr(packets_out[WMM_AC_VI]), 1}, + {"wmm_ac_be", item_size(packets_out[WMM_AC_BE]), + item_addr(packets_out[WMM_AC_BE]), 1}, + {"wmm_ac_bk", item_size(packets_out[WMM_AC_BK]), + item_addr(packets_out[WMM_AC_BK]), 1}, + {"tx_buf_size", item_size(tx_buf_size), + item_addr(tx_buf_size), 1}, + {"curr_tx_buf_size", item_size(curr_tx_buf_size), + item_addr(curr_tx_buf_size), 1}, + {"ps_mode", item_size(ps_mode), + item_addr(ps_mode), 1}, + {"ps_state", item_size(ps_state), + item_addr(ps_state), 1}, + {"is_deep_sleep", item_size(is_deep_sleep), + item_addr(is_deep_sleep), 1}, + {"wakeup_dev_req", item_size(pm_wakeup_card_req), + item_addr(pm_wakeup_card_req), 1}, + {"wakeup_tries", item_size(pm_wakeup_fw_try), + item_addr(pm_wakeup_fw_try), 1}, + {"hs_configured", item_size(is_hs_configured), + item_addr(is_hs_configured), 1}, + {"hs_activated", item_size(hs_activated), + item_addr(hs_activated), 1}, + {"num_tx_timeout", item_size(num_tx_timeout), + item_addr(num_tx_timeout), 1}, + {"is_cmd_timedout", item_size(is_cmd_timedout), + item_addr(is_cmd_timedout), 1}, + {"timeout_cmd_id", item_size(timeout_cmd_id), + item_addr(timeout_cmd_id), 1}, + {"timeout_cmd_act", item_size(timeout_cmd_act), + item_addr(timeout_cmd_act), 1}, + {"last_cmd_id", item_size(last_cmd_id), + item_addr(last_cmd_id), DBG_CMD_NUM}, + {"last_cmd_act", item_size(last_cmd_act), + item_addr(last_cmd_act), DBG_CMD_NUM}, + {"last_cmd_index", item_size(last_cmd_index), + item_addr(last_cmd_index), 1}, + {"last_cmd_resp_id", item_size(last_cmd_resp_id), + item_addr(last_cmd_resp_id), DBG_CMD_NUM}, + {"last_cmd_resp_index", item_size(last_cmd_resp_index), + item_addr(last_cmd_resp_index), 1}, + {"last_event", item_size(last_event), + item_addr(last_event), DBG_CMD_NUM}, + {"last_event_index", item_size(last_event_index), + item_addr(last_event_index), 1}, + {"last_mp_wr_bitmap", item_size(last_mp_wr_bitmap), + item_addr(last_mp_wr_bitmap), NXPWIFI_DBG_SDIO_MP_NUM}, + {"last_mp_wr_ports", item_size(last_mp_wr_ports), + item_addr(last_mp_wr_ports), NXPWIFI_DBG_SDIO_MP_NUM}, + {"last_mp_wr_len", item_size(last_mp_wr_len), + item_addr(last_mp_wr_len), NXPWIFI_DBG_SDIO_MP_NUM}, + {"last_mp_curr_wr_port", item_size(last_mp_curr_wr_port), + item_addr(last_mp_curr_wr_port), NXPWIFI_DBG_SDIO_MP_NUM}, + {"last_sdio_mp_index", item_size(last_sdio_mp_index), + item_addr(last_sdio_mp_index), 1}, + {"num_cmd_h2c_fail", item_size(num_cmd_host_to_card_failure), + item_addr(num_cmd_host_to_card_failure), 1}, + {"num_cmd_sleep_cfm_fail", + item_size(num_cmd_sleep_cfm_host_to_card_failure), + item_addr(num_cmd_sleep_cfm_host_to_card_failure), 1}, + {"num_tx_h2c_fail", item_size(num_tx_host_to_card_failure), + item_addr(num_tx_host_to_card_failure), 1}, + {"num_evt_deauth", item_size(num_event_deauth), + item_addr(num_event_deauth), 1}, + {"num_evt_disassoc", item_size(num_event_disassoc), + item_addr(num_event_disassoc), 1}, + {"num_evt_link_lost", item_size(num_event_link_lost), + item_addr(num_event_link_lost), 1}, + {"num_cmd_deauth", item_size(num_cmd_deauth), + item_addr(num_cmd_deauth), 1}, + {"num_cmd_assoc_ok", item_size(num_cmd_assoc_success), + item_addr(num_cmd_assoc_success), 1}, + {"num_cmd_assoc_fail", item_size(num_cmd_assoc_failure), + item_addr(num_cmd_assoc_failure), 1}, + {"cmd_sent", item_size(cmd_sent), + item_addr(cmd_sent), 1}, + {"data_sent", item_size(data_sent), + item_addr(data_sent), 1}, + {"cmd_resp_received", item_size(cmd_resp_received), + item_addr(cmd_resp_received), 1}, + {"event_received", item_size(event_received), + item_addr(event_received), 1}, + + /* variables defined in struct nxpwifi_adapter */ + {"cmd_pending", adapter_item_size(cmd_pending), + adapter_item_addr(cmd_pending), 1}, + {"tx_pending", adapter_item_size(tx_pending), + adapter_item_addr(tx_pending), 1}, + {"rx_pending", adapter_item_size(rx_pending), + adapter_item_addr(rx_pending), 1}, +}; + +static int num_of_items = ARRAY_SIZE(items); + +/* Send init or shutdown command to firmware. */ +int nxpwifi_init_shutdown_fw(struct nxpwifi_private *priv, + u32 func_init_shutdown) +{ + u16 cmd; + + if (func_init_shutdown == NXPWIFI_FUNC_INIT) { + cmd = HOST_CMD_FUNC_INIT; + } else if (func_init_shutdown == NXPWIFI_FUNC_SHUTDOWN) { + cmd = HOST_CMD_FUNC_SHUTDOWN; + } else { + nxpwifi_dbg(priv->adapter, ERROR, + "unsupported parameter\n"); + return -EINVAL; + } + + return nxpwifi_send_cmd(priv, cmd, HOST_ACT_GEN_SET, 0, NULL, true); +} +EXPORT_SYMBOL_GPL(nxpwifi_init_shutdown_fw); + +/* Handle IOCTL get/set of debug info across driver structures. */ +int nxpwifi_get_debug_info(struct nxpwifi_private *priv, + struct nxpwifi_debug_info *info) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (info) { + info->debug_mask = adapter->debug_mask; + memcpy(info->packets_out, + priv->wmm.packets_out, + sizeof(priv->wmm.packets_out)); + info->curr_tx_buf_size = (u32)adapter->curr_tx_buf_size; + info->tx_buf_size = (u32)adapter->tx_buf_size; + info->rx_tbl_num = nxpwifi_get_rx_reorder_tbl(priv, + info->rx_tbl); + info->tx_tbl_num = nxpwifi_get_tx_ba_stream_tbl(priv, + info->tx_tbl); + info->ps_mode = adapter->ps_mode; + info->ps_state = adapter->ps_state; + info->is_deep_sleep = adapter->is_deep_sleep; + info->pm_wakeup_card_req = adapter->pm_wakeup_card_req; + info->pm_wakeup_fw_try = adapter->pm_wakeup_fw_try; + info->is_hs_configured = test_bit(NXPWIFI_IS_HS_CONFIGURED, + &adapter->work_flags); + info->hs_activated = adapter->hs_activated; + info->is_cmd_timedout = test_bit(NXPWIFI_IS_CMD_TIMEDOUT, + &adapter->work_flags); + info->num_cmd_host_to_card_failure = + adapter->dbg.num_cmd_host_to_card_failure; + info->num_cmd_sleep_cfm_host_to_card_failure = + adapter->dbg.num_cmd_sleep_cfm_host_to_card_failure; + info->num_tx_host_to_card_failure = + adapter->dbg.num_tx_host_to_card_failure; + info->num_event_deauth = adapter->dbg.num_event_deauth; + info->num_event_disassoc = adapter->dbg.num_event_disassoc; + info->num_event_link_lost = adapter->dbg.num_event_link_lost; + info->num_cmd_deauth = adapter->dbg.num_cmd_deauth; + info->num_cmd_assoc_success = + adapter->dbg.num_cmd_assoc_success; + info->num_cmd_assoc_failure = + adapter->dbg.num_cmd_assoc_failure; + info->num_tx_timeout = adapter->dbg.num_tx_timeout; + info->timeout_cmd_id = adapter->dbg.timeout_cmd_id; + info->timeout_cmd_act = adapter->dbg.timeout_cmd_act; + memcpy(info->last_cmd_id, adapter->dbg.last_cmd_id, + sizeof(adapter->dbg.last_cmd_id)); + memcpy(info->last_cmd_act, adapter->dbg.last_cmd_act, + sizeof(adapter->dbg.last_cmd_act)); + info->last_cmd_index = adapter->dbg.last_cmd_index; + memcpy(info->last_cmd_resp_id, adapter->dbg.last_cmd_resp_id, + sizeof(adapter->dbg.last_cmd_resp_id)); + info->last_cmd_resp_index = adapter->dbg.last_cmd_resp_index; + memcpy(info->last_event, adapter->dbg.last_event, + sizeof(adapter->dbg.last_event)); + info->last_event_index = adapter->dbg.last_event_index; + memcpy(info->last_mp_wr_bitmap, adapter->dbg.last_mp_wr_bitmap, + sizeof(adapter->dbg.last_mp_wr_bitmap)); + memcpy(info->last_mp_wr_ports, adapter->dbg.last_mp_wr_ports, + sizeof(adapter->dbg.last_mp_wr_ports)); + memcpy(info->last_mp_curr_wr_port, + adapter->dbg.last_mp_curr_wr_port, + sizeof(adapter->dbg.last_mp_curr_wr_port)); + memcpy(info->last_mp_wr_len, adapter->dbg.last_mp_wr_len, + sizeof(adapter->dbg.last_mp_wr_len)); + info->last_sdio_mp_index = adapter->dbg.last_sdio_mp_index; + info->data_sent = adapter->data_sent; + info->cmd_sent = adapter->cmd_sent; + info->cmd_resp_received = adapter->cmd_resp_received; + } + + return 0; +} + +int nxpwifi_debug_info_to_buffer(struct nxpwifi_private *priv, char *buf, + struct nxpwifi_debug_info *info) +{ + char *p = buf; + struct nxpwifi_debug_data *d = &items[0]; + size_t size, addr; + long val; + int i, j; + + if (!info) + return 0; + + for (i = 0; i < num_of_items; i++) { + p += sprintf(p, "%s=", d[i].name); + + size = d[i].size / d[i].num; + + if (i < (num_of_items - 3)) + addr = d[i].addr + (size_t)info; + else /* The last 3 items are struct nxpwifi_adapter variables */ + addr = d[i].addr + (size_t)priv->adapter; + + for (j = 0; j < d[i].num; j++) { + switch (size) { + case 1: + val = *((u8 *)addr); + break; + case 2: + val = get_unaligned((u16 *)addr); + break; + case 4: + val = get_unaligned((u32 *)addr); + break; + case 8: + val = get_unaligned((long long *)addr); + break; + default: + val = -1; + break; + } + + p += sprintf(p, "%#lx ", val); + addr += size; + } + + p += sprintf(p, "\n"); + } + + if (info->tx_tbl_num) { + p += sprintf(p, "Tx BA stream table:\n"); + for (i = 0; i < info->tx_tbl_num; i++) + p += sprintf(p, "tid = %d, ra = %pM\n", + info->tx_tbl[i].tid, info->tx_tbl[i].ra); + } + + if (info->rx_tbl_num) { + p += sprintf(p, "Rx reorder table:\n"); + for (i = 0; i < info->rx_tbl_num; i++) { + p += sprintf(p, "tid = %d, ta = %pM, ", + info->rx_tbl[i].tid, + info->rx_tbl[i].ta); + p += sprintf(p, "start_win = %d, ", + info->rx_tbl[i].start_win); + p += sprintf(p, "win_size = %d, buffer: ", + info->rx_tbl[i].win_size); + + for (j = 0; j < info->rx_tbl[i].win_size; j++) + p += sprintf(p, "%c ", + info->rx_tbl[i].buffer[j] ? + '1' : '0'); + + p += sprintf(p, "\n"); + } + } + + return p - buf; +} + +bool nxpwifi_is_channel_setting_allowable(struct nxpwifi_private *priv, + struct ieee80211_channel *check_chan) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int i; + struct nxpwifi_private *tmp_priv; + u8 bss_role = GET_BSS_ROLE(priv); + struct ieee80211_channel *set_chan; + + for (i = 0; i < adapter->priv_num; i++) { + tmp_priv = adapter->priv[i]; + if (tmp_priv == priv) + continue; + + set_chan = NULL; + if (bss_role == NXPWIFI_BSS_ROLE_STA) { + if (GET_BSS_ROLE(tmp_priv) == NXPWIFI_BSS_ROLE_UAP && + netif_carrier_ok(tmp_priv->netdev) && + cfg80211_chandef_valid(&tmp_priv->bss_chandef)) + set_chan = tmp_priv->bss_chandef.chan; + } else if (bss_role == NXPWIFI_BSS_ROLE_UAP) { + struct nxpwifi_current_bss_params *bss_params = + &tmp_priv->curr_bss_params; + int channel = bss_params->bss_descriptor.channel; + enum nl80211_band band = + nxpwifi_band_to_radio_type(bss_params->band); + int freq = + ieee80211_channel_to_frequency(channel, band); + + if (GET_BSS_ROLE(tmp_priv) == NXPWIFI_BSS_ROLE_STA && + tmp_priv->media_connected) + set_chan = ieee80211_get_channel(adapter->wiphy, freq); + } + + if (set_chan && !ieee80211_channel_equal(check_chan, set_chan)) { + nxpwifi_dbg(adapter, ERROR, + "AP/STA must run on the same channel\n"); + return false; + } + } + + return true; +} + +void nxpwifi_convert_chan_to_band_cfg(struct nxpwifi_private *priv, + u8 *band_cfg, + struct cfg80211_chan_def *chan_def) +{ + u8 chan_band = 0, chan_width = 0, chan2_offset = 0; + + switch (chan_def->chan->band) { + case NL80211_BAND_2GHZ: + chan_band = BAND_2GHZ; + break; + case NL80211_BAND_5GHZ: + chan_band = BAND_5GHZ; + break; + default: + break; + } + + switch (chan_def->width) { + case NL80211_CHAN_WIDTH_20_NOHT: + case NL80211_CHAN_WIDTH_20: + chan_width = CHAN_BW_20MHZ; + break; + case NL80211_CHAN_WIDTH_40: + chan_width = CHAN_BW_40MHZ; + if (chan_def->center_freq1 > chan_def->chan->center_freq) + chan2_offset = IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + else + chan2_offset = IEEE80211_HT_PARAM_CHA_SEC_BELOW; + break; + case NL80211_CHAN_WIDTH_80: + chan2_offset = + nxpwifi_get_sec_chan_offset(chan_def->chan->hw_value); + chan_width = CHAN_BW_80MHZ; + break; + case NL80211_CHAN_WIDTH_80P80: + case NL80211_CHAN_WIDTH_160: + default: + nxpwifi_dbg(priv->adapter, + WARN, "Unknown channel width: %d\n", + chan_def->width); + break; + } + + *band_cfg = ((chan2_offset << BAND_CFG_CHAN2_SHIFT_BIT) & + BAND_CFG_CHAN2_OFFSET_MASK) | + ((chan_width << BAND_CFG_CHAN_WIDTH_SHIFT_BIT) & + BAND_CFG_CHAN_WIDTH_MASK) | + ((chan_band << BAND_CFG_CHAN_BAND_SHIFT_BIT) & + BAND_CFG_CHAN_BAND_MASK); +} + +static int +nxpwifi_parse_mgmt_packet(struct nxpwifi_private *priv, u8 *payload, u16 len, + struct rxpd *rx_pd) +{ + u16 stype; + u8 category; + struct ieee80211_hdr *ieee_hdr = (void *)payload; + + stype = (le16_to_cpu(ieee_hdr->frame_control) & IEEE80211_FCTL_STYPE); + + switch (stype) { + case IEEE80211_STYPE_ACTION: + category = *(payload + sizeof(struct ieee80211_hdr)); + switch (category) { + case WLAN_CATEGORY_BACK: + /*we dont indicate BACK action frames to cfg80211*/ + nxpwifi_dbg(priv->adapter, INFO, + "drop BACK action frames"); + return -EINVAL; + default: + nxpwifi_dbg(priv->adapter, INFO, + "unknown public action frame category %d\n", + category); + } + break; + default: + nxpwifi_dbg(priv->adapter, INFO, + "unknown mgmt frame subtype %#x\n", stype); + return 0; + } + + return 0; +} + +/* Send deauth frame to cfg80211. */ +void nxpwifi_host_mlme_disconnect(struct nxpwifi_private *priv, + u16 reason_code, u8 *sa) +{ + u8 frame_buf[100]; + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)frame_buf; + + memset(frame_buf, 0, sizeof(frame_buf)); + mgmt->frame_control = cpu_to_le16(IEEE80211_STYPE_DEAUTH); + mgmt->duration = 0; + mgmt->seq_ctrl = 0; + mgmt->u.deauth.reason_code = cpu_to_le16(reason_code); + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) { + eth_broadcast_addr(mgmt->da); + memcpy(mgmt->sa, + priv->curr_bss_params.bss_descriptor.mac_address, + ETH_ALEN); + memcpy(mgmt->bssid, priv->cfg_bssid, ETH_ALEN); + priv->auth_flag = 0; + priv->auth_alg = WLAN_AUTH_NONE; + } else { + memcpy(mgmt->da, priv->curr_addr, ETH_ALEN); + memcpy(mgmt->sa, sa, ETH_ALEN); + memcpy(mgmt->bssid, priv->curr_addr, ETH_ALEN); + } + + if (GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_UAP) { + cfg80211_rx_mlme_mgmt(priv->netdev, frame_buf, 26); + } else { + cfg80211_rx_mgmt(&priv->wdev, + priv->bss_chandef.chan->center_freq, + 0, frame_buf, 26, 0); + } +} + +/* Parse and forward received management packet to cfg80211. */ +int +nxpwifi_process_mgmt_packet(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct rxpd *rx_pd; + u16 pkt_len; + struct ieee80211_hdr *ieee_hdr; + int ret; + + if (!skb) + return -ENOMEM; + + if (!priv->mgmt_frame_mask || + priv->wdev.iftype == NL80211_IFTYPE_UNSPECIFIED) { + nxpwifi_dbg(adapter, ERROR, + "do not receive mgmt frames on uninitialized intf"); + return -EINVAL; + } + + rx_pd = (struct rxpd *)skb->data; + pkt_len = le16_to_cpu(rx_pd->rx_pkt_length); + if (pkt_len < sizeof(struct ieee80211_hdr) + sizeof(pkt_len)) { + nxpwifi_dbg(adapter, ERROR, "invalid rx_pkt_length"); + return -EINVAL; + } + + skb_pull(skb, le16_to_cpu(rx_pd->rx_pkt_offset)); + skb_pull(skb, sizeof(pkt_len)); + pkt_len -= sizeof(pkt_len); + + ieee_hdr = (void *)skb->data; + if (ieee80211_is_mgmt(ieee_hdr->frame_control)) { + ret = nxpwifi_parse_mgmt_packet(priv, (u8 *)ieee_hdr, + pkt_len, rx_pd); + if (ret) + return ret; + } + /* Remove address4 */ + memmove(skb->data + sizeof(struct ieee80211_hdr_3addr), + skb->data + sizeof(struct ieee80211_hdr), + pkt_len - sizeof(struct ieee80211_hdr)); + + pkt_len -= ETH_ALEN; + rx_pd->rx_pkt_length = cpu_to_le16(pkt_len); + + if (priv->host_mlme_reg && + (GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_UAP) && + (ieee80211_is_auth(ieee_hdr->frame_control) || + ieee80211_is_deauth(ieee_hdr->frame_control) || + ieee80211_is_disassoc(ieee_hdr->frame_control))) { + struct nxpwifi_rxinfo *rx_info; + + if (ieee80211_is_auth(ieee_hdr->frame_control)) { + if (priv->auth_flag & HOST_MLME_AUTH_PENDING) { + if (priv->auth_alg != WLAN_AUTH_SAE) { + priv->auth_flag &= + ~HOST_MLME_AUTH_PENDING; + priv->auth_flag |= + HOST_MLME_AUTH_DONE; + } + } else { + return 0; + } + + nxpwifi_dbg(adapter, MSG, + "auth: receive authentication from %pM\n", + ieee_hdr->addr3); + } else { + if (!priv->wdev.connected) + return 0; + + if (ieee80211_is_deauth(ieee_hdr->frame_control)) { + nxpwifi_dbg(adapter, MSG, + "auth: receive deauth from %pM\n", + ieee_hdr->addr3); + priv->auth_flag = 0; + priv->auth_alg = WLAN_AUTH_NONE; + } else { + nxpwifi_dbg(adapter, MSG, + "assoc: receive disassoc from %pM\n", + ieee_hdr->addr3); + } + } + + rx_info = NXPWIFI_SKB_RXCB(skb); + rx_info->pkt_len = pkt_len; + skb_queue_tail(&adapter->rx_mlme_q, skb); + nxpwifi_queue_wiphy_work(adapter, &adapter->host_mlme_work); + return -EINPROGRESS; + } + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + if (ieee80211_is_auth(ieee_hdr->frame_control)) + nxpwifi_dbg(adapter, MSG, + "auth: receive auth from %pM\n", + ieee_hdr->addr2); + if (ieee80211_is_deauth(ieee_hdr->frame_control)) + nxpwifi_dbg(adapter, MSG, + "auth: receive deauth from %pM\n", + ieee_hdr->addr2); + if (ieee80211_is_disassoc(ieee_hdr->frame_control)) + nxpwifi_dbg(adapter, MSG, + "assoc: receive disassoc from %pM\n", + ieee_hdr->addr2); + if (ieee80211_is_assoc_req(ieee_hdr->frame_control)) + nxpwifi_dbg(adapter, MSG, + "assoc: receive assoc req from %pM\n", + ieee_hdr->addr2); + if (ieee80211_is_reassoc_req(ieee_hdr->frame_control)) + nxpwifi_dbg(adapter, MSG, + "assoc: receive reassoc req from %pM\n", + ieee_hdr->addr2); + } + + cfg80211_rx_mgmt(&priv->wdev, priv->roc_cfg.chan.center_freq, + CAL_RSSI(rx_pd->snr, rx_pd->nf), skb->data, pkt_len, + 0); + + return 0; +} + +#define RTAP_MAX_LEN 128 + +int nxpwifi_recv_packet_to_monif(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct rxpd *rxpd; + struct rxpd_extra_info ext; + struct ieee80211_hdr *dot11; + struct ieee80211_radiotap_header *hdr; + int freq, band; + bool has_ext = false, has_ts = false, use_tsft = false; + u16 rx_flags = 0, off, chan_flags, rx_pkt_offset; + __le16 freq_le, flags_le; + u8 rthdr[RTAP_MAX_LEN] = {0}; + s8 signal, noise; + u8 flags = 0, chan, ant, format, bw, gi, ldpc, mcs, nss, stbc; + + if (!skb) + return -EINVAL; + + rxpd = (struct rxpd *)skb->data; + rx_pkt_offset = le16_to_cpu(rxpd->rx_pkt_offset); + + if (rx_pkt_offset > skb->len || rx_pkt_offset < sizeof(struct rxpd)) + return -EINVAL; + + if (skb->len - rx_pkt_offset < sizeof(struct ieee80211_hdr)) + return -EINVAL; + + has_ext = rxpd->flags & RXPD_FLAG_EXTRA_HEADER; + + if (has_ext) + memcpy((void *)&ext, (void *)(rxpd + 1), sizeof(ext)); + + dot11 = (void *)skb->data + rx_pkt_offset; + memset(rthdr, 0, sizeof(rthdr)); + hdr = (void *)rthdr; + hdr->it_version = 0; + hdr->it_pad = 0; + hdr->it_present = 0; + off = sizeof(*hdr); + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_FLAGS)); + + if (has_ext) { + has_ts = true; + use_tsft = (ext.timestamp.position == 0); + + if (has_ts) { + if (use_tsft) + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_TSFT)); + else + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_TIMESTAMP)); + } + + flags = ext.flags & ~IEEE80211_RADIOTAP_F_BADFCS; + /* reverse fail fcs, 1 means pass FCS in FW, + * but means fail FCS in radiotap + */ + flags &= ~((~ext.flags) & IEEE80211_RADIOTAP_F_BADFCS); + + if (ext.plcp_crc_failed) + rx_flags |= IEEE80211_RADIOTAP_F_RX_BADPLCP; + } + + if (has_ts && use_tsft) { + off = ALIGN(off, 8); + memcpy(rthdr + off, &ext.timestamp.device_timestamp, 8); + off += 8; + } + + if (ieee80211_has_morefrags(dot11->frame_control)) + flags |= IEEE80211_RADIOTAP_F_FRAG; + + if (ieee80211_has_protected(dot11->frame_control)) + flags |= IEEE80211_RADIOTAP_F_WEP; + + rthdr[off++] = flags; + off = ALIGN(off, 2); + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_CHANNEL)); + chan = (le32_to_cpu(rxpd->rx_info) >> 5) & 0xff; + band = (chan <= 14) ? NL80211_BAND_2GHZ : NL80211_BAND_5GHZ; + freq = ieee80211_channel_to_frequency(chan, band); + + if (has_ext) + chan_flags = ext.channel_flags; + else if (band == NL80211_BAND_2GHZ) + chan_flags = IEEE80211_CHAN_2GHZ; + else + chan_flags = IEEE80211_CHAN_5GHZ; + + freq_le = cpu_to_le16(freq); + flags_le = cpu_to_le16(chan_flags); + memcpy(rthdr + off, &freq_le, 2); + off += 2; + memcpy(rthdr + off, &flags_le, 2); + off += 2; + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_DBM_ANTSIGNAL)); + signal = -(rxpd->nf - rxpd->snr); + rthdr[off++] = signal; + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_DBM_ANTNOISE)); + noise = -rxpd->nf; + rthdr[off++] = noise; + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_ANTENNA)); + ant = rxpd->antenna >> 1; + rthdr[off++] = ant; + + if (rx_flags) { + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_RX_FLAGS)); + off = ALIGN(off, 2); + memcpy(rthdr + off, &rx_flags, 2); + off += 2; + } + + format = FIELD_GET(RX_RATE_FORMAT_MASK, rxpd->rate_info); + bw = FIELD_GET(RX_RATE_BW_MASK, rxpd->rate_info); + ldpc = FIELD_GET(RX_RATE_LDPC_MASK, rxpd->rate_info) ? 1 : 0; + stbc = FIELD_GET(RX_RATE_STBC_MASK, rxpd->rate_info) ? 1 : 0; + + if (format == NXPWIFI_RATE_FORMAT_HE) { + gi = ((rxpd->rate_info >> 7) & 0x1) << 1 | + ((rxpd->rate_info >> 4) & 0x1); + } else { + gi = FIELD_GET(RX_RATE_GI_MASK, rxpd->rate_info); + } + + mcs = rxpd->rx_rate & 0xf; + nss = ((rxpd->rx_rate >> 4) & 0xf) + 1; + + if (format == NXPWIFI_RATE_FORMAT_HT) { + u8 mcs_flags = 0; + + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_MCS)); + rthdr[off++] = IEEE80211_RADIOTAP_MCS_HAVE_MCS | + IEEE80211_RADIOTAP_MCS_HAVE_BW | + IEEE80211_RADIOTAP_MCS_HAVE_GI; + + if (bw == 1) + mcs_flags |= IEEE80211_RADIOTAP_MCS_BW_40; + + if (gi) + mcs_flags |= IEEE80211_RADIOTAP_MCS_SGI; + + if (ldpc) + mcs_flags |= IEEE80211_RADIOTAP_MCS_FEC_LDPC; + + if (stbc) + mcs_flags |= (1 << IEEE80211_RADIOTAP_MCS_STBC_SHIFT); + + rthdr[off++] = mcs_flags; + rthdr[off++] = mcs; + } + + if (format == NXPWIFI_RATE_FORMAT_VHT && has_ext) { + struct ieee80211_radiotap_vht vht = {0}; + u32 vht_sig1 = 0, vht_sig2 = 0; + __le16 partial_aid = 0; + u8 bw_field = 0; + + vht_sig1 = ext.vht_he_sig1; + vht_sig2 = ext.vht_he_sig2; + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_VHT)); + vht.known = cpu_to_le16(IEEE80211_RADIOTAP_VHT_KNOWN_GI | + IEEE80211_RADIOTAP_VHT_KNOWN_BANDWIDTH | + IEEE80211_RADIOTAP_VHT_KNOWN_STBC | + IEEE80211_RADIOTAP_VHT_KNOWN_BEAMFORMED | + IEEE80211_RADIOTAP_VHT_KNOWN_GROUP_ID); + + if (stbc) + vht.flags |= IEEE80211_RADIOTAP_VHT_FLAG_STBC; + + if (gi) + vht.flags |= IEEE80211_RADIOTAP_VHT_FLAG_SGI; + + if (vht_sig2 & BIT(1)) + vht.flags |= IEEE80211_RADIOTAP_VHT_FLAG_SGI_NSYM_M10_9; + + if (vht_sig2 & BIT(8)) + vht.flags |= IEEE80211_RADIOTAP_VHT_FLAG_BEAMFORMED; + + switch (bw) { + case 1: + bw_field = 1; /* 40 MHz */ + break; + case 2: + bw_field = 4; /* 80 MHz */ + break; + case 3: + bw_field = 11; /* 160 MHz */ + break; + default: + bw_field = 0; /* 20 MHz */ + } + + vht.bandwidth = bw_field; + vht.mcs_nss[0] = (nss & 0xf) | (mcs << 4); + + if (vht_sig2 & BIT(2)) + vht.coding |= IEEE80211_RADIOTAP_CODING_LDPC_USER0; + + vht.group_id = (vht_sig1 >> 4) & 0x3f; + memcpy(&vht.partial_aid, &partial_aid, 2); + off = ALIGN(off, 2); + memcpy(rthdr + off, &vht, sizeof(vht)); + off += sizeof(vht); + } + + /* TIMESTAMP */ + if (has_ts && !use_tsft) { + u64 ts; + __le64 ts_le; + u16 accuracy = 0; + __le16 acc_le; + u8 flags = 0; + + if (ext.timestamp.position <= 15) { + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_TIMESTAMP)); + off = ALIGN(off, 8); + + if (ext.timestamp.flags & 0x01) { + flags |= IEEE80211_RADIOTAP_TIMESTAMP_FLAG_32BIT; + ts = (u32)ext.timestamp.device_timestamp; + } else { + flags |= IEEE80211_RADIOTAP_TIMESTAMP_FLAG_64BIT; + ts = ext.timestamp.device_timestamp; + } + + ts_le = cpu_to_le64(ts); + memcpy(rthdr + off, &ts_le, sizeof(ts_le)); + off += sizeof(ts_le); + + if (ext.timestamp.flags & 0x02) { + accuracy = ext.timestamp.accuracy; + flags |= IEEE80211_RADIOTAP_TIMESTAMP_FLAG_ACCURACY; + } + + acc_le = cpu_to_le16(accuracy); + memcpy(rthdr + off, &acc_le, sizeof(acc_le)); + off += sizeof(acc_le); + rthdr[off++] = (ext.timestamp.unit & 0x0f) | + ((ext.timestamp.position & 0x0f) << 4); + rthdr[off++] = flags; + } + } + + if (format == NXPWIFI_RATE_FORMAT_HE && has_ext) { + struct ieee80211_radiotap_he he = {0}; + u16 data1 = 0, data2 = 0, data3 = 0, data5 = 0, data6 = 0; + u8 bw_val = 0; + + memcpy((void *)&ext, (void *)(rxpd + 1), sizeof(ext)); + + off = ALIGN(off, 2); + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_HE)); + data1 |= IEEE80211_RADIOTAP_HE_DATA1_DATA_MCS_KNOWN; + data1 |= IEEE80211_RADIOTAP_HE_DATA1_BW_RU_ALLOC_KNOWN; + data1 |= IEEE80211_RADIOTAP_HE_DATA1_STBC_KNOWN; + data1 |= IEEE80211_RADIOTAP_HE_DATA1_CODING_KNOWN; + data2 |= IEEE80211_RADIOTAP_HE_DATA2_GI_KNOWN; + data3 = mcs << 8; + data3 |= FIELD_PREP(IEEE80211_RADIOTAP_HE_DATA3_DATA_MCS, mcs); + + if (stbc) + data3 |= IEEE80211_RADIOTAP_HE_DATA3_STBC; + + switch (bw) { + case 0: + bw_val |= IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_20MHZ; + break; + case 1: + bw_val |= IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_40MHZ; + break; + case 2: + bw_val |= IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_80MHZ; + break; + case 3: + bw_val |= IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_160MHZ; + break; + } + + data5 |= FIELD_PREP(IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC, + bw_val); + + switch (gi) { + case 0: + data5 |= IEEE80211_RADIOTAP_HE_DATA5_GI_0_8; + break; + case 1: + data5 |= IEEE80211_RADIOTAP_HE_DATA5_GI_1_6; + break; + case 2: + data5 |= IEEE80211_RADIOTAP_HE_DATA5_GI_3_2; + break; + } + + data6 |= FIELD_PREP(IEEE80211_RADIOTAP_HE_DATA6_NSTS, nss); + he.data1 = cpu_to_le16(data1); + he.data2 = cpu_to_le16(data2); + he.data3 = cpu_to_le16(data3); + he.data5 = cpu_to_le16(data5); + he.data6 = cpu_to_le16(data6); + memcpy(rthdr + off, &he, sizeof(he)); + off += sizeof(he); + } + + hdr->it_len = cpu_to_le16(off); + + if (off > sizeof(rthdr)) + return -EINVAL; + + /* Remove RXPD */ + skb_pull(skb, rx_pkt_offset); + + /* Ensure enough headroom */ + if (skb_cow_head(skb, off)) + return -ENOMEM; + + /* Push radiotap header */ + skb_push(skb, off); + memcpy(skb->data, rthdr, off); + skb_reset_mac_header(skb); + skb->ip_summed = CHECKSUM_NONE; + skb->pkt_type = PACKET_OTHERHOST; + skb->dev = priv->netdev; + skb->protocol = htons(ETH_P_802_2); + netif_rx(skb); + return 0; +} + +/* Process received packet and pass to net stack; reuse or build skb as needed. */ +int nxpwifi_recv_packet(struct nxpwifi_private *priv, struct sk_buff *skb) +{ + struct nxpwifi_sta_node *src_node; + struct ethhdr *p_ethhdr; + + if (!skb) + return -ENOMEM; + + priv->stats.rx_bytes += skb->len; + priv->stats.rx_packets++; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + p_ethhdr = (void *)skb->data; + rcu_read_lock(); + src_node = nxpwifi_get_sta_entry(priv, p_ethhdr->h_source); + if (src_node) { + src_node->stats.last_rx = jiffies; + src_node->stats.rx_bytes += skb->len; + src_node->stats.rx_packets++; + } + rcu_read_unlock(); + } + + skb->dev = priv->netdev; + skb->protocol = eth_type_trans(skb, priv->netdev); + skb->ip_summed = CHECKSUM_NONE; + + netif_rx(skb); + return 0; +} + +/* IOCTL completion callback: wake waiters or process response as needed. */ +int nxpwifi_complete_cmd(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node) +{ + WARN_ON(!cmd_node->wait_q_enabled); + nxpwifi_dbg(adapter, CMD, "cmd completed: status=%d\n", + adapter->cmd_wait_q.status); + + *cmd_node->condition = true; + wake_up_interruptible(&adapter->cmd_wait_q.wait); + + return 0; +} + +/* Find STA entry by MAC under rcu_read_lock(); return NULL if not found. */ +struct nxpwifi_sta_node * +nxpwifi_get_sta_entry(struct nxpwifi_private *priv, const u8 *mac) +{ + struct nxpwifi_sta_node *node; + struct nxpwifi_sta_node *found = NULL; + + if (!mac) + return NULL; + list_for_each_entry_rcu(node, &priv->sta_list, list) { + if (!memcmp(node->mac_addr, mac, ETH_ALEN)) { + found = node; + break; + } + } + + return found; +} + +struct nxpwifi_sta_node * +nxpwifi_get_sta_entry_rcu(struct nxpwifi_private *priv, const u8 *mac) +{ + struct nxpwifi_sta_node *node; + + rcu_read_lock(); + node = nxpwifi_get_sta_entry(priv, mac); + rcu_read_unlock(); + + return node; +} + +/* Add STA entry by MAC; return existing entry or NULL on invalid MAC. */ +struct nxpwifi_sta_node * +nxpwifi_add_sta_entry(struct nxpwifi_private *priv, const u8 *mac) +{ + struct nxpwifi_sta_node *node; + + if (!mac) + return NULL; + + spin_lock_bh(&priv->sta_list_spinlock); + node = nxpwifi_get_sta_entry_rcu(priv, mac); + + if (node) + goto done; + + node = kzalloc_obj(*node, GFP_ATOMIC); + if (!node) + goto done; + + memcpy(node->mac_addr, mac, ETH_ALEN); + list_add_tail_rcu(&node->list, &priv->sta_list); + +done: + spin_unlock_bh(&priv->sta_list_spinlock); + return node; +} + +/* Parse HT cap IE from association IEs and set STA HT parameters. */ +void +nxpwifi_set_sta_ht_cap(struct nxpwifi_private *priv, const u8 *ies, + int ies_len, struct nxpwifi_sta_node *node) +{ + struct element *ht_cap_ie; + const struct ieee80211_ht_cap *ht_cap; + + if (!ies) + return; + + ht_cap_ie = (void *)cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, ies, + ies_len); + if (ht_cap_ie) { + ht_cap = (void *)(ht_cap_ie + 1); + node->is_11n_enabled = 1; + node->max_amsdu = le16_to_cpu(ht_cap->cap_info) & + IEEE80211_HT_CAP_MAX_AMSDU ? + NXPWIFI_TX_DATA_BUF_SIZE_8K : + NXPWIFI_TX_DATA_BUF_SIZE_4K; + } else { + node->is_11n_enabled = 0; + } +} + +/* Delete a station from list; called under cfg80211 mutex. */ + +void nxpwifi_del_sta_entry(struct nxpwifi_private *priv, const u8 *mac) +{ + struct nxpwifi_sta_node *node; + + list_for_each_entry_rcu(node, &priv->sta_list, list) { + if (!memcmp(node->mac_addr, mac, ETH_ALEN)) { + list_del_rcu(&node->list); + kfree_rcu(node, rcu); + break; + } + } +} + +/* Delete all stations from list. */ +void nxpwifi_del_all_sta_list(struct nxpwifi_private *priv) +{ + struct nxpwifi_sta_node *node, *tmp; + + spin_lock_bh(&priv->sta_list_spinlock); + + list_for_each_entry_safe(node, tmp, &priv->sta_list, list) { + list_del_rcu(&node->list); + kfree_rcu(node, rcu); + } + + INIT_LIST_HEAD(&priv->sta_list); + spin_unlock_bh(&priv->sta_list_spinlock); +} + +/* Add one histogram sample. */ +void nxpwifi_hist_data_add(struct nxpwifi_private *priv, + u8 rx_rate, s8 snr, s8 nflr) +{ + struct nxpwifi_histogram_data *phist_data = priv->hist_data; + + if (atomic_read(&phist_data->num_samples) > NXPWIFI_HIST_MAX_SAMPLES) + nxpwifi_hist_data_reset(priv); + nxpwifi_hist_data_set(priv, rx_rate, snr, nflr); +} + +/* function to add histogram record */ +void nxpwifi_hist_data_set(struct nxpwifi_private *priv, u8 rx_rate, s8 snr, + s8 nflr) +{ + struct nxpwifi_histogram_data *phist_data = priv->hist_data; + s8 nf = -nflr; + s8 rssi = snr - nflr; + + atomic_inc(&phist_data->num_samples); + atomic_inc(&phist_data->rx_rate[rx_rate]); + atomic_inc(&phist_data->snr[snr + 128]); + atomic_inc(&phist_data->noise_flr[nf + 128]); + atomic_inc(&phist_data->sig_str[rssi + 128]); +} + +/* function to reset histogram data during init/reset */ +void nxpwifi_hist_data_reset(struct nxpwifi_private *priv) +{ + int ix; + struct nxpwifi_histogram_data *phist_data = priv->hist_data; + + atomic_set(&phist_data->num_samples, 0); + for (ix = 0; ix < NXPWIFI_MAX_AC_RX_RATES; ix++) + atomic_set(&phist_data->rx_rate[ix], 0); + for (ix = 0; ix < NXPWIFI_MAX_SNR; ix++) + atomic_set(&phist_data->snr[ix], 0); + for (ix = 0; ix < NXPWIFI_MAX_NOISE_FLR; ix++) + atomic_set(&phist_data->noise_flr[ix], 0); + for (ix = 0; ix < NXPWIFI_MAX_SIG_STRENGTH; ix++) + atomic_set(&phist_data->sig_str[ix], 0); +} + +void *nxpwifi_alloc_dma_align_buf(int rx_len, gfp_t flags) +{ + struct sk_buff *skb; + int buf_len, pad; + + buf_len = rx_len + NXPWIFI_RX_HEADROOM + NXPWIFI_DMA_ALIGN_SZ; + + skb = __dev_alloc_skb(buf_len, flags); + + if (!skb) + return NULL; + + skb_reserve(skb, NXPWIFI_RX_HEADROOM); + + pad = NXPWIFI_ALIGN_ADDR(skb->data, NXPWIFI_DMA_ALIGN_SZ) - + (long)skb->data; + + skb_reserve(skb, pad); + + return skb; +} +EXPORT_SYMBOL_GPL(nxpwifi_alloc_dma_align_buf); + +void nxpwifi_fw_dump_event(struct nxpwifi_private *priv) +{ + nxpwifi_send_cmd(priv, HOST_CMD_FW_DUMP_EVENT, HOST_ACT_GEN_SET, + 0, NULL, true); +} +EXPORT_SYMBOL_GPL(nxpwifi_fw_dump_event); + +int nxpwifi_append_data_tlv(u16 id, u8 *data, int len, u8 *pos, u8 *cmd_end) +{ + struct nxpwifi_ie_types_data *tlv; + u16 header_len = sizeof(struct nxpwifi_ie_types_header); + + tlv = (struct nxpwifi_ie_types_data *)pos; + tlv->header.len = cpu_to_le16(len); + + if (id == WLAN_EID_EXT_HE_CAPABILITY) { + if ((pos + header_len + len + 1) > cmd_end) + return 0; + + tlv->header.type = cpu_to_le16(WLAN_EID_EXTENSION); + tlv->data[0] = WLAN_EID_EXT_HE_CAPABILITY; + memcpy(tlv->data + 1, data, len); + } else { + if ((pos + header_len + len) > cmd_end) + return 0; + + tlv->header.type = cpu_to_le16(id); + memcpy(tlv->data, data, len); + } + + return (header_len + len); +} + +static int nxpwifi_get_vdll_image(struct nxpwifi_adapter *adapter, u32 vdll_len) +{ + struct vdll_dnld_ctrl *ctrl = &adapter->vdll_ctrl; + bool req_fw = false; + u32 offset; + + if (ctrl->vdll_mem) { + nxpwifi_dbg(adapter, EVENT, + "VDLL mem is not empty: %p old_len=%d new_len=%d\n", + ctrl->vdll_mem, ctrl->vdll_len, vdll_len); + vfree(ctrl->vdll_mem); + ctrl->vdll_mem = NULL; + ctrl->vdll_len = 0; + } + + ctrl->vdll_mem = vmalloc(vdll_len); + if (!ctrl->vdll_mem) + return -ENOMEM; + + if (!adapter->firmware) { + req_fw = true; + if (request_firmware(&adapter->firmware, adapter->fw_name, + adapter->dev)) + return -ENOENT; + } + + if (adapter->firmware) { + if (vdll_len < adapter->firmware->size) { + offset = adapter->firmware->size - vdll_len; + memcpy(ctrl->vdll_mem, adapter->firmware->data + offset, + vdll_len); + } else { + nxpwifi_dbg(adapter, ERROR, + "Invalid VDLL length = %d, fw_len=%d\n", + vdll_len, (int)adapter->firmware->size); + return -EINVAL; + } + if (req_fw) { + release_firmware(adapter->firmware); + adapter->firmware = NULL; + } + } + + ctrl->vdll_len = vdll_len; + nxpwifi_dbg(adapter, MSG, "VDLL image: len=%d\n", ctrl->vdll_len); + + return 0; +} + +int nxpwifi_download_vdll_block(struct nxpwifi_adapter *adapter, + u8 *block, u16 block_len) +{ + struct vdll_dnld_ctrl *ctrl = &adapter->vdll_ctrl; + struct host_cmd_ds_command *host_cmd; + u16 msg_len = block_len + S_DS_GEN; + int ret = 0; + + skb_trim(ctrl->skb, 0); + skb_put_zero(ctrl->skb, msg_len); + + host_cmd = (struct host_cmd_ds_command *)(ctrl->skb->data); + + host_cmd->command = cpu_to_le16(HOST_CMD_VDLL); + host_cmd->seq_num = cpu_to_le16(0xFF00); + host_cmd->size = cpu_to_le16(msg_len); + memcpy(ctrl->skb->data + S_DS_GEN, block, block_len); + + skb_push(ctrl->skb, adapter->intf_hdr_len); + ret = adapter->if_ops.host_to_card(adapter, NXPWIFI_TYPE_VDLL, + ctrl->skb, NULL); + skb_pull(ctrl->skb, adapter->intf_hdr_len); + + if (ret) + nxpwifi_dbg(adapter, ERROR, + "Fail to download VDLL: block: %p, len: %d\n", + block, block_len); + + return ret; +} + +int nxpwifi_process_vdll_event(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct vdll_ind_event *vdll_evt = + (struct vdll_ind_event *)(skb->data + sizeof(u32)); + u16 type = le16_to_cpu(vdll_evt->type); + u16 vdll_id = le16_to_cpu(vdll_evt->vdll_id); + u32 offset = le32_to_cpu(vdll_evt->offset); + u16 block_len = le16_to_cpu(vdll_evt->block_len); + struct vdll_dnld_ctrl *ctrl = &adapter->vdll_ctrl; + int ret = 0; + + switch (type) { + case VDLL_IND_TYPE_REQ: + nxpwifi_dbg(adapter, EVENT, + "VDLL IND (REG): ID: %d, offset: %#x, len: %d\n", + vdll_id, offset, block_len); + if (offset <= ctrl->vdll_len) { + block_len = + min((u32)block_len, ctrl->vdll_len - offset); + if (!adapter->cmd_sent) { + ret = nxpwifi_download_vdll_block(adapter, + ctrl->vdll_mem + + offset, + block_len); + if (ret) + nxpwifi_dbg(adapter, ERROR, + "Download VDLL failed\n"); + } else { + nxpwifi_dbg(adapter, EVENT, + "Delay download VDLL block\n"); + ctrl->pending_block_len = block_len; + ctrl->pending_block = ctrl->vdll_mem + offset; + } + } else { + nxpwifi_dbg(adapter, ERROR, + "Err Req: offset=%#x, len=%d, vdll_len=%d\n", + offset, block_len, ctrl->vdll_len); + ret = -EINVAL; + } + break; + case VDLL_IND_TYPE_OFFSET: + nxpwifi_dbg(adapter, EVENT, + "VDLL IND (OFFSET): offset: %#x\n", offset); + ret = nxpwifi_get_vdll_image(adapter, offset); + break; + case VDLL_IND_TYPE_ERR_SIG: + case VDLL_IND_TYPE_ERR_ID: + case VDLL_IND_TYPE_SEC_ERR_ID: + nxpwifi_dbg(adapter, ERROR, "VDLL IND: error: %d\n", type); + break; + case VDLL_IND_TYPE_INTF_RESET: + nxpwifi_dbg(adapter, EVENT, "VDLL IND: interface reset\n"); + break; + default: + nxpwifi_dbg(adapter, ERROR, "VDLL IND: unknown type: %d", type); + ret = -EINVAL; + break; + } + + return ret; +} + +u64 nxpwifi_roc_cookie(struct nxpwifi_adapter *adapter) +{ + adapter->roc_cookie_counter++; + + /* wow, you wrapped 64 bits ... more likely a bug */ + if (WARN_ON(adapter->roc_cookie_counter == 0)) + adapter->roc_cookie_counter++; + + return adapter->roc_cookie_counter; +} + +static bool nxpwifi_can_queue_work(struct nxpwifi_adapter *adapter) +{ + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags) || + test_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags) || + test_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags)) { + nxpwifi_dbg(adapter, WARN, + "queueing nxpwifi work while going to suspend\n"); + return false; + } + + return true; +} + +void nxpwifi_queue_work(struct nxpwifi_adapter *adapter, + struct work_struct *work) +{ + if (!nxpwifi_can_queue_work(adapter)) + return; + + queue_work(adapter->workqueue, work); +} +EXPORT_SYMBOL(nxpwifi_queue_work); + +void nxpwifi_queue_delayed_work(struct nxpwifi_adapter *adapter, + struct delayed_work *dwork, + unsigned long delay) +{ + if (!nxpwifi_can_queue_work(adapter)) + return; + + queue_delayed_work(adapter->workqueue, dwork, delay); +} +EXPORT_SYMBOL(nxpwifi_queue_delayed_work); + +void nxpwifi_queue_wiphy_work(struct nxpwifi_adapter *adapter, + struct wiphy_work *work) +{ + if (!nxpwifi_can_queue_work(adapter)) + return; + + wiphy_work_queue(adapter->wiphy, work); +} + +void nxpwifi_queue_delayed_wiphy_work(struct nxpwifi_adapter *adapter, + struct wiphy_delayed_work *dwork, + unsigned long delay) +{ + if (!nxpwifi_can_queue_work(adapter)) + return; + + wiphy_delayed_work_queue(adapter->wiphy, dwork, delay); +} diff --git a/drivers/net/wireless/nxp/nxpwifi/util.h b/drivers/net/wireless/nxp/nxpwifi/util.h new file mode 100644 index 000000000000..1a47c8c5b530 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/util.h @@ -0,0 +1,155 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * NXP Wireless LAN device driver: utility functions + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_UTIL_H_ +#define _NXPWIFI_UTIL_H_ +#include "fw.h" + +struct nxpwifi_adapter; + +struct nxpwifi_private; + +struct nxpwifi_dma_mapping { + dma_addr_t addr; + size_t len; +}; + +struct nxpwifi_cb { + struct nxpwifi_dma_mapping dma_mapping; + union { + struct nxpwifi_rxinfo rx_info; + struct nxpwifi_txinfo tx_info; + }; +}; + +/* size/addr for nxpwifi_debug_info */ +#define item_size(n) (sizeof_field(struct nxpwifi_debug_info, n)) +#define item_addr(n) (offsetof(struct nxpwifi_debug_info, n)) + +/* size/addr for struct nxpwifi_adapter */ +#define adapter_item_size(n) (sizeof_field(struct nxpwifi_adapter, n)) +#define adapter_item_addr(n) (offsetof(struct nxpwifi_adapter, n)) + +struct nxpwifi_debug_data { + char name[32]; /* variable/array name */ + u32 size; /* size of the variable/array */ + size_t addr; /* address of the variable/array */ + int num; /* number of variables in an array */ +}; + +static inline struct nxpwifi_rxinfo *NXPWIFI_SKB_RXCB(struct sk_buff *skb) +{ + struct nxpwifi_cb *cb = (struct nxpwifi_cb *)skb->cb; + + BUILD_BUG_ON(sizeof(struct nxpwifi_cb) > sizeof(skb->cb)); + return &cb->rx_info; +} + +static inline struct nxpwifi_txinfo *NXPWIFI_SKB_TXCB(struct sk_buff *skb) +{ + struct nxpwifi_cb *cb = (struct nxpwifi_cb *)skb->cb; + + return &cb->tx_info; +} + +static inline void nxpwifi_store_mapping(struct sk_buff *skb, + struct nxpwifi_dma_mapping *mapping) +{ + struct nxpwifi_cb *cb = (struct nxpwifi_cb *)skb->cb; + + memcpy(&cb->dma_mapping, mapping, sizeof(*mapping)); +} + +static inline void nxpwifi_get_mapping(struct sk_buff *skb, + struct nxpwifi_dma_mapping *mapping) +{ + struct nxpwifi_cb *cb = (struct nxpwifi_cb *)skb->cb; + + memcpy(mapping, &cb->dma_mapping, sizeof(*mapping)); +} + +static inline dma_addr_t NXPWIFI_SKB_DMA_ADDR(struct sk_buff *skb) +{ + struct nxpwifi_dma_mapping mapping; + + nxpwifi_get_mapping(skb, &mapping); + + return mapping.addr; +} + +int nxpwifi_debug_info_to_buffer(struct nxpwifi_private *priv, char *buf, + struct nxpwifi_debug_info *info); + +static inline void le16_unaligned_add_cpu(__le16 *var, u16 val) +{ + put_unaligned_le16(get_unaligned_le16(var) + val, var); +} + +/* + * Iterate over TLVs safely. + * Ensures no out-of-bound access even if firmware sends malformed data. + */ +#define nxpwifi_for_each_tlv(tlv, buf, buf_len) \ + for (tlv = (const struct nxpwifi_tlv *)(buf); \ + (u8 *)(tlv) + sizeof(*tlv) <= (u8 *)(buf) + (buf_len) && \ + (u8 *)(tlv) + sizeof(*tlv) + le16_to_cpu(tlv->len) <= \ + (u8 *)(buf) + (buf_len); \ + tlv = (const struct nxpwifi_tlv *)((u8 *)(tlv) + sizeof(*tlv) + \ + le16_to_cpu(tlv->len))) + +/* Return first TLV matching @type in given buffer. */ +static inline const struct nxpwifi_tlv * +nxpwifi_find_tlv(u16 type, const u8 *buf, u32 buf_len) +{ + const struct nxpwifi_tlv *tlv; + + nxpwifi_for_each_tlv(tlv, buf, buf_len) { + if (le16_to_cpu(tlv->type) == type) + return tlv; + } + + return NULL; +} + +int nxpwifi_append_data_tlv(u16 id, u8 *data, int len, u8 *pos, u8 *cmd_end); + +int nxpwifi_download_vdll_block(struct nxpwifi_adapter *adapter, + u8 *block, u16 block_len); + +int nxpwifi_process_vdll_event(struct nxpwifi_private *priv, + struct sk_buff *skb); + +u64 nxpwifi_roc_cookie(struct nxpwifi_adapter *adapter); + +void nxpwifi_queue_work(struct nxpwifi_adapter *adapter, + struct work_struct *work); + +void nxpwifi_queue_delayed_work(struct nxpwifi_adapter *adapter, + struct delayed_work *dwork, + unsigned long delay); + +void nxpwifi_queue_wiphy_work(struct nxpwifi_adapter *adapter, + struct wiphy_work *work); + +void nxpwifi_queue_delayed_wiphy_work(struct nxpwifi_adapter *adapter, + struct wiphy_delayed_work *dwork, + unsigned long delay); + +/* + * Firmware cannot run AP and STA on different channels simultaneously, + * and doing so may trigger a crash. Check whether check_chan can be set + * safely; return true if allowed, false if another channel is already + * active in firmware. + */ +bool nxpwifi_is_channel_setting_allowable(struct nxpwifi_private *priv, + struct ieee80211_channel *check_chan); + +void nxpwifi_convert_chan_to_band_cfg(struct nxpwifi_private *priv, + u8 *band_cfg, + struct cfg80211_chan_def *chan_def); + +#endif /* !_NXPWIFI_UTIL_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/wmm.c b/drivers/net/wireless/nxp/nxpwifi/wmm.c new file mode 100644 index 000000000000..bb4bb724b6fb --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/wmm.c @@ -0,0 +1,1318 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: WMM + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" + +/* Maximum value FW can accept for driver delay in packet transmission */ +#define DRV_PKT_DELAY_TO_FW_MAX 512 + +#define WMM_QUEUED_PACKET_LOWER_LIMIT 180 + +#define WMM_QUEUED_PACKET_UPPER_LIMIT 200 + +/* Offset for TOS field in the IP header */ +#define IPTOS_OFFSET 5 + +static bool disable_tx_amsdu; + +/* + * This table inverses the tos_to_tid operation to get a priority + * which is in sequential order, and can be compared. + * Use this to compare the priority of two different TIDs. + */ +static const u8 tos_to_tid_inv[] = { + 0x02, /* from tos_to_tid[2] = 0 */ + 0x00, /* from tos_to_tid[0] = 1 */ + 0x01, /* from tos_to_tid[1] = 2 */ + 0x03, + 0x04, + 0x05, + 0x06, + 0x07 +}; + +/* WMM information element */ +static const u8 wmm_info_ie[] = { WLAN_EID_VENDOR_SPECIFIC, 0x07, + 0x00, 0x50, 0xf2, 0x02, + 0x00, 0x01, 0x00 +}; + +static const u8 wmm_aci_to_qidx_map[] = { WMM_AC_BE, + WMM_AC_BK, + WMM_AC_VI, + WMM_AC_VO +}; + +static u8 tos_to_tid[] = { + /* TID DSCP_P2 DSCP_P1 DSCP_P0 WMM_AC */ + 0x01, /* 0 1 0 AC_BK */ + 0x02, /* 0 0 0 AC_BK */ + 0x00, /* 0 0 1 AC_BE */ + 0x03, /* 0 1 1 AC_BE */ + 0x04, /* 1 0 0 AC_VI */ + 0x05, /* 1 0 1 AC_VI */ + 0x06, /* 1 1 0 AC_VO */ + 0x07 /* 1 1 1 AC_VO */ +}; + +static u8 ac_to_tid[4][2] = { {1, 2}, {0, 3}, {4, 5}, {6, 7} }; + +/* Debug prints the priority parameters for a WMM AC. */ +static void +nxpwifi_wmm_ac_debug_print(const struct ieee80211_wmm_ac_param *ac_param) +{ + static const char * const ac_str[] = { "BK", "BE", "VI", "VO" }; + + pr_debug("info: WMM AC_%s: ACI=%d, ACM=%d, Aifsn=%d, ", + ac_str[wmm_aci_to_qidx_map[(ac_param->aci_aifsn + & NXPWIFI_ACI) >> 5]], + (ac_param->aci_aifsn & NXPWIFI_ACI) >> 5, + (ac_param->aci_aifsn & NXPWIFI_ACM) >> 4, + ac_param->aci_aifsn & NXPWIFI_AIFSN); + pr_debug("EcwMin=%d, EcwMax=%d, TxopLimit=%d\n", + ac_param->cw & NXPWIFI_ECW_MIN, + (ac_param->cw & NXPWIFI_ECW_MAX) >> 4, + le16_to_cpu(ac_param->txop_limit)); +} + +/* Allocates a route address list. */ +static struct nxpwifi_ra_list_tbl * +nxpwifi_wmm_allocate_ralist_node(struct nxpwifi_adapter *adapter, const u8 *ra) +{ + struct nxpwifi_ra_list_tbl *ra_list; + + ra_list = kzalloc_obj(*ra_list, GFP_ATOMIC); + if (!ra_list) + return NULL; + + INIT_LIST_HEAD(&ra_list->list); + skb_queue_head_init(&ra_list->skb_head); + + memcpy(ra_list->ra, ra, ETH_ALEN); + + ra_list->total_pkt_count = 0; + + nxpwifi_dbg(adapter, INFO, "info: allocated ra_list %p\n", ra_list); + + return ra_list; +} + +/* + * Returns random no between 16 and 32 to be used as threshold for no of + * packets after which BA setup is initiated. + */ +static u8 nxpwifi_get_random_ba_threshold(void) +{ + u64 ns; + /* + * setup ba_packet_threshold here random number between + * [BA_SETUP_PACKET_OFFSET, + * BA_SETUP_PACKET_OFFSET+BA_SETUP_MAX_PACKET_THRESHOLD-1] + */ + ns = ktime_get_ns(); + ns += (ns >> 32) + (ns >> 16); + + return ((u8)ns % BA_SETUP_MAX_PACKET_THRESHOLD) + BA_SETUP_PACKET_OFFSET; +} + +/* Allocates and adds a RA list for all TIDs with the given RA. */ +void nxpwifi_ralist_add(struct nxpwifi_private *priv, const u8 *ra) +{ + int i; + struct nxpwifi_ra_list_tbl *ra_list; + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_sta_node *node; + + for (i = 0; i < MAX_NUM_TID; ++i) { + ra_list = nxpwifi_wmm_allocate_ralist_node(adapter, ra); + nxpwifi_dbg(adapter, INFO, + "info: created ra_list %p\n", ra_list); + + if (!ra_list) + break; + + ra_list->is_11n_enabled = 0; + ra_list->ba_status = BA_SETUP_NONE; + ra_list->amsdu_in_ampdu = false; + if (!nxpwifi_queuing_ra_based(priv)) { + ra_list->is_11n_enabled = IS_11N_ENABLED(priv); + } else { + rcu_read_lock(); + node = nxpwifi_get_sta_entry(priv, ra); + if (node) + ra_list->tx_paused = node->tx_pause; + ra_list->is_11n_enabled = + nxpwifi_is_sta_11n_enabled(priv, node); + if (ra_list->is_11n_enabled) + ra_list->max_amsdu = node->max_amsdu; + rcu_read_unlock(); + } + + nxpwifi_dbg(adapter, DATA, "data: ralist %p: is_11n_enabled=%d\n", + ra_list, ra_list->is_11n_enabled); + + if (ra_list->is_11n_enabled) { + ra_list->ba_pkt_count = 0; + ra_list->ba_packet_thr = + nxpwifi_get_random_ba_threshold(); + } + list_add_tail(&ra_list->list, + &priv->wmm.tid_tbl_ptr[i].ra_list); + } +} + +/* Sets the WMM queue priorities to their default values. */ +static void nxpwifi_wmm_default_queue_priorities(struct nxpwifi_private *priv) +{ + /* Default queue priorities: VO->VI->BE->BK */ + priv->wmm.queue_priority[0] = WMM_AC_VO; + priv->wmm.queue_priority[1] = WMM_AC_VI; + priv->wmm.queue_priority[2] = WMM_AC_BE; + priv->wmm.queue_priority[3] = WMM_AC_BK; +} + +/* Map ACs to TIDs. */ +static void +nxpwifi_wmm_queue_priorities_tid(struct nxpwifi_private *priv) +{ + struct nxpwifi_wmm_desc *wmm = &priv->wmm; + u8 *queue_priority = wmm->queue_priority; + int i; + + for (i = 0; i < 4; ++i) { + tos_to_tid[7 - (i * 2)] = ac_to_tid[queue_priority[i]][1]; + tos_to_tid[6 - (i * 2)] = ac_to_tid[queue_priority[i]][0]; + } + + for (i = 0; i < MAX_NUM_TID; ++i) + priv->tos_to_tid_inv[tos_to_tid[i]] = (u8)i; + + atomic_set(&wmm->highest_queued_prio, HIGH_PRIO_TID); +} + +/* Initializes WMM priority queues. */ +void +nxpwifi_wmm_setup_queue_priorities(struct nxpwifi_private *priv, + struct ieee80211_wmm_param_ie *wmm_ie) +{ + u16 cw_min, avg_back_off, tmp[4]; + u32 i, j, num_ac; + u8 ac_idx; + + if (!wmm_ie || !priv->wmm_enabled) { + /* WMM is not enabled, just set the defaults and return */ + nxpwifi_wmm_default_queue_priorities(priv); + return; + } + + nxpwifi_dbg(priv->adapter, INFO, + "info: WMM Parameter element: version=%d,\t" + "qos_info Parameter Set Count=%d, Reserved=%#x\n", + wmm_ie->version, wmm_ie->qos_info & + IEEE80211_WMM_IE_AP_QOSINFO_PARAM_SET_CNT_MASK, + wmm_ie->reserved); + + for (num_ac = 0; num_ac < ARRAY_SIZE(wmm_ie->ac); num_ac++) { + u8 ecw = wmm_ie->ac[num_ac].cw; + u8 aci_aifsn = wmm_ie->ac[num_ac].aci_aifsn; + + cw_min = (1 << (ecw & NXPWIFI_ECW_MIN)) - 1; + avg_back_off = (cw_min >> 1) + (aci_aifsn & NXPWIFI_AIFSN); + + ac_idx = wmm_aci_to_qidx_map[(aci_aifsn & NXPWIFI_ACI) >> 5]; + priv->wmm.queue_priority[ac_idx] = ac_idx; + tmp[ac_idx] = avg_back_off; + + nxpwifi_dbg(priv->adapter, INFO, + "info: WMM: CWmax=%d CWmin=%d Avg Back-off=%d\n", + (1 << ((ecw & NXPWIFI_ECW_MAX) >> 4)) - 1, + cw_min, avg_back_off); + nxpwifi_wmm_ac_debug_print(&wmm_ie->ac[num_ac]); + } + + /* Bubble sort */ + for (i = 0; i < num_ac; i++) { + for (j = 1; j < num_ac - i; j++) { + if (tmp[j - 1] > tmp[j]) { + swap(tmp[j - 1], tmp[j]); + swap(priv->wmm.queue_priority[j - 1], + priv->wmm.queue_priority[j]); + } else if (tmp[j - 1] == tmp[j]) { + if (priv->wmm.queue_priority[j - 1] + < priv->wmm.queue_priority[j]) + swap(priv->wmm.queue_priority[j - 1], + priv->wmm.queue_priority[j]); + } + } + } + + nxpwifi_wmm_queue_priorities_tid(priv); +} + +/* Evaluates whether or not an AC is to be downgraded. */ +static enum nxpwifi_wmm_ac_e +nxpwifi_wmm_eval_downgrade_ac(struct nxpwifi_private *priv, + enum nxpwifi_wmm_ac_e eval_ac) +{ + int down_ac; + enum nxpwifi_wmm_ac_e ret_ac; + struct nxpwifi_wmm_ac_status *ac_status; + + ac_status = &priv->wmm.ac_status[eval_ac]; + + if (!ac_status->disabled) + /* Okay to use this AC, its enabled */ + return eval_ac; + + /* Setup a default return value of the lowest priority */ + ret_ac = WMM_AC_BK; + + /* + * Find the highest AC that is enabled and does not require + * admission control. The spec disallows downgrading to an AC, + * which is enabled due to a completed admission control. + * Unadmitted traffic is not to be sent on an AC with admitted + * traffic. + */ + for (down_ac = WMM_AC_BK; down_ac < eval_ac; down_ac++) { + ac_status = &priv->wmm.ac_status[down_ac]; + + if (!ac_status->disabled && !ac_status->flow_required) + /* + * AC is enabled and does not require admission + * control + */ + ret_ac = (enum nxpwifi_wmm_ac_e)down_ac; + } + + return ret_ac; +} + +/* Downgrades WMM priority queue. */ +void +nxpwifi_wmm_setup_ac_downgrade(struct nxpwifi_private *priv) +{ + int ac_val; + + nxpwifi_dbg(priv->adapter, INFO, "info: WMM: AC Priorities:\t" + "BK(0), BE(1), VI(2), VO(3)\n"); + + if (!priv->wmm_enabled) { + /* WMM is not enabled, default priorities */ + for (ac_val = WMM_AC_BK; ac_val <= WMM_AC_VO; ac_val++) + priv->wmm.ac_down_graded_vals[ac_val] = + (enum nxpwifi_wmm_ac_e)ac_val; + } else { + for (ac_val = WMM_AC_BK; ac_val <= WMM_AC_VO; ac_val++) { + priv->wmm.ac_down_graded_vals[ac_val] = + nxpwifi_wmm_eval_downgrade_ac + (priv, (enum nxpwifi_wmm_ac_e)ac_val); + nxpwifi_dbg(priv->adapter, INFO, + "info: WMM: AC PRIO %d maps to %d\n", + ac_val, + priv->wmm.ac_down_graded_vals[ac_val]); + } + } +} + +/* Converts the IP TOS field to an WMM AC Queue assignment. */ +static enum nxpwifi_wmm_ac_e +nxpwifi_wmm_convert_tos_to_ac(struct nxpwifi_adapter *adapter, u32 tos) +{ + /* Map of TOS UP values to WMM AC */ + static const enum nxpwifi_wmm_ac_e tos_to_ac[] = { + WMM_AC_BE, + WMM_AC_BK, + WMM_AC_BK, + WMM_AC_BE, + WMM_AC_VI, + WMM_AC_VI, + WMM_AC_VO, + WMM_AC_VO + }; + + if (tos >= ARRAY_SIZE(tos_to_ac)) + return WMM_AC_BE; + + return tos_to_ac[tos]; +} + +/* + * Evaluates a given TID and downgrades it to a lower TID if the WMM Parameter + * element received from the AP indicates that the AP is disabled (due to call + * admission control (ACM bit). + */ +u8 nxpwifi_wmm_downgrade_tid(struct nxpwifi_private *priv, u32 tid) +{ + enum nxpwifi_wmm_ac_e ac, ac_down; + u8 new_tid; + + ac = nxpwifi_wmm_convert_tos_to_ac(priv->adapter, tid); + ac_down = priv->wmm.ac_down_graded_vals[ac]; + + /* + * Send the index to tid array, picking from the array will be + * taken care by dequeuing function + */ + new_tid = ac_to_tid[ac_down][tid % 2]; + + return new_tid; +} + +/* Initializes the WMM state information and the WMM data path queues. */ +void +nxpwifi_wmm_init(struct nxpwifi_adapter *adapter) +{ + int i, j; + struct nxpwifi_private *priv; + + for (j = 0; j < adapter->priv_num; ++j) { + priv = adapter->priv[j]; + + for (i = 0; i < MAX_NUM_TID; ++i) { + if (!disable_tx_amsdu && + adapter->tx_buf_size > NXPWIFI_TX_DATA_BUF_SIZE_2K) + priv->aggr_prio_tbl[i].amsdu = + priv->tos_to_tid_inv[i]; + else + priv->aggr_prio_tbl[i].amsdu = + BA_STREAM_NOT_ALLOWED; + priv->aggr_prio_tbl[i].ampdu_ap = + priv->tos_to_tid_inv[i]; + priv->aggr_prio_tbl[i].ampdu_user = + priv->tos_to_tid_inv[i]; + } + + priv->aggr_prio_tbl[6].amsdu = + priv->aggr_prio_tbl[6].ampdu_ap = + priv->aggr_prio_tbl[6].ampdu_user = + BA_STREAM_NOT_ALLOWED; + + priv->aggr_prio_tbl[7].amsdu = + priv->aggr_prio_tbl[7].ampdu_ap = + priv->aggr_prio_tbl[7].ampdu_user = + BA_STREAM_NOT_ALLOWED; + + nxpwifi_set_ba_params(priv); + nxpwifi_reset_11n_rx_seq_num(priv); + + priv->wmm.drv_pkt_delay_max = NXPWIFI_WMM_DRV_DELAY_MAX; + atomic_set(&priv->wmm.tx_pkts_queued, 0); + atomic_set(&priv->wmm.highest_queued_prio, HIGH_PRIO_TID); + } +} + +bool nxpwifi_bypass_txlist_empty(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + int i; + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (!skb_queue_empty(&priv->bypass_txq)) + return false; + } + + return true; +} + +/* Checks if WMM Tx queue is empty. */ +bool nxpwifi_wmm_lists_empty(struct nxpwifi_adapter *adapter) +{ + int i; + struct nxpwifi_private *priv; + + for (i = 0; i < adapter->priv_num; ++i) { + priv = adapter->priv[i]; + if (!priv->port_open) + continue; + if (atomic_read(&priv->wmm.tx_pkts_queued)) + return false; + } + + return true; +} + +/* Deletes all packets in an RA list node. */ +static void +nxpwifi_wmm_del_pkts_in_ralist_node(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ra_list) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct sk_buff *skb, *tmp; + + skb_queue_walk_safe(&ra_list->skb_head, skb, tmp) { + skb_unlink(skb, &ra_list->skb_head); + nxpwifi_write_data_complete(adapter, skb, 0, -1); + } +} + +/* Deletes all packets in an RA list. */ +static void +nxpwifi_wmm_del_pkts_in_ralist(struct nxpwifi_private *priv, + struct list_head *ra_list_head) +{ + struct nxpwifi_ra_list_tbl *ra_list; + + list_for_each_entry(ra_list, ra_list_head, list) + nxpwifi_wmm_del_pkts_in_ralist_node(priv, ra_list); +} + +/* Deletes all packets in all RA lists. */ +static void nxpwifi_wmm_cleanup_queues(struct nxpwifi_private *priv) +{ + int i; + + for (i = 0; i < MAX_NUM_TID; i++) + nxpwifi_wmm_del_pkts_in_ralist + (priv, &priv->wmm.tid_tbl_ptr[i].ra_list); + + atomic_set(&priv->wmm.tx_pkts_queued, 0); + atomic_set(&priv->wmm.highest_queued_prio, HIGH_PRIO_TID); +} + +/* Deletes all route addresses from all RA lists. */ +static void nxpwifi_wmm_delete_all_ralist(struct nxpwifi_private *priv) +{ + struct nxpwifi_ra_list_tbl *ra_list, *tmp_node; + int i; + + for (i = 0; i < MAX_NUM_TID; ++i) { + nxpwifi_dbg(priv->adapter, INFO, + "info: ra_list: freeing buf for tid %d\n", i); + list_for_each_entry_safe(ra_list, tmp_node, + &priv->wmm.tid_tbl_ptr[i].ra_list, + list) { + list_del(&ra_list->list); + kfree(ra_list); + } + + INIT_LIST_HEAD(&priv->wmm.tid_tbl_ptr[i].ra_list); + } +} + +static int nxpwifi_free_ack_frame(int id, void *p, void *data) +{ + pr_warn("Have pending ack frames!\n"); + kfree_skb(p); + return 0; +} + +/* Cleans up the Tx and Rx queues. */ +void +nxpwifi_clean_txrx(struct nxpwifi_private *priv) +{ + struct sk_buff *skb, *tmp; + unsigned long index; + void *entry; + + nxpwifi_11n_cleanup_reorder_tbl(priv); + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + nxpwifi_wmm_cleanup_queues(priv); + nxpwifi_11n_delete_all_tx_ba_stream_tbl(priv); + + if (priv->adapter->if_ops.cleanup_mpa_buf) + priv->adapter->if_ops.cleanup_mpa_buf(priv->adapter); + + nxpwifi_wmm_delete_all_ralist(priv); + memcpy(tos_to_tid, ac_to_tid, sizeof(tos_to_tid)); + + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + + skb_queue_walk_safe(&priv->bypass_txq, skb, tmp) { + skb_unlink(skb, &priv->bypass_txq); + nxpwifi_write_data_complete(priv->adapter, skb, 0, -1); + } + atomic_set(&priv->adapter->bypass_tx_pending, 0); + + xa_for_each(&priv->ack_status_frames, index, entry) { + nxpwifi_free_ack_frame(index, entry, NULL); + xa_erase(&priv->ack_status_frames, index); + } + + xa_destroy(&priv->ack_status_frames); +} + +/* Retrieves a particular RA list node, matching with the given TID and RA address. */ +struct nxpwifi_ra_list_tbl * +nxpwifi_wmm_get_ralist_node(struct nxpwifi_private *priv, u8 tid, + const u8 *ra_addr) +{ + struct nxpwifi_ra_list_tbl *ra_list; + + list_for_each_entry(ra_list, &priv->wmm.tid_tbl_ptr[tid].ra_list, + list) { + if (!memcmp(ra_list->ra, ra_addr, ETH_ALEN)) + return ra_list; + } + + return NULL; +} + +void nxpwifi_update_ralist_tx_pause(struct nxpwifi_private *priv, u8 *mac, + u8 tx_pause) +{ + struct nxpwifi_ra_list_tbl *ra_list; + u32 pkt_cnt = 0, tx_pkts_queued; + int i; + + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + for (i = 0; i < MAX_NUM_TID; ++i) { + ra_list = nxpwifi_wmm_get_ralist_node(priv, i, mac); + if (ra_list && ra_list->tx_paused != tx_pause) { + pkt_cnt += ra_list->total_pkt_count; + ra_list->tx_paused = tx_pause; + if (tx_pause) + priv->wmm.pkts_paused[i] += + ra_list->total_pkt_count; + else + priv->wmm.pkts_paused[i] -= + ra_list->total_pkt_count; + } + } + + if (pkt_cnt) { + tx_pkts_queued = atomic_read(&priv->wmm.tx_pkts_queued); + if (tx_pause) + tx_pkts_queued -= pkt_cnt; + else + tx_pkts_queued += pkt_cnt; + + atomic_set(&priv->wmm.tx_pkts_queued, tx_pkts_queued); + atomic_set(&priv->wmm.highest_queued_prio, HIGH_PRIO_TID); + } + spin_unlock_bh(&priv->wmm.ra_list_spinlock); +} + +/* Retrieves an RA list node for a given TID and RA address pair. */ +struct nxpwifi_ra_list_tbl * +nxpwifi_wmm_get_queue_raptr(struct nxpwifi_private *priv, u8 tid, + const u8 *ra_addr) +{ + struct nxpwifi_ra_list_tbl *ra_list; + + ra_list = nxpwifi_wmm_get_ralist_node(priv, tid, ra_addr); + if (ra_list) + return ra_list; + nxpwifi_ralist_add(priv, ra_addr); + + return nxpwifi_wmm_get_ralist_node(priv, tid, ra_addr); +} + +/* Deletes RA list nodes for given mac for all TIDs. */ +void +nxpwifi_wmm_del_peer_ra_list(struct nxpwifi_private *priv, const u8 *ra_addr) +{ + struct nxpwifi_ra_list_tbl *ra_list; + int i; + + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + for (i = 0; i < MAX_NUM_TID; ++i) { + ra_list = nxpwifi_wmm_get_ralist_node(priv, i, ra_addr); + + if (!ra_list) + continue; + nxpwifi_wmm_del_pkts_in_ralist_node(priv, ra_list); + if (ra_list->tx_paused) + priv->wmm.pkts_paused[i] -= ra_list->total_pkt_count; + else + atomic_sub(ra_list->total_pkt_count, + &priv->wmm.tx_pkts_queued); + list_del(&ra_list->list); + kfree(ra_list); + } + spin_unlock_bh(&priv->wmm.ra_list_spinlock); +} + +/* Checks if a particular RA list node exists in a given TID table index. */ +bool nxpwifi_is_ralist_valid(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ra_list, int ptr_index) +{ + struct nxpwifi_ra_list_tbl *rlist; + + list_for_each_entry(rlist, &priv->wmm.tid_tbl_ptr[ptr_index].ra_list, + list) { + if (rlist == ra_list) + return true; + } + + return false; +} + +/* Adds a packet to bypass TX queue. */ +void +nxpwifi_wmm_add_buf_bypass_txqueue(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + skb_queue_tail(&priv->bypass_txq, skb); +} + +/* Adds a packet to WMM queue. */ +void +nxpwifi_wmm_add_buf_txqueue(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u32 tid; + struct nxpwifi_ra_list_tbl *ra_list = NULL; + struct list_head list_head; + u8 ra[ETH_ALEN], tid_down; + struct ethhdr *eth_hdr = (struct ethhdr *)skb->data; + + memcpy(ra, eth_hdr->h_dest, ETH_ALEN); + + if (!priv->media_connected && !nxpwifi_is_skb_mgmt_frame(skb)) { + nxpwifi_dbg(adapter, DATA, "data: drop packet in disconnect\n"); + nxpwifi_write_data_complete(adapter, skb, 0, -1); + return; + } + + tid = skb->priority; + + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + tid_down = nxpwifi_wmm_downgrade_tid(priv, tid); + + /* + * In case of infra as we have already created the list during + * association we just don't have to call get_queue_raptr, we will + * have only 1 raptr for a tid in case of infra + */ + if (!nxpwifi_queuing_ra_based(priv) && + !nxpwifi_is_skb_mgmt_frame(skb)) { + list_head = priv->wmm.tid_tbl_ptr[tid_down].ra_list; + ra_list = list_first_entry_or_null(&list_head, + struct nxpwifi_ra_list_tbl, + list); + } else { + memcpy(ra, skb->data, ETH_ALEN); + if (is_multicast_ether_addr(ra) || + nxpwifi_is_skb_mgmt_frame(skb)) + eth_broadcast_addr(ra); + ra_list = nxpwifi_wmm_get_queue_raptr(priv, tid_down, ra); + } + + if (!ra_list) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_write_data_complete(adapter, skb, 0, -1); + return; + } + + skb_queue_tail(&ra_list->skb_head, skb); + + ra_list->ba_pkt_count++; + ra_list->total_pkt_count++; + + if (atomic_read(&priv->wmm.highest_queued_prio) < + priv->tos_to_tid_inv[tid_down]) + atomic_set(&priv->wmm.highest_queued_prio, + priv->tos_to_tid_inv[tid_down]); + + if (ra_list->tx_paused) + priv->wmm.pkts_paused[tid_down]++; + else + atomic_inc(&priv->wmm.tx_pkts_queued); + + spin_unlock_bh(&priv->wmm.ra_list_spinlock); +} + +/* Processes the get WMM status command response from firmware. */ +int nxpwifi_ret_wmm_get_status(struct nxpwifi_private *priv, + const struct host_cmd_ds_command *resp) +{ + u8 *curr; + u16 resp_len = le16_to_cpu(resp->size), tlv_len; + bool valid = true; + + struct nxpwifi_ie_types_data *tlv_hdr; + struct nxpwifi_ie_types_wmm_queue_status *wmm_qs; + struct ieee80211_wmm_param_ie *wmm_param_ie = NULL; + struct nxpwifi_wmm_ac_status *ac_status; + u32 base; + + nxpwifi_dbg(priv->adapter, INFO, + "info: WMM: WMM_GET_STATUS cmdresp received: %d\n", + resp_len); + + base = offsetofend(struct host_cmd_ds_command, params.get_wmm_status); + + if (resp_len < base) + return -EINVAL; + + curr = (u8 *)&resp->params.get_wmm_status; + resp_len -= base; + + while (resp_len >= sizeof(tlv_hdr->header) && valid) { + tlv_hdr = (struct nxpwifi_ie_types_data *)curr; + tlv_len = le16_to_cpu(tlv_hdr->header.len); + + if (resp_len < tlv_len + sizeof(tlv_hdr->header)) + break; + + switch (le16_to_cpu(tlv_hdr->header.type)) { + case TLV_TYPE_WMMQSTATUS: + if (tlv_len < + sizeof(struct nxpwifi_ie_types_wmm_queue_status) - + sizeof(tlv_hdr->header)) + break; + + wmm_qs = + (struct nxpwifi_ie_types_wmm_queue_status *)tlv_hdr; + + if (wmm_qs->queue_index >= IEEE80211_NUM_ACS) + break; + + ac_status = &priv->wmm.ac_status[wmm_qs->queue_index]; + ac_status->disabled = wmm_qs->disabled; + ac_status->flow_required = wmm_qs->flow_required; + ac_status->flow_created = wmm_qs->flow_created; + break; + + case WLAN_EID_VENDOR_SPECIFIC: + /* Need at least OUI(4) + WMM fixed fields */ + if (tlv_len + sizeof(tlv_hdr->header) < + offsetofend(struct ieee80211_wmm_param_ie, + qos_info)) + break; + + wmm_param_ie = + (struct ieee80211_wmm_param_ie *)(curr + 2); + + if (tlv_len + 2 > sizeof(struct ieee80211_wmm_param_ie)) + break; + + wmm_param_ie->len = (u8)tlv_len; + wmm_param_ie->element_id = WLAN_EID_VENDOR_SPECIFIC; + + memcpy(&priv->curr_bss_params.bss_descriptor.wmm_ie, + wmm_param_ie, wmm_param_ie->len + 2); + break; + + default: + valid = false; + break; + } + + curr += sizeof(tlv_hdr->header) + tlv_len; + resp_len -= sizeof(tlv_hdr->header) + tlv_len; + } + + nxpwifi_wmm_setup_queue_priorities(priv, wmm_param_ie); + nxpwifi_wmm_setup_ac_downgrade(priv); + + return 0; +} + +/* + * Callback handler from the command module to allow insertion of a WMM TLV. + * + * If the BSS we are associating to supports WMM, this function adds the + * required WMM Information element to the association request command buffer in + * the form of a NXP extended IEEE element. + */ +u32 +nxpwifi_wmm_process_association_req(struct nxpwifi_private *priv, + u8 **assoc_buf, + struct ieee80211_wmm_param_ie *wmm_ie, + struct ieee80211_ht_cap *ht_cap) +{ + struct nxpwifi_ie_types_wmm_param_set *wmm_tlv; + u32 ret_len = 0; + + /* Null checks */ + if (!assoc_buf) + return 0; + if (!(*assoc_buf)) + return 0; + + if (!wmm_ie) + return 0; + + nxpwifi_dbg(priv->adapter, INFO, + "info: WMM: process assoc req: bss->wmm_ie=%#x\n", + wmm_ie->element_id); + + if ((priv->wmm_required || + (ht_cap && (priv->config_bands & BAND_GN || + priv->config_bands & BAND_AN))) && + wmm_ie->element_id == WLAN_EID_VENDOR_SPECIFIC) { + wmm_tlv = (struct nxpwifi_ie_types_wmm_param_set *)*assoc_buf; + wmm_tlv->header.type = cpu_to_le16((u16)wmm_info_ie[0]); + wmm_tlv->header.len = cpu_to_le16((u16)wmm_info_ie[1]); + memcpy(wmm_tlv->wmm_ie, &wmm_info_ie[2], + le16_to_cpu(wmm_tlv->header.len)); + if (wmm_ie->qos_info & IEEE80211_WMM_IE_AP_QOSINFO_UAPSD) + memcpy((u8 *)(wmm_tlv->wmm_ie + + le16_to_cpu(wmm_tlv->header.len) + - sizeof(priv->wmm_qosinfo)), + &priv->wmm_qosinfo, sizeof(priv->wmm_qosinfo)); + + ret_len = sizeof(wmm_tlv->header) + + le16_to_cpu(wmm_tlv->header.len); + + *assoc_buf += ret_len; + } + + return ret_len; +} + +/* Computes the time delay in the driver queues for a given packet. */ +u8 +nxpwifi_wmm_compute_drv_pkt_delay(struct nxpwifi_private *priv, + const struct sk_buff *skb) +{ + u32 queue_delay = ktime_to_ms(net_timedelta(skb->tstamp)); + u8 ret_val; + + /* + * Queue delay is passed as a uint8 in units of 2ms (ms shifted + * by 1). Min value (other than 0) is therefore 2ms, max is 510ms. + * + * Pass max value if queue_delay is beyond the uint8 range + */ + ret_val = (u8)(min(queue_delay, priv->wmm.drv_pkt_delay_max) >> 1); + + nxpwifi_dbg(priv->adapter, DATA, "data: WMM: Pkt Delay: %d ms,\t" + "%d ms sent to FW\n", queue_delay, ret_val); + + return ret_val; +} + +/* Retrieves the highest priority RA list table pointer. */ +static struct nxpwifi_ra_list_tbl * +nxpwifi_wmm_get_highest_priolist_ptr(struct nxpwifi_adapter *adapter, + struct nxpwifi_private **priv, int *tid) +{ + struct nxpwifi_private *priv_tmp; + struct nxpwifi_ra_list_tbl *ptr; + struct nxpwifi_tid_tbl *tid_ptr; + atomic_t *hqp; + int i, j; + u8 to_tid; + + /* check the BSS with highest priority first */ + for (j = adapter->priv_num - 1; j >= 0; --j) { + /* iterate over BSS with the equal priority */ + list_for_each_entry(adapter->bss_prio_tbl[j].bss_prio_cur, + &adapter->bss_prio_tbl[j].bss_prio_head, + list) { +try_again: + priv_tmp = adapter->bss_prio_tbl[j].bss_prio_cur->priv; + + if (!priv_tmp->port_open || + (atomic_read(&priv_tmp->wmm.tx_pkts_queued) == 0)) + continue; + + /* iterate over the WMM queues of the BSS */ + hqp = &priv_tmp->wmm.highest_queued_prio; + for (i = atomic_read(hqp); i >= LOW_PRIO_TID; --i) { + spin_lock_bh(&priv_tmp->wmm.ra_list_spinlock); + + to_tid = tos_to_tid[i]; + tid_ptr = &(priv_tmp)->wmm.tid_tbl_ptr[to_tid]; + + /* iterate over receiver addresses */ + list_for_each_entry(ptr, &tid_ptr->ra_list, + list) { + if (!ptr->tx_paused && + !skb_queue_empty(&ptr->skb_head)) + /* holds both locks */ + goto found; + } + + spin_unlock_bh(&priv_tmp->wmm.ra_list_spinlock); + } + + if (atomic_read(&priv_tmp->wmm.tx_pkts_queued) != 0) { + atomic_set(&priv_tmp->wmm.highest_queued_prio, + HIGH_PRIO_TID); + /* + * Iterate current private once more, since + * there still exist packets in data queue + */ + goto try_again; + } else { + atomic_set(&priv_tmp->wmm.highest_queued_prio, + NO_PKT_PRIO_TID); + } + } + } + + return NULL; + +found: + /* holds ra_list_spinlock */ + if (atomic_read(hqp) > i) + atomic_set(hqp, i); + spin_unlock_bh(&priv_tmp->wmm.ra_list_spinlock); + + *priv = priv_tmp; + *tid = tos_to_tid[i]; + + return ptr; +} + +/* Rotates ra and bss lists so packets are picked round robin. */ +void nxpwifi_rotate_priolists(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ra, + int tid) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_bss_prio_tbl *tbl = adapter->bss_prio_tbl; + struct nxpwifi_tid_tbl *tid_ptr = &priv->wmm.tid_tbl_ptr[tid]; + + spin_lock_bh(&tbl[priv->bss_priority].bss_prio_lock); + /* + * dirty trick: we remove 'head' temporarily and reinsert it after + * curr bss node. imagine list to stay fixed while head is moved + */ + list_move(&tbl[priv->bss_priority].bss_prio_head, + &tbl[priv->bss_priority].bss_prio_cur->list); + spin_unlock_bh(&tbl[priv->bss_priority].bss_prio_lock); + + spin_lock_bh(&priv->wmm.ra_list_spinlock); + if (nxpwifi_is_ralist_valid(priv, ra, tid)) { + priv->wmm.packets_out[tid]++; + /* same as above */ + list_move(&tid_ptr->ra_list, &ra->list); + } + spin_unlock_bh(&priv->wmm.ra_list_spinlock); +} + +/* Checks if 11n aggregation is possible. */ +static bool +nxpwifi_is_11n_aggragation_possible(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ptr, + int max_buf_size) +{ + int count = 0, total_size = 0; + struct sk_buff *skb, *tmp; + int max_amsdu_size; + + if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP && priv->ap_11n_enabled && + ptr->is_11n_enabled) + max_amsdu_size = min_t(int, ptr->max_amsdu, max_buf_size); + else + max_amsdu_size = max_buf_size; + + skb_queue_walk_safe(&ptr->skb_head, skb, tmp) { + total_size += skb->len; + if (total_size >= max_amsdu_size) + break; + if (++count >= MIN_NUM_AMSDU) + return true; + } + + return false; +} + +/* Sends a single packet to firmware for transmission. */ +static void +nxpwifi_send_single_packet(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ptr, int ptr_index) +__releases(&priv->wmm.ra_list_spinlock) +{ + struct sk_buff *skb, *skb_next; + struct nxpwifi_tx_param tx_param; + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_txinfo *tx_info; + + if (skb_queue_empty(&ptr->skb_head)) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_dbg(adapter, DATA, "data: nothing to send\n"); + return; + } + + skb = skb_dequeue(&ptr->skb_head); + + tx_info = NXPWIFI_SKB_TXCB(skb); + nxpwifi_dbg(adapter, DATA, + "data: dequeuing the packet %p %p\n", ptr, skb); + + ptr->total_pkt_count--; + + if (!skb_queue_empty(&ptr->skb_head)) + skb_next = skb_peek(&ptr->skb_head); + else + skb_next = NULL; + + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + + tx_param.next_pkt_len = ((skb_next) ? skb_next->len + + sizeof(struct txpd) : 0); + + if (nxpwifi_process_tx(priv, skb, &tx_param) == -EBUSY) { + /* Queue the packet back at the head */ + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + if (!nxpwifi_is_ralist_valid(priv, ptr, ptr_index)) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_write_data_complete(adapter, skb, 0, -1); + return; + } + + skb_queue_tail(&ptr->skb_head, skb); + + ptr->total_pkt_count++; + ptr->ba_pkt_count++; + tx_info->flags |= NXPWIFI_BUF_FLAG_REQUEUED_PKT; + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + } else { + nxpwifi_rotate_priolists(priv, ptr, ptr_index); + atomic_dec(&priv->wmm.tx_pkts_queued); + } +} + +/* Checks if the first packet in the given RA list is already processed or not. */ +static bool +nxpwifi_is_ptr_processed(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ptr) +{ + struct sk_buff *skb; + struct nxpwifi_txinfo *tx_info; + + if (skb_queue_empty(&ptr->skb_head)) + return false; + + skb = skb_peek(&ptr->skb_head); + + tx_info = NXPWIFI_SKB_TXCB(skb); + if (tx_info->flags & NXPWIFI_BUF_FLAG_REQUEUED_PKT) + return true; + + return false; +} + +/* Sends a single processed packet to firmware for transmission. */ +static void +nxpwifi_send_processed_packet(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ptr, int ptr_index) + __releases(&priv->wmm.ra_list_spinlock) +{ + struct nxpwifi_tx_param tx_param; + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + struct sk_buff *skb, *skb_next; + struct nxpwifi_txinfo *tx_info; + + if (skb_queue_empty(&ptr->skb_head)) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + return; + } + + skb = skb_dequeue(&ptr->skb_head); + + if (adapter->data_sent || adapter->tx_lock_flag) { + ptr->total_pkt_count--; + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + skb_queue_tail(&adapter->tx_data_q, skb); + atomic_dec(&priv->wmm.tx_pkts_queued); + atomic_inc(&adapter->tx_queued); + return; + } + + if (!skb_queue_empty(&ptr->skb_head)) + skb_next = skb_peek(&ptr->skb_head); + else + skb_next = NULL; + + tx_info = NXPWIFI_SKB_TXCB(skb); + + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + + tx_param.next_pkt_len = + ((skb_next) ? skb_next->len + + sizeof(struct txpd) : 0); + + ret = adapter->if_ops.host_to_card(adapter, NXPWIFI_TYPE_DATA, + skb, &tx_param); + + switch (ret) { + case -EBUSY: + nxpwifi_dbg(adapter, ERROR, "data: -EBUSY is returned\n"); + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + if (!nxpwifi_is_ralist_valid(priv, ptr, ptr_index)) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_write_data_complete(adapter, skb, 0, -1); + return; + } + + skb_queue_tail(&ptr->skb_head, skb); + + tx_info->flags |= NXPWIFI_BUF_FLAG_REQUEUED_PKT; + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + break; + case -EINPROGRESS: + break; + case 0: + nxpwifi_write_data_complete(adapter, skb, 0, ret); + break; + default: + nxpwifi_dbg(adapter, ERROR, "host_to_card failed: %#x\n", ret); + adapter->dbg.num_tx_host_to_card_failure++; + nxpwifi_write_data_complete(adapter, skb, 0, ret); + break; + } + + if (ret != -EBUSY) { + nxpwifi_rotate_priolists(priv, ptr, ptr_index); + atomic_dec(&priv->wmm.tx_pkts_queued); + spin_lock_bh(&priv->wmm.ra_list_spinlock); + ptr->total_pkt_count--; + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + } +} + +/* Dequeues a packet from the highest priority list and transmits it. */ +static int +nxpwifi_dequeue_tx_packet(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_ra_list_tbl *ptr; + struct nxpwifi_private *priv = NULL; + int ptr_index = 0; + u8 ra[ETH_ALEN]; + int tid_del = 0, tid = 0; + + ptr = nxpwifi_wmm_get_highest_priolist_ptr(adapter, &priv, &ptr_index); + if (!ptr) + return -ENOENT; + + tid = nxpwifi_get_tid(ptr); + + nxpwifi_dbg(adapter, DATA, "data: tid=%d\n", tid); + + spin_lock_bh(&priv->wmm.ra_list_spinlock); + if (!nxpwifi_is_ralist_valid(priv, ptr, ptr_index)) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + return -EINVAL; + } + + if (nxpwifi_is_ptr_processed(priv, ptr)) { + nxpwifi_send_processed_packet(priv, ptr, ptr_index); + /* + * ra_list_spinlock has been freed in + * nxpwifi_send_processed_packet() + */ + return 0; + } + + if (!ptr->is_11n_enabled || + ptr->ba_status || + priv->wps.session_enable) { + if (ptr->is_11n_enabled && + ptr->ba_status && + ptr->amsdu_in_ampdu && + nxpwifi_is_amsdu_allowed(priv, tid) && + nxpwifi_is_11n_aggragation_possible(priv, ptr, + adapter->tx_buf_size)) + nxpwifi_11n_aggregate_pkt(priv, ptr, ptr_index); + /* + * ra_list_spinlock has been freed in + * nxpwifi_11n_aggregate_pkt() + */ + else + nxpwifi_send_single_packet(priv, ptr, ptr_index); + /* + * ra_list_spinlock has been freed in + * nxpwifi_send_single_packet() + */ + } else { + if (nxpwifi_is_ampdu_allowed(priv, ptr, tid) && + ptr->ba_pkt_count > ptr->ba_packet_thr) { + if (nxpwifi_space_avail_for_new_ba_stream(adapter)) { + nxpwifi_create_ba_tbl(priv, ptr->ra, tid, + BA_SETUP_INPROGRESS); + nxpwifi_send_addba(priv, tid, ptr->ra); + } else if (nxpwifi_find_stream_to_delete + (priv, tid, &tid_del, ra)) { + nxpwifi_create_ba_tbl(priv, ptr->ra, tid, + BA_SETUP_INPROGRESS); + nxpwifi_send_delba(priv, tid_del, ra, 1); + } + } + if (nxpwifi_is_amsdu_allowed(priv, tid) && + nxpwifi_is_11n_aggragation_possible(priv, ptr, + adapter->tx_buf_size)) + nxpwifi_11n_aggregate_pkt(priv, ptr, ptr_index); + /* + * ra_list_spinlock has been freed in + * nxpwifi_11n_aggregate_pkt() + */ + else + nxpwifi_send_single_packet(priv, ptr, ptr_index); + /* + * ra_list_spinlock has been freed in + * nxpwifi_send_single_packet() + */ + } + return 0; +} + +void nxpwifi_process_bypass_tx(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_tx_param tx_param; + struct sk_buff *skb; + struct nxpwifi_txinfo *tx_info; + struct nxpwifi_private *priv; + int i; + + if (adapter->data_sent || adapter->tx_lock_flag) + return; + + for (i = 0; i < adapter->priv_num; ++i) { + priv = adapter->priv[i]; + + if (skb_queue_empty(&priv->bypass_txq)) + continue; + + skb = skb_dequeue(&priv->bypass_txq); + tx_info = NXPWIFI_SKB_TXCB(skb); + + /* no aggregation for bypass packets */ + tx_param.next_pkt_len = 0; + + if (nxpwifi_process_tx(priv, skb, &tx_param) == -EBUSY) { + skb_queue_head(&priv->bypass_txq, skb); + tx_info->flags |= NXPWIFI_BUF_FLAG_REQUEUED_PKT; + } else { + atomic_dec(&adapter->bypass_tx_pending); + } + } +} + +/* Transmits the highest priority packet awaiting in the WMM Queues. */ +void +nxpwifi_wmm_process_tx(struct nxpwifi_adapter *adapter) +{ + do { + if (nxpwifi_dequeue_tx_packet(adapter)) + break; + if (adapter->iface_type != NXPWIFI_SDIO) { + if (adapter->data_sent || + adapter->tx_lock_flag) + break; + } else { + if (atomic_read(&adapter->tx_queued) >= + NXPWIFI_MAX_PKTS_TXQ) + break; + } + } while (!nxpwifi_wmm_lists_empty(adapter)); +} + +void nxpwifi_wmm_init_tos_to_tid_inv(struct nxpwifi_private *priv) +{ + memcpy(priv->tos_to_tid_inv, tos_to_tid_inv, sizeof(priv->tos_to_tid_inv)); +} diff --git a/drivers/net/wireless/nxp/nxpwifi/wmm.h b/drivers/net/wireless/nxp/nxpwifi/wmm.h new file mode 100644 index 000000000000..d7f4a29bc301 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/wmm.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * NXP Wireless LAN device driver: WMM + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_WMM_H_ +#define _NXPWIFI_WMM_H_ + +enum ieee_types_wmm_aciaifsn_bitmasks { + NXPWIFI_AIFSN = (BIT(0) | BIT(1) | BIT(2) | BIT(3)), + NXPWIFI_ACM = BIT(4), + NXPWIFI_ACI = (BIT(5) | BIT(6)), +}; + +enum ieee_types_wmm_ecw_bitmasks { + NXPWIFI_ECW_MIN = (BIT(0) | BIT(1) | BIT(2) | BIT(3)), + NXPWIFI_ECW_MAX = (BIT(4) | BIT(5) | BIT(6) | BIT(7)), +}; + +extern const u16 nxpwifi_1d_to_wmm_queue[]; + +/* Retrieve the TID of the given RA list. */ +static inline int +nxpwifi_get_tid(struct nxpwifi_ra_list_tbl *ptr) +{ + struct sk_buff *skb; + + if (skb_queue_empty(&ptr->skb_head)) + return 0; + + skb = skb_peek(&ptr->skb_head); + + return skb->priority; +} + +void nxpwifi_wmm_add_buf_txqueue(struct nxpwifi_private *priv, + struct sk_buff *skb); +void nxpwifi_wmm_add_buf_bypass_txqueue(struct nxpwifi_private *priv, + struct sk_buff *skb); +void nxpwifi_ralist_add(struct nxpwifi_private *priv, const u8 *ra); +void nxpwifi_rotate_priolists(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ra, int tid); + +bool nxpwifi_wmm_lists_empty(struct nxpwifi_adapter *adapter); +bool nxpwifi_bypass_txlist_empty(struct nxpwifi_adapter *adapter); +void nxpwifi_wmm_process_tx(struct nxpwifi_adapter *adapter); +void nxpwifi_process_bypass_tx(struct nxpwifi_adapter *adapter); +bool nxpwifi_is_ralist_valid(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ra_list, int tid); + +u8 nxpwifi_wmm_compute_drv_pkt_delay(struct nxpwifi_private *priv, + const struct sk_buff *skb); +void nxpwifi_wmm_init(struct nxpwifi_adapter *adapter); + +u32 nxpwifi_wmm_process_association_req(struct nxpwifi_private *priv, + u8 **assoc_buf, + struct ieee80211_wmm_param_ie *wmmie, + struct ieee80211_ht_cap *htcap); + +void nxpwifi_wmm_setup_queue_priorities(struct nxpwifi_private *priv, + struct ieee80211_wmm_param_ie *wmm_ie); +void nxpwifi_wmm_setup_ac_downgrade(struct nxpwifi_private *priv); +int nxpwifi_ret_wmm_get_status(struct nxpwifi_private *priv, + const struct host_cmd_ds_command *resp); +struct nxpwifi_ra_list_tbl * +nxpwifi_wmm_get_queue_raptr(struct nxpwifi_private *priv, u8 tid, + const u8 *ra_addr); +u8 nxpwifi_wmm_downgrade_tid(struct nxpwifi_private *priv, u32 tid); +void nxpwifi_update_ralist_tx_pause(struct nxpwifi_private *priv, u8 *mac, + u8 tx_pause); + +struct nxpwifi_ra_list_tbl *nxpwifi_wmm_get_ralist_node(struct nxpwifi_private + *priv, u8 tid, const u8 *ra_addr); +void nxpwifi_wmm_init_tos_to_tid_inv(struct nxpwifi_private *priv); +#endif /* !_NXPWIFI_WMM_H_ */ From 7410e4548a17bd868dfc60b48f2e78eacde3ad78 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 15 Jul 2026 21:57:50 +0800 Subject: [PATCH 0307/1433] wifi: iwlwifi: validate PNVM SKU TLV length iwl_pnvm_parse() reads an iwl_sku_id from an IWL_UCODE_TLV_PNVM_SKU payload after only checking that the generic TLV payload is present. A short type-specific payload can therefore make the three data[] reads extend beyond the TLV. Reject SKU TLVs shorter than the structure before accessing it. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260715135916.24417-1-pengpeng@iscas.ac.cn Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/pnvm.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/pnvm.c b/drivers/net/wireless/intel/iwlwifi/fw/pnvm.c index afff8d51ca95..6daa5cc8c20f 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/pnvm.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/pnvm.c @@ -198,6 +198,12 @@ static int iwl_pnvm_parse(struct iwl_trans *trans, const u8 *data, IWL_DEBUG_FW(trans, "Got IWL_UCODE_TLV_PNVM_SKU len %d\n", tlv_len); + if (tlv_len < sizeof(*tlv_sku_id)) { + IWL_ERR(trans, "invalid PNVM SKU TLV len: %u\n", + tlv_len); + return -EINVAL; + } + IWL_DEBUG_FW(trans, "sku_id 0x%0x 0x%0x 0x%0x\n", le32_to_cpu(tlv_sku_id->data[0]), le32_to_cpu(tlv_sku_id->data[1]), From f051995c539582b05276abda4c5874fd4cafd2a5 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 15 Jul 2026 21:57:50 +0800 Subject: [PATCH 0308/1433] wifi: iwlwifi: validate UEFI reduced-power SKU TLV length iwl_uefi_reduce_power_parse() reads an iwl_sku_id from an IWL_UCODE_TLV_PNVM_SKU payload after only checking that the generic TLV payload is present. A short type-specific payload can therefore make the three data[] reads extend beyond the TLV. Reject SKU TLVs shorter than the structure before accessing it. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260715135916.24417-2-pengpeng@iscas.ac.cn Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/uefi.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/uefi.c b/drivers/net/wireless/intel/iwlwifi/fw/uefi.c index 2ef0a7a920ad..4cd36b42262c 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/uefi.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/uefi.c @@ -248,6 +248,12 @@ int iwl_uefi_reduce_power_parse(struct iwl_trans *trans, IWL_DEBUG_FW(trans, "Got IWL_UCODE_TLV_PNVM_SKU len %d\n", tlv_len); + if (tlv_len < sizeof(*tlv_sku_id)) { + IWL_ERR(trans, "invalid PNVM SKU TLV len: %u\n", + tlv_len); + return -EINVAL; + } + IWL_DEBUG_FW(trans, "sku_id 0x%0x 0x%0x 0x%0x\n", le32_to_cpu(tlv_sku_id->data[0]), le32_to_cpu(tlv_sku_id->data[1]), From 478cad8bba59e8ee0e08d49edabf14bf1fbde5e4 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Tue, 14 Jul 2026 17:02:04 +0300 Subject: [PATCH 0309/1433] wifi: iwlwifi: add a compile time check for too long hcmds A host command that is bigger than the allowed payload length should be sent with the NOCOPY flag. If it is sent without, we will get a warning. We do know at compile time what is the maximum size of a hcmd payload that the transport supports, so in order to catch bugs early, add a compile time check to iwl_*_send_cmd_pdu to catch that. Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20260714165826.a549b9499e3e.Id1a95bbbf92b5862862becaf57419bb9fe1385e5@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/hcmd.h | 9 +++++++-- drivers/net/wireless/intel/iwlwifi/mvm/mvm.h | 20 ++++++++++++++----- .../net/wireless/intel/iwlwifi/mvm/utils.c | 8 ++++---- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/hcmd.h b/drivers/net/wireless/intel/iwlwifi/mld/hcmd.h index 64a8d4248324..19e1c9e509cf 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/hcmd.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/hcmd.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2024-2025 Intel Corporation + * Copyright (C) 2024-2026 Intel Corporation */ #ifndef __iwl_mld_hcmd_h__ #define __iwl_mld_hcmd_h__ @@ -42,7 +42,12 @@ __iwl_mld_send_cmd_with_flags_pdu(struct iwl_mld *mld, u32 id, #define _iwl_mld_send_cmd_with_flags_pdu(mld, id, flags, data, len, \ ignored...) \ - __iwl_mld_send_cmd_with_flags_pdu(mld, id, flags, data, len) + ({ \ + BUILD_BUG_ON(__builtin_constant_p(len) && \ + (u16)(len) > IWL_MAX_CMD_PAYLOAD_SIZE); \ + __iwl_mld_send_cmd_with_flags_pdu(mld, id, flags, \ + data, len); \ + }) #define iwl_mld_send_cmd_with_flags_pdu(mld, id, flags, data, len...) \ _iwl_mld_send_cmd_with_flags_pdu(mld, id, flags, data, ##len, \ sizeof(*(data))) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h index 683cac56822c..1b56a2695bde 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h @@ -1675,14 +1675,24 @@ u32 iwl_mvm_get_systime(struct iwl_mvm *mvm); /* Tx / Host Commands */ int iwl_mvm_send_cmd(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd); -int iwl_mvm_send_cmd_pdu(struct iwl_mvm *mvm, u32 id, - u32 flags, u16 len, const void *data); +int _iwl_mvm_send_cmd_pdu(struct iwl_mvm *mvm, u32 id, + u32 flags, u16 len, const void *data); +#define iwl_mvm_send_cmd_pdu(mvm, id, flags, len, data) ({ \ + BUILD_BUG_ON(__builtin_constant_p(len) && \ + (u16)(len) > IWL_MAX_CMD_PAYLOAD_SIZE); \ + _iwl_mvm_send_cmd_pdu(mvm, id, flags, len, data); \ +}) int iwl_mvm_send_cmd_status(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd, u32 *status); -int iwl_mvm_send_cmd_pdu_status(struct iwl_mvm *mvm, u32 id, - u16 len, const void *data, - u32 *status); +int _iwl_mvm_send_cmd_pdu_status(struct iwl_mvm *mvm, u32 id, + u16 len, const void *data, + u32 *status); +#define iwl_mvm_send_cmd_pdu_status(mvm, id, len, data, status) ({ \ + BUILD_BUG_ON(__builtin_constant_p(len) && \ + (u16)(len) > IWL_MAX_CMD_PAYLOAD_SIZE); \ + _iwl_mvm_send_cmd_pdu_status(mvm, id, len, data, status); \ +}) int iwl_mvm_tx_skb_sta(struct iwl_mvm *mvm, struct sk_buff *skb, struct ieee80211_sta *sta); int iwl_mvm_tx_skb_non_sta(struct iwl_mvm *mvm, struct sk_buff *skb); diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/utils.c b/drivers/net/wireless/intel/iwlwifi/mvm/utils.c index 2e12f93ad32b..849342c96ae7 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/utils.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/utils.c @@ -73,8 +73,8 @@ int iwl_mvm_send_cmd(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd) return ret; } -int iwl_mvm_send_cmd_pdu(struct iwl_mvm *mvm, u32 id, - u32 flags, u16 len, const void *data) +int _iwl_mvm_send_cmd_pdu(struct iwl_mvm *mvm, u32 id, + u32 flags, u16 len, const void *data) { struct iwl_host_cmd cmd = { .id = id, @@ -137,8 +137,8 @@ int iwl_mvm_send_cmd_status(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd, /* * We assume that the caller set the status to the sucess value */ -int iwl_mvm_send_cmd_pdu_status(struct iwl_mvm *mvm, u32 id, u16 len, - const void *data, u32 *status) +int _iwl_mvm_send_cmd_pdu_status(struct iwl_mvm *mvm, u32 id, u16 len, + const void *data, u32 *status) { struct iwl_host_cmd cmd = { .id = id, From 1e749bd58e556f69a0251cfc8cd110b986f641f0 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 14 Jul 2026 17:02:05 +0300 Subject: [PATCH 0310/1433] wifi: iwlwifi: mvm: remove iwl_mvm_recalc_tcm() This function is only called in the worker, so it doesn't need to exist at all, simply move the code there. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260714165826.dd9f49714128.I65fbe6890d67ec424d333c362aa7041a117aed44@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mvm/mvm.h | 1 - drivers/net/wireless/intel/iwlwifi/mvm/utils.c | 16 +++++----------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h index 1b56a2695bde..4420005ef69e 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h @@ -2412,7 +2412,6 @@ bool iwl_mvm_is_vif_assoc(struct iwl_mvm *mvm); #define MVM_TCM_PERIOD (HZ * MVM_TCM_PERIOD_MSEC / 1000) #define MVM_LL_PERIOD (10 * HZ) void iwl_mvm_tcm_work(struct work_struct *work); -void iwl_mvm_recalc_tcm(struct iwl_mvm *mvm); void iwl_mvm_pause_tcm(struct iwl_mvm *mvm, bool with_cancel); void iwl_mvm_resume_tcm(struct iwl_mvm *mvm); void iwl_mvm_tcm_add_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif); diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/utils.c b/drivers/net/wireless/intel/iwlwifi/mvm/utils.c index 849342c96ae7..7eae10982869 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/utils.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/utils.c @@ -1055,7 +1055,7 @@ static unsigned long iwl_mvm_calc_tcm_stats(struct iwl_mvm *mvm, /* * If the current load isn't low we need to force re-evaluation * in the TCM period, so that we can return to low load if there - * was no traffic at all (and thus iwl_mvm_recalc_tcm didn't get + * was no traffic at all (and thus iwl_mvm_tcm_work() didn't get * triggered by traffic). */ if (load != IWL_MVM_TRAFFIC_LOW) @@ -1081,8 +1081,11 @@ static unsigned long iwl_mvm_calc_tcm_stats(struct iwl_mvm *mvm, return 0; } -void iwl_mvm_recalc_tcm(struct iwl_mvm *mvm) +void iwl_mvm_tcm_work(struct work_struct *work) { + struct delayed_work *delayed_work = to_delayed_work(work); + struct iwl_mvm *mvm = container_of(delayed_work, struct iwl_mvm, + tcm.work); unsigned long ts = jiffies; bool handle_uapsd = time_after(ts, mvm->tcm.uapsd_nonagg_ts + @@ -1119,15 +1122,6 @@ void iwl_mvm_recalc_tcm(struct iwl_mvm *mvm) iwl_mvm_tcm_results(mvm); } -void iwl_mvm_tcm_work(struct work_struct *work) -{ - struct delayed_work *delayed_work = to_delayed_work(work); - struct iwl_mvm *mvm = container_of(delayed_work, struct iwl_mvm, - tcm.work); - - iwl_mvm_recalc_tcm(mvm); -} - void iwl_mvm_pause_tcm(struct iwl_mvm *mvm, bool with_cancel) { spin_lock_bh(&mvm->tcm.lock); From 6800559a1042bfef9985b400f9dd971650d122f4 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 14 Jul 2026 17:02:06 +0300 Subject: [PATCH 0311/1433] wifi: iwlwifi: claim UHR DBE capability for UHR devices When an Intel device supports UHR it also supports DBE (dynamic bandwidth extension) since that's handled in mac80211. Claim support for it for client mode. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260714165826.435828046f11.I538b2d90a4c282118ca2e56292cf5615d477a44c@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c index d47b4ae2f486..761424812609 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c @@ -699,7 +699,8 @@ static const struct ieee80211_sband_iftype_data iwl_iftype_cap[] = { .mac.mac_cap = { [0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP | IEEE80211_UHR_MAC_CAP0_DPS_SUPP, - [1] = IEEE80211_UHR_MAC_CAP1_DUO_SUPP, + [1] = IEEE80211_UHR_MAC_CAP1_DUO_SUPP | + IEEE80211_UHR_MAC_CAP1_DBE_SUPP, }, }, }, From 3bff0d12c36247073fca976498b58a940fca72dc Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Tue, 14 Jul 2026 17:02:07 +0300 Subject: [PATCH 0312/1433] wifi: iwlwifi: support TTL platform device ID Add support for a new device ID that we will have on TTL (sc2). Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20260714165826.46183446954f.Icc260831e530c1c92c9be615a7077768b1b9ae30@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/pcie/drv.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c index 7a7b10152e5a..a3e6c9e09a3b 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c @@ -548,6 +548,7 @@ VISIBLE_IF_IWLWIFI_KUNIT const struct pci_device_id iwl_hw_card_ids[] = { {IWL_PCI_DEVICE(0xD340, PCI_ANY_ID, iwl_sc_mac_cfg)}, {IWL_PCI_DEVICE(0x6E70, PCI_ANY_ID, iwl_sc_mac_cfg)}, {IWL_PCI_DEVICE(0xD240, PCI_ANY_ID, iwl_sc_mac_cfg)}, + {IWL_PCI_DEVICE(0x9327, PCI_ANY_ID, iwl_sc_mac_cfg)}, #endif /* CONFIG_IWLMVM || CONFIG_IWLMLD */ {0} From 9119aeeddc210cdfce0537de3ea3bab9316b2589 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 17:02:08 +0300 Subject: [PATCH 0313/1433] wifi: iwlwifi: mvm: fix the FCS truncation logic in d3 Fix a harmless mistake in the wake packet management code in the d3 wakeup flow. If the FCS is truncated, we want to detect it, but we cleared the icvlen before updating the truncated variable that holds the number of bytes having been truncated. Fix that. Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260714165826.a7d094168ed3.I1a4d13f276c7e75514ab2032ae387873337470b8@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mvm/d3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c index 9a74f60c9185..d7ceb385ae0b 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c @@ -1539,8 +1539,8 @@ static void iwl_mvm_report_wakeup_reasons(struct iwl_mvm *mvm, /* if truncated, FCS/ICV is (partially) gone */ if (truncated >= icvlen) { - icvlen = 0; truncated -= icvlen; + icvlen = 0; } else { icvlen -= truncated; truncated = 0; From 68f7d05494403ce89eba2b5476b2cd9ac03041db Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 17:02:09 +0300 Subject: [PATCH 0314/1433] wifi: iwlwifi: mld: treat valid BAID without STA as a FW error Somehow, the firmware sometimes seems to have a valid BAID even if the ieee80211_sta was not found. This happens in sniffer mode. Treat those as a firmware error. Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260714165826.4902f73de145.I2cec7133f2a2ec8c39dcfb36938aba2ea3d6be24@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/agg.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/agg.c b/drivers/net/wireless/intel/iwlwifi/mld/agg.c index e3627ad0321c..1aa30d2e8133 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/agg.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/agg.c @@ -200,11 +200,11 @@ iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi, struct iwl_mld_baid_data *baid_data; struct iwl_mld_reorder_buffer *buffer; struct iwl_mld_reorder_buf_entry *entries; - struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta); struct iwl_mld_link_sta *mld_link_sta; u32 reorder = le32_to_cpu(desc->reorder_data); bool amsdu, last_subframe, is_old_sn, is_dup; u8 tid = ieee80211_get_tid(hdr); + struct iwl_mld_sta *mld_sta; u8 baid; u16 nssn, sn; u32 sta_mask = 0; @@ -223,10 +223,13 @@ iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi, return IWL_MLD_PASS_SKB; /* no sta yet */ - if (WARN_ONCE(!sta, - "Got valid BAID without a valid station assigned\n")) + if (IWL_FW_CHECK(mld, !sta, + "Got valid BAID without a valid station assigned - %d\n", + baid)) return IWL_MLD_PASS_SKB; + mld_sta = iwl_mld_sta_from_mac80211(sta); + /* not a data packet */ if (!ieee80211_is_data_qos(hdr->frame_control) || is_multicast_ether_addr(hdr->addr1)) From 21b3263b90e8006d488894e8a71c8debf45f6b88 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 17:02:10 +0300 Subject: [PATCH 0315/1433] wifi: iwlwifi: mvm: validate monitor notif link_id MONITOR_NOTIF link_id is firmware-provided. Validate link_id range with IWL_FW_CHECK before vif lookup. Payload length is already checked by RX_HANDLER. Use iwl_mvm_rcu_dereference_vif_id which does all we need which allows us to drop iwl_mvm_get_vif_by_macid. Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260714165826.7601d05649a4.I237f58a007af761468057c9c09039953a3bb37da@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mvm/mvm.h | 1 - drivers/net/wireless/intel/iwlwifi/mvm/ops.c | 4 +-- .../net/wireless/intel/iwlwifi/mvm/utils.c | 30 ------------------- 3 files changed, 2 insertions(+), 33 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h index 4420005ef69e..979b39314981 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h @@ -2405,7 +2405,6 @@ void iwl_mvm_sync_rx_queues_internal(struct iwl_mvm *mvm, bool sync, const void *data, u32 size); struct ieee80211_vif *iwl_mvm_get_bss_vif(struct iwl_mvm *mvm); -struct ieee80211_vif *iwl_mvm_get_vif_by_macid(struct iwl_mvm *mvm, u32 macid); bool iwl_mvm_is_vif_assoc(struct iwl_mvm *mvm); #define MVM_TCM_PERIOD_MSEC 500 diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c index 2297392db955..f43895c2f3c6 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c @@ -155,8 +155,8 @@ static void iwl_mvm_rx_monitor_notif(struct iwl_mvm *mvm, if (notif->type != cpu_to_le32(IWL_DP_MON_NOTIF_TYPE_EXT_CCA)) return; - /* FIXME: should fetch the link and not the vif */ - vif = iwl_mvm_get_vif_by_macid(mvm, notif->link_id); + /* mac_id = link_id since we don't support MLO */ + vif = iwl_mvm_rcu_dereference_vif_id(mvm, notif->link_id, false); if (!vif || vif->type != NL80211_IFTYPE_STATION) return; diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/utils.c b/drivers/net/wireless/intel/iwlwifi/mvm/utils.c index 7eae10982869..2fd4a9961145 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/utils.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/utils.c @@ -689,36 +689,6 @@ struct ieee80211_vif *iwl_mvm_get_bss_vif(struct iwl_mvm *mvm) return bss_iter_data.vif; } -struct iwl_bss_find_iter_data { - struct ieee80211_vif *vif; - u32 macid; -}; - -static void iwl_mvm_bss_find_iface_iterator(void *_data, u8 *mac, - struct ieee80211_vif *vif) -{ - struct iwl_bss_find_iter_data *data = _data; - struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); - - if (mvmvif->id == data->macid) - data->vif = vif; -} - -struct ieee80211_vif *iwl_mvm_get_vif_by_macid(struct iwl_mvm *mvm, u32 macid) -{ - struct iwl_bss_find_iter_data data = { - .macid = macid, - }; - - lockdep_assert_held(&mvm->mutex); - - ieee80211_iterate_active_interfaces_atomic( - mvm->hw, IEEE80211_IFACE_ITER_NORMAL, - iwl_mvm_bss_find_iface_iterator, &data); - - return data.vif; -} - struct iwl_sta_iter_data { bool assoc; }; From c99bc4d4e0ba6f4661b2b914c96a597d26e7cc05 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Tue, 14 Jul 2026 17:02:11 +0300 Subject: [PATCH 0316/1433] wifi: iwlwifi: mld: cancel wiphy work before freeing wiphy When we fail to load the fw during op-mode start, we purge the list of the async handlers but we don't cancel the work. Same when we stop the op-mode. Before freeing wiphy, we need to cancel/flush any pending wiphy work, otherwise the work will fire with a freed memory. cfg80211 will do it anyway, but it will warn. Cancel the work in those cases. Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20260714165826.07aa49f755a2.I734a27b1b0eeb5b0e821aee3318fee8dc0a6bc03@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/mld.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.c b/drivers/net/wireless/intel/iwlwifi/mld/mld.c index 78c78cf891cd..5a6c9ab20fd6 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mld.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.c @@ -446,6 +446,8 @@ iwl_op_mode_mld_start(struct iwl_trans *trans, const struct iwl_rf_cfg *cfg, } if (ret) { + /* wiphy memory is about to be freed, we should cancel any pending work */ + wiphy_work_cancel(mld->wiphy, &mld->async_handlers_wk); wiphy_unlock(mld->wiphy); rtnl_unlock(); goto err; @@ -511,6 +513,7 @@ iwl_op_mode_mld_stop(struct iwl_op_mode *op_mode) iwl_mld_thermal_exit(mld); wiphy_lock(mld->wiphy); + wiphy_work_cancel(mld->wiphy, &mld->async_handlers_wk); iwl_mld_low_latency_stop(mld); iwl_mld_deinit_time_sync(mld); wiphy_unlock(mld->wiphy); From 4e777dcdfe70d9b3ddf38cb49de9e30306889b18 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 17:02:12 +0300 Subject: [PATCH 0317/1433] wifi: iwlwifi: mld: validate D3_END notif size Check D3_END_NOTIFICATION payload length before reading notif->flags. On short payloads, mark notif handling as failed. Avoid out-of-bounds reads from malformed notifications. Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260714165826.db2df8b6b6bb.I6163bbdf433379bf1dbf9eb46fb9562892217bd7@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/d3.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/d3.c b/drivers/net/wireless/intel/iwlwifi/mld/d3.c index 458a668ba916..b5fed6090340 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/d3.c @@ -1449,8 +1449,16 @@ static bool iwl_mld_handle_d3_notif(struct iwl_notif_wait_data *notif_wait, } case WIDE_ID(PROT_OFFLOAD_GROUP, D3_END_NOTIFICATION): { struct iwl_d3_end_notif *notif = (void *)pkt->data; + u32 len = iwl_rx_packet_payload_len(pkt); + + if (IWL_FW_CHECK(mld, len < sizeof(*notif), + "Invalid D3_END notification (expected=%zu got=%u)\n", + sizeof(*notif), len)) { + resume_data->notif_handling_err = true; + } else { + resume_data->d3_end_flags = le32_to_cpu(notif->flags); + } - resume_data->d3_end_flags = le32_to_cpu(notif->flags); resume_data->notifs_received |= IWL_D3_NOTIF_D3_END_NOTIF; break; } From 5826799a26fd1c7dd954f6386a49e9fad747f21f Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 17:02:13 +0300 Subject: [PATCH 0318/1433] wifi: iwlwifi: pcie: validate txq_id in txq_enable Avoid indexing txq arrays and queue-used bitmaps with an invalid queue ID by adding an early bounds check in iwl_trans_pcie_txq_enable(). Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260714165826.273be072f5ff.I0d6c36a4c06bdbb4655164c7792da32b6143731e@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/tx.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/tx.c b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/tx.c index fdb3ba4f63c0..dc2fe0c4f68c 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/tx.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/tx.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2003-2014, 2018-2021, 2023-2025 Intel Corporation + * Copyright (C) 2003-2014, 2018-2021, 2023-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -1149,10 +1149,17 @@ bool iwl_trans_pcie_txq_enable(struct iwl_trans *trans, int txq_id, u16 ssn, unsigned int wdg_timeout) { struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); - struct iwl_txq *txq = trans_pcie->txqs.txq[txq_id]; + struct iwl_txq *txq; int fifo = -1; bool scd_bug = false; + if (WARN_ONCE(txq_id < 0 || + txq_id >= trans->mac_cfg->base->num_of_queues, + "queue %d out of range", txq_id)) + return false; + + txq = trans_pcie->txqs.txq[txq_id]; + if (test_and_set_bit(txq_id, trans_pcie->txqs.queue_used)) WARN_ONCE(1, "queue %d already used - expect issues", txq_id); From 678148622fa8e5a118784fa3fcb9bd1876b8322a Mon Sep 17 00:00:00 2001 From: Pagadala Yesu Anjaneyulu Date: Tue, 14 Jul 2026 17:02:14 +0300 Subject: [PATCH 0319/1433] wifi: iwlwifi: ignore raw-DSM TLV for LARI cmd version 13 and above LARI_CONFIG_CHANGE command version 13 and above accepts raw DSM values by default in firmware, so FW_ACCEPTS_RAW_DSM_TABLE should not gate DSM bitmap handling for these versions. Set has_raw_dsm_capa based on command version (version 13 and above is true) with TLV fallback for older command versions. Also update TLV kernel-doc to mark this capability obsolete for LARI command version 13 and above. Signed-off-by: Pagadala Yesu Anjaneyulu Reviewed-by: Avinash Bhatt Link: https://patch.msgid.link/20260714165826.12c8b407e115.I6809041f1eb52b7fafe9172ca3e47323d43cc30a@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/file.h | 5 +++-- .../net/wireless/intel/iwlwifi/mld/regulatory.c | 16 +++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/file.h b/drivers/net/wireless/intel/iwlwifi/fw/file.h index a26ed82a8106..664ac25754e9 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/file.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/file.h @@ -448,8 +448,9 @@ typedef unsigned int __bitwise iwl_ucode_tlv_capa_t; * @IWL_UCODE_TLV_CAPA_EXT_FSEQ_IMAGE_SUPPORT: external FSEQ image support * @IWL_UCODE_TLV_CAPA_RESET_DURING_ASSERT: FW reset handshake is needed * during assert handling even if the dump isn't split - * @IWL_UCODE_TLV_CAPA_FW_ACCEPTS_RAW_DSM_TABLE: Firmware has capability of - * handling raw DSM table data. + * @IWL_UCODE_TLV_CAPA_FW_ACCEPTS_RAW_DSM_TABLE: Firmware can handle raw DSM + * table data. For LARI_CONFIG_CHANGE command version 13 and above, this + * capability is obsolete since raw DSM values are accepted by default. * @IWL_UCODE_TLV_CAPA_NAN_SYNC_SUPPORT: Supports NAN synchronization * * @NUM_IWL_UCODE_TLV_CAPA: number of bits used diff --git a/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c b/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c index 659243ada86c..858635607f5d 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c @@ -325,8 +325,16 @@ void iwl_mld_configure_lari(struct iwl_mld *mld) struct iwl_lari_config_change_cmd cmd = { .config_bitmap = iwl_mld_get_lari_config_bitmap(fwrt), }; - bool has_raw_dsm_capa = fw_has_capa(&fwrt->fw->ucode_capa, - IWL_UCODE_TLV_CAPA_FW_ACCEPTS_RAW_DSM_TABLE); + u8 cmd_ver = iwl_fw_lookup_cmd_ver(mld->fw, + WIDE_ID(REGULATORY_AND_NVM_GROUP, + LARI_CONFIG_CHANGE), 12); + /* + * For LARI_CONFIG_CHANGE command version 13 and above, firmware accepts + * raw DSM values by default and this TLV is no longer needed. + */ + bool has_raw_dsm_capa = cmd_ver >= 13 || + fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_FW_ACCEPTS_RAW_DSM_TABLE); int ret; u32 value; @@ -428,9 +436,7 @@ void iwl_mld_configure_lari(struct iwl_mld *mld) "sending LARI_CONFIG_CHANGE, oem_unii9_enable=0x%x\n", le32_to_cpu(cmd.oem_unii9_enable)); - if (iwl_fw_lookup_cmd_ver(mld->fw, - WIDE_ID(REGULATORY_AND_NVM_GROUP, - LARI_CONFIG_CHANGE), 12) == 12) { + if (cmd_ver == 12) { int cmd_size = offsetof(typeof(cmd), oem_11bn_allow_bitmap); ret = iwl_mld_send_cmd_pdu(mld, From 974cbad4c86d6442d9e08710322b924e4ae79023 Mon Sep 17 00:00:00 2001 From: Pagadala Yesu Anjaneyulu Date: Tue, 14 Jul 2026 17:02:15 +0300 Subject: [PATCH 0320/1433] wifi: iwlwifi: regulatory: add LARI_CONFIG_CHANGE command v14 support Add support for LARI_CONFIG_CHANGE version 14 and populate the newly added BIOS-related fields in the command payload. Extend the version 14 command layout with UHB extension, puncturing and WBEM metadata fields, update command-size handling for version negotiation, and wire the new data into the LARI configuration flow. Track WBEM and puncturing source/revision in fw runtime, set them when loading ACPI or UEFI tables, and pass the headers to firmware. Update send conditions, debug traces, and related documentation/comments to match the new format. Signed-off-by: Pagadala Yesu Anjaneyulu Link: https://patch.msgid.link/20260714165826.c73d2fbebbfe.I93af7c456f04ef10d03646a43aaeb1858ecdc36d@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/acpi.c | 4 +- .../wireless/intel/iwlwifi/fw/api/nvm-reg.h | 15 ++++- .../net/wireless/intel/iwlwifi/fw/runtime.h | 13 +++- drivers/net/wireless/intel/iwlwifi/fw/uefi.c | 20 ++++--- drivers/net/wireless/intel/iwlwifi/fw/uefi.h | 4 +- drivers/net/wireless/intel/iwlwifi/mld/mcc.c | 2 +- drivers/net/wireless/intel/iwlwifi/mld/mld.c | 2 +- drivers/net/wireless/intel/iwlwifi/mld/mld.h | 2 - .../wireless/intel/iwlwifi/mld/regulatory.c | 60 ++++++++++++++----- 9 files changed, 88 insertions(+), 34 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/acpi.c b/drivers/net/wireless/intel/iwlwifi/fw/acpi.c index bf0f851a9075..9f2f4a6af1ca 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/acpi.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/acpi.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* * Copyright (C) 2017 Intel Deutschland GmbH - * Copyright (C) 2019-2025 Intel Corporation + * Copyright (C) 2019-2026 Intel Corporation */ #include #include "iwl-drv.h" @@ -1188,6 +1188,8 @@ int iwl_acpi_get_wbem(struct iwl_fw_runtime *fwrt, u32 *value) *value = wifi_pkg->package.elements[1].integer.value & IWL_ACPI_WBEM_REV0_MASK; + fwrt->wbem_source = BIOS_SOURCE_ACPI; + fwrt->wbem_revision = tbl_rev; IWL_DEBUG_RADIO(fwrt, "Loaded WBEM config from ACPI\n"); ret = 0; out_free: diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h b/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h index 443a9a416325..d8ec9934a9b6 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2012-2014, 2018-2025 Intel Corporation + * Copyright (C) 2012-2014, 2018-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -662,6 +662,12 @@ struct iwl_lari_config_change_cmd_v8 { * get the data from the BIOS. * @oem_unii9_enable: UNII-9 enablement as read from the BIOS * @bios_hdr: bios config header + * @oem_uhb_allow_extension_bitmap: DSM Function 4 data as an extension of UHB + * enabled MCC sets + * @bios_wcpe_hdr: puncturing config header + * @wcpe_bitmap: bitmap of puncturing enablement per MCC + * @bios_wbem_hdr: 320 MHz per-MCC WBEM config header + * @reserved: reserved */ struct iwl_lari_config_change_cmd { __le32 config_bitmap; @@ -679,9 +685,16 @@ struct iwl_lari_config_change_cmd { __le32 oem_unii9_enable; /* since version 13 */ struct iwl_bios_config_hdr bios_hdr; + /* All the below are since version 14 */ + __le32 oem_uhb_allow_extension_bitmap; + struct iwl_bios_config_hdr bios_wcpe_hdr; + __le32 wcpe_bitmap; + struct iwl_bios_config_hdr bios_wbem_hdr; + __le32 reserved[10]; } __packed; /* LARI_CHANGE_CONF_CMD_S_VER_12 * LARI_CHANGE_CONF_CMD_S_VER_13 + * LARI_CHANGE_CONF_CMD_S_VER_14 */ /* Activate UNII-1 (5.2GHz) for World Wide */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/runtime.h b/drivers/net/wireless/intel/iwlwifi/fw/runtime.h index d80ae610e56c..ac01ac0092ee 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/runtime.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/runtime.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* * Copyright (C) 2017 Intel Deutschland GmbH - * Copyright (C) 2018-2025 Intel Corporation + * Copyright (C) 2018-2026 Intel Corporation */ #ifndef __iwl_fw_runtime_h__ #define __iwl_fw_runtime_h__ @@ -144,6 +144,11 @@ struct iwl_txf_iter_data { * @tpc_enabled: TPC enabled * @dsm_source: one of &enum bios_source. UEFI, ACPI or NONE * @dsm_revision: the revision of the DSM table + * @wbem_source: one of &enum bios_source for the WBEM table + * @wbem_revision: the revision of the WBEM table + * @puncturing_source: one of &enum bios_source for the puncturing table + * @puncturing_revision: the revision of the puncturing table + * @bios_puncturing: per-country puncturing enablement bitmap from BIOS */ struct iwl_fw_runtime { struct iwl_trans *trans; @@ -226,6 +231,12 @@ struct iwl_fw_runtime { u32 dsm_funcs_valid; u32 dsm_values[DSM_FUNC_NUM_FUNCS]; #endif + + enum bios_source wbem_source; + u8 wbem_revision; + enum bios_source puncturing_source; + u8 puncturing_revision; + u32 bios_puncturing; }; void iwl_fw_runtime_init(struct iwl_fw_runtime *fwrt, struct iwl_trans *trans, diff --git a/drivers/net/wireless/intel/iwlwifi/fw/uefi.c b/drivers/net/wireless/intel/iwlwifi/fw/uefi.c index 4cd36b42262c..01e495e24ebc 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/uefi.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/uefi.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright(c) 2021-2025 Intel Corporation + * Copyright(c) 2021-2026 Intel Corporation */ #include "iwl-drv.h" @@ -890,6 +890,8 @@ int iwl_uefi_get_wbem(struct iwl_fw_runtime *fwrt, u32 *value) goto out; } *value = data->wbem_320mhz_per_mcc & IWL_UEFI_WBEM_REV0_MASK; + fwrt->wbem_source = BIOS_SOURCE_UEFI; + fwrt->wbem_revision = data->revision; IWL_DEBUG_RADIO(fwrt, "Loaded WBEM config from UEFI\n"); out: kfree(data); @@ -976,29 +978,29 @@ int iwl_uefi_get_dsm(struct iwl_fw_runtime *fwrt, enum iwl_dsm_funcs func, int iwl_uefi_get_puncturing(struct iwl_fw_runtime *fwrt) { struct uefi_cnv_var_puncturing_data *data; - /* default value is not enabled if there is any issue in reading - * uefi variable or revision is not supported - */ - int puncturing = 0; + int ret = 0; data = iwl_uefi_get_verified_variable(fwrt->trans, IWL_UEFI_PUNCTURING_NAME, "UefiCnvWlanPuncturing", sizeof(*data), NULL); if (IS_ERR(data)) - return puncturing; + return -EINVAL; if (data->revision != IWL_UEFI_PUNCTURING_REVISION) { IWL_DEBUG_RADIO(fwrt, "Unsupported UEFI PUNCTURING rev:%d\n", data->revision); + ret = -EINVAL; } else { - puncturing = data->puncturing & IWL_UEFI_PUNCTURING_REV0_MASK; + fwrt->puncturing_source = BIOS_SOURCE_UEFI; + fwrt->puncturing_revision = data->revision; + fwrt->bios_puncturing = data->puncturing; IWL_DEBUG_RADIO(fwrt, "Loaded puncturing bits from UEFI: %d\n", - puncturing); + fwrt->bios_puncturing); } kfree(data); - return puncturing; + return ret; } IWL_EXPORT_SYMBOL(iwl_uefi_get_puncturing); diff --git a/drivers/net/wireless/intel/iwlwifi/fw/uefi.h b/drivers/net/wireless/intel/iwlwifi/fw/uefi.h index 474f06db4d43..386ca3744408 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/uefi.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/uefi.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright(c) 2021-2025 Intel Corporation + * Copyright(c) 2021-2026 Intel Corporation */ #ifndef __iwl_fw_uefi__ #define __iwl_fw_uefi__ @@ -275,8 +275,6 @@ enum iwl_uefi_cnv_puncturing_flags { IWL_UEFI_CNV_PUNCTURING_CANADA_EN_MSK = BIT(1), }; -#define IWL_UEFI_PUNCTURING_REV0_MASK (IWL_UEFI_CNV_PUNCTURING_USA_EN_MSK | \ - IWL_UEFI_CNV_PUNCTURING_CANADA_EN_MSK) /** * struct uefi_cnv_var_puncturing_data - controlling channel * puncturing for few countries. diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mcc.c b/drivers/net/wireless/intel/iwlwifi/mld/mcc.c index 8502129abe49..7649ae794dfd 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mcc.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mcc.c @@ -131,7 +131,7 @@ iwl_mld_get_regdomain(struct iwl_mld *mld, /* FM follows BIOS/MCC policy, WH disallows puncturing only in US/CA. */ if (CSR_HW_RFID_TYPE(mld->trans->info.hw_rf_id) == IWL_CFG_RF_TYPE_FM) { - if (!iwl_puncturing_is_allowed_in_bios(mld->bios_enable_puncturing, + if (!iwl_puncturing_is_allowed_in_bios(mld->fwrt.bios_puncturing, le16_to_cpu(resp->mcc))) ieee80211_hw_set(mld->hw, DISALLOW_PUNCTURING); else diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.c b/drivers/net/wireless/intel/iwlwifi/mld/mld.c index 5a6c9ab20fd6..99506f354bfa 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mld.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.c @@ -419,7 +419,7 @@ iwl_op_mode_mld_start(struct iwl_trans *trans, const struct iwl_rf_cfg *cfg, iwl_mld_get_bios_tables(mld); iwl_uefi_get_sgom_table(trans, &mld->fwrt); - mld->bios_enable_puncturing = iwl_uefi_get_puncturing(&mld->fwrt); + iwl_uefi_get_puncturing(&mld->fwrt); iwl_mld_hw_set_regulatory(mld); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.h b/drivers/net/wireless/intel/iwlwifi/mld/mld.h index 922aa3dbff54..37f41b504b54 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mld.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.h @@ -172,7 +172,6 @@ * @debug_max_sleep: maximum sleep time in D3 (for debug purposes) * @led: the led device * @mcc_src: the source id of the MCC, comes from the firmware - * @bios_enable_puncturing: is puncturing enabled by bios * @fw_id_to_ba: maps a fw (BA) id to a corresponding Block Ack session data. * @num_rx_ba_sessions: tracks the number of active Rx Block Ack (BA) sessions. * the driver ensures that new BA sessions are blocked once the maximum @@ -279,7 +278,6 @@ struct iwl_mld { struct led_classdev led; #endif enum iwl_mcc_source mcc_src; - bool bios_enable_puncturing; struct iwl_mld_baid_data __rcu *fw_id_to_ba[IWL_MAX_BAID]; u8 num_rx_ba_sessions; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c b/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c index 858635607f5d..4a93ccbef495 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c @@ -13,6 +13,23 @@ #include "mld.h" #include "hcmd.h" +static ssize_t iwl_mld_get_lari_config_cmd_size(u8 cmd_ver) +{ + switch (cmd_ver) { + case 14: + return sizeof(struct iwl_lari_config_change_cmd); + case 13: + return offsetof(struct iwl_lari_config_change_cmd, + oem_uhb_allow_extension_bitmap); + case 12: + return offsetof(struct iwl_lari_config_change_cmd, + oem_11bn_allow_bitmap); + default: + WARN(true, "unsupported version: %d", cmd_ver); + return -EINVAL; + } +} + void iwl_mld_get_bios_tables(struct iwl_mld *mld) { int ret; @@ -335,9 +352,13 @@ void iwl_mld_configure_lari(struct iwl_mld *mld) bool has_raw_dsm_capa = cmd_ver >= 13 || fw_has_capa(&fwrt->fw->ucode_capa, IWL_UCODE_TLV_CAPA_FW_ACCEPTS_RAW_DSM_TABLE); + ssize_t cmd_len = iwl_mld_get_lari_config_cmd_size(cmd_ver); int ret; u32 value; + if (cmd_len < 0) + return; + ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_11AX_ENABLEMENT, &value); if (!ret) { if (!has_raw_dsm_capa) @@ -379,8 +400,11 @@ void iwl_mld_configure_lari(struct iwl_mld *mld) } ret = iwl_bios_get_wbem(fwrt, &value); - if (!ret) + if (!ret) { cmd.oem_320mhz_allow_bitmap = cpu_to_le32(value); + cmd.bios_wbem_hdr.table_source = fwrt->wbem_source; + cmd.bios_wbem_hdr.table_revision = fwrt->wbem_revision; + } ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_ENABLE_11BE, &value); if (!ret) @@ -394,8 +418,17 @@ void iwl_mld_configure_lari(struct iwl_mld *mld) if (!ret) cmd.oem_unii9_enable = cpu_to_le32(value); + ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_REGULATORY_CONFIG, &value); + if (!ret) + cmd.oem_uhb_allow_extension_bitmap = cpu_to_le32(value); + + cmd.bios_wcpe_hdr.table_source = fwrt->puncturing_source; + cmd.bios_wcpe_hdr.table_revision = fwrt->puncturing_revision; + cmd.wcpe_bitmap = cpu_to_le32(fwrt->bios_puncturing); + if (!cmd.config_bitmap && !cmd.oem_uhb_allow_bitmap && + !cmd.oem_uhb_allow_extension_bitmap && !cmd.oem_11ax_allow_bitmap && !cmd.oem_unii4_allow_bitmap && !cmd.chan_state_active_bitmap && @@ -404,7 +437,8 @@ void iwl_mld_configure_lari(struct iwl_mld *mld) !cmd.oem_320mhz_allow_bitmap && !cmd.oem_11be_allow_bitmap && !cmd.oem_11bn_allow_bitmap && - !cmd.oem_unii9_enable) + !cmd.oem_unii9_enable && + !cmd.wcpe_bitmap) return; cmd.bios_hdr.table_source = fwrt->dsm_source; @@ -422,6 +456,9 @@ void iwl_mld_configure_lari(struct iwl_mld *mld) "sending LARI_CONFIG_CHANGE, oem_uhb_allow_bitmap=0x%x, force_disable_channels_bitmap=0x%x\n", le32_to_cpu(cmd.oem_uhb_allow_bitmap), le32_to_cpu(cmd.force_disable_channels_bitmap)); + IWL_DEBUG_RADIO(mld, + "sending LARI_CONFIG_CHANGE, oem_uhb_allow_extension_bitmap=0x%x\n", + le32_to_cpu(cmd.oem_uhb_allow_extension_bitmap)); IWL_DEBUG_RADIO(mld, "sending LARI_CONFIG_CHANGE, edt_bitmap=0x%x, oem_320mhz_allow_bitmap=0x%x\n", le32_to_cpu(cmd.edt_bitmap), @@ -435,20 +472,13 @@ void iwl_mld_configure_lari(struct iwl_mld *mld) IWL_DEBUG_RADIO(mld, "sending LARI_CONFIG_CHANGE, oem_unii9_enable=0x%x\n", le32_to_cpu(cmd.oem_unii9_enable)); + IWL_DEBUG_RADIO(mld, + "sending LARI_CONFIG_CHANGE, wcpe_bitmap=0x%x\n", + le32_to_cpu(cmd.wcpe_bitmap)); - if (cmd_ver == 12) { - int cmd_size = offsetof(typeof(cmd), oem_11bn_allow_bitmap); - - ret = iwl_mld_send_cmd_pdu(mld, - WIDE_ID(REGULATORY_AND_NVM_GROUP, - LARI_CONFIG_CHANGE), - &cmd, cmd_size); - } else { - ret = iwl_mld_send_cmd_pdu(mld, - WIDE_ID(REGULATORY_AND_NVM_GROUP, - LARI_CONFIG_CHANGE), - &cmd); - } + ret = iwl_mld_send_cmd_pdu(mld, + WIDE_ID(REGULATORY_AND_NVM_GROUP, + LARI_CONFIG_CHANGE), &cmd, cmd_len); if (ret) IWL_DEBUG_RADIO(mld, "Failed to send LARI_CONFIG_CHANGE (%d)\n", From a51cc8131214870af92abbceab64877b27e690c2 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 17:02:16 +0300 Subject: [PATCH 0321/1433] wifi: iwlwifi: mvm: reset the smart fifo state upon FW stop The smart fifo is a feature in the firmware configured by the driver. The driver keeps a state to remember what was the last configuration sent to the firmware. Obviously, if the firmware stops, we need to reconfigure the smart fifo. Since we didn't reset that state upon firmware stop, we thought the firmware is already properly configured and we didn't send the smart fifo configuration command as part of the init sequence. Reset the smart fifo state in iwl_mvm_stop_device() so that we will properly send the command during the init that will come later. Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260714165826.7d3c5efe2d1a.I16b23c328a677257257f695fa6f439e41fbcd081@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mvm/ops.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c index f43895c2f3c6..09150feb330d 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c @@ -1535,6 +1535,7 @@ void iwl_mvm_stop_device(struct iwl_mvm *mvm) iwl_fw_cancel_timestamp(&mvm->fwrt); clear_bit(IWL_MVM_STATUS_FIRMWARE_RUNNING, &mvm->status); + mvm->sf_state = SF_UNINIT; iwl_mvm_pause_tcm(mvm, false); From 0a00c3ec2db7674483dece4863f105b18a966202 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 17:02:17 +0300 Subject: [PATCH 0322/1433] wifi: iwlwifi: mvm: cleanup the driver state after device_powered_off If the device was powered off during suspend, we need to reload the firmware in resume which also means that we need to reconfigure it. It could be tempting to just set IWL_MVM_STATUS_HW_RESTART_REQUESTED but that would leave IWL_MVM_STATUS_IN_HW_RESTART set forever since that recovery is not managed by mac80211. Just call iwl_mvm_restart_cleanup() from device_powered_off(). Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260714165826.dc33533ac962.I6d7cca9c4e5643a477eae1d0a4f5fc83a10d0ee7@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c | 2 +- drivers/net/wireless/intel/iwlwifi/mvm/mvm.h | 1 + drivers/net/wireless/intel/iwlwifi/mvm/ops.c | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c index 74bd4038fd56..fd2a50563ab5 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c @@ -1129,7 +1129,7 @@ static void iwl_mvm_cleanup_iterator(void *data, u8 *mac, RCU_INIT_POINTER(mvmvif->deflink.probe_resp_data, NULL); } -static void iwl_mvm_restart_cleanup(struct iwl_mvm *mvm) +void iwl_mvm_restart_cleanup(struct iwl_mvm *mvm) { iwl_mvm_stop_device(mvm); diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h index 979b39314981..d6bed2c08606 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h @@ -1660,6 +1660,7 @@ void iwl_mvm_hwrate_to_tx_rate(u32 rate_n_flags, u8 iwl_mvm_rate_idx_to_fw_idx(const struct iwl_fw *fw, int rate_idx); u8 iwl_mvm_mac80211_ac_to_ucode_ac(enum ieee80211_ac_numbers ac); bool iwl_mvm_is_nic_ack_enabled(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_restart_cleanup(struct iwl_mvm *mvm); static inline void iwl_mvm_dump_nic_error_log(struct iwl_mvm *mvm) { diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c index 09150feb330d..6ae9f87d5221 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c @@ -2091,7 +2091,7 @@ static void iwl_op_mode_mvm_device_powered_off(struct iwl_op_mode *op_mode) mutex_lock(&mvm->mutex); clear_bit(IWL_MVM_STATUS_IN_D3, &mvm->status); - iwl_mvm_stop_device(mvm); + iwl_mvm_restart_cleanup(mvm); mvm->fast_resume = false; mutex_unlock(&mvm->mutex); } From 71ac392d8b5c23c45c66f30eff1c319580ada4da Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 17:02:18 +0300 Subject: [PATCH 0323/1433] wifi: iwlwifi: mld: reset the driver state upon firmware recovery Just like we did in iwlmvm, we also need to clear the mld state when the firmware was killed because of the device being powered off during suspend. Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260714165826.ff1c9e05e0a9.I8df8d4a0384065fd2a32cf258339be77084c55bd@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/mac80211.c | 3 +-- drivers/net/wireless/intel/iwlwifi/mld/mac80211.h | 2 ++ drivers/net/wireless/intel/iwlwifi/mld/mld.c | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index 17286b3341c0..4065d8e4fa8c 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -543,8 +543,7 @@ iwl_mld_mac80211_tx(struct ieee80211_hw *hw, iwl_mld_tx_skb(mld, skb, NULL); } -static void -iwl_mld_restart_cleanup(struct iwl_mld *mld) +void iwl_mld_restart_cleanup(struct iwl_mld *mld) { iwl_cleanup_mld(mld); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.h b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.h index aad04d7b2617..f3302997b28f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* * Copyright (C) 2024 Intel Corporation + * Copyright (C) 2026 Intel Corporation */ #ifndef __iwl_mld_mac80211_h__ #define __iwl_mld_mac80211_h__ @@ -9,5 +10,6 @@ int iwl_mld_register_hw(struct iwl_mld *mld); void iwl_mld_recalc_multicast_filter(struct iwl_mld *mld); +void iwl_mld_restart_cleanup(struct iwl_mld *mld); #endif /* __iwl_mld_mac80211_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.c b/drivers/net/wireless/intel/iwlwifi/mld/mld.c index 99506f354bfa..093bdc130704 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mld.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.c @@ -748,6 +748,7 @@ static void iwl_mld_device_powered_off(struct iwl_op_mode *op_mode) wiphy_lock(mld->wiphy); iwl_mld_stop_fw(mld); + iwl_mld_restart_cleanup(mld); mld->fw_status.in_d3 = false; wiphy_unlock(mld->wiphy); } From 0b7b17502300f7e7788fa5f1eab1147a960ede3c Mon Sep 17 00:00:00 2001 From: Shay Drory Date: Mon, 13 Jul 2026 11:43:19 +0300 Subject: [PATCH 0324/1433] net/mlx5: Drop redundant esw_cap, reuse e_switch_cap esw_manager_vport_number{,_valid} and merged_eswitch were read through a separate mlx5_ifc_esw_cap_bits struct, but these bits live in the e-switch capability that mlx5_ifc_e_switch_cap_bits already describes (both overlay the same QUERY_HCA_CAP op_mod 0x9 output). Add esw_manager_vport_number{,_valid} to mlx5_ifc_e_switch_cap_bits at the same offsets, drop the redundant mlx5_ifc_esw_cap_bits and its hca_cap_union member, and switch the only user (hws/cmd.c) to capability.e_switch_cap. Signed-off-by: Shay Drory Reviewed-by: Yevgeny Kliteynik Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260713084320.1015240-2-tariqt@nvidia.com Reviewed-by: Jacob Keller Signed-off-by: Leon Romanovsky --- .../mellanox/mlx5/core/steering/hws/cmd.c | 6 +++--- include/linux/mlx5/mlx5_ifc.h | 21 +++++-------------- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c index e624f5da96c8..8fae90101653 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c @@ -1172,13 +1172,13 @@ int mlx5hws_cmd_query_caps(struct mlx5_core_dev *mdev, } if (MLX5_GET(query_hca_cap_out, out, - capability.esw_cap.esw_manager_vport_number_valid)) + capability.e_switch_cap.esw_manager_vport_number_valid)) caps->eswitch_manager_vport_number = MLX5_GET(query_hca_cap_out, out, - capability.esw_cap.esw_manager_vport_number); + capability.e_switch_cap.esw_manager_vport_number); caps->merged_eswitch = MLX5_GET(query_hca_cap_out, out, - capability.esw_cap.merged_eswitch); + capability.e_switch_cap.merged_eswitch); } ret = mlx5_cmd_exec(mdev, in, sizeof(in), out, out_size); diff --git a/include/linux/mlx5/mlx5_ifc.h b/include/linux/mlx5/mlx5_ifc.h index 695c86ee6d7a..9c8dbf29f1ef 100644 --- a/include/linux/mlx5/mlx5_ifc.h +++ b/include/linux/mlx5/mlx5_ifc.h @@ -1042,20 +1042,6 @@ struct mlx5_ifc_wqe_based_flow_table_cap_bits { u8 reserved_at_1c1[0x1f]; }; -struct mlx5_ifc_esw_cap_bits { - u8 reserved_at_0[0x1d]; - u8 merged_eswitch[0x1]; - u8 reserved_at_1e[0x2]; - - u8 reserved_at_20[0x40]; - - u8 esw_manager_vport_number_valid[0x1]; - u8 reserved_at_61[0xf]; - u8 esw_manager_vport_number[0x10]; - - u8 reserved_at_80[0x780]; -}; - enum { MLX5_COUNTER_SOURCE_ESWITCH = 0x0, MLX5_COUNTER_FLOW_ESWITCH = 0x1, @@ -1096,7 +1082,11 @@ struct mlx5_ifc_e_switch_cap_bits { u8 log_max_esw_sf[0x5]; u8 esw_sf_base_id[0x10]; - u8 reserved_at_60[0x7a0]; + u8 esw_manager_vport_number_valid[0x1]; + u8 reserved_at_61[0xf]; + u8 esw_manager_vport_number[0x10]; + + u8 reserved_at_80[0x780]; }; @@ -3859,7 +3849,6 @@ union mlx5_ifc_hca_cap_union_bits { struct mlx5_ifc_flow_table_nic_cap_bits flow_table_nic_cap; struct mlx5_ifc_flow_table_eswitch_cap_bits flow_table_eswitch_cap; struct mlx5_ifc_wqe_based_flow_table_cap_bits wqe_based_flow_table_cap; - struct mlx5_ifc_esw_cap_bits esw_cap; struct mlx5_ifc_e_switch_cap_bits e_switch_cap; struct mlx5_ifc_port_selection_cap_bits port_selection_cap; struct mlx5_ifc_qos_cap_bits qos_cap; From bee40a7d0bd1263934f99054db037cdd4a33fd86 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Mon, 13 Jul 2026 11:43:20 +0300 Subject: [PATCH 0325/1433] net/mlx5: Add PSP related fields to the mlx5_ifc This adds: - misc_parameters_6, containing a few fields for matching PSP headers. As this is the last misc_parameters field defined, retire the old optimization added in commit [1] to not touch the reserved part. - PSP decap action. - PSP SPI header field pointer. [1] commit 667cb65ae5ad ("net/mlx5: Don't store reserved part in FTEs and FGs") Signed-off-by: Cosmin Ratiu Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260713084320.1015240-3-tariqt@nvidia.com Reviewed-by: Jacob Keller Signed-off-by: Leon Romanovsky --- .../net/ethernet/mellanox/mlx5/core/fs_core.h | 12 +----------- include/linux/mlx5/device.h | 1 + include/linux/mlx5/mlx5_ifc.h | 17 +++++++++++++++-- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/fs_core.h b/drivers/net/ethernet/mellanox/mlx5/core/fs_core.h index dbaf33b537f7..906584345a02 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/fs_core.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/fs_core.h @@ -214,17 +214,7 @@ struct mlx5_ft_underlay_qp { u32 qpn; }; -#define MLX5_FTE_MATCH_PARAM_RESERVED reserved_at_e00 -/* Calculate the fte_match_param length and without the reserved length. - * Make sure the reserved field is the last. - */ -#define MLX5_ST_SZ_DW_MATCH_PARAM \ - ((MLX5_BYTE_OFF(fte_match_param, MLX5_FTE_MATCH_PARAM_RESERVED) / sizeof(u32)) + \ - BUILD_BUG_ON_ZERO(MLX5_ST_SZ_BYTES(fte_match_param) != \ - MLX5_FLD_SZ_BYTES(fte_match_param, \ - MLX5_FTE_MATCH_PARAM_RESERVED) +\ - MLX5_BYTE_OFF(fte_match_param, \ - MLX5_FTE_MATCH_PARAM_RESERVED))) +#define MLX5_ST_SZ_DW_MATCH_PARAM MLX5_ST_SZ_DW(fte_match_param) struct fs_fte_action { int modify_mask; diff --git a/include/linux/mlx5/device.h b/include/linux/mlx5/device.h index 07a25f264292..8cb321a9fb3d 100644 --- a/include/linux/mlx5/device.h +++ b/include/linux/mlx5/device.h @@ -1171,6 +1171,7 @@ enum { MLX5_MATCH_MISC_PARAMETERS_3 = 1 << 4, MLX5_MATCH_MISC_PARAMETERS_4 = 1 << 5, MLX5_MATCH_MISC_PARAMETERS_5 = 1 << 6, + MLX5_MATCH_MISC_PARAMETERS_6 = 1 << 7, }; enum { diff --git a/include/linux/mlx5/mlx5_ifc.h b/include/linux/mlx5/mlx5_ifc.h index 9c8dbf29f1ef..c7206a9d6731 100644 --- a/include/linux/mlx5/mlx5_ifc.h +++ b/include/linux/mlx5/mlx5_ifc.h @@ -508,7 +508,8 @@ struct mlx5_ifc_flow_table_prop_layout_bits { u8 reformat_l2_to_l3_audp_tunnel[0x1]; u8 reformat_l3_audp_tunnel_to_l2[0x1]; u8 ignore_flow_level_rtc_valid[0x1]; - u8 reserved_at_70[0x8]; + u8 reserved_at_70[0x7]; + u8 reformat_del_psp_transport[0x1]; u8 log_max_ft_num[0x8]; u8 reserved_at_80[0x10]; @@ -798,6 +799,15 @@ struct mlx5_ifc_fte_match_set_misc5_bits { u8 reserved_at_100[0x100]; }; +struct mlx5_ifc_fte_match_set_misc6_bits { + u8 reserved_at_0[0x1a]; + u8 psp_version[0x4]; + u8 reserved_at_1e[0x2]; + + u8 reserved_at_20[0x1e0]; +}; + + struct mlx5_ifc_cmd_pas_bits { u8 pa_h[0x20]; @@ -2342,7 +2352,7 @@ struct mlx5_ifc_fte_match_param_bits { struct mlx5_ifc_fte_match_set_misc5_bits misc_parameters_5; - u8 reserved_at_e00[0x200]; + struct mlx5_ifc_fte_match_set_misc6_bits misc_parameters_6; }; enum { @@ -6988,6 +6998,7 @@ enum { MLX5_QUERY_FLOW_GROUP_IN_MATCH_CRITERIA_ENABLE_MISC_PARAMETERS_3 = 0x4, MLX5_QUERY_FLOW_GROUP_IN_MATCH_CRITERIA_ENABLE_MISC_PARAMETERS_4 = 0x5, MLX5_QUERY_FLOW_GROUP_IN_MATCH_CRITERIA_ENABLE_MISC_PARAMETERS_5 = 0x6, + MLX5_QUERY_FLOW_GROUP_IN_MATCH_CRITERIA_ENABLE_MISC_PARAMETERS_6 = 0x7, }; struct mlx5_ifc_query_flow_group_out_bits { @@ -7249,6 +7260,7 @@ enum mlx5_reformat_ctx_type { MLX5_REFORMAT_TYPE_REMOVE_HDR = 0x10, MLX5_REFORMAT_TYPE_ADD_MACSEC = 0x11, MLX5_REFORMAT_TYPE_DEL_MACSEC = 0x12, + MLX5_REFORMAT_TYPE_REMOVE_PSP_TRANSPORT = 0x16, }; struct mlx5_ifc_alloc_packet_reformat_context_in_bits { @@ -7372,6 +7384,7 @@ enum { MLX5_ACTION_IN_FIELD_OUT_EMD_47_32 = 0x6F, MLX5_ACTION_IN_FIELD_OUT_EMD_31_0 = 0x70, MLX5_ACTION_IN_FIELD_PSP_SYNDROME = 0x71, + MLX5_ACTION_IN_FIELD_PSP_HEADER_1 = 0x78, }; struct mlx5_ifc_alloc_modify_header_context_out_bits { From ef704fc32ae1a519801ac4a06aa290fece5f403c Mon Sep 17 00:00:00 2001 From: Avraham Stern Date: Wed, 15 Jul 2026 22:04:17 +0300 Subject: [PATCH 0326/1433] wifi: iwlwifi: mld: support aborting an ongoing ftm request Add support for aborting an ongoing FTM request. Signed-off-by: Avraham Stern Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715220243.340040bdc6a3.Ifbbf70021e42bf1d59db6ec45a73f1806a1c2289@changeid --- .../intel/iwlwifi/mld/ftm-initiator.c | 21 +++++++++++++++++++ .../intel/iwlwifi/mld/ftm-initiator.h | 1 + .../net/wireless/intel/iwlwifi/mld/mac80211.c | 10 +++++++++ 3 files changed, 32 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.c b/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.c index 81df3fdfcbf5..a4b8037b5c76 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.c @@ -459,3 +459,24 @@ void iwl_mld_ftm_restart_cleanup(struct iwl_mld *mld) mld->ftm_initiator.req, GFP_KERNEL); iwl_mld_ftm_reset(mld); } + +void iwl_mld_ftm_abort(struct iwl_mld *mld, struct cfg80211_pmsr_request *req) +{ + struct iwl_tof_range_abort_cmd cmd = { + .request_id = req->cookie, + }; + + lockdep_assert_wiphy(mld->wiphy); + + if (req != mld->ftm_initiator.req) + return; + + if (iwl_mld_send_cmd_pdu(mld, WIDE_ID(LOCATION_GROUP, + TOF_RANGE_ABORT_CMD), + &cmd)) + IWL_ERR(mld, "failed to abort FTM process\n"); + + iwl_mld_cancel_notifications_of_object(mld, IWL_MLD_OBJECT_TYPE_FTM_REQ, + mld->ftm_initiator.req->cookie); + iwl_mld_ftm_reset(mld); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.h b/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.h index 3fab25a52508..e82237b31467 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.h @@ -25,5 +25,6 @@ int iwl_mld_ftm_start(struct iwl_mld *mld, struct ieee80211_vif *vif, void iwl_mld_handle_ftm_resp_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt); void iwl_mld_ftm_restart_cleanup(struct iwl_mld *mld); +void iwl_mld_ftm_abort(struct iwl_mld *mld, struct cfg80211_pmsr_request *req); #endif /* __iwl_mld_ftm_initiator_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index 4065d8e4fa8c..17922ed3800d 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -2852,6 +2852,15 @@ static int iwl_mld_start_pmsr(struct ieee80211_hw *hw, return iwl_mld_ftm_start(mld, vif, request); } +static void iwl_mld_abort_pmsr(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *request) +{ + struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw); + + iwl_mld_ftm_abort(mld, request); +} + static enum ieee80211_neg_ttlm_res iwl_mld_can_neg_ttlm(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_neg_ttlm *neg_ttlm) @@ -2973,6 +2982,7 @@ const struct ieee80211_ops iwl_mld_hw_ops = { .prep_add_interface = iwl_mld_prep_add_interface, .set_hw_timestamp = iwl_mld_set_hw_timestamp, .start_pmsr = iwl_mld_start_pmsr, + .abort_pmsr = iwl_mld_abort_pmsr, .can_neg_ttlm = iwl_mld_can_neg_ttlm, .start_nan = iwl_mld_start_nan, .stop_nan = iwl_mld_stop_nan, From 7e16dad5d47e29338db9effbeb26b4e8bcfbc2c0 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 15 Jul 2026 22:04:18 +0300 Subject: [PATCH 0327/1433] wifi: iwlwifi: mld: validate WoWLAN notif header Validate fixed wowlan_info_notif header size first. Only then read num_mlo_link_keys from pkt->data. Apply this to v5 and v6 parsing paths. Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715220243.9c33c20194ad.I691d019927cc56898f2516fcf5795c8b1fae362c@changeid --- drivers/net/wireless/intel/iwlwifi/mld/d3.c | 57 ++++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/d3.c b/drivers/net/wireless/intel/iwlwifi/mld/d3.c index b5fed6090340..c9c0e3c729c9 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/d3.c @@ -573,18 +573,42 @@ iwl_mld_convert_wowlan_notif_v5(const struct iwl_wowlan_info_notif_v5 *notif_v5, } } -static bool iwl_mld_validate_wowlan_notif_size(struct iwl_mld *mld, - u32 len, - u32 expected_len, - u8 num_mlo_keys, +static bool iwl_mld_validate_wowlan_notif_size(struct iwl_mld *mld, u32 len, + const void *notif_data, int version) { u32 len_with_mlo_keys; + u32 expected_len; + u8 num_mlo_keys; - if (IWL_FW_CHECK(mld, len < expected_len, - "Invalid wowlan_info_notif v%d (expected=%u got=%u)\n", - version, expected_len, len)) + /* Extract num_mlo_keys from the void pointer based on version */ + if (version == 5) { + const struct iwl_wowlan_info_notif_v5 *notif_v5 = notif_data; + + expected_len = sizeof(*notif_v5); + + if (IWL_FW_CHECK(mld, len < expected_len, + "Invalid wowlan_info_notif v5 (expected=%u got=%u)\n", + expected_len, len)) + return false; + + num_mlo_keys = notif_v5->num_mlo_link_keys; + } else if (version == 6) { + const struct iwl_wowlan_info_notif *notif = notif_data; + + expected_len = sizeof(*notif); + + if (IWL_FW_CHECK(mld, len < expected_len, + "Invalid wowlan_info_notif v6 (expected=%u got=%u)\n", + expected_len, len)) + return false; + + num_mlo_keys = notif->num_mlo_link_keys; + } else { + IWL_WARN(mld, "Unsupported wowlan_info_notif version %d\n", + version); return false; + } len_with_mlo_keys = expected_len + (num_mlo_keys * sizeof(struct iwl_wowlan_mlo_gtk)); @@ -616,16 +640,14 @@ iwl_mld_handle_wowlan_info_notif(struct iwl_mld *mld, if (wowlan_info_ver == 5) { /* v5 format - validate before conversion */ - const struct iwl_wowlan_info_notif_v5 *notif_v5 = (void *)pkt->data; + const struct iwl_wowlan_info_notif_v5 *_notif = + (void *)pkt->data; - if (!iwl_mld_validate_wowlan_notif_size(mld, len, - sizeof(*notif_v5), - notif_v5->num_mlo_link_keys, - 5)) + if (!iwl_mld_validate_wowlan_notif_size(mld, len, _notif, 5)) return true; converted_notif = kzalloc_flex(*converted_notif, mlo_gtks, - notif_v5->num_mlo_link_keys, + _notif->num_mlo_link_keys, GFP_ATOMIC); if (!converted_notif) { IWL_ERR(mld, @@ -633,15 +655,12 @@ iwl_mld_handle_wowlan_info_notif(struct iwl_mld *mld, return true; } - iwl_mld_convert_wowlan_notif_v5(notif_v5, - converted_notif); + iwl_mld_convert_wowlan_notif_v5(_notif, converted_notif); notif = converted_notif; } else if (wowlan_info_ver == 6) { notif = (void *)pkt->data; - if (!iwl_mld_validate_wowlan_notif_size(mld, len, - sizeof(*notif), - notif->num_mlo_link_keys, - 6)) + + if (!iwl_mld_validate_wowlan_notif_size(mld, len, notif, 6)) return true; } else { /* smaller versions are not supported */ From bbe2d2fa8780a04dac8de0ecaa3895d3f3bc093e Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 15 Jul 2026 22:04:19 +0300 Subject: [PATCH 0328/1433] wifi: iwlwifi: mvm/mld: fix PPE threshold debug print loop The loop should print all bandwidths, the extra * results in calculating the wrong ARRAY_SIZE() here (of the array inside the per-bandwidth, not the per-bandwidth array.) Fix that, and also clarify the array variable assignment. Signed-off-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715220243.5e1d3448bfca.I6919627382d46605b41b0c6a6f34deedfd45523c@changeid --- drivers/net/wireless/intel/iwlwifi/mld/sta.c | 5 ++--- drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/sta.c b/drivers/net/wireless/intel/iwlwifi/mld/sta.c index e18d86f021dc..7957ac11b0cd 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/sta.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/sta.c @@ -355,10 +355,9 @@ static void iwl_mld_fill_pkt_ext(struct iwl_mld *mld, for (int i = 0; i < MAX_HE_SUPP_NSS; i++) { for (int bw = 0; - bw < ARRAY_SIZE(*pkt_ext->pkt_ext_qam_th[i]); + bw < ARRAY_SIZE(pkt_ext->pkt_ext_qam_th[i]); bw++) { - u8 *qam_th = - &pkt_ext->pkt_ext_qam_th[i][bw][0]; + u8 *qam_th = pkt_ext->pkt_ext_qam_th[i][bw]; IWL_DEBUG_HT(mld, "PPE table: nss[%d] bw[%d] PPET8 = %d, PPET16 = %d\n", diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c index fd2a50563ab5..14aec0bfd173 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c @@ -2352,10 +2352,9 @@ int iwl_mvm_set_sta_pkt_ext(struct iwl_mvm *mvm, int bw; for (bw = 0; - bw < ARRAY_SIZE(*pkt_ext->pkt_ext_qam_th[i]); + bw < ARRAY_SIZE(pkt_ext->pkt_ext_qam_th[i]); bw++) { - u8 *qam_th = - &pkt_ext->pkt_ext_qam_th[i][bw][0]; + u8 *qam_th = pkt_ext->pkt_ext_qam_th[i][bw]; IWL_DEBUG_HT(mvm, "PPE table: nss[%d] bw[%d] PPET8 = %d, PPET16 = %d\n", From 71e67b4b59337b2f9f4fef976a27de2dad7aabf2 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 15 Jul 2026 22:04:20 +0300 Subject: [PATCH 0329/1433] wifi: iwlwifi: fix counter type in iwl_fwrt_dump_error_logs The loop counter 'count' was declared as u8 while num_pc is u32. If firmware advertises more than 255 PC entries the counter wraps back to zero and the loop never terminates potentially causing an infinite loop or reading past the allocated pc_data array. Change the declaration to u32 to match num_pc. Fixes: 2b69d242e29b ("wifi: iwlwifi: fw: print PC register value instead of address") Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715220243.a61c65f34e87.Ie5f1a7ca43e0cc5a0ddc8305b0448ddffc09cd18@changeid --- drivers/net/wireless/intel/iwlwifi/fw/dump.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/dump.c b/drivers/net/wireless/intel/iwlwifi/fw/dump.c index c2af66899a78..bbbf3669a555 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/dump.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/dump.c @@ -369,7 +369,7 @@ static void iwl_fwrt_dump_fseq_regs(struct iwl_fw_runtime *fwrt) void iwl_fwrt_dump_error_logs(struct iwl_fw_runtime *fwrt) { struct iwl_pc_data *pc_data; - u8 count; + u32 count; if (!iwl_trans_device_enabled(fwrt->trans)) { IWL_ERR(fwrt, From 405ff50b72db1dfb86d7502c3c84208779809ab2 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 15 Jul 2026 22:04:21 +0300 Subject: [PATCH 0330/1433] wifi: iwlwifi: mld: fix validation fallback in iwl_mld_notif_is_valid When a firmware notification version is not in the handler's size table, iwl_mld_notif_is_valid() falls back to comparing against the last known structure size but the comparison is wrong: 'return size < last_known_size' returns true (accept) for undersized payloads and false (reject) for payloads that are large enough. Instead of trying to accept notifications that are large enough, just refuse the notification. We shouldn't ever get a notification that is longer than what we expect. Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715220243.e0b91efe689d.I7d7604be6819da263e9091370892e6b6f4c57913@changeid --- drivers/net/wireless/intel/iwlwifi/mld/notif.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/notif.c b/drivers/net/wireless/intel/iwlwifi/mld/notif.c index 7574689e4088..b3a899828db9 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/notif.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/notif.c @@ -517,7 +517,7 @@ iwl_mld_notif_is_valid(struct iwl_mld *mld, struct iwl_rx_packet *pkt, handler->cmd_id, notif_ver, handler->sizes[handler->n_sizes - 1].ver); - return size < handler->sizes[handler->n_sizes - 1].size; + return false; } struct iwl_async_handler_entry { From f6a6c01cbc046f68e6916a7e047a1bc881c8c9ab Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 15 Jul 2026 22:04:22 +0300 Subject: [PATCH 0331/1433] wifi: iwlwifi: mvm: fix off-by-one in TXF key sanitiser iwl_mvm_frob_txf_key_iter() tracks the last matched byte position in loop variable 'i'. When a full key match is found (match == keylen), 'i' points at the last byte of the matched key. The memset start offset should therefore be i + 1 - keylen, not i - keylen; the current code zeroes one byte before the match and leaves the final key byte un-sanitised. Fixes: 12d60c1efc29 ("iwlwifi: mvm: scrub key material in firmware dumps") Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715220243.355998ec4fbe.I40f3427657b897e911bdf4ebf8e494745508d126@changeid --- drivers/net/wireless/intel/iwlwifi/mvm/ops.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c index 6ae9f87d5221..da4c67a91113 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c @@ -954,7 +954,7 @@ static void iwl_mvm_frob_txf_key_iter(struct ieee80211_hw *hw, } match++; if (match == keylen) { - memset(txf->buf + i - keylen, 0xAA, keylen); + memset(txf->buf + i + 1 - keylen, 0xAA, keylen); match = 0; } } From ad13072308f82a9aa2e68a135e70672454367d92 Mon Sep 17 00:00:00 2001 From: Pagadala Yesu Anjaneyulu Date: Wed, 15 Jul 2026 22:04:23 +0300 Subject: [PATCH 0332/1433] wifi: iwlwifi: mld: honor FW puncturing capability in MCC response New MCC response versions expose puncturing support directly in the regulatory capability flags. Propagate that information from NVM MCC parsing to MLD MCC handling and fall back to legacy FM/WH MCC-specific policy when puncturing status is unknown. Signed-off-by: Pagadala Yesu Anjaneyulu Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715220243.47d1389fa134.I5c7921d6e3c065e3962c5927991498c2d277fd8f@changeid --- .../wireless/intel/iwlwifi/iwl-nvm-parse.c | 30 ++++++++++++++++++- .../wireless/intel/iwlwifi/iwl-nvm-parse.h | 22 ++++++++++++-- drivers/net/wireless/intel/iwlwifi/mld/mcc.c | 24 ++++++++++----- .../net/wireless/intel/iwlwifi/mvm/mac80211.c | 2 +- 4 files changed, 67 insertions(+), 11 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c index 761424812609..2f38eea42963 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c @@ -230,6 +230,18 @@ enum iwl_reg_capa_flags_v5 { REG_CAPA_V5_11BN_DISABLED = BIT(17), }; /* GEO_CHANNEL_CAPABILITIES_API_S_VER_4, 5 */ +/** + * enum iwl_reg_capa_flags_v6 - global capability flags, + * applicable from MCC response version 10 onwards. + * Response v6 includes all members of iwl_reg_capa_flags_v5; only v6-specific + * additions are listed here. + * @REG_CAPA_V6_EHT_PUNCTURING_ENABLED: EHT puncturing is enabled for this + * regulatory domain. + */ +enum iwl_reg_capa_flags_v6 { + REG_CAPA_V6_EHT_PUNCTURING_ENABLED = BIT(18), +}; /* GEO_CHANNEL_CAPABILITIES_API_S_VER_6 */ + /* * API v2 for reg_capa_flags is relevant from version 6 and onwards of the * MCC update command response. @@ -241,6 +253,11 @@ enum iwl_reg_capa_flags_v5 { */ #define REG_CAPA_V4_RESP_VER 8 +/* API v6 for reg_capa_flags is relevant from version 10 and onwards of the + * MCC update command response. + */ +#define REG_CAPA_V6_RESP_VER 10 + static inline void iwl_nvm_print_channel_flags(struct device *dev, u32 level, int chan, u32 flags) { @@ -1686,6 +1703,13 @@ static struct iwl_reg_capa iwl_get_reg_capa(u32 flags, u8 resp_ver) { struct iwl_reg_capa reg_capa = {}; + if (resp_ver >= REG_CAPA_V6_RESP_VER) { + if (flags & REG_CAPA_V6_EHT_PUNCTURING_ENABLED) + reg_capa.puncturing_status = IWL_PUNCTURING_STATUS_ENABLED; + else + reg_capa.puncturing_status = IWL_PUNCTURING_STATUS_DISABLED; + } + if (resp_ver >= REG_CAPA_V4_RESP_VER) { reg_capa.allow_40mhz = true; reg_capa.allow_80mhz = flags & REG_CAPA_V5_80MHZ_ALLOWED; @@ -1712,7 +1736,8 @@ static struct iwl_reg_capa iwl_get_reg_capa(u32 flags, u8 resp_ver) struct ieee80211_regdomain * iwl_parse_nvm_mcc_info(struct iwl_trans *trans, int num_of_ch, __le32 *channels, u16 fw_mcc, - u16 geo_info, u32 cap, u8 resp_ver) + u16 geo_info, u32 cap, u8 resp_ver, + enum iwl_puncturing_status *puncturing_status) { const struct iwl_rf_cfg *cfg = trans->cfg; struct device *dev = trans->dev; @@ -1772,6 +1797,9 @@ iwl_parse_nvm_mcc_info(struct iwl_trans *trans, /* parse regulatory capability flags */ reg_capa = iwl_get_reg_capa(cap, resp_ver); + if (puncturing_status) + *puncturing_status = reg_capa.puncturing_status; + for (ch_idx = 0; ch_idx < num_of_ch; ch_idx++) { enum nl80211_band band = iwl_nl80211_band_from_channel_idx(ch_idx); diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.h b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.h index e676d7c2d6cc..9ebb72d3726a 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2005-2015, 2018-2025 Intel Corporation + * Copyright (C) 2005-2015, 2018-2026 Intel Corporation * Copyright (C) 2016-2017 Intel Deutschland GmbH */ #ifndef __iwl_nvm_parse_h__ @@ -21,6 +21,19 @@ enum iwl_nvm_sbands_flags { IWL_NVM_SBANDS_FLAGS_NO_WIDE_IN_5GHZ = BIT(1), }; +/** + * enum iwl_puncturing_status - EHT puncturing status from MCC capabilities + * @IWL_PUNCTURING_STATUS_UNKNOWN: puncturing status is not provided by FW + * or is not initialized yet + * @IWL_PUNCTURING_STATUS_ENABLED: puncturing is enabled for the current MCC + * @IWL_PUNCTURING_STATUS_DISABLED: puncturing is disabled for the current MCC + */ +enum iwl_puncturing_status { + IWL_PUNCTURING_STATUS_UNKNOWN, + IWL_PUNCTURING_STATUS_ENABLED, + IWL_PUNCTURING_STATUS_DISABLED, +}; + /** * struct iwl_reg_capa - struct for global regulatory capabilities, Used for * handling the different APIs of reg_capa_flags. @@ -36,6 +49,8 @@ enum iwl_nvm_sbands_flags { * @disable_11ax: 11ax is forbidden for this regulatory domain. * @disable_11be: 11be is forbidden for this regulatory domain. * @disable_11bn: UHR/11bn is not allowed for this regulatory domain + * @puncturing_status: EHT puncturing status for the current MCC. + * See &enum iwl_puncturing_status. */ struct iwl_reg_capa { bool allow_40mhz; @@ -45,6 +60,7 @@ struct iwl_reg_capa { bool disable_11ax; bool disable_11be; bool disable_11bn; + enum iwl_puncturing_status puncturing_status; }; /** @@ -131,11 +147,13 @@ iwl_parse_nvm_data(struct iwl_trans *trans, const struct iwl_rf_cfg *cfg, * @geo_info: geo info value * @cap: capability * @resp_ver: FW response version + * @puncturing_status: FW puncturing status for current MCC, when available */ struct ieee80211_regdomain * iwl_parse_nvm_mcc_info(struct iwl_trans *trans, int num_of_ch, __le32 *channels, u16 fw_mcc, - u16 geo_info, u32 cap, u8 resp_ver); + u16 geo_info, u32 cap, u8 resp_ver, + enum iwl_puncturing_status *puncturing_status); /** * struct iwl_nvm_section - describes an NVM section in memory. diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mcc.c b/drivers/net/wireless/intel/iwlwifi/mld/mcc.c index 7649ae794dfd..e3d1fa370616 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mcc.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mcc.c @@ -89,6 +89,8 @@ iwl_mld_get_regdomain(struct iwl_mld *mld, struct iwl_mcc_update_resp_v8 *resp; u8 resp_ver = iwl_fw_lookup_notif_ver(mld->fw, IWL_ALWAYS_LONG_GROUP, MCC_UPDATE_CMD, 0); + enum iwl_puncturing_status puncturing_status; + u16 mcc; IWL_DEBUG_LAR(mld, "Getting regdomain data for %s from FW\n", alpha2); @@ -110,12 +112,13 @@ iwl_mld_get_regdomain(struct iwl_mld *mld, } IWL_DEBUG_LAR(mld, "MCC update response version: %d\n", resp_ver); + mcc = le16_to_cpu(resp->mcc); regd = iwl_parse_nvm_mcc_info(mld->trans, __le32_to_cpu(resp->n_channels), - resp->channels, - __le16_to_cpu(resp->mcc), + resp->channels, mcc, __le16_to_cpu(resp->geo_info), - le32_to_cpu(resp->cap), resp_ver); + le32_to_cpu(resp->cap), resp_ver, + &puncturing_status); if (IS_ERR(regd)) { IWL_DEBUG_LAR(mld, "Could not get parse update from FW %ld\n", @@ -129,18 +132,25 @@ iwl_mld_get_regdomain(struct iwl_mld *mld, mld->mcc_src = resp->source_id; + if (puncturing_status == IWL_PUNCTURING_STATUS_ENABLED) + __clear_bit(IEEE80211_HW_DISALLOW_PUNCTURING, + mld->hw->flags); + else if (puncturing_status == IWL_PUNCTURING_STATUS_DISABLED) + ieee80211_hw_set(mld->hw, DISALLOW_PUNCTURING); + + if (resp_ver >= 10) + goto out; + /* FM follows BIOS/MCC policy, WH disallows puncturing only in US/CA. */ if (CSR_HW_RFID_TYPE(mld->trans->info.hw_rf_id) == IWL_CFG_RF_TYPE_FM) { if (!iwl_puncturing_is_allowed_in_bios(mld->fwrt.bios_puncturing, - le16_to_cpu(resp->mcc))) + mcc)) ieee80211_hw_set(mld->hw, DISALLOW_PUNCTURING); else __clear_bit(IEEE80211_HW_DISALLOW_PUNCTURING, mld->hw->flags); } else if (CSR_HW_RFID_TYPE(mld->trans->info.hw_rf_id) == - IWL_CFG_RF_TYPE_WH) { - u16 mcc = le16_to_cpu(resp->mcc); - + IWL_CFG_RF_TYPE_WH) { if (mcc == IWL_MCC_US || mcc == IWL_MCC_CANADA) ieee80211_hw_set(mld->hw, DISALLOW_PUNCTURING); else diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c index 14aec0bfd173..37e2e2c6b716 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c @@ -152,7 +152,7 @@ struct ieee80211_regdomain *iwl_mvm_get_regdomain(struct wiphy *wiphy, resp->channels, __le16_to_cpu(resp->mcc), __le16_to_cpu(resp->geo_info), - le32_to_cpu(resp->cap), resp_ver); + le32_to_cpu(resp->cap), resp_ver, NULL); /* Store the return source id */ src_id = resp->source_id; if (IS_ERR_OR_NULL(regd)) { From ebc246e1d5a3e4502dba60cf9e9644de0fc5a8b6 Mon Sep 17 00:00:00 2001 From: Pagadala Yesu Anjaneyulu Date: Wed, 15 Jul 2026 22:04:24 +0300 Subject: [PATCH 0333/1433] wifi: iwlwifi: mvm: add LARI_CONFIG_EXTENSION command There is a new UHB extension bitmap that is part of the LARI configuration, which needs to be sent to the FW - also frozen ones. But in frozen FWs we cannot increase the version of an API, since the driver assumes a specific version, depending on the core number. In case of a (new) FW that expects the new version and a (old) driver that doesn't support that new version, the driver will send a default old version, causing a fw assert about its bad size. To mitigate this, there is a special command which will be supported only on those frozen FWs. Old drivers will simply not support/send it, and new driver will send it if supported by fw. Signed-off-by: Pagadala Yesu Anjaneyulu Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715220243.551f40ee2de3.I3e32a5d5c9aa13cbb0e599bef630cdb8e3b031c4@changeid --- .../wireless/intel/iwlwifi/fw/api/nvm-reg.h | 26 ++++++++++++++ drivers/net/wireless/intel/iwlwifi/mvm/fw.c | 36 +++++++++++++++++++ drivers/net/wireless/intel/iwlwifi/mvm/ops.c | 1 + 3 files changed, 63 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h b/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h index d8ec9934a9b6..360b626a9572 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h @@ -46,6 +46,11 @@ enum iwl_regulatory_and_nvm_subcmd_ids { */ MCC_ALLOWED_AP_TYPE_CMD = 0x5, + /** + * @LARI_CONFIG_EXTENSION: &struct iwl_lari_config_extension_cmd + */ + LARI_CONFIG_EXTENSION = 0x8, + /** * @PNVM_INIT_COMPLETE_NTFY: &struct iwl_pnvm_init_complete_ntfy */ @@ -514,6 +519,27 @@ struct iwl_bios_config_hdr { u8 reserved[2]; } __packed; /* BIOS_CONFIG_HDR_API_S_VER_1 */ +/** + * struct iwl_lari_config_extension_cmd - extend LARI configuration + * + * LARI_CONFIG_CHANGE's version must remain stable for frozen firmware. + * Because the driver might not know this version but still load that + * frozen FW and then send some default old version of LARI, causing the FW to + * assert about the bad size of it. To handle this, we do the following: + * 1. For newer firmware: increase the LARI_CONFIG_CHANGE version to support the + * new firmware API with extra UHB bits. + * 2. For frozen firmware: add a special alternative API that doesn't require + * modifying the frozen LARI_CONFIG_CHANGE's version. + * @dsm_table_hdr: BIOS DSM table source and revision + * @oem_uhb_allow_extension_bitmap: extension bitmap for OEM UHB config + * @reserved: reserved + */ +struct iwl_lari_config_extension_cmd { + struct iwl_bios_config_hdr dsm_table_hdr; + __le32 oem_uhb_allow_extension_bitmap; + __le32 reserved[10]; +} __packed; /* LARI_CONFIG_EXTENSION_CMD_API_S_VER_1 */ + /** * struct bios_value_u32 - BIOS configuration. * @hdr: bios config header diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/fw.c b/drivers/net/wireless/intel/iwlwifi/mvm/fw.c index 6e507d6dcdd2..187f39aa6249 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/fw.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/fw.c @@ -1507,12 +1507,48 @@ static int iwl_mvm_fill_lari_config(struct iwl_fw_runtime *fwrt, return 0; } +static void iwl_mvm_send_lari_cfg_extension(struct iwl_mvm *mvm) +{ + struct iwl_fw_runtime *fwrt = &mvm->fwrt; + struct iwl_lari_config_extension_cmd cmd = {}; + u32 cmd_id = WIDE_ID(REGULATORY_AND_NVM_GROUP, + LARI_CONFIG_EXTENSION); + u32 value; + int ret; + + if (iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id, 0) < 1) + return; + + ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_REGULATORY_CONFIG, &value); + if (ret) + return; + + cmd.dsm_table_hdr.table_source = fwrt->dsm_source; + cmd.dsm_table_hdr.table_revision = fwrt->dsm_revision; + cmd.oem_uhb_allow_extension_bitmap = cpu_to_le32(value); + + IWL_DEBUG_RADIO(mvm, + "sending LARI_CONFIG_EXTENSION, oem_uhb_allow_extension_bitmap=0x%x\n", + le32_to_cpu(cmd.oem_uhb_allow_extension_bitmap)); + ret = iwl_mvm_send_cmd_pdu(mvm, cmd_id, 0, sizeof(cmd), &cmd); + if (ret < 0) + IWL_DEBUG_RADIO(mvm, + "Failed to send LARI_CONFIG_EXTENSION (%d)\n", + ret); +} + static void iwl_mvm_lari_cfg(struct iwl_mvm *mvm) { struct iwl_lari_config_change_cmd cmd; size_t cmd_size; int ret; + /* + * LARI_CONFIG_CHANGE triggers a profile update, so send + * LARI_CONFIG_EXTENSION first to make sure its data is applied + * in the same update. + */ + iwl_mvm_send_lari_cfg_extension(mvm); ret = iwl_mvm_fill_lari_config(&mvm->fwrt, &cmd, &cmd_size); if (!ret) { ret = iwl_mvm_send_cmd_pdu(mvm, diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c index da4c67a91113..1dd27292af9b 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c @@ -707,6 +707,7 @@ static const struct iwl_hcmd_names iwl_mvm_regulatory_and_nvm_names[] = { HCMD_NAME(NVM_ACCESS_COMPLETE), HCMD_NAME(NVM_GET_INFO), HCMD_NAME(TAS_CONFIG), + HCMD_NAME(LARI_CONFIG_EXTENSION), }; /* Please keep this array *SORTED* by hex value. From 51c45bb2c884e0e2f56d6770011994d653371702 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Wed, 15 Jul 2026 22:04:25 +0300 Subject: [PATCH 0334/1433] wifi: iwlwifi: mld: add PNVM_INIT_COMPLETE_NTFY to the hcmd names Add it to the array of host command name so it will be printed with iwl_get_cmd_string Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715220243.e8da467f1883.I75ab56a022f31365042d29aff5484e4329b0f6ce@changeid --- drivers/net/wireless/intel/iwlwifi/mld/mld.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.c b/drivers/net/wireless/intel/iwlwifi/mld/mld.c index 093bdc130704..7c49d0441fdf 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mld.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.c @@ -212,6 +212,7 @@ static const struct iwl_hcmd_names iwl_mld_reg_and_nvm_names[] = { HCMD_NAME(TAS_CONFIG), HCMD_NAME(SAR_OFFSET_MAPPING_TABLE_CMD), HCMD_NAME(MCC_ALLOWED_AP_TYPE_CMD), + HCMD_NAME(PNVM_INIT_COMPLETE_NTFY), }; /* Please keep this array *SORTED* by hex value. From c5aeb11489150be84d3a700711abffd98969d2d5 Mon Sep 17 00:00:00 2001 From: Shahar Tzarfati Date: Wed, 15 Jul 2026 22:04:26 +0300 Subject: [PATCH 0335/1433] wifi: iwlwifi: mld: fix read in wake packet notification handler In iwl_mld_handle_wake_pkt_notif(), expected_size was initialized from notif->wake_packet_length before the IWL_FW_CHECK that validates the payload covers sizeof(*notif). Move the assignment of expected_size to after the size check so that notif->wake_packet_length is only accessed once the payload length has been validated. Signed-off-by: Shahar Tzarfati Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715220243.94c526d2c66e.I065a19a9dcc7f45a7457667c0f625fcd2c7bf6b6@changeid --- drivers/net/wireless/intel/iwlwifi/mld/d3.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/d3.c b/drivers/net/wireless/intel/iwlwifi/mld/d3.c index c9c0e3c729c9..3b785c53948f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/d3.c @@ -705,7 +705,7 @@ iwl_mld_handle_wake_pkt_notif(struct iwl_mld *mld, { const struct iwl_wowlan_wake_pkt_notif *notif = (void *)pkt->data; u32 actual_size, len = iwl_rx_packet_payload_len(pkt); - u32 expected_size = le32_to_cpu(notif->wake_packet_length); + u32 expected_size; if (IWL_FW_CHECK(mld, len < sizeof(*notif), "Invalid WoWLAN wake packet notification (expected size=%zu got=%u)\n", @@ -718,6 +718,7 @@ iwl_mld_handle_wake_pkt_notif(struct iwl_mld *mld, wowlan_status->wakeup_reasons)) return true; + expected_size = le32_to_cpu(notif->wake_packet_length); actual_size = len - offsetof(struct iwl_wowlan_wake_pkt_notif, wake_packet); From 7d8cc301bcba233f31b589a45f4c1c97f2bb90d6 Mon Sep 17 00:00:00 2001 From: Avraham Stern Date: Wed, 15 Jul 2026 22:04:27 +0300 Subject: [PATCH 0336/1433] wifi: iwlwifi: mei: check SAP message length before reading it Verify the SAP message size is not larger than the local buffer before reading the message to avoid buffer overflow. Fixes: bcd68b3dbe78 ("wifi: iwlwifi: mei: fix tx DHCP packet for devices with new Tx API") Signed-off-by: Avraham Stern Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715220243.f0026ce26218.I00a856d3aacae1caac605c708f7362689b734234@changeid --- drivers/net/wireless/intel/iwlwifi/mei/main.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mei/main.c b/drivers/net/wireless/intel/iwlwifi/mei/main.c index c5ff1b1b720f..c01435859349 100644 --- a/drivers/net/wireless/intel/iwlwifi/mei/main.c +++ b/drivers/net/wireless/intel/iwlwifi/mei/main.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2021-2024 Intel Corporation + * Copyright (C) 2026 Intel Corporation */ #include @@ -1147,6 +1148,11 @@ static void iwl_mei_handle_sap_rx_cmd(struct mei_cl_device *cldev, iwl_mei_read_from_q(q_head, q_sz, &rd, wr, hdr, sizeof(*hdr)); valid_rx_sz -= sizeof(*hdr); len = le16_to_cpu(hdr->len); + if (len + sizeof(*hdr) > PAGE_SIZE) { + dev_err(&cldev->dev, + "SAP message is too big: %u\n", len); + break; + } if (valid_rx_sz < len) break; From c00a5d65b7ada536eb488280fae53fcf1724a7c3 Mon Sep 17 00:00:00 2001 From: Avraham Stern Date: Wed, 15 Jul 2026 22:04:28 +0300 Subject: [PATCH 0337/1433] wifi: iwlwifi: mei: skip data read if length is too short When calculating the SAP data length, the code subtracts sizeof(*ethhdr) from len. If the SAP data header indicates a length that is shorter than ethernet header length, this will result in an unsigned underflow which will lead to a kernel panic when trying to put the data into the SKB. Fix it by skipping a message if the indicated length is too short. In addition, if the message type is not SAP_MSG_DATA_PACKET or skb allocation fails, the loop skips to the next message but without reading the message payload. This may result in reading the payload as the next message header, which will lead to errors in parsing the next messages. Fix it by skipping the message payload as well. Signed-off-by: Avraham Stern Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715220243.f66b10736047.I4a1dde517c36561d41358dd82a5cec8b6c886c14@changeid --- drivers/net/wireless/intel/iwlwifi/mei/main.c | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mei/main.c b/drivers/net/wireless/intel/iwlwifi/mei/main.c index c01435859349..c462c3b22ec1 100644 --- a/drivers/net/wireless/intel/iwlwifi/mei/main.c +++ b/drivers/net/wireless/intel/iwlwifi/mei/main.c @@ -1041,11 +1041,14 @@ static void iwl_mei_read_from_q(const u8 *q_head, u32 q_sz, u32 rd = *_rd; if (rd + len <= q_sz) { - memcpy(buf, q_head + rd, len); + if (buf) + memcpy(buf, q_head + rd, len); rd += len; } else { - memcpy(buf, q_head + rd, q_sz - rd); - memcpy(buf + q_sz - rd, q_head, len - (q_sz - rd)); + if (buf) { + memcpy(buf, q_head + rd, q_sz - rd); + memcpy(buf + q_sz - rd, q_head, len - (q_sz - rd)); + } rd = len - (q_sz - rd); } @@ -1086,24 +1089,29 @@ static void iwl_mei_handle_sap_data(struct mei_cl_device *cldev, break; } + valid_rx_sz -= len; + if (len < sizeof(*ethhdr)) { dev_err(&cldev->dev, "Data len is smaller than an ethernet header? len = %d\n", len); + iwl_mei_read_from_q(q_head, q_sz, &rd, wr, NULL, len); + continue; } - valid_rx_sz -= len; - if (le16_to_cpu(hdr.type) != SAP_MSG_DATA_PACKET) { dev_err(&cldev->dev, "Unsupported Rx data: type %d, len %d\n", le16_to_cpu(hdr.type), len); + iwl_mei_read_from_q(q_head, q_sz, &rd, wr, NULL, len); continue; } /* We need enough room for the WiFi header + SNAP + IV */ skb = netdev_alloc_skb(netdev, len + QOS_HDR_IV_SNAP_LEN); - if (!skb) + if (!skb) { + iwl_mei_read_from_q(q_head, q_sz, &rd, wr, NULL, len); continue; + } skb_reserve(skb, QOS_HDR_IV_SNAP_LEN); ethhdr = skb_push(skb, sizeof(*ethhdr)); From 9318bc0c41b24705690cf80d1596cf6b711e7027 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 15 Jul 2026 22:04:29 +0300 Subject: [PATCH 0338/1433] wifi: iwlwifi: guard against division by zero in iwl_dbg_tlv_alloc_fragments Make sure we don't end-up with a num_frags = 0 situation. For that, check that the required size is not 0 and put a checker on num_frags as well. Fixes: 14124b25780d ("iwlwifi: dbg_ini: implement monitor allocation flow") Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715220243.60121deecf2c.Iebc891c95a7bd1b2a093b0bb88532db446a758ee@changeid --- drivers/net/wireless/intel/iwlwifi/iwl-dbg-tlv.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-dbg-tlv.c b/drivers/net/wireless/intel/iwlwifi/iwl-dbg-tlv.c index d021b24d04d6..8b0f091ff4c1 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-dbg-tlv.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-dbg-tlv.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2018-2025 Intel Corporation + * Copyright (C) 2018-2026 Intel Corporation */ #include #include "iwl-drv.h" @@ -602,6 +602,9 @@ static int iwl_dbg_tlv_alloc_fragments(struct iwl_fw_runtime *fwrt, cpu_to_le32(IWL_FW_INI_LOCATION_DRAM_PATH)) return 0; + if (!fw_mon_cfg->req_size) + return -EIO; + num_frags = le32_to_cpu(fw_mon_cfg->max_frags_num); if (fwrt->trans->mac_cfg->device_family < IWL_DEVICE_FAMILY_AX210) { if (alloc_id != IWL_FW_INI_ALLOCATION_ID_DBGC1) @@ -612,6 +615,9 @@ static int iwl_dbg_tlv_alloc_fragments(struct iwl_fw_runtime *fwrt, return -EIO; } + if (!num_frags) + return -EIO; + remain_pages = DIV_ROUND_UP(le32_to_cpu(fw_mon_cfg->req_size), PAGE_SIZE); num_frags = min_t(u32, num_frags, BUF_ALLOC_MAX_NUM_FRAGS); From 1c031ac5a39ebcc3269eace8b606043adc398bfa Mon Sep 17 00:00:00 2001 From: Ilan Peer Date: Wed, 15 Jul 2026 22:04:30 +0300 Subject: [PATCH 0339/1433] wifi: iwlwifi: mld: Do not cleanup FW state when the device is dead When a channel context is unassigned, there is a path to cleanup the FW state in case of NON MLO connection: remove the link and add it again. However, when the transport is dead, e.g., during device removal etc., this flow will fail and as a result the mld_vif->link[0] would be set to NULL. Later, when the interface is removed, iwl_mld_remove_link() would warn as the link is NULL. Fix this by not doing the cleanup when the device is dead. Signed-off-by: Ilan Peer Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715220243.89a0c44a72a3.I45cca8b84a25943d5771199af6b0155dbac77b58@changeid --- drivers/net/wireless/intel/iwlwifi/mld/mac80211.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index 17922ed3800d..92985e500459 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -1280,11 +1280,13 @@ void iwl_mld_unassign_vif_chanctx(struct ieee80211_hw *hw, iwl_mld_tlc_update_phy(mld, vif, link); /* in the non-MLO case, remove/re-add the link to clean up FW state. - * In MLO, it'll be done in drv_change_vif_link + * In MLO, it'll be done in drv_change_vif_link. + * Do not do so during restart or in case the device is dead. */ if (!ieee80211_vif_is_mld(vif) && !mld_vif->ap_sta && !WARN_ON_ONCE(vif->cfg.assoc) && - vif->type != NL80211_IFTYPE_AP && !mld->fw_status.in_hw_restart) { + vif->type != NL80211_IFTYPE_AP && !mld->fw_status.in_hw_restart && + !iwl_trans_is_dead(mld->trans)) { iwl_mld_remove_link(mld, link); iwl_mld_add_link(mld, link); } From 905f57aefde4f4092a411c8a55856182fb1c7598 Mon Sep 17 00:00:00 2001 From: Avraham Stern Date: Wed, 15 Jul 2026 22:04:31 +0300 Subject: [PATCH 0340/1433] wifi: iwlwifi: mei: pass correct argument to function The first argument to iwl_mei_write_cyclic_buf() should be the cldev but the q_head pointer is passed instead. Fix it. Fixes: 652291601459 ("iwlwifi: mei: don't rely on the size from the shared area") Signed-off-by: Avraham Stern Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715220243.24cea60c6428.I42301010c31487b1458faa967b22c8320b0cfd23@changeid --- drivers/net/wireless/intel/iwlwifi/mei/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mei/main.c b/drivers/net/wireless/intel/iwlwifi/mei/main.c index c462c3b22ec1..ce92e26c42d7 100644 --- a/drivers/net/wireless/intel/iwlwifi/mei/main.c +++ b/drivers/net/wireless/intel/iwlwifi/mei/main.c @@ -458,7 +458,7 @@ static int iwl_mei_send_sap_msg_payload(struct mei_cl_device *cldev, notif_q = &dir->q_ctrl_blk[SAP_QUEUE_IDX_NOTIF]; q_head = mei->shared_mem.q_head[SAP_DIRECTION_HOST_TO_ME][SAP_QUEUE_IDX_NOTIF]; q_sz = mei->shared_mem.q_size[SAP_DIRECTION_HOST_TO_ME][SAP_QUEUE_IDX_NOTIF]; - ret = iwl_mei_write_cyclic_buf(q_head, notif_q, q_head, hdr, q_sz); + ret = iwl_mei_write_cyclic_buf(cldev, notif_q, q_head, hdr, q_sz); if (ret < 0) return ret; From d4157cd3aeab49e994826733aba628f45c7fcf0a Mon Sep 17 00:00:00 2001 From: Chelsy Ratnawat Date: Thu, 9 Jul 2026 12:43:15 -0700 Subject: [PATCH 0341/1433] wifi: rtlwifi: rtl8192d: remove dead SMPS rate mask code mimo_ps is initialized to IEEE80211_SMPS_OFF and never modified in rtl92d_update_hal_rate_table(). Therefore, the IEEE80211_SMPS_STATIC case is unreachable. Remove the unused mimo_ps variable and the dead branch. Signed-off-by: Chelsy Ratnawat Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260709194315.157030-1-chelsyratnawat2001@gmail.com --- .../realtek/rtlwifi/rtl8192d/hw_common.c | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/drivers/net/wireless/realtek/rtlwifi/rtl8192d/hw_common.c b/drivers/net/wireless/realtek/rtlwifi/rtl8192d/hw_common.c index 97e0d9c01e0a..cfefbe86380f 100644 --- a/drivers/net/wireless/realtek/rtlwifi/rtl8192d/hw_common.c +++ b/drivers/net/wireless/realtek/rtlwifi/rtl8192d/hw_common.c @@ -775,7 +775,6 @@ static void rtl92d_update_hal_rate_table(struct ieee80211_hw *hw, struct rtl_priv *rtlpriv = rtl_priv(hw); struct rtl_phy *rtlphy = &rtlpriv->phy; enum wireless_mode wirelessmode; - u8 mimo_ps = IEEE80211_SMPS_OFF; u8 curtxbw_40mhz = mac->bw_40; u8 nmode = mac->ht_enable; u8 curshortgi_40mhz; @@ -784,6 +783,7 @@ static void rtl92d_update_hal_rate_table(struct ieee80211_hw *hw, u8 ratr_index = 0; u16 shortgi_rate; u32 ratr_value; + u32 ratr_mask; curshortgi_40mhz = !!(sta->deflink.ht_cap.cap & IEEE80211_HT_CAP_SGI_40); curshortgi_20mhz = !!(sta->deflink.ht_cap.cap & IEEE80211_HT_CAP_SGI_20); @@ -811,20 +811,15 @@ static void rtl92d_update_hal_rate_table(struct ieee80211_hw *hw, case WIRELESS_MODE_N_24G: case WIRELESS_MODE_N_5G: nmode = 1; - if (mimo_ps == IEEE80211_SMPS_STATIC) { - ratr_value &= 0x0007F005; + + if (get_rf_type(rtlphy) == RF_1T2R || + get_rf_type(rtlphy) == RF_1T1R) { + ratr_mask = 0x000ff005; } else { - u32 ratr_mask; - - if (get_rf_type(rtlphy) == RF_1T2R || - get_rf_type(rtlphy) == RF_1T1R) { - ratr_mask = 0x000ff005; - } else { - ratr_mask = 0x0f0ff005; - } - - ratr_value &= ratr_mask; + ratr_mask = 0x0f0ff005; } + + ratr_value &= ratr_mask; break; default: if (rtlphy->rf_type == RF_1T2R) From af7f59e8e8763d4120f00f17683d3cf93801742a Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Sun, 12 Jul 2026 11:04:56 +0800 Subject: [PATCH 0342/1433] wifi: rtw89: coex: Add Wi-Fi role info version 10 Because the new generation Bluetooth will able to work on 5/6GHz band, it will suffer 5/6GHz Wi-Fi, the mechanism need to cover more scenario with different Wi-Fi/Bluetooth combination. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260712030506.43438-2-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 2493 +++++++---------- drivers/net/wireless/realtek/rtw89/coex.h | 34 + drivers/net/wireless/realtek/rtw89/core.h | 153 +- drivers/net/wireless/realtek/rtw89/fw.c | 384 ++- drivers/net/wireless/realtek/rtw89/fw.h | 50 +- drivers/net/wireless/realtek/rtw89/rtw8851b.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8852a.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8852b.c | 1 + .../net/wireless/realtek/rtw89/rtw8852bt.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8852c.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8922a.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8922d.c | 1 + 12 files changed, 1412 insertions(+), 1709 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 989344e93ab7..c02de0b4f7bf 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -436,11 +436,6 @@ union rtw89_fbtc_set_mon_reg { struct rtw89_btc_btf_set_mon_reg_v7 v7; } __packed; -struct _wl_rinfo_now { - u8 link_mode; - u32 dbcc_2g_phy: 2; -}; - enum btc_btf_set_cx_policy { CXPOLICY_TDMA = 0x0, CXPOLICY_SLOT = 0x1, @@ -570,9 +565,21 @@ enum btc_cx_poicy_type { /* TDMA off + pri: WL > BT, Block-BT*/ BTC_CXP_OFF_WL2 = (BTC_CXP_OFF << 8) | 12, - /* TDMA off+Bcn-Protect + pri: WL_Hi-Tx > BT_Hi_Rx, BT_Hi > WL > BT_Lo*/ + /* TDMA off+Bcn-Protect + pri: BT_Hi > WL > BT_Lo */ BTC_CXP_OFFB_BWB0 = (BTC_CXP_OFFB << 8) | 0, + /* TDMA off+Bcn-Protect + pri: BT_Hi_Tx = WL_Hi-Tx > BT_Hi_Rx, BT_Hi > WL > BT_Lo */ + BTC_CXP_OFFB_BWB1 = (BTC_CXP_OFFB << 8) | 1, + + /* TDMA off+Bcn-Protect + pri: WL_Hi-Tx > BT, BT_Hi > other-WL > BT_Lo */ + BTC_CXP_OFFB_BWB2 = (BTC_CXP_OFFB << 8) | 2, + + /* TDMA off+Bcn-Protect + pri: WL_Hi-Tx = BT*/ + BTC_CXP_OFFB_BWB3 = (BTC_CXP_OFFB << 8) | 3, + + /* TDMA off+Bcn-Protect + pri: BT > WL*/ + BTC_CXP_OFFB_BWB4 = (BTC_CXP_OFFB << 8) | 4, + /* TDMA off + Ext-Ctrl + pri: default */ BTC_CXP_OFFE_DEF = (BTC_CXP_OFFE << 8) | 0, @@ -752,19 +759,31 @@ enum btc_w2b_scoreboard { BTC_WSCB_ALL = GENMASK(23, 0), }; -enum btc_wl_link_mode { +enum btc_wl_link_mode_v0 { + BTC_WLINK_V0_NOLINK = 0x0, + BTC_WLINK_V0_2G_STA, + BTC_WLINK_V0_2G_AP, + BTC_WLINK_V0_2G_GO, + BTC_WLINK_V0_2G_GC, + BTC_WLINK_V0_2G_SCC, + BTC_WLINK_V0_2G_MCC, + BTC_WLINK_V0_25G_MCC, + BTC_WLINK_V0_25G_DBCC, + BTC_WLINK_V0_5G, + BTC_WLINK_V0_2G_NAN, + BTC_WLINK_V0_OTHER, + BTC_WLINK_V0_MAX +}; + +enum btc_wl_link_mode_v1 { BTC_WLINK_NOLINK = 0x0, - BTC_WLINK_2G_STA, - BTC_WLINK_2G_AP, - BTC_WLINK_2G_GO, - BTC_WLINK_2G_GC, - BTC_WLINK_2G_SCC, - BTC_WLINK_2G_MCC, - BTC_WLINK_25G_MCC, - BTC_WLINK_25G_DBCC, - BTC_WLINK_5G, - BTC_WLINK_2G_NAN, - BTC_WLINK_OTHER, + BTC_WLINK_STA, + BTC_WLINK_AP, + BTC_WLINK_GO, + BTC_WLINK_GC, + BTC_WLINK_SCC, + BTC_WLINK_SB_MCC, + BTC_WLINK_DB_MCC, /* 2+0/0+2 Dual-RF-band MCC */ BTC_WLINK_MAX }; @@ -774,16 +793,13 @@ static const char *id_to_linkmode(u8 id) { switch (id) { CASE_BTC_WL_LINK_MODE(NOLINK); - CASE_BTC_WL_LINK_MODE(2G_STA); - CASE_BTC_WL_LINK_MODE(2G_AP); - CASE_BTC_WL_LINK_MODE(2G_GO); - CASE_BTC_WL_LINK_MODE(2G_GC); - CASE_BTC_WL_LINK_MODE(2G_SCC); - CASE_BTC_WL_LINK_MODE(2G_MCC); - CASE_BTC_WL_LINK_MODE(25G_MCC); - CASE_BTC_WL_LINK_MODE(25G_DBCC); - CASE_BTC_WL_LINK_MODE(5G); - CASE_BTC_WL_LINK_MODE(OTHER); + CASE_BTC_WL_LINK_MODE(STA); + CASE_BTC_WL_LINK_MODE(AP); + CASE_BTC_WL_LINK_MODE(GO); + CASE_BTC_WL_LINK_MODE(GC); + CASE_BTC_WL_LINK_MODE(SCC); + CASE_BTC_WL_LINK_MODE(SB_MCC); + CASE_BTC_WL_LINK_MODE(DB_MCC); default: return "unknown"; } @@ -969,7 +985,7 @@ static void _reset_btc_var(struct rtw89_dev *rtwdev, u8 type) struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; struct rtw89_btc_wl_link_info *wl_linfo; - u8 i; + u8 i, j; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s\n", __func__); @@ -990,12 +1006,11 @@ static void _reset_btc_var(struct rtw89_dev *rtwdev, u8 type) if (type & BTC_RESET_DM) { memset(&btc->dm, 0, sizeof(btc->dm)); memset(bt_linfo->rssi_state, 0, sizeof(bt_linfo->rssi_state)); - for (i = 0; i < RTW89_PORT_NUM; i++) { - if (btc->ver->fwlrole == 8) - wl_linfo = &wl->rlink_info[i][0]; - else - wl_linfo = &wl->link_info[i]; - memset(wl_linfo->rssi_state, 0, sizeof(wl_linfo->rssi_state)); + for (j = RTW89_MAC_0; j <= RTW89_MAC_1; j++) { + for (i = 0; i < RTW89_BE_BTC_WL_MAX_ROLE_NUMBER; i++) { + wl_linfo = &wl->rlink_info[i][j]; + memset(wl_linfo->rssi_state, 0, sizeof(wl_linfo->rssi_state)); + } } /* set the slot_now table to original */ @@ -2847,6 +2862,8 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index) rtw89_fw_h2c_cxdrv_role_v7(rtwdev, index); else if (ver->fwlrole == 8) rtw89_fw_h2c_cxdrv_role_v8(rtwdev, index); + else if (ver->fwlrole == 10) + rtw89_fw_h2c_cxdrv_role_v10(rtwdev, index); break; case CXDRVINFO_CTRL: if (ver->drvinfo_ver != 0) @@ -3396,29 +3413,17 @@ static void _set_rf_trx_para(struct rtw89_dev *rtwdev) const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_wl_info *wl = &btc->cx.wl; + struct rtw89_btc_wl_role_info *r = &wl->role_info; struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_bt_link_info *b = &bt->link_info; struct rtw89_btc_wl_smap *wl_smap = &wl->status.map; struct rtw89_btc_rf_trx_para_v9 para; - u8 lv, link_mode = 0, i, dbcc_2g_phy = 0; - u8 ul_para_num, dl_para_num; - u8 rf_band = RTW89_BAND_2G; - u8 bid = BTC_BT_1ST; - u32 wl_stb_chg = 0; - - if (ver->fwlrole == 0) { - link_mode = wl->role_info.link_mode; - for (i = 0; i < RTW89_PHY_NUM; i++) { - if (wl->dbcc_info.real_band[i] == RTW89_BAND_2G) - dbcc_2g_phy = i; - } - } else if (ver->fwlrole == 1) { - link_mode = wl->role_info_v1.link_mode; - dbcc_2g_phy = wl->role_info_v1.dbcc_2g_phy; - } else if (ver->fwlrole == 2) { - link_mode = wl->role_info_v2.link_mode; - dbcc_2g_phy = wl->role_info_v2.dbcc_2g_phy; - } + u8 bmode = dm->tdd_bind.wl_link_mode; + u8 rf_band = dm->tdd_bind.rf_band; + u8 i, ul_para_num, dl_para_num; + u8 mode = BTC_WLINK_NOLINK; + u8 bid = BTC_BT_1ST, lv; + u32 wl_stb_chg; if (ver->fcxtrx == 9 && chip->rf_para_ulink_v9) { ul_para_num = chip->rf_para_ulink_num_v9; @@ -3434,6 +3439,19 @@ static void _set_rf_trx_para(struct rtw89_dev *rtwdev) goto next; } + if (bmode == BTC_WLINK_NOLINK) { + return; + } else if (btc->ver->fwlrole == 10) { + if (rf_band == RTW89_BAND_5G) { + mode = BTC_WLINK_V0_5G; + } else if (rf_band == RTW89_BAND_2G && + bmode == BTC_WLINK_STA) { + mode = BTC_WLINK_V0_2G_STA; + } + } else { + mode = r->link_mode_v0; + } + /* decide trx_para_level */ if (btc->ant_type == BTC_ANT_SHARED) { /* fix LNA2 + TIA gain not change by GNT_BT */ @@ -3446,7 +3464,7 @@ static void _set_rf_trx_para(struct rtw89_dev *rtwdev) dm->trx_para_level = 5; /* modify trx_para if WK 2.4G-STA-DL + bt link */ if (b->link_cnt.now != 0 && - link_mode == BTC_WLINK_2G_STA && + mode == BTC_WLINK_V0_2G_STA && wl->status.map.traffic_dir & BIT(RTW89_TFC_UL)) { /* uplink */ if (wl->rssi_level == 4 && bt->rssi_level > 2) dm->trx_para_level = 6; @@ -3504,10 +3522,8 @@ static void _set_rf_trx_para(struct rtw89_dev *rtwdev) next: if (!bt->enable.now || dm->wl_only || wl_smap->rf_off || - wl_smap->lps == BTC_LPS_RF_OFF || - link_mode == BTC_WLINK_5G || - link_mode == BTC_WLINK_NOLINK || - (rtwdev->dbcc_en && dbcc_2g_phy != RTW89_PHY_1)) + wl_smap->lps == BTC_LPS_RF_OFF || mode == BTC_WLINK_V0_5G || + (rtwdev->dbcc_en && r->dbcc_2g_phy != RTW89_PHY_1)) wl_stb_chg = 0; else wl_stb_chg = 1; @@ -3544,282 +3560,372 @@ static void _update_btc_state_map(struct rtw89_dev *rtwdev) } } -static void _set_bt_afh_info_v0(struct rtw89_dev *rtwdev) +static u8 _get_wl_guard_bw(struct rtw89_dev *rtwdev, u8 index) { - const struct rtw89_chip_info *chip = rtwdev->chip; - struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt0; - struct rtw89_btc_bt_link_info *b = &bt->link_info; - struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; - struct rtw89_btc_wl_role_info_v1 *wl_rinfo_v1 = &wl->role_info_v1; - struct rtw89_btc_wl_role_info_v2 *wl_rinfo_v2 = &wl->role_info_v2; - struct rtw89_btc_wl_role_info_v7 *wl_rinfo_v7 = &wl->role_info_v7; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo_v8 = &wl->role_info_v8; - struct rtw89_btc_wl_active_role *r; - struct rtw89_btc_wl_active_role_v1 *r1; - struct rtw89_btc_wl_active_role_v2 *r2; - struct rtw89_btc_wl_active_role_v7 *r7; - struct rtw89_btc_wl_rlink *rlink; - u8 en = 0, i, ch = 0, bw = 0; - u8 mode, connect_cnt; + struct rtw89_btc_dm *dm = &rtwdev->btc.dm; + u8 guard_ch = rtwdev->chip->afh_guard_ch; + u8 bw = 0; - if (btc->manual_ctrl || wl->status.map.scan) - return; - - if (ver->fwlrole == 0) { - mode = wl_rinfo->link_mode; - connect_cnt = wl_rinfo->connect_cnt; - } else if (ver->fwlrole == 1) { - mode = wl_rinfo_v1->link_mode; - connect_cnt = wl_rinfo_v1->connect_cnt; - } else if (ver->fwlrole == 2) { - mode = wl_rinfo_v2->link_mode; - connect_cnt = wl_rinfo_v2->connect_cnt; - } else if (ver->fwlrole == 7) { - mode = wl_rinfo_v7->link_mode; - connect_cnt = wl_rinfo_v7->connect_cnt; - } else if (ver->fwlrole == 8) { - mode = wl_rinfo_v8->link_mode; - connect_cnt = wl_rinfo_v8->connect_cnt; - } else { - return; - } - - if (wl->status.map.rf_off || bt->whql_test || - mode == BTC_WLINK_NOLINK || mode == BTC_WLINK_5G || - connect_cnt > BTC_TDMA_WLROLE_MAX) { - en = false; - } else if (mode == BTC_WLINK_2G_MCC || mode == BTC_WLINK_2G_SCC) { - en = true; - /* get p2p channel */ - for (i = 0; i < RTW89_PORT_NUM; i++) { - r = &wl_rinfo->active_role[i]; - r1 = &wl_rinfo_v1->active_role_v1[i]; - r2 = &wl_rinfo_v2->active_role_v2[i]; - r7 = &wl_rinfo_v7->active_role[i]; - rlink = &wl_rinfo_v8->rlink[i][0]; - - if (ver->fwlrole == 0 && - (r->role == RTW89_WIFI_ROLE_P2P_GO || - r->role == RTW89_WIFI_ROLE_P2P_CLIENT)) { - ch = r->ch; - bw = r->bw; - break; - } else if (ver->fwlrole == 1 && - (r1->role == RTW89_WIFI_ROLE_P2P_GO || - r1->role == RTW89_WIFI_ROLE_P2P_CLIENT)) { - ch = r1->ch; - bw = r1->bw; - break; - } else if (ver->fwlrole == 2 && - (r2->role == RTW89_WIFI_ROLE_P2P_GO || - r2->role == RTW89_WIFI_ROLE_P2P_CLIENT)) { - ch = r2->ch; - bw = r2->bw; - break; - } else if (ver->fwlrole == 7 && - (r7->role == RTW89_WIFI_ROLE_P2P_GO || - r7->role == RTW89_WIFI_ROLE_P2P_CLIENT)) { - ch = r7->ch; - bw = r7->bw; - break; - } else if (ver->fwlrole == 8 && - (rlink->role == RTW89_WIFI_ROLE_P2P_GO || - rlink->role == RTW89_WIFI_ROLE_P2P_CLIENT)) { - ch = rlink->ch; - bw = rlink->bw; - break; - } - } - } else { - en = true; - /* get 2g channel */ - for (i = 0; i < RTW89_PORT_NUM; i++) { - r = &wl_rinfo->active_role[i]; - r1 = &wl_rinfo_v1->active_role_v1[i]; - r2 = &wl_rinfo_v2->active_role_v2[i]; - r7 = &wl_rinfo_v7->active_role[i]; - rlink = &wl_rinfo_v8->rlink[i][0]; - - if (ver->fwlrole == 0 && - r->connected && r->band == RTW89_BAND_2G) { - ch = r->ch; - bw = r->bw; - break; - } else if (ver->fwlrole == 1 && - r1->connected && r1->band == RTW89_BAND_2G) { - ch = r1->ch; - bw = r1->bw; - break; - } else if (ver->fwlrole == 2 && - r2->connected && r2->band == RTW89_BAND_2G) { - ch = r2->ch; - bw = r2->bw; - break; - } else if (ver->fwlrole == 7 && - r7->connected && r7->band == RTW89_BAND_2G) { - ch = r7->ch; - bw = r7->bw; - break; - } else if (ver->fwlrole == 8 && - rlink->connected && rlink->rf_band == RTW89_BAND_2G) { - ch = rlink->ch; - bw = rlink->bw; - break; - } - } - } - - switch (bw) { + /* default AFH channel sapn = center-ch +- 6MHz */ + switch (index) { case RTW89_CHANNEL_WIDTH_20: - bw = 20 + chip->afh_guard_ch * 2; - break; - case RTW89_CHANNEL_WIDTH_40: - bw = 40 + chip->afh_guard_ch * 2; - break; - case RTW89_CHANNEL_WIDTH_5: - bw = 5 + chip->afh_guard_ch * 2; - break; - case RTW89_CHANNEL_WIDTH_10: - bw = 10 + chip->afh_guard_ch * 2; - break; - default: - bw = 0; - en = false; /* turn off AFH info if BW > 40 */ - break; - } - - if (wl->afh_info.en == en && - wl->afh_info.ch == ch && - wl->afh_info.bw == bw && - b->link_cnt.last == b->link_cnt.now) { - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): return because no change!\n", - __func__); - return; - } - - wl->afh_info.en = en; - wl->afh_info.ch = ch; - wl->afh_info.bw = bw; - - _send_fw_cmd(rtwdev, BTFC_SET, SET_BT_WL_CH_INFO, &wl->afh_info, 3); - - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): en=%d, ch=%d, bw=%d\n", - __func__, en, ch, bw); - wl->wcnt[BTC_WCNT_CH_UPDATE]++; -} - -static void _set_bt_afh_info_v1(struct rtw89_dev *rtwdev) -{ - const struct rtw89_chip_info *chip = rtwdev->chip; - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo = &wl->role_info_v8; - struct rtw89_btc_wl_afh_info *wl_afh = &wl->afh_info; - struct rtw89_btc_bt_info *bt = &btc->cx.bt0; - struct rtw89_btc_wl_rlink *rlink; - u8 en = 0, ch = 0, bw = 0, buf[3] = {}; - u8 i, j, link_mode; - - if (btc->manual_ctrl || wl->status.map.scan) - return; - - link_mode = wl_rinfo->link_mode; - - for (i = 0; i < btc->ver->max_role_num; i++) { - for (j = RTW89_MAC_0; j < RTW89_MAC_NUM; j++) { - if (wl->status.map.rf_off || bt->whql_test || - link_mode == BTC_WLINK_NOLINK || - link_mode == BTC_WLINK_5G) - break; - - rlink = &wl_rinfo->rlink[i][j]; - - /* Don't care no-connected/non-2G-band role */ - if (!rlink->connected || !rlink->active || - rlink->rf_band != RTW89_BAND_2G) - continue; - - en = 1; - ch = rlink->ch; - bw = rlink->bw; - - if (link_mode == BTC_WLINK_2G_MCC && - (rlink->role == RTW89_WIFI_ROLE_AP || - rlink->role == RTW89_WIFI_ROLE_P2P_GO || - rlink->role == RTW89_WIFI_ROLE_P2P_CLIENT)) { - /* for 2.4G MCC, take role = ap/go/gc */ - break; - } else if (link_mode != BTC_WLINK_2G_SCC || - rlink->bw == RTW89_CHANNEL_WIDTH_40) { - /* for 2.4G scc, take bw = 40M */ - break; - } - } - } - - /* default AFH channel sapn = center-ch +- 6MHz */ - switch (bw) { - case RTW89_CHANNEL_WIDTH_20: - if (btc->dm.freerun || btc->dm.fddt_train) + if (dm->freerun || dm->fddt_train) bw = 48; else - bw = 20 + chip->afh_guard_ch * 2; + bw = 20 + guard_ch * 2; break; case RTW89_CHANNEL_WIDTH_40: - if (btc->dm.freerun) - bw = 40 + chip->afh_guard_ch * 2; - else - bw = 40; + bw = 40 + guard_ch * 2; + break; + case RTW89_CHANNEL_WIDTH_80: + bw = 80 + guard_ch * 2; + break; + case RTW89_CHANNEL_WIDTH_160: + case RTW89_CHANNEL_WIDTH_80_80: + bw = 160 + guard_ch * 2; + break; + case RTW89_CHANNEL_WIDTH_320: + bw = 320 + guard_ch * 2; break; case RTW89_CHANNEL_WIDTH_5: - bw = 5 + chip->afh_guard_ch * 2; + bw = 5 + guard_ch * 2; break; case RTW89_CHANNEL_WIDTH_10: - bw = 10 + chip->afh_guard_ch * 2; + bw = 10 + guard_ch * 2; break; default: - en = false; /* turn off AFH info if invalid BW */ bw = 0; - ch = 0; break; } - if (!en || ch > 14 || ch == 0) { - en = false; - bw = 0; - ch = 0; + return bw; +} + +static void _set_wl_ch_map(struct rtw89_dev *rtwdev) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_wl_info *wl = &btc->cx.wl; + struct rtw89_btc_wl_afh_info *afh; + u16 ch_start, ch_end, i; + u8 b_group, b_pos; /* byte-group, bit-position */ + u8 hwb; + + if (wl->afh_info[RTW89_MAC_0][RTW89_BAND_2G].en && + wl->afh_info[RTW89_MAC_1][RTW89_BAND_2G].en) { + /* select wilder bw if 1+1 */ + if (wl->afh_info[RTW89_MAC_1][RTW89_BAND_2G].bw > + wl->afh_info[RTW89_MAC_0][RTW89_BAND_2G].bw) + hwb = RTW89_MAC_1; + else + hwb = RTW89_MAC_0; + } else if (wl->afh_info[RTW89_MAC_1][RTW89_BAND_2G].en) { + hwb = RTW89_MAC_1; + } else { + hwb = RTW89_MAC_0; } - if (wl_afh->en == en && - wl_afh->ch == ch && - wl_afh->bw == bw && - (!bt->enable.now || bt->enable.last)) + afh = &wl->afh_info[hwb][RTW89_BAND_2G]; + + /* focus on WL 2.4GHz link */ + if (!afh->en || afh->band != RTW89_BAND_2G) return; - wl_afh->en = buf[0]; - wl_afh->ch = buf[1]; - wl_afh->bw = buf[2]; + memset(wl->ch_map, 0x0, sizeof(wl->ch_map)); + memset(wl->ch_map_le, 0x0, sizeof(wl->ch_map_le)); - if (_send_fw_cmd(rtwdev, BTFC_SET, SET_BT_WL_CH_INFO, &wl->afh_info, 3)) { - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): en=%d, ch=%d, bw=%d\n", - __func__, en, ch, bw); + ch_start = afh->ch * 5 + 2407 - afh->bw / 2; + ch_end = ch_start + afh->bw; - wl->wcnt[BTC_WCNT_CH_UPDATE]++; + for (i = ch_start; i < ch_end; i++) { + if (i < 2402 || i > 2480) + continue; + b_group = (i - 2402) / 8; + b_pos = 7 - ((i - 2402) % 8); + wl->ch_map[b_group] |= BIT(b_pos); + + if (i % 2) /* BLE ch = even-freq (ex:2402,2404) */ + continue; + + b_group = (i - 2402) / 16; + b_pos = 7 - (((i - 2402) / 2) % 8); + wl->ch_map_le[b_group] |= BIT(b_pos); + } +} + +static u8 _get_w2b_fbd_ch(struct rtw89_dev *rtwdev, + u8 bt_fb_band, u32 wl_vco_freq, + u32 *ch_lo_bound, u32 *ch_up_bound) +{ + u32 ch_lo, ch_pup, ch_min, ch_max; + u8 out_of_range = 0; + s32 val_lo, val_up; + u32 vco_lo_in_KHz; /* VCO pulling lower bound */ + u32 vco_up_in_KHz; /* VCO pulling Upper bound */ + u32 freq_start; + + vco_lo_in_KHz = wl_vco_freq - BTC_VCO_GUARD_W2B * 1000; /* KHz */ + vco_up_in_KHz = wl_vco_freq + BTC_VCO_GUARD_W2B * 1000; + + switch (bt_fb_band) { + default: + case BTC_BT_B2G: /* BT 2GHz forbiddened Channel */ + val_lo = vco_lo_in_KHz * BTC_LO_2G_DNM / BTC_LO_2G_NMR; + val_up = vco_up_in_KHz * BTC_LO_2G_DNM / BTC_LO_2G_NMR; + freq_start = BTC_FREQ_B2G * 1000; /* KHz */ + ch_min = BTC_CH_B2G_MIN; + ch_max = BTC_CH_B2G_MAX; + break; + case BTC_BT_B5G: /* BT 5/6GHz forbiddened Channel */ + val_lo = vco_lo_in_KHz * BTC_LO_6G_DNM / BTC_LO_6G_NMR; + val_up = vco_up_in_KHz * BTC_LO_6G_DNM / BTC_LO_6G_NMR; + freq_start = BTC_FREQ_B6G * 1000; /* KHz */ + ch_min = BTC_CH_B5G_MIN; + ch_max = BTC_CH_B5G_MAX; + break; + } + + /* Search forbiddened Channel (un-process range)*/ + ch_lo = (val_lo - freq_start) / 1000; + if ((val_lo - freq_start) % 1000 != 0) /* Round-UP */ + ch_lo++; + + ch_pup = (val_up - freq_start) / 1000; /* Round-down */ + + /* Search forbiddened Channel */ + if (ch_lo > ch_max || ch_pup < ch_min) { /* out-of-range -> NA */ + out_of_range = 1; + *ch_lo_bound = 0; + *ch_up_bound = 0; + } + + if (ch_lo < ch_min) + *ch_lo_bound = ch_min; + else if (ch_lo <= ch_max) + *ch_lo_bound = ch_lo; + + if (ch_pup > ch_max) + *ch_up_bound = ch_max; + else if (ch_pup >= ch_min) + *ch_up_bound = ch_pup; + + if (bt_fb_band == BTC_BT_B5G) { /* BT ch is 8MHz-spacing for 5/6GHz */ + *ch_lo_bound = *ch_lo_bound / 8; /* low-bound -> Round-down */ + + if (*ch_up_bound % 8 != 0) /* Up-bound -> cover wider range */ + *ch_up_bound = *ch_up_bound / 8 + 1; + else + *ch_up_bound = *ch_up_bound / 8; + } + + return out_of_range; +} + +static void _set_wl_ch_info(struct rtw89_dev *rtwdev) +{ + static const u8 path_hwb[BTC_RF_NUM] = {RTW89_PHY_0, RTW89_PHY_1}; + struct rtw89_btc_dm *dm = &rtwdev->btc.dm; + struct rtw89_btc_cx *cx = &rtwdev->btc.cx; + struct rtw89_btc_wl_info *wl = &cx->wl; + u32 vco_freq, ch_lo = 0, ch_up = 0; + u16 rf_band, freq, f_diff; + u8 i, j, ch; + + if (!(rtwdev->chip->para_ver & BTC_FEAT_MULTI_PTA)) /* only new-PTA require setup */ + return; + + for (i = RTW89_PHY_0; i <= RTW89_PHY_1; i++) { + if (wl->mlo_info.wmode[i] == RTW89_MR_WMODE_NONE) { /* no-connect */ + dm->ost_info.rf_center_freq[i] = 0; + dm->ost_info.fbd_group_en[i][0] = 0; + dm->ost_info.fbd_group_en[i][1] = 0; + dm->ost_info.fbd_group_bound[i][0] = 0; + dm->ost_info.fbd_group_bound[i][1] = 0; + continue; + } + + ch = wl->rf_ch_info[i].center_ch; + switch (wl->rf_ch_info[i].band) { + case RTW89_BAND_2G: + default: + rf_band = 0; + if (ch == 14) + freq = BTC_FREQ_W2G_CH14; + else + freq = BTC_FREQ_W2G + (ch - 1) * 5; + vco_freq = freq * 1000 * BTC_LO_2G_NMR / BTC_LO_2G_DNM; + break; + case RTW89_BAND_5G: + rf_band = BIT(15); + freq = BTC_FREQ_W5G + (ch - 1) * 5; + vco_freq = freq * 1000 * BTC_LO_5G_NMR / BTC_LO_5G_DNM; + break; + case RTW89_BAND_6G: + rf_band = BIT(15); + freq = BTC_FREQ_W6G + (ch - 1) * 5; + vco_freq = freq * 1000 * BTC_LO_6G_NMR / BTC_LO_6G_DNM; + break; + } + + dm->ost_info.rf_center_freq[i] = rf_band + freq; + + for (j = BTC_BT_B2G; j <= BTC_BT_B5G; j++) { + /* no WL->BT forbidden channel */ + if (_get_w2b_fbd_ch(rtwdev, j, vco_freq, &ch_lo, &ch_up)) { + dm->ost_info.fbd_group_en[i][j] = j << 1; + dm->ost_info.fbd_group_bound[i][j] = 0; + continue; + } + + dm->ost_info.fbd_group_en[i][j] = (j << 1) + 0x1; + dm->ost_info.fbd_group_bound[i][j] = (ch_up << 8) + ch_lo; + } + } + + for (i = BTC_RF_S0; i <= BTC_RF_S1; i++) { + for (j = BTC_BT_1ST; j <= BTC_BT_EXT; j++) { + /* + * If WL & BT share same ANT/Path, set freq-diff > 2GHz, + * because TDD will always run in 5/6GHz band. + * Otherwise, the frq-diff thres is decided by the RF + * out-of-channel rejection capability. + */ + if (dm->ant_xmap[i][j]) + f_diff = 0x7ff; /* 2047MHz */ + else + f_diff = rtwdev->chip->fdd_iso_freq; + + dm->ost_info.freq_diff_thres[path_hwb[i]][j] = f_diff; + } } } static void _set_bt_afh_info(struct rtw89_dev *rtwdev) { - if (rtwdev->chip->chip_id == RTL8922A) - _set_bt_afh_info_v1(rtwdev); - else - _set_bt_afh_info_v0(rtwdev); + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_wl_info *wl = &cx->wl; + struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; + struct rtw89_btc_wl_rlink *rlink; + u8 h2c_func, hwb, bt_link_chg; + u8 i, j, sz, bw; + u8 buf[4]; + + if (btc->manual_ctrl || wl->status.map.rf_off || + (wl->status.val & btc_scanning_map.val)) + return; + + memset(wl->afh_info, 0, sizeof(wl->afh_info)); + + for (j = RTW89_MAC_0; j < RTW89_MAC_NUM; j++) { + /* skip HW-Band if no fdd/fdd bind */ + if ((!(btc->dm.tdd_bind.wl_hwb_sel & BIT(j))) && + (!(btc->dm.fdd_bind.wl_hwb_sel & BIT(j)))) + continue; + + for (i = 0; i < RTW89_BE_BTC_WL_MAX_ROLE_NUMBER; i++) { + rlink = &wl_rinfo->rlink[i][j]; + + /* Skip no-active case */ + if (!rlink->connected || !rlink->active || + rlink->rf_band >= RTW89_BAND_NUM) + continue; + + /* In addition to AP/P2P, skip if same-rf-band*/ + if (wl->afh_info[j][rlink->rf_band].en && + (rlink->role != RTW89_WIFI_ROLE_AP && + rlink->role != RTW89_WIFI_ROLE_P2P_GO && + rlink->role != RTW89_WIFI_ROLE_P2P_CLIENT)) + continue; + + bw = _get_wl_guard_bw(rtwdev, rlink->bw); + wl->afh_info[j][rlink->rf_band].en = 1; + wl->afh_info[j][rlink->rf_band].ch = rlink->ch; + wl->afh_info[j][rlink->rf_band].bw = bw; + wl->afh_info[j][rlink->rf_band].band = rlink->rf_band; + } + } + + for (j = RTW89_BAND_2G; j < RTW89_BAND_NUM; j++) { + /* skip non-2.4GHz if BT is 2.4GHz only*/ + if (!(rtwdev->chip->para_ver & BTC_FEAT_BT_6G)) { + if (j != RTW89_BAND_2G) + continue; + sz = 3; + } else { + sz = 4; + } + + if (wl->afh_info[RTW89_MAC_0][j].en && + wl->afh_info[RTW89_MAC_1][j].en) { + /* select wilder bw if 1+1 */ + if (wl->afh_info[RTW89_MAC_1][j].bw > + wl->afh_info[RTW89_MAC_0][j].bw) + hwb = RTW89_MAC_1; + else + hwb = RTW89_MAC_0; + } else if (wl->afh_info[RTW89_MAC_1][j].en) { + hwb = RTW89_MAC_1; + } else { + hwb = RTW89_MAC_0; + } + + if (j == RTW89_BAND_2G) { + if (cx->bt0.link_info.link_cnt.chg || + cx->bt1.link_info.link_cnt.chg) + bt_link_chg = 1; + else + bt_link_chg = 0; + } else { + if (cx->bt0.link_info_56g.link_cnt.chg || + cx->bt1.link_info_56g.link_cnt.chg) + bt_link_chg = 1; + else + bt_link_chg = 0; + } + + /* return if no-afh-info-change/no wl-bt link change */ + if (!memcmp(&wl->afh_info_last[hwb][j], + &wl->afh_info[hwb][j], + sizeof(struct rtw89_btc_wl_afh_info)) && + !bt_link_chg && + !wl->link_mode_chg) + continue; + + memcpy(&wl->afh_info_last[hwb][j], + &wl->afh_info[hwb][j], + sizeof(struct rtw89_btc_wl_afh_info)); + + buf[0] = wl->afh_info[hwb][j].en; + buf[1] = wl->afh_info[hwb][j].ch; + buf[2] = wl->afh_info[hwb][j].bw; + buf[3] = wl->afh_info[hwb][j].band; + + h2c_func = SET_BT_WL_CH_INFO; + if (cx->bt0.enable.now) + _send_fw_cmd(rtwdev, BTFC_SET, h2c_func, buf, sz); + + /* send mailbox to 2nd-BT if exist */ + if (cx->bt1.enable.now) { + h2c_func |= BT_H2C_FUNC_BT2ND; + _send_fw_cmd(rtwdev, BTFC_SET, h2c_func, buf, sz); + } + + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(): en=%d, ch=%d, bw=%d, rf_band=%d\n", + __func__, buf[0], buf[1], buf[2], buf[3]); + + wl->wcnt[BTC_WCNT_CH_UPDATE]++; + + /* ToDo: Remove if BT update new-define to support 6GHz */ + if (j == RTW89_BAND_2G) + break; + } + + /* for AFH map display in coex log */ + _set_wl_ch_map(rtwdev); + + /* set HWB center-ch/forbidden group to ost_info for CR setup in FW */ + _set_wl_ch_info(rtwdev); } static bool _check_freerun(struct rtw89_dev *rtwdev) @@ -3828,39 +3934,24 @@ static bool _check_freerun(struct rtw89_dev *rtwdev) struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; - struct rtw89_btc_wl_role_info_v1 *wl_rinfo_v1 = &wl->role_info_v1; - struct rtw89_btc_wl_role_info_v2 *wl_rinfo_v2 = &wl->role_info_v2; - struct rtw89_btc_wl_role_info_v7 *wl_rinfo_v7 = &wl->role_info_v7; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo_v8 = &wl->role_info_v8; struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; struct rtw89_btc_bt_hid_desc *hid = &bt_linfo->hid_desc; union rtw89_btc_module_info *md = &btc->mdinfo; const struct rtw89_btc_ver *ver = btc->ver; - u8 isolation, connect_cnt = 0; + u8 isolation; if (ver->fcxinit == 7) isolation = md->md_v7.ant.isolation; else isolation = md->md.ant.isolation; - if (ver->fwlrole == 0) - connect_cnt = wl_rinfo->connect_cnt; - else if (ver->fwlrole == 1) - connect_cnt = wl_rinfo_v1->connect_cnt; - else if (ver->fwlrole == 2) - connect_cnt = wl_rinfo_v2->connect_cnt; - else if (ver->fwlrole == 7) - connect_cnt = wl_rinfo_v7->connect_cnt; - else if (ver->fwlrole == 8) - connect_cnt = wl_rinfo_v8->connect_cnt; - if (btc->ant_type == BTC_ANT_SHARED) { btc->dm.trx_para_level = 0; return false; } /* The below is dedicated antenna case */ - if (connect_cnt > BTC_TDMA_WLROLE_MAX) { + if (wl_rinfo->connect_cnt > BTC_TDMA_WLROLE_MAX) { btc->dm.trx_para_level = 5; return true; } @@ -4097,11 +4188,26 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) case BTC_CXP_OFFB: /* TDMA off + beacon protect */ tdma_on = false; *t = t_def[CXTD_OFF_B2]; - s[CXST_OFF] = s_def[CXST_OFF]; + _slot_set_le(btc, CXST_OFF, s_def[CXST_OFF].dur, + s_def[CXST_OFF].cxtbl, s_def[CXST_OFF].cxtype); + _slot_set(btc, CXST_B2, 10, cxtbl[1], SLOT_ISO); + switch (policy_type) { case BTC_CXP_OFFB_BWB0: + _slot_set_tbl(btc, CXST_OFF, cxtbl[5]); + break; + case BTC_CXP_OFFB_BWB1: _slot_set_tbl(btc, CXST_OFF, cxtbl[8]); break; + case BTC_CXP_OFFB_BWB2: + _slot_set_tbl(btc, CXST_OFF, cxtbl[7]); + break; + case BTC_CXP_OFFB_BWB3: + _slot_set_tbl(btc, CXST_OFF, cxtbl[6]); + break; + case BTC_CXP_OFFB_BWB4: + _slot_set_tbl(btc, CXST_OFF, cxtbl[2]); + break; } break; case BTC_CXP_OFFE: /* TDMA off + beacon protect + Ext_control */ @@ -4182,7 +4288,7 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) case BTC_CXP_PFIX: /* PS-TDMA Fix-Slot */ tdma_on = true; *t = t_def[CXTD_PFIX]; - if (btc->cx.wl.role_info.role_map.role.ap) + if (btc->cx.wl.role_info.role_map & BIT(RTW89_WIFI_ROLE_AP)) _tdma_set_flctrl(btc, CXFLC_QOSNULL); switch (policy_type) { @@ -4461,12 +4567,23 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) *t = t_def[CXTD_OFF_B2]; _slot_set_le(btc, CXST_OFF, s_def[CXST_OFF].dur, s_def[CXST_OFF].cxtbl, s_def[CXST_OFF].cxtype); + _slot_set(btc, CXST_B2, 10, cxtbl[1], SLOT_ISO); switch (policy_type) { case BTC_CXP_OFFB_BWB0: + _slot_set_tbl(btc, CXST_OFF, cxtbl[5]); + break; + case BTC_CXP_OFFB_BWB1: _slot_set_tbl(btc, CXST_OFF, cxtbl[8]); break; - default: + case BTC_CXP_OFFB_BWB2: + _slot_set_tbl(btc, CXST_OFF, cxtbl[7]); + break; + case BTC_CXP_OFFB_BWB3: + _slot_set_tbl(btc, CXST_OFF, cxtbl[6]); + break; + case BTC_CXP_OFFB_BWB4: + _slot_set_tbl(btc, CXST_OFF, cxtbl[2]); break; } break; @@ -4841,35 +4958,17 @@ static void _set_ant_v0(struct rtw89_dev *rtwdev, bool force_exec, struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &btc->cx.wl; + struct rtw89_btc_wl_role_info *r = &btc->cx.wl.role_info; struct rtw89_btc_bt_info *bt = &cx->bt0; struct rtw89_btc_wl_dbcc_info *wl_dinfo = &wl->dbcc_info; - u8 gwl, gwl0, gwl1, gbt, plt_ctrl, i, dbcc_2g_phy, b2g = 0; - bool dbcc_chg = false, dbcc_en = false; + u8 gwl, gwl0, gwl1, gbt, plt_ctrl, i, b2g = 0; u32 ant_path_type; ant_path_type = ((phy_map << 8) + type); - if (btc->ver->fwlrole == 1) { - dbcc_chg = wl->role_info_v1.dbcc_chg; - dbcc_en = wl->role_info_v1.dbcc_en; - dbcc_2g_phy = wl->role_info_v1.dbcc_2g_phy; - } else if (btc->ver->fwlrole == 2) { - dbcc_chg = wl->role_info_v2.dbcc_chg; - dbcc_en = wl->role_info_v2.dbcc_en; - dbcc_2g_phy = wl->role_info_v2.dbcc_2g_phy; - } else if (btc->ver->fwlrole == 7) { - dbcc_chg = wl->role_info_v7.dbcc_chg; - dbcc_en = wl->role_info_v7.dbcc_en; - dbcc_2g_phy = wl->role_info_v7.dbcc_2g_phy; - } else if (btc->ver->fwlrole == 8) { - dbcc_chg = wl->role_info_v8.dbcc_chg; - dbcc_en = wl->role_info_v8.dbcc_en; - dbcc_2g_phy = wl->role_info_v8.dbcc_2g_phy; - } - if (btc->dm.run_reason == BTC_RSN_NTFY_POWEROFF || btc->dm.run_reason == BTC_RSN_NTFY_RADIO_STATE || - btc->dm.run_reason == BTC_RSN_CMD_SET_COEX || dbcc_chg) + btc->dm.run_reason == BTC_RSN_CMD_SET_COEX || r->dbcc_chg) force_exec = FC_EXEC; if (!force_exec && ant_path_type == dm->set_ant_path) { @@ -4967,12 +5066,12 @@ static void _set_ant_v0(struct rtw89_dev *rtwdev, bool force_exec, default: gbt = BTC_GNT_HW; if ((rtwdev->chip->para_ver & BTC_FEAT_MULTI_PTA) || - !dbcc_en) { + !r->dbcc_en) { gwl = BTC_GNT_HW; _set_gnt(rtwdev, BTC_PHY_ALL, gwl, gbt); } else { /* for DBCC Only-1-PTA */ - if (dbcc_2g_phy == RTW89_PHY_0) { + if (r->dbcc_2g_phy == RTW89_PHY_0) { gwl0 = BTC_GNT_HW; gwl1 = BTC_GNT_SW_HI; } else { @@ -4994,7 +5093,7 @@ static void _set_ant_v1(struct rtw89_dev *rtwdev, bool force_exec, struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_bt_info *bt = &btc->cx.bt0; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo = &wl->role_info_v8; + struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; u32 ant_path_type = rtw89_get_antpath_type(phy_map, type); struct rtw89_btc_wl_dbcc_info *wl_dinfo = &wl->dbcc_info; struct rtw89_btc_dm *dm = &btc->dm; @@ -5005,7 +5104,7 @@ static void _set_ant_v1(struct rtw89_dev *rtwdev, bool force_exec, btc->dm.run_reason == BTC_RSN_CMD_SET_COEX || wl_rinfo->dbcc_chg) force_exec = FC_EXEC; - if (wl_rinfo->link_mode != BTC_WLINK_25G_MCC && + if (wl_rinfo->link_mode != BTC_WLINK_DB_MCC && btc->dm.wl_btg_rx == 2) force_exec = FC_EXEC; @@ -5111,7 +5210,7 @@ static void _set_ant_v1(struct rtw89_dev *rtwdev, bool force_exec, static void _set_ant(struct rtw89_dev *rtwdev, bool force_exec, u8 phy_map, u8 type) { - if (rtwdev->chip->chip_id == RTL8922A) + if (rtwdev->chip->chip_id >= RTL8922A) _set_ant_v1(rtwdev, force_exec, phy_map, type); else _set_ant_v0(rtwdev, force_exec, phy_map, type); @@ -5134,29 +5233,24 @@ static void _action_wl_init(struct rtw89_dev *rtwdev) static void _action_wl_off(struct rtw89_dev *rtwdev, u8 mode) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_wl_info *wl = &cx->wl; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): !!\n", __func__); - if (wl->status.map.rf_off || btc->dm.bt_only) { + if (wl->status.map.rf_off || btc->dm.bt_only) _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_WOFF); - } else if (wl->status.map.lps == BTC_LPS_RF_ON) { - if (mode == BTC_WLINK_5G) - _set_ant(rtwdev, FC_EXEC, BTC_PHY_ALL, BTC_ANT_W5G); - else - _set_ant(rtwdev, FC_EXEC, BTC_PHY_ALL, BTC_ANT_W2G); - } + else if (wl->status.map.lps == BTC_LPS_RF_ON || + cx->bt0.whql_test || cx->bt1.whql_test) + _set_ant(rtwdev, FC_EXEC, BTC_PHY_ALL, BTC_ANT_PTA); - if (mode == BTC_WLINK_5G) { + if (dm->out_of_band) _set_policy(rtwdev, BTC_CXP_OFF_EQ0, BTC_ACT_WL_OFF); - } else if (wl->status.map.lps == BTC_LPS_RF_ON) { - if (btc->cx.bt0.link_info.a2dp_desc.active) - _set_policy(rtwdev, BTC_CXP_OFF_BT, BTC_ACT_WL_OFF); - else - _set_policy(rtwdev, BTC_CXP_OFF_BWB1, BTC_ACT_WL_OFF); - } else { + else if (cx->bt0.whql_test || cx->bt1.whql_test) + _set_policy(rtwdev, BTC_CXP_OFFB_BWB4, BTC_ACT_WL_OFF); + else _set_policy(rtwdev, BTC_CXP_OFF_BT, BTC_ACT_WL_OFF); - } } static void _action_freerun(struct rtw89_dev *rtwdev) @@ -5563,48 +5657,29 @@ static void _action_wl_rfk(struct rtw89_dev *rtwdev) static void _set_btg_ctrl(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_role_info_v1 *wl_rinfo_v1 = &wl->role_info_v1; - struct rtw89_btc_wl_role_info_v2 *wl_rinfo_v2 = &wl->role_info_v2; - struct rtw89_btc_wl_role_info_v7 *wl_rinfo_v7 = &wl->role_info_v7; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo_v8 = &wl->role_info_v8; - struct rtw89_btc_fbtc_outsrc_set_info *o_info = &btc->dm.ost_info; - struct rtw89_btc_wl_role_info *wl_rinfo_v0 = &wl->role_info; - const struct rtw89_chip_info *chip = rtwdev->chip; - const struct rtw89_btc_ver *ver = btc->ver; - struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_dm *dm = &btc->dm; - struct _wl_rinfo_now wl_rinfo; + struct rtw89_btc_fbtc_outsrc_set_info *o_info = &dm->ost_info; + struct rtw89_btc_wl_info *wl = &btc->cx.wl; + struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; + const struct rtw89_chip_info *chip = rtwdev->chip; + struct rtw89_btc_bt_info *bt = &btc->cx.bt0; u32 is_btg = BTC_BTGCTRL_DISABLE; if (btc->manual_ctrl) return; - if (ver->fwlrole == 0) - wl_rinfo.link_mode = wl_rinfo_v0->link_mode; - else if (ver->fwlrole == 1) - wl_rinfo.link_mode = wl_rinfo_v1->link_mode; - else if (ver->fwlrole == 2) - wl_rinfo.link_mode = wl_rinfo_v2->link_mode; - else if (ver->fwlrole == 7) - wl_rinfo.link_mode = wl_rinfo_v7->link_mode; - else if (ver->fwlrole == 8) - wl_rinfo.link_mode = wl_rinfo_v8->link_mode; - else - return; - /* notify halbb ignore GNT_BT or not for WL BB Rx-AGC control */ if (btc->ant_type == BTC_ANT_SHARED) { if (!(bt->run_patch_code && bt->enable.now)) is_btg = BTC_BTGCTRL_DISABLE; - else if (wl_rinfo.link_mode != BTC_WLINK_5G) + else if (wl_rinfo->link_mode_v0 != BTC_WLINK_V0_5G) is_btg = BTC_BTGCTRL_ENABLE; else is_btg = BTC_BTGCTRL_DISABLE; /* bb call ctrl_btg() in WL FW by slot */ - if (!ver->fcxosi && - wl_rinfo.link_mode == BTC_WLINK_25G_MCC) + if (!btc->ver->fcxosi && + wl_rinfo->link_mode_v0 == BTC_WLINK_V0_25G_MCC) is_btg = BTC_BTGCTRL_BB_GNT_FWCTRL; } @@ -5614,7 +5689,7 @@ static void _set_btg_ctrl(struct rtw89_dev *rtwdev) dm->wl_btg_rx = is_btg; /* skip setup if btg_ctrl set by wl fw */ - if (!ver->fcxosi && is_btg > BTC_BTGCTRL_ENABLE) + if (!btc->ver->fcxosi && is_btg > BTC_BTGCTRL_ENABLE) return; /* Below flow is for BTC_FEAT_NEW_BBAPI_FLOW = 1 */ @@ -5633,7 +5708,7 @@ static void _set_btg_ctrl(struct rtw89_dev *rtwdev) o_info->btg_rx[BTC_RF_S1] = is_btg; } - if (ver->fcxosi) + if (btc->ver->fcxosi) return; chip->ops->ctrl_btg_bt_rx(rtwdev, o_info->btg_rx[BTC_RF_S0], @@ -5651,40 +5726,20 @@ static void _set_wl_preagc_ctrl(struct rtw89_dev *rtwdev) struct rtw89_btc_fbtc_outsrc_set_info *o_info = &btc->dm.ost_info; struct rtw89_btc_bt_link_info *bt_linfo = &btc->cx.bt0.link_info; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_role_info_v2 *rinfo_v2 = &wl->role_info_v2; - struct rtw89_btc_wl_role_info_v7 *rinfo_v7 = &wl->role_info_v7; - struct rtw89_btc_wl_role_info_v8 *rinfo_v8 = &wl->role_info_v8; + struct rtw89_btc_wl_role_info *rinfo = &wl->role_info; const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_dm *dm = &btc->dm; - u8 is_preagc, val, link_mode, dbcc_2g_phy; - u8 role_ver = rtwdev->btc.ver->fwlrole; - bool dbcc_en; + u8 is_preagc, val; if (btc->manual_ctrl) return; - if (role_ver == 2) { - dbcc_en = rinfo_v2->dbcc_en; - link_mode = rinfo_v2->link_mode; - dbcc_2g_phy = rinfo_v2->dbcc_2g_phy; - } else if (role_ver == 7) { - dbcc_en = rinfo_v7->dbcc_en; - link_mode = rinfo_v7->link_mode; - dbcc_2g_phy = rinfo_v7->dbcc_2g_phy; - } else if (role_ver == 8) { - dbcc_en = rinfo_v8->dbcc_en; - link_mode = rinfo_v8->link_mode; - dbcc_2g_phy = rinfo_v7->dbcc_2g_phy; - } else { - return; - } - if (!(bt->run_patch_code && bt->enable.now)) { is_preagc = BTC_PREAGC_DISABLE; - } else if (link_mode == BTC_WLINK_5G) { + } else if (dm->tdd_bind.rf_band == BIT(RTW89_BAND_5G)) { is_preagc = BTC_PREAGC_DISABLE; - } else if (link_mode == BTC_WLINK_NOLINK || + } else if (rinfo->link_mode == BTC_WLINK_NOLINK || btc->cx.bt0.link_info.link_cnt.now == 0) { is_preagc = BTC_PREAGC_DISABLE; } else if (dm->tdma_now.type != CXTDMA_OFF && @@ -5692,7 +5747,7 @@ static void _set_wl_preagc_ctrl(struct rtw89_dev *rtwdev) !bt_linfo->hid_desc.exist && dm->fddt_train == BTC_FDDT_DISABLE) { is_preagc = BTC_PREAGC_DISABLE; - } else if (dbcc_en && (dbcc_2g_phy != RTW89_PHY_1)) { + } else if (rinfo->dbcc_en && (rinfo->dbcc_2g_phy != RTW89_PHY_1)) { is_preagc = BTC_PREAGC_DISABLE; } else if (btc->ant_type == BTC_ANT_SHARED) { is_preagc = BTC_PREAGC_DISABLE; @@ -5700,7 +5755,7 @@ static void _set_wl_preagc_ctrl(struct rtw89_dev *rtwdev) is_preagc = BTC_PREAGC_ENABLE; } - if (!btc->ver->fcxosi && link_mode == BTC_WLINK_25G_MCC) + if (!btc->ver->fcxosi && rinfo->link_mode == BTC_WLINK_DB_MCC) is_preagc = BTC_PREAGC_BB_FWCTRL; if (dm->wl_pre_agc_rb != dm->wl_pre_agc && @@ -5771,10 +5826,7 @@ static void __rtw89_tx_time_iter(struct rtw89_vif_link *rtwvif_link, u16 enable = iter_data->enable; bool reenable = iter_data->reenable; - if (btc->ver->fwlrole == 8) - plink = &wl->rlink_info[port][0]; - else - plink = &wl->link_info[port]; + plink = &wl->rlink_info[port][rtwsta_link->link_id]; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): port = %d\n", __func__, port); @@ -5840,39 +5892,23 @@ static void _set_wl_tx_limit(struct rtw89_dev *rtwdev) struct rtw89_btc_bt_hfp_desc *hfp = &b->hfp_desc; struct rtw89_btc_bt_hid_desc *hid = &b->hid_desc; struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; - struct rtw89_btc_wl_role_info_v1 *wl_rinfo_v1 = &wl->role_info_v1; - struct rtw89_btc_wl_role_info_v2 *wl_rinfo_v2 = &wl->role_info_v2; - struct rtw89_btc_wl_role_info_v7 *wl_rinfo_v7 = &wl->role_info_v7; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo_v8 = &wl->role_info_v8; struct rtw89_txtime_data data = {.rtwdev = rtwdev}; - u8 mode, igno_bt, tx_retry; + bool reenable = false; + u8 igno_bt, tx_retry; u32 tx_time; u16 enable; - bool reenable = false; if (btc->manual_ctrl) return; - if (ver->fwlrole == 0) - mode = wl_rinfo->link_mode; - else if (ver->fwlrole == 1) - mode = wl_rinfo_v1->link_mode; - else if (ver->fwlrole == 2) - mode = wl_rinfo_v2->link_mode; - else if (ver->fwlrole == 7) - mode = wl_rinfo_v7->link_mode; - else if (ver->fwlrole == 8) - mode = wl_rinfo_v8->link_mode; - else - return; - if (ver->fcxctrl == 7) igno_bt = btc->ctrl.ctrl_v7.igno_bt; else igno_bt = btc->ctrl.ctrl.igno_bt; if (btc->dm.freerun || igno_bt || b->link_cnt.now == 0 || - mode == BTC_WLINK_5G || mode == BTC_WLINK_NOLINK) { + dm->tdd_bind.rf_band == BIT(RTW89_BAND_5G) || + wl_rinfo->link_mode == BTC_WLINK_NOLINK) { enable = 0; tx_time = BTC_MAX_TX_TIME_DEF; tx_retry = BTC_MAX_TX_RETRY_DEF; @@ -5915,31 +5951,12 @@ static void _set_wl_tx_limit(struct rtw89_dev *rtwdev) static void _set_bt_rx_agc(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; - struct rtw89_btc_wl_role_info_v1 *wl_rinfo_v1 = &wl->role_info_v1; - struct rtw89_btc_wl_role_info_v2 *wl_rinfo_v2 = &wl->role_info_v2; - struct rtw89_btc_wl_role_info_v7 *wl_rinfo_v7 = &wl->role_info_v7; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo_v8 = &wl->role_info_v8; struct rtw89_btc_bt_info *bt = &btc->cx.bt0; bool bt_hi_lna_rx = false; - u8 mode; - if (ver->fwlrole == 0) - mode = wl_rinfo->link_mode; - else if (ver->fwlrole == 1) - mode = wl_rinfo_v1->link_mode; - else if (ver->fwlrole == 2) - mode = wl_rinfo_v2->link_mode; - else if (ver->fwlrole == 7) - mode = wl_rinfo_v7->link_mode; - else if (ver->fwlrole == 8) - mode = wl_rinfo_v8->link_mode; - else - return; - - if (mode != BTC_WLINK_NOLINK && btc->dm.wl_btg_rx) + if (wl_rinfo->link_mode != BTC_WLINK_NOLINK && btc->dm.wl_btg_rx) bt_hi_lna_rx = true; if (bt_hi_lna_rx == bt->hi_lna_rx) @@ -5985,20 +6002,51 @@ static void _wl_req_mac(struct rtw89_dev *rtwdev, u8 mac) rtw89_write32_set(rtwdev, add, B_AX_WL_SRC); } +static void _update_zb_coex_tbl(struct rtw89_dev *rtwdev) +{ + struct rtw89_btc *btc = &rtwdev->btc; + const struct rtw89_btc_ver *ver = btc->ver; + u32 zb_tbl0 = 0xda5a5a5a, zb_tbl1 = 0xda5a5a5a; + u8 link_mode_chg = btc->cx.wl.link_mode_chg; + u8 mode = btc->cx.wl.role_info.link_mode; + u8 wa_type; + + if (btc->dm.run_reason != BTC_RSN_NTFY_INIT && !link_mode_chg) + return; + + if (ver->fcxinit == 7) + wa_type = btc->mdinfo.md_v7.wa_type; + else + wa_type = btc->mdinfo.md.wa_type; + + if (!(wa_type & BTC_WA_HFP_ZB)) + return; + + if (btc->dm.tdd_bind.rf_band == BIT(RTW89_BAND_5G) || + rtwdev->btc.dm.freerun) { + zb_tbl0 = 0xffffffff; + zb_tbl1 = 0xffffffff; + } else if (mode == BTC_WLINK_DB_MCC) { + zb_tbl0 = 0xffffffff; /* for E5G slot */ + zb_tbl1 = 0xda5a5a5a; /* for E2G slot */ + } + rtw89_write32(rtwdev, R_BTC_ZB_COEX_TBL_0, zb_tbl0); + rtw89_write32(rtwdev, R_BTC_ZB_COEX_TBL_1, zb_tbl1); +} + static void _action_common(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_role_info_v8 *rinfo_v8 = &wl->role_info_v8; + struct rtw89_btc_wl_role_info *rinfo = &wl->role_info; struct rtw89_btc_wl_smap *wl_smap = &wl->status.map; struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_dm *dm = &btc->dm; u32 bt_rom_code_id, bt_fw_ver; u8 i; - if (btc->ver->fwlrole == 8) - _wl_req_mac(rtwdev, rinfo_v8->pta_req_band); - + _wl_req_mac(rtwdev, rinfo->pta_req_band); + _update_zb_coex_tbl(rtwdev); _set_btg_ctrl(rtwdev); _set_wl_preagc_ctrl(rtwdev); _set_wl_tx_limit(rtwdev); @@ -6041,7 +6089,10 @@ static void _action_common(struct rtw89_dev *rtwdev) } dm->tdma_instant_excute = 0; dm->lps_ctrl_change = false; + wl->role_info.link_mode_chg = 0; wl->pta_reg_mac_chg = false; + dm->pre_agc_chg = false; + wl->dbcc_chg = 0; } static void _action_by_bt(struct rtw89_dev *rtwdev) @@ -6407,473 +6458,29 @@ void _update_dbcc_band(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx) btc->cx.wl.dbcc_info.op_band[phy_idx]; } -static void _update_wl_info(struct rtw89_dev *rtwdev) -{ - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_link_info *wl_linfo = wl->link_info; - struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; - struct rtw89_btc_wl_dbcc_info *wl_dinfo = &wl->dbcc_info; - u8 i, cnt_connect = 0, cnt_connecting = 0, cnt_active = 0; - u8 cnt_2g = 0, cnt_5g = 0, phy; - u32 wl_2g_ch[2] = {0}, wl_5g_ch[2] = {0}; - bool b2g = false, b5g = false, client_joined = false; - - memset(wl_rinfo, 0, sizeof(*wl_rinfo)); - - for (i = 0; i < RTW89_PORT_NUM; i++) { - /* check if role active? */ - if (!wl_linfo[i].active) - continue; - - cnt_active++; - wl_rinfo->active_role[cnt_active - 1].role = wl_linfo[i].role; - wl_rinfo->active_role[cnt_active - 1].pid = wl_linfo[i].pid; - wl_rinfo->active_role[cnt_active - 1].phy = wl_linfo[i].phy; - wl_rinfo->active_role[cnt_active - 1].band = wl_linfo[i].band; - wl_rinfo->active_role[cnt_active - 1].noa = (u8)wl_linfo[i].noa; - wl_rinfo->active_role[cnt_active - 1].connected = 0; - - wl->port_id[wl_linfo[i].role] = wl_linfo[i].pid; - - phy = wl_linfo[i].phy; - - /* check dbcc role */ - if (rtwdev->dbcc_en && phy < RTW89_PHY_NUM) { - wl_dinfo->role[phy] = wl_linfo[i].role; - wl_dinfo->op_band[phy] = wl_linfo[i].band; - _update_dbcc_band(rtwdev, phy); - _fw_set_drv_info(rtwdev, CXDRVINFO_DBCC); - } - - if (wl_linfo[i].connected == MLME_NO_LINK) { - continue; - } else if (wl_linfo[i].connected == MLME_LINKING) { - cnt_connecting++; - } else { - cnt_connect++; - if ((wl_linfo[i].role == RTW89_WIFI_ROLE_P2P_GO || - wl_linfo[i].role == RTW89_WIFI_ROLE_AP) && - wl_linfo[i].client_cnt > 1) - client_joined = true; - } - - wl_rinfo->role_map.val |= BIT(wl_linfo[i].role); - wl_rinfo->active_role[cnt_active - 1].ch = wl_linfo[i].ch; - wl_rinfo->active_role[cnt_active - 1].bw = wl_linfo[i].bw; - wl_rinfo->active_role[cnt_active - 1].connected = 1; - - /* only care 2 roles + BT coex */ - if (wl_linfo[i].band != RTW89_BAND_2G) { - if (cnt_5g <= ARRAY_SIZE(wl_5g_ch) - 1) - wl_5g_ch[cnt_5g] = wl_linfo[i].ch; - cnt_5g++; - b5g = true; - } else { - if (cnt_2g <= ARRAY_SIZE(wl_2g_ch) - 1) - wl_2g_ch[cnt_2g] = wl_linfo[i].ch; - cnt_2g++; - b2g = true; - } - } - - wl_rinfo->connect_cnt = cnt_connect; - - /* Be careful to change the following sequence!! */ - if (cnt_connect == 0) { - wl_rinfo->link_mode = BTC_WLINK_NOLINK; - wl_rinfo->role_map.role.none = 1; - } else if (!b2g && b5g) { - wl_rinfo->link_mode = BTC_WLINK_5G; - } else if (wl_rinfo->role_map.role.nan) { - wl_rinfo->link_mode = BTC_WLINK_2G_NAN; - } else if (cnt_connect > BTC_TDMA_WLROLE_MAX) { - wl_rinfo->link_mode = BTC_WLINK_OTHER; - } else if (b2g && b5g && cnt_connect == 2) { - if (rtwdev->dbcc_en) { - switch (wl_dinfo->role[RTW89_PHY_0]) { - case RTW89_WIFI_ROLE_STATION: - wl_rinfo->link_mode = BTC_WLINK_2G_STA; - break; - case RTW89_WIFI_ROLE_P2P_GO: - wl_rinfo->link_mode = BTC_WLINK_2G_GO; - break; - case RTW89_WIFI_ROLE_P2P_CLIENT: - wl_rinfo->link_mode = BTC_WLINK_2G_GC; - break; - case RTW89_WIFI_ROLE_AP: - wl_rinfo->link_mode = BTC_WLINK_2G_AP; - break; - default: - wl_rinfo->link_mode = BTC_WLINK_OTHER; - break; - } - } else { - wl_rinfo->link_mode = BTC_WLINK_25G_MCC; - } - } else if (!b5g && cnt_connect == 2) { - if (wl_rinfo->role_map.role.station && - (wl_rinfo->role_map.role.p2p_go || - wl_rinfo->role_map.role.p2p_gc || - wl_rinfo->role_map.role.ap)) { - if (wl_2g_ch[0] == wl_2g_ch[1]) - wl_rinfo->link_mode = BTC_WLINK_2G_SCC; - else - wl_rinfo->link_mode = BTC_WLINK_2G_MCC; - } else { - wl_rinfo->link_mode = BTC_WLINK_2G_MCC; - } - } else if (!b5g && cnt_connect == 1) { - if (wl_rinfo->role_map.role.station) - wl_rinfo->link_mode = BTC_WLINK_2G_STA; - else if (wl_rinfo->role_map.role.ap) - wl_rinfo->link_mode = BTC_WLINK_2G_AP; - else if (wl_rinfo->role_map.role.p2p_go) - wl_rinfo->link_mode = BTC_WLINK_2G_GO; - else if (wl_rinfo->role_map.role.p2p_gc) - wl_rinfo->link_mode = BTC_WLINK_2G_GC; - else - wl_rinfo->link_mode = BTC_WLINK_OTHER; - } - - /* if no client_joined, don't care P2P-GO/AP role */ - if (wl_rinfo->role_map.role.p2p_go || wl_rinfo->role_map.role.ap) { - if (!client_joined) { - if (wl_rinfo->link_mode == BTC_WLINK_2G_SCC || - wl_rinfo->link_mode == BTC_WLINK_2G_MCC) { - wl_rinfo->link_mode = BTC_WLINK_2G_STA; - wl_rinfo->connect_cnt = 1; - } else if (wl_rinfo->link_mode == BTC_WLINK_2G_GO || - wl_rinfo->link_mode == BTC_WLINK_2G_AP) { - wl_rinfo->link_mode = BTC_WLINK_NOLINK; - wl_rinfo->connect_cnt = 0; - } - } - } - - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], cnt_connect = %d, connecting = %d, link_mode = %d\n", - cnt_connect, cnt_connecting, wl_rinfo->link_mode); - - _fw_set_drv_info(rtwdev, CXDRVINFO_ROLE); -} - -static void _update_wl_info_v1(struct rtw89_dev *rtwdev) -{ - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_link_info *wl_linfo = wl->link_info; - struct rtw89_btc_wl_role_info_v1 *wl_rinfo = &wl->role_info_v1; - struct rtw89_btc_wl_dbcc_info *wl_dinfo = &wl->dbcc_info; - u8 cnt_connect = 0, cnt_connecting = 0, cnt_active = 0; - u8 cnt_2g = 0, cnt_5g = 0, phy; - u32 wl_2g_ch[2] = {}, wl_5g_ch[2] = {}; - bool b2g = false, b5g = false, client_joined = false; - u8 i; - - memset(wl_rinfo, 0, sizeof(*wl_rinfo)); - - for (i = 0; i < RTW89_PORT_NUM; i++) { - if (!wl_linfo[i].active) - continue; - - cnt_active++; - wl_rinfo->active_role_v1[cnt_active - 1].role = wl_linfo[i].role; - wl_rinfo->active_role_v1[cnt_active - 1].pid = wl_linfo[i].pid; - wl_rinfo->active_role_v1[cnt_active - 1].phy = wl_linfo[i].phy; - wl_rinfo->active_role_v1[cnt_active - 1].band = wl_linfo[i].band; - wl_rinfo->active_role_v1[cnt_active - 1].noa = (u8)wl_linfo[i].noa; - wl_rinfo->active_role_v1[cnt_active - 1].connected = 0; - - wl->port_id[wl_linfo[i].role] = wl_linfo[i].pid; - - phy = wl_linfo[i].phy; - - if (rtwdev->dbcc_en && phy < RTW89_PHY_NUM) { - wl_dinfo->role[phy] = wl_linfo[i].role; - wl_dinfo->op_band[phy] = wl_linfo[i].band; - _update_dbcc_band(rtwdev, phy); - _fw_set_drv_info(rtwdev, CXDRVINFO_DBCC); - } - - if (wl_linfo[i].connected == MLME_NO_LINK) { - continue; - } else if (wl_linfo[i].connected == MLME_LINKING) { - cnt_connecting++; - } else { - cnt_connect++; - if ((wl_linfo[i].role == RTW89_WIFI_ROLE_P2P_GO || - wl_linfo[i].role == RTW89_WIFI_ROLE_AP) && - wl_linfo[i].client_cnt > 1) - client_joined = true; - } - - wl_rinfo->role_map.val |= BIT(wl_linfo[i].role); - wl_rinfo->active_role_v1[cnt_active - 1].ch = wl_linfo[i].ch; - wl_rinfo->active_role_v1[cnt_active - 1].bw = wl_linfo[i].bw; - wl_rinfo->active_role_v1[cnt_active - 1].connected = 1; - - /* only care 2 roles + BT coex */ - if (wl_linfo[i].band != RTW89_BAND_2G) { - if (cnt_5g <= ARRAY_SIZE(wl_5g_ch) - 1) - wl_5g_ch[cnt_5g] = wl_linfo[i].ch; - cnt_5g++; - b5g = true; - } else { - if (cnt_2g <= ARRAY_SIZE(wl_2g_ch) - 1) - wl_2g_ch[cnt_2g] = wl_linfo[i].ch; - cnt_2g++; - b2g = true; - } - } - - wl_rinfo->connect_cnt = cnt_connect; - - /* Be careful to change the following sequence!! */ - if (cnt_connect == 0) { - wl_rinfo->link_mode = BTC_WLINK_NOLINK; - wl_rinfo->role_map.role.none = 1; - } else if (!b2g && b5g) { - wl_rinfo->link_mode = BTC_WLINK_5G; - } else if (wl_rinfo->role_map.role.nan) { - wl_rinfo->link_mode = BTC_WLINK_2G_NAN; - } else if (cnt_connect > BTC_TDMA_WLROLE_MAX) { - wl_rinfo->link_mode = BTC_WLINK_OTHER; - } else if (b2g && b5g && cnt_connect == 2) { - if (rtwdev->dbcc_en) { - switch (wl_dinfo->role[RTW89_PHY_0]) { - case RTW89_WIFI_ROLE_STATION: - wl_rinfo->link_mode = BTC_WLINK_2G_STA; - break; - case RTW89_WIFI_ROLE_P2P_GO: - wl_rinfo->link_mode = BTC_WLINK_2G_GO; - break; - case RTW89_WIFI_ROLE_P2P_CLIENT: - wl_rinfo->link_mode = BTC_WLINK_2G_GC; - break; - case RTW89_WIFI_ROLE_AP: - wl_rinfo->link_mode = BTC_WLINK_2G_AP; - break; - default: - wl_rinfo->link_mode = BTC_WLINK_OTHER; - break; - } - } else { - wl_rinfo->link_mode = BTC_WLINK_25G_MCC; - } - } else if (!b5g && cnt_connect == 2) { - if (wl_rinfo->role_map.role.station && - (wl_rinfo->role_map.role.p2p_go || - wl_rinfo->role_map.role.p2p_gc || - wl_rinfo->role_map.role.ap)) { - if (wl_2g_ch[0] == wl_2g_ch[1]) - wl_rinfo->link_mode = BTC_WLINK_2G_SCC; - else - wl_rinfo->link_mode = BTC_WLINK_2G_MCC; - } else { - wl_rinfo->link_mode = BTC_WLINK_2G_MCC; - } - } else if (!b5g && cnt_connect == 1) { - if (wl_rinfo->role_map.role.station) - wl_rinfo->link_mode = BTC_WLINK_2G_STA; - else if (wl_rinfo->role_map.role.ap) - wl_rinfo->link_mode = BTC_WLINK_2G_AP; - else if (wl_rinfo->role_map.role.p2p_go) - wl_rinfo->link_mode = BTC_WLINK_2G_GO; - else if (wl_rinfo->role_map.role.p2p_gc) - wl_rinfo->link_mode = BTC_WLINK_2G_GC; - else - wl_rinfo->link_mode = BTC_WLINK_OTHER; - } - - /* if no client_joined, don't care P2P-GO/AP role */ - if (wl_rinfo->role_map.role.p2p_go || wl_rinfo->role_map.role.ap) { - if (!client_joined) { - if (wl_rinfo->link_mode == BTC_WLINK_2G_SCC || - wl_rinfo->link_mode == BTC_WLINK_2G_MCC) { - wl_rinfo->link_mode = BTC_WLINK_2G_STA; - wl_rinfo->connect_cnt = 1; - } else if (wl_rinfo->link_mode == BTC_WLINK_2G_GO || - wl_rinfo->link_mode == BTC_WLINK_2G_AP) { - wl_rinfo->link_mode = BTC_WLINK_NOLINK; - wl_rinfo->connect_cnt = 0; - } - } - } - - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], cnt_connect = %d, connecting = %d, link_mode = %d\n", - cnt_connect, cnt_connecting, wl_rinfo->link_mode); - - _fw_set_drv_info(rtwdev, CXDRVINFO_ROLE); -} - -static void _update_wl_info_v2(struct rtw89_dev *rtwdev) -{ - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_link_info *wl_linfo = wl->link_info; - struct rtw89_btc_wl_role_info_v2 *wl_rinfo = &wl->role_info_v2; - struct rtw89_btc_wl_dbcc_info *wl_dinfo = &wl->dbcc_info; - u8 cnt_connect = 0, cnt_connecting = 0, cnt_active = 0; - u8 cnt_2g = 0, cnt_5g = 0, phy; - u32 wl_2g_ch[2] = {}, wl_5g_ch[2] = {}; - bool b2g = false, b5g = false, client_joined = false; - u8 i; - - memset(wl_rinfo, 0, sizeof(*wl_rinfo)); - - for (i = 0; i < RTW89_PORT_NUM; i++) { - if (!wl_linfo[i].active) - continue; - - cnt_active++; - wl_rinfo->active_role_v2[cnt_active - 1].role = wl_linfo[i].role; - wl_rinfo->active_role_v2[cnt_active - 1].pid = wl_linfo[i].pid; - wl_rinfo->active_role_v2[cnt_active - 1].phy = wl_linfo[i].phy; - wl_rinfo->active_role_v2[cnt_active - 1].band = wl_linfo[i].band; - wl_rinfo->active_role_v2[cnt_active - 1].noa = (u8)wl_linfo[i].noa; - wl_rinfo->active_role_v2[cnt_active - 1].connected = 0; - - wl->port_id[wl_linfo[i].role] = wl_linfo[i].pid; - - phy = wl_linfo[i].phy; - - if (rtwdev->dbcc_en && phy < RTW89_PHY_NUM) { - wl_dinfo->role[phy] = wl_linfo[i].role; - wl_dinfo->op_band[phy] = wl_linfo[i].band; - _update_dbcc_band(rtwdev, phy); - _fw_set_drv_info(rtwdev, CXDRVINFO_DBCC); - } - - if (wl_linfo[i].connected == MLME_NO_LINK) { - continue; - } else if (wl_linfo[i].connected == MLME_LINKING) { - cnt_connecting++; - } else { - cnt_connect++; - if ((wl_linfo[i].role == RTW89_WIFI_ROLE_P2P_GO || - wl_linfo[i].role == RTW89_WIFI_ROLE_AP) && - wl_linfo[i].client_cnt > 1) - client_joined = true; - } - - wl_rinfo->role_map.val |= BIT(wl_linfo[i].role); - wl_rinfo->active_role_v2[cnt_active - 1].ch = wl_linfo[i].ch; - wl_rinfo->active_role_v2[cnt_active - 1].bw = wl_linfo[i].bw; - wl_rinfo->active_role_v2[cnt_active - 1].connected = 1; - - /* only care 2 roles + BT coex */ - if (wl_linfo[i].band != RTW89_BAND_2G) { - if (cnt_5g <= ARRAY_SIZE(wl_5g_ch) - 1) - wl_5g_ch[cnt_5g] = wl_linfo[i].ch; - cnt_5g++; - b5g = true; - } else { - if (cnt_2g <= ARRAY_SIZE(wl_2g_ch) - 1) - wl_2g_ch[cnt_2g] = wl_linfo[i].ch; - cnt_2g++; - b2g = true; - } - } - - wl_rinfo->connect_cnt = cnt_connect; - - /* Be careful to change the following sequence!! */ - if (cnt_connect == 0) { - wl_rinfo->link_mode = BTC_WLINK_NOLINK; - wl_rinfo->role_map.role.none = 1; - } else if (!b2g && b5g) { - wl_rinfo->link_mode = BTC_WLINK_5G; - } else if (wl_rinfo->role_map.role.nan) { - wl_rinfo->link_mode = BTC_WLINK_2G_NAN; - } else if (cnt_connect > BTC_TDMA_WLROLE_MAX) { - wl_rinfo->link_mode = BTC_WLINK_OTHER; - } else if (b2g && b5g && cnt_connect == 2) { - if (rtwdev->dbcc_en) { - switch (wl_dinfo->role[RTW89_PHY_0]) { - case RTW89_WIFI_ROLE_STATION: - wl_rinfo->link_mode = BTC_WLINK_2G_STA; - break; - case RTW89_WIFI_ROLE_P2P_GO: - wl_rinfo->link_mode = BTC_WLINK_2G_GO; - break; - case RTW89_WIFI_ROLE_P2P_CLIENT: - wl_rinfo->link_mode = BTC_WLINK_2G_GC; - break; - case RTW89_WIFI_ROLE_AP: - wl_rinfo->link_mode = BTC_WLINK_2G_AP; - break; - default: - wl_rinfo->link_mode = BTC_WLINK_OTHER; - break; - } - } else { - wl_rinfo->link_mode = BTC_WLINK_25G_MCC; - } - } else if (!b5g && cnt_connect == 2) { - if (wl_rinfo->role_map.role.station && - (wl_rinfo->role_map.role.p2p_go || - wl_rinfo->role_map.role.p2p_gc || - wl_rinfo->role_map.role.ap)) { - if (wl_2g_ch[0] == wl_2g_ch[1]) - wl_rinfo->link_mode = BTC_WLINK_2G_SCC; - else - wl_rinfo->link_mode = BTC_WLINK_2G_MCC; - } else { - wl_rinfo->link_mode = BTC_WLINK_2G_MCC; - } - } else if (!b5g && cnt_connect == 1) { - if (wl_rinfo->role_map.role.station) - wl_rinfo->link_mode = BTC_WLINK_2G_STA; - else if (wl_rinfo->role_map.role.ap) - wl_rinfo->link_mode = BTC_WLINK_2G_AP; - else if (wl_rinfo->role_map.role.p2p_go) - wl_rinfo->link_mode = BTC_WLINK_2G_GO; - else if (wl_rinfo->role_map.role.p2p_gc) - wl_rinfo->link_mode = BTC_WLINK_2G_GC; - else - wl_rinfo->link_mode = BTC_WLINK_OTHER; - } - - /* if no client_joined, don't care P2P-GO/AP role */ - if (wl_rinfo->role_map.role.p2p_go || wl_rinfo->role_map.role.ap) { - if (!client_joined) { - if (wl_rinfo->link_mode == BTC_WLINK_2G_SCC || - wl_rinfo->link_mode == BTC_WLINK_2G_MCC) { - wl_rinfo->link_mode = BTC_WLINK_2G_STA; - wl_rinfo->connect_cnt = 1; - } else if (wl_rinfo->link_mode == BTC_WLINK_2G_GO || - wl_rinfo->link_mode == BTC_WLINK_2G_AP) { - wl_rinfo->link_mode = BTC_WLINK_NOLINK; - wl_rinfo->connect_cnt = 0; - } - } - } - - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], cnt_connect = %d, connecting = %d, link_mode = %d\n", - cnt_connect, cnt_connecting, wl_rinfo->link_mode); - - _fw_set_drv_info(rtwdev, CXDRVINFO_ROLE); -} - #define BTC_CHK_HANG_MAX 3 #define BTC_SCB_INV_VALUE GENMASK(31, 0) -static u8 _get_role_link_mode(u8 role) +static u8 _get_role_link_mode(struct rtw89_btc_wl_role_info *r, u8 role, bool notv10) { switch (role) { case RTW89_WIFI_ROLE_STATION: - return BTC_WLINK_2G_STA; - case RTW89_WIFI_ROLE_P2P_GO: - return BTC_WLINK_2G_GO; - case RTW89_WIFI_ROLE_P2P_CLIENT: - return BTC_WLINK_2G_GC; - case RTW89_WIFI_ROLE_AP: - return BTC_WLINK_2G_AP; default: - return BTC_WLINK_OTHER; + if (notv10) + r->link_mode_v0 = BTC_WLINK_V0_2G_STA; + return BTC_WLINK_STA; + case RTW89_WIFI_ROLE_P2P_GO: + if (notv10) + r->link_mode_v0 = BTC_WLINK_V0_2G_GO; + return BTC_WLINK_GO; + case RTW89_WIFI_ROLE_P2P_CLIENT: + if (notv10) + r->link_mode_v0 = BTC_WLINK_V0_2G_GC; + return BTC_WLINK_GC; + case RTW89_WIFI_ROLE_AP: + if (notv10) + r->link_mode_v0 = BTC_WLINK_V0_2G_AP; + return BTC_WLINK_AP; } } @@ -6891,13 +6498,13 @@ static bool _chk_role_ch_group(const struct rtw89_btc_chdef *r1, } static u8 _chk_dbcc(struct rtw89_dev *rtwdev, struct rtw89_btc_chdef *ch, - u8 *phy, u8 *role, u8 link_cnt) + u8 *phy, u8 *role, u8 link_cnt, bool notv10) { struct rtw89_btc_wl_info *wl = &rtwdev->btc.cx.wl; - struct rtw89_btc_wl_role_info_v7 *rinfo_v7 = &wl->role_info_v7; - struct rtw89_btc_wl_role_info_v8 *rinfo_v8 = &wl->role_info_v8; + struct rtw89_btc_wl_role_info *rinfo = &wl->role_info; bool is_2g_ch_exist = false, is_multi_role_in_2g_phy = false; - u8 j, k, dbcc_2g_cid, dbcc_2g_cid2, dbcc_2g_phy, pta_req_band; + u8 j, k, dbcc_2g_cid = 0, dbcc_2g_cid2 = 0; + u8 mode; /* find out the 2G-PHY by connect-id ->ch */ for (j = 0; j < link_cnt; j++) { @@ -6908,34 +6515,31 @@ static u8 _chk_dbcc(struct rtw89_dev *rtwdev, struct rtw89_btc_chdef *ch, } /* If no any 2G-port exist, it's impossible because 5G-exclude */ - if (!is_2g_ch_exist) - return BTC_WLINK_5G; + if (!is_2g_ch_exist) { + mode = BTC_WLINK_SB_MCC; + if (notv10) + rinfo->link_mode_v0 = BTC_WLINK_V0_5G; + return mode; + } dbcc_2g_cid = j; - dbcc_2g_phy = phy[dbcc_2g_cid]; + rinfo->dbcc_2g_phy = phy[dbcc_2g_cid]; - if (dbcc_2g_phy == RTW89_PHY_1) - pta_req_band = RTW89_PHY_1; + if (rinfo->dbcc_2g_phy == RTW89_PHY_1) + rinfo->pta_req_band = RTW89_PHY_1; else - pta_req_band = RTW89_PHY_0; - - if (rtwdev->btc.ver->fwlrole == 7) { - rinfo_v7->dbcc_2g_phy = dbcc_2g_phy; - } else if (rtwdev->btc.ver->fwlrole == 8) { - rinfo_v8->dbcc_2g_phy = dbcc_2g_phy; - rinfo_v8->pta_req_band = pta_req_band; - } + rinfo->pta_req_band = RTW89_PHY_0; /* connect_cnt <= 2 */ if (link_cnt < BTC_TDMA_WLROLE_MAX) - return (_get_role_link_mode((role[dbcc_2g_cid]))); + return _get_role_link_mode(rinfo, role[dbcc_2g_cid], notv10); /* find the other-port in the 2G-PHY, ex: PHY-0:6G, PHY1: mcc/scc */ for (k = 0; k < link_cnt; k++) { if (k == dbcc_2g_cid) continue; - if (phy[k] == dbcc_2g_phy) { + if (phy[k] == rinfo->dbcc_2g_phy) { is_multi_role_in_2g_phy = true; dbcc_2g_cid2 = k; break; @@ -6944,319 +6548,151 @@ static u8 _chk_dbcc(struct rtw89_dev *rtwdev, struct rtw89_btc_chdef *ch, /* Single-role in 2G-PHY */ if (!is_multi_role_in_2g_phy) - return (_get_role_link_mode(role[dbcc_2g_cid])); + return _get_role_link_mode(rinfo, role[dbcc_2g_cid], notv10); /* 2-role in 2G-PHY */ - if (ch[dbcc_2g_cid2].center_ch > 14) - return BTC_WLINK_25G_MCC; - else if (_chk_role_ch_group(&ch[dbcc_2g_cid], &ch[dbcc_2g_cid2])) - return BTC_WLINK_2G_SCC; - else - return BTC_WLINK_2G_MCC; -} - -static void _update_role_link_mode(struct rtw89_dev *rtwdev, - bool client_joined, u32 noa) -{ - struct rtw89_btc_wl_role_info_v8 *rinfo_v8 = &rtwdev->btc.cx.wl.role_info_v8; - struct rtw89_btc_wl_role_info_v7 *rinfo_v7 = &rtwdev->btc.cx.wl.role_info_v7; - u8 role_ver = rtwdev->btc.ver->fwlrole; - u32 type = BTC_WLMROLE_NONE, dur = 0; - u8 link_mode, connect_cnt; - u32 wl_role; - - if (role_ver == 7) { - wl_role = rinfo_v7->role_map; - link_mode = rinfo_v7->link_mode; - connect_cnt = rinfo_v7->connect_cnt; - } else if (role_ver == 8) { - wl_role = rinfo_v8->role_map; - link_mode = rinfo_v8->link_mode; - connect_cnt = rinfo_v8->connect_cnt; + if (ch[dbcc_2g_cid2].center_ch > 14) { + if (notv10) + rinfo->link_mode_v0 = BTC_WLINK_V0_25G_MCC; + return BTC_WLINK_DB_MCC; + } else if (_chk_role_ch_group(&ch[dbcc_2g_cid], &ch[dbcc_2g_cid2])) { + if (notv10) + rinfo->link_mode_v0 = BTC_WLINK_V0_2G_SCC; + return BTC_WLINK_SCC; } else { - return; - } - - /* if no client_joined, don't care P2P-GO/AP role */ - if (((wl_role & BIT(RTW89_WIFI_ROLE_P2P_GO)) || - (wl_role & BIT(RTW89_WIFI_ROLE_AP))) && !client_joined) { - if (link_mode == BTC_WLINK_2G_SCC) { - if (role_ver == 7) { - rinfo_v7->link_mode = BTC_WLINK_2G_STA; - rinfo_v7->connect_cnt--; - } else if (role_ver == 8) { - rinfo_v8->link_mode = BTC_WLINK_2G_STA; - rinfo_v8->connect_cnt--; - } - } else if (link_mode == BTC_WLINK_2G_GO || - link_mode == BTC_WLINK_2G_AP) { - if (role_ver == 7) { - rinfo_v7->link_mode = BTC_WLINK_NOLINK; - rinfo_v7->connect_cnt--; - } else if (role_ver == 8) { - rinfo_v8->link_mode = BTC_WLINK_NOLINK; - rinfo_v8->connect_cnt--; - } - } - } - - /* Identify 2-Role type */ - if (connect_cnt >= 2 && - (link_mode == BTC_WLINK_2G_SCC || - link_mode == BTC_WLINK_2G_MCC || - link_mode == BTC_WLINK_25G_MCC || - link_mode == BTC_WLINK_5G)) { - if ((wl_role & BIT(RTW89_WIFI_ROLE_P2P_GO)) || - (wl_role & BIT(RTW89_WIFI_ROLE_AP))) - type = noa ? BTC_WLMROLE_STA_GO_NOA : BTC_WLMROLE_STA_GO; - else if (wl_role & BIT(RTW89_WIFI_ROLE_P2P_CLIENT)) - type = noa ? BTC_WLMROLE_STA_GC_NOA : BTC_WLMROLE_STA_GC; - else - type = BTC_WLMROLE_STA_STA; - - dur = noa; - } - - if (role_ver == 7) { - rinfo_v7->mrole_type = type; - rinfo_v7->mrole_noa_duration = dur; - } else if (role_ver == 8) { - rinfo_v8->mrole_type = type; - rinfo_v8->mrole_noa_duration = dur; + if (notv10) + rinfo->link_mode_v0 = BTC_WLINK_V0_2G_MCC; + return BTC_WLINK_SB_MCC; } } -static void _update_wl_info_v7(struct rtw89_dev *rtwdev, u8 rid) +static void _update_wl_link_mode(struct rtw89_dev *rtwdev, u8 hw_band, u8 type) { - struct rtw89_btc_chdef cid_ch[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER]; struct rtw89_btc *btc = &rtwdev->btc; + const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_role_info_v7 *wl_rinfo = &wl->role_info_v7; - struct rtw89_btc_wl_dbcc_info *wl_dinfo = &wl->dbcc_info; - struct rtw89_btc_wl_link_info *wl_linfo = wl->link_info; - struct rtw89_btc_wl_active_role_v7 *act_role = NULL; - u8 i, mode, cnt = 0, cnt_2g = 0, cnt_5g = 0, phy_now = RTW89_PHY_NUM, phy_dbcc; - bool b2g = false, b5g = false, client_joined = false, client_inc_2g = false; - u8 client_cnt_last[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER] = {}; - u8 cid_role[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER] = {}; - u8 cid_phy[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER] = {}; - u8 mac = RTW89_MAC_0, dbcc_2g_phy = RTW89_PHY_0; - u32 noa_duration = 0; - - memset(wl_rinfo, 0, sizeof(*wl_rinfo)); - - for (i = 0; i < RTW89_PORT_NUM; i++) { - if (!wl_linfo[i].active || wl_linfo[i].phy >= RTW89_PHY_NUM) - continue; - - act_role = &wl_rinfo->active_role[i]; - act_role->role = wl_linfo[i].role; - - /* check if role connect? */ - if (wl_linfo[i].connected == MLME_NO_LINK) { - act_role->connected = 0; - continue; - } else if (wl_linfo[i].connected == MLME_LINKING) { - continue; - } - - cnt++; - act_role->connected = 1; - act_role->pid = wl_linfo[i].pid; - act_role->phy = wl_linfo[i].phy; - act_role->band = wl_linfo[i].band; - act_role->ch = wl_linfo[i].ch; - act_role->bw = wl_linfo[i].bw; - act_role->noa = wl_linfo[i].noa; - act_role->noa_dur = wl_linfo[i].noa_duration; - cid_ch[cnt - 1] = wl_linfo[i].chdef; - cid_phy[cnt - 1] = wl_linfo[i].phy; - cid_role[cnt - 1] = wl_linfo[i].role; - wl_rinfo->role_map |= BIT(wl_linfo[i].role); - - if (rid == i) - phy_now = act_role->phy; - - if (wl_linfo[i].role == RTW89_WIFI_ROLE_P2P_GO || - wl_linfo[i].role == RTW89_WIFI_ROLE_AP) { - if (wl_linfo[i].client_cnt > 1) - client_joined = true; - if (client_cnt_last[i] < wl_linfo[i].client_cnt && - wl_linfo[i].chdef.band == RTW89_BAND_2G) - client_inc_2g = true; - act_role->client_cnt = wl_linfo[i].client_cnt; - } else { - act_role->client_cnt = 0; - } - - if (act_role->noa && act_role->noa_dur > 0) - noa_duration = act_role->noa_dur; - - if (rtwdev->dbcc_en) { - phy_dbcc = wl_linfo[i].phy; - wl_dinfo->role[phy_dbcc] |= BIT(wl_linfo[i].role); - wl_dinfo->op_band[phy_dbcc] = wl_linfo[i].chdef.band; - } - - if (wl_linfo[i].chdef.band != RTW89_BAND_2G) { - cnt_5g++; - b5g = true; - } else { - if (((wl_linfo[i].role == RTW89_WIFI_ROLE_P2P_GO || - wl_linfo[i].role == RTW89_WIFI_ROLE_AP) && - client_joined) || - wl_linfo[i].role == RTW89_WIFI_ROLE_P2P_CLIENT) - wl_rinfo->p2p_2g = 1; - - if ((wl_linfo[i].mode & BIT(BTC_WL_MODE_11B)) || - (wl_linfo[i].mode & BIT(BTC_WL_MODE_11G))) - wl->bg_mode = 1; - else if (wl_linfo[i].mode & BIT(BTC_WL_MODE_HE)) - wl->he_mode = true; - - cnt_2g++; - b2g = true; - } - - if (act_role->band == RTW89_BAND_5G && act_role->ch >= 100) - wl->is_5g_hi_channel = 1; - else - wl->is_5g_hi_channel = 0; - } - - wl_rinfo->connect_cnt = cnt; - wl->client_cnt_inc_2g = client_inc_2g; - - if (cnt == 0) { - mode = BTC_WLINK_NOLINK; - wl_rinfo->role_map = BIT(RTW89_WIFI_ROLE_NONE); - } else if (!b2g && b5g) { - mode = BTC_WLINK_5G; - } else if (wl_rinfo->role_map & BIT(RTW89_WIFI_ROLE_NAN)) { - mode = BTC_WLINK_2G_NAN; - } else if (cnt > BTC_TDMA_WLROLE_MAX) { - mode = BTC_WLINK_OTHER; - } else if (rtwdev->dbcc_en) { - mode = _chk_dbcc(rtwdev, cid_ch, cid_phy, cid_role, cnt); - - /* correct 2G-located PHY band for gnt ctrl */ - if (dbcc_2g_phy < RTW89_PHY_NUM) - wl_dinfo->op_band[dbcc_2g_phy] = RTW89_BAND_2G; - } else if (b2g && b5g && cnt == 2) { - mode = BTC_WLINK_25G_MCC; - } else if (!b5g && cnt == 2) { /* cnt_connect = 2 */ - if (_chk_role_ch_group(&cid_ch[0], &cid_ch[cnt - 1])) - mode = BTC_WLINK_2G_SCC; - else - mode = BTC_WLINK_2G_MCC; - } else if (!b5g && cnt == 1) { /* cnt_connect = 1 */ - mode = _get_role_link_mode(cid_role[0]); - } else { - mode = BTC_WLINK_NOLINK; - } - - wl_rinfo->link_mode = mode; - _update_role_link_mode(rtwdev, client_joined, noa_duration); - - /* todo DBCC related event */ - rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC] wl_info phy_now=%d\n", phy_now); - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC] rlink cnt_2g=%d cnt_5g=%d\n", cnt_2g, cnt_5g); - - if (wl_rinfo->dbcc_en != rtwdev->dbcc_en) { - wl_rinfo->dbcc_chg = 1; - wl_rinfo->dbcc_en = rtwdev->dbcc_en; - wl->wcnt[BTC_WCNT_DBCC_CHG]++; - } - - if (rtwdev->dbcc_en) { - wl_rinfo->dbcc_2g_phy = dbcc_2g_phy; - - if (dbcc_2g_phy == RTW89_PHY_1) - mac = RTW89_MAC_1; - - _update_dbcc_band(rtwdev, RTW89_PHY_0); - _update_dbcc_band(rtwdev, RTW89_PHY_1); - } - _wl_req_mac(rtwdev, mac); - _fw_set_drv_info(rtwdev, CXDRVINFO_ROLE); -} - -static u8 _update_wl_link_mode(struct rtw89_dev *rtwdev, u8 hw_band, u8 type) -{ - struct rtw89_btc_wl_info *wl = &rtwdev->btc.cx.wl; struct rtw89_btc_wl_mlo_info *mlo_info = &wl->mlo_info; - u8 mode = BTC_WLINK_NOLINK; + struct rtw89_btc_wl_role_info *r = &wl->role_info; + u8 p2p_exist = wl->role_info.p2p_exist; + if (hw_band == RTW89_PHY_1) + p2p_exist = wl->role_info.p2p_exist_hb1; + + /* MLD: nLmR --> 2L1R: 2-Link by 1-RF, 2L2R: 2-link by 2-RF */ switch (type) { case RTW89_MR_WTYPE_NONE: /* no-link */ - mode = BTC_WLINK_NOLINK; + r->link_mode = BTC_WLINK_NOLINK; break; case RTW89_MR_WTYPE_NONMLD: /* Non_MLO 1-role 2+0/0+2 */ case RTW89_MR_WTYPE_MLD1L1R: /* MLO only-1 link 2+0/0+2 */ + if (mlo_info->wmode[hw_band] == RTW89_MR_WMODE_1AP) { + r->link_mode = BTC_WLINK_GO; + } else if (mlo_info->wmode[hw_band] == RTW89_MR_WMODE_1CLIENT && + p2p_exist) { + r->link_mode = BTC_WLINK_GC; + } else { + r->link_mode = BTC_WLINK_STA; + } + + if (ver->fwlrole >= 10) + break; + if (mlo_info->hwb_rf_band[hw_band] != RTW89_BAND_2G) { - mode = BTC_WLINK_5G; + r->link_mode_v0 = BTC_WLINK_V0_5G; } else if (mlo_info->wmode[hw_band] == RTW89_MR_WMODE_1AP) { - mode = BTC_WLINK_2G_GO; + r->link_mode_v0 = BTC_WLINK_V0_2G_GO; } else if (mlo_info->wmode[hw_band] == RTW89_MR_WMODE_1CLIENT) { - if (wl->role_info_v8.p2p_2g) - mode = BTC_WLINK_2G_GC; + if (wl->role_info.p2p_2g) + r->link_mode_v0 = BTC_WLINK_V0_2G_GC; else - mode = BTC_WLINK_2G_STA; + r->link_mode_v0 = BTC_WLINK_V0_2G_STA; } break; case RTW89_MR_WTYPE_NONMLD_NONMLD: /* Non_MLO 2-role 2+0/0+2 */ case RTW89_MR_WTYPE_MLD1L1R_NONMLD: /* MLO only-1 link + P2P 2+0/0+2 */ + if (mlo_info->ch_type[hw_band] == RTW89_MR_CTX2_2GHZ_5GHZ || + mlo_info->ch_type[hw_band] == RTW89_MR_CTX2_2GHZ_6GHZ) { + r->link_mode = BTC_WLINK_DB_MCC; + } else if (mlo_info->ch_type[hw_band] == RTW89_MR_CTX2_2GHZ || + mlo_info->ch_type[hw_band] == RTW89_MR_CTX2_5GHZ || + mlo_info->ch_type[hw_band] == RTW89_MR_CTX2_6GHZ || + mlo_info->ch_type[hw_band] == RTW89_MR_CTX2_5GHZ_6GHZ) { + r->link_mode = BTC_WLINK_SB_MCC; + } else { + r->link_mode = BTC_WLINK_SCC; + } + + if (ver->fwlrole >= 10) + break; + if (mlo_info->hwb_rf_band[hw_band] != RTW89_BAND_2G) { - mode = BTC_WLINK_5G; + r->link_mode_v0 = BTC_WLINK_V0_5G; } else if (mlo_info->ch_type[hw_band] == RTW89_MR_CTX2_2GHZ_5GHZ || mlo_info->ch_type[hw_band] == RTW89_MR_CTX2_2GHZ_6GHZ) { - mode = BTC_WLINK_25G_MCC; + r->link_mode_v0 = BTC_WLINK_V0_25G_MCC; } else if (mlo_info->ch_type[hw_band] == RTW89_MR_CTX2_2GHZ) { - mode = BTC_WLINK_2G_MCC; + r->link_mode_v0 = BTC_WLINK_V0_2G_MCC; } else if (mlo_info->ch_type[hw_band] == RTW89_MR_CTX1_2GHZ) { - mode = BTC_WLINK_2G_SCC; + r->link_mode_v0 = BTC_WLINK_V0_2G_SCC; } break; case RTW89_MR_WTYPE_MLD2L1R: /* MLO_MLSR 2+0/0+2 */ - if (mlo_info->hwb_rf_band[hw_band] != RTW89_BAND_2G) - mode = BTC_WLINK_5G; - else if (wl->role_info_v8.p2p_2g) - mode = BTC_WLINK_2G_GC; + if (p2p_exist) /* MLO_MLSR only support STA/GC */ + r->link_mode = BTC_WLINK_GC; else - mode = BTC_WLINK_2G_STA; + r->link_mode = BTC_WLINK_STA; + + if (ver->fwlrole >= 10) + break; + + if (mlo_info->hwb_rf_band[hw_band] != RTW89_BAND_2G) + r->link_mode_v0 = BTC_WLINK_V0_5G; + else if (wl->role_info.p2p_2g) + r->link_mode_v0 = BTC_WLINK_V0_2G_GC; + else + r->link_mode_v0 = BTC_WLINK_V0_2G_STA; break; case RTW89_MR_WTYPE_MLD2L1R_NONMLD: /* MLO_MLSR + P2P 2+0/0+2 */ - case RTW89_MR_WTYPE_MLD2L2R_NONMLD: /* MLO_MLMR + P2P 1+1/2+2 */ - /* driver may doze 1-link to + case RTW89_MR_WTYPE_MLD2L2R_NONMLD: /* MLO_MLMR + P2P 1+1/2+2*/ + /* driver may doze 1-link (1+1) or 2+0->0+2->1+1 * 2G+5G -> TDMA slot switch by E2G/E5G * 5G only -> TDMA slot switch by E5G */ - mode = BTC_WLINK_25G_MCC; + r->link_mode = BTC_WLINK_DB_MCC; + + if (ver->fwlrole >= 10) + break; + + r->link_mode_v0 = BTC_WLINK_V0_25G_MCC; break; case RTW89_MR_WTYPE_MLD2L2R: /* MLO_MLMR 1+1/2+2 */ + /* MLMR only support STA now (2024) */ + r->link_mode = BTC_WLINK_STA; + + if (ver->fwlrole >= 10) + break; + if (mlo_info->hwb_rf_band[hw_band] != RTW89_BAND_2G) { - mode = BTC_WLINK_5G; + r->link_mode_v0 = BTC_WLINK_V0_5G; } else if (mlo_info->wmode[hw_band] == RTW89_MR_WMODE_1AP) { - mode = BTC_WLINK_2G_GO; + r->link_mode_v0 = BTC_WLINK_V0_2G_GO; } else if (mlo_info->wmode[hw_band] == RTW89_MR_WMODE_1CLIENT) { - if (wl->role_info_v8.p2p_2g) - mode = BTC_WLINK_2G_GC; + if (wl->role_info.p2p_2g) + r->link_mode_v0 = BTC_WLINK_V0_2G_GC; else - mode = BTC_WLINK_2G_STA; + r->link_mode_v0 = BTC_WLINK_V0_2G_STA; } break; } - return mode; } -static void _update_wl_mlo_info(struct rtw89_dev *rtwdev) +static void _update_wl_mlo_info(struct rtw89_dev *rtwdev, u8 hw_band) { struct rtw89_btc_wl_info *wl = &rtwdev->btc.cx.wl; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo = &wl->role_info_v8; + struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; struct rtw89_btc_wl_mlo_info *mlo_info = &wl->mlo_info; struct rtw89_mr_chanctx_info qinfo; - u8 track_band = RTW89_PHY_0; + struct rtw89_chanctx *ch; u8 rf_band = RTW89_BAND_2G; u8 i, type; @@ -7265,19 +6701,46 @@ static void _update_wl_mlo_info(struct rtw89_dev *rtwdev) memset(&qinfo, 0, sizeof(qinfo)); rtw89_query_mr_chanctx_info(rtwdev, i, &qinfo); + + switch (qinfo.ctxtype) { + default: + mlo_info->hwb_rf_band[i] = 0; + break; + case RTW89_MR_CTX1_2GHZ: + case RTW89_MR_CTX2_2GHZ: + mlo_info->hwb_rf_band[i] = BIT(RTW89_BAND_2G); + break; + case RTW89_MR_CTX1_5GHZ: + case RTW89_MR_CTX2_5GHZ: + mlo_info->hwb_rf_band[i] = BIT(RTW89_BAND_5G); + break; + case RTW89_MR_CTX1_6GHZ: + case RTW89_MR_CTX2_6GHZ: + mlo_info->hwb_rf_band[i] = BIT(RTW89_BAND_6G); + break; + case RTW89_MR_CTX2_2GHZ_5GHZ: + mlo_info->hwb_rf_band[i] = BIT(RTW89_BAND_2G) | + BIT(RTW89_BAND_5G); + break; + case RTW89_MR_CTX2_2GHZ_6GHZ: + mlo_info->hwb_rf_band[i] = BIT(RTW89_BAND_2G) | + BIT(RTW89_BAND_6G); + break; + case RTW89_MR_CTX2_5GHZ_6GHZ: + mlo_info->hwb_rf_band[i] = BIT(RTW89_BAND_5G) | + BIT(RTW89_BAND_6G); + break; + } mlo_info->wmode[i] = qinfo.wmode; mlo_info->ch_type[i] = qinfo.ctxtype; mlo_info->wtype = qinfo.wtype; - - if (mlo_info->ch_type[i] == RTW89_MR_CTX1_5GHZ || - mlo_info->ch_type[i] == RTW89_MR_CTX2_5GHZ || - mlo_info->ch_type[i] == RTW89_MR_CTX2_5GHZ_6GHZ) - mlo_info->hwb_rf_band[i] = RTW89_BAND_5G; - else if (mlo_info->ch_type[i] == RTW89_MR_CTX1_6GHZ || - mlo_info->ch_type[i] == RTW89_MR_CTX2_6GHZ) - mlo_info->hwb_rf_band[i] = RTW89_BAND_6G; - else /* check if "2G-included" or unknown in each HW-band */ - mlo_info->hwb_rf_band[i] = RTW89_BAND_2G; + wl->rf_band_map[i] = mlo_info->hwb_rf_band[i]; + ch = &rtwdev->hal.chanctx[i]; + wl->rf_ch_info[i].center_ch = ch->chan.channel; + wl->rf_ch_info[i].band = ch->chan.band_type; + wl->rf_ch_info[i].chan = ch->chan.channel; + wl->rf_ch_info[i].offset = ch->chan.pri_ch_idx; + wl->rf_ch_info[i].bw = ch->chan.band_width; } mlo_info->link_status = rtwdev->mlo_dbcc_mode; @@ -7312,71 +6775,64 @@ static void _update_wl_mlo_info(struct rtw89_dev *rtwdev) default: case MLO_2_PLUS_0_1RF: /* 2+0 */ case MLO_2_PLUS_0_2RF: - mlo_info->rf_combination = BTC_MLO_RF_2_PLUS_0; - track_band = RTW89_MAC_0; - rf_band = mlo_info->hwb_rf_band[RTW89_MAC_0]; - mlo_info->path_rf_band[BTC_RF_S0] = rf_band; - mlo_info->path_rf_band[BTC_RF_S1] = rf_band; - + _update_wl_link_mode(rtwdev, RTW89_MAC_0, type); + wl_rinfo->link_mode_hb1 = wl_rinfo->link_mode; wl_rinfo->pta_req_band = RTW89_MAC_0; wl_rinfo->dbcc_2g_phy = RTW89_PHY_0; wl_rinfo->dbcc_en = 0; + + rf_band = wl->rf_band_map[RTW89_PHY_0]; + mlo_info->path_rf_band[BTC_RF_S0] = rf_band; + mlo_info->path_rf_band[BTC_RF_S1] = rf_band; break; case MLO_0_PLUS_2_1RF: /* 0+2 */ case MLO_0_PLUS_2_2RF: - mlo_info->rf_combination = BTC_MLO_RF_0_PLUS_2; - track_band = RTW89_MAC_1; - rf_band = mlo_info->hwb_rf_band[RTW89_MAC_1]; - mlo_info->path_rf_band[BTC_RF_S0] = rf_band; - mlo_info->path_rf_band[BTC_RF_S1] = rf_band; - + _update_wl_link_mode(rtwdev, RTW89_MAC_1, type); + wl_rinfo->link_mode_hb1 = wl_rinfo->link_mode; wl_rinfo->pta_req_band = RTW89_MAC_1; wl_rinfo->dbcc_2g_phy = RTW89_PHY_1; wl_rinfo->dbcc_en = 0; + + rf_band = wl->rf_band_map[RTW89_PHY_1]; + mlo_info->path_rf_band[BTC_RF_S0] = rf_band; + mlo_info->path_rf_band[BTC_RF_S1] = rf_band; break; case MLO_1_PLUS_1_1RF: /* 1+1 */ case MLO_1_PLUS_1_2RF: /* 1+1 */ case MLO_2_PLUS_2_2RF: /* 2+2 */ case DBCC_LEGACY: /* DBCC 1+1 */ - if (mlo_info->link_status == MLO_2_PLUS_2_2RF) - mlo_info->rf_combination = BTC_MLO_RF_2_PLUS_2; - else - mlo_info->rf_combination = BTC_MLO_RF_1_PLUS_1; - - if (mlo_info->hwb_rf_band[RTW89_MAC_0] == RTW89_BAND_2G) - track_band = RTW89_MAC_0; - else - track_band = RTW89_MAC_1; - - mlo_info->path_rf_band[BTC_RF_S0] = - mlo_info->hwb_rf_band[RTW89_MAC_0]; - mlo_info->path_rf_band[BTC_RF_S1] = - mlo_info->hwb_rf_band[RTW89_MAC_1]; - - /* Check ch count from ch_type @ 2.4G HW-band, and modify type */ - if (mlo_info->ch_type[track_band] == RTW89_MR_CTX1_2GHZ) - type = RTW89_MR_WTYPE_NONMLD; /* only 1-role at 2G */ + /* Check ch count from ch_type and modify type*/ + if (mlo_info->ch_type[hw_band] == RTW89_MR_CTX1_2GHZ || + mlo_info->ch_type[hw_band] == RTW89_MR_CTX1_5GHZ || + mlo_info->ch_type[hw_band] == RTW89_MR_CTX1_6GHZ) + type = RTW89_MR_WTYPE_NONMLD; /* only 1-role */ else type = RTW89_MR_WTYPE_NONMLD_NONMLD; - if (mlo_info->hwb_rf_band[RTW89_MAC_0] == RTW89_BAND_2G) { - wl_rinfo->pta_req_band = RTW89_MAC_0; - wl_rinfo->dbcc_2g_phy = RTW89_PHY_0; + rf_band = wl->rf_band_map[hw_band]; + _update_wl_link_mode(rtwdev, hw_band, type); + + if (hw_band == RTW89_MAC_0) { + mlo_info->path_rf_band[BTC_RF_S0] = rf_band; } else { - wl_rinfo->pta_req_band = RTW89_MAC_1; - wl_rinfo->dbcc_2g_phy = RTW89_PHY_1; + wl_rinfo->link_mode_hb1 = wl_rinfo->link_mode; + mlo_info->path_rf_band[BTC_RF_S1] = rf_band; } - if (mlo_info->wmode[RTW89_MAC_0] == RTW89_MR_WMODE_NONE && - mlo_info->wmode[RTW89_MAC_1] == RTW89_MR_WMODE_NONE) - wl_rinfo->dbcc_en = 0; - else - wl_rinfo->dbcc_en = 1; + /* set pta_req_band for 1-PTA architecture */ + if (wl->rf_band_map[RTW89_MAC_0] & BIT(RTW89_BAND_2G)) { + wl_rinfo->pta_req_band = RTW89_MAC_0; + wl_rinfo->dbcc_2g_phy = RTW89_PHY_0; + } else if (wl->rf_band_map[RTW89_MAC_1] & BIT(RTW89_BAND_2G)) { + wl_rinfo->pta_req_band = RTW89_MAC_1; + wl_rinfo->dbcc_2g_phy = RTW89_PHY_1; + } else { /* Both HW-BAND are not on 2.4G*/ + wl_rinfo->pta_req_band = RTW89_MAC_0; + wl_rinfo->dbcc_2g_phy = RTW89_PHY_0; + } break; } - wl_rinfo->link_mode = _update_wl_link_mode(rtwdev, track_band, type); - rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(), mode=%s, pta_band=%d", __func__, id_to_linkmode(wl_rinfo->link_mode), wl_rinfo->pta_req_band); @@ -7386,16 +6842,19 @@ static void _update_wl_non_mlo_info(struct rtw89_dev *rtwdev) { struct rtw89_btc_wl_info *wl = &rtwdev->btc.cx.wl; struct rtw89_btc_wl_rlink *rlink = NULL; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo = &wl->role_info_v8; + struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; struct rtw89_btc_chdef cid_ch[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER] = {}; u8 cid_role[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER] = {}; u8 cid_phy[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER] = {}; bool b2g = false, b5g = false, outloop = false; + bool notv10 = rtwdev->btc.ver->fwlrole != 10; + u8 mode_v0 = BTC_WLINK_V0_NOLINK; u8 mode = BTC_WLINK_NOLINK; u8 cnt_2g = 0, cnt_5g = 0; u8 i, j, cnt = 0; for (j = RTW89_PHY_0; j < RTW89_PHY_NUM; j++) { + wl->rf_band_map[j] = 0; /* j=link id, is the link use which band */ for (i = 0; i < RTW89_BE_BTC_WL_MAX_ROLE_NUMBER; i++) { rlink = &wl_rinfo->rlink[i][j]; @@ -7419,6 +6878,7 @@ static void _update_wl_non_mlo_info(struct rtw89_dev *rtwdev) cnt_2g++; b2g = true; } + wl->rf_band_map[j] |= BIT(rlink->rf_band); } if (outloop) break; @@ -7427,56 +6887,73 @@ static void _update_wl_non_mlo_info(struct rtw89_dev *rtwdev) rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): cnt_2g=%d, cnt_5g=%d\n", __func__, cnt_2g, cnt_5g); + wl_rinfo->pta_req_band = RTW89_PHY_0; /* no 0+2 for non-MLO */ wl_rinfo->dbcc_en = rtwdev->dbcc_en; + /* Be careful to change the following sequence!! */ if (cnt == 0) { mode = BTC_WLINK_NOLINK; - } else if (!b2g && b5g) { - mode = BTC_WLINK_5G; + mode_v0 = BTC_WLINK_V0_NOLINK; } else if (wl_rinfo->dbcc_en) { - mode = _chk_dbcc(rtwdev, cid_ch, cid_phy, cid_role, cnt); + /* update pta_req_band to 2.4GHz HW-BAND for DBCC = 1*/ + mode = _chk_dbcc(rtwdev, cid_ch, cid_phy, cid_role, cnt, notv10); + mode_v0 = wl_rinfo->link_mode_v0; + } else if (!b2g && b5g && notv10) { + mode_v0 = BTC_WLINK_V0_5G; } else if (b2g && b5g) { - mode = BTC_WLINK_25G_MCC; - } else if (!b5g && cnt >= 2) { - if (_chk_role_ch_group(&cid_ch[0], &cid_ch[1])) - mode = BTC_WLINK_2G_SCC; - else - mode = BTC_WLINK_2G_MCC; - } else if (!b5g) { /* cnt_connect = 1 */ - mode = _get_role_link_mode(cid_role[0]); + mode = BTC_WLINK_DB_MCC; + mode_v0 = BTC_WLINK_V0_25G_MCC; + } else if (cnt >= 2) { + if (_chk_role_ch_group(&cid_ch[0], &cid_ch[1])) { + mode = BTC_WLINK_SCC; + mode_v0 = BTC_WLINK_V0_2G_SCC; + } else { + mode = BTC_WLINK_SB_MCC; + mode_v0 = BTC_WLINK_V0_2G_MCC; + } + } else { + mode = _get_role_link_mode(wl_rinfo, cid_role[0], notv10); } wl_rinfo->link_mode = mode; + wl_rinfo->link_mode_hb1 = mode; + wl_rinfo->link_mode_v0 = mode_v0; } -static void _modify_role_link_mode(struct rtw89_dev *rtwdev) +static void _modify_role_link_mode(struct rtw89_dev *rtwdev, u8 hw_band) { - struct rtw89_btc_wl_info *wl = &rtwdev->btc.cx.wl; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo = &wl->role_info_v8; + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_wl_info *wl = &btc->cx.wl; + struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; u8 go_cleint_exist = wl->go_client_exist; - u8 link_mode = wl_rinfo->link_mode; + u8 *link_mode = &wl_rinfo->link_mode; u32 role_map = wl_rinfo->role_map; u8 noa_exist = wl->noa_exist; u32 mrole = BTC_WLMROLE_NONE; + if (hw_band == RTW89_PHY_1) { + *link_mode = wl_rinfo->link_mode_hb1; + role_map = wl_rinfo->role_map_hb1; + go_cleint_exist = wl->go_client_exist_hb1; + } + /* if no client_joined, don't care P2P-GO/AP role */ if (((role_map & BIT(RTW89_WIFI_ROLE_P2P_GO)) || - (role_map & BIT(RTW89_WIFI_ROLE_AP))) && !go_cleint_exist) { - if (link_mode == BTC_WLINK_2G_SCC) { - wl_rinfo->link_mode = BTC_WLINK_2G_STA; - } else if (link_mode == BTC_WLINK_2G_GO || - link_mode == BTC_WLINK_2G_AP) { - wl_rinfo->link_mode = BTC_WLINK_NOLINK; + (role_map & BIT(RTW89_WIFI_ROLE_AP))) && !go_cleint_exist) { + if (*link_mode == BTC_WLINK_SCC) { + *link_mode = BTC_WLINK_STA; + } else if (*link_mode == BTC_WLINK_GO || + *link_mode == BTC_WLINK_AP) { + *link_mode = BTC_WLINK_NOLINK; } } - /* Identify 2-Role type */ - if (link_mode == BTC_WLINK_2G_SCC || - link_mode == BTC_WLINK_2G_MCC || - link_mode == BTC_WLINK_25G_MCC || - link_mode == BTC_WLINK_5G) { + /* Identify 2-Role type */ + if (*link_mode == BTC_WLINK_SCC || + *link_mode == BTC_WLINK_SB_MCC || + *link_mode == BTC_WLINK_DB_MCC) { if ((role_map & BIT(RTW89_WIFI_ROLE_P2P_GO)) || - (role_map & BIT(RTW89_WIFI_ROLE_AP))) { + (role_map & BIT(RTW89_WIFI_ROLE_AP))) { if (noa_exist) mrole = BTC_WLMROLE_STA_GO_NOA; else @@ -7489,127 +6966,147 @@ static void _modify_role_link_mode(struct rtw89_dev *rtwdev) } else { mrole = BTC_WLMROLE_STA_STA; } - } - wl_rinfo->mrole_type = mrole; + if (hw_band == RTW89_PHY_0) { + wl_rinfo->mrole_type &= GENMASK(31, 16); /* clear Low-Word */ + wl_rinfo->mrole_type |= mrole; + } else { + wl_rinfo->mrole_type &= GENMASK(15, 0); /* clear high-Word */ + wl_rinfo->mrole_type |= (mrole << 16); + } + } else { + wl_rinfo->mrole_type = BTC_WLMROLE_NONE; + } rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): link_mode=%s, mrole_type=%d\n", __func__, id_to_linkmode(wl_rinfo->link_mode), wl_rinfo->mrole_type); } -static void _update_wl_info_v8(struct rtw89_dev *rtwdev, u8 role_id, u8 rlink_id, - enum btc_role_state state) +static void _update_wl_info(struct rtw89_dev *rtwdev, struct rtw89_btc_wl_link_info *wl_linfo) { - struct rtw89_btc_wl_rlink *rlink = NULL; - struct rtw89_btc_wl_link_info *wl_linfo; struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo = &wl->role_info_v8; - bool client_joined = false, noa_exist = false, p2p_exist = false; - bool is_5g_hi_channel = false, bg_mode = false, dbcc_en_ori; - u8 i, j, link_mode_ori; + struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; + struct rtw89_btc_wl_rlink *rlink = NULL; + u8 dbcc_en_ori, is_5g_hi_ch = 0, bg_mode = 0, p2p_exist = 0; + u8 go_client_exist = 0, noa_exist = 0; + u8 rlink_id = wl_linfo->phy; + u8 role_id = wl_linfo->pid; + u8 i, link_mode_ori; u32 role_map = 0; - if (role_id >= RTW89_BE_BTC_WL_MAX_ROLE_NUMBER || rlink_id >= RTW89_MAC_NUM) + if (role_id >= RTW89_BE_BTC_WL_MAX_ROLE_NUMBER || rlink_id >= RTW89_BAND_NUM) return; - /* Extract wl->link_info[role_id][rlink_id] to wl->role_info + /* + * Extract wl->link_info[role_id][rlink_id] to wl->role_info * role_id: role index * rlink_id: rlink index (= HW-band index) * pid: port_index */ - - wl_linfo = &wl->rlink_info[role_id][rlink_id]; rlink = &wl_rinfo->rlink[role_id][rlink_id]; rlink->role = wl_linfo->role; rlink->active = wl_linfo->active; /* Doze or not */ rlink->pid = wl_linfo->pid; + rlink->mac_id = wl_linfo->mac_id; rlink->phy = wl_linfo->phy; - rlink->rf_band = wl_linfo->band; - rlink->ch = wl_linfo->ch; - rlink->bw = wl_linfo->bw; + rlink->rf_band = wl_linfo->chdef.band; + rlink->ch = wl_linfo->chdef.center_ch; + rlink->bw = wl_linfo->chdef.bw; rlink->noa = wl_linfo->noa; rlink->noa_dur = wl_linfo->noa_duration / 1000; rlink->client_cnt = wl_linfo->client_cnt; rlink->mode = wl_linfo->mode; + /* check if rlink connect? */ switch (wl_linfo->connected) { case MLME_NO_LINK: rlink->connected = 0; break; case MLME_LINKED: rlink->connected = 1; + + /* reset special-AP flag if station mode linked */ + dm->leak_ap = 0; break; default: return; } - for (j = RTW89_MAC_0; j <= RTW89_MAC_1; j++) { - for (i = 0; i < RTW89_BE_BTC_WL_MAX_ROLE_NUMBER; i++) { - rlink = &wl_rinfo->rlink[i][j]; + /* update by HW-Band */ + for (i = 0; i < RTW89_BE_BTC_WL_MAX_ROLE_NUMBER; i++) { /* i = role_id */ + rlink = &wl_rinfo->rlink[i][rlink_id]; - if (!rlink->active || !rlink->connected) - continue; + if (!rlink->active || !rlink->connected) + continue; - role_map |= BIT(rlink->role); + role_map |= BIT(rlink->role); - /* only one noa-role exist */ - if (rlink->noa && rlink->noa_dur > 0) - noa_exist = true; - - /* for WL 5G-Rx interfered with BT issue */ - if (rlink->rf_band == RTW89_BAND_5G) { - if (rlink->ch >= 100) - is_5g_hi_channel = true; - - continue; - } - - /* only if client connect for p2p-Go/AP */ - if ((rlink->role == RTW89_WIFI_ROLE_P2P_GO || - rlink->role == RTW89_WIFI_ROLE_AP) && - rlink->client_cnt > 1) { - p2p_exist = true; - client_joined = true; - } - - /* Identify if P2P-Go (GO/GC/AP) exist at 2G band */ - if (rlink->role == RTW89_WIFI_ROLE_P2P_CLIENT) - p2p_exist = true; - - if ((rlink->mode & BIT(BTC_WL_MODE_11B)) || - (rlink->mode & BIT(BTC_WL_MODE_11G))) - bg_mode = true; + /* Identify if P2P-Go (GO/GC/AP) exist at 2GHz band */ + if (((rlink->role == RTW89_WIFI_ROLE_P2P_GO || + rlink->role == RTW89_WIFI_ROLE_AP) && + rlink->client_cnt > 1)) { + p2p_exist = 1; + go_client_exist = 1; } + + if (rlink->role == RTW89_WIFI_ROLE_P2P_CLIENT) + p2p_exist = 1; + + /* only one noa-role exist */ + if (rlink->noa && rlink->noa_dur > 0) + noa_exist = 1; + + /* for WL 5G-Rx interfered with BT issue */ + if (rlink->rf_band == RTW89_BAND_5G && rlink->ch >= 100) + is_5g_hi_ch = 1; + + if ((rlink->mode & BIT(BTC_WL_MODE_11B)) || + (rlink->mode & BIT(BTC_WL_MODE_11G))) + bg_mode = 1; } - link_mode_ori = wl_rinfo->link_mode; - wl->is_5g_hi_channel = is_5g_hi_channel; - wl->bg_mode = bg_mode; - wl->go_client_exist = client_joined; - wl->noa_exist = noa_exist; - wl_rinfo->p2p_2g = p2p_exist; - wl_rinfo->role_map = role_map; + if (rlink_id == RTW89_PHY_0) { + link_mode_ori = wl_rinfo->link_mode; + wl->is_5g_hi_ch = is_5g_hi_ch; + wl->bg_mode = bg_mode; + wl->go_client_exist = go_client_exist; + wl->noa_exist = noa_exist; + wl_rinfo->p2p_exist = p2p_exist; + wl_rinfo->role_map = role_map; + } else { + link_mode_ori = wl_rinfo->link_mode_hb1; + wl->is_5g_hi_ch_hb1 = is_5g_hi_ch; + wl->bg_mode_hb1 = bg_mode; + wl->go_client_exist_hb1 = go_client_exist; + wl->noa_exist_hb1 = noa_exist; + wl_rinfo->p2p_exist_hb1 = p2p_exist; + wl_rinfo->role_map_hb1 = role_map; + } dbcc_en_ori = wl_rinfo->dbcc_en; + /* for MLO-supported, link-mode from driver directly */ if (rtwdev->chip->para_ver & BTC_FEAT_MLO_SUPPORT) { - /* for MLO-supported, link-mode from driver directly */ - _update_wl_mlo_info(rtwdev); - } else { - /* for non-MLO-supported, link-mode by BTC */ + _update_wl_mlo_info(rtwdev, rlink_id); + } else { /* for non-MLO-supported, link-mode by BTC */ _update_wl_non_mlo_info(rtwdev); } - _modify_role_link_mode(rtwdev); + _modify_role_link_mode(rtwdev, rlink_id); - if (link_mode_ori != wl_rinfo->link_mode) - wl->link_mode_chg = true; + if ((rlink_id == RTW89_PHY_0 && wl_rinfo->link_mode != link_mode_ori) || + (rlink_id == RTW89_PHY_1 && wl_rinfo->link_mode_hb1 != link_mode_ori)) { + wl_rinfo->link_mode_chg = 1; + wl->link_mode_chg = 1; + } if (wl_rinfo->dbcc_en != dbcc_en_ori) { - wl->dbcc_chg = true; + wl->dbcc_chg = 1; + wl->role_info.dbcc_chg = 1; wl->wcnt[BTC_WCNT_DBCC_CHG]++; } } @@ -7685,25 +7182,21 @@ static void _update_bt_scbd(struct rtw89_dev *rtwdev, u8 bid) struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_bt_info *bt; u32 val, any_bt_connect, any_bt_6g_connect = 0; - u8 id, id_start, id_stop, mode; + u8 id, id_start, id_stop, mode = 0; bool bt_link_change = false; bool lps_ctrl = false; if (!rtwdev->chip->scbd || bid > BTC_ALL_BT) return; - if (ver->fwlrole == 0) - mode = wl->role_info.link_mode; - else if (ver->fwlrole == 1) - mode = wl->role_info_v1.link_mode; - else if (ver->fwlrole == 2) - mode = wl->role_info_v2.link_mode; - else if (ver->fwlrole == 7) - mode = wl->role_info_v7.link_mode; - else if (ver->fwlrole == 8) - mode = wl->role_info_v8.link_mode; - else - return; + if (ver->fwlrole == 10) { + if (wl->role_info.link_mode == BTC_WLINK_STA && + (dm->tdd_en && wl->rf_band_map[RTW89_MAC_0] & + BIT(RTW89_BAND_2G))) + mode = BTC_WLINK_V0_2G_STA; + } else { + mode = wl->role_info.link_mode_v0; + } if (bid == BTC_ALL_BT) { id_start = BTC_BT_1ST; @@ -7796,7 +7289,7 @@ static void _update_bt_scbd(struct rtw89_dev *rtwdev, u8 bid) if (((bt->link_info.a2dp_desc.exist || bt->link_info.pan_desc.exist || bt->link_info.hfp_desc.exist) && - mode == BTC_WLINK_2G_STA) || + mode == BTC_WLINK_V0_2G_STA) || bt->whql_test) lps_ctrl = true; @@ -7900,7 +7393,12 @@ static void _set_bind_info(struct rtw89_btc *btc, u8 type) * HW-Band is decided by wl->mlo_info.mrcx_act_hwb_map */ if (wl->mlo_info.wtype == RTW89_MR_WTYPE_MLD2L1R_NONMLD) { - /* TODO: Should patched WiFi mode & WiFi role patch */ + if (wl->role_info.link_mode != BTC_WLINK_DB_MCC) { /* mode chg */ + if (wl->mlo_info.mrcx_act_hwb_map == BIT(RTW89_MAC_1)) + path_hwb[BTC_RF_S0] = RTW89_PHY_1; /* S0/1->HWB1 */ + else + path_hwb[BTC_RF_S1] = RTW89_PHY_0; /* S0/1->HWB0 */ + } } else { /* * Dual-RF_band(HWB)TDMA if BT-profile is TDMA-type at both @@ -7933,7 +7431,22 @@ static void _set_bind_info(struct rtw89_btc *btc, u8 type) } } - /* TODO: Should patched WiFi mode & WiFi role patch */ + if (wl->mlo_info.wtype == RTW89_MR_WTYPE_MLD2L2R && + (bd->wl_hwb_sel == (BIT(RTW89_MAC_1) | BIT(RTW89_MAC_0)))) { + if (wl->role_info.link_mode == BTC_WLINK_AP || + wl->role_info.link_mode_hb1 == BTC_WLINK_AP) + bd->wl_link_mode = BTC_WLINK_DB_MCC; + else + bd->wl_link_mode = BTC_WLINK_STA; + bd->wl_bg_mode = wl->bg_mode | wl->bg_mode_hb1; + } else if (bd->wl_hwb_sel == (BIT(RTW89_MAC_1))) { + bd->wl_link_mode = wl->role_info.link_mode_hb1; + bd->wl_bg_mode = wl->bg_mode_hb1; + } else {/* HW-BAND-0 or no-hw-band */ + bd->wl_hwb_sel = BIT(RTW89_MAC_0); + bd->wl_link_mode = wl->role_info.link_mode; + bd->wl_bg_mode = wl->bg_mode; + } /* update Bind-BT status map for BT0/BT1 */ for (i = BTC_BT_1ST; i < BTC_ALL_BT_EZL; i++) { @@ -7966,9 +7479,9 @@ static void _set_bind_info(struct rtw89_btc *btc, u8 type) bd->bt_smap.a2dp_sink |= b->a2dp_desc.sink; bd->bt_smap.pan_active |= b->pan_desc.active; bd->bt_smap.connect |= b->status.map.connect; - bd->bt_smap.hid_cnt += (u8)b->hid_desc.pair_cnt; + bd->bt_smap.hid_cnt += b->hid_desc.pair_cnt; bd->bt_smap.hid_type |= b->hid_desc.type; - bd->bt_smap.cis_cnt += (u8)b->leaudio_desc.cis_cnt; + bd->bt_smap.cis_cnt += b->leaudio_desc.cis_cnt; bd->bt_smap.link_cnt += b->link_cnt.now; bd->bt_smap.inq_page |= b->status.map.inq_pag; bd->bt_smap.page |= b->pag; @@ -8028,7 +7541,7 @@ static void _set_coex_binding(struct rtw89_btc *btc) * ==> 1: interference, 0: no-interference * * fdm_map(Frequency-Division-Multiplexing): WL/BT RF_Band overlap-map - * 2bit-map: bit[1]:5GHz/6GHz, bit[0]:2.4GHz + * 2bit-map: bit[1]:5/6GHz, bit[0]:2.4GHz */ for (i = 0; i < RTW89_PHY_NUM; i++) { if (wl->rf_band_map[i] & BIT(RTW89_BAND_2G)) @@ -8050,9 +7563,7 @@ static void _set_coex_binding(struct rtw89_btc *btc) * In this case, BTC_RF_S0->HWB0, BTC_RF_S1->HWB1 */ if (wl->mlo_info.wtype == RTW89_MR_WTYPE_MLD2L1R_NONMLD) { - if (wl->role_info.link_mode != BTC_WLINK_2G_MCC && - wl->role_info.link_mode != BTC_WLINK_25G_MCC && - wl->role_info.link_mode != BTC_WLINK_25G_DBCC) {/* mode chg */ + if (wl->role_info.link_mode != BTC_WLINK_DB_MCC) {/* mode chg */ if (wl->mlo_info.mrcx_act_hwb_map == BIT(RTW89_PHY_1)) path_hwb[BTC_RF_S0] = RTW89_PHY_1;/* S0/1->HWB1 */ else @@ -8114,31 +7625,14 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_bt_info *bt = &btc->cx.bt0; - struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; - struct rtw89_btc_wl_role_info_v1 *wl_rinfo_v1 = &wl->role_info_v1; - struct rtw89_btc_wl_role_info_v2 *wl_rinfo_v2 = &wl->role_info_v2; - struct rtw89_btc_wl_role_info_v7 *wl_rinfo_v7 = &wl->role_info_v7; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo_v8 = &wl->role_info_v8; - u8 mode, igno_bt, always_freerun; + u8 mode = btc->dm.tdd_bind.wl_link_mode; + u8 mode_v0 = wl->role_info.link_mode_v0; + u8 igno_bt, always_freerun; lockdep_assert_wiphy(rtwdev->hw->wiphy); dm->run_reason = reason; _update_dm_step(rtwdev, reason); - _update_btc_state_map(rtwdev); - - if (ver->fwlrole == 0) - mode = wl_rinfo->link_mode; - else if (ver->fwlrole == 1) - mode = wl_rinfo_v1->link_mode; - else if (ver->fwlrole == 2) - mode = wl_rinfo_v2->link_mode; - else if (ver->fwlrole == 7) - mode = wl_rinfo_v7->link_mode; - else if (ver->fwlrole == 8) - mode = wl_rinfo_v8->link_mode; - else - return; if (ver->fcxctrl == 7) { igno_bt = btc->ctrl.ctrl_v7.igno_bt; @@ -8206,6 +7700,7 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) igno_bt = false; _set_coex_binding(btc); + _update_btc_state_map(rtwdev); dm->freerun_chk = _check_freerun(rtwdev); /* check if meet freerun */ @@ -8253,43 +7748,76 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) goto exit; } - switch (mode) { + if (mode == BTC_WLINK_NOLINK) { + mode_v0 = BTC_WLINK_V0_NOLINK; + } else if (btc->dm.tdd_bind.rf_band == BIT(RTW89_BAND_5G)) { + mode_v0 = BTC_WLINK_V0_5G; + } else if (btc->dm.tdd_bind.rf_band & BIT(RTW89_BAND_5G) && + btc->dm.tdd_bind.rf_band & BIT(RTW89_BAND_2G)) { + mode_v0 = BTC_WLINK_V0_25G_MCC; + } else if (btc->dm.tdd_bind.rf_band == BIT(RTW89_BAND_2G)) { + switch (mode) { + case BTC_WLINK_STA: + mode_v0 = BTC_WLINK_V0_2G_STA; + break; + case BTC_WLINK_AP: + mode_v0 = BTC_WLINK_V0_2G_AP; + break; + case BTC_WLINK_GO: + mode_v0 = BTC_WLINK_V0_2G_GO; + break; + case BTC_WLINK_GC: + mode_v0 = BTC_WLINK_V0_2G_GC; + break; + case BTC_WLINK_SCC: + mode_v0 = BTC_WLINK_V0_2G_SCC; + break; + case BTC_WLINK_SB_MCC: + mode_v0 = BTC_WLINK_V0_2G_MCC; + break; + default: + mode_v0 = BTC_WLINK_V0_OTHER; + break; + } + } + + switch (mode_v0) { case BTC_WLINK_NOLINK: _action_wl_nc(rtwdev); break; - case BTC_WLINK_2G_STA: + case BTC_WLINK_V0_2G_STA: if (wl->status.map.traffic_dir & BIT(RTW89_TFC_DL)) bt->scan_rx_low_pri = true; _action_wl_2g_sta(rtwdev); break; - case BTC_WLINK_2G_AP: + case BTC_WLINK_V0_2G_AP: bt->scan_rx_low_pri = true; _action_wl_2g_ap(rtwdev); break; - case BTC_WLINK_2G_GO: + case BTC_WLINK_V0_2G_GO: bt->scan_rx_low_pri = true; _action_wl_2g_go(rtwdev); break; - case BTC_WLINK_2G_GC: + case BTC_WLINK_V0_2G_GC: bt->scan_rx_low_pri = true; _action_wl_2g_gc(rtwdev); break; - case BTC_WLINK_2G_SCC: + case BTC_WLINK_V0_2G_SCC: bt->scan_rx_low_pri = true; _action_wl_2g_scc(rtwdev); break; - case BTC_WLINK_2G_MCC: + case BTC_WLINK_V0_2G_MCC: bt->scan_rx_low_pri = true; _action_wl_2g_mcc(rtwdev); break; - case BTC_WLINK_25G_MCC: + case BTC_WLINK_V0_25G_MCC: bt->scan_rx_low_pri = true; _action_wl_25g_mcc(rtwdev); break; - case BTC_WLINK_5G: + case BTC_WLINK_V0_5G: _action_wl_5g(rtwdev); break; - case BTC_WLINK_2G_NAN: + case BTC_WLINK_V0_2G_NAN: _action_wl_2g_nan(rtwdev); break; default: @@ -8678,22 +8206,6 @@ static u8 _update_bt_rssi_level(struct rtw89_dev *rtwdev, u8 rssi) return rssi_level; } -static void _update_zb_coex_tbl(struct rtw89_dev *rtwdev) -{ - u8 mode = rtwdev->btc.cx.wl.role_info.link_mode; - u32 zb_tbl0 = 0xda5a5a5a, zb_tbl1 = 0xda5a5a5a; - - if (mode == BTC_WLINK_5G || rtwdev->btc.dm.freerun) { - zb_tbl0 = 0xffffffff; - zb_tbl1 = 0xffffffff; - } else if (mode == BTC_WLINK_25G_MCC) { - zb_tbl0 = 0xffffffff; /* for E5G slot */ - zb_tbl1 = 0xda5a5a5a; /* for E2G slot */ - } - rtw89_write32(rtwdev, R_BTC_ZB_COEX_TBL_0, zb_tbl0); - rtw89_write32(rtwdev, R_BTC_ZB_COEX_TBL_1, zb_tbl1); -} - #define BT_PROFILE_PROTOCOL_MASK GENMASK(7, 4) static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) @@ -8842,11 +8354,10 @@ void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev, struct ieee80211_bss_conf *bss_conf; struct ieee80211_link_sta *link_sta; struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_wl_link_info r = {0}; struct rtw89_btc_wl_link_info *wlinfo = NULL; - u8 mode = 0, rlink_id, link_mode_ori, pta_req_mac_ori, wa_type; + u8 mode = 0; rcu_read_lock(); @@ -8894,66 +8405,32 @@ void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev, rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], wifi_role=%d\n", rtwvif_link->wifi_role); - r.role = rtwvif_link->wifi_role; - r.phy = rtwvif_link->phy_idx; - r.pid = rtwvif_link->port; - r.active = true; - r.connected = MLME_LINKED; - r.bcn_period = bss_conf->beacon_int; - r.dtim_period = bss_conf->dtim_period; - r.band = chan->band_type; - r.ch = chan->channel; - r.bw = chan->band_width; - r.chdef.band = chan->band_type; - r.chdef.center_ch = chan->channel; - r.chdef.bw = chan->band_width; - r.chdef.chan = chan->primary_channel; + wlinfo = &wl->rlink_info[rtwvif_link->port][rtwvif_link->phy_idx]; + + wlinfo->mode = mode; + wlinfo->role = rtwvif_link->wifi_role; + wlinfo->phy = rtwvif_link->phy_idx; + wlinfo->pid = rtwvif_link->port; + wlinfo->active = true; + wlinfo->connected = MLME_LINKED; + wlinfo->bcn_period = bss_conf->beacon_int; + wlinfo->dtim_period = bss_conf->dtim_period; + wlinfo->band = chan->band_type; + wlinfo->ch = chan->channel; + wlinfo->bw = chan->band_width; + wlinfo->chdef.band = chan->band_type; + wlinfo->chdef.center_ch = chan->channel; + wlinfo->chdef.bw = chan->band_width; + wlinfo->chdef.chan = chan->primary_channel; ether_addr_copy(r.mac_addr, rtwvif_link->mac_addr); rcu_read_unlock(); if (rtwsta_link && vif->type == NL80211_IFTYPE_STATION) - r.mac_id = rtwsta_link->mac_id; + wlinfo->mac_id = rtwsta_link->mac_id; btc->dm.cnt_notify[BTC_NCNT_ROLE_INFO]++; - wlinfo = &wl->link_info[r.pid]; - - if (ver->fwlrole == 0) { - *wlinfo = r; - _update_wl_info(rtwdev); - } else if (ver->fwlrole == 1) { - *wlinfo = r; - _update_wl_info_v1(rtwdev); - } else if (ver->fwlrole == 2) { - *wlinfo = r; - _update_wl_info_v2(rtwdev); - } else if (ver->fwlrole == 7) { - *wlinfo = r; - _update_wl_info_v7(rtwdev, r.pid); - } else if (ver->fwlrole == 8) { - rlink_id = rtwvif_link->mac_idx; - wlinfo = &wl->rlink_info[r.pid][rlink_id]; - *wlinfo = r; - link_mode_ori = wl->role_info_v8.link_mode; - pta_req_mac_ori = wl->pta_req_mac; - _update_wl_info_v8(rtwdev, r.pid, rlink_id, state); - - if (wl->role_info_v8.link_mode != link_mode_ori) { - wl->role_info_v8.link_mode_chg = 1; - if (ver->fcxinit == 7) - wa_type = btc->mdinfo.md_v7.wa_type; - else - wa_type = btc->mdinfo.md.wa_type; - - if (wa_type & BTC_WA_HFP_ZB) - _update_zb_coex_tbl(rtwdev); - } - - if (wl->pta_req_mac != pta_req_mac_ori) - wl->pta_reg_mac_chg = 1; - } - if (wlinfo->role == RTW89_WIFI_ROLE_STATION && wlinfo->connected == MLME_NO_LINK) btc->dm.leak_ap = 0; @@ -8967,6 +8444,8 @@ void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev, state == BTC_ROLE_MSTS_STA_CONN_END) wl->status.map._4way = false; + _update_wl_info(rtwdev, wlinfo); + _run_coex(rtwdev, BTC_RSN_NTFY_ROLE_INFO); } @@ -9150,15 +8629,14 @@ void __rtw89_btc_ntfy_wl_sta_iter(struct rtw89_vif_link *rtwvif_link, struct rtw89_dev *rtwdev = iter_data->rtwdev; struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_dm *dm = &btc->dm; - const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_wl_link_info *link_info = NULL; struct rtw89_traffic_stats *link_info_t = NULL; struct rtw89_traffic_stats *stats = &rtwvif->stats; const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc_wl_role_info *r; - struct rtw89_btc_wl_role_info_v1 *r1; u32 last_tx_rate, last_rx_rate; + u8 link = rtwvif_link->phy_idx; u16 last_tx_lvl, last_rx_lvl; u8 port = rtwvif_link->port; u8 rssi; @@ -9171,10 +8649,7 @@ void __rtw89_btc_ntfy_wl_sta_iter(struct rtw89_vif_link *rtwvif_link, rssi = ewma_rssi_read(&rtwsta_link->avg_rssi) >> RSSI_FACTOR; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], rssi=%d\n", rssi); - if (btc->ver->fwlrole != 8) - link_info = &wl->link_info[port]; - else - link_info = &wl->rlink_info[port][rtwvif_link->mac_idx]; + link_info = &wl->rlink_info[port][link]; link_info->stat.traffic = *stats; link_info_t = &link_info->stat.traffic; @@ -9214,7 +8689,6 @@ void __rtw89_btc_ntfy_wl_sta_iter(struct rtw89_vif_link *rtwvif_link, else dir = RTW89_TFC_DL; - link_info = &wl->link_info[port]; if (link_info->busy != busy || link_info->dir != dir) { is_sta_change = true; link_info->busy = busy; @@ -9244,19 +8718,11 @@ void __rtw89_btc_ntfy_wl_sta_iter(struct rtw89_vif_link *rtwvif_link, dm->trx_info.rx_rate = link_info_t->rx_rate; } - if (ver->fwlrole == 0) { - r = &wl->role_info; - r->active_role[port].tx_lvl = stats->tx_tfc_lv; - r->active_role[port].rx_lvl = stats->rx_tfc_lv; - r->active_role[port].tx_rate = rtwsta_link->ra_report.hw_rate; - r->active_role[port].rx_rate = rtwsta_link->rx_hw_rate; - } else if (ver->fwlrole == 1) { - r1 = &wl->role_info_v1; - r1->active_role_v1[port].tx_lvl = stats->tx_tfc_lv; - r1->active_role_v1[port].rx_lvl = stats->rx_tfc_lv; - r1->active_role_v1[port].tx_rate = rtwsta_link->ra_report.hw_rate; - r1->active_role_v1[port].rx_rate = rtwsta_link->rx_hw_rate; - } + r = &wl->role_info; + r->rlink[port][link].tx_lvl = stats->tx_tfc_lv; + r->rlink[port][link].rx_lvl = stats->rx_tfc_lv; + r->rlink[port][link].tx_rate = rtwsta_link->ra_report.hw_rate; + r->rlink[port][link].rx_rate = rtwsta_link->rx_hw_rate; dm->trx_info.tx_lvl = stats->tx_tfc_lv; dm->trx_info.rx_lvl = stats->rx_tfc_lv; @@ -9584,10 +9050,7 @@ static int _show_wl_role_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) for (i = 0; i < btc->ver->max_role_num; i++) { for (j = 0; j < RTW89_MAC_NUM; j++) { - if (btc->ver->fwlrole == 8) - plink = &btc->cx.wl.rlink_info[i][j]; - else - plink = &btc->cx.wl.link_info[i]; + plink = &btc->cx.wl.rlink_info[i][j]; if (!plink->active) continue; @@ -9630,14 +9093,9 @@ static int _show_wl_role_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) static int _show_wl_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) { struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &cx->wl; struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; - struct rtw89_btc_wl_role_info_v1 *wl_rinfo_v1 = &wl->role_info_v1; - struct rtw89_btc_wl_role_info_v2 *wl_rinfo_v2 = &wl->role_info_v2; - struct rtw89_btc_wl_role_info_v7 *wl_rinfo_v7 = &wl->role_info_v7; - struct rtw89_btc_wl_role_info_v8 *wl_rinfo_v8 = &wl->role_info_v8; char *p = buf, *end = buf + bufsz; u8 mode; @@ -9646,18 +9104,7 @@ static int _show_wl_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, "========== [WL Status] ==========\n"); - if (ver->fwlrole == 0) - mode = wl_rinfo->link_mode; - else if (ver->fwlrole == 1) - mode = wl_rinfo_v1->link_mode; - else if (ver->fwlrole == 2) - mode = wl_rinfo_v2->link_mode; - else if (ver->fwlrole == 7) - mode = wl_rinfo_v7->link_mode; - else if (ver->fwlrole == 8) - mode = wl_rinfo_v8->link_mode; - else - goto out; + mode = wl_rinfo->link_mode; p += scnprintf(p, end - p, " %-15s : link_mode:%s, ", "[status]", id_to_linkmode(mode)); @@ -9677,7 +9124,6 @@ static int _show_wl_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += _show_wl_role_info(rtwdev, p, end - p); -out: return p - buf; } @@ -9755,6 +9201,8 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) union rtw89_btc_module_info *md = &btc->mdinfo; s8 br_dbm = bt->link_info.bt_txpwr_desc.br_dbm; s8 le_dbm = bt->link_info.bt_txpwr_desc.le_dbm; + u8 hw_band = wl->role_info.pta_req_band; + struct rtw89_btc_wl_afh_info *wl_afh; char *p = buf, *end = buf + bufsz; u8 *afh = bt_linfo->afh_map; u8 *afh_le = bt_linfo->afh_map_le; @@ -9822,8 +9270,9 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) afh_le[0], afh_le[1], afh_le[2], afh_le[3], afh_le[4]); - p += scnprintf(p, end - p, "wl_ch_map[en:%d/ch:%d/bw:%d]\n", - wl->afh_info.en, wl->afh_info.ch, wl->afh_info.bw); + wl_afh = &wl->afh_info[hw_band][RTW89_BAND_2G]; + p += scnprintf(p, end - p, "wl_ch_map[hwb:%d/en:%d/ch:%d/bw:%d/rf_band:%d]\n", + hw_band, wl_afh->en, wl_afh->ch, wl_afh->bw, wl_afh->band); p += scnprintf(p, end - p, " %-15s : retry:%d, relink:%d, rate_chg:%d, reinit:%d, reenable:%d, ", diff --git a/drivers/net/wireless/realtek/rtw89/coex.h b/drivers/net/wireless/realtek/rtw89/coex.h index 74027ca4eccc..c127bd80d31c 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.h +++ b/drivers/net/wireless/realtek/rtw89/coex.h @@ -11,6 +11,40 @@ #define BTC_TLV_SLOT_ID_LEN_V7 1 #define BTC_SLOT_REQ_TH 2 +#define BTC_FREQ_W2G 2412 +#define BTC_FREQ_W5G 5005 +#define BTC_FREQ_W6G 5955 + +#define BTC_FREQ_B2G 2402 +#define BTC_FREQ_B6G 5125 + +#define BTC_FREQ_W2G_CH14 2484 + +#define BTC_LO_2G_DNM 125 /* LO Coefficient Denominator */ +#define BTC_LO_5G_DNM 125 +#define BTC_LO_6G_DNM 150 + +#define BTC_LO_2G_NMR 400 /* LO Coefficient Numerator */ +#define BTC_LO_5G_NMR 200 +#define BTC_LO_6G_NMR 200 + +#define BTC_CH_W2G_MIN 1 /* start from 2412MHz */ +#define BTC_CH_W2G_MAX 14 +#define BTC_CH_W5G_MIN 1 /* start from 5005MHz */ +#define BTC_CH_W5G_MAX 165 +#define BTC_CH_W6G_MIN 1 /* start from 5955MHz */ +#define BTC_CH_W6G_MAX 233 + +#define BTC_CH_B2G_MIN 0 /* start from 2402MHz */ +#define BTC_CH_B2G_MAX 78 + +#define BTC_CH_B5G_MIN 600 /* start from 5725MHz */ +#define BTC_CH_B5G_MAX 1300 + +#define BTC_VCO_GUARD_W2W 10 /* WiFi to WiFi VCO freq forbiddened range (MHZ) */ +#define BTC_VCO_GUARD_W2B 25 /* WiFi to WiFi VCO freq forbiddened range (MHZ) */ +#define BTC_VCO_GUARD_B2B 10 /* WiFi to WiFi VCO freq forbiddened range (MHZ) */ + enum btc_mode { BTC_MODE_NORMAL, BTC_MODE_WL, diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 174349c6bc58..ded788534651 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -1854,7 +1854,7 @@ union rtw89_btc_wl_role_info_map { struct rtw89_btc_wl_role_info_bpos role; }; -struct rtw89_btc_wl_role_info { /* struct size must be n*4 bytes */ +struct rtw89_btc_wl_role_info_v0 { /* struct size must be n*4 bytes */ u8 connect_cnt; u8 link_mode; union rtw89_btc_wl_role_info_map role_map; @@ -1891,7 +1891,7 @@ struct rtw89_btc_wl_role_info_v2 { /* struct size must be n*4 bytes */ u32 rsvd: 27; }; -struct rtw89_btc_wl_rlink { /* H2C info, struct size must be n*4 bytes */ +struct rtw89_btc_wl_rlink_v0 { /* H2C info, struct size must be n*4 bytes */ u8 connected; u8 pid; u8 phy; @@ -1908,6 +1908,28 @@ struct rtw89_btc_wl_rlink { /* H2C info, struct size must be n*4 bytes */ u8 mode; /* wifi protocol */ } __packed; +struct rtw89_btc_wl_rlink_v10 { /* H2C info, struct size must be n*4 bytes */ + u8 connected; + u8 pid; + u8 phy; + u8 noa; + + u8 rf_band; /* enum band_type RF band: 2.4G/5G/6G */ + u8 active; /* 0:rlink is under doze */ + u8 bw; /* enum channel_width */ + u8 role; /*enum role_type */ + + u8 ch; + u8 noa_dur; /* ms */ + u8 client_cnt; /* for Role = P2P-Go/AP */ + u8 mode; /* wifi protocol */ + + u8 mac_id; + u8 rsvd0; + u8 rsvd1; + u8 rsvd2; +} __packed; + #define RTW89_BE_BTC_WL_MAX_ROLE_NUMBER 6 struct rtw89_btc_wl_role_info_v7 { /* struct size must be n*4 bytes */ u8 connect_cnt; @@ -1917,12 +1939,12 @@ struct rtw89_btc_wl_role_info_v7 { /* struct size must be n*4 bytes */ struct rtw89_btc_wl_active_role_v7 active_role[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER]; - u32 role_map; - u32 mrole_type; /* btc_wl_mrole_type */ - u32 mrole_noa_duration; /* ms */ - u32 dbcc_en; - u32 dbcc_chg; - u32 dbcc_2g_phy; /* which phy operate in 2G, HW_PHY_0 or HW_PHY_1 */ + __le32 role_map; + __le32 mrole_type; /* btc_wl_mrole_type */ + __le32 mrole_noa_duration; /* ms */ + __le32 dbcc_en; + __le32 dbcc_chg; + __le32 dbcc_2g_phy; /* which phy operate in 2G, HW_PHY_0 or HW_PHY_1 */ } __packed; struct rtw89_btc_wl_role_info_v8 { /* H2C info, struct size must be n*4 bytes */ @@ -1936,12 +1958,90 @@ struct rtw89_btc_wl_role_info_v8 { /* H2C info, struct size must be n*4 bytes */ u8 dbcc_chg; u8 dbcc_2g_phy; /* which phy operate in 2G, HW_PHY_0 or HW_PHY_1 */ + struct rtw89_btc_wl_rlink_v0 rlink[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER][RTW89_MAC_NUM]; + + __le32 role_map; + __le32 mrole_type; /* btc_wl_mrole_type */ + __le32 mrole_noa_duration; /* ms */ +} __packed; + +struct rtw89_btc_wl_role_info_v10 { /* H2C info, struct size must be n*4 bytes */ + struct rtw89_btc_wl_rlink_v10 rlink[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER][RTW89_MAC_NUM]; + u8 link_mode; + u8 link_mode_hb1; + u8 p2p_exist; + u8 p2p_exist_hb1; + + u8 pta_req_band; + u8 dbcc_en; /* 1+1 and 2.4G-included */ + u8 dbcc_2g_phy; /* which phy operate in 2G, HW_PHY_0 or HW_PHY_1 */ + u8 rsvd; + + __le32 role_map; + __le32 role_map_hb1; + __le32 mrole_type; /* btc_wl_mrole_type: [31:16]:band1, [15:0]:band0 */ +} __packed; + +struct rtw89_btc_wl_rlink { /* Logic dynamic using */ + u8 connected; + u8 pid; + u8 phy; + u8 noa; + + u8 rf_band; /* enum band_type RF band: 2.4G/5G/6G */ + u8 active; /* 0:rlink is under doze */ + u8 bw; /* enum channel_width */ + u8 role; /*enum role_type */ + + u8 ch; + u8 noa_dur; /* ms */ + u8 client_cnt; /* for Role = P2P-Go/AP */ + u8 mode; /* wifi protocol */ + + /*v0 v1*/ + u16 tx_lvl; + u16 rx_lvl; + u16 tx_rate; + u16 rx_rate; + + /* v7 */ + u8 client_ps; /*v7 v2 v1 v0*/ + + /* v10 */ + u8 mac_id; + u8 rsvd0; + u8 rsvd1; + u8 rsvd2; +}; + +struct rtw89_btc_wl_role_info { /* Logic dynamic using */ struct rtw89_btc_wl_rlink rlink[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER][RTW89_MAC_NUM]; + u8 link_mode; + u8 link_mode_hb1; + u8 p2p_exist; + u8 p2p_exist_hb1; + + u8 pta_req_band; + u8 dbcc_en; /* 1+1 and 2.4G-included */ + u8 dbcc_2g_phy; /* which phy operate in 2G, HW_PHY_0 or HW_PHY_1 */ + u8 rsvd; u32 role_map; - u32 mrole_type; /* btc_wl_mrole_type */ - u32 mrole_noa_duration; /* ms */ -} __packed; + u32 role_map_hb1; + u32 mrole_type; /* btc_wl_mrole_type: [31:16]:band1, [15:0]:band0 */ + + /* Before v10 use this linkmode */ + u8 link_mode_v0; + + /* v7 */ + u8 connect_cnt; + u32 dbcc_chg; /* v7 v2*/ + + /* v8 */ + u8 link_mode_chg; /* v8, v7 v2*/ + u8 p2p_2g; /* v8, v7 */ + u32 mrole_noa_duration; /* v8, v7 v2*/ +}; struct rtw89_btc_wl_ver_info { u32 fw_coex; /* match with which coex_ver */ @@ -1955,7 +2055,7 @@ struct rtw89_btc_wl_afh_info { u8 en; u8 ch; u8 bw; - u8 rsvd; + u8 band; } __packed; struct rtw89_btc_wl_rfk_info { @@ -2167,16 +2267,13 @@ struct rtw89_btc_wl_nhm { }; struct rtw89_btc_wl_info { - struct rtw89_btc_wl_link_info link_info[RTW89_PORT_NUM]; struct rtw89_btc_wl_link_info rlink_info[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER][RTW89_MAC_NUM]; + struct rtw89_btc_chdef rf_ch_info[RTW89_PHY_NUM]; struct rtw89_btc_wl_rfk_info rfk_info; - struct rtw89_btc_wl_ver_info ver_info; - struct rtw89_btc_wl_afh_info afh_info; + struct rtw89_btc_wl_ver_info ver_info; + struct rtw89_btc_wl_afh_info afh_info[RTW89_MAC_NUM][RTW89_BAND_NUM]; + struct rtw89_btc_wl_afh_info afh_info_last[RTW89_MAC_NUM][RTW89_BAND_NUM]; struct rtw89_btc_wl_role_info role_info; - struct rtw89_btc_wl_role_info_v1 role_info_v1; - struct rtw89_btc_wl_role_info_v2 role_info_v2; - struct rtw89_btc_wl_role_info_v7 role_info_v7; - struct rtw89_btc_wl_role_info_v8 role_info_v8; struct rtw89_btc_wl_scan_info scan_info; struct rtw89_btc_wl_dbcc_info dbcc_info; struct rtw89_btc_wl_mlo_info mlo_info; @@ -2191,12 +2288,18 @@ struct rtw89_btc_wl_info { u8 pta_req_mac; u8 bt_polut_type[RTW89_PHY_NUM]; /* BT polluted WL-Tx type for phy0/1 */ u8 rf_band_map[RTW89_PHY_NUM]; /* rf_band bit-map */ + u8 ch_map[12]; + u8 ch_map_le[5]; - bool is_5g_hi_channel; + bool is_5g_hi_ch; + bool is_5g_hi_ch_hb1; bool go_client_exist; + bool go_client_exist_hb1; bool noa_exist; + bool noa_exist_hb1; bool pta_reg_mac_chg; bool bg_mode; + bool bg_mode_hb1; bool he_mode; bool scbd_chg[BTC_ALL_BT]; bool fw_ver_mismatch; @@ -3280,6 +3383,14 @@ struct rtw89_btc_fbtc_outsrc_set_info { u8 rf_gbt_source; u8 bt_enable_state; u8 wl_btg_standby_chg; + /* bit[15]-> 0:2G/1:5G,6G, bit[14:0]-> WL HWBx ch freq in MHz */ + /* forbidden group-> bit[1]:fbd rf-band, bit[0]: fbd enable */ + u8 fbd_group_en[RTW89_MAC_NUM][2]; /* HWB0/1 +.Group0/1 */ + u16 rf_center_freq[RTW89_MAC_NUM]; /* HWB0/1 */ + /* forbidden group boundary: [15:8]->UP, [7:0]->LO */ + u16 fbd_group_bound[RTW89_MAC_NUM][2]; /* HWB0/1 +.Group0/1 */ + /* 11-bit in MHz, freq diff threshold */ + u16 freq_diff_thres[RTW89_MAC_NUM][BTC_ALL_BT_EZL]; /* HWB0/1 vs.BT0/1/2 */ } __packed; union rtw89_btc_fbtc_slot_u { @@ -3363,6 +3474,7 @@ struct rtw89_btc_dm { u8 lps_ctrl_change: 1; u8 scbd_write_instant; bool scbd_b2w_update; + bool pre_agc_chg; }; struct rtw89_btc_ctrl { @@ -4961,6 +5073,7 @@ struct rtw89_chip_info { u8 mailbox; u8 afh_guard_ch; + u16 fdd_iso_freq; const u8 *wl_rssi_thres; const u8 *bt_rssi_thres; u8 rssi_tol; diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 0e7168605850..41c033d2ae7b 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -5922,14 +5922,14 @@ int rtw89_fw_h2c_cxdrv_init_v10(struct rtw89_dev *rtwdev, u8 type) int rtw89_fw_h2c_cxdrv_role(struct rtw89_dev *rtwdev, u8 type) { struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_role_info *role_info = &wl->role_info; - struct rtw89_btc_wl_role_info_bpos *bpos = &role_info->role_map.role; - struct rtw89_btc_wl_active_role *active = role_info->active_role; + struct rtw89_btc_wl_role_info *r = &wl->role_info; + const struct rtw89_btc_ver *ver = btc->ver; + struct rtw89_btc_wl_rlink *rl; struct sk_buff *skb; - u32 len; + u32 rmap = r->role_map; u8 offset = 0; + u32 len; u8 *cmd; int ret; int i; @@ -5947,36 +5947,37 @@ int rtw89_fw_h2c_cxdrv_role(struct rtw89_dev *rtwdev, u8 type) RTW89_SET_FWCMD_CXHDR_TYPE(cmd, type); RTW89_SET_FWCMD_CXHDR_LEN(cmd, len - H2C_LEN_CXDRVHDR); - RTW89_SET_FWCMD_CXROLE_CONNECT_CNT(cmd, role_info->connect_cnt); - RTW89_SET_FWCMD_CXROLE_LINK_MODE(cmd, role_info->link_mode); + RTW89_SET_FWCMD_CXROLE_CONNECT_CNT(cmd, r->connect_cnt); + RTW89_SET_FWCMD_CXROLE_LINK_MODE(cmd, r->link_mode); - RTW89_SET_FWCMD_CXROLE_ROLE_NONE(cmd, bpos->none); - RTW89_SET_FWCMD_CXROLE_ROLE_STA(cmd, bpos->station); - RTW89_SET_FWCMD_CXROLE_ROLE_AP(cmd, bpos->ap); - RTW89_SET_FWCMD_CXROLE_ROLE_VAP(cmd, bpos->vap); - RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC(cmd, bpos->adhoc); - RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC_MASTER(cmd, bpos->adhoc_master); - RTW89_SET_FWCMD_CXROLE_ROLE_MESH(cmd, bpos->mesh); - RTW89_SET_FWCMD_CXROLE_ROLE_MONITOR(cmd, bpos->moniter); - RTW89_SET_FWCMD_CXROLE_ROLE_P2P_DEV(cmd, bpos->p2p_device); - RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GC(cmd, bpos->p2p_gc); - RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GO(cmd, bpos->p2p_go); - RTW89_SET_FWCMD_CXROLE_ROLE_NAN(cmd, bpos->nan); + RTW89_SET_FWCMD_CXROLE_ROLE_NONE(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_NONE))); + RTW89_SET_FWCMD_CXROLE_ROLE_STA(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_STATION))); + RTW89_SET_FWCMD_CXROLE_ROLE_AP(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_AP))); + RTW89_SET_FWCMD_CXROLE_ROLE_VAP(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_AP_VLAN))); + RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_ADHOC))); + RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC_MASTER(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_ADHOC_MASTER))); + RTW89_SET_FWCMD_CXROLE_ROLE_MESH(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_MESH_POINT))); + RTW89_SET_FWCMD_CXROLE_ROLE_MONITOR(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_MONITOR))); + RTW89_SET_FWCMD_CXROLE_ROLE_P2P_DEV(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_P2P_DEVICE))); + RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GC(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_P2P_CLIENT))); + RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GO(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_P2P_GO))); + RTW89_SET_FWCMD_CXROLE_ROLE_NAN(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_NAN))); - for (i = 0; i < RTW89_PORT_NUM; i++, active++) { - RTW89_SET_FWCMD_CXROLE_ACT_CONNECTED(cmd, active->connected, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_PID(cmd, active->pid, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_PHY(cmd, active->phy, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_NOA(cmd, active->noa, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_BAND(cmd, active->band, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_CLIENT_PS(cmd, active->client_ps, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_BW(cmd, active->bw, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_ROLE(cmd, active->role, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_CH(cmd, active->ch, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_TX_LVL(cmd, active->tx_lvl, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_RX_LVL(cmd, active->rx_lvl, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_TX_RATE(cmd, active->tx_rate, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_RX_RATE(cmd, active->rx_rate, i, offset); + for (i = 0; i < RTW89_PORT_NUM; i++) { + rl = &r->rlink[i][RTW89_MAC_0]; + RTW89_SET_FWCMD_CXROLE_ACT_CONNECTED(cmd, rl->connected, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_PID(cmd, rl->pid, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_PHY(cmd, rl->phy, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_NOA(cmd, rl->noa, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_BAND(cmd, rl->rf_band, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_CLIENT_PS(cmd, rl->client_ps, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_BW(cmd, rl->bw, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_ROLE(cmd, rl->role, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_CH(cmd, rl->ch, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_TX_LVL(cmd, rl->tx_lvl, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_RX_LVL(cmd, rl->rx_lvl, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_TX_RATE(cmd, rl->tx_rate, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_RX_RATE(cmd, rl->rx_rate, i, offset); } rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, @@ -6005,12 +6006,12 @@ int rtw89_fw_h2c_cxdrv_role_v1(struct rtw89_dev *rtwdev, u8 type) struct rtw89_btc *btc = &rtwdev->btc; const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_role_info_v1 *role_info = &wl->role_info_v1; - struct rtw89_btc_wl_role_info_bpos *bpos = &role_info->role_map.role; - struct rtw89_btc_wl_active_role_v1 *active = role_info->active_role_v1; + struct rtw89_btc_wl_role_info *r = &wl->role_info; + struct rtw89_btc_wl_rlink *rl; struct sk_buff *skb; - u32 len; + u32 rmap = r->role_map; u8 *cmd, offset; + u32 len; int ret; int i; @@ -6027,47 +6028,49 @@ int rtw89_fw_h2c_cxdrv_role_v1(struct rtw89_dev *rtwdev, u8 type) RTW89_SET_FWCMD_CXHDR_TYPE(cmd, type); RTW89_SET_FWCMD_CXHDR_LEN(cmd, len - H2C_LEN_CXDRVHDR); - RTW89_SET_FWCMD_CXROLE_CONNECT_CNT(cmd, role_info->connect_cnt); - RTW89_SET_FWCMD_CXROLE_LINK_MODE(cmd, role_info->link_mode); + RTW89_SET_FWCMD_CXROLE_CONNECT_CNT(cmd, r->connect_cnt); + RTW89_SET_FWCMD_CXROLE_LINK_MODE(cmd, r->link_mode); - RTW89_SET_FWCMD_CXROLE_ROLE_NONE(cmd, bpos->none); - RTW89_SET_FWCMD_CXROLE_ROLE_STA(cmd, bpos->station); - RTW89_SET_FWCMD_CXROLE_ROLE_AP(cmd, bpos->ap); - RTW89_SET_FWCMD_CXROLE_ROLE_VAP(cmd, bpos->vap); - RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC(cmd, bpos->adhoc); - RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC_MASTER(cmd, bpos->adhoc_master); - RTW89_SET_FWCMD_CXROLE_ROLE_MESH(cmd, bpos->mesh); - RTW89_SET_FWCMD_CXROLE_ROLE_MONITOR(cmd, bpos->moniter); - RTW89_SET_FWCMD_CXROLE_ROLE_P2P_DEV(cmd, bpos->p2p_device); - RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GC(cmd, bpos->p2p_gc); - RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GO(cmd, bpos->p2p_go); - RTW89_SET_FWCMD_CXROLE_ROLE_NAN(cmd, bpos->nan); + RTW89_SET_FWCMD_CXROLE_ROLE_NONE(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_NONE))); + RTW89_SET_FWCMD_CXROLE_ROLE_STA(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_STATION))); + RTW89_SET_FWCMD_CXROLE_ROLE_AP(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_AP))); + RTW89_SET_FWCMD_CXROLE_ROLE_VAP(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_AP_VLAN))); + RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_ADHOC))); + RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC_MASTER(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_ADHOC_MASTER))); + RTW89_SET_FWCMD_CXROLE_ROLE_MESH(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_MESH_POINT))); + RTW89_SET_FWCMD_CXROLE_ROLE_MONITOR(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_MONITOR))); + RTW89_SET_FWCMD_CXROLE_ROLE_P2P_DEV(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_P2P_DEVICE))); + RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GC(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_P2P_CLIENT))); + RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GO(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_P2P_GO))); + RTW89_SET_FWCMD_CXROLE_ROLE_NAN(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_NAN))); offset = PORT_DATA_OFFSET; - for (i = 0; i < RTW89_PORT_NUM; i++, active++) { - RTW89_SET_FWCMD_CXROLE_ACT_CONNECTED(cmd, active->connected, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_PID(cmd, active->pid, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_PHY(cmd, active->phy, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_NOA(cmd, active->noa, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_BAND(cmd, active->band, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_CLIENT_PS(cmd, active->client_ps, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_BW(cmd, active->bw, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_ROLE(cmd, active->role, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_CH(cmd, active->ch, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_TX_LVL(cmd, active->tx_lvl, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_RX_LVL(cmd, active->rx_lvl, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_TX_RATE(cmd, active->tx_rate, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_RX_RATE(cmd, active->rx_rate, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_NOA_DUR(cmd, active->noa_duration, i, offset); + + for (i = 0; i < RTW89_PORT_NUM; i++) { + rl = &r->rlink[i][RTW89_MAC_0]; + RTW89_SET_FWCMD_CXROLE_ACT_CONNECTED(cmd, rl->connected, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_PID(cmd, rl->pid, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_PHY(cmd, rl->phy, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_NOA(cmd, rl->noa, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_BAND(cmd, rl->rf_band, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_CLIENT_PS(cmd, rl->client_ps, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_BW(cmd, rl->bw, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_ROLE(cmd, rl->role, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_CH(cmd, rl->ch, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_TX_LVL(cmd, rl->tx_lvl, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_RX_LVL(cmd, rl->rx_lvl, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_TX_RATE(cmd, rl->tx_rate, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_RX_RATE(cmd, rl->rx_rate, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_NOA_DUR(cmd, rl->noa_dur, i, offset); } offset = len - H2C_LEN_CXDRVINFO_ROLE_DBCC_LEN; - RTW89_SET_FWCMD_CXROLE_MROLE_TYPE(cmd, role_info->mrole_type, offset); - RTW89_SET_FWCMD_CXROLE_MROLE_NOA(cmd, role_info->mrole_noa_duration, offset); - RTW89_SET_FWCMD_CXROLE_DBCC_EN(cmd, role_info->dbcc_en, offset); - RTW89_SET_FWCMD_CXROLE_DBCC_CHG(cmd, role_info->dbcc_chg, offset); - RTW89_SET_FWCMD_CXROLE_DBCC_2G_PHY(cmd, role_info->dbcc_2g_phy, offset); - RTW89_SET_FWCMD_CXROLE_LINK_MODE_CHG(cmd, role_info->link_mode_chg, offset); + RTW89_SET_FWCMD_CXROLE_MROLE_TYPE(cmd, r->mrole_type, offset); + RTW89_SET_FWCMD_CXROLE_MROLE_NOA(cmd, r->mrole_noa_duration, offset); + RTW89_SET_FWCMD_CXROLE_DBCC_EN(cmd, r->dbcc_en, offset); + RTW89_SET_FWCMD_CXROLE_DBCC_CHG(cmd, r->dbcc_chg, offset); + RTW89_SET_FWCMD_CXROLE_DBCC_2G_PHY(cmd, r->dbcc_2g_phy, offset); + RTW89_SET_FWCMD_CXROLE_LINK_MODE_CHG(cmd, r->link_mode_chg, offset); rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_OUTSRC, BTFC_SET, @@ -6095,10 +6098,10 @@ int rtw89_fw_h2c_cxdrv_role_v2(struct rtw89_dev *rtwdev, u8 type) struct rtw89_btc *btc = &rtwdev->btc; const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_role_info_v2 *role_info = &wl->role_info_v2; - struct rtw89_btc_wl_role_info_bpos *bpos = &role_info->role_map.role; - struct rtw89_btc_wl_active_role_v2 *active = role_info->active_role_v2; + struct rtw89_btc_wl_role_info *r = &wl->role_info; + struct rtw89_btc_wl_rlink *rl; struct sk_buff *skb; + u32 rmap = r->role_map; u32 len; u8 *cmd, offset; int ret; @@ -6117,43 +6120,45 @@ int rtw89_fw_h2c_cxdrv_role_v2(struct rtw89_dev *rtwdev, u8 type) RTW89_SET_FWCMD_CXHDR_TYPE(cmd, type); RTW89_SET_FWCMD_CXHDR_LEN(cmd, len - H2C_LEN_CXDRVHDR); - RTW89_SET_FWCMD_CXROLE_CONNECT_CNT(cmd, role_info->connect_cnt); - RTW89_SET_FWCMD_CXROLE_LINK_MODE(cmd, role_info->link_mode); + RTW89_SET_FWCMD_CXROLE_CONNECT_CNT(cmd, r->connect_cnt); + RTW89_SET_FWCMD_CXROLE_LINK_MODE(cmd, r->link_mode); - RTW89_SET_FWCMD_CXROLE_ROLE_NONE(cmd, bpos->none); - RTW89_SET_FWCMD_CXROLE_ROLE_STA(cmd, bpos->station); - RTW89_SET_FWCMD_CXROLE_ROLE_AP(cmd, bpos->ap); - RTW89_SET_FWCMD_CXROLE_ROLE_VAP(cmd, bpos->vap); - RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC(cmd, bpos->adhoc); - RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC_MASTER(cmd, bpos->adhoc_master); - RTW89_SET_FWCMD_CXROLE_ROLE_MESH(cmd, bpos->mesh); - RTW89_SET_FWCMD_CXROLE_ROLE_MONITOR(cmd, bpos->moniter); - RTW89_SET_FWCMD_CXROLE_ROLE_P2P_DEV(cmd, bpos->p2p_device); - RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GC(cmd, bpos->p2p_gc); - RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GO(cmd, bpos->p2p_go); - RTW89_SET_FWCMD_CXROLE_ROLE_NAN(cmd, bpos->nan); + RTW89_SET_FWCMD_CXROLE_ROLE_NONE(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_NONE))); + RTW89_SET_FWCMD_CXROLE_ROLE_STA(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_STATION))); + RTW89_SET_FWCMD_CXROLE_ROLE_AP(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_AP))); + RTW89_SET_FWCMD_CXROLE_ROLE_VAP(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_AP_VLAN))); + RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_ADHOC))); + RTW89_SET_FWCMD_CXROLE_ROLE_ADHOC_MASTER(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_ADHOC_MASTER))); + RTW89_SET_FWCMD_CXROLE_ROLE_MESH(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_MESH_POINT))); + RTW89_SET_FWCMD_CXROLE_ROLE_MONITOR(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_MONITOR))); + RTW89_SET_FWCMD_CXROLE_ROLE_P2P_DEV(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_P2P_DEVICE))); + RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GC(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_P2P_CLIENT))); + RTW89_SET_FWCMD_CXROLE_ROLE_P2P_GO(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_P2P_GO))); + RTW89_SET_FWCMD_CXROLE_ROLE_NAN(cmd, !!(rmap & BIT(RTW89_WIFI_ROLE_NAN))); offset = PORT_DATA_OFFSET; - for (i = 0; i < RTW89_PORT_NUM; i++, active++) { - RTW89_SET_FWCMD_CXROLE_ACT_CONNECTED_V2(cmd, active->connected, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_PID_V2(cmd, active->pid, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_PHY_V2(cmd, active->phy, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_NOA_V2(cmd, active->noa, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_BAND_V2(cmd, active->band, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_CLIENT_PS_V2(cmd, active->client_ps, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_BW_V2(cmd, active->bw, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_ROLE_V2(cmd, active->role, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_CH_V2(cmd, active->ch, i, offset); - RTW89_SET_FWCMD_CXROLE_ACT_NOA_DUR_V2(cmd, active->noa_duration, i, offset); + + for (i = 0; i < RTW89_PORT_NUM; i++) { + rl = &r->rlink[i][RTW89_MAC_0]; + RTW89_SET_FWCMD_CXROLE_ACT_CONNECTED_V2(cmd, rl->connected, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_PID_V2(cmd, rl->pid, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_PHY_V2(cmd, rl->phy, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_NOA_V2(cmd, rl->noa, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_BAND_V2(cmd, rl->rf_band, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_CLIENT_PS_V2(cmd, rl->client_ps, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_BW_V2(cmd, rl->bw, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_ROLE_V2(cmd, rl->role, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_CH_V2(cmd, rl->ch, i, offset); + RTW89_SET_FWCMD_CXROLE_ACT_NOA_DUR_V2(cmd, rl->noa_dur, i, offset); } offset = len - H2C_LEN_CXDRVINFO_ROLE_DBCC_LEN; - RTW89_SET_FWCMD_CXROLE_MROLE_TYPE(cmd, role_info->mrole_type, offset); - RTW89_SET_FWCMD_CXROLE_MROLE_NOA(cmd, role_info->mrole_noa_duration, offset); - RTW89_SET_FWCMD_CXROLE_DBCC_EN(cmd, role_info->dbcc_en, offset); - RTW89_SET_FWCMD_CXROLE_DBCC_CHG(cmd, role_info->dbcc_chg, offset); - RTW89_SET_FWCMD_CXROLE_DBCC_2G_PHY(cmd, role_info->dbcc_2g_phy, offset); - RTW89_SET_FWCMD_CXROLE_LINK_MODE_CHG(cmd, role_info->link_mode_chg, offset); + RTW89_SET_FWCMD_CXROLE_MROLE_TYPE(cmd, r->mrole_type, offset); + RTW89_SET_FWCMD_CXROLE_MROLE_NOA(cmd, r->mrole_noa_duration, offset); + RTW89_SET_FWCMD_CXROLE_DBCC_EN(cmd, r->dbcc_en, offset); + RTW89_SET_FWCMD_CXROLE_DBCC_CHG(cmd, r->dbcc_chg, offset); + RTW89_SET_FWCMD_CXROLE_DBCC_2G_PHY(cmd, r->dbcc_2g_phy, offset); + RTW89_SET_FWCMD_CXROLE_LINK_MODE_CHG(cmd, r->link_mode_chg, offset); rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_OUTSRC, BTFC_SET, @@ -6175,12 +6180,14 @@ int rtw89_fw_h2c_cxdrv_role_v2(struct rtw89_dev *rtwdev, u8 type) int rtw89_fw_h2c_cxdrv_role_v7(struct rtw89_dev *rtwdev, u8 type) { - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_role_info_v7 *role = &btc->cx.wl.role_info_v7; + struct rtw89_btc_wl_role_info *r = &rtwdev->btc.cx.wl.role_info; struct rtw89_h2c_cxrole_v7 *h2c; + struct rtw89_btc_wl_rlink *rl; u32 len = sizeof(*h2c); struct sk_buff *skb; + __le32 rm; int ret; + u8 i; skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); if (!skb) { @@ -6190,16 +6197,35 @@ int rtw89_fw_h2c_cxdrv_role_v7(struct rtw89_dev *rtwdev, u8 type) skb_put(skb, len); h2c = (struct rtw89_h2c_cxrole_v7 *)skb->data; - h2c->hdr.type = type; - h2c->hdr.ver = btc->ver->fwlrole; - h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; - memcpy(&h2c->_u8, role, sizeof(h2c->_u8)); - h2c->_u32.role_map = cpu_to_le32(role->role_map); - h2c->_u32.mrole_type = cpu_to_le32(role->mrole_type); - h2c->_u32.mrole_noa_duration = cpu_to_le32(role->mrole_noa_duration); - h2c->_u32.dbcc_en = cpu_to_le32(role->dbcc_en); - h2c->_u32.dbcc_chg = cpu_to_le32(role->dbcc_chg); - h2c->_u32.dbcc_2g_phy = cpu_to_le32(role->dbcc_2g_phy); + h2c->r.connect_cnt = r->connect_cnt; + h2c->r.link_mode = r->link_mode; + h2c->r.link_mode_chg = r->link_mode_chg; + h2c->r.p2p_2g = r->p2p_2g; + + for (i = 0; i < RTW89_BE_BTC_WL_MAX_ROLE_NUMBER; i++) { + rl = &r->rlink[i][RTW89_MAC_0]; + h2c->r.active_role[i].connected = rl->connected; + h2c->r.active_role[i].pid = rl->pid; + h2c->r.active_role[i].phy = rl->phy; + h2c->r.active_role[i].noa = rl->noa; + + h2c->r.active_role[i].band = rl->rf_band; + h2c->r.active_role[i].client_ps = rl->client_ps; + h2c->r.active_role[i].bw = rl->bw; + h2c->r.active_role[i].role = rl->role; + + h2c->r.active_role[i].ch = rl->ch; + h2c->r.active_role[i].noa_dur = rl->noa_dur; + h2c->r.active_role[i].client_cnt = rl->client_cnt; + } + + rm = cpu_to_le32(r->role_map); + memcpy(&h2c->r.role_map, &rm, sizeof(h2c->r.role_map)); + h2c->r.mrole_type = cpu_to_le32(r->mrole_type); + h2c->r.mrole_noa_duration = cpu_to_le32(r->mrole_noa_duration); + h2c->r.dbcc_en = cpu_to_le32((u32)r->dbcc_en); + h2c->r.dbcc_chg = cpu_to_le32(r->dbcc_chg); + h2c->r.dbcc_2g_phy = cpu_to_le32(r->dbcc_2g_phy); rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_OUTSRC, BTFC_SET, @@ -6221,12 +6247,14 @@ int rtw89_fw_h2c_cxdrv_role_v7(struct rtw89_dev *rtwdev, u8 type) int rtw89_fw_h2c_cxdrv_role_v8(struct rtw89_dev *rtwdev, u8 type) { - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_role_info_v8 *role = &btc->cx.wl.role_info_v8; + struct rtw89_btc_wl_role_info *r = &rtwdev->btc.cx.wl.role_info; struct rtw89_h2c_cxrole_v8 *h2c; + struct rtw89_btc_wl_rlink *rl; u32 len = sizeof(*h2c); struct sk_buff *skb; + __le32 rm; int ret; + u8 i, j; skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); if (!skb) { @@ -6237,12 +6265,118 @@ int rtw89_fw_h2c_cxdrv_role_v8(struct rtw89_dev *rtwdev, u8 type) h2c = (struct rtw89_h2c_cxrole_v8 *)skb->data; h2c->hdr.type = type; - h2c->hdr.ver = btc->ver->fwlrole; + h2c->hdr.ver = rtwdev->btc.ver->fwlrole; h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; - memcpy(&h2c->_u8, role, sizeof(h2c->_u8)); - h2c->_u32.role_map = cpu_to_le32(role->role_map); - h2c->_u32.mrole_type = cpu_to_le32(role->mrole_type); - h2c->_u32.mrole_noa_duration = cpu_to_le32(role->mrole_noa_duration); + h2c->r.connect_cnt = r->connect_cnt; + h2c->r.link_mode = r->link_mode; + h2c->r.link_mode_chg = r->link_mode_chg; + h2c->r.p2p_2g = r->p2p_2g; + h2c->r.pta_req_band = r->pta_req_band; + h2c->r.dbcc_en = r->dbcc_en; + h2c->r.dbcc_chg = r->dbcc_chg; + h2c->r.dbcc_2g_phy = r->dbcc_2g_phy; + + for (j = RTW89_MAC_0; j <= RTW89_MAC_1; j++) { + for (i = 0; i < RTW89_BE_BTC_WL_MAX_ROLE_NUMBER; i++) { + rl = &r->rlink[i][j]; + h2c->r.rlink[i][j].connected = rl->connected; + h2c->r.rlink[i][j].pid = rl->pid; + h2c->r.rlink[i][j].phy = rl->phy; + h2c->r.rlink[i][j].noa = rl->noa; + + h2c->r.rlink[i][j].rf_band = rl->rf_band; + h2c->r.rlink[i][j].active = rl->active; + h2c->r.rlink[i][j].bw = rl->bw; + h2c->r.rlink[i][j].role = rl->role; + + h2c->r.rlink[i][j].ch = rl->ch; + h2c->r.rlink[i][j].noa_dur = rl->noa_dur; + h2c->r.rlink[i][j].client_cnt = rl->client_cnt; + h2c->r.rlink[i][j].mode = rl->mode; + } + } + + rm = cpu_to_le32(r->role_map); + memcpy(&h2c->r.role_map, &rm, sizeof(h2c->r.role_map)); + h2c->r.mrole_type = cpu_to_le32(r->mrole_type); + h2c->r.mrole_noa_duration = cpu_to_le32(r->mrole_noa_duration); + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, + len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + +int rtw89_fw_h2c_cxdrv_role_v10(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc_wl_role_info *r = &rtwdev->btc.cx.wl.role_info; + struct rtw89_h2c_cxrole_v10 *h2c; + struct rtw89_btc_wl_rlink *rl; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + __le32 rm; + int ret; + u8 i, j; + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_role\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxrole_v10 *)skb->data; + + h2c->hdr.type = type; + h2c->hdr.ver = rtwdev->btc.ver->fwlrole; + h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; + + for (j = RTW89_MAC_0; j <= RTW89_MAC_1; j++) { + for (i = 0; i < RTW89_BE_BTC_WL_MAX_ROLE_NUMBER; i++) { + rl = &r->rlink[i][j]; + h2c->r.rlink[i][j].connected = rl->connected; + h2c->r.rlink[i][j].pid = rl->pid; + h2c->r.rlink[i][j].phy = rl->phy; + h2c->r.rlink[i][j].noa = rl->noa; + + h2c->r.rlink[i][j].rf_band = rl->rf_band; + h2c->r.rlink[i][j].active = rl->active; + h2c->r.rlink[i][j].bw = rl->bw; + h2c->r.rlink[i][j].role = rl->role; + + h2c->r.rlink[i][j].ch = rl->ch; + h2c->r.rlink[i][j].noa_dur = rl->noa_dur; + h2c->r.rlink[i][j].client_cnt = rl->client_cnt; + h2c->r.rlink[i][j].mode = rl->mode; + h2c->r.rlink[i][j].mac_id = rl->mac_id; + } + } + + h2c->r.link_mode = r->link_mode; + h2c->r.link_mode_hb1 = r->link_mode_hb1; + h2c->r.p2p_exist = r->p2p_exist; + h2c->r.p2p_exist_hb1 = r->p2p_exist_hb1; + + h2c->r.pta_req_band = r->pta_req_band; + h2c->r.dbcc_en = r->dbcc_en; + h2c->r.dbcc_2g_phy = r->dbcc_2g_phy; + + rm = cpu_to_le32(r->role_map); + memcpy(&h2c->r.role_map, &rm, sizeof(h2c->r.role_map)); + rm = cpu_to_le32(r->role_map_hb1); + memcpy(&h2c->r.role_map_hb1, &rm, sizeof(h2c->r.role_map_hb1)); + h2c->r.mrole_type = cpu_to_le32(r->mrole_type); rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_OUTSRC, BTFC_SET, diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index af126d15a1fb..1e4267b22b36 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -2433,54 +2433,19 @@ struct rtw89_h2c_cxctrl_v7 { #define H2C_LEN_CXDRVHDR sizeof(struct rtw89_h2c_cxhdr) #define H2C_LEN_CXDRVHDR_V7 sizeof(struct rtw89_h2c_cxhdr_v7) -struct rtw89_btc_wl_role_info_v7_u8 { - u8 connect_cnt; - u8 link_mode; - u8 link_mode_chg; - u8 p2p_2g; - - struct rtw89_btc_wl_active_role_v7 active_role[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER]; -} __packed; - -struct rtw89_btc_wl_role_info_v7_u32 { - __le32 role_map; - __le32 mrole_type; - __le32 mrole_noa_duration; - __le32 dbcc_en; - __le32 dbcc_chg; - __le32 dbcc_2g_phy; -} __packed; - struct rtw89_h2c_cxrole_v7 { struct rtw89_h2c_cxhdr_v7 hdr; - struct rtw89_btc_wl_role_info_v7_u8 _u8; - struct rtw89_btc_wl_role_info_v7_u32 _u32; -} __packed; - -struct rtw89_btc_wl_role_info_v8_u8 { - u8 connect_cnt; - u8 link_mode; - u8 link_mode_chg; - u8 p2p_2g; - - u8 pta_req_band; - u8 dbcc_en; - u8 dbcc_chg; - u8 dbcc_2g_phy; - - struct rtw89_btc_wl_rlink rlink[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER][RTW89_MAC_NUM]; -} __packed; - -struct rtw89_btc_wl_role_info_v8_u32 { - __le32 role_map; - __le32 mrole_type; - __le32 mrole_noa_duration; + struct rtw89_btc_wl_role_info_v7 r; } __packed; struct rtw89_h2c_cxrole_v8 { struct rtw89_h2c_cxhdr_v7 hdr; - struct rtw89_btc_wl_role_info_v8_u8 _u8; - struct rtw89_btc_wl_role_info_v8_u32 _u32; + struct rtw89_btc_wl_role_info_v8 r; +} __packed; + +struct rtw89_h2c_cxrole_v10 { + struct rtw89_h2c_cxhdr_v7 hdr; + struct rtw89_btc_wl_role_info_v10 r; } __packed; struct rtw89_h2c_cxosi { @@ -5413,6 +5378,7 @@ int rtw89_fw_h2c_cxdrv_role_v1(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role_v2(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role_v7(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role_v8(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxdrv_role_v10(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_osi_info(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_ctrl(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_ctrl_v7(struct rtw89_dev *rtwdev, u8 type); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b.c b/drivers/net/wireless/realtek/rtw89/rtw8851b.c index 50480f72c96d..91d6cddc713e 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851b.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851b.c @@ -2725,6 +2725,7 @@ const struct rtw89_chip_info rtw8851b_chip_info = { .mailbox = 0x1, .afh_guard_ch = 6, + .fdd_iso_freq = 1000, .wl_rssi_thres = rtw89_btc_8851b_wl_rssi_thres, .bt_rssi_thres = rtw89_btc_8851b_bt_rssi_thres, .rssi_tol = 2, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852a.c b/drivers/net/wireless/realtek/rtw89/rtw8852a.c index 0c7794742650..ed6dee63694f 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852a.c @@ -2464,6 +2464,7 @@ const struct rtw89_chip_info rtw8852a_chip_info = { .mailbox = 0x1, .afh_guard_ch = 6, + .fdd_iso_freq = 1000, .wl_rssi_thres = rtw89_btc_8852a_wl_rssi_thres, .bt_rssi_thres = rtw89_btc_8852a_bt_rssi_thres, .rssi_tol = 2, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852b.c b/drivers/net/wireless/realtek/rtw89/rtw8852b.c index 5904127cc836..4f55a153d169 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852b.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852b.c @@ -1060,6 +1060,7 @@ const struct rtw89_chip_info rtw8852b_chip_info = { .mailbox = 0x1, .afh_guard_ch = 6, + .fdd_iso_freq = 1000, .wl_rssi_thres = rtw89_btc_8852b_wl_rssi_thres, .bt_rssi_thres = rtw89_btc_8852b_bt_rssi_thres, .rssi_tol = 2, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bt.c b/drivers/net/wireless/realtek/rtw89/rtw8852bt.c index e68f73827fa5..9e0c48fde2fa 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852bt.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852bt.c @@ -898,6 +898,7 @@ const struct rtw89_chip_info rtw8852bt_chip_info = { .mailbox = 0x1, .afh_guard_ch = 6, + .fdd_iso_freq = 1000, .wl_rssi_thres = rtw89_btc_8852bt_wl_rssi_thres, .bt_rssi_thres = rtw89_btc_8852bt_bt_rssi_thres, .rssi_tol = 2, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852c.c b/drivers/net/wireless/realtek/rtw89/rtw8852c.c index 968666280c89..f21dd87e3846 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852c.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852c.c @@ -3266,6 +3266,7 @@ const struct rtw89_chip_info rtw8852c_chip_info = { .mailbox = 0x1, .afh_guard_ch = 6, + .fdd_iso_freq = 1000, .wl_rssi_thres = rtw89_btc_8852c_wl_rssi_thres, .bt_rssi_thres = rtw89_btc_8852c_bt_rssi_thres, .rssi_tol = 2, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922a.c b/drivers/net/wireless/realtek/rtw89/rtw8922a.c index fe87b4929ddc..cdcce091b922 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922a.c @@ -3249,6 +3249,7 @@ const struct rtw89_chip_info rtw8922a_chip_info = { .mailbox = 0x1, .afh_guard_ch = 6, + .fdd_iso_freq = 1000, .wl_rssi_thres = rtw89_btc_8922a_wl_rssi_thres, .bt_rssi_thres = rtw89_btc_8922a_bt_rssi_thres, .rssi_tol = 2, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c index 768434db14c6..aeade858ce95 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c @@ -3535,6 +3535,7 @@ const struct rtw89_chip_info rtw8922d_chip_info = { .mailbox = 0x1, .afh_guard_ch = 6, + .fdd_iso_freq = 1000, .wl_rssi_thres = rtw89_btc_8922d_wl_rssi_thres, .bt_rssi_thres = rtw89_btc_8922d_bt_rssi_thres, .rssi_tol = 2, From 3b6dd05aee282bcbba5410babccebb1272cbf6b2 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Sun, 12 Jul 2026 11:04:57 +0800 Subject: [PATCH 0343/1433] wifi: rtw89: coex: Rearrange coexistence control structure The control structure will record some Wi-Fi/Bluetooth status, and packed send to firmware. The new generation chip had offloaded many mechanism control to firmware, firmware may need update these very often to make sure run in correct mechanism. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260712030506.43438-3-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 154 ++++++++++----------- drivers/net/wireless/realtek/rtw89/core.h | 76 ++++++++-- drivers/net/wireless/realtek/rtw89/debug.c | 6 +- drivers/net/wireless/realtek/rtw89/fw.c | 63 ++++++++- drivers/net/wireless/realtek/rtw89/fw.h | 6 + 5 files changed, 207 insertions(+), 98 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index c02de0b4f7bf..72586179ec2c 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -295,10 +295,11 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { static const union rtw89_btc_wl_state_map btc_scanning_map = { .map = { .scan = 1, - .connecting = 1, + .dhcp = 1, .roaming = 1, - .dbccing = 1, + .transacting = 1, ._4way = 1, + .handshake = 1, }, }; @@ -998,8 +999,7 @@ static void _reset_btc_var(struct rtw89_dev *rtwdev, u8 type) if (type & BTC_RESET_CTRL) { memset(&btc->ctrl, 0, sizeof(btc->ctrl)); btc->manual_ctrl = false; - if (ver->fcxctrl != 7) - btc->ctrl.ctrl.trace_step = FCXDEF_STEP; + btc->ctrl.trace_step = FCXDEF_STEP; } /* Init Coex variables that are not zero */ @@ -1601,7 +1601,7 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, case BTC_RPT_TYPE_STEP: pcinfo = &pfwinfo->rpt_fbtc_step.cinfo; if (ver->fcxctrl != 7) - trace_step = btc->ctrl.ctrl.trace_step; + trace_step = btc->ctrl.trace_step; if (ver->fcxstep == 2) { pfinfo = &pfwinfo->rpt_fbtc_step.finfo.v2; @@ -3542,8 +3542,9 @@ static void _update_btc_state_map(struct rtw89_dev *rtwdev) struct rtw89_btc_bt_info *bt = &cx->bt0; struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; - if (wl->status.map.connecting || wl->status.map._4way || - wl->status.map.roaming || wl->status.map.dbccing) { + if (wl->status.map.dhcp || wl->status.map._4way || + wl->status.map.roaming || wl->status.map.transacting || + wl->status.map.handshake) { cx->state_map = BTC_WLINKING; } else if (wl->status.map.scan) { /* wl scan */ if (bt_linfo->status.map.inq_pag) @@ -5883,7 +5884,6 @@ static void rtw89_tx_time_iter(void *data, struct ieee80211_sta *sta) static void _set_wl_tx_limit(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_wl_info *wl = &cx->wl; @@ -5894,19 +5894,14 @@ static void _set_wl_tx_limit(struct rtw89_dev *rtwdev) struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; struct rtw89_txtime_data data = {.rtwdev = rtwdev}; bool reenable = false; - u8 igno_bt, tx_retry; + u8 tx_retry; u32 tx_time; u16 enable; if (btc->manual_ctrl) return; - if (ver->fcxctrl == 7) - igno_bt = btc->ctrl.ctrl_v7.igno_bt; - else - igno_bt = btc->ctrl.ctrl.igno_bt; - - if (btc->dm.freerun || igno_bt || b->link_cnt.now == 0 || + if (btc->dm.freerun || btc->ctrl.igno_bt || b->link_cnt.now == 0 || dm->tdd_bind.rf_band == BIT(RTW89_BAND_5G) || wl_rinfo->link_mode == BTC_WLINK_NOLINK) { enable = 0; @@ -6216,7 +6211,7 @@ static void _action_wl_scan(struct rtw89_dev *rtwdev) struct rtw89_btc_wl_dbcc_info *wl_dinfo = &wl->dbcc_info; if (btc->cx.state_map != BTC_WLINKING && - RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD, &rtwdev->fw)) { + btc->ctrl.wl_ctrl_info.fw_scan) { _action_wl_25g_mcc(rtwdev); rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], Scan offload!\n"); } else if (rtwdev->dbcc_en) { @@ -7124,11 +7119,13 @@ void rtw89_coex_act1_work(struct wiphy *wiphy, struct wiphy_work *work) rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): enter\n", __func__); dm->cnt_notify[BTC_NCNT_TIMER]++; - if (wl->status.map._4way) - wl->status.map._4way = false; - if (wl->status.map.connecting) - wl->status.map.connecting = false; - + if (wl->status.map.handshake || + wl->status.map._4way || + wl->status.map.dhcp) { + wl->status.map._4way = 0; + wl->status.map.dhcp = 0; + wl->status.map.handshake = 0; + } _run_coex(rtwdev, BTC_RSN_ACT1_WORK); } @@ -7616,32 +7613,53 @@ static void _set_coex_binding(struct rtw89_btc *btc) dm->ost_info.bt_enable_state = dm->bt_only ? _bind_is_btonly : val; } +static void _update_run_ctrl_info(struct rtw89_dev *rtwdev) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_wl_info *wl = &btc->cx.wl; + struct rtw89_btc_ctrl ctrl = btc->ctrl; + struct rtw89_fbtc_wl_ctrl_info *wl_ctrl_info = &ctrl.wl_ctrl_info; + u8 i; + + ctrl.wl_only = dm->wl_only; + ctrl.bt_only = dm->bt_only; + ctrl.ntfy_type = dm->run_reason; + + wl_ctrl_info->smap_val = wl->status.val; + wl_ctrl_info->rfk_state = wl->rfk_info.state; + wl_ctrl_info->rfk_type = wl->rfk_info.type; + wl_ctrl_info->client_pstdma_on = dm->client_ps_tdma_on; + wl_ctrl_info->fw_scan = wl->scan_info.fw_scan; + + for (i = RTW89_MAC_0; i <= RTW89_MAC_1; i++) { + wl_ctrl_info->rf_band_map[i] = wl->rf_band_map[i]; + wl_ctrl_info->rf_ch[i] = wl->rf_ch_info[i].center_ch; + } + + if (!memcmp(&btc->ctrl, &ctrl, sizeof(struct rtw89_btc_ctrl))) { + memcpy(&btc->ctrl, &ctrl, sizeof(struct rtw89_btc_ctrl)); + if (btc->ver->fcxctrl >= 9) + _fw_set_drv_info(rtwdev, CXDRVINFO_CTRL); + } +} + static void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) { struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_dm *dm = &rtwdev->btc.dm; struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_bt_info *bt = &btc->cx.bt0; u8 mode = btc->dm.tdd_bind.wl_link_mode; u8 mode_v0 = wl->role_info.link_mode_v0; - u8 igno_bt, always_freerun; lockdep_assert_wiphy(rtwdev->hw->wiphy); dm->run_reason = reason; _update_dm_step(rtwdev, reason); - if (ver->fcxctrl == 7) { - igno_bt = btc->ctrl.ctrl_v7.igno_bt; - always_freerun = btc->ctrl.ctrl_v7.always_freerun; - } else { - igno_bt = btc->ctrl.ctrl.igno_bt; - always_freerun = btc->ctrl.ctrl.always_freerun; - } - rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): reason=%d, mode=%d\n", __func__, reason, mode); rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): wl_only=%d, bt_only=%d\n", @@ -7655,15 +7673,6 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) return; } - if (igno_bt && - (reason == BTC_RSN_UPDATE_BT_INFO || - reason == BTC_RSN_UPDATE_BT_SCBD)) { - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): return for Stop Coex DM!!\n", - __func__); - return; - } - if (!wl->status.map.init_ok) { rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): return for WL init fail!!\n", @@ -7690,6 +7699,8 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) } } + _update_run_ctrl_info(rtwdev); + if (reason == BTC_RSN_NTFY_INIT || reason == BTC_RSN_NTFY_RADIO_STATE) _update_bt_scbd(rtwdev, false); @@ -7697,28 +7708,24 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) dm->cnt_dm[BTC_DCNT_RUN]++; dm->fddt_train = BTC_FDDT_DISABLE; bt->scan_rx_low_pri = false; - igno_bt = false; _set_coex_binding(btc); _update_btc_state_map(rtwdev); dm->freerun_chk = _check_freerun(rtwdev); /* check if meet freerun */ - if (always_freerun) { + if (btc->ctrl.always_freerun) { _action_freerun(rtwdev); - igno_bt = true; goto exit; } if (dm->wl_only) { _action_wl_only(rtwdev); - igno_bt = true; goto exit; } if (wl->status.map.rf_off || dm->bt_only || wl->status.map.lps) { _action_wl_off(rtwdev, mode); - igno_bt = true; goto exit; } @@ -7827,10 +7834,6 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) exit: rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): exit\n", __func__); - if (ver->fcxctrl == 7) - btc->ctrl.ctrl_v7.igno_bt = igno_bt; - else - btc->ctrl.ctrl.igno_bt = igno_bt; _action_common(rtwdev); } @@ -7839,7 +7842,6 @@ void rtw89_btc_init(struct rtw89_dev *rtwdev) const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - const struct rtw89_btc_ver *ver = btc->ver; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): Init %s !!\n", __func__, @@ -7849,10 +7851,6 @@ void rtw89_btc_init(struct rtw89_dev *rtwdev) btc->dm.run_reason = BTC_RSN_NONE; btc->dm.run_action = BTC_ACT_NONE; - if (ver->fcxctrl >= 7) - btc->ctrl.ctrl_v7.igno_bt = true; - else - btc->ctrl.ctrl.igno_bt = true; wl->status.map.init_ok = true; } @@ -7937,7 +7935,6 @@ void rtw89_btc_ntfy_init(struct rtw89_dev *rtwdev, u8 mode) struct rtw89_btc_dm *dm = &rtwdev->btc.dm; struct rtw89_btc_wl_info *wl = &btc->cx.wl; const struct rtw89_chip_info *chip = rtwdev->chip; - const struct rtw89_btc_ver *ver = btc->ver; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): mode=%d\n", __func__, mode); @@ -7948,10 +7945,7 @@ void rtw89_btc_ntfy_init(struct rtw89_dev *rtwdev, u8 mode) dm->bt_only = mode == BTC_MODE_BT ? 1 : 0; wl->status.map.rf_off = mode == BTC_MODE_WLOFF ? 1 : 0; dm->vid = rtwdev->custid; - if (ver->fcxctrl >= 7) - btc->ctrl.ctrl_v7.always_freerun = mode == BTC_MODE_COTX; - else - btc->ctrl.ctrl.always_freerun = mode == BTC_MODE_COTX; + btc->ctrl.always_freerun = mode == BTC_MODE_COTX; if (!wl->status.map.init_ok) { rtw89_debug(rtwdev, RTW89_DBG_BTC, @@ -8020,6 +8014,9 @@ void rtw89_btc_ntfy_scan_start(struct rtw89_dev *rtwdev, u8 phy_idx, u8 band) _fw_set_drv_info(rtwdev, CXDRVINFO_DBCC); } + if (RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD, &rtwdev->fw)) + btc->ctrl.wl_ctrl_info.fw_scan = 1; + _run_coex(rtwdev, BTC_RSN_NTFY_SCAN_START); } @@ -8086,14 +8083,18 @@ void rtw89_btc_ntfy_specific_packet(struct rtw89_dev *rtwdev, cnt = ++wl->wcnt[BTC_WCNT_DHCP]; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): DHCP cnt=%d\n", __func__, cnt); - wl->status.map.connecting = true; + wl->status.map.handshake = 0; + wl->status.map._4way = 0; + wl->status.map.dhcp = 1; delay_work = true; break; case PACKET_EAPOL: cnt = ++wl->wcnt[BTC_WCNT_EAPOL]; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): EAPOL cnt=%d\n", __func__, cnt); - wl->status.map._4way = true; + wl->status.map.handshake = 0; + wl->status.map._4way = 1; + wl->status.map.dhcp = 0; delay_work = true; if (hfp->exist || hid->exist) delay /= 2; @@ -8103,7 +8104,7 @@ void rtw89_btc_ntfy_specific_packet(struct rtw89_dev *rtwdev, rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): EAPOL_End cnt=%d\n", __func__, cnt); - wl->status.map._4way = false; + wl->status.map._4way = 0; wiphy_delayed_work_cancel(rtwdev->hw->wiphy, &rtwdev->coex_act1_work); break; case PACKET_ARP: @@ -8435,10 +8436,16 @@ void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev, wlinfo->connected == MLME_NO_LINK) btc->dm.leak_ap = 0; - if (state == BTC_ROLE_MSTS_STA_CONN_START) - wl->status.map.connecting = 1; - else - wl->status.map.connecting = 0; + if (state == BTC_ROLE_MSTS_STA_CONN_START) { + wl->status.map.transacting = 1; + wiphy_delayed_work_cancel(rtwdev->hw->wiphy, &rtwdev->coex_act1_work); + wiphy_delayed_work_queue(rtwdev->hw->wiphy, + &rtwdev->coex_act1_work, + RTW89_COEX_ACT1_WORK_PERIOD); + } else { + wiphy_delayed_work_cancel(rtwdev->hw->wiphy, &rtwdev->coex_act1_work); + wl->status.map.transacting = 0; + } if (state == BTC_ROLE_MSTS_STA_DIS_CONN || state == BTC_ROLE_MSTS_STA_CONN_END) @@ -9116,8 +9123,8 @@ static int _show_wl_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) wl->scan_info.band[RTW89_PHY_0], wl->scan_info.phy_map); p += scnprintf(p, end - p, - "connecting:%s, roam:%s, 4way:%s, init_ok:%s\n", - wl->status.map.connecting ? "Y" : "N", + "handshake:%s, roam:%s, 4way:%s, init_ok:%s\n", + wl->status.map.handshake ? "Y" : "N", wl->status.map.roaming ? "Y" : "N", wl->status.map._4way ? "Y" : "N", wl->status.map.init_ok ? "Y" : "N"); @@ -9718,12 +9725,10 @@ static int _show_dm_step(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) static int _show_dm_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) { struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_bt_info *bt = &btc->cx.bt0; char *p = buf, *end = buf + bufsz; - u8 igno_bt; if (!(dm->coex_info_map & BTC_COEX_INFO_DM)) return 0; @@ -9744,14 +9749,9 @@ static int _show_dm_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += _show_dm_step(rtwdev, p, end - p); - if (ver->fcxctrl == 7) - igno_bt = btc->ctrl.ctrl_v7.igno_bt; - else - igno_bt = btc->ctrl.ctrl.igno_bt; - p += scnprintf(p, end - p, " %-15s : wl_only:%d, bt_only:%d, igno_bt:%d, free_run:%d, wl_ps_ctrl:%d, wl_mimo_ps:%d, ", - "[dm_flag]", dm->wl_only, dm->bt_only, igno_bt, + "[dm_flag]", dm->wl_only, dm->bt_only, btc->ctrl.igno_bt, dm->freerun, btc->btc_ctrl_lps, dm->wl_mimo_ps); p += scnprintf(p, end - p, "leak_ap:%d, fw_offload:%s%s\n", @@ -10742,7 +10742,7 @@ static int _show_fbtc_step_v2(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) if (ver->fcxctrl == 7 || ver->fcxctrl == 1) trace_step = 50; else - trace_step = btc->ctrl.ctrl.trace_step; + trace_step = btc->ctrl.trace_step; n_start = pos_old; if (pos_new >= pos_old) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index ded788534651..0b20ee7d7a28 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -1531,19 +1531,20 @@ enum rtw89_tfc_dir { struct rtw89_btc_wl_smap { u32 busy: 1; u32 scan: 1; - u32 connecting: 1; + u32 dhcp: 1; u32 roaming: 1; - u32 dbccing: 1; + u32 transacting: 1; u32 _4way: 1; + u32 handshake: 1; u32 rf_off: 1; - u32 lps: 2; - u32 ips: 1; - u32 init_ok: 1; - u32 traffic_dir : 2; u32 rf_off_pre: 1; + u32 ips: 1; + u32 lps: 2; u32 lps_pre: 2; u32 lps_exiting: 1; u32 emlsr: 1; + u32 init_ok: 1; + u32 traffic_dir : 2; }; enum rtw89_tfc_interval { @@ -1725,7 +1726,9 @@ struct rtw89_btc_u8_sta_chg { struct rtw89_btc_wl_scan_info { u8 band[RTW89_PHY_NUM]; u8 phy_map; - u8 rsvd; + u8 hw_band_map; + u8 type; + u8 fw_scan; }; struct rtw89_btc_wl_dbcc_info { @@ -3458,6 +3461,7 @@ struct rtw89_btc_dm { u8 run_action; u8 wl_tx_pwr_phy_map; u8 vid; + u8 client_ps_tdma_on; u8 wl_pre_agc: 2; u8 wl_lna2: 1; @@ -3477,13 +3481,38 @@ struct rtw89_btc_dm { bool pre_agc_chg; }; +struct rtw89_fbtc_wl_ctrl_info { + u8 rf_band_map[RTW89_MAC_NUM]; + u8 rf_ch[RTW89_MAC_NUM]; + + u8 client_pstdma_on; + u8 fw_scan; + u8 rfk_state; + u8 rfk_type; + + u32 smap_val; +}; + struct rtw89_btc_ctrl { + u32 manual; + u32 igno_bt: 1; + u32 always_freerun: 1; + u32 trace_step: 16; + + u8 wl_only; + u8 bt_only; + + u8 ntfy_type; + struct rtw89_fbtc_wl_ctrl_info wl_ctrl_info; +}; + +struct rtw89_btc_ctrl_v0 { u32 manual: 1; u32 igno_bt: 1; u32 always_freerun: 1; u32 trace_step: 16; u32 rsvd: 12; -}; +} __packed; struct rtw89_btc_ctrl_v7 { u8 manual; @@ -3492,10 +3521,31 @@ struct rtw89_btc_ctrl_v7 { u8 rsvd; } __packed; -union rtw89_btc_ctrl_list { - struct rtw89_btc_ctrl ctrl; - struct rtw89_btc_ctrl_v7 ctrl_v7; /* ver 8, 9 is the same */ -}; +struct rtw89_fbtc_wl_ctrl_info_v9 { + u8 rf_band_map[RTW89_MAC_NUM]; + u8 rf_ch[RTW89_MAC_NUM]; + + u8 client_pstdma_on; + u8 fw_scan; + u8 rfk_state; + u8 rfk_type; + + __le32 smap_val; +} __packed; + +struct rtw89_btc_ctrl_v9 { + u8 manual; + u8 always_freerun; + u8 wl_only; + u8 bt_only; + + u8 ntfy_type; + u8 rsvd0; + u8 rsvd1; + u8 rsvd2; + + struct rtw89_fbtc_wl_ctrl_info_v9 wl_ctrl_info; +} __packed; struct rtw89_btc_dbg { /* cmd "rb" */ @@ -3701,7 +3751,7 @@ struct rtw89_btc { struct rtw89_btc_cx cx; struct rtw89_btc_dm dm; - union rtw89_btc_ctrl_list ctrl; + struct rtw89_btc_ctrl ctrl; union rtw89_btc_module_info mdinfo; struct rtw89_btc_btf_fwinfo fwinfo; struct rtw89_btc_dbg dbg; diff --git a/drivers/net/wireless/realtek/rtw89/debug.c b/drivers/net/wireless/realtek/rtw89/debug.c index e42dc5707576..8b41991963e8 100644 --- a/drivers/net/wireless/realtek/rtw89/debug.c +++ b/drivers/net/wireless/realtek/rtw89/debug.c @@ -3900,17 +3900,13 @@ static ssize_t rtw89_debug_priv_btc_manual_set(struct rtw89_dev *rtwdev, const char *buf, size_t count) { struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; int ret; ret = kstrtobool(buf, &btc->manual_ctrl); if (ret) return ret; - if (ver->fcxctrl == 7) - btc->ctrl.ctrl_v7.manual = btc->manual_ctrl; - else - btc->ctrl.ctrl.manual = btc->manual_ctrl; + btc->ctrl.manual = btc->manual_ctrl; return count; } diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 41c033d2ae7b..59e51e67d51b 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -6441,7 +6441,7 @@ int rtw89_fw_h2c_cxdrv_ctrl(struct rtw89_dev *rtwdev, u8 type) { struct rtw89_btc *btc = &rtwdev->btc; const struct rtw89_btc_ver *ver = btc->ver; - struct rtw89_btc_ctrl *ctrl = &btc->ctrl.ctrl; + struct rtw89_btc_ctrl *ctrl = &btc->ctrl; struct sk_buff *skb; u8 *cmd; int ret; @@ -6484,7 +6484,7 @@ int rtw89_fw_h2c_cxdrv_ctrl(struct rtw89_dev *rtwdev, u8 type) int rtw89_fw_h2c_cxdrv_ctrl_v7(struct rtw89_dev *rtwdev, u8 type) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_ctrl_v7 *ctrl = &btc->ctrl.ctrl_v7; + struct rtw89_btc_ctrl *ctrl = &btc->ctrl; struct rtw89_h2c_cxctrl_v7 *h2c; u32 len = sizeof(*h2c); struct sk_buff *skb; @@ -6501,7 +6501,64 @@ int rtw89_fw_h2c_cxdrv_ctrl_v7(struct rtw89_dev *rtwdev, u8 type) h2c->hdr.type = type; h2c->hdr.ver = btc->ver->fcxctrl; h2c->hdr.len = sizeof(*h2c) - H2C_LEN_CXDRVHDR_V7; - h2c->ctrl = *ctrl; + + h2c->ctrl.manual = ctrl->manual; + h2c->ctrl.igno_bt = ctrl->igno_bt; + h2c->ctrl.always_freerun = ctrl->always_freerun; + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + +int rtw89_fw_h2c_cxdrv_ctrl_v9(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_ctrl *ctrl = &btc->ctrl; + struct rtw89_fbtc_wl_ctrl_info_v9 *cinfo; + struct rtw89_h2c_cxctrl_v9 *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + int ret; + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_ctrl_v7\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxctrl_v9 *)skb->data; + cinfo = &h2c->ctrl.wl_ctrl_info; + + h2c->hdr.type = type; + h2c->hdr.ver = btc->ver->fcxctrl; + h2c->hdr.len = sizeof(*h2c) - H2C_LEN_CXDRVHDR_V7; + + h2c->ctrl.manual = ctrl->manual; + h2c->ctrl.always_freerun = ctrl->always_freerun; + h2c->ctrl.wl_only = ctrl->wl_only; + h2c->ctrl.bt_only = ctrl->bt_only; + h2c->ctrl.ntfy_type = ctrl->ntfy_type; + memcpy(cinfo->rf_band_map, ctrl->wl_ctrl_info.rf_band_map, + sizeof(cinfo->rf_band_map)); + memcpy(cinfo->rf_ch, ctrl->wl_ctrl_info.rf_ch, sizeof(cinfo->rf_ch)); + cinfo->client_pstdma_on = ctrl->wl_ctrl_info.client_pstdma_on; + cinfo->fw_scan = ctrl->wl_ctrl_info.fw_scan; + cinfo->rfk_state = ctrl->wl_ctrl_info.rfk_state; + cinfo->rfk_type = ctrl->wl_ctrl_info.rfk_type; + cinfo->smap_val = cpu_to_le32(ctrl->wl_ctrl_info.smap_val); rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_OUTSRC, BTFC_SET, diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index 1e4267b22b36..72f56d32e3b0 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -2430,6 +2430,11 @@ struct rtw89_h2c_cxctrl_v7 { struct rtw89_btc_ctrl_v7 ctrl; } __packed; +struct rtw89_h2c_cxctrl_v9 { + struct rtw89_h2c_cxhdr_v7 hdr; + struct rtw89_btc_ctrl_v9 ctrl; +} __packed; + #define H2C_LEN_CXDRVHDR sizeof(struct rtw89_h2c_cxhdr) #define H2C_LEN_CXDRVHDR_V7 sizeof(struct rtw89_h2c_cxhdr_v7) @@ -5382,6 +5387,7 @@ int rtw89_fw_h2c_cxdrv_role_v10(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_osi_info(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_ctrl(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_ctrl_v7(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxdrv_ctrl_v9(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_trx_v7(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_trx_v9(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_rfk(struct rtw89_dev *rtwdev, u8 type); From 404edeea3b182d08ccdcdba1a1c43ae4ed7f4f0f Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Sun, 12 Jul 2026 11:04:58 +0800 Subject: [PATCH 0344/1433] wifi: rtw89: coex: Update driver outsource info to firmware version 6 In order to make dual MAC Wi-Fi performance more stable, and take effect in time, offload more register/ hardware control to firmware. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260712030506.43438-4-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 13 ++- drivers/net/wireless/realtek/rtw89/core.h | 89 +++++++++++++++++-- drivers/net/wireless/realtek/rtw89/fw.c | 101 +++++++++++++++++++++- drivers/net/wireless/realtek/rtw89/fw.h | 8 +- 4 files changed, 199 insertions(+), 12 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 72586179ec2c..78360e73d959 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -2923,7 +2923,12 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index) else return; - rtw89_fw_h2c_cxdrv_osi_info(rtwdev, index); + if (ver->fcxosi == 1) + rtw89_fw_h2c_cxdrv_osi_info(rtwdev, index); + else if (ver->fcxosi == 6) + rtw89_fw_h2c_cxdrv_osi_info_v6(rtwdev, index); + else + return; break; default: break; @@ -3090,7 +3095,11 @@ static void _set_gnt_v1(struct rtw89_dev *rtwdev, u8 phy_map, } memcpy(osi->gnt_set, dm->gnt.band, sizeof(osi->gnt_set)); - memcpy(osi->wlact_set, dm->gnt.bt, sizeof(osi->wlact_set)); + + for (i = 0; i < BTC_ALL_BT; i++) { + osi->wlact_set[i].wlan_act_en = dm->gnt.bt[i].wlan_act_en; + osi->wlact_set[i].wlan_act = dm->gnt.bt[i].wlan_act; + } /* GBT source should be GBT_S1 in 1+1 (HWB0:5G + HWB1:2G) case */ if (osi->rf_band[BTC_RF_S0] == 1 && diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 0b20ee7d7a28..676d3ba855d7 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -1322,6 +1322,17 @@ struct rtw89_mac_ax_gnt { u8 gnt_wl; } __packed; +struct rtw89_btc_gnt_ctrl { + u8 gnt_zb_sw_en; + u8 gnt_zb; + u8 gnt_bt1_sw_en; + u8 gnt_bt1; + u8 gnt_bt0_sw_en; + u8 gnt_bt0; + u8 gnt_wl_sw_en; + u8 gnt_wl; +} __packed; + struct rtw89_mac_ax_wl_act { u8 wlan_act_en; u8 wlan_act; @@ -3374,27 +3385,89 @@ enum btc_rf_path { BTC_RF_NUM, }; -struct rtw89_btc_fbtc_outsrc_set_info { - u8 rf_band[BTC_RF_NUM]; /* 0:2G, 1:non-2G */ +struct rtw89_btc_fbtc_outsrc_set_info_v1 { + u8 rf_band[BTC_RF_NUM]; u8 btg_rx[BTC_RF_NUM]; u8 nbtg_tx[BTC_RF_NUM]; - struct rtw89_mac_ax_gnt gnt_set[BTC_RF_NUM]; /* refer to btc_gnt_ctrl */ - struct rtw89_mac_ax_wl_act wlact_set[BTC_RF_NUM]; /* BT0/BT1 */ + struct rtw89_mac_ax_gnt gnt_set[BTC_RF_NUM]; + struct rtw89_mac_ax_wl_act wlact_set[BTC_ALL_BT]; u8 pta_req_hw_band; u8 rf_gbt_source; u8 bt_enable_state; u8 wl_btg_standby_chg; - /* bit[15]-> 0:2G/1:5G,6G, bit[14:0]-> WL HWBx ch freq in MHz */ + + u8 fbd_group_en[RTW89_MAC_NUM][2]; + __le16 rf_center_freq[RTW89_MAC_NUM]; + __le16 fbd_group_bound[RTW89_MAC_NUM][2]; + __le16 freq_diff_thres[RTW89_MAC_NUM][BTC_ALL_BT]; +} __packed; + +struct rtw89_btc_fbtc_outsrc_set_info_v6 { + u8 rf_band[BTC_RF_NUM]; + u8 btg_rx[BTC_RF_NUM]; + u8 nbtg_tx[BTC_RF_NUM]; + + struct rtw89_btc_gnt_ctrl gnt_set[RTW89_MAC_AX_COEX_GNT_NR]; + struct rtw89_mac_ax_wl_act wlact_set[BTC_ALL_BT_EZL]; + + u8 pta_req_hw_band; + u8 rf_gbt_source; + u8 bt_enable_state; + u8 bt_plut_type; + u8 wl_tx_limit_en; + u8 fc_exec; + u8 wl_btg_standby_chg; + u8 rsvd; + u8 bb_path_sel_bt[BTC_RF_NUM]; + u8 bb_phy_sel_bt[RTW89_PHY_NUM]; + u8 fbd_group_en[RTW89_MAC_NUM][2]; + __le16 rf_center_freq[RTW89_MAC_NUM]; + __le16 freq_diff_thres[RTW89_MAC_NUM][BTC_ALL_BT_EZL]; + __le16 fbd_group_bound[RTW89_MAC_NUM][2]; + __le32 wl_tx_limit_time; +} __packed; + +struct rtw89_btc_fbtc_outsrc_set_info { + u8 rf_band[BTC_RF_NUM]; /* 0:2GHz/1:5GHz for MPI_bb_hwsi_ignore_gnt_wl() */ + u8 btg_rx[BTC_RF_NUM]; /* for MPI_bb_btg_bt_rx() */ + u8 nbtg_tx[BTC_RF_NUM]; /* for MPI_bb_nbtg_bt_tx pre=AGC control */ + + struct rtw89_mac_ax_gnt gnt_set[BTC_RF_NUM]; /* refer to btc_gnt_ctrl */ + struct rtw89_btc_gnt_ctrl gnt_set_be[RTW89_MAC_AX_COEX_GNT_NR]; + struct rtw89_mac_ax_wl_act wlact_set[BTC_ALL_BT_EZL]; + + u8 pta_req_hw_band; /* Bind PTA to HWB0 or HWB1, only for 8922a 1-PTA */ + u8 rf_gbt_source; /* gbt from S0 or S1 for RF 0x2[9], only for 8922a */ + + /* The followngs are for 8922c/d new Multi-PTA design */ + /* 0:BT0/1:BT1/2:ZB on/off for MAC(0xe580[0]/0xe680[0])/ RF 0x4[3:2] */ + u8 bt_enable_state; + u8 bt_plut_type; /* BT polluted type, refer to enum btc_plt_map */ + + u8 wl_tx_limit_en; + u8 fc_exec; + u8 wl_btg_standby_chg; /* keep RX-IQGen on in standby mode */ + u8 rsvd; + + u8 bb_path_sel_bt[BTC_RF_NUM]; /* bb s0(1) select GNT_BT0 or BT1 */ + u8 bb_phy_sel_bt[RTW89_PHY_NUM]; /* bb phy0(1) select GNT_BT0 or BT1 */ + /* forbidden group-> bit[1]:fbd rf-band, bit[0]: fbd enable */ u8 fbd_group_en[RTW89_MAC_NUM][2]; /* HWB0/1 +.Group0/1 */ + + /* bit[15]-> 0:2G/1:5G,6G, bit[14:0]-> WL HWBx ch freq in MHz */ u16 rf_center_freq[RTW89_MAC_NUM]; /* HWB0/1 */ - /* forbidden group boundary: [15:8]->UP, [7:0]->LO */ - u16 fbd_group_bound[RTW89_MAC_NUM][2]; /* HWB0/1 +.Group0/1 */ + /* 11-bit in MHz, freq diff threshold */ u16 freq_diff_thres[RTW89_MAC_NUM][BTC_ALL_BT_EZL]; /* HWB0/1 vs.BT0/1/2 */ -} __packed; + + /* forbidden group boundary: [15:8]->UP, [7:0]->LO */ + u16 fbd_group_bound[RTW89_MAC_NUM][2]; /* HWB0/1 +.Group0/1 */ + + u32 wl_tx_limit_time; +}; union rtw89_btc_fbtc_slot_u { struct rtw89_btc_fbtc_slot v1[CXST_MAX]; diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 59e51e67d51b..027f8121efd1 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -6404,6 +6404,7 @@ int rtw89_fw_h2c_cxdrv_osi_info(struct rtw89_dev *rtwdev, u8 type) u32 len = sizeof(*h2c); struct sk_buff *skb; int ret; + u8 i, j; skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); if (!skb) { @@ -6416,7 +6417,105 @@ int rtw89_fw_h2c_cxdrv_osi_info(struct rtw89_dev *rtwdev, u8 type) h2c->hdr.type = type; h2c->hdr.ver = btc->ver->fcxosi; h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; - h2c->osi = *osi; + + memcpy(h2c->osi.rf_band, osi->rf_band, sizeof(osi->rf_band)); + memcpy(h2c->osi.btg_rx, osi->btg_rx, sizeof(osi->btg_rx)); + memcpy(h2c->osi.nbtg_tx, osi->nbtg_tx, sizeof(osi->nbtg_tx)); + + for (i = 0; i < BTC_RF_NUM; i++) { + h2c->osi.gnt_set[i] = osi->gnt_set[i]; + h2c->osi.wlact_set[i] = osi->wlact_set[i]; + } + + h2c->osi.pta_req_hw_band = osi->pta_req_hw_band; + h2c->osi.rf_gbt_source = osi->rf_gbt_source; + h2c->osi.bt_enable_state = osi->bt_enable_state; + h2c->osi.wl_btg_standby_chg = osi->wl_btg_standby_chg; + + memcpy(h2c->osi.fbd_group_en, osi->fbd_group_en, sizeof(osi->fbd_group_en)); + + for (i = 0; i < RTW89_MAC_NUM; i++) { + h2c->osi.rf_center_freq[i] = cpu_to_le16(osi->rf_center_freq[i]); + for (j = 0; j < BTC_RF_NUM; j++) + h2c->osi.fbd_group_bound[i][j] = cpu_to_le16(osi->fbd_group_bound[i][j]); + } + + for (i = 0; i < RTW89_MAC_NUM; i++) { + for (j = 0; j < BTC_ALL_BT; j++) + h2c->osi.freq_diff_thres[i][j] = cpu_to_le16(osi->freq_diff_thres[i][j]); + } + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, + len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + +int rtw89_fw_h2c_cxdrv_osi_info_v6(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_fbtc_outsrc_set_info *osi = &btc->dm.ost_info; + struct rtw89_h2c_cxosi_v6 *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + int ret; + u8 i, j; + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_osi_v6\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxosi_v6 *)skb->data; + + h2c->hdr.type = type; + h2c->hdr.ver = btc->ver->fcxosi; + h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; + + memcpy(h2c->osi.rf_band, osi->rf_band, sizeof(osi->rf_band)); + memcpy(h2c->osi.btg_rx, osi->btg_rx, sizeof(osi->btg_rx)); + memcpy(h2c->osi.nbtg_tx, osi->nbtg_tx, sizeof(osi->nbtg_tx)); + memcpy(h2c->osi.gnt_set, osi->gnt_set, sizeof(osi->gnt_set)); + memcpy(h2c->osi.wlact_set, osi->wlact_set, sizeof(osi->wlact_set)); + + h2c->osi.pta_req_hw_band = osi->pta_req_hw_band; + h2c->osi.rf_gbt_source = osi->rf_gbt_source; + h2c->osi.bt_enable_state = osi->bt_enable_state; + h2c->osi.bt_plut_type = osi->bt_plut_type; + h2c->osi.wl_tx_limit_en = osi->wl_tx_limit_en; + h2c->osi.fc_exec = osi->fc_exec; + h2c->osi.wl_btg_standby_chg = osi->wl_btg_standby_chg; + h2c->osi.rsvd = osi->rsvd; + + memcpy(h2c->osi.bb_path_sel_bt, osi->bb_path_sel_bt, sizeof(osi->bb_path_sel_bt)); + memcpy(h2c->osi.bb_phy_sel_bt, osi->bb_phy_sel_bt, sizeof(osi->bb_phy_sel_bt)); + memcpy(h2c->osi.fbd_group_en, osi->fbd_group_en, sizeof(osi->fbd_group_en)); + + for (i = 0; i < RTW89_MAC_NUM; i++) { + h2c->osi.rf_center_freq[i] = cpu_to_le16(osi->rf_center_freq[i]); + for (j = 0; j < BTC_RF_NUM; j++) + h2c->osi.fbd_group_bound[i][j] = cpu_to_le16(osi->fbd_group_bound[i][j]); + } + + for (i = 0; i < RTW89_MAC_NUM; i++) { + for (j = 0; j < BTC_ALL_BT_EZL; j++) + h2c->osi.freq_diff_thres[i][j] = cpu_to_le16(osi->freq_diff_thres[i][j]); + } + + h2c->osi.wl_tx_limit_time = cpu_to_le32(osi->wl_tx_limit_time); rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_OUTSRC, BTFC_SET, diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index 72f56d32e3b0..c6131778f6a2 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -2455,7 +2455,12 @@ struct rtw89_h2c_cxrole_v10 { struct rtw89_h2c_cxosi { struct rtw89_h2c_cxhdr_v7 hdr; - struct rtw89_btc_fbtc_outsrc_set_info osi; + struct rtw89_btc_fbtc_outsrc_set_info_v1 osi; +} __packed; + +struct rtw89_h2c_cxosi_v6 { + struct rtw89_h2c_cxhdr_v7 hdr; + struct rtw89_btc_fbtc_outsrc_set_info_v6 osi; } __packed; struct rtw89_h2c_cxinit { @@ -5385,6 +5390,7 @@ int rtw89_fw_h2c_cxdrv_role_v7(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role_v8(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role_v10(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_osi_info(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxdrv_osi_info_v6(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_ctrl(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_ctrl_v7(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_ctrl_v9(struct rtw89_dev *rtwdev, u8 type); From a20edfbc15a50ad5a445db2dc13a6a5780308deb Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Sun, 12 Jul 2026 11:04:59 +0800 Subject: [PATCH 0345/1433] wifi: rtw89: ceox: Update antenna & grant signal setting Merge set antenna & grant signal logic. Combine all information to big structure for runtime logic using, only separate to version format while it is going to assign value to register or offload to firmware. Add new format for dual-BT & external BT for RTL8922D. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260712030506.43438-5-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 505 ++++++++------------ drivers/net/wireless/realtek/rtw89/core.h | 4 +- drivers/net/wireless/realtek/rtw89/mac.c | 27 +- drivers/net/wireless/realtek/rtw89/mac_be.c | 21 +- 4 files changed, 218 insertions(+), 339 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 78360e73d959..2ca6090e9f62 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -492,7 +492,7 @@ enum btc_ant_phase { BTC_ANT_WONLY, BTC_ANT_WOFF, BTC_ANT_W2G, - BTC_ANT_W5G, + BTC_ANT_FDD, BTC_ANT_W25G, BTC_ANT_FREERUN, BTC_ANT_WRFK, @@ -841,7 +841,9 @@ enum btc_gnt_state { BTC_GNT_HW = 0, BTC_GNT_SW_LO, BTC_GNT_SW_HI, - BTC_GNT_MAX + BTC_GNT_MAX, + + BTC_GNT_SET_SKIP = 0xff, }; enum btc_ctr_path { @@ -1787,8 +1789,8 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, dm->wl_fw_cx_offload = !!le32_to_cpu(prpt->v4.wl_fw_info.cx_offload); for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) - memcpy(&dm->gnt.band[i], &prpt->v4.gnt_val[i], - sizeof(dm->gnt.band[i])); + memcpy(&dm->gnt_set[i], &prpt->v4.gnt_val[i], + sizeof(dm->gnt_set[i])); bt->bcnt[BTC_BCNT_HIPRI_TX] = le32_to_cpu(prpt->v4.bt_cnt[BTC_BCNT_HI_TX]); @@ -1819,8 +1821,8 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, dm->wl_fw_cx_offload = 0; for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) - memcpy(&dm->gnt.band[i], &prpt->v5.gnt_val[i][0], - sizeof(dm->gnt.band[i])); + memcpy(&dm->gnt_set[i], &prpt->v5.gnt_val[i], + sizeof(dm->gnt_set[i])); bt->bcnt[BTC_BCNT_HIPRI_TX] = le16_to_cpu(prpt->v5.bt_cnt[BTC_BCNT_HI_TX]); @@ -1846,8 +1848,8 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, dm->wl_fw_cx_offload = 0; for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) - memcpy(&dm->gnt.band[i], &prpt->v105.gnt_val[i][0], - sizeof(dm->gnt.band[i])); + memcpy(&dm->gnt_set[i], &prpt->v105.gnt_val[i], + sizeof(dm->gnt_set[i])); bt->bcnt[BTC_BCNT_HIPRI_TX] = le16_to_cpu(prpt->v105.bt_cnt[BTC_BCNT_HI_TX_V105]); @@ -1872,8 +1874,8 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, wl->ver_info.fw = le32_to_cpu(prpt->v7.rpt_info.fw_ver); for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) - memcpy(&dm->gnt.band[i], &prpt->v7.gnt_val[i][0], - sizeof(dm->gnt.band[i])); + memcpy(&dm->gnt_set[i], &prpt->v7.gnt_val[i], + sizeof(dm->gnt_set[i])); bt->bcnt[BTC_BCNT_HIPRI_TX] = le16_to_cpu(prpt->v7.bt_cnt[BTC_BCNT_HI_TX_V105]); @@ -1904,8 +1906,8 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, wl->ver_info.fw = le32_to_cpu(prpt->v8.rpt_info.fw_ver); for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) - memcpy(&dm->gnt.band[i], &prpt->v8.gnt_val[i][0], - sizeof(dm->gnt.band[i])); + memcpy(&dm->gnt_set[i], &prpt->v8.gnt_val[i], + sizeof(dm->gnt_set[i])); bt->bcnt[BTC_BCNT_HIPRI_TX] = le16_to_cpu(prpt->v8.bt_cnt[BTC_BCNT_HI_TX_V105]); @@ -2971,11 +2973,14 @@ void btc_fw_event(struct rtw89_dev *rtwdev, u8 evt_id, void *data, u32 len) btc->dm.scbd_b2w_update = 0; } -static void _set_gnt(struct rtw89_dev *rtwdev, u8 phy_map, u8 wl_state, u8 bt_state) +static void _set_gnt(struct rtw89_dev *rtwdev, u8 phy_map, + u8 wl_state, u8 bt_state, u8 wlact_state) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_mac_ax_gnt *g = dm->gnt.band; + struct rtw89_btc_fbtc_outsrc_set_info *o = &dm->ost_info; + struct rtw89_mac_ax_wl_act *wlact_set; + struct rtw89_mac_ax_coex_gnt mg; u8 i; if (phy_map > BTC_PHY_ALL) @@ -2987,126 +2992,118 @@ static void _set_gnt(struct rtw89_dev *rtwdev, u8 phy_map, u8 wl_state, u8 bt_st switch (wl_state) { case BTC_GNT_HW: - g[i].gnt_wl_sw_en = 0; - g[i].gnt_wl = 0; + dm->gnt_set[i].gnt_wl_sw_en = 0; + dm->gnt_set[i].gnt_wl = 0; break; case BTC_GNT_SW_LO: - g[i].gnt_wl_sw_en = 1; - g[i].gnt_wl = 0; + dm->gnt_set[i].gnt_wl_sw_en = 1; + dm->gnt_set[i].gnt_wl = 0; break; case BTC_GNT_SW_HI: - g[i].gnt_wl_sw_en = 1; - g[i].gnt_wl = 1; + dm->gnt_set[i].gnt_wl_sw_en = 1; + dm->gnt_set[i].gnt_wl = 1; + break; + case BTC_GNT_SET_SKIP: + default: break; } switch (bt_state) { case BTC_GNT_HW: - g[i].gnt_bt_sw_en = 0; - g[i].gnt_bt = 0; + dm->gnt_set[i].gnt_bt0_sw_en = 0; + dm->gnt_set[i].gnt_bt0 = 0; + dm->gnt_set[i].gnt_bt1_sw_en = 0; + dm->gnt_set[i].gnt_bt1 = 0; + dm->gnt_set[i].gnt_zb_sw_en = 0; + dm->gnt_set[i].gnt_zb = 0; break; case BTC_GNT_SW_LO: - g[i].gnt_bt_sw_en = 1; - g[i].gnt_bt = 0; + dm->gnt_set[i].gnt_bt0_sw_en = 1; + dm->gnt_set[i].gnt_bt0 = 0; + dm->gnt_set[i].gnt_bt1_sw_en = 1; + dm->gnt_set[i].gnt_bt1 = 0; + dm->gnt_set[i].gnt_zb_sw_en = 1; + dm->gnt_set[i].gnt_zb = 0; break; case BTC_GNT_SW_HI: - g[i].gnt_bt_sw_en = 1; - g[i].gnt_bt = 1; + dm->gnt_set[i].gnt_bt0_sw_en = 1; + dm->gnt_set[i].gnt_bt0 = 1; + dm->gnt_set[i].gnt_bt1_sw_en = 1; + dm->gnt_set[i].gnt_bt1 = 1; + dm->gnt_set[i].gnt_zb_sw_en = 1; + dm->gnt_set[i].gnt_zb = 1; + break; + case BTC_GNT_SET_SKIP: + default: break; } } - rtw89_chip_mac_cfg_gnt(rtwdev, &dm->gnt); -} - -static void _set_gnt_v1(struct rtw89_dev *rtwdev, u8 phy_map, - u8 wl_state, u8 bt_state, u8 wlact_state) -{ - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_btc_fbtc_outsrc_set_info *osi = &dm->ost_info; - struct rtw89_mac_ax_wl_act *b = dm->gnt.bt; - struct rtw89_mac_ax_gnt *g = dm->gnt.band; - u8 i, bt_idx = dm->bt_select + 1; - - if (phy_map > BTC_PHY_ALL) - return; - - for (i = 0; i < RTW89_PHY_NUM; i++) { - if (!(phy_map & BIT(i))) - continue; - - switch (wl_state) { - case BTC_GNT_HW: - g[i].gnt_wl_sw_en = 0; - g[i].gnt_wl = 0; + for (i = 0; i < BTC_ALL_BT_EZL; i++) { + if (!(rtwdev->chip->para_ver & BTC_FEAT_WLAN_ACT_MUX)) break; - case BTC_GNT_SW_LO: - g[i].gnt_wl_sw_en = 1; - g[i].gnt_wl = 0; + + switch (wlact_state) { + case BTC_WLACT_HW: + dm->wlact_set[i].wlan_act_en = 0; + dm->wlact_set[i].wlan_act = 0; break; - case BTC_GNT_SW_HI: - g[i].gnt_wl_sw_en = 1; - g[i].gnt_wl = 1; + case BTC_WLACT_SW_LO: + dm->wlact_set[i].wlan_act_en = 1; + dm->wlact_set[i].wlan_act = 0; + break; + case BTC_WLACT_SW_HI: + dm->wlact_set[i].wlan_act_en = 1; + dm->wlact_set[i].wlan_act = 1; + break; + default: break; } - switch (bt_state) { - case BTC_GNT_HW: - g[i].gnt_bt_sw_en = 0; - g[i].gnt_bt = 0; + if (!(rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT)) break; - case BTC_GNT_SW_LO: - g[i].gnt_bt_sw_en = 1; - g[i].gnt_bt = 0; - break; - case BTC_GNT_SW_HI: - g[i].gnt_bt_sw_en = 1; - g[i].gnt_bt = 1; - break; - } } - if (rtwdev->chip->para_ver & BTC_FEAT_WLAN_ACT_MUX) { - for (i = 0; i < 2; i++) { - if (!(bt_idx & BIT(i))) - continue; + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(): phy_map=0x%x, gnt_wl:%d, gnt_bt:%d, wl_act:%d\n", + __func__, phy_map, wl_state, bt_state, wlact_state); - switch (wlact_state) { - case BTC_WLACT_HW: - b[i].wlan_act_en = 0; - b[i].wlan_act = 0; - break; - case BTC_WLACT_SW_LO: - b[i].wlan_act_en = 1; - b[i].wlan_act = 0; - break; - case BTC_WLACT_SW_HI: - b[i].wlan_act_en = 1; - b[i].wlan_act = 1; - break; + if (rtwdev->chip->para_ver & BTC_FEAT_H2C_MACRO) { + if (o->rf_band[BTC_RF_S0] == 1 && o->rf_band[BTC_RF_S1] == 0) + o->rf_gbt_source = BTC_RF_S1; + else + o->rf_gbt_source = BTC_RF_S0; + + memcpy(o->wlact_set, dm->wlact_set, sizeof(o->wlact_set)); + + if (btc->ver->fcxosi == 6) { + memcpy(o->gnt_set_be, dm->gnt_set, sizeof(o->gnt_set_be)); + return; + } else if (btc->ver->fcxosi == 1) { + for (i = 0; i < RTW89_PHY_NUM; i++) { /* gnt[MAC0/MAC1] */ + mg.band[i].gnt_bt_sw_en = dm->gnt_set[i].gnt_bt0_sw_en; + mg.band[i].gnt_bt = dm->gnt_set[i].gnt_bt0; + mg.band[i].gnt_wl_sw_en = dm->gnt_set[i].gnt_wl_sw_en; + mg.band[i].gnt_wl = dm->gnt_set[i].gnt_wl; + memcpy(o->gnt_set, mg.band, sizeof(o->gnt_set)); } + return; } } - if (!btc->ver->fcxosi) { - rtw89_mac_cfg_gnt_v2(rtwdev, &dm->gnt); - return; + for (i = 0; i < RTW89_PHY_NUM; i++) { /* gnt[MAC0/MAC1] */ + mg.band[i].gnt_bt_sw_en = dm->gnt_set[i].gnt_bt0_sw_en; + mg.band[i].gnt_bt = dm->gnt_set[i].gnt_bt0; + mg.band[i].gnt_wl_sw_en = dm->gnt_set[i].gnt_wl_sw_en; + mg.band[i].gnt_wl = dm->gnt_set[i].gnt_wl; + } + for (i = 0; i < BTC_ALL_BT; i++) { /* wlact[BT0/BT1] */ + wlact_set = &dm->wlact_set[i]; + mg.bt[i].wlan_act_en = wlact_set->wlan_act_en; + mg.bt[i].wlan_act = wlact_set->wlan_act; } - memcpy(osi->gnt_set, dm->gnt.band, sizeof(osi->gnt_set)); - - for (i = 0; i < BTC_ALL_BT; i++) { - osi->wlact_set[i].wlan_act_en = dm->gnt.bt[i].wlan_act_en; - osi->wlact_set[i].wlan_act = dm->gnt.bt[i].wlan_act; - } - - /* GBT source should be GBT_S1 in 1+1 (HWB0:5G + HWB1:2G) case */ - if (osi->rf_band[BTC_RF_S0] == 1 && - osi->rf_band[BTC_RF_S1] == 0) - osi->rf_gbt_source = BTC_RF_S1; - else - osi->rf_gbt_source = BTC_RF_S0; + rtw89_chip_mac_cfg_gnt(rtwdev, &mg); } #define BTC_TDMA_WLROLE_MAX 3 @@ -4961,157 +4958,22 @@ static void _set_bt_plut(struct rtw89_dev *rtwdev, u8 phy_map, } } -static void _set_ant_v0(struct rtw89_dev *rtwdev, bool force_exec, - u8 phy_map, u8 type) +static void _set_ant(struct rtw89_dev *rtwdev, bool force_exec, + u8 phy_map, u8 type) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_wl_role_info *r = &btc->cx.wl.role_info; - struct rtw89_btc_bt_info *bt = &cx->bt0; - struct rtw89_btc_wl_dbcc_info *wl_dinfo = &wl->dbcc_info; - u8 gwl, gwl0, gwl1, gbt, plt_ctrl, i, b2g = 0; - u32 ant_path_type; - - ant_path_type = ((phy_map << 8) + type); - - if (btc->dm.run_reason == BTC_RSN_NTFY_POWEROFF || - btc->dm.run_reason == BTC_RSN_NTFY_RADIO_STATE || - btc->dm.run_reason == BTC_RSN_CMD_SET_COEX || r->dbcc_chg) - force_exec = FC_EXEC; - - if (!force_exec && ant_path_type == dm->set_ant_path) { - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): return by no change!!\n", - __func__); - return; - } else if (bt->rfk_info.map.run) { - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): return by bt rfk!!\n", __func__); - return; - } else if (btc->dm.run_reason != BTC_RSN_NTFY_WL_RFK && - wl->rfk_info.state != BTC_WRFK_STOP) { - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): return by wl rfk!!\n", __func__); - return; - } - - dm->set_ant_path = ant_path_type; - - rtw89_debug(rtwdev, - RTW89_DBG_BTC, - "[BTC], %s(): path=0x%x, set_type=0x%x\n", - __func__, phy_map, dm->set_ant_path & 0xff); - - switch (type) { - case BTC_ANT_WPOWERON: - rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_BT); - break; - case BTC_ANT_WINIT: - if (bt->enable.now) - _set_gnt(rtwdev, phy_map, BTC_GNT_SW_LO, BTC_GNT_SW_HI); - else - _set_gnt(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_SW_LO); - - rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_WL); - _set_bt_plut(rtwdev, BTC_PHY_ALL, BTC_PLT_BT, BTC_PLT_BT); - break; - case BTC_ANT_WONLY: - _set_gnt(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_SW_LO); - rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_WL); - _set_bt_plut(rtwdev, BTC_PHY_ALL, BTC_PLT_NONE, BTC_PLT_NONE); - break; - case BTC_ANT_WOFF: - rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_BT); - _set_bt_plut(rtwdev, BTC_PHY_ALL, BTC_PLT_NONE, BTC_PLT_NONE); - break; - case BTC_ANT_W2G: - rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_WL); - if (rtwdev->dbcc_en) { - for (i = 0; i < RTW89_PHY_NUM; i++) { - b2g = (wl_dinfo->real_band[i] == RTW89_BAND_2G); - - gwl = b2g ? BTC_GNT_HW : BTC_GNT_SW_HI; - gbt = b2g ? BTC_GNT_HW : BTC_GNT_SW_HI; - /* BT should control by GNT_BT if WL_2G at S0 */ - if (i == 1 && - wl_dinfo->real_band[0] == RTW89_BAND_2G && - wl_dinfo->real_band[1] == RTW89_BAND_5G) - gbt = BTC_GNT_HW; - _set_gnt(rtwdev, BIT(i), gwl, gbt); - plt_ctrl = b2g ? BTC_PLT_BT : BTC_PLT_NONE; - _set_bt_plut(rtwdev, BIT(i), - plt_ctrl, plt_ctrl); - } - } else { - _set_gnt(rtwdev, phy_map, BTC_GNT_HW, BTC_GNT_HW); - _set_bt_plut(rtwdev, BTC_PHY_ALL, - BTC_PLT_BT, BTC_PLT_BT); - } - break; - case BTC_ANT_W5G: - rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_WL); - _set_gnt(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_HW); - _set_bt_plut(rtwdev, BTC_PHY_ALL, BTC_PLT_NONE, BTC_PLT_NONE); - break; - case BTC_ANT_W25G: - rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_WL); - _set_gnt(rtwdev, phy_map, BTC_GNT_HW, BTC_GNT_HW); - _set_bt_plut(rtwdev, BTC_PHY_ALL, - BTC_PLT_GNT_WL, BTC_PLT_GNT_WL); - break; - case BTC_ANT_FREERUN: - rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_WL); - _set_gnt(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_SW_HI); - _set_bt_plut(rtwdev, BTC_PHY_ALL, BTC_PLT_NONE, BTC_PLT_NONE); - break; - case BTC_ANT_WRFK: - case BTC_ANT_WRFK2: - rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_WL); - _set_gnt(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_SW_LO); - _set_bt_plut(rtwdev, phy_map, BTC_PLT_NONE, BTC_PLT_NONE); - break; - case BTC_ANT_PTA: - default: - gbt = BTC_GNT_HW; - if ((rtwdev->chip->para_ver & BTC_FEAT_MULTI_PTA) || - !r->dbcc_en) { - gwl = BTC_GNT_HW; - _set_gnt(rtwdev, BTC_PHY_ALL, gwl, gbt); - } else { - /* for DBCC Only-1-PTA */ - if (r->dbcc_2g_phy == RTW89_PHY_0) { - gwl0 = BTC_GNT_HW; - gwl1 = BTC_GNT_SW_HI; - } else { - gwl0 = BTC_GNT_SW_HI; - gwl1 = BTC_GNT_HW; - } - _set_gnt(rtwdev, BTC_PHY_0, gwl0, gbt); - _set_gnt(rtwdev, BTC_PHY_1, gwl1, gbt); - } - rtw89_chip_cfg_ctrl_path(rtwdev, BTC_CTRL_BY_WL); - _set_bt_plut(rtwdev, phy_map, BTC_PLT_NONE, BTC_PLT_NONE); - break; - } -} - -static void _set_ant_v1(struct rtw89_dev *rtwdev, bool force_exec, - u8 phy_map, u8 type) -{ - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; u32 ant_path_type = rtw89_get_antpath_type(phy_map, type); - struct rtw89_btc_wl_dbcc_info *wl_dinfo = &wl->dbcc_info; struct rtw89_btc_dm *dm = &btc->dm; u8 gwl = BTC_GNT_HW, gwl0, gwl1, gbt; + bool cx_ctrl; - if (btc->dm.run_reason == BTC_RSN_NTFY_POWEROFF || - btc->dm.run_reason == BTC_RSN_NTFY_RADIO_STATE || - btc->dm.run_reason == BTC_RSN_CMD_SET_COEX || wl_rinfo->dbcc_chg) + if (btc->cli_h2c_cmd || wl_rinfo->dbcc_chg || + dm->run_reason == BTC_RSN_NTFY_POWEROFF || + dm->run_reason == BTC_RSN_NTFY_RADIO_STATE || + dm->run_reason == BTC_RSN_CMD_SET_COEX) force_exec = FC_EXEC; if (wl_rinfo->link_mode != BTC_WLINK_DB_MCC && @@ -5123,10 +4985,6 @@ static void _set_ant_v1(struct rtw89_dev *rtwdev, bool force_exec, "[BTC], %s(): return by no change!!\n", __func__); return; - } else if (bt->rfk_info.map.run) { - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): return by bt rfk!!\n", __func__); - return; } else if (btc->dm.run_reason != BTC_RSN_NTFY_WL_RFK && wl->rfk_info.state != BTC_WRFK_STOP) { rtw89_debug(rtwdev, RTW89_DBG_BTC, @@ -5141,64 +4999,38 @@ static void _set_ant_v1(struct rtw89_dev *rtwdev, bool force_exec, __func__, phy_map, dm->set_ant_path & 0xff); switch (type) { + case BTC_ANT_WPOWERON: + cx_ctrl = BTC_CTRL_BY_BT; + break; case BTC_ANT_WINIT: + cx_ctrl = BTC_CTRL_BY_WL; /* To avoid BT MP driver case (bt_enable but no mailbox) */ - if (bt->enable.now && bt->run_patch_code) - _set_gnt_v1(rtwdev, phy_map, BTC_GNT_SW_LO, BTC_GNT_SW_HI, - BTC_WLACT_SW_LO); + if ((cx->bt0.enable.now && cx->bt0.run_patch_code) || + (cx->bt1.enable.now && cx->bt1.run_patch_code)) + _set_gnt(rtwdev, BTC_PHY_ALL, BTC_GNT_SW_LO, BTC_GNT_SW_HI, + BTC_WLACT_SW_LO); else - _set_gnt_v1(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_SW_LO, - BTC_WLACT_SW_HI); + _set_gnt(rtwdev, BTC_PHY_ALL, BTC_GNT_SW_HI, BTC_GNT_SW_LO, + BTC_WLACT_SW_HI); break; case BTC_ANT_WONLY: - _set_gnt_v1(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_SW_LO, - BTC_WLACT_SW_HI); + cx_ctrl = BTC_CTRL_BY_WL; + _set_gnt(rtwdev, BTC_PHY_ALL, BTC_GNT_SW_HI, BTC_GNT_SW_LO, + BTC_WLACT_SW_HI); break; case BTC_ANT_WOFF: - _set_gnt_v1(rtwdev, phy_map, BTC_GNT_SW_LO, BTC_GNT_SW_HI, - BTC_WLACT_SW_LO); - break; - case BTC_ANT_W2G: - case BTC_ANT_W25G: - if (wl_rinfo->dbcc_en) { - if (wl_dinfo->real_band[RTW89_PHY_0] == RTW89_BAND_2G) - gwl = BTC_GNT_HW; - else - gwl = BTC_GNT_SW_HI; - _set_gnt_v1(rtwdev, BTC_PHY_0, gwl, BTC_GNT_HW, BTC_WLACT_HW); - - if (wl_dinfo->real_band[RTW89_PHY_1] == RTW89_BAND_2G) - gwl = BTC_GNT_HW; - else - gwl = BTC_GNT_SW_HI; - _set_gnt_v1(rtwdev, BTC_PHY_1, gwl, BTC_GNT_HW, BTC_WLACT_HW); - } else { - gwl = BTC_GNT_HW; - _set_gnt_v1(rtwdev, phy_map, gwl, BTC_GNT_HW, BTC_WLACT_HW); - } - break; - case BTC_ANT_W5G: - _set_gnt_v1(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_HW, BTC_WLACT_HW); - break; - case BTC_ANT_FREERUN: - _set_gnt_v1(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_SW_HI, - BTC_WLACT_SW_LO); - break; - case BTC_ANT_WRFK: - _set_gnt_v1(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_SW_LO, - BTC_WLACT_HW); - break; - case BTC_ANT_WRFK2: - _set_gnt_v1(rtwdev, phy_map, BTC_GNT_SW_HI, BTC_GNT_SW_LO, - BTC_WLACT_SW_HI); /* no BT-Tx */ + cx_ctrl = BTC_CTRL_BY_BT; + _set_gnt(rtwdev, BTC_PHY_ALL, BTC_GNT_SW_LO, BTC_GNT_SW_HI, + BTC_WLACT_SW_LO); break; case BTC_ANT_PTA: default: + cx_ctrl = BTC_CTRL_BY_WL; gbt = BTC_GNT_HW; if ((rtwdev->chip->para_ver & BTC_FEAT_MULTI_PTA) || !wl_rinfo->dbcc_en) { gwl = BTC_GNT_HW; - _set_gnt_v1(rtwdev, BTC_PHY_ALL, gwl, gbt, BTC_WLACT_HW); + _set_gnt(rtwdev, BTC_PHY_ALL, gwl, gbt, BTC_WLACT_HW); } else { /* for DBCC Only-1-PTA */ if (wl_rinfo->dbcc_2g_phy == RTW89_PHY_0) { @@ -5208,24 +5040,36 @@ static void _set_ant_v1(struct rtw89_dev *rtwdev, bool force_exec, gwl0 = BTC_GNT_SW_HI; gwl1 = BTC_GNT_HW; } - _set_gnt_v1(rtwdev, BTC_PHY_0, gwl0, gbt, BTC_WLACT_HW); - _set_gnt_v1(rtwdev, BTC_PHY_1, gwl1, gbt, BTC_WLACT_HW); + _set_gnt(rtwdev, BTC_PHY_0, gwl0, gbt, BTC_WLACT_HW); + _set_gnt(rtwdev, BTC_PHY_1, gwl1, gbt, BTC_WLACT_HW); } break; + case BTC_ANT_FDD: + cx_ctrl = BTC_CTRL_BY_WL; + _set_gnt(rtwdev, BTC_PHY_ALL, BTC_GNT_SW_HI, BTC_GNT_HW, + BTC_WLACT_HW); + break; + case BTC_ANT_FREERUN: + cx_ctrl = BTC_CTRL_BY_WL; + _set_gnt(rtwdev, BTC_PHY_ALL, BTC_GNT_SW_HI, BTC_GNT_SW_HI, + BTC_WLACT_SW_LO); + break; + case BTC_ANT_WRFK: + cx_ctrl = BTC_CTRL_BY_WL; + _set_gnt(rtwdev, BTC_PHY_ALL, BTC_GNT_SW_HI, BTC_GNT_SW_LO, + BTC_WLACT_HW); + break; + case BTC_ANT_WRFK2: + cx_ctrl = BTC_CTRL_BY_WL; + _set_gnt(rtwdev, BTC_PHY_ALL, BTC_GNT_SW_HI, BTC_GNT_SW_LO, + BTC_WLACT_SW_HI); /* no BT-Tx */ + break; } + rtw89_chip_cfg_ctrl_path(rtwdev, cx_ctrl); _set_bt_plut(rtwdev, phy_map, BTC_PLT_GNT_WL, BTC_PLT_GNT_WL); } -static void _set_ant(struct rtw89_dev *rtwdev, bool force_exec, - u8 phy_map, u8 type) -{ - if (rtwdev->chip->chip_id >= RTL8922A) - _set_ant_v1(rtwdev, force_exec, phy_map, type); - else - _set_ant_v0(rtwdev, force_exec, phy_map, type); -} - static void _action_wl_only(struct rtw89_dev *rtwdev) { _set_ant(rtwdev, FC_EXEC, BTC_PHY_ALL, BTC_ANT_WONLY); @@ -5619,7 +5463,7 @@ static void _action_bt_a2dp_pan_hid(struct rtw89_dev *rtwdev) static void _action_wl_5g(struct rtw89_dev *rtwdev) { - _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_W5G); + _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_FDD); _set_policy(rtwdev, BTC_CXP_OFF_EQ0, BTC_ACT_WL_5G); } @@ -9674,7 +9518,7 @@ static const char *id_to_ant(u32 id) CASE_BTC_ANTPATH_STR(WONLY); CASE_BTC_ANTPATH_STR(WOFF); CASE_BTC_ANTPATH_STR(W2G); - CASE_BTC_ANTPATH_STR(W5G); + CASE_BTC_ANTPATH_STR(FDD); CASE_BTC_ANTPATH_STR(W25G); CASE_BTC_ANTPATH_STR(FREERUN); CASE_BTC_ANTPATH_STR(WRFK); @@ -10898,6 +10742,7 @@ static int _show_fw_dm_msg(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) static void _get_gnt(struct rtw89_dev *rtwdev, struct rtw89_mac_ax_coex_gnt *gnt_cfg) { const struct rtw89_chip_info *chip = rtwdev->chip; + struct rtw89_mac_ax_wl_act *bt; struct rtw89_mac_ax_gnt *gnt; u32 val, status; @@ -10932,7 +10777,39 @@ static void _get_gnt(struct rtw89_dev *rtwdev, struct rtw89_mac_ax_coex_gnt *gnt gnt->gnt_bt = !!(status & B_AX_GNT_BT_RFC_S1); gnt->gnt_wl_sw_en = !!(val & B_AX_GNT_WL_RFC_S1_SWCTRL); gnt->gnt_wl = !!(status & B_AX_GNT_WL_RFC_S1); - } else { + + bt = &gnt_cfg->bt[0]; + bt->wlan_act_en = !!(val & B_BE_WL_ACT_SWCTRL); + bt->wlan_act = !!(status & B_BE_WL_ACT_VAL); + + bt = &gnt_cfg->bt[1]; + bt->wlan_act_en = !!(val & B_BE_WL_ACT2_SWCTRL); + bt->wlan_act = !!(status & B_BE_WL_ACT2_VAL); + } else if (chip->chip_id == RTL8922A) { + val = rtw89_read32(rtwdev, R_BE_GNT_SW_CTRL); + status = rtw89_read32(rtwdev, R_BE_PTA_GNT_VAL); + + gnt = &gnt_cfg->band[0]; + gnt->gnt_bt_sw_en = !!(val & B_BE_GNT_BT_BB0_SWCTRL); + gnt->gnt_bt = !!(status & B_BE_GNT_BT_BB0_VAL); + gnt->gnt_wl_sw_en = !!(val & B_BE_GNT_WL_BB0_SWCTRL); + gnt->gnt_wl = !!(status & B_BE_GNT_WL_BB0_VAL); + + gnt = &gnt_cfg->band[1]; + gnt->gnt_bt_sw_en = !!(val & B_BE_GNT_BT_BB1_SWCTRL); + gnt->gnt_bt = !!(status & B_BE_GNT_BT_BB1_VAL); + gnt->gnt_wl_sw_en = !!(val & B_BE_GNT_WL_BB1_SWCTRL); + gnt->gnt_wl = !!(status & B_BE_GNT_WL_BB1_VAL); + + bt = &gnt_cfg->bt[0]; + bt->wlan_act_en = !!(val & B_BE_WL_ACT_SWCTRL); + bt->wlan_act = !!(status & B_BE_WL_ACT_VAL); + + bt = &gnt_cfg->bt[1]; + bt->wlan_act_en = !!(val & B_BE_WL_ACT2_SWCTRL); + bt->wlan_act = !!(status & B_BE_WL_ACT2_VAL); + } else if (chip->chip_id == RTL8922D) { + /* Get from firmware */ return; } } @@ -11181,7 +11058,6 @@ static int _show_mreg_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) struct rtw89_btc_bt_info *bt0 = &cx->bt0; struct rtw89_btc_bt_info *bt1 = &cx->bt1; struct rtw89_btc_wl_info *wl = &cx->wl; - struct rtw89_mac_ax_gnt *gnt = NULL; struct rtw89_btc_dm *dm = &btc->dm; char *p = buf, *end = buf + bufsz; u8 i, type, cnt = 0; @@ -11219,18 +11095,19 @@ static int _show_mreg_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) wl->pta_req_mac, id_to_polut(wl->bt_polut_type[wl->pta_req_mac])); - gnt = &dm->gnt.band[RTW89_PHY_0]; - p += scnprintf(p, end - p, ", phy-0[gnt_wl:%s-%d/gnt_bt:%s-%d]", - gnt->gnt_wl_sw_en ? "SW" : "HW", gnt->gnt_wl, - gnt->gnt_bt_sw_en ? "SW" : "HW", gnt->gnt_bt); + dm->gnt_set[RTW89_PHY_0].gnt_wl_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_0].gnt_wl, + dm->gnt_set[RTW89_PHY_0].gnt_bt0_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_0].gnt_bt0); if (rtwdev->dbcc_en) { - gnt = &dm->gnt.band[RTW89_PHY_1]; p += scnprintf(p, end - p, ", phy-1[gnt_wl:%s-%d/gnt_bt:%s-%d]", - gnt->gnt_wl_sw_en ? "SW" : "HW", gnt->gnt_wl, - gnt->gnt_bt_sw_en ? "SW" : "HW", gnt->gnt_bt); + dm->gnt_set[RTW89_PHY_1].gnt_wl_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_1].gnt_wl, + dm->gnt_set[RTW89_PHY_1].gnt_bt0_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_1].gnt_bt0); } pcinfo = &pfwinfo->rpt_fbtc_mregval.cinfo; diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 676d3ba855d7..a928140300b9 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -3481,7 +3481,9 @@ struct rtw89_btc_dm { union rtw89_btc_fbtc_slot_u slot_now; struct rtw89_btc_fbtc_tdma tdma; struct rtw89_btc_fbtc_tdma tdma_now; - struct rtw89_mac_ax_coex_gnt gnt; + struct rtw89_btc_gnt_ctrl gnt_set[RTW89_MAC_AX_COEX_GNT_NR]; + struct rtw89_btc_gnt_ctrl gnt_val[RTW89_MAC_AX_COEX_GNT_NR]; + struct rtw89_mac_ax_wl_act wlact_set[BTC_ALL_BT_EZL]; union rtw89_btc_init_info_u init_info; /* pass to wl_fw if offload */ struct rtw89_btc_rf_trx_para_v9 rf_trx_para; struct rtw89_btc_wl_tx_limit_para wl_tx_limit; diff --git a/drivers/net/wireless/realtek/rtw89/mac.c b/drivers/net/wireless/realtek/rtw89/mac.c index 6e3da5e4a1b3..b9cd7fd8d76b 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.c +++ b/drivers/net/wireless/realtek/rtw89/mac.c @@ -6538,8 +6538,6 @@ int rtw89_mac_cfg_gnt_v1(struct rtw89_dev *rtwdev, if (gnt_cfg->band[0].gnt_bt) val |= B_AX_GNT_BT_RFC_S0_VAL | B_AX_GNT_BT_RX_VAL | B_AX_GNT_BT_TX_VAL; - else - val |= B_AX_WL_ACT_VAL; if (gnt_cfg->band[0].gnt_bt_sw_en) val |= B_AX_GNT_BT_RFC_S0_SWCTRL | B_AX_GNT_BT_RX_SWCTRL | @@ -6556,8 +6554,6 @@ int rtw89_mac_cfg_gnt_v1(struct rtw89_dev *rtwdev, if (gnt_cfg->band[1].gnt_bt) val |= B_AX_GNT_BT_RFC_S1_VAL | B_AX_GNT_BT_RX_VAL | B_AX_GNT_BT_TX_VAL; - else - val |= B_AX_WL_ACT_VAL; if (gnt_cfg->band[1].gnt_bt_sw_en) val |= B_AX_GNT_BT_RFC_S1_SWCTRL | B_AX_GNT_BT_RX_SWCTRL | @@ -6571,6 +6567,15 @@ int rtw89_mac_cfg_gnt_v1(struct rtw89_dev *rtwdev, val |= B_AX_GNT_WL_RFC_S1_SWCTRL | B_AX_GNT_WL_RX_SWCTRL | B_AX_GNT_WL_TX_SWCTRL | B_AX_GNT_WL_BB_SWCTRL; + if (gnt_cfg->bt[0].wlan_act_en) + val |= B_AX_WL_ACT_SWCTRL; + if (gnt_cfg->bt[0].wlan_act) + val |= B_AX_WL_ACT_VAL; + if (gnt_cfg->bt[1].wlan_act_en) + val |= B_AX_WL_ACT2_SWCTRL; + if (gnt_cfg->bt[1].wlan_act) + val |= B_AX_WL_ACT2_VAL; + rtw89_write32(rtwdev, R_AX_GNT_SW_CTRL, val); return 0; @@ -6645,22 +6650,20 @@ EXPORT_SYMBOL(rtw89_mac_cfg_ctrl_path); int rtw89_mac_cfg_ctrl_path_v1(struct rtw89_dev *rtwdev, bool wl) { - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_mac_ax_gnt *g = dm->gnt.band; + struct rtw89_mac_ax_coex_gnt gnt = {}; int i; if (wl) return 0; for (i = 0; i < RTW89_PHY_NUM; i++) { - g[i].gnt_bt_sw_en = 1; - g[i].gnt_bt = 1; - g[i].gnt_wl_sw_en = 1; - g[i].gnt_wl = 0; + gnt.band[i].gnt_bt_sw_en = 1; + gnt.band[i].gnt_bt = 1; + gnt.band[i].gnt_wl_sw_en = 1; + gnt.band[i].gnt_wl = 0; } - return rtw89_mac_cfg_gnt_v1(rtwdev, &dm->gnt); + return rtw89_mac_cfg_gnt_v1(rtwdev, &gnt); } EXPORT_SYMBOL(rtw89_mac_cfg_ctrl_path_v1); diff --git a/drivers/net/wireless/realtek/rtw89/mac_be.c b/drivers/net/wireless/realtek/rtw89/mac_be.c index 4bdf20b7ba6d..33513b283d84 100644 --- a/drivers/net/wireless/realtek/rtw89/mac_be.c +++ b/drivers/net/wireless/realtek/rtw89/mac_be.c @@ -2466,29 +2466,26 @@ EXPORT_SYMBOL(rtw89_mac_cfg_gnt_v3); int rtw89_mac_cfg_ctrl_path_v2(struct rtw89_dev *rtwdev, bool wl) { - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_mac_ax_gnt *g = dm->gnt.band; - struct rtw89_mac_ax_wl_act *gbt = dm->gnt.bt; const struct rtw89_chip_info *chip = rtwdev->chip; + struct rtw89_mac_ax_coex_gnt gnt = {}; int i; if (wl) return 0; for (i = 0; i < RTW89_PHY_NUM; i++) { - g[i].gnt_bt_sw_en = 1; - g[i].gnt_bt = 1; - g[i].gnt_wl_sw_en = 1; - g[i].gnt_wl = 0; - gbt[i].wlan_act = 1; - gbt[i].wlan_act_en = 0; + gnt.band[i].gnt_bt_sw_en = 1; + gnt.band[i].gnt_bt = 1; + gnt.band[i].gnt_wl_sw_en = 1; + gnt.band[i].gnt_wl = 0; + gnt.bt[i].wlan_act = 1; + gnt.bt[i].wlan_act_en = 0; } if (chip->chip_id == RTL8922A) - return rtw89_mac_cfg_gnt_v2(rtwdev, &dm->gnt); + return rtw89_mac_cfg_gnt_v2(rtwdev, &gnt); else - return rtw89_mac_cfg_gnt_v3(rtwdev, &dm->gnt); + return rtw89_mac_cfg_gnt_v3(rtwdev, &gnt); } EXPORT_SYMBOL(rtw89_mac_cfg_ctrl_path_v2); From 5d5a5eb7ae2ed66ec77febd76807804dcdd0279f Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Sun, 12 Jul 2026 11:05:00 +0800 Subject: [PATCH 0346/1433] wifi: rtw89: coex: Rearrange Bluetooth firmware report entry To enable/disable firmware report once at the end of mechanism round. This can make the logic more clearly, and make sure every round the mechanism running can refresh the settings. It can avoid some report missing after driver status change. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260712030506.43438-6-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 209 +++++++++++----------- 1 file changed, 104 insertions(+), 105 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 2ca6090e9f62..ff3345824a2a 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -2659,37 +2659,27 @@ static void rtw89_btc_fw_set_slots(struct rtw89_dev *rtwdev) } static void rtw89_btc_fw_en_rpt(struct rtw89_dev *rtwdev, - u32 rpt_map, bool rpt_state) + bool force_exec, u32 rpt_map) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_smap *wl_smap = &btc->cx.wl.status.map; struct rtw89_btc_btf_fwinfo *fwinfo = &btc->fwinfo; union rtw89_fbtc_rtp_ctrl r; - u32 val, bit_map; int ret; + u32 i; - if ((wl_smap->rf_off || wl_smap->lps != BTC_LPS_OFF) && rpt_state != 0) + if (!force_exec && rpt_map == fwinfo->rpt_en_map) return; - bit_map = rtw89_btc_fw_rpt_ver(rtwdev, rpt_map); + rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): rpt_map=%x\n", + __func__, fwinfo->rpt_en_map); - rtw89_debug(rtwdev, RTW89_DBG_BTC, - "[BTC], %s(): rpt_map=%x, rpt_state=%x\n", - __func__, rpt_map, rpt_state); - - if (rpt_state) - val = fwinfo->rpt_en_map | bit_map; - else - val = fwinfo->rpt_en_map & ~bit_map; - - if (val == fwinfo->rpt_en_map) - return; - - if (btc->ver->fcxbtcrpt == 7 || btc->ver->fcxbtcrpt == 8) { + if (btc->ver->fcxbtcrpt == 7 || + btc->ver->fcxbtcrpt == 8 || + btc->ver->fcxbtcrpt == 11) { r.v8.type = SET_REPORT_EN; r.v8.fver = btc->ver->fcxbtcrpt; r.v8.len = sizeof(r.v8.map); - r.v8.map = cpu_to_le32(val); + r.v8.map = cpu_to_le32(rpt_map); ret = _send_fw_cmd(rtwdev, BTFC_SET, SET_REPORT_EN, &r.v8, sizeof(r.v8)); } else { @@ -2697,14 +2687,21 @@ static void rtw89_btc_fw_en_rpt(struct rtw89_dev *rtwdev, r.v1.fver = 5; else r.v1.fver = btc->ver->fcxbtcrpt; - r.v1.enable = cpu_to_le32(val); - r.v1.para = cpu_to_le32(rpt_state); - ret = _send_fw_cmd(rtwdev, BTFC_SET, SET_REPORT_EN, &r.v1, - sizeof(r.v1)); + + for (i = 0; i < RPT_EN_MONITER; i++) { + r.v1.enable = cpu_to_le32(i); + if (rpt_map & BIT(i)) + r.v1.para = cpu_to_le32(1); + else + r.v1.para = 0; + + ret = _send_fw_cmd(rtwdev, BTFC_SET, SET_REPORT_EN, + &r.v1, sizeof(r.v1)); + } } if (!ret) - fwinfo->rpt_en_map = val; + fwinfo->rpt_en_map = rpt_map; } static void btc_fw_set_monreg(struct rtw89_dev *rtwdev) @@ -2766,8 +2763,6 @@ static void btc_fw_set_monreg(struct rtw89_dev *rtwdev) rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): sz=%d ulen=%d n=%d\n", __func__, sz, ulen, n); - - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_MREG, 1); } static void _update_dm_step(struct rtw89_dev *rtwdev, @@ -5882,15 +5877,72 @@ static void _update_zb_coex_tbl(struct rtw89_dev *rtwdev) rtw89_write32(rtwdev, R_BTC_ZB_COEX_TBL_1, zb_tbl1); } +static void _set_fw_report_map(struct rtw89_dev *rtwdev) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_wl_smap *wl_smap = &btc->cx.wl.status.map; + struct rtw89_btc_bt_info *bt0 = &btc->cx.bt0; + struct rtw89_btc_bt_info *bt1 = &btc->cx.bt1; + u32 rpt_map = btc->fwinfo.rpt_en_map; + u32 bt_rom_code_id, bt_fw_ver; + bool bt_ver_unknown = false; + u32 bitmap; + + if (wl_smap->rf_off == 1 || wl_smap->lps != BTC_LPS_OFF) { + rtw89_btc_fw_en_rpt(rtwdev, false, 0); + return; + } + + rpt_map |= rtw89_btc_fw_rpt_ver(rtwdev, RPT_EN_MREG); + + bitmap = rtw89_btc_fw_rpt_ver(rtwdev, RPT_EN_BT_SCAN_INFO); + if ((bt0->run_patch_code && bt0->enable.now) || + (bt1->run_patch_code && bt1->enable.now)) + rpt_map |= bitmap; + else + rpt_map &= ~bitmap; + + bt_rom_code_id = chip_id_to_bt_rom_code_id(rtwdev->btc.ver->chip_id); + bt_fw_ver = bt0->ver_info.fw & 0xffff; + if (bt_fw_ver == 0 || + (bt_fw_ver == bt_rom_code_id && bt0->run_patch_code)) + bt_ver_unknown = true; + + bitmap = rtw89_btc_fw_rpt_ver(rtwdev, RPT_EN_BT_VER_INFO); + if (bt0->enable.now && bt_ver_unknown) + rpt_map |= bitmap; + else + rpt_map &= ~bitmap; + + bitmap = rtw89_btc_fw_rpt_ver(rtwdev, RPT_EN_BT_AFH_MAP) | + rtw89_btc_fw_rpt_ver(rtwdev, RPT_EN_BT_AFH_MAP_LE) | + rtw89_btc_fw_rpt_ver(rtwdev, RPT_EN_BT_TX_PWR_LVL); + if (bt0->link_info.link_cnt.now || + bt0->link_info_56g.link_cnt.now || + bt1->link_info.link_cnt.now || + bt1->link_info_56g.link_cnt.now) + rpt_map |= bitmap; + else + rpt_map &= ~bitmap; + + bitmap = rtw89_btc_fw_rpt_ver(rtwdev, RPT_EN_BT_DEVICE_INFO); + if ((bt0->link_info.a2dp_desc.exist && + bt0->link_info.a2dp_desc.play_latency) || + (bt1->link_info.a2dp_desc.exist && + bt1->link_info.a2dp_desc.play_latency)) + rpt_map |= bitmap; + else + rpt_map &= ~bitmap; + + rtw89_btc_fw_en_rpt(rtwdev, false, rpt_map); +} + static void _action_common(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_wl_role_info *rinfo = &wl->role_info; - struct rtw89_btc_wl_smap *wl_smap = &wl->status.map; - struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_dm *dm = &btc->dm; - u32 bt_rom_code_id, bt_fw_ver; u8 i; _wl_req_mac(rtwdev, rinfo->pta_req_band); @@ -5902,27 +5954,13 @@ static void _action_common(struct rtw89_dev *rtwdev) _set_bt_rx_agc(rtwdev); _set_rf_trx_para(rtwdev); _set_bt_rx_scan_pri(rtwdev); - - bt_rom_code_id = chip_id_to_bt_rom_code_id(rtwdev->btc.ver->chip_id); - bt_fw_ver = bt->ver_info.fw & 0xffff; - if (bt->enable.now && - (bt_fw_ver == 0 || - (bt_fw_ver == bt_rom_code_id && bt->run_patch_code && rtwdev->chip->scbd))) - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_VER_INFO, 1); - else - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_VER_INFO, 0); + _set_fw_report_map(rtwdev); if (dm->run_reason == BTC_RSN_NTFY_INIT || dm->run_reason == BTC_RSN_NTFY_RADIO_STATE || - dm->run_reason == BTC_RSN_NTFY_POWEROFF) { + dm->run_reason == BTC_RSN_NTFY_POWEROFF) _fw_set_drv_info(rtwdev, CXDRVINFO_ROLE); - if (wl_smap->rf_off == 1 || wl_smap->lps != BTC_LPS_OFF) - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_ALL, 0); - else - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_MREG, 1); - } - for (i = BTC_BT_1ST; i <= BTC_BT_2ND; i++) _sned_h2c_w2bscbd(rtwdev, false, i); @@ -7730,8 +7768,6 @@ void rtw89_btc_ntfy_poweroff(struct rtw89_dev *rtwdev) _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_ALL, false); _run_coex(rtwdev, BTC_RSN_NTFY_POWEROFF); - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_ALL, 0); - btc->cx.wl.status.map.rf_off_pre = btc->cx.wl.status.map.rf_off; } @@ -8344,12 +8380,10 @@ void rtw89_btc_ntfy_radio_state(struct rtw89_dev *rtwdev, enum btc_rfctrl rf_sta } if (rf_state == BTC_RFCTRL_WL_ON) { - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_MREG, true); val = BTC_WSCB_ACTIVE | BTC_WSCB_ON | BTC_WSCB_BTLOG; _write_scbd(rtwdev, BTC_ALL_BT, val, true); chip->ops->btc_init_cfg(rtwdev); } else { - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_ALL, false); if (rf_state == BTC_RFCTRL_FW_CTRL) _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_ACTIVE, false); else if (rf_state == BTC_RFCTRL_WL_OFF) @@ -8857,11 +8891,6 @@ static int _show_cx_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) (bt->ver_info.fw_coex >= ver->bt_desired ? "Match" : "Mismatch"), ver->bt_desired); - if (bt->enable.now && bt->ver_info.fw == 0) - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_VER_INFO, true); - else - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_VER_INFO, false); - ver_main = FIELD_GET(GENMASK(31, 24), wl->ver_info.fw); ver_sub = FIELD_GET(GENMASK(23, 16), wl->ver_info.fw); ver_hotfix = FIELD_GET(GENMASK(15, 8), wl->ver_info.fw); @@ -9056,7 +9085,6 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_bt_info *bt = &cx->bt0; struct rtw89_btc_wl_info *wl = &cx->wl; - u32 ver_main = FIELD_GET(GENMASK(31, 24), wl->ver_info.fw_coex); struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; union rtw89_btc_module_info *md = &btc->mdinfo; s8 br_dbm = bt->link_info.bt_txpwr_desc.br_dbm; @@ -9167,39 +9195,28 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) bt->bcnt[BTC_BCNT_LOPRI_TX], bt->bcnt[BTC_BCNT_POLLUTED]); - if (!bt->scan_info_update) { - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_SCAN_INFO, true); - p += scnprintf(p, end - p, "\n"); - } else { - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_SCAN_INFO, false); - if (ver->fcxbtscan == 1) { - p += scnprintf(p, end - p, - "(INQ:%d-%d/PAGE:%d-%d/LE:%d-%d/INIT:%d-%d)", - le16_to_cpu(bt->scan_info_v1[BTC_SCAN_INQ].win), - le16_to_cpu(bt->scan_info_v1[BTC_SCAN_INQ].intvl), - le16_to_cpu(bt->scan_info_v1[BTC_SCAN_PAGE].win), - le16_to_cpu(bt->scan_info_v1[BTC_SCAN_PAGE].intvl), - le16_to_cpu(bt->scan_info_v1[BTC_SCAN_BLE].win), - le16_to_cpu(bt->scan_info_v1[BTC_SCAN_BLE].intvl), - le16_to_cpu(bt->scan_info_v1[BTC_SCAN_INIT].win), - le16_to_cpu(bt->scan_info_v1[BTC_SCAN_INIT].intvl)); - } else if (ver->fcxbtscan == 2) { - p += scnprintf(p, end - p, - "(BG:%d-%d/INIT:%d-%d/LE:%d-%d)", - le16_to_cpu(bt->scan_info_v2[CXSCAN_BG].win), - le16_to_cpu(bt->scan_info_v2[CXSCAN_BG].intvl), - le16_to_cpu(bt->scan_info_v2[CXSCAN_INIT].win), - le16_to_cpu(bt->scan_info_v2[CXSCAN_INIT].intvl), - le16_to_cpu(bt->scan_info_v2[CXSCAN_LE].win), - le16_to_cpu(bt->scan_info_v2[CXSCAN_LE].intvl)); - } - p += scnprintf(p, end - p, "\n"); + if (ver->fcxbtscan == 1) { + p += scnprintf(p, end - p, + "(INQ:%d-%d/PAGE:%d-%d/LE:%d-%d/INIT:%d-%d)", + le16_to_cpu(bt->scan_info_v1[BTC_SCAN_INQ].win), + le16_to_cpu(bt->scan_info_v1[BTC_SCAN_INQ].intvl), + le16_to_cpu(bt->scan_info_v1[BTC_SCAN_PAGE].win), + le16_to_cpu(bt->scan_info_v1[BTC_SCAN_PAGE].intvl), + le16_to_cpu(bt->scan_info_v1[BTC_SCAN_BLE].win), + le16_to_cpu(bt->scan_info_v1[BTC_SCAN_BLE].intvl), + le16_to_cpu(bt->scan_info_v1[BTC_SCAN_INIT].win), + le16_to_cpu(bt->scan_info_v1[BTC_SCAN_INIT].intvl)); + } else if (ver->fcxbtscan == 2) { + p += scnprintf(p, end - p, + "(BG:%d-%d/INIT:%d-%d/LE:%d-%d)", + le16_to_cpu(bt->scan_info_v2[CXSCAN_BG].win), + le16_to_cpu(bt->scan_info_v2[CXSCAN_BG].intvl), + le16_to_cpu(bt->scan_info_v2[CXSCAN_INIT].win), + le16_to_cpu(bt->scan_info_v2[CXSCAN_INIT].intvl), + le16_to_cpu(bt->scan_info_v2[CXSCAN_LE].win), + le16_to_cpu(bt->scan_info_v2[CXSCAN_LE].intvl)); } - - if (ver_main >= 9 && bt_linfo->link_cnt.now) - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_TX_PWR_LVL, true); - else - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_TX_PWR_LVL, false); + p += scnprintf(p, end - p, "\n"); if (bt->bcnt[BTC_BCNT_TXPWR_UPDATE]) { p += scnprintf(p, end - p, @@ -9218,24 +9235,6 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) } p += scnprintf(p, end - p, "\n"); - if (bt_linfo->link_cnt.now || bt_linfo->status.map.ble_connect) - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_AFH_MAP, true); - else - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_AFH_MAP, false); - - if (ver->fcxbtafh == 2 && bt_linfo->status.map.ble_connect) - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_AFH_MAP_LE, true); - else - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_AFH_MAP_LE, false); - - if (bt_linfo->a2dp_desc.exist && - (bt_linfo->a2dp_desc.flush_time == 0 || - bt_linfo->a2dp_desc.vendor_id == 0 || - bt_linfo->a2dp_desc.play_latency == 1)) - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_DEVICE_INFO, true); - else - rtw89_btc_fw_en_rpt(rtwdev, RPT_EN_BT_DEVICE_INFO, false); - return p - buf; } From a67bb858ba4dbd64e5be901cab414110c06e86d3 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Sun, 12 Jul 2026 11:05:01 +0800 Subject: [PATCH 0347/1433] wifi: rtw89: coex: Refine send firmware command function Because the coexistence offload more register/ hardware setting I/O to firmware by coexistence itself, and it goes with the same entry with other control action, so the firmware command entry need to add different condition to judge should it followed coexistence TLV format or not. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260712030506.43438-7-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 107 +++++++++++++++++++++- drivers/net/wireless/realtek/rtw89/core.h | 4 + 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index ff3345824a2a..07d054f59eb5 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -936,6 +936,14 @@ static void _run_coex(struct rtw89_dev *rtwdev, static void _write_scbd(struct rtw89_dev *rtwdev, u8 bid, u32 val, bool state); static u8 _sned_h2c_w2bscbd(struct rtw89_dev *rtwdev, bool force_exec, u8 bid); static void _update_bt_scbd(struct rtw89_dev *rtwdev, u8 bid); +static const char *id_to_h2c(u32 id); + +static void _reset_h2c_macro(struct rtw89_btc *btc) +{ + btc->hbuf_len = 0; + btc->hbuf_cnt = 0; + memset(btc->hbuf, 0, BTC_H2C_MAXLEN); +} static int _send_fw_cmd(struct rtw89_dev *rtwdev, u8 h2c_class, u8 h2c_func, void *param, u16 len) @@ -945,6 +953,8 @@ static int _send_fw_cmd(struct rtw89_dev *rtwdev, u8 h2c_class, u8 h2c_func, struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &cx->wl; struct rtw89_btc_dm *dm = &btc->dm; + u8 h2c_func_mask, bid = BTC_BT_1ST; + u8 *buf = param; int ret; if (len > BTC_H2C_MAXLEN || len == 0) { @@ -966,7 +976,69 @@ static int _send_fw_cmd(struct rtw89_dev *rtwdev, u8 h2c_class, u8 h2c_func, return -EINVAL; } - ret = rtw89_fw_h2c_raw_with_hdr(rtwdev, h2c_class, h2c_func, param, len, + h2c_func_mask = h2c_func & (~BT_H2C_FUNC_BT2ND); + if (rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) + bid = !!(h2c_func & BT_H2C_FUNC_BT2ND); + + if (btc->io_oflld_type != BTC_IO_OFLD_BTC_H2C || btc->cli_h2c_cmd || + h2c_func_mask == SET_H2C_MACRO || + (h2c_func_mask == SET_DRV_INFO && buf[0] == CXDRVINFO_TRX) || + (h2c_func_mask == SET_IOFLD_SCBD && dm->scbd_write_instant) || + h2c_func_mask == SET_BT_LNA_CONSTRAIN) { + h2c_class = BTFC_SET; + + ret = rtw89_fw_h2c_raw_with_hdr(rtwdev, h2c_class, h2c_func, + buf, len, false, true); + + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s():class=%d, bt%d-func=%s, len=%d\n", + __func__, h2c_class, bid, + id_to_h2c(h2c_func_mask), len); + + if (h2c_func_mask == SET_H2C_MACRO) { + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s():Send H2C-MACRO cnt=%d, len=%d\n", + __func__, btc->hbuf_cnt, btc->hbuf_len); + _reset_h2c_macro(btc); /* clear H2C MACRO buffer */ + } + + if (ret != 0) { /* Send H2C fail */ + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s():return by rtw_hal_mac_send_h2c\n", + __func__); + btc->fwinfo.cnt_h2c_fail++; + return 0; + } + + btc->fwinfo.cnt_h2c++; + } else { /* Fill H2C MACRO buffer(TLV format) temporarily */ + if (btc->hbuf_cnt == 0) + _reset_h2c_macro(btc); + + /* Type:1 byte, Length:2 Bytes, Data:len bytes */ + if (btc->hbuf_len + len + 3 >= BTC_H2C_MAXLEN) { + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s():return by MACRO buf full(%d)\n", + __func__, btc->hbuf_len + len + 3); + btc->fwinfo.cnt_h2c_fail++; + return 0; + } + + btc->hbuf[btc->hbuf_len] = h2c_func; + btc->hbuf[btc->hbuf_len + 1] = len & GENMASK(7, 0); + btc->hbuf[btc->hbuf_len + 2] = (len & GENMASK(15, 8)) >> 8; + + memcpy(&btc->hbuf[btc->hbuf_len + 3], buf, len); + btc->hbuf_len = btc->hbuf_len + len + 3; + btc->hbuf_cnt++; + + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s():Buffer H2C-MACRO cnt=%d/bt%d-func=%s/len=%d\n", + __func__, btc->hbuf_cnt, bid, + id_to_h2c(h2c_func_mask), len); + } + + ret = rtw89_fw_h2c_raw_with_hdr(rtwdev, h2c_class, h2c_func, buf, len, false, true); if (ret) pfwinfo->cnt_h2c_fail++; @@ -9249,6 +9321,7 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) #define CASE_BTC_POLUT_STR(e) case BTC_PLT_## e: return #e #define CASE_BTC_REGTYPE_STR(e) case REG_## e: return #e #define CASE_BTC_GDBG_STR(e) case BTC_DBG_## e: return #e +#define CASE_BTC_H2CCMD(e) case SET_##e: return #e static const char *id_to_polut(u32 id) { @@ -9529,6 +9602,38 @@ static const char *id_to_ant(u32 id) } } +static const char *id_to_h2c(u32 id) +{ + switch (id) { + CASE_BTC_H2CCMD(REPORT_EN); + CASE_BTC_H2CCMD(SLOT_TABLE); + CASE_BTC_H2CCMD(MREG_TABLE); + CASE_BTC_H2CCMD(CX_POLICY); + CASE_BTC_H2CCMD(GPIO_DBG); + CASE_BTC_H2CCMD(DRV_INFO); + CASE_BTC_H2CCMD(DRV_EVENT); + CASE_BTC_H2CCMD(BT_WREG_ADDR); + CASE_BTC_H2CCMD(BT_WREG_VAL); + CASE_BTC_H2CCMD(BT_RREG_ADDR); + CASE_BTC_H2CCMD(BT_WL_CH_INFO); + CASE_BTC_H2CCMD(BT_INFO_REPORT); + CASE_BTC_H2CCMD(BT_IGNORE_WLAN_ACT); + CASE_BTC_H2CCMD(BT_TX_PWR); + CASE_BTC_H2CCMD(BT_LNA_CONSTRAIN); + CASE_BTC_H2CCMD(BT_QUERY_DEV_LIST); + CASE_BTC_H2CCMD(BT_QUERY_DEV_INFO); + CASE_BTC_H2CCMD(BT_PSD_REPORT); + CASE_BTC_H2CCMD(H2C_TEST); + CASE_BTC_H2CCMD(IOFLD_RF); + CASE_BTC_H2CCMD(IOFLD_BB); + CASE_BTC_H2CCMD(IOFLD_MAC); + CASE_BTC_H2CCMD(IOFLD_SCBD); + CASE_BTC_H2CCMD(H2C_MACRO); + default: + return "unknown"; + } +} + static int scnprintf_segment(char *buf, size_t bufsz, const char *prefix, const u16 *data, u8 len, u8 seg_len, u8 start_idx, u8 ring_len) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index a928140300b9..cb3a740e4719 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -3820,6 +3820,7 @@ struct rtw89_btc_btf_fwinfo { }; #define RTW89_BTC_POLICY_MAXLEN 512 +#define BTC_H2C_MAXLENC 2020 struct rtw89_btc { const struct rtw89_btc_ver *ver; @@ -3839,11 +3840,14 @@ struct rtw89_btc { u32 bt_req_len[RTW89_PHY_NUM]; u8 policy[RTW89_BTC_POLICY_MAXLEN]; + u8 hbuf[BTC_H2C_MAXLENC]; /* H2C Macro buffer */ + u8 hbuf_cnt; /* H2C cmd count in buffer */ u8 ant_type; u8 btg_pos; u8 io_oflld_type; u16 policy_len; u16 policy_type; + u16 hbuf_len; /* H2C used length, it sshould be <= BTC_H2C_MAXLEN */ u32 hubmsg_cnt; bool bt_req_en; bool update_policy_force; From aaba33e780f5be7d71d7119578012e964f1846c4 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Sun, 12 Jul 2026 11:05:02 +0800 Subject: [PATCH 0348/1433] wifi: rtw89: coex: Refine _reset_btc_var() To avoid the default value not match the real using scenario, it should after assign desired default value after variable reset. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260712030506.43438-8-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 66 +++++++---- drivers/net/wireless/realtek/rtw89/coex.h | 8 ++ drivers/net/wireless/realtek/rtw89/core.h | 127 ++++++++++++++++++++++ 3 files changed, 179 insertions(+), 22 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 07d054f59eb5..17290b2cb2c8 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -1048,44 +1048,52 @@ static int _send_fw_cmd(struct rtw89_dev *rtwdev, u8 h2c_class, u8 h2c_func, return ret; } -#define BTC_BT_DEF_BR_TX_PWR 4 -#define BTC_BT_DEF_LE_TX_PWR 4 +#define BTC_DEFAULT_SIR_THRES 51 /* dB, BT Pin - This = TDD/FDD swtch thres */ +#define BTC_FDDR_TP_SETUP_TIME 1 /* in second */ +#define BTC_FDDR_TP_HOLD_TIME 3 /* in second */ +#define BTC_FDDR_RX_LOW_RATE_THRES 4 /* low rate for TDD swotch */ static void _reset_btc_var(struct rtw89_dev *rtwdev, u8 type) { struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_cx *cx = &btc->cx; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_btc_bt_info *bt = &btc->cx.bt0; - struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; - struct rtw89_btc_wl_link_info *wl_linfo; - u8 i, j; + const struct rtw89_btc_ver *ver = btc->ver; + struct rtw89_btc_bt_info *bt0 = &cx->bt0; + struct rtw89_btc_bt_info *bt1 = &cx->bt1; + u8 i; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s\n", __func__); - if (type & BTC_RESET_CX) + if (type & BTC_RESET_CX) { memset(cx, 0, sizeof(*cx)); + cx->bt_ext.max_tx_pwr = RTW89_BTC_BT_DEF_LE_TX_PWR; + cx->bt_ext.ant_iso_to_wl = RTW89_BTC_DEFAULT_ANISO; + } - if (type & BTC_RESET_BTINFO) /* only for BT enable */ - memset(bt, 0, sizeof(*bt)); + if (type & BTC_RESET_BTINFO) {/* only for BT enable */ + memset(bt0, 0, sizeof(*bt0)); + bt0->link_info.bt_txpwr_desc.br_dbm = RTW89_BTC_BT_DEF_BR_TX_PWR; + bt0->link_info_56g.bt_txpwr_desc.br_dbm = RTW89_BTC_BT_DEF_BR_TX_PWR; + bt0->link_info.bt_txpwr_desc.le_dbm = RTW89_BTC_BT_DEF_LE_TX_PWR; + bt0->link_info_56g.bt_txpwr_desc.le_dbm = RTW89_BTC_BT_DEF_LE_TX_PWR; + bt0->ant_iso_to_wl = RTW89_BTC_DEFAULT_ANISO; + } else if (type & BTC_RESET_BTINFO2) { + memset(bt1, 0, sizeof(*bt1)); + bt1->link_info.bt_txpwr_desc.br_dbm = RTW89_BTC_BT_DEF_BR_TX_PWR; + bt1->link_info_56g.bt_txpwr_desc.br_dbm = RTW89_BTC_BT_DEF_BR_TX_PWR; + bt1->link_info.bt_txpwr_desc.le_dbm = RTW89_BTC_BT_DEF_LE_TX_PWR; + bt1->link_info_56g.bt_txpwr_desc.le_dbm = RTW89_BTC_BT_DEF_LE_TX_PWR; + bt1->ant_iso_to_wl = RTW89_BTC_DEFAULT_ANISO; + } if (type & BTC_RESET_CTRL) { memset(&btc->ctrl, 0, sizeof(btc->ctrl)); - btc->manual_ctrl = false; btc->ctrl.trace_step = FCXDEF_STEP; } /* Init Coex variables that are not zero */ if (type & BTC_RESET_DM) { memset(&btc->dm, 0, sizeof(btc->dm)); - memset(bt_linfo->rssi_state, 0, sizeof(bt_linfo->rssi_state)); - for (j = RTW89_MAC_0; j <= RTW89_MAC_1; j++) { - for (i = 0; i < RTW89_BE_BTC_WL_MAX_ROLE_NUMBER; i++) { - wl_linfo = &wl->rlink_info[i][j]; - memset(wl_linfo->rssi_state, 0, sizeof(wl_linfo->rssi_state)); - } - } /* set the slot_now table to original */ btc->dm.tdma_now = t_def[CXTD_OFF]; @@ -1108,18 +1116,32 @@ static void _reset_btc_var(struct rtw89_dev *rtwdev, u8 type) btc->policy_len = 0; btc->bt_req_len[RTW89_PHY_0] = 0; btc->bt_req_len[RTW89_PHY_1] = 0; + btc->hubmsg_cnt = 0; + btc->dm.coex_info_map = BTC_COEX_INFO_ALL; btc->dm.wl_tx_limit.tx_time = BTC_MAX_TX_TIME_DEF; btc->dm.wl_tx_limit.tx_retry = BTC_MAX_TX_RETRY_DEF; + btc->dm.bt_slot_flood = BTC_B1_MAX; + btc->dm.sir_thres = BTC_DEFAULT_SIR_THRES; + btc->dm.fddr_info.tp_setup_time = BTC_FDDR_TP_SETUP_TIME; + btc->dm.fddr_info.tp_hold_time = BTC_FDDR_TP_HOLD_TIME; + btc->dm.fddr_info.wl_rx_rate_thres = BTC_FDDR_RX_LOW_RATE_THRES; + btc->dm.fddt_info.type = BTC_FDDT_TYPE_AUTO; + btc->dm.wl_pre_agc_rb = BTC_PREAGC_NOTFOUND; btc->dm.wl_btg_rx_rb = BTC_BTGCTRL_BB_GNT_NOTFOUND; } - if (type & BTC_RESET_MDINFO) + if (type & BTC_RESET_MDINFO) { memset(&btc->mdinfo, 0, sizeof(btc->mdinfo)); - bt->link_info.bt_txpwr_desc.br_dbm = BTC_BT_DEF_BR_TX_PWR; - bt->link_info.bt_txpwr_desc.le_dbm = BTC_BT_DEF_LE_TX_PWR; + if (ver->fcxinit == 10) + btc->mdinfo.md_v10.ant.isolation = RTW89_BTC_DEFAULT_ANISO; + else if (ver->fcxinit == 7) + btc->mdinfo.md_v7.ant.isolation = RTW89_BTC_DEFAULT_ANISO; + else + btc->mdinfo.md.ant.isolation = RTW89_BTC_DEFAULT_ANISO; + } } static u8 _search_reg_index(struct rtw89_dev *rtwdev, u8 mreg_num, u16 reg_type, u32 target) diff --git a/drivers/net/wireless/realtek/rtw89/coex.h b/drivers/net/wireless/realtek/rtw89/coex.h index c127bd80d31c..fdd63e2c0837 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.h +++ b/drivers/net/wireless/realtek/rtw89/coex.h @@ -241,6 +241,14 @@ enum btc_3cx_type { BTC_3CX_MAX, }; +enum btc_fddt_type { + BTC_FDDT_TYPE_STOP, + BTC_FDDT_TYPE_AUTO, + BTC_FDDT_TYPE_FIX_TDD, + BTC_FDDT_TYPE_FIX_FULL_FDD, + BTC_FDDT_MAX, +}; + enum btc_chip_feature { BTC_FEAT_PTA_ONOFF_CTRL = BIT(0), /* on/off ctrl by HW (not 0x73[2]) */ BTC_FEAT_NONBTG_GWL_THRU = BIT(1), /* non-BTG GNT_WL!=0 if GNT_BT = 1 */ diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index cb3a740e4719..f784e8437292 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -2462,6 +2462,9 @@ enum rtw89_btc_bt_func_type { #define RTW89_BTC_BTC_SCAN_V1_FLAG_ENABLE BIT(0) #define RTW89_BTC_BTC_SCAN_V1_FLAG_INTERLACE BIT(1) +#define RTW89_BTC_BT_DEF_BR_TX_PWR 4 +#define RTW89_BTC_BT_DEF_LE_TX_PWR 4 +#define RTW89_BTC_DEFAULT_ANISO 10 struct rtw89_btc_bt_scan_info_v1 { __le16 win; @@ -2521,6 +2524,7 @@ struct rtw89_btc_bt_info { u8 func_type; u8 tx_power_now; u8 tx_power_now_6g; + u8 ant_iso_to_wl; /* ant isolation between BTx and WL */ u8 fw_ver_mismatch: 1; u8 band_56G_support: 1; @@ -3474,6 +3478,123 @@ union rtw89_btc_fbtc_slot_u { struct rtw89_btc_fbtc_slot_v7 v7[CXST_MAX]; }; +struct rtw89_btc_fddr_cell { + u8 en; + u8 wl_rx_max; + u8 wl_rx_min; +}; + +struct rtw89_btc_fddr_result { + u8 wl_rx_limit; + u8 wl_rx_limit_step[6]; /* record search process */ + u8 search_cnt; /* the rx-limit serach count */ + u32 wl_tp; + u32 wl_tp_step[6]; /* record search process */ +}; + +struct rtw89_btc_fddr_train_info { + u8 rx_limit_pre; + u8 rx_limit_now; + + u32 tp_rec_cnt; + u32 tp_avg_cnt; + + u32 wl_tp_pre; + u32 wl_tp_now; +}; + +struct rtw89_btc_fddr_info { + u8 state; /* refer to enum btc_fddr_state */ + u8 cell_now; /* wl rssi_level after filter LNA != 6 */ + u8 cell_change; + u8 wl_low_rate; + + u8 tp_setup_time; /* calculate TP after this value (in second) */ + u8 tp_hold_time; /* TP calculation period (in second) */ + u8 wl_rssi_thres[BTC_WL_RSSI_THMAX]; /* index 0 -> Max RSSI */ + + u8 search_mode; /* 0: search, 1:look-up */ + u8 search_dir; /* 0: Max->Min, 1:Min->Max */ + + struct rtw89_btc_fddr_train_info tctrl; /* train flag */ + struct rtw89_btc_fddr_result cell_result[BTC_WL_RSSI_THMAX + 1]; + struct rtw89_btc_fddr_cell cell[BTC_WL_RSSI_THMAX + 1]; /* parameters */ + + u16 wl_rx_rate_thres; /* switch to TDD if rx_rate < this threshold */ + + u32 nrsn_map; /* the reason map for no-run fdd-traing */ + u32 wl_rx_rate_now; +}; + +struct rtw89_btc_rpt_ctrl_a2dp_empty { + u32 cnt_empty; /* a2dp empty count */ + u32 cnt_flowctrl; /* a2dp empty flow control counter */ + u32 cnt_tx; + u32 cnt_ack; + u32 cnt_nack; +}; + +struct rtw89_btc_fddt_bt_stat { + struct rtw89_btc_rpt_ctrl_a2dp_empty a2dp_last; + u32 retry_last; +}; + +struct rtw89_btc_fddt_cell { + s8 wl_pwr_min; + s8 wl_pwr_max; + s8 bt_pwr_dec_max; + s8 bt_rx_gain; +}; + +struct rtw89_btc_fddt_fail_check { /* for cell stay in training */ + u8 check_map; /* check pass condition if bit-map = 1 */ + u8 bt_no_empty_cnt; /* 0-fail if no bt-empty >= th in train_cycle */ + u8 wl_tp_ratio; /* 1-fail if wl tp rise ratio < th */ + u8 wl_kpibtr_ratio; /* 2-fail if phase_now_tp < phase_last_tp * kpibtr_ratio */ +}; + +struct rtw89_btc_fddt_break_check { /* for cell stay in training or train-ok */ + u8 check_map; /* check break condition if bit-map = 1 */ + u8 bt_no_empty_cnt; /* 0-break if no empty count >= th */ + u8 wl_tp_ratio; /* 1-break if wl tp ratio < th (%) */ + u8 wl_tp_low_bound; /* 2-break if wl tp (in Mbps) < th */ + + u8 cn; /* 3-break if (cn >= cn_limit) >= th cycle */ + u8 cell_chg; /* 4-break if non-matched-RSSI >= th cycle */ + s8 nhm_limit; /* 5-break if nhm >= th --> ill-condition */ + u8 cn_limit; /* if condition number >= th --> ill-condition */ +}; + +struct rtw89_btc_fddt_time_ctrl { + /* 1 TDD cycle = w1 + b1, FDD 1cycle = w1fdd-slot + b1fdd-slot */ + u8 m_cycle; /* KPI Moving-Average-Cycle: 1~32 cycles */ + u8 w_cycle; /* Start to calcul WKPI after this if train-phase change */ + u8 k_cycle; /* Total kpi-estimate cycles for each training-step */ + u8 rsvd; +}; + +struct rtw89_btc_fddt_train_info { + struct rtw89_btc_fddt_time_ctrl t_ctrl; + struct rtw89_btc_fddt_break_check b_chk; + struct rtw89_btc_fddt_fail_check f_chk; + struct rtw89_btc_fddt_cell cell_ul[5][5]; + struct rtw89_btc_fddt_cell cell_dl[5][5]; +}; + +struct rtw89_btc_fddt_info { + u8 type; /* refer to enum btc_fddt_type */ + u8 result; /* fw send fdd-training status by c2h */ + u8 state; /* refer to enum btc_fddt_state */ + + u8 wl_iot[6]; /* wl bssid */ + u16 bt_iot; /* bt vendor-id */ + + u32 nrsn_map; /* the reason map for no-run fdd-traing */ + struct rtw89_btc_fddt_bt_stat bt_stat; /* bt statistics */ + struct rtw89_btc_fddt_train_info train; + struct rtw89_btc_fddt_train_info train_now; +}; + struct rtw89_btc_dm { struct rtw89_btc_fbtc_outsrc_set_info ost_info_last; /* outsrc API setup info */ struct rtw89_btc_fbtc_outsrc_set_info ost_info; /* outsrc API setup info */ @@ -3493,6 +3614,8 @@ struct rtw89_btc_dm { union rtw89_btc_dm_error_map error; struct rtw89_btc_bind_info tdd_bind; struct rtw89_btc_bind_info fdd_bind; + struct rtw89_btc_fddt_info fddt_info; + struct rtw89_btc_fddr_info fddr_info; u32 cnt_dm[BTC_DCNT_NUM]; u32 cnt_notify[BTC_NCNT_NUM]; u8 ant_xmap[BTC_RF_NUM][BTC_ALL_BT_EZL]; /* WL-BT ANT interact-map */ @@ -3506,6 +3629,10 @@ struct rtw89_btc_dm { u8 sit_xmap_last[BTC_RF_NUM][BTC_ALL_BT_EZL]; u8 fit_xmap_last[RTW89_PHY_NUM][BTC_ALL_BT_EZL]; + u8 tdd_rssi_thres; /* The FDD/TDD switch RSSI (in %) */ + u8 sir_thres; /* WL(Signal) to BT(interference Pin) ratio */ + u8 sir_state[BTC_ALL_BT_EZL]; /* 1: WL RSSI > BTx-interference */ + u32 update_slot_map; u32 set_ant_path; u32 e2g_slot_limit; From f59f0767348ac196e94f4471585201e20d1cff6b Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Sun, 12 Jul 2026 11:05:03 +0800 Subject: [PATCH 0349/1433] wifi: rtw89: coex: Correct SET_RFE settings Because of dual-BT & dual-MAC, RTL8922D has more complex antenna settings. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260712030506.43438-9-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.h | 31 +++ drivers/net/wireless/realtek/rtw89/core.h | 11 + drivers/net/wireless/realtek/rtw89/rtw8922d.c | 213 ++++++++++++++++-- 3 files changed, 231 insertions(+), 24 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.h b/drivers/net/wireless/realtek/rtw89/coex.h index fdd63e2c0837..e17407696d8a 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.h +++ b/drivers/net/wireless/realtek/rtw89/coex.h @@ -143,6 +143,11 @@ enum btc_switch { BTC_SWITCH_EXTERNAL }; +enum btc_ant_switch_type { + BTC_SWITCH_V1_NONE = 0, /* independent antenna */ + BTC_SWITCH_V1_INTERNAL, /* internal-switch: BTGA structure */ +}; + enum btc_pkt_type { PACKET_DHCP, PACKET_ARP, @@ -262,6 +267,32 @@ enum btc_chip_feature { BTC_FEAT_DUAL_BTGA = BIT(9) /* the future A-Die */ }; +enum btc_efuse_ant_function_map { + BTC_EFMAP_NONE = 0, + BTC_EFMAP_BT0 = BIT(0), + BTC_EFMAP_BT1 = BIT(1), + BTC_EFMAP_ZB = BIT(2), /* ZB or thread */ + BTC_EFMAP_24GP = BIT(3), + BTC_EFMAP_ULL = BIT(4), +}; + +enum btc_extsoc_interface { /* cx->other.hw_coex */ + BTC_EXTSOC_INTF_NONE = 0, + BTC_EXTSOC_INTF_PTA = BIT(0), + BTC_EXTSOC_INTF_MBX = BIT(1), + BTC_EXTSOC_INTF_SWIO = BIT(2), + BTC_EXTSOC_INTF_MAX, +}; + +enum btc_esoc_type { + BTC_ESOC_NONE, + BTC_ESOC_8761, + BTC_ESOC_8771, + BTC_ESOC_SILAB_MG21, + BTC_ESOC_NORDI_NRF52840, + BTC_ESOC_MAX, +}; + enum btc_wl_mode { BTC_WL_MODE_11B = 0, BTC_WL_MODE_11A = 1, diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index f784e8437292..d876de21482f 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -2434,6 +2434,15 @@ struct rtw89_btc_wl_tx_limit_para { u16 tx_retry; }; +struct rtw89_btc_wl_trx_nss_para { + u8 tx_limit; + u8 rx_limit; + u8 tx_ss; + u8 rx_ss; + u8 tx_path; + u8 rx_path; +}; + enum rtw89_btc_bt_scan_type { BTC_SCAN_INQ = 0, BTC_SCAN_PAGE, @@ -3616,6 +3625,7 @@ struct rtw89_btc_dm { struct rtw89_btc_bind_info fdd_bind; struct rtw89_btc_fddt_info fddt_info; struct rtw89_btc_fddr_info fddr_info; + struct rtw89_btc_wl_trx_nss_para wl_trx_nss; u32 cnt_dm[BTC_DCNT_NUM]; u32 cnt_notify[BTC_NCNT_NUM]; u8 ant_xmap[BTC_RF_NUM][BTC_ALL_BT_EZL]; /* WL-BT ANT interact-map */ @@ -3664,6 +3674,7 @@ struct rtw89_btc_dm { u8 wl_tx_pwr_phy_map; u8 vid; u8 client_ps_tdma_on; + u8 wl_trx_nss_en; u8 wl_pre_agc: 2; u8 wl_lna2: 1; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c index aeade858ce95..c28670e82c3e 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c @@ -3106,46 +3106,211 @@ static u32 rtw8922d_chan_to_rf18_val(struct rtw89_dev *rtwdev, static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev) { + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_cx *cx = &btc->cx; union rtw89_btc_module_info *md = &rtwdev->btc.mdinfo; - struct rtw89_btc_module_v7 *module = &md->md_v7; + struct rtw89_btc_module_v10 *module = &md->md_v10; + u8 efuse_bt_func, efuse_ant_info, bt_sw_gpio_pos; + u8 is_combo, is_bt_share; + rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s !!\n", __func__); + + /* get from final capability of device */ module->rfe_type = rtwdev->efuse.rfe_type; module->kt_ver = rtwdev->hal.cv; - module->bt_solo = 0; - module->switch_type = BTC_SWITCH_INTERNAL; + module->kt_ver_adie = rtwdev->hal.acv; module->wa_type = 0; + dm->wl_trx_nss_en = 0; - module->ant.type = BTC_ANT_SHARED; module->ant.num = 2; - module->ant.isolation = 10; - module->ant.diversity = 0; - module->ant.single_pos = RF_PATH_A; - module->ant.btg_pos = RF_PATH_B; + module->ant.single_pos = BTC_RF_S0; /* WL 1ss+1Ant 0:s0(A)/ 1:s1(B) */ - if (module->kt_ver <= 1) - module->wa_type |= BTC_WA_HFP_ZB; + /* set default antenna isolation */ + cx->bt0.ant_iso_to_wl = module->ant.isolation; + cx->bt1.ant_iso_to_wl = module->ant.isolation; - rtwdev->btc.cx.bt_ext.func_type = BTC_3CX_NONE; + module->ant.stream_cnt = 2; + module->ant.btg_pos = BTC_RF_S1; /* BTG0 at WL-S1 */ + module->ant.btg1_pos = BTC_RF_S0; /* BTG1 at WL-S0 if Dual-BTGA */ - if (module->rfe_type == 0) { - rtwdev->btc.dm.error.map.rfe_type0 = true; - return; + cx->bt0.band_56G_support = 1; + cx->bt1.band_56G_support = 1; + cx->bt0.func_type = BTC_BTF_BT; + cx->bt1.func_type = BTC_BTF_BT; + + efuse_bt_func = rtwdev->efuse.bt_setting_2; + + switch ((efuse_bt_func & 0xe0) >> 5) { /* 0xcd[7:5] */ + default: + case 0: + case 1: + bt_sw_gpio_pos = 5; + break; + case 2: + bt_sw_gpio_pos = 11; + break; + case 3: + bt_sw_gpio_pos = 15; + break; + case 4: + bt_sw_gpio_pos = 20; + break; } - module->ant.num = (module->rfe_type % 2) ? 2 : 3; + efuse_bt_func &= 0x1f; /* 0xcd[4:0] */ + efuse_ant_info = rtwdev->efuse.bt_setting_3; + module->ant.num = (efuse_ant_info & 0xe0) >> 5; /* 0xCE[7:5] */ + is_combo = (efuse_ant_info & 0xe) >> 1; /* 0xCE[3:1] */ + is_bt_share = efuse_ant_info & BIT(0); /* 0xCE[0] */ - if (module->kt_ver == 0) - module->ant.num = 2; + memset(dm->ant_xmap, 0, sizeof(dm->ant_xmap)); - if (module->ant.num == 3) { - module->ant.type = BTC_ANT_DEDICATED; - module->bt_pos = BTC_BT_ALONE; - } else { + /* To-Do: "RFE_TYpe" to "ant.num" translation */ + switch (module->ant.num) { + case 1: /* 1-Ant WL-S0 only & BT0 only */ module->ant.type = BTC_ANT_SHARED; - module->bt_pos = BTC_BT_BTG; + module->bt0_pos = BTC_BT_BTG; + module->bt0_sw_type = BTC_SWITCH_INTERNAL; + module->ant.btg_pos = BTC_RF_S0; /* BTG0 at WL-S0 */ + module->ant.stream_cnt = 1; + module->ant.func[0] = BTC_EFMAP_BT0; + dm->ant_xmap[BTC_RF_S0][BTC_BT_1ST] = 1; /* BT0 shared with S0*/ + dm->ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 0; /* WL 1T1R no RF-S1 */ + break; + case 2: /* 2-Ant */ + default: + if (is_combo) { + if (efuse_bt_func == (BTC_EFMAP_BT0 | BTC_EFMAP_BT1)) + module->ant.func[0] = BTC_EFMAP_BT1; + + module->ant.func[1] = BTC_EFMAP_BT0; + } else { + module->ant.func[0] = BTC_EFMAP_NONE; + module->ant.func[1] = BTC_EFMAP_NONE; + } + + if (is_bt_share) { /* WL-S0 + (WL-S1 & BT0-S1) */ + module->ant.type = BTC_ANT_SHARED; + module->bt0_pos = BTC_BT_BTG; + module->bt0_sw_type = BTC_SWITCH_INTERNAL; + dm->ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 1; + } else { /* WL-S0 + BT0-S1 */ + module->ant.type = BTC_ANT_DEDICATED; + module->bt0_pos = BTC_BT_ALONE; + module->bt0_sw_type = BTC_SWITCH_V1_NONE; + } + + if (module->ant.func[0] == BTC_EFMAP_BT1) { /* if 2nd BT exist */ + dm->ant_xmap[BTC_RF_S0][BTC_BT_2ND] = 1; + if (module->rfe_type == 12) { /* WL-S0 & BT1 by SPDT */ + module->bt1_pos = BTC_BT_ALONE; + /* Todo: set SPDT GPIO-ctrl */ + module->bt1_sw_type = bt_sw_gpio_pos; + } else { /* WL-S0 & BT1-S1 by BTGA */ + module->bt1_pos = BTC_BT_BTG; + module->bt1_sw_type = BTC_SWITCH_INTERNAL; + } + } + + dm->wl_trx_nss_en = 1; /* 1ss MIMO-PS capability if 1-BT */ + break; + case 3: /* 3-Ant, 3 different BT-configuration */ + if (is_bt_share) { + module->ant.func[0] = BTC_EFMAP_NONE; + module->ant.func[1] = BTC_EFMAP_BT0; + module->ant.func[2] = efuse_bt_func & (~BTC_EFMAP_BT0); + module->ant.type = BTC_ANT_SHARED; + module->bt0_pos = BTC_BT_BTG; + module->bt0_sw_type = BTC_SWITCH_INTERNAL; + dm->ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 1; + dm->wl_trx_nss_en = 1; /* 1ss MIMO-PS capability */ + } else { + module->ant.func[0] = BTC_EFMAP_NONE; + module->ant.func[1] = BTC_EFMAP_NONE; + module->ant.func[2] = efuse_bt_func; + module->ant.type = BTC_ANT_DEDICATED; + module->bt0_pos = BTC_BT_ALONE; + module->bt0_sw_type = BTC_SWITCH_V1_NONE; + } + + module->bt1_pos = BTC_BT_ALONE; /* BT1 may exist or not */ + module->bt1_sw_type = BTC_SWITCH_V1_NONE; + break; + case 4: /* 4-Ant, WL-S0 + WL-S1 + BT0 + BT1 */ + module->ant.func[0] = BTC_EFMAP_NONE; + module->ant.func[1] = BTC_EFMAP_NONE; + module->ant.func[2] = BTC_EFMAP_BT0; + module->ant.func[3] = efuse_bt_func & (~BTC_EFMAP_BT0); + module->ant.type = BTC_ANT_DEDICATED; + module->bt0_pos = BTC_BT_ALONE; + module->bt0_sw_type = BTC_SWITCH_V1_NONE; + module->bt1_pos = BTC_BT_ALONE; + module->bt1_sw_type = BTC_SWITCH_V1_NONE; + break; + case 5: + module->ant.func[0] = BTC_EFMAP_NONE; + module->ant.func[1] = BTC_EFMAP_NONE; + module->ant.func[2] = BTC_EFMAP_BT0; + module->ant.func[3] = BTC_EFMAP_BT1; + module->ant.func[4] = BTC_EFMAP_ZB; + module->ant.type = BTC_ANT_DEDICATED; + module->bt0_pos = BTC_BT_ALONE; + module->bt0_sw_type = BTC_SWITCH_V1_NONE; + module->bt1_pos = BTC_BT_ALONE; + module->bt1_sw_type = BTC_SWITCH_V1_NONE; + break; } - rtwdev->btc.btg_pos = module->ant.btg_pos; - rtwdev->btc.ant_type = module->ant.type; + + /* + * if only BT0 at BTGA: 2-Ant, 3-Ant(BT1 used dedicated-ant) + * can setup dm->wl_trx_nss_en = 1, WL MIMO-PS to 1T1R + * (WL at WL-S0 only, BT0 at WL-S1) + * tx_limit/rx_limit is decided by _set_trx_nss() + */ + if (dm->wl_trx_nss_en && + (dm->wl_trx_nss.tx_limit && dm->wl_trx_nss.rx_limit)) { + module->ant.type = BTC_ANT_DEDICATED; + module->ant.stream_cnt = 1; + dm->ant_xmap[BTC_RF_S0][BTC_BT_1ST] = 0; /* wl 1ss-> RF-S0 */ + dm->ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 0; /* BT0-> RF-S1 */ + dm->ant_xmap[BTC_RF_S0][BTC_BT_2ND] = 0; + dm->ant_xmap[BTC_RF_S1][BTC_BT_2ND] = 0; /* BT1 -> 3rd Ant */ + } + + /* + * Todo: call HALBB API to set BT0/1 at WL_S0 or WL_S1 + * r_sel_gnt_bt_rx_path0[1:0], r_sel_gnt_bt_rx_path1[1:0] + * WL_S0 with BT1 -> r_sel_gnt_bt_rx_path0[1:0]= 2b'10 + * WL_S1 with BT0 -> r_sel_gnt_bt_rx_path1[1:0]= 2b'01 + */ + + /* To Do: setup ext-SOC coex if exist */ + switch (cx->bt_ext.chip_id) { + default: + case BTC_ESOC_NONE: + memset(&cx->bt_ext, 0, sizeof(struct rtw89_btc_extsoc_info)); + cx->bt_ext.max_tx_pwr = RTW89_BTC_BT_DEF_LE_TX_PWR; + cx->bt_ext.ant_iso_to_wl = RTW89_BTC_DEFAULT_ANISO; + break; + case BTC_ESOC_8771: + cx->bt_ext.func_type = BTC_BTF_THREAD; + cx->bt_ext.hw_coex = BTC_EXTSOC_INTF_PTA; + cx->bt_ext.rf_band_map = 0x1; /* 2.4GHz only */ + cx->bt_ext.link_weight[BTC_BT_B2G] = 10; + cx->bt_ext.profile_map[BTC_BT_B2G] |= BTC_BT_THREAD; + /* use GPIO 12~15 for Ext-4-wire-PTA */ + cx->bt_ext.hpta_cfg = BIT(12) | BIT(13) | BIT(14) | BIT(15); + /* for Ext-SOC locate at Ant-2 */ + if (module->ant.num >= 5) + module->ant.func[4] = BTC_EFMAP_ZB; + else + module->ant.func[module->ant.num - 1] = BTC_EFMAP_ZB; + break; + } + + dm->ant_xmap[BTC_RF_S0][BTC_BT_EXT] = 0; + dm->ant_xmap[BTC_RF_S1][BTC_BT_EXT] = 0; } static void rtw8922d_btc_init_cfg(struct rtw89_dev *rtwdev) From 578f9b48276c07e3bc9a8a5f208a18b42da1e1bb Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Sun, 12 Jul 2026 11:05:04 +0800 Subject: [PATCH 0350/1433] wifi: rtw89: coex: Add firmware report control report v11 In the version 11 report control report, firmware will report firmware build date, version. And Bluetooth to Wi-Fi scoreboard value will be read at Wi-Fi firmware and update to Wi-Fi driver. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260712030506.43438-10-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 218 +++++++++++++++++++++- drivers/net/wireless/realtek/rtw89/core.h | 26 +++ 2 files changed, 243 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 17290b2cb2c8..c2356d6d9811 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -1417,6 +1417,40 @@ static void _chk_btc_err(struct rtw89_dev *rtwdev, u8 type, u32 cnt) dm->error.map.bt_slot_drift = false; break; + case BTC_DCNT_WL_STA_NTFY: + cnt = dm->cnt_notify[BTC_NCNT_WL_STA] - + dm->cnt_notify[BTC_NCNT_WL_STA_LAST]; + + dm->cnt_notify[BTC_NCNT_WL_STA_LAST] = + dm->cnt_notify[BTC_NCNT_WL_STA]; + + if (cnt == 0 && + (dm->tdd_bind.wl_link_mode != BTC_WLINK_NOLINK || + dm->fdd_bind.wl_link_mode != BTC_WLINK_NOLINK)) + dm->cnt_dm[BTC_DCNT_WL_STA_NTFY]++; + else + dm->cnt_dm[BTC_DCNT_WL_STA_NTFY] = 0; + + if (dm->cnt_dm[BTC_DCNT_WL_STA_NTFY] >= BTC_CHK_HANG_MAX) + dm->error.map.wl_no_sta_ntfy = true; + else + dm->error.map.wl_no_sta_ntfy = false; + break; + case BTC_DCNT_W2B_SCBD_NOSYNC: + if (!(rtwdev->chip->para_ver & BTC_FEAT_MULTI_PTA)) + return; /* return if no-support W2B scbd readback */ + + if (wl->scbd_rb[BTC_BT_1ST] != wl->scbd[BTC_BT_1ST] || + wl->scbd_rb[BTC_BT_2ND] != wl->scbd[BTC_BT_2ND]) + dm->cnt_dm[BTC_DCNT_W2B_SCBD_NOSYNC]++; + else + dm->cnt_dm[BTC_DCNT_W2B_SCBD_NOSYNC] = 0; + + if (dm->cnt_dm[BTC_DCNT_W2B_SCBD_NOSYNC] >= BTC_CHK_HANG_MAX) + dm->error.map.w2b_scbd_no_sync = true; + else + dm->error.map.w2b_scbd_no_sync = false; + break; } } @@ -1564,6 +1598,7 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; + struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_bt_info *bt = &btc->cx.bt0; union rtw89_btc_fbtc_rpt_ctrl_ver_info *prpt = NULL; @@ -1574,7 +1609,8 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, u16 wl_slot_set = 0, wl_slot_real = 0, val16; u32 trace_step = 0, rpt_len = 0, diff_t = 0; u32 cnt_leak_slot, bt_slot_real, bt_slot_set, cnt_rx_imr; - u8 i, val = 0, val1, val2; + u8 i, j, val = 0, val1, val2; + u32 *bt_cnt, start_idx; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): index:%d\n", @@ -1626,6 +1662,10 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, pfinfo = &pfwinfo->rpt_ctrl.finfo.v7; pcinfo->req_len = sizeof(pfwinfo->rpt_ctrl.finfo.v7); fwsubver->fcxbtcrpt = pfwinfo->rpt_ctrl.finfo.v7.fver; + } else if (ver->fcxbtcrpt == 11) { + pfinfo = &pfwinfo->rpt_ctrl.finfo.v11; + pcinfo->req_len = sizeof(pfwinfo->rpt_ctrl.finfo.v11); + fwsubver->fcxbtcrpt = pfwinfo->rpt_ctrl.finfo.v11.fver; } else { goto err; } @@ -2031,6 +2071,70 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, _chk_btc_err(rtwdev, BTC_DCNT_RPT_HANG, val1); _chk_btc_err(rtwdev, BTC_DCNT_WL_FW_VER_MATCH, 0); _chk_btc_err(rtwdev, BTC_DCNT_BTTX_HANG, 0); + } else if (ver->fcxbtcrpt == 11) { + prpt->v11 = pfwinfo->rpt_ctrl.finfo.v11; + pfwinfo->rpt_en_map = le32_to_cpu(prpt->v11.rpt_info.en); + wl->ver_info.fw_coex = le32_to_cpu(prpt->v11.rpt_info.cx_ver); + wl->ver_info.fw = le32_to_cpu(prpt->v11.rpt_info.fw_ver); + + memset(wl->ver_info.build_time, 0, + sizeof(wl->ver_info.build_time)); + memcpy(wl->ver_info.build_time, + prpt->v11.build_time, + sizeof(wl->ver_info.build_time)); + + memset(wl->ver_info.build_date, 0, + sizeof(wl->ver_info.build_date)); + memcpy(wl->ver_info.build_date, + prpt->v11.build_date, + sizeof(wl->ver_info.build_date)); + + for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) + memcpy(&dm->gnt_set[i], &prpt->v11.gnt_val[i][0], + sizeof(dm->gnt_set[i])); + + for (i = BTC_BT_1ST; i <= BTC_BT_EXT; i++) { + if (i == BTC_BT_EXT) { + if (cx->bt_ext.func_type == BTC_BTF_NONE) + continue; + bt_cnt = cx->bt_ext.bcnt; + } else if (i == BTC_BT_2ND) { + if (!(rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT)) + continue; + bt_cnt = cx->bt1.bcnt; + wl->scbd_rb[i] = le32_to_cpu(prpt->v11.scbd_w2b[i]); + cx->bt1.scbd_rb = le32_to_cpu(prpt->v11.scbd_b2w[i]); + } else { + bt_cnt = cx->bt0.bcnt; + wl->scbd_rb[i] = le32_to_cpu(prpt->v11.scbd_w2b[i]); + cx->bt0.scbd_rb = le32_to_cpu(prpt->v11.scbd_b2w[i]); + } + + start_idx = BTC_BCNT_HIPRI_TX; + for (j = BTC_BCNT_HI_TX_V105; j <= BTC_BCNT_LO_RX_V105 ; j++) + bt_cnt[start_idx++] = le16_to_cpu(prpt->v11.bt_cnt[i][j]); + + /* check if polut counter increase */ + val1 = le16_to_cpu(prpt->v11.bt_cnt[i][BTC_BCNT_POLLUTED_V105]); + if (val1 > bt_cnt[BTC_BCNT_POLUT_NOW]) /* cnt increase*/ + val2 = val1 - bt_cnt[BTC_BCNT_POLUT_NOW]; + else + val2 = val1; /* overflow */ + + bt_cnt[BTC_BCNT_POLUT_DIFF] = val2; + bt_cnt[BTC_BCNT_POLUT_NOW] = val1; + + if (i <= BTC_BT_2ND) + bt_cnt[BTC_BCNT_SCBDREAD]++; + } + dm->scbd_w2b_update = true; + dm->scbd_b2w_update = true; + + _chk_btc_err(rtwdev, BTC_DCNT_BTCNT_HANG, 0); + _chk_btc_err(rtwdev, BTC_DCNT_WL_FW_VER_MATCH, 0); + _chk_btc_err(rtwdev, BTC_DCNT_BTTX_HANG, 0); + _chk_btc_err(rtwdev, BTC_DCNT_WL_STA_NTFY, 0); + _chk_btc_err(rtwdev, BTC_DCNT_W2B_SCBD_NOSYNC, 0); } else { goto err; } @@ -11953,6 +12057,116 @@ static int _show_summary_v8(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) return p - buf; } +static int _show_summary_v11(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) +{ + struct rtw89_btc_btf_fwinfo *pfwinfo = &rtwdev->btc.fwinfo; + struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; + struct rtw89_btc_fbtc_rpt_ctrl_v11 *prptctrl = NULL; + struct rtw89_btc_cx *cx = &rtwdev->btc.cx; + struct rtw89_btc_dm *dm = &rtwdev->btc.dm; + struct rtw89_btc_wl_info *wl = &cx->wl; + u32 *cnt = rtwdev->btc.dm.cnt_notify; + char *p = buf, *end = buf + bufsz; + u32 cnt_sum = 0; + u8 i; + + if (!(dm->coex_info_map & BTC_COEX_INFO_SUMMARY)) + return 0; + + p += scnprintf(p, end - p, "%s", + "\n\r========== [Statistics] =========="); + + pcinfo = &pfwinfo->rpt_ctrl.cinfo; + if (pcinfo->valid && wl->status.map.lps != BTC_LPS_RF_OFF && + !wl->status.map.rf_off) { + prptctrl = &pfwinfo->rpt_ctrl.finfo.v11; + + p += scnprintf(p, end - p, + "\n\r %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d), c2h_cnt=%d(fw_send:%d, len:%d, max:fw-%d/drv-%d), ", + "[summary]", pfwinfo->cnt_h2c, + pfwinfo->cnt_h2c_fail, + le16_to_cpu(prptctrl->rpt_info.cnt_h2c), + pfwinfo->cnt_c2h, + le16_to_cpu(prptctrl->rpt_info.cnt_c2h), + le16_to_cpu(prptctrl->rpt_info.len_c2h), + (prptctrl->rpt_len_max_h << 8) + prptctrl->rpt_len_max_l, + rtwdev->btc.ver->info_buf); + + p += scnprintf(p, end - p, + "rpt_cnt=%d(fw_send:%d), rpt_map=0x%x", + pfwinfo->event[BTF_EVNT_RPT], + le16_to_cpu(prptctrl->rpt_info.cnt), + le32_to_cpu(prptctrl->rpt_info.en)); + + if (dm->error.map.wl_fw_hang) + p += scnprintf(p, end - p, " (WL FW Hang!!)"); + + p += scnprintf(p, end - p, + "\n\r %-15s : send_ok:%d, send_fail:%d, recv:%d, ", + "[mailbox]", + le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_ok), + le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_fail), + le32_to_cpu(prptctrl->bt_mbx_info.cnt_recv)); + + p += scnprintf(p, end - p, + "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)", + le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_empty), + le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_flowctrl), + le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_tx), + le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_ack), + le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_nack)); + + p += scnprintf(p, end - p, + "\n\r %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]", + "[RFK/LPS]", wl->wcnt[BTC_WCNT_RFK_REQ], + wl->wcnt[BTC_WCNT_RFK_GO], + wl->wcnt[BTC_WCNT_RFK_REJECT], + wl->wcnt[BTC_WCNT_RFK_TIMEOUT], + wl->rfk_info.proc_time); + + p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]", + le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_on), + le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_off)); + } else { + p += scnprintf(p, end - p, + "\n\r %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)", + "[summary]", + pfwinfo->cnt_h2c, pfwinfo->cnt_h2c_fail, + pfwinfo->cnt_c2h, + wl->status.map.lps, wl->status.map.rf_off); + } + + for (i = 0; i < BTC_NCNT_NUM; i++) + cnt_sum += dm->cnt_notify[i]; + + p += scnprintf(p, end - p, + "\n\r %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ", + "[notify_cnt]", + cnt_sum, cnt[BTC_NCNT_SHOW_COEX_INFO], + cnt[BTC_NCNT_POWER_ON], cnt[BTC_NCNT_INIT_COEX]); + + p += scnprintf(p, end - p, + "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d", + cnt[BTC_NCNT_POWER_OFF], cnt[BTC_NCNT_RADIO_STATE], + cnt[BTC_NCNT_ROLE_INFO], cnt[BTC_NCNT_WL_RFK], + cnt[BTC_NCNT_WL_STA]); + + p += scnprintf(p, end - p, + "\n\r %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ", + "[notify_cnt]", + cnt[BTC_NCNT_SCAN_START], cnt[BTC_NCNT_SCAN_FINISH], + cnt[BTC_NCNT_SWITCH_BAND], cnt[BTC_NCNT_SWITCH_CHBW], + cnt[BTC_NCNT_SPECIAL_PACKET]); + + p += scnprintf(p, end - p, + "timer=%d, customerize=%d, hub_msg=%d, chg_fw=%d, send_cc=%d", + cnt[BTC_NCNT_TIMER], cnt[BTC_NCNT_CUSTOMERIZE], + rtwdev->btc.hubmsg_cnt, cnt[BTC_NCNT_RESUME_DL_FW], + cnt[BTC_NCNT_COUNTRYCODE]); + + return p - buf; +} + ssize_t rtw89_btc_dump_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) { struct rtw89_btc *btc = &rtwdev->btc; @@ -12016,6 +12230,8 @@ ssize_t rtw89_btc_dump_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += _show_summary_v7(rtwdev, p, end - p); else if (ver->fcxbtcrpt == 8) p += _show_summary_v8(rtwdev, p, end - p); + else if (ver->fcxbtcrpt == 11) + p += _show_summary_v11(rtwdev, p, end - p); return p - buf; } diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index d876de21482f..e7e72f742847 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -1406,6 +1406,7 @@ enum rtw89_btc_dcnt { BTC_DCNT_WL_FW_VER_MATCH, BTC_DCNT_NULL_TX_FAIL, BTC_DCNT_WL_STA_NTFY, + BTC_DCNT_W2B_SCBD_NOSYNC, BTC_DCNT_NUM, }; @@ -2058,6 +2059,8 @@ struct rtw89_btc_wl_role_info { /* Logic dynamic using */ }; struct rtw89_btc_wl_ver_info { + char build_time[12]; + char build_date[12]; u32 fw_coex; /* match with which coex_ver */ u32 fw; u32 mac; @@ -2245,6 +2248,7 @@ struct rtw89_btc_dm_emap { u32 h2c_buffer_over: 1; u32 bt_tx_hang: 1; /* for SNR too low bug, BT has no Tx req*/ u32 wl_no_sta_ntfy: 1; + u32 w2b_scbd_no_sync: 1; u32 h2c_bmap_mismatch: 1; u32 c2h_bmap_mismatch: 1; @@ -2784,6 +2788,26 @@ struct rtw89_btc_fbtc_rpt_ctrl_v8 { struct rtw89_btc_fbtc_rpt_ctrl_bt_mailbox bt_mbx_info; } __packed; +struct rtw89_btc_fbtc_rpt_ctrl_v11 { + u8 fver; + u8 rsvd0; + u8 rpt_len_max_l; /* BTC_RPT_MAX bit0~7 */ + u8 rpt_len_max_h; /* BTC_RPT_MAX bit8~15 */ + + u8 build_time[12]; + u8 build_date[12]; + + u8 gnt_val[RTW89_PHY_NUM][8]; /* gwl/gbt012 refer to struct btc_gnt_ctrl */ + __le16 bt_cnt[BTC_ALL_BT_EZL][BTC_BCNT_STA_MAX_V105]; + + struct rtw89_btc_fbtc_rpt_ctrl_info_v8 rpt_info; + struct rtw89_btc_fbtc_rpt_ctrl_bt_mailbox bt_mbx_info; + + __le32 error_code; + __le32 scbd_w2b[2]; + __le32 scbd_b2w[2]; +} __packed; + union rtw89_btc_fbtc_rpt_ctrl_ver_info { struct rtw89_btc_fbtc_rpt_ctrl_v1 v1; struct rtw89_btc_fbtc_rpt_ctrl_v4 v4; @@ -2791,6 +2815,7 @@ union rtw89_btc_fbtc_rpt_ctrl_ver_info { struct rtw89_btc_fbtc_rpt_ctrl_v105 v105; struct rtw89_btc_fbtc_rpt_ctrl_v7 v7; struct rtw89_btc_fbtc_rpt_ctrl_v8 v8; + struct rtw89_btc_fbtc_rpt_ctrl_v11 v11; }; enum rtw89_fbtc_ext_ctrl_type { @@ -3691,6 +3716,7 @@ struct rtw89_btc_dm { u8 lps_ctrl_change: 1; u8 scbd_write_instant; bool scbd_b2w_update; + bool scbd_w2b_update; bool pre_agc_chg; }; From aed0d7771d52d0e21eba4d3a4969fae4e450d23e Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Sun, 12 Jul 2026 11:05:05 +0800 Subject: [PATCH 0351/1433] wifi: rtw89: coex: update external control length by case Update recommend external control slot length to driver. Some of the Wi-Fi feature has its time slot requirement can not be simply controlled by coexistence firmware TDMA timer. For example: Wi-Fi scan/MCC etc. In the same time, coexistence need to tell driver the recommend Bluetooth slot length to make sure Bluetooth can still has enough time slot to traffic. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260712030506.43438-11-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 70 +++++++++++++++++++++++ drivers/net/wireless/realtek/rtw89/core.c | 11 ++-- drivers/net/wireless/realtek/rtw89/core.h | 4 +- 3 files changed, 80 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index c2356d6d9811..a6b1aa2cb24e 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -526,6 +526,14 @@ enum btc_cx_poicy_main_type { BTC_CXP_MAIN_MAX }; +enum btc_bslot_length { + BTC_BSLOT_A2DP_HID = 60, + BTC_BSLOT_A2DP = 50, + BTC_BSLOT_A2DP_2 = 40, + BTC_BSLOT_INQ = 30, + BTC_BSLOT_IDLE = 20, +}; + enum btc_cx_poicy_type { /* TDMA off + pri: BT > WL */ BTC_CXP_OFF_BT = (BTC_CXP_OFF << 8) | 0, @@ -5989,6 +5997,67 @@ static void _set_wl_tx_limit(struct rtw89_dev *rtwdev) &data); } +static void _set_phl_bt_slot_req(struct rtw89_dev *rtwdev) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_wl_info *wl = &cx->wl; + struct rtw89_btc_dm *dm = &btc->dm; + u8 len = 0; + u8 i; + + /* don't change bt slot req state during RFK for p2p/mcc case */ + if (dm->run_reason == BTC_RSN_NTFY_WL_RFK || + wl->status.map.transacting) + return; + + /* enable bt-slot req if ext-slot-control */ + if (dm->tdma_now.type == CXTDMA_OFF && + dm->tdma_now.ext_ctrl == CXECTL_EXT && + dm->tdd_bind.bt_sel != 0) { + if (btc->cx.wl.status.val & btc_scanning_map.val) + btc->bt_req_en = false; + else + btc->bt_req_en = true; + } else { + btc->bt_req_en = false; + } + + if (dm->slot_req_more) { + len = BTC_BSLOT_A2DP_HID; + } else { + if (dm->tdd_bind.bt_link_weight >= BTC_BSLOT_A2DP_HID) + len = BTC_BSLOT_A2DP_HID; + else if (dm->tdd_bind.bt_link_weight <= BTC_BSLOT_IDLE) + len = BTC_BSLOT_IDLE; + else + len = dm->tdd_bind.bt_link_weight; + } + + if (dm->tdd_bind.rf_band == BIT(RTW89_BAND_5G)) + len = 100 - len; + + if (!btc->bt_req_en) + len = 0; + + for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) { + if (!(dm->tdd_bind.wl_hwb_sel & BIT(i))) + continue; + + if (len == btc->bt_req_len[i]) + continue; + + btc->bt_req_len[i] = len; + + rtw89_core_ntfy_btc_event(rtwdev, + RTW89_BTC_HMSG_SET_BT_REQ_SLOT, i); + + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(): HWB%d bt_req_len = %d\n", + __func__, i, btc->bt_req_len[i]); + } +} + static void _set_bt_rx_agc(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; @@ -6148,6 +6217,7 @@ static void _action_common(struct rtw89_dev *rtwdev) _set_btg_ctrl(rtwdev); _set_wl_preagc_ctrl(rtwdev); _set_wl_tx_limit(rtwdev); + _set_phl_bt_slot_req(rtwdev); _set_bt_afh_info(rtwdev); _set_bt_rx_agc(rtwdev); _set_rf_trx_para(rtwdev); diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index 0343cd1a0ee1..30a8e337498e 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -6539,15 +6539,18 @@ void rtw89_complete_cond(struct rtw89_wait_info *wait, unsigned int cond, rtw89_complete_cond_resp(resp, data); } -void rtw89_core_ntfy_btc_event(struct rtw89_dev *rtwdev, enum rtw89_btc_hmsg event) +void rtw89_core_ntfy_btc_event(struct rtw89_dev *rtwdev, + enum rtw89_btc_hmsg event, + enum rtw89_phy_idx phy_idx) { - u16 bt_req_len; + u16 bt_slot_req[RTW89_PHY_NUM]; switch (event) { case RTW89_BTC_HMSG_SET_BT_REQ_SLOT: - bt_req_len = rtw89_coex_query_bt_req_len(rtwdev, RTW89_PHY_0); + bt_slot_req[phy_idx] = rtw89_coex_query_bt_req_len(rtwdev, phy_idx); rtw89_debug(rtwdev, RTW89_DBG_BTC, - "coex updates BT req len to %d TU\n", bt_req_len); + "coex updates PHY-%d BT req len to %d TU\n", + phy_idx, bt_slot_req[phy_idx]); rtw89_queue_chanctx_change(rtwdev, RTW89_CHANCTX_BT_SLOT_CHANGE); break; default: diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index e7e72f742847..c8b5c9ed55be 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -8866,7 +8866,9 @@ int rtw89_reg_6ghz_recalc(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvi void rtw89_core_update_p2p_ps(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link, struct ieee80211_bss_conf *bss_conf); -void rtw89_core_ntfy_btc_event(struct rtw89_dev *rtwdev, enum rtw89_btc_hmsg event); +void rtw89_core_ntfy_btc_event(struct rtw89_dev *rtwdev, + enum rtw89_btc_hmsg event, + enum rtw89_phy_idx phy_idx); int rtw89_core_mlsr_switch(struct rtw89_dev *rtwdev, struct rtw89_vif *rtwvif, unsigned int link_id); void rtw89_core_dm_disable_cfg(struct rtw89_dev *rtwdev, u32 new); From c2d96cd05c17c7b53f52395c766f629df3a78b3a Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Sun, 12 Jul 2026 11:05:06 +0800 Subject: [PATCH 0352/1433] wifi: rtw89: coex: Update coexistence version to 9.24.0 RTL8922D first release, add related feature support. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260712030506.43438-12-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index a6b1aa2cb24e..b044b25aeade 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -11,7 +11,7 @@ #include "ps.h" #include "reg.h" -#define RTW89_COEX_VERSION 0x09000113 +#define RTW89_COEX_VERSION 0x09180013 #define FCXDEF_STEP 50 /* MUST <= FCXMAX_STEP and match with wl fw*/ #define BTC_E2G_LIMIT_DEF 80 From b45b22bbe9d51493e8d4b5466d3dc0c7b7e26278 Mon Sep 17 00:00:00 2001 From: Eric Huang Date: Sun, 12 Jul 2026 11:44:59 +0800 Subject: [PATCH 0353/1433] wifi: rtw89: pack I/O during bb_sethw to reduce API execution time Wrap rtw89_chip_bb_sethw() with rtw89_io_pack/unpack so all register writes during baseband hardware initialization are batched into a single bus transaction. This reduces API execution time from ~11000 us to ~4000 us on affected platforms. Signed-off-by: Eric Huang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260712034506.53209-2-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index c8b5c9ed55be..0ff090175ce5 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -8216,8 +8216,12 @@ static inline void rtw89_chip_bb_sethw(struct rtw89_dev *rtwdev) { const struct rtw89_chip_info *chip = rtwdev->chip; + rtw89_io_pack(rtwdev); + if (chip->ops->bb_sethw) chip->ops->bb_sethw(rtwdev); + + rtw89_io_unpack(rtwdev); } static inline void rtw89_chip_rfk_init(struct rtw89_dev *rtwdev) From 07c26ead36e7dc9a598b815ed750d35440c440fa Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Sun, 12 Jul 2026 11:45:00 +0800 Subject: [PATCH 0354/1433] wifi: rtw89: mac: abstract register definition of firmware boot debug The registers of firmware boot debug are different between WiFi 6 and 7 chips. Add field to abstract it accordingly. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260712034506.53209-3-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/fw.c | 5 +++-- drivers/net/wireless/realtek/rtw89/mac.c | 1 + drivers/net/wireless/realtek/rtw89/mac.h | 1 + drivers/net/wireless/realtek/rtw89/mac_be.c | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 027f8121efd1..f290bcdd4fda 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -1939,13 +1939,14 @@ static void rtw89_fw_prog_cnt_dump(struct rtw89_dev *rtwdev) static void rtw89_fw_dl_fail_dump(struct rtw89_dev *rtwdev) { + const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; u32 val32; val32 = rtw89_read32(rtwdev, R_AX_WCPU_FW_CTRL); rtw89_err(rtwdev, "[ERR]fwdl 0x1E0 = 0x%x\n", val32); - val32 = rtw89_read32(rtwdev, R_AX_BOOT_DBG); - rtw89_err(rtwdev, "[ERR]fwdl 0x83F0 = 0x%x\n", val32); + val32 = rtw89_read32(rtwdev, mac->boot_dbg); + rtw89_err(rtwdev, "[ERR]fwdl 0x%x = 0x%x\n", mac->boot_dbg, val32); rtw89_fw_prog_cnt_dump(rtwdev); } diff --git a/drivers/net/wireless/realtek/rtw89/mac.c b/drivers/net/wireless/realtek/rtw89/mac.c index b9cd7fd8d76b..3e06a0bdf7e9 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.c +++ b/drivers/net/wireless/realtek/rtw89/mac.c @@ -7445,6 +7445,7 @@ const struct rtw89_mac_gen_def rtw89_mac_gen_ax = { .agg_len_ht = R_AX_AGG_LEN_HT_0, .ps_status = R_AX_PPWRBIT_SETTING, .mu_gid = &rtw89_mac_mu_gid_addr_ax, + .boot_dbg = R_AX_BOOT_DBG, .muedca_ctrl = { .addr = R_AX_MUEDCA_EN, diff --git a/drivers/net/wireless/realtek/rtw89/mac.h b/drivers/net/wireless/realtek/rtw89/mac.h index a5f1694af91a..c05f5ee0d2fd 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.h +++ b/drivers/net/wireless/realtek/rtw89/mac.h @@ -1067,6 +1067,7 @@ struct rtw89_mac_gen_def { u32 agg_len_ht; u32 ps_status; const struct rtw89_mac_mu_gid_addr *mu_gid; + u32 boot_dbg; struct rtw89_reg_def muedca_ctrl; struct rtw89_reg_def bfee_ctrl; diff --git a/drivers/net/wireless/realtek/rtw89/mac_be.c b/drivers/net/wireless/realtek/rtw89/mac_be.c index 33513b283d84..8de0fe5a3b1d 100644 --- a/drivers/net/wireless/realtek/rtw89/mac_be.c +++ b/drivers/net/wireless/realtek/rtw89/mac_be.c @@ -3284,6 +3284,7 @@ const struct rtw89_mac_gen_def rtw89_mac_gen_be = { .agg_len_ht = R_BE_AGG_LEN_HT_0, .ps_status = R_BE_WMTX_POWER_BE_BIT_CTL, .mu_gid = &rtw89_mac_mu_gid_addr_be, + .boot_dbg = R_BE_BOOT_DBG, .muedca_ctrl = { .addr = R_BE_MUEDCA_EN, From b9f582e939f6777df5b11d3c99549fec60737644 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Sun, 12 Jul 2026 11:45:01 +0800 Subject: [PATCH 0355/1433] wifi: rtw89: 8922d: add TX time limit for 2GHz band Fix 2.4GHz specific L-SIG length TX issue, causing interoperability problem with certain APs. Limit the A-MPDU duration to be workaround. For 8922DE, the MAC limit is 164 ticks, and BB limit is 4608 us. The conversion is 32.768us / tick. Since smaller limit should be adopted, BB limit is filled into newly added field. The units of register and CCTL table are tick and us/512 respectively. Convert to target unit when filling values. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260712034506.53209-4-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.h | 1 + drivers/net/wireless/realtek/rtw89/mac.c | 20 +++++++++++++++++-- drivers/net/wireless/realtek/rtw89/rtw8851b.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8852a.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8852b.c | 1 + .../net/wireless/realtek/rtw89/rtw8852bt.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8852c.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8922a.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8922d.c | 9 +++++++++ 9 files changed, 34 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 0ff090175ce5..11b05dbabee6 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -5418,6 +5418,7 @@ struct rtw89_chip_info { const struct wiphy_wowlan_support *wowlan_stub; const struct rtw89_xtal_info *xtal_info; unsigned long default_quirks; /* bitmap of rtw89_quirks */ + u16 txtime_limit_2ghz; }; struct rtw89_chip_variant { diff --git a/drivers/net/wireless/realtek/rtw89/mac.c b/drivers/net/wireless/realtek/rtw89/mac.c index 3e06a0bdf7e9..d8f0add7e1b0 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.c +++ b/drivers/net/wireless/realtek/rtw89/mac.c @@ -7050,11 +7050,23 @@ __rtw89_mac_set_tx_time(struct rtw89_dev *rtwdev, struct rtw89_sta_link *rtwsta_ { #define MAC_AX_DFLT_TX_TIME 5280 const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; + const struct rtw89_chip_info *chip = rtwdev->chip; u8 mac_idx = rtwsta_link->rtwvif_link->mac_idx; u32 max_tx_time = tx_time == 0 ? MAC_AX_DFLT_TX_TIME : tx_time; + struct rtw89_entity_conf conf; + const struct rtw89_chan *chan; u32 reg; int ret = 0; + if (chip->txtime_limit_2ghz) { + rtw89_entity_get_conf(rtwdev, &conf); + chan = conf.chans[mac_idx]; + + if (chan->band_type == RTW89_BAND_2G) + max_tx_time = min_t(u32, max_tx_time, + chip->txtime_limit_2ghz); + } + if (rtwsta_link->cctl_tx_time) { rtwsta_link->ampdu_max_time = (max_tx_time - 512) >> 9; ret = rtw89_chip_h2c_txtime_cmac_tbl(rtwdev, rtwsta_link); @@ -7065,9 +7077,13 @@ __rtw89_mac_set_tx_time(struct rtw89_dev *rtwdev, struct rtw89_sta_link *rtwsta_ return ret; } + if (chip->chip_gen == RTW89_CHIP_AX) + max_tx_time >>= 5; + else + max_tx_time = max_tx_time * 1000 >> 15; + reg = rtw89_mac_reg_by_idx(rtwdev, mac->agg_limit.addr, mac_idx); - rtw89_write32_mask(rtwdev, reg, mac->agg_limit.mask, - max_tx_time >> 5); + rtw89_write32_mask(rtwdev, reg, mac->agg_limit.mask, max_tx_time); } return ret; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b.c b/drivers/net/wireless/realtek/rtw89/rtw8851b.c index 91d6cddc713e..35ca38fa30e8 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851b.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851b.c @@ -2780,6 +2780,7 @@ const struct rtw89_chip_info rtw8851b_chip_info = { #endif .xtal_info = &rtw8851b_xtal_info, .default_quirks = 0, + .txtime_limit_2ghz = 0, }; EXPORT_SYMBOL(rtw8851b_chip_info); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852a.c b/drivers/net/wireless/realtek/rtw89/rtw8852a.c index ed6dee63694f..f32c7c6a4075 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852a.c @@ -2518,6 +2518,7 @@ const struct rtw89_chip_info rtw8852a_chip_info = { #endif .xtal_info = &rtw8852a_xtal_info, .default_quirks = 0, + .txtime_limit_2ghz = 0, }; EXPORT_SYMBOL(rtw8852a_chip_info); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852b.c b/drivers/net/wireless/realtek/rtw89/rtw8852b.c index 4f55a153d169..a45f05309af8 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852b.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852b.c @@ -1116,6 +1116,7 @@ const struct rtw89_chip_info rtw8852b_chip_info = { #endif .xtal_info = NULL, .default_quirks = 0, + .txtime_limit_2ghz = 0, }; EXPORT_SYMBOL(rtw8852b_chip_info); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bt.c b/drivers/net/wireless/realtek/rtw89/rtw8852bt.c index 9e0c48fde2fa..8d5dd93626b3 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852bt.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852bt.c @@ -952,6 +952,7 @@ const struct rtw89_chip_info rtw8852bt_chip_info = { #endif .xtal_info = NULL, .default_quirks = 0, + .txtime_limit_2ghz = 0, }; EXPORT_SYMBOL(rtw8852bt_chip_info); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852c.c b/drivers/net/wireless/realtek/rtw89/rtw8852c.c index f21dd87e3846..89c8b0d7a554 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852c.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852c.c @@ -3321,6 +3321,7 @@ const struct rtw89_chip_info rtw8852c_chip_info = { #endif .xtal_info = NULL, .default_quirks = 0, + .txtime_limit_2ghz = 0, }; EXPORT_SYMBOL(rtw8852c_chip_info); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922a.c b/drivers/net/wireless/realtek/rtw89/rtw8922a.c index cdcce091b922..030866080cbd 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922a.c @@ -3303,6 +3303,7 @@ const struct rtw89_chip_info rtw8922a_chip_info = { #endif .xtal_info = NULL, .default_quirks = 0, + .txtime_limit_2ghz = 0, }; EXPORT_SYMBOL(rtw8922a_chip_info); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c index c28670e82c3e..50a896c0cee9 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c @@ -1173,8 +1173,10 @@ static void rtw8922d_set_channel_mac(struct rtw89_dev *rtwdev, u32 sub_carr = rtw89_mac_reg_by_idx(rtwdev, R_BE_TX_SUB_BAND_VALUE, mac_idx); u32 chk_rate = rtw89_mac_reg_by_idx(rtwdev, R_BE_TXRATE_CHK, mac_idx); u32 rf_mod = rtw89_mac_reg_by_idx(rtwdev, R_BE_WMAC_RFMOD, mac_idx); + const struct rtw89_chip_info *chip = rtwdev->chip; u8 txsb20 = 0, txsb40 = 0, txsb80 = 0; u8 rf_mod_val, chk_rate_mask, sifs; + u16 tx_time = AMPDU_MAX_TIME_V1; u32 txsb; u32 reg; @@ -1255,6 +1257,12 @@ static void rtw8922d_set_channel_mac(struct rtw89_dev *rtwdev, reg = rtw89_mac_reg_by_idx(rtwdev, R_BE_MUEDCA_EN, mac_idx); rtw89_write32_mask(rtwdev, reg, B_BE_SIFS_MACTXEN_TB_T1_DOT05US_MASK, sifs); + + if (chan->band_type == RTW89_BAND_2G && chip->txtime_limit_2ghz) + tx_time = min_t(u32, tx_time, chip->txtime_limit_2ghz * 1000 >> 15); + + reg = rtw89_mac_reg_by_idx(rtwdev, R_BE_AMPDU_AGG_LIMIT, mac_idx); + rtw89_write32_mask(rtwdev, reg, B_BE_AMPDU_MAX_TIME_MASK, tx_time); } static const u32 rtw8922d_sco_barker_threshold[14] = { @@ -3757,6 +3765,7 @@ const struct rtw89_chip_info rtw8922d_chip_info = { #endif .xtal_info = NULL, .default_quirks = BIT(RTW89_QUIRK_THERMAL_PROT_120C), + .txtime_limit_2ghz = 4608, }; EXPORT_SYMBOL(rtw8922d_chip_info); From ed74acea8320fa60d89abe0c3b6212d60fcd8227 Mon Sep 17 00:00:00 2001 From: Zong-Zhe Yang Date: Sun, 12 Jul 2026 11:45:02 +0800 Subject: [PATCH 0356/1433] wifi: rtw89: introduce helper to get tx shape index TX shape has a set of parameters inside RFE (RF Front End) parameters. It also depends on regulation and even will depend on regulatory 6 GHz power type afterwards. Introduce a helper to encapsulate the access to TX shape index. Signed-off-by: Zong-Zhe Yang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260712034506.53209-5-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.h | 13 +++++++++++++ drivers/net/wireless/realtek/rtw89/rtw8851b.c | 6 ++---- .../net/wireless/realtek/rtw89/rtw8852b_common.c | 6 ++---- drivers/net/wireless/realtek/rtw89/rtw8852c.c | 6 ++---- drivers/net/wireless/realtek/rtw89/rtw8922a.c | 9 ++------- drivers/net/wireless/realtek/rtw89/rtw8922d.c | 9 ++------- 6 files changed, 23 insertions(+), 26 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 11b05dbabee6..f554e2ca1d1c 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -8448,6 +8448,19 @@ static inline u8 rtw89_regd_get(struct rtw89_dev *rtwdev, u8 band) return txpwr_regd; } +static inline u8 rtw89_get_tx_shape_idx(struct rtw89_dev *rtwdev, u8 band, + enum rtw89_rate_section rs) +{ + const struct rtw89_rfe_parms *rfe_parms = rtwdev->rfe_parms; + const struct rtw89_tx_shape *tx_shape = &rfe_parms->tx_shape; + u8 regd = rtw89_regd_get(rtwdev, band); + + if (unlikely(rs >= RTW89_RS_TX_SHAPE_NUM)) + rs = RTW89_RS_OFDM; + + return (*tx_shape->lmt)[band][rs][regd]; +} + static inline void rtw89_ctrl_btg_bt_rx(struct rtw89_dev *rtwdev, bool en, enum rtw89_phy_idx phy_idx) { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b.c b/drivers/net/wireless/realtek/rtw89/rtw8851b.c index 35ca38fa30e8..e3a17f539ad6 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851b.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851b.c @@ -1918,11 +1918,9 @@ static void rtw8851b_set_tx_shape(struct rtw89_dev *rtwdev, const struct rtw89_chan *chan, enum rtw89_phy_idx phy_idx) { - const struct rtw89_rfe_parms *rfe_parms = rtwdev->rfe_parms; u8 band = chan->band_type; - u8 regd = rtw89_regd_get(rtwdev, band); - u8 tx_shape_cck = (*rfe_parms->tx_shape.lmt)[band][RTW89_RS_CCK][regd]; - u8 tx_shape_ofdm = (*rfe_parms->tx_shape.lmt)[band][RTW89_RS_OFDM][regd]; + u8 tx_shape_cck = rtw89_get_tx_shape_idx(rtwdev, band, RTW89_RS_CCK); + u8 tx_shape_ofdm = rtw89_get_tx_shape_idx(rtwdev, band, RTW89_RS_OFDM); if (band == RTW89_BAND_2G) rtw8851b_bb_set_tx_shape_dfir(rtwdev, chan, tx_shape_cck, phy_idx); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852b_common.c b/drivers/net/wireless/realtek/rtw89/rtw8852b_common.c index 7d409a64869f..7c68260dce70 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852b_common.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852b_common.c @@ -1321,11 +1321,9 @@ static void rtw8852bx_set_tx_shape(struct rtw89_dev *rtwdev, const struct rtw89_chan *chan, enum rtw89_phy_idx phy_idx) { - const struct rtw89_rfe_parms *rfe_parms = rtwdev->rfe_parms; u8 band = chan->band_type; - u8 regd = rtw89_regd_get(rtwdev, band); - u8 tx_shape_cck = (*rfe_parms->tx_shape.lmt)[band][RTW89_RS_CCK][regd]; - u8 tx_shape_ofdm = (*rfe_parms->tx_shape.lmt)[band][RTW89_RS_OFDM][regd]; + u8 tx_shape_cck = rtw89_get_tx_shape_idx(rtwdev, band, RTW89_RS_CCK); + u8 tx_shape_ofdm = rtw89_get_tx_shape_idx(rtwdev, band, RTW89_RS_OFDM); if (band == RTW89_BAND_2G) rtw8852bx_bb_set_tx_shape_dfir(rtwdev, chan, tx_shape_cck, phy_idx); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852c.c b/drivers/net/wireless/realtek/rtw89/rtw8852c.c index 89c8b0d7a554..3dc6dfce082a 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852c.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852c.c @@ -2193,11 +2193,9 @@ static void rtw8852c_set_tx_shape(struct rtw89_dev *rtwdev, const struct rtw89_chan *chan, enum rtw89_phy_idx phy_idx) { - const struct rtw89_rfe_parms *rfe_parms = rtwdev->rfe_parms; u8 band = chan->band_type; - u8 regd = rtw89_regd_get(rtwdev, band); - u8 tx_shape_cck = (*rfe_parms->tx_shape.lmt)[band][RTW89_RS_CCK][regd]; - u8 tx_shape_ofdm = (*rfe_parms->tx_shape.lmt)[band][RTW89_RS_OFDM][regd]; + u8 tx_shape_cck = rtw89_get_tx_shape_idx(rtwdev, band, RTW89_RS_CCK); + u8 tx_shape_ofdm = rtw89_get_tx_shape_idx(rtwdev, band, RTW89_RS_OFDM); if (band == RTW89_BAND_2G) rtw8852c_bb_set_tx_shape_dfir(rtwdev, chan, tx_shape_cck, phy_idx); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922a.c b/drivers/net/wireless/realtek/rtw89/rtw8922a.c index 030866080cbd..854ef9a980bd 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922a.c @@ -2502,15 +2502,10 @@ static void rtw8922a_set_tx_shape(struct rtw89_dev *rtwdev, const struct rtw89_chan *chan, enum rtw89_phy_idx phy_idx) { - const struct rtw89_rfe_parms *rfe_parms = rtwdev->rfe_parms; - const struct rtw89_tx_shape *tx_shape = &rfe_parms->tx_shape; + u8 band = chan->band_type; u8 tx_shape_idx; - u8 band, regd; - - band = chan->band_type; - regd = rtw89_regd_get(rtwdev, band); - tx_shape_idx = (*tx_shape->lmt)[band][RTW89_RS_OFDM][regd]; + tx_shape_idx = rtw89_get_tx_shape_idx(rtwdev, band, RTW89_RS_OFDM); if (tx_shape_idx == 0) rtw8922a_bb_tx_triangular(rtwdev, false, phy_idx); else diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c index 50a896c0cee9..929bcfc8089d 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c @@ -2854,16 +2854,11 @@ static void rtw8922d_set_tx_shape(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx) { const struct rtw89_bb_wrap_data *d = rtwdev->phy_info.bb_wrap_data; - const struct rtw89_rfe_parms *rfe_parms = rtwdev->rfe_parms; - const struct rtw89_tx_shape *tx_shape = &rfe_parms->tx_shape; + u8 band = chan->band_type; u8 tx_shape_idx; - u8 band, regd; const u16 *th; - band = chan->band_type; - regd = rtw89_regd_get(rtwdev, band); - tx_shape_idx = (*tx_shape->lmt)[band][RTW89_RS_OFDM][regd]; - + tx_shape_idx = rtw89_get_tx_shape_idx(rtwdev, band, RTW89_RS_OFDM); if (tx_shape_idx == 0) goto disable; From 7054b03136e4bd5645d12cbd8d93b15afae4b8be Mon Sep 17 00:00:00 2001 From: Zong-Zhe Yang Date: Sun, 12 Jul 2026 11:45:03 +0800 Subject: [PATCH 0357/1433] wifi: rtw89: add tx shape v0 to keep built-in arrays compatible during transitions TX shape parameters can come from (old way) built-in arrays or (new way) FW elements. The built-in arrays will no longer be updated, but will be retained during a certain transition period. However, the format of newer TX shape parameters are going to be expanded. It will only be applied to FW elements. To keep built-in arrays compatible during transition period, add tx shape v0 for old format. The v0 fields can be removed along with built-in arrays once transition period ends. Signed-off-by: Zong-Zhe Yang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260712034506.53209-6-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.h | 9 +++++++++ drivers/net/wireless/realtek/rtw89/rtw8851b_table.c | 8 ++++---- drivers/net/wireless/realtek/rtw89/rtw8852b_table.c | 4 ++-- drivers/net/wireless/realtek/rtw89/rtw8852c_table.c | 4 ++-- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index f554e2ca1d1c..45e77d84f3b8 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -4923,6 +4923,9 @@ struct rtw89_txpwr_rule_6ghz { struct rtw89_tx_shape { const u8 (*lmt)[RTW89_BAND_NUM][RTW89_RS_TX_SHAPE_NUM][RTW89_REGD_NUM]; const u8 (*lmt_ru)[RTW89_BAND_NUM][RTW89_REGD_NUM]; + + const u8 (*lmt_v0)[RTW89_BAND_NUM][RTW89_RS_TX_SHAPE_NUM][RTW89_REGD_NUM]; + const u8 (*lmt_ru_v0)[RTW89_BAND_NUM][RTW89_REGD_NUM]; }; struct rtw89_rfe_parms { @@ -8458,7 +8461,13 @@ static inline u8 rtw89_get_tx_shape_idx(struct rtw89_dev *rtwdev, u8 band, if (unlikely(rs >= RTW89_RS_TX_SHAPE_NUM)) rs = RTW89_RS_OFDM; + if (!tx_shape->lmt) + goto v0; + return (*tx_shape->lmt)[band][rs][regd]; + +v0: + return (*tx_shape->lmt_v0)[band][rs][regd]; } static inline void rtw89_ctrl_btg_bt_rx(struct rtw89_dev *rtwdev, bool en, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b_table.c b/drivers/net/wireless/realtek/rtw89/rtw8851b_table.c index b8105f6e94f1..f875beaec555 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851b_table.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851b_table.c @@ -14887,8 +14887,8 @@ const struct rtw89_rfe_parms rtw89_8851b_dflt_parms = { .lmt_ru = &rtw89_8851b_txpwr_lmt_ru_5g, }, .tx_shape = { - .lmt = &rtw89_8851b_tx_shape_lmt, - .lmt_ru = &rtw89_8851b_tx_shape_lmt_ru, + .lmt_v0 = &rtw89_8851b_tx_shape_lmt, + .lmt_ru_v0 = &rtw89_8851b_tx_shape_lmt_ru, }, }; @@ -14903,8 +14903,8 @@ static const struct rtw89_rfe_parms rtw89_8851b_rfe_parms_type2 = { .lmt_ru = &rtw89_8851b_txpwr_lmt_ru_5g_type2, }, .tx_shape = { - .lmt = &rtw89_8851b_tx_shape_lmt, - .lmt_ru = &rtw89_8851b_tx_shape_lmt_ru, + .lmt_v0 = &rtw89_8851b_tx_shape_lmt, + .lmt_ru_v0 = &rtw89_8851b_tx_shape_lmt_ru, }, }; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852b_table.c b/drivers/net/wireless/realtek/rtw89/rtw8852b_table.c index 96b18e9095b3..1485a8fc4bde 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852b_table.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852b_table.c @@ -22922,7 +22922,7 @@ const struct rtw89_rfe_parms rtw89_8852b_dflt_parms = { .lmt_ru = &rtw89_8852b_txpwr_lmt_ru_5g, }, .tx_shape = { - .lmt = &rtw89_8852b_tx_shape_lmt, - .lmt_ru = &rtw89_8852b_tx_shape_lmt_ru, + .lmt_v0 = &rtw89_8852b_tx_shape_lmt, + .lmt_ru_v0 = &rtw89_8852b_tx_shape_lmt_ru, }, }; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852c_table.c b/drivers/net/wireless/realtek/rtw89/rtw8852c_table.c index b4cf497e8524..c5ede63e7e23 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852c_table.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852c_table.c @@ -57154,7 +57154,7 @@ const struct rtw89_rfe_parms rtw89_8852c_dflt_parms = { .lmt_ru = &rtw89_8852c_txpwr_lmt_ru_6g, }, .tx_shape = { - .lmt = &rtw89_8852c_tx_shape_lmt, - .lmt_ru = &rtw89_8852c_tx_shape_lmt_ru, + .lmt_v0 = &rtw89_8852c_tx_shape_lmt, + .lmt_ru_v0 = &rtw89_8852c_tx_shape_lmt_ru, }, }; From b4eaac15cbb4ae90130bdde9952d2e50deeb40c3 Mon Sep 17 00:00:00 2001 From: Zong-Zhe Yang Date: Sun, 12 Jul 2026 11:45:04 +0800 Subject: [PATCH 0358/1433] wifi: rtw89: extend tx shape format for regulatory 6 GHz power type Even under the same regulation, TX shape may need different settings for different 6 GHz power types. So, add one more dimension for that. Because TX shape parameters are not quite large, the 2/5/6 GHz sections are not divided into different structures. So, the 2/5 GHz sections will also get the new dimension. To 2/5 GHz sections, fill the TX shape settings with RTW89_REG_6GHZ_POWER_DFLT (0) field. Signed-off-by: Zong-Zhe Yang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260712034506.53209-7-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.h | 17 ++++++++++++----- drivers/net/wireless/realtek/rtw89/fw.c | 16 ++++++++++++++-- drivers/net/wireless/realtek/rtw89/fw.h | 2 ++ 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 45e77d84f3b8..1ecaa14b790e 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -4921,8 +4921,9 @@ struct rtw89_txpwr_rule_6ghz { }; struct rtw89_tx_shape { - const u8 (*lmt)[RTW89_BAND_NUM][RTW89_RS_TX_SHAPE_NUM][RTW89_REGD_NUM]; - const u8 (*lmt_ru)[RTW89_BAND_NUM][RTW89_REGD_NUM]; + const u8 (*lmt)[RTW89_BAND_NUM][RTW89_RS_TX_SHAPE_NUM][RTW89_REGD_NUM] + [NUM_OF_RTW89_REG_6GHZ_POWER]; + const u8 (*lmt_ru)[RTW89_BAND_NUM][RTW89_REGD_NUM][NUM_OF_RTW89_REG_6GHZ_POWER]; const u8 (*lmt_v0)[RTW89_BAND_NUM][RTW89_RS_TX_SHAPE_NUM][RTW89_REGD_NUM]; const u8 (*lmt_ru_v0)[RTW89_BAND_NUM][RTW89_REGD_NUM]; @@ -5019,12 +5020,13 @@ struct rtw89_txpwr_lmt_ru_6ghz_data { struct rtw89_tx_shape_lmt_data { struct rtw89_txpwr_conf conf; - u8 v[RTW89_BAND_NUM][RTW89_RS_TX_SHAPE_NUM][RTW89_REGD_NUM]; + u8 v[RTW89_BAND_NUM][RTW89_RS_TX_SHAPE_NUM][RTW89_REGD_NUM] + [NUM_OF_RTW89_REG_6GHZ_POWER]; }; struct rtw89_tx_shape_lmt_ru_data { struct rtw89_txpwr_conf conf; - u8 v[RTW89_BAND_NUM][RTW89_REGD_NUM]; + u8 v[RTW89_BAND_NUM][RTW89_REGD_NUM][NUM_OF_RTW89_REG_6GHZ_POWER]; }; struct rtw89_rfe_data { @@ -8454,8 +8456,10 @@ static inline u8 rtw89_regd_get(struct rtw89_dev *rtwdev, u8 band) static inline u8 rtw89_get_tx_shape_idx(struct rtw89_dev *rtwdev, u8 band, enum rtw89_rate_section rs) { + struct rtw89_regulatory_info *regulatory = &rtwdev->regulatory; const struct rtw89_rfe_parms *rfe_parms = rtwdev->rfe_parms; const struct rtw89_tx_shape *tx_shape = &rfe_parms->tx_shape; + u8 reg6_pwr = regulatory->reg_6ghz_power; u8 regd = rtw89_regd_get(rtwdev, band); if (unlikely(rs >= RTW89_RS_TX_SHAPE_NUM)) @@ -8464,7 +8468,10 @@ static inline u8 rtw89_get_tx_shape_idx(struct rtw89_dev *rtwdev, u8 band, if (!tx_shape->lmt) goto v0; - return (*tx_shape->lmt)[band][rs][regd]; + if (band != RTW89_BAND_6G) + reg6_pwr = RTW89_REG_6GHZ_POWER_DFLT; + + return (*tx_shape->lmt)[band][rs][regd][reg6_pwr]; v0: return (*tx_shape->lmt_v0)[band][rs][regd]; diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index f290bcdd4fda..4df2ba5bfa44 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -11916,6 +11916,12 @@ fw_tx_shape_lmt_entry_valid(const struct rtw89_fw_tx_shape_lmt_entry *e, if (e->regd >= RTW89_REGD_NUM) return false; + /* ensure compatibility work with 2/5 GHz */ + static_assert(RTW89_REG_6GHZ_POWER_DFLT == 0); + + if (e->reg6_pwr >= NUM_OF_RTW89_REG_6GHZ_POWER) + return false; + return true; } @@ -11930,7 +11936,7 @@ void rtw89_fw_load_tx_shape_lmt(struct rtw89_tx_shape_lmt_data *data) if (!fw_tx_shape_lmt_entry_valid(&entry, cursor, conf)) continue; - data->v[entry.band][entry.tx_shape_rs][entry.regd] = entry.v; + data->v[entry.band][entry.tx_shape_rs][entry.regd][entry.reg6_pwr] = entry.v; } } @@ -11947,6 +11953,12 @@ fw_tx_shape_lmt_ru_entry_valid(const struct rtw89_fw_tx_shape_lmt_ru_entry *e, if (e->regd >= RTW89_REGD_NUM) return false; + /* ensure compatibility work with 2/5 GHz */ + static_assert(RTW89_REG_6GHZ_POWER_DFLT == 0); + + if (e->reg6_pwr >= NUM_OF_RTW89_REG_6GHZ_POWER) + return false; + return true; } @@ -11961,7 +11973,7 @@ void rtw89_fw_load_tx_shape_lmt_ru(struct rtw89_tx_shape_lmt_ru_data *data) if (!fw_tx_shape_lmt_ru_entry_valid(&entry, cursor, conf)) continue; - data->v[entry.band][entry.regd] = entry.v; + data->v[entry.band][entry.regd][entry.reg6_pwr] = entry.v; } } diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index c6131778f6a2..166c4bc9c1d0 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -5812,6 +5812,7 @@ struct rtw89_fw_tx_shape_lmt_entry { u8 tx_shape_rs; u8 regd; u8 v; + u8 reg6_pwr; } __packed; /* must consider compatibility; don't insert new in the mid */ @@ -5819,6 +5820,7 @@ struct rtw89_fw_tx_shape_lmt_ru_entry { u8 band; u8 regd; u8 v; + u8 reg6_pwr; } __packed; const struct rtw89_rfe_parms * From b5bf2dc96f95a34378df32ed0e271875c5242908 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Sun, 12 Jul 2026 11:45:05 +0800 Subject: [PATCH 0359/1433] wifi: rtw89: fw: do bb_preinit before downloading firmware The firmware access BB registers while initialization, so driver should do bb_preinit before downloading firmware. Otherwise, it might get BB IO stuck and throw error. rtw89_8922de 0000:04:00.0: loaded firmware rtw89/rtw8922d_fw.bin rtw89_8922de 0000:04:00.0: Firmware version 0.35.111.7 (51c56e7b), cmd version 1, type 14 rtw89_8922de 0000:04:00.0: Firmware version 0.35.111.7 (51c56e7b), cmd version 1, type 15 rtw89_8922de 0000:04:00.0: fw unexpected status 6 rtw89_8922de 0000:04:00.0: download firmware fail rtw89_8922de 0000:04:00.0: [ERR]fwdl 0x1E0 = 0x8000012 rtw89_8922de 0000:04:00.0: [ERR]fwdl 0x78F0 = 0x290900 rtw89_8922de 0000:04:00.0: [ERR]fw PC = 0x201445f2 rtw89_8922de 0000:04:00.0: [ERR]fw PC = 0x201445f2 rtw89_8922de 0000:04:00.0: [ERR]fw PC = 0x201445f2 rtw89_8922de 0000:04:00.0: [ERR]fw PC = 0x201445f2 rtw89_8922de 0000:04:00.0: [ERR]fw PC = 0x201445f2 rtw89_8922de 0000:04:00.0: [ERR]fw PC = 0x201445f2 rtw89_8922de 0000:04:00.0: [ERR]fw PC = 0x201445f2 rtw89_8922de 0000:04:00.0: [ERR]fw PC = 0x201445f2 rtw89_8922de 0000:04:00.0: [ERR]fw PC = 0x201445f2 rtw89_8922de 0000:04:00.0: [ERR]fw PC = 0x201445f2 rtw89_8922de 0000:04:00.0: [ERR]fw PC = 0x201445f2 rtw89_8922de 0000:04:00.0: [ERR]fw PC = 0x201445f2 rtw89_8922de 0000:04:00.0: [ERR]fw PC = 0x201445f2 rtw89_8922de 0000:04:00.0: [ERR]fw PC = 0x201445f2 rtw89_8922de 0000:04:00.0: [ERR]fw PC = 0x201445f2 rtw89_8922de 0000:04:00.0: [ERR]H2C path ready Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260712034506.53209-8-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.c | 12 +++++++++++- drivers/net/wireless/realtek/rtw89/mac.c | 7 ++++--- drivers/net/wireless/realtek/rtw89/mac.h | 3 ++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index 30a8e337498e..ffb39440a254 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -7200,9 +7200,19 @@ int rtw89_core_mlsr_switch(struct rtw89_dev *rtwdev, struct rtw89_vif *rtwvif, static int rtw89_chip_efuse_info_setup(struct rtw89_dev *rtwdev) { const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; + const struct rtw89_chip_info *chip = rtwdev->chip; + bool bb_preinit = false; int ret; - ret = rtw89_mac_partial_init(rtwdev, false); + /* + * Normally in probe stage, we don't need to do BB pre-initialization. + * However, RTL8922D firmware can access BB registers at firmware + * initial state, so initialize it to avoid BB IO stuck in firmware. + */ + if (chip->chip_id == RTL8922D) + bb_preinit = true; + + ret = rtw89_mac_partial_init(rtwdev, false, bb_preinit); if (ret) return ret; diff --git a/drivers/net/wireless/realtek/rtw89/mac.c b/drivers/net/wireless/realtek/rtw89/mac.c index d8f0add7e1b0..db9fca828829 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.c +++ b/drivers/net/wireless/realtek/rtw89/mac.c @@ -4308,13 +4308,14 @@ int rtw89_mac_disable_bb_rf(struct rtw89_dev *rtwdev) } EXPORT_SYMBOL(rtw89_mac_disable_bb_rf); -int rtw89_mac_partial_init(struct rtw89_dev *rtwdev, bool include_bb) +int rtw89_mac_partial_init(struct rtw89_dev *rtwdev, bool include_bb, + bool bb_preinit) { int ret; rtw89_mac_ctrl_hci_dma_trx(rtwdev, true); - if (include_bb) { + if (include_bb || bb_preinit) { /* Only call BB preinit including configuration of BB MCU for * the chips which need to download BB MCU firmware. Otherwise, * calling preinit later to prevent touching registers affecting @@ -4367,7 +4368,7 @@ int rtw89_mac_init(struct rtw89_dev *rtwdev) bool include_bb = !!chip->bbmcu_nr; int ret; - ret = rtw89_mac_partial_init(rtwdev, include_bb); + ret = rtw89_mac_partial_init(rtwdev, include_bb, include_bb); if (ret) goto fail; diff --git a/drivers/net/wireless/realtek/rtw89/mac.h b/drivers/net/wireless/realtek/rtw89/mac.h index c05f5ee0d2fd..493d2b9626a6 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.h +++ b/drivers/net/wireless/realtek/rtw89/mac.h @@ -1302,7 +1302,8 @@ rtw89_write32_port_set(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_l int rtw89_mac_pwr_on(struct rtw89_dev *rtwdev); void rtw89_mac_pwr_off(struct rtw89_dev *rtwdev); -int rtw89_mac_partial_init(struct rtw89_dev *rtwdev, bool include_bb); +int rtw89_mac_partial_init(struct rtw89_dev *rtwdev, bool include_bb, + bool bb_preinit); int rtw89_mac_preinit(struct rtw89_dev *rtwdev); int rtw89_mac_init(struct rtw89_dev *rtwdev); int rtw89_mac_dle_init(struct rtw89_dev *rtwdev, enum rtw89_qta_mode mode, From 3c15399ef64e89270b8c4cddb857e2f8886cb140 Mon Sep 17 00:00:00 2001 From: Zong-Zhe Yang Date: Sun, 12 Jul 2026 11:45:06 +0800 Subject: [PATCH 0360/1433] wifi: rtw89: debug: add diagnosis for RF Add debugfs diag_rf and show RFK (RF calibration) diagnosis things for now. Record channel related info before triggering RFK, and then record state of each kind of RFK from C2H event report. Besides, in track work, monitor TSSI status too. Both support history up to 10, and show records via debugfs. The following is an example of output. RFK (next index: 2) PHY-X = 0 0 0 0 0 0 0 0 0 0 S0-CH = 0012a 0012a 00000 00000 00000 00000 00000 00000 00000 00000 S0-CV = 0032c 0032c 00000 00000 00000 00000 00000 00000 00000 00000 S0-C5 = 10000 10000 00000 00000 00000 00000 00000 00000 00000 00000 S1-CH = 0012a 0012a 00000 00000 00000 00000 00000 00000 00000 00000 S1-CV = 0032d 0032d 00000 00000 00000 00000 00000 00000 00000 00000 S1-C5 = 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 PRE_NTFY = 0 0 0 0 0 0 0 0 0 0 TSSI = 1 1 0 0 0 0 0 0 0 0 IQK = 1 1 0 0 0 0 0 0 0 0 DPK = 1 1 0 0 0 0 0 0 0 0 TXGAPK = 1 1 0 0 0 0 0 0 0 0 DACK = 0 0 0 0 0 0 0 0 0 0 RX_DCK = 1 1 0 0 0 0 0 0 0 0 TX_IQK = 1 1 0 0 0 0 0 0 0 0 CIM3k = 1 1 0 0 0 0 0 0 0 0 TSSI-track (next index: 6) S0 = 00e 00e 00c 00d 00d 00e 00d 00d 00d 00e S1 = 00a 00b 009 00a 00a 00a 009 009 009 00a Debugfs diag_rf can also be used to manually trigger RFK when written by 1. Signed-off-by: Zong-Zhe Yang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260712034506.53209-9-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.c | 71 ++++++++++++- drivers/net/wireless/realtek/rtw89/core.h | 32 ++++++ drivers/net/wireless/realtek/rtw89/debug.c | 115 +++++++++++++++++++++ drivers/net/wireless/realtek/rtw89/phy.c | 62 +++++++++-- drivers/net/wireless/realtek/rtw89/phy.h | 3 + drivers/net/wireless/realtek/rtw89/reg.h | 1 + 6 files changed, 272 insertions(+), 12 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index ffb39440a254..b4b0a451fed7 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -486,6 +486,37 @@ void rtw89_core_set_chip_txpwr(struct rtw89_dev *rtwdev) __rtw89_core_set_chip_txpwr(rtwdev, conf.chans[1], RTW89_PHY_1); } +static void rtw89_core_rfk_record(struct rtw89_dev *rtwdev, + struct rtw89_vif_link *rtwvif_link, + bool start) +{ + struct rtw89_rfk_wait_info *info = &rtwdev->rfk_wait; + struct rtw89_rfk_record *record = &info->records[info->record_idx]; + const struct rtw89_chip_info *chip = rtwdev->chip; + u8 path_num = min(chip->rf_path_num, RTW89_RFK_RECORD_PATH_NR); + int path; + + if (!start) + goto end; + + info->record_ptr = record; + record->phy_idx = rtwvif_link->phy_idx; + + for (path = 0; path < path_num; path++) { + record->ch[path] = rtw89_read_rf(rtwdev, path, RR_CFGCH, + RR_CFGCH_BAND0 | RR_CFGCH_CH); + record->cv[path] = rtw89_read_rf(rtwdev, path, RR_VCO, + RR_VCO_SEL | RR_VCO_STS); + record->c5[path] = rtw89_read_rf(rtwdev, path, RR_SYNFB, RFREG_MASK); + } + + return; + +end: + info->record_idx = (info->record_idx + 1) % RTW89_RFK_RECORD_HISTORY_NR; + info->record_ptr = NULL; +} + void rtw89_chip_rfk_channel(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link) { @@ -503,9 +534,14 @@ void rtw89_chip_rfk_channel(struct rtw89_dev *rtwdev, rtw89_set_channel(rtwdev); } - if (chip->ops->rfk_channel) + if (chip->ops->rfk_channel) { + rtw89_core_rfk_record(rtwdev, rtwvif_link, true); + chip->ops->rfk_channel(rtwdev, rtwvif_link); + rtw89_core_rfk_record(rtwdev, rtwvif_link, false); + } + if (prehdl_link) { rtw89_entity_force_hw(rtwdev, RTW89_PHY_NUM); rtw89_set_channel(rtwdev); @@ -5274,6 +5310,37 @@ static void rtw89_enter_lps_track(struct rtw89_dev *rtwdev, } } +static void rtw89_core_rfk_tssi_record(struct rtw89_dev *rtwdev) +{ + const struct rtw89_chip_info *chip = rtwdev->chip; + u8 path_num = min(chip->rf_path_num, RTW89_RFK_RECORD_PATH_NR); + struct rtw89_rfk_wait_info *info = &rtwdev->rfk_wait; + int i = info->record_tssi_idx; + u32 base; + u32 mask; + int path; + + if (chip->chip_gen == RTW89_CHIP_AX) + return; + + info->record_tssi_idx = (info->record_tssi_idx + 1) % + RTW89_RFK_RECORD_HISTORY_NR; + + if (chip->chip_id == RTL8922A) { + base = 0x2ee10; + mask = 0x7fc00; + } else { + base = 0x2f918; + mask = 0x000001ff; + } + + for (path = 0; path < path_num; path++) { + u32 addr = base + 0x100 * path; + + info->tssi_code[i][path] = rtw89_read32_mask(rtwdev, addr, mask); + } +} + static void rtw89_core_rfk_track(struct rtw89_dev *rtwdev) { enum rtw89_entity_mode mode; @@ -5283,6 +5350,8 @@ static void rtw89_core_rfk_track(struct rtw89_dev *rtwdev) return; rtw89_chip_rfk_track(rtwdev); + + rtw89_core_rfk_tssi_record(rtwdev); } void rtw89_core_update_p2p_ps(struct rtw89_dev *rtwdev, diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 1ecaa14b790e..c0795e0d10cd 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -6134,11 +6134,43 @@ enum rtw89_rfk_report_state { RTW89_RFK_STATE_H2C_CMD_ERR = 0x4, }; +enum rtw89_rfk_report_types { + RTW89_RFK_REPORT_PRE_NTFY, + RTW89_RFK_REPORT_TSSI, + RTW89_RFK_REPORT_IQK, + RTW89_RFK_REPORT_DPK, + RTW89_RFK_REPORT_TXGAPK, + RTW89_RFK_REPORT_DACK, + RTW89_RFK_REPORT_RX_DCK, + RTW89_RFK_REPORT_TX_IQK, + RTW89_RFK_REPORT_CIM3k, + + NUM_OF_RTW89_RFK_REPORT_TYPES, +}; + +#define RTW89_RFK_RECORD_PATH_NR 2 +#define RTW89_RFK_RECORD_HISTORY_NR 10 + +struct rtw89_rfk_record { + u32 ch[RTW89_RFK_RECORD_PATH_NR]; + u32 cv[RTW89_RFK_RECORD_PATH_NR]; + u32 c5[RTW89_RFK_RECORD_PATH_NR]; + enum rtw89_phy_idx phy_idx; + enum rtw89_rfk_report_state states[NUM_OF_RTW89_RFK_REPORT_TYPES]; +}; + struct rtw89_rfk_wait_info { struct completion completion; ktime_t start_time; enum rtw89_rfk_report_state state; u8 version; + + int record_idx; + struct rtw89_rfk_record *record_ptr; + struct rtw89_rfk_record records[RTW89_RFK_RECORD_HISTORY_NR]; + + int record_tssi_idx; + u32 tssi_code[RTW89_RFK_RECORD_HISTORY_NR][RTW89_RFK_RECORD_PATH_NR]; }; #define RTW89_DACK_PATH_NR 2 diff --git a/drivers/net/wireless/realtek/rtw89/debug.c b/drivers/net/wireless/realtek/rtw89/debug.c index 8b41991963e8..7e640c7166e4 100644 --- a/drivers/net/wireless/realtek/rtw89/debug.c +++ b/drivers/net/wireless/realtek/rtw89/debug.c @@ -93,6 +93,7 @@ struct rtw89_debugfs { struct rtw89_debugfs_priv beacon_info; struct rtw89_debugfs_priv diag_mac; struct rtw89_debugfs_priv diag_bb; + struct rtw89_debugfs_priv diag_rf; struct rtw89_debugfs_priv monitor_opts; }; @@ -5393,6 +5394,118 @@ rtw89_debug_priv_diag_bb_get(struct rtw89_dev *rtwdev, return p - buf; } +static ssize_t +rtw89_debug_priv_diag_rf_get(struct rtw89_dev *rtwdev, + struct rtw89_debugfs_priv *debugfs_priv, + char *buf, size_t bufsz) +{ + struct rtw89_rfk_wait_info *info = &rtwdev->rfk_wait; + char *p = buf, *end = buf + bufsz; + int i, j; + + p += scnprintf(p, end - p, "RFK (next index: %d)\n", info->record_idx); + + p += scnprintf(p, end - p, " PHY-X ="); + for (i = 0; i < RTW89_RFK_RECORD_HISTORY_NR; i++) { + struct rtw89_rfk_record *record = &info->records[i]; + + p += scnprintf(p, end - p, " %4d ", record->phy_idx); + } + p += scnprintf(p, end - p, "\n"); + + for (j = 0; j < RTW89_RFK_RECORD_PATH_NR; j++) { + p += scnprintf(p, end - p, " S%d-CH =", j); + for (i = 0; i < RTW89_RFK_RECORD_HISTORY_NR; i++) { + struct rtw89_rfk_record *record = &info->records[i]; + + p += scnprintf(p, end - p, " %05x", record->ch[j]); + } + p += scnprintf(p, end - p, "\n"); + + p += scnprintf(p, end - p, " S%d-CV =", j); + for (i = 0; i < RTW89_RFK_RECORD_HISTORY_NR; i++) { + struct rtw89_rfk_record *record = &info->records[i]; + + p += scnprintf(p, end - p, " %05x", record->cv[j]); + } + p += scnprintf(p, end - p, "\n"); + + p += scnprintf(p, end - p, " S%d-C5 =", j); + for (i = 0; i < RTW89_RFK_RECORD_HISTORY_NR; i++) { + struct rtw89_rfk_record *record = &info->records[i]; + + p += scnprintf(p, end - p, " %05x", record->c5[j]); + } + p += scnprintf(p, end - p, "\n"); + } + + for (j = 0; j < NUM_OF_RTW89_RFK_REPORT_TYPES; j++) { + p += scnprintf(p, end - p, " %-8s =", rtw89_rfk_report_names[j]); + for (i = 0; i < RTW89_RFK_RECORD_HISTORY_NR; i++) { + struct rtw89_rfk_record *record = &info->records[i]; + + p += scnprintf(p, end - p, " %4d ", record->states[j]); + } + p += scnprintf(p, end - p, "\n"); + } + + p += scnprintf(p, end - p, "TSSI-track (next index: %d)\n", info->record_tssi_idx); + + for (j = 0; j < RTW89_RFK_RECORD_PATH_NR; j++) { + p += scnprintf(p, end - p, " S%d =", j); + for (i = 0; i < RTW89_RFK_RECORD_HISTORY_NR; i++) + p += scnprintf(p, end - p, " %03x", info->tssi_code[i][j]); + p += scnprintf(p, end - p, "\n"); + } + + return p - buf; +} + +static void rtw89_dbg_diag_rf_set_rfk(struct rtw89_dev *rtwdev) +{ + struct rtw89_entity_mgnt *mgnt = &rtwdev->hal.entity_mgnt; + struct rtw89_vif_link *rtwvif_link; + struct rtw89_vif *rtwvif; + unsigned int link_id; + + list_for_each_entry(rtwvif, &mgnt->active_list, mgnt_entry) + rtw89_vif_for_each_link(rtwvif, rtwvif_link, link_id) + rtw89_chip_rfk_channel(rtwdev, rtwvif_link); +} + +enum rtw89_dbg_diag_rf_type { + RTW89_DBG_DIAG_RF_RSVD = 0, + RTW89_DBG_DIAG_RF_RFK = 1, +}; + +static ssize_t +rtw89_debug_priv_diag_rf_set(struct rtw89_dev *rtwdev, + struct rtw89_debugfs_priv *debugfs_priv, + const char *buf, size_t count) +{ + u8 type; + int ret; + + ret = kstrtou8(buf, 0, &type); + if (ret) + return -EINVAL; + + if (rtwdev->scanning) + rtw89_hw_scan_abort(rtwdev, rtwdev->scan_info.scanning_vif); + + rtw89_leave_ps_mode(rtwdev); + + switch (type) { + case RTW89_DBG_DIAG_RF_RFK: + rtw89_dbg_diag_rf_set_rfk(rtwdev); + break; + default: + return -EOPNOTSUPP; + } + + return count; +} + static ssize_t rtw89_debug_priv_monitor_opts_get(struct rtw89_dev *rtwdev, struct rtw89_debugfs_priv *debugfs_priv, @@ -5505,6 +5618,7 @@ static const struct rtw89_debugfs rtw89_debugfs_templ = { .beacon_info = rtw89_debug_priv_get(beacon_info), .diag_mac = rtw89_debug_priv_get(diag_mac, RSIZE_16K, RLOCK), .diag_bb = rtw89_debug_priv_get(diag_bb, RSIZE_8K, RLOCK), + .diag_rf = rtw89_debug_priv_set_and_get(diag_rf, RWLOCK), .monitor_opts = rtw89_debug_priv_set_and_get(monitor_opts, RWLOCK), }; @@ -5562,6 +5676,7 @@ void rtw89_debugfs_add_sec2(struct rtw89_dev *rtwdev, struct dentry *debugfs_top rtw89_debugfs_add_r(beacon_info); rtw89_debugfs_add_r(diag_mac); rtw89_debugfs_add_r(diag_bb); + rtw89_debugfs_add_rw(diag_rf); rtw89_debugfs_add_rw(monitor_opts); } diff --git a/drivers/net/wireless/realtek/rtw89/phy.c b/drivers/net/wireless/realtek/rtw89/phy.c index 30d5741da87e..c78107e92206 100644 --- a/drivers/net/wireless/realtek/rtw89/phy.c +++ b/drivers/net/wireless/realtek/rtw89/phy.c @@ -3968,6 +3968,23 @@ void (* const rtw89_phy_c2h_rfk_log_handler[])(struct rtw89_dev *rtwdev, [RTW89_PHY_C2H_RFK_LOG_FUNC_CIM3K] = rtw89_phy_c2h_rfk_log_cim3k, }; +#define NAME_RTW89_RFK_REPORT(type) \ + [RTW89_RFK_REPORT_ ## type] = #type + +const char * const rtw89_rfk_report_names[] = { + NAME_RTW89_RFK_REPORT(PRE_NTFY), + NAME_RTW89_RFK_REPORT(TSSI), + NAME_RTW89_RFK_REPORT(IQK), + NAME_RTW89_RFK_REPORT(DPK), + NAME_RTW89_RFK_REPORT(TXGAPK), + NAME_RTW89_RFK_REPORT(DACK), + NAME_RTW89_RFK_REPORT(RX_DCK), + NAME_RTW89_RFK_REPORT(TX_IQK), + NAME_RTW89_RFK_REPORT(CIM3k), +}; + +static_assert(ARRAY_SIZE(rtw89_rfk_report_names) == NUM_OF_RTW89_RFK_REPORT_TYPES); + static void rtw89_phy_rfk_report_prep(struct rtw89_dev *rtwdev) { @@ -3979,8 +3996,8 @@ void rtw89_phy_rfk_report_prep(struct rtw89_dev *rtwdev) } static -int rtw89_phy_rfk_report_wait(struct rtw89_dev *rtwdev, const char *rfk_name, - unsigned int ms) +int ____rtw89_phy_rfk_report_wait(struct rtw89_dev *rtwdev, const char *rfk_name, + unsigned int ms) { struct rtw89_rfk_wait_info *wait = &rtwdev->rfk_wait; unsigned long time_left; @@ -4017,6 +4034,29 @@ int rtw89_phy_rfk_report_wait(struct rtw89_dev *rtwdev, const char *rfk_name, return 0; } +static +int __rtw89_phy_rfk_report_wait(struct rtw89_dev *rtwdev, + enum rtw89_rfk_report_types rfk_type, + enum rtw89_phy_idx phy_idx, + const struct rtw89_chan *chan, + unsigned int ms) +{ + const char *rfk_name = rtw89_rfk_report_names[rfk_type]; + struct rtw89_rfk_wait_info *wait = &rtwdev->rfk_wait; + struct rtw89_rfk_record *record = wait->record_ptr; + int ret; + + ret = ____rtw89_phy_rfk_report_wait(rtwdev, rfk_name, ms); + + if (record) + record->states[rfk_type] = wait->state; + + return ret; +} + +#define rtw89_phy_rfk_report_wait(rtwdev, type, phy_idx, chan, ms) \ + __rtw89_phy_rfk_report_wait(rtwdev, RTW89_RFK_REPORT_ ## type, phy_idx, chan, ms) + static void rtw89_phy_c2h_rfk_report_state(struct rtw89_dev *rtwdev, struct sk_buff *c2h, u32 len) { @@ -4122,7 +4162,7 @@ int rtw89_phy_rfk_pre_ntfy_and_wait(struct rtw89_dev *rtwdev, if (RTW89_CHK_FW_FEATURE_GROUP(WITH_RFK_PRE_NOTIFY, &rtwdev->fw)) { rtw89_phy_rfk_report_prep(rtwdev); rtw89_fw_h2c_rf_pre_ntfy(rtwdev, phy_idx); - ret = rtw89_phy_rfk_report_wait(rtwdev, "PRE_NTFY", ms); + ret = rtw89_phy_rfk_report_wait(rtwdev, PRE_NTFY, phy_idx, NULL, ms); if (ret) return ret; } @@ -4152,7 +4192,7 @@ int rtw89_phy_rfk_tssi_and_wait(struct rtw89_dev *rtwdev, if (ret) return ret; - return rtw89_phy_rfk_report_wait(rtwdev, "TSSI", ms); + return rtw89_phy_rfk_report_wait(rtwdev, TSSI, phy_idx, chan, ms); } EXPORT_SYMBOL(rtw89_phy_rfk_tssi_and_wait); @@ -4169,7 +4209,7 @@ int rtw89_phy_rfk_iqk_and_wait(struct rtw89_dev *rtwdev, if (ret) return ret; - return rtw89_phy_rfk_report_wait(rtwdev, "IQK", ms); + return rtw89_phy_rfk_report_wait(rtwdev, IQK, phy_idx, chan, ms); } EXPORT_SYMBOL(rtw89_phy_rfk_iqk_and_wait); @@ -4186,7 +4226,7 @@ int rtw89_phy_rfk_dpk_and_wait(struct rtw89_dev *rtwdev, if (ret) return ret; - return rtw89_phy_rfk_report_wait(rtwdev, "DPK", ms); + return rtw89_phy_rfk_report_wait(rtwdev, DPK, phy_idx, chan, ms); } EXPORT_SYMBOL(rtw89_phy_rfk_dpk_and_wait); @@ -4203,7 +4243,7 @@ int rtw89_phy_rfk_txgapk_and_wait(struct rtw89_dev *rtwdev, if (ret) return ret; - return rtw89_phy_rfk_report_wait(rtwdev, "TXGAPK", ms); + return rtw89_phy_rfk_report_wait(rtwdev, TXGAPK, phy_idx, chan, ms); } EXPORT_SYMBOL(rtw89_phy_rfk_txgapk_and_wait); @@ -4220,7 +4260,7 @@ int rtw89_phy_rfk_dack_and_wait(struct rtw89_dev *rtwdev, if (ret) return ret; - return rtw89_phy_rfk_report_wait(rtwdev, "DACK", ms); + return rtw89_phy_rfk_report_wait(rtwdev, DACK, phy_idx, chan, ms); } EXPORT_SYMBOL(rtw89_phy_rfk_dack_and_wait); @@ -4237,7 +4277,7 @@ int rtw89_phy_rfk_rxdck_and_wait(struct rtw89_dev *rtwdev, if (ret) return ret; - return rtw89_phy_rfk_report_wait(rtwdev, "RX_DCK", ms); + return rtw89_phy_rfk_report_wait(rtwdev, RX_DCK, phy_idx, chan, ms); } EXPORT_SYMBOL(rtw89_phy_rfk_rxdck_and_wait); @@ -4254,7 +4294,7 @@ int rtw89_phy_rfk_txiqk_and_wait(struct rtw89_dev *rtwdev, if (ret) return ret; - return rtw89_phy_rfk_report_wait(rtwdev, "TX_IQK", ms); + return rtw89_phy_rfk_report_wait(rtwdev, TX_IQK, phy_idx, chan, ms); } EXPORT_SYMBOL(rtw89_phy_rfk_txiqk_and_wait); @@ -4271,7 +4311,7 @@ int rtw89_phy_rfk_cim3k_and_wait(struct rtw89_dev *rtwdev, if (ret) return ret; - return rtw89_phy_rfk_report_wait(rtwdev, "CIM3k", ms); + return rtw89_phy_rfk_report_wait(rtwdev, CIM3k, phy_idx, chan, ms); } EXPORT_SYMBOL(rtw89_phy_rfk_cim3k_and_wait); diff --git a/drivers/net/wireless/realtek/rtw89/phy.h b/drivers/net/wireless/realtek/rtw89/phy.h index b0c82bac72f0..0963cdfb1594 100644 --- a/drivers/net/wireless/realtek/rtw89/phy.h +++ b/drivers/net/wireless/realtek/rtw89/phy.h @@ -1150,6 +1150,9 @@ void rtw89_phy_rate_pattern_vif(struct rtw89_dev *rtwdev, bool rtw89_phy_c2h_chk_atomic(struct rtw89_dev *rtwdev, u8 class, u8 func); void rtw89_phy_c2h_handle(struct rtw89_dev *rtwdev, struct sk_buff *skb, u32 len, u8 class, u8 func); + +extern const char * const rtw89_rfk_report_names[]; + int rtw89_phy_rfk_pre_ntfy_and_wait(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx, unsigned int ms); diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h index 4d2117de798b..3bf2865b81ab 100644 --- a/drivers/net/wireless/realtek/rtw89/reg.h +++ b/drivers/net/wireless/realtek/rtw89/reg.h @@ -8763,6 +8763,7 @@ #define RR_VCO 0xb2 #define RR_VCO_VAL GENMASK(18, 14) #define RR_VCO_SEL GENMASK(9, 8) +#define RR_VCO_STS GENMASK(7, 0) #define RR_VCI 0xb3 #define RR_VCI_ON BIT(7) #define RR_LPF 0xb7 From 73aecc221e7df482b2dcf1a643e840ffce1b83c3 Mon Sep 17 00:00:00 2001 From: GuoHan Zhao Date: Wed, 15 Jul 2026 14:08:13 +0800 Subject: [PATCH 0361/1433] wifi: rtw89: wow: fix unsupported cipher debug messages Correct two WoWLAN debug messages to say "unsupported cipher". Signed-off-by: GuoHan Zhao Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260715060813.476245-1-zhaoguohan@kylinos.cn --- drivers/net/wireless/realtek/rtw89/wow.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/wow.c b/drivers/net/wireless/realtek/rtw89/wow.c index a7539f91264d..d6bf5cd8be3b 100644 --- a/drivers/net/wireless/realtek/rtw89/wow.c +++ b/drivers/net/wireless/realtek/rtw89/wow.c @@ -358,7 +358,7 @@ static void rtw89_wow_get_key_info_iter(struct ieee80211_hw *hw, key_info->gtk_keyidx = key->keyidx; break; default: - rtw89_debug(rtwdev, RTW89_DBG_WOW, "unsupport cipher %x\n", + rtw89_debug(rtwdev, RTW89_DBG_WOW, "unsupported cipher %x\n", key->cipher); goto err; } @@ -443,7 +443,7 @@ static void rtw89_wow_set_key_info_iter(struct ieee80211_hw *hw, case WLAN_CIPHER_SUITE_WEP104: break; default: - rtw89_debug(rtwdev, RTW89_DBG_WOW, "unsupport cipher %x\n", + rtw89_debug(rtwdev, RTW89_DBG_WOW, "unsupported cipher %x\n", key->cipher); goto err; } From 730dbda6dc70d29180eb2a7e9fa36823838bb042 Mon Sep 17 00:00:00 2001 From: Chia-Yuan Li Date: Tue, 14 Jul 2026 15:48:11 +0800 Subject: [PATCH 0362/1433] wifi: rtw89: fw: use MAC source for IO offload delay command The udelay/mdelay helpers set the command source to RTW89_FW_CMD_OFLD_SRC_OTHER (4), which does not fit the two-bit field RTW89_H2C_CMD_OFLD_W0_SRC (GENMASK(1, 0)). The le32_encode_bits() masks it down to 0 (RTW89_FW_CMD_OFLD_SRC_BB), and compiler throws __field_overflow() error. Fortunately it still works because firmware ignores the source field for a delay command. Use RTW89_FW_CMD_OFLD_SRC_MAC as the vendor driver does, and drop the unused RTW89_FW_CMD_OFLD_SRC_OTHER enumerator. Reported-by: Bitterblue Smith Closes: https://github.com/morrownr/rtw89/issues/111 Fixes: ae3d327515f2 ("wifi: rtw89: add IO offload support via firmware") Signed-off-by: Chia-Yuan Li Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260714074811.30124-1-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/fw.c | 4 ++-- drivers/net/wireless/realtek/rtw89/fw.h | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 4df2ba5bfa44..0db77120298f 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -12383,7 +12383,7 @@ static void rtw89_fw_cmd_ofld_write_rf(struct rtw89_dev *rtwdev, static void rtw89_fw_cmd_ofld_udelay(struct rtw89_dev *rtwdev, u32 us) { struct rtw89_fw_cmd_ofld_arg cmd = { - .src = RTW89_FW_CMD_OFLD_SRC_OTHER, + .src = RTW89_FW_CMD_OFLD_SRC_MAC, .type = RTW89_FW_CMD_OFLD_DELAY, .value = us, }; @@ -12397,7 +12397,7 @@ static void rtw89_fw_cmd_ofld_udelay(struct rtw89_dev *rtwdev, u32 us) static void rtw89_fw_cmd_ofld_mdelay(struct rtw89_dev *rtwdev, u32 ms) { struct rtw89_fw_cmd_ofld_arg cmd = { - .src = RTW89_FW_CMD_OFLD_SRC_OTHER, + .src = RTW89_FW_CMD_OFLD_SRC_MAC, .type = RTW89_FW_CMD_OFLD_DELAY, .value = ms * 1000, }; diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index 166c4bc9c1d0..a1aab8293c14 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -3114,7 +3114,6 @@ enum rtw89_fw_cmd_ofld_arg_src { RTW89_FW_CMD_OFLD_SRC_RF, RTW89_FW_CMD_OFLD_SRC_MAC, RTW89_FW_CMD_OFLD_SRC_RF_DDIE, - RTW89_FW_CMD_OFLD_SRC_OTHER, }; enum rtw89_fw_cmd_ofld_arg_type { From a620ff84d42cf46cbfb708bacd40ad5e36d02de8 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 10 Jul 2026 04:38:53 -0700 Subject: [PATCH 0363/1433] netconsole: clean up released targets dropped before the cleanup worker drop_netconsole_target() might eventually tear down a target that netconsole_netdev_event() had moved to target_cleanup_list but that netconsole_process_cleanups_core() had not processed yet. Always cleanup devices that eventually have a device attached to the target, independent of the state. Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260710-netconsole_move_more-v3-1-6f63f76b28bc@debian.org Reviewed-by: Simon Horman Signed-off-by: Paolo Abeni --- drivers/net/netconsole.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index c1812a98365b..a939daa07cf9 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -1481,15 +1481,15 @@ static void drop_netconsole_target(struct config_group *group, mutex_lock(&target_cleanup_list_lock); spin_lock_irqsave(&target_list_lock, flags); - /* A STATE_DEACTIVATED target may have been moved to - * target_cleanup_list by netconsole_netdev_event() but not yet - * processed by netconsole_process_cleanups_core(). Unlinking it below - * hides it from the cleanup worker, so this path has to clean it up - * itself. Record that the target still owns a netpoll before the - * state is downgraded. + /* A target moved to target_cleanup_list by netconsole_netdev_event() + * but not yet processed still owns a netpoll; unlinking it below hides + * it from the cleanup worker, so this path must tear it down itself. + * This covers NETDEV_UNREGISTER (STATE_DEACTIVATED) and + * NETDEV_RELEASE / NETDEV_JOIN (STATE_DISABLED); key off nt->np.dev, + * which stays set until the netpoll is cleaned up. */ needs_cleanup = nt->state == STATE_ENABLED || - nt->state == STATE_DEACTIVATED; + nt->state == STATE_DEACTIVATED || nt->np.dev; /* Disable deactivated target to prevent races between resume attempt * and target removal. */ From ede59d06c28f62135857b93663860029429f6907 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 10 Jul 2026 04:38:54 -0700 Subject: [PATCH 0364/1433] netpoll: export refill_skbs(), refill_skbs_work_handler(), skb_pool_flush() These three helpers manage the per-netpoll fallback skb pool. They are file-static today because all of their callers live in net/core/netpoll.c. Subsequent patches relocate the pool's owner from struct netpoll to the only consumer that actually uses it (netconsole), and that work needs netconsole to drive the helpers directly while the function bodies still live here. Drop static, add prototypes in , and EXPORT_SYMBOL_GPL() each. No behaviour change. The exports are transitional. Each helper is moved into drivers/net/netconsole.c later in this series, and at that point its EXPORT_SYMBOL_GPL() and prototype are dropped. By the end of the series no symbol introduced here remains exported. The goal of this patch is to make the subsequente patches easy to review. Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260710-netconsole_move_more-v3-2-6f63f76b28bc@debian.org Reviewed-by: Simon Horman Signed-off-by: Paolo Abeni --- include/linux/netpoll.h | 3 +++ net/core/netpoll.c | 9 ++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index 88f7daa8560e..a7b96e179220 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -90,6 +90,9 @@ void netpoll_cleanup(struct netpoll *np); void do_netpoll_cleanup(struct netpoll *np); netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb); void netpoll_zap_completion_queue(void); +void refill_skbs(struct netpoll *np); +void refill_skbs_work_handler(struct work_struct *work); +void skb_pool_flush(struct netpoll *np); #ifdef CONFIG_NETPOLL static inline void *netpoll_poll_lock(struct napi_struct *napi) diff --git a/net/core/netpoll.c b/net/core/netpoll.c index aed415d3cd74..e062d88d10a3 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -213,7 +213,7 @@ void netpoll_poll_enable(struct net_device *dev) up(&ni->dev_lock); } -static void refill_skbs(struct netpoll *np) +void refill_skbs(struct netpoll *np) { struct sk_buff_head *skb_pool; struct sk_buff *skb; @@ -228,6 +228,7 @@ static void refill_skbs(struct netpoll *np) skb_queue_tail(skb_pool, skb); } } +EXPORT_SYMBOL_GPL(refill_skbs); void netpoll_zap_completion_queue(void) { @@ -351,7 +352,7 @@ netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb) } EXPORT_SYMBOL(netpoll_send_skb); -static void skb_pool_flush(struct netpoll *np) +void skb_pool_flush(struct netpoll *np) { struct sk_buff_head *skb_pool; @@ -359,14 +360,16 @@ static void skb_pool_flush(struct netpoll *np) skb_pool = &np->skb_pool; skb_queue_purge_reason(skb_pool, SKB_CONSUMED); } +EXPORT_SYMBOL_GPL(skb_pool_flush); -static void refill_skbs_work_handler(struct work_struct *work) +void refill_skbs_work_handler(struct work_struct *work) { struct netpoll *np = container_of(work, struct netpoll, refill_wq); refill_skbs(np); } +EXPORT_SYMBOL_GPL(refill_skbs_work_handler); int __netpoll_setup(struct netpoll *np, struct net_device *ndev) { From 1fee9a9c5904760ffefea4aa360e87f98938b71f Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 10 Jul 2026 04:38:55 -0700 Subject: [PATCH 0365/1433] netconsole: take over skb pool lifecycle from netpoll The fallback skb pool fronted by find_skb() is netconsole's only client: every other netpoll goes through __netpoll_setup() / netpoll_send_skb() without ever touching np->skb_pool. Today __netpoll_setup() and __netpoll_cleanup() create and destroy the pool for everyone, paying ~48 KB of pre-allocated skbs per netpoll instance that only netconsole uses, what a waste! Move the responsibility to netconsole. __netpoll_setup() did this under the RTNL, but netconsole enables targets from enabled_store() / alloc_param_target() without it, while the teardown path flushes the pool (cancel_work_sync() + skb_queue_purge()) under the RTNL from netconsole_process_cleanups_core(). Initialising the queue head and the refill work on every enable would therefore race that flush. They only need initialising once: after a flush the queue head is left valid and empty and cancel_work_sync() leaves the work re-armable. Set them up in alloc_and_init(), while the target is not yet reachable, and let the enable paths only refill the pool via refill_skbs(), which serialises with the flush through skb_pool.lock. See discussions in [1] Link: https://lore.kernel.org/all/alDMvD5S7TZnoD_V@gmail.com/ [1] Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260710-netconsole_move_more-v3-3-6f63f76b28bc@debian.org Reviewed-by: Simon Horman Signed-off-by: Paolo Abeni --- drivers/net/netconsole.c | 53 ++++++++++++++++++++++++++++++++++++++-- net/core/netpoll.c | 12 +-------- 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index a939daa07cf9..1f75c4bbea8b 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -292,11 +292,33 @@ static void netcons_release_dev(struct netconsole_target *nt) memset(&nt->np.dev_name, 0, IFNAMSIZ); } +/* Seed the per-target skb pool that find_skb() falls back to. The queue + * head and refill work are set up once in alloc_and_init(); this only + * (re)fills the pool. Pair with netconsole_skb_pool_flush(). + */ +static void netconsole_skb_pool_init(struct netconsole_target *nt) +{ + refill_skbs(&nt->np); +} + +static void netconsole_skb_pool_flush(struct netconsole_target *nt) +{ + skb_pool_flush(&nt->np); +} + /* Attempts to resume logging to a deactivated target. */ static void resume_target(struct netconsole_target *nt) { + /* Initialise the skb pool before netpoll_setup() makes nt->np.dev + * visible to target_list walkers (e.g. netconsole_netdev_event), + * which otherwise may move the target to the cleanup list and + * call netconsole_skb_pool_flush() on uninitialised state. + */ + netconsole_skb_pool_init(nt); + if (netpoll_setup(&nt->np)) { /* netpoll fails setup once, do not try again. */ + netconsole_skb_pool_flush(nt); nt->state = STATE_DISABLED; return; } @@ -358,6 +380,7 @@ static void process_resume_target(struct work_struct *work) rtnl_lock(); if (nt->state == STATE_ENABLED && nt->np.dev && nt->np.dev->reg_state != NETREG_REGISTERED) { + netconsole_skb_pool_flush(nt); netcons_release_dev(nt); nt->state = STATE_DISABLED; } @@ -398,6 +421,9 @@ static struct netconsole_target *alloc_and_init(void) eth_broadcast_addr(nt->np.remote_mac); nt->state = STATE_DISABLED; INIT_WORK(&nt->resume_wq, process_resume_target); + /* Set up the skb pool primitives once; enabling only refills it. */ + skb_queue_head_init(&nt->np.skb_pool); + INIT_WORK(&nt->np.refill_wq, refill_skbs_work_handler); return nt; } @@ -417,6 +443,7 @@ static void netconsole_process_cleanups_core(void) list_for_each_entry_safe(nt, tmp, &target_cleanup_list, list) { /* all entries in the cleanup_list needs to be disabled */ WARN_ON_ONCE(nt->state == STATE_ENABLED); + netconsole_skb_pool_flush(nt); netcons_release_dev(nt); /* moved the cleaned target to target_list. Need to hold both * locks @@ -758,9 +785,19 @@ static ssize_t enabled_store(struct config_item *item, */ netconsole_print_banner(&nt->np); + /* Initialise the skb pool before netpoll_setup() so the pool + * is valid as soon as nt->np.dev becomes visible to + * target_list walkers (netconsole_netdev_event), which would + * otherwise call netconsole_skb_pool_flush() on uninitialised + * state. + */ + netconsole_skb_pool_init(nt); + ret = netpoll_setup(&nt->np); - if (ret) + if (ret) { + netconsole_skb_pool_flush(nt); goto out_unlock; + } nt->state = STATE_ENABLED; pr_info("network logging started\n"); @@ -1514,8 +1551,10 @@ static void drop_netconsole_target(struct config_group *group, * netpoll_cleanup() is idempotent (it skips when np->dev is NULL), so * it is safe even if the cleanup worker already tore the netpoll down. */ - if (needs_cleanup) + if (needs_cleanup) { + netconsole_skb_pool_flush(nt); netpoll_cleanup(&nt->np); + } config_item_put(&nt->group.cg_item); } @@ -2330,10 +2369,18 @@ static struct netconsole_target *alloc_param_target(char *target_config, if (err) goto fail; + /* Initialise the skb pool before netpoll_setup() so the pool is + * valid as soon as nt->np.dev becomes visible. The target is not + * yet on target_list, so a netdev event cannot reach it here, but + * mirror the configfs path for symmetry. + */ + netconsole_skb_pool_init(nt); + err = netpoll_setup(&nt->np); if (err) { pr_err("Not enabling netconsole for %s%d. Netpoll setup failed\n", NETCONSOLE_PARAM_TARGET_PREFIX, cmdline_count); + netconsole_skb_pool_flush(nt); if (!IS_ENABLED(CONFIG_NETCONSOLE_DYNAMIC)) /* only fail if dynamic reconfiguration is set, * otherwise, keep the target in the list, but disabled. @@ -2355,6 +2402,8 @@ static struct netconsole_target *alloc_param_target(char *target_config, static void free_param_target(struct netconsole_target *nt) { cancel_work_sync(&nt->resume_wq); + if (nt->state == STATE_ENABLED) + netconsole_skb_pool_flush(nt); netpoll_cleanup(&nt->np); #ifdef CONFIG_NETCONSOLE_DYNAMIC kfree(nt->userdata); diff --git a/net/core/netpoll.c b/net/core/netpoll.c index e062d88d10a3..58f30a4d5eb0 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -377,9 +377,6 @@ int __netpoll_setup(struct netpoll *np, struct net_device *ndev) const struct net_device_ops *ops; int err; - skb_queue_head_init(&np->skb_pool); - INIT_WORK(&np->refill_wq, refill_skbs_work_handler); - if (ndev->priv_flags & IFF_DISABLE_NETPOLL) { np_err(np, "%s doesn't support polling, aborting\n", ndev->name); @@ -414,9 +411,6 @@ int __netpoll_setup(struct netpoll *np, struct net_device *ndev) np->dev = ndev; strscpy(np->dev_name, ndev->name, IFNAMSIZ); - /* fill up the skb queue */ - refill_skbs(np); - /* last thing to do is link it to the net device structure */ rcu_assign_pointer(ndev->npinfo, npinfo); @@ -606,7 +600,7 @@ int netpoll_setup(struct netpoll *np) err = __netpoll_setup(np, ndev); if (err) - goto flush; + goto put; rtnl_unlock(); /* Make sure all NAPI polls which started before dev->npinfo @@ -617,8 +611,6 @@ int netpoll_setup(struct netpoll *np) return 0; -flush: - skb_pool_flush(np); put: DEBUG_NET_WARN_ON_ONCE(np->dev); if (ip_overwritten) @@ -662,8 +654,6 @@ static void __netpoll_cleanup(struct netpoll *np) disable_delayed_work_sync(&npinfo->tx_work); call_rcu(&npinfo->rcu, rcu_cleanup_netpoll_info); } - - skb_pool_flush(np); } void __netpoll_free(struct netpoll *np) From 28511289088c04861af80f600561a6ee1035f1c2 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 10 Jul 2026 04:38:56 -0700 Subject: [PATCH 0366/1433] netconsole: move refill_skbs_work_handler() from netpoll The work handler is wired via INIT_WORK() in netconsole_skb_pool_init() and has no other callers since the previous patch took the skb pool lifecycle out of __netpoll_setup(). Move the function body into drivers/net/netconsole.c as a file-static helper, drop EXPORT_SYMBOL_GPL() and remove the prototype from . Pure code motion: the body is unchanged and still calls the exported refill_skbs() in net/core/netpoll.c. Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260710-netconsole_move_more-v3-4-6f63f76b28bc@debian.org Reviewed-by: Simon Horman Signed-off-by: Paolo Abeni --- drivers/net/netconsole.c | 8 ++++++++ include/linux/netpoll.h | 1 - net/core/netpoll.c | 9 --------- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 1f75c4bbea8b..96d9a47312cd 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -292,6 +292,14 @@ static void netcons_release_dev(struct netconsole_target *nt) memset(&nt->np.dev_name, 0, IFNAMSIZ); } +static void refill_skbs_work_handler(struct work_struct *work) +{ + struct netpoll *np = + container_of(work, struct netpoll, refill_wq); + + refill_skbs(np); +} + /* Seed the per-target skb pool that find_skb() falls back to. The queue * head and refill work are set up once in alloc_and_init(); this only * (re)fills the pool. Pair with netconsole_skb_pool_flush(). diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index a7b96e179220..51e5863d8e67 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -91,7 +91,6 @@ void do_netpoll_cleanup(struct netpoll *np); netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb); void netpoll_zap_completion_queue(void); void refill_skbs(struct netpoll *np); -void refill_skbs_work_handler(struct work_struct *work); void skb_pool_flush(struct netpoll *np); #ifdef CONFIG_NETPOLL diff --git a/net/core/netpoll.c b/net/core/netpoll.c index 58f30a4d5eb0..93a16faf808c 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -362,15 +362,6 @@ void skb_pool_flush(struct netpoll *np) } EXPORT_SYMBOL_GPL(skb_pool_flush); -void refill_skbs_work_handler(struct work_struct *work) -{ - struct netpoll *np = - container_of(work, struct netpoll, refill_wq); - - refill_skbs(np); -} -EXPORT_SYMBOL_GPL(refill_skbs_work_handler); - int __netpoll_setup(struct netpoll *np, struct net_device *ndev) { struct netpoll_info *npinfo; From 2fbebfbe2953bc0ba1656b25d03338fde1066ad5 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 10 Jul 2026 04:38:57 -0700 Subject: [PATCH 0367/1433] netconsole: move refill_skbs() and skb-pool sizing macros from netpoll refill_skbs() is now only called from netconsole (directly via netconsole_skb_pool_init() and indirectly via the just-moved refill_skbs_work_handler()), and the MAX_UDP_CHUNK / MAX_SKBS / MAX_SKB_SIZE macros are private to it. Move them all into drivers/net/netconsole.c. MAX_UDP_CHUNK and MAX_SKB_SIZE were promoted to by commit 6c537b845c99 ("netconsole: do not dequeue pooled skbs that cannot satisfy len") so find_skb() could detect oversized requests against the same value refill_skbs() used. With both functions now local to netconsole, the shared definition no longer needs to live in the header. Pure code motion: bodies and pool sizing semantics are unchanged. Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260710-netconsole_move_more-v3-5-6f63f76b28bc@debian.org Reviewed-by: Simon Horman Signed-off-by: Paolo Abeni --- drivers/net/netconsole.c | 29 +++++++++++++++++++++++++++++ include/linux/netpoll.h | 15 --------------- net/core/netpoll.c | 23 ----------------------- 3 files changed, 29 insertions(+), 38 deletions(-) diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 96d9a47312cd..efeada762536 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -61,6 +61,19 @@ MODULE_IMPORT_NS("NETDEV_INTERNAL"); #define MAX_USERDATA_ITEMS 256 #define MAX_PRINT_CHUNK 1000 +/* + * Sizing for the per-target fallback skb pool consulted by find_skb() + * when its GFP_ATOMIC allocation fails so messages still get out under + * memory pressure. + */ +#define MAX_UDP_CHUNK 1460 +#define MAX_SKBS 32 +#define MAX_SKB_SIZE \ + (sizeof(struct ethhdr) + \ + sizeof(struct iphdr) + \ + sizeof(struct udphdr) + \ + MAX_UDP_CHUNK) + static char config[MAX_PARAM_LENGTH]; module_param_string(netconsole, config, MAX_PARAM_LENGTH, 0); MODULE_PARM_DESC(netconsole, " netconsole=[src-port]@[src-ip]/[dev],[tgt-port]@/[tgt-macaddr]"); @@ -292,6 +305,22 @@ static void netcons_release_dev(struct netconsole_target *nt) memset(&nt->np.dev_name, 0, IFNAMSIZ); } +static void refill_skbs(struct netpoll *np) +{ + struct sk_buff_head *skb_pool; + struct sk_buff *skb; + + skb_pool = &np->skb_pool; + + while (READ_ONCE(skb_pool->qlen) < MAX_SKBS) { + skb = alloc_skb(MAX_SKB_SIZE, GFP_ATOMIC | __GFP_NOWARN); + if (!skb) + break; + + skb_queue_tail(skb_pool, skb); + } +} + static void refill_skbs_work_handler(struct work_struct *work) { struct netpoll *np = diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index 51e5863d8e67..7e2fbce863e9 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -21,20 +21,6 @@ union inet_addr { struct in6_addr in6; }; -/* - * Maximum payload netpoll's preallocated skb pool can carry. Keep this in - * sync with the buffer size used by refill_skbs() in net/core/netpoll.c; - * callers (e.g. netconsole) use it to detect requests the pool can never - * satisfy and avoid dequeuing a pooled skb that would later trip - * skb_over_panic() in skb_put(). - */ -#define MAX_UDP_CHUNK 1460 -#define MAX_SKB_SIZE \ - (sizeof(struct ethhdr) + \ - sizeof(struct iphdr) + \ - sizeof(struct udphdr) + \ - MAX_UDP_CHUNK) - struct netpoll { struct net_device *dev; netdevice_tracker dev_tracker; @@ -90,7 +76,6 @@ void netpoll_cleanup(struct netpoll *np); void do_netpoll_cleanup(struct netpoll *np); netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb); void netpoll_zap_completion_queue(void); -void refill_skbs(struct netpoll *np); void skb_pool_flush(struct netpoll *np); #ifdef CONFIG_NETPOLL diff --git a/net/core/netpoll.c b/net/core/netpoll.c index 93a16faf808c..9ca695f64210 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -36,12 +36,6 @@ #include #include -/* - * We maintain a small pool of fully-sized skbs, to make sure the - * message gets out even in extreme OOM situations. - */ - -#define MAX_SKBS 32 #define USEC_PER_POLL 50 static unsigned int carrier_timeout = 4; @@ -213,23 +207,6 @@ void netpoll_poll_enable(struct net_device *dev) up(&ni->dev_lock); } -void refill_skbs(struct netpoll *np) -{ - struct sk_buff_head *skb_pool; - struct sk_buff *skb; - - skb_pool = &np->skb_pool; - - while (READ_ONCE(skb_pool->qlen) < MAX_SKBS) { - skb = alloc_skb(MAX_SKB_SIZE, GFP_ATOMIC | __GFP_NOWARN); - if (!skb) - break; - - skb_queue_tail(skb_pool, skb); - } -} -EXPORT_SYMBOL_GPL(refill_skbs); - void netpoll_zap_completion_queue(void) { unsigned long flags; From 3b247a595663744745dd0b602c6c6825c24eb354 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 10 Jul 2026 04:39:00 -0700 Subject: [PATCH 0368/1433] netconsole: move local_port / remote_port from struct netpoll to netconsole_target The source and destination UDP ports live in struct netpoll but are netconsole configuration. No other netpoll user (bonding, team, vlan, bridge, macvlan, dsa) touches np->local_port or np->remote_port; they only use the netpoll TX/forwarding path. Only netconsole's UDP framing and its configfs/cmdline interface read these fields. Move both into struct netconsole_target and convert the three helpers that read them - push_udp(), netconsole_print_banner() and netconsole_parser_cmdline() - to take the netconsole_target. The configfs show/store handlers already have the target in hand. No functional change; the local_port / remote_port sysfs attributes are unchanged. Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260710-netconsole_move_more-v3-8-6f63f76b28bc@debian.org Reviewed-by: Simon Horman Signed-off-by: Paolo Abeni --- drivers/net/netconsole.c | 47 ++++++++++++++++++++++------------------ include/linux/netpoll.h | 1 - 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index cf591ae66736..d0739b45f66e 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -175,12 +175,12 @@ enum target_state { * @np: The netpoll structure for this target. * Contains the other userspace visible parameters: * dev_name (read-write) - * local_port (read-write) - * remote_port (read-write) * local_ip (read-write) * remote_ip (read-write) * local_mac (read-only) * remote_mac (read-write) + * @local_port: Source UDP port of the target (read-write). + * @remote_port: Destination UDP port of the target (read-write). * @buf: The buffer used to send the full msg to the network stack * @resume_wq: Workqueue to resume deactivated target * @skb_pool: Per-target fallback skb pool consulted by find_skb() when @@ -208,6 +208,7 @@ struct netconsole_target { bool extended; bool release; struct netpoll np; + u16 local_port, remote_port; /* protected by target_list_lock; +1 gives scnprintf() room for its * NUL terminator so a full MAX_PRINT_CHUNK payload is not truncated */ @@ -459,8 +460,8 @@ static struct netconsole_target *alloc_and_init(void) nt->np.name = "netconsole"; strscpy(nt->np.dev_name, "eth0", IFNAMSIZ); - nt->np.local_port = 6665; - nt->np.remote_port = 6666; + nt->local_port = 6665; + nt->remote_port = 6666; eth_broadcast_addr(nt->np.remote_mac); nt->state = STATE_DISABLED; INIT_WORK(&nt->resume_wq, process_resume_target); @@ -499,16 +500,18 @@ static void netconsole_process_cleanups_core(void) mutex_unlock(&target_cleanup_list_lock); } -static void netconsole_print_banner(struct netpoll *np) +static void netconsole_print_banner(struct netconsole_target *nt) { - np_info(np, "local port %d\n", np->local_port); + struct netpoll *np = &nt->np; + + np_info(np, "local port %d\n", nt->local_port); if (np->ipv6) np_info(np, "local IPv6 address %pI6c\n", &np->local_ip.in6); else np_info(np, "local IPv4 address %pI4\n", &np->local_ip.ip); np_info(np, "interface name '%s'\n", np->dev_name); np_info(np, "local ethernet address '%pM'\n", np->dev_mac); - np_info(np, "remote port %d\n", np->remote_port); + np_info(np, "remote port %d\n", nt->remote_port); if (np->ipv6) np_info(np, "remote IPv6 address %pI6c\n", &np->remote_ip.in6); else @@ -631,12 +634,12 @@ static ssize_t dev_name_show(struct config_item *item, char *buf) static ssize_t local_port_show(struct config_item *item, char *buf) { - return sysfs_emit(buf, "%d\n", to_target(item)->np.local_port); + return sysfs_emit(buf, "%d\n", to_target(item)->local_port); } static ssize_t remote_port_show(struct config_item *item, char *buf) { - return sysfs_emit(buf, "%d\n", to_target(item)->np.remote_port); + return sysfs_emit(buf, "%d\n", to_target(item)->remote_port); } static ssize_t local_ip_show(struct config_item *item, char *buf) @@ -826,7 +829,7 @@ static ssize_t enabled_store(struct config_item *item, * Skip netconsole_parser_cmdline() -- all the attributes are * already configured via configfs. Just print them out. */ - netconsole_print_banner(&nt->np); + netconsole_print_banner(nt); /* Initialise the skb pool before netpoll_setup() so the pool * is valid as soon as nt->np.dev becomes visible to @@ -965,7 +968,7 @@ static ssize_t local_port_store(struct config_item *item, const char *buf, goto out_unlock; } - ret = kstrtou16(buf, 10, &nt->np.local_port); + ret = kstrtou16(buf, 10, &nt->local_port); if (ret < 0) goto out_unlock; ret = count; @@ -987,7 +990,7 @@ static ssize_t remote_port_store(struct config_item *item, goto out_unlock; } - ret = kstrtou16(buf, 10, &nt->np.remote_port); + ret = kstrtou16(buf, 10, &nt->remote_port); if (ret < 0) goto out_unlock; ret = count; @@ -1863,8 +1866,9 @@ static void netpoll_udp_checksum(struct netpoll *np, struct sk_buff *skb, udph->check = CSUM_MANGLED_0; } -static void push_udp(struct netpoll *np, struct sk_buff *skb, int len) +static void push_udp(struct netconsole_target *nt, struct sk_buff *skb, int len) { + struct netpoll *np = &nt->np; struct udphdr *udph; int udp_len; @@ -1874,8 +1878,8 @@ static void push_udp(struct netpoll *np, struct sk_buff *skb, int len) skb_reset_transport_header(skb); udph = udp_hdr(skb); - udph->source = htons(np->local_port); - udph->dest = htons(np->remote_port); + udph->source = htons(nt->local_port); + udph->dest = htons(nt->remote_port); udph->len = htons(udp_len); netpoll_udp_checksum(np, skb, len); @@ -1971,7 +1975,7 @@ static int netpoll_send_udp(struct netconsole_target *nt, const char *msg, skb_copy_to_linear_data(skb, msg, len); skb_put(skb, len); - push_udp(np, skb, len); + push_udp(nt, skb, len); if (np->ipv6) push_ipv6(np, skb, len); else @@ -2289,8 +2293,9 @@ __releases(&target_list_lock) spin_unlock_irqrestore(&target_list_lock, flags); } -static int netconsole_parser_cmdline(struct netpoll *np, char *opt) +static int netconsole_parser_cmdline(struct netconsole_target *nt, char *opt) { + struct netpoll *np = &nt->np; bool ipversion_set = false; char *cur = opt; char *delim; @@ -2301,7 +2306,7 @@ static int netconsole_parser_cmdline(struct netpoll *np, char *opt) if (!delim) goto parse_failed; *delim = 0; - if (kstrtou16(cur, 10, &np->local_port)) + if (kstrtou16(cur, 10, &nt->local_port)) goto parse_failed; cur = delim; } @@ -2348,7 +2353,7 @@ static int netconsole_parser_cmdline(struct netpoll *np, char *opt) *delim = 0; if (*cur == ' ' || *cur == '\t') np_info(np, "warning: whitespace is not allowed\n"); - if (kstrtou16(cur, 10, &np->remote_port)) + if (kstrtou16(cur, 10, &nt->remote_port)) goto parse_failed; cur = delim; } @@ -2374,7 +2379,7 @@ static int netconsole_parser_cmdline(struct netpoll *np, char *opt) goto parse_failed; } - netconsole_print_banner(np); + netconsole_print_banner(nt); return 0; @@ -2412,7 +2417,7 @@ static struct netconsole_target *alloc_param_target(char *target_config, } /* Parse parameters and setup netpoll */ - err = netconsole_parser_cmdline(&nt->np, target_config); + err = netconsole_parser_cmdline(nt, target_config); if (err) goto fail; diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index f377fdf7839c..5ca79fa7d943 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -35,7 +35,6 @@ struct netpoll { union inet_addr local_ip, remote_ip; bool ipv6; - u16 local_port, remote_port; u8 remote_mac[ETH_ALEN]; }; From 67fb2038e1f65309a91f61169e75ee973f936f1c Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 10 Jul 2026 04:38:58 -0700 Subject: [PATCH 0369/1433] netconsole: move skb_pool_flush() from netpoll skb_pool_flush() has no callers left in net/core/netpoll.c after netconsole took over the pool lifecycle. Inline its body into netconsole_skb_pool_flush() (the only caller) and drop the function and its export from netpoll. The prototype goes from . Pure code motion: cancel_work_sync() + skb_queue_purge_reason() semantics are unchanged. Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260710-netconsole_move_more-v3-6-6f63f76b28bc@debian.org Reviewed-by: Simon Horman Signed-off-by: Paolo Abeni --- drivers/net/netconsole.c | 5 ++++- include/linux/netpoll.h | 1 - net/core/netpoll.c | 10 ---------- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index efeada762536..742723c5a7d4 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -340,7 +340,10 @@ static void netconsole_skb_pool_init(struct netconsole_target *nt) static void netconsole_skb_pool_flush(struct netconsole_target *nt) { - skb_pool_flush(&nt->np); + struct netpoll *np = &nt->np; + + cancel_work_sync(&np->refill_wq); + skb_queue_purge_reason(&np->skb_pool, SKB_CONSUMED); } /* Attempts to resume logging to a deactivated target. */ diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index 7e2fbce863e9..1216b5c237ce 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -76,7 +76,6 @@ void netpoll_cleanup(struct netpoll *np); void do_netpoll_cleanup(struct netpoll *np); netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb); void netpoll_zap_completion_queue(void); -void skb_pool_flush(struct netpoll *np); #ifdef CONFIG_NETPOLL static inline void *netpoll_poll_lock(struct napi_struct *napi) diff --git a/net/core/netpoll.c b/net/core/netpoll.c index 9ca695f64210..f8da1048ea3a 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -329,16 +329,6 @@ netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb) } EXPORT_SYMBOL(netpoll_send_skb); -void skb_pool_flush(struct netpoll *np) -{ - struct sk_buff_head *skb_pool; - - cancel_work_sync(&np->refill_wq); - skb_pool = &np->skb_pool; - skb_queue_purge_reason(skb_pool, SKB_CONSUMED); -} -EXPORT_SYMBOL_GPL(skb_pool_flush); - int __netpoll_setup(struct netpoll *np, struct net_device *ndev) { struct netpoll_info *npinfo; From 49e03ca58334cd46dfd9267ced0ff91dcae2c451 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 10 Jul 2026 04:39:01 -0700 Subject: [PATCH 0370/1433] netconsole: move remote_mac from struct netpoll to netconsole_target The destination ethernet address is netconsole configuration: no other netpoll user (bonding, team, vlan, bridge, macvlan, dsa) references np->remote_mac, only netconsole's ethernet framing and its configfs/cmdline interface do. Move it into struct netconsole_target and convert push_eth() to take the netconsole_target; netconsole_print_banner() and netconsole_parser_cmdline() already take it. The configfs show/store handlers and alloc_and_init() reach the field directly. No functional change; the remote_mac sysfs attribute is unchanged. Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260710-netconsole_move_more-v3-9-6f63f76b28bc@debian.org Reviewed-by: Simon Horman Signed-off-by: Paolo Abeni --- drivers/net/netconsole.c | 20 +++++++++++--------- include/linux/netpoll.h | 1 - 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index d0739b45f66e..49b2243a20b2 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -178,9 +178,9 @@ enum target_state { * local_ip (read-write) * remote_ip (read-write) * local_mac (read-only) - * remote_mac (read-write) * @local_port: Source UDP port of the target (read-write). * @remote_port: Destination UDP port of the target (read-write). + * @remote_mac: Destination ethernet address of the target (read-write). * @buf: The buffer used to send the full msg to the network stack * @resume_wq: Workqueue to resume deactivated target * @skb_pool: Per-target fallback skb pool consulted by find_skb() when @@ -209,6 +209,7 @@ struct netconsole_target { bool release; struct netpoll np; u16 local_port, remote_port; + u8 remote_mac[ETH_ALEN]; /* protected by target_list_lock; +1 gives scnprintf() room for its * NUL terminator so a full MAX_PRINT_CHUNK payload is not truncated */ @@ -462,7 +463,7 @@ static struct netconsole_target *alloc_and_init(void) strscpy(nt->np.dev_name, "eth0", IFNAMSIZ); nt->local_port = 6665; nt->remote_port = 6666; - eth_broadcast_addr(nt->np.remote_mac); + eth_broadcast_addr(nt->remote_mac); nt->state = STATE_DISABLED; INIT_WORK(&nt->resume_wq, process_resume_target); /* Set up the skb pool primitives once; enabling only refills it. */ @@ -516,7 +517,7 @@ static void netconsole_print_banner(struct netconsole_target *nt) np_info(np, "remote IPv6 address %pI6c\n", &np->remote_ip.in6); else np_info(np, "remote IPv4 address %pI4\n", &np->remote_ip.ip); - np_info(np, "remote ethernet address %pM\n", np->remote_mac); + np_info(np, "remote ethernet address %pM\n", nt->remote_mac); } /* Parse the string and populate the `inet_addr` union. Return 0 if IPv4 is @@ -672,7 +673,7 @@ static ssize_t local_mac_show(struct config_item *item, char *buf) static ssize_t remote_mac_show(struct config_item *item, char *buf) { - return sysfs_emit(buf, "%pM\n", to_target(item)->np.remote_mac); + return sysfs_emit(buf, "%pM\n", to_target(item)->remote_mac); } static ssize_t transmit_errors_show(struct config_item *item, char *buf) @@ -1077,7 +1078,7 @@ static ssize_t remote_mac_store(struct config_item *item, const char *buf, goto out_unlock; if (buf[MAC_ADDR_STR_LEN] && buf[MAC_ADDR_STR_LEN] != '\n') goto out_unlock; - memcpy(nt->np.remote_mac, remote_mac, ETH_ALEN); + memcpy(nt->remote_mac, remote_mac, ETH_ALEN); ret = count; out_unlock: @@ -1885,14 +1886,15 @@ static void push_udp(struct netconsole_target *nt, struct sk_buff *skb, int len) netpoll_udp_checksum(np, skb, len); } -static void push_eth(struct netpoll *np, struct sk_buff *skb) +static void push_eth(struct netconsole_target *nt, struct sk_buff *skb) { + struct netpoll *np = &nt->np; struct ethhdr *eth; eth = skb_push(skb, ETH_HLEN); skb_reset_mac_header(skb); ether_addr_copy(eth->h_source, np->dev->dev_addr); - ether_addr_copy(eth->h_dest, np->remote_mac); + ether_addr_copy(eth->h_dest, nt->remote_mac); if (np->ipv6) eth->h_proto = htons(ETH_P_IPV6); else @@ -1980,7 +1982,7 @@ static int netpoll_send_udp(struct netconsole_target *nt, const char *msg, push_ipv6(np, skb, len); else push_ipv4(np, skb, len); - push_eth(np, skb); + push_eth(nt, skb); skb->dev = np->dev; return (int)netpoll_send_skb(np, skb); @@ -2375,7 +2377,7 @@ static int netconsole_parser_cmdline(struct netconsole_target *nt, char *opt) if (*cur != 0) { /* MAC address */ - if (!mac_pton(cur, np->remote_mac)) + if (!mac_pton(cur, nt->remote_mac)) goto parse_failed; } diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index 5ca79fa7d943..79315461a7b1 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -35,7 +35,6 @@ struct netpoll { union inet_addr local_ip, remote_ip; bool ipv6; - u8 remote_mac[ETH_ALEN]; }; #define np_info(np, fmt, ...) \ From ba520084dc3b7f8b0bca534bd272266b764427f2 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 10 Jul 2026 04:38:59 -0700 Subject: [PATCH 0371/1433] netconsole: move skb_pool / refill_wq from struct netpoll to netconsole_target These two fields back the fallback skb pool that find_skb() uses. Every helper that touches them lives in netconsole now (refill_skbs, refill_skbs_work_handler, netconsole_skb_pool_init, netconsole_skb_pool_flush, find_skb, netcons_skb_pop), so the data can move alongside its only consumer. Add skb_pool and refill_wq to struct netconsole_target, drop them from struct netpoll. This will save 48-bytes for every netpoll user instance (except netconsole that will have it in netconsole target struct). Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260710-netconsole_move_more-v3-7-6f63f76b28bc@debian.org Reviewed-by: Simon Horman Signed-off-by: Paolo Abeni --- drivers/net/netconsole.c | 53 +++++++++++++++++++++++----------------- include/linux/netpoll.h | 2 -- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 742723c5a7d4..cf591ae66736 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -183,6 +183,11 @@ enum target_state { * remote_mac (read-write) * @buf: The buffer used to send the full msg to the network stack * @resume_wq: Workqueue to resume deactivated target + * @skb_pool: Per-target fallback skb pool consulted by find_skb() when + * its GFP_ATOMIC allocation fails. Lifetime brackets a + * successful netpoll_setup() / netpoll_cleanup() pair on @np. + * @refill_wq: Work item that asynchronously tops @skb_pool back up to + * MAX_SKBS after find_skb() drains an entry. */ struct netconsole_target { struct list_head list; @@ -208,6 +213,8 @@ struct netconsole_target { */ char buf[MAX_PRINT_CHUNK + 1]; struct work_struct resume_wq; + struct sk_buff_head skb_pool; + struct work_struct refill_wq; }; #ifdef CONFIG_NETCONSOLE_DYNAMIC @@ -305,13 +312,11 @@ static void netcons_release_dev(struct netconsole_target *nt) memset(&nt->np.dev_name, 0, IFNAMSIZ); } -static void refill_skbs(struct netpoll *np) +static void refill_skbs(struct netconsole_target *nt) { - struct sk_buff_head *skb_pool; + struct sk_buff_head *skb_pool = &nt->skb_pool; struct sk_buff *skb; - skb_pool = &np->skb_pool; - while (READ_ONCE(skb_pool->qlen) < MAX_SKBS) { skb = alloc_skb(MAX_SKB_SIZE, GFP_ATOMIC | __GFP_NOWARN); if (!skb) @@ -323,10 +328,10 @@ static void refill_skbs(struct netpoll *np) static void refill_skbs_work_handler(struct work_struct *work) { - struct netpoll *np = - container_of(work, struct netpoll, refill_wq); + struct netconsole_target *nt = + container_of(work, struct netconsole_target, refill_wq); - refill_skbs(np); + refill_skbs(nt); } /* Seed the per-target skb pool that find_skb() falls back to. The queue @@ -335,15 +340,13 @@ static void refill_skbs_work_handler(struct work_struct *work) */ static void netconsole_skb_pool_init(struct netconsole_target *nt) { - refill_skbs(&nt->np); + refill_skbs(nt); } static void netconsole_skb_pool_flush(struct netconsole_target *nt) { - struct netpoll *np = &nt->np; - - cancel_work_sync(&np->refill_wq); - skb_queue_purge_reason(&np->skb_pool, SKB_CONSUMED); + cancel_work_sync(&nt->refill_wq); + skb_queue_purge_reason(&nt->skb_pool, SKB_CONSUMED); } /* Attempts to resume logging to a deactivated target. */ @@ -462,8 +465,8 @@ static struct netconsole_target *alloc_and_init(void) nt->state = STATE_DISABLED; INIT_WORK(&nt->resume_wq, process_resume_target); /* Set up the skb pool primitives once; enabling only refills it. */ - skb_queue_head_init(&nt->np.skb_pool); - INIT_WORK(&nt->np.refill_wq, refill_skbs_work_handler); + skb_queue_head_init(&nt->skb_pool); + INIT_WORK(&nt->refill_wq, refill_skbs_work_handler); return nt; } @@ -1785,7 +1788,7 @@ static struct notifier_block netconsole_netdev_notifier = { * pool locks and is therefore not NMI-safe. Skip the refill when called * from NMI context; the next non-NMI caller will top the pool back up. */ -static struct sk_buff *netcons_skb_pop(struct netpoll *np, int len) +static struct sk_buff *netcons_skb_pop(struct netconsole_target *nt, int len) { struct sk_buff *skb; @@ -1797,19 +1800,21 @@ static struct sk_buff *netcons_skb_pop(struct netpoll *np, int len) if (!in_nmi()) net_warn_ratelimited("netconsole: dropping message, requested skb len %d exceeds pool buffer size %zu on %s\n", len, (size_t)MAX_SKB_SIZE, - np->dev->name); + nt->np.dev->name); return NULL; } - skb = skb_dequeue(&np->skb_pool); + skb = skb_dequeue(&nt->skb_pool); if (!in_nmi()) - schedule_work(&np->refill_wq); + schedule_work(&nt->refill_wq); return skb; } -static struct sk_buff *find_skb(struct netpoll *np, int len, int reserve) +static struct sk_buff *find_skb(struct netconsole_target *nt, int len, + int reserve) { + struct netpoll *np = &nt->np; int count = 0; struct sk_buff *skb; @@ -1818,7 +1823,7 @@ static struct sk_buff *find_skb(struct netpoll *np, int len, int reserve) skb = alloc_skb(len, GFP_ATOMIC | __GFP_NOWARN); if (!skb) - skb = netcons_skb_pop(np, len); + skb = netcons_skb_pop(nt, len); if (!skb) { if (++count < 10) { @@ -1940,8 +1945,10 @@ static void push_ipv6(struct netpoll *np, struct sk_buff *skb, int len) skb->protocol = htons(ETH_P_IPV6); } -static int netpoll_send_udp(struct netpoll *np, const char *msg, int len) +static int netpoll_send_udp(struct netconsole_target *nt, const char *msg, + int len) { + struct netpoll *np = &nt->np; int total_len, ip_len, udp_len; struct sk_buff *skb; @@ -1956,7 +1963,7 @@ static int netpoll_send_udp(struct netpoll *np, const char *msg, int len) total_len = ip_len + LL_RESERVED_SPACE(np->dev); - skb = find_skb(np, total_len + np->dev->needed_tailroom, + skb = find_skb(nt, total_len + np->dev->needed_tailroom, total_len - len); if (!skb) return -ENOMEM; @@ -1987,7 +1994,7 @@ static int netpoll_send_udp(struct netpoll *np, const char *msg, int len) */ static void send_udp(struct netconsole_target *nt, const char *msg, int len) { - int result = netpoll_send_udp(&nt->np, msg, len); + int result = netpoll_send_udp(nt, msg, len); if (IS_ENABLED(CONFIG_NETCONSOLE_DYNAMIC)) { if (result == NET_XMIT_DROP) { diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index 1216b5c237ce..f377fdf7839c 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -37,8 +37,6 @@ struct netpoll { bool ipv6; u16 local_port, remote_port; u8 remote_mac[ETH_ALEN]; - struct sk_buff_head skb_pool; - struct work_struct refill_wq; }; #define np_info(np, fmt, ...) \ From 008f965fb40f88c47f5fb852e1fef5becb0fa3b2 Mon Sep 17 00:00:00 2001 From: Siddaraju DH Date: Fri, 3 Jul 2026 15:35:37 +0530 Subject: [PATCH 0372/1433] ethtool: link 10000baseCR to SFF-8431, Appendix-E SFP+ DA Add comment to clarify the physical media 10000baseCR follows. 10000baseCR does not correspond to any IEEE 802.3 *base-CR PMD. It has no autonegotiation, no link training, and no mandatory FEC. The industry standard for this media type is SFF-8431 Appendix-E Direct Attach cable, also known as 10G_SFI_DA. Link: https://lore.kernel.org/r/SN7PR11MB69003D33489DB1D6B17EF72A9AF52@SN7PR11MB6900.namprd11.prod.outlook.com Signed-off-by: Siddaraju DH Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260703100537.1109838-1-siddaraju.dh@intel.com Signed-off-by: Paolo Abeni --- include/uapi/linux/ethtool.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h index a2091d4e00f3..986d70caec33 100644 --- a/include/uapi/linux/ethtool.h +++ b/include/uapi/linux/ethtool.h @@ -2013,7 +2013,13 @@ enum ethtool_link_mode_bit_indices { ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT = 39, ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT = 40, ETHTOOL_LINK_MODE_1000baseX_Full_BIT = 41, + + /* Despite the "baseCR" in 10000baseCR, this is not an IEEE 802.3 baseCR + * It represents SFF-8431 Appendix-E SFP+ Direct Attach (10G-SFI-DA). + * The name is kept as-is for uAPI backward compatibility. + */ ETHTOOL_LINK_MODE_10000baseCR_Full_BIT = 42, + ETHTOOL_LINK_MODE_10000baseSR_Full_BIT = 43, ETHTOOL_LINK_MODE_10000baseLR_Full_BIT = 44, ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT = 45, From 9df92875d6d741f9bff1ad95eeaf40b34943d2c4 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 3 Jul 2026 13:02:17 +0200 Subject: [PATCH 0373/1433] net: airoha: add preliminary support to configure tx hw QoS queue during flowtable offloading Add the plumbing to program the AIROHA_FOE_QID field in the PPE FOE entry with a per-flow priority value during flowtable offload. This allows the hardware to steer offloaded flows to a specific QoS queue on the egress QDMA block for traffic forwarded between two interfaces via hardware acceleration, bypassing the kernel forwarding path. The priority parameter is currently always zero because netfilter does not yet provide a mechanism to pass the skb priority field to the flowtable offload driver. Once that support is added in the netfilter subsystem, the driver will be able to extract the priority from the flow rule and map it to the appropriate hardware queue. Signed-off-by: Lorenzo Bianconi Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260703-airoha-hw-qos-queue-stub-v1-1-ef253ffdd093@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/airoha/airoha_ppe.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c index e7c78293002a..fdb973fc779c 100644 --- a/drivers/net/ethernet/airoha/airoha_ppe.c +++ b/drivers/net/ethernet/airoha/airoha_ppe.c @@ -331,7 +331,7 @@ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth, struct airoha_foe_entry *hwe, struct net_device *netdev, int type, struct airoha_flow_data *data, - int l4proto) + int l4proto, u8 priority) { u32 qdata = FIELD_PREP(AIROHA_FOE_SHAPER_ID, 0x7f), ports_pad, val; int wlan_etype = -EINVAL, dsa_port = airoha_get_dsa_port(&netdev); @@ -386,7 +386,9 @@ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth, */ channel = dsa_port >= 0 ? dsa_port : port->id; channel = channel % AIROHA_NUM_QOS_CHANNELS; - qdata |= FIELD_PREP(AIROHA_FOE_CHANNEL, channel); + priority = priority % AIROHA_NUM_QOS_QUEUES; + qdata |= FIELD_PREP(AIROHA_FOE_CHANNEL, channel) | + FIELD_PREP(AIROHA_FOE_QID, priority); val |= FIELD_PREP(AIROHA_FOE_IB2_PSE_PORT, pse_port) | AIROHA_FOE_IB2_PSE_QOS; @@ -1079,10 +1081,10 @@ static int airoha_ppe_flow_offload_replace(struct airoha_eth *eth, struct airoha_flow_data data = {}; struct net_device *odev = NULL; struct flow_action_entry *act; + u8 l4proto = 0, priority = 0; struct airoha_foe_entry hwe; int err, i, offload_type; u16 addr_type = 0; - u8 l4proto = 0; if (rhashtable_lookup(ð->flow_table, &f->cookie, airoha_flow_table_params)) @@ -1177,7 +1179,7 @@ static int airoha_ppe_flow_offload_replace(struct airoha_eth *eth, return -EINVAL; err = airoha_ppe_foe_entry_prepare(eth, &hwe, odev, offload_type, - &data, l4proto); + &data, l4proto, priority); if (err) return err; From 922cc43c624330b9cf646d52fc82c820d9f699b3 Mon Sep 17 00:00:00 2001 From: Aditya Garg Date: Fri, 10 Jul 2026 06:22:29 -0700 Subject: [PATCH 0374/1433] net: mana: Add debug knob to skip TX timeout recovery reset Add a per-port debugfs boolean "tx_timeout_skip_reset" that, when enabled, makes mana_tx_timeout() log the TX timeout and return without queueing the per-port detach/attach recovery work. This is a debug-only aid for bringup and qualification: skipping the recovery reset keeps the device and queue state intact so a TX timeout can be correlated with hardware telemetry. The knob defaults to false, so production recovery behaviour is unchanged. Signed-off-by: Aditya Garg Reviewed-by: Haiyang Zhang Reviewed-by: Dipayaan Roy Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260710132229.2851441-1-gargaditya@linux.microsoft.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/microsoft/mana/mana_en.c | 14 ++++++++++++++ include/net/mana/mana.h | 3 +++ 2 files changed, 17 insertions(+) diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index 89e7f59f635d..a8c329bdbacf 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -892,6 +892,17 @@ static void mana_tx_timeout(struct net_device *netdev, unsigned int txqueue) struct mana_context *ac = apc->ac; struct gdma_context *gc = ac->gdma_dev->gdma_context; + /* Debug knob for bringup/qualification: when set, log the timeout and + * skip the reset so the failing state is preserved for telemetry. + * Disabled by default; production behaviour is unchanged. + */ + if (READ_ONCE(apc->tx_timeout_skip_reset)) { + netdev_warn(netdev, + "TX timeout on queue %u: reset skipped (tx_timeout_skip_reset enabled)\n", + txqueue); + return; + } + /* Already in service, hence tx queue reset is not required.*/ if (test_bit(GC_IN_SERVICE, &gc->flags)) return; @@ -3486,6 +3497,9 @@ static int mana_init_port(struct net_device *ndev) &apc->steer_cqe_coalescing); debugfs_create_u32("current_speed", 0400, apc->mana_port_debugfs, &apc->speed); + debugfs_create_bool("tx_timeout_skip_reset", 0600, + apc->mana_port_debugfs, + &apc->tx_timeout_skip_reset); return 0; reset_apc: diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h index 226b61504596..4d041fb8437f 100644 --- a/include/net/mana/mana.h +++ b/include/net/mana/mana.h @@ -530,6 +530,9 @@ struct mana_port_context { struct net_device *ndev; struct work_struct queue_reset_work; + /* Debug knob to log TX timeout but skip recovery reset */ + bool tx_timeout_skip_reset; + u8 mac_addr[ETH_ALEN]; struct mana_eq *eqs; From ce6b4d3216b63f902bb8e9695ee6c10c83415f65 Mon Sep 17 00:00:00 2001 From: Haiyang Zhang Date: Fri, 10 Jul 2026 12:27:29 -0700 Subject: [PATCH 0375/1433] net: mana: Add handler for sriov configure Add callback function for the pci_driver / sriov_configure. It asks the NIC to provide certain number of VFs, or disable VFs if the request is zero. Signed-off-by: Haiyang Zhang Link: https://patch.msgid.link/20260710192735.2921300-1-haiyangz@linux.microsoft.com Signed-off-by: Paolo Abeni --- .../net/ethernet/microsoft/mana/gdma_main.c | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c index aef3b77229c1..a38d4bb74621 100644 --- a/drivers/net/ethernet/microsoft/mana/gdma_main.c +++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c @@ -2456,6 +2456,8 @@ static void mana_gd_remove(struct pci_dev *pdev) { struct gdma_context *gc = pci_get_drvdata(pdev); + pci_disable_sriov(pdev); + mana_rdma_remove(&gc->mana_ib); mana_remove(&gc->mana, false); @@ -2525,6 +2527,27 @@ static void mana_gd_shutdown(struct pci_dev *pdev) pci_disable_device(pdev); } +static int mana_sriov_configure(struct pci_dev *pdev, int numvfs) +{ + int err = 0; + + dev_info(&pdev->dev, "Requested num VFs: %d\n", numvfs); + + if (numvfs > 0) { + err = pci_enable_sriov(pdev, numvfs); + } else { + if (pci_vfs_assigned(pdev)) { + dev_warn(&pdev->dev, + "Cannot disable SR-IOV while VFs are assigned\n"); + return -EPERM; + } + + pci_disable_sriov(pdev); + } + + return err ? err : numvfs; +} + static const struct pci_device_id mana_id_table[] = { { PCI_DEVICE(PCI_VENDOR_ID_MICROSOFT, MANA_PF_DEVICE_ID) }, { PCI_DEVICE(PCI_VENDOR_ID_MICROSOFT, MANA_PF2_DEVICE_ID) }, @@ -2540,6 +2563,7 @@ static struct pci_driver mana_driver = { .suspend = mana_gd_suspend, .resume = mana_gd_resume, .shutdown = mana_gd_shutdown, + .sriov_configure = mana_sriov_configure, }; static int __init mana_driver_init(void) From 285fd588859f42b14f6f455faaa336b4077c3a87 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Wed, 15 Jul 2026 12:13:54 +0200 Subject: [PATCH 0376/1433] net: phy: at803x: Use a helper to check for phy reset existence The at803x family of devices are subjected to an errata that requires hard-reseting the PHY upon link change. That can only work if there's a physical reset line wired to the PHY, which the driver checks by looking if there's a reset GPIO configured for the MDIO device. The reset may however be controlled through a reset controller, which isn't accounted for in the errata handling. Besides that, PHY drivers aren't expected to directly access the mdiodev's resources directly, let's therefore wrap this with a phylib helper, that uses a similar mdio helper to check for reset existence. This was found in preparation for bus-level resource management for better mdio scan support. Signed-off-by: Maxime Chevallier Reviewed-by: Andrew Lunn Reviewed-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260715101355.88536-1-maxime.chevallier@bootlin.com Signed-off-by: Paolo Abeni --- drivers/net/phy/qcom/at803x.c | 2 +- include/linux/mdio.h | 5 +++++ include/linux/phy.h | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/net/phy/qcom/at803x.c b/drivers/net/phy/qcom/at803x.c index ba4dc07752b6..6872dbf77856 100644 --- a/drivers/net/phy/qcom/at803x.c +++ b/drivers/net/phy/qcom/at803x.c @@ -537,7 +537,7 @@ static void at803x_link_change_notify(struct phy_device *phydev) * in the FIFO. In such cases, the FIFO enters an error mode it * cannot recover from by software. */ - if (phydev->state == PHY_NOLINK && phydev->mdio.reset_gpio) { + if (phydev->state == PHY_NOLINK && phy_device_has_reset(phydev)) { struct at803x_context context; at803x_context_save(phydev, &context); diff --git a/include/linux/mdio.h b/include/linux/mdio.h index 300805e66592..a7d9e3ae362a 100644 --- a/include/linux/mdio.h +++ b/include/linux/mdio.h @@ -86,6 +86,11 @@ static inline void *mdiodev_get_drvdata(struct mdio_device *mdio) return dev_get_drvdata(&mdio->dev); } +static inline bool mdiodev_has_reset(struct mdio_device *mdio) +{ + return (mdio->reset_gpio || mdio->reset_ctrl); +} + void mdio_device_free(struct mdio_device *mdiodev); struct mdio_device *mdio_device_create(struct mii_bus *bus, int addr); int mdio_device_register(struct mdio_device *mdiodev); diff --git a/include/linux/phy.h b/include/linux/phy.h index fc680901275b..beff1d6fcc7c 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -2231,6 +2231,11 @@ static inline void phy_device_reset(struct phy_device *phydev, int value) mdio_device_reset(&phydev->mdio, value); } +static inline bool phy_device_has_reset(struct phy_device *phydev) +{ + return mdiodev_has_reset(&phydev->mdio); +} + #define phydev_err(_phydev, format, args...) \ dev_err(&_phydev->mdio.dev, format, ##args) From c5cb9dd220ba9fdcf4ba485ce3d1d36ca32ac00b Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Fri, 17 Jul 2026 17:31:05 +0300 Subject: [PATCH 0377/1433] wifi: iwlwifi: mld: validate wake packet crypto overhead Wake packet parsing only accounted for FCS and missed per-key IV/ICV overhead for protected data frames. Prevent size underflow and bad packet trimming when notifications are malformed or truncated. Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260717172958.e06595623533.Ie09494b7e34e5872b750fd90e325648ee469d0da@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/d3.c | 63 +++++++++++++++++++-- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/d3.c b/drivers/net/wireless/intel/iwlwifi/mld/d3.c index 3b785c53948f..301983428a63 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/d3.c @@ -59,6 +59,30 @@ struct iwl_mld_suspend_key_iter_data { __le32 bigtk_cipher; }; +struct iwl_mld_wake_pkt_iter_data { + bool multicast; + u32 ivlen; + u32 icvlen; +}; + +static void +iwl_mld_wake_pkt_key_iter(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key, void *_data) +{ + struct iwl_mld_wake_pkt_iter_data *data = _data; + bool is_group_key = !sta; + + /* ignore anything that is not a PTK / GTK */ + if (key->keyidx > 3) + return; + if (is_group_key != data->multicast) + return; + + data->ivlen = key->iv_len; + data->icvlen = key->icv_len; +} + struct iwl_mld_mcast_key_data { u8 key[WOWLAN_KEY_MAX_SIZE]; u8 len; @@ -743,11 +767,17 @@ iwl_mld_set_wake_packet(struct iwl_mld *mld, struct cfg80211_wowlan_wakeup *wakeup, struct sk_buff **_pkt) { - int pkt_bufsize = wowlan_status->wake_packet_bufsize; - int expected_pktlen = wowlan_status->wake_packet_length; + u32 pkt_bufsize = wowlan_status->wake_packet_bufsize; + u32 expected_pktlen = wowlan_status->wake_packet_length; const u8 *pktdata = wowlan_status->wake_packet; const struct ieee80211_hdr *hdr = (const void *)pktdata; - int truncated = expected_pktlen - pkt_bufsize; + u32 truncated; + + if (IWL_FW_CHECK(mld, pkt_bufsize < sizeof(*hdr), + "pkt_bufsize is too short: %u\n", pkt_bufsize)) + return; + + truncated = expected_pktlen - pkt_bufsize; if (ieee80211_is_data(hdr->frame_control)) { int hdrlen = ieee80211_hdrlen(hdr->frame_control); @@ -758,9 +788,18 @@ iwl_mld_set_wake_packet(struct iwl_mld *mld, if (!pkt) return; - skb_put_data(pkt, pktdata, hdrlen); - pktdata += hdrlen; - pkt_bufsize -= hdrlen; + if (ieee80211_has_protected(hdr->frame_control)) { + struct iwl_mld_wake_pkt_iter_data iter_data = { + .multicast = + is_multicast_ether_addr(hdr->addr1), + }; + + ieee80211_iter_keys(mld->hw, vif, + iwl_mld_wake_pkt_key_iter, + &iter_data); + ivlen = iter_data.ivlen; + icvlen += iter_data.icvlen; + } /* if truncated, FCS/ICV is (partially) gone */ if (truncated >= icvlen) { @@ -771,6 +810,13 @@ iwl_mld_set_wake_packet(struct iwl_mld *mld, truncated = 0; } + if (IWL_FW_CHECK(mld, pkt_bufsize <= hdrlen + ivlen + icvlen, + "pkt_bufsize is too small %u\n", pkt_bufsize)) + return; + + skb_put_data(pkt, pktdata, hdrlen); + pktdata += hdrlen; + pkt_bufsize -= hdrlen; pkt_bufsize -= ivlen + icvlen; pktdata += ivlen; @@ -792,6 +838,11 @@ iwl_mld_set_wake_packet(struct iwl_mld *mld, fcslen -= truncated; truncated = 0; } + + if (IWL_FW_CHECK(mld, pkt_bufsize <= fcslen, + "pkt_bufsize is too small %u\n", pkt_bufsize)) + return; + pkt_bufsize -= fcslen; wakeup->packet = wowlan_status->wake_packet; wakeup->packet_present_len = pkt_bufsize; From 6aa811062cd78ad961410a64db55694ff609da57 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Fri, 17 Jul 2026 17:31:06 +0300 Subject: [PATCH 0378/1433] wifi: iwlwifi: mld: initialize scan-abort status Initialize abort status before issuing the abort command so debug logging never reads an uninitialized value on error paths. Assisted-by: GitHubCopilot:GPT-5.3-Codex Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260717172958.9d804f466534.I4e10270bd1dde4a80940a47ef5d383729cc66cb1@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/scan.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/scan.c b/drivers/net/wireless/intel/iwlwifi/mld/scan.c index d80a1cfc2ed5..0faca402bc14 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/scan.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/scan.c @@ -1681,7 +1681,8 @@ iwl_mld_scan_send_abort_cmd_status(struct iwl_mld *mld, int uid, u32 *status) static int iwl_mld_scan_abort(struct iwl_mld *mld, int type, int uid, bool *wait) { - enum iwl_umac_scan_abort_status status; + enum iwl_umac_scan_abort_status status = + IWL_UMAC_SCAN_ABORT_STATUS_NOT_FOUND; int ret; *wait = true; From a790f60cc4d7dc64a2d7cadb94a3d9f60392bed4 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Fri, 17 Jul 2026 17:31:07 +0300 Subject: [PATCH 0379/1433] wifi: iwlwifi: mvm: ignore sync frames when sync is disabled Gate time-sync frame interception on the active flag so frames are not queued after time-sync teardown. Assisted-by: GitHubCopilot:GPT-5.3-Codex Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260717172958.ac73ee199a25.Ic1489244f9b02da93060f0a0e5b300a73527f265@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mvm/time-sync.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/time-sync.h b/drivers/net/wireless/intel/iwlwifi/mvm/time-sync.h index 2cfd0fb5e781..7bdbacb64078 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/time-sync.h +++ b/drivers/net/wireless/intel/iwlwifi/mvm/time-sync.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* * Copyright (C) 2022 Intel Corporation + * Copyright (C) 2026 Intel Corporation */ #ifndef __TIME_SYNC_H__ #define __TIME_SYNC_H__ @@ -19,6 +20,9 @@ int iwl_mvm_time_sync_config(struct iwl_mvm *mvm, const u8 *addr, static inline bool iwl_mvm_time_sync_frame(struct iwl_mvm *mvm, struct sk_buff *skb, u8 *addr) { + if (!mvm->time_sync.active) + return false; + if (ether_addr_equal(mvm->time_sync.peer_addr, addr) && (ieee80211_is_timing_measurement(skb) || ieee80211_is_ftm(skb))) { skb_queue_tail(&mvm->time_sync.frame_list, skb); From 4f5384b58b483e4aec576ce461dec45b41df18db Mon Sep 17 00:00:00 2001 From: Ayala Beker Date: Fri, 17 Jul 2026 17:31:08 +0300 Subject: [PATCH 0380/1433] wifi: iwlwifi: mld: drop connection on D3 resume failure When FW crashes on D3 exit, iwl_mld_nic_error() sets STATUS_RESET_PENDING and queues restart wk, but mac80211's resume callback synchronously calls iwl_trans_stop_device() which clears the flag. As a result restart wk skips sw_reset, and the FW error recovery buffer is never read. The new FW boots with empty BA state and initial sequence numbers, while the AP still holds its A-MPDU RX reorder window. This causes MPDUs to be dropped as IWL_RX_MPDU_REORDER_BA_OLD_SN until ADDBA is renegotiated. We don't know how long the firmware has been in an error state or whether the AP still considers us associated, so keeping the connection alive is not worth it. Call ieee80211_resume_disconnect() when iwl_mld_wait_d3_notif() fails, and let userspace reassociate. Signed-off-by: Ayala Beker Reviewed-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260717172958.3e10c8498f53.Icf5644b42d79e984ecc16abfa873bd37f611e778@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/d3.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/d3.c b/drivers/net/wireless/intel/iwlwifi/mld/d3.c index 301983428a63..55f9b809e764 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/d3.c @@ -2219,6 +2219,9 @@ int iwl_mld_wowlan_resume(struct iwl_mld *mld) ret = iwl_mld_wait_d3_notif(mld, &resume_data, true); if (ret) { IWL_ERR(mld, "Couldn't get the d3 notifs %d\n", ret); + + if (bss_vif->cfg.assoc) + ieee80211_resume_disconnect(bss_vif); goto err; } From 7e1d5ccac87ec618f393ec137743207282ef1af5 Mon Sep 17 00:00:00 2001 From: Avinash Bhatt Date: Fri, 17 Jul 2026 17:31:09 +0300 Subject: [PATCH 0381/1433] wifi: iwlwifi: fw: move SAR defines from acpi.h to regulatory.h IWL_SAR_ENABLE_MSK and IWL_REDUCE_POWER_FLAGS_POS describe the layout of the shared WRDS/SAR table format. They are not ACPI-specific: the same bit positions are used regardless of whether the data originates from ACPI, UEFI, or another BIOS source. IWL_SAR_ENABLE_MSK was already duplicated in regulatory.h; remove it from acpi.h to eliminate the duplication. Move IWL_REDUCE_POWER_FLAGS_POS to regulatory.h alongside IWL_SAR_ENABLE_MSK so that both SAR field descriptors live in the shared regulatory header, accessible to all BIOS configuration sources. No functional change. Signed-off-by: Avinash Bhatt Link: https://patch.msgid.link/20260717172958.32e5dcde4b90.I420c58b05ab6ab011c4c771ca9e4eb62740de549@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/acpi.h | 5 +---- drivers/net/wireless/intel/iwlwifi/fw/regulatory.h | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/acpi.h b/drivers/net/wireless/intel/iwlwifi/fw/acpi.h index 51a57e57de7a..45eb35ffb637 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/acpi.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/acpi.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* * Copyright (C) 2017 Intel Deutschland GmbH - * Copyright (C) 2018-2023, 2025 Intel Corporation + * Copyright (C) 2018-2023, 2025-2026 Intel Corporation */ #ifndef __iwl_fw_acpi__ #define __iwl_fw_acpi__ @@ -111,9 +111,6 @@ #define ACPI_PPAG_WIFI_DATA_SIZE_V3 ((ACPI_PPAG_NUM_CHAINS * \ ACPI_PPAG_NUM_BANDS_V3) + 2) -#define IWL_SAR_ENABLE_MSK BIT(0) -#define IWL_REDUCE_POWER_FLAGS_POS 1 - /* The Inidcator whether UEFI WIFI GUID tables are locked is read from ACPI */ #define UEFI_WIFI_GUID_UNLOCKED 0 diff --git a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h index 6fffc032efd3..22c97c75b83c 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2023-2025 Intel Corporation + * Copyright (C) 2023-2026 Intel Corporation */ #ifndef __fw_regulatory_h__ @@ -30,6 +30,7 @@ #define BIOS_GEO_MIN_PROFILE_NUM 3 #define IWL_SAR_ENABLE_MSK BIT(0) +#define IWL_REDUCE_POWER_FLAGS_POS 1 /* PPAG gain value bounds in 1/8 dBm */ #define IWL_PPAG_MIN_LB -16 From 340eafb30b35a600a66da333bb0c119a880b7062 Mon Sep 17 00:00:00 2001 From: Pagadala Yesu Anjaneyulu Date: Fri, 17 Jul 2026 17:31:10 +0300 Subject: [PATCH 0382/1433] wifi: iwlwifi: mld: support update_mcc notification v2 New firmware will support version 2 of the update_mcc notification. The extra field is used for a new feature, but we does not support it. Keep the existing payload definition compatible with both versions and register version 2 in the MLD notification version table so the driver accepts the newer notification without changing the behavior. This preserves version 1 support and adds compatibility with firmware that sends version 2. Signed-off-by: Pagadala Yesu Anjaneyulu Link: https://patch.msgid.link/20260717172958.9c5a940d37dc.I955800c2377b802ffb99003349552cc4036ca4bd@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h | 5 ++++- drivers/net/wireless/intel/iwlwifi/mld/notif.c | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h b/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h index 360b626a9572..9fbdb45e88db 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h @@ -411,7 +411,10 @@ struct iwl_mcc_chub_notif { __le16 mcc; u8 source_id; u8 reserved1; -} __packed; /* LAR_MCC_NOTIFY_S */ +} __packed; +/* LAR_MCC_NOTIFY_S_VER_1 + * LAR_MCC_NOTIFY_S_VER_2 + */ enum iwl_mcc_update_status { MCC_RESP_NEW_CHAN_PROFILE, diff --git a/drivers/net/wireless/intel/iwlwifi/mld/notif.c b/drivers/net/wireless/intel/iwlwifi/mld/notif.c index b3a899828db9..90733bbaf733 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/notif.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/notif.c @@ -298,7 +298,8 @@ CMD_VERSIONS(channel_survey_notif, CMD_VERSIONS(mfuart_notif, CMD_VER_ENTRY(2, iwl_mfuart_load_notif)) CMD_VERSIONS(update_mcc, - CMD_VER_ENTRY(1, iwl_mcc_chub_notif)) + CMD_VER_ENTRY(1, iwl_mcc_chub_notif) + CMD_VER_ENTRY(2, iwl_mcc_chub_notif)) CMD_VERSIONS(session_prot_notif, CMD_VER_ENTRY(3, iwl_session_prot_notif)) CMD_VERSIONS(missed_beacon_notif, From ac8227e35ff7c8953a7d8adf7f5230127ad97aae Mon Sep 17 00:00:00 2001 From: Pagadala Yesu Anjaneyulu Date: Fri, 17 Jul 2026 17:31:11 +0300 Subject: [PATCH 0383/1433] wifi: iwlwifi: mld: add debug log after AP type command Add a radio debug trace when MCC_ALLOWED_AP_TYPE_CMD is sent successfully during AP type table initialization. This improves bring-up visibility without changing runtime behavior. Failures are still reported through the existing error log path. Signed-off-by: Pagadala Yesu Anjaneyulu Link: https://patch.msgid.link/20260717172958.18e1fc5ec109.I76dd832f62d00a8f358f8e4a705f25184ac53da2@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/regulatory.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c b/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c index 4a93ccbef495..be311f6c3d9f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c @@ -524,6 +524,8 @@ void iwl_mld_init_ap_type_tables(struct iwl_mld *mld) if (ret) IWL_ERR(mld, "failed to send MCC_ALLOWED_AP_TYPE_CMD (%d)\n", ret); + else + IWL_DEBUG_RADIO(mld, "MCC_ALLOWED_AP_TYPE_CMD sent to FW\n"); } void iwl_mld_init_tas(struct iwl_mld *mld) From a47ab1b9c0827f5bd6717abb3f9e3f4f6eb5e00c Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Fri, 17 Jul 2026 17:31:12 +0300 Subject: [PATCH 0384/1433] wifi: iwlwifi: mld: move BIOS reading code to where it belongs We have a dedicated function to fetch all the BIOS tables when the opmode starts, and yet we read a couple of tables directly from iwl_op_mode_mld_start, which is already a large function that does multiple things. Move the reading of the sgom, puncturing, and RFI enablement to the dedicated iwl_mld_get_bios_tables. Link: https://patch.msgid.link/20260717172958.b19a33e0b507.I73f6b5e6a81d0f411f12589ceb30afa655c0a16b@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/mld.c | 2 -- drivers/net/wireless/intel/iwlwifi/mld/regulatory.c | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.c b/drivers/net/wireless/intel/iwlwifi/mld/mld.c index 7c49d0441fdf..6e991fb99ff3 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mld.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.c @@ -419,8 +419,6 @@ iwl_op_mode_mld_start(struct iwl_trans *trans, const struct iwl_rf_cfg *cfg, iwl_mld_construct_fw_runtime(mld, trans, fw, dbgfs_dir); iwl_mld_get_bios_tables(mld); - iwl_uefi_get_sgom_table(trans, &mld->fwrt); - iwl_uefi_get_puncturing(&mld->fwrt); iwl_mld_hw_set_regulatory(mld); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c b/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c index be311f6c3d9f..ad899ce5c64a 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c @@ -84,6 +84,9 @@ void iwl_mld_get_bios_tables(struct iwl_mld *mld) iwl_uefi_get_uneb_table(mld->trans, &mld->fwrt); iwl_bios_get_phy_filters(&mld->fwrt); + + iwl_uefi_get_sgom_table(mld->trans, &mld->fwrt); + iwl_uefi_get_puncturing(&mld->fwrt); } static int iwl_mld_geo_sar_init(struct iwl_mld *mld) From e354f7d60f14a3eacd5ec7b607346a5612f655ec Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Tue, 7 Jul 2026 11:16:16 +0530 Subject: [PATCH 0385/1433] bnx2x: fix null pointer dereference in bnx2x_free_mem_bp() In one of the error path in bnx2x_alloc_mem_bp(), bnx2x_free_mem_bp() may be called with bp->fp uninitialized. And so, there could be a null pointer dereference in bnx2x_free_mem_bp(). Fix that by initializing the fp_array_size after the bp->fp pointer is correctly initialized. Cc: stable+noautosel@kernel.org # untested fix to unlikely error path Reviewed-by: Maciej Fijalkowski Signed-off-by: Abdun Nihaal Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260707054618.932108-1-nihaal@cse.iitm.ac.in Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnx2x/bnx2x_cmn.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_cmn.c b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_cmn.c index 5b2640bd31c3..5a9742fd3ddf 100644 --- a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_cmn.c +++ b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_cmn.c @@ -4742,13 +4742,13 @@ int bnx2x_alloc_mem_bp(struct bnx2x *bp) /* fp array: RSS plus CNIC related L2 queues */ fp_array_size = BNX2X_MAX_RSS_COUNT(bp) + CNIC_SUPPORT(bp); - bp->fp_array_size = fp_array_size; - BNX2X_DEV_INFO("fp_array_size %d\n", bp->fp_array_size); - - fp = kzalloc_objs(*fp, bp->fp_array_size); + BNX2X_DEV_INFO("fp_array_size %d\n", fp_array_size); + fp = kzalloc_objs(*fp, fp_array_size); if (!fp) goto alloc_err; bp->fp = fp; + bp->fp_array_size = fp_array_size; + for (i = 0; i < bp->fp_array_size; i++) { fp[i].tpa_info = kzalloc_objs(struct bnx2x_agg_info, From 7c27c1b7b32b9a7e8c2b07354f8d8f1ae7953ef6 Mon Sep 17 00:00:00 2001 From: Zhi Li Date: Tue, 7 Jul 2026 14:41:30 +0800 Subject: [PATCH 0386/1433] dt-bindings: ethernet: eswin: relax internal delay model to range-based constraints Relax internal delay constraints for EIC7700 Ethernet binding. Replace fixed enumeration of rx-internal-delay-ps and tx-internal-delay-ps with a range-based definition (0-2540 ps, 20 ps steps) to reflect actual hardware capability. Mark rx/tx internal delay properties as optional, as they are board- specific tuning parameters rather than mandatory configuration. Update the device tree example to align with the relaxed constraint model and remove delay properties from the example to avoid implying they are required. No functional change to existing DT users. Reviewed-by: Rob Herring (Arm) Signed-off-by: Zhi Li Link: https://patch.msgid.link/20260707064131.1282-1-lizhi2@eswincomputing.com Signed-off-by: Paolo Abeni --- .../bindings/net/eswin,eic7700-eth.yaml | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/Documentation/devicetree/bindings/net/eswin,eic7700-eth.yaml b/Documentation/devicetree/bindings/net/eswin,eic7700-eth.yaml index 65882ff79d8d..4e02fedae5c6 100644 --- a/Documentation/devicetree/bindings/net/eswin,eic7700-eth.yaml +++ b/Documentation/devicetree/bindings/net/eswin,eic7700-eth.yaml @@ -63,10 +63,14 @@ properties: - const: stmmaceth rx-internal-delay-ps: - enum: [0, 200, 600, 1200, 1600, 1800, 2000, 2200, 2400] + minimum: 0 + maximum: 2540 + multipleOf: 20 tx-internal-delay-ps: - enum: [0, 200, 600, 1200, 1600, 1800, 2000, 2200, 2400] + minimum: 0 + maximum: 2540 + multipleOf: 20 eswin,hsp-sp-csr: description: @@ -105,8 +109,6 @@ required: - phy-mode - resets - reset-names - - rx-internal-delay-ps - - tx-internal-delay-ps - eswin,hsp-sp-csr unevaluatedProperties: false @@ -116,23 +118,22 @@ examples: ethernet@50400000 { compatible = "eswin,eic7700-qos-eth", "snps,dwmac-5.20"; reg = <0x50400000 0x10000>; - clocks = <&d0_clock 186>, <&d0_clock 171>, <&d0_clock 40>, - <&d0_clock 193>; - clock-names = "axi", "cfg", "stmmaceth", "tx"; interrupt-parent = <&plic>; interrupts = <61>; interrupt-names = "macirq"; - phy-mode = "rgmii-id"; - phy-handle = <&phy0>; + clocks = <&d0_clock 186>, <&d0_clock 171>, <&d0_clock 40>, + <&d0_clock 193>; + clock-names = "axi", "cfg", "stmmaceth", "tx"; resets = <&reset 95>; reset-names = "stmmaceth"; - rx-internal-delay-ps = <200>; - tx-internal-delay-ps = <200>; eswin,hsp-sp-csr = <&hsp_sp_csr 0x100 0x108 0x118 0x114 0x11c>; - snps,axi-config = <&stmmac_axi_setup>; + phy-handle = <&phy0>; + phy-mode = "rgmii-id"; snps,aal; snps,fixed-burst; snps,tso; + snps,axi-config = <&stmmac_axi_setup>; + stmmac_axi_setup: stmmac-axi-config { snps,blen = <0 0 0 0 16 8 4>; snps,rd_osr_lmt = <2>; From 3c6dfb86db07534ee34fa834c51d3608df4af197 Mon Sep 17 00:00:00 2001 From: Zhi Li Date: Tue, 7 Jul 2026 14:41:59 +0800 Subject: [PATCH 0387/1433] dt-bindings: ethernet: eswin: add EIC7700 eth1 RX clock inversion variant The EIC7700 SoC integrates two GMAC instances. The eth1 MAC exhibits different RX clock sampling characteristics due to silicon-inherent timing behavior. The eth1 MAC has a fixed, non-configurable RX clock-to-data skew at the MAC input in the order of 4-5 ns. This cannot be compensated solely by the standard MAC internal delay configuration and PHY delay, and RX clock inversion is required at 1000Mbps for correct sampling. The eth1 TX path also includes a fixed silicon-inherent delay of approximately 2 ns. This delay is always present and cannot be disabled. It is therefore part of the effective transmit timing observed on the wire. For the eth1 variant, the valid tx-internal-delay-ps values include this fixed delay component. Consequently, the effective range becomes 2000-4540 ps (approximately 2000 ps fixed delay plus 0-2540 ps programmable delay). Introduce a dedicated compatible string "eswin,eic7700-qos-eth-clk-inversion" to represent the eth1 variant, allowing the driver to apply RX clock inversion only when required by hardware variant selection. This keeps SoC-level differentiation without exposing silicon-fixed skew as configurable device tree parameters. To reflect this, model the TX internal delay as a base 0-4540 ps range, and constrain valid values per compatible using conditional schema rules. Update the binding schema as follows: - Define tx-internal-delay-ps as a base range: 0-4540 ps - Add compatible-specific constraints using if/then rules: * eswin,eic7700-qos-eth: max 2540 ps * eswin,eic7700-qos-eth-clk-inversion: minimum 2000 ps (effective range 2000-4540 ps) No functional change for existing "eswin,eic7700-qos-eth" users. Acked-by: Conor Dooley Signed-off-by: Zhi Li Link: https://patch.msgid.link/20260707064159.1299-1-lizhi2@eswincomputing.com Signed-off-by: Paolo Abeni --- .../bindings/net/eswin,eic7700-eth.yaml | 51 ++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/net/eswin,eic7700-eth.yaml b/Documentation/devicetree/bindings/net/eswin,eic7700-eth.yaml index 4e02fedae5c6..ba49fd6a086c 100644 --- a/Documentation/devicetree/bindings/net/eswin,eic7700-eth.yaml +++ b/Documentation/devicetree/bindings/net/eswin,eic7700-eth.yaml @@ -20,16 +20,37 @@ select: contains: enum: - eswin,eic7700-qos-eth + - eswin,eic7700-qos-eth-clk-inversion required: - compatible allOf: - $ref: snps,dwmac.yaml# + - if: + properties: + compatible: + contains: + const: eswin,eic7700-qos-eth + then: + properties: + tx-internal-delay-ps: + maximum: 2540 + - if: + properties: + compatible: + contains: + const: eswin,eic7700-qos-eth-clk-inversion + then: + properties: + tx-internal-delay-ps: + minimum: 2000 properties: compatible: items: - - const: eswin,eic7700-qos-eth + - enum: + - eswin,eic7700-qos-eth + - eswin,eic7700-qos-eth-clk-inversion - const: snps,dwmac-5.20 reg: @@ -69,7 +90,7 @@ properties: tx-internal-delay-ps: minimum: 0 - maximum: 2540 + maximum: 4540 multipleOf: 20 eswin,hsp-sp-csr: @@ -140,3 +161,29 @@ examples: snps,wr_osr_lmt = <2>; }; }; + + ethernet@50410000 { + compatible = "eswin,eic7700-qos-eth-clk-inversion", "snps,dwmac-5.20"; + reg = <0x50410000 0x10000>; + interrupt-parent = <&plic>; + interrupts = <70>; + interrupt-names = "macirq"; + clocks = <&d0_clock 186>, <&d0_clock 171>, <&d0_clock 40>, + <&d0_clock 194>; + clock-names = "axi", "cfg", "stmmaceth", "tx"; + resets = <&reset 94>; + reset-names = "stmmaceth"; + eswin,hsp-sp-csr = <&hsp_sp_csr 0x200 0x208 0x218 0x214 0x21c>; + phy-handle = <&gmac1_phy0>; + phy-mode = "rgmii-id"; + snps,aal; + snps,fixed-burst; + snps,tso; + snps,axi-config = <&stmmac_axi_setup_gmac1>; + + stmmac_axi_setup_gmac1: stmmac-axi-config { + snps,blen = <0 0 0 0 16 8 4>; + snps,rd_osr_lmt = <2>; + snps,wr_osr_lmt = <2>; + }; + }; From 186f38935e28ae700b0fe062dcc6ab345211dcee Mon Sep 17 00:00:00 2001 From: Zhi Li Date: Tue, 7 Jul 2026 14:42:17 +0800 Subject: [PATCH 0388/1433] net: stmmac: eic7700: make RGMII delay properties optional Make rx-internal-delay-ps and tx-internal-delay-ps optional in the EIC7700 DWMAC driver. The driver previously required both properties to be present and would fail probe when they were missing. This restricts valid hardware configurations where RGMII timing is instead provided by the PHY or board design. Update the driver to treat missing delay properties as zero delay, allowing systems without explicit MAC-side delay tuning to operate correctly. This aligns the driver behavior with the updated device tree binding and provides a safe default configuration when MAC-side delay programming is not required. Signed-off-by: Zhi Li Link: https://patch.msgid.link/20260707064218.1316-1-lizhi2@eswincomputing.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c index 4ac979d874d6..ec99b597aeaf 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c @@ -165,9 +165,6 @@ static int eic7700_dwmac_probe(struct platform_device *pdev) dwc_priv->eth_clk_dly_param &= ~EIC7700_ETH_RX_ADJ_DELAY; dwc_priv->eth_clk_dly_param |= FIELD_PREP(EIC7700_ETH_RX_ADJ_DELAY, val); - } else { - return dev_err_probe(&pdev->dev, -EINVAL, - "missing required property rx-internal-delay-ps\n"); } /* Read tx-internal-delay-ps and update tx_clk delay */ @@ -187,9 +184,6 @@ static int eic7700_dwmac_probe(struct platform_device *pdev) dwc_priv->eth_clk_dly_param &= ~EIC7700_ETH_TX_ADJ_DELAY; dwc_priv->eth_clk_dly_param |= FIELD_PREP(EIC7700_ETH_TX_ADJ_DELAY, val); - } else { - return dev_err_probe(&pdev->dev, -EINVAL, - "missing required property tx-internal-delay-ps\n"); } dwc_priv->eic7700_hsp_regmap = From 0cb57bdebd101da07587e7f43d9473af826dd527 Mon Sep 17 00:00:00 2001 From: Zhi Li Date: Tue, 7 Jul 2026 14:42:32 +0800 Subject: [PATCH 0389/1433] net: stmmac: eic7700: add support for eth1 clock inversion variant The eth1 MAC exhibits silicon-inherent RX and TX timing behavior that differs from the eth0 implementation. At 1000Mbps, RX sampling requires clock inversion due to a fixed MAC input skew that cannot be compensated by standard RGMII delay settings. The TX path includes a fixed ~2ns internal delay introduced by the MAC silicon. This delay is always present and is already accounted for in the device tree tx-internal-delay-ps property as part of the effective output timing. The tx-internal-delay-ps property describes the effective delay seen at the MAC output. Since the hardware register controls only the programmable portion of the delay, the driver subtracts the fixed silicon-inherent component before programming the delay register. Use compatible-specific match data to identify the eth1 variant and apply RX clock inversion only at 1000Mbps. The PHY interface mode is adjusted via phy_fix_phy_mode_for_mac_delays() to avoid double-application of RGMII delays when MAC-side delays are already present. Link speed dependency means RX sampling configuration is applied in the fix_mac_speed callback after negotiation. No behavior changes for the existing eth0 controller. Signed-off-by: Zhi Li Link: https://patch.msgid.link/20260707064234.1333-1-lizhi2@eswincomputing.com Signed-off-by: Paolo Abeni --- .../ethernet/stmicro/stmmac/dwmac-eic7700.c | 111 ++++++++++++++++-- 1 file changed, 103 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c index ec99b597aeaf..eab8c13fbdcc 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c @@ -28,11 +28,15 @@ /* * TX/RX Clock Delay Bit Masks: - * - TX Delay: bits [14:8] — TX_CLK delay (unit: 0.02ns per bit) - * - RX Delay: bits [30:24] — RX_CLK delay (unit: 0.02ns per bit) + * - TX Delay: bits [14:8] - TX_CLK delay (unit: 0.02ns per bit) + * - TX Invert : bit [15] + * - RX Delay: bits [30:24] - RX_CLK delay (unit: 0.02ns per bit) + * - RX Invert : bit [31] */ #define EIC7700_ETH_TX_ADJ_DELAY GENMASK(14, 8) #define EIC7700_ETH_RX_ADJ_DELAY GENMASK(30, 24) +#define EIC7700_ETH_TX_INV_DELAY BIT(15) +#define EIC7700_ETH_RX_INV_DELAY BIT(31) #define EIC7700_MAX_DELAY_STEPS 0x7F #define EIC7700_DELAY_STEP_PS 20 @@ -43,7 +47,14 @@ static const char * const eic7700_clk_names[] = { "tx", "axi", "cfg", }; +struct eic7700_dwmac_data { + bool rgmii_rx_clk_invert; + bool has_internal_tx_delay; + u32 tx_clk_inherent_skew_ps; +}; + struct eic7700_qos_priv { + struct device *dev; struct plat_stmmacenet_data *plat_dat; struct regmap *eic7700_hsp_regmap; u32 eth_axi_lp_ctrl_offset; @@ -54,6 +65,7 @@ struct eic7700_qos_priv { u32 eth_clk_dly_param; bool has_txd_offset; bool has_rxd_offset; + bool eth_rx_clk_inv; }; static int eic7700_clks_config(void *priv, bool enabled) @@ -97,9 +109,6 @@ static int eic7700_dwmac_init(struct device *dev, void *priv) if (dwc->has_rxd_offset) regmap_write(dwc->eic7700_hsp_regmap, dwc->eth_rxd_offset, 0); - regmap_write(dwc->eic7700_hsp_regmap, dwc->eth_clk_offset, - dwc->eth_clk_dly_param); - return 0; } @@ -126,8 +135,38 @@ static int eic7700_dwmac_resume(struct device *dev, void *priv) return ret; } +/* + * eth1 requires RX clock inversion at 1000Mbps due to silicon-inherent + * RX sampling skew at MAC input. + * + * The configuration is updated in fix_mac_speed() because the required + * sampling behavior depends on the negotiated link speed. + */ +static void eic7700_dwmac_fix_speed(void *priv, phy_interface_t interface, + int speed, unsigned int mode) +{ + struct eic7700_qos_priv *dwc = (struct eic7700_qos_priv *)priv; + u32 dly_param = dwc->eth_clk_dly_param; + + switch (speed) { + case SPEED_1000: + if (dwc->eth_rx_clk_inv) + dly_param |= EIC7700_ETH_RX_INV_DELAY; + break; + case SPEED_100: + case SPEED_10: + break; + default: + dev_warn(dwc->dev, "unsupported speed %u\n", speed); + return; + } + + regmap_write(dwc->eic7700_hsp_regmap, dwc->eth_clk_offset, dly_param); +} + static int eic7700_dwmac_probe(struct platform_device *pdev) { + const struct eic7700_dwmac_data *data; struct plat_stmmacenet_data *plat_dat; struct stmmac_resources stmmac_res; struct eic7700_qos_priv *dwc_priv; @@ -148,6 +187,30 @@ static int eic7700_dwmac_probe(struct platform_device *pdev) if (!dwc_priv) return -ENOMEM; + dwc_priv->dev = &pdev->dev; + + data = device_get_match_data(&pdev->dev); + if (!data) + return dev_err_probe(&pdev->dev, + -EINVAL, "no match data found\n"); + + dwc_priv->eth_rx_clk_inv = data->rgmii_rx_clk_invert; + /* + * The MAC silicon unconditionally adds ~2 ns TX delay; prevent + * the PHY from also adding TX delay to avoid doubling it. + * + * DT specifies rgmii-id (TX from MAC silicon, RX from PHY); + * override to rgmii-rxid so the PHY only adds its RX delay. + */ + if (data->has_internal_tx_delay) { + plat_dat->phy_interface = + phy_fix_phy_mode_for_mac_delays(plat_dat->phy_interface, + true, false); + if (plat_dat->phy_interface == PHY_INTERFACE_MODE_NA) + return dev_err_probe(&pdev->dev, -EINVAL, + "phy interface mode is NA\n"); + } + /* Read rx-internal-delay-ps and update rx_clk delay */ if (!of_property_read_u32(pdev->dev.of_node, "rx-internal-delay-ps", &delay_ps)) { @@ -167,7 +230,13 @@ static int eic7700_dwmac_probe(struct platform_device *pdev) FIELD_PREP(EIC7700_ETH_RX_ADJ_DELAY, val); } - /* Read tx-internal-delay-ps and update tx_clk delay */ + /* Read tx-internal-delay-ps and update tx_clk delay. + * + * For eswin,eic7700-qos-eth-clk-inversion, the DT property describes + * the effective TX delay at the MAC output, including the inherent + * silicon delay. Subtract the fixed component to obtain the + * programmable delay value. + */ if (!of_property_read_u32(pdev->dev.of_node, "tx-internal-delay-ps", &delay_ps)) { if (delay_ps % EIC7700_DELAY_STEP_PS) @@ -175,9 +244,16 @@ static int eic7700_dwmac_probe(struct platform_device *pdev) "tx delay must be multiple of %dps\n", EIC7700_DELAY_STEP_PS); + if (delay_ps < data->tx_clk_inherent_skew_ps) + return dev_err_probe(&pdev->dev, -EINVAL, + "tx delay %ups below inherent skew %ups\n", + delay_ps, data->tx_clk_inherent_skew_ps); + + delay_ps -= data->tx_clk_inherent_skew_ps; + if (delay_ps > EIC7700_MAX_DELAY_PS) return dev_err_probe(&pdev->dev, -EINVAL, - "tx delay out of range\n"); + "tx delay out of programmable range\n"); val = delay_ps / EIC7700_DELAY_STEP_PS; @@ -254,12 +330,31 @@ static int eic7700_dwmac_probe(struct platform_device *pdev) plat_dat->exit = eic7700_dwmac_exit; plat_dat->suspend = eic7700_dwmac_suspend; plat_dat->resume = eic7700_dwmac_resume; + plat_dat->fix_mac_speed = eic7700_dwmac_fix_speed; return devm_stmmac_pltfr_probe(pdev, plat_dat, &stmmac_res); } +static const struct eic7700_dwmac_data eic7700_dwmac_data = { + .rgmii_rx_clk_invert = false, + .has_internal_tx_delay = false, + .tx_clk_inherent_skew_ps = 0, +}; + +static const struct eic7700_dwmac_data eic7700_dwmac_data_clk_inversion = { + .rgmii_rx_clk_invert = true, + .has_internal_tx_delay = true, + .tx_clk_inherent_skew_ps = 2000, +}; + static const struct of_device_id eic7700_dwmac_match[] = { - { .compatible = "eswin,eic7700-qos-eth" }, + { .compatible = "eswin,eic7700-qos-eth", + .data = &eic7700_dwmac_data, + }, + { + .compatible = "eswin,eic7700-qos-eth-clk-inversion", + .data = &eic7700_dwmac_data_clk_inversion, + }, { } }; MODULE_DEVICE_TABLE(of, eic7700_dwmac_match); From 777434f53e77f716561eac27e8a21278f1f81e4e Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 7 Jul 2026 14:53:28 +0000 Subject: [PATCH 0390/1433] geneve: pass geneve_config pointer to helper functions In preparation for converting geneve->cfg to an RCU-protected pointer, update helper functions to explicitly accept a const struct geneve_config pointer instead of dereferencing geneve->cfg directly. Signed-off-by: Eric Dumazet Suggested-by: Paolo Abeni Link: https://patch.msgid.link/20260707145331.3717941-2-edumazet@google.com Signed-off-by: Paolo Abeni --- drivers/net/geneve.c | 140 +++++++++++++++++++++++-------------------- 1 file changed, 76 insertions(+), 64 deletions(-) diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index 396e1a113cd4..cab38e7de871 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -762,9 +762,10 @@ static int geneve_udp_encap_err_lookup(struct sock *sk, struct sk_buff *skb) } static struct sock *geneve_create_sock(struct net *net, - struct geneve_dev *geneve, bool ipv6) + struct geneve_dev *geneve, + const struct geneve_config *cfg, bool ipv6) { - struct ip_tunnel_info *info = &geneve->cfg.info; + const struct ip_tunnel_info *info = &cfg->info; struct udp_port_cfg udp_conf; struct socket *sock; int err; @@ -775,7 +776,7 @@ static struct sock *geneve_create_sock(struct net *net, if (ipv6) { udp_conf.family = AF_INET6; udp_conf.ipv6_v6only = 1; - udp_conf.use_udp6_rx_checksums = geneve->cfg.use_udp6_rx_checksums; + udp_conf.use_udp6_rx_checksums = cfg->use_udp6_rx_checksums; udp_conf.local_ip6 = info->key.u.ipv6.src; } else #endif @@ -991,7 +992,8 @@ static int geneve_gro_complete(struct sock *sk, struct sk_buff *skb, /* Create new listen socket if needed */ static struct geneve_sock *geneve_socket_create(struct net *net, - struct geneve_dev *geneve, bool ipv6) + struct geneve_dev *geneve, + const struct geneve_config *cfg, bool ipv6) { struct geneve_net *gn = net_generic(net, geneve_net_id); struct udp_tunnel_sock_cfg tunnel_cfg; @@ -1003,7 +1005,7 @@ static struct geneve_sock *geneve_socket_create(struct net *net, if (!gs) return ERR_PTR(-ENOMEM); - sk = geneve_create_sock(net, geneve, ipv6); + sk = geneve_create_sock(net, geneve, cfg, ipv6); if (IS_ERR(sk)) { kfree(gs); return ERR_CAST(sk); @@ -1060,12 +1062,13 @@ static void geneve_sock_release(struct geneve_dev *geneve) } static struct geneve_sock *geneve_find_sock(struct net *net, - struct geneve_dev *geneve, bool ipv6) + struct geneve_dev *geneve, + const struct geneve_config *cfg, bool ipv6) { struct geneve_net *gn = net_generic(net, geneve_net_id); - struct ip_tunnel_info *info = &geneve->cfg.info; + const struct ip_tunnel_info *info = &cfg->info; sa_family_t family = ipv6 ? AF_INET6 : AF_INET; - bool gro_hint = geneve->cfg.gro_hint; + bool gro_hint = cfg->gro_hint; __be16 dst_port = info->key.tp_dst; struct geneve_sock *gs; @@ -1095,7 +1098,8 @@ static struct geneve_sock *geneve_find_sock(struct net *net, return NULL; } -static int geneve_sock_add(struct geneve_dev *geneve, bool ipv6) +static int geneve_sock_add(struct geneve_dev *geneve, + const struct geneve_config *cfg, bool ipv6) { struct net *net = geneve->net; struct geneve_dev_node *node; @@ -1103,19 +1107,19 @@ static int geneve_sock_add(struct geneve_dev *geneve, bool ipv6) __u8 vni[3]; __u32 hash; - gs = geneve_find_sock(net, geneve, ipv6); + gs = geneve_find_sock(net, geneve, cfg, ipv6); if (gs) { gs->refcnt++; goto out; } - gs = geneve_socket_create(net, geneve, ipv6); + gs = geneve_socket_create(net, geneve, cfg, ipv6); if (IS_ERR(gs)) return PTR_ERR(gs); out: - gs->collect_md = geneve->cfg.collect_md; - gs->gro_hint = geneve->cfg.gro_hint; + gs->collect_md = cfg->collect_md; + gs->gro_hint = cfg->gro_hint; #if IS_ENABLED(CONFIG_IPV6) if (ipv6) { rcu_assign_pointer(geneve->sock6, gs); @@ -1128,7 +1132,7 @@ static int geneve_sock_add(struct geneve_dev *geneve, bool ipv6) } node->geneve = geneve; - tunnel_id_to_vni(geneve->cfg.info.key.tun_id, vni); + tunnel_id_to_vni(cfg->info.key.tun_id, vni); hash = geneve_net_vni_hash(vni); hlist_add_head_rcu(&node->hlist, &gs->vni_list[hash]); return 0; @@ -1137,21 +1141,22 @@ static int geneve_sock_add(struct geneve_dev *geneve, bool ipv6) static int geneve_open(struct net_device *dev) { struct geneve_dev *geneve = netdev_priv(dev); - bool dualstack = geneve->cfg.dualstack; - bool ipv4, ipv6; + const struct geneve_config *cfg = &geneve->cfg; + bool ipv4, ipv6, dualstack; int ret = 0; - ipv6 = geneve->cfg.info.mode & IP_TUNNEL_INFO_IPV6 || dualstack; + dualstack = cfg->dualstack; + ipv6 = cfg->info.mode & IP_TUNNEL_INFO_IPV6 || dualstack; ipv4 = !ipv6 || dualstack; #if IS_ENABLED(CONFIG_IPV6) if (ipv6) { - ret = geneve_sock_add(geneve, true); + ret = geneve_sock_add(geneve, cfg, true); if (ret < 0 && ret != -EAFNOSUPPORT) ipv4 = false; } #endif if (ipv4) - ret = geneve_sock_add(geneve, false); + ret = geneve_sock_add(geneve, cfg, false); if (ret < 0) geneve_sock_release(geneve); @@ -1189,6 +1194,7 @@ static void geneve_build_header(struct genevehdr *geneveh, } static int geneve_build_gro_hint_opt(const struct geneve_dev *geneve, + const struct geneve_config *cfg, struct sk_buff *skb) { struct geneve_skb_cb *cb = GENEVE_SKB_CB(skb); @@ -1201,7 +1207,7 @@ static int geneve_build_gro_hint_opt(const struct geneve_dev *geneve, cb->gro_hint_len = 0; /* Try to add the GRO hint only in case of double encap. */ - if (!geneve->cfg.gro_hint || !skb->encapsulation) + if (!cfg->gro_hint || !skb->encapsulation) return 0; /* @@ -1262,10 +1268,11 @@ static void geneve_put_gro_hint_opt(struct genevehdr *gnvh, int opt_size, static int geneve_build_skb(struct dst_entry *dst, struct sk_buff *skb, const struct ip_tunnel_info *info, - const struct geneve_dev *geneve, int ip_hdr_len) + const struct geneve_dev *geneve, + const struct geneve_config *cfg, int ip_hdr_len) { bool udp_sum = test_bit(IP_TUNNEL_CSUM_BIT, info->key.tun_flags); - bool inner_proto_inherit = geneve->cfg.inner_proto_inherit; + bool inner_proto_inherit = cfg->inner_proto_inherit; bool xnet = !net_eq(geneve->net, dev_net(geneve->dev)); struct geneve_skb_cb *cb = GENEVE_SKB_CB(skb); struct genevehdr *gnvh; @@ -1306,14 +1313,14 @@ static int geneve_build_skb(struct dst_entry *dst, struct sk_buff *skb, } static u8 geneve_get_dsfield(struct sk_buff *skb, struct net_device *dev, + const struct geneve_config *cfg, const struct ip_tunnel_info *info, bool *use_cache) { - struct geneve_dev *geneve = netdev_priv(dev); u8 dsfield; dsfield = info->key.tos; - if (dsfield == 1 && !geneve->cfg.collect_md) { + if (cfg && dsfield == 1 && !cfg->collect_md) { dsfield = ip_tunnel_get_dsfield(ip_hdr(skb), skb); *use_cache = false; } @@ -1323,6 +1330,7 @@ static u8 geneve_get_dsfield(struct sk_buff *skb, struct net_device *dev, static int geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev, struct geneve_dev *geneve, + const struct geneve_config *cfg, const struct ip_tunnel_info *info) { struct geneve_sock *gs4 = rcu_dereference(geneve->sock4); @@ -1335,35 +1343,35 @@ static int geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev, __be16 sport; int err; - if (skb_vlan_inet_prepare(skb, geneve->cfg.inner_proto_inherit)) + if (skb_vlan_inet_prepare(skb, cfg->inner_proto_inherit)) return -EINVAL; if (!gs4) return -EIO; use_cache = ip_tunnel_dst_cache_usable(skb, info); - tos = geneve_get_dsfield(skb, dev, info, &use_cache); + tos = geneve_get_dsfield(skb, dev, cfg, info, &use_cache); sport = udp_flow_src_port(geneve->net, skb, - geneve->cfg.port_min, - geneve->cfg.port_max, true); + cfg->port_min, + cfg->port_max, true); rt = udp_tunnel_dst_lookup(skb, dev, geneve->net, 0, &saddr, &info->key, - sport, geneve->cfg.info.key.tp_dst, tos, + sport, cfg->info.key.tp_dst, tos, use_cache ? (struct dst_cache *)&info->dst_cache : NULL); if (IS_ERR(rt)) return PTR_ERR(rt); - if (geneve->cfg.info.key.u.ipv4.src && - saddr != geneve->cfg.info.key.u.ipv4.src) { + if (cfg->info.key.u.ipv4.src && + saddr != cfg->info.key.u.ipv4.src) { dst_release(&rt->dst); return -EADDRNOTAVAIL; } err = skb_tunnel_check_pmtu(skb, &rt->dst, GENEVE_IPV4_HLEN + info->options_len + - geneve_build_gro_hint_opt(geneve, skb), + geneve_build_gro_hint_opt(geneve, cfg, skb), netif_is_any_bridge_port(dev)); if (err < 0) { dst_release(&rt->dst); @@ -1397,21 +1405,21 @@ static int geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev, } tos = ip_tunnel_ecn_encap(tos, ip_hdr(skb), skb); - if (geneve->cfg.collect_md) { + if (cfg->collect_md) { ttl = key->ttl; df = test_bit(IP_TUNNEL_DONT_FRAGMENT_BIT, key->tun_flags) ? htons(IP_DF) : 0; } else { - if (geneve->cfg.ttl_inherit) + if (cfg->ttl_inherit) ttl = ip_tunnel_get_ttl(ip_hdr(skb), skb); else ttl = key->ttl; ttl = ttl ? : ip4_dst_hoplimit(&rt->dst); - if (geneve->cfg.df == GENEVE_DF_SET) { + if (cfg->df == GENEVE_DF_SET) { df = htons(IP_DF); - } else if (geneve->cfg.df == GENEVE_DF_INHERIT) { + } else if (cfg->df == GENEVE_DF_INHERIT) { struct ethhdr *eth = skb_eth_hdr(skb); if (ntohs(eth->h_proto) == ETH_P_IPV6) { @@ -1425,13 +1433,13 @@ static int geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev, } } - err = geneve_build_skb(&rt->dst, skb, info, geneve, + err = geneve_build_skb(&rt->dst, skb, info, geneve, cfg, sizeof(struct iphdr)); if (unlikely(err)) return err; udp_tunnel_xmit_skb(rt, gs4->sk, skb, saddr, info->key.u.ipv4.dst, - tos, ttl, df, sport, geneve->cfg.info.key.tp_dst, + tos, ttl, df, sport, cfg->info.key.tp_dst, !net_eq(geneve->net, dev_net(geneve->dev)), !test_bit(IP_TUNNEL_CSUM_BIT, info->key.tun_flags), 0); @@ -1441,6 +1449,7 @@ static int geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev, #if IS_ENABLED(CONFIG_IPV6) static int geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev, struct geneve_dev *geneve, + const struct geneve_config *cfg, const struct ip_tunnel_info *info) { struct geneve_sock *gs6 = rcu_dereference(geneve->sock6); @@ -1452,35 +1461,35 @@ static int geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev, __be16 sport; int err; - if (skb_vlan_inet_prepare(skb, geneve->cfg.inner_proto_inherit)) + if (skb_vlan_inet_prepare(skb, cfg->inner_proto_inherit)) return -EINVAL; if (!gs6) return -EIO; use_cache = ip_tunnel_dst_cache_usable(skb, info); - prio = geneve_get_dsfield(skb, dev, info, &use_cache); + prio = geneve_get_dsfield(skb, dev, cfg, info, &use_cache); sport = udp_flow_src_port(geneve->net, skb, - geneve->cfg.port_min, - geneve->cfg.port_max, true); + cfg->port_min, + cfg->port_max, true); dst = udp_tunnel6_dst_lookup(skb, dev, geneve->net, gs6->sk, 0, &saddr, key, sport, - geneve->cfg.info.key.tp_dst, prio, + cfg->info.key.tp_dst, prio, use_cache ? (struct dst_cache *)&info->dst_cache : NULL); if (IS_ERR(dst)) return PTR_ERR(dst); - if (!ipv6_addr_any(&geneve->cfg.info.key.u.ipv6.src) && - !ipv6_addr_equal(&saddr, &geneve->cfg.info.key.u.ipv6.src)) { + if (!ipv6_addr_any(&cfg->info.key.u.ipv6.src) && + !ipv6_addr_equal(&saddr, &cfg->info.key.u.ipv6.src)) { dst_release(dst); return -EADDRNOTAVAIL; } err = skb_tunnel_check_pmtu(skb, dst, GENEVE_IPV6_HLEN + info->options_len + - geneve_build_gro_hint_opt(geneve, skb), + geneve_build_gro_hint_opt(geneve, cfg, skb), netif_is_any_bridge_port(dev)); if (err < 0) { dst_release(dst); @@ -1513,22 +1522,22 @@ static int geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev, } prio = ip_tunnel_ecn_encap(prio, ip_hdr(skb), skb); - if (geneve->cfg.collect_md) { + if (cfg->collect_md) { ttl = key->ttl; } else { - if (geneve->cfg.ttl_inherit) + if (cfg->ttl_inherit) ttl = ip_tunnel_get_ttl(ip_hdr(skb), skb); else ttl = key->ttl; ttl = ttl ? : ip6_dst_hoplimit(dst); } - err = geneve_build_skb(dst, skb, info, geneve, sizeof(struct ipv6hdr)); + err = geneve_build_skb(dst, skb, info, geneve, cfg, sizeof(struct ipv6hdr)); if (unlikely(err)) return err; udp_tunnel6_xmit_skb(dst, gs6->sk, skb, dev, &saddr, &key->u.ipv6.dst, prio, ttl, - info->key.label, sport, geneve->cfg.info.key.tp_dst, + info->key.label, sport, cfg->info.key.tp_dst, !test_bit(IP_TUNNEL_CSUM_BIT, info->key.tun_flags), 0); @@ -1539,10 +1548,12 @@ static int geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev, static netdev_tx_t geneve_xmit(struct sk_buff *skb, struct net_device *dev) { struct geneve_dev *geneve = netdev_priv(dev); - struct ip_tunnel_info *info = NULL; + const struct ip_tunnel_info *info = NULL; + const struct geneve_config *cfg; int err; - if (geneve->cfg.collect_md) { + cfg = &geneve->cfg; + if (cfg->collect_md) { info = skb_tunnel_info(skb); if (unlikely(!info || !(info->mode & IP_TUNNEL_INFO_TX))) { netdev_dbg(dev, "no tunnel metadata\n"); @@ -1551,16 +1562,16 @@ static netdev_tx_t geneve_xmit(struct sk_buff *skb, struct net_device *dev) return NETDEV_TX_OK; } } else { - info = &geneve->cfg.info; + info = &cfg->info; } rcu_read_lock(); #if IS_ENABLED(CONFIG_IPV6) if (info->mode & IP_TUNNEL_INFO_IPV6) - err = geneve6_xmit_skb(skb, dev, geneve, info); + err = geneve6_xmit_skb(skb, dev, geneve, cfg, info); else #endif - err = geneve_xmit_skb(skb, dev, geneve, info); + err = geneve_xmit_skb(skb, dev, geneve, cfg, info); rcu_read_unlock(); if (likely(!err)) @@ -1593,6 +1604,7 @@ static int geneve_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb) { struct ip_tunnel_info *info = skb_tunnel_info(skb); struct geneve_dev *geneve = netdev_priv(dev); + const struct geneve_config *cfg = &geneve->cfg; __be16 sport; if (ip_tunnel_info_af(info) == AF_INET) { @@ -1606,14 +1618,14 @@ static int geneve_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb) return -EIO; use_cache = ip_tunnel_dst_cache_usable(skb, info); - tos = geneve_get_dsfield(skb, dev, info, &use_cache); + tos = geneve_get_dsfield(skb, dev, cfg, info, &use_cache); sport = udp_flow_src_port(geneve->net, skb, - geneve->cfg.port_min, - geneve->cfg.port_max, true); + cfg->port_min, + cfg->port_max, true); rt = udp_tunnel_dst_lookup(skb, dev, geneve->net, 0, &saddr, &info->key, - sport, geneve->cfg.info.key.tp_dst, + sport, cfg->info.key.tp_dst, tos, use_cache ? &info->dst_cache : NULL); if (IS_ERR(rt)) @@ -1633,14 +1645,14 @@ static int geneve_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb) return -EIO; use_cache = ip_tunnel_dst_cache_usable(skb, info); - prio = geneve_get_dsfield(skb, dev, info, &use_cache); + prio = geneve_get_dsfield(skb, dev, cfg, info, &use_cache); sport = udp_flow_src_port(geneve->net, skb, - geneve->cfg.port_min, - geneve->cfg.port_max, true); + cfg->port_min, + cfg->port_max, true); dst = udp_tunnel6_dst_lookup(skb, dev, geneve->net, gs6->sk, 0, &saddr, &info->key, sport, - geneve->cfg.info.key.tp_dst, prio, + cfg->info.key.tp_dst, prio, use_cache ? &info->dst_cache : NULL); if (IS_ERR(dst)) return PTR_ERR(dst); @@ -1653,7 +1665,7 @@ static int geneve_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb) } info->key.tp_src = sport; - info->key.tp_dst = geneve->cfg.info.key.tp_dst; + info->key.tp_dst = cfg->info.key.tp_dst; return 0; } From 0ba269933f733222a5819d0cfc5f77f5606fabac Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 7 Jul 2026 14:53:29 +0000 Subject: [PATCH 0391/1433] geneve: convert config to RCU-protected pointer geneve_changelink() currently updates configuration by copying it over the old one using memcpy() under RTNL, forcing data path pause via geneve_quiesce() and synchronize_net() to avoid reading torn values. Convert geneve->cfg to an RCU-protected pointer, allowing lockless and safe reads under RCU read lock without synchronization overhead. Key changes: - Introduced geneve_config_alloc/free() helpers for lifecycle. - geneve_configure() allocates config and publishes it via RCU. - Setting dev->priv_destructor = geneve_free_dev handles config cleanup if register_netdevice() fails or during netdev unregistration. - geneve_changelink() performs RCU swap; old config is freed via call_rcu_hurry(). - Allocates new dst_cache during changelink to prevent pcpu sharing. - Removed geneve_quiesce/unquiesce() and synchronize_net() from changelink. - Added rcu_barrier() to module exit to wait for pending callbacks. - Updated data path to use rcu_dereference(). - Updated geneve_fill_info() to use rtnl_dereference() for now. Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260707145331.3717941-3-edumazet@google.com Signed-off-by: Paolo Abeni --- drivers/net/geneve.c | 213 ++++++++++++++++++++++++------------------- 1 file changed, 118 insertions(+), 95 deletions(-) diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index cab38e7de871..f14e019f3f3f 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -82,6 +82,7 @@ struct geneve_config { u16 port_min; u16 port_max; + struct rcu_head rcu; /* Must be last --ends in a flexible-array member. */ struct ip_tunnel_info info; }; @@ -100,7 +101,7 @@ struct geneve_dev { #endif struct list_head next; /* geneve's per namespace list */ struct gro_cells gro_cells; - struct geneve_config cfg; + struct geneve_config __rcu *cfg; }; struct geneve_sock { @@ -182,8 +183,10 @@ static struct geneve_dev *geneve_lookup(struct geneve_sock *gs, hash = geneve_net_vni_hash(vni); vni_list_head = &gs->vni_list[hash]; hlist_for_each_entry_rcu(node, vni_list_head, hlist) { - if (eq_tun_id_and_vni((u8 *)&node->geneve->cfg.info.key.tun_id, vni) && - addr == node->geneve->cfg.info.key.u.ipv4.dst) + const struct geneve_config *cfg = rcu_dereference(node->geneve->cfg); + + if (eq_tun_id_and_vni((u8 *)&cfg->info.key.tun_id, vni) && + addr == cfg->info.key.u.ipv4.dst) return node->geneve; } return NULL; @@ -201,8 +204,10 @@ static struct geneve_dev *geneve6_lookup(struct geneve_sock *gs, hash = geneve_net_vni_hash(vni); vni_list_head = &gs->vni_list[hash]; hlist_for_each_entry_rcu(node, vni_list_head, hlist) { - if (eq_tun_id_and_vni((u8 *)&node->geneve->cfg.info.key.tun_id, vni) && - ipv6_addr_equal(&addr6, &node->geneve->cfg.info.key.u.ipv6.dst)) + const struct geneve_config *cfg = rcu_dereference(node->geneve->cfg); + + if (eq_tun_id_and_vni((u8 *)&cfg->info.key.tun_id, vni) && + ipv6_addr_equal(&addr6, &cfg->info.key.u.ipv6.dst)) return node->geneve; } return NULL; @@ -386,11 +391,6 @@ static int geneve_init(struct net_device *dev) if (err) return err; - err = dst_cache_init(&geneve->cfg.info.dst_cache, GFP_KERNEL); - if (err) { - gro_cells_destroy(&geneve->gro_cells); - return err; - } netdev_lockdep_set_classes(dev); return 0; } @@ -399,7 +399,6 @@ static void geneve_uninit(struct net_device *dev) { struct geneve_dev *geneve = netdev_priv(dev); - dst_cache_destroy(&geneve->cfg.info.dst_cache); gro_cells_destroy(&geneve->gro_cells); } @@ -650,6 +649,7 @@ static int geneve_post_decap_hint(const struct sock *sk, struct sk_buff *skb, /* Callback from net/ipv4/udp.c to receive packets */ static int geneve_udp_encap_recv(struct sock *sk, struct sk_buff *skb) { + const struct geneve_config *cfg; struct genevehdr *geneveh; struct geneve_dev *geneve; struct geneve_sock *gs; @@ -675,8 +675,9 @@ static int geneve_udp_encap_recv(struct sock *sk, struct sk_buff *skb) inner_proto = geneveh->proto_type; - if (unlikely((!geneve->cfg.inner_proto_inherit && - inner_proto != htons(ETH_P_TEB)))) { + cfg = rcu_dereference(geneve->cfg); + if (unlikely(!cfg || (!cfg->inner_proto_inherit && + inner_proto != htons(ETH_P_TEB)))) { dev_dstats_rx_dropped(geneve->dev); goto drop; } @@ -1141,10 +1142,11 @@ static int geneve_sock_add(struct geneve_dev *geneve, static int geneve_open(struct net_device *dev) { struct geneve_dev *geneve = netdev_priv(dev); - const struct geneve_config *cfg = &geneve->cfg; + const struct geneve_config *cfg; bool ipv4, ipv6, dualstack; int ret = 0; + cfg = rtnl_dereference(geneve->cfg); dualstack = cfg->dualstack; ipv6 = cfg->info.mode & IP_TUNNEL_INFO_IPV6 || dualstack; ipv4 = !ipv6 || dualstack; @@ -1552,20 +1554,21 @@ static netdev_tx_t geneve_xmit(struct sk_buff *skb, struct net_device *dev) const struct geneve_config *cfg; int err; - cfg = &geneve->cfg; + rcu_read_lock(); + cfg = rcu_dereference(geneve->cfg); if (cfg->collect_md) { info = skb_tunnel_info(skb); if (unlikely(!info || !(info->mode & IP_TUNNEL_INFO_TX))) { netdev_dbg(dev, "no tunnel metadata\n"); dev_kfree_skb(skb); dev_dstats_tx_dropped(dev); + rcu_read_unlock(); return NETDEV_TX_OK; } } else { info = &cfg->info; } - rcu_read_lock(); #if IS_ENABLED(CONFIG_IPV6) if (info->mode & IP_TUNNEL_INFO_IPV6) err = geneve6_xmit_skb(skb, dev, geneve, cfg, info); @@ -1604,9 +1607,13 @@ static int geneve_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb) { struct ip_tunnel_info *info = skb_tunnel_info(skb); struct geneve_dev *geneve = netdev_priv(dev); - const struct geneve_config *cfg = &geneve->cfg; + const struct geneve_config *cfg; __be16 sport; + cfg = rcu_dereference(geneve->cfg); + if (unlikely(!cfg)) + return -ENODEV; + if (ip_tunnel_info_af(info) == AF_INET) { struct rtable *rt; struct geneve_sock *gs4 = rcu_dereference(geneve->sock4); @@ -1721,7 +1728,50 @@ static void geneve_offload_rx_ports(struct net_device *dev, bool push) } } +static struct geneve_config *geneve_config_alloc(const struct geneve_config *src) +{ + struct geneve_config *cfg; + int err; + + cfg = kmemdup(src, sizeof(*src), GFP_KERNEL); + if (!cfg) + return ERR_PTR(-ENOMEM); + + cfg->info.dst_cache.cache = NULL; + err = dst_cache_init(&cfg->info.dst_cache, GFP_KERNEL); + if (err) { + kfree(cfg); + return ERR_PTR(err); + } + + return cfg; +} + +static void geneve_config_free(struct geneve_config *cfg) +{ + if (cfg) { + dst_cache_destroy(&cfg->info.dst_cache); + kfree(cfg); + } +} + +static void geneve_config_free_rcu(struct rcu_head *head) +{ + struct geneve_config *cfg = container_of(head, struct geneve_config, rcu); + + geneve_config_free(cfg); +} + /* Initialize the device structure. */ +static void geneve_free_dev(struct net_device *dev) +{ + struct geneve_dev *geneve = netdev_priv(dev); + struct geneve_config *cfg = rcu_dereference_protected(geneve->cfg, 1); + + geneve_config_free(cfg); + RCU_INIT_POINTER(geneve->cfg, NULL); +} + static void geneve_setup(struct net_device *dev) { ether_setup(dev); @@ -1729,6 +1779,7 @@ static void geneve_setup(struct net_device *dev) dev->netdev_ops = &geneve_netdev_ops; dev->ethtool_ops = &geneve_ethtool_ops; dev->needs_free_netdev = true; + dev->priv_destructor = geneve_free_dev; SET_NETDEV_DEVTYPE(dev, &geneve_type); @@ -1890,15 +1941,17 @@ static struct geneve_dev *geneve_find_dev(struct geneve_net *gn, *tun_on_same_port = false; *tun_collect_md = false; list_for_each_entry(geneve, &gn->geneve_list, next) { - if (info->key.tp_dst == geneve->cfg.info.key.tp_dst && - (cfg->dualstack || geneve->cfg.dualstack || - geneve_saddr_conflict(info, &geneve->cfg.info))) { - *tun_collect_md |= geneve->cfg.collect_md; + const struct geneve_config *gcfg = rtnl_dereference(geneve->cfg); + + if (info->key.tp_dst == gcfg->info.key.tp_dst && + (cfg->dualstack || gcfg->dualstack || + geneve_saddr_conflict(info, &gcfg->info))) { + *tun_collect_md |= gcfg->collect_md; *tun_on_same_port = true; } - if (info->key.tun_id == geneve->cfg.info.key.tun_id && - info->key.tp_dst == geneve->cfg.info.key.tp_dst && - !memcmp(&info->key.u, &geneve->cfg.info.key.u, sizeof(info->key.u))) + if (info->key.tun_id == gcfg->info.key.tun_id && + info->key.tp_dst == gcfg->info.key.tp_dst && + !memcmp(&info->key.u, &gcfg->info.key.u, sizeof(info->key.u))) t = geneve; } return t; @@ -1934,6 +1987,7 @@ static int geneve_configure(struct net *net, struct net_device *dev, struct geneve_dev *t, *geneve = netdev_priv(dev); const struct ip_tunnel_info *info = &cfg->info; bool tun_collect_md, tun_on_same_port; + struct geneve_config *new_cfg; int err, encap_len; if (cfg->collect_md && !is_tnl_info_zero(info)) { @@ -1974,10 +2028,13 @@ static int geneve_configure(struct net *net, struct net_device *dev, } } - dst_cache_reset(&geneve->cfg.info.dst_cache); - memcpy(&geneve->cfg, cfg, sizeof(*cfg)); + new_cfg = geneve_config_alloc(cfg); + if (IS_ERR(new_cfg)) + return PTR_ERR(new_cfg); - if (geneve->cfg.inner_proto_inherit) { + rcu_assign_pointer(geneve->cfg, new_cfg); + + if (cfg->inner_proto_inherit) { dev->header_ops = NULL; dev->type = ARPHRD_NONE; dev->hard_header_len = 0; @@ -2335,81 +2392,45 @@ static int geneve_newlink(struct net_device *dev, return 0; } -/* Quiesces the geneve device data path for both TX and RX. - * - * On transmit geneve checks for non-NULL geneve_sock before it proceeds. - * So, if we set that socket to NULL under RCU and wait for synchronize_net() - * to complete for the existing set of in-flight packets to be transmitted, - * then we would have quiesced the transmit data path. All the future packets - * will get dropped until we unquiesce the data path. - * - * On receive geneve dereference the geneve_sock stashed in the socket. So, - * if we set that to NULL under RCU and wait for synchronize_net() to - * complete, then we would have quiesced the receive data path. +/* Update the device configuration under RTNL. + * We use RCU swap to update the configuration atomically, so the data path + * (both TX and RX) can continue running without interruption or packet loss. */ -static void geneve_quiesce(struct geneve_dev *geneve, struct geneve_sock **gs4, - struct geneve_sock **gs6) -{ - *gs4 = rtnl_dereference(geneve->sock4); - rcu_assign_pointer(geneve->sock4, NULL); - if (*gs4) - rcu_assign_sk_user_data((*gs4)->sk, NULL); -#if IS_ENABLED(CONFIG_IPV6) - *gs6 = rtnl_dereference(geneve->sock6); - rcu_assign_pointer(geneve->sock6, NULL); - if (*gs6) - rcu_assign_sk_user_data((*gs6)->sk, NULL); -#else - *gs6 = NULL; -#endif - synchronize_net(); -} - -/* Resumes the geneve device data path for both TX and RX. */ -static void geneve_unquiesce(struct geneve_dev *geneve, struct geneve_sock *gs4, - struct geneve_sock __maybe_unused *gs6) -{ - rcu_assign_pointer(geneve->sock4, gs4); - if (gs4) - rcu_assign_sk_user_data(gs4->sk, gs4); -#if IS_ENABLED(CONFIG_IPV6) - rcu_assign_pointer(geneve->sock6, gs6); - if (gs6) - rcu_assign_sk_user_data(gs6->sk, gs6); -#endif -} - static int geneve_changelink(struct net_device *dev, struct nlattr *tb[], struct nlattr *data[], struct netlink_ext_ack *extack) { struct geneve_dev *geneve = netdev_priv(dev); - struct geneve_sock *gs4, *gs6; - struct geneve_config cfg; + struct geneve_config *old_cfg = rtnl_dereference(geneve->cfg); + struct geneve_config *cfg; int err; /* If the geneve device is configured for metadata (or externally * controlled, for example, OVS), then nothing can be changed. */ - if (geneve->cfg.collect_md) + if (old_cfg->collect_md) return -EOPNOTSUPP; /* Start with the existing info. */ - memcpy(&cfg, &geneve->cfg, sizeof(cfg)); - err = geneve_nl2info(tb, data, extack, &cfg, true); + cfg = geneve_config_alloc(old_cfg); + if (IS_ERR(cfg)) + return PTR_ERR(cfg); + + err = geneve_nl2info(tb, data, extack, cfg, true); if (err) - return err; + goto err_free_cfg; - if (!geneve_dst_addr_equal(&geneve->cfg.info, &cfg.info)) { - dst_cache_reset(&cfg.info.dst_cache); - geneve_link_config(dev, &cfg.info, tb); - } + if (!geneve_dst_addr_equal(&old_cfg->info, &cfg->info)) + geneve_link_config(dev, &cfg->info, tb); - geneve_quiesce(geneve, &gs4, &gs6); - memcpy(&geneve->cfg, &cfg, sizeof(cfg)); - geneve_unquiesce(geneve, gs4, gs6); + rcu_assign_pointer(geneve->cfg, cfg); + call_rcu_hurry(&old_cfg->rcu, geneve_config_free_rcu); return 0; + +err_free_cfg: + geneve_config_free(cfg); + return err; } static void geneve_dellink(struct net_device *dev, struct list_head *head) @@ -2444,12 +2465,13 @@ static size_t geneve_get_size(const struct net_device *dev) static int geneve_fill_info(struct sk_buff *skb, const struct net_device *dev) { struct geneve_dev *geneve = netdev_priv(dev); - struct ip_tunnel_info *info = &geneve->cfg.info; - bool ttl_inherit = geneve->cfg.ttl_inherit; - bool metadata = geneve->cfg.collect_md; + struct geneve_config *cfg = rtnl_dereference(geneve->cfg); + struct ip_tunnel_info *info = &cfg->info; + bool ttl_inherit = cfg->ttl_inherit; + bool metadata = cfg->collect_md; struct ifla_geneve_port_range ports = { - .low = htons(geneve->cfg.port_min), - .high = htons(geneve->cfg.port_max), + .low = htons(cfg->port_min), + .high = htons(cfg->port_max), }; __u8 tmp_vni[3]; __u32 vni; @@ -2480,17 +2502,17 @@ static int geneve_fill_info(struct sk_buff *skb, const struct net_device *dev) #endif } - if (!geneve->cfg.dualstack) { + if (!cfg->dualstack) { if (ip_tunnel_info_af(info) == AF_INET) { if ((info->key.u.ipv4.src || - geneve->cfg.collect_md) && + metadata) && nla_put_in_addr(skb, IFLA_GENEVE_LOCAL, info->key.u.ipv4.src)) goto nla_put_failure; #if IS_ENABLED(CONFIG_IPV6) } else { if ((!ipv6_addr_any(&info->key.u.ipv6.src) || - geneve->cfg.collect_md) && + metadata) && nla_put_in6_addr(skb, IFLA_GENEVE_LOCAL6, &info->key.u.ipv6.src)) goto nla_put_failure; @@ -2503,7 +2525,7 @@ static int geneve_fill_info(struct sk_buff *skb, const struct net_device *dev) nla_put_be32(skb, IFLA_GENEVE_LABEL, info->key.label)) goto nla_put_failure; - if (nla_put_u8(skb, IFLA_GENEVE_DF, geneve->cfg.df)) + if (nla_put_u8(skb, IFLA_GENEVE_DF, cfg->df)) goto nla_put_failure; if (nla_put_be16(skb, IFLA_GENEVE_PORT, info->key.tp_dst)) @@ -2514,21 +2536,21 @@ static int geneve_fill_info(struct sk_buff *skb, const struct net_device *dev) #if IS_ENABLED(CONFIG_IPV6) if (nla_put_u8(skb, IFLA_GENEVE_UDP_ZERO_CSUM6_RX, - !geneve->cfg.use_udp6_rx_checksums)) + !cfg->use_udp6_rx_checksums)) goto nla_put_failure; #endif if (nla_put_u8(skb, IFLA_GENEVE_TTL_INHERIT, ttl_inherit)) goto nla_put_failure; - if (geneve->cfg.inner_proto_inherit && + if (cfg->inner_proto_inherit && nla_put_flag(skb, IFLA_GENEVE_INNER_PROTO_INHERIT)) goto nla_put_failure; if (nla_put(skb, IFLA_GENEVE_PORT_RANGE, sizeof(ports), &ports)) goto nla_put_failure; - if (geneve->cfg.gro_hint && + if (cfg->gro_hint && nla_put_flag(skb, IFLA_GENEVE_GRO_HINT)) goto nla_put_failure; @@ -2683,6 +2705,7 @@ static void __exit geneve_cleanup_module(void) rtnl_link_unregister(&geneve_link_ops); unregister_netdevice_notifier(&geneve_notifier_block); unregister_pernet_subsys(&geneve_net_ops); + rcu_barrier(); } module_exit(geneve_cleanup_module); From 5415c41a83789f02238a61cecd3f9125045d4306 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 7 Jul 2026 14:53:30 +0000 Subject: [PATCH 0392/1433] geneve: make geneve_fill_info() RTNL independent Now that geneve->cfg is an RCU-protected pointer, update geneve_fill_info() to read the configuration under RCU read lock instead of relying on RTNL. Also add const qualifiers to the dereferenced pointers where appropriate and fix local variable declaration ordering. Signed-off-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260707145331.3717941-4-edumazet@google.com Signed-off-by: Paolo Abeni --- drivers/net/geneve.c | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index f14e019f3f3f..17bd3543d587 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -2464,17 +2464,27 @@ static size_t geneve_get_size(const struct net_device *dev) static int geneve_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct geneve_dev *geneve = netdev_priv(dev); - struct geneve_config *cfg = rtnl_dereference(geneve->cfg); - struct ip_tunnel_info *info = &cfg->info; - bool ttl_inherit = cfg->ttl_inherit; - bool metadata = cfg->collect_md; - struct ifla_geneve_port_range ports = { - .low = htons(cfg->port_min), - .high = htons(cfg->port_max), - }; + const struct geneve_dev *geneve = netdev_priv(dev); + struct ifla_geneve_port_range ports; + const struct geneve_config *cfg; + const struct ip_tunnel_info *info; + bool ttl_inherit, metadata; __u8 tmp_vni[3]; __u32 vni; + int err = 0; + + rcu_read_lock(); + cfg = rcu_dereference(geneve->cfg); + if (!cfg) { + err = -ENODEV; + goto out; + } + + info = &cfg->info; + ttl_inherit = cfg->ttl_inherit; + metadata = cfg->collect_md; + ports.low = htons(cfg->port_min); + ports.high = htons(cfg->port_max); tunnel_id_to_vni(info->key.tun_id, tmp_vni); vni = (tmp_vni[0] << 16) | (tmp_vni[1] << 8) | tmp_vni[2]; @@ -2554,10 +2564,13 @@ static int geneve_fill_info(struct sk_buff *skb, const struct net_device *dev) nla_put_flag(skb, IFLA_GENEVE_GRO_HINT)) goto nla_put_failure; - return 0; +out: + rcu_read_unlock(); + return err; nla_put_failure: - return -EMSGSIZE; + err = -EMSGSIZE; + goto out; } static struct rtnl_link_ops geneve_link_ops __read_mostly = { From 80d8e1d428e898f792e3c87161cd3b522a2159a9 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Tue, 7 Jul 2026 19:04:48 +0200 Subject: [PATCH 0393/1433] net: sparx5: configure TAS port link speed On the TSN and RED variants of LAN969x and SparX-5i TAS (Time-Aware Shaper) is present in the silicon. Currently, the driver does not use configure it at all, which means that the TAS_PROFILE_CONFIG.LINK_SPEED[1] value is left at the default of 3 which means that its configured for 1 Gbps. So, running iperf between two 10G switch ports will result in only 940-ish Mbps while we should be getting around 9.3 Gbps. Correctly populating the TAS_PROFILE_CONFIG.LINK_SPEED[1] with the current port speed fixes this issue and we achieve around 9.4 Gbps between two 10G switch ports. So, port the TAS port link speed setting from the vendor BSP 6.18 kernel[2] [1] https://microchip-ung.github.io/lan969x-industrial_reginfo/reginfo_LAN969x-Industrial.html?select=hsch,tas_profile_cfg,tas_profile_config,link_speed [2] https://github.com/microchip-ung/linux/tree/bsp-6.18-2026 Signed-off-by: Robert Marko Link: https://patch.msgid.link/20260707170531.1129866-1-robert.marko@sartura.hr Signed-off-by: Paolo Abeni --- .../microchip/sparx5/lan969x/lan969x_regs.c | 3 ++ .../microchip/sparx5/sparx5_main_regs.h | 12 +++++ .../ethernet/microchip/sparx5/sparx5_port.c | 4 ++ .../ethernet/microchip/sparx5/sparx5_qos.c | 49 +++++++++++++++++++ .../ethernet/microchip/sparx5/sparx5_qos.h | 1 + .../ethernet/microchip/sparx5/sparx5_regs.c | 3 ++ .../ethernet/microchip/sparx5/sparx5_regs.h | 3 ++ 7 files changed, 75 insertions(+) diff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c index ace4ba21eec4..3fc2c006ba12 100644 --- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c +++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c @@ -95,6 +95,7 @@ const unsigned int lan969x_gaddr[GADDR_LAST] = { [GA_HSCH_SYSTEM] = 37384, [GA_HSCH_MMGT] = 36260, [GA_HSCH_TAS_CONFIG] = 37696, + [GA_HSCH_TAS_PROFILE_CFG] = 37712, [GA_PTP_PTP_CFG] = 512, [GA_PTP_PTP_TOD_DOMAINS] = 528, [GA_PTP_PHASE_DETECTOR_CTRL] = 628, @@ -129,6 +130,7 @@ const unsigned int lan969x_gcnt[GCNT_LAST] = { [GC_GCB_SIO_CTRL] = 1, [GC_HSCH_HSCH_CFG] = 1120, [GC_HSCH_HSCH_DWRR] = 32, + [GC_HSCH_TAS_PROFILE_CFG] = 30, [GC_PTP_PTP_PINS] = 8, [GC_PTP_PHASE_DETECTOR_CTRL] = 8, [GC_REW_PORT] = 35, @@ -144,6 +146,7 @@ const unsigned int lan969x_gsize[GSIZE_LAST] = { [GW_FDMA_FDMA] = 448, [GW_GCB_CHIP_REGS] = 180, [GW_HSCH_TAS_CONFIG] = 16, + [GW_HSCH_TAS_PROFILE_CFG] = 68, [GW_PTP_PHASE_DETECTOR_CTRL] = 12, [GW_QSYS_PAUSE_CFG] = 988, }; diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h index d9ef4ef137b8..d34467513648 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h @@ -5369,6 +5369,18 @@ extern const struct sparx5_regs *regs; #define HSCH_TAS_STATEMACHINE_CFG_REVISIT_DLY_GET(x)\ FIELD_GET(HSCH_TAS_STATEMACHINE_CFG_REVISIT_DLY, x) +/* HSCH:TAS_PROFILE_CFG:TAS_PROFILE_CONFIG */ +#define HSCH_TAS_PROFILE_CONFIG(g) \ + __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_PROFILE_CFG], g, \ + regs->gcnt[GC_HSCH_TAS_PROFILE_CFG], \ + regs->gsize[GW_HSCH_TAS_PROFILE_CFG], 32, 0, 1, 4) + +#define HSCH_TAS_PROFILE_CONFIG_LINK_SPEED GENMASK(10, 8) +#define HSCH_TAS_PROFILE_CONFIG_LINK_SPEED_SET(x)\ + FIELD_PREP(HSCH_TAS_PROFILE_CONFIG_LINK_SPEED, x) +#define HSCH_TAS_PROFILE_CONFIG_LINK_SPEED_GET(x)\ + FIELD_GET(HSCH_TAS_PROFILE_CONFIG_LINK_SPEED, x) + /* LAN969X ONLY */ /* HSIOWRAP:XMII_CFG:XMII_CFG */ #define HSIO_WRAP_XMII_CFG(g) \ diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_port.c b/drivers/net/ethernet/microchip/sparx5/sparx5_port.c index 62c49893de3c..ef06bed3a9cc 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_port.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_port.c @@ -11,6 +11,7 @@ #include "sparx5_main_regs.h" #include "sparx5_main.h" #include "sparx5_port.h" +#include "sparx5_qos.h" #define SPX5_ETYPE_TAG_C 0x8100 #define SPX5_ETYPE_TAG_S 0x88a8 @@ -1050,6 +1051,9 @@ int sparx5_port_config(struct sparx5 *sparx5, sparx5, QFWD_SWITCH_PORT_MODE(port->portno)); + /* Notify TAS about the speed. */ + sparx5_tas_speed(port, conf->speed); + /* Save the new values */ port->conf = *conf; diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_qos.c b/drivers/net/ethernet/microchip/sparx5/sparx5_qos.c index e580670f3992..972da8a71f5a 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_qos.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_qos.c @@ -9,6 +9,17 @@ #include "sparx5_main.h" #include "sparx5_qos.h" +enum sparx5_tas_link_speed { + TAS_SPEED_NO_GB, + TAS_SPEED_10, + TAS_SPEED_100, + TAS_SPEED_1000, + TAS_SPEED_2500, + TAS_SPEED_5000, + TAS_SPEED_10000, + TAS_SPEED_25000, +}; + /* Calculate new base_time based on cycle_time. * * The hardware requires a base_time that is always in the future. @@ -581,3 +592,41 @@ int sparx5_tc_ets_del(struct sparx5_port *port) return sparx5_dwrr_conf_set(port, &dwrr); } + +void sparx5_tas_speed(struct sparx5_port *port, int speed) +{ + struct sparx5 *sparx5 = port->sparx5; + u8 spd; + + switch (speed) { + case SPEED_10: + spd = TAS_SPEED_10; + break; + case SPEED_100: + spd = TAS_SPEED_100; + break; + case SPEED_1000: + spd = TAS_SPEED_1000; + break; + case SPEED_2500: + spd = TAS_SPEED_2500; + break; + case SPEED_5000: + spd = TAS_SPEED_5000; + break; + case SPEED_10000: + spd = TAS_SPEED_10000; + break; + case SPEED_25000: + spd = TAS_SPEED_25000; + break; + default: + netdev_err(port->ndev, "TAS: Unsupported speed: %d\n", speed); + return; + } + + spx5_rmw(HSCH_TAS_PROFILE_CONFIG_LINK_SPEED_SET(spd), + HSCH_TAS_PROFILE_CONFIG_LINK_SPEED, + sparx5, + HSCH_TAS_PROFILE_CONFIG(port->portno)); +} diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_qos.h b/drivers/net/ethernet/microchip/sparx5/sparx5_qos.h index 04f76f1e23f6..a92a699c551f 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_qos.h +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_qos.h @@ -60,6 +60,7 @@ struct sparx5_dwrr { }; int sparx5_qos_init(struct sparx5 *sparx5); +void sparx5_tas_speed(struct sparx5_port *port, int speed); /* Multi-Queue Priority */ int sparx5_tc_mqprio_add(struct net_device *ndev, u8 num_tc); diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c index 220e81b714d4..3863f954bd83 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c @@ -95,6 +95,7 @@ const unsigned int sparx5_gaddr[GADDR_LAST] = { [GA_HSCH_SYSTEM] = 184000, [GA_HSCH_MMGT] = 162368, [GA_HSCH_TAS_CONFIG] = 162384, + [GA_HSCH_TAS_PROFILE_CFG] = 188416, [GA_PTP_PTP_CFG] = 320, [GA_PTP_PTP_TOD_DOMAINS] = 336, [GA_PTP_PHASE_DETECTOR_CTRL] = 420, @@ -129,6 +130,7 @@ const unsigned int sparx5_gcnt[GCNT_LAST] = { [GC_GCB_SIO_CTRL] = 3, [GC_HSCH_HSCH_CFG] = 5040, [GC_HSCH_HSCH_DWRR] = 72, + [GC_HSCH_TAS_PROFILE_CFG] = 100, [GC_PTP_PTP_PINS] = 5, [GC_PTP_PHASE_DETECTOR_CTRL] = 5, [GC_REW_PORT] = 70, @@ -144,6 +146,7 @@ const unsigned int sparx5_gsize[GSIZE_LAST] = { [GW_FDMA_FDMA] = 428, [GW_GCB_CHIP_REGS] = 424, [GW_HSCH_TAS_CONFIG] = 12, + [GW_HSCH_TAS_PROFILE_CFG] = 64, [GW_PTP_PHASE_DETECTOR_CTRL] = 8, [GW_QSYS_PAUSE_CFG] = 1128, }; diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h index ea28130c2341..585589a31e90 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h @@ -104,6 +104,7 @@ enum sparx5_gaddr_enum { GA_HSCH_SYSTEM, GA_HSCH_MMGT, GA_HSCH_TAS_CONFIG, + GA_HSCH_TAS_PROFILE_CFG, GA_PTP_PTP_CFG, GA_PTP_PTP_TOD_DOMAINS, GA_PTP_PHASE_DETECTOR_CTRL, @@ -139,6 +140,7 @@ enum sparx5_gcnt_enum { GC_GCB_SIO_CTRL, GC_HSCH_HSCH_CFG, GC_HSCH_HSCH_DWRR, + GC_HSCH_TAS_PROFILE_CFG, GC_PTP_PTP_PINS, GC_PTP_PHASE_DETECTOR_CTRL, GC_REW_PORT, @@ -155,6 +157,7 @@ enum sparx5_gsize_enum { GW_FDMA_FDMA, GW_GCB_CHIP_REGS, GW_HSCH_TAS_CONFIG, + GW_HSCH_TAS_PROFILE_CFG, GW_PTP_PHASE_DETECTOR_CTRL, GW_QSYS_PAUSE_CFG, GSIZE_LAST, From 2aa955bc52e93228e2ae6be91806cfc707476deb Mon Sep 17 00:00:00 2001 From: Ciprian Regus Date: Wed, 8 Jul 2026 01:33:29 +0300 Subject: [PATCH 0394/1433] dt-bindings: net: Add ADIN1140 The ADIN1140 is a single port 10BASE-T1S Ethernet controller that includes both the MAC and a PHY in the same package. Reviewed-by: Conor Dooley Signed-off-by: Ciprian Regus Link: https://patch.msgid.link/20260708-adin1140-driver-v5-1-4aca7b51a58b@analog.com Signed-off-by: Paolo Abeni --- .../devicetree/bindings/net/adi,ad3306.yaml | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/adi,ad3306.yaml diff --git a/Documentation/devicetree/bindings/net/adi,ad3306.yaml b/Documentation/devicetree/bindings/net/adi,ad3306.yaml new file mode 100644 index 000000000000..785d05c995db --- /dev/null +++ b/Documentation/devicetree/bindings/net/adi,ad3306.yaml @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/adi,ad3306.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ADI ADIN1140 10BASE-T1S MAC-PHY + +maintainers: + - Ciprian Regus + +description: | + The ADIN1140 (also called AD3306) is a low power single port + 10BASE-T1S MAC-PHY. It integrates an Ethernet PHY with a MAC + and all the associated analog circuitry. + The device tries to implement the Open Alliance TC6 10BASE-T1x MAC-PHY + Serial Interface specification and is compliant with the + IEEE 802.3cg-2019 Ethernet standard for 10 Mbps single pair + Ethernet (SPE). The device has a 4-wire SPI interface for + communication between the MAC and host processor. + +allOf: + - $ref: /schemas/net/ethernet-controller.yaml# + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + oneOf: + - items: + - const: adi,adin1140 + - const: adi,ad3306 + - const: adi,ad3306 + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 25000000 + + interrupts: + maxItems: 1 + description: Interrupt from the MAC-PHY for receive data available + and error conditions + +required: + - compatible + - reg + - interrupts + - spi-max-frequency + +unevaluatedProperties: false + +examples: + - | + #include + + spi { + #address-cells = <1>; + #size-cells = <0>; + + ethernet@0 { + compatible = "adi,ad3306"; + reg = <0>; + spi-max-frequency = <23000000>; + + interrupt-parent = <&gpio>; + interrupts = <6 IRQ_TYPE_LEVEL_LOW>; + + local-mac-address = [ 00 11 22 33 44 55 ]; + }; + }; From 7d0e4c4b8c85d8ea2c77a90e1f7a7f74ce531e52 Mon Sep 17 00:00:00 2001 From: Ciprian Regus Date: Wed, 8 Jul 2026 01:33:30 +0300 Subject: [PATCH 0395/1433] net: ethernet: oa_tc6: Handle the OA TC6 SPI protected mode Implement the OA TC6 standard defined protected mode for control (register access) transactions. In addition to the current register access formats the oa_tc6 driver handles, 1's complement values of the data field are included (by both the host and the MACPHY) in the SPI transfer frames. This feature acts as an integrity check. Control write transactions look like this: |<- 32 bits ->|<--- data_size --->|<- 32 bits ->| MOSI: | ctrl header | reg write data | ignored | MISO: | (discard) | echoed ctrl hdr | echoed data | data_size (LEN = number of registers to read in a sequence): Unprotected: 32 x (LEN + 1) bits Protected: 2 x 32 x (LEN + 1) bits Control read transaction: |<- 32 bits ->|<--- 32 bits --> |<- data_size ->| MOSI: | ctrl header | ignored ... | MISO: | (discard) | echoed ctrl hdr | reg read data | data_size (LEN = number of registers to read in a sequence): Unprotected: 32 x (LEN + 1) bits Protected: 2 x 32 x (LEN + 1) bits Register data format ("reg write data" and "reg read data"): Unprotected: | W1 (normal) | W2 (normal) | ... | Wx (normal) | Protected: | W1 (normal) | W1 (complement) | ... | Wx (normal) | Wx (complement)| The protected mode state can be read from the bit 5 of CONFIG0 (0x4) register, and this setting is usually only configured during the MACPHY's reset (depending on the device it can be done by setting the state of a pin). We can read the protected mode configuration before any other register access and since the SPI transfer is initially sized for an unprotected read, the MACPHY's complement words are never clocked out and no checking is required. The data transactions (Ethernet frames) remain unchanged. Signed-off-by: Ciprian Regus Link: https://patch.msgid.link/20260708-adin1140-driver-v5-2-4aca7b51a58b@analog.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/oa_tc6.c | 93 ++++++++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 17 deletions(-) diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c index 0727d53345a3..8b9655883496 100644 --- a/drivers/net/ethernet/oa_tc6.c +++ b/drivers/net/ethernet/oa_tc6.c @@ -25,6 +25,7 @@ #define OA_TC6_REG_CONFIG0 0x0004 #define CONFIG0_SYNC BIT(15) #define CONFIG0_ZARFE_ENABLE BIT(12) +#define CONFIG0_PROTE BIT(5) /* Status Register #0 */ #define OA_TC6_REG_STATUS0 0x0008 @@ -90,14 +91,17 @@ #define OA_TC6_PHY_C45_AUTO_NEG_MMS5 5 /* MMD 7 */ #define OA_TC6_PHY_C45_POWER_UNIT_MMS6 6 /* MMD 13 */ +#define OA_TC6_CTRL_PROT_REPLY_SIZE 4 #define OA_TC6_CTRL_HEADER_SIZE 4 #define OA_TC6_CTRL_REG_VALUE_SIZE 4 #define OA_TC6_CTRL_IGNORED_SIZE 4 #define OA_TC6_CTRL_MAX_REGISTERS 128 -#define OA_TC6_CTRL_SPI_BUF_SIZE (OA_TC6_CTRL_HEADER_SIZE +\ - (OA_TC6_CTRL_MAX_REGISTERS *\ - OA_TC6_CTRL_REG_VALUE_SIZE) +\ - OA_TC6_CTRL_IGNORED_SIZE) +#define OA_TC6_CTRL_SPI_BUF_SIZE (OA_TC6_CTRL_HEADER_SIZE +\ + (OA_TC6_CTRL_MAX_REGISTERS *\ + (OA_TC6_CTRL_REG_VALUE_SIZE +\ + OA_TC6_CTRL_PROT_REPLY_SIZE)) +\ + OA_TC6_CTRL_IGNORED_SIZE) + #define OA_TC6_CHUNK_PAYLOAD_SIZE 64 #define OA_TC6_DATA_HEADER_SIZE 4 #define OA_TC6_CHUNK_SIZE (OA_TC6_DATA_HEADER_SIZE +\ @@ -130,6 +134,7 @@ struct oa_tc6 { bool rx_buf_overflow; bool int_flag; bool disable_traffic; + bool prot_ctrl; }; enum oa_tc6_header_type { @@ -213,25 +218,36 @@ static void oa_tc6_update_ctrl_write_data(struct oa_tc6 *tc6, u32 value[], { __be32 *tx_buf = tc6->spi_ctrl_tx_buf + OA_TC6_CTRL_HEADER_SIZE; - for (int i = 0; i < length; i++) + for (int i = 0; i < length; i++) { *tx_buf++ = cpu_to_be32(value[i]); + if (tc6->prot_ctrl) + *tx_buf++ = cpu_to_be32(~value[i]); + } } -static u16 oa_tc6_calculate_ctrl_buf_size(u8 length) +static u16 oa_tc6_calculate_ctrl_buf_size(u8 length, bool ctrl_prot) { + u32 reply_size = OA_TC6_CTRL_REG_VALUE_SIZE; + + if (ctrl_prot) + reply_size += OA_TC6_CTRL_PROT_REPLY_SIZE; + /* Control command consists 4 bytes header + 4 bytes register value for - * each register + 4 bytes ignored value. + * each register (+ 4 bytes for the register value complement in case + * protected mode is used) + 4 bytes ignored value. */ - return OA_TC6_CTRL_HEADER_SIZE + OA_TC6_CTRL_REG_VALUE_SIZE * length + + return OA_TC6_CTRL_HEADER_SIZE + reply_size * length + OA_TC6_CTRL_IGNORED_SIZE; } static void oa_tc6_prepare_ctrl_spi_buf(struct oa_tc6 *tc6, u32 address, u32 value[], u8 length, - enum oa_tc6_register_op reg_op) + enum oa_tc6_register_op reg_op, + u16 buf_size) { __be32 *tx_buf = tc6->spi_ctrl_tx_buf; + memset(tx_buf, 0, buf_size); *tx_buf = oa_tc6_prepare_ctrl_header(address, length, reg_op); if (reg_op == OA_TC6_CTRL_REG_WRITE) @@ -254,10 +270,12 @@ static int oa_tc6_check_ctrl_write_reply(struct oa_tc6 *tc6, u8 size) return 0; } -static int oa_tc6_check_ctrl_read_reply(struct oa_tc6 *tc6, u8 size) +static int oa_tc6_check_ctrl_read_reply(struct oa_tc6 *tc6, u8 length) { - u32 *rx_buf = tc6->spi_ctrl_rx_buf + OA_TC6_CTRL_IGNORED_SIZE; - u32 *tx_buf = tc6->spi_ctrl_tx_buf; + __be32 *rx_buf = tc6->spi_ctrl_rx_buf + OA_TC6_CTRL_IGNORED_SIZE; + __be32 *tx_buf = tc6->spi_ctrl_tx_buf; + u32 complement; + u32 reply; /* The echoed control read header must match with the one that was * transmitted. @@ -265,6 +283,20 @@ static int oa_tc6_check_ctrl_read_reply(struct oa_tc6 *tc6, u8 size) if (*tx_buf != *rx_buf) return -EPROTO; + if (tc6->prot_ctrl) { + /* Skip past the echoed header to the value/complement pairs */ + rx_buf += 1; + for (int i = 0; i < length; i++) { + reply = be32_to_cpu(rx_buf[0]); + complement = be32_to_cpu(rx_buf[1]); + + if (complement != ~reply) + return -EPROTO; + + rx_buf += 2; + } + } + return 0; } @@ -274,8 +306,13 @@ static void oa_tc6_copy_ctrl_read_data(struct oa_tc6 *tc6, u32 value[], __be32 *rx_buf = tc6->spi_ctrl_rx_buf + OA_TC6_CTRL_IGNORED_SIZE + OA_TC6_CTRL_HEADER_SIZE; - for (int i = 0; i < length; i++) + for (int i = 0; i < length; i++) { value[i] = be32_to_cpu(*rx_buf++); + + /* skip complement word */ + if (tc6->prot_ctrl) + rx_buf++; + } } static int oa_tc6_perform_ctrl(struct oa_tc6 *tc6, u32 address, u32 value[], @@ -284,10 +321,10 @@ static int oa_tc6_perform_ctrl(struct oa_tc6 *tc6, u32 address, u32 value[], u16 size; int ret; - /* Prepare control command and copy to SPI control buffer */ - oa_tc6_prepare_ctrl_spi_buf(tc6, address, value, length, reg_op); + size = oa_tc6_calculate_ctrl_buf_size(length, tc6->prot_ctrl); - size = oa_tc6_calculate_ctrl_buf_size(length); + /* Prepare control command and copy to SPI control buffer */ + oa_tc6_prepare_ctrl_spi_buf(tc6, address, value, length, reg_op, size); /* Perform SPI transfer */ ret = oa_tc6_spi_transfer(tc6, OA_TC6_CTRL_HEADER, size); @@ -302,7 +339,7 @@ static int oa_tc6_perform_ctrl(struct oa_tc6 *tc6, u32 address, u32 value[], return oa_tc6_check_ctrl_write_reply(tc6, size); /* Check echoed/received control read command reply for errors */ - ret = oa_tc6_check_ctrl_read_reply(tc6, size); + ret = oa_tc6_check_ctrl_read_reply(tc6, length); if (ret) return ret; @@ -1272,6 +1309,20 @@ netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb) } EXPORT_SYMBOL_GPL(oa_tc6_start_xmit); +static int oa_tc6_check_ctrl_protection(struct oa_tc6 *tc6) +{ + u32 regval; + int ret; + + ret = oa_tc6_read_register(tc6, OA_TC6_REG_CONFIG0, ®val); + if (ret) + return ret; + + tc6->prot_ctrl = FIELD_GET(CONFIG0_PROTE, regval); + + return 0; +} + /** * oa_tc6_init - allocates and initializes oa_tc6 structure. * @spi: device with which data will be exchanged. @@ -1324,6 +1375,14 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev) if (!tc6->spi_data_rx_buf) return NULL; + /* Check the PROTE bit status so that we can reset the device */ + ret = oa_tc6_check_ctrl_protection(tc6); + if (ret) { + dev_err(&tc6->spi->dev, + "Failed to check the protection mode: %d\n", ret); + return NULL; + } + ret = oa_tc6_sw_reset_macphy(tc6); if (ret) { dev_err(&tc6->spi->dev, From 1d030cdd52ee0042753327601156e7938f2455c1 Mon Sep 17 00:00:00 2001 From: Ciprian Regus Date: Wed, 8 Jul 2026 01:33:31 +0300 Subject: [PATCH 0396/1433] net: ethernet: oa_tc6: add OA_TC6_BROKEN_PHY quirk flag Some MAC-PHY devices need custom MDIO bus access functions to work around hardware issues. Add the OA_TC6_BROKEN_PHY quirk flag so drivers can opt in to skip oa_tc6's internal PHY init and manage the PHY themselves. When the flag is set, oa_tc6 skips MDIO bus registration, PHY discovery and PHY connection, leaving these to the driver. Drivers that do not set the flag retain the existing behavior. Update lan865x and the framework documentation accordingly. Signed-off-by: Ciprian Regus Link: https://patch.msgid.link/20260708-adin1140-driver-v5-3-4aca7b51a58b@analog.com Signed-off-by: Paolo Abeni --- Documentation/networking/oa-tc6-framework.rst | 3 ++- drivers/net/ethernet/microchip/lan865x/lan865x.c | 2 +- drivers/net/ethernet/oa_tc6.c | 14 +++++++++++++- include/linux/oa_tc6.h | 11 ++++++++++- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Documentation/networking/oa-tc6-framework.rst b/Documentation/networking/oa-tc6-framework.rst index fe2aabde923a..013824078cea 100644 --- a/Documentation/networking/oa-tc6-framework.rst +++ b/Documentation/networking/oa-tc6-framework.rst @@ -454,7 +454,8 @@ Device drivers API The include/linux/oa_tc6.h defines the following functions: .. c:function:: struct oa_tc6 *oa_tc6_init(struct spi_device *spi, \ - struct net_device *netdev) + struct net_device *netdev, \ + struct oa_tc6_quirks *quirks) Initialize OA TC6 lib. diff --git a/drivers/net/ethernet/microchip/lan865x/lan865x.c b/drivers/net/ethernet/microchip/lan865x/lan865x.c index 0277d9737369..26a2761332a5 100644 --- a/drivers/net/ethernet/microchip/lan865x/lan865x.c +++ b/drivers/net/ethernet/microchip/lan865x/lan865x.c @@ -346,7 +346,7 @@ static int lan865x_probe(struct spi_device *spi) spi_set_drvdata(spi, priv); INIT_WORK(&priv->multicast_work, lan865x_multicast_work_handler); - priv->tc6 = oa_tc6_init(spi, netdev); + priv->tc6 = oa_tc6_init(spi, netdev, NULL); if (!priv->tc6) { ret = -ENODEV; goto free_netdev; diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c index 8b9655883496..fa1359224535 100644 --- a/drivers/net/ethernet/oa_tc6.c +++ b/drivers/net/ethernet/oa_tc6.c @@ -135,6 +135,7 @@ struct oa_tc6 { bool int_flag; bool disable_traffic; bool prot_ctrl; + enum oa_tc6_quirk_flag quirk_flags; }; enum oa_tc6_header_type { @@ -581,6 +582,9 @@ static int oa_tc6_phy_init(struct oa_tc6 *tc6) { int ret; + if (tc6->quirk_flags & OA_TC6_BROKEN_PHY) + return 0; + ret = oa_tc6_check_phy_reg_direct_access_capability(tc6); if (ret) { netdev_err(tc6->netdev, @@ -617,6 +621,9 @@ static int oa_tc6_phy_init(struct oa_tc6 *tc6) static void oa_tc6_phy_exit(struct oa_tc6 *tc6) { + if (tc6->quirk_flags & OA_TC6_BROKEN_PHY) + return; + phy_disconnect(tc6->phydev); oa_tc6_mdiobus_unregister(tc6); } @@ -1327,11 +1334,13 @@ static int oa_tc6_check_ctrl_protection(struct oa_tc6 *tc6) * oa_tc6_init - allocates and initializes oa_tc6 structure. * @spi: device with which data will be exchanged. * @netdev: network device interface structure. + * @quirks: device specific modifiers for the OA TC6 protocol. * * Return: pointer reference to the oa_tc6 structure if the MAC-PHY * initialization is successful otherwise NULL. */ -struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev) +struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev, + struct oa_tc6_quirks *quirks) { struct oa_tc6 *tc6; int ret; @@ -1346,6 +1355,9 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev) mutex_init(&tc6->spi_ctrl_lock); spin_lock_init(&tc6->tx_skb_lock); + if (quirks) + tc6->quirk_flags = quirks->quirk_flags; + /* Set the SPI controller to pump at realtime priority */ tc6->spi->rt = true; if (spi_setup(tc6->spi) < 0) diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h index 15f58e3c56c7..62e3d89f80ed 100644 --- a/include/linux/oa_tc6.h +++ b/include/linux/oa_tc6.h @@ -12,7 +12,16 @@ struct oa_tc6; -struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev); +enum oa_tc6_quirk_flag { + OA_TC6_BROKEN_PHY = BIT(0), +}; + +struct oa_tc6_quirks { + enum oa_tc6_quirk_flag quirk_flags; +}; + +struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev, + struct oa_tc6_quirks *quirks); void oa_tc6_exit(struct oa_tc6 *tc6); int oa_tc6_write_register(struct oa_tc6 *tc6, u32 address, u32 value); int oa_tc6_write_registers(struct oa_tc6 *tc6, u32 address, u32 value[], From 87ac7ea2153c0e52aa24568226a90929ee5249c3 Mon Sep 17 00:00:00 2001 From: Ciprian Regus Date: Wed, 8 Jul 2026 01:33:32 +0300 Subject: [PATCH 0397/1433] net: ethernet: oa_tc6: Export the C45 access functions The C45 access functions can still be used by some Ethernet drivers which set the OA_TC6_BROKEN_PHY flag. Export them. Reviewed-by: Andrew Lunn Signed-off-by: Ciprian Regus Link: https://patch.msgid.link/20260708-adin1140-driver-v5-4-4aca7b51a58b@analog.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/oa_tc6.c | 10 ++++++---- include/linux/oa_tc6.h | 4 ++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c index fa1359224535..541f740dd516 100644 --- a/drivers/net/ethernet/oa_tc6.c +++ b/drivers/net/ethernet/oa_tc6.c @@ -500,8 +500,8 @@ static int oa_tc6_get_phy_c45_mms(int devnum) } } -static int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum, - int regnum) +int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum, + int regnum) { struct oa_tc6 *tc6 = bus->priv; u32 regval; @@ -517,9 +517,10 @@ static int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum, return regval; } +EXPORT_SYMBOL_GPL(oa_tc6_mdiobus_read_c45); -static int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum, - int regnum, u16 val) +int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum, + int regnum, u16 val) { struct oa_tc6 *tc6 = bus->priv; int ret; @@ -530,6 +531,7 @@ static int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum, return oa_tc6_write_register(tc6, (ret << 16) | regnum, val); } +EXPORT_SYMBOL_GPL(oa_tc6_mdiobus_write_c45); static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6) { diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h index 62e3d89f80ed..2660eefa3504 100644 --- a/include/linux/oa_tc6.h +++ b/include/linux/oa_tc6.h @@ -31,3 +31,7 @@ int oa_tc6_read_registers(struct oa_tc6 *tc6, u32 address, u32 value[], u8 length); netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb); int oa_tc6_zero_align_receive_frame_enable(struct oa_tc6 *tc6); +int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum, + int regnum); +int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum, + int regnum, u16 val); From 9210d402bdf54240b8aec9815b2f9aad360fcb42 Mon Sep 17 00:00:00 2001 From: Ciprian Regus Date: Wed, 8 Jul 2026 01:33:33 +0300 Subject: [PATCH 0398/1433] net: ethernet: oa_tc6: Export standard defined registers Move defines for standard Open Alliance TC6 register addresses and subfields in the oa_tc6's header. As such, other ethernet drivers that rely on oa_tc6 can use them directly. Reviewed-by: Andrew Lunn Signed-off-by: Ciprian Regus Link: https://patch.msgid.link/20260708-adin1140-driver-v5-5-4aca7b51a58b@analog.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/oa_tc6.c | 50 ----------------------------------- include/linux/oa_tc6.h | 50 +++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c index 541f740dd516..076895720655 100644 --- a/drivers/net/ethernet/oa_tc6.c +++ b/drivers/net/ethernet/oa_tc6.c @@ -12,47 +12,6 @@ #include #include -/* OPEN Alliance TC6 registers */ -/* Standard Capabilities Register */ -#define OA_TC6_REG_STDCAP 0x0002 -#define STDCAP_DIRECT_PHY_REG_ACCESS BIT(8) - -/* Reset Control and Status Register */ -#define OA_TC6_REG_RESET 0x0003 -#define RESET_SWRESET BIT(0) /* Software Reset */ - -/* Configuration Register #0 */ -#define OA_TC6_REG_CONFIG0 0x0004 -#define CONFIG0_SYNC BIT(15) -#define CONFIG0_ZARFE_ENABLE BIT(12) -#define CONFIG0_PROTE BIT(5) - -/* Status Register #0 */ -#define OA_TC6_REG_STATUS0 0x0008 -#define STATUS0_RESETC BIT(6) /* Reset Complete */ -#define STATUS0_HEADER_ERROR BIT(5) -#define STATUS0_LOSS_OF_FRAME_ERROR BIT(4) -#define STATUS0_RX_BUFFER_OVERFLOW_ERROR BIT(3) -#define STATUS0_TX_PROTOCOL_ERROR BIT(0) - -/* Buffer Status Register */ -#define OA_TC6_REG_BUFFER_STATUS 0x000B -#define BUFFER_STATUS_TX_CREDITS_AVAILABLE GENMASK(15, 8) -#define BUFFER_STATUS_RX_CHUNKS_AVAILABLE GENMASK(7, 0) - -/* Interrupt Mask Register #0 */ -#define OA_TC6_REG_INT_MASK0 0x000C -#define INT_MASK0_HEADER_ERR_MASK BIT(5) -#define INT_MASK0_LOSS_OF_FRAME_ERR_MASK BIT(4) -#define INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK BIT(3) -#define INT_MASK0_TX_PROTOCOL_ERR_MASK BIT(0) -#define INT_MASK0_ALL_INTERRUPTS (GENMASK(5, 0) | \ - GENMASK(12, 7)) - -/* PHY Clause 22 registers base address and mask */ -#define OA_TC6_PHY_STD_REG_ADDR_BASE 0xFF00 -#define OA_TC6_PHY_STD_REG_ADDR_MASK 0x1F - /* Control command header */ #define OA_TC6_CTRL_HEADER_DATA_NOT_CTRL BIT(31) #define OA_TC6_CTRL_HEADER_WRITE_NOT_READ BIT(29) @@ -82,15 +41,6 @@ #define OA_TC6_DATA_FOOTER_END_BYTE_OFFSET GENMASK(13, 8) #define OA_TC6_DATA_FOOTER_TX_CREDITS GENMASK(5, 1) -/* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in the - * OPEN Alliance specification. - */ -#define OA_TC6_PHY_C45_PCS_MMS2 2 /* MMD 3 */ -#define OA_TC6_PHY_C45_PMA_PMD_MMS3 3 /* MMD 1 */ -#define OA_TC6_PHY_C45_VS_PLCA_MMS4 4 /* MMD 31 */ -#define OA_TC6_PHY_C45_AUTO_NEG_MMS5 5 /* MMD 7 */ -#define OA_TC6_PHY_C45_POWER_UNIT_MMS6 6 /* MMD 13 */ - #define OA_TC6_CTRL_PROT_REPLY_SIZE 4 #define OA_TC6_CTRL_HEADER_SIZE 4 #define OA_TC6_CTRL_REG_VALUE_SIZE 4 diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h index 2660eefa3504..d99e0f79af84 100644 --- a/include/linux/oa_tc6.h +++ b/include/linux/oa_tc6.h @@ -10,6 +10,56 @@ #include #include +/* OPEN Alliance TC6 registers */ +/* Standard Capabilities Register */ +#define OA_TC6_REG_STDCAP 0x0002 +#define STDCAP_DIRECT_PHY_REG_ACCESS BIT(8) + +/* Reset Control and Status Register */ +#define OA_TC6_REG_RESET 0x0003 +#define RESET_SWRESET BIT(0) /* Software Reset */ + +/* Configuration Register #0 */ +#define OA_TC6_REG_CONFIG0 0x0004 +#define CONFIG0_SYNC BIT(15) +#define CONFIG0_ZARFE_ENABLE BIT(12) +#define CONFIG0_PROTE BIT(5) + +/* Status Register #0 */ +#define OA_TC6_REG_STATUS0 0x0008 +#define STATUS0_RESETC BIT(6) /* Reset Complete */ +#define STATUS0_HEADER_ERROR BIT(5) +#define STATUS0_LOSS_OF_FRAME_ERROR BIT(4) +#define STATUS0_RX_BUFFER_OVERFLOW_ERROR BIT(3) +#define STATUS0_TX_PROTOCOL_ERROR BIT(0) + +/* Buffer Status Register */ +#define OA_TC6_REG_BUFFER_STATUS 0x000B +#define BUFFER_STATUS_TX_CREDITS_AVAILABLE GENMASK(15, 8) +#define BUFFER_STATUS_RX_CHUNKS_AVAILABLE GENMASK(7, 0) + +/* Interrupt Mask Register #0 */ +#define OA_TC6_REG_INT_MASK0 0x000C +#define INT_MASK0_HEADER_ERR_MASK BIT(5) +#define INT_MASK0_LOSS_OF_FRAME_ERR_MASK BIT(4) +#define INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK BIT(3) +#define INT_MASK0_TX_PROTOCOL_ERR_MASK BIT(0) +#define INT_MASK0_ALL_INTERRUPTS (GENMASK(5, 0) | \ + GENMASK(12, 7)) + +/* PHY Clause 22 registers base address and mask */ +#define OA_TC6_PHY_STD_REG_ADDR_BASE 0xFF00 +#define OA_TC6_PHY_STD_REG_ADDR_MASK 0x1F + +/* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in the + * OPEN Alliance specification. + */ +#define OA_TC6_PHY_C45_PCS_MMS2 2 /* MMD 3 */ +#define OA_TC6_PHY_C45_PMA_PMD_MMS3 3 /* MMD 1 */ +#define OA_TC6_PHY_C45_VS_PLCA_MMS4 4 /* MMD 31 */ +#define OA_TC6_PHY_C45_AUTO_NEG_MMS5 5 /* MMD 7 */ +#define OA_TC6_PHY_C45_POWER_UNIT_MMS6 6 /* MMD 13 */ + struct oa_tc6; enum oa_tc6_quirk_flag { From 31bc75f17c1f5ff989fa896aae3e4e411d9b0b7a Mon Sep 17 00:00:00 2001 From: Ciprian Regus Date: Wed, 8 Jul 2026 01:33:34 +0300 Subject: [PATCH 0399/1433] net: ethernet: oa_tc6: Add the OA_TC6_ prefix to standard registers The OA TC6 standard registers are currently exported in a header file. Add the OA_TC6_ prefix to the register address and subfield mask macros to avoid future naming conflicts. Reviewed-by: Andrew Lunn Signed-off-by: Ciprian Regus Link: https://patch.msgid.link/20260708-adin1140-driver-v5-6-4aca7b51a58b@analog.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/oa_tc6.c | 37 ++++++++++++++++---------------- include/linux/oa_tc6.h | 40 +++++++++++++++++------------------ 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c index 076895720655..3c19233fb38f 100644 --- a/drivers/net/ethernet/oa_tc6.c +++ b/drivers/net/ethernet/oa_tc6.c @@ -396,7 +396,7 @@ static int oa_tc6_check_phy_reg_direct_access_capability(struct oa_tc6 *tc6) if (ret) return ret; - if (!(regval & STDCAP_DIRECT_PHY_REG_ACCESS)) + if (!(regval & OA_TC6_STDCAP_DIRECT_PHY_REG_ACCESS)) return -ENODEV; return 0; @@ -597,7 +597,7 @@ static int oa_tc6_read_status0(struct oa_tc6 *tc6) static int oa_tc6_sw_reset_macphy(struct oa_tc6 *tc6) { - u32 regval = RESET_SWRESET; + u32 regval = OA_TC6_RESET_SWRESET; int ret; ret = oa_tc6_write_register(tc6, OA_TC6_REG_RESET, regval); @@ -606,7 +606,7 @@ static int oa_tc6_sw_reset_macphy(struct oa_tc6 *tc6) /* Poll for soft reset complete for every 1ms until 1s timeout */ ret = readx_poll_timeout(oa_tc6_read_status0, tc6, regval, - regval & STATUS0_RESETC, + regval & OA_TC6_STATUS0_RESETC, STATUS0_RESETC_POLL_DELAY, STATUS0_RESETC_POLL_TIMEOUT); if (ret) @@ -625,10 +625,10 @@ static int oa_tc6_unmask_macphy_error_interrupts(struct oa_tc6 *tc6) if (ret) return ret; - regval &= ~(INT_MASK0_TX_PROTOCOL_ERR_MASK | - INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK | - INT_MASK0_LOSS_OF_FRAME_ERR_MASK | - INT_MASK0_HEADER_ERR_MASK); + regval &= ~(OA_TC6_INT_MASK0_TX_PROTOCOL_ERR_MASK | + OA_TC6_INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK | + OA_TC6_INT_MASK0_LOSS_OF_FRAME_ERR_MASK | + OA_TC6_INT_MASK0_HEADER_ERR_MASK); return oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, regval); } @@ -643,7 +643,7 @@ static int oa_tc6_enable_data_transfer(struct oa_tc6 *tc6) return ret; /* Enable configuration synchronization for data transfer */ - value |= CONFIG0_SYNC; + value |= OA_TC6_CONFIG0_SYNC; return oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, value); } @@ -688,7 +688,7 @@ static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6) */ static void oa_tc6_disable_traffic(struct oa_tc6 *tc6) { - u32 regval = INT_MASK0_ALL_INTERRUPTS; + u32 regval = OA_TC6_INT_MASK0_ALL_INTERRUPTS; tc6->disable_traffic = true; oa_tc6_free_pending_skbs(tc6); @@ -718,25 +718,25 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6) return ret; } - if (FIELD_GET(STATUS0_RX_BUFFER_OVERFLOW_ERROR, value)) { + if (FIELD_GET(OA_TC6_STATUS0_RX_BUFFER_OVERFLOW_ERROR, value)) { tc6->rx_buf_overflow = true; oa_tc6_cleanup_ongoing_rx_skb(tc6); net_err_ratelimited("%s: Receive buffer overflow error\n", tc6->netdev->name); return -EAGAIN; } - if (FIELD_GET(STATUS0_TX_PROTOCOL_ERROR, value)) { + if (FIELD_GET(OA_TC6_STATUS0_TX_PROTOCOL_ERROR, value)) { netdev_err(tc6->netdev, "Transmit protocol error\n"); return -ENODEV; } /* TODO: Currently loss of frame and header errors are treated as * non-recoverable errors. They will be handled in the next version. */ - if (FIELD_GET(STATUS0_LOSS_OF_FRAME_ERROR, value)) { + if (FIELD_GET(OA_TC6_STATUS0_LOSS_OF_FRAME_ERROR, value)) { netdev_err(tc6->netdev, "Loss of frame error\n"); return -ENODEV; } - if (FIELD_GET(STATUS0_HEADER_ERROR, value)) { + if (FIELD_GET(OA_TC6_STATUS0_HEADER_ERROR, value)) { netdev_err(tc6->netdev, "Header error\n"); return -ENODEV; } @@ -1183,9 +1183,10 @@ static int oa_tc6_update_buffer_status_from_register(struct oa_tc6 *tc6) if (ret) return ret; - tc6->tx_credits = FIELD_GET(BUFFER_STATUS_TX_CREDITS_AVAILABLE, value); - tc6->rx_chunks_available = FIELD_GET(BUFFER_STATUS_RX_CHUNKS_AVAILABLE, - value); + tc6->tx_credits = FIELD_GET(OA_TC6_BUFFER_STATUS_TX_CREDITS_AVAILABLE, + value); + tc6->rx_chunks_available = + FIELD_GET(OA_TC6_BUFFER_STATUS_RX_CHUNKS_AVAILABLE, value); return 0; } @@ -1229,7 +1230,7 @@ int oa_tc6_zero_align_receive_frame_enable(struct oa_tc6 *tc6) return ret; /* Set Zero-Align Receive Frame Enable */ - regval |= CONFIG0_ZARFE_ENABLE; + regval |= OA_TC6_CONFIG0_ZARFE_ENABLE; return oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, regval); } @@ -1277,7 +1278,7 @@ static int oa_tc6_check_ctrl_protection(struct oa_tc6 *tc6) if (ret) return ret; - tc6->prot_ctrl = FIELD_GET(CONFIG0_PROTE, regval); + tc6->prot_ctrl = FIELD_GET(OA_TC6_CONFIG0_PROTE, regval); return 0; } diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h index d99e0f79af84..84b3e5176a53 100644 --- a/include/linux/oa_tc6.h +++ b/include/linux/oa_tc6.h @@ -13,39 +13,39 @@ /* OPEN Alliance TC6 registers */ /* Standard Capabilities Register */ #define OA_TC6_REG_STDCAP 0x0002 -#define STDCAP_DIRECT_PHY_REG_ACCESS BIT(8) +#define OA_TC6_STDCAP_DIRECT_PHY_REG_ACCESS BIT(8) /* Reset Control and Status Register */ #define OA_TC6_REG_RESET 0x0003 -#define RESET_SWRESET BIT(0) /* Software Reset */ +#define OA_TC6_RESET_SWRESET BIT(0) /* Software Reset */ /* Configuration Register #0 */ #define OA_TC6_REG_CONFIG0 0x0004 -#define CONFIG0_SYNC BIT(15) -#define CONFIG0_ZARFE_ENABLE BIT(12) -#define CONFIG0_PROTE BIT(5) +#define OA_TC6_CONFIG0_SYNC BIT(15) +#define OA_TC6_CONFIG0_ZARFE_ENABLE BIT(12) +#define OA_TC6_CONFIG0_PROTE BIT(5) /* Status Register #0 */ #define OA_TC6_REG_STATUS0 0x0008 -#define STATUS0_RESETC BIT(6) /* Reset Complete */ -#define STATUS0_HEADER_ERROR BIT(5) -#define STATUS0_LOSS_OF_FRAME_ERROR BIT(4) -#define STATUS0_RX_BUFFER_OVERFLOW_ERROR BIT(3) -#define STATUS0_TX_PROTOCOL_ERROR BIT(0) +#define OA_TC6_STATUS0_RESETC BIT(6) /* Reset Complete */ +#define OA_TC6_STATUS0_HEADER_ERROR BIT(5) +#define OA_TC6_STATUS0_LOSS_OF_FRAME_ERROR BIT(4) +#define OA_TC6_STATUS0_RX_BUFFER_OVERFLOW_ERROR BIT(3) +#define OA_TC6_STATUS0_TX_PROTOCOL_ERROR BIT(0) /* Buffer Status Register */ -#define OA_TC6_REG_BUFFER_STATUS 0x000B -#define BUFFER_STATUS_TX_CREDITS_AVAILABLE GENMASK(15, 8) -#define BUFFER_STATUS_RX_CHUNKS_AVAILABLE GENMASK(7, 0) +#define OA_TC6_REG_BUFFER_STATUS 0x000B +#define OA_TC6_BUFFER_STATUS_TX_CREDITS_AVAILABLE GENMASK(15, 8) +#define OA_TC6_BUFFER_STATUS_RX_CHUNKS_AVAILABLE GENMASK(7, 0) /* Interrupt Mask Register #0 */ -#define OA_TC6_REG_INT_MASK0 0x000C -#define INT_MASK0_HEADER_ERR_MASK BIT(5) -#define INT_MASK0_LOSS_OF_FRAME_ERR_MASK BIT(4) -#define INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK BIT(3) -#define INT_MASK0_TX_PROTOCOL_ERR_MASK BIT(0) -#define INT_MASK0_ALL_INTERRUPTS (GENMASK(5, 0) | \ - GENMASK(12, 7)) +#define OA_TC6_REG_INT_MASK0 0x000C +#define OA_TC6_INT_MASK0_HEADER_ERR_MASK BIT(5) +#define OA_TC6_INT_MASK0_LOSS_OF_FRAME_ERR_MASK BIT(4) +#define OA_TC6_INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK BIT(3) +#define OA_TC6_INT_MASK0_TX_PROTOCOL_ERR_MASK BIT(0) +#define OA_TC6_INT_MASK0_ALL_INTERRUPTS (GENMASK(5, 0) | \ + GENMASK(12, 7)) /* PHY Clause 22 registers base address and mask */ #define OA_TC6_PHY_STD_REG_ADDR_BASE 0xFF00 From 6ad250179486d718dde90d815fc77df4de7d1dc4 Mon Sep 17 00:00:00 2001 From: Ciprian Regus Date: Wed, 8 Jul 2026 01:33:35 +0300 Subject: [PATCH 0400/1433] net: ethernet: oa_tc6: Add read_mms/write_mms register access functions The Open Alliance TC6 standard defines multiple memory maps for the MAC-PHY's register space. These are used to separate standard, vendor and PHY MMD specific registers. Define register access functions that allow the caller to specify the MMS. Signed-off-by: Ciprian Regus Link: https://patch.msgid.link/20260708-adin1140-driver-v5-7-4aca7b51a58b@analog.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/oa_tc6.c | 44 +++++++++++++++++++++++++++++++++++ include/linux/oa_tc6.h | 4 ++++ 2 files changed, 48 insertions(+) diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c index 3c19233fb38f..955148d3cefc 100644 --- a/drivers/net/ethernet/oa_tc6.c +++ b/drivers/net/ethernet/oa_tc6.c @@ -62,6 +62,8 @@ #define STATUS0_RESETC_POLL_DELAY 1000 #define STATUS0_RESETC_POLL_TIMEOUT 1000000 +#define OA_TC6_REG_MMS_MASK GENMASK(19, 16) + /* Internal structure for MAC-PHY drivers */ struct oa_tc6 { struct net_device *netdev; @@ -343,6 +345,27 @@ int oa_tc6_read_register(struct oa_tc6 *tc6, u32 address, u32 *value) } EXPORT_SYMBOL_GPL(oa_tc6_read_register); +/** + * oa_tc6_read_register_mms - function for reading a MAC-PHY register in a + * specified memory map. + * @tc6: oa_tc6 struct. + * @mms: Memory map selector for the register. + * @address: register address of the MAC-PHY to be read. + * @value: value read from the @address register address of the MAC-PHY. + * + * Return: 0 on success or a negative error code on failure. + */ +int oa_tc6_read_register_mms(struct oa_tc6 *tc6, u8 mms, u16 address, + u32 *value) +{ + u32 mms_reg; + + mms_reg = FIELD_PREP(OA_TC6_REG_MMS_MASK, mms) | address; + + return oa_tc6_read_registers(tc6, mms_reg, value, 1); +} +EXPORT_SYMBOL_GPL(oa_tc6_read_register_mms); + /** * oa_tc6_write_registers - function for writing multiple consecutive registers. * @tc6: oa_tc6 struct. @@ -387,6 +410,27 @@ int oa_tc6_write_register(struct oa_tc6 *tc6, u32 address, u32 value) } EXPORT_SYMBOL_GPL(oa_tc6_write_register); +/** + * oa_tc6_write_register_mms - function for writing a MAC-PHY register in a + * specified memory map. + * @tc6: oa_tc6 struct. + * @mms: Memory map selector for the register. + * @address: register address of the MAC-PHY to be written. + * @value: value to be written in the @address register address of the MAC-PHY. + * + * Return: 0 on success or a negative error code on failure. + */ +int oa_tc6_write_register_mms(struct oa_tc6 *tc6, u8 mms, u16 address, + u32 value) +{ + u32 mms_reg; + + mms_reg = FIELD_PREP(OA_TC6_REG_MMS_MASK, mms) | address; + + return oa_tc6_write_registers(tc6, mms_reg, &value, 1); +} +EXPORT_SYMBOL_GPL(oa_tc6_write_register_mms); + static int oa_tc6_check_phy_reg_direct_access_capability(struct oa_tc6 *tc6) { u32 regval; diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h index 84b3e5176a53..701e8930d80d 100644 --- a/include/linux/oa_tc6.h +++ b/include/linux/oa_tc6.h @@ -74,9 +74,13 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev, struct oa_tc6_quirks *quirks); void oa_tc6_exit(struct oa_tc6 *tc6); int oa_tc6_write_register(struct oa_tc6 *tc6, u32 address, u32 value); +int oa_tc6_write_register_mms(struct oa_tc6 *tc6, u8 mms, u16 address, + u32 value); int oa_tc6_write_registers(struct oa_tc6 *tc6, u32 address, u32 value[], u8 length); int oa_tc6_read_register(struct oa_tc6 *tc6, u32 address, u32 *value); +int oa_tc6_read_register_mms(struct oa_tc6 *tc6, u8 mms, u16 address, + u32 *value); int oa_tc6_read_registers(struct oa_tc6 *tc6, u32 address, u32 value[], u8 length); netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb); From 92ec8d69ddb0a75cd416f571236915d09fcb9bf6 Mon Sep 17 00:00:00 2001 From: Ciprian Regus Date: Wed, 8 Jul 2026 01:33:36 +0300 Subject: [PATCH 0401/1433] net: ethernet: oa_tc6: Use the read_mms/write_mms functions for C45 Accessing PHY MMD devices requires control transactions to registers in a memory map other than 0. Replace the current formatting of the register addresses with the oa_tc6_{read,write}_register_mms() functions. While we're here, introduce the mms variable to store the memory map returned by oa_tc6_get_phy_c45_mms() instead of ret, in order to improve the code readability. Reviewed-by: Andrew Lunn Signed-off-by: Ciprian Regus Link: https://patch.msgid.link/20260708-adin1140-driver-v5-8-4aca7b51a58b@analog.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/oa_tc6.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c index 955148d3cefc..417c15d1ff42 100644 --- a/drivers/net/ethernet/oa_tc6.c +++ b/drivers/net/ethernet/oa_tc6.c @@ -499,13 +499,14 @@ int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum, { struct oa_tc6 *tc6 = bus->priv; u32 regval; + int mms; int ret; - ret = oa_tc6_get_phy_c45_mms(devnum); - if (ret < 0) - return ret; + mms = oa_tc6_get_phy_c45_mms(devnum); + if (mms < 0) + return mms; - ret = oa_tc6_read_register(tc6, (ret << 16) | regnum, ®val); + ret = oa_tc6_read_register_mms(tc6, mms, regnum, ®val); if (ret) return ret; @@ -517,13 +518,13 @@ int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum, int regnum, u16 val) { struct oa_tc6 *tc6 = bus->priv; - int ret; + int mms; - ret = oa_tc6_get_phy_c45_mms(devnum); - if (ret < 0) - return ret; + mms = oa_tc6_get_phy_c45_mms(devnum); + if (mms < 0) + return mms; - return oa_tc6_write_register(tc6, (ret << 16) | regnum, val); + return oa_tc6_write_register_mms(tc6, mms, regnum, val); } EXPORT_SYMBOL_GPL(oa_tc6_mdiobus_write_c45); From 638b41f770ade0dfc45f7f6f25e1cf75a9c89808 Mon Sep 17 00:00:00 2001 From: Ciprian Regus Date: Wed, 8 Jul 2026 01:33:37 +0300 Subject: [PATCH 0402/1433] net: ethernet: oa_tc6: Add new register address defines Add macro defines for the CONFIG2 register and the MMS1 memory map. Signed-off-by: Ciprian Regus Link: https://patch.msgid.link/20260708-adin1140-driver-v5-9-4aca7b51a58b@analog.com Signed-off-by: Paolo Abeni --- include/linux/oa_tc6.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h index 701e8930d80d..27f652d4920b 100644 --- a/include/linux/oa_tc6.h +++ b/include/linux/oa_tc6.h @@ -25,6 +25,9 @@ #define OA_TC6_CONFIG0_ZARFE_ENABLE BIT(12) #define OA_TC6_CONFIG0_PROTE BIT(5) +/* Configuration Register #2 */ +#define OA_TC6_REG_CONFIG2 0x0006 + /* Status Register #0 */ #define OA_TC6_REG_STATUS0 0x0008 #define OA_TC6_STATUS0_RESETC BIT(6) /* Reset Complete */ @@ -51,9 +54,10 @@ #define OA_TC6_PHY_STD_REG_ADDR_BASE 0xFF00 #define OA_TC6_PHY_STD_REG_ADDR_MASK 0x1F -/* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in the +/* Memory map selector (MMS) values as per table 6 in the * OPEN Alliance specification. */ +#define OA_TC6_MAC_MMS1 1 #define OA_TC6_PHY_C45_PCS_MMS2 2 /* MMD 3 */ #define OA_TC6_PHY_C45_PMA_PMD_MMS3 3 /* MMD 1 */ #define OA_TC6_PHY_C45_VS_PLCA_MMS4 4 /* MMD 31 */ From aa63217916e1818a28b711384a9340d965ad073f Mon Sep 17 00:00:00 2001 From: Ciprian Regus Date: Wed, 8 Jul 2026 01:33:38 +0300 Subject: [PATCH 0403/1433] net: phy: add generic helpers for direct C45 MMD access Some PHYs support direct C45 register access but not C22 indirect MMD access (registers 0xD and 0xE). When discovered via C22, phylib routes MMD access through the indirect path, which won't work on these devices. Add genphy_read_mmd_c45() and genphy_write_mmd_c45() as read_mmd/ write_mmd callbacks that bypass the C22 indirect path and use the bus C45 accessors directly. Reviewed-by: Andrew Lunn Signed-off-by: Ciprian Regus Link: https://patch.msgid.link/20260708-adin1140-driver-v5-10-4aca7b51a58b@analog.com Signed-off-by: Paolo Abeni --- drivers/net/phy/phy_device.c | 25 +++++++++++++++++++++++++ include/linux/phy.h | 3 +++ 2 files changed, 28 insertions(+) diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 0615228459ef..94b2e85e00a3 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -2770,6 +2770,31 @@ int genphy_read_abilities(struct phy_device *phydev) } EXPORT_SYMBOL(genphy_read_abilities); +/* Some PHYs support direct C45 register access but not C22 indirect + * MMD access (registers 13 and 14). When discovered via C22, phylib + * routes MMD access through the indirect path, which won't work on + * these devices. These helpers bypass indirect access and use the bus + * C45 accessors directly. + */ +int genphy_read_mmd_c45(struct phy_device *phydev, int devnum, u16 regnum) +{ + struct mii_bus *bus = phydev->mdio.bus; + int addr = phydev->mdio.addr; + + return __mdiobus_c45_read(bus, addr, devnum, regnum); +} +EXPORT_SYMBOL(genphy_read_mmd_c45); + +int genphy_write_mmd_c45(struct phy_device *phydev, int devnum, u16 regnum, + u16 val) +{ + struct mii_bus *bus = phydev->mdio.bus; + int addr = phydev->mdio.addr; + + return __mdiobus_c45_write(bus, addr, devnum, regnum, val); +} +EXPORT_SYMBOL(genphy_write_mmd_c45); + /* This is used for the phy device which doesn't support the MMD extended * register access, but it does have side effect when we are trying to access * the MMD register via indirect method. diff --git a/include/linux/phy.h b/include/linux/phy.h index beff1d6fcc7c..11092c3175b3 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -2302,6 +2302,9 @@ static inline int genphy_no_config_intr(struct phy_device *phydev) { return 0; } +int genphy_read_mmd_c45(struct phy_device *phydev, int devnum, u16 regnum); +int genphy_write_mmd_c45(struct phy_device *phydev, int devnum, u16 regnum, + u16 val); int genphy_read_mmd_unsupported(struct phy_device *phdev, int devad, u16 regnum); int genphy_write_mmd_unsupported(struct phy_device *phdev, int devnum, From 85032df227f9e7b7d6a74269968edad891d80ef9 Mon Sep 17 00:00:00 2001 From: Ciprian Regus Date: Wed, 8 Jul 2026 01:33:39 +0300 Subject: [PATCH 0404/1433] net: phy: microchip-t1s: use generic C45 MMD access helpers Replace the driver specific lan865x_phy_read_mmd() and lan865x_phy_write_mmd() with the shared genphy_read_mmd_c45() and genphy_write_mmd_c45() helpers. No functional change. Reviewed-by: Andrew Lunn Signed-off-by: Ciprian Regus Link: https://patch.msgid.link/20260708-adin1140-driver-v5-11-4aca7b51a58b@analog.com Signed-off-by: Paolo Abeni --- drivers/net/phy/microchip_t1s.c | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/drivers/net/phy/microchip_t1s.c b/drivers/net/phy/microchip_t1s.c index e601d56b2507..73c23d311d72 100644 --- a/drivers/net/phy/microchip_t1s.c +++ b/drivers/net/phy/microchip_t1s.c @@ -506,34 +506,6 @@ static int lan86xx_read_status(struct phy_device *phydev) return 0; } -/* OPEN Alliance 10BASE-T1x compliance MAC-PHYs will have both C22 and - * C45 registers space. If the PHY is discovered via C22 bus protocol it assumes - * it uses C22 protocol and always uses C22 registers indirect access to access - * C45 registers. This is because, we don't have a clean separation between - * C22/C45 register space and C22/C45 MDIO bus protocols. Resulting, PHY C45 - * registers direct access can't be used which can save multiple SPI bus access. - * To support this feature, set .read_mmd/.write_mmd in the PHY driver to call - * .read_c45/.write_c45 in the OPEN Alliance framework - * drivers/net/ethernet/oa_tc6.c - */ -static int lan865x_phy_read_mmd(struct phy_device *phydev, int devnum, - u16 regnum) -{ - struct mii_bus *bus = phydev->mdio.bus; - int addr = phydev->mdio.addr; - - return __mdiobus_c45_read(bus, addr, devnum, regnum); -} - -static int lan865x_phy_write_mmd(struct phy_device *phydev, int devnum, - u16 regnum, u16 val) -{ - struct mii_bus *bus = phydev->mdio.bus; - int addr = phydev->mdio.addr; - - return __mdiobus_c45_write(bus, addr, devnum, regnum, val); -} - static struct phy_driver microchip_t1s_driver[] = { { PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVB1), @@ -584,8 +556,8 @@ static struct phy_driver microchip_t1s_driver[] = { .features = PHY_BASIC_T1S_P2MP_FEATURES, .config_init = lan865x_revb_config_init, .read_status = lan86xx_read_status, - .read_mmd = lan865x_phy_read_mmd, - .write_mmd = lan865x_phy_write_mmd, + .read_mmd = genphy_read_mmd_c45, + .write_mmd = genphy_write_mmd_c45, .get_plca_cfg = genphy_c45_plca_get_cfg, .set_plca_cfg = lan86xx_plca_set_cfg, .get_plca_status = genphy_c45_plca_get_status, From 0feaf415b7822b27ff60c2711fd345a0414a80f0 Mon Sep 17 00:00:00 2001 From: Ciprian Regus Date: Wed, 8 Jul 2026 01:33:40 +0300 Subject: [PATCH 0405/1433] net: phy: Add support for the ADIN1140 PHY Add a driver for the ADIN1140's internal 10BASE-T1S PHY. The device doesn't implement autonegotiation, so the link is always reported as being up. The device implements both C22 and C45 MDIO access methods, but can only be discovered over C22, since the C45 MMD devices lack the MDIO_DEVID1 and MDIO_DEVID2 registers. The indirect C45 over C22 feature is not supported. Reviewed-by: Andrew Lunn Signed-off-by: Ciprian Regus Link: https://patch.msgid.link/20260708-adin1140-driver-v5-12-4aca7b51a58b@analog.com Signed-off-by: Paolo Abeni --- MAINTAINERS | 7 ++++ drivers/net/phy/Kconfig | 6 +++ drivers/net/phy/Makefile | 1 + drivers/net/phy/adin1140-phy.c | 72 ++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+) create mode 100644 drivers/net/phy/adin1140-phy.c diff --git a/MAINTAINERS b/MAINTAINERS index 00e7b26e0a23..7a57275ac4ba 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1900,6 +1900,13 @@ S: Supported W: https://ez.analog.com/linux-software-drivers F: drivers/dma/dma-axi-dmac.c +ANALOG DEVICES INC ETHERNET PHY DRIVERS +M: Ciprian Regus +L: netdev@vger.kernel.org +S: Maintained +W: https://ez.analog.com/linux-software-drivers +F: drivers/net/phy/adin1140-phy.c + ANALOG DEVICES INC IIO DRIVERS M: Nuno Sá M: Michael Hennerich diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 099f25dceabb..a29d3fed8a05 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -136,6 +136,12 @@ config ADIN1100_PHY Currently supports the: - ADIN1100 - Robust,Industrial, Low Power 10BASE-T1L Ethernet PHY +config ADIN1140_PHY + tristate "Analog Devices ADIN1140 10BASE-T1S PHY" + help + Adds support for the Analog Devices, Inc. ADIN1140's internal + 10BASE-T1S PHY. + config AMCC_QT2025_PHY tristate "AMCC QT2025 PHY" depends on RUST_PHYLIB_ABSTRACTIONS diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index de660ae94945..e23df5e836e9 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -29,6 +29,7 @@ obj-y += $(sfp-obj-y) $(sfp-obj-m) obj-$(CONFIG_ADIN_PHY) += adin.o obj-$(CONFIG_ADIN1100_PHY) += adin1100.o +obj-$(CONFIG_ADIN1140_PHY) += adin1140-phy.o obj-$(CONFIG_AIR_AN8801_PHY) += air_an8801.o obj-$(CONFIG_AIR_EN8811H_PHY) += air_en8811h.o obj-$(CONFIG_AIR_NET_PHYLIB) += air_phy_lib.o diff --git a/drivers/net/phy/adin1140-phy.c b/drivers/net/phy/adin1140-phy.c new file mode 100644 index 000000000000..d35da4ad680d --- /dev/null +++ b/drivers/net/phy/adin1140-phy.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Analog Devices, Inc. ADIN1140 10BASE-T1S PHY + * + * Copyright 2026 Analog Devices Inc. + */ + +#include +#include +#include + +#define ADIN1140_PHY_ID 0x0283be00 + +#define ADIN1140_PCS_CTRL 0x08f3 +#define ADIN1140_PCS_CTRL_LOOPBACK BIT(14) + +static int adin1140_config_aneg(struct phy_device *phydev) +{ + /* phylib tries to clear BIT(12) in MDIO_CTRL1, since AN is disabled. + * However, on the ADIN1140, that field is non-standard, being used + * to control the reset status of the PHY (thus it needs to remain set). + */ + return 0; +} + +static int adin1140_loopback(struct phy_device *phydev, bool enable, int speed) +{ + if (enable && speed) + return -EOPNOTSUPP; + + return phy_modify_mmd(phydev, MDIO_MMD_PCS, ADIN1140_PCS_CTRL, + ADIN1140_PCS_CTRL_LOOPBACK, + enable ? ADIN1140_PCS_CTRL_LOOPBACK : 0); +} + +static int adin1140_read_status(struct phy_device *phydev) +{ + phydev->link = 1; + phydev->duplex = DUPLEX_HALF; + phydev->speed = SPEED_10; + phydev->autoneg = AUTONEG_DISABLE; + + return 0; +} + +static struct phy_driver adin1140_driver[] = { + { + PHY_ID_MATCH_EXACT(ADIN1140_PHY_ID), + .name = "ADIN1140_PHY", + .features = PHY_BASIC_T1S_P2MP_FEATURES, + .read_status = adin1140_read_status, + .config_aneg = adin1140_config_aneg, + .set_loopback = adin1140_loopback, + .read_mmd = genphy_read_mmd_c45, + .write_mmd = genphy_write_mmd_c45, + .get_plca_cfg = genphy_c45_plca_get_cfg, + .set_plca_cfg = genphy_c45_plca_set_cfg, + .get_plca_status = genphy_c45_plca_get_status, + }, +}; +module_phy_driver(adin1140_driver); + +static const struct mdio_device_id __maybe_unused adin1140_tbl[] = { + { PHY_ID_MATCH_EXACT(ADIN1140_PHY_ID) }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, adin1140_tbl); + +MODULE_DESCRIPTION("Analog Devices, Inc. ADIN1140 10BASE-T1S PHY"); +MODULE_AUTHOR("Ciprian Regus "); +MODULE_LICENSE("GPL"); From 20e69e671070ab0b712b34a0e8977e8a402aa5eb Mon Sep 17 00:00:00 2001 From: Ciprian Regus Date: Wed, 8 Jul 2026 01:33:41 +0300 Subject: [PATCH 0406/1433] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY Add a driver for ADIN1140. The device is a 10BASE-T1S MAC-PHY (integrated in the same package) that connects to a CPU over an SPI bus, and implements the Open Alliance TC6 protocol for control and frame transfers. As such, this driver relies on oa_tc6 for the communication with the device. The device has an alternative name (AD3306), so the driver can be probed using one of the two compatible strings. For control transactions, ADIN1140 only implements the protected mode. The driver has a custom implementation for the mii_bus access methods as a workaround for hardware issues: 1. The OA TC6 standard defines the direct and indirect access modes for MDIO transactions. The ADIN1140 incorrectly advertises indirect mode only (supported capabilities register - 0x2, bit 9), while actually implementing just the direct mode. We cannot rely on the CAP register to choose an access method (which oa_tc6 does by default, even though it only implements the direct mode), so the driver has to use its own. 2. The ADIN1140 cannot access the C22 register space of the internal PHY, while the PHY is busy receiving frames. If that happens, the CONFIG0 and CONFIG2 registers of the MAC will get corrupted and the data transfer will stop. Those two registers configure settings for the transfer protocol between the MAC and host, so the value for some of their subfields shouldn't be changed while the netdev is up. Since we know the PHY is internal, the MAC driver can implement a custom mii_bus, which can intercept C22 accesses. Most of the registers mapped in the 0x0 - 0x3 range (the only ones the PHY offers) are read only, and their value can be read from somewhere else (e.g the PHYID 1 & 2 have the same value as 0x1 in the MAC memory map). For the fields that are R/W (loopback and AN/reset) in the control register, the PHY driver already implements the set_loopback() and config_aneg() functions. The C22 write function of the driver is a no-op and is used to protect against the ioctl MDIO access path. C45 accesses do not cause this issue, so we can properly implement them. Signed-off-by: Ciprian Regus Link: https://patch.msgid.link/20260708-adin1140-driver-v5-13-4aca7b51a58b@analog.com Signed-off-by: Paolo Abeni --- MAINTAINERS | 8 + drivers/net/ethernet/adi/Kconfig | 12 + drivers/net/ethernet/adi/Makefile | 1 + drivers/net/ethernet/adi/adin1140.c | 791 ++++++++++++++++++++++++++++ 4 files changed, 812 insertions(+) create mode 100644 drivers/net/ethernet/adi/adin1140.c diff --git a/MAINTAINERS b/MAINTAINERS index 7a57275ac4ba..6940aa3d498b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1900,6 +1900,14 @@ S: Supported W: https://ez.analog.com/linux-software-drivers F: drivers/dma/dma-axi-dmac.c +ANALOG DEVICES INC ETHERNET DRIVERS +M: Ciprian Regus +L: netdev@vger.kernel.org +S: Maintained +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/net/adi,ad3306.yaml +F: drivers/net/ethernet/adi/adin1140.c + ANALOG DEVICES INC ETHERNET PHY DRIVERS M: Ciprian Regus L: netdev@vger.kernel.org diff --git a/drivers/net/ethernet/adi/Kconfig b/drivers/net/ethernet/adi/Kconfig index 760a9a60bc15..bdb8ff7d15da 100644 --- a/drivers/net/ethernet/adi/Kconfig +++ b/drivers/net/ethernet/adi/Kconfig @@ -26,4 +26,16 @@ config ADIN1110 Say yes here to build support for Analog Devices ADIN1110 Low Power 10BASE-T1L Ethernet MAC-PHY. +config ADIN1140 + tristate "Analog Devices ADIN1140 MAC-PHY" + depends on SPI + select ADIN1140_PHY + select OA_TC6 + help + Say yes here to build support for Analog Devices, Inc. ADIN1140 + 10BASE-T1S Ethernet MAC-PHY. + + To compile this driver as a module, choose M here. The module will be + called adin1140. + endif # NET_VENDOR_ADI diff --git a/drivers/net/ethernet/adi/Makefile b/drivers/net/ethernet/adi/Makefile index d0383d94303c..0390ca8ccc49 100644 --- a/drivers/net/ethernet/adi/Makefile +++ b/drivers/net/ethernet/adi/Makefile @@ -4,3 +4,4 @@ # obj-$(CONFIG_ADIN1110) += adin1110.o +obj-$(CONFIG_ADIN1140) += adin1140.o diff --git a/drivers/net/ethernet/adi/adin1140.c b/drivers/net/ethernet/adi/adin1140.c new file mode 100644 index 000000000000..93710baca151 --- /dev/null +++ b/drivers/net/ethernet/adi/adin1140.c @@ -0,0 +1,791 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Analog Devices, Inc. ADIN1140 10BASE-T1S MAC-PHY + * + * Copyright 2026 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define ADIN1140_MAC_CONFIG2_STAT_CLR_RD BIT(6) +#define ADIN1140_MAC_CONFIG2_FWD_UNK2HOST BIT(2) + +#define ADIN1140_MAC_P1_LOOP_ADDR_REG 0xC4 + +#define ADIN1140_MAC_ADDR_FILT_UPR_REG 0x50 +#define ADIN1140_MAC_ADDR_FILT_APPLY2PORT1 BIT(30) +#define ADIN1140_MAC_ADDR_FILT_TO_HOST BIT(16) + +#define ADIN1140_MAC_ADDR_FILT_LWR_REG 0x51 + +#define ADIN1140_MAC_ADDR_MASK_UPR_REG 0x70 +#define ADIN1140_MAC_ADDR_MASK_LWR_REG 0x71 + +#define ADIN1140_MAC_FILT_MC_SLOT 0U +#define ADIN1140_MAC_FILT_BC_SLOT 1U +#define ADIN1140_MAC_FILT_UC_SLOT 2U +#define ADIN1140_MAC_FILT_MAX_SLOT 16U +#define ADIN1140_MAC_FILT_MASK_LIMIT 2U + +#define ADIN1140_MAC_RX_FRAME_CNT 0xA1 +#define ADIN1140_MAC_RX_BC_FRAME_CNT 0xA2 +#define ADIN1140_MAC_RX_MC_FRAME_CNT 0xA3 +#define ADIN1140_MAC_RX_CRC_ERR_CNT 0xA5 +#define ADIN1140_MAC_RX_ALIGN_ERR_CNT 0xA6 +#define ADIN1140_MAC_RX_PREAMBLE_ERR_CNT 0xA7 +#define ADIN1140_MAC_RX_SHORT_ERR_CNT 0xA8 +#define ADIN1140_MAC_RX_LONG_ERR_CNT 0xA9 +#define ADIN1140_MAC_RX_PHY_ERR_CNT 0xAA +#define ADIN1140_MAC_RX_DRP_FULL_CNT 0xAB +#define ADIN1140_MAC_RX_DRP_FILTER_CNT 0xAD +#define ADIN1140_MAC_RX_IFG_ERR_CNT 0xAE +#define ADIN1140_MAC_TX_FRAME_CNT 0xB1 +#define ADIN1140_MAC_TX_BC_FRAME_CNT 0xB2 +#define ADIN1140_MAC_TX_MC_FRAME_CNT 0xB3 +#define ADIN1140_MAC_TX_SINGLE_COL_CNT 0xB5 +#define ADIN1140_MAC_TX_MULTI_COL_CNT 0xB6 +#define ADIN1140_MAC_TX_DEFERRED_CNT 0xB7 +#define ADIN1140_MAC_TX_LATE_COL_CNT 0xB8 +#define ADIN1140_MAC_TX_EXCESS_COL_CNT 0xB9 +#define ADIN1140_MAC_TX_UNDERRUN_CNT 0xBA + +/* ADIN1140_MAC_FILT_MAX_SLOT - 3 (multicast, broadcast and unicast + * reserved slots) + */ +#define ADIN1140_MAC_FILT_AVAIL 13U + +#define ADIN1140_PHY_CTRL_DEFAULT 0x1000 +#define ADIN1140_PHY_STATUS_DEFAULT 0x082D +#define ADIN1140_PHY_ID1 0x0283 +#define ADIN1140_PHY_ID2 0xBE00 + +#define ADIN1140_STATS_CHECK_DELAY (3 * HZ) + +enum adin1140_statistics_entry { + rx_frames, + rx_bc_frames, + rx_mc_frames, + rx_crc_errors, + rx_align_errors, + rx_preamble_errors, + rx_short_frame_errors, + rx_long_frame_errors, + rx_phy_errors, + rx_fifo_full_dropped, + rx_addr_filter_dropped, + rx_ifg_errors, + tx_frames, + tx_bc_frames, + tx_mc_frames, + tx_single_collision, + tx_multi_collision, + tx_deferred, + tx_late_collision, + tx_excess_collision, + tx_underrun, + ADIN1140_STATS_CNT, +}; + +struct adin1140_statistics_reg { + const char *name; + enum adin1140_statistics_entry idx; +}; + +struct adin1140_priv { + struct net_device *netdev; + struct oa_tc6 *tc6; + struct mii_bus *mdiobus; + struct phy_device *phydev; + struct delayed_work stats_work; + + /* Protects stats[] from concurrent updates in adin1140_stats_work + * and reads in the get_stats functions + */ + spinlock_t stat_lock; + u64 stats[ADIN1140_STATS_CNT]; +}; + +static const u32 adin1140_stat_regs[] = { + [rx_frames] = ADIN1140_MAC_RX_FRAME_CNT, + [rx_bc_frames] = ADIN1140_MAC_RX_BC_FRAME_CNT, + [rx_mc_frames] = ADIN1140_MAC_RX_MC_FRAME_CNT, + [rx_crc_errors] = ADIN1140_MAC_RX_CRC_ERR_CNT, + [rx_align_errors] = ADIN1140_MAC_RX_ALIGN_ERR_CNT, + [rx_preamble_errors] = ADIN1140_MAC_RX_PREAMBLE_ERR_CNT, + [rx_short_frame_errors] = ADIN1140_MAC_RX_SHORT_ERR_CNT, + [rx_long_frame_errors] = ADIN1140_MAC_RX_LONG_ERR_CNT, + [rx_phy_errors] = ADIN1140_MAC_RX_PHY_ERR_CNT, + [rx_fifo_full_dropped] = ADIN1140_MAC_RX_DRP_FULL_CNT, + [rx_addr_filter_dropped] = ADIN1140_MAC_RX_DRP_FILTER_CNT, + [rx_ifg_errors] = ADIN1140_MAC_RX_IFG_ERR_CNT, + [tx_frames] = ADIN1140_MAC_TX_FRAME_CNT, + [tx_bc_frames] = ADIN1140_MAC_TX_BC_FRAME_CNT, + [tx_mc_frames] = ADIN1140_MAC_TX_MC_FRAME_CNT, + [tx_single_collision] = ADIN1140_MAC_TX_SINGLE_COL_CNT, + [tx_multi_collision] = ADIN1140_MAC_TX_MULTI_COL_CNT, + [tx_deferred] = ADIN1140_MAC_TX_DEFERRED_CNT, + [tx_late_collision] = ADIN1140_MAC_TX_LATE_COL_CNT, + [tx_excess_collision] = ADIN1140_MAC_TX_EXCESS_COL_CNT, + [tx_underrun] = ADIN1140_MAC_TX_UNDERRUN_CNT, +}; + +static const struct adin1140_statistics_reg adin1140_stats[] = { + {.name = "rx_preamble_errors", .idx = rx_preamble_errors}, + {.name = "rx_ifg_errors", .idx = rx_ifg_errors}, +}; + +static int adin1140_mac_filter_set(struct adin1140_priv *priv, + const u8 *addr, const u8 *mask, + u8 slot) +{ + u32 reg_address; + u32 val; + int ret; + + if (slot >= ADIN1140_MAC_FILT_MAX_SLOT) + return -ENOSPC; + + reg_address = ADIN1140_MAC_ADDR_FILT_UPR_REG + 2 * slot; + + ret = oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + reg_address, + get_unaligned_be16(&addr[0]) | + ADIN1140_MAC_ADDR_FILT_APPLY2PORT1 | + ADIN1140_MAC_ADDR_FILT_TO_HOST); + if (ret) + return ret; + + reg_address = ADIN1140_MAC_ADDR_FILT_LWR_REG + 2 * slot; + ret = oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + reg_address, + get_unaligned_be32(&addr[2])); + if (ret) + return ret; + + /* Only the first 2 destination MAC filter slots support masking. + * For the other entries, the destination address in the received + * frame must match exactly. + */ + if (slot >= ADIN1140_MAC_FILT_MASK_LIMIT) + return 0; + + val = get_unaligned_be16(&mask[0]); + reg_address = ADIN1140_MAC_ADDR_MASK_UPR_REG + (2 * slot); + + ret = oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + reg_address, val); + if (ret) + return ret; + + val = get_unaligned_be32(&mask[2]); + reg_address = ADIN1140_MAC_ADDR_MASK_LWR_REG + (2 * slot); + + return oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + reg_address, val); +} + +static int adin1140_mac_filter_clear(struct adin1140_priv *priv, u8 slot) +{ + u8 mask[ETH_ALEN]; + u8 addr[ETH_ALEN]; + + memset(mask, 0xFF, ETH_ALEN); + memset(addr, 0x0, ETH_ALEN); + + return adin1140_mac_filter_set(priv, addr, mask, slot); +} + +static int adin1140_filter_unicast(struct adin1140_priv *priv) +{ + /* Only the first 2 filter slots support masking, so no unicast + * address will ever need a mask. The first slots are used for the + * all multicast and broadcast filter. + */ + return adin1140_mac_filter_set(priv, priv->netdev->dev_addr, NULL, + ADIN1140_MAC_FILT_UC_SLOT); +} + +static int adin1140_filter_all_multicast(struct adin1140_priv *priv, bool en) +{ + u8 multicast_addr[ETH_ALEN] = {1, 0, 0, 0, 0, 0}; + + if (en) + return adin1140_mac_filter_set(priv, multicast_addr, + multicast_addr, + ADIN1140_MAC_FILT_MC_SLOT); + + return adin1140_mac_filter_clear(priv, ADIN1140_MAC_FILT_MC_SLOT); +} + +static int adin1140_filter_broadcast(struct adin1140_priv *priv, bool enabled) +{ + u8 mask[ETH_ALEN]; + + if (enabled) { + memset(mask, 0xFF, ETH_ALEN); + return adin1140_mac_filter_set(priv, mask, mask, + ADIN1140_MAC_FILT_BC_SLOT); + } + + return adin1140_mac_filter_clear(priv, ADIN1140_MAC_FILT_BC_SLOT); +} + +static int adin1140_default_filter_config(struct adin1140_priv *priv) +{ + int ret; + + ret = adin1140_filter_broadcast(priv, true); + if (ret) + return ret; + + return adin1140_filter_unicast(priv); +} + +static int adin1140_promiscuous_mode(struct adin1140_priv *priv, bool enabled) +{ + int ret; + u32 val; + + ret = oa_tc6_read_register(priv->tc6, OA_TC6_REG_CONFIG2, &val); + if (ret) + return ret; + + if (enabled) + val |= ADIN1140_MAC_CONFIG2_FWD_UNK2HOST; + else + val &= ~ADIN1140_MAC_CONFIG2_FWD_UNK2HOST; + + return oa_tc6_write_register(priv->tc6, OA_TC6_REG_CONFIG2, val); +} + +static int adin1140_rx_mode(struct net_device *dev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) +{ + struct adin1140_priv *priv = netdev_priv(dev); + struct netdev_hw_addr *ha; + bool all_multi, promisc; + u32 mac_addrs; + u8 slot, i; + int ret; + + /* The ADIN1140 has 16 dest MAC address filter slots: + * 0 - reserved for all multicast filter. + * 1 - reserved for broadcast filter. + * 2 - reserved for the device's own unicast MAC. + * 3 -> 15 - available for other unicast/multicast filters. + */ + + mac_addrs = netdev_hw_addr_list_count(uc); + all_multi = false; + promisc = false; + + if (priv->netdev->flags & IFF_PROMISC) + promisc = true; + else if (priv->netdev->flags & IFF_ALLMULTI) + all_multi = true; + else + mac_addrs += netdev_hw_addr_list_count(mc); + + if (mac_addrs > ADIN1140_MAC_FILT_AVAIL) { + /* The filter table is full. Enable promisc mode. */ + promisc = true; + } + + ret = adin1140_promiscuous_mode(priv, promisc); + if (ret) + return ret; + + ret = adin1140_filter_all_multicast(priv, all_multi); + if (ret) + return ret; + + slot = ADIN1140_MAC_FILT_UC_SLOT + 1; + if (!promisc) { + netdev_hw_addr_list_for_each(ha, uc) { + ret = adin1140_mac_filter_set(priv, ha->addr, NULL, + slot); + if (ret) + return ret; + + slot++; + } + + if (!all_multi) { + netdev_hw_addr_list_for_each(ha, mc) { + ret = adin1140_mac_filter_set(priv, ha->addr, + NULL, slot); + if (ret) + return ret; + + slot++; + } + } + } + + for (i = slot; i < ADIN1140_MAC_FILT_MAX_SLOT; i++) { + ret = adin1140_mac_filter_clear(priv, i); + if (ret) + return ret; + } + + return 0; +} + +static void adin1140_stats_work(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct adin1140_priv *priv; + u32 reg_val; + int ret; + u32 i; + + priv = container_of(dwork, struct adin1140_priv, stats_work); + + for (i = 0; i < ARRAY_SIZE(adin1140_stat_regs); i++) { + ret = oa_tc6_read_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + adin1140_stat_regs[i], + ®_val); + if (ret) + goto out; + + scoped_guard(spinlock, &priv->stat_lock) + priv->stats[i] += reg_val; + } + +out: + schedule_delayed_work(dwork, ADIN1140_STATS_CHECK_DELAY); +} + +static int adin1140_configure(struct adin1140_priv *priv) +{ + u32 reg_val; + int ret; + + ret = oa_tc6_zero_align_receive_frame_enable(priv->tc6); + if (ret) + return ret; + + /* Disable MAC loopback */ + ret = oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + ADIN1140_MAC_P1_LOOP_ADDR_REG, 0x0); + if (ret) + return ret; + + ret = oa_tc6_read_register(priv->tc6, OA_TC6_REG_CONFIG2, ®_val); + if (ret) + return ret; + + reg_val |= ADIN1140_MAC_CONFIG2_STAT_CLR_RD; + + ret = oa_tc6_write_register(priv->tc6, OA_TC6_REG_CONFIG2, reg_val); + if (ret) + return ret; + + return adin1140_default_filter_config(priv); +} + +static int adin1140_open(struct net_device *netdev) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + + schedule_delayed_work(&priv->stats_work, ADIN1140_STATS_CHECK_DELAY); + + phy_start(netdev->phydev); + netif_start_queue(netdev); + + return 0; +} + +static int adin1140_close(struct net_device *netdev) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + + cancel_delayed_work_sync(&priv->stats_work); + + netif_stop_queue(netdev); + phy_stop(netdev->phydev); + + return 0; +} + +static netdev_tx_t adin1140_start_xmit(struct sk_buff *skb, + struct net_device *netdev) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + + /* The MAC doesn't automatically pad the frame to a 60 byte minimum + * size in case the host sends a shorter skb, so we have to do it in + * the driver. The FCS will be added by the MAC. + */ + if (skb_put_padto(skb, ETH_ZLEN)) + return NETDEV_TX_OK; + + return oa_tc6_start_xmit(priv->tc6, skb); +} + +static int adin1140_set_mac_address(struct net_device *netdev, void *addr) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + struct sockaddr *address = addr; + u8 mask[ETH_ALEN]; + int ret; + + ret = eth_prepare_mac_addr_change(netdev, addr); + if (ret < 0) + return ret; + + if (ether_addr_equal(address->sa_data, netdev->dev_addr)) + return 0; + + memset(mask, 0xFF, ETH_ALEN); + ret = adin1140_mac_filter_set(priv, address->sa_data, mask, + ADIN1140_MAC_FILT_UC_SLOT); + if (ret) + return ret; + + eth_commit_mac_addr_change(netdev, addr); + + return 0; +} + +static void __adin1140_ndo_get_stats64(struct adin1140_priv *priv, + struct rtnl_link_stats64 *storage) +{ + storage->rx_errors = priv->stats[rx_crc_errors] + + priv->stats[rx_align_errors] + + priv->stats[rx_preamble_errors] + + priv->stats[rx_short_frame_errors] + + priv->stats[rx_long_frame_errors] + + priv->stats[rx_phy_errors] + + priv->stats[rx_ifg_errors]; + + storage->tx_errors = priv->stats[tx_excess_collision] + + priv->stats[tx_underrun]; + + storage->rx_dropped = priv->stats[rx_fifo_full_dropped] + + priv->stats[rx_addr_filter_dropped]; + + storage->multicast = priv->stats[rx_mc_frames]; + + storage->collisions = priv->stats[tx_single_collision] + + priv->stats[tx_multi_collision]; + + storage->rx_length_errors = priv->stats[rx_short_frame_errors] + + priv->stats[rx_long_frame_errors]; + storage->rx_over_errors = priv->stats[rx_fifo_full_dropped]; + storage->rx_crc_errors = priv->stats[rx_crc_errors]; + storage->rx_frame_errors = priv->stats[rx_align_errors]; + storage->rx_missed_errors = priv->stats[rx_fifo_full_dropped]; + + storage->tx_aborted_errors = priv->stats[tx_excess_collision]; + storage->tx_fifo_errors = priv->stats[tx_underrun]; + storage->tx_window_errors = priv->stats[tx_late_collision]; +} + +static void adin1140_ndo_get_stats64(struct net_device *dev, + struct rtnl_link_stats64 *storage) +{ + struct adin1140_priv *priv = netdev_priv(dev); + + storage->rx_packets = priv->netdev->stats.rx_packets; + storage->tx_packets = priv->netdev->stats.tx_packets; + + storage->rx_bytes = priv->netdev->stats.rx_bytes; + storage->tx_bytes = priv->netdev->stats.tx_bytes; + + scoped_guard(spinlock, &priv->stat_lock) + __adin1140_ndo_get_stats64(priv, storage); +} + +static void adin1140_get_drvinfo(struct net_device *netdev, + struct ethtool_drvinfo *info) +{ + strscpy(info->driver, "ADIN1140", sizeof(info->driver)); + strscpy(info->bus_info, dev_name(netdev->dev.parent), + sizeof(info->bus_info)); +} + +static void adin1140_get_ethtool_stats(struct net_device *netdev, + struct ethtool_stats *stats, u64 *data) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + u32 i; + + scoped_guard(spinlock, &priv->stat_lock) { + for (i = 0; i < ARRAY_SIZE(adin1140_stats); i++) + data[i] = priv->stats[adin1140_stats[i].idx]; + } +} + +static void adin1140_get_ethtool_strings(struct net_device *netdev, u32 sset, + u8 *p) +{ + u32 i; + + switch (sset) { + case ETH_SS_STATS: + for (i = 0; i < ARRAY_SIZE(adin1140_stats); i++) + ethtool_puts(&p, adin1140_stats[i].name); + + break; + } +} + +static int adin1140_get_sset_count(struct net_device *netdev, int sset) +{ + switch (sset) { + case ETH_SS_STATS: + return ARRAY_SIZE(adin1140_stats); + default: + return -EOPNOTSUPP; + } +} + +static void __adin1140_eth_mac_stats(struct adin1140_priv *priv, + struct ethtool_eth_mac_stats *mac_stats) +{ + mac_stats->FramesReceivedOK = priv->stats[rx_frames]; + mac_stats->BroadcastFramesReceivedOK = priv->stats[rx_bc_frames]; + mac_stats->MulticastFramesReceivedOK = priv->stats[rx_mc_frames]; + mac_stats->FrameCheckSequenceErrors = priv->stats[rx_crc_errors]; + mac_stats->AlignmentErrors = priv->stats[rx_align_errors]; + mac_stats->FrameTooLongErrors = priv->stats[rx_long_frame_errors]; + mac_stats->FramesLostDueToIntMACRcvError = + priv->stats[rx_fifo_full_dropped]; + mac_stats->FramesTransmittedOK = priv->stats[tx_frames]; + mac_stats->BroadcastFramesXmittedOK = priv->stats[tx_bc_frames]; + mac_stats->MulticastFramesXmittedOK = priv->stats[tx_mc_frames]; + mac_stats->SingleCollisionFrames = priv->stats[tx_single_collision]; + mac_stats->MultipleCollisionFrames = priv->stats[tx_multi_collision]; + mac_stats->FramesWithDeferredXmissions = priv->stats[tx_deferred]; + mac_stats->LateCollisions = priv->stats[tx_late_collision]; + mac_stats->FramesAbortedDueToXSColls = + priv->stats[tx_excess_collision]; + mac_stats->FramesLostDueToIntMACXmitError = priv->stats[tx_underrun]; +} + +static void adin1140_get_eth_mac_stats(struct net_device *netdev, + struct ethtool_eth_mac_stats *mac_stats) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + + scoped_guard(spinlock, &priv->stat_lock) + __adin1140_eth_mac_stats(priv, mac_stats); +} + +static int adin1140_mdiobus_read(struct mii_bus *bus, int addr, int regnum) +{ + /* The ADIN1140's standard PHY C22 register map (OA TC6 0xFF00 - + * 0xFF1F), of which only 0xFF00 - 0xFF03 are implemented) cannot be + * accessed while frames are being received by the PHY. In case this + * happens the CONFIG0 and CONFIG2 register values will get corrupted, + * getting a random value. Both reads and writes cause the same + * behavior. This is a workaround that avoids MDIO accesses all + * together. Since this is a 10BASE-T1S PHY, only the loopback and + * reset (AN) bits in the control register (0x0) can be written. + * These functionalities have custom implementations in the PHY + * driver. C45 accesses do not cause this issue. + */ + + switch (regnum) { + case MII_BMCR: + return ADIN1140_PHY_CTRL_DEFAULT; + case MII_BMSR: + return ADIN1140_PHY_STATUS_DEFAULT; + case MII_PHYSID1: + return ADIN1140_PHY_ID1; + case MII_PHYSID2: + return ADIN1140_PHY_ID2; + default: + return 0xFFFF; + } +} + +static int adin1140_mdiobus_write(struct mii_bus *bus, int addr, int regnum, + u16 val) +{ + return -EIO; +} + +static int adin1140_mdio_register(struct adin1140_priv *priv, + struct spi_device *spidev) +{ + priv->mdiobus = devm_mdiobus_alloc(&spidev->dev); + if (!priv->mdiobus) + return dev_err_probe(&spidev->dev, -ENOMEM, + "MDIO bus alloc failed\n"); + + priv->mdiobus->name = "adin1140-mdiobus"; + priv->mdiobus->priv = priv->tc6; + priv->mdiobus->parent = &spidev->dev; + priv->mdiobus->phy_mask = GENMASK(31, 1); + priv->mdiobus->read = adin1140_mdiobus_read; + priv->mdiobus->write = adin1140_mdiobus_write; + priv->mdiobus->read_c45 = oa_tc6_mdiobus_read_c45; + priv->mdiobus->write_c45 = oa_tc6_mdiobus_write_c45; + + snprintf(priv->mdiobus->id, MII_BUS_ID_SIZE, "adin1140-%s.%u", + dev_name(&spidev->dev), spi_get_chipselect(spidev, 0)); + + return devm_mdiobus_register(&spidev->dev, priv->mdiobus); +} + +static void adin1140_handle_link_change(struct net_device *netdev) +{ + phy_print_status(netdev->phydev); +} + +static void adin1140_phy_remove(void *data) +{ + phy_disconnect(data); +} + +static int adin1140_phy_init(struct adin1140_priv *priv, + struct spi_device *spidev) +{ + int ret; + + ret = adin1140_mdio_register(priv, spidev); + if (ret) + return ret; + + priv->phydev = phy_find_first(priv->mdiobus); + if (!priv->phydev) + return dev_err_probe(&spidev->dev, -ENODEV, "No PHY found\n"); + + priv->phydev->is_internal = true; + ret = phy_connect_direct(priv->netdev, priv->phydev, + &adin1140_handle_link_change, + PHY_INTERFACE_MODE_INTERNAL); + if (ret) + return dev_err_probe(&spidev->dev, ret, + "Can't attach PHY to %s\n", + priv->mdiobus->id); + + ret = devm_add_action_or_reset(&spidev->dev, adin1140_phy_remove, + priv->phydev); + if (ret) + return ret; + + phy_attached_info(priv->phydev); + + return 0; +} + +static const struct ethtool_ops adin1140_ethtool_ops = { + .get_drvinfo = adin1140_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_ethtool_stats = adin1140_get_ethtool_stats, + .get_sset_count = adin1140_get_sset_count, + .get_strings = adin1140_get_ethtool_strings, + .get_link_ksettings = phy_ethtool_get_link_ksettings, + .set_link_ksettings = phy_ethtool_set_link_ksettings, + .get_eth_mac_stats = adin1140_get_eth_mac_stats, +}; + +static const struct net_device_ops adin1140_netdev_ops = { + .ndo_open = adin1140_open, + .ndo_stop = adin1140_close, + .ndo_start_xmit = adin1140_start_xmit, + .ndo_set_mac_address = adin1140_set_mac_address, + .ndo_validate_addr = eth_validate_addr, + .ndo_set_rx_mode_async = adin1140_rx_mode, + .ndo_eth_ioctl = phy_do_ioctl_running, + .ndo_get_stats64 = adin1140_ndo_get_stats64, +}; + +static void adin1140_oa_tc6_remove(void *data) +{ + oa_tc6_exit(data); +} + +static int adin1140_probe(struct spi_device *spi) +{ + struct oa_tc6_quirks tc6_quirks = {}; + struct net_device *netdev; + struct adin1140_priv *priv; + int ret; + + netdev = devm_alloc_etherdev(&spi->dev, sizeof(struct adin1140_priv)); + if (!netdev) + return -ENOMEM; + + priv = netdev_priv(netdev); + priv->netdev = netdev; + spi_set_drvdata(spi, priv); + spin_lock_init(&priv->stat_lock); + + tc6_quirks.quirk_flags = OA_TC6_BROKEN_PHY; + + priv->tc6 = oa_tc6_init(spi, netdev, &tc6_quirks); + if (!priv->tc6) + return -ENODEV; + + ret = devm_add_action_or_reset(&spi->dev, adin1140_oa_tc6_remove, + priv->tc6); + if (ret) + return ret; + + ret = adin1140_phy_init(priv, spi); + if (ret) + return ret; + + if (device_get_ethdev_address(&spi->dev, netdev)) + eth_hw_addr_random(netdev); + + ret = adin1140_configure(priv); + if (ret) + return ret; + + INIT_DELAYED_WORK(&priv->stats_work, adin1140_stats_work); + + netdev->if_port = IF_PORT_10BASET; + netdev->irq = spi->irq; + netdev->netdev_ops = &adin1140_netdev_ops; + netdev->ethtool_ops = &adin1140_ethtool_ops; + netdev->netns_immutable = true; + netdev->priv_flags |= IFF_LIVE_ADDR_CHANGE | + IFF_UNICAST_FLT; + + ret = devm_register_netdev(&spi->dev, netdev); + if (ret) + return dev_err_probe(&spi->dev, ret, + "Failed to register netdev"); + + return 0; +} + +static const struct spi_device_id adin1140_spi_id[] = { + { .name = "ad3306" }, + { .name = "adin1140" }, + {}, +}; +MODULE_DEVICE_TABLE(spi, adin1140_spi_id); + +static const struct of_device_id adin1140_match_table[] = { + { .compatible = "adi,ad3306" }, + { .compatible = "adi,adin1140" }, + { } +}; +MODULE_DEVICE_TABLE(of, adin1140_match_table); + +static struct spi_driver adin1140_driver = { + .driver = { + .name = "adin1140", + .of_match_table = adin1140_match_table, + }, + .probe = adin1140_probe, + .id_table = adin1140_spi_id, +}; +module_spi_driver(adin1140_driver); + +MODULE_DESCRIPTION("Analog Devices, Inc. ADIN1140 10BASE-T1S MAC-PHY"); +MODULE_AUTHOR("Ciprian Regus "); +MODULE_LICENSE("GPL"); From 3cb8d4b9bfeb8a76fc895975842539aa6d5084c4 Mon Sep 17 00:00:00 2001 From: Anton Danilov Date: Wed, 8 Jul 2026 03:35:03 +0300 Subject: [PATCH 0407/1433] udp: fix encapsulation packet resubmit in multicast deliver When a UDP encapsulation socket (e.g., FOU) receives a multicast packet, __udp4_lib_mcast_deliver() and __udp6_lib_mcast_deliver() call consume_skb() when udp_queue_rcv_skb() returns a positive value. A positive return value from udp_queue_rcv_skb() indicates that the encap_rcv handler (e.g., fou_udp_recv) has consumed the UDP header and wants the packet to be resubmitted to the IP protocol handler for further processing (e.g., as a GRE packet). The unicast paths handle this correctly by propagating the return value up to ip_protocol_deliver_rcu() / ip6_protocol_deliver_rcu() for resubmission. However, the multicast paths destroy the packet via consume_skb() instead of resubmitting it, causing silent packet loss. This affects any UDP encapsulation (FOU, GUE) combined with multicast destination addresses. Fix this by returning the value from udp_queue_rcv_skb() when it is positive, matching the behavior of the corresponding unicast paths. Note the sign difference between IPv4 and IPv6: - IPv4: udp_unicast_rcv_skb() returns -ret, and ip_protocol_deliver_rcu() resubmits when ret < 0 (using -ret as the protocol number). - IPv6: udp6_unicast_rcv_skb() returns ret, and ip6_protocol_deliver_rcu() resubmits when ret > 0 (using ret as the nexthdr). Both mcast paths now follow the same convention as their respective unicast paths. Suggested-by: Kuniyuki Iwashima Signed-off-by: Anton Danilov Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/5372ccac062193147e02b991d5328a5c3fa3a85a.1783372173.git.littlesmilingcloud@gmail.com Signed-off-by: Paolo Abeni --- net/ipv4/udp.c | 6 ++++-- net/ipv6/udp.c | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index 59248a59358c..d3ddcbfc8477 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -2476,6 +2476,7 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb, struct udp_hslot *hslot; struct sk_buff *nskb; bool use_hash2; + int ret; hash2_any = 0; hash2 = 0; @@ -2520,8 +2521,9 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb, } if (first) { - if (udp_queue_rcv_skb(first, skb) > 0) - consume_skb(skb); + ret = udp_queue_rcv_skb(first, skb); + if (ret > 0) + return -ret; } else { kfree_skb(skb); __UDP_INC_STATS(net, UDP_MIB_IGNOREDMULTI); diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c index 392e18b97045..0910cc171776 100644 --- a/net/ipv6/udp.c +++ b/net/ipv6/udp.c @@ -949,6 +949,7 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb, struct udp_hslot *hslot; struct sk_buff *nskb; bool use_hash2; + int ret; hash2_any = 0; hash2 = 0; @@ -998,8 +999,9 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb, } if (first) { - if (udpv6_queue_rcv_skb(first, skb) > 0) - consume_skb(skb); + ret = udpv6_queue_rcv_skb(first, skb); + if (ret > 0) + return ret; } else { kfree_skb(skb); __UDP6_INC_STATS(net, UDP_MIB_IGNOREDMULTI); From e5382133c51cc92766914b54c9c257d7c54c8079 Mon Sep 17 00:00:00 2001 From: Anton Danilov Date: Wed, 8 Jul 2026 03:35:04 +0300 Subject: [PATCH 0408/1433] selftests: net: add FOU multicast encapsulation resubmit test Add a selftest to verify that FOU-encapsulated packets addressed to a multicast destination are correctly resubmitted to the inner protocol handler (GRE) via the UDP multicast delivery path. Both IPv4 and IPv6 paths are tested. The test creates two network namespaces connected by a veth pair with a FOU/GRETAP (IPv4) and FOU/ip6gretap (IPv6) tunnel using multicast remote addresses (239.0.0.1 and ff0e::1). Ping is sent through each tunnel and received packets are counted on the receiver's tunnel interface. The veth pair is created directly inside the namespaces to avoid possible name collisions with devices in the root namespace. Static neighbor entries are configured on the sender because ARP/ND replies from the receiver cannot traverse the unidirectional multicast tunnel back to the sender. The early demux optimization (net.ipv4.ip_early_demux, which controls both IPv4 and IPv6) is disabled on the receiver to force packets through __udp4_lib_mcast_deliver() / __udp6_lib_mcast_deliver(), which is the code path being tested. Signed-off-by: Anton Danilov Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/a5b65f092d22a12b52fc536c0565b948cd8ecae3.1783372173.git.littlesmilingcloud@gmail.com Signed-off-by: Paolo Abeni --- tools/testing/selftests/net/Makefile | 1 + tools/testing/selftests/net/config | 2 + .../testing/selftests/net/fou_mcast_encap.sh | 172 ++++++++++++++++++ 3 files changed, 175 insertions(+) create mode 100755 tools/testing/selftests/net/fou_mcast_encap.sh diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index 708d960ae07d..7e9ae937cffa 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -39,6 +39,7 @@ TEST_PROGS := \ fib_rule_tests.sh \ fib_tests.sh \ fin_ack_lat.sh \ + fou_mcast_encap.sh \ fq_band_pktlimit.sh \ gre_gso.sh \ gre_ipv6_lladdr.sh \ diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config index e1ce35c2abbe..96fffca6547c 100644 --- a/tools/testing/selftests/net/config +++ b/tools/testing/selftests/net/config @@ -38,6 +38,8 @@ CONFIG_IP_NF_TARGET_REJECT=m CONFIG_IP_NF_TARGET_TTL=m CONFIG_IP_SCTP=m CONFIG_IPV6=y +CONFIG_IPV6_FOU=m +CONFIG_IPV6_FOU_TUNNEL=m CONFIG_IPV6_GRE=m CONFIG_IPV6_ILA=m CONFIG_IPV6_IOAM6_LWTUNNEL=y diff --git a/tools/testing/selftests/net/fou_mcast_encap.sh b/tools/testing/selftests/net/fou_mcast_encap.sh new file mode 100755 index 000000000000..70210d39fba3 --- /dev/null +++ b/tools/testing/selftests/net/fou_mcast_encap.sh @@ -0,0 +1,172 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Test that UDP encapsulation (FOU) correctly handles packet resubmit +# when packets are delivered via the multicast UDP delivery path. +# +# When a FOU-encapsulated packet arrives with a multicast destination IP, +# __udp4_lib_mcast_deliver() / __udp6_lib_mcast_deliver() must resubmit +# it to the inner protocol handler (e.g., GRE) rather than consuming it. +# This test verifies both IPv4 and IPv6 paths by creating a FOU/GRETAP +# tunnel with a multicast remote address and sending ping through it. +# +# The early demux optimization can mask this issue by routing packets via +# the unicast path (udp[6]_unicast_rcv_skb), so we disable it to force +# packets through the multicast delivery function. + +source lib.sh + +NSENDER="" +NRECV="" + +FOU_PORT4=4797 +FOU_PORT6=4798 +MCAST4=239.0.0.1 +MCAST6=ff0e::1 + +TUN4_S=192.168.99.1 +TUN4_R=192.168.99.2 +TUN6_S=2001:db8:99::1 +TUN6_R=2001:db8:99::2 + +cleanup() { + cleanup_all_ns +} + +trap cleanup EXIT + +setup_common() { + setup_ns NSENDER NRECV + + # Create veth pair directly inside namespaces to avoid name + # collisions with devices in the root namespace. + ip link add veth_s netns "$NSENDER" type veth \ + peer name veth_r netns "$NRECV" + + ip -n "$NSENDER" link set veth_s up + ip -n "$NRECV" link set veth_r up + + # Same sysctl controls early demux for both IPv4 and IPv6. + ip netns exec "$NRECV" sysctl -wq net.ipv4.ip_early_demux=0 +} + +setup_ipv4() { + # IPv4 FOU (CONFIG_NET_FOU) is built in on kernels configured for + # these tests, so no module load is needed here. + ip -n "$NSENDER" addr add 10.0.0.1/24 dev veth_s + ip -n "$NRECV" addr add 10.0.0.2/24 dev veth_r + + # Join multicast group on receiver + ip -n "$NRECV" addr add "$MCAST4/32" dev veth_r autojoin + + ip -n "$NSENDER" route add 239.0.0.0/8 dev veth_s + ip -n "$NRECV" route add 239.0.0.0/8 dev veth_r + + # Sender: GRETAP with FOU encap (no FOU listener needed on TX side) + ip -n "$NSENDER" link add eoudp4 type gretap \ + remote "$MCAST4" local 10.0.0.1 \ + encap fou encap-sport "$FOU_PORT4" encap-dport "$FOU_PORT4" \ + key "$MCAST4" + ip -n "$NSENDER" link set eoudp4 up + ip -n "$NSENDER" addr add "$TUN4_S/24" dev eoudp4 + + # Receiver: FOU listener + GRETAP + ip netns exec "$NRECV" ip fou add port "$FOU_PORT4" ipproto 47 + ip -n "$NRECV" link add eoudp4 type gretap \ + remote "$MCAST4" local 10.0.0.2 \ + encap fou encap-sport "$FOU_PORT4" encap-dport "$FOU_PORT4" \ + key "$MCAST4" + ip -n "$NRECV" link set eoudp4 up + ip -n "$NRECV" addr add "$TUN4_R/24" dev eoudp4 + + # Static neigh on sender: ARP replies cannot traverse the + # unidirectional multicast tunnel. + local recv_mac + recv_mac=$(ip -n "$NRECV" link show eoudp4 | awk '/ether/{print $2}') + ip -n "$NSENDER" neigh add "$TUN4_R" lladdr "$recv_mac" dev eoudp4 +} + +setup_ipv6() { + # Skip cleanly if IPv6 or the fou6 module is not available. + [ -e /proc/sys/net/ipv6 ] || return "$ksft_skip" + modprobe -q fou6 || return "$ksft_skip" + + ip -n "$NSENDER" addr add 2001:db8::1/64 dev veth_s nodad + ip -n "$NRECV" addr add 2001:db8::2/64 dev veth_r nodad + + # Join multicast group on receiver + ip -n "$NRECV" addr add "$MCAST6/128" dev veth_r autojoin + + ip -n "$NSENDER" -6 route add ff00::/8 dev veth_s + ip -n "$NRECV" -6 route add ff00::/8 dev veth_r + + # Sender: ip6gretap with FOU encap + ip -n "$NSENDER" link add eoudp6 type ip6gretap \ + remote "$MCAST6" local 2001:db8::1 \ + encap fou encap-sport "$FOU_PORT6" encap-dport "$FOU_PORT6" \ + key 42 + ip -n "$NSENDER" link set eoudp6 up + ip -n "$NSENDER" addr add "$TUN6_S/64" dev eoudp6 nodad + + # Receiver: FOU listener (IPv6) + ip6gretap + ip netns exec "$NRECV" ip fou add port "$FOU_PORT6" ipproto 47 -6 + ip -n "$NRECV" link add eoudp6 type ip6gretap \ + remote "$MCAST6" local 2001:db8::2 \ + encap fou encap-sport "$FOU_PORT6" encap-dport "$FOU_PORT6" \ + key 42 + ip -n "$NRECV" link set eoudp6 up + ip -n "$NRECV" addr add "$TUN6_R/64" dev eoudp6 nodad + + # Static neigh on sender: neighbor discovery cannot traverse the + # unidirectional multicast tunnel. + local recv_mac + recv_mac=$(ip -n "$NRECV" link show eoudp6 | awk '/ether/{print $2}') + ip -n "$NSENDER" neigh add "$TUN6_R" lladdr "$recv_mac" dev eoudp6 +} + +get_rx_packets() { + local dev="$1" + + ip -n "$NRECV" -s link show "$dev" | awk '/RX:/{getline; print $2}' +} + +run_ping_test() { + local family="$1" + local dev="$2" + local dst="$3" + local name="$4" + local count=100 + local rx_before rx_after rx_delta + + # Warmup: let any initial broadcast/ND traffic settle + ip netns exec "$NSENDER" ping "$family" -c 1 -W 1 "$dst" \ + >/dev/null 2>&1 + sleep 1 + + rx_before=$(get_rx_packets "$dev") + ip netns exec "$NSENDER" ping "$family" -i 0.01 -c $count -W 1 "$dst" \ + >/dev/null 2>&1 + sleep 1 + rx_after=$(get_rx_packets "$dev") + + rx_delta=$((rx_after - rx_before)) + + if [ "$rx_delta" -ge "$count" ]; then + RET=$ksft_pass + else + RET=$ksft_fail + fi + log_test "$name (received $rx_delta/$count)" +} + +setup_common +setup_ipv4 +run_ping_test -4 eoudp4 "$TUN4_R" "FOU/GRETAP IPv4 multicast encap resubmit" + +if setup_ipv6; then + run_ping_test -6 eoudp6 "$TUN6_R" "FOU/ip6gretap IPv6 multicast encap resubmit" +else + log_test_skip "FOU/ip6gretap IPv6 multicast encap resubmit" +fi + +exit "$EXIT_STATUS" From b9ecdfda4d48b7cb33bff3bd924ded7019aa4df2 Mon Sep 17 00:00:00 2001 From: Yun Lu Date: Wed, 8 Jul 2026 13:54:54 +0800 Subject: [PATCH 0409/1433] net: skbuff: optimization of net_zcopy_get() call in pskb_carve helpers Commit 98d0912e9f84 ("net: skbuff: fix missing zerocopy reference in pskb_carve helpers") introduced two calls of net_zcopy_get(skb_zcopy(skb)). In fact, skb_zcopy() has already been executed once before. When calling net_zcopy_get(), skb_zcopy() always returns skb_uarg(skb), which results in adding some unnecessary instructions in skb_zcopy. So, change these two calls to directly use skb_uarg(skb) instead of skb_zcopy. In addition, also use net_zcopy_get() instead of refcount_inc() in pskb_expand_head() for code consistency. No functional change intended. Signed-off-by: Yun Lu Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260708055454.9167-1-luyun_611@163.com Signed-off-by: Paolo Abeni --- net/core/skbuff.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index 18dabb4e9cfa..d798fbdc3da7 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -2326,7 +2326,7 @@ int pskb_expand_head(struct sk_buff *skb, int nhead, int ntail, if (skb_orphan_frags(skb, gfp_mask)) goto nofrags; if (skb_zcopy(skb)) - refcount_inc(&skb_uarg(skb)->refcnt); + net_zcopy_get(skb_uarg(skb)); for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) skb_frag_ref(skb, i); @@ -6842,7 +6842,7 @@ static int pskb_carve_inside_header(struct sk_buff *skb, const u32 off, return -ENOMEM; } if (skb_zcopy(skb)) - net_zcopy_get(skb_zcopy(skb)); + net_zcopy_get(skb_uarg(skb)); for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) skb_frag_ref(skb, i); if (skb_has_frag_list(skb)) @@ -6992,7 +6992,7 @@ static int pskb_carve_inside_nonlinear(struct sk_buff *skb, const u32 off, return -ENOMEM; } if (skb_zcopy(skb)) - net_zcopy_get(skb_zcopy(skb)); + net_zcopy_get(skb_uarg(skb)); skb_release_data(skb, SKB_CONSUMED); skb->head = data; From 1469773b246a2b99fddc7331378e270e611f04bc Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Wed, 8 Jul 2026 14:05:36 +0800 Subject: [PATCH 0410/1433] ipv4: use rcu_assign_pointer() in rt_flush_dev() rt_flush_dev() replaces rt->dst.dev with blackhole_netdev on uncached routes. The field is also exposed as dst.dev_rcu, and existing readers use dst_dev_rcu(). Use rcu_assign_pointer() for the replacement, as dst_dev_put() already does for the same field. Signed-off-by: Xuanqiang Luo Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260708060537.17188-2-xuanqiang.luo@linux.dev Signed-off-by: Paolo Abeni --- net/ipv4/route.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv4/route.c b/net/ipv4/route.c index 3f3de5164d6e..b668375df71e 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -1565,7 +1565,7 @@ void rt_flush_dev(struct net_device *dev) list_for_each_entry_safe(rt, safe, &ul->head, dst.rt_uncached) { if (rt->dst.dev != dev) continue; - rt->dst.dev = blackhole_netdev; + rcu_assign_pointer(rt->dst.dev_rcu, blackhole_netdev); netdev_ref_replace(dev, blackhole_netdev, &rt->dst.dev_tracker, GFP_ATOMIC); list_del_init(&rt->dst.rt_uncached); From 7804eaa057fe19da09fa109484a3081d7bba2b76 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Wed, 8 Jul 2026 14:05:37 +0800 Subject: [PATCH 0411/1433] ipv4: snapshot dst.dev in ip_rt_send_redirect() and ip_rt_get_source() rt_flush_dev() can replace rt->dst.dev with blackhole_netdev while RCU readers are running. ip_rt_send_redirect() and ip_rt_get_source() both read rt->dst.dev more than once and use the results in one operation. If rt->dst.dev changes between those reads, the operation can use values from two devices. For example, ip_rt_send_redirect() can use in_dev from the old device and the L3 master ifindex from blackhole_netdev. Read rt->dst.dev once in these two functions and use the snapshot for the later device accesses. Signed-off-by: Xuanqiang Luo Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260708060537.17188-3-xuanqiang.luo@linux.dev Signed-off-by: Paolo Abeni --- net/ipv4/route.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/net/ipv4/route.c b/net/ipv4/route.c index b668375df71e..4fed07cae7da 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -874,21 +874,23 @@ void ip_rt_send_redirect(struct sk_buff *skb) { struct rtable *rt = skb_rtable(skb); struct in_device *in_dev; + struct net_device *dev; struct inet_peer *peer; - struct net *net; int log_martians; + struct net *net; int vif; rcu_read_lock(); - in_dev = __in_dev_get_rcu(rt->dst.dev); + dev = dst_dev_rcu(&rt->dst); + in_dev = __in_dev_get_rcu(dev); if (!in_dev || !IN_DEV_TX_REDIRECTS(in_dev)) { rcu_read_unlock(); return; } log_martians = IN_DEV_LOG_MARTIANS(in_dev); - vif = l3mdev_master_ifindex_rcu(rt->dst.dev); + vif = l3mdev_master_ifindex_rcu(dev); - net = dev_net(rt->dst.dev); + net = dev_net_rcu(dev); peer = inet_getpeer_v4(net->ipv4.peers, ip_hdr(skb)->saddr, vif); if (!peer) { rcu_read_unlock(); @@ -1287,29 +1289,32 @@ void ip_rt_get_source(u8 *addr, struct sk_buff *skb, struct rtable *rt) { __be32 src; - if (rt_is_output_route(rt)) + rcu_read_lock(); + if (rt_is_output_route(rt)) { src = ip_hdr(skb)->saddr; - else { - struct fib_result res; + } else { + struct net_device *dev = dst_dev_rcu(&rt->dst); + struct net *net = dev_net_rcu(dev); struct iphdr *iph = ip_hdr(skb); + struct fib_result res; struct flowi4 fl4 = { .daddr = iph->daddr, .saddr = iph->saddr, .flowi4_dscp = ip4h_dscp(iph), - .flowi4_oif = rt->dst.dev->ifindex, + .flowi4_oif = dev->ifindex, .flowi4_iif = skb->dev->ifindex, .flowi4_mark = skb->mark, }; - rcu_read_lock(); - if (fib_lookup(dev_net(rt->dst.dev), &fl4, &res, 0) == 0) - src = fib_result_prefsrc(dev_net(rt->dst.dev), &res); + if (fib_lookup(net, &fl4, &res, 0) == 0) + src = fib_result_prefsrc(net, &res); else - src = inet_select_addr(rt->dst.dev, + src = inet_select_addr(dev, rt_nexthop(rt, iph->daddr), RT_SCOPE_UNIVERSE); - rcu_read_unlock(); } + rcu_read_unlock(); + memcpy(addr, &src, 4); } From f6195e3c30266679d1b93196e81424cc01862715 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Wed, 8 Jul 2026 13:25:18 +0200 Subject: [PATCH 0412/1433] net: ipip: use tunnel parameters for fill_forward_path route lookup Pass source address, DSCP and output interface from the tunnel configuration to ip_route_output() in ipip_fill_forward_path(), aligning the route lookup with the slow path in ipip_tunnel_xmit(). Signed-off-by: Lorenzo Bianconi Reviewed-by: David Ahern Link: https://patch.msgid.link/20260708-ipip-route-lookup-fill_forward_path-v1-1-b77df74822ed@kernel.org Signed-off-by: Paolo Abeni --- net/ipv4/ipip.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/net/ipv4/ipip.c b/net/ipv4/ipip.c index b643194f57d2..d1aa048a6099 100644 --- a/net/ipv4/ipip.c +++ b/net/ipv4/ipip.c @@ -360,8 +360,9 @@ static int ipip_fill_forward_path(struct net_device_path_ctx *ctx, const struct iphdr *tiph = &tunnel->parms.iph; struct rtable *rt; - rt = ip_route_output(dev_net(ctx->dev), tiph->daddr, 0, 0, 0, - RT_SCOPE_UNIVERSE); + rt = ip_route_output(dev_net(ctx->dev), tiph->daddr, tiph->saddr, + inet_dsfield_to_dscp(tiph->tos), + tunnel->parms.link, RT_SCOPE_UNIVERSE); if (IS_ERR(rt)) return PTR_ERR(rt); From 4b0eb6fbc1fd96390226cbc1156f85ca04dc2ebb Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Wed, 8 Jul 2026 15:28:19 +0300 Subject: [PATCH 0413/1433] bridge: mcast: Fix a false positive lockdep splat Connecting two bridges on the same system [1] can result in a lockdep splat [2]. The report is a false positive. Multicast queries are built and transmitted under the bridge multicast lock. When the outgoing port of one bridge is configured on top of another bridge, the transmit path re-enters bridge code and acquires the other bridge's multicast lock in order to snoop the query. Both lock instances share a single lockdep class, so lockdep flags the nested acquisition as an AA deadlock. Giving each bridge its own lock class will not solve the problem: the reverse topology would produce an ABBA splat with the same pair of classes. It also consumes a lockdep key per bridge. Instead, fix the problem by deferring the transmission of the queries to a workqueue. Build the skb and update querier state under the lock as before, then enqueue the skb on a per multicast context queue and schedule the work. Purge the queue when the multicast context is de-initialized. At this stage the work cannot be requeued. There is no need to take a reference on skb->dev since the work cannot outlive the bridge or the bridge port. Use the high priority workqueue to reduce the delay between the enqueue time and the transmission time. With default settings (i.e., querier interval - 255 seconds, query interval - 125 seconds) the extra delay should not be a problem. Avoid the unlikely case of the queue growing endlessly by limiting it to 1,000 skbs. Use this number for the simple reason that this is the default Tx queue length. Use local_bh_{disable,enable}() to disable/enable softIRQs and migration in order to avoid corrupting the multicast statistics (per-CPU u64_stats). [1] ip link add name br1 up type bridge mcast_snooping 1 mcast_querier 1 ip link add name br0 up type bridge mcast_snooping 1 mcast_querier 1 ip link add link br0 name br0.10 up master br1 type vlan id 10 [2] WARNING: possible recursive locking detected 7.0.0-virtme-gb50c64a58a90 #1 Not tainted [...] ip/339 is trying to acquire lock: ffff888104f0b480 (&br->multicast_lock){+.-.}-{3:3}, at: br_ip6_multicast_query (net/bridge/br_multicast.c:3584) but task is already holding lock: ffff888104f03480 (&br->multicast_lock){+.-.}-{3:3}, at: br_multicast_port_query_expired (net/bridge/br_multicast.c:1904) [...] Call Trace: [...] br_ip6_multicast_query (net/bridge/br_multicast.c:3584) br_multicast_ipv6_rcv (net/bridge/br_multicast.c:3988) br_dev_xmit (net/bridge/br_device.c:98 (discriminator 1)) dev_hard_start_xmit (net/core/dev.c:3904) __dev_queue_xmit (net/core/dev.c:4871) vlan_dev_hard_start_xmit (net/8021q/vlan_dev.c:131 (discriminator 1)) dev_hard_start_xmit (net/core/dev.c:3904) __dev_queue_xmit (net/core/dev.c:4871) br_dev_queue_push_xmit (net/bridge/br_forward.c:60) __br_multicast_send_query (net/bridge/br_multicast.c:1811 (discriminator 1)) br_multicast_send_query (net/bridge/br_multicast.c:1889) br_multicast_port_query_expired (net/bridge/br_multicast.c:1914) call_timer_fn (kernel/time/timer.c:1749) [...] Reported-by: syzbot+d7b7f1412c02134efa6d@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/000000000000c4c9d405f2643e01@google.com/ Reviewed-by: Petr Machata Acked-by: Nikolay Aleksandrov Signed-off-by: Ido Schimmel Link: https://patch.msgid.link/20260708122820.1298718-2-idosch@nvidia.com Signed-off-by: Paolo Abeni --- net/bridge/br_multicast.c | 87 +++++++++++++++++++++++++++++++++++---- net/bridge/br_private.h | 4 ++ 2 files changed, 83 insertions(+), 8 deletions(-) diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 6b3ac473fd22..e39494b26ab1 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -1774,6 +1774,64 @@ static void br_multicast_select_own_querier(struct net_bridge_mcast *brmctx, #endif } +static u8 br_multicast_query_type(const struct sk_buff *skb) +{ + return skb->protocol == htons(ETH_P_IP) ? IGMP_HOST_MEMBERSHIP_QUERY : + ICMPV6_MGM_QUERY; +} + +static void br_multicast_port_query_queue_work(struct work_struct *work) +{ + struct net_bridge_mcast_port *pmctx; + struct sk_buff_head list; + struct sk_buff *skb; + + pmctx = container_of(work, struct net_bridge_mcast_port, + query_queue_work); + + __skb_queue_head_init(&list); + spin_lock_bh(&pmctx->query_queue.lock); + skb_queue_splice_tail_init(&pmctx->query_queue, &list); + spin_unlock_bh(&pmctx->query_queue.lock); + + while ((skb = __skb_dequeue(&list))) { + u8 query_type = br_multicast_query_type(skb); + + local_bh_disable(); + br_multicast_count(pmctx->port->br, pmctx->port, skb, + query_type, BR_MCAST_DIR_TX); + NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT, dev_net(skb->dev), + NULL, skb, NULL, skb->dev, br_dev_queue_push_xmit); + local_bh_enable(); + } +} + +static void br_multicast_query_queue_work(struct work_struct *work) +{ + struct net_bridge_mcast *brmctx; + struct sk_buff_head list; + struct sk_buff *skb; + + brmctx = container_of(work, struct net_bridge_mcast, query_queue_work); + + __skb_queue_head_init(&list); + spin_lock_bh(&brmctx->query_queue.lock); + skb_queue_splice_tail_init(&brmctx->query_queue, &list); + spin_unlock_bh(&brmctx->query_queue.lock); + + while ((skb = __skb_dequeue(&list))) { + u8 query_type = br_multicast_query_type(skb); + + local_bh_disable(); + br_multicast_count(brmctx->br, NULL, skb, query_type, + BR_MCAST_DIR_RX); + netif_rx(skb); + local_bh_enable(); + } +} + +#define BR_MULTICAST_QUERY_QUEUE_LEN_MAX 1000 + static void __br_multicast_send_query(struct net_bridge_mcast *brmctx, struct net_bridge_mcast_port *pmctx, struct net_bridge_port_group *pg, @@ -1783,6 +1841,7 @@ static void __br_multicast_send_query(struct net_bridge_mcast *brmctx, u8 sflag, bool *need_rexmit) { + struct sk_buff_head *queue; bool over_lmqt = !!sflag; struct sk_buff *skb; u8 igmp_type; @@ -1791,7 +1850,12 @@ static void __br_multicast_send_query(struct net_bridge_mcast *brmctx, !br_multicast_ctx_matches_vlan_snooping(brmctx)) return; + queue = pmctx ? &pmctx->query_queue : &brmctx->query_queue; + again_under_lmqt: + if (skb_queue_len_lockless(queue) >= BR_MULTICAST_QUERY_QUEUE_LEN_MAX) + return; + skb = br_multicast_alloc_query(brmctx, pmctx, pg, ip_dst, group, with_srcs, over_lmqt, sflag, &igmp_type, need_rexmit); @@ -1800,11 +1864,8 @@ static void __br_multicast_send_query(struct net_bridge_mcast *brmctx, if (pmctx) { skb->dev = pmctx->port->dev; - br_multicast_count(brmctx->br, pmctx->port, skb, igmp_type, - BR_MCAST_DIR_TX); - NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT, - dev_net(pmctx->port->dev), NULL, skb, NULL, skb->dev, - br_dev_queue_push_xmit); + skb_queue_tail(queue, skb); + queue_work(system_highpri_wq, &pmctx->query_queue_work); if (over_lmqt && with_srcs && sflag) { over_lmqt = false; @@ -1812,9 +1873,8 @@ static void __br_multicast_send_query(struct net_bridge_mcast *brmctx, } } else { br_multicast_select_own_querier(brmctx, group, skb); - br_multicast_count(brmctx->br, NULL, skb, igmp_type, - BR_MCAST_DIR_RX); - netif_rx(skb); + skb_queue_tail(queue, skb); + queue_work(system_highpri_wq, &brmctx->query_queue_work); } } @@ -1997,6 +2057,10 @@ void br_multicast_port_ctx_init(struct net_bridge_port *port, pmctx->port = port; pmctx->vlan = vlan; pmctx->multicast_router = MDB_RTR_TYPE_TEMP_QUERY; + + skb_queue_head_init(&pmctx->query_queue); + INIT_WORK(&pmctx->query_queue_work, br_multicast_port_query_queue_work); + timer_setup(&pmctx->ip4_mc_router_timer, br_ip4_multicast_router_expired, 0); timer_setup(&pmctx->ip4_own_query.timer, @@ -2038,6 +2102,8 @@ void br_multicast_port_ctx_deinit(struct net_bridge_mcast_port *pmctx) del |= br_ip4_multicast_rport_del(pmctx); br_multicast_rport_del_notify(pmctx, del); spin_unlock_bh(&br->multicast_lock); + cancel_work_sync(&pmctx->query_queue_work); + __skb_queue_purge(&pmctx->query_queue); } int br_multicast_add_port(struct net_bridge_port *port) @@ -4112,6 +4178,9 @@ void br_multicast_ctx_init(struct net_bridge *br, seqcount_spinlock_init(&brmctx->ip6_querier.seq, &br->multicast_lock); #endif + skb_queue_head_init(&brmctx->query_queue); + INIT_WORK(&brmctx->query_queue_work, br_multicast_query_queue_work); + timer_setup(&brmctx->ip4_mc_router_timer, br_ip4_multicast_local_router_expired, 0); timer_setup(&brmctx->ip4_other_query.timer, @@ -4135,6 +4204,8 @@ void br_multicast_ctx_init(struct net_bridge *br, void br_multicast_ctx_deinit(struct net_bridge_mcast *brmctx) { __br_multicast_stop(brmctx); + cancel_work_sync(&brmctx->query_queue_work); + __skb_queue_purge(&brmctx->query_queue); } void br_multicast_init(struct net_bridge *br) diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h index d55ea9516e3e..f8f77a2d4891 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h @@ -131,6 +131,8 @@ struct net_bridge_mcast_port { unsigned char multicast_router; u32 mdb_n_entries; u32 mdb_max_entries; + struct sk_buff_head query_queue; + struct work_struct query_queue_work; #endif /* CONFIG_BRIDGE_IGMP_SNOOPING */ }; @@ -167,6 +169,8 @@ struct net_bridge_mcast { struct bridge_mcast_own_query ip6_own_query; struct bridge_mcast_querier ip6_querier; #endif /* IS_ENABLED(CONFIG_IPV6) */ + struct sk_buff_head query_queue; + struct work_struct query_queue_work; #endif /* CONFIG_BRIDGE_IGMP_SNOOPING */ }; From dbda4c27bd772155356a9a6506de988607a708a3 Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Wed, 8 Jul 2026 15:28:20 +0300 Subject: [PATCH 0414/1433] bridge: mcast: Remove unnecessary argument from br_multicast_alloc_query() After the previous patch, __br_multicast_send_query() no longer relies on br_multicast_alloc_query() to determine the IGMP type of the query. Remove the argument. Reviewed-by: Petr Machata Acked-by: Nikolay Aleksandrov Signed-off-by: Ido Schimmel Link: https://patch.msgid.link/20260708122820.1298718-3-idosch@nvidia.com Signed-off-by: Paolo Abeni --- net/bridge/br_multicast.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index e39494b26ab1..f112fbb374c0 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -926,7 +926,7 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge_mcast *brm struct net_bridge_port_group *pg, __be32 ip_dst, __be32 group, bool with_srcs, bool over_lmqt, - u8 sflag, u8 *igmp_type, + u8 sflag, bool *need_rexmit) { struct net_bridge_port *p = pg ? pg->key.port : NULL; @@ -1006,7 +1006,6 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge_mcast *brm skb_set_transport_header(skb, skb->len); mrt = group ? brmctx->multicast_last_member_interval : brmctx->multicast_query_response_interval; - *igmp_type = IGMP_HOST_MEMBERSHIP_QUERY; switch (brmctx->multicast_igmp_version) { case 2: @@ -1072,7 +1071,7 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge_mcast *brm const struct in6_addr *ip6_dst, const struct in6_addr *group, bool with_srcs, bool over_llqt, - u8 sflag, u8 *igmp_type, + u8 sflag, bool *need_rexmit) { struct net_bridge_port *p = pg ? pg->key.port : NULL; @@ -1166,7 +1165,6 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge_mcast *brm interval = ipv6_addr_any(group) ? brmctx->multicast_query_response_interval : brmctx->multicast_last_member_interval; - *igmp_type = ICMPV6_MGM_QUERY; switch (brmctx->multicast_mld_version) { case 1: mldq = (struct mld_msg *)icmp6_hdr(skb); @@ -1237,8 +1235,7 @@ static struct sk_buff *br_multicast_alloc_query(struct net_bridge_mcast *brmctx, struct br_ip *ip_dst, struct br_ip *group, bool with_srcs, bool over_lmqt, - u8 sflag, u8 *igmp_type, - bool *need_rexmit) + u8 sflag, bool *need_rexmit) { __be32 ip4_dst; @@ -1248,8 +1245,7 @@ static struct sk_buff *br_multicast_alloc_query(struct net_bridge_mcast *brmctx, return br_ip4_multicast_alloc_query(brmctx, pmctx, pg, ip4_dst, group->dst.ip4, with_srcs, over_lmqt, - sflag, igmp_type, - need_rexmit); + sflag, need_rexmit); #if IS_ENABLED(CONFIG_IPV6) case htons(ETH_P_IPV6): { struct in6_addr ip6_dst; @@ -1263,8 +1259,7 @@ static struct sk_buff *br_multicast_alloc_query(struct net_bridge_mcast *brmctx, return br_ip6_multicast_alloc_query(brmctx, pmctx, pg, &ip6_dst, &group->dst.ip6, with_srcs, over_lmqt, - sflag, igmp_type, - need_rexmit); + sflag, need_rexmit); } #endif } @@ -1844,7 +1839,6 @@ static void __br_multicast_send_query(struct net_bridge_mcast *brmctx, struct sk_buff_head *queue; bool over_lmqt = !!sflag; struct sk_buff *skb; - u8 igmp_type; if (!br_multicast_ctx_should_use(brmctx, pmctx) || !br_multicast_ctx_matches_vlan_snooping(brmctx)) @@ -1857,7 +1851,7 @@ static void __br_multicast_send_query(struct net_bridge_mcast *brmctx, return; skb = br_multicast_alloc_query(brmctx, pmctx, pg, ip_dst, group, - with_srcs, over_lmqt, sflag, &igmp_type, + with_srcs, over_lmqt, sflag, need_rexmit); if (!skb) return; From a5faaa079c61bba53e3de25471d5b2e666908fee Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Wed, 8 Jul 2026 15:39:31 +0300 Subject: [PATCH 0415/1433] mlxsw: Convert to async version of ndo_set_rx_mode Commit c5b9b518adab ("mlxsw: spectrum: Add set_rx_mode ndo stub") added a stub for ndo_set_rx_mode to prevent dev_ifsioc() from returning an error for the SIOCADDMULTI and SIOCDELMULTI cases. Since then dev_ifsioc() was taught to also accept ndo_set_rx_mode_async and commit 3cbd22938877 ("net: warn ops-locked drivers still using ndo_set_rx_mode") modified register_netdevice() to warn when registering an ops-locked net device that still uses ndo_set_rx_mode instead of ndo_set_rx_mode_async. In preparation for converting the driver to be ops-locked, convert the ndo_set_rx_mode stub to a ndo_set_rx_mode_async stub. Reviewed-by: Danielle Ratson Signed-off-by: Ido Schimmel Link: https://patch.msgid.link/20260708123933.1303291-2-idosch@nvidia.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/mellanox/mlxsw/spectrum.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum.c index 82569162d2e5..3ee1272dcf0e 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum.c @@ -663,8 +663,11 @@ static netdev_tx_t mlxsw_sp_port_xmit(struct sk_buff *skb, return NETDEV_TX_OK; } -static void mlxsw_sp_set_rx_mode(struct net_device *dev) +static int mlxsw_sp_set_rx_mode_async(struct net_device *dev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) { + return 0; } static int mlxsw_sp_port_set_mac_address(struct net_device *dev, void *p) @@ -1191,7 +1194,7 @@ static const struct net_device_ops mlxsw_sp_port_netdev_ops = { .ndo_stop = mlxsw_sp_port_stop, .ndo_start_xmit = mlxsw_sp_port_xmit, .ndo_setup_tc = mlxsw_sp_setup_tc, - .ndo_set_rx_mode = mlxsw_sp_set_rx_mode, + .ndo_set_rx_mode_async = mlxsw_sp_set_rx_mode_async, .ndo_set_mac_address = mlxsw_sp_port_set_mac_address, .ndo_change_mtu = mlxsw_sp_port_change_mtu, .ndo_get_stats64 = mlxsw_sp_port_get_stats64, From 925a17fe430983412bbe10424dae289b60744ada Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Wed, 8 Jul 2026 15:39:32 +0300 Subject: [PATCH 0416/1433] mlxsw: ethtool: Prepare for RTNL-less ethtool operations A subsequent patch is going to make the driver ops-locked and allow ethtool operations to run without RTNL. In preparation for this change, tell the core about a couple of ethtool operations that should remain under RTNL: 1. Set pause parameters: Configures the port's headroom buffer which is also configured by RTNL-only paths such as DCB and qdisc. These paths can probably be converted to acquire the netdev instance lock, but this operation in not frequently called (unlike stats query), so avoid the added complexity for now. 2. Get link state: Calls ethtool_op_get_link() which requires RTNL. See commit 1105ef941c1a ("net: ethtool: keep rtnl_lock for ops using ethtool_op_get_link()"). All the other operations do not access shared resources, do not invoke helpers that require RTNL or already have the appropriate locking in place. Reviewed-by: Danielle Ratson Signed-off-by: Ido Schimmel Link: https://patch.msgid.link/20260708123933.1303291-3-idosch@nvidia.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/mellanox/mlxsw/spectrum_ethtool.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_ethtool.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_ethtool.c index 7f78b1ef61cc..3bdb532d833b 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_ethtool.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_ethtool.c @@ -1262,6 +1262,8 @@ mlxsw_sp_set_module_power_mode(struct net_device *dev, const struct ethtool_ops mlxsw_sp_port_ethtool_ops = { .cap_link_lanes_supported = true, + .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SPAUSEPARAM | + ETHTOOL_OP_NEEDS_RTNL_GLINK, .get_drvinfo = mlxsw_sp_port_get_drvinfo, .get_link = ethtool_op_get_link, .get_link_ext_state = mlxsw_sp_port_get_link_ext_state, From 0501dd682648dd6c94570b01ef37f76b489c9fdf Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Wed, 8 Jul 2026 15:39:33 +0300 Subject: [PATCH 0417/1433] mlxsw: Tell the core to use the netdev instance lock After the previous changes the driver is now ready to have its net device and ethtool operations invoked with the netdev instance lock held. Tell the core about it by setting request_ops_lock to true. Reviewed-by: Danielle Ratson Signed-off-by: Ido Schimmel Link: https://patch.msgid.link/20260708123933.1303291-4-idosch@nvidia.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/mellanox/mlxsw/spectrum.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum.c index 3ee1272dcf0e..815e8d8e3185 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum.c @@ -1552,6 +1552,7 @@ static int mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u16 local_port, dev->vlan_features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM; dev->lltx = true; dev->netns_immutable = true; + dev->request_ops_lock = true; dev->min_mtu = ETH_MIN_MTU; dev->max_mtu = MLXSW_PORT_MAX_MTU - MLXSW_PORT_ETH_FRAME_HDR; From 55e9b2788fdc051710885fd30e3a3e813d9bed49 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Tue, 7 Jul 2026 16:08:44 +0300 Subject: [PATCH 0418/1433] net/mlx5e: psp: Rename the saved psp_dev to 'psd' This is the canonical name used in the core, so try to be consistent. No-op change. Signed-off-by: Cosmin Ratiu Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Reviewed-by: Aleksandr Loktionov Link: https://patch.msgid.link/20260707130858.969928-2-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c | 8 ++++---- drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h | 2 +- .../net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c index d9adb993e64d..4f2fa6756b82 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c @@ -1072,11 +1072,11 @@ void mlx5e_psp_unregister(struct mlx5e_priv *priv) { struct mlx5e_psp *psp = priv->psp; - if (!psp || !psp->psp) + if (!psp || !psp->psd) return; - psp_dev_unregister(psp->psp); - psp->psp = NULL; + psp_dev_unregister(psp->psd); + psp->psd = NULL; } void mlx5e_psp_register(struct mlx5e_priv *priv) @@ -1100,7 +1100,7 @@ void mlx5e_psp_register(struct mlx5e_priv *priv) psd); return; } - psp->psp = psd; + psp->psd = psd; } int mlx5e_psp_init(struct mlx5e_priv *priv) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h index 6b62fef0d9a7..a53f90f7c341 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h @@ -23,7 +23,7 @@ struct mlx5e_psp_stats { }; struct mlx5e_psp { - struct psp_dev *psp; + struct psp_dev *psd; struct psp_dev_caps caps; struct mlx5e_psp_fs *fs; atomic_t tx_key_cnt; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c index ef7f5338540f..c2f9899d23a5 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c @@ -124,7 +124,7 @@ bool mlx5e_psp_offload_handle_rx_skb(struct net_device *netdev, struct sk_buff * { u32 psp_meta_data = be32_to_cpu(cqe->ft_metadata); struct mlx5e_priv *priv = netdev_priv(netdev); - u16 dev_id = priv->psp->psp->id; + u16 dev_id = priv->psp->psd->id; bool strip_icv = true; u8 generation = 0; From c61cb6647a2c4624af55c5f4ee706bf8a0dd6942 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Tue, 7 Jul 2026 16:08:45 +0300 Subject: [PATCH 0419/1433] net/mlx5e: psp: Remove PSP steering mutexes PSP steering uses three mutexes to serialize steering rule init/cleanup. But init/cleanup are already serialized with the higher level devlink lock (for both device init and esw mode changes), so there's no need for multiple additional mutexes. Remove them to make room for the new changes. Later in the series, the netdev lock will be used to serialize PSP steering changes from multiple sources, so don't bother adding assertions now only for them to be overwritten later. Signed-off-by: Cosmin Ratiu Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Reviewed-by: Aleksandr Loktionov Link: https://patch.msgid.link/20260707130858.969928-3-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../mellanox/mlx5/core/en_accel/psp.c | 43 +++---------------- 1 file changed, 7 insertions(+), 36 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c index 4f2fa6756b82..d4686b5af776 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c @@ -26,7 +26,6 @@ struct mlx5e_psp_tx { struct mlx5_flow_table *ft; struct mlx5_flow_group *fg; struct mlx5_flow_handle *rule; - struct mutex mutex; /* Protect PSP TX steering */ u32 refcnt; struct mlx5_fc *tx_counter; }; @@ -48,7 +47,6 @@ struct mlx5e_accel_fs_psp_prot { struct mlx5_flow_destination default_dest; struct mlx5e_psp_rx_err rx_err; u32 refcnt; - struct mutex prot_mutex; /* protect ESP4/ESP6 protocol */ struct mlx5_flow_handle *def_rule; }; @@ -485,15 +483,14 @@ static int accel_psp_fs_rx_ft_get(struct mlx5e_psp_fs *fs, enum accel_fs_psp_typ ttc = mlx5e_fs_get_ttc(fs->fs, false); accel_psp = fs->rx_fs; fs_prot = &accel_psp->fs_prot[type]; - mutex_lock(&fs_prot->prot_mutex); if (fs_prot->refcnt++) - goto out; + return 0; /* create FT */ err = accel_psp_fs_rx_create(fs, type); if (err) { fs_prot->refcnt--; - goto out; + return err; } /* connect */ @@ -501,9 +498,7 @@ static int accel_psp_fs_rx_ft_get(struct mlx5e_psp_fs *fs, enum accel_fs_psp_typ dest.ft = fs_prot->ft; mlx5_ttc_fwd_dest(ttc, fs_psp2tt(type), &dest); -out: - mutex_unlock(&fs_prot->prot_mutex); - return err; + return 0; } static void accel_psp_fs_rx_ft_put(struct mlx5e_psp_fs *fs, enum accel_fs_psp_type type) @@ -514,18 +509,14 @@ static void accel_psp_fs_rx_ft_put(struct mlx5e_psp_fs *fs, enum accel_fs_psp_ty accel_psp = fs->rx_fs; fs_prot = &accel_psp->fs_prot[type]; - mutex_lock(&fs_prot->prot_mutex); if (--fs_prot->refcnt) - goto out; + return; /* disconnect */ mlx5_ttc_fwd_default_dest(ttc, fs_psp2tt(type)); /* remove FT */ accel_psp_fs_rx_destroy(fs, type); - -out: - mutex_unlock(&fs_prot->prot_mutex); } static void accel_psp_fs_cleanup_rx(struct mlx5e_psp_fs *fs) @@ -544,7 +535,6 @@ static void accel_psp_fs_cleanup_rx(struct mlx5e_psp_fs *fs) mlx5_fc_destroy(fs->mdev, accel_psp->rx_counter); for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) { fs_prot = &accel_psp->fs_prot[i]; - mutex_destroy(&fs_prot->prot_mutex); WARN_ON(fs_prot->refcnt); } kfree(fs->rx_fs); @@ -553,22 +543,15 @@ static void accel_psp_fs_cleanup_rx(struct mlx5e_psp_fs *fs) static int accel_psp_fs_init_rx(struct mlx5e_psp_fs *fs) { - struct mlx5e_accel_fs_psp_prot *fs_prot; struct mlx5e_accel_fs_psp *accel_psp; struct mlx5_core_dev *mdev = fs->mdev; struct mlx5_fc *flow_counter; - enum accel_fs_psp_type i; int err; accel_psp = kzalloc_obj(*accel_psp); if (!accel_psp) return -ENOMEM; - for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) { - fs_prot = &accel_psp->fs_prot[i]; - mutex_init(&fs_prot->prot_mutex); - } - flow_counter = mlx5_fc_create(mdev, false); if (IS_ERR(flow_counter)) { mlx5_core_warn(mdev, @@ -623,10 +606,6 @@ static int accel_psp_fs_init_rx(struct mlx5e_psp_fs *fs) mlx5_fc_destroy(mdev, accel_psp->rx_counter); accel_psp->rx_counter = NULL; out_err: - for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) { - fs_prot = &accel_psp->fs_prot[i]; - mutex_destroy(&fs_prot->prot_mutex); - } kfree(accel_psp); fs->rx_fs = NULL; @@ -763,17 +742,14 @@ static void accel_psp_fs_tx_destroy(struct mlx5e_psp_tx *tx_fs) static int accel_psp_fs_tx_ft_get(struct mlx5e_psp_fs *fs) { struct mlx5e_psp_tx *tx_fs = fs->tx_fs; - int err = 0; + int err; - mutex_lock(&tx_fs->mutex); if (tx_fs->refcnt++) - goto out; + return 0; err = accel_psp_fs_tx_create_ft_table(fs); if (err) tx_fs->refcnt--; -out: - mutex_unlock(&tx_fs->mutex); return err; } @@ -781,13 +757,10 @@ static void accel_psp_fs_tx_ft_put(struct mlx5e_psp_fs *fs) { struct mlx5e_psp_tx *tx_fs = fs->tx_fs; - mutex_lock(&tx_fs->mutex); if (--tx_fs->refcnt) - goto out; + return; accel_psp_fs_tx_destroy(tx_fs); -out: - mutex_unlock(&tx_fs->mutex); } static void accel_psp_fs_cleanup_tx(struct mlx5e_psp_fs *fs) @@ -798,7 +771,6 @@ static void accel_psp_fs_cleanup_tx(struct mlx5e_psp_fs *fs) return; mlx5_fc_destroy(fs->mdev, tx_fs->tx_counter); - mutex_destroy(&tx_fs->mutex); WARN_ON(tx_fs->refcnt); kfree(tx_fs); fs->tx_fs = NULL; @@ -828,7 +800,6 @@ static int accel_psp_fs_init_tx(struct mlx5e_psp_fs *fs) return PTR_ERR(flow_counter); } tx_fs->tx_counter = flow_counter; - mutex_init(&tx_fs->mutex); tx_fs->ns = ns; fs->tx_fs = tx_fs; return 0; From 997dd8fef048634465bc46fb18131dc7d85d4b79 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Tue, 7 Jul 2026 16:08:46 +0300 Subject: [PATCH 0420/1433] net/mlx5e: psp: Remove unneeded ref counting for PSP steering PSP steering uses reference counting for TX and RX steering tables, but there's only a single reference for each acquired and thus the reference counting is unnecessary. Remove it and consolidate functions to simplify the code. Signed-off-by: Cosmin Ratiu Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Reviewed-by: Aleksandr Loktionov Link: https://patch.msgid.link/20260707130858.969928-4-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../mellanox/mlx5/core/en_accel/psp.c | 129 +++++------------- 1 file changed, 33 insertions(+), 96 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c index d4686b5af776..a69c4e2821e9 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c @@ -26,7 +26,6 @@ struct mlx5e_psp_tx { struct mlx5_flow_table *ft; struct mlx5_flow_group *fg; struct mlx5_flow_handle *rule; - u32 refcnt; struct mlx5_fc *tx_counter; }; @@ -46,7 +45,6 @@ struct mlx5e_accel_fs_psp_prot { struct mlx5_modify_hdr *rx_modify_hdr; struct mlx5_flow_destination default_dest; struct mlx5e_psp_rx_err rx_err; - u32 refcnt; struct mlx5_flow_handle *def_rule; }; @@ -469,75 +467,18 @@ static int accel_psp_fs_rx_create(struct mlx5e_psp_fs *fs, enum accel_fs_psp_typ return err; } -static int accel_psp_fs_rx_ft_get(struct mlx5e_psp_fs *fs, enum accel_fs_psp_type type) -{ - struct mlx5e_accel_fs_psp_prot *fs_prot; - struct mlx5_flow_destination dest = {}; - struct mlx5e_accel_fs_psp *accel_psp; - struct mlx5_ttc_table *ttc; - int err = 0; - - if (!fs || !fs->rx_fs) - return -EINVAL; - - ttc = mlx5e_fs_get_ttc(fs->fs, false); - accel_psp = fs->rx_fs; - fs_prot = &accel_psp->fs_prot[type]; - if (fs_prot->refcnt++) - return 0; - - /* create FT */ - err = accel_psp_fs_rx_create(fs, type); - if (err) { - fs_prot->refcnt--; - return err; - } - - /* connect */ - dest.type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE; - dest.ft = fs_prot->ft; - mlx5_ttc_fwd_dest(ttc, fs_psp2tt(type), &dest); - - return 0; -} - -static void accel_psp_fs_rx_ft_put(struct mlx5e_psp_fs *fs, enum accel_fs_psp_type type) -{ - struct mlx5_ttc_table *ttc = mlx5e_fs_get_ttc(fs->fs, false); - struct mlx5e_accel_fs_psp_prot *fs_prot; - struct mlx5e_accel_fs_psp *accel_psp; - - accel_psp = fs->rx_fs; - fs_prot = &accel_psp->fs_prot[type]; - if (--fs_prot->refcnt) - return; - - /* disconnect */ - mlx5_ttc_fwd_default_dest(ttc, fs_psp2tt(type)); - - /* remove FT */ - accel_psp_fs_rx_destroy(fs, type); -} - static void accel_psp_fs_cleanup_rx(struct mlx5e_psp_fs *fs) { - struct mlx5e_accel_fs_psp_prot *fs_prot; - struct mlx5e_accel_fs_psp *accel_psp; - enum accel_fs_psp_type i; + struct mlx5e_accel_fs_psp *accel_psp = fs->rx_fs; - if (!fs->rx_fs) + if (!accel_psp) return; - accel_psp = fs->rx_fs; mlx5_fc_destroy(fs->mdev, accel_psp->rx_bad_counter); mlx5_fc_destroy(fs->mdev, accel_psp->rx_err_counter); mlx5_fc_destroy(fs->mdev, accel_psp->rx_auth_fail_counter); mlx5_fc_destroy(fs->mdev, accel_psp->rx_counter); - for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) { - fs_prot = &accel_psp->fs_prot[i]; - WARN_ON(fs_prot->refcnt); - } - kfree(fs->rx_fs); + kfree(accel_psp); fs->rx_fs = NULL; } @@ -614,17 +555,27 @@ static int accel_psp_fs_init_rx(struct mlx5e_psp_fs *fs) void mlx5_accel_psp_fs_cleanup_rx_tables(struct mlx5e_priv *priv) { + struct mlx5_ttc_table *ttc; + struct mlx5e_psp_fs *fs; int i; if (!priv->psp) return; - for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) - accel_psp_fs_rx_ft_put(priv->psp->fs, i); + fs = priv->psp->fs; + ttc = mlx5e_fs_get_ttc(fs->fs, false); + for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) { + /* disconnect */ + mlx5_ttc_fwd_default_dest(ttc, fs_psp2tt(i)); + + /* remove FT */ + accel_psp_fs_rx_destroy(fs, i); + } } int mlx5_accel_psp_fs_init_rx_tables(struct mlx5e_priv *priv) { + struct mlx5_ttc_table *ttc; struct mlx5e_psp_fs *fs; int err, i; @@ -632,19 +583,30 @@ int mlx5_accel_psp_fs_init_rx_tables(struct mlx5e_priv *priv) return 0; fs = priv->psp->fs; + ttc = mlx5e_fs_get_ttc(fs->fs, false); + for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) { - err = accel_psp_fs_rx_ft_get(fs, i); + struct mlx5e_accel_fs_psp_prot *fs_prot; + struct mlx5_flow_destination dest = {}; + + /* create FT */ + err = accel_psp_fs_rx_create(fs, i); if (err) goto out_err; + + /* connect */ + dest.type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE; + fs_prot = &fs->rx_fs->fs_prot[i]; + dest.ft = fs_prot->ft; + mlx5_ttc_fwd_dest(ttc, fs_psp2tt(i), &dest); } return 0; out_err: - i--; - while (i >= 0) { - accel_psp_fs_rx_ft_put(fs, i); - --i; + while (--i >= 0) { + mlx5_ttc_fwd_default_dest(ttc, fs_psp2tt(i)); + accel_psp_fs_rx_destroy(fs, i); } return err; @@ -739,30 +701,6 @@ static void accel_psp_fs_tx_destroy(struct mlx5e_psp_tx *tx_fs) mlx5_destroy_flow_table(tx_fs->ft); } -static int accel_psp_fs_tx_ft_get(struct mlx5e_psp_fs *fs) -{ - struct mlx5e_psp_tx *tx_fs = fs->tx_fs; - int err; - - if (tx_fs->refcnt++) - return 0; - - err = accel_psp_fs_tx_create_ft_table(fs); - if (err) - tx_fs->refcnt--; - return err; -} - -static void accel_psp_fs_tx_ft_put(struct mlx5e_psp_fs *fs) -{ - struct mlx5e_psp_tx *tx_fs = fs->tx_fs; - - if (--tx_fs->refcnt) - return; - - accel_psp_fs_tx_destroy(tx_fs); -} - static void accel_psp_fs_cleanup_tx(struct mlx5e_psp_fs *fs) { struct mlx5e_psp_tx *tx_fs = fs->tx_fs; @@ -771,7 +709,6 @@ static void accel_psp_fs_cleanup_tx(struct mlx5e_psp_fs *fs) return; mlx5_fc_destroy(fs->mdev, tx_fs->tx_counter); - WARN_ON(tx_fs->refcnt); kfree(tx_fs); fs->tx_fs = NULL; } @@ -844,7 +781,7 @@ void mlx5_accel_psp_fs_cleanup_tx_tables(struct mlx5e_priv *priv) if (!priv->psp) return; - accel_psp_fs_tx_ft_put(priv->psp->fs); + accel_psp_fs_tx_destroy(priv->psp->fs->tx_fs); } int mlx5_accel_psp_fs_init_tx_tables(struct mlx5e_priv *priv) @@ -852,7 +789,7 @@ int mlx5_accel_psp_fs_init_tx_tables(struct mlx5e_priv *priv) if (!priv->psp) return 0; - return accel_psp_fs_tx_ft_get(priv->psp->fs); + return accel_psp_fs_tx_create_ft_table(priv->psp->fs); } static void mlx5e_accel_psp_fs_cleanup(struct mlx5e_psp_fs *fs) From 346bdf9caa2952ea0703f8fcd8f3772d6f60826e Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Tue, 7 Jul 2026 16:08:47 +0300 Subject: [PATCH 0421/1433] net/mlx5e: psp: Merge rx_err rule add/delete with ft create/delete The rx_err table is different than the others, having separate functions to create the flow rules in addition to the flow table. Merge the add/delete rules functions with the ft create/delete functions for consistency. Noop change. Signed-off-by: Cosmin Ratiu Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260707130858.969928-5-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../mellanox/mlx5/core/en_accel/psp.c | 78 +++++++------------ 1 file changed, 30 insertions(+), 48 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c index a69c4e2821e9..5c34c0be997a 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c @@ -73,8 +73,8 @@ static enum mlx5_traffic_types fs_psp2tt(enum accel_fs_psp_type i) return MLX5_TT_IPV6_UDP; } -static void accel_psp_fs_rx_err_del_rules(struct mlx5e_psp_fs *fs, - struct mlx5e_psp_rx_err *rx_err) +static void accel_psp_fs_rx_err_destroy_ft(struct mlx5e_psp_fs *fs, + struct mlx5e_psp_rx_err *rx_err) { if (rx_err->bad_rule) { mlx5_del_flow_rules(rx_err->bad_rule); @@ -100,12 +100,6 @@ static void accel_psp_fs_rx_err_del_rules(struct mlx5e_psp_fs *fs, mlx5_modify_header_dealloc(fs->mdev, rx_err->copy_modify_hdr); rx_err->copy_modify_hdr = NULL; } -} - -static void accel_psp_fs_rx_err_destroy_ft(struct mlx5e_psp_fs *fs, - struct mlx5e_psp_rx_err *rx_err) -{ - accel_psp_fs_rx_err_del_rules(fs, rx_err); if (rx_err->ft) { mlx5_destroy_flow_table(rx_err->ft); @@ -125,23 +119,40 @@ static void accel_psp_setup_syndrome_match(struct mlx5_flow_spec *spec, MLX5_SET(fte_match_set_misc2, misc_params_2, psp_syndrome, syndrome); } -static int accel_psp_fs_rx_err_add_rule(struct mlx5e_psp_fs *fs, - struct mlx5e_accel_fs_psp_prot *fs_prot, - struct mlx5e_psp_rx_err *rx_err) +static +int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, + struct mlx5e_accel_fs_psp_prot *fs_prot, + struct mlx5e_psp_rx_err *rx_err) { + struct mlx5_flow_namespace *ns = mlx5e_fs_get_ns(fs->fs, false); u8 action[MLX5_UN_SZ_BYTES(set_add_copy_action_in_auto)] = {}; + struct mlx5_flow_table_attr ft_attr = {}; struct mlx5_core_dev *mdev = fs->mdev; struct mlx5_flow_destination dest[2]; struct mlx5_flow_act flow_act = {}; struct mlx5_modify_hdr *modify_hdr; struct mlx5_flow_handle *fte; struct mlx5_flow_spec *spec; + struct mlx5_flow_table *ft; int err = 0; spec = kzalloc_obj(*spec); if (!spec) return -ENOMEM; + ft_attr.max_fte = 2; + ft_attr.autogroup.max_num_groups = 2; + ft_attr.level = MLX5E_ACCEL_FS_ESP_FT_ERR_LEVEL; + ft_attr.prio = MLX5E_NIC_PRIO; + ft = mlx5_create_auto_grouped_flow_table(ns, &ft_attr); + if (IS_ERR(ft)) { + err = PTR_ERR(ft); + mlx5_core_err(fs->mdev, + "fail to create psp rx inline ft err=%d\n", err); + goto out_spec; + } + rx_err->ft = ft; + /* Action to copy 7 bit psp_syndrome to regB[23:29] */ MLX5_SET(copy_action_in, action, action_type, MLX5_ACTION_TYPE_COPY); MLX5_SET(copy_action_in, action, src_field, MLX5_ACTION_IN_FIELD_PSP_SYNDROME); @@ -156,8 +167,9 @@ static int accel_psp_fs_rx_err_add_rule(struct mlx5e_psp_fs *fs, err = PTR_ERR(modify_hdr); mlx5_core_err(mdev, "fail to alloc psp copy modify_header_id err=%d\n", err); - goto out_spec; + goto out_ft; } + rx_err->copy_modify_hdr = modify_hdr; accel_psp_setup_syndrome_match(spec, PSP_OK); /* create fte */ @@ -173,7 +185,7 @@ static int accel_psp_fs_rx_err_add_rule(struct mlx5e_psp_fs *fs, if (IS_ERR(fte)) { err = PTR_ERR(fte); mlx5_core_err(mdev, "fail to add psp rx err copy rule err=%d\n", err); - goto out; + goto out_modhdr; } rx_err->rule = fte; @@ -230,8 +242,6 @@ static int accel_psp_fs_rx_err_add_rule(struct mlx5e_psp_fs *fs, } rx_err->bad_rule = fte; - rx_err->copy_modify_hdr = modify_hdr; - goto out_spec; out_drop_error_rule: @@ -243,45 +253,17 @@ static int accel_psp_fs_rx_err_add_rule(struct mlx5e_psp_fs *fs, out_drop_rule: mlx5_del_flow_rules(rx_err->rule); rx_err->rule = NULL; -out: +out_modhdr: mlx5_modify_header_dealloc(mdev, modify_hdr); + rx_err->copy_modify_hdr = NULL; +out_ft: + mlx5_destroy_flow_table(rx_err->ft); + rx_err->ft = NULL; out_spec: kfree(spec); return err; } -static int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, - struct mlx5e_accel_fs_psp_prot *fs_prot, - struct mlx5e_psp_rx_err *rx_err) -{ - struct mlx5_flow_namespace *ns = mlx5e_fs_get_ns(fs->fs, false); - struct mlx5_flow_table_attr ft_attr = {}; - struct mlx5_flow_table *ft; - int err; - - ft_attr.max_fte = 2; - ft_attr.autogroup.max_num_groups = 2; - ft_attr.level = MLX5E_ACCEL_FS_ESP_FT_ERR_LEVEL; // MLX5E_ACCEL_FS_TCP_FT_LEVEL - ft_attr.prio = MLX5E_NIC_PRIO; - ft = mlx5_create_auto_grouped_flow_table(ns, &ft_attr); - if (IS_ERR(ft)) { - err = PTR_ERR(ft); - mlx5_core_err(fs->mdev, "fail to create psp rx inline ft err=%d\n", err); - return err; - } - - rx_err->ft = ft; - err = accel_psp_fs_rx_err_add_rule(fs, fs_prot, rx_err); - if (err) - goto out_err; - - return 0; - -out_err: - mlx5_destroy_flow_table(ft); - rx_err->ft = NULL; - return err; -} static void accel_psp_fs_rx_fs_destroy(struct mlx5e_psp_fs *fs, struct mlx5e_accel_fs_psp_prot *fs_prot) From feaf6f90644efad66dce366c6816130996317609 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Tue, 7 Jul 2026 16:08:48 +0300 Subject: [PATCH 0422/1433] net/mlx5e: psp: Use helpers for steering object manipulation Add helper functions for creating and destroying PSP steering objects to reduce code duplication. This will become more relevant in future patches which add more steering tables/groups/flows. One nice side-effect of this is that the cleanup functions become idempotent and can be used instead of long goto chains. This further simplifies the code. Signed-off-by: Cosmin Ratiu Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260707130858.969928-6-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../mellanox/mlx5/core/en_accel/psp.c | 316 +++++++++--------- 1 file changed, 155 insertions(+), 161 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c index 5c34c0be997a..a1c7ca4ae722 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c @@ -73,38 +73,103 @@ static enum mlx5_traffic_types fs_psp2tt(enum accel_fs_psp_type i) return MLX5_TT_IPV6_UDP; } +static int accel_psp_fs_create_ft(struct mlx5e_psp_fs *fs, + struct mlx5_flow_table_attr *ft_attr, + struct mlx5_flow_table **ft) +{ + struct mlx5_flow_namespace *ns = mlx5e_fs_get_ns(fs->fs, false); + int err = 0; + + *ft = mlx5_create_auto_grouped_flow_table(ns, ft_attr); + if (IS_ERR(*ft)) { + err = PTR_ERR(*ft); + *ft = NULL; + } + + return err; +} + +static void accel_psp_fs_destroy_ft(struct mlx5_flow_table **table) +{ + if (*table) { + mlx5_destroy_flow_table(*table); + *table = NULL; + } +} + +static void accel_psp_fs_del_flow_rule(struct mlx5_flow_handle **rule) +{ + if (*rule) { + mlx5_del_flow_rules(*rule); + *rule = NULL; + } +} + +static int accel_psp_fs_create_miss_group(struct mlx5_flow_table *ft, + struct mlx5_flow_group **group) +{ + int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in); + u32 *in = kvzalloc(inlen, GFP_KERNEL); + int err = 0; + + if (!in) + return -ENOMEM; + + MLX5_SET(create_flow_group_in, in, start_flow_index, ft->max_fte - 1); + MLX5_SET(create_flow_group_in, in, end_flow_index, ft->max_fte - 1); + *group = mlx5_create_flow_group(ft, in); + if (IS_ERR(*group)) { + err = PTR_ERR(*group); + *group = NULL; + } + kvfree(in); + + return err; +} + +static void accel_psp_fs_destroy_flow_group(struct mlx5_flow_group **group) +{ + if (*group) { + mlx5_destroy_flow_group(*group); + *group = NULL; + } +} + +static int accel_psp_fs_create_counter(struct mlx5_core_dev *dev, + struct mlx5_fc **counter) +{ + *counter = mlx5_fc_create(dev, false); + if (IS_ERR(*counter)) { + int err = PTR_ERR(*counter); + + *counter = NULL; + return err; + } + + return 0; +} + +static void accel_psp_fs_destroy_counter(struct mlx5_core_dev *dev, + struct mlx5_fc **counter) +{ + if (*counter) { + mlx5_fc_destroy(dev, *counter); + *counter = NULL; + } +} + static void accel_psp_fs_rx_err_destroy_ft(struct mlx5e_psp_fs *fs, struct mlx5e_psp_rx_err *rx_err) { - if (rx_err->bad_rule) { - mlx5_del_flow_rules(rx_err->bad_rule); - rx_err->bad_rule = NULL; - } - - if (rx_err->err_rule) { - mlx5_del_flow_rules(rx_err->err_rule); - rx_err->err_rule = NULL; - } - - if (rx_err->auth_fail_rule) { - mlx5_del_flow_rules(rx_err->auth_fail_rule); - rx_err->auth_fail_rule = NULL; - } - - if (rx_err->rule) { - mlx5_del_flow_rules(rx_err->rule); - rx_err->rule = NULL; - } - + accel_psp_fs_del_flow_rule(&rx_err->bad_rule); + accel_psp_fs_del_flow_rule(&rx_err->err_rule); + accel_psp_fs_del_flow_rule(&rx_err->auth_fail_rule); + accel_psp_fs_del_flow_rule(&rx_err->rule); if (rx_err->copy_modify_hdr) { mlx5_modify_header_dealloc(fs->mdev, rx_err->copy_modify_hdr); rx_err->copy_modify_hdr = NULL; } - - if (rx_err->ft) { - mlx5_destroy_flow_table(rx_err->ft); - rx_err->ft = NULL; - } + accel_psp_fs_destroy_ft(&rx_err->ft); } static void accel_psp_setup_syndrome_match(struct mlx5_flow_spec *spec, @@ -124,7 +189,6 @@ int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, struct mlx5e_accel_fs_psp_prot *fs_prot, struct mlx5e_psp_rx_err *rx_err) { - struct mlx5_flow_namespace *ns = mlx5e_fs_get_ns(fs->fs, false); u8 action[MLX5_UN_SZ_BYTES(set_add_copy_action_in_auto)] = {}; struct mlx5_flow_table_attr ft_attr = {}; struct mlx5_core_dev *mdev = fs->mdev; @@ -133,7 +197,6 @@ int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, struct mlx5_modify_hdr *modify_hdr; struct mlx5_flow_handle *fte; struct mlx5_flow_spec *spec; - struct mlx5_flow_table *ft; int err = 0; spec = kzalloc_obj(*spec); @@ -144,14 +207,12 @@ int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, ft_attr.autogroup.max_num_groups = 2; ft_attr.level = MLX5E_ACCEL_FS_ESP_FT_ERR_LEVEL; ft_attr.prio = MLX5E_NIC_PRIO; - ft = mlx5_create_auto_grouped_flow_table(ns, &ft_attr); - if (IS_ERR(ft)) { - err = PTR_ERR(ft); + err = accel_psp_fs_create_ft(fs, &ft_attr, &rx_err->ft); + if (err) { mlx5_core_err(fs->mdev, "fail to create psp rx inline ft err=%d\n", err); - goto out_spec; + goto out_err; } - rx_err->ft = ft; /* Action to copy 7 bit psp_syndrome to regB[23:29] */ MLX5_SET(copy_action_in, action, action_type, MLX5_ACTION_TYPE_COPY); @@ -167,7 +228,7 @@ int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, err = PTR_ERR(modify_hdr); mlx5_core_err(mdev, "fail to alloc psp copy modify_header_id err=%d\n", err); - goto out_ft; + goto out_err; } rx_err->copy_modify_hdr = modify_hdr; @@ -184,8 +245,9 @@ int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, fte = mlx5_add_flow_rules(rx_err->ft, spec, &flow_act, dest, 2); if (IS_ERR(fte)) { err = PTR_ERR(fte); - mlx5_core_err(mdev, "fail to add psp rx err copy rule err=%d\n", err); - goto out_modhdr; + mlx5_core_err(mdev, "fail to add psp rx err rule err=%d\n", + err); + goto out_err; } rx_err->rule = fte; @@ -203,7 +265,7 @@ int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, err = PTR_ERR(fte); mlx5_core_err(mdev, "fail to add psp rx auth fail drop rule err=%d\n", err); - goto out_drop_rule; + goto out_err; } rx_err->auth_fail_rule = fte; @@ -221,7 +283,7 @@ int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, err = PTR_ERR(fte); mlx5_core_err(mdev, "fail to add psp rx framing err drop rule err=%d\n", err); - goto out_drop_auth_fail_rule; + goto out_err; } rx_err->err_rule = fte; @@ -238,27 +300,14 @@ int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, err = PTR_ERR(fte); mlx5_core_err(mdev, "fail to add psp rx misc. err drop rule err=%d\n", err); - goto out_drop_error_rule; + goto out_err; } rx_err->bad_rule = fte; goto out_spec; -out_drop_error_rule: - mlx5_del_flow_rules(rx_err->err_rule); - rx_err->err_rule = NULL; -out_drop_auth_fail_rule: - mlx5_del_flow_rules(rx_err->auth_fail_rule); - rx_err->auth_fail_rule = NULL; -out_drop_rule: - mlx5_del_flow_rules(rx_err->rule); - rx_err->rule = NULL; -out_modhdr: - mlx5_modify_header_dealloc(mdev, modify_hdr); - rx_err->copy_modify_hdr = NULL; -out_ft: - mlx5_destroy_flow_table(rx_err->ft); - rx_err->ft = NULL; +out_err: + accel_psp_fs_rx_err_destroy_ft(fs, rx_err); out_spec: kfree(spec); return err; @@ -268,30 +317,14 @@ int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, static void accel_psp_fs_rx_fs_destroy(struct mlx5e_psp_fs *fs, struct mlx5e_accel_fs_psp_prot *fs_prot) { - if (fs_prot->def_rule) { - mlx5_del_flow_rules(fs_prot->def_rule); - fs_prot->def_rule = NULL; - } - + accel_psp_fs_del_flow_rule(&fs_prot->def_rule); if (fs_prot->rx_modify_hdr) { mlx5_modify_header_dealloc(fs->mdev, fs_prot->rx_modify_hdr); fs_prot->rx_modify_hdr = NULL; } - - if (fs_prot->miss_rule) { - mlx5_del_flow_rules(fs_prot->miss_rule); - fs_prot->miss_rule = NULL; - } - - if (fs_prot->miss_group) { - mlx5_destroy_flow_group(fs_prot->miss_group); - fs_prot->miss_group = NULL; - } - - if (fs_prot->ft) { - mlx5_destroy_flow_table(fs_prot->ft); - fs_prot->ft = NULL; - } + accel_psp_fs_del_flow_rule(&fs_prot->miss_rule); + accel_psp_fs_destroy_flow_group(&fs_prot->miss_group); + accel_psp_fs_destroy_ft(&fs_prot->ft); } static void setup_fte_udp_psp(struct mlx5_flow_spec *spec, u16 udp_port) @@ -306,55 +339,42 @@ static void setup_fte_udp_psp(struct mlx5_flow_spec *spec, u16 udp_port) static int accel_psp_fs_rx_create_ft(struct mlx5e_psp_fs *fs, struct mlx5e_accel_fs_psp_prot *fs_prot) { - struct mlx5_flow_namespace *ns = mlx5e_fs_get_ns(fs->fs, false); u8 action[MLX5_UN_SZ_BYTES(set_add_copy_action_in_auto)] = {}; - int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in); struct mlx5_modify_hdr *modify_hdr = NULL; struct mlx5_flow_table_attr ft_attr = {}; struct mlx5_flow_destination dest = {}; struct mlx5_core_dev *mdev = fs->mdev; - struct mlx5_flow_group *miss_group; MLX5_DECLARE_FLOW_ACT(flow_act); struct mlx5_flow_handle *rule; struct mlx5_flow_spec *spec; - struct mlx5_flow_table *ft; - u32 *flow_group_in; int err = 0; - flow_group_in = kvzalloc(inlen, GFP_KERNEL); spec = kvzalloc_obj(*spec); - if (!flow_group_in || !spec) { - err = -ENOMEM; - goto out; - } + if (!spec) + return -ENOMEM; /* Create FT */ ft_attr.max_fte = 2; ft_attr.level = MLX5E_ACCEL_FS_ESP_FT_LEVEL; - ft_attr.prio = MLX5E_NIC_PRIO; ft_attr.autogroup.num_reserved_entries = 1; ft_attr.autogroup.max_num_groups = 1; - ft = mlx5_create_auto_grouped_flow_table(ns, &ft_attr); - if (IS_ERR(ft)) { - err = PTR_ERR(ft); + ft_attr.prio = MLX5E_NIC_PRIO; + err = accel_psp_fs_create_ft(fs, &ft_attr, &fs_prot->ft); + if (err) { mlx5_core_err(mdev, "fail to create psp rx ft err=%d\n", err); goto out_err; } - fs_prot->ft = ft; /* Create miss_group */ - MLX5_SET(create_flow_group_in, flow_group_in, start_flow_index, ft->max_fte - 1); - MLX5_SET(create_flow_group_in, flow_group_in, end_flow_index, ft->max_fte - 1); - miss_group = mlx5_create_flow_group(ft, flow_group_in); - if (IS_ERR(miss_group)) { - err = PTR_ERR(miss_group); + err = accel_psp_fs_create_miss_group(fs_prot->ft, &fs_prot->miss_group); + if (err) { mlx5_core_err(mdev, "fail to create psp rx miss_group err=%d\n", err); goto out_err; } - fs_prot->miss_group = miss_group; /* Create miss rule */ - rule = mlx5_add_flow_rules(ft, spec, &flow_act, &fs_prot->default_dest, 1); + rule = mlx5_add_flow_rules(fs_prot->ft, spec, &flow_act, + &fs_prot->default_dest, 1); if (IS_ERR(rule)) { err = PTR_ERR(rule); mlx5_core_err(mdev, "fail to create psp rx miss_rule err=%d\n", err); @@ -399,12 +419,11 @@ static int accel_psp_fs_rx_create_ft(struct mlx5e_psp_fs *fs, } fs_prot->def_rule = rule; - goto out; + goto out_spec; out_err: accel_psp_fs_rx_fs_destroy(fs, fs_prot); -out: - kvfree(flow_group_in); +out_spec: kvfree(spec); return err; } @@ -456,82 +475,61 @@ static void accel_psp_fs_cleanup_rx(struct mlx5e_psp_fs *fs) if (!accel_psp) return; - mlx5_fc_destroy(fs->mdev, accel_psp->rx_bad_counter); - mlx5_fc_destroy(fs->mdev, accel_psp->rx_err_counter); - mlx5_fc_destroy(fs->mdev, accel_psp->rx_auth_fail_counter); - mlx5_fc_destroy(fs->mdev, accel_psp->rx_counter); + accel_psp_fs_destroy_counter(fs->mdev, &accel_psp->rx_bad_counter); + accel_psp_fs_destroy_counter(fs->mdev, &accel_psp->rx_err_counter); + accel_psp_fs_destroy_counter(fs->mdev, + &accel_psp->rx_auth_fail_counter); + accel_psp_fs_destroy_counter(fs->mdev, &accel_psp->rx_counter); kfree(accel_psp); fs->rx_fs = NULL; } static int accel_psp_fs_init_rx(struct mlx5e_psp_fs *fs) { - struct mlx5e_accel_fs_psp *accel_psp; struct mlx5_core_dev *mdev = fs->mdev; - struct mlx5_fc *flow_counter; int err; - accel_psp = kzalloc_obj(*accel_psp); - if (!accel_psp) + fs->rx_fs = kzalloc_obj(*fs->rx_fs); + if (!fs->rx_fs) return -ENOMEM; - flow_counter = mlx5_fc_create(mdev, false); - if (IS_ERR(flow_counter)) { + err = accel_psp_fs_create_counter(mdev, &fs->rx_fs->rx_counter); + if (err) { mlx5_core_warn(mdev, - "fail to create psp rx flow counter err=%pe\n", - flow_counter); - err = PTR_ERR(flow_counter); + "fail to create psp rx flow counter err=%d\n", + err); goto out_err; } - accel_psp->rx_counter = flow_counter; - flow_counter = mlx5_fc_create(mdev, false); - if (IS_ERR(flow_counter)) { + err = accel_psp_fs_create_counter(mdev, + &fs->rx_fs->rx_auth_fail_counter); + if (err) { mlx5_core_warn(mdev, - "fail to create psp rx auth fail flow counter err=%pe\n", - flow_counter); - err = PTR_ERR(flow_counter); - goto out_counter_err; + "fail to create psp rx auth fail flow counter err=%d\n", + err); + goto out_err; } - accel_psp->rx_auth_fail_counter = flow_counter; - flow_counter = mlx5_fc_create(mdev, false); - if (IS_ERR(flow_counter)) { + err = accel_psp_fs_create_counter(mdev, &fs->rx_fs->rx_err_counter); + if (err) { mlx5_core_warn(mdev, - "fail to create psp rx error flow counter err=%pe\n", - flow_counter); - err = PTR_ERR(flow_counter); - goto out_auth_fail_counter_err; + "fail to create psp rx error flow counter err=%d\n", + err); + goto out_err; } - accel_psp->rx_err_counter = flow_counter; - flow_counter = mlx5_fc_create(mdev, false); - if (IS_ERR(flow_counter)) { + err = accel_psp_fs_create_counter(mdev, &fs->rx_fs->rx_bad_counter); + if (err) { mlx5_core_warn(mdev, - "fail to create psp rx bad flow counter err=%pe\n", - flow_counter); - err = PTR_ERR(flow_counter); - goto out_err_counter_err; + "fail to create psp rx bad flow counter err=%d\n", + err); + goto out_err; } - accel_psp->rx_bad_counter = flow_counter; - - fs->rx_fs = accel_psp; return 0; -out_err_counter_err: - mlx5_fc_destroy(mdev, accel_psp->rx_err_counter); - accel_psp->rx_err_counter = NULL; -out_auth_fail_counter_err: - mlx5_fc_destroy(mdev, accel_psp->rx_auth_fail_counter); - accel_psp->rx_auth_fail_counter = NULL; -out_counter_err: - mlx5_fc_destroy(mdev, accel_psp->rx_counter); - accel_psp->rx_counter = NULL; out_err: - kfree(accel_psp); - fs->rx_fs = NULL; - + accel_psp_fs_cleanup_rx(fs); return err; } @@ -675,12 +673,9 @@ static int accel_psp_fs_tx_create_ft_table(struct mlx5e_psp_fs *fs) static void accel_psp_fs_tx_destroy(struct mlx5e_psp_tx *tx_fs) { - if (!tx_fs->ft) - return; - - mlx5_del_flow_rules(tx_fs->rule); - mlx5_destroy_flow_group(tx_fs->fg); - mlx5_destroy_flow_table(tx_fs->ft); + accel_psp_fs_del_flow_rule(&tx_fs->rule); + accel_psp_fs_destroy_flow_group(&tx_fs->fg); + accel_psp_fs_destroy_ft(&tx_fs->ft); } static void accel_psp_fs_cleanup_tx(struct mlx5e_psp_fs *fs) @@ -690,7 +685,7 @@ static void accel_psp_fs_cleanup_tx(struct mlx5e_psp_fs *fs) if (!tx_fs) return; - mlx5_fc_destroy(fs->mdev, tx_fs->tx_counter); + accel_psp_fs_destroy_counter(fs->mdev, &tx_fs->tx_counter); kfree(tx_fs); fs->tx_fs = NULL; } @@ -699,8 +694,8 @@ static int accel_psp_fs_init_tx(struct mlx5e_psp_fs *fs) { struct mlx5_core_dev *mdev = fs->mdev; struct mlx5_flow_namespace *ns; - struct mlx5_fc *flow_counter; struct mlx5e_psp_tx *tx_fs; + int err; ns = mlx5_get_flow_namespace(mdev, MLX5_FLOW_NAMESPACE_EGRESS_IPSEC); if (!ns) @@ -710,15 +705,14 @@ static int accel_psp_fs_init_tx(struct mlx5e_psp_fs *fs) if (!tx_fs) return -ENOMEM; - flow_counter = mlx5_fc_create(mdev, false); - if (IS_ERR(flow_counter)) { + err = accel_psp_fs_create_counter(mdev, &tx_fs->tx_counter); + if (err) { mlx5_core_warn(mdev, - "fail to create psp tx flow counter err=%pe\n", - flow_counter); + "fail to create psp tx flow counter err=%d\n", + err); kfree(tx_fs); - return PTR_ERR(flow_counter); + return err; } - tx_fs->tx_counter = flow_counter; tx_fs->ns = ns; fs->tx_fs = tx_fs; return 0; From 46a1240b2e117c4e54b6c9687d9ec2b23b47bece Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Tue, 7 Jul 2026 16:08:49 +0300 Subject: [PATCH 0423/1433] net/mlx5e: psp: Factor out drop rule creation code There are 3 rules added with the same structure. Factor out common code into a helper function to reduce duplication. Signed-off-by: Cosmin Ratiu Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260707130858.969928-7-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../mellanox/mlx5/core/en_accel/psp.c | 65 ++++++++++--------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c index a1c7ca4ae722..bdf97e373b42 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c @@ -184,6 +184,27 @@ static void accel_psp_setup_syndrome_match(struct mlx5_flow_spec *spec, MLX5_SET(fte_match_set_misc2, misc_params_2, psp_syndrome, syndrome); } +static int accel_psp_add_drop_rule(struct mlx5_flow_table *ft, + struct mlx5_flow_spec *spec, + struct mlx5_fc *counter, + struct mlx5_flow_handle **rule) +{ + struct mlx5_flow_destination dest = {}; + struct mlx5_flow_act flow_act = {}; + int err = 0; + + flow_act.action = MLX5_FLOW_CONTEXT_ACTION_DROP | + MLX5_FLOW_CONTEXT_ACTION_COUNT; + dest.type = MLX5_FLOW_DESTINATION_TYPE_COUNTER; + dest.counter = counter; + *rule = mlx5_add_flow_rules(ft, spec, &flow_act, &dest, 1); + if (IS_ERR(*rule)) { + err = PTR_ERR(*rule); + *rule = NULL; + } + return err; +} + static int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, struct mlx5e_accel_fs_psp_prot *fs_prot, @@ -253,56 +274,38 @@ int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, /* add auth fail drop rule */ memset(spec, 0, sizeof(*spec)); - memset(&flow_act, 0, sizeof(flow_act)); accel_psp_setup_syndrome_match(spec, PSP_ICV_FAIL); - /* create fte */ - flow_act.action = MLX5_FLOW_CONTEXT_ACTION_DROP | - MLX5_FLOW_CONTEXT_ACTION_COUNT; - dest[0].type = MLX5_FLOW_DESTINATION_TYPE_COUNTER; - dest[0].counter = fs->rx_fs->rx_auth_fail_counter; - fte = mlx5_add_flow_rules(rx_err->ft, spec, &flow_act, dest, 1); - if (IS_ERR(fte)) { - err = PTR_ERR(fte); + err = accel_psp_add_drop_rule(rx_err->ft, spec, + fs->rx_fs->rx_auth_fail_counter, + &rx_err->auth_fail_rule); + if (err) { mlx5_core_err(mdev, "fail to add psp rx auth fail drop rule err=%d\n", err); goto out_err; } - rx_err->auth_fail_rule = fte; /* add framing drop rule */ memset(spec, 0, sizeof(*spec)); - memset(&flow_act, 0, sizeof(flow_act)); accel_psp_setup_syndrome_match(spec, PSP_BAD_TRAILER); - /* create fte */ - flow_act.action = MLX5_FLOW_CONTEXT_ACTION_DROP | - MLX5_FLOW_CONTEXT_ACTION_COUNT; - dest[0].type = MLX5_FLOW_DESTINATION_TYPE_COUNTER; - dest[0].counter = fs->rx_fs->rx_err_counter; - fte = mlx5_add_flow_rules(rx_err->ft, spec, &flow_act, dest, 1); - if (IS_ERR(fte)) { - err = PTR_ERR(fte); - mlx5_core_err(mdev, "fail to add psp rx framing err drop rule err=%d\n", + err = accel_psp_add_drop_rule(rx_err->ft, spec, + fs->rx_fs->rx_err_counter, + &rx_err->err_rule); + if (err) { + mlx5_core_err(mdev, "fail to add psp rx framing drop rule err=%d\n", err); goto out_err; } - rx_err->err_rule = fte; /* add misc. errors drop rule */ memset(spec, 0, sizeof(*spec)); - memset(&flow_act, 0, sizeof(flow_act)); - /* create fte */ - flow_act.action = MLX5_FLOW_CONTEXT_ACTION_DROP | - MLX5_FLOW_CONTEXT_ACTION_COUNT; - dest[0].type = MLX5_FLOW_DESTINATION_TYPE_COUNTER; - dest[0].counter = fs->rx_fs->rx_bad_counter; - fte = mlx5_add_flow_rules(rx_err->ft, spec, &flow_act, dest, 1); - if (IS_ERR(fte)) { - err = PTR_ERR(fte); + err = accel_psp_add_drop_rule(rx_err->ft, spec, + fs->rx_fs->rx_bad_counter, + &rx_err->bad_rule); + if (err) { mlx5_core_err(mdev, "fail to add psp rx misc. err drop rule err=%d\n", err); goto out_err; } - rx_err->bad_rule = fte; goto out_spec; From 9454d5edfd92b64b1a0e982ccccd838ffd972176 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Tue, 7 Jul 2026 16:08:50 +0300 Subject: [PATCH 0424/1433] net/mlx5e: psp: Remove unused PSP syndrome copy action The PSP error flow table copies the HW syndrome to metadata register B, but this value is never used in the RX path. Bad packets (auth fail, bad trailer) are dropped by HW via explicit drop rules before reaching software. Remove the syndrome copy action, the syndrome macro, and the dead syndrome check in the RX handler. Signed-off-by: Cosmin Ratiu Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260707130858.969928-8-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../mellanox/mlx5/core/en_accel/psp.c | 30 +------------------ .../mellanox/mlx5/core/en_accel/psp_rxtx.c | 11 ------- .../mellanox/mlx5/core/en_accel/psp_rxtx.h | 3 +- 3 files changed, 2 insertions(+), 42 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c index bdf97e373b42..534dba678761 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c @@ -35,7 +35,6 @@ struct mlx5e_psp_rx_err { struct mlx5_flow_handle *auth_fail_rule; struct mlx5_flow_handle *err_rule; struct mlx5_flow_handle *bad_rule; - struct mlx5_modify_hdr *copy_modify_hdr; }; struct mlx5e_accel_fs_psp_prot { @@ -165,10 +164,6 @@ static void accel_psp_fs_rx_err_destroy_ft(struct mlx5e_psp_fs *fs, accel_psp_fs_del_flow_rule(&rx_err->err_rule); accel_psp_fs_del_flow_rule(&rx_err->auth_fail_rule); accel_psp_fs_del_flow_rule(&rx_err->rule); - if (rx_err->copy_modify_hdr) { - mlx5_modify_header_dealloc(fs->mdev, rx_err->copy_modify_hdr); - rx_err->copy_modify_hdr = NULL; - } accel_psp_fs_destroy_ft(&rx_err->ft); } @@ -210,12 +205,10 @@ int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, struct mlx5e_accel_fs_psp_prot *fs_prot, struct mlx5e_psp_rx_err *rx_err) { - u8 action[MLX5_UN_SZ_BYTES(set_add_copy_action_in_auto)] = {}; struct mlx5_flow_table_attr ft_attr = {}; struct mlx5_core_dev *mdev = fs->mdev; struct mlx5_flow_destination dest[2]; struct mlx5_flow_act flow_act = {}; - struct mlx5_modify_hdr *modify_hdr; struct mlx5_flow_handle *fte; struct mlx5_flow_spec *spec; int err = 0; @@ -235,30 +228,10 @@ int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, goto out_err; } - /* Action to copy 7 bit psp_syndrome to regB[23:29] */ - MLX5_SET(copy_action_in, action, action_type, MLX5_ACTION_TYPE_COPY); - MLX5_SET(copy_action_in, action, src_field, MLX5_ACTION_IN_FIELD_PSP_SYNDROME); - MLX5_SET(copy_action_in, action, src_offset, 0); - MLX5_SET(copy_action_in, action, length, 7); - MLX5_SET(copy_action_in, action, dst_field, MLX5_ACTION_IN_FIELD_METADATA_REG_B); - MLX5_SET(copy_action_in, action, dst_offset, 23); - - modify_hdr = mlx5_modify_header_alloc(mdev, MLX5_FLOW_NAMESPACE_KERNEL, - 1, action); - if (IS_ERR(modify_hdr)) { - err = PTR_ERR(modify_hdr); - mlx5_core_err(mdev, - "fail to alloc psp copy modify_header_id err=%d\n", err); - goto out_err; - } - rx_err->copy_modify_hdr = modify_hdr; - accel_psp_setup_syndrome_match(spec, PSP_OK); /* create fte */ - flow_act.action = MLX5_FLOW_CONTEXT_ACTION_MOD_HDR | - MLX5_FLOW_CONTEXT_ACTION_FWD_DEST | + flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST | MLX5_FLOW_CONTEXT_ACTION_COUNT; - flow_act.modify_hdr = modify_hdr; dest[0].type = fs_prot->default_dest.type; dest[0].ft = fs_prot->default_dest.ft; dest[1].type = MLX5_FLOW_DESTINATION_TYPE_COUNTER; @@ -389,7 +362,6 @@ static int accel_psp_fs_rx_create_ft(struct mlx5e_psp_fs *fs, setup_fte_udp_psp(spec, PSP_DEFAULT_UDP_PORT); flow_act.crypto.type = MLX5_FLOW_CONTEXT_ENCRYPT_DECRYPT_TYPE_PSP; /* Set bit[31, 30] PSP marker */ - /* Set bit[29-23] psp_syndrome is set in error FT */ #define MLX5E_PSP_MARKER_BIT (BIT(30) | BIT(31)) MLX5_SET(set_action_in, action, action_type, MLX5_ACTION_TYPE_SET); MLX5_SET(set_action_in, action, field, MLX5_ACTION_IN_FIELD_METADATA_REG_B); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c index c2f9899d23a5..348fd7a96261 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c @@ -14,12 +14,6 @@ #include "en_accel/psp_rxtx.h" #include "en_accel/psp.h" -enum { - MLX5E_PSP_OFFLOAD_RX_SYNDROME_DECRYPTED, - MLX5E_PSP_OFFLOAD_RX_SYNDROME_AUTH_FAILED, - MLX5E_PSP_OFFLOAD_RX_SYNDROME_BAD_TRAILER, -}; - static void mlx5e_psp_set_swp(struct sk_buff *skb, struct mlx5e_accel_tx_psp_state *psp_st, struct mlx5_wqe_eth_seg *eseg) @@ -122,16 +116,11 @@ static bool mlx5e_psp_set_state(struct mlx5e_priv *priv, bool mlx5e_psp_offload_handle_rx_skb(struct net_device *netdev, struct sk_buff *skb, struct mlx5_cqe64 *cqe) { - u32 psp_meta_data = be32_to_cpu(cqe->ft_metadata); struct mlx5e_priv *priv = netdev_priv(netdev); u16 dev_id = priv->psp->psd->id; bool strip_icv = true; u8 generation = 0; - /* TBD: report errors as SW counters to ethtool, any further handling ? */ - if (MLX5_PSP_METADATA_SYNDROME(psp_meta_data) != MLX5E_PSP_OFFLOAD_RX_SYNDROME_DECRYPTED) - goto drop; - if (psp_dev_rcv(skb, dev_id, generation, strip_icv)) goto drop; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.h b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.h index 70289c921bd6..2b080c39cc37 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.h @@ -10,9 +10,8 @@ #include "en.h" #include "en/txrx.h" -/* Bit30: PSP marker, Bit29-23: PSP syndrome, Bit22-0: PSP obj id */ +/* Bit30: PSP marker, Bit22-0: PSP obj id */ #define MLX5_PSP_METADATA_MARKER(metadata) ((((metadata) >> 30) & 0x3) == 0x3) -#define MLX5_PSP_METADATA_SYNDROME(metadata) (((metadata) >> 23) & GENMASK(6, 0)) #define MLX5_PSP_METADATA_HANDLE(metadata) ((metadata) & GENMASK(22, 0)) struct mlx5e_accel_tx_psp_state { From 1b1a66b37e2c7b212f54697ebb4442214001b396 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Tue, 7 Jul 2026 16:08:51 +0300 Subject: [PATCH 0425/1433] net/mlx5e: psp: Rename and consolidate steering functions There are multiple naming inconsistencies and the code is fragmented and hard to follow. For example, the PSP TX steering structure is named 'mlx5e_psp_tx', but its RX counterpart is 'mlx5e_accel_fs_psp' and its protocol instantiation 'mlx5e_accel_fs_psp_prot', neither of which make it clear they relate to RX. This commit renames things to be more consistent, realigns declarations to abide by the xmas tree rule, and merges some functions to reduce fragmentation. Renamed: mlx5e_accel_fs_psp -> mlx5e_psp_rx mlx5e_accel_fs_psp_prot -> mlx5e_psp_rx_decrypt_table fs_prot -> decrypt accel_psp -> rx_fs mlx5e_psp_rx_err -> mlx5e_psp_rx_check_table mlx5e_psp_tx -> mlx5e_psp_tx_table def_rule -> rule Also renamed many functions with names of the form accel_psp_fs_A_B_C_..._verb, with A->B->C->... following a general->specific hierarchy. Full list: accel_psp_fs_rx_err_destroy_ft -> accel_psp_fs_rx_check_ft_destroy accel_psp_fs_rx_err_create_ft -> accel_psp_fs_rx_check_ft_create accel_psp_fs_rx_fs_destroy -> accel_psp_fs_rx_decrypt_ft_destroy accel_psp_fs_rx_create_ft -> accel_psp_fs_rx_decrypt_ft_create accel_psp_fs_tx_create_ft_table -> accel_psp_fs_tx_ft_create accel_psp_fs_tx_destroy -> accel_psp_fs_tx_ft_destroy accel_psp_fs_{init,cleanup}_{rx,tx} -> accel_psp_fs_{rx,tx}_{init,cleanup} Signed-off-by: Cosmin Ratiu Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260707130858.969928-9-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../mellanox/mlx5/core/en_accel/psp.c | 252 +++++++++--------- 1 file changed, 131 insertions(+), 121 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c index 534dba678761..c83d62724ff7 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c @@ -21,7 +21,7 @@ enum accel_psp_syndrome { PSP_BAD_TRAILER, }; -struct mlx5e_psp_tx { +struct mlx5e_psp_tx_table { struct mlx5_flow_namespace *ns; struct mlx5_flow_table *ft; struct mlx5_flow_group *fg; @@ -29,7 +29,7 @@ struct mlx5e_psp_tx { struct mlx5_fc *tx_counter; }; -struct mlx5e_psp_rx_err { +struct mlx5e_psp_rx_check_table { struct mlx5_flow_table *ft; struct mlx5_flow_handle *rule; struct mlx5_flow_handle *auth_fail_rule; @@ -37,18 +37,18 @@ struct mlx5e_psp_rx_err { struct mlx5_flow_handle *bad_rule; }; -struct mlx5e_accel_fs_psp_prot { +struct mlx5e_psp_rx_decrypt_table { struct mlx5_flow_table *ft; struct mlx5_flow_group *miss_group; struct mlx5_flow_handle *miss_rule; struct mlx5_modify_hdr *rx_modify_hdr; struct mlx5_flow_destination default_dest; - struct mlx5e_psp_rx_err rx_err; - struct mlx5_flow_handle *def_rule; + struct mlx5e_psp_rx_check_table check; + struct mlx5_flow_handle *rule; }; -struct mlx5e_accel_fs_psp { - struct mlx5e_accel_fs_psp_prot fs_prot[ACCEL_FS_PSP_NUM_TYPES]; +struct mlx5e_psp_rx { + struct mlx5e_psp_rx_decrypt_table decrypt[ACCEL_FS_PSP_NUM_TYPES]; struct mlx5_fc *rx_counter; struct mlx5_fc *rx_auth_fail_counter; struct mlx5_fc *rx_err_counter; @@ -57,10 +57,10 @@ struct mlx5e_accel_fs_psp { struct mlx5e_psp_fs { struct mlx5_core_dev *mdev; - struct mlx5e_psp_tx *tx_fs; + struct mlx5e_psp_tx_table *tx_fs; /* Rx manage */ struct mlx5e_flow_steering *fs; - struct mlx5e_accel_fs_psp *rx_fs; + struct mlx5e_psp_rx *rx_fs; }; /* PSP RX flow steering */ @@ -157,14 +157,15 @@ static void accel_psp_fs_destroy_counter(struct mlx5_core_dev *dev, } } -static void accel_psp_fs_rx_err_destroy_ft(struct mlx5e_psp_fs *fs, - struct mlx5e_psp_rx_err *rx_err) +static +void accel_psp_fs_rx_check_ft_destroy(struct mlx5e_psp_fs *fs, + struct mlx5e_psp_rx_check_table *check) { - accel_psp_fs_del_flow_rule(&rx_err->bad_rule); - accel_psp_fs_del_flow_rule(&rx_err->err_rule); - accel_psp_fs_del_flow_rule(&rx_err->auth_fail_rule); - accel_psp_fs_del_flow_rule(&rx_err->rule); - accel_psp_fs_destroy_ft(&rx_err->ft); + accel_psp_fs_del_flow_rule(&check->bad_rule); + accel_psp_fs_del_flow_rule(&check->err_rule); + accel_psp_fs_del_flow_rule(&check->auth_fail_rule); + accel_psp_fs_del_flow_rule(&check->rule); + accel_psp_fs_destroy_ft(&check->ft); } static void accel_psp_setup_syndrome_match(struct mlx5_flow_spec *spec, @@ -201,9 +202,9 @@ static int accel_psp_add_drop_rule(struct mlx5_flow_table *ft, } static -int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, - struct mlx5e_accel_fs_psp_prot *fs_prot, - struct mlx5e_psp_rx_err *rx_err) +int accel_psp_fs_rx_check_ft_create(struct mlx5e_psp_fs *fs, + struct mlx5e_psp_rx_decrypt_table *decrypt, + struct mlx5e_psp_rx_check_table *check) { struct mlx5_flow_table_attr ft_attr = {}; struct mlx5_core_dev *mdev = fs->mdev; @@ -221,10 +222,10 @@ int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, ft_attr.autogroup.max_num_groups = 2; ft_attr.level = MLX5E_ACCEL_FS_ESP_FT_ERR_LEVEL; ft_attr.prio = MLX5E_NIC_PRIO; - err = accel_psp_fs_create_ft(fs, &ft_attr, &rx_err->ft); + err = accel_psp_fs_create_ft(fs, &ft_attr, &check->ft); if (err) { mlx5_core_err(fs->mdev, - "fail to create psp rx inline ft err=%d\n", err); + "fail to create psp rx check ft err=%d\n", err); goto out_err; } @@ -232,27 +233,28 @@ int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, /* create fte */ flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST | MLX5_FLOW_CONTEXT_ACTION_COUNT; - dest[0].type = fs_prot->default_dest.type; - dest[0].ft = fs_prot->default_dest.ft; + dest[0].type = decrypt->default_dest.type; + dest[0].ft = decrypt->default_dest.ft; dest[1].type = MLX5_FLOW_DESTINATION_TYPE_COUNTER; dest[1].counter = fs->rx_fs->rx_counter; - fte = mlx5_add_flow_rules(rx_err->ft, spec, &flow_act, dest, 2); + fte = mlx5_add_flow_rules(check->ft, spec, &flow_act, dest, 2); if (IS_ERR(fte)) { err = PTR_ERR(fte); - mlx5_core_err(mdev, "fail to add psp rx err rule err=%d\n", + mlx5_core_err(mdev, "fail to add psp rx check ok rule err=%d\n", err); goto out_err; } - rx_err->rule = fte; + check->rule = fte; /* add auth fail drop rule */ memset(spec, 0, sizeof(*spec)); accel_psp_setup_syndrome_match(spec, PSP_ICV_FAIL); - err = accel_psp_add_drop_rule(rx_err->ft, spec, + err = accel_psp_add_drop_rule(check->ft, spec, fs->rx_fs->rx_auth_fail_counter, - &rx_err->auth_fail_rule); + &check->auth_fail_rule); if (err) { - mlx5_core_err(mdev, "fail to add psp rx auth fail drop rule err=%d\n", + mlx5_core_err(mdev, + "fail to add psp rx check auth fail drop rule err=%d\n", err); goto out_err; } @@ -260,22 +262,24 @@ int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, /* add framing drop rule */ memset(spec, 0, sizeof(*spec)); accel_psp_setup_syndrome_match(spec, PSP_BAD_TRAILER); - err = accel_psp_add_drop_rule(rx_err->ft, spec, + err = accel_psp_add_drop_rule(check->ft, spec, fs->rx_fs->rx_err_counter, - &rx_err->err_rule); + &check->err_rule); if (err) { - mlx5_core_err(mdev, "fail to add psp rx framing drop rule err=%d\n", + mlx5_core_err(mdev, + "fail to add psp rx check framing drop rule err=%d\n", err); goto out_err; } /* add misc. errors drop rule */ memset(spec, 0, sizeof(*spec)); - err = accel_psp_add_drop_rule(rx_err->ft, spec, + err = accel_psp_add_drop_rule(check->ft, spec, fs->rx_fs->rx_bad_counter, - &rx_err->bad_rule); + &check->bad_rule); if (err) { - mlx5_core_err(mdev, "fail to add psp rx misc. err drop rule err=%d\n", + mlx5_core_err(mdev, + "fail to add psp rx check misc. err drop rule err=%d\n", err); goto out_err; } @@ -283,24 +287,25 @@ int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, goto out_spec; out_err: - accel_psp_fs_rx_err_destroy_ft(fs, rx_err); + accel_psp_fs_rx_check_ft_destroy(fs, check); out_spec: kfree(spec); return err; } -static void accel_psp_fs_rx_fs_destroy(struct mlx5e_psp_fs *fs, - struct mlx5e_accel_fs_psp_prot *fs_prot) +static void +accel_psp_fs_rx_decrypt_ft_destroy(struct mlx5e_psp_fs *fs, + struct mlx5e_psp_rx_decrypt_table *decrypt) { - accel_psp_fs_del_flow_rule(&fs_prot->def_rule); - if (fs_prot->rx_modify_hdr) { - mlx5_modify_header_dealloc(fs->mdev, fs_prot->rx_modify_hdr); - fs_prot->rx_modify_hdr = NULL; + accel_psp_fs_del_flow_rule(&decrypt->rule); + if (decrypt->rx_modify_hdr) { + mlx5_modify_header_dealloc(fs->mdev, decrypt->rx_modify_hdr); + decrypt->rx_modify_hdr = NULL; } - accel_psp_fs_del_flow_rule(&fs_prot->miss_rule); - accel_psp_fs_destroy_flow_group(&fs_prot->miss_group); - accel_psp_fs_destroy_ft(&fs_prot->ft); + accel_psp_fs_del_flow_rule(&decrypt->miss_rule); + accel_psp_fs_destroy_flow_group(&decrypt->miss_group); + accel_psp_fs_destroy_ft(&decrypt->ft); } static void setup_fte_udp_psp(struct mlx5_flow_spec *spec, u16 udp_port) @@ -312,8 +317,9 @@ static void setup_fte_udp_psp(struct mlx5_flow_spec *spec, u16 udp_port) MLX5_SET(fte_match_set_lyr_2_4, spec->match_value, ip_protocol, IPPROTO_UDP); } -static int accel_psp_fs_rx_create_ft(struct mlx5e_psp_fs *fs, - struct mlx5e_accel_fs_psp_prot *fs_prot) +static int +accel_psp_fs_rx_decrypt_ft_create(struct mlx5e_psp_fs *fs, + struct mlx5e_psp_rx_decrypt_table *decrypt) { u8 action[MLX5_UN_SZ_BYTES(set_add_copy_action_in_auto)] = {}; struct mlx5_modify_hdr *modify_hdr = NULL; @@ -335,30 +341,36 @@ static int accel_psp_fs_rx_create_ft(struct mlx5e_psp_fs *fs, ft_attr.autogroup.num_reserved_entries = 1; ft_attr.autogroup.max_num_groups = 1; ft_attr.prio = MLX5E_NIC_PRIO; - err = accel_psp_fs_create_ft(fs, &ft_attr, &fs_prot->ft); + err = accel_psp_fs_create_ft(fs, &ft_attr, &decrypt->ft); if (err) { - mlx5_core_err(mdev, "fail to create psp rx ft err=%d\n", err); + mlx5_core_err(mdev, "fail to create psp rx decrypt ft err=%d\n", + err); goto out_err; } /* Create miss_group */ - err = accel_psp_fs_create_miss_group(fs_prot->ft, &fs_prot->miss_group); + err = accel_psp_fs_create_miss_group(decrypt->ft, &decrypt->miss_group); if (err) { - mlx5_core_err(mdev, "fail to create psp rx miss_group err=%d\n", err); + mlx5_core_err(mdev, + "fail to create psp rx decrypt miss_group err=%d\n", + err); goto out_err; } /* Create miss rule */ - rule = mlx5_add_flow_rules(fs_prot->ft, spec, &flow_act, - &fs_prot->default_dest, 1); + flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST; + rule = mlx5_add_flow_rules(decrypt->ft, spec, &flow_act, + &decrypt->default_dest, 1); if (IS_ERR(rule)) { err = PTR_ERR(rule); - mlx5_core_err(mdev, "fail to create psp rx miss_rule err=%d\n", err); + mlx5_core_err(mdev, + "fail to create psp rx decrypt miss_rule err=%d\n", + err); goto out_err; } - fs_prot->miss_rule = rule; + decrypt->miss_rule = rule; - /* Add default Rx psp rule */ + /* Add PSP RX decrypt rule */ setup_fte_udp_psp(spec, PSP_DEFAULT_UDP_PORT); flow_act.crypto.type = MLX5_FLOW_CONTEXT_ENCRYPT_DECRYPT_TYPE_PSP; /* Set bit[31, 30] PSP marker */ @@ -376,46 +388,44 @@ static int accel_psp_fs_rx_create_ft(struct mlx5e_psp_fs *fs, modify_hdr = NULL; goto out_err; } - fs_prot->rx_modify_hdr = modify_hdr; + decrypt->rx_modify_hdr = modify_hdr; flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST | MLX5_FLOW_CONTEXT_ACTION_CRYPTO_DECRYPT | MLX5_FLOW_CONTEXT_ACTION_MOD_HDR; flow_act.modify_hdr = modify_hdr; dest.type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE; - dest.ft = fs_prot->rx_err.ft; - rule = mlx5_add_flow_rules(fs_prot->ft, spec, &flow_act, &dest, 1); + dest.ft = decrypt->check.ft; + rule = mlx5_add_flow_rules(decrypt->ft, spec, &flow_act, &dest, 1); if (IS_ERR(rule)) { err = PTR_ERR(rule); - mlx5_core_err(mdev, - "fail to add psp rule Rx decryption, err=%d, flow_act.action = %#04X\n", - err, flow_act.action); + mlx5_core_err(mdev, "fail to add psp rx decrypt rule, err=%d\n", + err); goto out_err; } - fs_prot->def_rule = rule; + decrypt->rule = rule; goto out_spec; out_err: - accel_psp_fs_rx_fs_destroy(fs, fs_prot); + accel_psp_fs_rx_decrypt_ft_destroy(fs, decrypt); out_spec: kvfree(spec); return err; } -static int accel_psp_fs_rx_destroy(struct mlx5e_psp_fs *fs, enum accel_fs_psp_type type) +static int accel_psp_fs_rx_destroy(struct mlx5e_psp_fs *fs, + enum accel_fs_psp_type type) { - struct mlx5e_accel_fs_psp_prot *fs_prot; - struct mlx5e_accel_fs_psp *accel_psp; - - accel_psp = fs->rx_fs; + struct mlx5e_psp_rx_decrypt_table *decrypt; + struct mlx5e_psp_rx *rx_fs = fs->rx_fs; /* The netdev unreg already happened, so all offloaded rule are already removed */ - fs_prot = &accel_psp->fs_prot[type]; + decrypt = &rx_fs->decrypt[type]; - accel_psp_fs_rx_fs_destroy(fs, fs_prot); + accel_psp_fs_rx_decrypt_ft_destroy(fs, decrypt); - accel_psp_fs_rx_err_destroy_ft(fs, &fs_prot->rx_err); + accel_psp_fs_rx_check_ft_destroy(fs, &decrypt->check); return 0; } @@ -423,43 +433,45 @@ static int accel_psp_fs_rx_destroy(struct mlx5e_psp_fs *fs, enum accel_fs_psp_ty static int accel_psp_fs_rx_create(struct mlx5e_psp_fs *fs, enum accel_fs_psp_type type) { struct mlx5_ttc_table *ttc = mlx5e_fs_get_ttc(fs->fs, false); - struct mlx5e_accel_fs_psp_prot *fs_prot; - struct mlx5e_accel_fs_psp *accel_psp; + struct mlx5e_psp_rx_decrypt_table *decrypt; + struct mlx5e_psp_rx *rx_fs = fs->rx_fs; int err; - accel_psp = fs->rx_fs; - fs_prot = &accel_psp->fs_prot[type]; + decrypt = &rx_fs->decrypt[type]; + decrypt->default_dest = mlx5_ttc_get_default_dest(ttc, + fs_psp2tt(type)); - fs_prot->default_dest = mlx5_ttc_get_default_dest(ttc, fs_psp2tt(type)); - - err = accel_psp_fs_rx_err_create_ft(fs, fs_prot, &fs_prot->rx_err); + err = accel_psp_fs_rx_check_ft_create(fs, decrypt, &decrypt->check); if (err) return err; - err = accel_psp_fs_rx_create_ft(fs, fs_prot); + err = accel_psp_fs_rx_decrypt_ft_create(fs, decrypt); if (err) - accel_psp_fs_rx_err_destroy_ft(fs, &fs_prot->rx_err); + goto out_err_ft; + return 0; + +out_err_ft: + accel_psp_fs_rx_check_ft_destroy(fs, &decrypt->check); return err; } -static void accel_psp_fs_cleanup_rx(struct mlx5e_psp_fs *fs) +static void accel_psp_fs_rx_cleanup(struct mlx5e_psp_fs *fs) { - struct mlx5e_accel_fs_psp *accel_psp = fs->rx_fs; + struct mlx5e_psp_rx *rx_fs = fs->rx_fs; - if (!accel_psp) + if (!rx_fs) return; - accel_psp_fs_destroy_counter(fs->mdev, &accel_psp->rx_bad_counter); - accel_psp_fs_destroy_counter(fs->mdev, &accel_psp->rx_err_counter); - accel_psp_fs_destroy_counter(fs->mdev, - &accel_psp->rx_auth_fail_counter); - accel_psp_fs_destroy_counter(fs->mdev, &accel_psp->rx_counter); - kfree(accel_psp); + accel_psp_fs_destroy_counter(fs->mdev, &rx_fs->rx_bad_counter); + accel_psp_fs_destroy_counter(fs->mdev, &rx_fs->rx_err_counter); + accel_psp_fs_destroy_counter(fs->mdev, &rx_fs->rx_auth_fail_counter); + accel_psp_fs_destroy_counter(fs->mdev, &rx_fs->rx_counter); + kfree(rx_fs); fs->rx_fs = NULL; } -static int accel_psp_fs_init_rx(struct mlx5e_psp_fs *fs) +static int accel_psp_fs_rx_init(struct mlx5e_psp_fs *fs) { struct mlx5_core_dev *mdev = fs->mdev; int err; @@ -504,7 +516,7 @@ static int accel_psp_fs_init_rx(struct mlx5e_psp_fs *fs) return 0; out_err: - accel_psp_fs_cleanup_rx(fs); + accel_psp_fs_rx_cleanup(fs); return err; } @@ -541,7 +553,7 @@ int mlx5_accel_psp_fs_init_rx_tables(struct mlx5e_priv *priv) ttc = mlx5e_fs_get_ttc(fs->fs, false); for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) { - struct mlx5e_accel_fs_psp_prot *fs_prot; + struct mlx5e_psp_rx_decrypt_table *decrypt; struct mlx5_flow_destination dest = {}; /* create FT */ @@ -551,8 +563,8 @@ int mlx5_accel_psp_fs_init_rx_tables(struct mlx5e_priv *priv) /* connect */ dest.type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE; - fs_prot = &fs->rx_fs->fs_prot[i]; - dest.ft = fs_prot->ft; + decrypt = &fs->rx_fs->decrypt[i]; + dest.ft = decrypt->ft; mlx5_ttc_fwd_dest(ttc, fs_psp2tt(i), &dest); } @@ -567,17 +579,17 @@ int mlx5_accel_psp_fs_init_rx_tables(struct mlx5e_priv *priv) return err; } -static int accel_psp_fs_tx_create_ft_table(struct mlx5e_psp_fs *fs) +static int accel_psp_fs_tx_ft_create(struct mlx5e_psp_fs *fs) { int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in); struct mlx5_flow_table_attr ft_attr = {}; struct mlx5_flow_destination dest = {}; struct mlx5_core_dev *mdev = fs->mdev; struct mlx5_flow_act flow_act = {}; + struct mlx5e_psp_tx_table *tx_fs; u32 *in, *mc, *outer_headers_c; struct mlx5_flow_handle *rule; struct mlx5_flow_spec *spec; - struct mlx5e_psp_tx *tx_fs; struct mlx5_flow_table *ft; struct mlx5_flow_group *fg; int err = 0; @@ -646,16 +658,16 @@ static int accel_psp_fs_tx_create_ft_table(struct mlx5e_psp_fs *fs) return err; } -static void accel_psp_fs_tx_destroy(struct mlx5e_psp_tx *tx_fs) +static void accel_psp_fs_tx_ft_destroy(struct mlx5e_psp_tx_table *tx_fs) { accel_psp_fs_del_flow_rule(&tx_fs->rule); accel_psp_fs_destroy_flow_group(&tx_fs->fg); accel_psp_fs_destroy_ft(&tx_fs->ft); } -static void accel_psp_fs_cleanup_tx(struct mlx5e_psp_fs *fs) +static void accel_psp_fs_tx_cleanup(struct mlx5e_psp_fs *fs) { - struct mlx5e_psp_tx *tx_fs = fs->tx_fs; + struct mlx5e_psp_tx_table *tx_fs = fs->tx_fs; if (!tx_fs) return; @@ -665,11 +677,11 @@ static void accel_psp_fs_cleanup_tx(struct mlx5e_psp_fs *fs) fs->tx_fs = NULL; } -static int accel_psp_fs_init_tx(struct mlx5e_psp_fs *fs) +static int accel_psp_fs_tx_init(struct mlx5e_psp_fs *fs) { struct mlx5_core_dev *mdev = fs->mdev; + struct mlx5e_psp_tx_table *tx_fs; struct mlx5_flow_namespace *ns; - struct mlx5e_psp_tx *tx_fs; int err; ns = mlx5_get_flow_namespace(mdev, MLX5_FLOW_NAMESPACE_EGRESS_IPSEC); @@ -697,32 +709,30 @@ static void mlx5e_accel_psp_fs_get_stats_fill(struct mlx5e_priv *priv, struct mlx5e_psp_stats *stats) { - struct mlx5e_psp_tx *tx_fs = priv->psp->fs->tx_fs; + struct mlx5e_psp_tx_table *tx_fs = priv->psp->fs->tx_fs; + struct mlx5e_psp_rx *rx_fs = priv->psp->fs->rx_fs; struct mlx5_core_dev *mdev = priv->mdev; - struct mlx5e_accel_fs_psp *accel_psp; - - accel_psp = (struct mlx5e_accel_fs_psp *)priv->psp->fs->rx_fs; if (tx_fs->tx_counter) mlx5_fc_query(mdev, tx_fs->tx_counter, &stats->psp_tx_pkts, &stats->psp_tx_bytes); - if (accel_psp->rx_counter) - mlx5_fc_query(mdev, accel_psp->rx_counter, &stats->psp_rx_pkts, + if (rx_fs->rx_counter) + mlx5_fc_query(mdev, rx_fs->rx_counter, &stats->psp_rx_pkts, &stats->psp_rx_bytes); - if (accel_psp->rx_auth_fail_counter) - mlx5_fc_query(mdev, accel_psp->rx_auth_fail_counter, + if (rx_fs->rx_auth_fail_counter) + mlx5_fc_query(mdev, rx_fs->rx_auth_fail_counter, &stats->psp_rx_pkts_auth_fail, &stats->psp_rx_bytes_auth_fail); - if (accel_psp->rx_err_counter) - mlx5_fc_query(mdev, accel_psp->rx_err_counter, + if (rx_fs->rx_err_counter) + mlx5_fc_query(mdev, rx_fs->rx_err_counter, &stats->psp_rx_pkts_frame_err, &stats->psp_rx_bytes_frame_err); - if (accel_psp->rx_bad_counter) - mlx5_fc_query(mdev, accel_psp->rx_bad_counter, + if (rx_fs->rx_bad_counter) + mlx5_fc_query(mdev, rx_fs->rx_bad_counter, &stats->psp_rx_pkts_drop, &stats->psp_rx_bytes_drop); } @@ -732,7 +742,7 @@ void mlx5_accel_psp_fs_cleanup_tx_tables(struct mlx5e_priv *priv) if (!priv->psp) return; - accel_psp_fs_tx_destroy(priv->psp->fs->tx_fs); + accel_psp_fs_tx_ft_destroy(priv->psp->fs->tx_fs); } int mlx5_accel_psp_fs_init_tx_tables(struct mlx5e_priv *priv) @@ -740,13 +750,13 @@ int mlx5_accel_psp_fs_init_tx_tables(struct mlx5e_priv *priv) if (!priv->psp) return 0; - return accel_psp_fs_tx_create_ft_table(priv->psp->fs); + return accel_psp_fs_tx_ft_create(priv->psp->fs); } static void mlx5e_accel_psp_fs_cleanup(struct mlx5e_psp_fs *fs) { - accel_psp_fs_cleanup_rx(fs); - accel_psp_fs_cleanup_tx(fs); + accel_psp_fs_rx_cleanup(fs); + accel_psp_fs_tx_cleanup(fs); kfree(fs); } @@ -760,19 +770,19 @@ static struct mlx5e_psp_fs *mlx5e_accel_psp_fs_init(struct mlx5e_priv *priv) return ERR_PTR(-ENOMEM); fs->mdev = priv->mdev; - err = accel_psp_fs_init_tx(fs); + err = accel_psp_fs_tx_init(fs); if (err) goto err_tx; fs->fs = priv->fs; - err = accel_psp_fs_init_rx(fs); + err = accel_psp_fs_rx_init(fs); if (err) goto err_rx; return fs; err_rx: - accel_psp_fs_cleanup_tx(fs); + accel_psp_fs_tx_cleanup(fs); err_tx: kfree(fs); return ERR_PTR(err); From 25f756e29feda96056b6408bbd2941dba9325035 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Tue, 7 Jul 2026 16:08:52 +0300 Subject: [PATCH 0426/1433] net/mlx5e: psp: Adjust rx_check FT size and use a drop_group The rx_check ft was requesting max_fte == 2, but it created 4 entries. While this accidentally works, it's not accurate, so change that and use the correct number of entries. Also use an explicit drop_group for the last match(*) drop rule. Signed-off-by: Cosmin Ratiu Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260707130858.969928-10-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/mellanox/mlx5/core/en_accel/psp.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c index c83d62724ff7..f8b289c50a42 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c @@ -31,6 +31,7 @@ struct mlx5e_psp_tx_table { struct mlx5e_psp_rx_check_table { struct mlx5_flow_table *ft; + struct mlx5_flow_group *drop_group; struct mlx5_flow_handle *rule; struct mlx5_flow_handle *auth_fail_rule; struct mlx5_flow_handle *err_rule; @@ -165,6 +166,7 @@ void accel_psp_fs_rx_check_ft_destroy(struct mlx5e_psp_fs *fs, accel_psp_fs_del_flow_rule(&check->err_rule); accel_psp_fs_del_flow_rule(&check->auth_fail_rule); accel_psp_fs_del_flow_rule(&check->rule); + accel_psp_fs_destroy_flow_group(&check->drop_group); accel_psp_fs_destroy_ft(&check->ft); } @@ -218,7 +220,8 @@ int accel_psp_fs_rx_check_ft_create(struct mlx5e_psp_fs *fs, if (!spec) return -ENOMEM; - ft_attr.max_fte = 2; + ft_attr.max_fte = 4; + ft_attr.autogroup.num_reserved_entries = 1; ft_attr.autogroup.max_num_groups = 2; ft_attr.level = MLX5E_ACCEL_FS_ESP_FT_ERR_LEVEL; ft_attr.prio = MLX5E_NIC_PRIO; @@ -229,6 +232,14 @@ int accel_psp_fs_rx_check_ft_create(struct mlx5e_psp_fs *fs, goto out_err; } + err = accel_psp_fs_create_miss_group(check->ft, &check->drop_group); + if (err) { + mlx5_core_err(fs->mdev, + "fail to create psp rx check drop group err=%d\n", + err); + goto out_err; + } + accel_psp_setup_syndrome_match(spec, PSP_OK); /* create fte */ flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST | From 4bb6e87aceeab945a4db4aabd1b486f8d88f6262 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Tue, 7 Jul 2026 16:08:53 +0300 Subject: [PATCH 0427/1433] net/mlx5e: psp: Add an RX steering table Successfully decrypted PSP traffic is currently forwarded to the UDP v4/v6 TTC default destination from its respective PSP rx_check table. In preparation for flattening out RX steering and for decapsulation support (which needs to handle non-UDP traffic as well), add an RX table which directs traffic to either the UDP v4/v6 default TTC destinations, or back to the TTC table itself for further processing. There can be no loops as non-UDP traffic will not go through PSP processing again. This is now used as a destination for successfully decrypted PSP packets. The rx_counter is also incremented there, freeing the rx_check rule for PSP_OK for atomic destination update in a future patch. Use this opportunity to separate RX flow table levels from IPsec, as reusing random IPsec ft levels as PSP isn't clear and now is a good opportunity to separate them. Signed-off-by: Cosmin Ratiu Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260707130858.969928-11-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/mellanox/mlx5/core/en/fs.h | 7 +- .../mellanox/mlx5/core/en_accel/psp.c | 149 +++++++++++++++--- 2 files changed, 136 insertions(+), 20 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/fs.h b/drivers/net/ethernet/mellanox/mlx5/core/en/fs.h index 091b80a67189..4973fb473ff0 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/fs.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/fs.h @@ -88,13 +88,18 @@ enum { #ifdef CONFIG_MLX5_EN_ARFS MLX5E_ARFS_FT_LEVEL = MLX5E_INNER_TTC_FT_LEVEL + 1, #endif -#if defined(CONFIG_MLX5_EN_IPSEC) || defined(CONFIG_MLX5_EN_PSP) +#if defined(CONFIG_MLX5_EN_IPSEC) MLX5E_ACCEL_FS_ESP_FT_LEVEL = MLX5E_INNER_TTC_FT_LEVEL + 1, MLX5E_ACCEL_FS_ESP_FT_ERR_LEVEL, MLX5E_ACCEL_FS_POL_FT_LEVEL, MLX5E_ACCEL_FS_POL_MISS_FT_LEVEL, MLX5E_ACCEL_FS_ESP_FT_ROCE_LEVEL, #endif +#if defined(CONFIG_MLX5_EN_PSP) + MLX5E_ACCEL_FS_PSP_FT_LEVEL = MLX5E_INNER_TTC_FT_LEVEL + 1, + MLX5E_ACCEL_FS_PSP_ERR_FT_LEVEL, + MLX5E_ACCEL_FS_PSP_RX_FT_LEVEL, +#endif }; struct mlx5e_flow_steering; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c index f8b289c50a42..c45241025b16 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c @@ -43,7 +43,6 @@ struct mlx5e_psp_rx_decrypt_table { struct mlx5_flow_group *miss_group; struct mlx5_flow_handle *miss_rule; struct mlx5_modify_hdr *rx_modify_hdr; - struct mlx5_flow_destination default_dest; struct mlx5e_psp_rx_check_table check; struct mlx5_flow_handle *rule; }; @@ -56,12 +55,21 @@ struct mlx5e_psp_rx { struct mlx5_fc *rx_bad_counter; }; +struct mlx5e_psp_rx_table { + struct mlx5_flow_table *ft; + struct mlx5_flow_group *miss_group; + struct mlx5_flow_handle *miss_rule; + struct mlx5_flow_handle *udp_rules[ACCEL_FS_PSP_NUM_TYPES]; +}; + struct mlx5e_psp_fs { struct mlx5_core_dev *mdev; struct mlx5e_psp_tx_table *tx_fs; /* Rx manage */ struct mlx5e_flow_steering *fs; struct mlx5e_psp_rx *rx_fs; + + struct mlx5e_psp_rx_table rx; }; /* PSP RX flow steering */ @@ -158,6 +166,106 @@ static void accel_psp_fs_destroy_counter(struct mlx5_core_dev *dev, } } +static void accel_psp_fs_rx_ft_destroy(struct mlx5e_psp_rx_table *rx) +{ + int i; + + for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) + accel_psp_fs_del_flow_rule(&rx->udp_rules[i]); + accel_psp_fs_del_flow_rule(&rx->miss_rule); + accel_psp_fs_destroy_flow_group(&rx->miss_group); + accel_psp_fs_destroy_ft(&rx->ft); +} + +static int accel_psp_fs_rx_ft_create(struct mlx5e_psp_fs *fs, + struct mlx5e_psp_rx_table *rx) +{ + struct mlx5_ttc_table *ttc = mlx5e_fs_get_ttc(fs->fs, false); + struct mlx5_flow_destination dest[2] = {}; + struct mlx5_flow_table_attr ft_attr = {}; + struct mlx5_core_dev *mdev = fs->mdev; + MLX5_DECLARE_FLOW_ACT(flow_act); + struct mlx5_flow_handle *rule; + struct mlx5_flow_spec *spec; + int i, err = 0; + + spec = kzalloc_obj(*spec); + if (!spec) + return -ENOMEM; + + ft_attr.max_fte = 1 + ACCEL_FS_PSP_NUM_TYPES; + ft_attr.level = MLX5E_ACCEL_FS_PSP_RX_FT_LEVEL; + ft_attr.prio = MLX5E_NIC_PRIO; + ft_attr.autogroup.num_reserved_entries = 1; + err = accel_psp_fs_create_ft(fs, &ft_attr, &rx->ft); + if (err) { + mlx5_core_err(mdev, "fail to create psp rx ft err=%d\n", err); + goto out_err; + } + + err = accel_psp_fs_create_miss_group(rx->ft, &rx->miss_group); + if (err) { + mlx5_core_err(mdev, "fail to create psp rx miss_group err=%d\n", + err); + goto out_err; + } + + /* Add miss rule */ + flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST | + MLX5_FLOW_CONTEXT_ACTION_COUNT; + flow_act.flags = FLOW_ACT_IGNORE_FLOW_LEVEL; + dest[0].type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE; + dest[0].ft = mlx5_get_ttc_flow_table(ttc); + dest[1].type = MLX5_FLOW_DESTINATION_TYPE_COUNTER; + dest[1].counter = fs->rx_fs->rx_counter; + rule = mlx5_add_flow_rules(rx->ft, NULL, &flow_act, dest, 2); + if (IS_ERR(rule)) { + err = PTR_ERR(rule); + mlx5_core_err(mdev, "fail to create psp rx rule, err=%d\n", + err); + goto out_err; + } + rx->miss_rule = rule; + + /* Add UDP v4/v6 rules */ + spec->match_criteria_enable = MLX5_MATCH_OUTER_HEADERS; + MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria, + outer_headers.ip_version); + MLX5_SET_TO_ONES(fte_match_set_lyr_2_4, spec->match_criteria, + ip_protocol); + MLX5_SET(fte_match_set_lyr_2_4, spec->match_value, ip_protocol, + IPPROTO_UDP); + flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST | + MLX5_FLOW_CONTEXT_ACTION_COUNT; + flow_act.flags = 0; + for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) { + int version = i == ACCEL_FS_PSP4 ? 4 : 6; + + MLX5_SET(fte_match_param, spec->match_value, + outer_headers.ip_version, version); + dest[0] = mlx5_ttc_get_default_dest(ttc, fs_psp2tt(i)); + dest[1].type = MLX5_FLOW_DESTINATION_TYPE_COUNTER; + dest[1].counter = fs->rx_fs->rx_counter; + rule = mlx5_add_flow_rules(rx->ft, spec, &flow_act, dest, + 2); + if (IS_ERR(rule)) { + err = PTR_ERR(rule); + mlx5_core_err(mdev, + "fail to create psp rx UDP%d rule err=%d\n", + version, err); + goto out_err; + } + rx->udp_rules[i] = rule; + } + goto out_spec; + +out_err: + accel_psp_fs_rx_ft_destroy(rx); +out_spec: + kvfree(spec); + return err; +} + static void accel_psp_fs_rx_check_ft_destroy(struct mlx5e_psp_fs *fs, struct mlx5e_psp_rx_check_table *check) @@ -205,12 +313,11 @@ static int accel_psp_add_drop_rule(struct mlx5_flow_table *ft, static int accel_psp_fs_rx_check_ft_create(struct mlx5e_psp_fs *fs, - struct mlx5e_psp_rx_decrypt_table *decrypt, struct mlx5e_psp_rx_check_table *check) { struct mlx5_flow_table_attr ft_attr = {}; + struct mlx5_flow_destination dest = {}; struct mlx5_core_dev *mdev = fs->mdev; - struct mlx5_flow_destination dest[2]; struct mlx5_flow_act flow_act = {}; struct mlx5_flow_handle *fte; struct mlx5_flow_spec *spec; @@ -223,7 +330,7 @@ int accel_psp_fs_rx_check_ft_create(struct mlx5e_psp_fs *fs, ft_attr.max_fte = 4; ft_attr.autogroup.num_reserved_entries = 1; ft_attr.autogroup.max_num_groups = 2; - ft_attr.level = MLX5E_ACCEL_FS_ESP_FT_ERR_LEVEL; + ft_attr.level = MLX5E_ACCEL_FS_PSP_ERR_FT_LEVEL; ft_attr.prio = MLX5E_NIC_PRIO; err = accel_psp_fs_create_ft(fs, &ft_attr, &check->ft); if (err) { @@ -242,13 +349,10 @@ int accel_psp_fs_rx_check_ft_create(struct mlx5e_psp_fs *fs, accel_psp_setup_syndrome_match(spec, PSP_OK); /* create fte */ - flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST | - MLX5_FLOW_CONTEXT_ACTION_COUNT; - dest[0].type = decrypt->default_dest.type; - dest[0].ft = decrypt->default_dest.ft; - dest[1].type = MLX5_FLOW_DESTINATION_TYPE_COUNTER; - dest[1].counter = fs->rx_fs->rx_counter; - fte = mlx5_add_flow_rules(check->ft, spec, &flow_act, dest, 2); + flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST; + dest.type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE; + dest.ft = fs->rx.ft; + fte = mlx5_add_flow_rules(check->ft, spec, &flow_act, &dest, 1); if (IS_ERR(fte)) { err = PTR_ERR(fte); mlx5_core_err(mdev, "fail to add psp rx check ok rule err=%d\n", @@ -330,7 +434,8 @@ static void setup_fte_udp_psp(struct mlx5_flow_spec *spec, u16 udp_port) static int accel_psp_fs_rx_decrypt_ft_create(struct mlx5e_psp_fs *fs, - struct mlx5e_psp_rx_decrypt_table *decrypt) + struct mlx5e_psp_rx_decrypt_table *decrypt, + struct mlx5_flow_destination *default_dest) { u8 action[MLX5_UN_SZ_BYTES(set_add_copy_action_in_auto)] = {}; struct mlx5_modify_hdr *modify_hdr = NULL; @@ -348,7 +453,7 @@ accel_psp_fs_rx_decrypt_ft_create(struct mlx5e_psp_fs *fs, /* Create FT */ ft_attr.max_fte = 2; - ft_attr.level = MLX5E_ACCEL_FS_ESP_FT_LEVEL; + ft_attr.level = MLX5E_ACCEL_FS_PSP_FT_LEVEL; ft_attr.autogroup.num_reserved_entries = 1; ft_attr.autogroup.max_num_groups = 1; ft_attr.prio = MLX5E_NIC_PRIO; @@ -370,8 +475,8 @@ accel_psp_fs_rx_decrypt_ft_create(struct mlx5e_psp_fs *fs, /* Create miss rule */ flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST; - rule = mlx5_add_flow_rules(decrypt->ft, spec, &flow_act, - &decrypt->default_dest, 1); + rule = mlx5_add_flow_rules(decrypt->ft, spec, &flow_act, default_dest, + 1); if (IS_ERR(rule)) { err = PTR_ERR(rule); mlx5_core_err(mdev, @@ -446,17 +551,17 @@ static int accel_psp_fs_rx_create(struct mlx5e_psp_fs *fs, enum accel_fs_psp_typ struct mlx5_ttc_table *ttc = mlx5e_fs_get_ttc(fs->fs, false); struct mlx5e_psp_rx_decrypt_table *decrypt; struct mlx5e_psp_rx *rx_fs = fs->rx_fs; + struct mlx5_flow_destination dest = {}; int err; decrypt = &rx_fs->decrypt[type]; - decrypt->default_dest = mlx5_ttc_get_default_dest(ttc, - fs_psp2tt(type)); - err = accel_psp_fs_rx_check_ft_create(fs, decrypt, &decrypt->check); + err = accel_psp_fs_rx_check_ft_create(fs, &decrypt->check); if (err) return err; - err = accel_psp_fs_rx_decrypt_ft_create(fs, decrypt); + dest = mlx5_ttc_get_default_dest(ttc, fs_psp2tt(type)); + err = accel_psp_fs_rx_decrypt_ft_create(fs, decrypt, &dest); if (err) goto out_err_ft; @@ -549,6 +654,7 @@ void mlx5_accel_psp_fs_cleanup_rx_tables(struct mlx5e_priv *priv) /* remove FT */ accel_psp_fs_rx_destroy(fs, i); } + accel_psp_fs_rx_ft_destroy(&priv->psp->fs->rx); } int mlx5_accel_psp_fs_init_rx_tables(struct mlx5e_priv *priv) @@ -563,6 +669,10 @@ int mlx5_accel_psp_fs_init_rx_tables(struct mlx5e_priv *priv) fs = priv->psp->fs; ttc = mlx5e_fs_get_ttc(fs->fs, false); + err = accel_psp_fs_rx_ft_create(fs, &fs->rx); + if (err) + return err; + for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) { struct mlx5e_psp_rx_decrypt_table *decrypt; struct mlx5_flow_destination dest = {}; @@ -586,6 +696,7 @@ int mlx5_accel_psp_fs_init_rx_tables(struct mlx5e_priv *priv) mlx5_ttc_fwd_default_dest(ttc, fs_psp2tt(i)); accel_psp_fs_rx_destroy(fs, i); } + accel_psp_fs_rx_ft_destroy(&fs->rx); return err; } From 92be057b8048229695f2d582a789ca5543233f4c Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Tue, 7 Jul 2026 16:08:54 +0300 Subject: [PATCH 0428/1433] net/mlx5e: psp: Use a single rx_check table PSP uses a check steering table per IP version, but the PSP rules are IP-version agnostic, so there's no point duplicating these in HW. This commit makes the rx check steering table independent of the IP version, with the final table added in the previous patch responsible for directing packets to the corresponding UDP TIRs (or the TTC table itself for non-UDP traffic). Signed-off-by: Cosmin Ratiu Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260707130858.969928-12-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../mellanox/mlx5/core/en_accel/psp.c | 34 +++++++------------ 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c index c45241025b16..48bc59045dfe 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c @@ -43,7 +43,6 @@ struct mlx5e_psp_rx_decrypt_table { struct mlx5_flow_group *miss_group; struct mlx5_flow_handle *miss_rule; struct mlx5_modify_hdr *rx_modify_hdr; - struct mlx5e_psp_rx_check_table check; struct mlx5_flow_handle *rule; }; @@ -69,6 +68,7 @@ struct mlx5e_psp_fs { struct mlx5e_flow_steering *fs; struct mlx5e_psp_rx *rx_fs; + struct mlx5e_psp_rx_check_table check; struct mlx5e_psp_rx_table rx; }; @@ -267,8 +267,7 @@ static int accel_psp_fs_rx_ft_create(struct mlx5e_psp_fs *fs, } static -void accel_psp_fs_rx_check_ft_destroy(struct mlx5e_psp_fs *fs, - struct mlx5e_psp_rx_check_table *check) +void accel_psp_fs_rx_check_ft_destroy(struct mlx5e_psp_rx_check_table *check) { accel_psp_fs_del_flow_rule(&check->bad_rule); accel_psp_fs_del_flow_rule(&check->err_rule); @@ -402,13 +401,12 @@ int accel_psp_fs_rx_check_ft_create(struct mlx5e_psp_fs *fs, goto out_spec; out_err: - accel_psp_fs_rx_check_ft_destroy(fs, check); + accel_psp_fs_rx_check_ft_destroy(check); out_spec: kfree(spec); return err; } - static void accel_psp_fs_rx_decrypt_ft_destroy(struct mlx5e_psp_fs *fs, struct mlx5e_psp_rx_decrypt_table *decrypt) @@ -511,7 +509,7 @@ accel_psp_fs_rx_decrypt_ft_create(struct mlx5e_psp_fs *fs, MLX5_FLOW_CONTEXT_ACTION_MOD_HDR; flow_act.modify_hdr = modify_hdr; dest.type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE; - dest.ft = decrypt->check.ft; + dest.ft = fs->check.ft; rule = mlx5_add_flow_rules(decrypt->ft, spec, &flow_act, &dest, 1); if (IS_ERR(rule)) { err = PTR_ERR(rule); @@ -541,8 +539,6 @@ static int accel_psp_fs_rx_destroy(struct mlx5e_psp_fs *fs, accel_psp_fs_rx_decrypt_ft_destroy(fs, decrypt); - accel_psp_fs_rx_check_ft_destroy(fs, &decrypt->check); - return 0; } @@ -552,24 +548,11 @@ static int accel_psp_fs_rx_create(struct mlx5e_psp_fs *fs, enum accel_fs_psp_typ struct mlx5e_psp_rx_decrypt_table *decrypt; struct mlx5e_psp_rx *rx_fs = fs->rx_fs; struct mlx5_flow_destination dest = {}; - int err; decrypt = &rx_fs->decrypt[type]; - err = accel_psp_fs_rx_check_ft_create(fs, &decrypt->check); - if (err) - return err; - dest = mlx5_ttc_get_default_dest(ttc, fs_psp2tt(type)); - err = accel_psp_fs_rx_decrypt_ft_create(fs, decrypt, &dest); - if (err) - goto out_err_ft; - - return 0; - -out_err_ft: - accel_psp_fs_rx_check_ft_destroy(fs, &decrypt->check); - return err; + return accel_psp_fs_rx_decrypt_ft_create(fs, decrypt, &dest); } static void accel_psp_fs_rx_cleanup(struct mlx5e_psp_fs *fs) @@ -654,6 +637,7 @@ void mlx5_accel_psp_fs_cleanup_rx_tables(struct mlx5e_priv *priv) /* remove FT */ accel_psp_fs_rx_destroy(fs, i); } + accel_psp_fs_rx_check_ft_destroy(&fs->check); accel_psp_fs_rx_ft_destroy(&priv->psp->fs->rx); } @@ -673,6 +657,10 @@ int mlx5_accel_psp_fs_init_rx_tables(struct mlx5e_priv *priv) if (err) return err; + err = accel_psp_fs_rx_check_ft_create(fs, &fs->check); + if (err) + goto err_ft; + for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) { struct mlx5e_psp_rx_decrypt_table *decrypt; struct mlx5_flow_destination dest = {}; @@ -696,6 +684,8 @@ int mlx5_accel_psp_fs_init_rx_tables(struct mlx5e_priv *priv) mlx5_ttc_fwd_default_dest(ttc, fs_psp2tt(i)); accel_psp_fs_rx_destroy(fs, i); } + accel_psp_fs_rx_check_ft_destroy(&fs->check); +err_ft: accel_psp_fs_rx_ft_destroy(&fs->rx); return err; From 823f296008f39bb95c189cedf3b9f16d4ed65234 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Tue, 7 Jul 2026 16:08:55 +0300 Subject: [PATCH 0429/1433] net/mlx5e: psp: Flatten steering structures PSP steering code has two dynamically allocated structures to store RX and TX steering structs. Remove those and flatten out everything into the parent mlx5e_psp_fs. The tx_counter was moved out of the TX table as well, because the table doesn't own it, it outlives TX table destruction. All table creation/destruction now happens in accel_psp_fs_{rx,tx}_{create,destroy}. This will be used in subsequent patches to make PSP configuration dynamic. Signed-off-by: Cosmin Ratiu Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260707130858.969928-13-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../mellanox/mlx5/core/en_accel/psp.c | 267 +++++++----------- 1 file changed, 102 insertions(+), 165 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c index 48bc59045dfe..b713f235a0f7 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c @@ -26,7 +26,6 @@ struct mlx5e_psp_tx_table { struct mlx5_flow_table *ft; struct mlx5_flow_group *fg; struct mlx5_flow_handle *rule; - struct mlx5_fc *tx_counter; }; struct mlx5e_psp_rx_check_table { @@ -46,14 +45,6 @@ struct mlx5e_psp_rx_decrypt_table { struct mlx5_flow_handle *rule; }; -struct mlx5e_psp_rx { - struct mlx5e_psp_rx_decrypt_table decrypt[ACCEL_FS_PSP_NUM_TYPES]; - struct mlx5_fc *rx_counter; - struct mlx5_fc *rx_auth_fail_counter; - struct mlx5_fc *rx_err_counter; - struct mlx5_fc *rx_bad_counter; -}; - struct mlx5e_psp_rx_table { struct mlx5_flow_table *ft; struct mlx5_flow_group *miss_group; @@ -63,11 +54,17 @@ struct mlx5e_psp_rx_table { struct mlx5e_psp_fs { struct mlx5_core_dev *mdev; - struct mlx5e_psp_tx_table *tx_fs; - /* Rx manage */ - struct mlx5e_flow_steering *fs; - struct mlx5e_psp_rx *rx_fs; + struct mlx5_fc *tx_counter; + struct mlx5e_psp_tx_table tx; + /* Rx */ + struct mlx5e_flow_steering *fs; + struct mlx5_fc *rx_counter; + struct mlx5_fc *rx_auth_fail_counter; + struct mlx5_fc *rx_err_counter; + struct mlx5_fc *rx_bad_counter; + + struct mlx5e_psp_rx_decrypt_table decrypt[ACCEL_FS_PSP_NUM_TYPES]; struct mlx5e_psp_rx_check_table check; struct mlx5e_psp_rx_table rx; }; @@ -217,7 +214,7 @@ static int accel_psp_fs_rx_ft_create(struct mlx5e_psp_fs *fs, dest[0].type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE; dest[0].ft = mlx5_get_ttc_flow_table(ttc); dest[1].type = MLX5_FLOW_DESTINATION_TYPE_COUNTER; - dest[1].counter = fs->rx_fs->rx_counter; + dest[1].counter = fs->rx_counter; rule = mlx5_add_flow_rules(rx->ft, NULL, &flow_act, dest, 2); if (IS_ERR(rule)) { err = PTR_ERR(rule); @@ -245,7 +242,7 @@ static int accel_psp_fs_rx_ft_create(struct mlx5e_psp_fs *fs, outer_headers.ip_version, version); dest[0] = mlx5_ttc_get_default_dest(ttc, fs_psp2tt(i)); dest[1].type = MLX5_FLOW_DESTINATION_TYPE_COUNTER; - dest[1].counter = fs->rx_fs->rx_counter; + dest[1].counter = fs->rx_counter; rule = mlx5_add_flow_rules(rx->ft, spec, &flow_act, dest, 2); if (IS_ERR(rule)) { @@ -364,7 +361,7 @@ int accel_psp_fs_rx_check_ft_create(struct mlx5e_psp_fs *fs, memset(spec, 0, sizeof(*spec)); accel_psp_setup_syndrome_match(spec, PSP_ICV_FAIL); err = accel_psp_add_drop_rule(check->ft, spec, - fs->rx_fs->rx_auth_fail_counter, + fs->rx_auth_fail_counter, &check->auth_fail_rule); if (err) { mlx5_core_err(mdev, @@ -376,8 +373,7 @@ int accel_psp_fs_rx_check_ft_create(struct mlx5e_psp_fs *fs, /* add framing drop rule */ memset(spec, 0, sizeof(*spec)); accel_psp_setup_syndrome_match(spec, PSP_BAD_TRAILER); - err = accel_psp_add_drop_rule(check->ft, spec, - fs->rx_fs->rx_err_counter, + err = accel_psp_add_drop_rule(check->ft, spec, fs->rx_err_counter, &check->err_rule); if (err) { mlx5_core_err(mdev, @@ -388,8 +384,7 @@ int accel_psp_fs_rx_check_ft_create(struct mlx5e_psp_fs *fs, /* add misc. errors drop rule */ memset(spec, 0, sizeof(*spec)); - err = accel_psp_add_drop_rule(check->ft, spec, - fs->rx_fs->rx_bad_counter, + err = accel_psp_add_drop_rule(check->ft, spec, fs->rx_bad_counter, &check->bad_rule); if (err) { mlx5_core_err(mdev, @@ -528,46 +523,66 @@ accel_psp_fs_rx_decrypt_ft_create(struct mlx5e_psp_fs *fs, return err; } -static int accel_psp_fs_rx_destroy(struct mlx5e_psp_fs *fs, - enum accel_fs_psp_type type) -{ - struct mlx5e_psp_rx_decrypt_table *decrypt; - struct mlx5e_psp_rx *rx_fs = fs->rx_fs; - - /* The netdev unreg already happened, so all offloaded rule are already removed */ - decrypt = &rx_fs->decrypt[type]; - - accel_psp_fs_rx_decrypt_ft_destroy(fs, decrypt); - - return 0; -} - -static int accel_psp_fs_rx_create(struct mlx5e_psp_fs *fs, enum accel_fs_psp_type type) +static void accel_psp_fs_rx_destroy(struct mlx5e_psp_fs *fs) { struct mlx5_ttc_table *ttc = mlx5e_fs_get_ttc(fs->fs, false); - struct mlx5e_psp_rx_decrypt_table *decrypt; - struct mlx5e_psp_rx *rx_fs = fs->rx_fs; - struct mlx5_flow_destination dest = {}; + int i; - decrypt = &rx_fs->decrypt[type]; + /* disconnect */ + for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) { + mlx5_ttc_fwd_default_dest(ttc, fs_psp2tt(i)); + accel_psp_fs_rx_decrypt_ft_destroy(fs, &fs->decrypt[i]); + } + accel_psp_fs_rx_check_ft_destroy(&fs->check); + accel_psp_fs_rx_ft_destroy(&fs->rx); +} - dest = mlx5_ttc_get_default_dest(ttc, fs_psp2tt(type)); - return accel_psp_fs_rx_decrypt_ft_create(fs, decrypt, &dest); +static int accel_psp_fs_rx_create(struct mlx5e_psp_fs *fs) +{ + struct mlx5_ttc_table *ttc = mlx5e_fs_get_ttc(fs->fs, false); + int i, err; + + err = accel_psp_fs_rx_ft_create(fs, &fs->rx); + if (err) + return err; + + err = accel_psp_fs_rx_check_ft_create(fs, &fs->check); + if (err) + goto err_ft; + + for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) { + struct mlx5_flow_destination dest; + + dest = mlx5_ttc_get_default_dest(ttc, fs_psp2tt(i)); + err = accel_psp_fs_rx_decrypt_ft_create(fs, &fs->decrypt[i], + &dest); + if (err) + goto err_decrypt_ft; + + dest.type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE; + dest.ft = fs->decrypt[i].ft; + mlx5_ttc_fwd_dest(ttc, fs_psp2tt(i), &dest); + } + + return 0; + +err_decrypt_ft: + while (--i >= 0) { + mlx5_ttc_fwd_default_dest(ttc, fs_psp2tt(i)); + accel_psp_fs_rx_decrypt_ft_destroy(fs, &fs->decrypt[i]); + } + accel_psp_fs_rx_check_ft_destroy(&fs->check); +err_ft: + accel_psp_fs_rx_ft_destroy(&fs->rx); + return err; } static void accel_psp_fs_rx_cleanup(struct mlx5e_psp_fs *fs) { - struct mlx5e_psp_rx *rx_fs = fs->rx_fs; - - if (!rx_fs) - return; - - accel_psp_fs_destroy_counter(fs->mdev, &rx_fs->rx_bad_counter); - accel_psp_fs_destroy_counter(fs->mdev, &rx_fs->rx_err_counter); - accel_psp_fs_destroy_counter(fs->mdev, &rx_fs->rx_auth_fail_counter); - accel_psp_fs_destroy_counter(fs->mdev, &rx_fs->rx_counter); - kfree(rx_fs); - fs->rx_fs = NULL; + accel_psp_fs_destroy_counter(fs->mdev, &fs->rx_bad_counter); + accel_psp_fs_destroy_counter(fs->mdev, &fs->rx_err_counter); + accel_psp_fs_destroy_counter(fs->mdev, &fs->rx_auth_fail_counter); + accel_psp_fs_destroy_counter(fs->mdev, &fs->rx_counter); } static int accel_psp_fs_rx_init(struct mlx5e_psp_fs *fs) @@ -575,11 +590,7 @@ static int accel_psp_fs_rx_init(struct mlx5e_psp_fs *fs) struct mlx5_core_dev *mdev = fs->mdev; int err; - fs->rx_fs = kzalloc_obj(*fs->rx_fs); - if (!fs->rx_fs) - return -ENOMEM; - - err = accel_psp_fs_create_counter(mdev, &fs->rx_fs->rx_counter); + err = accel_psp_fs_create_counter(mdev, &fs->rx_counter); if (err) { mlx5_core_warn(mdev, "fail to create psp rx flow counter err=%d\n", @@ -587,8 +598,7 @@ static int accel_psp_fs_rx_init(struct mlx5e_psp_fs *fs) goto out_err; } - err = accel_psp_fs_create_counter(mdev, - &fs->rx_fs->rx_auth_fail_counter); + err = accel_psp_fs_create_counter(mdev, &fs->rx_auth_fail_counter); if (err) { mlx5_core_warn(mdev, "fail to create psp rx auth fail flow counter err=%d\n", @@ -596,7 +606,7 @@ static int accel_psp_fs_rx_init(struct mlx5e_psp_fs *fs) goto out_err; } - err = accel_psp_fs_create_counter(mdev, &fs->rx_fs->rx_err_counter); + err = accel_psp_fs_create_counter(mdev, &fs->rx_err_counter); if (err) { mlx5_core_warn(mdev, "fail to create psp rx error flow counter err=%d\n", @@ -604,7 +614,7 @@ static int accel_psp_fs_rx_init(struct mlx5e_psp_fs *fs) goto out_err; } - err = accel_psp_fs_create_counter(mdev, &fs->rx_fs->rx_bad_counter); + err = accel_psp_fs_create_counter(mdev, &fs->rx_bad_counter); if (err) { mlx5_core_warn(mdev, "fail to create psp rx bad flow counter err=%d\n", @@ -621,84 +631,28 @@ static int accel_psp_fs_rx_init(struct mlx5e_psp_fs *fs) void mlx5_accel_psp_fs_cleanup_rx_tables(struct mlx5e_priv *priv) { - struct mlx5_ttc_table *ttc; - struct mlx5e_psp_fs *fs; - int i; - if (!priv->psp) return; - fs = priv->psp->fs; - ttc = mlx5e_fs_get_ttc(fs->fs, false); - for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) { - /* disconnect */ - mlx5_ttc_fwd_default_dest(ttc, fs_psp2tt(i)); - - /* remove FT */ - accel_psp_fs_rx_destroy(fs, i); - } - accel_psp_fs_rx_check_ft_destroy(&fs->check); - accel_psp_fs_rx_ft_destroy(&priv->psp->fs->rx); + accel_psp_fs_rx_destroy(priv->psp->fs); } int mlx5_accel_psp_fs_init_rx_tables(struct mlx5e_priv *priv) { - struct mlx5_ttc_table *ttc; - struct mlx5e_psp_fs *fs; - int err, i; - if (!priv->psp) return 0; - fs = priv->psp->fs; - ttc = mlx5e_fs_get_ttc(fs->fs, false); - - err = accel_psp_fs_rx_ft_create(fs, &fs->rx); - if (err) - return err; - - err = accel_psp_fs_rx_check_ft_create(fs, &fs->check); - if (err) - goto err_ft; - - for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) { - struct mlx5e_psp_rx_decrypt_table *decrypt; - struct mlx5_flow_destination dest = {}; - - /* create FT */ - err = accel_psp_fs_rx_create(fs, i); - if (err) - goto out_err; - - /* connect */ - dest.type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE; - decrypt = &fs->rx_fs->decrypt[i]; - dest.ft = decrypt->ft; - mlx5_ttc_fwd_dest(ttc, fs_psp2tt(i), &dest); - } - - return 0; - -out_err: - while (--i >= 0) { - mlx5_ttc_fwd_default_dest(ttc, fs_psp2tt(i)); - accel_psp_fs_rx_destroy(fs, i); - } - accel_psp_fs_rx_check_ft_destroy(&fs->check); -err_ft: - accel_psp_fs_rx_ft_destroy(&fs->rx); - - return err; + return accel_psp_fs_rx_create(priv->psp->fs); } -static int accel_psp_fs_tx_ft_create(struct mlx5e_psp_fs *fs) +static int accel_psp_fs_tx_ft_create(struct mlx5e_psp_fs *fs, + struct mlx5e_psp_tx_table *tx) { int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in); struct mlx5_flow_table_attr ft_attr = {}; struct mlx5_flow_destination dest = {}; struct mlx5_core_dev *mdev = fs->mdev; struct mlx5_flow_act flow_act = {}; - struct mlx5e_psp_tx_table *tx_fs; u32 *in, *mc, *outer_headers_c; struct mlx5_flow_handle *rule; struct mlx5_flow_spec *spec; @@ -720,8 +674,7 @@ static int accel_psp_fs_tx_ft_create(struct mlx5e_psp_fs *fs) ft_attr.level = MLX5E_PSP_LEVEL; ft_attr.autogroup.max_num_groups = 1; - tx_fs = fs->tx_fs; - ft = mlx5_create_flow_table(tx_fs->ns, &ft_attr); + ft = mlx5_create_flow_table(tx->ns, &ft_attr); if (IS_ERR(ft)) { err = PTR_ERR(ft); mlx5_core_err(mdev, "PSP: fail to add psp tx flow table, err = %d\n", err); @@ -747,7 +700,7 @@ static int accel_psp_fs_tx_ft_create(struct mlx5e_psp_fs *fs) MLX5_FLOW_CONTEXT_ACTION_CRYPTO_ENCRYPT | MLX5_FLOW_CONTEXT_ACTION_COUNT; dest.type = MLX5_FLOW_DESTINATION_TYPE_COUNTER; - dest.counter = tx_fs->tx_counter; + dest.counter = fs->tx_counter; rule = mlx5_add_flow_rules(ft, spec, &flow_act, &dest, 1); if (IS_ERR(rule)) { err = PTR_ERR(rule); @@ -755,9 +708,9 @@ static int accel_psp_fs_tx_ft_create(struct mlx5e_psp_fs *fs) goto err_add_flow_rule; } - tx_fs->ft = ft; - tx_fs->fg = fg; - tx_fs->rule = rule; + tx->ft = ft; + tx->fg = fg; + tx->rule = rule; goto out; err_add_flow_rule: @@ -770,50 +723,35 @@ static int accel_psp_fs_tx_ft_create(struct mlx5e_psp_fs *fs) return err; } -static void accel_psp_fs_tx_ft_destroy(struct mlx5e_psp_tx_table *tx_fs) +static void accel_psp_fs_tx_ft_destroy(struct mlx5e_psp_tx_table *tx) { - accel_psp_fs_del_flow_rule(&tx_fs->rule); - accel_psp_fs_destroy_flow_group(&tx_fs->fg); - accel_psp_fs_destroy_ft(&tx_fs->ft); + accel_psp_fs_del_flow_rule(&tx->rule); + accel_psp_fs_destroy_flow_group(&tx->fg); + accel_psp_fs_destroy_ft(&tx->ft); } static void accel_psp_fs_tx_cleanup(struct mlx5e_psp_fs *fs) { - struct mlx5e_psp_tx_table *tx_fs = fs->tx_fs; - - if (!tx_fs) - return; - - accel_psp_fs_destroy_counter(fs->mdev, &tx_fs->tx_counter); - kfree(tx_fs); - fs->tx_fs = NULL; + accel_psp_fs_destroy_counter(fs->mdev, &fs->tx_counter); } static int accel_psp_fs_tx_init(struct mlx5e_psp_fs *fs) { struct mlx5_core_dev *mdev = fs->mdev; - struct mlx5e_psp_tx_table *tx_fs; - struct mlx5_flow_namespace *ns; int err; - ns = mlx5_get_flow_namespace(mdev, MLX5_FLOW_NAMESPACE_EGRESS_IPSEC); - if (!ns) + fs->tx.ns = mlx5_get_flow_namespace(mdev, + MLX5_FLOW_NAMESPACE_EGRESS_IPSEC); + if (!fs->tx.ns) return -EOPNOTSUPP; - tx_fs = kzalloc_obj(*tx_fs); - if (!tx_fs) - return -ENOMEM; - - err = accel_psp_fs_create_counter(mdev, &tx_fs->tx_counter); + err = accel_psp_fs_create_counter(mdev, &fs->tx_counter); if (err) { mlx5_core_warn(mdev, "fail to create psp tx flow counter err=%d\n", err); - kfree(tx_fs); return err; } - tx_fs->ns = ns; - fs->tx_fs = tx_fs; return 0; } @@ -821,30 +759,29 @@ static void mlx5e_accel_psp_fs_get_stats_fill(struct mlx5e_priv *priv, struct mlx5e_psp_stats *stats) { - struct mlx5e_psp_tx_table *tx_fs = priv->psp->fs->tx_fs; - struct mlx5e_psp_rx *rx_fs = priv->psp->fs->rx_fs; + struct mlx5e_psp_fs *fs = priv->psp->fs; struct mlx5_core_dev *mdev = priv->mdev; - if (tx_fs->tx_counter) - mlx5_fc_query(mdev, tx_fs->tx_counter, &stats->psp_tx_pkts, + if (fs->tx_counter) + mlx5_fc_query(mdev, fs->tx_counter, &stats->psp_tx_pkts, &stats->psp_tx_bytes); - if (rx_fs->rx_counter) - mlx5_fc_query(mdev, rx_fs->rx_counter, &stats->psp_rx_pkts, + if (fs->rx_counter) + mlx5_fc_query(mdev, fs->rx_counter, &stats->psp_rx_pkts, &stats->psp_rx_bytes); - if (rx_fs->rx_auth_fail_counter) - mlx5_fc_query(mdev, rx_fs->rx_auth_fail_counter, + if (fs->rx_auth_fail_counter) + mlx5_fc_query(mdev, fs->rx_auth_fail_counter, &stats->psp_rx_pkts_auth_fail, &stats->psp_rx_bytes_auth_fail); - if (rx_fs->rx_err_counter) - mlx5_fc_query(mdev, rx_fs->rx_err_counter, + if (fs->rx_err_counter) + mlx5_fc_query(mdev, fs->rx_err_counter, &stats->psp_rx_pkts_frame_err, &stats->psp_rx_bytes_frame_err); - if (rx_fs->rx_bad_counter) - mlx5_fc_query(mdev, rx_fs->rx_bad_counter, + if (fs->rx_bad_counter) + mlx5_fc_query(mdev, fs->rx_bad_counter, &stats->psp_rx_pkts_drop, &stats->psp_rx_bytes_drop); } @@ -854,7 +791,7 @@ void mlx5_accel_psp_fs_cleanup_tx_tables(struct mlx5e_priv *priv) if (!priv->psp) return; - accel_psp_fs_tx_ft_destroy(priv->psp->fs->tx_fs); + accel_psp_fs_tx_ft_destroy(&priv->psp->fs->tx); } int mlx5_accel_psp_fs_init_tx_tables(struct mlx5e_priv *priv) @@ -862,7 +799,7 @@ int mlx5_accel_psp_fs_init_tx_tables(struct mlx5e_priv *priv) if (!priv->psp) return 0; - return accel_psp_fs_tx_ft_create(priv->psp->fs); + return accel_psp_fs_tx_ft_create(priv->psp->fs, &priv->psp->fs->tx); } static void mlx5e_accel_psp_fs_cleanup(struct mlx5e_psp_fs *fs) From 1e6e0cec0dab96f5968a29c4d363da05200171e4 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Tue, 7 Jul 2026 16:08:56 +0300 Subject: [PATCH 0430/1433] net/mlx5e: psp: Make PSP steering config dynamic Only create PSP steering tables when PSP configuration is enabled on a PSP device. Previously, mlx5e_psp_set_config (== .set_config on the PSP device) did nothing. Steering was created and hooked up to incoming traffic at device initialization time, via mlx5e_init_nic_rx -> mlx5e_accel_init_rx -> mlx5_accel_psp_fs_init_rx_tables. Similarly, TX tables were created and hooked to egress traffic at mlx5e_init_nic_tx -> mlx5e_accel_init_tx -> mlx5_accel_psp_fs_init_tx_tables Doing this means both ingress and egress UDP packets go through the PSP steering tables, causing extra latency and overhead. A better solution is to let the incoming encrypted PSP packets get dropped by SW and not impose an overhead on all UDP packets which have to traverse the PSP steering rules when PSP isn't used. Additionally, upcoming changes to support HW-GRO need to reconfigure PSP steering dynamically and this patch is a necessary step in that direction. Two new functions are defined: - accel_psp_fs_create: Creates steering tables and connects RX UDP v4/v6 traffic to PSP RX tables. - accel_psp_fs_destroy: Disconnects incoming RX traffic from PSP steering and destroys steering tables. PSP steering cleanup, which happens independently from PSP device configuration, is unchanged. When the device is going away, steering tables are destroyed as well. The netdev lock is now used for proper synchronization between the new set_config flow and device steering init/cleanup. This will be important in future patches, when PSP will be able to reconfigure itself dynamically upon netdev feature changes. Signed-off-by: Cosmin Ratiu Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260707130858.969928-14-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../mellanox/mlx5/core/en_accel/en_accel.h | 19 +---- .../mellanox/mlx5/core/en_accel/psp.c | 73 +++++++++++++------ .../mellanox/mlx5/core/en_accel/psp.h | 12 --- 3 files changed, 53 insertions(+), 51 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/en_accel.h b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/en_accel.h index b526b3898c22..3f212e46fc2f 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/en_accel.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/en_accel.h @@ -220,18 +220,7 @@ static inline void mlx5e_accel_tx_finish(struct mlx5e_txqsq *sq, static inline int mlx5e_accel_init_rx(struct mlx5e_priv *priv) { - int err; - - err = mlx5_accel_psp_fs_init_rx_tables(priv); - if (err) - goto out; - - err = mlx5e_ktls_init_rx(priv); - if (err) - mlx5_accel_psp_fs_cleanup_rx_tables(priv); - -out: - return err; + return mlx5e_ktls_init_rx(priv); } static inline void mlx5e_accel_cleanup_rx(struct mlx5e_priv *priv) @@ -242,12 +231,6 @@ static inline void mlx5e_accel_cleanup_rx(struct mlx5e_priv *priv) static inline int mlx5e_accel_init_tx(struct mlx5e_priv *priv) { - int err; - - err = mlx5_accel_psp_fs_init_tx_tables(priv); - if (err) - return err; - return mlx5e_ktls_init_tx(priv); } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c index b713f235a0f7..b3521c3861f6 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c @@ -537,18 +537,24 @@ static void accel_psp_fs_rx_destroy(struct mlx5e_psp_fs *fs) accel_psp_fs_rx_ft_destroy(&fs->rx); } -static int accel_psp_fs_rx_create(struct mlx5e_psp_fs *fs) +static int accel_psp_fs_rx_create(struct mlx5e_psp_fs *fs, + struct netlink_ext_ack *extack) { struct mlx5_ttc_table *ttc = mlx5e_fs_get_ttc(fs->fs, false); int i, err; err = accel_psp_fs_rx_ft_create(fs, &fs->rx); - if (err) + if (err) { + NL_SET_ERR_MSG(extack, "Failed creating RX steering table"); return err; + } err = accel_psp_fs_rx_check_ft_create(fs, &fs->check); - if (err) + if (err) { + NL_SET_ERR_MSG(extack, + "Failed creating RX check steering table"); goto err_ft; + } for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) { struct mlx5_flow_destination dest; @@ -556,8 +562,11 @@ static int accel_psp_fs_rx_create(struct mlx5e_psp_fs *fs) dest = mlx5_ttc_get_default_dest(ttc, fs_psp2tt(i)); err = accel_psp_fs_rx_decrypt_ft_create(fs, &fs->decrypt[i], &dest); - if (err) + if (err) { + NL_SET_ERR_MSG(extack, + "Failed creating RX decrypt steering table"); goto err_decrypt_ft; + } dest.type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE; dest.ft = fs->decrypt[i].ft; @@ -634,15 +643,9 @@ void mlx5_accel_psp_fs_cleanup_rx_tables(struct mlx5e_priv *priv) if (!priv->psp) return; + netdev_lock(priv->netdev); accel_psp_fs_rx_destroy(priv->psp->fs); -} - -int mlx5_accel_psp_fs_init_rx_tables(struct mlx5e_priv *priv) -{ - if (!priv->psp) - return 0; - - return accel_psp_fs_rx_create(priv->psp->fs); + netdev_unlock(priv->netdev); } static int accel_psp_fs_tx_ft_create(struct mlx5e_psp_fs *fs, @@ -791,15 +794,9 @@ void mlx5_accel_psp_fs_cleanup_tx_tables(struct mlx5e_priv *priv) if (!priv->psp) return; + netdev_lock(priv->netdev); accel_psp_fs_tx_ft_destroy(&priv->psp->fs->tx); -} - -int mlx5_accel_psp_fs_init_tx_tables(struct mlx5e_priv *priv) -{ - if (!priv->psp) - return 0; - - return accel_psp_fs_tx_ft_create(priv->psp->fs, &priv->psp->fs->tx); + netdev_unlock(priv->netdev); } static void mlx5e_accel_psp_fs_cleanup(struct mlx5e_psp_fs *fs) @@ -837,11 +834,45 @@ static struct mlx5e_psp_fs *mlx5e_accel_psp_fs_init(struct mlx5e_priv *priv) return ERR_PTR(err); } +static int accel_psp_fs_create(struct mlx5e_priv *priv, + struct netlink_ext_ack *extack) +{ + int err; + + err = accel_psp_fs_rx_create(priv->psp->fs, extack); + if (err) + return err; + + err = accel_psp_fs_tx_ft_create(priv->psp->fs, &priv->psp->fs->tx); + if (err) { + NL_SET_ERR_MSG(extack, "Failed creating TX steering table"); + accel_psp_fs_rx_destroy(priv->psp->fs); + } + return err; +} + +static void accel_psp_fs_destroy(struct mlx5e_priv *priv) +{ + accel_psp_fs_tx_ft_destroy(&priv->psp->fs->tx); + accel_psp_fs_rx_destroy(priv->psp->fs); +} + static int mlx5e_psp_set_config(struct psp_dev *psd, struct psp_dev_config *conf, struct netlink_ext_ack *extack) { - return 0; /* TODO: this should actually do things to the device */ + struct mlx5e_priv *priv = netdev_priv(psd->main_netdev); + bool psp_enabled = psd->config.versions; + bool enable_psp = conf->versions; + int err = 0; + + netdev_lock(priv->netdev); + if (!psp_enabled && enable_psp) + err = accel_psp_fs_create(priv, extack); + else if (psp_enabled && !enable_psp) + accel_psp_fs_destroy(priv); + netdev_unlock(priv->netdev); + return err; } static int diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h index a53f90f7c341..57fffcf4a62c 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h @@ -43,26 +43,14 @@ static inline bool mlx5_is_psp_device(struct mlx5_core_dev *mdev) return true; } -int mlx5_accel_psp_fs_init_rx_tables(struct mlx5e_priv *priv); void mlx5_accel_psp_fs_cleanup_rx_tables(struct mlx5e_priv *priv); -int mlx5_accel_psp_fs_init_tx_tables(struct mlx5e_priv *priv); void mlx5_accel_psp_fs_cleanup_tx_tables(struct mlx5e_priv *priv); void mlx5e_psp_register(struct mlx5e_priv *priv); void mlx5e_psp_unregister(struct mlx5e_priv *priv); int mlx5e_psp_init(struct mlx5e_priv *priv); void mlx5e_psp_cleanup(struct mlx5e_priv *priv); #else -static inline int mlx5_accel_psp_fs_init_rx_tables(struct mlx5e_priv *priv) -{ - return 0; -} - static inline void mlx5_accel_psp_fs_cleanup_rx_tables(struct mlx5e_priv *priv) { } -static inline int mlx5_accel_psp_fs_init_tx_tables(struct mlx5e_priv *priv) -{ - return 0; -} - static inline void mlx5_accel_psp_fs_cleanup_tx_tables(struct mlx5e_priv *priv) { } static inline bool mlx5_is_psp_device(struct mlx5_core_dev *mdev) { From 13ae76a6a3e6f1b6d5a2505192288f9f58c903fc Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Tue, 7 Jul 2026 16:08:57 +0300 Subject: [PATCH 0431/1433] net/mlx5e: Return errors from profile->enable profile->enable is called before enabling an mlx5 netdevice and currently doesn't return errors. Code called from it has to either: 1. eat errors and keep going, leaving a netdevice initialized with missing functionality or 2. manually clean up things that other parts of the init flow might have set up. Option 1 might be useful in some cases for optional functionality but option 2 doesn't make for good design. Add a 3rd option for code which wants to propagate errors from profile->enable and fail netdev init. This change is a noop for now, the first 'user' of this option 3 will be in the next patch. Signed-off-by: Cosmin Ratiu Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260707130858.969928-15-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en.h | 2 +- drivers/net/ethernet/mellanox/mlx5/core/en_main.c | 15 +++++++++++---- drivers/net/ethernet/mellanox/mlx5/core/en_rep.c | 8 ++++++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en.h b/drivers/net/ethernet/mellanox/mlx5/core/en.h index d507289096c2..45bda7b226e9 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en.h @@ -1028,7 +1028,7 @@ struct mlx5e_profile { void (*cleanup_rx)(struct mlx5e_priv *priv); int (*init_tx)(struct mlx5e_priv *priv); void (*cleanup_tx)(struct mlx5e_priv *priv); - void (*enable)(struct mlx5e_priv *priv); + int (*enable)(struct mlx5e_priv *priv); void (*disable)(struct mlx5e_priv *priv); int (*update_rx)(struct mlx5e_priv *priv); void (*update_stats)(struct mlx5e_priv *priv); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index aa8610cedaa8..c9bcb8738f17 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -6193,7 +6193,7 @@ static int mlx5e_init_nic_tx(struct mlx5e_priv *priv) return 0; } -static void mlx5e_nic_enable(struct mlx5e_priv *priv) +static int mlx5e_nic_enable(struct mlx5e_priv *priv) { struct net_device *netdev = priv->netdev; struct mlx5_core_dev *mdev = priv->mdev; @@ -6224,7 +6224,7 @@ static void mlx5e_nic_enable(struct mlx5e_priv *priv) mlx5e_pcie_cong_event_init(priv); mlx5e_hv_vhca_stats_create(priv); if (netdev->reg_state != NETREG_REGISTERED) - return; + return 0; mlx5e_dcbnl_init_app(priv); mlx5e_nic_set_rx_mode(priv); @@ -6237,6 +6237,8 @@ static void mlx5e_nic_enable(struct mlx5e_priv *priv) netdev_unlock(netdev); netif_device_attach(netdev); rtnl_unlock(); + + return 0; } static void mlx5e_nic_disable(struct mlx5e_priv *priv) @@ -6618,13 +6620,18 @@ int mlx5e_attach_netdev(struct mlx5e_priv *priv) if (err) goto err_cleanup_tx; - if (profile->enable) - profile->enable(priv); + if (profile->enable) { + err = profile->enable(priv); + if (err) + goto err_cleanup_rx; + } mlx5e_update_features(priv->netdev); return 0; +err_cleanup_rx: + profile->cleanup_rx(priv); err_cleanup_tx: profile->cleanup_tx(priv); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c index c8b76d301c92..603051ab1eaa 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c @@ -1262,9 +1262,11 @@ static void mlx5e_cleanup_rep_tx(struct mlx5e_priv *priv) mlx5e_rep_neigh_cleanup(rpriv); } -static void mlx5e_rep_enable(struct mlx5e_priv *priv) +static int mlx5e_rep_enable(struct mlx5e_priv *priv) { mlx5e_set_netdev_mtu_boundaries(priv); + + return 0; } static void mlx5e_rep_disable(struct mlx5e_priv *priv) @@ -1322,7 +1324,7 @@ static int uplink_rep_async_event(struct notifier_block *nb, unsigned long event return NOTIFY_DONE; } -static void mlx5e_uplink_rep_enable(struct mlx5e_priv *priv) +static int mlx5e_uplink_rep_enable(struct mlx5e_priv *priv) { struct net_device *netdev = priv->netdev; struct mlx5_core_dev *mdev = priv->mdev; @@ -1357,6 +1359,8 @@ static void mlx5e_uplink_rep_enable(struct mlx5e_priv *priv) netdev_unlock(netdev); netif_device_attach(netdev); rtnl_unlock(); + + return 0; } static void mlx5e_uplink_rep_disable(struct mlx5e_priv *priv) From 676fe97d57df18c8ce00b42c4881e8d848e23a54 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Tue, 7 Jul 2026 16:08:58 +0300 Subject: [PATCH 0432/1433] net/mlx5e: psp: Report PSP dev registration errors mlx5e_psp_register() was forced to eat PSP dev registration errors as the caller was not propagating them. Change this so PSP dev registration failures get reported back to the caller instead. After the recent changes in the series, PSP dev registration failures will just leave some data structs in priv->psp (mostly counters), with no steering rules and no means to configure them. There's no point actively cleaning those up on failure, as they'll get removed during profile->cleanup. Signed-off-by: Cosmin Ratiu Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260707130858.969928-16-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c | 8 +++++--- drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h | 4 ++-- drivers/net/ethernet/mellanox/mlx5/core/en_main.c | 8 +++++++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c index b3521c3861f6..73b232379263 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c @@ -1028,14 +1028,14 @@ void mlx5e_psp_unregister(struct mlx5e_priv *priv) psp->psd = NULL; } -void mlx5e_psp_register(struct mlx5e_priv *priv) +int mlx5e_psp_register(struct mlx5e_priv *priv) { struct mlx5e_psp *psp = priv->psp; struct psp_dev *psd; /* FW Caps missing */ if (!priv->psp) - return; + return 0; psp->caps.assoc_drv_spc = sizeof(u32); psp->caps.versions = 1 << PSP_VERSION_HDR0_AES_GCM_128; @@ -1047,9 +1047,11 @@ void mlx5e_psp_register(struct mlx5e_priv *priv) if (IS_ERR(psd)) { mlx5_core_err(priv->mdev, "PSP failed to register due to %pe\n", psd); - return; + return PTR_ERR(psd); } psp->psd = psd; + + return 0; } int mlx5e_psp_init(struct mlx5e_priv *priv) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h index 57fffcf4a62c..3f441e7dd55a 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h @@ -45,7 +45,7 @@ static inline bool mlx5_is_psp_device(struct mlx5_core_dev *mdev) void mlx5_accel_psp_fs_cleanup_rx_tables(struct mlx5e_priv *priv); void mlx5_accel_psp_fs_cleanup_tx_tables(struct mlx5e_priv *priv); -void mlx5e_psp_register(struct mlx5e_priv *priv); +int mlx5e_psp_register(struct mlx5e_priv *priv); void mlx5e_psp_unregister(struct mlx5e_priv *priv); int mlx5e_psp_init(struct mlx5e_priv *priv); void mlx5e_psp_cleanup(struct mlx5e_priv *priv); @@ -57,7 +57,7 @@ static inline bool mlx5_is_psp_device(struct mlx5_core_dev *mdev) return false; } -static inline void mlx5e_psp_register(struct mlx5e_priv *priv) { } +static inline int mlx5e_psp_register(struct mlx5e_priv *priv) { return 0; } static inline void mlx5e_psp_unregister(struct mlx5e_priv *priv) { } static inline int mlx5e_psp_init(struct mlx5e_priv *priv) { return 0; } static inline void mlx5e_psp_cleanup(struct mlx5e_priv *priv) { } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index c9bcb8738f17..43471c6e6385 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -6201,7 +6201,9 @@ static int mlx5e_nic_enable(struct mlx5e_priv *priv) mlx5e_fs_init_l2_addr(priv->fs, netdev); mlx5e_ipsec_init(priv); - mlx5e_psp_register(priv); + err = mlx5e_psp_register(priv); + if (err) + goto out_ipsec_cleanup; err = mlx5e_macsec_init(priv); if (err) @@ -6239,6 +6241,10 @@ static int mlx5e_nic_enable(struct mlx5e_priv *priv) rtnl_unlock(); return 0; + +out_ipsec_cleanup: + mlx5e_ipsec_cleanup(priv); + return err; } static void mlx5e_nic_disable(struct mlx5e_priv *priv) From 860b693bca593c10e8294b79648799d96f6953d1 Mon Sep 17 00:00:00 2001 From: Simon Horman Date: Wed, 8 Jul 2026 20:02:05 +0100 Subject: [PATCH 0433/1433] Revert "gtp: annotate PDP lookups under RTNL" This reverts commit 0be5c3f0fbef3679f50f345b9237b8f9ea5de4e9. Commit 0be5c3f0fbef ("gtp: annotate PDP lookups under RTNL") added a lockdep_rtnl_is_held condition to hlist_for_each_rcu() loops to help insure that RTNL is held. Unfortunately, as pointed out by Pablo Neira Ayuso, the PDP context list is actually protected by the genetlink mutex. And so the condition is incorrect. Compile tested only. Link: https://lore.kernel.org/ak4NgOrro-4OZjz3@chamomile Signed-off-by: Simon Horman Link: https://patch.msgid.link/20260708-gtp-rtnl-v1-1-218091f171bc@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/gtp.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c index 4ad9528322c4..a60ef32b35b8 100644 --- a/drivers/net/gtp.c +++ b/drivers/net/gtp.c @@ -151,8 +151,7 @@ static struct pdp_ctx *gtp0_pdp_find(struct gtp_dev *gtp, u64 tid, u16 family) head = >p->tid_hash[gtp0_hashfn(tid) % gtp->hash_size]; - hlist_for_each_entry_rcu(pdp, head, hlist_tid, - lockdep_rtnl_is_held()) { + hlist_for_each_entry_rcu(pdp, head, hlist_tid) { if (pdp->af == family && pdp->gtp_version == GTP_V0 && pdp->u.v0.tid == tid) @@ -169,8 +168,7 @@ static struct pdp_ctx *gtp1_pdp_find(struct gtp_dev *gtp, u32 tid, u16 family) head = >p->tid_hash[gtp1u_hashfn(tid) % gtp->hash_size]; - hlist_for_each_entry_rcu(pdp, head, hlist_tid, - lockdep_rtnl_is_held()) { + hlist_for_each_entry_rcu(pdp, head, hlist_tid) { if (pdp->af == family && pdp->gtp_version == GTP_V1 && pdp->u.v1.i_tei == tid) @@ -187,8 +185,7 @@ static struct pdp_ctx *ipv4_pdp_find(struct gtp_dev *gtp, __be32 ms_addr) head = >p->addr_hash[ipv4_hashfn(ms_addr) % gtp->hash_size]; - hlist_for_each_entry_rcu(pdp, head, hlist_addr, - lockdep_rtnl_is_held()) { + hlist_for_each_entry_rcu(pdp, head, hlist_addr) { if (pdp->af == AF_INET && pdp->ms.addr.s_addr == ms_addr) return pdp; @@ -223,8 +220,7 @@ static struct pdp_ctx *ipv6_pdp_find(struct gtp_dev *gtp, head = >p->addr_hash[ipv6_hashfn(ms_addr) % gtp->hash_size]; - hlist_for_each_entry_rcu(pdp, head, hlist_addr, - lockdep_rtnl_is_held()) { + hlist_for_each_entry_rcu(pdp, head, hlist_addr) { if (pdp->af == AF_INET6 && ipv6_pdp_addr_equal(&pdp->ms.addr6, ms_addr)) return pdp; From 7e7bd5158e989814ce0a03a4d1517d54a102c3f2 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Wed, 15 Jul 2026 22:12:12 +0200 Subject: [PATCH 0434/1433] net: phy: drop duplicated header include in mdio-device During a tree-wide gpio include cleanup, the linux/gpio.h include was replaced with linux/gpio/consumer.h. mdio-device.c was already including that header, resulting in a duplicated inclusion. Let's drop it. Signed-off-by: Maxime Chevallier Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260715201213.206180-1-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/mdio_device.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/phy/mdio_device.c b/drivers/net/phy/mdio_device.c index a18263d5bb02..06151f207134 100644 --- a/drivers/net/phy/mdio_device.c +++ b/drivers/net/phy/mdio_device.c @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include From d05338c1290e8c6bc2f70fe7ad39f4ab61c8410a Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Wed, 8 Jul 2026 14:08:37 -0400 Subject: [PATCH 0435/1433] net/tcp: Prevent inlining tcp_syn_ack_timeout() The tcp_syn_ack_timeout() function gets inlined by Clang, preventing tracing. Since the call is not in the fast path, prevent it from being inlined. Signed-off-by: Emil Tsalapatis Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260708180837.9507-1-emil@etsalapatis.com Signed-off-by: Jakub Kicinski --- net/ipv4/tcp_timer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv4/tcp_timer.c b/net/ipv4/tcp_timer.c index bf171b5e1eb3..f7215d53bbda 100644 --- a/net/ipv4/tcp_timer.c +++ b/net/ipv4/tcp_timer.c @@ -748,7 +748,7 @@ static void tcp_write_timer(struct timer_list *t) sock_put(sk); } -void tcp_syn_ack_timeout(const struct request_sock *req) +noinline_for_tracing void tcp_syn_ack_timeout(const struct request_sock *req) { struct net *net = read_pnet(&inet_rsk(req)->ireq_net); From 965a251f23ff69cfb4486974d4532e9bb551c7fc Mon Sep 17 00:00:00 2001 From: Griffin Kroah-Hartman Date: Thu, 9 Jul 2026 14:24:01 +0200 Subject: [PATCH 0436/1433] rndis_host: add overflow check in rndis_rx_fixup() Add an overflow check to ensure that data_offset + data_len + 8 does not wrap, which would enable an OOB read of the USB data buffer. Cc: Andrew Lunn Cc: Shaoxu Liu Signed-off-by: Griffin Kroah-Hartman Signed-off-by: Greg Kroah-Hartman Reviewed-by: Simon Horman Link: https://patch.msgid.link/2026070900-denim-brook-52d4@gregkh Signed-off-by: Jakub Kicinski --- drivers/net/usb/rndis_host.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/usb/rndis_host.c b/drivers/net/usb/rndis_host.c index 5e39d05a2d7b..37d4865f5c5e 100644 --- a/drivers/net/usb/rndis_host.c +++ b/drivers/net/usb/rndis_host.c @@ -14,6 +14,7 @@ #include #include #include +#include /* @@ -506,6 +507,7 @@ int rndis_rx_fixup(struct usbnet *dev, struct sk_buff *skb) struct rndis_data_hdr *hdr = (void *)skb->data; struct sk_buff *skb2; u32 msg_type, msg_len, data_offset, data_len; + u32 overflow_check; msg_type = le32_to_cpu(hdr->msg_type); msg_len = le32_to_cpu(hdr->msg_len); @@ -514,7 +516,9 @@ int rndis_rx_fixup(struct usbnet *dev, struct sk_buff *skb) /* don't choke if we see oob, per-packet data, etc */ if (unlikely(msg_type != RNDIS_MSG_PACKET || skb->len < msg_len - || (data_offset + data_len + 8) > msg_len)) { + || (data_offset + data_len + 8) > msg_len + || check_add_overflow(data_offset, data_len, &overflow_check) + || check_add_overflow(overflow_check, 8, &overflow_check))) { dev->net->stats.rx_frame_errors++; netdev_dbg(dev->net, "bad rndis message %d/%d/%d/%d, len %d\n", le32_to_cpu(hdr->msg_type), From aeea930c7a878957a4b74d4888cd22880db2258c Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Thu, 9 Jul 2026 03:59:05 +0800 Subject: [PATCH 0437/1433] wifi: mac80211_hwsim: authenticate PMSR report senders hwsim_pmsr_report_nl() looks up the radio by HWSIM_ATTR_ADDR_TRANSMITTER and, when data->pmsr_request is set, parses the reported peer results, hands them to cfg80211_pmsr_report(), then unconditionally clears data->pmsr_request and calls cfg80211_pmsr_complete() to end the measurement. Unlike the sibling wmediumd data-path handlers hwsim_tx_info_frame_received_nl() and hwsim_cloned_frame_received_nl(), which check the sending socket's netgroup against data->netgroup and its portid against data->wmediumd, this handler did not check the sender at all, and its genl op carries no GENL_UNS_ADMIN_PERM flag. In non-virtio (wmediumd) mode any process in the netns that can reach the hwsim generic netlink family could therefore send a report. The transmitter address is not secret, so such a process could inject spoofed ranging results for another radio's in-flight request and, because the handler always completes the measurement, terminate a ranging operation owned by the real wmediumd session. Reject reports whose sender does not match the registered wmediumd instance, mirroring the sibling handlers: in non-virtio mode require the sending socket's netgroup to equal data->netgroup and info->snd_portid to equal data->wmediumd before touching the request state. Assisted-by: Codex:gpt-5 Assisted-by: Claude:opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260708195911.84365-3-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/virtual/mac80211_hwsim_main.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index f66da1a343f1..06ca47f01fd7 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -4197,6 +4197,15 @@ static int hwsim_pmsr_report_nl(struct sk_buff *msg, struct genl_info *info) if (!data) return -EINVAL; + if (!hwsim_virtio_enabled) { + if (hwsim_net_get_netgroup(genl_info_net(info)) != + data->netgroup) + return -EINVAL; + + if (info->snd_portid != data->wmediumd) + return -EPERM; + } + mutex_lock(&data->mutex); if (!data->pmsr_request) { err = -EINVAL; From 37a77bd1395e8261d1760ae39c7f5eb637300550 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Thu, 9 Jul 2026 03:59:04 +0800 Subject: [PATCH 0438/1433] wifi: mac80211_hwsim: clear PMSR request state on abort mac80211_hwsim saves the in-flight cfg80211 PMSR request and its wdev in data->pmsr_request / data->pmsr_request_wdev when a measurement starts, and clears them only when it reports completion. mac80211_hwsim_abort_pmsr() never cleared that saved state. cfg80211 owns the request and frees it once the abort callback returns (cfg80211_pmsr_process_abort() calls rdev_abort_pmsr() then kfree(req)), so after an abort data->pmsr_request dangles. A later hwsim PMSR report then dereferences the freed request in hwsim_pmsr_report_nl() and completes it; a use-after-free. Clear data->pmsr_request and data->pmsr_request_wdev once the abort matches the active request. Move the wmediumd/virtio notification check below the clear so the saved state is dropped even when no notification is sent. Assisted-by: Codex:gpt-5 Assisted-by: Claude:opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260708195911.84365-2-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/virtual/mac80211_hwsim_main.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index 06ca47f01fd7..d5e3d19ccc3e 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -3831,9 +3831,6 @@ static void mac80211_hwsim_abort_pmsr(struct ieee80211_hw *hw, int err = 0; data = hw->priv; - _portid = READ_ONCE(data->wmediumd); - if (!_portid && !hwsim_virtio_enabled) - return; mutex_lock(&data->mutex); @@ -3842,6 +3839,13 @@ static void mac80211_hwsim_abort_pmsr(struct ieee80211_hw *hw, goto out; } + data->pmsr_request = NULL; + data->pmsr_request_wdev = NULL; + + _portid = READ_ONCE(data->wmediumd); + if (!_portid && !hwsim_virtio_enabled) + goto out; + skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); if (!skb) { err = -ENOMEM; From dd406779999fa2065ec6b7c4f80906b727041d2c Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Mon, 13 Jul 2026 07:29:46 +0530 Subject: [PATCH 0439/1433] wifi: mac80211: don't encrypt pre-auth (ETH_P_PREAUTH) frames Pre-authentication frames (ETH_P_PREAUTH, 0x88C7) are sent before the authentication handshake completes with the target AP, so no encryption key exists for them yet. Unlike normal EAPOL frames (ETH_P_8021X, 0x888E) which are registered as the control port protocol, pre-auth frames are not recognized as control port frames, causing the kernel to incorrectly assign the current AP's key and attempt encryption, resulting in a WARN_ON in ieee80211_encrypt_tx_skb when the cipher is not handled. Fix this by setting IEEE80211_TX_INTFL_DONT_ENCRYPT for pre-auth frames in ieee80211_tx_h_check_control_port_protocol(), so that key selection skips them and they are sent unencrypted as intended. Note that the only driver hitting this path is hwsim. Reported-by: syzbot+b6ce23950fd636e6efb6@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=b6ce23950fd636e6efb6 Signed-off-by: Deepanshu Kartikey Link: https://patch.msgid.link/20260713015946.44636-1-kartikey406@gmail.com [add note about hwsim, fix subject] Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 42cfd76850b8..eb36f1b9771b 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -557,6 +557,9 @@ ieee80211_tx_h_check_control_port_protocol(struct ieee80211_tx_data *tx) info->flags |= IEEE80211_TX_CTL_USE_MINRATE; } + if (tx->skb->protocol == htons(ETH_P_PREAUTH)) + info->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT; + return TX_CONTINUE; } From a3262f61d102eaae81048d10eb0aadcc623ea026 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 15 Jul 2026 21:03:33 +0300 Subject: [PATCH 0440/1433] wifi: mac80211: always send regulatory connectivity element The spec says to include it if the STA is "capable of operating as STA 6G", which is a bit unclear because this is defined at a STA level and not at the MLD level or so, but WFA requires this to be included. Either way, the element is completely advisory and intended mostly for debugging (and perhaps a bit steering), so just include it in association request all the time if 6 GHz is supported. To determine what exactly to include, check all the channels that aren't disabled. That way, it ends up being a lowest common denominator, which is most useful for steering etc. Signed-off-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715210322.02a4f3fced21.I94bbd08ac38001e11d5143a8b3f54dcea8ae8e15@changeid Signed-off-by: Johannes Berg --- net/mac80211/ieee80211_i.h | 4 ++-- net/mac80211/mlme.c | 13 ++++++------- net/mac80211/util.c | 24 +++++++++++++++++++++--- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index d585820245dd..11f449e8ff00 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -2771,8 +2771,8 @@ int ieee80211_put_eht_cap(struct sk_buff *skb, int ieee80211_put_uhr_cap(struct sk_buff *skb, struct ieee80211_sub_if_data *sdata, const struct ieee80211_supported_band *sband); -int ieee80211_put_reg_conn(struct sk_buff *skb, - enum ieee80211_channel_flags flags); +void ieee80211_put_reg_conn(struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb); /* channel management */ bool ieee80211_chandef_ht_oper(const struct ieee80211_ht_operation *ht_oper, diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 9e92337bb6f9..1afdb610dff2 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -2307,13 +2307,14 @@ ieee80211_add_link_elems(struct ieee80211_sub_if_data *sdata, offset = ieee80211_add_before_reg_conn(skb, extra_elems, extra_elems_len, offset); - if (sband->band == NL80211_BAND_6GHZ) { + /* only add this on the assoc link, not in per-STA profiles */ + if (link) { /* * as per Section E.2.7 of IEEE 802.11 REVme D7.0, non-AP STA * capable of operating on the 6 GHz band shall transmit * regulatory connectivity element. */ - ieee80211_put_reg_conn(skb, chan->flags); + ieee80211_put_reg_conn(sdata, skb); } /* @@ -2564,11 +2565,8 @@ ieee80211_link_common_elems_size(struct ieee80211_sub_if_data *sdata, sizeof(struct ieee80211_he_mcs_nss_supp) + IEEE80211_HE_PPE_THRES_MAX_LEN; - if (sband->band == NL80211_BAND_6GHZ) { + if (sband->band == NL80211_BAND_6GHZ) size += 2 + 1 + sizeof(struct ieee80211_he_6ghz_capa); - /* reg connection */ - size += 4; - } size += 2 + 1 + sizeof(struct ieee80211_eht_cap_elem) + sizeof(struct ieee80211_eht_mcs_nss_supp) + @@ -2615,7 +2613,8 @@ static int ieee80211_send_assoc(struct ieee80211_sub_if_data *sdata) 2 + assoc_data->ssid_len + /* SSID */ assoc_data->ie_len + /* extra IEs */ (assoc_data->fils_kek_len ? 16 /* AES-SIV */ : 0) + - 9; /* WMM */ + 9 /* WMM */ + + 4 /* regulatory connectivity, if 6 GHz is supported */; for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) { struct cfg80211_bss *cbss = assoc_data->link[link_id].bss; diff --git a/net/mac80211/util.c b/net/mac80211/util.c index f6d4ae4127c8..34f500deb2c8 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -2703,12 +2703,31 @@ int ieee80211_put_he_cap(struct sk_buff *skb, return 0; } -int ieee80211_put_reg_conn(struct sk_buff *skb, - enum ieee80211_channel_flags flags) +void ieee80211_put_reg_conn(struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb) { + struct ieee80211_local *local = sdata->local; u8 reg_conn = IEEE80211_REG_CONN_LPI_VALID | IEEE80211_REG_CONN_LPI_VALUE | IEEE80211_REG_CONN_SP_VALID; + struct ieee80211_supported_band *sband; + bool available_channels = false; + u32 flags = 0; + int i; + + sband = local->hw.wiphy->bands[NL80211_BAND_6GHZ]; + if (!sband) + return; + + for (i = 0; i < sband->n_channels; i++) { + if (sband->channels[i].flags & IEEE80211_CHAN_DISABLED) + continue; + flags |= sband->channels[i].flags; + available_channels = true; + } + + if (!available_channels) + return; if (!(flags & IEEE80211_CHAN_NO_6GHZ_AFC_CLIENT)) reg_conn |= IEEE80211_REG_CONN_SP_VALUE; @@ -2717,7 +2736,6 @@ int ieee80211_put_reg_conn(struct sk_buff *skb, skb_put_u8(skb, 1 + sizeof(reg_conn)); skb_put_u8(skb, WLAN_EID_EXT_NON_AP_STA_REG_CON); skb_put_u8(skb, reg_conn); - return 0; } int ieee80211_put_he_6ghz_cap(struct sk_buff *skb, From 410d70acf9ca73fdf370e1eb7e1da64e486dcbb5 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 15 Jul 2026 21:08:34 +0300 Subject: [PATCH 0441/1433] wifi: use UHR operation field presence bits The spec originally had the idea that the fact that it's a beacon frame determines the (non-)presence of the values, but added presence bits in D1.4. Use those presence bits in addition to the enable bits. Signed-off-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715210407.3b1a79b0d002.Iaa762c55b4b6dc63d55f2d7b8b42acd47e640d50@changeid Signed-off-by: Johannes Berg --- include/linux/ieee80211-uhr.h | 44 +++++++++++++++++------------------ net/mac80211/mlme.c | 3 +-- net/mac80211/parse.c | 4 +--- net/wireless/nl80211.c | 2 +- 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/include/linux/ieee80211-uhr.h b/include/linux/ieee80211-uhr.h index 597c9e559261..665d4b3a5b41 100644 --- a/include/linux/ieee80211-uhr.h +++ b/include/linux/ieee80211-uhr.h @@ -17,6 +17,11 @@ #define IEEE80211_UHR_OPER_PARAMS_PEDCA_ENA 0x0004 #define IEEE80211_UHR_OPER_PARAMS_DBE_ENA 0x0008 #define IEEE80211_UHR_OPER_PARAMS_DBE_BW 0x0070 +#define IEEE80211_UHR_OPER_PARAMS_DUO_PRES 0x0080 +#define IEEE80211_UHR_OPER_PARAMS_DPS_PRES 0x0100 +#define IEEE80211_UHR_OPER_PARAMS_NPCA_PRES 0x0200 +#define IEEE80211_UHR_OPER_PARAMS_PEDCA_PRES 0x0400 +#define IEEE80211_UHR_OPER_PARAMS_DBE_PRES 0x0800 struct ieee80211_uhr_operation { __le16 params; @@ -265,8 +270,7 @@ struct ieee80211_uhr_p_edca_info { __le16 params; } __packed; -static inline bool ieee80211_uhr_oper_size_ok(const u8 *data, u8 len, - bool beacon) +static inline bool ieee80211_uhr_oper_size_ok(const u8 *data, u8 len) { const struct ieee80211_uhr_operation *oper = (const void *)data; u8 needed = sizeof(*oper); @@ -274,19 +278,15 @@ static inline bool ieee80211_uhr_oper_size_ok(const u8 *data, u8 len, if (len < needed) return false; - /* nothing else present in beacons */ - if (beacon) - return true; - /* DPS Operation Parameters (fixed 4 bytes) */ - if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DPS_ENA)) { + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DPS_PRES)) { needed += sizeof(struct ieee80211_uhr_dps_info); if (len < needed) return false; } /* NPCA Operation Parameters (fixed 4 bytes + optional 2 bytes) */ - if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_NPCA_ENA)) { + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_NPCA_PRES)) { const struct ieee80211_uhr_npca_info *npca = (const void *)(data + needed); @@ -303,14 +303,14 @@ static inline bool ieee80211_uhr_oper_size_ok(const u8 *data, u8 len, } /* P-EDCA Operation Parameters (fixed 3 bytes) */ - if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_PEDCA_ENA)) { + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_PEDCA_PRES)) { needed += sizeof(struct ieee80211_uhr_p_edca_info); if (len < needed) return false; } /* DBE Operation Parameters (fixed 1 byte + optional 2 bytes) */ - if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DBE_ENA)) { + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DBE_PRES)) { const struct ieee80211_uhr_dbe_info *dbe = (const void *)(data + needed); @@ -329,19 +329,19 @@ static inline bool ieee80211_uhr_oper_size_ok(const u8 *data, u8 len, return len >= needed; } -/* - * Note: cannot call this on the element coming from a beacon, - * must ensure ieee80211_uhr_oper_size_ok(..., false) first - */ +/* Note: must ensure ieee80211_uhr_oper_size_ok(...) first */ static inline const struct ieee80211_uhr_npca_info * ieee80211_uhr_npca_info(const struct ieee80211_uhr_operation *oper) { const u8 *pos = oper->variable; + if (!(oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_NPCA_PRES))) + return NULL; + if (!(oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_NPCA_ENA))) return NULL; - if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DPS_ENA)) + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DPS_PRES)) pos += sizeof(struct ieee80211_uhr_dps_info); return (const void *)pos; @@ -360,22 +360,22 @@ ieee80211_uhr_npca_dis_subch_bitmap(const struct ieee80211_uhr_operation *oper) return npca->dis_subch_bmap; } -/* - * Note: cannot call this on the element coming from a beacon, - * must ensure ieee80211_uhr_oper_size_ok(..., false) first - */ +/* Note: must ensure ieee80211_uhr_oper_size_ok(...) first */ static inline const struct ieee80211_uhr_dbe_info * ieee80211_uhr_oper_dbe_info(const struct ieee80211_uhr_operation *oper) { const u8 *pos = oper->variable; + if (!(oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DBE_PRES))) + return NULL; + if (!(oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DBE_ENA))) return NULL; - if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DPS_ENA)) + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DPS_PRES)) pos += sizeof(struct ieee80211_uhr_dps_info); - if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_NPCA_ENA)) { + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_NPCA_PRES)) { const struct ieee80211_uhr_npca_info *npca = (const void *)pos; pos += sizeof(*npca); @@ -383,7 +383,7 @@ ieee80211_uhr_oper_dbe_info(const struct ieee80211_uhr_operation *oper) pos += sizeof(npca->dis_subch_bmap[0]); } - if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_PEDCA_ENA)) + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_PEDCA_PRES)) pos += sizeof(struct ieee80211_uhr_p_edca_info); return (const void *)pos; diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 1afdb610dff2..c4590c4a56b0 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -1701,8 +1701,7 @@ static int ieee80211_config_bw(struct ieee80211_link_data *link, if (stype != IEEE80211_STYPE_BEACON && chanreq.oper.npca_chan && elems->uhr_operation && ieee80211_uhr_oper_size_ok((const void *)elems->uhr_operation, - elems->uhr_operation_len, - false)) { + elems->uhr_operation_len)) { const struct ieee80211_uhr_npca_info *npca; struct ieee80211_bss_npca_params params = {}; diff --git a/net/mac80211/parse.c b/net/mac80211/parse.c index c2f2f78f2b4f..cb2be167cde7 100644 --- a/net/mac80211/parse.c +++ b/net/mac80211/parse.c @@ -209,9 +209,7 @@ ieee80211_parse_extension_element(u32 *crc, if (params->mode < IEEE80211_CONN_MODE_UHR) break; calc_crc = true; - if (ieee80211_uhr_oper_size_ok(data, len, - params->type == (IEEE80211_FTYPE_MGMT | - IEEE80211_STYPE_BEACON))) { + if (ieee80211_uhr_oper_size_ok(data, len)) { elems->uhr_operation = data; elems->uhr_operation_len = len; } diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 242071ad10d6..5fa70974d57c 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -441,7 +441,7 @@ static int validate_uhr_operation(const struct nlattr *attr, const u8 *data = nla_data(attr); unsigned int len = nla_len(attr); - if (!ieee80211_uhr_oper_size_ok(data, len, false)) + if (!ieee80211_uhr_oper_size_ok(data, len)) return -EINVAL; return 0; } From 3002812cbedb09a359e0865670d7ae6267b1dd6f Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 15 Jul 2026 21:11:25 +0300 Subject: [PATCH 0442/1433] wifi: cfg80211: improve multi-BSSID profile continuation parser The previous change from John Walker fixed the loop iteration, but the code is written in a bad way. Pass the pointers needed for the iteration to the function instead. Signed-off-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715211048.04877081fd0a.I48f0135ba83dcc5f0b736b61f8f9e86ecc72583f@changeid Signed-off-by: Johannes Berg --- net/wireless/scan.c | 49 +++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 05b7dc6b766c..e62b7dd2b7c2 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -2403,12 +2403,11 @@ cfg80211_inform_single_bss_data(struct wiphy *wiphy, return NULL; } -static const struct element -*cfg80211_get_profile_continuation(const u8 *ie, size_t ielen, - const struct element *mbssid_elem, - const struct element *sub_elem) +static bool cfg80211_iter_profile_continuation(const u8 *ie, size_t ielen, + const struct element **mbssid, + const struct element **sub_elem) { - const u8 *mbssid_end = mbssid_elem->data + mbssid_elem->datalen; + const u8 *mbssid_end = (*mbssid)->data + (*mbssid)->datalen; const struct element *next_mbssid; const struct element *next_sub; @@ -2420,30 +2419,34 @@ static const struct element * If it is not the last subelement in current MBSSID IE or there isn't * a next MBSSID IE - profile is complete. */ - if ((sub_elem->data + sub_elem->datalen < mbssid_end - 1) || + if (((*sub_elem)->data + (*sub_elem)->datalen < mbssid_end - 1) || !next_mbssid) - return NULL; + return false; - /* For any length error, just return NULL */ + /* For any length error, just return false to stop iteration */ if (next_mbssid->datalen < 4) - return NULL; + return false; next_sub = (void *)&next_mbssid->data[1]; if (next_mbssid->data + next_mbssid->datalen < next_sub->data + next_sub->datalen) - return NULL; + return false; if (next_sub->id != 0 || next_sub->datalen < 2) - return NULL; + return false; /* * Check if the first element in the next sub element is a start * of a new profile */ - return next_sub->data[0] == WLAN_EID_NON_TX_BSSID_CAP ? - NULL : next_mbssid; + if (next_sub->data[0] == WLAN_EID_NON_TX_BSSID_CAP) + return false; + + *mbssid = next_mbssid; + *sub_elem = next_sub; + return true; } size_t cfg80211_merge_profile(const u8 *ie, size_t ielen, @@ -2452,26 +2455,20 @@ size_t cfg80211_merge_profile(const u8 *ie, size_t ielen, u8 *merged_ie, size_t max_copy_len) { size_t copied_len = sub_elem->datalen; - const struct element *next_mbssid; if (sub_elem->datalen > max_copy_len) return 0; memcpy(merged_ie, sub_elem->data, sub_elem->datalen); - while ((next_mbssid = cfg80211_get_profile_continuation(ie, ielen, - mbssid_elem, - sub_elem))) { - const struct element *next_sub = (void *)&next_mbssid->data[1]; - - if (copied_len + next_sub->datalen > max_copy_len) + while (cfg80211_iter_profile_continuation(ie, ielen, + &mbssid_elem, + &sub_elem)) { + if (copied_len + sub_elem->datalen > max_copy_len) break; - memcpy(merged_ie + copied_len, next_sub->data, - next_sub->datalen); - copied_len += next_sub->datalen; - - mbssid_elem = next_mbssid; - sub_elem = next_sub; + memcpy(merged_ie + copied_len, sub_elem->data, + sub_elem->datalen); + copied_len += sub_elem->datalen; } return copied_len; From 8ff047b9c7b2900ec6e49361f81d74ac61563cdc Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 15 Jul 2026 21:24:40 +0300 Subject: [PATCH 0443/1433] wifi: cfg80211: clarify and tighten key checks Currently, we accept per-STA GTK for any interface type if the IBSS_RSN flag is set, which doesn't make sense, and also accept various key indices that aren't really (meant to be) supported, such as IGTK/BIGTK on IBSS or AP_VLAN etc. For MESH and NAN_DATA interface types, per-STA GTKs are required, so their support shouldn't depend on IBSS_RSN. Conversely a driver setting IBSS_RSN doesn't really say it also accepts per-STA GTK for other interface types. Move more checks into cfg80211_valid_key_idx() and make them more precise: - allow IGTK and, if supported, BIGTK for NAN - allow per-STA (RX) GTK only for - NAN_DATA - IBSS if IBSS_RSN is supported - MESH - allow B/I/GTK for station/P2P-client without mac_addr for RX with the current AP (historic API quirk), subject to support - allow TX GTK for AP/P2P-GO/AP_VLAN - allow TX IGTK/BIGTK for AP/P2P-GO subject to support Other settings are rejected, clearing up corner cases and disallowing unexpected settings. Signed-off-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715212403.725e6b63e890.I24684374112bb94d0633d61ef76ecb8a1517f7f1@changeid Signed-off-by: Johannes Berg --- net/wireless/core.h | 5 +- net/wireless/nl80211.c | 11 ++-- net/wireless/util.c | 107 +++++++++++++++++++++++++++---------- net/wireless/wext-compat.c | 3 +- 4 files changed, 87 insertions(+), 39 deletions(-) diff --git a/net/wireless/core.h b/net/wireless/core.h index df47ed6208a5..399037514943 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -443,8 +443,9 @@ void cfg80211_sme_abandon_assoc(struct wireless_dev *wdev); /* internal helpers */ bool cfg80211_supported_cipher_suite(struct wiphy *wiphy, u32 cipher); -bool cfg80211_valid_key_idx(struct cfg80211_registered_device *rdev, - int key_idx, bool pairwise); +bool cfg80211_valid_key_idx(struct wireless_dev *wdev, + int key_idx, bool pairwise, + const u8 *mac_addr); int cfg80211_validate_key_settings(struct cfg80211_registered_device *rdev, struct wireless_dev *wdev, struct key_params *params, int key_idx, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 5fa70974d57c..4044b5505104 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -5409,7 +5409,7 @@ static int nl80211_get_key(struct sk_buff *skb, struct genl_info *info) if (!rdev->ops->get_key) return -EOPNOTSUPP; - if (!pairwise && mac_addr && !(rdev->wiphy.flags & WIPHY_FLAG_IBSS_RSN)) + if (!cfg80211_valid_key_idx(wdev, key_idx, pairwise, mac_addr)) return -ENOENT; msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); @@ -5663,8 +5663,9 @@ static int nl80211_del_key(struct sk_buff *skb, struct genl_info *info) key.type != NL80211_KEYTYPE_GROUP) return -EINVAL; - if (!cfg80211_valid_key_idx(rdev, key.idx, - key.type == NL80211_KEYTYPE_PAIRWISE)) + if (!cfg80211_valid_key_idx(wdev, key.idx, + key.type == NL80211_KEYTYPE_PAIRWISE, + mac_addr)) return -EINVAL; if (!rdev->ops->del_key) @@ -5672,10 +5673,6 @@ static int nl80211_del_key(struct sk_buff *skb, struct genl_info *info) err = nl80211_key_allowed(wdev); - if (key.type == NL80211_KEYTYPE_GROUP && mac_addr && - !(rdev->wiphy.flags & WIPHY_FLAG_IBSS_RSN)) - err = -ENOENT; - if (!err) err = nl80211_validate_key_link_id(info, wdev, link_id, key.type == NL80211_KEYTYPE_PAIRWISE); diff --git a/net/wireless/util.c b/net/wireless/util.c index 24527bf321b2..3e584d0ca3e2 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -241,10 +241,8 @@ bool cfg80211_supported_cipher_suite(struct wiphy *wiphy, u32 cipher) return false; } -static bool -cfg80211_igtk_cipher_supported(struct cfg80211_registered_device *rdev) +static bool cfg80211_igtk_cipher_supported(struct wiphy *wiphy) { - struct wiphy *wiphy = &rdev->wiphy; int i; for (i = 0; i < wiphy->n_cipher_suites; i++) { @@ -260,27 +258,86 @@ cfg80211_igtk_cipher_supported(struct cfg80211_registered_device *rdev) return false; } -bool cfg80211_valid_key_idx(struct cfg80211_registered_device *rdev, - int key_idx, bool pairwise) +bool cfg80211_valid_key_idx(struct wireless_dev *wdev, + int key_idx, bool pairwise, + const u8 *mac_addr) { - int max_key_idx; - - if (pairwise) - max_key_idx = 3; - else if (wiphy_ext_feature_isset(&rdev->wiphy, - NL80211_EXT_FEATURE_BEACON_PROTECTION) || - wiphy_ext_feature_isset(&rdev->wiphy, - NL80211_EXT_FEATURE_BEACON_PROTECTION_CLIENT)) - max_key_idx = 7; - else if (cfg80211_igtk_cipher_supported(rdev)) - max_key_idx = 5; - else - max_key_idx = 3; - - if (key_idx < 0 || key_idx > max_key_idx) + if (WARN_ON(!wdev)) return false; - return true; + if (key_idx < 0) + return false; + + /* + * Can't differentiate ciphers here so allow 0..3. + * Pairwise keys must be for a station (MAC address given). + */ + if (pairwise) { + if (!mac_addr) + return false; + + return key_idx < 4; + } + + /* + * For group keys, mac_addr==NULL means setting a group key + * for TX, which is only supported on some interface types, + * except for STATION/P2P_CLIENT, where it's setting the RX + * key with the current AP (for legacy reasons.) + * + * Apart from that exception, a non-NULL mac_addr means RX + * key being set. + */ + + switch (wdev->iftype) { + case NL80211_IFTYPE_ADHOC: + if (!(wdev->wiphy->flags & WIPHY_FLAG_IBSS_RSN)) + return false; + fallthrough; + case NL80211_IFTYPE_MESH_POINT: + /* no support for IGTK/BIGTK (yet?) */ + return key_idx < 4; + case NL80211_IFTYPE_NAN_DATA: + /* these always need to support per-STA GTK */ + return key_idx < 4; + case NL80211_IFTYPE_NAN: + /* no data */ + if (key_idx < 4) + return false; + /* NAN reused this flag */ + if (wiphy_ext_feature_isset(wdev->wiphy, + NL80211_EXT_FEATURE_BEACON_PROTECTION)) + return key_idx <= 7; + return key_idx <= 5; + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + /* see note about exception above */ + if (mac_addr) + return false; + /* BIGTK support implies IGTK support */ + if (wiphy_ext_feature_isset(wdev->wiphy, + NL80211_EXT_FEATURE_BEACON_PROTECTION_CLIENT)) + return key_idx <= 7; + fallthrough; + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + /* no RX with [B]IGTK */ + if (mac_addr) + return false; + if (wiphy_ext_feature_isset(wdev->wiphy, + NL80211_EXT_FEATURE_BEACON_PROTECTION)) + return key_idx <= 7; + fallthrough; + case NL80211_IFTYPE_AP_VLAN: + /* no RX with GTK */ + if (mac_addr) + return false; + if (cfg80211_igtk_cipher_supported(wdev->wiphy)) + return key_idx <= 5; + return key_idx <= 3; + default: + return false; + } } int cfg80211_validate_key_settings(struct cfg80211_registered_device *rdev, @@ -288,13 +345,7 @@ int cfg80211_validate_key_settings(struct cfg80211_registered_device *rdev, struct key_params *params, int key_idx, bool pairwise, const u8 *mac_addr) { - if (!cfg80211_valid_key_idx(rdev, key_idx, pairwise)) - return -EINVAL; - - if (!pairwise && mac_addr && !(rdev->wiphy.flags & WIPHY_FLAG_IBSS_RSN)) - return -EINVAL; - - if (pairwise && !mac_addr) + if (!cfg80211_valid_key_idx(wdev, key_idx, pairwise, mac_addr)) return -EINVAL; switch (params->cipher) { diff --git a/net/wireless/wext-compat.c b/net/wireless/wext-compat.c index 5dbf3ef4b257..d45bc08c0de4 100644 --- a/net/wireless/wext-compat.c +++ b/net/wireless/wext-compat.c @@ -454,8 +454,7 @@ static int cfg80211_set_encryption(struct cfg80211_registered_device *rdev, rejoin = true; } - if (!pairwise && addr && - !(rdev->wiphy.flags & WIPHY_FLAG_IBSS_RSN)) + if (!cfg80211_valid_key_idx(wdev, idx, pairwise, addr)) err = -ENOENT; else err = rdev_del_key(rdev, wdev, -1, idx, pairwise, From aa4c0a649903567762edf2bbc7fff608953b3ca4 Mon Sep 17 00:00:00 2001 From: Shahar Tzarfati Date: Wed, 15 Jul 2026 21:27:43 +0300 Subject: [PATCH 0444/1433] wifi: mac80211: ibss: read deauth reason_code after frame length check The function was reading reason_code from the frame before validating that the frame is at least IEEE80211_DEAUTH_FRAME_LEN bytes long. Move the reason_code read to after the length check so the field is guaranteed to be present before it is accessed. Signed-off-by: Shahar Tzarfati Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715212706.db26604650bd.I2caa73c396b8c9d357224b9334d5df3cafac498e@changeid Signed-off-by: Johannes Berg --- net/mac80211/ibss.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/mac80211/ibss.c b/net/mac80211/ibss.c index d0fd6054f182..9915dd5c36df 100644 --- a/net/mac80211/ibss.c +++ b/net/mac80211/ibss.c @@ -881,11 +881,13 @@ static void ieee80211_rx_mgmt_deauth_ibss(struct ieee80211_sub_if_data *sdata, struct ieee80211_mgmt *mgmt, size_t len) { - u16 reason = le16_to_cpu(mgmt->u.deauth.reason_code); + u16 reason; if (len < IEEE80211_DEAUTH_FRAME_LEN) return; + reason = le16_to_cpu(mgmt->u.deauth.reason_code); + ibss_dbg(sdata, "RX DeAuth SA=%pM DA=%pM\n", mgmt->sa, mgmt->da); ibss_dbg(sdata, "\tBSSID=%pM (reason: %d)\n", mgmt->bssid, reason); sta_info_destroy_addr(sdata, mgmt->sa); From 6deab902b4c06abadeb5242db1488a17fd614e2b Mon Sep 17 00:00:00 2001 From: Minxi Hou Date: Thu, 9 Jul 2026 08:05:41 -0400 Subject: [PATCH 0445/1433] selftests/net/openvswitch: add ICMPv6 echo type match test Register OVS_KEY_ATTR_ICMPV6 in the flow key parser so that icmpv6(type=...) can be used in flow specifications. Without this registration the parser silently drops the token and the kernel rejects the flow with EINVAL because the expected ICMPv6 key attribute is missing. While here, add convert_int() to the ovs_key_ipv6 and ovs_key_icmp fields_map entries so that specifying a field value produces the correct wildcard mask. The IPv6 flow label uses convert_int(20) to produce a 20-bit mask (0x000FFFFF), matching the kernel constraint in flow_netlink.c that rejects masks with bits 20-31 set; byte-wide fields use convert_int(8). The ipv4 counterpart already does this via convert_int(); the ipv6 and icmp classes were simply missing the fifth tuple element. Existing callers that pass empty parentheses are unaffected because convert_int("") returns (0, 0). Add test_icmpv6 exercising the ICMPv6 echo flow key. The test uses static neighbour entries with nud permanent to prevent racy NDP, then verifies in three steps: install icmpv6(type=128) and icmpv6(type=129) flows and confirm ping works, remove the flows and confirm ping fails, reinstall and confirm recovery. Signed-off-by: Minxi Hou Reviewed-by: Aaron Conole Link: https://patch.msgid.link/20260709120541.3556748-1-houminxi@gmail.com Signed-off-by: Jakub Kicinski --- .../selftests/net/openvswitch/openvswitch.sh | 81 +++++++++++++++++++ .../selftests/net/openvswitch/ovs-dpctl.py | 26 ++++-- 2 files changed, 100 insertions(+), 7 deletions(-) diff --git a/tools/testing/selftests/net/openvswitch/openvswitch.sh b/tools/testing/selftests/net/openvswitch/openvswitch.sh index f75ee723415a..853dbc1b00d7 100755 --- a/tools/testing/selftests/net/openvswitch/openvswitch.sh +++ b/tools/testing/selftests/net/openvswitch/openvswitch.sh @@ -33,6 +33,7 @@ tests=" flow_set flow-set: Flow modify action_set set: SET action rewrites fields trunc trunc: output truncation + icmpv6 icmpv6: ICMPv6 echo type match psample psample: Sampling packets with psample" info() { @@ -530,6 +531,86 @@ test_trunc() { return 0 } +# icmpv6 test +# - static neighbours to bypass NDP (nud permanent) +# - icmpv6(type=128) echo request, icmpv6(type=129) echo reply +# - remove flows and verify ping fails, reinstall and recover +test_icmpv6() { + local t="test_icmpv6" + local v6="eth_type(0x86dd),ipv6(proto=58)" + + sbx_add "$t" || return $? + ovs_add_dp "$t" icmpv6 || return 1 + + info "create namespaces" + for ns in client server; do + ovs_add_netns_and_veths "$t" "icmpv6" \ + "$ns" "${ns:0:1}0" "${ns:0:1}1" || return 1 + done + + ip netns exec client ip addr add fd00::1/64 dev c1 nodad + ip netns exec client ip link set c1 up + ip netns exec server ip addr add fd00::2/64 dev s1 nodad + ip netns exec server ip link set s1 up + + local cl_mac sl_mac + cl_mac=$(ip netns exec client ip link show c1 \ + | awk '/link\/ether/ {print $2}') + [ -z "$cl_mac" ] && \ + { info "failed to get c1 hwaddr"; return 1; } + sl_mac=$(ip netns exec server ip link show s1 \ + | awk '/link\/ether/ {print $2}') + [ -z "$sl_mac" ] && \ + { info "failed to get s1 hwaddr"; return 1; } + ip netns exec client ip -6 neigh add fd00::2 \ + lladdr "$sl_mac" nud permanent dev c1 || return 1 + ip netns exec server ip -6 neigh add fd00::1 \ + lladdr "$cl_mac" nud permanent dev s1 || return 1 + + # Probe: check if kernel supports icmpv6 flow key. + ovs_add_flow "$t" icmpv6 \ + "in_port(1),eth(),$v6,icmpv6(type=128)" \ + '2' &>/dev/null + if [ $? -ne 0 ]; then + info "no support for icmpv6 key - skipping" + ovs_exit_sig + return $ksft_skip + fi + ovs_del_flows "$t" icmpv6 + + ovs_add_flow "$t" icmpv6 \ + "in_port(1),eth(),$v6,icmpv6(type=128)" \ + '2' || return 1 + ovs_add_flow "$t" icmpv6 \ + "in_port(2),eth(),$v6,icmpv6(type=129)" \ + '1' || return 1 + + info "verify ICMPv6 echo with type-specific flows" + ovs_sbx "$t" ip netns exec client \ + ping -6 -c 1 -W 2 fd00::2 || return 1 + + ovs_del_flows "$t" icmpv6 + + info "verify ping fails without echo flows" + ovs_sbx "$t" ip netns exec client \ + ping -6 -c 1 -W 2 fd00::2 >/dev/null 2>&1 \ + && { info "ping should fail without flows" + return 1; } + + ovs_add_flow "$t" icmpv6 \ + "in_port(1),eth(),$v6,icmpv6(type=128)" \ + '2' || return 1 + ovs_add_flow "$t" icmpv6 \ + "in_port(2),eth(),$v6,icmpv6(type=129)" \ + '1' || return 1 + + info "verify connectivity restored" + ovs_sbx "$t" ip netns exec client \ + ping -6 -c 1 -W 2 fd00::2 || return 1 + + return 0 +} + # psample test # - use psample to observe packets test_psample() { diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py index e1ecfad2c03e..f3edd198223f 100644 --- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py +++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py @@ -1255,11 +1255,16 @@ class ovskey(nla): lambda x: ipaddress.IPv6Address(x).packed if x else 0, convert_ipv6, ), - ("label", "label", "%d", lambda x: int(x) if x else 0), - ("proto", "proto", "%d", lambda x: int(x) if x else 0), - ("tclass", "tclass", "%d", lambda x: int(x) if x else 0), - ("hlimit", "hlimit", "%d", lambda x: int(x) if x else 0), - ("frag", "frag", "%d", lambda x: int(x) if x else 0), + ("label", "label", "%d", lambda x: int(x) if x else 0, + convert_int(20)), + ("proto", "proto", "%d", lambda x: int(x) if x else 0, + convert_int(8)), + ("tclass", "tclass", "%d", lambda x: int(x) if x else 0, + convert_int(8)), + ("hlimit", "hlimit", "%d", lambda x: int(x) if x else 0, + convert_int(8)), + ("frag", "frag", "%d", lambda x: int(x) if x else 0, + convert_int(8)), ) def __init__( @@ -1344,8 +1349,10 @@ class ovskey(nla): ) fields_map = ( - ("type", "type", "%d", lambda x: int(x) if x else 0), - ("code", "code", "%d", lambda x: int(x) if x else 0), + ("type", "type", "%d", lambda x: int(x) if x else 0, + convert_int(8)), + ("code", "code", "%d", lambda x: int(x) if x else 0, + convert_int(8)), ) def __init__( @@ -1982,6 +1989,11 @@ class ovskey(nla): "icmp", ovskey.ovs_key_icmp, ), + ( + "OVS_KEY_ATTR_ICMPV6", + "icmpv6", + ovskey.ovs_key_icmpv6, + ), ( "OVS_KEY_ATTR_TCP_FLAGS", "tcp_flags", From ede0f99cef53ae142ec9f54c705e4b1e48355fd3 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 9 Jul 2026 10:27:13 +0200 Subject: [PATCH 0446/1433] net: mvneta: bm: fix device reference leak on failed lookup Make sure to drop the reference taken to the buffer manager device when attempting to look up its driver data before the driver has been bound. Note that holding a reference to a device does not prevent its driver data from going away. Cc: stable+noautosel@kernel.org # untested fix to unlikely error path Cc: Gregory CLEMENT Signed-off-by: Johan Hovold Reviewed-by: Harshitha Ramamurthy Link: https://patch.msgid.link/20260709082713.829446-1-johan@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/marvell/mvneta_bm.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/marvell/mvneta_bm.c b/drivers/net/ethernet/marvell/mvneta_bm.c index e0c693c0a910..d6d76fbf27b6 100644 --- a/drivers/net/ethernet/marvell/mvneta_bm.c +++ b/drivers/net/ethernet/marvell/mvneta_bm.c @@ -397,9 +397,20 @@ static void mvneta_bm_put_sram(struct mvneta_bm *priv) struct mvneta_bm *mvneta_bm_get(struct device_node *node) { - struct platform_device *pdev = of_find_device_by_node(node); + struct platform_device *pdev; + struct mvneta_bm *priv; - return pdev ? platform_get_drvdata(pdev) : NULL; + pdev = of_find_device_by_node(node); + if (!pdev) + return NULL; + + priv = platform_get_drvdata(pdev); + if (!priv) { + platform_device_put(pdev); + return NULL; + } + + return priv; } EXPORT_SYMBOL_GPL(mvneta_bm_get); From be9381d5776c9b790d0b3429846c46bca3b8634a Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Fri, 10 Jul 2026 17:05:22 +0800 Subject: [PATCH 0447/1433] bna: fix use-after-free on DMA mapping failure If dma_map_single() fails in bnad_start_xmit(), the skb is freed, but head_unmap->skb was set before the mapping attempt and is not cleared. The producer index is not advanced, so later transmissions normally overwrite the entry. However, if the interface is brought down first, bnad_txq_cleanup() scans the entire unmap queue, finds the stale pointer, and calls bnad_tx_buff_unmap() on it. That function dereferences the freed skb in skb_headlen(). Its zero nvecs count is decremented to -1, causing its while (nvecs) loop to repeatedly unmap entries around the TX ring and potentially hang cleanup. Set head_unmap->skb after the first DMA mapping succeeds. This prevents the stale entry from reaching bnad_tx_buff_unmap(). Cc: stable+noautosel@kernel.org # untested fix to unlikely error path Signed-off-by: Xuanqiang Luo Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260710090527.58354-2-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/brocade/bna/bnad.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/brocade/bna/bnad.c b/drivers/net/ethernet/brocade/bna/bnad.c index 8e19add764db..8b75004ba7c9 100644 --- a/drivers/net/ethernet/brocade/bna/bnad.c +++ b/drivers/net/ethernet/brocade/bna/bnad.c @@ -3006,7 +3006,6 @@ bnad_start_xmit(struct sk_buff *skb, struct net_device *netdev) txqent->hdr.wi.reserved = 0; txqent->hdr.wi.num_vectors = vectors; - head_unmap->skb = skb; head_unmap->nvecs = 0; /* Program the vectors */ @@ -3018,6 +3017,7 @@ bnad_start_xmit(struct sk_buff *skb, struct net_device *netdev) BNAD_UPDATE_CTR(bnad, tx_skb_map_failed); return NETDEV_TX_OK; } + head_unmap->skb = skb; BNA_SET_DMA_ADDR(dma_addr, &txqent->vector[0].host_addr); txqent->vector[0].length = htons(len); dma_unmap_addr_set(&unmap->vectors[0], dma_addr, dma_addr); From dd5de39af5efa962f5f12c3ecf3841fca74858b5 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Fri, 10 Jul 2026 17:05:23 +0800 Subject: [PATCH 0448/1433] hinic3: fix use-after-free on DMA mapping failure If hinic3_tx_map_skb() fails in hinic3_send_one_skb(), the skb is freed, but tx_info->skb was set before the mapping attempt and is not cleared. The SQ producer index is rolled back, so later transmissions normally overwrite the entry. If the interface is brought down first, hinic3_free_txqs_res() calls free_all_tx_skbs(). It scans the entire tx_info array and finds the stale pointer. hinic3_tx_unmap_skb() then dereferences the freed skb in skb_shinfo(), before it is freed again. Set tx_info->skb and its WQEBB count only after DMA mapping succeeds, preventing the stale pointer from reaching free_all_tx_skbs(). Cc: stable+noautosel@kernel.org # untested fix to unlikely driver error path Signed-off-by: Xuanqiang Luo Reviewed-by: Fan Gong Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260710090527.58354-3-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/huawei/hinic3/hinic3_tx.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c index 9306bf0020ca..5739ecb08d0d 100644 --- a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c +++ b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c @@ -578,8 +578,6 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb, *wqe_combo.task = task; tx_info = &txq->tx_info[pi]; - tx_info->skb = skb; - tx_info->wqebb_cnt = wqebb_cnt; err = hinic3_tx_map_skb(netdev, skb, txq, tx_info, &wqe_combo); if (err) { @@ -589,6 +587,9 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb, goto err_drop_pkt; } + tx_info->skb = skb; + tx_info->wqebb_cnt = wqebb_cnt; + netif_subqueue_sent(netdev, txq->sq->q_id, skb->len); netif_subqueue_maybe_stop(netdev, txq->sq->q_id, hinic3_wq_free_wqebbs(&txq->sq->wq), From 569595dc92e05a03083287adce9c58dd3ddb16a0 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Fri, 10 Jul 2026 17:05:24 +0800 Subject: [PATCH 0449/1433] net: hibmcge: fix double-free of tx skb on DMA mapping failure If hbg_dma_map() fails, hbg_net_start_xmit() frees the skb, but buffer->skb is left pointing to it. ring->ntu is not advanced, so the buffer is not visible to the TX cleanup path. A subsequent transmit normally overwrites the buffer. However, if the interface is brought down first, hbg_ring_uninit() calls hbg_buffer_free(). It sees the stale pointer, attempts to unmap the failed mapping, and frees the skb again. Clear buffer->skb before freeing the skb in the error path, preventing hbg_buffer_free() from treating it as an outstanding TX buffer. Cc: stable+noautosel@kernel.org # untested fix to unlikely driver error path Signed-off-by: Xuanqiang Luo Reviewed-by: Jijie Shao Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260710090527.58354-4-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/hisilicon/hibmcge/hbg_txrx.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/hisilicon/hibmcge/hbg_txrx.c b/drivers/net/ethernet/hisilicon/hibmcge/hbg_txrx.c index 0ae314994676..4382af937e2e 100644 --- a/drivers/net/ethernet/hisilicon/hibmcge/hbg_txrx.c +++ b/drivers/net/ethernet/hisilicon/hibmcge/hbg_txrx.c @@ -155,6 +155,7 @@ netdev_tx_t hbg_net_start_xmit(struct sk_buff *skb, struct net_device *netdev) buffer->skb = skb; buffer->skb_len = skb->len; if (unlikely(hbg_dma_map(buffer))) { + buffer->skb = NULL; dev_kfree_skb_any(skb); return NETDEV_TX_OK; } From f2596ce59b151b4bac31f355ea82bed22b57d502 Mon Sep 17 00:00:00 2001 From: Chukun Pan Date: Fri, 10 Jul 2026 18:00:00 +0800 Subject: [PATCH 0450/1433] net: dsa: yt921x: Fix external port detection The YT921x switch has two MAC ports: 8 and 9. Currently, the driver only allows port 8 as an external port, while port 9 is not working: yt921x mdio-bus:1d: Wrong mode 23 on port 9 yt921x mdio-bus:1d: Failed to config port 9: -22 Update the external port detection logic to enable the external PHY connected to port 9. Cc: stable+noautosel@kernel.org # never worked Signed-off-by: Chukun Pan Link: https://patch.msgid.link/20260710100000.3018614-1-amadeus@jmu.edu.cn Signed-off-by: Jakub Kicinski --- drivers/net/dsa/yt921x.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/dsa/yt921x.h b/drivers/net/dsa/yt921x.h index 555046526669..5f3b99e189c4 100644 --- a/drivers/net/dsa/yt921x.h +++ b/drivers/net/dsa/yt921x.h @@ -854,7 +854,7 @@ enum yt921x_fdb_entry_status { #define YT921X_PORT_NUM 11 #define yt921x_port_is_internal(port) ((port) < 8) -#define yt921x_port_is_external(port) (8 <= (port) && (port) < 9) +#define yt921x_port_is_external(port) ((port) == 8 || (port) == 9) struct yt921x_mib { u64 rx_broadcast; From 3cbbc9fa33382d03f2813118e672eb318eba9cde Mon Sep 17 00:00:00 2001 From: Yuyang Huang Date: Sat, 11 Jul 2026 09:54:02 +0900 Subject: [PATCH 0451/1433] net: prestera: ignore duplicate RIF destruction events During address teardown, the inetaddr notifier may be called multiple times for the same interface. Ignore NETDEV_DOWN events if the RIF has already been destroyed, rather than returning -EEXIST, which aborts the notifier chain. Cc: Ido Schimmel Cc: Kuniyuki Iwashima Signed-off-by: Yuyang Huang Link: https://patch.msgid.link/20260711005405.2861680-2-yuyanghuang@google.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/marvell/prestera/prestera_router.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/marvell/prestera/prestera_router.c b/drivers/net/ethernet/marvell/prestera/prestera_router.c index b036b173a308..0c4f462baa6e 100644 --- a/drivers/net/ethernet/marvell/prestera/prestera_router.c +++ b/drivers/net/ethernet/marvell/prestera/prestera_router.c @@ -1302,10 +1302,8 @@ static int __prestera_inetaddr_port_event(struct net_device *port_dev, dev_hold(port_dev); break; case NETDEV_DOWN: - if (!re) { - NL_SET_ERR_MSG_MOD(extack, "Can't find RIF"); - return -EEXIST; - } + if (!re) + return 0; prestera_rif_entry_destroy(port->sw, re); dev_put(port_dev); break; From e5b14e9ae82b6ba661a40bcf13c5cfe0d3254628 Mon Sep 17 00:00:00 2001 From: Yuyang Huang Date: Sat, 11 Jul 2026 09:54:03 +0900 Subject: [PATCH 0452/1433] wifi: mac80211: use ifa_dev from event argument During address teardown, the netdevice's ip_ptr might be cleared before the inetaddr notifier is called. In this case, __in_dev_get_rtnl() returns NULL, causing the notifier to abort early and fail to update the ARP filter. Fix this by using the in_device pointer from the event argument (ifa->ifa_dev) which is guaranteed to be valid. Cc: Ido Schimmel Cc: Kuniyuki Iwashima Signed-off-by: Yuyang Huang Link: https://patch.msgid.link/20260711005405.2861680-3-yuyanghuang@google.com Signed-off-by: Jakub Kicinski --- net/mac80211/main.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/net/mac80211/main.c b/net/mac80211/main.c index eb1eaaf34612..f996e15e3d9e 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -588,9 +588,7 @@ static int ieee80211_ifa_changed(struct notifier_block *nb, if (sdata->vif.type != NL80211_IFTYPE_STATION) return NOTIFY_DONE; - idev = __in_dev_get_rtnl(sdata->dev); - if (!idev) - return NOTIFY_DONE; + idev = ifa->ifa_dev; ifmgd = &sdata->u.mgd; From aa22336b76b732d2c890f9de3419e05873711027 Mon Sep 17 00:00:00 2001 From: Yuyang Huang Date: Sat, 11 Jul 2026 09:54:04 +0900 Subject: [PATCH 0453/1433] net: ipv4: clear dev->ip_ptr before destroying inetdev To prevent RCU readers from accessing a partially destroyed in_device, clear dev->ip_ptr early in inetdev_destroy() before freeing the multicast list and individual IP addresses. This aligns the IPv4 teardown sequence with the IPv6 implementation. Cc: Kuniyuki Iwashima Signed-off-by: Yuyang Huang Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260711005405.2861680-4-yuyanghuang@google.com Signed-off-by: Jakub Kicinski --- net/ipv4/devinet.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c index a35b72662e43..3b31f4bec30e 100644 --- a/net/ipv4/devinet.c +++ b/net/ipv4/devinet.c @@ -322,6 +322,8 @@ static void inetdev_destroy(struct in_device *in_dev) in_dev->dead = 1; + RCU_INIT_POINTER(dev->ip_ptr, NULL); + ip_mc_destroy_dev(in_dev); while ((ifa = rtnl_dereference(in_dev->ifa_list)) != NULL) { @@ -329,8 +331,6 @@ static void inetdev_destroy(struct in_device *in_dev) inet_free_ifa(ifa); } - RCU_INIT_POINTER(dev->ip_ptr, NULL); - devinet_sysctl_unregister(in_dev); neigh_parms_release(&arp_tbl, in_dev->arp_parms); arp_ifdown(dev); From c86cfc982957729aa17a043c8c50bf981aa0e4bf Mon Sep 17 00:00:00 2001 From: Mikhail Lukianchikov Date: Sun, 12 Jul 2026 17:38:21 +0600 Subject: [PATCH 0454/1433] dt-bindings: net: microchip,lan78xx: convert to DT schema Convert the Microchip LAN78xx family (LAN7800, LAN7801, LAN7850) binding documentation from plain text to DT schema. Restoring a mistakenly deleted email in MAINTAINERS file and fixing microchip,lan7800.yaml. Signed-off-by: Mikhail Lukianchikov Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260712113821.12543-1-avermoal@gmail.com Signed-off-by: Jakub Kicinski --- .../bindings/net/microchip,lan7800.yaml | 89 +++++++++++++++++++ .../bindings/net/microchip,lan78xx.txt | 53 ----------- MAINTAINERS | 2 +- 3 files changed, 90 insertions(+), 54 deletions(-) create mode 100644 Documentation/devicetree/bindings/net/microchip,lan7800.yaml delete mode 100644 Documentation/devicetree/bindings/net/microchip,lan78xx.txt diff --git a/Documentation/devicetree/bindings/net/microchip,lan7800.yaml b/Documentation/devicetree/bindings/net/microchip,lan7800.yaml new file mode 100644 index 000000000000..cb3927215dfc --- /dev/null +++ b/Documentation/devicetree/bindings/net/microchip,lan7800.yaml @@ -0,0 +1,89 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/microchip,lan7800.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Microchip LAN7800/LAN7801/LAN7850 Gigabit Ethernet controller + +maintainers: + - Thangaraj Samynathan + - Rengarajan Sundararajan + +description: + The LAN7800/LAN7801/LAN7850 devices are usually configured by + programming their OTP or with an external EEPROM, but some + platforms (e.g. Raspberry Pi 3 B+) have neither. The Device Tree + properties, if present, override the OTP and EEPROM. + +allOf: + - $ref: /schemas/usb/usb-device.yaml# + - $ref: /schemas/net/ethernet-controller.yaml# + +properties: + compatible: + enum: + - usb424,7800 + - usb424,7801 + - usb424,7850 + + reg: + maxItems: 1 + description: USB port number + + mdio: + $ref: /schemas/net/mdio.yaml# + unevaluatedProperties: false + + patternProperties: + "^ethernet-phy(@[0-9a-f]+)?$": + $ref: /schemas/net/ethernet-phy.yaml# + unevaluatedProperties: false + type: object + + properties: + microchip,led-modes: + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 4 + description: + Array of LED mode values for each of up to 4 LEDs. + Omitted LEDs are turned off. Allowed values are defined + in include/dt-bindings/net/microchip-lan78xx.h. + + required: + - reg + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + #include + + usb { + #address-cells = <1>; + #size-cells = <0>; + + ethernet@1 { + compatible = "usb424,7800"; + reg = <1>; + local-mac-address = [00 11 22 33 44 55]; + + mdio { + #address-cells = <1>; + #size-cells = <0>; + ethernet-phy@1 { + reg = <1>; + microchip,led-modes = < + LAN78XX_LINK_1000_ACTIVITY + LAN78XX_LINK_10_100_ACTIVITY + >; + }; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/net/microchip,lan78xx.txt b/Documentation/devicetree/bindings/net/microchip,lan78xx.txt deleted file mode 100644 index 11a679530ae6..000000000000 --- a/Documentation/devicetree/bindings/net/microchip,lan78xx.txt +++ /dev/null @@ -1,53 +0,0 @@ -Microchip LAN78xx Gigabit Ethernet controller - -The LAN78XX devices are usually configured by programming their OTP or with -an external EEPROM, but some platforms (e.g. Raspberry Pi 3 B+) have neither. -The Device Tree properties, if present, override the OTP and EEPROM. - -Required properties: -- compatible: Should be one of "usb424,7800", "usb424,7801" or "usb424,7850". - -The MAC address will be determined using the optional properties -defined in ethernet.txt. - -Optional properties of the embedded PHY: -- microchip,led-modes: a 0..4 element vector, with each element configuring - the operating mode of an LED. Omitted LEDs are turned off. Allowed values - are defined in "include/dt-bindings/net/microchip-lan78xx.h". - -Example: - -/* Based on the configuration for a Raspberry Pi 3 B+ */ -&usb { - usb-port@1 { - compatible = "usb424,2514"; - reg = <1>; - #address-cells = <1>; - #size-cells = <0>; - - usb-port@1 { - compatible = "usb424,2514"; - reg = <1>; - #address-cells = <1>; - #size-cells = <0>; - - ethernet: ethernet@1 { - compatible = "usb424,7800"; - reg = <1>; - local-mac-address = [ 00 11 22 33 44 55 ]; - - mdio { - #address-cells = <0x1>; - #size-cells = <0x0>; - eth_phy: ethernet-phy@1 { - reg = <1>; - microchip,led-modes = < - LAN78XX_LINK_1000_ACTIVITY - LAN78XX_LINK_10_100_ACTIVITY - >; - }; - }; - }; - }; - }; -}; diff --git a/MAINTAINERS b/MAINTAINERS index 6940aa3d498b..3fb962e917c2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -27943,7 +27943,7 @@ M: Rengarajan Sundararajan M: UNGLinuxDriver@microchip.com L: netdev@vger.kernel.org S: Maintained -F: Documentation/devicetree/bindings/net/microchip,lan78xx.txt +F: Documentation/devicetree/bindings/net/microchip,lan7800.yaml F: drivers/net/usb/lan78xx.* F: include/dt-bindings/net/microchip-lan78xx.h From 1bb028baab541900d3603d4f93c1c0fb5aba5401 Mon Sep 17 00:00:00 2001 From: Aleksander Jan Bajkowski Date: Sun, 19 Jul 2026 12:01:55 +0200 Subject: [PATCH 0455/1433] net: sfp: add quirk for HORACO copper SFP+ module Add quirk for a copper SFP+ module that identifies itself as "OEM" "HC-10GE-113C". It uses RollBall protocol to talk to the PHY. Signed-off-by: Aleksander Jan Bajkowski Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260719100158.874882-1-olek2@wp.pl Signed-off-by: Jakub Kicinski --- drivers/net/phy/sfp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index f520206734da..6c25b73c668a 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -594,6 +594,9 @@ static const struct sfp_quirk sfp_quirks[] = { SFP_QUIRK_F("YV", "SFP+ONU-XGSPON", sfp_fixup_potron), + // HORACO HC-10GE-113C uses Rollball protocol to talk to the PHY. + SFP_QUIRK_F("OEM", "HC-10GE-113C", sfp_fixup_rollball), + // OEM SFP-GE-T is a 1000Base-T module with broken TX_FAULT indicator SFP_QUIRK_F("OEM", "SFP-GE-T", sfp_fixup_ignore_tx_fault), From 357997831d0ba7c4210a84c38bca531c6a63a6d6 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Sat, 18 Jul 2026 16:38:47 +0200 Subject: [PATCH 0456/1433] net: stmmac: Simplify ioctl handling Now that timestamping is controlled through an NDO, we can simply call phylink_mii_ioctl() to handle ioctls. The only functional difference is that phylink_mii_ioctl() -> phy_mii_ioctl() can handle SIOCSHWTSTAMP, but this no longer happens as this ioctl is not longer dispatched to the ndo_eth_ioctl(). Signed-off-by: Maxime Chevallier Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260718143848.677531-1-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 2a0d7eff88d3..562d20830b94 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -6371,28 +6371,17 @@ static irqreturn_t stmmac_msi_intr_rx(int irq, void *data) * @rq: An IOCTL specific structure, that can contain a pointer to * a proprietary structure used to pass information to the driver. * @cmd: IOCTL command - * Description: - * Currently it supports the phy_mii_ioctl(...) and HW time stamping. + * Description: Forward the PHY ioctls to phylink + * Return: Zero on success or negative error code. */ static int stmmac_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) { struct stmmac_priv *priv = netdev_priv (dev); - int ret = -EOPNOTSUPP; if (!netif_running(dev)) return -EINVAL; - switch (cmd) { - case SIOCGMIIPHY: - case SIOCGMIIREG: - case SIOCSMIIREG: - ret = phylink_mii_ioctl(priv->phylink, rq, cmd); - break; - default: - break; - } - - return ret; + return phylink_mii_ioctl(priv->phylink, rq, cmd); } static int stmmac_setup_tc_block_cb(enum tc_setup_type type, void *type_data, From 43473cc1b0dcfac394378f77b12b54decf5db460 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 15 Jul 2026 21:33:38 +0300 Subject: [PATCH 0457/1433] wifi: mac80211: fix monitor min_def bandwidth Due to reshuffling, the min_def is no longer calculated taking a newly created monitor interface into account as link->conf->chanctx_conf is only assigned after the new bandwidth has been calculated. To handle this, rsvd_for is assigned, but the monitor code didn't handle that. Fix this by checking rsvd_for for monitor explicitly. Signed-off-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715213312.9fe95421689d.I24e08f95e97deb46c6393ee732057d63e6436a2e@changeid Signed-off-by: Johannes Berg --- net/mac80211/chan.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c index 5152b84a3357..75bb204ad743 100644 --- a/net/mac80211/chan.c +++ b/net/mac80211/chan.c @@ -607,10 +607,12 @@ ieee80211_get_chanctx_max_required_bw(struct ieee80211_local *local, max_bw = max(max_bw, width); } - if (!rsvd_for || - rsvd_for->sdata == rcu_access_pointer(local->monitor_sdata)) + if (!rsvd_for) goto check_monitor; + if (rsvd_for->sdata == rcu_access_pointer(local->monitor_sdata)) + return max(max_bw, ctx->conf.def.width); + /* Consider the link for which this chanctx is reserved/going to be assigned */ width = ieee80211_get_width_of_link(rsvd_for); max_bw = max(max_bw, width); From f13e573ab3f12ef94b51f237cb10276278062ff6 Mon Sep 17 00:00:00 2001 From: Pagadala Yesu Anjaneyulu Date: Fri, 17 Jul 2026 17:10:44 +0300 Subject: [PATCH 0458/1433] wifi: mac80211: notify driver before destroying assoc link During association completion, mac80211 must notify the driver before destroying the association link context. Previously, the driver callback drv_mgd_complete_tx() was invoked after ieee80211_destroy_assoc_data(), which left the driver with no link context to perform per-link resource cleanup operations. Move drv_mgd_complete_tx() invocation into ieee80211_destroy_assoc_data(), before link destruction. This ensures the driver has access to link context when needed for cleanup. Similar for ieee80211_destroy_auth_data(). On failed associations, the driver now has the link available to safely cancel any active sessions. Signed-off-by: Pagadala Yesu Anjaneyulu Assisted-by: GitHubCopilot:gpt-5.3-codex Reviewed-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260717171018.3afb91fcaac7.I308890d98c35acf1ceb79c295526d18c8eb164b9@changeid Signed-off-by: Johannes Berg --- net/mac80211/mlme.c | 75 ++++++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index c4590c4a56b0..6b5332198ffa 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -5257,7 +5257,8 @@ void ieee80211_disconnect(struct ieee80211_vif *vif, bool reconnect) EXPORT_SYMBOL(ieee80211_disconnect); static void ieee80211_destroy_auth_data(struct ieee80211_sub_if_data *sdata, - bool assoc) + bool assoc, + struct ieee80211_prep_tx_info *info) { struct ieee80211_mgd_auth_data *auth_data = sdata->u.mgd.auth_data; @@ -5265,6 +5266,9 @@ static void ieee80211_destroy_auth_data(struct ieee80211_sub_if_data *sdata, sdata->u.mgd.auth_data = NULL; + if (info) + drv_mgd_complete_tx(sdata->local, sdata, info); + if (!assoc) { /* * we are not authenticated yet, the only timer that could be @@ -5296,7 +5300,8 @@ enum assoc_status { }; static void ieee80211_destroy_assoc_data(struct ieee80211_sub_if_data *sdata, - enum assoc_status status) + enum assoc_status status, + struct ieee80211_prep_tx_info *info) { struct ieee80211_mgd_assoc_data *assoc_data = sdata->u.mgd.assoc_data; @@ -5304,6 +5309,9 @@ static void ieee80211_destroy_assoc_data(struct ieee80211_sub_if_data *sdata, sdata->u.mgd.assoc_data = NULL; + if (info) + drv_mgd_complete_tx(sdata->local, sdata, info); + if (status != ASSOC_SUCCESS) { /* * we are not associated yet, the only timer that could be @@ -5495,11 +5503,11 @@ static void ieee80211_rx_mgmt_auth(struct ieee80211_sub_if_data *sdata, sdata_info(sdata, "%pM denied authentication (status %d)\n", mgmt->sa, status_code); - ieee80211_destroy_auth_data(sdata, false); + ieee80211_destroy_auth_data(sdata, false, &info); event.u.mlme.status = MLME_DENIED; event.u.mlme.reason = status_code; drv_event_callback(sdata->local, sdata, &event); - goto notify_driver; + return; } switch (ifmgd->auth_data->algorithm) { @@ -5671,7 +5679,7 @@ static void ieee80211_rx_mgmt_deauth(struct ieee80211_sub_if_data *sdata, ifmgd->assoc_data->ap_addr, reason_code, ieee80211_get_reason_code_string(reason_code)); - ieee80211_destroy_assoc_data(sdata, ASSOC_ABANDON); + ieee80211_destroy_assoc_data(sdata, ASSOC_ABANDON, NULL); cfg80211_rx_mlme_mgmt(sdata->dev, (u8 *)mgmt, len); return; @@ -7136,6 +7144,7 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, { struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; struct ieee80211_mgd_assoc_data *assoc_data = ifmgd->assoc_data; + enum assoc_status assoc_status = ASSOC_ABANDON; u16 capab_info, status_code, aid; struct ieee80211_elems_parse_params parse_params = { .bss = NULL, @@ -7143,7 +7152,6 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, .from_ap = true, .type = le16_to_cpu(mgmt->frame_control) & IEEE80211_FCTL_TYPE, }; - struct ieee802_11_elems *elems; struct sta_info *sta; int ac; const u8 *elem_start; @@ -7209,7 +7217,8 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, elem_len = len - (elem_start - (u8 *)mgmt); parse_params.start = elem_start; parse_params.len = elem_len; - elems = ieee802_11_parse_elems_full(&parse_params); + struct ieee802_11_elems *elems __free(kfree) = + ieee802_11_parse_elems_full(&parse_params); if (!elems) goto notify_driver; @@ -7275,7 +7284,7 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, sdata_info(sdata, "MLO association with %pM but no (basic) multi-link element in response!\n", assoc_data->ap_addr); - goto abandon_assoc; + goto destroy_assoc_data; } common = (void *)elems->ml_basic->variable; @@ -7286,7 +7295,7 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, "AP MLD MAC address mismatch: got %pM expected %pM\n", common->mld_mac_addr, assoc_data->ap_addr); - goto abandon_assoc; + goto destroy_assoc_data; } sdata->vif.cfg.eml_cap = @@ -7303,8 +7312,8 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, if (!ieee80211_assoc_success(sdata, mgmt, elems, elem_start, elem_len)) { /* oops -- internal error -- send timeout for now */ - ieee80211_destroy_assoc_data(sdata, ASSOC_TIMEOUT); - goto notify_driver; + assoc_status = ASSOC_TIMEOUT; + goto destroy_assoc_data; } event.u.mlme.status = MLME_SUCCESS; drv_event_callback(sdata->local, sdata, &event); @@ -7348,23 +7357,19 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, sta = sta_info_get_bss(sdata, sdata->vif.cfg.ap_addr); resp.assoc_encrypted = sta && sta->sta.epp_peer; - ieee80211_destroy_assoc_data(sdata, - status_code == WLAN_STATUS_SUCCESS ? - ASSOC_SUCCESS : - ASSOC_REJECTED); - resp.buf = (u8 *)mgmt; resp.len = len; resp.req_ies = ifmgd->assoc_req_ies; resp.req_ies_len = ifmgd->assoc_req_ies_len; cfg80211_rx_assoc_resp(sdata->dev, &resp); + assoc_status = status_code == WLAN_STATUS_SUCCESS ? ASSOC_SUCCESS : + ASSOC_REJECTED; +destroy_assoc_data: + ieee80211_destroy_assoc_data(sdata, assoc_status, &info); + return; + notify_driver: drv_mgd_complete_tx(sdata->local, sdata, &info); - kfree(elems); - return; -abandon_assoc: - ieee80211_destroy_assoc_data(sdata, ASSOC_ABANDON); - goto notify_driver; } static void ieee80211_rx_bss_info(struct ieee80211_link_data *link, @@ -9097,7 +9102,7 @@ void ieee80211_sta_work(struct ieee80211_sub_if_data *sdata) * ok ... we waited for assoc or continuation but * userspace didn't do it, so kill the auth data */ - ieee80211_destroy_auth_data(sdata, false); + ieee80211_destroy_auth_data(sdata, false, NULL); } else if (ieee80211_auth(sdata)) { u8 ap_addr[ETH_ALEN]; struct ieee80211_event event = { @@ -9108,7 +9113,7 @@ void ieee80211_sta_work(struct ieee80211_sub_if_data *sdata) memcpy(ap_addr, ifmgd->auth_data->ap_addr, ETH_ALEN); - ieee80211_destroy_auth_data(sdata, false); + ieee80211_destroy_auth_data(sdata, false, NULL); cfg80211_auth_timeout(sdata->dev, ap_addr); drv_event_callback(sdata->local, sdata, &event); @@ -9127,7 +9132,8 @@ void ieee80211_sta_work(struct ieee80211_sub_if_data *sdata) .u.mlme.status = MLME_TIMEOUT, }; - ieee80211_destroy_assoc_data(sdata, ASSOC_TIMEOUT); + ieee80211_destroy_assoc_data(sdata, ASSOC_TIMEOUT, + NULL); drv_event_callback(sdata->local, sdata, &event); } } else if (ifmgd->assoc_data && ifmgd->assoc_data->timeout_started) @@ -9340,9 +9346,10 @@ void ieee80211_mgd_quiesce(struct ieee80211_sub_if_data *sdata) WLAN_REASON_DEAUTH_LEAVING, false, frame_buf); if (ifmgd->assoc_data) - ieee80211_destroy_assoc_data(sdata, ASSOC_ABANDON); + ieee80211_destroy_assoc_data(sdata, ASSOC_ABANDON, + NULL); if (ifmgd->auth_data) - ieee80211_destroy_auth_data(sdata, false); + ieee80211_destroy_auth_data(sdata, false, NULL); cfg80211_tx_mlme_mgmt(sdata->dev, frame_buf, IEEE80211_DEAUTH_FRAME_LEN, false); @@ -9959,7 +9966,7 @@ int ieee80211_mgd_auth(struct ieee80211_sub_if_data *sdata, auth_data->peer_confirmed = ifmgd->auth_data->peer_confirmed; } - ieee80211_destroy_auth_data(sdata, cont_auth); + ieee80211_destroy_auth_data(sdata, cont_auth, NULL); } /* prep auth_data so we don't go into idle on disassoc */ @@ -10493,7 +10500,7 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata, /* Cleanup is delayed if auth_data matches */ if (ifmgd->auth_data && !match_auth) - ieee80211_destroy_auth_data(sdata, false); + ieee80211_destroy_auth_data(sdata, false, NULL); if (req->ie && req->ie_len) { memcpy(assoc_data->ie, req->ie, req->ie_len); @@ -10643,7 +10650,7 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata, /* We are associating, clean up auth_data */ if (ifmgd->auth_data) - ieee80211_destroy_auth_data(sdata, true); + ieee80211_destroy_auth_data(sdata, true, NULL); return 0; err_clear: @@ -10681,11 +10688,10 @@ int ieee80211_mgd_deauth(struct ieee80211_sub_if_data *sdata, IEEE80211_STYPE_DEAUTH, req->reason_code, tx, frame_buf); - ieee80211_destroy_auth_data(sdata, false); + ieee80211_destroy_auth_data(sdata, false, &info); ieee80211_report_disconnect(sdata, frame_buf, sizeof(frame_buf), true, req->reason_code, false); - drv_mgd_complete_tx(sdata->local, sdata, &info); return 0; } @@ -10702,11 +10708,10 @@ int ieee80211_mgd_deauth(struct ieee80211_sub_if_data *sdata, IEEE80211_STYPE_DEAUTH, req->reason_code, tx, frame_buf); - ieee80211_destroy_assoc_data(sdata, ASSOC_ABANDON); + ieee80211_destroy_assoc_data(sdata, ASSOC_ABANDON, &info); ieee80211_report_disconnect(sdata, frame_buf, sizeof(frame_buf), true, req->reason_code, false); - drv_mgd_complete_tx(sdata->local, sdata, &info); return 0; } @@ -10783,9 +10788,9 @@ void ieee80211_mgd_stop(struct ieee80211_sub_if_data *sdata) &ifmgd->uhr_omp.status_work); if (ifmgd->assoc_data) - ieee80211_destroy_assoc_data(sdata, ASSOC_TIMEOUT); + ieee80211_destroy_assoc_data(sdata, ASSOC_TIMEOUT, NULL); if (ifmgd->auth_data) - ieee80211_destroy_auth_data(sdata, false); + ieee80211_destroy_auth_data(sdata, false, NULL); spin_lock_bh(&ifmgd->teardown_lock); if (ifmgd->teardown_skb) { kfree_skb(ifmgd->teardown_skb); From 5a69b8dffd2a1b06e4cc4c9003c639eb78bac4cd Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Tue, 21 Jul 2026 15:39:58 +1000 Subject: [PATCH 0459/1433] wifi: cfg80211: include cf1 offset when sending chandef The cf1 offset is not included when sending the chandef leading to incorrect channel resolution in usermode. Include it. Signed-off-by: Lachlan Hodges Link: https://patch.msgid.link/20260721053958.227853-1-lachlan.hodges@morsemicro.com Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 4044b5505104..d962b5944533 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -4627,6 +4627,10 @@ int nl80211_send_chandef(struct sk_buff *msg, const struct cfg80211_chan_def *ch return -ENOBUFS; if (nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ1, chandef->center_freq1)) return -ENOBUFS; + if (chandef->freq1_offset && + nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ1_OFFSET, + chandef->freq1_offset)) + return -ENOBUFS; if (chandef->center_freq2 && nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ2, chandef->center_freq2)) return -ENOBUFS; From 40918ce98b0bfb3c1860fb60b5a041e6cdfe3e05 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 14 Jul 2026 13:43:16 +0300 Subject: [PATCH 0460/1433] wifi: mac80211: refactor multi-link assoc response parsing Refactor the parsing code a bit, introducing specific error messages for the various failures and moving the BSS parameter change count parsing out a level to be easier to extend for UHR. Signed-off-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260714134154.e2fb22738aa1.Id86c1bd6ddb5ec13ad9cbf24fd36b8246f351fe6@changeid Signed-off-by: Johannes Berg --- net/mac80211/mlme.c | 53 ++++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 6b5332198ffa..3e421efe870c 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -5877,8 +5877,6 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link, const struct cfg80211_bss_ies *bss_ies = NULL; struct ieee80211_supported_band *sband; struct ieee802_11_elems *elems; - const __le16 prof_bss_param_ch_present = - cpu_to_le16(IEEE80211_MLE_STA_CONTROL_BSS_PARAM_CHANGE_CNT_PRESENT); u16 capab_info; bool ret; @@ -5894,20 +5892,13 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link, * successful, so set the status directly to success */ assoc_data->link[link_id].status = WLAN_STATUS_SUCCESS; - if (elems->ml_basic) { - int bss_param_ch_cnt = - ieee80211_mle_get_bss_param_ch_cnt((const void *)elems->ml_basic); - - if (bss_param_ch_cnt < 0) { - ret = false; - goto out; - } - bss_conf->bss_param_ch_cnt = bss_param_ch_cnt; - bss_conf->bss_param_ch_cnt_link_id = link_id; - } - } else if (elems->parse_error & IEEE80211_PARSE_ERR_DUP_NEST_ML_BASIC || - !elems->prof || - !(elems->prof->control & prof_bss_param_ch_present)) { + } else if (elems->parse_error & IEEE80211_PARSE_ERR_DUP_NEST_ML_BASIC) { + sdata_info(sdata, + "association response had nested multi-link element\n"); + ret = false; + goto out; + } else if (!elems->prof) { + link_info(link, "link missing from association response\n"); ret = false; goto out; } else { @@ -5921,10 +5912,6 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link, */ capab_info = get_unaligned_le16(ptr); assoc_data->link[link_id].status = get_unaligned_le16(ptr + 2); - bss_param_ch_cnt = - ieee80211_mle_basic_sta_prof_bss_param_ch_cnt(elems->prof); - bss_conf->bss_param_ch_cnt = bss_param_ch_cnt; - bss_conf->bss_param_ch_cnt_link_id = link_id; if (assoc_data->link[link_id].status != WLAN_STATUS_SUCCESS) { link_info(link, "association response status code=%u\n", @@ -5932,6 +5919,32 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link, ret = true; goto out; } + + if (!(elems->prof->control & + cpu_to_le16(IEEE80211_MLE_STA_CONTROL_BSS_PARAM_CHANGE_CNT_PRESENT))) { + link_info(link, + "per-STA profile missing BSS parameter change count\n"); + ret = false; + goto out; + } + bss_param_ch_cnt = + ieee80211_mle_basic_sta_prof_bss_param_ch_cnt(elems->prof); + bss_conf->bss_param_ch_cnt = bss_param_ch_cnt; + bss_conf->bss_param_ch_cnt_link_id = link_id; + } + + if (link_id == assoc_data->assoc_link_id && elems->ml_basic) { + int bss_param_ch_cnt = + ieee80211_mle_get_bss_param_ch_cnt((const void *)elems->ml_basic); + + if (bss_param_ch_cnt < 0) { + sdata_info(sdata, + "No BSS parameter change count in assoc response\n"); + ret = false; + goto out; + } + bss_conf->bss_param_ch_cnt = bss_param_ch_cnt; + bss_conf->bss_param_ch_cnt_link_id = link_id; } if (!is_s1g && !elems->supp_rates) { From c4b825b50c177e80595db6507f3b324bc8918e0a Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 14 Jul 2026 13:43:17 +0300 Subject: [PATCH 0461/1433] wifi: mac80211: parse enhanced critical updates field For association and link reconfiguration response, parse and store the enhanced BSS parameter change counter out of the enhanced critical updates field in the multi-link common info or per-STA profile. These are required for UHR connections. Signed-off-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260714134154.adb9fc29252d.I625580fbadbbf4a1440d88d3675586477f1a3263@changeid Signed-off-by: Johannes Berg --- include/linux/ieee80211-eht.h | 92 +++++++++++++++++++++++++++++++++++ include/net/mac80211.h | 6 +++ net/mac80211/mlme.c | 38 ++++++++++++++- 3 files changed, 134 insertions(+), 2 deletions(-) diff --git a/include/linux/ieee80211-eht.h b/include/linux/ieee80211-eht.h index 18f9c662cf4c..928811328bb0 100644 --- a/include/linux/ieee80211-eht.h +++ b/include/linux/ieee80211-eht.h @@ -481,6 +481,7 @@ struct ieee80211_multi_link_elem { #define IEEE80211_MLC_BASIC_PRES_MLD_CAPA_OP 0x0100 #define IEEE80211_MLC_BASIC_PRES_MLD_ID 0x0200 #define IEEE80211_MLC_BASIC_PRES_EXT_MLD_CAPA_OP 0x0400 +#define IEEE80211_MLC_BASIC_PRES_ENH_CRIT_UPD 0x0800 #define IEEE80211_MED_SYNC_DELAY_DURATION 0x00ff #define IEEE80211_MED_SYNC_DELAY_SYNC_OFDM_ED_THRESH 0x0f00 @@ -809,6 +810,49 @@ static inline u16 ieee80211_mle_get_ext_mld_capa_op(const u8 *data) return get_unaligned_le16(common); } +/** + * ieee80211_mle_get_enh_crit_upd_info - returns the enhanced critical + * updates information + * @data: pointer to the multi-link element + * Return: the enhanced critical updates information field, or %NULL + * + * The element is assumed to be of the correct type (BASIC) and big enough, + * this must be checked using ieee80211_mle_type_ok(). + */ +static inline const struct ieee80211_enh_crit_upd * +ieee80211_mle_get_enh_crit_upd_info(const u8 *data) +{ + const struct ieee80211_multi_link_elem *mle = (const void *)data; + u16 control = le16_to_cpu(mle->control); + const u8 *common = mle->variable; + + /* + * common points now at the beginning of + * ieee80211_mle_basic_common_info + */ + common += sizeof(struct ieee80211_mle_basic_common_info); + + if (!(control & IEEE80211_MLC_BASIC_PRES_ENH_CRIT_UPD)) + return NULL; + + if (control & IEEE80211_MLC_BASIC_PRES_LINK_ID) + common += 1; + if (control & IEEE80211_MLC_BASIC_PRES_BSS_PARAM_CH_CNT) + common += 1; + if (control & IEEE80211_MLC_BASIC_PRES_MED_SYNC_DELAY) + common += 2; + if (control & IEEE80211_MLC_BASIC_PRES_EML_CAPA) + common += 2; + if (control & IEEE80211_MLC_BASIC_PRES_MLD_CAPA_OP) + common += 2; + if (control & IEEE80211_MLC_BASIC_PRES_MLD_ID) + common += 1; + if (control & IEEE80211_MLC_BASIC_PRES_EXT_MLD_CAPA_OP) + common += 2; + + return (const void *)common; +} + /** * ieee80211_mle_get_mld_id - returns the MLD ID * @data: pointer to the multi-link element @@ -883,6 +927,8 @@ static inline bool ieee80211_mle_size_ok(const u8 *data, size_t len) common += 1; if (control & IEEE80211_MLC_BASIC_PRES_EXT_MLD_CAPA_OP) common += 2; + if (control & IEEE80211_MLC_BASIC_PRES_ENH_CRIT_UPD) + common += 1; break; case IEEE80211_ML_CONTROL_TYPE_PREQ: common += sizeof(struct ieee80211_mle_preq_common_info); @@ -959,6 +1005,8 @@ enum ieee80211_mle_subelems { #define IEEE80211_MLE_STA_CONTROL_NSTR_LINK_PAIR_PRESENT 0x0200 #define IEEE80211_MLE_STA_CONTROL_NSTR_BITMAP_SIZE 0x0400 #define IEEE80211_MLE_STA_CONTROL_BSS_PARAM_CHANGE_CNT_PRESENT 0x0800 +#define IEEE80211_MLE_STA_CONTROL_ENH_CRIT_UPD_PRESENT 0x1000 +#define IEEE80211_MLE_STA_CONTROL_AP_CONDUCTED_TX_PWR_PRESENT 0x2000 struct ieee80211_mle_per_sta_profile { __le16 control; @@ -1004,6 +1052,12 @@ static inline bool ieee80211_mle_basic_sta_prof_size_ok(const u8 *data, if (control & IEEE80211_MLE_STA_CONTROL_BSS_PARAM_CHANGE_CNT_PRESENT) info_len += 1; + if (control & IEEE80211_MLE_STA_CONTROL_ENH_CRIT_UPD_PRESENT) + info_len += 1; + + if (control & IEEE80211_MLE_STA_CONTROL_AP_CONDUCTED_TX_PWR_PRESENT) + info_len += 1; + return prof->sta_info_len >= info_len && fixed + prof->sta_info_len - 1 <= len; } @@ -1044,6 +1098,44 @@ ieee80211_mle_basic_sta_prof_bss_param_ch_cnt(const struct ieee80211_mle_per_sta return *pos; } +/** + * ieee80211_mle_basic_sta_prof_enh_crit_upd - get per-STA profile enhanced + * critical updates field + * @prof: the per-STA profile, having been checked with + * ieee80211_mle_basic_sta_prof_size_ok() for the correct length + * + * Return: The enhanced critical updates field if present, %NULL otherwise. + */ +static inline const struct ieee80211_enh_crit_upd * +ieee80211_mle_basic_sta_prof_enh_crit_upd(const struct ieee80211_mle_per_sta_profile *prof) +{ + u16 control = le16_to_cpu(prof->control); + const u8 *pos = prof->variable; + + if (!(control & IEEE80211_MLE_STA_CONTROL_ENH_CRIT_UPD_PRESENT)) + return NULL; + + if (control & IEEE80211_MLE_STA_CONTROL_STA_MAC_ADDR_PRESENT) + pos += 6; + if (control & IEEE80211_MLE_STA_CONTROL_BEACON_INT_PRESENT) + pos += 2; + if (control & IEEE80211_MLE_STA_CONTROL_TSF_OFFS_PRESENT) + pos += 8; + if (control & IEEE80211_MLE_STA_CONTROL_DTIM_INFO_PRESENT) + pos += 2; + if (control & IEEE80211_MLE_STA_CONTROL_COMPLETE_PROFILE && + control & IEEE80211_MLE_STA_CONTROL_NSTR_LINK_PAIR_PRESENT) { + if (control & IEEE80211_MLE_STA_CONTROL_NSTR_BITMAP_SIZE) + pos += 2; + else + pos += 1; + } + if (control & IEEE80211_MLE_STA_CONTROL_BSS_PARAM_CHANGE_CNT_PRESENT) + pos += 1; + + return (const void *)pos; +} + #define IEEE80211_MLE_STA_RECONF_CONTROL_LINK_ID 0x000f #define IEEE80211_MLE_STA_RECONF_CONTROL_COMPLETE_PROFILE 0x0010 #define IEEE80211_MLE_STA_RECONF_CONTROL_STA_MAC_ADDR_PRESENT 0x0020 diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 4f95da023746..948a7cdab27c 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -790,6 +790,10 @@ struct ieee80211_bss_npca_params { * be updated to 1, even if bss_param_ch_cnt didn't change. This allows * the link to know that it heard the latest value from its own beacon * (as opposed to hearing its value from another link's beacon). + * @enh_bss_param_ch_cnt: In BSS-mode, the enhanced BSS parameters change + * counter. See @bss_param_ch_cnt, it works the same way. + * @enh_bss_param_ch_cnt_link_id: In BSS-mode, the link_id for the enhanced + * BSS parameter change counter, see @bss_param_ch_cnt_link_id. * @s1g_long_beacon_period: number of beacon intervals between each long * beacon transmission. * @npca: NPCA parameters @@ -894,6 +898,8 @@ struct ieee80211_bss_conf { u8 bss_param_ch_cnt; u8 bss_param_ch_cnt_link_id; + u8 enh_bss_param_ch_cnt; + u8 enh_bss_param_ch_cnt_link_id; u8 s1g_long_beacon_period; diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 3e421efe870c..d577252dbb9f 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -5931,11 +5931,28 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link, ieee80211_mle_basic_sta_prof_bss_param_ch_cnt(elems->prof); bss_conf->bss_param_ch_cnt = bss_param_ch_cnt; bss_conf->bss_param_ch_cnt_link_id = link_id; + + if (link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_UHR) { + const struct ieee80211_enh_crit_upd *enh_crit_upd; + + enh_crit_upd = ieee80211_mle_basic_sta_prof_enh_crit_upd(elems->prof); + if (!enh_crit_upd) { + link_info(link, + "per-STA profile missing enhanced critical updates\n"); + ret = false; + goto out; + } + + bss_conf->enh_bss_param_ch_cnt = + u8_get_bits(enh_crit_upd->v, + IEEE80211_ENH_CRIT_UPD_EBPCC); + bss_conf->enh_bss_param_ch_cnt_link_id = link_id; + } } if (link_id == assoc_data->assoc_link_id && elems->ml_basic) { - int bss_param_ch_cnt = - ieee80211_mle_get_bss_param_ch_cnt((const void *)elems->ml_basic); + const void *mle = (const void *)elems->ml_basic; + int bss_param_ch_cnt = ieee80211_mle_get_bss_param_ch_cnt(mle); if (bss_param_ch_cnt < 0) { sdata_info(sdata, @@ -5945,6 +5962,23 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link, } bss_conf->bss_param_ch_cnt = bss_param_ch_cnt; bss_conf->bss_param_ch_cnt_link_id = link_id; + + if (link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_UHR) { + const struct ieee80211_enh_crit_upd *enh_crit_upd; + + enh_crit_upd = ieee80211_mle_get_enh_crit_upd_info(mle); + if (!enh_crit_upd) { + link_info(link, + "No enhanced critical updates in assoc response\n"); + ret = false; + goto out; + } + + bss_conf->enh_bss_param_ch_cnt = + u8_get_bits(enh_crit_upd->v, + IEEE80211_ENH_CRIT_UPD_EBPCC); + bss_conf->enh_bss_param_ch_cnt_link_id = link_id; + } } if (!is_s1g && !elems->supp_rates) { From 69bd85e933c617a84fa54fcc80e628e98afdc45d Mon Sep 17 00:00:00 2001 From: Jason Huang Date: Wed, 15 Jul 2026 16:47:20 +0800 Subject: [PATCH 0462/1433] wifi: brcmfmac: add DPP support Add DPP AKM handling and RSN parsing support. Map DPP to the firmware wpa_auth value and recognize DPP public action frames in the P2P action-frame TX path. Gate sup_wpa programming on firmware supplicant capability. Disable it only when the selected connection mode does not use firmware supplicant. This keeps pure SAE and 802.1X firmware-supplicant paths intact while avoiding stale firmware supplicant state for DPP. Signed-off-by: Kurt Lee Signed-off-by: Jason Huang Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260715084718.667522-1-Jason.Huang2@infineon.com Signed-off-by: Johannes Berg --- .../broadcom/brcm80211/brcmfmac/cfg80211.c | 149 ++++++++++-------- .../broadcom/brcm80211/brcmfmac/p2p.c | 54 +++++-- .../broadcom/brcm80211/include/brcmu_wifi.h | 2 + 3 files changed, 129 insertions(+), 76 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 0b55d445895f..20f7fccd6121 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -2174,6 +2175,9 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) val = WPA2_AUTH_PSK | WPA2_AUTH_FT; profile->is_ft = true; break; + case WLAN_AKM_SUITE_WFA_DPP: + val = WFA_AUTH_DPP; + break; default: bphy_err(drvr, "invalid akm suite (%d)\n", sme->crypto.akm_suites[0]); @@ -2483,43 +2487,50 @@ brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev, goto done; } - if (sme->crypto.psk && - profile->use_fwsup != BRCMF_PROFILE_FWSUP_SAE) { - if (WARN_ON(profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE)) { - err = -EINVAL; - goto done; - } - brcmf_dbg(INFO, "using PSK offload\n"); - profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK; - } + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FWSUP)) { + u32 akm = sme->crypto.n_akm_suites ? sme->crypto.akm_suites[0] : 0; + bool is_sae_akm = akm == WLAN_AKM_SUITE_SAE || + akm == WLAN_AKM_SUITE_FT_OVER_SAE; - if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE) { - /* enable firmware supplicant for this interface */ - err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 1); - if (err < 0) { - bphy_err(drvr, "failed to enable fw supplicant\n"); - goto done; + if (sme->crypto.psk && !is_sae_akm && + profile->use_fwsup != BRCMF_PROFILE_FWSUP_SAE) { + if (WARN_ON(profile->use_fwsup != + BRCMF_PROFILE_FWSUP_NONE)) { + err = -EINVAL; + goto done; + } + brcmf_dbg(INFO, "using PSK offload\n"); + profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK; } - } - - if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_PSK) - err = brcmf_set_pmk(ifp, sme->crypto.psk, - BRCMF_WSEC_MAX_PSK_LEN); - else if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_SAE) { - /* clean up user-space RSNE */ - err = brcmf_fil_iovar_data_set(ifp, "wpaie", NULL, 0); - if (err) { - bphy_err(drvr, "failed to clean up user-space RSNE\n"); - goto done; + if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE) { + /* enable firmware supplicant for this interface */ + err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 1); + if (err < 0) { + bphy_err(drvr, "failed to enable fw supplicant\n"); + goto done; + } + } else { + err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 0); } - err = brcmf_fwvid_set_sae_password(ifp, &sme->crypto); - if (!err && sme->crypto.psk) + if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_PSK) err = brcmf_set_pmk(ifp, sme->crypto.psk, BRCMF_WSEC_MAX_PSK_LEN); + else if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_SAE && + sme->crypto.sae_pwd && + brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SAE)) { + /* clean up user-space RSNE */ + if (brcmf_fil_iovar_data_set(ifp, "wpaie", NULL, 0)) { + bphy_err(drvr, "failed to clean up user-space RSNE\n"); + goto done; + } + err = brcmf_fwvid_set_sae_password(ifp, &sme->crypto); + if (!err && sme->crypto.psk) + err = brcmf_set_pmk(ifp, sme->crypto.psk, + BRCMF_WSEC_MAX_PSK_LEN); + } + if (err) + goto done; } - if (err) - goto done; - /* Join with specific BSSID and cached SSID * If SSID is zero join based on BSSID only */ @@ -4538,6 +4549,11 @@ static bool brcmf_valid_wpa_oui(u8 *oui, bool is_rsn_ie) return (memcmp(oui, WPA_OUI, TLV_OUI_LEN) == 0); } +static bool brcmf_valid_dpp_suite(u8 *oui) +{ + return get_unaligned_be32(oui) == WLAN_AKM_SUITE_WFA_DPP; +} + static s32 brcmf_configure_wpaie(struct brcmf_if *ifp, const struct brcmf_vs_tlv *wpa_ie, @@ -4651,42 +4667,47 @@ brcmf_configure_wpaie(struct brcmf_if *ifp, goto exit; } for (i = 0; i < count; i++) { - if (!brcmf_valid_wpa_oui(&data[offset], is_rsn_ie)) { + if (brcmf_valid_dpp_suite(&data[offset])) { + wpa_auth |= WFA_AUTH_DPP; + offset += TLV_OUI_LEN; + } else if (brcmf_valid_wpa_oui(&data[offset], is_rsn_ie)) { + offset += TLV_OUI_LEN; + switch (data[offset]) { + case RSN_AKM_NONE: + brcmf_dbg(TRACE, "RSN_AKM_NONE\n"); + wpa_auth |= WPA_AUTH_NONE; + break; + case RSN_AKM_UNSPECIFIED: + brcmf_dbg(TRACE, "RSN_AKM_UNSPECIFIED\n"); + is_rsn_ie ? + (wpa_auth |= WPA2_AUTH_UNSPECIFIED) : + (wpa_auth |= WPA_AUTH_UNSPECIFIED); + break; + case RSN_AKM_PSK: + brcmf_dbg(TRACE, "RSN_AKM_PSK\n"); + is_rsn_ie ? (wpa_auth |= WPA2_AUTH_PSK) : + (wpa_auth |= WPA_AUTH_PSK); + break; + case RSN_AKM_SHA256_PSK: + brcmf_dbg(TRACE, "RSN_AKM_MFP_PSK\n"); + wpa_auth |= WPA2_AUTH_PSK_SHA256; + break; + case RSN_AKM_SHA256_1X: + brcmf_dbg(TRACE, "RSN_AKM_MFP_1X\n"); + wpa_auth |= WPA2_AUTH_1X_SHA256; + break; + case RSN_AKM_SAE: + brcmf_dbg(TRACE, "RSN_AKM_SAE\n"); + wpa_auth |= WPA3_AUTH_SAE_PSK; + break; + default: + bphy_err(drvr, "Invalid key mgmt info\n"); + } + } else { err = -EINVAL; bphy_err(drvr, "invalid OUI\n"); goto exit; } - offset += TLV_OUI_LEN; - switch (data[offset]) { - case RSN_AKM_NONE: - brcmf_dbg(TRACE, "RSN_AKM_NONE\n"); - wpa_auth |= WPA_AUTH_NONE; - break; - case RSN_AKM_UNSPECIFIED: - brcmf_dbg(TRACE, "RSN_AKM_UNSPECIFIED\n"); - is_rsn_ie ? (wpa_auth |= WPA2_AUTH_UNSPECIFIED) : - (wpa_auth |= WPA_AUTH_UNSPECIFIED); - break; - case RSN_AKM_PSK: - brcmf_dbg(TRACE, "RSN_AKM_PSK\n"); - is_rsn_ie ? (wpa_auth |= WPA2_AUTH_PSK) : - (wpa_auth |= WPA_AUTH_PSK); - break; - case RSN_AKM_SHA256_PSK: - brcmf_dbg(TRACE, "RSN_AKM_MFP_PSK\n"); - wpa_auth |= WPA2_AUTH_PSK_SHA256; - break; - case RSN_AKM_SHA256_1X: - brcmf_dbg(TRACE, "RSN_AKM_MFP_1X\n"); - wpa_auth |= WPA2_AUTH_1X_SHA256; - break; - case RSN_AKM_SAE: - brcmf_dbg(TRACE, "RSN_AKM_SAE\n"); - wpa_auth |= WPA3_AUTH_SAE_PSK; - break; - default: - bphy_err(drvr, "Invalid key mgmt info\n"); - } offset++; } @@ -4706,10 +4727,12 @@ brcmf_configure_wpaie(struct brcmf_if *ifp, */ if (!(wpa_auth & (WPA2_AUTH_PSK_SHA256 | WPA2_AUTH_1X_SHA256 | + WFA_AUTH_DPP | WPA3_AUTH_SAE_PSK))) { err = -EINVAL; goto exit; } + /* Firmware has requirement that WPA2_AUTH_PSK/ * WPA2_AUTH_UNSPECIFIED be set, if SHA256 OUI * is to be included in the rsn ie. diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c index 92c16a317328..c7d7b35ab125 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -44,9 +45,6 @@ #define BRCMF_SCB_TIMEOUT_VALUE 20 -#define P2P_VER 9 /* P2P version: 9=WiFi P2P v1.0 */ -#define P2P_PUB_AF_CATEGORY 0x04 -#define P2P_PUB_AF_ACTION 0x09 #define P2P_AF_CATEGORY 0x7f #define P2P_OUI "\x50\x6F\x9A" /* P2P OUI */ #define P2P_OUI_LEN 3 /* P2P OUI length */ @@ -143,10 +141,10 @@ struct brcmf_p2p_scan_le { /** * struct brcmf_p2p_pub_act_frame - WiFi P2P Public Action Frame * - * @category: P2P_PUB_AF_CATEGORY - * @action: P2P_PUB_AF_ACTION + * @category: WLAN_CATEGORY_PUBLIC + * @action: WLAN_PUB_ACTION_VENDOR_SPECIFIC * @oui: P2P_OUI - * @oui_type: OUI type - P2P_VER + * @oui_type: OUI type - WLAN_OUI_TYPE_WFA_P2P * @subtype: OUI subtype - P2P_TYPE_* * @dialog_token: nonzero, identifies req/rsp transaction * @elts: Variable length information elements. @@ -166,7 +164,7 @@ struct brcmf_p2p_pub_act_frame { * * @category: P2P_AF_CATEGORY * @oui: OUI - P2P_OUI - * @type: OUI Type - P2P_VER + * @type: OUI Type - WLAN_OUI_TYPE_WFA_P2P * @subtype: OUI Subtype - P2P_AF_* * @dialog_token: nonzero, identifies req/resp tranaction * @elts: Variable length information elements. @@ -228,10 +226,38 @@ static bool brcmf_p2p_is_pub_action(void *frame, u32 frame_len) if (frame_len < sizeof(*pact_frm)) return false; - if (pact_frm->category == P2P_PUB_AF_CATEGORY && - pact_frm->action == P2P_PUB_AF_ACTION && - pact_frm->oui_type == P2P_VER && - memcmp(pact_frm->oui, P2P_OUI, P2P_OUI_LEN) == 0) + if (pact_frm->category == WLAN_CATEGORY_PUBLIC && + pact_frm->action == WLAN_PUB_ACTION_VENDOR_SPECIFIC && + pact_frm->oui_type == WLAN_OUI_TYPE_WFA_P2P && + get_unaligned_be24(pact_frm->oui) == WLAN_OUI_WFA) + return true; + + return false; +} + +/** + * brcmf_p2p_is_dpp_pub_action() - true if dpp public type frame. + * + * @frame: action frame data. + * @frame_len: length of action frame data. + * + * Determine if action frame is dpp public action type + */ +static bool brcmf_p2p_is_dpp_pub_action(void *frame, u32 frame_len) +{ + struct brcmf_p2p_pub_act_frame *pact_frm; + + if (!frame) + return false; + + pact_frm = (struct brcmf_p2p_pub_act_frame *)frame; + if (frame_len < sizeof(struct brcmf_p2p_pub_act_frame) - 1) + return false; + + if (pact_frm->category == WLAN_CATEGORY_PUBLIC && + pact_frm->action == WLAN_PUB_ACTION_VENDOR_SPECIFIC && + pact_frm->oui_type == WLAN_OUI_TYPE_WFA_DPP && + get_unaligned_be24(pact_frm->oui) == WLAN_OUI_WFA) return true; return false; @@ -257,7 +283,7 @@ static bool brcmf_p2p_is_p2p_action(void *frame, u32 frame_len) return false; if (act_frm->category == P2P_AF_CATEGORY && - act_frm->type == P2P_VER && + act_frm->type == WLAN_OUI_TYPE_WFA_P2P && memcmp(act_frm->oui, P2P_OUI, P2P_OUI_LEN) == 0) return true; @@ -1782,7 +1808,9 @@ bool brcmf_p2p_send_action_frame(struct brcmf_if *ifp, goto exit; } } else if (brcmf_p2p_is_p2p_action(action_frame->data, - action_frame_len)) { + action_frame_len) || + brcmf_p2p_is_dpp_pub_action(action_frame->data, + action_frame_len)) { /* do not configure anything. it will be */ /* sent with a default configuration */ } else { diff --git a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h index 7552bdb91991..c465208c4331 100644 --- a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h +++ b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h @@ -233,6 +233,8 @@ static inline bool ac_bitmap_tst(u8 bitmap, int prec) #define WPA3_AUTH_SAE_PSK 0x40000 /* SAE with 4-way handshake */ +#define WFA_AUTH_DPP 0x200000 /* WFA DPP AUTH */ + #define DOT11_DEFAULT_RTS_LEN 2347 #define DOT11_DEFAULT_FRAG_LEN 2346 From 0ce45ae881fd9041b6ce242de33faa59d8c6cba5 Mon Sep 17 00:00:00 2001 From: Mengyuan Lou Date: Fri, 10 Jul 2026 09:59:24 +0800 Subject: [PATCH 0463/1433] net: libwx: add support for set_ringparam in wx_ethtool_ops_vf Add support for the set_ringparam in wx_ethtool_ops_vf, which is used to set ring sizes for ngbevf and txgbevf. Signed-off-by: Mengyuan Lou Link: https://patch.msgid.link/20260710015925.34769-2-mengyuanlou@net-swift.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/wangxun/libwx/wx_ethtool.c | 61 +++++++++++++++++++ drivers/net/ethernet/wangxun/libwx/wx_lib.c | 9 +-- drivers/net/ethernet/wangxun/libwx/wx_lib.h | 4 +- .../net/ethernet/wangxun/libwx/wx_vf_common.c | 4 +- .../net/ethernet/wangxun/libwx/wx_vf_common.h | 2 + 5 files changed, 72 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c index 5df971aca9e3..eae038df6875 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c @@ -9,6 +9,7 @@ #include "wx_ethtool.h" #include "wx_hw.h" #include "wx_lib.h" +#include "wx_vf_common.h" struct wx_stats { char stat_string[ETH_GSTRING_LEN]; @@ -775,6 +776,65 @@ static int wx_get_link_ksettings_vf(struct net_device *netdev, return 0; } +static int wx_set_ringparam_vf(struct net_device *netdev, + struct ethtool_ringparam *ring, + struct kernel_ethtool_ringparam *kernel_ring, + struct netlink_ext_ack *extack) +{ + struct wx *wx = netdev_priv(netdev); + u32 new_rx_count, new_tx_count; + struct wx_ring *temp_ring; + int i, err = 0; + + new_tx_count = clamp_t(u32, ring->tx_pending, WX_MIN_TXD, WX_MAX_TXD); + new_tx_count = ALIGN(new_tx_count, WX_REQ_TX_DESCRIPTOR_MULTIPLE); + + new_rx_count = clamp_t(u32, ring->rx_pending, WX_MIN_RXD, WX_MAX_RXD); + new_rx_count = ALIGN(new_rx_count, WX_REQ_RX_DESCRIPTOR_MULTIPLE); + + if (new_tx_count == wx->tx_ring_count && + new_rx_count == wx->rx_ring_count) + return 0; + + mutex_lock(&wx->reset_lock); + set_bit(WX_STATE_RESETTING, wx->state); + + if (!netif_running(wx->netdev)) { + for (i = 0; i < wx->num_tx_queues; i++) + wx->tx_ring[i]->count = new_tx_count; + for (i = 0; i < wx->num_rx_queues; i++) + wx->rx_ring[i]->count = new_rx_count; + wx->tx_ring_count = new_tx_count; + wx->rx_ring_count = new_rx_count; + + goto clear_reset; + } + + /* allocate temporary buffer to store rings in */ + i = max_t(int, wx->num_tx_queues, wx->num_rx_queues); + temp_ring = kvmalloc_objs(struct wx_ring, i); + if (!temp_ring) { + err = -ENOMEM; + goto clear_reset; + } + + wxvf_down(wx); + /* wx_set_ring() may partially apply changes before + * returning an error. The error indicates that not all + * requested ring parameters could be configured. + */ + err = wx_set_ring(wx, new_tx_count, new_rx_count, temp_ring); + if (err) + wx_err(wx, "failed to set ring parameters: %d", err); + wx_configure_vf(wx); + wxvf_up_complete(wx); + kvfree(temp_ring); +clear_reset: + clear_bit(WX_STATE_RESETTING, wx->state); + mutex_unlock(&wx->reset_lock); + return err; +} + static const struct ethtool_ops wx_ethtool_ops_vf = { .supported_coalesce_params = ETHTOOL_COALESCE_USECS | ETHTOOL_COALESCE_TX_MAX_FRAMES_IRQ | @@ -782,6 +842,7 @@ static const struct ethtool_ops wx_ethtool_ops_vf = { .get_drvinfo = wx_get_drvinfo, .get_link = ethtool_op_get_link, .get_ringparam = wx_get_ringparam, + .set_ringparam = wx_set_ringparam_vf, .get_msglevel = wx_get_msglevel, .get_coalesce = wx_get_coalesce, .get_ts_info = ethtool_op_get_ts_info, diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.c b/drivers/net/ethernet/wangxun/libwx/wx_lib.c index 814d88d2aee4..29e2d2164c15 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_lib.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.c @@ -3249,8 +3249,8 @@ netdev_features_t wx_features_check(struct sk_buff *skb, } EXPORT_SYMBOL(wx_features_check); -void wx_set_ring(struct wx *wx, u32 new_tx_count, - u32 new_rx_count, struct wx_ring *temp_ring) +int wx_set_ring(struct wx *wx, u32 new_tx_count, + u32 new_rx_count, struct wx_ring *temp_ring) { int i, err = 0; @@ -3272,7 +3272,7 @@ void wx_set_ring(struct wx *wx, u32 new_tx_count, i--; wx_free_tx_resources(&temp_ring[i]); } - return; + return err; } } @@ -3300,7 +3300,7 @@ void wx_set_ring(struct wx *wx, u32 new_tx_count, i--; wx_free_rx_resources(&temp_ring[i]); } - return; + return err; } } @@ -3312,6 +3312,7 @@ void wx_set_ring(struct wx *wx, u32 new_tx_count, wx->rx_ring_count = new_rx_count; } + return 0; } EXPORT_SYMBOL(wx_set_ring); diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.h b/drivers/net/ethernet/wangxun/libwx/wx_lib.h index aed6ea8cf0d6..bc671786978e 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_lib.h +++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.h @@ -36,8 +36,8 @@ netdev_features_t wx_fix_features(struct net_device *netdev, netdev_features_t wx_features_check(struct sk_buff *skb, struct net_device *netdev, netdev_features_t features); -void wx_set_ring(struct wx *wx, u32 new_tx_count, - u32 new_rx_count, struct wx_ring *temp_ring); +int wx_set_ring(struct wx *wx, u32 new_tx_count, + u32 new_rx_count, struct wx_ring *temp_ring); void wx_service_event_schedule(struct wx *wx); void wx_service_event_complete(struct wx *wx); void wx_service_timer(struct timer_list *t); diff --git a/drivers/net/ethernet/wangxun/libwx/wx_vf_common.c b/drivers/net/ethernet/wangxun/libwx/wx_vf_common.c index 0d2db8d38cd5..26de78e9a69e 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_vf_common.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_vf_common.c @@ -269,7 +269,7 @@ static void wxvf_irq_enable(struct wx *wx) wr32(wx, WX_VXIMC, wx->eims_enable_mask); } -static void wxvf_up_complete(struct wx *wx) +void wxvf_up_complete(struct wx *wx) { /* Always set the carrier off */ netif_carrier_off(wx->netdev); @@ -324,7 +324,7 @@ int wxvf_open(struct net_device *netdev) } EXPORT_SYMBOL(wxvf_open); -static void wxvf_down(struct wx *wx) +void wxvf_down(struct wx *wx) { struct net_device *netdev = wx->netdev; diff --git a/drivers/net/ethernet/wangxun/libwx/wx_vf_common.h b/drivers/net/ethernet/wangxun/libwx/wx_vf_common.h index cbbb1b178cb2..d45d5d8ac3ab 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_vf_common.h +++ b/drivers/net/ethernet/wangxun/libwx/wx_vf_common.h @@ -15,7 +15,9 @@ void wx_set_rx_mode_vf(struct net_device *netdev); void wx_configure_vf(struct wx *wx); int wx_set_mac_vf(struct net_device *netdev, void *p); void wxvf_watchdog_update_link(struct wx *wx); +void wxvf_up_complete(struct wx *wx); int wxvf_open(struct net_device *netdev); +void wxvf_down(struct wx *wx); int wxvf_close(struct net_device *netdev); void wxvf_init_service(struct wx *wx); From e424cd462638cf32435bfbe7cca4c3bc1088ab0c Mon Sep 17 00:00:00 2001 From: Mengyuan Lou Date: Fri, 10 Jul 2026 09:59:25 +0800 Subject: [PATCH 0464/1433] net: libwx: add support for set_coalesce in wx_ethtool_ops_vf Add support for set_coalesce in wx_ethtool_ops_vf, which is used to set interrupt coalescing parameters. Update wx_write_eitr_vf() to use the same interrupt moderation encoding as PF devices, since PF and VF share the same register layout. And remove the now-unused WX_VXITR_MASK definition. Signed-off-by: Mengyuan Lou Reviewed-by: Przemek Kitszel Link: https://patch.msgid.link/20260710015925.34769-3-mengyuanlou@net-swift.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/wangxun/libwx/wx_ethtool.c | 7 ++++++- drivers/net/ethernet/wangxun/libwx/wx_vf.h | 1 - drivers/net/ethernet/wangxun/libwx/wx_vf_lib.c | 13 ++++++++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c index eae038df6875..22037f015ded 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c @@ -10,6 +10,7 @@ #include "wx_hw.h" #include "wx_lib.h" #include "wx_vf_common.h" +#include "wx_vf_lib.h" struct wx_stats { char stat_string[ETH_GSTRING_LEN]; @@ -488,7 +489,10 @@ int wx_set_coalesce(struct net_device *netdev, else /* rx only or mixed */ q_vector->itr = rx_itr_param; - wx_write_eitr(q_vector); + if (wx->pdev->is_virtfn) + wx_write_eitr_vf(q_vector); + else + wx_write_eitr(q_vector); } wx_update_rsc(wx); @@ -845,6 +849,7 @@ static const struct ethtool_ops wx_ethtool_ops_vf = { .set_ringparam = wx_set_ringparam_vf, .get_msglevel = wx_get_msglevel, .get_coalesce = wx_get_coalesce, + .set_coalesce = wx_set_coalesce, .get_ts_info = ethtool_op_get_ts_info, .get_link_ksettings = wx_get_link_ksettings_vf, }; diff --git a/drivers/net/ethernet/wangxun/libwx/wx_vf.h b/drivers/net/ethernet/wangxun/libwx/wx_vf.h index eb6ca3fe4e97..b64a4de089f2 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_vf.h +++ b/drivers/net/ethernet/wangxun/libwx/wx_vf.h @@ -41,7 +41,6 @@ #define WX_VF_MAX_RX_QUEUES 4 #define WX_VXITR(i) (0x200 + (4 * (i))) /* i=[0,1] */ -#define WX_VXITR_MASK GENMASK(8, 0) #define WX_VXITR_CNT_WDIS BIT(31) #define WX_VXIVAR_MISC 0x260 #define WX_VXIVAR(i) (0x240 + (4 * (i))) /* i=[0,3] */ diff --git a/drivers/net/ethernet/wangxun/libwx/wx_vf_lib.c b/drivers/net/ethernet/wangxun/libwx/wx_vf_lib.c index aa8be036956c..7325b475ee10 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_vf_lib.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_vf_lib.c @@ -16,7 +16,18 @@ void wx_write_eitr_vf(struct wx_q_vector *q_vector) int v_idx = q_vector->v_idx; u32 itr_reg; - itr_reg = q_vector->itr & WX_VXITR_MASK; + switch (wx->mac.type) { + case wx_mac_sp: + itr_reg = q_vector->itr & WX_SP_MAX_EITR; + break; + case wx_mac_aml: + case wx_mac_aml40: + itr_reg = (q_vector->itr >> 3) & WX_AML_MAX_EITR; + break; + default: + itr_reg = q_vector->itr & WX_EM_MAX_EITR; + break; + } /* set the WDIS bit to not clear the timer bits and cause an * immediate assertion of the interrupt From df13c1df8147675470213ffff29dd5762fa321f5 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Tue, 14 Jul 2026 20:14:05 +0100 Subject: [PATCH 0465/1433] net: dsa: microchip: make read-only const array ts_reg static Don't populate the read-only const array ts_reg on the stack at run time, instead make it static Signed-off-by: Colin Ian King Link: https://patch.msgid.link/20260714191406.195375-1-colin.i.king@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/microchip/ksz_ptp.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz_ptp.c b/drivers/net/dsa/microchip/ksz_ptp.c index 8b98039320ad..5bdf829a6e38 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.c +++ b/drivers/net/dsa/microchip/ksz_ptp.c @@ -1101,8 +1101,10 @@ static void ksz_ptp_msg_irq_free(struct ksz_port *port, u8 n) static int ksz_ptp_msg_irq_setup(struct ksz_port *port, u8 n) { - u16 ts_reg[] = {REG_PTP_PORT_PDRESP_TS, REG_PTP_PORT_XDELAY_TS, - REG_PTP_PORT_SYNC_TS}; + static const u16 ts_reg[] = { + REG_PTP_PORT_PDRESP_TS, REG_PTP_PORT_XDELAY_TS, + REG_PTP_PORT_SYNC_TS + }; static const char * const name[] = {"pdresp-msg", "xdreq-msg", "sync-msg"}; const struct ksz_dev_ops *ops = port->ksz_dev->dev_ops; From 5329647dad1bdb512e36905d0ae6a817b64f7f52 Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Fri, 10 Jul 2026 16:42:34 +0300 Subject: [PATCH 0466/1433] net: Use helpers to get/set UDP len tree-wide Since BIG TCP for UDP tunnels will start using len=0 in the UDP header as an indicator of a GSO packet bigger than 65535 bytes, this commit introduces the following getter and setters to use tree-wide, in order to explicitly mark places where len=0 may be expected, and handle them properly: 1. udp_set_len() sets uh->len to its real value if it's not bigger than 65535, and to 0 otherwise: to be used in GSO context with aggregated packets. 2. udp_set_len_short() is to be used when the length is known to fit 16 bits. It WARNs when the caller tries to assign a bigger value if CONFIG_DEBUG_NET=y. 3. udp_get_len_short() returns len in host byte order: to be used on the RX side to deal with non-aggregated packets, or to access the raw value of the len field. 4. udp_get_len() decodes uh->len set by udp_set_len(). It checks whether the packet is GSO to guard from malformed packets. At the moment udp_set_len() is not used, a following commit will start using it after enabling len>65535 for GSO. Raw uh->len (in network byte order) is still accessed in a few places for checksum calculation purposes, and to decode len=0 in udpv6_rcv for jumbograms. udp_rcv and udpv6_rcv will be addressed by the commit that starts using udp_set_len() to set UDP len=0 for BIG TCP packets in UDP tunnels. Signed-off-by: Alice Mikityanska Reviewed-by: Willem de Bruijn Acked-by: Jason A. Donenfeld Link: https://patch.msgid.link/20260710134242.216538-2-alice.kernel@fastmail.im Reviewed-by: Nikolay Aleksandrov Signed-off-by: Paolo Abeni --- drivers/infiniband/core/lag.c | 2 +- drivers/infiniband/sw/rxe/rxe_net.c | 4 +- drivers/net/amt.c | 6 +-- drivers/net/ethernet/intel/i40e/i40e_txrx.c | 2 +- drivers/net/ethernet/intel/iavf/iavf_txrx.c | 2 +- drivers/net/ethernet/intel/ice/ice_txrx.c | 2 +- drivers/net/ethernet/intel/idpf/idpf_txrx.c | 2 +- .../marvell/octeontx2/nic/otx2_txrx.c | 2 +- .../net/ethernet/mellanox/mlx5/core/en_rx.c | 4 +- .../ethernet/mellanox/mlx5/core/en_selftest.c | 2 +- drivers/net/ethernet/sfc/falcon/selftest.c | 4 +- drivers/net/ethernet/sfc/selftest.c | 4 +- drivers/net/ethernet/sfc/siena/selftest.c | 4 +- drivers/net/ethernet/sfc/tc_encap_actions.c | 2 +- .../stmicro/stmmac/stmmac_selftests.c | 4 +- drivers/net/geneve.c | 2 +- drivers/net/netconsole.c | 2 +- drivers/net/netdevsim/dev.c | 2 +- drivers/net/netdevsim/psample.c | 2 +- drivers/net/netdevsim/psp.c | 8 ++-- drivers/net/wireguard/receive.c | 2 +- include/linux/udp.h | 27 ++++++++++++++ include/trace/events/icmp.h | 2 +- lib/tests/blackhole_dev_kunit.c | 2 +- net/6lowpan/nhc_udp.c | 10 ++--- net/core/pktgen.c | 4 +- net/core/selftests.c | 4 +- net/core/tso.c | 3 +- net/ipv4/esp4.c | 2 +- net/ipv4/fou_core.c | 2 +- net/ipv4/ipconfig.c | 6 +-- net/ipv4/netfilter/nf_nat_snmp_basic_main.c | 4 +- net/ipv4/route.c | 2 +- net/ipv4/udp.c | 3 +- net/ipv4/udp_offload.c | 37 +++++++++---------- net/ipv4/udp_tunnel_core.c | 2 +- net/ipv6/esp6.c | 5 ++- net/ipv6/fou6.c | 2 +- net/ipv6/ip6_udp_tunnel.c | 2 +- net/ipv6/udp.c | 3 +- net/ipv6/udp_offload.c | 2 +- net/l2tp/l2tp_core.c | 2 +- net/netfilter/ipvs/ip_vs_xmit.c | 2 +- net/netfilter/nf_conntrack_proto_udp.c | 15 +++++++- net/netfilter/nf_log_syslog.c | 2 +- net/netfilter/nf_nat_helper.c | 2 +- net/psp/psp_main.c | 2 +- net/sched/act_csum.c | 4 +- net/xfrm/xfrm_nat_keepalive.c | 2 +- 49 files changed, 131 insertions(+), 88 deletions(-) diff --git a/drivers/infiniband/core/lag.c b/drivers/infiniband/core/lag.c index 8fd80adfe833..00fe241737ff 100644 --- a/drivers/infiniband/core/lag.c +++ b/drivers/infiniband/core/lag.c @@ -36,7 +36,7 @@ static struct sk_buff *rdma_build_skb(struct net_device *netdev, uh->source = htons(rdma_flow_label_to_udp_sport(ah_attr->grh.flow_label)); uh->dest = htons(ROCE_V2_UDP_DPORT); - uh->len = htons(sizeof(struct udphdr)); + udp_set_len_short(uh, sizeof(struct udphdr)); if (is_ipv4) { skb_push(skb, sizeof(struct iphdr)); diff --git a/drivers/infiniband/sw/rxe/rxe_net.c b/drivers/infiniband/sw/rxe/rxe_net.c index 3741b2c4b0bb..53daaf4c1eb2 100644 --- a/drivers/infiniband/sw/rxe/rxe_net.c +++ b/drivers/infiniband/sw/rxe/rxe_net.c @@ -242,7 +242,7 @@ static int rxe_udp_encap_recv(struct sock *sk, struct sk_buff *skb) pkt->port_num = 1; pkt->hdr = (u8 *)(udph + 1); pkt->mask = RXE_GRH_MASK; - pkt->paylen = be16_to_cpu(udph->len) - sizeof(*udph); + pkt->paylen = udp_get_len_short(udph) - sizeof(*udph); /* remove udp header */ skb_pull(skb, sizeof(struct udphdr)); @@ -305,7 +305,7 @@ static void prepare_udp_hdr(struct sk_buff *skb, __be16 src_port, udph->dest = dst_port; udph->source = src_port; - udph->len = htons(skb->len); + udp_set_len_short(udph, skb->len); udph->check = 0; } diff --git a/drivers/net/amt.c b/drivers/net/amt.c index 02168862f676..f8169c5512a5 100644 --- a/drivers/net/amt.c +++ b/drivers/net/amt.c @@ -667,7 +667,7 @@ static void amt_send_discovery(struct amt_dev *amt) udph = udp_hdr(skb); udph->source = amt->gw_port; udph->dest = amt->relay_port; - udph->len = htons(sizeof(*udph) + sizeof(*amtd)); + udp_set_len_short(udph, sizeof(*udph) + sizeof(*amtd)); udph->check = 0; offset = skb_transport_offset(skb); skb->csum = skb_checksum(skb, offset, skb->len - offset, 0); @@ -760,7 +760,7 @@ static void amt_send_request(struct amt_dev *amt, bool v6) udph = udp_hdr(skb); udph->source = amt->gw_port; udph->dest = amt->relay_port; - udph->len = htons(sizeof(*amtrh) + sizeof(*udph)); + udp_set_len_short(udph, sizeof(*amtrh) + sizeof(*udph)); udph->check = 0; offset = skb_transport_offset(skb); skb->csum = skb_checksum(skb, offset, skb->len - offset, 0); @@ -2611,7 +2611,7 @@ static void amt_send_advertisement(struct amt_dev *amt, __be32 nonce, udph = udp_hdr(skb); udph->source = amt->relay_port; udph->dest = dport; - udph->len = htons(sizeof(*amta) + sizeof(*udph)); + udp_set_len_short(udph, sizeof(*amta) + sizeof(*udph)); udph->check = 0; offset = skb_transport_offset(skb); skb->csum = skb_checksum(skb, offset, skb->len - offset, 0); diff --git a/drivers/net/ethernet/intel/i40e/i40e_txrx.c b/drivers/net/ethernet/intel/i40e/i40e_txrx.c index 894f2d06d39d..ef5e657816f0 100644 --- a/drivers/net/ethernet/intel/i40e/i40e_txrx.c +++ b/drivers/net/ethernet/intel/i40e/i40e_txrx.c @@ -3129,7 +3129,7 @@ static int i40e_tso(struct i40e_tx_buffer *first, u8 *hdr_len, SKB_GSO_UDP_TUNNEL_CSUM)) { if (!(skb_shinfo(skb)->gso_type & SKB_GSO_PARTIAL) && (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_TUNNEL_CSUM)) { - l4.udp->len = 0; + udp_set_len_short(l4.udp, 0); /* determine offset of outer transport header */ l4_offset = l4.hdr - skb->data; diff --git a/drivers/net/ethernet/intel/iavf/iavf_txrx.c b/drivers/net/ethernet/intel/iavf/iavf_txrx.c index 363c42bf3dcf..c30abf17cf5d 100644 --- a/drivers/net/ethernet/intel/iavf/iavf_txrx.c +++ b/drivers/net/ethernet/intel/iavf/iavf_txrx.c @@ -1774,7 +1774,7 @@ static int iavf_tso(struct iavf_tx_buffer *first, u8 *hdr_len, SKB_GSO_UDP_TUNNEL_CSUM)) { if (!(skb_shinfo(skb)->gso_type & SKB_GSO_PARTIAL) && (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_TUNNEL_CSUM)) { - l4.udp->len = 0; + udp_set_len_short(l4.udp, 0); /* determine offset of outer transport header */ l4_offset = l4.hdr - skb->data; diff --git a/drivers/net/ethernet/intel/ice/ice_txrx.c b/drivers/net/ethernet/intel/ice/ice_txrx.c index 4ca1a0602307..fdea2758adf1 100644 --- a/drivers/net/ethernet/intel/ice/ice_txrx.c +++ b/drivers/net/ethernet/intel/ice/ice_txrx.c @@ -1893,7 +1893,7 @@ int ice_tso(struct ice_tx_buf *first, struct ice_tx_offload_params *off) SKB_GSO_UDP_TUNNEL_CSUM)) { if (!(skb_shinfo(skb)->gso_type & SKB_GSO_PARTIAL) && (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_TUNNEL_CSUM)) { - l4.udp->len = 0; + udp_set_len_short(l4.udp, 0); /* determine offset of outer transport header */ l4_start = (u8)(l4.hdr - skb->data); diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.c b/drivers/net/ethernet/intel/idpf/idpf_txrx.c index 7f9056404f64..566b08ca3a6c 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.c +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.c @@ -2871,7 +2871,7 @@ int idpf_tso(struct sk_buff *skb, struct idpf_tx_offload_params *off) (__force __wsum)htonl(paylen)); /* compute length of segmentation header */ off->tso_hdr_len = sizeof(struct udphdr) + l4_start; - l4.udp->len = htons(shinfo->gso_size + sizeof(struct udphdr)); + udp_set_len_short(l4.udp, shinfo->gso_size + sizeof(struct udphdr)); break; default: return -EINVAL; diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.c index 625bb5a05344..8d2d607bc92f 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.c @@ -750,7 +750,7 @@ static void otx2_sqe_add_ext(struct otx2_nic *pfvf, struct otx2_snd_queue *sq, ext->lso_format = pfvf->hw.lso_udpv6_idx; } - udph->len = htons(sizeof(struct udphdr)); + udp_set_len_short(udph, sizeof(struct udphdr)); } } else if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) { ext->tstmp = 1; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c index 6fbc0441c4b8..04af54b704d8 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c @@ -1081,7 +1081,7 @@ static void mlx5e_shampo_update_ipv4_udp_hdr(struct mlx5e_rq *rq, struct iphdr * struct udphdr *uh; uh = (struct udphdr *)(skb->data + udp_off); - uh->len = htons(skb->len - udp_off); + udp_set_len_short(uh, skb->len - udp_off); if (uh->check) uh->check = ~udp_v4_check(skb->len - udp_off, ipv4->saddr, @@ -1100,7 +1100,7 @@ static void mlx5e_shampo_update_ipv6_udp_hdr(struct mlx5e_rq *rq, struct ipv6hdr struct udphdr *uh; uh = (struct udphdr *)(skb->data + udp_off); - uh->len = htons(skb->len - udp_off); + udp_set_len_short(uh, skb->len - udp_off); if (uh->check) uh->check = ~udp_v6_check(skb->len - udp_off, &ipv6->saddr, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_selftest.c b/drivers/net/ethernet/mellanox/mlx5/core/en_selftest.c index accc26d1a872..1dcdb86690bb 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_selftest.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_selftest.c @@ -113,7 +113,7 @@ static struct sk_buff *mlx5e_test_get_udp_skb(struct mlx5e_priv *priv) /* Fill UDP header */ udph->source = htons(9); udph->dest = htons(9); /* Discard Protocol */ - udph->len = htons(sizeof(struct mlx5ehdr) + sizeof(struct udphdr)); + udp_set_len_short(udph, sizeof(struct mlx5ehdr) + sizeof(struct udphdr)); udph->check = 0; /* Fill IP header */ diff --git a/drivers/net/ethernet/sfc/falcon/selftest.c b/drivers/net/ethernet/sfc/falcon/selftest.c index db4dd7fb77f5..4d29e0baf2eb 100644 --- a/drivers/net/ethernet/sfc/falcon/selftest.c +++ b/drivers/net/ethernet/sfc/falcon/selftest.c @@ -401,8 +401,8 @@ static void ef4_iterate_state(struct ef4_nic *efx) /* Initialise udp header */ payload->udp.source = 0; - payload->udp.len = htons(sizeof(*payload) - - offsetof(struct ef4_loopback_payload, udp)); + udp_set_len_short(&payload->udp, sizeof(*payload) - + offsetof(struct ef4_loopback_payload, udp)); payload->udp.check = 0; /* checksum ignored */ /* Fill out payload */ diff --git a/drivers/net/ethernet/sfc/selftest.c b/drivers/net/ethernet/sfc/selftest.c index 8ec76329237a..dc716feb79cb 100644 --- a/drivers/net/ethernet/sfc/selftest.c +++ b/drivers/net/ethernet/sfc/selftest.c @@ -398,8 +398,8 @@ static void efx_iterate_state(struct efx_nic *efx) /* Initialise udp header */ payload->udp.source = 0; - payload->udp.len = htons(sizeof(*payload) - - offsetof(struct efx_loopback_payload, udp)); + udp_set_len_short(&payload->udp, sizeof(*payload) - + offsetof(struct efx_loopback_payload, udp)); payload->udp.check = 0; /* checksum ignored */ /* Fill out payload */ diff --git a/drivers/net/ethernet/sfc/siena/selftest.c b/drivers/net/ethernet/sfc/siena/selftest.c index 930643612df5..c74cf5131364 100644 --- a/drivers/net/ethernet/sfc/siena/selftest.c +++ b/drivers/net/ethernet/sfc/siena/selftest.c @@ -399,8 +399,8 @@ static void efx_iterate_state(struct efx_nic *efx) /* Initialise udp header */ payload->udp.source = 0; - payload->udp.len = htons(sizeof(*payload) - - offsetof(struct efx_loopback_payload, udp)); + udp_set_len_short(&payload->udp, sizeof(*payload) - + offsetof(struct efx_loopback_payload, udp)); payload->udp.check = 0; /* checksum ignored */ /* Fill out payload */ diff --git a/drivers/net/ethernet/sfc/tc_encap_actions.c b/drivers/net/ethernet/sfc/tc_encap_actions.c index db222abef53b..c2ad3a358d20 100644 --- a/drivers/net/ethernet/sfc/tc_encap_actions.c +++ b/drivers/net/ethernet/sfc/tc_encap_actions.c @@ -311,7 +311,7 @@ static void efx_gen_tun_header_udp(struct efx_tc_encap_action *encap, u8 len) encap->encap_hdr_len += sizeof(*udp); udp->dest = key->tp_dst; - udp->len = cpu_to_be16(sizeof(*udp) + len); + udp_set_len_short(udp, sizeof(*udp) + len); } static void efx_gen_tun_header_vxlan(struct efx_tc_encap_action *encap) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index a0c75886587c..29e824bd90ca 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -154,9 +154,9 @@ static struct sk_buff *stmmac_test_get_udp_skb(struct stmmac_priv *priv, } else { uhdr->source = htons(attr->sport); uhdr->dest = htons(attr->dport); - uhdr->len = htons(sizeof(*shdr) + sizeof(*uhdr) + attr->size); + udp_set_len_short(uhdr, sizeof(*shdr) + sizeof(*uhdr) + attr->size); if (attr->max_size) - uhdr->len = htons(attr->max_size - + udp_set_len_short(uhdr, attr->max_size - (sizeof(*ihdr) + sizeof(*ehdr))); uhdr->check = 0; } diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index 396e1a113cd4..011bf9d833ca 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -631,7 +631,7 @@ static int geneve_post_decap_hint(const struct sock *sk, struct sk_buff *skb, /* Adjust the nested UDP header len and checksum. */ uh = udp_hdr(skb); - uh->len = htons(skb->len - gro_hint->nested_tp_offset); + udp_set_len_short(uh, skb->len - gro_hint->nested_tp_offset); if (uh->check) { len = skb->len - gro_hint->nested_tp_offset; skb_shinfo(skb)->gso_type |= SKB_GSO_UDP_TUNNEL_CSUM; diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index cf591ae66736..cc109c144496 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -1876,7 +1876,7 @@ static void push_udp(struct netpoll *np, struct sk_buff *skb, int len) udph = udp_hdr(skb); udph->source = htons(np->local_port); udph->dest = htons(np->remote_port); - udph->len = htons(udp_len); + udp_set_len_short(udph, udp_len); netpoll_udp_checksum(np, skb, len); } diff --git a/drivers/net/netdevsim/dev.c b/drivers/net/netdevsim/dev.c index aed9ad5f1b43..f65b4cf4ea39 100644 --- a/drivers/net/netdevsim/dev.c +++ b/drivers/net/netdevsim/dev.c @@ -845,7 +845,7 @@ static struct sk_buff *nsim_dev_trap_skb_build(void) udph = skb_put_zero(skb, sizeof(struct udphdr) + data_len); get_random_bytes(&udph->source, sizeof(u16)); get_random_bytes(&udph->dest, sizeof(u16)); - udph->len = htons(sizeof(struct udphdr) + data_len); + udp_set_len_short(udph, sizeof(struct udphdr) + data_len); return skb; } diff --git a/drivers/net/netdevsim/psample.c b/drivers/net/netdevsim/psample.c index 717d157c3ae2..1e71c3da4def 100644 --- a/drivers/net/netdevsim/psample.c +++ b/drivers/net/netdevsim/psample.c @@ -73,7 +73,7 @@ static struct sk_buff *nsim_dev_psample_skb_build(void) udph = skb_put_zero(skb, sizeof(struct udphdr) + data_len); get_random_bytes(&udph->source, sizeof(u16)); get_random_bytes(&udph->dest, sizeof(u16)); - udph->len = htons(sizeof(struct udphdr) + data_len); + udp_set_len_short(udph, sizeof(struct udphdr) + data_len); return skb; } diff --git a/drivers/net/netdevsim/psp.c b/drivers/net/netdevsim/psp.c index 59c990fdc79e..6b3532b5e360 100644 --- a/drivers/net/netdevsim/psp.c +++ b/drivers/net/netdevsim/psp.c @@ -84,6 +84,7 @@ nsim_do_psp(struct sk_buff *skb, struct netdevsim *ns, struct iphdr *iph; struct udphdr *uh; __wsum csum; + int udplen; /* Do not decapsulate. Receive the skb with the udp and psp * headers still there as if this is a normal udp packet. @@ -91,19 +92,20 @@ nsim_do_psp(struct sk_buff *skb, struct netdevsim *ns, * provide a valid checksum here, so the skb isn't dropped. */ uh = udp_hdr(skb); + udplen = udp_get_len(skb, uh, skb_transport_offset(skb)); csum = skb_checksum(skb, skb_transport_offset(skb), - ntohs(uh->len), 0); + udplen, 0); switch (skb->protocol) { case htons(ETH_P_IP): iph = ip_hdr(skb); - uh->check = udp_v4_check(ntohs(uh->len), iph->saddr, + uh->check = udp_v4_check(udplen, iph->saddr, iph->daddr, csum); break; #if IS_ENABLED(CONFIG_IPV6) case htons(ETH_P_IPV6): ip6h = ipv6_hdr(skb); - uh->check = udp_v6_check(ntohs(uh->len), &ip6h->saddr, + uh->check = udp_v6_check(udplen, &ip6h->saddr, &ip6h->daddr, csum); break; #endif diff --git a/drivers/net/wireguard/receive.c b/drivers/net/wireguard/receive.c index eb8851113654..824bbefce61c 100644 --- a/drivers/net/wireguard/receive.c +++ b/drivers/net/wireguard/receive.c @@ -62,7 +62,7 @@ static int prepare_skb_header(struct sk_buff *skb, struct wg_device *wg) * to have UDP fields. */ return -EINVAL; - data_len = ntohs(udp->len); + data_len = udp_get_len_short(udp); if (unlikely(data_len < sizeof(struct udphdr) || data_len > skb->len - data_offset)) /* UDP packet is reporting too small of a size or lying about diff --git a/include/linux/udp.h b/include/linux/udp.h index ce56ebcee5cb..998906ec3b32 100644 --- a/include/linux/udp.h +++ b/include/linux/udp.h @@ -23,6 +23,33 @@ static inline struct udphdr *udp_hdr(const struct sk_buff *skb) return (struct udphdr *)skb_transport_header(skb); } +static inline unsigned int udp_get_len(const struct sk_buff *skb, + const struct udphdr *uh, + unsigned int dataoff) +{ + if (uh->len) + return ntohs(uh->len); + if (skb_is_gso(skb)) /* BIG TCP */ + return skb->len - dataoff; + return 0; +} + +static inline unsigned int udp_get_len_short(const struct udphdr *uh) +{ + return ntohs(uh->len); +} + +static inline void udp_set_len(struct udphdr *uh, unsigned int len) +{ + uh->len = len < GRO_LEGACY_MAX_SIZE ? htons(len) : 0; +} + +static inline void udp_set_len_short(struct udphdr *uh, unsigned int len) +{ + DEBUG_NET_WARN_ON_ONCE(len >= GRO_LEGACY_MAX_SIZE); + uh->len = htons(len); +} + #define UDP_HTABLE_SIZE_MIN_PERNET 128 #define UDP_HTABLE_SIZE_MIN (IS_ENABLED(CONFIG_BASE_SMALL) ? 128 : 256) #define UDP_HTABLE_SIZE_MAX 65536 diff --git a/include/trace/events/icmp.h b/include/trace/events/icmp.h index 31559796949a..09ae115099df 100644 --- a/include/trace/events/icmp.h +++ b/include/trace/events/icmp.h @@ -44,7 +44,7 @@ TRACE_EVENT(icmp_send, } else { __entry->sport = ntohs(uh->source); __entry->dport = ntohs(uh->dest); - __entry->ulen = ntohs(uh->len); + __entry->ulen = udp_get_len_short(uh); } p32 = (__be32 *) __entry->saddr; diff --git a/lib/tests/blackhole_dev_kunit.c b/lib/tests/blackhole_dev_kunit.c index 06834ab35f43..fa3e0533038d 100644 --- a/lib/tests/blackhole_dev_kunit.c +++ b/lib/tests/blackhole_dev_kunit.c @@ -46,7 +46,7 @@ static void test_blackholedev(struct kunit *test) uh = (struct udphdr *)skb_push(skb, sizeof(struct udphdr)); skb_set_transport_header(skb, 0); uh->source = uh->dest = htons(UDP_PORT); - uh->len = htons(data_len); + udp_set_len_short(uh, data_len); uh->check = 0; /* (Network) IPv6 */ ip6h = (struct ipv6hdr *)skb_push(skb, sizeof(struct ipv6hdr)); diff --git a/net/6lowpan/nhc_udp.c b/net/6lowpan/nhc_udp.c index 0a506c77283d..ed4227e6db74 100644 --- a/net/6lowpan/nhc_udp.c +++ b/net/6lowpan/nhc_udp.c @@ -88,16 +88,16 @@ static int udp_uncompress(struct sk_buff *skb, size_t needed) switch (lowpan_dev(skb->dev)->lltype) { case LOWPAN_LLTYPE_IEEE802154: if (lowpan_802154_cb(skb)->d_size) - uh.len = htons(lowpan_802154_cb(skb)->d_size - - sizeof(struct ipv6hdr)); + udp_set_len_short(&uh, lowpan_802154_cb(skb)->d_size - + sizeof(struct ipv6hdr)); else - uh.len = htons(skb->len + sizeof(struct udphdr)); + udp_set_len_short(&uh, skb->len + sizeof(struct udphdr)); break; default: - uh.len = htons(skb->len + sizeof(struct udphdr)); + udp_set_len_short(&uh, skb->len + sizeof(struct udphdr)); break; } - pr_debug("uncompressed UDP length: src = %d", ntohs(uh.len)); + pr_debug("uncompressed UDP length: src = %d", udp_get_len_short(&uh)); /* replace the compressed UDP head by the uncompressed UDP * header diff --git a/net/core/pktgen.c b/net/core/pktgen.c index 8e185b318288..5b4dd04d6124 100644 --- a/net/core/pktgen.c +++ b/net/core/pktgen.c @@ -3005,7 +3005,7 @@ static struct sk_buff *fill_packet_ipv4(struct net_device *odev, udph->source = htons(pkt_dev->cur_udp_src); udph->dest = htons(pkt_dev->cur_udp_dst); - udph->len = htons(datalen + 8); /* DATA + udphdr */ + udp_set_len_short(udph, datalen + 8); /* DATA + udphdr */ udph->check = 0; iph->ihl = 5; @@ -3138,7 +3138,7 @@ static struct sk_buff *fill_packet_ipv6(struct net_device *odev, udplen = datalen + sizeof(struct udphdr); udph->source = htons(pkt_dev->cur_udp_src); udph->dest = htons(pkt_dev->cur_udp_dst); - udph->len = htons(udplen); + udp_set_len_short(udph, udplen); udph->check = 0; *(__be32 *) iph = htonl(0x60000000); /* Version + flow */ diff --git a/net/core/selftests.c b/net/core/selftests.c index 0a203d3fb9dc..36b949ae520b 100644 --- a/net/core/selftests.c +++ b/net/core/selftests.c @@ -72,9 +72,9 @@ struct sk_buff *net_test_get_skb(struct net_device *ndev, u8 id, } else { uhdr->source = htons(attr->sport); uhdr->dest = htons(attr->dport); - uhdr->len = htons(sizeof(*shdr) + sizeof(*uhdr) + attr->size); + udp_set_len_short(uhdr, sizeof(*shdr) + sizeof(*uhdr) + attr->size); if (attr->max_size) - uhdr->len = htons(attr->max_size - + udp_set_len_short(uhdr, attr->max_size - (sizeof(*ihdr) + sizeof(*ehdr))); uhdr->check = 0; } diff --git a/net/core/tso.c b/net/core/tso.c index 347b3856ddb9..d2934bcfa795 100644 --- a/net/core/tso.c +++ b/net/core/tso.c @@ -39,7 +39,8 @@ void tso_build_hdr(const struct sk_buff *skb, char *hdr, struct tso_t *tso, } else { struct udphdr *uh = (struct udphdr *)hdr; - uh->len = htons(sizeof(*uh) + size); + /* size is after segmentation. */ + udp_set_len_short(uh, sizeof(*uh) + size); } } EXPORT_SYMBOL(tso_build_hdr); diff --git a/net/ipv4/esp4.c b/net/ipv4/esp4.c index dfc81ee969ae..a6c18aea7498 100644 --- a/net/ipv4/esp4.c +++ b/net/ipv4/esp4.c @@ -323,7 +323,7 @@ static struct ip_esp_hdr *esp_output_udp_encap(struct sk_buff *skb, uh = (struct udphdr *)esp->esph; uh->source = sport; uh->dest = dport; - uh->len = htons(len); + udp_set_len_short(uh, len); uh->check = 0; /* For IPv4 ESP with UDP encapsulation, if xo is not null, the skb is in the crypto offload diff --git a/net/ipv4/fou_core.c b/net/ipv4/fou_core.c index 865bd7205122..a50740d0f288 100644 --- a/net/ipv4/fou_core.c +++ b/net/ipv4/fou_core.c @@ -1040,7 +1040,7 @@ static void fou_build_udp(struct sk_buff *skb, struct ip_tunnel_encap *e, uh->dest = e->dport; uh->source = sport; - uh->len = htons(skb->len); + udp_set_len_short(uh, skb->len); udp_set_csum(!(e->flags & TUNNEL_ENCAP_FLAG_CSUM), skb, fl4->saddr, fl4->daddr, skb->len); diff --git a/net/ipv4/ipconfig.c b/net/ipv4/ipconfig.c index a35ffedacc7c..155db067eaec 100644 --- a/net/ipv4/ipconfig.c +++ b/net/ipv4/ipconfig.c @@ -847,7 +847,7 @@ static void __init ic_bootp_send_if(struct ic_device *d, unsigned long jiffies_d /* Construct UDP header */ b->udph.source = htons(68); b->udph.dest = htons(67); - b->udph.len = htons(sizeof(struct bootp_pkt) - sizeof(struct iphdr)); + udp_set_len_short(&b->udph, sizeof(struct bootp_pkt) - sizeof(struct iphdr)); /* UDP checksum not calculated -- explicitly allowed in BOOTP RFC */ /* Construct DHCP/BOOTP header */ @@ -1025,10 +1025,10 @@ static int __init ic_bootp_recv(struct sk_buff *skb, struct net_device *dev, str if (b->udph.source != htons(67) || b->udph.dest != htons(68)) goto drop; - if (ntohs(h->tot_len) < ntohs(b->udph.len) + sizeof(struct iphdr)) + if (ntohs(h->tot_len) < udp_get_len_short(&b->udph) + sizeof(struct iphdr)) goto drop; - len = ntohs(b->udph.len) - sizeof(struct udphdr); + len = udp_get_len_short(&b->udph) - sizeof(struct udphdr); ext_len = len - (sizeof(*b) - sizeof(struct iphdr) - sizeof(struct udphdr) - diff --git a/net/ipv4/netfilter/nf_nat_snmp_basic_main.c b/net/ipv4/netfilter/nf_nat_snmp_basic_main.c index e540b86bd15b..4492bc548a66 100644 --- a/net/ipv4/netfilter/nf_nat_snmp_basic_main.c +++ b/net/ipv4/netfilter/nf_nat_snmp_basic_main.c @@ -127,7 +127,7 @@ static int snmp_translate(struct nf_conn *ct, int dir, struct sk_buff *skb) { struct iphdr *iph = ip_hdr(skb); struct udphdr *udph = (struct udphdr *)((__be32 *)iph + iph->ihl); - u16 datalen = ntohs(udph->len) - sizeof(struct udphdr); + u16 datalen = udp_get_len_short(udph) - sizeof(struct udphdr); char *data = (unsigned char *)udph + sizeof(struct udphdr); struct snmp_ctx ctx; int ret; @@ -181,7 +181,7 @@ static int help(struct sk_buff *skb, unsigned int protoff, * enough room for a UDP header. Just verify the UDP length field so we * can mess around with the payload. */ - if (ntohs(udph->len) != skb->len - (iph->ihl << 2)) { + if (udp_get_len_short(udph) != skb->len - (iph->ihl << 2)) { nf_ct_helper_log(skb, ct, "dropping malformed packet\n"); return NF_DROP; } diff --git a/net/ipv4/route.c b/net/ipv4/route.c index 3f3de5164d6e..0825ed983714 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -3187,7 +3187,7 @@ static struct sk_buff *inet_rtm_getroute_build_skb(__be32 src, __be32 dst, udph = skb_put_zero(skb, sizeof(struct udphdr)); udph->source = sport; udph->dest = dport; - udph->len = htons(sizeof(struct udphdr)); + udp_set_len_short(udph, sizeof(struct udphdr)); udph->check = 0; break; } diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index 59248a59358c..cb86124fd963 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -1108,7 +1108,8 @@ static int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4, uh = udp_hdr(skb); uh->source = inet_sk(sk)->inet_sport; uh->dest = fl4->fl4_dport; - uh->len = htons(len); + /* Datagram length checked in udp_sendmsg. */ + udp_set_len_short(uh, len); uh->check = 0; if (cork->gso_size) { diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c index 29651b1a0bc7..493e2b9e16fb 100644 --- a/net/ipv4/udp_offload.c +++ b/net/ipv4/udp_offload.c @@ -279,11 +279,11 @@ static struct sk_buff *__skb_udp_tunnel_segment(struct sk_buff *skb, * segment instead of the entire frame. */ if (gso_partial && skb_is_gso(skb)) { - uh->len = htons(skb_shinfo(skb)->gso_size + - SKB_GSO_CB(skb)->data_offset + - skb->head - (unsigned char *)uh); + udp_set_len_short(uh, skb_shinfo(skb)->gso_size + + SKB_GSO_CB(skb)->data_offset + + skb->head - (unsigned char *)uh); } else { - uh->len = htons(len); + udp_set_len_short(uh, len); } if (!need_csum) @@ -468,7 +468,7 @@ static struct sk_buff *__udp_gso_segment_list(struct sk_buff *skb, if (IS_ERR(skb)) return skb; - udp_hdr(skb)->len = htons(sizeof(struct udphdr) + mss); + udp_set_len_short(udp_hdr(skb), sizeof(struct udphdr) + mss); if (is_ipv6) return __udpv6_gso_segment_list_csum(skb); @@ -486,8 +486,8 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb, unsigned int mss; bool copy_dtor; __sum16 check; - __be16 newlen; int ret = 0; + u16 newlen; mss = skb_shinfo(gso_skb)->gso_size; if (gso_skb->len <= sizeof(*uh) + mss) @@ -564,8 +564,8 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb, (skb_shinfo(gso_skb)->tx_flags & SKBTX_ANY_TSTAMP); /* compute checksum adjustment based on old length versus new */ - newlen = htons(sizeof(*uh) + mss); - check = csum16_add(csum16_sub(uh->check, uh->len), newlen); + newlen = sizeof(*uh) + mss; + check = csum16_add(csum16_sub(uh->check, uh->len), htons(newlen)); for (;;) { if (copy_dtor) { @@ -577,7 +577,7 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb, if (!seg->next) break; - uh->len = newlen; + udp_set_len_short(uh, newlen); uh->check = check; if (seg->ip_summed == CHECKSUM_PARTIAL) @@ -594,11 +594,10 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb, * segment may not be full MSS, account for that in the checksum */ if (!skb_is_gso(seg)) - newlen = htons(skb_tail_pointer(seg) - - skb_transport_header(seg) + seg->data_len); - check = csum16_add(csum16_sub(uh->check, uh->len), newlen); + newlen = skb_tail_pointer(seg) - skb_transport_header(seg) + seg->data_len; + check = csum16_add(csum16_sub(uh->check, uh->len), htons(newlen)); - uh->len = newlen; + udp_set_len_short(uh, newlen); uh->check = check; if (seg->ip_summed == CHECKSUM_PARTIAL) @@ -709,7 +708,7 @@ static struct sk_buff *udp_gro_receive_segment(struct list_head *head, } /* Do not deal with padded or malicious packets, sorry ! */ - ulen = ntohs(uh->len); + ulen = udp_get_len_short(uh); if (ulen <= sizeof(*uh) || ulen != skb_gro_len(skb)) { NAPI_GRO_CB(skb)->flush = 1; return NULL; @@ -742,7 +741,7 @@ static struct sk_buff *udp_gro_receive_segment(struct list_head *head, * On len mismatch merge the first packet shorter than gso_size, * otherwise complete the GRO packet. */ - if (ulen > ntohs(uh2->len) || flush) { + if (ulen > udp_get_len_short(uh2) || flush) { pp = p; } else { if (NAPI_GRO_CB(skb)->is_flist) { @@ -765,7 +764,7 @@ static struct sk_buff *udp_gro_receive_segment(struct list_head *head, } } - if (ret || ulen != ntohs(uh2->len) || + if (ret || ulen != udp_get_len_short(uh2) || NAPI_GRO_CB(p)->count >= UDP_GRO_CNT_MAX) pp = p; @@ -915,12 +914,12 @@ static int udp_gro_complete_segment(struct sk_buff *skb) int udp_gro_complete(struct sk_buff *skb, int nhoff, udp_lookup_t lookup) { - __be16 newlen = htons(skb->len - nhoff); struct udphdr *uh = (struct udphdr *)(skb->data + nhoff); + unsigned int newlen = skb->len - nhoff; struct sock *sk; int err; - uh->len = newlen; + udp_set_len_short(uh, newlen); sk = INDIRECT_CALL_INET(lookup, udp6_lib_lookup_skb, udp4_lib_lookup_skb, skb, uh->source, uh->dest); @@ -957,7 +956,7 @@ INDIRECT_CALLABLE_SCOPE int udp4_gro_complete(struct sk_buff *skb, int nhoff) /* do fraglist only if there is no outer UDP encap (or we already processed it) */ if (NAPI_GRO_CB(skb)->is_flist && !NAPI_GRO_CB(skb)->encap_mark) { - uh->len = htons(skb->len - nhoff); + udp_set_len_short(uh, skb->len - nhoff); skb_shinfo(skb)->gso_type |= (SKB_GSO_FRAGLIST|SKB_GSO_UDP_L4); skb_shinfo(skb)->gso_segs = NAPI_GRO_CB(skb)->count; diff --git a/net/ipv4/udp_tunnel_core.c b/net/ipv4/udp_tunnel_core.c index 9ab3728f9630..0fccb38f074d 100644 --- a/net/ipv4/udp_tunnel_core.c +++ b/net/ipv4/udp_tunnel_core.c @@ -178,7 +178,7 @@ void udp_tunnel_xmit_skb(struct rtable *rt, struct sock *sk, struct sk_buff *skb uh->dest = dst_port; uh->source = src_port; - uh->len = htons(skb->len); + udp_set_len_short(uh, skb->len); memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt)); diff --git a/net/ipv6/esp6.c b/net/ipv6/esp6.c index 296b57926abb..72ec0d7d1120 100644 --- a/net/ipv6/esp6.c +++ b/net/ipv6/esp6.c @@ -230,7 +230,8 @@ static void esp_output_encap_csum(struct sk_buff *skb) if (*skb_mac_header(skb) == IPPROTO_UDP) { struct udphdr *uh = udp_hdr(skb); struct ipv6hdr *ip6h = ipv6_hdr(skb); - int len = ntohs(uh->len); + /* esp6_output_udp_encap limits len to U16_MAX. */ + int len = udp_get_len_short(uh); unsigned int offset = skb_transport_offset(skb); __wsum csum = skb_checksum(skb, offset, skb->len - offset, 0); @@ -358,7 +359,7 @@ static struct ip_esp_hdr *esp6_output_udp_encap(struct sk_buff *skb, uh = (struct udphdr *)esp->esph; uh->source = sport; uh->dest = dport; - uh->len = htons(len); + udp_set_len_short(uh, len); uh->check = 0; *skb_mac_header(skb) = IPPROTO_UDP; diff --git a/net/ipv6/fou6.c b/net/ipv6/fou6.c index 157765259e2f..588929409241 100644 --- a/net/ipv6/fou6.c +++ b/net/ipv6/fou6.c @@ -30,7 +30,7 @@ static void fou6_build_udp(struct sk_buff *skb, struct ip_tunnel_encap *e, uh->dest = e->dport; uh->source = sport; - uh->len = htons(skb->len); + udp_set_len_short(uh, skb->len); udp6_set_csum(!(e->flags & TUNNEL_ENCAP_FLAG_CSUM6), skb, &fl6->saddr, &fl6->daddr, skb->len); diff --git a/net/ipv6/ip6_udp_tunnel.c b/net/ipv6/ip6_udp_tunnel.c index 9adb5775487f..dcff7fb16ff6 100644 --- a/net/ipv6/ip6_udp_tunnel.c +++ b/net/ipv6/ip6_udp_tunnel.c @@ -93,7 +93,7 @@ void udp_tunnel6_xmit_skb(struct dst_entry *dst, struct sock *sk, uh->dest = dst_port; uh->source = src_port; - uh->len = htons(skb->len); + udp_set_len_short(uh, skb->len); skb_dst_set(skb, dst); diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c index 392e18b97045..8bbd55546b6e 100644 --- a/net/ipv6/udp.c +++ b/net/ipv6/udp.c @@ -1369,7 +1369,8 @@ static int udp_v6_send_skb(struct sk_buff *skb, struct flowi6 *fl6, uh = udp_hdr(skb); uh->source = fl6->fl6_sport; uh->dest = fl6->fl6_dport; - uh->len = htons(len); + /* Datagram length checked in udpv6_sendmsg. */ + udp_set_len_short(uh, len); uh->check = 0; if (cork->gso_size) { diff --git a/net/ipv6/udp_offload.c b/net/ipv6/udp_offload.c index 778afc7453ce..c92cf5ee3e6a 100644 --- a/net/ipv6/udp_offload.c +++ b/net/ipv6/udp_offload.c @@ -171,7 +171,7 @@ int udp6_gro_complete(struct sk_buff *skb, int nhoff) /* do fraglist only if there is no outer UDP encap (or we already processed it) */ if (NAPI_GRO_CB(skb)->is_flist && !NAPI_GRO_CB(skb)->encap_mark) { - uh->len = htons(skb->len - nhoff); + udp_set_len_short(uh, skb->len - nhoff); skb_shinfo(skb)->gso_type |= (SKB_GSO_FRAGLIST|SKB_GSO_UDP_L4); skb_shinfo(skb)->gso_segs = NAPI_GRO_CB(skb)->count; diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c index f940914959b1..4712cc41881a 100644 --- a/net/l2tp/l2tp_core.c +++ b/net/l2tp/l2tp_core.c @@ -1296,7 +1296,7 @@ static int l2tp_xmit_core(struct l2tp_session *session, struct sk_buff *skb, uns ret = NET_XMIT_DROP; goto out_unlock; } - uh->len = htons(udp_len); + udp_set_len_short(uh, udp_len); /* Calculate UDP checksum if configured to do so */ #if IS_ENABLED(CONFIG_IPV6) diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c index ce542ed4b013..ae3ed2c00ec3 100644 --- a/net/netfilter/ipvs/ip_vs_xmit.c +++ b/net/netfilter/ipvs/ip_vs_xmit.c @@ -1100,7 +1100,7 @@ ipvs_gue_encap(struct net *net, struct sk_buff *skb, dport = cp->dest->tun_port; udph->dest = dport; udph->source = sport; - udph->len = htons(skb->len); + udp_set_len_short(udph, skb->len); udph->check = 0; *next_protocol = IPPROTO_UDP; diff --git a/net/netfilter/nf_conntrack_proto_udp.c b/net/netfilter/nf_conntrack_proto_udp.c index cc9b7e5e1935..8a5675983e7c 100644 --- a/net/netfilter/nf_conntrack_proto_udp.c +++ b/net/netfilter/nf_conntrack_proto_udp.c @@ -41,11 +41,22 @@ static void udp_error_log(const struct sk_buff *skb, nf_l4proto_log_invalid(skb, state, IPPROTO_UDP, "%s", msg); } +static bool udp_validate_len(struct sk_buff *skb, + const struct udphdr *hdr, + unsigned int dataoff) +{ + unsigned int udplen = udp_get_len_short(hdr); + unsigned int skblen = skb->len - dataoff; + + if (udplen > skblen || udplen < sizeof(*hdr)) + return false; + return true; +} + static bool udp_error(struct sk_buff *skb, unsigned int dataoff, const struct nf_hook_state *state) { - unsigned int udplen = skb->len - dataoff; const struct udphdr *hdr; struct udphdr _hdr; @@ -57,7 +68,7 @@ static bool udp_error(struct sk_buff *skb, } /* Truncated/malformed packets */ - if (ntohs(hdr->len) > udplen || ntohs(hdr->len) < sizeof(*hdr)) { + if (!udp_validate_len(skb, hdr, dataoff)) { udp_error_log(skb, state, "truncated/malformed packet"); return true; } diff --git a/net/netfilter/nf_log_syslog.c b/net/netfilter/nf_log_syslog.c index e37b09b3203b..71dff0eb672c 100644 --- a/net/netfilter/nf_log_syslog.c +++ b/net/netfilter/nf_log_syslog.c @@ -301,7 +301,7 @@ nf_log_dump_udp_header(struct nf_log_buf *m, /* Max length: 20 "SPT=65535 DPT=65535 " */ nf_log_buf_add(m, "SPT=%u DPT=%u LEN=%u ", - ntohs(uh->source), ntohs(uh->dest), ntohs(uh->len)); + ntohs(uh->source), ntohs(uh->dest), udp_get_len_short(uh)); out: return 0; diff --git a/net/netfilter/nf_nat_helper.c b/net/netfilter/nf_nat_helper.c index bf591e6af005..3853f41db499 100644 --- a/net/netfilter/nf_nat_helper.c +++ b/net/netfilter/nf_nat_helper.c @@ -161,7 +161,7 @@ nf_nat_mangle_udp_packet(struct sk_buff *skb, /* update the length of the UDP packet */ datalen = skb->len - protoff; - udph->len = htons(datalen); + udp_set_len_short(udph, datalen); /* fix udp checksum if udp checksum was previously calculated */ if (!udph->check && skb->ip_summed != CHECKSUM_PARTIAL) diff --git a/net/psp/psp_main.c b/net/psp/psp_main.c index 8b2f178e317c..90c7dc4e76a1 100644 --- a/net/psp/psp_main.c +++ b/net/psp/psp_main.c @@ -231,7 +231,7 @@ static void psp_write_headers(struct net *net, struct sk_buff *skb, __be32 spi, uh->source = udp_flow_src_port(net, skb, 0, 0, false); } uh->check = 0; - uh->len = htons(udp_len); + udp_set_len_short(uh, udp_len); psph->nexthdr = IPPROTO_TCP; psph->hdrlen = PSP_HDRLEN_NOOPT; diff --git a/net/sched/act_csum.c b/net/sched/act_csum.c index 078d3a27130b..5fff52a8ca90 100644 --- a/net/sched/act_csum.c +++ b/net/sched/act_csum.c @@ -276,7 +276,7 @@ static int tcf_csum_ipv4_udp(struct sk_buff *skb, unsigned int ihl, return 0; iph = ip_hdr(skb); - ul = ntohs(udph->len); + ul = udp_get_len_short(udph); if (udplite || udph->check) { @@ -334,7 +334,7 @@ static int tcf_csum_ipv6_udp(struct sk_buff *skb, unsigned int ihl, return 0; ip6h = ipv6_hdr(skb); - ul = ntohs(udph->len); + ul = udp_get_len_short(udph); udph->check = 0; diff --git a/net/xfrm/xfrm_nat_keepalive.c b/net/xfrm/xfrm_nat_keepalive.c index 458931062a04..906458f3d8c5 100644 --- a/net/xfrm/xfrm_nat_keepalive.c +++ b/net/xfrm/xfrm_nat_keepalive.c @@ -133,7 +133,7 @@ static void nat_keepalive_send(struct nat_keepalive *ka) uh = skb_push(skb, sizeof(*uh)); uh->source = ka->encap_sport; uh->dest = ka->encap_dport; - uh->len = htons(skb->len); + udp_set_len_short(uh, skb->len); uh->check = 0; skb->mark = ka->smark; From 842870cdfa33b9191b46484a3264bd5126a90570 Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Fri, 10 Jul 2026 16:42:35 +0300 Subject: [PATCH 0467/1433] net: Enable BIG TCP with partial GSO skb_segment is called for partial GSO, when netif_needs_gso returns true in validate_xmit_skb. Partial GSO is needed, for example, when segmentation of tunneled traffic is offloaded to a NIC that only supports inner checksum offload. Currently, skb_segment clamps the segment length to 65534 bytes, because gso_size == 65535 is a special value GSO_BY_FRAGS, and we don't want to accidentally assign mss = 65535, as it would fall into the GSO_BY_FRAGS check further in the function. This implementation, however, artificially blocks len > 65534, which is possible since the introduction of BIG TCP. To allow bigger lengths and avoid resegmentation of BIG TCP packets, store the gso_by_frags flag in the beginning and don't use a special value of mss for this purpose after mss was modified. Signed-off-by: Alice Mikityanska Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260710134242.216538-3-alice.kernel@fastmail.im Reviewed-by: Nikolay Aleksandrov Signed-off-by: Paolo Abeni --- net/core/skbuff.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index 18dabb4e9cfa..6ae4c2b205f2 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -4775,6 +4775,7 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb, struct sk_buff *tail = NULL; struct sk_buff *list_skb = skb_shinfo(head_skb)->frag_list; unsigned int mss = skb_shinfo(head_skb)->gso_size; + bool gso_by_frags = mss == GSO_BY_FRAGS; unsigned int doffset = head_skb->data - skb_mac_header(head_skb); unsigned int offset = doffset; unsigned int tnl_hlen = skb_tnl_header_len(head_skb); @@ -4790,7 +4791,7 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb, int nfrags, pos; if ((skb_shinfo(head_skb)->gso_type & SKB_GSO_DODGY) && - mss != GSO_BY_FRAGS && mss != skb_headlen(head_skb)) { + !gso_by_frags && mss != skb_headlen(head_skb)) { struct sk_buff *check_skb; for (check_skb = list_skb; check_skb; check_skb = check_skb->next) { @@ -4818,7 +4819,7 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb, sg = !!(features & NETIF_F_SG); csum = !!can_checksum_protocol(features, proto); - if (sg && csum && (mss != GSO_BY_FRAGS)) { + if (sg && csum && !gso_by_frags) { if (!(features & NETIF_F_GSO_PARTIAL)) { struct sk_buff *iter; unsigned int frag_len; @@ -4852,9 +4853,8 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb, /* GSO partial only requires that we trim off any excess that * doesn't fit into an MSS sized block, so take care of that * now. - * Cap len to not accidentally hit GSO_BY_FRAGS. */ - partial_segs = min(len, GSO_BY_FRAGS - 1) / mss; + partial_segs = len / mss; if (partial_segs > 1) mss *= partial_segs; else @@ -4878,7 +4878,7 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb, int hsize; int size; - if (unlikely(mss == GSO_BY_FRAGS)) { + if (unlikely(gso_by_frags)) { len = list_skb->len; } else { len = head_skb->len - offset; From 47282504ad219d10a30d8b38a68a6697494d2d12 Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Fri, 10 Jul 2026 16:42:36 +0300 Subject: [PATCH 0468/1433] udp: Support BIG TCP GSO packets where they can occur Wherever a GSO packet can occur, and its length is used to fill the UDP header, use udp_set_len that assigns 0 if the length doesn't fit 16 bits, so that the packet can be properly parsed and segmented later, instead of having truncated length. Use udp_get_len in udp_validate_len to treat BIG TCP packets as valid. Signed-off-by: Alice Mikityanska Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260710134242.216538-4-alice.kernel@fastmail.im Reviewed-by: Nikolay Aleksandrov Signed-off-by: Paolo Abeni --- net/ipv4/fou_core.c | 2 +- net/ipv6/fou6.c | 2 +- net/netfilter/ipvs/ip_vs_xmit.c | 2 +- net/netfilter/nf_conntrack_proto_udp.c | 2 +- net/psp/psp_main.c | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/net/ipv4/fou_core.c b/net/ipv4/fou_core.c index a50740d0f288..aef3ce1dec7a 100644 --- a/net/ipv4/fou_core.c +++ b/net/ipv4/fou_core.c @@ -1040,7 +1040,7 @@ static void fou_build_udp(struct sk_buff *skb, struct ip_tunnel_encap *e, uh->dest = e->dport; uh->source = sport; - udp_set_len_short(uh, skb->len); + udp_set_len(uh, skb->len); udp_set_csum(!(e->flags & TUNNEL_ENCAP_FLAG_CSUM), skb, fl4->saddr, fl4->daddr, skb->len); diff --git a/net/ipv6/fou6.c b/net/ipv6/fou6.c index 588929409241..4b659ca60ba9 100644 --- a/net/ipv6/fou6.c +++ b/net/ipv6/fou6.c @@ -30,7 +30,7 @@ static void fou6_build_udp(struct sk_buff *skb, struct ip_tunnel_encap *e, uh->dest = e->dport; uh->source = sport; - udp_set_len_short(uh, skb->len); + udp_set_len(uh, skb->len); udp6_set_csum(!(e->flags & TUNNEL_ENCAP_FLAG_CSUM6), skb, &fl6->saddr, &fl6->daddr, skb->len); diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c index ae3ed2c00ec3..c51ebd83a476 100644 --- a/net/netfilter/ipvs/ip_vs_xmit.c +++ b/net/netfilter/ipvs/ip_vs_xmit.c @@ -1100,7 +1100,7 @@ ipvs_gue_encap(struct net *net, struct sk_buff *skb, dport = cp->dest->tun_port; udph->dest = dport; udph->source = sport; - udp_set_len_short(udph, skb->len); + udp_set_len(udph, skb->len); udph->check = 0; *next_protocol = IPPROTO_UDP; diff --git a/net/netfilter/nf_conntrack_proto_udp.c b/net/netfilter/nf_conntrack_proto_udp.c index 8a5675983e7c..a7edfefd6fd1 100644 --- a/net/netfilter/nf_conntrack_proto_udp.c +++ b/net/netfilter/nf_conntrack_proto_udp.c @@ -45,7 +45,7 @@ static bool udp_validate_len(struct sk_buff *skb, const struct udphdr *hdr, unsigned int dataoff) { - unsigned int udplen = udp_get_len_short(hdr); + unsigned int udplen = udp_get_len(skb, hdr, dataoff); unsigned int skblen = skb->len - dataoff; if (udplen > skblen || udplen < sizeof(*hdr)) diff --git a/net/psp/psp_main.c b/net/psp/psp_main.c index 90c7dc4e76a1..c9c1a8826b7f 100644 --- a/net/psp/psp_main.c +++ b/net/psp/psp_main.c @@ -231,7 +231,7 @@ static void psp_write_headers(struct net *net, struct sk_buff *skb, __be32 spi, uh->source = udp_flow_src_port(net, skb, 0, 0, false); } uh->check = 0; - udp_set_len_short(uh, udp_len); + udp_set_len(uh, udp_len); psph->nexthdr = IPPROTO_TCP; psph->hdrlen = PSP_HDRLEN_NOOPT; From efbc1aa8ed54d1f48d2855f8cff0017429531331 Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Fri, 10 Jul 2026 16:42:37 +0300 Subject: [PATCH 0469/1433] udp: Support gro_ipv4_max_size > 65536 Currently, gro_max_size and gro_ipv4_max_size can be set to values bigger than 65536, and GRO will happily aggregate UDP to the configured size (for example, with TCP traffic in VXLAN tunnels). However, udp_gro_complete uses the 16-bit length field in the UDP header to store the length of the aggregated packet. It leads to the packet truncation later in udp_rcv. Fix this by storing 0 to the UDP length field and by restoring the real length from skb->len in udp_rcv. IP GRO already can store 0 to the IP length field, and iph_totlen()/ipv6_payload_len() are capable of restoring the real length, because the relevant packets (BIG TCP tunneled in UDP tunnels) will have skb_is_gso_tcp == true. Additionally, restrict handling uh->len=0 in udpv6_rcv to BIG TCP and jumbograms only by using the udp_get_len helper. Signed-off-by: Alice Mikityanska Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260710134242.216538-5-alice.kernel@fastmail.im Reviewed-by: Nikolay Aleksandrov Signed-off-by: Paolo Abeni --- net/ipv4/udp.c | 4 ++-- net/ipv4/udp_offload.c | 4 ++-- net/ipv6/udp.c | 4 ++-- net/ipv6/udp_offload.c | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index cb86124fd963..2b9b55a14758 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -2592,8 +2592,8 @@ int udp_rcv(struct sk_buff *skb) struct rtable *rt = skb_rtable(skb); struct net *net = dev_net(skb->dev); struct sock *sk = NULL; - unsigned short ulen; __be32 saddr, daddr; + unsigned int ulen; struct udphdr *uh; bool refcounted; int drop_reason; @@ -2607,7 +2607,7 @@ int udp_rcv(struct sk_buff *skb) goto drop; /* No space for header. */ uh = udp_hdr(skb); - ulen = ntohs(uh->len); + ulen = udp_get_len(skb, uh, 0); saddr = ip_hdr(skb)->saddr; daddr = ip_hdr(skb)->daddr; diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c index 493e2b9e16fb..4f9a3922937c 100644 --- a/net/ipv4/udp_offload.c +++ b/net/ipv4/udp_offload.c @@ -919,7 +919,7 @@ int udp_gro_complete(struct sk_buff *skb, int nhoff, struct sock *sk; int err; - udp_set_len_short(uh, newlen); + udp_set_len(uh, newlen); sk = INDIRECT_CALL_INET(lookup, udp6_lib_lookup_skb, udp4_lib_lookup_skb, skb, uh->source, uh->dest); @@ -956,7 +956,7 @@ INDIRECT_CALLABLE_SCOPE int udp4_gro_complete(struct sk_buff *skb, int nhoff) /* do fraglist only if there is no outer UDP encap (or we already processed it) */ if (NAPI_GRO_CB(skb)->is_flist && !NAPI_GRO_CB(skb)->encap_mark) { - udp_set_len_short(uh, skb->len - nhoff); + udp_set_len(uh, skb->len - nhoff); skb_shinfo(skb)->gso_type |= (SKB_GSO_FRAGLIST|SKB_GSO_UDP_L4); skb_shinfo(skb)->gso_segs = NAPI_GRO_CB(skb)->count; diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c index 8bbd55546b6e..02272466f4c2 100644 --- a/net/ipv6/udp.c +++ b/net/ipv6/udp.c @@ -1081,12 +1081,12 @@ INDIRECT_CALLABLE_SCOPE int udpv6_rcv(struct sk_buff *skb) daddr = &ipv6_hdr(skb)->daddr; uh = udp_hdr(skb); - ulen = ntohs(uh->len); + ulen = udp_get_len(skb, uh, 0); if (ulen > skb->len) goto short_packet; /* Check for jumbo payload */ - if (ulen == 0) + if (ulen == 0 && inet6_is_jumbogram(skb)) ulen = skb->len; if (ulen < sizeof(*uh)) diff --git a/net/ipv6/udp_offload.c b/net/ipv6/udp_offload.c index c92cf5ee3e6a..7370bcb80332 100644 --- a/net/ipv6/udp_offload.c +++ b/net/ipv6/udp_offload.c @@ -171,7 +171,7 @@ int udp6_gro_complete(struct sk_buff *skb, int nhoff) /* do fraglist only if there is no outer UDP encap (or we already processed it) */ if (NAPI_GRO_CB(skb)->is_flist && !NAPI_GRO_CB(skb)->encap_mark) { - udp_set_len_short(uh, skb->len - nhoff); + udp_set_len(uh, skb->len - nhoff); skb_shinfo(skb)->gso_type |= (SKB_GSO_FRAGLIST|SKB_GSO_UDP_L4); skb_shinfo(skb)->gso_segs = NAPI_GRO_CB(skb)->count; From 8475a3efe6e64c1136c3f2bb87294c072b95b7c1 Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Fri, 10 Jul 2026 16:42:38 +0300 Subject: [PATCH 0470/1433] udp: Validate UDP length in udp_gro_receive In the previous commit we started using uh->len = 0 as a marker of a GRO packet bigger than 65536 bytes. Filter out malformed packets coming from the wire with len=0 at udp_gro_receive to exclude them from GRO. Note that a similar check was present in udp_gro_receive_segment, but not in the UDP socket gro_receive flow. By adding an early check to udp_gro_receive, the check in udp_gro_receive_segment can be dropped. Signed-off-by: Alice Mikityanska Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260710134242.216538-6-alice.kernel@fastmail.im Reviewed-by: Nikolay Aleksandrov Signed-off-by: Paolo Abeni --- net/ipv4/udp_offload.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c index 4f9a3922937c..8f77c8788f6d 100644 --- a/net/ipv4/udp_offload.c +++ b/net/ipv4/udp_offload.c @@ -707,12 +707,8 @@ static struct sk_buff *udp_gro_receive_segment(struct list_head *head, return NULL; } - /* Do not deal with padded or malicious packets, sorry ! */ ulen = udp_get_len_short(uh); - if (ulen <= sizeof(*uh) || ulen != skb_gro_len(skb)) { - NAPI_GRO_CB(skb)->flush = 1; - return NULL; - } + /* pull encapsulating udp header */ skb_gro_pull(skb, sizeof(struct udphdr)); @@ -782,8 +778,14 @@ struct sk_buff *udp_gro_receive(struct list_head *head, struct sk_buff *skb, struct sk_buff *p; struct udphdr *uh2; unsigned int off = skb_gro_offset(skb); + unsigned int ulen; int flush = 1; + /* Do not deal with padded or malicious packets, sorry! */ + ulen = udp_get_len_short(uh); + if (ulen <= sizeof(*uh) || ulen != skb_gro_len(skb)) + goto out; + /* We can do L4 aggregation only if the packet can't land in a tunnel * otherwise we could corrupt the inner stream. Detecting such packets * cannot be foolproof and the aggregation might still happen in some From 99ad24516295c170e968c4df5679846334d35aa9 Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Fri, 10 Jul 2026 16:42:39 +0300 Subject: [PATCH 0471/1433] udp: Set length in UDP header to 0 for big GSO packets skb->len may be bigger than 65535 in UDP-based tunnels that have BIG TCP enabled. If GSO aggregates packets that large, set the length in the UDP header to 0, so that tcpdump can print such packets properly (treating them as RFC 2675 jumbograms). Later in the pipeline, __udp_gso_segment will set uh->len to the size of individual packets. Signed-off-by: Alice Mikityanska Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260710134242.216538-7-alice.kernel@fastmail.im Reviewed-by: Nikolay Aleksandrov Signed-off-by: Paolo Abeni --- net/ipv4/udp_tunnel_core.c | 2 +- net/ipv6/ip6_udp_tunnel.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ipv4/udp_tunnel_core.c b/net/ipv4/udp_tunnel_core.c index 0fccb38f074d..a128fe85620d 100644 --- a/net/ipv4/udp_tunnel_core.c +++ b/net/ipv4/udp_tunnel_core.c @@ -178,7 +178,7 @@ void udp_tunnel_xmit_skb(struct rtable *rt, struct sock *sk, struct sk_buff *skb uh->dest = dst_port; uh->source = src_port; - udp_set_len_short(uh, skb->len); + udp_set_len(uh, skb->len); memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt)); diff --git a/net/ipv6/ip6_udp_tunnel.c b/net/ipv6/ip6_udp_tunnel.c index dcff7fb16ff6..32525a051a6f 100644 --- a/net/ipv6/ip6_udp_tunnel.c +++ b/net/ipv6/ip6_udp_tunnel.c @@ -93,7 +93,7 @@ void udp_tunnel6_xmit_skb(struct dst_entry *dst, struct sock *sk, uh->dest = dst_port; uh->source = src_port; - udp_set_len_short(uh, skb->len); + udp_set_len(uh, skb->len); skb_dst_set(skb, dst); From f3d0f753f06665e1981936dfe343d89d6dda9f0f Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Fri, 10 Jul 2026 16:42:40 +0300 Subject: [PATCH 0472/1433] vxlan: Enable BIG TCP packets In Cilium we do support BIG TCP, but so far the latter has only been enabled for direct routing use-cases. A lot of users rely on Cilium with vxlan/geneve tunneling though. The underlying kernel infra for tunneling has not been supporting BIG TCP up to this point. Given we do now, bump tso_max_size for vxlan netdevs up to GSO_MAX_SIZE to allow the admin to use BIG TCP with vxlan tunnels. BIG TCP on vxlan disabled: Standard MTU: # netperf -H 10.1.0.2 -t TCP_STREAM -l60 MIGRATED TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 10.1.0.2 () port 0 AF_INET : demo Recv Send Send Socket Socket Message Elapsed Size Size Size Time Throughput bytes bytes bytes secs. 10^6bits/sec 131072 16384 16384 30.00 34440.00 8k MTU: # netperf -H 10.1.0.2 -t TCP_STREAM -l60 MIGRATED TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 10.1.0.2 () port 0 AF_INET : demo Recv Send Send Socket Socket Message Elapsed Size Size Size Time Throughput bytes bytes bytes secs. 10^6bits/sec 262144 32768 32768 30.00 55684.26 BIG TCP on vxlan enabled: Standard MTU: # netperf -H 10.1.0.2 -t TCP_STREAM -l60 MIGRATED TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 10.1.0.2 () port 0 AF_INET : demo Recv Send Send Socket Socket Message Elapsed Size Size Size Time Throughput bytes bytes bytes secs. 10^6bits/sec 131072 16384 16384 30.00 39564.78 8k MTU: # netperf -H 10.1.0.2 -t TCP_STREAM -l60 MIGRATED TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 10.1.0.2 () port 0 AF_INET : demo Recv Send Send Socket Socket Message Elapsed Size Size Size Time Throughput bytes bytes bytes secs. 10^6bits/sec 262144 32768 32768 30.00 61466.47 When tunnel offloads are not enabled/exposed and we fully need to rely on SW-based segmentation on transmit (e.g. in case of Azure) then the more aggressive batching also has a visible effect. Below example was on the same setup as with above benchmarks but with HW support disabled: # ethtool -k enp10s0f0np0 | grep udp tx-udp_tnl-segmentation: off tx-udp_tnl-csum-segmentation: off tx-udp-segmentation: off rx-udp_tunnel-port-offload: off rx-udp-gro-forwarding: off Before: # netperf -H 10.1.0.2 -t TCP_STREAM -l60 MIGRATED TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 10.1.0.2 () port 0 AF_INET : demo Recv Send Send Socket Socket Message Elapsed Size Size Size Time Throughput bytes bytes bytes secs. 10^6bits/sec 131072 16384 16384 60.00 21820.82 After: # netperf -H 10.1.0.2 -t TCP_STREAM -l60 MIGRATED TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 10.1.0.2 () port 0 AF_INET : demo Recv Send Send Socket Socket Message Elapsed Size Size Size Time Throughput bytes bytes bytes secs. 10^6bits/sec 131072 16384 16384 60.00 29390.78 Example receive side: swapper 0 [002] 4712.645070: net:netif_receive_skb: dev=enp10s0f0np0 skbaddr=0xffff8f3b086e0200 len=129542 ffffffff8cfe3aaa __netif_receive_skb_core.constprop.0+0x6ca ([kernel.kallsyms]) ffffffff8cfe3aaa __netif_receive_skb_core.constprop.0+0x6ca ([kernel.kallsyms]) ffffffff8cfe47dd __netif_receive_skb_list_core+0xed ([kernel.kallsyms]) ffffffff8cfe4e52 netif_receive_skb_list_internal+0x1d2 ([kernel.kallsyms]) ffffffff8d0210d8 gro_complete.constprop.0+0x108 ([kernel.kallsyms]) ffffffff8d021724 dev_gro_receive+0x4e4 ([kernel.kallsyms]) ffffffff8d021a99 gro_receive_skb+0x89 ([kernel.kallsyms]) ffffffffc06edb71 mlx5e_handle_rx_cqe_mpwrq+0x131 ([kernel.kallsyms]) ffffffffc06ee38a mlx5e_poll_rx_cq+0x9a ([kernel.kallsyms]) ffffffffc06ef2c7 mlx5e_napi_poll+0x107 ([kernel.kallsyms]) ffffffff8cfe586d __napi_poll+0x2d ([kernel.kallsyms]) ffffffff8cfe5f8d net_rx_action+0x20d ([kernel.kallsyms]) ffffffff8c35d252 handle_softirqs+0xe2 ([kernel.kallsyms]) ffffffff8c35d556 __irq_exit_rcu+0xd6 ([kernel.kallsyms]) ffffffff8c35d81e irq_exit_rcu+0xe ([kernel.kallsyms]) ffffffff8d2602b8 common_interrupt+0x98 ([kernel.kallsyms]) ffffffff8c000da7 asm_common_interrupt+0x27 ([kernel.kallsyms]) ffffffff8d2645c5 cpuidle_enter_state+0xd5 ([kernel.kallsyms]) ffffffff8cf6358e cpuidle_enter+0x2e ([kernel.kallsyms]) ffffffff8c3ba932 call_cpuidle+0x22 ([kernel.kallsyms]) ffffffff8c3bfb5e do_idle+0x1ce ([kernel.kallsyms]) ffffffff8c3bfd79 cpu_startup_entry+0x29 ([kernel.kallsyms]) ffffffff8c30a6c2 start_secondary+0x112 ([kernel.kallsyms]) ffffffff8c2c142d common_startup_64+0x13e ([kernel.kallsyms]) Example transmit side: swapper 0 [005] 4768.021375: net:net_dev_xmit: dev=enp10s0f0np0 skbaddr=0xffff8af32ebe1200 len=129556 rc=0 ffffffffa75e19c3 dev_hard_start_xmit+0x173 ([kernel.kallsyms]) ffffffffa75e19c3 dev_hard_start_xmit+0x173 ([kernel.kallsyms]) ffffffffa7653823 sch_direct_xmit+0x143 ([kernel.kallsyms]) ffffffffa75e2780 __dev_queue_xmit+0xc70 ([kernel.kallsyms]) ffffffffa76a1205 ip_finish_output2+0x265 ([kernel.kallsyms]) ffffffffa76a1577 __ip_finish_output+0x87 ([kernel.kallsyms]) ffffffffa76a165b ip_finish_output+0x2b ([kernel.kallsyms]) ffffffffa76a179e ip_output+0x5e ([kernel.kallsyms]) ffffffffa76a19d5 ip_local_out+0x35 ([kernel.kallsyms]) ffffffffa770d0e5 iptunnel_xmit+0x185 ([kernel.kallsyms]) ffffffffc179634e nf_nat_used_tuple_new.cold+0x1129 ([kernel.kallsyms]) ffffffffc17a7301 vxlan_xmit_one+0xc21 ([kernel.kallsyms]) ffffffffc17a80a2 vxlan_xmit+0x4a2 ([kernel.kallsyms]) ffffffffa75e18af dev_hard_start_xmit+0x5f ([kernel.kallsyms]) ffffffffa75e1d3f __dev_queue_xmit+0x22f ([kernel.kallsyms]) ffffffffa76a1205 ip_finish_output2+0x265 ([kernel.kallsyms]) ffffffffa76a1577 __ip_finish_output+0x87 ([kernel.kallsyms]) ffffffffa76a165b ip_finish_output+0x2b ([kernel.kallsyms]) ffffffffa76a179e ip_output+0x5e ([kernel.kallsyms]) ffffffffa76a1de2 __ip_queue_xmit+0x1b2 ([kernel.kallsyms]) ffffffffa76a2135 ip_queue_xmit+0x15 ([kernel.kallsyms]) ffffffffa76c70a2 __tcp_transmit_skb+0x522 ([kernel.kallsyms]) ffffffffa76c931a tcp_write_xmit+0x65a ([kernel.kallsyms]) ffffffffa76cb42e tcp_tsq_write+0x5e ([kernel.kallsyms]) ffffffffa76cb7ef tcp_tasklet_func+0x10f ([kernel.kallsyms]) ffffffffa695d9f7 tasklet_action_common+0x107 ([kernel.kallsyms]) ffffffffa695db99 tasklet_action+0x29 ([kernel.kallsyms]) ffffffffa695d252 handle_softirqs+0xe2 ([kernel.kallsyms]) ffffffffa695d556 __irq_exit_rcu+0xd6 ([kernel.kallsyms]) ffffffffa695d81e irq_exit_rcu+0xe ([kernel.kallsyms]) ffffffffa78602b8 common_interrupt+0x98 ([kernel.kallsyms]) ffffffffa6600da7 asm_common_interrupt+0x27 ([kernel.kallsyms]) ffffffffa78645c5 cpuidle_enter_state+0xd5 ([kernel.kallsyms]) ffffffffa756358e cpuidle_enter+0x2e ([kernel.kallsyms]) ffffffffa69ba932 call_cpuidle+0x22 ([kernel.kallsyms]) ffffffffa69bfb5e do_idle+0x1ce ([kernel.kallsyms]) ffffffffa69bfd79 cpu_startup_entry+0x29 ([kernel.kallsyms]) ffffffffa690a6c2 start_secondary+0x112 ([kernel.kallsyms]) ffffffffa68c142d common_startup_64+0x13e ([kernel.kallsyms]) Signed-off-by: Alice Mikityanska Co-developed-by: Daniel Borkmann Signed-off-by: Daniel Borkmann Cc: Nikolay Aleksandrov Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260710134242.216538-8-alice.kernel@fastmail.im Reviewed-by: Nikolay Aleksandrov Signed-off-by: Paolo Abeni --- drivers/net/vxlan/vxlan_core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index 67c367cc5662..3a32f067e701 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -3370,6 +3370,8 @@ static void vxlan_setup(struct net_device *dev) dev->mangleid_features = NETIF_F_GSO_PARTIAL; netif_keep_dst(dev); + netif_set_tso_max_size(dev, GSO_MAX_SIZE); + dev->priv_flags |= IFF_NO_QUEUE; dev->change_proto_down = true; dev->lltx = true; From 03ebe91b0f61536eeef834e3c08bbd83dc33b7c0 Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Fri, 10 Jul 2026 16:42:41 +0300 Subject: [PATCH 0473/1433] geneve: Enable BIG TCP packets From: Daniel Borkmann In Cilium we do support BIG TCP, but so far the latter has only been enabled for direct routing use-cases. A lot of users rely on Cilium with vxlan/geneve tunneling though. The underlying kernel infra for tunneling has not been supporting BIG TCP up to this point. Given we do now, bump tso_max_size for geneve netdevs up to GSO_MAX_SIZE to allow the admin to use BIG TCP with geneve tunnels. BIG TCP on geneve disabled: Standard MTU: # netperf -H 10.1.0.2 -t TCP_STREAM -l60 MIGRATED TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 10.1.0.2 () port 0 AF_INET : demo Recv Send Send Socket Socket Message Elapsed Size Size Size Time Throughput bytes bytes bytes secs. 10^6bits/sec 131072 16384 16384 30.00 37391.34 8k MTU: # netperf -H 10.1.0.2 -t TCP_STREAM -l60 MIGRATED TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 10.1.0.2 () port 0 AF_INET : demo Recv Send Send Socket Socket Message Elapsed Size Size Size Time Throughput bytes bytes bytes secs. 10^6bits/sec 262144 32768 32768 60.00 58030.19 BIG TCP on geneve enabled: Standard MTU: # netperf -H 10.1.0.2 -t TCP_STREAM -l60 MIGRATED TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 10.1.0.2 () port 0 AF_INET : demo Recv Send Send Socket Socket Message Elapsed Size Size Size Time Throughput bytes bytes bytes secs. 10^6bits/sec 131072 16384 16384 30.00 40891.57 8k MTU: # netperf -H 10.1.0.2 -t TCP_STREAM -l60 MIGRATED TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 10.1.0.2 () port 0 AF_INET : demo Recv Send Send Socket Socket Message Elapsed Size Size Size Time Throughput bytes bytes bytes secs. 10^6bits/sec 262144 32768 32768 60.00 61458.39 Example receive side: swapper 0 [008] 3682.509996: net:netif_receive_skb: dev=geneve0 skbaddr=0xffff8f3b0a781800 len=129492 ffffffff8cfe3aaa __netif_receive_skb_core.constprop.0+0x6ca ([kernel.kallsyms]) ffffffff8cfe3aaa __netif_receive_skb_core.constprop.0+0x6ca ([kernel.kallsyms]) ffffffff8cfe47dd __netif_receive_skb_list_core+0xed ([kernel.kallsyms]) ffffffff8cfe4e52 netif_receive_skb_list_internal+0x1d2 ([kernel.kallsyms]) ffffffff8cfe573c napi_complete_done+0x7c ([kernel.kallsyms]) ffffffff8d046c23 gro_cell_poll+0x83 ([kernel.kallsyms]) ffffffff8cfe586d __napi_poll+0x2d ([kernel.kallsyms]) ffffffff8cfe5f8d net_rx_action+0x20d ([kernel.kallsyms]) ffffffff8c35d252 handle_softirqs+0xe2 ([kernel.kallsyms]) ffffffff8c35d556 __irq_exit_rcu+0xd6 ([kernel.kallsyms]) ffffffff8c35d81e irq_exit_rcu+0xe ([kernel.kallsyms]) ffffffff8d2602b8 common_interrupt+0x98 ([kernel.kallsyms]) ffffffff8c000da7 asm_common_interrupt+0x27 ([kernel.kallsyms]) ffffffff8d2645c5 cpuidle_enter_state+0xd5 ([kernel.kallsyms]) ffffffff8cf6358e cpuidle_enter+0x2e ([kernel.kallsyms]) ffffffff8c3ba932 call_cpuidle+0x22 ([kernel.kallsyms]) ffffffff8c3bfb5e do_idle+0x1ce ([kernel.kallsyms]) ffffffff8c3bfd79 cpu_startup_entry+0x29 ([kernel.kallsyms]) ffffffff8c30a6c2 start_secondary+0x112 ([kernel.kallsyms]) ffffffff8c2c142d common_startup_64+0x13e ([kernel.kallsyms]) Example transmit side: swapper 0 [002] 3403.688687: net:net_dev_xmit: dev=enp10s0f0np0 skbaddr=0xffff8af31d104ae8 len=129556 rc=0 ffffffffa75e19c3 dev_hard_start_xmit+0x173 ([kernel.kallsyms]) ffffffffa75e19c3 dev_hard_start_xmit+0x173 ([kernel.kallsyms]) ffffffffa7653823 sch_direct_xmit+0x143 ([kernel.kallsyms]) ffffffffa75e2780 __dev_queue_xmit+0xc70 ([kernel.kallsyms]) ffffffffa76a1205 ip_finish_output2+0x265 ([kernel.kallsyms]) ffffffffa76a1577 __ip_finish_output+0x87 ([kernel.kallsyms]) ffffffffa76a165b ip_finish_output+0x2b ([kernel.kallsyms]) ffffffffa76a179e ip_output+0x5e ([kernel.kallsyms]) ffffffffa76a19d5 ip_local_out+0x35 ([kernel.kallsyms]) ffffffffa770d0e5 iptunnel_xmit+0x185 ([kernel.kallsyms]) ffffffffc179634e nf_nat_used_tuple_new.cold+0x1129 ([kernel.kallsyms]) ffffffffc179d3e0 geneve_xmit+0x920 ([kernel.kallsyms]) ffffffffa75e18af dev_hard_start_xmit+0x5f ([kernel.kallsyms]) ffffffffa75e1d3f __dev_queue_xmit+0x22f ([kernel.kallsyms]) ffffffffa76a1205 ip_finish_output2+0x265 ([kernel.kallsyms]) ffffffffa76a1577 __ip_finish_output+0x87 ([kernel.kallsyms]) ffffffffa76a165b ip_finish_output+0x2b ([kernel.kallsyms]) ffffffffa76a179e ip_output+0x5e ([kernel.kallsyms]) ffffffffa76a1de2 __ip_queue_xmit+0x1b2 ([kernel.kallsyms]) ffffffffa76a2135 ip_queue_xmit+0x15 ([kernel.kallsyms]) ffffffffa76c70a2 __tcp_transmit_skb+0x522 ([kernel.kallsyms]) ffffffffa76c931a tcp_write_xmit+0x65a ([kernel.kallsyms]) ffffffffa76ca3b9 __tcp_push_pending_frames+0x39 ([kernel.kallsyms]) ffffffffa76c1fb6 tcp_rcv_established+0x276 ([kernel.kallsyms]) ffffffffa76d3957 tcp_v4_do_rcv+0x157 ([kernel.kallsyms]) ffffffffa76d6053 tcp_v4_rcv+0x1243 ([kernel.kallsyms]) ffffffffa769b8ea ip_protocol_deliver_rcu+0x2a ([kernel.kallsyms]) ffffffffa769bab7 ip_local_deliver_finish+0x77 ([kernel.kallsyms]) ffffffffa769bb4d ip_local_deliver+0x6d ([kernel.kallsyms]) ffffffffa769abe7 ip_sublist_rcv_finish+0x37 ([kernel.kallsyms]) ffffffffa769b713 ip_sublist_rcv+0x173 ([kernel.kallsyms]) ffffffffa769bde2 ip_list_rcv+0x102 ([kernel.kallsyms]) ffffffffa75e4868 __netif_receive_skb_list_core+0x178 ([kernel.kallsyms]) ffffffffa75e4e52 netif_receive_skb_list_internal+0x1d2 ([kernel.kallsyms]) ffffffffa75e573c napi_complete_done+0x7c ([kernel.kallsyms]) ffffffffa7646c23 gro_cell_poll+0x83 ([kernel.kallsyms]) ffffffffa75e586d __napi_poll+0x2d ([kernel.kallsyms]) ffffffffa75e5f8d net_rx_action+0x20d ([kernel.kallsyms]) ffffffffa695d252 handle_softirqs+0xe2 ([kernel.kallsyms]) ffffffffa695d556 __irq_exit_rcu+0xd6 ([kernel.kallsyms]) ffffffffa695d81e irq_exit_rcu+0xe ([kernel.kallsyms]) ffffffffa78602b8 common_interrupt+0x98 ([kernel.kallsyms]) ffffffffa6600da7 asm_common_interrupt+0x27 ([kernel.kallsyms]) ffffffffa78645c5 cpuidle_enter_state+0xd5 ([kernel.kallsyms]) ffffffffa756358e cpuidle_enter+0x2e ([kernel.kallsyms]) ffffffffa69ba932 call_cpuidle+0x22 ([kernel.kallsyms]) ffffffffa69bfb5e do_idle+0x1ce ([kernel.kallsyms]) ffffffffa69bfd79 cpu_startup_entry+0x29 ([kernel.kallsyms]) ffffffffa690a6c2 start_secondary+0x112 ([kernel.kallsyms]) ffffffffa68c142d common_startup_64+0x13e ([kernel.kallsyms]) Signed-off-by: Daniel Borkmann Co-developed-by: Alice Mikityanska Signed-off-by: Alice Mikityanska Cc: Nikolay Aleksandrov Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260710134242.216538-9-alice.kernel@fastmail.im Reviewed-by: Nikolay Aleksandrov Signed-off-by: Paolo Abeni --- drivers/net/geneve.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index 011bf9d833ca..a1639ad53077 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -1745,6 +1745,8 @@ static void geneve_setup(struct net_device *dev) dev->max_mtu = IP_MAX_MTU - GENEVE_BASE_HLEN - dev->hard_header_len; netif_keep_dst(dev); + netif_set_tso_max_size(dev, GSO_MAX_SIZE); + dev->priv_flags &= ~IFF_TX_SKB_SHARING; dev->priv_flags |= IFF_LIVE_ADDR_CHANGE | IFF_NO_QUEUE; dev->lltx = true; From 5cb53743e1ff3a2e0eac415412c724b78c5f047f Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Fri, 10 Jul 2026 16:42:42 +0300 Subject: [PATCH 0474/1433] selftests: net: Add a test for BIG TCP in UDP tunnels The test sets up VXLAN and GENEVE tunnels over IPv4 and IPv6 and runs IPv4 and IPv6 traffic through them with BIG TCP enabled. It checks that a non-negligible amount of big aggregated packets are seen by setting up iptables counters. Check the number of packets on both TX and RX sides to verify that GSO packets are valid and not dropped. Capture on the lower netdev (veth), when checksum offload is on, to verify that encapsulated BIG TCP packets can get to their destination. In the test with TX checksum offload off, software GSO splits aggregated VXLAN packets before passing them to veth, so capture inside the tunnel instead to check that the big packets are not dropped. Check that the amount of SACKs is negligible. On unsupported kernels, some amount of broken GSO packets bigger than 65536 bytes can be produced in VXLAN tunnels, but they don't reach the destination. Seeing TCP SACKs is a sign that such packets could have been dropped (in such cases, the amount of SACKs is a few times bigger than the number of attempts to send BIG TCP packets). Signed-off-by: Alice Mikityanska Link: https://patch.msgid.link/20260710134242.216538-10-alice.kernel@fastmail.im Reviewed-by: Nikolay Aleksandrov Signed-off-by: Paolo Abeni --- tools/testing/selftests/net/Makefile | 1 + .../testing/selftests/net/big_tcp_tunnels.sh | 188 ++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100755 tools/testing/selftests/net/big_tcp_tunnels.sh diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index 708d960ae07d..4cb0a0bc1eea 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -13,6 +13,7 @@ TEST_PROGS := \ arp_ndisc_untracked_subnets.sh \ bareudp.sh \ big_tcp.sh \ + big_tcp_tunnels.sh \ bind_bhash.sh \ bpf_offload.py \ bridge_stp_mode.sh \ diff --git a/tools/testing/selftests/net/big_tcp_tunnels.sh b/tools/testing/selftests/net/big_tcp_tunnels.sh new file mode 100755 index 000000000000..d6513ed8d4e8 --- /dev/null +++ b/tools/testing/selftests/net/big_tcp_tunnels.sh @@ -0,0 +1,188 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-2.0 +# +# Testing for IPv4 and IPv6 BIG TCP over VXLAN and GENEVE tunnels. + +SERVER_NS=$(mktemp -u server-XXXXXXXX) +SERVER_IP4="192.168.1.1" +SERVER_IP6="2001:db8::1:1" +SERVER_IP4_TUN="192.168.2.1" +SERVER_IP6_TUN="2001:db8::2:1" + +CLIENT_NS=$(mktemp -u client-XXXXXXXX) +CLIENT_IP4="192.168.1.2" +CLIENT_IP6="2001:db8::1:2" +CLIENT_IP4_TUN="192.168.2.2" +CLIENT_IP6_TUN="2001:db8::2:2" + +: "${PACKETS_THRESHOLD:=1000}" + +# Kselftest framework requirement - SKIP code is 4. +ksft_skip=4 + +setup() { + ip netns add "$SERVER_NS" + ip netns add "$CLIENT_NS" + ip -netns "$SERVER_NS" link add link1 type veth peer name link0 netns "$CLIENT_NS" + + ip -netns "$CLIENT_NS" link set link0 up + ip -netns "$CLIENT_NS" addr replace "$CLIENT_IP4/24" dev link0 + ip -netns "$CLIENT_NS" addr replace "$CLIENT_IP6/112" dev link0 nodad + ip -netns "$CLIENT_NS" link set link0 \ + gso_max_size 196608 gso_ipv4_max_size 196608 \ + gro_max_size 196608 gro_ipv4_max_size 196608 + ip -netns "$SERVER_NS" link set link1 up + ip -netns "$SERVER_NS" addr replace "$SERVER_IP4/24" dev link1 + ip -netns "$SERVER_NS" addr replace "$SERVER_IP6/112" dev link1 nodad + ip -netns "$SERVER_NS" link set link1 \ + gso_max_size 196608 gso_ipv4_max_size 196608 \ + gro_max_size 196608 gro_ipv4_max_size 196608 + + ip netns exec "$SERVER_NS" netserver >/dev/null +} + +setup_tunnel() { + if [ "$2" = 4 ]; then + SERVER_IP="$SERVER_IP4" + CLIENT_IP="$CLIENT_IP4" + echo "Setting up ${1^^} over IPv4, veth tx csum offload $3" + else + SERVER_IP="$SERVER_IP6" + CLIENT_IP="$CLIENT_IP6" + echo "Setting up ${1^^} over IPv6, veth tx csum offload $3" + fi + + if [ "$1" = vxlan ]; then + ip -netns "$CLIENT_NS" link add tun0 type vxlan \ + id 5001 remote "$SERVER_IP" local "$CLIENT_IP" dev link0 dstport 4789 + else + ip -netns "$CLIENT_NS" link add tun0 type geneve \ + id 5001 remote "$SERVER_IP" + fi + ip -netns "$CLIENT_NS" link set tun0 up + ip -netns "$CLIENT_NS" addr replace "$CLIENT_IP4_TUN/24" dev tun0 + ip -netns "$CLIENT_NS" addr replace "$CLIENT_IP6_TUN/112" dev tun0 nodad + ip -netns "$CLIENT_NS" link set tun0 \ + gso_max_size 196608 gso_ipv4_max_size 196608 \ + gro_max_size 196608 gro_ipv4_max_size 196608 + if [ "$1" = vxlan ]; then + ip -netns "$SERVER_NS" link add tun1 type vxlan \ + id 5001 remote "$CLIENT_IP" local "$SERVER_IP" dev link1 dstport 4789 + else + ip -netns "$SERVER_NS" link add tun1 type geneve \ + id 5001 remote "$CLIENT_IP" + fi + ip -netns "$SERVER_NS" link set tun1 up + ip -netns "$SERVER_NS" addr replace "$SERVER_IP4_TUN/24" dev tun1 + ip -netns "$SERVER_NS" addr replace "$SERVER_IP6_TUN/112" dev tun1 nodad + ip -netns "$SERVER_NS" link set tun1 \ + gso_max_size 196608 gso_ipv4_max_size 196608 \ + gro_max_size 196608 gro_ipv4_max_size 196608 + + ip netns exec "$CLIENT_NS" ethtool -K link0 tx-checksumming "$3" > /dev/null + ip netns exec "$SERVER_NS" ethtool -K link1 tx-checksumming "$3" > /dev/null +} + +cleanup_tunnel() { + ip -netns "$CLIENT_NS" link del tun0 + ip -netns "$SERVER_NS" link del tun1 +} + +cleanup() { + ip netns pids "$SERVER_NS" | xargs -r kill + ip netns pids "$CLIENT_NS" | xargs -r kill + ip netns del "$SERVER_NS" + ip netns del "$CLIENT_NS" + rm -rf "$WORKDIR" +} + +do_test() { + # When tx csum offload is off, software GSO is performed before passing the + # packet to veth. Check BIG TCP packets inside the VXLAN tunnel to verify + # the software checksum path: if the checksum code is broken, these packets + # will be dropped. + if [ "$3" = on ]; then + CAPTURE_IFACE='link' + if [ "$1" = 4 ]; then + IPTABLES=iptables + else + IPTABLES=ip6tables + fi + else + CAPTURE_IFACE='tun' + if [ "$2" = 4 ]; then + IPTABLES=iptables + else + IPTABLES=ip6tables + fi + fi + if [ "$2" = 4 ]; then + IPTABLES_SACK=iptables + else + IPTABLES_SACK=ip6tables + fi + + ip netns exec "$SERVER_NS" "$IPTABLES" -w -t raw -I PREROUTING -i "${CAPTURE_IFACE}1" -m length ! --length 0:65535 -m comment --comment "bigtcp" + ip netns exec "$CLIENT_NS" "$IPTABLES" -w -t raw -I OUTPUT -o "${CAPTURE_IFACE}0" -m length ! --length 0:65535 -m comment --comment "bigtcp" + ip netns exec "$SERVER_NS" "$IPTABLES_SACK" -w -t raw -I OUTPUT -o "tun1" -p tcp -m tcp --tcp-flags ACK ACK --tcp-option 5 -m comment --comment "sack" + + if [ "$2" = 4 ]; then + SERVER_IP="$SERVER_IP4_TUN" + echo "Running IPv4 traffic in the tunnel" + else + SERVER_IP="$SERVER_IP6_TUN" + echo "Running IPv6 traffic in the tunnel" + fi + + ip netns exec "$CLIENT_NS" netperf -t TCP_STREAM -l 5 -H "$SERVER_IP" -- \ + -m 80000 > /dev/null + + PACKETS_SERVER=$(ip netns exec "$SERVER_NS" "$IPTABLES-save" -c -t raw | sed -rn '/ --comment bigtcp/{s/^\[([0-9]+):.*/\1/p;q}') + PACKETS_CLIENT=$(ip netns exec "$CLIENT_NS" "$IPTABLES-save" -c -t raw | sed -rn '/ --comment bigtcp/{s/^\[([0-9]+):.*/\1/p;q}') + PACKETS_SACK=$(ip netns exec "$SERVER_NS" "$IPTABLES_SACK-save" -c -t raw | sed -rn '/ --comment sack/{s/^\[([0-9]+):.*/\1/p;q}') + ip netns exec "$SERVER_NS" "$IPTABLES" -w -t raw -D PREROUTING -i "${CAPTURE_IFACE}1" -m length ! --length 0:65535 -m comment --comment "bigtcp" + ip netns exec "$CLIENT_NS" "$IPTABLES" -w -t raw -D OUTPUT -o "${CAPTURE_IFACE}0" -m length ! --length 0:65535 -m comment --comment "bigtcp" + ip netns exec "$SERVER_NS" "$IPTABLES_SACK" -w -t raw -D OUTPUT -o "tun1" -p tcp -m tcp --tcp-flags ACK ACK --tcp-option 5 -m comment --comment "sack" + + echo "Captured BIG TCP RX packets: $PACKETS_SERVER" + echo "Captured BIG TCP TX packets: $PACKETS_CLIENT" + echo "Captured TCP SACK packets: $PACKETS_SACK" + [ "$PACKETS_SERVER" -gt "$PACKETS_THRESHOLD" ] || return 1 + [ "$PACKETS_CLIENT" -gt "$PACKETS_THRESHOLD" ] || return 1 + [ "$PACKETS_SACK" -lt "$(( PACKETS_CLIENT / 2 ))" ] || return 1 +} + +if ! netperf -V &> /dev/null; then + echo "SKIP: Could not run test without netperf tool" + exit "$ksft_skip" +fi + +if ! iptables --version &> /dev/null; then + echo "SKIP: Could not run test without iptables tool" + exit "$ksft_skip" +fi + +if ! ethtool --version &> /dev/null; then + echo "SKIP: Could not run test without ethtool tool" + exit "$ksft_skip" +fi + +if ! ip link help 2>&1 | grep gso_ipv4_max_size &> /dev/null; then + echo "SKIP: Could not run test without gso/gro_ipv4_max_size supported in ip-link" + exit "$ksft_skip" +fi + +WORKDIR=$(mktemp -d) +trap cleanup EXIT +setup +for tunnel in vxlan geneve; do + for tun_family in 4 6; do + for traffic_family in 4 6; do + for csum_offload in on off; do + setup_tunnel "$tunnel" "$tun_family" "$csum_offload" || exit "$?" + do_test "$tun_family" "$traffic_family" "$csum_offload" || exit "$?" + cleanup_tunnel + done + done + done +done From 1c3f880ed00ef704d140a10777274937b8de29cc Mon Sep 17 00:00:00 2001 From: Priyansha Tiwari Date: Thu, 9 Jul 2026 17:12:27 +0530 Subject: [PATCH 0475/1433] wifi: mac80211: implement STA-mode peer probing Add STA/P2P-client support to ieee80211_probe_peer(): when called for a station interface, send a null-data frame (TODS) to the associated AP and report the ACK via cfg80211_probe_status(). For MLO connections the driver/firmware selects the link (IEEE80211_LINK_UNSPECIFIED); for non-MLO the single link is used. Signed-off-by: Priyansha Tiwari Link: https://patch.msgid.link/20260709114228.672317-2-pritiwa@qti.qualcomm.com Signed-off-by: Johannes Berg --- include/net/mac80211.h | 7 ++- net/mac80211/cfg.c | 117 ++++++++++++++++++++--------------------- net/mac80211/status.c | 5 +- 3 files changed, 67 insertions(+), 62 deletions(-) diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 948a7cdab27c..999e5189f113 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -1348,6 +1348,11 @@ ieee80211_rate_get_vht_nss(const struct ieee80211_tx_rate *rate) * @status.tx_time: airtime consumed for transmission; note this is only * used for WMM AC, not for airtime fairness * @status.flags: status flags, see &enum mac80211_tx_status_flags + * @status.link_valid: if the link which is identified by @status.link_id is + * valid. This flag is set by the driver in the TX status callback when the + * connection is MLO and the driver knows which link was used for TX. + * @status.link_id: id of the link used to transmit the packet. This is used + * along with @status.link_valid. * @status.status_driver_data: driver use area * @ack: union part for pure ACK data * @ack.cookie: cookie for the ACK @@ -1402,7 +1407,7 @@ struct ieee80211_tx_info { u8 pad; u16 tx_time; u8 flags; - u8 pad2; + u8 link_valid:1, link_id:4; void *status_driver_data[16 / sizeof(void *)]; } status; struct { diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 9c311c8290f7..df68a5bdeb2f 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -4956,99 +4956,100 @@ static int ieee80211_probe_peer(struct wiphy *wiphy, struct net_device *dev, struct ieee80211_local *local = sdata->local; struct ieee80211_qos_hdr *nullfunc; struct sk_buff *skb; - int size = sizeof(*nullfunc); __le16 fc; - bool qos; + bool qos, fromds; + struct ieee80211_bss_conf *conf; struct ieee80211_tx_info *info; struct sta_info *sta; struct ieee80211_chanctx_conf *chanctx_conf; - struct ieee80211_bss_conf *conf; enum nl80211_band band; - u8 link_id; + const u8 *dst_addr; + const u8 *src_addr; + int link_id; + int size; int ret; /* the lock is needed to assign the cookie later */ lockdep_assert_wiphy(local->hw.wiphy); - rcu_read_lock(); - sta = sta_info_get_bss(sdata, peer); - if (!sta) { - ret = -ENOLINK; - goto unlock; + switch (ieee80211_vif_type_p2p(&sdata->vif)) { + case NL80211_IFTYPE_AP: + fromds = true; + break; + case NL80211_IFTYPE_STATION: + /* For STA, the peer is always the associated AP/GO */ + peer = sdata->vif.cfg.ap_addr; + fromds = false; + break; + default: + return -EOPNOTSUPP; } + sta = sta_info_get_bss(sdata, peer); + if (!sta) + return -ENOLINK; + qos = sta->sta.wme; + dst_addr = sta->sta.addr; if (ieee80211_vif_is_mld(&sdata->vif)) { - if (sta->sta.mlo) { - link_id = IEEE80211_LINK_UNSPECIFIED; - } else { + if (fromds && !sta->sta.mlo) { /* - * For non-MLO clients connected to an AP MLD, band - * information is not used; instead, sta->deflink is - * used to send packets. + * AP mode, non-MLO client on AP MLD: use the + * per-link address for the client's link. */ link_id = sta->deflink.link_id; - - conf = rcu_dereference(sdata->vif.link_conf[link_id]); - - if (unlikely(!conf)) { - ret = -ENOLINK; - goto unlock; - } + conf = wiphy_dereference(local->hw.wiphy, + sdata->vif.link_conf[link_id]); + if (!conf) + return -ENOLINK; + src_addr = conf->addr; + } else { + /* + * MLO client (AP or STA mode), or STA mode: + * always use LINK_UNSPECIFIED and MLD address. + */ + link_id = IEEE80211_LINK_UNSPECIFIED; + src_addr = sdata->vif.addr; } /* MLD transmissions must not rely on the band */ band = 0; } else { - chanctx_conf = rcu_dereference(sdata->vif.bss_conf.chanctx_conf); - if (WARN_ON(!chanctx_conf)) { - ret = -EINVAL; - goto unlock; - } + chanctx_conf = wiphy_dereference(local->hw.wiphy, + sdata->vif.bss_conf.chanctx_conf); + if (WARN_ON(!chanctx_conf)) + return -EINVAL; band = chanctx_conf->def.chan->band; link_id = 0; + src_addr = sdata->vif.addr; } - if (qos) { - fc = cpu_to_le16(IEEE80211_FTYPE_DATA | - IEEE80211_STYPE_QOS_NULLFUNC | - IEEE80211_FCTL_FROMDS); - } else { + size = sizeof(*nullfunc); + fc = cpu_to_le16(IEEE80211_FTYPE_DATA | + (qos ? IEEE80211_STYPE_QOS_NULLFUNC + : IEEE80211_STYPE_NULLFUNC) | + (fromds ? IEEE80211_FCTL_FROMDS : IEEE80211_FCTL_TODS)); + if (!qos) size -= 2; - fc = cpu_to_le16(IEEE80211_FTYPE_DATA | - IEEE80211_STYPE_NULLFUNC | - IEEE80211_FCTL_FROMDS); - } skb = dev_alloc_skb(local->hw.extra_tx_headroom + size); - if (!skb) { - ret = -ENOMEM; - goto unlock; - } + if (!skb) + return -ENOMEM; skb->dev = dev; - skb_reserve(skb, local->hw.extra_tx_headroom); - nullfunc = skb_put(skb, size); + nullfunc = skb_put_zero(skb, size); nullfunc->frame_control = fc; - nullfunc->duration_id = 0; - memcpy(nullfunc->addr1, sta->sta.addr, ETH_ALEN); - if (ieee80211_vif_is_mld(&sdata->vif) && !sta->sta.mlo) { - memcpy(nullfunc->addr2, conf->addr, ETH_ALEN); - memcpy(nullfunc->addr3, conf->addr, ETH_ALEN); - } else { - memcpy(nullfunc->addr2, sdata->vif.addr, ETH_ALEN); - memcpy(nullfunc->addr3, sdata->vif.addr, ETH_ALEN); - } - nullfunc->seq_ctrl = 0; + + memcpy(nullfunc->addr1, dst_addr, ETH_ALEN); + memcpy(nullfunc->addr2, src_addr, ETH_ALEN); + memcpy(nullfunc->addr3, fromds ? src_addr : dst_addr, ETH_ALEN); info = IEEE80211_SKB_CB(skb); - info->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS | IEEE80211_TX_INTFL_NL80211_FRAME_TX; info->band = band; - info->control.flags |= u32_encode_bits(link_id, IEEE80211_TX_CTRL_MLO_LINK); skb_set_queue_mapping(skb, IEEE80211_AC_VO); @@ -5059,18 +5060,14 @@ static int ieee80211_probe_peer(struct wiphy *wiphy, struct net_device *dev, ret = ieee80211_attach_ack_skb(local, skb, cookie, GFP_ATOMIC); if (ret) { kfree_skb(skb); - goto unlock; + return ret; } local_bh_disable(); ieee80211_xmit(sdata, sta, skb); local_bh_enable(); - ret = 0; -unlock: - rcu_read_unlock(); - - return ret; + return 0; } static int ieee80211_cfg_get_channel(struct wiphy *wiphy, diff --git a/net/mac80211/status.c b/net/mac80211/status.c index c3d29aed93fe..d635490f59d3 100644 --- a/net/mac80211/status.c +++ b/net/mac80211/status.c @@ -655,7 +655,10 @@ static void ieee80211_report_ack_skb(struct ieee80211_local *local, GFP_ATOMIC); else if (ieee80211_is_any_nullfunc(hdr->frame_control)) cfg80211_probe_status(sdata->dev, hdr->addr1, - cookie, -1, acked, + cookie, + info->status.link_valid ? + info->status.link_id : -1, + acked, info->status.ack_signal, is_valid_ack_signal, GFP_ATOMIC); From f15350104130bfa6fd9ba692e9a806843e6e3f8d Mon Sep 17 00:00:00 2001 From: Priyansha Tiwari Date: Thu, 9 Jul 2026 17:12:28 +0530 Subject: [PATCH 0476/1433] wifi: mac80211_hwsim: report TX status link_id Populate link_valid/link_id in mac80211_hwsim TX status so the transmitted link is reported to mac80211. Set the link information in both the direct TX status path and the wmediumd/netlink TX status path. With that done, enable NL80211_EXT_FEATURE_PROBE_AP. Signed-off-by: Priyansha Tiwari Link: https://patch.msgid.link/20260709114228.672317-3-pritiwa@qti.qualcomm.com [add note about NL80211_EXT_FEATURE_PROBE_AP] Signed-off-by: Johannes Berg --- .../wireless/virtual/mac80211_hwsim_main.c | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index d5e3d19ccc3e..4293b7dc3253 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -2103,6 +2103,7 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw, bool ack, unicast_data; enum nl80211_chan_width confbw = NL80211_CHAN_WIDTH_20_NOHT; u32 _portid, i; + int tx_link_id = -1; if (WARN_ON(skb->len < 10)) { /* Should not happen; just a sanity check for addr1 use */ @@ -2160,6 +2161,9 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw, hdr, &link_sta); } + if (bss_conf) + tx_link_id = bss_conf->link_id; + if (unlikely(!bss_conf)) { /* if it's an MLO STA, it might have deactivated all * links temporarily - but we don't handle real PS in @@ -2271,6 +2275,12 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw, if (!(txi->flags & IEEE80211_TX_CTL_NO_ACK) && ack) txi->flags |= IEEE80211_TX_STAT_ACK; + + if (tx_link_id >= 0) { + txi->status.link_valid = 1; + txi->status.link_id = tx_link_id; + } + ieee80211_tx_status_irqsafe(hw, skb); } @@ -6106,6 +6116,7 @@ static int mac80211_hwsim_new_radio(struct genl_info *info, wiphy_ext_feature_set(hw->wiphy, NL80211_EXT_FEATURE_CQM_RSSI_LIST); wiphy_ext_feature_set(hw->wiphy, NL80211_EXT_FEATURE_PUNCT); + wiphy_ext_feature_set(hw->wiphy, NL80211_EXT_FEATURE_PROBE_AP); for (i = 0; i < ARRAY_SIZE(data->link_data); i++) { hrtimer_setup(&data->link_data[i].beacon_timer, mac80211_hwsim_beacon, @@ -6333,6 +6344,27 @@ static void hwsim_register_wmediumd(struct net *net, u32 portid) spin_unlock_bh(&hwsim_radio_lock); } +static int mac80211_hwsim_get_link_id(struct ieee80211_vif *vif, + struct ieee80211_hdr *hdr) +{ + int i; + + if (!vif || !ieee80211_vif_is_mld(vif)) + return -1; + + for (i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) { + struct ieee80211_bss_conf *link_conf; + + link_conf = rcu_dereference(vif->link_conf[i]); + if (!link_conf) + continue; + if (ether_addr_equal(link_conf->addr, hdr->addr2)) + return i; + } + + return -1; +} + static int hwsim_tx_info_frame_received_nl(struct sk_buff *skb_2, struct genl_info *info) { @@ -6413,13 +6445,18 @@ static int hwsim_tx_info_frame_received_nl(struct sk_buff *skb_2, txi->status.ack_signal = nla_get_u32(info->attrs[HWSIM_ATTR_SIGNAL]); + hdr = (struct ieee80211_hdr *)skb->data; + i = mac80211_hwsim_get_link_id(txi->control.vif, hdr); + if (i >= 0) { + txi->status.link_valid = 1; + txi->status.link_id = i; + } + if (!(hwsim_flags & HWSIM_TX_CTL_NO_ACK) && (hwsim_flags & HWSIM_TX_STAT_ACK)) { - if (skb->len >= 16) { - hdr = (struct ieee80211_hdr *) skb->data; + if (skb->len >= 16) mac80211_hwsim_monitor_ack(data2->channel, hdr->addr2); - } txi->flags |= IEEE80211_TX_STAT_ACK; } From a37286345f71561eb5b3834bc7e10b00c434b5c2 Mon Sep 17 00:00:00 2001 From: Louis Kotze Date: Wed, 22 Jul 2026 09:07:33 +0200 Subject: [PATCH 0477/1433] wifi: cfg80211: say why the auth/assoc BSS lookup failed The BSS lookup for an authentication or association request can fail for three distinct reasons: cfg80211 has no scan entry at all for the BSSID/channel, an entry exists but is older than IEEE80211_SCAN_RESULT_EXPIRE (and not held), or a fresh entry exists but its use_for flags do not allow this use. All three currently surface as the same generic extack message "Error fetching BSS for link" on the MLO association path, and as a bare -ENOENT with no message at all on the authentication and non-MLO association paths. Since wpa_supplicant logs the extack message verbatim ("nl80211: kernel reports: ..."), that message is often the only diagnostic a user sees when an MLO association degrades to fewer links, and it does not say whether a fresh scan could have helped. In practice the expired case is common for MLO partner links: 6 GHz is passive-scan in many regulatory domains, so the partner-link entry is routinely stale by the time userspace requests the association even though the link is perfectly usable. Let __cfg80211_get_bss() take an optional extack and record, during the same bss_lock walk that fails the lookup, whether any matching entry was rejected for being expired or for not being usable for the requested use, and set a distinct message for each case (and a combined one when different entries were rejected for different reasons). Reorder the checks in the walk so that an entry's identity (type, privacy, channel, BSSID/SSID) is established before the usability checks; this doesn't change which entry is returned since an entry is only used when all checks pass. Also give the -EINVAL paths in nl80211_assoc_bss() proper messages while at it, and keep pointing the bad_attr at the failing link on the MLO path there; the message for that case is already set by the lookup itself. Signed-off-by: Louis Kotze Link: https://patch.msgid.link/20260722070734.3612581-2-loukot@gmail.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 7 +++++-- net/wireless/nl80211.c | 27 +++++++++++++++--------- net/wireless/scan.c | 44 ++++++++++++++++++++++++++++++++------- net/wireless/tests/scan.c | 2 +- 4 files changed, 59 insertions(+), 21 deletions(-) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index b8e9fbb89e69..15c08b24502f 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -8424,6 +8424,8 @@ cfg80211_inform_bss(struct wiphy *wiphy, * @bss_type: type of BSS, see &enum ieee80211_bss_type * @privacy: privacy filter, see &enum ieee80211_privacy * @use_for: indicates which use is intended + * @extack: (optional) extack that is filled with the reason when no + * usable entry was found; may be %NULL * * Return: Reference-counted BSS on success. %NULL on error. */ @@ -8433,7 +8435,8 @@ struct cfg80211_bss *__cfg80211_get_bss(struct wiphy *wiphy, const u8 *ssid, size_t ssid_len, enum ieee80211_bss_type bss_type, enum ieee80211_privacy privacy, - u32 use_for); + u32 use_for, + struct netlink_ext_ack *extack); /** * cfg80211_get_bss - get a BSS reference @@ -8457,7 +8460,7 @@ cfg80211_get_bss(struct wiphy *wiphy, struct ieee80211_channel *channel, { return __cfg80211_get_bss(wiphy, channel, bssid, ssid, ssid_len, bss_type, privacy, - NL80211_BSS_USE_FOR_NORMAL); + NL80211_BSS_USE_FOR_NORMAL, NULL); } static inline struct cfg80211_bss * diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index d962b5944533..ac0c0da45241 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -12890,9 +12890,11 @@ static int nl80211_authenticate(struct sk_buff *skb, struct genl_info *info) return -EINVAL; } - req.bss = cfg80211_get_bss(&rdev->wiphy, chan, bssid, ssid, ssid_len, - IEEE80211_BSS_TYPE_ESS, - IEEE80211_PRIVACY_ANY); + req.bss = __cfg80211_get_bss(&rdev->wiphy, chan, bssid, ssid, ssid_len, + IEEE80211_BSS_TYPE_ESS, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, + info->extack); if (!req.bss) return -ENOENT; @@ -13037,6 +13039,7 @@ static int nl80211_crypto_settings(struct cfg80211_registered_device *rdev, } static struct cfg80211_bss *nl80211_assoc_bss(struct cfg80211_registered_device *rdev, + struct genl_info *info, const u8 *ssid, int ssid_len, struct nlattr **attrs, int assoc_link_id, int link_id) @@ -13046,8 +13049,10 @@ static struct cfg80211_bss *nl80211_assoc_bss(struct cfg80211_registered_device const u8 *bssid; u32 freq, use_for = 0; - if (!attrs[NL80211_ATTR_MAC] || !attrs[NL80211_ATTR_WIPHY_FREQ]) + if (!attrs[NL80211_ATTR_MAC] || !attrs[NL80211_ATTR_WIPHY_FREQ]) { + GENL_SET_ERR_MSG(info, "BSSID or frequency missing"); return ERR_PTR(-EINVAL); + } bssid = nla_data(attrs[NL80211_ATTR_MAC]); @@ -13056,8 +13061,10 @@ static struct cfg80211_bss *nl80211_assoc_bss(struct cfg80211_registered_device freq += nla_get_u32(attrs[NL80211_ATTR_WIPHY_FREQ_OFFSET]); chan = nl80211_get_valid_chan(&rdev->wiphy, freq); - if (!chan) + if (!chan) { + GENL_SET_ERR_MSG(info, "invalid or disabled channel"); return ERR_PTR(-EINVAL); + } if (assoc_link_id >= 0) use_for = NL80211_BSS_USE_FOR_MLD_LINK; @@ -13068,7 +13075,7 @@ static struct cfg80211_bss *nl80211_assoc_bss(struct cfg80211_registered_device ssid, ssid_len, IEEE80211_BSS_TYPE_ESS, IEEE80211_PRIVACY_ANY, - use_for); + use_for, info->extack); if (!bss) return ERR_PTR(-ENOENT); @@ -13107,13 +13114,13 @@ static int nl80211_process_links(struct cfg80211_registered_device *rdev, return -EINVAL; } links[link_id].bss = - nl80211_assoc_bss(rdev, ssid, ssid_len, attrs, + nl80211_assoc_bss(rdev, info, ssid, ssid_len, attrs, assoc_link_id, link_id); if (IS_ERR(links[link_id].bss)) { err = PTR_ERR(links[link_id].bss); links[link_id].bss = NULL; - NL_SET_ERR_MSG_ATTR(info->extack, link, - "Error fetching BSS for link"); + /* the BSS lookup set the specific message already */ + NL_SET_BAD_ATTR(info->extack, link); return err; } @@ -13329,7 +13336,7 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info) if (req.link_id >= 0) return -EINVAL; - req.bss = nl80211_assoc_bss(rdev, ssid, ssid_len, info->attrs, + req.bss = nl80211_assoc_bss(rdev, info, ssid, ssid_len, info->attrs, -1, -1); if (IS_ERR(req.bss)) return PTR_ERR(req.bss); diff --git a/net/wireless/scan.c b/net/wireless/scan.c index e62b7dd2b7c2..90a4f285654f 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -1609,10 +1609,12 @@ struct cfg80211_bss *__cfg80211_get_bss(struct wiphy *wiphy, const u8 *ssid, size_t ssid_len, enum ieee80211_bss_type bss_type, enum ieee80211_privacy privacy, - u32 use_for) + u32 use_for, + struct netlink_ext_ack *extack) { struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); struct cfg80211_internal_bss *bss, *res = NULL; + bool expired = false, unusable = false; unsigned long now = jiffies; int bss_privacy; @@ -1634,22 +1636,48 @@ struct cfg80211_bss *__cfg80211_get_bss(struct wiphy *wiphy, continue; if (!is_valid_ether_addr(bss->pub.bssid)) continue; - if ((bss->pub.use_for & use_for) != use_for) + if (!is_bss(&bss->pub, bssid, ssid, ssid_len)) continue; + + /* + * The identity checks above must all come first so that + * the expired/unusable classification below only ever + * applies to entries that actually match the request. + */ + /* Don't get expired BSS structs */ if (time_after(now, bss->ts + IEEE80211_SCAN_RESULT_EXPIRE) && - !atomic_read(&bss->hold)) + !atomic_read(&bss->hold)) { + expired = true; continue; - if (is_bss(&bss->pub, bssid, ssid, ssid_len)) { - res = bss; - bss_ref_get(rdev, res); - break; } + + if ((bss->pub.use_for & use_for) != use_for) { + unusable = true; + continue; + } + + res = bss; + bss_ref_get(rdev, res); + break; } spin_unlock_bh(&rdev->bss_lock); - if (!res) + if (!res) { + if (expired && unusable) + NL_SET_ERR_MSG(extack, + "BSS entries are expired or cannot be used for the requested operation"); + else if (unusable) + NL_SET_ERR_MSG(extack, + "BSS cannot be used for the requested operation"); + else if (expired) + NL_SET_ERR_MSG(extack, + "BSS entry in scan results is expired"); + else + NL_SET_ERR_MSG(extack, + "BSS not found in scan results"); return NULL; + } trace_cfg80211_return_bss(&res->pub); return &res->pub; } diff --git a/net/wireless/tests/scan.c b/net/wireless/tests/scan.c index b1a9c1466d6c..2fc717317ac3 100644 --- a/net/wireless/tests/scan.c +++ b/net/wireless/tests/scan.c @@ -617,7 +617,7 @@ static void test_inform_bss_ml_sta(struct kunit *test) link_bss = __cfg80211_get_bss(wiphy, NULL, sta_prof.bssid, NULL, 0, IEEE80211_BSS_TYPE_ANY, IEEE80211_PRIVACY_ANY, - 0); + 0, NULL); KUNIT_ASSERT_NOT_NULL(test, link_bss); KUNIT_EXPECT_EQ(test, link_bss->signal, 0); KUNIT_EXPECT_EQ(test, link_bss->beacon_interval, From 617564804c5f44bb2a3ac80de96f9252f65b34d4 Mon Sep 17 00:00:00 2001 From: Louis Kotze Date: Wed, 22 Jul 2026 09:07:34 +0200 Subject: [PATCH 0478/1433] wifi: cfg80211: tests: check BSS lookup failure reasons Add a KUnit test for the extack failure reasons that __cfg80211_get_bss() now reports: no matching scan entry at all, a matching entry that is expired, and a matching entry whose use_for flags do not allow the requested use. Also cover the cases that must not report a failure (a fresh entry, and an expired-but-held entry), an entry that is both expired and unusable, and the combined message when one matching entry is expired while another is current but unusable. Signed-off-by: Louis Kotze Link: https://patch.msgid.link/20260722070734.3612581-3-loukot@gmail.com Signed-off-by: Johannes Berg --- net/wireless/tests/scan.c | 119 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/net/wireless/tests/scan.c b/net/wireless/tests/scan.c index 2fc717317ac3..8c20278b5d3a 100644 --- a/net/wireless/tests/scan.c +++ b/net/wireless/tests/scan.c @@ -402,6 +402,124 @@ static void test_inform_bss_ssid_only(struct kunit *test) cfg80211_put_bss(wiphy, bss); } +static void test_get_bss_miss_reason(struct kunit *test) +{ + struct inform_bss ctx = { + .test = test, + }; + struct wiphy *wiphy = T_WIPHY(test, ctx); + struct cfg80211_inform_bss inform_bss = { + .signal = 50, + .drv_data = &ctx, + }; + const u8 bssid[ETH_ALEN] = { 0x10, 0x22, 0x33, 0x44, 0x55, 0x66 }; + const u8 other_bssid[ETH_ALEN] = { 0x66, 0x55, 0x44, 0x33, 0x22, 0x11 }; + static const u8 ies[] = { + [0] = WLAN_EID_SSID, + [1] = 4, + [2] = 'T', 'E', 'S', 'T' + }; + struct cfg80211_internal_bss *ibss; + struct netlink_ext_ack extack = {}; + struct cfg80211_bss *bss, *bss2, *found; + + inform_bss.chan = ieee80211_get_channel_khz(wiphy, MHZ_TO_KHZ(2412)); + KUNIT_ASSERT_NOT_NULL(test, inform_bss.chan); + + bss = cfg80211_inform_bss_data(wiphy, &inform_bss, + CFG80211_BSS_FTYPE_PRESP, bssid, 0, + 0x1234, 100, ies, sizeof(ies), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, bss); + ibss = container_of(bss, struct cfg80211_internal_bss, pub); + + /* Fresh usable entry: found, no message is set */ + found = __cfg80211_get_bss(wiphy, NULL, bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_ASSERT_PTR_EQ(test, found, bss); + KUNIT_EXPECT_NULL(test, extack._msg); + cfg80211_put_bss(wiphy, found); + + /* No entry at all for this BSSID */ + found = __cfg80211_get_bss(wiphy, NULL, other_bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_EXPECT_NULL(test, found); + KUNIT_EXPECT_STREQ(test, extack._msg, "BSS not found in scan results"); + + /* Fresh entry that is not usable for the requested use */ + extack._msg = NULL; + bss->use_for = 0; + found = __cfg80211_get_bss(wiphy, NULL, bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_EXPECT_NULL(test, found); + KUNIT_EXPECT_STREQ(test, extack._msg, + "BSS cannot be used for the requested operation"); + bss->use_for = NL80211_BSS_USE_FOR_ALL; + + /* Expired entry, > IEEE80211_SCAN_RESULT_EXPIRE (30s) old */ + extack._msg = NULL; + ibss->ts = jiffies - 60 * HZ; + found = __cfg80211_get_bss(wiphy, NULL, bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_EXPECT_NULL(test, found); + KUNIT_EXPECT_STREQ(test, extack._msg, + "BSS entry in scan results is expired"); + + /* An entry both expired and unusable reports expired */ + extack._msg = NULL; + bss->use_for = 0; + found = __cfg80211_get_bss(wiphy, NULL, bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_EXPECT_NULL(test, found); + KUNIT_EXPECT_STREQ(test, extack._msg, + "BSS entry in scan results is expired"); + bss->use_for = NL80211_BSS_USE_FOR_ALL; + + /* Expired but held entries are still usable, no message is set */ + extack._msg = NULL; + atomic_set(&ibss->hold, 1); + found = __cfg80211_get_bss(wiphy, NULL, bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_ASSERT_PTR_EQ(test, found, bss); + KUNIT_EXPECT_NULL(test, extack._msg); + cfg80211_put_bss(wiphy, found); + atomic_set(&ibss->hold, 0); + + /* + * With one matching entry expired and another current but + * unusable, both reasons are reported. + */ + bss2 = cfg80211_inform_bss_data(wiphy, &inform_bss, + CFG80211_BSS_FTYPE_PRESP, other_bssid, + 0, 0x1234, 100, ies, sizeof(ies), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, bss2); + bss2->use_for = 0; + extack._msg = NULL; + found = __cfg80211_get_bss(wiphy, NULL, NULL, "TEST", 4, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_EXPECT_NULL(test, found); + KUNIT_EXPECT_STREQ(test, extack._msg, + "BSS entries are expired or cannot be used for the requested operation"); + + cfg80211_put_bss(wiphy, bss2); + cfg80211_put_bss(wiphy, bss); +} + static struct inform_bss_ml_sta_case { const char *desc; int mld_id; @@ -855,6 +973,7 @@ kunit_test_suite(gen_new_ie); static struct kunit_case inform_bss_test_cases[] = { KUNIT_CASE(test_inform_bss_ssid_only), + KUNIT_CASE(test_get_bss_miss_reason), KUNIT_CASE_PARAM(test_inform_bss_ml_sta, inform_bss_ml_sta_gen_params), {} }; From db9f9e603fe5f9aa03bda9960aabb21e3cfdcc52 Mon Sep 17 00:00:00 2001 From: Jiajia Liu Date: Wed, 22 Jul 2026 15:18:19 +0800 Subject: [PATCH 0479/1433] wifi: cfg80211: reg: add a newline in DFS debug message Add a missing newline in the debug message in print_regdomain. Signed-off-by: Jiajia Liu Link: https://patch.msgid.link/20260722071819.22465-1-liujiajia@kylinos.cn [break long line] Signed-off-by: Johannes Berg --- net/wireless/reg.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/wireless/reg.c b/net/wireless/reg.c index 1e8214d6b6d8..a8336baf85dc 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -3792,7 +3792,8 @@ static void print_regdomain(const struct ieee80211_regdomain *rd) } } - pr_debug(" DFS Master region: %s", reg_dfs_region_str(rd->dfs_region)); + pr_debug(" DFS Master region: %s\n", + reg_dfs_region_str(rd->dfs_region)); print_rd_rules(rd); } From 43d8026ecc18b9e18600c265f7eb4a1b20829287 Mon Sep 17 00:00:00 2001 From: Phil Elwell Date: Mon, 25 May 2026 16:39:26 +0800 Subject: [PATCH 0480/1433] wifi: brcmfmac: 43430 and 43455 are CYW parts The brcmfmac driver uses the SDIO vendor ID values to identify which vendor's driver extensions to use. However, the Cypress/Infineon devices have a vendor ID of 02d0, which is Broadcom. In order to use the Cypress driver extensions, modify the static mapping for "43430", "4345" (sic) and "43455" to indicate that they are Cypress parts. Signed-off-by: Phil Elwell Signed-off-by: Shelley Yang Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260525083926.583964-1-shelley.yang@infineon.com Signed-off-by: Johannes Berg --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c index d24b80e492e0..5926811c5411 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c @@ -988,10 +988,10 @@ static const struct sdio_device_id brcmf_sdmmc_ids[] = { BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43364, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4335_4339, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4339, WCC), - BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43430, WCC), + BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43430, CYW), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43439, WCC), - BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4345, WCC), - BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43455, WCC), + BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4345, CYW), + BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43455, CYW), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4354, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4356, WCC), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4359, WCC), From ffdc1ee2d6d6f7554426f9d11cbcb0a32f324173 Mon Sep 17 00:00:00 2001 From: Andrea Mayer Date: Sat, 11 Jul 2026 18:29:06 +0200 Subject: [PATCH 0481/1433] seg6: add FIB table attribute for post-encap SID route lookup After SRv6 encapsulation the kernel looks up the route for the first SID, that is the outer IPv6 destination of the encapsulated packet. This post-encap SID route lookup uses the FIB table of the current routing context. When the encap route is installed in a VRF, the VRF's table may not have a route matching the SID. In that case another table should handle it, e.g. one configured for underlay connectivity. Add an optional SEG6_IPTUNNEL_TABLE attribute that selects the FIB table used for this lookup. When set by the user, the attribute is honored on both the input path (forwarded traffic) and the output path (locally originated traffic). SRv6 encap routes that do not set the attribute use the current routing context, as before. For example: # SID route installed in the underlay table 500 ip -6 route add fc00::100/128 via fd00::1 dev veth0 table 500 # encap route in vrf-100; the first SID is looked up in table 500 ip -6 route add cafe::1/128 vrf vrf-100 \ encap seg6 mode encap segs fc00::100 lookup 500 dev veth0 # or look up the SID in the main table ip -6 route add cafe::1/128 vrf vrf-100 \ encap seg6 mode encap segs fc00::100 lookup main dev veth0 Suggested-by: Nicolas Dichtel Signed-off-by: Andrea Mayer Reviewed-by: Nicolas Dichtel Acked-by: David Ahern Link: https://patch.msgid.link/20260711162907.6521-2-andrea.mayer@uniroma2.it Signed-off-by: Jakub Kicinski --- include/uapi/linux/seg6_iptunnel.h | 1 + net/ipv6/seg6_iptunnel.c | 132 +++++++++++++++++++++++++---- 2 files changed, 117 insertions(+), 16 deletions(-) diff --git a/include/uapi/linux/seg6_iptunnel.h b/include/uapi/linux/seg6_iptunnel.h index 485889b19900..e1964b3a4bb0 100644 --- a/include/uapi/linux/seg6_iptunnel.h +++ b/include/uapi/linux/seg6_iptunnel.h @@ -21,6 +21,7 @@ enum { SEG6_IPTUNNEL_UNSPEC, SEG6_IPTUNNEL_SRH, SEG6_IPTUNNEL_SRC, /* struct in6_addr */ + SEG6_IPTUNNEL_TABLE, /* __u32 FIB table for post-encap SID lookup */ __SEG6_IPTUNNEL_MAX, }; #define SEG6_IPTUNNEL_MAX (__SEG6_IPTUNNEL_MAX - 1) diff --git a/net/ipv6/seg6_iptunnel.c b/net/ipv6/seg6_iptunnel.c index 4c45c0a77d75..61c6a27bf202 100644 --- a/net/ipv6/seg6_iptunnel.c +++ b/net/ipv6/seg6_iptunnel.c @@ -51,6 +51,7 @@ struct seg6_lwt { struct dst_cache cache_input; struct dst_cache cache_output; struct in6_addr tunsrc; + u32 table; struct seg6_iptunnel_encap tuninfo[]; }; @@ -68,6 +69,7 @@ seg6_encap_lwtunnel(struct lwtunnel_state *lwt) static const struct nla_policy seg6_iptunnel_policy[SEG6_IPTUNNEL_MAX + 1] = { [SEG6_IPTUNNEL_SRH] = { .type = NLA_BINARY }, [SEG6_IPTUNNEL_SRC] = NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)), + [SEG6_IPTUNNEL_TABLE] = { .type = NLA_U32 }, }; static int nla_put_srh(struct sk_buff *skb, int attrtype, @@ -479,6 +481,73 @@ int seg6_do_srh_inline(struct sk_buff *skb, struct ipv6_sr_hdr *osrh) } EXPORT_SYMBOL_GPL(seg6_do_srh_inline); +/* look up a route in a specific FIB table. + * Returns a refcounted dst, or NULL if the table does not exist. + */ +static struct dst_entry *seg6_table_lookup(struct net *net, + struct sk_buff *skb, + struct flowi6 *fl6, u32 tbl_id) +{ + struct fib6_table *table; + struct rt6_info *rt; + + table = fib6_get_table(net, tbl_id); + if (!table) + return NULL; + + rt = ip6_pol_route(net, table, 0, fl6, skb, RT6_LOOKUP_F_HAS_SADDR); + return &rt->dst; +} + +static void seg6_init_flowi6(struct sk_buff *skb, struct ipv6hdr *hdr, + struct flowi6 *fl6) +{ + memset(fl6, 0, sizeof(*fl6)); + + fl6->daddr = hdr->daddr; + fl6->saddr = hdr->saddr; + fl6->flowlabel = ip6_flowinfo(hdr); + fl6->flowi6_mark = skb->mark; + fl6->flowi6_proto = hdr->nexthdr; +} + +/* look up the route for the first SID on the input path and set it on the skb. + * Returns the refcounted dst, or NULL if a reference could not be safely taken. + */ +static struct dst_entry *seg6_input_route(struct net *net, + struct sk_buff *skb, + struct seg6_lwt *slwt) +{ + u32 table = slwt->table; + + if (table) { + struct ipv6hdr *hdr = ipv6_hdr(skb); + struct dst_entry *dst; + struct flowi6 fl6; + + seg6_init_flowi6(skb, hdr, &fl6); + fl6.flowi6_iif = skb->dev->ifindex; + + dst = seg6_table_lookup(net, skb, &fl6, table); + if (!dst) { + dst = &net->ipv6.ip6_blk_hole_entry->dst; + dst_hold(dst); + } + + skb_dst_drop(skb); + skb_dst_set(skb, dst); + } else { + ip6_route_input(skb); + + /* ip6_route_input() sets a NOREF dst; force a refcount on it + * before caching or further use. + */ + skb_dst_force(skb); + } + + return skb_dst(skb); +} + static int seg6_input_finish(struct net *net, struct sock *sk, struct sk_buff *skb) { @@ -513,15 +582,9 @@ static int seg6_input_core(struct net *net, struct sock *sk, goto drop; } - if (!dst) { - ip6_route_input(skb); - - /* ip6_route_input() sets a NOREF dst; force a refcount on it - * before caching or further use. - */ - skb_dst_force(skb); - dst = skb_dst(skb); - if (unlikely(!dst)) { + if (unlikely(!dst)) { + dst = seg6_input_route(net, skb, slwt); + if (!dst) { err = -ENETUNREACH; goto drop; } @@ -578,6 +641,29 @@ static int seg6_input(struct sk_buff *skb) return seg6_input_core(dev_net(skb->dev), NULL, skb); } +/* look up the route for the first SID on the output path. Always returns a + * refcounted dst. + */ +static struct dst_entry *seg6_output_dst_lookup(struct net *net, + struct sk_buff *skb, + struct flowi6 *fl6, + struct seg6_lwt *slwt) +{ + struct dst_entry *dst; + + if (slwt->table) { + dst = seg6_table_lookup(net, skb, fl6, slwt->table); + if (!dst) { + dst = &net->ipv6.ip6_blk_hole_entry->dst; + dst_hold(dst); + } + } else { + dst = ip6_route_output(net, NULL, fl6); + } + + return dst; +} + static int seg6_output_core(struct net *net, struct sock *sk, struct sk_buff *skb) { @@ -600,14 +686,9 @@ static int seg6_output_core(struct net *net, struct sock *sk, struct ipv6hdr *hdr = ipv6_hdr(skb); struct flowi6 fl6; - memset(&fl6, 0, sizeof(fl6)); - fl6.daddr = hdr->daddr; - fl6.saddr = hdr->saddr; - fl6.flowlabel = ip6_flowinfo(hdr); - fl6.flowi6_mark = skb->mark; - fl6.flowi6_proto = hdr->nexthdr; + seg6_init_flowi6(skb, hdr, &fl6); - dst = ip6_route_output(net, NULL, &fl6); + dst = seg6_output_dst_lookup(net, skb, &fl6, slwt); if (dst->error) { err = dst->error; goto drop; @@ -752,6 +833,15 @@ static int seg6_build_state(struct net *net, struct nlattr *nla, } } + if (tb[SEG6_IPTUNNEL_TABLE]) { + slwt->table = nla_get_u32(tb[SEG6_IPTUNNEL_TABLE]); + if (!slwt->table) { + NL_SET_ERR_MSG(extack, "invalid lookup table"); + err = -EINVAL; + goto err_destroy_output; + } + } + newts->type = LWTUNNEL_ENCAP_SEG6; newts->flags |= LWTUNNEL_STATE_INPUT_REDIRECT; @@ -795,6 +885,10 @@ static int seg6_fill_encap_info(struct sk_buff *skb, nla_put_in6_addr(skb, SEG6_IPTUNNEL_SRC, &slwt->tunsrc)) return -EMSGSIZE; + if (slwt->table && + nla_put_u32(skb, SEG6_IPTUNNEL_TABLE, slwt->table)) + return -EMSGSIZE; + return 0; } @@ -809,6 +903,9 @@ static int seg6_encap_nlsize(struct lwtunnel_state *lwtstate) if (!ipv6_addr_any(&slwt->tunsrc)) nlsize += nla_total_size(sizeof(slwt->tunsrc)); + if (slwt->table) + nlsize += nla_total_size(sizeof(u32)); + return nlsize; } @@ -826,6 +923,9 @@ static int seg6_encap_cmp(struct lwtunnel_state *a, struct lwtunnel_state *b) if (!ipv6_addr_equal(&a_slwt->tunsrc, &b_slwt->tunsrc)) return 1; + if (a_slwt->table != b_slwt->table) + return 1; + return memcmp(a_hdr, b_hdr, len); } From 1016a547c6851cde2f5f57f173cba08153e3bcef Mon Sep 17 00:00:00 2001 From: Andrea Mayer Date: Sat, 11 Jul 2026 18:29:07 +0200 Subject: [PATCH 0482/1433] selftests: seg6: add test for post-encap SID route lookup Add a selftest for the SEG6_IPTUNNEL_TABLE attribute, which selects the FIB table for the post-encap SID route lookup. This looks up the route for the first SID, the outer destination of the encapsulated packet. Two routers provide L3 VPN services over an IPv6 underlay. Each router uses a separate VRF per tenant, with default blackhole routes (IPv4 and IPv6) that drop unmatched traffic. Tenant traffic is encapsulated, then decapsulated with an End.DT46. The encap routes are installed in the tenant VRF, but the routes that match the first SIDs live in a separate underlay table (500). The "lookup 500" attribute points the lookup there rather than to the VRF. The test covers both the input path, where forwarded host traffic triggers encapsulation, and the output path, where a router originates traffic from its own loopback inside a VRF. With the "lookup" attribute, traffic reaches its destination on both paths. Without it, on the input path the lookup stays in the VRF and hits the blackhole, and on the output path it falls through to the main table, which has no matching route. Signed-off-by: Andrea Mayer Reviewed-by: Nicolas Dichtel Link: https://patch.msgid.link/20260711162907.6521-3-andrea.mayer@uniroma2.it Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/Makefile | 1 + .../net/srv6_encap_lookup_l3vpn_test.sh | 1027 +++++++++++++++++ 2 files changed, 1028 insertions(+) create mode 100755 tools/testing/selftests/net/srv6_encap_lookup_l3vpn_test.sh diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index b43ddf192ecc..ab890e6f79dd 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -87,6 +87,7 @@ TEST_PROGS := \ rxtimestamp.sh \ sctp_vrf.sh \ skf_net_off.sh \ + srv6_encap_lookup_l3vpn_test.sh \ srv6_end_dt46_l3vpn_test.sh \ srv6_end_dt4_l3vpn_test.sh \ srv6_end_dt6_l3vpn_test.sh \ diff --git a/tools/testing/selftests/net/srv6_encap_lookup_l3vpn_test.sh b/tools/testing/selftests/net/srv6_encap_lookup_l3vpn_test.sh new file mode 100755 index 000000000000..d6249303b7ea --- /dev/null +++ b/tools/testing/selftests/net/srv6_encap_lookup_l3vpn_test.sh @@ -0,0 +1,1027 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# author: Andrea Mayer + +# This test evaluates the SRv6 encap "lookup" attribute. After encapsulation +# the router looks up the route for the first SID, that is the outer IPv6 +# destination of the encapsulated packet. The attribute selects the FIB table +# used for this post-encap SID route lookup. +# +# Two routers (rt-1, rt-2) provide L3 VPN services over an IPv6 underlay +# (fd00::/64). Each router uses a separate VRF per tenant, with default +# blackhole routes (IPv4 and IPv6) to prevent traffic from leaking out of +# the VRF. Tenant traffic is encapsulated, then decapsulated with an +# End.DT46. Each router proxies both NDP and ARP. +# +# The routes that match the first SIDs are installed in a dedicated underlay +# table (500) rather than the main table (254). The encap routes use +# "lookup 500" to select this table for the post-encap SID route lookup. +# +# Without the "lookup" attribute, the route for the first SID cannot be found: +# - on the input path (forwarded traffic), the lookup stays in the VRF +# and hits the blackhole; +# - on the output path (locally originated traffic), the lookup falls +# through to the main table, with no route to the first SID. +# +# +# Legend (specific per-tenant addresses are in the instantiation tables below): +# X = tenant id and VRF table id; two tenants: 100 and 200 +# ("tX" means tenant X, e.g. t100, t200) +# a, b = the two host ids of the tenant +# HA, HB = addresses of host a, host b +# RLO1, RLO2 = rlo-X router loopback address on rt-1, rt-2 (tenant gateway +# for the output path, dual-stack) +# vrf-X = per-tenant VRF on each router (table X) +# +# Constants (same for every tenant): +# underlay = table 500; post-encap SID route lookup (via "lookup 500") +# localsid = table 90; holds the decap SIDs (End.DT46) +# fd00::/64 = underlay link between rt-1 and rt-2 +# veth-tX = cafe::254/10.0.0.254 (tenant gateway on veth, both routers) +# +# +# +-------------------+ +-------------------+ +# | | | | +# | hs-tX-a netns | | hs-tX-b netns | +# | | | | +# | +-------------+ | | +-------------+ | +# | | veth0 | | | | veth0 | | +# | | HA | | | | HB | | +# | +-------------+ | | +-------------+ | +# | . | | . | +# +-------------------+ +-------------------+ +# . . +# . . +# +-----------------------------------+ +-----------------------------------+ +# | . | | . | +# | +---------------+ | | +---------------+ | +# | | veth-tX | +----------+ | | +----------+ | veth-tX | | +# | | ::254/.254 | | localsid | | | | localsid | | ::254/.254 | | +# | +-------+-------+ +----------+ | | +----------+ +-------+-------+ | +# | | +----------+ | | +----------+ | | +# | +----+----+ | underlay | | | | underlay | +----+----+ | +# | | vrf-X | +----------+ | | +----------+ | vrf-X | | +# | +----+----+ | | +----+----+ | +# | | | | | | +# | +-----+----+ +------------+ | | +------------+ +----+-----+ | +# | | rlo-X | | veth0 | | | | veth0 | | rlo-X | | +# | | RLO1 | | fd00::1/64 |..|...|..| fd00::2/64 | | RLO2 | | +# | +----------+ +------------+ | | +------------+ +----------+ | +# | rt-1 netns | | rt-2 netns | +# +-----------------------------------+ +-----------------------------------+ +# +# +# Per-tenant instantiation: +# +-----+------+-------------------+-------------------+ +# | X | a, b | HA | HB | +# +-----+------+-------------------+-------------------+ +# | 100 | 1, 2 | cafe::1, 10.0.0.1 | cafe::2, 10.0.0.2 | +# | 200 | 3, 4 | cafe::3, 10.0.0.3 | cafe::4, 10.0.0.4 | +# +-----+------+-------------------+-------------------+ +# +# Router loopback (rlo-X) addresses, per tenant: +# +-----+-----------------------+-----------------------+ +# | X | RLO1 (rt-1) | RLO2 (rt-2) | +# +-----+-----------------------+-----------------------+ +# | 100 | cafe::101, 10.0.0.101 | cafe::102, 10.0.0.102 | +# | 200 | cafe::201, 10.0.0.201 | cafe::202, 10.0.0.202 | +# +-----+-----------------------+-----------------------+ +# +# +# Network configuration +# ===================== +# +# rt-1: localsid table (table 90) +# +--------+--------------------+----------------------------------+ +# | tenant | SID | Action | +# +--------+--------------------+----------------------------------+ +# | 100 | fc00:2:1:100::0d46 | apply SRv6 End.DT46 vrftable 100 | +# | 200 | fc00:2:1:200::0d46 | apply SRv6 End.DT46 vrftable 200 | +# +--------+--------------------+----------------------------------+ +# +# rt-1: underlay table (table 500) - post-encap SID route lookup +# +--------+--------------------+-------------------------------+ +# | tenant | SID | Action | +# +--------+--------------------+-------------------------------+ +# | 100 | fc00:1:2:100::0d46 | forward via fd00::2 dev veth0 | +# | 200 | fc00:1:2:200::0d46 | forward via fd00::2 dev veth0 | +# +--------+--------------------+-------------------------------+ +# +# rt-1: VRF tables (per tenant: vrf-X = table X) +# +--------+------------+------------------------------------------+ +# | tenant | dst | encap action | +# +--------+------------+------------------------------------------+ +# | 100 | cafe::2 | encap segs fc00:1:2:100::0d46 lookup 500 | +# | | 10.0.0.2 | | +# | | cafe::102 | | +# | | 10.0.0.102 | | +# | 200 | cafe::4 | encap segs fc00:1:2:200::0d46 lookup 500 | +# | | 10.0.0.4 | | +# | | cafe::202 | | +# | | 10.0.0.202 | | +# +--------+------------+------------------------------------------+ +# +# +# rt-2: localsid table (table 90) +# +--------+--------------------+----------------------------------+ +# | tenant | SID | Action | +# +--------+--------------------+----------------------------------+ +# | 100 | fc00:1:2:100::0d46 | apply SRv6 End.DT46 vrftable 100 | +# | 200 | fc00:1:2:200::0d46 | apply SRv6 End.DT46 vrftable 200 | +# +--------+--------------------+----------------------------------+ +# +# rt-2: underlay table (table 500) - post-encap SID route lookup +# +--------+--------------------+-------------------------------+ +# | tenant | SID | Action | +# +--------+--------------------+-------------------------------+ +# | 100 | fc00:2:1:100::0d46 | forward via fd00::1 dev veth0 | +# | 200 | fc00:2:1:200::0d46 | forward via fd00::1 dev veth0 | +# +--------+--------------------+-------------------------------+ +# +# rt-2: VRF tables (per tenant: vrf-X = table X) +# +--------+------------+------------------------------------------+ +# | tenant | dst | encap action | +# +--------+------------+------------------------------------------+ +# | 100 | cafe::1 | encap segs fc00:2:1:100::0d46 lookup 500 | +# | | 10.0.0.1 | | +# | | cafe::101 | | +# | | 10.0.0.101 | | +# | 200 | cafe::3 | encap segs fc00:2:1:200::0d46 lookup 500 | +# | | 10.0.0.3 | | +# | | cafe::201 | | +# | | 10.0.0.201 | | +# +--------+------------+------------------------------------------+ +# Within a tenant, a single SID reaches the adjacent router (its loopback) +# and the remote host connected to it, in both IPv4 and IPv6. +# +# For both rt-1 and rt-2, each VRF also has the connected host prefix (cafe::/64 +# or 10.0.0.0/24) and a default blackhole (IPv4 and IPv6). +# +# +# Locally originated traffic (output path) +# ======================================== +# +# The configuration above covers forwarded traffic, where packets arrive from +# a host and are encapsulated by the router. To also test router-originated +# traffic, each router pings the other router's loopback address through +# the VPN. +# +# Example (tenant 100), rt-1 pings cafe::102 (rt-2's loopback): +# 1. rt-1 looks up cafe::102 in vrf-100 and encapsulates it (SID +# fc00:1:2:100::0d46), then "lookup 500" finds the route for the SID in the +# underlay table (next hop fd00::2) and forwards it; +# 2. rt-2 decapsulates it (localsid, End.DT46) and delivers it locally +# (cafe::102 is on the rlo-100 interface); +# 3. rt-2 replies with destination cafe::101 (rt-1's loopback). rt-2 looks up +# cafe::101 in vrf-100 and encapsulates it back to rt-1 (again via "lookup +# 500"). rt-1 decapsulates it and delivers it. + +# shellcheck source=lib.sh +source lib.sh + +readonly LOCALSID_TABLE_ID=90 +readonly UNDERLAY_TABLE_ID=500 +readonly IPv6_RT_NETWORK=fd00 +readonly IPv6_HS_NETWORK=cafe +readonly IPv4_HS_NETWORK=10.0.0 +readonly VPN_LOCATOR_SERVICE=fc00 +readonly DT46_FUNC=0d46 +readonly DUMMY_DEVNAME=dum0 +readonly IPv6_TESTS_ADDR=2001:db8::1 +readonly TESTS_TABLE_ID=54321 +PING_TIMEOUT_SEC=4 + +SETUP_ERR=1 + +ret=${ksft_skip} +nsuccess=0 +nfail=0 + +PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no} + +log_test() +{ + local rc="$1" + local expected="$2" + local msg="$3" + + if [ "${rc}" -eq "${expected}" ]; then + nsuccess=$((nsuccess+1)) + printf "\n TEST: %-60s [ OK ]\n" "${msg}" + else + ret=1 + nfail=$((nfail+1)) + printf "\n TEST: %-60s [FAIL]\n" "${msg}" + if [ "${PAUSE_ON_FAIL}" = "yes" ]; then + echo + echo "hit enter to continue, 'q' to quit" + read -r a + [ "$a" = "q" ] && exit 1 + fi + fi +} + +print_log_test_results() +{ + printf "\nTests passed: %3d\n" "${nsuccess}" + printf "Tests failed: %3d\n" "${nfail}" + + # when a test fails, the value of 'ret' is set to 1 (error code). + # Conversely, when all tests are passed successfully, the 'ret' value + # is set to 0 (success code). + if [ "${ret}" -ne 1 ]; then + ret=0 + fi +} + +log_section() +{ + echo + echo "################################################################################" + echo "TEST SECTION: $*" + echo "################################################################################" +} + +get_rtname() +{ + local rtid="$1" + + echo "rt_${rtid}" +} + +get_rt_nsname() +{ + local rtid="$1" + local varname + + varname="$(get_rtname "${rtid}")" + echo "${!varname}" +} + +get_hsname() +{ + local tid="$1" + local hsid="$2" + + echo "hs_t${tid}_${hsid}" +} + +get_hs_nsname() +{ + local tid="$1" + local hsid="$2" + local varname + + varname="$(get_hsname "${tid}" "${hsid}")" + echo "${!varname}" +} + +cleanup() +{ + ip link del veth-rt-1 2>/dev/null || true + ip link del veth-rt-2 2>/dev/null || true + + cleanup_all_ns + + # check whether the setup phase was completed successfully or not. In + # case of an error during the setup phase of the testing environment, + # the selftest is considered as "skipped". + if [ "${SETUP_ERR}" -ne 0 ]; then + echo "SKIP: Setting up the testing environment failed" + exit "${ksft_skip}" + fi + + exit "${ret}" +} + +# Host id of the router loopback (rlo) for a (router, tenant) pair. +# E.g. rt-1/tenant 100 -> 101, rt-2/tenant 200 -> 202. +get_rlo_hostid() +{ + local rtid="$1" + local tid="$2" + + echo "$((tid + rtid))" +} + +build_vpn_sid() +{ + local rtsrc="$1" + local rtdst="$2" + local tid="$3" + + echo "${VPN_LOCATOR_SERVICE}:${rtsrc}:${rtdst}:${tid}::${DT46_FUNC}" +} + +# Install a dual-stack (IPv6 and IPv4) encap route in a VRF on the given +# router. +# args: +# $1 - router id +# $2 - host part of the IPv6 destination +# $3 - host part of the IPv4 destination +# $4 - SRv6 SID used as the encap destination +# $5 - tenant id +# $6 - if "true", add the "lookup" attribute to the encap route +__set_encap_route() +{ + local rt="$1" + local dst6="$2" + local dst4="$3" + local sid="$4" + local tid="$5" + local use_lookup="$6" + local lookup='' + local rtname + + rtname="$(get_rt_nsname "${rt}")" + + if [ "${use_lookup}" = "true" ]; then + lookup="lookup ${UNDERLAY_TABLE_ID}" + fi + + # shellcheck disable=SC2086 + ip -netns "${rtname}" -6 route replace \ + "${IPv6_HS_NETWORK}::${dst6}/128" vrf "vrf-${tid}" \ + encap seg6 mode encap segs "${sid}" ${lookup} dev veth0 + + # shellcheck disable=SC2086 + ip -netns "${rtname}" -4 route replace \ + "${IPv4_HS_NETWORK}.${dst4}/32" vrf "vrf-${tid}" \ + encap seg6 mode encap segs "${sid}" ${lookup} dev veth0 +} + +# Install the dual-stack encap route for a tenant host on rt, with the +# "lookup" attribute so the first SID is looked up in the underlay table. +# args: +# $1 - router id where the encap route is installed +# $2 - host destination id (host part of cafe::/128 and 10.0.0./32) +# $3 - SRv6 SID used as the encap destination +# $4 - tenant id +set_host_encap_route() +{ + local rt="$1" + local hsdst="$2" + local sid="$3" + local tid="$4" + + __set_encap_route "${rt}" "${hsdst}" "${hsdst}" "${sid}" "${tid}" true +} + +set_host_encap_route_nolookup() +{ + local rt="$1" + local hsdst="$2" + local sid="$3" + local tid="$4" + + __set_encap_route "${rt}" "${hsdst}" "${hsdst}" "${sid}" "${tid}" false +} + +# Install the dual-stack encap route on rtsrc toward rtdst's rlo loopback +# (RLO1 or RLO2, see header), with the "lookup" attribute so the first +# SID is looked up in the underlay table. +# args: +# $1 - router id where the encap route is installed +# $2 - router id whose loopback address is the route destination +# $3 - SRv6 SID used as the encap destination +# $4 - tenant id +set_gw_encap_route() +{ + local rtsrc="$1" + local rtdst="$2" + local sid="$3" + local tid="$4" + local dst + + dst="$(get_rlo_hostid "${rtdst}" "${tid}")" + + __set_encap_route "${rtsrc}" "${dst}" "${dst}" "${sid}" "${tid}" true +} + +set_gw_encap_route_nolookup() +{ + local rtsrc="$1" + local rtdst="$2" + local sid="$3" + local tid="$4" + local dst + + dst="$(get_rlo_hostid "${rtdst}" "${tid}")" + + __set_encap_route "${rtsrc}" "${dst}" "${dst}" "${sid}" "${tid}" false +} + +# Setup the basic networking for a router +setup_rt_networking() +{ + local id="$1" + local nsname + + nsname="$(get_rt_nsname "${id}")" + + ip link set "veth-rt-${id}" netns "${nsname}" + ip -netns "${nsname}" link set "veth-rt-${id}" name veth0 + + ip netns exec "${nsname}" sysctl -wq net.ipv6.conf.all.accept_dad=0 + ip netns exec "${nsname}" sysctl -wq net.ipv6.conf.default.accept_dad=0 + + ip -netns "${nsname}" addr add "${IPv6_RT_NETWORK}::${id}/64" dev veth0 nodad + ip -netns "${nsname}" link set veth0 up + + ip netns exec "${nsname}" sysctl -wq net.ipv4.ip_forward=1 + ip netns exec "${nsname}" sysctl -wq net.ipv6.conf.all.forwarding=1 +} + +# Setup a host namespace and attach it to its gateway +setup_hs() +{ + local hid="$1" + local rid="$2" + local tid="$3" + local rtveth="veth-t${tid}" + local hsname + local rtname + + hsname="$(get_hs_nsname "${tid}" "${hid}")" + rtname="$(get_rt_nsname "${rid}")" + + ip netns exec "${hsname}" sysctl -wq net.ipv6.conf.all.accept_dad=0 + ip netns exec "${hsname}" sysctl -wq net.ipv6.conf.default.accept_dad=0 + + ip -netns "${hsname}" link add veth0 type veth peer name "${rtveth}" + ip -netns "${hsname}" link set "${rtveth}" netns "${rtname}" + + ip -netns "${hsname}" addr add \ + "${IPv6_HS_NETWORK}::${hid}/64" dev veth0 nodad + ip -netns "${hsname}" addr add \ + "${IPv4_HS_NETWORK}.${hid}/24" dev veth0 + + ip -netns "${hsname}" link set veth0 up +} + +# Setup the per-tenant VRF on a router (gateway, loopback, blackhole) +setup_rt() +{ + local rid="$1" + local tid="$2" + local rtveth="veth-t${tid}" + local rlo_dev="rlo-${tid}" + local rtname + local gw_addr_v6 + local gw_addr_v4 + + rtname="$(get_rt_nsname "${rid}")" + + gw_addr_v6="${IPv6_HS_NETWORK}::$(get_rlo_hostid "${rid}" "${tid}")" + gw_addr_v4="${IPv4_HS_NETWORK}.$(get_rlo_hostid "${rid}" "${tid}")" + + ip -netns "${rtname}" link add "vrf-${tid}" type vrf table "${tid}" + ip -netns "${rtname}" link set "vrf-${tid}" up + + ip -netns "${rtname}" link set "${rtveth}" master "vrf-${tid}" + + ip -netns "${rtname}" addr add \ + "${IPv6_HS_NETWORK}::254/64" dev "${rtveth}" nodad + ip -netns "${rtname}" addr add \ + "${IPv4_HS_NETWORK}.254/24" dev "${rtveth}" + + ip -netns "${rtname}" link set "${rtveth}" up + + ip netns exec "${rtname}" \ + sysctl -wq "net.ipv6.conf.${rtveth}.proxy_ndp=1" + ip netns exec "${rtname}" \ + sysctl -wq "net.ipv4.conf.${rtveth}.proxy_arp=1" + + ip netns exec "${rtname}" sh -c "echo 1 > /proc/sys/net/vrf/strict_mode" + + # router loopback interface for locally originated traffic + ip -netns "${rtname}" link add "${rlo_dev}" type dummy + ip -netns "${rtname}" link set "${rlo_dev}" master "vrf-${tid}" + + ip -netns "${rtname}" addr add "${gw_addr_v6}/128" \ + dev "${rlo_dev}" nodad + ip -netns "${rtname}" addr add "${gw_addr_v4}/32" \ + dev "${rlo_dev}" + + ip -netns "${rtname}" link set "${rlo_dev}" up + + # default blackhole routes in the VRF: any traffic that does not match + # a specific route is dropped. Without the "lookup" attribute on the + # encap route, the route for the first SID cannot be found from within + # the VRF. + ip -netns "${rtname}" -6 route add blackhole default metric 4278198272 \ + vrf "vrf-${tid}" + ip -netns "${rtname}" -4 route add blackhole default metric 4278198272 \ + vrf "vrf-${tid}" +} + +# Configure a one-way VPN path towards hsdst (on rtdst) for tenant tid. +# The encap side is set up on rtsrc and the decap side on rtdst. +# args: +# $1 - router id where the encap side is set up +# $2 - host id of the destination host +# $3 - router id of the destination router (connected to the destination host) +# $4 - tenant id +setup_vpn_config() +{ + local rtsrc="$1" + local hsdst="$2" + local rtdst="$3" + local tid="$4" + local rtveth="veth-t${tid}" + local rtsrc_name + local rtdst_name + local vpn_sid + + rtsrc_name="$(get_rt_nsname "${rtsrc}")" + rtdst_name="$(get_rt_nsname "${rtdst}")" + vpn_sid="$(build_vpn_sid "${rtsrc}" "${rtdst}" "${tid}")" + + ip -netns "${rtsrc_name}" -6 neigh add proxy \ + "${IPv6_HS_NETWORK}::${hsdst}" dev "${rtveth}" + set_host_encap_route "${rtsrc}" "${hsdst}" "${vpn_sid}" "${tid}" + + ip -netns "${rtsrc_name}" -6 route add "${vpn_sid}/128" \ + table "${UNDERLAY_TABLE_ID}" \ + via "fd00::${rtdst}" dev veth0 + + # set the decap route for decapsulating packets arriving from rtsrc + # and destined to hsdst + ip -netns "${rtdst_name}" -6 route add "${vpn_sid}/128" \ + table "${LOCALSID_TABLE_ID}" \ + encap seg6local action End.DT46 \ + vrftable "${tid}" dev "vrf-${tid}" + + # all SIDs for VPNs start with a common locator which is fc00::/16. + # Routes for handling the SRv6 End.DT* behavior instances are grouped + # together in the 'localsid' table. + # + # NOTE: added only once + if ! ip -netns "${rtdst_name}" -6 rule show | \ + grep -q "to ${VPN_LOCATOR_SERVICE}::/16 lookup ${LOCALSID_TABLE_ID}"; then + ip -netns "${rtdst_name}" -6 rule add \ + to "${VPN_LOCATOR_SERVICE}::/16" \ + lookup "${LOCALSID_TABLE_ID}" prio 999 + fi +} + +# Configure rtsrc to reach rtdst's loopback address through the VPN. +# args: +# $1 - router id where the encap route is installed +# $2 - router id whose loopback is the destination +# $3 - tenant id +setup_vpn_gw_encap() +{ + local rtsrc="$1" + local rtdst="$2" + local tid="$3" + local sid + + sid="$(build_vpn_sid "${rtsrc}" "${rtdst}" "${tid}")" + + set_gw_encap_route "${rtsrc}" "${rtdst}" "${sid}" "${tid}" +} + +setup() +{ + ip link add veth-rt-1 type veth peer name veth-rt-2 + setup_ns rt_1 rt_2 + setup_rt_networking 1 + setup_rt_networking 2 + + # setup two hosts for the tenant 100. + # - host hs-t100-1 is directly connected to the router rt-1; + # - host hs-t100-2 is directly connected to the router rt-2. + setup_ns hs_t100_1 hs_t100_2 + setup_hs 1 1 100 + setup_hs 2 2 100 + + # setup two hosts for the tenant 200. + # - host hs-t200-3 is directly connected to the router rt-1; + # - host hs-t200-4 is directly connected to the router rt-2. + setup_ns hs_t200_3 hs_t200_4 + setup_hs 3 1 200 + setup_hs 4 2 200 + + # configure each router for each tenant: VRF, blackhole routes, + # router loopback interface + setup_rt 1 100 + setup_rt 2 100 + setup_rt 1 200 + setup_rt 2 200 + + # setup the L3 VPN which connects the host hs-t100-1 and host hs-t100-2 + # within the same tenant 100. + setup_vpn_config 1 2 2 100 + setup_vpn_config 2 1 1 100 + + # setup the L3 VPN which connects the host hs-t200-3 and host hs-t200-4 + # within the same tenant 200. + setup_vpn_config 1 4 2 200 + setup_vpn_config 2 3 1 200 + + # allow each router to reach the other's loopback through the VPN + setup_vpn_gw_encap 2 1 100 + setup_vpn_gw_encap 1 2 100 + setup_vpn_gw_encap 2 1 200 + setup_vpn_gw_encap 1 2 200 + + # testing environment was set up successfully + SETUP_ERR=0 +} + +check_rt_connectivity() +{ + local rtsrc="$1" + local rtdst="$2" + local nsname + + nsname="$(get_rt_nsname "${rtsrc}")" + + ip netns exec "${nsname}" ping -c 1 -W 1 "${IPv6_RT_NETWORK}::${rtdst}" \ + >/dev/null 2>&1 +} + +check_and_log_rt_connectivity() +{ + local rtsrc="$1" + local rtdst="$2" + + check_rt_connectivity "${rtsrc}" "${rtdst}" + log_test $? 0 "Routers connectivity: rt-${rtsrc} -> rt-${rtdst}" +} + +check_hs_ipv6_connectivity() +{ + local hssrc="$1" + local hsdst="$2" + local tid="$3" + local nsname + + nsname="$(get_hs_nsname "${tid}" "${hssrc}")" + + ip netns exec "${nsname}" ping -c 1 -W "${PING_TIMEOUT_SEC}" \ + "${IPv6_HS_NETWORK}::${hsdst}" >/dev/null 2>&1 +} + +check_hs_ipv4_connectivity() +{ + local hssrc="$1" + local hsdst="$2" + local tid="$3" + local nsname + + nsname="$(get_hs_nsname "${tid}" "${hssrc}")" + + ip netns exec "${nsname}" ping -c 1 -W "${PING_TIMEOUT_SEC}" \ + "${IPv4_HS_NETWORK}.${hsdst}" >/dev/null 2>&1 +} + +check_and_log_hs_connectivity() +{ + local hssrc="$1" + local hsdst="$2" + local tid="$3" + + check_hs_ipv6_connectivity "${hssrc}" "${hsdst}" "${tid}" + log_test $? 0 "IPv6 connectivity: hs-t${tid}-${hssrc} -> hs-t${tid}-${hsdst} (tenant ${tid})" + + check_hs_ipv4_connectivity "${hssrc}" "${hsdst}" "${tid}" + log_test $? 0 "IPv4 connectivity: hs-t${tid}-${hssrc} -> hs-t${tid}-${hsdst} (tenant ${tid})" +} + +check_and_log_hs_isolation() +{ + local hssrc="$1" + local tidsrc="$2" + local hsdst="$3" + local tiddst="$4" + + check_hs_ipv6_connectivity "${hssrc}" "${hsdst}" "${tidsrc}" + log_test $? 1 "IPv6 isolation: hs-t${tidsrc}-${hssrc} -X-> hs-t${tiddst}-${hsdst}" + + check_hs_ipv4_connectivity "${hssrc}" "${hsdst}" "${tidsrc}" + log_test $? 1 "IPv4 isolation: hs-t${tidsrc}-${hssrc} -X-> hs-t${tiddst}-${hsdst}" +} + +check_and_log_hs2gw_connectivity() +{ + local hssrc="$1" + local tid="$2" + + check_hs_ipv6_connectivity "${hssrc}" 254 "${tid}" + log_test $? 0 "IPv6 connectivity: hs-t${tid}-${hssrc} -> gw (tenant ${tid})" + + check_hs_ipv4_connectivity "${hssrc}" 254 "${tid}" + log_test $? 0 "IPv4 connectivity: hs-t${tid}-${hssrc} -> gw (tenant ${tid})" +} + +router_tests() +{ + log_section "IPv6 routers connectivity test" + + check_and_log_rt_connectivity 1 2 + check_and_log_rt_connectivity 2 1 +} + +host2gateway_tests() +{ + log_section "Connectivity test among hosts and gateway" + + check_and_log_hs2gw_connectivity 1 100 + check_and_log_hs2gw_connectivity 2 100 + + check_and_log_hs2gw_connectivity 3 200 + check_and_log_hs2gw_connectivity 4 200 +} + +host_vpn_tests() +{ + log_section "SRv6 VPN connectivity test among hosts in the same tenant" + + check_and_log_hs_connectivity 1 2 100 + check_and_log_hs_connectivity 2 1 100 + + check_and_log_hs_connectivity 3 4 200 + check_and_log_hs_connectivity 4 3 200 +} + +host_vpn_isolation_tests() +{ + local l1="1 2" + local l2="3 4" + local t1=100 + local t2=200 + local i + local j + local tmp + + log_section "SRv6 VPN isolation test among hosts in different tenants" + + for _ in 0 1; do + for i in ${l1}; do + for j in ${l2}; do + check_and_log_hs_isolation "${i}" "${t1}" "${j}" "${t2}" + done + done + + # let us test the reverse path + tmp="${l1}"; l1="${l2}"; l2="${tmp}" + tmp=${t1}; t1=${t2}; t2=${tmp} + done +} + +__test_nolookup() +{ + local hssrc="$1" + local hsdst="$2" + local rtsrc="$3" + local rtdst="$4" + local tid="$5" + local vpn_sid + + vpn_sid="$(build_vpn_sid "${rtsrc}" "${rtdst}" "${tid}")" + + # replace encap route(s) without "lookup" attribute + set_host_encap_route_nolookup "${rtsrc}" "${hsdst}" "${vpn_sid}" "${tid}" + + check_hs_ipv6_connectivity "${hssrc}" "${hsdst}" "${tid}" + log_test $? 1 "IPv6 w/o lookup: hs-t${tid}-${hssrc} -X-> hs-t${tid}-${hsdst} (tenant ${tid})" + + check_hs_ipv4_connectivity "${hssrc}" "${hsdst}" "${tid}" + log_test $? 1 "IPv4 w/o lookup: hs-t${tid}-${hssrc} -X-> hs-t${tid}-${hsdst} (tenant ${tid})" + + # restore encap route(s) with "lookup" for subsequent tests + set_host_encap_route "${rtsrc}" "${hsdst}" "${vpn_sid}" "${tid}" +} + +host_vpn_nolookup_tests() +{ + log_section "SRv6 VPN connectivity test among hosts w/o lookup" + + __test_nolookup 1 2 1 2 100 + __test_nolookup 2 1 2 1 100 + + __test_nolookup 3 4 1 2 200 + __test_nolookup 4 3 2 1 200 +} + +check_gw_ipv6_connectivity() +{ + local rtsrc="$1" + local rtdst="$2" + local tidsrc="$3" + local tiddst="$4" + local rtname + local src_v6 + local dst_v6 + + rtname="$(get_rt_nsname "${rtsrc}")" + src_v6="${IPv6_HS_NETWORK}::$(get_rlo_hostid "${rtsrc}" "${tidsrc}")" + dst_v6="${IPv6_HS_NETWORK}::$(get_rlo_hostid "${rtdst}" "${tiddst}")" + + ip netns exec "${rtname}" ip vrf exec "vrf-${tidsrc}" \ + ping -c 1 -W "${PING_TIMEOUT_SEC}" \ + -I "${src_v6}" "${dst_v6}" >/dev/null 2>&1 +} + +check_gw_ipv4_connectivity() +{ + local rtsrc="$1" + local rtdst="$2" + local tidsrc="$3" + local tiddst="$4" + local rtname + local src_v4 + local dst_v4 + + rtname="$(get_rt_nsname "${rtsrc}")" + src_v4="${IPv4_HS_NETWORK}.$(get_rlo_hostid "${rtsrc}" "${tidsrc}")" + dst_v4="${IPv4_HS_NETWORK}.$(get_rlo_hostid "${rtdst}" "${tiddst}")" + + ip netns exec "${rtname}" ip vrf exec "vrf-${tidsrc}" \ + ping -c 1 -W "${PING_TIMEOUT_SEC}" \ + -I "${src_v4}" "${dst_v4}" >/dev/null 2>&1 +} + +check_and_log_gw_connectivity() +{ + local rtsrc="$1" + local rtdst="$2" + local tid="$3" + + check_gw_ipv6_connectivity "${rtsrc}" "${rtdst}" "${tid}" "${tid}" + log_test $? 0 "IPv6 connectivity: rt-${rtsrc} -> rt-${rtdst} (tenant ${tid})" + + check_gw_ipv4_connectivity "${rtsrc}" "${rtdst}" "${tid}" "${tid}" + log_test $? 0 "IPv4 connectivity: rt-${rtsrc} -> rt-${rtdst} (tenant ${tid})" +} + +check_and_log_gw_isolation() +{ + local rtsrc="$1" + local rtdst="$2" + local tidsrc="$3" + local tiddst="$4" + + check_gw_ipv6_connectivity "${rtsrc}" "${rtdst}" "${tidsrc}" "${tiddst}" + log_test $? 1 "IPv6 isolation: rt-${rtsrc} -X-> rt-${rtdst} (tenants ${tidsrc}/${tiddst})" + + check_gw_ipv4_connectivity "${rtsrc}" "${rtdst}" "${tidsrc}" "${tiddst}" + log_test $? 1 "IPv4 isolation: rt-${rtsrc} -X-> rt-${rtdst} (tenants ${tidsrc}/${tiddst})" +} + +gw_vpn_isolation_tests() +{ + log_section "SRv6 VPN isolation test among routers in different tenants" + + check_and_log_gw_isolation 1 2 100 200 + check_and_log_gw_isolation 2 1 100 200 + + check_and_log_gw_isolation 1 2 200 100 + check_and_log_gw_isolation 2 1 200 100 +} + +gw_vpn_tests() +{ + log_section "SRv6 VPN connectivity test among routers in the same tenant" + + check_and_log_gw_connectivity 1 2 100 + check_and_log_gw_connectivity 2 1 100 + + check_and_log_gw_connectivity 1 2 200 + check_and_log_gw_connectivity 2 1 200 +} + +__test_gw_nolookup() +{ + local rtsrc="$1" + local rtdst="$2" + local tid="$3" + local sid + + sid="$(build_vpn_sid "${rtsrc}" "${rtdst}" "${tid}")" + + # replace gw encap route without "lookup" attribute + set_gw_encap_route_nolookup "${rtsrc}" "${rtdst}" "${sid}" "${tid}" + + check_gw_ipv6_connectivity "${rtsrc}" "${rtdst}" "${tid}" "${tid}" + log_test $? 1 "IPv6 w/o lookup: rt-${rtsrc} -X-> rt-${rtdst} (tenant ${tid})" + + check_gw_ipv4_connectivity "${rtsrc}" "${rtdst}" "${tid}" "${tid}" + log_test $? 1 "IPv4 w/o lookup: rt-${rtsrc} -X-> rt-${rtdst} (tenant ${tid})" + + # restore gw encap route with "lookup" for subsequent tests + set_gw_encap_route "${rtsrc}" "${rtdst}" "${sid}" "${tid}" +} + +gw_vpn_nolookup_tests() +{ + log_section "SRv6 VPN connectivity test among routers w/o lookup" + + __test_gw_nolookup 1 2 100 + __test_gw_nolookup 2 1 100 + + __test_gw_nolookup 1 2 200 + __test_gw_nolookup 2 1 200 +} + +test_command_or_ksft_skip() +{ + local cmd="$1" + + if [ ! -x "$(command -v "${cmd}")" ]; then + echo "SKIP: Could not run test without \"${cmd}\" tool" + exit "${ksft_skip}" + fi +} + +test_vrf_or_ksft_skip() +{ + modprobe vrf &>/dev/null || true + if [ ! -e /proc/sys/net/vrf/strict_mode ]; then + echo "SKIP: vrf sysctl does not exist" + exit "${ksft_skip}" + fi +} + +test_dummy_dev_or_ksft_skip() +{ + local test_netns + + setup_ns test_netns + + modprobe dummy &>/dev/null || true + if ! ip -netns "${test_netns}" link add "${DUMMY_DEVNAME}" \ + type dummy; then + cleanup_ns "${test_netns}" + echo "SKIP: dummy dev not supported" + exit "${ksft_skip}" + fi + + cleanup_ns "${test_netns}" +} + +test_encap_lookup_supp_or_ksft_skip() +{ + local nsname + + setup_ns nsname + + ip -netns "${nsname}" link add "${DUMMY_DEVNAME}" type dummy + ip -netns "${nsname}" link set "${DUMMY_DEVNAME}" up + + if ! ip -netns "${nsname}" -6 route add "${IPv6_TESTS_ADDR}/128" \ + encap seg6 mode encap segs fc00::1 \ + lookup "${TESTS_TABLE_ID}" \ + dev "${DUMMY_DEVNAME}" 2>/dev/null; then + cleanup_ns "${nsname}" + echo "SKIP: seg6 encap lookup attribute not supported" + exit "${ksft_skip}" + fi + + # An old kernel with a recent iproute2 accepts the route but + # silently ignores the lookup attribute. Dump the route and check + # the attribute is really there, otherwise the test falsely passes. + if ! ip -netns "${nsname}" -6 route show "${IPv6_TESTS_ADDR}/128" | \ + grep -q "lookup ${TESTS_TABLE_ID}"; then + cleanup_ns "${nsname}" + echo "SKIP: seg6 encap lookup attribute not supported" + exit "${ksft_skip}" + fi + + cleanup_ns "${nsname}" +} + +if [ "$(id -u)" -ne 0 ]; then + echo "SKIP: Need root privileges" + exit "${ksft_skip}" +fi + +# required programs to carry out this selftest +test_command_or_ksft_skip ip +test_command_or_ksft_skip ping +test_command_or_ksft_skip sysctl +test_command_or_ksft_skip grep + +test_dummy_dev_or_ksft_skip +test_vrf_or_ksft_skip +test_encap_lookup_supp_or_ksft_skip + +set -e +trap cleanup EXIT + +setup +set +e + +router_tests +host2gateway_tests +host_vpn_tests +host_vpn_isolation_tests +host_vpn_nolookup_tests +gw_vpn_tests +gw_vpn_isolation_tests +gw_vpn_nolookup_tests + +print_log_test_results From f5cfb576ce39ba5647024c6d6eeb5b7a822fab6e Mon Sep 17 00:00:00 2001 From: Jiawen Wu Date: Mon, 13 Jul 2026 14:04:41 +0800 Subject: [PATCH 0483/1433] net: libwx: disable TX VLAN offload for packets with >2 VLAN tags The current hardware does not support TX VLAN offload for packets with three or more VLAN tags. When such packets are transmitted with hardware VLAN offload enabled, the hardware may malfunction or produce corrupted frames. Add a check in wx_features_check() to parse the VLAN depth of the skb. If more than two VLAN tags are detected (including both the hardware tag and in-band tags), strip NETIF_F_HW_VLAN_CTAG_TX and NETIF_F_HW_VLAN_STAG_TX from the feature set. This forces the kernel networking stack to handle VLAN insertion in software for these specific packets, ensuring correct transmission. Signed-off-by: Jiawen Wu Link: https://patch.msgid.link/069DF89AA8029189+20260713060441.276612-1-jiawenwu@trustnetic.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/wangxun/libwx/wx_lib.c | 24 +++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.c b/drivers/net/ethernet/wangxun/libwx/wx_lib.c index 29e2d2164c15..31572f59b9ce 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_lib.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.c @@ -3228,6 +3228,30 @@ netdev_features_t wx_features_check(struct sk_buff *skb, netdev_features_t features) { struct wx *wx = netdev_priv(netdev); + __be16 type = skb->protocol; + u16 vlan_depth = ETH_HLEN; + u32 vlan_num = 0; + + if (skb_vlan_tag_present(skb)) + vlan_num++; + + while (eth_type_vlan(type)) { + struct vlan_hdr vhdr, *vh; + + vh = skb_header_pointer(skb, vlan_depth, sizeof(vhdr), &vhdr); + if (unlikely(!vh)) + break; + + type = vh->h_vlan_encapsulated_proto; + vlan_depth += VLAN_HLEN; + vlan_num++; + + if (vlan_num > 2) { + features &= ~(NETIF_F_HW_VLAN_CTAG_TX | + NETIF_F_HW_VLAN_STAG_TX); + break; + } + } if (!skb->encapsulation) return features; From be72d6aecea491ee202de81fc3a71c7ea9d34d2b Mon Sep 17 00:00:00 2001 From: Tamizh Chelvam Raja Date: Wed, 1 Jul 2026 23:54:28 +0530 Subject: [PATCH 0484/1433] wifi: ath12k: Set IEEE80211_OFFLOAD_ENCAP_4ADDR after tx_encap_type vdev param Currently, IEEE80211_OFFLOAD_ENCAP_4ADDR is set when IEEE80211_OFFLOAD_ENCAP_ENABLED is present in vif->offload_flags at the beginning of ath12k_mac_update_vif_offload(). However, if the WMI vdev set_param for tx_encap_type fails, IEEE80211_OFFLOAD_ENCAP_ENABLED is cleared but IEEE80211_OFFLOAD_ENCAP_4ADDR remains set, leaving the flags in an inconsistent state. Fix this by setting IEEE80211_OFFLOAD_ENCAP_4ADDR only after the tx_encap_type has been configured via the WMI vdev set parameter. Compile tested only. Fixes: 729cad3c3c9e ("wifi: ath12k: Add 4-address mode support for eth offload") Signed-off-by: Tamizh Chelvam Raja Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260701182428.906441-1-tamizh.raja@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 6667019a0049..d7e93a20ca36 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -10118,16 +10118,15 @@ static void ath12k_mac_update_vif_offload(struct ath12k_link_vif *arvif) vif->type != NL80211_IFTYPE_AP) vif->offload_flags &= ~(IEEE80211_OFFLOAD_ENCAP_ENABLED | IEEE80211_OFFLOAD_DECAP_ENABLED | - IEEE80211_OFFLOAD_ENCAP_MCAST); + IEEE80211_OFFLOAD_ENCAP_MCAST | + IEEE80211_OFFLOAD_ENCAP_4ADDR); - if (vif->offload_flags & IEEE80211_OFFLOAD_ENCAP_ENABLED) { + if (vif->offload_flags & IEEE80211_OFFLOAD_ENCAP_ENABLED) ahvif->dp_vif.tx_encap_type = ATH12K_HW_TXRX_ETHERNET; - vif->offload_flags |= IEEE80211_OFFLOAD_ENCAP_4ADDR; - } else if (test_bit(ATH12K_FLAG_RAW_MODE, &ab->dev_flags)) { + else if (test_bit(ATH12K_FLAG_RAW_MODE, &ab->dev_flags)) ahvif->dp_vif.tx_encap_type = ATH12K_HW_TXRX_RAW; - } else { + else ahvif->dp_vif.tx_encap_type = ATH12K_HW_TXRX_NATIVE_WIFI; - } ret = ath12k_wmi_vdev_set_param_cmd(ar, arvif->vdev_id, param_id, ahvif->dp_vif.tx_encap_type); @@ -10138,7 +10137,8 @@ static void ath12k_mac_update_vif_offload(struct ath12k_link_vif *arvif) } if (vif->offload_flags & IEEE80211_OFFLOAD_ENCAP_ENABLED) - vif->offload_flags |= IEEE80211_OFFLOAD_ENCAP_MCAST; + vif->offload_flags |= (IEEE80211_OFFLOAD_ENCAP_MCAST | + IEEE80211_OFFLOAD_ENCAP_4ADDR); param_id = WMI_VDEV_PARAM_RX_DECAP_TYPE; if (vif->offload_flags & IEEE80211_OFFLOAD_DECAP_ENABLED) From f78210dd4bea09090b828adc2733cad4b9916306 Mon Sep 17 00:00:00 2001 From: Manikanta Pubbisetty Date: Fri, 10 Jul 2026 11:34:06 +0530 Subject: [PATCH 0485/1433] wifi: ath10k: trigger hardware recovery upon rx failures When an error occurs during RX packet processing (e.g., MSDU done failure), the driver sets rx_confused and drops all subsequent RX packets until a Wi-Fi ON/OFF cycle clears the flag. This can leave the device in a bad state where it cannot process RX data traffic. Instead of leaving the device in such a state, trigger hardware recovery so that such an error state can be reset and the device can function again normally. Tested-on: WCN3990 hw1.0 WLAN.HL.3.2.2.c10-00754-QCAHLSWMTPL-1 Tested-on: QCA6174 hw3.2 PCI WLAN.RM.4.4.1-00288-QCARMSWPZ-1 Tested-on: QCA6174 hw3.2 SDIO WLAN.RMH.4.4.1-00189 Signed-off-by: Manikanta Pubbisetty Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260710060406.3323260-1-manikanta.pubbisetty@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath10k/htt_rx.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/ath/ath10k/htt_rx.c b/drivers/net/wireless/ath/ath10k/htt_rx.c index faac359aa9ac..1005daaaf158 100644 --- a/drivers/net/wireless/ath/ath10k/htt_rx.c +++ b/drivers/net/wireless/ath/ath10k/htt_rx.c @@ -2343,10 +2343,8 @@ static int ath10k_htt_rx_handle_amsdu(struct ath10k_htt *htt) if (ret < 0) { ath10k_warn(ar, "rx ring became corrupted: %d\n", ret); __skb_queue_purge(&amsdu); - /* FIXME: It's probably a good idea to reboot the - * device instead of leaving it inoperable. - */ htt->rx_confused = true; + ath10k_core_start_recovery(ar); return ret; } @@ -3311,6 +3309,7 @@ static int ath10k_htt_rx_in_ord_ind(struct ath10k *ar, struct sk_buff *skb) if (ret < 0) { ath10k_warn(ar, "failed to pop paddr list: %d\n", ret); htt->rx_confused = true; + ath10k_core_start_recovery(ar); return -EIO; } @@ -3344,6 +3343,7 @@ static int ath10k_htt_rx_in_ord_ind(struct ath10k *ar, struct sk_buff *skb) ath10k_warn(ar, "failed to extract amsdu: %d\n", ret); htt->rx_confused = true; __skb_queue_purge(&list); + ath10k_core_start_recovery(ar); return -EIO; } } From f8729a40ebcc94d164be8f154483a21669f3ab52 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 09:10:40 +0800 Subject: [PATCH 0486/1433] wifi: ath11k: validate regulatory capability phy_id ath11k_wmi_tlv_ext_hal_reg_caps() copies firmware regulatory capability records into soc->hal_reg_cap[] using reg_cap.phy_id as the destination index. The loop count is bounded by num_phy, but the phy_id embedded in each record is not checked against the fixed MAX_RADIOS-sized destination array. Reject firmware records whose phy_id does not fit soc->hal_reg_cap[] before copying the parsed capability. Signed-off-by: Pengpeng Hou Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260704011040.26233-1-pengpeng@iscas.ac.cn Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/wmi.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c index dca6e011cc40..28acfc6829df 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.c +++ b/drivers/net/wireless/ath/ath11k/wmi.c @@ -4858,6 +4858,12 @@ static int ath11k_wmi_tlv_ext_hal_reg_caps(struct ath11k_base *soc, return ret; } + if (reg_cap.phy_id >= ARRAY_SIZE(soc->hal_reg_cap)) { + ath11k_warn(soc, "invalid reg cap phy_id %u\n", + reg_cap.phy_id); + return -EINVAL; + } + memcpy(&soc->hal_reg_cap[reg_cap.phy_id], ®_cap, sizeof(reg_cap)); } From ff651212f2e237d790a5b36deef9d332360dc272 Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Sat, 4 Jul 2026 15:30:00 +0800 Subject: [PATCH 0487/1433] wifi: ath12k: fix ML-STA authentication timeout on QCC2072 QCC2072 firmware interprets the MLO_LINK_ADD and MLO_START_AS_ACTIVE flags to control the link state during MLO vdev start. MLO_LINK_ADD indicates that a link is being added, while MLO_START_AS_ACTIVE specifies that the link should become active during the start. When an association link is added without setting MLO_START_AS_ACTIVE, the firmware may transition the link into a suspended state. In this case, authentication frames transmitted by the host can be dropped, leading to repeated authentication retries and eventual timeout, for example: wlp1s0: send auth to (try 1/3) wlp1s0: send auth to (try 2/3) wlp1s0: send auth to (try 3/3) wlp1s0: authentication with timed out Avoid triggering this behavior by setting the MLO_START_AS_ACTIVE flag when MLO_ASSOC_LINK is set, which tells the firmware that the current vdev must not enter suspend mode Note that this change relies on firmware behavior observed on the QCC2072 platform. The firmware on WCN7850 and QCN9274 does not use the MLO_START_AS_ACTIVE flag, so this change is effectively a no-op on those platforms Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00068-QCACOLSWPL_V1_TO_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Fixes: d8e1f4a19310 ("wifi: ath12k: enable QCC2072 support") Signed-off-by: Miaoqing Pan Reviewed-by: Vasanthakumar Thiagarajan Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260704073000.3300099-1-miaoqing.pan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 2 ++ drivers/net/wireless/ath/ath12k/wmi.h | 1 + 2 files changed, 3 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index da14cfda0528..614e02dbb6f9 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -1229,6 +1229,8 @@ int ath12k_wmi_vdev_start(struct ath12k *ar, struct wmi_vdev_start_req_arg *arg, ATH12K_WMI_FLAG_MLO_MCAST_VDEV) | le32_encode_bits(arg->ml.link_add, ATH12K_WMI_FLAG_MLO_LINK_ADD) | + le32_encode_bits(arg->ml.assoc_link, + ATH12K_WMI_FLAG_MLO_START_AS_ACTIVE) | cpu_to_le32(ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID); ml_params->ieee_link_id = cpu_to_le32(arg->ml.ieee_link_id); diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index 51f3426e1fcd..20e3939e8820 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -2954,6 +2954,7 @@ struct wmi_vdev_create_mlo_params { #define ATH12K_WMI_FLAG_MLO_EMLSR_SUPPORT BIT(6) #define ATH12K_WMI_FLAG_MLO_FORCED_INACTIVE BIT(7) #define ATH12K_WMI_FLAG_MLO_LINK_ADD BIT(8) +#define ATH12K_WMI_FLAG_MLO_START_AS_ACTIVE BIT(17) #define ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID BIT(18) #define ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID_PARTNER BIT(19) From 408ec3ffcc766d540bc58120f59c8cc1343f2ed4 Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Fri, 10 Jul 2026 11:05:34 +0530 Subject: [PATCH 0488/1433] wifi: ath12k: update IPQ5332 BDF address offset In the ath12k driver, the BDF_MEM_REGION_TYPE address is derived by adding a fixed bdf_addr_offset to the WCSS Q6 region base address. The current offset (0xC00000) works only when the Q6 region contains the IPQ5332 ucode alone. On some IPQ5332 platform variants, additional devices share the same WCSS Q6 processor and place their firmware ucode in the same Q6 region. This results in multiple ucode sections within the region, and the existing offset can cause the BDF memory region to overlap with firmware read-only sections, which can lead to firmware crash and driver boot failure. Increase the bdf_addr_offset to 0x1A00000, determined by analyzing firmware memory maps across all known IPQ5332 platform variants. This value represents the upper bound of the largest combined firmware and ensures all IPQ5332 variants can allocate the BDF region safely without overlapping firmware regions. Tested-on: IPQ5332 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1 Signed-off-by: Aaradhana Sahu Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260710053534.879233-1-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wifi7/hw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c index d54c2a6d83b2..8bc841dbac00 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c @@ -689,7 +689,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { .ce_ie_addr = &ath12k_wifi7_ce_ie_addr_ipq5332, .ce_remap = &ath12k_wifi7_ce_remap_ipq5332, - .bdf_addr_offset = 0xC00000, + .bdf_addr_offset = 0x1A00000, .dp_primary_link_only = true, .client = { From 007875346097ab22d0040bd61f7bee3e92321fff Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 5 Jul 2026 19:24:06 +0200 Subject: [PATCH 0489/1433] wifi: ath10k: Drop redundant NULL check on devm_clk_get() devm_clk_get() does not return NULL (only valid clock or ERR pointer), so simplify the code to drop redundant IS_ERR_OR_NULL(). Signed-off-by: Krzysztof Kozlowski Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260705172405.119084-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath10k/ahb.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/ath/ath10k/ahb.c b/drivers/net/wireless/ath/ath10k/ahb.c index eb8b35b6224d..7456f885d2b5 100644 --- a/drivers/net/wireless/ath/ath10k/ahb.c +++ b/drivers/net/wireless/ath/ath10k/ahb.c @@ -87,24 +87,24 @@ static int ath10k_ahb_clock_init(struct ath10k *ar) dev = &ar_ahb->pdev->dev; ar_ahb->cmd_clk = devm_clk_get(dev, "wifi_wcss_cmd"); - if (IS_ERR_OR_NULL(ar_ahb->cmd_clk)) { + if (IS_ERR(ar_ahb->cmd_clk)) { ath10k_err(ar, "failed to get cmd clk: %ld\n", PTR_ERR(ar_ahb->cmd_clk)); - return ar_ahb->cmd_clk ? PTR_ERR(ar_ahb->cmd_clk) : -ENODEV; + return PTR_ERR(ar_ahb->cmd_clk); } ar_ahb->ref_clk = devm_clk_get(dev, "wifi_wcss_ref"); - if (IS_ERR_OR_NULL(ar_ahb->ref_clk)) { + if (IS_ERR(ar_ahb->ref_clk)) { ath10k_err(ar, "failed to get ref clk: %ld\n", PTR_ERR(ar_ahb->ref_clk)); - return ar_ahb->ref_clk ? PTR_ERR(ar_ahb->ref_clk) : -ENODEV; + return PTR_ERR(ar_ahb->ref_clk); } ar_ahb->rtc_clk = devm_clk_get(dev, "wifi_wcss_rtc"); - if (IS_ERR_OR_NULL(ar_ahb->rtc_clk)) { + if (IS_ERR(ar_ahb->rtc_clk)) { ath10k_err(ar, "failed to get rtc clk: %ld\n", PTR_ERR(ar_ahb->rtc_clk)); - return ar_ahb->rtc_clk ? PTR_ERR(ar_ahb->rtc_clk) : -ENODEV; + return PTR_ERR(ar_ahb->rtc_clk); } return 0; From f57314aade9d74d30f3360ec5ef85a83654748be Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Sat, 11 Jul 2026 11:04:43 -0700 Subject: [PATCH 0490/1433] wifi: ath6kl: avoid buffer overreads in WMI event handlers The following WMI event handlers currently read from the event buffer without first verifying that the message was large enough to hold the expected event: ath6kl_wmi_scan_complete_rx() ath6kl_wmi_addba_req_event_rx() ath6kl_wmi_delba_req_event_rx() Add length checks to prevent overread. Fixes: bdcd81707973 ("Add ath6kl cleaned up driver") Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260711-ath6kl_wmi_scan_complete_rx-v2-1-22dc0f7f45e7@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath6kl/wmi.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath6kl/wmi.c b/drivers/net/wireless/ath/ath6kl/wmi.c index 72611a2ceb9d..c0c455d6e2dc 100644 --- a/drivers/net/wireless/ath/ath6kl/wmi.c +++ b/drivers/net/wireless/ath/ath6kl/wmi.c @@ -1276,6 +1276,9 @@ static int ath6kl_wmi_scan_complete_rx(struct wmi *wmi, u8 *datap, int len, { struct wmi_scan_complete_event *ev; + if (len < sizeof(*ev)) + return -EINVAL; + ev = (struct wmi_scan_complete_event *) datap; ath6kl_scan_complete_evt(vif, a_sle32_to_cpu(ev->status)); @@ -3352,7 +3355,12 @@ static int ath6kl_wmi_get_pmkid_list_event_rx(struct wmi *wmi, u8 *datap, static int ath6kl_wmi_addba_req_event_rx(struct wmi *wmi, u8 *datap, int len, struct ath6kl_vif *vif) { - struct wmi_addba_req_event *cmd = (struct wmi_addba_req_event *) datap; + struct wmi_addba_req_event *cmd; + + if (len < sizeof(*cmd)) + return -EINVAL; + + cmd = (struct wmi_addba_req_event *)datap; aggr_recv_addba_req_evt(vif, cmd->tid, le16_to_cpu(cmd->st_seq_no), cmd->win_sz); @@ -3363,7 +3371,12 @@ static int ath6kl_wmi_addba_req_event_rx(struct wmi *wmi, u8 *datap, int len, static int ath6kl_wmi_delba_req_event_rx(struct wmi *wmi, u8 *datap, int len, struct ath6kl_vif *vif) { - struct wmi_delba_event *cmd = (struct wmi_delba_event *) datap; + struct wmi_delba_event *cmd; + + if (len < sizeof(*cmd)) + return -EINVAL; + + cmd = (struct wmi_delba_event *)datap; aggr_recv_delba_req_evt(vif, cmd->tid); From e23d90cfcbe2ed9bf0662e478ab3985a852aefc4 Mon Sep 17 00:00:00 2001 From: Thiraviyam Mariyappan Date: Mon, 22 Jun 2026 11:56:14 +0530 Subject: [PATCH 0491/1433] wifi: ath12k: Set congestion control max MSDU count Currently when running 128 clients UDP DL test in 5 GHz HE80 (NSS 2x2), firmware uses the default max MSDU count (16K MSDUs). This lower limit causes the firmware to compute a smaller TQM drop threshold, aggregate packets at a reduced rate, and results in increased packet drops with TQM drop threshold as the completion reason. To fix this issue, set WMI_PDEV_PARAM_SET_CONG_CTRL_MAX_MSDUS to the TX descriptor count using ath12k_wmi_pdev_set_param(). This increases the TQM drop threshold, reduces drop events, and improves throughput from ~722 Mbps to ~1060 Mbps with 1200 Mbps ingress. Add a new HW capability flag (supports_cong_ctrl_max_msdus) and enable the WMI parameter only on supported platforms. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01181-QCAHKSWPL_SILICONZ-1 Signed-off-by: Thiraviyam Mariyappan Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260622062614.760166-1-thiraviyam.mariyappan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/hw.h | 1 + drivers/net/wireless/ath/ath12k/mac.c | 13 +++++++++++++ drivers/net/wireless/ath/ath12k/wifi7/hw.c | 6 ++++++ drivers/net/wireless/ath/ath12k/wmi.h | 1 + 4 files changed, 21 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/hw.h b/drivers/net/wireless/ath/ath12k/hw.h index d135b2936378..ad62f93441b3 100644 --- a/drivers/net/wireless/ath/ath12k/hw.h +++ b/drivers/net/wireless/ath/ath12k/hw.h @@ -192,6 +192,7 @@ struct ath12k_hw_params { bool supports_shadow_regs:1; bool supports_aspm:1; bool current_cc_support:1; + bool supports_cong_ctrl_max_msdus:1; u32 num_tcl_banks; u32 max_tx_ring; diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index d7e93a20ca36..5e4798ae8e99 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -9722,6 +9722,19 @@ static int ath12k_mac_start(struct ath12k *ar) goto err; } + if (ab->hw_params->supports_cong_ctrl_max_msdus) { + ret = ath12k_wmi_pdev_set_param(ar, + WMI_PDEV_PARAM_SET_CONG_CTRL_MAX_MSDUS, + ATH12K_NUM_POOL_TX_DESC(ab), + pdev->pdev_id); + if (ret) { + ath12k_err(ab, + "failed to set congestion control MAX MSDUS: %d\n", + ret); + goto err; + } + } + __ath12k_set_antenna(ar, ar->cfg_tx_chainmask, ar->cfg_rx_chainmask); /* TODO: Do we need to enable ANI? */ diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c index 8bc841dbac00..1ab1168510aa 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c @@ -390,6 +390,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { BIT(NL80211_IFTYPE_MESH_POINT) | BIT(NL80211_IFTYPE_AP_VLAN), .supports_monitor = false, + .supports_cong_ctrl_max_msdus = true, .idle_ps = false, .download_calib = true, @@ -480,6 +481,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO), .supports_monitor = true, + .supports_cong_ctrl_max_msdus = false, .idle_ps = true, .download_calib = false, @@ -568,6 +570,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { BIT(NL80211_IFTYPE_MESH_POINT) | BIT(NL80211_IFTYPE_AP_VLAN), .supports_monitor = true, + .supports_cong_ctrl_max_msdus = true, .idle_ps = false, .download_calib = true, @@ -654,6 +657,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { BIT(NL80211_IFTYPE_AP) | BIT(NL80211_IFTYPE_MESH_POINT), .supports_monitor = true, + .supports_cong_ctrl_max_msdus = true, .idle_ps = false, .download_calib = true, @@ -738,6 +742,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO), .supports_monitor = true, + .supports_cong_ctrl_max_msdus = false, .idle_ps = true, .download_calib = false, @@ -826,6 +831,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { BIT(NL80211_IFTYPE_AP) | BIT(NL80211_IFTYPE_MESH_POINT), .supports_monitor = true, + .supports_cong_ctrl_max_msdus = true, .idle_ps = false, .download_calib = true, diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index 20e3939e8820..c813b2848b5b 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -1083,6 +1083,7 @@ enum wmi_tlv_pdev_param { WMI_PDEV_PARAM_RADIO_CHAN_STATS_ENABLE, WMI_PDEV_PARAM_RADIO_DIAGNOSIS_ENABLE, WMI_PDEV_PARAM_MESH_MCAST_ENABLE, + WMI_PDEV_PARAM_SET_CONG_CTRL_MAX_MSDUS = 0xa6, WMI_PDEV_PARAM_SET_CMD_OBSS_PD_THRESHOLD = 0xbc, WMI_PDEV_PARAM_SET_CMD_OBSS_PD_PER_AC = 0xbe, WMI_PDEV_PARAM_ENABLE_SR_PROHIBIT = 0xc6, From 3fe59edd1901c040e5b8e9d2428bf9ec6b4ce630 Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Tue, 30 Jun 2026 11:50:46 +0530 Subject: [PATCH 0492/1433] wifi: ath12k: switch to name-based reserved memory lookup The driver currently retrieves reserved memory regions using index-based lookup, which depends on the ordering of reserved-memory nodes in the device tree. Since different platforms define these regions in varying orders and combinations, this approach is not compatible and can result in incorrect memory region access. Switch to looking up memory regions by name instead of index so it does not depend on node order. Use names already defined in qcom,ipq5332-wifi.yaml, so there are no backward compatibility issues. Tested-on: IPQ5332 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Signed-off-by: Aaradhana Sahu Link: https://patch.msgid.link/20260630062048.1615178-2-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/ahb.c | 18 ++++++------ drivers/net/wireless/ath/ath12k/core.c | 25 ----------------- drivers/net/wireless/ath/ath12k/core.h | 2 -- drivers/net/wireless/ath/ath12k/qmi.c | 38 +++++++++++++------------- 4 files changed, 29 insertions(+), 54 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/ahb.c b/drivers/net/wireless/ath/ath12k/ahb.c index 4944ea7855ca..07bb83710b1f 100644 --- a/drivers/net/wireless/ath/ath12k/ahb.c +++ b/drivers/net/wireless/ath/ath12k/ahb.c @@ -12,6 +12,7 @@ #include #include #include +#include #include "ahb.h" #include "debug.h" #include "hif.h" @@ -337,24 +338,25 @@ static int ath12k_ahb_power_up(struct ath12k_base *ab) char fw2_name[ATH12K_USERPD_FW_NAME_LEN]; struct device *dev = ab->dev; const struct firmware *fw, *fw2; - struct reserved_mem *rmem = NULL; unsigned long time_left; phys_addr_t mem_phys; + struct resource res; void *mem_region; size_t mem_size; u32 pasid; int ret; - rmem = ath12k_core_get_reserved_mem(ab, 0); - if (!rmem) - return -ENODEV; + ret = of_reserved_mem_region_to_resource_byname(dev->of_node, "q6-region", + &res); + if (ret) + return ret; - mem_phys = rmem->base; - mem_size = rmem->size; + mem_phys = res.start; + mem_size = resource_size(&res); mem_region = devm_memremap(dev, mem_phys, mem_size, MEMREMAP_WC); if (IS_ERR(mem_region)) { - ath12k_err(ab, "unable to map memory region: %pa+%pa\n", - &rmem->base, &rmem->size); + ath12k_err(ab, "unable to map memory region: %pa+%zx\n", + &res.start, mem_size); return PTR_ERR(mem_region); } diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index fb599caa3aab..a9112760185f 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -637,31 +637,6 @@ u32 ath12k_core_get_max_peers_per_radio(struct ath12k_base *ab) } EXPORT_SYMBOL(ath12k_core_get_max_peers_per_radio); -struct reserved_mem *ath12k_core_get_reserved_mem(struct ath12k_base *ab, - int index) -{ - struct device *dev = ab->dev; - struct reserved_mem *rmem; - struct device_node *node; - - node = of_parse_phandle(dev->of_node, "memory-region", index); - if (!node) { - ath12k_dbg(ab, ATH12K_DBG_BOOT, - "failed to parse memory-region for index %d\n", index); - return NULL; - } - - rmem = of_reserved_mem_lookup(node); - of_node_put(node); - if (!rmem) { - ath12k_dbg(ab, ATH12K_DBG_BOOT, - "unable to get memory-region for index %d\n", index); - return NULL; - } - - return rmem; -} - static inline void ath12k_core_to_group_ref_get(struct ath12k_base *ab) { diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index 1436ff4316e7..f9fb1399e0b6 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -1295,8 +1295,6 @@ void ath12k_fw_stats_init(struct ath12k *ar); void ath12k_fw_stats_bcn_free(struct list_head *head); void ath12k_fw_stats_free(struct ath12k_fw_stats *stats); void ath12k_fw_stats_reset(struct ath12k *ar); -struct reserved_mem *ath12k_core_get_reserved_mem(struct ath12k_base *ab, - int index); enum ath12k_qmi_mem_mode ath12k_core_get_memory_mode(struct ath12k_base *ab); static inline const char *ath12k_scan_state_str(enum ath12k_scan_state state) diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index cabdd544bbc7..7abd93766918 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -13,6 +13,7 @@ #include #include #include +#include #define SLEEP_CLOCK_SELECT_INTERNAL_BIT 0x02 #define HOST_CSTATE_BIT 0x04 @@ -2778,20 +2779,20 @@ static int ath12k_qmi_alloc_target_mem_chunk(struct ath12k_base *ab) static int ath12k_qmi_assign_target_mem_chunk(struct ath12k_base *ab) { - struct reserved_mem *rmem; + struct device_node *np = ab->dev->of_node; size_t avail_rmem_size; + struct resource res; int i, idx, ret; for (i = 0, idx = 0; i < ab->qmi.mem_seg_count; i++) { switch (ab->qmi.target_mem[i].type) { case HOST_DDR_REGION_TYPE: - rmem = ath12k_core_get_reserved_mem(ab, 0); - if (!rmem) { - ret = -ENODEV; + ret = of_reserved_mem_region_to_resource_byname(np, "q6-region", + &res); + if (ret) goto out; - } - avail_rmem_size = rmem->size; + avail_rmem_size = resource_size(&res); if (avail_rmem_size < ab->qmi.target_mem[i].size) { ath12k_dbg(ab, ATH12K_DBG_QMI, "failed to assign mem type %u req size %u avail size %zu\n", @@ -2802,7 +2803,7 @@ static int ath12k_qmi_assign_target_mem_chunk(struct ath12k_base *ab) goto out; } - ab->qmi.target_mem[idx].paddr = rmem->base; + ab->qmi.target_mem[idx].paddr = res.start; ab->qmi.target_mem[idx].v.ioaddr = ioremap(ab->qmi.target_mem[idx].paddr, ab->qmi.target_mem[i].size); @@ -2815,13 +2816,13 @@ static int ath12k_qmi_assign_target_mem_chunk(struct ath12k_base *ab) idx++; break; case BDF_MEM_REGION_TYPE: - rmem = ath12k_core_get_reserved_mem(ab, 0); - if (!rmem) { - ret = -ENODEV; + ret = of_reserved_mem_region_to_resource_byname(np, "q6-region", + &res); + if (ret) goto out; - } - avail_rmem_size = rmem->size - ab->hw_params->bdf_addr_offset; + avail_rmem_size = resource_size(&res) - + ab->hw_params->bdf_addr_offset; if (avail_rmem_size < ab->qmi.target_mem[i].size) { ath12k_dbg(ab, ATH12K_DBG_QMI, "failed to assign mem type %u req size %u avail size %zu\n", @@ -2832,7 +2833,7 @@ static int ath12k_qmi_assign_target_mem_chunk(struct ath12k_base *ab) goto out; } ab->qmi.target_mem[idx].paddr = - rmem->base + ab->hw_params->bdf_addr_offset; + res.start + ab->hw_params->bdf_addr_offset; ab->qmi.target_mem[idx].v.ioaddr = ioremap(ab->qmi.target_mem[idx].paddr, ab->qmi.target_mem[i].size); @@ -2857,13 +2858,12 @@ static int ath12k_qmi_assign_target_mem_chunk(struct ath12k_base *ab) idx++; break; case M3_DUMP_REGION_TYPE: - rmem = ath12k_core_get_reserved_mem(ab, 1); - if (!rmem) { - ret = -EINVAL; + ret = of_reserved_mem_region_to_resource_byname(np, "m3-dump", + &res); + if (ret) goto out; - } - avail_rmem_size = rmem->size; + avail_rmem_size = resource_size(&res); if (avail_rmem_size < ab->qmi.target_mem[i].size) { ath12k_dbg(ab, ATH12K_DBG_QMI, "failed to assign mem type %u req size %u avail size %zu\n", @@ -2874,7 +2874,7 @@ static int ath12k_qmi_assign_target_mem_chunk(struct ath12k_base *ab) goto out; } - ab->qmi.target_mem[idx].paddr = rmem->base; + ab->qmi.target_mem[idx].paddr = res.start; ab->qmi.target_mem[idx].v.ioaddr = ioremap(ab->qmi.target_mem[idx].paddr, ab->qmi.target_mem[i].size); From ecb517f97e629d3b8c360cbb5db3fed4d599ea2e Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Tue, 30 Jun 2026 11:50:47 +0530 Subject: [PATCH 0493/1433] wifi: ath12k: refactor QMI memory assignment ath12k_qmi_assign_target_mem_chunk() uses a large switch-case to handle both memory region identification and allocation for each memory request type, leading to redundant allocation logic. Refactor this by introducing ath12k_qmi_get_mem_reg_name() to map memory request types to their corresponding reserved memory region names. Tested-on: IPQ5332 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Signed-off-by: Aaradhana Sahu Link: https://patch.msgid.link/20260630062048.1615178-3-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/qmi.c | 161 ++++++++++---------------- 1 file changed, 63 insertions(+), 98 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index 7abd93766918..4636aef29300 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -2777,120 +2777,85 @@ static int ath12k_qmi_alloc_target_mem_chunk(struct ath12k_base *ab) return ret; } +static const char *ath12k_qmi_get_mem_reg_name(int mem_type) +{ + switch (mem_type) { + case HOST_DDR_REGION_TYPE: + case BDF_MEM_REGION_TYPE: + return "q6-region"; + case M3_DUMP_REGION_TYPE: + return "m3-dump"; + case CALDB_MEM_REGION_TYPE: + return "q6-caldb"; + case MLO_GLOBAL_MEM_REGION_TYPE: + return "mlo-global-mem"; + default: + return NULL; + } +} + static int ath12k_qmi_assign_target_mem_chunk(struct ath12k_base *ab) { struct device_node *np = ab->dev->of_node; + struct target_mem_chunk *chunk; size_t avail_rmem_size; struct resource res; + const char *rname; int i, idx, ret; for (i = 0, idx = 0; i < ab->qmi.mem_seg_count; i++) { - switch (ab->qmi.target_mem[i].type) { - case HOST_DDR_REGION_TYPE: - ret = of_reserved_mem_region_to_resource_byname(np, "q6-region", - &res); - if (ret) - goto out; - - avail_rmem_size = resource_size(&res); - if (avail_rmem_size < ab->qmi.target_mem[i].size) { - ath12k_dbg(ab, ATH12K_DBG_QMI, - "failed to assign mem type %u req size %u avail size %zu\n", - ab->qmi.target_mem[i].type, - ab->qmi.target_mem[i].size, - avail_rmem_size); - ret = -EINVAL; - goto out; - } - - ab->qmi.target_mem[idx].paddr = res.start; - ab->qmi.target_mem[idx].v.ioaddr = - ioremap(ab->qmi.target_mem[idx].paddr, - ab->qmi.target_mem[i].size); - if (!ab->qmi.target_mem[idx].v.ioaddr) { - ret = -EIO; - goto out; - } - ab->qmi.target_mem[idx].size = ab->qmi.target_mem[i].size; - ab->qmi.target_mem[idx].type = ab->qmi.target_mem[i].type; - idx++; - break; - case BDF_MEM_REGION_TYPE: - ret = of_reserved_mem_region_to_resource_byname(np, "q6-region", - &res); - if (ret) - goto out; - - avail_rmem_size = resource_size(&res) - - ab->hw_params->bdf_addr_offset; - if (avail_rmem_size < ab->qmi.target_mem[i].size) { - ath12k_dbg(ab, ATH12K_DBG_QMI, - "failed to assign mem type %u req size %u avail size %zu\n", - ab->qmi.target_mem[i].type, - ab->qmi.target_mem[i].size, - avail_rmem_size); - ret = -EINVAL; - goto out; - } - ab->qmi.target_mem[idx].paddr = - res.start + ab->hw_params->bdf_addr_offset; - ab->qmi.target_mem[idx].v.ioaddr = - ioremap(ab->qmi.target_mem[idx].paddr, - ab->qmi.target_mem[i].size); - if (!ab->qmi.target_mem[idx].v.ioaddr) { - ret = -EIO; - goto out; - } - ab->qmi.target_mem[idx].size = ab->qmi.target_mem[i].size; - ab->qmi.target_mem[idx].type = ab->qmi.target_mem[i].type; - idx++; - break; - case CALDB_MEM_REGION_TYPE: - /* Cold boot calibration is not enabled in Ath12k. Hence, + chunk = &ab->qmi.target_mem[i]; + if (chunk->type == CALDB_MEM_REGION_TYPE) { + /* + * Cold boot calibration is not enabled in Ath12k. Hence, * assign paddr = 0. * Once cold boot calibration is enabled add support to * assign reserved memory from DT. */ ab->qmi.target_mem[idx].paddr = 0; ab->qmi.target_mem[idx].v.ioaddr = NULL; - ab->qmi.target_mem[idx].size = ab->qmi.target_mem[i].size; - ab->qmi.target_mem[idx].type = ab->qmi.target_mem[i].type; + ab->qmi.target_mem[idx].size = chunk->size; + ab->qmi.target_mem[idx].type = chunk->type; idx++; - break; - case M3_DUMP_REGION_TYPE: - ret = of_reserved_mem_region_to_resource_byname(np, "m3-dump", - &res); - if (ret) - goto out; - - avail_rmem_size = resource_size(&res); - if (avail_rmem_size < ab->qmi.target_mem[i].size) { - ath12k_dbg(ab, ATH12K_DBG_QMI, - "failed to assign mem type %u req size %u avail size %zu\n", - ab->qmi.target_mem[i].type, - ab->qmi.target_mem[i].size, - avail_rmem_size); - ret = -EINVAL; - goto out; - } - - ab->qmi.target_mem[idx].paddr = res.start; - ab->qmi.target_mem[idx].v.ioaddr = - ioremap(ab->qmi.target_mem[idx].paddr, - ab->qmi.target_mem[i].size); - if (!ab->qmi.target_mem[idx].v.ioaddr) { - ret = -EIO; - goto out; - } - ab->qmi.target_mem[idx].size = ab->qmi.target_mem[i].size; - ab->qmi.target_mem[idx].type = ab->qmi.target_mem[i].type; - idx++; - break; - default: - ath12k_warn(ab, "qmi ignore invalid mem req type %u\n", - ab->qmi.target_mem[i].type); - break; + continue; } + + rname = ath12k_qmi_get_mem_reg_name(chunk->type); + if (!rname) { + ath12k_warn(ab, "qmi ignore invalid mem req type %u\n", + chunk->type); + continue; + } + + ret = of_reserved_mem_region_to_resource_byname(np, rname, &res); + if (ret) + goto out; + + avail_rmem_size = resource_size(&res); + if (chunk->type == BDF_MEM_REGION_TYPE) { + avail_rmem_size -= ab->hw_params->bdf_addr_offset; + res.start += ab->hw_params->bdf_addr_offset; + } + + if (avail_rmem_size < chunk->size) { + ath12k_dbg(ab, ATH12K_DBG_QMI, + "failed to assign mem type %u req size %u avail size %zu\n", + chunk->type, chunk->size, avail_rmem_size); + ret = -EINVAL; + goto out; + } + + ab->qmi.target_mem[idx].paddr = res.start; + ab->qmi.target_mem[idx].v.ioaddr = ioremap(ab->qmi.target_mem[idx].paddr, + chunk->size); + if (!ab->qmi.target_mem[idx].v.ioaddr) { + ret = -EIO; + goto out; + } + + ab->qmi.target_mem[idx].size = chunk->size; + ab->qmi.target_mem[idx].type = chunk->type; + idx++; } ab->qmi.mem_seg_count = idx; From 42399be44b13eafb45c56b1c7d7c92107e50c289 Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Tue, 30 Jun 2026 11:50:48 +0530 Subject: [PATCH 0494/1433] wifi: ath12k: allocate HOST_DDR and BDF regions after Q6 RO region Currently, the Q6 region contains a read-only firmware region along with the BDF_MEM_REGION_TYPE and HOST_DDR_REGION_TYPE memory areas. The firmware expects these writable memory regions to be assigned after the Q6 read-only section. However, the ath12k driver currently allocates the HOST_DDR_REGION_TYPE starting from the base of the Q6 region, which includes the read-only firmware area. As a result, the allocated memory regions overlap with the read-only section, causing the firmware to assert during QMI memory allocation. The Q6 memory region layout is as follows: Q6 Reserved Memory +--------------------------------------+ | | | Read-only Firmware Region | | (Q6 RO Region) | | | +--------------------------------------+ <--- bdf_addr_offset | Writable Memory Region | | (BDF + HOST_DDR allocations) | | | +--------------------------------------+ Fix this by allocating the required memory regions only after the end of the read-only region in the Q6 address space. The bdf_addr_offset parameter indicates where the writable region starts. Both HOST_DDR and BDF regions are allocated sequentially after this offset, with each region placed immediately after the previous one to avoid gaps and overlaps. Tested-on: IPQ5332 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1 Fixes: 6757079c5890 ("wifi: ath12k: add support for fixed QMI firmware memory") Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Signed-off-by: Aaradhana Sahu Link: https://patch.msgid.link/20260630062048.1615178-4-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/qmi.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index 4636aef29300..bb61c78e5c29 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -2797,8 +2797,8 @@ static const char *ath12k_qmi_get_mem_reg_name(int mem_type) static int ath12k_qmi_assign_target_mem_chunk(struct ath12k_base *ab) { struct device_node *np = ab->dev->of_node; + size_t avail_rmem_size, offset = 0; struct target_mem_chunk *chunk; - size_t avail_rmem_size; struct resource res; const char *rname; int i, idx, ret; @@ -2832,9 +2832,20 @@ static int ath12k_qmi_assign_target_mem_chunk(struct ath12k_base *ab) goto out; avail_rmem_size = resource_size(&res); - if (chunk->type == BDF_MEM_REGION_TYPE) { - avail_rmem_size -= ab->hw_params->bdf_addr_offset; - res.start += ab->hw_params->bdf_addr_offset; + if (chunk->type == BDF_MEM_REGION_TYPE || + chunk->type == HOST_DDR_REGION_TYPE) { + if (ab->hw_params->bdf_addr_offset > avail_rmem_size || + offset > avail_rmem_size - ab->hw_params->bdf_addr_offset) { + ath12k_err(ab, "qmi mem offset overflow: bdf_offset=%u offset=%zu size=%zu\n", + ab->hw_params->bdf_addr_offset, offset, + avail_rmem_size); + ret = -EINVAL; + goto out; + } + + avail_rmem_size -= ab->hw_params->bdf_addr_offset + offset; + res.start += ab->hw_params->bdf_addr_offset + offset; + offset += chunk->size; } if (avail_rmem_size < chunk->size) { From 7b0bd40e97a00991122122d5888ae455fb2bfc7a Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Mon, 13 Jul 2026 09:15:49 -0700 Subject: [PATCH 0495/1433] wifi: ath12k: Correctly copy the hint BSSID in WMI scan request Currently, in ath12k_wmi_send_scan_start_cmd(), the logic to populate the hint_bssid copies the BSSID in the wrong direction, from the firmware message to the argument buffer. Swap the parameters so that the BSSID is correctly populated in the firmware message from the argument buffer. Compile tested only. Reported-by: Baochen Qiang Closes: https://lore.kernel.org/linux-wireless/afbff608-a005-43c4-af76-968a58bf0cc3@oss.qualcomm.com/ Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260713-ath12k_wmi_send_scan_start_cmd-bad-hint_bssid-v1-1-4ffc4a472992@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index 614e02dbb6f9..f16ec3d21242 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -2798,8 +2798,8 @@ int ath12k_wmi_send_scan_start_cmd(struct ath12k *ar, for (i = 0; i < arg->num_hint_bssid; ++i) { hint_bssid->freq_flags = arg->hint_bssid[i].freq_flags; - ether_addr_copy(&arg->hint_bssid[i].bssid.addr[0], - &hint_bssid->bssid.addr[0]); + ether_addr_copy(&hint_bssid->bssid.addr[0], + &arg->hint_bssid[i].bssid.addr[0]); hint_bssid++; } } From 6fe2dddf59bbb2a96be0fcf23a205807b25ac173 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Mon, 13 Jul 2026 09:15:50 -0700 Subject: [PATCH 0496/1433] wifi: ath11k: Correctly copy the hint BSSID in WMI scan request Currently, in ath11k_wmi_send_scan_start_cmd(), the logic to populate the hint_bssid copies the BSSID in the wrong direction, from the firmware message to the argument buffer. Swap the parameters so that the BSSID is correctly populated in the firmware message from the argument buffer. This issue was reported on ath12k, but exists in ath11k as well. Compile tested only. Reported-by: Baochen Qiang Closes: https://lore.kernel.org/linux-wireless/afbff608-a005-43c4-af76-968a58bf0cc3@oss.qualcomm.com/ Fixes: 74601ecfef6e ("ath11k: Add support for 6g scan hint") Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260713-ath12k_wmi_send_scan_start_cmd-bad-hint_bssid-v1-2-4ffc4a472992@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/wmi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c index 28acfc6829df..2d2c6d7a4a3b 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.c +++ b/drivers/net/wireless/ath/ath11k/wmi.c @@ -2423,8 +2423,8 @@ int ath11k_wmi_send_scan_start_cmd(struct ath11k *ar, for (i = 0; i < params->num_hint_bssid; ++i) { hint_bssid->freq_flags = params->hint_bssid[i].freq_flags; - ether_addr_copy(¶ms->hint_bssid[i].bssid.addr[0], - &hint_bssid->bssid.addr[0]); + ether_addr_copy(&hint_bssid->bssid.addr[0], + ¶ms->hint_bssid[i].bssid.addr[0]); hint_bssid++; } } From 6c90f68f97de591e0413f7d2de59449a0388bbab Mon Sep 17 00:00:00 2001 From: Alexander Wilhelm Date: Fri, 3 Jul 2026 09:35:38 +0200 Subject: [PATCH 0497/1433] wifi: ath12k: fix scan command endianness on big endian ath12k_wmi_scan_req_arg stores scan parameters in CPU-native byte order, while ath12k_wmi_send_scan_start_cmd() writes them into a WMI command buffer whose contents must be in little-endian format. The existing code copies the channel list and writes s_ssid and hint_bssid related values to the command buffer without endian conversion. As a result, scan requests contain invalid parameters on big-endian systems and fail. Convert the channel list as well as the s_ssid and hint_bssid related values to little-endian before writing them to the WMI command buffer. This preserves the existing behaviour on little-endian systems while fixing scan requests on big-endian architectures. Signed-off-by: Alexander Wilhelm Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260703-fix-channel-list-copy-v2-1-372c39306d79@westermo.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 20 ++++++++++++-------- drivers/net/wireless/ath/ath12k/wmi.h | 10 ++++++++++ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index f16ec3d21242..672eae237ac6 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -2639,9 +2639,10 @@ int ath12k_wmi_send_scan_start_cmd(struct ath12k *ar, struct wmi_tlv *tlv; void *ptr; int i, ret, len; - u32 *tmp_ptr, extraie_len_with_pad = 0; - struct ath12k_wmi_hint_short_ssid_arg *s_ssid = NULL; - struct ath12k_wmi_hint_bssid_arg *hint_bssid = NULL; + __le32 *tmp_ptr; + u32 extraie_len_with_pad = 0; + struct ath12k_wmi_hint_short_ssid_params *s_ssid = NULL; + struct ath12k_wmi_hint_bssid_params *hint_bssid = NULL; len = sizeof(*cmd); @@ -2724,9 +2725,10 @@ int ath12k_wmi_send_scan_start_cmd(struct ath12k *ar, tlv = ptr; tlv->header = ath12k_wmi_tlv_hdr(WMI_TAG_ARRAY_UINT32, len); ptr += TLV_HDR_SIZE; - tmp_ptr = (u32 *)ptr; + tmp_ptr = (__le32 *)ptr; - memcpy(tmp_ptr, arg->chan_list, arg->num_chan * 4); + for (i = 0; i < arg->num_chan; i++) + tmp_ptr[i] = cpu_to_le32(arg->chan_list[i]); ptr += len; @@ -2782,8 +2784,10 @@ int ath12k_wmi_send_scan_start_cmd(struct ath12k *ar, ptr += TLV_HDR_SIZE; s_ssid = ptr; for (i = 0; i < arg->num_hint_s_ssid; ++i) { - s_ssid->freq_flags = arg->hint_s_ssid[i].freq_flags; - s_ssid->short_ssid = arg->hint_s_ssid[i].short_ssid; + s_ssid->freq_flags = + cpu_to_le32(arg->hint_s_ssid[i].freq_flags); + s_ssid->short_ssid = + cpu_to_le32(arg->hint_s_ssid[i].short_ssid); s_ssid++; } ptr += len; @@ -2797,7 +2801,7 @@ int ath12k_wmi_send_scan_start_cmd(struct ath12k *ar, hint_bssid = ptr; for (i = 0; i < arg->num_hint_bssid; ++i) { hint_bssid->freq_flags = - arg->hint_bssid[i].freq_flags; + cpu_to_le32(arg->hint_bssid[i].freq_flags); ether_addr_copy(&hint_bssid->bssid.addr[0], &arg->hint_bssid[i].bssid.addr[0]); hint_bssid++; diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index c813b2848b5b..83103fdefafc 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -3558,6 +3558,16 @@ struct ath12k_wmi_hint_bssid_arg { struct ath12k_wmi_mac_addr_params bssid; }; +struct ath12k_wmi_hint_short_ssid_params { + __le32 freq_flags; + __le32 short_ssid; +}; + +struct ath12k_wmi_hint_bssid_params { + __le32 freq_flags; + struct ath12k_wmi_mac_addr_params bssid; +}; + struct ath12k_wmi_scan_req_arg { u32 scan_id; u32 scan_req_id; From d6850f1984eb1e5dcec3de2703d32ea4c34c8ad8 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Tue, 14 Jul 2026 10:39:14 +0200 Subject: [PATCH 0498/1433] wifi: ath12k: Constify struct ath12k_dp_arch_ops 'struct ath12k_dp_arch_ops' is not modified in this driver. Constifying this structure moves some data to a read-only section, so increases overall security, especially when the structure holds some function pointers. On a x86_64, with allmodconfig, as an example: Before: ====== text data bss dec hex filename 6318 3384 0 9702 25e6 drivers/net/wireless/ath/ath12k/wifi7/dp.o After: ===== text data bss dec hex filename 6478 3224 0 9702 25e6 drivers/net/wireless/ath/ath12k/wifi7/dp.o Signed-off-by: Christophe JAILLET Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/969d732e2c6f169e1aa5e89c7e01743a1adb55df.1784010931.git.christophe.jaillet@wanadoo.fr Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp.h | 2 +- drivers/net/wireless/ath/ath12k/wifi7/dp.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/dp.h b/drivers/net/wireless/ath/ath12k/dp.h index 64f79e43341e..a94bbc337df4 100644 --- a/drivers/net/wireless/ath/ath12k/dp.h +++ b/drivers/net/wireless/ath/ath12k/dp.h @@ -538,7 +538,7 @@ struct ath12k_dp { /* Lock for protection of peers and rhead_peer_addr */ spinlock_t dp_lock; - struct ath12k_dp_arch_ops *ops; + const struct ath12k_dp_arch_ops *ops; /* Linked list of struct ath12k_dp_link_peer */ struct list_head peers; diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp.c b/drivers/net/wireless/ath/ath12k/wifi7/dp.c index c72f604661ce..397da016bc78 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp.c @@ -139,7 +139,7 @@ static int ath12k_wifi7_dp_service_srng(struct ath12k_dp *dp, return tot_work_done; } -static struct ath12k_dp_arch_ops ath12k_wifi7_dp_arch_ops = { +static const struct ath12k_dp_arch_ops ath12k_wifi7_dp_arch_ops = { .service_srng = ath12k_wifi7_dp_service_srng, .tx_get_vdev_bank_config = ath12k_wifi7_dp_tx_get_vdev_bank_config, .reo_cmd_send = ath12k_wifi7_dp_reo_cmd_send, From a711bf00b8b3c6d879cb5d95bbe921366228b318 Mon Sep 17 00:00:00 2001 From: Masi Osmani Date: Thu, 12 Mar 2026 11:37:58 +0100 Subject: [PATCH 0499/1433] wifi: carl9170: mac80211: document spatial multiplexing power save handler Replace the bare TODO comment in the SMPS configuration handler with documentation explaining why the driver accepts but does not act on SMPS mode changes. The AR9170 advertises SM_PS disabled (both chains always active) in its HT capabilities. While mac80211 may still send SMPS configuration requests, implementing static or dynamic SMPS would require firmware support for per-chain enable/disable that the AR9170 firmware (v1.9.9) does not provide. Signed-off-by: Masi Osmani Acked-by: Christian Lamparter Link: https://patch.msgid.link/AM7PPF5613FA0B6B76223A73FFC756A19A99444A@AM7PPF5613FA0B6.EURP251.PROD.OUTLOOK.COM Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/carl9170/main.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/carl9170/main.c b/drivers/net/wireless/ath/carl9170/main.c index af632418fa06..61c7a1288743 100644 --- a/drivers/net/wireless/ath/carl9170/main.c +++ b/drivers/net/wireless/ath/carl9170/main.c @@ -908,7 +908,13 @@ static int carl9170_op_config(struct ieee80211_hw *hw, int radio_idx, u32 change } if (changed & IEEE80211_CONF_CHANGE_SMPS) { - /* TODO */ + /* + * We advertise SM_PS disabled (all chains active). + * mac80211 may still request mode changes, which we + * accept but only support OFF (both chains active). + * Static/dynamic SMPS would require firmware support + * for chain control that the AR9170 does not provide. + */ err = 0; } From 2ac1d4de8e5113b8b40bd22374ff51a5cd95dd9e Mon Sep 17 00:00:00 2001 From: Masi Osmani Date: Thu, 12 Mar 2026 11:38:00 +0100 Subject: [PATCH 0500/1433] wifi: carl9170: rx: track PHY errors via debugfs Count PHY errors reported by the hardware in the RX status and expose the counter through debugfs as rx_phy_errors. Previously, PHY errors from ar9170_rx_phystatus were silently ignored (marked with a TODO comment). The counter helps diagnose RF environment issues (interference, multipath, low SNR) without requiring monitor mode or additional tooling. Signed-off-by: Masi Osmani Acked-by: Christian Lamparter Link: https://patch.msgid.link/AM7PPF5613FA0B6B42814E38096301FE9429444A@AM7PPF5613FA0B6.EURP251.PROD.OUTLOOK.COM Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/carl9170/carl9170.h | 1 + drivers/net/wireless/ath/carl9170/debug.c | 2 ++ drivers/net/wireless/ath/carl9170/rx.c | 4 +++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/carl9170/carl9170.h b/drivers/net/wireless/ath/carl9170/carl9170.h index b13685e22a0d..e66e3e2ae952 100644 --- a/drivers/net/wireless/ath/carl9170/carl9170.h +++ b/drivers/net/wireless/ath/carl9170/carl9170.h @@ -381,6 +381,7 @@ struct ar9170 { unsigned int tx_ack_failures; unsigned int tx_fcs_errors; unsigned int rx_dropped; + unsigned int rx_phy_errors; /* EEPROM */ struct ar9170_eeprom eeprom; diff --git a/drivers/net/wireless/ath/carl9170/debug.c b/drivers/net/wireless/ath/carl9170/debug.c index 2d734567000a..0498df2a2160 100644 --- a/drivers/net/wireless/ath/carl9170/debug.c +++ b/drivers/net/wireless/ath/carl9170/debug.c @@ -794,6 +794,7 @@ DEBUGFS_READONLY_FILE(tx_janitor_last_run, 64, "last run:%d ms ago", DEBUGFS_READONLY_FILE(tx_dropped, 20, "%d", ar->tx_dropped); DEBUGFS_READONLY_FILE(rx_dropped, 20, "%d", ar->rx_dropped); +DEBUGFS_READONLY_FILE(rx_phy_errors, 20, "%d", ar->rx_phy_errors); DEBUGFS_READONLY_FILE(sniffer_enabled, 20, "%d", ar->sniffer_enabled); DEBUGFS_READONLY_FILE(rx_software_decryption, 20, "%d", @@ -830,6 +831,7 @@ void carl9170_debugfs_register(struct ar9170 *ar) DEBUGFS_ADD(tx_ampdu_list_len); DEBUGFS_ADD(rx_dropped); + DEBUGFS_ADD(rx_phy_errors); DEBUGFS_ADD(sniffer_enabled); DEBUGFS_ADD(rx_software_decryption); diff --git a/drivers/net/wireless/ath/carl9170/rx.c b/drivers/net/wireless/ath/carl9170/rx.c index 6833430130f4..ec4d440e6ac8 100644 --- a/drivers/net/wireless/ath/carl9170/rx.c +++ b/drivers/net/wireless/ath/carl9170/rx.c @@ -455,7 +455,9 @@ static void carl9170_rx_phy_status(struct ar9170 *ar, if (phy->rssi[i] & 0x80) phy->rssi[i] = ((~phy->rssi[i] & 0x7f) + 1) & 0x7f; - /* TODO: we could do something with phy_errors */ + if (phy->phy_err) + ar->rx_phy_errors++; + status->signal = ar->noise[0] + phy->rssi_combined; } From 89f3630439f1555db4f12b98775c0399195d5027 Mon Sep 17 00:00:00 2001 From: Masi Osmani Date: Tue, 17 Mar 2026 12:06:34 +0100 Subject: [PATCH 0501/1433] wifi: carl9170: cmd: downgrade transient register I/O errors to wiphy_dbg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Register read/write failures during deauth/teardown transitions are harmless — mac80211 tries to read survey stats or write slot_time while the firmware is in a transitional state. The command times out with -EIO but the adapter recovers and re-authenticates normally. Downgrade both "writing reg ... failed" and "reading regs failed" from wiphy_err to wiphy_dbg to reduce dmesg noise. The errors are still visible with dynamic debug enabled for investigation. Signed-off-by: Masi Osmani Acked-by: Christian Lamparter Link: https://patch.msgid.link/AM7PPF5613FA0B67FB95CB5305CEF9DAA209441A@AM7PPF5613FA0B6.EURP251.PROD.OUTLOOK.COM Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/carl9170/cmd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/carl9170/cmd.c b/drivers/net/wireless/ath/carl9170/cmd.c index 402fd0633e09..ad0a018119c9 100644 --- a/drivers/net/wireless/ath/carl9170/cmd.c +++ b/drivers/net/wireless/ath/carl9170/cmd.c @@ -52,7 +52,7 @@ int carl9170_write_reg(struct ar9170 *ar, const u32 reg, const u32 val) (u8 *) buf, 0, NULL); if (err) { if (net_ratelimit()) { - wiphy_err(ar->hw->wiphy, "writing reg %#x " + wiphy_dbg(ar->hw->wiphy, "writing reg %#x " "(val %#x) failed (%d)\n", reg, val, err); } } @@ -78,7 +78,7 @@ int carl9170_read_mreg(struct ar9170 *ar, const int nregs, 4 * nregs, (u8 *)res); if (err) { if (net_ratelimit()) { - wiphy_err(ar->hw->wiphy, "reading regs failed (%d)\n", + wiphy_dbg(ar->hw->wiphy, "reading regs failed (%d)\n", err); } return err; From af50baccaa5fab96b045700a8dc378f73c77822c Mon Sep 17 00:00:00 2001 From: Daizhuang Bai Date: Fri, 17 Jul 2026 07:13:02 +0530 Subject: [PATCH 0502/1433] wifi: ath12k: Set DTIM policy to stick mode for station interface Currently, the station always follows the listen interval regardless of the DTIM value. The DTIM function does not work as expected. The default value of the listen interval is 5 so that the STA wakes up every 500ms when power save is on. This can cause a data transmission delay. Set the DTIM policy to DTIM stick mode so that the station follows the AP DTIM interval rather than the listen interval, which is set in the peer assoc command. DTIM stick mode is preferable per the firmware team's request. Apply this only for STA vdevs and only when STA power save is supported, to avoid affecting unsupported targets and P2P client vdevs. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Signed-off-by: Daizhuang Bai Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260717014302.284034-1-daizhuang.bai@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 11 +++++++++++ drivers/net/wireless/ath/ath12k/wmi.h | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 5e4798ae8e99..22dbc86578ab 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -3985,6 +3985,17 @@ static void ath12k_bss_assoc(struct ath12k *ar, ath12k_warn(ar->ab, "failed to set vdev %i OBSS PD parameters: %d\n", arvif->vdev_id, ret); + if (ar->ab->hw_params->supports_sta_ps && + ahvif->vdev_type == WMI_VDEV_TYPE_STA && + ahvif->vdev_subtype == WMI_VDEV_SUBTYPE_NONE) { + ret = ath12k_wmi_vdev_set_param_cmd(ar, arvif->vdev_id, + WMI_VDEV_PARAM_DTIM_POLICY, + WMI_DTIM_POLICY_STICK); + if (ret) + ath12k_warn(ar->ab, "failed to set vdev %d stick DTIM policy: %d\n", + arvif->vdev_id, ret); + } + if (test_bit(WMI_TLV_SERVICE_11D_OFFLOAD, ar->ab->wmi_ab.svc_map) && ahvif->vdev_type == WMI_VDEV_TYPE_STA && ahvif->vdev_subtype == WMI_VDEV_SUBTYPE_NONE) diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index 83103fdefafc..b508aa759bd8 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -2331,6 +2331,13 @@ enum wmi_slot_time { WMI_VDEV_SLOT_TIME_SHORT = 2, }; +enum wmi_dtim_policy { + WMI_DTIM_POLICY_IGNORE = 1, + WMI_DTIM_POLICY_NORMAL = 2, + WMI_DTIM_POLICY_STICK = 3, + WMI_DTIM_POLICY_AUTO = 4, +}; + enum wmi_preamble { WMI_VDEV_PREAMBLE_LONG = 1, WMI_VDEV_PREAMBLE_SHORT = 2, From 3e66c9cc169d680df38bfbcb7cc2febbaa2899dc Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Tue, 14 Jul 2026 09:14:00 +0200 Subject: [PATCH 0503/1433] wifi: ath6kl: Constify struct cfg80211_ops 'struct cfg80211_ops' is not modified in this driver. Constifying this structure moves some data to a read-only section, so increases overall security, especially when the structure holds some function pointers. On a x86_64, with allmodconfig, as an example: Before: ====== text data bss dec hex filename 143726 34579 192 178497 2b941 drivers/net/wireless/ath/ath6kl/cfg80211.o After: ===== text data bss dec hex filename 144814 33491 192 178497 2b941 drivers/net/wireless/ath/ath6kl/cfg80211.o Signed-off-by: Christophe JAILLET Link: https://patch.msgid.link/5aace954b6ef5c42017b83a1bffb859618e9498a.1784013180.git.christophe.jaillet@wanadoo.fr Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath6kl/cfg80211.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath6kl/cfg80211.c b/drivers/net/wireless/ath/ath6kl/cfg80211.c index cc0f2c45fc3a..ecde91159b54 100644 --- a/drivers/net/wireless/ath/ath6kl/cfg80211.c +++ b/drivers/net/wireless/ath/ath6kl/cfg80211.c @@ -3437,7 +3437,7 @@ ath6kl_mgmt_stypes[NUM_NL80211_IFTYPES] = { }, }; -static struct cfg80211_ops ath6kl_cfg80211_ops = { +static const struct cfg80211_ops ath6kl_cfg80211_ops = { .add_virtual_intf = ath6kl_cfg80211_add_iface, .del_virtual_intf = ath6kl_cfg80211_del_iface, .change_virtual_intf = ath6kl_cfg80211_change_iface, From c42b27336eeffd7926a77604cdefcc8918bed926 Mon Sep 17 00:00:00 2001 From: Matthew Leach Date: Fri, 3 Jul 2026 16:56:02 +0100 Subject: [PATCH 0504/1433] wifi: ath12k: fix survey indexing across bands When running 'iw dev wlan0 survey dump' the values for the channel busy time have the same sequence across bands. This is caused by indexing into the ath12k survey array using a band-local index rather than the global index passed by mac80211. This results in surveys for 5 GHz and 6 GHz channels returning values from 2.4 GHz slots, making the survey unusable on those bands. Further, there are redundant survey slots for multi-radio/single-phy instances. Fix by moving the survey data into ath12k_hw so multiple radios under a single wiphy share one table, and index into it using the global mac80211 index. A new spinlock in ath12k_hw serialises access to the survey array, which is now shared across all radios under a single hw. Band busy-times Before this fix: 2.4 GHz: 9, 2, 2, 2, 4, 2, 10, 16, 4, 12, 5 5 GHz: 9, 2, 2, 2, 4, 2, 10, 16, 4, 12, 5 6 GHz: 9, 2, 2, 2, 4, 2, 10, 16, 4, 12, 5 After this fix, times are independent: 2.4 GHz: 23, 5, 5, 12, 2, 12, 26, 5, 3, 1, 27 5 GHz: 30, 40, 29, 27, 118, 118, 112, 120, 11, 11, 11 6 GHz: 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 Tested-on: wcn7850 hw2.0 PCI WLAN.IOE_HMT.1.1-00018-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1 Fixes: 4f242b1d6996 ("wifi: ath12k: support get_survey mac op for single wiphy") Signed-off-by: Matthew Leach Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260703-ath12-survey-band-fix-v3-1-2fb050c2505a@collabora.com [fixed ath12k-check issues] Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.h | 8 ++- drivers/net/wireless/ath/ath12k/mac.c | 33 +++++++------ drivers/net/wireless/ath/ath12k/wmi.c | 68 ++++++++++++++------------ 3 files changed, 62 insertions(+), 47 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index f9fb1399e0b6..37a194e00248 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -665,7 +665,7 @@ struct ath12k { /* protects the radio specific data like debug stats, ppdu_stats_info stats, * vdev_stop_status info, scan data, ath12k_sta info, ath12k_link_vif info, - * channel context data, survey info, test mode data, regd_channel_update_queue, + * channel context data, test mode data, regd_channel_update_queue, * peer_delete_waits. */ spinlock_t data_lock; @@ -722,7 +722,6 @@ struct ath12k { * avoid reporting garbage data. */ bool ch_info_can_report_survey; - struct survey_info survey[ATH12K_NUM_CHANS]; struct completion bss_survey_done; struct work_struct regd_update_work; @@ -792,6 +791,11 @@ struct ath12k_hw { */ struct mutex hw_mutex; enum ath12k_hw_state state; + + /* protects survey[] shared across radios of this hw. */ + spinlock_t survey_lock; + struct survey_info survey[ATH12K_NUM_CHANS]; + bool regd_updated; bool use_6ghz_regd; diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 22dbc86578ab..956414b953ec 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -13598,52 +13598,54 @@ ath12k_mac_update_bss_chan_survey(struct ath12k *ar, int ath12k_mac_op_get_survey(struct ieee80211_hw *hw, int idx, struct survey_info *survey) { + struct ath12k_hw *ah = hw->priv; struct ath12k *ar; struct ieee80211_supported_band *sband; - struct survey_info *ar_survey; + struct survey_info *ah_survey; + int sband_idx = idx; lockdep_assert_wiphy(hw->wiphy); - if (idx >= ATH12K_NUM_CHANS) + if (sband_idx >= ATH12K_NUM_CHANS) return -ENOENT; sband = hw->wiphy->bands[NL80211_BAND_2GHZ]; - if (sband && idx >= sband->n_channels) { - idx -= sband->n_channels; + if (sband && sband_idx >= sband->n_channels) { + sband_idx -= sband->n_channels; sband = NULL; } if (!sband) sband = hw->wiphy->bands[NL80211_BAND_5GHZ]; - if (sband && idx >= sband->n_channels) { - idx -= sband->n_channels; + if (sband && sband_idx >= sband->n_channels) { + sband_idx -= sband->n_channels; sband = NULL; } if (!sband) sband = hw->wiphy->bands[NL80211_BAND_6GHZ]; - if (!sband || idx >= sband->n_channels) + if (!sband || sband_idx >= sband->n_channels) return -ENOENT; - ar = ath12k_mac_get_ar_by_chan(hw, &sband->channels[idx]); + ar = ath12k_mac_get_ar_by_chan(hw, &sband->channels[sband_idx]); if (!ar) { - if (sband->channels[idx].flags & IEEE80211_CHAN_DISABLED) { + if (sband->channels[sband_idx].flags & IEEE80211_CHAN_DISABLED) { memset(survey, 0, sizeof(*survey)); return 0; } return -ENOENT; } - ar_survey = &ar->survey[idx]; + ah_survey = &ah->survey[idx]; - ath12k_mac_update_bss_chan_survey(ar, &sband->channels[idx]); + ath12k_mac_update_bss_chan_survey(ar, &sband->channels[sband_idx]); - spin_lock_bh(&ar->data_lock); - memcpy(survey, ar_survey, sizeof(*survey)); - spin_unlock_bh(&ar->data_lock); + scoped_guard(spinlock_bh, &ah->survey_lock) { + memcpy(survey, ah_survey, sizeof(*survey)); + } - survey->channel = &sband->channels[idx]; + survey->channel = &sband->channels[sband_idx]; if (ar->rx_channel == survey->channel) survey->filled |= SURVEY_INFO_IN_USE; @@ -15324,6 +15326,7 @@ static struct ath12k_hw *ath12k_mac_hw_allocate(struct ath12k_hw_group *ag, mutex_init(&ah->hw_mutex); + spin_lock_init(&ah->survey_lock); spin_lock_init(&ah->dp_hw.peer_lock); INIT_LIST_HEAD(&ah->dp_hw.dp_peers_list); diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index 672eae237ac6..0178ab169754 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -6742,16 +6742,12 @@ static int ath12k_pull_roam_ev(struct ath12k_base *ab, struct sk_buff *skb, return 0; } -static int freq_to_idx(struct ath12k *ar, int freq) +static int freq_to_idx(struct ieee80211_hw *hw, int freq) { struct ieee80211_supported_band *sband; - struct ieee80211_hw *hw = ath12k_ar_to_hw(ar); int band, ch, idx = 0; for (band = NL80211_BAND_2GHZ; band < NUM_NL80211_BANDS; band++) { - if (!ar->mac.sbands[band].channels) - continue; - sband = hw->wiphy->bands[band]; if (!sband) continue; @@ -7662,6 +7658,7 @@ static void ath12k_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb) { struct wmi_chan_info_event ch_info_ev = {}; struct ath12k *ar; + struct ath12k_hw *ah; struct survey_info *survey; int idx; /* HW channel counters frequency value in hertz */ @@ -7693,6 +7690,7 @@ static void ath12k_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb) return; } spin_lock_bh(&ar->data_lock); + ah = ath12k_ar_to_ah(ar); switch (ar->scan.state) { case ATH12K_SCAN_IDLE: @@ -7704,8 +7702,8 @@ static void ath12k_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb) break; } - idx = freq_to_idx(ar, le32_to_cpu(ch_info_ev.freq)); - if (idx >= ARRAY_SIZE(ar->survey)) { + idx = freq_to_idx(ath12k_ar_to_hw(ar), le32_to_cpu(ch_info_ev.freq)); + if (idx >= ARRAY_SIZE(ah->survey)) { ath12k_warn(ab, "chan info: invalid frequency %d (idx %d out of bounds)\n", ch_info_ev.freq, idx); goto exit; @@ -7718,14 +7716,20 @@ static void ath12k_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb) cc_freq_hz = (le32_to_cpu(ch_info_ev.mac_clk_mhz) * 1000); if (ch_info_ev.cmd_flags == WMI_CHAN_INFO_START_RESP) { - survey = &ar->survey[idx]; - memset(survey, 0, sizeof(*survey)); - survey->noise = le32_to_cpu(ch_info_ev.noise_floor); - survey->filled = SURVEY_INFO_NOISE_DBM | SURVEY_INFO_TIME | - SURVEY_INFO_TIME_BUSY; - survey->time = div_u64(le32_to_cpu(ch_info_ev.cycle_count), cc_freq_hz); - survey->time_busy = div_u64(le32_to_cpu(ch_info_ev.rx_clear_count), - cc_freq_hz); + scoped_guard(spinlock_bh, &ah->survey_lock) { + survey = &ah->survey[idx]; + memset(survey, 0, sizeof(*survey)); + survey->noise = le32_to_cpu(ch_info_ev.noise_floor); + survey->time = + div_u64(le32_to_cpu(ch_info_ev.cycle_count), + cc_freq_hz); + survey->time_busy = + div_u64(le32_to_cpu(ch_info_ev.rx_clear_count), + cc_freq_hz); + survey->filled = SURVEY_INFO_NOISE_DBM | + SURVEY_INFO_TIME | + SURVEY_INFO_TIME_BUSY; + } } exit: spin_unlock_bh(&ar->data_lock); @@ -7738,6 +7742,7 @@ ath12k_pdev_bss_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb) struct wmi_pdev_bss_chan_info_event bss_ch_info_ev = {}; struct survey_info *survey; struct ath12k *ar; + struct ath12k_hw *ah; u32 cc_freq_hz = ab->cc_freq_hz; u64 busy, total, tx, rx, rx_bss; int idx; @@ -7778,28 +7783,31 @@ ath12k_pdev_bss_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb) return; } - spin_lock_bh(&ar->data_lock); - idx = freq_to_idx(ar, le32_to_cpu(bss_ch_info_ev.freq)); - if (idx >= ARRAY_SIZE(ar->survey)) { + ah = ath12k_ar_to_ah(ar); + + idx = freq_to_idx(ath12k_ar_to_hw(ar), le32_to_cpu(bss_ch_info_ev.freq)); + if (idx >= ARRAY_SIZE(ah->survey)) { ath12k_warn(ab, "bss chan info: invalid frequency %d (idx %d out of bounds)\n", bss_ch_info_ev.freq, idx); goto exit; } - survey = &ar->survey[idx]; + scoped_guard(spinlock_bh, &ah->survey_lock) { + survey = &ah->survey[idx]; + + survey->noise = le32_to_cpu(bss_ch_info_ev.noise_floor); + survey->time = div_u64(total, cc_freq_hz); + survey->time_busy = div_u64(busy, cc_freq_hz); + survey->time_rx = div_u64(rx_bss, cc_freq_hz); + survey->time_tx = div_u64(tx, cc_freq_hz); + survey->filled |= (SURVEY_INFO_NOISE_DBM | + SURVEY_INFO_TIME | + SURVEY_INFO_TIME_BUSY | + SURVEY_INFO_TIME_RX | + SURVEY_INFO_TIME_TX); + } - survey->noise = le32_to_cpu(bss_ch_info_ev.noise_floor); - survey->time = div_u64(total, cc_freq_hz); - survey->time_busy = div_u64(busy, cc_freq_hz); - survey->time_rx = div_u64(rx_bss, cc_freq_hz); - survey->time_tx = div_u64(tx, cc_freq_hz); - survey->filled |= (SURVEY_INFO_NOISE_DBM | - SURVEY_INFO_TIME | - SURVEY_INFO_TIME_BUSY | - SURVEY_INFO_TIME_RX | - SURVEY_INFO_TIME_TX); exit: - spin_unlock_bh(&ar->data_lock); complete(&ar->bss_survey_done); rcu_read_unlock(); From 7698656a2f7b045af5a6859766238cefea1b1945 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Thu, 16 Jul 2026 13:01:11 -0700 Subject: [PATCH 0505/1433] wifi: ath12k: Avoid buffer overread in ath12k_wmi_op_rx() Currently, in ath12k_wmi_op_rx(), the firmware buffer is read without first verifying that the buffer has enough data to hold a header. This could result in a buffer overread. Update the logic to verify the buffer contains at least enough data to hold a wmi_cmd_hdr before reading from the buffer. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260716-ath12k_wmi_op_rx-overread-v1-1-327a4b1c2372@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index 0178ab169754..2b707ffc1a20 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -10298,12 +10298,12 @@ static void ath12k_wmi_op_rx(struct ath12k_base *ab, struct sk_buff *skb) struct wmi_cmd_hdr *cmd_hdr; enum wmi_tlv_event_id id; - cmd_hdr = (struct wmi_cmd_hdr *)skb->data; - id = le32_get_bits(cmd_hdr->cmd_id, WMI_CMD_HDR_CMD_ID); - - if (!skb_pull(skb, sizeof(struct wmi_cmd_hdr))) + cmd_hdr = skb_pull_data(skb, sizeof(*cmd_hdr)); + if (!cmd_hdr) goto out; + id = le32_get_bits(cmd_hdr->cmd_id, WMI_CMD_HDR_CMD_ID); + switch (id) { /* Process all the WMI events here */ case WMI_SERVICE_READY_EVENTID: From 9ef9dd30058cc9223c72f711dca1a28a5947d0c5 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Thu, 16 Jul 2026 13:01:31 -0700 Subject: [PATCH 0506/1433] wifi: ath11k: Avoid buffer overread in ath11k_wmi_tlv_op_rx() Currently, in ath11k_wmi_tlv_op_rx(), the firmware buffer is read without first verifying that the buffer has enough data to hold a header. This could result in a buffer overread. Add an upfront length check before dereferencing skb->data as a wmi_cmd_hdr. The check is placed before the trace_ath11k_wmi_event() call to preserve the existing trace semantics (tracing the full raw WMI event including the header), unlike the analogous ath12k fix which could use skb_pull_data() directly. Compile tested only. Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices") Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260716-ath11k_wmi_tlv_op_rx-overread-v1-1-0b972b3f1368@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/wmi.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c index 2d2c6d7a4a3b..4cbd7293845a 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.c +++ b/drivers/net/wireless/ath/ath11k/wmi.c @@ -8901,13 +8901,15 @@ static void ath11k_wmi_tlv_op_rx(struct ath11k_base *ab, struct sk_buff *skb) struct wmi_cmd_hdr *cmd_hdr; enum wmi_tlv_event_id id; + if (skb->len < sizeof(*cmd_hdr)) + goto out; + cmd_hdr = (struct wmi_cmd_hdr *)skb->data; id = FIELD_GET(WMI_CMD_HDR_CMD_ID, (cmd_hdr->cmd_id)); trace_ath11k_wmi_event(ab, id, skb->data, skb->len); - if (skb_pull(skb, sizeof(struct wmi_cmd_hdr)) == NULL) - goto out; + skb_pull(skb, sizeof(*cmd_hdr)); switch (id) { /* Process all the WMI events here */ From 3826a4d293abb82826dc79ad682d7f885f125dc5 Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Mon, 13 Jul 2026 10:03:59 +0800 Subject: [PATCH 0507/1433] wifi: ath11k: add purwa-iot-evk and qcs6490-rb3gen2 to usecase firmware table Add purwa-iot-evk and qcs6490-rb3gen2 platform support to the usecase firmware lookup table for WCN6855 hw2.1. These platforms use the nfa765 firmware path for usecase-based firmware selection. Also reorder the table entries by compatible string. Tested-on: WCN6855 hw2.1 PCI WLAN.HSP.1.1-04685-QCAHSPSWPL_V1_V2_SILICONZ_IOE-1 Signed-off-by: Miaoqing Pan Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260713020359.3618193-1-miaoqing.pan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath11k/core.c b/drivers/net/wireless/ath/ath11k/core.c index 8dacc878c006..8039124e7832 100644 --- a/drivers/net/wireless/ath/ath11k/core.c +++ b/drivers/net/wireless/ath/ath11k/core.c @@ -1049,9 +1049,11 @@ static const struct __ath11k_core_usecase_firmware_table { const char *compatible; const char *firmware_name; } ath11k_core_usecase_firmware_table[] = { + { ATH11K_HW_WCN6855_HW21, "qcom,hamoa-iot-evk", "nfa765"}, { ATH11K_HW_WCN6855_HW21, "qcom,lemans-evk", "nfa765"}, { ATH11K_HW_WCN6855_HW21, "qcom,monaco-evk", "nfa765"}, - { ATH11K_HW_WCN6855_HW21, "qcom,hamoa-iot-evk", "nfa765"}, + { ATH11K_HW_WCN6855_HW21, "qcom,purwa-iot-evk", "nfa765"}, + { ATH11K_HW_WCN6855_HW21, "qcom,qcs6490-rb3gen2", "nfa765"}, { /* Sentinel */ } }; From 0b577e2fe06c023ab996c3d7684538dbbf6e99bc Mon Sep 17 00:00:00 2001 From: Johan Alvarado Date: Sat, 11 Jul 2026 23:31:58 -0500 Subject: [PATCH 0508/1433] net: dsa: realtek: rtl8365mb: add SGMII support for RTL8367S The RTL8367S can mux its embedded SerDes to external interface 1, which is typically used to connect the switch to a CPU port. The chip info table already declares SGMII as a supported interface mode for this chip, but the driver only implements RGMII so far. Implement SGMII support as a phylink PCS, with the configuration sequence derived from the GPL-licensed Realtek rtl8367c vendor driver as distributed in the Mercusys MR80X GPL code drop: - Add accessors for the SerDes indirect access registers (SDS_INDACS), through which the SerDes internal registers are reached. - Register a phylink_pcs for the SerDes, selected from mac_select_pcs for the SGMII interface, so the SerDes handling lives in the PCS operations rather than in the MAC operations. - Probe the SerDes tuning variant from the chip option register once at setup. The vendor driver keeps two sets of SerDes tuning parameters and selects between them based on this option; only the variant for a non-zero option (which all RTL8367S parts seen so far report) has been validated on hardware, so the SerDes interface modes are only advertised in that case. An unsupported variant thus fails at phylink validation time instead of at link configuration time. - Keep the embedded DW8051 microcontroller in reset and disabled. The vendor driver loads firmware into it to manage the SerDes link, but analysis of that firmware shows it only duplicates the link management phylink already performs: it polls the port status and writes the external interface force registers behind the driver's back. - Clear the line rate bypass bit for the external interface, tune the SerDes with the vendor-prescribed parameters, mux the SerDes to MAC8 in SGMII mode and only then take the SerDes out of reset, as the vendor driver does. - After deasserting the SerDes reset, reset the SerDes data path via the SerDes BMCR register to flush the FIFOs and resync the PLL. This mirrors what the vendor firmware does right after deasserting the SerDes reset, and ensures a clean link state from cold boot. - Force the SGMII link parameters (link, speed, duplex) in the SDS_MISC register from pcs_link_up(). SGMII in-band autonegotiation is not implemented, so only fixed-link and conventional PHY setups are supported, just like RGMII. This is reported to phylink through pcs_inband_caps() returning LINK_INBAND_DISABLE, so phylink never selects an in-band-enabled negotiation mode for this PCS. - Program the SerDes pause enables in SDS_MISC from the resolved pause modes when forcing the MAC external interface in mac_link_up, as the vendor driver does, rather than leaving whatever state the boot firmware left there. Flow control testing shows these bits, not the MAC force pause bits, gate pause on the SerDes external interface. This is done in the MAC layer because pcs_link_up() carries no pause information. - Implement pcs_get_state() by reading the link status from the SerDes, with the forced speed and duplex read back from SDS_MISC. Although the supported fixed-link and conventional PHY setups do not use it, the PCS owns the SerDes link state, and phylink consults pcs_get_state() to track the physical link when operating in in-band mode with autonegotiation disabled. The SerDes has no link interrupt wired up, so the PCS sets its poll flag. Tested on a Mercusys MR80X v2.20, where the RTL8367S is connected to the SoC over SGMII. Suggested-by: Luiz Angelo Daros de Luca Suggested-by: Maxime Chevallier Suggested-by: Mieczyslaw Nalewaj Signed-off-by: Johan Alvarado Reviewed-by: Maxime Chevallier Reviewed-by: Luiz Angelo Daros de Luca Reviewed-by: Mieczyslaw Nalewaj Tested-by: Stanislaw Pal Link: https://patch.msgid.link/20260711-rtl8367s-sgmii-v6-1-88f7944ddca7@c127.dev Signed-off-by: Jakub Kicinski --- drivers/net/dsa/realtek/rtl8365mb_main.c | 515 ++++++++++++++++++++++- 1 file changed, 511 insertions(+), 4 deletions(-) diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c index 5ac091bf93c9..ea03c42d0f1a 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_main.c +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c @@ -40,7 +40,8 @@ * driver has only been tested with a fixed-link, but in principle it should not * matter. * - * NOTE: Currently, only the RGMII interface is implemented in this driver. + * NOTE: Currently, only the RGMII and SGMII interfaces are implemented in this + * driver. * * The interrupt line is asserted on link UP/DOWN events. The driver creates a * custom irqchip to handle this interrupt and demultiplex the events by reading @@ -94,11 +95,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include "realtek.h" #include "realtek-smi.h" @@ -129,6 +132,7 @@ /* Chip reset register */ #define RTL8365MB_CHIP_RESET_REG 0x1322 +#define RTL8365MB_CHIP_RESET_DW8051_MASK 0x0010 #define RTL8365MB_CHIP_RESET_SW_MASK 0x0002 #define RTL8365MB_CHIP_RESET_HW_MASK 0x0001 @@ -238,6 +242,76 @@ #define RTL8365MB_EXT_RGMXF_RXDELAY_MASK 0x0007 #define RTL8365MB_EXT_RGMXF_TXDELAY_MASK 0x0008 +/* External interface line rate bypass register - one bit per external + * interface, indexed by the external port number with port 5 (the first + * external port) as the base. Other RTL8367 families index this register + * differently (e.g. the RTL8367R uses (id + 1) % 2), so this mapping only + * holds for the RTL8367C-style parts this driver supports. + */ +#define RTL8365MB_BYPASS_LINE_RATE_REG 0x03F7 +#define RTL8365MB_BYPASS_LINE_RATE_MASK(_port) BIT((_port) - 5) + +/* SerDes indirect access registers */ +#define RTL8365MB_SDS_INDACS_CMD_REG 0x6600 +#define RTL8365MB_SDS_INDACS_CMD_BUSY_MASK 0x0100 +#define RTL8365MB_SDS_INDACS_CMD_RUN_MASK 0x0080 +#define RTL8365MB_SDS_INDACS_CMD_WR_MASK 0x0040 +#define RTL8365MB_SDS_INDACS_ADR_REG 0x6601 +#define RTL8365MB_SDS_INDACS_DATA_REG 0x6602 + +/* SerDes miscellaneous configuration register */ +#define RTL8365MB_SDS_MISC_REG 0x1D11 +#define RTL8365MB_SDS_MISC_SGMII_RXFC_MASK 0x4000 +#define RTL8365MB_SDS_MISC_SGMII_TXFC_MASK 0x2000 +#define RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK 0x0800 +#define RTL8365MB_SDS_MISC_SGMII_FDUP_MASK 0x0400 +#define RTL8365MB_SDS_MISC_SGMII_LINK_MASK 0x0200 +#define RTL8365MB_SDS_MISC_SGMII_SPD_MASK 0x0180 +#define RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK 0x0040 + +/* SerDes internal registers, accessed via the SDS_INDACS registers. The BMCR + * data path reset holds BMCR_ANENABLE | BMCR_ISOLATE while toggling the + * vendor-specific low bits from phase 1 to phase 2, which triggers a data path + * reset and PLL resync. + */ +#define RTL8365MB_SDS_REG_BMCR 0x0000 +#define RTL8365MB_SDS_BMCR_DPRST_PHASE1 (BMCR_ANENABLE | BMCR_ISOLATE | 0x1) +#define RTL8365MB_SDS_BMCR_DPRST_PHASE2 (BMCR_ANENABLE | BMCR_ISOLATE | 0x3) +#define RTL8365MB_SDS_REG_NWAY 0x0002 +#define RTL8365MB_SDS_NWAY_EN_MASK 0x0200 +#define RTL8365MB_SDS_NWAY_RESTART_MASK 0x0100 +#define RTL8365MB_SDS_REG_RESET 0x0003 +#define RTL8365MB_SDS_RESET_DEASSERT 0x7106 +#define RTL8365MB_SDS_REG_LINK_STATUS 0x003d +#define RTL8365MB_SDS_LINK_STATUS_LINK_MASK 0x0010 + +/* The embedded SerDes can only be muxed to external interface 1 (MAC8), + * which is port 6. + */ +#define RTL8365MB_SDS_EXT_INTERFACE_ID 1 +#define RTL8365MB_SDS_EXT_INTERFACE_PORT 6 + +/* Line rate bypass bit for the SerDes external interface */ +#define RTL8365MB_SDS_BYPASS_LINE_RATE_MASK \ + RTL8365MB_BYPASS_LINE_RATE_MASK(RTL8365MB_SDS_EXT_INTERFACE_PORT) + +/* SerDes tuning parameter variant selector. The vendor driver picks between + * two sets of SerDes tuning parameters based on this chip option. Reading it + * requires first arming the read by writing a magic key to the arm register, + * then disarming it afterwards. + */ +#define RTL8365MB_SDS_OPTION_ARM_REG 0x13C0 +#define RTL8365MB_SDS_OPTION_ARM_KEY 0x0249 +#define RTL8365MB_SDS_OPTION_REG 0x13C1 + +/* Embedded DW8051 microcontroller control registers. The microcontroller + * can run firmware to manage the SerDes link, but this driver keeps it in + * reset and disabled: phylink already performs the link management that + * the firmware would otherwise do. + */ +#define RTL8365MB_MISC_CFG0_REG 0x130C +#define RTL8365MB_MISC_CFG0_DW8051_EN_MASK 0x0020 + /* External interface port speed values - used in DIGITAL_INTERFACE_FORCE */ #define RTL8365MB_PORT_SPEED_10M 0 #define RTL8365MB_PORT_SPEED_100M 1 @@ -551,6 +625,18 @@ static const struct rtl8365mb_jam_tbl_entry rtl8365mb_init_jam_common[] = { { 0x1D32, 0x0002 }, }; +/* SGMII SerDes tuning parameters, lifted from the vendor driver sources. The + * vendor driver keeps two variants of this table and selects between them + * based on the chip option register; these are the values for a non-zero + * option, which is what RTL8367S parts seen so far report. See + * rtl8365mb_sds_probe_option(). + */ +static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_sgmii[] = { + { 0x0480, 0x04D7 }, { 0x0481, 0xF994 }, { 0x0482, 0x2420 }, + { 0x0483, 0x6960 }, { 0x0484, 0x9728 }, { 0x0423, 0x9D85 }, + { 0x0424, 0xD810 }, { 0x002E, 0x83F2 }, +}; + enum rtl8365mb_phy_interface_mode { RTL8365MB_PHY_INTERFACE_MODE_INVAL = 0, RTL8365MB_PHY_INTERFACE_MODE_INTERNAL = BIT(0), @@ -730,6 +816,9 @@ struct rtl8365mb_port { * @cpu: CPU tagging and CPU port configuration for this chip * @mib_lock: prevent concurrent reads of MIB counters * @ports: per-port data + * @pcs: PCS for the SerDes external interface + * @sds_supported: SerDes tuning parameters match the chip option, so the + * SerDes interface modes can be advertised * * Private data for this driver. */ @@ -740,8 +829,12 @@ struct rtl8365mb { struct rtl8365mb_cpu cpu; struct mutex mib_lock; struct rtl8365mb_port ports[RTL8365MB_MAX_NUM_PORTS]; + struct phylink_pcs pcs; + bool sds_supported; }; +#define pcs_to_rtl8365mb(_pcs) container_of((_pcs), struct rtl8365mb, pcs) + static int rtl8365mb_phy_poll_busy(struct realtek_priv *priv) { u32 val; @@ -1042,6 +1135,334 @@ static int rtl8365mb_ext_config_rgmii(struct realtek_priv *priv, int port, return 0; } +static int rtl8365mb_sds_write(struct realtek_priv *priv, u16 addr, u16 data) +{ + int ret; + + ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_DATA_REG, data); + if (ret) + return ret; + + ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_ADR_REG, addr); + if (ret) + return ret; + + /* The SerDes indirect access engine completes the command within the + * register write transaction, so there is no need to wait or poll for + * completion before the next access, matching the vendor driver. + */ + return regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG, + RTL8365MB_SDS_INDACS_CMD_RUN_MASK | + RTL8365MB_SDS_INDACS_CMD_WR_MASK); +} + +static int rtl8365mb_sds_read(struct realtek_priv *priv, u16 addr, u16 *data) +{ + u32 val; + int ret; + + ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_ADR_REG, addr); + if (ret) + return ret; + + ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG, + RTL8365MB_SDS_INDACS_CMD_RUN_MASK); + if (ret) + return ret; + + /* Wait for the indirect read to complete: the engine clears the BUSY + * bit once the data register holds the result. + */ + ret = regmap_read_poll_timeout(priv->map, RTL8365MB_SDS_INDACS_CMD_REG, + val, + !(val & RTL8365MB_SDS_INDACS_CMD_BUSY_MASK), + 10, 1000); + if (ret) + return ret; + + ret = regmap_read(priv->map, RTL8365MB_SDS_INDACS_DATA_REG, &val); + if (ret) + return ret; + + *data = val; + + return 0; +} + +/* The vendor driver selects between two sets of SerDes tuning parameters based + * on the chip option register. Only the variant for a non-zero option has been + * tested on real hardware - the RTL8367S parts seen so far all report 1. The + * variant for option 0 uses different tuning values that cannot be verified, + * so probe the option once at setup and only advertise the SerDes interface + * modes when the tuning parameters are known to match, so that an unsupported + * variant fails at phylink validation time rather than when configuring the + * link. + */ +static int rtl8365mb_sds_probe_option(struct realtek_priv *priv) +{ + struct rtl8365mb *mb = priv->chip_data; + const struct rtl8365mb_extint *extint; + u32 option; + int ret; + int i; + + /* Nothing to probe if no external interface is wired to the SerDes */ + for (i = 0; i < RTL8365MB_MAX_NUM_EXTINTS; i++) { + extint = &mb->chip_info->extints[i]; + + if (extint->supported_interfaces & + (RTL8365MB_PHY_INTERFACE_MODE_SGMII | + RTL8365MB_PHY_INTERFACE_MODE_HSGMII)) + break; + } + if (i == RTL8365MB_MAX_NUM_EXTINTS) + return 0; + + ret = regmap_write(priv->map, RTL8365MB_SDS_OPTION_ARM_REG, + RTL8365MB_SDS_OPTION_ARM_KEY); + if (ret) + return ret; + + ret = regmap_read(priv->map, RTL8365MB_SDS_OPTION_REG, &option); + if (ret) + return ret; + + ret = regmap_write(priv->map, RTL8365MB_SDS_OPTION_ARM_REG, 0); + if (ret) + return ret; + + if (option == 0) { + dev_warn(priv->dev, + "unsupported SerDes tuning variant (chip option 0), disabling SerDes interface modes\n"); + return 0; + } + + mb->sds_supported = true; + + return 0; +} + +static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, + phy_interface_t interface, + const unsigned long *advertising, + bool permit_pause_to_mac) +{ + const int id = RTL8365MB_SDS_EXT_INTERFACE_ID; + struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs); + struct realtek_priv *priv; + u16 val; + int ret; + int i; + + priv = mb->priv; + + /* Hold the embedded DW8051 microcontroller in reset and keep it + * disabled. The vendor driver loads firmware into it to manage the + * SerDes link, but the firmware only duplicates work that phylink + * already does: it polls the port status and forces the external + * interface configuration in the very registers this driver manages. + * Letting it run would race with phylink. + */ + ret = regmap_update_bits(priv->map, RTL8365MB_CHIP_RESET_REG, + RTL8365MB_CHIP_RESET_DW8051_MASK, + RTL8365MB_CHIP_RESET_DW8051_MASK); + if (ret) + return ret; + + ret = regmap_update_bits(priv->map, RTL8365MB_MISC_CFG0_REG, + RTL8365MB_MISC_CFG0_DW8051_EN_MASK, 0); + if (ret) + return ret; + + /* The vendor driver clears the line rate bypass for all interface + * modes except TMII. + */ + ret = regmap_update_bits(priv->map, RTL8365MB_BYPASS_LINE_RATE_REG, + RTL8365MB_SDS_BYPASS_LINE_RATE_MASK, 0); + if (ret) + return ret; + + /* Tune the SerDes with vendor-prescribed parameters */ + for (i = 0; i < ARRAY_SIZE(rtl8365mb_sds_jam_sgmii); i++) { + ret = rtl8365mb_sds_write(priv, + rtl8365mb_sds_jam_sgmii[i].reg, + rtl8365mb_sds_jam_sgmii[i].val); + if (ret) + return ret; + } + + /* Mux the SerDes to MAC8 in SGMII mode */ + ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG, + RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK | + RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK, + RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK); + if (ret) + return ret; + + val = RTL8365MB_EXT_PORT_MODE_SGMII + << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id); + ret = regmap_update_bits(priv->map, + RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(id), + RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(id), + val); + if (ret) + return ret; + + /* Take the SerDes out of reset. The vendor driver does this only + * after the SerDes mux and the interface mode are configured. + */ + ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_RESET, + RTL8365MB_SDS_RESET_DEASSERT); + if (ret) + return ret; + + /* Reset the SerDes data path and resync its PLL, mirroring what the + * vendor firmware does right after deasserting the SerDes reset. + * This flushes the FIFOs and ensures a clean state for the link, + * preventing silent drops and CRC errors. + */ + ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_BMCR, + RTL8365MB_SDS_BMCR_DPRST_PHASE1); + if (ret) + return ret; + + ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_BMCR, + RTL8365MB_SDS_BMCR_DPRST_PHASE2); + if (ret) + return ret; + + /* Keep SGMII in-band autonegotiation disabled: the link parameters are + * forced from rtl8365mb_pcs_link_up() instead. + */ + ret = rtl8365mb_sds_read(priv, RTL8365MB_SDS_REG_NWAY, &val); + if (ret) + return ret; + + val &= ~RTL8365MB_SDS_NWAY_EN_MASK; + val |= RTL8365MB_SDS_NWAY_RESTART_MASK; + + return rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_NWAY, val); +} + +static bool rtl8365mb_interface_is_serdes(phy_interface_t interface) +{ + return interface == PHY_INTERFACE_MODE_SGMII; +} + +static unsigned int rtl8365mb_pcs_inband_caps(struct phylink_pcs *pcs, + phy_interface_t interface) +{ + /* In-band autonegotiation is not implemented; the link is always + * forced. Report that to phylink so that it never selects an + * in-band-enabled negotiation mode for this PCS. + */ + return LINK_INBAND_DISABLE; +} + +static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs, + unsigned int neg_mode, + struct phylink_link_state *state) +{ + struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs); + struct realtek_priv *priv = mb->priv; + u16 status; + u32 val; + int ret; + + /* In-band autonegotiation is not implemented, so the link parameters are + * forced from rtl8365mb_pcs_link_up(). The real link state must still be + * read from the SerDes itself: the embedded DW8051 microcontroller that + * the vendor firmware uses to poll the SerDes is kept disabled (see + * rtl8365mb_pcs_config()), so the link status register can be read + * directly through the SDS_INDACS window without racing the auto-poll. + */ + ret = rtl8365mb_sds_read(priv, RTL8365MB_SDS_REG_LINK_STATUS, &status); + if (ret) { + state->link = false; + return; + } + + state->link = !!(status & RTL8365MB_SDS_LINK_STATUS_LINK_MASK); + state->an_complete = state->link; + if (!state->link) + return; + + /* The speed and duplex are forced; read them back from the values + * programmed into the SerDes MISC register. + */ + ret = regmap_read(priv->map, RTL8365MB_SDS_MISC_REG, &val); + if (ret) { + state->link = false; + return; + } + + state->duplex = (val & RTL8365MB_SDS_MISC_SGMII_FDUP_MASK) ? + DUPLEX_FULL : DUPLEX_HALF; + + switch (FIELD_GET(RTL8365MB_SDS_MISC_SGMII_SPD_MASK, val)) { + case RTL8365MB_PORT_SPEED_1000M: + state->speed = SPEED_1000; + break; + case RTL8365MB_PORT_SPEED_100M: + state->speed = SPEED_100; + break; + case RTL8365MB_PORT_SPEED_10M: + state->speed = SPEED_10; + break; + } +} + +static void rtl8365mb_pcs_link_up(struct phylink_pcs *pcs, + unsigned int neg_mode, + phy_interface_t interface, int speed, + int duplex) +{ + struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs); + struct realtek_priv *priv = mb->priv; + u32 mask = RTL8365MB_SDS_MISC_SGMII_FDUP_MASK | + RTL8365MB_SDS_MISC_SGMII_LINK_MASK | + RTL8365MB_SDS_MISC_SGMII_SPD_MASK; + u32 val = RTL8365MB_SDS_MISC_SGMII_LINK_MASK; + u32 r_speed; + int ret; + + if (speed == SPEED_1000) { + r_speed = RTL8365MB_PORT_SPEED_1000M; + } else if (speed == SPEED_100) { + r_speed = RTL8365MB_PORT_SPEED_100M; + } else if (speed == SPEED_10) { + r_speed = RTL8365MB_PORT_SPEED_10M; + } else { + dev_err(priv->dev, "unsupported SerDes speed %s\n", + phy_speed_to_str(speed)); + return; + } + + val |= FIELD_PREP(RTL8365MB_SDS_MISC_SGMII_SPD_MASK, r_speed); + + if (duplex == DUPLEX_FULL) + val |= RTL8365MB_SDS_MISC_SGMII_FDUP_MASK; + + /* pcs_link_up() carries no pause information, so the SerDes flow + * control bits are programmed together with the MAC external interface + * force from rtl8365mb_phylink_mac_link_up(), where the resolved pause + * modes are known. + */ + ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG, mask, val); + if (ret) { + dev_err(priv->dev, "failed to force SerDes link: %pe\n", + ERR_PTR(ret)); + return; + } +} + +static const struct phylink_pcs_ops rtl8365mb_pcs_ops = { + .pcs_inband_caps = rtl8365mb_pcs_inband_caps, + .pcs_config = rtl8365mb_pcs_config, + .pcs_get_state = rtl8365mb_pcs_get_state, + .pcs_link_up = rtl8365mb_pcs_link_up, +}; + static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port, bool link, int speed, int duplex, bool tx_pause, bool rx_pause) @@ -1118,6 +1539,8 @@ static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port, { const struct rtl8365mb_extint *extint = rtl8365mb_get_port_extint(ds->priv, port); + struct realtek_priv *priv = ds->priv; + struct rtl8365mb *mb = priv->chip_data; config->mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE | MAC_10 | MAC_100 | MAC_1000FD; @@ -1141,6 +1564,25 @@ static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port, if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_RGMII) phy_interface_set_rgmii(config->supported_interfaces); + + if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_SGMII && + mb->sds_supported) + __set_bit(PHY_INTERFACE_MODE_SGMII, + config->supported_interfaces); +} + +static struct phylink_pcs * +rtl8365mb_phylink_mac_select_pcs(struct phylink_config *config, + phy_interface_t interface) +{ + struct dsa_port *dp = dsa_phylink_to_port(config); + struct realtek_priv *priv = dp->ds->priv; + struct rtl8365mb *mb = priv->chip_data; + + if (rtl8365mb_interface_is_serdes(interface)) + return &mb->pcs; + + return NULL; } static void rtl8365mb_phylink_mac_config(struct phylink_config *config, @@ -1168,6 +1610,12 @@ static void rtl8365mb_phylink_mac_config(struct phylink_config *config, return; } + /* SGMII is handled by the SerDes PCS, configured through the + * phylink_pcs ops, so there is nothing to do here for it. + */ + if (rtl8365mb_interface_is_serdes(state->interface)) + return; + /* TODO: Implement MII and RMII modes, which the RTL8365MB-VC also * supports */ @@ -1188,7 +1636,13 @@ static void rtl8365mb_phylink_mac_link_down(struct phylink_config *config, p = &mb->ports[port]; cancel_delayed_work_sync(&p->mib_work); - if (phy_interface_mode_is_rgmii(interface)) { + /* phylink has no pcs_link_down callback, so on the SerDes path only the + * MAC external interface force is reset here. Clearing the MAC force is + * enough to bring the link down; the SerDes keeps presenting its last + * forced state until the next pcs_link_up() reprograms it. + */ + if (phy_interface_mode_is_rgmii(interface) || + rtl8365mb_interface_is_serdes(interface)) { ret = rtl8365mb_ext_config_forcemode(priv, port, false, 0, 0, false, false); if (ret) @@ -1218,14 +1672,51 @@ static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config, p = &mb->ports[port]; schedule_delayed_work(&p->mib_work, 0); - if (phy_interface_mode_is_rgmii(interface)) { + /* The SerDes forced link state is programmed by the PCS in + * rtl8365mb_pcs_link_up(); here only the MAC external interface force + * is configured, for both RGMII and SerDes. + */ + if (phy_interface_mode_is_rgmii(interface) || + rtl8365mb_interface_is_serdes(interface)) { ret = rtl8365mb_ext_config_forcemode(priv, port, true, speed, duplex, tx_pause, rx_pause); - if (ret) + if (ret) { dev_err(priv->dev, "failed to force mode on port %d: %pe\n", port, ERR_PTR(ret)); + return; + } + + /* The SerDes has its own pause enables; program them from + * the resolved pause modes, as the vendor driver does when + * forcing the link on a SerDes external interface. These + * bits, not the MAC force pause bits, gate pause on the + * SerDes external interface: flow control testing shows + * that pause frames are only emitted with the SerDes TXFC + * bit set, while the MAC force pause bits alone have no + * effect on this port. This is done here rather than in + * rtl8365mb_pcs_link_up() because pcs_link_up() carries no + * pause information. + */ + if (rtl8365mb_interface_is_serdes(interface)) { + u32 val = 0; + + if (tx_pause) + val |= RTL8365MB_SDS_MISC_SGMII_TXFC_MASK; + if (rx_pause) + val |= RTL8365MB_SDS_MISC_SGMII_RXFC_MASK; + + ret = regmap_update_bits(priv->map, + RTL8365MB_SDS_MISC_REG, + RTL8365MB_SDS_MISC_SGMII_TXFC_MASK | + RTL8365MB_SDS_MISC_SGMII_RXFC_MASK, + val); + if (ret) + dev_err(priv->dev, + "failed to force SerDes pause modes on port %d: %pe\n", + port, ERR_PTR(ret)); + } return; } @@ -2419,6 +2910,14 @@ static int rtl8365mb_setup(struct dsa_switch *ds) mb = priv->chip_data; cpu = &mb->cpu; + mb->pcs.ops = &rtl8365mb_pcs_ops; + + /* The SerDes has no link interrupt wired up, so phylink must poll the + * PCS for link changes when it tracks the link through pcs_get_state() + * (in-band mode with autonegotiation disabled). + */ + mb->pcs.poll = true; + ret = rtl8365mb_reset_chip(priv); if (ret) { dev_err(priv->dev, "failed to reset chip: %pe\n", @@ -2426,6 +2925,13 @@ static int rtl8365mb_setup(struct dsa_switch *ds) goto out_error; } + ret = rtl8365mb_sds_probe_option(priv); + if (ret) { + dev_err(priv->dev, "failed to probe SerDes chip option: %pe\n", + ERR_PTR(ret)); + goto out_error; + } + /* Configure switch to vendor-defined initial state */ ret = rtl8365mb_switch_init(priv); if (ret) { @@ -2658,6 +3164,7 @@ static int rtl8365mb_detect(struct realtek_priv *priv) } static const struct phylink_mac_ops rtl8365mb_phylink_mac_ops = { + .mac_select_pcs = rtl8365mb_phylink_mac_select_pcs, .mac_config = rtl8365mb_phylink_mac_config, .mac_link_down = rtl8365mb_phylink_mac_link_down, .mac_link_up = rtl8365mb_phylink_mac_link_up, From 987137345f3312fd68cc9c11bd46b754bfb0046f Mon Sep 17 00:00:00 2001 From: Johan Alvarado Date: Sat, 11 Jul 2026 23:31:59 -0500 Subject: [PATCH 0509/1433] net: dsa: realtek: rtl8365mb: add HSGMII support for RTL8367S MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In addition to SGMII, the RTL8367S SerDes also supports HSGMII, which carries 2.5 Gbps with the same signaling as SGMII at 2.5x clock rate. The chip info table already declares HSGMII as a supported interface mode for external interface 1. Extend the SerDes PCS to handle HSGMII, which phylink represents as 2500base-x: - Select the HSGMII SerDes tuning parameters and external interface mode, and mux the SerDes to MAC8 in HSGMII mode, from pcs_config() according to the interface. The parameters are again lifted from the GPL-licensed Realtek rtl8367c vendor driver, and again only cover the tuning variant for a non-zero chip option, so the mode is gated on the option probed at setup. - Advertise 2500base-x and MAC_2500FD on ports whose external interface supports HSGMII. - Accept SPEED_2500 in the forced link configuration. The MAC speed field has no 2.5 Gbps value: the rate is determined by the HSGMII SerDes configuration, and the vendor driver programs the 1 Gbps value here, so do the same. - Raise the port 6 ingress and egress rate limiters to their maximum at setup time, as the vendor switch init does unconditionally for the whole chip family. The chip resets them to 0x1FFFF (~1.048 Gbps in units of 8 Kbps), which caps the aggregate HSGMII throughput at roughly 1 Gbps. The vendor documentation describes the reset default as disabling the limiter, but the cap is real: on an RTL8367S-based Mercusys MR85X running an OpenWrt backport of this series, several clients on 1 Gbps user ports were limited to about 1.02 Gbps combined across the HSGMII CPU port until these limiters were raised, after which throughput reached about 2 Gbps [1]. The related HSGMII scheduler line rate (LINE_RATE_HSG_H) is already set to its maximum by the common init jam table. Tested on a Mercusys MR80X v2.20, where the RTL8367S is connected to the SoC over HSGMII. Link: https://github.com/openwrt/openwrt/pull/19445#issuecomment-4505613294 [1] Suggested-by: Luiz Angelo Daros de Luca Suggested-by: Mieczyslaw Nalewaj Signed-off-by: Johan Alvarado Tested-by: StanisÅ‚aw Pal Reviewed-by: Luiz Angelo Daros de Luca Reviewed-by: Mieczyslaw Nalewaj Tested-by: Stanislaw Pal Link: https://patch.msgid.link/20260711-rtl8367s-sgmii-v6-2-88f7944ddca7@c127.dev Signed-off-by: Jakub Kicinski --- drivers/net/dsa/realtek/rtl8365mb_main.c | 134 ++++++++++++++++++++--- 1 file changed, 118 insertions(+), 16 deletions(-) diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c index ea03c42d0f1a..d1ba0cc9426f 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_main.c +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c @@ -40,8 +40,8 @@ * driver has only been tested with a fixed-link, but in principle it should not * matter. * - * NOTE: Currently, only the RGMII and SGMII interfaces are implemented in this - * driver. + * NOTE: Currently, only the RGMII, SGMII and HSGMII interfaces are implemented + * in this driver. * * The interrupt line is asserted on link UP/DOWN events. The driver creates a * custom irqchip to handle this interrupt and demultiplex the events by reading @@ -251,6 +251,18 @@ #define RTL8365MB_BYPASS_LINE_RATE_REG 0x03F7 #define RTL8365MB_BYPASS_LINE_RATE_MASK(_port) BIT((_port) - 5) +/* Port 6 ingress and egress rate limiter registers. Each limit is a 19-bit + * value in units of 8 Kbps, split across a 16-bit LSB register (CTRL0) and a + * 3-bit MSB field (CTRL1). The chip resets them to 0x1FFFF; see + * rtl8365mb_sds_raise_rate_limits(). + */ +#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL0_REG 0x00CF +#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_REG 0x00D0 +#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_MASK 0x0007 +#define RTL8365MB_PORT6_EGRESSBW_CTRL0_REG 0x0398 +#define RTL8365MB_PORT6_EGRESSBW_CTRL1_REG 0x0399 +#define RTL8365MB_PORT6_EGRESSBW_CTRL1_MASK 0x0007 + /* SerDes indirect access registers */ #define RTL8365MB_SDS_INDACS_CMD_REG 0x6600 #define RTL8365MB_SDS_INDACS_CMD_BUSY_MASK 0x0100 @@ -637,6 +649,18 @@ static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_sgmii[] = { { 0x0424, 0xD810 }, { 0x002E, 0x83F2 }, }; +/* HSGMII SerDes tuning parameters, lifted from the vendor driver sources. As + * with the SGMII table, the vendor driver keeps several variants and selects + * one based on the chip option register; these are the values for a non-zero + * option, which is what RTL8367S parts seen so far report. See + * rtl8365mb_sds_probe_option(). + */ +static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_hsgmii[] = { + { 0x0500, 0x82F0 }, { 0x0501, 0xF195 }, { 0x0502, 0x31A2 }, + { 0x0503, 0x7960 }, { 0x0504, 0x9728 }, { 0x0423, 0x9D85 }, + { 0x0424, 0xD810 }, { 0x0001, 0x0F80 }, { 0x002E, 0x83F2 }, +}; + enum rtl8365mb_phy_interface_mode { RTL8365MB_PHY_INTERFACE_MODE_INVAL = 0, RTL8365MB_PHY_INTERFACE_MODE_INTERNAL = BIT(0), @@ -1242,20 +1266,70 @@ static int rtl8365mb_sds_probe_option(struct realtek_priv *priv) return 0; } +/* The vendor driver raises the port 6 ingress and egress rate limiters to + * their maximum in its switch init, unconditionally for the whole chip + * family. The chip reset in rtl8365mb_setup() puts them back to their reset + * default of 0x1FFFF, a ~1.048 Gbps limit which caps the aggregate + * throughput of an HSGMII CPU port at roughly 1 Gbps. The vendor + * documentation describes the reset default as disabling the limiter, but + * the cap has been observed on hardware. Raise them likewise, to 0x7FFFF + * (~4.19 Gbps, above the HSGMII line rate). The related HSGMII scheduler + * line rate register (LINE_RATE_HSG_H, 0x03FA) is already set to its + * maximum by the common init jam table. + */ +static int rtl8365mb_sds_raise_rate_limits(struct realtek_priv *priv) +{ + int ret; + + ret = regmap_write(priv->map, RTL8365MB_INGRESSBW_PORT6_RATE_CTRL0_REG, + 0xFFFF); + if (ret) + return ret; + + ret = regmap_update_bits(priv->map, + RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_REG, + RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_MASK, + RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_MASK); + if (ret) + return ret; + + ret = regmap_write(priv->map, RTL8365MB_PORT6_EGRESSBW_CTRL0_REG, + 0xFFFF); + if (ret) + return ret; + + return regmap_update_bits(priv->map, RTL8365MB_PORT6_EGRESSBW_CTRL1_REG, + RTL8365MB_PORT6_EGRESSBW_CTRL1_MASK, + RTL8365MB_PORT6_EGRESSBW_CTRL1_MASK); +} + static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, phy_interface_t interface, const unsigned long *advertising, bool permit_pause_to_mac) { + const struct rtl8365mb_jam_tbl_entry *sds_jam; const int id = RTL8365MB_SDS_EXT_INTERFACE_ID; struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs); struct realtek_priv *priv; + size_t sds_jam_size; + u32 mode; u16 val; int ret; int i; priv = mb->priv; + if (interface == PHY_INTERFACE_MODE_2500BASEX) { + sds_jam = rtl8365mb_sds_jam_hsgmii; + sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_hsgmii); + mode = RTL8365MB_EXT_PORT_MODE_HSGMII; + } else { + sds_jam = rtl8365mb_sds_jam_sgmii; + sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_sgmii); + mode = RTL8365MB_EXT_PORT_MODE_SGMII; + } + /* Hold the embedded DW8051 microcontroller in reset and keep it * disabled. The vendor driver loads firmware into it to manage the * SerDes link, but the firmware only duplicates work that phylink @@ -1283,24 +1357,24 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, return ret; /* Tune the SerDes with vendor-prescribed parameters */ - for (i = 0; i < ARRAY_SIZE(rtl8365mb_sds_jam_sgmii); i++) { - ret = rtl8365mb_sds_write(priv, - rtl8365mb_sds_jam_sgmii[i].reg, - rtl8365mb_sds_jam_sgmii[i].val); + for (i = 0; i < sds_jam_size; i++) { + ret = rtl8365mb_sds_write(priv, sds_jam[i].reg, + sds_jam[i].val); if (ret) return ret; } - /* Mux the SerDes to MAC8 in SGMII mode */ + /* Mux the SerDes to MAC8 in the requested mode */ ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG, RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK | RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK, - RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK); + mode == RTL8365MB_EXT_PORT_MODE_SGMII ? + RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK : + RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK); if (ret) return ret; - val = RTL8365MB_EXT_PORT_MODE_SGMII - << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id); + val = mode << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id); ret = regmap_update_bits(priv->map, RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(id), RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(id), @@ -1346,7 +1420,8 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, static bool rtl8365mb_interface_is_serdes(phy_interface_t interface) { - return interface == PHY_INTERFACE_MODE_SGMII; + return interface == PHY_INTERFACE_MODE_SGMII || + interface == PHY_INTERFACE_MODE_2500BASEX; } static unsigned int rtl8365mb_pcs_inband_caps(struct phylink_pcs *pcs, @@ -1401,7 +1476,9 @@ static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs, switch (FIELD_GET(RTL8365MB_SDS_MISC_SGMII_SPD_MASK, val)) { case RTL8365MB_PORT_SPEED_1000M: - state->speed = SPEED_1000; + state->speed = + state->interface == PHY_INTERFACE_MODE_2500BASEX ? + SPEED_2500 : SPEED_1000; break; case RTL8365MB_PORT_SPEED_100M: state->speed = SPEED_100; @@ -1426,7 +1503,11 @@ static void rtl8365mb_pcs_link_up(struct phylink_pcs *pcs, u32 r_speed; int ret; - if (speed == SPEED_1000) { + /* The speed field has no value for 2.5 Gbps: the rate is determined by + * the HSGMII SerDes configuration, and the vendor driver programs the + * 1 Gbps value here. + */ + if (speed == SPEED_2500 || speed == SPEED_1000) { r_speed = RTL8365MB_PORT_SPEED_1000M; } else if (speed == SPEED_100) { r_speed = RTL8365MB_PORT_SPEED_100M; @@ -1486,7 +1567,11 @@ static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port, r_rx_pause = rx_pause ? 1 : 0; r_tx_pause = tx_pause ? 1 : 0; - if (speed == SPEED_1000) { + /* The speed field has no value for 2.5 Gbps: the rate is + * determined by the HSGMII SerDes configuration, and the + * vendor driver programs the 1 Gbps value here. + */ + if (speed == SPEED_2500 || speed == SPEED_1000) { r_speed = RTL8365MB_PORT_SPEED_1000M; } else if (speed == SPEED_100) { r_speed = RTL8365MB_PORT_SPEED_100M; @@ -1569,6 +1654,13 @@ static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port, mb->sds_supported) __set_bit(PHY_INTERFACE_MODE_SGMII, config->supported_interfaces); + + if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_HSGMII && + mb->sds_supported) { + __set_bit(PHY_INTERFACE_MODE_2500BASEX, + config->supported_interfaces); + config->mac_capabilities |= MAC_2500FD; + } } static struct phylink_pcs * @@ -1610,8 +1702,8 @@ static void rtl8365mb_phylink_mac_config(struct phylink_config *config, return; } - /* SGMII is handled by the SerDes PCS, configured through the - * phylink_pcs ops, so there is nothing to do here for it. + /* SGMII and 2500base-x are handled by the SerDes PCS, configured + * through the phylink_pcs ops, so nothing to do here for them. */ if (rtl8365mb_interface_is_serdes(state->interface)) return; @@ -2940,6 +3032,16 @@ static int rtl8365mb_setup(struct dsa_switch *ds) goto out_error; } + if (mb->sds_supported) { + ret = rtl8365mb_sds_raise_rate_limits(priv); + if (ret) { + dev_err(priv->dev, + "failed to raise port rate limits: %pe\n", + ERR_PTR(ret)); + goto out_error; + } + } + /* Set up cascading IRQs */ ret = rtl8365mb_irq_setup(priv); if (ret == -EPROBE_DEFER) From 24d0af194bcca043bd82f8a0842a051cdde266ee Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 21 Jul 2026 16:39:50 +0000 Subject: [PATCH 0510/1433] geneve: fix geneve_config leak on register_netdevice() failure When geneve_configure() allocates a new geneve_config structure via geneve_config_alloc() and assigns it to geneve->cfg before calling register_netdevice(), if register_netdevice() fails early (for example, in dev_get_valid_name() due to an invalid or duplicate interface name), register_netdevice() exits without calling dev->priv_destructor. The caller (e.g. rtnl_newlink()) subsequently calls free_netdev(), which frees the net_device structure directly via kvfree() because reg_state is NETREG_UNINITIALIZED, bypassing dev->priv_destructor (geneve_free_dev()). As a result, the newly allocated geneve_config and its per-CPU dst_cache are leaked. Fix this by invoking geneve_free_dev(dev) directly on the error path of register_netdevice(). Since geneve_free_dev() sets geneve->cfg to NULL, this call is fully idempotent and safe even if register_netdevice() failed on a later error path that already ran dev->priv_destructor. Fixes: 0ba269933f73 ("geneve: convert config to RCU-protected pointer") Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260721163950.1483019-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/geneve.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index 359d6da7348c..9a9e357fca1a 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -2045,8 +2045,10 @@ static int geneve_configure(struct net *net, struct net_device *dev, } err = register_netdevice(dev); - if (err) + if (err) { + geneve_free_dev(dev); return err; + } list_add(&geneve->next, &gn->geneve_list); return 0; From d6587d0c0f3d6ffe0be9ddc5569f0e4b1bb2ef7b Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Mon, 20 Jul 2026 05:27:10 -0700 Subject: [PATCH 0511/1433] selftests/net: Test PACKET_STATISTICS Update the existing packet socket test to include a test for the sockopt PACKET_STATISTICS. Signed-off-by: Joe Damato Reviewed-by: Willem de Bruijn Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/psock_snd.c | 32 ++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/net/psock_snd.c b/tools/testing/selftests/net/psock_snd.c index edf1e6f80d41..5be481a3d2bd 100644 --- a/tools/testing/selftests/net/psock_snd.c +++ b/tools/testing/selftests/net/psock_snd.c @@ -359,6 +359,34 @@ static void parse_opts(int argc, char **argv) error(1, 0, "option gso (-g) requires csum offload (-c)"); } +static void check_packet_stats(int fd) +{ + struct tpacket_stats st = {}; + socklen_t len = sizeof(st); + + if (getsockopt(fd, SOL_PACKET, PACKET_STATISTICS, &st, &len)) + error(1, errno, "getsockopt packet statistics"); + + if (st.tp_packets != 1) + error(1, 0, "stats: tp_packets %u != 1", st.tp_packets); + + if (st.tp_drops != 0) + error(1, 0, "stats: tp_drops %u != 0", st.tp_drops); + + /* verify clear on read */ + memset(&st, 0xff, sizeof(st)); + len = sizeof(st); + + if (getsockopt(fd, SOL_PACKET, PACKET_STATISTICS, &st, &len)) + error(1, errno, "getsockopt packet statistics"); + + if (st.tp_packets != 0) + error(1, 0, "stats: tp_packets %u != 0 after clear", st.tp_packets); + + if (st.tp_drops != 0) + error(1, 0, "stats: tp_drops %u != 0 after clear", st.tp_drops); +} + static void run_test(void) { int fdr, fds, total_len; @@ -369,9 +397,11 @@ static void run_test(void) total_len = do_tx(); /* BPF filter accepts only this length, vlan changes MAC */ - if (cfg_payload_len == DATA_LEN && !cfg_use_vlan) + if (cfg_payload_len == DATA_LEN && !cfg_use_vlan) { do_rx(fds, total_len - sizeof(struct virtio_net_hdr), tbuf + sizeof(struct virtio_net_hdr)); + check_packet_stats(fds); + } do_rx(fdr, cfg_payload_len, tbuf + total_len - cfg_payload_len); From 1451e5302941988cc2da2d2c0bc3e2f7b9f93451 Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Mon, 20 Jul 2026 05:27:11 -0700 Subject: [PATCH 0512/1433] selftests/net: Test PACKET_STATISTICS drops Extend psock_snd to test drops by setting a tiny receive buffer and sending a large burst of packets. Signed-off-by: Joe Damato Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260720122714.759175-3-joe@dama.to Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/psock_snd.c | 48 ++++++++++++++++++++---- tools/testing/selftests/net/psock_snd.sh | 5 +++ 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/tools/testing/selftests/net/psock_snd.c b/tools/testing/selftests/net/psock_snd.c index 5be481a3d2bd..81096df5cffc 100644 --- a/tools/testing/selftests/net/psock_snd.c +++ b/tools/testing/selftests/net/psock_snd.c @@ -39,6 +39,7 @@ static bool cfg_use_gso; static bool cfg_use_qdisc_bypass; static bool cfg_use_vlan; static bool cfg_use_vnet; +static bool cfg_drop; static char *cfg_ifname = "lo"; static int cfg_mtu = 1500; @@ -49,6 +50,8 @@ static uint16_t cfg_port = 8000; /* test sending up to max mtu + 1 */ #define TEST_SZ (sizeof(struct virtio_net_hdr) + ETH_HLEN + ETH_MAX_MTU + 1) +#define BURST_CNT (1000) + static char tbuf[TEST_SZ], rbuf[TEST_SZ]; static unsigned long add_csum_hword(const uint16_t *start, int num_u16) @@ -212,13 +215,14 @@ static void do_send(int fd, char *buf, int len) if (ret != len) error(1, 0, "write: %u %u", ret, len); - fprintf(stderr, "tx: %u\n", ret); + if (!cfg_drop) + fprintf(stderr, "tx: %u\n", ret); } static int do_tx(void) { const int one = 1; - int fd, len; + int i, fd, len; fd = socket(PF_PACKET, cfg_use_dgram ? SOCK_DGRAM : SOCK_RAW, 0); if (fd == -1) @@ -242,6 +246,10 @@ static int do_tx(void) do_send(fd, tbuf, len); + if (cfg_drop) + for (i = 0; i < BURST_CNT; i++) + do_send(fd, tbuf, len); + if (close(fd)) error(1, errno, "close t"); @@ -290,6 +298,7 @@ static void do_rx(int fd, int expected_len, char *expected) static int setup_sniffer(void) { struct timeval tv = { .tv_usec = 100 * 1000 }; + const int one = 1; int fd; fd = socket(PF_PACKET, SOCK_RAW, 0); @@ -299,6 +308,10 @@ static int setup_sniffer(void) if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) error(1, errno, "setsockopt rcv timeout"); + if (cfg_drop) + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &one, sizeof(one))) + error(1, errno, "setsockopt SO_RCVBUF"); + pair_udp_setfilter(fd); do_bind(fd); @@ -309,7 +322,7 @@ static void parse_opts(int argc, char **argv) { int c; - while ((c = getopt(argc, argv, "bcCdgl:qt:vV")) != -1) { + while ((c = getopt(argc, argv, "bcCdDgl:qt:vV")) != -1) { switch (c) { case 'b': cfg_use_bind = true; @@ -323,6 +336,9 @@ static void parse_opts(int argc, char **argv) case 'd': cfg_use_dgram = true; break; + case 'D': + cfg_drop = true; + break; case 'g': cfg_use_gso = true; break; @@ -367,11 +383,23 @@ static void check_packet_stats(int fd) if (getsockopt(fd, SOL_PACKET, PACKET_STATISTICS, &st, &len)) error(1, errno, "getsockopt packet statistics"); - if (st.tp_packets != 1) - error(1, 0, "stats: tp_packets %u != 1", st.tp_packets); + if (cfg_drop) { + /* PACKET_STATISTICS reports all packets seen (including + * drops) in tp_packets + */ + if (st.tp_packets < st.tp_drops) + error(1, 0, "stats: tp_packets %u < tp_drops %u", + st.tp_packets, st.tp_drops); - if (st.tp_drops != 0) - error(1, 0, "stats: tp_drops %u != 0", st.tp_drops); + if (st.tp_drops == 0) + error(1, 0, "stats: expected drops but tp_drops == 0"); + } else { + if (st.tp_packets != 1) + error(1, 0, "stats: tp_packets %u != 1", st.tp_packets); + + if (st.tp_drops != 0) + error(1, 0, "stats: tp_drops %u != 0", st.tp_drops); + } /* verify clear on read */ memset(&st, 0xff, sizeof(st)); @@ -396,6 +424,11 @@ static void run_test(void) total_len = do_tx(); + if (cfg_drop) { + check_packet_stats(fds); + goto out; + } + /* BPF filter accepts only this length, vlan changes MAC */ if (cfg_payload_len == DATA_LEN && !cfg_use_vlan) { do_rx(fds, total_len - sizeof(struct virtio_net_hdr), @@ -405,6 +438,7 @@ static void run_test(void) do_rx(fdr, cfg_payload_len, tbuf + total_len - cfg_payload_len); +out: if (close(fds)) error(1, errno, "close s"); if (close(fdr)) diff --git a/tools/testing/selftests/net/psock_snd.sh b/tools/testing/selftests/net/psock_snd.sh index 1cbfeb5052ec..b6ef12fad5d5 100755 --- a/tools/testing/selftests/net/psock_snd.sh +++ b/tools/testing/selftests/net/psock_snd.sh @@ -92,4 +92,9 @@ echo "raw gso max size" echo "raw gso max size + 1 (expected to fail)" (! ./in_netns.sh ./psock_snd -v -c -g -l "${max_mss_exceeds}") +# test drops statistics + +echo "test drops statistics" +./in_netns.sh ./psock_snd -D + echo "OK. All tests passed" From 707f5c2de0469c76693a02af136929dece809056 Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Mon, 20 Jul 2026 05:27:12 -0700 Subject: [PATCH 0513/1433] selftests/net: Test PACKET_AUXDATA Extend the packet socket selftest, adding a recvmsg path, to test PACKET_AUXDATA. Check basic attributes of tpacket_auxdata. Signed-off-by: Joe Damato Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260720122714.759175-4-joe@dama.to Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/psock_snd.c | 70 ++++++++++++++++++++++-- tools/testing/selftests/net/psock_snd.sh | 5 ++ 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/net/psock_snd.c b/tools/testing/selftests/net/psock_snd.c index 81096df5cffc..3313a15e0ca1 100644 --- a/tools/testing/selftests/net/psock_snd.c +++ b/tools/testing/selftests/net/psock_snd.c @@ -40,6 +40,7 @@ static bool cfg_use_qdisc_bypass; static bool cfg_use_vlan; static bool cfg_use_vnet; static bool cfg_drop; +static bool cfg_aux_data; static char *cfg_ifname = "lo"; static int cfg_mtu = 1500; @@ -279,11 +280,54 @@ static int setup_rx(void) return fd; } -static void do_rx(int fd, int expected_len, char *expected) +static void check_aux_data(struct cmsghdr *cmsg, int expected_len) { + struct tpacket_auxdata *adata; + + if (!cmsg) + error(1, 0, "auxdata null"); + + if (cmsg->cmsg_level != SOL_PACKET) + error(1, 0, "cmsg_level != SOL_PACKET"); + + if (cmsg->cmsg_type != PACKET_AUXDATA) + error(1, 0, "cmsg_type != PACKET_AUXDATA"); + + adata = (struct tpacket_auxdata *)CMSG_DATA(cmsg); + + if (adata->tp_net != ETH_HLEN) + error(1, 0, "cmsg tp_net != ETH_HLEN"); + + if (adata->tp_len != expected_len) + error(1, 0, "cmsg tp_len != %u", expected_len); + + if (adata->tp_snaplen != expected_len) + error(1, 0, "cmsg tp_snaplen != %u", expected_len); +} + +static void do_rx(int fd, int expected_len, char *expected, bool is_psock) +{ + char cmsg_buf[1024] __attribute__((aligned(8))) = {}; + bool aux = is_psock && cfg_aux_data; + struct msghdr msg = {}; + struct iovec iov[1]; int ret; - ret = recv(fd, rbuf, sizeof(rbuf), 0); + if (aux) { + iov[0].iov_base = rbuf; + iov[0].iov_len = sizeof(rbuf); + + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + msg.msg_control = cmsg_buf; + msg.msg_controllen = sizeof(cmsg_buf); + + ret = recvmsg(fd, &msg, 0); + } else { + ret = recv(fd, rbuf, sizeof(rbuf), 0); + } + if (ret == -1) error(1, errno, "recv"); if (ret != expected_len) @@ -292,6 +336,12 @@ static void do_rx(int fd, int expected_len, char *expected) if (memcmp(rbuf, expected, ret)) error(1, 0, "recv: data mismatch"); + if (aux) { + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + + check_aux_data(cmsg, expected_len); + } + fprintf(stderr, "rx: %u\n", ret); } @@ -312,6 +362,10 @@ static int setup_sniffer(void) if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &one, sizeof(one))) error(1, errno, "setsockopt SO_RCVBUF"); + if (cfg_aux_data) + if (setsockopt(fd, SOL_PACKET, PACKET_AUXDATA, &one, sizeof(one))) + error(1, errno, "setsockopt PACKET_AUXDATA"); + pair_udp_setfilter(fd); do_bind(fd); @@ -322,8 +376,11 @@ static void parse_opts(int argc, char **argv) { int c; - while ((c = getopt(argc, argv, "bcCdDgl:qt:vV")) != -1) { + while ((c = getopt(argc, argv, "abcCdDgl:qt:vV")) != -1) { switch (c) { + case 'a': + cfg_aux_data = true; + break; case 'b': cfg_use_bind = true; break; @@ -373,6 +430,9 @@ static void parse_opts(int argc, char **argv) if (cfg_use_gso && !cfg_use_csum_off) error(1, 0, "option gso (-g) requires csum offload (-c)"); + + if (cfg_aux_data && cfg_drop) + error(1, 0, "option aux data (-a) conflicts with drop (-D)"); } static void check_packet_stats(int fd) @@ -432,11 +492,11 @@ static void run_test(void) /* BPF filter accepts only this length, vlan changes MAC */ if (cfg_payload_len == DATA_LEN && !cfg_use_vlan) { do_rx(fds, total_len - sizeof(struct virtio_net_hdr), - tbuf + sizeof(struct virtio_net_hdr)); + tbuf + sizeof(struct virtio_net_hdr), true); check_packet_stats(fds); } - do_rx(fdr, cfg_payload_len, tbuf + total_len - cfg_payload_len); + do_rx(fdr, cfg_payload_len, tbuf + total_len - cfg_payload_len, false); out: if (close(fds)) diff --git a/tools/testing/selftests/net/psock_snd.sh b/tools/testing/selftests/net/psock_snd.sh index b6ef12fad5d5..111c9e2f0d21 100755 --- a/tools/testing/selftests/net/psock_snd.sh +++ b/tools/testing/selftests/net/psock_snd.sh @@ -97,4 +97,9 @@ echo "raw gso max size + 1 (expected to fail)" echo "test drops statistics" ./in_netns.sh ./psock_snd -D +# test aux data + +echo "test aux data" +./in_netns.sh ./psock_snd -a + echo "OK. All tests passed" From 886f928a7849446f2dec127adc9ffe3b505172a3 Mon Sep 17 00:00:00 2001 From: Ahmed Naseef Date: Sat, 11 Jul 2026 15:41:00 +0400 Subject: [PATCH 0514/1433] dt-bindings: net: dsa: mediatek,mt7530: add econet,en7528-switch The EcoNet EN7528 MIPS SoC integrates an MT7530 Gigabit switch, memory-mapped in the SoC register space like the built-in switches of the MediaTek MT7988 and Airoha EN7581/AN7583 SoCs. Its four user ports are connected to integrated Gigabit PHYs and its CPU port is connected internally to the SoC Ethernet MAC. Those three switches are MT7531-based, whereas the EN7528 has a genuine MT7530 switch core (its chip revision register reads 0x7530). The two generations differ in their register programming - for example the CPU port is selected through the MT7530-style MFC register rather than the MT7531 CFC register - so the EN7528 is not compatible with the existing switch compatibles and cannot fall back to one of them. Add the econet,en7528-switch compatible, with the same constraints as the other built-in switches. Signed-off-by: Ahmed Naseef Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/2133035bb22eacc8a0e21f86c0c800a45023ee01.1783770059.git.naseefkm@gmail.com Signed-off-by: Jakub Kicinski --- .../devicetree/bindings/net/dsa/mediatek,mt7530.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/net/dsa/mediatek,mt7530.yaml b/Documentation/devicetree/bindings/net/dsa/mediatek,mt7530.yaml index 815a90808901..90b3582b7619 100644 --- a/Documentation/devicetree/bindings/net/dsa/mediatek,mt7530.yaml +++ b/Documentation/devicetree/bindings/net/dsa/mediatek,mt7530.yaml @@ -100,6 +100,10 @@ properties: Built-in switch of the Airoha AN7583 SoC const: airoha,an7583-switch + - description: + Built-in switch of the EcoNet EN7528 SoC + const: econet,en7528-switch + reg: maxItems: 1 @@ -318,6 +322,7 @@ allOf: - mediatek,mt7988-switch - airoha,en7581-switch - airoha,an7583-switch + - econet,en7528-switch then: $ref: "#/$defs/builtin-dsa-port" properties: From cf23fcc9437e5c383d9f282197d580a5a3fd6e6e Mon Sep 17 00:00:00 2001 From: Ahmed Naseef Date: Sat, 11 Jul 2026 15:41:01 +0400 Subject: [PATCH 0515/1433] net: dsa: mt7530: add EN7528 support The EcoNet EN7528 SoC integrates an MT7530 switch (the chip revision register reads 0x7530), memory-mapped in the SoC register space and reached through the same MMIO glue used for the built-in switches of the MediaTek MT7988 and Airoha EN7581/AN7583 SoCs. Its reset sequence and its PHY indirect access registers are the same as on those switches, so add an ID_EN7528 variant bound with the "econet,en7528-switch" compatible, reusing mt7988_setup() and the indirect PHY accessors. The switch core, however, is an MT7530 and not an MT7531 derivative: the CPU port to trap frames to is set through the MT7530-style CPU_EN / CPU_PORT fields of the MFC register rather than the MT7531 CFC register, so add it to the MT7530 handling in mt753x_conduit_state_change(). For the same reason the MT7530 mirror and force-mode register layouts already apply to it as the default of the MT753X_*() macros. The four user ports (1-4) are connected to integrated Gigabit PHYs at MDIO addresses 9-12 of the switch internal MDIO bus. The CPU port (port 6) is connected to the SoC Ethernet MAC at a fixed 1000 Mbps full duplex link, so the port capabilities cannot be shared with the MT7988 and EN7581 switches, whose CPU ports run at 10 Gbps. The LAN GPHYs advertise EEE by default, but negotiating EEE with some link partners results in an unstable link with dropped frames. Leave the LPI capabilities empty for the EN7528 so that phylink disables EEE on these PHYs and refuses to enable it from userspace. Signed-off-by: Ahmed Naseef Link: https://patch.msgid.link/8c7dfabd860ab0a6dd771c2bac7b7599eb369a4f.1783770059.git.naseefkm@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mt7530-mmio.c | 1 + drivers/net/dsa/mt7530.c | 58 ++++++++++++++++++++++++++++++----- drivers/net/dsa/mt7530.h | 1 + 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/drivers/net/dsa/mt7530-mmio.c b/drivers/net/dsa/mt7530-mmio.c index 119fdd863d91..fd68b1cd0630 100644 --- a/drivers/net/dsa/mt7530-mmio.c +++ b/drivers/net/dsa/mt7530-mmio.c @@ -12,6 +12,7 @@ static const struct of_device_id mt7988_of_match[] = { { .compatible = "airoha,an7583-switch", .data = &mt753x_table[ID_AN7583], }, { .compatible = "airoha,en7581-switch", .data = &mt753x_table[ID_EN7581], }, + { .compatible = "econet,en7528-switch", .data = &mt753x_table[ID_EN7528], }, { .compatible = "mediatek,mt7988-switch", .data = &mt753x_table[ID_MT7988], }, { /* sentinel */ }, }; diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index 3c2a3029b10c..6c8ed00ee9e7 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -2912,6 +2912,30 @@ static void en7581_mac_port_get_caps(struct dsa_switch *ds, int port, } } +static void en7528_mac_port_get_caps(struct dsa_switch *ds, int port, + struct phylink_config *config) +{ + switch (port) { + /* Ports which are connected to switch PHYs. There is no MII pinout. */ + case 1 ... 4: + __set_bit(PHY_INTERFACE_MODE_INTERNAL, + config->supported_interfaces); + + config->mac_capabilities |= MAC_10 | MAC_100 | MAC_1000FD; + break; + + /* Port 6 is connected to SoC's GMAC at 1000 Mbps full duplex. There + * is no MII pinout. + */ + case 6: + __set_bit(PHY_INTERFACE_MODE_INTERNAL, + config->supported_interfaces); + + config->mac_capabilities |= MAC_1000FD; + break; + } +} + static void mt7530_mac_config(struct dsa_switch *ds, int port, unsigned int mode, phy_interface_t interface) @@ -3101,17 +3125,24 @@ static void mt753x_phylink_get_caps(struct dsa_switch *ds, int port, struct phylink_config *config) { struct mt7530_priv *priv = ds->priv; - u32 eeecr; config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE; - config->lpi_capabilities = MAC_100FD | MAC_1000FD | MAC_2500FD; - - eeecr = mt7530_read(priv, MT753X_PMEEECR_P(port)); - /* tx_lpi_timer should be in microseconds. The time units for - * LPI threshold are unspecified. + /* The EN7528 GPHYs report EEE capability, but negotiating EEE with + * common link partners (e.g. Realtek GbE NICs) results in an unstable + * link with dropped frames. Leave the LPI capabilities empty so that + * phylink disables EEE on these PHYs and refuses to enable it from + * userspace. */ - config->lpi_timer_default = FIELD_GET(LPI_THRESH_MASK, eeecr); + if (priv->id != ID_EN7528) { + u32 eeecr = mt7530_read(priv, MT753X_PMEEECR_P(port)); + + config->lpi_capabilities = MAC_100FD | MAC_1000FD | MAC_2500FD; + /* tx_lpi_timer should be in microseconds. The time units for + * LPI threshold are unspecified. + */ + config->lpi_timer_default = FIELD_GET(LPI_THRESH_MASK, eeecr); + } priv->info->mac_port_get_caps(ds, port, config); } @@ -3254,7 +3285,8 @@ mt753x_conduit_state_change(struct dsa_switch *ds, * forwarded to the numerically smallest CPU port whose conduit * interface is up. */ - if (priv->id != ID_MT7530 && priv->id != ID_MT7621) + if (priv->id != ID_MT7530 && priv->id != ID_MT7621 && + priv->id != ID_EN7528) return; mask = BIT(cpu_dp->index); @@ -3459,6 +3491,16 @@ const struct mt753x_info mt753x_table[] = { .phy_write_c45 = mt7531_ind_c45_phy_write, .mac_port_get_caps = en7581_mac_port_get_caps, }, + [ID_EN7528] = { + .id = ID_EN7528, + .pcs_ops = &mt7530_pcs_ops, + .sw_setup = mt7988_setup, + .phy_read_c22 = mt7531_ind_c22_phy_read, + .phy_write_c22 = mt7531_ind_c22_phy_write, + .phy_read_c45 = mt7531_ind_c45_phy_read, + .phy_write_c45 = mt7531_ind_c45_phy_write, + .mac_port_get_caps = en7528_mac_port_get_caps, + }, }; EXPORT_SYMBOL_GPL(mt753x_table); diff --git a/drivers/net/dsa/mt7530.h b/drivers/net/dsa/mt7530.h index dd33b0df3419..5f1e841f42c0 100644 --- a/drivers/net/dsa/mt7530.h +++ b/drivers/net/dsa/mt7530.h @@ -21,6 +21,7 @@ enum mt753x_id { ID_MT7988 = 3, ID_EN7581 = 4, ID_AN7583 = 5, + ID_EN7528 = 6, }; #define NUM_TRGMII_CTRL 5 From 65f1820835f382de93857e0933915645b0b3669a Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Wed, 15 Jul 2026 10:22:24 +0200 Subject: [PATCH 0516/1433] net: mdio: Kconfig: Group mdio controller drivers in a submenu Currently, all inidivual drivers for MDIO bus controllers are directly listed under Device drivers -> Network device support. Let's group them altogether in a submenu, while keeping the dependency on PHYLIB. No intended functional change besides the menuconfig ordering. Signed-off-by: Maxime Chevallier Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260715082226.51481-2-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/mdio/Kconfig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/mdio/Kconfig b/drivers/net/mdio/Kconfig index e57121019153..b845f1c17843 100644 --- a/drivers/net/mdio/Kconfig +++ b/drivers/net/mdio/Kconfig @@ -3,7 +3,8 @@ # MDIO Layer Configuration # -if PHYLIB +menu "MDIO controller drivers" + depends on PHYLIB config FWNODE_MDIO def_tristate (ACPI || OF) || COMPILE_TEST @@ -288,5 +289,4 @@ config MDIO_BUS_MUX_MMIOREG Currently, only 8/16/32 bits registers are supported. - -endif +endmenu From 8a48eb846f7e2cf819495db8a6b194a1ad0e95fb Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Wed, 15 Jul 2026 10:22:25 +0200 Subject: [PATCH 0517/1433] net: mdio: Kconfig: Group mdio multiplexers in a submenu Move all MDIO muxes under the "MDIO controller drivers" submenu. This doesn't change any dependency for KConfig options and is purely cosmetic. Suggested-by: Andrew Lunn Signed-off-by: Maxime Chevallier Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260715082226.51481-3-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/mdio/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/mdio/Kconfig b/drivers/net/mdio/Kconfig index b845f1c17843..a05229838cb4 100644 --- a/drivers/net/mdio/Kconfig +++ b/drivers/net/mdio/Kconfig @@ -199,7 +199,7 @@ config MDIO_THUNDER ThunderX SoCs when the MDIO bus device appears as a PCI device. -comment "MDIO Multiplexers" +menu "MDIO Multiplexers" config MDIO_BUS_MUX tristate @@ -290,3 +290,4 @@ config MDIO_BUS_MUX_MMIOREG Currently, only 8/16/32 bits registers are supported. endmenu +endmenu From 42310a24389c1bdda82e4c30750a6b72e98238d1 Mon Sep 17 00:00:00 2001 From: Yanan He Date: Tue, 14 Jul 2026 21:11:01 +0800 Subject: [PATCH 0518/1433] net: phy: motorcomm: Enable optional clock for YT8531 Some boards feed the YT8531 PHY from an SoC-provided external reference clock described by the common ethernet-phy "clocks" property. Enable the optional PHY clock during probe so boards can model this clock as a PHY input instead of keeping the clock alive from the MAC driver. This is needed on the Alientek DLRV1126, where the PHY reference clock is provided by CLK_GMAC_ETHERNET_OUT. Reviewed-by: Andrew Lunn Signed-off-by: Yanan He Link: https://patch.msgid.link/20260714-motorcomm-yt8531-clk-v3-1-10dc303ef1a5@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/motorcomm.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/phy/motorcomm.c b/drivers/net/phy/motorcomm.c index 5071605a1a11..3396a38cfc0f 100644 --- a/drivers/net/phy/motorcomm.c +++ b/drivers/net/phy/motorcomm.c @@ -6,6 +6,7 @@ * Author: Frank */ +#include #include #include #include @@ -1180,9 +1181,15 @@ static int yt8521_probe(struct phy_device *phydev) static int yt8531_probe(struct phy_device *phydev) { struct device *dev = &phydev->mdio.dev; + struct clk *clk; u16 mask, val; u32 freq; + clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "failed to get and enable PHY clock\n"); + if (device_property_read_u32(dev, "motorcomm,clk-out-frequency-hz", &freq)) freq = YTPHY_DTS_OUTPUT_CLK_DIS; From 1df10cef2d1e7f9f2fb7eddb67fc70d3abf101f9 Mon Sep 17 00:00:00 2001 From: Chenguang Zhao Date: Fri, 17 Jul 2026 17:14:23 +0800 Subject: [PATCH 0519/1433] net: sxgbe: fix null pointer dereference in probe error path The platform drvdata is not set until all IRQs have been mapped, so the local net_device pointer is NULL when IRQ mapping fails. Remove the device allocated by sxgbe_drv_probe() through priv instead. Cc: stable+noautosel@kernel.org # untested fix to unlikely driver error path Signed-off-by: Chenguang Zhao Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260717091423.1557737-1-chenguang.zhao@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/samsung/sxgbe/sxgbe_platform.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/ethernet/samsung/sxgbe/sxgbe_platform.c b/drivers/net/ethernet/samsung/sxgbe/sxgbe_platform.c index 2eccc7617507..e4701b29e1a0 100644 --- a/drivers/net/ethernet/samsung/sxgbe/sxgbe_platform.c +++ b/drivers/net/ethernet/samsung/sxgbe/sxgbe_platform.c @@ -82,7 +82,6 @@ static int sxgbe_platform_probe(struct platform_device *pdev) void __iomem *addr; struct sxgbe_priv_data *priv = NULL; struct sxgbe_plat_data *plat_dat = NULL; - struct net_device *ndev = platform_get_drvdata(pdev); struct device_node *node = dev->of_node; /* Get memory resource */ @@ -158,7 +157,7 @@ static int sxgbe_platform_probe(struct platform_device *pdev) irq_dispose_mapping(priv->txq[i]->irq_no); irq_dispose_mapping(priv->irq); err_drv_remove: - sxgbe_drv_remove(ndev); + sxgbe_drv_remove(priv->dev); err_out: return -ENODEV; } From 6217246bc5bf33f3caccf290983f65b23edb38be Mon Sep 17 00:00:00 2001 From: Alessio Faina Date: Wed, 15 Jul 2026 14:28:59 +0200 Subject: [PATCH 0520/1433] selftests/net: Skip srv6_end_dt46_l3vpn_test if iproute2 too old In case iproute2 is older than version 5.14.0, released ~Sept 1, 2021, the End.DT46 support is not available and the host_vpn_tests test contained in the srv6_end_dt46_l3vpn_test.sh file is failing in some kernel backports. This is the result of those tests: ################################################################################ TEST SECTION: SRv6 VPN connectivity test among hosts in the same tenant ################################################################################ TEST: IPv6 Hosts connectivity: hs-t100-1 -> hs-t100-2 (tenant 100) [ FAIL ] TEST: IPv4 Hosts connectivity: hs-t100-1 -> hs-t100-2 (tenant 100) [ FAIL ] TEST: IPv6 Hosts connectivity: hs-t100-2 -> hs-t100-1 (tenant 100) [ FAIL ] TEST: IPv4 Hosts connectivity: hs-t100-2 -> hs-t100-1 (tenant 100) [ FAIL ] TEST: IPv6 Hosts connectivity: hs-t200-3 -> hs-t200-4 (tenant 200) [ FAIL ] TEST: IPv4 Hosts connectivity: hs-t200-3 -> hs-t200-4 (tenant 200) [ FAIL ] TEST: IPv6 Hosts connectivity: hs-t200-4 -> hs-t200-3 (tenant 200) [ FAIL ] TEST: IPv4 Hosts connectivity: hs-t200-4 -> hs-t200-3 (tenant 200) [ FAIL ] To amend this, check the current running iproute2 supports the required feature and, if not, just skip the entire test to avoid a failure. Signed-off-by: Alessio Faina Reviewed-by: Andrea Mayer Link: https://patch.msgid.link/20260715122859.36177-1-alessio.faina@canonical.com Signed-off-by: Paolo Abeni --- .../testing/selftests/net/srv6_end_dt46_l3vpn_test.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh b/tools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh index a5e959a080bb..50e37d3217ea 100755 --- a/tools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh +++ b/tools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh @@ -536,6 +536,14 @@ host_vpn_isolation_tests() done } +test_iproute2_supp_or_ksft_skip() +{ + if ! ip route add help 2>&1 | grep -qo "End.DT46"; then + echo "SKIP: Missing SRv6 End.DT46 support in iproute2" + exit "${ksft_skip}" + fi +} + if [ "$(id -u)" -ne 0 ];then echo "SKIP: Need root privileges" exit $ksft_skip @@ -546,6 +554,8 @@ if [ ! -x "$(command -v ip)" ]; then exit $ksft_skip fi +test_iproute2_supp_or_ksft_skip + modprobe vrf &>/dev/null if [ ! -e /proc/sys/net/vrf/strict_mode ]; then echo "SKIP: vrf sysctl does not exist" From 92f0217f8afd8c96288a5e88263d1a15cf98ceec Mon Sep 17 00:00:00 2001 From: Shijith Thotton Date: Wed, 15 Jul 2026 13:01:11 +0530 Subject: [PATCH 0521/1433] octeontx2-af: return VWQE timer delay in NIX HW info Update the NIX_GET_HW_INFO mailbox message to return the VWQE timer delay, which is used for vector packet wait time calculation. Repurpose the existing rsvs16 reserved field in struct nix_hw_info to vwqe_delay and populate it by reading the NIX_AF_VWQE_TIMER register (applicable for non-OTX2 silicon). Signed-off-by: Shijith Thotton Signed-off-by: Nitin Shetty J Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260715073112.622662-1-nshettyj@marvell.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/marvell/octeontx2/af/mbox.h | 2 +- drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h index f87cdf1b971d..253dfee9646e 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h +++ b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h @@ -1463,7 +1463,7 @@ struct nix_inline_ipsec_lf_cfg { struct nix_hw_info { struct mbox_msghdr hdr; - u16 rsvs16; + u16 vwqe_delay; u16 max_mtu; u16 min_mtu; u32 rpm_dwrr_mtu; diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c index 78667a0977c0..c5bddd2ad920 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c @@ -3936,6 +3936,11 @@ int rvu_mbox_handler_nix_get_hw_info(struct rvu *rvu, struct msg_req *req, if (blkaddr < 0) return NIX_AF_ERR_AF_LF_INVALID; + rsp->vwqe_delay = 0; + if (!is_rvu_otx2(rvu)) + rsp->vwqe_delay = rvu_read64(rvu, blkaddr, NIX_AF_VWQE_TIMER) & + GENMASK_ULL(9, 0); + if (is_lbk_vf(rvu, pcifunc)) rvu_get_lbk_link_max_frs(rvu, &rsp->max_mtu); else From 94cdc6a2c837d33e64d9453e9110d35af3833eda Mon Sep 17 00:00:00 2001 From: longlong yan Date: Wed, 22 Jul 2026 09:51:29 +0800 Subject: [PATCH 0522/1433] selftests/net: use MAP_FAILED instead of (void *)-1 in tcp_mmap mmap() is documented to return MAP_FAILED on error, but tcp_mmap.c compares the return value against (void *)-1 and (unsigned char *)-1. Replace these with the standard MAP_FAILED macro for better readability and type safety. Signed-off-by: longlong yan Reviewed-by: Joe Damato Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260722015129.916-1-yanlonglong@kylinos.cn Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/tcp_mmap.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/net/tcp_mmap.c b/tools/testing/selftests/net/tcp_mmap.c index 2544ae35d07a..487ae659a1f1 100644 --- a/tools/testing/selftests/net/tcp_mmap.c +++ b/tools/testing/selftests/net/tcp_mmap.c @@ -141,12 +141,12 @@ static void *mmap_large_buffer(size_t need, size_t *allocated) buffer = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0); - if (buffer == (void *)-1) { + if (buffer == MAP_FAILED) { sz = need; buffer = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0); - if (buffer != (void *)-1) + if (buffer != MAP_FAILED) fprintf(stderr, "MAP_HUGETLB attempt failed, look at /sys/kernel/mm/hugepages for optimal performance\n"); } *allocated = sz; @@ -189,13 +189,13 @@ void *child_thread(void *arg) fcntl(fd, F_SETFL, O_NDELAY); buffer = mmap_large_buffer(chunk_size, &buffer_sz); - if (buffer == (void *)-1) { + if (buffer == MAP_FAILED) { perror("mmap"); goto error; } if (zflg) { raddr = mmap(NULL, chunk_size + map_align, PROT_READ, flags, fd, 0); - if (raddr == (void *)-1) { + if (raddr == MAP_FAILED) { perror("mmap"); zflg = 0; } else { @@ -547,7 +547,7 @@ int main(int argc, char *argv[]) } buffer = mmap_large_buffer(chunk_size, &buffer_sz); - if (buffer == (unsigned char *)-1) { + if (buffer == MAP_FAILED) { perror("mmap"); exit(1); } From b8363908ea1ea08c726c4804211a38dd353219fe Mon Sep 17 00:00:00 2001 From: Petr Vorel Date: Mon, 20 Jul 2026 23:51:48 +0200 Subject: [PATCH 0523/1433] selftests: drv-net: Fix csum path in doc C source was moved in 1d0dc857b5d87. Signed-off-by: Petr Vorel Link: https://patch.msgid.link/20260720215149.631145-1-pvorel@suse.cz Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/hw/csum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/drivers/net/hw/csum.py b/tools/testing/selftests/drivers/net/hw/csum.py index 3e3a89a34afe..0e99198f8d39 100755 --- a/tools/testing/selftests/drivers/net/hw/csum.py +++ b/tools/testing/selftests/drivers/net/hw/csum.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0 -"""Run the tools/testing/selftests/net/csum testsuite.""" +"""Run the tools/testing/selftests/net/lib/csum testsuite.""" from os import path From 1306cf6dc1dfd34b97ccff98fcb08168c352dbef Mon Sep 17 00:00:00 2001 From: Petr Vorel Date: Mon, 20 Jul 2026 23:51:49 +0200 Subject: [PATCH 0524/1433] selftests: drv-net: Fix TSO test doc Replace copy paste from csum.py with a real test purpose. Signed-off-by: Petr Vorel Link: https://patch.msgid.link/20260720215149.631145-2-pvorel@suse.cz Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/hw/tso.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/drivers/net/hw/tso.py b/tools/testing/selftests/drivers/net/hw/tso.py index 802bb4868046..67f6c9ca9a64 100755 --- a/tools/testing/selftests/drivers/net/hw/tso.py +++ b/tools/testing/selftests/drivers/net/hw/tso.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0 -"""Run the tools/testing/selftests/net/csum testsuite.""" +"""A simple test for TSO.""" import fcntl import socket From 01f41f5fd823fb70a2f28ea87f6cf85a268ef8b9 Mon Sep 17 00:00:00 2001 From: Pagadala Yesu Anjaneyulu Date: Thu, 23 Jul 2026 14:23:36 +0300 Subject: [PATCH 0525/1433] wifi: iwlwifi: regulatory: add LARI v15 DSM support bitmap Firmware API v15 extends LARI config with the DSM function-0 support bitmap, and host must provide this information for regulatory handling. Without this, FW cannot use BIOS-reported DSM capability bits. Add oem_supported_dsm_bitmap to the host LARI config command definition and wire it into regulatory command construction. Populate it from DSM query data and keep backward compatibility by using the pre-v15 command size for command version 14. Behavior change: When DSM function-0 data is available, host includes it in the LARI config command sent to FW; older command versions remain compatible. Signed-off-by: Pagadala Yesu Anjaneyulu Link: https://patch.msgid.link/20260723142324.c576a8c7560b.I5cc1277d0ef31409f6a3364342a3f35cd9318ceb@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/acpi.c | 3 ++- .../net/wireless/intel/iwlwifi/fw/api/nvm-reg.h | 5 +++++ drivers/net/wireless/intel/iwlwifi/fw/uefi.c | 2 +- .../net/wireless/intel/iwlwifi/mld/regulatory.c | 15 +++++++++++++-- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/acpi.c b/drivers/net/wireless/intel/iwlwifi/fw/acpi.c index 9f2f4a6af1ca..0b1e478fb7b0 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/acpi.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/acpi.c @@ -181,6 +181,7 @@ static int iwl_acpi_load_dsm_values(struct iwl_fw_runtime *fwrt) fwrt->dsm_revision = ACPI_DSM_REV; fwrt->dsm_source = BIOS_SOURCE_ACPI; + fwrt->dsm_values[DSM_FUNC_QUERY] = (u32)query_func_val; IWL_DEBUG_RADIO(fwrt, "ACPI DSM validity bitmap 0x%x\n", (u32)query_func_val); @@ -246,7 +247,7 @@ int iwl_acpi_get_dsm(struct iwl_fw_runtime *fwrt, BUILD_BUG_ON(ARRAY_SIZE(fwrt->dsm_values) != DSM_FUNC_NUM_FUNCS); BUILD_BUG_ON(BITS_PER_TYPE(fwrt->dsm_funcs_valid) < DSM_FUNC_NUM_FUNCS); - if (WARN_ON(func >= ARRAY_SIZE(fwrt->dsm_values) || !func)) + if (WARN_ON(func >= ARRAY_SIZE(fwrt->dsm_values))) return -EINVAL; if (!(fwrt->dsm_funcs_valid & BIT(func))) { diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h b/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h index 9fbdb45e88db..5f48e3b306eb 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h @@ -697,6 +697,8 @@ struct iwl_lari_config_change_cmd_v8 { * @wcpe_bitmap: bitmap of puncturing enablement per MCC * @bios_wbem_hdr: 320 MHz per-MCC WBEM config header * @reserved: reserved + * @oem_supported_dsm_bitmap: DSM function 0 bitmap describing supported + * DSM function indices */ struct iwl_lari_config_change_cmd { __le32 config_bitmap; @@ -720,10 +722,13 @@ struct iwl_lari_config_change_cmd { __le32 wcpe_bitmap; struct iwl_bios_config_hdr bios_wbem_hdr; __le32 reserved[10]; + /* since version 15 */ + __le32 oem_supported_dsm_bitmap; } __packed; /* LARI_CHANGE_CONF_CMD_S_VER_12 * LARI_CHANGE_CONF_CMD_S_VER_13 * LARI_CHANGE_CONF_CMD_S_VER_14 + * LARI_CHANGE_CONF_CMD_S_VER_15 */ /* Activate UNII-1 (5.2GHz) for World Wide */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/uefi.c b/drivers/net/wireless/intel/iwlwifi/fw/uefi.c index 01e495e24ebc..f9187989c988 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/uefi.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/uefi.c @@ -927,7 +927,7 @@ static int iwl_uefi_load_dsm_values(struct iwl_fw_runtime *fwrt) */ fwrt->dsm_funcs_valid |= BIT(DSM_FUNC_QUERY); - for (int func = 1; func < ARRAY_SIZE(fwrt->dsm_values); func++) { + for (int func = 0; func < ARRAY_SIZE(fwrt->dsm_values); func++) { if (!(fwrt->dsm_funcs_valid & BIT(func))) { IWL_DEBUG_RADIO(fwrt, "DSM func %d not in 0x%x\n", func, fwrt->dsm_funcs_valid); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c b/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c index ad899ce5c64a..533870cf443f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c @@ -16,8 +16,11 @@ static ssize_t iwl_mld_get_lari_config_cmd_size(u8 cmd_ver) { switch (cmd_ver) { - case 14: + case 15: return sizeof(struct iwl_lari_config_change_cmd); + case 14: + return offsetof(struct iwl_lari_config_change_cmd, + oem_supported_dsm_bitmap); case 13: return offsetof(struct iwl_lari_config_change_cmd, oem_uhb_allow_extension_bitmap); @@ -425,6 +428,10 @@ void iwl_mld_configure_lari(struct iwl_mld *mld) if (!ret) cmd.oem_uhb_allow_extension_bitmap = cpu_to_le32(value); + ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_QUERY, &value); + if (!ret) + cmd.oem_supported_dsm_bitmap = cpu_to_le32(value); + cmd.bios_wcpe_hdr.table_source = fwrt->puncturing_source; cmd.bios_wcpe_hdr.table_revision = fwrt->puncturing_revision; cmd.wcpe_bitmap = cpu_to_le32(fwrt->bios_puncturing); @@ -441,7 +448,8 @@ void iwl_mld_configure_lari(struct iwl_mld *mld) !cmd.oem_11be_allow_bitmap && !cmd.oem_11bn_allow_bitmap && !cmd.oem_unii9_enable && - !cmd.wcpe_bitmap) + !cmd.wcpe_bitmap && + !cmd.oem_supported_dsm_bitmap) return; cmd.bios_hdr.table_source = fwrt->dsm_source; @@ -478,6 +486,9 @@ void iwl_mld_configure_lari(struct iwl_mld *mld) IWL_DEBUG_RADIO(mld, "sending LARI_CONFIG_CHANGE, wcpe_bitmap=0x%x\n", le32_to_cpu(cmd.wcpe_bitmap)); + IWL_DEBUG_RADIO(mld, + "sending LARI_CONFIG_CHANGE, oem_supported_dsm_bitmap=0x%x\n", + le32_to_cpu(cmd.oem_supported_dsm_bitmap)); ret = iwl_mld_send_cmd_pdu(mld, WIDE_ID(REGULATORY_AND_NVM_GROUP, From 4a2610a5a9fcf77eaad82bb030ec77ec5e381db8 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Thu, 23 Jul 2026 14:23:37 +0300 Subject: [PATCH 0526/1433] wifi: iwlwifi: bump core version for BZ/SC/DR Start supporting Core 107 FW on these devices. Link: https://patch.msgid.link/20260723142324.ed92b1d7f927.I6dd10d2f9a04a36751aea9e238fecfa62fe280a6@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/cfg/bz.c | 2 +- drivers/net/wireless/intel/iwlwifi/cfg/dr.c | 2 +- drivers/net/wireless/intel/iwlwifi/cfg/sc.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c index 606362463dc7..1099d8cfe6aa 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c @@ -10,7 +10,7 @@ #include "fw/api/txq.h" /* Highest firmware core release supported */ -#define IWL_BZ_UCODE_CORE_MAX 106 +#define IWL_BZ_UCODE_CORE_MAX 107 /* Lowest firmware core release supported */ #define IWL_BZ_UCODE_CORE_MIN 102 diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c b/drivers/net/wireless/intel/iwlwifi/cfg/dr.c index 946975294b4f..a89a2c75687f 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/dr.c @@ -9,7 +9,7 @@ #include "fw/api/txq.h" /* Highest firmware core release supported */ -#define IWL_DR_UCODE_CORE_MAX 106 +#define IWL_DR_UCODE_CORE_MAX 107 /* Lowest firmware core release supported */ #define IWL_DR_UCODE_CORE_MIN 102 diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c index e8240c1782ac..16abf0147fe4 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c @@ -10,7 +10,7 @@ #include "fw/api/txq.h" /* Highest firmware core release supported */ -#define IWL_SC_UCODE_CORE_MAX 106 +#define IWL_SC_UCODE_CORE_MAX 107 /* Lowest firmware core release supported */ #define IWL_SC_UCODE_CORE_MIN 102 From ed6dc972c19ff9ffca504f5739848da4275219e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Wed, 22 Jul 2026 19:48:53 +0200 Subject: [PATCH 0527/1433] net: dsa: microchip: Fix log typo in error path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All other log messages in the driver prefix hex values with 0x, add it in the one missing message. While at it also add the missing opening parenthesis in the same log message. Signed-off-by: Niklas Söderlund Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260722174853.2316333-1-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Jakub Kicinski --- drivers/net/dsa/microchip/ksz_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index 67ab6ddb9e53..ff4dd51f6cb0 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -2994,7 +2994,7 @@ static int ksz_switch_detect(struct ksz_device *dev) break; default: dev_err(dev->dev, - "unsupported switch detected %x)\n", id32); + "unsupported switch detected (0x%x)\n", id32); return -ENODEV; } } From 888b1934642b796fd3fc00be5681cb36ce7eae4e Mon Sep 17 00:00:00 2001 From: Anirudh Srinivasan Date: Thu, 16 Jul 2026 18:05:10 -0500 Subject: [PATCH 0528/1433] PCI: Move Spacemit vendor and device IDs to linux/pci_ids.h Move the vendor and device ID for the existing Spacemit K1 PCIe Root Complex to include/linux/pci_ids.h. Also add K3's Root Complex device ID to this header. This is done so that these values can be referenced in the rtw89 driver to enable 36-bit DMA ability in it for WiFi to function on the K3 Pico ITX board. Acked-by: Bjorn Helgaas Signed-off-by: Anirudh Srinivasan Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260716-rtw89-spacemit-k3-v2-1-392b577ebf75@oss.tenstorrent.com --- drivers/pci/controller/dwc/pcie-spacemit-k1.c | 3 --- include/linux/pci_ids.h | 4 ++++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-spacemit-k1.c b/drivers/pci/controller/dwc/pcie-spacemit-k1.c index be20a520255b..f89c6d46c768 100644 --- a/drivers/pci/controller/dwc/pcie-spacemit-k1.c +++ b/drivers/pci/controller/dwc/pcie-spacemit-k1.c @@ -21,9 +21,6 @@ #include "pcie-designware.h" -#define PCI_VENDOR_ID_SPACEMIT 0x201f -#define PCI_DEVICE_ID_SPACEMIT_K1 0x0001 - /* Offsets and field definitions for link management registers */ #define K1_PHY_AHB_IRQ_EN 0x0000 #define PCIE_INTERRUPT_EN BIT(0) diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 1c9d40e09107..d6f26cacc8e3 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -2640,6 +2640,10 @@ #define PCI_VENDOR_ID_SUNIX 0x1fd4 #define PCI_DEVICE_ID_SUNIX_1999 0x1999 +#define PCI_VENDOR_ID_SPACEMIT 0x201f +#define PCI_DEVICE_ID_SPACEMIT_K1 0x0001 +#define PCI_DEVICE_ID_SPACEMIT_K3 0x0002 + #define PCI_VENDOR_ID_HINT 0x3388 #define PCI_DEVICE_ID_HINT_VXPROII_IDE 0x8013 From 27a5046cc56e851d2c498214b134170681ccb28a Mon Sep 17 00:00:00 2001 From: Anirudh Srinivasan Date: Thu, 16 Jul 2026 18:05:11 -0500 Subject: [PATCH 0529/1433] wifi: rtw89: pci: enable 36-bit DMA on spacemit K3 The Spacemit K3 Pico ITX Board has a RTL8852BE pcie card behind a PCIe root port, but the SoC doesn't have any 32 bit DMA addreseses which the rtw89 seems to use by default. Enable 36 bit DMA ability that the driver has when this particular root port is detected so that the driver can probe on this SoC. Tested-by: Aurelien Jarno Acked-by: Ping-Ke Shih Signed-off-by: Anirudh Srinivasan Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260716-rtw89-spacemit-k3-v2-2-392b577ebf75@oss.tenstorrent.com --- drivers/net/wireless/realtek/rtw89/pci.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/pci.c b/drivers/net/wireless/realtek/rtw89/pci.c index 102bae488180..c5b82fc46d06 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.c +++ b/drivers/net/wireless/realtek/rtw89/pci.c @@ -3326,6 +3326,10 @@ static bool rtw89_pci_is_dac_compatible_bridge(struct rtw89_dev *rtwdev) if (bridge->device == 0x2806) return true; break; + case PCI_VENDOR_ID_SPACEMIT: + if (bridge->device == PCI_DEVICE_ID_SPACEMIT_K3) + return true; + break; } return false; From d910631ff3522c54d20b6a5e03818d00d874ea50 Mon Sep 17 00:00:00 2001 From: Johnson Tsai Date: Fri, 17 Jul 2026 14:19:02 +0800 Subject: [PATCH 0530/1433] wifi: rtw89: add LED support to reflect the wireless association status Add a new RTW89_LEDS Kconfig option, along with LED structures to describe flexible GPIO mappings for chip common LED definition. Core LED lifecycle and registration logic default to the mac80211 association trigger, and chips are wired up as the first user with a single-GPIO monochrome LED. Usage: - Auto-triggered (default): ON when connected, OFF when disconnected - Manual override: echo <1|0> > /sys/class/leds/rtw89-phyX/brightness Signed-off-by: Johnson Tsai Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717061910.54466-2-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/Kconfig | 7 ++ drivers/net/wireless/realtek/rtw89/Makefile | 1 + drivers/net/wireless/realtek/rtw89/core.c | 3 + drivers/net/wireless/realtek/rtw89/core.h | 22 ++++ drivers/net/wireless/realtek/rtw89/debug.h | 1 + drivers/net/wireless/realtek/rtw89/led.c | 123 ++++++++++++++++++++ drivers/net/wireless/realtek/rtw89/led.h | 24 ++++ 7 files changed, 181 insertions(+) create mode 100644 drivers/net/wireless/realtek/rtw89/led.c create mode 100644 drivers/net/wireless/realtek/rtw89/led.h diff --git a/drivers/net/wireless/realtek/rtw89/Kconfig b/drivers/net/wireless/realtek/rtw89/Kconfig index 43e3b0ef44da..be31eaab7621 100644 --- a/drivers/net/wireless/realtek/rtw89/Kconfig +++ b/drivers/net/wireless/realtek/rtw89/Kconfig @@ -190,4 +190,11 @@ config RTW89_DEBUGFS If unsure, say Y to simplify debug problems +config RTW89_LEDS + bool + depends on RTW89_CORE + depends on LEDS_CLASS=y || LEDS_CLASS=MAC80211 + imply MAC80211_LEDS + default y + endif diff --git a/drivers/net/wireless/realtek/rtw89/Makefile b/drivers/net/wireless/realtek/rtw89/Makefile index 475bad743d75..3d47f13d1e66 100644 --- a/drivers/net/wireless/realtek/rtw89/Makefile +++ b/drivers/net/wireless/realtek/rtw89/Makefile @@ -92,6 +92,7 @@ obj-$(CONFIG_RTW89_8922AU) += rtw89_8922au.o rtw89_8922au-objs := rtw8922au.o rtw89_core-$(CONFIG_RTW89_DEBUG) += debug.o +rtw89_core-$(CONFIG_RTW89_LEDS) += led.o obj-$(CONFIG_RTW89_PCI) += rtw89_pci.o rtw89_pci-y := pci.o pci_be.o diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index b4b0a451fed7..bb04f649bfb0 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -11,6 +11,7 @@ #include "core.h" #include "efuse.h" #include "fw.h" +#include "led.h" #include "mac.h" #include "phy.h" #include "ps.h" @@ -7588,6 +7589,7 @@ static int rtw89_core_register_hw(struct rtw89_dev *rtwdev) rtw89_rfkill_polling_init(rtwdev); rtw89_btc_init(rtwdev); + rtw89_led_init(rtwdev); return 0; @@ -7601,6 +7603,7 @@ static void rtw89_core_unregister_hw(struct rtw89_dev *rtwdev) { struct ieee80211_hw *hw = rtwdev->hw; + rtw89_led_deinit(rtwdev); rtw89_rfkill_polling_deinit(rtwdev); ieee80211_unregister_hw(hw); } diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index c0795e0d10cd..41cc519317c9 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -5278,6 +5279,26 @@ struct rtw89_chanctx_listener { #define RTW89_NHM_TH_NUM 11 #define RTW89_NHM_RPT_NUM 12 +struct rtw89_led_gpio_entry { + u8 pin; + struct rtw89_reg3_def pinmux; + struct rtw89_reg2_def mode; + struct rtw89_reg2_def out; +}; + +struct rtw89_led_desc { + const struct rtw89_led_gpio_entry *gpios; + u8 n_gpio; +}; + +struct rtw89_led { + bool registered; + const struct rtw89_led_desc *desc; + struct led_classdev led; + enum led_brightness brightness_cache; + char name[32]; +}; + struct rtw89_chip_info { enum rtw89_core_chip_id chip_id; enum rtw89_chip_gen chip_gen; @@ -7245,6 +7266,7 @@ struct rtw89_dev { struct rtw89_debugfs *debugfs; struct rtw89_vif *pure_monitor_mode_vif; + struct rtw89_led led; /* HCI related data, keep last */ u8 priv[] __aligned(sizeof(void *)); diff --git a/drivers/net/wireless/realtek/rtw89/debug.h b/drivers/net/wireless/realtek/rtw89/debug.h index 7cdceb24f52d..f53d8acbd0d2 100644 --- a/drivers/net/wireless/realtek/rtw89/debug.h +++ b/drivers/net/wireless/realtek/rtw89/debug.h @@ -32,6 +32,7 @@ enum rtw89_debug_mask { RTW89_DBG_ACPI = BIT(21), RTW89_DBG_EDCCA = BIT(22), RTW89_DBG_PS = BIT(23), + RTW89_DBG_LED = BIT(24), RTW89_DBG_UNEXP = BIT(31), }; diff --git a/drivers/net/wireless/realtek/rtw89/led.c b/drivers/net/wireless/realtek/rtw89/led.c new file mode 100644 index 000000000000..64e7eaa7ee39 --- /dev/null +++ b/drivers/net/wireless/realtek/rtw89/led.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* Copyright(c) 2026 Realtek Corporation + */ + +#include "core.h" +#include "debug.h" +#include "led.h" +#include "reg.h" + +static const struct rtw89_led_gpio_entry rtw89_common_led_gpios[] = {{ + .pin = 8, + .pinmux = {.addr = R_AX_GPIO8_15_FUNC_SEL, .mask = GENMASK(3, 0), .data = 0xf}, + .mode = {.addr = R_AX_GPIO_EXT_CTRL + 2, .data = BIT(0) | BIT(8)}, + .out = {.addr = R_AX_GPIO_EXT_CTRL + 1, .data = BIT(0)}, +}}; + +static const struct rtw89_led_desc rtw89_common_led_desc = { + .gpios = rtw89_common_led_gpios, + .n_gpio = ARRAY_SIZE(rtw89_common_led_gpios), +}; + +void rtw89_led_gpio_config(struct rtw89_dev *rtwdev, + const struct rtw89_led_gpio_entry *e) +{ + rtw89_write32_mask(rtwdev, e->pinmux.addr, e->pinmux.mask, e->pinmux.data); + rtw89_write16_clr(rtwdev, e->mode.addr, e->mode.data); +} + +void rtw89_led_gpio_set(struct rtw89_dev *rtwdev, + const struct rtw89_led_gpio_entry *e, + enum led_brightness brightness) +{ + if (brightness == LED_OFF) { + rtw89_write16_clr(rtwdev, e->mode.addr, e->mode.data); + } else { + rtw89_write16_set(rtwdev, e->mode.addr, e->mode.data); + rtw89_write8_clr(rtwdev, e->out.addr, e->out.data); + } +} + +static int rtw89_led_set(struct led_classdev *led, enum led_brightness brightness) +{ + struct rtw89_led *rtw_led = container_of(led, struct rtw89_led, led); + struct rtw89_dev *rtwdev = container_of(rtw_led, struct rtw89_dev, led); + const struct rtw89_led_desc *desc = rtw_led->desc; + const struct rtw89_led_gpio_entry *e = &desc->gpios[0]; + + if (rtw_led->brightness_cache == brightness) { + rtw89_debug(rtwdev, RTW89_DBG_LED, "led_set: pin=%u skip (no change)\n", + e->pin); + return 0; + } + + wiphy_lock(rtwdev->hw->wiphy); + + rtw89_debug(rtwdev, RTW89_DBG_LED, "led_set: pin=%u brightness=%u\n", + e->pin, brightness); + rtw89_led_gpio_set(rtwdev, e, brightness); + rtw_led->brightness_cache = brightness; + + wiphy_unlock(rtwdev->hw->wiphy); + + return 0; +} + +static int rtw89_led_sc_init(struct rtw89_dev *rtwdev, const struct rtw89_led_desc *desc) +{ + struct rtw89_led *rtw_led = &rtwdev->led; + int ret; + + snprintf(rtw_led->name, sizeof(rtw_led->name), "rtw89-%s", + wiphy_name(rtwdev->hw->wiphy)); + rtw_led->led.name = rtw_led->name; + rtw_led->led.brightness_set_blocking = rtw89_led_set; + rtw_led->led.max_brightness = LED_ON; + rtw_led->led.default_trigger = ieee80211_get_assoc_led_name(rtwdev->hw); + + ret = led_classdev_register(rtwdev->dev, &rtw_led->led); + if (ret) { + rtw89_warn(rtwdev, "failed to register LED, ret=%d\n", ret); + return ret; + } + + rtw89_led_gpio_config(rtwdev, &desc->gpios[0]); + + return 0; +} + +static void rtw89_led_sc_deinit(struct rtw89_dev *rtwdev) +{ + struct rtw89_led *rtw_led = &rtwdev->led; + + led_classdev_unregister(&rtw_led->led); +} + +void rtw89_led_init(struct rtw89_dev *rtwdev) +{ + const struct rtw89_led_desc *desc = &rtw89_common_led_desc; + struct rtw89_led *rtw_led = &rtwdev->led; + int ret; + + /* single-GPIO monochrome LED is the only supported layout */ + BUILD_BUG_ON(ARRAY_SIZE(rtw89_common_led_gpios) != 1); + + rtw_led->desc = desc; + rtw_led->brightness_cache = LED_OFF; + + ret = rtw89_led_sc_init(rtwdev, desc); + if (ret) + return; + + rtw_led->registered = true; +} + +void rtw89_led_deinit(struct rtw89_dev *rtwdev) +{ + struct rtw89_led *rtw_led = &rtwdev->led; + + if (!rtw_led->registered) + return; + + rtw89_led_sc_deinit(rtwdev); +} diff --git a/drivers/net/wireless/realtek/rtw89/led.h b/drivers/net/wireless/realtek/rtw89/led.h new file mode 100644 index 000000000000..27e782cff7c7 --- /dev/null +++ b/drivers/net/wireless/realtek/rtw89/led.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* Copyright(c) 2026 Realtek Corporation + */ + +#ifndef __RTW89_LED_H__ +#define __RTW89_LED_H__ + +#include "core.h" + +void rtw89_led_gpio_config(struct rtw89_dev *rtwdev, + const struct rtw89_led_gpio_entry *e); +void rtw89_led_gpio_set(struct rtw89_dev *rtwdev, + const struct rtw89_led_gpio_entry *e, + enum led_brightness brightness); + +#ifdef CONFIG_RTW89_LEDS +void rtw89_led_init(struct rtw89_dev *rtwdev); +void rtw89_led_deinit(struct rtw89_dev *rtwdev); +#else +static inline void rtw89_led_init(struct rtw89_dev *rtwdev) {} +static inline void rtw89_led_deinit(struct rtw89_dev *rtwdev) {} +#endif + +#endif From 721d90c8509aee195c3714b70c4cff612fbb3a1c Mon Sep 17 00:00:00 2001 From: Johnson Tsai Date: Fri, 17 Jul 2026 14:19:03 +0800 Subject: [PATCH 0531/1433] wifi: rtw89: add multicolor LED support for RTL8852CU valve board Add multicolor LED support for the RTL8852CU valve board to reflect wireless connection status (by default, green LED is ON when associated and OFF when disconnected). Extend the rtw89 LED subsystem with a multicolor path via led_classdev_mc, for board-level variants with more than one LED GPIO channel. Add a new RTW89_LEDS_MC Kconfig option and support multicolor LEDs through led_classdev_mc, with per-channel caching to minimize redundant register writes. The RTL8852CU valve board is wired up as the first user, driving a multicolor WRGB LED over four GPIO channels (8, 18, 16, and 17) that map to LED_COLOR_ID_WHITE/RED/GREEN/BLUE. Usage: - Auto-triggered (default): Green LED ON when connected, OFF when disconnected - Manual override (e.g., set red; channel order: WHITE RED GREEN BLUE): echo "0 1 0 0" > /sys/class/leds/rtw89-phyX-multicolor/multi_intensity echo 1 > /sys/class/leds/rtw89-phyX-multicolor/brightness Signed-off-by: Johnson Tsai Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717061910.54466-3-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/Kconfig | 7 ++ drivers/net/wireless/realtek/rtw89/Makefile | 1 + drivers/net/wireless/realtek/rtw89/core.c | 1 + drivers/net/wireless/realtek/rtw89/core.h | 15 +++- drivers/net/wireless/realtek/rtw89/led.c | 28 +++++-- drivers/net/wireless/realtek/rtw89/led.h | 13 +++ drivers/net/wireless/realtek/rtw89/led_mc.c | 81 +++++++++++++++++++ drivers/net/wireless/realtek/rtw89/reg.h | 2 + .../net/wireless/realtek/rtw89/rtw8851be.c | 1 + .../net/wireless/realtek/rtw89/rtw8851bu.c | 1 + .../net/wireless/realtek/rtw89/rtw8852ae.c | 1 + .../net/wireless/realtek/rtw89/rtw8852au.c | 1 + .../net/wireless/realtek/rtw89/rtw8852be.c | 1 + .../net/wireless/realtek/rtw89/rtw8852bte.c | 1 + .../net/wireless/realtek/rtw89/rtw8852bu.c | 1 + .../net/wireless/realtek/rtw89/rtw8852ce.c | 1 + .../net/wireless/realtek/rtw89/rtw8852cu.c | 54 +++++++++++++ .../net/wireless/realtek/rtw89/rtw8922ae.c | 2 + .../net/wireless/realtek/rtw89/rtw8922au.c | 1 + .../net/wireless/realtek/rtw89/rtw8922de.c | 2 + 20 files changed, 208 insertions(+), 7 deletions(-) create mode 100644 drivers/net/wireless/realtek/rtw89/led_mc.c diff --git a/drivers/net/wireless/realtek/rtw89/Kconfig b/drivers/net/wireless/realtek/rtw89/Kconfig index be31eaab7621..7200a944260f 100644 --- a/drivers/net/wireless/realtek/rtw89/Kconfig +++ b/drivers/net/wireless/realtek/rtw89/Kconfig @@ -197,4 +197,11 @@ config RTW89_LEDS imply MAC80211_LEDS default y +config RTW89_LEDS_MC + bool + depends on RTW89_LEDS + depends on LEDS_CLASS_MULTICOLOR + imply LEDS_TRIGGER_TIMER + default y + endif diff --git a/drivers/net/wireless/realtek/rtw89/Makefile b/drivers/net/wireless/realtek/rtw89/Makefile index 3d47f13d1e66..5536714e0268 100644 --- a/drivers/net/wireless/realtek/rtw89/Makefile +++ b/drivers/net/wireless/realtek/rtw89/Makefile @@ -93,6 +93,7 @@ rtw89_8922au-objs := rtw8922au.o rtw89_core-$(CONFIG_RTW89_DEBUG) += debug.o rtw89_core-$(CONFIG_RTW89_LEDS) += led.o +rtw89_core-$(CONFIG_RTW89_LEDS_MC) += led_mc.o obj-$(CONFIG_RTW89_PCI) += rtw89_pci.o rtw89_pci-y := pci.o pci_be.o diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index bb04f649bfb0..b87a72700b99 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -7701,6 +7701,7 @@ struct rtw89_dev *rtw89_alloc_ieee80211_hw(struct device *device, rtwdev->ops = ops; rtwdev->chip = chip; rtwdev->variant = variant; + rtwdev->board = info->board; rtwdev->fw.req.firmware = firmware; rtwdev->fw.fw_format = fw_format; rtwdev->support_mlo = support_mlo; diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 41cc519317c9..70cf6cbf4e8a 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -5279,8 +5280,12 @@ struct rtw89_chanctx_listener { #define RTW89_NHM_TH_NUM 11 #define RTW89_NHM_RPT_NUM 12 +#define RTW89_LED_MAX_NUM 4 + struct rtw89_led_gpio_entry { u8 pin; + unsigned int color; + u8 intensity; struct rtw89_reg3_def pinmux; struct rtw89_reg2_def mode; struct rtw89_reg2_def out; @@ -5295,7 +5300,9 @@ struct rtw89_led { bool registered; const struct rtw89_led_desc *desc; struct led_classdev led; - enum led_brightness brightness_cache; + struct led_classdev_mc led_mc; + struct mc_subled subled[RTW89_LED_MAX_NUM]; + enum led_brightness brightness_cache[RTW89_LED_MAX_NUM]; char name[32]; }; @@ -5454,6 +5461,10 @@ struct rtw89_chip_variant { const struct rtw89_qta_def *qta_def_override; }; +struct rtw89_board_variant { + const struct rtw89_led_desc *led_desc; +}; + union rtw89_bus_info { const struct rtw89_pci_info *pci; const struct rtw89_usb_info *usb; @@ -5462,6 +5473,7 @@ union rtw89_bus_info { struct rtw89_driver_info { const struct rtw89_chip_info *chip; const struct rtw89_chip_variant *variant; + const struct rtw89_board_variant *board; const struct dmi_system_id *quirks; unsigned long dev_id_quirks; /* bitmap of rtw89_quirks */ union rtw89_bus_info bus; @@ -7136,6 +7148,7 @@ struct rtw89_dev { struct rtw89_hw_scan_info scan_info; const struct rtw89_chip_info *chip; const struct rtw89_chip_variant *variant; + const struct rtw89_board_variant *board; const struct rtw89_pci_info *pci_info; const struct rtw89_rfe_parms *rfe_parms; struct rtw89_hal hal; diff --git a/drivers/net/wireless/realtek/rtw89/led.c b/drivers/net/wireless/realtek/rtw89/led.c index 64e7eaa7ee39..ae74b7075d7c 100644 --- a/drivers/net/wireless/realtek/rtw89/led.c +++ b/drivers/net/wireless/realtek/rtw89/led.c @@ -45,7 +45,7 @@ static int rtw89_led_set(struct led_classdev *led, enum led_brightness brightnes const struct rtw89_led_desc *desc = rtw_led->desc; const struct rtw89_led_gpio_entry *e = &desc->gpios[0]; - if (rtw_led->brightness_cache == brightness) { + if (rtw_led->brightness_cache[0] == brightness) { rtw89_debug(rtwdev, RTW89_DBG_LED, "led_set: pin=%u skip (no change)\n", e->pin); return 0; @@ -56,7 +56,7 @@ static int rtw89_led_set(struct led_classdev *led, enum led_brightness brightnes rtw89_debug(rtwdev, RTW89_DBG_LED, "led_set: pin=%u brightness=%u\n", e->pin, brightness); rtw89_led_gpio_set(rtwdev, e, brightness); - rtw_led->brightness_cache = brightness; + rtw_led->brightness_cache[0] = brightness; wiphy_unlock(rtwdev->hw->wiphy); @@ -97,15 +97,27 @@ void rtw89_led_init(struct rtw89_dev *rtwdev) { const struct rtw89_led_desc *desc = &rtw89_common_led_desc; struct rtw89_led *rtw_led = &rtwdev->led; + const struct rtw89_board_variant *board = rtwdev->board; int ret; + int i; /* single-GPIO monochrome LED is the only supported layout */ BUILD_BUG_ON(ARRAY_SIZE(rtw89_common_led_gpios) != 1); - rtw_led->desc = desc; - rtw_led->brightness_cache = LED_OFF; + if (board) + desc = board->led_desc; + if (!desc->n_gpio || desc->n_gpio > RTW89_LED_MAX_NUM) + return; + + rtw_led->desc = desc; + for (i = 0; i < ARRAY_SIZE(rtw_led->brightness_cache); i++) + rtw_led->brightness_cache[i] = LED_OFF; + + if (desc->n_gpio == 1) + ret = rtw89_led_sc_init(rtwdev, desc); + else + ret = rtw89_led_mc_init(rtwdev, desc); - ret = rtw89_led_sc_init(rtwdev, desc); if (ret) return; @@ -115,9 +127,13 @@ void rtw89_led_init(struct rtw89_dev *rtwdev) void rtw89_led_deinit(struct rtw89_dev *rtwdev) { struct rtw89_led *rtw_led = &rtwdev->led; + const struct rtw89_led_desc *desc = rtw_led->desc; if (!rtw_led->registered) return; - rtw89_led_sc_deinit(rtwdev); + if (desc->n_gpio == 1) + rtw89_led_sc_deinit(rtwdev); + else + rtw89_led_mc_deinit(rtwdev); } diff --git a/drivers/net/wireless/realtek/rtw89/led.h b/drivers/net/wireless/realtek/rtw89/led.h index 27e782cff7c7..f5ccfe1d1752 100644 --- a/drivers/net/wireless/realtek/rtw89/led.h +++ b/drivers/net/wireless/realtek/rtw89/led.h @@ -21,4 +21,17 @@ static inline void rtw89_led_init(struct rtw89_dev *rtwdev) {} static inline void rtw89_led_deinit(struct rtw89_dev *rtwdev) {} #endif +#ifdef CONFIG_RTW89_LEDS_MC +int rtw89_led_mc_init(struct rtw89_dev *rtwdev, const struct rtw89_led_desc *desc); +void rtw89_led_mc_deinit(struct rtw89_dev *rtwdev); +#else +static inline int rtw89_led_mc_init(struct rtw89_dev *rtwdev, + const struct rtw89_led_desc *desc) +{ + return -EOPNOTSUPP; +} + +static inline void rtw89_led_mc_deinit(struct rtw89_dev *rtwdev) {} +#endif + #endif diff --git a/drivers/net/wireless/realtek/rtw89/led_mc.c b/drivers/net/wireless/realtek/rtw89/led_mc.c new file mode 100644 index 000000000000..7e9243baef68 --- /dev/null +++ b/drivers/net/wireless/realtek/rtw89/led_mc.c @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* Copyright(c) 2026 Realtek Corporation + */ + +#include "core.h" +#include "debug.h" +#include "led.h" + +static int rtw89_led_mc_brightness_set(struct led_classdev *led, + enum led_brightness brightness) +{ + struct led_classdev_mc *mcdev = lcdev_to_mccdev(led); + struct rtw89_led *rtw_led = container_of(mcdev, struct rtw89_led, led_mc); + struct rtw89_dev *rtwdev = container_of(rtw_led, struct rtw89_dev, led); + const struct rtw89_led_desc *desc = rtw_led->desc; + const struct rtw89_led_gpio_entry *e; + unsigned int i; + + led_mc_calc_color_components(mcdev, brightness); + + wiphy_lock(rtwdev->hw->wiphy); + + for (i = 0; i < mcdev->num_colors; i++) { + e = &desc->gpios[i]; + if (rtw_led->brightness_cache[i] == mcdev->subled_info[i].brightness) { + rtw89_debug(rtwdev, RTW89_DBG_LED, + "led_mc_set: pin=%u skip (no change)\n", e->pin); + continue; + } + + rtw89_debug(rtwdev, RTW89_DBG_LED, "led_mc_set: pin=%u brightness=%u\n", + e->pin, mcdev->subled_info[i].brightness); + rtw89_led_gpio_set(rtwdev, e, mcdev->subled_info[i].brightness); + rtw_led->brightness_cache[i] = mcdev->subled_info[i].brightness; + } + + wiphy_unlock(rtwdev->hw->wiphy); + + return 0; +} + +int rtw89_led_mc_init(struct rtw89_dev *rtwdev, const struct rtw89_led_desc *desc) +{ + struct rtw89_led *rtw_led = &rtwdev->led; + struct led_classdev_mc *led_mc = &rtw_led->led_mc; + int i, ret; + + led_mc->num_colors = desc->n_gpio; + led_mc->subled_info = rtw_led->subled; + + for (i = 0; i < desc->n_gpio; i++) { + const struct rtw89_led_gpio_entry *e = &desc->gpios[i]; + + rtw_led->subled[i].color_index = e->color; + rtw_led->subled[i].intensity = e->intensity; + rtw_led->subled[i].brightness = 0; + rtw_led->subled[i].channel = i; + + rtw89_led_gpio_config(rtwdev, e); + } + + snprintf(rtw_led->name, sizeof(rtw_led->name), "rtw89-%s-multicolor", + wiphy_name(rtwdev->hw->wiphy)); + led_mc->led_cdev.name = rtw_led->name; + led_mc->led_cdev.brightness_set_blocking = rtw89_led_mc_brightness_set; + led_mc->led_cdev.max_brightness = LED_ON; + led_mc->led_cdev.default_trigger = ieee80211_get_assoc_led_name(rtwdev->hw); + + ret = led_classdev_multicolor_register(rtwdev->dev, led_mc); + if (ret) + rtw89_warn(rtwdev, "failed to register multicolor LED, ret=%d\n", ret); + + return ret; +} + +void rtw89_led_mc_deinit(struct rtw89_dev *rtwdev) +{ + struct rtw89_led *rtw_led = &rtwdev->led; + + led_classdev_multicolor_unregister(&rtw_led->led_mc); +} diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h index 3bf2865b81ab..053028b9ef16 100644 --- a/drivers/net/wireless/realtek/rtw89/reg.h +++ b/drivers/net/wireless/realtek/rtw89/reg.h @@ -205,6 +205,8 @@ #define MAC_AX_HCI_SEL_PCIE_USB 3 #define MAC_AX_HCI_SEL_MULTI_SDIO 4 +#define R_AX_GPIO_16_TO_18_EXT_CTRL 0x0150 + #define R_AX_HALT_H2C_CTRL 0x0160 #define R_AX_HALT_H2C 0x0168 #define B_AX_HALT_H2C_TRIGGER BIT(0) diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851be.c b/drivers/net/wireless/realtek/rtw89/rtw8851be.c index 640672eb0d26..459d2e778b83 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851be.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851be.c @@ -72,6 +72,7 @@ static const struct rtw89_pci_info rtw8851b_pci_info = { static const struct rtw89_driver_info rtw89_8851be_info = { .chip = &rtw8851b_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851bu.c b/drivers/net/wireless/realtek/rtw89/rtw8851bu.c index 34ba5661d771..1e827205f254 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851bu.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851bu.c @@ -30,6 +30,7 @@ static const struct rtw89_usb_info rtw8851b_usb_info = { static const struct rtw89_driver_info rtw89_8851bu_info = { .chip = &rtw8851b_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852ae.c b/drivers/net/wireless/realtek/rtw89/rtw8852ae.c index 64306cdc1ee4..142a45a2e718 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852ae.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852ae.c @@ -70,6 +70,7 @@ static const struct rtw89_pci_info rtw8852a_pci_info = { static const struct rtw89_driver_info rtw89_8852ae_info = { .chip = &rtw8852a_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852au.c b/drivers/net/wireless/realtek/rtw89/rtw8852au.c index 29b7f7769370..065f4e5b17af 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852au.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852au.c @@ -32,6 +32,7 @@ static const struct rtw89_usb_info rtw8852a_usb_info = { static const struct rtw89_driver_info rtw89_8852au_info = { .chip = &rtw8852a_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852be.c b/drivers/net/wireless/realtek/rtw89/rtw8852be.c index 5bc0a6a99d1d..1c622a99c070 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852be.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852be.c @@ -72,6 +72,7 @@ static const struct rtw89_pci_info rtw8852b_pci_info = { static const struct rtw89_driver_info rtw89_8852be_info = { .chip = &rtw8852b_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bte.c b/drivers/net/wireless/realtek/rtw89/rtw8852bte.c index 49a72ca835ac..73c7ebd33ab4 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852bte.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852bte.c @@ -78,6 +78,7 @@ static const struct rtw89_pci_info rtw8852bt_pci_info = { static const struct rtw89_driver_info rtw89_8852bte_info = { .chip = &rtw8852bt_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bu.c b/drivers/net/wireless/realtek/rtw89/rtw8852bu.c index 308d3d570ff3..de79a19a2824 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852bu.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852bu.c @@ -30,6 +30,7 @@ static const struct rtw89_usb_info rtw8852b_usb_info = { static const struct rtw89_driver_info rtw89_8852bu_info = { .chip = &rtw8852b_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852ce.c b/drivers/net/wireless/realtek/rtw89/rtw8852ce.c index 3c64c0539205..bb5d5745aa62 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852ce.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852ce.c @@ -101,6 +101,7 @@ static const struct dmi_system_id rtw8852c_pci_quirks[] = { static const struct rtw89_driver_info rtw89_8852ce_info = { .chip = &rtw8852c_chip_info, .variant = NULL, + .board = NULL, .quirks = rtw8852c_pci_quirks, .dev_id_quirks = 0, .bus = { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852cu.c b/drivers/net/wireless/realtek/rtw89/rtw8852cu.c index 81ee96b0a048..51dd8fb05366 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852cu.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852cu.c @@ -32,6 +32,7 @@ static const struct rtw89_usb_info rtw8852c_usb_info = { static const struct rtw89_driver_info rtw89_8852cu_info = { .chip = &rtw8852c_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { @@ -39,9 +40,62 @@ static const struct rtw89_driver_info rtw89_8852cu_info = { }, }; +static const struct rtw89_led_gpio_entry rtw8852cu_valve_led_gpios[] = { + { + .pin = 8, + .color = LED_COLOR_ID_WHITE, + .intensity = 0, + .pinmux = {.addr = R_AX_GPIO8_15_FUNC_SEL, + .mask = GENMASK(3, 0), + .data = 0xf}, + .mode = {.addr = R_AX_GPIO_EXT_CTRL + 2, .data = BIT(0) | BIT(8)}, + .out = {.addr = R_AX_GPIO_EXT_CTRL + 1, .data = BIT(0)}, + }, { + .pin = 18, + .color = LED_COLOR_ID_RED, + .intensity = 0, + .pinmux = {.addr = R_AX_GPIO16_23_FUNC_SEL, + .mask = GENMASK(11, 8), + .data = 0xf}, + .mode = {.addr = R_AX_GPIO_16_TO_18_EXT_CTRL + 2, + .data = BIT(2) | BIT(10)}, + .out = {.addr = R_AX_GPIO_16_TO_18_EXT_CTRL + 1, .data = BIT(2)}, + }, { + .pin = 16, + .color = LED_COLOR_ID_GREEN, + .intensity = 1, + .pinmux = {.addr = R_AX_GPIO16_23_FUNC_SEL, + .mask = GENMASK(3, 0), + .data = 0xf}, + .mode = {.addr = R_AX_GPIO_16_TO_18_EXT_CTRL + 2, + .data = BIT(0) | BIT(8)}, + .out = {.addr = R_AX_GPIO_16_TO_18_EXT_CTRL + 1, .data = BIT(0)}, + }, { + .pin = 17, + .color = LED_COLOR_ID_BLUE, + .intensity = 0, + .pinmux = {.addr = R_AX_GPIO16_23_FUNC_SEL, + .mask = GENMASK(7, 4), + .data = 0xf}, + .mode = {.addr = R_AX_GPIO_16_TO_18_EXT_CTRL + 2, + .data = BIT(1) | BIT(9)}, + .out = {.addr = R_AX_GPIO_16_TO_18_EXT_CTRL + 1, .data = BIT(1)}, + }, +}; + +static const struct rtw89_led_desc rtw8852cu_valve_led_desc = { + .gpios = rtw8852cu_valve_led_gpios, + .n_gpio = ARRAY_SIZE(rtw8852cu_valve_led_gpios), +}; + +static const struct rtw89_board_variant rtw89_8852cu_valve_board = { + .led_desc = &rtw8852cu_valve_led_desc, +}; + static const struct rtw89_driver_info rtw89_8852cu_valve_info = { .chip = &rtw8852c_chip_info, .variant = NULL, + .board = &rtw89_8852cu_valve_board, .quirks = NULL, .dev_id_quirks = BIT(RTW89_QUIRK_HW_INFO_SYSFS), .bus = { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922ae.c b/drivers/net/wireless/realtek/rtw89/rtw8922ae.c index 5527a8db393b..8f3a1cd463d9 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922ae.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922ae.c @@ -76,6 +76,7 @@ static const struct rtw89_pci_info rtw8922a_pci_info = { static const struct rtw89_driver_info rtw89_8922ae_info = { .chip = &rtw8922a_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { @@ -86,6 +87,7 @@ static const struct rtw89_driver_info rtw89_8922ae_info = { static const struct rtw89_driver_info rtw89_8922ae_vs_info = { .chip = &rtw8922a_chip_info, .variant = &rtw8922ae_vs_variant, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922au.c b/drivers/net/wireless/realtek/rtw89/rtw8922au.c index 2b81de501d62..56c79b1ec865 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922au.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922au.c @@ -31,6 +31,7 @@ static const struct rtw89_usb_info rtw8922a_usb_info = { static const struct rtw89_driver_info rtw89_8922au_info = { .chip = &rtw8922a_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922de.c b/drivers/net/wireless/realtek/rtw89/rtw8922de.c index a1a81c338be3..09400a0efcfd 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922de.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922de.c @@ -72,6 +72,7 @@ static const struct rtw89_pci_info rtw8922d_pci_info = { static const struct rtw89_driver_info rtw89_8922de_vs_info = { .chip = &rtw8922d_chip_info, .variant = &rtw8922de_vs_variant, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { @@ -82,6 +83,7 @@ static const struct rtw89_driver_info rtw89_8922de_vs_info = { static const struct rtw89_driver_info rtw89_8922de_info = { .chip = &rtw8922d_chip_info, .variant = NULL, + .board = NULL, .quirks = NULL, .dev_id_quirks = 0, .bus = { From 355626a2c23271bfbb9accaf372d89557b0d64ba Mon Sep 17 00:00:00 2001 From: David Lee Date: Fri, 17 Jul 2026 14:19:04 +0800 Subject: [PATCH 0532/1433] wifi: rtw89: 8852cu: add quirk to disable 2.4 GHz band Add RTW89_QUIRK_DISABLE_2GHZ to the rtw89_quirks enum to allow per-device suppression of the 2.4 GHz band. Apply the quirk only for the 0x28de:0x2432 (VID:PID) USB device, which operates only in 5/6 GHz bands. Signed-off-by: David Lee Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717061910.54466-4-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.c | 3 +++ drivers/net/wireless/realtek/rtw89/core.h | 1 + drivers/net/wireless/realtek/rtw89/rtw8852cu.c | 3 ++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index b87a72700b99..77a0e2582dbe 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -6418,6 +6418,9 @@ static int rtw89_core_set_supported_band(struct rtw89_dev *rtwdev) u8 support_bands = rtwdev->chip->support_bands; int ret; + if (test_bit(RTW89_QUIRK_DISABLE_2GHZ, rtwdev->quirks)) + support_bands &= ~BIT(NL80211_BAND_2GHZ); + if (support_bands & BIT(NL80211_BAND_2GHZ)) { sband = rtw89_core_sband_dup(rtwdev, &rtw89_sband_2ghz); if (!sband) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 70cf6cbf4e8a..9d782cbfee70 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -6057,6 +6057,7 @@ enum rtw89_quirks { RTW89_QUIRK_THERMAL_PROT_120C, RTW89_QUIRK_THERMAL_PROT_110C, RTW89_QUIRK_HW_INFO_SYSFS, + RTW89_QUIRK_DISABLE_2GHZ, NUM_OF_RTW89_QUIRKS, }; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852cu.c b/drivers/net/wireless/realtek/rtw89/rtw8852cu.c index 51dd8fb05366..2dec9b845481 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852cu.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852cu.c @@ -97,7 +97,8 @@ static const struct rtw89_driver_info rtw89_8852cu_valve_info = { .variant = NULL, .board = &rtw89_8852cu_valve_board, .quirks = NULL, - .dev_id_quirks = BIT(RTW89_QUIRK_HW_INFO_SYSFS), + .dev_id_quirks = BIT(RTW89_QUIRK_HW_INFO_SYSFS) | + BIT(RTW89_QUIRK_DISABLE_2GHZ), .bus = { .usb = &rtw8852c_usb_info, }, From e92d5b02b4563bb1ad6cd34acfa06e5b8f7c92dc Mon Sep 17 00:00:00 2001 From: Chih-Kang Chang Date: Fri, 17 Jul 2026 14:19:05 +0800 Subject: [PATCH 0533/1433] wifi: rtw89: rfk: update TXIQK H2C command format to v1 TX IQK is a RF calibration, the v1 format adds a field for the thermal re-calibration parameter for RTL8922D after FW 0.35.113.0. Update the format accordingly. Signed-off-by: Chih-Kang Chang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717061910.54466-5-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.h | 1 + drivers/net/wireless/realtek/rtw89/fw.c | 35 ++++++++++++++++------- drivers/net/wireless/realtek/rtw89/fw.h | 7 ++++- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 9d782cbfee70..9b7f197a3af2 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -5599,6 +5599,7 @@ enum rtw89_fw_feature { ), RTW89_FW_FEATURE_RFK_RXDCK_V0, RTW89_FW_FEATURE_RFK_IQK_V0, + RTW89_FW_FEATURE_RFK_TXIQK_V0, RTW89_FW_FEATURE_NO_WOW_CPU_IO_RX, RTW89_FW_FEATURE_NOTIFY_AP_INFO, RTW89_FW_FEATURE_CH_INFO_BE_V0, diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 0db77120298f..ad87484f51a7 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -948,6 +948,7 @@ static const struct __fw_feat_cfg fw_feat_tbl[] = { __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 104, 0, TX_HISTORY_V1), __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 108, 0, SIM_SER_L0L1_BY_HALT_H2C), __CFG_FW_FEAT(RTL8922D, lt, 0, 35, 109, 1, SCAN_OFFLOAD_BE_V1), + __CFG_FW_FEAT(RTL8922D, lt, 0, 35, 113, 0, RFK_TXIQK_V0), }; static void rtw89_fw_iterate_feature_cfg(struct rtw89_fw_info *fw, @@ -8464,29 +8465,43 @@ int rtw89_fw_h2c_rf_tas_trigger(struct rtw89_dev *rtwdev, bool enable) int rtw89_fw_h2c_rf_txiqk(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx, const struct rtw89_chan *chan) { + struct rtw89_h2c_rf_txiqk_v0 *h2c_v0; struct rtw89_h2c_rf_txiqk *h2c; u32 len = sizeof(*h2c); struct sk_buff *skb; + u8 ver = U8_MAX; int ret; + if (RTW89_CHK_FW_FEATURE(RFK_TXIQK_V0, &rtwdev->fw)) { + len = sizeof(*h2c_v0); + ver = 0; + } + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); if (!skb) { rtw89_err(rtwdev, "failed to alloc skb for h2c RF TXIQK\n"); return -ENOMEM; } skb_put(skb, len); + h2c_v0 = (struct rtw89_h2c_rf_txiqk_v0 *)skb->data; + + h2c_v0->len = len; + h2c_v0->phy = phy_idx; + h2c_v0->txiqk_enable = true; + h2c_v0->is_wb_txiqk = true; + h2c_v0->kpath = RF_AB; + h2c_v0->cur_band = chan->band_type; + h2c_v0->cur_bw = chan->band_width; + h2c_v0->cur_ch = chan->channel; + h2c_v0->txiqk_dbg_en = rtw89_debug_is_enabled(rtwdev, RTW89_DBG_RFK); + + if (ver == 0) + goto hdr; + h2c = (struct rtw89_h2c_rf_txiqk *)skb->data; + h2c->is_ther_rek = false; - h2c->len = len; - h2c->phy = phy_idx; - h2c->txiqk_enable = true; - h2c->is_wb_txiqk = true; - h2c->kpath = RF_AB; - h2c->cur_band = chan->band_type; - h2c->cur_bw = chan->band_width; - h2c->cur_ch = chan->channel; - h2c->txiqk_dbg_en = rtw89_debug_is_enabled(rtwdev, RTW89_DBG_RFK); - +hdr: rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_OUTSRC, H2C_CL_OUTSRC_RF_FW_RFK, H2C_FUNC_RFK_TXIQK_OFFOAD, 0, 0, len); diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index a1aab8293c14..2654f8fd0e51 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -5042,7 +5042,7 @@ struct rtw89_h2c_rf_rxdck { u8 is_chl_k; } __packed; -struct rtw89_h2c_rf_txiqk { +struct rtw89_h2c_rf_txiqk_v0 { u8 len; u8 phy; u8 txiqk_enable; @@ -5054,6 +5054,11 @@ struct rtw89_h2c_rf_txiqk { u8 txiqk_dbg_en; } __packed; +struct rtw89_h2c_rf_txiqk { + struct rtw89_h2c_rf_txiqk_v0 v0; + u8 is_ther_rek; +} __packed; + struct rtw89_h2c_rf_cim3k { u8 len; u8 phy; From 89833570775ad001a79b6fc970d814c9fc70cf40 Mon Sep 17 00:00:00 2001 From: Chih-Kang Chang Date: Fri, 17 Jul 2026 14:19:06 +0800 Subject: [PATCH 0534/1433] wifi: rtw89: 8922d: bypass TXIQK when scan When connected to an AP and a scan is triggered, the FW may fail to transmit probe request because switching channels may load an uninitialized TXIQK table. Therefore, bypass TXIQK during scanning to avoid using invalid calibration values. Signed-off-by: Chih-Kang Chang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717061910.54466-6-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.h | 1 + drivers/net/wireless/realtek/rtw89/reg.h | 10 +++ drivers/net/wireless/realtek/rtw89/rtw8922d.c | 64 ++++++++++++++++++- 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 9b7f197a3af2..2d9113cb43f9 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -6284,6 +6284,7 @@ struct rtw89_iqk_info { u8 iqk_table_idx[RTW89_IQK_PATH_NR]; u32 lok_idac[RTW89_IQK_CHS_NR][RTW89_IQK_PATH_NR]; u32 lok_vbuf[RTW89_IQK_CHS_NR][RTW89_IQK_PATH_NR]; + u32 iqc_bak[2]; }; #define RTW89_DPK_RF_PATH 2 diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h index 053028b9ef16..756b94dcd475 100644 --- a/drivers/net/wireless/realtek/rtw89/reg.h +++ b/drivers/net/wireless/realtek/rtw89/reg.h @@ -10121,6 +10121,7 @@ #define B_S1_DACKQ8_K GENMASK(15, 8) #define R_NCTL_CFG 0x8000 #define R_NCTL_CFG_BE4 0x38000 +#define B_NCTL_CFG_BE4_SPAGE GENMASK(25, 24) #define B_NCTL_CHK_EN BIT(3) #define B_NCTL_CFG_SPAGE GENMASK(2, 1) #define R_NCTL_RPT 0x8008 @@ -11061,6 +11062,15 @@ #define B_KTBL0_MLD_IDX0 GENMASK(25, 24) #define B_KTBL0_MLD_IDX1 GENMASK(27, 26) #define B_KTBL0_RST BIT(31) +#define R_CFIR_CTRL_A_BE4 0x38124 +#define R_CFIR_CTRL_B_BE4 0x38224 +#define B_CFIR_CTRL_KIDX0_EN BIT(8) +#define B_CFIR_CTRL_KIDX1_EN BIT(24) +#define R_TX_IQC_A_BE4 0x38138 +#define R_TX_IQC_B_BE4 0x38238 +#define B_TX_IQC_X GENMASK(31, 20) +#define B_TX_IQC_Y GENMASK(19, 8) +#define B_TX_IQC_DPK BIT(3) #define R_KTBL1A_BE4 0x38154 #define R_KTBL1B_BE4 0x38254 #define B_KTBL1_TBL0 BIT(3) diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c index 929bcfc8089d..fde4117bcd49 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c @@ -2765,14 +2765,74 @@ static void rtw8922d_rfk_band_changed(struct rtw89_dev *rtwdev, { } +static void __rtw8922d_txiqk_disable(struct rtw89_dev *rtwdev) +{ + struct rtw89_iqk_info *iqk_info = &rtwdev->iqk; + u8 path, kidx; + + for (path = RF_PATH_A; path <= RF_PATH_B; path++) { + kidx = rtw89_phy_read32_mask(rtwdev, R_KTBL0A_BE4 + (path << 8), + B_KTBL0_IDX0); + if (kidx == 0) { + rtw89_phy_write32_clr(rtwdev, R_CFIR_CTRL_A_BE4 + (path << 8), + B_CFIR_CTRL_KIDX0_EN); + } else if (kidx == 1) { + rtw89_phy_write32_clr(rtwdev, R_CFIR_CTRL_A_BE4 + (path << 8), + B_CFIR_CTRL_KIDX1_EN); + } else { + rtw89_phy_write32_mask(rtwdev, R_NCTL_CFG_BE4, + B_NCTL_CFG_BE4_SPAGE, 0x1); + rtw89_phy_write32_clr(rtwdev, R_CFIR_CTRL_A_BE4 + (path << 8), + B_CFIR_CTRL_KIDX0_EN); + rtw89_phy_write32_mask(rtwdev, R_NCTL_CFG_BE4, + B_NCTL_CFG_BE4_SPAGE, 0x0); + } + + iqk_info->iqc_bak[path] = + rtw89_phy_read32(rtwdev, R_TX_IQC_A_BE4 + (path << 8)); + rtw89_phy_write32(rtwdev, R_TX_IQC_A_BE4 + (path << 8), 0x40000002); + } +} + +static void __rtw8922d_txiqk_enable(struct rtw89_dev *rtwdev) +{ + struct rtw89_iqk_info *iqk_info = &rtwdev->iqk; + u8 path, kidx; + + for (path = RF_PATH_A; path <= RF_PATH_B; path++) { + kidx = rtw89_phy_read32_mask(rtwdev, R_KTBL0A_BE4 + (path << 8), + B_KTBL0_IDX0); + if (kidx == 0) { + rtw89_phy_write32_set(rtwdev, R_CFIR_CTRL_A_BE4 + (path << 8), + B_CFIR_CTRL_KIDX0_EN); + } else if (kidx == 1) { + rtw89_phy_write32_set(rtwdev, R_CFIR_CTRL_A_BE4 + (path << 8), + B_CFIR_CTRL_KIDX1_EN); + } else { + rtw89_phy_write32_mask(rtwdev, R_NCTL_CFG_BE4, + B_NCTL_CFG_BE4_SPAGE, 0x1); + rtw89_phy_write32_set(rtwdev, R_CFIR_CTRL_A_BE4 + (path << 8), + B_CFIR_CTRL_KIDX0_EN); + rtw89_phy_write32_mask(rtwdev, R_NCTL_CFG_BE4, + B_NCTL_CFG_BE4_SPAGE, 0x0); + } + + rtw89_phy_write32(rtwdev, R_TX_IQC_A_BE4 + (path << 8), + iqk_info->iqc_bak[path]); + } +} + static void rtw8922d_rfk_scan(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link, bool start) { - if (start) + if (start) { __rtw8922d_tssi_disable(rtwdev, rtwvif_link->phy_idx); - else + __rtw8922d_txiqk_disable(rtwdev); + } else { __rtw8922d_tssi_enable(rtwdev, rtwvif_link->phy_idx); + __rtw8922d_txiqk_enable(rtwdev); + } } static void rtw8922d_rfk_track(struct rtw89_dev *rtwdev) From ce18a0cbc39b47f4b642b2e28fe1e37176f0b074 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Fri, 17 Jul 2026 14:19:07 +0800 Subject: [PATCH 0535/1433] wifi: rtw89: wow: extend timeout unit to avoid SER false alarm With original timeout unit, it might trigger SER false alarm causing WiFi card lost. Extend timeout unit to avoid wrong SER. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717061910.54466-7-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/pci.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/pci.h b/drivers/net/wireless/realtek/rtw89/pci.h index 92c30c7f9fb2..339706d99f6c 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.h +++ b/drivers/net/wireless/realtek/rtw89/pci.h @@ -1022,7 +1022,7 @@ #define B_BE_PL1_IGNORE_HOT_RST BIT(30) #define B_BE_PL1_TIMER_UNIT_MASK GENMASK(19, 17) #define PCIE_SER_TIMER_UNIT 0x2 -#define PCIE_SER_WOW_TIMER_UNIT 0x4 +#define PCIE_SER_WOW_TIMER_UNIT 0x7 #define B_BE_PL1_TIMER_CLEAR BIT(0) #define R_BE_REG_PL1_MASK 0x34B0 From 238bc3141cde49aef7d815d277d4a3282160c4fe Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Fri, 17 Jul 2026 14:19:08 +0800 Subject: [PATCH 0536/1433] wifi: rtw89: pci: set PCIe maximum TS1 for RTL8922DE Set TS1 (training sequence 1) to 1024 when PCIe enters recovery state to improve RF interference while entering and leaving L1ss. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717061910.54466-8-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/pci.h | 1 + drivers/net/wireless/realtek/rtw89/pci_be.c | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/pci.h b/drivers/net/wireless/realtek/rtw89/pci.h index 339706d99f6c..0e1557cedd20 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.h +++ b/drivers/net/wireless/realtek/rtw89/pci.h @@ -1138,6 +1138,7 @@ #define RTW89_PCIE_GEN1_SPEED 0x01 #define RTW89_PCIE_GEN2_SPEED 0x02 #define RTW89_PCIE_PHY_RATE 0x82 +#define RTW89_PCIE_EXTENDED_SYNCH BIT(7) #define RTW89_PCIE_PHY_RATE_MASK GENMASK(1, 0) #define RTW89_PCIE_LINK_CHANGE_SPEED 0xA0 #define RTW89_PCIE_L1SS_STS_V1 0x0168 diff --git a/drivers/net/wireless/realtek/rtw89/pci_be.c b/drivers/net/wireless/realtek/rtw89/pci_be.c index 6390980b8ee0..d338bf633272 100644 --- a/drivers/net/wireless/realtek/rtw89/pci_be.c +++ b/drivers/net/wireless/realtek/rtw89/pci_be.c @@ -85,13 +85,22 @@ static void _patch_pcie_power_wake_be(struct rtw89_dev *rtwdev, bool power_up) static void _patch_pre_init_be(struct rtw89_dev *rtwdev) { + struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; struct rtw89_hal *hal = &rtwdev->hal; + struct pci_dev *pdev = rtwpci->pdev; - if (!(rtwdev->chip->chip_id == RTL8922D && hal->cid == RTL8922D_CID7090)) + if (rtwdev->chip->chip_id != RTL8922D) return; + if (hal->cid != RTL8922D_CID7090) + goto set_ts1; + rtw89_write16_clr(rtwdev, R_RAC_DIRECT_OFFSET_BE_LANE0_G2 + RAC_ANA14 * RAC_MULT, EIEOS_L1SS_WAIT_CLKRDY); + +set_ts1: + pci_clear_and_set_config_dword(pdev, RTW89_PCIE_L1_STS_V1, + 0, RTW89_PCIE_EXTENDED_SYNCH); } static void rtw89_pci_set_io_rcy_be(struct rtw89_dev *rtwdev) From e738d2ac80c945c8fdf91b673e9cadeb394bfe6c Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Fri, 17 Jul 2026 14:19:09 +0800 Subject: [PATCH 0537/1433] wifi: rtw89: 8922d: reduce IO in power-on function To improve initial time, merge some IO to reduce IO times. Two registers are: 1. R_BE_SYS_PW_CTRL: 0x4[12:11]=0, 0x4[18]=1, 0x4[15]=0, and 0x4[10]=0 2. R_BE_SYS_ADIE_PAD_PWR_CTRL: Merge 0x18[6] and 0x18[5] Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717061910.54466-9-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/rtw8922d.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c index fde4117bcd49..fd4c92ba3f7b 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c @@ -518,14 +518,15 @@ static int rtw8922d_pwr_on_func(struct rtw89_dev *rtwdev) } begin: - rtw89_write32_clr(rtwdev, R_BE_SYS_PW_CTRL, B_BE_AFSM_WLSUS_EN | - B_BE_AFSM_PCIE_SUS_EN); - rtw89_write32_set(rtwdev, R_BE_SYS_PW_CTRL, B_BE_DIS_WLBT_PDNSUSEN_SOPC); - rtw89_write32_set(rtwdev, R_BE_WLLPS_CTRL, B_BE_DIS_WLBT_LPSEN_LOPC); + val32 = rtw89_read32(rtwdev, R_BE_SYS_PW_CTRL); + val32 &= ~(B_BE_AFSM_WLSUS_EN | B_BE_AFSM_PCIE_SUS_EN | B_BE_APFM_SWLPS); + val32 |= B_BE_DIS_WLBT_PDNSUSEN_SOPC; if (hal->cid != RTL8922D_CID7090) - rtw89_write32_clr(rtwdev, R_BE_SYS_PW_CTRL, B_BE_APDM_HPDN); + val32 &= ~B_BE_APDM_HPDN; + rtw89_write32(rtwdev, R_BE_SYS_PW_CTRL, val32); + + rtw89_write32_set(rtwdev, R_BE_WLLPS_CTRL, B_BE_DIS_WLBT_LPSEN_LOPC); rtw89_write32_clr(rtwdev, R_BE_FWS1ISR, B_BE_FS_WL_HW_RADIO_OFF_INT); - rtw89_write32_clr(rtwdev, R_BE_SYS_PW_CTRL, B_BE_APFM_SWLPS); ret = read_poll_timeout(rtw89_read32, val32, val32 & B_BE_RDY_SYSPWR, 1000, 3000000, false, rtwdev, R_BE_SYS_PW_CTRL); @@ -581,14 +582,13 @@ static int rtw8922d_pwr_on_func(struct rtw89_dev *rtwdev) if (ret) return ret; - rtw89_write32_set(rtwdev, R_BE_SYS_ADIE_PAD_PWR_CTRL, B_BE_SYM_PADPDN_WL_RFC1_1P3); + rtw89_write32_set(rtwdev, R_BE_SYS_ADIE_PAD_PWR_CTRL, + B_BE_SYM_PADPDN_WL_RFC1_1P3 | B_BE_SYM_PADPDN_WL_RFC0_1P3); ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_ANAPAR_WL, 0x40, 0x40); if (ret) return ret; - rtw89_write32_set(rtwdev, R_BE_SYS_ADIE_PAD_PWR_CTRL, B_BE_SYM_PADPDN_WL_RFC0_1P3); - ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_ANAPAR_WL, 0x20, 0x20); if (ret) return ret; From b3dc0a37fd3ad1feb248e7d4c51c49799359b0e2 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Fri, 17 Jul 2026 14:19:10 +0800 Subject: [PATCH 0538/1433] wifi: rtw89: phy: set CFR to manual mode for some 2GHz channels The CFR (Channel Frequency Response) manual mode condition for 2GHz is missing to limit on bandwidth for specific channels, which are channel 13 with 20MHz bandwidth and channel 11 with 40MHz bandwidth. Also add a band check to avoid affecting 5GHz/6GHz bands. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717061910.54466-10-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/phy_be.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/phy_be.c b/drivers/net/wireless/realtek/rtw89/phy_be.c index e471409a4b8f..c06927d1dc78 100644 --- a/drivers/net/wireless/realtek/rtw89/phy_be.c +++ b/drivers/net/wireless/realtek/rtw89/phy_be.c @@ -707,13 +707,19 @@ void rtw89_phy_bb_wrap_set_rfsi_bandedge_ch(struct rtw89_dev *rtwdev, const struct rtw89_chan *chan, enum rtw89_phy_idx phy_idx) { + bool cfr_manual_en = false; u32 reg; u32 val; val = rtw89_phy_bb_wrap_be_bandedge_decision(rtwdev, chan); + if (chan->band_type == RTW89_BAND_2G && + ((chan->primary_channel == 13 && chan->band_width == RTW89_CHANNEL_WIDTH_20) || + (chan->primary_channel == 11 && chan->band_width == RTW89_CHANNEL_WIDTH_40))) + cfr_manual_en = true; + rtw89_phy_write32_idx(rtwdev, R_TX_CFR_MANUAL_EN_BE4, B_TX_CFR_MANUAL_EN_BE4_M, - chan->primary_channel == 13, phy_idx); + cfr_manual_en, phy_idx); reg = rtw89_mac_reg_by_idx(rtwdev, R_BANDEDGE_DBWX_BE4, phy_idx); rtw89_write32_mask(rtwdev, reg, B_BANDEDGE_DBW20_BE4, val & BIT(0)); From 0427315a6ed1478199dadaed9b4d0f633f702391 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 17 Jul 2026 14:57:27 +0800 Subject: [PATCH 0539/1433] wifi: rtw89: coex: Add version 107 TX/RX info for firmware feature The previous version 7 format is for Dual-Bluetooth using. This patch is for single Bluetooth solution using. Driver will summary Wi-Fi now status for firmware to train RF parameters & TDMA mechanism. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717065739.64124-2-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 27 ++++-- drivers/net/wireless/realtek/rtw89/fw.c | 112 ++++++++++++++++------ drivers/net/wireless/realtek/rtw89/fw.h | 24 +++++ 3 files changed, 126 insertions(+), 37 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index b044b25aeade..3addcdfd89eb 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -3081,10 +3081,15 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index) if (ver->drvinfo_ver > 1) index = 3; + if (ver->fcxtrx == 0) + return; + if (ver->fcxtrx == 7) rtw89_fw_h2c_cxdrv_trx_v7(rtwdev, index); else if (ver->fcxtrx == 9) rtw89_fw_h2c_cxdrv_trx_v9(rtwdev, index); + else if (ver->fcxtrx == 107) + rtw89_fw_h2c_cxdrv_trx_v107(rtwdev, index); break; case CXDRVINFO_RFK: if (ver->drvinfo_ver != 0) @@ -3096,7 +3101,7 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index) if (ver->drvinfo_ver == 3) index = 4; - if (ver->fcxtrx == 7) + if (ver->fcxtrx == 7 || ver->fcxtrx == 107) rtw89_fw_h2c_cxtxpwr_v7(rtwdev, index); else if (ver->fcxtrx == 9) rtw89_fw_h2c_cxtxpwr_v9(rtwdev, index); @@ -3358,7 +3363,8 @@ static void _set_wl_tx_power(struct rtw89_dev *rtwdev, u32 level, u8 phy_map) dm->rf_trx_para.wl_tx_power[RTW89_PHY_0], dm->rf_trx_para.wl_tx_power[RTW89_PHY_1]); - if (ver->fcxtrx == 7 && chip->chip_id == RTL8922A) { + if ((ver->fcxtrx == 7 && chip->chip_id == RTL8922A) || + ver->fcxtrx == 107) { _fw_set_drv_info(rtwdev, CXDRVINFO_TXPWR); } else if (ver->fcxtrx == 9) { _fw_set_drv_info(rtwdev, CXDRVINFO_TXPWR); @@ -3632,12 +3638,17 @@ static void _set_rf_trx_para(struct rtw89_dev *rtwdev) u8 bid = BTC_BT_1ST, lv; u32 wl_stb_chg; - if (ver->fcxtrx == 9 && chip->rf_para_ulink_v9) { - ul_para_num = chip->rf_para_ulink_num_v9; - dl_para_num = chip->rf_para_dlink_num_v9; - _set_rf_trx_para_v9(rtwdev); - return; - } else if (ver->fcxtrx == 0 && chip->rf_para_ulink_v0) { + if (ver->fcxtrx == 9) { + /* Early v9 need to assign UL/DL RF para at driver */ + if (chip->rf_para_ulink_v9) { + ul_para_num = chip->rf_para_ulink_num_v9; + dl_para_num = chip->rf_para_dlink_num_v9; + } else { + _set_rf_trx_para_v9(rtwdev); + return; + } + } else if ((ver->fcxtrx == 0 || ver->fcxtrx == 7 || ver->fcxtrx == 107) && + chip->rf_para_ulink_v0) { ul_para_num = chip->rf_para_ulink_num_v0; dl_para_num = chip->rf_para_dlink_num_v0; } else { diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index ad87484f51a7..533fb782bda8 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -6678,16 +6678,12 @@ int rtw89_fw_h2c_cxdrv_ctrl_v9(struct rtw89_dev *rtwdev, u8 type) return ret; } -int rtw89_fw_h2c_cxdrv_trx_v7(struct rtw89_dev *rtwdev, u8 type) +static void rtw89_btc_load_trx_para(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_rf_trx_para_v9 rf_para = btc->dm.rf_trx_para; struct rtw89_btc_trx_info *trx = &btc->dm.trx_info; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - struct rtw89_h2c_cxtrx_v7 *h2c; - u32 len = sizeof(*h2c); - struct sk_buff *skb; - int ret; u8 i; for (i = 0; i < RTW89_PHY_NUM; i++) { @@ -6695,19 +6691,38 @@ int rtw89_fw_h2c_cxdrv_trx_v7(struct rtw89_dev *rtwdev, u8 type) RTW89_BTC_WL_DEF_TX_PWR); trx->wl_rx_gain[i] = u32_get_bits(rf_para.wl_rx_gain[i], RTW89_BTC_WL_DEF_TX_PWR); + if (btc->ver->fcxtrx == 107) + break; } + for (i = 0; i < BTC_ALL_BT; i++) { trx->bt_tx_power[i] = u32_get_bits(rf_para.bt_tx_power[i], RTW89_BTC_WL_DEF_TX_PWR); trx->bt_rx_gain[i] = u32_get_bits(rf_para.bt_rx_gain[i], RTW89_BTC_WL_DEF_TX_PWR); + if (btc->ver->fcxtrx == 107) + break; + trx->zb_tx_power[i] = u32_get_bits(rf_para.zb_tx_power[i], RTW89_BTC_WL_DEF_TX_PWR); trx->zb_rx_gain[i] = u32_get_bits(rf_para.zb_rx_gain[i], RTW89_BTC_WL_DEF_TX_PWR); } + trx->cn = wl->cn_report; trx->nhm = wl->nhm.pwr; +} + +int rtw89_fw_h2c_cxdrv_trx_v7(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_trx_info *trx = &btc->dm.trx_info; + struct rtw89_h2c_cxtrx_v7 *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + int ret; + + rtw89_btc_load_trx_para(rtwdev); skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); if (!skb) { @@ -6718,7 +6733,7 @@ int rtw89_fw_h2c_cxdrv_trx_v7(struct rtw89_dev *rtwdev, u8 type) h2c = (struct rtw89_h2c_cxtrx_v7 *)skb->data; h2c->hdr.type = type; - h2c->hdr.ver = btc->ver->fcxtrx; + h2c->hdr.ver = 7; h2c->hdr.len = sizeof(*h2c) - H2C_LEN_CXDRVHDR_V7; h2c->v7_u8.tx_lvl = trx->tx_lvl; @@ -6761,33 +6776,14 @@ int rtw89_fw_h2c_cxdrv_trx_v7(struct rtw89_dev *rtwdev, u8 type) int rtw89_fw_h2c_cxdrv_trx_v9(struct rtw89_dev *rtwdev, u8 type) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_rf_trx_para_v9 rf_para = btc->dm.rf_trx_para; struct rtw89_btc_trx_info *trx = &btc->dm.trx_info; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_h2c_cxtrx_v9 *h2c; u32 len = sizeof(*h2c); struct sk_buff *skb; int ret; u8 i; - for (i = 0; i < RTW89_PHY_NUM; i++) { - trx->wl_tx_power[i] = u32_get_bits(rf_para.wl_tx_power[i], - RTW89_BTC_WL_DEF_TX_PWR); - trx->wl_rx_gain[i] = u32_get_bits(rf_para.wl_rx_gain[i], - RTW89_BTC_WL_DEF_TX_PWR); - } - for (i = 0; i < BTC_ALL_BT; i++) { - trx->bt_tx_power[i] = u32_get_bits(rf_para.bt_tx_power[i], - RTW89_BTC_WL_DEF_TX_PWR); - trx->bt_rx_gain[i] = u32_get_bits(rf_para.bt_rx_gain[i], - RTW89_BTC_WL_DEF_TX_PWR); - trx->zb_tx_power[i] = u32_get_bits(rf_para.zb_tx_power[i], - RTW89_BTC_WL_DEF_TX_PWR); - trx->zb_rx_gain[i] = u32_get_bits(rf_para.zb_rx_gain[i], - RTW89_BTC_WL_DEF_TX_PWR); - } - trx->cn = wl->cn_report; - trx->nhm = wl->nhm.pwr; + rtw89_btc_load_trx_para(rtwdev); skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); if (!skb) { @@ -6798,7 +6794,7 @@ int rtw89_fw_h2c_cxdrv_trx_v9(struct rtw89_dev *rtwdev, u8 type) h2c = (struct rtw89_h2c_cxtrx_v9 *)skb->data; h2c->hdr.type = type; - h2c->hdr.ver = btc->ver->fcxtrx; + h2c->hdr.ver = 9; h2c->hdr.len = sizeof(*h2c) - H2C_LEN_CXDRVHDR_V7; h2c->v9_u8.tx_lvl = trx->tx_lvl; @@ -6844,6 +6840,64 @@ int rtw89_fw_h2c_cxdrv_trx_v9(struct rtw89_dev *rtwdev, u8 type) return ret; } +int rtw89_fw_h2c_cxdrv_trx_v107(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_trx_info *trx = &btc->dm.trx_info; + struct rtw89_h2c_cxtrx_v107 *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + int ret; + + rtw89_btc_load_trx_para(rtwdev); + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxtrx_v107\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxtrx_v107 *)skb->data; + + h2c->hdr.type = type; + h2c->hdr.ver = 7; + h2c->hdr.len = sizeof(*h2c) - H2C_LEN_CXDRVHDR_V7; + + h2c->v107_u8.tx_lvl = trx->tx_lvl; + h2c->v107_u8.rx_lvl = trx->rx_lvl; + h2c->v107_u8.wl_rssi = trx->wl_rssi; + h2c->v107_u8.bt_rssi = trx->bt_rssi; + h2c->v107_u8.wl_tx_power = trx->bt_tx_power[RTW89_PHY_0]; + h2c->v107_u8.wl_rx_gain = trx->wl_rx_gain[RTW89_PHY_0]; + h2c->v107_u8.bt_tx_power = trx->bt_tx_power[BTC_BT_1ST]; + h2c->v107_u8.bt_rx_gain = trx->bt_rx_gain[BTC_BT_1ST]; + h2c->v107_u8.cn = trx->cn; + h2c->v107_u8.nhm = trx->nhm; + h2c->v107_u8.bt_profile = trx->bt_profile; + h2c->v107_u8.rsvd2 = trx->rsvd2; + h2c->v7_le.tx_rate = cpu_to_le16(trx->tx_rate); + h2c->v7_le.rx_rate = cpu_to_le16(trx->rx_rate); + h2c->v7_le.tx_tp = cpu_to_le32(trx->tx_tp); + h2c->v7_le.rx_tp = cpu_to_le32(trx->rx_tp); + h2c->v7_le.rx_err_ratio = cpu_to_le32(trx->rx_err_ratio); + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + #define H2C_LEN_CXDRVINFO_RFK (4 + H2C_LEN_CXDRVHDR) int rtw89_fw_h2c_cxdrv_rfk(struct rtw89_dev *rtwdev, u8 type) { @@ -6908,7 +6962,7 @@ int rtw89_fw_h2c_cxtxpwr_v7(struct rtw89_dev *rtwdev, u8 type) h2c = (struct rtw89_h2c_cxtxpwr_v7 *)skb->data; h2c->hdr.type = type; - h2c->hdr.ver = btc->ver->fcxtrx; + h2c->hdr.ver = 7; h2c->hdr.len = sizeof(*h2c) - H2C_LEN_CXDRVHDR_V7; h2c->pwr = rp.wl_tx_power[RTW89_PHY_0] & 0xff; @@ -6948,7 +7002,7 @@ int rtw89_fw_h2c_cxtxpwr_v9(struct rtw89_dev *rtwdev, u8 type) h2c = (struct rtw89_h2c_cxtxpwr_v9 *)skb->data; h2c->hdr.type = type; - h2c->hdr.ver = btc->ver->fcxtrx; + h2c->hdr.ver = 9; h2c->hdr.len = sizeof(*h2c) - H2C_LEN_CXDRVHDR_V7; if (dm->wl_tx_pwr_phy_map == BIT(RTW89_PHY_1)) h2c->pwr = rp.wl_tx_power[RTW89_PHY_1] & 0xff; diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index 2654f8fd0e51..67e9c8daff5f 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -2517,6 +2517,23 @@ struct rtw89_btc_trx_info_v7_u8 { u8 rsvd2; } __packed; +struct rtw89_btc_trx_info_v107_u8 { + u8 tx_lvl; + u8 rx_lvl; + u8 wl_rssi; + u8 bt_rssi; + + s8 wl_tx_power; + s8 wl_rx_gain; + s8 bt_tx_power; + s8 bt_rx_gain; + + u8 cn; + s8 nhm; + u8 bt_profile; + u8 rsvd2; +} __packed; + struct rtw89_btc_trx_info_le { __le16 tx_rate; __le16 rx_rate; @@ -2538,6 +2555,12 @@ struct rtw89_h2c_cxtrx_v7 { struct rtw89_btc_trx_info_le v7_le; } __packed; +struct rtw89_h2c_cxtrx_v107 { + struct rtw89_h2c_cxhdr_v7 hdr; + struct rtw89_btc_trx_info_v107_u8 v107_u8; + struct rtw89_btc_trx_info_le v7_le; +} __packed; + struct rtw89_h2c_cxtxpwr_v7 { struct rtw89_h2c_cxhdr_v7 hdr; u8 pwr; @@ -5400,6 +5423,7 @@ int rtw89_fw_h2c_cxdrv_ctrl_v7(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_ctrl_v9(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_trx_v7(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_trx_v9(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxdrv_trx_v107(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_rfk(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxtxpwr_v7(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxtxpwr_v9(struct rtw89_dev *rtwdev, u8 type); From 4bedf26e132c79ceb9a19556fd198f2b15b406c4 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 17 Jul 2026 14:57:28 +0800 Subject: [PATCH 0540/1433] wifi: rtw89: coex: Add version 9 report control info WiFi firmware will save its build date/time and package into C2H event then send to driver. It helps to analyze what kind of firmware was loaded now. Remove unnecessary memory set. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717065739.64124-3-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 166 +++++++++++++++++++++- drivers/net/wireless/realtek/rtw89/core.h | 18 +++ 2 files changed, 178 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 3addcdfd89eb..1bfd3f3d67de 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -1670,6 +1670,10 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, pfinfo = &pfwinfo->rpt_ctrl.finfo.v7; pcinfo->req_len = sizeof(pfwinfo->rpt_ctrl.finfo.v7); fwsubver->fcxbtcrpt = pfwinfo->rpt_ctrl.finfo.v7.fver; + } else if (ver->fcxbtcrpt == 9) { + pfinfo = &pfwinfo->rpt_ctrl.finfo.v9; + pcinfo->req_len = sizeof(pfwinfo->rpt_ctrl.finfo.v9); + fwsubver->fcxbtcrpt = pfwinfo->rpt_ctrl.finfo.v9.fver; } else if (ver->fcxbtcrpt == 11) { pfinfo = &pfwinfo->rpt_ctrl.finfo.v11; pcinfo->req_len = sizeof(pfwinfo->rpt_ctrl.finfo.v11); @@ -2075,6 +2079,46 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, else dm->error.map.h2c_c2h_buffer_mismatch = false; + _chk_btc_err(rtwdev, BTC_DCNT_BTCNT_HANG, 0); + _chk_btc_err(rtwdev, BTC_DCNT_RPT_HANG, val1); + _chk_btc_err(rtwdev, BTC_DCNT_WL_FW_VER_MATCH, 0); + _chk_btc_err(rtwdev, BTC_DCNT_BTTX_HANG, 0); + } else if (ver->fcxbtcrpt == 9) { + prpt->v9 = pfwinfo->rpt_ctrl.finfo.v9; + pfwinfo->rpt_en_map = le32_to_cpu(prpt->v9.rpt_info.en); + wl->ver_info.fw_coex = le32_to_cpu(prpt->v9.rpt_info.cx_ver); + wl->ver_info.fw = le32_to_cpu(prpt->v9.rpt_info.fw_ver); + + memcpy(wl->ver_info.build_time, + prpt->v9.build_time, + sizeof(wl->ver_info.build_time)); + memcpy(wl->ver_info.build_date, + prpt->v9.build_date, + sizeof(wl->ver_info.build_date)); + + for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) + memcpy(&dm->gnt_val[i], &prpt->v9.gnt_val[i], + sizeof(dm->gnt_val[i])); + + bt->bcnt[BTC_BCNT_HIPRI_TX] = + le16_to_cpu(prpt->v9.bt_cnt[BTC_BCNT_HI_TX_V105]); + bt->bcnt[BTC_BCNT_HIPRI_RX] = + le16_to_cpu(prpt->v9.bt_cnt[BTC_BCNT_HI_RX_V105]); + bt->bcnt[BTC_BCNT_LOPRI_TX] = + le16_to_cpu(prpt->v9.bt_cnt[BTC_BCNT_LO_TX_V105]); + bt->bcnt[BTC_BCNT_LOPRI_RX] = + le16_to_cpu(prpt->v9.bt_cnt[BTC_BCNT_LO_RX_V105]); + + val1 = le16_to_cpu(prpt->v9.bt_cnt[BTC_BCNT_POLLUTED_V105]); + if (val1 > bt->bcnt[BTC_BCNT_POLUT_NOW]) + val1 -= bt->bcnt[BTC_BCNT_POLUT_NOW]; /* diff */ + + bt->bcnt[BTC_BCNT_POLUT_DIFF] = val1; + bt->bcnt[BTC_BCNT_POLUT_NOW] = + le16_to_cpu(prpt->v9.bt_cnt[BTC_BCNT_POLLUTED_V105]); + + dm->pta_owner = prpt->v9.pta_owner; + _chk_btc_err(rtwdev, BTC_DCNT_BTCNT_HANG, 0); _chk_btc_err(rtwdev, BTC_DCNT_RPT_HANG, val1); _chk_btc_err(rtwdev, BTC_DCNT_WL_FW_VER_MATCH, 0); @@ -2085,14 +2129,9 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, wl->ver_info.fw_coex = le32_to_cpu(prpt->v11.rpt_info.cx_ver); wl->ver_info.fw = le32_to_cpu(prpt->v11.rpt_info.fw_ver); - memset(wl->ver_info.build_time, 0, - sizeof(wl->ver_info.build_time)); memcpy(wl->ver_info.build_time, prpt->v11.build_time, sizeof(wl->ver_info.build_time)); - - memset(wl->ver_info.build_date, 0, - sizeof(wl->ver_info.build_date)); memcpy(wl->ver_info.build_date, prpt->v11.build_date, sizeof(wl->ver_info.build_date)); @@ -2881,6 +2920,7 @@ static void rtw89_btc_fw_en_rpt(struct rtw89_dev *rtwdev, if (btc->ver->fcxbtcrpt == 7 || btc->ver->fcxbtcrpt == 8 || + btc->ver->fcxbtcrpt == 9 || btc->ver->fcxbtcrpt == 11) { r.v8.type = SET_REPORT_EN; r.v8.fver = btc->ver->fcxbtcrpt; @@ -9175,9 +9215,10 @@ static int _show_cx_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) ver_hotfix = FIELD_GET(GENMASK(15, 8), wl->ver_info.fw); id_branch = FIELD_GET(GENMASK(7, 0), wl->ver_info.fw); p += scnprintf(p, end - p, - " %-15s : WL_FW:%d.%d.%d.%d, BT_FW:0x%x(%s)\n", + " %-15s : WL_FW:%d.%d.%d.%d(built:%.12s-%.12s), BT_FW:0x%x(%s)\n", "[sub_module]", ver_main, ver_sub, ver_hotfix, id_branch, + wl->ver_info.build_time, wl->ver_info.build_date, bt->ver_info.fw, bt->run_patch_code ? "patch" : "ROM"); if (ver->fcxinit == 7) { @@ -12138,6 +12179,117 @@ static int _show_summary_v8(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) return p - buf; } +static int _show_summary_v9(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) +{ + struct rtw89_btc_btf_fwinfo *pfwinfo = &rtwdev->btc.fwinfo; + struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; + struct rtw89_btc_fbtc_rpt_ctrl_v9 *prptctrl = NULL; + struct rtw89_btc_cx *cx = &rtwdev->btc.cx; + struct rtw89_btc_dm *dm = &rtwdev->btc.dm; + struct rtw89_btc_wl_info *wl = &cx->wl; + u32 *cnt = rtwdev->btc.dm.cnt_notify; + char *p = buf, *end = buf + bufsz; + u32 cnt_sum = 0; + u8 i; + + if (!(dm->coex_info_map & BTC_COEX_INFO_SUMMARY)) + return 0; + + p += scnprintf(p, end - p, "%s", + "\n\r========== [Statistics] =========="); + + pcinfo = &pfwinfo->rpt_ctrl.cinfo; + if (pcinfo->valid && wl->status.map.lps != BTC_LPS_RF_OFF && + !wl->status.map.rf_off) { + prptctrl = &pfwinfo->rpt_ctrl.finfo.v9; + + p += scnprintf(p, end - p, + "\n\r %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d), c2h_cnt=%d(fw_send:%d, len:%d, ", + "[summary]", pfwinfo->cnt_h2c, + pfwinfo->cnt_h2c_fail, + le16_to_cpu(prptctrl->rpt_info.cnt_h2c), + pfwinfo->cnt_c2h, + le16_to_cpu(prptctrl->rpt_info.cnt_c2h), + le16_to_cpu(prptctrl->rpt_info.len_c2h)); + + p += scnprintf(p, end - p, + "rpt_cnt=%d(fw_send:%d), rpt_map=0x%x", + pfwinfo->event[BTF_EVNT_RPT], + le16_to_cpu(prptctrl->rpt_info.cnt), + le32_to_cpu(prptctrl->rpt_info.en)); + + if (dm->error.map.wl_fw_hang) + p += scnprintf(p, end - p, " (WL FW Hang!!)"); + + p += scnprintf(p, end - p, + "\n\r %-15s : send_ok:%d, send_fail:%d, recv:%d, ", + "[mailbox]", + le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_ok), + le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_fail), + le32_to_cpu(prptctrl->bt_mbx_info.cnt_recv)); + + p += scnprintf(p, end - p, + "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)", + le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_empty), + le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_flowctrl), + le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_tx), + le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_ack), + le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_nack)); + + p += scnprintf(p, end - p, + "\n\r %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]", + "[RFK/LPS]", wl->wcnt[BTC_WCNT_RFK_REQ], + wl->wcnt[BTC_WCNT_RFK_GO], + wl->wcnt[BTC_WCNT_RFK_REJECT], + wl->wcnt[BTC_WCNT_RFK_TIMEOUT], + wl->rfk_info.proc_time); + + p += scnprintf(p, end - p, ", bt_rfk[req:%d]", + le16_to_cpu(prptctrl->bt_cnt[BTC_BCNT_RFK_REQ])); + + p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]", + le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_on), + le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_off)); + } else { + p += scnprintf(p, end - p, + "\n\r %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)", + "[summary]", + pfwinfo->cnt_h2c, pfwinfo->cnt_h2c_fail, + pfwinfo->cnt_c2h, + wl->status.map.lps, wl->status.map.rf_off); + } + + for (i = 0; i < BTC_NCNT_NUM; i++) + cnt_sum += dm->cnt_notify[i]; + + p += scnprintf(p, end - p, + "\n\r %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ", + "[notify_cnt]", + cnt_sum, cnt[BTC_NCNT_SHOW_COEX_INFO], + cnt[BTC_NCNT_POWER_ON], cnt[BTC_NCNT_INIT_COEX]); + + p += scnprintf(p, end - p, + "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d", + cnt[BTC_NCNT_POWER_OFF], cnt[BTC_NCNT_RADIO_STATE], + cnt[BTC_NCNT_ROLE_INFO], cnt[BTC_NCNT_WL_RFK], + cnt[BTC_NCNT_WL_STA]); + + p += scnprintf(p, end - p, + "\n\r %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ", + "[notify_cnt]", + cnt[BTC_NCNT_SCAN_START], cnt[BTC_NCNT_SCAN_FINISH], + cnt[BTC_NCNT_SWITCH_BAND], cnt[BTC_NCNT_SWITCH_CHBW], + cnt[BTC_NCNT_SPECIAL_PACKET]); + + p += scnprintf(p, end - p, + "timer=%d, customerize=%d, hub_msg=%d, chg_fw=%d, send_cc=%d", + cnt[BTC_NCNT_TIMER], cnt[BTC_NCNT_CUSTOMERIZE], + rtwdev->btc.hubmsg_cnt, cnt[BTC_NCNT_RESUME_DL_FW], + cnt[BTC_NCNT_COUNTRYCODE]); + + return p - buf; +} + static int _show_summary_v11(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) { struct rtw89_btc_btf_fwinfo *pfwinfo = &rtwdev->btc.fwinfo; @@ -12311,6 +12463,8 @@ ssize_t rtw89_btc_dump_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += _show_summary_v7(rtwdev, p, end - p); else if (ver->fcxbtcrpt == 8) p += _show_summary_v8(rtwdev, p, end - p); + else if (ver->fcxbtcrpt == 9) + p += _show_summary_v9(rtwdev, p, end - p); else if (ver->fcxbtcrpt == 11) p += _show_summary_v11(rtwdev, p, end - p); diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 2d9113cb43f9..fb4c95ed1904 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -2790,6 +2790,23 @@ struct rtw89_btc_fbtc_rpt_ctrl_v8 { struct rtw89_btc_fbtc_rpt_ctrl_bt_mailbox bt_mbx_info; } __packed; +#define RTW89_BTC_TIME_DATE_FMT 12 +struct rtw89_btc_fbtc_rpt_ctrl_v9 { + u8 fver; + u8 ext_req_exist; + u8 pta_owner; + u8 rsvd; + + u8 build_time[RTW89_BTC_TIME_DATE_FMT]; + u8 build_date[RTW89_BTC_TIME_DATE_FMT]; + + u8 gnt_val[RTW89_PHY_NUM][4]; /* gwl/gbt012 refer to struct btc_gnt_ctrl */ + __le16 bt_cnt[BTC_BCNT_STA_MAX_V105]; + + struct rtw89_btc_fbtc_rpt_ctrl_info_v8 rpt_info; + struct rtw89_btc_fbtc_rpt_ctrl_bt_mailbox bt_mbx_info; +} __packed; + struct rtw89_btc_fbtc_rpt_ctrl_v11 { u8 fver; u8 rsvd0; @@ -2817,6 +2834,7 @@ union rtw89_btc_fbtc_rpt_ctrl_ver_info { struct rtw89_btc_fbtc_rpt_ctrl_v105 v105; struct rtw89_btc_fbtc_rpt_ctrl_v7 v7; struct rtw89_btc_fbtc_rpt_ctrl_v8 v8; + struct rtw89_btc_fbtc_rpt_ctrl_v9 v9; struct rtw89_btc_fbtc_rpt_ctrl_v11 v11; }; From b5bfb43844e1bb5681ad23d00a4d6930c4778fb1 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 17 Jul 2026 14:57:29 +0800 Subject: [PATCH 0541/1433] wifi: rtw89: coex: Fix unexpected grant-signal assignee The C2H report is for knowing what the grant-signal setting is now, not for applying new grant-signal setting. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717065739.64124-4-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 1bfd3f3d67de..5f2658eb516c 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -1935,8 +1935,8 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, dm->wl_fw_cx_offload = !!le32_to_cpu(prpt->v4.wl_fw_info.cx_offload); for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) - memcpy(&dm->gnt_set[i], &prpt->v4.gnt_val[i], - sizeof(dm->gnt_set[i])); + memcpy(&dm->gnt_val[i], &prpt->v4.gnt_val[i], + sizeof(dm->gnt_val[i])); bt->bcnt[BTC_BCNT_HIPRI_TX] = le32_to_cpu(prpt->v4.bt_cnt[BTC_BCNT_HI_TX]); @@ -1967,8 +1967,8 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, dm->wl_fw_cx_offload = 0; for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) - memcpy(&dm->gnt_set[i], &prpt->v5.gnt_val[i], - sizeof(dm->gnt_set[i])); + memcpy(&dm->gnt_val[i], &prpt->v5.gnt_val[i], + sizeof(dm->gnt_val[i])); bt->bcnt[BTC_BCNT_HIPRI_TX] = le16_to_cpu(prpt->v5.bt_cnt[BTC_BCNT_HI_TX]); @@ -1994,8 +1994,8 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, dm->wl_fw_cx_offload = 0; for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) - memcpy(&dm->gnt_set[i], &prpt->v105.gnt_val[i], - sizeof(dm->gnt_set[i])); + memcpy(&dm->gnt_val[i], &prpt->v105.gnt_val[i], + sizeof(dm->gnt_val[i])); bt->bcnt[BTC_BCNT_HIPRI_TX] = le16_to_cpu(prpt->v105.bt_cnt[BTC_BCNT_HI_TX_V105]); @@ -2020,8 +2020,8 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, wl->ver_info.fw = le32_to_cpu(prpt->v7.rpt_info.fw_ver); for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) - memcpy(&dm->gnt_set[i], &prpt->v7.gnt_val[i], - sizeof(dm->gnt_set[i])); + memcpy(&dm->gnt_val[i], &prpt->v7.gnt_val[i], + sizeof(dm->gnt_val[i])); bt->bcnt[BTC_BCNT_HIPRI_TX] = le16_to_cpu(prpt->v7.bt_cnt[BTC_BCNT_HI_TX_V105]); @@ -2052,8 +2052,8 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, wl->ver_info.fw = le32_to_cpu(prpt->v8.rpt_info.fw_ver); for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) - memcpy(&dm->gnt_set[i], &prpt->v8.gnt_val[i], - sizeof(dm->gnt_set[i])); + memcpy(&dm->gnt_val[i], &prpt->v8.gnt_val[i], + sizeof(dm->gnt_val[i])); bt->bcnt[BTC_BCNT_HIPRI_TX] = le16_to_cpu(prpt->v8.bt_cnt[BTC_BCNT_HI_TX_V105]); @@ -2137,8 +2137,8 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, sizeof(wl->ver_info.build_date)); for (i = RTW89_PHY_0; i < RTW89_PHY_NUM; i++) - memcpy(&dm->gnt_set[i], &prpt->v11.gnt_val[i][0], - sizeof(dm->gnt_set[i])); + memcpy(&dm->gnt_val[i], &prpt->v11.gnt_val[i][0], + sizeof(dm->gnt_val[i])); for (i = BTC_BT_1ST; i <= BTC_BT_EXT; i++) { if (i == BTC_BT_EXT) { From 00961e2705b70c6af69e2bf83991e2349ff2a569 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 17 Jul 2026 14:57:30 +0800 Subject: [PATCH 0542/1433] wifi: rtw89: coex: Branch out version 105 firmware report map index RTL8852B in firmware 0.29.133.X do not support BT-TX-PWR report, re-index and branch out to version 105. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717065739.64124-5-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 5f2658eb516c..b73040428c58 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -2707,6 +2707,7 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) case 3: case 4: case 5: + case 105: bit_map = BIT(5); break; default: @@ -2723,6 +2724,7 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) case 3: case 4: case 5: + case 105: bit_map = BIT(6); break; default: @@ -2737,6 +2739,7 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) case 3: case 4: case 5: + case 105: bit_map = BIT(7); break; default: @@ -2749,6 +2752,7 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) case 1: case 2: case 3: + case 105: break; case 4: case 5: @@ -2765,6 +2769,7 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) bit_map = BIT(7); break; case 3: + case 105: bit_map = BIT(8); break; case 4: @@ -2778,6 +2783,8 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) case RPT_EN_TEST: if (ver->frptmap == 5) bit_map = BIT(10); + else if (ver->frptmap == 105) + bit_map = BIT(9); else bit_map = BIT(31); break; @@ -2789,6 +2796,7 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) bit_map = GENMASK(2, 0); break; case 3: + case 105: bit_map = GENMASK(2, 0) | BIT(8); break; case 4: @@ -2809,6 +2817,7 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) bit_map = GENMASK(6, 3) | BIT(8); break; case 3: + case 105: bit_map = GENMASK(7, 3); break; case 4: @@ -2833,6 +2842,7 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) break; case 4: case 5: + case 105: bit_map = GENMASK(9, 0); break; default: @@ -2849,6 +2859,7 @@ static u32 rtw89_btc_fw_rpt_ver(struct rtw89_dev *rtwdev, u32 rpt_map) bit_map = GENMASK(6, 2) | BIT(8); break; case 3: + case 105: bit_map = GENMASK(8, 2); break; case 4: From 49989a3fef378fffc60085864b33291c48706b7e Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 17 Jul 2026 14:57:31 +0800 Subject: [PATCH 0543/1433] wifi: rtw89: coex: Refine chip initial related structure Due to the firmware version become more and more, the version divided branch coding method make the related code scattered everywhere, and too much version macro, rearrange the code, assign the value to version format only when H2C commands. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717065739.64124-6-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 121 ++++---------- drivers/net/wireless/realtek/rtw89/core.h | 89 +++++++++-- drivers/net/wireless/realtek/rtw89/fw.c | 101 +++++++++--- drivers/net/wireless/realtek/rtw89/fw.h | 5 + drivers/net/wireless/realtek/rtw89/rtw8851b.c | 120 +++++--------- drivers/net/wireless/realtek/rtw89/rtw8852a.c | 62 +++----- drivers/net/wireless/realtek/rtw89/rtw8852b.c | 62 +++----- .../net/wireless/realtek/rtw89/rtw8852bt.c | 65 ++++---- drivers/net/wireless/realtek/rtw89/rtw8852c.c | 62 +++----- drivers/net/wireless/realtek/rtw89/rtw8922a.c | 53 +++---- drivers/net/wireless/realtek/rtw89/rtw8922d.c | 149 +++++++++--------- 11 files changed, 413 insertions(+), 476 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index b73040428c58..ad52274a3979 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -1142,13 +1142,7 @@ static void _reset_btc_var(struct rtw89_dev *rtwdev, u8 type) if (type & BTC_RESET_MDINFO) { memset(&btc->mdinfo, 0, sizeof(btc->mdinfo)); - - if (ver->fcxinit == 10) - btc->mdinfo.md_v10.ant.isolation = RTW89_BTC_DEFAULT_ANISO; - else if (ver->fcxinit == 7) - btc->mdinfo.md_v7.ant.isolation = RTW89_BTC_DEFAULT_ANISO; - else - btc->mdinfo.md.ant.isolation = RTW89_BTC_DEFAULT_ANISO; + btc->mdinfo.ant.isolation = RTW89_BTC_DEFAULT_ANISO; } } @@ -1169,27 +1163,22 @@ static void _get_reg_status(struct rtw89_dev *rtwdev, u8 type, u8 *val) { struct rtw89_btc *btc = &rtwdev->btc; const struct rtw89_btc_ver *ver = btc->ver; - union rtw89_btc_module_info *md = &btc->mdinfo; + struct rtw89_btc_module *md = &btc->mdinfo; union rtw89_btc_fbtc_mreg_val *pmreg; u32 pre_agc_addr = R_BTC_BB_PRE_AGC_S1; u32 reg_val; - u8 idx, switch_type; - - if (ver->fcxinit == 7) - switch_type = md->md_v7.switch_type; - else - switch_type = md->md.switch_type; + u8 idx; if (btc->btg_pos == RF_PATH_A) pre_agc_addr = R_BTC_BB_PRE_AGC_S0; switch (type) { case BTC_CSTATUS_TXDIV_POS: - if (switch_type == BTC_SWITCH_INTERNAL) + if (md->bt0_sw_type == BTC_SWITCH_INTERNAL) *val = BTC_ANT_DIV_MAIN; break; case BTC_CSTATUS_RXDIV_POS: - if (switch_type == BTC_SWITCH_INTERNAL) + if (md->bt0_sw_type == BTC_SWITCH_INTERNAL) *val = BTC_ANT_DIV_MAIN; break; case BTC_CSTATUS_BB_GNT_MUX: @@ -3098,7 +3087,7 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index) switch (index) { case CXDRVINFO_INIT: - if (ver->fcxinit == 7) + if (ver->fcxinit == 7 || ver->fcxinit == 107) rtw89_fw_h2c_cxdrv_init_v7(rtwdev, index); else if (ver->fcxinit == 10) rtw89_fw_h2c_cxdrv_init_v10(rtwdev, index); @@ -4206,14 +4195,7 @@ static bool _check_freerun(struct rtw89_dev *rtwdev) struct rtw89_btc_wl_role_info *wl_rinfo = &wl->role_info; struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; struct rtw89_btc_bt_hid_desc *hid = &bt_linfo->hid_desc; - union rtw89_btc_module_info *md = &btc->mdinfo; - const struct rtw89_btc_ver *ver = btc->ver; - u8 isolation; - - if (ver->fcxinit == 7) - isolation = md->md_v7.ant.isolation; - else - isolation = md->md.ant.isolation; + struct rtw89_btc_module *md = &btc->mdinfo; if (btc->ant_type == BTC_ANT_SHARED) { btc->dm.trx_para_level = 0; @@ -4237,7 +4219,7 @@ static bool _check_freerun(struct rtw89_dev *rtwdev) } /* TODO get isolation by BT psd */ - if (isolation >= BTC_FREERUN_ANTISO_MIN) { + if (md->ant.isolation >= BTC_FREERUN_ANTISO_MIN) { btc->dm.trx_para_level = 5; return true; } @@ -6177,21 +6159,14 @@ static void _wl_req_mac(struct rtw89_dev *rtwdev, u8 mac) static void _update_zb_coex_tbl(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; u32 zb_tbl0 = 0xda5a5a5a, zb_tbl1 = 0xda5a5a5a; u8 link_mode_chg = btc->cx.wl.link_mode_chg; u8 mode = btc->cx.wl.role_info.link_mode; - u8 wa_type; if (btc->dm.run_reason != BTC_RSN_NTFY_INIT && !link_mode_chg) return; - if (ver->fcxinit == 7) - wa_type = btc->mdinfo.md_v7.wa_type; - else - wa_type = btc->mdinfo.md.wa_type; - - if (!(wa_type & BTC_WA_HFP_ZB)) + if (!(btc->mdinfo.wa_type & BTC_WA_HFP_ZB)) return; if (btc->dm.tdd_bind.rf_band == BIT(RTW89_BAND_5G) || @@ -8106,40 +8081,26 @@ static void _set_init_info(struct rtw89_dev *rtwdev) { const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_cx *cx = &btc->cx; struct rtw89_btc_wl_info *wl = &btc->cx.wl; - if (ver->fcxinit == 10) { - dm->init_info.init_v10.init_mode = wl->coex_mode; - dm->init_info.init_v10.wl_init_ok = wl->status.map.init_ok; - dm->init_info.init_v10.endian_type = BTC_PLATFORM_LITTLE_ENDIAN; + dm->init_info.init_mode = wl->coex_mode; + dm->init_info.wl_init_ok = wl->status.map.init_ok; + dm->init_info.endian_type = BTC_PLATFORM_LITTLE_ENDIAN; + dm->init_info.bt0_function = cx->bt0.func_type; + dm->init_info.bt1_function = cx->bt1.func_type; + dm->init_info.bt2_function = cx->bt_ext.func_type; + dm->init_info.pta_mode = RTW89_MAC_AX_COEX_RTK_MODE; + dm->init_info.pta_direction = RTW89_MAC_AX_COEX_INNER; + dm->init_info.wl_only = dm->wl_only; + dm->init_info.bt_only = dm->bt_only; + dm->init_info.wl_init_ok = wl->status.map.init_ok; + dm->init_info.cx_other = btc->cx.bt_ext.func_type; + dm->init_info.wl_guard_ch = chip->afh_guard_ch; + dm->init_info.dbcc_en = rtwdev->dbcc_en; - dm->init_info.init_v10.module = btc->mdinfo.md_v10; - - dm->init_info.init_v10.bt0_function = cx->bt0.func_type; - dm->init_info.init_v10.bt1_function = cx->bt1.func_type; - dm->init_info.init_v10.bt2_function = cx->bt_ext.func_type; - - dm->init_info.init_v10.pta_mode = RTW89_MAC_AX_COEX_RTK_MODE; - dm->init_info.init_v10.pta_direction = RTW89_MAC_AX_COEX_INNER; - } else if (ver->fcxinit == 7) { - dm->init_info.init_v7.wl_only = (u8)dm->wl_only; - dm->init_info.init_v7.bt_only = (u8)dm->bt_only; - dm->init_info.init_v7.wl_init_ok = (u8)wl->status.map.init_ok; - dm->init_info.init_v7.cx_other = btc->cx.bt_ext.func_type; - dm->init_info.init_v7.wl_guard_ch = chip->afh_guard_ch; - dm->init_info.init_v7.module = btc->mdinfo.md_v7; - } else { - dm->init_info.init.wl_only = (u8)dm->wl_only; - dm->init_info.init.bt_only = (u8)dm->bt_only; - dm->init_info.init.wl_init_ok = (u8)wl->status.map.init_ok; - dm->init_info.init.dbcc_en = rtwdev->dbcc_en; - dm->init_info.init.cx_other = btc->cx.bt_ext.func_type; - dm->init_info.init.wl_guard_ch = chip->afh_guard_ch; - dm->init_info.init.module = btc->mdinfo.md; - } + dm->init_info.module = btc->mdinfo; _fw_set_drv_info(rtwdev, CXDRVINFO_INIT); _fw_set_drv_info(rtwdev, CXDRVINFO_CTRL); @@ -9175,7 +9136,7 @@ void rtw89_btc_c2h_handle(struct rtw89_dev *rtwdev, struct sk_buff *skb, static int _show_cx_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) { - union rtw89_btc_module_info *md = &rtwdev->btc.mdinfo; + struct rtw89_btc_module *md = &rtwdev->btc.mdinfo; const struct rtw89_chip_info *chip = rtwdev->chip; const struct rtw89_btc_ver *ver = rtwdev->btc.ver; struct rtw89_hal *hal = &rtwdev->hal; @@ -9184,7 +9145,6 @@ static int _show_cx_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_wl_info *wl = &btc->cx.wl; u32 ver_main = 0, ver_sub = 0, ver_hotfix = 0, id_branch = 0; - u8 cv, rfe, iso, ant_num, ant_single_pos; char *p = buf, *end = buf + bufsz; if (!(dm->coex_info_map & BTC_COEX_INFO_CX)) @@ -9232,25 +9192,12 @@ static int _show_cx_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) wl->ver_info.build_time, wl->ver_info.build_date, bt->ver_info.fw, bt->run_patch_code ? "patch" : "ROM"); - if (ver->fcxinit == 7) { - cv = md->md_v7.kt_ver; - rfe = md->md_v7.rfe_type; - iso = md->md_v7.ant.isolation; - ant_num = md->md_v7.ant.num; - ant_single_pos = md->md_v7.ant.single_pos; - } else { - cv = md->md.cv; - rfe = md->md.rfe_type; - iso = md->md.ant.isolation; - ant_num = md->md.ant.num; - ant_single_pos = md->md.ant.single_pos; - } - p += scnprintf(p, end - p, " %-15s : cv:%x, rfe_type:0x%x, ant_iso:%d, ant_pg:%d, %s", - "[hw_info]", cv, rfe, iso, ant_num, - ant_num > 1 ? "" : - ant_single_pos ? "1Ant_Pos:S1, " : "1Ant_Pos:S0, "); + "[hw_info]", + md->kt_ver, md->rfe_type, md->ant.isolation, + md->ant.num, md->ant.num > 1 ? "" : + md->ant.single_pos ? "1Ant_Pos:S1, " : "1Ant_Pos:S0, "); p += scnprintf(p, end - p, "3rd_coex:%d, dbcc:%d, tx_num:%d, rx_num:%d\n", @@ -9417,7 +9364,7 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) struct rtw89_btc_bt_info *bt = &cx->bt0; struct rtw89_btc_wl_info *wl = &cx->wl; struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; - union rtw89_btc_module_info *md = &btc->mdinfo; + struct rtw89_btc_module *md = &btc->mdinfo; s8 br_dbm = bt->link_info.bt_txpwr_desc.br_dbm; s8 le_dbm = bt->link_info.bt_txpwr_desc.le_dbm; u8 hw_band = wl->role_info.pta_req_band; @@ -9425,23 +9372,17 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) char *p = buf, *end = buf + bufsz; u8 *afh = bt_linfo->afh_map; u8 *afh_le = bt_linfo->afh_map_le; - u8 bt_pos; if (!(btc->dm.coex_info_map & BTC_COEX_INFO_BT)) return 0; - if (ver->fcxinit == 7) - bt_pos = md->md_v7.bt_pos; - else - bt_pos = md->md.bt_pos; - p += scnprintf(p, end - p, "========== [BT Status] ==========\n"); p += scnprintf(p, end - p, " %-15s : enable:%s, btg:%s%s, connect:%s, ", "[status]", bt->enable.now ? "Y" : "N", bt->btg_type ? "Y" : "N", - (bt->enable.now && (bt->btg_type != bt_pos) ? + (bt->enable.now && (bt->btg_type != md->bt0_pos) ? "(efuse-mismatch!!)" : ""), (bt_linfo->status.map.connect ? "Y" : "N")); diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index fb4c95ed1904..f7b3523c790c 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -1501,7 +1501,7 @@ enum rtw89_btc_bt_profile { BTC_PROFILE_MAX = 4, }; -struct rtw89_btc_ant_info { +struct rtw89_btc_ant_info_v0 { u8 type; /* shared, dedicated */ u8 num; u8 isolation; @@ -1537,6 +1537,21 @@ struct rtw89_btc_ant_info_v10 { u8 ant_xmap[2][4]; } __packed; +struct rtw89_btc_ant_info { + u8 type; /* shared, dedicated(non-shared) */ + u8 num; /* antenna count */ + u8 isolation; /* Ant-Iso between WL/BT */ + u8 single_pos; /* wifi 1ss-1ant at 0:S0 or 1:S1 */ + + u8 stream_cnt; /* spatial_stream count: Tx[7:4], Rx[3:0] */ + u8 btg_pos; /* BT0 btg-circuit at 0:WL-S0/1:WL-S1 */ + u8 btg1_pos; /* BT1 btg-circuit at 0:WL-S0/1:WL-S1 */ + u8 func[5]; /* function at 1~5 Ant refer to enum btc_bt_func_type */ + u8 ant_xmap[2][4]; + + u8 diversity; /* only for wifi use 1-antenna */ +}; + enum rtw89_tfc_dir { RTW89_TFC_UL, RTW89_TFC_DL, @@ -2331,8 +2346,8 @@ struct rtw89_btc_wl_info { u32 wcnt[BTC_WCNT_NUM]; }; -struct rtw89_btc_module { - struct rtw89_btc_ant_info ant; +struct rtw89_btc_module_v0 { + struct rtw89_btc_ant_info_v0 ant; u8 rfe_type; u8 cv; @@ -2373,11 +2388,27 @@ struct rtw89_btc_module_v10 { } __packed; union rtw89_btc_module_info { - struct rtw89_btc_module md; + struct rtw89_btc_module_v0 md_v0; struct rtw89_btc_module_v7 md_v7; struct rtw89_btc_module_v10 md_v10; }; +struct rtw89_btc_module { + u8 rfe_type; + u8 wa_type; /* Refer to enum btc_wa_type */ + u8 kt_ver; + u8 kt_ver_adie; + + u8 bt0_pos; /* wl-end view: get from efuse, must compare bt.btg_type*/ + u8 bt0_sw_type; /* BT Ant-switch: None(non-share), Int(BTG), Ext(SPDT)*/ + u8 bt1_pos; /* BTC_BT_ALONE or BTC_BT_BTG */ + u8 bt1_sw_type; + + u8 bt_solo; + + struct rtw89_btc_ant_info ant; +}; + #define RTW89_BTC_DM_MAXSTEP 30 #define RTW89_BTC_DM_CNT_MAX (RTW89_BTC_DM_MAXSTEP * 8) @@ -2387,8 +2418,8 @@ struct rtw89_btc_dm_step { bool step_ov; }; -struct rtw89_btc_init_info { - struct rtw89_btc_module module; +struct rtw89_btc_init_info_v0 { + struct rtw89_btc_module_v0 module; u8 wl_guard_ch; u8 wl_only: 1; @@ -2398,7 +2429,7 @@ struct rtw89_btc_init_info { u8 bt_only: 1; u16 rsvd; -}; +} __packed; struct rtw89_btc_init_info_v7 { u8 wl_guard_ch; @@ -2414,6 +2445,20 @@ struct rtw89_btc_init_info_v7 { struct rtw89_btc_module_v7 module; } __packed; +struct rtw89_btc_init_info_v107 { + u8 wl_guard_ch; + u8 wl_only; + u8 wl_init_ok; + u8 dbcc_en; + + u8 cx_other; + u8 bt_only; + u8 rsvd; + u8 rsvd1; + + struct rtw89_btc_module_v7 module; +} __packed; + struct rtw89_btc_init_info_v10 { u8 endian_type; /* 0: little-endian, 1:big-endian */ u8 init_mode; /* refer to enum BTC_MODE_xxx */ @@ -2426,12 +2471,34 @@ struct rtw89_btc_init_info_v10 { u8 pta_direction; struct rtw89_btc_module_v10 module; -}; +} __packed; union rtw89_btc_init_info_u { - struct rtw89_btc_init_info init; + struct rtw89_btc_init_info_v0 init_v0; struct rtw89_btc_init_info_v7 init_v7; struct rtw89_btc_init_info_v10 init_v10; + struct rtw89_btc_init_info_v107 init_v107; +}; + +struct rtw89_btc_init_info { + u8 endian_type; /* 0: little-endian, 1:big-endian */ + u8 init_mode; /* refer to enum BTC_MODE_xxx */ + u8 wl_init_ok; + u8 bt0_function; + + u8 bt1_function; + u8 bt2_function; + u8 pta_mode; + u8 pta_direction; + + u8 dbcc_en; + u8 cx_other; + u8 bt_only; + u8 wl_only; + + u8 wl_guard_ch; + + struct rtw89_btc_module module; }; struct rtw89_btc_wl_tx_limit_para { @@ -3659,7 +3726,7 @@ struct rtw89_btc_dm { struct rtw89_btc_gnt_ctrl gnt_set[RTW89_MAC_AX_COEX_GNT_NR]; struct rtw89_btc_gnt_ctrl gnt_val[RTW89_MAC_AX_COEX_GNT_NR]; struct rtw89_mac_ax_wl_act wlact_set[BTC_ALL_BT_EZL]; - union rtw89_btc_init_info_u init_info; /* pass to wl_fw if offload */ + struct rtw89_btc_init_info init_info; /* pass to wl_fw if offload */ struct rtw89_btc_rf_trx_para_v9 rf_trx_para; struct rtw89_btc_wl_tx_limit_para wl_tx_limit; struct rtw89_btc_dm_step dm_step; @@ -4012,7 +4079,7 @@ struct rtw89_btc { struct rtw89_btc_cx cx; struct rtw89_btc_dm dm; struct rtw89_btc_ctrl ctrl; - union rtw89_btc_module_info mdinfo; + struct rtw89_btc_module mdinfo; struct rtw89_btc_btf_fwinfo fwinfo; struct rtw89_btc_dbg dbg; diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 533fb782bda8..520429bddb4f 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -5771,9 +5771,9 @@ int rtw89_fw_h2c_cxdrv_init(struct rtw89_dev *rtwdev, u8 type) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_btc_init_info *init_info = &dm->init_info.init; - struct rtw89_btc_module *module = &init_info->module; - struct rtw89_btc_ant_info *ant = &module->ant; + struct rtw89_btc_init_info *init = &dm->init_info; + struct rtw89_btc_module *md = &init->module; + struct rtw89_btc_ant_info *ant = &md->ant; struct rtw89_h2c_cxinit *h2c; u32 len = sizeof(*h2c); struct sk_buff *skb; @@ -5799,22 +5799,22 @@ int rtw89_fw_h2c_cxdrv_init(struct rtw89_dev *rtwdev, u8 type) u8_encode_bits(ant->btg_pos, RTW89_H2C_CXINIT_ANT_INFO_BTG_POS) | u8_encode_bits(ant->stream_cnt, RTW89_H2C_CXINIT_ANT_INFO_STREAM_CNT); - h2c->mod_rfe = module->rfe_type; - h2c->mod_cv = module->cv; + h2c->mod_rfe = md->rfe_type; + h2c->mod_cv = md->kt_ver; h2c->mod_info = - u8_encode_bits(module->bt_solo, RTW89_H2C_CXINIT_MOD_INFO_BT_SOLO) | - u8_encode_bits(module->bt_pos, RTW89_H2C_CXINIT_MOD_INFO_BT_POS) | - u8_encode_bits(module->switch_type, RTW89_H2C_CXINIT_MOD_INFO_SW_TYPE) | - u8_encode_bits(module->wa_type, RTW89_H2C_CXINIT_MOD_INFO_WA_TYPE); - h2c->mod_adie_kt = module->kt_ver_adie; - h2c->wl_gch = init_info->wl_guard_ch; + u8_encode_bits(md->bt_solo, RTW89_H2C_CXINIT_MOD_INFO_BT_SOLO) | + u8_encode_bits(md->bt0_pos, RTW89_H2C_CXINIT_MOD_INFO_BT_POS) | + u8_encode_bits(md->bt0_sw_type, RTW89_H2C_CXINIT_MOD_INFO_SW_TYPE) | + u8_encode_bits(md->wa_type, RTW89_H2C_CXINIT_MOD_INFO_WA_TYPE); + h2c->mod_adie_kt = md->kt_ver_adie; + h2c->wl_gch = init->wl_guard_ch; h2c->info = - u8_encode_bits(init_info->wl_only, RTW89_H2C_CXINIT_INFO_WL_ONLY) | - u8_encode_bits(init_info->wl_init_ok, RTW89_H2C_CXINIT_INFO_WL_INITOK) | - u8_encode_bits(init_info->dbcc_en, RTW89_H2C_CXINIT_INFO_DBCC_EN) | - u8_encode_bits(init_info->cx_other, RTW89_H2C_CXINIT_INFO_CX_OTHER) | - u8_encode_bits(init_info->bt_only, RTW89_H2C_CXINIT_INFO_BT_ONLY); + u8_encode_bits(init->wl_only, RTW89_H2C_CXINIT_INFO_WL_ONLY) | + u8_encode_bits(init->wl_init_ok, RTW89_H2C_CXINIT_INFO_WL_INITOK) | + u8_encode_bits(init->dbcc_en, RTW89_H2C_CXINIT_INFO_DBCC_EN) | + u8_encode_bits(init->cx_other, RTW89_H2C_CXINIT_INFO_CX_OTHER) | + u8_encode_bits(init->bt_only, RTW89_H2C_CXINIT_INFO_BT_ONLY); rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_OUTSRC, BTFC_SET, @@ -5838,7 +5838,9 @@ int rtw89_fw_h2c_cxdrv_init_v7(struct rtw89_dev *rtwdev, u8 type) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_btc_init_info_v7 *init_info = &dm->init_info.init_v7; + struct rtw89_btc_init_info *init = &dm->init_info; + struct rtw89_btc_module *md = &init->module; + struct rtw89_btc_ant_info *ant = &md->ant; struct rtw89_h2c_cxinit_v7 *h2c; u32 len = sizeof(*h2c); struct sk_buff *skb; @@ -5853,9 +5855,33 @@ int rtw89_fw_h2c_cxdrv_init_v7(struct rtw89_dev *rtwdev, u8 type) h2c = (struct rtw89_h2c_cxinit_v7 *)skb->data; h2c->hdr.type = type; - h2c->hdr.ver = btc->ver->fcxinit; + h2c->hdr.ver = 7; h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; - h2c->init = *init_info; + + h2c->init.wl_guard_ch = init->wl_guard_ch; + h2c->init.wl_only = init->wl_only; + h2c->init.wl_init_ok = init->wl_init_ok; + h2c->init.cx_other = init->cx_other; + h2c->init.bt_only = init->bt_only; + h2c->init.pta_mode = init->pta_mode; + h2c->init.pta_direction = init->pta_direction; + h2c->init.rsvd3 = init->dbcc_en; /* available at v107 */ + + h2c->init.module.rfe_type = md->rfe_type; + h2c->init.module.kt_ver = md->kt_ver; + h2c->init.module.bt_solo = md->bt_solo; + h2c->init.module.bt_pos = md->bt0_pos; + h2c->init.module.switch_type = md->bt0_sw_type; + h2c->init.module.wa_type = md->wa_type; + h2c->init.module.kt_ver_adie = md->kt_ver_adie; + + h2c->init.module.ant.type = ant->type; + h2c->init.module.ant.num = ant->num; + h2c->init.module.ant.isolation = ant->isolation; + h2c->init.module.ant.single_pos = ant->single_pos; + h2c->init.module.ant.diversity = ant->diversity; + h2c->init.module.ant.btg_pos = ant->btg_pos; + h2c->init.module.ant.stream_cnt = ant->stream_cnt; rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_OUTSRC, BTFC_SET, @@ -5879,7 +5905,9 @@ int rtw89_fw_h2c_cxdrv_init_v10(struct rtw89_dev *rtwdev, u8 type) { struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_dm *dm = &btc->dm; - struct rtw89_btc_init_info_v10 *init_info = &dm->init_info.init_v10; + struct rtw89_btc_init_info *init = &dm->init_info; + struct rtw89_btc_module *md = &init->module; + struct rtw89_btc_ant_info *ant = &md->ant; struct rtw89_h2c_cxinit_v10 *h2c; u32 len = sizeof(*h2c); struct sk_buff *skb; @@ -5894,9 +5922,38 @@ int rtw89_fw_h2c_cxdrv_init_v10(struct rtw89_dev *rtwdev, u8 type) h2c = (struct rtw89_h2c_cxinit_v10 *)skb->data; h2c->hdr.type = type; - h2c->hdr.ver = btc->ver->fcxinit; + h2c->hdr.ver = 10; h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; - h2c->init = *init_info; + + h2c->init.endian_type = init->endian_type; + h2c->init.init_mode = init->init_mode; + h2c->init.wl_init_ok = init->wl_init_ok; + h2c->init.bt0_function = init->bt0_function; + h2c->init.bt1_function = init->bt1_function; + h2c->init.bt2_function = init->bt2_function; + h2c->init.pta_mode = init->pta_mode; + h2c->init.pta_direction = init->pta_direction; + + h2c->init.module.rfe_type = md->rfe_type; + h2c->init.module.wa_type = md->wa_type; + h2c->init.module.kt_ver = md->kt_ver; + h2c->init.module.kt_ver_adie = md->kt_ver_adie; + h2c->init.module.bt0_pos = md->bt0_pos; + h2c->init.module.bt0_sw_type = md->bt0_sw_type; + h2c->init.module.bt1_pos = md->bt1_pos; + h2c->init.module.bt1_sw_type = md->bt1_sw_type; + + h2c->init.module.ant.type = ant->type; + h2c->init.module.ant.num = ant->num; + h2c->init.module.ant.isolation = ant->isolation; + h2c->init.module.ant.single_pos = ant->single_pos; + h2c->init.module.ant.stream_cnt = ant->stream_cnt; + h2c->init.module.ant.btg_pos = ant->btg_pos; + h2c->init.module.ant.btg1_pos = ant->btg1_pos; + + memcpy(h2c->init.module.ant.func, ant->func, sizeof(ant->func)); + memcpy(h2c->init.module.ant.ant_xmap, ant->ant_xmap, + sizeof(ant->ant_xmap)); rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_OUTSRC, BTFC_SET, diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index 67e9c8daff5f..38090105b412 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -2593,6 +2593,11 @@ struct rtw89_h2c_cxinit_v7 { struct rtw89_btc_init_info_v7 init; } __packed; +struct rtw89_h2c_cxinit_v107 { + struct rtw89_h2c_cxhdr_v7 hdr; + struct rtw89_btc_init_info_v107 init; +} __packed; + struct rtw89_h2c_cxinit_v10 { struct rtw89_h2c_cxhdr_v7 hdr; struct rtw89_btc_init_info_v10 init; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b.c b/drivers/net/wireless/realtek/rtw89/rtw8851b.c index e3a17f539ad6..db37ad6653bc 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851b.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851b.c @@ -2118,82 +2118,43 @@ static u8 rtw8851b_get_thermal(struct rtw89_dev *rtwdev, enum rtw89_rf_path rf_p static void rtw8851b_btc_set_rfe(struct rtw89_dev *rtwdev) { - const struct rtw89_btc_ver *ver = rtwdev->btc.ver; - union rtw89_btc_module_info *md = &rtwdev->btc.mdinfo; + struct rtw89_btc_module *md = &rtwdev->btc.mdinfo; - if (ver->fcxinit == 7) { - md->md_v7.rfe_type = rtwdev->efuse.rfe_type; - md->md_v7.kt_ver = rtwdev->hal.cv; - md->md_v7.bt_solo = 0; - md->md_v7.switch_type = BTC_SWITCH_INTERNAL; - md->md_v7.ant.isolation = 10; - md->md_v7.kt_ver_adie = rtwdev->hal.acv; + md->rfe_type = rtwdev->efuse.rfe_type; + md->kt_ver = rtwdev->hal.cv; + md->bt_solo = 0; + md->bt0_sw_type = BTC_SWITCH_INTERNAL; + md->ant.isolation = 10; + md->kt_ver_adie = rtwdev->hal.acv; - if (md->md_v7.rfe_type == 0) - return; + if (md->rfe_type == 0) + return; - /* rfe_type 3*n+1: 1-Ant(shared), - * 3*n+2: 2-Ant+Div(non-shared), - * 3*n+3: 2-Ant+no-Div(non-shared) - */ - md->md_v7.ant.num = (md->md_v7.rfe_type % 3 == 1) ? 1 : 2; - /* WL-1ss at S0, btg at s0 (On 1 WL RF) */ - md->md_v7.ant.single_pos = RF_PATH_A; - md->md_v7.ant.btg_pos = RF_PATH_A; - md->md_v7.ant.stream_cnt = 1; + /* rfe_type 3*n+1: 1-Ant(shared), + * 3*n+2: 2-Ant+Div(non-shared), + * 3*n+3: 2-Ant+no-Div(non-shared) + */ + md->ant.num = (md->rfe_type % 3 == 1) ? 1 : 2; + /* WL-1ss at S0, btg at s0 (On 1 WL RF) */ + md->ant.single_pos = RF_PATH_A; + md->ant.btg_pos = RF_PATH_A; + md->ant.stream_cnt = 1; - if (md->md_v7.ant.num == 1) { - md->md_v7.ant.type = BTC_ANT_SHARED; - md->md_v7.bt_pos = BTC_BT_BTG; - md->md_v7.wa_type = 1; - md->md_v7.ant.diversity = 0; - } else { /* ant.num == 2 */ - md->md_v7.ant.type = BTC_ANT_DEDICATED; - md->md_v7.bt_pos = BTC_BT_ALONE; - md->md_v7.switch_type = BTC_SWITCH_EXTERNAL; - md->md_v7.wa_type = 0; - if (md->md_v7.rfe_type % 3 == 2) - md->md_v7.ant.diversity = 1; - } - rtwdev->btc.btg_pos = md->md_v7.ant.btg_pos; - rtwdev->btc.ant_type = md->md_v7.ant.type; - } else { - md->md.rfe_type = rtwdev->efuse.rfe_type; - md->md.cv = rtwdev->hal.cv; - md->md.bt_solo = 0; - md->md.switch_type = BTC_SWITCH_INTERNAL; - md->md.ant.isolation = 10; - md->md.kt_ver_adie = rtwdev->hal.acv; - - if (md->md.rfe_type == 0) - return; - - /* rfe_type 3*n+1: 1-Ant(shared), - * 3*n+2: 2-Ant+Div(non-shared), - * 3*n+3: 2-Ant+no-Div(non-shared) - */ - md->md.ant.num = (md->md.rfe_type % 3 == 1) ? 1 : 2; - /* WL-1ss at S0, btg at s0 (On 1 WL RF) */ - md->md.ant.single_pos = RF_PATH_A; - md->md.ant.btg_pos = RF_PATH_A; - md->md.ant.stream_cnt = 1; - - if (md->md.ant.num == 1) { - md->md.ant.type = BTC_ANT_SHARED; - md->md.bt_pos = BTC_BT_BTG; - md->md.wa_type = 1; - md->md.ant.diversity = 0; - } else { /* ant.num == 2 */ - md->md.ant.type = BTC_ANT_DEDICATED; - md->md.bt_pos = BTC_BT_ALONE; - md->md.switch_type = BTC_SWITCH_EXTERNAL; - md->md.wa_type = 0; - if (md->md.rfe_type % 3 == 2) - md->md.ant.diversity = 1; - } - rtwdev->btc.btg_pos = md->md.ant.btg_pos; - rtwdev->btc.ant_type = md->md.ant.type; + if (md->ant.num == 1) { + md->ant.type = BTC_ANT_SHARED; + md->bt0_pos = BTC_BT_BTG; + md->wa_type = 1; + md->ant.diversity = 0; + } else { /* ant.num == 2 */ + md->ant.type = BTC_ANT_DEDICATED; + md->bt0_pos = BTC_BT_ALONE; + md->bt0_sw_type = BTC_SWITCH_EXTERNAL; + md->wa_type = 0; + if (md->rfe_type % 3 == 2) + md->ant.diversity = 1; } + rtwdev->btc.btg_pos = md->ant.btg_pos; + rtwdev->btc.ant_type = md->ant.type; } static @@ -2217,9 +2178,8 @@ static void rtw8851b_btc_init_cfg(struct rtw89_dev *rtwdev) }; const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; - union rtw89_btc_module_info *md = &btc->mdinfo; - const struct rtw89_btc_ver *ver = btc->ver; - u8 path, path_min, path_max, str_cnt, ant_sing_pos; + struct rtw89_btc_module *md = &btc->mdinfo; + u8 path, path_min, path_max; /* PTA init */ rtw89_mac_coex_init(rtwdev, &coex_params); @@ -2228,17 +2188,9 @@ static void rtw8851b_btc_init_cfg(struct rtw89_dev *rtwdev) chip->ops->btc_set_wl_pri(rtwdev, BTC_PRI_MASK_TX_RESP, true); chip->ops->btc_set_wl_pri(rtwdev, BTC_PRI_MASK_BEACON, true); - if (ver->fcxinit == 7) { - str_cnt = md->md_v7.ant.stream_cnt; - ant_sing_pos = md->md_v7.ant.single_pos; - } else { - str_cnt = md->md.ant.stream_cnt; - ant_sing_pos = md->md.ant.single_pos; - } - /* for 1-Ant && 1-ss case: only 1-path */ - if (str_cnt == 1) { - path_min = ant_sing_pos; + if (md->ant.stream_cnt == 1) { + path_min = md->ant.single_pos; path_max = path_min; } else { path_min = RF_PATH_A; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852a.c b/drivers/net/wireless/realtek/rtw89/rtw8852a.c index f32c7c6a4075..5d7b5d1a24b1 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852a.c @@ -1827,56 +1827,30 @@ static u8 rtw8852a_get_thermal(struct rtw89_dev *rtwdev, enum rtw89_rf_path rf_p static void rtw8852a_btc_set_rfe(struct rtw89_dev *rtwdev) { - const struct rtw89_btc_ver *ver = rtwdev->btc.ver; - union rtw89_btc_module_info *md = &rtwdev->btc.mdinfo; + struct rtw89_btc_module *md = &rtwdev->btc.mdinfo; - if (ver->fcxinit == 7) { - md->md_v7.rfe_type = rtwdev->efuse.rfe_type; - md->md_v7.kt_ver = rtwdev->hal.cv; - md->md_v7.bt_solo = 0; - md->md_v7.switch_type = BTC_SWITCH_INTERNAL; + md->rfe_type = rtwdev->efuse.rfe_type; + md->kt_ver = rtwdev->hal.cv; + md->bt_solo = 0; + md->bt0_sw_type = BTC_SWITCH_INTERNAL; - if (md->md_v7.rfe_type > 0) - md->md_v7.ant.num = (md->md_v7.rfe_type % 2 ? 2 : 3); - else - md->md_v7.ant.num = 2; + if (md->rfe_type > 0) + md->ant.num = (md->rfe_type % 2 ? 2 : 3); + else + md->ant.num = 2; - md->md_v7.ant.diversity = 0; - md->md_v7.ant.isolation = 10; + md->ant.diversity = 0; + md->ant.isolation = 10; - if (md->md_v7.ant.num == 3) { - md->md_v7.ant.type = BTC_ANT_DEDICATED; - md->md_v7.bt_pos = BTC_BT_ALONE; - } else { - md->md_v7.ant.type = BTC_ANT_SHARED; - md->md_v7.bt_pos = BTC_BT_BTG; - } - rtwdev->btc.btg_pos = md->md_v7.ant.btg_pos; - rtwdev->btc.ant_type = md->md_v7.ant.type; + if (md->ant.num == 3) { + md->ant.type = BTC_ANT_DEDICATED; + md->bt0_pos = BTC_BT_ALONE; } else { - md->md.rfe_type = rtwdev->efuse.rfe_type; - md->md.cv = rtwdev->hal.cv; - md->md.bt_solo = 0; - md->md.switch_type = BTC_SWITCH_INTERNAL; - - if (md->md.rfe_type > 0) - md->md.ant.num = (md->md.rfe_type % 2 ? 2 : 3); - else - md->md.ant.num = 2; - - md->md.ant.diversity = 0; - md->md.ant.isolation = 10; - - if (md->md.ant.num == 3) { - md->md.ant.type = BTC_ANT_DEDICATED; - md->md.bt_pos = BTC_BT_ALONE; - } else { - md->md.ant.type = BTC_ANT_SHARED; - md->md.bt_pos = BTC_BT_BTG; - } - rtwdev->btc.btg_pos = md->md.ant.btg_pos; - rtwdev->btc.ant_type = md->md.ant.type; + md->ant.type = BTC_ANT_SHARED; + md->bt0_pos = BTC_BT_BTG; } + rtwdev->btc.btg_pos = md->ant.btg_pos; + rtwdev->btc.ant_type = md->ant.type; } static diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852b.c b/drivers/net/wireless/realtek/rtw89/rtw8852b.c index a45f05309af8..356623341f65 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852b.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852b.c @@ -750,56 +750,30 @@ static void rtw8852b_rfk_track(struct rtw89_dev *rtwdev) static void rtw8852b_btc_set_rfe(struct rtw89_dev *rtwdev) { - const struct rtw89_btc_ver *ver = rtwdev->btc.ver; - union rtw89_btc_module_info *md = &rtwdev->btc.mdinfo; + struct rtw89_btc_module *md = &rtwdev->btc.mdinfo; - if (ver->fcxinit == 7) { - md->md_v7.rfe_type = rtwdev->efuse.rfe_type; - md->md_v7.kt_ver = rtwdev->hal.cv; - md->md_v7.bt_solo = 0; - md->md_v7.switch_type = BTC_SWITCH_INTERNAL; + md->rfe_type = rtwdev->efuse.rfe_type; + md->kt_ver = rtwdev->hal.cv; + md->bt_solo = 0; + md->bt0_sw_type = BTC_SWITCH_INTERNAL; - if (md->md_v7.rfe_type > 0) - md->md_v7.ant.num = (md->md_v7.rfe_type % 2 ? 2 : 3); - else - md->md_v7.ant.num = 2; + if (md->rfe_type > 0) + md->ant.num = (md->rfe_type % 2 ? 2 : 3); + else + md->ant.num = 2; - md->md_v7.ant.diversity = 0; - md->md_v7.ant.isolation = 10; + md->ant.diversity = 0; + md->ant.isolation = 10; - if (md->md_v7.ant.num == 3) { - md->md_v7.ant.type = BTC_ANT_DEDICATED; - md->md_v7.bt_pos = BTC_BT_ALONE; - } else { - md->md_v7.ant.type = BTC_ANT_SHARED; - md->md_v7.bt_pos = BTC_BT_BTG; - } - rtwdev->btc.btg_pos = md->md_v7.ant.btg_pos; - rtwdev->btc.ant_type = md->md_v7.ant.type; + if (md->ant.num == 3) { + md->ant.type = BTC_ANT_DEDICATED; + md->bt0_pos = BTC_BT_ALONE; } else { - md->md.rfe_type = rtwdev->efuse.rfe_type; - md->md.cv = rtwdev->hal.cv; - md->md.bt_solo = 0; - md->md.switch_type = BTC_SWITCH_INTERNAL; - - if (md->md.rfe_type > 0) - md->md.ant.num = (md->md.rfe_type % 2 ? 2 : 3); - else - md->md.ant.num = 2; - - md->md.ant.diversity = 0; - md->md.ant.isolation = 10; - - if (md->md.ant.num == 3) { - md->md.ant.type = BTC_ANT_DEDICATED; - md->md.bt_pos = BTC_BT_ALONE; - } else { - md->md.ant.type = BTC_ANT_SHARED; - md->md.bt_pos = BTC_BT_BTG; - } - rtwdev->btc.btg_pos = md->md.ant.btg_pos; - rtwdev->btc.ant_type = md->md.ant.type; + md->ant.type = BTC_ANT_SHARED; + md->bt0_pos = BTC_BT_BTG; } + rtwdev->btc.btg_pos = md->ant.btg_pos; + rtwdev->btc.ant_type = md->ant.type; } union rtw8852b_btc_wl_txpwr_ctrl { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bt.c b/drivers/net/wireless/realtek/rtw89/rtw8852bt.c index 8d5dd93626b3..3d93cee562c2 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852bt.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852bt.c @@ -635,46 +635,41 @@ static void rtw8852bt_rfk_track(struct rtw89_dev *rtwdev) static void rtw8852bt_btc_set_rfe(struct rtw89_dev *rtwdev) { - const struct rtw89_btc_ver *ver = rtwdev->btc.ver; - union rtw89_btc_module_info *md = &rtwdev->btc.mdinfo; + struct rtw89_btc_module *md = &rtwdev->btc.mdinfo; - if (ver->fcxinit == 7) { - md->md_v7.rfe_type = rtwdev->efuse.rfe_type; - md->md_v7.kt_ver = rtwdev->hal.cv; - md->md_v7.kt_ver_adie = rtwdev->hal.acv; - md->md_v7.bt_solo = 0; - md->md_v7.bt_pos = BTC_BT_BTG; - md->md_v7.switch_type = BTC_SWITCH_INTERNAL; - md->md_v7.wa_type = 0; + md->rfe_type = rtwdev->efuse.rfe_type; + md->kt_ver = rtwdev->hal.cv; + md->kt_ver_adie = rtwdev->hal.acv; + md->bt_solo = 0; + md->bt0_pos = BTC_BT_BTG; + md->bt0_sw_type = BTC_SWITCH_INTERNAL; + md->wa_type = 0; - md->md_v7.ant.type = BTC_ANT_SHARED; - md->md_v7.ant.num = 2; - md->md_v7.ant.isolation = 10; - md->md_v7.ant.diversity = 0; - /* WL 1-stream+1-Ant is located at 0:s0(path-A) or 1:s1(path-B) */ - md->md_v7.ant.single_pos = RF_PATH_A; - md->md_v7.ant.btg_pos = RF_PATH_B; + md->ant.type = BTC_ANT_SHARED; + md->ant.num = 2; + md->ant.isolation = 10; + md->ant.diversity = 0; + /* WL 1-stream+1-Ant is located at 0:s0(path-A) or 1:s1(path-B) */ + md->ant.single_pos = RF_PATH_A; + md->ant.btg_pos = RF_PATH_B; - if (md->md_v7.rfe_type == 0) { - rtwdev->btc.dm.error.map.rfe_type0 = true; - return; - } - - md->md_v7.ant.num = (md->md_v7.rfe_type % 2) ? 2 : 3; - md->md_v7.ant.stream_cnt = 2; - md->md_v7.wa_type |= BTC_WA_INIT_SCAN; - - if (md->md_v7.ant.num == 2) { - md->md_v7.ant.type = BTC_ANT_SHARED; - md->md_v7.bt_pos = BTC_BT_BTG; - md->md_v7.wa_type |= BTC_WA_HFP_LAG; - } else { - md->md_v7.ant.type = BTC_ANT_DEDICATED; - md->md_v7.bt_pos = BTC_BT_ALONE; - } - } else { + if (md->rfe_type == 0) { + rtwdev->btc.dm.error.map.rfe_type0 = true; return; } + + md->ant.num = (md->rfe_type % 2) ? 2 : 3; + md->ant.stream_cnt = 2; + md->wa_type |= BTC_WA_INIT_SCAN; + + if (md->ant.num == 2) { + md->ant.type = BTC_ANT_SHARED; + md->bt0_pos = BTC_BT_BTG; + md->wa_type |= BTC_WA_HFP_LAG; + } else { + md->ant.type = BTC_ANT_DEDICATED; + md->bt0_pos = BTC_BT_ALONE; + } } static void diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852c.c b/drivers/net/wireless/realtek/rtw89/rtw8852c.c index 3dc6dfce082a..c68959b63a01 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852c.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852c.c @@ -2608,56 +2608,30 @@ static u8 rtw8852c_get_thermal(struct rtw89_dev *rtwdev, enum rtw89_rf_path rf_p static void rtw8852c_btc_set_rfe(struct rtw89_dev *rtwdev) { - const struct rtw89_btc_ver *ver = rtwdev->btc.ver; - union rtw89_btc_module_info *md = &rtwdev->btc.mdinfo; + struct rtw89_btc_module *md = &rtwdev->btc.mdinfo; - if (ver->fcxinit == 7) { - md->md_v7.rfe_type = rtwdev->efuse.rfe_type; - md->md_v7.kt_ver = rtwdev->hal.cv; - md->md_v7.bt_solo = 0; - md->md_v7.switch_type = BTC_SWITCH_INTERNAL; + md->rfe_type = rtwdev->efuse.rfe_type; + md->kt_ver = rtwdev->hal.cv; + md->bt_solo = 0; + md->bt0_sw_type = BTC_SWITCH_INTERNAL; - if (md->md_v7.rfe_type > 0) - md->md_v7.ant.num = (md->md_v7.rfe_type % 2 ? 2 : 3); - else - md->md_v7.ant.num = 2; + if (md->rfe_type > 0) + md->ant.num = (md->rfe_type % 2 ? 2 : 3); + else + md->ant.num = 2; - md->md_v7.ant.diversity = 0; - md->md_v7.ant.isolation = 10; + md->ant.diversity = 0; + md->ant.isolation = 10; - if (md->md_v7.ant.num == 3) { - md->md_v7.ant.type = BTC_ANT_DEDICATED; - md->md_v7.bt_pos = BTC_BT_ALONE; - } else { - md->md_v7.ant.type = BTC_ANT_SHARED; - md->md_v7.bt_pos = BTC_BT_BTG; - } - rtwdev->btc.btg_pos = md->md_v7.ant.btg_pos; - rtwdev->btc.ant_type = md->md_v7.ant.type; + if (md->ant.num == 3) { + md->ant.type = BTC_ANT_DEDICATED; + md->bt0_pos = BTC_BT_ALONE; } else { - md->md.rfe_type = rtwdev->efuse.rfe_type; - md->md.cv = rtwdev->hal.cv; - md->md.bt_solo = 0; - md->md.switch_type = BTC_SWITCH_INTERNAL; - - if (md->md.rfe_type > 0) - md->md.ant.num = (md->md.rfe_type % 2 ? 2 : 3); - else - md->md.ant.num = 2; - - md->md.ant.diversity = 0; - md->md.ant.isolation = 10; - - if (md->md.ant.num == 3) { - md->md.ant.type = BTC_ANT_DEDICATED; - md->md.bt_pos = BTC_BT_ALONE; - } else { - md->md.ant.type = BTC_ANT_SHARED; - md->md.bt_pos = BTC_BT_BTG; - } - rtwdev->btc.btg_pos = md->md.ant.btg_pos; - rtwdev->btc.ant_type = md->md.ant.type; + md->ant.type = BTC_ANT_SHARED; + md->bt0_pos = BTC_BT_BTG; } + rtwdev->btc.btg_pos = md->ant.btg_pos; + rtwdev->btc.ant_type = md->ant.type; } static void rtw8852c_ctrl_btg_bt_rx(struct rtw89_dev *rtwdev, bool en, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922a.c b/drivers/net/wireless/realtek/rtw89/rtw8922a.c index 854ef9a980bd..2586fd2df8ab 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922a.c @@ -2721,46 +2721,45 @@ static u32 rtw8922a_chan_to_rf18_val(struct rtw89_dev *rtwdev, static void rtw8922a_btc_set_rfe(struct rtw89_dev *rtwdev) { - union rtw89_btc_module_info *md = &rtwdev->btc.mdinfo; - struct rtw89_btc_module_v7 *module = &md->md_v7; + struct rtw89_btc_module *md = &rtwdev->btc.mdinfo; - module->rfe_type = rtwdev->efuse.rfe_type; - module->kt_ver = rtwdev->hal.cv; - module->bt_solo = 0; - module->switch_type = BTC_SWITCH_INTERNAL; - module->wa_type = 0; + md->rfe_type = rtwdev->efuse.rfe_type; + md->kt_ver = rtwdev->hal.cv; + md->bt_solo = 0; + md->bt0_sw_type = BTC_SWITCH_INTERNAL; + md->wa_type = 0; - module->ant.type = BTC_ANT_SHARED; - module->ant.num = 2; - module->ant.isolation = 10; - module->ant.diversity = 0; - module->ant.single_pos = RF_PATH_A; - module->ant.btg_pos = RF_PATH_B; + md->ant.type = BTC_ANT_SHARED; + md->ant.num = 2; + md->ant.isolation = 10; + md->ant.diversity = 0; + md->ant.single_pos = RF_PATH_A; + md->ant.btg_pos = RF_PATH_B; - if (module->kt_ver <= 1) - module->wa_type |= BTC_WA_HFP_ZB; + if (md->kt_ver <= 1) + md->wa_type |= BTC_WA_HFP_ZB; rtwdev->btc.cx.bt_ext.func_type = BTC_3CX_NONE; - if (module->rfe_type == 0) { + if (md->rfe_type == 0) { rtwdev->btc.dm.error.map.rfe_type0 = true; return; } - module->ant.num = (module->rfe_type % 2) ? 2 : 3; + md->ant.num = (md->rfe_type % 2) ? 2 : 3; - if (module->kt_ver == 0) - module->ant.num = 2; + if (md->kt_ver == 0) + md->ant.num = 2; - if (module->ant.num == 3) { - module->ant.type = BTC_ANT_DEDICATED; - module->bt_pos = BTC_BT_ALONE; + if (md->ant.num == 3) { + md->ant.type = BTC_ANT_DEDICATED; + md->bt0_pos = BTC_BT_ALONE; } else { - module->ant.type = BTC_ANT_SHARED; - module->bt_pos = BTC_BT_BTG; + md->ant.type = BTC_ANT_SHARED; + md->bt0_pos = BTC_BT_BTG; } - rtwdev->btc.btg_pos = module->ant.btg_pos; - rtwdev->btc.ant_type = module->ant.type; + rtwdev->btc.btg_pos = md->ant.btg_pos; + rtwdev->btc.ant_type = md->ant.type; } static @@ -2773,7 +2772,7 @@ void rtw8922a_set_trx_mask(struct rtw89_dev *rtwdev, u8 path, u8 group, u32 val) static void rtw8922a_btc_init_cfg(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_ant_info_v7 *ant = &btc->mdinfo.md_v7.ant; + struct rtw89_btc_ant_info *ant = &btc->mdinfo.ant; u32 wl_pri, path_min, path_max; u8 path; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c index fd4c92ba3f7b..12c96425a6ff 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c @@ -3170,32 +3170,31 @@ static u32 rtw8922d_chan_to_rf18_val(struct rtw89_dev *rtwdev, static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_module *md = &btc->mdinfo; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_cx *cx = &btc->cx; - union rtw89_btc_module_info *md = &rtwdev->btc.mdinfo; - struct rtw89_btc_module_v10 *module = &md->md_v10; u8 efuse_bt_func, efuse_ant_info, bt_sw_gpio_pos; u8 is_combo, is_bt_share; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s !!\n", __func__); /* get from final capability of device */ - module->rfe_type = rtwdev->efuse.rfe_type; - module->kt_ver = rtwdev->hal.cv; - module->kt_ver_adie = rtwdev->hal.acv; - module->wa_type = 0; + md->rfe_type = rtwdev->efuse.rfe_type; + md->kt_ver = rtwdev->hal.cv; + md->kt_ver_adie = rtwdev->hal.acv; + md->wa_type = 0; dm->wl_trx_nss_en = 0; - module->ant.num = 2; - module->ant.single_pos = BTC_RF_S0; /* WL 1ss+1Ant 0:s0(A)/ 1:s1(B) */ + md->ant.num = 2; + md->ant.single_pos = BTC_RF_S0; /* WL 1ss+1Ant 0:s0(A)/ 1:s1(B) */ /* set default antenna isolation */ - cx->bt0.ant_iso_to_wl = module->ant.isolation; - cx->bt1.ant_iso_to_wl = module->ant.isolation; + cx->bt0.ant_iso_to_wl = md->ant.isolation; + cx->bt1.ant_iso_to_wl = md->ant.isolation; - module->ant.stream_cnt = 2; - module->ant.btg_pos = BTC_RF_S1; /* BTG0 at WL-S1 */ - module->ant.btg1_pos = BTC_RF_S0; /* BTG1 at WL-S0 if Dual-BTGA */ + md->ant.stream_cnt = 2; + md->ant.btg_pos = BTC_RF_S1; /* BTG0 at WL-S1 */ + md->ant.btg1_pos = BTC_RF_S0; /* BTG1 at WL-S0 if Dual-BTGA */ cx->bt0.band_56G_support = 1; cx->bt1.band_56G_support = 1; @@ -3223,21 +3222,21 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev) efuse_bt_func &= 0x1f; /* 0xcd[4:0] */ efuse_ant_info = rtwdev->efuse.bt_setting_3; - module->ant.num = (efuse_ant_info & 0xe0) >> 5; /* 0xCE[7:5] */ + md->ant.num = (efuse_ant_info & 0xe0) >> 5; /* 0xCE[7:5] */ is_combo = (efuse_ant_info & 0xe) >> 1; /* 0xCE[3:1] */ is_bt_share = efuse_ant_info & BIT(0); /* 0xCE[0] */ memset(dm->ant_xmap, 0, sizeof(dm->ant_xmap)); /* To-Do: "RFE_TYpe" to "ant.num" translation */ - switch (module->ant.num) { + switch (md->ant.num) { case 1: /* 1-Ant WL-S0 only & BT0 only */ - module->ant.type = BTC_ANT_SHARED; - module->bt0_pos = BTC_BT_BTG; - module->bt0_sw_type = BTC_SWITCH_INTERNAL; - module->ant.btg_pos = BTC_RF_S0; /* BTG0 at WL-S0 */ - module->ant.stream_cnt = 1; - module->ant.func[0] = BTC_EFMAP_BT0; + md->ant.type = BTC_ANT_SHARED; + md->bt0_pos = BTC_BT_BTG; + md->bt0_sw_type = BTC_SWITCH_INTERNAL; + md->ant.btg_pos = BTC_RF_S0; /* BTG0 at WL-S0 */ + md->ant.stream_cnt = 1; + md->ant.func[0] = BTC_EFMAP_BT0; dm->ant_xmap[BTC_RF_S0][BTC_BT_1ST] = 1; /* BT0 shared with S0*/ dm->ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 0; /* WL 1T1R no RF-S1 */ break; @@ -3245,34 +3244,34 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev) default: if (is_combo) { if (efuse_bt_func == (BTC_EFMAP_BT0 | BTC_EFMAP_BT1)) - module->ant.func[0] = BTC_EFMAP_BT1; + md->ant.func[0] = BTC_EFMAP_BT1; - module->ant.func[1] = BTC_EFMAP_BT0; + md->ant.func[1] = BTC_EFMAP_BT0; } else { - module->ant.func[0] = BTC_EFMAP_NONE; - module->ant.func[1] = BTC_EFMAP_NONE; + md->ant.func[0] = BTC_EFMAP_NONE; + md->ant.func[1] = BTC_EFMAP_NONE; } if (is_bt_share) { /* WL-S0 + (WL-S1 & BT0-S1) */ - module->ant.type = BTC_ANT_SHARED; - module->bt0_pos = BTC_BT_BTG; - module->bt0_sw_type = BTC_SWITCH_INTERNAL; + md->ant.type = BTC_ANT_SHARED; + md->bt0_pos = BTC_BT_BTG; + md->bt0_sw_type = BTC_SWITCH_INTERNAL; dm->ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 1; } else { /* WL-S0 + BT0-S1 */ - module->ant.type = BTC_ANT_DEDICATED; - module->bt0_pos = BTC_BT_ALONE; - module->bt0_sw_type = BTC_SWITCH_V1_NONE; + md->ant.type = BTC_ANT_DEDICATED; + md->bt0_pos = BTC_BT_ALONE; + md->bt0_sw_type = BTC_SWITCH_V1_NONE; } - if (module->ant.func[0] == BTC_EFMAP_BT1) { /* if 2nd BT exist */ + if (md->ant.func[0] == BTC_EFMAP_BT1) { /* if 2nd BT exist */ dm->ant_xmap[BTC_RF_S0][BTC_BT_2ND] = 1; - if (module->rfe_type == 12) { /* WL-S0 & BT1 by SPDT */ - module->bt1_pos = BTC_BT_ALONE; + if (md->rfe_type == 12) { /* WL-S0 & BT1 by SPDT */ + md->bt1_pos = BTC_BT_ALONE; /* Todo: set SPDT GPIO-ctrl */ - module->bt1_sw_type = bt_sw_gpio_pos; + md->bt1_sw_type = bt_sw_gpio_pos; } else { /* WL-S0 & BT1-S1 by BTGA */ - module->bt1_pos = BTC_BT_BTG; - module->bt1_sw_type = BTC_SWITCH_INTERNAL; + md->bt1_pos = BTC_BT_BTG; + md->bt1_sw_type = BTC_SWITCH_INTERNAL; } } @@ -3280,48 +3279,48 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev) break; case 3: /* 3-Ant, 3 different BT-configuration */ if (is_bt_share) { - module->ant.func[0] = BTC_EFMAP_NONE; - module->ant.func[1] = BTC_EFMAP_BT0; - module->ant.func[2] = efuse_bt_func & (~BTC_EFMAP_BT0); - module->ant.type = BTC_ANT_SHARED; - module->bt0_pos = BTC_BT_BTG; - module->bt0_sw_type = BTC_SWITCH_INTERNAL; + md->ant.func[0] = BTC_EFMAP_NONE; + md->ant.func[1] = BTC_EFMAP_BT0; + md->ant.func[2] = efuse_bt_func & (~BTC_EFMAP_BT0); + md->ant.type = BTC_ANT_SHARED; + md->bt0_pos = BTC_BT_BTG; + md->bt0_sw_type = BTC_SWITCH_INTERNAL; dm->ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 1; dm->wl_trx_nss_en = 1; /* 1ss MIMO-PS capability */ } else { - module->ant.func[0] = BTC_EFMAP_NONE; - module->ant.func[1] = BTC_EFMAP_NONE; - module->ant.func[2] = efuse_bt_func; - module->ant.type = BTC_ANT_DEDICATED; - module->bt0_pos = BTC_BT_ALONE; - module->bt0_sw_type = BTC_SWITCH_V1_NONE; + md->ant.func[0] = BTC_EFMAP_NONE; + md->ant.func[1] = BTC_EFMAP_NONE; + md->ant.func[2] = efuse_bt_func; + md->ant.type = BTC_ANT_DEDICATED; + md->bt0_pos = BTC_BT_ALONE; + md->bt0_sw_type = BTC_SWITCH_V1_NONE; } - module->bt1_pos = BTC_BT_ALONE; /* BT1 may exist or not */ - module->bt1_sw_type = BTC_SWITCH_V1_NONE; + md->bt1_pos = BTC_BT_ALONE; /* BT1 may exist or not */ + md->bt1_sw_type = BTC_SWITCH_V1_NONE; break; case 4: /* 4-Ant, WL-S0 + WL-S1 + BT0 + BT1 */ - module->ant.func[0] = BTC_EFMAP_NONE; - module->ant.func[1] = BTC_EFMAP_NONE; - module->ant.func[2] = BTC_EFMAP_BT0; - module->ant.func[3] = efuse_bt_func & (~BTC_EFMAP_BT0); - module->ant.type = BTC_ANT_DEDICATED; - module->bt0_pos = BTC_BT_ALONE; - module->bt0_sw_type = BTC_SWITCH_V1_NONE; - module->bt1_pos = BTC_BT_ALONE; - module->bt1_sw_type = BTC_SWITCH_V1_NONE; + md->ant.func[0] = BTC_EFMAP_NONE; + md->ant.func[1] = BTC_EFMAP_NONE; + md->ant.func[2] = BTC_EFMAP_BT0; + md->ant.func[3] = efuse_bt_func & (~BTC_EFMAP_BT0); + md->ant.type = BTC_ANT_DEDICATED; + md->bt0_pos = BTC_BT_ALONE; + md->bt0_sw_type = BTC_SWITCH_V1_NONE; + md->bt1_pos = BTC_BT_ALONE; + md->bt1_sw_type = BTC_SWITCH_V1_NONE; break; case 5: - module->ant.func[0] = BTC_EFMAP_NONE; - module->ant.func[1] = BTC_EFMAP_NONE; - module->ant.func[2] = BTC_EFMAP_BT0; - module->ant.func[3] = BTC_EFMAP_BT1; - module->ant.func[4] = BTC_EFMAP_ZB; - module->ant.type = BTC_ANT_DEDICATED; - module->bt0_pos = BTC_BT_ALONE; - module->bt0_sw_type = BTC_SWITCH_V1_NONE; - module->bt1_pos = BTC_BT_ALONE; - module->bt1_sw_type = BTC_SWITCH_V1_NONE; + md->ant.func[0] = BTC_EFMAP_NONE; + md->ant.func[1] = BTC_EFMAP_NONE; + md->ant.func[2] = BTC_EFMAP_BT0; + md->ant.func[3] = BTC_EFMAP_BT1; + md->ant.func[4] = BTC_EFMAP_ZB; + md->ant.type = BTC_ANT_DEDICATED; + md->bt0_pos = BTC_BT_ALONE; + md->bt0_sw_type = BTC_SWITCH_V1_NONE; + md->bt1_pos = BTC_BT_ALONE; + md->bt1_sw_type = BTC_SWITCH_V1_NONE; break; } @@ -3333,8 +3332,8 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev) */ if (dm->wl_trx_nss_en && (dm->wl_trx_nss.tx_limit && dm->wl_trx_nss.rx_limit)) { - module->ant.type = BTC_ANT_DEDICATED; - module->ant.stream_cnt = 1; + md->ant.type = BTC_ANT_DEDICATED; + md->ant.stream_cnt = 1; dm->ant_xmap[BTC_RF_S0][BTC_BT_1ST] = 0; /* wl 1ss-> RF-S0 */ dm->ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 0; /* BT0-> RF-S1 */ dm->ant_xmap[BTC_RF_S0][BTC_BT_2ND] = 0; @@ -3365,10 +3364,10 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev) /* use GPIO 12~15 for Ext-4-wire-PTA */ cx->bt_ext.hpta_cfg = BIT(12) | BIT(13) | BIT(14) | BIT(15); /* for Ext-SOC locate at Ant-2 */ - if (module->ant.num >= 5) - module->ant.func[4] = BTC_EFMAP_ZB; + if (md->ant.num >= 5) + md->ant.func[4] = BTC_EFMAP_ZB; else - module->ant.func[module->ant.num - 1] = BTC_EFMAP_ZB; + md->ant.func[md->ant.num - 1] = BTC_EFMAP_ZB; break; } From 81d30a7be825f0a27cb64a5385d57aa4aab70ca3 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 17 Jul 2026 14:57:32 +0800 Subject: [PATCH 0544/1433] wifi: rtw89: coex: Add driver info H2C command index version 103 The 0.29.133.X firmware driver info H2C index maximum is 5, to prevent switch case fall through and send unexpected H2C commands, add version code 103 as judgment. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717065739.64124-7-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index ad52274a3979..10699c92273a 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -3138,7 +3138,7 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index) rtw89_fw_h2c_cxdrv_rfk(rtwdev, index); break; case CXDRVINFO_TXPWR: - if (ver->drvinfo_ver == 3) + if (ver->drvinfo_ver == 3 || ver->drvinfo_ver == 103) index = 4; if (ver->fcxtrx == 7 || ver->fcxtrx == 107) @@ -3147,26 +3147,27 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index) rtw89_fw_h2c_cxtxpwr_v9(rtwdev, index); break; case CXDRVINFO_FDDT: - if (ver->drvinfo_ver == 3) + if (ver->drvinfo_ver == 3 || ver->drvinfo_ver == 103) index = 5; else return; - rtw89_debug(rtwdev, RTW89_DBG_BTC, "drv_info FDDT index=%d\n", index); break; case CXDRVINFO_MLO: + if (!ver->fcxmlo || ver->drvinfo_ver == 103) + return; + if (ver->drvinfo_ver == 3) index = 6; else return; - rtw89_debug(rtwdev, RTW89_DBG_BTC, "drv_info MLO index=%d\n", index); break; case CXDRVINFO_OSI: - if (!ver->fcxosi) + if (!ver->fcxosi || ver->drvinfo_ver == 103) return; - if (ver->drvinfo_ver > 1) + if (ver->drvinfo_ver == 3) index = 7; else return; From 35b569b60aa0d339dd14473f19c215c67911ffce Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 17 Jul 2026 14:57:33 +0800 Subject: [PATCH 0545/1433] wifi: rtw89: coex: Fix Wi-Fi role info H2C command header issue The function to filled up H2C command data is the last step in the driver, the next step is going to firmware. So the structure version number should not included driver local branch number (like firmware is v5, but driver branch to v105), it should be assigned as a explicit version number which paired with firmware. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717065739.64124-8-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/fw.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 520429bddb4f..196de240fad1 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -6256,6 +6256,10 @@ int rtw89_fw_h2c_cxdrv_role_v7(struct rtw89_dev *rtwdev, u8 type) skb_put(skb, len); h2c = (struct rtw89_h2c_cxrole_v7 *)skb->data; + h2c->hdr.type = type; + h2c->hdr.ver = 7; + h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; + h2c->r.connect_cnt = r->connect_cnt; h2c->r.link_mode = r->link_mode; h2c->r.link_mode_chg = r->link_mode_chg; @@ -6324,7 +6328,7 @@ int rtw89_fw_h2c_cxdrv_role_v8(struct rtw89_dev *rtwdev, u8 type) h2c = (struct rtw89_h2c_cxrole_v8 *)skb->data; h2c->hdr.type = type; - h2c->hdr.ver = rtwdev->btc.ver->fwlrole; + h2c->hdr.ver = 8; h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; h2c->r.connect_cnt = r->connect_cnt; h2c->r.link_mode = r->link_mode; @@ -6398,7 +6402,7 @@ int rtw89_fw_h2c_cxdrv_role_v10(struct rtw89_dev *rtwdev, u8 type) h2c = (struct rtw89_h2c_cxrole_v10 *)skb->data; h2c->hdr.type = type; - h2c->hdr.ver = rtwdev->btc.ver->fwlrole; + h2c->hdr.ver = 10; h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; for (j = RTW89_MAC_0; j <= RTW89_MAC_1; j++) { From 58b1bde3712e74ead009b21c707f409b121ea51c Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 17 Jul 2026 14:57:34 +0800 Subject: [PATCH 0546/1433] wifi: rtw89: coex: Add firmware 0.29.133.X support for RTL8852B family The new firmware modified GPIO setup structure format for third party chip set I/O control & offloaded Wi-Fi TRX status to firmware for training traffic RF-Parameters & TDMA mechanism. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717065739.64124-9-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 10699c92273a..6bbcc6cd5dae 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -142,6 +142,15 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .max_role_num = 6, .fcxosi = 6, .fcxmlo = 2, .bt_desired = 8, .fcxtrx = 9, }, + {RTL8852BT, RTW89_FW_VER_CODE(0, 29, 133, 0), + .fcxbtcrpt = 9, .fcxtdma = 7, .fcxslots = 7, .fcxcysta = 7, + .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 8, + .fcxbtver = 7, .fcxbtscan = 7, .fcxbtafh = 7, .fcxbtdevinfo = 7, + .fwlrole = 7, .frptmap = 5, .fcxctrl = 7, .fcxinit = 107, + .fwevntrptl = 1, .fwc2hfunc = 2, .drvinfo_ver = 103, .info_buf = 1800, + .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 8, + .fcxtrx = 107, + }, {RTL8852BT, RTW89_FW_VER_CODE(0, 29, 122, 0), .fcxbtcrpt = 8, .fcxtdma = 7, .fcxslots = 7, .fcxcysta = 7, .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 7, @@ -187,6 +196,15 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, .fcxtrx = 0, }, + {RTL8851B, RTW89_FW_VER_CODE(0, 29, 133, 0), + .fcxbtcrpt = 9, .fcxtdma = 7, .fcxslots = 7, .fcxcysta = 7, + .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 8, + .fcxbtver = 7, .fcxbtscan = 7, .fcxbtafh = 7, .fcxbtdevinfo = 7, + .fwlrole = 7, .frptmap = 5, .fcxctrl = 7, .fcxinit = 107, + .fwevntrptl = 1, .fwc2hfunc = 2, .drvinfo_ver = 103, .info_buf = 1800, + .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 8, + .fcxtrx = 107, + }, {RTL8851B, RTW89_FW_VER_CODE(0, 29, 29, 0), .fcxbtcrpt = 105, .fcxtdma = 3, .fcxslots = 1, .fcxcysta = 5, .fcxstep = 3, .fcxnullsta = 2, .fcxmreg = 2, .fcxgpiodbg = 1, @@ -223,6 +241,15 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, .fcxtrx = 0, }, + {RTL8852B, RTW89_FW_VER_CODE(0, 29, 133, 0), + .fcxbtcrpt = 9, .fcxtdma = 7, .fcxslots = 7, .fcxcysta = 7, + .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 8, + .fcxbtver = 7, .fcxbtscan = 7, .fcxbtafh = 7, .fcxbtdevinfo = 7, + .fwlrole = 7, .frptmap = 105, .fcxctrl = 7, .fcxinit = 107, + .fwevntrptl = 1, .fwc2hfunc = 2, .drvinfo_ver = 103, .info_buf = 1800, + .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 8, + .fcxtrx = 107, + }, {RTL8852B, RTW89_FW_VER_CODE(0, 29, 122, 0), .fcxbtcrpt = 8, .fcxtdma = 7, .fcxslots = 7, .fcxcysta = 7, .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 7, From 42a83b5f13e3d9820113f4de752e5b8353f3f7dd Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 17 Jul 2026 14:57:35 +0800 Subject: [PATCH 0547/1433] wifi: rtw89: coex: Add TDMA version 4 TDMA version 4 uses new TLV-Header to package TDMA information, patch related entry for version 4. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717065739.64124-10-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 6bbcc6cd5dae..51108ce5ce31 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -1705,7 +1705,8 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, pfinfo = &pfwinfo->rpt_fbtc_tdma.finfo.v1; pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_tdma.finfo.v1); fwsubver->fcxtdma = 0; - } else if (ver->fcxtdma == 3 || ver->fcxtdma == 7) { + } else if (ver->fcxtdma == 3 || ver->fcxtdma == 4 || + ver->fcxtdma == 7) { pfinfo = &pfwinfo->rpt_fbtc_tdma.finfo.v3; pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_tdma.finfo.v3); fwsubver->fcxtdma = pfwinfo->rpt_fbtc_tdma.finfo.v3.fver; @@ -2212,7 +2213,8 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, memcmp(&dm->tdma_now, &pfwinfo->rpt_fbtc_tdma.finfo.v1, sizeof(dm->tdma_now))); - else if (ver->fcxtdma == 3 || ver->fcxtdma == 7) + else if (ver->fcxtdma == 3 || ver->fcxtdma == 4 || + ver->fcxtdma == 7) _chk_btc_err(rtwdev, BTC_DCNT_TDMA_NONSYNC, memcmp(&dm->tdma_now, &pfwinfo->rpt_fbtc_tdma.finfo.v3.tdma, @@ -2559,7 +2561,7 @@ static void _append_tdma(struct rtw89_dev *rtwdev) tlv->len = sizeof(*v); *v = dm->tdma; btc->policy_len += BTC_TLV_HDR_LEN + sizeof(*v); - } else if (ver->fcxtdma == 7) { + } else if (ver->fcxtdma == 7 || ver->fcxtdma == 4) { tlv_v7 = (struct rtw89_btc_btf_tlv_v7 *)&btc->policy[len]; tlv_v7->len = sizeof(dm->tdma); tlv_v7->ver = ver->fcxtdma; From b87da03ee7e9a2e0137bada600add2f2097e11c3 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 17 Jul 2026 14:57:36 +0800 Subject: [PATCH 0548/1433] wifi: rtw89: coex: Add slots version 2 Slots structure version 2 uses new TLV-Header to package slots information, patch related entry for version 2. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717065739.64124-11-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 54 ++++++++++++++++------- drivers/net/wireless/realtek/rtw89/coex.h | 8 ++-- drivers/net/wireless/realtek/rtw89/core.h | 9 ++++ 3 files changed, 51 insertions(+), 20 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 51108ce5ce31..65d4e9139342 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -1133,7 +1133,7 @@ static void _reset_btc_var(struct rtw89_dev *rtwdev, u8 type) /* set the slot_now table to original */ btc->dm.tdma_now = t_def[CXTD_OFF]; btc->dm.tdma = t_def[CXTD_OFF]; - if (ver->fcxslots >= 7) { + if (ver->fcxslots >= 2) { for (i = 0; i < ARRAY_SIZE(s_def); i++) { btc->dm.slot.v7[i].dur = s_def[i].dur; btc->dm.slot.v7[i].cxtype = s_def[i].cxtype; @@ -1721,6 +1721,10 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, pfinfo = &pfwinfo->rpt_fbtc_slots.finfo.v1; pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_slots.finfo.v1); fwsubver->fcxslots = pfwinfo->rpt_fbtc_slots.finfo.v1.fver; + } else if (ver->fcxslots == 2) { + pfinfo = &pfwinfo->rpt_fbtc_slots.finfo.v2; + pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_slots.finfo.v2); + fwsubver->fcxslots = pfwinfo->rpt_fbtc_slots.finfo.v2.fver; } else if (ver->fcxslots == 7) { pfinfo = &pfwinfo->rpt_fbtc_slots.finfo.v7; pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_slots.finfo.v7); @@ -2232,6 +2236,15 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, memcmp(dm->slot_now.v7, pfwinfo->rpt_fbtc_slots.finfo.v7.slot, sizeof(dm->slot_now.v7))); + } else if (ver->fcxslots == 2) { + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(): check %d %zu\n", + __func__, BTC_DCNT_SLOT_NONSYNC, + sizeof(dm->slot_now.v7)); + _chk_btc_err(rtwdev, BTC_DCNT_SLOT_NONSYNC, + memcmp(dm->slot_now.v7, + pfwinfo->rpt_fbtc_slots.finfo.v2.slot, + sizeof(dm->slot_now.v7))); } else if (ver->fcxslots == 1) { rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): check %d %zu\n", @@ -2687,7 +2700,7 @@ static void _append_slot(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; - if (btc->ver->fcxslots == 7) + if (btc->ver->fcxslots == 2 || btc->ver->fcxslots == 7) _append_slot_v7(rtwdev); else _append_slot_v1(rtwdev); @@ -2902,7 +2915,7 @@ static void rtw89_btc_fw_set_slots(struct rtw89_dev *rtwdev) struct rtw89_btc_dm *dm = &btc->dm; u16 n, len; - if (ver->fcxslots == 7) { + if (ver->fcxslots == 2 || ver->fcxslots == 7) { len = sizeof(*tlv_v7) + sizeof(dm->slot.v7); tlv_v7 = kmalloc(len, GFP_KERNEL); if (!tlv_v7) @@ -3096,7 +3109,7 @@ static void _fw_set_policy(struct rtw89_dev *rtwdev, u16 policy_type, btc->policy, btc->policy_len); if (!ret) { memcpy(&dm->tdma_now, &dm->tdma, sizeof(dm->tdma_now)); - if (btc->ver->fcxslots == 7) + if (btc->ver->fcxslots == 2 || btc->ver->fcxslots == 7) memcpy(&dm->slot_now.v7, &dm->slot.v7, sizeof(dm->slot_now.v7)); else memcpy(&dm->slot_now.v1, &dm->slot.v1, sizeof(dm->slot_now.v1)); @@ -4404,7 +4417,6 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_fbtc_tdma *t = &dm->tdma; - struct rtw89_btc_fbtc_slot *s = dm->slot.v1; u8 type; u32 tbl_w1, tbl_b1, tbl_b4; bool tdma_on = false; @@ -4428,14 +4440,16 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) switch (type) { case BTC_CXP_USERDEF0: *t = t_def[CXTD_OFF]; - s[CXST_OFF] = s_def[CXST_OFF]; + _slot_set_le(btc, CXST_OFF, s_def[CXST_OFF].dur, + s_def[CXST_OFF].cxtbl, s_def[CXST_OFF].cxtype); _slot_set_tbl(btc, CXST_OFF, cxtbl[2]); btc->update_policy_force = true; break; case BTC_CXP_OFF: /* TDMA off */ tdma_on = false; *t = t_def[CXTD_OFF]; - s[CXST_OFF] = s_def[CXST_OFF]; + _slot_set_le(btc, CXST_OFF, s_def[CXST_OFF].dur, + s_def[CXST_OFF].cxtbl, s_def[CXST_OFF].cxtype); switch (policy_type) { case BTC_CXP_OFF_BT: @@ -4498,16 +4512,23 @@ void rtw89_btc_set_policy(struct rtw89_dev *rtwdev, u16 policy_type) *t = t_def[CXTD_OFF_EXT]; switch (policy_type) { case BTC_CXP_OFFE_DEF: - s[CXST_E2G] = s_def[CXST_E2G]; - s[CXST_E5G] = s_def[CXST_E5G]; - s[CXST_EBT] = s_def[CXST_EBT]; - s[CXST_ENULL] = s_def[CXST_ENULL]; + _slot_set_le(btc, CXST_E2G, s_def[CXST_E2G].dur, + s_def[CXST_E2G].cxtbl, s_def[CXST_E2G].cxtype); + _slot_set_le(btc, CXST_E5G, s_def[CXST_E5G].dur, + s_def[CXST_E5G].cxtbl, s_def[CXST_E5G].cxtype); + _slot_set_le(btc, CXST_EBT, s_def[CXST_EBT].dur, + s_def[CXST_EBT].cxtbl, s_def[CXST_EBT].cxtype); + _slot_set_le(btc, CXST_ENULL, s_def[CXST_ENULL].dur, + s_def[CXST_ENULL].cxtbl, s_def[CXST_ENULL].cxtype); break; case BTC_CXP_OFFE_DEF2: _slot_set(btc, CXST_E2G, 20, cxtbl[1], SLOT_ISO); - s[CXST_E5G] = s_def[CXST_E5G]; - s[CXST_EBT] = s_def[CXST_EBT]; - s[CXST_ENULL] = s_def[CXST_ENULL]; + _slot_set_le(btc, CXST_E5G, s_def[CXST_E5G].dur, + s_def[CXST_E5G].cxtbl, s_def[CXST_E5G].cxtype); + _slot_set_le(btc, CXST_EBT, s_def[CXST_EBT].dur, + s_def[CXST_EBT].cxtbl, s_def[CXST_EBT].cxtype); + _slot_set_le(btc, CXST_ENULL, s_def[CXST_ENULL].dur, + s_def[CXST_ENULL].cxtbl, s_def[CXST_ENULL].cxtype); break; } break; @@ -10081,6 +10102,7 @@ static int _show_fbtc_tdma(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) static int _show_fbtc_slots(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) { struct rtw89_btc *btc = &rtwdev->btc; + const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_dm *dm = &btc->dm; char *p = buf, *end = buf + bufsz; u16 dur, cxtype; @@ -10088,11 +10110,11 @@ static int _show_fbtc_slots(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) u8 i = 0; for (i = 0; i < CXST_MAX; i++) { - if (btc->ver->fcxslots == 1) { + if (ver->fcxslots == 1) { dur = le16_to_cpu(dm->slot_now.v1[i].dur); tbl = le32_to_cpu(dm->slot_now.v1[i].cxtbl); cxtype = le16_to_cpu(dm->slot_now.v1[i].cxtype); - } else if (btc->ver->fcxslots == 7) { + } else if (ver->fcxslots == 2 || ver->fcxslots == 7) { dur = le16_to_cpu(dm->slot_now.v7[i].dur); tbl = le32_to_cpu(dm->slot_now.v7[i].cxtbl); cxtype = le16_to_cpu(dm->slot_now.v7[i].cxtype); diff --git a/drivers/net/wireless/realtek/rtw89/coex.h b/drivers/net/wireless/realtek/rtw89/coex.h index e17407696d8a..3238c44429a9 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.h +++ b/drivers/net/wireless/realtek/rtw89/coex.h @@ -424,7 +424,7 @@ void _slot_set_le(struct rtw89_btc *btc, u8 sid, __le16 dura, __le32 tbl, __le16 btc->dm.slot.v1[sid].dur = dura; btc->dm.slot.v1[sid].cxtbl = tbl; btc->dm.slot.v1[sid].cxtype = type; - } else if (btc->ver->fcxslots == 7) { + } else if (btc->ver->fcxslots == 2 || btc->ver->fcxslots == 7) { btc->dm.slot.v7[sid].dur = dura; btc->dm.slot.v7[sid].cxtype = type; btc->dm.slot.v7[sid].cxtbl = tbl; @@ -442,7 +442,7 @@ void _slot_set_dur(struct rtw89_btc *btc, u8 sid, u16 dura) { if (btc->ver->fcxslots == 1) btc->dm.slot.v1[sid].dur = cpu_to_le16(dura); - else if (btc->ver->fcxslots == 7) + else if (btc->ver->fcxslots == 2 || btc->ver->fcxslots == 7) btc->dm.slot.v7[sid].dur = cpu_to_le16(dura); } @@ -451,7 +451,7 @@ void _slot_set_type(struct rtw89_btc *btc, u8 sid, u16 type) { if (btc->ver->fcxslots == 1) btc->dm.slot.v1[sid].cxtype = cpu_to_le16(type); - else if (btc->ver->fcxslots == 7) + else if (btc->ver->fcxslots == 2 || btc->ver->fcxslots == 7) btc->dm.slot.v7[sid].cxtype = cpu_to_le16(type); } @@ -460,7 +460,7 @@ void _slot_set_tbl(struct rtw89_btc *btc, u8 sid, u32 tbl) { if (btc->ver->fcxslots == 1) btc->dm.slot.v1[sid].cxtbl = cpu_to_le32(tbl); - else if (btc->ver->fcxslots == 7) + else if (btc->ver->fcxslots == 2 || btc->ver->fcxslots == 7) btc->dm.slot.v7[sid].cxtbl = cpu_to_le32(tbl); } diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index f7b3523c790c..10d432d622cc 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -3093,6 +3093,14 @@ struct rtw89_btc_fbtc_slot_v7 { __le32 cxtbl; } __packed; +struct rtw89_btc_fbtc_slots_v2 { + u8 fver; /* btc_ver::fcxslots */ + u8 tbl_num; + __le16 rsvd; + __le32 update_map; + struct rtw89_btc_fbtc_slot_v7 slot[CXST_MAX]; +} __packed; + struct rtw89_btc_fbtc_slot_u16 { __le16 dur; /* slot duration */ __le16 cxtype; @@ -3118,6 +3126,7 @@ struct rtw89_btc_fbtc_slots_v7 { union rtw89_btc_fbtc_slots_info { struct rtw89_btc_fbtc_slots v1; + struct rtw89_btc_fbtc_slots_v2 v2; struct rtw89_btc_fbtc_slots_v7 v7; } __packed; From b8892add7e51a472430f5ba0c3f3d0da6a20c800 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 17 Jul 2026 14:57:37 +0800 Subject: [PATCH 0549/1433] wifi: rtw89: coex: Add cycle status report version 105 The exists version 5 format has FDDT(frequency divided training) related information. But the feature wasn't support for RTL8852C now, so firmware will not send the related reference value. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717065739.64124-12-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 197 ++++++++++++++++++++++ drivers/net/wireless/realtek/rtw89/core.h | 21 +++ 2 files changed, 218 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 65d4e9139342..54c11af46f8d 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -1757,6 +1757,11 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, pcysta->v5 = pfwinfo->rpt_fbtc_cysta.finfo.v5; pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_cysta.finfo.v5); fwsubver->fcxcysta = pfwinfo->rpt_fbtc_cysta.finfo.v5.fver; + } else if (ver->fcxcysta == 105) { + pfinfo = &pfwinfo->rpt_fbtc_cysta.finfo.v105; + pcysta->v105 = pfwinfo->rpt_fbtc_cysta.finfo.v105; + pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_cysta.finfo.v105); + fwsubver->fcxcysta = pfwinfo->rpt_fbtc_cysta.finfo.v105.fver; } else if (ver->fcxcysta == 7) { pfinfo = &pfwinfo->rpt_fbtc_cysta.finfo.v7; pcysta->v7 = pfwinfo->rpt_fbtc_cysta.finfo.v7; @@ -2434,6 +2439,58 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, le16_to_cpu(pcysta->v5.slot_cnt[CXST_B1])); _chk_btc_err(rtwdev, BTC_DCNT_CYCLE_HANG, le16_to_cpu(pcysta->v5.cycles)); + } else if (ver->fcxcysta == 105) { + if (dm->fddt_train == BTC_FDDT_ENABLE) + break; + + cnt_leak_slot = le16_to_cpu(pcysta->v105.slot_cnt[CXST_LK]); + cnt_rx_imr = le32_to_cpu(pcysta->v105.leak_slot.cnt_rximr); + + /* Check Leak-AP */ + if (cnt_leak_slot != 0 && cnt_rx_imr != 0 && + dm->tdma_now.rxflctrl) { + if (le16_to_cpu(pcysta->v5.cycles) >= BTC_CYSTA_CHK_PERIOD && + cnt_leak_slot < BTC_LEAK_AP_TH * cnt_rx_imr) + dm->leak_ap = 1; + } + + /* Check diff time between real WL slot and W1 slot */ + if (dm->tdma_now.type == CXTDMA_OFF) { + if (ver->fcxslots == 1) + wl_slot_set = le16_to_cpu(dm->slot_now.v1[CXST_W1].dur); + else if (ver->fcxslots == 7) + wl_slot_set = le16_to_cpu(dm->slot_now.v7[CXST_W1].dur); + wl_slot_real = le16_to_cpu(pcysta->v105.cycle_time.tavg[CXT_WL]); + + if (wl_slot_real > wl_slot_set) + diff_t = wl_slot_real - wl_slot_set; + else + diff_t = wl_slot_set - wl_slot_real; + } + _chk_btc_err(rtwdev, BTC_DCNT_WL_SLOT_DRIFT, diff_t); + + /* Check diff time between real BT slot and EBT/E5G slot */ + bt_slot_set = btc->bt_req_len[RTW89_PHY_0]; + bt_slot_real = le16_to_cpu(pcysta->v105.cycle_time.tavg[CXT_BT]); + diff_t = 0; + if (dm->tdma_now.type == CXTDMA_OFF && + dm->tdma_now.ext_ctrl == CXECTL_EXT && + bt_slot_set != 0) { + if (bt_slot_set > bt_slot_real) + diff_t = bt_slot_set - bt_slot_real; + else + diff_t = bt_slot_real - bt_slot_set; + } + + _chk_btc_err(rtwdev, BTC_DCNT_BT_SLOT_DRIFT, diff_t); + _chk_btc_err(rtwdev, BTC_DCNT_E2G_HANG, + le16_to_cpu(pcysta->v105.slot_cnt[CXST_E2G])); + _chk_btc_err(rtwdev, BTC_DCNT_W1_HANG, + le16_to_cpu(pcysta->v105.slot_cnt[CXST_W1])); + _chk_btc_err(rtwdev, BTC_DCNT_B1_HANG, + le16_to_cpu(pcysta->v105.slot_cnt[CXST_B1])); + _chk_btc_err(rtwdev, BTC_DCNT_CYCLE_HANG, + le16_to_cpu(pcysta->v105.cycles)); } else if (ver->fcxcysta == 7) { if (dm->fddt_train == BTC_FDDT_ENABLE) break; @@ -10690,6 +10747,144 @@ static int _show_fbtc_cysta_v5(struct rtw89_dev *rtwdev, char *buf, size_t bufsz return p - buf; } +static int _show_fbtc_cysta_v105(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_bt_a2dp_desc *a2dp = &btc->cx.bt0.link_info.a2dp_desc; + struct rtw89_btc_btf_fwinfo *pfwinfo = &btc->fwinfo; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_fbtc_a2dp_trx_stat_v4 *a2dp_trx; + struct rtw89_btc_fbtc_cysta_v105 *pcysta; + struct rtw89_btc_rpt_cmn_info *pcinfo; + u8 i, cnt = 0, slot_pair, divide_cnt; + u16 cycle, c_begin, c_end, store_index; + char *p = buf, *end = buf + bufsz; + + pcinfo = &pfwinfo->rpt_fbtc_cysta.cinfo; + if (!pcinfo->valid) + return 0; + + pcysta = &pfwinfo->rpt_fbtc_cysta.finfo.v105; + p += scnprintf(p, end - p, + " %-15s : cycle:%d, bcn[all:%d/all_ok:%d/bt:%d/bt_ok:%d]", + "[cycle_cnt]", + le16_to_cpu(pcysta->cycles), + le16_to_cpu(pcysta->bcn_cnt[CXBCN_ALL]), + le16_to_cpu(pcysta->bcn_cnt[CXBCN_ALL_OK]), + le16_to_cpu(pcysta->bcn_cnt[CXBCN_BT_SLOT]), + le16_to_cpu(pcysta->bcn_cnt[CXBCN_BT_OK])); + + for (i = 0; i < CXST_MAX; i++) { + if (!le16_to_cpu(pcysta->slot_cnt[i])) + continue; + + p += scnprintf(p, end - p, ", %s:%d", id_to_slot(i), + le16_to_cpu(pcysta->slot_cnt[i])); + } + + if (dm->tdma_now.rxflctrl) + p += scnprintf(p, end - p, ", leak_rx:%d", + le32_to_cpu(pcysta->leak_slot.cnt_rximr)); + + if (pcysta->collision_cnt) + p += scnprintf(p, end - p, ", collision:%d", + pcysta->collision_cnt); + + if (le16_to_cpu(pcysta->skip_cnt)) + p += scnprintf(p, end - p, ", skip:%d", + le16_to_cpu(pcysta->skip_cnt)); + + p += scnprintf(p, end - p, "\n"); + + p += scnprintf(p, end - p, " %-15s : avg_t[wl:%d/bt:%d/lk:%d.%03d]", + "[cycle_time]", + le16_to_cpu(pcysta->cycle_time.tavg[CXT_WL]), + le16_to_cpu(pcysta->cycle_time.tavg[CXT_BT]), + le16_to_cpu(pcysta->leak_slot.tavg) / 1000, + le16_to_cpu(pcysta->leak_slot.tavg) % 1000); + p += scnprintf(p, end - p, + ", max_t[wl:%d/bt:%d/lk:%d.%03d]\n", + le16_to_cpu(pcysta->cycle_time.tmax[CXT_WL]), + le16_to_cpu(pcysta->cycle_time.tmax[CXT_BT]), + le16_to_cpu(pcysta->leak_slot.tmax) / 1000, + le16_to_cpu(pcysta->leak_slot.tmax) % 1000); + + cycle = le16_to_cpu(pcysta->cycles); + if (cycle <= 1) + goto out; + + /* 1 cycle record 1 wl-slot and 1 bt-slot */ + slot_pair = BTC_CYCLE_SLOT_MAX / 2; + + if (cycle <= slot_pair) + c_begin = 1; + else + c_begin = cycle - slot_pair + 1; + + c_end = cycle; + + if (a2dp->exist) + divide_cnt = 3; + else + divide_cnt = BTC_CYCLE_SLOT_MAX / 4; + + if (c_begin > c_end) + goto out; + + for (cycle = c_begin; cycle <= c_end; cycle++) { + cnt++; + store_index = ((cycle - 1) % slot_pair) * 2; + + if (cnt % divide_cnt == 1) + p += scnprintf(p, end - p, " %-15s : ", + "[cycle_step]"); + + p += scnprintf(p, end - p, "->b%02d", + le16_to_cpu(pcysta->slot_step_time[store_index])); + if (a2dp->exist) { + a2dp_trx = &pcysta->a2dp_trx[store_index]; + p += scnprintf(p, end - p, "(%d/%d/%dM/%d/%d/%d)", + a2dp_trx->empty_cnt, + a2dp_trx->retry_cnt, + a2dp_trx->tx_rate ? 3 : 2, + a2dp_trx->tx_cnt, + a2dp_trx->ack_cnt, + a2dp_trx->nack_cnt); + } + p += scnprintf(p, end - p, "->w%02d", + le16_to_cpu(pcysta->slot_step_time[store_index + 1])); + if (a2dp->exist) { + a2dp_trx = &pcysta->a2dp_trx[store_index + 1]; + p += scnprintf(p, end - p, "(%d/%d/%dM/%d/%d/%d)", + a2dp_trx->empty_cnt, + a2dp_trx->retry_cnt, + a2dp_trx->tx_rate ? 3 : 2, + a2dp_trx->tx_cnt, + a2dp_trx->ack_cnt, + a2dp_trx->nack_cnt); + } + if (cnt % divide_cnt == 0 || cnt == c_end) + p += scnprintf(p, end - p, "\n"); + } + + if (a2dp->exist) { + p += scnprintf(p, end - p, + " %-15s : a2dp_ept:%d, a2dp_late:%d", + "[a2dp_t_sta]", + le16_to_cpu(pcysta->a2dp_ept.cnt), + le16_to_cpu(pcysta->a2dp_ept.cnt_timeout)); + + p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d", + le16_to_cpu(pcysta->a2dp_ept.tavg), + le16_to_cpu(pcysta->a2dp_ept.tmax)); + + p += scnprintf(p, end - p, "\n"); + } + +out: + return p - buf; +} + static int _show_fbtc_cysta_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) { struct rtw89_btc_bt_info *bt = &rtwdev->btc.cx.bt0; @@ -11081,6 +11276,8 @@ static int _show_fw_dm_msg(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += _show_fbtc_cysta_v4(rtwdev, p, end - p); else if (ver->fcxcysta == 5) p += _show_fbtc_cysta_v5(rtwdev, p, end - p); + else if (ver->fcxcysta == 105) + p += _show_fbtc_cysta_v105(rtwdev, p, end - p); else if (ver->fcxcysta == 7) p += _show_fbtc_cysta_v7(rtwdev, p, end - p); diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 10d432d622cc..7573f1965c98 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -3354,6 +3354,26 @@ struct rtw89_btc_fbtc_cysta_v5 { /* statistics for cycles */ __le32 except_map; } __packed; +struct rtw89_btc_fbtc_cysta_v105 { + u8 fver; + u8 rsvd; + u8 collision_cnt; + u8 except_cnt; + u8 wl_rx_err_ratio[BTC_CYCLE_SLOT_MAX]; + + __le16 skip_cnt; + __le16 cycles; + + __le16 slot_step_time[BTC_CYCLE_SLOT_MAX]; + __le16 slot_cnt[CXST_MAX]; + __le16 bcn_cnt[CXBCN_MAX]; + struct rtw89_btc_fbtc_cycle_time_info_v5 cycle_time; + struct rtw89_btc_fbtc_cycle_leak_info leak_slot; + struct rtw89_btc_fbtc_cycle_a2dp_empty_info a2dp_ept; + struct rtw89_btc_fbtc_a2dp_trx_stat_v4 a2dp_trx[BTC_CYCLE_SLOT_MAX]; + __le32 except_map; +} __packed; + struct rtw89_btc_fbtc_cysta_v7 { /* statistics for cycles */ u8 fver; u8 rsvd; @@ -3383,6 +3403,7 @@ union rtw89_btc_fbtc_cysta_info { struct rtw89_btc_fbtc_cysta_v3 v3; struct rtw89_btc_fbtc_cysta_v4 v4; struct rtw89_btc_fbtc_cysta_v5 v5; + struct rtw89_btc_fbtc_cysta_v105 v105; struct rtw89_btc_fbtc_cysta_v7 v7; }; From 236badaa50b3a2d27543d8024ae5c78cd242615d Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 17 Jul 2026 14:57:38 +0800 Subject: [PATCH 0550/1433] wifi: rtw89: coex: Add wifi role info version 101 The structure active_role which describes the using Wi-Fi role format is different with the exist v1. Add branch to cover the difference. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717065739.64124-13-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 13 ++-- drivers/net/wireless/realtek/rtw89/fw.c | 82 +++++++++++++++++++++++ drivers/net/wireless/realtek/rtw89/fw.h | 35 ++++++++++ 3 files changed, 125 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 54c11af46f8d..b95e34cb1567 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -3198,6 +3198,8 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index) rtw89_fw_h2c_cxdrv_role(rtwdev, index); else if (ver->fwlrole == 1) rtw89_fw_h2c_cxdrv_role_v1(rtwdev, index); + else if (ver->fwlrole == 101) + rtw89_fw_h2c_cxdrv_role_v101(rtwdev, index); else if (ver->fwlrole == 2) rtw89_fw_h2c_cxdrv_role_v2(rtwdev, index); else if (ver->fwlrole == 7) @@ -6873,6 +6875,7 @@ static void _update_wl_link_mode(struct rtw89_dev *rtwdev, u8 hw_band, u8 type) struct rtw89_btc_wl_mlo_info *mlo_info = &wl->mlo_info; struct rtw89_btc_wl_role_info *r = &wl->role_info; u8 p2p_exist = wl->role_info.p2p_exist; + u8 rver = ver->fwlrole; if (hw_band == RTW89_PHY_1) p2p_exist = wl->role_info.p2p_exist_hb1; @@ -6893,7 +6896,7 @@ static void _update_wl_link_mode(struct rtw89_dev *rtwdev, u8 hw_band, u8 type) r->link_mode = BTC_WLINK_STA; } - if (ver->fwlrole >= 10) + if (rver >= 10 && rver < 100) break; if (mlo_info->hwb_rf_band[hw_band] != RTW89_BAND_2G) { @@ -6921,7 +6924,7 @@ static void _update_wl_link_mode(struct rtw89_dev *rtwdev, u8 hw_band, u8 type) r->link_mode = BTC_WLINK_SCC; } - if (ver->fwlrole >= 10) + if (rver >= 10 && rver < 100) break; if (mlo_info->hwb_rf_band[hw_band] != RTW89_BAND_2G) { @@ -6941,7 +6944,7 @@ static void _update_wl_link_mode(struct rtw89_dev *rtwdev, u8 hw_band, u8 type) else r->link_mode = BTC_WLINK_STA; - if (ver->fwlrole >= 10) + if (rver >= 10 && rver < 100) break; if (mlo_info->hwb_rf_band[hw_band] != RTW89_BAND_2G) @@ -6959,7 +6962,7 @@ static void _update_wl_link_mode(struct rtw89_dev *rtwdev, u8 hw_band, u8 type) */ r->link_mode = BTC_WLINK_DB_MCC; - if (ver->fwlrole >= 10) + if (rver >= 10 && rver < 100) break; r->link_mode_v0 = BTC_WLINK_V0_25G_MCC; @@ -6968,7 +6971,7 @@ static void _update_wl_link_mode(struct rtw89_dev *rtwdev, u8 hw_band, u8 type) /* MLMR only support STA now (2024) */ r->link_mode = BTC_WLINK_STA; - if (ver->fwlrole >= 10) + if (rver >= 10 && rver < 100) break; if (mlo_info->hwb_rf_band[hw_band] != RTW89_BAND_2G) { diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 196de240fad1..3b8c144e0e98 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -6149,6 +6149,88 @@ int rtw89_fw_h2c_cxdrv_role_v1(struct rtw89_dev *rtwdev, u8 type) return ret; } +int rtw89_fw_h2c_cxdrv_role_v101(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_wl_info *wl = &btc->cx.wl; + struct rtw89_btc_wl_role_info *role_info = &wl->role_info; + struct rtw89_btc_wl_rlink *active; + struct rtw89_h2c_cxrole_v101 *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + int ret; + u8 i; + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_role_v101\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxrole_v101 *)skb->data; + + h2c->hdr.type = type; + h2c->hdr.len = len - H2C_LEN_CXDRVHDR; + + h2c->connect_cnt = role_info->connect_cnt; + h2c->link_mode = role_info->link_mode; + h2c->role_map = cpu_to_le16(role_info->role_map); + + for (i = 0; i < RTW89_PORT_NUM; i++) { + active = &role_info->rlink[i][0]; + h2c->act_role[i].map_role_status = + u8_encode_bits(active->connected, + RTW89_H2C_CXROLE_V101_ROLE_STAT_CONNTECTED) | + u8_encode_bits(active->pid, + RTW89_H2C_CXROLE_V101_ROLE_STAT_PID) | + u8_encode_bits(active->phy, + RTW89_H2C_CXROLE_V101_ROLE_STAT_PHY) | + u8_encode_bits(active->noa, + RTW89_H2C_CXROLE_V101_ROLE_STAT_NOA) | + u8_encode_bits(active->rf_band, + RTW89_H2C_CXROLE_V101_ROLE_STAT_BADN); + h2c->act_role[i].map_clips_bw = + u8_encode_bits(active->client_ps, + RTW89_H2C_CXROLE_V101_CLIPS_BW_CLIENTPS) | + u8_encode_bits(active->bw, + RTW89_H2C_CXROLE_V101_CLIPS_BW_BW); + h2c->act_role[i].role = active->role; + h2c->act_role[i].ch = active->ch; + h2c->act_role[i].noa_duration = cpu_to_le32(active->noa_dur); + } + + h2c->mrole_type = cpu_to_le32(role_info->mrole_type); + h2c->mrole_noa_duration = cpu_to_le32(role_info->mrole_noa_duration); + h2c->map_dbcc_linkmode_chg = + le32_encode_bits(role_info->dbcc_en, + RTW89_H2C_CXROLE_V101_DBCC_EN) | + le32_encode_bits(role_info->dbcc_chg, + RTW89_H2C_CXROLE_V101_DBCC_CHG) | + le32_encode_bits(role_info->dbcc_2g_phy, + RTW89_H2C_CXROLE_V101_DBCC_2G_PHY) | + le32_encode_bits(role_info->link_mode_chg, + RTW89_H2C_CXROLE_V101_DBCC_LINKMODE_CHG) | + le32_encode_bits(role_info->rsvd, + RTW89_H2C_CXROLE_V101_DBCC_RSVD); + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, + len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + #define H2C_LEN_CXDRVINFO_ROLE_SIZE_V2(max_role_num) \ (4 + 8 * (max_role_num) + H2C_LEN_CXDRVINFO_ROLE_DBCC_LEN + H2C_LEN_CXDRVHDR) diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index 38090105b412..e86014ca249a 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -2603,6 +2603,40 @@ struct rtw89_h2c_cxinit_v10 { struct rtw89_btc_init_info_v10 init; } __packed; +#define RTW89_H2C_CXROLE_V101_ROLE_STAT_CONNTECTED BIT(0) +#define RTW89_H2C_CXROLE_V101_ROLE_STAT_PID GENMASK(3, 1) +#define RTW89_H2C_CXROLE_V101_ROLE_STAT_PHY BIT(4) +#define RTW89_H2C_CXROLE_V101_ROLE_STAT_NOA BIT(5) +#define RTW89_H2C_CXROLE_V101_ROLE_STAT_BADN GENMASK(7, 6) + +#define RTW89_H2C_CXROLE_V101_CLIPS_BW_CLIENTPS BIT(0) +#define RTW89_H2C_CXROLE_V101_CLIPS_BW_BW GENMASK(7, 1) + +#define RTW89_H2C_CXROLE_V101_DBCC_EN BIT(0) +#define RTW89_H2C_CXROLE_V101_DBCC_CHG BIT(1) +#define RTW89_H2C_CXROLE_V101_DBCC_2G_PHY GENMASK(3, 2) +#define RTW89_H2C_CXROLE_V101_DBCC_LINKMODE_CHG BIT(4) +#define RTW89_H2C_CXROLE_V101_DBCC_RSVD GENMASK(31, 5) + +struct rtw89_btc_wl_active_role_v101 { + u8 map_role_status; + u8 map_clips_bw; + u8 role; + u8 ch; + __le32 noa_duration; +} __packed; + +struct rtw89_h2c_cxrole_v101 { + struct rtw89_h2c_cxhdr hdr; + u8 connect_cnt; + u8 link_mode; + __le16 role_map; + struct rtw89_btc_wl_active_role_v101 act_role[RTW89_PORT_NUM]; + __le32 mrole_type; + __le32 mrole_noa_duration; + __le32 map_dbcc_linkmode_chg; +} __packed; + static inline void RTW89_SET_FWCMD_CXROLE_CONNECT_CNT(void *cmd, u8 val) { u8p_replace_bits((u8 *)(cmd) + 2, val, GENMASK(7, 0)); @@ -5417,6 +5451,7 @@ int rtw89_fw_h2c_cxdrv_init_v7(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_init_v10(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role_v1(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxdrv_role_v101(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role_v2(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role_v7(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role_v8(struct rtw89_dev *rtwdev, u8 type); From 38c58d541cfe286880cdadebc5d726fe18e3b615 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 17 Jul 2026 14:57:39 +0800 Subject: [PATCH 0551/1433] wifi: rtw89: coex: Add firmware 0.27.97.X support for RTL8852C Newer firmware is using the new TLV-Header format, without this patch it will lead driver run into length mismatch state. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260717065739.64124-14-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index b95e34cb1567..6a1888d8c476 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -214,6 +214,15 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, .fcxtrx = 0, }, + {RTL8852C, RTW89_FW_VER_CODE(0, 27, 97, 0), + .fcxbtcrpt = 4, .fcxtdma = 4, .fcxslots = 2, .fcxcysta = 105, + .fcxstep = 3, .fcxnullsta = 2, .fcxmreg = 2, .fcxgpiodbg = 1, + .fcxbtver = 1, .fcxbtscan = 2, .fcxbtafh = 2, .fcxbtdevinfo = 1, + .fwlrole = 101, .frptmap = 3, .fcxctrl = 1, .fcxinit = 0, + .fwevntrptl = 0, .fwc2hfunc = 1, .drvinfo_ver = 0, .info_buf = 1280, + .max_role_num = 5, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 7, + .fcxtrx = 107, + }, {RTL8852C, RTW89_FW_VER_CODE(0, 27, 57, 0), .fcxbtcrpt = 4, .fcxtdma = 3, .fcxslots = 1, .fcxcysta = 3, .fcxstep = 3, .fcxnullsta = 2, .fcxmreg = 1, .fcxgpiodbg = 1, From b0034a2e415603daa1cf00363b407cc145bbcd31 Mon Sep 17 00:00:00 2001 From: Mihail Dimoski Date: Sat, 18 Jul 2026 14:40:45 +0200 Subject: [PATCH 0552/1433] wifi: rtw88: disable ASPM and deep PS on ASUS TUF Gaming A15 FA506II The RTL8822CE on the ASUS TUF Gaming A15 FA506II wedges during normal use. The driver watchdog toggles PCIe ASPM while leaving power save; the DBI read of the ASPM link-config register fails with -EIO, the PCIe link becomes unstable, and the device drops off the bus, taking Wi-Fi down until a cold power cycle: rtw88_8822ce 0000:03:00.0: failed to read ASPM, ret=-5 rtw88_8822ce 0000:03:00.0: firmware failed to leave lps state rtw88_8822ce 0000:03:00.0: mac power on failed This is the same platform ASPM inter-operability problem already handled for other machines through rtw_pci_quirks[]. Disabling PCI ASPM and deep power save on this model stops the failure. Add a DMI quirk so the workaround is applied automatically. Signed-off-by: Mihail Dimoski Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260718124045.23493-1-mihaildimoski@gmail.com --- drivers/net/wireless/realtek/rtw88/pci.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw88/pci.c b/drivers/net/wireless/realtek/rtw88/pci.c index 69f2840fed09..5b00c1e1eec6 100644 --- a/drivers/net/wireless/realtek/rtw88/pci.c +++ b/drivers/net/wireless/realtek/rtw88/pci.c @@ -1779,6 +1779,16 @@ static const struct dmi_system_id rtw_pci_quirks[] = { .driver_data = (void *)(BIT(QUIRK_DIS_CAP_PCI_ASPM) | BIT(QUIRK_DIS_CAP_LPS_DEEP)), }, + { + .callback = rtw_pci_disable_caps, + .ident = "ASUS TUF Gaming A15 FA506II", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_BOARD_NAME, "FA506II"), + }, + .driver_data = (void *)(BIT(QUIRK_DIS_CAP_PCI_ASPM) | + BIT(QUIRK_DIS_CAP_LPS_DEEP)), + }, {} }; From 0a33bdce34c68f1d158d2ee3c9dfd011bef5c290 Mon Sep 17 00:00:00 2001 From: Ismael Luceno Date: Thu, 2 Jul 2026 12:10:50 +0200 Subject: [PATCH 0553/1433] ipvs: Move defense_work and est_reload_work to system_dfl_long_wq Under synflood conditions binding these handlers to system_long_wq may pin them to a saturated CPU. We've observed improved throughtput on a DPDK/VPP application with this change, which we attribute to the reduced context switching. Neither handler has per-CPU data dependencies nor cache locality requirements that would prevent this change. Signed-off-by: Ismael Luceno Acked-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipvs/ip_vs_ctl.c | 6 +++--- net/netfilter/ipvs/ip_vs_est.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/net/netfilter/ipvs/ip_vs_ctl.c b/net/netfilter/ipvs/ip_vs_ctl.c index bcf40b8c41cf..d7e669efab4d 100644 --- a/net/netfilter/ipvs/ip_vs_ctl.c +++ b/net/netfilter/ipvs/ip_vs_ctl.c @@ -235,7 +235,7 @@ static void defense_work_handler(struct work_struct *work) update_defense_level(ipvs); if (atomic_read(&ipvs->dropentry)) ip_vs_random_dropentry(ipvs); - queue_delayed_work(system_long_wq, &ipvs->defense_work, + queue_delayed_work(system_dfl_long_wq, &ipvs->defense_work, DEFENSE_TIMER_PERIOD); } #endif @@ -290,7 +290,7 @@ static void est_reload_work_handler(struct work_struct *work) atomic_set(&ipvs->est_genid_done, genid); if (repeat) - queue_delayed_work(system_long_wq, &ipvs->est_reload_work, + queue_delayed_work(system_dfl_long_wq, &ipvs->est_reload_work, delay); unlock: @@ -5126,7 +5126,7 @@ static int __net_init ip_vs_control_net_init_sysctl(struct netns_ipvs *ipvs) goto err; /* Schedule defense work */ - queue_delayed_work(system_long_wq, &ipvs->defense_work, + queue_delayed_work(system_dfl_long_wq, &ipvs->defense_work, DEFENSE_TIMER_PERIOD); return 0; diff --git a/net/netfilter/ipvs/ip_vs_est.c b/net/netfilter/ipvs/ip_vs_est.c index ab09f5182951..78964aa861e9 100644 --- a/net/netfilter/ipvs/ip_vs_est.c +++ b/net/netfilter/ipvs/ip_vs_est.c @@ -243,7 +243,7 @@ void ip_vs_est_reload_start(struct netns_ipvs *ipvs, bool restart) /* Bump the kthread configuration genid if stopping is requested */ if (restart) atomic_inc(&ipvs->est_genid); - queue_delayed_work(system_long_wq, &ipvs->est_reload_work, 0); + queue_delayed_work(system_dfl_long_wq, &ipvs->est_reload_work, 0); } /* Start kthread task with current configuration */ From 4172fd3697b0405e4f9654a950b4adf33f3a796c Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Sat, 4 Jul 2026 09:31:33 +0200 Subject: [PATCH 0554/1433] netfilter: xt_tcpmss: extend checkentry to ipv6 sashiko reports: Is it intentional that the new parameter validation callback is applied only to the NFPROTO_IPV4 match? Fixes: 68fc6c6470d6 ("netfilter: xt_tcpmss: add checkentry for parameter validation") Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- net/netfilter/xt_tcpmss.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/netfilter/xt_tcpmss.c b/net/netfilter/xt_tcpmss.c index b08b077d7f0a..5f7f97dbace5 100644 --- a/net/netfilter/xt_tcpmss.c +++ b/net/netfilter/xt_tcpmss.c @@ -103,6 +103,7 @@ static struct xt_match tcpmss_mt_reg[] __read_mostly = { { .name = "tcpmss", .family = NFPROTO_IPV6, + .checkentry = tcpmss_mt_check, .match = tcpmss_mt, .matchsize = sizeof(struct xt_tcpmss_match_info), .proto = IPPROTO_TCP, From 16aecbe3036f6097c26b51b12e4c1cf207769690 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Mon, 6 Jul 2026 14:30:55 +0200 Subject: [PATCH 0555/1433] netfilter: nf_nat_sip: rewind offset when NAT shrinks the packet sashiko says: If map_addr() changes the packet length, such as when the public NAT IP string is shorter or longer than the internal IP, coff will still point to the offset relative to the pre-mangled packet. If the packet shrinks, coff could overshoot the correct position, potentially causing the next ct_sip_parse_header_uri() call to silently skip bytes and miss subsequent Contact headers. Could this lead to a failure to NAT those subsequent headers and leak internal network details? Fixes: c978cd3a9371 ("[NETFILTER]: nf_nat_sip: translate all Contact headers") Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_nat_sip.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/netfilter/nf_nat_sip.c b/net/netfilter/nf_nat_sip.c index aea02f6aff09..762d7e7bb7c7 100644 --- a/net/netfilter/nf_nat_sip.c +++ b/net/netfilter/nf_nat_sip.c @@ -273,12 +273,17 @@ static unsigned int nf_nat_sip(struct sk_buff *skb, unsigned int protoff, SIP_HDR_CONTACT, &in_header, &matchoff, &matchlen, &addr, &port) > 0) { + int old_len = skb->len, delta; + if (!map_addr(skb, protoff, dataoff, dptr, datalen, matchoff, matchlen, &addr, port)) { nf_ct_helper_log(skb, ct, "cannot mangle contact"); return NF_DROP; } + + delta = (int)skb->len - old_len; + coff += delta; } if (!map_sip_addr(skb, protoff, dataoff, dptr, datalen, SIP_HDR_FROM) || From edd51a23343870dbd7cedf6e2765c13cf7a5ccbc Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Fri, 10 Jul 2026 09:54:09 +0200 Subject: [PATCH 0556/1433] netfilter: flowtable: tear down flow entries with stale dst from GC In case of route updates, tear down flow entries with stale dst to give them a chance to obtain a fresh route. This is specifically useful for hardware offloaded entries, where the flowtable software dataplane sees no packet, where the existing check for stale dst entries does not help. Signed-off-by: Pablo Neira Ayuso --- include/net/netfilter/nf_flow_table.h | 8 ++++++++ net/netfilter/nf_flow_table_core.c | 2 ++ net/netfilter/nf_flow_table_ip.c | 8 -------- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h index ce414118962f..a090ec3ffef2 100644 --- a/include/net/netfilter/nf_flow_table.h +++ b/include/net/netfilter/nf_flow_table.h @@ -310,6 +310,14 @@ int flow_offload_add(struct nf_flowtable *flow_table, struct flow_offload *flow) void flow_offload_refresh(struct nf_flowtable *flow_table, struct flow_offload *flow, bool force); +static inline bool nf_flow_dst_check(struct flow_offload_tuple *tuple) +{ + if (!tuple->dst_cache) + return true; + + return dst_check(tuple->dst_cache, tuple->dst_cookie); +} + struct flow_offload_tuple_rhash *flow_offload_lookup(struct nf_flowtable *flow_table, struct flow_offload_tuple *tuple); void nf_flow_table_gc_run(struct nf_flowtable *flow_table); diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c index b66e65439341..58e6a675332d 100644 --- a/net/netfilter/nf_flow_table_core.c +++ b/net/netfilter/nf_flow_table_core.c @@ -571,6 +571,8 @@ static void nf_flow_offload_gc_step(struct nf_flowtable *flow_table, if (nf_flow_has_expired(flow) || nf_ct_is_dying(flow->ct) || + !nf_flow_dst_check(&flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple) || + !nf_flow_dst_check(&flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple) || nf_flow_custom_gc(flow_table, flow)) { flow_offload_teardown(flow); teardown = true; diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c index 0b78decce8a9..0b314b10e705 100644 --- a/net/netfilter/nf_flow_table_ip.c +++ b/net/netfilter/nf_flow_table_ip.c @@ -297,14 +297,6 @@ static bool nf_flow_exceeds_mtu(const struct sk_buff *skb, unsigned int mtu) return true; } -static inline bool nf_flow_dst_check(struct flow_offload_tuple *tuple) -{ - if (!tuple->dst_cache) - return true; - - return dst_check(tuple->dst_cache, tuple->dst_cookie); -} - static unsigned int nf_flow_xmit_xfrm(struct sk_buff *skb, const struct nf_hook_state *state, struct dst_entry *dst) From 1c66ad76ddd4c9f141cae84e6a56c34e090cb3d9 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Mon, 13 Jul 2026 18:06:57 +0200 Subject: [PATCH 0557/1433] netfilter: conntrack_helper: pass master conntrack to helper functions Pass master conntrack as argument to helper functions that parse the packet payload, instead of using exp->master. Although accessing exp->master is safe in this case because it refers to the master conntrack in used by this skb, remove it to step towards turning the exp->master field into a cookie value. Signed-off-by: Pablo Neira Ayuso --- include/linux/netfilter/nf_conntrack_amanda.h | 1 + include/linux/netfilter/nf_conntrack_ftp.h | 1 + include/linux/netfilter/nf_conntrack_irc.h | 1 + include/linux/netfilter/nf_conntrack_tftp.h | 1 + net/netfilter/nf_conntrack_amanda.c | 2 +- net/netfilter/nf_conntrack_ftp.c | 2 +- net/netfilter/nf_conntrack_irc.c | 2 +- net/netfilter/nf_conntrack_tftp.c | 2 +- net/netfilter/nf_nat_amanda.c | 7 ++++--- net/netfilter/nf_nat_ftp.c | 4 ++-- net/netfilter/nf_nat_irc.c | 2 +- net/netfilter/nf_nat_tftp.c | 5 ++--- 12 files changed, 17 insertions(+), 13 deletions(-) diff --git a/include/linux/netfilter/nf_conntrack_amanda.h b/include/linux/netfilter/nf_conntrack_amanda.h index 1719987e8fd8..deb560bb79c4 100644 --- a/include/linux/netfilter/nf_conntrack_amanda.h +++ b/include/linux/netfilter/nf_conntrack_amanda.h @@ -9,6 +9,7 @@ typedef unsigned int nf_nat_amanda_hook_fn(struct sk_buff *skb, + struct nf_conn *ct, enum ip_conntrack_info ctinfo, unsigned int protoff, unsigned int matchoff, diff --git a/include/linux/netfilter/nf_conntrack_ftp.h b/include/linux/netfilter/nf_conntrack_ftp.h index 7b62446ccec4..712702183b94 100644 --- a/include/linux/netfilter/nf_conntrack_ftp.h +++ b/include/linux/netfilter/nf_conntrack_ftp.h @@ -28,6 +28,7 @@ struct nf_ct_ftp_master { * connection we should expect. */ typedef unsigned int nf_nat_ftp_hook_fn(struct sk_buff *skb, + struct nf_conn *ct, enum ip_conntrack_info ctinfo, enum nf_ct_ftp_type type, unsigned int protoff, diff --git a/include/linux/netfilter/nf_conntrack_irc.h b/include/linux/netfilter/nf_conntrack_irc.h index ce07250afb4e..c73b3b44a0b7 100644 --- a/include/linux/netfilter/nf_conntrack_irc.h +++ b/include/linux/netfilter/nf_conntrack_irc.h @@ -10,6 +10,7 @@ typedef unsigned int nf_nat_irc_hook_fn(struct sk_buff *skb, + struct nf_conn *ct, enum ip_conntrack_info ctinfo, unsigned int protoff, unsigned int matchoff, diff --git a/include/linux/netfilter/nf_conntrack_tftp.h b/include/linux/netfilter/nf_conntrack_tftp.h index e3d1739c557d..802cb7fc19cd 100644 --- a/include/linux/netfilter/nf_conntrack_tftp.h +++ b/include/linux/netfilter/nf_conntrack_tftp.h @@ -19,6 +19,7 @@ struct tftphdr { typedef unsigned int nf_nat_tftp_hook_fn(struct sk_buff *skb, + struct nf_conn *ct, enum ip_conntrack_info ctinfo, struct nf_conntrack_expect *exp); diff --git a/net/netfilter/nf_conntrack_amanda.c b/net/netfilter/nf_conntrack_amanda.c index 06d6ec12c86d..14ae660491f3 100644 --- a/net/netfilter/nf_conntrack_amanda.c +++ b/net/netfilter/nf_conntrack_amanda.c @@ -151,7 +151,7 @@ static int amanda_help(struct sk_buff *skb, nf_nat_amanda = rcu_dereference(nf_nat_amanda_hook); if (nf_nat_amanda && ct->status & IPS_NAT_MASK) - ret = nf_nat_amanda(skb, ctinfo, protoff, + ret = nf_nat_amanda(skb, ct, ctinfo, protoff, off - dataoff, len, exp); else if (nf_ct_expect_related(exp, 0) != 0) { nf_ct_helper_log(skb, ct, "cannot add expectation"); diff --git a/net/netfilter/nf_conntrack_ftp.c b/net/netfilter/nf_conntrack_ftp.c index f3944598c172..f4fe13fd0e70 100644 --- a/net/netfilter/nf_conntrack_ftp.c +++ b/net/netfilter/nf_conntrack_ftp.c @@ -515,7 +515,7 @@ static int help(struct sk_buff *skb, * (possibly changed) expectation itself. */ nf_nat_ftp = rcu_dereference(nf_nat_ftp_hook); if (nf_nat_ftp && ct->status & IPS_NAT_MASK) - ret = nf_nat_ftp(skb, ctinfo, search[dir][i].ftptype, + ret = nf_nat_ftp(skb, ct, ctinfo, search[dir][i].ftptype, protoff, matchoff, matchlen, exp); else { /* Can't expect this? Best to drop packet now. */ diff --git a/net/netfilter/nf_conntrack_irc.c b/net/netfilter/nf_conntrack_irc.c index 4e6bafe41437..92360963757a 100644 --- a/net/netfilter/nf_conntrack_irc.c +++ b/net/netfilter/nf_conntrack_irc.c @@ -231,7 +231,7 @@ static int help(struct sk_buff *skb, unsigned int protoff, nf_nat_irc = rcu_dereference(nf_nat_irc_hook); if (nf_nat_irc && ct->status & IPS_NAT_MASK) - ret = nf_nat_irc(skb, ctinfo, protoff, + ret = nf_nat_irc(skb, ct, ctinfo, protoff, addr_beg_p - ib_ptr, addr_end_p - addr_beg_p, exp); diff --git a/net/netfilter/nf_conntrack_tftp.c b/net/netfilter/nf_conntrack_tftp.c index a69559edf9b3..e672d74a6817 100644 --- a/net/netfilter/nf_conntrack_tftp.c +++ b/net/netfilter/nf_conntrack_tftp.c @@ -69,7 +69,7 @@ static int tftp_help(struct sk_buff *skb, nf_nat_tftp = rcu_dereference(nf_nat_tftp_hook); if (nf_nat_tftp && ct->status & IPS_NAT_MASK) - ret = nf_nat_tftp(skb, ctinfo, exp); + ret = nf_nat_tftp(skb, ct, ctinfo, exp); else if (nf_ct_expect_related(exp, 0) != 0) { nf_ct_helper_log(skb, ct, "cannot add expectation"); ret = NF_DROP; diff --git a/net/netfilter/nf_nat_amanda.c b/net/netfilter/nf_nat_amanda.c index 8f1054920a85..330415809425 100644 --- a/net/netfilter/nf_nat_amanda.c +++ b/net/netfilter/nf_nat_amanda.c @@ -26,6 +26,7 @@ static struct nf_conntrack_nat_helper nat_helper_amanda = NF_CT_NAT_HELPER_INIT(NAT_HELPER_NAME); static unsigned int help(struct sk_buff *skb, + struct nf_conn *ct, enum ip_conntrack_info ctinfo, unsigned int protoff, unsigned int matchoff, @@ -46,15 +47,15 @@ static unsigned int help(struct sk_buff *skb, /* Try to get same port: if not, try to change it. */ port = nf_nat_exp_find_port(exp, ntohs(exp->saved_proto.tcp.port)); if (port == 0) { - nf_ct_helper_log(skb, exp->master, "all ports in use"); + nf_ct_helper_log(skb, ct, "all ports in use"); return NF_DROP; } snprintf(buffer, sizeof(buffer), "%u", port); - if (!nf_nat_mangle_udp_packet(skb, exp->master, ctinfo, + if (!nf_nat_mangle_udp_packet(skb, ct, ctinfo, protoff, matchoff, matchlen, buffer, strlen(buffer))) { - nf_ct_helper_log(skb, exp->master, "cannot mangle packet"); + nf_ct_helper_log(skb, ct, "cannot mangle packet"); nf_ct_unexpect_related(exp); return NF_DROP; } diff --git a/net/netfilter/nf_nat_ftp.c b/net/netfilter/nf_nat_ftp.c index c92a436d9c48..25d20e2970ae 100644 --- a/net/netfilter/nf_nat_ftp.c +++ b/net/netfilter/nf_nat_ftp.c @@ -61,6 +61,7 @@ static int nf_nat_ftp_fmt_cmd(struct nf_conn *ct, enum nf_ct_ftp_type type, /* So, this packet has hit the connection tracking matching code. Mangle it, and change the expectation to match the new version. */ static unsigned int nf_nat_ftp(struct sk_buff *skb, + struct nf_conn *ct, enum ip_conntrack_info ctinfo, enum nf_ct_ftp_type type, unsigned int protoff, @@ -71,7 +72,6 @@ static unsigned int nf_nat_ftp(struct sk_buff *skb, union nf_inet_addr newaddr; u_int16_t port; int dir = CTINFO2DIR(ctinfo); - struct nf_conn *ct = exp->master; char buffer[sizeof("|1||65535|") + INET6_ADDRSTRLEN]; unsigned int buflen; @@ -88,7 +88,7 @@ static unsigned int nf_nat_ftp(struct sk_buff *skb, port = nf_nat_exp_find_port(exp, ntohs(exp->saved_proto.tcp.port)); if (port == 0) { - nf_ct_helper_log(skb, exp->master, "all ports in use"); + nf_ct_helper_log(skb, ct, "all ports in use"); return NF_DROP; } diff --git a/net/netfilter/nf_nat_irc.c b/net/netfilter/nf_nat_irc.c index 19c4fcc60c50..89b31fe932ba 100644 --- a/net/netfilter/nf_nat_irc.c +++ b/net/netfilter/nf_nat_irc.c @@ -30,6 +30,7 @@ static struct nf_conntrack_nat_helper nat_helper_irc = NF_CT_NAT_HELPER_INIT(NAT_HELPER_NAME); static unsigned int help(struct sk_buff *skb, + struct nf_conn *ct, enum ip_conntrack_info ctinfo, unsigned int protoff, unsigned int matchoff, @@ -37,7 +38,6 @@ static unsigned int help(struct sk_buff *skb, struct nf_conntrack_expect *exp) { char buffer[sizeof("4294967296 65635")]; - struct nf_conn *ct = exp->master; union nf_inet_addr newaddr; u_int16_t port; diff --git a/net/netfilter/nf_nat_tftp.c b/net/netfilter/nf_nat_tftp.c index 1a591132d6eb..7121e6704f34 100644 --- a/net/netfilter/nf_nat_tftp.c +++ b/net/netfilter/nf_nat_tftp.c @@ -21,17 +21,16 @@ static struct nf_conntrack_nat_helper nat_helper_tftp = NF_CT_NAT_HELPER_INIT(NAT_HELPER_NAME); static unsigned int help(struct sk_buff *skb, + struct nf_conn *ct, enum ip_conntrack_info ctinfo, struct nf_conntrack_expect *exp) { - const struct nf_conn *ct = exp->master; - exp->saved_proto.udp.port = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.udp.port; exp->dir = IP_CT_DIR_REPLY; exp->expectfn = nf_nat_follow_master; if (nf_ct_expect_related(exp, 0) != 0) { - nf_ct_helper_log(skb, exp->master, "cannot add expectation"); + nf_ct_helper_log(skb, ct, "cannot add expectation"); return NF_DROP; } return NF_ACCEPT; From 874f455c3a2954b5b56fbedf97da39c34df9dc0e Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Mon, 13 Jul 2026 18:06:58 +0200 Subject: [PATCH 0558/1433] netfilter: nf_conntrack_expect: store event cache in expectation Store the event cache in the expectation instead of accessing the exp->master cache, as a step forward towards turning the exp->master into a cookie. Signed-off-by: Pablo Neira Ayuso --- include/net/netfilter/nf_conntrack_expect.h | 3 +++ net/netfilter/nf_conntrack_broadcast.c | 6 ++++++ net/netfilter/nf_conntrack_ecache.c | 7 +------ net/netfilter/nf_conntrack_expect.c | 5 +++++ net/netfilter/nf_conntrack_netlink.c | 5 +++++ 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/include/net/netfilter/nf_conntrack_expect.h b/include/net/netfilter/nf_conntrack_expect.h index c024345c9bd8..5d0f5b2f12a7 100644 --- a/include/net/netfilter/nf_conntrack_expect.h +++ b/include/net/netfilter/nf_conntrack_expect.h @@ -42,6 +42,9 @@ struct nf_conntrack_expect { /* Expectation class */ unsigned int class; + /* Event filter mask */ + u16 event_mask; + /* Function to call after setup and insertion */ void (*expectfn)(struct nf_conn *new, struct nf_conntrack_expect *this); diff --git a/net/netfilter/nf_conntrack_broadcast.c b/net/netfilter/nf_conntrack_broadcast.c index 6ff954f1bfb8..0922e30b6ab0 100644 --- a/net/netfilter/nf_conntrack_broadcast.c +++ b/net/netfilter/nf_conntrack_broadcast.c @@ -14,6 +14,7 @@ #include #include #include +#include int nf_conntrack_broadcast_help(struct sk_buff *skb, struct nf_conn *ct, @@ -27,6 +28,7 @@ int nf_conntrack_broadcast_help(struct sk_buff *skb, struct rtable *rt = skb_rtable(skb); struct in_device *in_dev; struct nf_conn_help *help = nfct_help(ct); + struct nf_conntrack_ecache *ecache; __be32 mask = 0; if (!help) @@ -79,6 +81,10 @@ int nf_conntrack_broadcast_help(struct sk_buff *skb, #ifdef CONFIG_NF_CONNTRACK_ZONES exp->zone = ct->zone; #endif + ecache = nf_ct_ecache_find(ct); + if (ecache) + exp->event_mask = ecache->expmask; + nf_ct_expect_related(exp, 0); nf_ct_expect_put(exp); diff --git a/net/netfilter/nf_conntrack_ecache.c b/net/netfilter/nf_conntrack_ecache.c index cc8d8e85169f..fb731ee235d8 100644 --- a/net/netfilter/nf_conntrack_ecache.c +++ b/net/netfilter/nf_conntrack_ecache.c @@ -245,7 +245,6 @@ void nf_ct_expect_event_report(enum ip_conntrack_expect_events event, { struct net *net = nf_ct_exp_net(exp); struct nf_ct_event_notifier *notify; - struct nf_conntrack_ecache *e; lockdep_nfct_expect_lock_held(); @@ -254,11 +253,7 @@ void nf_ct_expect_event_report(enum ip_conntrack_expect_events event, if (!notify) goto out_unlock; - e = nf_ct_ecache_find(exp->master); - if (!e) - goto out_unlock; - - if (e->expmask & (1 << event)) { + if (exp->event_mask & (1 << event)) { struct nf_exp_event item = { .exp = exp, .portid = portid, diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c index 7ae68d60586a..cd9af3620af5 100644 --- a/net/netfilter/nf_conntrack_expect.c +++ b/net/netfilter/nf_conntrack_expect.c @@ -330,6 +330,7 @@ void nf_ct_expect_init(struct nf_conntrack_expect *exp, unsigned int class, struct nf_conntrack_helper *helper = NULL; struct nf_conn *ct = exp->master; struct net *net = read_pnet(&ct->ct_net); + struct nf_conntrack_ecache *ecache; struct nf_conn_help *help; int len; @@ -342,6 +343,10 @@ void nf_ct_expect_init(struct nf_conntrack_expect *exp, unsigned int class, exp->class = class; exp->expectfn = NULL; + ecache = nf_ct_ecache_find(ct); + if (ecache) + exp->event_mask = ecache->expmask; + help = nfct_help(ct); if (help) helper = rcu_dereference(help->helper); diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c index 31cbb1b55b9e..fc3f60099af3 100644 --- a/net/netfilter/nf_conntrack_netlink.c +++ b/net/netfilter/nf_conntrack_netlink.c @@ -3524,6 +3524,7 @@ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct, { struct net *net = read_pnet(&ct->ct_net); struct nf_conntrack_helper *helper; + struct nf_conntrack_ecache *ecache; struct nf_conntrack_expect *exp; struct nf_conn_help *help; u32 class = 0; @@ -3575,6 +3576,10 @@ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct, exp->mask.src.u3 = mask->src.u3; exp->mask.src.u.all = mask->src.u.all; + ecache = nf_ct_ecache_find(ct); + if (ecache) + exp->event_mask = ecache->expmask; + if (cda[CTA_EXPECT_NAT]) { err = ctnetlink_parse_expect_nat(cda[CTA_EXPECT_NAT], exp, nf_ct_l3num(ct)); From cca2780b61947fd27ec621541edd0902e193a609 Mon Sep 17 00:00:00 2001 From: Subasri S Date: Thu, 16 Jul 2026 19:37:10 +0530 Subject: [PATCH 0559/1433] ipvs: use type-safe allocation helpers in ip_vs_rht_alloc As per Documentation/process/deprecated.rst, open-coded kmalloc assignments for struct objects are deprecated. Replace kzalloc(sizeof(*ptr), GFP_KERNEL) with kzalloc_obj() and kvmalloc_array(n, sizeof(*ptr), GFP_KERNEL) with kvmalloc_objs() in ip_vs_rht_alloc(). Compile tested with CONFIG_IP_VS=y and runtime tested using tools/testing/selftests/net/netfilter/ipvs.sh on x86_64/QEMU. Signed-off-by: Subasri S Reviewed-by: Phil Sutter Acked-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipvs/ip_vs_core.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c index bafab93451d0..a896bfb53f07 100644 --- a/net/netfilter/ipvs/ip_vs_core.c +++ b/net/netfilter/ipvs/ip_vs_core.c @@ -176,7 +176,7 @@ void ip_vs_rht_rcu_free(struct rcu_head *head) struct ip_vs_rht *ip_vs_rht_alloc(int buckets, int scounts, int locks) { - struct ip_vs_rht *t = kzalloc(sizeof(*t), GFP_KERNEL); + struct ip_vs_rht *t = kzalloc_obj(*t); int i; if (!t) @@ -186,7 +186,7 @@ struct ip_vs_rht *ip_vs_rht_alloc(int buckets, int scounts, int locks) scounts = min(scounts, buckets); scounts = min(scounts, ml); - t->seqc = kvmalloc_array(scounts, sizeof(*t->seqc), GFP_KERNEL); + t->seqc = kvmalloc_objs(*t->seqc, scounts); if (!t->seqc) goto err; for (i = 0; i < scounts; i++) @@ -194,8 +194,7 @@ struct ip_vs_rht *ip_vs_rht_alloc(int buckets, int scounts, int locks) if (locks) { locks = min(locks, scounts); - t->lock = kvmalloc_array(locks, sizeof(*t->lock), - GFP_KERNEL); + t->lock = kvmalloc_objs(*t->lock, locks); if (!t->lock) goto err; for (i = 0; i < locks; i++) @@ -203,7 +202,7 @@ struct ip_vs_rht *ip_vs_rht_alloc(int buckets, int scounts, int locks) } } - t->buckets = kvmalloc_array(buckets, sizeof(*t->buckets), GFP_KERNEL); + t->buckets = kvmalloc_objs(*t->buckets, buckets); if (!t->buckets) goto err; for (i = 0; i < buckets; i++) From be6f0d0bae229ebd03e9bfe736f78e2d6b35885f Mon Sep 17 00:00:00 2001 From: "T.J. Mercier" Date: Wed, 22 Jul 2026 13:54:42 -0700 Subject: [PATCH 0560/1433] selftests: drv-net: ncdevmem: Open /dev/udmabuf O_RDONLY Write permissions on the /dev/udmabuf device file are not required to issue ioctls and allocate udmabufs. Applications should be opening this file as O_RDONLY. The BPF dmabuf_iter selftest already does this. [1] Users are pointing to these selftests as examples of how use udmabuf, and encountering permission errors on systems where write permissions are not available on /dev/udmabuf. Apply the principle of least privilege to selftests which use udmabuf by removing the write access mode from drivers/net/hw/ncdevmem.c. [1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/prog_tests/dmabuf_iter.c?h=v7.1#n49 Signed-off-by: T.J. Mercier Reviewed-by: Bobby Eshleman Link: https://patch.msgid.link/20260722205442.1894665-1-tjmercier@google.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/hw/ncdevmem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/drivers/net/hw/ncdevmem.c b/tools/testing/selftests/drivers/net/hw/ncdevmem.c index d96e8a3b5a65..ffe1d5c1fa4e 100644 --- a/tools/testing/selftests/drivers/net/hw/ncdevmem.c +++ b/tools/testing/selftests/drivers/net/hw/ncdevmem.c @@ -150,7 +150,7 @@ static struct memory_buffer *udmabuf_alloc(size_t size) ctx->size = size; - ctx->devfd = open("/dev/udmabuf", O_RDWR); + ctx->devfd = open("/dev/udmabuf", O_RDONLY); if (ctx->devfd < 0) { pr_err("[skip,no-udmabuf: Unable to access DMA buffer device file]"); goto err_free_ctx; From bf8cdde4ef35b47cff5611a2a7062ac6a64fa7ff Mon Sep 17 00:00:00 2001 From: Daniil Agalakov Date: Wed, 15 Jul 2026 15:58:48 +0300 Subject: [PATCH 0561/1433] net: hns: use u32 for register offset in RCB TX coalescing In both hns_rcb_get_tx_coalesced_frames() and hns_rcb_set_tx_coalesced_frames(), the local variable reg holds a register offset passed to dsaf_read_dev() or dsaf_write_dev(). Register offsets on this hardware are 32-bit values. Use u32 for reg to match the register access interfaces and avoid implying that 64-bit offsets are supported. Signed-off-by: Daniil Agalakov Signed-off-by: Daniil Iskhakov Link: https://patch.msgid.link/20260715125856.19346-1-dish@amicon.ru Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/hisilicon/hns/hns_dsaf_rcb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/hisilicon/hns/hns_dsaf_rcb.c b/drivers/net/ethernet/hisilicon/hns/hns_dsaf_rcb.c index 635b3a95dd82..3c4e4e8ca140 100644 --- a/drivers/net/ethernet/hisilicon/hns/hns_dsaf_rcb.c +++ b/drivers/net/ethernet/hisilicon/hns/hns_dsaf_rcb.c @@ -563,7 +563,7 @@ u32 hns_rcb_get_rx_coalesced_frames( u32 hns_rcb_get_tx_coalesced_frames( struct rcb_common_cb *rcb_common, u32 port_idx) { - u64 reg; + u32 reg; reg = RCB_CFG_PKTLINE_REG + (port_idx + HNS_RCB_TX_PKTLINE_OFFSET) * 4; return dsaf_read_dev(rcb_common, reg); @@ -634,7 +634,7 @@ int hns_rcb_set_tx_coalesced_frames( { u32 old_waterline = hns_rcb_get_tx_coalesced_frames(rcb_common, port_idx); - u64 reg; + u32 reg; if (coalesced_frames == old_waterline) return 0; From e2834100751ab80b32a29390fac4c26660b86a9b Mon Sep 17 00:00:00 2001 From: Satheesh Paul A Date: Wed, 15 Jul 2026 12:50:34 +0530 Subject: [PATCH 0562/1433] octeontx2-af: add support for custom L2 header Add packet parsing support for custom L2 headers. Also add support to include a field from the custom header for flow tag generation. Introduce a new flow key type NIX_FLOW_KEY_TYPE_CH_LEN_90B which maps to the NPC_LT_LA_CUSTOM_L2_90B_ETHER layer type. This extracts a 2-byte field at a 24-byte offset in layer A to be used in flow tag generation. Signed-off-by: Satheesh Paul A Signed-off-by: Nitin Shetty J Link: https://patch.msgid.link/20260715072035.617544-1-nshettyj@marvell.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/marvell/octeontx2/af/mbox.h | 1 + drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h index 253dfee9646e..10552e9cf519 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h +++ b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h @@ -1263,6 +1263,7 @@ struct nix_rss_flowkey_cfg { #define NIX_FLOW_KEY_TYPE_INNR_UDP BIT(15) #define NIX_FLOW_KEY_TYPE_INNR_SCTP BIT(16) #define NIX_FLOW_KEY_TYPE_INNR_ETH_DMAC BIT(17) +#define NIX_FLOW_KEY_TYPE_CH_LEN_90B BIT(18) #define NIX_FLOW_KEY_TYPE_CUSTOM0 BIT(19) #define NIX_FLOW_KEY_TYPE_VLAN BIT(20) #define NIX_FLOW_KEY_TYPE_IPV4_PROTO BIT(21) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c index c5bddd2ad920..dade7bb6deb7 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c @@ -4288,6 +4288,13 @@ static int set_flowkey_fields(struct nix_rx_flowkey_alg *alg, u32 flow_cfg) field->ltype_match = NPC_LT_LC_CUSTOM0; field->ltype_mask = 0xF; break; + case NIX_FLOW_KEY_TYPE_CH_LEN_90B: + field->lid = NPC_LID_LA; + field->hdr_offset = 24; + field->bytesm1 = 1; /* 2 Bytes*/ + field->ltype_match = NPC_LT_LA_CUSTOM_L2_90B_ETHER; + field->ltype_mask = 0xF; + break; case NIX_FLOW_KEY_TYPE_VLAN: field->lid = NPC_LID_LB; field->hdr_offset = 2; /* Skip TPID (2-bytes) */ From 04026c998c24ac47eb76886b9790c5710b603eb4 Mon Sep 17 00:00:00 2001 From: Weimin Xiong Date: Fri, 17 Jul 2026 10:25:37 +0800 Subject: [PATCH 0563/1433] net/rds: use krealloc_array() for iovector growth Use krealloc_array() for growing the RDS iovector array. This makes the array allocation overflow-safe and derives the element size from the array pointer. Reviewed-by: Allison Henderson Signed-off-by: Weimin Xiong Link: https://patch.msgid.link/20260717022537.331863-1-xiongwm2026@163.com Signed-off-by: Jakub Kicinski --- net/rds/send.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/net/rds/send.c b/net/rds/send.c index 68be1bf0e0ad..6a567c97a999 100644 --- a/net/rds/send.c +++ b/net/rds/send.c @@ -971,11 +971,8 @@ static int rds_rm_size(struct msghdr *msg, int num_sgs, return -EINVAL; if (vct->indx >= vct->len) { vct->len += vct->incr; - tmp_iov = - krealloc(vct->vec, - vct->len * - sizeof(struct rds_iov_vector), - GFP_KERNEL); + tmp_iov = krealloc_array(vct->vec, vct->len, + sizeof(*vct->vec), GFP_KERNEL); if (!tmp_iov) { vct->len -= vct->incr; return -ENOMEM; From 2557834ad0ff36fe3ec220bf1d7e6098265a326d Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Mon, 20 Jul 2026 09:43:03 +0800 Subject: [PATCH 0564/1433] net: enetc: extract common helpers for MAC promiscuous mode setting The PSIPMMR (Port Station Interface Promiscuous MAC Mode Register) in ENETC v4 has the same bit layout as the PSIPMR register in ENETC v1: bit n controls unicast promiscuous mode for SI n, and bit (n + 16) controls multicast promiscuous mode for SI n. The only difference between the two hardware generations is the register address offset. Since the register functionality is identical, the MAC promiscuous mode setting code can be shared between ENETC v1 and v4 drivers. Rename ENETC_PSIPMR to ENETC_PSIPMMR in enetc_hw.h to match the actual register name used in the reference manual, and extract two new common helper functions, enetc_set_si_uc_promisc() and enetc_set_si_mc_promisc(), into enetc_pf_common.c. These helpers select the correct register offset based on the hardware revision via is_enetc_rev1(). Remove the v4-specific enetc4_pf_set_si_mac_promisc() function from enetc4_pf.c and the duplicate PSIPMMR_SI_MAC_UP/MP macro definitions from enetc4_hw.h, as they are now superseded by the shared code. Signed-off-by: Wei Fang Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260720014317.1059359-2-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/freescale/enetc/enetc4_hw.h | 2 - .../net/ethernet/freescale/enetc/enetc4_pf.c | 21 +-------- .../ethernet/freescale/enetc/enetc_ethtool.c | 2 +- .../net/ethernet/freescale/enetc/enetc_hw.h | 7 +-- .../net/ethernet/freescale/enetc/enetc_pf.c | 11 ++--- .../freescale/enetc/enetc_pf_common.c | 44 +++++++++++++++++++ .../freescale/enetc/enetc_pf_common.h | 2 + 7 files changed, 56 insertions(+), 33 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_hw.h b/drivers/net/ethernet/freescale/enetc/enetc4_hw.h index f18437556a0e..6a8f2ed56017 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_hw.h +++ b/drivers/net/ethernet/freescale/enetc/enetc4_hw.h @@ -69,8 +69,6 @@ /* Port Station interface promiscuous MAC mode register */ #define ENETC4_PSIPMMR 0x200 -#define PSIPMMR_SI_MAC_UP(a) BIT(a) /* a = SI index */ -#define PSIPMMR_SI_MAC_MP(a) BIT((a) + 16) /* Port Station interface promiscuous VLAN mode register */ #define ENETC4_PSIPVMR 0x204 diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index 437a15bbb47b..304ec069654d 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c @@ -75,24 +75,6 @@ static void enetc4_pf_get_si_primary_mac(struct enetc_hw *hw, int si, put_unaligned_le16(lower, addr + 4); } -static void enetc4_pf_set_si_mac_promisc(struct enetc_hw *hw, int si, - bool uc_promisc, bool mc_promisc) -{ - u32 val = enetc_port_rd(hw, ENETC4_PSIPMMR); - - if (uc_promisc) - val |= PSIPMMR_SI_MAC_UP(si); - else - val &= ~PSIPMMR_SI_MAC_UP(si); - - if (mc_promisc) - val |= PSIPMMR_SI_MAC_MP(si); - else - val &= ~PSIPMMR_SI_MAC_MP(si); - - enetc_port_wr(hw, ENETC4_PSIPMMR, val); -} - static void enetc4_pf_set_si_uc_hash_filter(struct enetc_hw *hw, int si, u64 hash) { @@ -515,7 +497,8 @@ static void enetc4_psi_do_set_rx_mode(struct work_struct *work) type = ENETC_MAC_FILTER_TYPE_ALL; } - enetc4_pf_set_si_mac_promisc(hw, 0, uc_promisc, mc_promisc); + enetc_set_si_uc_promisc(si, 0, uc_promisc); + enetc_set_si_mc_promisc(si, 0, mc_promisc); if (uc_promisc) { enetc4_pf_set_si_uc_hash_filter(hw, 0, 0); diff --git a/drivers/net/ethernet/freescale/enetc/enetc_ethtool.c b/drivers/net/ethernet/freescale/enetc/enetc_ethtool.c index 71f376ef1be1..07b7832f2427 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_ethtool.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_ethtool.c @@ -29,7 +29,7 @@ static const u32 enetc_rxbdr_regs[] = { }; static const u32 enetc_port_regs[] = { - ENETC_PMR, ENETC_PSR, ENETC_PSIPMR, ENETC_PSIPMAR0(0), + ENETC_PMR, ENETC_PSR, ENETC_PSIPMMR, ENETC_PSIPMAR0(0), ENETC_PSIPMAR1(0), ENETC_PTXMBAR, ENETC_PCAPR0, ENETC_PCAPR1, ENETC_PSICFGR0(0), ENETC_PRFSCAPR, ENETC_PTCMSDUR(0), ENETC_PM0_CMD_CFG, ENETC_PM0_MAXFRM, ENETC_PM0_IF_MODE diff --git a/drivers/net/ethernet/freescale/enetc/enetc_hw.h b/drivers/net/ethernet/freescale/enetc/enetc_hw.h index bf99b65d7598..66bfda60da9c 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_hw.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_hw.h @@ -180,9 +180,10 @@ enum enetc_bdr_type {TX, RX}; #define ENETC_PMR_PSPEED_1000M BIT(9) #define ENETC_PMR_PSPEED_2500M BIT(10) #define ENETC_PSR 0x0004 /* RO */ -#define ENETC_PSIPMR 0x0018 -#define ENETC_PSIPMR_SET_UP(n) BIT(n) /* n = SI index */ -#define ENETC_PSIPMR_SET_MP(n) BIT((n) + 16) +#define ENETC_PSIPMMR 0x0018 +#define PSIPMMR_SI_MAC_UP(n) BIT(n) /* n = SI index */ +#define PSIPMMR_SI_MAC_MP(n) BIT((n) + 16) + #define ENETC_PSIPVMR 0x001c #define ENETC_VLAN_PROMISC_MAP_ALL 0x7 #define ENETC_PSIPVMR_SET_VP(simap) ((simap) & 0x7) diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c b/drivers/net/ethernet/freescale/enetc/enetc_pf.c index 2d687bb8c3a0..a97d2e2dd07b 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c @@ -159,21 +159,17 @@ static void enetc_pf_set_rx_mode(struct net_device *ndev) { struct enetc_ndev_priv *priv = netdev_priv(ndev); struct enetc_pf *pf = enetc_si_priv(priv->si); - struct enetc_hw *hw = &priv->si->hw; bool uprom = false, mprom = false; struct enetc_mac_filter *filter; struct netdev_hw_addr *ha; - u32 psipmr = 0; bool em; if (ndev->flags & IFF_PROMISC) { /* enable promisc mode for SI0 (PF) */ - psipmr = ENETC_PSIPMR_SET_UP(0) | ENETC_PSIPMR_SET_MP(0); uprom = true; mprom = true; } else if (ndev->flags & IFF_ALLMULTI) { /* enable multi cast promisc mode for SI0 (PF) */ - psipmr = ENETC_PSIPMR_SET_MP(0); mprom = true; } @@ -211,9 +207,8 @@ static void enetc_pf_set_rx_mode(struct net_device *ndev) /* update PF entries */ enetc_sync_mac_filters(pf); - psipmr |= enetc_port_rd(hw, ENETC_PSIPMR) & - ~(ENETC_PSIPMR_SET_UP(0) | ENETC_PSIPMR_SET_MP(0)); - enetc_port_wr(hw, ENETC_PSIPMR, psipmr); + enetc_set_si_uc_promisc(priv->si, 0, uprom); + enetc_set_si_mc_promisc(priv->si, 0, mprom); } static void enetc_set_loopback(struct net_device *ndev, bool en) @@ -474,7 +469,7 @@ static void enetc_configure_port(struct enetc_pf *pf) pf->vlan_promisc_simap = ENETC_VLAN_PROMISC_MAP_ALL; enetc_set_vlan_promisc(hw, pf->vlan_promisc_simap); - enetc_port_wr(hw, ENETC_PSIPMR, 0); + enetc_port_wr(hw, ENETC_PSIPMMR, 0); /* enable port */ enetc_port_wr(hw, ENETC_PMR, ENETC_PMR_EN); diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c index 6e5d2f869915..b0c0dc668e34 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c @@ -87,6 +87,50 @@ int enetc_setup_mac_addresses(struct device_node *np, struct enetc_pf *pf) } EXPORT_SYMBOL_GPL(enetc_setup_mac_addresses); +void enetc_set_si_uc_promisc(struct enetc_si *si, int si_id, bool promisc) +{ + struct enetc_hw *hw = &si->hw; + int psipmmr_off; + u32 val; + + if (is_enetc_rev1(si)) + psipmmr_off = ENETC_PSIPMMR; + else + psipmmr_off = ENETC4_PSIPMMR; + + val = enetc_port_rd(hw, psipmmr_off); + + if (promisc) + val |= PSIPMMR_SI_MAC_UP(si_id); + else + val &= ~PSIPMMR_SI_MAC_UP(si_id); + + enetc_port_wr(hw, psipmmr_off, val); +} +EXPORT_SYMBOL_GPL(enetc_set_si_uc_promisc); + +void enetc_set_si_mc_promisc(struct enetc_si *si, int si_id, bool promisc) +{ + struct enetc_hw *hw = &si->hw; + int psipmmr_off; + u32 val; + + if (is_enetc_rev1(si)) + psipmmr_off = ENETC_PSIPMMR; + else + psipmmr_off = ENETC4_PSIPMMR; + + val = enetc_port_rd(hw, psipmmr_off); + + if (promisc) + val |= PSIPMMR_SI_MAC_MP(si_id); + else + val &= ~PSIPMMR_SI_MAC_MP(si_id); + + enetc_port_wr(hw, psipmmr_off, val); +} +EXPORT_SYMBOL_GPL(enetc_set_si_mc_promisc); + void enetc_pf_netdev_setup(struct enetc_si *si, struct net_device *ndev, const struct net_device_ops *ndev_ops) { diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h index 57d2e0ebd2b0..a619fb8fed9c 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h @@ -17,6 +17,8 @@ void enetc_set_default_rss_key(struct enetc_pf *pf); int enetc_vlan_rx_add_vid(struct net_device *ndev, __be16 prot, u16 vid); int enetc_vlan_rx_del_vid(struct net_device *ndev, __be16 prot, u16 vid); int enetc_init_sriov_resources(struct enetc_pf *pf); +void enetc_set_si_uc_promisc(struct enetc_si *si, int si_id, bool promisc); +void enetc_set_si_mc_promisc(struct enetc_si *si, int si_id, bool promisc); static inline u16 enetc_get_ip_revision(struct enetc_hw *hw) { From 0ce10770963e7b4bf252fe0f0283326504bcbb36 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Mon, 20 Jul 2026 09:43:04 +0800 Subject: [PATCH 0565/1433] net: enetc: extract common helpers for MAC hash filter configuration The PSIUMHFR and PSIMMHFR registers in ENETC v4 have the same bit layout as in ENETC v1. The only difference between the two hardware generations is the register address offsets. Since the register functionality is identical, the MAC hash filter configuration code can be shared between the ENETC v1 and v4 drivers. Extract two new common helper functions, enetc_set_si_uc_hash_filter() and enetc_set_si_mc_hash_filter(), into enetc_pf_common.c. These helpers select the correct register offset based on the hardware revision via is_enetc_rev1(). Remove v1-specific enetc_clear_mac_ht_flt() and enetc_set_mac_ht_flt() from enetc_pf.c, and v4-specific enetc4_pf_set_si_uc_hash_filter() and enetc4_pf_set_si_mc_hash_filter() from enetc4_pf.c, as they are now superseded by the shared implementations. Signed-off-by: Wei Fang Link: https://patch.msgid.link/20260720014317.1059359-3-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/freescale/enetc/enetc4_pf.c | 43 +++++++--------- .../net/ethernet/freescale/enetc/enetc_pf.c | 51 +++++-------------- .../freescale/enetc/enetc_pf_common.c | 40 +++++++++++++++ .../freescale/enetc/enetc_pf_common.h | 2 + 4 files changed, 73 insertions(+), 63 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index 304ec069654d..48a74db90ed5 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c @@ -75,20 +75,6 @@ static void enetc4_pf_get_si_primary_mac(struct enetc_hw *hw, int si, put_unaligned_le16(lower, addr + 4); } -static void enetc4_pf_set_si_uc_hash_filter(struct enetc_hw *hw, int si, - u64 hash) -{ - enetc_port_wr(hw, ENETC4_PSIUMHFR0(si), lower_32_bits(hash)); - enetc_port_wr(hw, ENETC4_PSIUMHFR1(si), upper_32_bits(hash)); -} - -static void enetc4_pf_set_si_mc_hash_filter(struct enetc_hw *hw, int si, - u64 hash) -{ - enetc_port_wr(hw, ENETC4_PSIMMHFR0(si), lower_32_bits(hash)); - enetc_port_wr(hw, ENETC4_PSIMMHFR1(si), upper_32_bits(hash)); -} - static void enetc4_pf_set_loopback(struct net_device *ndev, bool en) { struct enetc_ndev_priv *priv = netdev_priv(ndev); @@ -147,11 +133,12 @@ static int enetc4_pf_set_uc_exact_filter(struct enetc_pf *pf) int max_num_mfe = pf->caps.mac_filter_num; struct enetc_mac_filter mac_filter = {}; struct net_device *ndev = pf->si->ndev; - struct enetc_hw *hw = &pf->si->hw; struct enetc_mac_addr *mac_tbl; + struct enetc_si *si = pf->si; struct netdev_hw_addr *ha; int i = 0, err; int mac_cnt; + u64 hash; netif_addr_lock_bh(ndev); @@ -159,7 +146,7 @@ static int enetc4_pf_set_uc_exact_filter(struct enetc_pf *pf) if (!mac_cnt) { netif_addr_unlock_bh(ndev); /* clear both MAC hash and exact filters */ - enetc4_pf_set_si_uc_hash_filter(hw, 0, 0); + enetc_set_si_uc_hash_filter(si, 0, 0); enetc4_pf_clear_maft_entries(pf); return 0; @@ -186,11 +173,13 @@ static int enetc4_pf_set_uc_exact_filter(struct enetc_pf *pf) /* Set temporary unicast hash filters in case of Rx loss when * updating MAC address filter table */ - enetc4_pf_set_si_uc_hash_filter(hw, 0, *mac_filter.mac_hash_table); + bitmap_to_arr64(&hash, mac_filter.mac_hash_table, + ENETC_MADDR_HASH_TBL_SZ); + enetc_set_si_uc_hash_filter(si, 0, hash); enetc4_pf_clear_maft_entries(pf); if (!enetc4_pf_add_maft_entries(pf, mac_tbl, i)) - enetc4_pf_set_si_uc_hash_filter(hw, 0, 0); + enetc_set_si_uc_hash_filter(si, 0, 0); kfree(mac_tbl); @@ -206,8 +195,9 @@ static void enetc4_pf_set_mac_hash_filter(struct enetc_pf *pf, int type) { struct net_device *ndev = pf->si->ndev; struct enetc_mac_filter *mac_filter; - struct enetc_hw *hw = &pf->si->hw; + struct enetc_si *si = pf->si; struct netdev_hw_addr *ha; + u64 hash; netif_addr_lock_bh(ndev); if (type & ENETC_MAC_FILTER_TYPE_UC) { @@ -216,8 +206,9 @@ static void enetc4_pf_set_mac_hash_filter(struct enetc_pf *pf, int type) netdev_for_each_uc_addr(ha, ndev) enetc_add_mac_addr_ht_filter(mac_filter, ha->addr); - enetc4_pf_set_si_uc_hash_filter(hw, 0, - *mac_filter->mac_hash_table); + bitmap_to_arr64(&hash, mac_filter->mac_hash_table, + ENETC_MADDR_HASH_TBL_SZ); + enetc_set_si_uc_hash_filter(si, 0, hash); } if (type & ENETC_MAC_FILTER_TYPE_MC) { @@ -226,8 +217,9 @@ static void enetc4_pf_set_mac_hash_filter(struct enetc_pf *pf, int type) netdev_for_each_mc_addr(ha, ndev) enetc_add_mac_addr_ht_filter(mac_filter, ha->addr); - enetc4_pf_set_si_mc_hash_filter(hw, 0, - *mac_filter->mac_hash_table); + bitmap_to_arr64(&hash, mac_filter->mac_hash_table, + ENETC_MADDR_HASH_TBL_SZ); + enetc_set_si_mc_hash_filter(si, 0, hash); } netif_addr_unlock_bh(ndev); } @@ -480,7 +472,6 @@ static void enetc4_psi_do_set_rx_mode(struct work_struct *work) struct enetc_si *si = container_of(work, struct enetc_si, rx_mode_task); struct enetc_pf *pf = enetc_si_priv(si); struct net_device *ndev = si->ndev; - struct enetc_hw *hw = &si->hw; bool uc_promisc = false; bool mc_promisc = false; int type = 0; @@ -501,12 +492,12 @@ static void enetc4_psi_do_set_rx_mode(struct work_struct *work) enetc_set_si_mc_promisc(si, 0, mc_promisc); if (uc_promisc) { - enetc4_pf_set_si_uc_hash_filter(hw, 0, 0); + enetc_set_si_uc_hash_filter(si, 0, 0); enetc4_pf_clear_maft_entries(pf); } if (mc_promisc) - enetc4_pf_set_si_mc_hash_filter(hw, 0, 0); + enetc_set_si_mc_hash_filter(si, 0, 0); /* Set new MAC filter */ enetc4_pf_set_mac_filter(pf, type); diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c b/drivers/net/ethernet/freescale/enetc/enetc_pf.c index a97d2e2dd07b..db2a800a7aaf 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c @@ -80,37 +80,6 @@ static void enetc_add_mac_addr_em_filter(struct enetc_mac_filter *filter, filter->mac_addr_cnt++; } -static void enetc_clear_mac_ht_flt(struct enetc_si *si, int si_idx, int type) -{ - bool err = si->errata & ENETC_ERR_UCMCSWP; - - if (type == UC) { - enetc_port_wr(&si->hw, ENETC_PSIUMHFR0(si_idx, err), 0); - enetc_port_wr(&si->hw, ENETC_PSIUMHFR1(si_idx), 0); - } else { /* MC */ - enetc_port_wr(&si->hw, ENETC_PSIMMHFR0(si_idx, err), 0); - enetc_port_wr(&si->hw, ENETC_PSIMMHFR1(si_idx), 0); - } -} - -static void enetc_set_mac_ht_flt(struct enetc_si *si, int si_idx, int type, - unsigned long hash) -{ - bool err = si->errata & ENETC_ERR_UCMCSWP; - - if (type == UC) { - enetc_port_wr(&si->hw, ENETC_PSIUMHFR0(si_idx, err), - lower_32_bits(hash)); - enetc_port_wr(&si->hw, ENETC_PSIUMHFR1(si_idx), - upper_32_bits(hash)); - } else { /* MC */ - enetc_port_wr(&si->hw, ENETC_PSIMMHFR0(si_idx, err), - lower_32_bits(hash)); - enetc_port_wr(&si->hw, ENETC_PSIMMHFR1(si_idx), - upper_32_bits(hash)); - } -} - static void enetc_sync_mac_filters(struct enetc_pf *pf) { struct enetc_mac_filter *f = pf->mac_filter; @@ -122,12 +91,16 @@ static void enetc_sync_mac_filters(struct enetc_pf *pf) for (i = 0; i < MADDR_TYPE; i++, f++) { bool em = (f->mac_addr_cnt == 1) && (i == UC); bool clear = !f->mac_addr_cnt; + u64 hash; if (clear) { - if (i == UC) + if (i == UC) { enetc_clear_mac_flt_entry(si, pos); + enetc_set_si_uc_hash_filter(si, 0, 0); + } else { + enetc_set_si_mc_hash_filter(si, 0, 0); + } - enetc_clear_mac_ht_flt(si, 0, i); continue; } @@ -135,7 +108,7 @@ static void enetc_sync_mac_filters(struct enetc_pf *pf) if (em) { int err; - enetc_clear_mac_ht_flt(si, 0, UC); + enetc_set_si_uc_hash_filter(si, 0, 0); err = enetc_set_mac_flt_entry(si, pos, f->mac_addr, BIT(0)); @@ -147,11 +120,15 @@ static void enetc_sync_mac_filters(struct enetc_pf *pf) err); } + bitmap_to_arr64(&hash, f->mac_hash_table, + ENETC_MADDR_HASH_TBL_SZ); /* hash table filter, clear EM filter for UC entries */ - if (i == UC) + if (i == UC) { enetc_clear_mac_flt_entry(si, pos); - - enetc_set_mac_ht_flt(si, 0, i, *f->mac_hash_table); + enetc_set_si_uc_hash_filter(si, 0, hash); + } else { + enetc_set_si_mc_hash_filter(si, 0, hash); + } } } diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c index b0c0dc668e34..3597cb81a7cc 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c @@ -131,6 +131,46 @@ void enetc_set_si_mc_promisc(struct enetc_si *si, int si_id, bool promisc) } EXPORT_SYMBOL_GPL(enetc_set_si_mc_promisc); +void enetc_set_si_uc_hash_filter(struct enetc_si *si, int si_id, u64 hash) +{ + int psiumhfr0_off, psiumhfr1_off; + struct enetc_hw *hw = &si->hw; + + if (is_enetc_rev1(si)) { + bool err = si->errata & ENETC_ERR_UCMCSWP; + + psiumhfr0_off = ENETC_PSIUMHFR0(si_id, err); + psiumhfr1_off = ENETC_PSIUMHFR1(si_id); + } else { + psiumhfr0_off = ENETC4_PSIUMHFR0(si_id); + psiumhfr1_off = ENETC4_PSIUMHFR1(si_id); + } + + enetc_port_wr(hw, psiumhfr0_off, lower_32_bits(hash)); + enetc_port_wr(hw, psiumhfr1_off, upper_32_bits(hash)); +} +EXPORT_SYMBOL_GPL(enetc_set_si_uc_hash_filter); + +void enetc_set_si_mc_hash_filter(struct enetc_si *si, int si_id, u64 hash) +{ + int psimmhfr0_off, psimmhfr1_off; + struct enetc_hw *hw = &si->hw; + + if (is_enetc_rev1(si)) { + bool err = si->errata & ENETC_ERR_UCMCSWP; + + psimmhfr0_off = ENETC_PSIMMHFR0(si_id, err); + psimmhfr1_off = ENETC_PSIMMHFR1(si_id); + } else { + psimmhfr0_off = ENETC4_PSIMMHFR0(si_id); + psimmhfr1_off = ENETC4_PSIMMHFR1(si_id); + } + + enetc_port_wr(hw, psimmhfr0_off, lower_32_bits(hash)); + enetc_port_wr(hw, psimmhfr1_off, upper_32_bits(hash)); +} +EXPORT_SYMBOL_GPL(enetc_set_si_mc_hash_filter); + void enetc_pf_netdev_setup(struct enetc_si *si, struct net_device *ndev, const struct net_device_ops *ndev_ops) { diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h index a619fb8fed9c..bf9029b0a017 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h @@ -19,6 +19,8 @@ int enetc_vlan_rx_del_vid(struct net_device *ndev, __be16 prot, u16 vid); int enetc_init_sriov_resources(struct enetc_pf *pf); void enetc_set_si_uc_promisc(struct enetc_si *si, int si_id, bool promisc); void enetc_set_si_mc_promisc(struct enetc_si *si, int si_id, bool promisc); +void enetc_set_si_uc_hash_filter(struct enetc_si *si, int si_id, u64 hash); +void enetc_set_si_mc_hash_filter(struct enetc_si *si, int si_id, u64 hash); static inline u16 enetc_get_ip_revision(struct enetc_hw *hw) { From debf0c7a34fab2c4c47fc257a97a145543b6d3aa Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Mon, 20 Jul 2026 09:43:05 +0800 Subject: [PATCH 0566/1433] net: enetc: convert ndo_set_rx_mode() to ndo_set_rx_mode_async() The current ndo_set_rx_mode() is called under netif_addr_lock spinlock with BHs disabled, which prevents drivers from sleeping. To work around this limitation, the enetc driver uses a dedicated workqueue to defer MAC address list updates to a sleepable context. Since commit 3554b4345d85 ("net: introduce ndo_set_rx_mode_async and netdev_rx_mode_work") introduced the ndo_set_rx_mode_async() callback, drivers can now handle address list updates directly in a sleepable context. Therefore, convert the enetc driver to use ndo_set_rx_mode_async() and remove the dedicated workqueue and the deferred work item accordingly. Signed-off-by: Wei Fang Link: https://patch.msgid.link/20260720014317.1059359-4-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/enetc.h | 2 - .../net/ethernet/freescale/enetc/enetc4_pf.c | 178 ++++++------------ 2 files changed, 58 insertions(+), 122 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc.h b/drivers/net/ethernet/freescale/enetc/enetc.h index 04a5dd5ea6c7..06a9f1ee0970 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc.h +++ b/drivers/net/ethernet/freescale/enetc/enetc.h @@ -324,8 +324,6 @@ struct enetc_si { const struct enetc_drvdata *drvdata; const struct enetc_si_ops *ops; - struct workqueue_struct *workqueue; - struct work_struct rx_mode_task; struct dentry *debugfs_root; struct enetc_msg_swbd msg; /* Only valid for VSI */ }; diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index 48a74db90ed5..a02b01753ff2 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c @@ -101,24 +101,23 @@ static void enetc4_pf_clear_maft_entries(struct enetc_pf *pf) } static int enetc4_pf_add_maft_entries(struct enetc_pf *pf, - struct enetc_mac_addr *mac, - int mac_cnt) + struct netdev_hw_addr_list *uc) { struct maft_entry_data maft = {}; + struct netdev_hw_addr *ha; u16 si_bit = BIT(0); - int i, err; + int err; maft.cfge.si_bitmap = cpu_to_le16(si_bit); - for (i = 0; i < mac_cnt; i++) { - ether_addr_copy(maft.keye.mac_addr, mac[i].addr); - err = ntmp_maft_add_entry(&pf->si->ntmp_user, i, &maft); - if (unlikely(err)) { - pf->num_mfe = i; + netdev_hw_addr_list_for_each(ha, uc) { + ether_addr_copy(maft.keye.mac_addr, ha->addr); + err = ntmp_maft_add_entry(&pf->si->ntmp_user, pf->num_mfe, + &maft); + if (unlikely(err)) goto clear_maft_entries; - } - } - pf->num_mfe = mac_cnt; + pf->num_mfe++; + } return 0; @@ -128,23 +127,29 @@ static int enetc4_pf_add_maft_entries(struct enetc_pf *pf, return err; } -static int enetc4_pf_set_uc_exact_filter(struct enetc_pf *pf) +static void enetc4_pf_set_uc_hash_filter(struct enetc_pf *pf, + struct netdev_hw_addr_list *uc) { - int max_num_mfe = pf->caps.mac_filter_num; - struct enetc_mac_filter mac_filter = {}; - struct net_device *ndev = pf->si->ndev; - struct enetc_mac_addr *mac_tbl; - struct enetc_si *si = pf->si; + struct enetc_mac_filter *mac_filter = &pf->mac_filter[UC]; struct netdev_hw_addr *ha; - int i = 0, err; - int mac_cnt; u64 hash; - netif_addr_lock_bh(ndev); + enetc_reset_mac_addr_filter(mac_filter); + netdev_hw_addr_list_for_each(ha, uc) + enetc_add_mac_addr_ht_filter(mac_filter, ha->addr); + + bitmap_to_arr64(&hash, mac_filter->mac_hash_table, + ENETC_MADDR_HASH_TBL_SZ); + enetc_set_si_uc_hash_filter(pf->si, 0, hash); +} + +static int enetc4_pf_set_uc_exact_filter(struct enetc_pf *pf, + struct netdev_hw_addr_list *uc) +{ + int mac_cnt = netdev_hw_addr_list_count(uc); + struct enetc_si *si = pf->si; - mac_cnt = netdev_uc_count(ndev); if (!mac_cnt) { - netif_addr_unlock_bh(ndev); /* clear both MAC hash and exact filters */ enetc_set_si_uc_hash_filter(si, 0, 0); enetc4_pf_clear_maft_entries(pf); @@ -152,79 +157,42 @@ static int enetc4_pf_set_uc_exact_filter(struct enetc_pf *pf) return 0; } - if (mac_cnt > max_num_mfe) { - err = -ENOSPC; - goto unlock_netif_addr; - } - - mac_tbl = kzalloc_objs(*mac_tbl, mac_cnt, GFP_ATOMIC); - if (!mac_tbl) { - err = -ENOMEM; - goto unlock_netif_addr; - } - - netdev_for_each_uc_addr(ha, ndev) { - enetc_add_mac_addr_ht_filter(&mac_filter, ha->addr); - ether_addr_copy(mac_tbl[i++].addr, ha->addr); - } - - netif_addr_unlock_bh(ndev); + if (mac_cnt > pf->caps.mac_filter_num) + return -ENOSPC; /* Set temporary unicast hash filters in case of Rx loss when * updating MAC address filter table */ - bitmap_to_arr64(&hash, mac_filter.mac_hash_table, - ENETC_MADDR_HASH_TBL_SZ); - enetc_set_si_uc_hash_filter(si, 0, hash); + enetc4_pf_set_uc_hash_filter(pf, uc); enetc4_pf_clear_maft_entries(pf); - if (!enetc4_pf_add_maft_entries(pf, mac_tbl, i)) + if (!enetc4_pf_add_maft_entries(pf, uc)) { + enetc_reset_mac_addr_filter(&pf->mac_filter[UC]); enetc_set_si_uc_hash_filter(si, 0, 0); - - kfree(mac_tbl); + } return 0; - -unlock_netif_addr: - netif_addr_unlock_bh(ndev); - - return err; } -static void enetc4_pf_set_mac_hash_filter(struct enetc_pf *pf, int type) +static void enetc4_pf_set_mc_hash_filter(struct enetc_pf *pf, + struct netdev_hw_addr_list *mc) { - struct net_device *ndev = pf->si->ndev; - struct enetc_mac_filter *mac_filter; - struct enetc_si *si = pf->si; + struct enetc_mac_filter *mac_filter = &pf->mac_filter[MC]; struct netdev_hw_addr *ha; u64 hash; - netif_addr_lock_bh(ndev); - if (type & ENETC_MAC_FILTER_TYPE_UC) { - mac_filter = &pf->mac_filter[UC]; - enetc_reset_mac_addr_filter(mac_filter); - netdev_for_each_uc_addr(ha, ndev) - enetc_add_mac_addr_ht_filter(mac_filter, ha->addr); + enetc_reset_mac_addr_filter(mac_filter); + netdev_hw_addr_list_for_each(ha, mc) + enetc_add_mac_addr_ht_filter(mac_filter, ha->addr); - bitmap_to_arr64(&hash, mac_filter->mac_hash_table, - ENETC_MADDR_HASH_TBL_SZ); - enetc_set_si_uc_hash_filter(si, 0, hash); - } - - if (type & ENETC_MAC_FILTER_TYPE_MC) { - mac_filter = &pf->mac_filter[MC]; - enetc_reset_mac_addr_filter(mac_filter); - netdev_for_each_mc_addr(ha, ndev) - enetc_add_mac_addr_ht_filter(mac_filter, ha->addr); - - bitmap_to_arr64(&hash, mac_filter->mac_hash_table, - ENETC_MADDR_HASH_TBL_SZ); - enetc_set_si_mc_hash_filter(si, 0, hash); - } - netif_addr_unlock_bh(ndev); + bitmap_to_arr64(&hash, mac_filter->mac_hash_table, + ENETC_MADDR_HASH_TBL_SZ); + enetc_set_si_mc_hash_filter(pf->si, 0, hash); } -static void enetc4_pf_set_mac_filter(struct enetc_pf *pf, int type) +static void enetc4_pf_set_mac_filter(struct enetc_pf *pf, int type, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) { /* Currently, the MAC address filter table (MAFT) only has 4 entries, * and multiple multicast addresses for filtering will be configured @@ -232,15 +200,16 @@ static void enetc4_pf_set_mac_filter(struct enetc_pf *pf, int type) * unicast filtering. If the number of unicast addresses exceeds the * table capacity, the MAC hash filter will be used. */ - if (type & ENETC_MAC_FILTER_TYPE_UC && enetc4_pf_set_uc_exact_filter(pf)) { + if (type & ENETC_MAC_FILTER_TYPE_UC && + enetc4_pf_set_uc_exact_filter(pf, uc)) { /* Fall back to the MAC hash filter */ - enetc4_pf_set_mac_hash_filter(pf, ENETC_MAC_FILTER_TYPE_UC); + enetc4_pf_set_uc_hash_filter(pf, uc); /* Clear the old MAC exact filter */ enetc4_pf_clear_maft_entries(pf); } if (type & ENETC_MAC_FILTER_TYPE_MC) - enetc4_pf_set_mac_hash_filter(pf, ENETC_MAC_FILTER_TYPE_MC); + enetc4_pf_set_mc_hash_filter(pf, mc); } static const struct enetc_pf_ops enetc4_pf_ops = { @@ -467,17 +436,17 @@ static void enetc4_pf_free(struct enetc_pf *pf) enetc4_free_ntmp_user(pf->si); } -static void enetc4_psi_do_set_rx_mode(struct work_struct *work) +static int enetc4_pf_set_rx_mode(struct net_device *ndev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) { - struct enetc_si *si = container_of(work, struct enetc_si, rx_mode_task); - struct enetc_pf *pf = enetc_si_priv(si); - struct net_device *ndev = si->ndev; + struct enetc_ndev_priv *priv = netdev_priv(ndev); + struct enetc_pf *pf = enetc_si_priv(priv->si); + struct enetc_si *si = priv->si; bool uc_promisc = false; bool mc_promisc = false; int type = 0; - rtnl_lock(); - if (ndev->flags & IFF_PROMISC) { uc_promisc = true; mc_promisc = true; @@ -500,17 +469,9 @@ static void enetc4_psi_do_set_rx_mode(struct work_struct *work) enetc_set_si_mc_hash_filter(si, 0, 0); /* Set new MAC filter */ - enetc4_pf_set_mac_filter(pf, type); + enetc4_pf_set_mac_filter(pf, type, uc, mc); - rtnl_unlock(); -} - -static void enetc4_pf_set_rx_mode(struct net_device *ndev) -{ - struct enetc_ndev_priv *priv = netdev_priv(ndev); - struct enetc_si *si = priv->si; - - queue_work(si->workqueue, &si->rx_mode_task); + return 0; } static int enetc4_pf_set_features(struct net_device *ndev, @@ -540,7 +501,7 @@ static const struct net_device_ops enetc4_ndev_ops = { .ndo_start_xmit = enetc_xmit, .ndo_get_stats = enetc_get_stats, .ndo_set_mac_address = enetc_pf_set_mac_addr, - .ndo_set_rx_mode = enetc4_pf_set_rx_mode, + .ndo_set_rx_mode_async = enetc4_pf_set_rx_mode, .ndo_set_features = enetc4_pf_set_features, .ndo_vlan_rx_add_vid = enetc_vlan_rx_add_vid, .ndo_vlan_rx_kill_vid = enetc_vlan_rx_del_vid, @@ -983,19 +944,6 @@ static void enetc4_link_deinit(struct enetc_ndev_priv *priv) enetc_mdiobus_destroy(pf); } -static int enetc4_psi_wq_task_init(struct enetc_si *si) -{ - char wq_name[24]; - - INIT_WORK(&si->rx_mode_task, enetc4_psi_do_set_rx_mode); - snprintf(wq_name, sizeof(wq_name), "enetc-%s", pci_name(si->pdev)); - si->workqueue = create_singlethread_workqueue(wq_name); - if (!si->workqueue) - return -ENOMEM; - - return 0; -} - static int enetc4_pf_netdev_create(struct enetc_si *si) { struct device *dev = &si->pdev->dev; @@ -1036,12 +984,6 @@ static int enetc4_pf_netdev_create(struct enetc_si *si) if (err) goto err_link_init; - err = enetc4_psi_wq_task_init(si); - if (err) { - dev_err(dev, "Failed to init workqueue\n"); - goto err_wq_init; - } - err = register_netdev(ndev); if (err) { dev_err(dev, "Failed to register netdev\n"); @@ -1051,8 +993,6 @@ static int enetc4_pf_netdev_create(struct enetc_si *si) return 0; err_reg_netdev: - destroy_workqueue(si->workqueue); -err_wq_init: enetc4_link_deinit(priv); err_link_init: enetc_free_msix(priv); @@ -1070,8 +1010,6 @@ static void enetc4_pf_netdev_destroy(struct enetc_si *si) struct net_device *ndev = si->ndev; unregister_netdev(ndev); - cancel_work(&si->rx_mode_task); - destroy_workqueue(si->workqueue); enetc4_link_deinit(priv); enetc_free_msix(priv); free_netdev(ndev); From 6228fc9c2bbe224a376c77e309bcf90158e0595c Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Mon, 20 Jul 2026 09:43:06 +0800 Subject: [PATCH 0567/1433] net: enetc: improve MAFT entry management with bitmap tracking Replace the counter-based MAFT entry tracking (num_mfe/mac_filter_num) with a bitmap (maft_eid_bitmap) stored in struct ntmp_user, which is a more appropriate place for NTMP resource management. The bitmap approach brings two improvements. First, the entry deletion in enetc4_pf_clear_maft_entries() now checks the return value of ntmp_maft_delete_entry() and only clears the corresponding bit on success, keeping hardware and software state in sync. Previously, the counter was reset unconditionally regardless of whether the hardware deletion actually succeeded. Second, entry allocation in enetc4_pf_add_maft_entries() uses ntmp_lookup_free_eid() to find available IDs dynamically, with an upfront capacity check via bitmap_weight() to avoid partial failures. The MAFT entry count is moved into ntmp_user.maft_num_entries and initialized once during enetc4_init_ntmp_user(). Helper functions enetc4_ntmp_bitmap_init() and enetc4_ntmp_bitmap_free() manage the bitmap lifetime. The debugfs show function is updated accordingly to iterate over set bits under rtnl_lock(). Signed-off-by: Wei Fang Link: https://patch.msgid.link/20260720014317.1059359-5-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- .../ethernet/freescale/enetc/enetc4_debugfs.c | 30 ++++-- .../net/ethernet/freescale/enetc/enetc4_pf.c | 97 ++++++++++++++----- .../net/ethernet/freescale/enetc/enetc_pf.h | 3 - include/linux/fsl/ntmp.h | 2 + 4 files changed, 96 insertions(+), 36 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c b/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c index 1b1591dce73d..4a769d9e5679 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c @@ -31,9 +31,11 @@ static int enetc_mac_filter_show(struct seq_file *s, void *data) struct enetc_si *si = s->private; struct enetc_hw *hw = &si->hw; struct maft_entry_data maft; + struct ntmp_user *user; struct enetc_pf *pf; - int i, err, num_si; - u32 val; + u32 val, entry_id; + int i, num_si; + int err = 0; pf = enetc_si_priv(si); num_si = pf->caps.num_vsi + 1; @@ -50,22 +52,30 @@ static int enetc_mac_filter_show(struct seq_file *s, void *data) for (i = 0; i < num_si; i++) enetc_show_si_mac_hash_filter(s, i); - if (!pf->num_mfe) - return 0; + user = &si->ntmp_user; + rtnl_lock(); + + if (bitmap_empty(user->maft_eid_bitmap, user->maft_num_entries)) + goto unlock_rtnl; /* MAC address filter table */ seq_puts(s, "MAC address filter table\n"); - for (i = 0; i < pf->num_mfe; i++) { + for_each_set_bit(entry_id, user->maft_eid_bitmap, + user->maft_num_entries) { memset(&maft, 0, sizeof(maft)); - err = ntmp_maft_query_entry(&si->ntmp_user, i, &maft); + err = ntmp_maft_query_entry(user, entry_id, &maft); if (err) - return err; + goto unlock_rtnl; - seq_printf(s, "Entry %d, MAC: %pM, SI bitmap: 0x%04x\n", i, - maft.keye.mac_addr, le16_to_cpu(maft.cfge.si_bitmap)); + seq_printf(s, "Entry %d, MAC: %pM, SI bitmap: 0x%04x\n", + entry_id, maft.keye.mac_addr, + le16_to_cpu(maft.cfge.si_bitmap)); } - return 0; +unlock_rtnl: + rtnl_unlock(); + + return err; } DEFINE_SHOW_ATTRIBUTE(enetc_mac_filter); diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index a02b01753ff2..b966637572a7 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c @@ -32,9 +32,6 @@ static void enetc4_get_port_caps(struct enetc_pf *pf) val = enetc_port_rd(hw, ENETC4_PMCAPR); pf->caps.half_duplex = (val & PMCAPR_HD) ? 1 : 0; - - val = enetc_port_rd(hw, ENETC4_PSIMAFCAPR); - pf->caps.mac_filter_num = val & PSIMAFCAPR_NUM_MAC_AFTE; } static void enetc4_get_psi_hw_features(struct enetc_si *si) @@ -92,31 +89,45 @@ static void enetc4_pf_set_loopback(struct net_device *ndev, bool en) static void enetc4_pf_clear_maft_entries(struct enetc_pf *pf) { - int i; + struct ntmp_user *user = &pf->si->ntmp_user; + u32 entry_id; - for (i = 0; i < pf->num_mfe; i++) - ntmp_maft_delete_entry(&pf->si->ntmp_user, i); - - pf->num_mfe = 0; + for_each_set_bit(entry_id, user->maft_eid_bitmap, + user->maft_num_entries) { + if (!ntmp_maft_delete_entry(user, entry_id)) + ntmp_clear_eid_bitmap(user->maft_eid_bitmap, entry_id); + } } static int enetc4_pf_add_maft_entries(struct enetc_pf *pf, struct netdev_hw_addr_list *uc) { + struct ntmp_user *user = &pf->si->ntmp_user; + int mac_cnt = netdev_hw_addr_list_count(uc); struct maft_entry_data maft = {}; struct netdev_hw_addr *ha; + u32 available_entries; u16 si_bit = BIT(0); + u32 entry_id; int err; + available_entries = user->maft_num_entries - + bitmap_weight(user->maft_eid_bitmap, + user->maft_num_entries); + + if (mac_cnt > available_entries) + return -ENOSPC; + maft.cfge.si_bitmap = cpu_to_le16(si_bit); netdev_hw_addr_list_for_each(ha, uc) { + entry_id = ntmp_lookup_free_eid(user->maft_eid_bitmap, + user->maft_num_entries); ether_addr_copy(maft.keye.mac_addr, ha->addr); - err = ntmp_maft_add_entry(&pf->si->ntmp_user, pf->num_mfe, - &maft); - if (unlikely(err)) + err = ntmp_maft_add_entry(user, entry_id, &maft); + if (unlikely(err)) { + ntmp_clear_eid_bitmap(user->maft_eid_bitmap, entry_id); goto clear_maft_entries; - - pf->num_mfe++; + } } return 0; @@ -146,10 +157,10 @@ static void enetc4_pf_set_uc_hash_filter(struct enetc_pf *pf, static int enetc4_pf_set_uc_exact_filter(struct enetc_pf *pf, struct netdev_hw_addr_list *uc) { - int mac_cnt = netdev_hw_addr_list_count(uc); struct enetc_si *si = pf->si; + int err; - if (!mac_cnt) { + if (netdev_hw_addr_list_empty(uc)) { /* clear both MAC hash and exact filters */ enetc_set_si_uc_hash_filter(si, 0, 0); enetc4_pf_clear_maft_entries(pf); @@ -157,21 +168,19 @@ static int enetc4_pf_set_uc_exact_filter(struct enetc_pf *pf, return 0; } - if (mac_cnt > pf->caps.mac_filter_num) - return -ENOSPC; - - /* Set temporary unicast hash filters in case of Rx loss when + /* Set temporary unicast hash filter in case of Rx loss when * updating MAC address filter table */ enetc4_pf_set_uc_hash_filter(pf, uc); enetc4_pf_clear_maft_entries(pf); - if (!enetc4_pf_add_maft_entries(pf, uc)) { + err = enetc4_pf_add_maft_entries(pf, uc); + if (!err) { enetc_reset_mac_addr_filter(&pf->mac_filter[UC]); enetc_set_si_uc_hash_filter(si, 0, 0); } - return 0; + return err; } static void enetc4_pf_set_mc_hash_filter(struct enetc_pf *pf, @@ -393,18 +402,60 @@ static void enetc4_configure_port(struct enetc_pf *pf) enetc_set_default_rss_key(pf); } +static void enetc4_get_ntmp_caps(struct enetc_si *si) +{ + struct ntmp_user *user = &si->ntmp_user; + struct enetc_hw *hw = &si->hw; + u32 val; + + val = enetc_port_rd(hw, ENETC4_PSIMAFCAPR); + user->maft_num_entries = FIELD_GET(PSIMAFCAPR_NUM_MAC_AFTE, val); +} + +static int enetc4_ntmp_bitmap_init(struct ntmp_user *user) +{ + user->maft_eid_bitmap = bitmap_zalloc(user->maft_num_entries, + GFP_KERNEL); + if (!user->maft_eid_bitmap) + return -ENOMEM; + + return 0; +} + +static void enetc4_ntmp_bitmap_free(struct ntmp_user *user) +{ + bitmap_free(user->maft_eid_bitmap); + user->maft_eid_bitmap = NULL; +} + static int enetc4_init_ntmp_user(struct enetc_si *si) { struct ntmp_user *user = &si->ntmp_user; + int err; /* For ENETC 4.1, all table versions are 0 */ memset(&user->tbl, 0, sizeof(user->tbl)); - return enetc4_setup_cbdr(si); + err = enetc4_setup_cbdr(si); + if (err) + return err; + + enetc4_get_ntmp_caps(si); + err = enetc4_ntmp_bitmap_init(user); + if (err) + goto teardown_cbdr; + + return 0; + +teardown_cbdr: + enetc4_teardown_cbdr(si); + + return err; } static void enetc4_free_ntmp_user(struct enetc_si *si) { + enetc4_ntmp_bitmap_free(&si->ntmp_user); enetc4_teardown_cbdr(si); } @@ -422,7 +473,7 @@ static int enetc4_pf_init(struct enetc_pf *pf) err = enetc4_init_ntmp_user(pf->si); if (err) { - dev_err(dev, "Failed to init CBDR\n"); + dev_err(dev, "Failed to init NTMP user\n"); return err; } diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.h b/drivers/net/ethernet/freescale/enetc/enetc_pf.h index 285b7e5c48fd..6f15f9ea1664 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.h @@ -22,7 +22,6 @@ struct enetc_port_caps { int num_msix; int num_rx_bdr; int num_tx_bdr; - int mac_filter_num; }; struct enetc_pf; @@ -60,8 +59,6 @@ struct enetc_pf { struct enetc_port_caps caps; const struct enetc_pf_ops *ops; - - int num_mfe; /* number of mac address filter table entries */ }; #define phylink_to_enetc_pf(config) \ diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h index d3b6c476b91a..764ef2892608 100644 --- a/include/linux/fsl/ntmp.h +++ b/include/linux/fsl/ntmp.h @@ -75,8 +75,10 @@ struct ntmp_user { /* NTMP table bitmaps for resource management */ u32 ett_bitmap_size; u32 ect_bitmap_size; + u16 maft_num_entries; unsigned long *ett_gid_bitmap; /* only valid for switch */ unsigned long *ect_gid_bitmap; /* only valid for switch */ + unsigned long *maft_eid_bitmap; /* only valid for ENETC */ }; struct maft_entry_data { From 9db43e6f35db54f35742e23143cdd7251f952bfd Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Mon, 20 Jul 2026 09:43:07 +0800 Subject: [PATCH 0568/1433] net: enetc: use PCI device name for debugfs directory enetc_create_debugfs() is called right after register_netdev(), at which point ndev->name still holds the format "eth%d" (e.g., eth0) rather than the final assigned name (e.g., via udev rules). Use pci_name() instead of netdev_name() to name the debugfs directory. The PCI device name is unique, stable, and available from the start, making it a more reliable identifier for the debugfs entry. Therefore, the observable debugfs path from something like /sys/kernel/debug/eth0/mac_filter to a PCI BDF-style path such as /sys/kernel/debug/0002:00:00.0/mac_filter. Signed-off-by: Wei Fang Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260720014317.1059359-6-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c b/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c index 4a769d9e5679..be378bf8f74d 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c @@ -81,10 +81,9 @@ DEFINE_SHOW_ATTRIBUTE(enetc_mac_filter); void enetc_create_debugfs(struct enetc_si *si) { - struct net_device *ndev = si->ndev; struct dentry *root; - root = debugfs_create_dir(netdev_name(ndev), NULL); + root = debugfs_create_dir(pci_name(si->pdev), NULL); if (IS_ERR(root)) return; From c969fbc01d9ea95623e82eac396d8838bba4da07 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Mon, 20 Jul 2026 09:43:08 +0800 Subject: [PATCH 0569/1433] net: enetc: simplify enetc4_set_port_speed() Since phylink may pass SPEED_UNKNOWN to mac_link_up, handle it explicitly by defaulting to SPEED_10, then replace the switch statement with a direct call to PCR_PSPEED_VAL(). Also update PCR_PSPEED_VAL() to use FIELD_PREP() for proper field masking instead of an open-coded shift. Signed-off-by: Wei Fang Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260720014317.1059359-7-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/freescale/enetc/enetc4_hw.h | 2 +- .../net/ethernet/freescale/enetc/enetc4_pf.c | 25 +++++++------------ 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_hw.h b/drivers/net/ethernet/freescale/enetc/enetc4_hw.h index 6a8f2ed56017..dea1fd0b8175 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_hw.h +++ b/drivers/net/ethernet/freescale/enetc/enetc4_hw.h @@ -148,7 +148,7 @@ #define PCR_L2DOSE BIT(4) #define PCR_TIMER_CS BIT(8) #define PCR_PSPEED GENMASK(29, 16) -#define PCR_PSPEED_VAL(speed) (((speed) / 10 - 1) << 16) +#define PCR_PSPEED_VAL(s) FIELD_PREP(PCR_PSPEED, ((s) / 10 - 1)) /* Port MAC address register 0/1 */ #define ENETC4_PMAR0 0x4020 diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index b966637572a7..f24269a48c26 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c @@ -628,26 +628,19 @@ static void enetc4_set_port_speed(struct enetc_ndev_priv *priv, int speed) u32 old_speed = priv->speed; u32 val; + /* If the speed is unknown, use the minimum value */ + if (speed == SPEED_UNKNOWN) { + speed = SPEED_10; + dev_warn(priv->dev, "Speed unknown, default is 10Mbps\n"); + } + if (speed == old_speed) return; - val = enetc_port_rd(&priv->si->hw, ENETC4_PCR); - val &= ~PCR_PSPEED; - - switch (speed) { - case SPEED_100: - case SPEED_1000: - case SPEED_2500: - case SPEED_10000: - val |= (PCR_PSPEED & PCR_PSPEED_VAL(speed)); - break; - case SPEED_10: - default: - val |= (PCR_PSPEED & PCR_PSPEED_VAL(SPEED_10)); - } - - priv->speed = speed; + val = enetc_port_rd(&priv->si->hw, ENETC4_PCR) & (~PCR_PSPEED); + val |= PCR_PSPEED_VAL(speed); enetc_port_wr(&priv->si->hw, ENETC4_PCR, val); + priv->speed = speed; } static void enetc4_set_rgmii_mac(struct enetc_pf *pf, int speed, int duplex) From 7c9a6ae0edb2975d6491fbbc1a08007a89e4efd9 Mon Sep 17 00:00:00 2001 From: Claudiu Manoil Date: Mon, 20 Jul 2026 09:43:09 +0800 Subject: [PATCH 0570/1433] net: enetc: differentiate phylink capabilities for pseudo-MAC and standalone MAC The ENETC pseudo-MACs are proprietary internal links that do not implement any standard MII interface, so restrict their supported PHY interface modes to PHY_INTERFACE_MODE_INTERNAL only. Since pseudo-MACs can operate at any speed between 10Mbps and 25Gbps in multiples of 10Mbps, set their MAC capabilities to cover the full range of standard full-duplex speeds: 10/100/1000/2500/5000/10000/ 20000/25000 Mbps. For standalone ENETC (v4), expand the supported interface modes to include 10GBASER in addition to the existing RGMII, SGMII, 1000BASEX, 2500BASEX and USXGMII modes, with MAC capabilities up to 10G. MAC_1000 is replaced with MAC_1000FD to explicitly exclude 1000M half-duplex, which is not supported. Note that 10GBASE-R mode of ENETC v4 has not supported yet, the current patch adds PHY_INTERFACE_MODE_10GBASER simply as preparation for the upcoming support of the 10GBASE-R mode. Signed-off-by: Claudiu Manoil Signed-off-by: Wei Fang Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260720014317.1059359-8-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/enetc.h | 2 +- .../net/ethernet/freescale/enetc/enetc4_pf.c | 1 - .../freescale/enetc/enetc_pf_common.c | 44 +++++++++++++------ 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc.h b/drivers/net/ethernet/freescale/enetc/enetc.h index 06a9f1ee0970..8839cfb49bcf 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc.h +++ b/drivers/net/ethernet/freescale/enetc/enetc.h @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */ -/* Copyright 2017-2019 NXP */ +/* Copyright 2017-2019, 2025-2026 NXP */ #include #include diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index f24269a48c26..75ee117e9b1d 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c @@ -602,7 +602,6 @@ static void enetc4_mac_config(struct enetc_pf *pf, unsigned int mode, val |= IFMODE_SGMII; break; case PHY_INTERFACE_MODE_10GBASER: - case PHY_INTERFACE_MODE_XGMII: case PHY_INTERFACE_MODE_USXGMII: val |= IFMODE_XGMII; break; diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c index 3597cb81a7cc..781b22198ca8 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) -/* Copyright 2024 NXP */ +/* Copyright 2024-2026 NXP */ #include #include @@ -359,7 +359,8 @@ static bool enetc_port_has_pcs(struct enetc_pf *pf) return (pf->if_mode == PHY_INTERFACE_MODE_SGMII || pf->if_mode == PHY_INTERFACE_MODE_1000BASEX || pf->if_mode == PHY_INTERFACE_MODE_2500BASEX || - pf->if_mode == PHY_INTERFACE_MODE_USXGMII); + pf->if_mode == PHY_INTERFACE_MODE_USXGMII || + pf->if_mode == PHY_INTERFACE_MODE_10GBASER); } int enetc_mdiobus_create(struct enetc_pf *pf, struct device_node *node) @@ -400,25 +401,42 @@ int enetc_phylink_create(struct enetc_ndev_priv *priv, struct device_node *node, { struct enetc_pf *pf = enetc_si_priv(priv->si); struct phylink *phylink; + unsigned long mac_caps; int err; pf->phylink_config.dev = &priv->ndev->dev; pf->phylink_config.type = PHYLINK_NETDEV; - pf->phylink_config.mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | - MAC_10 | MAC_100 | MAC_1000 | MAC_2500FD; __set_bit(PHY_INTERFACE_MODE_INTERNAL, pf->phylink_config.supported_interfaces); - __set_bit(PHY_INTERFACE_MODE_SGMII, - pf->phylink_config.supported_interfaces); - __set_bit(PHY_INTERFACE_MODE_1000BASEX, - pf->phylink_config.supported_interfaces); - __set_bit(PHY_INTERFACE_MODE_2500BASEX, - pf->phylink_config.supported_interfaces); - __set_bit(PHY_INTERFACE_MODE_USXGMII, - pf->phylink_config.supported_interfaces); - phy_interface_set_rgmii(pf->phylink_config.supported_interfaces); + mac_caps = MAC_ASYM_PAUSE | MAC_SYM_PAUSE; + if (!enetc_is_pseudo_mac(priv->si)) { + mac_caps |= MAC_10 | MAC_100 | MAC_1000FD | MAC_2500FD; + + __set_bit(PHY_INTERFACE_MODE_SGMII, + pf->phylink_config.supported_interfaces); + __set_bit(PHY_INTERFACE_MODE_1000BASEX, + pf->phylink_config.supported_interfaces); + __set_bit(PHY_INTERFACE_MODE_2500BASEX, + pf->phylink_config.supported_interfaces); + __set_bit(PHY_INTERFACE_MODE_USXGMII, + pf->phylink_config.supported_interfaces); + + if (!is_enetc_rev1(priv->si)) { + mac_caps |= MAC_5000FD | MAC_10000FD; + __set_bit(PHY_INTERFACE_MODE_10GBASER, + pf->phylink_config.supported_interfaces); + } + + phy_interface_set_rgmii(pf->phylink_config.supported_interfaces); + } else { + mac_caps |= MAC_10FD | MAC_100FD | MAC_1000FD | MAC_2500FD | + MAC_5000FD | MAC_10000FD | MAC_20000FD | + MAC_25000FD; + } + + pf->phylink_config.mac_capabilities = mac_caps; phylink = phylink_create(&pf->phylink_config, of_fwnode_handle(node), pf->if_mode, ops); if (IS_ERR(phylink)) { From 59bb3d62489ce08d45dac2b7ec7b8f99a7b2040b Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Mon, 20 Jul 2026 09:43:10 +0800 Subject: [PATCH 0571/1433] net: enetc: remove invalid code from enetc4_pl_mac_link_up() When adding phylink MAC operations support to the NETC switch driver, Russell King pointed out several pieces of invalid logic in the .mac_link_up() implementation (see [1] and [2]): 1) Half-duplex backpressure is not supported by the kernel, Ethernet relies on packet dropping for congestion management. 2) phylink_autoneg_inband() is unnecessary, as RGMII in-band status is not supported. 3) TX and RX pause are disabled in half-duplex mode, so there is no need to override them in .mac_link_up(). The same invalid logic is also present in enetc4_pl_mac_link_up(), so remove the invalid code from it. Given enetc4_set_hd_flow_control() is removed, pf->caps.half_duplex has also become useless and should therefore be removed as well. Link: https://lore.kernel.org/imx/acEIQqI-_oyCym8O@shell.armlinux.org.uk/ # 1 Link: https://lore.kernel.org/imx/acEFwqmAvWls_9Ef@shell.armlinux.org.uk/ # 2 Signed-off-by: Wei Fang Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260720014317.1059359-9-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/freescale/enetc/enetc4_hw.h | 2 - .../net/ethernet/freescale/enetc/enetc4_pf.c | 38 +------------------ .../net/ethernet/freescale/enetc/enetc_pf.h | 1 - 3 files changed, 1 insertion(+), 40 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_hw.h b/drivers/net/ethernet/freescale/enetc/enetc4_hw.h index dea1fd0b8175..09025e7a2a3a 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_hw.h +++ b/drivers/net/ethernet/freescale/enetc/enetc4_hw.h @@ -135,7 +135,6 @@ #define ENETC4_PSIVHFR1(a) ((a) * 0x80 + 0x2064) #define ENETC4_PMCAPR 0x4004 -#define PMCAPR_HD BIT(8) #define PMCAPR_FP GENMASK(10, 9) /* Port capability register */ @@ -198,7 +197,6 @@ #define PM_CMD_CFG_CNT_FRM_EN BIT(13) #define PM_CMD_CFG_TXP BIT(15) #define PM_CMD_CFG_SEND_IDLE BIT(16) -#define PM_CMD_CFG_HD_FCEN BIT(18) #define PM_CMD_CFG_SFD BIT(21) #define PM_CMD_CFG_TX_FLUSH BIT(22) #define PM_CMD_CFG_TX_LOWP_EN BIT(23) diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index 75ee117e9b1d..859b02f5170a 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c @@ -29,9 +29,6 @@ static void enetc4_get_port_caps(struct enetc_pf *pf) val = enetc_port_rd(hw, ENETC4_ECAPR2); pf->caps.num_rx_bdr = (val & ECAPR2_NUM_RX_BDR) >> 16; pf->caps.num_tx_bdr = val & ECAPR2_NUM_TX_BDR; - - val = enetc_port_rd(hw, ENETC4_PMCAPR); - pf->caps.half_duplex = (val & PMCAPR_HD) ? 1 : 0; } static void enetc4_get_psi_hw_features(struct enetc_si *si) @@ -588,11 +585,6 @@ static void enetc4_mac_config(struct enetc_pf *pf, unsigned int mode, case PHY_INTERFACE_MODE_RGMII_RXID: case PHY_INTERFACE_MODE_RGMII_TXID: val |= IFMODE_RGMII; - /* We need to enable auto-negotiation for the MAC - * if its RGMII interface support In-Band status. - */ - if (phylink_autoneg_inband(mode)) - val |= PM_IF_MODE_ENA; break; case PHY_INTERFACE_MODE_RMII: val |= IFMODE_RMII; @@ -695,22 +687,6 @@ static void enetc4_set_rmii_mac(struct enetc_pf *pf, int speed, int duplex) enetc_port_mac_wr(si, ENETC4_PM_IF_MODE(0), val); } -static void enetc4_set_hd_flow_control(struct enetc_pf *pf, bool enable) -{ - struct enetc_si *si = pf->si; - u32 old_val, val; - - if (!pf->caps.half_duplex) - return; - - old_val = enetc_port_mac_rd(si, ENETC4_PM_CMD_CFG(0)); - val = u32_replace_bits(old_val, enable ? 1 : 0, PM_CMD_CFG_HD_FCEN); - if (val == old_val) - return; - - enetc_port_mac_wr(si, ENETC4_PM_CMD_CFG(0), val); -} - static void enetc4_set_rx_pause(struct enetc_pf *pf, bool rx_pause) { struct enetc_si *si = pf->si; @@ -886,13 +862,11 @@ static void enetc4_pl_mac_link_up(struct phylink_config *config, struct enetc_pf *pf = phylink_to_enetc_pf(config); struct enetc_si *si = pf->si; struct enetc_ndev_priv *priv; - bool hd_fc = false; priv = netdev_priv(si->ndev); enetc4_set_port_speed(priv, speed); - if (!phylink_autoneg_inband(mode) && - phy_interface_mode_is_rgmii(interface)) + if (phy_interface_mode_is_rgmii(interface)) enetc4_set_rgmii_mac(pf, speed, duplex); if (interface == PHY_INTERFACE_MODE_RMII) @@ -904,18 +878,8 @@ static void enetc4_pl_mac_link_up(struct phylink_config *config, */ if (priv->active_offloads & ENETC_F_QBU) tx_pause = false; - } else { /* DUPLEX_HALF */ - if (tx_pause || rx_pause) - hd_fc = true; - - /* As per 802.3 annex 31B, PAUSE frames are only supported - * when the link is configured for full duplex operation. - */ - tx_pause = false; - rx_pause = false; } - enetc4_set_hd_flow_control(pf, hd_fc); enetc4_set_tx_pause(pf, priv->num_rx_rings, tx_pause); enetc4_set_rx_pause(pf, rx_pause); enetc4_mac_tx_enable(pf); diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.h b/drivers/net/ethernet/freescale/enetc/enetc_pf.h index 6f15f9ea1664..7e886dc49997 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.h @@ -17,7 +17,6 @@ struct enetc_vf_state { }; struct enetc_port_caps { - u32 half_duplex:1; int num_vsi; int num_msix; int num_rx_bdr; From 75e134261f6124c44b6dd32d7ba8e57af53a4419 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Mon, 20 Jul 2026 09:43:11 +0800 Subject: [PATCH 0572/1433] net: enetc: open-code enetc4_set_default_si_vlan_promisc() The function enetc4_set_default_si_vlan_promisc() is only called once, from enetc4_configure_port_si(). Open-code the loop at the call site and remove the single-use wrapper. Signed-off-by: Wei Fang Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260720014317.1059359-10-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/enetc4_pf.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index 859b02f5170a..505e4abf6c37 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c @@ -307,17 +307,6 @@ static void enetc4_pf_set_si_vlan_promisc(struct enetc_hw *hw, int si, bool en) enetc_port_wr(hw, ENETC4_PSIPVMR, val); } -static void enetc4_set_default_si_vlan_promisc(struct enetc_pf *pf) -{ - struct enetc_hw *hw = &pf->si->hw; - int num_si = pf->caps.num_vsi + 1; - int i; - - /* enforce VLAN promiscuous mode for all SIs */ - for (i = 0; i < num_si; i++) - enetc4_pf_set_si_vlan_promisc(hw, i, true); -} - /* Allocate the number of MSI-X vectors for per SI. */ static void enetc4_set_si_msix_num(struct enetc_pf *pf) { @@ -361,7 +350,9 @@ static void enetc4_configure_port_si(struct enetc_pf *pf) /* Outer VLAN tag will be used for VLAN filtering */ enetc_port_wr(hw, ENETC4_PSIVLANFMR, PSIVLANFMR_VS); - enetc4_set_default_si_vlan_promisc(pf); + /* Enforce VLAN promiscuous mode for all SIs */ + for (int i = 0; i < pf->caps.num_vsi + 1; i++) + enetc4_pf_set_si_vlan_promisc(hw, i, true); /* Disable SI MAC multicast & unicast promiscuous */ enetc_port_wr(hw, ENETC4_PSIPMMR, 0); From f7c6dcd6444b745fe3df3d3f468c61e7fb8cb8c9 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Mon, 20 Jul 2026 09:43:12 +0800 Subject: [PATCH 0573/1433] net: enetc: refactor SI VLAN promiscuous mode configuration Remove the enetc_set_vlan_promisc(), enetc_enable_si_vlan_promisc() and enetc_disable_si_vlan_promisc() functions, and introduce a new unified function enetc_set_si_vlan_promisc() to enable or disable VLAN promiscuous mode for a specific SI. This simplifies the logic and makes the interface more straightforward. The vlan_promisc_simap field in struct enetc_pf is no longer needed to track the current state. As ENETC V4 only changes the address offset of PSIPVMR register compared to V1 without any functional difference, enetc_set_si_vlan_promisc() can be moved to enetc_pf_common.c in the future with minor adjustments to be reused by the ENETC V4 driver Signed-off-by: Wei Fang Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260720014317.1059359-11-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/freescale/enetc/enetc_hw.h | 5 ++- .../net/ethernet/freescale/enetc/enetc_pf.c | 36 ++++++++----------- .../net/ethernet/freescale/enetc/enetc_pf.h | 1 - 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc_hw.h b/drivers/net/ethernet/freescale/enetc/enetc_hw.h index 66bfda60da9c..16da732dc5de 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_hw.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_hw.h @@ -185,9 +185,8 @@ enum enetc_bdr_type {TX, RX}; #define PSIPMMR_SI_MAC_MP(n) BIT((n) + 16) #define ENETC_PSIPVMR 0x001c -#define ENETC_VLAN_PROMISC_MAP_ALL 0x7 -#define ENETC_PSIPVMR_SET_VP(simap) ((simap) & 0x7) -#define ENETC_PSIPVMR_SET_VUTA(simap) (((simap) & 0x7) << 16) +#define PSIPVMR_SI_VLAN_P(n) BIT(n) /* n = SI index */ + #define ENETC_PSIPMAR0(n) (0x0100 + (n) * 0x8) /* n = SI index */ #define ENETC_PSIPMAR1(n) (0x0104 + (n) * 0x8) #define ENETC_PVCLCTR 0x0208 diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c b/drivers/net/ethernet/freescale/enetc/enetc_pf.c index db2a800a7aaf..afc02ed62c77 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c @@ -42,24 +42,20 @@ static void enetc_pf_destroy_pcs(struct phylink_pcs *pcs) lynx_pcs_destroy(pcs); } -static void enetc_set_vlan_promisc(struct enetc_hw *hw, char si_map) +static void enetc_set_si_vlan_promisc(struct enetc_si *si, int si_id, + bool promisc) { - u32 val = enetc_port_rd(hw, ENETC_PSIPVMR); + struct enetc_hw *hw = &si->hw; + u32 val; - val &= ~ENETC_PSIPVMR_SET_VP(ENETC_VLAN_PROMISC_MAP_ALL); - enetc_port_wr(hw, ENETC_PSIPVMR, ENETC_PSIPVMR_SET_VP(si_map) | val); -} + val = enetc_port_rd(hw, ENETC_PSIPVMR); -static void enetc_enable_si_vlan_promisc(struct enetc_pf *pf, int si_idx) -{ - pf->vlan_promisc_simap |= BIT(si_idx); - enetc_set_vlan_promisc(&pf->si->hw, pf->vlan_promisc_simap); -} + if (promisc) + val |= PSIPVMR_SI_VLAN_P(si_id); + else + val &= ~PSIPVMR_SI_VLAN_P(si_id); -static void enetc_disable_si_vlan_promisc(struct enetc_pf *pf, int si_idx) -{ - pf->vlan_promisc_simap &= ~BIT(si_idx); - enetc_set_vlan_promisc(&pf->si->hw, pf->vlan_promisc_simap); + enetc_port_wr(hw, ENETC_PSIPVMR, val); } static void enetc_set_isol_vlan(struct enetc_hw *hw, int si, u16 vlan, u8 qos) @@ -441,10 +437,9 @@ static void enetc_configure_port(struct enetc_pf *pf) /* split up RFS entries */ enetc_port_assign_rfs_entries(pf->si); - /* enforce VLAN promisc mode for all SIs */ - pf->vlan_promisc_simap = ENETC_VLAN_PROMISC_MAP_ALL; - enetc_set_vlan_promisc(hw, pf->vlan_promisc_simap); + for (int i = 0; i < pf->total_vfs + 1; i++) + enetc_set_si_vlan_promisc(pf->si, i, true); enetc_port_wr(hw, ENETC_PSIPMMR, 0); @@ -466,12 +461,9 @@ static int enetc_pf_set_features(struct net_device *ndev, } if (changed & NETIF_F_HW_VLAN_CTAG_FILTER) { - struct enetc_pf *pf = enetc_si_priv(priv->si); + bool promisc = !(features & NETIF_F_HW_VLAN_CTAG_FILTER); - if (!!(features & NETIF_F_HW_VLAN_CTAG_FILTER)) - enetc_disable_si_vlan_promisc(pf, 0); - else - enetc_enable_si_vlan_promisc(pf, 0); + enetc_set_si_vlan_promisc(priv->si, 0, promisc); } if (changed & NETIF_F_LOOPBACK) diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.h b/drivers/net/ethernet/freescale/enetc/enetc_pf.h index 7e886dc49997..1bd3063a3be3 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.h @@ -45,7 +45,6 @@ struct enetc_pf { struct work_struct msg_task; char msg_int_name[ENETC_INT_NAME_MAX]; - char vlan_promisc_simap; /* bitmap of SIs in VLAN promisc mode */ DECLARE_BITMAP(vlan_ht_filter, ENETC_VLAN_HT_SIZE); DECLARE_BITMAP(active_vlans, VLAN_N_VID); From ba07e3bef1b5b26760ecf467924fb905d2bc330b Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Mon, 20 Jul 2026 09:43:13 +0800 Subject: [PATCH 0574/1433] net: enetc: move enetc_set_si_vlan_promisc() to enetc_pf_common.c The PSIPVMR in ENETC v4 has the same bit layout and functionality as the PSIPVMR register in ENETC v1: bit n (n <= 15) controls VLAN promiscuous mode for SI n. The only difference between the two hardware generations is the register address offset. Since the register functionality is identical, the VLAN promiscuous mode setting code can be shared between ENETC v1 and v4 drivers. Move enetc_set_si_vlan_promisc() from enetc_pf.c to enetc_pf_common.c and export it so that it can be shared between the two drivers. Add a revision check using is_enetc_rev1() to select the correct register offset (ENETC_PSIPVMR for v1 and ENETC4_PSIPVMR for v4) while keeping the same logic. Remove the v4-specific enetc4_pf_set_si_vlan_promisc() from enetc4_pf.c and replace its call site with the new common enetc_set_si_vlan_promisc() to eliminate code duplication. Signed-off-by: Wei Fang Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260720014317.1059359-12-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/freescale/enetc/enetc4_pf.c | 17 ++------------ .../net/ethernet/freescale/enetc/enetc_pf.c | 16 -------------- .../freescale/enetc/enetc_pf_common.c | 22 +++++++++++++++++++ .../freescale/enetc/enetc_pf_common.h | 1 + 4 files changed, 25 insertions(+), 31 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index 505e4abf6c37..cab79f81d6fe 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c @@ -295,18 +295,6 @@ static void enetc4_allocate_si_rings(struct enetc_pf *pf) enetc4_default_rings_allocation(pf); } -static void enetc4_pf_set_si_vlan_promisc(struct enetc_hw *hw, int si, bool en) -{ - u32 val = enetc_port_rd(hw, ENETC4_PSIPVMR); - - if (en) - val |= BIT(si); - else - val &= ~BIT(si); - - enetc_port_wr(hw, ENETC4_PSIPVMR, val); -} - /* Allocate the number of MSI-X vectors for per SI. */ static void enetc4_set_si_msix_num(struct enetc_pf *pf) { @@ -352,7 +340,7 @@ static void enetc4_configure_port_si(struct enetc_pf *pf) /* Enforce VLAN promiscuous mode for all SIs */ for (int i = 0; i < pf->caps.num_vsi + 1; i++) - enetc4_pf_set_si_vlan_promisc(hw, i, true); + enetc_set_si_vlan_promisc(pf->si, i, true); /* Disable SI MAC multicast & unicast promiscuous */ enetc_port_wr(hw, ENETC4_PSIPMMR, 0); @@ -518,12 +506,11 @@ static int enetc4_pf_set_features(struct net_device *ndev, { netdev_features_t changed = ndev->features ^ features; struct enetc_ndev_priv *priv = netdev_priv(ndev); - struct enetc_hw *hw = &priv->si->hw; if (changed & NETIF_F_HW_VLAN_CTAG_FILTER) { bool promisc_en = !(features & NETIF_F_HW_VLAN_CTAG_FILTER); - enetc4_pf_set_si_vlan_promisc(hw, 0, promisc_en); + enetc_set_si_vlan_promisc(priv->si, 0, promisc_en); } if (changed & NETIF_F_LOOPBACK) diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c b/drivers/net/ethernet/freescale/enetc/enetc_pf.c index afc02ed62c77..a509929f89f2 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c @@ -42,22 +42,6 @@ static void enetc_pf_destroy_pcs(struct phylink_pcs *pcs) lynx_pcs_destroy(pcs); } -static void enetc_set_si_vlan_promisc(struct enetc_si *si, int si_id, - bool promisc) -{ - struct enetc_hw *hw = &si->hw; - u32 val; - - val = enetc_port_rd(hw, ENETC_PSIPVMR); - - if (promisc) - val |= PSIPVMR_SI_VLAN_P(si_id); - else - val &= ~PSIPVMR_SI_VLAN_P(si_id); - - enetc_port_wr(hw, ENETC_PSIPVMR, val); -} - static void enetc_set_isol_vlan(struct enetc_hw *hw, int si, u16 vlan, u8 qos) { u32 val = 0; diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c index 781b22198ca8..d32a195a04c9 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c @@ -171,6 +171,28 @@ void enetc_set_si_mc_hash_filter(struct enetc_si *si, int si_id, u64 hash) } EXPORT_SYMBOL_GPL(enetc_set_si_mc_hash_filter); +void enetc_set_si_vlan_promisc(struct enetc_si *si, int si_id, bool promisc) +{ + struct enetc_hw *hw = &si->hw; + int psipvmr_off; + u32 val; + + if (is_enetc_rev1(si)) + psipvmr_off = ENETC_PSIPVMR; + else + psipvmr_off = ENETC4_PSIPVMR; + + val = enetc_port_rd(hw, psipvmr_off); + + if (promisc) + val |= PSIPVMR_SI_VLAN_P(si_id); + else + val &= ~PSIPVMR_SI_VLAN_P(si_id); + + enetc_port_wr(hw, psipvmr_off, val); +} +EXPORT_SYMBOL_GPL(enetc_set_si_vlan_promisc); + void enetc_pf_netdev_setup(struct enetc_si *si, struct net_device *ndev, const struct net_device_ops *ndev_ops) { diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h index bf9029b0a017..8243ce0de57f 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h @@ -21,6 +21,7 @@ void enetc_set_si_uc_promisc(struct enetc_si *si, int si_id, bool promisc); void enetc_set_si_mc_promisc(struct enetc_si *si, int si_id, bool promisc); void enetc_set_si_uc_hash_filter(struct enetc_si *si, int si_id, u64 hash); void enetc_set_si_mc_hash_filter(struct enetc_si *si, int si_id, u64 hash); +void enetc_set_si_vlan_promisc(struct enetc_si *si, int si_id, bool promisc); static inline u16 enetc_get_ip_revision(struct enetc_hw *hw) { From 75b136cf0f6faca1d183f5c0be7a1773d11bfbc3 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Mon, 20 Jul 2026 09:43:14 +0800 Subject: [PATCH 0575/1433] net: enetc: remove redundant num_vsi field from enetc_port_caps The num_vsi field in struct enetc_port_caps is populated by reading the NUM_VSI field of the ECAPR1 register, which reports the number of VSIs supported by the ENETC4 port. When CONFIG_PCI_IOV is enabled, this value always matches pf->total_vfs, which is obtained from the read-only PCI_SRIOV_TOTAL_VF register via pci_sriov_get_totalvfs() during probe. Both ECAPR1[NUM_VSI] and PCI_SRIOV_TOTAL_VF are derived from the same IERB register EaVFRIDAR[NUM_VF] (a 4-bit field), so they are guaranteed to be equal. When CONFIG_PCI_IOV is disabled, pci_sriov_get_totalvfs() returns 0, but this is benign since pci_enable_sriov() is also stubbed to return -ENODEV, so no VF can be created, and enetc4_enable_all_si() only enables the PF SI (PSI). Since pf->total_vfs already reflects the number of VFs that can actually be used, and is the established convention in the sibling FSL_ENETC PF driver, there is no need to read and cache num_vsi separately in the port capabilities structure. Remove the num_vsi field from enetc_port_caps, and replace all uses of pf->caps.num_vsi with pf->total_vfs in the ring allocation, SI enable, and debugfs code paths. Note that in the MSI-X configuration, it is still necessary to obtain the actual number of VSIs from ECAPR1. Signed-off-by: Wei Fang Link: https://patch.msgid.link/20260720014317.1059359-13-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- .../ethernet/freescale/enetc/enetc4_debugfs.c | 13 ++- .../net/ethernet/freescale/enetc/enetc4_pf.c | 86 ++++++++++++++----- .../net/ethernet/freescale/enetc/enetc_pf.h | 1 - 3 files changed, 68 insertions(+), 32 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c b/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c index be378bf8f74d..5029038bf99f 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c @@ -28,17 +28,14 @@ static void enetc_show_si_mac_hash_filter(struct seq_file *s, int i) static int enetc_mac_filter_show(struct seq_file *s, void *data) { - struct enetc_si *si = s->private; - struct enetc_hw *hw = &si->hw; + struct enetc_pf *pf = enetc_si_priv(s->private); + struct enetc_hw *hw = &pf->si->hw; + int num_si = pf->total_vfs + 1; struct maft_entry_data maft; struct ntmp_user *user; - struct enetc_pf *pf; u32 val, entry_id; - int i, num_si; int err = 0; - - pf = enetc_si_priv(si); - num_si = pf->caps.num_vsi + 1; + int i; val = enetc_port_rd(hw, ENETC4_PSIPMMR); for (i = 0; i < num_si; i++) { @@ -52,7 +49,7 @@ static int enetc_mac_filter_show(struct seq_file *s, void *data) for (i = 0; i < num_si; i++) enetc_show_si_mac_hash_filter(s, i); - user = &si->ntmp_user; + user = &pf->si->ntmp_user; rtnl_lock(); if (bitmap_empty(user->maft_eid_bitmap, user->maft_num_entries)) diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index cab79f81d6fe..fcfbabb29d22 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c @@ -23,7 +23,6 @@ static void enetc4_get_port_caps(struct enetc_pf *pf) u32 val; val = enetc_port_rd(hw, ENETC4_ECAPR1); - pf->caps.num_vsi = (val & ECAPR1_NUM_VSI) >> 24; pf->caps.num_msix = ((val & ECAPR1_NUM_MSIX) >> 12) + 1; val = enetc_port_rd(hw, ENETC4_ECAPR2); @@ -255,34 +254,35 @@ static void enetc4_default_rings_allocation(struct enetc_pf *pf) { struct enetc_hw *hw = &pf->si->hw; u32 num_rx_bdr, num_tx_bdr, val; + int num_vfs = pf->total_vfs; u32 vf_tx_bdr, vf_rx_bdr; int i, rx_rem, tx_rem; - if (pf->caps.num_rx_bdr < ENETC_SI_MAX_RING_NUM + pf->caps.num_vsi) - num_rx_bdr = pf->caps.num_rx_bdr - pf->caps.num_vsi; + if (pf->caps.num_rx_bdr < ENETC_SI_MAX_RING_NUM + num_vfs) + num_rx_bdr = pf->caps.num_rx_bdr - num_vfs; else num_rx_bdr = ENETC_SI_MAX_RING_NUM; - if (pf->caps.num_tx_bdr < ENETC_SI_MAX_RING_NUM + pf->caps.num_vsi) - num_tx_bdr = pf->caps.num_tx_bdr - pf->caps.num_vsi; + if (pf->caps.num_tx_bdr < ENETC_SI_MAX_RING_NUM + num_vfs) + num_tx_bdr = pf->caps.num_tx_bdr - num_vfs; else num_tx_bdr = ENETC_SI_MAX_RING_NUM; val = enetc4_psicfgr0_val_construct(false, num_tx_bdr, num_rx_bdr); enetc_port_wr(hw, ENETC4_PSICFGR0(0), val); - if (!pf->caps.num_vsi) + if (!num_vfs) return; num_rx_bdr = pf->caps.num_rx_bdr - num_rx_bdr; - rx_rem = num_rx_bdr % pf->caps.num_vsi; - num_rx_bdr = num_rx_bdr / pf->caps.num_vsi; + rx_rem = num_rx_bdr % num_vfs; + num_rx_bdr = num_rx_bdr / num_vfs; num_tx_bdr = pf->caps.num_tx_bdr - num_tx_bdr; - tx_rem = num_tx_bdr % pf->caps.num_vsi; - num_tx_bdr = num_tx_bdr / pf->caps.num_vsi; + tx_rem = num_tx_bdr % num_vfs; + num_tx_bdr = num_tx_bdr / num_vfs; - for (i = 0; i < pf->caps.num_vsi; i++) { + for (i = 0; i < num_vfs; i++) { vf_tx_bdr = (i < tx_rem) ? num_tx_bdr + 1 : num_tx_bdr; vf_rx_bdr = (i < rx_rem) ? num_rx_bdr + 1 : num_rx_bdr; val = enetc4_psicfgr0_val_construct(true, vf_tx_bdr, vf_rx_bdr); @@ -298,27 +298,67 @@ static void enetc4_allocate_si_rings(struct enetc_pf *pf) /* Allocate the number of MSI-X vectors for per SI. */ static void enetc4_set_si_msix_num(struct enetc_pf *pf) { + int valid_num_si = pf->total_vfs + 1; struct enetc_hw *hw = &pf->si->hw; - int i, num_msix, total_si; + int i, num_msix, num_vsi; u32 val; - total_si = pf->caps.num_vsi + 1; + val = enetc_port_rd(hw, ENETC4_ECAPR1); + num_vsi = FIELD_GET(ECAPR1_NUM_VSI, val); - num_msix = pf->caps.num_msix / total_si + - pf->caps.num_msix % total_si - 1; - val = num_msix & PSICFGR2_NUM_MSIX; - enetc_port_wr(hw, ENETC4_PSICFGR2(0), val); + /* The PSIaCFGR2[NUM_MSIX] indicates the number of MSI-X allocated to + * the SI is NUM_MSIX + 1, so the minimum number of MSI-X allocated to + * each SI is 1. The total number of MSI-X allocated to PSI and VSIs + * cannot exceed the total number of MSI-X owned by this ENETC, which + * is ECAPR1[NUM_MSIX]. Otherwise, when multiple ENETC instances exist, + * it will affect other ENETCs whose MSI-X interrupts cannot be + * generated. This is similar to out-of-bounds array access: the array + * itself is not affected, but adjacent arrays will be corrupted. + * + * pf->total_vfs is 0 if CONFIG_PCI_IOV is disabled. If the hardware + * itself supports SR-IOV, then when allocating the number of MSIXs to + * the SI, it must be taken into account that the VSI has at least 1 + * MSIX, and the total number of MSIXs of all SIs cannot exceed + * ECAPR1[NUM_MSIX]. + */ + if (!pf->total_vfs && num_vsi) { + /* Because each SI has at least one MSIX, and from the hardware + * perspective, pf->caps.num_msix will always be greater than + * num_vsi. So num_msix is always greater than or equal to 0. + */ + num_msix = pf->caps.num_msix - num_vsi - 1; + if (num_msix > PSICFGR2_NUM_MSIX) + num_msix = PSICFGR2_NUM_MSIX; + enetc_port_wr(hw, ENETC4_PSICFGR2(0), num_msix); - num_msix = pf->caps.num_msix / total_si - 1; - val = num_msix & PSICFGR2_NUM_MSIX; - for (i = 0; i < pf->caps.num_vsi; i++) - enetc_port_wr(hw, ENETC4_PSICFGR2(i + 1), val); + for (i = 0; i < num_vsi; i++) + enetc_port_wr(hw, ENETC4_PSICFGR2(i + 1), 0); + + return; + } + + /* Likewise, from the hardware perspective pf->caps.num_msix is always + * greater than valid_num_si. So num_msix is always greater than or + * equal to 0. + */ + num_msix = pf->caps.num_msix / valid_num_si + + pf->caps.num_msix % valid_num_si - 1; + if (num_msix > PSICFGR2_NUM_MSIX) + num_msix = PSICFGR2_NUM_MSIX; + enetc_port_wr(hw, ENETC4_PSICFGR2(0), num_msix); + + num_msix = pf->caps.num_msix / valid_num_si - 1; + if (num_msix > PSICFGR2_NUM_MSIX) + num_msix = PSICFGR2_NUM_MSIX; + + for (i = 0; i < pf->total_vfs; i++) + enetc_port_wr(hw, ENETC4_PSICFGR2(i + 1), num_msix); } static void enetc4_enable_all_si(struct enetc_pf *pf) { struct enetc_hw *hw = &pf->si->hw; - int num_si = pf->caps.num_vsi + 1; + int num_si = pf->total_vfs + 1; u32 si_bitmap = 0; int i; @@ -339,7 +379,7 @@ static void enetc4_configure_port_si(struct enetc_pf *pf) enetc_port_wr(hw, ENETC4_PSIVLANFMR, PSIVLANFMR_VS); /* Enforce VLAN promiscuous mode for all SIs */ - for (int i = 0; i < pf->caps.num_vsi + 1; i++) + for (int i = 0; i < pf->total_vfs + 1; i++) enetc_set_si_vlan_promisc(pf->si, i, true); /* Disable SI MAC multicast & unicast promiscuous */ diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.h b/drivers/net/ethernet/freescale/enetc/enetc_pf.h index 1bd3063a3be3..56d23a8a11a0 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.h @@ -17,7 +17,6 @@ struct enetc_vf_state { }; struct enetc_port_caps { - int num_vsi; int num_msix; int num_rx_bdr; int num_tx_bdr; From f0c1f31afd97abcdaa70d78ac556bad84fc41871 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Mon, 20 Jul 2026 09:43:15 +0800 Subject: [PATCH 0576/1433] net: enetc: use alloc_etherdev_mqs() to create netdev for VF driver The VF driver uses alloc_etherdev_mq() with ENETC_MAX_NUM_TXQS as the queue count, which forces the TX and RX queue counts to be equal and uses a compile-time constant rather than the actual hardware capability. After enetc_get_si_caps() is called, si->num_tx_rings and si->num_rx_rings reflect the actual number of rings assigned to the VF by the PF. For the ENETC VF on LS1028A and the upcoming i.MX95/94, their SoCs have no more than 6 CPUs, and the number of TX/RX rings allocated to the VF is less than 8. Therefore, switch to alloc_etherdev_mqs() so that the TX and RX queue counts are set independently, each capped at ENETC_MAX_NUM_TXQS, based on the actual number of rings assigned to the VF by the PF. Note that if future SoCs have more than 6 CPUs and more than 6 RX rings allocated to VFs, the size of the int_vector array in struct enetc_ndev_priv will need to be modified. Similarly, if more than 8 TX rings are allocated to each int_vector, ENETC_MAX_NUM_TXQS will also need to be modified. Signed-off-by: Wei Fang Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260720014317.1059359-14-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/enetc_vf.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc_vf.c b/drivers/net/ethernet/freescale/enetc/enetc_vf.c index 9cdb0a4d6baf..7dcb4a0246f5 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_vf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_vf.c @@ -317,7 +317,14 @@ static int enetc_vf_probe(struct pci_dev *pdev, enetc_get_si_caps(si); - ndev = alloc_etherdev_mq(sizeof(*priv), ENETC_MAX_NUM_TXQS); + /* Currently, the supported SoCs have a max of 6 CPUs and the VFs + * have less than 6 RX/TX rings. So no issues for these supported + * SoCs, but for future SoCs which have more CPUs or more TX/RX + * rings, all the related logic needs to be improved. + */ + ndev = alloc_etherdev_mqs(sizeof(*priv), + min(si->num_tx_rings, ENETC_MAX_NUM_TXQS), + min(si->num_rx_rings, ENETC_MAX_NUM_TXQS)); if (!ndev) { err = -ENOMEM; dev_err(&pdev->dev, "netdev creation failed\n"); From 18bf0ac84334aac237ecf14a8c427866109fb8c9 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Mon, 20 Jul 2026 09:43:16 +0800 Subject: [PATCH 0577/1433] net: enetc: use kzalloc_flex() for enetc_psfp_gate allocation Replace the open-coded struct_size() + kzalloc() pattern with the kzalloc_flex() helper when allocating struct enetc_psfp_gate. This removes the intermediate entries_size local variable and makes the allocation site more concise. Signed-off-by: Wei Fang Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260720014317.1059359-15-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/enetc_qos.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc_qos.c b/drivers/net/ethernet/freescale/enetc/enetc_qos.c index 7b17bca24f26..2aa0fcaafcd2 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_qos.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_qos.c @@ -1135,7 +1135,6 @@ static int enetc_psfp_parse_clsflower(struct enetc_ndev_priv *priv, struct flow_action_entry *entry; struct action_gate_entry *e; u8 sfi_overwrite = 0; - int entries_size; int i, err; if (f->common.chain_index >= priv->psfp_cap.max_streamid) { @@ -1242,8 +1241,7 @@ static int enetc_psfp_parse_clsflower(struct enetc_ndev_priv *priv, goto free_filter; } - entries_size = struct_size(sgi, entries, entryg->gate.num_entries); - sgi = kzalloc(entries_size, GFP_KERNEL); + sgi = kzalloc_flex(*sgi, entries, entryg->gate.num_entries); if (!sgi) { err = -ENOMEM; goto free_filter; From 841afc9143ee340818a4a9cf191f4e894a43c750 Mon Sep 17 00:00:00 2001 From: Xin Xie Date: Fri, 17 Jul 2026 22:14:54 +0200 Subject: [PATCH 0578/1433] net: hsr: add PRP interlink (RedBox) datapath and duplicate discard A PRP RedBox proxies SANs that sit behind an interlink port: their frames must reach the PRP network with the SAN source MAC preserved, and PRP unicast must be steered between the LAN and the SAN segment correctly. Add the PRP interlink forwarding rules to prp_drop_frame() and give RedBox nodes a second duplicate-discard slot so the two LAN copies of a frame destined to a SAN collapse to a single delivery out the interlink. The destination classification (is the unicast DA a PRP-network node or a proxied SAN) is resolved once per frame in fill_frame_info(), gated to PRP RedBox devices, and cached in struct hsr_frame_info, so prp_drop_frame() stays O(1) and does not walk the node tables for every candidate egress port in the softIRQ path. HSR RedBox frame classification is untouched. Factor the LAN A/B duplicate test into prp_is_lan_dup() so the new PRP interlink rules do not change hsr_drop_frame() behaviour, including the NETIF_F_HW_HSR_FWD path which keeps using the LAN-duplicate test only. Publish the RedBox state before the first hsr_add_port(): the slave and interlink rx handlers are live from hsr_add_port() on and rtnl does not stop softirq processing, so a frame could otherwise be handled while hsr->redbox is still false. hsr_add_node() sizes each node's per-port sequence state from hsr->redbox; a node learned in that window would get a single-port sequence block, breaking the interlink duplicate discard (WARN_ON_ONCE plus duplicate delivery to the SAN) and letting the supervision sequence-block merge read beyond the source node's allocated sequence bitmap. Publishing the flag before any port exists makes the per-node sizing uniform by construction. This is safe: the proxy announce timer is only armed from hsr_check_announce() once the master is running, the packet-path readers of hsr->redbox tolerate an empty proxy node database and an absent interlink port, and the prune_proxy_timer is still armed only after the interlink port has been attached successfully. Additionally bound the supervision sequence-block merge by the smaller of the two nodes' seq_port_cnt as defense in depth against mismatched node sizes. Signed-off-by: Xin Xie Link: https://patch.msgid.link/20260717201457.54-2-xiexinet@gmail.com Signed-off-by: Jakub Kicinski --- net/hsr/hsr_device.c | 11 ++++++++-- net/hsr/hsr_forward.c | 46 ++++++++++++++++++++++++++++++++++++------ net/hsr/hsr_framereg.c | 14 +++++++++---- net/hsr/hsr_framereg.h | 2 ++ 4 files changed, 61 insertions(+), 12 deletions(-) diff --git a/net/hsr/hsr_device.c b/net/hsr/hsr_device.c index 5555b71ab19b..5af491ed2b72 100644 --- a/net/hsr/hsr_device.c +++ b/net/hsr/hsr_device.c @@ -768,6 +768,15 @@ int hsr_dev_finalize(struct net_device *hsr_dev, struct net_device *slave[2], /* Make sure the 1st call to netif_carrier_on() gets through */ netif_carrier_off(hsr_dev); + /* Publish the RedBox state before any port is attached: the rx + * handlers are live from hsr_add_port() on, and hsr_add_node() + * sizes each node's per-port sequence state from hsr->redbox. + */ + if (interlink) { + hsr->redbox = true; + ether_addr_copy(hsr->macaddress_redbox, interlink->dev_addr); + } + res = hsr_add_port(hsr, hsr_dev, HSR_PT_MASTER, extack); if (res) goto err_add_master; @@ -805,8 +814,6 @@ int hsr_dev_finalize(struct net_device *hsr_dev, struct net_device *slave[2], if (res) goto err_unregister; - hsr->redbox = true; - ether_addr_copy(hsr->macaddress_redbox, interlink->dev_addr); mod_timer(&hsr->prune_proxy_timer, jiffies + msecs_to_jiffies(PRUNE_PROXY_PERIOD)); } diff --git a/net/hsr/hsr_forward.c b/net/hsr/hsr_forward.c index 0774981a65c1..7734a521a96c 100644 --- a/net/hsr/hsr_forward.c +++ b/net/hsr/hsr_forward.c @@ -440,12 +440,34 @@ static int hsr_xmit(struct sk_buff *skb, struct hsr_port *port, return dev_queue_xmit(skb); } +static bool prp_is_lan_dup(enum hsr_port_type rx, struct hsr_port *port) +{ + return (rx == HSR_PT_SLAVE_A && port->type == HSR_PT_SLAVE_B) || + (rx == HSR_PT_SLAVE_B && port->type == HSR_PT_SLAVE_A); +} + bool prp_drop_frame(struct hsr_frame_info *frame, struct hsr_port *port) { - return ((frame->port_rcv->type == HSR_PT_SLAVE_A && - port->type == HSR_PT_SLAVE_B) || - (frame->port_rcv->type == HSR_PT_SLAVE_B && - port->type == HSR_PT_SLAVE_A)); + enum hsr_port_type rx = frame->port_rcv->type; + + /* Supervision frames are not delivered to a SAN on the interlink. */ + if (frame->is_supervision && port->type == HSR_PT_INTERLINK) + return true; + + if (prp_is_lan_dup(rx, port)) + return true; + + /* LAN to interlink: keep PRP-network unicast off the SAN segment. */ + if ((rx == HSR_PT_SLAVE_A || rx == HSR_PT_SLAVE_B) && + port->type == HSR_PT_INTERLINK) + return frame->dst_in_node_db; + + /* Interlink to LAN: keep SAN-to-SAN unicast local. */ + if ((port->type == HSR_PT_SLAVE_A || port->type == HSR_PT_SLAVE_B) && + rx == HSR_PT_INTERLINK) + return frame->dst_in_proxy_node_db; + + return false; } bool hsr_drop_frame(struct hsr_frame_info *frame, struct hsr_port *port) @@ -453,7 +475,7 @@ bool hsr_drop_frame(struct hsr_frame_info *frame, struct hsr_port *port) struct sk_buff *skb; if (port->dev->features & NETIF_F_HW_HSR_FWD) - return prp_drop_frame(frame, port); + return prp_is_lan_dup(frame->port_rcv->type, port); /* RedBox specific frames dropping policies * @@ -466,7 +488,7 @@ bool hsr_drop_frame(struct hsr_frame_info *frame, struct hsr_port *port) * are addressed to interlink port (and are in the ProxyNodeTable). */ skb = frame->skb_hsr; - if (skb && prp_drop_frame(frame, port) && + if (skb && prp_is_lan_dup(frame->port_rcv->type, port) && is_unicast_ether_addr(eth_hdr(skb)->h_dest) && hsr_is_node_in_db(&port->hsr->proxy_node_db, eth_hdr(skb)->h_dest)) { @@ -706,6 +728,18 @@ static int fill_frame_info(struct hsr_frame_info *frame, frame->is_vlan = false; proto = ethhdr->h_proto; + /* PRP RedBox only: classify the unicast destination once so the + * per-egress-port decision in prp_drop_frame() stays O(1). HSR RedBox + * does its own classification and must not pay these node-table walks. + */ + if (hsr->prot_version == PRP_V1 && hsr->redbox && + is_unicast_ether_addr(ethhdr->h_dest)) { + frame->dst_in_node_db = + hsr_is_node_in_db(&hsr->node_db, ethhdr->h_dest); + frame->dst_in_proxy_node_db = + hsr_is_node_in_db(&hsr->proxy_node_db, ethhdr->h_dest); + } + if (proto == htons(ETH_P_8021Q)) frame->is_vlan = true; diff --git a/net/hsr/hsr_framereg.c b/net/hsr/hsr_framereg.c index e44929871274..8f708b6e6c33 100644 --- a/net/hsr/hsr_framereg.c +++ b/net/hsr/hsr_framereg.c @@ -199,7 +199,7 @@ static struct hsr_node *hsr_add_node(struct hsr_priv *hsr, spin_lock_init(&new_node->seq_out_lock); if (hsr->prot_version == PRP_V1) - new_node->seq_port_cnt = 1; + new_node->seq_port_cnt = hsr->redbox ? 2 : 1; else new_node->seq_port_cnt = HSR_PT_PORTS - 1; @@ -381,6 +381,7 @@ void hsr_handle_sup_frame(struct hsr_frame_info *frame) struct ethhdr *ethhdr; unsigned int total_pull_size = 0; unsigned int pull_size = 0; + unsigned int seq_port_cnt; unsigned long idx; int i; @@ -474,6 +475,7 @@ void hsr_handle_sup_frame(struct hsr_frame_info *frame) } } + seq_port_cnt = min(node_real->seq_port_cnt, node_curr->seq_port_cnt); xa_for_each(&node_curr->seq_blocks, idx, src_blk) { if (hsr_seq_block_is_old(src_blk)) continue; @@ -482,7 +484,7 @@ void hsr_handle_sup_frame(struct hsr_frame_info *frame) if (!merge_blk) continue; merge_blk->time = min(merge_blk->time, src_blk->time); - for (i = 0; i < node_real->seq_port_cnt; i++) { + for (i = 0; i < seq_port_cnt; i++) { bitmap_or(merge_blk->seq_nrs[i], merge_blk->seq_nrs[i], src_blk->seq_nrs[i], HSR_SEQ_BLOCK_SIZE); } @@ -649,9 +651,13 @@ int prp_register_frame_out(struct hsr_port *port, struct hsr_frame_info *frame) if (frame->port_rcv->type == HSR_PT_MASTER) return 0; - /* for PRP we should only forward frames from the slave ports - * to the master port + /* RedBox: forward LAN frames out the interlink to a SAN, deduping the + * two LAN copies on a dedicated slot. */ + if (port->type == HSR_PT_INTERLINK) + return hsr_check_duplicate(frame, 1); + + /* For PRP only slave-to-master frames are forwarded. */ if (port->type != HSR_PT_MASTER) return 1; diff --git a/net/hsr/hsr_framereg.h b/net/hsr/hsr_framereg.h index c65ecb925734..127a3fb64d5f 100644 --- a/net/hsr/hsr_framereg.h +++ b/net/hsr/hsr_framereg.h @@ -27,6 +27,8 @@ struct hsr_frame_info { bool is_local_dest; bool is_local_exclusive; bool is_from_san; + bool dst_in_node_db; + bool dst_in_proxy_node_db; }; void hsr_del_self_node(struct hsr_priv *hsr); From 852c6a8d7cbe359fff3ee439f4b908231253dc3d Mon Sep 17 00:00:00 2001 From: Xin Xie Date: Fri, 17 Jul 2026 22:14:55 +0200 Subject: [PATCH 0579/1433] net: hsr: emit RedBox-MAC TLV in PRP RedBox supervision frames A PRP RedBox must announce the SANs it proxies so peers populate their proxy node tables. The proxy-announce machinery (hsr_proxy_announce(), armed via hsr->redbox) already iterates proxy_node_db under RCU and calls send_sv_frame() once per SAN, but the PRP sender emitted neither the announced SAN MAC nor the RedBox-MAC TLV that IEC 62439-3 requires. Extend send_prp_supervision_frame() so that, for a proxy-announce (identified by the interlink port, an O(1) test), the frame carries the proxied SAN MAC as MacAddressA followed by the RedBox-MAC TLV (Type 30) and an explicit End-of-TLV marker before padding. hsr_get_node() must also accept the reinjected proxy-announce: a PRP supervision frame is an untagged ETH_P_PRP frame (mac_len == ETH_HLEN, the RCT is appended only on egress) sourced from macaddress_redbox, which is never learned from data. Exempt only PRP supervision frames from the hsr_ethhdr length guard; HSR (ETH_P_HSR) supervision is front-tagged and keeps the original guard, so HSR malformed-frame filtering is unchanged. Also align macaddress_redbox so that ether_addr_copy() and ether_addr_equal() on it are safe on architectures without efficient unaligned access. Signed-off-by: Xin Xie Link: https://patch.msgid.link/20260717201457.54-3-xiexinet@gmail.com Signed-off-by: Jakub Kicinski --- net/hsr/hsr_device.c | 33 ++++++++++++++++++++++++++++++--- net/hsr/hsr_framereg.c | 13 +++++++++++-- net/hsr/hsr_main.h | 2 +- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/net/hsr/hsr_device.c b/net/hsr/hsr_device.c index 5af491ed2b72..0973f9a94f4d 100644 --- a/net/hsr/hsr_device.c +++ b/net/hsr/hsr_device.c @@ -372,10 +372,21 @@ static void send_prp_supervision_frame(struct hsr_port *master, { struct hsr_priv *hsr = master->hsr; struct hsr_sup_payload *hsr_sp; + struct hsr_sup_tlv *hsr_stlv; struct hsr_sup_tag *hsr_stag; struct sk_buff *skb; + bool redbox_proxy; + int extra = 0; - skb = hsr_init_skb(master, 0); + redbox_proxy = hsr->redbox && master->type == HSR_PT_INTERLINK; + + /* A proxy-announce carries a RedBox-MAC TLV and an EOT marker. */ + if (redbox_proxy) + extra = sizeof(struct hsr_sup_tlv) + + sizeof(struct hsr_sup_payload) + + sizeof(struct hsr_sup_tlv); + + skb = hsr_init_skb(master, extra); if (!skb) { netdev_warn_once(master->dev, "PRP: Could not send supervision frame\n"); return; @@ -393,9 +404,25 @@ static void send_prp_supervision_frame(struct hsr_port *master, hsr_stag->tlv.HSR_TLV_type = PRP_TLV_LIFE_CHECK_DD; hsr_stag->tlv.HSR_TLV_length = sizeof(struct hsr_sup_payload); - /* Payload: MacAddressA */ + /* Payload: MacAddressA, the announced node. */ hsr_sp = skb_put(skb, sizeof(struct hsr_sup_payload)); - ether_addr_copy(hsr_sp->macaddress_A, master->dev->dev_addr); + ether_addr_copy(hsr_sp->macaddress_A, addr); + + /* Proxy-announce: append the RedBox-MAC TLV (Type 30) and an explicit + * EOT to terminate the TLV chain before zero padding. + */ + if (redbox_proxy) { + hsr_stlv = skb_put(skb, sizeof(struct hsr_sup_tlv)); + hsr_stlv->HSR_TLV_type = PRP_TLV_REDBOX_MAC; + hsr_stlv->HSR_TLV_length = sizeof(struct hsr_sup_payload); + + hsr_sp = skb_put(skb, sizeof(struct hsr_sup_payload)); + ether_addr_copy(hsr_sp->macaddress_A, hsr->macaddress_redbox); + + hsr_stlv = skb_put(skb, sizeof(struct hsr_sup_tlv)); + hsr_stlv->HSR_TLV_type = HSR_TLV_EOT; + hsr_stlv->HSR_TLV_length = 0; + } if (skb_put_padto(skb, ETH_ZLEN)) { spin_unlock_bh(&hsr->seqnr_lock); diff --git a/net/hsr/hsr_framereg.c b/net/hsr/hsr_framereg.c index 8f708b6e6c33..b3b106be692e 100644 --- a/net/hsr/hsr_framereg.c +++ b/net/hsr/hsr_framereg.c @@ -293,8 +293,17 @@ struct hsr_node *hsr_get_node(struct hsr_port *port, struct list_head *node_db, */ if (ethhdr->h_proto == htons(ETH_P_PRP) || ethhdr->h_proto == htons(ETH_P_HSR)) { - /* Check if skb contains hsr_ethhdr */ - if (skb->mac_len < sizeof(struct hsr_ethhdr)) + bool prp_sup; + + /* A PRP supervision frame is an untagged ETH_P_PRP frame + * (mac_len == ETH_HLEN); its RCT is appended only on egress. + * HSR (ETH_P_HSR) supervision is front-tagged and still must + * contain a struct hsr_ethhdr. + */ + prp_sup = hsr->prot_version == PRP_V1 && + ethhdr->h_proto == htons(ETH_P_PRP) && is_sup; + + if (!prp_sup && skb->mac_len < sizeof(struct hsr_ethhdr)) return NULL; } else { rct = skb_get_PRP_rct(skb); diff --git a/net/hsr/hsr_main.h b/net/hsr/hsr_main.h index 134e4f3fff60..53e95bae0ee2 100644 --- a/net/hsr/hsr_main.h +++ b/net/hsr/hsr_main.h @@ -211,7 +211,7 @@ struct hsr_priv { */ bool fwd_offloaded; /* Forwarding offloaded to HW */ bool redbox; /* Device supports HSR RedBox */ - unsigned char macaddress_redbox[ETH_ALEN]; + unsigned char macaddress_redbox[ETH_ALEN] __aligned(sizeof(u16)); unsigned char sup_multicast_addr[ETH_ALEN] __aligned(sizeof(u16)); /* Align to u16 boundary to avoid unaligned access * in ether_addr_equal From 85abc2db7ba3b5dfd35a037ef3d6135a99b00508 Mon Sep 17 00:00:00 2001 From: Xin Xie Date: Fri, 17 Jul 2026 22:14:56 +0200 Subject: [PATCH 0580/1433] net: hsr: allow PRP RedBox (interlink) creation With the PRP interlink datapath, duplicate discard and supervision support in place, a PRP device can act as a RedBox. Remove the rtnetlink rejection of "type hsr ... interlink proto 1"; the feature is implemented unconditionally by the preceding patches. Signed-off-by: Xin Xie Link: https://patch.msgid.link/20260717201457.54-4-xiexinet@gmail.com Signed-off-by: Jakub Kicinski --- net/hsr/hsr_netlink.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/net/hsr/hsr_netlink.c b/net/hsr/hsr_netlink.c index 8099f2069a74..88940e8014b2 100644 --- a/net/hsr/hsr_netlink.c +++ b/net/hsr/hsr_netlink.c @@ -121,14 +121,8 @@ static int hsr_newlink(struct net_device *dev, } } - if (proto == HSR_PROTOCOL_PRP) { + if (proto == HSR_PROTOCOL_PRP) proto_version = PRP_V1; - if (interlink) { - NL_SET_ERR_MSG_MOD(extack, - "Interlink only works with HSR"); - return -EINVAL; - } - } return hsr_dev_finalize(dev, link, interlink, multicast_spec, proto_version, extack); From 6d548d0fc16058d3c24b9125f43b56cf5369b0d6 Mon Sep 17 00:00:00 2001 From: Xin Xie Date: Fri, 17 Jul 2026 22:14:57 +0200 Subject: [PATCH 0581/1433] selftests: net: hsr: add PRP RedBox test Add a kselftest that builds a PRP RedBox (interlink) with a SAN behind the interlink and a peer DANP, and checks bidirectional unicast across the interlink, preservation of the SAN source MAC on the PRP network, and that the proxy-announce supervision frame carries the RedBox-MAC TLV (Type 30) terminated by an EOT marker. It reuses the hsr_common.sh / lib.sh helpers and skips cleanly on a kernel or iproute2 without PRP interlink support. The background ping is killed by its exact PID: ip netns exec does not isolate the PID namespace, so a pattern-based pkill could hit unrelated processes on the host. Signed-off-by: Xin Xie Link: https://patch.msgid.link/20260717201457.54-5-xiexinet@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/hsr/Makefile | 1 + .../selftests/net/hsr/hsr_prp_redbox.sh | 99 +++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100755 tools/testing/selftests/net/hsr/hsr_prp_redbox.sh diff --git a/tools/testing/selftests/net/hsr/Makefile b/tools/testing/selftests/net/hsr/Makefile index 31fb9326cf53..2150e487ac7d 100644 --- a/tools/testing/selftests/net/hsr/Makefile +++ b/tools/testing/selftests/net/hsr/Makefile @@ -4,6 +4,7 @@ top_srcdir = ../../../../.. TEST_PROGS := \ hsr_ping.sh \ + hsr_prp_redbox.sh \ hsr_redbox.sh \ link_faults.sh \ prp_ping.sh \ diff --git a/tools/testing/selftests/net/hsr/hsr_prp_redbox.sh b/tools/testing/selftests/net/hsr/hsr_prp_redbox.sh new file mode 100755 index 000000000000..479c892225b1 --- /dev/null +++ b/tools/testing/selftests/net/hsr/hsr_prp_redbox.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Test a PRP RedBox (PRP-SAN): a SAN that sits behind the interlink port must +# reach, and be reached by, a peer DANP on the PRP network with its own MAC +# preserved on the wire, and the RedBox must announce the SAN with a RedBox-MAC +# TLV (terminated by an EOT marker) in its PRP supervision frames. +# +# RB PRP RedBox: prp0 over rb_a/rb_b (LAN A/B) + interlink rb_il +# PEER peer DANP : prp0 over pe_a/pe_b, 100.64.0.2 +# SAN SAN : san_il, own MAC, 100.64.0.51 (behind the interlink) + +ipv6=false + +source ./hsr_common.sh + +check_prerequisites + +if ! command -v tcpdump >/dev/null 2>&1; then + echo "SKIP: This test requires tcpdump" + exit $ksft_skip +fi + +if ! ip link help hsr 2>&1 | grep -q interlink; then + echo "SKIP: iproute2 too old (no hsr interlink support)" + exit $ksft_skip +fi + +setup_ns RB PEER SAN +trap 'cleanup_ns "$RB" "$PEER" "$SAN"' EXIT + +ip link add rb_a netns "$RB" type veth peer name pe_a netns "$PEER" +ip link add rb_b netns "$RB" type veth peer name pe_b netns "$PEER" +ip link add rb_il netns "$RB" type veth peer name san_il netns "$SAN" + +ip -n "$RB" link set rb_a up +ip -n "$RB" link set rb_b up +ip -n "$RB" link set rb_il up +ip -n "$PEER" link set pe_a up +ip -n "$PEER" link set pe_b up +ip -n "$SAN" link set san_il up +ip -n "$SAN" addr add 100.64.0.51/24 dev san_il + +# Feature gate: PRP interlink (RedBox) creation. A kernel without PRP RedBox +# support rejects this with -EINVAL, so SKIP rather than FAIL. +if ! ip -n "$RB" link add name prp0 type hsr slave1 rb_a slave2 rb_b \ + interlink rb_il proto 1 2>/dev/null; then + echo "SKIP: kernel without PRP RedBox (interlink) support" + exit $ksft_skip +fi +ip -n "$RB" link set prp0 up +ip -n "$PEER" link add name prp0 type hsr slave1 pe_a slave2 pe_b proto 1 +ip -n "$PEER" link set prp0 up +ip -n "$PEER" addr add 100.64.0.2/24 dev prp0 +sleep 1 + +san_mac=$(ip -n "$SAN" -br link show san_il | awk '{print $3}') +rb_mac=$(ip -n "$RB" -br link show rb_il | awk '{print $3}') + +# Bidirectional unicast across the interlink. +do_ping "$PEER" 100.64.0.51 +do_ping "$SAN" 100.64.0.2 +stop_if_error "PRP RedBox bidirectional unicast failed" + +# The SAN source MAC must be preserved on the PRP network, not laundered to the +# RedBox MAC: the peer resolves the SAN IP to the SAN's own MAC. +neigh=$(ip -n "$PEER" neigh show 100.64.0.51 | awk '{print $5}') +if [ "$neigh" != "$san_mac" ]; then + echo "SAN MAC preservation [ FAIL ]: peer resolved 100.64.0.51 to" \ + "'$neigh', expected $san_mac" 1>&2 + ret=1 +fi +stop_if_error "SAN MAC not preserved on the PRP network" + +# The proxy-announce supervision frame must carry, in order, the life-check TLV +# (type 0x14, len 6) + MacAddressA == SAN MAC + the RedBox-MAC TLV (type 0x1e, +# len 6) + MacAddressRedBox == RedBox MAC + the EOT marker (0x0000). +ip netns exec "$SAN" ping -i 0.2 -q 100.64.0.2 >/dev/null 2>&1 & +ping_pid=$! +cap=$(ip netns exec "$PEER" timeout 5 tcpdump -i pe_a -nn -x \ + "ether proto 0x88fb and ether src $rb_mac" 2>/dev/null || true) +kill "$ping_pid" 2>/dev/null || true +wait "$ping_pid" 2>/dev/null || true + +san_hex=$(echo "$san_mac" | tr -d ':') +rb_hex=$(echo "$rb_mac" | tr -d ':') +# Reassemble contiguous frame hex: drop the "0x0010:" offset labels and spaces. +frame_hex=$(echo "$cap" | awk '/^[[:space:]]*0x[0-9a-f]+:/ { + sub(/^[[:space:]]*0x[0-9a-f]+:[[:space:]]*/, ""); + gsub(/ /, ""); printf "%s", $0 }') +if ! echo "$frame_hex" | grep -q "1406${san_hex}1e06${rb_hex}0000"; then + echo "supervision RedBox-MAC TLV [ FAIL ]: missing SAN MAC, Type-30" \ + "payload, or EOT" 1>&2 + ret=1 +fi +stop_if_error "PRP RedBox supervision RedBox-MAC TLV/EOT check failed" + +echo "INFO: PRP RedBox (PRP-SAN) conformance checks passed" +exit $ret From 7d394ab234f744404f340638b02e9883c2d8be77 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Fri, 24 Jul 2026 11:24:04 +0200 Subject: [PATCH 0582/1433] docs: netdev: clarify handling of idle patches Off-list pings are very bad, but unfortunately too common. Explicitly state that, so that at least LLMs could learn it. Signed-off-by: Paolo Abeni Reviewed-by: Nicolai Buchwitz Reviewed-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/38b75373f813354ad0ee8bfde12ae5a42e9a11b6.1784884817.git.pabeni@redhat.com Signed-off-by: Jakub Kicinski --- Documentation/process/maintainer-netdev.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/process/maintainer-netdev.rst b/Documentation/process/maintainer-netdev.rst index ec7b9aa2877f..e58f541c29e0 100644 --- a/Documentation/process/maintainer-netdev.rst +++ b/Documentation/process/maintainer-netdev.rst @@ -210,6 +210,10 @@ landed - describe your best guess and ask if it's correct. For example:: I don't understand what the next steps are. Person X seems to be unhappy with A, should I do B and repost the patches? +Don't reach out to maintainers or reviewers via private email and/or other +communications channels: all the discussion must remain public, and +requesting special attention is unfair towards the community, at best. + .. _Changes requested: Changes requested From c82ff94592fb68f529afe63ca7f5ddb7dae4ba83 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Fri, 24 Jul 2026 11:24:05 +0200 Subject: [PATCH 0583/1433] docs: netdev: clarify expected interactions with LLMs The official documentation has not captured yet the current impact of AI-generated reviews on the patch process. Explicitly state the status quo and expectations. Signed-off-by: Paolo Abeni Reviewed-by: Nicolai Buchwitz Reviewed-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/83360de7addb13a3b5f4d5e722148f248fdb2ae0.1784884817.git.pabeni@redhat.com Signed-off-by: Jakub Kicinski --- Documentation/process/maintainer-netdev.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Documentation/process/maintainer-netdev.rst b/Documentation/process/maintainer-netdev.rst index e58f541c29e0..1739d9f856c3 100644 --- a/Documentation/process/maintainer-netdev.rst +++ b/Documentation/process/maintainer-netdev.rst @@ -203,6 +203,22 @@ For RFC postings specifically, if nobody responded in a week - reviewers either missed the posting or have no strong opinions. If the code is ready, repost as a PATCH. +There are 2 services actively providing LLM-generated review on posted patches: + +- https://sashiko.dev/ +- https://netdev-ai.bots.linux.dev/sashiko/ + +both use the Sashiko infrastructure on top of different models. Reviews are +available after 24h. Patch authors are expected to proactively look into the +AI-generated reviews and handle such feedback as any other kind of review: +either debate it or address it. In both cases a reply on the mailing list is +expected. + +Authors are strongly encouraged to run LLM reviews on the posted patches in +advance of the actual post. Large series triggering a significant amount of +AI-generated feedback will likely get little attention from maintainers and +reviewers. + Emails saying just "ping" or "bump" are considered rude. If you can't figure out the status of the patch from patchwork or where the discussion has landed - describe your best guess and ask if it's correct. For example:: From 8fe79aa2f1d616b6adfc14d1bcf3db5df5ba0344 Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Thu, 23 Jul 2026 17:02:41 +0300 Subject: [PATCH 0584/1433] selftests: net: Add a missing config option Commit 5cb53743e1ff ("selftests: net: Add a test for BIG TCP in UDP tunnels") used iptables match comment, which was missed from the CI kernel config. Add the missing config option. Signed-off-by: Alice Mikityanska Reviewed-by: Matthieu Baerts Link: https://patch.msgid.link/20260723140241.132120-1-alice.kernel@fastmail.im Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/config | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config index 96fffca6547c..a2d14ec9df1a 100644 --- a/tools/testing/selftests/net/config +++ b/tools/testing/selftests/net/config @@ -82,6 +82,7 @@ CONFIG_NETFILTER=y CONFIG_NETFILTER_ADVANCED=y CONFIG_NETFILTER_XTABLES_LEGACY=y CONFIG_NETFILTER_XT_MATCH_BPF=m +CONFIG_NETFILTER_XT_MATCH_COMMENT=y CONFIG_NETFILTER_XT_MATCH_LENGTH=m CONFIG_NETFILTER_XT_MATCH_POLICY=m CONFIG_NETFILTER_XT_NAT=m From 41ff498d3392113f7aa2bff5762dc7c772b5eca7 Mon Sep 17 00:00:00 2001 From: Tariq Toukan Date: Thu, 23 Jul 2026 12:44:52 +0300 Subject: [PATCH 0585/1433] net/mlx5e: Fix indentation in mlx5e_free_mpwqe_rq_drop_page() Remove a stray leading space before __free_pages() call. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202607142323.qm25Crps-lkp@intel.com/ Signed-off-by: Tariq Toukan Reviewed-by: Cosmin Ratiu Link: https://patch.msgid.link/20260723094452.1888786-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index 0d8248f32fe5..4a8351f95b27 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -695,7 +695,7 @@ static void mlx5e_free_mpwqe_rq_drop_page(struct mlx5e_rq *rq) dma_unmap_page(rq->pdev, rq->wqe_overflow.addr, page_size, rq->buff.map_dir); - __free_pages(rq->wqe_overflow.page, page_order); + __free_pages(rq->wqe_overflow.page, page_order); } static int mlx5e_init_rxq_rq(struct mlx5e_channel *c, struct mlx5e_params *params, From e4213520e9769ae8fc2814e778617b49d1ac0c7f Mon Sep 17 00:00:00 2001 From: Gal Pressman Date: Thu, 23 Jul 2026 11:17:43 +0300 Subject: [PATCH 0586/1433] net/mlx5e: Remove _once from PCI heuristic debug print The _once rate-limiting in slow_pci_heuristic() is unnecessary because this function only runs during probe. Worse, it interacts poorly with dynamic debug: if the first probe happens before dynamic debug is enabled for this callsite, the _once flag is permanently consumed and the message becomes unreachable without reloading the module. Additionally, only the first probed device values were printable in case of multiple devices. Replace with mlx5_core_dbg() which allows enabling the print via dynamic debug at any time and observing it on the next probe. Signed-off-by: Gal Pressman Reviewed-by: Alex Lazar Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260723081743.1868357-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en/params.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/params.c b/drivers/net/ethernet/mellanox/mlx5/core/en/params.c index 1f4a547917ba..5caf7820a136 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/params.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/params.c @@ -545,8 +545,8 @@ bool slow_pci_heuristic(struct mlx5_core_dev *mdev) mlx5_port_max_linkspeed(mdev, &link_speed); pci_bw = pcie_bandwidth_available(mdev->pdev, NULL, NULL, NULL); - mlx5_core_dbg_once(mdev, "Max link speed = %d, PCI BW = %d\n", - link_speed, pci_bw); + mlx5_core_dbg(mdev, "Max link speed = %d, PCI BW = %d\n", link_speed, + pci_bw); #define MLX5E_SLOW_PCI_RATIO (2) From defbb6534ff3a3b91607a842afc72edc1000d447 Mon Sep 17 00:00:00 2001 From: Dragos Tatulea Date: Thu, 23 Jul 2026 10:28:29 +0300 Subject: [PATCH 0587/1433] net/mlx5e: SHAMPO, Remove dead CWR handling in GRO header update mlx5e_shampo_update_ipv{4,6}_tcp_hdr() runs only from the gro_count > 1 path in mlx5e_shampo_flush_skb(). HW-GRO flushes the current session on a CWR packet and delivers it as a single-segment skb via napi_gro_receive(), so the aggregated (gro_count > 1) skb never carries a CWR-set TCP header. This patch drops the unreachable branch. Discussion context: https://lore.kernel.org/all/8b610e49-ff64-497e-8712-588b2228df02@nvidia.com/ Suggested-by: Paolo Abeni Signed-off-by: Dragos Tatulea Cc: Chia-Yu Chang Reviewed-by: Cosmin Ratiu Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260723072829.1864366-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_rx.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c index 04af54b704d8..fb7110b1b683 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c @@ -1178,9 +1178,6 @@ static void mlx5e_shampo_update_ipv4_tcp_hdr(struct mlx5e_rq *rq, struct iphdr * skb->csum_start = (unsigned char *)tcp - skb->head; skb->csum_offset = offsetof(struct tcphdr, check); - - if (tcp->cwr) - skb_shinfo(skb)->gso_type |= SKB_GSO_TCP_ECN; } static void mlx5e_shampo_update_ipv6_tcp_hdr(struct mlx5e_rq *rq, struct ipv6hdr *ipv6, @@ -1199,9 +1196,6 @@ static void mlx5e_shampo_update_ipv6_tcp_hdr(struct mlx5e_rq *rq, struct ipv6hdr skb_shinfo(skb)->gso_type |= SKB_GSO_TCPV6; skb->csum_start = (unsigned char *)tcp - skb->head; skb->csum_offset = offsetof(struct tcphdr, check); - - if (tcp->cwr) - skb_shinfo(skb)->gso_type |= SKB_GSO_TCP_ECN; } static void mlx5e_shampo_update_hdr(struct mlx5e_rq *rq, struct mlx5_cqe64 *cqe, bool match) From 0b7763e3a0ec1cb4b9fd749e29377083ba93d392 Mon Sep 17 00:00:00 2001 From: Yael Chemla Date: Thu, 23 Jul 2026 10:04:27 +0300 Subject: [PATCH 0588/1433] net/mlx5: E-Switch, defer fwd2vport egress ACL allocation On every VF/SF vport enable, esw_acl_egress_ofld_setup() allocates an egress ACL flow table and a fwd_grp whenever the device supports egress_acl_forward_to_vport. The only consumer of that group is the active/passive fwd2vport rule installed when two representor netdevs are bonded - a path that almost never fires. As a result, hosts with many VFs/SFs pay a per-vport flow table and flow group cost for a feature most ports never use. Defer the flow table and fwd_grp creation to the moment they are actually needed, when mlx5e_rep_esw_bond_netevent() drives mlx5_esw_acl_egress_vport_bond() for the passive vport: - esw_acl_egress_ofld_setup() now returns early unless prio_tag_required is set. When prio_tag_required is set the flow table is still allocated eagerly for the VLAN pop rule, and its size is grown by one when fwd2vport is supported so the lazy fwd_grp can later be added without re-creating the table. Only the VLAN group is built up-front. - A new helper, esw_acl_egress_ofld_fwd2vport_setup(), allocates the egress ACL flow table (size 1) and the fwd_grp on demand, and rolls back the flow table if group creation fails and the helper had just allocated it. Existing cleanup paths (esw_acl_egress_ofld_cleanup() -> *_groups_destroy() / *_table_destroy()) already tolerate NULL fields, so vport disable continues to free everything that was actually allocated. - mlx5_esw_acl_egress_vport_bond() calls the helper for the passive vport before installing the fwd2vport rule. The active vport does not need the flow table on its own: with a NULL fwd_dest, esw_acl_egress_ofld_rules_create() is a no-op unless prio_tag_required is set, in which case the eager path already built the table. mlx5_esw_acl_egress_vport_bond() and mlx5_esw_acl_egress_vport_unbond() now take esw->state_lock for the duration of the operation, because they may mutate vport->egress.acl, which is also written by the vport enable/disable path under the same lock. Signed-off-by: Yael Chemla Reviewed-by: Cosmin Ratiu Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260723070427.1861502-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../mellanox/mlx5/core/esw/acl/egress_ofld.c | 105 ++++++++++++------ 1 file changed, 69 insertions(+), 36 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/acl/egress_ofld.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/acl/egress_ofld.c index 24b1ca4e4ff8..981ce4463582 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/acl/egress_ofld.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/acl/egress_ofld.c @@ -113,8 +113,8 @@ static void esw_acl_egress_ofld_rules_destroy(struct mlx5_vport *vport) esw_acl_egress_ofld_bounce_rules_destroy(vport); } -static int esw_acl_egress_ofld_groups_create(struct mlx5_eswitch *esw, - struct mlx5_vport *vport) +static int esw_acl_egress_ofld_fwd_grp_create(struct mlx5_eswitch *esw, + struct mlx5_vport *vport) { int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in); struct mlx5_flow_group *fwd_grp; @@ -122,22 +122,12 @@ static int esw_acl_egress_ofld_groups_create(struct mlx5_eswitch *esw, u32 flow_index = 0; int ret = 0; - if (MLX5_CAP_GEN(esw->dev, prio_tag_required)) { - ret = esw_acl_egress_vlan_grp_create(esw, vport); - if (ret) - return ret; - + if (MLX5_CAP_GEN(esw->dev, prio_tag_required)) flow_index++; - } - - if (!mlx5_esw_acl_egress_fwd2vport_supported(esw)) - goto out; flow_group_in = kvzalloc(inlen, GFP_KERNEL); - if (!flow_group_in) { - ret = -ENOMEM; - goto fwd_grp_err; - } + if (!flow_group_in) + return -ENOMEM; /* This group holds 1 FTE to forward all packets to other vport * when bond vports is supported. @@ -150,16 +140,15 @@ static int esw_acl_egress_ofld_groups_create(struct mlx5_eswitch *esw, esw_warn(esw->dev, "Failed to create vport[%d] egress fwd2vport flow group, err(%d)\n", vport->vport, ret); - kvfree(flow_group_in); - goto fwd_grp_err; + goto out; } vport->egress.offloads.fwd_grp = fwd_grp; - kvfree(flow_group_in); - return 0; + esw_debug(esw->dev, + "lazy-created fwd_grp for vport %d (flow_index=%u)\n", + vport->vport, flow_index); -fwd_grp_err: - esw_acl_egress_vlan_grp_destroy(vport); out: + kvfree(flow_group_in); return ret; } @@ -185,22 +174,23 @@ static bool esw_acl_egress_needed(struct mlx5_eswitch *esw, u16 vport_num) int esw_acl_egress_ofld_setup(struct mlx5_eswitch *esw, struct mlx5_vport *vport) { - int table_size = 0; + int table_size = 1; int err; - if (!mlx5_esw_acl_egress_fwd2vport_supported(esw) && - !MLX5_CAP_GEN(esw->dev, prio_tag_required)) - return 0; - if (!esw_acl_egress_needed(esw, vport->vport)) return 0; + /* The fwd2vport FT/group is created lazily on bond events; if prio_tag + * is not required, skip eager FT allocation here entirely. + */ + if (!MLX5_CAP_GEN(esw->dev, prio_tag_required)) + return 0; + esw_acl_egress_ofld_rules_destroy(vport); + /* Reserve an extra FTE so the fwd_grp can be added lazily later. */ if (mlx5_esw_acl_egress_fwd2vport_supported(esw)) table_size++; - if (MLX5_CAP_GEN(esw->dev, prio_tag_required)) - table_size++; vport->egress.acl = esw_acl_table_create(esw, vport, MLX5_FLOW_NAMESPACE_ESW_EGRESS, table_size); if (IS_ERR(vport->egress.acl)) { @@ -210,21 +200,21 @@ int esw_acl_egress_ofld_setup(struct mlx5_eswitch *esw, struct mlx5_vport *vport } vport->egress.type = VPORT_EGRESS_ACL_TYPE_DEFAULT; - err = esw_acl_egress_ofld_groups_create(esw, vport); + err = esw_acl_egress_vlan_grp_create(esw, vport); if (err) - goto group_err; + goto table_err; esw_debug(esw->dev, "vport[%d] configure egress rules\n", vport->vport); err = esw_acl_egress_ofld_rules_create(esw, vport, NULL); if (err) - goto rules_err; + goto vlan_grp_err; return 0; -rules_err: - esw_acl_egress_ofld_groups_destroy(vport); -group_err: +vlan_grp_err: + esw_acl_egress_vlan_grp_destroy(vport); +table_err: esw_acl_egress_table_destroy(vport); return err; } @@ -236,18 +226,53 @@ void esw_acl_egress_ofld_cleanup(struct mlx5_vport *vport) esw_acl_egress_table_destroy(vport); } +/* Lazily allocate the egress ACL table and fwd_grp for a vport that is about + * to receive a fwd2vport rule. The table is otherwise created eagerly only + * when prio_tag is required. + */ +static int esw_acl_egress_ofld_fwd2vport_setup(struct mlx5_eswitch *esw, + struct mlx5_vport *vport) +{ + struct mlx5_flow_table *acl = NULL; + int err; + + if (!vport->egress.acl) { + acl = esw_acl_table_create(esw, vport, + MLX5_FLOW_NAMESPACE_ESW_EGRESS, 1); + if (IS_ERR(acl)) + return PTR_ERR(acl); + vport->egress.acl = acl; + vport->egress.type = VPORT_EGRESS_ACL_TYPE_DEFAULT; + } + + if (vport->egress.offloads.fwd_grp) + return 0; + + err = esw_acl_egress_ofld_fwd_grp_create(esw, vport); + if (err && acl) + esw_acl_egress_table_destroy(vport); + return err; +} + int mlx5_esw_acl_egress_vport_bond(struct mlx5_eswitch *esw, u16 active_vport_num, u16 passive_vport_num) { struct mlx5_vport *passive_vport = mlx5_eswitch_get_vport(esw, passive_vport_num); struct mlx5_vport *active_vport = mlx5_eswitch_get_vport(esw, active_vport_num); struct mlx5_flow_destination fwd_dest = {}; + int err; if (IS_ERR(active_vport)) return PTR_ERR(active_vport); if (IS_ERR(passive_vport)) return PTR_ERR(passive_vport); + mutex_lock(&esw->state_lock); + + err = esw_acl_egress_ofld_fwd2vport_setup(esw, passive_vport); + if (err) + goto unlock; + /* Cleanup and recreate rules WITHOUT fwd2vport of active vport */ esw_acl_egress_ofld_rules_destroy(active_vport); esw_acl_egress_ofld_rules_create(esw, active_vport, NULL); @@ -259,16 +284,24 @@ int mlx5_esw_acl_egress_vport_bond(struct mlx5_eswitch *esw, u16 active_vport_nu fwd_dest.vport.vhca_id = MLX5_CAP_GEN(esw->dev, vhca_id); fwd_dest.vport.flags = MLX5_FLOW_DEST_VPORT_VHCA_ID; - return esw_acl_egress_ofld_rules_create(esw, passive_vport, &fwd_dest); + err = esw_acl_egress_ofld_rules_create(esw, passive_vport, &fwd_dest); + +unlock: + mutex_unlock(&esw->state_lock); + return err; } int mlx5_esw_acl_egress_vport_unbond(struct mlx5_eswitch *esw, u16 vport_num) { struct mlx5_vport *vport = mlx5_eswitch_get_vport(esw, vport_num); + int err; if (IS_ERR(vport)) return PTR_ERR(vport); + mutex_lock(&esw->state_lock); esw_acl_egress_ofld_rules_destroy(vport); - return esw_acl_egress_ofld_rules_create(esw, vport, NULL); + err = esw_acl_egress_ofld_rules_create(esw, vport, NULL); + mutex_unlock(&esw->state_lock); + return err; } From a50eba1e778ad4da5b6f9ddbbf57dabbea59bc05 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Wed, 22 Jul 2026 18:28:37 -0300 Subject: [PATCH 0589/1433] net: dns_resolver: allow shorter names in dns_query() Customer reported a problem with mounting CIFS shares where the server hostname was 2 chars long. Turned out that the CIFS client wasn't able to resolve NetBIOS names shorter than 3 chars. Fix this by allowing a minimum of one character per hostname in dns_query(). Reproducer with samba server: # 'ab' and 'srv' hotnames resolve to same ip address $ ssh srv ln -s 'msdfs:\\ab\\share' /home/shares/dfs/link1 $ mount.cifs //srv/dfs/link1 /mnt -o ... [EINVAL] Reported-by: Pierguido Lambri Signed-off-by: Paulo Alcantara Acked-by: David Howells Acked-by: Frank Sorenson Link: https://patch.msgid.link/20260722-net-dns_resolver-v1-1-c3385898ccf9@manguebit.org Signed-off-by: Jakub Kicinski --- net/dns_resolver/dns_query.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/dns_resolver/dns_query.c b/net/dns_resolver/dns_query.c index 14bee83cbe22..b8fb9a1245bf 100644 --- a/net/dns_resolver/dns_query.c +++ b/net/dns_resolver/dns_query.c @@ -72,7 +72,7 @@ int dns_query(struct net *net, kenter("%s,%*.*s,%zu,%s", type, (int)namelen, (int)namelen, name, namelen, options); - if (!name || namelen < 3 || namelen > 255) + if (!name || namelen < 1 || namelen > 255) return -EINVAL; if (type && *type == '\0') return -EINVAL; From 72207e1b15d4b9d28a3cbf1ed8f6dcb43bcf2617 Mon Sep 17 00:00:00 2001 From: Hariprasad Kelam Date: Tue, 21 Jul 2026 12:33:03 +0530 Subject: [PATCH 0590/1433] octeontx2-af: npc: Warn on NPC_IPSEC_SPI key overlap When scanning the MKEX profile to determine supported NPC features, warn if the SPI extraction field overlaps with other key fields. AH and ESP may legitimately use the same key offset for SPI, so continue to advertise NPC_IPSEC_SPI via npc_is_field_present() instead of treating the overlap as a hard failure. Signed-off-by: Hariprasad Kelam Signed-off-by: Ratheesh Kannoth Link: https://patch.msgid.link/20260721070303.986740-1-rkannoth@marvell.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c index 91b5947dae06..d422bdd5e8f8 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c @@ -729,7 +729,12 @@ static void npc_set_features(struct rvu *rvu, int blkaddr, u8 intf) if (!npc_check_field(rvu, blkaddr, NPC_LB, intf)) *features &= ~BIT_ULL(NPC_OUTER_VID); - /* Allow extracting SPI field from AH and ESP headers at same offset */ + /* Warn on unrelated MKEX fields colliding with SPI key bits. AH/ESP + * sharing the same SPI key offset is valid; use npc_is_field_present(), + * not npc_check_field(), to advertise the feature. + */ + if (npc_check_overlap(rvu, blkaddr, NPC_IPSEC_SPI, 0, intf)) + dev_warn(rvu->dev, "Overlap detected the field NPC_IPSEC_SPI\n"); if (npc_is_field_present(rvu, NPC_IPSEC_SPI, intf) && (*features & (BIT_ULL(NPC_IPPROTO_ESP) | BIT_ULL(NPC_IPPROTO_AH)))) *features |= BIT_ULL(NPC_IPSEC_SPI); From 11492872341100afefc2b923b4012743c78f5284 Mon Sep 17 00:00:00 2001 From: Joshua Washington Date: Wed, 22 Jul 2026 15:16:33 -0700 Subject: [PATCH 0591/1433] gve: use xdp_build_skb methods for XDP_PASS case Newer common methods have been introduced to construct SKBs in the event of XDP_PASS because many drivers replicated very similar functionality. Update GVE to use these common methods for copy mode and zero-copy mode. Reviewed-by: Harshitha Ramamurthy Reviewed-by: Jordan Rhee Signed-off-by: Joshua Washington Reviewed-by: Larysa Zaremba Link: https://patch.msgid.link/20260722221634.186886-2-joshwash@google.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/google/gve/gve_rx_dqo.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/google/gve/gve_rx_dqo.c b/drivers/net/ethernet/google/gve/gve_rx_dqo.c index 8271f731a91f..97db9f701e63 100644 --- a/drivers/net/ethernet/google/gve/gve_rx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_rx_dqo.c @@ -770,8 +770,7 @@ static int gve_rx_xsk_dqo(struct napi_struct *napi, struct gve_rx_ring *rx, } /* Copy the data to skb */ - rx->ctx.skb_head = gve_rx_copy_data(priv->dev, napi, - xdp->data, buf_len); + rx->ctx.skb_head = xdp_build_skb_from_zc(xdp); if (unlikely(!rx->ctx.skb_head)) { xsk_buff_free(xdp); gve_free_buf_state(rx, buf_state); @@ -779,8 +778,6 @@ static int gve_rx_xsk_dqo(struct napi_struct *napi, struct gve_rx_ring *rx, } rx->ctx.skb_tail = rx->ctx.skb_head; - /* Free XSK buffer and Buffer state */ - xsk_buff_free(xdp); gve_free_buf_state(rx, buf_state); /* Update Stats */ @@ -933,9 +930,17 @@ static int gve_rx_dqo(struct napi_struct *napi, struct gve_rx_ring *rx, return 0; } + rx->ctx.skb_head = xdp_build_skb_from_buff(&gve_xdp.xdp); + if (unlikely(!rx->ctx.skb_head)) + goto error; + rx->ctx.skb_tail = rx->ctx.skb_head; + + gve_reuse_buffer(rx, buf_state); + u64_stats_update_begin(&rx->statss); rx->xdp_actions[XDP_PASS]++; u64_stats_update_end(&rx->statss); + return 0; } if (eop && buf_len <= priv->rx_copybreak && From 871657dc6996ec7e6b90a87369d52a4a59f22753 Mon Sep 17 00:00:00 2001 From: Joshua Washington Date: Wed, 22 Jul 2026 15:16:34 -0700 Subject: [PATCH 0592/1433] gve: add XDP metadata support for DQ RDA Commit 1b42e07af1ee ("gve: Add Rx HWTS metadata to AF_XDP ZC mode") exposes support for the XDP RX timestamping metadata operation in the DQ RDA mode. While the operation works on its own, the intent was to enable XDP metadata support for the queue format as a whole along with it. Currently bpf_xdp_adjust_meta fails because meta_valid is set to false. This change updates xdp_buff preparation to set meta_valid to true, so metadata can be fully used by XDP programs. Reviewed-by: Harshitha Ramamurthy Reviewed-by: Jordan Rhee Signed-off-by: Joshua Washington Link: https://patch.msgid.link/20260722221634.186886-3-joshwash@google.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/google/gve/gve_rx_dqo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/google/gve/gve_rx_dqo.c b/drivers/net/ethernet/google/gve/gve_rx_dqo.c index 97db9f701e63..36ad2b02d7d9 100644 --- a/drivers/net/ethernet/google/gve/gve_rx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_rx_dqo.c @@ -916,7 +916,7 @@ static int gve_rx_dqo(struct napi_struct *napi, struct gve_rx_ring *rx, buf_state->page_info.page_address + buf_state->page_info.page_offset, buf_state->page_info.pad, - buf_len, false); + buf_len, true); gve_xdp.gve = priv; gve_xdp.compl_desc = compl_desc; From b515dc54795ef370be3cb396e7c12ad91686b6d1 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 24 Jul 2026 09:25:00 +0200 Subject: [PATCH 0593/1433] net: ip6_tunnel: use tunnel parameters for fill_forward_path route lookup Reuse the flowi6 template t->fl.u.ip6 built by ip6_tnl_link_config() in ip6_tnl_fill_forward_path(), aligning the fast-path route lookup with the slow path in ipxip6_tnl_xmit(). This automatically inherits the correct conditional FLOWLABEL masking based on the IP6_TNL_F_USE_ORIG_FLOWLABEL flag. Return -EOPNOTSUPP when IP6_TNL_F_USE_ORIG_TCLASS, IP6_TNL_F_USE_ORIG_FLOWLABEL or IP6_TNL_F_USE_ORIG_FWMARK is set, or for collect_md tunnels, since fill_forward_path has no access to the original skb and cannot recover the per-packet traffic class, flowlabel, mark or tunnel destination needed for the route lookup. Reviewed-by: David Ahern Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260724-ip6ip6-route-lookup-fill_forward_path-v3-1-7b7991538614@kernel.org Signed-off-by: Paolo Abeni --- net/ipv6/ip6_tunnel.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index bf8e40af60b0..97c3f61d627b 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -1845,24 +1845,30 @@ static int ip6_tnl_fill_forward_path(struct net_device_path_ctx *ctx, struct net_device_path *path) { struct ip6_tnl *t = netdev_priv(ctx->dev); - struct flowi6 fl6 = { - .daddr = t->parms.raddr, - }; struct dst_entry *dst; + struct flowi6 fl6; int err; - if (!(t->parms.flags & IP6_TNL_F_IGN_ENCAP_LIMIT)) { - /* encaplimit option is currently not supported is - * sw-acceleration path. - */ + if (t->parms.flags & (IP6_TNL_F_USE_ORIG_TCLASS | + IP6_TNL_F_USE_ORIG_FLOWLABEL | + IP6_TNL_F_USE_ORIG_FWMARK)) return -EOPNOTSUPP; - } + + if (t->parms.collect_md) + return -EOPNOTSUPP; + + if (!(t->parms.flags & IP6_TNL_F_IGN_ENCAP_LIMIT)) + return -EOPNOTSUPP; + + memcpy(&fl6, &t->fl.u.ip6, sizeof(fl6)); + fl6.flowi6_mark = t->parms.fwmark; + fl6.flowi6_proto = 0; dst = ip6_route_output(dev_net(ctx->dev), NULL, &fl6); if (!dst->error) { path->type = DEV_PATH_TUN; - path->tun.src_v6 = t->parms.laddr; - path->tun.dst_v6 = t->parms.raddr; + path->tun.src_v6 = fl6.saddr; + path->tun.dst_v6 = fl6.daddr; path->tun.l3_proto = IPPROTO_IPV6; path->dev = ctx->dev; ctx->dev = dst->dev; From c706dc5da6e1764b2e75132f7815f75e75a6a34a Mon Sep 17 00:00:00 2001 From: Srinivas Achary Date: Thu, 23 Jul 2026 19:15:50 +0530 Subject: [PATCH 0594/1433] wifi: cfg80211: change mesh_setup::ie_len to size_t The ie_len field in struct mesh_setup stores the length of the information elements (IEs) buffer. It is currently defined as u8, which limits the maximum supported length to 255 bytes. The IE length is derived from memory buffers whose size is naturally represented by size_t. Using u8 may truncate larger values and can result in incorrect length handling. Change ie_len to size_t so it can represent the full buffer length and match the type commonly used for memory sizes throughout the kernel. Signed-off-by: Ramakrishnan Rathinasamy Signed-off-by: Srinivas Achary Link: https://patch.msgid.link/20260723134550.35167-1-srinivas@aerlync.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 47bc5f55b147..a201979f066e 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -2823,7 +2823,7 @@ struct mesh_setup { u8 path_metric; u8 auth_id; const u8 *ie; - u8 ie_len; + size_t ie_len; bool is_authenticated; bool is_secure; bool user_mpm; From 36743788d685d9a8a7239c32d75f847a06e60d13 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 23 Jul 2026 09:27:50 -0700 Subject: [PATCH 0595/1433] rfkill: repair malformed kernel-doc and add some descriptions Use kernel-doc format for function descriptions and add the missing function parameter descriptions to avoid kernel-doc warnings: Warning: ../include/linux/rfkill.h:102 This comment starts with '/**', but isn't a kernel-doc comment. * rfkill_pause_polling(struct rfkill *rfkill) Warning: include/linux/rfkill.h:109 function parameter 'rfkill' not described in 'rfkill_pause_polling' Warning: ../include/linux/rfkill.h:112 This comment starts with '/**', but isn't a kernel-doc comment. * rfkill_resume_polling(struct rfkill *rfkill) Warning: include/linux/rfkill.h:117 function parameter 'rfkill' not described in 'rfkill_resume_polling' Warning: ../include/linux/rfkill.h:330 function parameter 'rfkill' not described in 'rfkill_get_led_trigger_name' Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260723162750.167914-1-rdunlap@infradead.org Signed-off-by: Johannes Berg --- include/linux/rfkill.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/include/linux/rfkill.h b/include/linux/rfkill.h index 6816e4c5f3f0..deea02a034c3 100644 --- a/include/linux/rfkill.h +++ b/include/linux/rfkill.h @@ -100,7 +100,8 @@ struct rfkill * __must_check rfkill_alloc(const char *name, int __must_check rfkill_register(struct rfkill *rfkill); /** - * rfkill_pause_polling(struct rfkill *rfkill) + * rfkill_pause_polling - Pause polling + * @rfkill: rfkill struct * * Pause polling -- say transmitter is off for other reasons. * NOTE: not necessary for suspend/resume -- in that case the @@ -110,9 +111,9 @@ int __must_check rfkill_register(struct rfkill *rfkill); void rfkill_pause_polling(struct rfkill *rfkill); /** - * rfkill_resume_polling(struct rfkill *rfkill) + * rfkill_resume_polling - Resume polling + * @rfkill: rfkill struct * - * Resume polling * NOTE: not necessary for suspend/resume -- in that case the * core stops polling anyway */ @@ -325,6 +326,8 @@ static inline enum rfkill_type rfkill_find_type(const char *name) #ifdef CONFIG_RFKILL_LEDS /** * rfkill_get_led_trigger_name - Get the LED trigger name for the button's LED. + * @rfkill: rfkill struct + * * This function might return a NULL pointer if registering of the * LED trigger failed. Use this as "default_trigger" for the LED. */ From 8e4f5ca8bf67efc6006c066e873f0535bd7a9cd9 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Fri, 24 Jul 2026 18:36:56 +0800 Subject: [PATCH 0596/1433] wifi: nxpwifi: reject zero-length extension elements in beacon IEs nxpwifi_update_bss_desc_with_ie() dispatches on elem->data[0] for WLAN_EID_EXTENSION without checking that the element has a payload. A well-formed extension element carries at least the element ID extension byte, but nothing enforces that in the IE stream, and the loop accepts a zero-length element because its header alone fits. elem->data[0] then reads the byte after the element, which is past the kmemdup()ed IE buffer when that element ends the stream. Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Link: https://patch.msgid.link/20260724103656.2494129-1-lilinmao@kylinos.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/nxp/nxpwifi/scan.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/nxp/nxpwifi/scan.c b/drivers/net/wireless/nxp/nxpwifi/scan.c index bb3ce2b6f4b9..b77056983e83 100644 --- a/drivers/net/wireless/nxp/nxpwifi/scan.c +++ b/drivers/net/wireless/nxp/nxpwifi/scan.c @@ -1255,6 +1255,9 @@ int nxpwifi_update_bss_desc_with_ie(struct nxpwifi_adapter *adapter, (u16)(current_ptr - bss_entry->beacon_buf); break; case WLAN_EID_EXTENSION: + if (!element_len) + return -EINVAL; + elem = (struct element *)current_ptr; switch (elem->data[0]) { From 094dc1619cb025b6fedbe609d09d3768cf645ab4 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 11:54:26 +0000 Subject: [PATCH 0597/1433] wifi: mac80211: factor out part of ieee80211_calc_expected_tx_airtime Create ieee80211_rate_expected_tx_airtime helper function, which returns the expected tx airtime for a given rate and packet length in units of 1/1024 usec, for more accuracy. Signed-off-by: Felix Fietkau Link: https://patch.msgid.link/20260724115429.3921457-1-nbd@nbd.name Signed-off-by: Johannes Berg --- net/mac80211/airtime.c | 87 ++++++++++++++++++++++---------------- net/mac80211/ieee80211_i.h | 5 +++ 2 files changed, 56 insertions(+), 36 deletions(-) diff --git a/net/mac80211/airtime.c b/net/mac80211/airtime.c index c61df637232a..0c54cdbd753c 100644 --- a/net/mac80211/airtime.c +++ b/net/mac80211/airtime.c @@ -685,7 +685,7 @@ static int ieee80211_fill_rx_status(struct ieee80211_rx_status *stat, if (ieee80211_fill_rate_info(hw, stat, band, ri)) return 0; - if (!ieee80211_rate_valid(rate)) + if (!rate || !ieee80211_rate_valid(rate)) return -1; if (rate->flags & IEEE80211_TX_RC_160_MHZ_WIDTH) @@ -753,6 +753,53 @@ u32 ieee80211_calc_tx_airtime(struct ieee80211_hw *hw, } EXPORT_SYMBOL_GPL(ieee80211_calc_tx_airtime); +u32 ieee80211_rate_expected_tx_airtime(struct ieee80211_hw *hw, + struct ieee80211_tx_rate *tx_rate, + struct rate_info *ri, + enum nl80211_band band, + bool ampdu, int len) +{ + struct ieee80211_rx_status stat; + u32 duration, overhead; + u8 agg_shift; + + if (ieee80211_fill_rx_status(&stat, hw, tx_rate, ri, band, len)) + return 0; + + if (stat.encoding == RX_ENC_LEGACY || !ampdu) + return ieee80211_calc_rx_airtime(hw, &stat, len) * 1024; + + duration = ieee80211_get_rate_duration(hw, &stat, &overhead); + + /* + * Assume that HT/VHT transmission on any AC except VO will + * use aggregation. Since we don't have reliable reporting + * of aggregation length, assume an average size based on the + * tx rate. + * This will not be very accurate, but much better than simply + * assuming un-aggregated tx in all cases. + */ + if (duration > 400 * 1024) /* <= VHT20 MCS2 1S */ + agg_shift = 1; + else if (duration > 250 * 1024) /* <= VHT20 MCS3 1S or MCS1 2S */ + agg_shift = 2; + else if (duration > 150 * 1024) /* <= VHT20 MCS5 1S or MCS2 2S */ + agg_shift = 3; + else if (duration > 70 * 1024) /* <= VHT20 MCS5 2S */ + agg_shift = 4; + else if (stat.encoding != RX_ENC_HE || + duration > 20 * 1024) /* <= HE40 MCS6 2S */ + agg_shift = 5; + else + agg_shift = 6; + + duration *= len; + duration /= AVG_PKT_SIZE; + duration += (overhead * 1024 >> agg_shift); + + return duration; +} + u32 ieee80211_calc_expected_tx_airtime(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *pubsta, @@ -775,45 +822,13 @@ u32 ieee80211_calc_expected_tx_airtime(struct ieee80211_hw *hw, if (pubsta) { struct sta_info *sta = container_of(pubsta, struct sta_info, sta); - struct ieee80211_rx_status stat; struct ieee80211_tx_rate *tx_rate = &sta->deflink.tx_stats.last_rate; struct rate_info *ri = &sta->deflink.tx_stats.last_rate_info; - u32 duration, overhead; - u8 agg_shift; + u32 duration; - if (ieee80211_fill_rx_status(&stat, hw, tx_rate, ri, band, len)) - return 0; - - if (stat.encoding == RX_ENC_LEGACY || !ampdu) - return ieee80211_calc_rx_airtime(hw, &stat, len); - - duration = ieee80211_get_rate_duration(hw, &stat, &overhead); - /* - * Assume that HT/VHT transmission on any AC except VO will - * use aggregation. Since we don't have reliable reporting - * of aggregation length, assume an average size based on the - * tx rate. - * This will not be very accurate, but much better than simply - * assuming un-aggregated tx in all cases. - */ - if (duration > 400 * 1024) /* <= VHT20 MCS2 1S */ - agg_shift = 1; - else if (duration > 250 * 1024) /* <= VHT20 MCS3 1S or MCS1 2S */ - agg_shift = 2; - else if (duration > 150 * 1024) /* <= VHT20 MCS5 1S or MCS2 2S */ - agg_shift = 3; - else if (duration > 70 * 1024) /* <= VHT20 MCS5 2S */ - agg_shift = 4; - else if (stat.encoding != RX_ENC_HE || - duration > 20 * 1024) /* <= HE40 MCS6 2S */ - agg_shift = 5; - else - agg_shift = 6; - - duration *= len; - duration /= AVG_PKT_SIZE; + duration = ieee80211_rate_expected_tx_airtime(hw, tx_rate, ri, + band, true, len); duration /= 1024; - duration += (overhead >> agg_shift); return max_t(u32, duration, 4); } diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 11f449e8ff00..4034b71a31cf 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -2937,6 +2937,11 @@ u8 *ieee80211_get_bssid(struct ieee80211_hdr *hdr, size_t len, extern const struct ethtool_ops ieee80211_ethtool_ops; +u32 ieee80211_rate_expected_tx_airtime(struct ieee80211_hw *hw, + struct ieee80211_tx_rate *tx_rate, + struct rate_info *ri, + enum nl80211_band band, + bool ampdu, int len); u32 ieee80211_calc_expected_tx_airtime(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *pubsta, From 2f925427e27ab684bedd37b0047c89105270747c Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 11:54:27 +0000 Subject: [PATCH 0598/1433] wifi: mac80211: estimate expected throughput if not provided by driver/rc Estimate the tx throughput based on the expected per-packet tx time. This is useful for mesh implementations that rely on expected throughput, e.g. 802.11s or batman-adv. Signed-off-by: Felix Fietkau Link: https://patch.msgid.link/20260724115429.3921457-2-nbd@nbd.name Signed-off-by: Johannes Berg --- net/mac80211/sta_info.c | 49 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index 22eba0e6e54c..c03aca57eb73 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -2797,6 +2797,28 @@ void sta_set_accumulated_removed_links_sinfo(struct sta_info *sta, } } +static u32 sta_estimate_expected_throughput(struct sta_info *sta, + struct rate_info *ri, + struct ieee80211_bss_conf *bss_conf) +{ + struct ieee80211_hw *hw = &sta->sdata->local->hw; + struct ieee80211_chanctx_conf *conf; + u32 duration; + u8 band; + + conf = rcu_dereference(bss_conf->chanctx_conf); + if (!conf) + return 0; + band = conf->def.chan->band; + + duration = ieee80211_rate_expected_tx_airtime(hw, NULL, ri, band, true, 1024); + duration += duration >> 4; /* add assumed packet error rate of ~6% */ + if (!duration) + return 0; + + return ((1024 * USEC_PER_SEC) / duration) * 8; +} + static void sta_set_link_sinfo(struct sta_info *sta, struct link_station_info *link_sinfo, struct ieee80211_link_data *link, @@ -3011,6 +3033,10 @@ static void sta_set_link_sinfo(struct sta_info *sta, link_sinfo->bss_param.beacon_interval = link->conf->beacon_int; thr = sta_get_expected_throughput(sta); + if (!thr && (link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_TX_BITRATE))) + thr = sta_estimate_expected_throughput(sta, + &link_sinfo->txrate, + link->conf); if (thr != 0) { link_sinfo->filled |= @@ -3264,6 +3290,14 @@ void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo, if (thr != 0) { sinfo->filled |= BIT_ULL(NL80211_STA_INFO_EXPECTED_THROUGHPUT); sinfo->expected_throughput = thr; + } else if (!sta->sta.valid_links && + (sinfo->filled & BIT_ULL(NL80211_STA_INFO_TX_BITRATE))) { + thr = sta_estimate_expected_throughput(sta, &sinfo->txrate, + &sdata->vif.bss_conf); + if (thr) { + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_EXPECTED_THROUGHPUT); + sinfo->expected_throughput = thr; + } } if (!(sinfo->filled & BIT_ULL(NL80211_STA_INFO_ACK_SIGNAL)) && @@ -3284,6 +3318,7 @@ void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo, if (sta->sta.valid_links) { struct ieee80211_link_data *link; struct link_sta_info *link_sta; + u32 est_thr = 0; int link_id; sinfo->mlo_params_valid = true; @@ -3295,17 +3330,25 @@ void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo, sinfo->valid_links = sta->sta.valid_links; for_each_valid_link(sinfo, link_id) { + struct link_station_info *link_sinfo = sinfo->links[link_id]; + link_sta = wiphy_dereference(sta->local->hw.wiphy, sta->link[link_id]); link = wiphy_dereference(sdata->local->hw.wiphy, sdata->link[link_id]); - if (!link_sta || !sinfo->links[link_id] || !link) { + if (!link_sta || !link_sinfo || !link) { sinfo->valid_links &= ~BIT(link_id); continue; } - sta_set_link_sinfo(sta, sinfo->links[link_id], - link, tidstats); + sta_set_link_sinfo(sta, link_sinfo, link, tidstats); + if (!thr && + (link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_EXPECTED_THROUGHPUT))) + est_thr += link_sinfo->expected_throughput; + } + if (est_thr) { + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_EXPECTED_THROUGHPUT); + sinfo->expected_throughput = est_thr; } } } From 836c1addd3bf42d47ca5f7a5780a7ec52abf332e Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 11:54:28 +0000 Subject: [PATCH 0599/1433] wifi: mac80211: add AQL support for multicast packets Excessive multicast traffic with little competing unicast traffic can easily flood hardware queues, leading to throughput issues. Additionally, filling the hardware queues with too many packets breaks FQ for multicast data. Fix this by enabling AQL for multicast packets. Signed-off-by: Felix Fietkau Link: https://patch.msgid.link/20260724115429.3921457-3-nbd@nbd.name Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 1 + include/net/mac80211.h | 3 ++- net/mac80211/debugfs.c | 13 ++++++++-- net/mac80211/ieee80211_i.h | 2 ++ net/mac80211/main.c | 1 + net/mac80211/sta_info.c | 17 ++++++++++++- net/mac80211/sta_info.h | 3 ++- net/mac80211/status.c | 5 ++-- net/mac80211/tx.c | 52 ++++++++++++++++++++------------------ 9 files changed, 66 insertions(+), 31 deletions(-) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index a201979f066e..dcbb0e55c32e 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -3742,6 +3742,7 @@ enum wiphy_params_flags { /* The per TXQ device queue limit in airtime */ #define IEEE80211_DEFAULT_AQL_TXQ_LIMIT_L 5000 #define IEEE80211_DEFAULT_AQL_TXQ_LIMIT_H 12000 +#define IEEE80211_DEFAULT_AQL_TXQ_LIMIT_MC 50000 /* The per interface airtime threshold to switch to lower queue limit */ #define IEEE80211_AQL_THRESHOLD 24000 diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 999e5189f113..a68bccebc525 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -1323,6 +1323,7 @@ ieee80211_rate_get_vht_nss(const struct ieee80211_tx_rate *rate) * @status_data: internal data for TX status handling, assigned privately, * see also &enum ieee80211_status_data for the internal documentation * @status_data_idr: indicates status data is IDR allocated ID for ack frame + * @tx_time_mc: TX time estimate is for a multicast frame, used internally * @tx_time_est: TX time estimate in units of 4us, used internally * @control: union part for control data * @control.rates: TX rates array to try @@ -1365,8 +1366,8 @@ struct ieee80211_tx_info { status_data_idr:1, status_data:13, hw_queue:4, + tx_time_mc:1, tx_time_est:10; - /* 1 free bit */ union { struct { diff --git a/net/mac80211/debugfs.c b/net/mac80211/debugfs.c index a4d5461f6480..8ebf5bcf3c0e 100644 --- a/net/mac80211/debugfs.c +++ b/net/mac80211/debugfs.c @@ -210,11 +210,13 @@ static ssize_t aql_pending_read(struct file *file, "VI %u us\n" "BE %u us\n" "BK %u us\n" + "MC %u us\n" "total %u us\n", atomic_read(&local->aql_ac_pending_airtime[IEEE80211_AC_VO]), atomic_read(&local->aql_ac_pending_airtime[IEEE80211_AC_VI]), atomic_read(&local->aql_ac_pending_airtime[IEEE80211_AC_BE]), atomic_read(&local->aql_ac_pending_airtime[IEEE80211_AC_BK]), + atomic_read(&local->aql_mc_pending_airtime), atomic_read(&local->aql_total_pending_airtime)); return simple_read_from_buffer(user_buf, count, ppos, buf, len); @@ -239,7 +241,8 @@ static ssize_t aql_txq_limit_read(struct file *file, "VO %u %u\n" "VI %u %u\n" "BE %u %u\n" - "BK %u %u\n", + "BK %u %u\n" + "MC %u\n", local->aql_txq_limit_low[IEEE80211_AC_VO], local->aql_txq_limit_high[IEEE80211_AC_VO], local->aql_txq_limit_low[IEEE80211_AC_VI], @@ -247,7 +250,8 @@ static ssize_t aql_txq_limit_read(struct file *file, local->aql_txq_limit_low[IEEE80211_AC_BE], local->aql_txq_limit_high[IEEE80211_AC_BE], local->aql_txq_limit_low[IEEE80211_AC_BK], - local->aql_txq_limit_high[IEEE80211_AC_BK]); + local->aql_txq_limit_high[IEEE80211_AC_BK], + local->aql_txq_limit_mc); return simple_read_from_buffer(user_buf, count, ppos, buf, len); } @@ -273,6 +277,11 @@ static ssize_t aql_txq_limit_write(struct file *file, else buf[count] = '\0'; + if (sscanf(buf, "mcast %u", &q_limit_low) == 1) { + local->aql_txq_limit_mc = q_limit_low; + return count; + } + if (sscanf(buf, "%u %u %u", &ac, &q_limit_low, &q_limit_high) != 3) return -EINVAL; diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 4034b71a31cf..a1ef88fe846d 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -1464,10 +1464,12 @@ struct ieee80211_local { spinlock_t handle_wake_tx_queue_lock; u16 airtime_flags; + u32 aql_txq_limit_mc; u32 aql_txq_limit_low[IEEE80211_NUM_ACS]; u32 aql_txq_limit_high[IEEE80211_NUM_ACS]; u32 aql_threshold; atomic_t aql_total_pending_airtime; + atomic_t aql_mc_pending_airtime; atomic_t aql_ac_pending_airtime[IEEE80211_NUM_ACS]; const struct ieee80211_ops *ops; diff --git a/net/mac80211/main.c b/net/mac80211/main.c index f996e15e3d9e..a59837b9f480 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -986,6 +986,7 @@ struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len, spin_lock_init(&local->rx_path_lock); spin_lock_init(&local->queue_stop_reason_lock); + local->aql_txq_limit_mc = IEEE80211_DEFAULT_AQL_TXQ_LIMIT_MC; for (i = 0; i < IEEE80211_NUM_ACS; i++) { INIT_LIST_HEAD(&local->active_txqs[i]); spin_lock_init(&local->active_txq_lock[i]); diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index c03aca57eb73..d12aed9c1756 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -2491,13 +2491,28 @@ EXPORT_SYMBOL(ieee80211_sta_recalc_aggregates); void ieee80211_sta_update_pending_airtime(struct ieee80211_local *local, struct sta_info *sta, u8 ac, - u16 tx_airtime, bool tx_completed) + u16 tx_airtime, bool tx_completed, + bool mcast) { int tx_pending; if (!wiphy_ext_feature_isset(local->hw.wiphy, NL80211_EXT_FEATURE_AQL)) return; + if (mcast) { + if (!tx_completed) { + atomic_add(tx_airtime, &local->aql_mc_pending_airtime); + return; + } + + tx_pending = atomic_sub_return(tx_airtime, + &local->aql_mc_pending_airtime); + if (tx_pending < 0) + atomic_cmpxchg(&local->aql_mc_pending_airtime, + tx_pending, 0); + return; + } + if (!tx_completed) { if (sta) atomic_add(tx_airtime, diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h index 5da3142d8516..ee0d32877c5b 100644 --- a/net/mac80211/sta_info.h +++ b/net/mac80211/sta_info.h @@ -147,7 +147,8 @@ struct airtime_info { void ieee80211_sta_update_pending_airtime(struct ieee80211_local *local, struct sta_info *sta, u8 ac, - u16 tx_airtime, bool tx_completed); + u16 tx_airtime, bool tx_completed, + bool mcast); struct sta_info; diff --git a/net/mac80211/status.c b/net/mac80211/status.c index d635490f59d3..3d811f652603 100644 --- a/net/mac80211/status.c +++ b/net/mac80211/status.c @@ -777,7 +777,7 @@ static void ieee80211_report_used_skb(struct ieee80211_local *local, ieee80211_sta_update_pending_airtime(local, sta, skb_get_queue_mapping(skb), tx_time_est, - true); + true, info->tx_time_mc); rcu_read_unlock(); } @@ -1189,10 +1189,11 @@ void ieee80211_tx_status_ext(struct ieee80211_hw *hw, /* Do this here to avoid the expensive lookup of the sta * in ieee80211_report_used_skb(). */ + bool mcast = IEEE80211_SKB_CB(skb)->tx_time_mc; ieee80211_sta_update_pending_airtime(local, sta, skb_get_queue_mapping(skb), tx_time_est, - true); + true, mcast); ieee80211_info_set_tx_time_est(IEEE80211_SKB_CB(skb), 0); } diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 76489129e9a1..49ef9494cc94 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -4064,20 +4064,20 @@ struct sk_buff *ieee80211_tx_dequeue(struct ieee80211_hw *hw, encap_out: info->control.vif = vif; - if (tx.sta && - wiphy_ext_feature_isset(local->hw.wiphy, NL80211_EXT_FEATURE_AQL)) { - bool ampdu = txq->ac != IEEE80211_AC_VO; + if (wiphy_ext_feature_isset(local->hw.wiphy, NL80211_EXT_FEATURE_AQL)) { + bool ampdu = txq->sta && txq->ac != IEEE80211_AC_VO; u32 airtime; airtime = ieee80211_calc_expected_tx_airtime(hw, vif, txq->sta, skb->len, ampdu); - if (airtime) { - airtime = ieee80211_info_set_tx_time_est(info, airtime); - ieee80211_sta_update_pending_airtime(local, tx.sta, - txq->ac, - airtime, - false); - } + if (!airtime) + return skb; + + airtime = ieee80211_info_set_tx_time_est(info, airtime); + info->tx_time_mc = !tx.sta; + ieee80211_sta_update_pending_airtime(local, tx.sta, txq->ac, + airtime, false, + info->tx_time_mc); } return skb; @@ -4129,6 +4129,7 @@ struct ieee80211_txq *ieee80211_next_txq(struct ieee80211_hw *hw, u8 ac) struct ieee80211_txq *ret = NULL; struct txq_info *txqi = NULL, *head = NULL; bool found_eligible_txq = false; + bool aql_check; spin_lock_bh(&local->active_txq_lock[ac]); @@ -4152,26 +4153,28 @@ struct ieee80211_txq *ieee80211_next_txq(struct ieee80211_hw *hw, u8 ac) if (!head) head = txqi; + aql_check = ieee80211_txq_airtime_check(hw, &txqi->txq); + if (aql_check) + found_eligible_txq = true; + if (txqi->txq.sta) { struct sta_info *sta = container_of(txqi->txq.sta, struct sta_info, sta); - bool aql_check = ieee80211_txq_airtime_check(hw, &txqi->txq); - s32 deficit = ieee80211_sta_deficit(sta, txqi->txq.ac); - if (aql_check) - found_eligible_txq = true; - - if (deficit < 0) + if (ieee80211_sta_deficit(sta, txqi->txq.ac) < 0) { sta->airtime[txqi->txq.ac].deficit += sta->airtime_weight; - if (deficit < 0 || !aql_check) { - list_move_tail(&txqi->schedule_order, - &local->active_txqs[txqi->txq.ac]); - goto begin; + aql_check = false; } } + if (!aql_check) { + list_move_tail(&txqi->schedule_order, + &local->active_txqs[txqi->txq.ac]); + goto begin; + } + if (txqi->schedule_round == local->schedule_round[ac]) goto out; @@ -4238,7 +4241,8 @@ bool ieee80211_txq_airtime_check(struct ieee80211_hw *hw, return true; if (!txq->sta) - return true; + return atomic_read(&local->aql_mc_pending_airtime) < + local->aql_txq_limit_mc; if (unlikely(txq->tid == IEEE80211_NUM_TIDS)) return true; @@ -4287,15 +4291,15 @@ bool ieee80211_txq_may_transmit(struct ieee80211_hw *hw, spin_lock_bh(&local->active_txq_lock[ac]); - if (!txqi->txq.sta) - goto out; - if (list_empty(&txqi->schedule_order)) goto out; if (!ieee80211_txq_schedule_airtime_check(local, ac)) goto out; + if (!txqi->txq.sta) + goto out; + list_for_each_entry_safe(iter, tmp, &local->active_txqs[ac], schedule_order) { if (iter == txqi) From 9a197e71eb7b45860e37a9f3bdf61a843781ae12 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 11:54:29 +0000 Subject: [PATCH 0600/1433] wifi: mac80211: add ieee80211_txq_aql_pending() Add a function to allow drivers to query the pending AQL airtime for a given txq, for both unicast and broadcast. This will be used for mt76 to limit buffering in AP mode for power-save stations. Signed-off-by: Felix Fietkau Link: https://patch.msgid.link/20260724115429.3921457-4-nbd@nbd.name Signed-off-by: Johannes Berg --- include/net/mac80211.h | 11 +++++++++++ net/mac80211/tx.c | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/include/net/mac80211.h b/include/net/mac80211.h index a68bccebc525..9d1fac6e8082 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -6888,6 +6888,17 @@ void ieee80211_sta_register_airtime(struct ieee80211_sta *pubsta, u8 tid, bool ieee80211_txq_airtime_check(struct ieee80211_hw *hw, struct ieee80211_txq *txq); +/** + * ieee80211_txq_aql_pending - get pending AQL airtime for a txq + * + * @hw: pointer obtained from ieee80211_alloc_hw() + * @txq: pointer obtained from station or virtual interface + * + * Return: pending airtime (in usec) for the given txq. + */ +u32 ieee80211_txq_aql_pending(struct ieee80211_hw *hw, + struct ieee80211_txq *txq); + /** * ieee80211_iter_keys - iterate keys programmed into the device * @hw: pointer obtained from ieee80211_alloc_hw() diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 49ef9494cc94..0cf5f6ec75e6 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -4262,6 +4262,24 @@ bool ieee80211_txq_airtime_check(struct ieee80211_hw *hw, } EXPORT_SYMBOL(ieee80211_txq_airtime_check); +u32 ieee80211_txq_aql_pending(struct ieee80211_hw *hw, + struct ieee80211_txq *txq) +{ + struct ieee80211_local *local = hw_to_local(hw); + struct sta_info *sta; + + if (unlikely(txq->tid == IEEE80211_NUM_TIDS)) + return 0; + + if (!txq->sta) + return atomic_read(&local->aql_mc_pending_airtime); + + sta = container_of(txq->sta, struct sta_info, sta); + + return atomic_read(&sta->airtime[txq->ac].aql_tx_pending); +} +EXPORT_SYMBOL(ieee80211_txq_aql_pending); + static bool ieee80211_txq_schedule_airtime_check(struct ieee80211_local *local, u8 ac) { From ef06882c7d8a7400b67d0d003b1008093dd589ed Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Fri, 24 Jul 2026 17:33:19 -0300 Subject: [PATCH 0601/1433] wifi: mwifiex: Detach sync cmd buffer on interrupted wait mwifiex synchronous commands keep the caller-provided data buffer in cmd_node->data_buf. Several callers pass stack-allocated objects there. If wait_event_interruptible_timeout() is interrupted, the caller can return and release that stack object while the firmware command is still the current command. A late firmware response then reaches the normal response handler, which can copy data through cmd_node->data_buf into the stale stack address. This fixes a stack corruption observed during repeated association and disassociation cycles. The panic trace showed the command wait being interrupted immediately before a bad pointer dereference: cmd_wait_q terminated: -512 Unable to handle kernel paging request at virtual address 002c583837384662 Kernel panic - not syncing: stack-protector: Kernel stack is corrupted ... Tainted: [M]=MACHINE_CHECK The fault address decodes as little-endian ASCII: 0x002c583837384662 -> "bF878X,\0" which is a fragment of the VERSION_EXT firmware string exposed as debugfs "verext": w8997o-V4, RF878X, FP92, 16.92.21.p153.7 The same runs also showed corrupted control data containing: 0x2400372e333531 -> "153.7\0$" which is the tail of the same VERSION_EXT string. This points at a late VERSION_EXT response writing through a stale stack-backed data_buf after the interrupted wait returned. After cancelling pending commands on an interrupted or timed-out wait, detach the caller-owned data buffer from the still-current command. This preserves the existing command cancellation behaviour while preventing a late response from writing through a pointer whose lifetime ended with the waiting caller. Tested on an i.MX8MP board using an 88W8997. Cc: stable@vger.kernel.org Fixes: 3d026d09b28d ("mwifiex: cancel pending commands for signal") Signed-off-by: Fabio Estevam Link: https://patch.msgid.link/20260724203320.78793-1-festevam@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/sta_ioctl.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/net/wireless/marvell/mwifiex/sta_ioctl.c b/drivers/net/wireless/marvell/mwifiex/sta_ioctl.c index 9460d5352b23..19196848778c 100644 --- a/drivers/net/wireless/marvell/mwifiex/sta_ioctl.c +++ b/drivers/net/wireless/marvell/mwifiex/sta_ioctl.c @@ -57,6 +57,18 @@ int mwifiex_wait_queue_complete(struct mwifiex_adapter *adapter, mwifiex_dbg(adapter, ERROR, "cmd_wait_q terminated: %d\n", status); mwifiex_cancel_all_pending_cmd(adapter); + + /* The command response path writes through cmd_node->data_buf. + * On an interrupted wait, the caller can return and release a + * stack-allocated data_buf before a late firmware response is + * processed. Detach the caller-owned buffer from the current + * command so a late response cannot corrupt freed stack memory. + */ + spin_lock_bh(&adapter->mwifiex_cmd_lock); + if (adapter->curr_cmd == cmd_queued) + adapter->curr_cmd->data_buf = NULL; + spin_unlock_bh(&adapter->mwifiex_cmd_lock); + return status; } From 058d979d4f418510279383e508ebc0795ab956f2 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Fri, 24 Jul 2026 17:33:20 -0300 Subject: [PATCH 0602/1433] wifi: mwifiex: Remove WQ_HIGHPRI from main workqueue The MWIFIEX_WORK_QUEUE handles command and event processing, including the commands used for scheduled scans. Running this work on the high-priority worker pool can interfere with latency-sensitive workloads. On an i.MX8MP-based audio system using an 88W8997, background scheduled scans caused audible glitches in USB audio playback. Remove WQ_HIGHPRI from the main workqueue so that command and scan processing use the normal-priority worker pool. Leave the RX and host MLME workqueues unchanged. Signed-off-by: Fabio Estevam Link: https://patch.msgid.link/20260724203320.78793-2-festevam@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c index a8eab6b1e63b..c8df3edff675 100644 --- a/drivers/net/wireless/marvell/mwifiex/main.c +++ b/drivers/net/wireless/marvell/mwifiex/main.c @@ -1550,7 +1550,7 @@ mwifiex_reinit_sw(struct mwifiex_adapter *adapter) adapter->workqueue = alloc_workqueue("MWIFIEX_WORK_QUEUE", - WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, 0); + WQ_MEM_RECLAIM | WQ_UNBOUND, 0); if (!adapter->workqueue) goto err_kmalloc; @@ -1716,7 +1716,7 @@ mwifiex_add_card(void *card, struct completion *fw_done, adapter->workqueue = alloc_workqueue("MWIFIEX_WORK_QUEUE", - WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, 0); + WQ_MEM_RECLAIM | WQ_UNBOUND, 0); if (!adapter->workqueue) goto err_kmalloc; From 7d86b0a8aceff34f7178e39261966903de01c2a1 Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Mon, 27 Jul 2026 12:57:14 +0300 Subject: [PATCH 0603/1433] wifi: mac80211: simplify airtime_flags_write() Use 'kstrtou16_from_user()' to simplify 'airtime_flags_write()'. Signed-off-by: Dmitry Antipov Link: https://patch.msgid.link/20260727095714.347039-1-dmantipov@yandex.ru Signed-off-by: Johannes Berg --- net/mac80211/debugfs.c | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/net/mac80211/debugfs.c b/net/mac80211/debugfs.c index 8ebf5bcf3c0e..105653a16b68 100644 --- a/net/mac80211/debugfs.c +++ b/net/mac80211/debugfs.c @@ -171,23 +171,10 @@ static ssize_t airtime_flags_write(struct file *file, size_t count, loff_t *ppos) { struct ieee80211_local *local = file->private_data; - char buf[16]; + int ret; - if (count >= sizeof(buf)) - return -EINVAL; - - if (copy_from_user(buf, user_buf, count)) - return -EFAULT; - - if (count && buf[count - 1] == '\n') - buf[count - 1] = '\0'; - else - buf[count] = '\0'; - - if (kstrtou16(buf, 0, &local->airtime_flags)) - return -EINVAL; - - return count; + ret = kstrtou16_from_user(user_buf, count, 0, &local->airtime_flags); + return ret ? : count; } static const struct debugfs_short_fops airtime_flags_ops = { From 4a0bd262df757b25fc4e2a53c947317c119ced4e Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Tue, 28 Jul 2026 19:13:26 +0800 Subject: [PATCH 0604/1433] wifi: mac80211: fix per-STA profile length in cross-link CSA parsing ieee80211_mgd_check_cross_link_csa() starts parsing elements after the fixed per-STA profile header and the STA Info field, but subtracts only the STA Info length from the profile length. As a result, ieee802_11_parse_elems() is given sizeof(*prof) == 3 bytes beyond the current profile's element area, and data following the profile may be interpreted as belonging to it. Subtract the fixed profile header as well. The preceding ieee80211_mle_basic_sta_prof_size_ok() check guarantees that the corrected calculation cannot underflow, and ieee80211_rx_uhr_link_reconfig_req() uses the same calculation. The call site currently states that cross-link CSA parsing has no effect because the broader parsing is still incorrect. This patch does not address that broader problem; it only makes the per-STA profile parser stop at the end of that profile. No production allocation over-read or user-visible failure has been demonstrated. Fixes: 7ef8f6821d16 ("wifi: mac80211: mlme: handle cross-link CSA") Assisted-by: Codex:gpt-5.6-sol Assisted-by: Kimi:K3 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260728111326.63087-1-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/mlme.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 50587ab110d2..f51167f0fc46 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -8027,7 +8027,7 @@ ieee80211_mgd_check_cross_link_csa(struct ieee80211_sub_if_data *sdata, prof = (void *)sta_profiles[link_id]; prof_elems = ieee802_11_parse_elems(prof->variable + (prof->sta_info_len - 1), - len - + len - sizeof(*prof) - (prof->sta_info_len - 1), IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_BEACON, From 16812d9674d4991ebbae80769b15f7342dbfa988 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Fri, 24 Jul 2026 14:07:54 -0700 Subject: [PATCH 0605/1433] net_shaper: remove incorrect comment about group leaves It is true that the user-facing group() operation can only be invoked with queues as leaves (see net_shaper_parse_leaf()), but the driver facing op is also called when we delete a node. When we delete a node we conceptually call group(parent, node.list_of_leaves) to add node's leaves to the parent. Node deletion "mid-hierarchy" is supported so some of the leaves may themselves be nodes. Therefore the driver facing group() may be called with nodes. Remove the incorrect comment, and add a comment about differences between the Netlink API and driver facing API. Link: https://patch.msgid.link/20260724210756.1553565-2-kuba@kernel.org Signed-off-by: Jakub Kicinski --- include/net/net_shaper.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/include/net/net_shaper.h b/include/net/net_shaper.h index 3939b816b001..0fcca29207ac 100644 --- a/include/net/net_shaper.h +++ b/include/net/net_shaper.h @@ -72,6 +72,18 @@ struct net_shaper { * * Each shaper is uniquely identified within the device with a 'handle' * comprising the shaper scope and a scope-specific id. + * + * Driver ops vs uAPI + * ------------------ + * Members of the driver ops mirror the Netlink uAPI but driver calls do not + * map 1:1 to user calls. Drivers need to be careful when assuming that calls + * disallowed at the uAPI level will never be made at the driver level. + * The shaper core performs automatic reparenting and cleanup, generating + * additional calls. Notably: + * - @group calls in the driver facing API may have nodes as leaves (user is + * only allowed to construct groups with queues as leaves) + * - @group calls may update leaf's parent if the parent is about + * to be removed (re-parenting nodes explicitly is not supported in the uAPI) */ struct net_shaper_ops { /** @@ -82,7 +94,6 @@ struct net_shaper_ops { * The @leaves arrays size is specified by @leaves_count. * Create either the @leaves and the @node shaper; or if they already * exists, links them together in the desired way. - * @leaves scope must be NET_SHAPER_SCOPE_QUEUE. */ int (*group)(struct net_shaper_binding *binding, int leaves_count, const struct net_shaper *leaves, From 26bc4cfb17374f69717970699b4ccbdb1fd2a027 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Fri, 24 Jul 2026 14:07:55 -0700 Subject: [PATCH 0606/1433] net_shaper: clarify the kernel API / comments The shaper API takes some getting used to. Try to improve the doc on struct net_shaper_ops to help driver developers. Link: https://patch.msgid.link/20260724210756.1553565-3-kuba@kernel.org Signed-off-by: Jakub Kicinski --- include/net/net_shaper.h | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/include/net/net_shaper.h b/include/net/net_shaper.h index 0fcca29207ac..c14eb87efe5e 100644 --- a/include/net/net_shaper.h +++ b/include/net/net_shaper.h @@ -68,7 +68,7 @@ struct net_shaper { * The operations are serialized via a per device lock. * * Device not supporting any kind of nesting should not provide the - * group operation. + * @group operation. * * Each shaper is uniquely identified within the device with a 'handle' * comprising the shaper scope and a scope-specific id. @@ -84,16 +84,30 @@ struct net_shaper { * only allowed to construct groups with queues as leaves) * - @group calls may update leaf's parent if the parent is about * to be removed (re-parenting nodes explicitly is not supported in the uAPI) + * + * Implicit creation + * ----------------- + * Shapers are created implicitly, meaning that @set and @group operations + * are called both for existing and new shapers. The driver has to infer + * whether the operation is an update or a creation by tracking the handles. + * Removal of shapers is explicit and done with a @delete call. + * + * The @set operation implicitly creates NET_SHAPER_SCOPE_NETDEV and + * NET_SHAPER_SCOPE_QUEUE shapers. + * The @group operation implicitly creates NET_SHAPER_SCOPE_NETDEV and + * NET_SHAPER_SCOPE_NODE shapers (the group shaper itself), as well as + * NET_SHAPER_SCOPE_QUEUE shapers (leaves). */ struct net_shaper_ops { /** - * @group: create the specified shapers scheduling group + * @group: create a scheduling group or add leaves * - * Nest the @leaves shapers identified under the * @node shaper. + * Nest the @leaves shapers identified under the @node shaper. * All the shapers belong to the device specified by @binding. - * The @leaves arrays size is specified by @leaves_count. - * Create either the @leaves and the @node shaper; or if they already - * exists, links them together in the desired way. + * The @leaves array's size is specified by @leaves_count. + * + * @node and @leaves may or may not already exist + * (see the "Implicit creation" note). */ int (*group)(struct net_shaper_binding *binding, int leaves_count, const struct net_shaper *leaves, From ff6461c1483420d0da542ff085dbd94e841afc1a Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Fri, 24 Jul 2026 14:07:56 -0700 Subject: [PATCH 0607/1433] net_shaper: add some notes on re-parenting Clarify the re-parenting expectations. Specifically that @delete on a queue removes it from the hierarchy which is a bit unusual in the overall API structure. IIRC the implicit delete behavior was introduced because otherwise it would not be possible to remove a queue from the hierarchy without changing at least one handle of the shapers. Normally "removal" is done by "adding" to the new parent, but "outside the hierarchy" does not have a parent we can point at. Link: https://patch.msgid.link/20260724210756.1553565-4-kuba@kernel.org Signed-off-by: Jakub Kicinski --- include/net/net_shaper.h | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/include/net/net_shaper.h b/include/net/net_shaper.h index c14eb87efe5e..05cb625b0fe5 100644 --- a/include/net/net_shaper.h +++ b/include/net/net_shaper.h @@ -107,7 +107,12 @@ struct net_shaper_ops { * The @leaves array's size is specified by @leaves_count. * * @node and @leaves may or may not already exist - * (see the "Implicit creation" note). + * (see the "Implicit creation" note). If @node already exists, + * the @leaves should be *added* to its children. In this case, + * the @leaves array only holds new/modified leaves, not the full list. + * + * Re-parenting @leaves is implemented by a @group call on a new parent. + * There's no explicit call to remove the children from the old parent. */ int (*group)(struct net_shaper_binding *binding, int leaves_count, const struct net_shaper *leaves, @@ -128,6 +133,13 @@ struct net_shaper_ops { * * Removes the shaper configuration as identified by the given @handle * on the device specified by @binding, restoring the default behavior. + * + * Note that a @delete call on a NET_SHAPER_SCOPE_QUEUE shaper also + * implicitly removes the associated queue from the scheduling + * hierarchy. The driver must take care of that step. + * @delete calls on NET_SHAPER_SCOPE_NODE should not require any + * implicit re-parenting in the driver as core will re-parent the leaves + * first, before deleting the SCOPE_NODE shaper. */ int (*delete)(struct net_shaper_binding *binding, const struct net_shaper_handle *handle, From 2bb54b49e9d522f54dc9c0fe10ba40fbc56041c8 Mon Sep 17 00:00:00 2001 From: Qingfang Deng Date: Mon, 27 Jul 2026 11:28:00 +0800 Subject: [PATCH 0608/1433] ppp: convert chan_sem to a mutex chan_sem's read-side lock is taken under another mutex, so there is no benefit in keeping it as an rwsem. Replace the rwsem with a mutex. Signed-off-by: Qingfang Deng Link: https://patch.msgid.link/20260727032802.4090-1-qingfang.deng@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ppp/ppp_generic.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c index cacc4c3a37d2..08bb89765487 100644 --- a/drivers/net/ppp/ppp_generic.c +++ b/drivers/net/ppp/ppp_generic.c @@ -39,7 +39,6 @@ #include #include #include -#include #include #include #include @@ -176,7 +175,7 @@ struct channel { struct ppp_file file; /* stuff for read/write/poll */ struct list_head list; /* link in all/new_channels list */ struct ppp_channel *chan; /* public channel data structure */ - struct rw_semaphore chan_sem; /* protects `chan' during chan ioctl */ + struct mutex chan_sem; /* protects `chan' during chan ioctl */ spinlock_t downl; /* protects `chan', file.xq dequeue */ struct ppp __rcu *ppp; /* ppp unit we're connected to */ struct net *chan_net; /* the net channel belongs to */ @@ -788,12 +787,12 @@ static long ppp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) break; default: - down_read(&pch->chan_sem); + mutex_lock(&pch->chan_sem); chan = pch->chan; err = -ENOTTY; if (chan && chan->ops->ioctl) err = chan->ops->ioctl(chan, cmd, arg); - up_read(&pch->chan_sem); + mutex_unlock(&pch->chan_sem); } goto out; } @@ -2922,7 +2921,7 @@ int ppp_register_net_channel(struct net *net, struct ppp_channel *chan) #ifdef CONFIG_PPP_MULTILINK pch->lastseq = -1; #endif /* CONFIG_PPP_MULTILINK */ - init_rwsem(&pch->chan_sem); + mutex_init(&pch->chan_sem); spin_lock_init(&pch->downl); spin_lock_init(&pch->upl); @@ -3005,11 +3004,11 @@ ppp_unregister_channel(struct ppp_channel *chan) * the channel's start_xmit or ioctl routine before we proceed. */ ppp_disconnect_channel(pch); - down_write(&pch->chan_sem); + mutex_lock(&pch->chan_sem); spin_lock_bh(&pch->downl); pch->chan = NULL; spin_unlock_bh(&pch->downl); - up_write(&pch->chan_sem); + mutex_unlock(&pch->chan_sem); pn = ppp_pernet(pch->chan_net); spin_lock_bh(&pn->all_channels_lock); @@ -3600,6 +3599,7 @@ static void ppp_release_channel(struct channel *pch) pr_err("ppp: destroying undead channel %p !\n", pch); return; } + mutex_destroy(&pch->chan_sem); call_rcu(&pch->rcu, ppp_release_channel_free); } From 878654eb78c6aa0ff585baf1376567c775ca28ec Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Fri, 24 Jul 2026 08:56:13 -0700 Subject: [PATCH 0609/1433] wifi: ath12k: fix overreads in ath12k_wmi_process_csa_switch_count_event() There is no policy entry for WMI_TAG_PDEV_CSA_SWITCH_COUNT_STATUS_EVENT, so the parse infrastructure does not enforce a minimum length for the event struct. Additionally, the num_vdevs field is taken directly from firmware and used as a loop bound over the vdev_ids array without checking that it fits within the TLV payload. Either condition can cause an out-of-bounds read. Add a TLV policy entry for WMI_TAG_PDEV_CSA_SWITCH_COUNT_STATUS_EVENT so the parse infrastructure enforces a minimum length for the fixed-size event struct. Add a helper ath12k_wmi_tlv_data_len() to recover the payload length of a parsed TLV from the header preceding its data pointer. Use it in ath12k_wmi_process_csa_switch_count_event() to bound num_vdevs before the loop. Compile tested only. Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260724-ath12k_wmi_process_csa_switch_count_event-cleanup-v2-1-02a45d7246c0@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index 2b707ffc1a20..c2833d1e95d3 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -207,6 +207,8 @@ static const struct ath12k_wmi_tlv_policy ath12k_wmi_tlv_policies[] = { .min_len = sizeof(struct wmi_per_chain_rssi_stat_params) }, [WMI_TAG_OBSS_COLOR_COLLISION_EVT] = { .min_len = sizeof(struct wmi_obss_color_collision_event) }, + [WMI_TAG_PDEV_CSA_SWITCH_COUNT_STATUS_EVENT] = { + .min_len = sizeof(struct ath12k_wmi_pdev_csa_event) }, }; __le32 ath12k_wmi_tlv_hdr(u32 cmd, u32 len) @@ -374,6 +376,13 @@ ath12k_wmi_tlv_parse(struct ath12k_base *ab, struct sk_buff *skb) return tb; } +static u32 ath12k_wmi_tlv_data_len(const void *data) +{ + const struct wmi_tlv *tlv = (const struct wmi_tlv *)data - 1; + + return le32_get_bits(tlv->header, WMI_TLV_LEN); +} + static int ath12k_wmi_cmd_send_nowait(struct ath12k_wmi_pdev *wmi, struct sk_buff *skb, u32 cmd_id) { @@ -9075,12 +9084,19 @@ ath12k_wmi_process_csa_switch_count_event(struct ath12k_base *ab, const u32 *vdev_ids) { u32 current_switch_count = le32_to_cpu(ev->current_switch_count); + u32 vdev_ids_len = ath12k_wmi_tlv_data_len(vdev_ids); u32 num_vdevs = le32_to_cpu(ev->num_vdevs); struct ieee80211_bss_conf *conf; struct ath12k_link_vif *arvif; struct ath12k_vif *ahvif; int i; + if (num_vdevs > vdev_ids_len / sizeof(*vdev_ids)) { + ath12k_warn(ab, "csa switch count num_vdevs %u exceeds tlv array length %u\n", + num_vdevs, vdev_ids_len); + return; + } + rcu_read_lock(); for (i = 0; i < num_vdevs; i++) { arvif = ath12k_mac_get_arvif_by_vdev_id(ab, vdev_ids[i]); From 208d7fdb85976a737a715b81d54efaff6703880c Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Fri, 24 Jul 2026 08:56:14 -0700 Subject: [PATCH 0610/1433] wifi: ath11k: fix overreads in ath11k_wmi_process_csa_switch_count_event() There is no policy entry for WMI_TAG_PDEV_CSA_SWITCH_COUNT_STATUS_EVENT, so the parse infrastructure does not enforce a minimum length for the event struct. Additionally, the num_vdevs field is taken directly from firmware and used as a loop bound over the vdev_ids array without checking that it fits within the TLV payload. Either condition can cause an out-of-bounds read. Add a TLV policy entry for WMI_TAG_PDEV_CSA_SWITCH_COUNT_STATUS_EVENT so the parse infrastructure enforces a minimum length for the fixed-size event struct. Add a helper ath11k_wmi_tlv_data_len() to recover the payload length of a parsed TLV from the header preceding its data pointer. Use it in ath11k_wmi_process_csa_switch_count_event() to bound num_vdevs before the loop. Compile tested only. Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices") Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260724-ath12k_wmi_process_csa_switch_count_event-cleanup-v2-2-02a45d7246c0@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/wmi.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c index 4cbd7293845a..d6feaa710fe2 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.c +++ b/drivers/net/wireless/ath/ath11k/wmi.c @@ -159,6 +159,8 @@ static const struct wmi_tlv_policy wmi_tlv_policies[] = { .min_len = sizeof(struct ath11k_wmi_p2p_noa_info) }, [WMI_TAG_P2P_NOA_EVENT] = { .min_len = sizeof(struct wmi_p2p_noa_event) }, + [WMI_TAG_PDEV_CSA_SWITCH_COUNT_STATUS_EVENT] = { + .min_len = sizeof(struct wmi_pdev_csa_switch_ev) }, }; #define PRIMAP(_hw_mode_) \ @@ -262,6 +264,13 @@ const void **ath11k_wmi_tlv_parse_alloc(struct ath11k_base *ab, return tb; } +static u32 ath11k_wmi_tlv_data_len(const void *data) +{ + const struct wmi_tlv *tlv = (const struct wmi_tlv *)data - 1; + + return FIELD_GET(WMI_TLV_LEN, tlv->header); +} + static int ath11k_wmi_cmd_send_nowait(struct ath11k_pdev_wmi *wmi, struct sk_buff *skb, u32 cmd_id) { @@ -8359,15 +8368,23 @@ ath11k_wmi_process_csa_switch_count_event(struct ath11k_base *ab, const struct wmi_pdev_csa_switch_ev *ev, const u32 *vdev_ids) { - int i; + u32 vdev_ids_len = ath11k_wmi_tlv_data_len(vdev_ids); + u32 num_vdevs = ev->num_vdevs; struct ath11k_vif *arvif; + int i; /* Finish CSA once the switch count becomes NULL */ if (ev->current_switch_count) return; + if (num_vdevs > vdev_ids_len / sizeof(*vdev_ids)) { + ath11k_warn(ab, "csa switch count num_vdevs %u exceeds tlv array length %u\n", + num_vdevs, vdev_ids_len); + return; + } + rcu_read_lock(); - for (i = 0; i < ev->num_vdevs; i++) { + for (i = 0; i < num_vdevs; i++) { arvif = ath11k_mac_get_arvif_by_vdev_id(ab, vdev_ids[i]); if (!arvif) { From 8e415b8068480d51a057197ded974e2637e8c42b Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Sun, 26 Jul 2026 16:02:07 -0700 Subject: [PATCH 0611/1433] wifi: ath12k: validate TLV length in process_tpc_stats() The outer skb->len guard only confirms the SKB is large enough to hold the full fixed_param struct, but the TLV's own WMI_TLV_LEN field is never checked. Firmware advertising a TLV length shorter than sizeof(*fixed_param) causes reads of pdev_id and event_count beyond the declared TLV payload. Add a check that the TLV length is at least sizeof(*fixed_param) before casting and dereferencing the pointer. Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260726-ath12k_wmi_process_tpc_stats-len-check-v1-1-c4ba2f84d9c6@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index c2833d1e95d3..e9e7566e0f69 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -9986,6 +9986,7 @@ static void ath12k_wmi_process_tpc_stats(struct ath12k_base *ab, void *ptr = skb->data; struct ath12k *ar; u16 tlv_tag; + u16 tlv_len; u32 event_count; int ret; @@ -10001,6 +10002,7 @@ static void ath12k_wmi_process_tpc_stats(struct ath12k_base *ab, tlv = (struct wmi_tlv *)ptr; tlv_tag = le32_get_bits(tlv->header, WMI_TLV_TAG); + tlv_len = le32_get_bits(tlv->header, WMI_TLV_LEN); ptr += sizeof(*tlv); if (tlv_tag != WMI_TAG_HALPHY_CTRL_PATH_EVENT_FIXED_PARAM) { @@ -10008,6 +10010,12 @@ static void ath12k_wmi_process_tpc_stats(struct ath12k_base *ab, return; } + if (tlv_len < sizeof(*fixed_param)) { + ath12k_warn(ab, "TPC stats fixed param tlv len %u too short\n", + tlv_len); + return; + } + fixed_param = (struct ath12k_wmi_pdev_tpc_stats_event_fixed_params *)ptr; rcu_read_lock(); ar = ath12k_mac_get_ar_by_pdev_id(ab, le32_to_cpu(fixed_param->pdev_id) + 1); From 0702eddffff1b637a9e90187785a0b44542bb365 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Sat, 25 Jul 2026 11:11:45 -0700 Subject: [PATCH 0612/1433] wifi: ath12k: move firmware_mode enum to qmi.h The enum ath12k_firmware_mode defines values that are part of the QMI ABI, so it belongs in qmi.h rather than core.h. Consolidate it there along with ATH12K_FIRMWARE_MODE_OFF, which is currently a bare macro. Rename the enum to ath12k_qmi_firmware_mode to align with the naming convention of the other enums in qmi.h, and place it with the other ath12k_qmi_* enums. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260725-consolidate-firmware_mode-v1-1-aedff0ce0ba5@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.c | 3 ++- drivers/net/wireless/ath/ath12k/core.h | 10 +--------- drivers/net/wireless/ath/ath12k/pci.c | 1 + drivers/net/wireless/ath/ath12k/qmi.c | 4 ++-- drivers/net/wireless/ath/ath12k/qmi.h | 14 ++++++++++++-- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index a9112760185f..a052a77828f3 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -23,6 +23,7 @@ #include "wow.h" #include "dp_cmn.h" #include "peer.h" +#include "qmi.h" unsigned int ath12k_debug_mask; module_param_named(debug_mask, ath12k_debug_mask, uint, 0644); @@ -1188,7 +1189,7 @@ static int ath12k_core_hw_group_start(struct ath12k_hw_group *ag) } static int ath12k_core_start_firmware(struct ath12k_base *ab, - enum ath12k_firmware_mode mode) + enum ath12k_qmi_firmware_mode mode) { int ret; diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index 37a194e00248..ecb451d93f45 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -160,14 +160,6 @@ enum ath12k_hw_rev { ATH12K_HW_IPQ5424_HW10, }; -enum ath12k_firmware_mode { - /* the default mode, standard 802.11 functionality */ - ATH12K_FIRMWARE_MODE_NORMAL, - - /* factory tests etc */ - ATH12K_FIRMWARE_MODE_FTM, -}; - #define ATH12K_IRQ_NUM_MAX 57 #define ATH12K_EXT_IRQ_NUM_MAX 16 #define ATH12K_MAX_TCL_RING_NUM 3 @@ -1147,7 +1139,7 @@ struct ath12k_base { struct ath12k_hw_group *ag; struct ath12k_wsi_info wsi_info; - enum ath12k_firmware_mode fw_mode; + enum ath12k_qmi_firmware_mode fw_mode; struct ath12k_ftm_event_obj ftm_event_obj; bool hw_group_ref; diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c index ad74140e0fa5..907d29b1020c 100644 --- a/drivers/net/wireless/ath/ath12k/pci.c +++ b/drivers/net/wireless/ath/ath12k/pci.c @@ -17,6 +17,7 @@ #include "mhi.h" #include "debug.h" #include "hal.h" +#include "qmi.h" #define ATH12K_PCI_BAR_NUM 0 #define ATH12K_PCI_DMA_MASK 36 diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index bb61c78e5c29..c466c3ae793a 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -3431,7 +3431,7 @@ int ath12k_qmi_wlanfw_aux_uc_info_send(struct ath12k_base *ab) } static int ath12k_qmi_wlanfw_mode_send(struct ath12k_base *ab, - u32 mode) + enum ath12k_qmi_firmware_mode mode) { struct qmi_wlanfw_wlan_mode_req_msg_v01 req = {}; struct qmi_wlanfw_wlan_mode_resp_msg_v01 resp = {}; @@ -3631,7 +3631,7 @@ void ath12k_qmi_firmware_stop(struct ath12k_base *ab) } int ath12k_qmi_firmware_start(struct ath12k_base *ab, - u32 mode) + enum ath12k_qmi_firmware_mode mode) { int ret; diff --git a/drivers/net/wireless/ath/ath12k/qmi.h b/drivers/net/wireless/ath/ath12k/qmi.h index cbe5be30053a..27b69847a15e 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.h +++ b/drivers/net/wireless/ath/ath12k/qmi.h @@ -32,7 +32,6 @@ #define QMI_WLFW_FW_READY_IND_V01 0x0038 #define QMI_WLANFW_MAX_DATA_SIZE_V01 6144 -#define ATH12K_FIRMWARE_MODE_OFF 4 #define ATH12K_BOARD_ID_DEFAULT 0xFF @@ -602,6 +601,17 @@ enum ath12k_qmi_mem_mode { ATH12K_QMI_MEMORY_MODE_LOW_512_M, }; +enum ath12k_qmi_firmware_mode { + /* the default mode, standard 802.11 functionality */ + ATH12K_FIRMWARE_MODE_NORMAL, + + /* factory tests etc */ + ATH12K_FIRMWARE_MODE_FTM, + + /* firmware offline */ + ATH12K_FIRMWARE_MODE_OFF = 4, +}; + static inline void ath12k_qmi_set_event_block(struct ath12k_qmi *qmi, bool block) { lockdep_assert_held(&qmi->event_lock); @@ -617,7 +627,7 @@ static inline bool ath12k_qmi_get_event_block(struct ath12k_qmi *qmi) } int ath12k_qmi_firmware_start(struct ath12k_base *ab, - u32 mode); + enum ath12k_qmi_firmware_mode mode); void ath12k_qmi_firmware_stop(struct ath12k_base *ab); void ath12k_qmi_deinit_service(struct ath12k_base *ab); int ath12k_qmi_init_service(struct ath12k_base *ab); From 0090ec7ad252f1a597d782b7bce7eff7bdde00c0 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Sat, 25 Jul 2026 11:11:46 -0700 Subject: [PATCH 0613/1433] wifi: ath12k: rename firmware_mode enum members to use QMI namespace The enumerator names ATH12K_FIRMWARE_MODE_* lack the QMI infix that all other constants in qmi.h use (ATH12K_QMI_FILE_TYPE_*, ATH12K_QMI_BDF_TYPE_*, ATH12K_QMI_MEMORY_MODE_*, etc.). Rename them to ATH12K_QMI_FIRMWARE_MODE_* for consistency and to prevent a future re-introduction of ATH12K_FIRMWARE_MODE_* names causing a silent collision. While here, add a comment noting that values 2-3 are reserved by the firmware QMI ABI to explain the gap before ATH12K_QMI_FIRMWARE_MODE_OFF = 4. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260725-consolidate-firmware_mode-v1-2-aedff0ce0ba5@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.c | 2 +- drivers/net/wireless/ath/ath12k/mac.c | 2 +- drivers/net/wireless/ath/ath12k/pci.c | 2 +- drivers/net/wireless/ath/ath12k/qmi.c | 4 ++-- drivers/net/wireless/ath/ath12k/qmi.h | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index a052a77828f3..d023c646478f 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -795,7 +795,7 @@ static int ath12k_core_soc_create(struct ath12k_base *ab) int ret; if (ath12k_ftm_mode) { - ab->fw_mode = ATH12K_FIRMWARE_MODE_FTM; + ab->fw_mode = ATH12K_QMI_FIRMWARE_MODE_FTM; ath12k_info(ab, "Booting in ftm mode\n"); } diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 310976247dbb..9a775602775d 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -859,7 +859,7 @@ struct ath12k *ath12k_mac_get_ar_by_pdev_id(struct ath12k_base *ab, u32 pdev_id) return NULL; for (i = 0; i < ab->num_radios; i++) { - if (ab->fw_mode == ATH12K_FIRMWARE_MODE_FTM) + if (ab->fw_mode == ATH12K_QMI_FIRMWARE_MODE_FTM) pdev = &ab->pdevs[i]; else pdev = rcu_dereference(ab->pdevs_active[i]); diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c index 907d29b1020c..6441927b5382 100644 --- a/drivers/net/wireless/ath/ath12k/pci.c +++ b/drivers/net/wireless/ath/ath12k/pci.c @@ -1556,7 +1556,7 @@ static int ath12k_pci_probe(struct pci_dev *pdev, ab_pci->ab = ab; ab_pci->pdev = pdev; ab->hif.ops = &ath12k_pci_hif_ops; - ab->fw_mode = ATH12K_FIRMWARE_MODE_NORMAL; + ab->fw_mode = ATH12K_QMI_FIRMWARE_MODE_NORMAL; pci_set_drvdata(pdev, ab); spin_lock_init(&ab_pci->window_lock); diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index c466c3ae793a..280e50a1f31d 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -3460,7 +3460,7 @@ static int ath12k_qmi_wlanfw_mode_send(struct ath12k_base *ab, ret = qmi_txn_wait(&txn, msecs_to_jiffies(ATH12K_QMI_WLANFW_TIMEOUT_MS)); if (ret < 0) { - if (mode == ATH12K_FIRMWARE_MODE_OFF && ret == -ENETRESET) { + if (mode == ATH12K_QMI_FIRMWARE_MODE_OFF && ret == -ENETRESET) { ath12k_warn(ab, "WLFW service is dis-connected\n"); return 0; } @@ -3623,7 +3623,7 @@ void ath12k_qmi_firmware_stop(struct ath12k_base *ab) clear_bit(ATH12K_FLAG_QMI_FW_READY_COMPLETE, &ab->dev_flags); - ret = ath12k_qmi_wlanfw_mode_send(ab, ATH12K_FIRMWARE_MODE_OFF); + ret = ath12k_qmi_wlanfw_mode_send(ab, ATH12K_QMI_FIRMWARE_MODE_OFF); if (ret < 0) { ath12k_warn(ab, "qmi failed to send wlan mode off\n"); return; diff --git a/drivers/net/wireless/ath/ath12k/qmi.h b/drivers/net/wireless/ath/ath12k/qmi.h index 27b69847a15e..6da10f3cb597 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.h +++ b/drivers/net/wireless/ath/ath12k/qmi.h @@ -603,13 +603,13 @@ enum ath12k_qmi_mem_mode { enum ath12k_qmi_firmware_mode { /* the default mode, standard 802.11 functionality */ - ATH12K_FIRMWARE_MODE_NORMAL, + ATH12K_QMI_FIRMWARE_MODE_NORMAL, /* factory tests etc */ - ATH12K_FIRMWARE_MODE_FTM, + ATH12K_QMI_FIRMWARE_MODE_FTM, - /* firmware offline */ - ATH12K_FIRMWARE_MODE_OFF = 4, + /* firmware offline; values 2-3 reserved by firmware ABI */ + ATH12K_QMI_FIRMWARE_MODE_OFF = 4, }; static inline void ath12k_qmi_set_event_block(struct ath12k_qmi *qmi, bool block) From 534459ac562b207ba1a9bb2c95dba77b5939e2da Mon Sep 17 00:00:00 2001 From: Pavankumar Nandeshwar Date: Tue, 21 Jul 2026 16:34:59 +0530 Subject: [PATCH 0614/1433] wifi: ath12k: Use different RX release ring sizes as per memory profiles Currently, the RX release ring size is hardcoded to 1024 entries via DP_RX_RELEASE_RING_SIZE. This value was sufficient for older generations, but is not adequate for Wi-Fi 7 scenarios with higher aggregation, parallel processing, and increased likelihood of error bursts. In Wi-Fi 7, a PPDU can carry up to 1024 MPDUs and each MPDU may contain multiple MSDUs. In error scenarios such as REO out-of-order (OOR) events, a large number of MSDUs can be pushed to the RX release ring in a short duration. With multiple PPDUs being processed in parallel (e.g. multi-core or MLO scenarios), this can lead to significant bursts of descriptors. Field observations have shown frequent OOR conditions and back-pressure issues with smaller ring sizes. Increasing the RX release ring size helps absorb these bursts and avoids back-pressure in the RXDMA/REO pipeline. Without sufficient ring capacity (e.g. 16K), back-pressure was observed under stress conditions. To address this, make the RX release ring size configurable per memory profile by adding rx_release_ring_size to ath12k_dp_profile_params: - Default memory profile: 16384 entries - Low memory profile (512M): 8192 entries The larger size in the default profile improves robustness under high traffic and error conditions by reducing the probability of ring overflow and pipeline stalls. The reduced size in the low memory profile balances memory usage while still providing sufficient headroom compared to the previous fixed value. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.0.1-00029-QCAHKSWPL_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3 Signed-off-by: Pavankumar Nandeshwar Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260721110459.2203038-1-pavankumar.nandeshwar@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.c | 2 ++ drivers/net/wireless/ath/ath12k/core.h | 1 + drivers/net/wireless/ath/ath12k/dp.c | 2 +- drivers/net/wireless/ath/ath12k/dp.h | 3 ++- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index d023c646478f..cb4ca93f625c 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -53,6 +53,7 @@ ath12k_mem_profile_based_param ath12k_mem_profile_based_param[] = { .rxdma_monitor_dst_ring_size = 8192, .num_pool_tx_desc = 32768, .rx_desc_count = 12288, + .rx_release_ring_size = 16384, }, }, [ATH12K_QMI_MEMORY_MODE_LOW_512_M] = { @@ -66,6 +67,7 @@ ath12k_mem_profile_based_param ath12k_mem_profile_based_param[] = { .rxdma_monitor_dst_ring_size = 512, .num_pool_tx_desc = 16384, .rx_desc_count = 6144, + .rx_release_ring_size = 8192, }, }, }; diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index ecb451d93f45..3bd71bf02afb 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -931,6 +931,7 @@ struct ath12k_dp_profile_params { u32 rxdma_monitor_dst_ring_size; u32 num_pool_tx_desc; u32 rx_desc_count; + u32 rx_release_ring_size; }; struct ath12k_mem_profile_based_param { diff --git a/drivers/net/wireless/ath/ath12k/dp.c b/drivers/net/wireless/ath/ath12k/dp.c index fbc0788b37a0..f9b37d75956d 100644 --- a/drivers/net/wireless/ath/ath12k/dp.c +++ b/drivers/net/wireless/ath/ath12k/dp.c @@ -487,7 +487,7 @@ static int ath12k_dp_srng_common_setup(struct ath12k_base *ab) ret = ath12k_dp_srng_setup(ab, &dp->rx_rel_ring, HAL_WBM2SW_RELEASE, HAL_WBM2SW_REL_ERR_RING_NUM, 0, - DP_RX_RELEASE_RING_SIZE); + DP_RX_RELEASE_RING_SIZE(ab)); if (ret) { ath12k_warn(ab, "failed to set up rx_rel ring :%d\n", ret); goto err; diff --git a/drivers/net/wireless/ath/ath12k/dp.h b/drivers/net/wireless/ath/ath12k/dp.h index a94bbc337df4..bef0f2ba0560 100644 --- a/drivers/net/wireless/ath/ath12k/dp.h +++ b/drivers/net/wireless/ath/ath12k/dp.h @@ -200,7 +200,8 @@ struct ath12k_pdev_dp { #define DP_REO_DST_RING_MAX 8 #define DP_REO_DST_RING_SIZE 2048 #define DP_REO_REINJECT_RING_SIZE 32 -#define DP_RX_RELEASE_RING_SIZE 1024 +#define DP_RX_RELEASE_RING_SIZE(ab) \ + ((ab)->profile_param->dp_params.rx_release_ring_size) #define DP_REO_EXCEPTION_RING_SIZE 128 #define DP_REO_CMD_RING_SIZE 256 #define DP_REO_STATUS_RING_SIZE 2048 From 43c521c11ce8fe904e14d1ae0566ffea931cf2e9 Mon Sep 17 00:00:00 2001 From: Pavankumar Nandeshwar Date: Thu, 23 Jul 2026 11:16:53 +0530 Subject: [PATCH 0615/1433] wifi: ath12k: skip MLO multicast links during crash recovery in Tx path In ath12k_wifi7_mac_op_tx(), the MLO multicast broadcast path iterates over all active links and copies the original skb for transmission on each link. When firmware crash recovery is underway (ATH12K_FLAG_CRASH_FLUSH set), the per-link copy is allocated and partially processed before ath12k_wifi7_dp_tx() eventually rejects it with -ESHUTDOWN. This wastes GFP_ATOMIC memory and produces spurious "failed to transmit frame" warnings for every active MLO link during the recovery window. The unicast and non-MLO paths are unaffected: they call ath12k_wifi7_dp_tx() directly, which already guards against the flag at its entry. Skip any link whose associated ath12k_base has ATH12K_FLAG_CRASH_FLUSH set before performing the skb_copy(), matching the behaviour of ath12k_wifi7_dp_tx() but avoiding the unnecessary allocation entirely. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Pavankumar Nandeshwar Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260723054653.2794550-1-pavankumar.nandeshwar@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wifi7/hw.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c index 4c1119edcaed..d890801d5822 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c @@ -1032,6 +1032,9 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, continue; tmp_ar = tmp_arvif->ar; + if (unlikely(test_bit(ATH12K_FLAG_CRASH_FLUSH, &tmp_ar->ab->dev_flags))) + continue; + tmp_dp = ath12k_ab_to_dp(tmp_ar->ab); tmp_dp_pdev = ath12k_dp_to_pdev_dp(tmp_dp, tmp_ar->pdev_idx); From 96f46607bbcee8aac00c4b5a1213b7d82ceee36d Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Tue, 21 Jul 2026 12:20:37 +0530 Subject: [PATCH 0616/1433] wifi: ath12k: add AHB platform descriptor support AHB-based platforms associate each device with a userPD ID that determines the firmware name and Peripheral Authentication Service ID (PASID) used during firmware authentication. Current implementation does not support platforms with multiple devices sharing the same compatible string but using different userPD IDs. As a result, the driver cannot uniquely identify each device for firmware selection and authentication. Add an AHB platform descriptor to store device-specific configuration. Implement userPD ID resolution by matching device tree reg properties, with node name matching as a fallback. Centralize platform configuration to simplify the probe path by removing hardware-specific conditionals. Tested-on: IPQ5332 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1 Signed-off-by: Aaradhana Sahu Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260721065038.126046-2-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/ahb.c | 4 +- drivers/net/wireless/ath/ath12k/ahb.h | 19 +++++ drivers/net/wireless/ath/ath12k/hw.h | 1 - drivers/net/wireless/ath/ath12k/wifi7/ahb.c | 90 +++++++++++++++++---- 4 files changed, 94 insertions(+), 20 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/ahb.c b/drivers/net/wireless/ath/ath12k/ahb.c index 07bb83710b1f..14ee696960c7 100644 --- a/drivers/net/wireless/ath/ath12k/ahb.c +++ b/drivers/net/wireless/ath/ath12k/ahb.c @@ -704,7 +704,7 @@ static int ath12k_ahb_map_service_to_pipe(struct ath12k_base *ab, u16 service_id return 0; } -static const struct ath12k_hif_ops ath12k_ahb_hif_ops = { +const struct ath12k_hif_ops ath12k_ahb_hif_ops = { .start = ath12k_ahb_start, .stop = ath12k_ahb_stop, .read32 = ath12k_ahb_read32, @@ -715,6 +715,7 @@ static const struct ath12k_hif_ops ath12k_ahb_hif_ops = { .power_up = ath12k_ahb_power_up, .power_down = ath12k_ahb_power_down, }; +EXPORT_SYMBOL(ath12k_ahb_hif_ops); static irqreturn_t ath12k_userpd_irq_handler(int irq, void *data) { @@ -1038,7 +1039,6 @@ static int ath12k_ahb_probe(struct platform_device *pdev) ab_ahb = ath12k_ab_to_ahb(ab); ab_ahb->ab = ab; - ab->hif.ops = &ath12k_ahb_hif_ops; ab->pdev = pdev; platform_set_drvdata(pdev, ab); diff --git a/drivers/net/wireless/ath/ath12k/ahb.h b/drivers/net/wireless/ath/ath12k/ahb.h index a153db6cf1d3..037347ccd21b 100644 --- a/drivers/net/wireless/ath/ath12k/ahb.h +++ b/drivers/net/wireless/ath/ath12k/ahb.h @@ -30,6 +30,24 @@ #define ATH12K_USERPD_ID_MASK GENMASK(10, 8) #define ATH12K_USERPD_FW_NAME_LEN 35 +enum ath12k_ahb_userpd_id { + ATH12K_AHB_USERPD_ID_0 = 1, + ATH12K_AHB_USERPD_ID_1, + ATH12K_AHB_USERPD_ID_2, +}; + +struct ath12k_ahb_userpd_map { + phys_addr_t io_start; + const char *node_name; + u32 upd_id; +}; + +struct ath12k_ahb_desc { + enum ath12k_hw_rev hw_rev; + bool auth_enabled; + const struct ath12k_hif_ops *ops; +}; + enum ath12k_ahb_smp2p_msg_id { ATH12K_AHB_POWER_SAVE_ENTER = 1, ATH12K_AHB_POWER_SAVE_EXIT, @@ -43,6 +61,7 @@ enum ath12k_ahb_userpd_irq { }; struct ath12k_base; +extern const struct ath12k_hif_ops ath12k_ahb_hif_ops; struct ath12k_ahb_device_family_ops { int (*probe)(struct platform_device *pdev); diff --git a/drivers/net/wireless/ath/ath12k/hw.h b/drivers/net/wireless/ath/ath12k/hw.h index 49cfd5dfc70a..3ed38f8f2b48 100644 --- a/drivers/net/wireless/ath/ath12k/hw.h +++ b/drivers/net/wireless/ath/ath12k/hw.h @@ -100,7 +100,6 @@ struct ieee80211_rx_status; #define ATH12K_REGDB_FILE_NAME "regdb.bin" #define ATH12K_PCIE_MAX_PAYLOAD_SIZE 128 -#define ATH12K_IPQ5332_USERPD_ID 1 enum ath12k_hw_rate_cck { ATH12K_HW_RATE_CCK_LP_11M = 0, diff --git a/drivers/net/wireless/ath/ath12k/wifi7/ahb.c b/drivers/net/wireless/ath/ath12k/wifi7/ahb.c index 6a8b8b2a56f9..98a6606ffd76 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/ahb.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/ahb.c @@ -15,44 +15,100 @@ #include "dp.h" #include "core.h" +/* + * Node name to UserPD ID mapping + * + * The io_start field is used for additional validation when the reg + * property is present in the device tree. If io_start is 0, only + * node_name matching is performed. + * + * For platforms where not all WiFi nodes have a 'reg' property, set + * io_start to 0 for those entries. The driver will match purely by + * node name in such cases. + */ +static const struct ath12k_ahb_userpd_map ath12k_wifi7_ahb_userpd_map[] = { + { .io_start = 0x0c000000, .node_name = "wifi", .upd_id = ATH12K_AHB_USERPD_ID_0 }, +}; + +static const struct ath12k_ahb_desc ath12k_wifi7_ahb_desc[] = { + [ATH12K_HW_IPQ5332_HW10] = { + .hw_rev = ATH12K_HW_IPQ5332_HW10, + .auth_enabled = true, + .ops = &ath12k_ahb_hif_ops, + }, + [ATH12K_HW_IPQ5424_HW10] = { + .hw_rev = ATH12K_HW_IPQ5424_HW10, + .auth_enabled = false, + .ops = &ath12k_ahb_hif_ops, + }, +}; + static const struct of_device_id ath12k_wifi7_ahb_of_match[] = { { .compatible = "qcom,ipq5332-wifi", - .data = (void *)ATH12K_HW_IPQ5332_HW10, + .data = (void *)&ath12k_wifi7_ahb_desc[ATH12K_HW_IPQ5332_HW10], }, { .compatible = "qcom,ipq5424-wifi", - .data = (void *)ATH12K_HW_IPQ5424_HW10, + .data = (void *)&ath12k_wifi7_ahb_desc[ATH12K_HW_IPQ5424_HW10], }, { } }; MODULE_DEVICE_TABLE(of, ath12k_wifi7_ahb_of_match); +/* + * ath12k_wifi7_ahb_get_userpd_id - Resolve UserPD ID from DT properties + * @ab: ath12k base structure + * + * Returns: UserPD ID (1-based) on success, 0 on failure + * + * Resolution logic: + * 1. If reg property exist in DT, get userpd_id from io_start + * 2. If reg property is absent, get userpd_id from DT node name + * 3. Return 0 if no match found (probe will fail) + */ +static u32 ath12k_wifi7_ahb_get_userpd_id(struct ath12k_base *ab) +{ + const struct ath12k_ahb_userpd_map *map; + struct resource *res; + size_t i; + + res = platform_get_resource(ab->pdev, IORESOURCE_MEM, 0); + + for (i = 0; i < ARRAY_SIZE(ath12k_wifi7_ahb_userpd_map); i++) { + map = &ath12k_wifi7_ahb_userpd_map[i]; + + if (res) { + if (map->io_start && map->io_start == res->start) + return map->upd_id; + } else if (map->node_name && + of_node_name_eq(ab->dev->of_node, map->node_name)) { + return map->upd_id; + } + } + + return 0; +} + static int ath12k_wifi7_ahb_probe(struct platform_device *pdev) { + const struct ath12k_ahb_desc *desc; struct ath12k_ahb *ab_ahb; - enum ath12k_hw_rev hw_rev; struct ath12k_base *ab; int ret; ab = platform_get_drvdata(pdev); ab_ahb = ath12k_ab_to_ahb(ab); - - hw_rev = (enum ath12k_hw_rev)(kernel_ulong_t)of_device_get_match_data(&pdev->dev); - switch (hw_rev) { - case ATH12K_HW_IPQ5332_HW10: - ab_ahb->userpd_id = ATH12K_IPQ5332_USERPD_ID; - ab_ahb->scm_auth_enabled = true; - break; - case ATH12K_HW_IPQ5424_HW10: - ab_ahb->userpd_id = ATH12K_IPQ5332_USERPD_ID; - ab_ahb->scm_auth_enabled = false; - break; - default: + desc = of_device_get_match_data(&pdev->dev); + if (!desc) return -EOPNOTSUPP; - } ab->target_mem_mode = ATH12K_QMI_MEMORY_MODE_DEFAULT; - ab->hw_rev = hw_rev; + ab->hw_rev = desc->hw_rev; + ab->hif.ops = desc->ops; + ab_ahb->scm_auth_enabled = desc->auth_enabled; + ab_ahb->userpd_id = ath12k_wifi7_ahb_get_userpd_id(ab); + if (!ab_ahb->userpd_id) + return -EOPNOTSUPP; ret = ath12k_wifi7_hw_init(ab); if (ret) { From c6ab3b1dfa3e62dbf42c66121037de460a182642 Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Tue, 21 Jul 2026 12:20:38 +0530 Subject: [PATCH 0617/1433] wifi: ath12k: Share RootPD state across UserPDs to avoid duplicate operations Currently, each ath12k AHB device maintains its own RootPD-related information. However, RootPD is shared across all UserPD devices, so RootPD-related operations such as RootPD boot, and notifier registration, should be performed only once during the first UserPD boot up. Due to per-device RootPD information, the driver is unable to track shared RootPD state across multiple UserPDs, which can result in these operations being performed multiple times. Fix this by introducing a new ath12k_ahb_rproc_info structure to hold shared RootPD-related information such as notifier callbacks, boot state, and number of userPD. Allocate this structure during the first device probe in ath12k_ahb_rproc_info_alloc() and reuse the same structure for all subsequent device probes. Also handle rproc deconfiguration correctly when multiple UserPDs share a common RootPD. The RootPD provides shared firmware services and resources for all UserPDs. Therefore, do not shut down the RootPD while any UserPD remains powered on or is still in the boot process. In addition, a UserPD can be powered down before its associated resources are fully released. Defer g_rproc_info cleanup until all UserPD-related state and resources have been cleaned up. For intermediate UserPD removal, cleanup only per-device information and remove the UserPD from the tracking array while keeping the RootPD running for remaining active UserPDs. Note: UserPD IDs start from 1, as ID 0 is used by RootPD, which is completely handled by the remoteproc driver. The multi-PD architecture on AHB platforms operates as follows: +-----------------------------+ | Q6 RootPD (rproc) | | (Shared Resource) | | | | - Manages UserPD lifecycle | | - Provides SSR notifiers | +--------------+--------------+ | | Manages | +---------------------+---------------------+ | | | +----v----+ +----v----+ +----v----+ | UserPD1 | | UserPD2 | | UserPD3 | | ID=1 | | ID=2 | | ID=3 | | (Radio) | | (Radio) | | (Radio) | +---------+ +---------+ +---------+ | | | | | | ath12k_ahb ath12k_ahb ath12k_ahb (device 1) (device 2) (device 3) | | | +---------------------+---------------------+ | | All reference | +---------v----------+ | ath12k_ahb_rproc_ | | info (shared) | | | | - tgt_rproc | | - notifiers | | - rootpd_ready | | - num_userpd | | - userpd[] array | +--------------------+ Tested-on: IPQ5332 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1 Signed-off-by: Aaradhana Sahu Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260721065038.126046-3-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/ahb.c | 211 +++++++++++++++++++++----- drivers/net/wireless/ath/ath12k/ahb.h | 15 +- 2 files changed, 180 insertions(+), 46 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/ahb.c b/drivers/net/wireless/ath/ath12k/ahb.c index 14ee696960c7..0fc55c9169e1 100644 --- a/drivers/net/wireless/ath/ath12k/ahb.c +++ b/drivers/net/wireless/ath/ath12k/ahb.c @@ -25,6 +25,22 @@ static const char ath12k_userpd_irq[][9] = {"spawn", "ready", "stop-ack"}; +/* + * Multi-UserPD Architecture: + * + * One Q6 RootPD (managed by separate rproc driver) supports multiple + * ath12k UserPDs. Each UserPD represents a WiFi radio instance. + * + * Lifecycle: + * - RootPD boots when first UserPD probes + * - All UserPDs share RootPD's SSR notifier + * + * Locking: + * - ath12k_rproc_info_lock: Protects g_rproc_info allocation/free + */ +static struct ath12k_ahb_rproc_info *g_rproc_info; +static DEFINE_MUTEX(ath12k_rproc_info_lock); + static const char *irq_name[ATH12K_IRQ_NUM_MAX] = { "misc-pulse1", "misc-latch", @@ -786,44 +802,85 @@ static int ath12k_ahb_config_rproc_irq(struct ath12k_base *ab) static int ath12k_ahb_root_pd_state_notifier(struct notifier_block *nb, const unsigned long event, void *data) { - struct ath12k_ahb *ab_ahb = container_of(nb, struct ath12k_ahb, root_pd_nb); - struct ath12k_base *ab = ab_ahb->ab; + struct ath12k_ahb_rproc_info *rproc_info = + container_of(nb, struct ath12k_ahb_rproc_info, root_pd_nb); if (event == ATH12K_RPROC_AFTER_POWERUP) { - ath12k_dbg(ab, ATH12K_DBG_AHB, "Root PD is UP\n"); - complete(&ab_ahb->rootpd_ready); + ath12k_generic_dbg(ATH12K_DBG_AHB, "Root PD is UP\n"); + complete(&rproc_info->rootpd_ready); } return 0; } -static int ath12k_ahb_register_rproc_notifier(struct ath12k_base *ab) +static int ath12k_ahb_register_rproc_notifier(void) { - struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); + int ret; - ab_ahb->root_pd_nb.notifier_call = ath12k_ahb_root_pd_state_notifier; - init_completion(&ab_ahb->rootpd_ready); + lockdep_assert_held(&ath12k_rproc_info_lock); - ab_ahb->root_pd_notifier = qcom_register_ssr_notifier(ab_ahb->tgt_rproc->name, - &ab_ahb->root_pd_nb); - if (IS_ERR(ab_ahb->root_pd_notifier)) - return PTR_ERR(ab_ahb->root_pd_notifier); + if (g_rproc_info->root_pd_notifier) + return 0; + + g_rproc_info->root_pd_nb.notifier_call = ath12k_ahb_root_pd_state_notifier; + + g_rproc_info->root_pd_notifier = + qcom_register_ssr_notifier(g_rproc_info->tgt_rproc->name, + &g_rproc_info->root_pd_nb); + if (IS_ERR(g_rproc_info->root_pd_notifier)) { + ret = PTR_ERR(g_rproc_info->root_pd_notifier); + g_rproc_info->root_pd_notifier = NULL; + return ret; + } return 0; } -static void ath12k_ahb_unregister_rproc_notifier(struct ath12k_base *ab) +static void ath12k_ahb_unregister_rproc_notifier(void) { - struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); + lockdep_assert_held(&ath12k_rproc_info_lock); - if (!ab_ahb->root_pd_notifier) { - ath12k_err(ab, "Rproc notifier not registered\n"); + if (!g_rproc_info->root_pd_notifier) return; - } - qcom_unregister_ssr_notifier(ab_ahb->root_pd_notifier, - &ab_ahb->root_pd_nb); - ab_ahb->root_pd_notifier = NULL; + qcom_unregister_ssr_notifier(g_rproc_info->root_pd_notifier, + &g_rproc_info->root_pd_nb); + g_rproc_info->root_pd_notifier = NULL; +} + +static void ath12k_ahb_cleanup_userpd(struct ath12k_base *ab) +{ + struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); + struct ath12k_ahb_rproc_info *rproc_info = ab_ahb->rproc_info; + + lockdep_assert_held(&ath12k_rproc_info_lock); + + if (!rproc_info) + return; + + rproc_info->userpd[ab_ahb->userpd_id - 1] = NULL; + rproc_info->num_userpd--; + ab_ahb->rproc_info = NULL; +} + +static struct ath12k_ahb_rproc_info *ath12k_ahb_rproc_info_alloc(struct ath12k_base *ab) +{ + struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); + struct ath12k_ahb_rproc_info *rproc_info; + + lockdep_assert_held(&ath12k_rproc_info_lock); + + rproc_info = kzalloc_obj(*rproc_info, GFP_KERNEL); + if (!rproc_info) + return NULL; + + rproc_info->rootpd_booted_by_driver = false; + rproc_info->userpd[ab_ahb->userpd_id - 1] = ab_ahb; + rproc_info->num_userpd = 1; + init_completion(&rproc_info->rootpd_ready); + ab_ahb->rproc_info = rproc_info; + + return rproc_info; } static int ath12k_ahb_get_rproc(struct ath12k_base *ab) @@ -832,37 +889,69 @@ static int ath12k_ahb_get_rproc(struct ath12k_base *ab) struct device *dev = ab->dev; struct device_node *np; struct rproc *prproc; + int ret; + + lockdep_assert_held(&ath12k_rproc_info_lock); + + if (ab_ahb->userpd_id > ATH12K_MAX_DEVICES) + return -ENOSPC; + + if (g_rproc_info) { + if (g_rproc_info->num_userpd >= ATH12K_MAX_DEVICES) { + ath12k_err(ab, "Max UserPD limit reached\n"); + return -ENOSPC; + } + + g_rproc_info->userpd[ab_ahb->userpd_id - 1] = ab_ahb; + g_rproc_info->num_userpd++; + ab_ahb->rproc_info = g_rproc_info; + return 0; + } + + g_rproc_info = ath12k_ahb_rproc_info_alloc(ab); + if (!g_rproc_info) + return -ENOMEM; np = of_parse_phandle(dev->of_node, "qcom,rproc", 0); if (!np) { ath12k_err(ab, "failed to get q6_rproc handle\n"); - return -ENOENT; + ret = -ENOENT; + goto err_free_rproc_info; } prproc = rproc_get_by_phandle(np->phandle); of_node_put(np); - if (!prproc) - return dev_err_probe(&ab->pdev->dev, -EPROBE_DEFER, - "failed to get rproc\n"); - - ab_ahb->tgt_rproc = prproc; + if (!prproc) { + ret = dev_err_probe(&ab->pdev->dev, -EPROBE_DEFER, + "failed to get rproc\n"); + goto err_free_rproc_info; + } + g_rproc_info->tgt_rproc = prproc; return 0; + +err_free_rproc_info: + ab_ahb->rproc_info = NULL; + kfree(g_rproc_info); + g_rproc_info = NULL; + return ret; } static int ath12k_ahb_boot_root_pd(struct ath12k_base *ab) { - struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); unsigned long time_left; int ret; - ret = rproc_boot(ab_ahb->tgt_rproc); + lockdep_assert_held(&ath12k_rproc_info_lock); + reinit_completion(&g_rproc_info->rootpd_ready); + + ret = rproc_boot(g_rproc_info->tgt_rproc); if (ret < 0) { ath12k_err(ab, "RootPD boot failed\n"); return ret; } - time_left = wait_for_completion_timeout(&ab_ahb->rootpd_ready, + time_left = wait_for_completion_timeout(&g_rproc_info->rootpd_ready, ATH12K_ROOTPD_READY_TIMEOUT); if (!time_left) { ath12k_err(ab, "RootPD ready wait timed out\n"); @@ -874,44 +963,74 @@ static int ath12k_ahb_boot_root_pd(struct ath12k_base *ab) static int ath12k_ahb_configure_rproc(struct ath12k_base *ab) { - struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); int ret; - ret = ath12k_ahb_get_rproc(ab); - if (ret < 0) - return ret; + mutex_lock(&ath12k_rproc_info_lock); - ret = ath12k_ahb_register_rproc_notifier(ab); + ret = ath12k_ahb_get_rproc(ab); + if (ret < 0) { + mutex_unlock(&ath12k_rproc_info_lock); + return ret; + } + + ret = ath12k_ahb_register_rproc_notifier(); if (ret < 0) { ret = dev_err_probe(&ab->pdev->dev, ret, "failed to register rproc notifier\n"); - goto err_put_rproc; + goto err_cleanup_userpd; } - if (ab_ahb->tgt_rproc->state != RPROC_RUNNING) { + if (g_rproc_info->tgt_rproc->state != RPROC_RUNNING) { ret = ath12k_ahb_boot_root_pd(ab); if (ret < 0) { ath12k_err(ab, "failed to boot the remote processor Q6\n"); goto err_unreg_notifier; } + g_rproc_info->rootpd_booted_by_driver = true; } - return ath12k_ahb_config_rproc_irq(ab); + mutex_unlock(&ath12k_rproc_info_lock); + return 0; err_unreg_notifier: - ath12k_ahb_unregister_rproc_notifier(ab); + ath12k_ahb_unregister_rproc_notifier(); -err_put_rproc: - rproc_put(ab_ahb->tgt_rproc); +err_cleanup_userpd: + ath12k_ahb_cleanup_userpd(ab); + + if (g_rproc_info && !g_rproc_info->num_userpd) { + rproc_put(g_rproc_info->tgt_rproc); + kfree(g_rproc_info); + g_rproc_info = NULL; + } + + mutex_unlock(&ath12k_rproc_info_lock); return ret; } static void ath12k_ahb_deconfigure_rproc(struct ath12k_base *ab) { struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); + struct ath12k_ahb_rproc_info *rproc_info = ab_ahb->rproc_info; - ath12k_ahb_unregister_rproc_notifier(ab); - rproc_put(ab_ahb->tgt_rproc); + lockdep_assert_held(&ath12k_rproc_info_lock); + + if (!rproc_info || !g_rproc_info) + return; + + ath12k_ahb_cleanup_userpd(ab); + + if (!g_rproc_info->num_userpd) { + ath12k_ahb_unregister_rproc_notifier(); + + if (g_rproc_info->rootpd_booted_by_driver && + g_rproc_info->tgt_rproc->state == RPROC_RUNNING) + rproc_shutdown(g_rproc_info->tgt_rproc); + + rproc_put(g_rproc_info->tgt_rproc); + kfree(g_rproc_info); + g_rproc_info = NULL; + } } static int ath12k_ahb_resource_init(struct ath12k_base *ab) @@ -1094,6 +1213,10 @@ static int ath12k_ahb_probe(struct platform_device *pdev) if (ret) goto err_ce_free; + ret = ath12k_ahb_config_rproc_irq(ab); + if (ret) + goto err_rproc_deconfigure; + ret = ath12k_ahb_config_irq(ab); if (ret) { ath12k_err(ab, "failed to configure irq: %d\n", ret); @@ -1121,7 +1244,9 @@ static int ath12k_ahb_probe(struct platform_device *pdev) ab_ahb->device_family_ops->arch_deinit(ab); err_rproc_deconfigure: + mutex_lock(&ath12k_rproc_info_lock); ath12k_ahb_deconfigure_rproc(ab); + mutex_unlock(&ath12k_rproc_info_lock); err_ce_free: ath12k_ce_free_pipes(ab); @@ -1163,7 +1288,9 @@ static void ath12k_ahb_free_resources(struct ath12k_base *ab) ath12k_hal_srng_deinit(ab); ath12k_ce_free_pipes(ab); ath12k_ahb_resource_deinit(ab); + mutex_lock(&ath12k_rproc_info_lock); ath12k_ahb_deconfigure_rproc(ab); + mutex_unlock(&ath12k_rproc_info_lock); ab_ahb->device_family_ops->arch_deinit(ab); ath12k_core_free(ab); platform_set_drvdata(pdev, NULL); diff --git a/drivers/net/wireless/ath/ath12k/ahb.h b/drivers/net/wireless/ath/ath12k/ahb.h index 037347ccd21b..cdb58b07338f 100644 --- a/drivers/net/wireless/ath/ath12k/ahb.h +++ b/drivers/net/wireless/ath/ath12k/ahb.h @@ -69,13 +69,19 @@ struct ath12k_ahb_device_family_ops { void (*arch_deinit)(struct ath12k_base *ab); }; -struct ath12k_ahb { - struct ath12k_base *ab; +struct ath12k_ahb_rproc_info { struct rproc *tgt_rproc; - struct clk *xo_clk; - struct completion rootpd_ready; struct notifier_block root_pd_nb; void *root_pd_notifier; + struct completion rootpd_ready; + u8 num_userpd; + bool rootpd_booted_by_driver; + struct ath12k_ahb *userpd[ATH12K_MAX_DEVICES]; +}; + +struct ath12k_ahb { + struct ath12k_base *ab; + struct clk *xo_clk; struct qcom_smem_state *spawn_state; struct qcom_smem_state *stop_state; struct completion userpd_spawned; @@ -88,6 +94,7 @@ struct ath12k_ahb { const struct ath12k_ahb_ops *ahb_ops; const struct ath12k_ahb_device_family_ops *device_family_ops; bool scm_auth_enabled; + struct ath12k_ahb_rproc_info *rproc_info; }; struct ath12k_ahb_driver { From 9ae7ed101cf28613071ebfbc42668c8917a670cf Mon Sep 17 00:00:00 2001 From: Qing Luo Date: Thu, 23 Jul 2026 14:11:07 +0800 Subject: [PATCH 0618/1433] sctp: auth: discard auth_chunk when skb_clone fails When processing AUTH + COOKIE-ECHO packets, if skb_clone() fails due to memory pressure, chunk->auth_chunk is NULL. The original code still sets chunk->auth = 1 and continues, leaving the COOKIE-ECHO to be processed without a valid auth_chunk for deferred verification. Discard the AUTH chunk early via pdiscard when skb_clone() fails, so that the receive loop can continue processing remaining chunks in the inqueue instead of stalling the entire packet. Signed-off-by: Qing Luo Acked-by: Xin Long Link: https://patch.msgid.link/20260723061107.384106-1-l1138897701@163.com Signed-off-by: Jakub Kicinski --- net/sctp/associola.c | 4 ++++ net/sctp/endpointola.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/net/sctp/associola.c b/net/sctp/associola.c index 62d3cc155809..a5f2835dbe0f 100644 --- a/net/sctp/associola.c +++ b/net/sctp/associola.c @@ -999,6 +999,10 @@ static void sctp_assoc_bh_rcv(struct work_struct *work) if (next_hdr->type == SCTP_CID_COOKIE_ECHO) { chunk->auth_chunk = skb_clone(chunk->skb, GFP_ATOMIC); + if (!chunk->auth_chunk) { + chunk->pdiscard = 1; + continue; + } chunk->auth = 1; continue; } diff --git a/net/sctp/endpointola.c b/net/sctp/endpointola.c index dfb1719275db..a15b599b20b7 100644 --- a/net/sctp/endpointola.c +++ b/net/sctp/endpointola.c @@ -368,6 +368,10 @@ static void sctp_endpoint_bh_rcv(struct work_struct *work) if (next_hdr->type == SCTP_CID_COOKIE_ECHO) { chunk->auth_chunk = skb_clone(chunk->skb, GFP_ATOMIC); + if (!chunk->auth_chunk) { + chunk->pdiscard = 1; + continue; + } chunk->auth = 1; continue; } From bb3fed50cb019adb6f3d8daf26d3a3e923c6e74e Mon Sep 17 00:00:00 2001 From: Shaikh Kamaluddin Date: Tue, 28 Jul 2026 21:11:42 +0530 Subject: [PATCH 0619/1433] netlink: specs: nl80211: fix TOOD -> TODO Fix spelling error reported by codespell in description strings: TOOD -> TODO. Attribute names are untouched. No functional change. Signed-off-by: Shaikh Kamaluddin Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260728154142.12133-1-shaikhkamal2012@gmail.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/nl80211.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/netlink/specs/nl80211.yaml b/Documentation/netlink/specs/nl80211.yaml index 802097128bda..d9fdd66b497e 100644 --- a/Documentation/netlink/specs/nl80211.yaml +++ b/Documentation/netlink/specs/nl80211.yaml @@ -1139,10 +1139,10 @@ attribute-sets: type: binary - name: fils-discovery - type: binary # TOOD: nest + type: binary # TODO: nest - name: unsol-bcast-probe-resp - type: binary # TOOD: nest + type: binary # TODO: nest - name: s1g-capability type: binary From 0982666c1bb02792970a7f3ba27f0b4af68e6245 Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Tue, 28 Jul 2026 09:09:26 -0700 Subject: [PATCH 0620/1433] selftests/net: psock: Generalize psock bind Generalize packet socket binding so that types other than ETH_P_IP can be used. Signed-off-by: Joe Damato Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260728160935.4128982-2-joe@dama.to Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/psock_snd.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/net/psock_snd.c b/tools/testing/selftests/net/psock_snd.c index 3313a15e0ca1..bb9a9cfdbe95 100644 --- a/tools/testing/selftests/net/psock_snd.c +++ b/tools/testing/selftests/net/psock_snd.c @@ -171,12 +171,12 @@ static int build_packet(int payload_len) return off + payload_len; } -static void do_bind(int fd) +static void do_bind_proto(int fd, uint16_t proto) { struct sockaddr_ll laddr = {0}; laddr.sll_family = AF_PACKET; - laddr.sll_protocol = htons(ETH_P_IP); + laddr.sll_protocol = htons(proto); laddr.sll_ifindex = if_nametoindex(cfg_ifname); if (!laddr.sll_ifindex) error(1, errno, "if_nametoindex"); @@ -185,6 +185,11 @@ static void do_bind(int fd) error(1, errno, "bind"); } +static void do_bind(int fd) +{ + do_bind_proto(fd, ETH_P_IP); +} + static void do_send(int fd, char *buf, int len) { int ret; From 9404fe514efccfd01abc9746f4714de63fe54f1f Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Tue, 28 Jul 2026 09:09:27 -0700 Subject: [PATCH 0621/1433] selftests/net: psock: Generalize stats check Generalize stats check code so that callers can specify the expected packets. Signed-off-by: Joe Damato Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260728160935.4128982-3-joe@dama.to Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/psock_snd.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/net/psock_snd.c b/tools/testing/selftests/net/psock_snd.c index bb9a9cfdbe95..d506ea19491b 100644 --- a/tools/testing/selftests/net/psock_snd.c +++ b/tools/testing/selftests/net/psock_snd.c @@ -440,7 +440,7 @@ static void parse_opts(int argc, char **argv) error(1, 0, "option aux data (-a) conflicts with drop (-D)"); } -static void check_packet_stats(int fd) +static void check_packet_stats(int fd, unsigned int expected_packets) { struct tpacket_stats st = {}; socklen_t len = sizeof(st); @@ -459,8 +459,9 @@ static void check_packet_stats(int fd) if (st.tp_drops == 0) error(1, 0, "stats: expected drops but tp_drops == 0"); } else { - if (st.tp_packets != 1) - error(1, 0, "stats: tp_packets %u != 1", st.tp_packets); + if (st.tp_packets != expected_packets) + error(1, 0, "stats: tp_packets %u != %u", + st.tp_packets, expected_packets); if (st.tp_drops != 0) error(1, 0, "stats: tp_drops %u != 0", st.tp_drops); @@ -490,7 +491,7 @@ static void run_test(void) total_len = do_tx(); if (cfg_drop) { - check_packet_stats(fds); + check_packet_stats(fds, 0); goto out; } @@ -498,7 +499,7 @@ static void run_test(void) if (cfg_payload_len == DATA_LEN && !cfg_use_vlan) { do_rx(fds, total_len - sizeof(struct virtio_net_hdr), tbuf + sizeof(struct virtio_net_hdr), true); - check_packet_stats(fds); + check_packet_stats(fds, 1); } do_rx(fdr, cfg_payload_len, tbuf + total_len - cfg_payload_len, false); From 2544d30c11eb3f70ffe867449f0a5721e130ae7d Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Tue, 28 Jul 2026 09:09:28 -0700 Subject: [PATCH 0622/1433] selftests/net: psock_snd: unify on recvmsg() Don't maintain two RX paths in do_rx(). Unify on recvmsg() and pass aux data only when needed. No functional change. Signed-off-by: Joe Damato Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260728160935.4128982-4-joe@dama.to Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/psock_snd.c | 28 ++++++++++--------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/tools/testing/selftests/net/psock_snd.c b/tools/testing/selftests/net/psock_snd.c index d506ea19491b..b2593daca603 100644 --- a/tools/testing/selftests/net/psock_snd.c +++ b/tools/testing/selftests/net/psock_snd.c @@ -314,25 +314,22 @@ static void do_rx(int fd, int expected_len, char *expected, bool is_psock) { char cmsg_buf[1024] __attribute__((aligned(8))) = {}; bool aux = is_psock && cfg_aux_data; - struct msghdr msg = {}; - struct iovec iov[1]; + struct iovec iov = { + .iov_base = rbuf, + .iov_len = sizeof(rbuf), + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + }; int ret; if (aux) { - iov[0].iov_base = rbuf; - iov[0].iov_len = sizeof(rbuf); - - msg.msg_iov = iov; - msg.msg_iovlen = 1; - msg.msg_control = cmsg_buf; msg.msg_controllen = sizeof(cmsg_buf); - - ret = recvmsg(fd, &msg, 0); - } else { - ret = recv(fd, rbuf, sizeof(rbuf), 0); } + ret = recvmsg(fd, &msg, 0); if (ret == -1) error(1, errno, "recv"); if (ret != expected_len) @@ -341,11 +338,8 @@ static void do_rx(int fd, int expected_len, char *expected, bool is_psock) if (memcmp(rbuf, expected, ret)) error(1, 0, "recv: data mismatch"); - if (aux) { - struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); - - check_aux_data(cmsg, expected_len); - } + if (aux) + check_aux_data(CMSG_FIRSTHDR(&msg), expected_len); fprintf(stderr, "rx: %u\n", ret); } From f5a445d2a2a335b2011e90558ec9cdd48ae79ada Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Tue, 28 Jul 2026 09:09:29 -0700 Subject: [PATCH 0623/1433] selftests/net: psock_snd: Verify sll_pkttype Extend do_rx() to take an expected packet type. Existing callers pass -1 to skip the check. Signed-off-by: Joe Damato Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260728160935.4128982-5-joe@dama.to Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/psock_snd.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/net/psock_snd.c b/tools/testing/selftests/net/psock_snd.c index b2593daca603..f23877942ea3 100644 --- a/tools/testing/selftests/net/psock_snd.c +++ b/tools/testing/selftests/net/psock_snd.c @@ -310,10 +310,13 @@ static void check_aux_data(struct cmsghdr *cmsg, int expected_len) error(1, 0, "cmsg tp_snaplen != %u", expected_len); } -static void do_rx(int fd, int expected_len, char *expected, bool is_psock) +/* expected_pkttype < 0 skips the sll_pkttype check. */ +static void do_rx(int fd, int expected_len, char *expected, bool is_psock, + int expected_pkttype) { char cmsg_buf[1024] __attribute__((aligned(8))) = {}; bool aux = is_psock && cfg_aux_data; + struct sockaddr_ll saddr = {}; struct iovec iov = { .iov_base = rbuf, .iov_len = sizeof(rbuf), @@ -328,6 +331,10 @@ static void do_rx(int fd, int expected_len, char *expected, bool is_psock) msg.msg_control = cmsg_buf; msg.msg_controllen = sizeof(cmsg_buf); } + if (is_psock) { + msg.msg_name = &saddr; + msg.msg_namelen = sizeof(saddr); + } ret = recvmsg(fd, &msg, 0); if (ret == -1) @@ -341,6 +348,10 @@ static void do_rx(int fd, int expected_len, char *expected, bool is_psock) if (aux) check_aux_data(CMSG_FIRSTHDR(&msg), expected_len); + if (expected_pkttype >= 0 && saddr.sll_pkttype != expected_pkttype) + error(1, 0, "recv: sll_pkttype %d != %d", + saddr.sll_pkttype, expected_pkttype); + fprintf(stderr, "rx: %u\n", ret); } @@ -492,11 +503,11 @@ static void run_test(void) /* BPF filter accepts only this length, vlan changes MAC */ if (cfg_payload_len == DATA_LEN && !cfg_use_vlan) { do_rx(fds, total_len - sizeof(struct virtio_net_hdr), - tbuf + sizeof(struct virtio_net_hdr), true); + tbuf + sizeof(struct virtio_net_hdr), true, -1); check_packet_stats(fds, 1); } - do_rx(fdr, cfg_payload_len, tbuf + total_len - cfg_payload_len, false); + do_rx(fdr, cfg_payload_len, tbuf + total_len - cfg_payload_len, false, -1); out: if (close(fds)) From b75609e42bbc8fa23b83eb127c0a337a6e85f2ed Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Tue, 28 Jul 2026 09:09:30 -0700 Subject: [PATCH 0624/1433] selftests/net: test PACKET_IGNORE_OUTGOING Test that setsockopt PACKET_IGNORE_OUTGOING: - works with valid values (0 and 1), - rejects values outside of that range, and When enabled for a loopback sniffer, it only produces 1 copy of the packet (rx) instead of two copies (tx and rx). Use sll_pkttype to check the direction of the packet to ensure the correct packet is sniffed. Reviewed-by: Willem de Bruijn Signed-off-by: Joe Damato Link: https://patch.msgid.link/20260728160935.4128982-6-joe@dama.to Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/psock_snd.c | 84 +++++++++++++++++++++++- tools/testing/selftests/net/psock_snd.sh | 5 ++ 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/net/psock_snd.c b/tools/testing/selftests/net/psock_snd.c index f23877942ea3..0f6a30b26912 100644 --- a/tools/testing/selftests/net/psock_snd.c +++ b/tools/testing/selftests/net/psock_snd.c @@ -41,6 +41,7 @@ static bool cfg_use_vlan; static bool cfg_use_vnet; static bool cfg_drop; static bool cfg_aux_data; +static bool cfg_ignore_outgoing; static char *cfg_ifname = "lo"; static int cfg_mtu = 1500; @@ -377,7 +378,14 @@ static int setup_sniffer(void) error(1, errno, "setsockopt PACKET_AUXDATA"); pair_udp_setfilter(fd); - do_bind(fd); + + /* binding to ETH_P_ALL adds the sniffer to ptype_all, which will see + * the dev_queue_xmit_nit copy. ignore_outgoing should suppress this. + */ + if (cfg_ignore_outgoing) + do_bind_proto(fd, ETH_P_ALL); + else + do_bind(fd); return fd; } @@ -386,7 +394,7 @@ static void parse_opts(int argc, char **argv) { int c; - while ((c = getopt(argc, argv, "abcCdDgl:qt:vV")) != -1) { + while ((c = getopt(argc, argv, "abcCdDgil:qt:vV")) != -1) { switch (c) { case 'a': cfg_aux_data = true; @@ -409,6 +417,9 @@ static void parse_opts(int argc, char **argv) case 'g': cfg_use_gso = true; break; + case 'i': + cfg_ignore_outgoing = true; + break; case 'l': cfg_payload_len = strtoul(optarg, NULL, 0); break; @@ -443,6 +454,10 @@ static void parse_opts(int argc, char **argv) if (cfg_aux_data && cfg_drop) error(1, 0, "option aux data (-a) conflicts with drop (-D)"); + + if (cfg_ignore_outgoing && (cfg_drop || cfg_aux_data)) + error(1, 0, + "option ignore outgoing (-i) conflicts with -D and -a"); } static void check_packet_stats(int fd, unsigned int expected_packets) @@ -486,6 +501,66 @@ static void check_packet_stats(int fd, unsigned int expected_packets) error(1, 0, "stats: tp_drops %u != 0 after clear", st.tp_drops); } +static void set_ignore_outgoing(int fd, int val) +{ + socklen_t len = sizeof(int); + int got = -1; + + if (setsockopt(fd, SOL_PACKET, PACKET_IGNORE_OUTGOING, + &val, sizeof(val))) + error(1, errno, "setsockopt PACKET_IGNORE_OUTGOING %d", val); + + if (getsockopt(fd, SOL_PACKET, PACKET_IGNORE_OUTGOING, &got, &len)) + error(1, errno, "getsockopt PACKET_IGNORE_OUTGOING"); + if (got != val) + error(1, 0, "getsockopt: expected %d got %d", val, got); +} + +static void check_ignore_outgoing_range(int fd) +{ + int val; + + /* Values outside [0, 1] must be rejected with -EINVAL. */ + val = 2; + if (setsockopt(fd, SOL_PACKET, PACKET_IGNORE_OUTGOING, + &val, sizeof(val)) != -1 || errno != EINVAL) + error(1, errno, + "setsockopt PACKET_IGNORE_OUTGOING val=2: expected EINVAL"); + + val = -1; + if (setsockopt(fd, SOL_PACKET, PACKET_IGNORE_OUTGOING, + &val, sizeof(val)) != -1 || errno != EINVAL) + error(1, errno, + "setsockopt PACKET_IGNORE_OUTGOING val=-1: expected EINVAL"); +} + +static void test_ignore_outgoing(int fds) +{ + char *expected = tbuf + sizeof(struct virtio_net_hdr); + int expected_len; + + /* ptype_all sniffer on loopback should produce two copies per packet + * (RX and TX). + */ + expected_len = do_tx(); + expected_len -= sizeof(struct virtio_net_hdr); + do_rx(fds, expected_len, expected, true, PACKET_OUTGOING); + do_rx(fds, expected_len, expected, true, PACKET_HOST); + check_packet_stats(fds, 2); + + /* 0 and 1 accepted; anything else rejected. */ + set_ignore_outgoing(fds, 0); + set_ignore_outgoing(fds, 1); + check_ignore_outgoing_range(fds); + + /* With PACKET_IGNORE_OUTGOING set, only the rx copy survives. */ + do_tx(); + do_rx(fds, expected_len, expected, true, PACKET_HOST); + if (recv(fds, rbuf, sizeof(rbuf), 0) != -1 || errno != EAGAIN) + error(1, errno, "expected EAGAIN, got extra packet"); + check_packet_stats(fds, 1); +} + static void run_test(void) { int fdr, fds, total_len; @@ -493,6 +568,11 @@ static void run_test(void) fdr = setup_rx(); fds = setup_sniffer(); + if (cfg_ignore_outgoing) { + test_ignore_outgoing(fds); + goto out; + } + total_len = do_tx(); if (cfg_drop) { diff --git a/tools/testing/selftests/net/psock_snd.sh b/tools/testing/selftests/net/psock_snd.sh index 111c9e2f0d21..7fa0a3297988 100755 --- a/tools/testing/selftests/net/psock_snd.sh +++ b/tools/testing/selftests/net/psock_snd.sh @@ -102,4 +102,9 @@ echo "test drops statistics" echo "test aux data" ./in_netns.sh ./psock_snd -a +# test ignore outgoing + +echo "test ignore outgoing" +./in_netns.sh ./psock_snd -i + echo "OK. All tests passed" From ec83512ba345b1e415ca491509a7a952db84c4c7 Mon Sep 17 00:00:00 2001 From: Xiang-Bin Shi Date: Mon, 27 Jul 2026 13:45:38 +0800 Subject: [PATCH 0625/1433] atm: remove unused exported helpers Commit 6deb53595092 ("net: remove unused ATM protocols and legacy ATM device drivers") removed the remaining in-tree users of atm_alloc_charge(), atm_pcr_goal(), sonet_copy_stats() and sonet_subtract_stats(). Remove these unused exported helpers and their declarations. The removal of the SONET statistics helpers also leaves include/linux/sonet.h without users, so remove the internal header and its MAINTAINERS entry. Signed-off-by: Xiang-Bin Shi Link: https://patch.msgid.link/20260727054538.196437-1-eric91102091@gmail.com Signed-off-by: Jakub Kicinski --- MAINTAINERS | 1 - include/linux/atmdev.h | 3 -- include/linux/sonet.h | 20 ----------- net/atm/atm_misc.c | 79 ------------------------------------------ 4 files changed, 103 deletions(-) delete mode 100644 include/linux/sonet.h diff --git a/MAINTAINERS b/MAINTAINERS index 2d420d40782e..3d17b7b143c4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4279,7 +4279,6 @@ W: http://linux-atm.sourceforge.net F: drivers/atm/ F: drivers/usb/atm/ F: include/linux/atm* -F: include/linux/sonet.h F: include/uapi/linux/atm* F: include/uapi/linux/sonet.h F: net/atm/ diff --git a/include/linux/atmdev.h b/include/linux/atmdev.h index fe21d2f547b5..92c2e20e7416 100644 --- a/include/linux/atmdev.h +++ b/include/linux/atmdev.h @@ -229,9 +229,6 @@ static inline void atm_dev_put(struct atm_dev *dev) int atm_charge(struct atm_vcc *vcc,int truesize); -struct sk_buff *atm_alloc_charge(struct atm_vcc *vcc,int pdu_size, - gfp_t gfp_flags); -int atm_pcr_goal(const struct atm_trafprm *tp); void vcc_release_async(struct atm_vcc *vcc, int reply); diff --git a/include/linux/sonet.h b/include/linux/sonet.h deleted file mode 100644 index 2b802b6d12ad..000000000000 --- a/include/linux/sonet.h +++ /dev/null @@ -1,20 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* sonet.h - SONET/SHD physical layer control */ -#ifndef LINUX_SONET_H -#define LINUX_SONET_H - - -#include -#include - -struct k_sonet_stats { -#define __HANDLE_ITEM(i) atomic_t i - __SONET_ITEMS -#undef __HANDLE_ITEM -}; - -extern void sonet_copy_stats(struct k_sonet_stats *from,struct sonet_stats *to); -extern void sonet_subtract_stats(struct k_sonet_stats *from, - struct sonet_stats *to); - -#endif diff --git a/net/atm/atm_misc.c b/net/atm/atm_misc.c index a30b83c1cb3f..c9d7609da184 100644 --- a/net/atm/atm_misc.c +++ b/net/atm/atm_misc.c @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -22,81 +21,3 @@ int atm_charge(struct atm_vcc *vcc, int truesize) return 0; } EXPORT_SYMBOL(atm_charge); - -struct sk_buff *atm_alloc_charge(struct atm_vcc *vcc, int pdu_size, - gfp_t gfp_flags) -{ - struct sock *sk = sk_atm(vcc); - int guess = SKB_TRUESIZE(pdu_size); - - atm_force_charge(vcc, guess); - if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf) { - struct sk_buff *skb = alloc_skb(pdu_size, gfp_flags); - - if (skb) { - atomic_add(skb->truesize-guess, - &sk->sk_rmem_alloc); - return skb; - } - } - atm_return(vcc, guess); - atomic_inc(&vcc->stats->rx_drop); - return NULL; -} -EXPORT_SYMBOL(atm_alloc_charge); - - -/* - * atm_pcr_goal returns the positive PCR if it should be rounded up, the - * negative PCR if it should be rounded down, and zero if the maximum available - * bandwidth should be used. - * - * The rules are as follows (* = maximum, - = absent (0), x = value "x", - * (x+ = x or next value above x, x- = x or next value below): - * - * min max pcr result min max pcr result - * - - - * (UBR only) x - - x+ - * - - * * x - * * - * - - z z- x - z z- - * - * - * x * - x+ - * - * * * x * * * - * - * z z- x * z z- - * - y - y- x y - x+ - * - y * y- x y * y- - * - y z z- x y z z- - * - * All non-error cases can be converted with the following simple set of rules: - * - * if pcr == z then z- - * else if min == x && pcr == - then x+ - * else if max == y then y- - * else * - */ - -int atm_pcr_goal(const struct atm_trafprm *tp) -{ - if (tp->pcr && tp->pcr != ATM_MAX_PCR) - return -tp->pcr; - if (tp->min_pcr && !tp->pcr) - return tp->min_pcr; - if (tp->max_pcr != ATM_MAX_PCR) - return -tp->max_pcr; - return 0; -} -EXPORT_SYMBOL(atm_pcr_goal); - -void sonet_copy_stats(struct k_sonet_stats *from, struct sonet_stats *to) -{ -#define __HANDLE_ITEM(i) to->i = atomic_read(&from->i) - __SONET_ITEMS -#undef __HANDLE_ITEM -} -EXPORT_SYMBOL(sonet_copy_stats); - -void sonet_subtract_stats(struct k_sonet_stats *from, struct sonet_stats *to) -{ -#define __HANDLE_ITEM(i) atomic_sub(to->i, &from->i) - __SONET_ITEMS -#undef __HANDLE_ITEM -} -EXPORT_SYMBOL(sonet_subtract_stats); From 799b5f45cb8194ebd06c9c89e0afdad5bedd2cc5 Mon Sep 17 00:00:00 2001 From: Stanislaw Gruszka Date: Thu, 23 Jul 2026 13:06:40 +0200 Subject: [PATCH 0626/1433] wifi: rtl818x: initialize eeprom_93cx6 struct to zero Commit 7738a7ab9d12 ("misc: eeprom: eeprom_93cx6: Add quirk for extra read clock cycle") added extra 'quirk' field to struct eeprom_93cx6. Many existing users of eeprom_93cx6, including rtl818x drivers, allocate the structure on the stack without initializing all fields. As a result, the added quirk field has an undefined value and can randomly cause reading wrong data from the EEPROM. Fix by initializing the structures with {}. Fixes: 7738a7ab9d12 ("misc: eeprom: eeprom_93cx6: Add quirk for extra read clock cycle") Cc: stable@kernel.org # v6.13+ Signed-off-by: Stanislaw Gruszka Reviewed-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260723110640.8588-1-stf_xl@wp.pl --- drivers/net/wireless/realtek/rtl818x/rtl8180/dev.c | 2 +- drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtl818x/rtl8180/dev.c b/drivers/net/wireless/realtek/rtl818x/rtl8180/dev.c index 070c0431c482..4a5989172a5e 100644 --- a/drivers/net/wireless/realtek/rtl818x/rtl8180/dev.c +++ b/drivers/net/wireless/realtek/rtl818x/rtl8180/dev.c @@ -1652,7 +1652,7 @@ static void rtl8180_eeprom_register_write(struct eeprom_93cx6 *eeprom) static void rtl8180_eeprom_read(struct rtl8180_priv *priv) { - struct eeprom_93cx6 eeprom; + struct eeprom_93cx6 eeprom = {}; int eeprom_cck_table_adr; u16 eeprom_val; int i; diff --git a/drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c b/drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c index 1d21c468a236..a766712187f7 100644 --- a/drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c +++ b/drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c @@ -1445,7 +1445,7 @@ static int rtl8187_probe(struct usb_interface *intf, struct usb_device *udev = interface_to_usbdev(intf); struct ieee80211_hw *dev; struct rtl8187_priv *priv; - struct eeprom_93cx6 eeprom; + struct eeprom_93cx6 eeprom = {}; struct ieee80211_channel *channel; const char *chip_name; u16 txpwr, reg; From 6496ce90845df2d22fb8e8ed235cd2936fad41c8 Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Thu, 23 Jul 2026 17:15:37 +0530 Subject: [PATCH 0627/1433] wifi: rtlwifi: rtl8192du: Fix possible memory leak in rtl92du_init_sw_vars() The memory allocated inside rtl92du_init_shared_data() is not freed in any of the subsequent error paths in rtl92du_init_sw_vars(). Fix that by adding a call to rtl92du_deinit_shared_data() in the error path. Fixes: b5dc8873b6ff ("wifi: rtlwifi: Add rtl8192du/sw.c") Cc: stable@vger.kernel.org Signed-off-by: Abdun Nihaal Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260723114539.136986-1-nihaal@cse.iitm.ac.in --- drivers/net/wireless/realtek/rtlwifi/rtl8192du/sw.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/realtek/rtlwifi/rtl8192du/sw.c b/drivers/net/wireless/realtek/rtlwifi/rtl8192du/sw.c index 7bafb051d5ec..88e8cc590c54 100644 --- a/drivers/net/wireless/realtek/rtlwifi/rtl8192du/sw.c +++ b/drivers/net/wireless/realtek/rtlwifi/rtl8192du/sw.c @@ -145,8 +145,10 @@ static int rtl92du_init_sw_vars(struct ieee80211_hw *hw) /* for firmware buf */ rtlpriv->rtlhal.pfirmware = kmalloc(0x8000, GFP_KERNEL); - if (!rtlpriv->rtlhal.pfirmware) - return -ENOMEM; + if (!rtlpriv->rtlhal.pfirmware) { + err = -ENOMEM; + goto error; + } rtlpriv->max_fw_size = 0x8000; pr_info("Driver for Realtek RTL8192DU WLAN interface\n"); @@ -160,10 +162,14 @@ static int rtl92du_init_sw_vars(struct ieee80211_hw *hw) pr_err("Failed to request firmware!\n"); kfree(rtlpriv->rtlhal.pfirmware); rtlpriv->rtlhal.pfirmware = NULL; - return err; + goto error; } return 0; + +error: + rtl92du_deinit_shared_data(hw); + return err; } static void rtl92du_deinit_sw_vars(struct ieee80211_hw *hw) From 3c2999d13eeb222ae56631aeb7ca248090f2b210 Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Thu, 23 Jul 2026 17:31:15 +0530 Subject: [PATCH 0628/1433] wifi: rtlwifi: pci: fix error path in rtl_pci_probe() In the last error path in rtl_pci_probe(), the cleanup functions are skipped due to a wrong goto label. Moreover, the successful call to rtl_init_rfkill(), ieee80211_register_hw(), rtl_debug_add_one() have to be reverted. Fix this issue by updating the labels and adding the relevant cleanup functions to the last error path. Fixes: 0c8173385e54 ("rtl8192ce: Add new driver") Signed-off-by: Abdun Nihaal Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260723120118.145383-1-nihaal@cse.iitm.ac.in --- drivers/net/wireless/realtek/rtlwifi/pci.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtlwifi/pci.c b/drivers/net/wireless/realtek/rtlwifi/pci.c index 220fb4dc7927..d3b79d0d3eb6 100644 --- a/drivers/net/wireless/realtek/rtlwifi/pci.c +++ b/drivers/net/wireless/realtek/rtlwifi/pci.c @@ -2244,13 +2244,17 @@ int rtl_pci_probe(struct pci_dev *pdev, rtl_dbg(rtlpriv, COMP_INIT, DBG_DMESG, "%s: failed to register IRQ handler\n", wiphy_name(hw->wiphy)); - goto fail3; + goto fail6; } rtlpci->irq_alloc = 1; set_bit(RTL_STATUS_INTERFACE_START, &rtlpriv->status); return 0; +fail6: + rtl_deinit_rfkill(hw); + rtl_debug_remove_one(hw); + ieee80211_unregister_hw(hw); fail5: rtl_pci_deinit(hw); fail4: From 6f24d22a31af9d53853a3005714888cc66cf3e1d Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 24 Jul 2026 21:56:27 +0800 Subject: [PATCH 0629/1433] wifi: rtw89: coex: Add chip initial info version 11 for RTL8922A/D The version 11 init info add current RF path control information. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260724135640.3195044-2-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 2 + drivers/net/wireless/realtek/rtw89/coex.h | 19 ++++ drivers/net/wireless/realtek/rtw89/core.h | 47 ++++++++++ drivers/net/wireless/realtek/rtw89/fw.c | 75 ++++++++++++++++ drivers/net/wireless/realtek/rtw89/fw.h | 6 ++ drivers/net/wireless/realtek/rtw89/rtw8922a.c | 88 ++++++++++++++++--- drivers/net/wireless/realtek/rtw89/rtw8922d.c | 40 ++++++--- 7 files changed, 251 insertions(+), 26 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 6a1888d8c476..f94866e1f43a 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -3199,6 +3199,8 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index) rtw89_fw_h2c_cxdrv_init_v7(rtwdev, index); else if (ver->fcxinit == 10) rtw89_fw_h2c_cxdrv_init_v10(rtwdev, index); + else if (ver->fcxinit == 11) + rtw89_fw_h2c_cxdrv_init_v11(rtwdev, index); else rtw89_fw_h2c_cxdrv_init(rtwdev, index); break; diff --git a/drivers/net/wireless/realtek/rtw89/coex.h b/drivers/net/wireless/realtek/rtw89/coex.h index 3238c44429a9..41a68ab21d80 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.h +++ b/drivers/net/wireless/realtek/rtw89/coex.h @@ -127,6 +127,11 @@ enum btc_fddt_en { ((__rssi == BTC_RSSI_ST_LOW || \ __rssi == BTC_RSSI_ST_HIGH) ? 1 : 0); }) +/* Antenna TX/RX path position masks and helpers */ +#define BTC_ANT_TX_MASK 0xf0 +#define BTC_ANT_RX_MASK 0x0f +#define BTC_ANT_SHIFT 4 + enum btc_ant { BTC_ANT_SHARED = 0, BTC_ANT_DEDICATED, @@ -473,4 +478,18 @@ void btc_dw2b(u8 *buf, size_t idx, u32 val) buf[idx + 3] = u32_get_bits(val, MASKBYTE3); } +static inline +u8 _btc_get_rf_path_from_ant_num(struct rtw89_btc *btc, u8 antnum) +{ + switch (antnum) { + default: + case 1: + return btc->mdinfo.ant.single_pos; + case 2: + return RF_PATH_AB; + case 3: + return RF_PATH_ABC; + } +} + #endif diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 7573f1965c98..7147e186b4a5 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -1537,6 +1537,21 @@ struct rtw89_btc_ant_info_v10 { u8 ant_xmap[2][4]; } __packed; +struct rtw89_btc_ant_info_v11 { + u8 type; + u8 num; + u8 isolation; + u8 single_pos; + + u8 stream_cnt; + u8 path_pos; /* WL path position: Tx[7:4], Rx[3:0] */ + u8 btg_pos; + u8 btg1_pos; + u8 func[5]; + u8 ant_xmap[2][4]; + u8 rsvd0[3]; +} __packed; + struct rtw89_btc_ant_info { u8 type; /* shared, dedicated(non-shared) */ u8 num; /* antenna count */ @@ -1546,6 +1561,7 @@ struct rtw89_btc_ant_info { u8 stream_cnt; /* spatial_stream count: Tx[7:4], Rx[3:0] */ u8 btg_pos; /* BT0 btg-circuit at 0:WL-S0/1:WL-S1 */ u8 btg1_pos; /* BT1 btg-circuit at 0:WL-S0/1:WL-S1 */ + u8 path_pos; /* WL path position: Tx[7:4], Rx[3:0] */ u8 func[5]; /* function at 1~5 Ant refer to enum btc_bt_func_type */ u8 ant_xmap[2][4]; @@ -2387,10 +2403,25 @@ struct rtw89_btc_module_v10 { struct rtw89_btc_ant_info_v10 ant; } __packed; +struct rtw89_btc_module_v11 { + u8 rfe_type; + u8 wa_type; + u8 kt_ver; + u8 kt_ver_adie; + + u8 bt0_pos; + u8 bt0_sw_type; + u8 bt1_pos; + u8 bt1_sw_type; + + struct rtw89_btc_ant_info_v11 ant; +} __packed; + union rtw89_btc_module_info { struct rtw89_btc_module_v0 md_v0; struct rtw89_btc_module_v7 md_v7; struct rtw89_btc_module_v10 md_v10; + struct rtw89_btc_module_v11 md_v11; }; struct rtw89_btc_module { @@ -2473,11 +2504,26 @@ struct rtw89_btc_init_info_v10 { struct rtw89_btc_module_v10 module; } __packed; +struct rtw89_btc_init_info_v11 { + u8 endian_type; /* 0: little-endian, 1:big-endian */ + u8 init_mode; /* refer to enum BTC_MODE_xxx */ + u8 wl_init_ok; + u8 bt0_function; + + u8 bt1_function; + u8 bt2_function; + u8 pta_mode; + u8 pta_direction; + + struct rtw89_btc_module_v11 module; +}; + union rtw89_btc_init_info_u { struct rtw89_btc_init_info_v0 init_v0; struct rtw89_btc_init_info_v7 init_v7; struct rtw89_btc_init_info_v10 init_v10; struct rtw89_btc_init_info_v107 init_v107; + struct rtw89_btc_init_info_v11 init_v11; }; struct rtw89_btc_init_info { @@ -2547,6 +2593,7 @@ enum rtw89_btc_bt_func_type { #define RTW89_BTC_BT_DEF_BR_TX_PWR 4 #define RTW89_BTC_BT_DEF_LE_TX_PWR 4 #define RTW89_BTC_DEFAULT_ANISO 10 +#define RTW89_BTC_BT_DEF_LE_TX_PWR_1 10 struct rtw89_btc_bt_scan_info_v1 { __le16 win; diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 3b8c144e0e98..a3a04697e2e6 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -5973,6 +5973,81 @@ int rtw89_fw_h2c_cxdrv_init_v10(struct rtw89_dev *rtwdev, u8 type) return ret; } +int rtw89_fw_h2c_cxdrv_init_v11(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_init_info *init = &dm->init_info; + struct rtw89_h2c_cxinit_v11 *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + int ret; + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_init_v11\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxinit_v11 *)skb->data; + + h2c->hdr.type = type; + h2c->hdr.ver = 11; + h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; + + h2c->init.endian_type = init->endian_type; + h2c->init.init_mode = init->init_mode; + h2c->init.wl_init_ok = init->wl_init_ok; + h2c->init.bt0_function = init->bt0_function; + + h2c->init.bt1_function = init->bt1_function; + h2c->init.bt2_function = init->bt2_function; + h2c->init.pta_mode = init->pta_mode; + h2c->init.pta_direction = init->pta_direction; + + h2c->init.module.rfe_type = init->module.rfe_type; + h2c->init.module.wa_type = init->module.wa_type; + h2c->init.module.kt_ver = init->module.kt_ver; + h2c->init.module.kt_ver_adie = init->module.kt_ver_adie; + + h2c->init.module.bt0_pos = init->module.bt0_pos; + h2c->init.module.bt0_sw_type = init->module.bt0_sw_type; + h2c->init.module.bt1_pos = init->module.bt1_pos; + h2c->init.module.bt1_sw_type = init->module.bt1_sw_type; + + h2c->init.module.ant.type = init->module.ant.type; + h2c->init.module.ant.num = init->module.ant.num; + h2c->init.module.ant.isolation = init->module.ant.isolation; + h2c->init.module.ant.single_pos = init->module.ant.single_pos; + + h2c->init.module.ant.stream_cnt = init->module.ant.stream_cnt; + h2c->init.module.ant.path_pos = init->module.ant.path_pos; + h2c->init.module.ant.btg_pos = init->module.ant.btg_pos; + h2c->init.module.ant.btg1_pos = init->module.ant.btg1_pos; + memcpy(h2c->init.module.ant.func, init->module.ant.func, + sizeof(init->module.ant.func)); + + memcpy(h2c->init.module.ant.ant_xmap, init->module.ant.ant_xmap, + sizeof(init->module.ant.ant_xmap)); + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, + len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + #define PORT_DATA_OFFSET 4 #define H2C_LEN_CXDRVINFO_ROLE_DBCC_LEN 12 #define H2C_LEN_CXDRVINFO_ROLE_SIZE(max_role_num) \ diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index e86014ca249a..c0a00b721060 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -2603,6 +2603,11 @@ struct rtw89_h2c_cxinit_v10 { struct rtw89_btc_init_info_v10 init; } __packed; +struct rtw89_h2c_cxinit_v11 { + struct rtw89_h2c_cxhdr_v7 hdr; + struct rtw89_btc_init_info_v11 init; +} __packed; + #define RTW89_H2C_CXROLE_V101_ROLE_STAT_CONNTECTED BIT(0) #define RTW89_H2C_CXROLE_V101_ROLE_STAT_PID GENMASK(3, 1) #define RTW89_H2C_CXROLE_V101_ROLE_STAT_PHY BIT(4) @@ -5456,6 +5461,7 @@ int rtw89_fw_h2c_cxdrv_role_v2(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role_v7(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role_v8(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role_v10(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxdrv_init_v11(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_osi_info(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_osi_info_v6(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_ctrl(struct rtw89_dev *rtwdev, u8 type); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922a.c b/drivers/net/wireless/realtek/rtw89/rtw8922a.c index 2586fd2df8ab..2d225e009b28 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922a.c @@ -2721,25 +2721,33 @@ static u32 rtw8922a_chan_to_rf18_val(struct rtw89_dev *rtwdev, static void rtw8922a_btc_set_rfe(struct rtw89_dev *rtwdev) { - struct rtw89_btc_module *md = &rtwdev->btc.mdinfo; + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_module *md = &btc->mdinfo; + u8 i, j; + u8 tx_path_pos, rx_path_pos; md->rfe_type = rtwdev->efuse.rfe_type; md->kt_ver = rtwdev->hal.cv; - md->bt_solo = 0; - md->bt0_sw_type = BTC_SWITCH_INTERNAL; + md->kt_ver_adie = rtwdev->hal.acv; md->wa_type = 0; - md->ant.type = BTC_ANT_SHARED; md->ant.num = 2; - md->ant.isolation = 10; - md->ant.diversity = 0; - md->ant.single_pos = RF_PATH_A; - md->ant.btg_pos = RF_PATH_B; + md->ant.single_pos = BTC_RF_S0; if (md->kt_ver <= 1) md->wa_type |= BTC_WA_HFP_ZB; - rtwdev->btc.cx.bt_ext.func_type = BTC_3CX_NONE; + btc->cx.bt0.ant_iso_to_wl = md->ant.isolation; + btc->cx.bt1.ant_iso_to_wl = md->ant.isolation; + + md->ant.stream_cnt = 2; + md->ant.btg_pos = RF_PATH_B; + md->ant.single_pos = RF_PATH_A; + + btc->cx.bt0.band_56G_support = 0; + btc->cx.bt1.band_56G_support = 0; + btc->cx.bt0.func_type = BTC_BTF_BT; + btc->cx.bt1.func_type = BTC_BTF_NONE; if (md->rfe_type == 0) { rtwdev->btc.dm.error.map.rfe_type0 = true; @@ -2747,17 +2755,69 @@ static void rtw8922a_btc_set_rfe(struct rtw89_dev *rtwdev) } md->ant.num = (md->rfe_type % 2) ? 2 : 3; - if (md->kt_ver == 0) md->ant.num = 2; - if (md->ant.num == 3) { - md->ant.type = BTC_ANT_DEDICATED; - md->bt0_pos = BTC_BT_ALONE; - } else { + memset(btc->dm.ant_xmap, 0, sizeof(btc->dm.ant_xmap)); + + switch (md->ant.num) { + case 1: md->ant.type = BTC_ANT_SHARED; md->bt0_pos = BTC_BT_BTG; + md->bt0_sw_type = BTC_SWITCH_V1_INTERNAL; + md->ant.btg_pos = BTC_RF_S0; + btc->dm.ant_xmap[BTC_RF_S0][BTC_BT_1ST] = 1; + md->ant.func[0] = BTC_EFMAP_BT0; + break; + case 2: /* 2-Ant */ + default: + md->ant.type = BTC_ANT_SHARED; + md->bt0_pos = BTC_BT_BTG; + md->bt0_sw_type = BTC_SWITCH_V1_INTERNAL; + btc->dm.wl_trx_nss_en = 1; + btc->dm.ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 1; + md->ant.func[1] = BTC_EFMAP_BT0; + break; + case 3: /* 3-Ant, 3 different BT-configuration */ + /* WL-S0 + WL-S1 + BT0-S0 */ + md->ant.type = BTC_ANT_DEDICATED; + md->bt0_pos = BTC_BT_ALONE; + md->bt0_sw_type = BTC_SWITCH_V1_NONE; + md->ant.func[2] = BTC_EFMAP_BT0; } + + tx_path_pos = _btc_get_rf_path_from_ant_num(btc, rtwdev->hal.tx_nss); + rx_path_pos = _btc_get_rf_path_from_ant_num(btc, rtwdev->hal.rx_nss); + /* Combine TX[7:4] and RX[3:0] into path_pos byte */ + md->ant.path_pos = ((tx_path_pos << BTC_ANT_SHIFT) & BTC_ANT_TX_MASK) | + (rx_path_pos & BTC_ANT_RX_MASK); + + switch (btc->cx.bt_ext.chip_id) { + default: + case BTC_ESOC_NONE: + memset(&btc->cx.bt_ext, 0, sizeof(struct rtw89_btc_extsoc_info)); + btc->cx.bt_ext.max_tx_pwr = RTW89_BTC_BT_DEF_LE_TX_PWR_1; + btc->cx.bt_ext.ant_iso_to_wl = RTW89_BTC_DEFAULT_ANISO; + break; + case BTC_ESOC_8771: + btc->cx.bt_ext.func_type = BTC_BTF_THREAD; + btc->cx.bt_ext.hw_coex = BTC_EXTSOC_INTF_PTA; + btc->cx.bt_ext.rf_band_map = 0x1; /* 2.4G only */ + btc->cx.bt_ext.link_weight[BTC_BT_B2G] = 10; + btc->cx.bt_ext.profile_map[BTC_BT_B2G] |= BTC_BT_THREAD; + /* use GPIO 12~15 for Ext-4-wire-PTA */ + btc->cx.bt_ext.hpta_cfg = BIT(12) | BIT(13) | BIT(14) | BIT(15); + md->ant.func[2] = BTC_EFMAP_ZB; + break; + } + + btc->dm.ant_xmap[BTC_RF_S0][BTC_BT_EXT] = 0; + btc->dm.ant_xmap[BTC_RF_S1][BTC_BT_EXT] = 0; + + for (i = BTC_RF_S0; i <= BTC_RF_S1; i++) + for (j = BTC_BT_1ST; j <= BTC_BT_EXT; j++) + md->ant.ant_xmap[i][j] = btc->dm.ant_xmap[i][j]; + rtwdev->btc.btg_pos = md->ant.btg_pos; rtwdev->btc.ant_type = md->ant.type; } diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c index 12c96425a6ff..142e693c87bf 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c @@ -3174,7 +3174,9 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev) struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_cx *cx = &btc->cx; u8 efuse_bt_func, efuse_ant_info, bt_sw_gpio_pos; - u8 is_combo, is_bt_share; + u8 is_combo, is_bt_share, is_spdt; + u8 tx_path_pos, rx_path_pos; + u8 i, j; rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s !!\n", __func__); @@ -3192,7 +3194,8 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev) cx->bt0.ant_iso_to_wl = md->ant.isolation; cx->bt1.ant_iso_to_wl = md->ant.isolation; - md->ant.stream_cnt = 2; + md->ant.stream_cnt = (rtwdev->hal.tx_nss << BTC_ANT_SHIFT) + + rtwdev->hal.rx_nss; md->ant.btg_pos = BTC_RF_S1; /* BTG0 at WL-S1 */ md->ant.btg1_pos = BTC_RF_S0; /* BTG1 at WL-S0 if Dual-BTGA */ @@ -3203,19 +3206,20 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev) efuse_bt_func = rtwdev->efuse.bt_setting_2; - switch ((efuse_bt_func & 0xe0) >> 5) { /* 0xcd[7:5] */ + is_spdt = !!(efuse_bt_func & BIT(5)); + + switch ((efuse_bt_func & 0xc0) >> 6) { /* 0xcd[7:6] */ default: case 0: - case 1: bt_sw_gpio_pos = 5; break; - case 2: + case 1: bt_sw_gpio_pos = 11; break; - case 3: + case 2: bt_sw_gpio_pos = 15; break; - case 4: + case 3: bt_sw_gpio_pos = 20; break; } @@ -3233,9 +3237,8 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev) case 1: /* 1-Ant WL-S0 only & BT0 only */ md->ant.type = BTC_ANT_SHARED; md->bt0_pos = BTC_BT_BTG; - md->bt0_sw_type = BTC_SWITCH_INTERNAL; + md->bt0_sw_type = BTC_SWITCH_V1_INTERNAL; md->ant.btg_pos = BTC_RF_S0; /* BTG0 at WL-S0 */ - md->ant.stream_cnt = 1; md->ant.func[0] = BTC_EFMAP_BT0; dm->ant_xmap[BTC_RF_S0][BTC_BT_1ST] = 1; /* BT0 shared with S0*/ dm->ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 0; /* WL 1T1R no RF-S1 */ @@ -3265,13 +3268,13 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev) if (md->ant.func[0] == BTC_EFMAP_BT1) { /* if 2nd BT exist */ dm->ant_xmap[BTC_RF_S0][BTC_BT_2ND] = 1; - if (md->rfe_type == 12) { /* WL-S0 & BT1 by SPDT */ + if (is_spdt) { /* WL-S0 & BT1 by SPDT */ md->bt1_pos = BTC_BT_ALONE; /* Todo: set SPDT GPIO-ctrl */ md->bt1_sw_type = bt_sw_gpio_pos; } else { /* WL-S0 & BT1-S1 by BTGA */ md->bt1_pos = BTC_BT_BTG; - md->bt1_sw_type = BTC_SWITCH_INTERNAL; + md->bt1_sw_type = BTC_SWITCH_V1_INTERNAL; } } @@ -3284,7 +3287,7 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev) md->ant.func[2] = efuse_bt_func & (~BTC_EFMAP_BT0); md->ant.type = BTC_ANT_SHARED; md->bt0_pos = BTC_BT_BTG; - md->bt0_sw_type = BTC_SWITCH_INTERNAL; + md->bt0_sw_type = BTC_SWITCH_V1_INTERNAL; dm->ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 1; dm->wl_trx_nss_en = 1; /* 1ss MIMO-PS capability */ } else { @@ -3324,6 +3327,12 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev) break; } + tx_path_pos = _btc_get_rf_path_from_ant_num(btc, rtwdev->hal.tx_nss); + rx_path_pos = _btc_get_rf_path_from_ant_num(btc, rtwdev->hal.rx_nss); + /* Combine TX[7:4] and RX[3:0] into path_pos byte */ + md->ant.path_pos = ((tx_path_pos << BTC_ANT_SHIFT) & BTC_ANT_TX_MASK) | + (rx_path_pos & BTC_ANT_RX_MASK); + /* * if only BT0 at BTGA: 2-Ant, 3-Ant(BT1 used dedicated-ant) * can setup dm->wl_trx_nss_en = 1, WL MIMO-PS to 1T1R @@ -3373,6 +3382,13 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev) dm->ant_xmap[BTC_RF_S0][BTC_BT_EXT] = 0; dm->ant_xmap[BTC_RF_S1][BTC_BT_EXT] = 0; + + for (i = BTC_RF_S0; i <= BTC_RF_S1; i++) + for (j = BTC_BT_1ST; j <= BTC_BT_EXT; j++) + md->ant.ant_xmap[i][j] = dm->ant_xmap[i][j]; + + rtwdev->btc.btg_pos = md->ant.btg_pos; + rtwdev->btc.ant_type = md->ant.type; } static void rtw8922d_btc_init_cfg(struct rtw89_dev *rtwdev) From 04852fcad5a9cdec07a162b387643887c3505ece Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 24 Jul 2026 21:56:28 +0800 Subject: [PATCH 0630/1433] wifi: rtw89: coex: Add Wi-Fi MLO info version 2 H2C command The info included MLO status, hardware status, firmware will set corresponding register control to do coexistence (PTA slot priority, RF switch etc.) Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260724135640.3195044-3-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 3 ++ drivers/net/wireless/realtek/rtw89/core.h | 19 ++++++++ drivers/net/wireless/realtek/rtw89/fw.c | 58 +++++++++++++++++++++++ drivers/net/wireless/realtek/rtw89/fw.h | 6 +++ 4 files changed, 86 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index f94866e1f43a..0c5599a23b4a 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -3274,6 +3274,9 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index) else return; + if (ver->fcxmlo == 2) + rtw89_fw_h2c_cxdrv_mlo_v2(rtwdev, index); + break; case CXDRVINFO_OSI: if (!ver->fcxosi || ver->drvinfo_ver == 103) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 7147e186b4a5..0ec63f9510bd 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -1783,6 +1783,25 @@ struct rtw89_btc_wl_dbcc_info { u8 role[RTW89_PHY_NUM]; /* role in each phy */ }; +struct rtw89_btc_wl_mlo_info_v2 { + u8 wmode[RTW89_PHY_NUM]; + u8 ch_type[RTW89_PHY_NUM]; + u8 hwb_rf_band[RTW89_PHY_NUM]; + u8 path_rf_band[RTW89_PHY_NUM]; + + u8 wtype; + u8 mrcx_mode; + u8 mrcx_act_hwb_map; + u8 mrcx_bt_slot_rsp; + + u8 rf_combination; + u8 mlo_en; + u8 mlo_adie; + u8 dual_hw_band_en; + + __le32 link_status; +} __packed; + struct rtw89_btc_wl_mlo_info { u8 wmode[RTW89_PHY_NUM]; /* enum phl_mr_wmode */ u8 ch_type[RTW89_PHY_NUM]; /* enum phl_mr_ch_type */ diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index a3a04697e2e6..5ab80bda3ae5 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -6616,6 +6616,64 @@ int rtw89_fw_h2c_cxdrv_role_v10(struct rtw89_dev *rtwdev, u8 type) return ret; } +int rtw89_fw_h2c_cxdrv_mlo_v2(struct rtw89_dev *rtwdev, u8 type) +{ + struct rtw89_btc_wl_mlo_info *mlo = &rtwdev->btc.cx.wl.mlo_info; + struct rtw89_h2c_cxmlo_v2 *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + int ret; + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_mlo_v2\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_cxmlo_v2 *)skb->data; + + h2c->hdr.type = type; + h2c->hdr.ver = 2; + h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7; + + memcpy(h2c->mlo.wmode, mlo->wmode, + sizeof(h2c->mlo.wmode)); + memcpy(h2c->mlo.ch_type, mlo->ch_type, + sizeof(h2c->mlo.ch_type)); + memcpy(h2c->mlo.hwb_rf_band, mlo->hwb_rf_band, + sizeof(h2c->mlo.hwb_rf_band)); + memcpy(h2c->mlo.path_rf_band, mlo->path_rf_band, + sizeof(h2c->mlo.path_rf_band)); + + h2c->mlo.wtype = mlo->wtype; + h2c->mlo.mrcx_mode = mlo->mrcx_mode; + h2c->mlo.mrcx_act_hwb_map = mlo->mrcx_act_hwb_map; + h2c->mlo.mrcx_bt_slot_rsp = mlo->mrcx_bt_slot_rsp; + + h2c->mlo.rf_combination = mlo->rf_combination; + h2c->mlo.mlo_en = mlo->mlo_en; + h2c->mlo.mlo_adie = mlo->mlo_adie; + h2c->mlo.dual_hw_band_en = mlo->dual_hw_band_en; + h2c->mlo.link_status = cpu_to_le32(mlo->link_status); + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, BTFC_SET, + SET_DRV_INFO, 0, 0, + len); + + ret = rtw89_h2c_tx(rtwdev, skb, false); + if (ret) { + rtw89_err(rtwdev, "failed to send h2c\n"); + goto fail; + } + + return 0; +fail: + dev_kfree_skb_any(skb); + + return ret; +} + int rtw89_fw_h2c_cxdrv_osi_info(struct rtw89_dev *rtwdev, u8 type) { struct rtw89_btc *btc = &rtwdev->btc; diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index c0a00b721060..538df0fb42cc 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -2453,6 +2453,11 @@ struct rtw89_h2c_cxrole_v10 { struct rtw89_btc_wl_role_info_v10 r; } __packed; +struct rtw89_h2c_cxmlo_v2 { + struct rtw89_h2c_cxhdr_v7 hdr; + struct rtw89_btc_wl_mlo_info_v2 mlo; +} __packed; + struct rtw89_h2c_cxosi { struct rtw89_h2c_cxhdr_v7 hdr; struct rtw89_btc_fbtc_outsrc_set_info_v1 osi; @@ -5462,6 +5467,7 @@ int rtw89_fw_h2c_cxdrv_role_v7(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role_v8(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_role_v10(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_init_v11(struct rtw89_dev *rtwdev, u8 type); +int rtw89_fw_h2c_cxdrv_mlo_v2(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_osi_info(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_osi_info_v6(struct rtw89_dev *rtwdev, u8 type); int rtw89_fw_h2c_cxdrv_ctrl(struct rtw89_dev *rtwdev, u8 type); From eb70edecee469aa60421d4a105475008ca275096 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 24 Jul 2026 21:56:29 +0800 Subject: [PATCH 0631/1433] wifi: rtw89: coex: Add firmware 0.35.111.X support for RTL8922A/D Add BTC version table entries for RTL8922A and RTL8922D. The new firmware need driver provide more chip initial related parameters. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260724135640.3195044-4-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 0c5599a23b4a..d873b9482e49 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -133,6 +133,15 @@ static const u32 cxtbl[] = { static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { /* firmware version must be in decreasing order for each chip */ + {RTL8922D, RTW89_FW_VER_CODE(0, 35, 111, 0), + .fcxbtcrpt = 11, .fcxtdma = 8, .fcxslots = 7, .fcxcysta = 8, + .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 8, + .fcxbtver = 8, .fcxbtscan = 8, .fcxbtafh = 8, .fcxbtdevinfo = 8, + .fwlrole = 10, .frptmap = 5, .fcxctrl = 9, .fcxinit = 11, + .fwevntrptl = 1, .fwc2hfunc = 4, .drvinfo_ver = 3, .info_buf = 1800, + .max_role_num = 6, .fcxosi = 6, .fcxmlo = 2, .bt_desired = 8, + .fcxtrx = 9, + }, {RTL8922D, RTW89_FW_VER_CODE(0, 35, 94, 0), .fcxbtcrpt = 11, .fcxtdma = 8, .fcxslots = 7, .fcxcysta = 8, .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 8, @@ -169,6 +178,15 @@ static const struct rtw89_btc_ver rtw89_btc_ver_defs[] = { .max_role_num = 6, .fcxosi = 0, .fcxmlo = 0, .bt_desired = 8, .fcxtrx = 0, }, + {RTL8922A, RTW89_FW_VER_CODE(0, 35, 111, 0), + .fcxbtcrpt = 11, .fcxtdma = 8, .fcxslots = 7, .fcxcysta = 8, + .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 8, + .fcxbtver = 8, .fcxbtscan = 8, .fcxbtafh = 8, .fcxbtdevinfo = 8, + .fwlrole = 10, .frptmap = 5, .fcxctrl = 9, .fcxinit = 11, + .fwevntrptl = 1, .fwc2hfunc = 4, .drvinfo_ver = 3, .info_buf = 1800, + .max_role_num = 6, .fcxosi = 6, .fcxmlo = 2, .bt_desired = 8, + .fcxtrx = 9, + }, {RTL8922A, RTW89_FW_VER_CODE(0, 35, 71, 0), .fcxbtcrpt = 8, .fcxtdma = 7, .fcxslots = 7, .fcxcysta = 7, .fcxstep = 7, .fcxnullsta = 7, .fcxmreg = 7, .fcxgpiodbg = 7, From 86f763e372485932c3f593b720faf31758ab3d62 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 24 Jul 2026 21:56:30 +0800 Subject: [PATCH 0632/1433] wifi: rtw89: coex: Rearrange _ntfy_role_info info rtw89_btc_wl_link_info is duplicated declaring in the function, remove one of them. We need MAC Address only when Wi-Fi role is station, included the copy operation into if statement. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260724135640.3195044-5-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 77 +++++++++++++++-------- 1 file changed, 52 insertions(+), 25 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index d873b9482e49..9da3b23b2d14 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -8675,13 +8675,14 @@ void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev, const struct rtw89_chan *chan = rtw89_chan_get(rtwdev, rtwvif_link->chanctx_idx); struct ieee80211_vif *vif = rtwvif_link_to_vif(rtwvif_link); + struct ieee80211_p2p_noa_attr *noa_attr; + struct ieee80211_p2p_noa_desc *noa_desc; struct ieee80211_bss_conf *bss_conf; struct ieee80211_link_sta *link_sta; struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_wl_link_info r = {0}; - struct rtw89_btc_wl_link_info *wlinfo = NULL; - u8 mode = 0; + u8 i, mode = 0; rcu_read_lock(); @@ -8729,34 +8730,60 @@ void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev, rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], wifi_role=%d\n", rtwvif_link->wifi_role); - wlinfo = &wl->rlink_info[rtwvif_link->port][rtwvif_link->phy_idx]; + if (vif->type == NL80211_IFTYPE_P2P_GO || + vif->type == NL80211_IFTYPE_P2P_CLIENT) { + noa_attr = &bss_conf->p2p_noa_attr; - wlinfo->mode = mode; - wlinfo->role = rtwvif_link->wifi_role; - wlinfo->phy = rtwvif_link->phy_idx; - wlinfo->pid = rtwvif_link->port; - wlinfo->active = true; - wlinfo->connected = MLME_LINKED; - wlinfo->bcn_period = bss_conf->beacon_int; - wlinfo->dtim_period = bss_conf->dtim_period; - wlinfo->band = chan->band_type; - wlinfo->ch = chan->channel; - wlinfo->bw = chan->band_width; - wlinfo->chdef.band = chan->band_type; - wlinfo->chdef.center_ch = chan->channel; - wlinfo->chdef.bw = chan->band_width; - wlinfo->chdef.chan = chan->primary_channel; - ether_addr_copy(r.mac_addr, rtwvif_link->mac_addr); + for (i = 0; i < RTW89_P2P_MAX_NOA_NUM; i++) { + noa_desc = &noa_attr->desc[i]; + if (noa_desc->count != 0) { + r.noa = 1; + r.noa_duration = le32_to_cpu(noa_desc->duration); + break; + } + } + } + + r.mode = mode; + r.role = rtwvif_link->wifi_role; + r.phy = rtwvif_link->phy_idx; + r.pid = rtwvif_link->port; + r.active = true; + if (vif->active_links) { + if (rtwvif_link->link_id < 16) + r.active = !!(vif->active_links & BIT(rtwvif_link->link_id)); + } + r.bcn_period = bss_conf->beacon_int; + r.dtim_period = bss_conf->dtim_period; + r.band = chan->band_type; + r.ch = chan->channel; + r.bw = chan->band_width; + r.chdef.band = chan->band_type; + r.chdef.center_ch = chan->channel; + r.chdef.bw = chan->band_width; + r.chdef.chan = chan->primary_channel; + + if (rtwsta_link && vif->type == NL80211_IFTYPE_STATION) { + r.mac_id = rtwsta_link->mac_id; + ether_addr_copy(r.mac_addr, rtwvif_link->mac_addr); + } + + switch (state) { + case BTC_ROLE_MSTS_STA_CONN_END: + case BTC_ROLE_MSTS_AP_START: + r.connected = MLME_LINKED; + break; + default: + r.connected = MLME_NO_LINK; + break; + } rcu_read_unlock(); - if (rtwsta_link && vif->type == NL80211_IFTYPE_STATION) - wlinfo->mac_id = rtwsta_link->mac_id; - btc->dm.cnt_notify[BTC_NCNT_ROLE_INFO]++; - if (wlinfo->role == RTW89_WIFI_ROLE_STATION && - wlinfo->connected == MLME_NO_LINK) + if (r.role == RTW89_WIFI_ROLE_STATION && + r.connected == MLME_NO_LINK) btc->dm.leak_ap = 0; if (state == BTC_ROLE_MSTS_STA_CONN_START) { @@ -8774,7 +8801,7 @@ void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev, state == BTC_ROLE_MSTS_STA_CONN_END) wl->status.map._4way = false; - _update_wl_info(rtwdev, wlinfo); + _update_wl_info(rtwdev, &r); _run_coex(rtwdev, BTC_RSN_NTFY_ROLE_INFO); } From a76501e1d148cc7052b1b6c5a00ea3e404dcae26 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 24 Jul 2026 21:56:31 +0800 Subject: [PATCH 0633/1433] wifi: rtw89: coex: Separate _ntfy_role_info into two function To make logic more clearly, separate _ntfy_role_info into two function by data collecting and using. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260724135640.3195044-6-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 109 ++++++++++++++++------ 1 file changed, 80 insertions(+), 29 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 9da3b23b2d14..ae458917ecf7 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -7233,6 +7233,7 @@ static void _update_wl_non_mlo_info(struct rtw89_dev *rtwdev) mode = _chk_dbcc(rtwdev, cid_ch, cid_phy, cid_role, cnt, notv10); mode_v0 = wl_rinfo->link_mode_v0; } else if (!b2g && b5g && notv10) { + mode = _get_role_link_mode(wl_rinfo, cid_role[0], notv10); mode_v0 = BTC_WLINK_V0_5G; } else if (b2g && b5g) { mode = BTC_WLINK_DB_MCC; @@ -7247,6 +7248,7 @@ static void _update_wl_non_mlo_info(struct rtw89_dev *rtwdev) } } else { mode = _get_role_link_mode(wl_rinfo, cid_role[0], notv10); + mode_v0 = wl_rinfo->link_mode_v0; } wl_rinfo->link_mode = mode; @@ -8667,6 +8669,78 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) _run_coex(rtwdev, BTC_RSN_UPDATE_BT_INFO); } +static void _ntfy_role_info(struct rtw89_dev *rtwdev, u8 rid, + struct rtw89_btc_wl_link_info *info, + enum btc_role_state reason) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_wl_link_info *wlinfo = NULL; + struct rtw89_btc_wl_info *wl = &btc->cx.wl; + const struct rtw89_btc_ver *ver = btc->ver; + u8 rlink_id = info->phy; /* 1 role_id has 2 rlink_id(by HW_Band0/1) */ + bool refresh_role = false; + + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(), role_id=%d, reason=%d\n", + __func__, rid, reason); + + if (rid >= ver->max_role_num || rlink_id >= RTW89_PHY_NUM) + return; + + btc->dm.cnt_notify[BTC_NCNT_ROLE_INFO]++; + + wlinfo = &wl->rlink_info[rid][rlink_id]; + memcpy(wlinfo, info, sizeof(struct rtw89_btc_wl_link_info)); + + switch (reason) { + case BTC_ROLE_START: + wlinfo->active = true; + return; + case BTC_ROLE_STOP: + wlinfo->active = false; + return; + case BTC_ROLE_MSTS_STA_CONN_START: + wl->status.map.transacting = 1; + wiphy_delayed_work_cancel(rtwdev->hw->wiphy, + &rtwdev->coex_act1_work); + wiphy_delayed_work_queue(rtwdev->hw->wiphy, + &rtwdev->coex_act1_work, + RTW89_COEX_ACT1_WORK_PERIOD); + break; + case BTC_ROLE_MSTS_STA_CONN_END: + wl->status.map.transacting = 0; + refresh_role = true; + break; + case BTC_ROLE_MSTS_STA_DIS_CONN: + wl->status.map.transacting = 1; + refresh_role = false; + wiphy_delayed_work_cancel(rtwdev->hw->wiphy, + &rtwdev->coex_act1_work); + wiphy_delayed_work_queue(rtwdev->hw->wiphy, + &rtwdev->coex_act1_work, + RTW89_COEX_ACT1_WORK_PERIOD); + break; + case BTC_ROLE_MSTS_AP_START: + case BTC_ROLE_MSTS_AP_STOP: + refresh_role = true; + break; + case BTC_ROLE_STATE_UNKNOWN: + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(), role_id=%d, Unknown reason return!\n", + __func__, rid); + return; + default: + return; + } + + if (refresh_role) { + _update_wl_info(rtwdev, wlinfo); + _fw_set_drv_info(rtwdev, CXDRVINFO_ROLE); + } + + _run_coex(rtwdev, BTC_RSN_NTFY_ROLE_INFO); +} + void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link, struct rtw89_sta_link *rtwsta_link, @@ -8679,20 +8753,20 @@ void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev, struct ieee80211_p2p_noa_desc *noa_desc; struct ieee80211_bss_conf *bss_conf; struct ieee80211_link_sta *link_sta; - struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_wl_link_info r = {0}; - u8 i, mode = 0; + u8 i, role_id, mode = 0; rcu_read_lock(); bss_conf = rtw89_vif_rcu_dereference_link(rtwvif_link, false); + role_id = rtwvif_link->port; + rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], state=%d\n", state); rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], role is STA=%d\n", vif->type == NL80211_IFTYPE_STATION); - rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], port=%d\n", rtwvif_link->port); + rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], port=%d\n", role_id); rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], band=%d ch=%d bw=%d\n", chan->band_type, chan->channel, chan->band_width); rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], associated=%d\n", @@ -8747,7 +8821,7 @@ void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev, r.mode = mode; r.role = rtwvif_link->wifi_role; r.phy = rtwvif_link->phy_idx; - r.pid = rtwvif_link->port; + r.pid = role_id; r.active = true; if (vif->active_links) { if (rtwvif_link->link_id < 16) @@ -8780,30 +8854,7 @@ void rtw89_btc_ntfy_role_info(struct rtw89_dev *rtwdev, rcu_read_unlock(); - btc->dm.cnt_notify[BTC_NCNT_ROLE_INFO]++; - - if (r.role == RTW89_WIFI_ROLE_STATION && - r.connected == MLME_NO_LINK) - btc->dm.leak_ap = 0; - - if (state == BTC_ROLE_MSTS_STA_CONN_START) { - wl->status.map.transacting = 1; - wiphy_delayed_work_cancel(rtwdev->hw->wiphy, &rtwdev->coex_act1_work); - wiphy_delayed_work_queue(rtwdev->hw->wiphy, - &rtwdev->coex_act1_work, - RTW89_COEX_ACT1_WORK_PERIOD); - } else { - wiphy_delayed_work_cancel(rtwdev->hw->wiphy, &rtwdev->coex_act1_work); - wl->status.map.transacting = 0; - } - - if (state == BTC_ROLE_MSTS_STA_DIS_CONN || - state == BTC_ROLE_MSTS_STA_CONN_END) - wl->status.map._4way = false; - - _update_wl_info(rtwdev, &r); - - _run_coex(rtwdev, BTC_RSN_NTFY_ROLE_INFO); + _ntfy_role_info(rtwdev, role_id, &r, state); } void rtw89_btc_ntfy_radio_state(struct rtw89_dev *rtwdev, enum btc_rfctrl rf_state) From b01bfd2fc752e598ba1acfd76c64152f646c4d72 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 24 Jul 2026 21:56:32 +0800 Subject: [PATCH 0634/1433] wifi: rtw89: coex: Decrease Wi-Fi special packet protect time While Wi-Fi is doing special packet handshake, or going into some transient state, BT-Coexistence will held timer to fix control logic to protect the segment. Set the protection duration to 1 second, it is enough to cover the situation. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260724135640.3195044-7-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.h b/drivers/net/wireless/realtek/rtw89/coex.h index 41a68ab21d80..355784941edd 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.h +++ b/drivers/net/wireless/realtek/rtw89/coex.h @@ -69,7 +69,7 @@ enum btc_wl_rfk_type { #define NM_EXEC false #define FC_EXEC true -#define RTW89_COEX_ACT1_WORK_PERIOD round_jiffies_relative(HZ * 4) +#define RTW89_COEX_ACT1_WORK_PERIOD round_jiffies_relative(HZ) #define RTW89_COEX_BT_DEVINFO_WORK_PERIOD round_jiffies_relative(HZ * 16) #define RTW89_COEX_RFK_CHK_WORK_PERIOD msecs_to_jiffies(300) #define BTC_RFK_PATH_MAP GENMASK(3, 0) From 5c90259b9748f3a75522cbd3da33f55b203752e2 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 24 Jul 2026 21:56:33 +0800 Subject: [PATCH 0635/1433] wifi: rtw89: coex: complete GPIO debug configuration handler Complete the implementation of _fw_set_gpio() function to support all GPIO control configuration types for coexistence. Included debug signal, antenna switch, external I2C mailbox, external PTA related GPIO configuration. This function is called during initialization and when BT re-enables. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260724135640.3195044-8-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 234 +++++++++++++++++++++- drivers/net/wireless/realtek/rtw89/coex.h | 35 ---- drivers/net/wireless/realtek/rtw89/core.h | 162 ++++++++++++++- drivers/net/wireless/realtek/rtw89/fw.h | 10 + 4 files changed, 392 insertions(+), 49 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index ae458917ecf7..96ec5fa20422 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -3206,6 +3206,212 @@ static void _fw_set_policy(struct rtw89_dev *rtwdev, u16 policy_type, rtw89_set_coex_ctrl_lps(rtwdev, btc->btc_ctrl_lps); } +static u8 _get_gpiosig_for_ver(struct rtw89_dev *rtwdev, u8 sig_v8) +{ + const struct rtw89_btc_ver *ver = rtwdev->btc.ver; + + /* v8: gpio_ver >= 8, directly use v8 signal ID */ + if (ver->fcxgpiodbg >= 8) + return sig_v8; + + /* v7: gpio_ver < 8, convert v8 signal ID to v7 signal ID */ + if (sig_v8 <= BTC_DBG_GNT_WL) { + return sig_v8; + } else if (sig_v8 <= BTC_DBG_GNT_WL1) { + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(): sig %d not supported in v7\n", + __func__, sig_v8); + return BTC_DBG_NUM; + } else if (sig_v8 >= BTC_DBG_NUM) { + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s(): sig %d not available\n", + __func__, sig_v8); + return BTC_DBG_NUM; + } else { + return sig_v8 - 2; + } +} + +static u32 _convert_gpio_enmap_to_ver(struct rtw89_dev *rtwdev, u32 en_map_v8) +{ + const struct rtw89_btc_ver *ver = rtwdev->btc.ver; + u32 en_map_v7 = 0; + u32 bit; + + /* v8: gpio_ver >= 8, directly use v8 en_map */ + if (ver->fcxgpiodbg >= 8) + return en_map_v8; + + /* v7: gpio_ver < 8, convert v8 en_map bitmap to v7 en_map bitmap */ + /* bit 0-1: GNT_BT, GNT_WL - same position in both versions */ + en_map_v7 |= en_map_v8 & GENMASK(1, 0); + + /* bit 2-3: GNT_BT1, GNT_WL1 - not supported in v7, skip these bits */ + + /* bit 4-31: shift right by 2 positions (become bit 2-29 in v7) */ + for (bit = BTC_DBG_BCN_EARLY; bit < BTC_DBG_NUM; bit++) { + if (en_map_v8 & BIT(bit)) + en_map_v7 |= BIT(bit - 2); + } + + return en_map_v7; +} + +static u32 _convert_gpio_enmap_from_ver(struct rtw89_dev *rtwdev, u32 en_map) +{ + const struct rtw89_btc_ver *ver = rtwdev->btc.ver; + u32 en_map_v8 = 0; + u32 bit; + + if (ver->fcxgpiodbg >= 8) + return en_map; + + /* bit 0-1: GNT_BT, GNT_WL - same position in both versions */ + en_map_v8 |= en_map & GENMASK(1, 0); + + /* bit 2-29: shift left by 2 positions (become bit 4-31 in v8) */ + for (bit = BTC_DBG_GNT_BT1; bit < BTC_DBG_NUM - 2; bit++) { + if (en_map & BIT(bit)) + en_map_v8 |= BIT(bit + 2); + } + + return en_map_v8; +} + +static void _fw_set_gpio(struct rtw89_dev *rtwdev, u8 type, u32 val) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_fbtc_h2c_set_gpio *gpio = &btc->gpio; + const struct rtw89_btc_ver *ver = btc->ver; + struct rtw89_fbtc_h2c_set_gpio_2b l2_h2c; + struct rtw89_fbtc_h2c_set_gpio_4b l4_h2c; + u8 gpio_ver = ver->fcxgpiodbg; + u8 buf[sizeof(l4_h2c)]; + u8 len; + + if (gpio_ver < 8 && type > CXDGPIO_MUX_MAP) + return; + + if (type >= CXDGPIO_MAX) + return; + + switch (type) { + case CXDGPIO_EN_MAP: /* GPIO debug signal en-map 0~31 */ + val = _convert_gpio_enmap_to_ver(rtwdev, val); + gpio->en_map.data.type = CXDGPIO_EN_MAP; + gpio->en_map.data.fver = gpio_ver; + gpio->en_map.data.dlen = CXDGPIO_SET_L4; + gpio->en_map.data.en_map = val; + l4_h2c = gpio->en_map.fmt; + put_unaligned_le32(val, (u32 *)l4_h2c.data); + memcpy(buf, &l4_h2c, sizeof(l4_h2c)); + len = sizeof(l4_h2c); + break; + case CXDGPIO_MUX_MAP: /* GPIO dbg: Signal to GPIO Mux */ + gpio->mux.data.type = CXDGPIO_MUX_MAP; + gpio->mux.data.fver = gpio_ver; + gpio->mux.data.dlen = CXDGPIO_SET_L2; + gpio->mux.data.sig = _get_gpiosig_for_ver(rtwdev, + FIELD_GET(GENMASK(7, 0), val)); + if (gpio->mux.data.sig == 0xff) + return; + gpio->mux.data.gpio = FIELD_GET(GENMASK(15, 8), val); + l2_h2c = gpio->mux.fmt; + memcpy(buf, &l2_h2c, sizeof(l2_h2c)); + len = sizeof(l2_h2c); + break; + case CXDGPIO_EXT_HPTA: /* GPIO config for Ext HW-PTA */ + gpio->ext_pta.data.type = CXDGPIO_EXT_HPTA; + gpio->ext_pta.data.fver = gpio_ver; + gpio->ext_pta.data.dlen = CXDGPIO_SET_L2; + gpio->ext_pta.data.map_low = FIELD_GET(GENMASK(7, 0), val); + gpio->ext_pta.data.map_high = FIELD_GET(GENMASK(15, 8), val); + l2_h2c = gpio->ext_pta.fmt; + memcpy(buf, &l2_h2c, sizeof(l2_h2c)); + len = sizeof(l2_h2c); + break; + case CXDGPIO_EXT_HMBX: /* GPIO config for Ext HW-mailbox */ + gpio->ext_mb.data.type = CXDGPIO_EXT_HMBX; + gpio->ext_mb.data.fver = gpio_ver; + gpio->ext_mb.data.dlen = CXDGPIO_SET_L2; + gpio->ext_mb.data.map_low = FIELD_GET(GENMASK(7, 0), val); + gpio->ext_mb.data.map_high = FIELD_GET(GENMASK(15, 8), val); + l2_h2c = gpio->ext_mb.fmt; + memcpy(buf, &l2_h2c, sizeof(l2_h2c)); + len = sizeof(l2_h2c); + break; + case CXDGPIO_EXT_SWOUT: /* GPIO config for Ext SW output (wlan_act) */ + gpio->ext_swout.data.type = CXDGPIO_EXT_SWOUT; + gpio->ext_swout.data.fver = gpio_ver; + gpio->ext_swout.data.dlen = CXDGPIO_SET_L2; + gpio->ext_swout.data.map_low = FIELD_GET(GENMASK(7, 0), val); + gpio->ext_swout.data.map_high = FIELD_GET(GENMASK(15, 8), val); + l2_h2c = gpio->ext_swout.fmt; + memcpy(buf, &l2_h2c, sizeof(l2_h2c)); + len = sizeof(l2_h2c); + break; + case CXDGPIO_EXT_SWIN: /* GPIO config for Ext SW input control */ + gpio->ext_swin.data.type = CXDGPIO_EXT_SWIN; + gpio->ext_swin.data.fver = gpio_ver; + gpio->ext_swin.data.dlen = CXDGPIO_SET_L4; + gpio->ext_swin.data.in_map_low = FIELD_GET(GENMASK(7, 0), val); + gpio->ext_swin.data.in_map_high = FIELD_GET(GENMASK(15, 8), val); + gpio->ext_swin.data.int_map_low = FIELD_GET(GENMASK(23, 16), val); + gpio->ext_swin.data.int_map_high = FIELD_GET(GENMASK(31, 24), val); + l4_h2c = gpio->ext_swin.fmt; + put_unaligned_le32(val, (u32 *)l4_h2c.data); + memcpy(buf, &l4_h2c, sizeof(l4_h2c)); + len = sizeof(l4_h2c); + break; + default: + return; + } + + _send_fw_cmd(rtwdev, BTFC_SET, SET_GPIO_DBG, buf, len); +} + +static void _set_ext_interface(struct rtw89_dev *rtwdev) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_cx *cx = &btc->cx; + u8 bt1_sw_type = BTC_SWITCH_INTERNAL; + u32 val; + + if (btc->ver->fcxgpiodbg < 7) + return; + + /* if BT1+WL-S0, route DBG_GNT_BT1 to control SPDT */ + if ((rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) && + btc->ver->fcxinit >= 10) { + if (btc->ver->fcxinit >= 10) + bt1_sw_type = btc->mdinfo.bt1_sw_type; + else + return; + + if (bt1_sw_type > BTC_SWITCH_INTERNAL) { + _fw_set_gpio(rtwdev, CXDGPIO_EN_MAP, + BIT(BTC_DBG_GNT_BT1)); + + val = (bt1_sw_type << 8) + BTC_DBG_GNT_BT1; + _fw_set_gpio(rtwdev, CXDGPIO_MUX_MAP, val); + } + } + + /* set GPIO as Ext-PTA interface wire */ + if (cx->bt_ext.hw_coex & BTC_EXTSOC_INTF_PTA) + _fw_set_gpio(rtwdev, CXDGPIO_EXT_HPTA, cx->bt_ext.hpta_cfg); + + /* set GPIO as Ext-mailbox interface wire */ + if (cx->bt_ext.hw_coex & BTC_EXTSOC_INTF_MBX) + _fw_set_gpio(rtwdev, CXDGPIO_EXT_HMBX, cx->bt_ext.hmbx_cfg); + + /* set GPIO as Ext-SWIO interface wire */ + if (cx->bt_ext.hw_coex & BTC_EXTSOC_INTF_SWIO) { + _fw_set_gpio(rtwdev, CXDGPIO_EXT_SWOUT, cx->bt_ext.swout_cfg); + _fw_set_gpio(rtwdev, CXDGPIO_EXT_SWIN, cx->bt_ext.swin_cfg); + } +} + static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index) { struct rtw89_btc *btc = &rtwdev->btc; @@ -8251,6 +8457,7 @@ static void _set_init_info(struct rtw89_dev *rtwdev) _fw_set_drv_info(rtwdev, CXDRVINFO_CTRL); rtw89_btc_fw_set_slots(rtwdev); btc_fw_set_monreg(rtwdev); + _set_ext_interface(rtwdev); _set_wl_tx_power(rtwdev, RTW89_BTC_WL_DEF_TX_PWR, RTW89_PHY_0); } @@ -9779,6 +9986,8 @@ static const char *id_to_gdbg(u32 id) switch (id) { CASE_BTC_GDBG_STR(GNT_BT); CASE_BTC_GDBG_STR(GNT_WL); + CASE_BTC_GDBG_STR(GNT_BT1); + CASE_BTC_GDBG_STR(GNT_WL1); CASE_BTC_GDBG_STR(BCN_EARLY); CASE_BTC_GDBG_STR(WL_NULL0); CASE_BTC_GDBG_STR(WL_NULL1); @@ -9807,8 +10016,6 @@ static const char *id_to_gdbg(u32 id) CASE_BTC_GDBG_STR(SLOT_B1FDD); CASE_BTC_GDBG_STR(BT_CHANGE); CASE_BTC_GDBG_STR(WL_CCA); - CASE_BTC_GDBG_STR(BT_LEAUDIO); - CASE_BTC_GDBG_STR(USER_DEF); default: return "unknown"; } @@ -11487,8 +11694,8 @@ static int _show_gpio_dbg(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) struct rtw89_btc_rpt_cmn_info *pcinfo = NULL; union rtw89_btc_fbtc_gpio_dbg *gdbg = NULL; char *p = buf, *end = buf + bufsz; - u8 *gpio_map, i; - u32 en_map; + u32 en_map_raw, en_map; + u8 *gpio_map, i, sig; pcinfo = &pfwinfo->rpt_fbtc_gpio_dbg.cinfo; gdbg = &rtwdev->btc.fwinfo.rpt_fbtc_gpio_dbg.finfo; @@ -11499,27 +11706,32 @@ static int _show_gpio_dbg(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) goto out; } - if (ver->fcxgpiodbg == 7) { - en_map = le32_to_cpu(gdbg->v7.en_map); + if (ver->fcxgpiodbg == 7 || ver->fcxgpiodbg == 8) { + en_map_raw = le32_to_cpu(gdbg->v7.en_map); gpio_map = gdbg->v7.gpio_map; } else { - en_map = le32_to_cpu(gdbg->v1.en_map); + en_map_raw = le32_to_cpu(gdbg->v1.en_map); gpio_map = gdbg->v1.gpio_map; } + en_map = _convert_gpio_enmap_from_ver(rtwdev, en_map_raw); if (!en_map) goto out; - p += scnprintf(p, end - p, " %-15s : enable_map:0x%08x", + p += scnprintf(p, end - p, "\n\r %-15s : enable_map:0x%08x", "[gpio_dbg]", en_map); - for (i = 0; i < BTC_DBG_MAX1; i++) { + for (i = 0; i < BTC_DBG_NUM; i++) { if (!(en_map & BIT(i))) continue; + + sig = ver->fcxgpiodbg >= 8 ? i : _get_gpiosig_for_ver(rtwdev, i); + if (sig >= BTC_DBG_NUM) + continue; + p += scnprintf(p, end - p, ", %s->GPIO%d", id_to_gdbg(i), - gpio_map[i]); + gpio_map[sig]); } - p += scnprintf(p, end - p, "\n"); out: return p - buf; diff --git a/drivers/net/wireless/realtek/rtw89/coex.h b/drivers/net/wireless/realtek/rtw89/coex.h index 355784941edd..3e9e1510c44f 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.h +++ b/drivers/net/wireless/realtek/rtw89/coex.h @@ -315,41 +315,6 @@ enum btc_mlo_rf_combin { BTC_MLO_RF_2_PLUS_2 = 3, }; -enum btc_wl_gpio_debug { - BTC_DBG_GNT_BT = 0, - BTC_DBG_GNT_WL = 1, - BTC_DBG_BCN_EARLY = 2, - BTC_DBG_WL_NULL0 = 3, - BTC_DBG_WL_NULL1 = 4, - BTC_DBG_WL_RXISR = 5, - BTC_DBG_TDMA_ENTRY = 6, - BTC_DBG_A2DP_EMPTY = 7, - BTC_DBG_BT_RETRY = 8, - BTC_DBG_BT_RELINK = 9, - BTC_DBG_SLOT_WL = 10, - BTC_DBG_SLOT_BT = 11, - BTC_DBG_WL_ERR = 12, - BTC_DBG_WL_OK = 13, - BTC_DBG_SLOT_B2W = 14, - BTC_DBG_SLOT_W1 = 15, - BTC_DBG_SLOT_W2 = 16, - BTC_DBG_SLOT_W2B = 17, - BTC_DBG_SLOT_B1 = 18, - BTC_DBG_SLOT_B2 = 19, - BTC_DBG_SLOT_B3 = 20, - BTC_DBG_SLOT_B4 = 21, - BTC_DBG_SLOT_LK = 22, - BTC_DBG_SLOT_E2G = 23, - BTC_DBG_SLOT_E5G = 24, - BTC_DBG_SLOT_EBT = 25, - BTC_DBG_SLOT_WLK = 26, - BTC_DBG_SLOT_B1FDD = 27, - BTC_DBG_BT_CHANGE = 28, - BTC_DBG_WL_CCA = 29, - BTC_DBG_BT_LEAUDIO = 30, - BTC_DBG_USER_DEF = 31, -}; - void rtw89_btc_init(struct rtw89_dev *rtwdev); void rtw89_btc_ntfy_poweron(struct rtw89_dev *rtwdev); void rtw89_btc_ntfy_poweroff(struct rtw89_dev *rtwdev); diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 0ec63f9510bd..ba3a9b0d4aa2 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -3074,14 +3074,55 @@ enum rtw89_btc_afh_map_type { /*AFH MAP TYPE */ RPT_BT_AFH_SEQ_LE = 0x20 }; -#define BTC_DBG_MAX1 32 +enum btc_wl_gpio_debug { + BTC_DBG_GNT_BT = 0, + BTC_DBG_GNT_WL = 1, + BTC_DBG_GNT_BT1 = 2, + BTC_DBG_GNT_WL1 = 3, + /* The following signals should 0-1 tiggle by each function-call */ + BTC_DBG_BCN_EARLY = 4, + BTC_DBG_WL_NULL0 = 5, + BTC_DBG_WL_NULL1 = 6, + BTC_DBG_WL_RXISR = 7, + BTC_DBG_TDMA_ENTRY = 8, + BTC_DBG_A2DP_EMPTY = 9, + BTC_DBG_BT_RETRY = 10, + /* The following signals should 0-1 tiggle by state L/H */ + BTC_DBG_BT_RELINK = 11, + BTC_DBG_SLOT_WL = 12, + BTC_DBG_SLOT_BT = 13, + /* The following signals should 0-1 tiggle by external*/ + BTC_DBG_WL_ERR = 14, + BTC_DBG_WL_OK = 15, + /* The following signals appear only 1-active at same time*/ + BTC_DBG_SLOT_B2W = 16, + BTC_DBG_SLOT_W1 = 17, + BTC_DBG_SLOT_W2 = 18, + BTC_DBG_SLOT_W2B = 19, + BTC_DBG_SLOT_B1 = 20, + BTC_DBG_SLOT_B2 = 21, + BTC_DBG_SLOT_B3 = 22, + BTC_DBG_SLOT_B4 = 23, + BTC_DBG_SLOT_LK = 24, + BTC_DBG_SLOT_E2G = 25, + BTC_DBG_SLOT_E5G = 26, + BTC_DBG_SLOT_EBT = 27, + BTC_DBG_SLOT_WLK = 28, + BTC_DBG_SLOT_B1FDD = 29, + BTC_DBG_BT_CHANGE = 30, + /* The following signals should 0-1 tiggle by external*/ + BTC_DBG_WL_CCA = 31, + + BTC_DBG_NUM, +}; + struct rtw89_btc_fbtc_gpio_dbg_v1 { u8 fver; /* btc_ver::fcxgpiodbg */ u8 rsvd; __le16 rsvd2; __le32 en_map; /* which debug signal (see btc_wl_gpio_debug) is enable */ __le32 pre_state; /* the debug signal is 1 or 0 */ - u8 gpio_map[BTC_DBG_MAX1]; /*the debug signals to GPIO-Position */ + u8 gpio_map[BTC_DBG_NUM]; /*the debug signals to GPIO-Position */ } __packed; struct rtw89_btc_fbtc_gpio_dbg_v7 { @@ -3090,7 +3131,7 @@ struct rtw89_btc_fbtc_gpio_dbg_v7 { u8 rsvd1; u8 rsvd2; - u8 gpio_map[BTC_DBG_MAX1]; + u8 gpio_map[BTC_DBG_NUM]; __le32 en_map; __le32 pre_state; @@ -3101,6 +3142,120 @@ union rtw89_btc_fbtc_gpio_dbg { struct rtw89_btc_fbtc_gpio_dbg_v7 v7; }; +/* + * SET_GPIO_CTRL payload (max len = 7 bytes) + * + * type = CXDGPIO_EN_MAP + * data.val[31:0] = debug signal enable map + * + * type = CXDGPIO_MUX_MAP + * data.mux.sig = debug signal id + * data.mux.gpio = GPIO id + * + * type = CXDGPIO_EXT_HPTA / CXDGPIO_EXT_HMBX / CXDGPIO_EXT_SWOUT + * data.map.map_low = GPIO 7~0 map + * data.map.map_high = GPIO 15~8 map + * + * type = CXDGPIO_EXT_SWIN + * data.swin.in_map_low = GPIO 7~0 input-en-map + * data.swin.in_map_high = GPIO 15~8 input-en-map + * data.swin.int_map_low = GPIO 7~0 interrupt source map + * data.swin.int_map_high = GPIO 15~8 interrupt source map + */ +#define CXDGPIO_SET_L4 4 +#define CXDGPIO_SET_L2 2 +struct rtw89_fbtc_h2c_set_gpio_en_map { + u8 type; /* gpio_type */ + u8 fver; /* FCX_VER_GPIODBG */ + u8 dlen; + u32 en_map; +}; + +struct rtw89_fbtc_h2c_set_gpio_mux { + u8 type; /* gpio_type */ + u8 fver; /* FCX_VER_GPIODBG */ + u8 dlen; + u8 sig; + u8 gpio; +}; + +struct rtw89_fbtc_h2c_set_gpio_ext_pta { + u8 type; /* gpio_type */ + u8 fver; /* FCX_VER_GPIODBG */ + u8 dlen; + u8 map_low; + u8 map_high; +}; + +struct rtw89_fbtc_h2c_set_gpio_ext_mb { + u8 type; /* gpio_type */ + u8 fver; /* FCX_VER_GPIODBG */ + u8 dlen; + u8 map_low; + u8 map_high; +}; + +struct rtw89_fbtc_h2c_set_gpio_ext_swout { + u8 type; /* gpio_type */ + u8 fver; /* FCX_VER_GPIODBG */ + u8 dlen; + u8 map_low; + u8 map_high; +}; + +struct rtw89_fbtc_h2c_set_gpio_ext_swin { + u8 type; /* gpio_type */ + u8 fver; /* FCX_VER_GPIODBG */ + u8 dlen; + u8 in_map_low; + u8 in_map_high; + u8 int_map_low; + u8 int_map_high; +}; + +struct rtw89_fbtc_h2c_set_gpio_2b { + u8 type; /* gpio_type */ + u8 fver; /* FCX_VER_GPIODBG */ + u8 dlen; + u8 data[CXDGPIO_SET_L2]; +} __packed; + +struct rtw89_fbtc_h2c_set_gpio_4b { + u8 type; /* gpio_type */ + u8 fver; /* FCX_VER_GPIODBG */ + u8 dlen; + u8 data[CXDGPIO_SET_L4]; +} __packed; + +union rtw89_fbtc_h2c_set_gpio_en_map_u { + struct rtw89_fbtc_h2c_set_gpio_4b fmt; + struct rtw89_fbtc_h2c_set_gpio_en_map data; +}; + +union rtw89_fbtc_h2c_set_gpio_mux_u { + struct rtw89_fbtc_h2c_set_gpio_2b fmt; + struct rtw89_fbtc_h2c_set_gpio_mux data; +}; + +union rtw89_fbtc_h2c_set_gpio_ext_pta_u { + struct rtw89_fbtc_h2c_set_gpio_2b fmt; + struct rtw89_fbtc_h2c_set_gpio_ext_pta data; +}; + +union rtw89_fbtc_h2c_set_gpio_ext_swin_u { + struct rtw89_fbtc_h2c_set_gpio_4b fmt; + struct rtw89_fbtc_h2c_set_gpio_ext_swin data; +}; + +struct rtw89_fbtc_h2c_set_gpio { + union rtw89_fbtc_h2c_set_gpio_en_map_u en_map; + union rtw89_fbtc_h2c_set_gpio_mux_u mux; + union rtw89_fbtc_h2c_set_gpio_ext_pta_u ext_pta; + union rtw89_fbtc_h2c_set_gpio_ext_pta_u ext_mb; + union rtw89_fbtc_h2c_set_gpio_ext_pta_u ext_swout; + union rtw89_fbtc_h2c_set_gpio_ext_swin_u ext_swin; +}; + struct rtw89_btc_fbtc_mreg_val_v1 { u8 fver; /* btc_ver::fcxmreg */ u8 reg_num; @@ -4178,6 +4333,7 @@ struct rtw89_btc { struct rtw89_btc_module mdinfo; struct rtw89_btc_btf_fwinfo fwinfo; struct rtw89_btc_dbg dbg; + struct rtw89_fbtc_h2c_set_gpio gpio; struct wiphy_work eapol_notify_work; struct wiphy_work arp_notify_work; diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index 538df0fb42cc..6147960521ee 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -2391,6 +2391,16 @@ enum rtw89_btc_cxdrvinfo { CXDRVINFO_MAX, }; +enum rtw89_fbtc_gpio_type { + CXDGPIO_EN_MAP = 0x0, + CXDGPIO_MUX_MAP = 0x1, + CXDGPIO_EXT_HPTA = 0x2, + CXDGPIO_EXT_HMBX = 0x3, + CXDGPIO_EXT_SWOUT = 0x4, + CXDGPIO_EXT_SWIN = 0x5, + CXDGPIO_MAX, +}; + enum rtw89_scan_mode { RTW89_SCAN_IMMEDIATE, RTW89_SCAN_DELAY, From 528e5e1d78253136ea1b6f400f09db956476dba3 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 24 Jul 2026 21:56:34 +0800 Subject: [PATCH 0636/1433] wifi: rtw89: coex: Refine RF calibration notify flow BT-coexistence only needs to record RF calibration is doing or not, don't need to record the status of the calibration steps. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260724135640.3195044-9-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 96ec5fa20422..187e7e70e86d 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -9144,14 +9144,13 @@ static bool _ntfy_wl_rfk(struct rtw89_dev *rtwdev, u8 phy_path, wl->rfk_info.state = BTC_WRFK_START; btc->cx.wl.wcnt[BTC_WCNT_RFK_REQ]++; - btc->cx.wl.wcnt[BTC_WCNT_RFK_GO]++; btc->dm.cnt_notify[BTC_NCNT_WL_RFK]++; _write_scbd(rtwdev, BTC_ALL_BT, BTC_WSCB_WLRFK, true); break; case BTC_WRFK_ONESHOT_START: case BTC_WRFK_ONESHOT_STOP: - wl->rfk_info.state = state; + btc->cx.wl.wcnt[BTC_WCNT_RFK_GO]++; if (type != BTC_WRFKT_RXDCK) return BTC_WRFK_ALLOW; break; @@ -9165,7 +9164,7 @@ static bool _ntfy_wl_rfk(struct rtw89_dev *rtwdev, u8 phy_path, default: rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s() warning state=%d\n", __func__, state); - break; + return result; } if (result == BTC_WRFK_ALLOW) { From b23378a5645417720dc54bfc94603be91af4f4e5 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 24 Jul 2026 21:56:35 +0800 Subject: [PATCH 0637/1433] wifi: rtw89: coex: Fix log dump format with proper newline Fix the log output format in _show_mreg_v7() where the phy-0 gnt_status line was missing the proper field label and newline. Use the standard " %-15s : " format with "[gnt_status]" label consistent with the rest of the dump output. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260724135640.3195044-10-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 167 ++++++++++++---------- 1 file changed, 88 insertions(+), 79 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 187e7e70e86d..5aa06ef430c8 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -9656,8 +9656,10 @@ static int _show_wl_role_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) plink->client_cnt - 1, plink->mode, plink->ch, plink->bw); - if (plink->connected == MLME_NO_LINK) + if (plink->connected == MLME_NO_LINK) { + p += scnprintf(p, end - p, "\n"); continue; + } p += scnprintf(p, end - p, ", mac_id=%d, max_tx_time=%dus, max_tx_retry=%d\n", @@ -9747,7 +9749,7 @@ static int _show_bt_profile_info(struct rtw89_dev *rtwdev, char *buf, size_t buf if (hid.exist) { p += scnprintf(p, end - p, - "\n\r %-15s : type:%s%s%s%s%s pair-cnt:%d, sut_pwr:%d, golden-rx:%d\n", + "\n %-15s : type:%s%s%s%s%s pair-cnt:%d, sut_pwr:%d, golden-rx:%d\n", "[HID]", hid.type & BTC_HID_218 ? "2/18," : "", hid.type & BTC_HID_418 ? "4/18," : "", @@ -10345,7 +10347,7 @@ static int _show_dm_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) "" : "(Mismatch!!)")); p += scnprintf(p, end - p, - " %-15s : wl[rssi_lvl:%d/para:%d/tx_pwr:[%d %d]/rx_lvl:[%d %d]/lna2:%d/stb_chg:%d]\n ", + " %-15s : wl[rssi_lvl:%d/para:%d/tx_pwr:[%d %d]/rx_lvl:[%d %d]/lna2:%d/stb_chg:%d]\n", "[dm_rf_ctrl]", wl->rssi_level, dm->trx_para_level, dm->rf_trx_para.wl_tx_power[RTW89_PHY_0], @@ -10355,7 +10357,7 @@ static int _show_dm_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) dm->wl_lna2, dm->wl_stb_chg); p += scnprintf(p, end - p, - " %-15s : pre_agc:%d, btg_rx:%d\n ", + " %-15s : pre_agc:%d, btg_rx:%d\n", "[dm_bb_ctrl]", dm->wl_pre_agc, dm->wl_btg_rx); p += scnprintf(p, end - p, @@ -10467,11 +10469,9 @@ static int _show_fbtc_tdma(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) t->bind, t->leak_n, t->ext_ctrl); p += scnprintf(p, end - p, - "policy_type:%d", + "policy_type:%d\n", (u32)btc->policy_type); - p += scnprintf(p, end - p, "\n"); - return p - buf; } @@ -10778,11 +10778,9 @@ static int _show_fbtc_cysta_v3(struct rtw89_dev *rtwdev, char *buf, size_t bufsz le16_to_cpu(pcysta->a2dp_ept.cnt), le16_to_cpu(pcysta->a2dp_ept.cnt_timeout)); - p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d", + p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d\n", le16_to_cpu(pcysta->a2dp_ept.tavg), le16_to_cpu(pcysta->a2dp_ept.tmax)); - - p += scnprintf(p, end - p, "\n"); } out: @@ -10917,11 +10915,9 @@ static int _show_fbtc_cysta_v4(struct rtw89_dev *rtwdev, char *buf, size_t bufsz le16_to_cpu(pcysta->a2dp_ept.cnt), le16_to_cpu(pcysta->a2dp_ept.cnt_timeout)); - p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d", + p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d\n", le16_to_cpu(pcysta->a2dp_ept.tavg), le16_to_cpu(pcysta->a2dp_ept.tmax)); - - p += scnprintf(p, end - p, "\n"); } out: @@ -11055,11 +11051,9 @@ static int _show_fbtc_cysta_v5(struct rtw89_dev *rtwdev, char *buf, size_t bufsz le16_to_cpu(pcysta->a2dp_ept.cnt), le16_to_cpu(pcysta->a2dp_ept.cnt_timeout)); - p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d", + p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d\n", le16_to_cpu(pcysta->a2dp_ept.tavg), le16_to_cpu(pcysta->a2dp_ept.tmax)); - - p += scnprintf(p, end - p, "\n"); } out: @@ -11193,11 +11187,9 @@ static int _show_fbtc_cysta_v105(struct rtw89_dev *rtwdev, char *buf, size_t buf le16_to_cpu(pcysta->a2dp_ept.cnt), le16_to_cpu(pcysta->a2dp_ept.cnt_timeout)); - p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d", + p += scnprintf(p, end - p, ", avg_t:%d, max_t:%d\n", le16_to_cpu(pcysta->a2dp_ept.tavg), le16_to_cpu(pcysta->a2dp_ept.tmax)); - - p += scnprintf(p, end - p, "\n"); } out: @@ -11246,7 +11238,7 @@ static int _show_fbtc_cysta_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz le16_to_cpu(pcysta->skip_cnt)); p += scnprintf(p, end - p, - "\n\r %-15s : avg_t[wl:%d/bt:%d/lk:%d.%03d]", + "\n %-15s : avg_t[wl:%d/bt:%d/lk:%d.%03d]", "[cycle_stat]", le16_to_cpu(pcysta->cycle_time.tavg[CXT_WL]), le16_to_cpu(pcysta->cycle_time.tavg[CXT_BT]), @@ -11267,7 +11259,7 @@ static int _show_fbtc_cysta_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz if (a2dp->exist) { p += scnprintf(p, end - p, - "\n\r %-15s : a2dp_ept:%d, a2dp_late:%d(streak 2S:%d/max:%d)", + "\n %-15s : a2dp_ept:%d, a2dp_late:%d(streak 2S:%d/max:%d)", "[a2dp_stat]", le16_to_cpu(pcysta->a2dp_ept.cnt), le16_to_cpu(pcysta->a2dp_ept.cnt_timeout), @@ -11306,10 +11298,10 @@ static int _show_fbtc_cysta_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz if (cnt % divide_cnt == 1) { if (a2dp->exist) - p += scnprintf(p, end - p, "\n\r %-15s : ", + p += scnprintf(p, end - p, "\n %-15s : ", "[slotT_wermtan]"); else - p += scnprintf(p, end - p, "\n\r %-15s : ", + p += scnprintf(p, end - p, "\n %-15s : ", "[slotT_rxerr]"); } @@ -11370,7 +11362,7 @@ static int _show_fbtc_nullsta(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) ns = &pfwinfo->rpt_fbtc_nullsta.finfo; if (ver->fcxnullsta == 1) { for (i = 0; i < 2; i++) { - p += scnprintf(p, end - p, " %-15s : ", "\n[NULL-STA]"); + p += scnprintf(p, end - p, "\n %-15s : ", "[NULL-STA]"); p += scnprintf(p, end - p, "null-%d", i); p += scnprintf(p, end - p, "[ok:%d/", le32_to_cpu(ns->v1.result[i][1])); @@ -11389,7 +11381,7 @@ static int _show_fbtc_nullsta(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) } } else if (ver->fcxnullsta == 7) { for (i = 0; i < 2; i++) { - p += scnprintf(p, end - p, " %-15s : ", "\n[NULL-STA]"); + p += scnprintf(p, end - p, "\n %-15s : ", "[NULL-STA]"); p += scnprintf(p, end - p, "null-%d", i); p += scnprintf(p, end - p, "[Tx:%d/", le32_to_cpu(ns->v7.result[i][4])); @@ -11410,7 +11402,7 @@ static int _show_fbtc_nullsta(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) } } else { for (i = 0; i < 2; i++) { - p += scnprintf(p, end - p, " %-15s : ", "\n[NULL-STA]"); + p += scnprintf(p, end - p, "\n %-15s : ", "[NULL-STA]"); p += scnprintf(p, end - p, "null-%d", i); p += scnprintf(p, end - p, "[Tx:%d/", le32_to_cpu(ns->v2.result[i][4])); @@ -11717,7 +11709,7 @@ static int _show_gpio_dbg(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) if (!en_map) goto out; - p += scnprintf(p, end - p, "\n\r %-15s : enable_map:0x%08x", + p += scnprintf(p, end - p, "\n %-15s : enable_map:0x%08x", "[gpio_dbg]", en_map); for (i = 0; i < BTC_DBG_NUM; i++) { @@ -11943,7 +11935,7 @@ static int _show_mreg_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) if (!(dm->coex_info_map & BTC_COEX_INFO_MREG)) return 0; - p += scnprintf(p, end - p, "\n\r========== [HW Status] =========="); + p += scnprintf(p, end - p, "\n========== [HW Status] ==========\n"); p += scnprintf(p, end - p, " %-15s : WL->BT0:0x%08x(cnt:%d), BT0->WL:0x%08x(total:%d, bt_update:%d)\n", @@ -11965,18 +11957,31 @@ static int _show_mreg_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) dm->pta_owner = rtw89_mac_get_ctrl_path(rtwdev); p += scnprintf(p, end - p, - "\n\r %-15s : pta_owner:%s, pta_req_mac:MAC%d, rf_gnt_source: polut_type:%s", + " %-15s : pta_owner:%s, pta_req_mac:MAC%d, rf_gnt_source: polut_type:%s\n", "[gnt_status]", rtwdev->chip->para_ver & BTC_FEAT_PTA_ONOFF_CTRL ? "HW" : dm->pta_owner == BTC_CTRL_BY_WL ? "WL" : "BT", wl->pta_req_mac, id_to_polut(wl->bt_polut_type[wl->pta_req_mac])); - p += scnprintf(p, end - p, ", phy-0[gnt_wl:%s-%d/gnt_bt:%s-%d]", - dm->gnt_set[RTW89_PHY_0].gnt_wl_sw_en ? "SW" : "HW", - dm->gnt_set[RTW89_PHY_0].gnt_wl, - dm->gnt_set[RTW89_PHY_0].gnt_bt0_sw_en ? "SW" : "HW", - dm->gnt_set[RTW89_PHY_0].gnt_bt0); + if (rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) + p += scnprintf(p, end - p, + " %-15s : phy-0[gnt_wl:%s-%d/gnt_bt0:%s-%d/gnt_bt1:%s-%d]", + "[gnt_status]", + dm->gnt_set[RTW89_PHY_0].gnt_wl_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_0].gnt_wl, + dm->gnt_set[RTW89_PHY_0].gnt_bt0_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_0].gnt_bt0, + dm->gnt_set[RTW89_PHY_0].gnt_bt1_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_0].gnt_bt1); + else + p += scnprintf(p, end - p, + " %-15s : phy-0[gnt_wl:%s-%d/gnt_bt:%s-%d]", + "[gnt_status]", + dm->gnt_set[RTW89_PHY_0].gnt_wl_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_0].gnt_wl, + dm->gnt_set[RTW89_PHY_0].gnt_bt0_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_0].gnt_bt0); if (rtwdev->dbcc_en) { p += scnprintf(p, end - p, @@ -12000,7 +12005,7 @@ static int _show_mreg_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) if (cnt % 6 == 0) p += scnprintf(p, end - p, - "\n\r %-15s : %s_0x%x=0x%x", "[reg]", + "\n %-15s : %s_0x%x=0x%x", "[reg]", id_to_regtype(type), offset, val); else p += scnprintf(p, end - p, ", %s_0x%x=0x%x", @@ -12336,12 +12341,11 @@ static int _show_summary_v5(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) cnt[BTC_NCNT_POWER_ON], cnt[BTC_NCNT_INIT_COEX]); p += scnprintf(p, end - p, - "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d", + "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d\n", cnt[BTC_NCNT_POWER_OFF], cnt[BTC_NCNT_RADIO_STATE], cnt[BTC_NCNT_ROLE_INFO], cnt[BTC_NCNT_WL_RFK], cnt[BTC_NCNT_WL_STA]); - p += scnprintf(p, end - p, "\n"); p += scnprintf(p, end - p, " %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, special_pkt=%d, ", "[notify_cnt]", @@ -12457,12 +12461,11 @@ static int _show_summary_v105(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) cnt[BTC_NCNT_POWER_ON], cnt[BTC_NCNT_INIT_COEX]); p += scnprintf(p, end - p, - "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d", + "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d\n", cnt[BTC_NCNT_POWER_OFF], cnt[BTC_NCNT_RADIO_STATE], cnt[BTC_NCNT_ROLE_INFO], cnt[BTC_NCNT_WL_RFK], cnt[BTC_NCNT_WL_STA]); - p += scnprintf(p, end - p, "\n"); p += scnprintf(p, end - p, " %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, special_pkt=%d, ", "[notify_cnt]", @@ -12495,7 +12498,7 @@ static int _show_summary_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) return 0; p += scnprintf(p, end - p, "%s", - "\n\r========== [Statistics] =========="); + "\n========== [Statistics] ==========\n"); pcinfo = &pfwinfo->rpt_ctrl.cinfo; if (pcinfo->valid && wl->status.map.lps != BTC_LPS_RF_OFF && @@ -12503,7 +12506,7 @@ static int _show_summary_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) prptctrl = &pfwinfo->rpt_ctrl.finfo.v7; p += scnprintf(p, end - p, - "\n\r %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d)," + " %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d)," "c2h_cnt=%d(fw_send:%d, len:%d, max:%d), ", "[summary]", pfwinfo->cnt_h2c, pfwinfo->cnt_h2c_fail, @@ -12521,16 +12524,17 @@ static int _show_summary_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) if (dm->error.map.wl_fw_hang) p += scnprintf(p, end - p, " (WL FW Hang!!)"); + p += scnprintf(p, end - p, "\n"); p += scnprintf(p, end - p, - "\n\r %-15s : send_ok:%d, send_fail:%d, recv:%d, ", + " %-15s : send_ok:%d, send_fail:%d, recv:%d, ", "[mailbox]", le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_ok), le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_fail), le32_to_cpu(prptctrl->bt_mbx_info.cnt_recv)); p += scnprintf(p, end - p, - "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)", + "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)\n", le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_empty), le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_flowctrl), le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_tx), @@ -12538,7 +12542,7 @@ static int _show_summary_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_nack)); p += scnprintf(p, end - p, - "\n\r %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]", + " %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]", "[RFK/LPS]", wl->wcnt[BTC_WCNT_RFK_REQ], wl->wcnt[BTC_WCNT_RFK_GO], wl->wcnt[BTC_WCNT_RFK_REJECT], @@ -12548,12 +12552,12 @@ static int _show_summary_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, ", bt_rfk[req:%d]", le16_to_cpu(prptctrl->bt_cnt[BTC_BCNT_RFK_REQ])); - p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]", + p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]\n", le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_on), le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_off)); } else { p += scnprintf(p, end - p, - "\n\r %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)", + " %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)\n", "[summary]", pfwinfo->cnt_h2c, pfwinfo->cnt_h2c_fail, pfwinfo->cnt_c2h, @@ -12564,19 +12568,19 @@ static int _show_summary_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) cnt_sum += dm->cnt_notify[i]; p += scnprintf(p, end - p, - "\n\r %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ", + " %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ", "[notify_cnt]", cnt_sum, cnt[BTC_NCNT_SHOW_COEX_INFO], cnt[BTC_NCNT_POWER_ON], cnt[BTC_NCNT_INIT_COEX]); p += scnprintf(p, end - p, - "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d", + "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d\n", cnt[BTC_NCNT_POWER_OFF], cnt[BTC_NCNT_RADIO_STATE], cnt[BTC_NCNT_ROLE_INFO], cnt[BTC_NCNT_WL_RFK], cnt[BTC_NCNT_WL_STA]); p += scnprintf(p, end - p, - "\n\r %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ", + " %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ", "[notify_cnt]", cnt[BTC_NCNT_SCAN_START], cnt[BTC_NCNT_SCAN_FINISH], cnt[BTC_NCNT_SWITCH_BAND], cnt[BTC_NCNT_SWITCH_CHBW], @@ -12608,7 +12612,7 @@ static int _show_summary_v8(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) return 0; p += scnprintf(p, end - p, "%s", - "\n\r========== [Statistics] =========="); + "\n========== [Statistics] ==========\n"); pcinfo = &pfwinfo->rpt_ctrl.cinfo; if (pcinfo->valid && wl->status.map.lps != BTC_LPS_RF_OFF && @@ -12616,7 +12620,7 @@ static int _show_summary_v8(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) prptctrl = &pfwinfo->rpt_ctrl.finfo.v8; p += scnprintf(p, end - p, - "\n\r %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d), c2h_cnt=%d(fw_send:%d, len:%d, max:fw-%d/drv-%d), ", + " %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d), c2h_cnt=%d(fw_send:%d, len:%d, max:fw-%d/drv-%d), ", "[summary]", pfwinfo->cnt_h2c, pfwinfo->cnt_h2c_fail, le16_to_cpu(prptctrl->rpt_info.cnt_h2c), @@ -12634,16 +12638,17 @@ static int _show_summary_v8(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) if (dm->error.map.wl_fw_hang) p += scnprintf(p, end - p, " (WL FW Hang!!)"); + p += scnprintf(p, end - p, "\n"); p += scnprintf(p, end - p, - "\n\r %-15s : send_ok:%d, send_fail:%d, recv:%d, ", + " %-15s : send_ok:%d, send_fail:%d, recv:%d, ", "[mailbox]", le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_ok), le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_fail), le32_to_cpu(prptctrl->bt_mbx_info.cnt_recv)); p += scnprintf(p, end - p, - "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)", + "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)\n", le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_empty), le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_flowctrl), le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_tx), @@ -12651,7 +12656,7 @@ static int _show_summary_v8(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_nack)); p += scnprintf(p, end - p, - "\n\r %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]", + " %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]", "[RFK/LPS]", wl->wcnt[BTC_WCNT_RFK_REQ], wl->wcnt[BTC_WCNT_RFK_GO], wl->wcnt[BTC_WCNT_RFK_REJECT], @@ -12661,12 +12666,12 @@ static int _show_summary_v8(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, ", bt_rfk[req:%d]", le16_to_cpu(prptctrl->bt_cnt[BTC_BCNT_RFK_REQ])); - p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]", + p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]\n", le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_on), le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_off)); } else { p += scnprintf(p, end - p, - "\n\r %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)", + " %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)\n", "[summary]", pfwinfo->cnt_h2c, pfwinfo->cnt_h2c_fail, pfwinfo->cnt_c2h, @@ -12677,19 +12682,19 @@ static int _show_summary_v8(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) cnt_sum += dm->cnt_notify[i]; p += scnprintf(p, end - p, - "\n\r %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ", + " %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ", "[notify_cnt]", cnt_sum, cnt[BTC_NCNT_SHOW_COEX_INFO], cnt[BTC_NCNT_POWER_ON], cnt[BTC_NCNT_INIT_COEX]); p += scnprintf(p, end - p, - "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d", + "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d\n", cnt[BTC_NCNT_POWER_OFF], cnt[BTC_NCNT_RADIO_STATE], cnt[BTC_NCNT_ROLE_INFO], cnt[BTC_NCNT_WL_RFK], cnt[BTC_NCNT_WL_STA]); p += scnprintf(p, end - p, - "\n\r %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ", + " %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ", "[notify_cnt]", cnt[BTC_NCNT_SCAN_START], cnt[BTC_NCNT_SCAN_FINISH], cnt[BTC_NCNT_SWITCH_BAND], cnt[BTC_NCNT_SWITCH_CHBW], @@ -12721,7 +12726,7 @@ static int _show_summary_v9(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) return 0; p += scnprintf(p, end - p, "%s", - "\n\r========== [Statistics] =========="); + "\n========== [Statistics] ==========\n"); pcinfo = &pfwinfo->rpt_ctrl.cinfo; if (pcinfo->valid && wl->status.map.lps != BTC_LPS_RF_OFF && @@ -12729,7 +12734,7 @@ static int _show_summary_v9(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) prptctrl = &pfwinfo->rpt_ctrl.finfo.v9; p += scnprintf(p, end - p, - "\n\r %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d), c2h_cnt=%d(fw_send:%d, len:%d, ", + " %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d), c2h_cnt=%d(fw_send:%d, len:%d, ", "[summary]", pfwinfo->cnt_h2c, pfwinfo->cnt_h2c_fail, le16_to_cpu(prptctrl->rpt_info.cnt_h2c), @@ -12745,16 +12750,17 @@ static int _show_summary_v9(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) if (dm->error.map.wl_fw_hang) p += scnprintf(p, end - p, " (WL FW Hang!!)"); + p += scnprintf(p, end - p, "\n"); p += scnprintf(p, end - p, - "\n\r %-15s : send_ok:%d, send_fail:%d, recv:%d, ", + " %-15s : send_ok:%d, send_fail:%d, recv:%d, ", "[mailbox]", le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_ok), le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_fail), le32_to_cpu(prptctrl->bt_mbx_info.cnt_recv)); p += scnprintf(p, end - p, - "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)", + "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)\n", le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_empty), le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_flowctrl), le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_tx), @@ -12762,7 +12768,7 @@ static int _show_summary_v9(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_nack)); p += scnprintf(p, end - p, - "\n\r %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]", + " %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]", "[RFK/LPS]", wl->wcnt[BTC_WCNT_RFK_REQ], wl->wcnt[BTC_WCNT_RFK_GO], wl->wcnt[BTC_WCNT_RFK_REJECT], @@ -12772,12 +12778,12 @@ static int _show_summary_v9(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += scnprintf(p, end - p, ", bt_rfk[req:%d]", le16_to_cpu(prptctrl->bt_cnt[BTC_BCNT_RFK_REQ])); - p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]", + p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]\n", le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_on), le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_off)); } else { p += scnprintf(p, end - p, - "\n\r %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)", + " %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)\n", "[summary]", pfwinfo->cnt_h2c, pfwinfo->cnt_h2c_fail, pfwinfo->cnt_c2h, @@ -12788,19 +12794,19 @@ static int _show_summary_v9(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) cnt_sum += dm->cnt_notify[i]; p += scnprintf(p, end - p, - "\n\r %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ", + " %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ", "[notify_cnt]", cnt_sum, cnt[BTC_NCNT_SHOW_COEX_INFO], cnt[BTC_NCNT_POWER_ON], cnt[BTC_NCNT_INIT_COEX]); p += scnprintf(p, end - p, - "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d", + "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d\n", cnt[BTC_NCNT_POWER_OFF], cnt[BTC_NCNT_RADIO_STATE], cnt[BTC_NCNT_ROLE_INFO], cnt[BTC_NCNT_WL_RFK], cnt[BTC_NCNT_WL_STA]); p += scnprintf(p, end - p, - "\n\r %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ", + " %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ", "[notify_cnt]", cnt[BTC_NCNT_SCAN_START], cnt[BTC_NCNT_SCAN_FINISH], cnt[BTC_NCNT_SWITCH_BAND], cnt[BTC_NCNT_SWITCH_CHBW], @@ -12832,7 +12838,7 @@ static int _show_summary_v11(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) return 0; p += scnprintf(p, end - p, "%s", - "\n\r========== [Statistics] =========="); + "\n========== [Statistics] ==========\n"); pcinfo = &pfwinfo->rpt_ctrl.cinfo; if (pcinfo->valid && wl->status.map.lps != BTC_LPS_RF_OFF && @@ -12840,7 +12846,7 @@ static int _show_summary_v11(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) prptctrl = &pfwinfo->rpt_ctrl.finfo.v11; p += scnprintf(p, end - p, - "\n\r %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d), c2h_cnt=%d(fw_send:%d, len:%d, max:fw-%d/drv-%d), ", + " %-15s : h2c_cnt=%d(fail:%d, fw_recv:%d), c2h_cnt=%d(fw_send:%d, len:%d, max:fw-%d/drv-%d), ", "[summary]", pfwinfo->cnt_h2c, pfwinfo->cnt_h2c_fail, le16_to_cpu(prptctrl->rpt_info.cnt_h2c), @@ -12858,16 +12864,17 @@ static int _show_summary_v11(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) if (dm->error.map.wl_fw_hang) p += scnprintf(p, end - p, " (WL FW Hang!!)"); + p += scnprintf(p, end - p, "\n"); p += scnprintf(p, end - p, - "\n\r %-15s : send_ok:%d, send_fail:%d, recv:%d, ", + " %-15s : send_ok:%d, send_fail:%d, recv:%d, ", "[mailbox]", le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_ok), le32_to_cpu(prptctrl->bt_mbx_info.cnt_send_fail), le32_to_cpu(prptctrl->bt_mbx_info.cnt_recv)); p += scnprintf(p, end - p, - "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)", + "A2DP_empty:%d(stop:%d/tx:%d/ack:%d/nack:%d)\n", le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_empty), le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_flowctrl), le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_tx), @@ -12875,19 +12882,19 @@ static int _show_summary_v11(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) le32_to_cpu(prptctrl->bt_mbx_info.a2dp.cnt_nack)); p += scnprintf(p, end - p, - "\n\r %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]", + " %-15s : wl_rfk[req:%d/go:%d/reject:%d/tout:%d/time:%dms]", "[RFK/LPS]", wl->wcnt[BTC_WCNT_RFK_REQ], wl->wcnt[BTC_WCNT_RFK_GO], wl->wcnt[BTC_WCNT_RFK_REJECT], wl->wcnt[BTC_WCNT_RFK_TIMEOUT], wl->rfk_info.proc_time); - p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]", + p += scnprintf(p, end - p, ", AOAC[RF_on:%d/RF_off:%d]\n", le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_on), le16_to_cpu(prptctrl->rpt_info.cnt_aoac_rf_off)); } else { p += scnprintf(p, end - p, - "\n\r %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)", + " %-15s : h2c_cnt=%d(fail:%d), c2h_cnt=%d (lps=%d/rf_off=%d)\n", "[summary]", pfwinfo->cnt_h2c, pfwinfo->cnt_h2c_fail, pfwinfo->cnt_c2h, @@ -12898,19 +12905,19 @@ static int _show_summary_v11(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) cnt_sum += dm->cnt_notify[i]; p += scnprintf(p, end - p, - "\n\r %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ", + " %-15s : total=%d, show_coex_info=%d, power_on=%d, init_coex=%d, ", "[notify_cnt]", cnt_sum, cnt[BTC_NCNT_SHOW_COEX_INFO], cnt[BTC_NCNT_POWER_ON], cnt[BTC_NCNT_INIT_COEX]); p += scnprintf(p, end - p, - "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d", + "power_off=%d, radio_state=%d, role_info=%d, wl_rfk=%d, wl_sta=%d\n", cnt[BTC_NCNT_POWER_OFF], cnt[BTC_NCNT_RADIO_STATE], cnt[BTC_NCNT_ROLE_INFO], cnt[BTC_NCNT_WL_RFK], cnt[BTC_NCNT_WL_STA]); p += scnprintf(p, end - p, - "\n\r %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ", + " %-15s : scan_start=%d, scan_finish=%d, switch_band=%d, switch_chbw=%d, special_pkt=%d, ", "[notify_cnt]", cnt[BTC_NCNT_SCAN_START], cnt[BTC_NCNT_SCAN_FINISH], cnt[BTC_NCNT_SWITCH_BAND], cnt[BTC_NCNT_SWITCH_CHBW], @@ -12993,6 +13000,8 @@ ssize_t rtw89_btc_dump_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) else if (ver->fcxbtcrpt == 11) p += _show_summary_v11(rtwdev, p, end - p); + p += scnprintf(p, end - p, "\n"); + return p - buf; } From 068daf7086fccf4f4297609cd0ec792460b4a3f2 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 24 Jul 2026 21:56:36 +0800 Subject: [PATCH 0638/1433] wifi: rtw89: coex: Add BTC report version 8 support for BT sub-reports RTL8922A (FW >= 0.35.111) and RTL8922D (FW >= 0.35.94) set fcxbtver, fcxbtscan and fcxbtafh to 8, but the handler in _chk_btc_report only had branches for version 1 and 7. When version 8 arrived pfinfo was left NULL and pcinfo->req_len was left at zero, so the length check at validation stage rejected the report and bt->ver_info.fw was never written, causing BT_FW:0x0 in the BTC dump. BT-scan and BT-afh version 8 hit the goto err path for the same reason, making all BT sub-reports silently broken on these chips. The structural change in version 8 is that the previously reserved second byte in each struct is now bt_id (0 = BT0, 1 = BT1), allowing firmware to send separate reports for each Bluetooth device. All three structs are otherwise layout-compatible with version 7. Add rtw89_btc_fbtc_btver_v8, rtw89_btc_fbtc_btscan_v8 and rtw89_btc_fbtc_btafh_v8 structs with the bt_id field, extend the corresponding unions, add version 8 branches to _chk_btc_report, and update _update_bt_report to route each report to BT0 or BT1 according to BT ID. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260724135640.3195044-11-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 52 ++++++++++++++++++++++- drivers/net/wireless/realtek/rtw89/core.h | 34 +++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 5aa06ef430c8..96bb68f09a01 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -1529,7 +1529,14 @@ static void _update_bt_report(struct rtw89_dev *rtwdev, u8 rpt_type, u8 *pfinfo) switch (rpt_type) { case BTC_RPT_TYPE_BT_VER: - if (ver->fcxbtver == 7) { + if (ver->fcxbtver == 8) { + pver->v8 = *(struct rtw89_btc_fbtc_btver_v8 *)pfinfo; + bt = pver->v8.bt_id ? &btc->cx.bt1 : &btc->cx.bt0; + bt->ver_info.fw = le32_to_cpu(pver->v8.fw_ver); + bt->ver_info.fw_coex = le32_get_bits(pver->v8.coex_ver, + GENMASK(7, 0)); + bt->feature = le32_to_cpu(pver->v8.feature); + } else if (ver->fcxbtver == 7) { pver->v7 = *(struct rtw89_btc_fbtc_btver_v7 *)pfinfo; bt->ver_info.fw = le32_to_cpu(pver->v7.fw_ver); bt->ver_info.fw_coex = le32_get_bits(pver->v7.coex_ver, @@ -1570,6 +1577,19 @@ static void _update_bt_report(struct rtw89_dev *rtwdev, u8 rpt_type, u8 *pfinfo) pscan_v7->para[i].intvl == 0) scan_update = false; } + } else if (ver->fcxbtscan == 8) { + struct rtw89_btc_fbtc_btscan_v8 *pscan_v8 = + (struct rtw89_btc_fbtc_btscan_v8 *)pfinfo; + struct rtw89_btc_bt_info *tbt = + pscan_v8->bt_id ? &btc->cx.bt1 : &btc->cx.bt0; + + for (i = 0; i < CXSCAN_MAX; i++) { + tbt->scan_info_v2[i] = pscan_v8->para[i]; + if ((pscan_v8->type & BIT(i)) && + pscan_v8->para[i].win == 0 && + pscan_v8->para[i].intvl == 0) + scan_update = false; + } } if (scan_update) bt->scan_info_update = 1; @@ -1597,6 +1617,22 @@ static void _update_bt_report(struct rtw89_dev *rtwdev, u8 rpt_type, u8 *pfinfo) memcpy(&bt_linfo->afh_map_le[0], pafh_v7->afh_le_a, 4); memcpy(&bt_linfo->afh_map_le[4], pafh_v7->afh_le_b, 1); } + } else if (ver->fcxbtafh == 8) { + struct rtw89_btc_fbtc_btafh_v8 *pafh_v8 = + (struct rtw89_btc_fbtc_btafh_v8 *)pfinfo; + struct rtw89_btc_bt_info *tbt = + pafh_v8->bt_id ? &btc->cx.bt1 : &btc->cx.bt0; + struct rtw89_btc_bt_link_info *tbt_linfo = &tbt->link_info; + + if (pafh_v8->map_type & RPT_BT_AFH_SEQ_LEGACY) { + memcpy(&tbt_linfo->afh_map[0], pafh_v8->afh_l, 4); + memcpy(&tbt_linfo->afh_map[4], pafh_v8->afh_m, 4); + memcpy(&tbt_linfo->afh_map[8], pafh_v8->afh_h, 2); + } + if (pafh_v8->map_type & RPT_BT_AFH_SEQ_LE) { + memcpy(&tbt_linfo->afh_map_le[0], pafh_v8->afh_le_a, 4); + memcpy(&tbt_linfo->afh_map_le[4], pafh_v8->afh_le_b, 1); + } } else if (ver->fcxbtafh == 1) { pafh_v1 = (struct rtw89_btc_fbtc_btafh *)pfinfo; memcpy(&bt_linfo->afh_map[0], pafh_v1->afh_l, 4); @@ -1882,6 +1918,12 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, pfinfo = &pfwinfo->rpt_fbtc_btver.finfo.v7; pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_btver.finfo.v7); fwsubver->fcxbtver = pfwinfo->rpt_fbtc_btver.finfo.v7.fver; + } else if (ver->fcxbtver == 8) { + pfinfo = &pfwinfo->rpt_fbtc_btver.finfo.v8; + pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_btver.finfo.v8); + fwsubver->fcxbtver = pfwinfo->rpt_fbtc_btver.finfo.v8.fver; + } else { + goto err; } pcinfo->req_fver = ver->fcxbtver; break; @@ -1899,6 +1941,10 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, pfinfo = &pfwinfo->rpt_fbtc_btscan.finfo.v7; pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_btscan.finfo.v7); fwsubver->fcxbtscan = pfwinfo->rpt_fbtc_btscan.finfo.v7.fver; + } else if (ver->fcxbtscan == 8) { + pfinfo = &pfwinfo->rpt_fbtc_btscan.finfo.v8; + pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_btscan.finfo.v8); + fwsubver->fcxbtscan = pfwinfo->rpt_fbtc_btscan.finfo.v8.fver; } else { goto err; } @@ -1918,6 +1964,10 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, pfinfo = &pfwinfo->rpt_fbtc_btafh.finfo.v7; pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_btafh.finfo.v7); fwsubver->fcxbtafh = pfwinfo->rpt_fbtc_btafh.finfo.v7.fver; + } else if (ver->fcxbtafh == 8) { + pfinfo = &pfwinfo->rpt_fbtc_btafh.finfo.v8; + pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_btafh.finfo.v8); + fwsubver->fcxbtafh = pfwinfo->rpt_fbtc_btafh.finfo.v8.fver; } else { goto err; } diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index ba3a9b0d4aa2..aaed155ea423 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -2647,10 +2647,19 @@ struct rtw89_btc_fbtc_btscan_v7 { struct rtw89_btc_bt_scan_info_v2 para[CXSCAN_MAX]; } __packed; +struct rtw89_btc_fbtc_btscan_v8 { + u8 fver; /* btc_ver::fcxbtscan */ + u8 type; + u8 bt_id; /* 0:BT0, 1:BT1 */ + u8 rsvd1; + struct rtw89_btc_bt_scan_info_v2 para[CXSCAN_MAX]; +} __packed; + union rtw89_btc_fbtc_btscan { struct rtw89_btc_fbtc_btscan_v1 v1; struct rtw89_btc_fbtc_btscan_v2 v2; struct rtw89_btc_fbtc_btscan_v7 v7; + struct rtw89_btc_fbtc_btscan_v8 v8; }; struct rtw89_btc_bt_info { @@ -3683,9 +3692,21 @@ struct rtw89_btc_fbtc_btver_v7 { __le32 feature; } __packed; +struct rtw89_btc_fbtc_btver_v8 { + u8 fver; + u8 bt_id; /* 0:BT0, 1:BT1 */ + u8 rsvd1; + u8 rsvd2; + + __le32 coex_ver; /*bit[15:8]->shared, bit[7:0]->non-shared */ + __le32 fw_ver; + __le32 feature; +} __packed; + union rtw89_btc_fbtc_btver { struct rtw89_btc_fbtc_btver_v1 v1; struct rtw89_btc_fbtc_btver_v7 v7; + struct rtw89_btc_fbtc_btver_v8 v8; } __packed; struct rtw89_btc_fbtc_btafh { @@ -3721,6 +3742,18 @@ struct rtw89_btc_fbtc_btafh_v7 { u8 afh_le_b[4]; } __packed; +struct rtw89_btc_fbtc_btafh_v8 { + u8 fver; + u8 map_type; + u8 bt_id; /* 0:BT0, 1:BT1 */ + u8 rsvd1; + u8 afh_l[4]; /*bit0:2402, bit1:2403.... bit31:2433 */ + u8 afh_m[4]; /*bit0:2434, bit1:2435.... bit31:2465 */ + u8 afh_h[4]; /*bit0:2466, bit1:2467.....bit14:2480 */ + u8 afh_le_a[4]; + u8 afh_le_b[4]; +} __packed; + struct rtw89_btc_fbtc_btdevinfo { u8 fver; /* btc_ver::fcxbtdevinfo */ u8 rsvd; @@ -4194,6 +4227,7 @@ union rtw89_btc_fbtc_btafh_info { struct rtw89_btc_fbtc_btafh v1; struct rtw89_btc_fbtc_btafh_v2 v2; struct rtw89_btc_fbtc_btafh_v7 v7; + struct rtw89_btc_fbtc_btafh_v8 v8; }; struct rtw89_btc_report_ctrl_state { From 2951ecd83cc43c112b9f9883d248406677a41e6c Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 24 Jul 2026 21:56:37 +0800 Subject: [PATCH 0639/1433] wifi: rtw89: coex: Add dual Bluetooth debug info dump As RTL8922D support dual Bluetooth, add BT debug info dump for it. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260724135640.3195044-12-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 51 ++++++++++++++++------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 96bb68f09a01..6a718c54de67 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -9779,10 +9779,13 @@ enum btc_bt_a2dp_type { BTC_A2DP_TWS_RELAY = 2, }; -static int _show_bt_profile_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) +static int _show_bt_profile_info(struct rtw89_dev *rtwdev, char *buf, + size_t bufsz, u8 bid) { struct rtw89_btc *btc = &rtwdev->btc; - struct rtw89_btc_bt_link_info *bt_linfo = &btc->cx.bt0.link_info; + struct rtw89_btc_bt_info *bt = (bid == BTC_BT_1ST) ? + &btc->cx.bt0 : &btc->cx.bt1; + struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; struct rtw89_btc_bt_hfp_desc hfp = bt_linfo->hfp_desc; struct rtw89_btc_bt_hid_desc hid = bt_linfo->hid_desc; struct rtw89_btc_bt_a2dp_desc a2dp = bt_linfo->a2dp_desc; @@ -9835,15 +9838,16 @@ static int _show_bt_profile_info(struct rtw89_dev *rtwdev, char *buf, size_t buf return p - buf; } -static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) +static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz, u8 bid) { struct rtw89_btc *btc = &rtwdev->btc; const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_cx *cx = &btc->cx; - struct rtw89_btc_bt_info *bt = &cx->bt0; + struct rtw89_btc_bt_info *bt = (bid == BTC_BT_1ST) ? &cx->bt0 : &cx->bt1; struct rtw89_btc_wl_info *wl = &cx->wl; struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; struct rtw89_btc_module *md = &btc->mdinfo; + u8 bt_pos = (bid == BTC_BT_1ST) ? md->bt0_pos : md->bt1_pos; s8 br_dbm = bt->link_info.bt_txpwr_desc.br_dbm; s8 le_dbm = bt->link_info.bt_txpwr_desc.le_dbm; u8 hw_band = wl->role_info.pta_req_band; @@ -9855,13 +9859,21 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) if (!(btc->dm.coex_info_map & BTC_COEX_INFO_BT)) return 0; - p += scnprintf(p, end - p, "========== [BT Status] ==========\n"); + if (bid == BTC_BT_2ND) { + if (!(rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT)) + return 0; + if (!bt->enable.now) + return 0; + } + + p += scnprintf(p, end - p, "========== [BT_%s Status] ==========\n", + bid == BTC_BT_1ST ? "1ST" : "2ND"); p += scnprintf(p, end - p, " %-15s : enable:%s, btg:%s%s, connect:%s, ", "[status]", bt->enable.now ? "Y" : "N", bt->btg_type ? "Y" : "N", - (bt->enable.now && (bt->btg_type != md->bt0_pos) ? + (bt->enable.now && (bt->btg_type != bt_pos) ? "(efuse-mismatch!!)" : ""), (bt_linfo->status.map.connect ? "Y" : "N")); @@ -9927,7 +9939,7 @@ static int _show_bt_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) bt->bcnt[BTC_BCNT_INQPAG], bt->bcnt[BTC_BCNT_INQ], bt->bcnt[BTC_BCNT_PAGE], bt->bcnt[BTC_BCNT_IGNOWL]); - p += _show_bt_profile_info(rtwdev, p, end - p); + p += _show_bt_profile_info(rtwdev, p, end - p, bid); p += scnprintf(p, end - p, " %-15s : raw_data[%02x %02x %02x %02x %02x %02x] (type:%s/cnt:%d/same:%d)\n", @@ -12034,12 +12046,22 @@ static int _show_mreg_v7(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) dm->gnt_set[RTW89_PHY_0].gnt_bt0); if (rtwdev->dbcc_en) { - p += scnprintf(p, end - p, - ", phy-1[gnt_wl:%s-%d/gnt_bt:%s-%d]", - dm->gnt_set[RTW89_PHY_1].gnt_wl_sw_en ? "SW" : "HW", - dm->gnt_set[RTW89_PHY_1].gnt_wl, - dm->gnt_set[RTW89_PHY_1].gnt_bt0_sw_en ? "SW" : "HW", - dm->gnt_set[RTW89_PHY_1].gnt_bt0); + if (rtwdev->chip->para_ver & BTC_FEAT_DUAL_BT) + p += scnprintf(p, end - p, + ", phy-1[gnt_wl:%s-%d/gnt_bt0:%s-%d/gnt_bt1:%s-%d]", + dm->gnt_set[RTW89_PHY_1].gnt_wl_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_1].gnt_wl, + dm->gnt_set[RTW89_PHY_1].gnt_bt0_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_1].gnt_bt0, + dm->gnt_set[RTW89_PHY_1].gnt_bt1_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_1].gnt_bt1); + else + p += scnprintf(p, end - p, + ", phy-1[gnt_wl:%s-%d/gnt_bt:%s-%d]", + dm->gnt_set[RTW89_PHY_1].gnt_wl_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_1].gnt_wl, + dm->gnt_set[RTW89_PHY_1].gnt_bt0_sw_en ? "SW" : "HW", + dm->gnt_set[RTW89_PHY_1].gnt_bt0); } pcinfo = &pfwinfo->rpt_fbtc_mregval.cinfo; @@ -13020,7 +13042,8 @@ ssize_t rtw89_btc_dump_info(struct rtw89_dev *rtwdev, char *buf, size_t bufsz) p += _show_cx_info(rtwdev, p, end - p); p += _show_wl_info(rtwdev, p, end - p); - p += _show_bt_info(rtwdev, p, end - p); + p += _show_bt_info(rtwdev, p, end - p, BTC_BT_1ST); + p += _show_bt_info(rtwdev, p, end - p, BTC_BT_2ND); p += _show_dm_info(rtwdev, p, end - p); p += _show_fw_dm_msg(rtwdev, p, end - p); From ffb7d6a1c62fa3411d6a27a9f664531d0df62272 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 24 Jul 2026 21:56:38 +0800 Subject: [PATCH 0640/1433] wifi: rtw89: coex: Fix TDMA v8 handling to unblock BTC report parsing fcxtdma=8 was not handled in _chk_btc_report(), causing the parser to hit 'goto err' and return 0 when processing the TDMA sub-report. This broke the _parse_btc_report() loop before reaching BT_VER (type=9), leaving bt->ver_info.fw always zero on RTL8922A/D. TDMA v8 uses the same struct layout as v3/v4/v7 (rtw89_btc_fbtc_tdma_v3, 12 bytes), so add it to the existing v3/v4/v7 branch in both switch cases. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260724135640.3195044-13-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 6a718c54de67..7ab22c60a420 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -1769,7 +1769,7 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_tdma.finfo.v1); fwsubver->fcxtdma = 0; } else if (ver->fcxtdma == 3 || ver->fcxtdma == 4 || - ver->fcxtdma == 7) { + ver->fcxtdma == 7 || ver->fcxtdma == 8) { pfinfo = &pfwinfo->rpt_fbtc_tdma.finfo.v3; pcinfo->req_len = sizeof(pfwinfo->rpt_fbtc_tdma.finfo.v3); fwsubver->fcxtdma = pfwinfo->rpt_fbtc_tdma.finfo.v3.fver; @@ -2300,7 +2300,7 @@ static u32 _chk_btc_report(struct rtw89_dev *rtwdev, &pfwinfo->rpt_fbtc_tdma.finfo.v1, sizeof(dm->tdma_now))); else if (ver->fcxtdma == 3 || ver->fcxtdma == 4 || - ver->fcxtdma == 7) + ver->fcxtdma == 7 || ver->fcxtdma == 8) _chk_btc_err(rtwdev, BTC_DCNT_TDMA_NONSYNC, memcmp(&dm->tdma_now, &pfwinfo->rpt_fbtc_tdma.finfo.v3.tdma, From 4406b550ae2db9d80334471742d0b8ef4984d9cc Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 24 Jul 2026 21:56:39 +0800 Subject: [PATCH 0641/1433] wifi: rtw89: coex: Fix H2C command redundant send & version fall through I/O offload higher priority sending event didn't return after H2C command was sent, add a return to prevent send twice in the same time. Update driver info entry which is handling module control info didn't handle the version 9 command format, add if condition to handle it. TX power update H2C command result checker logic was reversed, it will lead to the TX power value never update again after first update, fix the issue. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260724135640.3195044-14-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 7ab22c60a420..6b3e199398cf 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -1073,6 +1073,7 @@ static int _send_fw_cmd(struct rtw89_dev *rtwdev, u8 h2c_class, u8 h2c_func, } btc->fwinfo.cnt_h2c++; + return 0; } else { /* Fill H2C MACRO buffer(TLV format) temporarily */ if (btc->hbuf_cnt == 0) _reset_h2c_macro(btc); @@ -3500,6 +3501,8 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index) if (ver->fcxctrl == 7) rtw89_fw_h2c_cxdrv_ctrl_v7(rtwdev, index); + else if (ver->fcxctrl == 9) + rtw89_fw_h2c_cxdrv_ctrl_v9(rtwdev, index); else rtw89_fw_h2c_cxdrv_ctrl(rtwdev, index); break; @@ -3893,7 +3896,7 @@ static void _set_bt_tx_power(struct rtw89_dev *rtwdev, bool force_exec, u8 bid, if (rtwdev->chip->chip_gen == RTW89_CHIP_AX) len = SET_RF_PARA_AX_LEN; - if (_send_fw_cmd(rtwdev, BTFC_SET, h2c_func, buf, len)) { + if (!_send_fw_cmd(rtwdev, BTFC_SET, h2c_func, buf, len)) { btc->dm.rf_trx_para.bt_tx_power[i] = level; if (rf_band == RTW89_BAND_2G) bt->tx_power_now = level; From 6a43bbee78a54b01bb67718f4fc527c9f44d3804 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Fri, 24 Jul 2026 21:56:40 +0800 Subject: [PATCH 0642/1433] wifi: rtw89: coex: Handle Bluetooth LE-Audio related coexistence feature Implement event handler of BTF_EVNT_BT_LEAUDIO_INFO C2H command, and related coexistence mechanism for Bluetooth LE-Audio feature. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260724135640.3195044-15-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 199 ++++++++++++++++++++++ drivers/net/wireless/realtek/rtw89/core.h | 35 +++- 2 files changed, 233 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 6b3e199398cf..0e42c720819a 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -951,6 +951,7 @@ enum btc_reason_and_action { BTC_RSN_ACT1_WORK, BTC_RSN_BT_DEVINFO_WORK, BTC_RSN_RFK_CHK_WORK, + BTC_RSN_UPDATE_BT_LEAUDINFO, BTC_RSN_NUM, BTC_ACT_NONE = 100, BTC_ACT_WL_ONLY, @@ -975,6 +976,8 @@ enum btc_reason_and_action { BTC_ACT_BT_A2DP_PAN, BTC_ACT_BT_PAN_HID, BTC_ACT_BT_A2DP_PAN_HID, + BTC_ACT_BT_BIS, + BTC_ACT_BT_CIS, BTC_ACT_WL_25G_MCC, BTC_ACT_WL_2G_MCC, BTC_ACT_WL_2G_SCC, @@ -6076,6 +6079,43 @@ static void _action_bt_pan_hid(struct rtw89_dev *rtwdev) } } +static void _action_bt_bis(struct rtw89_dev *rtwdev) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_dm *dm = &btc->dm; + u16 policy_type; + + if (dm->bis_tdma) + policy_type = BTC_CXP_OFFE_2GBWISOB; + else + policy_type = BTC_CXP_OFF_EQ0; + + _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_PTA); + _set_policy(rtwdev, policy_type, BTC_ACT_BT_BIS); +} + +static void _action_bt_cis(struct rtw89_dev *rtwdev) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_bt_link_info *bt_linfo = &btc->cx.bt0.link_info; + struct rtw89_btc_wl_info *wl = &btc->cx.wl; + struct rtw89_btc_dm *dm = &btc->dm; + u16 policy_type; + + if (bt_linfo->a2dp_desc.active) { + dm->slot_dur[CXST_W1] = 80; + dm->slot_dur[CXST_B1] = 20; + policy_type = BTC_CXP_PFIX_TDW1B1; + } else if (wl->status.map.traffic_dir & BIT(RTW89_TFC_UL)) { + policy_type = BTC_CXP_OFF_BWB0; + } else { + policy_type = BTC_CXP_OFF_BWB1; + } + + _set_ant(rtwdev, NM_EXEC, BTC_PHY_ALL, BTC_ANT_PTA); + _set_policy(rtwdev, policy_type, BTC_ACT_BT_CIS); +} + static void _action_bt_a2dp_pan_hid(struct rtw89_dev *rtwdev) { struct rtw89_btc *btc = &rtwdev->btc; @@ -6692,6 +6732,7 @@ static void _action_by_bt(struct rtw89_dev *rtwdev) struct rtw89_btc *btc = &rtwdev->btc; struct rtw89_btc_bt_info *bt = &btc->cx.bt0; struct rtw89_btc_bt_link_info *bt_linfo = &bt->link_info; + struct rtw89_btc_bt_leaudio_desc leaudio = bt_linfo->leaudio_desc; struct rtw89_btc_bt_hid_desc hid = bt_linfo->hid_desc; struct rtw89_btc_bt_a2dp_desc a2dp = bt_linfo->a2dp_desc; struct rtw89_btc_bt_pan_desc pan = bt_linfo->pan_desc; @@ -6715,6 +6756,12 @@ static void _action_by_bt(struct rtw89_dev *rtwdev) if (bt_linfo->pan_desc.exist) profile_map |= BTC_BT_PAN; + if (leaudio.bis_exist) + profile_map |= BTC_BT_BIS; + + if (leaudio.cis_exist) + profile_map |= BTC_BT_CIS; + switch (profile_map) { case BTC_BT_NOPROFILE: if (pan.active) @@ -6740,6 +6787,13 @@ static void _action_by_bt(struct rtw89_dev *rtwdev) case BTC_BT_PAN: _action_bt_pan(rtwdev); break; + case BTC_BT_BIS: + _action_bt_bis(rtwdev); + break; + case BTC_BT_CIS: + case BTC_BT_CIS | BTC_BT_HID: + _action_bt_cis(rtwdev); + break; case BTC_BT_A2DP | BTC_BT_HFP: case BTC_BT_A2DP | BTC_BT_HID: case BTC_BT_A2DP | BTC_BT_HFP | BTC_BT_HID: @@ -7909,6 +7963,140 @@ static void _update_bt_scbd(struct rtw89_dev *rtwdev, u8 bid) } } +static void _update_bt_link_cnt(struct rtw89_dev *rtwdev, + struct rtw89_btc_bt_info *bt, bool b56g) +{ + struct rtw89_btc_bt_link_info *b = b56g ? &bt->link_info_56g : + &bt->link_info; + struct rtw89_btc_bt_leaudio_desc *leaudio = &b->leaudio_desc; + struct rtw89_btc_bt_a2dp_desc *a2dp = &b->a2dp_desc; + struct rtw89_btc_bt_hfp_desc *hfp = &b->hfp_desc; + struct rtw89_btc_bt_hid_desc *hid = &b->hid_desc; + struct rtw89_btc_bt_pan_desc *pan = &b->pan_desc; + + b->link_cnt.last = b->link_cnt.now; + b->link_cnt.now = 0; + b->link_cnt.chg = 0; + + b->link_cnt.now += hfp->exist; + b->link_cnt.now += hid->exist; + b->link_cnt.now += a2dp->exist; + b->link_cnt.now += pan->exist; + b->link_cnt.now += leaudio->bis_exist; + b->link_cnt.now += leaudio->cis_exist; +} + +static void _update_bt_leaudio_info(struct rtw89_dev *rtwdev, u8 bid, + void *buf, u32 len) +{ + const struct rtw89_chip_info *chip = rtwdev->chip; + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_bt_info *bt = (bid == BTC_BT_1ST) ? &cx->bt0 : &cx->bt1; + struct rtw89_btc_bt_link_info *b = &bt->link_info; + struct rtw89_btc_bt_leaudio_info *src = buf; + struct rtw89_btc_bt_leaudio_desc *leaudio; + bool bt_56g = false, cis_exist, bis_exist; + u8 bis_cnt, cis_cnt; + + if ((src->len & BIT(7)) && bt->band_56G_support) { + b = &bt->link_info_56g; + bt_56g = true; + } + + leaudio = &b->leaudio_desc; + + if (!memcmp(b->leaudio_raw_info, src, BTC_BTINFO_MAX)) { + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s return by leaudio-info duplicate!\n", + __func__); + bt->bcnt[BTC_BCNT_LEAUDIO_INFOSAME]++; + return; + } + + memcpy(b->leaudio_raw_info, src, BTC_BTINFO_MAX); + + bis_exist = u8_get_bits(src->bis_cis, RTW89_BTC_LEAU_INFO_L2_BIS_EX); + cis_exist = u8_get_bits(src->bis_cis, RTW89_BTC_LEAU_INFO_L2_CIS_EX); + bis_cnt = u8_get_bits(src->bis_cis, RTW89_BTC_LEAU_INFO_L2_BIS_CNT); + cis_cnt = u8_get_bits(src->bis_cis, RTW89_BTC_LEAU_INFO_L2_CIS_CNT); + + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s: bis_exist:%d bis_cnt:%d cis_exist:%d cis_cnt:%d rssi:%d\n", + __func__, bis_exist, bis_cnt, cis_exist, cis_cnt, src->rssi); + + if (!bis_exist && !cis_exist) + memset(leaudio, 0, sizeof(*leaudio)); + + leaudio->bis_cnt = bis_cnt; + leaudio->bis_exist = bis_exist; + leaudio->cis_cnt = cis_cnt; + leaudio->cis_exist = cis_exist; + + if (leaudio->bis_exist) + b->status.map.profile_map |= BTC_BT_BIS; + else + b->status.map.profile_map &= ~BTC_BT_BIS; + + if (leaudio->cis_exist) + b->status.map.profile_map |= BTC_BT_CIS; + else + b->status.map.profile_map &= ~BTC_BT_CIS; + + _update_bt_link_cnt(rtwdev, bt, bt_56g); + + leaudio->rssi = chip->ops->btc_get_bt_rssi(rtwdev, src->rssi); + + leaudio->bis_cnt_last = leaudio->bis_cnt; + leaudio->bis_exist_last = leaudio->bis_exist; + leaudio->cis_cnt_last = leaudio->cis_cnt; + leaudio->cis_exist_last = leaudio->cis_exist; +} + +static void _update_bt_bistdma_info(struct rtw89_dev *rtwdev, u8 bid, + void *buf, u32 len) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_cx *cx = &btc->cx; + struct rtw89_btc_dm *dm = &btc->dm; + struct rtw89_btc_bt_info *bt = (bid == BTC_BT_1ST) ? &cx->bt0 : &cx->bt1; + struct rtw89_btc_bt_link_info *b = &bt->link_info; + struct rtw89_btc_bt_bistdma_info_le *src = buf; + struct rtw89_btc_bt_leaudio_desc *leaudio; + + if ((src->len & BIT(7)) && bt->band_56G_support) + b = &bt->link_info_56g; + + leaudio = &b->leaudio_desc; + + if (!memcmp(b->bistdma_raw_info, src, BTC_BTINFO_MAX)) { + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s return by bistdma-info duplicate!\n", + __func__); + bt->bcnt[BTC_BCNT_LEAUDIO_INFOSAME]++; + return; + } + + memcpy(b->bistdma_raw_info, src, BTC_BTINFO_MAX); + + rtw89_debug(rtwdev, RTW89_DBG_BTC, + "[BTC], %s: bis_start_end:%d bis_trx:%d\n", + __func__, + u8_get_bits(src->bis, RTW89_BTC_BIS_INFO_L2_START_END), + u8_get_bits(src->bis, RTW89_BTC_BIS_INFO_L2_TRX)); + + leaudio->bis_trx = u8_get_bits(src->bis, RTW89_BTC_BIS_INFO_L2_TRX); + leaudio->bis_start_end = u8_get_bits(src->bis, + RTW89_BTC_BIS_INFO_L2_START_END); + + leaudio->diff_t = (src->diff_t_hb * 256 + src->diff_t_lb) * 625 / 1000; + + if (btc->ant_type == BTC_ANT_SHARED && leaudio->diff_t > 13) + dm->bis_tdma = true; + else + dm->bis_tdma = false; +} + #define BTC_BTINFO_PWR_LEN 5 static void _update_bt_txpwr_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) { @@ -9606,6 +9794,14 @@ void rtw89_btc_c2h_handle(struct rtw89_dev *rtwdev, struct sk_buff *skb, case BTF_EVNT_CX_RUNINFO: btc->dm.cnt_dm[BTC_DCNT_CX_RUNINFO]++; break; + case BTF_EVNT_BT_LEAUDIO_INFO: + bt->bcnt[BTC_BCNT_LEAUDIO_INFOUPDATE]++; + if (buf[BTC_BTINFO_L0] == BTC_BTINFO_BISTDMA) + _update_bt_bistdma_info(rtwdev, bid, buf, len); + else + _update_bt_leaudio_info(rtwdev, bid, buf, len); + _run_coex(rtwdev, BTC_RSN_UPDATE_BT_LEAUDINFO); + break; case BTF_EVNT_BT_QUERY_TXPWR: bt->bcnt[BTC_BCNT_TXPWR_UPDATE]++; _update_bt_txpwr_info(rtwdev, buf, len); @@ -10107,6 +10303,7 @@ static const char *steps_to_str(u16 step) CASE_BTC_RSN_STR(ACT1_WORK); CASE_BTC_RSN_STR(BT_DEVINFO_WORK); CASE_BTC_RSN_STR(RFK_CHK_WORK); + CASE_BTC_RSN_STR(UPDATE_BT_LEAUDINFO); CASE_BTC_ACT_STR(NONE); CASE_BTC_ACT_STR(WL_ONLY); @@ -10131,6 +10328,8 @@ static const char *steps_to_str(u16 step) CASE_BTC_ACT_STR(BT_A2DP_PAN); CASE_BTC_ACT_STR(BT_PAN_HID); CASE_BTC_ACT_STR(BT_A2DP_PAN_HID); + CASE_BTC_ACT_STR(BT_BIS); + CASE_BTC_ACT_STR(BT_CIS); CASE_BTC_ACT_STR(WL_25G_MCC); CASE_BTC_ACT_STR(WL_2G_MCC); CASE_BTC_ACT_STR(WL_2G_SCC); diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index aaed155ea423..09c19a6b9058 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -1383,6 +1383,8 @@ enum rtw89_btc_btinfo { BTC_BTINFO_MAX }; +#define BTC_BTINFO_BISTDMA 0x48 /* cmd value that identifies BISTDMA data */ + enum rtw89_btc_dcnt { BTC_DCNT_RUN = 0x0, BTC_DCNT_CX_RUNINFO, @@ -2167,6 +2169,31 @@ struct rtw89_btc_bt_txpwr_desc { u8 le_gain_index; }; +struct rtw89_btc_bt_leaudio_info { + u8 cmd; + u8 len; + u8 bis_cis; +#define RTW89_BTC_LEAU_INFO_L2_BIS_EX BIT(0) +#define RTW89_BTC_LEAU_INFO_L2_BIS_CNT GENMASK(3, 1) +#define RTW89_BTC_LEAU_INFO_L2_CIS_EX BIT(4) +#define RTW89_BTC_LEAU_INFO_L2_CIS_CNT GENMASK(7, 5) + u8 rssi; + __le32 hbrsvd; +} __packed; + +struct rtw89_btc_bt_bistdma_info_le { + u8 cmd; + u8 len; + u8 bis; /* BIT(2) ~ BIT(7) is rsvd */ +#define RTW89_BTC_BIS_INFO_L2_START_END BIT(0) /* 0: BIS start, 1: BIS end */ +#define RTW89_BTC_BIS_INFO_L2_TRX BIT(1) /* 0: BIS Tx, 1: BIS Rx */ + u8 diff_t_lb; + u8 diff_t_hb; /* diff_t = (diff_t_hb * 256 + diff_t_lb) * 0.625 ms */ + u8 hb1rsvd; + u8 hb2rsvd; + u8 hb3rsvd; +} __packed; + struct rtw89_btc_bt_leaudio_desc { u32 bis_exist: 1; u32 bis_exist_last: 1; @@ -2177,7 +2204,9 @@ struct rtw89_btc_bt_leaudio_desc { u32 rssi: 8; u32 bis_cnt_last: 3; u32 cis_cnt_last: 3; - u32 rsvd: 8; + u32 bis_trx: 1; + u32 bis_start_end: 1; + u32 rsvd: 6; u16 diff_t; }; @@ -2213,6 +2242,9 @@ struct rtw89_btc_bt_link_info { u8 ble_scan_en: 1; u8 reinit: 1; u8 rsvd: 6; + + u8 leaudio_raw_info[BTC_BTINFO_MAX]; /* raw LE audio info from BT mailbox */ + u8 bistdma_raw_info[BTC_BTINFO_MAX]; /* raw BIS-TDMA info from BT mailbox */ }; struct rtw89_btc_bind_bt_status { @@ -4085,6 +4117,7 @@ struct rtw89_btc_dm { u8 lps_ctrl_scbd: 1; u8 lps_ctrl_scbd_last: 1; u8 lps_ctrl_change: 1; + u8 bis_tdma: 1; /* BIS TDMA mode active */ u8 scbd_write_instant; bool scbd_b2w_update; bool scbd_w2b_update; From c13ab5bed71662fda7ca8573da560bd572a60f03 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Sun, 26 Jul 2026 22:40:30 +0900 Subject: [PATCH 0643/1433] wifi: rtlwifi: rtl8821ae: remove conditional return with no effect in _rtl8821ae_llt_write() Both branches of the check return the same value, so the check has no effect. Remove it and return the value directly. This is the result of running the Coccinelle script from scripts/coccinelle/misc/cond_return_no_effect.cocci. Signed-off-by: Sang-Heon Jeon Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260726134034.1385834-2-ekffu200098@gmail.com --- drivers/net/wireless/realtek/rtlwifi/rtl8821ae/hw.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/net/wireless/realtek/rtlwifi/rtl8821ae/hw.c b/drivers/net/wireless/realtek/rtlwifi/rtl8821ae/hw.c index 9b119a51bc30..1ba53e671207 100644 --- a/drivers/net/wireless/realtek/rtlwifi/rtl8821ae/hw.c +++ b/drivers/net/wireless/realtek/rtlwifi/rtl8821ae/hw.c @@ -1462,12 +1462,7 @@ static bool _rtl8821ae_init_llt_table(struct ieee80211_hw *hw, u32 boundary) return status; } - status = _rtl8821ae_llt_write(hw, last_entry_of_txpktbuf, - txpktbuf_bndy); - if (!status) - return status; - - return status; + return _rtl8821ae_llt_write(hw, last_entry_of_txpktbuf, txpktbuf_bndy); } static bool _rtl8821ae_dynamic_rqpn(struct ieee80211_hw *hw, u32 boundary, From bbc18041ad5f35fd64211b82df2b585866bf75d1 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Sun, 26 Jul 2026 22:40:31 +0900 Subject: [PATCH 0644/1433] wifi: rtw89: remove conditional return with no effect in sys_init_*() Both branches of the check return the same value, so the check has no effect. Remove it and return the value directly. This is the result of running the Coccinelle script from scripts/coccinelle/misc/cond_return_no_effect.cocci. Signed-off-by: Sang-Heon Jeon Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260726134034.1385834-3-ekffu200098@gmail.com --- drivers/net/wireless/realtek/rtw89/mac.c | 6 +----- drivers/net/wireless/realtek/rtw89/mac_be.c | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/mac.c b/drivers/net/wireless/realtek/rtw89/mac.c index db9fca828829..2cbff2be9bbb 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.c +++ b/drivers/net/wireless/realtek/rtw89/mac.c @@ -1710,11 +1710,7 @@ static int sys_init_ax(struct rtw89_dev *rtwdev) if (ret) return ret; - ret = chip_func_en_ax(rtwdev); - if (ret) - return ret; - - return ret; + return chip_func_en_ax(rtwdev); } const struct rtw89_mac_size_set rtw89_mac_size = { diff --git a/drivers/net/wireless/realtek/rtw89/mac_be.c b/drivers/net/wireless/realtek/rtw89/mac_be.c index 8de0fe5a3b1d..dfa0973e367c 100644 --- a/drivers/net/wireless/realtek/rtw89/mac_be.c +++ b/drivers/net/wireless/realtek/rtw89/mac_be.c @@ -929,11 +929,7 @@ static int sys_init_be(struct rtw89_dev *rtwdev) if (ret) return ret; - ret = chip_func_en_be(rtwdev); - if (ret) - return ret; - - return ret; + return chip_func_en_be(rtwdev); } static int mac_func_en_be(struct rtw89_dev *rtwdev) From 9f2948010764d708bda27369d09ce6f194abe8e3 Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Mon, 27 Jul 2026 12:12:22 +0530 Subject: [PATCH 0645/1433] wifi: rtw88: Fix potential memory leak in rtw_txq_push_skb() The skb passed to the rtw_hci_tx_write() is expected to be freed when the function fails, but the error path in rtw_txq_push_skb() does not free the skb before returning. This can lead to a memory leak in rtw_txq_push() where a dequeued skb is passed to rtw_txq_push_skb(). Fixes: aaab5d0e6737 ("rtw88: kick off TX packets once for higher efficiency") Cc: stable@vger.kernel.org Signed-off-by: Abdun Nihaal Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260727064223.61836-1-nihaal@cse.iitm.ac.in --- drivers/net/wireless/realtek/rtw88/tx.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/realtek/rtw88/tx.c b/drivers/net/wireless/realtek/rtw88/tx.c index 9d747a060b98..8786bbb421c2 100644 --- a/drivers/net/wireless/realtek/rtw88/tx.c +++ b/drivers/net/wireless/realtek/rtw88/tx.c @@ -619,6 +619,7 @@ static int rtw_txq_push_skb(struct rtw_dev *rtwdev, ret = rtw_hci_tx_write(rtwdev, &pkt_info, skb); if (ret) { rtw_err(rtwdev, "failed to write TX skb to HCI\n"); + ieee80211_free_txskb(rtwdev->hw, skb); return ret; } return 0; From fc2b13acef67e65efa77626b58dabcb8d99175eb Mon Sep 17 00:00:00 2001 From: Thangaraj Samynathan Date: Thu, 23 Jul 2026 10:38:26 +0530 Subject: [PATCH 0646/1433] net: lan743x: add RMII strap status detection for PCI11x1x Extend pci11x1x_strap_get_status() to read the RMII strap bits from the STRAP_READ register. The is_rmii_en flag is initialized to false and updated based on the hardware strap only if SGMII is not already enabled. This ensures correct interface identification during adapter initialization. Update the netif_dbg() to report the selected interface as SGMII, RMII, or RGMII. Signed-off-by: Thangaraj Samynathan Link: https://patch.msgid.link/20260723050827.694832-2-Thangaraj.S@microchip.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/microchip/lan743x_main.c | 12 ++++++++++-- drivers/net/ethernet/microchip/lan743x_main.h | 3 +++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/microchip/lan743x_main.c b/drivers/net/ethernet/microchip/lan743x_main.c index e759171bfd76..3a418105cd11 100644 --- a/drivers/net/ethernet/microchip/lan743x_main.c +++ b/drivers/net/ethernet/microchip/lan743x_main.c @@ -42,6 +42,7 @@ static void pci11x1x_strap_get_status(struct lan743x_adapter *adapter) u32 strap; int ret; + adapter->is_rmii_en = false; /* Timeout = 100 (i.e. 1 sec (10 msce * 100)) */ ret = lan743x_hs_syslock_acquire(adapter, 100); if (ret < 0) { @@ -73,8 +74,15 @@ static void pci11x1x_strap_get_status(struct lan743x_adapter *adapter) adapter->is_sgmii_en = false; } } - netif_dbg(adapter, drv, adapter->netdev, - "SGMII I/F %sable\n", adapter->is_sgmii_en ? "En" : "Dis"); + + if (!adapter->is_sgmii_en && strap & STRAP_READ_USE_RMII_EN_) { + if (strap & STRAP_READ_RMII_EN_) + adapter->is_rmii_en = true; + } + + netif_dbg(adapter, drv, adapter->netdev, "Selected I/F: %s\n", + adapter->is_sgmii_en ? "SGMII" : + adapter->is_rmii_en ? "RMII" : "RGMII"); } static bool is_pci11x1x_chip(struct lan743x_adapter *adapter) diff --git a/drivers/net/ethernet/microchip/lan743x_main.h b/drivers/net/ethernet/microchip/lan743x_main.h index 1573c8f9c993..1f8d9294a6ef 100644 --- a/drivers/net/ethernet/microchip/lan743x_main.h +++ b/drivers/net/ethernet/microchip/lan743x_main.h @@ -36,7 +36,9 @@ #define FPGA_SGMII_OP BIT(24) #define STRAP_READ (0x0C) +#define STRAP_READ_USE_RMII_EN_ BIT(23) #define STRAP_READ_USE_SGMII_EN_ BIT(22) +#define STRAP_READ_RMII_EN_ BIT(7) #define STRAP_READ_SGMII_EN_ BIT(6) #define STRAP_READ_SGMII_REFCLK_ BIT(5) #define STRAP_READ_SGMII_2_5G_ BIT(4) @@ -1072,6 +1074,7 @@ struct lan743x_adapter { struct lan743x_rx rx[LAN743X_USED_RX_CHANNELS]; bool is_pci11x1x; bool is_sgmii_en; + bool is_rmii_en; /* protect ethernet syslock */ spinlock_t eth_syslock_spinlock; bool eth_syslock_en; From c2e2921d7ade99bc0a5c29347cab3b995261eb05 Mon Sep 17 00:00:00 2001 From: Thangaraj Samynathan Date: Thu, 23 Jul 2026 10:38:27 +0530 Subject: [PATCH 0647/1433] net: lan743x: add support for RMII interface Enable RMII interface in the lan743x driver for PHY and MAC configuration. - Select RMII interface in lan743x_phy_interface_select(). - Update phylink supported_interfaces and MAC capabilities. - Enable RMII via RMII_CTL in lan743x_hardware_init(). - Define RMII_CTL register and enable bit in lan743x_main.h. EEE is not supported with RMII on PCI11x1x: the hardware does not implement LPI signaling over RMII. Clear RMII from lpi_interfaces to prevent phylink from enabling EEE on this interface. Signed-off-by: Thangaraj Samynathan Link: https://patch.msgid.link/20260723050827.694832-3-Thangaraj.S@microchip.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/microchip/lan743x_main.c | 23 +++++++++++++++++-- drivers/net/ethernet/microchip/lan743x_main.h | 3 +++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/microchip/lan743x_main.c b/drivers/net/ethernet/microchip/lan743x_main.c index 3a418105cd11..24ae56a3c9ed 100644 --- a/drivers/net/ethernet/microchip/lan743x_main.c +++ b/drivers/net/ethernet/microchip/lan743x_main.c @@ -1402,6 +1402,8 @@ static void lan743x_phy_interface_select(struct lan743x_adapter *adapter) if (adapter->is_pci11x1x && adapter->is_sgmii_en) adapter->phy_interface = PHY_INTERFACE_MODE_SGMII; + else if (adapter->is_pci11x1x && adapter->is_rmii_en) + adapter->phy_interface = PHY_INTERFACE_MODE_RMII; else if (id_rev == ID_REV_ID_LAN7430_) adapter->phy_interface = PHY_INTERFACE_MODE_GMII; else if ((id_rev == ID_REV_ID_LAN7431_) && (data & MAC_CR_MII_EN_)) @@ -3190,6 +3192,12 @@ static int lan743x_phylink_create(struct lan743x_adapter *adapter) __set_bit(PHY_INTERFACE_MODE_MII, adapter->phylink_config.supported_interfaces); break; + case PHY_INTERFACE_MODE_RMII: + __set_bit(PHY_INTERFACE_MODE_RMII, + adapter->phylink_config.supported_interfaces); + adapter->phylink_config.lpi_capabilities = 0; + break; + default: phy_interface_set_rgmii(adapter->phylink_config.supported_interfaces); } @@ -3197,6 +3205,9 @@ static int lan743x_phylink_create(struct lan743x_adapter *adapter) memcpy(adapter->phylink_config.lpi_interfaces, adapter->phylink_config.supported_interfaces, sizeof(adapter->phylink_config.lpi_interfaces)); + if (adapter->phy_interface == PHY_INTERFACE_MODE_RMII) + __clear_bit(PHY_INTERFACE_MODE_RMII, + adapter->phylink_config.lpi_interfaces); pl = phylink_create(&adapter->phylink_config, NULL, adapter->phy_interface, &lan743x_phylink_mac_ops); @@ -3541,6 +3552,7 @@ static int lan743x_hardware_init(struct lan743x_adapter *adapter, { struct lan743x_tx *tx; u32 sgmii_ctl; + u32 rmii_ctl; int index; int ret; @@ -3562,6 +3574,12 @@ static int lan743x_hardware_init(struct lan743x_adapter *adapter, sgmii_ctl |= SGMII_CTL_SGMII_POWER_DN_; } lan743x_csr_write(adapter, SGMII_CTL, sgmii_ctl); + rmii_ctl = lan743x_csr_read(adapter, RMII_CTL); + if (adapter->is_rmii_en) + rmii_ctl |= RMII_CTL_RMII_ENABLE_; + else + rmii_ctl &= ~RMII_CTL_RMII_ENABLE_; + lan743x_csr_write(adapter, RMII_CTL, rmii_ctl); } else { adapter->max_tx_channels = LAN743X_MAX_TX_CHANNELS; adapter->used_tx_channels = LAN743X_USED_TX_CHANNELS; @@ -3628,8 +3646,9 @@ static int lan743x_mdiobus_init(struct lan743x_adapter *adapter) adapter->mdiobus->name = "lan743x-mdiobus-c45"; dev_dbg(&adapter->pdev->dev, "lan743x-mdiobus-c45\n"); } else { - dev_dbg(&adapter->pdev->dev, "RGMII operation\n"); - // Only C22 support when RGMII I/F + dev_dbg(&adapter->pdev->dev, "%s operation\n", + adapter->is_rmii_en ? "RMII" : "RGMII"); + // Only C22 support when RGMII/RMII I/F adapter->mdiobus->read = lan743x_mdiobus_read_c22; adapter->mdiobus->write = lan743x_mdiobus_write_c22; adapter->mdiobus->name = "lan743x-mdiobus"; diff --git a/drivers/net/ethernet/microchip/lan743x_main.h b/drivers/net/ethernet/microchip/lan743x_main.h index 1f8d9294a6ef..d9495cf96b41 100644 --- a/drivers/net/ethernet/microchip/lan743x_main.h +++ b/drivers/net/ethernet/microchip/lan743x_main.h @@ -325,6 +325,9 @@ #define MAC_WUCSR2_IPV6_TCPSYN_RCD_ BIT(5) #define MAC_WUCSR2_IPV4_TCPSYN_RCD_ BIT(4) +#define RMII_CTL (0x710) +#define RMII_CTL_RMII_ENABLE_ BIT(0) + #define SGMII_ACC (0x720) #define SGMII_ACC_SGMII_BZY_ BIT(31) #define SGMII_ACC_SGMII_WR_ BIT(30) From 4762f405d0410a8c3492d643f586117ecc00cf76 Mon Sep 17 00:00:00 2001 From: Daniele Palmas Date: Fri, 24 Jul 2026 16:28:17 +0200 Subject: [PATCH 0648/1433] net: wwan: add minimalistic IOCTls support also to QCDM port Upstream libqcdm requires IOCTLs support to work, so add the current AT minimalistic support also to the QCDM port. Reviewed-by: Loic Poulain Signed-off-by: Daniele Palmas Link: https://patch.msgid.link/20260724142909.3270824-2-dnlplm@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/wwan/wwan_core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wwan/wwan_core.c b/drivers/net/wwan/wwan_core.c index ccce2ad74128..8168239e52c3 100644 --- a/drivers/net/wwan/wwan_core.c +++ b/drivers/net/wwan/wwan_core.c @@ -1046,7 +1046,8 @@ static long wwan_port_fops_ioctl(struct file *filp, unsigned int cmd, struct wwan_port *port = filp->private_data; int res; - if (port->type == WWAN_PORT_AT) { /* AT port specific IOCTLs */ + if (port->type == WWAN_PORT_AT || port->type == WWAN_PORT_QCDM) { + /* AT and QCDM port specific IOCTLs */ res = wwan_port_fops_at_ioctl(port, cmd, arg); if (res != -ENOIOCTLCMD) return res; From 1a87048a4c686e5da6af137f8e1218c40e3b59ad Mon Sep 17 00:00:00 2001 From: Daniele Palmas Date: Fri, 24 Jul 2026 16:28:18 +0200 Subject: [PATCH 0649/1433] net: wwan: add exclusive open mode capability to AT and QCDM ports Add exclusive open mode capability to AT and QCDM ports to improve compatibility with user-space tools using the Qualcomm diagnostic device (e.g. libqcdm). Signed-off-by: Daniele Palmas Reviewed-by: Loic Poulain Link: https://patch.msgid.link/20260724142909.3270824-3-dnlplm@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/wwan/wwan_core.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/net/wwan/wwan_core.c b/drivers/net/wwan/wwan_core.c index 8168239e52c3..ffbcf11e4e68 100644 --- a/drivers/net/wwan/wwan_core.c +++ b/drivers/net/wwan/wwan_core.c @@ -42,6 +42,7 @@ static struct dentry *wwan_debugfs_dir; /* WWAN port flags */ #define WWAN_PORT_TX_OFF 0 +#define WWAN_PORT_EXCLUSIVE 1 /** * struct wwan_device - The structure that defines a WWAN device @@ -748,6 +749,12 @@ static int wwan_port_op_start(struct wwan_port *port) goto out_unlock; } + if (test_bit(WWAN_PORT_EXCLUSIVE, &port->flags) && + !capable(CAP_SYS_ADMIN)) { + ret = -EBUSY; + goto out_unlock; + } + /* If port is already started, don't start again */ if (!port->start_count) ret = port->ops->start(port); @@ -769,6 +776,7 @@ static void wwan_port_op_stop(struct wwan_port *port) if (port->ops) port->ops->stop(port); skb_queue_purge(&port->rxq); + clear_bit(WWAN_PORT_EXCLUSIVE, &port->flags); } mutex_unlock(&port->ops_lock); } @@ -1031,6 +1039,22 @@ static long wwan_port_fops_at_ioctl(struct wwan_port *port, unsigned int cmd, break; } + case TIOCEXCL: + set_bit(WWAN_PORT_EXCLUSIVE, &port->flags); + break; + + case TIOCNXCL: + clear_bit(WWAN_PORT_EXCLUSIVE, &port->flags); + break; + + case TIOCGEXCL: + { + int excl = test_bit(WWAN_PORT_EXCLUSIVE, &port->flags); + + ret = put_user(excl, (int __user *)arg); + break; + } + default: ret = -ENOIOCTLCMD; } From ea771e17e995187b5e1debf81bf51949d3991469 Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Mon, 27 Jul 2026 11:18:33 +0200 Subject: [PATCH 0650/1433] ipv4: remove unnecessary reset of position pointer The position pointer is only advanced if the return value of the proc handler is positive at new_sync_write(). Therefore no need to manually reset it when doing error handling. Reviewed-by: Ido Schimmel Signed-off-by: Fernando Fernandez Mancera Link: https://patch.msgid.link/20260727091834.6645-1-fmancera@suse.de Signed-off-by: Paolo Abeni --- net/ipv4/devinet.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c index 3b31f4bec30e..47ded0f607d4 100644 --- a/net/ipv4/devinet.c +++ b/net/ipv4/devinet.c @@ -2607,10 +2607,9 @@ static int devinet_conf_proc(const struct ctl_table *ctl, int write, static int devinet_sysctl_forward(const struct ctl_table *ctl, int write, void *buffer, size_t *lenp, loff_t *ppos) { + struct net *net = ctl->extra2; int *valp = ctl->data; int val = *valp; - loff_t pos = *ppos; - struct net *net = ctl->extra2; int ret; if (write && !ns_capable(net->user_ns, CAP_NET_ADMIN)) @@ -2623,7 +2622,6 @@ static int devinet_sysctl_forward(const struct ctl_table *ctl, int write, if (!rtnl_net_trylock(net)) { /* Restore the original values before restarting */ *valp = val; - *ppos = pos; return restart_syscall(); } if (valp == &IPV4_DEVCONF_ALL(net, FORWARDING)) { From ac84c855180104e5d8928dc56a4895fb2207f5ff Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Mon, 27 Jul 2026 11:18:34 +0200 Subject: [PATCH 0651/1433] ipv6: remove unnecessary reset of position pointer The position pointer is only advanced if the return value of the proc handler is positive at new_sync_write(). Therefore no need to manually reset it when doing error handling. Reviewed-by: Ido Schimmel Signed-off-by: Fernando Fernandez Mancera Link: https://patch.msgid.link/20260727091834.6645-2-fmancera@suse.de Signed-off-by: Paolo Abeni --- net/ipv6/addrconf.c | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index f1fe9ede1edb..f6fa2715b450 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -6363,10 +6363,9 @@ static void ipv6_ifa_notify(int event, struct inet6_ifaddr *ifp) static int addrconf_sysctl_forward(const struct ctl_table *ctl, int write, void *buffer, size_t *lenp, loff_t *ppos) { + struct ctl_table lctl; int *valp = ctl->data; int val = *valp; - loff_t pos = *ppos; - struct ctl_table lctl; int ret; /* @@ -6382,8 +6381,6 @@ static int addrconf_sysctl_forward(const struct ctl_table *ctl, int write, if (write) ret = addrconf_fixup_forwarding(ctl, valp, val); - if (ret) - *ppos = pos; return ret; } @@ -6462,10 +6459,9 @@ static int addrconf_disable_ipv6(const struct ctl_table *table, int *p, int newf static int addrconf_sysctl_disable(const struct ctl_table *ctl, int write, void *buffer, size_t *lenp, loff_t *ppos) { + struct ctl_table lctl; int *valp = ctl->data; int val = *valp; - loff_t pos = *ppos; - struct ctl_table lctl; int ret; /* @@ -6481,8 +6477,6 @@ static int addrconf_sysctl_disable(const struct ctl_table *ctl, int write, if (write) ret = addrconf_disable_ipv6(ctl, valp, val); - if (ret) - *ppos = pos; return ret; } @@ -6667,10 +6661,9 @@ int addrconf_sysctl_ignore_routes_with_linkdown(const struct ctl_table *ctl, size_t *lenp, loff_t *ppos) { + struct ctl_table lctl; int *valp = ctl->data; int val = *valp; - loff_t pos = *ppos; - struct ctl_table lctl; int ret; /* ctl->data points to idev->cnf.ignore_routes_when_linkdown @@ -6685,8 +6678,6 @@ int addrconf_sysctl_ignore_routes_with_linkdown(const struct ctl_table *ctl, if (write) ret = addrconf_fixup_linkdown(ctl, valp, val); - if (ret) - *ppos = pos; return ret; } @@ -6767,10 +6758,9 @@ int addrconf_disable_policy(const struct ctl_table *ctl, int *valp, int val) static int addrconf_sysctl_disable_policy(const struct ctl_table *ctl, int write, void *buffer, size_t *lenp, loff_t *ppos) { + struct ctl_table lctl; int *valp = ctl->data; int val = *valp; - loff_t pos = *ppos; - struct ctl_table lctl; int ret; lctl = *ctl; @@ -6782,9 +6772,6 @@ static int addrconf_sysctl_disable_policy(const struct ctl_table *ctl, int write if (write && (*valp != val)) ret = addrconf_disable_policy(ctl, valp, val); - if (ret) - *ppos = pos; - return ret; } @@ -6816,7 +6803,6 @@ static int addrconf_sysctl_force_forwarding(const struct ctl_table *ctl, int wri int *valp = ctl->data; int new_val = *valp; int old_val = *valp; - loff_t pos = *ppos; int ret; tmp_ctl.extra1 = SYSCTL_ZERO; @@ -6852,8 +6838,6 @@ static int addrconf_sysctl_force_forwarding(const struct ctl_table *ctl, int wri rtnl_net_unlock(net); } - if (ret) - *ppos = pos; return ret; } From 0040ff44cd5b59f85ea2feac9ad6b6d9eed66833 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sat, 25 Jul 2026 22:41:40 +0200 Subject: [PATCH 0652/1433] net: airoha: rename airoha_priv_flags to airoha_dev_flags Rename the airoha_priv_flags enum to airoha_dev_flags and the AIROHA_PRIV_F_WAN flag to AIROHA_DEV_F_WAN. The "priv_flags" naming dates back to an earlier design that used ethtool private flags; since this series switched to tc qdisc offload for LAN/WAN configuration, align the naming to reflect that these are per-device flags rather than ethtool private flags. While at it, switch to test_bit()/set_bit()/clear_bit() APIs and convert the flags field from u32 to unsigned long to make flags manipulation atomic. Reviewed-by: Simon Horman Reviewed-by: Alexander Lobakin Reviewed-by: Jacob Keller Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260725-airoha-ethtool-priv_flags-v12-1-5136a30b2157@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/airoha/airoha_eth.c | 2 +- drivers/net/ethernet/airoha/airoha_eth.h | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c index 79418e682f71..d9a44a11d8db 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.c +++ b/drivers/net/ethernet/airoha/airoha_eth.c @@ -2078,7 +2078,7 @@ static int airoha_dev_init(struct net_device *netdev) fallthrough; case AIROHA_GDM2_IDX: /* GDM2 is always used as wan */ - dev->flags |= AIROHA_PRIV_F_WAN; + set_bit(AIROHA_DEV_F_WAN, &dev->flags); break; default: break; diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h index fe934f9ffe8a..24dd16fc509a 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.h +++ b/drivers/net/ethernet/airoha/airoha_eth.h @@ -570,8 +570,8 @@ struct airoha_qdma { DECLARE_BITMAP(qos_channel_map, AIROHA_NUM_QOS_CHANNELS); }; -enum airoha_priv_flags { - AIROHA_PRIV_F_WAN = BIT(0), +enum airoha_dev_flags { + AIROHA_DEV_F_WAN, }; struct airoha_gdm_dev { @@ -584,7 +584,7 @@ struct airoha_gdm_dev { u64 cpu_tx_packets; u64 fwd_tx_packets; - u32 flags; + unsigned long flags; int nbq; struct airoha_hw_stats stats; @@ -694,7 +694,7 @@ static inline u16 airoha_qdma_get_txq(struct airoha_qdma *qdma, u16 qid) static inline bool airoha_is_lan_gdm_dev(struct airoha_gdm_dev *dev) { - return !(dev->flags & AIROHA_PRIV_F_WAN); + return !test_bit(AIROHA_DEV_F_WAN, &dev->flags); } static inline bool airoha_is_7581(struct airoha_eth *eth) From 8b9a508819880c0c41d70f5b655bbd0baa231395 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sat, 25 Jul 2026 22:41:41 +0200 Subject: [PATCH 0653/1433] net: airoha: fix ETS QoS stats counter underflow and cross-channel corruption airoha_qdma_get_tx_ets_stats() has two bugs: - The hardware counters read via airoha_qdma_rr() are 32-bit values but are stored in u64 locals and subtracted from u64 baselines. When a 32-bit hardware counter wraps around, the subtraction produces a large underflow value passed to _bstats_update(). - The baseline counters (cpu_tx_packets, fwd_tx_packets) are stored as single per-device fields, but airoha_qdma_get_tx_ets_stats() is called with different channel values (0-3). Each call reads a different channel's hardware counter but overwrites the same baseline, corrupting the delta computation for other channels. Fix both by: - Narrowing the counter locals and baselines to u32 so that 32-bit unsigned subtraction handles wrap-around naturally. - Grouping the baselines into a per-channel qos_stats array so each channel tracks its own previous counter value independently. - Splitting the delta addition into two statements so the first u32 delta is widened to u64 on assignment and the second is added in u64 arithmetic, preventing overflow when both deltas are large. Fixes: 20bf7d07c956 ("net: airoha: Add sched ETS offload support") Reviewed-by: Simon Horman Reviewed-by: Alexander Lobakin Reviewed-by: Jacob Keller Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260725-airoha-ethtool-priv_flags-v12-2-5136a30b2157@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/airoha/airoha_eth.c | 18 +++++++++++------- drivers/net/ethernet/airoha/airoha_eth.h | 7 ++++--- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c index d9a44a11d8db..8165084daaf5 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.c +++ b/drivers/net/ethernet/airoha/airoha_eth.c @@ -2521,16 +2521,20 @@ static int airoha_qdma_get_tx_ets_stats(struct net_device *netdev, int channel, { struct airoha_gdm_dev *dev = netdev_priv(netdev); struct airoha_qdma *qdma = dev->qdma; + u32 cpu_tx_packets, fwd_tx_packets; + u64 tx_packets; - u64 cpu_tx_packets = airoha_qdma_rr(qdma, REG_CNTR_VAL(channel << 1)); - u64 fwd_tx_packets = airoha_qdma_rr(qdma, - REG_CNTR_VAL((channel << 1) + 1)); - u64 tx_packets = (cpu_tx_packets - dev->cpu_tx_packets) + - (fwd_tx_packets - dev->fwd_tx_packets); + cpu_tx_packets = airoha_qdma_rr(qdma, REG_CNTR_VAL(channel << 1)); + fwd_tx_packets = airoha_qdma_rr(qdma, + REG_CNTR_VAL((channel << 1) + 1)); + tx_packets = (u32)(cpu_tx_packets - + dev->qos_stats[channel].cpu_tx_packets); + tx_packets += (u32)(fwd_tx_packets - + dev->qos_stats[channel].fwd_tx_packets); _bstats_update(opt->stats.bstats, 0, tx_packets); - dev->cpu_tx_packets = cpu_tx_packets; - dev->fwd_tx_packets = fwd_tx_packets; + dev->qos_stats[channel].cpu_tx_packets = cpu_tx_packets; + dev->qos_stats[channel].fwd_tx_packets = fwd_tx_packets; return 0; } diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h index 24dd16fc509a..447a5a9552bb 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.h +++ b/drivers/net/ethernet/airoha/airoha_eth.h @@ -580,9 +580,10 @@ struct airoha_gdm_dev { struct airoha_eth *eth; DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS); - /* qos stats counters */ - u64 cpu_tx_packets; - u64 fwd_tx_packets; + struct { + u32 cpu_tx_packets; + u32 fwd_tx_packets; + } qos_stats[AIROHA_NUM_QOS_CHANNELS]; unsigned long flags; int nbq; From 78a35725e533eef0afe83f6f96cffd69be9eaac7 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sat, 25 Jul 2026 22:41:42 +0200 Subject: [PATCH 0654/1433] net: airoha: defer GDM3/GDM4 WAN mode and GDM2 loopback to QoS offload GDM3 and GDM4 ports require GDM2 loopback to be enabled for hardware QoS offload to function. Without it, HTB and ETS offload on these ports do not work. Previously, GDM3/GDM4 ports were automatically configured as WAN with GDM2 loopback enabled during ndo_init(). Add the capability to configure GDM3/GDM4 as WAN/LAN on demand when QoS offload is created or destroyed. Hook airoha_enable_qos_for_gdm34() into TC_HTB_CREATE so that requesting HTB offload on a GDM3/GDM4 LAN port switches it to WAN mode and enables GDM2 loopback. Introduce the AIROHA_DEV_F_TX_QOS flag to track whether a device has an active TX HTB qdisc; set it in airoha_tc_setup_qdisc_htb() on successful TC_HTB_CREATE and clear it on TC_HTB_DESTROY. The device keeps its WAN role after qdisc teardown so that its configuration is preserved until another device explicitly needs the WAN role for QoS offload. If another GDM3/GDM4 device already holds the WAN role without an active TX qdisc, demote it to LAN before promoting the requesting device. Skip the demotion when the requesting device is itself already the WAN device. Since airoha_dev_set_qdma() can now be called on a running device to migrate between QDMA blocks, make dev->qdma an RCU pointer so the TX path can safely dereference it without holding RTNL. When migrating between QDMA blocks, stop TX queues, swap the pointer via rcu_assign_pointer(), then synchronize_rcu() to ensure no in-flight ndo_start_xmit holds the old pointer. Serialize netdev_tx_completed_queue() calls via per-TX-queue spinlocks txq_lock[] so that both the old and new QDMA NAPI instances can complete packets concurrently on the same netdev TX queues without racing on dql_completed(). Hold flow_offload_mutex in airoha_enable_qos_for_gdm34() and airoha_disable_qos_for_gdm34() around the dev->flags update, airoha_dev_set_qdma() and GDM2 loopback configuration, serializing against concurrent airoha_ppe_hw_init() in the TC_SETUP_CLSFLOWER offload path. Introduce airoha_qdma_deref() helper that wraps rcu_dereference_protected() with a lockdep condition accepting either rtnl_lock or flow_offload_mutex, and use it across all control-path dereferences of the RCU-protected dev->qdma pointer. Add airoha_disable_gdm2_loopback() to disable GDM2 hw loopback. Tested-by: Madhur Agarwal Reviewed-by: Alexander Lobakin Reviewed-by: Simon Horman Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260725-airoha-ethtool-priv_flags-v12-3-5136a30b2157@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/airoha/airoha_eth.c | 245 +++++++++++++++++++--- drivers/net/ethernet/airoha/airoha_eth.h | 18 +- drivers/net/ethernet/airoha/airoha_ppe.c | 9 +- drivers/net/ethernet/airoha/airoha_regs.h | 1 + 4 files changed, 243 insertions(+), 30 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c index 8165084daaf5..dba7c52c0896 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.c +++ b/drivers/net/ethernet/airoha/airoha_eth.c @@ -934,7 +934,7 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q) if (!dev) continue; - if (dev->qdma != qdma) + if (rcu_access_pointer(dev->qdma) != qdma) continue; netdev = netdev_from_priv(dev); @@ -1038,10 +1038,16 @@ static int airoha_qdma_tx_napi_poll(struct napi_struct *napi, int budget) q->queued--; if (skb) { + struct airoha_gdm_dev *dev = netdev_priv(skb->dev); + u16 qidx = skb_get_queue_mapping(skb); struct netdev_queue *txq; txq = skb_get_tx_queue(skb->dev, skb); + + spin_lock_bh(&dev->txq_lock[qidx]); netdev_tx_completed_queue(txq, 1, skb->len); + spin_unlock_bh(&dev->txq_lock[qidx]); + dev_kfree_skb_any(skb); } @@ -1905,8 +1911,8 @@ static int airoha_dev_open(struct net_device *netdev) { struct airoha_gdm_dev *dev = netdev_priv(netdev); struct airoha_gdm_port *port = dev->port; - struct airoha_qdma *qdma = dev->qdma; u32 pse_port = FE_PSE_PORT_PPE1; + struct airoha_qdma *qdma; int err; netif_tx_start_all_queues(netdev); @@ -1914,6 +1920,7 @@ static int airoha_dev_open(struct net_device *netdev) if (err) return err; + qdma = airoha_qdma_deref(dev); if (netdev_uses_dsa(netdev)) airoha_fe_set(qdma->eth, REG_GDM_INGRESS_CFG(port->id), GDM_STAG_EN_MASK); @@ -1937,7 +1944,6 @@ static int airoha_dev_stop(struct net_device *netdev) { struct airoha_gdm_dev *dev = netdev_priv(netdev); struct airoha_gdm_port *port = dev->port; - struct airoha_qdma *qdma = dev->qdma; netif_tx_disable(netdev); airoha_set_vip_for_gdm_port(dev, false); @@ -1945,7 +1951,7 @@ static int airoha_dev_stop(struct net_device *netdev) if (--port->users) airoha_ppe_set_xmit_frame_size(dev); else - airoha_set_gdm_port_fwd_cfg(qdma->eth, + airoha_set_gdm_port_fwd_cfg(dev->eth, REG_GDM_FWD_CFG(port->id), FE_PSE_PORT_DROP); return 0; @@ -2028,6 +2034,53 @@ static int airoha_enable_gdm2_loopback(struct airoha_gdm_dev *dev) return 0; } +static int airoha_disable_gdm2_loopback(struct airoha_gdm_dev *dev) +{ + struct airoha_gdm_port *port = dev->port; + struct airoha_eth *eth = dev->eth; + int i, src_port; + u32 pse_port; + + src_port = eth->soc->ops.get_sport(dev->port, dev->nbq); + if (src_port < 0) + return src_port; + + airoha_fe_clear(eth, + REG_SP_DFT_CPORT(src_port >> fls(SP_CPORT_DFT_MASK)), + SP_CPORT_MASK(src_port & SP_CPORT_DFT_MASK)); + + airoha_fe_set(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX), + GDM_STRIP_CRC_MASK); + airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX), + FE_PSE_PORT_DROP); + airoha_fe_clear(eth, REG_GDM_LPBK_CFG(AIROHA_GDM2_IDX), + LPBK_CHAN_MASK | LPBK_MODE_MASK | LPBK_EN_MASK); + pse_port = airoha_ppe_is_enabled(eth, 1) ? FE_PSE_PORT_PPE2 + : FE_PSE_PORT_PPE1; + airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX), + pse_port); + + airoha_fe_rmw(eth, REG_FE_WAN_PORT, WAN0_MASK, + FIELD_PREP(WAN0_MASK, AIROHA_GDM2_IDX)); + + for (i = 0; i < eth->soc->num_ppe; i++) + airoha_fe_clear(eth, REG_PPE_DFT_CPORT(i, AIROHA_GDM2_IDX), + DFT_CPORT_MASK(AIROHA_GDM2_IDX)); + + /* Enable VIP and IFC for GDM2 */ + airoha_fe_set(eth, REG_FE_VIP_PORT_EN, BIT(AIROHA_GDM2_IDX)); + airoha_fe_set(eth, REG_FE_IFC_PORT_EN, BIT(AIROHA_GDM2_IDX)); + + if (port->id == AIROHA_GDM4_IDX && airoha_is_7581(eth)) { + u32 mask = FC_ID_OF_SRC_PORT_MASK(dev->nbq); + + airoha_fe_rmw(eth, REG_SRC_PORT_FC_MAP6, mask, + FC_MAP6_DEF_VALUE & mask); + } + + return 0; +} + static struct airoha_gdm_dev * airoha_get_wan_gdm_dev(struct airoha_eth *eth) { @@ -2054,15 +2107,37 @@ airoha_get_wan_gdm_dev(struct airoha_eth *eth) static void airoha_dev_set_qdma(struct airoha_gdm_dev *dev) { struct net_device *netdev = netdev_from_priv(dev); + struct airoha_qdma *cur_qdma, *qdma; struct airoha_eth *eth = dev->eth; - int ppe_id; + int i, ppe_id; /* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */ - dev->qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)]; - netdev->irq = dev->qdma->irq_banks[0].irq; + qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)]; + cur_qdma = airoha_qdma_deref(dev); + + if (cur_qdma) + netif_tx_stop_all_queues(netdev); + + rcu_assign_pointer(dev->qdma, qdma); + netdev->irq = qdma->irq_banks[0].irq; + synchronize_rcu(); ppe_id = !airoha_is_lan_gdm_dev(dev) && airoha_ppe_is_enabled(eth, 1); airoha_ppe_set_cpu_port(dev, ppe_id, airoha_get_fe_port(dev)); + + /* Seed qos_stats baselines with the new QDMA block's current + * counter values to avoid a spurious spike on the first stats + * query after init or migration. + */ + for (i = 0; i < ARRAY_SIZE(dev->qos_stats); i++) { + dev->qos_stats[i].cpu_tx_packets = + airoha_qdma_rr(qdma, REG_CNTR_VAL(i << 1)); + dev->qos_stats[i].fwd_tx_packets = + airoha_qdma_rr(qdma, REG_CNTR_VAL((i << 1) + 1)); + } + + if (cur_qdma) + netif_tx_wake_all_queues(netdev); } static int airoha_dev_init(struct net_device *netdev) @@ -2217,9 +2292,9 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb, struct net_device *netdev) { struct airoha_gdm_dev *dev = netdev_priv(netdev); - struct airoha_qdma *qdma = dev->qdma; u32 nr_frags, tag, msg0, msg1, len; struct airoha_queue_entry *e; + struct airoha_qdma *qdma; struct netdev_queue *txq; struct airoha_queue *q; LIST_HEAD(tx_list); @@ -2228,6 +2303,8 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb, u16 index; u8 fport; + rcu_read_lock(); + qdma = rcu_dereference(dev->qdma); qid = airoha_qdma_get_txq(qdma, skb_get_queue_mapping(skb)); tag = airoha_get_dsa_tag(skb, netdev); @@ -2277,6 +2354,8 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb, netif_tx_stop_queue(txq); q->txq_stopped = true; spin_unlock_bh(&q->lock); + rcu_read_unlock(); + return NETDEV_TX_BUSY; } @@ -2342,6 +2421,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb, FIELD_PREP(TX_RING_CPU_IDX_MASK, index)); spin_unlock_bh(&q->lock); + rcu_read_unlock(); return NETDEV_TX_OK; @@ -2354,6 +2434,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb, error: dev_kfree_skb_any(skb); netdev->stats.tx_dropped++; + rcu_read_unlock(); return NETDEV_TX_OK; } @@ -2433,17 +2514,19 @@ static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev, const u16 *weights, u8 n_weights) { struct airoha_gdm_dev *dev = netdev_priv(netdev); + struct airoha_qdma *qdma; int i; + qdma = airoha_qdma_deref(dev); for (i = 0; i < AIROHA_NUM_QOS_QUEUES; i++) - airoha_qdma_clear(dev->qdma, REG_QUEUE_CLOSE_CFG(channel), + airoha_qdma_clear(qdma, REG_QUEUE_CLOSE_CFG(channel), TXQ_DISABLE_CHAN_QUEUE_MASK(channel, i)); for (i = 0; i < n_weights; i++) { u32 status; int err; - airoha_qdma_wr(dev->qdma, REG_TXWRR_WEIGHT_CFG, + airoha_qdma_wr(qdma, REG_TXWRR_WEIGHT_CFG, TWRR_RW_CMD_MASK | FIELD_PREP(TWRR_CHAN_IDX_MASK, channel) | FIELD_PREP(TWRR_QUEUE_IDX_MASK, i) | @@ -2451,12 +2534,12 @@ static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev, err = read_poll_timeout(airoha_qdma_rr, status, status & TWRR_RW_CMD_DONE, USEC_PER_MSEC, 10 * USEC_PER_MSEC, - true, dev->qdma, REG_TXWRR_WEIGHT_CFG); + true, qdma, REG_TXWRR_WEIGHT_CFG); if (err) return err; } - airoha_qdma_rmw(dev->qdma, REG_CHAN_QOS_MODE(channel >> 3), + airoha_qdma_rmw(qdma, REG_CHAN_QOS_MODE(channel >> 3), CHAN_QOS_MODE_MASK(channel), __field_prep(CHAN_QOS_MODE_MASK(channel), mode)); @@ -2520,10 +2603,11 @@ static int airoha_qdma_get_tx_ets_stats(struct net_device *netdev, int channel, struct tc_ets_qopt_offload *opt) { struct airoha_gdm_dev *dev = netdev_priv(netdev); - struct airoha_qdma *qdma = dev->qdma; u32 cpu_tx_packets, fwd_tx_packets; + struct airoha_qdma *qdma; u64 tx_packets; + qdma = airoha_qdma_deref(dev); cpu_tx_packets = airoha_qdma_rr(qdma, REG_CNTR_VAL(channel << 1)); fwd_tx_packets = airoha_qdma_rr(qdma, REG_CNTR_VAL((channel << 1) + 1)); @@ -2789,16 +2873,18 @@ static int airoha_qdma_set_tx_rate_limit(struct net_device *netdev, u32 bucket_size) { struct airoha_gdm_dev *dev = netdev_priv(netdev); + struct airoha_qdma *qdma; int i, err; + qdma = airoha_qdma_deref(dev); for (i = 0; i <= TRTCM_PEAK_MODE; i++) { - err = airoha_qdma_set_trtcm_config(dev->qdma, channel, + err = airoha_qdma_set_trtcm_config(qdma, channel, REG_EGRESS_TRTCM_CFG, i, !!rate, TRTCM_METER_MODE); if (err) return err; - err = airoha_qdma_set_trtcm_token_bucket(dev->qdma, channel, + err = airoha_qdma_set_trtcm_token_bucket(qdma, channel, REG_EGRESS_TRTCM_CFG, i, rate, bucket_size); if (err) @@ -2834,11 +2920,12 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev, u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS; int err, num_tx_queues = AIROHA_NUM_TX_RING + channel + 1; struct airoha_gdm_dev *dev = netdev_priv(netdev); - struct airoha_qdma *qdma = dev->qdma; + struct airoha_qdma *qdma; /* Here we need to check the requested QDMA channel is not already * in use by another net_device running on the same QDMA block. */ + qdma = airoha_qdma_deref(dev); if (test_and_set_bit(channel, qdma->qos_channel_map)) { NL_SET_ERR_MSG_MOD(opt->extack, "qdma qos channel already in use"); @@ -2874,7 +2961,7 @@ static int airoha_qdma_set_rx_meter(struct airoha_gdm_dev *dev, u32 rate, u32 bucket_size, enum trtcm_unit_type unit_type) { - struct airoha_qdma *qdma = dev->qdma; + struct airoha_qdma *qdma = airoha_qdma_deref(dev); int i; for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) { @@ -3049,10 +3136,11 @@ static void airoha_tc_remove_htb_queue(struct net_device *netdev, int queue) { struct airoha_gdm_dev *dev = netdev_priv(netdev); int num_tx_queues = AIROHA_NUM_TX_RING; - struct airoha_qdma *qdma = dev->qdma; + struct airoha_qdma *qdma; airoha_qdma_set_tx_rate_limit(netdev, queue, 0, 0); + qdma = airoha_qdma_deref(dev); clear_bit(queue, qdma->qos_channel_map); clear_bit(queue, dev->qos_sq_bmap); @@ -3078,6 +3166,98 @@ static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev, return 0; } +static void airoha_disable_qos_for_gdm34(struct net_device *netdev) +{ + struct airoha_gdm_dev *dev = netdev_priv(netdev); + struct airoha_gdm_port *port = dev->port; + int err; + + if (port->id != AIROHA_GDM3_IDX && + port->id != AIROHA_GDM4_IDX) + return; + + err = airoha_disable_gdm2_loopback(dev); + if (err) + netdev_warn(netdev, + "failed disabling GDM2 loopback: %d\n", err); + + clear_bit(AIROHA_DEV_F_WAN, &dev->flags); + airoha_dev_set_qdma(dev); + + airoha_set_macaddr(dev, netdev->dev_addr); + airoha_ppe_set_xmit_frame_size(dev); + + if (netif_running(netdev)) + airoha_set_gdm_port_fwd_cfg(dev->eth, + REG_GDM_FWD_CFG(port->id), + FE_PSE_PORT_PPE1); +} + +static int airoha_enable_qos_for_gdm34(struct net_device *netdev, + struct netlink_ext_ack *extack) +{ + struct airoha_gdm_dev *wan_dev, *dev = netdev_priv(netdev); + struct airoha_gdm_port *port = dev->port; + struct airoha_eth *eth = dev->eth; + int err = -EBUSY; + + if (port->id != AIROHA_GDM3_IDX && + port->id != AIROHA_GDM4_IDX) { + /* HW QoS is always supported by GDM1 and GDM2 */ + return 0; + } + + if (!airoha_is_lan_gdm_dev(dev)) /* Already enabled */ + return 0; + + mutex_lock(&flow_offload_mutex); + + wan_dev = airoha_get_wan_gdm_dev(eth); + if (wan_dev) { + if (test_bit(AIROHA_DEV_F_TX_QOS, &wan_dev->flags) || + wan_dev->port->id == AIROHA_GDM2_IDX) { + NL_SET_ERR_MSG_MOD(extack, + "QoS configured for WAN device"); + goto error_unlock; + } + airoha_disable_qos_for_gdm34(netdev_from_priv(wan_dev)); + } + + set_bit(AIROHA_DEV_F_WAN, &dev->flags); + airoha_dev_set_qdma(dev); + err = airoha_enable_gdm2_loopback(dev); + if (err) + goto error_disable_wan; + + err = airoha_set_macaddr(dev, netdev->dev_addr); + if (err) + goto error_disable_loopback; + + airoha_dev_set_xmit_frame_size(netdev); + if (netif_running(netdev)) { + u32 pse_port; + + pse_port = airoha_ppe_is_enabled(eth, 1) ? FE_PSE_PORT_PPE2 + : FE_PSE_PORT_PPE1; + airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(port->id), + pse_port); + } + + mutex_unlock(&flow_offload_mutex); + + return 0; + +error_disable_loopback: + airoha_disable_gdm2_loopback(dev); +error_disable_wan: + clear_bit(AIROHA_DEV_F_WAN, &dev->flags); + airoha_dev_set_qdma(dev); +error_unlock: + mutex_unlock(&flow_offload_mutex); + + return err; +} + static int airoha_tc_htb_destroy(struct net_device *netdev) { struct airoha_gdm_dev *dev = netdev_priv(netdev); @@ -3086,6 +3266,8 @@ static int airoha_tc_htb_destroy(struct net_device *netdev) for_each_set_bit(q, dev->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS) airoha_tc_remove_htb_queue(netdev, q); + clear_bit(AIROHA_DEV_F_TX_QOS, &dev->flags); + return 0; } @@ -3105,24 +3287,33 @@ static int airoha_tc_get_htb_get_leaf_queue(struct net_device *netdev, return 0; } -static int airoha_tc_setup_qdisc_htb(struct net_device *dev, +static int airoha_tc_setup_qdisc_htb(struct net_device *netdev, struct tc_htb_qopt_offload *opt) { switch (opt->command) { - case TC_HTB_CREATE: + case TC_HTB_CREATE: { + struct airoha_gdm_dev *dev = netdev_priv(netdev); + int err; + + err = airoha_enable_qos_for_gdm34(netdev, opt->extack); + if (err) + return err; + + set_bit(AIROHA_DEV_F_TX_QOS, &dev->flags); break; + } case TC_HTB_DESTROY: - return airoha_tc_htb_destroy(dev); + return airoha_tc_htb_destroy(netdev); case TC_HTB_NODE_MODIFY: - return airoha_tc_htb_modify_queue(dev, opt); + return airoha_tc_htb_modify_queue(netdev, opt); case TC_HTB_LEAF_ALLOC_QUEUE: - return airoha_tc_htb_alloc_leaf_queue(dev, opt); + return airoha_tc_htb_alloc_leaf_queue(netdev, opt); case TC_HTB_LEAF_DEL: case TC_HTB_LEAF_DEL_LAST: case TC_HTB_LEAF_DEL_LAST_FORCE: - return airoha_tc_htb_delete_leaf_queue(dev, opt); + return airoha_tc_htb_delete_leaf_queue(netdev, opt); case TC_HTB_LEAF_QUERY_QUEUE: - return airoha_tc_get_htb_get_leaf_queue(dev, opt); + return airoha_tc_get_htb_get_leaf_queue(netdev, opt); default: return -EOPNOTSUPP; } @@ -3224,8 +3415,8 @@ static int airoha_alloc_gdm_device(struct airoha_eth *eth, { struct net_device *netdev; struct airoha_gdm_dev *dev; + int err, i; u8 index; - int err; netdev = devm_alloc_etherdev_mqs(eth->dev, sizeof(*dev), AIROHA_NUM_NETDEV_TX_RINGS, @@ -3276,6 +3467,8 @@ static int airoha_alloc_gdm_device(struct airoha_eth *eth, netdev->dev.of_node = of_node_get(np); dev = netdev_priv(netdev); u64_stats_init(&dev->stats.syncp); + for (i = 0; i < ARRAY_SIZE(dev->txq_lock); i++) + spin_lock_init(&dev->txq_lock[i]); dev->port = port; dev->eth = eth; dev->nbq = nbq; diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h index 447a5a9552bb..fa9a8edce22f 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.h +++ b/drivers/net/ethernet/airoha/airoha_eth.h @@ -572,11 +572,12 @@ struct airoha_qdma { enum airoha_dev_flags { AIROHA_DEV_F_WAN, + AIROHA_DEV_F_TX_QOS, }; struct airoha_gdm_dev { + struct airoha_qdma __rcu *qdma; struct airoha_gdm_port *port; - struct airoha_qdma *qdma; struct airoha_eth *eth; DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS); @@ -589,6 +590,11 @@ struct airoha_gdm_dev { int nbq; struct airoha_hw_stats stats; + + /* Serialize netdev_tx_completed_queue() calls per TX queue during + * QDMA migration. + */ + spinlock_t txq_lock[AIROHA_NUM_NETDEV_TX_RINGS]; }; struct airoha_gdm_port { @@ -712,6 +718,16 @@ int airoha_get_fe_port(struct airoha_gdm_dev *dev); bool airoha_is_valid_gdm_dev(struct airoha_eth *eth, struct airoha_gdm_dev *dev); +extern struct mutex flow_offload_mutex; + +static inline struct airoha_qdma * +airoha_qdma_deref(struct airoha_gdm_dev *dev) +{ + return rcu_dereference_protected(dev->qdma, + lockdep_rtnl_is_held() || + lockdep_is_held(&flow_offload_mutex)); +} + void airoha_ppe_set_xmit_frame_size(struct airoha_gdm_dev *dev); void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport); bool airoha_ppe_is_enabled(struct airoha_eth *eth, int index); diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c index e4c3644dd6ec..33ddf0d07855 100644 --- a/drivers/net/ethernet/airoha/airoha_ppe.c +++ b/drivers/net/ethernet/airoha/airoha_ppe.c @@ -15,7 +15,10 @@ #include "airoha_regs.h" #include "airoha_eth.h" -static DEFINE_MUTEX(flow_offload_mutex); +/* Serialize airoha_gdm_dev flags, QDMA pointer and PPE CPU port + * configuration. + */ +DEFINE_MUTEX(flow_offload_mutex); static DEFINE_SPINLOCK(ppe_lock); static const struct rhashtable_params airoha_flow_table_params = { @@ -86,8 +89,8 @@ static u32 airoha_ppe_get_timestamp(struct airoha_ppe *ppe) void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport) { - struct airoha_qdma *qdma = dev->qdma; - struct airoha_eth *eth = qdma->eth; + struct airoha_qdma *qdma = airoha_qdma_deref(dev); + struct airoha_eth *eth = dev->eth; u8 qdma_id = qdma - ð->qdma[0]; u32 fe_cpu_port; diff --git a/drivers/net/ethernet/airoha/airoha_regs.h b/drivers/net/ethernet/airoha/airoha_regs.h index 6fed63d013b4..442b48c9b991 100644 --- a/drivers/net/ethernet/airoha/airoha_regs.h +++ b/drivers/net/ethernet/airoha/airoha_regs.h @@ -375,6 +375,7 @@ #define REG_SRC_PORT_FC_MAP6 0x2298 #define FC_ID_OF_SRC_PORT_MASK(_n) GENMASK(4 + ((_n) << 3), ((_n) << 3)) +#define FC_MAP6_DEF_VALUE 0x1b1a1918 #define REG_WAN_MTU0 0x2300 #define WAN_MTU1_MASK GENMASK(29, 16) From d93608b0a20e7e6ce51bfb5671c561fa07a60e81 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 24 Jul 2026 08:04:09 -0700 Subject: [PATCH 0655/1433] netpoll: export carrier_timeout via netpoll_get_carrier_timeout() netpoll_wait_carrier() is only used in netconsole, and it will move to netconsole. The carrier_timeout module parameter has to stay in netpoll so the existing netpoll.carrier_timeout kernel parameter keeps working for current users, and we don't break user compatibility. Add a netpoll_get_carrier_timeout() accessor and export it so netconsole can read the value once the carrier wait lives there. Drop the now redundant timeout argument from netpoll_wait_carrier() (its only caller passed carrier_timeout) and read the parameter directly while the helper still lives here. Signed-off-by: Breno Leitao Reviewed-by: Gustavo Luiz Duarte Link: https://patch.msgid.link/20260724-netconsole_move_more_final-v1-1-a5f7691db81c@debian.org Signed-off-by: Paolo Abeni --- include/linux/netpoll.h | 1 + net/core/netpoll.c | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index 79315461a7b1..d426d1844cac 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -72,6 +72,7 @@ void netpoll_cleanup(struct netpoll *np); void do_netpoll_cleanup(struct netpoll *np); netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb); void netpoll_zap_completion_queue(void); +unsigned int netpoll_get_carrier_timeout(void); #ifdef CONFIG_NETPOLL static inline void *netpoll_poll_lock(struct napi_struct *napi) diff --git a/net/core/netpoll.c b/net/core/netpoll.c index f8da1048ea3a..9b9fcc307958 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -38,9 +38,20 @@ #define USEC_PER_POLL 50 +/* + * carrier_timeout is netconsole-specific and only kept here to preserve the + * netpoll.carrier_timeout module-parameter ABI. Its value is exposed to + * netconsole through netpoll_get_carrier_timeout(). + */ static unsigned int carrier_timeout = 4; module_param(carrier_timeout, uint, 0644); +unsigned int netpoll_get_carrier_timeout(void) +{ + return carrier_timeout; +} +EXPORT_SYMBOL_GPL(netpoll_get_carrier_timeout); + static netdev_tx_t netpoll_start_xmit(struct sk_buff *skb, struct net_device *dev, struct netdev_queue *txq) @@ -397,12 +408,11 @@ static char *egress_dev(struct netpoll *np, char *buf, size_t bufsz) return buf; } -static void netpoll_wait_carrier(struct netpoll *np, struct net_device *ndev, - unsigned int timeout) +static void netpoll_wait_carrier(struct netpoll *np, struct net_device *ndev) { unsigned long atmost; - atmost = jiffies + timeout * HZ; + atmost = jiffies + carrier_timeout * HZ; while (!netif_carrier_ok(ndev)) { if (time_after(jiffies, atmost)) { np_notice(np, "timeout waiting for carrier\n"); @@ -539,7 +549,7 @@ int netpoll_setup(struct netpoll *np) } rtnl_unlock(); - netpoll_wait_carrier(np, ndev, carrier_timeout); + netpoll_wait_carrier(np, ndev); rtnl_lock(); } From bb996303efae1240b364ca7658b11473b1ce8a2d Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 24 Jul 2026 08:04:10 -0700 Subject: [PATCH 0656/1433] netpoll: export the netpoll_setup() helpers for netconsole Temporarily export leaf functions that will be moved to netconsole. The upcoming patch will move the setup function to netconsole, and continue to call these leaf functions here in netpoll, then other patches will move these exports functions to netconsole (and make them statics). In summary, these exports are temporary in order to make the patchset digestible. Signed-off-by: Breno Leitao Reviewed-by: Gustavo Luiz Duarte Link: https://patch.msgid.link/20260724-netconsole_move_more_final-v1-2-a5f7691db81c@debian.org Signed-off-by: Paolo Abeni --- include/linux/netpoll.h | 5 +++++ net/core/netpoll.c | 15 ++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index d426d1844cac..0877515fa744 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -73,6 +73,11 @@ void do_netpoll_cleanup(struct netpoll *np); netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb); void netpoll_zap_completion_queue(void); unsigned int netpoll_get_carrier_timeout(void); +void netpoll_wait_carrier(struct netpoll *np, struct net_device *ndev); +char *egress_dev(struct netpoll *np, char *buf, size_t bufsz); +int netpoll_take_ipv4(struct netpoll *np, struct net_device *ndev); +int netpoll_take_ipv6(struct netpoll *np, struct net_device *ndev); +bool netpoll_local_ip_unset(const struct netpoll *np); #ifdef CONFIG_NETPOLL static inline void *netpoll_poll_lock(struct napi_struct *napi) diff --git a/net/core/netpoll.c b/net/core/netpoll.c index 9b9fcc307958..6a545063223b 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -399,7 +399,7 @@ EXPORT_SYMBOL_GPL(__netpoll_setup); * be at least MAC_ADDR_STR_LEN + 1 to fit the formatted MAC address * and its NUL terminator. */ -static char *egress_dev(struct netpoll *np, char *buf, size_t bufsz) +char *egress_dev(struct netpoll *np, char *buf, size_t bufsz) { if (np->dev_name[0]) return np->dev_name; @@ -407,8 +407,9 @@ static char *egress_dev(struct netpoll *np, char *buf, size_t bufsz) snprintf(buf, bufsz, "%pM", np->dev_mac); return buf; } +EXPORT_SYMBOL_GPL(egress_dev); -static void netpoll_wait_carrier(struct netpoll *np, struct net_device *ndev) +void netpoll_wait_carrier(struct netpoll *np, struct net_device *ndev) { unsigned long atmost; @@ -421,11 +422,12 @@ static void netpoll_wait_carrier(struct netpoll *np, struct net_device *ndev) msleep(1); } } +EXPORT_SYMBOL_GPL(netpoll_wait_carrier); /* * Take the IPv6 from ndev and populate local_ip structure in netpoll */ -static int netpoll_take_ipv6(struct netpoll *np, struct net_device *ndev) +int netpoll_take_ipv6(struct netpoll *np, struct net_device *ndev) { char buf[MAC_ADDR_STR_LEN + 1]; int err = -EDESTADDRREQ; @@ -462,11 +464,12 @@ static int netpoll_take_ipv6(struct netpoll *np, struct net_device *ndev) np_info(np, "local IPv6 %pI6c\n", &np->local_ip.in6); return 0; } +EXPORT_SYMBOL_GPL(netpoll_take_ipv6); /* * Take the IPv4 from ndev and populate local_ip structure in netpoll */ -static int netpoll_take_ipv4(struct netpoll *np, struct net_device *ndev) +int netpoll_take_ipv4(struct netpoll *np, struct net_device *ndev) { char buf[MAC_ADDR_STR_LEN + 1]; const struct in_ifaddr *ifa; @@ -491,6 +494,7 @@ static int netpoll_take_ipv4(struct netpoll *np, struct net_device *ndev) return 0; } +EXPORT_SYMBOL_GPL(netpoll_take_ipv4); /* * Test whether the caller left np->local_ip unset, so that @@ -502,12 +506,13 @@ static int netpoll_take_ipv4(struct netpoll *np, struct net_device *ndev) * doing so would misclassify a caller-supplied address as unset and * silently overwrite it with whatever address the device exposes. */ -static bool netpoll_local_ip_unset(const struct netpoll *np) +bool netpoll_local_ip_unset(const struct netpoll *np) { if (np->ipv6) return ipv6_addr_any(&np->local_ip.in6); return !np->local_ip.ip; } +EXPORT_SYMBOL_GPL(netpoll_local_ip_unset); int netpoll_setup(struct netpoll *np) { From 672ecd3bb145ac3b6e050e7fbdcaff0b46115c96 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 24 Jul 2026 08:04:11 -0700 Subject: [PATCH 0657/1433] netconsole: take over netpoll_setup() from netpoll netpoll_setup() is only used by netconsole. All the other users use __netpoll_setup(). Move netpoll_setup() to netconsole, and rename it to netcons_netpoll_setup(). Pure code motion: the body is unchanged. Signed-off-by: Breno Leitao Reviewed-by: Gustavo Luiz Duarte Link: https://patch.msgid.link/20260724-netconsole_move_more_final-v1-3-a5f7691db81c@debian.org Signed-off-by: Paolo Abeni --- drivers/net/netconsole.c | 86 ++++++++++++++++++++++++++++++++++++++-- include/linux/netpoll.h | 1 - net/core/netpoll.c | 81 ------------------------------------- 3 files changed, 83 insertions(+), 85 deletions(-) diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 7f8851a734fc..3a7a2cafc627 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -351,6 +351,86 @@ static void netconsole_skb_pool_flush(struct netconsole_target *nt) skb_queue_purge_reason(&nt->skb_pool, SKB_CONSUMED); } +static int netcons_netpoll_setup(struct netpoll *np) +{ + struct net *net = current->nsproxy->net_ns; + char buf[MAC_ADDR_STR_LEN + 1]; + struct net_device *ndev = NULL; + bool ip_overwritten = false; + int err; + + rtnl_lock(); + if (np->dev_name[0]) + ndev = __dev_get_by_name(net, np->dev_name); + else if (is_valid_ether_addr(np->dev_mac)) + ndev = dev_getbyhwaddr(net, ARPHRD_ETHER, np->dev_mac); + + if (!ndev) { + np_err(np, "%s doesn't exist, aborting\n", + egress_dev(np, buf, sizeof(buf))); + err = -ENODEV; + goto unlock; + } + netdev_hold(ndev, &np->dev_tracker, GFP_KERNEL); + + if (netdev_master_upper_dev_get(ndev)) { + np_err(np, "%s is a slave device, aborting\n", + egress_dev(np, buf, sizeof(buf))); + err = -EBUSY; + goto put; + } + + if (!netif_running(ndev)) { + np_info(np, "device %s not up yet, forcing it\n", + egress_dev(np, buf, sizeof(buf))); + + err = dev_open(ndev, NULL); + if (err) { + np_err(np, "failed to open %s\n", ndev->name); + goto put; + } + + rtnl_unlock(); + netpoll_wait_carrier(np, ndev); + rtnl_lock(); + } + + if (netpoll_local_ip_unset(np)) { + if (!np->ipv6) { + err = netpoll_take_ipv4(np, ndev); + if (err) + goto put; + } else { + err = netpoll_take_ipv6(np, ndev); + if (err) + goto put; + } + ip_overwritten = true; + } + + err = __netpoll_setup(np, ndev); + if (err) + goto put; + rtnl_unlock(); + + /* Make sure all NAPI polls which started before dev->npinfo + * was visible have exited before we start calling NAPI poll. + * NAPI skips locking if dev->npinfo is NULL. + */ + synchronize_rcu(); + + return 0; + +put: + DEBUG_NET_WARN_ON_ONCE(np->dev); + if (ip_overwritten) + memset(&np->local_ip, 0, sizeof(np->local_ip)); + netdev_put(ndev, &np->dev_tracker); +unlock: + rtnl_unlock(); + return err; +} + /* Attempts to resume logging to a deactivated target. */ static void resume_target(struct netconsole_target *nt) { @@ -361,7 +441,7 @@ static void resume_target(struct netconsole_target *nt) */ netconsole_skb_pool_init(nt); - if (netpoll_setup(&nt->np)) { + if (netcons_netpoll_setup(&nt->np)) { /* netpoll fails setup once, do not try again. */ netconsole_skb_pool_flush(nt); nt->state = STATE_DISABLED; @@ -840,7 +920,7 @@ static ssize_t enabled_store(struct config_item *item, */ netconsole_skb_pool_init(nt); - ret = netpoll_setup(&nt->np); + ret = netcons_netpoll_setup(&nt->np); if (ret) { netconsole_skb_pool_flush(nt); goto out_unlock; @@ -2430,7 +2510,7 @@ static struct netconsole_target *alloc_param_target(char *target_config, */ netconsole_skb_pool_init(nt); - err = netpoll_setup(&nt->np); + err = netcons_netpoll_setup(&nt->np); if (err) { pr_err("Not enabling netconsole for %s%d. Netpoll setup failed\n", NETCONSOLE_PARAM_TARGET_PREFIX, cmdline_count); diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index 0877515fa744..cd455a5a013d 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -66,7 +66,6 @@ static inline void netpoll_poll_enable(struct net_device *dev) { return; } #endif int __netpoll_setup(struct netpoll *np, struct net_device *ndev); -int netpoll_setup(struct netpoll *np); void __netpoll_free(struct netpoll *np); void netpoll_cleanup(struct netpoll *np); void do_netpoll_cleanup(struct netpoll *np); diff --git a/net/core/netpoll.c b/net/core/netpoll.c index 6a545063223b..d50d48a82def 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -514,87 +514,6 @@ bool netpoll_local_ip_unset(const struct netpoll *np) } EXPORT_SYMBOL_GPL(netpoll_local_ip_unset); -int netpoll_setup(struct netpoll *np) -{ - struct net *net = current->nsproxy->net_ns; - char buf[MAC_ADDR_STR_LEN + 1]; - struct net_device *ndev = NULL; - bool ip_overwritten = false; - int err; - - rtnl_lock(); - if (np->dev_name[0]) - ndev = __dev_get_by_name(net, np->dev_name); - else if (is_valid_ether_addr(np->dev_mac)) - ndev = dev_getbyhwaddr(net, ARPHRD_ETHER, np->dev_mac); - - if (!ndev) { - np_err(np, "%s doesn't exist, aborting\n", - egress_dev(np, buf, sizeof(buf))); - err = -ENODEV; - goto unlock; - } - netdev_hold(ndev, &np->dev_tracker, GFP_KERNEL); - - if (netdev_master_upper_dev_get(ndev)) { - np_err(np, "%s is a slave device, aborting\n", - egress_dev(np, buf, sizeof(buf))); - err = -EBUSY; - goto put; - } - - if (!netif_running(ndev)) { - np_info(np, "device %s not up yet, forcing it\n", - egress_dev(np, buf, sizeof(buf))); - - err = dev_open(ndev, NULL); - if (err) { - np_err(np, "failed to open %s\n", ndev->name); - goto put; - } - - rtnl_unlock(); - netpoll_wait_carrier(np, ndev); - rtnl_lock(); - } - - if (netpoll_local_ip_unset(np)) { - if (!np->ipv6) { - err = netpoll_take_ipv4(np, ndev); - if (err) - goto put; - } else { - err = netpoll_take_ipv6(np, ndev); - if (err) - goto put; - } - ip_overwritten = true; - } - - err = __netpoll_setup(np, ndev); - if (err) - goto put; - rtnl_unlock(); - - /* Make sure all NAPI polls which started before dev->npinfo - * was visible have exited before we start calling NAPI poll. - * NAPI skips locking if dev->npinfo is NULL. - */ - synchronize_rcu(); - - return 0; - -put: - DEBUG_NET_WARN_ON_ONCE(np->dev); - if (ip_overwritten) - memset(&np->local_ip, 0, sizeof(np->local_ip)); - netdev_put(ndev, &np->dev_tracker); -unlock: - rtnl_unlock(); - return err; -} -EXPORT_SYMBOL(netpoll_setup); - static void rcu_cleanup_netpoll_info(struct rcu_head *rcu_head) { struct netpoll_info *npinfo = From 6a55e81d4159e6745e4b77daac8362d85c30696e Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 24 Jul 2026 08:04:12 -0700 Subject: [PATCH 0658/1433] netconsole: move netpoll_local_ip_unset() as netcons_local_ip_unset() Move netpoll_local_ip_unset() from netpoll to netconsole and rename it to netcons_local_ip_unset(); The body is otherwise unchanged, only the comment's setup-function reference is updated. Signed-off-by: Breno Leitao Reviewed-by: Gustavo Luiz Duarte Link: https://patch.msgid.link/20260724-netconsole_move_more_final-v1-4-a5f7691db81c@debian.org Signed-off-by: Paolo Abeni --- drivers/net/netconsole.c | 19 ++++++++++++++++++- include/linux/netpoll.h | 1 - net/core/netpoll.c | 18 ------------------ 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 3a7a2cafc627..70f9c5bfb372 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -351,6 +351,23 @@ static void netconsole_skb_pool_flush(struct netconsole_target *nt) skb_queue_purge_reason(&nt->skb_pool, SKB_CONSUMED); } +/* + * Test whether the caller left np->local_ip unset, so that + * netcons_netpoll_setup() should auto-populate it from the egress device. + * + * np->local_ip is a union of __be32 (IPv4) and struct in6_addr (IPv6), + * so an IPv6 address whose first 4 bytes are zero (e.g. ::1, ::2, + * IPv4-mapped ::ffff:a.b.c.d) must not be tested via the IPv4 arm — + * doing so would misclassify a caller-supplied address as unset and + * silently overwrite it with whatever address the device exposes. + */ +static bool netcons_local_ip_unset(const struct netpoll *np) +{ + if (np->ipv6) + return ipv6_addr_any(&np->local_ip.in6); + return !np->local_ip.ip; +} + static int netcons_netpoll_setup(struct netpoll *np) { struct net *net = current->nsproxy->net_ns; @@ -395,7 +412,7 @@ static int netcons_netpoll_setup(struct netpoll *np) rtnl_lock(); } - if (netpoll_local_ip_unset(np)) { + if (netcons_local_ip_unset(np)) { if (!np->ipv6) { err = netpoll_take_ipv4(np, ndev); if (err) diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index cd455a5a013d..da05884f876e 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -76,7 +76,6 @@ void netpoll_wait_carrier(struct netpoll *np, struct net_device *ndev); char *egress_dev(struct netpoll *np, char *buf, size_t bufsz); int netpoll_take_ipv4(struct netpoll *np, struct net_device *ndev); int netpoll_take_ipv6(struct netpoll *np, struct net_device *ndev); -bool netpoll_local_ip_unset(const struct netpoll *np); #ifdef CONFIG_NETPOLL static inline void *netpoll_poll_lock(struct napi_struct *napi) diff --git a/net/core/netpoll.c b/net/core/netpoll.c index d50d48a82def..f4b110cb0416 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -496,24 +496,6 @@ int netpoll_take_ipv4(struct netpoll *np, struct net_device *ndev) } EXPORT_SYMBOL_GPL(netpoll_take_ipv4); -/* - * Test whether the caller left np->local_ip unset, so that - * netpoll_setup() should auto-populate it from the egress device. - * - * np->local_ip is a union of __be32 (IPv4) and struct in6_addr (IPv6), - * so an IPv6 address whose first 4 bytes are zero (e.g. ::1, ::2, - * IPv4-mapped ::ffff:a.b.c.d) must not be tested via the IPv4 arm — - * doing so would misclassify a caller-supplied address as unset and - * silently overwrite it with whatever address the device exposes. - */ -bool netpoll_local_ip_unset(const struct netpoll *np) -{ - if (np->ipv6) - return ipv6_addr_any(&np->local_ip.in6); - return !np->local_ip.ip; -} -EXPORT_SYMBOL_GPL(netpoll_local_ip_unset); - static void rcu_cleanup_netpoll_info(struct rcu_head *rcu_head) { struct netpoll_info *npinfo = From 3aa9ff037ba3ef12c75a924140955242e750fcbb Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 24 Jul 2026 08:04:13 -0700 Subject: [PATCH 0659/1433] netconsole: move netpoll_take_ipv4() as netcons_take_ipv4() Move netpoll_take_ipv4() to netconsole, which is the only user. Rename it to netcons_take_ipv4() for the netcons_ prefix. The body is unchanged. Signed-off-by: Breno Leitao Reviewed-by: Gustavo Luiz Duarte Link: https://patch.msgid.link/20260724-netconsole_move_more_final-v1-5-a5f7691db81c@debian.org Signed-off-by: Paolo Abeni --- drivers/net/netconsole.c | 32 +++++++++++++++++++++++++++++++- include/linux/netpoll.h | 1 - net/core/netpoll.c | 30 ------------------------------ 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 70f9c5bfb372..9506b1e1193f 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -351,6 +352,35 @@ static void netconsole_skb_pool_flush(struct netconsole_target *nt) skb_queue_purge_reason(&nt->skb_pool, SKB_CONSUMED); } +/* + * Take the IPv4 from ndev and populate local_ip structure in netpoll + */ +static int netcons_take_ipv4(struct netpoll *np, struct net_device *ndev) +{ + char buf[MAC_ADDR_STR_LEN + 1]; + const struct in_ifaddr *ifa; + struct in_device *in_dev; + + in_dev = __in_dev_get_rtnl(ndev); + if (!in_dev) { + np_err(np, "no IP address for %s, aborting\n", + egress_dev(np, buf, sizeof(buf))); + return -EDESTADDRREQ; + } + + ifa = rtnl_dereference(in_dev->ifa_list); + if (!ifa) { + np_err(np, "no IP address for %s, aborting\n", + egress_dev(np, buf, sizeof(buf))); + return -EDESTADDRREQ; + } + + np->local_ip.ip = ifa->ifa_local; + np_info(np, "local IP %pI4\n", &np->local_ip.ip); + + return 0; +} + /* * Test whether the caller left np->local_ip unset, so that * netcons_netpoll_setup() should auto-populate it from the egress device. @@ -414,7 +444,7 @@ static int netcons_netpoll_setup(struct netpoll *np) if (netcons_local_ip_unset(np)) { if (!np->ipv6) { - err = netpoll_take_ipv4(np, ndev); + err = netcons_take_ipv4(np, ndev); if (err) goto put; } else { diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index da05884f876e..e212cb86a942 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -74,7 +74,6 @@ void netpoll_zap_completion_queue(void); unsigned int netpoll_get_carrier_timeout(void); void netpoll_wait_carrier(struct netpoll *np, struct net_device *ndev); char *egress_dev(struct netpoll *np, char *buf, size_t bufsz); -int netpoll_take_ipv4(struct netpoll *np, struct net_device *ndev); int netpoll_take_ipv6(struct netpoll *np, struct net_device *ndev); #ifdef CONFIG_NETPOLL diff --git a/net/core/netpoll.c b/net/core/netpoll.c index f4b110cb0416..60c3faf9ea94 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -466,36 +466,6 @@ int netpoll_take_ipv6(struct netpoll *np, struct net_device *ndev) } EXPORT_SYMBOL_GPL(netpoll_take_ipv6); -/* - * Take the IPv4 from ndev and populate local_ip structure in netpoll - */ -int netpoll_take_ipv4(struct netpoll *np, struct net_device *ndev) -{ - char buf[MAC_ADDR_STR_LEN + 1]; - const struct in_ifaddr *ifa; - struct in_device *in_dev; - - in_dev = __in_dev_get_rtnl(ndev); - if (!in_dev) { - np_err(np, "no IP address for %s, aborting\n", - egress_dev(np, buf, sizeof(buf))); - return -EDESTADDRREQ; - } - - ifa = rtnl_dereference(in_dev->ifa_list); - if (!ifa) { - np_err(np, "no IP address for %s, aborting\n", - egress_dev(np, buf, sizeof(buf))); - return -EDESTADDRREQ; - } - - np->local_ip.ip = ifa->ifa_local; - np_info(np, "local IP %pI4\n", &np->local_ip.ip); - - return 0; -} -EXPORT_SYMBOL_GPL(netpoll_take_ipv4); - static void rcu_cleanup_netpoll_info(struct rcu_head *rcu_head) { struct netpoll_info *npinfo = From 80fcfc3538f3176c823eaf52fcf658d7f210d116 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 24 Jul 2026 08:04:14 -0700 Subject: [PATCH 0660/1433] netconsole: move netpoll_take_ipv6() as netcons_take_ipv6() Move netpoll_take_ipv6() to netconsole, and add netcons_ prefix. Signed-off-by: Breno Leitao Reviewed-by: Gustavo Luiz Duarte Link: https://patch.msgid.link/20260724-netconsole_move_more_final-v1-6-a5f7691db81c@debian.org Signed-off-by: Paolo Abeni --- drivers/net/netconsole.c | 44 +++++++++++++++++++++++++++++++++++++++- include/linux/netpoll.h | 1 - net/core/netpoll.c | 42 -------------------------------------- 3 files changed, 43 insertions(+), 44 deletions(-) diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 9506b1e1193f..b6cbda4722d5 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -352,6 +353,47 @@ static void netconsole_skb_pool_flush(struct netconsole_target *nt) skb_queue_purge_reason(&nt->skb_pool, SKB_CONSUMED); } +/* + * Take the IPv6 from ndev and populate local_ip structure in netpoll + */ +static int netcons_take_ipv6(struct netpoll *np, struct net_device *ndev) +{ + char buf[MAC_ADDR_STR_LEN + 1]; + int err = -EDESTADDRREQ; + struct inet6_dev *idev; + + if (!IS_ENABLED(CONFIG_IPV6)) { + np_err(np, "IPv6 is not supported %s, aborting\n", + egress_dev(np, buf, sizeof(buf))); + return -EINVAL; + } + + idev = __in6_dev_get(ndev); + if (idev) { + struct inet6_ifaddr *ifp; + + read_lock_bh(&idev->lock); + list_for_each_entry(ifp, &idev->addr_list, if_list) { + if (!!(ipv6_addr_type(&ifp->addr) & IPV6_ADDR_LINKLOCAL) != + !!(ipv6_addr_type(&np->remote_ip.in6) & IPV6_ADDR_LINKLOCAL)) + continue; + /* Got the IP, let's return */ + np->local_ip.in6 = ifp->addr; + err = 0; + break; + } + read_unlock_bh(&idev->lock); + } + if (err) { + np_err(np, "no IPv6 address for %s, aborting\n", + egress_dev(np, buf, sizeof(buf))); + return err; + } + + np_info(np, "local IPv6 %pI6c\n", &np->local_ip.in6); + return 0; +} + /* * Take the IPv4 from ndev and populate local_ip structure in netpoll */ @@ -448,7 +490,7 @@ static int netcons_netpoll_setup(struct netpoll *np) if (err) goto put; } else { - err = netpoll_take_ipv6(np, ndev); + err = netcons_take_ipv6(np, ndev); if (err) goto put; } diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index e212cb86a942..7736339f9880 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -74,7 +74,6 @@ void netpoll_zap_completion_queue(void); unsigned int netpoll_get_carrier_timeout(void); void netpoll_wait_carrier(struct netpoll *np, struct net_device *ndev); char *egress_dev(struct netpoll *np, char *buf, size_t bufsz); -int netpoll_take_ipv6(struct netpoll *np, struct net_device *ndev); #ifdef CONFIG_NETPOLL static inline void *netpoll_poll_lock(struct napi_struct *napi) diff --git a/net/core/netpoll.c b/net/core/netpoll.c index 60c3faf9ea94..246aa2bfd780 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -424,48 +424,6 @@ void netpoll_wait_carrier(struct netpoll *np, struct net_device *ndev) } EXPORT_SYMBOL_GPL(netpoll_wait_carrier); -/* - * Take the IPv6 from ndev and populate local_ip structure in netpoll - */ -int netpoll_take_ipv6(struct netpoll *np, struct net_device *ndev) -{ - char buf[MAC_ADDR_STR_LEN + 1]; - int err = -EDESTADDRREQ; - struct inet6_dev *idev; - - if (!IS_ENABLED(CONFIG_IPV6)) { - np_err(np, "IPv6 is not supported %s, aborting\n", - egress_dev(np, buf, sizeof(buf))); - return -EINVAL; - } - - idev = __in6_dev_get(ndev); - if (idev) { - struct inet6_ifaddr *ifp; - - read_lock_bh(&idev->lock); - list_for_each_entry(ifp, &idev->addr_list, if_list) { - if (!!(ipv6_addr_type(&ifp->addr) & IPV6_ADDR_LINKLOCAL) != - !!(ipv6_addr_type(&np->remote_ip.in6) & IPV6_ADDR_LINKLOCAL)) - continue; - /* Got the IP, let's return */ - np->local_ip.in6 = ifp->addr; - err = 0; - break; - } - read_unlock_bh(&idev->lock); - } - if (err) { - np_err(np, "no IPv6 address for %s, aborting\n", - egress_dev(np, buf, sizeof(buf))); - return err; - } - - np_info(np, "local IPv6 %pI6c\n", &np->local_ip.in6); - return 0; -} -EXPORT_SYMBOL_GPL(netpoll_take_ipv6); - static void rcu_cleanup_netpoll_info(struct rcu_head *rcu_head) { struct netpoll_info *npinfo = From cf6de67ba41c2b361cdab4d649f757d817bc6975 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 24 Jul 2026 08:04:15 -0700 Subject: [PATCH 0661/1433] netconsole: move egress_dev() as netcons_egress_dev() move egress_dev() from netpoll to netconsole, and append netcons_ prefix. Signed-off-by: Breno Leitao Reviewed-by: Gustavo Luiz Duarte Link: https://patch.msgid.link/20260724-netconsole_move_more_final-v1-7-a5f7691db81c@debian.org Signed-off-by: Paolo Abeni --- drivers/net/netconsole.c | 30 +++++++++++++++++++++++------- include/linux/netpoll.h | 1 - net/core/netpoll.c | 17 ----------------- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index b6cbda4722d5..fc575adc29bd 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -353,6 +353,22 @@ static void netconsole_skb_pool_flush(struct netconsole_target *nt) skb_queue_purge_reason(&nt->skb_pool, SKB_CONSUMED); } +/* + * Returns a pointer to a string representation of the identifier used + * to select the egress interface for the given netpoll instance. buf + * is used to format np->dev_mac when np->dev_name is empty; bufsz must + * be at least MAC_ADDR_STR_LEN + 1 to fit the formatted MAC address + * and its NUL terminator. + */ +static char *netcons_egress_dev(struct netpoll *np, char *buf, size_t bufsz) +{ + if (np->dev_name[0]) + return np->dev_name; + + snprintf(buf, bufsz, "%pM", np->dev_mac); + return buf; +} + /* * Take the IPv6 from ndev and populate local_ip structure in netpoll */ @@ -364,7 +380,7 @@ static int netcons_take_ipv6(struct netpoll *np, struct net_device *ndev) if (!IS_ENABLED(CONFIG_IPV6)) { np_err(np, "IPv6 is not supported %s, aborting\n", - egress_dev(np, buf, sizeof(buf))); + netcons_egress_dev(np, buf, sizeof(buf))); return -EINVAL; } @@ -386,7 +402,7 @@ static int netcons_take_ipv6(struct netpoll *np, struct net_device *ndev) } if (err) { np_err(np, "no IPv6 address for %s, aborting\n", - egress_dev(np, buf, sizeof(buf))); + netcons_egress_dev(np, buf, sizeof(buf))); return err; } @@ -406,14 +422,14 @@ static int netcons_take_ipv4(struct netpoll *np, struct net_device *ndev) in_dev = __in_dev_get_rtnl(ndev); if (!in_dev) { np_err(np, "no IP address for %s, aborting\n", - egress_dev(np, buf, sizeof(buf))); + netcons_egress_dev(np, buf, sizeof(buf))); return -EDESTADDRREQ; } ifa = rtnl_dereference(in_dev->ifa_list); if (!ifa) { np_err(np, "no IP address for %s, aborting\n", - egress_dev(np, buf, sizeof(buf))); + netcons_egress_dev(np, buf, sizeof(buf))); return -EDESTADDRREQ; } @@ -456,7 +472,7 @@ static int netcons_netpoll_setup(struct netpoll *np) if (!ndev) { np_err(np, "%s doesn't exist, aborting\n", - egress_dev(np, buf, sizeof(buf))); + netcons_egress_dev(np, buf, sizeof(buf))); err = -ENODEV; goto unlock; } @@ -464,14 +480,14 @@ static int netcons_netpoll_setup(struct netpoll *np) if (netdev_master_upper_dev_get(ndev)) { np_err(np, "%s is a slave device, aborting\n", - egress_dev(np, buf, sizeof(buf))); + netcons_egress_dev(np, buf, sizeof(buf))); err = -EBUSY; goto put; } if (!netif_running(ndev)) { np_info(np, "device %s not up yet, forcing it\n", - egress_dev(np, buf, sizeof(buf))); + netcons_egress_dev(np, buf, sizeof(buf))); err = dev_open(ndev, NULL); if (err) { diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index 7736339f9880..dac8dc2529de 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -73,7 +73,6 @@ netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb); void netpoll_zap_completion_queue(void); unsigned int netpoll_get_carrier_timeout(void); void netpoll_wait_carrier(struct netpoll *np, struct net_device *ndev); -char *egress_dev(struct netpoll *np, char *buf, size_t bufsz); #ifdef CONFIG_NETPOLL static inline void *netpoll_poll_lock(struct napi_struct *napi) diff --git a/net/core/netpoll.c b/net/core/netpoll.c index 246aa2bfd780..f385ae4b70f5 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -392,23 +392,6 @@ int __netpoll_setup(struct netpoll *np, struct net_device *ndev) } EXPORT_SYMBOL_GPL(__netpoll_setup); -/* - * Returns a pointer to a string representation of the identifier used - * to select the egress interface for the given netpoll instance. buf - * is used to format np->dev_mac when np->dev_name is empty; bufsz must - * be at least MAC_ADDR_STR_LEN + 1 to fit the formatted MAC address - * and its NUL terminator. - */ -char *egress_dev(struct netpoll *np, char *buf, size_t bufsz) -{ - if (np->dev_name[0]) - return np->dev_name; - - snprintf(buf, bufsz, "%pM", np->dev_mac); - return buf; -} -EXPORT_SYMBOL_GPL(egress_dev); - void netpoll_wait_carrier(struct netpoll *np, struct net_device *ndev) { unsigned long atmost; From a1116396476f643b748a1686184c48b44ac1c2e5 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 24 Jul 2026 08:04:16 -0700 Subject: [PATCH 0662/1433] netconsole: move local_ip/remote_ip/ipv6 to netconsole_target With netpoll_setup() and the packet-building path now living in netconsole, local_ip, remote_ip and ipv6 in struct netpoll are read and written only by netconsole. No other netpoll user touches them. Move the three fields into netconsole_target and switch the packet builders and setup helpers to take the target instead of the netpoll handle. struct netpoll is left holding only the device-binding state that the shared netpoll transport needs. Signed-off-by: Breno Leitao Reviewed-by: Gustavo Luiz Duarte Link: https://patch.msgid.link/20260724-netconsole_move_more_final-v1-8-a5f7691db81c@debian.org Signed-off-by: Paolo Abeni --- drivers/net/netconsole.c | 143 +++++++++++++++++++++------------------ include/linux/netpoll.h | 3 - 2 files changed, 76 insertions(+), 70 deletions(-) diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index fc575adc29bd..199d0e1ac17b 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -177,9 +177,10 @@ enum target_state { * @np: The netpoll structure for this target. * Contains the other userspace visible parameters: * dev_name (read-write) - * local_ip (read-write) - * remote_ip (read-write) * local_mac (read-only) + * @local_ip: Source IP address of the target (read-write). + * @remote_ip: Destination IP address of the target (read-write). + * @ipv6: Whether the target addresses are IPv6 (read-write). * @local_port: Source UDP port of the target (read-write). * @remote_port: Destination UDP port of the target (read-write). * @remote_mac: Destination ethernet address of the target (read-write). @@ -210,6 +211,8 @@ struct netconsole_target { bool extended; bool release; struct netpoll np; + union inet_addr local_ip, remote_ip; + bool ipv6; u16 local_port, remote_port; u8 remote_mac[ETH_ALEN]; /* protected by target_list_lock; +1 gives scnprintf() room for its @@ -370,11 +373,13 @@ static char *netcons_egress_dev(struct netpoll *np, char *buf, size_t bufsz) } /* - * Take the IPv6 from ndev and populate local_ip structure in netpoll + * Populate the target's local_ip with the IPv6 address from ndev. */ -static int netcons_take_ipv6(struct netpoll *np, struct net_device *ndev) +static int netcons_take_ipv6(struct netconsole_target *nt, + struct net_device *ndev) { char buf[MAC_ADDR_STR_LEN + 1]; + struct netpoll *np = &nt->np; int err = -EDESTADDRREQ; struct inet6_dev *idev; @@ -391,10 +396,10 @@ static int netcons_take_ipv6(struct netpoll *np, struct net_device *ndev) read_lock_bh(&idev->lock); list_for_each_entry(ifp, &idev->addr_list, if_list) { if (!!(ipv6_addr_type(&ifp->addr) & IPV6_ADDR_LINKLOCAL) != - !!(ipv6_addr_type(&np->remote_ip.in6) & IPV6_ADDR_LINKLOCAL)) + !!(ipv6_addr_type(&nt->remote_ip.in6) & IPV6_ADDR_LINKLOCAL)) continue; /* Got the IP, let's return */ - np->local_ip.in6 = ifp->addr; + nt->local_ip.in6 = ifp->addr; err = 0; break; } @@ -406,16 +411,18 @@ static int netcons_take_ipv6(struct netpoll *np, struct net_device *ndev) return err; } - np_info(np, "local IPv6 %pI6c\n", &np->local_ip.in6); + np_info(np, "local IPv6 %pI6c\n", &nt->local_ip.in6); return 0; } /* - * Take the IPv4 from ndev and populate local_ip structure in netpoll + * Populate the target's local_ip with the IPv4 address from ndev. */ -static int netcons_take_ipv4(struct netpoll *np, struct net_device *ndev) +static int netcons_take_ipv4(struct netconsole_target *nt, + struct net_device *ndev) { char buf[MAC_ADDR_STR_LEN + 1]; + struct netpoll *np = &nt->np; const struct in_ifaddr *ifa; struct in_device *in_dev; @@ -433,34 +440,35 @@ static int netcons_take_ipv4(struct netpoll *np, struct net_device *ndev) return -EDESTADDRREQ; } - np->local_ip.ip = ifa->ifa_local; - np_info(np, "local IP %pI4\n", &np->local_ip.ip); + nt->local_ip.ip = ifa->ifa_local; + np_info(np, "local IP %pI4\n", &nt->local_ip.ip); return 0; } /* - * Test whether the caller left np->local_ip unset, so that + * Test whether the caller left nt->local_ip unset, so that * netcons_netpoll_setup() should auto-populate it from the egress device. * - * np->local_ip is a union of __be32 (IPv4) and struct in6_addr (IPv6), + * nt->local_ip is a union of __be32 (IPv4) and struct in6_addr (IPv6), * so an IPv6 address whose first 4 bytes are zero (e.g. ::1, ::2, * IPv4-mapped ::ffff:a.b.c.d) must not be tested via the IPv4 arm — * doing so would misclassify a caller-supplied address as unset and * silently overwrite it with whatever address the device exposes. */ -static bool netcons_local_ip_unset(const struct netpoll *np) +static bool netcons_local_ip_unset(const struct netconsole_target *nt) { - if (np->ipv6) - return ipv6_addr_any(&np->local_ip.in6); - return !np->local_ip.ip; + if (nt->ipv6) + return ipv6_addr_any(&nt->local_ip.in6); + return !nt->local_ip.ip; } -static int netcons_netpoll_setup(struct netpoll *np) +static int netcons_netpoll_setup(struct netconsole_target *nt) { struct net *net = current->nsproxy->net_ns; char buf[MAC_ADDR_STR_LEN + 1]; struct net_device *ndev = NULL; + struct netpoll *np = &nt->np; bool ip_overwritten = false; int err; @@ -500,13 +508,13 @@ static int netcons_netpoll_setup(struct netpoll *np) rtnl_lock(); } - if (netcons_local_ip_unset(np)) { - if (!np->ipv6) { - err = netcons_take_ipv4(np, ndev); + if (netcons_local_ip_unset(nt)) { + if (!nt->ipv6) { + err = netcons_take_ipv4(nt, ndev); if (err) goto put; } else { - err = netcons_take_ipv6(np, ndev); + err = netcons_take_ipv6(nt, ndev); if (err) goto put; } @@ -529,7 +537,7 @@ static int netcons_netpoll_setup(struct netpoll *np) put: DEBUG_NET_WARN_ON_ONCE(np->dev); if (ip_overwritten) - memset(&np->local_ip, 0, sizeof(np->local_ip)); + memset(&nt->local_ip, 0, sizeof(nt->local_ip)); netdev_put(ndev, &np->dev_tracker); unlock: rtnl_unlock(); @@ -546,7 +554,7 @@ static void resume_target(struct netconsole_target *nt) */ netconsole_skb_pool_init(nt); - if (netcons_netpoll_setup(&nt->np)) { + if (netcons_netpoll_setup(nt)) { /* netpoll fails setup once, do not try again. */ netconsole_skb_pool_flush(nt); nt->state = STATE_DISABLED; @@ -691,17 +699,17 @@ static void netconsole_print_banner(struct netconsole_target *nt) struct netpoll *np = &nt->np; np_info(np, "local port %d\n", nt->local_port); - if (np->ipv6) - np_info(np, "local IPv6 address %pI6c\n", &np->local_ip.in6); + if (nt->ipv6) + np_info(np, "local IPv6 address %pI6c\n", &nt->local_ip.in6); else - np_info(np, "local IPv4 address %pI4\n", &np->local_ip.ip); + np_info(np, "local IPv4 address %pI4\n", &nt->local_ip.ip); np_info(np, "interface name '%s'\n", np->dev_name); np_info(np, "local ethernet address '%pM'\n", np->dev_mac); np_info(np, "remote port %d\n", nt->remote_port); - if (np->ipv6) - np_info(np, "remote IPv6 address %pI6c\n", &np->remote_ip.in6); + if (nt->ipv6) + np_info(np, "remote IPv6 address %pI6c\n", &nt->remote_ip.in6); else - np_info(np, "remote IPv4 address %pI4\n", &np->remote_ip.ip); + np_info(np, "remote IPv4 address %pI4\n", &nt->remote_ip.ip); np_info(np, "remote ethernet address %pM\n", nt->remote_mac); } @@ -832,20 +840,20 @@ static ssize_t local_ip_show(struct config_item *item, char *buf) { struct netconsole_target *nt = to_target(item); - if (nt->np.ipv6) - return sysfs_emit(buf, "%pI6c\n", &nt->np.local_ip.in6); + if (nt->ipv6) + return sysfs_emit(buf, "%pI6c\n", &nt->local_ip.in6); else - return sysfs_emit(buf, "%pI4\n", &nt->np.local_ip); + return sysfs_emit(buf, "%pI4\n", &nt->local_ip); } static ssize_t remote_ip_show(struct config_item *item, char *buf) { struct netconsole_target *nt = to_target(item); - if (nt->np.ipv6) - return sysfs_emit(buf, "%pI6c\n", &nt->np.remote_ip.in6); + if (nt->ipv6) + return sysfs_emit(buf, "%pI6c\n", &nt->remote_ip.in6); else - return sysfs_emit(buf, "%pI4\n", &nt->np.remote_ip); + return sysfs_emit(buf, "%pI4\n", &nt->remote_ip); } static ssize_t local_mac_show(struct config_item *item, char *buf) @@ -1025,7 +1033,7 @@ static ssize_t enabled_store(struct config_item *item, */ netconsole_skb_pool_init(nt); - ret = netcons_netpoll_setup(&nt->np); + ret = netcons_netpoll_setup(nt); if (ret) { netconsole_skb_pool_flush(nt); goto out_unlock; @@ -1199,10 +1207,10 @@ static ssize_t local_ip_store(struct config_item *item, const char *buf, goto out_unlock; } - ipv6 = netpoll_parse_ip_addr(buf, &nt->np.local_ip); + ipv6 = netpoll_parse_ip_addr(buf, &nt->local_ip); if (ipv6 == -1) goto out_unlock; - nt->np.ipv6 = !!ipv6; + nt->ipv6 = !!ipv6; ret = count; out_unlock: @@ -1224,10 +1232,10 @@ static ssize_t remote_ip_store(struct config_item *item, const char *buf, goto out_unlock; } - ipv6 = netpoll_parse_ip_addr(buf, &nt->np.remote_ip); + ipv6 = netpoll_parse_ip_addr(buf, &nt->remote_ip); if (ipv6 == -1) goto out_unlock; - nt->np.ipv6 = !!ipv6; + nt->ipv6 = !!ipv6; ret = count; out_unlock: @@ -2027,8 +2035,8 @@ static struct sk_buff *find_skb(struct netconsole_target *nt, int len, return skb; } -static void netpoll_udp_checksum(struct netpoll *np, struct sk_buff *skb, - int len) +static void netpoll_udp_checksum(struct netconsole_target *nt, + struct sk_buff *skb, int len) { struct udphdr *udph; int udp_len; @@ -2038,14 +2046,14 @@ static void netpoll_udp_checksum(struct netpoll *np, struct sk_buff *skb, /* check needs to be set, since it will be consumed in csum_partial */ udph->check = 0; - if (np->ipv6) - udph->check = csum_ipv6_magic(&np->local_ip.in6, - &np->remote_ip.in6, + if (nt->ipv6) + udph->check = csum_ipv6_magic(&nt->local_ip.in6, + &nt->remote_ip.in6, udp_len, IPPROTO_UDP, csum_partial(udph, udp_len, 0)); else - udph->check = csum_tcpudp_magic(np->local_ip.ip, - np->remote_ip.ip, + udph->check = csum_tcpudp_magic(nt->local_ip.ip, + nt->remote_ip.ip, udp_len, IPPROTO_UDP, csum_partial(udph, udp_len, 0)); if (udph->check == 0) @@ -2054,7 +2062,6 @@ static void netpoll_udp_checksum(struct netpoll *np, struct sk_buff *skb, static void push_udp(struct netconsole_target *nt, struct sk_buff *skb, int len) { - struct netpoll *np = &nt->np; struct udphdr *udph; int udp_len; @@ -2068,7 +2075,7 @@ static void push_udp(struct netconsole_target *nt, struct sk_buff *skb, int len) udph->dest = htons(nt->remote_port); udp_set_len_short(udph, udp_len); - netpoll_udp_checksum(np, skb, len); + netpoll_udp_checksum(nt, skb, len); } static void push_eth(struct netconsole_target *nt, struct sk_buff *skb) @@ -2080,13 +2087,14 @@ static void push_eth(struct netconsole_target *nt, struct sk_buff *skb) skb_reset_mac_header(skb); ether_addr_copy(eth->h_source, np->dev->dev_addr); ether_addr_copy(eth->h_dest, nt->remote_mac); - if (np->ipv6) + if (nt->ipv6) eth->h_proto = htons(ETH_P_IPV6); else eth->h_proto = htons(ETH_P_IP); } -static void push_ipv4(struct netpoll *np, struct sk_buff *skb, int len) +static void push_ipv4(struct netconsole_target *nt, struct sk_buff *skb, + int len) { static atomic_t ip_ident; struct iphdr *iph; @@ -2107,13 +2115,14 @@ static void push_ipv4(struct netpoll *np, struct sk_buff *skb, int len) iph->ttl = 64; iph->protocol = IPPROTO_UDP; iph->check = 0; - put_unaligned(np->local_ip.ip, &iph->saddr); - put_unaligned(np->remote_ip.ip, &iph->daddr); + put_unaligned(nt->local_ip.ip, &iph->saddr); + put_unaligned(nt->remote_ip.ip, &iph->daddr); iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl); skb->protocol = htons(ETH_P_IP); } -static void push_ipv6(struct netpoll *np, struct sk_buff *skb, int len) +static void push_ipv6(struct netconsole_target *nt, struct sk_buff *skb, + int len) { struct ipv6hdr *ip6h; @@ -2130,8 +2139,8 @@ static void push_ipv6(struct netpoll *np, struct sk_buff *skb, int len) ip6h->payload_len = htons(sizeof(struct udphdr) + len); ip6h->nexthdr = IPPROTO_UDP; ip6h->hop_limit = 32; - ip6h->saddr = np->local_ip.in6; - ip6h->daddr = np->remote_ip.in6; + ip6h->saddr = nt->local_ip.in6; + ip6h->daddr = nt->remote_ip.in6; skb->protocol = htons(ETH_P_IPV6); } @@ -2147,7 +2156,7 @@ static int netpoll_send_udp(struct netconsole_target *nt, const char *msg, WARN_ON_ONCE(!irqs_disabled()); udp_len = len + sizeof(struct udphdr); - if (np->ipv6) + if (nt->ipv6) ip_len = udp_len + sizeof(struct ipv6hdr); else ip_len = udp_len + sizeof(struct iphdr); @@ -2163,10 +2172,10 @@ static int netpoll_send_udp(struct netconsole_target *nt, const char *msg, skb_put(skb, len); push_udp(nt, skb, len); - if (np->ipv6) - push_ipv6(np, skb, len); + if (nt->ipv6) + push_ipv6(nt, skb, len); else - push_ipv4(np, skb, len); + push_ipv4(nt, skb, len); push_eth(nt, skb); skb->dev = np->dev; @@ -2505,11 +2514,11 @@ static int netconsole_parser_cmdline(struct netconsole_target *nt, char *opt) if (!delim) goto parse_failed; *delim = 0; - ipv6 = netpoll_parse_ip_addr(cur, &np->local_ip); + ipv6 = netpoll_parse_ip_addr(cur, &nt->local_ip); if (ipv6 < 0) goto parse_failed; else - np->ipv6 = (bool)ipv6; + nt->ipv6 = (bool)ipv6; cur = delim; } cur++; @@ -2551,13 +2560,13 @@ static int netconsole_parser_cmdline(struct netconsole_target *nt, char *opt) if (!delim) goto parse_failed; *delim = 0; - ipv6 = netpoll_parse_ip_addr(cur, &np->remote_ip); + ipv6 = netpoll_parse_ip_addr(cur, &nt->remote_ip); if (ipv6 < 0) goto parse_failed; - else if (ipversion_set && np->ipv6 != (bool)ipv6) + else if (ipversion_set && nt->ipv6 != (bool)ipv6) goto parse_failed; else - np->ipv6 = (bool)ipv6; + nt->ipv6 = (bool)ipv6; cur = delim + 1; if (*cur != 0) { @@ -2615,7 +2624,7 @@ static struct netconsole_target *alloc_param_target(char *target_config, */ netconsole_skb_pool_init(nt); - err = netcons_netpoll_setup(&nt->np); + err = netcons_netpoll_setup(nt); if (err) { pr_err("Not enabling netconsole for %s%d. Netpoll setup failed\n", NETCONSOLE_PARAM_TARGET_PREFIX, cmdline_count); diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index dac8dc2529de..ef88a30b11f4 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -32,9 +32,6 @@ struct netpoll { char dev_name[IFNAMSIZ]; u8 dev_mac[ETH_ALEN]; const char *name; - - union inet_addr local_ip, remote_ip; - bool ipv6; }; #define np_info(np, fmt, ...) \ From dc50a5c9cdeae6b71754446fae84b4d831957f85 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 24 Jul 2026 08:04:17 -0700 Subject: [PATCH 0663/1433] netconsole: move netpoll_wait_carrier() as netcons_wait_carrier() netpoll_wait_carrier() waits for the egress device carrier during netconsole setup. Its only caller, netcons_netpoll_setup(), already lives in netconsole. Move the function into drivers/net/netconsole.c, drop EXPORT_SYMBOL_GPL() and remove the prototype from . Rename it to netcons_wait_carrier() for the netcons_ prefix. It now reads the timeout through netpoll_get_carrier_timeout(), since carrier_timeout stays in netpoll to keep the netpoll.carrier_timeout parameter. Signed-off-by: Breno Leitao Reviewed-by: Gustavo Luiz Duarte Link: https://patch.msgid.link/20260724-netconsole_move_more_final-v1-9-a5f7691db81c@debian.org Signed-off-by: Paolo Abeni --- drivers/net/netconsole.c | 17 ++++++++++++++++- include/linux/netpoll.h | 1 - net/core/netpoll.c | 15 --------------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 199d0e1ac17b..03913302328c 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -48,6 +48,7 @@ #include #include #include +#include MODULE_AUTHOR("Matt Mackall "); MODULE_DESCRIPTION("Console driver for network interfaces"); @@ -356,6 +357,20 @@ static void netconsole_skb_pool_flush(struct netconsole_target *nt) skb_queue_purge_reason(&nt->skb_pool, SKB_CONSUMED); } +static void netcons_wait_carrier(struct netpoll *np, struct net_device *ndev) +{ + unsigned long atmost; + + atmost = jiffies + netpoll_get_carrier_timeout() * HZ; + while (!netif_carrier_ok(ndev)) { + if (time_after(jiffies, atmost)) { + np_notice(np, "timeout waiting for carrier\n"); + break; + } + msleep(1); + } +} + /* * Returns a pointer to a string representation of the identifier used * to select the egress interface for the given netpoll instance. buf @@ -504,7 +519,7 @@ static int netcons_netpoll_setup(struct netconsole_target *nt) } rtnl_unlock(); - netpoll_wait_carrier(np, ndev); + netcons_wait_carrier(np, ndev); rtnl_lock(); } diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index ef88a30b11f4..1c6b1eec5efd 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -69,7 +69,6 @@ void do_netpoll_cleanup(struct netpoll *np); netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb); void netpoll_zap_completion_queue(void); unsigned int netpoll_get_carrier_timeout(void); -void netpoll_wait_carrier(struct netpoll *np, struct net_device *ndev); #ifdef CONFIG_NETPOLL static inline void *netpoll_poll_lock(struct napi_struct *napi) diff --git a/net/core/netpoll.c b/net/core/netpoll.c index f385ae4b70f5..fe1e0cda5d6b 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -392,21 +392,6 @@ int __netpoll_setup(struct netpoll *np, struct net_device *ndev) } EXPORT_SYMBOL_GPL(__netpoll_setup); -void netpoll_wait_carrier(struct netpoll *np, struct net_device *ndev) -{ - unsigned long atmost; - - atmost = jiffies + carrier_timeout * HZ; - while (!netif_carrier_ok(ndev)) { - if (time_after(jiffies, atmost)) { - np_notice(np, "timeout waiting for carrier\n"); - break; - } - msleep(1); - } -} -EXPORT_SYMBOL_GPL(netpoll_wait_carrier); - static void rcu_cleanup_netpoll_info(struct rcu_head *rcu_head) { struct netpoll_info *npinfo = From 87579b8cda9ec6ecba8327c59d8505d3b83fda98 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Sun, 26 Jul 2026 00:08:49 +0900 Subject: [PATCH 0664/1433] dpll: zl3073x: remove conditional return with no effect Both branches of the check return the same value, so the check has no effect. Remove it and return the value directly. This is the result of running the Coccinelle script from scripts/coccinelle/misc/cond_return_no_effect.cocci. Signed-off-by: Sang-Heon Jeon Reviewed-by: Ivan Vecera Link: https://patch.msgid.link/20260725150852.859188-2-ekffu200098@gmail.com Signed-off-by: Paolo Abeni --- drivers/dpll/zl3073x/dpll.c | 6 +----- drivers/dpll/zl3073x/out.c | 8 ++------ 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c index a2a641b8358f..0488ae6ac486 100644 --- a/drivers/dpll/zl3073x/dpll.c +++ b/drivers/dpll/zl3073x/dpll.c @@ -2272,11 +2272,7 @@ zl3073x_dpll_init_fine_phase_adjust(struct zl3073x_dev *zldev) if (rc) return rc; - rc = zl3073x_write_u8(zldev, ZL_REG_SYNTH_PHASE_SHIFT_CTRL, 0x01); - if (rc) - return rc; - - return rc; + return zl3073x_write_u8(zldev, ZL_REG_SYNTH_PHASE_SHIFT_CTRL, 0x01); } /** diff --git a/drivers/dpll/zl3073x/out.c b/drivers/dpll/zl3073x/out.c index eb5628aebcee..410d15b96d0b 100644 --- a/drivers/dpll/zl3073x/out.c +++ b/drivers/dpll/zl3073x/out.c @@ -85,12 +85,8 @@ int zl3073x_out_state_fetch(struct zl3073x_dev *zldev, u8 index) if (rc) return rc; - rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, - &out->phase_comp); - if (rc) - return rc; - - return rc; + return zl3073x_read_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, + &out->phase_comp); } /** From 66084e9510a96436f905526454c67bf7aa0e96f1 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Sun, 26 Jul 2026 00:08:50 +0900 Subject: [PATCH 0665/1433] net: ethernet: remove conditional return with no effect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both branches of the check return the same value, so the check has no effect. Remove it and return the value directly. This is the result of running the Coccinelle script from scripts/coccinelle/misc/cond_return_no_effect.cocci. Signed-off-by: Sang-Heon Jeon Reviewed-by: Niklas Söderlund Reviewed-by: Arthur Kiyanovski Reviewed-by: Ioana Ciornei # for dpaa2-switch Link: https://patch.msgid.link/20260725150852.859188-3-ekffu200098@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/amazon/ena/ena_netdev.c | 6 +----- drivers/net/ethernet/aquantia/atlantic/aq_macsec.c | 6 +----- drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c | 6 +----- drivers/net/ethernet/freescale/gianfar.c | 6 +----- drivers/net/ethernet/qlogic/netxen/netxen_nic_hw.c | 7 +------ drivers/net/ethernet/qlogic/qlcnic/qlcnic_83xx_init.c | 6 +----- drivers/net/ethernet/renesas/rtsn.c | 7 +------ 7 files changed, 7 insertions(+), 37 deletions(-) diff --git a/drivers/net/ethernet/amazon/ena/ena_netdev.c b/drivers/net/ethernet/amazon/ena/ena_netdev.c index 5d05020a6d05..ea89619039d8 100644 --- a/drivers/net/ethernet/amazon/ena/ena_netdev.c +++ b/drivers/net/ethernet/amazon/ena/ena_netdev.c @@ -2303,11 +2303,7 @@ static int ena_open(struct net_device *netdev) return rc; } - rc = ena_up(adapter); - if (rc) - return rc; - - return rc; + return ena_up(adapter); } /* ena_close - Disables a network interface diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_macsec.c b/drivers/net/ethernet/aquantia/atlantic/aq_macsec.c index 3ca072360ec7..fd4ee6212234 100644 --- a/drivers/net/ethernet/aquantia/atlantic/aq_macsec.c +++ b/drivers/net/ethernet/aquantia/atlantic/aq_macsec.c @@ -735,11 +735,7 @@ static int aq_set_rxsc(struct aq_nic_s *nic, const u32 rxsc_idx) sc_record.valid = 1; sc_record.fresh = 1; - ret = aq_mss_set_ingress_sc_record(hw, &sc_record, hw_sc_idx); - if (ret) - return ret; - - return ret; + return aq_mss_set_ingress_sc_record(hw, &sc_record, hw_sc_idx); } static int aq_mdo_add_rxsc(struct macsec_context *ctx) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 33e7a99d3e49..79d4a77f72bd 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -3935,11 +3935,7 @@ static int dpaa2_switch_port_init(struct ethsw_port_priv *port_priv, u16 port) if (err) return err; - err = dpaa2_switch_port_trap_mac_addr(port_priv, ll_mac, ll_mask); - if (err) - return err; - - return err; + return dpaa2_switch_port_trap_mac_addr(port_priv, ll_mac, ll_mask); } static void dpaa2_switch_ctrl_if_teardown(struct ethsw_core *ethsw) diff --git a/drivers/net/ethernet/freescale/gianfar.c b/drivers/net/ethernet/freescale/gianfar.c index 89215e1ddc2d..cf636fc5aafa 100644 --- a/drivers/net/ethernet/freescale/gianfar.c +++ b/drivers/net/ethernet/freescale/gianfar.c @@ -2877,11 +2877,7 @@ static int gfar_enet_open(struct net_device *dev) if (err) return err; - err = startup_gfar(dev); - if (err) - return err; - - return err; + return startup_gfar(dev); } /* Stops the kernel queue, and halts the controller */ diff --git a/drivers/net/ethernet/qlogic/netxen/netxen_nic_hw.c b/drivers/net/ethernet/qlogic/netxen/netxen_nic_hw.c index fff8dc84212d..e96a268067f2 100644 --- a/drivers/net/ethernet/qlogic/netxen/netxen_nic_hw.c +++ b/drivers/net/ethernet/qlogic/netxen/netxen_nic_hw.c @@ -2478,7 +2478,6 @@ static int netxen_parse_md_template(struct netxen_adapter *adapter) static int netxen_collect_minidump(struct netxen_adapter *adapter) { - int ret = 0; struct netxen_minidump_template_hdr *hdr; hdr = (struct netxen_minidump_template_hdr *) adapter->mdump.md_template; @@ -2486,11 +2485,7 @@ netxen_collect_minidump(struct netxen_adapter *adapter) hdr->driver_timestamp = ktime_get_seconds(); hdr->driver_info_word2 = adapter->fw_version; hdr->driver_info_word3 = NXRD32(adapter, CRB_DRIVER_VERSION); - ret = netxen_parse_md_template(adapter); - if (ret) - return ret; - - return ret; + return netxen_parse_md_template(adapter); } diff --git a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_83xx_init.c b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_83xx_init.c index 45ed8705c7ca..47cd9ec665ee 100644 --- a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_83xx_init.c +++ b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_83xx_init.c @@ -1618,11 +1618,7 @@ static int qlcnic_83xx_check_hw_status(struct qlcnic_adapter *p_dev) if (err) return err; - err = qlcnic_83xx_check_heartbeat(p_dev); - if (err) - return err; - - return err; + return qlcnic_83xx_check_heartbeat(p_dev); } static int qlcnic_83xx_poll_reg(struct qlcnic_adapter *p_dev, u32 addr, diff --git a/drivers/net/ethernet/renesas/rtsn.c b/drivers/net/ethernet/renesas/rtsn.c index ee8381b60b8d..f7beeb73eb16 100644 --- a/drivers/net/ethernet/renesas/rtsn.c +++ b/drivers/net/ethernet/renesas/rtsn.c @@ -685,7 +685,6 @@ static void rtsn_set_rate(struct rtsn_private *priv) static int rtsn_rmac_init(struct rtsn_private *priv) { const u8 *mac_addr = priv->ndev->dev_addr; - int ret; /* Set MAC address */ rtsn_write(priv, MRMAC0, (mac_addr[0] << 8) | mac_addr[1]); @@ -702,11 +701,7 @@ static int rtsn_rmac_init(struct rtsn_private *priv) /* Link verification */ rtsn_modify(priv, MLVC, MLVC_PLV, MLVC_PLV); - ret = rtsn_reg_wait(priv, MLVC, MLVC_PLV, 0); - if (ret) - return ret; - - return ret; + return rtsn_reg_wait(priv, MLVC, MLVC_PLV, 0); } static int rtsn_hw_init(struct rtsn_private *priv) From cd833378bafae8036da3c2731cacc5d63a299eb9 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Sun, 26 Jul 2026 00:08:51 +0900 Subject: [PATCH 0666/1433] net: remove conditional return with no effect Both branches of the check return the same value, so the check has no effect. Remove it and return the value directly. This is the result of running the Coccinelle script from scripts/coccinelle/misc/cond_return_no_effect.cocci. Signed-off-by: Sang-Heon Jeon Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260725150852.859188-4-ekffu200098@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/phy/microchip_t1.c | 6 +----- drivers/net/pse-pd/tps23881.c | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/drivers/net/phy/microchip_t1.c b/drivers/net/phy/microchip_t1.c index 62b36a318100..3292b2235c8f 100644 --- a/drivers/net/phy/microchip_t1.c +++ b/drivers/net/phy/microchip_t1.c @@ -1028,11 +1028,7 @@ static int lan87xx_read_status(struct phy_device *phydev) if (rc < 0) return rc; - rc = genphy_read_status_fixed(phydev); - if (rc < 0) - return rc; - - return rc; + return genphy_read_status_fixed(phydev); } static int lan87xx_config_aneg(struct phy_device *phydev) diff --git a/drivers/net/pse-pd/tps23881.c b/drivers/net/pse-pd/tps23881.c index 49d6389da067..8a3bbd623412 100644 --- a/drivers/net/pse-pd/tps23881.c +++ b/drivers/net/pse-pd/tps23881.c @@ -1520,11 +1520,7 @@ static int tps23881_i2c_probe(struct i2c_client *client) "failed to register PSE controller\n"); } - ret = tps23881_setup_irq(priv, client->irq); - if (ret) - return ret; - - return ret; + return tps23881_setup_irq(priv, client->irq); } static const struct i2c_device_id tps23881_id[] = { From 1f35011c281a3308877cb00d00d585b8fe790d03 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Sun, 26 Jul 2026 00:08:52 +0900 Subject: [PATCH 0667/1433] net: intel: remove conditional return with no effect Both branches of the check return the same value, so the check has no effect. Remove it and return the value directly. This is the result of running the Coccinelle script from scripts/coccinelle/misc/cond_return_no_effect.cocci. Signed-off-by: Sang-Heon Jeon Reviewed-by: Marcin Szycik Link: https://patch.msgid.link/20260725150852.859188-5-ekffu200098@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/intel/i40e/i40e_main.c | 8 +------- drivers/net/ethernet/intel/igb/e1000_i210.c | 6 +----- drivers/net/ethernet/intel/igc/igc_phy.c | 6 +----- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/drivers/net/ethernet/intel/i40e/i40e_main.c b/drivers/net/ethernet/intel/i40e/i40e_main.c index a04683004a56..0cd0e5597c90 100644 --- a/drivers/net/ethernet/intel/i40e/i40e_main.c +++ b/drivers/net/ethernet/intel/i40e/i40e_main.c @@ -4864,16 +4864,10 @@ static void i40e_control_rx_q(struct i40e_pf *pf, int pf_q, bool enable) **/ int i40e_control_wait_rx_q(struct i40e_pf *pf, int pf_q, bool enable) { - int ret = 0; - i40e_control_rx_q(pf, pf_q, enable); /* wait for the change to finish */ - ret = i40e_pf_rxq_wait(pf, pf_q, enable); - if (ret) - return ret; - - return ret; + return i40e_pf_rxq_wait(pf, pf_q, enable); } /** diff --git a/drivers/net/ethernet/intel/igb/e1000_i210.c b/drivers/net/ethernet/intel/igb/e1000_i210.c index 9db29b231d6a..784f9a7bcbed 100644 --- a/drivers/net/ethernet/intel/igb/e1000_i210.c +++ b/drivers/net/ethernet/intel/igb/e1000_i210.c @@ -756,11 +756,7 @@ static s32 __igb_access_xmdio_reg(struct e1000_hw *hw, u16 address, return ret_val; /* Recalibrate the device back to 0 */ - ret_val = hw->phy.ops.write_reg(hw, E1000_MMDAC, 0); - if (ret_val) - return ret_val; - - return ret_val; + return hw->phy.ops.write_reg(hw, E1000_MMDAC, 0); } /** diff --git a/drivers/net/ethernet/intel/igc/igc_phy.c b/drivers/net/ethernet/intel/igc/igc_phy.c index 4cf737fb3b21..b758a7e0f013 100644 --- a/drivers/net/ethernet/intel/igc/igc_phy.c +++ b/drivers/net/ethernet/intel/igc/igc_phy.c @@ -675,11 +675,7 @@ static s32 __igc_access_xmdio_reg(struct igc_hw *hw, u16 address, return ret_val; /* Recalibrate the device back to 0 */ - ret_val = hw->phy.ops.write_reg(hw, IGC_MMDAC, 0); - if (ret_val) - return ret_val; - - return ret_val; + return hw->phy.ops.write_reg(hw, IGC_MMDAC, 0); } /** From d25c5beea9a076f67f785a85631ffb4769691d4d Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sat, 25 Jul 2026 11:33:45 +0200 Subject: [PATCH 0668/1433] ipip: reject unsupported configurations in fill_forward_path The ipip fill_forward_path callback currently does not check for configurations that cannot be offloaded to hardware: - Collect metadata (flow-based) tunnels have no fixed destination and rely on per-packet tunnel metadata, so the forward path cannot be pre-computed. - TOS inheritance (parms.iph.tos & 0x1) requires copying the outer TOS from the inner packet at encapsulation time, which is not known during forward path resolution. Return -EOPNOTSUPP for both cases to fall back to the software forwarding path. Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260725-ipip-fill-forward-path-fix-v1-1-bc69fd3127d5@kernel.org Signed-off-by: Paolo Abeni --- net/ipv4/ipip.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/ipv4/ipip.c b/net/ipv4/ipip.c index d1aa048a6099..0831f6b81717 100644 --- a/net/ipv4/ipip.c +++ b/net/ipv4/ipip.c @@ -360,6 +360,12 @@ static int ipip_fill_forward_path(struct net_device_path_ctx *ctx, const struct iphdr *tiph = &tunnel->parms.iph; struct rtable *rt; + if (tunnel->collect_md) + return -EOPNOTSUPP; + + if (tunnel->parms.iph.tos & 0x1) + return -EOPNOTSUPP; + rt = ip_route_output(dev_net(ctx->dev), tiph->daddr, tiph->saddr, inet_dsfield_to_dscp(tiph->tos), tunnel->parms.link, RT_SCOPE_UNIVERSE); From a5c6ae8de11ef0c3aedbe5e123354aad87d0d925 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Tue, 28 Jul 2026 09:52:20 +0200 Subject: [PATCH 0669/1433] net: phy: micrel: Add loopback support for ksz9131 ksz9131 is configured for local loopback in a similar fashion as the ksz9031, with a need for full-duplex operation, but with some extra steps to take as specified in section 4.13.1 : 1. Configure the following registers: - MMD 1C, Register 15 = EEEE - MMD 1C, Register 16 = EEEE - MMD 1C, Register 18 = EEEE - MMD 1C, Register 1B = EEEE These 4 registers are marked as "Reserved" in the register map. When setting loopback up without configuring these 4 registers, the PHY appears to shut its RXC down, which can trigger failures on MACs that require it, such as stmmac. The datasheet does not specify to which state the registers must be reset when disabling loopback, so let's restore them to their measured initial values. This was discovered when trying to use stmmac selftests on imx8mp with a ksz9131 connected in RGMII. Signed-off-by: Maxime Chevallier Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260728075222.956780-1-maxime.chevallier@bootlin.com Signed-off-by: Paolo Abeni --- drivers/net/phy/micrel.c | 60 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 55df5efcfc86..ae830781824b 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -1161,6 +1161,65 @@ static int ksz9031_set_loopback(struct phy_device *phydev, bool enable, 5000, 500000, true); } +/* KSZ9131-specific sequence to enable loopback, registers are undocumented + * in the datasheet but mentionned in the local loopback mode configuration + * steps. + * + * Without taking these steps, the PHY appears to disable its RXC while in + * loopback mode, which may be needed by some MACs such as stmmac. + */ +static int ksz9131_loopback_enable(struct phy_device *phydev) +{ + int ret; + + ret = phy_write_mmd(phydev, 0x1c, 0x15, 0xeeee); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, 0x1c, 0x16, 0xeeee); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, 0x1c, 0x18, 0xeeee); + if (ret) + return ret; + + return phy_write_mmd(phydev, 0x1c, 0x1b, 0xeeee); +} + +/* KSZ9131 datasheet doesn't state how to deal with the MMD 0x1c registers + * when disabling loopback. + * + * Set them back to their measured initial state when disabling loopback, and + * ignore errors while doing so. + */ +static void ksz9131_loopback_disable(struct phy_device *phydev) +{ + phy_write_mmd(phydev, 0x1c, 0x15, 0x6eff); + phy_write_mmd(phydev, 0x1c, 0x16, 0xe6ff); + phy_write_mmd(phydev, 0x1c, 0x18, 0x43ff); + phy_write_mmd(phydev, 0x1c, 0x1b, 0x07ff); +} + +static int ksz9131_set_loopback(struct phy_device *phydev, bool enable, + int speed) +{ + int ret; + + if (enable) { + ret = ksz9131_loopback_enable(phydev); + if (ret) + return ret; + } + + ret = ksz9031_set_loopback(phydev, enable, speed); + + if (ret || !enable) + ksz9131_loopback_disable(phydev); + + return ret; +} + static int ksz9031_of_load_skew_values(struct phy_device *phydev, const struct device_node *of_node, u16 reg, size_t field_sz, @@ -6993,6 +7052,7 @@ static struct phy_driver ksphy_driver[] = { .cable_test_start = ksz9x31_cable_test_start, .cable_test_get_status = ksz9x31_cable_test_get_status, .get_features = ksz9477_get_features, + .set_loopback = ksz9131_set_loopback, }, { PHY_ID_MATCH_MODEL(PHY_ID_KSZ8873MLL), .name = "Micrel KSZ8873MLL Switch", From 4c6eb712a91fa079be6f9f1419c96e0ad2227081 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Tue, 28 Jul 2026 18:05:28 -0700 Subject: [PATCH 0670/1433] wifi: ath12k: fix stride mismatch in mac_phy_caps_parse() Currently, in ath12k_wmi_mac_phy_caps_parse(), kzalloc() sizes the mac_phy_caps buffer as tot_phy_id * len, where len is clamped to min(firmware_len, sizeof(struct ath12k_wmi_mac_phy_caps_params)). The subsequent memcpy() destination advances by sizeof(full struct) per slot via C pointer arithmetic, not by the clamped len. When firmware sends short TLVs, the second and later slots are written past the end of the allocation. The reader in ath12k_pull_mac_phy_cap_svc_ready_ext() also indexes the buffer with full-struct pointer arithmetic, so the allocation must match that stride. Fix by using kzalloc_objs(), which derives the element size from the pointer type, making allocation size and pointer stride provably consistent regardless of what len the firmware provides. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260728-mac_phy_caps_parse-stride-mismatch-v1-1-27a9c1a3fbd0@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index e9e7566e0f69..d5160af60e00 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -4774,14 +4774,16 @@ static int ath12k_wmi_mac_phy_caps_parse(struct ath12k_base *soc, if (svc_rdy_ext->n_mac_phy_caps >= svc_rdy_ext->tot_phy_id) return -ENOBUFS; - len = min_t(u16, len, sizeof(struct ath12k_wmi_mac_phy_caps_params)); if (!svc_rdy_ext->n_mac_phy_caps) { - svc_rdy_ext->mac_phy_caps = kzalloc((svc_rdy_ext->tot_phy_id) * len, - GFP_ATOMIC); + svc_rdy_ext->mac_phy_caps = + kzalloc_objs(*svc_rdy_ext->mac_phy_caps, + svc_rdy_ext->tot_phy_id, + GFP_ATOMIC); if (!svc_rdy_ext->mac_phy_caps) return -ENOMEM; } + len = min_t(u16, len, sizeof(struct ath12k_wmi_mac_phy_caps_params)); memcpy(svc_rdy_ext->mac_phy_caps + svc_rdy_ext->n_mac_phy_caps, ptr, len); svc_rdy_ext->n_mac_phy_caps++; return 0; From 7a246c72132eb943b5844ba79dad597b47429dba Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Tue, 28 Jul 2026 18:05:29 -0700 Subject: [PATCH 0671/1433] wifi: ath11k: fix stride mismatch in mac_phy_caps_parse() Currently, in ath11k_wmi_tlv_mac_phy_caps_parse(), kcalloc() sizes the mac_phy_caps buffer as tot_phy_id * len, where len is clamped to min(firmware_len, sizeof(struct wmi_mac_phy_capabilities)). The subsequent memcpy() destination advances by sizeof(full struct) per slot via C pointer arithmetic, not by the clamped len. When firmware sends short TLVs, the second and later slots are written past the end of the allocation. The reader in ath11k_pull_mac_phy_cap_svc_ready_ext() also indexes the buffer with full-struct pointer arithmetic, so the allocation must match that stride. Fix by using kzalloc_objs(), which derives the element size from the pointer type, making allocation size and pointer stride provably consistent regardless of what len the firmware provides. Compile tested only. Fixes: 5b90fc760db5 ("ath11k: fix wmi service ready ext tlv parsing") Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260728-mac_phy_caps_parse-stride-mismatch-v1-2-27a9c1a3fbd0@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/wmi.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c index d6feaa710fe2..66547e9ee16c 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.c +++ b/drivers/net/wireless/ath/ath11k/wmi.c @@ -4809,14 +4809,16 @@ static int ath11k_wmi_tlv_mac_phy_caps_parse(struct ath11k_base *soc, if (svc_rdy_ext->n_mac_phy_caps >= svc_rdy_ext->tot_phy_id) return -ENOBUFS; - len = min_t(u16, len, sizeof(struct wmi_mac_phy_capabilities)); if (!svc_rdy_ext->n_mac_phy_caps) { - svc_rdy_ext->mac_phy_caps = kcalloc(svc_rdy_ext->tot_phy_id, - len, GFP_ATOMIC); + svc_rdy_ext->mac_phy_caps = + kzalloc_objs(*svc_rdy_ext->mac_phy_caps, + svc_rdy_ext->tot_phy_id, + GFP_ATOMIC); if (!svc_rdy_ext->mac_phy_caps) return -ENOMEM; } + len = min_t(u16, len, sizeof(struct wmi_mac_phy_capabilities)); memcpy(svc_rdy_ext->mac_phy_caps + svc_rdy_ext->n_mac_phy_caps, ptr, len); svc_rdy_ext->n_mac_phy_caps++; return 0; From 8b8202b2e31367434a5079a6faee31588e5b4aa4 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Thu, 30 Jul 2026 01:04:56 +0900 Subject: [PATCH 0672/1433] wifi: ath6kl: return 0 explicitly in ath6kl_init_upload() status is always zero at the last return in ath6kl_init_upload(). Explicitly return 0 on the success path instead of returning status. No functional change. Signed-off-by: Sang-Heon Jeon Link: https://patch.msgid.link/20260729160458.201962-1-ekffu200098@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath6kl/init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath6kl/init.c b/drivers/net/wireless/ath/ath6kl/init.c index 782209dcb782..6481da4c1991 100644 --- a/drivers/net/wireless/ath/ath6kl/init.c +++ b/drivers/net/wireless/ath/ath6kl/init.c @@ -1570,7 +1570,7 @@ static int ath6kl_init_upload(struct ath6kl *ar) if (status) return status; - return status; + return 0; } int ath6kl_init_hw_params(struct ath6kl *ar) From 61a878bafdf67748cde1358c0533b9dc73bda789 Mon Sep 17 00:00:00 2001 From: Manuel Ebner Date: Wed, 22 Jul 2026 11:32:59 +0200 Subject: [PATCH 0673/1433] dt-bindings: net: microchip: fix entry Remove needless ' ID)' Signed-off-by: Manuel Ebner Link: https://patch.msgid.link/20260722093259.3109588-2-manuelebnerli@mailbox.org Signed-off-by: Jakub Kicinski --- Documentation/devicetree/bindings/net/microchip,lan95xx.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/net/microchip,lan95xx.yaml b/Documentation/devicetree/bindings/net/microchip,lan95xx.yaml index b9c394009040..2bf6be9807a7 100644 --- a/Documentation/devicetree/bindings/net/microchip,lan95xx.yaml +++ b/Documentation/devicetree/bindings/net/microchip,lan95xx.yaml @@ -35,7 +35,7 @@ properties: - usb424,9906 # SMSC9505A USB Ethernet Device (HAL) - usb424,9907 # SMSC9500 USB Ethernet Device (Alternate ID) - usb424,9908 # SMSC9500A USB Ethernet Device (Alternate ID) - - usb424,9909 # SMSC9512/9514 USB Hub & Ethernet Device ID) + - usb424,9909 # SMSC9512/9514 USB Hub & Ethernet Device - usb424,9e00 # SMSC9500A USB Ethernet Device - usb424,9e01 # SMSC9505A USB Ethernet Device - usb424,9e08 # SMSC LAN89530 USB Ethernet Device From 53531e6a644a48c2d5a9423f084ae4e91ec6019a Mon Sep 17 00:00:00 2001 From: Jack Ma Date: Fri, 24 Jul 2026 00:26:16 +0000 Subject: [PATCH 0674/1433] net: nexthop: add NHA_DST_PORT for fdb nexthops Commit 1274e1cc4226 ("vxlan: ecmp support for mac fdb entries") lets a single inner MAC be reached through a group of remote VTEPs, with the kernel flow-hashing across the group members. Each member carries its own remote IP, but the UDP destination port is always taken from the VXLAN device (vxlan->cfg.dst_port) and cannot be set per member. Some deployments pack several receivers behind one underlay IP and tell them apart by UDP port, so they need a per-nexthop destination port to spread flows across (IP, port) tuples rather than IP alone. Add a netlink attribute NHA_DST_PORT (__be16, mirroring NDA_PORT) that carries an optional UDP destination port on an fdb nexthop. It is only accepted together with NHA_FDB and NHA_GATEWAY; it is stored in struct nh_info and echoed back on dump. The attribute is named generically rather than fdb-specific so it can be reused should another nexthop type ever need a destination port. This patch is control-plane plumbing only; the VXLAN datapath is wired up in a follow-up patch, so behaviour is unchanged for now. Signed-off-by: Jack Ma Reviewed-by: Ido Schimmel Reviewed-by: David Ahern Link: https://patch.msgid.link/20260724-b4-vxlan-fdb-port-v5-1-cd1c6aeee058@gmail.com Signed-off-by: Jakub Kicinski --- include/net/nexthop.h | 2 ++ include/uapi/linux/nexthop.h | 3 +++ net/ipv4/nexthop.c | 20 +++++++++++++++++++- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/include/net/nexthop.h b/include/net/nexthop.h index 572e69cda476..7673aaeff3e2 100644 --- a/include/net/nexthop.h +++ b/include/net/nexthop.h @@ -28,6 +28,7 @@ struct nh_config { u8 nh_protocol; u8 nh_blackhole; u8 nh_fdb; + __be16 nh_dst_port; u32 nh_flags; int nh_ifindex; @@ -63,6 +64,7 @@ struct nh_info { u8 family; bool reject_nh; bool fdb_nh; + __be16 dst_port; union { struct fib_nh_common fib_nhc; diff --git a/include/uapi/linux/nexthop.h b/include/uapi/linux/nexthop.h index bc49baf4a267..59cf1cee93bd 100644 --- a/include/uapi/linux/nexthop.h +++ b/include/uapi/linux/nexthop.h @@ -83,6 +83,9 @@ enum { /* u32; read-only; whether any driver collects HW stats */ NHA_HW_STATS_USED, + /* be16; UDP destination port for an fdb nexthop (e.g. VXLAN) */ + NHA_DST_PORT, + __NHA_MAX, }; diff --git a/net/ipv4/nexthop.c b/net/ipv4/nexthop.c index 0f1e21a5c812..af1dcb8ea427 100644 --- a/net/ipv4/nexthop.c +++ b/net/ipv4/nexthop.c @@ -39,6 +39,7 @@ static const struct nla_policy rtm_nh_policy_new[] = { [NHA_ENCAP_TYPE] = { .type = NLA_U16 }, [NHA_ENCAP] = { .type = NLA_NESTED }, [NHA_FDB] = { .type = NLA_FLAG }, + [NHA_DST_PORT] = NLA_POLICY_MIN(NLA_BE16, 1), [NHA_RES_GROUP] = { .type = NLA_NESTED }, [NHA_HW_STATS_ENABLE] = NLA_POLICY_MAX(NLA_U32, true), }; @@ -956,6 +957,9 @@ static int nh_fill_node(struct sk_buff *skb, struct nexthop *nh, } else if (nhi->fdb_nh) { if (nla_put_flag(skb, NHA_FDB)) goto nla_put_failure; + if (nhi->dst_port && + nla_put_be16(skb, NHA_DST_PORT, nhi->dst_port)) + goto nla_put_failure; } else { const struct net_device *dev; @@ -1055,6 +1059,9 @@ static size_t nh_nlmsg_size_single(struct nexthop *nh) break; } + if (nhi->dst_port) + sz += nla_total_size(2); /* NHA_DST_PORT */ + if (nhi->fib_nhc.nhc_lwtstate) { sz += lwtunnel_get_encap_size(nhi->fib_nhc.nhc_lwtstate); sz += nla_total_size(2); /* NHA_ENCAP_TYPE */ @@ -2965,8 +2972,10 @@ static struct nexthop *nexthop_create(struct net *net, struct nh_config *cfg, nhi->family = cfg->nh_family; nhi->fib_nhc.nhc_scope = RT_SCOPE_LINK; - if (cfg->nh_fdb) + if (cfg->nh_fdb) { nhi->fdb_nh = 1; + nhi->dst_port = cfg->nh_dst_port; + } if (cfg->nh_blackhole) { nhi->reject_nh = 1; @@ -3156,6 +3165,15 @@ static int rtm_to_nh_config(struct net *net, struct sk_buff *skb, cfg->nh_fdb = nla_get_flag(tb[NHA_FDB]); } + if (tb[NHA_DST_PORT]) { + if (!tb[NHA_FDB] || !tb[NHA_GATEWAY]) { + NL_SET_ERR_MSG(extack, + "Destination port can only be set on fdb nexthops that have a gateway"); + goto out; + } + cfg->nh_dst_port = nla_get_be16(tb[NHA_DST_PORT]); + } + if (tb[NHA_GROUP]) { if (nhm->nh_family != AF_UNSPEC) { NL_SET_ERR_MSG(extack, "Invalid family for group"); From 951085f82873dc53a62181499a7a7f77b70f9343 Mon Sep 17 00:00:00 2001 From: Jack Ma Date: Fri, 24 Jul 2026 00:26:17 +0000 Subject: [PATCH 0675/1433] vxlan: honor per-nexthop fdb destination port When an fdb entry points at a nexthop group, vxlan_fdb_nh_path_select() resolves the selected leg's remote IP but leaves the UDP destination port at the device default (vxlan->cfg.dst_port). Extend nexthop_path_fdb_result() to also return the selected nexthop's NHA_DST_PORT (0 when unset) and have vxlan_fdb_nh_path_select() store it in rdst->remote_port. vxlan_xmit_one() already prefers rdst->remote_port when non-zero and falls back to the device port otherwise, so nexthops without a port are unaffected. This lets one fdb nexthop group load-balance a flow across legs that share an underlay IP but differ in UDP destination port. Signed-off-by: Jack Ma Reviewed-by: Ido Schimmel Reviewed-by: David Ahern Link: https://patch.msgid.link/20260724-b4-vxlan-fdb-port-v5-2-cd1c6aeee058@gmail.com Signed-off-by: Jakub Kicinski --- include/net/nexthop.h | 4 +++- include/net/vxlan.h | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/include/net/nexthop.h b/include/net/nexthop.h index 7673aaeff3e2..f86c115074d7 100644 --- a/include/net/nexthop.h +++ b/include/net/nexthop.h @@ -576,7 +576,8 @@ struct fib_nh_common *nexthop_fdb_nhc(struct nexthop *nh) } static inline struct fib_nh_common *nexthop_path_fdb_result(struct nexthop *nh, - int hash) + int hash, + __be16 *dst_port) { struct nh_info *nhi; struct nexthop *nhp; @@ -585,6 +586,7 @@ static inline struct fib_nh_common *nexthop_path_fdb_result(struct nexthop *nh, if (unlikely(!nhp)) return NULL; nhi = rcu_dereference(nhp->nh_info); + *dst_port = nhi->dst_port; return &nhi->fib_nhc; } #endif diff --git a/include/net/vxlan.h b/include/net/vxlan.h index dfba89695efc..6e64757151b8 100644 --- a/include/net/vxlan.h +++ b/include/net/vxlan.h @@ -567,8 +567,9 @@ static inline bool vxlan_fdb_nh_path_select(struct nexthop *nh, struct vxlan_rdst *rdst) { struct fib_nh_common *nhc; + __be16 dst_port = 0; - nhc = nexthop_path_fdb_result(nh, hash >> 1); + nhc = nexthop_path_fdb_result(nh, hash >> 1, &dst_port); if (unlikely(!nhc)) return false; @@ -583,6 +584,8 @@ static inline bool vxlan_fdb_nh_path_select(struct nexthop *nh, break; } + rdst->remote_port = dst_port; + return true; } From 992965451db727a15988931655a825c5e0312edb Mon Sep 17 00:00:00 2001 From: Jack Ma Date: Fri, 24 Jul 2026 00:26:18 +0000 Subject: [PATCH 0676/1433] selftests: net: add coverage for fdb nexthop dst_port Add coverage for the new per-nexthop VXLAN destination port (NHA_DST_PORT). In fib_nexthops.sh, new ipv4_fdb_port_fcnal() and ipv6_fdb_port_fcnal() tests check that a dst_port is accepted on an fdb nexthop that has a gateway and echoed back on dump, that it is rejected without a gateway and rejected when zero, that a group may hold legs that differ only in UDP port, and that a portless fdb nexthop omits the attribute. The tests SKIP when iproute2 lacks the "dst_port" keyword. In test_vxlan_nh.sh, basic_tx_common() gains a second fdb nexthop group whose nexthop carries a destination port that differs from the VXLAN device default, plus a flower filter keyed on that port, to confirm the per-nexthop port is used on the wire. The test now requires an iproute2 with dst_port support. Signed-off-by: Jack Ma Reviewed-by: Ido Schimmel Reviewed-by: David Ahern Link: https://patch.msgid.link/20260724-b4-vxlan-fdb-port-v5-3-cd1c6aeee058@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/fib_nexthops.sh | 83 ++++++++++++++++++++ tools/testing/selftests/net/test_vxlan_nh.sh | 40 +++++++++- 2 files changed, 121 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/net/fib_nexthops.sh b/tools/testing/selftests/net/fib_nexthops.sh index ac868a731694..3d347126730a 100755 --- a/tools/testing/selftests/net/fib_nexthops.sh +++ b/tools/testing/selftests/net/fib_nexthops.sh @@ -30,6 +30,7 @@ IPV4_TESTS=" ipv4_large_res_grp ipv4_compat_mode ipv4_fdb_grp_fcnal + ipv4_fdb_port_fcnal ipv4_mpath_select ipv4_torture ipv4_res_torture @@ -44,6 +45,7 @@ IPV6_TESTS=" ipv6_large_res_grp ipv6_compat_mode ipv6_fdb_grp_fcnal + ipv6_fdb_port_fcnal ipv6_mpath_select ipv6_torture ipv6_res_torture @@ -432,6 +434,15 @@ check_nexthop_fdb_support() fi } +check_nexthop_fdb_port_support() +{ + $IP nexthop help 2>&1 | grep -q "dst_port" + if [ $? -ne 0 ]; then + echo "SKIP: iproute2 too old, missing nexthop dst_port support" + return $ksft_skip + fi +} + check_nexthop_res_support() { $IP nexthop help 2>&1 | grep -q resilient @@ -541,6 +552,42 @@ ipv6_fdb_grp_fcnal() $IP link del dev vx10 } +ipv6_fdb_port_fcnal() +{ + echo + echo "IPv6 fdb nexthop dst_port functional" + echo "------------------------------------" + + check_nexthop_fdb_port_support + if [ $? -eq $ksft_skip ]; then + return $ksft_skip + fi + + # NHA_DST_PORT: optional per-nexthop VXLAN destination UDP port, + # letting an fdb nexthop group balance a flow across legs that share + # an underlay IP but listen on different UDP ports. + run_cmd "$IP nexthop add id 80 via 2001:db8:91::2 fdb dst_port 4790" + check_nexthop "id 80" \ + "id 80 via 2001:db8:91::2 scope link fdb dst_port 4790" + log_test $? 0 "Fdb nexthop with dst_port" + + run_cmd "$IP nexthop add id 81 fdb dst_port 4790" + log_test $? 2 "Fdb nexthop with dst_port but no gateway" + + run_cmd "$IP nexthop add id 81 via 2001:db8:91::2 fdb dst_port 0" + log_test $? 2 "Fdb nexthop with dst_port 0" + + run_cmd "$IP nexthop add id 82 via 2001:db8:91::2 fdb dst_port 4789" + run_cmd "$IP nexthop add id 83 via 2001:db8:91::3 fdb dst_port 5789" + run_cmd "$IP nexthop add id 106 group 82/83 fdb" + check_nexthop "id 106" "id 106 group 82/83 fdb" + log_test $? 0 "Fdb nexthop group with legs differing in dst_port" + + run_cmd "$IP nexthop add id 84 via 2001:db8:91::2 fdb" + check_nexthop "id 84" "id 84 via 2001:db8:91::2 scope link fdb" + log_test $? 0 "Fdb nexthop without dst_port omits dst_port" +} + ipv4_fdb_grp_fcnal() { local rc @@ -641,6 +688,42 @@ ipv4_fdb_grp_fcnal() $IP link del dev vx10 } +ipv4_fdb_port_fcnal() +{ + echo + echo "IPv4 fdb nexthop dst_port functional" + echo "------------------------------------" + + check_nexthop_fdb_port_support + if [ $? -eq $ksft_skip ]; then + return $ksft_skip + fi + + # NHA_DST_PORT: optional per-nexthop VXLAN destination UDP port, + # letting an fdb nexthop group balance a flow across legs that share + # an underlay IP but listen on different UDP ports. + run_cmd "$IP nexthop add id 30 via 172.16.1.2 fdb dst_port 4790" + check_nexthop "id 30" \ + "id 30 via 172.16.1.2 scope link fdb dst_port 4790" + log_test $? 0 "Fdb nexthop with dst_port" + + run_cmd "$IP nexthop add id 31 fdb dst_port 4790" + log_test $? 2 "Fdb nexthop with dst_port but no gateway" + + run_cmd "$IP nexthop add id 31 via 172.16.1.2 fdb dst_port 0" + log_test $? 2 "Fdb nexthop with dst_port 0" + + run_cmd "$IP nexthop add id 32 via 172.16.1.2 fdb dst_port 4789" + run_cmd "$IP nexthop add id 33 via 172.16.1.3 fdb dst_port 5789" + run_cmd "$IP nexthop add id 105 group 32/33 fdb" + check_nexthop "id 105" "id 105 group 32/33 fdb" + log_test $? 0 "Fdb nexthop group with legs differing in dst_port" + + run_cmd "$IP nexthop add id 34 via 172.16.1.2 fdb" + check_nexthop "id 34" "id 34 via 172.16.1.2 scope link fdb" + log_test $? 0 "Fdb nexthop without dst_port omits dst_port" +} + ipv4_mpath_select() { local rc dev match h addr diff --git a/tools/testing/selftests/net/test_vxlan_nh.sh b/tools/testing/selftests/net/test_vxlan_nh.sh index 20f3369f776b..5ce6f27f6cf4 100755 --- a/tools/testing/selftests/net/test_vxlan_nh.sh +++ b/tools/testing/selftests/net/test_vxlan_nh.sh @@ -56,6 +56,17 @@ tc_stats_get() tc_rule_handle_stats_get "dev dummy1 egress" 101 ".packets" "-n $ns1" } +nh_stats_get_port() +{ + ip -n "$ns1" -s -j nexthop show id 20 | \ + jq ".[][\"group_stats\"][][\"packets\"]" +} + +tc_stats_get_port() +{ + tc_rule_handle_stats_get "dev dummy1 egress" 102 ".packets" "-n $ns1" +} + basic_tx_common() { local af_str=$1; shift @@ -90,6 +101,31 @@ basic_tx_common() busywait "$BUSYWAIT_TIMEOUT" until_counter_is "== 1" tc_stats_get > /dev/null check_err $? "tc filter stats did not increase" + # Add a second FDB nexthop group whose nexthop carries a per-nexthop + # destination port (NHA_DST_PORT) that differs from the VXLAN device + # default. Matching outer traffic must egress with that port, so a + # separate flower filter keyed on the new port catches it. + run_cmd "tc -n $ns1 filter add dev dummy1 egress proto $proto \ + pref 1 handle 102 flower ip_proto udp dst_ip $remote_addr \ + dst_port 4790 action pass" + + run_cmd "ip -n $ns1 nexthop add id 2 via $remote_addr fdb dst_port 4790" + run_cmd "ip -n $ns1 nexthop add id 20 group 2 fdb" + + run_cmd "bridge -n $ns1 fdb add 00:11:22:33:44:66 dev vx0 \ + self static nhid 20" + + run_cmd "ip netns exec $ns1 mausezahn vx0 -a own \ + -b 00:11:22:33:44:66 -c 1 -q" + + busywait "$BUSYWAIT_TIMEOUT" until_counter_is "== 1" \ + nh_stats_get_port > /dev/null + check_err $? "FDB nexthop group stats did not increase (with port)" + + busywait "$BUSYWAIT_TIMEOUT" until_counter_is "== 1" \ + tc_stats_get_port > /dev/null + check_err $? "tc filter stats did not increase (with port)" + log_test "VXLAN FDB nexthop: $af_str basic Tx" } @@ -210,8 +246,8 @@ require_command arping require_command ndisc6 require_command jq -if ! ip nexthop help 2>&1 | grep -q "stats"; then - echo "SKIP: iproute2 ip too old, missing nexthop stats support" +if ! ip nexthop help 2>&1 | grep -q "dst_port"; then + echo "SKIP: iproute2 ip too old, missing nexthop dst_port support" exit "$ksft_skip" fi From 8e4d7d120734936cb961d9dc46b788e0487d3256 Mon Sep 17 00:00:00 2001 From: Bence Csokas Date: Mon, 27 Jul 2026 16:02:07 +0200 Subject: [PATCH 0677/1433] wanxl: Remove pci_map_single_debug() Since commit 24dd377a76b0 ("wan: wanxl: switch from 'pci_' to 'dma_' API") this has been dead code anyways. The pci_map_single() function it attempts to redefine has been removed in commit 7968778914e5 ("PCI: Remove the deprecated "pci-dma-compat.h" API"). Reviewed-by: Simon Horman Link: https://lore.kernel.org/all/20260709151401.GO1364329@horms.kernel.org Signed-off-by: Bence Csokas Link: https://patch.msgid.link/20260727-wanxl-cleanup-v2-1-3826430829c9@arm.com Signed-off-by: Jakub Kicinski --- drivers/net/wan/wanxl.c | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/drivers/net/wan/wanxl.c b/drivers/net/wan/wanxl.c index 065c00c12cc1..c9873f07e06c 100644 --- a/drivers/net/wan/wanxl.c +++ b/drivers/net/wan/wanxl.c @@ -37,7 +37,6 @@ static const char *version = "wanXL serial card driver version: 0.48"; #define PLX_CTL_RESET 0x40000000 /* adapter reset */ #undef DEBUG_PKT -#undef DEBUG_PCI /* MAILBOX #1 - PUTS COMMANDS */ #define MBX1_CMD_ABORTJ 0x85000000 /* Abort and Jump */ @@ -88,22 +87,6 @@ static inline port_status_t *get_status(struct port *port) return &port->card->status->port_status[port->node]; } -#ifdef DEBUG_PCI -static inline dma_addr_t pci_map_single_debug(struct pci_dev *pdev, void *ptr, - size_t size, int direction) -{ - dma_addr_t addr = dma_map_single(&pdev->dev, ptr, size, direction); - - if (addr + size > 0x100000000LL) - pr_crit("%s: pci_map_single() returned memory at 0x%llx!\n", - pci_name(pdev), (unsigned long long)addr); - return addr; -} - -#undef pci_map_single -#define pci_map_single pci_map_single_debug -#endif - /* Cable and/or personality module change interrupt service */ static inline void wanxl_cable_intr(struct port *port) { From 2f2e974f8fbe8a19311842afaf66394f4caa0ae4 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Fri, 24 Jul 2026 13:15:22 +0800 Subject: [PATCH 0678/1433] net: mctp: usb: Include version indicator in max packet size defines DSP0283 v1.1.0 will introduce larger maximum packet sizes. In preparation, indicate that the current maxima are specific to v1.0.x. Signed-off-by: Jeremy Kerr Link: https://patch.msgid.link/20260724-dev-mctp-usb-1-1-v5-1-e66bbba0dbdc@codeconstruct.com.au Signed-off-by: Jakub Kicinski --- drivers/net/mctp/mctp-usb.c | 8 ++++---- include/linux/usb/mctp-usb.h | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/net/mctp/mctp-usb.c b/drivers/net/mctp/mctp-usb.c index fade65f2f269..545eff06322c 100644 --- a/drivers/net/mctp/mctp-usb.c +++ b/drivers/net/mctp/mctp-usb.c @@ -76,7 +76,7 @@ static netdev_tx_t mctp_usb_start_xmit(struct sk_buff *skb, plen = skb->len; - if (plen + sizeof(*hdr) > MCTP_USB_XFER_SIZE) + if (plen + sizeof(*hdr) > MCTP_USB_1_0_XFER_SIZE) goto err_drop; rc = skb_cow_head(skb, sizeof(*hdr)); @@ -128,7 +128,7 @@ static int mctp_usb_rx_queue(struct mctp_usb *mctp_usb, gfp_t gfp) struct sk_buff *skb; int rc; - skb = __netdev_alloc_skb(mctp_usb->netdev, MCTP_USB_XFER_SIZE, gfp); + skb = __netdev_alloc_skb(mctp_usb->netdev, MCTP_USB_1_0_XFER_SIZE, gfp); if (!skb) { rc = -ENOMEM; goto err_retry; @@ -136,7 +136,7 @@ static int mctp_usb_rx_queue(struct mctp_usb *mctp_usb, gfp_t gfp) usb_fill_bulk_urb(mctp_usb->rx_urb, mctp_usb->usbdev, usb_rcvbulkpipe(mctp_usb->usbdev, mctp_usb->ep_in), - skb->data, MCTP_USB_XFER_SIZE, + skb->data, MCTP_USB_1_0_XFER_SIZE, mctp_usb_in_complete, skb); rc = usb_submit_urb(mctp_usb->rx_urb, gfp); @@ -301,7 +301,7 @@ static void mctp_usb_netdev_setup(struct net_device *dev) dev->mtu = MCTP_USB_MTU_MIN; dev->min_mtu = MCTP_USB_MTU_MIN; - dev->max_mtu = MCTP_USB_MTU_MAX; + dev->max_mtu = MCTP_USB_1_0_MTU_MAX; dev->hard_header_len = sizeof(struct mctp_usb_hdr); dev->tx_queue_len = DEFAULT_TX_QUEUE_LEN; diff --git a/include/linux/usb/mctp-usb.h b/include/linux/usb/mctp-usb.h index a2f6f1e04efb..47e2e3931d63 100644 --- a/include/linux/usb/mctp-usb.h +++ b/include/linux/usb/mctp-usb.h @@ -21,10 +21,11 @@ struct mctp_usb_hdr { u8 len; } __packed; -#define MCTP_USB_XFER_SIZE 512 +/* max transfer size for DSP0283 v1.0 */ +#define MCTP_USB_1_0_XFER_SIZE 512 #define MCTP_USB_BTU 68 #define MCTP_USB_MTU_MIN MCTP_USB_BTU -#define MCTP_USB_MTU_MAX (U8_MAX - sizeof(struct mctp_usb_hdr)) +#define MCTP_USB_1_0_MTU_MAX (U8_MAX - sizeof(struct mctp_usb_hdr)) #define MCTP_USB_DMTF_ID 0x1ab4 #endif /* __LINUX_USB_MCTP_USB_H */ From 05b1a3a5eeaa64a5d412688e682a3b13acd99e8b Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Fri, 24 Jul 2026 13:15:23 +0800 Subject: [PATCH 0679/1433] net: mctp: usb: Use packet-length max for maximum packet-size check The max packet size is smaller than the max transfer size, as we only have a u8 length field in the transport header. Add a define for the maximum representable length, and use that for our check. Use this for the MTU maximum calculation too. Signed-off-by: Jeremy Kerr Link: https://patch.msgid.link/20260724-dev-mctp-usb-1-1-v5-2-e66bbba0dbdc@codeconstruct.com.au Signed-off-by: Jakub Kicinski --- drivers/net/mctp/mctp-usb.c | 2 +- include/linux/usb/mctp-usb.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/mctp/mctp-usb.c b/drivers/net/mctp/mctp-usb.c index 545eff06322c..c6e36b63e87a 100644 --- a/drivers/net/mctp/mctp-usb.c +++ b/drivers/net/mctp/mctp-usb.c @@ -76,7 +76,7 @@ static netdev_tx_t mctp_usb_start_xmit(struct sk_buff *skb, plen = skb->len; - if (plen + sizeof(*hdr) > MCTP_USB_1_0_XFER_SIZE) + if (plen + sizeof(*hdr) > MCTP_USB_1_0_PKTLEN_MAX) goto err_drop; rc = skb_cow_head(skb, sizeof(*hdr)); diff --git a/include/linux/usb/mctp-usb.h b/include/linux/usb/mctp-usb.h index 47e2e3931d63..2bece8afd1c7 100644 --- a/include/linux/usb/mctp-usb.h +++ b/include/linux/usb/mctp-usb.h @@ -25,7 +25,8 @@ struct mctp_usb_hdr { #define MCTP_USB_1_0_XFER_SIZE 512 #define MCTP_USB_BTU 68 #define MCTP_USB_MTU_MIN MCTP_USB_BTU -#define MCTP_USB_1_0_MTU_MAX (U8_MAX - sizeof(struct mctp_usb_hdr)) +#define MCTP_USB_1_0_PKTLEN_MAX U8_MAX +#define MCTP_USB_1_0_MTU_MAX (MCTP_USB_1_0_PKTLEN_MAX - sizeof(struct mctp_usb_hdr)) #define MCTP_USB_DMTF_ID 0x1ab4 #endif /* __LINUX_USB_MCTP_USB_H */ From b8564b19c0fffb0ddfef760655c419b3953ceb0e Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Fri, 24 Jul 2026 13:15:24 +0800 Subject: [PATCH 0680/1433] net: mctp: usblib: Move RX transfer processing to a new mctp-usblib The processing of USB receive transfers is common to both sides of a MCTP over USB transport. In order to support a future gadget driver, move the current host-side driver into a new common file, mctp-usblib. This currently handles the submit-complete-packetise process of the receive path of the USB transport. We'll add transmit handling in an upcoming change. Signed-off-by: Jeremy Kerr Link: https://patch.msgid.link/20260724-dev-mctp-usb-1-1-v5-3-e66bbba0dbdc@codeconstruct.com.au Signed-off-by: Jakub Kicinski --- drivers/net/mctp/Kconfig | 11 ++ drivers/net/mctp/Makefile | 1 + drivers/net/mctp/mctp-usb.c | 104 +++++-------------- drivers/net/mctp/mctp-usblib.c | 179 +++++++++++++++++++++++++++++++++ include/linux/usb/mctp-usb.h | 26 +++++ 5 files changed, 240 insertions(+), 81 deletions(-) create mode 100644 drivers/net/mctp/mctp-usblib.c diff --git a/drivers/net/mctp/Kconfig b/drivers/net/mctp/Kconfig index cf325ab0b1ef..a564a792801d 100644 --- a/drivers/net/mctp/Kconfig +++ b/drivers/net/mctp/Kconfig @@ -47,9 +47,20 @@ config MCTP_TRANSPORT_I3C A MCTP protocol network device is created for each I3C bus having a "mctp-controller" devicetree property. +config MCTP_TRANSPORT_USBLIB + tristate "MCTP over USB common library" + depends on USB + help + Common protocol handling functions for MCTP-over-USB transport + implementations, suitable for use in either host- or gadget-side + transport driver + + This will be automatically enabled by the transport driver. + config MCTP_TRANSPORT_USB tristate "MCTP USB transport" depends on USB + select MCTP_TRANSPORT_USBLIB help Provides a driver to access MCTP devices over USB transport, defined by DMTF specification DSP0283. diff --git a/drivers/net/mctp/Makefile b/drivers/net/mctp/Makefile index c36006849a1e..c870b62d3f1c 100644 --- a/drivers/net/mctp/Makefile +++ b/drivers/net/mctp/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_MCTP_SERIAL) += mctp-serial.o obj-$(CONFIG_MCTP_TRANSPORT_I2C) += mctp-i2c.o obj-$(CONFIG_MCTP_TRANSPORT_I3C) += mctp-i3c.o obj-$(CONFIG_MCTP_TRANSPORT_USB) += mctp-usb.o +obj-$(CONFIG_MCTP_TRANSPORT_USBLIB) += mctp-usblib.o diff --git a/drivers/net/mctp/mctp-usb.c b/drivers/net/mctp/mctp-usb.c index c6e36b63e87a..eedcf759e131 100644 --- a/drivers/net/mctp/mctp-usb.c +++ b/drivers/net/mctp/mctp-usb.c @@ -28,6 +28,8 @@ struct mctp_usb { u8 ep_in; u8 ep_out; + struct mctp_usblib_rx rx; + struct urb *tx_urb; struct urb *rx_urb; @@ -125,24 +127,23 @@ static const unsigned long RX_RETRY_DELAY = HZ / 4; static int mctp_usb_rx_queue(struct mctp_usb *mctp_usb, gfp_t gfp) { unsigned long flags; - struct sk_buff *skb; + size_t len; + void *buf; int rc; - skb = __netdev_alloc_skb(mctp_usb->netdev, MCTP_USB_1_0_XFER_SIZE, gfp); - if (!skb) { - rc = -ENOMEM; + rc = mctp_usblib_rx_prepare(mctp_usb->netdev, &mctp_usb->rx, + &buf, &len, gfp); + if (rc) goto err_retry; - } usb_fill_bulk_urb(mctp_usb->rx_urb, mctp_usb->usbdev, usb_rcvbulkpipe(mctp_usb->usbdev, mctp_usb->ep_in), - skb->data, MCTP_USB_1_0_XFER_SIZE, - mctp_usb_in_complete, skb); + buf, len, mctp_usb_in_complete, mctp_usb); rc = usb_submit_urb(mctp_usb->rx_urb, gfp); if (rc) { netdev_dbg(mctp_usb->netdev, "rx urb submit failure: %d\n", rc); - kfree_skb(skb); + mctp_usblib_rx_cancel(&mctp_usb->rx); if (rc == -ENOMEM) goto err_retry; } @@ -159,93 +160,28 @@ static int mctp_usb_rx_queue(struct mctp_usb *mctp_usb, gfp_t gfp) static void mctp_usb_in_complete(struct urb *urb) { - struct sk_buff *skb = urb->context; - struct net_device *netdev = skb->dev; - struct mctp_usb *mctp_usb = netdev_priv(netdev); - struct mctp_skb_cb *cb; - unsigned int len; + struct mctp_usb *mctp_usb = urb->context; + struct net_device *netdev = mctp_usb->netdev; int status; status = urb->status; switch (status) { + default: + netdev_dbg(netdev, "unexpected rx urb status: %d\n", status); + fallthrough; case -ENOENT: case -ECONNRESET: case -ESHUTDOWN: case -EPROTO: - kfree_skb(skb); + mctp_usblib_rx_cancel(&mctp_usb->rx); return; case 0: + mctp_usblib_rx_complete(netdev, &mctp_usb->rx, + urb->actual_length); break; - default: - netdev_dbg(netdev, "unexpected rx urb status: %d\n", status); - kfree_skb(skb); - return; } - len = urb->actual_length; - __skb_put(skb, len); - - while (skb) { - struct sk_buff *skb2 = NULL; - struct mctp_usb_hdr *hdr; - u8 pkt_len; /* length of MCTP packet, no USB header */ - - skb_reset_mac_header(skb); - hdr = skb_pull_data(skb, sizeof(*hdr)); - if (!hdr) - break; - - if (be16_to_cpu(hdr->id) != MCTP_USB_DMTF_ID) { - netdev_dbg(netdev, "rx: invalid id %04x\n", - be16_to_cpu(hdr->id)); - break; - } - - if (hdr->len < - sizeof(struct mctp_hdr) + sizeof(struct mctp_usb_hdr)) { - netdev_dbg(netdev, "rx: short packet (hdr) %d\n", - hdr->len); - break; - } - - /* we know we have at least sizeof(struct mctp_usb_hdr) here */ - pkt_len = hdr->len - sizeof(struct mctp_usb_hdr); - if (pkt_len > skb->len) { - netdev_dbg(netdev, - "rx: short packet (xfer) %d, actual %d\n", - hdr->len, skb->len); - break; - } - - if (pkt_len < skb->len) { - /* more packets may follow - clone to a new - * skb to use on the next iteration - */ - skb2 = skb_clone(skb, GFP_ATOMIC); - if (skb2) { - if (!skb_pull(skb2, pkt_len)) { - kfree_skb(skb2); - skb2 = NULL; - } - } - skb_trim(skb, pkt_len); - } - - dev_dstats_rx_add(netdev, skb->len); - - skb->protocol = htons(ETH_P_MCTP); - skb_reset_network_header(skb); - cb = __mctp_cb(skb); - cb->halen = 0; - netif_rx(skb); - - skb = skb2; - } - - if (skb) - kfree_skb(skb); - mctp_usb_rx_queue(mctp_usb, GFP_ATOMIC); } @@ -286,6 +222,8 @@ static int mctp_usb_stop(struct net_device *dev) usb_kill_urb(mctp_usb->rx_urb); usb_kill_urb(mctp_usb->tx_urb); + mctp_usblib_rx_cancel(&mctp_usb->rx); + return 0; } @@ -341,6 +279,8 @@ static int mctp_usb_probe(struct usb_interface *intf, spin_lock_init(&dev->rx_lock); usb_set_intfdata(intf, dev); + mctp_usblib_rx_init(&dev->rx); + dev->ep_in = ep_in->bEndpointAddress; dev->ep_out = ep_out->bEndpointAddress; @@ -362,6 +302,7 @@ static int mctp_usb_probe(struct usb_interface *intf, err_free_urbs: usb_free_urb(dev->tx_urb); usb_free_urb(dev->rx_urb); + mctp_usblib_rx_fini(&dev->rx); free_netdev(netdev); return rc; } @@ -371,6 +312,7 @@ static void mctp_usb_disconnect(struct usb_interface *intf) struct mctp_usb *dev = usb_get_intfdata(intf); mctp_unregister_netdev(dev->netdev); + mctp_usblib_rx_fini(&dev->rx); usb_free_urb(dev->tx_urb); usb_free_urb(dev->rx_urb); free_netdev(dev->netdev); diff --git a/drivers/net/mctp/mctp-usblib.c b/drivers/net/mctp/mctp-usblib.c new file mode 100644 index 000000000000..4140998c30fd --- /dev/null +++ b/drivers/net/mctp/mctp-usblib.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mctp-usblib.c - MCTP-over-USB (DMTF DSP0283) transport helper library + * + * DSP0283 is available at: + * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.0.1.pdf + * + * Copyright (C) 2024-2026 Code Construct Pty Ltd + */ + +#include +#include +#include +#include +#include + +void mctp_usblib_rx_init(struct mctp_usblib_rx *rx) +{ + memset(rx, 0, sizeof(*rx)); +} +EXPORT_SYMBOL_GPL(mctp_usblib_rx_init); + +void mctp_usblib_rx_fini(struct mctp_usblib_rx *rx) +{ + kfree_skb(rx->skb); +} +EXPORT_SYMBOL_GPL(mctp_usblib_rx_fini); + +/* + * Prepare a transfer buffer for future completion; *bufp and *lenp will + * be populated on success. + */ +int mctp_usblib_rx_prepare(struct net_device *netdev, + struct mctp_usblib_rx *rx, + void **bufp, size_t *lenp, gfp_t gfp) +{ + const unsigned int len = MCTP_USB_1_0_XFER_SIZE; + struct sk_buff *skb; + + skb = __netdev_alloc_skb(netdev, len, gfp); + if (!skb) + return -ENOMEM; + + rx->skb = skb; + + *bufp = skb_tail_pointer(skb); + *lenp = len; + + return 0; +} +EXPORT_SYMBOL_GPL(mctp_usblib_rx_prepare); + +static void mctp_usblib_rx(struct net_device *netdev, struct sk_buff *skb) +{ + struct pcpu_dstats *dstats = this_cpu_ptr(netdev->dstats); + struct mctp_skb_cb *cb; + unsigned long flags; + + /* we're called from an URB completion handler, and cannot assume local + * irqs are always disabled + */ + flags = u64_stats_update_begin_irqsave(&dstats->syncp); + u64_stats_inc(&dstats->rx_packets); + u64_stats_add(&dstats->rx_bytes, skb->len); + u64_stats_update_end_irqrestore(&dstats->syncp, flags); + + skb->protocol = htons(ETH_P_MCTP); + skb_reset_network_header(skb); + cb = __mctp_cb(skb); + cb->halen = 0; + netif_rx(skb); +} + +static void mctp_usblib_rx_stats_single_drop(struct net_device *dev) +{ + struct pcpu_dstats *dstats = this_cpu_ptr(dev->dstats); + unsigned long flags; + + flags = u64_stats_update_begin_irqsave(&dstats->syncp); + u64_stats_inc(&dstats->rx_drops); + u64_stats_update_end_irqrestore(&dstats->syncp, flags); +} + +/* + * Receive a USB completion of @len bytes of incoming data. We will then split + * this into packets and netif_rx() each. Intended to be called in atomic + * contexts - ie., URB completion. + * + * Assumes @netdev uses dstats. + */ +int mctp_usblib_rx_complete(struct net_device *netdev, + struct mctp_usblib_rx *rx, size_t len) +{ + struct sk_buff *skb = rx->skb; + int rc = 0; + + __skb_put(skb, len); + + while (skb) { + struct sk_buff *skb2 = NULL; + struct mctp_usb_hdr *hdr; + /* length of MCTP packet, no USB header */ + u8 pkt_len; + + skb_reset_mac_header(skb); + hdr = skb_pull_data(skb, sizeof(*hdr)); + if (!hdr) { + rc = -ENOMSG; + break; + } + + if (be16_to_cpu(hdr->id) != MCTP_USB_DMTF_ID) { + netdev_dbg(netdev, "rx: invalid id %04x\n", + be16_to_cpu(hdr->id)); + rc = -EPROTO; + break; + } + + if (hdr->len < + sizeof(struct mctp_hdr) + sizeof(struct mctp_usb_hdr)) { + netdev_dbg(netdev, "rx: short packet (hdr) %d\n", + hdr->len); + rc = -EPROTO; + break; + } + + /* we know we have at least sizeof(struct mctp_usb_hdr) here */ + pkt_len = hdr->len - sizeof(struct mctp_usb_hdr); + if (pkt_len > skb->len) { + rc = -EPROTO; + netdev_dbg(netdev, + "rx: short packet (xfer) %d, actual %d\n", + hdr->len, skb->len); + break; + } + + if (pkt_len < skb->len) { + /* more packets may follow - clone to a new + * skb to use on the next iteration + */ + skb2 = skb_clone(skb, GFP_ATOMIC); + if (skb2) { + if (!skb_pull(skb2, pkt_len)) { + dev_kfree_skb_any(skb2); + skb2 = NULL; + } + } else { + mctp_usblib_rx_stats_single_drop(netdev); + } + skb_trim(skb, pkt_len); + } + + mctp_usblib_rx(netdev, skb); + skb = skb2; + } + + if (skb) + dev_kfree_skb_any(skb); + + rx->skb = NULL; + + return rc; +} +EXPORT_SYMBOL_GPL(mctp_usblib_rx_complete); + +/* + * Cancel a rx context; subsequent prepare/complete calls will not be a + * continuation of any data already received. + */ +void mctp_usblib_rx_cancel(struct mctp_usblib_rx *rx) +{ + dev_kfree_skb_any(rx->skb); + rx->skb = NULL; +} +EXPORT_SYMBOL_GPL(mctp_usblib_rx_cancel); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jeremy Kerr "); +MODULE_DESCRIPTION("MCTP USB transport library"); diff --git a/include/linux/usb/mctp-usb.h b/include/linux/usb/mctp-usb.h index 2bece8afd1c7..595e6af16dd0 100644 --- a/include/linux/usb/mctp-usb.h +++ b/include/linux/usb/mctp-usb.h @@ -13,6 +13,8 @@ #ifndef __LINUX_USB_MCTP_USB_H #define __LINUX_USB_MCTP_USB_H +#include +#include #include struct mctp_usb_hdr { @@ -29,4 +31,28 @@ struct mctp_usb_hdr { #define MCTP_USB_1_0_MTU_MAX (MCTP_USB_1_0_PKTLEN_MAX - sizeof(struct mctp_usb_hdr)) #define MCTP_USB_DMTF_ID 0x1ab4 +/* mctp-usblib */ + +/* + * RX handle: drivers will typically create one on init, which persists for + * the life of the driver. The same handle is used for progressive + * prepare -> complete operations (for each incoming USB transfer), which + * result in netif_rx()-ing the MCTP packets received + */ +struct mctp_usblib_rx { + struct sk_buff *skb; +}; + +void mctp_usblib_rx_init(struct mctp_usblib_rx *rx); +void mctp_usblib_rx_fini(struct mctp_usblib_rx *rx); + +int mctp_usblib_rx_prepare(struct net_device *netdev, + struct mctp_usblib_rx *rx, + void **bufp, size_t *lenp, gfp_t gfp); + +int mctp_usblib_rx_complete(struct net_device *netdev, + struct mctp_usblib_rx *rx, size_t len); + +void mctp_usblib_rx_cancel(struct mctp_usblib_rx *rx); + #endif /* __LINUX_USB_MCTP_USB_H */ From 9f66be014070af8d88abf940240dddf35554328f Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Fri, 24 Jul 2026 13:15:25 +0800 Subject: [PATCH 0681/1433] net: mctp: usb: Improve IN endpoint status handling Currently, we give-up on all non-zero status values on our IN/rx urb, and do not re-queue the urb. This will stall the driver, and prevent any further receive. Instead, attempt a re-queue on transient errors, with a max of ten successive failures. Handle EPIPE specially, by scheduling a usb_clear_halt() in non-atomic context. Signed-off-by: Jeremy Kerr Link: https://patch.msgid.link/20260724-dev-mctp-usb-1-1-v5-4-e66bbba0dbdc@codeconstruct.com.au Signed-off-by: Jakub Kicinski --- drivers/net/mctp/mctp-usb.c | 91 ++++++++++++++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 7 deletions(-) diff --git a/drivers/net/mctp/mctp-usb.c b/drivers/net/mctp/mctp-usb.c index eedcf759e131..f9fdc89d8c95 100644 --- a/drivers/net/mctp/mctp-usb.c +++ b/drivers/net/mctp/mctp-usb.c @@ -32,6 +32,9 @@ struct mctp_usb { struct urb *tx_urb; struct urb *rx_urb; + int in_err_count; + int in_err_orig; + bool clear_halt; /* enforces atomic access to rx_stopped and requeuing the retry work */ spinlock_t rx_lock; @@ -158,27 +161,75 @@ static int mctp_usb_rx_queue(struct mctp_usb *mctp_usb, gfp_t gfp) return 0; } +static const unsigned int rx_err_max = 10; + +/* Returns -1 if we have hit excessive errors, zero otherwise. */ +static int mctp_usb_in_urb_err(struct mctp_usb *mctp_usb, int status, + bool stalled) +{ + mctp_usblib_rx_cancel(&mctp_usb->rx); + + if (!mctp_usb->in_err_count++) + mctp_usb->in_err_orig = status; + + if (mctp_usb->in_err_count >= rx_err_max) { + netdev_err(mctp_usb->netdev, + "excessive errors from%s IN EP, first: %d\n", + stalled ? " (stalled)" : "", + mctp_usb->in_err_orig); + return -1; + } + + return 0; +} + static void mctp_usb_in_complete(struct urb *urb) { struct mctp_usb *mctp_usb = urb->context; struct net_device *netdev = mctp_usb->netdev; - int status; + unsigned long flags; + int rc, status; status = urb->status; switch (status) { - default: - netdev_dbg(netdev, "unexpected rx urb status: %d\n", status); - fallthrough; case -ENOENT: case -ECONNRESET: case -ESHUTDOWN: - case -EPROTO: + /* device shutdown, don't resubmit */ mctp_usblib_rx_cancel(&mctp_usb->rx); return; + + case -EPIPE: + /* endpoint stall: clear halt, which will cause a resubmit */ + rc = mctp_usb_in_urb_err(mctp_usb, status, true); + if (rc) + return; + + mctp_usb->clear_halt = true; + spin_lock_irqsave(&mctp_usb->rx_lock, flags); + if (!mctp_usb->rx_stopped) + schedule_delayed_work(&mctp_usb->rx_retry_work, + RX_RETRY_DELAY); + spin_unlock_irqrestore(&mctp_usb->rx_lock, flags); + return; + + default: + netdev_dbg(netdev, "unexpected rx urb status: %d\n", status); + fallthrough; + case -ETIME: + case -EPROTO: + case -EILSEQ: + case -EOVERFLOW: + /* possibly transient; record first failure, resubmit */ + rc = mctp_usb_in_urb_err(mctp_usb, status, false); + if (rc) + return; + break; + case 0: - mctp_usblib_rx_complete(netdev, &mctp_usb->rx, - urb->actual_length); + mctp_usblib_rx_complete(netdev, &mctp_usb->rx, urb->actual_length); + mctp_usb->in_err_count = 0; break; } @@ -189,6 +240,30 @@ static void mctp_usb_rx_retry_work(struct work_struct *work) { struct mctp_usb *mctp_usb = container_of(work, struct mctp_usb, rx_retry_work.work); + unsigned long flags; + int rc; + + /* We are only called when rx completions are suspended */ + if (mctp_usb->clear_halt) { + int pipe = usb_rcvbulkpipe(mctp_usb->usbdev, mctp_usb->ep_in); + + rc = usb_clear_halt(mctp_usb->usbdev, pipe); + if (rc) { + netdev_err(mctp_usb->netdev, + "can't clear IN EP halt: %d\n", rc); + + if (++mctp_usb->in_err_count >= rx_err_max) + return; + + spin_lock_irqsave(&mctp_usb->rx_lock, flags); + if (!mctp_usb->rx_stopped) + schedule_delayed_work(&mctp_usb->rx_retry_work, + RX_RETRY_DELAY); + spin_unlock_irqrestore(&mctp_usb->rx_lock, flags); + return; + } + mctp_usb->clear_halt = false; + } mctp_usb_rx_queue(mctp_usb, GFP_KERNEL); } @@ -198,6 +273,8 @@ static int mctp_usb_open(struct net_device *dev) struct mctp_usb *mctp_usb = netdev_priv(dev); WRITE_ONCE(mctp_usb->rx_stopped, false); + mctp_usb->clear_halt = false; + mctp_usb->in_err_count = 0; netif_start_queue(dev); From aadf5ed03e85736d755162ecbfc4d57d2044be49 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Fri, 24 Jul 2026 13:15:26 +0800 Subject: [PATCH 0682/1433] net: mctp: usblib: Move TX transfer processing to mctp-usblib With the RX processing in mctp-usblib, add TX processing alongside. To accommodate packed transfers in DSP0283, where a transfer may contain multiple MCTP packets, we move to a split process for the transmit API: * push: create a new transmit context, and add a skb to it. * send: callback to the driver implementation to send the (possibly multi-packet) USB transfer * complete: update skb accounting and release the tx context The actual multi-packet transfer implementation will be added in the next change; no tx context persists beyond the single send at present. However, we use an anchor in the host driver implementation to track the submitted TX urb when necessary. While we're here, fix an inconsistency between tx and rx stats: both should not include the transport header. Signed-off-by: Jeremy Kerr Link: https://patch.msgid.link/20260724-dev-mctp-usb-1-1-v5-5-e66bbba0dbdc@codeconstruct.com.au Signed-off-by: Jakub Kicinski --- drivers/net/mctp/mctp-usb.c | 126 +++++++++----------- drivers/net/mctp/mctp-usblib.c | 203 +++++++++++++++++++++++++++++++++ include/linux/usb/mctp-usb.h | 39 +++++++ 3 files changed, 298 insertions(+), 70 deletions(-) diff --git a/drivers/net/mctp/mctp-usb.c b/drivers/net/mctp/mctp-usb.c index f9fdc89d8c95..f911d1412a44 100644 --- a/drivers/net/mctp/mctp-usb.c +++ b/drivers/net/mctp/mctp-usb.c @@ -29,8 +29,6 @@ struct mctp_usb { u8 ep_out; struct mctp_usblib_rx rx; - - struct urb *tx_urb; struct urb *rx_urb; int in_err_count; int in_err_orig; @@ -40,82 +38,66 @@ struct mctp_usb { spinlock_t rx_lock; bool rx_stopped; struct delayed_work rx_retry_work; + + struct mctp_usblib_tx tx; + struct usb_anchor tx_anchor; }; static void mctp_usb_out_complete(struct urb *urb) { - struct sk_buff *skb = urb->context; - struct net_device *netdev = skb->dev; - int status; + struct mctp_usblib_tx_ctx *tx_ctx = urb->context; + struct mctp_usb *mctp_usb = mctp_usblib_tx_ctx_priv(tx_ctx); + struct net_device *netdev = mctp_usb->netdev; - status = urb->status; + mctp_usblib_tx_send_complete(tx_ctx, netdev, urb->status == 0); - switch (status) { - case -ENOENT: - case -ECONNRESET: - case -ESHUTDOWN: - case -EPROTO: - dev_dstats_tx_dropped(netdev); - break; - case 0: - dev_dstats_tx_add(netdev, skb->len); - netif_wake_queue(netdev); - consume_skb(skb); - return; - default: - netdev_dbg(netdev, "unexpected tx urb status: %d\n", status); - dev_dstats_tx_dropped(netdev); + usb_free_urb(urb); + + netif_wake_queue(netdev); +} + +static int mctp_usb_tx_send(struct mctp_usblib_tx_ctx *tx_ctx, + void *data, size_t len) +{ + struct mctp_usb *mctp_usb = mctp_usblib_tx_ctx_priv(tx_ctx); + struct urb *urb; + int rc; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) + return -ENOMEM; + + usb_fill_bulk_urb(urb, mctp_usb->usbdev, + usb_sndbulkpipe(mctp_usb->usbdev, mctp_usb->ep_out), + data, len, mctp_usb_out_complete, tx_ctx); + + netif_stop_queue(mctp_usb->netdev); + + usb_anchor_urb(urb, &mctp_usb->tx_anchor); + + rc = usb_submit_urb(urb, GFP_ATOMIC); + if (rc) { + netdev_dbg(mctp_usb->netdev, "TX urb submit failed, %d\n", rc); + usb_unanchor_urb(urb); + usb_free_urb(urb); + netif_start_queue(mctp_usb->netdev); } - kfree_skb(skb); + return rc; } +static const struct mctp_usblib_tx_ops tx_ops = { + .send = mctp_usb_tx_send, +}; + static netdev_tx_t mctp_usb_start_xmit(struct sk_buff *skb, struct net_device *dev) { struct mctp_usb *mctp_usb = netdev_priv(dev); - struct mctp_usb_hdr *hdr; - unsigned int plen; - struct urb *urb; - int rc; + bool more = netdev_xmit_more(); - plen = skb->len; + mctp_usblib_tx_push(dev, &mctp_usb->tx, skb, more); - if (plen + sizeof(*hdr) > MCTP_USB_1_0_PKTLEN_MAX) - goto err_drop; - - rc = skb_cow_head(skb, sizeof(*hdr)); - if (rc) - goto err_drop; - - hdr = skb_push(skb, sizeof(*hdr)); - if (!hdr) - goto err_drop; - - hdr->id = cpu_to_be16(MCTP_USB_DMTF_ID); - hdr->rsvd = 0; - hdr->len = plen + sizeof(*hdr); - - urb = mctp_usb->tx_urb; - - usb_fill_bulk_urb(urb, mctp_usb->usbdev, - usb_sndbulkpipe(mctp_usb->usbdev, mctp_usb->ep_out), - skb->data, skb->len, - mctp_usb_out_complete, skb); - - /* Stops TX queue first to prevent race condition with URB complete */ - netif_stop_queue(dev); - rc = usb_submit_urb(urb, GFP_ATOMIC); - if (rc) { - netif_wake_queue(dev); - goto err_drop; - } - - return NETDEV_TX_OK; - -err_drop: - dev_dstats_tx_dropped(dev); - kfree_skb(skb); return NETDEV_TX_OK; } @@ -297,8 +279,10 @@ static int mctp_usb_stop(struct net_device *dev) flush_delayed_work(&mctp_usb->rx_retry_work); usb_kill_urb(mctp_usb->rx_urb); - usb_kill_urb(mctp_usb->tx_urb); + usb_kill_anchored_urbs(&mctp_usb->tx_anchor); + + mctp_usblib_tx_cancel(&mctp_usb->tx, dev, SKB_DROP_REASON_DEV_READY); mctp_usblib_rx_cancel(&mctp_usb->rx); return 0; @@ -357,28 +341,30 @@ static int mctp_usb_probe(struct usb_interface *intf, usb_set_intfdata(intf, dev); mctp_usblib_rx_init(&dev->rx); + mctp_usblib_tx_init(&dev->tx, &tx_ops, dev); + init_usb_anchor(&dev->tx_anchor); dev->ep_in = ep_in->bEndpointAddress; dev->ep_out = ep_out->bEndpointAddress; - dev->tx_urb = usb_alloc_urb(0, GFP_KERNEL); dev->rx_urb = usb_alloc_urb(0, GFP_KERNEL); - if (!dev->tx_urb || !dev->rx_urb) { + if (!dev->rx_urb) { rc = -ENOMEM; - goto err_free_urbs; + goto err_fini_rxtx; } INIT_DELAYED_WORK(&dev->rx_retry_work, mctp_usb_rx_retry_work); rc = mctp_register_netdev(netdev, NULL, MCTP_PHYS_BINDING_USB); if (rc) - goto err_free_urbs; + goto err_free_urb; return 0; -err_free_urbs: - usb_free_urb(dev->tx_urb); +err_free_urb: usb_free_urb(dev->rx_urb); +err_fini_rxtx: + mctp_usblib_tx_fini(&dev->tx); mctp_usblib_rx_fini(&dev->rx); free_netdev(netdev); return rc; @@ -390,7 +376,7 @@ static void mctp_usb_disconnect(struct usb_interface *intf) mctp_unregister_netdev(dev->netdev); mctp_usblib_rx_fini(&dev->rx); - usb_free_urb(dev->tx_urb); + mctp_usblib_tx_fini(&dev->tx); usb_free_urb(dev->rx_urb); free_netdev(dev->netdev); } diff --git a/drivers/net/mctp/mctp-usblib.c b/drivers/net/mctp/mctp-usblib.c index 4140998c30fd..3f4295f3145c 100644 --- a/drivers/net/mctp/mctp-usblib.c +++ b/drivers/net/mctp/mctp-usblib.c @@ -174,6 +174,209 @@ void mctp_usblib_rx_cancel(struct mctp_usblib_rx *rx) } EXPORT_SYMBOL_GPL(mctp_usblib_rx_cancel); +/* transmit context: encapsulates one transfer */ +struct mctp_usblib_tx_ctx { + struct mctp_usblib_tx *tx; + struct sk_buff *skb; + unsigned int len; + enum mctp_usblib_tx_buf_type { + TX_SINGLE, + } buf_type; +}; + +void mctp_usblib_tx_init(struct mctp_usblib_tx *tx, + const struct mctp_usblib_tx_ops *ops, + void *priv) +{ + memset(tx, 0, sizeof(*tx)); + tx->ops = *ops; + tx->priv = priv; +} +EXPORT_SYMBOL_GPL(mctp_usblib_tx_init); + +void mctp_usblib_tx_fini(struct mctp_usblib_tx *tx) +{ +} +EXPORT_SYMBOL_GPL(mctp_usblib_tx_fini); + +void *mctp_usblib_tx_ctx_priv(struct mctp_usblib_tx_ctx *tx_ctx) +{ + return tx_ctx->tx->priv; +} +EXPORT_SYMBOL_GPL(mctp_usblib_tx_ctx_priv); + +static struct mctp_usblib_tx_ctx * +mctp_usblib_tx_ctx_create(struct mctp_usblib_tx *tx, struct sk_buff *skb) +{ + struct mctp_usblib_tx_ctx *ctx; + + ctx = kzalloc_obj(*ctx, GFP_ATOMIC); + if (!ctx) + return NULL; + + ctx->tx = tx; + ctx->buf_type = TX_SINGLE; + ctx->skb = skb; + ctx->len += skb->len; + + return ctx; +} + +static int mctp_usblib_tx_send(struct mctp_usblib_tx_ctx *ctx) +{ + struct mctp_usblib_tx *tx = ctx->tx; + void *buf = ctx->skb->data; + + return tx->ops.send(ctx, buf, ctx->len); +} + +static void mctp_usblib_tx_ctx_free(struct mctp_usblib_tx_ctx *ctx, + enum skb_drop_reason reason) +{ + if (ctx) + dev_kfree_skb_any_reason(ctx->skb, reason); + kfree(ctx); +} + +static void mctp_usblib_tx_stats_update(struct mctp_usblib_tx_ctx *ctx, + struct net_device *dev, + bool ok) +{ + struct pcpu_dstats *dstats = get_cpu_ptr(dev->dstats); + unsigned long flags; + + flags = u64_stats_update_begin_irqsave(&dstats->syncp); + if (ok) { + /* Only include the network-layer data in tx stats; we know + * that there is a 4-byte header pushed to all skbs in + * tx_skb_prepare() + */ + s64 len = ctx->len - sizeof(struct mctp_usb_hdr); + + u64_stats_inc(&dstats->tx_packets); + u64_stats_add(&dstats->tx_bytes, len); + } else { + u64_stats_inc(&dstats->tx_drops); + } + u64_stats_update_end_irqrestore(&dstats->syncp, flags); + put_cpu_ptr(dev->dstats); +} + +static void mctp_usblib_tx_stats_single_drop(struct net_device *dev) +{ + struct pcpu_dstats *dstats = get_cpu_ptr(dev->dstats); + unsigned long flags; + + flags = u64_stats_update_begin_irqsave(&dstats->syncp); + u64_stats_inc(&dstats->tx_drops); + u64_stats_update_end_irqrestore(&dstats->syncp, flags); + put_cpu_ptr(dev->dstats); +} + +/* + * Completion for the ->send() op. This will update netdev stats and + * free the tx context. + * + * Likely called from (atomic) URB completion context. + */ +void mctp_usblib_tx_send_complete(struct mctp_usblib_tx_ctx *tx_ctx, + struct net_device *dev, bool ok) +{ + enum skb_drop_reason reason = + ok ? SKB_CONSUMED : SKB_DROP_REASON_NOT_SPECIFIED; + + mctp_usblib_tx_stats_update(tx_ctx, dev, ok); + mctp_usblib_tx_ctx_free(tx_ctx, reason); +} +EXPORT_SYMBOL_GPL(mctp_usblib_tx_send_complete); + +/* Prepare a skb for push() + * + * On error, populates @reason. + */ +static int mctp_usblib_tx_skb_prepare(struct sk_buff *skb, + enum skb_drop_reason *reason) +{ + struct mctp_usb_hdr *hdr; + unsigned long plen; + int rc; + + plen = skb->len; + if (plen + sizeof(*hdr) > MCTP_USB_1_0_PKTLEN_MAX) { + *reason = SKB_DROP_REASON_PKT_TOO_BIG; + return -EMSGSIZE; + } + + rc = skb_cow_head(skb, sizeof(*hdr)); + if (rc) { + *reason = SKB_DROP_REASON_NOMEM; + return rc; + } + + hdr = skb_push(skb, sizeof(*hdr)); + if (!hdr) { + *reason = SKB_DROP_REASON_NOMEM; + return -ENOMEM; + } + + hdr->id = cpu_to_be16(MCTP_USB_DMTF_ID); + hdr->rsvd = 0; + hdr->len = plen + sizeof(*hdr); + + return 0; +} + +/* + * Push a new skb to the transfer. At present, no send must be in progress, + * as we only handle single-packet USB transfers. + * + * Takes ownership of @skb, including on error. + */ +int mctp_usblib_tx_push(struct net_device *dev, + struct mctp_usblib_tx *tx, + struct sk_buff *skb, bool more) +{ + struct mctp_usblib_tx_ctx *ctx; + enum skb_drop_reason reason; + int rc; + + if (!skb) + return 0; + + rc = mctp_usblib_tx_skb_prepare(skb, &reason); + if (rc) + goto err_drop_single; + + ctx = mctp_usblib_tx_ctx_create(tx, skb); + if (!ctx) { + rc = -ENOMEM; + reason = SKB_DROP_REASON_NOMEM; + goto err_drop_single; + } + + rc = mctp_usblib_tx_send(ctx); + if (rc) { + mctp_usblib_tx_stats_update(ctx, dev, false); + mctp_usblib_tx_ctx_free(ctx, SKB_DROP_REASON_NOT_SPECIFIED); + } + + return rc; + +err_drop_single: + mctp_usblib_tx_stats_single_drop(dev); + kfree_skb_reason(skb, reason); + return rc; +} +EXPORT_SYMBOL_GPL(mctp_usblib_tx_push); + +/* Cancel a tx: any un-sent context is released. */ +void mctp_usblib_tx_cancel(struct mctp_usblib_tx *tx, struct net_device *dev, + enum skb_drop_reason reason) +{ + /* nothing to do at present, no ctx is persistent */ +} +EXPORT_SYMBOL_GPL(mctp_usblib_tx_cancel); + MODULE_LICENSE("GPL"); MODULE_AUTHOR("Jeremy Kerr "); MODULE_DESCRIPTION("MCTP USB transport library"); diff --git a/include/linux/usb/mctp-usb.h b/include/linux/usb/mctp-usb.h index 595e6af16dd0..76f9d8879254 100644 --- a/include/linux/usb/mctp-usb.h +++ b/include/linux/usb/mctp-usb.h @@ -55,4 +55,43 @@ int mctp_usblib_rx_complete(struct net_device *netdev, void mctp_usblib_rx_cancel(struct mctp_usblib_rx *rx); +/* + * TX handle: created by mctp_usblib_tx_push() during the tx path, and + * may persist across multiple packet transmits. + * + * Currently though, there is a 1:1 mapping between packets and transfers, so + * the tx context will be cleared over each transmit. This will change in + * future. + */ +struct mctp_usblib_tx_ctx; + +struct mctp_usblib_tx_ops { + /* Start a USB TX for @data. On returning success, the implementation + * must arrange for mctp_usblib_tx_send_complete() to be called at some + * later point (eg., on urb completion). + */ + int (*send)(struct mctp_usblib_tx_ctx *tx_ctx, void *data, size_t len); +}; + +struct mctp_usblib_tx { + struct mctp_usblib_tx_ops ops; + void *priv; +}; + +void mctp_usblib_tx_init(struct mctp_usblib_tx *tx, + const struct mctp_usblib_tx_ops *ops, void *priv); +void mctp_usblib_tx_fini(struct mctp_usblib_tx *tx); + +void *mctp_usblib_tx_ctx_priv(struct mctp_usblib_tx_ctx *tx_ctx); + +int mctp_usblib_tx_push(struct net_device *dev, + struct mctp_usblib_tx *tx, + struct sk_buff *skb, bool more); + +void mctp_usblib_tx_send_complete(struct mctp_usblib_tx_ctx *tx_ctx, + struct net_device *dev, bool ok); + +void mctp_usblib_tx_cancel(struct mctp_usblib_tx *tx, struct net_device *dev, + enum skb_drop_reason reason); + #endif /* __LINUX_USB_MCTP_USB_H */ From 76a58ffd741719eada095e311961539a82c75e71 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Fri, 24 Jul 2026 13:15:27 +0800 Subject: [PATCH 0683/1433] net: mctp: usblib: Add support for multi-packet transmit The MCTP over USB spec allows us to pack multiple packets in one transfer. Given the packet max length is 255, and the transfer max length is 512, we can typically include two full-size packets per urb submission. To do this, we allow a struct mctp_usb_tx to persist a tx_ctx, representing the ongoing context for a transmit. If possible, a TX skb will be queued to the context and the send deferred until the context is full, or the device queue reports no more packets. This typically requires a linear buffer for the 512-byte TX, which we allocate along with the TX context. Signed-off-by: Jeremy Kerr Link: https://patch.msgid.link/20260724-dev-mctp-usb-1-1-v5-6-e66bbba0dbdc@codeconstruct.com.au Signed-off-by: Jakub Kicinski --- drivers/net/mctp/mctp-usblib.c | 262 ++++++++++++++++++++++++++------- include/linux/usb/mctp-usb.h | 8 +- 2 files changed, 215 insertions(+), 55 deletions(-) diff --git a/drivers/net/mctp/mctp-usblib.c b/drivers/net/mctp/mctp-usblib.c index 3f4295f3145c..2e464254353e 100644 --- a/drivers/net/mctp/mctp-usblib.c +++ b/drivers/net/mctp/mctp-usblib.c @@ -177,11 +177,13 @@ EXPORT_SYMBOL_GPL(mctp_usblib_rx_cancel); /* transmit context: encapsulates one transfer */ struct mctp_usblib_tx_ctx { struct mctp_usblib_tx *tx; - struct sk_buff *skb; + struct sk_buff_head skbs; unsigned int len; enum mctp_usblib_tx_buf_type { TX_SINGLE, + TX_FLAT, } buf_type; + u8 buf[] ____cacheline_aligned; }; void mctp_usblib_tx_init(struct mctp_usblib_tx *tx, @@ -191,13 +193,87 @@ void mctp_usblib_tx_init(struct mctp_usblib_tx *tx, memset(tx, 0, sizeof(*tx)); tx->ops = *ops; tx->priv = priv; + spin_lock_init(&tx->lock); } EXPORT_SYMBOL_GPL(mctp_usblib_tx_init); -void mctp_usblib_tx_fini(struct mctp_usblib_tx *tx) +static int mctp_usblib_tx_avail(struct mctp_usblib_tx_ctx *ctx) { + return ctx->buf_type == TX_SINGLE ? 0 : MCTP_USB_1_0_XFER_SIZE - ctx->len; +} + +static bool mctp_usblib_tx_should_send(struct mctp_usblib_tx_ctx *ctx) +{ + /* Use the baseline length (ie, BTU) as an approximate + * "reasonably-sized" packet we could expect. If there is + * insufficient capacity for that, then send. + */ + const size_t pkt_len = MCTP_USB_BTU + sizeof(struct mctp_usb_hdr); + + return mctp_usblib_tx_avail(ctx) < pkt_len; +} + +/* + * Returns zero on success, non-zero on failure - indicating that the new skb + * could not be appended. So, errors reported here to the TX path will result + * in the TX being transmitted. + */ +static int mctp_usblib_tx_append(struct mctp_usblib_tx_ctx *ctx, + struct sk_buff *skb) +{ + if (ctx->buf_type == TX_SINGLE) + return -EINVAL; + + if (mctp_usblib_tx_avail(ctx) < skb->len) + return -ENOBUFS; + + __skb_queue_tail(&ctx->skbs, skb); + + ctx->len += skb->len; + + return 0; +} + +static int mctp_usblib_tx_send(struct mctp_usblib_tx_ctx *ctx) +{ + void *buf; + + /* If we have a qlen of 1, we only ended up packing a single skb, + * despite allocating for multiple. Skip the copy and send directly + * from the skb data. + */ + if (ctx->buf_type == TX_SINGLE || ctx->skbs.qlen == 1) { + buf = ctx->skbs.next->data; + + } else if (ctx->buf_type == TX_FLAT) { + struct sk_buff *skb; + size_t pos = 0; + + skb_queue_walk(&ctx->skbs, skb) { + skb_copy_bits(skb, 0, ctx->buf + pos, skb->len); + pos += skb->len; + } + + buf = ctx->buf; + } else { + return -EINVAL; + } + + return ctx->tx->ops.send(ctx, buf, ctx->len); +} + +static void mctp_usblib_tx_ctx_free(struct mctp_usblib_tx_ctx *ctx, + enum skb_drop_reason reason) +{ + struct sk_buff *skb; + + if (!ctx) + return; + + while ((skb = __skb_dequeue(&ctx->skbs)) != NULL) + dev_kfree_skb_any_reason(skb, reason); + kfree(ctx); } -EXPORT_SYMBOL_GPL(mctp_usblib_tx_fini); void *mctp_usblib_tx_ctx_priv(struct mctp_usblib_tx_ctx *tx_ctx) { @@ -205,39 +281,41 @@ void *mctp_usblib_tx_ctx_priv(struct mctp_usblib_tx_ctx *tx_ctx) } EXPORT_SYMBOL_GPL(mctp_usblib_tx_ctx_priv); -static struct mctp_usblib_tx_ctx * -mctp_usblib_tx_ctx_create(struct mctp_usblib_tx *tx, struct sk_buff *skb) +/* caller must ensure the tx & completion path is quiesced */ +void mctp_usblib_tx_fini(struct mctp_usblib_tx *tx) { - struct mctp_usblib_tx_ctx *ctx; + mctp_usblib_tx_ctx_free(tx->cur_ctx, SKB_DROP_REASON_NOT_SPECIFIED); +} +EXPORT_SYMBOL_GPL(mctp_usblib_tx_fini); - ctx = kzalloc_obj(*ctx, GFP_ATOMIC); +static struct mctp_usblib_tx_ctx * +mctp_usblib_tx_ctx_create(struct mctp_usblib_tx *tx, struct sk_buff *skb, + bool single) +{ + enum mctp_usblib_tx_buf_type type; + struct mctp_usblib_tx_ctx *ctx; + size_t sz = 0; + + if (single) { + type = TX_SINGLE; + } else { + type = TX_FLAT; + sz = MCTP_USB_1_0_XFER_SIZE; + } + + ctx = kzalloc_flex(*ctx, buf, sz, GFP_ATOMIC); if (!ctx) return NULL; ctx->tx = tx; - ctx->buf_type = TX_SINGLE; - ctx->skb = skb; - ctx->len += skb->len; + ctx->buf_type = type; + ctx->len = skb->len; + skb_queue_head_init(&ctx->skbs); + __skb_queue_tail(&ctx->skbs, skb); return ctx; } -static int mctp_usblib_tx_send(struct mctp_usblib_tx_ctx *ctx) -{ - struct mctp_usblib_tx *tx = ctx->tx; - void *buf = ctx->skb->data; - - return tx->ops.send(ctx, buf, ctx->len); -} - -static void mctp_usblib_tx_ctx_free(struct mctp_usblib_tx_ctx *ctx, - enum skb_drop_reason reason) -{ - if (ctx) - dev_kfree_skb_any_reason(ctx->skb, reason); - kfree(ctx); -} - static void mctp_usblib_tx_stats_update(struct mctp_usblib_tx_ctx *ctx, struct net_device *dev, bool ok) @@ -251,12 +329,13 @@ static void mctp_usblib_tx_stats_update(struct mctp_usblib_tx_ctx *ctx, * that there is a 4-byte header pushed to all skbs in * tx_skb_prepare() */ - s64 len = ctx->len - sizeof(struct mctp_usb_hdr); + u64 n = ctx->skbs.qlen; + s64 len = ctx->len - (n * sizeof(struct mctp_usb_hdr)); - u64_stats_inc(&dstats->tx_packets); + u64_stats_add(&dstats->tx_packets, n); u64_stats_add(&dstats->tx_bytes, len); } else { - u64_stats_inc(&dstats->tx_drops); + u64_stats_add(&dstats->tx_drops, ctx->skbs.qlen); } u64_stats_update_end_irqrestore(&dstats->syncp, flags); put_cpu_ptr(dev->dstats); @@ -327,8 +406,8 @@ static int mctp_usblib_tx_skb_prepare(struct sk_buff *skb, } /* - * Push a new skb to the transfer. At present, no send must be in progress, - * as we only handle single-packet USB transfers. + * Push a new skb to the transfer. May result in zero or more calls to + * ops->send(). * * Takes ownership of @skb, including on error. */ @@ -336,36 +415,106 @@ int mctp_usblib_tx_push(struct net_device *dev, struct mctp_usblib_tx *tx, struct sk_buff *skb, bool more) { - struct mctp_usblib_tx_ctx *ctx; + struct mctp_usblib_tx_ctx *ctx, *send_ctx = NULL; enum skb_drop_reason reason; - int rc; + const int max_tries = 3; + unsigned long flags; + int try = 1, rc; + rc = mctp_usblib_tx_skb_prepare(skb, &reason); + if (rc) { + mctp_usblib_tx_stats_single_drop(dev); + kfree_skb_reason(skb, reason); + /* we may still need to proceed, in case an existing ctx + * is now sendable (ie.: !more). + */ + skb = NULL; + } + + reason = SKB_DROP_REASON_NOT_SPECIFIED; +retry: + /* Try and queue to the current context. We exit this critical section + * with a few bits of state: + * - send_ctx: indicating a prior context that needs to be sent + * - skb: indicating that a skb still needs to be queued/sent + */ + spin_lock_irqsave(&tx->lock, flags); + ctx = tx->cur_ctx; + if (ctx) { + if (skb) { + rc = mctp_usblib_tx_append(ctx, skb); + if (rc) { + /* can't append to the pending tx - detach for + * sending, and we'll create a new tx below. + */ + swap(tx->cur_ctx, send_ctx); + } else { + /* we have queued */ + skb = NULL; + if (!more || mctp_usblib_tx_should_send(ctx)) + swap(tx->cur_ctx, send_ctx); + } + } else if (!more) { + swap(tx->cur_ctx, send_ctx); + } + } + spin_unlock_irqrestore(&tx->lock, flags); + + if (send_ctx) { + rc = mctp_usblib_tx_send(send_ctx); + if (rc) { + mctp_usblib_tx_stats_update(send_ctx, dev, false); + mctp_usblib_tx_ctx_free(send_ctx, reason); + } + send_ctx = NULL; + } + + /* we have either queued, or the prepare failed; nothing more to do */ if (!skb) return 0; - rc = mctp_usblib_tx_skb_prepare(skb, &reason); - if (rc) - goto err_drop_single; - - ctx = mctp_usblib_tx_ctx_create(tx, skb); + ctx = mctp_usblib_tx_ctx_create(tx, skb, !more); if (!ctx) { - rc = -ENOMEM; - reason = SKB_DROP_REASON_NOMEM; - goto err_drop_single; + netdev_dbg(dev, "TX context create failed\n"); + mctp_usblib_tx_stats_single_drop(dev); + kfree_skb(skb); + return -ENOMEM; } - rc = mctp_usblib_tx_send(ctx); - if (rc) { - mctp_usblib_tx_stats_update(ctx, dev, false); - mctp_usblib_tx_ctx_free(ctx, SKB_DROP_REASON_NOT_SPECIFIED); + /* if we're ready to send now, no need to enqueue */ + if (!more || mctp_usblib_tx_should_send(ctx)) { + rc = mctp_usblib_tx_send(ctx); + if (rc) { + mctp_usblib_tx_stats_update(ctx, dev, false); + mctp_usblib_tx_ctx_free(ctx, reason); + } + return 0; } - return rc; + spin_lock_irqsave(&tx->lock, flags); + if (!tx->cur_ctx) { + tx->cur_ctx = ctx; + ctx = NULL; + } + spin_unlock_irqrestore(&tx->lock, flags); -err_drop_single: - mctp_usblib_tx_stats_single_drop(dev); - kfree_skb_reason(skb, reason); - return rc; + /* we may have lost the race with a concurrent tx; shouldn't happen, as + * ndo_start_xmit should be serialised over one queue, but try again + * from the top, as we may be able to queue the skb to that context. + */ + if (ctx) { + /* unlink the new (sole) skb, we don't want it freed with ctx */ + __skb_queue_head_init(&ctx->skbs); + mctp_usblib_tx_ctx_free(ctx, reason); + if (++try > max_tries) { + kfree_skb(skb); + mctp_usblib_tx_stats_single_drop(dev); + return -EBUSY; + } + goto retry; + } + + return 0; } EXPORT_SYMBOL_GPL(mctp_usblib_tx_push); @@ -373,7 +522,18 @@ EXPORT_SYMBOL_GPL(mctp_usblib_tx_push); void mctp_usblib_tx_cancel(struct mctp_usblib_tx *tx, struct net_device *dev, enum skb_drop_reason reason) { - /* nothing to do at present, no ctx is persistent */ + struct mctp_usblib_tx_ctx *ctx = NULL; + unsigned long flags; + + spin_lock_irqsave(&tx->lock, flags); + swap(tx->cur_ctx, ctx); + spin_unlock_irqrestore(&tx->lock, flags); + + if (!ctx) + return; + + mctp_usblib_tx_stats_update(ctx, dev, false); + mctp_usblib_tx_ctx_free(ctx, reason); } EXPORT_SYMBOL_GPL(mctp_usblib_tx_cancel); diff --git a/include/linux/usb/mctp-usb.h b/include/linux/usb/mctp-usb.h index 76f9d8879254..2e1cde6a6745 100644 --- a/include/linux/usb/mctp-usb.h +++ b/include/linux/usb/mctp-usb.h @@ -58,10 +58,6 @@ void mctp_usblib_rx_cancel(struct mctp_usblib_rx *rx); /* * TX handle: created by mctp_usblib_tx_push() during the tx path, and * may persist across multiple packet transmits. - * - * Currently though, there is a 1:1 mapping between packets and transfers, so - * the tx context will be cleared over each transmit. This will change in - * future. */ struct mctp_usblib_tx_ctx; @@ -76,6 +72,10 @@ struct mctp_usblib_tx_ops { struct mctp_usblib_tx { struct mctp_usblib_tx_ops ops; void *priv; + /* protects access to cur_ctx */ + spinlock_t lock; + /* context to which we are adding packets, cleared on send */ + struct mctp_usblib_tx_ctx *cur_ctx; }; void mctp_usblib_tx_init(struct mctp_usblib_tx *tx, From f5bf226f3c7aab957d8874f7d437b08017b5c428 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Fri, 24 Jul 2026 13:15:28 +0800 Subject: [PATCH 0684/1433] net: mctp: usb: Accommodate DSP0283 v1.1 header format In the v1.1 update to DSP0283, we have a larger header field, of 13 bits rather than 8. In order to accommodate this, in preparation for proper v1.1 support, expand our struct mctp_usb_hdr's len field to a u16, and endian-convert when necessary. Because we don't yet support spanning mode, we will never receive or transmit with the top 5 bits set, so we always mask out anyway. This allows for a future change where we allow spanning mode with >512-byte transfers. Signed-off-by: Jeremy Kerr Link: https://patch.msgid.link/20260724-dev-mctp-usb-1-1-v5-7-e66bbba0dbdc@codeconstruct.com.au Signed-off-by: Jakub Kicinski --- drivers/net/mctp/mctp-usblib.c | 14 ++++++++------ include/linux/usb/mctp-usb.h | 11 ++++++++--- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/drivers/net/mctp/mctp-usblib.c b/drivers/net/mctp/mctp-usblib.c index 2e464254353e..d58178f47c06 100644 --- a/drivers/net/mctp/mctp-usblib.c +++ b/drivers/net/mctp/mctp-usblib.c @@ -99,6 +99,7 @@ int mctp_usblib_rx_complete(struct net_device *netdev, while (skb) { struct sk_buff *skb2 = NULL; struct mctp_usb_hdr *hdr; + u16 hdr_len; /* length of MCTP packet, no USB header */ u8 pkt_len; @@ -116,21 +117,23 @@ int mctp_usblib_rx_complete(struct net_device *netdev, break; } - if (hdr->len < + hdr_len = be16_to_cpu(hdr->len) & MCTP_USB_1_0_PKTLEN_MAX; + + if (hdr_len < sizeof(struct mctp_hdr) + sizeof(struct mctp_usb_hdr)) { netdev_dbg(netdev, "rx: short packet (hdr) %d\n", - hdr->len); + hdr_len); rc = -EPROTO; break; } /* we know we have at least sizeof(struct mctp_usb_hdr) here */ - pkt_len = hdr->len - sizeof(struct mctp_usb_hdr); + pkt_len = hdr_len - sizeof(struct mctp_usb_hdr); if (pkt_len > skb->len) { rc = -EPROTO; netdev_dbg(netdev, "rx: short packet (xfer) %d, actual %d\n", - hdr->len, skb->len); + hdr_len, skb->len); break; } @@ -399,8 +402,7 @@ static int mctp_usblib_tx_skb_prepare(struct sk_buff *skb, } hdr->id = cpu_to_be16(MCTP_USB_DMTF_ID); - hdr->rsvd = 0; - hdr->len = plen + sizeof(*hdr); + hdr->len = cpu_to_be16(plen + sizeof(*hdr)); return 0; } diff --git a/include/linux/usb/mctp-usb.h b/include/linux/usb/mctp-usb.h index 2e1cde6a6745..1a5e795b4ec1 100644 --- a/include/linux/usb/mctp-usb.h +++ b/include/linux/usb/mctp-usb.h @@ -2,7 +2,7 @@ /* * mctp-usb.h - MCTP USB transport binding: common definitions, * based on DMTF0283 specification: - * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.0.1.pdf + * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.1.0.pdf * * These are protocol-level definitions, that may be shared between host * and gadget drivers. @@ -17,10 +17,15 @@ #include #include +/* + * MCTP-over-USB transport header. DSP0283 v1.0 has an 8-bit length field + * (preceded by 8 reserved bits), v1.1 has a 13-bit length field (preceded by + * 3 reserved bits). We use a be16 for our length to handle the larger v1.1 + * representation, and mask as appropriate. + */ struct mctp_usb_hdr { __be16 id; - u8 rsvd; - u8 len; + __be16 len; } __packed; /* max transfer size for DSP0283 v1.0 */ From d52fc7f09a18d0c2b2c164e0a82136510cfa1798 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Fri, 24 Jul 2026 13:15:29 +0800 Subject: [PATCH 0685/1433] net: mctp: usblib: Implement receive-side packet spanning Using the existing prepare/complete API, we can persist the rx skb across receives to implement v1.1 packet spanning. Alter the packet-extraction loop to allow truncated packets, returning early with the skb persisted for the next IN urb completion. When we see we have a complete packet, netif_rx() that. If the packet boundary aligns with the urb completion, we can netif_rx() the whole thing. Those intermediate packets are cloned from the original (large-transfer-data) skb. Unlike existing behaviour, if the clone fails, we drop just that clone, instead of the existing transfer skb. This allows us to process the rest of the skb data, and any continuation of the span into the next transfer. One subtle change: the mctp_usblib_rx() helper now handles skbs with the full transport header, so we shift the skb_pull() for the header data to the helper, before doing the rx_bytes stats update. We still need to handle non-spanning mode, so error out on truncated-packet cases there. Signed-off-by: Jeremy Kerr Link: https://patch.msgid.link/20260724-dev-mctp-usb-1-1-v5-8-e66bbba0dbdc@codeconstruct.com.au Signed-off-by: Jakub Kicinski --- drivers/net/mctp/mctp-usb.c | 6 +- drivers/net/mctp/mctp-usblib.c | 165 +++++++++++++++++++++++---------- include/linux/usb/mctp-usb.h | 6 +- 3 files changed, 126 insertions(+), 51 deletions(-) diff --git a/drivers/net/mctp/mctp-usb.c b/drivers/net/mctp/mctp-usb.c index f911d1412a44..439e92722a0a 100644 --- a/drivers/net/mctp/mctp-usb.c +++ b/drivers/net/mctp/mctp-usb.c @@ -340,7 +340,10 @@ static int mctp_usb_probe(struct usb_interface *intf, spin_lock_init(&dev->rx_lock); usb_set_intfdata(intf, dev); - mctp_usblib_rx_init(&dev->rx); + rc = mctp_usblib_rx_init(&dev->rx, le16_to_cpu(ep_in->wMaxPacketSize), + false); + if (rc) + goto err_free_netdev; mctp_usblib_tx_init(&dev->tx, &tx_ops, dev); init_usb_anchor(&dev->tx_anchor); @@ -366,6 +369,7 @@ static int mctp_usb_probe(struct usb_interface *intf, err_fini_rxtx: mctp_usblib_tx_fini(&dev->tx); mctp_usblib_rx_fini(&dev->rx); +err_free_netdev: free_netdev(netdev); return rc; } diff --git a/drivers/net/mctp/mctp-usblib.c b/drivers/net/mctp/mctp-usblib.c index d58178f47c06..2304e33193b5 100644 --- a/drivers/net/mctp/mctp-usblib.c +++ b/drivers/net/mctp/mctp-usblib.c @@ -3,7 +3,7 @@ * mctp-usblib.c - MCTP-over-USB (DMTF DSP0283) transport helper library * * DSP0283 is available at: - * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.0.1.pdf + * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.1.0.pdf * * Copyright (C) 2024-2026 Code Construct Pty Ltd */ @@ -11,12 +11,23 @@ #include #include #include +#include #include #include -void mctp_usblib_rx_init(struct mctp_usblib_rx *rx) +int mctp_usblib_rx_init(struct mctp_usblib_rx *rx, u16 ep_pktlen, bool span) { + if (!ep_pktlen) + return -EINVAL; + + if (ep_pktlen & ~USB_ENDPOINT_MAXP_MASK) + return -EINVAL; + memset(rx, 0, sizeof(*rx)); + rx->span = span; + rx->ep_pktlen = ep_pktlen; + + return 0; } EXPORT_SYMBOL_GPL(mctp_usblib_rx_init); @@ -34,15 +45,51 @@ int mctp_usblib_rx_prepare(struct net_device *netdev, struct mctp_usblib_rx *rx, void **bufp, size_t *lenp, gfp_t gfp) { - const unsigned int len = MCTP_USB_1_0_XFER_SIZE; - struct sk_buff *skb; + struct sk_buff *skb = rx->skb; + unsigned int len = 0; - skb = __netdev_alloc_skb(netdev, len, gfp); - if (!skb) - return -ENOMEM; + if (skb && skb->len >= MCTP_USB_1_1_PKTLEN_MAX) { + /* something must have gone terribly wrong. clear and restart */ + mctp_usblib_rx_cancel(rx); + skb = NULL; + } + + len = rx->span ? roundup(MCTP_USB_1_1_PKTLEN_MAX, rx->ep_pktlen) + : MCTP_USB_1_0_XFER_SIZE; + + if (!skb) { + skb = __netdev_alloc_skb(netdev, len, gfp); + if (!skb) + return -ENOMEM; + + } else if (skb->cloned || skb_tailroom(skb) < rx->ep_pktlen) { + /* We always need to realloc if ->cloned, as we cannot + * resubmit the (now-shared) skb buffer for possible DMA. + * + * Otherwise (if we have an un-cloned SKB): just ensure we + * have sufficient space to prevent babble. Since we allocated + * for max size in the last prepare (and have not consumed any + * of that space for a prior MCTP packet, because !cloned), we + * have sufficient data to finish the current MCTP packet. + */ + struct sk_buff *skb2; + + skb2 = skb_copy_expand(skb, 0, len, gfp); + if (!skb2) + return -ENOMEM; + dev_kfree_skb_any(skb); + skb = skb2; + } rx->skb = skb; + /* Spanning mode allows ZLPs, so we don't require exactly one + * transfer packet. If we have extra tailroom, may as well use it, + * and we have ensured that the tailroom >= ep_pktlen. + */ + if (rx->span) + len = rounddown(skb_tailroom(skb), rx->ep_pktlen); + *bufp = skb_tail_pointer(skb); *lenp = len; @@ -56,6 +103,9 @@ static void mctp_usblib_rx(struct net_device *netdev, struct sk_buff *skb) struct mctp_skb_cb *cb; unsigned long flags; + skb_reset_mac_header(skb); + skb_pull(skb, sizeof(struct mctp_usb_hdr)); + /* we're called from an URB completion handler, and cannot assume local * irqs are always disabled */ @@ -96,72 +146,89 @@ int mctp_usblib_rx_complete(struct net_device *netdev, __skb_put(skb, len); - while (skb) { - struct sk_buff *skb2 = NULL; + for (;;) { struct mctp_usb_hdr *hdr; - u16 hdr_len; - /* length of MCTP packet, no USB header */ - u8 pkt_len; + struct sk_buff *skb2; + /* length of MCTP packet, including USB header */ + u16 pkt_len; - skb_reset_mac_header(skb); - hdr = skb_pull_data(skb, sizeof(*hdr)); - if (!hdr) { - rc = -ENOMSG; + /* no header yet, resubmit for the rest of the packet */ + if (skb->len < sizeof(*hdr)) { + if (!rx->span) { + netdev_dbg(netdev, + "rx: tiny xfer (%d) in non-span mode", + skb->len); + rc = -ENOMSG; + goto err_reset; + } break; } + hdr = (struct mctp_usb_hdr *)skb->data; + if (be16_to_cpu(hdr->id) != MCTP_USB_DMTF_ID) { + /* By resetting here, will start the next IN transfer + * at the beginning of the new skb. This will mean + * we re-sync when we next see a spanned packet aligned + * with the start of a transfer. + * + * In non-spanning mode, this just means we'll drop + * the current transfer only + */ netdev_dbg(netdev, "rx: invalid id %04x\n", be16_to_cpu(hdr->id)); rc = -EPROTO; - break; + goto err_reset; } - hdr_len = be16_to_cpu(hdr->len) & MCTP_USB_1_0_PKTLEN_MAX; - - if (hdr_len < - sizeof(struct mctp_hdr) + sizeof(struct mctp_usb_hdr)) { - netdev_dbg(netdev, "rx: short packet (hdr) %d\n", - hdr_len); + pkt_len = be16_to_cpu(hdr->len); + /* v1.1, with span enabled, has a 13-bit length */ + pkt_len &= rx->span ? + MCTP_USB_1_1_PKTLEN_MAX : MCTP_USB_1_0_PKTLEN_MAX; + if (pkt_len < sizeof(*hdr) + sizeof(struct mctp_hdr)) { + netdev_dbg(netdev, "rx: invalid len %d\n", pkt_len); rc = -EPROTO; - break; + goto err_reset; } - /* we know we have at least sizeof(struct mctp_usb_hdr) here */ - pkt_len = hdr_len - sizeof(struct mctp_usb_hdr); + /* span continues to the next transfer, resubmit */ if (pkt_len > skb->len) { - rc = -EPROTO; - netdev_dbg(netdev, - "rx: short packet (xfer) %d, actual %d\n", - hdr_len, skb->len); + if (!rx->span) { + netdev_dbg(netdev, + "rx: short xfer (%d vs %d) in non-span mode", + pkt_len, skb->len); + rc = -EPROTO; + goto err_reset; + } break; } - if (pkt_len < skb->len) { - /* more packets may follow - clone to a new - * skb to use on the next iteration - */ - skb2 = skb_clone(skb, GFP_ATOMIC); - if (skb2) { - if (!skb_pull(skb2, pkt_len)) { - dev_kfree_skb_any(skb2); - skb2 = NULL; - } - } else { - mctp_usblib_rx_stats_single_drop(netdev); - } - skb_trim(skb, pkt_len); + /* we have (exactly) a complete packet, RX it directly */ + if (pkt_len == skb->len) { + mctp_usblib_rx(netdev, skb); + rx->skb = NULL; + break; } - mctp_usblib_rx(netdev, skb); - skb = skb2; + /* more packets follow - RX a clone so that we can continue + * processing the current SKB, which may be the start of a + * span. + */ + skb2 = skb_clone(skb, GFP_ATOMIC); + if (skb2) { + skb_trim(skb2, pkt_len); + mctp_usblib_rx(netdev, skb2); + } else { + mctp_usblib_rx_stats_single_drop(netdev); + } + skb_pull(skb, pkt_len); } - if (skb) - dev_kfree_skb_any(skb); + return 0; +err_reset: + dev_kfree_skb_any(rx->skb); rx->skb = NULL; - return rc; } EXPORT_SYMBOL_GPL(mctp_usblib_rx_complete); diff --git a/include/linux/usb/mctp-usb.h b/include/linux/usb/mctp-usb.h index 1a5e795b4ec1..2979ddaa4dab 100644 --- a/include/linux/usb/mctp-usb.h +++ b/include/linux/usb/mctp-usb.h @@ -34,6 +34,8 @@ struct mctp_usb_hdr { #define MCTP_USB_MTU_MIN MCTP_USB_BTU #define MCTP_USB_1_0_PKTLEN_MAX U8_MAX #define MCTP_USB_1_0_MTU_MAX (MCTP_USB_1_0_PKTLEN_MAX - sizeof(struct mctp_usb_hdr)) +#define MCTP_USB_1_1_PKTLEN_MAX GENMASK(12, 0) +#define MCTP_USB_1_1_MTU_MAX (MCTP_USB_1_1_PKTLEN_MAX - sizeof(struct mctp_usb_hdr)) #define MCTP_USB_DMTF_ID 0x1ab4 /* mctp-usblib */ @@ -46,9 +48,11 @@ struct mctp_usb_hdr { */ struct mctp_usblib_rx { struct sk_buff *skb; + u16 ep_pktlen; + bool span; }; -void mctp_usblib_rx_init(struct mctp_usblib_rx *rx); +int mctp_usblib_rx_init(struct mctp_usblib_rx *rx, u16 ep_pktlen, bool span); void mctp_usblib_rx_fini(struct mctp_usblib_rx *rx); int mctp_usblib_rx_prepare(struct net_device *netdev, From 081ac93ca99c2ded63a4fb78c385ebedf03560c2 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Fri, 24 Jul 2026 13:15:30 +0800 Subject: [PATCH 0686/1433] net: mctp: usblib: Implement transmit-side packet spanning Add support for packet spanning as defined in DSP0283 v1.1. With the existing v1.0 implementation of multi-packet transfers, all we need here is to adjust the buffer sizes to suit v1.1. Signed-off-by: Jeremy Kerr Link: https://patch.msgid.link/20260724-dev-mctp-usb-1-1-v5-9-e66bbba0dbdc@codeconstruct.com.au Signed-off-by: Jakub Kicinski --- drivers/net/mctp/mctp-usb.c | 2 +- drivers/net/mctp/mctp-usblib.c | 28 +++++++++++++++++++--------- include/linux/usb/mctp-usb.h | 4 +++- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/drivers/net/mctp/mctp-usb.c b/drivers/net/mctp/mctp-usb.c index 439e92722a0a..2240c81cc6e7 100644 --- a/drivers/net/mctp/mctp-usb.c +++ b/drivers/net/mctp/mctp-usb.c @@ -344,7 +344,7 @@ static int mctp_usb_probe(struct usb_interface *intf, false); if (rc) goto err_free_netdev; - mctp_usblib_tx_init(&dev->tx, &tx_ops, dev); + mctp_usblib_tx_init(&dev->tx, &tx_ops, dev, false); init_usb_anchor(&dev->tx_anchor); dev->ep_in = ep_in->bEndpointAddress; diff --git a/drivers/net/mctp/mctp-usblib.c b/drivers/net/mctp/mctp-usblib.c index 2304e33193b5..816a86b26a11 100644 --- a/drivers/net/mctp/mctp-usblib.c +++ b/drivers/net/mctp/mctp-usblib.c @@ -248,7 +248,7 @@ EXPORT_SYMBOL_GPL(mctp_usblib_rx_cancel); struct mctp_usblib_tx_ctx { struct mctp_usblib_tx *tx; struct sk_buff_head skbs; - unsigned int len; + unsigned int buf_len, len; enum mctp_usblib_tx_buf_type { TX_SINGLE, TX_FLAT, @@ -258,18 +258,19 @@ struct mctp_usblib_tx_ctx { void mctp_usblib_tx_init(struct mctp_usblib_tx *tx, const struct mctp_usblib_tx_ops *ops, - void *priv) + void *priv, bool span) { memset(tx, 0, sizeof(*tx)); tx->ops = *ops; tx->priv = priv; + tx->span = span; spin_lock_init(&tx->lock); } EXPORT_SYMBOL_GPL(mctp_usblib_tx_init); static int mctp_usblib_tx_avail(struct mctp_usblib_tx_ctx *ctx) { - return ctx->buf_type == TX_SINGLE ? 0 : MCTP_USB_1_0_XFER_SIZE - ctx->len; + return ctx->buf_type == TX_SINGLE ? 0 : ctx->buf_len - ctx->len; } static bool mctp_usblib_tx_should_send(struct mctp_usblib_tx_ctx *ctx) @@ -358,6 +359,12 @@ void mctp_usblib_tx_fini(struct mctp_usblib_tx *tx) } EXPORT_SYMBOL_GPL(mctp_usblib_tx_fini); +/* Max size of a spanned TX. Since we allocate a separate span buffer, limit + * the tx-time allocations to 4k. Larger packets will be sent as single + * transfers. + */ +static const unsigned int TX_SPAN_MAX = 4096 - sizeof(struct mctp_usblib_tx_ctx); + static struct mctp_usblib_tx_ctx * mctp_usblib_tx_ctx_create(struct mctp_usblib_tx *tx, struct sk_buff *skb, bool single) @@ -366,11 +373,11 @@ mctp_usblib_tx_ctx_create(struct mctp_usblib_tx *tx, struct sk_buff *skb, struct mctp_usblib_tx_ctx *ctx; size_t sz = 0; - if (single) { + if (single || skb->len > TX_SPAN_MAX) { type = TX_SINGLE; } else { type = TX_FLAT; - sz = MCTP_USB_1_0_XFER_SIZE; + sz = tx->span ? TX_SPAN_MAX : MCTP_USB_1_0_XFER_SIZE; } ctx = kzalloc_flex(*ctx, buf, sz, GFP_ATOMIC); @@ -379,6 +386,7 @@ mctp_usblib_tx_ctx_create(struct mctp_usblib_tx *tx, struct sk_buff *skb, ctx->tx = tx; ctx->buf_type = type; + ctx->buf_len = sz; ctx->len = skb->len; skb_queue_head_init(&ctx->skbs); __skb_queue_tail(&ctx->skbs, skb); @@ -443,15 +451,17 @@ EXPORT_SYMBOL_GPL(mctp_usblib_tx_send_complete); * * On error, populates @reason. */ -static int mctp_usblib_tx_skb_prepare(struct sk_buff *skb, +static int mctp_usblib_tx_skb_prepare(struct sk_buff *skb, bool span, enum skb_drop_reason *reason) { + unsigned long plen, max_len; struct mctp_usb_hdr *hdr; - unsigned long plen; int rc; + max_len = span ? MCTP_USB_1_1_PKTLEN_MAX : MCTP_USB_1_0_PKTLEN_MAX; + plen = skb->len; - if (plen + sizeof(*hdr) > MCTP_USB_1_0_PKTLEN_MAX) { + if (plen + sizeof(*hdr) > max_len) { *reason = SKB_DROP_REASON_PKT_TOO_BIG; return -EMSGSIZE; } @@ -490,7 +500,7 @@ int mctp_usblib_tx_push(struct net_device *dev, unsigned long flags; int try = 1, rc; - rc = mctp_usblib_tx_skb_prepare(skb, &reason); + rc = mctp_usblib_tx_skb_prepare(skb, tx->span, &reason); if (rc) { mctp_usblib_tx_stats_single_drop(dev); kfree_skb_reason(skb, reason); diff --git a/include/linux/usb/mctp-usb.h b/include/linux/usb/mctp-usb.h index 2979ddaa4dab..4bb04a371105 100644 --- a/include/linux/usb/mctp-usb.h +++ b/include/linux/usb/mctp-usb.h @@ -81,6 +81,7 @@ struct mctp_usblib_tx_ops { struct mctp_usblib_tx { struct mctp_usblib_tx_ops ops; void *priv; + bool span; /* protects access to cur_ctx */ spinlock_t lock; /* context to which we are adding packets, cleared on send */ @@ -88,7 +89,8 @@ struct mctp_usblib_tx { }; void mctp_usblib_tx_init(struct mctp_usblib_tx *tx, - const struct mctp_usblib_tx_ops *ops, void *priv); + const struct mctp_usblib_tx_ops *ops, void *priv, + bool span); void mctp_usblib_tx_fini(struct mctp_usblib_tx *tx); void *mctp_usblib_tx_ctx_priv(struct mctp_usblib_tx_ctx *tx_ctx); From e84c0edbc41ab5d3d2650281c1aad10ea11906f1 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Fri, 24 Jul 2026 13:15:31 +0800 Subject: [PATCH 0687/1433] net: mctp: usblib: Add initial kunit tests Add some initial tests for the usblib receive path, where we're extracting MCTP packets from incoming USB transfer data. Signed-off-by: Jeremy Kerr Link: https://patch.msgid.link/20260724-dev-mctp-usb-1-1-v5-10-e66bbba0dbdc@codeconstruct.com.au Signed-off-by: Jakub Kicinski --- drivers/net/mctp/Kconfig | 5 + drivers/net/mctp/mctp-usblib-test.c | 412 ++++++++++++++++++++++++++++ drivers/net/mctp/mctp-usblib.c | 4 + 3 files changed, 421 insertions(+) create mode 100644 drivers/net/mctp/mctp-usblib-test.c diff --git a/drivers/net/mctp/Kconfig b/drivers/net/mctp/Kconfig index a564a792801d..c40ac9c665b7 100644 --- a/drivers/net/mctp/Kconfig +++ b/drivers/net/mctp/Kconfig @@ -57,6 +57,11 @@ config MCTP_TRANSPORT_USBLIB This will be automatically enabled by the transport driver. +config MCTP_TRANSPORT_USBLIB_TEST + bool "MCTP usblib tests" if !KUNIT_ALL_TESTS + depends on MCTP_TRANSPORT_USBLIB=y && KUNIT=y + default KUNIT_ALL_TESTS + config MCTP_TRANSPORT_USB tristate "MCTP USB transport" depends on USB diff --git a/drivers/net/mctp/mctp-usblib-test.c b/drivers/net/mctp/mctp-usblib-test.c new file mode 100644 index 000000000000..9df401a914ff --- /dev/null +++ b/drivers/net/mctp/mctp-usblib-test.c @@ -0,0 +1,412 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mctp-usblib-test.c - MCTP-over-USB (DMTF DSP0283) transport helper library, + * unit test definitions. + * + * Copyright (C) 2026 Code Construct Pty Ltd + */ + +#include +#include +#include +#include +#include +#include +#include + +struct mctp_usblib_test_dev { + struct net_device *ndev; + struct mctp_dev *mdev; + struct sk_buff_head rx_pkts; +}; + +struct mctp_usblib_test_ctx { + struct mctp_usblib_test_dev *dev; + struct mctp_route rt; +}; + +static netdev_tx_t mctp_usblib_dev_tx(struct sk_buff *skb, + struct net_device *ndev) +{ + /* we don't track any TXed packets at present */ + kfree_skb(skb); + return NETDEV_TX_OK; +} + +static const struct net_device_ops mctp_test_netdev_ops = { + .ndo_start_xmit = mctp_usblib_dev_tx, +}; + +static const u16 ep_maxpacket = 512; +static const mctp_eid_t local_eid = 8; + +static void mctp_usblib_dev_setup(struct net_device *ndev) +{ + ndev->type = ARPHRD_MCTP; + ndev->mtu = 8192; + ndev->flags = IFF_NOARP; + ndev->netdev_ops = &mctp_test_netdev_ops; + ndev->needs_free_netdev = true; + ndev->pcpu_stat_type = NETDEV_PCPU_STAT_DSTATS; +} + +static void mctp_usblib_test_dev_action(void *data) +{ + struct mctp_usblib_test_dev *dev = data; + + skb_queue_purge(&dev->rx_pkts); + if (dev->mdev) + mctp_dev_put(dev->mdev); + unregister_netdev(dev->ndev); +} + +static struct mctp_usblib_test_dev * +mctp_usblib_test_create_dev(struct kunit *test) +{ + struct mctp_usblib_test_dev *dev; + struct net_device *ndev; + int rc; + + ndev = alloc_netdev(sizeof(*dev), "mctptest%d", NET_NAME_ENUM, + mctp_usblib_dev_setup); + if (!ndev) + return NULL; + + dev = netdev_priv(ndev); + dev->ndev = ndev; + skb_queue_head_init(&dev->rx_pkts); + + rc = register_netdev(ndev); + if (rc) { + free_netdev(ndev); + return NULL; + } + + rc = kunit_add_action_or_reset(test, mctp_usblib_test_dev_action, dev); + if (rc) + return NULL; + + rcu_read_lock(); + dev->mdev = __mctp_dev_get(ndev); + if (dev->mdev) + dev->mdev->net = mctp_default_net(dev_net(ndev)); + rcu_read_unlock(); + + if (!dev->mdev) + return NULL; + + rtnl_lock(); + rc = dev_open(ndev, NULL); + rtnl_unlock(); + if (rc) + return NULL; + + return dev; +} + +static int mctp_usblib_test_dst_output(struct mctp_dst *dst, + struct sk_buff *skb) +{ + struct mctp_usblib_test_dev *dev = netdev_priv(skb->dev); + + skb_queue_tail(&dev->rx_pkts, skb); + + return 0; +} + +static void mctp_usblib_test_fini_action(void *data) +{ + struct mctp_usblib_test_ctx *ctx = data; + + /* The device will have been destroyed, so ->rt will be unlinked. + * Just ensure that the refcount is as expected. + */ + KUNIT_EXPECT_TRUE(current->kunit_test, + refcount_dec_and_test(&ctx->rt.refs)); + + kfree(ctx); +} + +static struct mctp_usblib_test_ctx *mctp_usblib_test_init(struct kunit *test) +{ + struct mctp_usblib_test_ctx *ctx; + struct mctp_route *rt; + int rc; + + ctx = kzalloc_obj(*ctx); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + INIT_LIST_HEAD(&ctx->rt.list); + rt = &ctx->rt; + refcount_set(&rt->refs, 1); + + rc = kunit_add_action_or_reset(test, mctp_usblib_test_fini_action, ctx); + KUNIT_ASSERT_EQ(test, rc, 0); + + ctx->dev = mctp_usblib_test_create_dev(test); + KUNIT_ASSERT_NOT_NULL(test, ctx->dev); + + rt->min = local_eid; + rt->max = local_eid; + rt->dst_type = MCTP_ROUTE_DIRECT; + rt->type = RTN_LOCAL; + rt->dev = ctx->dev->mdev; + rt->output = mctp_usblib_test_dst_output; + + rtnl_lock(); + list_add_rcu(&ctx->rt.list, &init_net.mctp.routes); + refcount_inc(&rt->refs); + rtnl_unlock(); + + return ctx; +} + +/* Init a MCTP-over-USB packet within a buffer. @len is the length of the + * buffer to write, @payload_len is the reported size of the MCTP-over-USB + * packet. + */ +static void mctp_usblib_test_init_pkt(void *data, size_t len, + size_t payload_len) +{ + struct { + struct mctp_usb_hdr usb; + struct mctp_hdr mctp; + } hdr; + + hdr.usb.id = cpu_to_be16(MCTP_USB_DMTF_ID); + hdr.usb.len = cpu_to_be16(payload_len); + hdr.mctp.ver = 1; + hdr.mctp.dest = local_eid; + hdr.mctp.src = 0; + hdr.mctp.flags_seq_tag = 0; + + memcpy(data, &hdr, min(len, sizeof(hdr))); + if (len > sizeof(hdr)) + memset(data + sizeof(hdr), 0, len - sizeof(hdr)); +} + +static void action_rx_fini(void *data) +{ + struct mctp_usblib_rx *rx = data; + + mctp_usblib_rx_fini(rx); + kfree(rx); +} + +static struct mctp_usblib_rx * +mctp_usblib_test_rx_init(struct kunit *test, bool span) +{ + struct mctp_usblib_rx *rx; + int rc; + + rx = kzalloc_obj(*rx); + if (rx) { + rc = kunit_add_action_or_reset(test, action_rx_fini, rx); + KUNIT_ASSERT_EQ(test, rc, 0); + } + KUNIT_ASSERT_NOT_NULL(test, rx); + + rc = mctp_usblib_rx_init(rx, ep_maxpacket, span); + KUNIT_ASSERT_EQ(test, rc, 0); + + return rx; +} + +/* Wrappers for usblib's rx_complete callback, which is intended to be called + * from atomic context + */ +static int mctp_usblib_test_rx_complete(struct net_device *netdev, + struct mctp_usblib_rx *rx, size_t len) +{ + int rc; + + local_bh_disable(); + rc = mctp_usblib_rx_complete(netdev, rx, len); + local_bh_enable(); + + return rc; +} + +/* Single packet, starting on a transfer boundary, contained entirely within + * the transfer + */ +static void mctp_usblib_test_rx_single(struct kunit *test) +{ + struct mctp_usblib_test_dev *dev; + struct mctp_usblib_test_ctx *ctx; + struct mctp_usblib_rx *rx; + struct sk_buff *skb; + size_t len; + void *buf; + int rc; + + ctx = mctp_usblib_test_init(test); + dev = ctx->dev; + + rx = mctp_usblib_test_rx_init(test, true); + + rc = mctp_usblib_rx_prepare(dev->ndev, rx, + &buf, &len, GFP_KERNEL); + KUNIT_ASSERT_EQ(test, rc, 0); + + /* we should always have a maxpacket of transfer available */ + KUNIT_ASSERT_GE(test, len, ep_maxpacket); + + mctp_usblib_test_init_pkt(buf, 8, 8); + + rc = mctp_usblib_test_rx_complete(dev->ndev, rx, 8); + KUNIT_ASSERT_EQ(test, rc, 0); + + skb = __skb_dequeue(&dev->rx_pkts); + KUNIT_EXPECT_NOT_NULL(test, skb); + if (skb) + KUNIT_EXPECT_EQ(test, skb->len, 4); + kfree_skb(skb); +} + +struct mctp_usblib_test_pkt_span { + const char *name; + size_t n_pkts; + size_t pkts[6]; + size_t n_xfers; + size_t xfers[6]; +}; + +static void +mctp_usblib_test_pkt_span_to_desc(const struct mctp_usblib_test_pkt_span *t, + char *desc) +{ + strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); +} + +static void +mctp_usblib_test_pkt_span_validate(struct kunit *test, + const struct mctp_usblib_test_pkt_span *span, + size_t *len) +{ + size_t pkt_len = 0, xfer_len = 0; + unsigned int i; + + for (i = 0; i < span->n_pkts; i++) { + KUNIT_ASSERT_GE_MSG(test, span->pkts[i], 8, + "pkt[%u] len too small (%zu) for %s", + i, span->pkts[i], span->name); + pkt_len += span->pkts[i]; + } + + for (i = 0; i < span->n_xfers; i++) + xfer_len += span->xfers[i]; + + KUNIT_ASSERT_EQ_MSG(test, pkt_len, xfer_len, + "invalid pkt_len (%zu) != xfer_len (%zu) for %s", + pkt_len, xfer_len, span->name); + + *len = pkt_len; +} + +static void mctp_usblib_test_rx_pkt_span(struct kunit *test) +{ + const struct mctp_usblib_test_pkt_span *pkt_span = test->param_value; + size_t len, xfer_len, off, xfer_off; + struct mctp_usblib_test_dev *dev; + struct mctp_usblib_test_ctx *ctx; + struct mctp_usblib_rx *rx; + unsigned int i; + u8 *pktbuf; + void *buf; + int rc; + + mctp_usblib_test_pkt_span_validate(test, pkt_span, &len); + pktbuf = kunit_kmalloc_array(test, 1, len, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pktbuf); + + /* lay out packets */ + for (off = 0, i = 0; i < pkt_span->n_pkts; i++) { + len = pkt_span->pkts[i]; + mctp_usblib_test_init_pkt(pktbuf + off, len, len); + off += len; + } + + ctx = mctp_usblib_test_init(test); + dev = ctx->dev; + + rx = mctp_usblib_test_rx_init(test, true); + + /* feed transfers */ + for (off = 0, xfer_off = 0, i = 0; i < pkt_span->n_xfers;) { + xfer_len = pkt_span->xfers[i] - xfer_off; + rc = mctp_usblib_rx_prepare(dev->ndev, rx, + &buf, &len, GFP_KERNEL); + KUNIT_ASSERT_EQ(test, rc, 0); + + KUNIT_ASSERT_GE(test, len, ep_maxpacket); + + len = min(len, xfer_len); + memcpy(buf, pktbuf + off, len); + + if (len == xfer_len) { + /* whole/end xfer, proceed to next */ + xfer_off = 0; + i++; + } else { + /* partial */ + xfer_off += len; + } + + rc = mctp_usblib_test_rx_complete(dev->ndev, rx, len); + KUNIT_ASSERT_EQ(test, rc, 0); + off += len; + } + + /* check received packets */ + KUNIT_EXPECT_EQ(test, dev->rx_pkts.qlen, pkt_span->n_pkts); + for (i = 0; ; i++) { + struct sk_buff *skb = __skb_dequeue(&dev->rx_pkts); + + if (!skb) + break; + + if (i < pkt_span->n_pkts) + KUNIT_EXPECT_EQ(test, skb->len, pkt_span->pkts[i] - 4); + + kfree_skb(skb); + } +} + +static const struct mctp_usblib_test_pkt_span mctp_usblib_test_pkt_spans[] = { + /* One packet completely within a transfer */ + { "1p1x-complete", 1, { 8 }, 1, { 8 } }, + /* Two small packets combined within one transfer */ + { "2p1x-combined", 2, { 8, 8 }, 1, { 16 } }, + /* A packet split over two transfers, at the MCTP payload */ + { "1p2x-split-payload", 1, { 16 }, 2, { 8, 8 } }, + /* A packet split over two transfers, at the USB transport header */ + { "1p2x-split-usbhdr", 1, { 16 }, 2, { 2, 14 } }, + /* A packet split over two transfers, at the MCTP header */ + { "1p2x-split-mctphdr", 1, { 16 }, 2, { 6, 10 } }, + /* Single packet split over 3 transfers, middle entirely continuation */ + { "1p3x-split", 1, { 12 }, 3, { 4, 4, 4 } }, + /* Max-sized single transfer */ + { "1p1x-large", 1, { 8191 }, 1, { 8191 } }, + /* Two large packets, split at the worst-case for allocation, with a + * single byte continuing the span + */ + { "2p2x-large-split", 2, { 8190, 8190 }, 2, { 8191, 8189 } }, +}; + +KUNIT_ARRAY_PARAM(mctp_usblib_test_rx_pkt_span, mctp_usblib_test_pkt_spans, + mctp_usblib_test_pkt_span_to_desc); + +static struct kunit_case mctp_usblib_test_cases[] = { + KUNIT_CASE(mctp_usblib_test_rx_single), + KUNIT_CASE_PARAM(mctp_usblib_test_rx_pkt_span, + mctp_usblib_test_rx_pkt_span_gen_params), + {} +}; + +static struct kunit_suite mctp_usblib_test_suite = { + .name = "mctp-usblib", + .test_cases = mctp_usblib_test_cases, +}; + +kunit_test_suite(mctp_usblib_test_suite); diff --git a/drivers/net/mctp/mctp-usblib.c b/drivers/net/mctp/mctp-usblib.c index 816a86b26a11..31997f989026 100644 --- a/drivers/net/mctp/mctp-usblib.c +++ b/drivers/net/mctp/mctp-usblib.c @@ -619,3 +619,7 @@ EXPORT_SYMBOL_GPL(mctp_usblib_tx_cancel); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Jeremy Kerr "); MODULE_DESCRIPTION("MCTP USB transport library"); + +#if IS_ENABLED(CONFIG_MCTP_TRANSPORT_USBLIB_TEST) +#include "mctp-usblib-test.c" +#endif From b4a27a474b5179b7c4a96424bdd5c132cf8a033e Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Fri, 24 Jul 2026 13:15:32 +0800 Subject: [PATCH 0688/1433] net: mctp: usb: enable v1.1 packet spanning Now that mctp-usblib supports DSP0283 v1.1 packet spanning, enable it in our host-side transport driver. Add a match for the new device subclass (0x02), and indicating spanning mode to the usblib rx/tx implementation. Signed-off-by: Jeremy Kerr Link: https://patch.msgid.link/20260724-dev-mctp-usb-1-1-v5-11-e66bbba0dbdc@codeconstruct.com.au Signed-off-by: Jakub Kicinski --- drivers/net/mctp/mctp-usb.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/drivers/net/mctp/mctp-usb.c b/drivers/net/mctp/mctp-usb.c index 2240c81cc6e7..c4ff9e91a768 100644 --- a/drivers/net/mctp/mctp-usb.c +++ b/drivers/net/mctp/mctp-usb.c @@ -3,9 +3,9 @@ * mctp-usb.c - MCTP-over-USB (DMTF DSP0283) transport binding driver. * * DSP0283 is available at: - * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.0.1.pdf + * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.1.0.pdf * - * Copyright (C) 2024-2025 Code Construct Pty Ltd + * Copyright (C) 2024-2026 Code Construct Pty Ltd */ #include @@ -22,6 +22,7 @@ struct mctp_usb { struct usb_device *usbdev; struct usb_interface *intf; + bool span; struct net_device *netdev; @@ -43,6 +44,11 @@ struct mctp_usb { struct usb_anchor tx_anchor; }; +enum { + MCTP_USB_SUBCLASS_BASE = 0x00, + MCTP_USB_SUBCLASS_SPAN = 0x02, +}; + static void mctp_usb_out_complete(struct urb *urb) { struct mctp_usblib_tx_ctx *tx_ctx = urb->context; @@ -71,6 +77,9 @@ static int mctp_usb_tx_send(struct mctp_usblib_tx_ctx *tx_ctx, usb_sndbulkpipe(mctp_usb->usbdev, mctp_usb->ep_out), data, len, mctp_usb_out_complete, tx_ctx); + if (mctp_usb->span) + urb->transfer_flags |= URB_ZERO_PACKET; + netif_stop_queue(mctp_usb->netdev); usb_anchor_urb(urb, &mctp_usb->tx_anchor); @@ -316,6 +325,7 @@ static int mctp_usb_probe(struct usb_interface *intf, struct usb_host_interface *iface_desc; struct net_device *netdev; struct mctp_usb *dev; + bool span; int rc; /* only one alternate */ @@ -327,6 +337,8 @@ static int mctp_usb_probe(struct usb_interface *intf, return rc; } + span = iface_desc->desc.bInterfaceSubClass == MCTP_USB_SUBCLASS_SPAN; + netdev = alloc_netdev(sizeof(*dev), "mctpusb%d", NET_NAME_ENUM, mctp_usb_netdev_setup); if (!netdev) @@ -334,17 +346,20 @@ static int mctp_usb_probe(struct usb_interface *intf, SET_NETDEV_DEV(netdev, &intf->dev); dev = netdev_priv(netdev); + dev->span = span; dev->netdev = netdev; dev->usbdev = interface_to_usbdev(intf); dev->intf = intf; spin_lock_init(&dev->rx_lock); + if (dev->span) + netdev->max_mtu = MCTP_USB_1_1_MTU_MAX; usb_set_intfdata(intf, dev); rc = mctp_usblib_rx_init(&dev->rx, le16_to_cpu(ep_in->wMaxPacketSize), - false); + dev->span); if (rc) goto err_free_netdev; - mctp_usblib_tx_init(&dev->tx, &tx_ops, dev, false); + mctp_usblib_tx_init(&dev->tx, &tx_ops, dev, dev->span); init_usb_anchor(&dev->tx_anchor); dev->ep_in = ep_in->bEndpointAddress; @@ -386,7 +401,8 @@ static void mctp_usb_disconnect(struct usb_interface *intf) } static const struct usb_device_id mctp_usb_devices[] = { - { USB_INTERFACE_INFO(USB_CLASS_MCTP, 0x0, 0x1) }, + { USB_INTERFACE_INFO(USB_CLASS_MCTP, MCTP_USB_SUBCLASS_BASE, 0x1) }, + { USB_INTERFACE_INFO(USB_CLASS_MCTP, MCTP_USB_SUBCLASS_SPAN, 0x1) }, { 0 }, }; From 88dbfe0d95817423ec7b2fb2d3fa1212fc5019a5 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Fri, 24 Jul 2026 13:15:33 +0800 Subject: [PATCH 0689/1433] net: mctp: usb: Allow multiple urbs in flight Currently, we stop tx queues when we have one urb submitted. This means we will immediately hit dev_hard_start_xmit's tx-queues-off -> NETDEV_TX_BUSY case, and revert to the requeue -> gso_skb single-dequeue path, and no longer be able to pack skbs without an xmit_more indication. Instead, allow a few urbs to be in-flight, with a limit of 16kB of data outstanding (after which we will disable queues). With this, the tx path will cause fewer requeues (and therefore non-packed transfers) under normal loads. Signed-off-by: Jeremy Kerr Link: https://patch.msgid.link/20260724-dev-mctp-usb-1-1-v5-12-e66bbba0dbdc@codeconstruct.com.au Signed-off-by: Jakub Kicinski --- drivers/net/mctp/mctp-usb.c | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/drivers/net/mctp/mctp-usb.c b/drivers/net/mctp/mctp-usb.c index c4ff9e91a768..542a570c76cc 100644 --- a/drivers/net/mctp/mctp-usb.c +++ b/drivers/net/mctp/mctp-usb.c @@ -42,6 +42,9 @@ struct mctp_usb { struct mctp_usblib_tx tx; struct usb_anchor tx_anchor; + /* serialises tx_qmem updates to netdev queue states */ + spinlock_t tx_qmem_lock; + int tx_qmem; }; enum { @@ -49,23 +52,41 @@ enum { MCTP_USB_SUBCLASS_SPAN = 0x02, }; +/* We use a total-size limit for outstanding URBs, as the transfer counts + * may vary a lot between spanning- and non-spanning modes. In spanning mode, + * this will allow for a couple of max-sized transfers to be in flight. In + * non-spanning mode, 32. + * + * We want to avoid disabling the tx queue if possible; doing so will end up + * requeueing to gso_skb, and we only dequeue from that one skb at a time, + * so can no longer perform transfer packing. + */ +static const unsigned int TX_QMEM_MAX = 16384; + static void mctp_usb_out_complete(struct urb *urb) { struct mctp_usblib_tx_ctx *tx_ctx = urb->context; struct mctp_usb *mctp_usb = mctp_usblib_tx_ctx_priv(tx_ctx); + unsigned int len = urb->transfer_buffer_length; struct net_device *netdev = mctp_usb->netdev; + unsigned long flags; mctp_usblib_tx_send_complete(tx_ctx, netdev, urb->status == 0); usb_free_urb(urb); - netif_wake_queue(netdev); + spin_lock_irqsave(&mctp_usb->tx_qmem_lock, flags); + mctp_usb->tx_qmem -= len; + if (mctp_usb->tx_qmem < TX_QMEM_MAX && netif_running(netdev)) + netif_wake_queue(netdev); + spin_unlock_irqrestore(&mctp_usb->tx_qmem_lock, flags); } static int mctp_usb_tx_send(struct mctp_usblib_tx_ctx *tx_ctx, void *data, size_t len) { struct mctp_usb *mctp_usb = mctp_usblib_tx_ctx_priv(tx_ctx); + unsigned long flags; struct urb *urb; int rc; @@ -80,8 +101,6 @@ static int mctp_usb_tx_send(struct mctp_usblib_tx_ctx *tx_ctx, if (mctp_usb->span) urb->transfer_flags |= URB_ZERO_PACKET; - netif_stop_queue(mctp_usb->netdev); - usb_anchor_urb(urb, &mctp_usb->tx_anchor); rc = usb_submit_urb(urb, GFP_ATOMIC); @@ -89,7 +108,12 @@ static int mctp_usb_tx_send(struct mctp_usblib_tx_ctx *tx_ctx, netdev_dbg(mctp_usb->netdev, "TX urb submit failed, %d\n", rc); usb_unanchor_urb(urb); usb_free_urb(urb); - netif_start_queue(mctp_usb->netdev); + } else { + spin_lock_irqsave(&mctp_usb->tx_qmem_lock, flags); + mctp_usb->tx_qmem += len; + if (mctp_usb->tx_qmem >= TX_QMEM_MAX) + netif_stop_queue(mctp_usb->netdev); + spin_unlock_irqrestore(&mctp_usb->tx_qmem_lock, flags); } return rc; @@ -353,6 +377,7 @@ static int mctp_usb_probe(struct usb_interface *intf, spin_lock_init(&dev->rx_lock); if (dev->span) netdev->max_mtu = MCTP_USB_1_1_MTU_MAX; + spin_lock_init(&dev->tx_qmem_lock); usb_set_intfdata(intf, dev); rc = mctp_usblib_rx_init(&dev->rx, le16_to_cpu(ep_in->wMaxPacketSize), From 333f7de560e1196034b67db16916b10a0c529e1d Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 23 Jul 2026 19:39:27 +0800 Subject: [PATCH 0690/1433] wifi: mm81x: prevent timers from outliving teardown The three timer teardown paths call timer_delete_sync_try() and ignore its return value. If a callback is running on another CPU it returns -1 without waiting, and it does not prevent a later rearm even when it does deactivate a pending timer. mm81x_skbq_tx_complete() can rearm the stale-status timer, and the rc and yaps callbacks queue work that rearms their timers. Teardown can therefore continue with a callback still running or the timer rearmed, so it fires after the associated state has been freed. Use timer_shutdown_sync() for these permanent teardowns: it waits for an in-flight callback and prevents any future rearm. In mm81x_rc_deinit() shut the timer down before cancel_work_sync() so the work can no longer recreate the timer/work cycle. Fixes: b1906cea00b0 ("wifi: mm81x: add mm81x Wi-Fi HaLow driver") Signed-off-by: Linmao Li Reviewed-by: Dan Callaghan Link: https://patch.msgid.link/20260723113927.2370301-1-lilinmao@kylinos.cn Signed-off-by: Lachlan Hodges --- drivers/net/wireless/morsemicro/mm81x/mac.c | 2 +- drivers/net/wireless/morsemicro/mm81x/rc.c | 2 +- drivers/net/wireless/morsemicro/mm81x/yaps.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/morsemicro/mm81x/mac.c b/drivers/net/wireless/morsemicro/mm81x/mac.c index 392dae5d7ce9..08ca116a68b4 100644 --- a/drivers/net/wireless/morsemicro/mm81x/mac.c +++ b/drivers/net/wireless/morsemicro/mm81x/mac.c @@ -2349,7 +2349,7 @@ static void mm81x_stale_tx_status_timer(struct timer_list *t) static void mm81x_stale_tx_status_timer_finish(struct mm81x *mors) { - timer_delete_sync_try(&mors->stale_status.timer); + timer_shutdown_sync(&mors->stale_status.timer); } static void mm81x_mac_stale_tx_status_timer_init(struct mm81x *mors) diff --git a/drivers/net/wireless/morsemicro/mm81x/rc.c b/drivers/net/wireless/morsemicro/mm81x/rc.c index 04aff66de4bd..28dd293df966 100644 --- a/drivers/net/wireless/morsemicro/mm81x/rc.c +++ b/drivers/net/wireless/morsemicro/mm81x/rc.c @@ -60,8 +60,8 @@ void mm81x_rc_init(struct mm81x *mors) void mm81x_rc_deinit(struct mm81x *mors) { + timer_shutdown_sync(&mors->mrc.timer); cancel_work_sync(&mors->mrc.work); - timer_delete_sync_try(&mors->mrc.timer); } static void mm81x_rc_sta_config_guard_per_bw(struct ieee80211_sta *sta, diff --git a/drivers/net/wireless/morsemicro/mm81x/yaps.c b/drivers/net/wireless/morsemicro/mm81x/yaps.c index bdadb822bf9a..e98a2a58726f 100644 --- a/drivers/net/wireless/morsemicro/mm81x/yaps.c +++ b/drivers/net/wireless/morsemicro/mm81x/yaps.c @@ -597,7 +597,7 @@ static void mm81x_yaps_q_chip_full_timer_init(struct mm81x_yaps *yaps) static void mm81x_yaps_q_chip_full_timer_finish(struct mm81x_yaps *yaps) { - timer_delete_sync_try(&yaps->chip_queue_full.timer); + timer_shutdown_sync(&yaps->chip_queue_full.timer); } int mm81x_yaps_init(struct mm81x *mors) From 574bd79955d166c00c2b1531fed591ee70b6ba04 Mon Sep 17 00:00:00 2001 From: Dmitry Gomzyakov Date: Sun, 10 May 2026 15:29:10 +0500 Subject: [PATCH 0691/1433] wifi: mt76: connac: add MT7991A (0x7991) to is_mt7996() The MT7991A chipset uses PCI device ID 0x7991 (MT7996_DEVICE_ID_2), but is_mt7996() only checks for 0x7990. This causes MT7991A devices to use incorrect chip-specific settings, such as: - MSDU_CNT_V2 instead of MSDU_CNT in TX descriptors - Wrong WTBL BMC size (32 instead of 64) - Incorrect prefetch depth for MCU queues Fixes: 7014fe535860 ("wifi: mt76: mt7996: add macros for pci device ids") Signed-off-by: Dmitry Gomzyakov Link: https://patch.msgid.link/20260510102911.1883849-2-kyoto1337@protonmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76_connac.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac.h index 2aa6078993e9..d15cce296c1e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac.h @@ -245,7 +245,8 @@ static inline bool is_mt798x(struct mt76_dev *dev) static inline bool is_mt7996(struct mt76_dev *dev) { - return mt76_chip(dev) == 0x7990; + u16 chip = mt76_chip(dev); + return chip == 0x7990 || chip == 0x7991; } static inline bool is_mt7992(struct mt76_dev *dev) From bda8324270b1ac91bfba1df8928e0570e29759e8 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Fri, 12 Jun 2026 12:13:31 +0800 Subject: [PATCH 0692/1433] wifi: mt76: mt7615: avoid waiting for mac work under the mt76 mutex mt7615_suspend() acquired the mt76 mutex and then called cancel_delayed_work_sync() on mac_work. mt7615_mac_work() acquires the same mutex via mt7615_mutex_acquire() at the top of the worker, so if mac_work is already running and blocked on the mutex, the suspend path deadlocks waiting for the work it holds the mutex against. Flush scan_work and mac_work before taking the mutex, matching the suspend paths in mt7921 and mt7925. scan_work only takes the mt76 spinlock, but moving it keeps the sequence consistent. This also keeps mac_work from running over an already suspended HIF, which the previous split (async cancel under the lock, sync cancel after release) would have allowed. Fixes: c6bf20109a3f ("mt76: mt7615: add WoW support") Cc: stable@vger.kernel.org Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260612041331.2596331-1-runyu.xiao@seu.edu.cn Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7615/main.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/main.c b/drivers/net/wireless/mediatek/mt76/mt7615/main.c index fc619acbb40d..67f56e428d9a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7615/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7615/main.c @@ -1239,11 +1239,12 @@ static int mt7615_suspend(struct ieee80211_hw *hw, cancel_delayed_work_sync(&dev->pm.ps_work); mt76_connac_free_pending_tx_skbs(&dev->pm, NULL); + cancel_delayed_work_sync(&phy->scan_work); + cancel_delayed_work_sync(&phy->mt76->mac_work); + mt7615_mutex_acquire(dev); clear_bit(MT76_STATE_RUNNING, &phy->mt76->state); - cancel_delayed_work_sync(&phy->scan_work); - cancel_delayed_work_sync(&phy->mt76->mac_work); set_bit(MT76_STATE_SUSPEND, &phy->mt76->state); ieee80211_iterate_active_interfaces(hw, From 047dc4dc582066266643b2617373e62f162de215 Mon Sep 17 00:00:00 2001 From: Emery Hsin Date: Fri, 12 Jun 2026 15:50:38 +0800 Subject: [PATCH 0693/1433] wifi: mt76: mt792x: consolidate rx interrupt masks into all_complete_mask Add all_complete_mask to irq_map rx sub-struct and use it in mt792x_irq_tasklet() and mt792x_dma_enable() to replace individual per-ring mask OR expressions. This is a preparation patch before enabling MT7928 PCIe support. Signed-off-by: Emery Hsin Link: https://patch.msgid.link/20260612075042.2577193-1-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt792x.h | 1 + drivers/net/wireless/mediatek/mt76/mt792x_dma.c | 8 ++------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index 70073b43af54..83c729f8bb76 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -200,6 +200,7 @@ struct mt792x_irq_map { u32 mcu_complete_mask; } tx; struct { + u32 all_complete_mask; u32 data_complete_mask; u32 wm_complete_mask; u32 wm2_complete_mask; diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c index fc326447c792..946ff6d58954 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c @@ -39,9 +39,7 @@ void mt792x_irq_tasklet(unsigned long data) trace_dev_irq(&dev->mt76, intr, dev->mt76.mmio.irqmask); - mask |= intr & (irq_map->rx.data_complete_mask | - irq_map->rx.wm_complete_mask | - irq_map->rx.wm2_complete_mask); + mask |= intr & irq_map->rx.all_complete_mask; if (intr & dev->irq_map->tx.mcu_complete_mask) mask |= dev->irq_map->tx.mcu_complete_mask; @@ -276,9 +274,7 @@ int mt792x_dma_enable(struct mt792x_dev *dev) /* enable interrupts for TX/RX rings */ mt76_connac_irq_enable(&dev->mt76, dev->irq_map->tx.all_complete_mask | - dev->irq_map->rx.data_complete_mask | - dev->irq_map->rx.wm2_complete_mask | - dev->irq_map->rx.wm_complete_mask | + dev->irq_map->rx.all_complete_mask | MT_INT_MCU_CMD); mt76_set(dev, MT_MCU2HOST_SW_INT_ENA, MT_MCU_CMD_WAKE_RX_PCIE); From 83c65e82e5b2470df71d5eb5480c16ac094c4462 Mon Sep 17 00:00:00 2001 From: Emery Hsin Date: Fri, 12 Jun 2026 15:50:39 +0800 Subject: [PATCH 0694/1433] wifi: mt76: connac2: apply new rx all_complete_mask Populate all_complete_mask in MT7921 irq_map and update pci_resume() and mac_reset() to use irq_map->rx.all_complete_mask instead of the hardcoded MT_INT_RX_DONE_ALL macro. This is a preparation patch before enabling MT7928 PCIe support. Signed-off-by: Emery Hsin Link: https://patch.msgid.link/20260612075042.2577193-2-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/pci.c | 4 +++- drivers/net/wireless/mediatek/mt76/mt7921/pci_mac.c | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c index 7728c5ae6791..764de48c87e4 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c @@ -293,6 +293,7 @@ static int mt7921_pci_probe(struct pci_dev *pdev, .mcu_complete_mask = MT_INT_TX_DONE_MCU, }, .rx = { + .all_complete_mask = MT_INT_RX_DONE_ALL, .data_complete_mask = MT_INT_RX_DONE_DATA, .wm_complete_mask = MT_INT_RX_DONE_WM, .wm2_complete_mask = MT_INT_RX_DONE_WM2, @@ -560,7 +561,8 @@ static int mt7921_pci_resume(struct device *device) mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0xff); mt76_connac_irq_enable(&dev->mt76, dev->irq_map->tx.all_complete_mask | - MT_INT_RX_DONE_ALL | MT_INT_MCU_CMD); + dev->irq_map->rx.all_complete_mask | + MT_INT_MCU_CMD); mt76_set(dev, MT_MCU2HOST_SW_INT_ENA, MT_MCU_CMD_WAKE_RX_PCIE); /* put dma enabled */ diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci_mac.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci_mac.c index 0db7acb3a637..98b3469ac597 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/pci_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci_mac.c @@ -96,7 +96,8 @@ int mt7921e_mac_reset(struct mt792x_dev *dev) mt76_wr(dev, dev->irq_map->host_irq_enable, dev->irq_map->tx.all_complete_mask | - MT_INT_RX_DONE_ALL | MT_INT_MCU_CMD); + dev->irq_map->rx.all_complete_mask | + MT_INT_MCU_CMD); mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0xff); err = mt7921e_driver_own(dev); From c4edf9e3e0e5a6a6e195cd35fae5d9404a559413 Mon Sep 17 00:00:00 2001 From: Emery Hsin Date: Fri, 12 Jun 2026 15:50:40 +0800 Subject: [PATCH 0695/1433] wifi: mt76: mt7925: replace shared rx irq masks with per-chip definitions Replace shared MT_INT_RX_DONE_* macros with chip-specific MT7925_INT_RX_DONE_{DATA,WM,WM2,ALL} and populate all_complete_mask in mt7925_irq_map. Update resume and mac_reset paths accordingly. This is a preparation patch before enabling MT7928 PCIe support. Signed-off-by: Emery Hsin Link: https://patch.msgid.link/20260612075042.2577193-3-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/pci.c | 33 ++++++++++--------- .../wireless/mediatek/mt76/mt7925/pci_mac.c | 4 +-- .../net/wireless/mediatek/mt76/mt7925/regs.h | 9 ++--- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c index ea64303283ed..95a0bd615167 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c @@ -298,6 +298,20 @@ static int mt7925_dma_init(struct mt792x_dev *dev) return mt792x_dma_enable(dev); } +static const struct mt792x_irq_map mt7925_irq_map = { + .host_irq_enable = MT_WFDMA0_HOST_INT_ENA, + .tx = { + .all_complete_mask = MT_INT_TX_DONE_ALL, + .mcu_complete_mask = MT_INT_TX_DONE_MCU, + }, + .rx = { + .all_complete_mask = MT7925_INT_RX_DONE_ALL, + .data_complete_mask = MT7925_INT_RX_DONE_DATA, + .wm_complete_mask = MT7925_INT_RX_DONE_WM, + .wm2_complete_mask = MT_INT_RX_DONE_WM2, + }, +}; + static const struct mt792x_irq_map mt7927_irq_map = { .host_irq_enable = MT_WFDMA0_HOST_INT_ENA, .tx = { @@ -339,17 +353,6 @@ static int mt7925_pci_probe(struct pci_dev *pdev, .drv_own = mt792xe_mcu_drv_pmctrl, .fw_own = mt792xe_mcu_fw_pmctrl, }; - static const struct mt792x_irq_map irq_map = { - .host_irq_enable = MT_WFDMA0_HOST_INT_ENA, - .tx = { - .all_complete_mask = MT_INT_TX_DONE_ALL, - .mcu_complete_mask = MT_INT_TX_DONE_MCU, - }, - .rx = { - .data_complete_mask = HOST_RX_DONE_INT_ENA2, - .wm_complete_mask = HOST_RX_DONE_INT_ENA0, - }, - }; struct ieee80211_ops *ops; struct mt76_bus_ops *bus_ops; struct mt792x_dev *dev; @@ -407,7 +410,7 @@ static int mt7925_pci_probe(struct pci_dev *pdev, dev = container_of(mdev, struct mt792x_dev, mt76); dev->fw_features = features; dev->hif_ops = &mt7925_pcie_ops; - dev->irq_map = is_mt7927_hw ? &mt7927_irq_map : &irq_map; + dev->irq_map = is_mt7927_hw ? &mt7927_irq_map : &mt7925_irq_map; mt76_mmio_init(&dev->mt76, pcim_iomap_table(pdev)[0]); tasklet_init(&mdev->irq_tasklet, mt792x_irq_tasklet, (unsigned long)dev); @@ -456,7 +459,7 @@ static int mt7925_pci_probe(struct pci_dev *pdev, if (ret) goto err_free_dev; - mt76_wr(dev, irq_map.host_irq_enable, 0); + mt76_wr(dev, dev->irq_map->host_irq_enable, 0); mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0xff); @@ -613,9 +616,7 @@ static int _mt7925_pci_resume(struct device *device, bool restore) mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0xff); mt76_connac_irq_enable(&dev->mt76, dev->irq_map->tx.all_complete_mask | - dev->irq_map->rx.data_complete_mask | - dev->irq_map->rx.wm_complete_mask | - dev->irq_map->rx.wm2_complete_mask | + dev->irq_map->rx.all_complete_mask | MT_INT_MCU_CMD); mt76_set(dev, MT_MCU2HOST_SW_INT_ENA, MT_MCU_CMD_WAKE_RX_PCIE); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c index 97683949a305..d288739e1307 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c @@ -119,9 +119,7 @@ int mt7925e_mac_reset(struct mt792x_dev *dev) mt76_wr(dev, dev->irq_map->host_irq_enable, dev->irq_map->tx.all_complete_mask | - dev->irq_map->rx.data_complete_mask | - dev->irq_map->rx.wm_complete_mask | - dev->irq_map->rx.wm2_complete_mask | + dev->irq_map->rx.all_complete_mask | MT_INT_MCU_CMD); mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0xff); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regs.h b/drivers/net/wireless/mediatek/mt76/mt7925/regs.h index 24985bba1b90..aed90cc82858 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/regs.h @@ -43,11 +43,12 @@ #define HOST_TX_DONE_INT_ENA17 BIT(27) /* WFDMA interrupt */ -#define MT_INT_RX_DONE_DATA HOST_RX_DONE_INT_ENA2 -#define MT_INT_RX_DONE_WM HOST_RX_DONE_INT_ENA0 #define MT_INT_RX_DONE_WM2 HOST_RX_DONE_INT_ENA1 -#define MT_INT_RX_DONE_ALL (MT_INT_RX_DONE_DATA | \ - MT_INT_RX_DONE_WM | \ + +#define MT7925_INT_RX_DONE_DATA HOST_RX_DONE_INT_ENA2 +#define MT7925_INT_RX_DONE_WM HOST_RX_DONE_INT_ENA0 +#define MT7925_INT_RX_DONE_ALL (MT7925_INT_RX_DONE_DATA | \ + MT7925_INT_RX_DONE_WM | \ MT_INT_RX_DONE_WM2) #define MT_INT_TX_DONE_MCU_WM (HOST_TX_DONE_INT_ENA15 | \ From 4590ceed74133709a6f6df4d241f152a2a0625db Mon Sep 17 00:00:00 2001 From: Emery Hsin Date: Fri, 12 Jun 2026 15:50:41 +0800 Subject: [PATCH 0696/1433] wifi: mt76: mt7925: add MT7927 per-chip rx irq definitions Add MT7927_INT_RX_DONE_{DATA,WM,WM2,ALL} macros and populate all_complete_mask in mt7927_irq_map. MT7927 maps RX_DONE_DATA to ENA4 and RX_DONE_WM to ENA6, differing from MT7925. This is a preparation patch before enabling MT7928 PCIe support. Signed-off-by: Emery Hsin Link: https://patch.msgid.link/20260612075042.2577193-4-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/pci.c | 8 +++++--- drivers/net/wireless/mediatek/mt76/mt7925/regs.h | 7 +++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c index 95a0bd615167..8a4fb53c718f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c @@ -319,11 +319,13 @@ static const struct mt792x_irq_map mt7927_irq_map = { .mcu_complete_mask = MT_INT_TX_DONE_MCU, }, .rx = { - .data_complete_mask = MT7927_RX_DONE_INT_ENA4, - .wm_complete_mask = MT7927_RX_DONE_INT_ENA6, - .wm2_complete_mask = MT7927_RX_DONE_INT_ENA7, + .all_complete_mask = MT7927_INT_RX_DONE_ALL, + .data_complete_mask = MT7927_INT_RX_DONE_DATA, + .wm_complete_mask = MT7927_INT_RX_DONE_WM, + .wm2_complete_mask = MT7927_INT_RX_DONE_WM2, }, }; + static int mt7925_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regs.h b/drivers/net/wireless/mediatek/mt76/mt7925/regs.h index aed90cc82858..bb5969689337 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/regs.h @@ -51,6 +51,13 @@ MT7925_INT_RX_DONE_WM | \ MT_INT_RX_DONE_WM2) +#define MT7927_INT_RX_DONE_DATA MT7927_RX_DONE_INT_ENA4 +#define MT7927_INT_RX_DONE_WM MT7927_RX_DONE_INT_ENA6 +#define MT7927_INT_RX_DONE_WM2 MT7927_RX_DONE_INT_ENA7 +#define MT7927_INT_RX_DONE_ALL (MT7927_INT_RX_DONE_DATA | \ + MT7927_INT_RX_DONE_WM | \ + MT7927_INT_RX_DONE_WM2) + #define MT_INT_TX_DONE_MCU_WM (HOST_TX_DONE_INT_ENA15 | \ HOST_TX_DONE_INT_ENA17) From 9ad48ff235162ddda1cfc7d65aecf3010678bfbb Mon Sep 17 00:00:00 2001 From: Emery Hsin Date: Fri, 12 Jun 2026 07:50:42 +0000 Subject: [PATCH 0697/1433] wifi: mt76: mt792x: add per-chip PCIe register struct Add a mt792x_pcie_reg struct and a pcie_reg pointer in mt792x_dev, so that each chip can supply its own PCIe register offsets. Users are converted in the following patches. This is a preparation patch before enabling MT7928 PCIe support. Signed-off-by: Emery Hsin Link: https://patch.msgid.link/20260612075042.2577193-5-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt792x.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index 83c729f8bb76..eacee63e13f3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -238,6 +238,11 @@ struct mt792x_hif_ops { int (*fw_own)(struct mt792x_dev *dev); }; +struct mt792x_pcie_reg { + u32 imask; + u32 pm; +}; + struct mt792x_dev { union { /* must be first */ struct mt76_dev mt76; @@ -273,6 +278,7 @@ struct mt792x_dev { struct mt76_connac_coredump coredump; const struct mt792x_hif_ops *hif_ops; const struct mt792x_irq_map *irq_map; + const struct mt792x_pcie_reg *pcie_reg; struct work_struct ipv6_ns_work; struct delayed_work mlo_pm_work; From 7e8c44d06a536aabff972766d29cf58d14742189 Mon Sep 17 00:00:00 2001 From: Emery Hsin Date: Fri, 12 Jun 2026 15:51:32 +0800 Subject: [PATCH 0698/1433] wifi: mt76: connac2: add per-chip PCIe register definitions Add MT_PCIE_MAC_{INT_ENABLE,PM} definitions to mt7921/regs.h and wire up mt7921_pcie_reg in mt7921_pci_probe() to provide connac2 series chips with their own PCIe register definitions. This is a preparation patch before enabling MT7928 PCIe support. Signed-off-by: Xiong Signed-off-by: Emery Hsin Link: https://patch.msgid.link/20260612075136.2577553-1-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/pci.c | 5 +++++ drivers/net/wireless/mediatek/mt76/mt7921/regs.h | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c index 764de48c87e4..2f51f653975f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c @@ -286,6 +286,10 @@ static int mt7921_pci_probe(struct pci_dev *pdev, .drv_own = mt792xe_mcu_drv_pmctrl, .fw_own = mt792xe_mcu_fw_pmctrl, }; + static const struct mt792x_pcie_reg mt7921_pcie_reg = { + .imask = MT_PCIE_MAC_INT_ENABLE, + .pm = MT_PCIE_MAC_PM, + }; static const struct mt792x_irq_map irq_map = { .host_irq_enable = MT_WFDMA0_HOST_INT_ENA, .tx = { @@ -355,6 +359,7 @@ static int mt7921_pci_probe(struct pci_dev *pdev, dev->fw_features = features; dev->hif_ops = &mt7921_pcie_ops; + dev->pcie_reg = &mt7921_pcie_reg; dev->irq_map = &irq_map; mt76_mmio_init(&dev->mt76, regs); diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regs.h b/drivers/net/wireless/mediatek/mt76/mt7921/regs.h index 4d9eaf1e0692..75b4e81c7224 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7921/regs.h @@ -78,4 +78,9 @@ #define MT_WTBL_UPDATE_WLAN_IDX GENMASK(9, 0) #define MT_WTBL_UPDATE_ADM_COUNT_CLEAR BIT(12) +#define MT_PCIE_MAC_BASE 0x10000 +#define MT_PCIE_MAC(ofs) (MT_PCIE_MAC_BASE + (ofs)) +#define MT_PCIE_MAC_INT_ENABLE MT_PCIE_MAC(0x188) +#define MT_PCIE_MAC_PM MT_PCIE_MAC(0x194) + #endif From 0d0205dbf33599b0762f1813de337f8f54d7cede Mon Sep 17 00:00:00 2001 From: Emery Hsin Date: Fri, 12 Jun 2026 15:51:33 +0800 Subject: [PATCH 0699/1433] wifi: mt76: mt7925: add per-chip PCIe register definitions Add MT7925_PCIE_MAC_{INT_ENABLE,PM} macros and mt7925_pcie_reg struct. Update all PCIe register accesses in pci.c, pci_mac.c, and pci_mcu.c to use dev->pcie_reg->{imask,pm}. This is a preparation patch before enabling MT7928 PCIe support. Signed-off-by: Xiong Signed-off-by: Emery Hsin Link: https://patch.msgid.link/20260612075136.2577553-2-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/pci.c | 15 ++++++++++----- .../net/wireless/mediatek/mt76/mt7925/pci_mac.c | 4 ++-- .../net/wireless/mediatek/mt76/mt7925/pci_mcu.c | 2 +- drivers/net/wireless/mediatek/mt76/mt7925/regs.h | 5 +++++ 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c index 8a4fb53c718f..61349c260b12 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c @@ -298,6 +298,11 @@ static int mt7925_dma_init(struct mt792x_dev *dev) return mt792x_dma_enable(dev); } +static const struct mt792x_pcie_reg mt7925_pcie_reg = { + .imask = MT7925_PCIE_MAC_INT_ENABLE, + .pm = MT7925_PCIE_MAC_PM, +}; + static const struct mt792x_irq_map mt7925_irq_map = { .host_irq_enable = MT_WFDMA0_HOST_INT_ENA, .tx = { @@ -413,6 +418,8 @@ static int mt7925_pci_probe(struct pci_dev *pdev, dev->fw_features = features; dev->hif_ops = &mt7925_pcie_ops; dev->irq_map = is_mt7927_hw ? &mt7927_irq_map : &mt7925_irq_map; + dev->pcie_reg = &mt7925_pcie_reg; + mt76_mmio_init(&dev->mt76, pcim_iomap_table(pdev)[0]); tasklet_init(&mdev->irq_tasklet, mt792x_irq_tasklet, (unsigned long)dev); @@ -462,8 +469,7 @@ static int mt7925_pci_probe(struct pci_dev *pdev, goto err_free_dev; mt76_wr(dev, dev->irq_map->host_irq_enable, 0); - - mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0xff); + mt76_wr(dev, dev->pcie_reg->imask, 0xff); ret = devm_request_irq(mdev->dev, pdev->irq, mt792x_irq_handler, IRQF_SHARED, KBUILD_MODNAME, dev); @@ -564,8 +570,7 @@ static int mt7925_pci_suspend(struct device *device) /* disable interrupt */ mt76_wr(dev, dev->irq_map->host_irq_enable, 0); - - mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0x0); + mt76_wr(dev, dev->pcie_reg->imask, 0x0); synchronize_irq(pdev->irq); tasklet_kill(&mdev->irq_tasklet); @@ -615,7 +620,7 @@ static int _mt7925_pci_resume(struct device *device, bool restore) mt792x_wpdma_reinit_cond(dev); /* enable interrupt */ - mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0xff); + mt76_wr(dev, dev->pcie_reg->imask, 0xff); mt76_connac_irq_enable(&dev->mt76, dev->irq_map->tx.all_complete_mask | dev->irq_map->rx.all_complete_mask | diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c index d288739e1307..8477d21abc66 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c @@ -78,7 +78,7 @@ int mt7925e_mac_reset(struct mt792x_dev *dev) mt76_connac_free_pending_tx_skbs(&dev->pm, NULL); mt76_wr(dev, dev->irq_map->host_irq_enable, 0); - mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0x0); + mt76_wr(dev, dev->pcie_reg->imask, 0x0); set_bit(MT76_RESET, &dev->mphy.state); set_bit(MT76_MCU_RESET, &dev->mphy.state); @@ -121,7 +121,7 @@ int mt7925e_mac_reset(struct mt792x_dev *dev) dev->irq_map->tx.all_complete_mask | dev->irq_map->rx.all_complete_mask | MT_INT_MCU_CMD); - mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0xff); + mt76_wr(dev, dev->pcie_reg->imask, 0xff); err = mt792xe_mcu_fw_pmctrl(dev); if (err) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mcu.c index 6cceff88c656..72707eddc3db 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mcu.c @@ -43,7 +43,7 @@ int mt7925e_mcu_init(struct mt792x_dev *dev) if (err) return err; - mt76_rmw_field(dev, MT_PCIE_MAC_PM, MT_PCIE_MAC_PM_L0S_DIS, 1); + mt76_rmw_field(dev, dev->pcie_reg->pm, MT_PCIE_MAC_PM_L0S_DIS, 1); err = mt7925_run_firmware(dev); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regs.h b/drivers/net/wireless/mediatek/mt76/mt7925/regs.h index bb5969689337..85adde2ad597 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/regs.h @@ -97,4 +97,9 @@ #define MT_WTBL_UPDATE_WLAN_IDX GENMASK(11, 0) #define MT_WTBL_UPDATE_ADM_COUNT_CLEAR BIT(14) +#define MT7925_PCIE_MAC_BASE 0x10000 +#define MT7925_PCIE_MAC(ofs) (MT7925_PCIE_MAC_BASE + (ofs)) +#define MT7925_PCIE_MAC_INT_ENABLE MT7925_PCIE_MAC(0x188) +#define MT7925_PCIE_MAC_PM MT7925_PCIE_MAC(0x194) + #endif From d63b19ece3e4bbc4ee2f6d800eb8d6c1ca171781 Mon Sep 17 00:00:00 2001 From: Emery Hsin Date: Fri, 12 Jun 2026 07:50:42 +0000 Subject: [PATCH 0700/1433] wifi: mt76: mt792x: replace shared PCIe MAC macros with per-chip struct Update mt792x_wpdma_reinit_cond() to use dev->pcie_reg and remove the now unused MT_PCIE_MAC_INT_ENABLE and MT_PCIE_MAC_PM macros from mt792x_regs.h. This is a preparation patch before enabling MT7928 PCIe support. Signed-off-by: Emery Hsin Link: https://patch.msgid.link/20260612075042.2577193-5-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt792x_dma.c | 6 ++++-- drivers/net/wireless/mediatek/mt76/mt792x_regs.h | 4 ---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c index 946ff6d58954..2947504ef20b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c @@ -345,7 +345,8 @@ int mt792x_wpdma_reinit_cond(struct mt792x_dev *dev) if (mt792x_dma_need_reinit(dev)) { /* disable interrutpts */ mt76_wr(dev, dev->irq_map->host_irq_enable, 0); - mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0x0); + if (dev->pcie_reg) + mt76_wr(dev, dev->pcie_reg->imask, 0x0); err = mt792x_wpdma_reset(dev, false); if (err) { @@ -354,7 +355,8 @@ int mt792x_wpdma_reinit_cond(struct mt792x_dev *dev) } /* enable interrutpts */ - mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0xff); + if (dev->pcie_reg) + mt76_wr(dev, dev->pcie_reg->imask, 0xff); pm->stats.lp_wake++; } diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_regs.h b/drivers/net/wireless/mediatek/mt76/mt792x_regs.h index 4cd5b33b640e..6d174b158915 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x_regs.h @@ -415,10 +415,6 @@ #define MT_HW_EMI_CTL 0x18011100 #define MT_HW_EMI_CTL_SLPPROT_EN BIT(1) -#define MT_PCIE_MAC_BASE 0x10000 -#define MT_PCIE_MAC(ofs) (MT_PCIE_MAC_BASE + (ofs)) -#define MT_PCIE_MAC_INT_ENABLE MT_PCIE_MAC(0x188) -#define MT_PCIE_MAC_PM MT_PCIE_MAC(0x194) #define MT_PCIE_MAC_PM_L0S_DIS BIT(8) #define MT_DMA_SHDL(ofs) (0x7c026000 + (ofs)) From 50a16805a95e3101740b3be218b3d6c209e08b00 Mon Sep 17 00:00:00 2001 From: Emery Hsin Date: Fri, 12 Jun 2026 15:51:34 +0800 Subject: [PATCH 0701/1433] wifi: mt76: mt792x: rename WFDMA DMASHDL enable bit to follow the convention Rename MT_WFDMA0_CSR_TX_DMASHDL_ENABLE to MT_WFDMA0_GLO_CFG_EXT0_CSR_TX_DMASHDL_EN to follow the register naming convention (parent register name as prefix). This is a preparation patch before enabling MT7928 PCIe support. Signed-off-by: Emery Hsin Link: https://patch.msgid.link/20260612075136.2577553-3-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt792x_dma.c | 2 +- drivers/net/wireless/mediatek/mt76/mt792x_regs.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c index 2947504ef20b..b090ba9cd676 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c @@ -386,7 +386,7 @@ int mt792x_dma_disable(struct mt792x_dev *dev, bool force) /* disable dmashdl */ mt76_clear(dev, MT_WFDMA0_GLO_CFG_EXT0, - MT_WFDMA0_CSR_TX_DMASHDL_ENABLE); + MT_WFDMA0_GLO_CFG_EXT0_CSR_TX_DMASHDL_EN); mt76_set(dev, MT_DMASHDL_SW_CONTROL, MT_DMASHDL_DMASHDL_BYPASS); if (force) { diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_regs.h b/drivers/net/wireless/mediatek/mt76/mt792x_regs.h index 6d174b158915..0e297fd9468a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x_regs.h @@ -335,7 +335,7 @@ #define MT_WFDMA0_INT_RX_PRI MT_WFDMA0(0x298) #define MT_WFDMA0_INT_TX_PRI MT_WFDMA0(0x29c) #define MT_WFDMA0_GLO_CFG_EXT0 MT_WFDMA0(0x2b0) -#define MT_WFDMA0_CSR_TX_DMASHDL_ENABLE BIT(6) +#define MT_WFDMA0_GLO_CFG_EXT0_CSR_TX_DMASHDL_EN BIT(6) #define MT_WFDMA0_PRI_DLY_INT_CFG0 MT_WFDMA0(0x2f0) #define MT_WFDMA0_TX_RING0_EXT_CTRL MT_WFDMA0(0x600) From 3422e61141e79d5edd51f27b12d146e631124960 Mon Sep 17 00:00:00 2001 From: Emery Hsin Date: Fri, 12 Jun 2026 15:51:35 +0800 Subject: [PATCH 0702/1433] wifi: mt76: mt7925: rename WTBL registers to chip-specific format Rename MT_WTBLON_TOP_WDUCR and MT_WTBL_UPDATE to MT7925-prefixed versions since MT7928 uses different WTBL register offsets. This is a preparation patch before enabling MT7928 PCIe support. Signed-off-by: Emery Hsin Link: https://patch.msgid.link/20260612075136.2577553-4-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 4 ++-- drivers/net/wireless/mediatek/mt76/mt7925/mac.h | 2 +- drivers/net/wireless/mediatek/mt76/mt7925/regs.h | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index 6b0cd1996ecb..778a0c68d98e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -12,10 +12,10 @@ bool mt7925_mac_wtbl_update(struct mt792x_dev *dev, int idx, u32 mask) { - mt76_rmw(dev, MT_WTBL_UPDATE, MT_WTBL_UPDATE_WLAN_IDX, + mt76_rmw(dev, MT7925_WTBL_UPDATE, MT_WTBL_UPDATE_WLAN_IDX, FIELD_PREP(MT_WTBL_UPDATE_WLAN_IDX, idx) | mask); - return mt76_poll(dev, MT_WTBL_UPDATE, MT_WTBL_UPDATE_BUSY, + return mt76_poll(dev, MT7925_WTBL_UPDATE, MT_WTBL_UPDATE_BUSY, 0, 5000); } diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.h b/drivers/net/wireless/mediatek/mt76/mt7925/mac.h index 83ea9021daea..67148c87de76 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.h @@ -14,7 +14,7 @@ static inline u32 mt7925_mac_wtbl_lmac_addr(struct mt792x_dev *dev, u16 wcid, u8 dw) { - mt76_wr(dev, MT_WTBLON_TOP_WDUCR, + mt76_wr(dev, MT7925_WTBLON_TOP_WDUCR, FIELD_PREP(MT_WTBLON_TOP_WDUCR_GROUP, (wcid >> 7))); return MT_WTBL_LMAC_OFFS(wcid, dw); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regs.h b/drivers/net/wireless/mediatek/mt76/mt7925/regs.h index 85adde2ad597..0bcfd1cf0338 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/regs.h @@ -90,10 +90,10 @@ #define MT_WFSYS_SW_RST_B 0x7c000140 -#define MT_WTBLON_TOP_WDUCR MT_WTBLON_TOP(0x370) +#define MT7925_WTBLON_TOP_WDUCR MT_WTBLON_TOP(0x370) #define MT_WTBLON_TOP_WDUCR_GROUP GENMASK(4, 0) -#define MT_WTBL_UPDATE MT_WTBLON_TOP(0x380) +#define MT7925_WTBL_UPDATE MT_WTBLON_TOP(0x380) #define MT_WTBL_UPDATE_WLAN_IDX GENMASK(11, 0) #define MT_WTBL_UPDATE_ADM_COUNT_CLEAR BIT(14) From 78ff45f849a7c24b96adbb38c901d84491e650e9 Mon Sep 17 00:00:00 2001 From: Emery Hsin Date: Fri, 12 Jun 2026 15:51:36 +0800 Subject: [PATCH 0703/1433] wifi: mt76: mt792x: add tx_done ring to common DMA queue allocation Add a tx_done field to mt792x_dma_layout and extend mt792x_dma_alloc_queues() to allocate the MT_RXQ_MCU_WA queue when tx_done.ring_base is configured. This is a preparation patch before enabling MT7928 PCIe support. Signed-off-by: Emery Hsin Link: https://patch.msgid.link/20260612075136.2577553-5-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt792x.h | 1 + drivers/net/wireless/mediatek/mt76/mt792x_dma.c | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index eacee63e13f3..b8dc7ce38c87 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -217,6 +217,7 @@ struct mt792x_dma_layout { struct mt792x_dma_ring tx_data0; struct mt792x_dma_ring tx_mcu; struct mt792x_dma_ring tx_fwdl; + struct mt792x_dma_ring tx_done; struct mt792x_dma_ring rx_data; struct mt792x_dma_ring rx_mcu; }; diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c index b090ba9cd676..4d4c62bb0a77 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c @@ -122,6 +122,17 @@ int mt792x_dma_alloc_queues(struct mt792x_dev *dev, if (ret) return ret; + /* tx done */ + if (layout->tx_done.ring_base) { + ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_MCU_WA], + layout->tx_done.qid, + layout->tx_done.n_desc, + MT_RX_BUF_SIZE, + layout->tx_done.ring_base); + if (ret) + return ret; + } + /* rx event */ ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_MCU], layout->rx_mcu.qid, From e058739cbf846160678dc878aadc2bb14a2af83d Mon Sep 17 00:00:00 2001 From: Emery Hsin Date: Fri, 12 Jun 2026 15:53:09 +0800 Subject: [PATCH 0704/1433] wifi: mt76: connac3: update basic rate table starting index Change MT792x_BASIC_RATES_TBL index from 11 to 14 to match the latest connac3 firmware rate table layout. This is a preparation patch before enabling MT7928 PCIe support. Signed-off-by: Emery Hsin Link: https://patch.msgid.link/20260612075313.2578154-1-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt792x.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index b8dc7ce38c87..7d014de030c4 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -33,7 +33,7 @@ #define MT792x_CHIP_CAP_MLO_EML_EN BIT(9) /* NOTE: used to map mt76_rates. idx may change if firmware expands table */ -#define MT792x_BASIC_RATES_TBL 11 +#define MT792x_BASIC_RATES_TBL 14 #define MT792x_WATCHDOG_TIME (HZ / 4) From e34e157e6e7cd048f764f2cc752dd4068031ddb1 Mon Sep 17 00:00:00 2001 From: Emery Hsin Date: Fri, 12 Jun 2026 15:53:10 +0800 Subject: [PATCH 0705/1433] wifi: mt76: mt7925: fix MMIO dynamic remap window size Fix the 0x7c500000 remap entry window size from 0x2000000 (32MB) to 0x200000 (2MB) to match the actual addressable range. This is a preparation patch before enabling MT7928 PCIe support. Signed-off-by: Emery Hsin Link: https://patch.msgid.link/20260612075313.2578154-2-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c index 61349c260b12..e181cd0b6403 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c @@ -161,7 +161,7 @@ static u32 __mt7925_reg_addr(struct mt792x_dev *dev, u32 addr) { 0x7c060000, 0x0e0000, 0x0010000 }, /* CONN_INFRA, conn_host_csr_top */ { 0x7c000000, 0x0f0000, 0x0010000 }, /* CONN_INFRA */ { 0x70020000, 0x1f0000, 0x0010000 }, /* Reserved for CBTOP, can't switch */ - { 0x7c500000, 0x060000, 0x2000000 }, /* remap */ + { 0x7c500000, 0x060000, 0x200000 }, /* remap */ { 0x0, 0x0, 0x0 } /* End */ }; int i; From 4f8ded4d5f0e1b1707e0e439666e314c92df9d8f Mon Sep 17 00:00:00 2001 From: Emery Hsin Date: Fri, 12 Jun 2026 15:53:11 +0800 Subject: [PATCH 0706/1433] wifi: mt76: mt7925: add MT7928 FWDL support Add CBMCU and PHY RAM firmware download flow for MT7928. The CBMCU firmware is loaded in sections before the main WM firmware. Register MT7928 firmware file names and is_mt7928() chip check. Signed-off-by: FC Wei Signed-off-by: Xiong Signed-off-by: Emery Hsin Link: https://patch.msgid.link/20260612075313.2578154-3-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt76_connac.h | 7 +- .../wireless/mediatek/mt76/mt76_connac_mcu.c | 251 +++++++++++++++++- .../wireless/mediatek/mt76/mt76_connac_mcu.h | 40 +++ .../net/wireless/mediatek/mt76/mt7925/mcu.c | 15 +- drivers/net/wireless/mediatek/mt76/mt792x.h | 29 ++ .../net/wireless/mediatek/mt76/mt792x_core.c | 15 ++ 6 files changed, 353 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac.h index d15cce296c1e..019d0275dc5c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac.h @@ -174,7 +174,7 @@ extern const struct wiphy_wowlan_support mt76_connac_wowlan_support; static inline bool is_connac3(struct mt76_dev *dev) { - return mt76_chip(dev) == 0x7925 || mt76_chip(dev) == 0x7927; + return mt76_chip(dev) == 0x7925 || mt76_chip(dev) == 0x7927 || mt76_chip(dev) == 0x7928; } static inline bool is_mt7925(struct mt76_dev *dev) @@ -187,6 +187,11 @@ static inline bool is_mt7927(struct mt76_dev *dev) return mt76_chip(dev) == 0x7927; } +static inline bool is_mt7928(struct mt76_dev *dev) +{ + return mt76_chip(dev) == 0x7928; +} + static inline bool is_320mhz_supported(struct mt76_dev *dev) { return mt76_chip(dev) == 0x7927; diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c index 58b0b15e4fd6..2cc5a5b67f30 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c @@ -67,7 +67,8 @@ int mt76_connac_mcu_init_download(struct mt76_dev *dev, u32 addr, u32 len, if ((!is_connac_v1(dev) && addr == MCU_PATCH_ADDRESS) || (is_connac2(dev) && addr == 0x900000) || - (is_connac3(dev) && (addr == 0x900000 || addr == 0xe0002800)) || + ((is_mt7925(dev) || is_mt7927(dev)) && (addr == 0x900000 || addr == 0xe0002800)) || + (is_mt7928(dev) && (addr == 0x900000 || addr == 0xe0002000)) || (is_mt799x(dev) && addr == 0x900000)) cmd = MCU_CMD(PATCH_START_REQ); else @@ -77,6 +78,48 @@ int mt76_connac_mcu_init_download(struct mt76_dev *dev, u32 addr, u32 len, } EXPORT_SYMBOL_GPL(mt76_connac_mcu_init_download); +int mt76_connac_cb_mcu_patch_sem_ctrl(struct mt76_dev *dev, bool get) +{ + u8 op = get ? PATCH_SEM_GET : PATCH_SEM_RELEASE; + struct { + u8 op; + u8 reserved[3]; + } req = { + .op = op, + }; + + return mt76_mcu_send_msg(dev, MCU_CMD(CB_PATCH_SEM_CONTROL), + &req, sizeof(req), true); +} +EXPORT_SYMBOL_GPL(mt76_connac_cb_mcu_patch_sem_ctrl); + +int mt76_connac_cb_mcu_start_patch(struct mt76_dev *dev) +{ + struct { + __le32 reserved; + } req = { + .reserved = 0, + }; + + return mt76_mcu_send_msg(dev, MCU_CMD(CB_PATCH_FINISH_REQ), + &req, sizeof(req), true); +} +EXPORT_SYMBOL_GPL(mt76_connac_cb_mcu_start_patch); + +int mt76_connac_cb_mcu_init_download(struct mt76_dev *dev, u32 len) +{ + struct { + __le32 addr; + __le32 len; + } req = { + .addr = 0, /* addr is meaningless for cbmcu fwdl */ + .len = cpu_to_le32(len), + }; + + return mt76_mcu_send_msg(dev, MCU_CMD(CB_PATCH_START_REQ), &req, sizeof(req), true); +} +EXPORT_SYMBOL_GPL(mt76_connac_cb_mcu_init_download); + int mt76_connac_mcu_set_channel_domain(struct mt76_phy *phy) { int len, i, n_max_channels, n_2ch = 0, n_5ch = 0, n_6ch = 0; @@ -3044,6 +3087,57 @@ mt76_connac_mcu_send_ram_firmware(struct mt76_dev *dev, return mt76_connac_mcu_start_firmware(dev, override, option); } +static int +mt76_connac_mcu_send_phy_ram_firmware(struct mt76_dev *dev, + const struct mt76_connac2_fw_trailer *hdr, + const u8 *data) +{ + int i, offset = 0, max_len = mt76_is_sdio(dev) ? 2048 : 4096; + u32 override = 0, option = 0; + + for (i = 0; i < hdr->n_region; i++) { + const struct mt76_connac2_fw_region *region; + u32 len, addr, mode; + int err; + + region = (const void *)((const u8 *)hdr - + (hdr->n_region - i) * sizeof(*region)); + mode = mt76_connac_mcu_gen_dl_mode(dev, region->feature_set, + true); + len = le32_to_cpu(region->len); + addr = le32_to_cpu(region->addr); + + if (region->feature_set & FW_FEATURE_NON_DL) + goto next; + + if (region->feature_set & FW_FEATURE_OVERRIDE_ADDR) + override = addr; + + err = mt76_connac_mcu_init_download(dev, addr, len, mode); + if (err) { + dev_err(dev->dev, + "The request to dowload PHY firmware failed.\n"); + return err; + } + + err = __mt76_mcu_send_firmware(dev, MCU_CMD(FW_SCATTER), + data + offset, len, max_len); + if (err) { + dev_err(dev->dev, "Failed to send PHY firmware.\n"); + return err; + } + +next: + offset += len; + } + + if (override) + option |= FW_START_OVERRIDE; + option |= FW_START_WORKING_PDA_DSP; + + return mt76_connac_mcu_start_firmware(dev, override, option); +} + int mt76_connac2_load_ram(struct mt76_dev *dev, const char *fw_wm, const char *fw_wa) { @@ -3111,6 +3205,39 @@ int mt76_connac2_load_ram(struct mt76_dev *dev, const char *fw_wm, } EXPORT_SYMBOL_GPL(mt76_connac2_load_ram); +int mt76_connac3_load_phy_ram(struct mt76_dev *dev, const char *fw_name) +{ + const struct mt76_connac2_fw_trailer *hdr; + const struct firmware *fw; + int ret; + + ret = request_firmware(&fw, fw_name, dev->dev); + if (ret) + return ret; + + if (!fw || !fw->data || fw->size < sizeof(*hdr)) { + dev_err(dev->dev, "Invalid PHY firmware\n"); + ret = -EINVAL; + goto out; + } + + hdr = (const void *)(fw->data + fw->size - sizeof(*hdr)); + dev_info(dev->dev, "PHY Firmware Version: %.10s, Build Time: %.15s\n", + hdr->fw_ver, hdr->build_date); + + ret = mt76_connac_mcu_send_phy_ram_firmware(dev, hdr, fw->data); + if (ret) { + dev_err(dev->dev, "Failed to start PHY firmware\n"); + goto out; + } + +out: + release_firmware(fw); + + return ret; +} +EXPORT_SYMBOL_GPL(mt76_connac3_load_phy_ram); + static u32 mt76_connac2_get_data_mode(struct mt76_dev *dev, u32 info) { u32 mode = DL_MODE_NEED_RSP; @@ -3224,6 +3351,128 @@ int mt76_connac2_load_patch(struct mt76_dev *dev, const char *fw_name) } EXPORT_SYMBOL_GPL(mt76_connac2_load_patch); +int mt76_connac3_load_cb_patch(struct mt76_dev *dev, const char *fw_name) +{ + const struct mt76_connac3_multi_header_v2_sec_raw_format *sect = NULL; + const struct mt76_connac3_multi_header_v2_raw_format *hdr = NULL; + int i, ret, sem, max_len = mt76_is_sdio(dev) ? 2048 : 4096; + const struct firmware *fw = NULL; + const u8 *dl; + u32 len; + + sem = mt76_connac_cb_mcu_patch_sem_ctrl(dev, true); + switch (sem) { + case CB_PATCH_IS_DL: + return 0; + case CB_PATCH_GET_SEM_NEED_PATCH: + break; + default: + dev_err(dev->dev, "Failed to get cb patch semaphore %u\n", sem); + return -EAGAIN; + } + + ret = request_firmware(&fw, fw_name, dev->dev); + if (ret) + goto out; + + if (!fw || !fw->data || fw->size < sizeof(*hdr)) { + dev_err(dev->dev, "Invalid firmware\n"); + ret = -EINVAL; + goto out; + } + + hdr = (const void *)fw->data; + dev_info(dev->dev, "HW/SW Version: 0x%x, Pack Time: %.21s\n", + le32_to_cpu(hdr->patch_ver), hdr->pack_time); + + if (le32_to_cpu(hdr->sec_num) < 2) { + dev_err(dev->dev, "Invalid section numbers %u\n", + le32_to_cpu(hdr->sec_num)); + ret = -EINVAL; + goto out; + } + + sect = &hdr->sects[0]; + /* check the fw bin basic info */ + if (((le32_to_cpu(sect->type) & SEC_TYPE_SUBSYS_MASK) + != SEC_TYPE_SUBSYS_CBMCU) || + ((le32_to_cpu(sect->type) & SEC_TYPE_SUBSYS_SEC_MASK) + != SEC_TYPE_SUBSYS_SEC_IMG_SIGN)) { + dev_err(dev->dev, "Invalid CBMCU firmware\n"); + ret = -EINVAL; + goto out; + } + + /* download global desc + sect map + cert section */ + len = sizeof(struct mt76_connac3_multi_header_v2_raw_format) - + offsetof(struct mt76_connac3_multi_header_v2_raw_format, + global_desc_head) + + sizeof(struct mt76_connac3_multi_header_v2_sec_raw_format) * + le32_to_cpu(hdr->sec_num) + + le32_to_cpu(hdr->sects[0].size); + dl = &hdr->global_desc_head[0]; + + ret = mt76_connac_cb_mcu_init_download(dev, len); + if (ret) { + dev_err(dev->dev, "Download cb cert section request failed\n"); + goto out; + } + + ret = __mt76_mcu_send_firmware(dev, MCU_CMD(FW_SCATTER), + dl, len, max_len); + if (ret) { + dev_err(dev->dev, "Failed to send cb patch cert section\n"); + goto out; + } + + ret = mt76_connac_cb_mcu_start_patch(dev); + if (ret) { + dev_err(dev->dev, "CBMCU cert check fail\n"); + goto out; + } + + /* download cbmcu idlm */ + for (i = 1; i < le32_to_cpu(hdr->sec_num); i++) { + dl = (uint8_t *)hdr + le32_to_cpu(hdr->sects[i].offset); + len = le32_to_cpu(hdr->sects[i].size); + + ret = mt76_connac_cb_mcu_init_download(dev, len); + if (ret) { + dev_err(dev->dev, + "Download cb section %u request failed\n", i); + goto out; + } + + ret = __mt76_mcu_send_firmware(dev, MCU_CMD(FW_SCATTER), + dl, len, max_len); + if (ret) { + dev_err(dev->dev, + "Failed to send cb patch section %u\n", i); + goto out; + } + } + + ret = mt76_connac_cb_mcu_start_patch(dev); + if (ret) + dev_err(dev->dev, "Failed to start cb patch\n"); + +out: + sem = mt76_connac_cb_mcu_patch_sem_ctrl(dev, false); + switch (sem) { + case CB_PATCH_REL_SEM_SUCCESS: + break; + default: + ret = -EAGAIN; + dev_err(dev->dev, "Failed to release cb patch semaphore\n"); + break; + } + + release_firmware(fw); + + return ret; +} +EXPORT_SYMBOL_GPL(mt76_connac3_load_cb_patch); + int mt76_connac2_mcu_fill_message(struct mt76_dev *dev, struct sk_buff *skb, int cmd, int *wait_seq) { diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h index 78f633ad81a0..6f554d78da39 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h @@ -35,6 +35,12 @@ #define PATCH_SEC_ENC_SCRAMBLE_INFO_MASK GENMASK(15, 0) #define PATCH_SEC_ENC_AES_KEY_MASK GENMASK(7, 0) +#define SEC_TYPE_SUBSYS_MASK GENMASK(23, 16) +#define SEC_TYPE_SUBSYS_CBMCU BIT(16) + +#define SEC_TYPE_SUBSYS_SEC_MASK GENMASK(15, 0) +#define SEC_TYPE_SUBSYS_SEC_IMG_SIGN 0x05 + enum { FW_TYPE_DEFAULT = 0, FW_TYPE_CLC = 2, @@ -193,6 +199,25 @@ struct mt76_connac2_fw_region { u8 rsv1[14]; } __packed; +struct mt76_connac3_multi_header_v2_sec_raw_format { + __le32 type; + __le32 offset; + __le32 size; + u8 spec[52]; +} __packed; + +struct mt76_connac3_multi_header_v2_raw_format { + u8 pack_time[20]; + u8 chip_id_eco_ver[8]; + __le32 patch_ver; + u8 global_desc_head[4]; + __le32 global_subsys; + u8 rsv2[4]; + __le32 sec_num; + u8 rsv3[48]; + struct mt76_connac3_multi_header_v2_sec_raw_format sects[]; +} __packed; + struct tlv { __le16 tag; __le16 len; @@ -1104,6 +1129,13 @@ enum { PATCH_REL_SEM_SUCCESS }; +enum { + CB_PATCH_GET_SEM_NEED_PATCH, + CB_PATCH_IS_DL, + CB_PATCH_NO_SEM_NEED_PATCH, + CB_PATCH_REL_SEM_SUCCESS +}; + enum { FW_STATE_INITIAL, FW_STATE_FW_DOWNLOAD, @@ -1332,6 +1364,9 @@ enum { MCU_CMD_PATCH_START_REQ = 0x05, MCU_CMD_PATCH_FINISH_REQ = 0x07, MCU_CMD_PATCH_SEM_CONTROL = 0x10, + MCU_CMD_CB_PATCH_SEM_CONTROL = 0x30, + MCU_CMD_CB_PATCH_START_REQ = 0x31, + MCU_CMD_CB_PATCH_FINISH_REQ = 0x32, MCU_CMD_WA_PARAM = 0xc4, MCU_CMD_EXT_CID = 0xed, MCU_CMD_FW_SCATTER = 0xee, @@ -2019,6 +2054,9 @@ int mt76_connac_mcu_init_download(struct mt76_dev *dev, u32 addr, u32 len, u32 mode); int mt76_connac_mcu_start_patch(struct mt76_dev *dev); int mt76_connac_mcu_patch_sem_ctrl(struct mt76_dev *dev, bool get); +int mt76_connac_cb_mcu_init_download(struct mt76_dev *dev, u32 len); +int mt76_connac_cb_mcu_start_patch(struct mt76_dev *dev); +int mt76_connac_cb_mcu_patch_sem_ctrl(struct mt76_dev *dev, bool get); int mt76_connac_mcu_start_firmware(struct mt76_dev *dev, u32 addr, u32 option); void mt76_connac_mcu_build_rnr_scan_param(struct mt76_dev *mdev, @@ -2102,7 +2140,9 @@ int mt76_connac_mcu_rdd_cmd(struct mt76_dev *dev, int cmd, u8 index, int mt76_connac_mcu_sta_wed_update(struct mt76_dev *dev, struct sk_buff *skb); int mt76_connac2_load_ram(struct mt76_dev *dev, const char *fw_wm, const char *fw_wa); +int mt76_connac3_load_phy_ram(struct mt76_dev *dev, const char *fw_name); int mt76_connac2_load_patch(struct mt76_dev *dev, const char *fw_name); +int mt76_connac3_load_cb_patch(struct mt76_dev *dev, const char *fw_name); int mt76_connac2_mcu_fill_message(struct mt76_dev *mdev, struct sk_buff *skb, int cmd, int *wait_seq); #endif /* __MT76_CONNAC_MCU_H */ diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index cb265a6fc7ad..5602544d3853 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -26,13 +26,24 @@ int mt7925_mcu_parse_response(struct mt76_dev *mdev, int cmd, } rxd = (struct mt7925_mcu_rxd *)skb->data; - if (seq != rxd->seq) - return -EAGAIN; + if (seq != rxd->seq) { + if (!is_mt7928(mdev)) + return -EAGAIN; + else if (is_mt7928(mdev) && + cmd != MCU_CMD(CB_PATCH_SEM_CONTROL) && + cmd != MCU_CMD(CB_PATCH_FINISH_REQ)) + return -EAGAIN; + } if (cmd == MCU_CMD(PATCH_SEM_CONTROL) || cmd == MCU_CMD(PATCH_FINISH_REQ)) { skb_pull(skb, sizeof(*rxd) - 4); ret = *skb->data; + } else if (is_mt7928(mdev) && + (cmd == MCU_CMD(CB_PATCH_SEM_CONTROL) || + cmd == MCU_CMD(CB_PATCH_FINISH_REQ))) { + skb_pull(skb, sizeof(*rxd) - 4); + ret = *skb->data; } else if (cmd == MCU_UNI_CMD(DEV_INFO_UPDATE) || cmd == MCU_UNI_CMD(BSS_INFO_UPDATE) || cmd == MCU_UNI_CMD(STA_REC_UPDATE) || diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index 7d014de030c4..8c7ecd3ce126 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -47,6 +47,7 @@ #define MT7922_FIRMWARE_WM "mediatek/WIFI_RAM_CODE_MT7922_1.bin" #define MT7925_FIRMWARE_WM "mediatek/mt7925/WIFI_RAM_CODE_MT7925_1_1.bin" #define MT7927_FIRMWARE_WM "mediatek/mt7927/WIFI_RAM_CODE_MT6639_2_1.bin" +#define MT7928_FIRMWARE_WM "mediatek/mt7928/WIFI_RAM_CODE_MT7935_1_1.bin" #define MT7902_ROM_PATCH "mediatek/WIFI_MT7902_patch_mcu_1_1_hdr.bin" #define MT7920_ROM_PATCH "mediatek/WIFI_MT7961_patch_mcu_1a_2_hdr.bin" @@ -54,6 +55,10 @@ #define MT7922_ROM_PATCH "mediatek/WIFI_MT7922_patch_mcu_1_1_hdr.bin" #define MT7925_ROM_PATCH "mediatek/mt7925/WIFI_MT7925_PATCH_MCU_1_1_hdr.bin" #define MT7927_ROM_PATCH "mediatek/mt7927/WIFI_MT6639_PATCH_MCU_2_1_hdr.bin" +#define MT7928_ROM_PATCH "mediatek/mt7928/WIFI_MT7935_PATCH_MCU_1_1_hdr.bin" + +#define MT7928_CB_ROM_PATCH "mediatek/mt7928/CBMCU_CODE_MT7935_1_1.bin" +#define MT7928_PHY_RAM "mediatek/mt7928/WIFI_MT7935_PHY_RAM_CODE_1_1.bin" #define MT792x_SDIO_HDR_TX_BYTES GENMASK(15, 0) #define MT792x_SDIO_HDR_PKT_TYPE GENMASK(17, 16) @@ -494,6 +499,8 @@ static inline char *mt792x_ram_name(struct mt792x_dev *dev) return MT7925_FIRMWARE_WM; case 0x7927: return MT7927_FIRMWARE_WM; + case 0x7928: + return MT7928_FIRMWARE_WM; default: return MT7921_FIRMWARE_WM; } @@ -512,11 +519,33 @@ static inline char *mt792x_patch_name(struct mt792x_dev *dev) return MT7925_ROM_PATCH; case 0x7927: return MT7927_ROM_PATCH; + case 0x7928: + return MT7928_ROM_PATCH; default: return MT7921_ROM_PATCH; } } +static inline char *mt792x_cb_patch_name(struct mt792x_dev *dev) +{ + switch (mt76_chip(&dev->mt76)) { + case 0x7928: + return MT7928_CB_ROM_PATCH; + default: + return NULL; + } +} + +static inline char *mt792x_phy_ram_name(struct mt792x_dev *dev) +{ + switch (mt76_chip(&dev->mt76)) { + case 0x7928: + return MT7928_PHY_RAM; + default: + return NULL; + } +} + int mt792x_load_firmware(struct mt792x_dev *dev); /* usb */ diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c index b50825eccdaf..9bd679b7e889 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c @@ -985,6 +985,15 @@ int mt792x_load_firmware(struct mt792x_dev *dev) dev_warn(dev->mt76.dev, "MCU is not ready for firmware download\n"); + if (is_mt7928(&dev->mt76)) { + dev_info(dev->mt76.dev, "Loading CB firmware patch: %s\n", + mt792x_cb_patch_name(dev)); + ret = mt76_connac3_load_cb_patch(&dev->mt76, mt792x_cb_patch_name(dev)); + if (ret) + return ret; + } + + dev_info(dev->mt76.dev, "Loading firmware patch: %s\n", mt792x_patch_name(dev)); ret = mt76_connac2_load_patch(&dev->mt76, mt792x_patch_name(dev)); if (ret) return ret; @@ -996,6 +1005,12 @@ int mt792x_load_firmware(struct mt792x_dev *dev) ret = __mt792x_mcu_drv_pmctrl(dev); } + if (is_mt7928(&dev->mt76)) { + ret = mt76_connac3_load_phy_ram(&dev->mt76, mt792x_phy_ram_name(dev)); + if (ret) + return ret; + } + ret = mt76_connac2_load_ram(&dev->mt76, mt792x_ram_name(dev), NULL); if (ret) return ret; From d92d0b0c06e3120a3786b5109d163307f90e97c5 Mon Sep 17 00:00:00 2001 From: Emery Hsin Date: Fri, 12 Jun 2026 15:53:12 +0800 Subject: [PATCH 0707/1433] wifi: mt76: mt7925: add MT7928 irq_map with chip-specific rx masks MT7928 uses different RX interrupt bit assignments (RX_DONE_DATA on ENA0, RX_DONE_WM on ENA3). Add MT7928-specific irq_map and select it at probe time based on PCI device ID. Signed-off-by: FC Wei Signed-off-by: Emery Hsin Link: https://patch.msgid.link/20260612075313.2578154-4-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/pci.c | 27 ++++++++++++++++--- .../net/wireless/mediatek/mt76/mt7925/regs.h | 6 +++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c index e181cd0b6403..6a65c630f85a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c @@ -331,6 +331,20 @@ static const struct mt792x_irq_map mt7927_irq_map = { }, }; +static const struct mt792x_irq_map mt7928_irq_map = { + .host_irq_enable = MT_WFDMA0_HOST_INT_ENA, + .tx = { + .all_complete_mask = MT_INT_TX_DONE_ALL, + .mcu_complete_mask = MT_INT_TX_DONE_MCU, + }, + .rx = { + .all_complete_mask = MT7928_INT_RX_DONE_ALL, + .data_complete_mask = MT7928_INT_RX_DONE_DATA, + .wm_complete_mask = MT7928_INT_RX_DONE_WM, + .wm2_complete_mask = MT_INT_RX_DONE_WM2, + }, +}; + static int mt7925_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { @@ -360,11 +374,11 @@ static int mt7925_pci_probe(struct pci_dev *pdev, .drv_own = mt792xe_mcu_drv_pmctrl, .fw_own = mt792xe_mcu_fw_pmctrl, }; - struct ieee80211_ops *ops; + bool is_mt7927_hw, is_mt7928_hw; struct mt76_bus_ops *bus_ops; + struct ieee80211_ops *ops; struct mt792x_dev *dev; struct mt76_dev *mdev; - bool is_mt7927_hw; u8 features; int ret; u16 cmd; @@ -394,6 +408,7 @@ static int mt7925_pci_probe(struct pci_dev *pdev, is_mt7927_hw = (pdev->device == 0x6639 || pdev->device == 0x7927 || pdev->device == 0x0738); + is_mt7928_hw = (pdev->device == 0x7928 || pdev->device == 0x7935); /* MT7927: ASPM L1 causes unreliable WFDMA register access */ if (mt7925_disable_aspm || is_mt7927_hw) @@ -417,9 +432,15 @@ static int mt7925_pci_probe(struct pci_dev *pdev, dev = container_of(mdev, struct mt792x_dev, mt76); dev->fw_features = features; dev->hif_ops = &mt7925_pcie_ops; - dev->irq_map = is_mt7927_hw ? &mt7927_irq_map : &mt7925_irq_map; dev->pcie_reg = &mt7925_pcie_reg; + if (is_mt7928_hw) + dev->irq_map = &mt7928_irq_map; + else if (is_mt7927_hw) + dev->irq_map = &mt7927_irq_map; + else + dev->irq_map = &mt7925_irq_map; + mt76_mmio_init(&dev->mt76, pcim_iomap_table(pdev)[0]); tasklet_init(&mdev->irq_tasklet, mt792x_irq_tasklet, (unsigned long)dev); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regs.h b/drivers/net/wireless/mediatek/mt76/mt7925/regs.h index 0bcfd1cf0338..855a53c0748a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/regs.h @@ -58,6 +58,12 @@ MT7927_INT_RX_DONE_WM | \ MT7927_INT_RX_DONE_WM2) +#define MT7928_INT_RX_DONE_DATA HOST_RX_DONE_INT_ENA0 +#define MT7928_INT_RX_DONE_WM HOST_RX_DONE_INT_ENA3 +#define MT7928_INT_RX_DONE_ALL (MT7928_INT_RX_DONE_DATA | \ + MT7928_INT_RX_DONE_WM | \ + MT_INT_RX_DONE_WM2) + #define MT_INT_TX_DONE_MCU_WM (HOST_TX_DONE_INT_ENA15 | \ HOST_TX_DONE_INT_ENA17) From a01a222ab4aa80ef201b9a2893134e7533d62348 Mon Sep 17 00:00:00 2001 From: Emery Hsin Date: Fri, 12 Jun 2026 15:53:13 +0800 Subject: [PATCH 0708/1433] wifi: mt76: mt7925: add MT7928 DMA configuration Add MT7928 DMA queue layout, DMASHDL configuration, prefetch ring setup, and WFDMA interrupt priority initialization. Select the MT7928-specific layout and GLO_CFG path in mt7925_dma_init(). Signed-off-by: FC Wei Signed-off-by: Xiong Signed-off-by: Emery Hsin Link: https://patch.msgid.link/20260612075313.2578154-5-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/mcu.h | 21 +++++ .../wireless/mediatek/mt76/mt7925/mt7925.h | 8 ++ .../net/wireless/mediatek/mt76/mt7925/pci.c | 91 ++++++++++++++++-- .../net/wireless/mediatek/mt76/mt7925/regs.h | 21 +++++ .../net/wireless/mediatek/mt76/mt792x_dma.c | 94 ++++++++++++++++--- .../net/wireless/mediatek/mt76/mt792x_regs.h | 19 +++- 6 files changed, 233 insertions(+), 21 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h index 293f173b23dd..1613c4765186 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h @@ -175,6 +175,27 @@ enum connac3_mcu_cipher_type { CONNAC3_CIPHER_GCMP_256 = 12, }; +enum DMASHDL_GROUP_IDX { + DMASHDL_GROUP_0 = 0, + DMASHDL_GROUP_1, + DMASHDL_GROUP_2, + DMASHDL_GROUP_3, + DMASHDL_GROUP_4, + DMASHDL_GROUP_5, + DMASHDL_GROUP_6, + DMASHDL_GROUP_7, + DMASHDL_GROUP_8, + DMASHDL_GROUP_9, + DMASHDL_GROUP_10, + DMASHDL_GROUP_11, + DMASHDL_GROUP_12, + DMASHDL_GROUP_13, + DMASHDL_GROUP_14, + DMASHDL_GROUP_15, + DMASHDL_GROUP_NUM, + DMASHDL_LITE_GROUP_NUM = 64 +}; + struct mt7925_mcu_scan_chinfo_event { u8 nr_chan; u8 alpha2[3]; diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h index 4cc259418afc..a5414fa2736f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h @@ -15,6 +15,7 @@ #define MT7925_RX_RING_SIZE 1536 #define MT7925_RX_MCU_RING_SIZE 512 +#define MT7928_RX_MCU_WA_RING_SIZE 512 #define MT7925_EEPROM_SIZE 3584 #define MT7925_TOKEN_SIZE 8192 @@ -139,6 +140,13 @@ enum mt7927_rxq_id { MT7927_RXQ_DATA2 = 7, }; +enum mt7928_rxq_id { + MT7928_RXQ_BAND0, + MT7928_RXQ_BAND1 = 2, + MT7928_RXQ_MCU_WM = 3, + MT7928_RXQ_MCU_WM2 = 1, /* for tx done */ +}; + enum { MODE_OPEN = 0, MODE_SHARED = 1, diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c index 6a65c630f85a..f79d4143e38b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c @@ -252,6 +252,27 @@ static const struct mt792x_dma_layout mt7927_dma_layout = { MT_RX_DATA_RING_BASE), }; +static const struct mt792x_dma_layout mt7928_dma_layout = { + .tx_data0 = mt792x_dma_ring(MT7925_TXQ_BAND0, + MT7925_TX_RING_SIZE, + MT_TX_RING_BASE), + .tx_mcu = mt792x_dma_ring(MT7925_TXQ_MCU_WM, + MT7925_TX_MCU_RING_SIZE, + MT_TX_RING_BASE), + .tx_fwdl = mt792x_dma_ring(MT7925_TXQ_FWDL, + MT7925_TX_FWDL_RING_SIZE, + MT_TX_RING_BASE), + .tx_done = mt792x_dma_ring(MT7928_RXQ_MCU_WM2, + MT7928_RX_MCU_WA_RING_SIZE, + MT_RX_EVENT_RING_BASE), + .rx_mcu = mt792x_dma_ring(MT7928_RXQ_MCU_WM, + MT7925_RX_MCU_RING_SIZE, + MT_RX_EVENT_RING_BASE), + .rx_data = mt792x_dma_ring(MT7928_RXQ_BAND0, + MT7925_RX_RING_SIZE, + MT_RX_DATA_RING_BASE), +}; + static int mt7927_dma_init(struct mt792x_dev *dev) { int ret; @@ -279,11 +300,62 @@ static int mt7927_dma_init(struct mt792x_dev *dev) return mt792x_dma_enable(dev); } +static void mt7928_dma_shdl_lite_init(struct mt792x_dev *dev) +{ + u32 addr, idx, grp1_5_quota, grp15_quota; + u32 q2group[8] = { + 0x04000000, /* AC00->G0,..., AC03->G4 */ + 0x04010101, /* AC10->G1,..., AC13->G4 */ + 0x04020202, /* AC20->G2,..., AC23->G4 */ + 0x04030303, /* AC30->G3,..., AC33->G4 */ + 0x00000005, /* ALTX->G5,BMC->G0,BCN->G0 */ + 0x00000005, /* TGID=1 ALTX->G5 */ + 0x00000000, /* NAF/NBCN/FIXFID -> G0 */ + 0x00000005, /* TGID=2 ALTX->G5 */ + }; + + /* RST */ + mt76_wr(dev, MT_DMASHDL_LITE_MAIN_CONTROL, MT_DMASHDL_LITE_MAIN_CONTROL_SW_RST); + /* pse page size 0x10, ple page size 0x7e0 */ + mt76_wr(dev, MT_DMASHDL_LITE_PAGE_SIZE, + FIELD_PREP(MT_DMASHDL_LITE_PSE_PAGE_SIZE_MASK, 0x10) | + FIELD_PREP(MT_DMASHDL_LITE_PLE_PAGE_SIZE_MASK, 0x7e0)); + /* pse max page 8, ple max page 1 */ + mt76_wr(dev, MT_DMASHDL_LITE_PKT_MAX_SIZE, + FIELD_PREP(MT_DMASHDL_LITE_PSE_PKT_MAX_SIZE_MASK, 8) | + FIELD_PREP(MT_DMASHDL_LITE_PLE_PKT_MAX_SIZE_MASK, 1)); + /* SN/UDF check */ + mt76_wr(dev, MT_DMASHDL_LITE_GROUP_SN_CHK0, 0xffffffff); + mt76_wr(dev, MT_DMASHDL_LITE_GROUP_SN_CHK1, 0xffffffff); + mt76_wr(dev, MT_DMASHDL_LITE_GROUP_UDF_CHK0, 0xffffffff); + mt76_wr(dev, MT_DMASHDL_LITE_GROUP_UDF_CHK1, 0xffffffff); + /* q mapping */ + for (addr = MT_DMASHDL_LITE_Q_MAPPING0, idx = 0; + idx < ARRAY_SIZE(q2group); + idx++, addr += 4) + mt76_wr(dev, addr, q2group[idx]); + /* refill, set 0 to enable group 0,1,2,3,4,5 & 15 */ + mt76_wr(dev, MT_DMASHDL_LITE_GROUP_DISABLE0, 0xffff7fc0); + mt76_wr(dev, MT_DMASHDL_LITE_GROUP_DISABLE1, 0xffffffff); + /* max/min quota */ + grp1_5_quota = FIELD_PREP(MT_DMASHDL_LITE_GROUP_MAX_QUOTA_MASK, 0x3f0) | + FIELD_PREP(MT_DMASHDL_LITE_GROUP_MIN_QUOTA_MASK, 0x10); + grp15_quota = FIELD_PREP(MT_DMASHDL_LITE_GROUP_MAX_QUOTA_MASK, 0x30); + + for (addr = MT_DMASHDL_LITE_GROUP0_QUOTA, idx = 0; + idx < DMASHDL_LITE_GROUP_NUM; + idx++, addr += 4) + mt76_wr(dev, addr, (idx <= 5) ? grp1_5_quota : + ((idx == 15) ? grp15_quota : 0)); +} + static int mt7925_dma_init(struct mt792x_dev *dev) { int ret; + const struct mt792x_dma_layout *layout = + is_mt7928(&dev->mt76) ? &mt7928_dma_layout : &mt7925_dma_layout; - ret = mt792x_dma_alloc_queues(dev, &mt7925_dma_layout); + ret = mt792x_dma_alloc_queues(dev, layout); if (ret) return ret; @@ -291,6 +363,9 @@ static int mt7925_dma_init(struct mt792x_dev *dev) if (ret < 0) return ret; + if (is_mt7928(&dev->mt76)) + mt7928_dma_shdl_lite_init(dev); + netif_napi_add_tx(dev->mt76.tx_napi_dev, &dev->mt76.tx_napi, mt792x_poll_tx); napi_enable(&dev->mt76.tx_napi); @@ -402,14 +477,18 @@ static int mt7925_pci_probe(struct pci_dev *pdev, if (ret < 0) return ret; - ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); - if (ret) - goto err_free_pci_vec; - is_mt7927_hw = (pdev->device == 0x6639 || pdev->device == 0x7927 || pdev->device == 0x0738); is_mt7928_hw = (pdev->device == 0x7928 || pdev->device == 0x7935); + if (is_mt7928_hw) + ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(34)); + else + ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); + + if (ret) + goto err_free_pci_vec; + /* MT7927: ASPM L1 causes unreliable WFDMA register access */ if (mt7925_disable_aspm || is_mt7927_hw) mt76_pci_disable_aspm(pdev); @@ -499,7 +578,7 @@ static int mt7925_pci_probe(struct pci_dev *pdev, if (is_mt7927(&dev->mt76)) ret = mt7927_dma_init(dev); - else if (is_mt7925(&dev->mt76)) + else if (is_mt7925(&dev->mt76) || is_mt7928(&dev->mt76)) ret = mt7925_dma_init(dev); else ret = -EINVAL; diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regs.h b/drivers/net/wireless/mediatek/mt76/mt7925/regs.h index 855a53c0748a..253ba72310ec 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/regs.h @@ -108,4 +108,25 @@ #define MT7925_PCIE_MAC_INT_ENABLE MT7925_PCIE_MAC(0x188) #define MT7925_PCIE_MAC_PM MT7925_PCIE_MAC(0x194) +#define MT_DMASHDL_LITE_BASE 0x20026200 +#define MT_DMASHDL_LITE(ofs) (MT_DMASHDL_LITE_BASE + (ofs)) +#define MT_DMASHDL_LITE_MAIN_CONTROL MT_DMASHDL_LITE(0x004) +#define MT_DMASHDL_LITE_MAIN_CONTROL_SW_RST BIT(18) +#define MT_DMASHDL_LITE_PAGE_SIZE MT_DMASHDL_LITE(0x008) +#define MT_DMASHDL_LITE_PLE_PAGE_SIZE_MASK GENMASK(12, 0) +#define MT_DMASHDL_LITE_PSE_PAGE_SIZE_MASK GENMASK(28, 16) +#define MT_DMASHDL_LITE_PKT_MAX_SIZE MT_DMASHDL_LITE(0x00c) +#define MT_DMASHDL_LITE_PLE_PKT_MAX_SIZE_MASK GENMASK(12, 0) +#define MT_DMASHDL_LITE_PSE_PKT_MAX_SIZE_MASK GENMASK(28, 16) +#define MT_DMASHDL_LITE_GROUP_DISABLE0 MT_DMASHDL_LITE(0x010) +#define MT_DMASHDL_LITE_GROUP_DISABLE1 MT_DMASHDL_LITE(0x014) +#define MT_DMASHDL_LITE_GROUP_SN_CHK0 MT_DMASHDL_LITE(0x018) +#define MT_DMASHDL_LITE_GROUP_SN_CHK1 MT_DMASHDL_LITE(0x01c) +#define MT_DMASHDL_LITE_GROUP_UDF_CHK0 MT_DMASHDL_LITE(0x020) +#define MT_DMASHDL_LITE_GROUP_UDF_CHK1 MT_DMASHDL_LITE(0x024) +#define MT_DMASHDL_LITE_Q_MAPPING0 MT_DMASHDL_LITE(0x028) +#define MT_DMASHDL_LITE_GROUP0_QUOTA MT_DMASHDL_LITE(0x100) +#define MT_DMASHDL_LITE_GROUP_MIN_QUOTA_MASK GENMASK(12, 0) +#define MT_DMASHDL_LITE_GROUP_MAX_QUOTA_MASK GENMASK(28, 16) + #endif diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c index 4d4c62bb0a77..8ad94fa58340 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c @@ -206,9 +206,38 @@ static void mt7927_wfdma_setup(struct mt792x_dev *dev) mt76_set(dev, MT_WFDMA0_INT_TX_PRI, 0x7F00); } +static void mt7928_dma_prefetch_setup(struct mt792x_dev *dev) +{ + /* rx ring */ + mt76_wr(dev, MT_WFDMA0_RX_RING0_EXT_CTRL, PREFETCH(0x0000, 0x8)); + mt76_wr(dev, MT_WFDMA0_RX_RING1_EXT_CTRL, PREFETCH(0x0080, 0x4)); + mt76_wr(dev, MT_WFDMA0_RX_RING2_EXT_CTRL, PREFETCH(0x00c0, 0x8)); + mt76_wr(dev, MT_WFDMA0_RX_RING3_EXT_CTRL, PREFETCH(0x0140, 0x4)); + mt76_wr(dev, MT_WFDMA0_RX_RING4_EXT_CTRL, PREFETCH(0x0180, 0x4)); + mt76_wr(dev, MT_WFDMA0_RX_RING5_EXT_CTRL, PREFETCH(0x01c0, 0x4)); + mt76_wr(dev, MT_WFDMA0_RX_RING6_EXT_CTRL, PREFETCH(0x0200, 0x4)); + /* tx ring */ + mt76_wr(dev, MT_WFDMA0_TX_RING0_EXT_CTRL, PREFETCH(0x0240, 0x10)); + mt76_wr(dev, MT_WFDMA0_TX_RING1_EXT_CTRL, PREFETCH(0x0340, 0x10)); + mt76_wr(dev, MT_WFDMA0_TX_RING2_EXT_CTRL, PREFETCH(0x0440, 0x10)); + mt76_wr(dev, MT_WFDMA0_TX_RING3_EXT_CTRL, PREFETCH(0x0540, 0x10)); + mt76_wr(dev, MT_WFDMA0_TX_RING4_EXT_CTRL, PREFETCH(0x0640, 0x10)); + mt76_wr(dev, MT_WFDMA0_TX_RING5_EXT_CTRL, PREFETCH(0x0740, 0x10)); + mt76_wr(dev, MT_WFDMA0_TX_RING15_EXT_CTRL, PREFETCH(0x0840, 0x4)); + mt76_wr(dev, MT_WFDMA0_TX_RING16_EXT_CTRL, PREFETCH(0x0880, 0x4)); +} + +static void mt7928_wfdma_setup(struct mt792x_dev *dev) +{ + mt76_wr(dev, MT_WFDMA0_INT_RX_PRI, 0); + mt76_wr(dev, MT_WFDMA0_INT_TX_PRI, 0); +} + static void mt792x_dma_prefetch(struct mt792x_dev *dev) { - if (is_mt7927(&dev->mt76)) { + if (is_mt7928(&dev->mt76)) { + mt7928_dma_prefetch_setup(dev); + } else if (is_mt7927(&dev->mt76)) { mt7927_dma_prefetch_setup(dev); } else if (is_mt7925(&dev->mt76)) { mt7925_dma_prefetch_setup(dev); @@ -250,32 +279,69 @@ static void mt792x_dma_prefetch(struct mt792x_dev *dev) int mt792x_dma_enable(struct mt792x_dev *dev) { - /* configure perfetch settings */ + u32 addr; + + /* configure prefetch settings */ mt792x_dma_prefetch(dev); /* reset dma idx */ mt76_wr(dev, MT_WFDMA0_RST_DTX_PTR, ~0); - if (is_mt7925(&dev->mt76)) + if (is_mt7925(&dev->mt76) || is_mt7928(&dev->mt76)) mt76_wr(dev, MT_WFDMA0_RST_DRX_PTR, ~0); /* configure delay interrupt */ mt76_wr(dev, MT_WFDMA0_PRI_DLY_INT_CFG0, 0); - mt76_set(dev, MT_WFDMA0_GLO_CFG, - MT_WFDMA0_GLO_CFG_TX_WB_DDONE | - MT_WFDMA0_GLO_CFG_FIFO_LITTLE_ENDIAN | - MT_WFDMA0_GLO_CFG_CLK_GAT_DIS | - MT_WFDMA0_GLO_CFG_OMIT_TX_INFO | - FIELD_PREP(MT_WFDMA0_GLO_CFG_DMA_SIZE, 3) | - MT_WFDMA0_GLO_CFG_FIFO_DIS_CHECK | - MT_WFDMA0_GLO_CFG_RX_WB_DDONE | - MT_WFDMA0_GLO_CFG_CSR_DISP_BASE_PTR_CHAIN_EN | - MT_WFDMA0_GLO_CFG_OMIT_RX_INFO_PFET2); + if (is_mt7928(&dev->mt76)) { + mt76_wr(dev, MT_WFDMA0_GLO_CFG, + MT_WFDMA0_GLO_CFG_TX_WB_DDONE | + MT_WFDMA0_GLO_CFG_FIFO_LITTLE_ENDIAN | + MT_WFDMA0_GLO_CFG_CLK_GAT_DIS | + MT_WFDMA0_GLO_CFG_OMIT_TX_INFO | + FIELD_PREP(MT_WFDMA0_GLO_CFG_DMA_SIZE, 1) | + MT_WFDMA0_GLO_CFG_FIFO_DIS_CHECK | + MT_WFDMA0_GLO_CFG_RX_WB_DDONE | + MT_WFDMA0_GLO_CFG_CSR_DISP_BASE_PTR_CHAIN_EN | + MT_WFDMA0_GLO_CFG_ADDR_EXT_EN | + MT_WFDMA0_GLO_CFG_CSR_LBK_RX_Q_SEL_EN); + /* set rxq threshold to 2 */ + for (addr = MT_WFDMA0_WPDMA_PAUSE_RXQ_TH10; + addr <= MT_WFDMA0_WPDMA_PAUSE_RXQ_TH76; + addr += 4) { + mt76_wr(dev, addr, + FIELD_PREP(MT_WFDMA0_WPDMA_PAUSE_RXQ_THXX_L_TH_MASK, 2) | + FIELD_PREP(MT_WFDMA0_WPDMA_PAUSE_RXQ_THXX_H_TH_MASK, 2)); + } + mt76_wr(dev, MT_WFDMA0_GLO_CFG_EXT0, + MT_WFDMA0_GLO_CFG_EXT0_CSR_MEM_ARB_LOCK_EN | + MT_WFDMA0_GLO_CFG_EXT0_CSR_TX_DMASHDL_LITE_EN | + MT_WFDMA0_GLO_CFG_EXT0_CSR_RX_WB_KEEP_RSVD | + MT_WFDMA0_GLO_CFG_EXT0_CSR_BID_CHECK_BYPASS_EN | + MT_WFDMA0_GLO_CFG_EXT0_CSR_RX_INFO_WB_EN | + MT_WFDMA0_GLO_CFG_EXT0_CSR_AXI_AWUSER_LOCK_EN | + FIELD_PREP(MT_WFDMA0_GLO_CFG_EXT0_CSR_MAX_PREFETCH_CNT_MASK, 3) | + FIELD_PREP(MT_WFDMA0_GLO_CFG_EXT0_CSR_MEM_BST_SIZE_MASK, 3) | + FIELD_PREP(MT_WFDMA0_GLO_CFG_EXT0_CSR_AXI_AW_OUTSTANDING_NUM_MASK, + 8)); + } else { + mt76_set(dev, MT_WFDMA0_GLO_CFG, + MT_WFDMA0_GLO_CFG_TX_WB_DDONE | + MT_WFDMA0_GLO_CFG_FIFO_LITTLE_ENDIAN | + MT_WFDMA0_GLO_CFG_CLK_GAT_DIS | + MT_WFDMA0_GLO_CFG_OMIT_TX_INFO | + FIELD_PREP(MT_WFDMA0_GLO_CFG_DMA_SIZE, 3) | + MT_WFDMA0_GLO_CFG_FIFO_DIS_CHECK | + MT_WFDMA0_GLO_CFG_RX_WB_DDONE | + MT_WFDMA0_GLO_CFG_CSR_DISP_BASE_PTR_CHAIN_EN | + MT_WFDMA0_GLO_CFG_OMIT_RX_INFO_PFET2); + } mt76_set(dev, MT_WFDMA0_GLO_CFG, MT_WFDMA0_GLO_CFG_TX_DMA_EN | MT_WFDMA0_GLO_CFG_RX_DMA_EN); - if (is_mt7927(&dev->mt76)) + if (is_mt7928(&dev->mt76)) + mt7928_wfdma_setup(dev); + else if (is_mt7927(&dev->mt76)) mt7927_wfdma_setup(dev); else if (is_mt7925(&dev->mt76)) mt7925_wfdma_setup(dev); diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_regs.h b/drivers/net/wireless/mediatek/mt76/mt792x_regs.h index 0e297fd9468a..3587f61e098a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x_regs.h @@ -331,11 +331,29 @@ #define MT_INT_MCU_CMD MCU2HOST_SW_INT_ENA #define MT_WFDMA0_RST_DTX_PTR MT_WFDMA0(0x20c) +#define MT_WFDMA0_WPDMA_PAUSE_RXQ_TH10 MT_WFDMA0(0x260) +#define MT_WFDMA0_WPDMA_PAUSE_RXQ_TH32 MT_WFDMA0(0x264) +#define MT_WFDMA0_WPDMA_PAUSE_RXQ_TH54 MT_WFDMA0(0x268) +#define MT_WFDMA0_WPDMA_PAUSE_RXQ_TH76 MT_WFDMA0(0x26c) +#define MT_WFDMA0_WPDMA_PAUSE_RXQ_THXX_L_TH_MASK GENMASK(11, 0) +#define MT_WFDMA0_WPDMA_PAUSE_RXQ_THXX_H_TH_MASK GENMASK(27, 16) + #define MT_WFDMA0_RST_DRX_PTR MT_WFDMA0(0x280) #define MT_WFDMA0_INT_RX_PRI MT_WFDMA0(0x298) #define MT_WFDMA0_INT_TX_PRI MT_WFDMA0(0x29c) #define MT_WFDMA0_GLO_CFG_EXT0 MT_WFDMA0(0x2b0) +#define MT_WFDMA0_GLO_CFG_EXT0_CSR_MEM_ARB_LOCK_EN BIT(4) #define MT_WFDMA0_GLO_CFG_EXT0_CSR_TX_DMASHDL_EN BIT(6) +#define MT_WFDMA0_GLO_CFG_EXT0_CSR_TX_DMASHDL_LITE_EN BIT(7) +#define MT_WFDMA0_GLO_CFG_EXT0_CSR_RX_WB_KEEP_RSVD BIT(10) +#define MT_WFDMA0_GLO_CFG_EXT0_CSR_BID_CHECK_BYPASS_EN BIT(22) +#define MT_WFDMA0_GLO_CFG_EXT0_CSR_RX_INFO_WB_EN BIT(23) +#define MT_WFDMA0_GLO_CFG_EXT0_CSR_AXI_AWUSER_LOCK_EN BIT(29) +#define MT_WFDMA0_GLO_CFG_EXT0_CSR_MAX_PREFETCH_CNT_MASK GENMASK(1, 0) +#define MT_WFDMA0_GLO_CFG_EXT0_CSR_MEM_BST_SIZE_MASK GENMASK(3, 2) +#define MT_WFDMA0_GLO_CFG_EXT0_CSR_AXI_AW_OUTSTANDING_NUM_MASK GENMASK(27, 24) + +#define MT_WFDMA0_GLO_CFG_EXT1 MT_WFDMA0(0x2b4) #define MT_WFDMA0_PRI_DLY_INT_CFG0 MT_WFDMA0(0x2f0) #define MT_WFDMA0_TX_RING0_EXT_CTRL MT_WFDMA0(0x600) @@ -375,7 +393,6 @@ #define MT_WFDMA_PREFETCH_CFG1 MT_WFDMA_EXT_CSR(0xf4) #define MT_WFDMA_PREFETCH_CFG2 MT_WFDMA_EXT_CSR(0xf8) #define MT_WFDMA_PREFETCH_CFG3 MT_WFDMA_EXT_CSR(0xfc) -#define MT_WFDMA0_GLO_CFG_EXT1 MT_WFDMA0(0x2b4) #define MT_SWDEF_BASE 0x41f200 #define MT_SWDEF(ofs) (MT_SWDEF_BASE + (ofs)) From 5e738c59e028d8998c8460a2f6431dff84de7e21 Mon Sep 17 00:00:00 2001 From: Emery Hsin Date: Fri, 12 Jun 2026 15:53:35 +0800 Subject: [PATCH 0709/1433] wifi: mt76: mt7925: add MT7928 TXD/TXS/TX_DONE support Add MT7928 TXD v2 fields, per-chip WTBL register addresses, UNI TxDone event parsing, and TXS format acceptance for MPDU/PPDU. Suppress HW AMSDU on management frames for MT7928. Signed-off-by: Xiong Signed-off-by: Emery Hsin Link: https://patch.msgid.link/20260612075339.2578327-1-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt76_connac.h | 1 + .../wireless/mediatek/mt76/mt76_connac3_mac.h | 42 ++++ .../net/wireless/mediatek/mt76/mt7925/mac.c | 221 +++++++++++++++++- .../net/wireless/mediatek/mt76/mt7925/mac.h | 9 +- .../net/wireless/mediatek/mt76/mt7925/mcu.c | 24 ++ .../wireless/mediatek/mt76/mt7925/mt7925.h | 1 + .../net/wireless/mediatek/mt76/mt7925/regs.h | 6 + 7 files changed, 292 insertions(+), 12 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac.h index 019d0275dc5c..0951038916d3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac.h @@ -301,6 +301,7 @@ static inline bool is_mt76_fw_txp(struct mt76_dev *dev) case 0x7902: case 0x7925: case 0x7927: + case 0x7928: case 0x7663: case 0x7622: return false; diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac3_mac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac3_mac.h index 247e2e7a47d8..d2d63767bfd0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac3_mac.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac3_mac.h @@ -18,6 +18,11 @@ enum { MT_LMAC_PSMP0, }; +enum { + TXS_FM_MPDU = 0, + TXS_FM_PPDU = 2, +}; + #define MT_CT_PARSE_LEN 72 #define MT_CT_DMA_BUF_NUM 2 @@ -236,7 +241,9 @@ enum tx_frag_idx { #define MT_TXD2_HDR_PAD GENMASK(11, 10) #define MT_TXD2_RTS BIT(9) #define MT_TXD2_OWN_MAC_MAP BIT(8) +#define MT_TXD2_OWN_MAC_MAP_V2 BIT(9) #define MT_TXD2_BF_TYPE GENMASK(6, 7) +#define MT_TXD2_BF_TYPE_V2 GENMASK(6, 8) #define MT_TXD2_FRAME_TYPE GENMASK(5, 4) #define MT_TXD2_SUB_TYPE GENMASK(3, 0) @@ -261,6 +268,7 @@ enum tx_frag_idx { #define MT_TXD5_BYPASS_TBB BIT(14) #define MT_TXD5_BYPASS_RBB BIT(13) #define MT_TXD5_BSS_COLOR_ZERO BIT(12) +#define MT_TXD5_OCUP_BY_OTHER_LNK BIT(11) #define MT_TXD5_TX_STATUS_HOST BIT(10) #define MT_TXD5_TX_STATUS_MCU BIT(9) #define MT_TXD5_TX_STATUS_FMT BIT(8) @@ -270,15 +278,19 @@ enum tx_frag_idx { #define MT_TXD6_VTA BIT(28) #define MT_TXD6_FIXED_BW BIT(25) #define MT_TXD6_BW GENMASK(24, 22) +#define MT_TXD6_BW_V2 GENMASK(25, 22) #define MT_TXD6_TX_RATE GENMASK(21, 16) #define MT_TXD6_TIMESTAMP_OFS_EN BIT(15) +#define MT_TXD6_TIMESTAMP_OFS_EN_V2 GENMASK(15, 13) #define MT_TXD6_TIMESTAMP_OFS_IDX GENMASK(14, 10) +#define MT_TXD6_TIMESTAMP_OFS_IDX_V2 GENMASK(12, 8) #define MT_TXD6_TID_ADDBA GENMASK(10, 8) #define MT_TXD6_MSDU_CNT GENMASK(9, 4) #define MT_TXD6_MSDU_CNT_V2 GENMASK(15, 10) #define MT_TXD6_DIS_MAT BIT(3) #define MT_TXD6_DAS BIT(2) #define MT_TXD6_AMSDU_CAP BIT(1) +#define MT_TXD6_MLD BIT(0) #define MT_TXD7_TXD_LEN GENMASK(31, 30) #define MT_TXD7_IP_SUM BIT(29) @@ -287,6 +299,9 @@ enum tx_frag_idx { #define MT_TXD7_CTXD BIT(26) #define MT_TXD7_CTXD_CNT GENMASK(25, 22) #define MT_TXD7_UDP_TCP_SUM BIT(15) +#define MT_TXD7_IMMEDIATE_TX BIT(14) +#define MT_TXD7_FORCE_RTS_CTS BIT(13) +#define MT_TXD7_ENABLE_ICI BIT(12) #define MT_TXD7_TX_TIME GENMASK(9, 0) #define MT_TXD9_WLAN_IDX GENMASK(23, 8) @@ -397,4 +412,31 @@ enum tx_frag_idx { #define MT_TXS7_MPDU_RETRY_BYTE_SCALE BIT(15) #define MT_TXS7_MPDU_RETRY_BYTE GENMASK(14, 0) +struct mt7928_uni_txdone_event { + __le16 tag; + __le16 len; + + u8 pid; /* HW packet ID */ + u8 status; /* TX_RESULT_xx */ + __le16 seq; /* packet sequence number */ + + u8 wcid; /* WLAN index (WTBL) */ + u8 tx_count; /* TX attempts including retries */ + __le16 tx_rate; + + u8 flag; /* TXS_WITH_ADVANCED_INFO or TXS_IS_EXIST */ + u8 tid; + u8 rsp_rate; + u8 rate_tbl_idx; /* last TX rate index from WLAN table */ + + u8 bw; /* bandwidth used for this PPDU */ + u8 tx_pwr; /* dBm */ + u8 flush_reason; + u8 rsv[1]; + + __le32 tx_delay; /* unit: 32us, UMAC TX to TX status */ + __le32 timestamp; /* local TSF at first bit of MAC header */ + __le32 applied_flags; +} __packed; + #endif /* __MT76_CONNAC3_MAC_H */ diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index 778a0c68d98e..a7eb80b22953 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -12,10 +12,17 @@ bool mt7925_mac_wtbl_update(struct mt792x_dev *dev, int idx, u32 mask) { - mt76_rmw(dev, MT7925_WTBL_UPDATE, MT_WTBL_UPDATE_WLAN_IDX, + u32 wtbl_update; + + if (is_mt7928(&dev->mt76)) + wtbl_update = MT7928_WTBL_UPDATE; + else + wtbl_update = MT7925_WTBL_UPDATE; + + mt76_rmw(dev, wtbl_update, MT_WTBL_UPDATE_WLAN_IDX, FIELD_PREP(MT_WTBL_UPDATE_WLAN_IDX, idx) | mask); - return mt76_poll(dev, MT7925_WTBL_UPDATE, MT_WTBL_UPDATE_BUSY, + return mt76_poll(dev, wtbl_update, MT_WTBL_UPDATE_BUSY, 0, 5000); } @@ -159,10 +166,17 @@ void mt7925_mac_set_fixed_rate_table(struct mt792x_dev *dev, { u32 ctrl = MT_WTBL_ITCR_WR | MT_WTBL_ITCR_EXEC | tbl_idx; - mt76_wr(dev, MT_WTBL_ITDR0, rate_idx); - /* use wtbl spe idx */ - mt76_wr(dev, MT_WTBL_ITDR1, MT_WTBL_SPE_IDX_SEL); - mt76_wr(dev, MT_WTBL_ITCR, ctrl); + if (is_mt7928(&dev->mt76)) { + mt76_wr(dev, MT7928_WTBL_ITDR0, rate_idx); + /* use wtbl spe idx */ + mt76_wr(dev, MT7928_WTBL_ITDR1, MT_WTBL_SPE_IDX_SEL); + mt76_wr(dev, MT7928_WTBL_ITCR, ctrl); + } else { + mt76_wr(dev, MT_WTBL_ITDR0, rate_idx); + /* use wtbl spe idx */ + mt76_wr(dev, MT_WTBL_ITDR1, MT_WTBL_SPE_IDX_SEL); + mt76_wr(dev, MT_WTBL_ITCR, ctrl); + } } /* The HW does not translate the mac header to 802.3 for mesh point */ @@ -705,6 +719,9 @@ mt7925_mac_write_txwi_80211(struct mt76_dev *dev, __le32 *txwi, txwi[2] |= cpu_to_le32(val); + if (is_mt7928(dev) && ieee80211_is_mgmt(hdr->frame_control)) + txwi[3] &= ~cpu_to_le32(MT_TXD3_HW_AMSDU); + txwi[3] |= cpu_to_le32(FIELD_PREP(MT_TXD3_BCM, multicast)); if (ieee80211_is_beacon(fc)) txwi[3] |= cpu_to_le32(MT_TXD3_REM_TX_COUNT); @@ -808,7 +825,11 @@ mt7925_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi, txwi[5] = cpu_to_le32(val); - val = MT_TXD6_DAS | FIELD_PREP(MT_TXD6_MSDU_CNT, 1); + if (is_mt7928(dev)) + val = MT_TXD6_DAS | FIELD_PREP(MT_TXD6_MSDU_CNT_V2, 1); + else + val = MT_TXD6_DAS | FIELD_PREP(MT_TXD6_MSDU_CNT, 1); + if (vif && (!ieee80211_vif_is_mld(vif) || (q_idx >= MT_LMAC_ALTX0 && q_idx <= MT_LMAC_BCN0))) val |= MT_TXD6_DIS_MAT; @@ -836,6 +857,10 @@ mt7925_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi, } txwi[6] |= cpu_to_le32(FIELD_PREP(MT_TXD6_TX_RATE, idx)); + + if (is_mt7928(dev)) + txwi[6] |= cpu_to_le32(FIELD_PREP(MT_TXD6_BW_V2, 8)); + txwi[3] |= cpu_to_le32(MT_TXD3_BA_DISABLE); } } @@ -916,7 +941,6 @@ mt7925_mac_add_txs_skb(struct mt792x_dev *dev, struct mt76_wcid *wcid, goto out_no_skb; txs = le32_to_cpu(txs_data[0]); - info = IEEE80211_SKB_CB(skb); if (!(txs & MT_TXS0_ACK_ERROR_MASK)) info->flags |= IEEE80211_TX_STAT_ACK; @@ -1033,17 +1057,192 @@ mt7925_mac_add_txs_skb(struct mt792x_dev *dev, struct mt76_wcid *wcid, return !!skb; } -void mt7925_mac_add_txs(struct mt792x_dev *dev, void *data) +static bool +mt7928_mac_add_txs_skb_msg(struct mt792x_dev *dev, struct mt76_wcid *wcid, + int pid, struct mt7928_uni_txdone_event *pevt) { + struct mt76_sta_stats *stats = &wcid->stats; + struct ieee80211_supported_band *sband; + struct mt76_dev *mdev = &dev->mt76; + struct ieee80211_tx_info *info; + struct sk_buff_head list; + u32 txrate, mode, stbc; + struct rate_info rate; + struct mt76_phy *mphy; + struct sk_buff *skb; + bool cck = false; + + mt76_tx_status_lock(mdev, &list); + skb = mt76_tx_status_skb_get(mdev, wcid, pid, &list); + if (!skb) + goto out_no_skb; + + info = IEEE80211_SKB_CB(skb); + if (!pevt->status) + info->flags |= IEEE80211_TX_STAT_ACK; + + info->status.ampdu_len = 1; + info->status.ampdu_ack_len = !!(info->flags & + IEEE80211_TX_STAT_ACK); + + info->status.rates[0].idx = -1; + + txrate = le16_to_cpu(pevt->tx_rate); + + rate.mcs = FIELD_GET(MT_TX_RATE_IDX, txrate); + rate.nss = FIELD_GET(MT_TX_RATE_NSS, txrate) + 1; + stbc = FIELD_GET(MT_TX_RATE_STBC, txrate); + + if (stbc && rate.nss > 1) + rate.nss >>= 1; + + if (rate.nss - 1 < ARRAY_SIZE(stats->tx_nss)) + stats->tx_nss[rate.nss - 1]++; + if (rate.mcs < ARRAY_SIZE(stats->tx_mcs)) + stats->tx_mcs[rate.mcs]++; + + mode = FIELD_GET(MT_TX_RATE_MODE, txrate); + switch (mode) { + case MT_PHY_TYPE_CCK: + cck = true; + fallthrough; + case MT_PHY_TYPE_OFDM: + mphy = mt76_dev_phy(mdev, wcid->phy_idx); + + if (mphy->chandef.chan->band == NL80211_BAND_5GHZ) + sband = &mphy->sband_5g.sband; + else if (mphy->chandef.chan->band == NL80211_BAND_6GHZ) + sband = &mphy->sband_6g.sband; + else + sband = &mphy->sband_2g.sband; + + rate.mcs = mt76_get_rate(mphy->dev, sband, rate.mcs, cck); + rate.legacy = sband->bitrates[rate.mcs].bitrate; + break; + case MT_PHY_TYPE_HT: + case MT_PHY_TYPE_HT_GF: + if (rate.mcs > 31) + goto out; + + rate.flags = RATE_INFO_FLAGS_MCS; + if (wcid->rate.flags & RATE_INFO_FLAGS_SHORT_GI) + rate.flags |= RATE_INFO_FLAGS_SHORT_GI; + break; + case MT_PHY_TYPE_VHT: + if (rate.mcs > 9) + goto out; + + rate.flags = RATE_INFO_FLAGS_VHT_MCS; + break; + case MT_PHY_TYPE_HE_SU: + case MT_PHY_TYPE_HE_EXT_SU: + case MT_PHY_TYPE_HE_TB: + case MT_PHY_TYPE_HE_MU: + if (rate.mcs > 11) + goto out; + + rate.he_gi = wcid->rate.he_gi; + rate.he_dcm = FIELD_GET(MT_TX_RATE_DCM, txrate); + rate.flags = RATE_INFO_FLAGS_HE_MCS; + break; + case MT_PHY_TYPE_EHT_SU: + case MT_PHY_TYPE_EHT_TRIG: + case MT_PHY_TYPE_EHT_MU: + if (rate.mcs > 13) + goto out; + + rate.eht_gi = wcid->rate.eht_gi; + rate.flags = RATE_INFO_FLAGS_EHT_MCS; + break; + default: + goto out; + } + + stats->tx_mode[mode]++; + + switch (pevt->bw) { + case IEEE80211_STA_RX_BW_160: + rate.bw = RATE_INFO_BW_160; + stats->tx_bw[3]++; + break; + case IEEE80211_STA_RX_BW_80: + rate.bw = RATE_INFO_BW_80; + stats->tx_bw[2]++; + break; + case IEEE80211_STA_RX_BW_40: + rate.bw = RATE_INFO_BW_40; + stats->tx_bw[1]++; + break; + default: + rate.bw = RATE_INFO_BW_20; + stats->tx_bw[0]++; + break; + } + wcid->rate = rate; + +out: + mt76_tx_status_skb_done(mdev, skb, &list); + +out_no_skb: + mt76_tx_status_unlock(mdev, &list); + + return !!skb; +} + +void mt7928_mac_add_txs_msg(struct mt792x_dev *dev, + void *evt) +{ + struct mt7928_uni_txdone_event *done_evt; struct mt792x_link_sta *mlink = NULL; struct mt76_wcid *wcid; - __le32 *txs_data = data; u16 wcidx; u8 pid; - if (le32_get_bits(txs_data[0], MT_TXS0_TXS_FORMAT) > 1) + done_evt = (struct mt7928_uni_txdone_event *)evt; + wcidx = done_evt->wcid; + pid = done_evt->pid; + + if (pid < MT_PACKET_ID_FIRST) return; + if (wcidx >= MT792x_WTBL_SIZE) + return; + + rcu_read_lock(); + + wcid = mt76_wcid_ptr(dev, wcidx); + if (!wcid) + goto out; + + mlink = container_of(wcid, struct mt792x_link_sta, wcid); + + mt7928_mac_add_txs_skb_msg(dev, wcid, pid, done_evt); + if (!wcid->sta) + goto out; + + mt76_wcid_add_poll(&dev->mt76, &mlink->wcid); + +out: + rcu_read_unlock(); +} + +void mt7925_mac_add_txs(struct mt792x_dev *dev, void *data) +{ + struct mt792x_link_sta *mlink = NULL; + __le32 *txs_data = data; + struct mt76_wcid *wcid; + u8 pid, txs_fm; + u16 wcidx; + + txs_fm = le32_get_bits(txs_data[0], MT_TXS0_TXS_FORMAT); + + if (is_mt7928(&dev->mt76)) { + if (txs_fm != TXS_FM_MPDU && txs_fm != TXS_FM_PPDU) + return; + } else if (txs_fm > 1) { + return; + } + wcidx = le32_get_bits(txs_data[2], MT_TXS2_WCID); pid = le32_get_bits(txs_data[3], MT_TXS3_PID); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.h b/drivers/net/wireless/mediatek/mt76/mt7925/mac.h index 67148c87de76..567d072cba02 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.h @@ -14,7 +14,14 @@ static inline u32 mt7925_mac_wtbl_lmac_addr(struct mt792x_dev *dev, u16 wcid, u8 dw) { - mt76_wr(dev, MT7925_WTBLON_TOP_WDUCR, + u32 wdu_cr; + + if (is_mt7928(&dev->mt76)) + wdu_cr = MT7928_WTBLON_TOP_WDUCR; + else + wdu_cr = MT7925_WTBLON_TOP_WDUCR; + + mt76_wr(dev, wdu_cr, FIELD_PREP(MT_WTBLON_TOP_WDUCR_GROUP, (wcid >> 7))); return MT_WTBL_LMAC_OFFS(wcid, dw); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index 5602544d3853..6c7fae71fe5b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -437,6 +437,7 @@ mt7925_mcu_tx_done_event(struct mt792x_dev *dev, struct sk_buff *skb) u8 rsv[3]; u8 data[]; } __packed * txs; + struct mt7928_uni_txdone_event *evt; struct tlv *tlv; u32 tlv_len; @@ -446,6 +447,29 @@ mt7925_mcu_tx_done_event(struct mt792x_dev *dev, struct sk_buff *skb) while (tlv_len > 0 && le16_to_cpu(tlv->len) <= tlv_len) { switch (le16_to_cpu(tlv->tag)) { + case UNI_EVENT_TX_DONE_MSG: + if (!is_mt7928(&dev->mt76)) + break; + + evt = (struct mt7928_uni_txdone_event *)tlv; + if (evt->status) { + dev_info(dev->mt76.dev, + "TxDone: pid=%u status=%#x sn=%#x wcid=%u " + "cnt=%u rate=%#x flag=%#x tid=%u pwr=%u " + "rsp_rate=%#x rate_idx=%u bw=%u flush=%#x " + "delay=%#x ts=%#x flags=%#x\n", + evt->pid, evt->status, + le16_to_cpu(evt->seq), evt->wcid, + evt->tx_count, le16_to_cpu(evt->tx_rate), + evt->flag, evt->tid, evt->tx_pwr, + evt->rsp_rate, evt->rate_tbl_idx, + evt->bw, evt->flush_reason, + le32_to_cpu(evt->tx_delay), + le32_to_cpu(evt->timestamp), + le32_to_cpu(evt->applied_flags)); + } + mt7928_mac_add_txs_msg(dev, evt); + break; case UNI_EVENT_TX_DONE_RAW: txs = (struct mt7925_mcu_txs_event *)tlv->data; mt7925_mac_add_txs(dev, txs->data); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h index a5414fa2736f..321e732347f2 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h @@ -354,6 +354,7 @@ int mt7925_mcu_parse_response(struct mt76_dev *mdev, int cmd, int mt7925e_mac_reset(struct mt792x_dev *dev); int mt7925e_mcu_init(struct mt792x_dev *dev); void mt7925_mac_add_txs(struct mt792x_dev *dev, void *data); +void mt7928_mac_add_txs_msg(struct mt792x_dev *dev, void *evt); void mt7925_set_runtime_pm(struct mt792x_dev *dev); void mt7925_mcu_set_suspend_iter(void *priv, u8 *mac, struct ieee80211_vif *vif); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regs.h b/drivers/net/wireless/mediatek/mt76/mt7925/regs.h index 253ba72310ec..af6291fc53cd 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/regs.h @@ -97,12 +97,18 @@ #define MT_WFSYS_SW_RST_B 0x7c000140 #define MT7925_WTBLON_TOP_WDUCR MT_WTBLON_TOP(0x370) +#define MT7928_WTBLON_TOP_WDUCR MT_WTBLON_TOP(0x400) #define MT_WTBLON_TOP_WDUCR_GROUP GENMASK(4, 0) #define MT7925_WTBL_UPDATE MT_WTBLON_TOP(0x380) +#define MT7928_WTBL_UPDATE MT_WTBLON_TOP(0x410) #define MT_WTBL_UPDATE_WLAN_IDX GENMASK(11, 0) #define MT_WTBL_UPDATE_ADM_COUNT_CLEAR BIT(14) +#define MT7928_WTBL_ITCR MT_WTBLON_TOP(0x440) +#define MT7928_WTBL_ITDR0 MT_WTBLON_TOP(0x448) +#define MT7928_WTBL_ITDR1 MT_WTBLON_TOP(0x44c) + #define MT7925_PCIE_MAC_BASE 0x10000 #define MT7925_PCIE_MAC(ofs) (MT7925_PCIE_MAC_BASE + (ofs)) #define MT7925_PCIE_MAC_INT_ENABLE MT7925_PCIE_MAC(0x188) From d93e31ba9fe20a6c8b065838134eaac36f06d6d1 Mon Sep 17 00:00:00 2001 From: Emery Hsin Date: Fri, 12 Jun 2026 15:53:36 +0800 Subject: [PATCH 0710/1433] wifi: mt76: mt7925: add MMIO register remapping table for MT7928 MT7928 has a different physical address layout. Add a dedicated mt7928_fixed_map[] remapping table and select it at runtime. Set mdev->rev early in probe for correct chip revision detection. Signed-off-by: Leon Yen Signed-off-by: Emery Hsin Link: https://patch.msgid.link/20260612075339.2578327-2-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/pci.c | 109 +++++++++++++++++- 1 file changed, 103 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c index f79d4143e38b..719f53ddf1eb 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c @@ -110,7 +110,7 @@ static u32 mt7925_reg_map_l2(struct mt792x_dev *dev, u32 addr) static u32 __mt7925_reg_addr(struct mt792x_dev *dev, u32 addr) { - static const struct mt76_connac_reg_map fixed_map[] = { + static const struct mt76_connac_reg_map default_fixed_map[] = { { 0x830c0000, 0x000000, 0x0001000 }, /* WF_MCU_BUS_CR_REMAP */ { 0x54000000, 0x002000, 0x0001000 }, /* WFDMA PCIE0 MCU DMA0 */ { 0x55000000, 0x003000, 0x0001000 }, /* WFDMA PCIE0 MCU DMA1 */ @@ -164,14 +164,109 @@ static u32 __mt7925_reg_addr(struct mt792x_dev *dev, u32 addr) { 0x7c500000, 0x060000, 0x200000 }, /* remap */ { 0x0, 0x0, 0x0 } /* End */ }; - int i; + /* The remap table was ordered from highest to lowest frequency + * to improve lookup efficiency. + */ + static const struct mt76_connac_reg_map mt7928_fixed_map[] = { + {0x54000000, 0x002000, 0x01000}, /* WFDMA_0 (PCIE0 MCU DMA0) */ + {0x55000000, 0x003000, 0x01000}, /* WFDMA_1 (PCIE0 MCU DMA1) */ + {0x57000000, 0x005000, 0x01000}, /* WFDMA_3 (MCU wrap CR) */ + {0x58000000, 0x006000, 0x01000}, /* WFDMA_4 (PCIE1 MCU DMA0) */ + {0x59000000, 0x007000, 0x01000}, /* WFDMA_5 (PCIE1 MCU DMA1) */ + {0x56000000, 0x004000, 0x01000}, /* WFDMA_2 (Reserved) */ + {0x820D0000, 0x030000, 0x10000}, /* WF_LMAC_TOP (WF_WTBLON) */ + {0x820C4000, 0x0A8000, 0x04000}, /* WF_LMAC_TOP (WF_UWTBL) */ + {0x820C0000, 0x008000, 0x04000}, /* WF_UMAC_TOP (PLE) */ + {0x820C8000, 0x00C000, 0x02000}, /* WF_UMAC_TOP (PSE) */ + {0x820CC000, 0x00E000, 0x02000}, /* WF_UMAC_TOP (PP) */ + {0x820F0000, 0x0A0000, 0x00400}, /* WF_LMAC_TOP (WF_CFG) */ + {0x820F1000, 0x0A0600, 0x00200}, /* WF_LMAC_TOP (WF_TRB) */ + {0x820F2000, 0x0A0800, 0x00400}, /* WF_LMAC_TOP (WF_AGG) */ + {0x820F3000, 0x0A0C00, 0x00400}, /* WF_LMAC_TOP (WF_ARB) */ + {0x820F4000, 0x0A1000, 0x00400}, /* WF_LMAC_TOP (WF_TMAC) */ + {0x820F5000, 0x0A1400, 0x00800}, /* WF_LMAC_TOP (WF_RMAC) */ + {0x820F7000, 0x0A1E00, 0x00200}, /* WF_LMAC_TOP (WF_DMA) */ + {0x820F9000, 0x0A3400, 0x00200}, /* WF_LMAC_TOP (WF_WTBLOFF) */ + {0x820FA000, 0x0A4000, 0x00200}, /* WF_LMAC_TOP (WF_ETBF) */ + {0x820FB000, 0x0A4200, 0x00400}, /* WF_LMAC_TOP (WF_LPON) */ + {0x820FC000, 0x0A4600, 0x00200}, /* WF_LMAC_TOP (WF_INT) */ + {0x820FD000, 0x0A4800, 0x00800}, /* WF_LMAC_TOP (WF_MIB) */ + {0x820E0000, 0x020000, 0x00400}, /* WF_LMAC_TOP (WF_CFG) */ + {0x820E1000, 0x020400, 0x00200}, /* WF_LMAC_TOP (WF_TRB) */ + {0x820E2000, 0x020800, 0x00400}, /* WF_LMAC_TOP (WF_AGG) */ + {0x820E3000, 0x020C00, 0x00400}, /* WF_LMAC_TOP (WF_ARB) */ + {0x820E4000, 0x021000, 0x00400}, /* WF_LMAC_TOP (WF_TMAC) */ + {0x820E5000, 0x021400, 0x00800}, /* WF_LMAC_TOP (WF_RMAC) */ + {0x820CE000, 0x021C00, 0x00200}, /* WF_LMAC_TOP (WF_SEC) */ + {0x820E7000, 0x021E00, 0x00200}, /* WF_LMAC_TOP (WF_DMA) */ + {0x820CF000, 0x022000, 0x01000}, /* WF_LMAC_TOP (WF_PF) */ + {0x820E9000, 0x023400, 0x00200}, /* WF_LMAC_TOP (WF_WTBLOFF) */ + {0x820EA000, 0x024000, 0x00200}, /* WF_LMAC_TOP (WF_ETBF) */ + {0x820EB000, 0x024200, 0x00400}, /* WF_LMAC_TOP (WF_LPON) */ + {0x820EC000, 0x024600, 0x00200}, /* WF_LMAC_TOP (WF_INT) */ + {0x820ED000, 0x024800, 0x00800}, /* WF_LMAC_TOP (WF_MIB) */ + {0x820CA000, 0x026000, 0x02000}, /* WF_LMAC_TOP (WF_MUCOP) */ + {0x7C500000, 0x060000, 0x200000}, /* remap */ + {0x7C000000, 0x0F0000, 0x10000}, /* CONN_INFRA (off2on) */ + {0x7C060000, 0x0E0000, 0x10000}, /* remap MT_CONN_ON_LPCTL and MT_CONN_ON_MISC */ + {0x20060000, 0x0E0000, 0x10000}, /* CONN_INFRA conn_host_csr_top */ + {0x7C010000, 0x100000, 0x10000}, /* CONN_INFRA (gpio clkgen cfg) */ + {0x7C050000, 0x1A0000, 0x10000}, /* CONN_INFRA SYSRAM */ + {0x7C080000, 0x190000, 0x10000}, /* CONN_INFRA (coex, pta) */ + {0x7C070000, 0x180000, 0x10000}, /* CONN_INFRA Semaphore */ + {0x7C040000, 0x170000, 0x10000}, /* CONN_INFRA (bus, afe) */ + {0x7C026000, 0x0D6000, 0x0019C}, /* remap DMASHL TOP */ + {0x20020000, 0x0D0000, 0x0C000}, /* CONN_INFRA wf_dma_host_side_cr */ + {0x200B0000, 0x050000, 0x10000}, /* CONN_INFRA conn_von_sysram */ + {0x20090000, 0x150000, 0x08000}, /* CONN_INFRA von_connsys_s0-s7 */ + {0x7C098000, 0x158000, 0x08000}, /* CONN_INFRA von_connsys_hclk_s0-s7 */ + {0x20030000, 0x160000, 0x10000}, /* CONN_INFRA CCIF */ + {0x70000000, 0x1E0000, 0x10000}, /* CONN_INFRA CONN2AP */ + {0x830C0000, 0x000000, 0x01000}, /* WF_MCU_BUS_CR_REMAP */ + {0x81020000, 0x0C0000, 0x10000}, /* WF_TOP_MISC_ON */ + {0x80020000, 0x0B0000, 0x10000}, /* WF_TOP_MISC_OFF */ + {0x81040000, 0x120000, 0x01000}, /* WF_MCU_CFG_ON */ + {0x00400000, 0x080000, 0x10000}, /* WF_MCU_SYSRAM */ + {0x00410000, 0x090000, 0x10000}, /* WF_MCU_SYSRAM (Common driver) */ + {0x88000000, 0x140000, 0x10000}, /* WF_MCU_CFG_LS */ + {0x80010000, 0x124000, 0x01000}, /* WF_AXIDMA */ + {0x81050000, 0x121000, 0x01000}, /* WF_MCU_EINT */ + {0x81060000, 0x122000, 0x01000}, /* WF_MCU_GPT */ + {0x81070000, 0x123000, 0x01000}, /* WF_MCU_WDT */ + {0x830A0000, 0x040000, 0x10000}, /* WF_PHY_MAP0 */ + {0x83090000, 0x060000, 0x10000}, /* WF_PHY_MAP2 */ + {0x83000000, 0x110000, 0x10000}, /* WF_PHY_MAP3 */ + {0x83010000, 0x130000, 0x10000}, /* WF_PHY_MAP4 */ + {0x81030000, 0x0AE000, 0x00100}, /* WFSYS_AON */ + {0x81031000, 0x0AE100, 0x00100}, /* WFSYS_AON */ + {0x81032000, 0x0AE200, 0x00100}, /* WFSYS_AON */ + {0x81033000, 0x0AE300, 0x00100}, /* WFSYS_AON */ + {0x81034000, 0x0AE400, 0x00100}, /* WFSYS_AON */ + {0xE0400000, 0x070000, 0x10000}, /* WF_UMCA_SYSRAM */ + {0x70010000, 0x1C0000, 0x10000}, /* CB Infra1 */ + {0x70020000, 0x1F0000, 0x10000}, /* Reserved for CBTOP, can't switch */ + {0x74040000, 0x1D0000, 0x10000}, /* CB PCIe (cbtop remap) */ + {0x18010000, 0x100000, 0x10000}, /* remap MT_HW_EMI_CTRL */ + {0x00000000, 0x000000, 0x00000}, /* END */ + }; + const struct mt76_connac_reg_map *fixed_map; + size_t array_size; + u32 i; if (addr < 0x200000) return addr; mt7925_reg_remap_restore(dev); - for (i = 0; i < ARRAY_SIZE(fixed_map); i++) { + if (is_mt7928(&dev->mt76)) { + fixed_map = mt7928_fixed_map; + array_size = ARRAY_SIZE(mt7928_fixed_map); + } else { + fixed_map = default_fixed_map; + array_size = ARRAY_SIZE(default_fixed_map); + } + + for (i = 0; i < array_size; i++) { u32 ofs; if (addr < fixed_map[i].phys) @@ -513,12 +608,14 @@ static int mt7925_pci_probe(struct pci_dev *pdev, dev->hif_ops = &mt7925_pcie_ops; dev->pcie_reg = &mt7925_pcie_reg; - if (is_mt7928_hw) + if (is_mt7928_hw) { dev->irq_map = &mt7928_irq_map; - else if (is_mt7927_hw) + mdev->rev = 0x7928 << 16; + } else if (is_mt7927_hw) { dev->irq_map = &mt7927_irq_map; - else + } else { dev->irq_map = &mt7925_irq_map; + } mt76_mmio_init(&dev->mt76, pcim_iomap_table(pdev)[0]); tasklet_init(&mdev->irq_tasklet, mt792x_irq_tasklet, (unsigned long)dev); From f26fda0c6ae2fb2347c16e65d158d9cc9e697751 Mon Sep 17 00:00:00 2001 From: Emery Hsin Date: Fri, 12 Jun 2026 15:53:37 +0800 Subject: [PATCH 0711/1433] wifi: mt76: mt7925: align scan IE and EFUSE TLV lengths to 4 bytes for MT7928 MT7928 firmware requires 4-byte aligned TLV payloads. Round up UNI_SCAN_IE allocation with ALIGN(..., 4) and track padded length. Signed-off-by: Xiong Signed-off-by: Emery Hsin Link: https://patch.msgid.link/20260612075339.2578327-3-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index 6c7fae71fe5b..bc6b6fc2d6f5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -1581,7 +1581,8 @@ int mt7925_mcu_set_eeprom(struct mt792x_dev *dev) .tag = cpu_to_le16(UNI_EFUSE_BUFFER_MODE), .len = cpu_to_le16(sizeof(req) - 4), .buffer_mode = EE_MODE_EFUSE, - .format = EE_FORMAT_WHOLE + .format = EE_FORMAT_WHOLE, + .buf_len = 0 }; return mt76_mcu_send_and_get_msg(&dev->mt76, MCU_UNI_CMD(EFUSE_CTRL), @@ -3037,11 +3038,11 @@ mt7925_mcu_build_scan_ie_tlv(struct mt76_dev *mdev, struct ieee80211_scan_ies *scan_ies) { u32 max_len = sizeof(struct scan_ie_tlv) + MT76_CONNAC_SCAN_IE_LEN; + u32 ies_len, alloc_len; struct scan_ie_tlv *ie; enum nl80211_band i; struct tlv *tlv; const u8 *ies; - u16 ies_len; for (i = 0; i <= NL80211_BAND_6GHZ; i++) { if (i == NL80211_BAND_60GHZ) @@ -3053,11 +3054,16 @@ mt7925_mcu_build_scan_ie_tlv(struct mt76_dev *mdev, if (!ies || !ies_len) continue; - if (ies_len > max_len) + if (is_mt7928(mdev)) + alloc_len = ALIGN(sizeof(*ie) + ies_len, 4); + else + alloc_len = sizeof(*ie) + ies_len; + + if (alloc_len > max_len) return; tlv = mt76_connac_mcu_add_tlv(skb, UNI_SCAN_IE, - sizeof(*ie) + ies_len); + alloc_len); ie = (struct scan_ie_tlv *)tlv; memcpy(ie->ies, ies, ies_len); @@ -3075,7 +3081,7 @@ mt7925_mcu_build_scan_ie_tlv(struct mt76_dev *mdev, break; } - max_len -= (sizeof(*ie) + ies_len); + max_len -= alloc_len; } } From 8905038d58aa5ffa2a42e0efd4e41da4e25ce101 Mon Sep 17 00:00:00 2001 From: Emery Hsin Date: Fri, 12 Jun 2026 15:53:38 +0800 Subject: [PATCH 0712/1433] wifi: mt76: mt7925: add MT7928 per-chip PCIe register definitions MT7928 maps PCIe MAC registers through base 0x74040000. Add MT7928_PCIE_MAC_{INT_ENABLE,PM} macros and override dev->pcie_reg at probe time for MT7928 hardware. Signed-off-by: Xiong Signed-off-by: Emery Hsin Link: https://patch.msgid.link/20260612075339.2578327-4-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/pci.c | 6 ++++++ drivers/net/wireless/mediatek/mt76/mt7925/regs.h | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c index 719f53ddf1eb..1ad5847d4a8c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c @@ -473,6 +473,11 @@ static const struct mt792x_pcie_reg mt7925_pcie_reg = { .pm = MT7925_PCIE_MAC_PM, }; +static const struct mt792x_pcie_reg mt7928_pcie_reg = { + .imask = MT7928_PCIE_MAC_INT_ENABLE, + .pm = MT7928_PCIE_MAC_PM, +}; + static const struct mt792x_irq_map mt7925_irq_map = { .host_irq_enable = MT_WFDMA0_HOST_INT_ENA, .tx = { @@ -609,6 +614,7 @@ static int mt7925_pci_probe(struct pci_dev *pdev, dev->pcie_reg = &mt7925_pcie_reg; if (is_mt7928_hw) { + dev->pcie_reg = &mt7928_pcie_reg; dev->irq_map = &mt7928_irq_map; mdev->rev = 0x7928 << 16; } else if (is_mt7927_hw) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regs.h b/drivers/net/wireless/mediatek/mt76/mt7925/regs.h index af6291fc53cd..cb937d565a80 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/regs.h @@ -114,6 +114,11 @@ #define MT7925_PCIE_MAC_INT_ENABLE MT7925_PCIE_MAC(0x188) #define MT7925_PCIE_MAC_PM MT7925_PCIE_MAC(0x194) +#define MT7928_PCIE_MAC_BASE 0x74040000 +#define MT7928_PCIE_MAC(ofs) (MT7928_PCIE_MAC_BASE + (ofs)) +#define MT7928_PCIE_MAC_INT_ENABLE MT7928_PCIE_MAC(0x188) +#define MT7928_PCIE_MAC_PM MT7928_PCIE_MAC(0x194) + #define MT_DMASHDL_LITE_BASE 0x20026200 #define MT_DMASHDL_LITE(ofs) (MT_DMASHDL_LITE_BASE + (ofs)) #define MT_DMASHDL_LITE_MAIN_CONTROL MT_DMASHDL_LITE(0x004) From e65b4ca3391e80d043d010237d8c8562800eb6bf Mon Sep 17 00:00:00 2001 From: Emery Hsin Date: Fri, 12 Jun 2026 15:53:39 +0800 Subject: [PATCH 0713/1433] wifi: mt76: mt7925: add MT7928 PCIe support Register MT7928 (0x7928, 0x7935) in the PCI device table and declare MODULE_FIRMWARE for all four MT7928 firmware blobs. Signed-off-by: Emery Hsin Link: https://patch.msgid.link/20260612075339.2578327-5-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/pci.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c index 1ad5847d4a8c..1514494f6c7b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c @@ -22,6 +22,10 @@ static const struct pci_device_id mt7925_pci_device_table[] = { .driver_data = (kernel_ulong_t)MT7927_FIRMWARE_WM }, { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x0738), .driver_data = (kernel_ulong_t)MT7927_FIRMWARE_WM }, + { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x7928), + .driver_data = (kernel_ulong_t)MT7928_FIRMWARE_WM }, + { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x7935), + .driver_data = (kernel_ulong_t)MT7928_FIRMWARE_WM }, { }, }; @@ -663,6 +667,11 @@ static int mt7925_pci_probe(struct pci_dev *pdev, "MT7927 raw CHIPID=0x%04x, forcing chip=0x7927\n", mt76_chip(mdev)); mdev->rev = (0x7927 << 16) | (mdev->rev & 0xff); + } else if (is_mt7928_hw && mt76_chip(mdev) != 0x7928) { + dev_info(mdev->dev, + "MT7928 raw CHIPID=0x%04x, forcing chip=0x7928\n", + mt76_chip(mdev)); + mdev->rev = (0x7928 << 16) | (mdev->rev & 0xff); } mt76_rmw_field(dev, MT_HW_EMI_CTL, MT_HW_EMI_CTL_SLPPROT_EN, 1); @@ -913,6 +922,10 @@ MODULE_FIRMWARE(MT7925_FIRMWARE_WM); MODULE_FIRMWARE(MT7925_ROM_PATCH); MODULE_FIRMWARE(MT7927_FIRMWARE_WM); MODULE_FIRMWARE(MT7927_ROM_PATCH); +MODULE_FIRMWARE(MT7928_FIRMWARE_WM); +MODULE_FIRMWARE(MT7928_ROM_PATCH); +MODULE_FIRMWARE(MT7928_CB_ROM_PATCH); +MODULE_FIRMWARE(MT7928_PHY_RAM); MODULE_AUTHOR("Deren Wu "); MODULE_AUTHOR("Lorenzo Bianconi "); MODULE_DESCRIPTION("MediaTek MT7925E (PCIe) wireless driver"); From a92cd5dd792a63a5d8a72142835382d4edbe9933 Mon Sep 17 00:00:00 2001 From: David Bauer Date: Sat, 16 May 2026 16:49:42 +0200 Subject: [PATCH 0714/1433] wifi: mt76: mt7915: configure noise floor reporting on reset When performing a full system recovery of the MCU on a dual-phy platform, band 0 (usually 2.4GHz) stops reading correct noise floor data. This is due to noise floor reporting only being configured correctly for the second device PHY. Configure the respective registers correctly after restarting the MCU firmware to fix reported noise-floor values. Signed-off-by: David Bauer Link: https://patch.msgid.link/20260516144944.2574053-1-mail@david-bauer.net Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/mac.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c index 334c19ab2b22..9e93201e4186 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c @@ -1292,6 +1292,7 @@ mt7915_mac_restart(struct mt7915_dev *dev) struct mt7915_phy *phy2; struct mt76_phy *ext_phy; struct mt76_dev *mdev = &dev->mt76; + bool run_main, run_ext; int i, ret; ext_phy = dev->mt76.phys[MT_BAND1]; @@ -1387,13 +1388,17 @@ mt7915_mac_restart(struct mt7915_dev *dev) mt7915_init_txpower(phy2); ret = mt7915_txbf_init(dev); - if (test_bit(MT76_STATE_RUNNING, &dev->mphy.state)) { + run_main = test_and_clear_bit(MT76_STATE_RUNNING, &dev->mphy.state); + run_ext = ext_phy && + test_and_clear_bit(MT76_STATE_RUNNING, &ext_phy->state); + + if (run_main) { ret = mt7915_run(dev->mphy.hw); if (ret) goto out; } - if (ext_phy && test_bit(MT76_STATE_RUNNING, &ext_phy->state)) { + if (run_ext) { ret = mt7915_run(ext_phy->hw); if (ret) goto out; From 5323d3e50c20ce53b9b393ef36b027d6cd1e6f11 Mon Sep 17 00:00:00 2001 From: Filip Bakreski Date: Tue, 9 Jun 2026 20:53:01 +1000 Subject: [PATCH 0715/1433] wifi: mt76: mt76u: use a threaded NAPI for the RX path The USB RX path delivers frames to the stack via mt76_rx_complete() with a NULL napi pointer, taking the netif_receive_skb_list() path, so it never benefits from GRO -- unlike the DMA-based mt76 drivers, which pass a real napi and use napi_gro_receive(). For bulk TCP traffic this is costly, as every segment traverses the stack individually. Service the MT_RXQ_MAIN queue from a threaded NAPI, reusing mt76_dev's existing napi_dev and napi[] rather than adding new fields. The URB completion handler schedules the napi; its poll drains the URBs, builds the skbs, resubmits and delivers them through napi_gro_receive(). The MCU queue stays on the existing RX worker. This enables GRO and moves RX processing into its own kernel thread, parallelising the datapath. On mt7921u at HE-MCS 11 (2x2, 80 MHz; fast.com, multiple streams) this averages ~588 Mbit/s, versus ~424 Mbit/s when the same napi is instead driven manually from the RX worker, and ~380 Mbit/s for the unmodified driver. Suggested-by: Lorenzo Bianconi Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Filip Bakreski Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260609105301.196302-1-phial@phiality.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/usb.c | 64 +++++++++++++++++++++--- 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/usb.c b/drivers/net/wireless/mediatek/mt76/usb.c index d9638a9b749b..e54b35e53b6a 100644 --- a/drivers/net/wireless/mediatek/mt76/usb.c +++ b/drivers/net/wireless/mediatek/mt76/usb.c @@ -580,7 +580,11 @@ static void mt76u_complete_rx(struct urb *urb) q->head = (q->head + 1) % q->ndesc; q->queued++; - mt76_worker_schedule(&dev->usb.rx_worker); + + if (q == &dev->q_rx[MT_RXQ_MAIN]) + napi_schedule(&dev->napi[MT_RXQ_MAIN]); + else + mt76_worker_schedule(&dev->usb.rx_worker); out: spin_unlock_irqrestore(&q->lock, flags); } @@ -618,11 +622,23 @@ mt76u_process_rx_queue(struct mt76_dev *dev, struct mt76_queue *q) } mt76u_submit_rx_buf(dev, qid, urb); } - if (qid == MT_RXQ_MAIN) { - local_bh_disable(); - mt76_rx_poll_complete(dev, MT_RXQ_MAIN, NULL); - local_bh_enable(); - } +} + +/* Threaded NAPI poll for the MAIN RX queue: drain URBs, build skbs, resubmit, + * then deliver through napi_gro_receive() and let napi_complete() flush GRO. + */ +static int mt76u_napi_poll(struct napi_struct *napi, int budget) +{ + struct mt76_dev *dev = mt76_priv(napi->dev); + + rcu_read_lock(); + mt76u_process_rx_queue(dev, &dev->q_rx[MT_RXQ_MAIN]); + mt76_rx_poll_complete(dev, MT_RXQ_MAIN, napi); + rcu_read_unlock(); + + napi_complete(napi); + + return 0; } static void mt76u_rx_worker(struct mt76_worker *w) @@ -632,8 +648,13 @@ static void mt76u_rx_worker(struct mt76_worker *w) int i; rcu_read_lock(); - mt76_for_each_q_rx(dev, i) + mt76_for_each_q_rx(dev, i) { + /* MT_RXQ_MAIN is serviced by the threaded NAPI poll */ + if (i == MT_RXQ_MAIN) + continue; + mt76u_process_rx_queue(dev, &dev->q_rx[i]); + } rcu_read_unlock(); } @@ -731,6 +752,13 @@ void mt76u_stop_rx(struct mt76_dev *dev) for (j = 0; j < q->ndesc; j++) usb_poison_urb(q->entry[j].urb); } + + /* The MAIN queue napi stays enabled for the device lifetime. The URBs + * are now poisoned, so mt76u_complete_rx() can no longer reschedule it; + * just drain any in-flight poll before the caller frees or resets. + */ + if (dev->napi_dev) + napi_synchronize(&dev->napi[MT_RXQ_MAIN]); } EXPORT_SYMBOL_GPL(mt76u_stop_rx); @@ -1051,6 +1079,13 @@ void mt76u_queues_deinit(struct mt76_dev *dev) mt76u_stop_rx(dev); mt76u_stop_tx(dev); + if (dev->napi_dev) { + napi_disable(&dev->napi[MT_RXQ_MAIN]); + netif_napi_del(&dev->napi[MT_RXQ_MAIN]); + free_netdev(dev->napi_dev); + dev->napi_dev = NULL; + } + mt76u_free_rx(dev); mt76u_free_tx(dev); } @@ -1078,6 +1113,7 @@ int __mt76u_init(struct mt76_dev *dev, struct usb_interface *intf, { struct usb_device *udev = interface_to_usbdev(intf); struct mt76_usb *usb = &dev->usb; + struct mt76_dev **priv; int err; INIT_WORK(&usb->stat_work, mt76u_tx_status_data); @@ -1115,6 +1151,20 @@ int __mt76u_init(struct mt76_dev *dev, struct usb_interface *intf, sched_set_fifo_low(usb->rx_worker.task); sched_set_fifo_low(usb->status_worker.task); + /* threaded NAPI on a dummy netdev (reusing mt76_dev's napi_dev/napi[]) + * services the MAIN RX queue and gives the RX path GRO + */ + dev->napi_dev = alloc_netdev_dummy(sizeof(struct mt76_dev *)); + if (!dev->napi_dev) + return -ENOMEM; + + priv = netdev_priv(dev->napi_dev); + *priv = dev; + strscpy(dev->napi_dev->name, "mt76u-rx", sizeof(dev->napi_dev->name)); + dev->napi_dev->threaded = 1; + netif_napi_add(dev->napi_dev, &dev->napi[MT_RXQ_MAIN], mt76u_napi_poll); + napi_enable(&dev->napi[MT_RXQ_MAIN]); + return 0; } EXPORT_SYMBOL_GPL(__mt76u_init); From a7c71a346591884eefbe5b3f52cd440671f46505 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 24 Jun 2026 19:18:26 -0500 Subject: [PATCH 0716/1433] wifi: mt76: mt792x: advertise mgmt frame registration Advertise multicast management frame registration support so userspace can subscribe to multicast management and action frames. This capability is required for NAN discovery and related operations. Co-developed-by: Stella Liu Signed-off-by: Stella Liu Co-developed-by: Jeremy Yu Signed-off-by: Jeremy Yu Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260625001834.475094-2-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt792x_core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c index 9bd679b7e889..8fc643fc0dfe 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c @@ -719,6 +719,7 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw) wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_BEACON_RATE_HE); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_ACK_SIGNAL_SUPPORT); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_CAN_REPLACE_PTK0); + wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_MULTICAST_REGISTRATIONS); ieee80211_hw_set(hw, SINGLE_SCAN_ON_ALL_BANDS); ieee80211_hw_set(hw, HAS_RATE_CONTROL); From 9080164f3b6db2ef56043440615fa6392e4098c7 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 24 Jun 2026 19:18:27 -0500 Subject: [PATCH 0717/1433] wifi: mt76: mt7925: guard BSS capability lookups mt7925 BSS setup may dereference missing channel data or query HE 6 GHz capabilities for an iftype without HE support. Guard both lookups before adding NAN paths that can use partially configured BSS state. Co-developed-by: Stella Liu Signed-off-by: Stella Liu Co-developed-by: Jeremy Yu Signed-off-by: Jeremy Yu Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260625001834.475094-3-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/mcu.c | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index bc6b6fc2d6f5..9795ac27f28f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -2400,11 +2400,18 @@ void mt7925_mcu_bss_rlm_tlv(struct sk_buff *skb, struct mt76_phy *phy, { struct cfg80211_chan_def *chandef = ctx ? &ctx->def : &link_conf->chanreq.oper; - int freq1 = chandef->center_freq1, freq2 = chandef->center_freq2; - enum nl80211_band band = chandef->chan->band; struct bss_rlm_tlv *req; + enum nl80211_band band; + int freq1, freq2; struct tlv *tlv; + if (WARN_ON_ONCE(!chandef || !chandef->chan)) + return; + + freq1 = chandef->center_freq1; + freq2 = chandef->center_freq2; + band = chandef->chan->band; + tlv = mt76_connac_mcu_add_tlv(skb, UNI_BSS_INFO_RLM, sizeof(*req)); req = (struct bss_rlm_tlv *)tlv; req->control_channel = chandef->chan->hw_value; @@ -2542,8 +2549,8 @@ mt7925_get_phy_mode_ext(struct mt76_phy *phy, struct ieee80211_vif *vif, enum nl80211_band band, struct ieee80211_link_sta *link_sta) { - struct ieee80211_he_6ghz_capa *he_6ghz_capa; - const struct ieee80211_sta_eht_cap *eht_cap; + struct ieee80211_he_6ghz_capa *he_6ghz_capa = NULL; + const struct ieee80211_sta_eht_cap *eht_cap = NULL; __le16 capa = 0; u8 mode = 0; @@ -2551,11 +2558,18 @@ mt7925_get_phy_mode_ext(struct mt76_phy *phy, struct ieee80211_vif *vif, he_6ghz_capa = &link_sta->he_6ghz_capa; eht_cap = &link_sta->eht_cap; } else { + const struct ieee80211_sta_he_cap *he_cap; struct ieee80211_supported_band *sband; sband = phy->hw->wiphy->bands[band]; - capa = ieee80211_get_he_6ghz_capa(sband, vif->type); - he_6ghz_capa = (struct ieee80211_he_6ghz_capa *)&capa; + + he_cap = (band == NL80211_BAND_6GHZ) ? + ieee80211_get_he_iftype_cap(sband, vif->type) : NULL; + + if (he_cap) { + capa = ieee80211_get_he_6ghz_capa(sband, vif->type); + he_6ghz_capa = (struct ieee80211_he_6ghz_capa *)&capa; + } eht_cap = ieee80211_get_eht_iftype_cap(sband, vif->type); } From b7ab9f780dc8f6cc9ce30333e9f131ed3419c6b3 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 24 Jun 2026 19:18:28 -0500 Subject: [PATCH 0718/1433] wifi: mt76: connac: add NAN connection type Introduce a dedicated NAN connection type for connac firmware and use it for NAN interface device, BSS and station records. Add the common NAN MCU command and event IDs used by mt7925. Co-developed-by: Stella Liu Signed-off-by: Stella Liu Co-developed-by: Jeremy Yu Signed-off-by: Jeremy Yu Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260625001834.475094-4-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt76_connac_mcu.c | 14 ++++++++++++++ .../net/wireless/mediatek/mt76/mt76_connac_mcu.h | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c index 2cc5a5b67f30..8f56f568fa0b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c @@ -465,6 +465,10 @@ void mt76_connac_mcu_sta_basic_tlv(struct mt76_dev *dev, struct sk_buff *skb, basic->conn_type = cpu_to_le32(CONNECTION_IBSS_ADHOC); basic->aid = cpu_to_le16(link_sta->sta->aid); break; + case NL80211_IFTYPE_NAN: + case NL80211_IFTYPE_NAN_DATA: + basic->conn_type = cpu_to_le32(CONNECTION_NAN); + break; default: WARN_ON(1); break; @@ -1260,6 +1264,11 @@ int mt76_connac_mcu_uni_add_dev(struct mt76_phy *phy, case NL80211_IFTYPE_ADHOC: basic_req.basic.conn_type = cpu_to_le32(CONNECTION_IBSS_ADHOC); break; + case NL80211_IFTYPE_NAN: + case NL80211_IFTYPE_NAN_DATA: + basic_req.basic.conn_type = cpu_to_le32(CONNECTION_NAN); + basic_req.basic.conn_state = !enable; + break; default: WARN_ON(1); break; @@ -1670,6 +1679,11 @@ int mt76_connac_mcu_uni_add_bss(struct mt76_phy *phy, case NL80211_IFTYPE_ADHOC: basic_req.basic.conn_type = cpu_to_le32(CONNECTION_IBSS_ADHOC); break; + case NL80211_IFTYPE_NAN: + case NL80211_IFTYPE_NAN_DATA: + basic_req.basic.conn_type = cpu_to_le32(CONNECTION_NAN); + basic_req.basic.active = enable; + break; default: WARN_ON(1); break; diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h index 6f554d78da39..8022ffd7af5a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h @@ -901,6 +901,7 @@ enum { #define NETWORK_P2P BIT(17) #define NETWORK_IBSS BIT(18) #define NETWORK_WDS BIT(21) +#define NETWORK_NAN BIT(22) #define SCAN_FUNC_RANDOM_MAC BIT(0) #define SCAN_FUNC_RNR_SCAN BIT(3) @@ -913,6 +914,7 @@ enum { #define CONNECTION_IBSS_ADHOC (STA_TYPE_ADHOC | NETWORK_IBSS) #define CONNECTION_WDS (STA_TYPE_WDS | NETWORK_WDS) #define CONNECTION_INFRA_BC (STA_TYPE_BC | NETWORK_INFRA) +#define CONNECTION_NAN (NETWORK_NAN) #define CONN_STATE_DISCONNECT 0 #define CONN_STATE_CONNECT 1 @@ -1099,6 +1101,7 @@ enum { MCU_UNI_EVENT_THERMAL = 0x35, MCU_UNI_EVENT_RSSI_MONITOR = 0x41, MCU_UNI_EVENT_NIC_CAPAB = 0x43, + MCU_UNI_EVENT_NAN = 0x56, MCU_UNI_EVENT_WED_RRO = 0x57, MCU_UNI_EVENT_PER_STA_INFO = 0x6d, MCU_UNI_EVENT_ALL_STA_INFO = 0x6e, @@ -1345,6 +1348,7 @@ enum { MCU_UNI_CMD_FIXED_RATE_TABLE = 0x40, MCU_UNI_CMD_RSSI_MONITOR = 0x41, MCU_UNI_CMD_TESTMODE_CTRL = 0x46, + MCU_UNI_CMD_NAN = 0x56, MCU_UNI_CMD_RRO = 0x57, MCU_UNI_CMD_OFFCH_SCAN_CTRL = 0x58, MCU_UNI_CMD_PER_STA_INFO = 0x6d, From a5487a6824068cba6fab02f3e5186940a59275ed Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 24 Jun 2026 19:18:29 -0500 Subject: [PATCH 0719/1433] wifi: mt76: mt7925: add NAN MCU helpers Add the mt7925 NAN MCU ABI and helpers for enable, disable, configuration updates, availability updates and peer schedule commands. Upper-layer integration is added by later patches. Co-developed-by: Stella Liu Signed-off-by: Stella Liu Co-developed-by: Jeremy Yu Signed-off-by: Jeremy Yu Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260625001834.475094-5-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt7925/Makefile | 2 +- .../net/wireless/mediatek/mt76/mt7925/nan.c | 927 ++++++++++++++++++ .../net/wireless/mediatek/mt76/mt7925/nan.h | 419 ++++++++ .../net/wireless/mediatek/mt76/mt7925/regd.c | 30 + .../net/wireless/mediatek/mt76/mt7925/regd.h | 3 + drivers/net/wireless/mediatek/mt76/mt792x.h | 38 + 6 files changed, 1418 insertions(+), 1 deletion(-) create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/nan.c create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/nan.h diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/Makefile b/drivers/net/wireless/mediatek/mt76/mt7925/Makefile index 8f1078ce3231..f9dcc0bba393 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/Makefile +++ b/drivers/net/wireless/mediatek/mt76/mt7925/Makefile @@ -4,7 +4,7 @@ obj-$(CONFIG_MT7925_COMMON) += mt7925-common.o obj-$(CONFIG_MT7925E) += mt7925e.o obj-$(CONFIG_MT7925U) += mt7925u.o -mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o +mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o nan.o mt7925-common-$(CONFIG_NL80211_TESTMODE) += testmode.o mt7925e-y := pci.o pci_mac.o pci_mcu.o mt7925u-y := usb.o diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c new file mode 100644 index 000000000000..74db344a6796 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c @@ -0,0 +1,927 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear +/* Copyright (C) 2025-2026 MediaTek Inc. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mt7925.h" +#include "mcu.h" +#include "nan.h" +#include "regd.h" + +static void mt7925_nan_set_5g_channel(struct mt792x_dev *dev, + struct mt7925_nan_enable_req_tlv *req, + struct cfg80211_nan_conf *conf) +{ + struct ieee80211_channel *chan; + u32 ch5g = 0; + + chan = conf->band_cfgs[NL80211_BAND_5GHZ].chan; + + if (!chan) + return; + + if (!mt7925_regd_is_valid_channel(dev, NL80211_BAND_5GHZ, chan)) + return; + + req->config_5g_channel = 1; + + if (chan->hw_value == NAN_5G_LOW_DISC_CHANNEL) + ch5g |= BIT(0); + else if (chan->hw_value == NAN_5G_HIGH_DISC_CHANNEL) + ch5g |= BIT(1); + + req->channel_5g_val = cpu_to_le32(ch5g); +} + +static void mt7925_nan_set_cluster_id(struct mt7925_nan_enable_req_tlv *req, + const u8 *cluster_id) +{ + if (!cluster_id) + return; + + req->cluster_high = cpu_to_le16(cluster_id[4] | cluster_id[5] << 8); + req->cluster_low = cpu_to_le16((u16)cluster_id[3]); +} + +static void mt7925_nan_set_dw_interval(struct mt7925_nan_enable_req_tlv *req, + struct cfg80211_nan_conf *conf) +{ + if (conf->band_cfgs[NL80211_BAND_2GHZ].awake_dw_interval > 0) { + req->config_dw.config_2dot4g_dw_band = 1; + req->config_dw.dw_2dot4g_interval_val = + cpu_to_le32(conf->band_cfgs[NL80211_BAND_2GHZ].awake_dw_interval); + } + + if (conf->band_cfgs[NL80211_BAND_5GHZ].awake_dw_interval > 0) { + req->config_dw.config_5g_dw_band = 1; + req->config_dw.dw_5g_interval_val = + cpu_to_le32(conf->band_cfgs[NL80211_BAND_5GHZ].awake_dw_interval); + } +} + +static void mt7925_nan_set_disc_beacon(struct mt7925_nan_enable_req_tlv *req, + struct cfg80211_nan_conf *conf) +{ + if (conf->discovery_beacon_interval > 0) { + req->config_2dot4g_beacons = true; + req->beacon_2dot4g_val = conf->discovery_beacon_interval; + } +} + +static void mt7925_nan_set_rssi_thresholds(struct mt7925_nan_enable_req_tlv *req, + struct cfg80211_nan_conf *conf) +{ + if (conf->band_cfgs[NL80211_BAND_2GHZ].chan) { + req->config_2dot4g_rssi_close = 1; + req->rssi_close_2dot4g_val = + abs(conf->band_cfgs[NL80211_BAND_2GHZ].rssi_close); + req->config_2dot4g_rssi_middle = 1; + req->rssi_middle_2dot4g_val = + abs(conf->band_cfgs[NL80211_BAND_2GHZ].rssi_middle); + } + + if (conf->band_cfgs[NL80211_BAND_5GHZ].chan) { + req->config_5g_rssi_close = 1; + req->rssi_close_5g_val = + abs(conf->band_cfgs[NL80211_BAND_5GHZ].rssi_close); + req->config_5g_rssi_middle = 1; + req->rssi_middle_5g_val = + abs(conf->band_cfgs[NL80211_BAND_5GHZ].rssi_middle); + } +} + +static void mt7925_nan_set_scan_params(struct mt7925_nan_enable_req_tlv *req, + struct cfg80211_nan_conf *conf) +{ + req->scan_params_val.scan_period[0] = + cpu_to_le16(conf->scan_period < 255 ? conf->scan_period : 255); + req->scan_params_val.dwell_time[0] = + conf->scan_dwell_time < 255 ? conf->scan_dwell_time : 255; +} + +static u16 +mt7925_nan_avail_attr_ctrl(const struct ieee80211_nan_sched_cfg *sched) +{ + if (sched->avail_blob_len < NAN_AVAIL_ATTR_CTRL_OFFSET + 2) + return 0; + + return sched->avail_blob[NAN_AVAIL_ATTR_CTRL_OFFSET] | + sched->avail_blob[NAN_AVAIL_ATTR_CTRL_OFFSET + 1] << 8; +} + +static void +mt7925_nan_update_conf(struct mt792x_vif *mvif, + const struct cfg80211_nan_conf *conf) +{ + mvif->nan.conf.master_pref = conf->master_pref; + mvif->nan.conf.bands = conf->bands; + mvif->nan.conf.discovery_beacon_interval = + conf->discovery_beacon_interval; + mvif->nan.conf.enable_dw_notification = + conf->enable_dw_notification; + + memcpy(mvif->nan.conf.cluster_id, conf->cluster_id, ETH_ALEN); +} + +int mt7925_nan_enable(struct ieee80211_vif *vif, + struct mt792x_dev *dev, + struct cfg80211_nan_conf *conf) +{ + struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; + struct mt76_dev *mdev = &dev->mt76; + struct { + u8 rsv[4]; + struct mt7925_nan_enable_req_tlv nan_req_tlv; + } nan_cmd = { + .rsv = { 0 }, + .nan_req_tlv = { + .tag = cpu_to_le16(NAN_UNI_CMD_ENABLE_REQUEST), + .len = cpu_to_le16(sizeof(struct mt7925_nan_enable_req_tlv)), + .config_random_factor_force = 0, + .random_factor_force_val = 0, + .config_hop_count_force = 0, + .hop_count_force_val = 0, + }, + }; + struct mt7925_nan_enable_req_tlv *p_nan_req_tlv = &nan_cmd.nan_req_tlv; + + if (!vif || !dev || !conf) + return -EINVAL; + + p_nan_req_tlv->master_pref = conf->master_pref; + + mt7925_nan_set_5g_channel(dev, p_nan_req_tlv, conf); + mt7925_nan_set_cluster_id(p_nan_req_tlv, conf->cluster_id); + mt7925_nan_set_dw_interval(p_nan_req_tlv, conf); + mt7925_nan_set_disc_beacon(p_nan_req_tlv, conf); + mt7925_nan_set_rssi_thresholds(p_nan_req_tlv, conf); + mt7925_nan_set_scan_params(p_nan_req_tlv, conf); + + mt7925_nan_update_conf(mvif, conf); + + return mt76_mcu_send_msg(mdev, MCU_UNI_CMD(NAN), &nan_cmd, sizeof(nan_cmd), true); +} + +int mt7925_nan_disable(struct ieee80211_vif *vif, struct mt792x_dev *dev) +{ + struct mt76_dev *mdev = &dev->mt76; + struct { + u8 rsv[4]; + struct tlv nan_dis_tlv; + } nan_cmd = { + .rsv = { 0 }, + .nan_dis_tlv = { + .tag = cpu_to_le16(NAN_UNI_CMD_DISABLE_REQUEST), + .len = cpu_to_le16(sizeof(struct tlv)), + }, + }; + + if (!dev) + return -EINVAL; + + return mt76_mcu_send_msg(mdev, MCU_UNI_CMD(NAN), &nan_cmd, sizeof(nan_cmd), true); +} + +static int +mt7925_nan_mp_tlv(struct sk_buff *skb, u8 master_pref) +{ + struct mt7925_nan_master_preference_tlv *mp_tlv = NULL; + struct tlv *tlv = NULL; + + if (!skb) + return -EINVAL; + + tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_SET_MASTER_PREFERENCE, + sizeof(struct mt7925_nan_master_preference_tlv)); + if (!tlv) + return -ENOMEM; + + mp_tlv = (struct mt7925_nan_master_preference_tlv *)tlv; + + if (master_pref > NAN_MAX_MASTER_PREFERENCE) + return 0; + + mp_tlv->master_preference = master_pref; + + return 0; +} + +static int +mt7925_nan_dw_tlv(struct sk_buff *skb, struct cfg80211_nan_conf *conf) +{ + struct mt7925_nan_dw_interval_tlv *dw_tlv = NULL; + struct tlv *tlv = NULL; + u16 interval; + + if (!skb || !conf) + return -EINVAL; + + tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_SET_DW_INTERVAL, + sizeof(struct mt7925_nan_dw_interval_tlv)); + + if (!tlv) + return -ENOMEM; + + dw_tlv = (struct mt7925_nan_dw_interval_tlv *)tlv; + + /* Set DW interval for 2.4GHz and 5GHz bands if available */ + if (conf->band_cfgs[NL80211_BAND_2GHZ].awake_dw_interval > 0) { + dw_tlv->dw_interval = conf->band_cfgs[NL80211_BAND_2GHZ].awake_dw_interval; + } else if (conf->band_cfgs[NL80211_BAND_5GHZ].awake_dw_interval > 0) { + dw_tlv->dw_interval = conf->band_cfgs[NL80211_BAND_5GHZ].awake_dw_interval; + } else { + /* Fallback to a default value or log a warning */ + dw_tlv->dw_interval = NAN_DEFAULT_DW_INTERVAL; + } + + /* Validate and set NAN Discovery Beacon Interval */ + interval = conf->discovery_beacon_interval > 0 ? + conf->discovery_beacon_interval : + NAN_DEFAULT_DISC_BCN_INTERVAL; + + dw_tlv->disc_bcn_interval = cpu_to_le16(interval); + + return 0; +} + +static int +mt7925_nan_cluster_id_tlv(struct sk_buff *skb, const u8 *cluster_id) +{ + struct mt7925_nan_cluster_id_tlv *cluster_tlv = NULL; + struct tlv *tlv = NULL; + + if (!skb || !cluster_id) + return -EINVAL; + + tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_SET_CLUSTER_ID, + sizeof(struct mt7925_nan_cluster_id_tlv)); + + if (!tlv) + return -ENOMEM; + + cluster_tlv = (struct mt7925_nan_cluster_id_tlv *)tlv; + + memcpy(cluster_tlv->cluster_id, cluster_id, ETH_ALEN); + + return 0; +} + +static int +mt7925_nan_sync_rssi_tlv(struct sk_buff *skb, struct cfg80211_nan_conf *conf) +{ + struct mt7925_nan_sync_rssi_tlv *rssi_tlv = NULL; + struct tlv *tlv = NULL; + + if (!skb || !conf) + return -EINVAL; + + tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_SET_SYNC_RSSI, + sizeof(struct mt7925_nan_sync_rssi_tlv)); + + if (!tlv) + return -ENOMEM; + + rssi_tlv = (struct mt7925_nan_sync_rssi_tlv *)tlv; + + if (conf->band_cfgs[NL80211_BAND_2GHZ].chan) { + rssi_tlv->rssi_close_2g = + conf->band_cfgs[NL80211_BAND_2GHZ].rssi_close; + rssi_tlv->rssi_middle_2g = + conf->band_cfgs[NL80211_BAND_2GHZ].rssi_middle; + } + + if (conf->band_cfgs[NL80211_BAND_5GHZ].chan) { + rssi_tlv->rssi_close_5g = + conf->band_cfgs[NL80211_BAND_5GHZ].rssi_close; + rssi_tlv->rssi_middle_5g = + conf->band_cfgs[NL80211_BAND_5GHZ].rssi_middle; + } + + return 0; +} + +int mt7925_nan_change_configure(struct ieee80211_vif *vif, + struct mt792x_dev *dev, + struct cfg80211_nan_conf *conf) +{ + struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; + struct mt7925_nan_common_hdr *hdr = NULL; + struct mt76_dev *mdev = &dev->mt76; + struct sk_buff *skb = NULL; + + if (!vif || !dev || !conf) + return -EINVAL; + + skb = mt76_mcu_msg_alloc(mdev, NULL, MT7925_NAN_CONF_MAX_SIZE); + if (!skb) + return -ENOMEM; + + hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr)); + memset(hdr, 0, sizeof(*hdr)); + + if (mt7925_nan_mp_tlv(skb, conf->master_pref) || + mt7925_nan_dw_tlv(skb, conf) || + mt7925_nan_cluster_id_tlv(skb, conf->cluster_id) || + mt7925_nan_sync_rssi_tlv(skb, conf)) { + dev_kfree_skb(skb); + return -ENOMEM; + } + + mt7925_nan_update_conf(mvif, conf); + + return mt76_mcu_skb_send_msg(mdev, skb, + MCU_UNI_CMD(NAN), true); +} + +static void +mt7925_nan_handle_dw_ind(struct mt792x_dev *dev, struct tlv *tlv) +{ + struct ieee80211_channel *chan; + struct nan_rpt_dw_evt *evt; + struct wireless_dev *wdev; + u16 len, channel, dw_num; + struct mt792x_vif *mvif; + enum nl80211_band band; + int freq; + + if (!dev || !tlv) + return; + + len = le16_to_cpu(tlv->len); + if (len < sizeof(*tlv) + sizeof(*evt)) { + dev_warn(dev->mt76.dev, + "nan: short dw event tlv len=%u\n", len); + return; + } + + if (!dev->nan_vif || !ieee80211_vif_nan_started(dev->nan_vif)) + return; + + wdev = ieee80211_vif_to_wdev(dev->nan_vif); + if (!wdev) + return; + + mvif = (struct mt792x_vif *)dev->nan_vif->drv_priv; + if (!mvif->nan.conf.enable_dw_notification) + return; + + evt = (struct nan_rpt_dw_evt *)tlv->data; + channel = le16_to_cpu(evt->channel); + dw_num = le16_to_cpu(evt->dw_num); + + band = channel > 13 ? NL80211_BAND_5GHZ : NL80211_BAND_2GHZ; + freq = ieee80211_channel_to_frequency(channel, band); + chan = ieee80211_get_channel(dev->mt76.hw->wiphy, freq); + if (!chan) { + dev_dbg(dev->mt76.dev, + "nan: no channel for dw end event ch=%u dw=%u\n", + channel, dw_num); + return; + } + + cfg80211_next_nan_dw_notif(wdev, chan, GFP_KERNEL); +} + +static void +mt7925_nan_mcu_handle_de_event(struct mt792x_dev *dev, struct tlv *tlv) +{ + u8 cluster_id[ETH_ALEN] __aligned(2) = {0x50, 0x6f, 0x9a, 0x01, 0x00, 0x00}; + struct mt7925_nan_de_event *de_evt = NULL; + u16 len; + + if (!dev || !tlv) { + if (dev) + dev_warn(dev->mt76.dev, "nan: failed to parse TLV\n"); + return; + } + + len = le16_to_cpu(tlv->len); + if (len < sizeof(*tlv) + sizeof(*de_evt)) { + dev_warn(dev->mt76.dev, + "nan: short de_event tlv len=%u\n", len); + return; + } + + de_evt = (struct mt7925_nan_de_event *)tlv->data; + if (!de_evt) { + dev_warn(dev->mt76.dev, "nan: missing DE event payload\n"); + return; + } + + if (de_evt->event_type == NAN_EVENT_ID_DISC_MAC_ADDR) + return; + + memcpy(cluster_id, de_evt->cluster_id, ETH_ALEN); + + dev_dbg(dev->mt76.dev, "nan: evt=%u cluster=%pM\n", + de_evt->event_type, de_evt->cluster_id); + + if (de_evt->event_type != NAN_EVENT_ID_JOINED_CLUSTER) + return; + + if (!ieee80211_vif_nan_started(dev->nan_vif)) { + dev_warn(dev->mt76.dev, "nan: joined-cluster event but NAN not started\n"); + return; + } + + dev_dbg(dev->mt76.dev, "nan: anchor_master_rank=%*phN\n", + NAN_ANCHOR_MASTER_RANK_NUM, de_evt->anchor_master_rank); + + dev_dbg(dev->mt76.dev, "nan: own_nmi=%pM master_nmi=%pM\n", + de_evt->own_nmi, de_evt->master_nmi); + + ieee80211_nan_cluster_joined(dev->nan_vif, cluster_id, true, GFP_KERNEL); +} + +void mt7925_nan_mcu_event(struct mt792x_dev *dev, struct sk_buff *skb) +{ + struct tlv *tlv; + u32 tlv_len; + + if (!dev || !skb) + return; + + if (skb->len < sizeof(struct mt7925_mcu_rxd) + 4) + return; + + skb_pull(skb, sizeof(struct mt7925_mcu_rxd) + 4); + tlv = (struct tlv *)skb->data; + tlv_len = skb->len; + + while (tlv_len >= sizeof(*tlv)) { + u16 len = le16_to_cpu(tlv->len); + + if (len < sizeof(*tlv) || len > tlv_len) + break; + + switch (le16_to_cpu(tlv->tag)) { + case NAN_UNI_EVENT_ID_DE_EVENT_IND: + mt7925_nan_mcu_handle_de_event(dev, tlv); + break; + case NAN_UNI_EVENT_REPORT_DW_END: + mt7925_nan_handle_dw_ind(dev, tlv); + break; + default: + break; + } + + tlv_len -= len; + tlv = (struct tlv *)((u8 *)tlv + len); + } +} + +static int mt7925_nan_avail_ctrl_tlv(struct sk_buff *skb, + struct ieee80211_vif *vif) +{ + struct mt7925_nan_avail_ctrl_tlv *avail_ctrl_tlv; + struct ieee80211_nan_sched_cfg *sched; + struct tlv *tlv; + u8 seq_id = 0; + u16 ctrl = 0; + + if (!skb || !vif) + return -EINVAL; + + tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_UPDATE_AVAILABILITY_CTRL, + sizeof(struct mt7925_nan_avail_ctrl_tlv)); + + if (!tlv) + return -ENOMEM; + + sched = &vif->cfg.nan_sched; + + ctrl = mt7925_nan_avail_attr_ctrl(sched); + if (sched->avail_blob_len >= NAN_AVAIL_ATTR_CTRL_OFFSET + 2) + seq_id = sched->avail_blob[NAN_AVAIL_SEQ_ID_OFFSET]; + + avail_ctrl_tlv = (struct mt7925_nan_avail_ctrl_tlv *)tlv; + avail_ctrl_tlv->avail_ctrl = + cpu_to_le16(ctrl & NAN_AVAIL_CTRL_CHECK_FOR_CHANGED); + avail_ctrl_tlv->seq_id = seq_id; + + return 0; +} + +static u32 mt7925_nan_slot_to_bitmap(struct ieee80211_vif *vif, + struct mt7925_nan_ch_timeline *ch_list) +{ + struct ieee80211_nan_channel **slots = vif->cfg.nan_sched.schedule; + struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; + u32 num_channels = 0; + u32 i, j; + + for (i = 0; i < ARRAY_SIZE(mvif->nan.local_sched); i++) { + struct cfg80211_chan_def *slot_chan = &mvif->nan.local_sched[i]; + struct ieee80211_nan_channel *slot = slots[i]; + bool is_found = false; + + if (slot && !IS_ERR(slot) && slot->chanctx_conf) { + *slot_chan = slot->chanctx_conf->def; + } else { + memset(slot_chan, 0, sizeof(*slot_chan)); + continue; + } + + for (j = 0; j < num_channels; j++) { + u32 raw = le32_to_cpu(ch_list[j].ch_info); + + if (FIELD_GET(NAN_CH_CTRL_PRIMARY_CH, raw) == + slot_chan->chan->hw_value) { + u32 map = le32_to_cpu(ch_list[j].avail_map[0]); + + ch_list[j].avail_map[0] = cpu_to_le32(map | BIT(i)); + le32_add_cpu(&ch_list[j].num, 1); + is_found = true; + break; + } + } + + if (!is_found && num_channels < NAN_TIMELINE_MGMT_CHNL_LIST_NUM) { + ch_list[num_channels].ch_info = + cpu_to_le32(FIELD_PREP(NAN_CH_CTRL_OP_CLASS, + slot->channel_entry[0]) | + FIELD_PREP(NAN_CH_CTRL_PRIMARY_CH, + slot_chan->chan->hw_value)); + ch_list[num_channels].avail_map[0] = cpu_to_le32(BIT(i)); + le32_add_cpu(&ch_list[num_channels].num, 1); + ch_list[num_channels].is_valid++; + num_channels++; + } + } + + return num_channels; +} + +static int mt7925_nan_avail_tlv(struct sk_buff *skb, + struct ieee80211_vif *vif) +{ + struct mt7925_nan_avail_entry_tlv *avail_tlv; + struct ieee80211_nan_sched_cfg *sched; + struct tlv *tlv; + u16 ctrl = 0; + + if (!skb || !vif) + return -EINVAL; + + tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_UPDATE_AVAILABILITY, + sizeof(struct mt7925_nan_avail_entry_tlv)); + + if (!tlv) + return -ENOMEM; + + sched = &vif->cfg.nan_sched; + + ctrl = mt7925_nan_avail_attr_ctrl(sched); + + avail_tlv = (struct mt7925_nan_avail_entry_tlv *)tlv; + avail_tlv->map_id = ctrl & NAN_AVAIL_CTRL_MAPID; + avail_tlv->is_cond_avail = false; + avail_tlv->timeline_idx = 0; + + mt7925_nan_slot_to_bitmap(vif, avail_tlv->ch_list); + + avail_tlv->is_multi_map = false; + + return 0; +} + +void mt7925_nan_local_sched_changed(struct mt792x_dev *dev, + struct ieee80211_vif *vif) +{ + struct mt7925_nan_common_hdr *hdr; + struct mt76_dev *mdev; + struct sk_buff *skb; + + if (!dev || !vif) + return; + + mdev = &dev->mt76; + + skb = mt76_mcu_msg_alloc(mdev, NULL, MT7925_NAN_AVAIL_MAX_SIZE); + if (!skb) + return; + + hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr)); + memset(hdr, 0, sizeof(*hdr)); + + if (mt7925_nan_avail_ctrl_tlv(skb, vif) || + mt7925_nan_avail_tlv(skb, vif)) { + dev_kfree_skb(skb); + return; + } + + mt76_mcu_skb_send_msg(mdev, skb, + MCU_UNI_CMD(NAN), true); +} + +static int mt7925_nan_peer_rec_tlv(struct sk_buff *skb, + struct ieee80211_sta *sta, + struct mt792x_sta *msta, + u8 is_activate) +{ + struct mt7925_nan_sched_manage_peer_rec_tlv *peer_rec_tlv; + struct tlv *tlv; + + if (!skb || !sta || !msta) + return -EINVAL; + + tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_MANAGE_PEER_SCH_RECORD, + sizeof(struct mt7925_nan_sched_manage_peer_rec_tlv)); + + if (!tlv) + return -ENOMEM; + + peer_rec_tlv = (struct mt7925_nan_sched_manage_peer_rec_tlv *)tlv; + peer_rec_tlv->sch_idx = cpu_to_le32(msta->nan_sched.sch_idx); + peer_rec_tlv->is_activate = is_activate; + memcpy(peer_rec_tlv->nmi_addr, sta->addr, ETH_ALEN); + + return 0; +} + +static int mt7925_nan_peer_cap_tlv(struct sk_buff *skb, + struct ieee80211_sta *sta, + struct mt792x_sta *msta) +{ + struct mt7925_nan_sched_update_peer_cap_tlv *peer_cap_tlv; + struct ieee80211_nan_peer_sched *sched; + enum nl80211_band band; + struct tlv *tlv; + u16 primary_ch; + u32 i; + + if (!skb || !sta || !msta) + return -EINVAL; + + sched = sta->nan_sched; + if (!sched) + return -EINVAL; + + tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_UPDATE_PEER_CAPABILITY, + sizeof(struct mt7925_nan_sched_update_peer_cap_tlv)); + + if (!tlv) + return -ENOMEM; + + peer_cap_tlv = (struct mt7925_nan_sched_update_peer_cap_tlv *)tlv; + peer_cap_tlv->sch_idx = cpu_to_le32(msta->nan_sched.sch_idx); + peer_cap_tlv->supported_bands = BIT(NAN_SUPPORTED_BAND_ID_2P4G); + peer_cap_tlv->max_chnl_switch_time = cpu_to_le16(sched->max_chan_switch); + + for (i = 0; i < sched->n_channels; i++) { + if (!sched->channels[i].chanctx_conf) + continue; + + band = sched->channels[i].chanctx_conf->def.chan->band; + primary_ch = + sched->channels[i].chanctx_conf->def.chan->hw_value; + + if (band == NL80211_BAND_2GHZ) + peer_cap_tlv->peer_supported_bands |= + BIT(NAN_SUPPORTED_BN_2G); + else if (primary_ch >= UNII1_LOWER_BOUND && + primary_ch <= UNII1_UPPER_BOUND) + peer_cap_tlv->peer_supported_bands |= + BIT(NAN_SUPPORTED_BN_5G_LOW); + else if (primary_ch >= UNII3_LOWER_BOUND && + primary_ch <= UNII3_UPPER_BOUND) + peer_cap_tlv->peer_supported_bands |= + BIT(NAN_SUPPORTED_BN_5G_HIGH); + } + + return 0; +} + +static void +mt7925_nan_fill_crb_committed(struct mt7925_nan_sched_update_crb_tlv *crb_tlv, + struct ieee80211_nan_peer_sched *sched) +{ + u32 m, slot; + + if (!sched) + return; + + for (m = 0; m < CFG80211_NAN_MAX_PEER_MAPS && + m < NAN_TIMELINE_MGMT_SIZE; m++) { + struct mt7925_nan_sched_timeline *tl = + &crb_tlv->comm_faw_timeline[m]; + struct ieee80211_nan_peer_map *map = &sched->maps[m]; + + if (map->map_id == CFG80211_NAN_INVALID_MAP_ID) + continue; + + tl->map_id = map->map_id; + + /* + * Convert peer schedule slots to FW avail_map bitmap. + * Each bit in avail_map[0] represents one time slot where + * the peer has committed availability. + */ + for (slot = 0; slot < CFG80211_NAN_SCHED_NUM_TIME_SLOTS; + slot++) { + struct ieee80211_nan_channel *ch = map->slots[slot]; + + if (!ch || !ch->chanctx_conf) + continue; + + tl->avail_map[0] |= cpu_to_le32(BIT(slot)); + } + } +} + +static int mt7925_nan_update_crb_tlv(struct sk_buff *skb, + struct ieee80211_sta *sta, + struct mt792x_sta *msta) +{ + struct mt7925_nan_sched_update_crb_tlv *crb_tlv; + struct tlv *tlv; + + if (!skb || !sta || !msta) + return -EINVAL; + + tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_UPDATE_CRB, + sizeof(struct mt7925_nan_sched_update_crb_tlv)); + + if (!tlv) + return -ENOMEM; + + crb_tlv = (struct mt7925_nan_sched_update_crb_tlv *)tlv; + crb_tlv->sch_idx = cpu_to_le32(msta->nan_sched.sch_idx); + crb_tlv->flags = NAN_CRB_USE_DATA_PATH; + crb_tlv->is_use_ranging = false; + crb_tlv->comm_ndc_ctrl.is_valid = false; + + mt7925_nan_fill_crb_committed(crb_tlv, sta->nan_sched); + + return 0; +} + +int mt792x_nan_set_peer_schedule(struct mt792x_dev *dev, + struct ieee80211_sta *sta) +{ + struct mt7925_nan_common_hdr *hdr; + struct mt792x_sta *msta; + struct mt792x_nan *nan; + struct mt76_dev *mdev; + struct sk_buff *skb; + + if (!dev || !sta) + return -EINVAL; + + mdev = &dev->mt76; + + skb = mt76_mcu_msg_alloc(mdev, NULL, MT7925_NAN_PEER_MAX_SIZE); + if (!skb) + return -ENOMEM; + + hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr)); + memset(hdr, 0, sizeof(*hdr)); + + msta = (struct mt792x_sta *)sta->drv_priv; + nan = &msta->vif->nan; + + /* Allocate connection index on first call for this peer */ + if (!msta->nan_sched.idx_assigned) { + int idx = find_first_zero_bit(&nan->conn_bitmap, + NAN_MAX_CONN_CFG); + if (idx >= NAN_MAX_CONN_CFG) { + dev_kfree_skb(skb); + return -ENOSPC; + } + + set_bit(idx, &nan->conn_bitmap); + msta->nan_sched.sch_idx = idx; + msta->nan_sched.idx_assigned = true; + + if (mt7925_nan_peer_rec_tlv(skb, sta, msta, true) || + mt7925_nan_peer_cap_tlv(skb, sta, msta)) { + dev_kfree_skb(skb); + return -ENOMEM; + } + } + + if (mt7925_nan_update_crb_tlv(skb, sta, msta)) { + dev_kfree_skb(skb); + return -ENOMEM; + } + + return mt76_mcu_skb_send_msg(mdev, skb, + MCU_UNI_CMD(NAN), true); +} + +int mt792x_nan_set_peer_rec(struct mt76_dev *mdev, + struct ieee80211_sta *sta) +{ + struct mt7925_nan_common_hdr *hdr; + struct mt792x_sta *msta; + struct mt792x_nan *nan; + struct sk_buff *skb; + + if (!mdev || !sta) + return -EINVAL; + + skb = mt76_mcu_msg_alloc(mdev, NULL, + sizeof(struct mt7925_nan_common_hdr) + + sizeof(struct mt7925_nan_sched_manage_peer_rec_tlv)); + if (!skb) + return -ENOMEM; + + hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr)); + memset(hdr, 0, sizeof(*hdr)); + + msta = (struct mt792x_sta *)sta->drv_priv; + nan = &msta->vif->nan; + + if (!msta->nan_sched.idx_assigned) { + dev_kfree_skb(skb); + return 0; + } + + if (mt7925_nan_peer_rec_tlv(skb, sta, msta, false)) { + dev_kfree_skb(skb); + return -ENOMEM; + } + + clear_bit(msta->nan_sched.sch_idx, &nan->conn_bitmap); + msta->nan_sched.idx_assigned = false; + + return mt76_mcu_skb_send_msg(mdev, skb, + MCU_UNI_CMD(NAN), true); +} + +int mt792x_nan_map_sta_rec(struct mt76_dev *mdev, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mt7925_nan_sched_map_sta_rec_tlv *map_tlv; + struct mt7925_nan_common_hdr *hdr; + struct ieee80211_sta *nmi_sta; + struct mt792x_sta *nmi_msta; + struct mt792x_sta *msta; + u8 nmi_addr[ETH_ALEN]; + struct sk_buff *skb; + int ndp_ctx_id = 0; + struct tlv *tlv; + + if (!mdev || !vif || !sta) + return -EINVAL; + + msta = (struct mt792x_sta *)sta->drv_priv; + + rcu_read_lock(); + nmi_sta = rcu_dereference(sta->nmi); + if (!nmi_sta) { + rcu_read_unlock(); + dev_err(mdev->dev, "NAN: NMI sta not found for NDI sta %pM\n", + sta->addr); + return -EINVAL; + } + + memcpy(nmi_addr, nmi_sta->addr, ETH_ALEN); + nmi_msta = (struct mt792x_sta *)nmi_sta->drv_priv; + + ndp_ctx_id = find_first_zero_bit(&nmi_msta->nan_sched.ndp_ctx_bitmap, + NAN_MAX_NDP_CXT); + if (ndp_ctx_id < NAN_MAX_NDP_CXT) + set_bit(ndp_ctx_id, &nmi_msta->nan_sched.ndp_ctx_bitmap); + else + ndp_ctx_id = 0; + rcu_read_unlock(); + + msta->nan_sched.ndp_ctx_id = ndp_ctx_id; + + skb = mt76_mcu_msg_alloc(mdev, NULL, + sizeof(struct mt7925_nan_common_hdr) + + sizeof(struct mt7925_nan_sched_map_sta_rec_tlv)); + if (!skb) + return -ENOMEM; + + hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr)); + memset(hdr, 0, sizeof(*hdr)); + + tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_MAP_STA_RECORD, + sizeof(struct mt7925_nan_sched_map_sta_rec_tlv)); + if (!tlv) { + dev_kfree_skb(skb); + return -ENOMEM; + } + + map_tlv = (struct mt7925_nan_sched_map_sta_rec_tlv *)tlv; + memcpy(map_tlv->nmi_addr, nmi_addr, ETH_ALEN); + map_tlv->sta_rec_idx = msta->deflink.wcid.idx; + map_tlv->ndp_ctx_id = ndp_ctx_id; + map_tlv->role_idx = 0; + memcpy(map_tlv->ndi_addr, vif->addr, ETH_ALEN); + + return mt76_mcu_skb_send_msg(mdev, skb, + MCU_UNI_CMD(NAN), true); +} diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h new file mode 100644 index 000000000000..356d9ef7f664 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h @@ -0,0 +1,419 @@ +/* SPDX-License-Identifier: BSD-3-Clause-Clear */ +/* Copyright (C) 2025-2026 MediaTek Inc. */ + +#ifndef __MT7925_NAN_H +#define __MT7925_NAN_H + +#include +#include + +#include "../mt76_connac_mcu.h" + +#define NAN_MAX_SOCIAL_CHANNELS 3 +#define NAN_ANCHOR_MASTER_RANK_NUM 8 +#define NAN_5G_LOW_DISC_CHANNEL 44 +#define NAN_5G_HIGH_DISC_CHANNEL 149 +#define NAN_MAX_MASTER_PREFERENCE 255 +#define NAN_DEFAULT_DW_INTERVAL 1 +#define NAN_DEFAULT_DISC_BCN_INTERVAL 100 +#define NAN_TOTAL_DW 16 +#define NAN_SUPPORTED_2G_FAW_CH_NUM 4 +#define NAN_SUPPORTED_5G_FAW_CH_NUM 4 +#define NAN_TIMELINE_MGMT_SIZE 2 +#define NAN_TIMELINE_MGMT_CHNL_LIST_NUM \ + ((NAN_SUPPORTED_2G_FAW_CH_NUM + \ + NAN_SUPPORTED_5G_FAW_CH_NUM) / NAN_TIMELINE_MGMT_SIZE) +#define NAN_NUM_AVAIL_DB 2 +#define NAN_NDC_ATTRIBUTE_ID_LENGTH 6 +#define NAN_MAX_CONN_CFG 8 +#define NAN_MAX_NDP_CXT 4 + +#define MT7925_NAN_CONF_MAX_SIZE \ + (sizeof(struct mt7925_nan_common_hdr) + \ + sizeof(struct mt7925_nan_master_preference_tlv) + \ + sizeof(struct mt7925_nan_dw_interval_tlv) + \ + sizeof(struct mt7925_nan_cluster_id_tlv) + \ + sizeof(struct mt7925_nan_sync_rssi_tlv)) + +#define MT7925_NAN_AVAIL_MAX_SIZE \ + (sizeof(struct mt7925_nan_common_hdr) + \ + sizeof(struct mt7925_nan_avail_ctrl_tlv) + \ + sizeof(struct mt7925_nan_avail_entry_tlv)) + +#define MT7925_NAN_PEER_MAX_SIZE \ + (sizeof(struct mt7925_nan_common_hdr) + \ + sizeof(struct mt7925_nan_sched_manage_peer_rec_tlv) + \ + sizeof(struct mt7925_nan_sched_update_peer_cap_tlv) + \ + sizeof(struct mt7925_nan_sched_update_crb_tlv)) + +/* NAN Availability Attribute */ +#define NAN_AVAIL_ATTR_ID_OFFSET 0 +#define NAN_AVAIL_ATTR_LEN_OFFSET 1 +#define NAN_AVAIL_SEQ_ID_OFFSET 3 +#define NAN_AVAIL_ATTR_CTRL_OFFSET 4 + +/* NAN Availability Attribute - Attribute Control Field */ +#define NAN_AVAIL_CTRL_MAPID GENMASK(3, 0) +#define NAN_AVAIL_CTRL_COMMIT_CHANGED BIT(4) +#define NAN_AVAIL_CTRL_POTN_CHANGED BIT(5) +#define NAN_AVAIL_CTRL_PUBLIC_AVAIL_CHANGED BIT(6) +#define NAN_AVAIL_CTRL_NDC_CHANGED BIT(7) +#define NAN_AVAIL_CTRL_CHECK_FOR_CHANGED GENMASK(7, 4) + +#define UNII1_LOWER_BOUND 36 +#define UNII1_UPPER_BOUND 50 +#define UNII3_LOWER_BOUND 149 +#define UNII3_UPPER_BOUND 165 + +enum nan_uni_cmd_tag { + NAN_UNI_CMD_SET_MASTER_PREFERENCE = 0, + NAN_UNI_CMD_ENABLE_REQUEST = 7, + NAN_UNI_CMD_DISABLE_REQUEST = 8, + NAN_UNI_CMD_UPDATE_AVAILABILITY = 9, + NAN_UNI_CMD_UPDATE_CRB = 10, + NAN_UNI_CMD_MANAGE_PEER_SCH_RECORD = 12, + NAN_UNI_CMD_MAP_STA_RECORD = 13, + NAN_UNI_CMD_UPDATE_AVAILABILITY_CTRL = 20, + NAN_UNI_CMD_UPDATE_PEER_CAPABILITY = 21, + NAN_UNI_CMD_CHANGE_NMI_ADDRESS = 24, + NAN_UNI_CMD_SET_DW_INTERVAL = 26, + NAN_UNI_CMD_SET_SYNC_RSSI = 39, + NAN_UNI_CMD_SET_CLUSTER_ID = 40, + NAN_UNI_CMD_KEY_MANAGEMENT = 53, +}; + +enum nan_uni_event_tag { + NAN_UNI_EVENT_ID_DE_EVENT_IND = 19, + NAN_UNI_EVENT_REPORT_DW_END = 60, +}; + +enum nan_disc_event_type { + NAN_EVENT_ID_DISC_MAC_ADDR = 0, + NAN_EVENT_ID_JOINED_CLUSTER = 2, +}; + +/* NAN 4.0 Table 79. Device Capability attribute format, Supported Bands */ +enum nan_supported_bands { + NAN_SUPPORTED_BAND_ID_2P4G = 2, + NAN_SUPPORTED_BAND_ID_5G = 4, + NAN_PROPRIETARY_BAND_ID_6G = 6, + NAN_SUPPORTED_BAND_ID_6G = 7, +}; + +enum nan_peer_supported_bands { + NAN_SUPPORTED_BN_2G = 0, + NAN_SUPPORTED_BN_5G_LOW, + NAN_SUPPORTED_BN_5G_HIGH, + NAN_SUPPORTED_BN_6G, + NAN_SUPPORTED_BN_NUM +}; + +#define NAN_CH_CTRL_OP_CLASS GENMASK(15, 8) +#define NAN_CH_CTRL_PRIMARY_CH GENMASK(23, 16) + +#define NAN_CRB_USE_DATA_PATH BIT(0) +#define NAN_CRB_AVAIL_6G_FORMAT GENMASK(2, 1) + +struct mt7925_nan_social_ch_scan_params { + u8 dwell_time[NAN_MAX_SOCIAL_CHANNELS]; + __le16 scan_period[NAN_MAX_SOCIAL_CHANNELS]; +} __packed; + +/* Firmware-reported NAN device information */ +struct nan_dev_info_evt { + u8 is_enabled; + u8 my_addr[ETH_ALEN]; + u8 en_fw_election; + __le32 nan_dev_role; + __le32 nan_dev_state; + u8 mst_preference; + u8 random_factor; + u8 cnt_hop; + u8 cluster_id[ETH_ALEN]; + u8 anchor_mst_addr[ETH_ALEN]; + u8 am_preference; + u8 am_random_factor; + u8 parent_mac[ETH_ALEN]; + u8 parent_am_preference; + u8 parent_am_factor; + __le32 ambtt; + __le32 tsf[2]; + u8 pn_igtk[6]; + u8 pn_bigtk[6]; +}; + +/* Firmware NAN discovery window event */ +struct nan_rpt_dw_evt { + struct nan_dev_info_evt device_info; + __le32 expected_tsf_h; + __le32 expected_tsf_l; + __le32 actual_tsf_h; + __le32 actual_tsf_l; + __le16 channel; + __le16 dw_num; +}; + +struct mt7925_nan_conf_dw { + u8 config_2dot4g_dw_band; + __le32 dw_2dot4g_interval_val; + + u8 config_5g_dw_band; + __le32 dw_5g_interval_val; +} __packed; + +struct mt7925_nan_enable_req_tlv { + __le16 tag; + __le16 len; + + u8 master_pref; + __le16 cluster_low; + __le16 cluster_high; + + u8 config_support_5g; + u8 support_5g_val; + + u8 config_sid_beacon; + u8 sid_beacon_val; + + u8 config_2dot4g_rssi_close; + u8 rssi_close_2dot4g_val; + u8 config_2dot4g_rssi_middle; + u8 rssi_middle_2dot4g_val; + + u8 config_2dot4g_rssi_proximity; + u8 rssi_proximity_2dot4g_val; + u8 config_hop_count_limit; + u8 hop_count_limit_val; + + u8 config_2dot4g_support; + u8 support_2dot4g_val; + + u8 config_2dot4g_beacons; + u8 beacon_2dot4g_val; + + u8 config_2dot4g_sdf; + u8 sdf_2dot4g_val; + + u8 config_5g_beacons; + u8 beacon_5g_val; + + u8 config_5g_sdf; + u8 sdf_5g_val; + + u8 config_5g_rssi_close; + u8 rssi_close_5g_val; + + u8 config_5g_rssi_middle; + u8 rssi_middle_5g_val; + + u8 config_5g_rssi_close_proximity; + u8 rssi_close_proximity_5g_val; + + u8 config_rssi_window_size; + u8 rssi_window_size_val; + + u8 config_oui; + __le32 oui_val; + + u8 config_intf_addr; + u8 intf_addr_val[ETH_ALEN]; + + u8 config_cluster_attribute_val; + + u8 config_scan_params; + struct mt7925_nan_social_ch_scan_params scan_params_val; + + u8 config_random_factor_force; + u8 random_factor_force_val; + + u8 config_hop_count_force; + u8 hop_count_force_val; + + u8 config_24g_channel; + __le32 channel_24g_val; + + u8 config_5g_channel; + __le32 channel_5g_val; + + struct mt7925_nan_conf_dw config_dw; + + u8 config_disc_mac_addr_randomization; + __le32 disc_mac_addr_rand_interval_sec; + + u8 discovery_indication_cfg; + + u8 config_subscribe_sid_beacon; + __le32 subscribe_sid_beacon_val; + + u8 enable_log_slot_statistics; +} __packed __aligned(4); + +struct mt7925_nan_common_hdr { + u8 reserved[4]; +}; + +struct mt7925_nan_master_preference_tlv { + __le16 tag; + __le16 len; + u8 master_preference; + u8 reserved[3]; +} __packed __aligned(4); + +struct mt7925_nan_dw_interval_tlv { + __le16 tag; + __le16 len; + u8 dw_interval; + u8 vendor_ioctl; + __le16 disc_bcn_interval; +} __packed __aligned(4); + +struct mt7925_nan_cluster_id_tlv { + __le16 tag; + __le16 len; + u8 cluster_id[ETH_ALEN]; + u8 reserved[2]; +} __packed __aligned(4); + +struct mt7925_nan_sync_rssi_tlv { + __le16 tag; + __le16 len; + s8 rssi_close_2g; + s8 rssi_middle_2g; + s8 rssi_close_5g; + s8 rssi_middle_5g; +} __packed __aligned(4); + +struct mt7925_nan_de_event { + u8 event_type; + u8 cluster_id[ETH_ALEN]; + u8 anchor_master_rank[NAN_ANCHOR_MASTER_RANK_NUM]; + u8 own_nmi[ETH_ALEN]; + u8 master_nmi[ETH_ALEN]; +}; + +struct mt7925_nan_nmi_addr_tlv { + __le16 tag; + __le16 len; + u8 nmi_addr[ETH_ALEN]; +} __packed __aligned(4); + +struct mt7925_nan_avail_ctrl_tlv { + __le16 tag; + __le16 len; + __le16 avail_ctrl; + u8 seq_id; + u8 reserved[1]; +} __packed __aligned(4); + +struct mt7925_nan_ch_timeline { + u8 is_valid; + u8 reserved[3]; + + __le32 ch_info; + + __le32 num; + __le32 avail_map[NAN_TOTAL_DW]; +}; + +struct mt7925_nan_avail_entry_tlv { + __le16 tag; + __le16 len; + u8 map_id; + u8 is_cond_avail; + u8 timeline_idx; + u8 is_multi_map; + + struct mt7925_nan_ch_timeline ch_list[NAN_TIMELINE_MGMT_CHNL_LIST_NUM]; +} __packed __aligned(4); + +struct mt7925_nan_sched_manage_peer_rec_tlv { + __le16 tag; + __le16 len; + __le32 sch_idx; + u8 is_activate; + u8 nmi_addr[ETH_ALEN]; + u8 reserved[1]; +} __packed __aligned(4); + +struct mt7925_nan_sched_update_peer_cap_tlv { + __le16 tag; + __le16 len; + __le32 sch_idx; + u8 supported_bands; + __le16 max_chnl_switch_time; + u8 peer_supported_bands; +} __packed __aligned(4); + +struct mt7925_nan_sched_timeline { + u8 map_id; + u8 local_map_id; + u8 reserved[2]; + union { + __le32 avail_map[NAN_TOTAL_DW]; + u8 avail_block[NAN_TOTAL_DW * 4]; + }; +}; + +struct mt7925_nan_sched_faw_ndc_timeline { + __le32 avail_map[NAN_TOTAL_DW]; +}; + +struct mt7925_nan_sched_ndc_ctrl { + u8 is_valid; + u8 ndc_id[NAN_NDC_ATTRIBUTE_ID_LENGTH]; + u8 ndc_idx; + struct mt7925_nan_sched_timeline timeline[NAN_NUM_AVAIL_DB]; +}; + +struct mt7925_nan_sched_update_crb_tlv { + __le16 tag; + __le16 len; + __le32 sch_idx; + u8 flags; + u8 is_use_ranging; + u8 reserved[2]; + struct mt7925_nan_sched_timeline comm_ranging_timeline[NAN_TIMELINE_MGMT_SIZE]; + struct mt7925_nan_sched_timeline comm_faw_timeline[NAN_TIMELINE_MGMT_SIZE]; + struct mt7925_nan_sched_ndc_ctrl comm_ndc_ctrl; + struct mt7925_nan_sched_faw_ndc_timeline faw_ndc_timeline[NAN_TIMELINE_MGMT_SIZE]; +} __packed __aligned(4); + +struct mt7925_nan_sched_map_sta_rec_tlv { + __le16 tag; + __le16 len; + u8 nmi_addr[ETH_ALEN]; + u8 sta_rec_idx; + u8 ndp_ctx_id; + + __le32 role_idx; + u8 ndi_addr[ETH_ALEN]; + u8 reserved[2]; +} __packed __aligned(4); + +int mt7925_nan_enable(struct ieee80211_vif *vif, + struct mt792x_dev *dev, + struct cfg80211_nan_conf *conf); + +int mt7925_nan_disable(struct ieee80211_vif *vif, + struct mt792x_dev *dev); + +int mt7925_nan_change_configure(struct ieee80211_vif *vif, + struct mt792x_dev *dev, + struct cfg80211_nan_conf *conf); + +void mt7925_nan_mcu_event(struct mt792x_dev *dev, struct sk_buff *skb); + +void mt7925_nan_local_sched_changed(struct mt792x_dev *dev, + struct ieee80211_vif *vif); + +int mt792x_nan_set_peer_schedule(struct mt792x_dev *dev, + struct ieee80211_sta *sta); + +int mt792x_nan_set_peer_rec(struct mt76_dev *mdev, + struct ieee80211_sta *sta); + +int mt792x_nan_map_sta_rec(struct mt76_dev *mdev, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta); + +#endif diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regd.c b/drivers/net/wireless/mediatek/mt76/mt7925/regd.c index 16f56ee879d4..0235437d11d5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/regd.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/regd.c @@ -217,6 +217,36 @@ mt7925_regd_is_valid_alpha2(const char *alpha2) return false; } +bool +mt7925_regd_is_valid_channel(struct mt792x_dev *dev, + enum nl80211_band band, + struct ieee80211_channel *chan) +{ + struct ieee80211_hw *hw = mt76_hw(dev); + struct wiphy *wiphy = hw->wiphy; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch; + int i; + + if (!chan) + return false; + + sband = wiphy->bands[band]; + if (!sband) + return false; + + for (i = 0; i < sband->n_channels; i++) { + ch = &sband->channels[i]; + + if (ch->hw_value == chan->hw_value && + ((ch->flags & IEEE80211_CHAN_DISABLED) == 0)) + return true; + } + + return false; +} +EXPORT_SYMBOL_GPL(mt7925_regd_is_valid_channel); + int mt7925_regd_change(struct mt792x_phy *phy, char *alpha2) { struct wiphy *wiphy = phy->mt76->hw->wiphy; diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regd.h b/drivers/net/wireless/mediatek/mt76/mt7925/regd.h index 0767f078862e..0b0754cf8ae7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/regd.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/regd.h @@ -13,6 +13,9 @@ void mt7925_regd_be_ctrl(struct mt792x_dev *dev, u8 *alpha2); void mt7925_regd_notifier(struct wiphy *wiphy, struct regulatory_request *req); bool mt7925_regd_clc_supported(struct mt792x_dev *dev); int mt7925_regd_change(struct mt792x_phy *phy, char *alpha2); +bool mt7925_regd_is_valid_channel(struct mt792x_dev *dev, + enum nl80211_band band, + struct ieee80211_channel *chan); int mt7925_regd_init(struct mt792x_phy *phy); #endif diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index 8c7ecd3ce126..337d6a100236 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -120,6 +120,18 @@ struct mt792x_link_sta { struct ieee80211_link_sta *pri_link; }; +struct mt792x_sta_nan_sched { + u16 committed_dw; + u32 sch_idx; + bool idx_assigned; + unsigned long ndp_ctx_bitmap; + u8 ndp_ctx_id; /* assigned NDP context ID (for NDI sta) */ + struct { + u8 map_id; + struct cfg80211_chan_def chans[CFG80211_NAN_SCHED_NUM_TIME_SLOTS]; + } maps[CFG80211_NAN_MAX_PEER_MAPS]; +}; + struct mt792x_sta { struct mt792x_link_sta deflink; /* must be first */ struct mt792x_link_sta __rcu *link[IEEE80211_MLD_MAX_NUM_LINKS]; @@ -128,6 +140,9 @@ struct mt792x_sta { u16 valid_links; u8 deflink_id; + + /* NAN peer schedule */ + struct mt792x_sta_nan_sched nan_sched; }; DECLARE_EWMA(rssi, 10, 8); @@ -144,6 +159,25 @@ struct mt792x_bss_conf { unsigned int link_id; }; +struct mt792x_nan_conf { + u8 master_pref; + u8 bands; + u8 cluster_id[ETH_ALEN]; + u32 discovery_beacon_interval; + bool enable_dw_notification; +}; + +struct mt792x_nan { + struct mt792x_nan_conf conf; + + /* Scheduler */ + struct cfg80211_chan_def local_sched[CFG80211_NAN_SCHED_NUM_TIME_SLOTS]; + u32 seq_id; + + /* Connection index bitmap, up to NAN_MAX_CONN_CFG peers */ + unsigned long conn_bitmap; +}; + struct mt792x_vif { struct mt792x_bss_conf bss_conf; /* must be first */ struct mt792x_bss_conf __rcu *link_conf[IEEE80211_MLD_MAX_NUM_LINKS]; @@ -158,6 +192,8 @@ struct mt792x_vif { struct work_struct csa_work; struct timer_list csa_timer; + + struct mt792x_nan nan; }; struct mt792x_phy { @@ -296,6 +332,8 @@ struct mt792x_dev { u32 backup_l2; struct ieee80211_chanctx_conf *new_ctx; + + struct ieee80211_vif *nan_vif; }; static inline struct mt792x_bss_conf * From 9824fb4edbf39468f9fa2f1f8c146aca74529c80 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 24 Jun 2026 19:18:30 -0500 Subject: [PATCH 0720/1433] wifi: mt76: mt7925: add NAN MCU handling Route NAN MCU responses and unsolicited events through the mt7925 MCU path, and handle NAN-specific BSS and station TLVs. Co-developed-by: Stella Liu Signed-off-by: Stella Liu Co-developed-by: Jeremy Yu Signed-off-by: Jeremy Yu Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260625001834.475094-6-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/mcu.c | 99 ++++++++++++++++--- 1 file changed, 84 insertions(+), 15 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index 9795ac27f28f..8abae7585e7b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -7,10 +7,17 @@ #include "regd.h" #include "mcu.h" #include "mac.h" +#include "nan.h" #define MT_STA_BFER BIT(0) #define MT_STA_BFEE BIT(1) +static bool mt7925_vif_is_nan(struct ieee80211_vif *vif) +{ + return vif->type == NL80211_IFTYPE_NAN || + vif->type == NL80211_IFTYPE_NAN_DATA; +} + int mt7925_mcu_parse_response(struct mt76_dev *mdev, int cmd, struct sk_buff *skb, int seq) { @@ -48,7 +55,8 @@ int mt7925_mcu_parse_response(struct mt76_dev *mdev, int cmd, cmd == MCU_UNI_CMD(BSS_INFO_UPDATE) || cmd == MCU_UNI_CMD(STA_REC_UPDATE) || cmd == MCU_UNI_CMD(OFFLOAD) || - cmd == MCU_UNI_CMD(SUSPEND)) { + cmd == MCU_UNI_CMD(SUSPEND) || + cmd == MCU_UNI_CMD(NAN)) { struct mt7925_mcu_uni_event *event; skb_pull(skb, sizeof(*rxd)); @@ -666,6 +674,9 @@ mt7925_mcu_uni_rx_unsolicited_event(struct mt792x_dev *dev, dev->fw_assert = true; mt76_connac_mcu_coredump_event(&dev->mt76, skb, &dev->coredump); return; + case MCU_UNI_EVENT_NAN: + mt7925_nan_mcu_event(dev, skb); + break; default: break; } @@ -1871,9 +1882,20 @@ mt7925_mcu_sta_phy_tlv(struct sk_buff *skb, tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_PHY, sizeof(*phy)); phy = (struct sta_rec_phy *)tlv; - phy->phy_type = mt76_connac_get_phy_mode_v2(mvif->phy->mt76, vif, - chandef->chan->band, - link_sta); + + if (mt7925_vif_is_nan(vif)) { + enum nl80211_band band = chandef->chan ? chandef->chan->band + : NL80211_BAND_2GHZ; + phy->phy_type = PHY_TYPE_BIT_OFDM | PHY_TYPE_BIT_ERP; + phy->phy_type |= mt76_connac_get_phy_mode_v2(mvif->phy->mt76, vif, + band, + link_sta); + } else { + phy->phy_type = mt76_connac_get_phy_mode_v2(mvif->phy->mt76, vif, + chandef->chan->band, + link_sta); + } + phy->basic_rate = cpu_to_le16((u16)link_conf->basic_rates); if (link_sta->ht_cap.ht_supported) { af = link_sta->ht_cap.ampdu_factor; @@ -1946,11 +1968,15 @@ mt7925_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, mconf = mt792x_vif_to_link(mvif, link_sta->link_id); chandef = mconf->mt76.ctx ? &mconf->mt76.ctx->def : &link_conf->chanreq.oper; - band = chandef->chan->band; tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_RA, sizeof(*ra_info)); ra_info = (struct sta_rec_ra_info *)tlv; + if (mt7925_vif_is_nan(vif)) + band = chandef->chan ? chandef->chan->band : NL80211_BAND_2GHZ; + else + band = chandef->chan->band; + supp_rates = link_sta->supp_rates[band]; if (band == NL80211_BAND_2GHZ) supp_rates = FIELD_PREP(RA_LEGACY_OFDM, supp_rates >> 4) | @@ -2597,6 +2623,29 @@ mt7925_get_phy_mode_ext(struct mt76_phy *phy, struct ieee80211_vif *vif, return mode; } +static void +mt7925_mcu_bss_basic_tlv_nan(struct mt76_phy *phy, + struct ieee80211_vif *vif, + struct ieee80211_link_sta *link_sta, + struct mt76_connac_bss_basic_tlv *basic_req) +{ + u8 mode_2g, mode_5g; + + mode_2g = mt7925_get_phy_mode_ext(phy, vif, NL80211_BAND_2GHZ, + link_sta); + mode_5g = mt7925_get_phy_mode_ext(phy, vif, NL80211_BAND_5GHZ, + link_sta); + basic_req->phymode_ext = mode_2g | mode_5g; + + basic_req->nonht_basic_phy = cpu_to_le16(PHY_TYPE_ERP_INDEX); + + mode_2g = mt76_connac_get_phy_mode(phy, vif, NL80211_BAND_2GHZ, + link_sta); + mode_5g = mt76_connac_get_phy_mode(phy, vif, NL80211_BAND_5GHZ, + link_sta); + basic_req->phymode = (mode_2g | mode_5g) & ~PHY_MODE_B; +} + static void mt7925_mcu_bss_basic_tlv(struct sk_buff *skb, struct ieee80211_bss_conf *link_conf, @@ -2611,7 +2660,7 @@ mt7925_mcu_bss_basic_tlv(struct sk_buff *skb, struct mt792x_bss_conf *mconf = mt792x_link_conf_to_mconf(link_conf); struct cfg80211_chan_def *chandef = ctx ? &ctx->def : &link_conf->chanreq.oper; - enum nl80211_band band = chandef->chan->band; + enum nl80211_band band = NL80211_BAND_2GHZ; struct mt76_connac_bss_basic_tlv *basic_req; struct tlv *tlv; int conn_type; @@ -2624,16 +2673,25 @@ mt7925_mcu_bss_basic_tlv(struct sk_buff *skb, mconf->mt76.omac_idx; basic_req->hw_bss_idx = idx; - basic_req->phymode_ext = mt7925_get_phy_mode_ext(phy, vif, band, - link_sta); + if (mt7925_vif_is_nan(vif)) { + mt7925_mcu_bss_basic_tlv_nan(phy, vif, link_sta, basic_req); + } else { + band = chandef->chan->band; + basic_req->phymode_ext = mt7925_get_phy_mode_ext(phy, vif, band, + link_sta); - if (band == NL80211_BAND_2GHZ) - basic_req->nonht_basic_phy = cpu_to_le16(PHY_TYPE_ERP_INDEX); - else - basic_req->nonht_basic_phy = cpu_to_le16(PHY_TYPE_OFDM_INDEX); + if (band == NL80211_BAND_2GHZ) + basic_req->nonht_basic_phy = + cpu_to_le16(PHY_TYPE_ERP_INDEX); + else + basic_req->nonht_basic_phy = + cpu_to_le16(PHY_TYPE_OFDM_INDEX); + + memcpy(basic_req->bssid, link_conf->bssid, ETH_ALEN); + basic_req->phymode = mt76_connac_get_phy_mode(phy, vif, band, + link_sta); + } - memcpy(basic_req->bssid, link_conf->bssid, ETH_ALEN); - basic_req->phymode = mt76_connac_get_phy_mode(phy, vif, band, link_sta); basic_req->bcn_interval = cpu_to_le16(link_conf->beacon_int); basic_req->dtim_period = link_conf->dtim_period; basic_req->bmc_tx_wlan_idx = cpu_to_le16(bmc_tx_wlan_idx); @@ -2666,6 +2724,11 @@ mt7925_mcu_bss_basic_tlv(struct sk_buff *skb, basic_req->conn_type = cpu_to_le32(CONNECTION_IBSS_ADHOC); basic_req->active = true; break; + case NL80211_IFTYPE_NAN: + case NL80211_IFTYPE_NAN_DATA: + basic_req->conn_type = cpu_to_le32(CONNECTION_NAN); + basic_req->active = enable; + break; default: WARN_ON(1); break; @@ -2724,10 +2787,11 @@ mt7925_mcu_bss_bmc_tlv(struct sk_buff *skb, struct mt792x_phy *phy, struct ieee80211_chanctx_conf *ctx, struct ieee80211_bss_conf *link_conf) { + struct ieee80211_vif *vif = link_conf->vif; struct cfg80211_chan_def *chandef = ctx ? &ctx->def : &link_conf->chanreq.oper; struct mt792x_bss_conf *mconf = mt792x_link_conf_to_mconf(link_conf); - enum nl80211_band band = chandef->chan->band; + enum nl80211_band band = NL80211_BAND_2GHZ; struct mt76_vif_link *mvif = &mconf->mt76; struct bss_rate_tlv *bmc; struct tlv *tlv; @@ -2738,6 +2802,11 @@ mt7925_mcu_bss_bmc_tlv(struct sk_buff *skb, struct mt792x_phy *phy, bmc = (struct bss_rate_tlv *)tlv; + if (mt7925_vif_is_nan(vif)) + band = chandef->chan ? chandef->chan->band : NL80211_BAND_2GHZ; + else + band = chandef->chan->band; + if (band == NL80211_BAND_2GHZ) bmc->basic_rate = cpu_to_le16(HR_DSSS_ERP_BASIC_RATE); else From 4ee3d2a5f2cd5b6d0ba0d997913c1b763ef4d5c5 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 24 Jun 2026 19:18:31 -0500 Subject: [PATCH 0721/1433] wifi: mt76: add init_wiphy callback Add an optional callback for drivers to finalize wiphy state after mt76 has initialized the supported bands and before registration. Co-developed-by: Stella Liu Signed-off-by: Stella Liu Co-developed-by: Jeremy Yu Signed-off-by: Jeremy Yu Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260625001834.475094-7-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mac80211.c | 7 +++++++ drivers/net/wireless/mediatek/mt76/mt76.h | 3 +++ 2 files changed, 10 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index 13c4e8abe281..c4cbf7195b80 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -681,6 +681,7 @@ mt76_alloc_device(struct device *pdev, unsigned int size, dev = hw->priv; dev->hw = hw; dev->dev = pdev; + dev->init_wiphy = NULL; dev->drv = drv_ops; dev->dma_dev = pdev; @@ -779,6 +780,12 @@ int mt76_register_device(struct mt76_dev *dev, bool vht, mt76_check_sband(&dev->phy, &phy->sband_5g, NL80211_BAND_5GHZ); mt76_check_sband(&dev->phy, &phy->sband_6g, NL80211_BAND_6GHZ); + if (dev->init_wiphy) { + ret = dev->init_wiphy(dev); + if (ret) + return ret; + } + if (IS_ENABLED(CONFIG_MT76_LEDS)) { ret = mt76_led_init(phy); if (ret) diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 3822eb8fd88f..6cff136407d8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -940,6 +940,9 @@ struct mt76_dev { const struct mt76_bus_ops *bus; const struct mt76_driver_ops *drv; const struct mt76_mcu_ops *mcu_ops; + + /* Optional callback to finalize wiphy state before registration. */ + int (*init_wiphy)(struct mt76_dev *dev); struct device *dev; struct device *dma_dev; From 0f3605e4f8de07c1c12271b7f602754e494fec5c Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 24 Jun 2026 19:18:32 -0500 Subject: [PATCH 0722/1433] wifi: mt76: mt7925: wire up NAN operations Wire mac80211 NAN start, stop and change_conf callbacks to the mt7925 NAN MCU helpers. Track the active NAN vif and notify mac80211 on cluster join events. Initialize NAN PHY capabilities after the supported bands are ready. Co-developed-by: Stella Liu Signed-off-by: Stella Liu Co-developed-by: Jeremy Yu Signed-off-by: Jeremy Yu Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260625001834.475094-8-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/init.c | 29 +++ .../net/wireless/mediatek/mt76/mt7925/main.c | 201 ++++++++++++++- .../net/wireless/mediatek/mt76/mt7925/nan.c | 239 +++++++++++++++--- .../net/wireless/mediatek/mt76/mt7925/nan.h | 2 + drivers/net/wireless/mediatek/mt76/mt792x.h | 3 + 5 files changed, 430 insertions(+), 44 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/init.c b/drivers/net/wireless/mediatek/mt76/mt7925/init.c index e85b0d104fbe..1b44f5c8fb0d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/init.c @@ -152,6 +152,33 @@ static int mt7925_init_hardware(struct mt792x_dev *dev) return 0; } +static int mt7925_init_nan_cap(struct mt76_dev *mdev) +{ + struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76); + const struct ieee80211_sta_he_cap *he_cap; + struct ieee80211_supported_band *sband; + struct wiphy *wiphy = mdev->hw->wiphy; + + if (!(dev->fw_features & MT792x_FW_CAP_NAN)) + return 0; + + sband = wiphy->bands[NL80211_BAND_2GHZ]; + if (sband) + wiphy->nan_capa.phy.ht = sband->ht_cap; + + sband = wiphy->bands[NL80211_BAND_5GHZ]; + if (sband) + wiphy->nan_capa.phy.vht = sband->vht_cap; + + sband = wiphy->bands[NL80211_BAND_2GHZ]; + he_cap = sband ? ieee80211_get_he_iftype_cap(sband, NL80211_IFTYPE_NAN) + : NULL; + if (he_cap) + wiphy->nan_capa.phy.he = *he_cap; + + return 0; +} + static void mt7925_init_work(struct work_struct *work) { struct mt792x_dev *dev = container_of(work, struct mt792x_dev, @@ -172,6 +199,8 @@ static void mt7925_init_work(struct work_struct *work) return; } + dev->mt76.init_wiphy = mt7925_init_nan_cap; + ret = mt76_register_device(&dev->mt76, true, mt76_rates, ARRAY_SIZE(mt76_rates)); if (ret) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 2b6cc8e253c0..6be5b60b9bac 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -11,6 +11,7 @@ #include "regd.h" #include "mcu.h" #include "mac.h" +#include "nan.h" static void mt7925_init_he_caps(struct mt792x_phy *phy, enum nl80211_band band, @@ -412,7 +413,8 @@ static int mt7925_mac_link_bss_add(struct mt792x_dev *dev, 0 : mconf->mt76.idx % MT76_CONNAC_MAX_WMM_SETS; mconf->mt76.link_idx = hweight16(mvif->valid_links); - if (mvif->phy->mt76->chandef.chan->band != NL80211_BAND_2GHZ) + if (mvif->phy->mt76->chandef.chan && + mvif->phy->mt76->chandef.chan->band != NL80211_BAND_2GHZ) mconf->mt76.basic_rates_idx = MT792x_BASIC_RATES_TBL + 4; else mconf->mt76.basic_rates_idx = MT792x_BASIC_RATES_TBL; @@ -474,12 +476,32 @@ mt7925_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) INIT_WORK(&mvif->csa_work, mt7925_csa_work); timer_setup(&mvif->csa_timer, mt792x_csa_timer, 0); + if (vif->type == NL80211_IFTYPE_NAN) + dev->nan_vif = vif; out: mt792x_mutex_release(dev); return ret; } +static void +mt7925_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; + struct mt792x_dev *dev = mt792x_hw_dev(hw); + struct mt792x_bss_conf *mconf; + + mt792x_mutex_acquire(dev); + + if (dev->nan_vif == vif) + dev->nan_vif = NULL; + + mconf = mt792x_link_conf_to_mconf(&vif->bss_conf); + mt792x_mac_link_bss_remove(dev, mconf, &mvif->sta.deflink); + + mt792x_mutex_release(dev); +} + static void mt7925_roc_iter(void *priv, u8 *mac, struct ieee80211_vif *vif) { @@ -1217,20 +1239,37 @@ static void mt7925_mac_link_sta_assoc(struct mt76_dev *mdev, int mt7925_mac_sta_event(struct mt76_dev *mdev, struct ieee80211_vif *vif, struct ieee80211_sta *sta, enum mt76_sta_event ev) { + struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76); struct ieee80211_link_sta *link_sta = &sta->deflink; - if (ev != MT76_STA_EVENT_ASSOC) - return 0; + switch (ev) { + case MT76_STA_EVENT_ASSOC: + if (ieee80211_vif_is_mld(vif)) { + struct mt792x_sta *msta = + (struct mt792x_sta *)sta->drv_priv; - if (ieee80211_vif_is_mld(vif)) { - struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv; + link_sta = mt792x_sta_to_link_sta(vif, sta, + msta->deflink_id); + mt7925_mac_set_links(mdev, vif); + } - link_sta = mt792x_sta_to_link_sta(vif, sta, msta->deflink_id); - mt7925_mac_set_links(mdev, vif); + mt7925_mac_link_sta_assoc(mdev, vif, link_sta); + break; + case MT76_STA_EVENT_AUTHORIZE: + if (vif->type == NL80211_IFTYPE_NAN_DATA) { + int ret; + + mt792x_mutex_acquire(dev); + ret = mt792x_nan_map_sta_rec(mdev, vif, sta); + mt792x_mutex_release(dev); + + return ret; + } + break; + default: + break; } - mt7925_mac_link_sta_assoc(mdev, vif, link_sta); - return 0; } EXPORT_SYMBOL_GPL(mt7925_mac_sta_event); @@ -1357,6 +1396,36 @@ void mt7925_mac_sta_remove(struct mt76_dev *mdev, struct ieee80211_vif *vif, struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv; struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; + /* Release NAN peer record before tearing down the STA. */ + if (vif->type == NL80211_IFTYPE_NAN || + vif->type == NL80211_IFTYPE_NAN_DATA) { + int ret = mt792x_nan_set_peer_rec(mdev, sta); + + if (ret) + dev_err(mdev->dev, + "NAN: failed to deactivate peer record: %d\n", + ret); + } + + /* Release NDP context ID for NAN_DATA sta. */ + if (vif->type == NL80211_IFTYPE_NAN_DATA) { + struct ieee80211_sta *nmi_sta; + + rcu_read_lock(); + nmi_sta = rcu_dereference(sta->nmi); + if (nmi_sta) { + struct mt792x_sta *nmi_msta = + (struct mt792x_sta *)nmi_sta->drv_priv; + + if (msta->nan_sched.ndp_ctx_assigned) { + clear_bit(msta->nan_sched.ndp_ctx_id, + &nmi_msta->nan_sched.ndp_ctx_bitmap); + msta->nan_sched.ndp_ctx_assigned = false; + } + } + rcu_read_unlock(); + } + if (ieee80211_vif_is_mld(vif)) { mt7925_mac_sta_remove_links(dev, vif, sta, msta->valid_links); mt7925_mcu_del_dev(mdev, vif); @@ -2062,6 +2131,11 @@ static void mt7925_vif_cfg_changed(struct ieee80211_hw *hw, } mt792x_mutex_release(dev); + + if (vif->type == NL80211_IFTYPE_NAN && + changed & BSS_CHANGED_NAN_LOCAL_SCHED) { + mt7925_nan_local_sched_changed(dev, vif); + } } static void mt7925_link_info_changed(struct ieee80211_hw *hw, @@ -2484,12 +2558,115 @@ static void mt7925_channel_switch_rx_beacon(struct ieee80211_hw *hw, } } +static int mt7925_start_nan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_nan_conf *conf) +{ + struct ieee80211_bss_conf *link_conf = &vif->bss_conf; + struct mt792x_dev *dev = mt792x_hw_dev(hw); + struct ieee80211_channel *chan; + int err = 0; + + mt792x_mutex_acquire(dev); + + chan = conf->band_cfgs[NL80211_BAND_2GHZ].chan; + if (!chan) { + err = -EINVAL; + goto out; + } + + cfg80211_chandef_create(&link_conf->chanreq.oper, chan, + NL80211_CHAN_NO_HT); + + err = mt7925_mcu_add_bss_info(&dev->phy, NULL, link_conf, + NULL, true); + if (err < 0) + goto out; + + dev->nan_vif = vif; + + err = mt7925_nan_set_nmi_addr(dev, vif->addr); + if (err) + goto rollback_bss; + + err = mt7925_nan_enable(vif, dev, conf); + if (err) + goto rollback_bss; + + goto out; + +rollback_bss: + dev->nan_vif = NULL; + mt7925_mcu_add_bss_info(&dev->phy, NULL, link_conf, NULL, false); + +out: + mt792x_mutex_release(dev); + + return err; +} + +static int mt7925_stop_nan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct ieee80211_bss_conf *link_conf = &vif->bss_conf; + struct mt792x_dev *dev = mt792x_hw_dev(hw); + int err, ret; + + mt792x_mutex_acquire(dev); + + err = mt7925_nan_disable(vif, dev); + + ret = mt7925_mcu_add_bss_info(&dev->phy, NULL, link_conf, + NULL, false); + if (!err) + err = ret; + + if (dev->nan_vif == vif) + dev->nan_vif = NULL; + + mt792x_mutex_release(dev); + + return err; +} + +static int mt7925_nan_change_conf(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_nan_conf *conf, + u32 changes) +{ + struct mt792x_dev *dev = mt792x_hw_dev(hw); + int err = 0; + + mt792x_mutex_acquire(dev); + + err = mt7925_nan_change_configure(vif, dev, conf); + + mt792x_mutex_release(dev); + + return err; +} + +static int mt7925_nan_peer_sched_changed(struct ieee80211_hw *hw, + struct ieee80211_sta *sta) +{ + struct mt792x_dev *dev = mt792x_hw_dev(hw); + int err = 0; + + mt792x_mutex_acquire(dev); + + err = mt792x_nan_set_peer_schedule(dev, sta); + + mt792x_mutex_release(dev); + + return err; +} + const struct ieee80211_ops mt7925_ops = { .tx = mt792x_tx, .start = mt7925_start, .stop = mt792x_stop, .add_interface = mt7925_add_interface, - .remove_interface = mt792x_remove_interface, + .remove_interface = mt7925_remove_interface, .config = mt7925_config, .conf_tx = mt7925_conf_tx, .configure_filter = mt7925_configure_filter, @@ -2553,6 +2730,10 @@ const struct ieee80211_ops mt7925_ops = { .channel_switch = mt7925_channel_switch, .abort_channel_switch = mt7925_abort_channel_switch, .channel_switch_rx_beacon = mt7925_channel_switch_rx_beacon, + .start_nan = mt7925_start_nan, + .stop_nan = mt7925_stop_nan, + .nan_change_conf = mt7925_nan_change_conf, + .nan_peer_sched_changed = mt7925_nan_peer_sched_changed, }; EXPORT_SYMBOL_GPL(mt7925_ops); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c index 74db344a6796..d260e803d056 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c @@ -32,6 +32,8 @@ static void mt7925_nan_set_5g_channel(struct mt792x_dev *dev, if (!mt7925_regd_is_valid_channel(dev, NL80211_BAND_5GHZ, chan)) return; + req->config_support_5g = 1; + req->support_5g_val = 1; req->config_5g_channel = 1; if (chan->hw_value == NAN_5G_LOW_DISC_CHANNEL) @@ -42,6 +44,16 @@ static void mt7925_nan_set_5g_channel(struct mt792x_dev *dev, req->channel_5g_val = cpu_to_le32(ch5g); } +static void mt7925_nan_set_2g_support(struct mt7925_nan_enable_req_tlv *req, + struct cfg80211_nan_conf *conf) +{ + if (!conf->band_cfgs[NL80211_BAND_2GHZ].chan) + return; + + req->config_2dot4g_support = 1; + req->support_2dot4g_val = 1; +} + static void mt7925_nan_set_cluster_id(struct mt7925_nan_enable_req_tlv *req, const u8 *cluster_id) { @@ -132,6 +144,38 @@ mt7925_nan_update_conf(struct mt792x_vif *mvif, memcpy(mvif->nan.conf.cluster_id, conf->cluster_id, ETH_ALEN); } +int mt7925_nan_set_nmi_addr(struct mt792x_dev *dev, const u8 *addr) +{ + struct mt76_dev *mdev; + struct { + u8 rsv[4]; + struct mt7925_nan_nmi_addr_tlv nmi_addr_tlv; + } nmi_cmd = { + .rsv = { 0 }, + .nmi_addr_tlv = { + .tag = cpu_to_le16(NAN_UNI_CMD_CHANGE_NMI_ADDRESS), + .len = cpu_to_le16(sizeof(struct mt7925_nan_nmi_addr_tlv)), + }, + }; + int ret; + + if (!dev || !addr) + return -EINVAL; + + if (is_zero_ether_addr(addr) || is_multicast_ether_addr(addr)) { + dev_err(dev->mt76.dev, "NAN: invalid NMI address %pM\n", addr); + return -EINVAL; + } + + mdev = &dev->mt76; + memcpy(nmi_cmd.nmi_addr_tlv.nmi_addr, addr, ETH_ALEN); + + ret = mt76_mcu_send_msg(mdev, MCU_UNI_CMD(NAN), &nmi_cmd, + sizeof(nmi_cmd), true); + + return ret; +} + int mt7925_nan_enable(struct ieee80211_vif *vif, struct mt792x_dev *dev, struct cfg80211_nan_conf *conf) @@ -153,12 +197,14 @@ int mt7925_nan_enable(struct ieee80211_vif *vif, }, }; struct mt7925_nan_enable_req_tlv *p_nan_req_tlv = &nan_cmd.nan_req_tlv; + int ret; if (!vif || !dev || !conf) return -EINVAL; p_nan_req_tlv->master_pref = conf->master_pref; + mt7925_nan_set_2g_support(p_nan_req_tlv, conf); mt7925_nan_set_5g_channel(dev, p_nan_req_tlv, conf); mt7925_nan_set_cluster_id(p_nan_req_tlv, conf->cluster_id); mt7925_nan_set_dw_interval(p_nan_req_tlv, conf); @@ -168,7 +214,9 @@ int mt7925_nan_enable(struct ieee80211_vif *vif, mt7925_nan_update_conf(mvif, conf); - return mt76_mcu_send_msg(mdev, MCU_UNI_CMD(NAN), &nan_cmd, sizeof(nan_cmd), true); + ret = mt76_mcu_send_msg(mdev, MCU_UNI_CMD(NAN), &nan_cmd, sizeof(nan_cmd), true); + + return ret; } int mt7925_nan_disable(struct ieee80211_vif *vif, struct mt792x_dev *dev) @@ -428,7 +476,7 @@ mt7925_nan_mcu_handle_de_event(struct mt792x_dev *dev, struct tlv *tlv) if (de_evt->event_type != NAN_EVENT_ID_JOINED_CLUSTER) return; - if (!ieee80211_vif_nan_started(dev->nan_vif)) { + if (!dev->nan_vif || !ieee80211_vif_nan_started(dev->nan_vif)) { dev_warn(dev->mt76.dev, "nan: joined-cluster event but NAN not started\n"); return; } @@ -599,16 +647,21 @@ void mt7925_nan_local_sched_changed(struct mt792x_dev *dev, { struct mt7925_nan_common_hdr *hdr; struct mt76_dev *mdev; + bool deferred; struct sk_buff *skb; + int ret = -ENOMEM; if (!dev || !vif) return; mdev = &dev->mt76; + deferred = vif->cfg.nan_sched.deferred; + + mt792x_mutex_acquire(dev); skb = mt76_mcu_msg_alloc(mdev, NULL, MT7925_NAN_AVAIL_MAX_SIZE); if (!skb) - return; + goto out; hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr)); memset(hdr, 0, sizeof(*hdr)); @@ -616,11 +669,22 @@ void mt7925_nan_local_sched_changed(struct mt792x_dev *dev, if (mt7925_nan_avail_ctrl_tlv(skb, vif) || mt7925_nan_avail_tlv(skb, vif)) { dev_kfree_skb(skb); - return; + goto out; } - mt76_mcu_skb_send_msg(mdev, skb, - MCU_UNI_CMD(NAN), true); + ret = mt76_mcu_skb_send_msg(mdev, skb, + MCU_UNI_CMD(NAN), true); +out: + mt792x_mutex_release(dev); + + if (deferred) { + if (ret) + dev_err(mdev->dev, + "NAN: local schedule update failed: %d\n", + ret); + + ieee80211_nan_sched_update_done(vif); + } } static int mt7925_nan_peer_rec_tlv(struct sk_buff *skb, @@ -648,6 +712,23 @@ static int mt7925_nan_peer_rec_tlv(struct sk_buff *skb, return 0; } +static u8 mt7925_nan_get_supported_bands(struct mt792x_vif *mvif) +{ + struct wiphy *wiphy; + u8 bands = 0; + + if (!mvif || !mvif->phy) + return BIT(NAN_SUPPORTED_BAND_ID_2P4G); + + wiphy = mvif->phy->mt76->hw->wiphy; + if (wiphy->nan_supported_bands & BIT(NL80211_BAND_2GHZ)) + bands |= BIT(NAN_SUPPORTED_BAND_ID_2P4G); + if (wiphy->nan_supported_bands & BIT(NL80211_BAND_5GHZ)) + bands |= BIT(NAN_SUPPORTED_BAND_ID_5G); + + return bands ?: BIT(NAN_SUPPORTED_BAND_ID_2P4G); +} + static int mt7925_nan_peer_cap_tlv(struct sk_buff *skb, struct ieee80211_sta *sta, struct mt792x_sta *msta) @@ -674,7 +755,8 @@ static int mt7925_nan_peer_cap_tlv(struct sk_buff *skb, peer_cap_tlv = (struct mt7925_nan_sched_update_peer_cap_tlv *)tlv; peer_cap_tlv->sch_idx = cpu_to_le32(msta->nan_sched.sch_idx); - peer_cap_tlv->supported_bands = BIT(NAN_SUPPORTED_BAND_ID_2P4G); + peer_cap_tlv->supported_bands = + mt7925_nan_get_supported_bands(msta->vif); peer_cap_tlv->max_chnl_switch_time = cpu_to_le16(sched->max_chan_switch); for (i = 0; i < sched->n_channels; i++) { @@ -703,38 +785,52 @@ static int mt7925_nan_peer_cap_tlv(struct sk_buff *skb, static void mt7925_nan_fill_crb_committed(struct mt7925_nan_sched_update_crb_tlv *crb_tlv, + struct ieee80211_vif *vif, struct ieee80211_nan_peer_sched *sched) { + struct ieee80211_nan_sched_cfg *local_sched; + u8 local_map_id; u32 m, slot; - if (!sched) + if (!vif || !sched) return; + local_sched = &vif->cfg.nan_sched; + local_map_id = mt7925_nan_avail_attr_ctrl(local_sched) & + NAN_AVAIL_CTRL_MAPID; + for (m = 0; m < CFG80211_NAN_MAX_PEER_MAPS && m < NAN_TIMELINE_MGMT_SIZE; m++) { struct mt7925_nan_sched_timeline *tl = &crb_tlv->comm_faw_timeline[m]; struct ieee80211_nan_peer_map *map = &sched->maps[m]; + u32 avail_map = 0; if (map->map_id == CFG80211_NAN_INVALID_MAP_ID) continue; tl->map_id = map->map_id; + tl->local_map_id = local_map_id; - /* - * Convert peer schedule slots to FW avail_map bitmap. - * Each bit in avail_map[0] represents one time slot where - * the peer has committed availability. - */ for (slot = 0; slot < CFG80211_NAN_SCHED_NUM_TIME_SLOTS; slot++) { - struct ieee80211_nan_channel *ch = map->slots[slot]; + struct ieee80211_nan_channel *local_ch; + struct ieee80211_nan_channel *peer_ch; - if (!ch || !ch->chanctx_conf) + local_ch = local_sched->schedule[slot]; + peer_ch = map->slots[slot]; + + if (!local_ch || !local_ch->chanctx_conf || + !peer_ch || !peer_ch->chanctx_conf) continue; - tl->avail_map[0] |= cpu_to_le32(BIT(slot)); + if (local_ch->chanctx_conf != peer_ch->chanctx_conf) + continue; + + avail_map |= BIT(slot); } + + tl->avail_map[0] = cpu_to_le32(avail_map); } } @@ -760,7 +856,8 @@ static int mt7925_nan_update_crb_tlv(struct sk_buff *skb, crb_tlv->is_use_ranging = false; crb_tlv->comm_ndc_ctrl.is_valid = false; - mt7925_nan_fill_crb_committed(crb_tlv, sta->nan_sched); + mt7925_nan_fill_crb_committed(crb_tlv, msta->vif->phy->dev->nan_vif, + sta->nan_sched); return 0; } @@ -769,10 +866,12 @@ int mt792x_nan_set_peer_schedule(struct mt792x_dev *dev, struct ieee80211_sta *sta) { struct mt7925_nan_common_hdr *hdr; + bool idx_allocated = false; struct mt792x_sta *msta; struct mt792x_nan *nan; struct mt76_dev *mdev; struct sk_buff *skb; + int ret; if (!dev || !sta) return -EINVAL; @@ -801,21 +900,36 @@ int mt792x_nan_set_peer_schedule(struct mt792x_dev *dev, set_bit(idx, &nan->conn_bitmap); msta->nan_sched.sch_idx = idx; msta->nan_sched.idx_assigned = true; + idx_allocated = true; if (mt7925_nan_peer_rec_tlv(skb, sta, msta, true) || mt7925_nan_peer_cap_tlv(skb, sta, msta)) { - dev_kfree_skb(skb); - return -ENOMEM; + ret = -ENOMEM; + goto free_skb; } } if (mt7925_nan_update_crb_tlv(skb, sta, msta)) { - dev_kfree_skb(skb); - return -ENOMEM; + ret = -ENOMEM; + goto free_skb; } - return mt76_mcu_skb_send_msg(mdev, skb, - MCU_UNI_CMD(NAN), true); + ret = mt76_mcu_skb_send_msg(mdev, skb, MCU_UNI_CMD(NAN), true); + if (ret && idx_allocated) + goto clear_idx; + + return ret; + +free_skb: + dev_kfree_skb(skb); + if (!idx_allocated) + return ret; + +clear_idx: + clear_bit(msta->nan_sched.sch_idx, &nan->conn_bitmap); + msta->nan_sched.idx_assigned = false; + + return ret; } int mt792x_nan_set_peer_rec(struct mt76_dev *mdev, @@ -825,6 +939,7 @@ int mt792x_nan_set_peer_rec(struct mt76_dev *mdev, struct mt792x_sta *msta; struct mt792x_nan *nan; struct sk_buff *skb; + int ret; if (!mdev || !sta) return -EINVAL; @@ -851,11 +966,14 @@ int mt792x_nan_set_peer_rec(struct mt76_dev *mdev, return -ENOMEM; } + ret = mt76_mcu_skb_send_msg(mdev, skb, MCU_UNI_CMD(NAN), true); + if (ret) + return ret; + clear_bit(msta->nan_sched.sch_idx, &nan->conn_bitmap); msta->nan_sched.idx_assigned = false; - return mt76_mcu_skb_send_msg(mdev, skb, - MCU_UNI_CMD(NAN), true); + return 0; } int mt792x_nan_map_sta_rec(struct mt76_dev *mdev, @@ -866,16 +984,21 @@ int mt792x_nan_map_sta_rec(struct mt76_dev *mdev, struct mt7925_nan_common_hdr *hdr; struct ieee80211_sta *nmi_sta; struct mt792x_sta *nmi_msta; + struct mt792x_vif *mvif; struct mt792x_sta *msta; u8 nmi_addr[ETH_ALEN]; struct sk_buff *skb; int ndp_ctx_id = 0; + int ret = -ENOMEM; + struct mt792x_dev *dev; struct tlv *tlv; if (!mdev || !vif || !sta) return -EINVAL; + dev = container_of(mdev, struct mt792x_dev, mt76); msta = (struct mt792x_sta *)sta->drv_priv; + mvif = (struct mt792x_vif *)vif->drv_priv; rcu_read_lock(); nmi_sta = rcu_dereference(sta->nmi); @@ -889,21 +1012,51 @@ int mt792x_nan_map_sta_rec(struct mt76_dev *mdev, memcpy(nmi_addr, nmi_sta->addr, ETH_ALEN); nmi_msta = (struct mt792x_sta *)nmi_sta->drv_priv; + if (!nmi_msta->nan_sched.idx_assigned) { + if (!nmi_sta->nan_sched) { + rcu_read_unlock(); + dev_err(mdev->dev, + "NAN: peer schedule missing for NDI sta %pM\n", + sta->addr); + return -EAGAIN; + } + + rcu_read_unlock(); + ret = mt792x_nan_set_peer_schedule(dev, nmi_sta); + if (ret) + return ret; + + rcu_read_lock(); + nmi_sta = rcu_dereference(sta->nmi); + if (!nmi_sta) { + rcu_read_unlock(); + dev_err(mdev->dev, + "NAN: NMI sta not found for NDI sta %pM\n", + sta->addr); + return -EINVAL; + } + + nmi_msta = (struct mt792x_sta *)nmi_sta->drv_priv; + } + ndp_ctx_id = find_first_zero_bit(&nmi_msta->nan_sched.ndp_ctx_bitmap, NAN_MAX_NDP_CXT); - if (ndp_ctx_id < NAN_MAX_NDP_CXT) - set_bit(ndp_ctx_id, &nmi_msta->nan_sched.ndp_ctx_bitmap); - else - ndp_ctx_id = 0; + if (ndp_ctx_id >= NAN_MAX_NDP_CXT) { + rcu_read_unlock(); + return -ENOSPC; + } + + set_bit(ndp_ctx_id, &nmi_msta->nan_sched.ndp_ctx_bitmap); rcu_read_unlock(); msta->nan_sched.ndp_ctx_id = ndp_ctx_id; + msta->nan_sched.ndp_ctx_assigned = true; skb = mt76_mcu_msg_alloc(mdev, NULL, sizeof(struct mt7925_nan_common_hdr) + sizeof(struct mt7925_nan_sched_map_sta_rec_tlv)); if (!skb) - return -ENOMEM; + goto clear_ndp_ctx; hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr)); memset(hdr, 0, sizeof(*hdr)); @@ -912,16 +1065,34 @@ int mt792x_nan_map_sta_rec(struct mt76_dev *mdev, sizeof(struct mt7925_nan_sched_map_sta_rec_tlv)); if (!tlv) { dev_kfree_skb(skb); - return -ENOMEM; + ret = -ENOMEM; + goto clear_ndp_ctx; } map_tlv = (struct mt7925_nan_sched_map_sta_rec_tlv *)tlv; memcpy(map_tlv->nmi_addr, nmi_addr, ETH_ALEN); map_tlv->sta_rec_idx = msta->deflink.wcid.idx; map_tlv->ndp_ctx_id = ndp_ctx_id; - map_tlv->role_idx = 0; + map_tlv->role_idx = cpu_to_le32(mvif->bss_conf.mt76.idx); memcpy(map_tlv->ndi_addr, vif->addr, ETH_ALEN); - return mt76_mcu_skb_send_msg(mdev, skb, - MCU_UNI_CMD(NAN), true); + ret = mt76_mcu_skb_send_msg(mdev, skb, + MCU_UNI_CMD(NAN), true); + if (ret) + goto clear_ndp_ctx; + + return 0; + +clear_ndp_ctx: + rcu_read_lock(); + nmi_sta = rcu_dereference(sta->nmi); + if (nmi_sta) { + nmi_msta = (struct mt792x_sta *)nmi_sta->drv_priv; + clear_bit(msta->nan_sched.ndp_ctx_id, + &nmi_msta->nan_sched.ndp_ctx_bitmap); + } + rcu_read_unlock(); + msta->nan_sched.ndp_ctx_assigned = false; + + return ret ?: -ENOMEM; } diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h index 356d9ef7f664..f55730e25f46 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h @@ -403,6 +403,8 @@ int mt7925_nan_change_configure(struct ieee80211_vif *vif, void mt7925_nan_mcu_event(struct mt792x_dev *dev, struct sk_buff *skb); +int mt7925_nan_set_nmi_addr(struct mt792x_dev *dev, const u8 *addr); + void mt7925_nan_local_sched_changed(struct mt792x_dev *dev, struct ieee80211_vif *vif); diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index 337d6a100236..319dc3ba8434 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -23,6 +23,7 @@ #define MT792x_CFEND_RATE_11B 0x03 /* 11B LP, 11M */ #define MT792x_FW_TAG_FEATURE 4 +#define MT792x_FW_CAP_NAN BIT(5) #define MT792x_FW_CAP_CNM BIT(7) #define MT792x_CHIP_CAP_CLC_EVT_EN BIT(0) @@ -121,10 +122,12 @@ struct mt792x_link_sta { }; struct mt792x_sta_nan_sched { + /* protects NAN peer schedule state */ u16 committed_dw; u32 sch_idx; bool idx_assigned; unsigned long ndp_ctx_bitmap; + bool ndp_ctx_assigned; u8 ndp_ctx_id; /* assigned NDP context ID (for NDI sta) */ struct { u8 map_id; From 9bb39d09ab0f0f0e2f341d727e65fa1712c3bffa Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 24 Jun 2026 19:18:33 -0500 Subject: [PATCH 0723/1433] wifi: mt76: mt792x: build iface combinations dynamically Move mt792x interface combination selection into a helper and store the selected table in mt792x device state. This keeps the existing non-CNM and CNM combinations unchanged while making later firmware-gated extensions add combinations without touching the common wiphy setup path. Co-developed-by: Stella Liu Signed-off-by: Stella Liu Co-developed-by: Jeremy Yu Signed-off-by: Jeremy Yu Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260625001834.475094-9-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt792x.h | 2 ++ .../net/wireless/mediatek/mt76/mt792x_core.c | 36 ++++++++++++++----- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index 319dc3ba8434..e98c4fd81044 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -337,6 +337,8 @@ struct mt792x_dev { struct ieee80211_chanctx_conf *new_ctx; struct ieee80211_vif *nan_vif; + const struct ieee80211_iface_combination *iface_combinations; + int n_iface_combinations; }; static inline struct mt792x_bss_conf * diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c index 8fc643fc0dfe..0c74fedf07e3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c @@ -60,7 +60,7 @@ static const struct ieee80211_iface_limit if_limits_chanctx_scc[] = { } }; -static const struct ieee80211_iface_combination if_comb_chanctx[] = { +static const struct ieee80211_iface_combination if_comb_chanctx_base[] = { { .limits = if_limits_chanctx_mcc, .n_limits = ARRAY_SIZE(if_limits_chanctx_mcc), @@ -77,6 +77,22 @@ static const struct ieee80211_iface_combination if_comb_chanctx[] = { } }; +static int mt792x_setup_iface_combinations(struct mt792x_dev *dev) +{ + const bool cnm = !!(dev->fw_features & MT792x_FW_CAP_CNM); + + if (!cnm) { + dev->iface_combinations = if_comb; + dev->n_iface_combinations = ARRAY_SIZE(if_comb); + return 0; + } + + dev->iface_combinations = if_comb_chanctx_base; + dev->n_iface_combinations = ARRAY_SIZE(if_comb_chanctx_base); + + return 0; +} + void mt792x_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control, struct sk_buff *skb) { @@ -663,6 +679,7 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw) struct mt792x_phy *phy = mt792x_hw_phy(hw); struct mt792x_dev *dev = phy->dev; struct wiphy *wiphy = hw->wiphy; + int err; hw->queues = 4; if (dev->has_eht) { @@ -683,15 +700,17 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw) hw->vif_data_size = sizeof(struct mt792x_vif); hw->chanctx_data_size = sizeof(struct mt792x_chanctx); - if (dev->fw_features & MT792x_FW_CAP_CNM) { + if (dev->fw_features & MT792x_FW_CAP_CNM) wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL; - wiphy->iface_combinations = if_comb_chanctx; - wiphy->n_iface_combinations = ARRAY_SIZE(if_comb_chanctx); - } else { + else wiphy->flags &= ~WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL; - wiphy->iface_combinations = if_comb; - wiphy->n_iface_combinations = ARRAY_SIZE(if_comb); - } + + err = mt792x_setup_iface_combinations(dev); + if (err) + return err; + + wiphy->iface_combinations = dev->iface_combinations; + wiphy->n_iface_combinations = dev->n_iface_combinations; wiphy->flags &= ~(WIPHY_FLAG_IBSS_RSN | WIPHY_FLAG_4ADDR_AP | WIPHY_FLAG_4ADDR_STATION); wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | @@ -699,6 +718,7 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw) BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO) | BIT(NL80211_IFTYPE_P2P_DEVICE); + wiphy->max_scan_ie_len = MT76_CONNAC_SCAN_IE_LEN; wiphy->max_scan_ssids = 4; wiphy->max_sched_scan_plan_interval = From 420e0bbd52ab6fa05ebfdb4dbe5442d47e4bfdf9 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 24 Jun 2026 19:18:34 -0500 Subject: [PATCH 0724/1433] wifi: mt76: mt792x: advertise NAN data support Advertise NAN and NAN data support when firmware exposes NAN capability. Add NAN interface combinations on top of the dynamic combination framework, advertise 2.4 GHz and 5 GHz NAN bands, and enable secure NAN. Keep the base interface combinations unchanged when NAN is unavailable so existing STA/AP/P2P modes keep the same limits. Co-developed-by: Stella Liu Signed-off-by: Stella Liu Co-developed-by: Jeremy Yu Signed-off-by: Jeremy Yu Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260625001834.475094-10-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt792x_core.c | 96 ++++++++++++++++++- 1 file changed, 92 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c index 0c74fedf07e3..2a8384a44d9f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c @@ -3,6 +3,7 @@ #include #include +#include #include "mt792x.h" #include "dma.h" @@ -60,6 +61,40 @@ static const struct ieee80211_iface_limit if_limits_chanctx_scc[] = { } }; +static const struct ieee80211_iface_limit if_limits_nan_mcc[] = { + { + .max = 2, + .types = BIT(NL80211_IFTYPE_STATION), + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_NAN), + }, + { + .max = 2, + .types = BIT(NL80211_IFTYPE_NAN_DATA), + }, +}; + +static const struct ieee80211_iface_limit if_limits_nan_scc[] = { + { + .max = 2, + .types = BIT(NL80211_IFTYPE_STATION), + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_NAN), + }, + { + .max = 2, + .types = BIT(NL80211_IFTYPE_NAN_DATA), + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_AP), + }, +}; + static const struct ieee80211_iface_combination if_comb_chanctx_base[] = { { .limits = if_limits_chanctx_mcc, @@ -77,9 +112,31 @@ static const struct ieee80211_iface_combination if_comb_chanctx_base[] = { } }; -static int mt792x_setup_iface_combinations(struct mt792x_dev *dev) +static const struct ieee80211_iface_combination if_comb_chanctx_nan[] = { + { + .limits = if_limits_nan_mcc, + .n_limits = ARRAY_SIZE(if_limits_nan_mcc), + .max_interfaces = MT792x_MAX_INTERFACES, + .num_different_channels = 2, + .beacon_int_infra_match = false, + }, + { + .limits = if_limits_nan_scc, + .n_limits = ARRAY_SIZE(if_limits_nan_scc), + .max_interfaces = MT792x_MAX_INTERFACES, + .num_different_channels = 1, + .beacon_int_infra_match = false, + }, +}; + +static int mt792x_setup_iface_combinations(struct mt792x_dev *dev, + struct wiphy *wiphy) { const bool cnm = !!(dev->fw_features & MT792x_FW_CAP_CNM); + const bool nan = !!(dev->fw_features & MT792x_FW_CAP_NAN); + const int n_base = ARRAY_SIZE(if_comb_chanctx_base); + const int n_nan = ARRAY_SIZE(if_comb_chanctx_nan); + struct ieee80211_iface_combination *comb; if (!cnm) { dev->iface_combinations = if_comb; @@ -87,8 +144,24 @@ static int mt792x_setup_iface_combinations(struct mt792x_dev *dev) return 0; } - dev->iface_combinations = if_comb_chanctx_base; - dev->n_iface_combinations = ARRAY_SIZE(if_comb_chanctx_base); + /* CNM enabled, NAN optional */ + if (!nan) { + dev->iface_combinations = if_comb_chanctx_base; + dev->n_iface_combinations = ARRAY_SIZE(if_comb_chanctx_base); + return 0; + } + + /* CNM + NAN: dynamically build base + nan list */ + comb = devm_kcalloc(&wiphy->dev, n_base + n_nan, sizeof(*comb), + GFP_KERNEL); + if (!comb) + return -ENOMEM; + + memcpy(comb, if_comb_chanctx_base, sizeof(if_comb_chanctx_base)); + memcpy(comb + n_base, if_comb_chanctx_nan, sizeof(if_comb_chanctx_nan)); + + dev->iface_combinations = comb; + dev->n_iface_combinations = n_base + n_nan; return 0; } @@ -705,7 +778,7 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw) else wiphy->flags &= ~WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL; - err = mt792x_setup_iface_combinations(dev); + err = mt792x_setup_iface_combinations(dev, wiphy); if (err) return err; @@ -719,6 +792,21 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw) BIT(NL80211_IFTYPE_P2P_GO) | BIT(NL80211_IFTYPE_P2P_DEVICE); + if ((dev->fw_features & MT792x_FW_CAP_CNM) && + (dev->fw_features & MT792x_FW_CAP_NAN)) { + wiphy->interface_modes |= BIT(NL80211_IFTYPE_NAN) | + BIT(NL80211_IFTYPE_NAN_DATA); + wiphy->nan_supported_bands = BIT(NL80211_BAND_2GHZ) | + BIT(NL80211_BAND_5GHZ); + wiphy->nan_capa.flags = WIPHY_NAN_FLAGS_CONFIGURABLE_SYNC | + WIPHY_NAN_FLAGS_USERSPACE_DE; + wiphy->nan_capa.op_mode = NAN_OP_MODE_PHY_MODE_MASK; + wiphy->nan_capa.n_antennas = 0x22; + wiphy->nan_capa.max_channel_switch_time = 12; + wiphy->nan_capa.dev_capabilities = NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED; + wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SECURE_NAN); + } + wiphy->max_scan_ie_len = MT76_CONNAC_SCAN_IE_LEN; wiphy->max_scan_ssids = 4; wiphy->max_sched_scan_plan_interval = From 81497634d9f872fd3e8b03aada55574afff6f174 Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Fri, 12 Jun 2026 17:25:43 -0700 Subject: [PATCH 0725/1433] wifi: mt76: mt76x02: do not WARN on invalid rx descriptor length The MPDU length in the rx descriptor comes from the hardware. In monitor mode with the fcsfail filter enabled, the hardware passes up corrupted frames, and a corrupted frame can report a length larger than the received buffer. The bounds check correctly discards such frames, but its WARN_ON_ONCE wrapper means any over-the-air garbage frame taints the kernel, and panics it on the first such frame when panic_on_warn is set. Drop the WARN and discard the frame silently, matching what commit c2d4c8723dbf ("mt76x2: remove some harmless WARN_ONs in tx status and rx path") did for the neighboring rx and tx status paths. Observed immediately on rx with an MT7612U in fcsfail monitor mode on a busy channel. Fixes: 7bc04215a66b ("mt76: add driver code for MT76x2e") Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260613002544.27750-2-lucid_duck@justthetip.ca Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76x02_mac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c b/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c index 14ee5b3b94d3..aa525adb6743 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c @@ -848,7 +848,7 @@ int mt76x02_mac_process_rx(struct mt76x02_dev *dev, struct sk_buff *skb, } } - if (WARN_ON_ONCE(len > skb->len)) + if (len > skb->len) return -EINVAL; if (pskb_trim(skb, len)) From ddae0bcb01e71c1184bb3f526f9622e31dc38f3b Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Fri, 12 Jun 2026 17:25:44 -0700 Subject: [PATCH 0726/1433] wifi: mt76: mt76x02: report rx FCS errors to mac80211 When the fcsfail filter is enabled the hardware passes frames with a bad FCS up to the driver, but mt76x02_mac_process_rx() never checks MT_RXINFO_CRCERR and hands them to mac80211 without RX_FLAG_FAILED_FCS_CRC. In monitor mode the radiotap flags byte then never gets IEEE80211_RADIOTAP_F_BADFCS set and corrupted frames cannot be told apart from clean ones. Set RX_FLAG_FAILED_FCS_CRC from the descriptor CRC error bit, matching mt7603, mt7615, mt7915, mt7921, mt7925 and mt7996. Reported-by: 0072a70 <90307219+0072a70@users.noreply.github.com> Closes: https://github.com/morrownr/mt76/issues/38 Tested-by: 0072a70 <90307219+0072a70@users.noreply.github.com> Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260613002544.27750-3-lucid_duck@justthetip.ca Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76x02_mac.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c b/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c index aa525adb6743..21f8b1e64101 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c @@ -792,6 +792,9 @@ int mt76x02_mac_process_rx(struct mt76x02_dev *dev, struct sk_buff *skb, if (rxinfo & MT_RXINFO_L2PAD) pad_len += 2; + if (rxinfo & MT_RXINFO_CRCERR) + status->flag |= RX_FLAG_FAILED_FCS_CRC; + if (rxinfo & MT_RXINFO_DECRYPT) { status->flag |= RX_FLAG_DECRYPTED; status->flag |= RX_FLAG_MMIC_STRIPPED; From 31beda21fbe569435ba8e080dc61694458e877b8 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Tue, 9 Jun 2026 21:24:26 -0700 Subject: [PATCH 0727/1433] wifi: mt76: mt7925: remove code guarded by nonexistent config option A small piece of code in mt7925/regs.h depends on CONFIG_MT76_DEV, which has never been defined in the kernel. Remove this dead code. Discovered while searching for CONFIG_* symbols referenced in code but not defined in any Kconfig file. Signed-off-by: Ethan Nelson-Moore Reviewed-by: Matthias Brugger Link: https://patch.msgid.link/20260610042429.222717-1-enelsonmoore@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/regs.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regs.h b/drivers/net/wireless/mediatek/mt76/mt7925/regs.h index cb937d565a80..ab6c33b4a180 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/regs.h @@ -88,11 +88,7 @@ #define MT_HIF_REMAP_BASE_L1 0x130000 #define MT_HIF_REMAP_L2 0x0120 -#if IS_ENABLED(CONFIG_MT76_DEV) -#define MT_HIF_REMAP_BASE_L2 (0x7c500000 - (0x7c000000 - 0x18000000)) -#else #define MT_HIF_REMAP_BASE_L2 0x18500000 -#endif #define MT_WFSYS_SW_RST_B 0x7c000140 From 429e516d4f8887cfa8dc65fef92d021ddc72e22c Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Tue, 9 Jun 2026 21:10:47 -0700 Subject: [PATCH 0728/1433] wifi: mt76: mt7996: remove code guarded by nonexistent config option A small piece of code in mt7996.h depends on CONFIG_MTK_DEBUG, which has never been defined in the kernel. Remove this dead code. Discovered while searching for CONFIG_* symbols referenced in code but not defined in any Kconfig file. Signed-off-by: Ethan Nelson-Moore Reviewed-by: Matthias Brugger Link: https://patch.msgid.link/20260610041050.206950-1-enelsonmoore@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 0d6488522ba7..d364fa8b8cf9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -941,10 +941,6 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, bool hif2, int *irq); u32 mt7996_wed_init_buf(void *ptr, dma_addr_t phys, int token_id); -#ifdef CONFIG_MTK_DEBUG -int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir); -#endif - int mt7996_dma_rro_init(struct mt7996_dev *dev); void mt7996_dma_rro_start(struct mt7996_dev *dev); From 965cbdbdfbd37a659a05ff88e49c216975bd9d2e Mon Sep 17 00:00:00 2001 From: David Bauer Date: Thu, 11 Jun 2026 23:56:56 +0200 Subject: [PATCH 0729/1433] wifi: mt76: mt7603: free beacon SKB on error The SKB containing the generated beacon is not freed when the beacon queue is deected stuck and scheduled for recovery. Fixes potential memory leaks in case the beacon queue is detected stuck. Signed-off-by: David Bauer Link: https://patch.msgid.link/20260611215658.259324-1-mail@david-bauer.net Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7603/beacon.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/beacon.c b/drivers/net/wireless/mediatek/mt76/mt7603/beacon.c index 300a7f9c2ef1..acca98139f92 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/beacon.c +++ b/drivers/net/wireless/mediatek/mt76/mt7603/beacon.c @@ -56,6 +56,7 @@ mt7603_update_beacon_iter(void *priv, u8 *mac, struct ieee80211_vif *vif) FIELD_PREP(MT_DMA_FQCR0_TARGET_QID, MT_TX_HW_QUEUE_BCN)); if (!mt76_poll(dev, MT_DMA_FQCR0, MT_DMA_FQCR0_BUSY, 0, 5000)) { dev->beacon_check = MT7603_WATCHDOG_TIMEOUT; + dev_kfree_skb(skb); goto out; } @@ -63,6 +64,7 @@ mt7603_update_beacon_iter(void *priv, u8 *mac, struct ieee80211_vif *vif) FIELD_PREP(MT_DMA_FQCR0_TARGET_QID, MT_TX_HW_QUEUE_BMC)); if (!mt76_poll(dev, MT_DMA_FQCR0, MT_DMA_FQCR0_BUSY, 0, 5000)) { dev->beacon_check = MT7603_WATCHDOG_TIMEOUT; + dev_kfree_skb(skb); goto out; } From 70869cc429fffc77de51e7777c0ecb651e8fca07 Mon Sep 17 00:00:00 2001 From: Shayne Chen Date: Mon, 20 Jul 2026 17:01:02 +0800 Subject: [PATCH 0730/1433] wifi: mt76: fix handling channel context with different bands in mt76_switch_vif_chanctx() When performing channel switches on different radios within a short timeframe, channel contexts with different bands can be carried for each struct ieee80211_vif_chanctx_switch. Rework mt76_switch_vif_chanctx() to properly handle this scenario. Fixes: 82334623af0c ("wifi: mt76: add chanctx functions for multi-channel phy support") Co-developed-by: Rex Lu Signed-off-by: Rex Lu Signed-off-by: Shayne Chen Link: https://patch.msgid.link/20260720090102.190729-1-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/channel.c | 98 +++++++++++--------- 1 file changed, 52 insertions(+), 46 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/channel.c b/drivers/net/wireless/mediatek/mt76/channel.c index 6edcb3b8f279..28ad7bcaffd4 100644 --- a/drivers/net/wireless/mediatek/mt76/channel.c +++ b/drivers/net/wireless/mediatek/mt76/channel.c @@ -186,68 +186,74 @@ int mt76_switch_vif_chanctx(struct ieee80211_hw *hw, int n_vifs, enum ieee80211_chanctx_switch_mode mode) { - struct mt76_chanctx *old_ctx = (struct mt76_chanctx *)vifs->old_ctx->drv_priv; - struct mt76_chanctx *new_ctx = (struct mt76_chanctx *)vifs->new_ctx->drv_priv; - struct ieee80211_chanctx_conf *conf = vifs->new_ctx; - struct mt76_phy *old_phy = old_ctx->phy; - struct mt76_phy *phy = hw->priv; + struct ieee80211_vif_chanctx_switch *v; + struct mt76_chanctx *old_ctx, *new_ctx; + struct mt76_phy *old_phy, *phy = hw->priv; struct mt76_dev *dev = phy->dev; struct mt76_vif_link *mlink; - bool update_chan; + bool need_update[__MT_MAX_BAND] = {}; int i, ret = 0; - if (mode == CHANCTX_SWMODE_SWAP_CONTEXTS) - phy = new_ctx->phy = dev->band_phys[conf->def.chan->band]; - else - phy = new_ctx->phy; - if (!phy) - return -EINVAL; + for (i = 0; i < n_vifs; i++) { + v = &vifs[i]; + new_ctx = (struct mt76_chanctx *)v->new_ctx->drv_priv; + if (mode == CHANCTX_SWMODE_SWAP_CONTEXTS) + phy = new_ctx->phy = dev->band_phys[v->new_ctx->def.chan->band]; + else + phy = new_ctx->phy; - update_chan = phy->chanctx != new_ctx; - if (update_chan) { - if (dev->scan.phy == phy) - mt76_abort_scan(dev); + if (!phy) + return -EINVAL; - cancel_delayed_work_sync(&phy->mac_work); + if (need_update[phy->band_idx]) + continue; + + if (phy->chanctx != new_ctx) { + if (dev->scan.phy == phy) + mt76_abort_scan(dev); + + cancel_delayed_work_sync(&phy->mac_work); + need_update[phy->band_idx] = true; + } } mutex_lock(&dev->mutex); - if (mode == CHANCTX_SWMODE_SWAP_CONTEXTS && - phy != old_phy && old_phy->chanctx == old_ctx) - old_phy->chanctx = NULL; - - if (update_chan) - ret = mt76_phy_update_channel(phy, vifs->new_ctx); - - if (ret) - goto out; - - if (old_phy == phy) - goto skip_link_replace; - for (i = 0; i < n_vifs; i++) { - mlink = mt76_vif_conf_link(dev, vifs[i].vif, vifs[i].link_conf); + v = &vifs[i]; + old_ctx = (struct mt76_chanctx *)v->old_ctx->drv_priv; + old_phy = old_ctx->phy; + + new_ctx = (struct mt76_chanctx *)v->new_ctx->drv_priv; + phy = new_ctx->phy; + + if (mode == CHANCTX_SWMODE_SWAP_CONTEXTS && old_phy->chanctx && + old_phy->chanctx == old_ctx && phy != old_phy) + old_phy->chanctx = NULL; + + if (need_update[phy->band_idx]) { + ret = mt76_phy_update_channel(phy, v->new_ctx); + if (ret) + goto out; + + need_update[phy->band_idx] = false; + } + + mlink = mt76_vif_conf_link(dev, v->vif, v->link_conf); if (!mlink) continue; - dev->drv->vif_link_remove(old_phy, vifs[i].vif, - vifs[i].link_conf, mlink); + if (old_phy != phy) { + dev->drv->vif_link_remove(old_phy, v->vif, v->link_conf, + mlink); - ret = dev->drv->vif_link_add(phy, vifs[i].vif, - vifs[i].link_conf, mlink); - if (ret) - goto out; + ret = dev->drv->vif_link_add(phy, v->vif, v->link_conf, + mlink); + if (ret) + goto out; + } - } - -skip_link_replace: - for (i = 0; i < n_vifs; i++) { - mlink = mt76_vif_conf_link(dev, vifs[i].vif, vifs[i].link_conf); - if (!mlink) - continue; - - mlink->ctx = vifs->new_ctx; + mlink->ctx = v->new_ctx; if (mlink->beacon_mon_interval) WRITE_ONCE(mlink->beacon_mon_last, jiffies); } From 7e87dc3b2157967b14ded97129e677a764573ec7 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 13 Jun 2026 17:41:26 -0500 Subject: [PATCH 0731/1433] wifi: mt76: mt7925: stop init retries on hung bus Stop retrying hardware init once the bus is marked hung. The control path is no longer usable at that point, so more retries only issue failing device accesses, including MCU commands or register operations, and delay teardown. Exit early and let the failed device be torn down quickly. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260613224131.2396026-2-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/init.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/init.c b/drivers/net/wireless/mediatek/mt76/mt7925/init.c index 1b44f5c8fb0d..cd22fcc021b1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/init.c @@ -137,10 +137,18 @@ static int mt7925_init_hardware(struct mt792x_dev *dev) set_bit(MT76_STATE_INITIALIZED, &dev->mphy.state); for (i = 0; i < MT792x_MCU_INIT_RETRY_COUNT; i++) { + if (atomic_read(&dev->mt76.bus_hung)) { + ret = -EIO; + break; + } + ret = __mt7925_init_hardware(dev); if (!ret) break; + if (atomic_read(&dev->mt76.bus_hung)) + break; + mt792x_init_reset(dev); } From c781b74c3fd0c24db33ca7cfc0b9a3857d623543 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 13 Jun 2026 17:41:27 -0500 Subject: [PATCH 0732/1433] wifi: mt76: mt7925: skip reset work on hung bus Skip mt7925 reset handling once the bus is marked hung. A hung bus cannot be recovered by issuing another device reset. Continuing the reset path may only send more failing MCU or register accesses and delay teardown. Return early from reset work and the USB reset path so the failed device can be torn down quickly. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260613224131.2396026-3-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 6 ++++++ drivers/net/wireless/mediatek/mt76/mt7925/usb.c | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index a7eb80b22953..b9973c4eb7f6 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -1516,6 +1516,9 @@ void mt7925_mac_reset_work(struct work_struct *work) struct mt76_connac_pm *pm = &dev->pm; int i, ret; + if (atomic_read(&dev->mt76.bus_hung)) + return; + dev_dbg(dev->mt76.dev, "chip reset\n"); dev->hw_full_reset = true; ieee80211_stop_queues(hw); @@ -1533,6 +1536,9 @@ void mt7925_mac_reset_work(struct work_struct *work) break; } + if (atomic_read(&dev->mt76.bus_hung)) + return; + if (i == 10) dev_err(dev->mt76.dev, "chip reset failed\n"); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c index e9f58492bf7d..49ad4fe9eb1b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c @@ -81,6 +81,13 @@ static int mt7925u_mac_reset(struct mt792x_dev *dev) { int err; + if (atomic_read(&dev->mt76.bus_hung)) + return 0; + + mt792xu_reset_on_bus_error(dev); + if (atomic_read(&dev->mt76.bus_hung)) + return 0; + mt76_txq_schedule_all(&dev->mphy); mt76_worker_disable(&dev->mt76.tx_worker); From e137e5fd245cb6c0ae378607cc8090c913d984b2 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 13 Jun 2026 17:41:28 -0500 Subject: [PATCH 0733/1433] wifi: mt76: mt792x: stop USB register access after bus hang Mark the mt792x USB bus hung on the first control timeout and switch register access to no-op bus ops. Each failed vendor request may spend up to MT_VEND_REQ_MAX_RETRY * MT_VEND_REQ_TOUT_MS, about 3 seconds, and teardown/reset paths can keep issuing such requests after the device has stopped responding. Also skip the USB WFSYS reset path after bus_hung is set, since it uses UHW vendor requests as well. mt7925u 1-2:1.3: vendor request req:63 off:0018 failed:-110 mt7925u 1-2:1.3: vendor request req:63 off:0018 failed:-110 mt7925u 1-2:1.3: vendor request req:63 off:0018 failed:-110 mt7925u 1-2:1.3: vendor request req:63 off:0018 failed:-110 mt7925u 1-2:1.3: vendor request req:63 off:0018 failed:-110 Avoid repeating those register reads after the bus is known to be hung by switching register access to no-op handlers. Fixes: 0d2afe09fad5 ("mt76: mt7921: add mt7921u driver") Fixes: c948b5da6bbe ("wifi: mt76: mt7925: add Mediatek Wi-Fi7 driver for mt7925 chips") Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260613224131.2396026-4-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76.h | 1 + .../net/wireless/mediatek/mt76/mt792x_usb.c | 91 ++++++++++++++++--- drivers/net/wireless/mediatek/mt76/usb.c | 11 +++ 3 files changed, 90 insertions(+), 13 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 6cff136407d8..640061276d76 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -672,6 +672,7 @@ struct mt76_usb { u8 out_ep[__MT_EP_OUT_MAX]; u8 in_ep[__MT_EP_IN_MAX]; + void (*ctrl_timeout)(struct mt76_dev *dev, int err); bool sg_en; struct mt76u_mcu { diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c index 910132e94956..47f80c9ec4e7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c @@ -31,10 +31,75 @@ static void mt792xu_reset_work(struct work_struct *work) atomic_set(&dev->usb_reset_pending, 0); } +static void mt792xu_queue_usb_reset(struct mt792x_dev *dev, int err) +{ + if (!atomic_xchg(&dev->usb_reset_pending, 1)) { + dev_warn(dev->mt76.dev, + "USB transport access failed (%d), queueing device reset\n", + err); + + schedule_work(&dev->usb_reset_work); + } +} + +static u32 mt792xu_bus_hung_rr(struct mt76_dev *mdev, u32 offset) +{ + return 0; +} + +static void mt792xu_bus_hung_wr(struct mt76_dev *mdev, u32 offset, u32 val) +{ +} + +static u32 mt792xu_bus_hung_rmw(struct mt76_dev *mdev, u32 offset, + u32 mask, u32 val) +{ + return 0; +} + +static void mt792xu_bus_hung_write_copy(struct mt76_dev *mdev, u32 offset, + const void *data, int len) +{ +} + +static void mt792xu_bus_hung_read_copy(struct mt76_dev *mdev, u32 offset, + void *data, int len) +{ + memset(data, 0, len); +} + +static const struct mt76_bus_ops mt792xu_bus_hung_ops = { + .rr = mt792xu_bus_hung_rr, + .wr = mt792xu_bus_hung_wr, + .rmw = mt792xu_bus_hung_rmw, + .write_copy = mt792xu_bus_hung_write_copy, + .read_copy = mt792xu_bus_hung_read_copy, + .type = MT76_BUS_USB, +}; + +static void mt792xu_set_bus_hung(struct mt792x_dev *dev) +{ + atomic_set(&dev->mt76.bus_hung, true); + + if (READ_ONCE(dev->mt76.bus) == &mt792xu_bus_hung_ops) + return; + + WRITE_ONCE(dev->mt76.bus, &mt792xu_bus_hung_ops); +} + +static void mt792xu_ctrl_timeout(struct mt76_dev *mdev, int err) +{ + struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76); + + mt792xu_set_bus_hung(dev); + mt792xu_queue_usb_reset(dev, err); +} + void mt792xu_reset_work_init(struct mt792x_dev *dev) { INIT_WORK(&dev->usb_reset_work, mt792xu_reset_work); atomic_set(&dev->usb_reset_pending, 0); + dev->mt76.usb.ctrl_timeout = mt792xu_ctrl_timeout; } EXPORT_SYMBOL_GPL(mt792xu_reset_work_init); @@ -62,26 +127,23 @@ EXPORT_SYMBOL_GPL(mt792xu_check_bus); int mt792xu_reset_on_bus_error(struct mt792x_dev *dev) { - int err = 0; + int err; - if (!atomic_read(&dev->mt76.bus_hung)) - err = mt792xu_check_bus(dev); + /* Once hung, the no-op bus ops stay installed until the queued USB + * reset re-probes the device. Do not clear bus_hung here, or the caller + * would run a full reset over dropped register I/O and report success. + */ + if (atomic_read(&dev->mt76.bus_hung)) + return -EIO; + err = mt792xu_check_bus(dev); if (err) { - atomic_set(&dev->mt76.bus_hung, true); - - if (!atomic_xchg(&dev->usb_reset_pending, 1)) { - dev_warn(dev->mt76.dev, - "USB transport access failed (%d), queueing device reset\n", - err); - - schedule_work(&dev->usb_reset_work); - } + mt792xu_set_bus_hung(dev); + mt792xu_queue_usb_reset(dev, err); return err; } - atomic_set(&dev->mt76.bus_hung, false); return 0; } EXPORT_SYMBOL_GPL(mt792xu_reset_on_bus_error); @@ -344,6 +406,9 @@ int mt792xu_wfsys_reset(struct mt792x_dev *dev) u32 val; int i; + if (atomic_read(&dev->mt76.bus_hung)) + return -EIO; + mt792xu_epctl_rst_opt(dev, false); val = mt792xu_uhw_rr(&dev->mt76, desc->rst_reg); diff --git a/drivers/net/wireless/mediatek/mt76/usb.c b/drivers/net/wireless/mediatek/mt76/usb.c index e54b35e53b6a..a9af3aa6b80a 100644 --- a/drivers/net/wireless/mediatek/mt76/usb.c +++ b/drivers/net/wireless/mediatek/mt76/usb.c @@ -30,6 +30,8 @@ int __mt76u_vendor_request(struct mt76_dev *dev, u8 req, u8 req_type, for (i = 0; i < MT_VEND_REQ_MAX_RETRY; i++) { if (test_bit(MT76_REMOVED, &dev->phy.state)) return -EIO; + if (dev->usb.ctrl_timeout && atomic_read(&dev->bus_hung)) + return -EIO; ret = usb_control_msg(udev, pipe, req, req_type, val, offset, buf, len, MT_VEND_REQ_TOUT_MS); @@ -42,6 +44,15 @@ int __mt76u_vendor_request(struct mt76_dev *dev, u8 req, u8 req_type, dev_err(dev->dev, "vendor request req:%02x off:%04x failed:%d\n", req, offset, ret); + + if (dev->usb.ctrl_timeout) { + atomic_set(&dev->bus_hung, true); + dev_err(dev->dev, "vendor request req:%02x off:%04x timed out, marking bus hung\n", + req, offset); + dev->usb.ctrl_timeout(dev, ret); + return ret; + } + return ret; } EXPORT_SYMBOL_GPL(__mt76u_vendor_request); From a4803d1801c701603e2403ba5eb2b8ed55667e58 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 13 Jun 2026 17:41:29 -0500 Subject: [PATCH 0734/1433] wifi: mt76: mt792x: drain USB UDMA before WFSYS reset Stop USB UDMA RX/TX and wait for idle before WFSYS reset. Warn if the engine remains busy. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260613224131.2396026-5-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt792x_usb.c | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c index 47f80c9ec4e7..9f7b307a2b33 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c @@ -11,6 +11,8 @@ #include "mt792x.h" #include "mt76_connac2_mac.h" +#define MT792X_USB_UDMA_IDLE_TIMEOUT 1000 + static int mt792xu_read32(struct mt76_dev *dev, u32 addr, void *buf) { return __mt76u_vendor_request(dev, MT_VEND_READ_EXT, @@ -343,6 +345,23 @@ static void mt792xu_epctl_rst_opt(struct mt792x_dev *dev, bool reset) mt792xu_uhw_wr(&dev->mt76, MT_SSUSB_EPCTL_CSR_EP_RST_OPT, val); } +static void mt792xu_wait_udma_idle(struct mt792x_dev *dev) +{ + u32 mask = MT_WL_RX_BUSY | MT_WL_TX_BUSY; + u32 val; + + mt76_set(dev, MT_UDMA_WLCFG_0, MT_WL_RX_FLUSH); + + if (mt76_poll_msec(dev, MT_UDMA_WLCFG_0, mask, 0, + MT792X_USB_UDMA_IDLE_TIMEOUT)) + return; + + val = mt76_rr(dev, MT_UDMA_WLCFG_0); + + dev_warn(dev->mt76.dev, + "UDMA busy before WFSYS reset: WLCFG0=0x%08x\n", val); +} + struct mt792xu_wfsys_desc { u32 rst_reg; u32 done_reg; @@ -409,6 +428,7 @@ int mt792xu_wfsys_reset(struct mt792x_dev *dev) if (atomic_read(&dev->mt76.bus_hung)) return -EIO; + mt792xu_wait_udma_idle(dev); mt792xu_epctl_rst_opt(dev, false); val = mt792xu_uhw_rr(&dev->mt76, desc->rst_reg); From b994cb409f21d5f82290370ebae8a035e7bff531 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 13 Jun 2026 17:41:30 -0500 Subject: [PATCH 0735/1433] wifi: mt76: mt792x: enable USB UDMA TX timeout Configure the USB UDMA TX timeout limit and enable timeout detection during DMA initialization, matching the vendor driver setup. Use a longer timeout to avoid false alarms. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260613224131.2396026-6-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt792x_usb.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c index 9f7b307a2b33..5b0b04106133 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c @@ -11,6 +11,7 @@ #include "mt792x.h" #include "mt76_connac2_mac.h" +#define MT792X_USB_TX_TIMEOUT_LIMIT 50000 #define MT792X_USB_UDMA_IDLE_TIMEOUT 1000 static int mt792xu_read32(struct mt76_dev *dev, u32 addr, void *buf) @@ -400,6 +401,10 @@ int mt792xu_dma_init(struct mt792x_dev *dev, bool resume) mt76_set(dev, MT_UDMA_WLCFG_0, MT_WL_RX_EN | MT_WL_TX_EN | MT_WL_RX_MPSZ_PAD0 | MT_TICK_1US_EN); + mt76_rmw(dev, MT_UDMA_WLCFG_1, MT_WL_TX_TMOUT_LMT, + FIELD_PREP(MT_WL_TX_TMOUT_LMT, + MT792X_USB_TX_TIMEOUT_LIMIT)); + mt76_set(dev, MT_UDMA_WLCFG_0, MT_WL_TX_TMOUT_FUNC_EN); mt76_clear(dev, MT_UDMA_WLCFG_0, MT_WL_RX_AGG_TO | MT_WL_RX_AGG_LMT); mt76_clear(dev, MT_UDMA_WLCFG_1, MT_WL_RX_AGG_PKT_LMT); From 7910bd565d8b287fd93888e0f5eb00e7067c91e4 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 13 Jun 2026 17:41:31 -0500 Subject: [PATCH 0736/1433] wifi: mt76: mt792x: quiesce USB paths on disconnect USB disconnect can leave reset/init work, TX worker, and MCU waiters active while the device is being removed. Stop those paths before unregistering the device to avoid teardown waiting on firmware or queue activity after disconnect. Run WFSYS reset after USB queue deinit so removal does not issue the reset while USB traffic may still be queued. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260613224131.2396026-7-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt792x_usb.c | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c index 5b0b04106133..850f6b2cfc4a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c @@ -234,9 +234,9 @@ EXPORT_SYMBOL_GPL(mt792xu_mcu_power_on); static void mt792xu_cleanup(struct mt792x_dev *dev) { clear_bit(MT76_STATE_INITIALIZED, &dev->mphy.state); - mt792xu_wfsys_reset(dev); skb_queue_purge(&dev->mt76.mcu.res_q); mt76u_queues_deinit(&dev->mt76); + mt792xu_wfsys_reset(dev); } static u32 mt792xu_uhw_rr(struct mt76_dev *dev, u32 addr) @@ -498,13 +498,27 @@ void mt792xu_disconnect(struct usb_interface *usb_intf) { struct mt792x_dev *dev = usb_get_intfdata(usb_intf); - mt792xu_reset_work_cleanup(dev); - cancel_work_sync(&dev->init_work); - if (!test_bit(MT76_STATE_INITIALIZED, &dev->mphy.state)) + if (!dev) return; + set_bit(MT76_RESET, &dev->mphy.state); + set_bit(MT76_MCU_RESET, &dev->mphy.state); + clear_bit(MT76_STATE_RUNNING, &dev->mphy.state); + wake_up(&dev->mt76.mcu.wait); + skb_queue_purge(&dev->mt76.mcu.res_q); + + cancel_work_sync(&dev->reset_work); + cancel_work_sync(&dev->init_work); + mt76_worker_disable(&dev->mt76.tx_worker); + mt792xu_reset_work_cleanup(dev); + if (!test_bit(MT76_STATE_INITIALIZED, &dev->mphy.state)) { + set_bit(MT76_REMOVED, &dev->mphy.state); + return; + } + mt76_unregister_device(&dev->mt76); mt792xu_cleanup(dev); + set_bit(MT76_REMOVED, &dev->mphy.state); usb_set_intfdata(usb_intf, NULL); From 9417c5818a0146980c2608fda94c908e604eb033 Mon Sep 17 00:00:00 2001 From: Laxman Acharya Padhya Date: Tue, 21 Jul 2026 10:17:40 +0000 Subject: [PATCH 0737/1433] wifi: mt76: mt7921: validate CLC firmware records The CLC region is supplied by firmware, but the loader trusts the region count and each record length. A malformed image can make the region table pointer precede the firmware buffer, make the record loop fail to advance, or index phy->clc past its end. Validate the table and record bounds before dereferencing or copying. Fixes: 23bdc5d8cadf ("wifi: mt76: mt7921: introduce Country Location Control support") Signed-off-by: Laxman Acharya Padhya Link: https://patch.msgid.link/CAMyXUJmh=WfwC4_KHupNxYR5e2Gy5QhBDL5TSG6XEW-XLa+X4Q@mail.gmail.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7921/mcu.c | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c index 25b9437250f7..564dd836e0b3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c @@ -415,7 +415,8 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name) struct mt76_dev *mdev = &dev->mt76; struct mt792x_phy *phy = &dev->phy; const struct firmware *fw; - int ret, i, len, offset = 0; + size_t clc_len, fw_data_len, len, offset = 0; + int ret, i; u8 *clc_base = NULL, hw_encap = 0; dev->phy.clc_chan_conf = 0xff; @@ -441,13 +442,21 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name) } hdr = (const void *)(fw->data + fw->size - sizeof(*hdr)); + if (hdr->n_region > (fw->size - sizeof(*hdr)) / sizeof(*region)) { + dev_err(mdev->dev, "Invalid firmware region table\n"); + ret = -EINVAL; + goto out; + } + fw_data_len = fw->size - sizeof(*hdr) - + hdr->n_region * sizeof(*region); + for (i = 0; i < hdr->n_region; i++) { region = (const void *)((const u8 *)hdr - (hdr->n_region - i) * sizeof(*region)); len = le32_to_cpu(region->len); /* check if we have valid buffer size */ - if (offset + len > fw->size) { + if (len > fw_data_len - offset) { dev_err(mdev->dev, "Invalid firmware region\n"); ret = -EINVAL; goto out; @@ -464,8 +473,19 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name) if (!clc_base) goto out; - for (offset = 0; offset < len; offset += le32_to_cpu(clc->len)) { + for (offset = 0; offset < len; offset += clc_len) { + if (len - offset < sizeof(*clc)) { + ret = -EINVAL; + goto out; + } + clc = (const struct mt7921_clc *)(clc_base + offset); + clc_len = le32_to_cpu(clc->len); + if (clc_len < sizeof(*clc) || clc_len > len - offset || + clc->idx >= ARRAY_SIZE(phy->clc)) { + ret = -EINVAL; + goto out; + } /* do not init buf again if chip reset triggered */ if (phy->clc[clc->idx]) @@ -477,7 +497,7 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name) continue; phy->clc[clc->idx] = devm_kmemdup(mdev->dev, clc, - le32_to_cpu(clc->len), + clc_len, GFP_KERNEL); if (!phy->clc[clc->idx]) { From 653c6e289b13cc6942f3e8f8e3c568e70fa42d1f Mon Sep 17 00:00:00 2001 From: Laxman Acharya Padhya Date: Mon, 13 Jul 2026 17:39:12 +0545 Subject: [PATCH 0738/1433] wifi: mt76: mt7996: validate default EEPROM firmware size The default EEPROM firmware is parsed and copied as a full EEPROM without checking its length. A truncated file can make the driver read beyond the firmware buffer during variant validation or the fallback copy. Reject files shorter than MT7996_EEPROM_SIZE before parsing or copying the firmware. Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices") Cc: stable@vger.kernel.org Signed-off-by: Laxman Acharya Padhya Link: https://patch.msgid.link/20260713115412.67095-1-acharyalaxman8848@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c b/drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c index ac05f7d75d63..ec33521db564 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c @@ -150,6 +150,12 @@ mt7996_eeprom_check_or_use_default(struct mt7996_dev *dev, bool use_default) goto out; } + if (fw->size < MT7996_EEPROM_SIZE) { + dev_err(dev->mt76.dev, "Invalid default bin size\n"); + ret = -EINVAL; + goto out; + } + if (!use_default && mt7996_eeprom_variant_valid(dev, fw->data)) goto out; From 8a177dabeeead47dda4d4f91331282790eed0097 Mon Sep 17 00:00:00 2001 From: Zhi-Jun You Date: Wed, 15 Jul 2026 23:21:12 +0800 Subject: [PATCH 0739/1433] wifi: mt76: wed: fix kernel panic on non-DBDC MT7986 In mt76_wed_init_rx_buf, it's hardcoded to use MT_RXQ_MAIN. But for non-DBDC MT7986 MT_RXQ_BAND1 is used for RX data queue which leads to kernel panic when attaching WED. Use the correct RX queue by checking WED version and band_idx. v2 and band 1 -> MT_RXQ_BAND1 Others -> MT_RXQ_MAIN Kernel panic: Unable to handle kernel access to user memory outside uaccess routines at virtual address 0000000000000000 Mem abort info: ESR = 0x0000000096000005 EC = 0x25: DABT (current EL), IL = 32 bits SET = 0, FnV = 0 EA = 0, S1PTW = 0 FSC = 0x05: level 1 translation fault Data abort info: ISV = 0, ISS = 0x00000005, ISS2 = 0x00000000 CM = 0, WnR = 0, TnD = 0, TagAccess = 0 GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0 Internal error: Oops: 0000000096000005 [#1] SMP CPU: 1 UID: 0 PID: 925 Comm: kmodloader Tainted: G O 6.18.26 #0 NONE Tainted: [O]=OOT_MODULE Hardware name: Acer Connect Vero W6m (DT) pstate: 40400005 (nZcv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--) pc : page_pool_alloc_frag_netmem+0x1c/0x1bc lr : page_pool_alloc_frag+0xc/0x34 sp : ffffffc081dab660 x29: ffffffc081dab660 x28: ffffffc081dabc60 x27: ffffff80091af040 x26: 0000008000000000 x25: ffffff80091a8898 x24: ffffff80091a5440 x23: 0000000000001000 x22: 0000000140000000 x21: ffffff80091a2040 x20: ffffff8003f1d780 x19: 0000000000000000 x18: 0000000000000020 x17: ffffffbfbf0ac000 x16: ffffffc080ee0000 x15: ffffff80049d47ca x14: 000000000000037b x13: 000000000000037b x12: 0000000000000001 x11: 0000000000000000 x10: 0000000000000000 x9 : 0000000000000000 x8 : ffffff8003f1d7c0 x7 : 0000000000000000 x6 : ffffff8003f1d780 x5 : 0000000000000680 x4 : 0000000000000000 x3 : 0000000000002824 x2 : 0000000000000000 x1 : ffffffc081dab71c x0 : 0000000000000000 Call trace: page_pool_alloc_frag_netmem+0x1c/0x1bc (P) page_pool_alloc_frag+0xc/0x34 mt76_wed_init_rx_buf+0xf8/0x2ac [mt76] mtk_wed_start+0x79c/0x12ac mt7915_dma_start+0x274/0x63c [mt7915e] mt7915_dma_start+0x5b4/0x63c [mt7915e] mt7915_dma_init+0x49c/0x81c [mt7915e] mt7915_register_device+0x24c/0x530 [mt7915e] mt7915_mmio_probe+0x91c/0x1980 [mt7915e] platform_probe+0x58/0xa0 really_probe+0xb8/0x2a8 __driver_probe_device+0x74/0x118 driver_probe_device+0x3c/0xe0 __driver_attach+0x88/0x154 bus_for_each_dev+0x60/0xb0 driver_attach+0x20/0x28 bus_add_driver+0xdc/0x200 driver_register+0x64/0x118 __platform_driver_register+0x20/0x30 init_module+0x74/0x1000 [mt7915e] do_one_initcall+0x4c/0x1f8 do_init_module+0x50/0x210 load_module+0x15f8/0x1b10 __do_sys_init_module+0x1a8/0x260 __arm64_sys_init_module+0x18/0x20 invoke_syscall.constprop.0+0x4c/0xd0 do_el0_svc+0x3c/0xd0 el0_svc+0x18/0x60 el0t_64_sync_handler+0x98/0xdc el0t_64_sync+0x158/0x15c Code: aa0003f3 a9025bf5 a90363f7 d2820017 (b9400000) Signed-off-by: Zhi-Jun You Link: https://patch.msgid.link/20260715152113.553-1-hujy652@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/wed.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/wed.c b/drivers/net/wireless/mediatek/mt76/wed.c index ed657d952de2..f210a0c57d81 100644 --- a/drivers/net/wireless/mediatek/mt76/wed.c +++ b/drivers/net/wireless/mediatek/mt76/wed.c @@ -33,10 +33,15 @@ u32 mt76_wed_init_rx_buf(struct mtk_wed_device *wed, int size) { struct mtk_wed_bm_desc *desc = wed->rx_buf_ring.desc; struct mt76_dev *dev = mt76_wed_to_dev(wed); - struct mt76_queue *q = &dev->q_rx[MT_RXQ_MAIN]; struct mt76_txwi_cache *t = NULL; + struct mt76_queue *q; int i; + if (wed->version == 2 && dev->phy.band_idx) + q = &dev->q_rx[MT_RXQ_BAND1]; + else + q = &dev->q_rx[MT_RXQ_MAIN]; + for (i = 0; i < size; i++) { dma_addr_t addr; u32 offset; From bade0d238b60c29dafcc7da17501fa489495d6ae Mon Sep 17 00:00:00 2001 From: Zhi-Jun You Date: Wed, 15 Jul 2026 23:21:13 +0800 Subject: [PATCH 0740/1433] wifi: mt76: mt7915: fix net_fill_forward_path for non-DBDC mt7986 Current implementation assumes that the hardware supports DBDC or single band and binds to band0. This causes net_fill_forward_path to select the wrong queue for non-DBDC mt7986 because it binds to band1 and getting the following in dmesg: ieee80211 phy2: WA: --> drop by reaseon:1, msdu id = 0xc002 but failed! mtk_wed1: error status=00000002 ieee80211 phy2: WA: txblk 10324e00 len = 128 DW0 : 10 00 00 00 DW1 : 00 00 00 00 DW2 : 00 00 00 00 DW3 : 72 0f 94 68 DW4 : 00 00 00 00 DW5 : ff 03 00 00 DW6 : 00 00 3c 40 DW7 : 00 17 dd 14 DW8 : 79 6f 00 00 DW9 : 02 c0 00 00 DW10 : 58 c5 34 10 DW11 : 00 00 00 00 DW12 : 00 06 3e 00 DW13 : 00 00 00 80 DW14 : 10 8c 00 00 DW15 : 00 00 00 00 DW16 : 00 00 00 00 DW17 : 00 00 00 00 DW18 : 00 00 00 00 DW19 : 00 00 00 00 DW20 : 00 00 00 00 DW21 : 00 00 00 00 DW22 : 00 00 00 00 DW23 : 00 00 00 00 DW24 : 00 00 00 00 DW25 : 00 00 00 00 DW26 : 00 00 00 00 DW27 : 00 00 00 00 DW28 : 00 00 00 00 DW29 : 00 00 00 00 DW30 : 00 00 00 00 DW31 : 00 00 00 00 Fix it by using phy->mt76->band_idx for queue which works for both non-DBDC and DBDC devices. Fixes: f68d67623dec ("mt76: mt7915: add Wireless Ethernet Dispatch support") Suggested-by: Benjamin Larsson Signed-off-by: Zhi-Jun You Link: https://patch.msgid.link/20260715152113.553-2-hujy652@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c index 51643a48ed15..044b592efe28 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c @@ -1743,7 +1743,7 @@ mt7915_net_fill_forward_path(struct ieee80211_hw *hw, path->mtk_wdma.wdma_idx = wed->wdma_idx; path->mtk_wdma.bss = mvif->mt76.idx; path->mtk_wdma.wcid = is_mt7915(&dev->mt76) ? msta->wcid.idx : 0x3ff; - path->mtk_wdma.queue = phy != &dev->phy; + path->mtk_wdma.queue = phy->mt76->band_idx; ctx->dev = NULL; From 4361f1660101270611eb5dc39799f620b35f2e4d Mon Sep 17 00:00:00 2001 From: Prashant Rahul Date: Thu, 16 Jul 2026 21:55:10 +0530 Subject: [PATCH 0741/1433] wifi: mt76: mt7921: fix memory leak when skb_linearize fails in mcu rx event The ownership of sk_buff skb is passed to mt7921_queue_rx_skb, each path inside it under the switch case handles cleaning of skb and it is true for mt7921_mcu_rx_event as well. mt7921_mcu_rx_event, on a success path, either queues skb via mt76_mcu_rx_event or cleans it immediately inside mt7921_mcu_uni_rx_unsolicited_event. However inside mt7921_mcu_rx_event, if skb_linearize fails, the function returns immediately and never bothers cleaning skb which leaks skb. Since skb is fully owned at this point, it is safe to call dev_kfree_skb which fixes the leak. Granted, the skb_linearize failure is rare as it can only fail under heavy memory usage, but at the same time, leaking memory under heavy memory usage can worsen the OOM condition. Signed-off-by: Prashant Rahul Link: https://patch.msgid.link/20260716-mt7921-mem-leak-v1-1-6e9c0ea19f63@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/mcu.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c index 564dd836e0b3..17d97407b8f3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c @@ -351,8 +351,10 @@ void mt7921_mcu_rx_event(struct mt792x_dev *dev, struct sk_buff *skb) { struct mt76_connac2_mcu_rxd *rxd; - if (skb_linearize(skb)) + if (skb_linearize(skb)) { + dev_kfree_skb(skb); return; + } rxd = (struct mt76_connac2_mcu_rxd *)skb->data; From 44b5adfe49499f53002737f5fe81d608c08122fc Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Thu, 25 Jun 2026 07:10:26 -0500 Subject: [PATCH 0742/1433] wifi: mt76: mt7915: bound the device EEPROM address before the EFUSE copy mt7915_mcu_get_eeprom() copies a fixed EFUSE block into the driver's dev->mt76.eeprom.data buffer at the offset reported by the MCU response (res->addr, a device-controlled __le32) without checking it against the buffer size. A malicious or malfunctioning device can report an arbitrary address and drive a 16-byte out-of-bounds write past eeprom.data. Reject a response whose address would place the copy outside eeprom.data before deriving the destination pointer. Devices that echo the requested in-bounds offset are unaffected. Fixes: e57b7901469f ("mt76: add mac80211 driver for MT7915 PCIe-based chipsets") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Link: https://patch.msgid.link/20260625-b4-disp-16f99062-v1-1-aee52ecf61b9@proton.me Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/mcu.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c index e8fe86f93309..bbb2fedacb25 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c @@ -2917,8 +2917,15 @@ int mt7915_mcu_get_eeprom(struct mt7915_dev *dev, u32 offset, u8 *read_buf) return ret; res = (struct mt7915_mcu_eeprom_info *)skb->data; - if (!buf) - buf = dev->mt76.eeprom.data + le32_to_cpu(res->addr); + if (!buf) { + u32 addr = le32_to_cpu(res->addr); + + if (addr > dev->mt76.eeprom.size - MT7915_EEPROM_BLOCK_SIZE) { + dev_kfree_skb(skb); + return -EINVAL; + } + buf = dev->mt76.eeprom.data + addr; + } memcpy(buf, res->data, MT7915_EEPROM_BLOCK_SIZE); dev_kfree_skb(skb); From 13b3c29a782033ce4a230be9e5618032813dbcd4 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Thu, 25 Jun 2026 07:10:27 -0500 Subject: [PATCH 0743/1433] wifi: mt76: mt7996: bound the device EEPROM address before the EFUSE copy mt7996_mcu_get_eeprom() derives the destination of the EFUSE/EXT block copy from the address reported by the MCU response (event->addr, a device-controlled __le32) and clamps only the copy length, never the destination offset into dev->mt76.eeprom.data. A malicious or malfunctioning device can report an arbitrary address and drive an out-of-bounds write of up to MT7996_EXT_EEPROM_BLOCK_SIZE bytes past eeprom.data. Reject a response whose address would place the copy outside eeprom.data before deriving the destination pointer. Devices that echo the requested in-bounds offset are unaffected. Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Link: https://patch.msgid.link/20260625-b4-disp-16f99062-v1-2-aee52ecf61b9@proton.me Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 2e83f4b79c87..a1bae5db8500 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -4352,11 +4352,18 @@ int mt7996_mcu_get_eeprom(struct mt7996_dev *dev, u32 offset, u8 *buf, u32 buf_l event = (struct mt7996_mcu_eeprom_access_event *)skb->data; if (event->valid) { u32 ret_len = le32_to_cpu(event->eeprom.ext_eeprom.data_len); + u32 block = mode == EEPROM_MODE_EXT ? MT7996_EXT_EEPROM_BLOCK_SIZE : + MT7996_EEPROM_BLOCK_SIZE; addr = le32_to_cpu(event->addr); - if (!buf) + if (!buf) { + if (addr > dev->mt76.eeprom.size - block) { + dev_kfree_skb(skb); + return -EINVAL; + } buf = (u8 *)dev->mt76.eeprom.data + addr; + } switch (mode) { case EEPROM_MODE_EFUSE: From 3c004f73d68b415d231189b75a3cff1582a17e90 Mon Sep 17 00:00:00 2001 From: Kenneth Kasilag Date: Sat, 20 Jun 2026 01:38:50 +0000 Subject: [PATCH 0744/1433] wifi: mt76: mt7996: expose per-band MAC addresses to cfg80211 mt7996/mt7992 are single-wiphy, multi-band devices. The driver assigns each band its own MAC address from a per-band EEPROM entry, or derives it from the primary band's address when that entry is empty, however only the primary band's is published as perm_addr. The per-band addresses are not exposed to cfg80211, so consumers usually fall back to deriving one from perm_addr. Store each band's address in wiphy->addresses[], indexed by radio, so cfg80211 exposes the address the hardware actually uses for that radio. addresses[0] is the primary band and matches perm_addr, as cfg80211 requires. Link: https://github.com/openwrt/openwrt/issues/23578 Tested-on: Gemtek W1700K (MT7996) Signed-off-by: Kenneth Kasilag Link: https://patch.msgid.link/20260620013850.3949359-1-kenneth@kasilag.me Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 3 +++ drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index d6f9aa1ab52d..dbea4887b7ad 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -463,6 +463,8 @@ mt7996_init_wiphy_band(struct ieee80211_hw *hw, struct mt7996_phy *phy) radio->n_freq_range = 1; radio->iface_combinations = &if_comb; radio->n_iface_combinations = 1; + memcpy(dev->radio_addrs[n_radios].addr, phy->mt76->macaddr, ETH_ALEN); + hw->wiphy->n_addresses++; hw->wiphy->n_radio++; wiphy->available_antennas_rx |= phy->mt76->chainmask; @@ -505,6 +507,7 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed) wiphy->n_iface_combinations = 1; wiphy->radio = dev->radios; + wiphy->addresses = dev->radio_addrs; wiphy->reg_notifier = mt7996_regd_notifier; wiphy->flags |= WIPHY_FLAG_HAS_CHANNEL_SWITCH | diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index d364fa8b8cf9..e03699e968ff 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -411,6 +411,7 @@ struct mt7996_dev { struct mt7996_phy *radio_phy[MT7996_MAX_RADIOS]; struct wiphy_radio radios[MT7996_MAX_RADIOS]; struct wiphy_radio_freq_range radio_freqs[MT7996_MAX_RADIOS]; + struct mac_address radio_addrs[MT7996_MAX_RADIOS]; struct mt7996_hif *hif2; struct mt7996_reg_desc reg; From 217f9e7bb02558759be9d9ecfe532e9708741c50 Mon Sep 17 00:00:00 2001 From: Eason Lai Date: Wed, 1 Jul 2026 09:06:54 +0800 Subject: [PATCH 0745/1433] wifi: mt76: mt792x: fix use-after-free in mt76_rx_poll_complete A use-after-free issue occurs in mt76_rx_poll_complete due to a race condition. The STA has already been removed, but the rx_status still had a pointer to the wcid in the STA. Set the links' wcid pointers to be NULL for a MLD in mt7925_sta_pre_rcu_remove() BUG: KASAN: invalid-access in mt76_rx_poll_complete+0x280/0x470 Call trace: dump_backtrace+0xec/0x128 show_stack+0x18/0x28 dump_stack_lvl+0x40/0xc8 print_report+0x1b8/0x710 kasan_report+0xe0/0x144 do_bad_area+0x120/0x260 do_tag_check_fault+0x20/0x34 do_mem_abort+0x54/0xa8 el1_abort+0x3c/0x5c el1h_64_sync_handler+0x40/0xcc el1h_64_sync+0x7c/0x80 mt76_rx_poll_complete+0x280/0x470 mt76_dma_rx_poll+0x114/0x51c mt792x_poll_rx+0x60/0xf8 napi_threaded_poll_loop+0xe0/0x450 napi_threaded_poll+0x80/0x9c kthread+0x11c/0x158 ret_from_fork+0x10/0x20 Fixes: c948b5da6bbe ("wifi: mt76: mt7925: add Mediatek Wi-Fi7 driver for mt7925 chips") Signed-off-by: Eason Lai Link: https://patch.msgid.link/20260701010654.956863-1-eason.lai@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/main.c | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 6be5b60b9bac..abb56f6fea77 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -2661,6 +2661,40 @@ static int mt7925_nan_peer_sched_changed(struct ieee80211_hw *hw, return err; } +static void mt7925_sta_pre_rcu_remove(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mt76_phy *phy = hw->priv; + struct mt76_dev *dev = phy->dev; + struct mt76_wcid *wcid = (struct mt76_wcid *)sta->drv_priv; + + mutex_lock(&dev->mutex); + spin_lock_bh(&dev->status_lock); + + if (ieee80211_vif_is_mld(vif)) { + struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv; + struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; + unsigned long valid = mvif->valid_links; + struct mt792x_link_sta *mlink; + unsigned int link_id; + + for_each_set_bit(link_id, &valid, IEEE80211_MLD_MAX_NUM_LINKS) { + mlink = mt792x_sta_to_link(msta, link_id); + if (!mlink || !mlink->wcid.sta) + continue; + if (mlink->wcid.idx < ARRAY_SIZE(dev->wcid)) + rcu_assign_pointer(dev->wcid[mlink->wcid.idx], + NULL); + } + } else { + rcu_assign_pointer(dev->wcid[wcid->idx], NULL); + } + + spin_unlock_bh(&dev->status_lock); + mutex_unlock(&dev->mutex); +} + const struct ieee80211_ops mt7925_ops = { .tx = mt792x_tx, .start = mt7925_start, @@ -2673,7 +2707,7 @@ const struct ieee80211_ops mt7925_ops = { .start_ap = mt7925_start_ap, .stop_ap = mt7925_stop_ap, .sta_state = mt76_sta_state, - .sta_pre_rcu_remove = mt76_sta_pre_rcu_remove, + .sta_pre_rcu_remove = mt7925_sta_pre_rcu_remove, .set_key = mt7925_set_key, .sta_set_decap_offload = mt7925_sta_set_decap_offload, #if IS_ENABLED(CONFIG_IPV6) From 313f1a27ebcb8687e3f8629e36631f7c593212eb Mon Sep 17 00:00:00 2001 From: Ryan Leung Date: Sun, 19 Jul 2026 02:14:19 +0000 Subject: [PATCH 0746/1433] wifi: mt76: mt7915: add thermal zone device registration Register the mt7915 phy as a thermal zone sensor using devm_thermal_of_zone_register() so that device tree thermal-zones nodes can reference the Wi-Fi chip as a temperature source. This allows the kernel thermal governor to control external cooling devices such as PWM fans based on Wi-Fi chip temperature. Registration is non-fatal: -ENODEV is returned when no thermal-sensors DT property references this device, which is the expected case on platforms without a thermal zone configured. Signed-off-by: Ryan Leung Link: https://patch.msgid.link/20260719-mt7915-thermal-zone-device-registration-v2-1-0eac68c2741e@protonmail.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7915/init.c | 30 +++++++++++++++++++ .../wireless/mediatek/mt76/mt7915/mt7915.h | 1 + 2 files changed, 31 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/init.c b/drivers/net/wireless/mediatek/mt76/mt7915/init.c index 250c2d2479b0..6568d7b6bc0a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/init.c @@ -177,6 +177,25 @@ static const struct thermal_cooling_device_ops mt7915_thermal_ops = { .set_cur_state = mt7915_thermal_set_cur_throttle_state, }; +static int mt7915_thermal_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct mt7915_phy *phy = thermal_zone_device_priv(tz); + int val; + + mutex_lock(&phy->dev->mt76.mutex); + val = mt7915_mcu_get_temperature(phy); + mutex_unlock(&phy->dev->mt76.mutex); + if (val < 0) + return val; + + *temp = val * 1000; + return 0; +} + +static const struct thermal_zone_device_ops mt7915_tz_ops = { + .get_temp = mt7915_thermal_get_temp, +}; + static void mt7915_unregister_thermal(struct mt7915_phy *phy) { struct wiphy *wiphy = phy->mt76->hw->wiphy; @@ -213,6 +232,17 @@ static int mt7915_thermal_init(struct mt7915_phy *phy) phy->throttle_temp[MT7915_CRIT_TEMP_IDX] = MT7915_CRIT_TEMP; phy->throttle_temp[MT7915_MAX_TEMP_IDX] = MT7915_MAX_TEMP; + phy->tzone = devm_thermal_of_zone_register(phy->dev->mt76.dev, + phy->mt76->band_idx, phy, + &mt7915_tz_ops); + if (IS_ERR(phy->tzone)) { + if (PTR_ERR(phy->tzone) != -ENODEV) + dev_warn(phy->dev->mt76.dev, + "failed to register thermal zone: %ld\n", + PTR_ERR(phy->tzone)); + phy->tzone = NULL; + } + if (!IS_REACHABLE(CONFIG_HWMON)) return 0; diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h b/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h index bf1d915a3ca2..92e0f9f0169c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h @@ -205,6 +205,7 @@ struct mt7915_phy { struct ieee80211_vif *monitor_vif; + struct thermal_zone_device *tzone; struct thermal_cooling_device *cdev; u8 cdev_state; u8 throttle_state; From d16447d7470502e998d82e806ca7ed49d1baa853 Mon Sep 17 00:00:00 2001 From: Ahmed Naseef Date: Sun, 19 Jul 2026 12:25:30 +0400 Subject: [PATCH 0747/1433] wifi: mt76: mt7603: add 0x7592 EEPROM chip ID Some EcoNet based routers ship an on-flash EEPROM whose chip-id is 0x7592 instead of the expected 0x7603. The device probes as PCI 14c3:7603 and the hardware MT_HW_CHIPID register reports 0x7603, independent of the EEPROM value. This is seen across multiple EcoNet EN751221 and EN7528 based devices (for example the Genexis Platinum 4410). Signed-off-by: Ahmed Naseef Link: https://patch.msgid.link/20260719082530.3879831-1-naseefkm@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7603/eeprom.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/eeprom.c b/drivers/net/wireless/mediatek/mt76/mt7603/eeprom.c index b89db2db6573..05f7aaefaf89 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/eeprom.c +++ b/drivers/net/wireless/mediatek/mt76/mt7603/eeprom.c @@ -142,6 +142,7 @@ static int mt7603_check_eeprom(struct mt76_dev *dev) case 0x7628: case 0x7603: case 0x7600: + case 0x7592: return 0; default: return -EINVAL; From ef3e34874d2332d0f63e72c2c35ce5c93568c125 Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Tue, 14 Jul 2026 19:33:48 -0700 Subject: [PATCH 0748/1433] wifi: mt76: mt7925: ensure tx headroom in usb_sdio_tx_prepare_skb mt7925_usb_sdio_tx_prepare_skb() pushes a TX descriptor and a USB header onto every skb and assumes the headroom for them is already there. That holds for locally generated traffic, where mac80211 reserves hw->extra_tx_headroom, but forwarded frames are sent through ieee80211_8023_xmit(), which does not reserve it. Bridge a wired interface to an mt7925u AP and the first forwarded frame that arrives short panics the kernel: skbuff: skb_under_panic: len:415 put:4 tail:0x19b end:0x640 dev:wlan1 kernel BUG at net/core/skbuff.c:212! Call trace: skb_panic+0x58/0x60 (P) skb_push+0x58/0x60 mt7925_usb_sdio_tx_prepare_skb+0xf8/0x1b8 [mt7925_common] mt76u_tx_queue_skb+0xa0/0x1f8 [mt76_usb] __mt76_tx_queue_skb+0x54/0xe8 [mt76] mt76_txq_schedule.part.0+0x204/0x478 [mt76] mt76_txq_schedule_all+0x50/0x80 [mt76] mt792x_tx_worker+0x68/0x100 [mt792x_lib] __mt76_worker_fn+0x84/0x150 [mt76] Whether a given setup hits it depends on how much headroom the ingress netdev leaves in its rx skbs. Reproduced on a Raspberry Pi 5 bridging onboard ethernet to a Netgear A9000; originally reported on an MT7986 router running OpenWrt. Nick Morrow's testing on a Pi 4 (bcmgenet), which leaves more headroom, helped narrow the trigger to the ingress path. The same bug was fixed on mt7921 by commit 98c4d0abf5c4 ("mt76: mt7921: don't assume adequate headroom for SDIO headers"), but mt7925 was copied from mt7921 without the fix. Add the same guard here. Fixes: c948b5da6bbe ("wifi: mt76: mt7925: add Mediatek Wi-Fi7 driver for mt7925 chips") Cc: stable@vger.kernel.org Link: https://github.com/morrownr/mt76/issues/52 Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260715023348.59506-1-lucid_duck@justthetip.ca Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index b9973c4eb7f6..53ced437c018 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -1639,6 +1639,10 @@ int mt7925_usb_sdio_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, if (!wcid) wcid = &dev->mt76.global_wcid; + err = skb_cow_head(skb, MT_SDIO_TXD_SIZE + MT_SDIO_HDR_SIZE); + if (err) + return err; + if (sta) { struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv; From eb628006a2c69228b32129a3937baa52b970a118 Mon Sep 17 00:00:00 2001 From: JB Tsai Date: Tue, 30 Jun 2026 17:06:10 +0800 Subject: [PATCH 0749/1433] wifi: mt76: mt7925: Fix unregister deadlock During device shutdown or removal, a deadlock can occur between the PCIe remove path and the driver's asynchronous reset work. The unregistration path calls napi_disable() before cancelling the reset work. If the reset work runs concurrently, it may re-enable NAPI and schedule it. Because the device is being unregistered, this can lead to NAPI state corruption where NAPI is marked as scheduled but never polled, causing subsequent napi_disable() calls to hang forever. Fix this by: 1. Moving cancel_work_sync(&dev->reset_work) to the very start of mt7925e_unregister_device(), ensuring it is stopped before NAPI is disabled. 2. Setting the MT76_REMOVED flag early in the PCI remove path to prevent new reset work from being queued. 3. Checking MT76_REMOVED in mt7925_mac_reset_work() and aborting the reset early if the device is being removed. Co-developed-by: Fei Shao Signed-off-by: JB Tsai Tested-by: Rafael Passos Link: https://patch.msgid.link/20260630090610.586954-1-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 7 +++++++ drivers/net/wireless/mediatek/mt76/mt7925/pci.c | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index 53ced437c018..23ed18bd7332 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -1528,6 +1528,9 @@ void mt7925_mac_reset_work(struct work_struct *work) cancel_work_sync(&pm->wake_work); for (i = 0; i < 10; i++) { + if (test_bit(MT76_REMOVED, &dev->mphy.state)) + goto out; + mutex_lock(&dev->mt76.mutex); ret = mt792x_dev_reset(dev); mutex_unlock(&dev->mt76.mutex); @@ -1550,8 +1553,12 @@ void mt7925_mac_reset_work(struct work_struct *work) ieee80211_scan_completed(dev->mphy.hw, &info); } +out: dev->hw_full_reset = false; pm->suspended = false; + if (test_bit(MT76_REMOVED, &dev->mphy.state)) + return; + ieee80211_wake_queues(hw); ieee80211_iterate_active_interfaces(hw, IEEE80211_IFACE_ITER_RESUME_ALL, diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c index 1514494f6c7b..366541bab223 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c @@ -47,13 +47,13 @@ static void mt7925e_unregister_device(struct mt792x_dev *dev) if (dev->phy.chip_cap & MT792x_CHIP_CAP_WF_RF_PIN_CTRL_EVT_EN) wiphy_rfkill_stop_polling(hw->wiphy); + cancel_work_sync(&dev->reset_work); cancel_work_sync(&dev->init_work); mt76_unregister_device(&dev->mt76); mt76_for_each_q_rx(&dev->mt76, i) napi_disable(&dev->mt76.napi[i]); cancel_delayed_work_sync(&pm->ps_work); cancel_work_sync(&pm->wake_work); - cancel_work_sync(&dev->reset_work); mt7925_tx_token_put(dev); __mt792x_mcu_drv_pmctrl(dev); @@ -721,8 +721,8 @@ static void mt7925_pci_remove(struct pci_dev *pdev) struct mt76_dev *mdev = pci_get_drvdata(pdev); struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76); - mt7925e_unregister_device(dev); set_bit(MT76_REMOVED, &mdev->phy.state); + mt7925e_unregister_device(dev); devm_free_irq(&pdev->dev, pdev->irq, dev); mt76_free_device(&dev->mt76); pci_free_irq_vectors(pdev); From 2889e84282dda147f10b10d94cf0efd90a349c53 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 30 Jun 2026 17:02:18 +0800 Subject: [PATCH 0750/1433] wifi: mt76: mt7925: cancel pending mlo_pm_work If the device is reset, suspended or unregistered within that window, the pending work can still run and access vif/bss data that may already be freed, or send MCU commands while the firmware is not available. Add cancel_delayed_work_sync(&dev->mlo_pm_work) in all relevant teardown and suspend paths: - mt7925_mac_reset_work() (chip reset recovery) - mt7925e_unregister_device() (PCIe unbind) - mt7925_pci_suspend() (PCIe bus suspend) - mt7925_suspend() (mac80211 suspend) - mt7925u_suspend() (USB bus / runtime suspend) This ensures the work is stopped before the device state becomes invalid. Assisted-by: kimi-cli:kimi-k2.7 code Assisted-by: atomcode:glm-5.2 #Reported-by Fixes: 276a568832577 ("wifi: mt76: mt7925: update the power-saving flow") Cc: stable@vger.kernel.org Signed-off-by: Wentao Guan Link: https://patch.msgid.link/20260630090218.3202029-1-guanwentao@uniontech.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 1 + drivers/net/wireless/mediatek/mt76/mt7925/main.c | 1 + drivers/net/wireless/mediatek/mt76/mt7925/pci.c | 2 ++ drivers/net/wireless/mediatek/mt76/mt7925/usb.c | 1 + 4 files changed, 5 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index 23ed18bd7332..2931f5176137 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -1525,6 +1525,7 @@ void mt7925_mac_reset_work(struct work_struct *work) cancel_delayed_work_sync(&dev->mphy.mac_work); cancel_delayed_work_sync(&pm->ps_work); + cancel_delayed_work_sync(&dev->mlo_pm_work); cancel_work_sync(&pm->wake_work); for (i = 0; i < 10; i++) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index abb56f6fea77..5d0eac8c4e14 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -1709,6 +1709,7 @@ static int mt7925_suspend(struct ieee80211_hw *hw, cancel_delayed_work_sync(&phy->mt76->mac_work); cancel_delayed_work_sync(&dev->pm.ps_work); + cancel_delayed_work_sync(&dev->mlo_pm_work); mt76_connac_free_pending_tx_skbs(&dev->pm, NULL); mt792x_mutex_acquire(dev); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c index 366541bab223..4e734f4f65d1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c @@ -53,6 +53,7 @@ static void mt7925e_unregister_device(struct mt792x_dev *dev) mt76_for_each_q_rx(&dev->mt76, i) napi_disable(&dev->mt76.napi[i]); cancel_delayed_work_sync(&pm->ps_work); + cancel_delayed_work_sync(&dev->mlo_pm_work); cancel_work_sync(&pm->wake_work); mt7925_tx_token_put(dev); @@ -740,6 +741,7 @@ static int mt7925_pci_suspend(struct device *device) dev->hif_resumed = false; flush_work(&dev->reset_work); cancel_delayed_work_sync(&pm->ps_work); + cancel_delayed_work_sync(&dev->mlo_pm_work); cancel_work_sync(&pm->wake_work); mt7925_roc_abort_sync(dev); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c index 49ad4fe9eb1b..1757023ea70d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c @@ -276,6 +276,7 @@ static int mt7925u_suspend(struct usb_interface *intf, pm_message_t state) pm->suspended = true; dev->hif_resumed = false; flush_work(&dev->reset_work); + cancel_delayed_work_sync(&dev->mlo_pm_work); mt76_connac_mcu_set_hif_suspend(&dev->mt76, true, false); ret = wait_event_timeout(dev->wait, From 81faf578320df2dfc682a96baa6e85851dd68b6f Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Sat, 27 Jun 2026 13:29:46 -0700 Subject: [PATCH 0751/1433] wifi: mt76: mt7925: cancel mlo_pm_work on stop mt7925 queues mlo_pm_work with a 5 second delay during multi-link power-save setup and never cancels it on the stop path. If the device is torn down inside that window, the work outlives the teardown and its timer fires afterwards, trying to queue onto the workqueue that is already gone: workqueue: cannot queue mt7925_mlo_pm_work [mt7925_common] on wq phy0 WARNING: kernel/workqueue.c:2283 at __queue_work+0x59/0xa0, CPU#1: swapper/1/0 call_timer_fn+0x2a/0x140 __run_timers+0x203/0x330 run_timer_softirq+0x86/0xf0 mt7921 already has its own stop callback, so add one for mt7925 that cancels the work before calling mt792x_stop(). mt7925_ops backs both the PCIe and USB drivers, so this covers both. Fixes: 276a56883257 ("wifi: mt76: mt7925: update the power-saving flow") Cc: stable@vger.kernel.org Tested-by: Traockl <281473483+Traockl@users.noreply.github.com> Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260627202946.25598-1-lucid_duck@justthetip.ca Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 5d0eac8c4e14..ec0bc61a9158 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -2696,10 +2696,19 @@ static void mt7925_sta_pre_rcu_remove(struct ieee80211_hw *hw, mutex_unlock(&dev->mutex); } +static void mt7925_stop(struct ieee80211_hw *hw, bool suspend) +{ + struct mt792x_dev *dev = mt792x_hw_dev(hw); + + cancel_delayed_work_sync(&dev->mlo_pm_work); + + mt792x_stop(hw, suspend); +} + const struct ieee80211_ops mt7925_ops = { .tx = mt792x_tx, .start = mt7925_start, - .stop = mt792x_stop, + .stop = mt7925_stop, .add_interface = mt7925_add_interface, .remove_interface = mt7925_remove_interface, .config = mt7925_config, From 915672c5ae32deeb72f4572856d123f314791136 Mon Sep 17 00:00:00 2001 From: Eason Lai Date: Wed, 6 May 2026 15:04:58 +0800 Subject: [PATCH 0752/1433] wifi: mt76: mt7921: Add PCIe AER handler support to prevent system crash When an AER error occurs and the bus is hung, the register reads return 0xFFFFFFFF, causing the DMA queue state to be corrupted and resulting in an invalid memory access when accessing q->desc[] or q->entry[]. Unable to handle kernel paging request at virtual address ffffffc01099eac0 pc : mt76_dma_add_buf+0x124/0x188 [mt76] lr : mt76_dma_rx_fill+0x11c/0x1d8 [mt76] sp : ffffffc016d9bbf0 x29: ffffffc016d9bc10 x28: 0000000000000000 x27: 0000000000000000 x26: ffffffb7855e50b8 x25: ffffffb80d04f000 x24: 0000000000000000 x23: 0000000000000ec0 x22: ffffffb796803648 x21: ffffffb796801f80 x20: ffffffb7968035f8 x19: 0000000000000ec0 x18: 0000000000000000 x17: 000000004ec00000 x16: 000000000ec00000 x15: ffffffc01099eac0 x14: 000000004ec00000 x13: 00000000ffc5a000 x12: ffffffc016d9bc32 x11: 00000000ffffffff x10: 0000000000000002 x9 : 0000000000000000 x8 : 000000000000b4ac x7 : 0000000000000a20 x6 : ffffffb6c1806400 x5 : 0000000000000000 x4 : ffffffb80d04f000 x3 : 0000000000000000 x2 : 0000000000000001 x1 : 000000000ec04000 x0 : ffffffb7968035f8 Call trace: mt76_dma_add_buf+0x124/0x188 [mt76 (HASH:1029 4)] mt76_dma_rx_reset+0xe8/0xfc [mt76 (HASH:1029 4)] mt7921_wpdma_reset+0x188/0x1b0 [mt7921e (HASH:ee48 5)] mt7921e_mac_reset+0x128/0x418 [mt7921e (HASH:ee48 5)] mt7921_mac_reset_work+0xac/0x1a8 [mt7921_common (HASH:f721 6)] process_one_work+0x188/0x514 worker_thread+0x12c/0x300 kthread+0x140/0x1fc ret_from_fork+0x10/0x30 Fix the invalid memory access by validating the DMA index read from the hardware before it is used as a queue index. An out-of-range value, such as the 0xFFFFFFFF returned while the bus is hung, is now clamped so it can no longer corrupt q->head or q->tail. In addition, check the bus_hung flag in mt7921_mac_reset_work() before attempting the reset sequence, reject MCU messages while the bus is hung, and install no-op bus operations when an unrecoverable AER error is detected, preventing further invalid hardware accesses. Due to hardware limitations - such as the lack of a connected hardware reset pin or the absence of host re-probe functionality - affected Wi-Fi devices may not fully recover to a normal operational state after certain errors, even with AER enabled. Fixes: 17f1de56df05 ("mt76: add common code shared between multiple chipsets") Co-developed-by: Sean Wang Signed-off-by: Sean Wang Co-developed-by: Jeff Hsu Signed-off-by: Jeff Hsu Signed-off-by: Eason Lai Co-developed-by: Michael Lo Link: https://patch.msgid.link/20260506070458.3096180-1-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.c | 27 +++-- drivers/net/wireless/mediatek/mt76/mcu.c | 12 +- .../net/wireless/mediatek/mt76/mt76_connac.h | 5 + .../net/wireless/mediatek/mt76/mt7921/mac.c | 3 + .../net/wireless/mediatek/mt76/mt7921/pci.c | 103 ++++++++++++++++++ 5 files changed, 139 insertions(+), 11 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index 322041859217..3c4abeb5440a 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -186,6 +186,18 @@ mt76_dma_queue_magic_cnt_init(struct mt76_dev *dev, struct mt76_queue *q) } } +/* A hung bus (e.g. after a PCIe AER error) reads 0xffffffff from every + * register, so clamp an out-of-range index to the fallback to keep it from + * corrupting q->head/q->tail. + */ +static int +mt76_dma_read_dma_idx(struct mt76_queue *q, int fallback) +{ + u32 idx = Q_READ(q, dma_idx); + + return idx < q->ndesc ? idx : fallback; +} + static void mt76_dma_sync_idx(struct mt76_dev *dev, struct mt76_queue *q) { @@ -201,7 +213,8 @@ mt76_dma_sync_idx(struct mt76_dev *dev, struct mt76_queue *q) } Q_WRITE(q, desc_base, q->desc_dma); - q->head = Q_READ(q, dma_idx); + + q->head = mt76_dma_read_dma_idx(q, 0); q->tail = q->head; } @@ -419,7 +432,7 @@ mt76_dma_tx_cleanup(struct mt76_dev *dev, struct mt76_queue *q, bool flush) if (flush) last = -1; else - last = Q_READ(q, dma_idx); + last = mt76_dma_read_dma_idx(q, -1); while (q->queued > 0 && q->tail != last) { mt76_dma_tx_cleanup_idx(dev, q, q->tail, &entry); @@ -432,7 +445,7 @@ mt76_dma_tx_cleanup(struct mt76_dev *dev, struct mt76_queue *q, bool flush) } if (!flush && q->tail == last) - last = Q_READ(q, dma_idx); + last = mt76_dma_read_dma_idx(q, -1); } spin_unlock_bh(&q->cleanup_lock); @@ -625,8 +638,8 @@ mt76_dma_tx_queue_skb_raw(struct mt76_dev *dev, struct mt76_queue *q, buf.len = skb->len; spin_lock_bh(&q->lock); - mt76_dma_add_buf(dev, q, &buf, 1, tx_info, skb, NULL); - mt76_dma_kick_queue(dev, q); + if (mt76_dma_add_buf(dev, q, &buf, 1, tx_info, skb, NULL) >= 0) + mt76_dma_kick_queue(dev, q); spin_unlock_bh(&q->lock); return 0; @@ -983,7 +996,7 @@ mt76_dma_rx_process(struct mt76_dev *dev, struct mt76_queue *q, int budget) if ((q->flags & MT_QFLAG_WED_RRO_EN) || (IS_ENABLED(CONFIG_NET_MEDIATEK_SOC_WED) && mt76_queue_is_wed_tx_free(q))) { - dma_idx = Q_READ(q, dma_idx); + dma_idx = mt76_dma_read_dma_idx(q, q->tail); check_ddone = true; } @@ -993,7 +1006,7 @@ mt76_dma_rx_process(struct mt76_dev *dev, struct mt76_queue *q, int budget) if (check_ddone) { if (q->tail == dma_idx) - dma_idx = Q_READ(q, dma_idx); + dma_idx = mt76_dma_read_dma_idx(q, q->tail); if (q->tail == dma_idx) break; diff --git a/drivers/net/wireless/mediatek/mt76/mcu.c b/drivers/net/wireless/mediatek/mt76/mcu.c index cbfb3bbec503..7149b2f7aafd 100644 --- a/drivers/net/wireless/mediatek/mt76/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mcu.c @@ -78,15 +78,19 @@ int mt76_mcu_skb_send_and_get_msg(struct mt76_dev *dev, struct sk_buff *skb, unsigned long expires; int ret, seq; - if (mt76_is_sdio(dev)) - if (test_bit(MT76_RESET, &dev->phy.state) && atomic_read(&dev->bus_hung)) - return -EIO; - if (ret_skb) *ret_skb = NULL; mutex_lock(&dev->mcu.mutex); + if ((mt76_is_mmio(dev) && atomic_read(&dev->bus_hung)) || + (mt76_is_sdio(dev) && test_bit(MT76_RESET, &dev->phy.state) && + atomic_read(&dev->bus_hung))) { + orig_skb = skb; + ret = -EIO; + goto out; + } + if (dev->mcu_ops->mcu_skb_prepare_msg) { orig_skb = skb; ret = dev->mcu_ops->mcu_skb_prepare_msg(dev, skb, cmd, &seq); diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac.h index 0951038916d3..b1677ca24703 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac.h @@ -48,6 +48,11 @@ enum rx_pkt_type { #define MT_TXD_LEN_MSDU_LAST BIT(14) #define MT_TXD_LEN_AMSDU_LAST BIT(15) +/* PCIE part */ +#define PCIE_AER_UNC_STATUS_OFFSET 0x204 +#define PCIE_AER_UNC_MASK_OFFSET 0x208 +#define PCIE_AER_CO_STATUS_OFFSET 0x210 + enum { CMD_CBW_20MHZ = IEEE80211_STA_RX_BW_20, CMD_CBW_40MHZ = IEEE80211_STA_RX_BW_40, diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c index f7d54472da1b..17014b1f91e0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c @@ -674,6 +674,9 @@ void mt7921_mac_reset_work(struct work_struct *work) cancel_work_sync(&pm->wake_work); for (i = 0; i < 10; i++) { + if (atomic_read(&dev->mt76.bus_hung)) + return; + mutex_lock(&dev->mt76.mutex); ret = mt792x_dev_reset(dev); mutex_unlock(&dev->mt76.mutex); diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c index 2f51f653975f..56a914aaa81e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c @@ -607,6 +607,108 @@ static int mt7921_pci_resume(struct device *device) return err; } +static u32 mt7921_aer_rr(struct mt76_dev *mdev, u32 offset) +{ + return 0; +} + +static void mt7921_aer_wr(struct mt76_dev *mdev, u32 offset, u32 val) +{ + ; +} + +static u32 mt791_aer_rmw(struct mt76_dev *mdev, u32 offset, u32 mask, u32 val) +{ + return 0; +} + +static const struct mt76_bus_ops mt7921_aer_bus_hung_ops = { + .rr = mt7921_aer_rr, + .wr = mt7921_aer_wr, + .rmw = mt791_aer_rmw, + .type = MT76_BUS_MMIO +}; + +static void mt7921_pci_set_aer_bus_hung_ops(struct mt792x_dev *dev) +{ + if (READ_ONCE(dev->mt76.bus) == &mt7921_aer_bus_hung_ops) + return; + + atomic_set(&dev->mt76.bus_hung, true); + WRITE_ONCE(dev->mt76.bus, &mt7921_aer_bus_hung_ops); +} + +static pci_ers_result_t mt7921_error_detected(struct pci_dev *pdev, + pci_channel_state_t state) +{ + struct mt76_dev *mdev = pci_get_drvdata(pdev); + struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76); + u32 aer_unc_val = 0, aer_co_val = 0; + + dev_err(mdev->dev, "PCIE error detect state: %d\n", state); + + /* Clear SW IRQ tasklet first */ + tasklet_kill(&mdev->irq_tasklet); + + if (state == pci_channel_io_perm_failure) { + mt7921_pci_set_aer_bus_hung_ops(dev); + return PCI_ERS_RESULT_DISCONNECT; + } + + pci_read_config_dword(pdev, PCIE_AER_UNC_STATUS_OFFSET, &aer_unc_val); + pci_read_config_dword(pdev, PCIE_AER_CO_STATUS_OFFSET, &aer_co_val); + + dev_warn(mdev->dev, "PCIE_AER_UNC_STATUS_OFFSET: 0x%x\n", aer_unc_val); + dev_warn(mdev->dev, "PCIE_AER_CO_STATUS_OFFSET: 0x%x\n", aer_co_val); + + /** + * Due to this error is from link error and this AER is un-correctable, + * so can't covered by device + **/ + if (aer_unc_val != 0) { + mt7921_pci_set_aer_bus_hung_ops(dev); + return PCI_ERS_RESULT_DISCONNECT; + } + + /** + * Try to recover it when state is pci_channel_io_frozen or + * AER is correctable error + **/ + if (state == pci_channel_io_frozen || aer_co_val != 0) { + /* Disable PCIE activity first. */ + pci_disable_device(pdev); + return PCI_ERS_RESULT_NEED_RESET; + } + + return PCI_ERS_RESULT_NONE; +} + +static pci_ers_result_t mt7921_slot_reset(struct pci_dev *pdev) +{ + struct mt76_dev *mdev = pci_get_drvdata(pdev); + int ret = 0; + + ret = pci_enable_device_mem(pdev); + + if (ret) { + dev_err(mdev->dev, "pci_enable_device_mem failed: %d\n", ret); + return PCI_ERS_RESULT_DISCONNECT; + } + + pci_set_master(pdev); + pci_restore_state(pdev); + pci_save_state(pdev); + /* Also try do the vendor reset to let it more clear. */ + mt792x_reset(mdev); + + return PCI_ERS_RESULT_RECOVERED; +} + +static const struct pci_error_handlers mt7921_err_handler = { + .error_detected = mt7921_error_detected, + .slot_reset = mt7921_slot_reset, +}; + static void mt7921_pci_shutdown(struct pci_dev *pdev) { mt7921_pci_remove(pdev); @@ -621,6 +723,7 @@ static struct pci_driver mt7921_pci_driver = { .remove = mt7921_pci_remove, .shutdown = mt7921_pci_shutdown, .driver.pm = pm_sleep_ptr(&mt7921_pm_ops), + .err_handler = &mt7921_err_handler, }; module_pci_driver(mt7921_pci_driver); From c410102bbff0803c9dc44056b917aff90abcbaec Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 13 Jun 2026 17:51:43 -0500 Subject: [PATCH 0753/1433] wifi: mt76: mt7927: set band index for sniffer mode Use the active channel context to select the SNIFFER command band index on MT7927, and fall back to the PHY chandef when no channel context is available. Also pass the same band index to the sniffer channel configuration. This keeps monitor setup on the correct band, especially when multiple PHY band contexts are present. Fixes: 35a5dcc71735 ("wifi: mt76: mt7925: add MT7927 PCIe support") Signed-off-by: Sean Wang Tested-by: Devin Wittmayer Link: https://patch.msgid.link/20260613225144.2414283-1-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index 8abae7585e7b..c792c1befc1e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -2236,6 +2236,8 @@ int mt7925_get_txpwr_info(struct mt792x_dev *dev, u8 band_idx, struct mt7925_txp int mt7925_mcu_set_sniffer(struct mt792x_dev *dev, struct ieee80211_vif *vif, bool enable) { + struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; + struct ieee80211_chanctx_conf *ctx = mvif->bss_conf.mt76.ctx; struct { struct { u8 band_idx; @@ -2258,6 +2260,15 @@ int mt7925_mcu_set_sniffer(struct mt792x_dev *dev, struct ieee80211_vif *vif, }, }; + if (is_mt7927(&dev->mt76)) { + struct ieee80211_channel *chan; + + chan = ctx ? ctx->def.chan : mvif->phy->mt76->chandef.chan; + + if (chan) + req.hdr.band_idx = mt7927_band_idx(chan->band); + } + return mt76_mcu_send_msg(&dev->mt76, MCU_UNI_CMD(SNIFFER), &req, sizeof(req), true); } @@ -2317,6 +2328,9 @@ int mt7925_mcu_config_sniffer(struct mt792x_vif *vif, }, }; + if (is_mt7927(mphy->dev)) + req.hdr.band_idx = mt7927_band_idx(chandef->chan->band); + if (chandef->chan->band < ARRAY_SIZE(ch_band)) req.tlv.ch_band = ch_band[chandef->chan->band]; if (chandef->width < ARRAY_SIZE(ch_width)) From 7673a2a159d74e7b1227be1d09f3e1040edf66ea Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 13 Jun 2026 17:51:44 -0500 Subject: [PATCH 0754/1433] wifi: mt76: mt7927: use real monitor vifs for dual-band monitors MT7927 needs monitor interfaces to be passed to the driver as real vifs so each monitor interface can be configured with its own band context. This is required to support concurrent 2 GHz and 5 GHz monitor operation on the same hw. Keep the existing virtual monitor behavior for older chips. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260613225144.2414283-2-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt792x_core.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c index 2a8384a44d9f..837d7af984e8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c @@ -833,7 +833,10 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw) ieee80211_hw_set(hw, HAS_RATE_CONTROL); ieee80211_hw_set(hw, SUPPORTS_TX_ENCAP_OFFLOAD); ieee80211_hw_set(hw, SUPPORTS_RX_DECAP_OFFLOAD); - ieee80211_hw_set(hw, WANT_MONITOR_VIF); + if (is_mt7927(&dev->mt76)) + ieee80211_hw_set(hw, NO_VIRTUAL_MONITOR); + else + ieee80211_hw_set(hw, WANT_MONITOR_VIF); ieee80211_hw_set(hw, SUPPORTS_PS); ieee80211_hw_set(hw, SUPPORTS_DYNAMIC_PS); ieee80211_hw_set(hw, SUPPORTS_VHT_EXT_NSS_BW); From 525c3eb2351f7292f4bc3ee276b6bc6e0614be5d Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Mon, 15 Jun 2026 16:21:37 -0500 Subject: [PATCH 0755/1433] wifi: mt76: mt7925: support new WoW pattern TLV Newer mt7925 firmware uses a shorter WoW pattern TLV with rsv[3]. Select the v2 layout based on the firmware build date, while keeping the old layout for older firmware. This also makes the WoW pattern handling compatible with newer devices such as MT7928. Tested-by: Stella Liu Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260615212137.477893-1-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/mcu.c | 30 +++++++++++++++++-- .../net/wireless/mediatek/mt76/mt7925/mcu.h | 3 ++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index c792c1befc1e..e7e553b4f716 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -11,6 +11,7 @@ #define MT_STA_BFER BIT(0) #define MT_STA_BFEE BIT(1) +#define MT7925_WOW_PATTERN_NEW_FW_DATE "20260414153105" static bool mt7925_vif_is_nan(struct ieee80211_vif *vif) { @@ -229,6 +230,23 @@ mt7925_connac_mcu_set_wow_ctrl(struct mt76_phy *phy, struct ieee80211_vif *vif, sizeof(req), true); } +static bool mt7925_mcu_wow_pattern_old_tlv(struct mt76_dev *dev) +{ + const char *fw_version = dev->hw->wiphy->fw_version; + const char *build_date = strrchr(fw_version, '-'); + + if (!is_mt7925(dev)) + return false; + + if (!build_date) + return false; + + build_date++; + + return strncmp(build_date, MT7925_WOW_PATTERN_NEW_FW_DATE, + strlen(MT7925_WOW_PATTERN_NEW_FW_DATE)) < 0; +} + static int mt7925_mcu_set_wow_pattern(struct mt76_dev *dev, struct ieee80211_vif *vif, @@ -238,6 +256,8 @@ mt7925_mcu_set_wow_pattern(struct mt76_dev *dev, struct mt76_vif_link *mvif = (struct mt76_vif_link *)vif->drv_priv; struct mt7925_wow_pattern_tlv *tlv; struct sk_buff *skb; + int tlv_len; + bool old_tlv; struct { u8 bss_idx; u8 pad[3]; @@ -245,14 +265,18 @@ mt7925_mcu_set_wow_pattern(struct mt76_dev *dev, .bss_idx = mvif->idx, }; - skb = mt76_mcu_msg_alloc(dev, NULL, sizeof(hdr) + sizeof(*tlv)); + old_tlv = mt7925_mcu_wow_pattern_old_tlv(dev); + tlv_len = old_tlv ? sizeof(struct mt7925_wow_pattern_tlv) : + MT7925_WOW_PATTERN_TLV_V2_SIZE; + + skb = mt76_mcu_msg_alloc(dev, NULL, sizeof(hdr) + tlv_len); if (!skb) return -ENOMEM; skb_put_data(skb, &hdr, sizeof(hdr)); - tlv = (struct mt7925_wow_pattern_tlv *)skb_put(skb, sizeof(*tlv)); + tlv = (struct mt7925_wow_pattern_tlv *)skb_put_zero(skb, tlv_len); tlv->tag = cpu_to_le16(UNI_SUSPEND_WOW_PATTERN); - tlv->len = cpu_to_le16(sizeof(*tlv)); + tlv->len = cpu_to_le16(tlv_len); tlv->bss_idx = 0xF; tlv->data_len = pattern->pattern_len; tlv->enable = enable; diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h index 1613c4765186..154f792a56bc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h @@ -608,6 +608,9 @@ struct mt7925_wow_pattern_tlv { u8 rsv[4]; }; +#define MT7925_WOW_PATTERN_TLV_V2_SIZE \ + (offsetof(struct mt7925_wow_pattern_tlv, rsv) + 3) + struct roc_acquire_tlv { __le16 tag; __le16 len; From 8a27c5c764040fbc990cc416a927b1d7eadf559f Mon Sep 17 00:00:00 2001 From: "Jared.Huang" Date: Wed, 17 Jun 2026 15:13:05 +0800 Subject: [PATCH 0756/1433] wifi: mt76: mt7925: update clc before setting sar power table Fix the power table update sequence to ensure CLC is loaded before setting SAR power table. The firmware requires CLC baseline to be established first to properly calculate the final power limit as min(clc_limit, rate_limit,sar_limit). Fixes: 9557b6fe0c8b ("wifi: mt76: mt7925: refine the txpower initialization flow") Signed-off-by: Jared.Huang Link: https://patch.msgid.link/20260617071305.1808394-1-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index ec0bc61a9158..2d79a895713c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -1897,9 +1897,15 @@ static int mt7925_set_sar_specs(struct ieee80211_hw *hw, int err; mt792x_mutex_acquire(dev); - err = mt7925_set_tx_sar_pwr(hw, sar); - mt792x_mutex_release(dev); + err = mt7925_mcu_set_clc(dev, dev->mt76.alpha2, + dev->country_ie_env); + if (err < 0) + goto out; + err = mt7925_set_tx_sar_pwr(hw, sar); + +out: + mt792x_mutex_release(dev); return err; } From 9ddb7487aa7cccb6e1880b4151ab5895109eb8d6 Mon Sep 17 00:00:00 2001 From: "Jared.Huang" Date: Wed, 17 Jun 2026 15:13:20 +0800 Subject: [PATCH 0757/1433] wifi: mt76: mt7925: fix msg len mismatch between driver and firmware The mt7925_tx_power_limit_tlv struct begins with a 4-byte rsv[] field that acts as a UNI command header prefix. The firmware dispatcher did not use the 4-byte rsv[] and will only check the payloads after the 4-byte rsv[] As a result, the total message length minus the 4-byte prefix. Fix this by setting len to msg_len - 4. Fixes: ccb186326bb6 ("wifi: mt76: mt7925: fix incorrect length field in txpower command") Signed-off-by: Jared.Huang Link: https://patch.msgid.link/20260617071320.1808499-1-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index e7e553b4f716..6994af9298a8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -3991,7 +3991,7 @@ mt7925_mcu_rate_txpower_band(struct mt76_phy *phy, memcpy(tx_power_tlv->alpha2, dev->alpha2, sizeof(dev->alpha2)); tx_power_tlv->n_chan = num_ch; tx_power_tlv->tag = cpu_to_le16(0x1); - tx_power_tlv->len = cpu_to_le16(msg_len); + tx_power_tlv->len = cpu_to_le16(msg_len - 4); switch (band) { case NL80211_BAND_2GHZ: From 808f2767d4217a5b96f674288573b9b89d432eed Mon Sep 17 00:00:00 2001 From: Eason Lai Date: Fri, 3 Jul 2026 08:59:45 +0800 Subject: [PATCH 0758/1433] wifi: mt76: mt792x: Fix memory leak in SDIO TX path When tx_prepare_skb() returns an error in the SDIO TX path, the skb is not freed, leading to a memory leak. This can occur when zero-length frames (such as WNM NULL frames) are dropped to prevent potential hardware TX hangs. Fix this by properly releasing the skb with ieee80211_tx_status_ext() when tx_prepare_skb() fails. Fixes: b747fa343817 ("mt76: mt7915: drop zero-length packet to avoid Tx hang") Signed-off-by: Eason Lai Link: https://patch.msgid.link/20260703005945.2244533-1-eason.lai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/sdio.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/sdio.c b/drivers/net/wireless/mediatek/mt76/sdio.c index 8bae77c761be..ba5f123f7e39 100644 --- a/drivers/net/wireless/mediatek/mt76/sdio.c +++ b/drivers/net/wireless/mediatek/mt76/sdio.c @@ -519,6 +519,10 @@ mt76s_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q, enum mt76_txq_id qid, struct sk_buff *skb, struct mt76_wcid *wcid, struct ieee80211_sta *sta) { + struct ieee80211_tx_status status = { + .sta = sta, + }; + struct mt76_tx_info tx_info = { .skb = skb, }; @@ -531,8 +535,13 @@ mt76s_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q, skb->prev = skb->next = NULL; err = dev->drv->tx_prepare_skb(dev, NULL, qid, wcid, sta, &tx_info); - if (err < 0) + if (err < 0) { + status.skb = tx_info.skb; + spin_lock_bh(&dev->rx_lock); + ieee80211_tx_status_ext(dev->hw, &status); + spin_unlock_bh(&dev->rx_lock); return err; + } q->entry[q->head].skb = tx_info.skb; q->entry[q->head].buf_sz = len; From 1f83a8378eb26c47ff947709e67d7145a84a3907 Mon Sep 17 00:00:00 2001 From: Charlie-cy Wu Date: Mon, 29 Jun 2026 16:35:43 +0800 Subject: [PATCH 0759/1433] wifi: mt76: mt7921: refactor regd update to fix recursive mutex deadlock Split mt7921_mcu_regd_update() into two functions to prevent recursive mutex acquisition. Introduce __mt7921_mcu_regd_update() as the internal implementation that assumes the mutex is already held by the caller, while mt7921_mcu_regd_update() remains as the external interface that handles mutex acquisition and release. This fixes a deadlock issue when mt7921_regd_set_6ghz_power_type() is called with the device mutex already held. Without this change, calling mt7921_mcu_regd_update() would attempt to acquire the same mutex again, causing a recursive lock deadlock. The __mt7921_mcu_regd_update() function can be safely called when the caller has already acquired the device mutex, avoiding the deadlock while maintaining proper synchronization for regulatory domain updates. Fixes: e88098133ed4 ("wifi: mt76: mt7921: refactor regulatory notifier flow") Signed-off-by: Charlie-cy Wu Link: https://patch.msgid.link/20260629083543.153564-1-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7921/main.c | 2 +- .../net/wireless/mediatek/mt76/mt7921/regd.c | 31 ++++++++++++------- .../net/wireless/mediatek/mt76/mt7921/regd.h | 2 ++ 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c index 3480205d5fb9..68a059504e83 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c @@ -802,7 +802,7 @@ mt7921_regd_set_6ghz_power_type(struct ieee80211_vif *vif, bool is_add) out: if (vif->bss_conf.chanreq.oper.chan->band == NL80211_BAND_6GHZ) - mt7921_mcu_regd_update(dev, dev->mt76.alpha2, dev->country_ie_env); + __mt7921_mcu_regd_update(dev, dev->mt76.alpha2, dev->country_ie_env); } int mt7921_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif, diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c index c0e2b48a50bf..80d1712ebfca 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c @@ -71,36 +71,45 @@ mt7921_regd_channel_update(struct wiphy *wiphy, struct mt792x_dev *dev) } } -int mt7921_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2, - enum environment_cap country_ie_env) +int __mt7921_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2, + enum environment_cap country_ie_env) { struct mt76_dev *mdev = &dev->mt76; struct ieee80211_hw *hw = mdev->hw; struct wiphy *wiphy = hw->wiphy; - int ret = 0; + int ret; - dev->regd_in_progress = true; + lockdep_assert_held(&dev->mt76.mutex); - mt792x_mutex_acquire(dev); if (!dev->regd_change) - goto err; + return 0; ret = mt7921_mcu_set_clc(dev, alpha2, country_ie_env); if (ret < 0) - goto err; + return ret; mt7921_regd_channel_update(wiphy, dev); ret = mt76_connac_mcu_set_channel_domain(hw->priv); if (ret < 0) - goto err; + return ret; ret = mt7921_set_tx_sar_pwr(hw, NULL); - if (ret < 0) - goto err; -err: + return ret; +} + +int mt7921_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2, + enum environment_cap country_ie_env) +{ + int ret; + + dev->regd_in_progress = true; + + mt792x_mutex_acquire(dev); + ret = __mt7921_mcu_regd_update(dev, alpha2, country_ie_env); mt792x_mutex_release(dev); + dev->regd_change = false; dev->regd_in_progress = false; wake_up(&dev->wait); diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.h b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h index 571f31629e9e..5b24d0902c36 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/regd.h +++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h @@ -10,6 +10,8 @@ struct regulatory_request; int mt7921_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2, enum environment_cap country_ie_env); +int __mt7921_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2, + enum environment_cap country_ie_env); void mt7921_regd_notifier(struct wiphy *wiphy, struct regulatory_request *request); bool mt7921_regd_clc_supported(struct mt792x_dev *dev); From e9f3f1cc133fc2ea51a11be7cfe51733081d176a Mon Sep 17 00:00:00 2001 From: Charlie-cy Wu Date: Tue, 9 Jun 2026 14:50:24 +0800 Subject: [PATCH 0760/1433] wifi: mt76: mt7925: add regulatory wiphy self manager support Introduce regulatory wiphy self-managed mode support for MT7925, allowing the driver to manage its own regulatory domain independently from the kernel's regulatory framework. Signed-off-by: Charlie-cy Wu Link: https://patch.msgid.link/20260609065024.577079-1-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/mcu.c | 3 +- .../net/wireless/mediatek/mt76/mt7925/regd.c | 220 ++++++++++++++++-- .../net/wireless/mediatek/mt76/mt7925/regd.h | 54 +++++ drivers/net/wireless/mediatek/mt76/mt792x.h | 2 + 4 files changed, 260 insertions(+), 19 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index 6994af9298a8..a6f28aa51a2f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -3703,7 +3703,8 @@ int mt7925_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2, /* submit all clc config */ for (i = 0; i < ARRAY_SIZE(phy->clc); i++) { - if (i == MT792x_CLC_BE_CTRL) + if (i == MT792x_CLC_BE_CTRL || + i == MT792x_CLC_REGD) continue; ret = __mt7925_mcu_set_clc(dev, alpha2, env_cap, diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regd.c b/drivers/net/wireless/mediatek/mt76/mt7925/regd.c index 0235437d11d5..f4beb7f52043 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/regd.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/regd.c @@ -9,6 +9,15 @@ static bool mt7925_disable_clc; module_param_named(disable_clc, mt7925_disable_clc, bool, 0644); MODULE_PARM_DESC(disable_clc, "disable CLC support"); +static struct ieee80211_regdomain mt7925_regd_ww = { + .n_reg_rules = 1, + .alpha2 = "00", + .reg_rules = { + /* IEEE 802.11b/g, channels 1..11 */ + REG_RULE(2412 - 10, 2462 + 10, 40, 6, 20, 0), + } +}; + bool mt7925_regd_clc_supported(struct mt792x_dev *dev) { if (mt7925_disable_clc || @@ -128,35 +137,37 @@ mt7925_regd_channel_update(struct wiphy *wiphy, struct mt792x_dev *dev) } } -int mt7925_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2, - enum environment_cap country_ie_env) +static int mt7925_mcu_apply_regd(struct mt792x_dev *dev, u8 *alpha2, + enum environment_cap env) { struct ieee80211_hw *hw = mt76_hw(dev); struct wiphy *wiphy = hw->wiphy; - int ret = 0; + int ret; - dev->regd_in_progress = true; - - mt792x_mutex_acquire(dev); - if (!dev->regd_change) - goto err; - - ret = mt7925_mcu_set_clc(dev, alpha2, country_ie_env); + ret = mt7925_mcu_set_clc(dev, alpha2, env); if (ret < 0) - goto err; + return ret; mt7925_regd_be_ctrl(dev, alpha2); mt7925_regd_channel_update(wiphy, dev); ret = mt7925_mcu_set_channel_domain(hw->priv); if (ret < 0) - goto err; + return ret; - ret = mt7925_set_tx_sar_pwr(hw, NULL); - if (ret < 0) - goto err; + return mt7925_set_tx_sar_pwr(hw, NULL); +} -err: +int mt7925_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2, + enum environment_cap country_ie_env) +{ + int ret = 0; + + dev->regd_in_progress = true; + + mt792x_mutex_acquire(dev); + if (dev->regd_change) + ret = mt7925_mcu_apply_regd(dev, alpha2, country_ie_env); mt792x_mutex_release(dev); dev->regd_change = false; dev->regd_in_progress = false; @@ -197,11 +208,178 @@ void mt7925_regd_notifier(struct wiphy *wiphy, struct regulatory_request *req) /* postpone the mcu update to resume */ return; + if (MT7925_REGD_SUPPORTED(&dev->phy)) { + mt7925_regd_update(&dev->phy, req->alpha2); + + return; + } + mt7925_mcu_regd_update(dev, req->alpha2, req->country_ie_env); return; } +static struct sk_buff * +mt7925_regd_query_regdb(struct mt792x_phy *phy, char *alpha2) +{ + struct wiphy *wiphy = phy->mt76->hw->wiphy; + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct mt792x_dev *dev = mt792x_hw_dev(hw); + struct mt7925_clc *clc = phy->clc[MT792x_CLC_REGD]; + struct mt7925_regd_query_req *req; + struct mt7925_regd_cc *regd_cc; + struct sk_buff *ret_skb = NULL; + u8 *pos, *last_pos; + int ret = 0; + + if (!clc) + return NULL; + + pos = clc->data; + last_pos = pos + le32_to_cpu(clc->len) - sizeof(struct mt7925_clc); + while (pos < last_pos) { + u32 req_len = 0; + u32 rules_len = 0; + u32 sign_len = 4; + u32 n_reg_rules; + + if (pos + sizeof(*regd_cc) > last_pos) + break; + + regd_cc = (struct mt7925_regd_cc *)pos; + n_reg_rules = le32_to_cpu(regd_cc->n_reg_rules); + if (n_reg_rules > NL80211_MAX_SUPP_REG_RULES) + break; + + rules_len = sizeof(struct mt7925_regd_rule_header) + + sizeof(struct mt7925_regd_rule) * n_reg_rules; + + if (pos + sizeof(*regd_cc) + rules_len + sign_len > last_pos) + break; + + pos += sizeof(*regd_cc) + rules_len + sign_len; + if (memcmp(regd_cc->alpha2, alpha2, 2)) + continue; + + req_len = sizeof(*req) + rules_len + sign_len; + req = kzalloc(req_len, GFP_KERNEL); + + if (!req) + return NULL; + + req->tag = cpu_to_le16(0x6); + req->len = cpu_to_le16(req_len - 4); + req->ver = regd_cc->ver; + req->sign_type = regd_cc->sign_type; + req->size = cpu_to_le32(rules_len + sign_len); + req->n_reg_rules = regd_cc->n_reg_rules; + + memcpy(req->alpha2, regd_cc->alpha2, 2); + memcpy(req->data, regd_cc->data, rules_len + sign_len); + + ret = mt76_mcu_send_and_get_msg(&dev->mt76, + MCU_UNI_CMD(SET_POWER_LIMIT), + req, req_len, true, &ret_skb); + kfree(req); + + return ret < 0 ? NULL : ret_skb; + } + + return NULL; +} + +int mt7925_regd_update(struct mt792x_phy *phy, char *alpha2) +{ + struct wiphy *wiphy = phy->mt76->hw->wiphy; + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct mt792x_dev *dev = mt792x_hw_dev(hw); + struct mt7925_regd_rule *mt7925_rule; + struct mt76_dev *mdev = &dev->mt76; + struct ieee80211_regdomain *regd; + struct ieee80211_reg_rule *rule; + struct mt7925_regd_rule_ev *ev; + int i, num_of_rules = 0; + struct sk_buff *skb; + int ret = 0; + + if (dev->hw_full_reset) + return 0; + + if (!MT7925_REGD_SUPPORTED(phy)) + return -EOPNOTSUPP; + + mt792x_mutex_acquire(dev); + skb = mt7925_regd_query_regdb(phy, alpha2); + mt792x_mutex_release(dev); + + if (!skb) { + ret = -EINVAL; + goto err; + } + + if (skb->len < sizeof(*ev) + 4) { + ret = -EINVAL; + goto err; + } + + ev = (struct mt7925_regd_rule_ev *)(skb->data + 4); + num_of_rules = le32_to_cpu(ev->n_reg_rules); + + if (!num_of_rules || + WARN_ON_ONCE(num_of_rules > NL80211_MAX_SUPP_REG_RULES)) { + ret = -EINVAL; + goto err; + } + + if (skb->len < struct_size(ev, reg_rule, num_of_rules) + 4) { + ret = -EINVAL; + goto err; + } + + regd = kzalloc(struct_size(regd, reg_rules, num_of_rules), GFP_KERNEL); + if (!regd) { + ret = -ENOMEM; + goto err; + } + + for (i = 0; i < num_of_rules; i++) { + mt7925_rule = &ev->reg_rule[i]; + rule = ®d->reg_rules[i]; + + rule->freq_range.start_freq_khz = + MHZ_TO_KHZ(le32_to_cpu(mt7925_rule->start_freq)); + rule->freq_range.end_freq_khz = + MHZ_TO_KHZ(le32_to_cpu(mt7925_rule->end_freq)); + rule->freq_range.max_bandwidth_khz = + MHZ_TO_KHZ(le32_to_cpu(mt7925_rule->max_bw)); + /* not used by fw */ + rule->power_rule.max_antenna_gain = DBI_TO_MBI(6); + rule->power_rule.max_eirp = DBM_TO_MBM(22); + rule->flags = le32_to_cpu(mt7925_rule->flags); + } + + regd->n_reg_rules = num_of_rules; + regd->dfs_region = ev->dfs_region; + + memcpy(regd->alpha2, alpha2, 2); + memcpy(mdev->alpha2, alpha2, 2); + + dev->regd_change = true; + mt7925_mcu_regd_update(dev, alpha2, ENVIRON_ANY); + + ret = regulatory_set_wiphy_regd(wiphy, regd); + + kfree(regd); +err: + dev_kfree_skb(skb); + + if (ret < 0) + return regulatory_set_wiphy_regd(wiphy, &mt7925_regd_ww); + + return ret; +} +EXPORT_SYMBOL_GPL(mt7925_regd_update); + static bool mt7925_regd_is_valid_alpha2(const char *alpha2) { @@ -270,7 +448,9 @@ int mt7925_regd_change(struct mt792x_phy *phy, char *alpha2) if (!memcmp(alpha2, mdev->alpha2, 2)) return 0; - if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN) { + if (MT7925_REGD_SUPPORTED(phy)) { + return mt7925_regd_update(phy, alpha2); + } else if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN) { return regulatory_hint(wiphy, alpha2); } else { return mt7925_mcu_set_clc(dev, alpha2, ENVIRON_INDOOR); @@ -285,7 +465,11 @@ int mt7925_regd_init(struct mt792x_phy *phy) struct mt792x_dev *dev = mt792x_hw_dev(hw); struct mt76_dev *mdev = &dev->mt76; - if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN) { + if (MT7925_REGD_SUPPORTED(phy)) { + wiphy->regulatory_flags |= REGULATORY_WIPHY_SELF_MANAGED | + REGULATORY_DISABLE_BEACON_HINTS; + return mt7925_regd_update(phy, "00"); + } else if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN) { wiphy->regulatory_flags |= REGULATORY_COUNTRY_IE_IGNORE | REGULATORY_DISABLE_BEACON_HINTS; } else { diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regd.h b/drivers/net/wireless/mediatek/mt76/mt7925/regd.h index 0b0754cf8ae7..65ef2b140f51 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/regd.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/regd.h @@ -6,12 +6,66 @@ #include "mt7925.h" +struct mt7925_regd_rule_header { + u8 alpha2[2]; + u8 dfs_region; + u8 rsv[13]; +}; + +struct mt7925_regd_rule { + __le32 start_freq; + __le32 end_freq; + __le32 max_bw; + __le32 eirp; + __le32 flags; + u8 rsv[12]; +}; + +struct mt7925_regd_cc { + u8 alpha2[2]; + u8 ver; + u8 rsv; + __le32 n_reg_rules; + u8 sign_type; + u8 rsv1[7]; + u8 data[]; +}; + +struct mt7925_regd_rule_ev { + __le16 tag; + __le16 len; + __le32 n_reg_rules; + u8 dfs_region; + u8 rsv[15]; + struct mt7925_regd_rule reg_rule[]; +}; + +struct mt7925_regd_query_req { + u8 rsv[4]; + __le16 tag; + __le16 len; + u8 ver; + u8 sign_type; + u8 rsv1[2]; + __le32 size; + u8 alpha2[2]; + u8 rsv2[2]; + __le32 n_reg_rules; + u8 rsv3[64]; + u8 data[]; +}; + +#define MT7925_REGD_SUPPORTED(phy) \ + (((phy)->chip_cap & MT792x_CHIP_CAP_REGD_EN) && \ + (phy)->clc[MT792x_CLC_REGD]) + int mt7925_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2, enum environment_cap country_ie_env); void mt7925_regd_be_ctrl(struct mt792x_dev *dev, u8 *alpha2); void mt7925_regd_notifier(struct wiphy *wiphy, struct regulatory_request *req); bool mt7925_regd_clc_supported(struct mt792x_dev *dev); +int mt7925_regd_update(struct mt792x_phy *phy, char *alpha2); int mt7925_regd_change(struct mt792x_phy *phy, char *alpha2); bool mt7925_regd_is_valid_channel(struct mt792x_dev *dev, enum nl80211_band band, diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index e98c4fd81044..9567ff883b28 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -30,6 +30,7 @@ #define MT792x_CHIP_CAP_RSSI_NOTIFY_EVT_EN BIT(1) #define MT792x_CHIP_CAP_WF_RF_PIN_CTRL_EVT_EN BIT(3) #define MT792x_CHIP_CAP_11D_EN BIT(4) +#define MT792x_CHIP_CAP_REGD_EN BIT(5) #define MT792x_CHIP_CAP_MLO_EN BIT(8) #define MT792x_CHIP_CAP_MLO_EML_EN BIT(9) @@ -83,6 +84,7 @@ enum { MT792x_CLC_POWER, MT792x_CLC_POWER_EXT, MT792x_CLC_BE_CTRL, + MT792x_CLC_REGD, MT792x_CLC_MAX_NUM, }; From 9b80bd9cab40c402a75e8dc850e97503ad3b8bbc Mon Sep 17 00:00:00 2001 From: Charlie-cy Wu Date: Tue, 9 Jun 2026 14:50:36 +0800 Subject: [PATCH 0761/1433] wifi: mt76: mt7921: add regulatory wiphy self manager support Introduce regulatory wiphy self-managed mode support for MT7921, allowing the driver to manage its own regulatory domain independently from the kernel's regulatory framework. Signed-off-by: Charlie-cy Wu Link: https://patch.msgid.link/20260609065036.577329-1-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt76_connac_mcu.h | 1 + .../net/wireless/mediatek/mt76/mt7921/mcu.c | 3 + .../net/wireless/mediatek/mt76/mt7921/regd.c | 188 +++++++++++++++++- .../net/wireless/mediatek/mt76/mt7921/regd.h | 55 ++++- 4 files changed, 242 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h index 8022ffd7af5a..8198efc6c05d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h @@ -1403,6 +1403,7 @@ enum { MCU_CE_CMD_FWLOG_2_HOST = 0xc5, MCU_CE_CMD_GET_WTBL = 0xcd, MCU_CE_CMD_GET_TXPWR = 0xd0, + MCU_CE_CMD_SET_REGD_CH = 0xd1, }; enum { diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c index 17d97407b8f3..a118a301564c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c @@ -1425,6 +1425,9 @@ int mt7921_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2, /* submit all clc config */ for (i = 0; i < ARRAY_SIZE(phy->clc); i++) { + if (i == MT792x_CLC_REGD) + continue; + ret = __mt7921_mcu_set_clc(dev, alpha2, env_cap, phy->clc[i], i); diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c index 80d1712ebfca..4a8ea4624fee 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c @@ -10,6 +10,15 @@ static bool mt7921_disable_clc; module_param_named(disable_clc, mt7921_disable_clc, bool, 0644); MODULE_PARM_DESC(disable_clc, "disable CLC support"); +static struct ieee80211_regdomain mt7921_regd_ww = { + .n_reg_rules = 1, + .alpha2 = "00", + .reg_rules = { + /* IEEE 802.11b/g, channels 1..11 */ + REG_RULE(2412 - 10, 2462 + 10, 40, 6, 20, 0), + } +}; + bool mt7921_regd_clc_supported(struct mt792x_dev *dev) { if (mt7921_disable_clc || @@ -33,6 +42,9 @@ mt7921_regd_channel_update(struct wiphy *wiphy, struct mt792x_dev *dev) np = mt76_find_power_limits_node(mdev); sband = wiphy->bands[NL80211_BAND_5GHZ]; + if (!sband) + return; + band_np = np ? of_get_child_by_name(np, "txpower-5g") : NULL; for (i = 0; i < sband->n_channels; i++) { ch = &sband->channels[i]; @@ -151,10 +163,176 @@ void mt7921_regd_notifier(struct wiphy *wiphy, if (pm->suspended) return; + if (MT7921_REGD_SUPPORTED(&dev->phy)) { + mt7921_regd_update(&dev->phy, req->alpha2); + + return; + } + mt7921_mcu_regd_update(dev, req->alpha2, req->country_ie_env); } +static struct sk_buff * +mt7921_regd_query_regdb(struct mt792x_phy *phy, char *alpha2) +{ + struct wiphy *wiphy = phy->mt76->hw->wiphy; + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct mt792x_dev *dev = mt792x_hw_dev(hw); + struct mt7921_clc *clc = phy->clc[MT792x_CLC_REGD]; + struct mt7921_regd_query_req *req; + struct mt7921_regd_cc *regd_cc; + struct sk_buff *ret_skb = NULL; + u8 *pos, *last_pos; + int ret = 0; + + if (!clc) + return NULL; + + pos = clc->data; + last_pos = pos + le32_to_cpu(clc->len) - sizeof(struct mt7921_clc); + while (pos < last_pos) { + u32 req_len = 0; + u32 rules_len = 0; + u32 sign_len = 4; + u32 n_reg_rules; + + if (pos + sizeof(*regd_cc) > last_pos) + break; + + regd_cc = (struct mt7921_regd_cc *)pos; + n_reg_rules = le32_to_cpu(regd_cc->n_reg_rules); + if (n_reg_rules > NL80211_MAX_SUPP_REG_RULES) + break; + + rules_len = sizeof(struct mt7921_regd_rule_header) + + sizeof(struct mt7921_regd_rule) * n_reg_rules; + + if (pos + sizeof(*regd_cc) + rules_len + sign_len > last_pos) + break; + + pos += sizeof(*regd_cc) + rules_len + sign_len; + if (memcmp(regd_cc->alpha2, alpha2, 2)) + continue; + + req_len = sizeof(*req) + rules_len + sign_len; + req = kzalloc(req_len, GFP_KERNEL); + + if (!req) + return NULL; + + req->ver = regd_cc->ver; + req->sign_type = regd_cc->sign_type; + req->size = cpu_to_le32(rules_len + sign_len); + req->n_reg_rules = regd_cc->n_reg_rules; + + memcpy(req->alpha2, regd_cc->alpha2, 2); + memcpy(req->data, regd_cc->data, rules_len + sign_len); + + ret = mt76_mcu_send_and_get_msg(&dev->mt76, + MCU_CE_CMD(SET_REGD_CH), + req, req_len, true, &ret_skb); + + kfree(req); + + return ret < 0 ? NULL : ret_skb; + } + + return NULL; +} + +int mt7921_regd_update(struct mt792x_phy *phy, char *alpha2) +{ + struct wiphy *wiphy = phy->mt76->hw->wiphy; + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct mt792x_dev *dev = mt792x_hw_dev(hw); + struct mt7921_regd_rule *mt7921_rule; + struct mt76_dev *mdev = &dev->mt76; + struct ieee80211_regdomain *regd; + struct ieee80211_reg_rule *rule; + struct mt7921_regd_rule_ev *ev; + int i, num_of_rules = 0; + struct sk_buff *skb; + int ret = 0; + + if (dev->hw_full_reset) + return 0; + + if (!MT7921_REGD_SUPPORTED(phy)) + return -EOPNOTSUPP; + + mt792x_mutex_acquire(dev); + skb = mt7921_regd_query_regdb(phy, alpha2); + mt792x_mutex_release(dev); + + if (!skb) { + ret = -EINVAL; + goto err; + } + + if (skb->len < sizeof(*ev) + 4) { + ret = -EINVAL; + goto err; + } + + ev = (struct mt7921_regd_rule_ev *)(skb->data + 4); + num_of_rules = le32_to_cpu(ev->n_reg_rules); + + if (!num_of_rules || + WARN_ON_ONCE(num_of_rules > NL80211_MAX_SUPP_REG_RULES)) { + ret = -EINVAL; + goto err; + } + + if (skb->len < struct_size(ev, reg_rule, num_of_rules) + 4) { + ret = -EINVAL; + goto err; + } + + regd = kzalloc(struct_size(regd, reg_rules, num_of_rules), GFP_KERNEL); + if (!regd) { + ret = -ENOMEM; + goto err; + } + + for (i = 0; i < num_of_rules; i++) { + mt7921_rule = &ev->reg_rule[i]; + rule = ®d->reg_rules[i]; + + rule->freq_range.start_freq_khz = + MHZ_TO_KHZ(le32_to_cpu(mt7921_rule->start_freq)); + rule->freq_range.end_freq_khz = + MHZ_TO_KHZ(le32_to_cpu(mt7921_rule->end_freq)); + rule->freq_range.max_bandwidth_khz = + MHZ_TO_KHZ(le32_to_cpu(mt7921_rule->max_bw)); + /* not used by fw */ + rule->power_rule.max_antenna_gain = DBI_TO_MBI(6); + rule->power_rule.max_eirp = DBM_TO_MBM(22); + rule->flags = le32_to_cpu(mt7921_rule->flags); + } + + regd->n_reg_rules = num_of_rules; + regd->dfs_region = ev->dfs_region; + + memcpy(regd->alpha2, alpha2, 2); + memcpy(mdev->alpha2, alpha2, 2); + + dev->regd_change = true; + mt7921_mcu_regd_update(dev, alpha2, ENVIRON_ANY); + + ret = regulatory_set_wiphy_regd(wiphy, regd); + + kfree(regd); +err: + dev_kfree_skb(skb); + + if (ret < 0) + return regulatory_set_wiphy_regd(wiphy, &mt7921_regd_ww); + + return ret; +} +EXPORT_SYMBOL_GPL(mt7921_regd_update); + static bool mt7921_regd_is_valid_alpha2(const char *alpha2) { @@ -192,7 +370,9 @@ int mt7921_regd_change(struct mt792x_phy *phy, char *alpha2) if (!memcmp(alpha2, mdev->alpha2, 2)) return 0; - if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN) + if (MT7921_REGD_SUPPORTED(phy)) + return mt7921_regd_update(phy, alpha2); + else if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN) return regulatory_hint(wiphy, alpha2); else return mt7921_mcu_set_clc(dev, alpha2, ENVIRON_INDOOR); @@ -205,7 +385,11 @@ int mt7921_regd_init(struct mt792x_phy *phy) struct mt792x_dev *dev = mt792x_hw_dev(hw); struct mt76_dev *mdev = &dev->mt76; - if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN) + if (MT7921_REGD_SUPPORTED(phy)) { + wiphy->regulatory_flags |= REGULATORY_WIPHY_SELF_MANAGED | + REGULATORY_DISABLE_BEACON_HINTS; + return mt7921_regd_update(phy, "00"); + } else if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN) wiphy->regulatory_flags |= REGULATORY_COUNTRY_IE_IGNORE | REGULATORY_DISABLE_BEACON_HINTS; else diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.h b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h index 5b24d0902c36..d4223ba8fbd5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/regd.h +++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h @@ -4,9 +4,57 @@ #ifndef __MT7921_REGD_H #define __MT7921_REGD_H -struct mt792x_dev; -struct wiphy; -struct regulatory_request; +#include "mt7921.h" + +struct mt7921_regd_rule_header { + u8 alpha2[2]; + u8 dfs_region; + u8 rsv[13]; +}; + +struct mt7921_regd_rule { + __le32 start_freq; + __le32 end_freq; + __le32 max_bw; + __le32 eirp; + __le32 flags; + u8 rsv[12]; +}; + +struct mt7921_regd_cc { + u8 alpha2[2]; + u8 ver; + u8 rsv; + __le32 n_reg_rules; + u8 sign_type; + u8 rsv1[7]; + u8 data[]; +}; + +struct mt7921_regd_rule_ev { + __le16 tag; + __le16 len; + __le32 n_reg_rules; + u8 dfs_region; + u8 rsv[15]; + struct mt7921_regd_rule reg_rule[]; +}; + +struct mt7921_regd_query_req { + u8 ver; + u8 sign_type; + u8 rsv1[2]; + __le32 size; + u8 alpha2[2]; + u8 rsv2[2]; + __le32 n_reg_rules; + u8 rsv3[64]; + u8 data[]; +}; + +#define MT7921_REGD_SUPPORTED(phy) \ + (((phy)->chip_cap & MT792x_CHIP_CAP_REGD_EN) && \ + (phy)->clc[MT792x_CLC_REGD]) int mt7921_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2, enum environment_cap country_ie_env); @@ -17,5 +65,6 @@ void mt7921_regd_notifier(struct wiphy *wiphy, bool mt7921_regd_clc_supported(struct mt792x_dev *dev); int mt7921_regd_change(struct mt792x_phy *phy, char *alpha2); int mt7921_regd_init(struct mt792x_phy *phy); +int mt7921_regd_update(struct mt792x_phy *phy, char *alpha2); #endif From 6bb5066cfcd4296ac5e0b6872f43f96a43bfe566 Mon Sep 17 00:00:00 2001 From: Chad Monroe Date: Tue, 21 Jul 2026 18:53:32 +0000 Subject: [PATCH 0762/1433] wifi: mt76: mt7996: fix EAPOL source BSS for non-MLD stations A non-MLD station's EAPOL and data frames are tagged with link_id == IEEE80211_LINK_UNSPECIFIED, which now skips the per-link lookup in mt7996_mac_write_txwi() and leaves omac_idx/band_idx/wmm_idx at slot 0. When the radio also runs AP VAPs the station's omac is non-zero (get_omac_idx() prefers HW BSSID slots 1-3), so its EAPOL frames egress from the wrong BSS and the 4-way handshake times out even though association succeeds. In mt7996_tx_prepare_skb(), resolve the link from the peer wcid when link_id is UNSPECIFIED and the wcid is not the global entry, restoring the pre-MLO behaviour for station traffic. Fixes: 729c83a3330c ("wifi: mt76: mt7996: fix reading zeroed info->control.flags after mt76_tx_status_skb_add()") Signed-off-by: Chad Monroe Link: https://patch.msgid.link/20260721185333.2419297-1-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 0eebc8182ca9..01208eac2a25 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -1030,6 +1030,11 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, IEEE80211_TX_CTRL_MLO_LINK); } + /* non-MLD frames are LINK_UNSPECIFIED; use the wcid's own link */ + if (link_id == IEEE80211_LINK_UNSPECIFIED && + wcid != &dev->mt76.global_wcid) + link_id = wcid->link_id; + if (link_id != wcid->link_id && link_id != IEEE80211_LINK_UNSPECIFIED) { if (msta) { struct mt7996_sta_link *msta_link = From 2087f029924b75324cfa9a41ba1a349a9620f06e Mon Sep 17 00:00:00 2001 From: Chad Monroe Date: Tue, 21 Jul 2026 18:53:33 +0000 Subject: [PATCH 0763/1433] wifi: mt76: mt7996: fix non-MLD station num_sta leak The MLO link-reconfiguration rework moved the per-phy num_sta decrement inside a link_valid guard. link_valid is only set for MLO links, but num_sta is incremented for every station link, including the non-MLO deflink. Non-MLO stations bump num_sta on association and never drop it on removal. A non-zero num_sta forces connected-mode off-channel scanning which prevents the directed probe exchange needed to find hidden APs. Decrement phy->num_sta on the actual link teardown, pairing it with the unconditional increment on link creation. Fixes: e8c819df0243 ("wifi: mt76: mt7996: Destroy active sta links in mt7996_mac_sta_remove()") Signed-off-by: Chad Monroe Link: https://patch.msgid.link/20260721185333.2419297-2-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index afbcc8c7b18b..574aeac286cc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1202,15 +1202,9 @@ void mt7996_mac_sta_remove_link(struct mt7996_dev *dev, mt76_wcid_cleanup(&dev->mt76, &msta_link->wcid); if (msta_link->wcid.link_valid) { - struct mt7996_phy *phy; - mt7996_mac_wtbl_update(dev, msta_link->wcid.idx, MT_WTBL_UPDATE_ADM_COUNT_CLEAR); - phy = __mt7996_phy(dev, msta_link->wcid.phy_idx); - if (phy) - phy->mt76->num_sta--; - if (msta->deflink_id == link_id) { msta->deflink_id = IEEE80211_LINK_UNSPECIFIED; if (msta->seclink_id == link_id) { @@ -1236,6 +1230,12 @@ void mt7996_mac_sta_remove_link(struct mt7996_dev *dev, } if (flush) { + struct mt7996_phy *phy = + __mt7996_phy(dev, msta_link->wcid.phy_idx); + + if (phy) + phy->mt76->num_sta--; + rcu_assign_pointer(msta->link[link_id], NULL); rcu_assign_pointer(dev->mt76.wcid[msta_link->wcid.idx], NULL); mt76_wcid_mask_clear(dev->mt76.wcid_mask, msta_link->wcid.idx); From 29e889c4ada83c69d10a3937f5ae2934306e2e3d Mon Sep 17 00:00:00 2001 From: Shayne Chen Date: Fri, 13 Mar 2026 14:21:50 +0800 Subject: [PATCH 0764/1433] wifi: mt76: mt7996: fix capability of EHT-MCS 15 in MRU According to the definition in IEEE Std 802.11be-2024, Table 9-417r: - If 80 MHz is not supported, bit 1-3 are set to 0. - If 160 MHz is not supported, bit 2-3 are set to 0. - If 320 MHz is not supported, bit 3 is set to 0. Fixes: 348533eb968d ("wifi: mt76: mt7996: add EHT capability init") Signed-off-by: Shayne Chen Link: https://patch.msgid.link/20260313062150.3165433-2-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index dbea4887b7ad..2ff93bd8b6dc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -1564,7 +1564,6 @@ mt7996_init_eht_caps(struct mt7996_phy *phy, enum nl80211_band band, struct ieee80211_sta_eht_cap *eht_cap = &data->eht_cap; struct ieee80211_eht_cap_elem_fixed *eht_cap_elem = &eht_cap->eht_cap_elem; struct ieee80211_eht_mcs_nss_supp *eht_nss = &eht_cap->eht_mcs_nss_supp; - enum nl80211_chan_width width = phy->mt76->chandef.width; int nss = hweight8(phy->mt76->antenna_mask); int sts = hweight16(phy->mt76->chainmask); u8 val; @@ -1640,11 +1639,16 @@ mt7996_init_eht_caps(struct mt7996_phy *phy, enum nl80211_band band, u8_encode_bits(u8_get_bits(1, GENMASK(1, 0)), IEEE80211_EHT_PHY_CAP5_MAX_NUM_SUPP_EHT_LTF_MASK); - val = width == NL80211_CHAN_WIDTH_320 ? 0xf : - width == NL80211_CHAN_WIDTH_160 ? 0x7 : - width == NL80211_CHAN_WIDTH_80 ? 0x3 : 0x1; - eht_cap_elem->phy_cap_info[6] = - u8_encode_bits(val, IEEE80211_EHT_PHY_CAP6_MCS15_SUPP_MASK); + eht_cap_elem->phy_cap_info[6] = IEEE80211_EHT_PHY_CAP6_MCS15_SUPP_MASK; + if (band != NL80211_BAND_6GHZ) { + eht_cap_elem->phy_cap_info[6] &= + ~IEEE80211_EHT_PHY_CAP6_MCS15_SUPP_320MHZ; + + if (band != NL80211_BAND_5GHZ) + eht_cap_elem->phy_cap_info[6] &= + ~(IEEE80211_EHT_PHY_CAP6_MCS15_SUPP_160MHZ | + IEEE80211_EHT_PHY_CAP6_MCS15_SUPP_80MHZ); + } val = u8_encode_bits(nss, IEEE80211_EHT_MCS_NSS_RX) | u8_encode_bits(nss, IEEE80211_EHT_MCS_NSS_TX); From 86897f106669c07eea4c34b54c3268d448d41426 Mon Sep 17 00:00:00 2001 From: Rex Lu Date: Wed, 22 Jul 2026 08:25:53 +0000 Subject: [PATCH 0765/1433] wifi: mt76: fix RX data queuing of RRO 3.0 For RRO 3.0, RX data released from a RRO data queue should be put to the indicator queue. The frames are processed and completed in the context of the indicator queue NAPI, which only polls skbs queued on the MT_RXQ_RRO_IND list; frames queued under the data queue id are left sitting on that list until the data queue NAPI happens to run, stalling and reordering RX data. Fixes: b1e58e137b61 ("wifi: mt76: mt7996: Introduce RRO MSDU callbacks") Signed-off-by: Rex Lu Signed-off-by: Shayne Chen Link: https://patch.msgid.link/20260722082610.2699628-1-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mac80211.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index c4cbf7195b80..f92277770488 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -893,6 +893,7 @@ static void mt76_rx_release_amsdu(struct mt76_phy *phy, enum mt76_rxq_id q) struct sk_buff *skb = phy->rx_amsdu[q].head; struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb; struct mt76_dev *dev = phy->dev; + struct mt76_queue *rxq = &dev->q_rx[q]; phy->rx_amsdu[q].head = NULL; phy->rx_amsdu[q].tail = NULL; @@ -921,6 +922,13 @@ static void mt76_rx_release_amsdu(struct mt76_phy *phy, enum mt76_rxq_id q) return; } } + + /* RRO 3.0 data queue skbs are processed and completed in the context + * of the indicator queue NAPI, which only polls its own skb list + */ + if (mt76_queue_is_wed_rro_data(rxq) && dev->hwrro_mode == MT76_HWRRO_V3) + q = MT_RXQ_RRO_IND; + __skb_queue_tail(&dev->rx_skb[q], skb); } From ce35ecffc96e6d097d27b6fe30677a2cfe2e0461 Mon Sep 17 00:00:00 2001 From: Peter Chiu Date: Wed, 22 Jul 2026 08:25:55 +0000 Subject: [PATCH 0766/1433] wifi: mt76: mt7996: fix MLD ID in MAC TXD and HIF TXP Problem: MCU command timeout while the firmware state is normal, and the firmware keeps showing the error log "ERROR!! NO PAUSE...". Root cause: If the MLD_ID field in the TXD is neither the primary link id nor the secondary link id, it may lead to a firmware busy loop when the third link is in power saving mode. Remap frames directed to a third link to the primary link wcid. Since TX status events and txfree completions carry the wcid the firmware saw, use the remapped wcid for packet id tracking and non-AQL packet accounting as well, while the frame keeps its original link context for addressing, band and OMAC selection. Fixes: 85cd5534a3f2 ("wifi: mt76: mt7996: use correct link_id when filling TXD and TXP") Signed-off-by: Peter Chiu Link: https://patch.msgid.link/20260722082610.2699628-3-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/mac.c | 29 +++++++++++++++++++ .../net/wireless/mediatek/mt76/mt7996/main.c | 2 +- .../wireless/mediatek/mt76/mt7996/mt7996.h | 1 + 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 01208eac2a25..a5a992bdbd15 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -854,6 +854,33 @@ mt7996_mac_write_txwi_80211(struct mt7996_dev *dev, __le32 *txwi, txwi[6] |= cpu_to_le32(MT_TXD6_DIS_MAT); } +/* The WLAN_IDX in the TXD and TXP must belong to the primary or secondary + * link of an MLD station; any other link id can make the firmware spin when + * that link is in powersave. Completion events carry the same index, so the + * wcid used for status tracking and accounting must match it + */ +struct mt76_wcid *mt7996_get_tx_wcid(struct mt76_wcid *wcid) +{ + struct mt7996_sta_link *msta_link; + struct mt7996_sta *msta; + + if (!wcid->sta) + return wcid; + + msta_link = container_of(wcid, struct mt7996_sta_link, wcid); + msta = msta_link->sta; + + if (!msta || wcid->link_id == msta->seclink_id || + wcid->link_id == msta->deflink_id) + return wcid; + + msta_link = mt7996_sta_link(msta, msta->deflink_id); + if (msta_link) + return &msta_link->wcid; + + return wcid; +} + void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi, struct sk_buff *skb, struct mt76_wcid *wcid, struct ieee80211_key_conf *key, int pid, @@ -1091,6 +1118,8 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, tx_info->buf[1].len, DMA_TO_DEVICE); } + wcid = mt7996_get_tx_wcid(wcid); + pid = mt76_tx_status_skb_add(mdev, wcid, tx_info->skb); memset(txwi_ptr, 0, MT_TXD_SIZE); /* Transmit non qos data by 802.11 header and need to fill txd by host*/ diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 574aeac286cc..f291560ae93c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1579,7 +1579,7 @@ static void mt7996_tx(struct ieee80211_hw *hw, if (msta_link) wcid = &msta_link->wcid; } - mt76_tx(mphy, control->sta, wcid, skb); + mt76_tx(mphy, control->sta, mt7996_get_tx_wcid(wcid), skb); unlock: rcu_read_unlock(); } diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index e03699e968ff..60397a1b1a2b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -872,6 +872,7 @@ bool mt7996_mac_wtbl_update(struct mt7996_dev *dev, int idx, u32 mask); void mt7996_mac_reset_counters(struct mt7996_phy *phy); void mt7996_mac_cca_stats_reset(struct mt7996_phy *phy); void mt7996_mac_enable_nf(struct mt7996_dev *dev, u8 band); +struct mt76_wcid *mt7996_get_tx_wcid(struct mt76_wcid *wcid); void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi, struct sk_buff *skb, struct mt76_wcid *wcid, struct ieee80211_key_conf *key, int pid, From 8ae659743ba936b22ecb4620815887728e2820d6 Mon Sep 17 00:00:00 2001 From: Michael-CY Lee Date: Wed, 22 Jul 2026 08:25:56 +0000 Subject: [PATCH 0767/1433] wifi: mt76: fix non-AQL packet accounting for MLO stations __mt76_tx_queue_skb() overrides the wcid passed by the driver with sta->drv_priv, so the wcid might incorrectly be changed after TX, causing wcid->non_aql_packets to be counted on the wrong wcid. For example, on the AP side, if a station's setup link is the 5G link and the station uses 2G to transmit a frame, the value of non_aql_packets is increased on the 5G wcid but decreased on the 2G wcid. Once the inflated counter exceeds MT_MAX_NON_AQL_PKT, the TX scheduler permanently refuses to service the station. Drop the reassignment and account on the wcid used for transmission. This also records the actual wcid in the queue entry. Fixes: e1378e5228aa ("mt76: rely on AQL for burst size limits on tx queueing") Signed-off-by: Michael-CY Lee Link: https://patch.msgid.link/20260722082610.2699628-4-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/tx.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index f96d9c471853..de8af32f15e5 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -324,10 +324,6 @@ __mt76_tx_queue_skb(struct mt76_phy *phy, int qid, struct sk_buff *skb, if (idx < 0 || !sta) return idx; - wcid = (struct mt76_wcid *)sta->drv_priv; - if (!wcid->sta) - return idx; - q->entry[idx].wcid = wcid->idx; if (!non_aql) From f137fabc1313427e08af06a414d929ebd9fd37d6 Mon Sep 17 00:00:00 2001 From: Michael-CY Lee Date: Wed, 22 Jul 2026 08:25:57 +0000 Subject: [PATCH 0768/1433] wifi: mt76: assign link_id when sending probe request during scan The link_id in info->control.flags is required by mt7996 to select the correct mt76_wcid for transmission. Not assigning the link_id in info->control.flags is equivalent to assigning the link_id to 0, causing mt7996 to select link_id 0 for transmission, so probe requests sent on behalf of an MLD vif scanning via a different link were transmitted with the wrong per-link wcid. Fixes: 31083e38548f ("wifi: mt76: add code for emulating hardware scanning") Signed-off-by: Michael-CY Lee Link: https://patch.msgid.link/20260722082610.2699628-5-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/scan.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/scan.c b/drivers/net/wireless/mediatek/mt76/scan.c index 7fe1b1fbb699..325638d587c9 100644 --- a/drivers/net/wireless/mediatek/mt76/scan.c +++ b/drivers/net/wireless/mediatek/mt76/scan.c @@ -48,6 +48,7 @@ mt76_scan_send_probe(struct mt76_dev *dev, struct cfg80211_ssid *ssid) struct mt76_phy *phy = dev->scan.phy; struct ieee80211_tx_info *info; struct sk_buff *skb; + u8 link_id; skb = ieee80211_probereq_get(phy->hw, vif->addr, ssid->ssid, ssid->ssid_len, req->ie_len); @@ -77,6 +78,10 @@ mt76_scan_send_probe(struct mt76_dev *dev, struct cfg80211_ssid *ssid) info->flags |= IEEE80211_TX_CTL_NO_CCK_RATE; info->control.flags |= IEEE80211_TX_CTRL_DONT_USE_RATE_MASK; + link_id = mvif->wcid ? mvif->wcid->link_id : IEEE80211_LINK_UNSPECIFIED; + info->control.flags &= ~IEEE80211_TX_CTRL_MLO_LINK; + info->control.flags |= u32_encode_bits(link_id, IEEE80211_TX_CTRL_MLO_LINK); + mt76_tx(phy, NULL, mvif->wcid, skb); out: From 2243778a5fae8329ab5f18e7adcd7e03b911a1b7 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Wed, 22 Jul 2026 08:25:58 +0000 Subject: [PATCH 0769/1433] wifi: mt76: mt7996: validate RX band_idx before dereferencing phys[] band_idx comes from a 2-bit descriptor field (0-3) and was used directly to index dev->mt76.phys[] (size __MT_MAX_BAND == 3) and dereference the result. A corrupt or reserved descriptor value could index out of bounds or hit a NULL phy on parts with fewer bands. Reject invalid band indices, mirroring mt7996_rx_get_wcid(). Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices") Link: https://patch.msgid.link/20260722082610.2699628-6-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index a5a992bdbd15..c93c3a0ab3da 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -441,7 +441,13 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q, memset(status, 0, sizeof(*status)); band_idx = FIELD_GET(MT_RXD1_NORMAL_BAND_IDX, rxd1); + if (!mt7996_band_valid(dev, band_idx)) + return -EINVAL; + mphy = dev->mt76.phys[band_idx]; + if (!mphy) + return -EINVAL; + phy = mphy->priv; status->phy_idx = mphy->band_idx; From 6469ae71e7e5d0132c934972f628f346ad0379cd Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Wed, 22 Jul 2026 08:25:59 +0000 Subject: [PATCH 0770/1433] wifi: mt76: mt7996: set MT76_MCU_RESET before waking MCU waiters on full reset mt7996_mac_full_reset() called wake_up(&dev->mt76.mcu.wait) without first setting MT76_MCU_RESET. The MCU response wait condition only checks the response queue and that bit, so the wake-up released nobody: a thread blocked in an MCU command against the dead firmware (typically holding dev->mt76.mutex) stayed asleep until its multi-second timeout, stalling recovery. Set the bit before the wake-up, as mt7915 does. Fixes: 27015b6fbcca ("wifi: mt76: mt7996: enable full system reset support") Link: https://patch.msgid.link/20260722082610.2699628-7-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index c93c3a0ab3da..5d0f8ec32b4a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2436,6 +2436,7 @@ mt7996_mac_full_reset(struct mt7996_dev *dev) dev->recovery.hw_full_reset = true; + set_bit(MT76_MCU_RESET, &dev->mphy.state); wake_up(&dev->mt76.mcu.wait); ieee80211_stop_queues(hw); From 6486e11a6e2f679597af2d5bb48c3b07a2b2a7ba Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Wed, 22 Jul 2026 08:26:00 +0000 Subject: [PATCH 0771/1433] wifi: mt76: mt7915: clear wcid mask under mutex after RCU pointer clear mt7915_remove_interface() cleared the wcid mask bit with no lock held and before clearing the RCU wcid pointer. The mask is a non-atomic RMW shared with the allocators, which all run under dev->mt76.mutex; on DBDC the two wiphys share one mt76_dev, so this raced add_interface/sta_add on the other band and could leak or double-hand-out a wcid. Clearing the bit before the RCU pointer also let a concurrent allocation reuse the index and publish its wcid, which the subsequent NULL assignment then wiped. Move the clear into the existing mutex section, after the RCU pointer is cleared. Fixes: f3049b88b2b3 ("wifi: mt76: mt7915: allocate vif wcid in the same range as stations") Link: https://patch.msgid.link/20260722082610.2699628-8-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c index 044b592efe28..c5eeafef3a90 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c @@ -294,7 +294,6 @@ static void mt7915_remove_interface(struct ieee80211_hw *hw, mt7915_mcu_add_bss_info(phy, vif, false); mt7915_mcu_add_sta(dev, vif, NULL, CONN_STATE_DISCONNECT, false); - mt76_wcid_mask_clear(dev->mt76.wcid_mask, mvif->sta.wcid.idx); mutex_lock(&dev->mt76.mutex); mt76_testmode_reset(phy->mt76, true); @@ -310,6 +309,7 @@ static void mt7915_remove_interface(struct ieee80211_hw *hw, mutex_lock(&dev->mt76.mutex); dev->mt76.vif_mask &= ~BIT_ULL(mvif->mt76.idx); phy->omac_mask &= ~BIT_ULL(mvif->mt76.omac_idx); + mt76_wcid_mask_clear(dev->mt76.wcid_mask, mvif->sta.wcid.idx); mutex_unlock(&dev->mt76.mutex); spin_lock_bh(&dev->mt76.sta_poll_lock); From 4a2f4be532e3ea4e2b536e411793a05aaa51af25 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Wed, 22 Jul 2026 08:26:01 +0000 Subject: [PATCH 0772/1433] wifi: mt76: mt7915: avoid nss underflow in mt7915_mcu_get_sta_nss If a peer's VHT/HE MCS map has no supported spatial stream (all fields 0x3), the loop exits with nss == 0 and the function returned (u8)-1 (255), which was then written into the firmware sta_rec_bf beamforming fields. Clamp the result to 0. Fixes: 89029a85482c ("mt76: mt7915: add Tx beamformer support") Link: https://patch.msgid.link/20260722082610.2699628-9-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/mcu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c index bbb2fedacb25..119bd3582295 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c @@ -51,7 +51,7 @@ mt7915_mcu_get_sta_nss(u16 mcs_map) break; } - return nss - 1; + return nss ? nss - 1 : 0; } static void From d4d92ccded678c92c390003926097ddbb6516bc7 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Wed, 22 Jul 2026 08:26:02 +0000 Subject: [PATCH 0773/1433] wifi: mt76: mt7996: don't report a zero TX bitrate mt7996_sta_statistics() set NL80211_STA_INFO_TX_BITRATE unconditionally after the block that already sets it, so a station with no rate info yet was reported to userspace with a valid-but-zero TX rate. Drop the redundant unconditional assignments; the in-block ones are sufficient. Fixes: b34f346b917e ("wifi: mt76: mt7996: drop return in mt7996_sta_statistics") Link: https://patch.msgid.link/20260722082610.2699628-10-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index f291560ae93c..2487b00c3790 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1883,8 +1883,6 @@ static void mt7996_sta_statistics(struct ieee80211_hw *hw, sinfo->txrate.flags = txrate->flags; sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE); } - sinfo->txrate.flags = txrate->flags; - sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE); sinfo->tx_failed = msta_link->wcid.stats.tx_failed; sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_FAILED); From 236145737480c4c0c515e09061d0d5f77cf52d3f Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Wed, 22 Jul 2026 08:26:03 +0000 Subject: [PATCH 0774/1433] wifi: mt76: mt7915: write RX header translation bit to the correct register MT_MDP_DCR0_RX_HDR_TRANS_EN is a field of MT_MDP_DCR0, but monitor-mode handling applied it to the per-band MT_DMA_DCR0 register instead. As a result RX header translation was never disabled in the MDP when entering monitor mode, and an undocumented bit of MT_DMA_DCR0 was toggled. Target MT_MDP_DCR0, matching the mt7996 driver. Fixes: b2491018587a ("wifi: mt76: mt7915: fix monitor mode issues") Link: https://patch.msgid.link/20260722082610.2699628-11-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c index c5eeafef3a90..4ed3d808654f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c @@ -493,7 +493,7 @@ static int mt7915_config(struct ieee80211_hw *hw, int radio_idx, mt76_rmw_field(dev, MT_DMA_DCR0(band), MT_DMA_DCR0_RXD_G5_EN, enabled); - mt76_rmw_field(dev, MT_DMA_DCR0(band), MT_MDP_DCR0_RX_HDR_TRANS_EN, + mt76_rmw_field(dev, MT_MDP_DCR0, MT_MDP_DCR0_RX_HDR_TRANS_EN, !dev->monitor_mask); mt76_testmode_reset(phy->mt76, true); mt76_wr(dev, MT_WF_RFCR(band), rxfilter); From 5caa622956166f6c445a384c7e964da4d553a501 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Wed, 22 Jul 2026 08:26:04 +0000 Subject: [PATCH 0775/1433] wifi: mt76: fix 4th chain ACK RSSI bitmask in sta_poll The per-chain response-frame RSSI values are packed one per byte, but the 4th chain was extracted with GENMASK(31, 14) instead of GENMASK(31, 24). The wrong mask overlaps chains 1-3 and shifts by 14, producing a garbage chain-3 value that corrupts ack_signal/avg_ack_signal on 4x4 radios. Extract the correct byte. Fixes: a71b648e3527 ("wifi: mt76: mt7915: add ack signal support") Fixes: ea5d99d07fbf ("wifi: mt76: mt7996: enable ack signal support") Fixes: 67fc7a304bf5 ("wifi: mt76: mt7921: add ack signal support") Fixes: c948b5da6bbe ("wifi: mt76: mt7925: add Mediatek Wi-Fi7 driver for mt7925 chips") Link: https://patch.msgid.link/20260722082610.2699628-12-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/mac.c | 2 +- drivers/net/wireless/mediatek/mt76/mt7921/mac.c | 2 +- drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 2 +- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c index 9e93201e4186..1154d3c4b811 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c @@ -221,7 +221,7 @@ static void mt7915_mac_sta_poll(struct mt7915_dev *dev) rssi[0] = to_rssi(GENMASK(7, 0), val); rssi[1] = to_rssi(GENMASK(15, 8), val); rssi[2] = to_rssi(GENMASK(23, 16), val); - rssi[3] = to_rssi(GENMASK(31, 14), val); + rssi[3] = to_rssi(GENMASK(31, 24), val); msta->ack_signal = mt76_rx_signal(msta->vif->phy->mt76->antenna_mask, rssi); diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c index 17014b1f91e0..e69978184f68 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c @@ -156,7 +156,7 @@ static void mt7921_mac_sta_poll(struct mt792x_dev *dev) rssi[0] = to_rssi(GENMASK(7, 0), val); rssi[1] = to_rssi(GENMASK(15, 8), val); rssi[2] = to_rssi(GENMASK(23, 16), val); - rssi[3] = to_rssi(GENMASK(31, 14), val); + rssi[3] = to_rssi(GENMASK(31, 24), val); mlink->ack_signal = mt76_rx_signal(msta->vif->phy->mt76->antenna_mask, rssi); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index 2931f5176137..101f571b027f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -152,7 +152,7 @@ static void mt7925_mac_sta_poll(struct mt792x_dev *dev) rssi[0] = to_rssi(GENMASK(7, 0), val); rssi[1] = to_rssi(GENMASK(15, 8), val); rssi[2] = to_rssi(GENMASK(23, 16), val); - rssi[3] = to_rssi(GENMASK(31, 14), val); + rssi[3] = to_rssi(GENMASK(31, 24), val); mlink->ack_signal = mt76_rx_signal(msta->vif->phy->mt76->antenna_mask, rssi); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 5d0f8ec32b4a..5667a89d488a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -166,7 +166,7 @@ static void mt7996_mac_sta_poll(struct mt7996_dev *dev) rssi[0] = to_rssi(GENMASK(7, 0), val); rssi[1] = to_rssi(GENMASK(15, 8), val); rssi[2] = to_rssi(GENMASK(23, 16), val); - rssi[3] = to_rssi(GENMASK(31, 14), val); + rssi[3] = to_rssi(GENMASK(31, 24), val); mlink = rcu_dereference(msta->vif->mt76.link[wcid->link_id]); if (mlink) { From deaa2e3656937fbbe312f0ee2616c756c6e2511f Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Wed, 22 Jul 2026 08:26:05 +0000 Subject: [PATCH 0776/1433] wifi: mt76: mt7996: fix TX DMA mapping leak for AddBA req frames mt7996/mt7992 hand the firmware a HW MAC-TXP for AddBA req action frames (MT_TXD7_MAC_TXD, set in mt7996_mac_write_txwi_80211()), but are otherwise FW-TXP devices. On tx free mt76_connac_txp_skb_unmap() therefore decodes the per-frame txp as a struct mt76_connac_fw_txp. For a MAC-TXP the fw_txp.nbuf byte aliases the AddBA TID word (MT_TXP1_TID_ADDBA), which is always zero, so the unmap loop runs zero times and the skb DMA mapping in buf[1] is never unmapped. buf[1].skip_unmap is set unconditionally, so the generic DMA-ring cleanup skips it as well. Each AddBA req therefore leaks one TX DMA mapping, roughly one per (re)association. With WED enabled these mappings are bounced through the WED swiotlb pool, so under continuous client reconnect churn the pool is exhausted after ~1-2 days, after which DMA mapping fails for WED, the WiFi MCU and other on-SoC consumers. Keep the deferred (token release) unmap that the design relies on, and add an mt7996-specific txp unmap that inspects MT_TXD7_MAC_TXD and unmaps buf[1] from the MAC-TXP layout for those frames, delegating to mt76_connac_txp_skb_unmap() otherwise. Cc: stable@vger.kernel.org Fixes: cb6ebbdffef2 ("wifi: mt76: mt7996: support writing MAC TXD for AddBA Request") Link: https://patch.msgid.link/20260722082610.2699628-13-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/mac.c | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 5667a89d488a..7024fadce204 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -1279,6 +1279,30 @@ mt7996_tx_check_aggr(struct ieee80211_link_sta *link_sta, clear_bit(tid, &wcid->ampdu_state); } +static void +mt7996_txp_skb_unmap(struct mt76_dev *mdev, struct mt76_txwi_cache *t) +{ + u8 *txwi_ptr = mt76_get_txwi_ptr(mdev, t); + __le32 *txwi = (__le32 *)txwi_ptr; + __le32 *txp; + dma_addr_t addr; + u32 val; + + if (!(le32_to_cpu(txwi[7]) & MT_TXD7_MAC_TXD)) { + mt76_connac_txp_skb_unmap(mdev, t); + return; + } + + txp = (__le32 *)(txwi_ptr + MT_TXD_SIZE); + val = le32_to_cpu(txp[3]); + addr = le32_to_cpu(txp[2]); +#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT + addr |= (dma_addr_t)FIELD_GET(MT_TXP3_DMA_ADDR_H, val) << 32; +#endif + dma_unmap_single(mdev->dma_dev, addr, FIELD_GET(MT_TXP_BUF_LEN, val), + DMA_TO_DEVICE); +} + static void mt7996_txwi_free(struct mt7996_dev *dev, struct mt76_txwi_cache *t, struct ieee80211_link_sta *link_sta, @@ -1288,7 +1312,7 @@ mt7996_txwi_free(struct mt7996_dev *dev, struct mt76_txwi_cache *t, __le32 *txwi; u16 wcid_idx; - mt76_connac_txp_skb_unmap(mdev, t); + mt7996_txp_skb_unmap(mdev, t); if (!t->skb) goto out; From 422dd2db28ae27c35a586acd9ad482f30000c090 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Wed, 22 Jul 2026 08:26:06 +0000 Subject: [PATCH 0777/1433] wifi: mt76: fix stranded frames in mt76_txq_schedule_pending A wcid is added to phy->tx_list whenever either tx_pending or tx_offchannel becomes non-empty, but the requeue check after a partial schedule required BOTH queues to be non-empty. When mt76_txq_schedule_pending_wcid() returns -1 (queue stopped or MT76_RESET) it leaves frames in tx_pending while tx_offchannel is empty, so the wcid is dropped from every scheduling list and its frames stall until the next mt76_tx() for that wcid or wcid cleanup. This strands EAPOL/mgmt/nullfunc frames under momentary queue-full or across scan/channel-switch, causing association and 4-way-handshake timeouts. Requeue when either queue still holds frames, matching the enqueue condition. Fixes: 0b3be9d1d34e ("wifi: mt76: add separate tx scheduling queue for off-channel tx") Link: https://patch.msgid.link/20260722082610.2699628-14-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/tx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index de8af32f15e5..dc8407be2891 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -682,8 +682,8 @@ void mt76_txq_schedule_pending(struct mt76_phy *phy) ret = mt76_txq_schedule_pending_wcid(phy, wcid, &wcid->tx_pending); spin_lock(&phy->tx_lock); - if (!skb_queue_empty(&wcid->tx_pending) && - !skb_queue_empty(&wcid->tx_offchannel) && + if ((!skb_queue_empty(&wcid->tx_pending) || + !skb_queue_empty(&wcid->tx_offchannel)) && list_empty(&wcid->tx_list)) list_add_tail(&wcid->tx_list, &phy->tx_list); } From 6650c8532173f5a8d08b013a5e701abe5723f574 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Wed, 22 Jul 2026 08:26:07 +0000 Subject: [PATCH 0778/1433] wifi: mt76: allow TX aggregation on the VO queue The TX aggregation check skipped TIDs 6 and 7, so all voice-priority traffic was sent without a BA session and therefore unaggregated, limiting throughput for stations that map bulk traffic to VO. The hardware handles aggregation on the VO queue fine, and a peer that prefers unaggregated voice frames can still decline the ADDBA request. Remove the skip from both the connac2 and the mt7996 aggregation setup paths. Link: https://patch.msgid.link/20260722082610.2699628-15-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c | 2 -- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 2 -- 2 files changed, 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c index fc9f782032ef..0ac3ab42ea40 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c @@ -1152,8 +1152,6 @@ void mt76_connac2_tx_check_aggr(struct ieee80211_sta *sta, __le32 *txwi) return; tid = le32_get_bits(txwi[1], MT_TXD1_TID); - if (tid >= 6) /* skip VO queue */ - return; val = le32_to_cpu(txwi[2]); fc = FIELD_GET(MT_TXD2_FRAME_TYPE, val) << 2 | diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 7024fadce204..c204327eaa37 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -1252,8 +1252,6 @@ mt7996_tx_check_aggr(struct ieee80211_link_sta *link_sta, return; tid = skb->priority & IEEE80211_QOS_CTL_TID_MASK; - if (tid >= 6) /* skip VO queue */ - return; if (is_8023) { fc = IEEE80211_FTYPE_DATA | From d3ecac68f73b11828e72eaf7952a9beb5caea12b Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Wed, 22 Jul 2026 08:26:08 +0000 Subject: [PATCH 0779/1433] wifi: mt76: fix uninitialised RXDMAD_C descriptor info Unlike other WED-RRO queues, RXDMAD_C frames continue into the skb build path, but mt76_dma_get_buf() skips the desc->info read for RRO queues, so the uninitialised on-stack info was stored into skb->cb and passed to rx_skb(); initialise it to zero. Fixes: e50d4d710efd ("wifi: mt76: Add mt76_dma_get_rxdmad_c_buf utility routione") Link: https://patch.msgid.link/20260722082610.2699628-16-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index 3c4abeb5440a..baa2a3525e1e 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -1002,7 +1002,7 @@ mt76_dma_rx_process(struct mt76_dev *dev, struct mt76_queue *q, int budget) while (done < budget) { bool drop = false; - u32 info; + u32 info = 0; if (check_ddone) { if (q->tail == dma_idx) From e1f97c10a4ec2b9db69a134b757304399ca903ce Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Wed, 22 Jul 2026 08:26:09 +0000 Subject: [PATCH 0780/1433] wifi: mt76: fix RXDMAD_C buffer recycling race The RXDMAD_C buffers come from the RRO data queues' page pools, which are bound to a different NAPI, so the direct page-pool recycle used here could race the owning NAPI; take the non-direct path as is already done for WED RX queues. Fixes: e50d4d710efd ("wifi: mt76: Add mt76_dma_get_rxdmad_c_buf utility routione") Link: https://patch.msgid.link/20260722082610.2699628-17-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index baa2a3525e1e..946b7f90dc0f 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -990,7 +990,8 @@ mt76_dma_rx_process(struct mt76_dev *dev, struct mt76_queue *q, int budget) struct sk_buff *skb; unsigned char *data; bool check_ddone = false; - bool allow_direct = !mt76_queue_is_wed_rx(q); + bool allow_direct = !mt76_queue_is_wed_rx(q) && + !mt76_queue_is_wed_rro_rxdmad_c(q); bool more; if ((q->flags & MT_QFLAG_WED_RRO_EN) || From dd59a6126a8f1bd52bf6bd057bf0c8307f76a74b Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Wed, 22 Jul 2026 08:26:10 +0000 Subject: [PATCH 0781/1433] wifi: mt76: mt7915: poll the correct SLP CTRL register for the second adie The clock enable path for the second adie sets MT_ADIE_SLP_CTRL_CK0(1) but polled the busy bit of MT_ADIE_SLP_CTRL_CK0(0), so dual-adie bring-up could proceed before the adie1 clock was stable. Fixes: 99ad32a4ca3a ("mt76: mt7915: add support for MT7986") Link: https://patch.msgid.link/20260722082610.2699628-18-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/soc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/soc.c b/drivers/net/wireless/mediatek/mt76/mt7915/soc.c index 54ff6de96f3e..13fba2a061c7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/soc.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/soc.c @@ -908,7 +908,7 @@ static void mt7986_wmac_clock_enable(struct mt7915_dev *dev, u32 adie_type) read_poll_timeout(mt76_rr, cur, !(cur & MT_SLP_CTRL_BSY_MASK), USEC_PER_MSEC, 50 * USEC_PER_MSEC, false, - dev, MT_ADIE_SLP_CTRL_CK0(0)); + dev, MT_ADIE_SLP_CTRL_CK0(1)); } mt76_wmac_spi_unlock(dev); From 3310e71a74b176d3613dfb42b6bc630d99e90cbb Mon Sep 17 00:00:00 2001 From: Rex Lu Date: Wed, 22 Jul 2026 08:25:54 +0000 Subject: [PATCH 0782/1433] wifi: mt76: check txfree done event on the WED hw path Check the txfree done event DW1 bit 15 when WED is enabled, to avoid the driver reading a txfree done event before WED has finished reading it. No need to check this flag on WED v2, otherwise SER will occur. The bit position was previously defined as MT_DMA_CTL_BURST, which is unused; rename it to match its function on the txfree ring. Fixes: 83eafc9251d6 ("wifi: mt76: mt7996: add wed tx support") Signed-off-by: Rex Lu Signed-off-by: Shayne Chen Link: https://patch.msgid.link/20260722082610.2699628-2-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.c | 9 +++++++++ drivers/net/wireless/mediatek/mt76/dma.h | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index 946b7f90dc0f..bd8ee5260acc 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -608,6 +608,15 @@ mt76_dma_dequeue(struct mt76_dev *dev, struct mt76_queue *q, bool flush, q->desc[idx].ctrl |= cpu_to_le32(MT_DMA_CTL_DMA_DONE); else if (!(q->desc[idx].ctrl & cpu_to_le32(MT_DMA_CTL_DMA_DONE))) return NULL; +#ifdef CONFIG_NET_MEDIATEK_SOC_WED + /* on WED v3 the M_DONE bit signals that WED is done reading + * the txfree descriptor; WED v2 does not set it + */ + else if (dev->mmio.wed.version > 2 && + mt76_queue_is_wed_tx_free(q) && + !(q->desc[idx].ctrl & cpu_to_le32(MT_DMA_CTL_M_DONE))) + return NULL; +#endif } done: q->tail = (q->tail + 1) % q->ndesc; diff --git a/drivers/net/wireless/mediatek/mt76/dma.h b/drivers/net/wireless/mediatek/mt76/dma.h index 2a0226c83f3c..a2cf82cfdaaa 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.h +++ b/drivers/net/wireless/mediatek/mt76/dma.h @@ -11,7 +11,7 @@ #define MT_DMA_CTL_SD_LEN1 GENMASK(13, 0) #define MT_DMA_CTL_LAST_SEC1 BIT(14) -#define MT_DMA_CTL_BURST BIT(15) +#define MT_DMA_CTL_M_DONE BIT(15) #define MT_DMA_CTL_SD_LEN0 GENMASK(29, 16) #define MT_DMA_CTL_LAST_SEC0 BIT(30) #define MT_DMA_CTL_DMA_DONE BIT(31) From cbeed7096794f748d16c78cd9d3e0004420d1e9d Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 12:47:45 +0000 Subject: [PATCH 0783/1433] wifi: mt76: fix HE DCM max-RU capability encoding sta_rec_he.dcm_rx_max_nss was assigned twice: the second assignment, sourced from HE PHY capability byte 8 (DCM max RU), overwrote the RX-NSS value and left dcm_max_ru at zero. Every associated HE station advertising DCM support was configured in firmware with a wrong dcm_rx_max_nss and a zero dcm_max_ru. Store the DCM max-RU value in dcm_max_ru as intended. The same copy-paste error existed in both the shared connac2 path and the mt7915 path. Fixes: c336318f57a9 ("mt76: mt7915: add HE capabilities support for peers") Fixes: 67aa27431c7f ("mt76: mt7921: rely on mt76_connac_mcu common library") Link: https://patch.msgid.link/20260724124813.3961474-1-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c | 2 +- drivers/net/wireless/mediatek/mt76/mt7915/mcu.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c index 8f56f568fa0b..2f925d22b9aa 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c @@ -800,7 +800,7 @@ mt76_connac_mcu_sta_he_tlv(struct sk_buff *skb, struct ieee80211_sta *sta) HE_PHY(CAP3_DCM_MAX_CONST_RX_MASK, elem->phy_cap_info[3]); he->dcm_rx_max_nss = HE_PHY(CAP3_DCM_MAX_RX_NSS_2, elem->phy_cap_info[3]); - he->dcm_rx_max_nss = + he->dcm_max_ru = HE_PHY(CAP8_DCM_MAX_RU_MASK, elem->phy_cap_info[8]); he->pkt_ext = 2; diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c index 119bd3582295..96d2770f7c39 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c @@ -902,7 +902,7 @@ mt7915_mcu_sta_he_tlv(struct sk_buff *skb, struct ieee80211_sta *sta, HE_PHY(CAP3_DCM_MAX_CONST_RX_MASK, elem->phy_cap_info[3]); he->dcm_rx_max_nss = HE_PHY(CAP3_DCM_MAX_RX_NSS_2, elem->phy_cap_info[3]); - he->dcm_rx_max_nss = + he->dcm_max_ru = HE_PHY(CAP8_DCM_MAX_RU_MASK, elem->phy_cap_info[8]); he->pkt_ext = 2; From 44af52467e72094351a362bf69effd52f1d9c186 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 12:47:46 +0000 Subject: [PATCH 0784/1433] wifi: mt76: mt7996: bound TLV walk in mt7996_mcu_get_chip_config The response TLV loop advanced by tlv->len without a minimum, so a theoretical firmware response containing a zero-length TLV could spin forever, hanging the CPU during device probe. The u32 payload was also read without bounds checking. Reject a short fixed field, stop on a TLV whose length underruns the header or overruns the skb. Fixes: 5d33053be609 ("wifi: mt76: mt7996: add variants support") Link: https://patch.msgid.link/20260724124813.3961474-2-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index a1bae5db8500..f57d4a28cc27 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -4445,21 +4445,31 @@ int mt7996_mcu_get_chip_config(struct mt7996_dev *dev, u32 *cap) return ret; /* fixed field */ + if (skb->len < 4) { + dev_kfree_skb(skb); + return -EINVAL; + } skb_pull(skb, 4); buf = skb->data; - while (buf - skb->data < skb->len) { + while (buf - skb->data + sizeof(struct tlv) <= skb->len) { struct tlv *tlv = (struct tlv *)buf; + u16 tlv_len = le16_to_cpu(tlv->len); + + if (tlv_len < sizeof(*tlv) || + tlv_len > skb->len - (buf - skb->data)) + break; switch (le16_to_cpu(tlv->tag)) { case UNI_EVENT_CHIP_CONFIG_EFUSE_VERSION: - *cap = le32_to_cpu(*(__le32 *)(buf + sizeof(*tlv))); + if (tlv_len >= sizeof(*tlv) + sizeof(__le32)) + *cap = le32_to_cpu(*(__le32 *)(buf + sizeof(*tlv))); break; default: break; } - buf += le16_to_cpu(tlv->len); + buf += tlv_len; } dev_kfree_skb(skb); From 6689b4a65e88d7b019e687fa9d6943ca070f3e43 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 12:47:47 +0000 Subject: [PATCH 0785/1433] wifi: mt76: fix out-of-bounds access in mmio copy helpers mt76_mmio_write_copy() and mt76_mmio_read_copy() iterate up to ALIGN(len, 4), so a length that is not a multiple of four reads past the source buffer (write_copy) or writes past the destination (read_copy). Copy the aligned body in the loop and handle the remaining tail through a 4-byte bounce buffer, keeping the register access width unchanged. Fixes: 2df00805f7db ("wifi: mt76: mmio_*_copy fix byte order and alignment") Link: https://patch.msgid.link/20260724124813.3961474-3-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mmio.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mmio.c b/drivers/net/wireless/mediatek/mt76/mmio.c index 05d74cd7248e..73d47608bf42 100644 --- a/drivers/net/wireless/mediatek/mt76/mmio.c +++ b/drivers/net/wireless/mediatek/mt76/mmio.c @@ -35,9 +35,16 @@ static void mt76_mmio_write_copy(struct mt76_dev *dev, u32 offset, { int i; - for (i = 0; i < ALIGN(len, 4); i += 4) + for (i = 0; i + 4 <= len; i += 4) writel(get_unaligned_le32(data + i), dev->mmio.regs + offset + i); + + if (i < len) { + u8 tmp[4] = {}; + + memcpy(tmp, data + i, len - i); + writel(get_unaligned_le32(tmp), dev->mmio.regs + offset + i); + } } static void mt76_mmio_read_copy(struct mt76_dev *dev, u32 offset, @@ -45,9 +52,16 @@ static void mt76_mmio_read_copy(struct mt76_dev *dev, u32 offset, { int i; - for (i = 0; i < ALIGN(len, 4); i += 4) + for (i = 0; i + 4 <= len; i += 4) put_unaligned_le32(readl(dev->mmio.regs + offset + i), data + i); + + if (i < len) { + u8 tmp[4]; + + put_unaligned_le32(readl(dev->mmio.regs + offset + i), tmp); + memcpy(data + i, tmp, len - i); + } } static int mt76_mmio_wr_rp(struct mt76_dev *dev, u32 base, From 2fb6480c52f611338e1b0abe5e6219be1fc9ab75 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 12:47:48 +0000 Subject: [PATCH 0786/1433] wifi: mt76: mt7915: unwind state on add_interface failure When mt76_wcid_alloc() fails, mt7915_add_interface() returned without clearing the vif_mask/omac_mask bits it had already set, without removing the firmware dev info added earlier, and without clearing a monitor_vif pointer to the vif mac80211 is about to free. mac80211 does not call remove_interface() for a failed add, so the indices and firmware dev entry leaked permanently and testmode could dereference the stale monitor_vif. Add a proper error unwind. Fixes: b619e01380ee ("mt76: fix MBSS index condition in DBDC mode") Link: https://patch.msgid.link/20260724124813.3961474-4-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/main.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c index 4ed3d808654f..d2130226de64 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c @@ -249,7 +249,7 @@ static int mt7915_add_interface(struct ieee80211_hw *hw, idx = mt76_wcid_alloc(dev->mt76.wcid_mask, mt7915_wtbl_size(dev)); if (idx < 0) { ret = -ENOSPC; - goto out; + goto err; } INIT_LIST_HEAD(&mvif->sta.rc_list); @@ -277,7 +277,17 @@ static int mt7915_add_interface(struct ieee80211_hw *hw, mt7915_mcu_add_sta(dev, vif, NULL, CONN_STATE_PORT_SECURE, true); rcu_assign_pointer(dev->mt76.wcid[idx], &mvif->sta.wcid); + mutex_unlock(&dev->mt76.mutex); + + return 0; + +err: + dev->mt76.vif_mask &= ~BIT_ULL(mvif->mt76.idx); + phy->omac_mask &= ~BIT_ULL(mvif->mt76.omac_idx); + mt7915_mcu_add_dev_info(phy, vif, false); out: + if (phy->monitor_vif == vif) + phy->monitor_vif = NULL; mutex_unlock(&dev->mt76.mutex); return ret; From 6190db312b8230813f529f014b26247c6d9800d0 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 12:47:49 +0000 Subject: [PATCH 0787/1433] wifi: mt76: mt7996: hold dev->mt76.mutex while disabling tx worker in SER mt7996_mac_reset_work() parked the tx worker and disabled the RX/TX NAPIs before taking dev->mt76.mutex. mt76_worker_disable()/_enable() are plain kthread park/unpark, not refcounted, and __mt76_set_channel() toggles the same worker and the MT76_RESET bit under the mutex. An L1 SER racing a channel switch could therefore have the worker unparked and MT76_RESET cleared while the reset path resets the DMA rings, corrupting descriptors or tokens. Take the mutex before disabling the worker, as mt7915 does. Fixes: 27015b6fbcca ("wifi: mt76: mt7996: enable full system reset support") Link: https://patch.msgid.link/20260724124813.3961474-5-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index c204327eaa37..e0a1076ac706 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2573,6 +2573,8 @@ void mt7996_mac_reset_work(struct work_struct *work) cancel_delayed_work_sync(&phy->mt76->mac_work); } + mutex_lock(&dev->mt76.mutex); + mt76_worker_disable(&dev->mt76.tx_worker); mt76_for_each_q_rx(&dev->mt76, i) { if (mtk_wed_device_active(&dev->mt76.mmio.wed) && @@ -2590,8 +2592,6 @@ void mt7996_mac_reset_work(struct work_struct *work) } napi_disable(&dev->mt76.tx_napi); - mutex_lock(&dev->mt76.mutex); - mt76_wr(dev, MT_MCU_INT_EVENT, MT_MCU_INT_EVENT_DMA_STOPPED); if (mt7996_wait_reset_state(dev, MT_MCU_CMD_RESET_DONE)) { From e8b76a6e0d04d13ae55dc746a270f382825c395b Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 12:47:50 +0000 Subject: [PATCH 0788/1433] wifi: mt76: decode the full VHT Rx STBC capability field The Rx STBC subfield of the VHT capabilities is a 3-bit cumulative value, but the driver only tested the RXSTBC_1 bit when advertising the peer's Rx STBC support to firmware. A peer reporting Rx STBC of 2, 3 or 4 has that bit clear, so STBC was never used towards it. Test the full IEEE80211_VHT_CAP_RXSTBC_MASK, matching the HT path. Fixes: 046d2e7c50e3 ("mac80211: prepare sta handling for MLO support") Fixes: 2660fde82f65 ("wifi: mt76: mt7996: Update mt7996_mcu_add_rate_ctrl to MLO") Link: https://patch.msgid.link/20260724124813.3961474-6-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/mcu.c | 2 +- drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c index 96d2770f7c39..8e0616504f4d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c @@ -1625,7 +1625,7 @@ mt7915_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, struct mt7915_dev *dev, cap |= STA_CAP_VHT_SGI_160; if (sta->deflink.vht_cap.cap & IEEE80211_VHT_CAP_TXSTBC) cap |= STA_CAP_VHT_TX_STBC; - if (sta->deflink.vht_cap.cap & IEEE80211_VHT_CAP_RXSTBC_1) + if (sta->deflink.vht_cap.cap & IEEE80211_VHT_CAP_RXSTBC_MASK) cap |= STA_CAP_VHT_RX_STBC; if (mvif->cap.vht_ldpc && (sta->deflink.vht_cap.cap & IEEE80211_VHT_CAP_RXLDPC)) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index f57d4a28cc27..289c8390b5fa 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -2483,7 +2483,7 @@ mt7996_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, struct mt7996_dev *dev, cap |= STA_CAP_VHT_SGI_160; if (link_sta->vht_cap.cap & IEEE80211_VHT_CAP_TXSTBC) cap |= STA_CAP_VHT_TX_STBC; - if (link_sta->vht_cap.cap & IEEE80211_VHT_CAP_RXSTBC_1) + if (link_sta->vht_cap.cap & IEEE80211_VHT_CAP_RXSTBC_MASK) cap |= STA_CAP_VHT_RX_STBC; if ((vif->type != NL80211_IFTYPE_AP || link_conf->vht_ldpc) && (link_sta->vht_cap.cap & IEEE80211_VHT_CAP_RXLDPC)) From 29fe963d86e434b32d07958a6785be724e45c787 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 12:47:51 +0000 Subject: [PATCH 0789/1433] wifi: mt76: fix ER-SU 106-tone RU check in RX rate decode MT_PHY_TYPE_HE_EXT_SU is an enum value (9), not a bit flag, so the bitwise test "*mode & MT_PHY_TYPE_HE_EXT_SU" also matches OFDM, HT-GF and several HE/EHT modes. Only genuine ER-SU should be classified as a 106-tone RU at 40 MHz; use an equality comparison. Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices") Fixes: d832f5e73815 ("mt76: connac: move mt76_connac2_mac_fill_rx_rate in connac module") Link: https://patch.msgid.link/20260724124813.3961474-7-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c | 2 +- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c index 0ac3ab42ea40..cbf6a987cf90 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c @@ -1114,7 +1114,7 @@ int mt76_connac2_mac_fill_rx_rate(struct mt76_dev *dev, case IEEE80211_STA_RX_BW_20: break; case IEEE80211_STA_RX_BW_40: - if (*mode & MT_PHY_TYPE_HE_EXT_SU && + if (*mode == MT_PHY_TYPE_HE_EXT_SU && (idx & MT_PRXV_TX_ER_SU_106T)) { status->bw = RATE_INFO_BW_HE_RU; status->he_ru = diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index e0a1076ac706..866752f2fa24 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -349,7 +349,7 @@ mt7996_mac_fill_rx_rate(struct mt7996_dev *dev, case IEEE80211_STA_RX_BW_20: break; case IEEE80211_STA_RX_BW_40: - if (*mode & MT_PHY_TYPE_HE_EXT_SU && + if (*mode == MT_PHY_TYPE_HE_EXT_SU && (idx & MT_PRXV_TX_ER_SU_106T)) { status->bw = RATE_INFO_BW_HE_RU; status->he_ru = From 50c66bab321140c49aa2ed779a3ec9d2f085b458 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 12:47:52 +0000 Subject: [PATCH 0790/1433] wifi: mt76: mt7996: reserve space for the CSA-abort countdown TLV When a CSA countdown is active, mt7996_mcu_beacon_cntdwn() emits two bss_bcn_cntdwn_tlv entries (the CSA countdown and the CCA-abort BCC), but MT7996_BEACON_UPDATE_SIZE only reserved one. With MBSSID enabled and a near-maximum beacon template the extra 8 bytes could push the offload command past MT7996_MAX_BSS_OFFLOAD_SIZE and trigger skb_over_panic(). Reserve room for both countdown TLVs. Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices") Link: https://patch.msgid.link/20260724124813.3961474-8-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mcu.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index 8902e16508b7..c673e986ecb5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -917,7 +917,7 @@ enum { #define MT7996_BEACON_UPDATE_SIZE (sizeof(struct bss_req_hdr) + \ sizeof(struct bss_bcn_content_tlv) + \ 4 + MT_TXD_SIZE + \ - sizeof(struct bss_bcn_cntdwn_tlv) + \ + sizeof(struct bss_bcn_cntdwn_tlv) * 2 + \ sizeof(struct bss_bcn_mbss_tlv)) #define MT7996_MAX_BSS_OFFLOAD_SIZE 2048 #define MT7996_MAX_BEACON_SIZE (MT7996_MAX_BSS_OFFLOAD_SIZE - \ From 151a6cf0d12f5d333b93b634dbe5834ea0b77ce7 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 12:47:53 +0000 Subject: [PATCH 0791/1433] wifi: mt76: mt7996: don't leak MLD group index on remap alloc failure mt7996_change_vif_links() sets the mld_idx_mask group bit before allocating the remap index. If the remap allocation fails it jumped to the exit without clearing that bit, permanently consuming one of the 16 MLD group slots. Release the group bit on the error path. Fixes: 4fb3b4e7d1ca ("wifi: mt76: mt7996: fix MLD group index assignment") Link: https://patch.msgid.link/20260724124813.3961474-9-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 2487b00c3790..57afe4e81666 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -2458,6 +2458,7 @@ mt7996_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, idx = get_free_idx(dev->mld_remap_idx_mask, 0, 15) - 1; if (idx < 0) { + dev->mld_idx_mask &= ~BIT_ULL(mvif->mld_group_idx); ret = -ENOSPC; goto out; } From e995d3dccc9d980af13a60ad37409ed1561f9eeb Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 12:47:54 +0000 Subject: [PATCH 0792/1433] wifi: mt76: cancel reset and rc work on device unregister Both drivers cancelled dump_work on unregister but left reset_work and rc_work to be flushed only by destroy_workqueue() in mt76_free_device(), which runs after the hw is unregistered and the hardware stopped. A reset_work that fires in that window calls ieee80211_restart_hw() and re-arms mac_work on an unregistered hw, and rc_work touches station state being torn down. Cancel both up front, alongside dump_work. Fixes: e57b7901469f ("mt76: add mac80211 driver for MT7915 PCIe-based chipsets") Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices") Link: https://patch.msgid.link/20260724124813.3961474-10-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/init.c | 2 ++ drivers/net/wireless/mediatek/mt76/mt7996/init.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/init.c b/drivers/net/wireless/mediatek/mt76/mt7915/init.c index 6568d7b6bc0a..0f6346731308 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/init.c @@ -1325,6 +1325,8 @@ int mt7915_register_device(struct mt7915_dev *dev) void mt7915_unregister_device(struct mt7915_dev *dev) { cancel_work_sync(&dev->dump_work); + cancel_work_sync(&dev->reset_work); + cancel_work_sync(&dev->rc_work); mt7915_unregister_ext_phy(dev); mt7915_coredump_unregister(dev); mt7915_unregister_thermal(&dev->phy); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 2ff93bd8b6dc..a344829f5a01 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -1806,6 +1806,8 @@ void mt7996_unregister_device(struct mt7996_dev *dev) { cancel_work_sync(&dev->dump_work); cancel_work_sync(&dev->wed_rro.work); + cancel_work_sync(&dev->reset_work); + cancel_work_sync(&dev->rc_work); mt7996_unregister_phy(mt7996_phy3(dev)); mt7996_unregister_phy(mt7996_phy2(dev)); mt7996_unregister_thermal(&dev->phy); From 4238535e85d41abe6fad545bbcdb3d91d191637f Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 12:47:55 +0000 Subject: [PATCH 0793/1433] wifi: mt76: report data NSS for STBC frames in RX rate decode The RX rate decoder set status->nss straight from the PRXV NSTS field, which for STBC frames is twice the data spatial-stream count. cfg80211 then reported a doubled RX bitrate in station dumps and radiotap. Halve nss for STBC, matching the TX status path. Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices") Fixes: d832f5e73815 ("mt76: connac: move mt76_connac2_mac_fill_rx_rate in connac module") Link: https://patch.msgid.link/20260724124813.3961474-11-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c | 4 ++++ drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c index cbf6a987cf90..8a8d608dd56e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c @@ -1069,6 +1069,10 @@ int mt76_connac2_mac_fill_rx_rate(struct mt76_dev *dev, bw = FIELD_GET(MT_CRXV_FRAME_MODE, v2); } + /* the hardware reports NSTS; report the data NSS for STBC frames */ + if (stbc && nss > 1) + nss >>= 1; + switch (*mode) { case MT_PHY_TYPE_CCK: cck = true; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 866752f2fa24..4449dde33f4e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -294,6 +294,10 @@ mt7996_mac_fill_rx_rate(struct mt7996_dev *dev, dcm = FIELD_GET(MT_PRXV_DCM, v2); bw = FIELD_GET(MT_PRXV_FRAME_MODE, v2); + /* the hardware reports NSTS; report the data NSS for STBC frames */ + if (stbc && nss > 1) + nss >>= 1; + switch (*mode) { case MT_PHY_TYPE_CCK: cck = true; From 04280d0a56be4264720e7b205daaa332c715e5ec Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 12:47:56 +0000 Subject: [PATCH 0794/1433] wifi: mt76: mt7915: use little-endian for bss_info_ra wire fields train_up_high_thres, train_up_rule_rssi and low_traffic_thres were declared as host-native short in a firmware-facing TLV and assigned host-order constants, so on a big-endian host the firmware received byte-swapped rate-adaptation thresholds. Declare them __le16 and convert with cpu_to_le16(). Fixes: e57b7901469f ("mt76: add mac80211 driver for MT7915 PCIe-based chipsets") Link: https://patch.msgid.link/20260724124813.3961474-12-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/mcu.c | 6 +++--- drivers/net/wireless/mediatek/mt76/mt7915/mcu.h | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c index 8e0616504f4d..75eb6d261033 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c @@ -576,9 +576,9 @@ mt7915_mcu_bss_ra_tlv(struct sk_buff *skb, struct ieee80211_vif *vif, ra->rx_streams = max_nss; ra->algo = 4; ra->train_up_rule = 2; - ra->train_up_high_thres = 110; - ra->train_up_rule_rssi = -70; - ra->low_traffic_thres = 2; + ra->train_up_high_thres = cpu_to_le16(110); + ra->train_up_rule_rssi = cpu_to_le16(-70); + ra->low_traffic_thres = cpu_to_le16(2); ra->phy_cap = cpu_to_le32(0xfdf); ra->interval = cpu_to_le32(500); ra->fast_interval = cpu_to_le32(100); diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h index 22f73a5ed425..7c472062a90e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h @@ -318,9 +318,9 @@ struct bss_info_ra { u8 antenna_idx; u8 train_up_rule; u8 rsv[3]; - unsigned short train_up_high_thres; - short train_up_rule_rssi; - unsigned short low_traffic_thres; + __le16 train_up_high_thres; + __le16 train_up_rule_rssi; + __le16 low_traffic_thres; __le16 max_phyrate; __le32 phy_cap; __le32 interval; From dbca5c4d29826cecd3185fb1ae2746205ab55127 Mon Sep 17 00:00:00 2001 From: StanleyYP Wang Date: Fri, 24 Jul 2026 12:47:57 +0000 Subject: [PATCH 0795/1433] wifi: mt76: mt7996: add missing rdd_idx check when enabling background radar Add the missing rdd idx check (< 0) in mt7996_mcu_rdd_background_enable(). mt7996_get_rdd_idx() returns -1 for phys without 5 GHz support, and the negative index was passed to the RDD MCU command unchecked. Fixes: 1529e335f93d ("wifi: mt76: mt7996: rework radar HWRDD idx") Signed-off-by: StanleyYP Wang Link: https://patch.msgid.link/20260724124813.3961474-13-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 289c8390b5fa..645a8b480871 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -4023,6 +4023,9 @@ int mt7996_mcu_rdd_background_enable(struct mt7996_phy *phy, struct mt7996_dev *dev = phy->dev; int err, region, rdd_idx = mt7996_get_rdd_idx(phy, true); + if (rdd_idx < 0) + return -EINVAL; + if (!chandef) { /* disable offchain */ err = mt7996_mcu_rdd_cmd(dev, RDD_STOP, rdd_idx, 0); if (err) From 1df54335590bb025c3bd706a9ba9c6e73a1d3000 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 12:47:58 +0000 Subject: [PATCH 0796/1433] wifi: mt76: only consume the WO drop bit on WED v2 devices The RX path is handled by the WO MCU only on WED v2 hardware. On WED v3 the same buf1 bit does not carry drop information, so evaluating it there causes spurious RX drops. Fixes: e4d2b8bcac11 ("wifi: mt76: drop the incorrect scatter and gather frame") Link: https://patch.msgid.link/20260724124813.3961474-14-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index bd8ee5260acc..f695a1459606 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -547,8 +547,13 @@ mt76_dma_get_buf(struct mt76_dev *dev, struct mt76_queue *q, int idx, t->ptr = NULL; mt76_put_rxwi(dev, t); - if (drop) +#ifdef CONFIG_NET_MEDIATEK_SOC_WED + /* the WO MCU owns the RX path only on WED v2, on newer + * versions this buf1 bit carries no drop information + */ + if (drop && dev->mmio.wed.version == 2) *drop |= !!(buf1 & MT_DMA_CTL_WO_DROP); +#endif } else { dma_sync_single_for_cpu(dev->dma_dev, e->dma_addr[0], SKB_WITH_OVERHEAD(q->buf_size), From 043e042666126064a833e45e31ef50ede6f7d9cf Mon Sep 17 00:00:00 2001 From: Peter Chiu Date: Fri, 24 Jul 2026 12:47:59 +0000 Subject: [PATCH 0797/1433] wifi: mt76: mt7996: add mcu command to set bssid mapping address When receiving a 4 address non-AMSDU packet, there is no bssid in the address fields, which breaks powersave handling for 4-address peers. Set the mcu command to use A1 as bssid when receiving 4 address non-AMSDU packets on mt7992 and mt7990. Also skip mt7996_mac_init_band() for invalid bands, so the command is only sent for bands that actually exist on the device. Signed-off-by: Peter Chiu Link: https://patch.msgid.link/20260724124813.3961474-15-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/init.c | 6 +++++ .../net/wireless/mediatek/mt76/mt7996/mcu.c | 26 +++++++++++++++++++ .../net/wireless/mediatek/mt76/mt7996/mcu.h | 1 + .../wireless/mediatek/mt76/mt7996/mt7996.h | 1 + 4 files changed, 34 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index a344829f5a01..ee1f7a9f4851 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -562,6 +562,9 @@ mt7996_mac_init_band(struct mt7996_dev *dev, u8 band) { u32 mask, set; + if (!mt7996_band_valid(dev, band)) + return; + /* clear estimated value of EIFS for Rx duration & OBSS time */ mt76_wr(dev, MT_WF_RMAC_RSVD0(band), MT_WF_RMAC_RSVD0_EIFS_CLR); @@ -593,6 +596,9 @@ mt7996_mac_init_band(struct mt7996_dev *dev, u8 band) * MT_AGG_ACR_PPDU_TXS2H (PPDU format) even though ACR bit is set. */ mt76_set(dev, MT_AGG_ACR4(band), MT_AGG_ACR_PPDU_TXS2H); + + if (!is_mt7996(&dev->mt76)) + mt7996_mcu_set_bssid_mapping_addr(&dev->mt76, band); } static void mt7996_mac_init_basic_rates(struct mt7996_dev *dev) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 645a8b480871..6a775e682f74 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -5049,6 +5049,32 @@ int mt7996_mcu_twt_agrt_update(struct mt7996_dev *dev, &req, sizeof(req), true); } +int mt7996_mcu_set_bssid_mapping_addr(struct mt76_dev *dev, u8 band_idx) +{ + enum { + BSSID_MAPPING_ADDR1, + BSSID_MAPPING_ADDR2, + BSSID_MAPPING_ADDR3, + }; + struct { + u8 band_idx; + u8 _rsv1[3]; + + __le16 tag; + __le16 len; + u8 addr; + u8 _rsv2[3]; + } __packed req = { + .band_idx = band_idx, + .tag = cpu_to_le16(UNI_BAND_CONFIG_BSSID_MAPPING_ADDR), + .len = cpu_to_le16(sizeof(req) - 4), + .addr = BSSID_MAPPING_ADDR1, + }; + + return mt76_mcu_send_msg(dev, MCU_WM_UNI_CMD(BAND_CONFIG), + &req, sizeof(req), true); +} + int mt7996_mcu_set_rts_thresh(struct mt7996_phy *phy, u32 val) { struct { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index c673e986ecb5..b53a9e71c281 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -927,6 +927,7 @@ enum { UNI_BAND_CONFIG_RADIO_ENABLE, UNI_BAND_CONFIG_RTS_THRESHOLD = 0x08, UNI_BAND_CONFIG_MAC_ENABLE_CTRL = 0x0c, + UNI_BAND_CONFIG_BSSID_MAPPING_ADDR = 0x12, }; enum { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 60397a1b1a2b..dcacc7d06e85 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -772,6 +772,7 @@ int mt7996_mcu_set_protection(struct mt7996_phy *phy, struct mt7996_vif_link *li u8 ht_mode, bool use_cts_prot); int mt7996_mcu_set_timing(struct mt7996_phy *phy, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link_conf); +int mt7996_mcu_set_bssid_mapping_addr(struct mt76_dev *dev, u8 band_idx); int mt7996_mcu_get_chan_mib_info(struct mt7996_phy *phy, bool chan_switch); int mt7996_mcu_get_temperature(struct mt7996_phy *phy); int mt7996_mcu_set_thermal_throttling(struct mt7996_phy *phy, u8 state); From e53932f0e5de19bff8740454ec6952df343d45bb Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Mon, 27 Jul 2026 21:41:29 +0200 Subject: [PATCH 0798/1433] netfilter: conncount: normalize tuple and zone on successful ct lookup When get_ct_or_tuple_from_skb() falls back to looking for a connection via nf_conntrack_find_get(), a successful lookup sets ct but leaves tuple and zone unupdated. If the packet belongs to a reply flow, tuple will remain in the reply direction. As conncount relies on the original direction tuple to count the connections consistenly, passing an unnormalized reply tuple could lead to problems. Fix this by making sure that tuple and zone are normalized. Suggested-by: Florian Westphal Signed-off-by: Fernando Fernandez Mancera Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_conncount.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/netfilter/nf_conncount.c b/net/netfilter/nf_conncount.c index e9ea6d9466e7..85487f92af50 100644 --- a/net/netfilter/nf_conncount.c +++ b/net/netfilter/nf_conncount.c @@ -158,6 +158,8 @@ static bool get_ct_or_tuple_from_skb(struct net *net, return true; found_ct = nf_ct_tuplehash_to_ctrack(h); + *tuple = found_ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple; + *zone = nf_ct_zone(found_ct); *refcounted = true; *ct = found_ct; From f19fd12143db730163ee5aa347bdc246e69f797e Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 28 Jul 2026 18:30:30 +0200 Subject: [PATCH 0799/1433] netfilter: flowtable: consolidate net_device field in nft_forward_info struct info->indev and info->outdev refer to the same device, a single info->dev field is sufficient. While at it, remove unused router parameter from the flowtable path discovery function. Acked-by: Lorenzo Bianconi Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_flow_table_path.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c index 98c03b487f52..261bb44d08eb 100644 --- a/net/netfilter/nf_flow_table_path.c +++ b/net/netfilter/nf_flow_table_path.c @@ -42,8 +42,7 @@ static bool nft_is_valid_ether_device(const struct net_device *dev) return true; } -static int nft_dev_fill_forward_path(const struct nf_flow_route *route, - const struct dst_entry *dst_cache, +static int nft_dev_fill_forward_path(const struct dst_entry *dst_cache, const struct nf_conn *ct, enum ip_conntrack_dir dir, u8 *ha, struct net_device_path_stack *stack) @@ -76,8 +75,7 @@ static int nft_dev_fill_forward_path(const struct nf_flow_route *route, } struct nft_forward_info { - const struct net_device *indev; - const struct net_device *outdev; + const struct net_device *dev; struct id { __u16 id; __be16 proto; @@ -109,7 +107,7 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack, case DEV_PATH_VLAN: case DEV_PATH_PPPOE: case DEV_PATH_TUN: - info->indev = path->dev; + info->dev = path->dev; if (is_zero_ether_addr(info->h_source)) memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN); @@ -179,10 +177,9 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack, return -1; } } - info->outdev = info->indev; if (nf_flowtable_hw_offload(flowtable) && - nft_is_valid_ether_device(info->indev)) + nft_is_valid_ether_device(info->dev)) info->xmit_type = FLOW_OFFLOAD_XMIT_DIRECT; return 0; @@ -255,17 +252,16 @@ static int nft_dev_forward_path(const struct nft_pktinfo *pkt, unsigned char ha[ETH_ALEN]; int i; - if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, &stack) < 0 || + if (nft_dev_fill_forward_path(dst, ct, dir, ha, &stack) < 0 || nft_dev_path_info(&stack, &info, ha, &ft->data) < 0) return -ENOENT; - if (!nft_flowtable_find_dev(info.indev, ft)) + if (!nft_flowtable_find_dev(info.dev, ft)) return -ENOENT; - if (info.outdev) - route->tuple[dir].out.ifindex = info.outdev->ifindex; + route->tuple[!dir].in.ifindex = info.dev->ifindex; + route->tuple[dir].out.ifindex = info.dev->ifindex; - route->tuple[!dir].in.ifindex = info.indev->ifindex; for (i = 0; i < info.num_encaps; i++) { route->tuple[!dir].in.encap[i].id = info.encap[i].id; route->tuple[!dir].in.encap[i].proto = info.encap[i].proto; From df1705f289ba27ef951a87f20d41a109e7d3f40c Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 28 Jul 2026 18:30:37 +0200 Subject: [PATCH 0800/1433] netfilter: flowtable: consolidate flowtable device check Check that device belongs to the flowtable right after the flowtable discovery path. This is a preparation patch to obtain the dst entry from the .fill_forward_path in tunnels. No functional changes are intended. Acked-by: Lorenzo Bianconi Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_flow_table_path.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c index 261bb44d08eb..8f04a4487897 100644 --- a/net/netfilter/nf_flow_table_path.c +++ b/net/netfilter/nf_flow_table_path.c @@ -90,9 +90,12 @@ struct nft_forward_info { enum flow_offload_xmit_type xmit_type; }; +static bool nft_flowtable_find_dev(const struct net_device *dev, + struct nft_flowtable *ft); + static int nft_dev_path_info(const struct net_device_path_stack *stack, struct nft_forward_info *info, - unsigned char *ha, struct nf_flowtable *flowtable) + unsigned char *ha, struct nft_flowtable *ft) { const struct net_device_path *path; int i; @@ -178,10 +181,13 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack, } } - if (nf_flowtable_hw_offload(flowtable) && + if (nf_flowtable_hw_offload(&ft->data) && nft_is_valid_ether_device(info->dev)) info->xmit_type = FLOW_OFFLOAD_XMIT_DIRECT; + if (!nft_flowtable_find_dev(info->dev, ft)) + return -1; + return 0; } @@ -253,10 +259,7 @@ static int nft_dev_forward_path(const struct nft_pktinfo *pkt, int i; if (nft_dev_fill_forward_path(dst, ct, dir, ha, &stack) < 0 || - nft_dev_path_info(&stack, &info, ha, &ft->data) < 0) - return -ENOENT; - - if (!nft_flowtable_find_dev(info.dev, ft)) + nft_dev_path_info(&stack, &info, ha, ft) < 0) return -ENOENT; route->tuple[!dir].in.ifindex = info.dev->ifindex; From 5be6e044bea648cc0cc083899c72252f7f2227fd Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 28 Jul 2026 18:30:43 +0200 Subject: [PATCH 0801/1433] net: dsa: stop at the user device in .fill_forward_path The flowtable path discovery stops at the DSA user device when setting up the forward path. Let's just report there is no more devices after the DSA user port through the .fill_forward_path interface. No functional changes are intended. Signed-off-by: Pablo Neira Ayuso --- net/dsa/user.c | 3 +-- net/netfilter/nf_flow_table_path.c | 7 ++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/net/dsa/user.c b/net/dsa/user.c index 03c7af6abe18..4065c6ee6fc6 100644 --- a/net/dsa/user.c +++ b/net/dsa/user.c @@ -2547,14 +2547,13 @@ static int dsa_user_fill_forward_path(struct net_device_path_ctx *ctx, struct net_device_path *path) { struct dsa_port *dp = dsa_user_to_port(ctx->dev); - struct net_device *conduit = dsa_port_to_conduit(dp); struct dsa_port *cpu_dp = dp->cpu_dp; path->dev = ctx->dev; path->type = DEV_PATH_DSA; path->dsa.proto = cpu_dp->tag_ops->proto; path->dsa.port = dp->index; - ctx->dev = conduit; + ctx->dev = NULL; return 0; } diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c index 8f04a4487897..004dc75ac357 100644 --- a/net/netfilter/nf_flow_table_path.c +++ b/net/netfilter/nf_flow_table_path.c @@ -114,12 +114,9 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack, if (is_zero_ether_addr(info->h_source)) memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN); - if (path->type == DEV_PATH_ETHERNET) + if (path->type == DEV_PATH_ETHERNET || + path->type == DEV_PATH_DSA) break; - if (path->type == DEV_PATH_DSA) { - i = stack->num_paths; - break; - } /* DEV_PATH_VLAN, DEV_PATH_PPPOE and DEV_PATH_TUN */ if (path->type == DEV_PATH_TUN) { From 5deda60c56eeeab25beb10cf4d48e07587076b11 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 28 Jul 2026 18:30:46 +0200 Subject: [PATCH 0802/1433] net: do not advance stack index from dev_fwd_path() Update stack index from dev_fill_forward_path() instead, once the forward path slot has been populated. Acked-by: Lorenzo Bianconi Signed-off-by: Pablo Neira Ayuso --- net/core/dev.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/net/core/dev.c b/net/core/dev.c index c1c1be1a6962..429a55fff667 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -742,12 +742,10 @@ EXPORT_SYMBOL_GPL(dev_fill_metadata_dst); static struct net_device_path *dev_fwd_path(struct net_device_path_stack *stack) { - int k = stack->num_paths++; - - if (k >= NET_DEVICE_PATH_STACK_MAX) + if (stack->num_paths + 1 > NET_DEVICE_PATH_STACK_MAX) return NULL; - return &stack->path[k]; + return &stack->path[stack->num_paths]; } int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr, @@ -773,6 +771,7 @@ int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr, if (ret < 0) return -1; + stack->num_paths++; if (WARN_ON_ONCE(last_dev == ctx.dev)) return -1; } @@ -785,6 +784,7 @@ int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr, return -1; path->type = DEV_PATH_ETHERNET; path->dev = ctx.dev; + stack->num_paths++; return ret; } From 0ad8404e776698de4dac5cc0df3be68d344e741c Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 28 Jul 2026 18:30:48 +0200 Subject: [PATCH 0803/1433] net: pass dst via net_device_path in dev_fill_forward_path() Add dst_entry to tunnel device path, this will allow us to remove a duplicated route lookup. This is a preparation patch to retrieve the tunnel route directly from the .fill_forward_path. This new dst_entry in the tunnel will be used by a follow up patch. Since dst_release() works fine on NULL interface, this is still noop until the flowtable starts using this. Add a new dev_fill_forward_path_release() function to drop the refcount on the tunnel device route and use it in case of error out. Export it so to drop the refcount on the tunnel route at a later stage. Adjust existing drivers that recycle dev_fill_forward_path() to call dev_fill_forward_path_release() for safety reasons. Acked-by: Lorenzo Bianconi Signed-off-by: Pablo Neira Ayuso --- drivers/net/ethernet/airoha/airoha_ppe.c | 10 ++++-- .../net/ethernet/mediatek/mtk_ppe_offload.c | 10 ++++-- include/linux/netdevice.h | 2 ++ net/core/dev.c | 36 ++++++++++++++++--- 4 files changed, 47 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c index 33ddf0d07855..a03af9750573 100644 --- a/drivers/net/ethernet/airoha/airoha_ppe.c +++ b/drivers/net/ethernet/airoha/airoha_ppe.c @@ -296,14 +296,18 @@ static int airoha_ppe_get_wdma_info(struct net_device *dev, const u8 *addr, return err; path = &stack.path[stack.num_paths - 1]; - if (path->type != DEV_PATH_MTK_WDMA) - return -EINVAL; + if (path->type != DEV_PATH_MTK_WDMA) { + err = -EINVAL; + goto err_out; + } info->idx = path->mtk_wdma.wdma_idx; info->bss = path->mtk_wdma.bss; info->wcid = path->mtk_wdma.wcid; +err_out: + dev_fill_forward_path_release(&stack); - return 0; + return err; } static int airoha_get_dsa_port(struct net_device **dev) diff --git a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c index cc8c4ef8038f..771d9118f94a 100644 --- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c +++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c @@ -108,16 +108,20 @@ mtk_flow_get_wdma_info(struct net_device *dev, const u8 *addr, struct mtk_wdma_i return err; path = &stack.path[stack.num_paths - 1]; - if (path->type != DEV_PATH_MTK_WDMA) - return -1; + if (path->type != DEV_PATH_MTK_WDMA) { + err = -EINVAL; + goto err_out; + } info->wdma_idx = path->mtk_wdma.wdma_idx; info->queue = path->mtk_wdma.queue; info->bss = path->mtk_wdma.bss; info->wcid = path->mtk_wdma.wcid; info->amsdu = path->mtk_wdma.amsdu; +err_out: + dev_fill_forward_path_release(&stack); - return 0; + return err; } diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 8db25b79573e..62cfad7e6b79 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -892,6 +892,7 @@ struct net_device_path { u8 h_dest[ETH_ALEN]; } encap; struct { + struct dst_entry *dst; union { struct in_addr src_v4; struct in6_addr src_v6; @@ -3427,6 +3428,7 @@ int dev_get_iflink(const struct net_device *dev); int dev_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb); int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr, struct net_device_path_stack *stack); +void dev_fill_forward_path_release(struct net_device_path_stack *stack); struct net_device *dev_get_by_name(struct net *net, const char *name); struct net_device *dev_get_by_name_rcu(struct net *net, const char *name); struct net_device *__dev_get_by_name(struct net *net, const char *name); diff --git a/net/core/dev.c b/net/core/dev.c index 429a55fff667..e50ed677de72 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -748,6 +748,27 @@ static struct net_device_path *dev_fwd_path(struct net_device_path_stack *stack) return &stack->path[stack->num_paths]; } +void dev_fill_forward_path_release(struct net_device_path_stack *stack) +{ + struct net_device_path *path; + int k; + + if (stack->num_paths == 0) + return; + + for (k = stack->num_paths - 1; k >= 0; k--) { + path = &stack->path[k]; + switch (path->type) { + case DEV_PATH_TUN: + dst_release(path->tun.dst); + break; + default: + break; + } + } +} +EXPORT_SYMBOL_GPL(dev_fill_forward_path_release); + int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr, struct net_device_path_stack *stack) { @@ -764,16 +785,16 @@ int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr, last_dev = ctx.dev; path = dev_fwd_path(stack); if (!path) - return -1; + goto err_out; memset(path, 0, sizeof(struct net_device_path)); ret = ctx.dev->netdev_ops->ndo_fill_forward_path(&ctx, path); if (ret < 0) - return -1; + goto err_out; stack->num_paths++; if (WARN_ON_ONCE(last_dev == ctx.dev)) - return -1; + goto err_out; } if (!ctx.dev) @@ -781,12 +802,17 @@ int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr, path = dev_fwd_path(stack); if (!path) - return -1; + goto err_out; + path->type = DEV_PATH_ETHERNET; path->dev = ctx.dev; stack->num_paths++; - return ret; + return 0; +err_out: + dev_fill_forward_path_release(stack); + + return -1; } EXPORT_SYMBOL_GPL(dev_fill_forward_path); From 806273fcaffb82fdea93b12ee281c3ed4b0b8e76 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 28 Jul 2026 18:30:55 +0200 Subject: [PATCH 0804/1433] netfilter: flowtable: release tunnel route on error when building forward path nft_flow_tunnel_update_route() can lazy fail, leaving an incomplete forward path set ip. The route lookup also happens twice, once from dev_fill_forward_path() and again in this aforementioned function. Update ipip and ip6ip6 not to release the dst_entry and pass it on via the tunnel forward path information. In case of failure when setting up the forwarding path, release the tunnel dst that was provided via dev_fill_forward_path(). Acked-by: Lorenzo Bianconi Signed-off-by: Pablo Neira Ayuso --- net/ipv4/ipip.c | 2 +- net/ipv6/ip6_tunnel.c | 4 +- net/netfilter/nf_flow_table_path.c | 65 ++++++++---------------------- 3 files changed, 21 insertions(+), 50 deletions(-) diff --git a/net/ipv4/ipip.c b/net/ipv4/ipip.c index 0831f6b81717..fb7d96f99b06 100644 --- a/net/ipv4/ipip.c +++ b/net/ipv4/ipip.c @@ -376,10 +376,10 @@ static int ipip_fill_forward_path(struct net_device_path_ctx *ctx, path->tun.src_v4.s_addr = tiph->saddr; path->tun.dst_v4.s_addr = tiph->daddr; path->tun.l3_proto = IPPROTO_IPIP; + path->tun.dst = &rt->dst; path->dev = ctx->dev; ctx->dev = rt->dst.dev; - ip_rt_put(rt); return 0; } diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index 97c3f61d627b..d80020bc2620 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -1870,12 +1870,14 @@ static int ip6_tnl_fill_forward_path(struct net_device_path_ctx *ctx, path->tun.src_v6 = fl6.saddr; path->tun.dst_v6 = fl6.daddr; path->tun.l3_proto = IPPROTO_IPV6; + path->tun.dst = dst; path->dev = ctx->dev; ctx->dev = dst->dev; } err = dst->error; - dst_release(dst); + if (err) + dst_release(dst); return err; } diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c index 004dc75ac357..56219b02e122 100644 --- a/net/netfilter/nf_flow_table_path.c +++ b/net/netfilter/nf_flow_table_path.c @@ -82,6 +82,7 @@ struct nft_forward_info { } encap[NF_FLOW_TABLE_ENCAP_MAX]; u8 num_encaps; struct flow_offload_tunnel tun; + struct dst_entry *tun_dst; u8 num_tuns; u8 ingress_vlans; u8 h_source[ETH_ALEN]; @@ -93,7 +94,7 @@ struct nft_forward_info { static bool nft_flowtable_find_dev(const struct net_device *dev, struct nft_flowtable *ft); -static int nft_dev_path_info(const struct net_device_path_stack *stack, +static int nft_dev_path_info(struct net_device_path_stack *stack, struct nft_forward_info *info, unsigned char *ha, struct nft_flowtable *ft) { @@ -121,15 +122,16 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack, /* DEV_PATH_VLAN, DEV_PATH_PPPOE and DEV_PATH_TUN */ if (path->type == DEV_PATH_TUN) { if (info->num_tuns) - return -1; + goto err_out; info->tun.src_v6 = path->tun.src_v6; info->tun.dst_v6 = path->tun.dst_v6; info->tun.l3_proto = path->tun.l3_proto; + info->tun_dst = path->tun.dst; info->num_tuns++; } else { if (info->num_encaps >= NF_FLOW_TABLE_ENCAP_MAX) - return -1; + goto err_out; info->encap[info->num_encaps].id = path->encap.id; @@ -150,13 +152,13 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack, switch (path->bridge.vlan_mode) { case DEV_PATH_BR_VLAN_UNTAG_HW: if (info->num_encaps == 0) - return -1; + goto err_out; info->ingress_vlans |= BIT(info->num_encaps - 1); break; case DEV_PATH_BR_VLAN_TAG: if (info->num_encaps >= NF_FLOW_TABLE_ENCAP_MAX) - return -1; + goto err_out; info->encap[info->num_encaps].id = path->bridge.vlan_id; info->encap[info->num_encaps].proto = path->bridge.vlan_proto; @@ -164,7 +166,7 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack, break; case DEV_PATH_BR_VLAN_UNTAG: if (info->num_encaps == 0) - return -1; + goto err_out; info->num_encaps--; break; @@ -174,7 +176,7 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack, info->xmit_type = FLOW_OFFLOAD_XMIT_DIRECT; break; default: - return -1; + goto err_out; } } @@ -183,9 +185,13 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack, info->xmit_type = FLOW_OFFLOAD_XMIT_DIRECT; if (!nft_flowtable_find_dev(info->dev, ft)) - return -1; + goto err_out; return 0; +err_out: + dev_fill_forward_path_release(stack); + + return -1; } static bool nft_flowtable_find_dev(const struct net_device *dev, @@ -205,44 +211,6 @@ static bool nft_flowtable_find_dev(const struct net_device *dev, return found; } -static int nft_flow_tunnel_update_route(const struct nft_pktinfo *pkt, - struct flow_offload_tunnel *tun, - struct nf_flow_route *route, - enum ip_conntrack_dir dir) -{ - struct dst_entry *cur_dst = route->tuple[dir].dst; - struct dst_entry *tun_dst = NULL; - struct flowi fl = {}; - - switch (nft_pf(pkt)) { - case NFPROTO_IPV4: - fl.u.ip4.daddr = tun->dst_v4.s_addr; - fl.u.ip4.saddr = tun->src_v4.s_addr; - fl.u.ip4.flowi4_iif = nft_in(pkt)->ifindex; - fl.u.ip4.flowi4_dscp = ip4h_dscp(ip_hdr(pkt->skb)); - fl.u.ip4.flowi4_mark = pkt->skb->mark; - fl.u.ip4.flowi4_flags = FLOWI_FLAG_ANYSRC; - break; - case NFPROTO_IPV6: - fl.u.ip6.daddr = tun->dst_v6; - fl.u.ip6.saddr = tun->src_v6; - fl.u.ip6.flowi6_iif = nft_in(pkt)->ifindex; - fl.u.ip6.flowlabel = ip6_flowinfo(ipv6_hdr(pkt->skb)); - fl.u.ip6.flowi6_mark = pkt->skb->mark; - fl.u.ip6.flowi6_flags = FLOWI_FLAG_ANYSRC; - break; - } - - nf_route(nft_net(pkt), &tun_dst, &fl, false, nft_pf(pkt)); - if (!tun_dst) - return -ENOENT; - - route->tuple[dir].dst = tun_dst; - dst_release(cur_dst); - - return 0; -} - static int nft_dev_forward_path(const struct nft_pktinfo *pkt, struct nf_flow_route *route, const struct nf_conn *ct, @@ -267,12 +235,13 @@ static int nft_dev_forward_path(const struct nft_pktinfo *pkt, route->tuple[!dir].in.encap[i].proto = info.encap[i].proto; } - if (info.num_tuns && - !nft_flow_tunnel_update_route(pkt, &info.tun, route, dir)) { + if (info.num_tuns) { route->tuple[!dir].in.tun.src_v6 = info.tun.dst_v6; route->tuple[!dir].in.tun.dst_v6 = info.tun.src_v6; route->tuple[!dir].in.tun.l3_proto = info.tun.l3_proto; route->tuple[!dir].in.num_tuns = info.num_tuns; + dst_release(route->tuple[dir].dst); + route->tuple[dir].dst = info.tun_dst; } route->tuple[!dir].in.num_encaps = info.num_encaps; From 689db98e535bf8efb5bc9c36f4fd3de3bd715eb6 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 28 Jul 2026 10:40:06 +0200 Subject: [PATCH 0805/1433] netfilter: nf_tables: call skb_valid_dst() before skb_dst() When fetching the dst_entry from the skb, check if it valid, ie. this is not a template dst, for extensions that can be used from the netdev ingress and egress chains. Signed-off-by: Pablo Neira Ayuso --- net/ipv4/netfilter/nf_reject_ipv4.c | 6 ++++-- net/ipv6/netfilter/nf_reject_ipv6.c | 8 ++++++-- net/netfilter/nft_meta.c | 6 ++++-- net/netfilter/nft_rt.c | 6 ++++-- net/netfilter/nft_xfrm.c | 9 ++++++++- 5 files changed, 26 insertions(+), 9 deletions(-) diff --git a/net/ipv4/netfilter/nf_reject_ipv4.c b/net/ipv4/netfilter/nf_reject_ipv4.c index 4626dc46808f..59ec465a9df9 100644 --- a/net/ipv4/netfilter/nf_reject_ipv4.c +++ b/net/ipv4/netfilter/nf_reject_ipv4.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -263,6 +264,7 @@ static int nf_reject_fill_skb_dst(struct sk_buff *skb_in) if (!dst) return -1; + skb_dst_drop(skb_in); skb_dst_set(skb_in, dst); return 0; } @@ -279,7 +281,7 @@ void nf_send_reset(struct net *net, struct sock *sk, struct sk_buff *oldskb, if (!oth) return; - if (!skb_dst(oldskb) && nf_reject_fill_skb_dst(oldskb) < 0) + if (!skb_valid_dst(oldskb) && nf_reject_fill_skb_dst(oldskb) < 0) return; if (skb_rtable(oldskb)->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST)) @@ -352,7 +354,7 @@ void nf_send_unreach(struct sk_buff *skb_in, int code, int hook) if (iph->frag_off & htons(IP_OFFSET)) return; - if (!skb_dst(skb_in) && nf_reject_fill_skb_dst(skb_in) < 0) + if (!skb_valid_dst(skb_in) && nf_reject_fill_skb_dst(skb_in) < 0) return; if (skb_csum_unnecessary(skb_in) || diff --git a/net/ipv6/netfilter/nf_reject_ipv6.c b/net/ipv6/netfilter/nf_reject_ipv6.c index ef5b7e85cffa..07cdaa10da0d 100644 --- a/net/ipv6/netfilter/nf_reject_ipv6.c +++ b/net/ipv6/netfilter/nf_reject_ipv6.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -304,6 +305,7 @@ static int nf_reject6_fill_skb_dst(struct sk_buff *skb_in) if (!dst) return -1; + skb_dst_drop(skb_in); skb_dst_set(skb_in, dst); return 0; } @@ -336,10 +338,12 @@ void nf_send_reset6(struct net *net, struct sock *sk, struct sk_buff *oldskb, fl6.fl6_sport = otcph->dest; fl6.fl6_dport = otcph->source; - if (!skb_dst(oldskb)) { + if (!skb_valid_dst(oldskb)) { nf_ip6_route(net, &dst, flowi6_to_flowi(&fl6), false); if (!dst) return; + + skb_dst_drop(oldskb); skb_dst_set(oldskb, dst); } @@ -440,7 +444,7 @@ void nf_send_unreach6(struct net *net, struct sk_buff *skb_in, if (hooknum == NF_INET_LOCAL_OUT && skb_in->dev == NULL) skb_in->dev = net->loopback_dev; - if (!skb_dst(skb_in) && nf_reject6_fill_skb_dst(skb_in) < 0) + if (!skb_valid_dst(skb_in) && nf_reject6_fill_skb_dst(skb_in) < 0) return; icmpv6_send(skb_in, ICMPV6_DEST_UNREACH, code, 0); diff --git a/net/netfilter/nft_meta.c b/net/netfilter/nft_meta.c index 0a43e0787a68..01cfbaa36525 100644 --- a/net/netfilter/nft_meta.c +++ b/net/netfilter/nft_meta.c @@ -20,6 +20,7 @@ #include #include #include +#include #include /* for TCP_TIME_WAIT */ #include #include @@ -279,11 +280,12 @@ static bool nft_meta_get_eval_ifname(enum nft_meta_keys key, u32 *dest, static noinline bool nft_meta_get_eval_rtclassid(const struct sk_buff *skb, u32 *dest) { - const struct dst_entry *dst = skb_dst(skb); + const struct dst_entry *dst; - if (!dst) + if (!skb_valid_dst(skb)) return false; + dst = skb_dst(skb); *dest = dst->tclassid; return true; } diff --git a/net/netfilter/nft_rt.c b/net/netfilter/nft_rt.c index aeb0094eafd8..841c863a08db 100644 --- a/net/netfilter/nft_rt.c +++ b/net/netfilter/nft_rt.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -59,10 +60,11 @@ void nft_rt_get_eval(const struct nft_expr *expr, u32 *dest = ®s->data[priv->dreg]; const struct dst_entry *dst; - dst = skb_dst(skb); - if (!dst) + if (!skb_valid_dst(skb)) goto err; + dst = skb_dst(skb); + switch (priv->key) { #ifdef CONFIG_IP_ROUTE_CLASSID case NFT_RT_CLASSID: diff --git a/net/netfilter/nft_xfrm.c b/net/netfilter/nft_xfrm.c index 8cec43064319..c8bba697f993 100644 --- a/net/netfilter/nft_xfrm.c +++ b/net/netfilter/nft_xfrm.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -177,9 +178,15 @@ static void nft_xfrm_get_eval_out(const struct nft_xfrm *priv, struct nft_regs *regs, const struct nft_pktinfo *pkt) { - const struct dst_entry *dst = skb_dst(pkt->skb); + const struct dst_entry *dst; int i; + if (!skb_valid_dst(pkt->skb)) { + regs->verdict.code = NFT_BREAK; + return; + } + + dst = skb_dst(pkt->skb); for (i = 0; dst && dst->xfrm; dst = ((const struct xfrm_dst *)dst)->child, i++) { if (i < priv->spnum) From bf80e6802273a900311b44e47c9b1a59c6d2473c Mon Sep 17 00:00:00 2001 From: Minghao Zhang Date: Wed, 29 Jul 2026 15:46:52 +0000 Subject: [PATCH 0806/1433] netfilter: conntrack: tcp: use UNACK timeout for non-closing RST packets Commit be0502a3f2e9 ("netfilter: conntrack: tcp: only close if RST matches exact sequence") keeps an established conntrack entry in ESTABLISHED when an in-window RST does not match the expected sequence number exactly, so the endpoint can validate the RST with a challenge ACK. The timeout selection nevertheless uses the CLOSE timeout for every RST packet. The bug is that timeout selection is based on the packet type, not on the state transition result: even when RST validation keeps new_state in ESTABLISHED, the timeout is still forced to TCP_CONNTRACK_CLOSE. Linux TCP independently rate limits challenge ACKs per socket. A second non-exact RST can therefore arrive after the first challenge ACK has restored the timeout but before the rate limit expires. The second RST lowers the timeout to 10 seconds again while the endpoint suppresses the second challenge ACK, allowing the conntrack entry to expire while both TCP endpoints remain established. Using the ESTABLISHED timeout for such RSTs would avoid this short expiration window, but it could also retain stale entries for the five-day default because conntrack cannot reliably match the endpoint's exact TCP state. Use the UNACK timeout for RST packets that leave the conntrack entry in TCP_CONNTRACK_ESTABLISHED. Exact-match RSTs and accepted RST packet trains still fall through to timeouts[new_state], which preserves the CLOSE timeout when conntrack accepts the RST as closing the flow. This avoids the aggressive 10-second expiration window for non-exact RSTs while preserving the short timeout for RSTs that conntrack accepts as closing the flow. Suggested-by: Florian Westphal Reported-by: Minghao Zhang Reported-by: Jianjun Chen Signed-off-by: Minghao Zhang Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_conntrack_proto_tcp.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/net/netfilter/nf_conntrack_proto_tcp.c b/net/netfilter/nf_conntrack_proto_tcp.c index ceeed3d7fe52..723e946a78f4 100644 --- a/net/netfilter/nf_conntrack_proto_tcp.c +++ b/net/netfilter/nf_conntrack_proto_tcp.c @@ -1281,8 +1281,9 @@ int nf_conntrack_tcp_packet(struct nf_conn *ct, if (ct->proto.tcp.retrans >= tn->tcp_max_retrans && timeouts[new_state] > timeouts[TCP_CONNTRACK_RETRANS]) timeout = timeouts[TCP_CONNTRACK_RETRANS]; - else if (unlikely(index == TCP_RST_SET)) - timeout = timeouts[TCP_CONNTRACK_CLOSE]; + else if (unlikely(index == TCP_RST_SET && + new_state == TCP_CONNTRACK_ESTABLISHED)) + timeout = timeouts[TCP_CONNTRACK_UNACK]; else if ((ct->proto.tcp.seen[0].flags | ct->proto.tcp.seen[1].flags) & IP_CT_TCP_FLAG_DATA_UNACKNOWLEDGED && timeouts[new_state] > timeouts[TCP_CONNTRACK_UNACK]) From 8ab38bbac02298e3fb03008ed4a0227485c0fd62 Mon Sep 17 00:00:00 2001 From: ZhaoJinming Date: Wed, 29 Jul 2026 10:00:05 +0800 Subject: [PATCH 0807/1433] wifi: ath11k: fix resource leak on error in ext IRQ setup In ath11k_ahb_config_irq(), when a CE request_irq() fails, the function returns the error immediately without freeing the CE IRQs that were successfully registered in previous loop iterations. The probe error path does not call ath11k_ahb_free_irq() either, so the previously registered CE IRQ handlers remain attached to the interrupt lines and are never released. In ath11k_ahb_config_ext_irq(), when an external request_irq() fails, the error is only logged and the loop continues. The function then returns 0 indicating success, leaving the device in a partially configured state where some external IRQs are not registered. This causes enable_irq()/disable_irq()/free_irq() to be called on unregistered IRQs during runtime and remove/shutdown, triggering WARN_ON(!desc->action), and missing interrupt handlers lead to data loss. Additionally, if alloc_netdev_dummy() fails for a later IRQ group, the function returns -ENOMEM without freeing the ext IRQs and napi_ndev that were successfully set up for earlier groups. Fix all three issues: propagate the error up to the caller and unwind all successfully registered IRQs and allocated resources on failure. Also move ab->irq_num[irq_idx] assignment after request_irq() succeeds in the ext IRQ path to match the CE IRQ path and avoid storing a stale IRQ number on failure. Signed-off-by: ZhaoJinming Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260729020005.219253-1-zhaojinming@uniontech.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/ahb.c | 79 +++++++++++++++++++-------- 1 file changed, 55 insertions(+), 24 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/ahb.c b/drivers/net/wireless/ath/ath11k/ahb.c index 1e1dea485760..ec5bf5c9fd79 100644 --- a/drivers/net/wireless/ath/ath11k/ahb.c +++ b/drivers/net/wireless/ath/ath11k/ahb.c @@ -431,36 +431,44 @@ static void ath11k_ahb_init_qmi_ce_config(struct ath11k_base *ab) ab->qmi.service_ins_id = ab->hw_params.qmi_service_ins_id; } -static void ath11k_ahb_free_ext_irq(struct ath11k_base *ab) +static void ath11k_ahb_free_ext_irq_grp(struct ath11k_base *ab, + struct ath11k_ext_irq_grp *irq_grp) { - int i, j; + int j; - for (i = 0; i < ATH11K_EXT_IRQ_GRP_NUM_MAX; i++) { - struct ath11k_ext_irq_grp *irq_grp = &ab->ext_irq_grp[i]; + for (j = 0; j < irq_grp->num_irq; j++) + free_irq(ab->irq_num[irq_grp->irqs[j]], irq_grp); - for (j = 0; j < irq_grp->num_irq; j++) - free_irq(ab->irq_num[irq_grp->irqs[j]], irq_grp); - - netif_napi_del(&irq_grp->napi); - free_netdev(irq_grp->napi_ndev); - } + netif_napi_del(&irq_grp->napi); + free_netdev(irq_grp->napi_ndev); } -static void ath11k_ahb_free_irq(struct ath11k_base *ab) +static void ath11k_ahb_free_ext_irq(struct ath11k_base *ab) { - int irq_idx; int i; - if (ab->hw_params.hybrid_bus_type) - return ath11k_pcic_free_irq(ab); + for (i = 0; i < ATH11K_EXT_IRQ_GRP_NUM_MAX; i++) + ath11k_ahb_free_ext_irq_grp(ab, &ab->ext_irq_grp[i]); +} - for (i = 0; i < ab->hw_params.ce_count; i++) { +static void ath11k_ahb_free_ce_irqs(struct ath11k_base *ab, int max_idx) +{ + int irq_idx, i; + + for (i = 0; i < max_idx; i++) { if (ath11k_ce_get_attr_flags(ab, i) & CE_ATTR_DIS_INTR) continue; irq_idx = ATH11K_IRQ_CE0_OFFSET + i; free_irq(ab->irq_num[irq_idx], &ab->ce.ce_pipe[i]); } +} +static void ath11k_ahb_free_irq(struct ath11k_base *ab) +{ + if (ab->hw_params.hybrid_bus_type) + return ath11k_pcic_free_irq(ab); + + ath11k_ahb_free_ce_irqs(ab, ab->hw_params.ce_count); ath11k_ahb_free_ext_irq(ab); } @@ -524,20 +532,25 @@ static irqreturn_t ath11k_ahb_ext_interrupt_handler(int irq, void *arg) static int ath11k_ahb_config_ext_irq(struct ath11k_base *ab) { struct ath11k_hw_params *hw = &ab->hw_params; + struct ath11k_ext_irq_grp *irq_grp; int i, j; int irq; int ret; for (i = 0; i < ATH11K_EXT_IRQ_GRP_NUM_MAX; i++) { - struct ath11k_ext_irq_grp *irq_grp = &ab->ext_irq_grp[i]; u32 num_irq = 0; + irq_grp = &ab->ext_irq_grp[i]; + irq_grp->ab = ab; irq_grp->grp_id = i; irq_grp->napi_ndev = alloc_netdev_dummy(0); - if (!irq_grp->napi_ndev) - return -ENOMEM; + if (!irq_grp->napi_ndev) { + ret = -ENOMEM; + irq_grp->num_irq = 0; + goto err_request_irq; + } netif_napi_add(irq_grp->napi_ndev, &irq_grp->napi, ath11k_ahb_ext_grp_napi_poll); @@ -585,14 +598,11 @@ static int ath11k_ahb_config_ext_irq(struct ath11k_base *ab) } } } - irq_grp->num_irq = num_irq; - - for (j = 0; j < irq_grp->num_irq; j++) { + for (j = 0; j < num_irq; j++) { int irq_idx = irq_grp->irqs[j]; irq = platform_get_irq_byname(ab->pdev, irq_name[irq_idx]); - ab->irq_num[irq_idx] = irq; irq_set_status_flags(irq, IRQ_NOAUTOEN | IRQ_DISABLE_UNLAZY); ret = request_irq(irq, ath11k_ahb_ext_interrupt_handler, IRQF_TRIGGER_RISING, @@ -600,11 +610,24 @@ static int ath11k_ahb_config_ext_irq(struct ath11k_base *ab) if (ret) { ath11k_err(ab, "failed request_irq for %d\n", irq); + irq_grp->num_irq = j; + ath11k_ahb_free_ext_irq_grp(ab, irq_grp); + goto err_request_irq; } + ab->irq_num[irq_idx] = irq; } + + irq_grp->num_irq = num_irq; } return 0; + +err_request_irq: + for (i--; i >= 0; i--) { + irq_grp = &ab->ext_irq_grp[i]; + ath11k_ahb_free_ext_irq_grp(ab, irq_grp); + } + return ret; } static int ath11k_ahb_config_irq(struct ath11k_base *ab) @@ -629,16 +652,24 @@ static int ath11k_ahb_config_irq(struct ath11k_base *ab) ret = request_irq(irq, ath11k_ahb_ce_interrupt_handler, IRQF_TRIGGER_RISING, irq_name[irq_idx], ce_pipe); - if (ret) + if (ret) { + ath11k_err(ab, "failed request_irq for %d\n", irq); + ath11k_ahb_free_ce_irqs(ab, i); return ret; + } ab->irq_num[irq_idx] = irq; } /* Configure external interrupts */ ret = ath11k_ahb_config_ext_irq(ab); + if (ret) { + ath11k_err(ab, "failed to configure ext irq: %d\n", ret); + ath11k_ahb_free_ce_irqs(ab, ab->hw_params.ce_count); + return ret; + } - return ret; + return 0; } static int ath11k_ahb_map_service_to_pipe(struct ath11k_base *ab, u16 service_id, From 0293be2212d319d59589082461abf2a9b626cd1c Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Mon, 27 Jul 2026 16:39:41 -0700 Subject: [PATCH 0808/1433] wifi: ath11k: fix leak in ath11k_service_ready_ext_event() Currently, during ath11k_service_ready_ext_event() processing, svc_rdy_ext.mac_phy_caps can be allocated during TLV parsing. This is a temporary allocation that is freed on the success path, but not on the error path. If parsing succeeds far enough to allocate mac_phy_caps and then fails on a later TLV, the allocation leaks. So free the allocation on the error path. Compile tested only. Fixes: 5b90fc760db5 ("ath11k: fix wmi service ready ext tlv parsing") Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260727-ath11k_service_ready_ext_event-memleak-v1-1-e8373d27bdd1@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/wmi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c index 66547e9ee16c..bbca275a8289 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.c +++ b/drivers/net/wireless/ath/ath11k/wmi.c @@ -5135,6 +5135,7 @@ static int ath11k_service_ready_ext_event(struct ath11k_base *ab, return 0; err: + kfree(svc_rdy_ext.mac_phy_caps); ath11k_wmi_free_dbring_caps(ab); return ret; } From 3bbd05723d15dd06f0560bcd94fbf9a91b5f5613 Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Mon, 13 Jul 2026 23:32:51 +0200 Subject: [PATCH 0809/1433] wifi: ath6kl: clamp assoc request/response lengths before subtracting IE offsets ath6kl_cfg80211_connect_event() subtracts fixed IE offsets from assoc_req_len (-= 4) and assoc_resp_len (-= 6), both u8, with no lower bound. The aggregate check recently added to ath6kl_wmi_connect_event_rx() bounds the declared lengths from above (their sum must fit the received event), but an assoc request/response shorter than its fixed offset still underflows here: the u8 wraps to ~250, and cfg80211_connect_result() / cfg80211_roamed() then treat that wrapped value as the IE length and copy that many bytes out of the small assoc_info buffer to user space via nl80211, disclosing adjacent slab memory. Clamp both lengths to their offsets before subtracting. Found by 0sec (https://0sec.ai) using automated source analysis; the missing lower bound is evident from source. Compile-tested. Fixes: bdcd81707973 ("Add ath6kl cleaned up driver") Cc: stable@vger.kernel.org Assisted-by: 0sec:claude-opus-4-8 Signed-off-by: Doruk Tan Ozturk Link: https://patch.msgid.link/20260713213251.21161-1-doruk@0sec.ai Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath6kl/cfg80211.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/wireless/ath/ath6kl/cfg80211.c b/drivers/net/wireless/ath/ath6kl/cfg80211.c index ecde91159b54..59cf1d0e7f19 100644 --- a/drivers/net/wireless/ath/ath6kl/cfg80211.c +++ b/drivers/net/wireless/ath/ath6kl/cfg80211.c @@ -754,6 +754,11 @@ void ath6kl_cfg80211_connect_event(struct ath6kl_vif *vif, u16 channel, u8 *assoc_resp_ie = assoc_info + beacon_ie_len + assoc_req_len + assoc_resp_ie_offset; + if (assoc_req_len < assoc_req_ie_offset) + assoc_req_len = assoc_req_ie_offset; + if (assoc_resp_len < assoc_resp_ie_offset) + assoc_resp_len = assoc_resp_ie_offset; + assoc_req_len -= assoc_req_ie_offset; assoc_resp_len -= assoc_resp_ie_offset; From 7ed1d2b87f376975c4540cfceb90e1dcc6ed8505 Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Mon, 27 Jul 2026 12:20:43 +0200 Subject: [PATCH 0810/1433] net: dsa: microchip: implement ksz8463_setup() KSZ8463 uses the ksz8_setup() as setup() callback for its DSA operations. Its behavior is quite different than other KSZ8 switches, especially its interrupt scheme. Remove from the ksz8_setup()/ksz8_reset_switch() everything that is ksz8463-related. Create a dedicated ksz8463_setup() and a ksz8463_reset_switch() function. This new ksz8463_setup() is widely inspired from ksz8_setup, it has following differences: - it doesn't configure drive strength (not supported on KSZ8463) - it uses the ksz8463_reset_switch() - it doesn't call ksz8_handle_global_errata() (the handled errata only affects the KSZ87xx variant) - it doesn't configure IRQs. Note that ksz8_setup()'s IRQ initialization doesn't work for the KSZ8463 anyway. Proper support for it comes in upcoming patches. Remove the teardown implementation from the KSZ8463 operations. Since PTP and interrupts aren't setup, the common ksz_teardown() wouldn't do anything anyway. Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260727-ksz-new-ptp-v3-1-caba39e680e3@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/microchip/ksz8.c | 126 ++++++++++++++++++++++++++++--- 1 file changed, 114 insertions(+), 12 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index c4c769028a20..5ed63f425013 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -181,6 +181,14 @@ static int ksz8_pme_pwrite8(struct ksz_device *dev, int port, int offset, u8 dat return ksz8_ind_write8(dev, table, (u8)(offset), data); } +static int ksz8463_reset_switch(struct ksz_device *dev) +{ + ksz_cfg(dev, KSZ8463_REG_SW_RESET, KSZ8463_GLOBAL_SOFTWARE_RESET, true); + ksz_cfg(dev, KSZ8463_REG_SW_RESET, KSZ8463_GLOBAL_SOFTWARE_RESET, + false); + return 0; +} + static int ksz8_reset_switch(struct ksz_device *dev) { if (ksz_is_ksz88x3(dev)) { @@ -189,11 +197,6 @@ static int ksz8_reset_switch(struct ksz_device *dev) KSZ8863_GLOBAL_SOFTWARE_RESET | KSZ8863_PCS_RESET, true); ksz_cfg(dev, KSZ8863_REG_SW_RESET, KSZ8863_GLOBAL_SOFTWARE_RESET | KSZ8863_PCS_RESET, false); - } else if (ksz_is_ksz8463(dev)) { - ksz_cfg(dev, KSZ8463_REG_SW_RESET, - KSZ8463_GLOBAL_SOFTWARE_RESET, true); - ksz_cfg(dev, KSZ8463_REG_SW_RESET, - KSZ8463_GLOBAL_SOFTWARE_RESET, false); } else { /* reset switch */ ksz_write8(dev, REG_POWER_MANAGEMENT_1, @@ -2300,6 +2303,110 @@ static void ksz88xx_r_mib_stats64(struct ksz_device *dev, int port) spin_unlock(&mib->stats64_lock); } +static int ksz8463_setup(struct dsa_switch *ds) +{ + struct ksz_device *dev = ds->priv; + u16 storm_mask, storm_rate; + struct ksz_port *p; + const u16 *regs; + int i, ret; + + regs = dev->info->regs; + + dev->vlan_cache = devm_kcalloc(dev->dev, sizeof(struct vlan_table), + dev->info->num_vlans, GFP_KERNEL); + if (!dev->vlan_cache) + return -ENOMEM; + + ret = ksz8463_reset_switch(dev); + if (ret) { + dev_err(ds->dev, "failed to reset switch\n"); + return ret; + } + + /* set broadcast storm protection 10% rate */ + storm_mask = BROADCAST_STORM_RATE; + storm_rate = (BROADCAST_STORM_VALUE * BROADCAST_STORM_PROT_RATE) / 100; + storm_mask = swab16(storm_mask); + storm_rate = swab16(storm_rate); + regmap_update_bits(ksz_regmap_16(dev), regs[S_BROADCAST_CTRL], + storm_mask, storm_rate); + + ksz8_config_cpu_port(ds); + + ksz8_enable_stp_addr(dev); + + ds->num_tx_queues = dev->info->num_tx_queues; + + regmap_update_bits(ksz_regmap_8(dev), regs[S_MULTICAST_CTRL], + MULTICAST_STORM_DISABLE, MULTICAST_STORM_DISABLE); + + ksz_init_mib_timer(dev); + + ds->configure_vlan_while_not_filtering = false; + ds->dscp_prio_mapping_is_global = true; + ds->mtu_enforcement_ingress = true; + + /* We rely on software untagging on the CPU port, so that we + * can support both tagged and untagged VLANs + */ + ds->untag_bridge_pvid = true; + + /* VLAN filtering is partly controlled by the global VLAN + * Enable flag + */ + ds->vlan_filtering_is_global = true; + + /* Enable automatic fast aging when link changed detected. */ + ksz_cfg(dev, S_LINK_AGING_CTRL, SW_LINK_AUTO_AGING, true); + + /* Enable aggressive back off algorithm in half duplex mode. */ + ret = ksz_rmw8(dev, REG_SW_CTRL_1, SW_AGGR_BACKOFF, SW_AGGR_BACKOFF); + if (ret) + return ret; + + /* + * Make sure unicast VLAN boundary is set as default and + * enable no excessive collision drop. + */ + ret = ksz_rmw8(dev, REG_SW_CTRL_2, + UNICAST_VLAN_BOUNDARY | NO_EXC_COLLISION_DROP, + UNICAST_VLAN_BOUNDARY | NO_EXC_COLLISION_DROP); + if (ret) + return ret; + + ksz_cfg(dev, S_REPLACE_VID_CTRL, SW_REPLACE_VID, false); + + ksz_cfg(dev, S_MIRROR_CTRL, SW_MIRROR_RX_TX, false); + + for (i = 0; i < (dev->info->num_vlans / 4); i++) + ksz8_r_vlan_entries(dev, i); + + /* Start with learning disabled on standalone user ports, and enabled + * on the CPU port. In lack of other finer mechanisms, learning on the + * CPU port will avoid flooding bridge local addresses on the network + * in some cases. + */ + p = &dev->ports[dev->cpu_port]; + p->learning = true; + + ret = ksz_mdio_register(dev); + if (ret < 0) { + dev_err(dev->dev, "failed to register the mdio"); + return ret; + } + + ret = ksz_dcb_init(dev); + if (ret) + return ret; + + /* start switch */ + regmap_update_bits(ksz_regmap_8(dev), regs[S_START_CTRL], + SW_START, SW_START); + + return 0; +} + /** * ksz88x3_drive_strength_write() - Set the drive strength configuration for * KSZ8863 compatible chip variants. @@ -2445,10 +2552,6 @@ static int ksz8_setup(struct dsa_switch *ds) /* set broadcast storm protection 10% rate */ storm_mask = BROADCAST_STORM_RATE; storm_rate = (BROADCAST_STORM_VALUE * BROADCAST_STORM_PROT_RATE) / 100; - if (ksz_is_ksz8463(dev)) { - storm_mask = swab16(storm_mask); - storm_rate = swab16(storm_rate); - } regmap_update_bits(ksz_regmap_16(dev), regs[S_BROADCAST_CTRL], storm_mask, storm_rate); @@ -2499,7 +2602,7 @@ static int ksz8_setup(struct dsa_switch *ds) ksz_cfg(dev, S_MIRROR_CTRL, SW_MIRROR_RX_TX, false); - if (!ksz_is_ksz88x3(dev) && !ksz_is_ksz8463(dev)) + if (!ksz_is_ksz88x3(dev)) ksz_cfg(dev, REG_SW_CTRL_19, SW_INS_TAG_ENABLE, true); for (i = 0; i < (dev->info->num_vlans / 4); i++) @@ -2889,8 +2992,7 @@ const struct ksz_dev_ops ksz88xx_dev_ops = { const struct dsa_switch_ops ksz8463_switch_ops = { .get_tag_protocol = ksz8463_get_tag_protocol, .connect_tag_protocol = ksz8463_connect_tag_protocol, - .setup = ksz8_setup, - .teardown = ksz_teardown, + .setup = ksz8463_setup, .phy_read = ksz8463_phy_read16, .phy_write = ksz8463_phy_write16, .phylink_get_caps = ksz8_phylink_get_caps, From 9089bde4db6027b84ad07b0a4b6fd802f98397d5 Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Mon, 27 Jul 2026 12:20:44 +0200 Subject: [PATCH 0811/1433] net: dsa: microchip: split ksz8_config_cpu_port() ksz8_config_cpu_port() is only called twice, once by ksz8_setup() and once by ksz8463_setup(). It contains a ksz8463 branch that could be avoided in the ksz8_setup() case and a ksz87xx/ksz88xx branches that could be avoided in ksz8463_setup() case. Create ksz8463_config_cpu_port() that only handles the ksz8463 case and remove the ksz8463 specificities from the common ksz8_config_cpu_port(). Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260727-ksz-new-ptp-v3-2-caba39e680e3@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/microchip/ksz8.c | 73 ++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 28 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index 5ed63f425013..3bbca6f9cfc5 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -2029,6 +2029,50 @@ static void ksz88x3_config_rmii_clk(struct ksz_device *dev) KSZ88X3_PORT3_RMII_CLK_INTERNAL, rmii_clk_internal); } +static void ksz8463_config_cpu_port(struct dsa_switch *ds) +{ + struct ksz_device *dev = ds->priv; + struct ksz_port *p; + u8 fiber_ports = 0; + const u32 *masks; + const u16 *regs; + int i; + + masks = dev->info->masks; + regs = dev->info->regs; + + ksz_cfg(dev, regs[S_TAIL_TAG_CTRL], masks[SW_TAIL_TAG_ENABLE], true); + + ksz8_port_setup(dev, dev->cpu_port, true); + + for (i = 0; i < dev->phy_port_cnt; i++) + ksz_port_stp_state_set(ds, i, BR_STATE_DISABLED); + + for (i = 0; i < dev->phy_port_cnt; i++) { + p = &dev->ports[i]; + ksz_port_cfg(dev, i, regs[P_STP_CTRL], PORT_FORCE_FLOW_CTRL, + p->fiber); + if (p->fiber) + fiber_ports |= (1 << i); + } + + /* Setup fiber ports. */ + if (fiber_ports) { + fiber_ports &= 3; + regmap_update_bits(ksz_regmap_16(dev), KSZ8463_REG_CFG_CTRL, + fiber_ports << PORT_COPPER_MODE_S, + 0); + regmap_update_bits(ksz_regmap_16(dev), KSZ8463_REG_DSP_CTRL_6, + COPPER_RECEIVE_ADJUSTMENT, 0); + } + + /* Turn off PTP function as the switch enables it by default */ + regmap_update_bits(ksz_regmap_16(dev), KSZ8463_PTP_MSG_CONF1, + PTP_ENABLE, 0); + regmap_update_bits(ksz_regmap_16(dev), KSZ8463_PTP_CLK_CTRL, + PTP_CLK_ENABLE, 0); +} + static void ksz8_config_cpu_port(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; @@ -2036,7 +2080,6 @@ static void ksz8_config_cpu_port(struct dsa_switch *ds) const u32 *masks; const u16 *regs; u8 remote; - u8 fiber_ports = 0; int i; masks = dev->info->masks; @@ -2067,32 +2110,6 @@ static void ksz8_config_cpu_port(struct dsa_switch *ds) else ksz_port_cfg(dev, i, regs[P_STP_CTRL], PORT_FORCE_FLOW_CTRL, false); - if (p->fiber) - fiber_ports |= (1 << i); - } - if (ksz_is_ksz8463(dev)) { - /* Setup fiber ports. */ - if (fiber_ports) { - fiber_ports &= 3; - regmap_update_bits(ksz_regmap_16(dev), - KSZ8463_REG_CFG_CTRL, - fiber_ports << PORT_COPPER_MODE_S, - 0); - regmap_update_bits(ksz_regmap_16(dev), - KSZ8463_REG_DSP_CTRL_6, - COPPER_RECEIVE_ADJUSTMENT, 0); - } - - /* Turn off PTP function as the switch's proprietary way of - * handling timestamp is not supported in current Linux PTP - * stack implementation. - */ - regmap_update_bits(ksz_regmap_16(dev), - KSZ8463_PTP_MSG_CONF1, - PTP_ENABLE, 0); - regmap_update_bits(ksz_regmap_16(dev), - KSZ8463_PTP_CLK_CTRL, - PTP_CLK_ENABLE, 0); } } @@ -2332,7 +2349,7 @@ static int ksz8463_setup(struct dsa_switch *ds) regmap_update_bits(ksz_regmap_16(dev), regs[S_BROADCAST_CTRL], storm_mask, storm_rate); - ksz8_config_cpu_port(ds); + ksz8463_config_cpu_port(ds); ksz8_enable_stp_addr(dev); From 2a6aab5d5bd478b4aac42dd3238c4fefb9412a0d Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Mon, 27 Jul 2026 12:20:45 +0200 Subject: [PATCH 0812/1433] net: dsa: microchip: allow the use of other IRQ operations. The IRQ setup uses an hardcoded set of IRQ operations. These operations don't fit with the KSZ8463 which has an inverted bit logic (it uses an 'enable irq' register instead of a 'mask irq' one) and 16-bits registers. Take the IRQ domain operations as input of ksz_irq_common_setup() to allow KSZ8463 to use the already existing setup with its own set of IRQ operations. Expose ksz_irq_common_setup() and ksz_irq_bus_lock/unlock() so they can be used by ksz8.c. Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260727-ksz-new-ptp-v3-3-caba39e680e3@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/microchip/ksz_common.c | 15 ++++++++------- drivers/net/dsa/microchip/ksz_common.h | 5 +++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index ff4dd51f6cb0..1a9d6f83a023 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -2419,14 +2419,14 @@ static void ksz_irq_unmask(struct irq_data *d) kirq->masked &= ~BIT(d->hwirq); } -static void ksz_irq_bus_lock(struct irq_data *d) +void ksz_irq_bus_lock(struct irq_data *d) { struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); mutex_lock(&kirq->dev->lock_irq); } -static void ksz_irq_bus_sync_unlock(struct irq_data *d) +void ksz_irq_bus_sync_unlock(struct irq_data *d) { struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); struct ksz_device *dev = kirq->dev; @@ -2504,14 +2504,15 @@ static irqreturn_t ksz_irq_thread_fn(int irq, void *dev_id) return (nhandled > 0 ? IRQ_HANDLED : IRQ_NONE); } -static int ksz_irq_common_setup(struct ksz_device *dev, struct ksz_irq *kirq) +int ksz_irq_common_setup(struct ksz_device *dev, struct ksz_irq *kirq, + const struct irq_domain_ops *ops) { int ret, n; kirq->dev = dev; - kirq->domain = irq_domain_create_simple(dev_fwnode(dev->dev), kirq->nirqs, 0, - &ksz_irq_domain_ops, kirq); + kirq->domain = irq_domain_create_simple(dev_fwnode(dev->dev), + kirq->nirqs, 0, ops, kirq); if (!kirq->domain) return -ENOMEM; @@ -2543,7 +2544,7 @@ int ksz_girq_setup(struct ksz_device *dev) girq->irq_num = dev->irq; - return ksz_irq_common_setup(dev, girq); + return ksz_irq_common_setup(dev, girq, &ksz_irq_domain_ops); } int ksz_pirq_setup(struct ksz_device *dev, u8 p) @@ -2560,7 +2561,7 @@ int ksz_pirq_setup(struct ksz_device *dev, u8 p) if (!pirq->irq_num) return -EINVAL; - return ksz_irq_common_setup(dev, pirq); + return ksz_irq_common_setup(dev, pirq, &ksz_irq_domain_ops); } void ksz_teardown(struct dsa_switch *ds) diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index acaf70e6f393..0f2abb22ca91 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -8,6 +8,7 @@ #define __KSZ_COMMON_H #include +#include #include #include #include @@ -508,6 +509,10 @@ int ksz_sw_mdio_write(struct mii_bus *bus, int addr, int regnum, u16 val); int ksz_parent_mdio_read(struct mii_bus *bus, int addr, int regnum); int ksz_parent_mdio_write(struct mii_bus *bus, int addr, int regnum, u16 val); int ksz_mdio_register(struct ksz_device *dev); +void ksz_irq_bus_lock(struct irq_data *d); +void ksz_irq_bus_sync_unlock(struct irq_data *d); +int ksz_irq_common_setup(struct ksz_device *dev, struct ksz_irq *kirq, + const struct irq_domain_ops *ops); int ksz_pirq_setup(struct ksz_device *dev, u8 p); int ksz_girq_setup(struct ksz_device *dev); void ksz_irq_free(struct ksz_irq *kirq); From b3422f60e931bb5a55bd5e16456e100434bb5a69 Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Mon, 27 Jul 2026 12:20:46 +0200 Subject: [PATCH 0813/1433] net: dsa: microchip: add PTP interrupt handling for KSZ8463 KSZ8463 PTP interrupts aren't handled by the driver. The interrupt layout in KSZ8463 has nothing to do with the other switches: - Its global interrupt enable register is 16-bits long and follow an 'enable' logic, instead of a 'mask' one - all the interrupts of all ports are grouped into one status register while others have one interrupt register per port - xdelay_req and pdresp timestamps share one single interrupt bit on the KSZ8463 while each of them has its own interrupt bit on other switches Create a KSZ8463-specific set of interrupt domain operations to handle the global IRQ layer. To limit code duplication, it uses the same interrupt handler than the other switches. Since other switches have 8-bits registers, only the high-byte of the interrupt status/enable registers are used. This high-byte is where the PTP interrupts are located. The low-byte contains the wake-up detection interrupts so if at some points these interrupts are needed we'll need a bit of rework here. Create KSZ8463-specific functions to setup the PTP interrupts. The created IRQ domain is tied to the first port of the KSZ8463. Again, the same PTP interrupt handler than the others switches is used. Implement the teardown callback to release the interrupts. Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260727-ksz-new-ptp-v3-4-caba39e680e3@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/microchip/ksz8.c | 93 ++++++++++++++++- drivers/net/dsa/microchip/ksz_ptp.c | 131 ++++++++++++++++++++++++ drivers/net/dsa/microchip/ksz_ptp.h | 9 ++ drivers/net/dsa/microchip/ksz_ptp_reg.h | 6 ++ 4 files changed, 237 insertions(+), 2 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index 3bbca6f9cfc5..c099a7005808 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -36,6 +36,13 @@ #include "ksz8_reg.h" #include "ksz8.h" +/* + * We use only the high-byte (so odd addresses) of the 16-bits registers to fit + * in the common IRQ framework + */ +#define KSZ8463_REG_ISR 0x191 +#define KSZ8463_REG_IER 0x193 + /* ksz88x3_drive_strengths - Drive strength mapping for KSZ8863, KSZ8873, .. * variants. * This values are documented in KSZ8873 and KSZ8863 datasheets. @@ -181,6 +188,58 @@ static int ksz8_pme_pwrite8(struct ksz_device *dev, int port, int offset, u8 dat return ksz8_ind_write8(dev, table, (u8)(offset), data); } +static void ksz8463_irq_mask(struct irq_data *d) +{ + struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); + + kirq->masked &= ~BIT(d->hwirq); +} + +static void ksz8463_irq_unmask(struct irq_data *d) +{ + struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); + + kirq->masked |= BIT(d->hwirq); +} + +static const struct irq_chip ksz8463_irq_chip = { + .name = "ksz8463-irq", + .irq_mask = ksz8463_irq_mask, + .irq_unmask = ksz8463_irq_unmask, + .irq_bus_lock = ksz_irq_bus_lock, + .irq_bus_sync_unlock = ksz_irq_bus_sync_unlock, +}; + +static int ksz8463_irq_domain_map(struct irq_domain *d, + unsigned int irq, irq_hw_number_t hwirq) +{ + irq_set_chip_data(irq, d->host_data); + irq_set_chip_and_handler(irq, &ksz8463_irq_chip, handle_level_irq); + irq_set_noprobe(irq); + + return 0; +} + +static const struct irq_domain_ops ksz8463_irq_domain_ops = { + .map = ksz8463_irq_domain_map, + .xlate = irq_domain_xlate_twocell, +}; + +static int ksz8463_girq_setup(struct ksz_device *dev) +{ + struct ksz_irq *girq = &dev->girq; + + girq->nirqs = 8; + girq->reg_mask = KSZ8463_REG_IER; + girq->reg_status = KSZ8463_REG_ISR; + girq->masked = 0; + snprintf(girq->name, sizeof(girq->name), "ksz8463-girq"); + + girq->irq_num = dev->irq; + + return ksz_irq_common_setup(dev, girq, &ksz8463_irq_domain_ops); +} + static int ksz8463_reset_switch(struct ksz_device *dev) { ksz_cfg(dev, KSZ8463_REG_SW_RESET, KSZ8463_GLOBAL_SOFTWARE_RESET, true); @@ -2407,21 +2466,50 @@ static int ksz8463_setup(struct dsa_switch *ds) p = &dev->ports[dev->cpu_port]; p->learning = true; + if (dev->irq > 0) { + ret = ksz8463_girq_setup(dev); + if (ret) + return ret; + + ret = ksz8463_ptp_irq_setup(ds); + if (ret) + goto free_girq; + } + ret = ksz_mdio_register(dev); if (ret < 0) { dev_err(dev->dev, "failed to register the mdio"); - return ret; + goto free_ptp_irq; } ret = ksz_dcb_init(dev); if (ret) - return ret; + goto free_ptp_irq; /* start switch */ regmap_update_bits(ksz_regmap_8(dev), regs[S_START_CTRL], SW_START, SW_START); return 0; + +free_ptp_irq: + if (dev->irq > 0) + ksz8463_ptp_irq_free(ds); +free_girq: + if (dev->irq > 0) + ksz_irq_free(&dev->girq); + + return ret; +} + +static void ksz8463_teardown(struct dsa_switch *ds) +{ + struct ksz_device *dev = ds->priv; + + if (dev->irq > 0) { + ksz8463_ptp_irq_free(ds); + ksz_irq_free(&dev->girq); + } } /** @@ -3010,6 +3098,7 @@ const struct dsa_switch_ops ksz8463_switch_ops = { .get_tag_protocol = ksz8463_get_tag_protocol, .connect_tag_protocol = ksz8463_connect_tag_protocol, .setup = ksz8463_setup, + .teardown = ksz8463_teardown, .phy_read = ksz8463_phy_read16, .phy_write = ksz8463_phy_write16, .phylink_get_caps = ksz8_phylink_get_caps, diff --git a/drivers/net/dsa/microchip/ksz_ptp.c b/drivers/net/dsa/microchip/ksz_ptp.c index 5bdf829a6e38..d8a583ed8b43 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.c +++ b/drivers/net/dsa/microchip/ksz_ptp.c @@ -32,6 +32,15 @@ #define KSZ_PTP_INT_START 13 +/* + * PTP interrupt bit is the bit 12 of the 16-bits ISR/IER. But ksz_common.c only + * accesses the high-byte of these registers so the PTP interrupt bit becomes 4. + */ +#define KSZ8463_SRC_PTP_INT 4 +#define KSZ8463_PTP_PORT1_INT_START 12 +#define KSZ8463_PTP_PORT2_INT_START 14 +#define KSZ8463_PTP_INT_START KSZ8463_PTP_PORT1_INT_START + static int ksz_ptp_tou_gpio(struct ksz_device *dev) { int ret; @@ -1131,6 +1140,128 @@ static int ksz_ptp_msg_irq_setup(struct ksz_port *port, u8 n) return ret; } +static int ksz8463_ptp_port_irq_setup(struct ksz_irq *ptpirq, + struct ksz_port *port, int hw_irq) +{ + u16 ts_reg[] = {KSZ8463_REG_PORT_SYNC_TS, KSZ8463_REG_PORT_DREQ_TS}; + static const char * const name[] = {"sync-msg", "delay-msg"}; + const struct ksz_dev_ops *ops = port->ksz_dev->dev_ops; + struct ksz_ptp_irq *ptpmsg_irq; + int ret; + int i; + + init_completion(&port->tstamp_msg_comp); + + for (i = 0; i < 2; i++) { + ptpmsg_irq = &port->ptpmsg_irq[i]; + ptpmsg_irq->num = irq_create_mapping(ptpirq->domain, + hw_irq + i); + if (!ptpmsg_irq->num) { + ret = -EINVAL; + goto release_msg_irq; + } + + ptpmsg_irq->port = port; + ptpmsg_irq->ts_reg = ops->get_port_addr(port->num, ts_reg[i]); + + strscpy(ptpmsg_irq->name, name[i]); + + ret = request_threaded_irq(ptpmsg_irq->num, NULL, + ksz_ptp_msg_thread_fn, IRQF_ONESHOT, + ptpmsg_irq->name, ptpmsg_irq); + if (ret) { + irq_dispose_mapping(ptpmsg_irq->num); + goto release_msg_irq; + } + } + + return 0; + +release_msg_irq: + while (i--) + ksz_ptp_msg_irq_free(port, i); + + return ret; +} + +static void ksz8463_ptp_port_irq_teardown(struct ksz_port *port) +{ + int i; + + for (i = 0; i < 2; i++) + ksz_ptp_msg_irq_free(port, i); +} + +int ksz8463_ptp_irq_setup(struct dsa_switch *ds) +{ + struct ksz_device *dev = ds->priv; + struct ksz_port *port1, *port2; + struct ksz_irq *ptpirq; + int ret; + + port1 = &dev->ports[0]; + port2 = &dev->ports[1]; + ptpirq = &port1->ptpirq; + + ptpirq->irq_num = irq_find_mapping(dev->girq.domain, + KSZ8463_SRC_PTP_INT); + if (!ptpirq->irq_num) + return -EINVAL; + + ptpirq->dev = dev; + ptpirq->nirqs = 4; + ptpirq->reg_mask = KSZ8463_PTP_TS_IER; + ptpirq->reg_status = KSZ8463_PTP_TS_ISR; + ptpirq->irq0_offset = KSZ8463_PTP_INT_START; + snprintf(ptpirq->name, sizeof(ptpirq->name), "ptp-irq"); + + ptpirq->domain = irq_domain_create_linear(dev_fwnode(dev->dev), + ptpirq->nirqs, + &ksz_ptp_irq_domain_ops, + ptpirq); + if (!ptpirq->domain) + return -ENOMEM; + + ret = ksz8463_ptp_port_irq_setup(ptpirq, port1, + KSZ8463_PTP_PORT1_INT_START - KSZ8463_PTP_INT_START); + if (ret) + goto release_domain; + + ret = ksz8463_ptp_port_irq_setup(ptpirq, port2, + KSZ8463_PTP_PORT2_INT_START - KSZ8463_PTP_INT_START); + if (ret) + goto free_port1; + + ret = request_threaded_irq(ptpirq->irq_num, NULL, ksz_ptp_irq_thread_fn, + IRQF_ONESHOT, ptpirq->name, ptpirq); + if (ret) + goto free_port2; + + return 0; + +free_port2: + ksz8463_ptp_port_irq_teardown(port2); +free_port1: + ksz8463_ptp_port_irq_teardown(port1); +release_domain: + irq_domain_remove(ptpirq->domain); + + return ret; +} + +void ksz8463_ptp_irq_free(struct dsa_switch *ds) +{ + struct ksz_device *dev = ds->priv; + struct ksz_port *port1 = &dev->ports[0]; + struct ksz_port *port2 = &dev->ports[1]; + struct ksz_irq *ptpirq = &port1->ptpirq; + + free_irq(ptpirq->irq_num, ptpirq); + ksz8463_ptp_port_irq_teardown(port2); + ksz8463_ptp_port_irq_teardown(port1); + irq_domain_remove(ptpirq->domain); +} + int ksz_ptp_irq_setup(struct dsa_switch *ds, u8 p) { struct ksz_device *dev = ds->priv; diff --git a/drivers/net/dsa/microchip/ksz_ptp.h b/drivers/net/dsa/microchip/ksz_ptp.h index 3086e519b1b6..11408580031d 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.h +++ b/drivers/net/dsa/microchip/ksz_ptp.h @@ -50,6 +50,8 @@ bool ksz_port_rxtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb, unsigned int type); int ksz_ptp_irq_setup(struct dsa_switch *ds, u8 p); void ksz_ptp_irq_free(struct dsa_switch *ds, u8 p); +int ksz8463_ptp_irq_setup(struct dsa_switch *ds); +void ksz8463_ptp_irq_free(struct dsa_switch *ds); #else @@ -72,6 +74,13 @@ static inline int ksz_ptp_irq_setup(struct dsa_switch *ds, u8 p) static inline void ksz_ptp_irq_free(struct dsa_switch *ds, u8 p) {} +static inline int ksz8463_ptp_irq_setup(struct dsa_switch *ds) +{ + return 0; +} + +static inline void ksz8463_ptp_irq_free(struct dsa_switch *ds) {} + #define ksz_get_ts_info NULL #define ksz_hwtstamp_get NULL diff --git a/drivers/net/dsa/microchip/ksz_ptp_reg.h b/drivers/net/dsa/microchip/ksz_ptp_reg.h index eab9aecb7fa8..1a669d6ee889 100644 --- a/drivers/net/dsa/microchip/ksz_ptp_reg.h +++ b/drivers/net/dsa/microchip/ksz_ptp_reg.h @@ -121,6 +121,12 @@ #define REG_PTP_PORT_SYNC_TS 0x0C0C #define REG_PTP_PORT_PDRESP_TS 0x0C10 +#define KSZ8463_REG_PORT_DREQ_TS 0x0648 +#define KSZ8463_REG_PORT_SYNC_TS 0x064C +#define KSZ8463_REG_PORT_DRESP_TS 0x0650 +#define KSZ8463_PTP_TS_ISR 0x068C +#define KSZ8463_PTP_TS_IER 0x068E + #define REG_PTP_PORT_TX_INT_STATUS__2 0x0C14 #define REG_PTP_PORT_TX_INT_ENABLE__2 0x0C16 From ac99cc6cd3f80308192e884547b3e384de9f6807 Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Mon, 27 Jul 2026 12:20:47 +0200 Subject: [PATCH 0814/1433] net: dsa: microchip: adapt port offset for KSZ8463's PTP register In KSZ8463 register's layout, the offset between port 1 and port 2 registers isn't the same in the generic control register area than in the PTP register area. The get_port_addr() always uses the same offset so it doesn't work when it's used to access PTP registers. Adapt the port offset in get_port_addr() when the accessed register is in the PTP area. Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260727-ksz-new-ptp-v3-5-caba39e680e3@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/microchip/ksz8.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index c099a7005808..5e5bfc5cae2d 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -2831,6 +2831,9 @@ static u32 ksz8_get_port_addr(int port, int offset) static u32 ksz8463_get_port_addr(int port, int offset) { + if (offset >= KSZ8463_PTP_CLK_CTRL) + return offset + 0x20 * port; + return offset + 0x18 * port; } From e5a0c13163684a8a1c16636437e627535a7b82ab Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Mon, 27 Jul 2026 12:20:48 +0200 Subject: [PATCH 0815/1433] net: dsa: tag_ksz: move the KSZ8795 tag handling below ksz_xmit_timestamp() Upcoming patch reduces code duplication between KSZ8795 and KSZ9893 by introducing a common xmit() function. This rework needs the KSZ8795 handlers to be implemented below ksz_defer_xmit(). Do the move now to reduce the noise in next patch. No functionnal change is intended in this patch. Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260727-ksz-new-ptp-v3-6-caba39e680e3@bootlin.com Signed-off-by: Jakub Kicinski --- net/dsa/tag_ksz.c | 132 +++++++++++++++++++++++----------------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/net/dsa/tag_ksz.c b/net/dsa/tag_ksz.c index 67fa89f102e0..f58ce0f0e9e4 100644 --- a/net/dsa/tag_ksz.c +++ b/net/dsa/tag_ksz.c @@ -103,72 +103,6 @@ static struct sk_buff *ksz_common_rcv(struct sk_buff *skb, return skb; } -/* - * For Ingress (Host -> KSZ8795), 1 byte is added before FCS. - * --------------------------------------------------------------------------- - * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag(1byte)|FCS(4bytes) - * --------------------------------------------------------------------------- - * tag : each bit represents port (eg, 0x01=port1, 0x02=port2, 0x10=port5) - * - * For Egress (KSZ8795 -> Host), 1 byte is added before FCS. - * --------------------------------------------------------------------------- - * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag0(1byte)|FCS(4bytes) - * --------------------------------------------------------------------------- - * tag0 : zero-based value represents port - * (eg, 0x0=port1, 0x2=port3, 0x3=port4) - */ - -#define KSZ8795_TAIL_TAG_EG_PORT_M GENMASK(1, 0) -#define KSZ8795_TAIL_TAG_OVERRIDE BIT(6) -#define KSZ8795_TAIL_TAG_LOOKUP BIT(7) - -static struct sk_buff *ksz8795_xmit(struct sk_buff *skb, struct net_device *dev) -{ - struct ethhdr *hdr; - u8 *tag; - - if (skb->ip_summed == CHECKSUM_PARTIAL && skb_checksum_help(skb)) { - kfree_skb(skb); - return NULL; - } - - /* Tag encoding */ - tag = skb_put(skb, KSZ_INGRESS_TAG_LEN); - hdr = skb_eth_hdr(skb); - - *tag = dsa_xmit_port_mask(skb, dev); - if (is_link_local_ether_addr(hdr->h_dest)) - *tag |= KSZ8795_TAIL_TAG_OVERRIDE; - - return skb; -} - -static struct sk_buff *ksz8795_rcv(struct sk_buff *skb, struct net_device *dev) -{ - u8 *tag; - - if (skb_linearize(skb)) { - kfree_skb(skb); - return NULL; - } - - tag = skb_tail_pointer(skb) - KSZ_EGRESS_TAG_LEN; - - return ksz_common_rcv(skb, dev, tag[0] & KSZ8795_TAIL_TAG_EG_PORT_M, - KSZ_EGRESS_TAG_LEN); -} - -static const struct dsa_device_ops ksz8795_netdev_ops = { - .name = KSZ8795_NAME, - .proto = DSA_TAG_PROTO_KSZ8795, - .xmit = ksz8795_xmit, - .rcv = ksz8795_rcv, - .needed_tailroom = KSZ_INGRESS_TAG_LEN, -}; - -DSA_TAG_DRIVER(ksz8795_netdev_ops); -MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ8795, KSZ8795_NAME); - /* * For Ingress (Host -> KSZ9477), 2/6 bytes are added before FCS. * --------------------------------------------------------------------------- @@ -353,6 +287,72 @@ static const struct dsa_device_ops ksz9477_netdev_ops = { DSA_TAG_DRIVER(ksz9477_netdev_ops); MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ9477, KSZ9477_NAME); +/* + * For Ingress (Host -> KSZ8795), 1 byte is added before FCS. + * --------------------------------------------------------------------------- + * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag(1byte)|FCS(4bytes) + * --------------------------------------------------------------------------- + * tag : each bit represents port (eg, 0x01=port1, 0x02=port2, 0x10=port5) + * + * For Egress (KSZ8795 -> Host), 1 byte is added before FCS. + * --------------------------------------------------------------------------- + * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag0(1byte)|FCS(4bytes) + * --------------------------------------------------------------------------- + * tag0 : zero-based value represents port + * (eg, 0x0=port1, 0x2=port3, 0x3=port4) + */ + +#define KSZ8795_TAIL_TAG_EG_PORT_M GENMASK(1, 0) +#define KSZ8795_TAIL_TAG_OVERRIDE BIT(6) +#define KSZ8795_TAIL_TAG_LOOKUP BIT(7) + +static struct sk_buff *ksz8795_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct ethhdr *hdr; + u8 *tag; + + if (skb->ip_summed == CHECKSUM_PARTIAL && skb_checksum_help(skb)) { + kfree_skb(skb); + return NULL; + } + + /* Tag encoding */ + tag = skb_put(skb, KSZ_INGRESS_TAG_LEN); + hdr = skb_eth_hdr(skb); + + *tag = dsa_xmit_port_mask(skb, dev); + if (is_link_local_ether_addr(hdr->h_dest)) + *tag |= KSZ8795_TAIL_TAG_OVERRIDE; + + return skb; +} + +static struct sk_buff *ksz8795_rcv(struct sk_buff *skb, struct net_device *dev) +{ + u8 *tag; + + if (skb_linearize(skb)) { + kfree_skb(skb); + return NULL; + } + + tag = skb_tail_pointer(skb) - KSZ_EGRESS_TAG_LEN; + + return ksz_common_rcv(skb, dev, tag[0] & KSZ8795_TAIL_TAG_EG_PORT_M, + KSZ_EGRESS_TAG_LEN); +} + +static const struct dsa_device_ops ksz8795_netdev_ops = { + .name = KSZ8795_NAME, + .proto = DSA_TAG_PROTO_KSZ8795, + .xmit = ksz8795_xmit, + .rcv = ksz8795_rcv, + .needed_tailroom = KSZ_INGRESS_TAG_LEN, +}; + +DSA_TAG_DRIVER(ksz8795_netdev_ops); +MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ8795, KSZ8795_NAME); + #define KSZ9893_TAIL_TAG_PRIO GENMASK(4, 3) #define KSZ9893_TAIL_TAG_OVERRIDE BIT(5) #define KSZ9893_TAIL_TAG_LOOKUP BIT(6) From a97c78093e9b900ede8d660b0c6121f76cb03b5f Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Mon, 27 Jul 2026 12:20:49 +0200 Subject: [PATCH 0816/1433] net: dsa: tag_ksz: share code for KSZ8795 and KSZ9893 xmit operations KSZ8795 and KSZ9893 have very similar tag handling in the xmit path, leading to code duplication. There are only two differences between the two ksz*_xmit(): - the KSZ8795 doesn't handle priorities between frames - ksz8795_xmit() directly returns the SKB instead of calling ksz_defer_xmit(). Yet, ksz_defer_xmit() also returns directly the SKB if no clone is present inside the SKB. Clones are only created by the KSZ driver when the PTP feature is enabled. Since KSZ8795 doesn't support PTP, returning the SKB directly or ksz_defer_xmit() is the same. The upcoming support for the KSZ8463 also requires a similar xmit(). Gather the common code from ksz8795_xmit() and ksz9893_xmit() into a new ksz_common_xmit() function that takes three input arguments: - do_tstamp to tell whether ksz_xmit_timestamp() should be called - prio to give the priority tag (if any) - override_mask to give the location of the override bit (if any) Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260727-ksz-new-ptp-v3-7-caba39e680e3@bootlin.com Signed-off-by: Jakub Kicinski --- net/dsa/tag_ksz.c | 73 +++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/net/dsa/tag_ksz.c b/net/dsa/tag_ksz.c index f58ce0f0e9e4..f8b40437c5fa 100644 --- a/net/dsa/tag_ksz.c +++ b/net/dsa/tag_ksz.c @@ -218,6 +218,37 @@ static struct sk_buff *ksz_defer_xmit(struct dsa_port *dp, struct sk_buff *skb) return NULL; } +static struct sk_buff *ksz_common_xmit(struct sk_buff *skb, + struct net_device *dev, + bool do_tstamp, + u8 prio, + u8 override_mask) +{ + struct dsa_port *dp = dsa_user_to_port(dev); + struct ethhdr *hdr; + u8 *tag; + + if (skb->ip_summed == CHECKSUM_PARTIAL && skb_checksum_help(skb)) { + kfree_skb(skb); + return NULL; + } + + /* Tag encoding */ + if (do_tstamp) + ksz_xmit_timestamp(dp, skb); + + tag = skb_put(skb, KSZ_INGRESS_TAG_LEN); + hdr = skb_eth_hdr(skb); + + *tag = dsa_xmit_port_mask(skb, dev); + *tag |= prio; + + if (is_link_local_ether_addr(hdr->h_dest)) + *tag |= override_mask; + + return ksz_defer_xmit(dp, skb); +} + static struct sk_buff *ksz9477_xmit(struct sk_buff *skb, struct net_device *dev) { @@ -308,23 +339,7 @@ MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ9477, KSZ9477_NAME); static struct sk_buff *ksz8795_xmit(struct sk_buff *skb, struct net_device *dev) { - struct ethhdr *hdr; - u8 *tag; - - if (skb->ip_summed == CHECKSUM_PARTIAL && skb_checksum_help(skb)) { - kfree_skb(skb); - return NULL; - } - - /* Tag encoding */ - tag = skb_put(skb, KSZ_INGRESS_TAG_LEN); - hdr = skb_eth_hdr(skb); - - *tag = dsa_xmit_port_mask(skb, dev); - if (is_link_local_ether_addr(hdr->h_dest)) - *tag |= KSZ8795_TAIL_TAG_OVERRIDE; - - return skb; + return ksz_common_xmit(skb, dev, false, 0, KSZ8795_TAIL_TAG_OVERRIDE); } static struct sk_buff *ksz8795_rcv(struct sk_buff *skb, struct net_device *dev) @@ -362,28 +377,10 @@ static struct sk_buff *ksz9893_xmit(struct sk_buff *skb, { u16 queue_mapping = skb_get_queue_mapping(skb); u8 prio = netdev_txq_to_tc(dev, queue_mapping); - struct dsa_port *dp = dsa_user_to_port(dev); - struct ethhdr *hdr; - u8 *tag; - if (skb->ip_summed == CHECKSUM_PARTIAL && skb_checksum_help(skb)) { - kfree_skb(skb); - return NULL; - } - - /* Tag encoding */ - ksz_xmit_timestamp(dp, skb); - - tag = skb_put(skb, KSZ_INGRESS_TAG_LEN); - hdr = skb_eth_hdr(skb); - - *tag = dsa_xmit_port_mask(skb, dev); - *tag |= FIELD_PREP(KSZ9893_TAIL_TAG_PRIO, prio); - - if (is_link_local_ether_addr(hdr->h_dest)) - *tag |= KSZ9893_TAIL_TAG_OVERRIDE; - - return ksz_defer_xmit(dp, skb); + return ksz_common_xmit(skb, dev, true, + FIELD_PREP(KSZ9893_TAIL_TAG_PRIO, prio), + KSZ9893_TAIL_TAG_OVERRIDE); } static const struct dsa_device_ops ksz9893_netdev_ops = { From 46e8ceb399d548dfb7def6b160613e63bd59623d Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Mon, 27 Jul 2026 12:20:50 +0200 Subject: [PATCH 0817/1433] net: dsa: microchip: add KSZ8463 tail tag handling KSZ8463 uses the KSZ9893 DSA TAG driver. However, the KSZ8463 doesn't use the tail tag to convey timestamps to the host as KSZ9893 does. It uses the reserved fields in the PTP header instead. Add a KSZ8463-specific DSA_TAG driver to handle KSZ8463 timestamps. There is no information in the tail tag to distinguish PTP packets from others so use the ptp_classify_raw() helper to find the PTP packets and extract the timestamp from their PTP headers. Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260727-ksz-new-ptp-v3-8-caba39e680e3@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/microchip/ksz8.c | 4 +- include/net/dsa.h | 2 + net/dsa/tag_ksz.c | 66 ++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index 5e5bfc5cae2d..ac9e8ef5774a 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -2966,7 +2966,7 @@ static enum dsa_tag_protocol ksz8463_get_tag_protocol(struct dsa_switch *ds, int port, enum dsa_tag_protocol mp) { - return DSA_TAG_PROTO_KSZ9893; + return DSA_TAG_PROTO_KSZ8463; } static int ksz8463_connect_tag_protocol(struct dsa_switch *ds, @@ -2974,7 +2974,7 @@ static int ksz8463_connect_tag_protocol(struct dsa_switch *ds, { struct ksz_tagger_data *tagger_data; - if (proto != DSA_TAG_PROTO_KSZ9893) + if (proto != DSA_TAG_PROTO_KSZ8463) return -EPROTONOSUPPORT; tagger_data = ksz_tagger_data(ds); diff --git a/include/net/dsa.h b/include/net/dsa.h index 8c16ef23cc10..6f7f5c17b532 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -59,6 +59,7 @@ struct tc_action; #define DSA_TAG_PROTO_MXL_GSW1XX_VALUE 31 #define DSA_TAG_PROTO_MXL862_VALUE 32 #define DSA_TAG_PROTO_NETC_VALUE 33 +#define DSA_TAG_PROTO_KSZ8463_VALUE 34 enum dsa_tag_protocol { DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE, @@ -95,6 +96,7 @@ enum dsa_tag_protocol { DSA_TAG_PROTO_MXL_GSW1XX = DSA_TAG_PROTO_MXL_GSW1XX_VALUE, DSA_TAG_PROTO_MXL862 = DSA_TAG_PROTO_MXL862_VALUE, DSA_TAG_PROTO_NETC = DSA_TAG_PROTO_NETC_VALUE, + DSA_TAG_PROTO_KSZ8463 = DSA_TAG_PROTO_KSZ8463_VALUE, }; struct dsa_switch; diff --git a/net/dsa/tag_ksz.c b/net/dsa/tag_ksz.c index f8b40437c5fa..b4d70ba930dc 100644 --- a/net/dsa/tag_ksz.c +++ b/net/dsa/tag_ksz.c @@ -12,6 +12,7 @@ #include "tag.h" +#define KSZ8463_NAME "ksz8463" #define KSZ8795_NAME "ksz8795" #define KSZ9477_NAME "ksz9477" #define KSZ9893_NAME "ksz9893" @@ -396,6 +397,70 @@ static const struct dsa_device_ops ksz9893_netdev_ops = { DSA_TAG_DRIVER(ksz9893_netdev_ops); MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ9893, KSZ9893_NAME); +#define KSZ8463_TAIL_TAG_PRIO GENMASK(4, 3) +#define KSZ8463_TAIL_TAG_EG_PORT_M GENMASK(2, 0) + +static struct sk_buff *ksz8463_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + u16 queue_mapping = skb_get_queue_mapping(skb); + u8 prio = netdev_txq_to_tc(dev, queue_mapping); + + return ksz_common_xmit(skb, dev, false, + FIELD_PREP(KSZ8463_TAIL_TAG_PRIO, prio), + 0); +} + +static struct sk_buff *ksz8463_rcv(struct sk_buff *skb, struct net_device *dev) +{ + unsigned int len = KSZ_EGRESS_TAG_LEN; + struct ptp_header *ptp_hdr; + unsigned int ptp_class; + unsigned int port; + ktime_t ts; + u8 *tag; + + if (skb_linearize(skb)) { + kfree_skb(skb); + return NULL; + } + + KSZ_SKB_CB(skb)->tstamp = 0; + + /* Tag decoding */ + tag = skb_tail_pointer(skb) - KSZ_EGRESS_TAG_LEN; + port = tag[0] & KSZ8463_TAIL_TAG_EG_PORT_M; + + __skb_push(skb, ETH_HLEN); + ptp_class = ptp_classify_raw(skb); + __skb_pull(skb, ETH_HLEN); + if (ptp_class == PTP_CLASS_NONE) + goto common_rcv; + + ptp_hdr = ptp_parse_header(skb, ptp_class); + if (ptp_hdr) { + ts = ksz_decode_tstamp(get_unaligned_be32(&ptp_hdr->reserved2)); + KSZ_SKB_CB(skb)->tstamp = ts; + ptp_hdr->reserved2 = 0; + } + +common_rcv: + return ksz_common_rcv(skb, dev, port, len); +} + +static const struct dsa_device_ops ksz8463_netdev_ops = { + .name = KSZ8463_NAME, + .proto = DSA_TAG_PROTO_KSZ8463, + .xmit = ksz8463_xmit, + .rcv = ksz8463_rcv, + .connect = ksz_connect, + .disconnect = ksz_disconnect, + .needed_tailroom = KSZ_INGRESS_TAG_LEN, +}; + +DSA_TAG_DRIVER(ksz8463_netdev_ops); +MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ8463, KSZ8463_NAME); + /* For xmit, 2/6 bytes are added before FCS. * --------------------------------------------------------------------------- * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|ts(4bytes)|tag0(1byte)|tag1(1byte)| @@ -468,6 +533,7 @@ DSA_TAG_DRIVER(lan937x_netdev_ops); MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_LAN937X, LAN937X_NAME); static struct dsa_tag_driver *dsa_tag_driver_array[] = { + &DSA_TAG_DRIVER_NAME(ksz8463_netdev_ops), &DSA_TAG_DRIVER_NAME(ksz8795_netdev_ops), &DSA_TAG_DRIVER_NAME(ksz9477_netdev_ops), &DSA_TAG_DRIVER_NAME(ksz9893_netdev_ops), From 172397752efb95d45c10fb21f8ec29103085aba2 Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Mon, 27 Jul 2026 12:20:51 +0200 Subject: [PATCH 0818/1433] net: dsa: microchip: explicitly enable detection of L2 PTP frames Detection of L2 PTP frames needs to be enabled for PTP to work at the L2 layer. The bit enabling this detection is set by default on the switches currently supported by the driver, but it is unset by default on the KSZ8463 for which support will be added in upcoming patches. Explicitly enable the detection of L2 PTP frames for all switches when PTP is enabled. Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260727-ksz-new-ptp-v3-9-caba39e680e3@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/microchip/ksz_ptp.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz_ptp.c b/drivers/net/dsa/microchip/ksz_ptp.c index d8a583ed8b43..febc3141f201 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.c +++ b/drivers/net/dsa/microchip/ksz_ptp.c @@ -953,8 +953,9 @@ int ksz_ptp_clock_register(struct dsa_switch *ds) /* Currently only P2P mode is supported. When 802_1AS bit is set, it * forwards all PTP packets to host port and none to other ports. */ - ret = ksz_rmw16(dev, regs[PTP_MSG_CONF1], PTP_TC_P2P | PTP_802_1AS, - PTP_TC_P2P | PTP_802_1AS); + ret = ksz_rmw16(dev, regs[PTP_MSG_CONF1], + PTP_TC_P2P | PTP_802_1AS | PTP_ETH_ENABLE, + PTP_TC_P2P | PTP_802_1AS | PTP_ETH_ENABLE); if (ret) return ret; From d06c58f4fcf2b62172d6bf5a9e65e557d2d0d582 Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Mon, 27 Jul 2026 12:20:52 +0200 Subject: [PATCH 0819/1433] net: dsa: microchip: add two-steps PTP support for KSZ8463 The KSZ8463 switch supports PTP but it's not supported by the driver. Add L2 two-step PTP support for the KSZ8463. IPv4 and IPv6 layers aren't supported. Neither is one-step PTP. Use KSZ8463-specific implementations of the .get_ts_info and .port_hwtstamp_set callbacks. The pdelay_req and pdelay_resp timestamps share one interrupt bit status while they're located in two different registers. So introduce last_tx_is_pdelayresp to keep track of the last sent event type. This flag is set by the xmit worker right before sending the packet and then used in the interrupt handler to retrieve the timestamp location. Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260727-ksz-new-ptp-v3-10-caba39e680e3@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/microchip/ksz8.c | 26 +++-- drivers/net/dsa/microchip/ksz8_reg.h | 1 + drivers/net/dsa/microchip/ksz_common.h | 1 + drivers/net/dsa/microchip/ksz_ptp.c | 135 +++++++++++++++++++++++- drivers/net/dsa/microchip/ksz_ptp.h | 7 ++ drivers/net/dsa/microchip/ksz_ptp_reg.h | 4 + 6 files changed, 167 insertions(+), 7 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index ac9e8ef5774a..941ae9f66f70 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -242,8 +242,11 @@ static int ksz8463_girq_setup(struct ksz_device *dev) static int ksz8463_reset_switch(struct ksz_device *dev) { - ksz_cfg(dev, KSZ8463_REG_SW_RESET, KSZ8463_GLOBAL_SOFTWARE_RESET, true); - ksz_cfg(dev, KSZ8463_REG_SW_RESET, KSZ8463_GLOBAL_SOFTWARE_RESET, + ksz_cfg(dev, KSZ8463_REG_SW_RESET, + KSZ8463_GLOBAL_SOFTWARE_RESET | KSZ8463_PTP_SOFTWARE_RESET, + true); + ksz_cfg(dev, KSZ8463_REG_SW_RESET, + KSZ8463_GLOBAL_SOFTWARE_RESET | KSZ8463_PTP_SOFTWARE_RESET, false); return 0; } @@ -2474,17 +2477,24 @@ static int ksz8463_setup(struct dsa_switch *ds) ret = ksz8463_ptp_irq_setup(ds); if (ret) goto free_girq; + + ret = ksz_ptp_clock_register(ds); + if (ret) { + dev_err(dev->dev, "Failed to register PTP clock: %d\n", + ret); + goto free_ptp_irq; + } } ret = ksz_mdio_register(dev); if (ret < 0) { dev_err(dev->dev, "failed to register the mdio"); - goto free_ptp_irq; + goto ptp_clock_unregister; } ret = ksz_dcb_init(dev); if (ret) - goto free_ptp_irq; + goto ptp_clock_unregister; /* start switch */ regmap_update_bits(ksz_regmap_8(dev), regs[S_START_CTRL], @@ -2492,6 +2502,9 @@ static int ksz8463_setup(struct dsa_switch *ds) return 0; +ptp_clock_unregister: + if (dev->irq > 0) + ksz_ptp_clock_unregister(ds); free_ptp_irq: if (dev->irq > 0) ksz8463_ptp_irq_free(ds); @@ -2507,6 +2520,7 @@ static void ksz8463_teardown(struct dsa_switch *ds) struct ksz_device *dev = ds->priv; if (dev->irq > 0) { + ksz_ptp_clock_unregister(ds); ksz8463_ptp_irq_free(ds); ksz_irq_free(&dev->girq); } @@ -3129,9 +3143,9 @@ const struct dsa_switch_ops ksz8463_switch_ops = { .port_max_mtu = ksz88xx_max_mtu, .suspend = ksz_suspend, .resume = ksz_resume, - .get_ts_info = ksz_get_ts_info, + .get_ts_info = ksz8463_get_ts_info, .port_hwtstamp_get = ksz_hwtstamp_get, - .port_hwtstamp_set = ksz_hwtstamp_set, + .port_hwtstamp_set = ksz8463_hwtstamp_set, .port_txtstamp = ksz_port_txtstamp, .port_rxtstamp = ksz_port_rxtstamp, .port_setup_tc = ksz8_setup_tc, diff --git a/drivers/net/dsa/microchip/ksz8_reg.h b/drivers/net/dsa/microchip/ksz8_reg.h index 981ab441d9b7..6bc511da1f7d 100644 --- a/drivers/net/dsa/microchip/ksz8_reg.h +++ b/drivers/net/dsa/microchip/ksz8_reg.h @@ -786,6 +786,7 @@ #define KSZ8463_REG_SW_RESET 0x126 #define KSZ8463_GLOBAL_SOFTWARE_RESET BIT(0) +#define KSZ8463_PTP_SOFTWARE_RESET BIT(2) #define KSZ8463_PTP_CLK_CTRL 0x600 diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index 0f2abb22ca91..cbe98494578c 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -194,6 +194,7 @@ struct ksz_port { struct kernel_hwtstamp_config tstamp_config; bool hwts_tx_en; bool hwts_rx_en; + bool last_tx_is_pdelayresp; struct ksz_irq ptpirq; struct ksz_ptp_irq ptpmsg_irq[3]; ktime_t tstamp_msg; diff --git a/drivers/net/dsa/microchip/ksz_ptp.c b/drivers/net/dsa/microchip/ksz_ptp.c index febc3141f201..39cc70d65900 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.c +++ b/drivers/net/dsa/microchip/ksz_ptp.c @@ -297,6 +297,31 @@ static int ksz_ptp_enable_mode(struct ksz_device *dev) tag_en ? PTP_ENABLE : 0); } +int ksz8463_get_ts_info(struct dsa_switch *ds, int port, + struct kernel_ethtool_ts_info *ts) +{ + struct ksz_device *dev = ds->priv; + struct ksz_ptp_data *ptp_data; + + ptp_data = &dev->ptp_data; + + if (!ptp_data->clock) + return -ENODEV; + + ts->so_timestamping = SOF_TIMESTAMPING_TX_HARDWARE | + SOF_TIMESTAMPING_RX_HARDWARE | + SOF_TIMESTAMPING_RAW_HARDWARE; + + ts->tx_types = BIT(HWTSTAMP_TX_OFF) | BIT(HWTSTAMP_TX_ON); + + ts->rx_filters = BIT(HWTSTAMP_FILTER_NONE) | + BIT(HWTSTAMP_FILTER_PTP_V2_L2_EVENT); + + ts->phc_index = ptp_clock_index(ptp_data->clock); + + return 0; +} + /* The function is return back the capability of timestamping feature when * requested through ethtool -T utility */ @@ -341,6 +366,72 @@ int ksz_hwtstamp_get(struct dsa_switch *ds, int port, return 0; } +static int ksz8463_set_hwtstamp_config(struct ksz_device *dev, + struct ksz_port *prt, + struct kernel_hwtstamp_config *config) +{ + const u16 *regs = dev->info->regs; + int ret; + + if (config->flags) + return -EINVAL; + + switch (config->tx_type) { + case HWTSTAMP_TX_OFF: + prt->ptpmsg_irq[KSZ8463_SYNC_MSG].ts_en = false; + prt->ptpmsg_irq[KSZ8463_XDREQ_PDRES_MSG].ts_en = false; + prt->hwts_tx_en = false; + break; + case HWTSTAMP_TX_ON: + prt->ptpmsg_irq[KSZ8463_SYNC_MSG].ts_en = true; + prt->ptpmsg_irq[KSZ8463_XDREQ_PDRES_MSG].ts_en = true; + prt->hwts_tx_en = true; + + ret = ksz_rmw16(dev, regs[PTP_MSG_CONF1], PTP_1STEP, 0); + if (ret) + return ret; + + break; + default: + return -ERANGE; + } + + switch (config->rx_filter) { + case HWTSTAMP_FILTER_NONE: + prt->hwts_rx_en = false; + break; + case HWTSTAMP_FILTER_PTP_V2_L2_EVENT: + case HWTSTAMP_FILTER_PTP_V2_L2_SYNC: + config->rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT; + prt->hwts_rx_en = true; + break; + default: + config->rx_filter = HWTSTAMP_FILTER_NONE; + return -ERANGE; + } + + return ksz_ptp_enable_mode(dev); +} + +int ksz8463_hwtstamp_set(struct dsa_switch *ds, int port, + struct kernel_hwtstamp_config *config, + struct netlink_ext_ack *extack) +{ + struct ksz_device *dev = ds->priv; + struct ksz_port *prt; + int ret; + + prt = &dev->ports[port]; + + ret = ksz8463_set_hwtstamp_config(dev, prt, config); + if (ret) + return ret; + + prt->tstamp_config = *config; + + return 0; +} + static int ksz_set_hwtstamp_config(struct ksz_device *dev, struct ksz_port *prt, struct kernel_hwtstamp_config *config) @@ -571,6 +662,31 @@ static void ksz_ptp_txtstamp_skb(struct ksz_device *dev, skb_complete_tx_timestamp(skb, &hwtstamps); } +static void ksz8463_set_pdelayresp_flag(struct ksz_port *prt, + struct sk_buff *skb) +{ + struct ptp_header *hdr; + unsigned int type; + u8 ptp_msg_type; + + if (!ksz_is_ksz8463(prt->ksz_dev)) + return; + + if (skb_linearize(skb)) + return; + + type = ptp_classify_raw(skb); + if (type == PTP_CLASS_NONE) + return; + + hdr = ptp_parse_header(skb, type); + if (!hdr) + return; + + ptp_msg_type = ptp_get_msgtype(hdr, type); + prt->last_tx_is_pdelayresp = (ptp_msg_type == PTP_MSGTYPE_PDELAY_RESP); +} + void ksz_port_deferred_xmit(struct kthread_work *work) { struct ksz_deferred_xmit_work *xmit_work = work_to_xmit_work(work); @@ -587,6 +703,8 @@ void ksz_port_deferred_xmit(struct kthread_work *work) reinit_completion(&prt->tstamp_msg_comp); + ksz8463_set_pdelayresp_flag(prt, skb); + dsa_enqueue_skb(skb, skb->dev); ksz_ptp_txtstamp_skb(dev, prt, clone); @@ -979,7 +1097,22 @@ void ksz_ptp_clock_unregister(struct dsa_switch *ds) static int ksz_read_ts(struct ksz_port *port, u16 reg, u32 *ts) { - return ksz_read32(port->ksz_dev, reg, ts); + u16 ts_reg = reg; + + /** + * On KSZ8463 DREQ and DRESP timestamps share one interrupt line + * so we have to check the nature of the latest event sent to know + * where the timestamp is located + */ + if (ksz_is_ksz8463(port->ksz_dev)) { + const struct ksz_dev_ops *ops = port->ksz_dev->dev_ops; + + if (port->last_tx_is_pdelayresp && + ts_reg == ops->get_port_addr(port->num, KSZ8463_REG_PORT_DREQ_TS)) + ts_reg += KSZ8463_DRESP_TS_OFFSET; + } + + return ksz_read32(port->ksz_dev, ts_reg, ts); } static irqreturn_t ksz_ptp_msg_thread_fn(int irq, void *dev_id) diff --git a/drivers/net/dsa/microchip/ksz_ptp.h b/drivers/net/dsa/microchip/ksz_ptp.h index 11408580031d..7067ec9bd1e6 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.h +++ b/drivers/net/dsa/microchip/ksz_ptp.h @@ -39,11 +39,16 @@ void ksz_ptp_clock_unregister(struct dsa_switch *ds); int ksz_get_ts_info(struct dsa_switch *ds, int port, struct kernel_ethtool_ts_info *ts); +int ksz8463_get_ts_info(struct dsa_switch *ds, int port, + struct kernel_ethtool_ts_info *ts); int ksz_hwtstamp_get(struct dsa_switch *ds, int port, struct kernel_hwtstamp_config *config); int ksz_hwtstamp_set(struct dsa_switch *ds, int port, struct kernel_hwtstamp_config *config, struct netlink_ext_ack *extack); +int ksz8463_hwtstamp_set(struct dsa_switch *ds, int port, + struct kernel_hwtstamp_config *config, + struct netlink_ext_ack *extack); void ksz_port_txtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb); void ksz_port_deferred_xmit(struct kthread_work *work); bool ksz_port_rxtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb, @@ -82,10 +87,12 @@ static inline int ksz8463_ptp_irq_setup(struct dsa_switch *ds) static inline void ksz8463_ptp_irq_free(struct dsa_switch *ds) {} #define ksz_get_ts_info NULL +#define ksz8463_get_ts_info NULL #define ksz_hwtstamp_get NULL #define ksz_hwtstamp_set NULL +#define ksz8463_hwtstamp_set NULL #define ksz_port_rxtstamp NULL diff --git a/drivers/net/dsa/microchip/ksz_ptp_reg.h b/drivers/net/dsa/microchip/ksz_ptp_reg.h index 1a669d6ee889..65ea8577af75 100644 --- a/drivers/net/dsa/microchip/ksz_ptp_reg.h +++ b/drivers/net/dsa/microchip/ksz_ptp_reg.h @@ -137,4 +137,8 @@ #define KSZ_XDREQ_MSG 1 #define KSZ_PDRES_MSG 0 +#define KSZ8463_DRESP_TS_OFFSET (KSZ8463_REG_PORT_DRESP_TS - KSZ8463_REG_PORT_DREQ_TS) +#define KSZ8463_SYNC_MSG 0 +#define KSZ8463_XDREQ_PDRES_MSG 1 + #endif From 4d3839943bbb1f139e891c75e876115295bbcbf6 Mon Sep 17 00:00:00 2001 From: Zxyan Zhu Date: Wed, 29 Jul 2026 10:36:53 +0800 Subject: [PATCH 0820/1433] net: stmmac: dwxgmac2: configure INTM for per-channel interrupt routing The XGMAC DMA_MODE register has an INTM field (bits 13:12) that controls interrupt routing behavior for DMA transfer completion events: 00 (default): sbd_perch_* are pulse signals, sbd_intr_o is also asserted for each completion event. 01: sbd_perch_* are level signals, sbd_intr_o is NOT asserted for packet transfer completion events. When multi-MSI is enabled, per-channel TX/RX interrupts are expected to arrive on their dedicated lines. In the default INTM=00 mode, sbd_intr_o also fires for DMA completion events, but the multi-MSI handler stmmac_mac_interrupt() only processes MAC-layer events (LPI, PMT, timestamps) and returns IRQ_NONE for every DMA completion interrupt, resulting in a continuous stream of unhandled interrupts on the common IRQ. Hardware verification with XGMAC and multi-MSI enabled: INTM=00: 5.4 million common IRQ interrupts in 3 seconds, ~1.8 million IRQ_NONE returns per second. INTM=01: 0 common IRQ interrupts, per-channel IRQs work normally, 10G line rate works correctly. Set INTM to mode 1 when multi-MSI is enabled. This matches the existing GMAC4 implementation. XGMAC multi-MSI has never worked correctly since it was introduced. Signed-off-by: Zxyan Zhu Reviewed-by: Qingfang Deng Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260729023653.1162763-1-zxyan0222@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h | 2 ++ drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h index 61b6d45a02f5..f8ab347f7b5b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h @@ -320,6 +320,8 @@ /* DMA Registers */ #define XGMAC_DMA_MODE 0x00003000 #define XGMAC_SWR BIT(0) +#define XGMAC_INTM_MASK GENMASK(13, 12) +#define XGMAC_INTM_MODE1 0x1 #define XGMAC_DMA_SYSBUS_MODE 0x00003004 #define XGMAC_WR_OSR_LMT GENMASK(29, 24) #define XGMAC_RD_OSR_LMT GENMASK(21, 16) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c index 03437f1cf3df..ff83858ebc1f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c @@ -31,6 +31,16 @@ static void dwxgmac2_dma_init(void __iomem *ioaddr, value |= XGMAC_EAME; writel(value, ioaddr + XGMAC_DMA_SYSBUS_MODE); + + /* + * Route DMA interrupts to per-channel lines when multi-MSI enabled. + */ + if (dma_cfg->multi_msi_en) { + value = readl(ioaddr + XGMAC_DMA_MODE); + value = u32_replace_bits(value, XGMAC_INTM_MODE1, + XGMAC_INTM_MASK); + writel(value, ioaddr + XGMAC_DMA_MODE); + } } static void dwxgmac2_dma_init_chan(struct stmmac_priv *priv, From 1327e30657d283976c839f1bd24e2d3f68474097 Mon Sep 17 00:00:00 2001 From: Satha Rao Date: Mon, 27 Jul 2026 15:46:08 +0530 Subject: [PATCH 0821/1433] octeontx2-af: add new mbox to support sync cycle on rx path sync ensures that all packets that were in flight are flushed out to memory. This can be used to assist in the tearing down of an active RQ. To complete disabling RQs or disabling SMQ and its SQs, LF software send mbox to AF to complete RX_SW_SYNC. Both VF and PF and invoke this mbox. Signed-off-by: Satha Rao Signed-off-by: Ratheesh Kannoth Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260727101608.300290-1-rkannoth@marvell.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/marvell/octeontx2/af/mbox.h | 2 ++ .../ethernet/marvell/octeontx2/af/rvu_nix.c | 31 +++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h index 10552e9cf519..73f743e4a83d 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h +++ b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h @@ -362,6 +362,7 @@ M(NIX_CPT_BP_ENABLE, 0x8020, nix_cpt_bp_enable, nix_bp_cfg_req, \ nix_bp_cfg_rsp) \ M(NIX_CPT_BP_DISABLE, 0x8021, nix_cpt_bp_disable, nix_bp_cfg_req, \ msg_rsp) \ +M(NIX_RX_SW_SYNC, 0x8022, nix_rx_sw_sync, msg_req, msg_rsp) \ M(NIX_READ_INLINE_IPSEC_CFG, 0x8023, nix_read_inline_ipsec_cfg, \ msg_req, nix_inline_ipsec_cfg) \ M(NIX_MCAST_GRP_CREATE, 0x802b, nix_mcast_grp_create, nix_mcast_grp_create_req, \ @@ -952,6 +953,7 @@ enum nix_af_status { NIX_AF_ERR_INVALID_MCAST_GRP = -436, NIX_AF_ERR_INVALID_MCAST_DEL_REQ = -437, NIX_AF_ERR_NON_CONTIG_MCE_LIST = -438, + NIX_AF_ERR_RX_SW_SYNC_FAIL = -439, }; /* For NIX RX vtag action */ diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c index 94aa8e3acfd1..b81c47ea023b 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c @@ -269,15 +269,19 @@ u32 convert_bytes_to_dwrr_mtu(u32 bytes) return 0; } -static void nix_rx_sync(struct rvu *rvu, int blkaddr) +static int nix_rx_sync(struct rvu *rvu, int blkaddr) { int err; + mutex_lock(&rvu->rsrc_lock); + /* Sync all in flight RX packets to LLC/DRAM */ rvu_write64(rvu, blkaddr, NIX_AF_RX_SW_SYNC, BIT_ULL(0)); err = rvu_poll_reg(rvu, blkaddr, NIX_AF_RX_SW_SYNC, BIT_ULL(0), true); - if (err) + if (err) { dev_err(rvu->dev, "SYNC1: NIX RX software sync failed\n"); + goto unlock; + } /* SW_SYNC ensures all existing transactions are finished and pkts * are written to LLC/DRAM, queues should be teared down after @@ -289,6 +293,10 @@ static void nix_rx_sync(struct rvu *rvu, int blkaddr) err = rvu_poll_reg(rvu, blkaddr, NIX_AF_RX_SW_SYNC, BIT_ULL(0), true); if (err) dev_err(rvu->dev, "SYNC2: NIX RX software sync failed\n"); + +unlock: + mutex_unlock(&rvu->rsrc_lock); + return err; } static bool is_valid_txschq(struct rvu *rvu, int blkaddr, @@ -6350,6 +6358,25 @@ int rvu_mbox_handler_nix_bandprof_get_hwinfo(struct rvu *rvu, struct msg_req *re return 0; } +int rvu_mbox_handler_nix_rx_sw_sync(struct rvu *rvu, struct msg_req *req, + struct msg_rsp *rsp) +{ + int blkaddr, nixlf, err; + + /* NIX_AF_RX_SW_SYNC is global per NIX block; nix_rx_sync() serializes + * access under rvu->rsrc_lock across mbox and teardown paths. + */ + err = nix_get_nixlf(rvu, req->hdr.pcifunc, &nixlf, &blkaddr); + if (err) + return err; + + err = nix_rx_sync(rvu, blkaddr); + if (err) + return NIX_AF_ERR_RX_SW_SYNC_FAIL; + + return 0; +} + static struct nix_mcast_grp_elem *rvu_nix_mcast_find_grp_elem(struct nix_mcast_grp *mcast_grp, u32 mcast_grp_idx) { From 3b63c19e64f859f446c00da9d97e7a6cf79ee1ac Mon Sep 17 00:00:00 2001 From: Javen Xu Date: Tue, 28 Jul 2026 15:31:02 +0800 Subject: [PATCH 0822/1433] net: phy: c45: add genphy_c45_pma_soft_reset() Add a generic Clause 45 software reset helper. The helper sets the reset bit in the PMA/PMD control register and waits until the bit is cleared by hardware. Reviewed-by: Maxime Chevallier Reviewed-by: Nicolai Buchwitz Signed-off-by: Javen Xu Link: https://patch.msgid.link/20260728073106.1515-2-javen_xu@realsil.com.cn Signed-off-by: Jakub Kicinski --- drivers/net/phy/phy-c45.c | 22 ++++++++++++++++++++++ include/linux/phy.h | 1 + 2 files changed, 23 insertions(+) diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c index 126951741428..c1f817a59739 100644 --- a/drivers/net/phy/phy-c45.c +++ b/drivers/net/phy/phy-c45.c @@ -384,6 +384,28 @@ int genphy_c45_check_and_restart_aneg(struct phy_device *phydev, bool restart) } EXPORT_SYMBOL_GPL(genphy_c45_check_and_restart_aneg); +/** + * genphy_c45_pma_soft_reset - software reset the PHY via Clause 45 PMA/PMD control register + * @phydev: target phy_device struct + * + * Return: 0 on success, negative errno on failure. + */ +int genphy_c45_pma_soft_reset(struct phy_device *phydev) +{ + int ret, val; + + ret = phy_set_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1, + MDIO_CTRL1_RESET); + if (ret < 0) + return ret; + + return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_PMAPMD, + MDIO_CTRL1, val, + !(val & MDIO_CTRL1_RESET), + 5000, 600000, true); +} +EXPORT_SYMBOL_GPL(genphy_c45_pma_soft_reset); + /** * genphy_c45_aneg_done - return auto-negotiation complete status * @phydev: target phy_device struct diff --git a/include/linux/phy.h b/include/linux/phy.h index 11092c3175b3..5f8d65868e0f 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -2317,6 +2317,7 @@ int genphy_c37_read_status(struct phy_device *phydev, bool *changed); /* Clause 45 PHY */ int genphy_c45_restart_aneg(struct phy_device *phydev); int genphy_c45_check_and_restart_aneg(struct phy_device *phydev, bool restart); +int genphy_c45_pma_soft_reset(struct phy_device *phydev); int genphy_c45_aneg_done(struct phy_device *phydev); int genphy_c45_read_link(struct phy_device *phydev); int genphy_c45_read_lpa(struct phy_device *phydev); From d7722c03089e35bf579405e8ad7dba41800e39e7 Mon Sep 17 00:00:00 2001 From: Javen Xu Date: Tue, 28 Jul 2026 15:31:03 +0800 Subject: [PATCH 0823/1433] net: phy: c45: add setup and read master/slave helpers This patch adds two static helpers in drivers/net/phy/phy-c45.c to configure and read back master-slave roles for non BASE-T1 Clause 45 PHYs via the 10GBASE-T AN control/status registers. These helpers are wired into genphy_c45_config_aneg() and genphy_c45_read_status(). This changes the observable ethtool output for drivers using the generic c45 read path. Reviewed-by: Andrew Lunn Signed-off-by: Javen Xu Link: https://patch.msgid.link/20260728073106.1515-3-javen_xu@realsil.com.cn Signed-off-by: Jakub Kicinski --- drivers/net/phy/phy-c45.c | 103 ++++++++++++++++++++++++++++++++++++++ include/uapi/linux/mdio.h | 5 ++ 2 files changed, 108 insertions(+) diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c index c1f817a59739..870920311f9a 100644 --- a/drivers/net/phy/phy-c45.c +++ b/drivers/net/phy/phy-c45.c @@ -406,6 +406,97 @@ int genphy_c45_pma_soft_reset(struct phy_device *phydev) } EXPORT_SYMBOL_GPL(genphy_c45_pma_soft_reset); +/** + * genphy_c45_an_setup_master_slave - Configure Master/Slave setting for C45 PHYs + * @phydev: target phy_device struct + * + * Description: Configure the forced or preferred Master/Slave role + * 10GBASE-T control register (MMD 7, Register 0x0020) according to + * IEEE 802.3 standards. + * + * Return: negative errno code on failure, 0 if Master/Slave didn't change, + * or 1 if Master/Slave modes changed. + */ +static int genphy_c45_an_setup_master_slave(struct phy_device *phydev) +{ + u16 ctl = 0; + + switch (phydev->master_slave_set) { + case MASTER_SLAVE_CFG_MASTER_PREFERRED: + ctl = MDIO_AN_10GBT_CTRL_MS_PORT_TYPE; + break; + case MASTER_SLAVE_CFG_SLAVE_PREFERRED: + break; + case MASTER_SLAVE_CFG_MASTER_FORCE: + ctl = MDIO_AN_10GBT_CTRL_MS_ENABLE | MDIO_AN_10GBT_CTRL_MS_VALUE; + break; + case MASTER_SLAVE_CFG_SLAVE_FORCE: + ctl = MDIO_AN_10GBT_CTRL_MS_ENABLE; + break; + case MASTER_SLAVE_CFG_UNKNOWN: + case MASTER_SLAVE_CFG_UNSUPPORTED: + return 0; + default: + phydev_warn(phydev, "Unsupported Master/Slave mode\n"); + return -EOPNOTSUPP; + } + + return phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL, + MDIO_AN_10GBT_CTRL_MS_ENABLE | + MDIO_AN_10GBT_CTRL_MS_VALUE | + MDIO_AN_10GBT_CTRL_MS_PORT_TYPE, ctl); +} + +/** + * genphy_c45_read_master_slave - read master/slave status + * @phydev: target phy_device struct + * + * Description: Read the Master/Slave configuration and status + * from 10GBASE-T control/status registers (MMD 7, Reg 0x0020 and 0x0021). + * + * Return: 0 on success, or a negative error code on failure. + */ +static int genphy_c45_read_master_slave(struct phy_device *phydev) +{ + int val; + + phydev->master_slave_get = MASTER_SLAVE_CFG_UNKNOWN; + phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN; + + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL); + if (val < 0) + return val; + + if (val & MDIO_AN_10GBT_CTRL_MS_ENABLE) { + if (val & MDIO_AN_10GBT_CTRL_MS_VALUE) + phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_FORCE; + else + phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_FORCE; + } else { + if (val & MDIO_AN_10GBT_CTRL_MS_PORT_TYPE) + phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_PREFERRED; + else + phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_PREFERRED; + } + + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT); + if (val < 0) + return val; + + if (val & MDIO_AN_10GBT_STAT_MS_FAULT) { + phydev->master_slave_state = MASTER_SLAVE_STATE_ERR; + } else if (phydev->link) { + if (val & MDIO_AN_10GBT_STAT_MS_RES) + phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER; + else + phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE; + } else { + phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN; + } + + return 0; +} + /** * genphy_c45_aneg_done - return auto-negotiation complete status * @phydev: target phy_device struct @@ -1214,6 +1305,10 @@ int genphy_c45_read_status(struct phy_device *phydev) ret = genphy_c45_baset1_read_status(phydev); if (ret < 0) return ret; + } else { + ret = genphy_c45_read_master_slave(phydev); + if (ret < 0) + return ret; } phy_resolve_aneg_linkmode(phydev); @@ -1247,6 +1342,14 @@ int genphy_c45_config_aneg(struct phy_device *phydev) if (ret > 0) changed = true; + if (!genphy_c45_baset1_able(phydev)) { + ret = genphy_c45_an_setup_master_slave(phydev); + if (ret < 0) + return ret; + if (ret > 0) + changed = true; + } + return genphy_c45_check_and_restart_aneg(phydev, changed); } EXPORT_SYMBOL_GPL(genphy_c45_config_aneg); diff --git a/include/uapi/linux/mdio.h b/include/uapi/linux/mdio.h index b2541c948fc1..06f4bc3c20c7 100644 --- a/include/uapi/linux/mdio.h +++ b/include/uapi/linux/mdio.h @@ -332,8 +332,13 @@ #define MDIO_AN_10GBT_CTRL_ADV2_5G 0x0080 /* Advertise 2.5GBASE-T */ #define MDIO_AN_10GBT_CTRL_ADV5G 0x0100 /* Advertise 5GBASE-T */ #define MDIO_AN_10GBT_CTRL_ADV10G 0x1000 /* Advertise 10GBASE-T */ +#define MDIO_AN_10GBT_CTRL_MS_ENABLE 0x8000 /* Master/slave manual config enable */ +#define MDIO_AN_10GBT_CTRL_MS_VALUE 0x4000 /* Master/slave config value (1=Master) */ +#define MDIO_AN_10GBT_CTRL_MS_PORT_TYPE 0x2000 /* Master Preferred Type */ /* AN 10GBASE-T status register. */ +#define MDIO_AN_10GBT_STAT_MS_FAULT 0x8000 /* Master/slave fault */ +#define MDIO_AN_10GBT_STAT_MS_RES 0x4000 /* Master/slave resolution (1=Master) */ #define MDIO_AN_10GBT_STAT_LP2_5G 0x0020 /* LP is 2.5GBT capable */ #define MDIO_AN_10GBT_STAT_LP5G 0x0040 /* LP is 5GBT capable */ #define MDIO_AN_10GBT_STAT_LPTRR 0x0200 /* LP training reset req. */ From b772b5ae4537fc4c1a3d5a233a85a6fcdd073185 Mon Sep 17 00:00:00 2001 From: Javen Xu Date: Tue, 28 Jul 2026 15:31:04 +0800 Subject: [PATCH 0824/1433] net: phy: realtek: add support for RTL8261C_CG This patch adds support for Realtek phy chip RTL8261C_CG. Its PHY ID is 0x001cc898. This patch introduces a distinct family of handlers (probe, get_features, config_aneg, read_status, config_intr, handle_interrupt). Reviewed-by: Andrew Lunn Reviewed-by: Nicolai Buchwitz Signed-off-by: Javen Xu Link: https://patch.msgid.link/20260728073106.1515-4-javen_xu@realsil.com.cn Signed-off-by: Jakub Kicinski --- drivers/net/phy/realtek/realtek_main.c | 192 +++++++++++++++++++++++++ 1 file changed, 192 insertions(+) diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index b65d0f5fa1a0..e09bff76e1dc 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -141,6 +141,10 @@ #define RTL8211F_PHYSICAL_ADDR_WORD1 17 #define RTL8211F_PHYSICAL_ADDR_WORD2 18 +#define RTL8261X_EXT_ADDR_REG 0xa436 +#define RTL8261X_EXT_DATA_REG 0xa438 +#define RTL_8261X_SUB_PHY_ID_ADDR 0x801d + #define RTL822X_VND1_SERDES_OPTION 0x697a #define RTL822X_VND1_SERDES_OPTION_MODE_MASK GENMASK(5, 0) #define RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX_SGMII 0 @@ -251,6 +255,32 @@ #define RTL_8221B_VM_CG 0x001cc84a #define RTL_8251B 0x001cc862 #define RTL_8261C 0x001cc890 +#define RTL_8261C_CG 0x001cc898 + +#define RTL8261C_CE_MODEL 0x00 +#define RTL8261X_INT_AUTONEG_ERROR BIT(0) +#define RTL8261X_INT_PAGE_RECV BIT(2) +#define RTL8261X_INT_AUTONEG_DONE BIT(3) +#define RTL8261X_INT_LINK_CHG BIT(4) +#define RTL8261X_INT_PHY_REG_ACCESS BIT(5) +#define RTL8261X_INT_PME BIT(7) +#define RTL8261X_INT_ALDPS_CHG BIT(9) +#define RTL8261X_INT_JABBER BIT(10) + +#define RTL8261X_INT_MASK_DEFAULT (RTL8261X_INT_AUTONEG_DONE | \ + RTL8261X_INT_LINK_CHG | \ + RTL8261X_INT_AUTONEG_ERROR | \ + RTL8261X_INT_JABBER) + +#define RTL8261X_INT_MASK_ALL (RTL8261X_INT_AUTONEG_ERROR | \ + RTL8261X_INT_PAGE_RECV | \ + RTL8261X_INT_AUTONEG_DONE | \ + RTL8261X_INT_LINK_CHG | \ + RTL8261X_INT_PHY_REG_ACCESS | \ + RTL8261X_INT_PME | \ + RTL8261X_INT_ALDPS_CHG | \ + RTL8261X_INT_JABBER) + /* RTL8211E and RTL8211F support up to three LEDs */ #define RTL8211x_LED_COUNT 3 @@ -310,6 +340,156 @@ static int rtl821x_modify_ext_page(struct phy_device *phydev, u16 ext_page, return phy_restore_page(phydev, oldpage, ret); } +static int rtl8261x_probe(struct phy_device *phydev) +{ + int sub_phy_id, ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_EXT_ADDR_REG, + RTL_8261X_SUB_PHY_ID_ADDR); + if (ret < 0) + return ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_EXT_DATA_REG); + if (ret < 0) + return ret; + + sub_phy_id = (ret >> 8) & 0xff; + + switch (sub_phy_id) { + case RTL8261C_CE_MODEL: + phydev_info(phydev, "RTL8261C detected (sub_id 0x%02x)\n", sub_phy_id); + break; + + default: + phydev_warn(phydev, "Unknown sub_id 0x%02x, default behavior\n", sub_phy_id); + return -ENODEV; + } + + return 0; +} + +static int rtl8261x_get_features(struct phy_device *phydev) +{ + int ret; + + ret = genphy_c45_pma_read_abilities(phydev); + if (ret) + return ret; + /* + * Supplement Multi-Gig speeds that may not be automatically detected + * RTL8261X supports 2.5G/5G in addition to standard 10G + */ + linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, + phydev->supported); + linkmode_set_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT, + phydev->supported); + + return 0; +} + +static int rtl8261x_read_status(struct phy_device *phydev) +{ + int ret, val = 0; + + if (phydev->autoneg == AUTONEG_ENABLE) { + ret = genphy_c45_aneg_done(phydev); + if (ret < 0) + return ret; + + if (ret) { + val = phy_read_mmd(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_C22_REG(MII_STAT1000)); + if (val < 0) + return val; + } + } + + mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, val); + + ret = genphy_c45_read_status(phydev); + if (ret < 0) + return ret; + + return 0; +} + +static int rtl8261x_config_intr(struct phy_device *phydev) +{ + int ret; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INER, + RTL8261X_INT_MASK_DEFAULT); + if (ret < 0) + return ret; + } else { + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INER, 0); + if (ret < 0) + return ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR); + if (ret < 0) + return ret; + } + + return 0; +} + +static irqreturn_t rtl8261x_handle_interrupt(struct phy_device *phydev) +{ + int irq_status; + + irq_status = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR); + if (irq_status < 0) { + phy_error(phydev); + return IRQ_NONE; + } + + if (!(irq_status & RTL8261X_INT_MASK_ALL)) + return IRQ_NONE; + + if (irq_status & (RTL8261X_INT_LINK_CHG | RTL8261X_INT_AUTONEG_DONE | + RTL8261X_INT_AUTONEG_ERROR | RTL8261X_INT_JABBER)) + phy_trigger_machine(phydev); + + return IRQ_HANDLED; +} + +static int rtl8261x_config_aneg(struct phy_device *phydev) +{ + u16 adv_1g = 0; + int ret; + + ret = genphy_c45_config_aneg(phydev); + if (ret < 0) + return ret; + + if (phydev->autoneg == AUTONEG_DISABLE) + return 0; + + if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, + phydev->advertising)) + adv_1g = ADVERTISE_1000FULL; + if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, + phydev->advertising)) + adv_1g |= ADVERTISE_1000HALF; + + ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_C22_REG(MII_CTRL1000), + ADVERTISE_1000FULL | ADVERTISE_1000HALF, + adv_1g); + if (ret < 0) + return ret; + if (ret > 0) + return genphy_c45_restart_aneg(phydev); + + return 0; +} + static int rtl821x_probe(struct phy_device *phydev) { struct device *dev = &phydev->mdio.dev; @@ -3002,6 +3182,18 @@ static struct phy_driver realtek_drvs[] = { .resume = genphy_resume, .read_mmd = genphy_read_mmd_unsupported, .write_mmd = genphy_write_mmd_unsupported, + }, { + PHY_ID_MATCH_EXACT(RTL_8261C_CG), + .name = "Realtek RTL8261C 10Gbps PHY", + .probe = rtl8261x_probe, + .get_features = rtl8261x_get_features, + .config_aneg = rtl8261x_config_aneg, + .read_status = rtl8261x_read_status, + .config_intr = rtl8261x_config_intr, + .handle_interrupt = rtl8261x_handle_interrupt, + .soft_reset = genphy_c45_pma_soft_reset, + .suspend = genphy_c45_pma_suspend, + .resume = genphy_c45_pma_resume, }, }; From a04171a1cf326092b2d769efeb4ea5a3f9ad7ff4 Mon Sep 17 00:00:00 2001 From: Javen Xu Date: Tue, 28 Jul 2026 15:31:05 +0800 Subject: [PATCH 0825/1433] net: phy: realtek: load firmware for RTL8261C_CG This patch adds support for loading firmware. Download some parameters for RTL8261C_CG. Signed-off-by: Javen Xu Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260728073106.1515-5-javen_xu@realsil.com.cn Signed-off-by: Jakub Kicinski --- drivers/net/phy/realtek/realtek_main.c | 214 +++++++++++++++++++++++++ 1 file changed, 214 insertions(+) diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index e09bff76e1dc..ba218559f39c 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -8,7 +8,9 @@ * Copyright (c) 2004 Freescale Semiconductor, Inc. */ #include +#include #include +#include #include #include #include @@ -281,6 +283,43 @@ RTL8261X_INT_ALDPS_CHG | \ RTL8261X_INT_JABBER) +#define FW_MAIN_MAGIC 0x52544C38 +#define FW_SUB_MAGIC_8261C 0x32363143 +#define RTL8261X_POLL_TIMEOUT_MS 100 +#define RTL8261X_MAX_MMD_DEV 31 + +#define RTL8261C_CE_FW_NAME "rtl_nic/rtl8261c.bin" +MODULE_FIRMWARE(RTL8261C_CE_FW_NAME); + +enum rtl8261x_fw_op { + OP_WRITE = 0x00, /* Write */ + OP_POLL = 0x02, /* Polling */ +}; + +struct rtl8261x_fw_header { + __le32 main_magic; /* Main magic number */ + __le32 sub_magic; /* Sub magic number */ + __le16 version_major; /* Major version */ + __le16 version_minor; /* Minor version */ + __le16 num_entries; /* Number of entries */ + __le16 reserved; /* Reserved */ + __le32 crc32; /* CRC32 checksum */ +}; + +struct rtl8261x_fw_entry { + __u8 type; /* Operation type (OP_*) */ + __u8 dev; /* MMD device */ + __le16 addr; /* Register address */ + __u8 msb; /* MSB bit position */ + __u8 lsb; /* LSB bit position */ + __le16 value; /* Value to write/compare */ + __le16 timeout_ms; /* Poll timeout in milliseconds */ + __u8 poll_set; /* Poll until equal (1) or not equal (0) */ + __u8 reserved; /* Reserved */ +}; + +#define FW_HEADER_SIZE sizeof(struct rtl8261x_fw_header) +#define FW_ENTRY_SIZE sizeof(struct rtl8261x_fw_entry) /* RTL8211E and RTL8211F support up to three LEDs */ #define RTL8211x_LED_COUNT 3 @@ -300,6 +339,11 @@ struct rtl821x_priv { u16 iner; }; +struct rtl8261x_priv { + const char *fw_name; + bool fw_loaded; +}; + static int rtl821x_read_page(struct phy_device *phydev) { return __phy_read(phydev, RTL821x_PAGE_SELECT); @@ -342,8 +386,16 @@ static int rtl821x_modify_ext_page(struct phy_device *phydev, u16 ext_page, static int rtl8261x_probe(struct phy_device *phydev) { + struct device *dev = &phydev->mdio.dev; + struct rtl8261x_priv *priv; int sub_phy_id, ret; + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + phydev->priv = priv; + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_EXT_ADDR_REG, RTL_8261X_SUB_PHY_ID_ADDR); if (ret < 0) @@ -357,6 +409,7 @@ static int rtl8261x_probe(struct phy_device *phydev) switch (sub_phy_id) { case RTL8261C_CE_MODEL: + priv->fw_name = RTL8261C_CE_FW_NAME; phydev_info(phydev, "RTL8261C detected (sub_id 0x%02x)\n", sub_phy_id); break; @@ -413,6 +466,152 @@ static int rtl8261x_read_status(struct phy_device *phydev) return 0; } +static int rtl8261x_verify_firmware(struct phy_device *phydev, const struct firmware *fw) +{ + const struct rtl8261x_fw_header *hdr; + u32 main_magic, sub_magic; + u32 calc_crc, file_crc; + size_t data_len; + u16 num_entries; + + if (fw->size < FW_HEADER_SIZE) { + phydev_err(phydev, "Firmware too small: %zu bytes\n", fw->size); + return -EINVAL; + } + + hdr = (const struct rtl8261x_fw_header *)fw->data; + + main_magic = le32_to_cpu(hdr->main_magic); + if (main_magic != FW_MAIN_MAGIC) { + phydev_err(phydev, "Invalid firmware magic: 0x%08x\n", main_magic); + return -EINVAL; + } + + sub_magic = le32_to_cpu(hdr->sub_magic); + if (sub_magic != FW_SUB_MAGIC_8261C) { + phydev_err(phydev, "Invalid sub magic: 0x%08x\n", sub_magic); + return -EINVAL; + } + + num_entries = le16_to_cpu(hdr->num_entries); + data_len = num_entries * FW_ENTRY_SIZE; + + if (fw->size != sizeof(*hdr) + data_len) { + phydev_err(phydev, "Firmware size mismatch\n"); + return -EINVAL; + } + + calc_crc = crc32(~0, fw->data + FW_HEADER_SIZE, data_len) ^ ~0; + file_crc = le32_to_cpu(hdr->crc32); + + if (calc_crc != file_crc) { + phydev_err(phydev, "CRC32 mismatch: calculated=0x%08x file=0x%08x\n", + calc_crc, file_crc); + return -EINVAL; + } + + return 0; +} + +static int rtl8261x_fw_execute_entry(struct phy_device *phydev, + const struct rtl8261x_fw_entry *entry) +{ + u16 addr, value, timeout_ms; + u8 dev, msb, lsb, poll_set; + u32 bits, expect_val; + int ret, val; + + dev = entry->dev; + addr = le16_to_cpu(entry->addr); + msb = entry->msb; + lsb = entry->lsb; + value = le16_to_cpu(entry->value); + timeout_ms = le16_to_cpu(entry->timeout_ms); + poll_set = entry->poll_set; + + if (timeout_ms == 0) + timeout_ms = RTL8261X_POLL_TIMEOUT_MS; + + if (dev > RTL8261X_MAX_MMD_DEV) { + phydev_err(phydev, "invalid firmware MMD device: dev=%u\n", dev); + return -EINVAL; + } + + if (msb > 15 || lsb > msb) { + phydev_err(phydev, "invalid firmware bits: msb=%u, lsb=%u\n", msb, lsb); + return -EINVAL; + } + + switch (entry->type) { + case OP_WRITE: + ret = phy_modify_mmd(phydev, dev, addr, + GENMASK(msb, lsb), (value << lsb) & GENMASK(msb, lsb)); + if (ret) + return ret; + break; + + case OP_POLL: + bits = GENMASK(msb, lsb); + expect_val = (value << lsb) & bits; + + if (poll_set) + ret = phy_read_mmd_poll_timeout(phydev, dev, addr, val, + (val & bits) == expect_val, + 1000, timeout_ms * 1000, false); + else + ret = phy_read_mmd_poll_timeout(phydev, dev, addr, val, + (val & bits) != expect_val, + 1000, timeout_ms * 1000, false); + if (ret) + return ret; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int rtl8261x_fw_load(struct phy_device *phydev) +{ + struct rtl8261x_priv *priv = phydev->priv; + const struct rtl8261x_fw_entry *entry; + const struct rtl8261x_fw_header *hdr; + const struct firmware *fw; + int ret, i; + + if (!priv->fw_name) + return 0; + + ret = request_firmware(&fw, priv->fw_name, &phydev->mdio.dev); + if (ret) { + phydev_err(phydev, "Failed to load firmware %s: %d\n", priv->fw_name, ret); + return ret; + } + + ret = rtl8261x_verify_firmware(phydev, fw); + if (ret) + goto release_fw; + + hdr = (const struct rtl8261x_fw_header *)fw->data; + + entry = (const struct rtl8261x_fw_entry *)(fw->data + FW_HEADER_SIZE); + for (i = 0; i < le16_to_cpu(hdr->num_entries); i++, entry++) { + ret = rtl8261x_fw_execute_entry(phydev, entry); + if (ret) { + phydev_err(phydev, "Entry %d failed: %d\n", i, ret); + goto release_fw; + } + } + + priv->fw_loaded = true; + +release_fw: + release_firmware(fw); + return ret; +} + static int rtl8261x_config_intr(struct phy_device *phydev) { int ret; @@ -490,6 +689,20 @@ static int rtl8261x_config_aneg(struct phy_device *phydev) return 0; } +static int rtl8261x_config_init(struct phy_device *phydev) +{ + struct rtl8261x_priv *priv = phydev->priv; + + /* The firmware parameters are preserved across IEEE soft resets and + * suspend/resume cycles. Reloading is only necessary after a power + * cycle or hard reset. + */ + if (priv->fw_name && !priv->fw_loaded) + return rtl8261x_fw_load(phydev); + + return 0; +} + static int rtl821x_probe(struct phy_device *phydev) { struct device *dev = &phydev->mdio.dev; @@ -3186,6 +3399,7 @@ static struct phy_driver realtek_drvs[] = { PHY_ID_MATCH_EXACT(RTL_8261C_CG), .name = "Realtek RTL8261C 10Gbps PHY", .probe = rtl8261x_probe, + .config_init = rtl8261x_config_init, .get_features = rtl8261x_get_features, .config_aneg = rtl8261x_config_aneg, .read_status = rtl8261x_read_status, From f0667918d5fcaf13c9016a94442726832ff29dd0 Mon Sep 17 00:00:00 2001 From: Javen Xu Date: Tue, 28 Jul 2026 15:31:06 +0800 Subject: [PATCH 0826/1433] net: phy: realtek: add support for RTL8261D RTL8261D is also 10g phy. It's sub_phy_id is 0x81. And it does not need any firmware. Reviewed-by: Maxime Chevallier Signed-off-by: Javen Xu Link: https://patch.msgid.link/20260728073106.1515-6-javen_xu@realsil.com.cn Signed-off-by: Jakub Kicinski --- drivers/net/phy/realtek/realtek_main.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index ba218559f39c..a0a79192384e 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -260,6 +260,7 @@ #define RTL_8261C_CG 0x001cc898 #define RTL8261C_CE_MODEL 0x00 +#define RTL8261D_MODEL 0x81 #define RTL8261X_INT_AUTONEG_ERROR BIT(0) #define RTL8261X_INT_PAGE_RECV BIT(2) #define RTL8261X_INT_AUTONEG_DONE BIT(3) @@ -413,6 +414,10 @@ static int rtl8261x_probe(struct phy_device *phydev) phydev_info(phydev, "RTL8261C detected (sub_id 0x%02x)\n", sub_phy_id); break; + case RTL8261D_MODEL: + phydev_info(phydev, "RTL8261D detected (sub_id 0x%02x)\n", sub_phy_id); + break; + default: phydev_warn(phydev, "Unknown sub_id 0x%02x, default behavior\n", sub_phy_id); return -ENODEV; @@ -3397,7 +3402,7 @@ static struct phy_driver realtek_drvs[] = { .write_mmd = genphy_write_mmd_unsupported, }, { PHY_ID_MATCH_EXACT(RTL_8261C_CG), - .name = "Realtek RTL8261C 10Gbps PHY", + .name = "Realtek RTL8261C/D 10Gbps PHY", .probe = rtl8261x_probe, .config_init = rtl8261x_config_init, .get_features = rtl8261x_get_features, From 490d81fd0cc1376ceb660ed2af602ad1bfe1ac77 Mon Sep 17 00:00:00 2001 From: Peter Chiu Date: Fri, 24 Jul 2026 12:48:00 +0000 Subject: [PATCH 0827/1433] wifi: mt76: mt7996: leave PS when a 4-address connection is established Because 4 address non-AMSDU packets do not have a bssid field, the hardware cannot get the bssid. Without the bssid, stations are not able to leave PS mode due to HW design. Wake up non-setup links when 4-address mode is established to prevent this issue. mt7992 and mt7990 handle this via the BSSID mapping band config instead, so restrict the command to mt7996. Signed-off-by: Peter Chiu Link: https://patch.msgid.link/20260724124813.3961474-16-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt76_connac_mcu.h | 1 + .../net/wireless/mediatek/mt76/mt7996/main.c | 8 ++++++++ .../net/wireless/mediatek/mt76/mt7996/mcu.c | 18 ++++++++++++++++++ .../net/wireless/mediatek/mt76/mt7996/mcu.h | 6 ++++++ .../net/wireless/mediatek/mt76/mt7996/mt7996.h | 2 ++ 5 files changed, 35 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h index 8198efc6c05d..51380849d24e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h @@ -867,6 +867,7 @@ enum { STA_REC_HDRT = 0x28, STA_REC_EML_OP = 0x29, STA_REC_HDR_TRANS = 0x2B, + STA_REC_PS_LEAVE = 0x45, STA_REC_MAX_NUM }; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 57afe4e81666..f92bfe5d6197 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -2027,6 +2027,14 @@ static void mt7996_sta_set_4addr(struct ieee80211_hw *hw, continue; mt7996_mcu_wtbl_update_hdr_trans(dev, vif, link, msta_link); + + /* HW cannot derive the BSSID from 4-address non-AMSDU frames, + * so PS exit is missed on links other than the setup link. + * Let the firmware wake those links instead. + */ + if (enabled && msta->deflink_id != link_id && + is_mt7996(&dev->mt76)) + mt7996_mcu_ps_leave(dev, link, msta_link); } mutex_unlock(&dev->mt76.mutex); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 6a775e682f74..24ce87d928e8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -5191,6 +5191,24 @@ int mt7996_mcu_wtbl_update_hdr_trans(struct mt7996_dev *dev, MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true); } +int mt7996_mcu_ps_leave(struct mt7996_dev *dev, struct mt7996_vif_link *link, + struct mt7996_sta_link *msta_link) +{ + struct sk_buff *skb; + + skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &link->mt76, + &msta_link->wcid, + MT7996_STA_UPDATE_MAX_SIZE); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + mt76_connac_mcu_add_tlv(skb, STA_REC_PS_LEAVE, + sizeof(struct sta_rec_ps_leave)); + + return mt76_mcu_skb_send_msg(&dev->mt76, skb, + MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true); +} + int mt7996_mcu_set_fixed_rate_table(struct mt7996_phy *phy, u8 table_idx, u16 rate_idx, bool beacon) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index b53a9e71c281..1487d33a8dbb 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -680,6 +680,12 @@ struct sta_rec_hdr_trans { u8 mesh; } __packed; +struct sta_rec_ps_leave { + __le16 tag; + __le16 len; + u8 __rsv[4]; +} __packed; + struct sta_rec_mld_setup { __le16 tag; __le16 len; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index dcacc7d06e85..2a0cdb56f822 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -928,6 +928,8 @@ int mt7996_mcu_wtbl_update_hdr_trans(struct mt7996_dev *dev, struct ieee80211_vif *vif, struct mt7996_vif_link *link, struct mt7996_sta_link *msta_link); +int mt7996_mcu_ps_leave(struct mt7996_dev *dev, struct mt7996_vif_link *link, + struct mt7996_sta_link *msta_link); int mt7996_mcu_cp_support(struct mt7996_dev *dev, u8 mode); int mt7996_mcu_set_emlsr_mode(struct mt7996_dev *dev, struct ieee80211_vif *vif, From 16a04441eab0dcd4d7126a6f66b370adbf28f96d Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 12:48:01 +0000 Subject: [PATCH 0828/1433] wifi: mt76: mt7915: unlink TWT flow if the MCU rejects the agreement The flow is added to dev->twt_list before sending the agreement to the firmware, but the error path leaves it linked while flowid_mask is never set. The flow slot can then be reused and memset while still on the list, corrupting twt_list, and station removal leaves a dangling entry behind that mt7915_mac_twt_sched_list_add() later walks. Fixes: 3782b69d03e7 ("mt76: mt7915: introduce mt7915_mac_add_twt_setup routine") Link: https://patch.msgid.link/20260724124813.3961474-17-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/mac.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c index 1154d3c4b811..ebe1376ffc2c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c @@ -2350,8 +2350,10 @@ void mt7915_mac_add_twt_setup(struct ieee80211_hw *hw, } flow->tsf = le64_to_cpu(twt_agrt->twt); - if (mt7915_mcu_twt_agrt_update(dev, msta->vif, flow, MCU_TWT_AGRT_ADD)) + if (mt7915_mcu_twt_agrt_update(dev, msta->vif, flow, MCU_TWT_AGRT_ADD)) { + list_del(&flow->list); goto unlock; + } setup_cmd = TWT_SETUP_CMD_ACCEPT; dev->twt.table_mask |= BIT(table_id); From ccb4bda277999959bc852480d8684b13b926e657 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 12:48:02 +0000 Subject: [PATCH 0829/1433] wifi: mt76: mt7996: skip key upload when adding an offchannel link No hw keys are ever uploaded for scanning/roc links and the link remove path already skips the key iteration for them. The add path still runs it, and since mt7996_set_hw_key() resolves the target through mvif->link[link_id] rather than the offchannel link, starting a scan on another band re-uploads the group keys of the link sharing the same link_id, re-sending its BSS cipher info and, for BIGTK with beacon protection on an AP link, toggling its beacons off and on. Skip the key iteration for offchannel links, mirroring the remove path. Fixes: 69d54ce7491d ("wifi: mt76: mt7996: switch to single multi-radio wiphy") Link: https://patch.msgid.link/20260724124813.3961474-18-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index f92bfe5d6197..3d30b9702a8f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -372,7 +372,8 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, CONN_STATE_PORT_SECURE, true); rcu_assign_pointer(dev->mt76.wcid[idx], &msta_link->wcid); - ieee80211_iter_keys(mphy->hw, vif, mt7996_key_iter, &it); + if (!mlink->wcid->offchannel) + ieee80211_iter_keys(mphy->hw, vif, mt7996_key_iter, &it); if (!mlink->wcid->offchannel) { if (vif->txq && From 6f8d8c458010b597bf4114e6fb3162bff7050041 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 12:48:03 +0000 Subject: [PATCH 0830/1433] wifi: mt76: mt7996: wake MCU waiters before aborting scan in L1 SER The L1 reset path calls mt76_abort_scan() between setting MT76_MCU_RESET and waking mcu.wait. A scan work blocked on an in-flight MCU command does not re-evaluate its wait condition until woken, so the cancel_delayed_work_sync() inside the abort sleeps out the full MCU timeout before recovery can proceed, adding several seconds of SER latency. mt7996_mac_full_reset() and the mt7915 counterpart already order the wake-up first. Wake mcu.wait immediately after setting MT76_MCU_RESET so in-flight commands bail out before the abort synchronises against them. Fixes: b36d55610215 ("wifi: mt76: abort scan/roc on hw restart") Link: https://patch.msgid.link/20260724124813.3961474-19-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 4449dde33f4e..96589aea1548 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2567,8 +2567,8 @@ void mt7996_mac_reset_work(struct work_struct *work) set_bit(MT76_RESET, &dev->mphy.state); set_bit(MT76_MCU_RESET, &dev->mphy.state); - mt76_abort_scan(&dev->mt76); wake_up(&dev->mt76.mcu.wait); + mt76_abort_scan(&dev->mt76); cancel_work_sync(&dev->wed_rro.work); mt7996_for_each_phy(dev, phy) { From 1fdf2b5958ee0ae9c6f1006338fb8f46d44478fc Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 12:48:04 +0000 Subject: [PATCH 0831/1433] wifi: mt76: fix queue reg access when building WED and NPU together Move the offload specific parts of Q_READ/Q_WRITE into mt76_dma_handle_read/write, which return false to fall back to readl/writel. The WED and NPU #ifdefs now live inside those helpers instead of selecting between mutually exclusive macro definitions, so a kernel with both enabled supports both at runtime. Also fixes the NPU path dereferencing a hardcoded q instead of the macro argument. Link: https://patch.msgid.link/20260724124813.3961474-20-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.h | 110 ++++++++++++----------- 1 file changed, 57 insertions(+), 53 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/dma.h b/drivers/net/wireless/mediatek/mt76/dma.h index a2cf82cfdaaa..7694764febe7 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.h +++ b/drivers/net/wireless/mediatek/mt76/dma.h @@ -5,6 +5,8 @@ #ifndef __MT76_DMA_H #define __MT76_DMA_H +#include + #define DMA_DUMMY_DATA ((void *)~0) #define MT_RING_SIZE 0x10 @@ -46,73 +48,75 @@ #define MT_FCE_INFO_LEN 4 #define MT_RX_RXWI_LEN 32 +static inline bool +mt76_dma_handle_read(struct mt76_queue *q, u32 offset, u32 *val) +{ #if IS_ENABLED(CONFIG_NET_MEDIATEK_SOC_WED) + if (q->flags & MT_QFLAG_WED) { + *val = mtk_wed_device_reg_read(q->wed, q->wed_regs + offset); + + return true; + } +#endif +#if IS_ENABLED(CONFIG_MT76_NPU) + if (q->flags & MT_QFLAG_NPU) { + struct airoha_npu *npu; + + *val = 0; + rcu_read_lock(); + npu = rcu_dereference(q->dev->mmio.npu); + if (npu) + regmap_read(npu->regmap, q->wed_regs + offset, val); + rcu_read_unlock(); + + return true; + } +#endif + + return false; +} + +static inline bool +mt76_dma_handle_write(struct mt76_queue *q, u32 offset, u32 val) +{ +#if IS_ENABLED(CONFIG_NET_MEDIATEK_SOC_WED) + if (q->flags & MT_QFLAG_WED) { + mtk_wed_device_reg_write(q->wed, q->wed_regs + offset, val); + + return true; + } +#endif +#if IS_ENABLED(CONFIG_MT76_NPU) + if (q->flags & MT_QFLAG_NPU) { + struct airoha_npu *npu; + + rcu_read_lock(); + npu = rcu_dereference(q->dev->mmio.npu); + if (npu) + regmap_write(npu->regmap, q->wed_regs + offset, val); + rcu_read_unlock(); + + return true; + } +#endif + + return false; +} #define Q_READ(_q, _field) ({ \ u32 _offset = offsetof(struct mt76_queue_regs, _field); \ u32 _val; \ - if ((_q)->flags & MT_QFLAG_WED) \ - _val = mtk_wed_device_reg_read((_q)->wed, \ - ((_q)->wed_regs + \ - _offset)); \ - else \ + if (!mt76_dma_handle_read(_q, _offset, &_val)) \ _val = readl(&(_q)->regs->_field); \ _val; \ }) #define Q_WRITE(_q, _field, _val) do { \ u32 _offset = offsetof(struct mt76_queue_regs, _field); \ - if ((_q)->flags & MT_QFLAG_WED) \ - mtk_wed_device_reg_write((_q)->wed, \ - ((_q)->wed_regs + _offset), \ - _val); \ - else \ + if (!mt76_dma_handle_write(_q, _offset, _val)) \ writel(_val, &(_q)->regs->_field); \ } while (0) -#elif IS_ENABLED(CONFIG_MT76_NPU) - -#define Q_READ(_q, _field) ({ \ - u32 _offset = offsetof(struct mt76_queue_regs, _field); \ - u32 _val = 0; \ - if ((_q)->flags & MT_QFLAG_NPU) { \ - struct airoha_npu *npu; \ - \ - rcu_read_lock(); \ - npu = rcu_dereference(q->dev->mmio.npu); \ - if (npu) \ - regmap_read(npu->regmap, \ - ((_q)->wed_regs + _offset), &_val); \ - rcu_read_unlock(); \ - } else { \ - _val = readl(&(_q)->regs->_field); \ - } \ - _val; \ -}) - -#define Q_WRITE(_q, _field, _val) do { \ - u32 _offset = offsetof(struct mt76_queue_regs, _field); \ - if ((_q)->flags & MT_QFLAG_NPU) { \ - struct airoha_npu *npu; \ - \ - rcu_read_lock(); \ - npu = rcu_dereference(q->dev->mmio.npu); \ - if (npu) \ - regmap_write(npu->regmap, \ - ((_q)->wed_regs + _offset), _val); \ - rcu_read_unlock(); \ - } else { \ - writel(_val, &(_q)->regs->_field); \ - } \ -} while (0) - -#else - -#define Q_READ(_q, _field) readl(&(_q)->regs->_field) -#define Q_WRITE(_q, _field, _val) writel(_val, &(_q)->regs->_field) - -#endif - struct mt76_desc { __le32 buf0; __le32 ctrl; From 54caf306b28e48054ee9edef711ac4ab689c967d Mon Sep 17 00:00:00 2001 From: Chad Monroe Date: Fri, 24 Jul 2026 12:48:05 +0000 Subject: [PATCH 0832/1433] wifi: mt76: mt7996: select net_setup_tc handler at runtime The net_setup_tc callback is chosen at compile time and the WED handler shadows the NPU one when CONFIG_NET_MEDIATEK_SOC_WED is enabled. mt76_wed_net_setup_tc() returns -EOPNOTSUPP without an active WED device, so on boards using the Airoha NPU with a WED enabled kernel the tc offload block is never bound and PPE flow offload for wireless traffic is silently disabled. Dispatch on the active offload backend instead: use the WED handler when a WED device is attached and fall back to the NPU handler otherwise. Builds without MT76_NPU keep the old behavior through the mt76_npu_net_setup_tc() stub. Signed-off-by: Chad Monroe Link: https://patch.msgid.link/20260724124813.3961474-21-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 3d30b9702a8f..21a928431c25 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -2443,6 +2443,23 @@ mt7996_net_fill_forward_path(struct ieee80211_hw *hw, return 0; } +#if defined(CONFIG_NET_MEDIATEK_SOC_WED) || defined(CONFIG_MT7996_NPU) +static int +mt7996_net_setup_tc(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct net_device *netdev, enum tc_setup_type type, + void *type_data) +{ +#ifdef CONFIG_NET_MEDIATEK_SOC_WED + struct mt7996_dev *dev = mt7996_hw_dev(hw); + + if (mtk_wed_device_active(&dev->mt76.mmio.wed)) + return mt76_wed_net_setup_tc(hw, vif, netdev, type, + type_data); +#endif + return mt76_npu_net_setup_tc(hw, vif, netdev, type, type_data); +} +#endif + static int mt7996_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u16 old_links, u16 new_links, @@ -2570,10 +2587,8 @@ const struct ieee80211_ops mt7996_ops = { #endif .set_radar_background = mt7996_set_radar_background, .net_fill_forward_path = mt7996_net_fill_forward_path, -#ifdef CONFIG_NET_MEDIATEK_SOC_WED - .net_setup_tc = mt76_wed_net_setup_tc, -#elif defined(CONFIG_MT7996_NPU) - .net_setup_tc = mt76_npu_net_setup_tc, +#if defined(CONFIG_NET_MEDIATEK_SOC_WED) || defined(CONFIG_MT7996_NPU) + .net_setup_tc = mt7996_net_setup_tc, #endif .change_vif_links = mt7996_change_vif_links, .change_sta_links = mt7996_mac_sta_change_links, From e11fddd3471d6d277495412d0774fa6f3e71c164 Mon Sep 17 00:00:00 2001 From: Chad Monroe Date: Fri, 24 Jul 2026 12:48:06 +0000 Subject: [PATCH 0833/1433] wifi: mt76: fix stuck TX queues after SER with no active interfaces When a firmware watchdog triggers full SER recovery before any VAPs are up, mac80211 skips drv_reconfig_complete() because open_count is zero. This leaves the DRIVER queue stop reason set permanently, blocking all TX when interfaces eventually start. Signed-off-by: Chad Monroe Link: https://patch.msgid.link/20260724124813.3961474-22-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/mac.c | 4 ++++ drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 2 ++ 2 files changed, 6 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c index ebe1376ffc2c..350d4573ced2 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c @@ -1469,6 +1469,10 @@ mt7915_mac_full_reset(struct mt7915_dev *dev) mutex_unlock(&dev->mt76.mutex); + ieee80211_wake_queues(mt76_hw(dev)); + if (ext_phy) + ieee80211_wake_queues(ext_phy->hw); + ieee80211_restart_hw(mt76_hw(dev)); if (ext_phy) ieee80211_restart_hw(ext_phy->hw); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 96589aea1548..544da01b0411 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2512,6 +2512,8 @@ mt7996_mac_full_reset(struct mt7996_dev *dev) mutex_unlock(&dev->mt76.mutex); + ieee80211_wake_queues(mt76_hw(dev)); + ieee80211_restart_hw(mt76_hw(dev)); } From 98aff74779da9f417386c589c36848e9c568f0f9 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 12:48:07 +0000 Subject: [PATCH 0834/1433] wifi: mt76: mt7615: fix NULL deref in mt7615_mac_set_rates mt7615_tx() falls back to the vif BSS wcid when mac80211 hands a frame over without a station, e.g. while a station is being torn down, and to the global wcid when there is no vif either. Both tx_prepare_skb implementations derive a mt7615_sta from that wcid unconditionally and, if the rate control probe flag is set, pass it to mt7615_mac_set_rates(), which dereferences the NULL vif backpointer of the per-vif embedded sta: Unable to handle kernel read from unreadable memory at virtual address 0000000000000002 ... pc : mt7615_mac_set_rates lr : mt7615_tx_prepare_skb Neither entry is rate controlled by mac80211: the embedded sta has an empty rate set, and for the global wcid the container_of does not yield a valid mt7615_sta at all. Only resolve the sta for wcids that belong to a station. Reported-by: Chad Monroe Link: https://patch.msgid.link/20260724124813.3961474-23-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7615/pci_mac.c | 5 +++-- drivers/net/wireless/mediatek/mt76/mt7615/usb_sdio.c | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/pci_mac.c b/drivers/net/wireless/mediatek/mt76/mt7615/pci_mac.c index 53cb1eed1e4f..0aca590f2bae 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7615/pci_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7615/pci_mac.c @@ -68,10 +68,11 @@ int mt7615_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, int pid, id; u8 *txwi = (u8 *)txwi_ptr; struct mt76_txwi_cache *t; - struct mt7615_sta *msta; + struct mt7615_sta *msta = NULL; void *txp; - msta = wcid ? container_of(wcid, struct mt7615_sta, wcid) : NULL; + if (wcid && wcid->sta) + msta = container_of(wcid, struct mt7615_sta, wcid); if (!wcid) wcid = &dev->mt76.global_wcid; diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/usb_sdio.c b/drivers/net/wireless/mediatek/mt76/mt7615/usb_sdio.c index f4169de939c4..9525d8062b44 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7615/usb_sdio.c +++ b/drivers/net/wireless/mediatek/mt76/mt7615/usb_sdio.c @@ -187,10 +187,11 @@ int mt7663_usb_sdio_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, struct sk_buff *skb = tx_info->skb; struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); struct ieee80211_key_conf *key = info->control.hw_key; - struct mt7615_sta *msta; + struct mt7615_sta *msta = NULL; int pad, err, pktid; - msta = wcid ? container_of(wcid, struct mt7615_sta, wcid) : NULL; + if (wcid && wcid->sta) + msta = container_of(wcid, struct mt7615_sta, wcid); if (!wcid) wcid = &dev->mt76.global_wcid; From 496ea8ceb2113d0fdeb1b37437d3a4d282162b2f Mon Sep 17 00:00:00 2001 From: Chad Monroe Date: Fri, 24 Jul 2026 12:48:08 +0000 Subject: [PATCH 0835/1433] wifi: mt76: mt7915: trigger L1 SER on PLE MDP RIOC hang WM firmware can enter a partial failure state where RX is hung. Detect this condition by monitoring SER_PLE_ERR_1 for MDP_RIOC_HANG_ERR and trigger L1 SER to restore operation. Use transition detection on the error bit to fire only once per new occurrence, preventing an infinite SER loop when the bit remains set across checks. Signed-off-by: Chad Monroe Suggested-by: Ryder Lee Link: https://patch.msgid.link/20260724124813.3961474-24-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/mac.c | 13 ++++++++++++- drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h | 1 + drivers/net/wireless/mediatek/mt76/mt7915/regs.h | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c index 350d4573ced2..df6359c88b18 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c @@ -1934,7 +1934,7 @@ void mt7915_mac_update_stats(struct mt7915_phy *phy) static void mt7915_mac_severe_check(struct mt7915_phy *phy) { struct mt7915_dev *dev = phy->dev; - u32 trb; + u32 trb, ple_err; if (!phy->omac_mask) return; @@ -1954,6 +1954,17 @@ static void mt7915_mac_severe_check(struct mt7915_phy *phy) phy->mt76->band_idx); phy->trb_ts = trb; + + ple_err = mt76_rr(dev, MT_SWDEF_PLE1_STATS); + if ((ple_err & MT_SWDEF_PLE1_MDP_RIOC_HANG_ERR) && + !(dev->ple1_sts & MT_SWDEF_PLE1_MDP_RIOC_HANG_ERR)) { + dev_warn(dev->mt76.dev, + "band%d: PLE error 0x%x detected, triggering L1 SER\n", + phy->mt76->band_idx, ple_err); + mt7915_mcu_set_ser(dev, SER_RECOVER, SER_SET_RECOVER_L1, + phy->mt76->band_idx); + } + dev->ple1_sts = ple_err; } void mt7915_mac_sta_rc_work(struct work_struct *work) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h b/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h index 92e0f9f0169c..0f7e871dc7a1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h @@ -296,6 +296,7 @@ struct mt7915_dev { spinlock_t reg_lock; u32 hw_pattern; + u32 ple1_sts; bool dbdc_support; bool flash_mode; diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/regs.h b/drivers/net/wireless/mediatek/mt76/mt7915/regs.h index 307bf6a75674..dc7bb9a1f223 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7915/regs.h @@ -1044,6 +1044,7 @@ enum offs_rev { #define MT_SWDEF_SER_STATS MT_SWDEF(0x040) #define MT_SWDEF_PLE_STATS MT_SWDEF(0x044) #define MT_SWDEF_PLE1_STATS MT_SWDEF(0x048) +#define MT_SWDEF_PLE1_MDP_RIOC_HANG_ERR BIT(3) #define MT_SWDEF_PLE_AMSDU_STATS MT_SWDEF(0x04C) #define MT_SWDEF_PSE_STATS MT_SWDEF(0x050) #define MT_SWDEF_PSE1_STATS MT_SWDEF(0x054) From 7e4208e9f6a876c2b7d28fdb6b86dff3b05db2f7 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 12:48:09 +0000 Subject: [PATCH 0836/1433] wifi: mt76: mt7996: free vif links after clearing wcid entries on full reset mt7996_mac_reset_vif_iter() queues non-default vif links for kfree_rcu while dev->wcid[] still holds pointers to the wcid embedded in each freed link; mt76_reset_device() then dereferences those entries and runs mt76_wcid_cleanup() on them. If a grace period elapses in between, the cleanup operates on freed memory. Run mt76_reset_device() first, so the wcid entries are cleaned up and cleared while the links are still valid. Fixes: ace5d3b6b49e ("wifi: mt76: mt7996: improve hardware restart reliability") Link: https://patch.msgid.link/20260724124813.3961474-25-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 544da01b0411..ce8c5a5ea8cc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2485,10 +2485,10 @@ mt7996_mac_full_reset(struct mt7996_dev *dev) phy->omac_mask = 0; ieee80211_iterate_stations_atomic(hw, mt7996_mac_reset_sta_iter, dev); + mt76_reset_device(&dev->mt76); ieee80211_iterate_active_interfaces_atomic(hw, IEEE80211_IFACE_SKIP_SDATA_NOT_IN_DRIVER, mt7996_mac_reset_vif_iter, dev); - mt76_reset_device(&dev->mt76); INIT_LIST_HEAD(&dev->sta_rc_list); INIT_LIST_HEAD(&dev->twt_list); From 75d2a4e2129b58bb0ddb842387299038495aad2a Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 12:48:10 +0000 Subject: [PATCH 0837/1433] wifi: mt76: mt7996: clear stale link state on full reset After a full chip reset, mac80211 reconfig replays interface, link and channel context setup. mt7996_vif_link_add() short-circuits when the link_id is still marked in mvif->valid_links, a state introduced for postponing link teardown to interface removal. The reset path frees the link structures without clearing those bits, so the replayed setup never re-creates dev_info/bss_info/STA records in the restarted firmware and never re-registers the link wcid, leaving the device inoperative. The reset path also leaks every allocated MLD index: per-link indices and the per-vif group/remap indices are re-allocated from scratch during reconfig, but the old bits stay set in the masks, so repeated full resets exhaust the index space. Clear valid_links in the reset vif iterator and reset the MLD index masks alongside the existing omac_mask clearing. Fixes: ace5d3b6b49e ("wifi: mt76: mt7996: improve hardware restart reliability") Fixes: 08813703ac41 ("wifi: mt76: mt7996: Destroy vif active links in mt7996_remove_interface()") Link: https://patch.msgid.link/20260724124813.3961474-26-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index ce8c5a5ea8cc..4794dacb3756 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2449,6 +2449,7 @@ mt7996_mac_reset_vif_iter(void *data, u8 *mac, struct ieee80211_vif *vif) rcu_assign_pointer(mvif->link[i], NULL); kfree_rcu(mlink, rcu_head); } + mvif->valid_links = 0; rcu_read_unlock(); } @@ -2483,6 +2484,8 @@ mt7996_mac_full_reset(struct mt7996_dev *dev) mt7996_for_each_phy(dev, phy) phy->omac_mask = 0; + dev->mld_idx_mask = 0; + dev->mld_remap_idx_mask = 0; ieee80211_iterate_stations_atomic(hw, mt7996_mac_reset_sta_iter, dev); mt76_reset_device(&dev->mt76); From 6ff1c217a00335c92aa6a9b35b3ff3b128efbfea Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 24 Jul 2026 12:48:11 +0000 Subject: [PATCH 0838/1433] wifi: mt76: set MT76_SCANNING when starting a hw scan The scan state bit is cleared by mt76_scan_complete(), but nothing ever sets it: mt76_sw_scan() is only called for drivers without hw scan support. As a result, all MT76_SCANNING checks are inert for drivers using mt76_hw_scan(), including the DFS state handling in mt76_phy_dfs_state() and the guard against manually triggered radar detection while scanning. Set the bit when the scan request is accepted. Since every channel programmed while scanning now evaluates the DFS state as disabled and stops the radar detector, re-program the operating channel at scan completion regardless of the off-channel state, after the scanning bit has been cleared. Otherwise a scan whose last visited channel was the operating channel, or one that returned to it early because of associated stations, would leave radar detection stopped until the next channel switch. Fixes: 31083e38548f ("wifi: mt76: add code for emulating hardware scanning") Link: https://patch.msgid.link/20260724124813.3961474-27-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/scan.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/scan.c b/drivers/net/wireless/mediatek/mt76/scan.c index 325638d587c9..3cb11689d4bf 100644 --- a/drivers/net/wireless/mediatek/mt76/scan.c +++ b/drivers/net/wireless/mediatek/mt76/scan.c @@ -16,10 +16,17 @@ static void mt76_scan_complete(struct mt76_dev *dev, bool abort) clear_bit(MT76_SCANNING, &phy->state); - if (dev->scan.chan && phy->main_chandef.chan && phy->offchannel && + /* Re-program the operating channel even when the scan never left it: + * any channel set during the scan ran with MT76_SCANNING held, which + * left DFS radar detection disabled + */ + if (phy->main_chandef.chan && !test_bit(MT76_MCU_RESET, &dev->phy.state)) { + bool offchannel = phy->offchannel; + mt76_set_channel(phy, &phy->main_chandef, false); - mt76_offchannel_notify(phy, false); + if (offchannel) + mt76_offchannel_notify(phy, false); } mt76_put_vif_phy_link(phy, dev->scan.vif, dev->scan.mlink); memset(&dev->scan, 0, sizeof(dev->scan)); @@ -211,6 +218,7 @@ int mt76_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif, dev->scan.vif = vif; dev->scan.phy = phy; dev->scan.mlink = mlink; + set_bit(MT76_SCANNING, &phy->state); ieee80211_queue_delayed_work(dev->phy.hw, &dev->scan_work, 0); out: From 4c3cf4a8b15090bfff518c09903bf7d3ff0ae110 Mon Sep 17 00:00:00 2001 From: Chad Monroe Date: Fri, 24 Jul 2026 12:48:12 +0000 Subject: [PATCH 0839/1433] wifi: mt76: serialize scan-link teardown with dev->mutex The offchannel scan link is allocated in mt76_hw_scan() under dev->mutex, but torn down without it: mt76_scan_complete() runs from mt76_scan_work() on the mac80211 workqueue, or from mt76_abort_scan(), and calls mt76_put_vif_phy_link(), whose vif_link_remove clears the per-phy omac_mask and the device-wide vif_mask/mld_idx_mask with plain read-modify-write. A vif link add or remove for another interface, running concurrently under dev->mutex, can interleave with these unlocked writes and lose an update: a cleared bit belonging to a live link gets handed out again (two links sharing an omac/bss/wcid index, breaking own-MAC unicast RX for the first one), or a freed bit stays set until reboot and eventually exhausts the index space. Take dev->mutex around the scan completion, mirroring the ROC teardown in mt76_roc_complete_work()/mt76_abort_roc(), and switch the channel restore to __mt76_set_channel() since the caller now holds the lock. mt76_abort_scan() keeps cancelling the scan work before taking the mutex, so the work-vs-abort ordering is unchanged. Signed-off-by: Chad Monroe Link: https://patch.msgid.link/20260724124813.3961474-28-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/scan.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/scan.c b/drivers/net/wireless/mediatek/mt76/scan.c index 3cb11689d4bf..3594b599662d 100644 --- a/drivers/net/wireless/mediatek/mt76/scan.c +++ b/drivers/net/wireless/mediatek/mt76/scan.c @@ -11,6 +11,8 @@ static void mt76_scan_complete(struct mt76_dev *dev, bool abort) .aborted = abort, }; + lockdep_assert_held(&dev->mutex); + if (!phy) return; @@ -24,7 +26,7 @@ static void mt76_scan_complete(struct mt76_dev *dev, bool abort) !test_bit(MT76_MCU_RESET, &dev->phy.state)) { bool offchannel = phy->offchannel; - mt76_set_channel(phy, &phy->main_chandef, false); + __mt76_set_channel(phy, &phy->main_chandef, false); if (offchannel) mt76_offchannel_notify(phy, false); } @@ -41,7 +43,10 @@ void mt76_abort_scan(struct mt76_dev *dev) spin_unlock_bh(&dev->scan_lock); cancel_delayed_work_sync(&dev->scan_work); + + mutex_lock(&dev->mutex); mt76_scan_complete(dev, true); + mutex_unlock(&dev->mutex); } EXPORT_SYMBOL_GPL(mt76_abort_scan); @@ -139,7 +144,9 @@ void mt76_scan_work(struct work_struct *work) goto probe; if (dev->scan.chan_idx >= req->n_channels) { + mutex_lock(&dev->mutex); mt76_scan_complete(dev, false); + mutex_unlock(&dev->mutex); return; } From a3da1f2a3e798553397b94d4cd0c55065f27035c Mon Sep 17 00:00:00 2001 From: Chad Monroe Date: Fri, 24 Jul 2026 12:48:13 +0000 Subject: [PATCH 0840/1433] wifi: mt76: mt7996: hold dev->mutex in remove_interface teardown mt7996_remove_interface() destroys the remaining vif links, clearing omac_mask, vif_mask and mld_idx_mask, with only the wiphy mutex held. Those masks are modified under dev->mutex everywhere else, so the unlocked clears can race the scan-link teardown and the reset work and lose updates. Take dev->mutex around the link destroy loop, matching mt7996_add_interface() and mt7915_remove_interface(). The mutex is released before mt76_vif_cleanup(), which aborts a pending scan and takes the mutex itself. Signed-off-by: Chad Monroe Link: https://patch.msgid.link/20260724124813.3961474-29-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 21a928431c25..54e79bd25995 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -606,6 +606,8 @@ static void mt7996_remove_interface(struct ieee80211_hw *hw, unsigned int link_id; int i; + mutex_lock(&dev->mt76.mutex); + /* Remove all active links */ for_each_set_bit(link_id, &rem_links, IEEE80211_MLD_MAX_NUM_LINKS) { struct mt7996_vif_link *link; @@ -622,6 +624,8 @@ static void mt7996_remove_interface(struct ieee80211_hw *hw, mt7996_vif_link_destroy(phy, link, vif, NULL); } + mutex_unlock(&dev->mt76.mutex); + ieee80211_iterate_active_interfaces_mtx(hw, 0, mt7996_remove_iter, &rdata); mt76_vif_cleanup(&dev->mt76, vif); From d0b750072a29c8ff0504c3bc28c411c402f26716 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 27 Jul 2026 15:04:20 +0000 Subject: [PATCH 0841/1433] wifi: mt76: mt7996: fix MIB TX aggregation counter registers for mt7990 The MIB_TSCR0-7 counters read by mt7996_mac_update_stats() are hardcoded at the mt7996/mt7992 offsets 0x6b0-0x6d0, but mt7990 moved them to 0x750-0x770, so TX AMPDU statistics were read from unrelated registers on that chip. Move the offsets into the per-chip register tables. Fixes: f6c87411d15f ("wifi: mt76: mt7996: rework register mapping for mt7990") Link: https://patch.msgid.link/20260727150434.1778520-1-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/mmio.c | 24 +++++++++++++++++++ .../net/wireless/mediatek/mt76/mt7996/regs.h | 24 ++++++++++++------- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c index d9780bb425a7..de68e60a1e86 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c @@ -54,6 +54,14 @@ static const u32 mt7996_offs[] = { [MIB_BSCR7] = 0x9e8, [MIB_BSCR17] = 0xa10, [MIB_TRDR1] = 0xa28, + [MIB_TSCR0] = 0x6b0, + [MIB_TSCR1] = 0x6b4, + [MIB_TSCR2] = 0x6b8, + [MIB_TSCR3] = 0x6bc, + [MIB_TSCR4] = 0x6c0, + [MIB_TSCR5] = 0x6c4, + [MIB_TSCR6] = 0x6c8, + [MIB_TSCR7] = 0x6d0, [HIF_REMAP_L1] = 0x24, [HIF_REMAP_BASE_L1] = 0x130000, [HIF_REMAP_L2] = 0x1b4, @@ -91,6 +99,14 @@ static const u32 mt7992_offs[] = { [MIB_BSCR7] = 0xae4, [MIB_BSCR17] = 0xb0c, [MIB_TRDR1] = 0xb24, + [MIB_TSCR0] = 0x6b0, + [MIB_TSCR1] = 0x6b4, + [MIB_TSCR2] = 0x6b8, + [MIB_TSCR3] = 0x6bc, + [MIB_TSCR4] = 0x6c0, + [MIB_TSCR5] = 0x6c4, + [MIB_TSCR6] = 0x6c8, + [MIB_TSCR7] = 0x6d0, [HIF_REMAP_L1] = 0x8, [HIF_REMAP_BASE_L1] = 0x40000, [HIF_REMAP_L2] = 0x1b4, @@ -128,6 +144,14 @@ static const u32 mt7990_offs[] = { [MIB_BSCR7] = 0xbd4, [MIB_BSCR17] = 0xbfc, [MIB_TRDR1] = 0xc14, + [MIB_TSCR0] = 0x750, + [MIB_TSCR1] = 0x754, + [MIB_TSCR2] = 0x758, + [MIB_TSCR3] = 0x75c, + [MIB_TSCR4] = 0x760, + [MIB_TSCR5] = 0x764, + [MIB_TSCR6] = 0x768, + [MIB_TSCR7] = 0x770, [HIF_REMAP_L1] = 0x8, [HIF_REMAP_BASE_L1] = 0x40000, [HIF_REMAP_L2] = 0x1b8, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h index c6379933b6c3..8ff78cf6eb04 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h @@ -64,6 +64,14 @@ enum offs_rev { MIB_BSCR7, MIB_BSCR17, MIB_TRDR1, + MIB_TSCR0, + MIB_TSCR1, + MIB_TSCR2, + MIB_TSCR3, + MIB_TSCR4, + MIB_TSCR5, + MIB_TSCR6, + MIB_TSCR7, HIF_REMAP_L1, HIF_REMAP_BASE_L1, HIF_REMAP_L2, @@ -250,9 +258,9 @@ enum offs_rev { #define MT_MIB_BSCR7(_band) MT_WF_MIB(_band, __OFFS(MIB_BSCR7)) #define MT_MIB_BSCR17(_band) MT_WF_MIB(_band, __OFFS(MIB_BSCR17)) -#define MT_MIB_TSCR5(_band) MT_WF_MIB(_band, 0x6c4) -#define MT_MIB_TSCR6(_band) MT_WF_MIB(_band, 0x6c8) -#define MT_MIB_TSCR7(_band) MT_WF_MIB(_band, 0x6d0) +#define MT_MIB_TSCR5(_band) MT_WF_MIB(_band, __OFFS(MIB_TSCR5)) +#define MT_MIB_TSCR6(_band) MT_WF_MIB(_band, __OFFS(MIB_TSCR6)) +#define MT_MIB_TSCR7(_band) MT_WF_MIB(_band, __OFFS(MIB_TSCR7)) #define MT_MIB_RSCR1(_band) MT_WF_MIB(_band, __OFFS(MIB_RSCR1)) /* rx mpdu counter, full 32 bits */ @@ -268,14 +276,14 @@ enum offs_rev { #define MT_MIB_RSCR36(_band) MT_WF_MIB(_band, __OFFS(MIB_RSCR36)) /* tx ampdu cnt, full 32 bits */ -#define MT_MIB_TSCR0(_band) MT_WF_MIB(_band, 0x6b0) -#define MT_MIB_TSCR2(_band) MT_WF_MIB(_band, 0x6b8) +#define MT_MIB_TSCR0(_band) MT_WF_MIB(_band, __OFFS(MIB_TSCR0)) +#define MT_MIB_TSCR2(_band) MT_WF_MIB(_band, __OFFS(MIB_TSCR2)) /* counts all mpdus in ampdu, regardless of success */ -#define MT_MIB_TSCR3(_band) MT_WF_MIB(_band, 0x6bc) +#define MT_MIB_TSCR3(_band) MT_WF_MIB(_band, __OFFS(MIB_TSCR3)) /* counts all successfully tx'd mpdus in ampdu */ -#define MT_MIB_TSCR4(_band) MT_WF_MIB(_band, 0x6c0) +#define MT_MIB_TSCR4(_band) MT_WF_MIB(_band, __OFFS(MIB_TSCR4)) /* rx ampdu count, 32-bit */ #define MT_MIB_RSCR27(_band) MT_WF_MIB(_band, __OFFS(MIB_RSCR27)) @@ -299,7 +307,7 @@ enum offs_rev { #define MT_MIB_RVSR1(_band) MT_WF_MIB(_band, __OFFS(MIB_RVSR1)) /* rx blockack count, 32 bits */ -#define MT_MIB_TSCR1(_band) MT_WF_MIB(_band, 0x6b4) +#define MT_MIB_TSCR1(_band) MT_WF_MIB(_band, __OFFS(MIB_TSCR1)) #define MT_MIB_BTSCR0(_band) MT_WF_MIB(_band, 0x5e0) #define MT_MIB_BTSCR5(_band) MT_WF_MIB(_band, __OFFS(MIB_BTSCR5)) From 3ae8ad277e2819a281b0e36b55633c8515c16ce7 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 27 Jul 2026 15:04:21 +0000 Subject: [PATCH 0842/1433] wifi: mt76: mt7915: fix double hif2 init on the non-WED path mt7915_pci_init_hif2() was called unconditionally and again inside the WED-inactive branch. The helper increments the global hif_idx, writes the PCIe RECOG_ID register and takes a get_device() reference via mt7915_pci_get_hif2(), while removal only drops one reference. On non-WED dual-hif hardware this double-incremented hif_idx, wrote RECOG_ID twice and leaked a device reference. Only the call inside the WED-inactive branch is correct; drop the unconditional one. hif2 is already initialised to NULL. Fixes: cacdd67812c6 ("mt76: mt7915: add mt7915_mmio_probe() as a common probing function") Link: https://patch.msgid.link/20260727150434.1778520-2-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/pci.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/pci.c b/drivers/net/wireless/mediatek/mt76/mt7915/pci.c index f6b03211a879..12b3e2dd530a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/pci.c @@ -135,7 +135,6 @@ static int mt7915_pci_probe(struct pci_dev *pdev, mdev = &dev->mt76; mt7915_wfsys_reset(dev); - hif2 = mt7915_pci_init_hif2(pdev); ret = mt7915_mmio_wed_init(dev, pdev, true, &irq); if (ret < 0) From 15b960014f24dce5388d4a2e7274e6490cb3c421 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 27 Jul 2026 15:04:22 +0000 Subject: [PATCH 0843/1433] wifi: mt76: mt7915: fix ext PHY use-after-free on register error path After mt7915_register_ext_phy() succeeded, a failure of the main PHY mt7915_init_debugfs() or mt7915_coredump_register() unwound through free_phy2, which called ieee80211_free_hw() on the ext PHY hw while it was still registered with mac80211, since mt76_unregister_device() only unregisters the main hw. Unregister the ext PHY (thermal + phy + hw) first and skip the redundant free. Fixes: 7b8e1ae886e4 ("mt76: mt7915: rework hardware/phy initialization") Link: https://patch.msgid.link/20260727150434.1778520-3-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/init.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/init.c b/drivers/net/wireless/mediatek/mt76/mt7915/init.c index 0f6346731308..ca46a203aa48 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/init.c @@ -1302,14 +1302,19 @@ int mt7915_register_device(struct mt7915_dev *dev) ret = mt7915_init_debugfs(&dev->phy); if (ret) - goto unreg_thermal; + goto unreg_ext_phy; ret = mt7915_coredump_register(dev); if (ret) - goto unreg_thermal; + goto unreg_ext_phy; return 0; +unreg_ext_phy: + if (phy2) { + mt7915_unregister_ext_phy(dev); + phy2 = NULL; + } unreg_thermal: mt7915_unregister_thermal(&dev->phy); unreg_dev: From 8370aebd26a9dfa2e0de665e3ab504c0e97ee730 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 27 Jul 2026 15:04:23 +0000 Subject: [PATCH 0844/1433] wifi: mt76: mt7915: release hif2 reference on probe IRQ failure The hif2 reference obtained by mt7915_pci_init_hif2() is only released on error paths that key off dev->hif2, which is not assigned until after the IRQ setup. If pci_alloc_irq_vectors() or the primary devm_request_irq() fails, the reference leaks. Drop it explicitly on those paths via mt7915_put_hif2(). Fixes: f68d67623dec ("mt76: mt7915: add Wireless Ethernet Dispatch support") Link: https://patch.msgid.link/20260727150434.1778520-4-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/pci.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/pci.c b/drivers/net/wireless/mediatek/mt76/mt7915/pci.c index 12b3e2dd530a..8007e620048b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/pci.c @@ -144,16 +144,20 @@ static int mt7915_pci_probe(struct pci_dev *pdev, hif2 = mt7915_pci_init_hif2(pdev); ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES); - if (ret < 0) + if (ret < 0) { + mt7915_put_hif2(hif2); goto free_device; + } irq = pdev->irq; } ret = devm_request_irq(mdev->dev, irq, mt7915_irq_handler, IRQF_SHARED, KBUILD_MODNAME, dev); - if (ret) + if (ret) { + mt7915_put_hif2(hif2); goto free_wed_or_irq_vector; + } /* master switch of PCIe tnterrupt enable */ mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0xff); From eb906eeff2d1e84b628dc210dada325269c71383 Mon Sep 17 00:00:00 2001 From: StanleyYP Wang Date: Mon, 27 Jul 2026 15:04:24 +0000 Subject: [PATCH 0845/1433] wifi: mt76: mt7996: fix reg addr remap when addr is 0 When addr is less than the hardcoded threshold in __mt7996_reg_addr, it indicates that remapping is unnecessary. Currently, the flow remaps address 0x0 to MT_HIF_REMAP_BASE_L2, which is incorrect. To address this, modify __mt7996_reg_addr to return INVALID_REG_ADDR if the address is not below the hardcoded value or is not present in the mt7996_reg_map array. Additionally, update the remap condition to check if addr is equal to INVALID_REG_ADDR. Fixes: 3687854d3e7e ("wifi: mt76: mt7996: add locking for accessing mapped registers") Signed-off-by: StanleyYP Wang Link: https://patch.msgid.link/20260727150434.1778520-5-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mmio.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c index de68e60a1e86..0d7521d06981 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c @@ -17,6 +17,8 @@ static bool wed_enable; module_param(wed_enable, bool, 0644); +#define INVALID_REG_ADDR 0xffffffff + static const struct __base mt7996_reg_base[] = { [WF_AGG_BASE] = { { 0x820e2000, 0x820f2000, 0x830e2000 } }, [WF_ARB_BASE] = { { 0x820e3000, 0x820f3000, 0x830e3000 } }, @@ -358,7 +360,7 @@ static u32 __mt7996_reg_addr(struct mt7996_dev *dev, u32 addr) return dev->reg.map[i].mapped + ofs; } - return 0; + return INVALID_REG_ADDR; } static u32 __mt7996_reg_remap_addr(struct mt7996_dev *dev, u32 addr) @@ -390,7 +392,7 @@ void mt7996_memcpy_fromio(struct mt7996_dev *dev, void *buf, u32 offset, { u32 addr = __mt7996_reg_addr(dev, offset); - if (addr) { + if (addr != INVALID_REG_ADDR) { memcpy_fromio(buf, dev->mt76.mmio.regs + addr, len); return; } @@ -406,7 +408,7 @@ static u32 mt7996_rr(struct mt76_dev *mdev, u32 offset) struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76); u32 addr = __mt7996_reg_addr(dev, offset), val; - if (addr) + if (addr != INVALID_REG_ADDR) return dev->bus_ops->rr(mdev, addr); spin_lock_bh(&dev->reg_lock); @@ -421,7 +423,7 @@ static void mt7996_wr(struct mt76_dev *mdev, u32 offset, u32 val) struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76); u32 addr = __mt7996_reg_addr(dev, offset); - if (addr) { + if (addr != INVALID_REG_ADDR) { dev->bus_ops->wr(mdev, addr, val); return; } @@ -436,7 +438,7 @@ static u32 mt7996_rmw(struct mt76_dev *mdev, u32 offset, u32 mask, u32 val) struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76); u32 addr = __mt7996_reg_addr(dev, offset); - if (addr) + if (addr != INVALID_REG_ADDR) return dev->bus_ops->rmw(mdev, addr, mask, val); spin_lock_bh(&dev->reg_lock); From 7c1924332e986019c6bcddf55c843361cccac73f Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 27 Jul 2026 15:04:25 +0000 Subject: [PATCH 0846/1433] wifi: mt76: mt7996: do not attach hif2 WED when the main WED attach failed If the WED attach for the primary PCIe function fails, the probe path still attached wed_hif2 for the secondary function, leaving the device in an inconsistent half-WED configuration that crashes later. The hif2 call also re-enabled hwrro_mode, which the failed primary attach had just turned off. Skip the hif2 WED setup when the primary WED device is not active. Fixes: 83eafc9251d6 ("wifi: mt76: mt7996: add wed tx support") Link: https://patch.msgid.link/20260727150434.1778520-6-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mmio.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c index 0d7521d06981..ba064324a7cc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c @@ -490,6 +490,9 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, if (!wed_enable) return 0; + if (hif2 && !mtk_wed_device_active(&dev->mt76.mmio.wed)) + return 0; + dev->mt76.hwrro_mode = is_mt7996(&dev->mt76) ? MT76_HWRRO_V3 : MT76_HWRRO_V3_1; From 6a4cabff1203791797683cf6be8f56bee983dc73 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 27 Jul 2026 15:04:26 +0000 Subject: [PATCH 0847/1433] wifi: mt76: mt7996: do not leave state behind after a failed WED attach mt7996_mmio_wed_init() set dev->mt76.hwrro_mode and rx_token_size while building the WED configuration, before knowing whether the WED attach can succeed. A failed attach left the enlarged rx_token_size behind and reset hwrro_mode to MT76_HWRRO_OFF, clobbering the values that another RX datapath owner may have configured earlier in probe: on Airoha platforms with the wed_enable module parameter set, this broke the NPU offload configuration set up by mt76_npu_init() (NPU offload requires HW-RRO and a larger rx token space, and the attach always fails there since no SoC has both an Airoha NPU and MTK WED). Move both assignments after a successful attach, next to the existing success-only dma_dev/irq assignments. This is safe for the regular WED attach case: the first consumer of either field runs after probe continues (mtk_wed_device_attach() only invokes the init_buf callback; rx buffers are allocated via init_rx_buf from mtk_wed_start(), long after mt7996_mmio_wed_init() has returned). Within the WED configuration the HW-RRO checks were constant: the mode was assigned unconditionally right before them, and the hif2 path is only reachable after a successful main attach has set it. Resolve them to their constant values and drop the dead branches. Fixes: 377aa17d2aed ("wifi: mt76: mt7996: Add NPU offload support to MT7996 driver") Link: https://patch.msgid.link/20260727150434.1778520-7-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/mmio.c | 53 +++++++------------ 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c index ba064324a7cc..ac81be5fe023 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c @@ -493,9 +493,6 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, if (hif2 && !mtk_wed_device_active(&dev->mt76.mmio.wed)) return 0; - dev->mt76.hwrro_mode = is_mt7996(&dev->mt76) ? MT76_HWRRO_V3 - : MT76_HWRRO_V3_1; - hif1_ofs = dev->hif2 ? MT_WFDMA0_PCIE1(0) - MT_WFDMA0(0) : 0; if (hif2) @@ -520,23 +517,16 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, wed->wlan.wpdma_tx = wed->wlan.phy_base + hif1_ofs + MT_TXQ_RING_BASE(0) + MT7996_TXQ_BAND2 * MT_RING_SIZE; - if (mt7996_has_hwrro(dev)) { - if (is_mt7996(&dev->mt76)) { - wed->wlan.txfree_tbit = ffs(MT_INT_RX_TXFREE_EXT) - 1; - wed->wlan.wpdma_txfree = wed->wlan.phy_base + hif1_ofs + - MT_RXQ_RING_BASE(0) + - MT7996_RXQ_TXFREE2 * MT_RING_SIZE; - } else { - wed->wlan.txfree_tbit = ffs(MT_INT_RX_TXFREE_BAND1_EXT) - 1; - wed->wlan.wpdma_txfree = wed->wlan.phy_base + hif1_ofs + - MT_RXQ_RING_BASE(0) + - MT7996_RXQ_MCU_WA_EXT * MT_RING_SIZE; - } - } else { + if (is_mt7996(&dev->mt76)) { + wed->wlan.txfree_tbit = ffs(MT_INT_RX_TXFREE_EXT) - 1; wed->wlan.wpdma_txfree = wed->wlan.phy_base + hif1_ofs + MT_RXQ_RING_BASE(0) + - MT7996_RXQ_MCU_WA_TRI * MT_RING_SIZE; - wed->wlan.txfree_tbit = ffs(MT_INT_RX_DONE_WA_TRI) - 1; + MT7996_RXQ_TXFREE2 * MT_RING_SIZE; + } else { + wed->wlan.txfree_tbit = ffs(MT_INT_RX_TXFREE_BAND1_EXT) - 1; + wed->wlan.wpdma_txfree = wed->wlan.phy_base + hif1_ofs + + MT_RXQ_RING_BASE(0) + + MT7996_RXQ_MCU_WA_EXT * MT_RING_SIZE; } wed->wlan.wpdma_rx_glo = wed->wlan.phy_base + hif1_ofs + MT_WFDMA0_GLO_CFG; @@ -547,7 +537,7 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, wed->wlan.id = MT7996_DEVICE_ID_2; wed->wlan.tx_tbit[0] = ffs(MT_INT_TX_DONE_BAND2) - 1; } else { - wed->wlan.hw_rro = mt7996_has_hwrro(dev); + wed->wlan.hw_rro = true; wed->wlan.wpdma_int = wed->wlan.phy_base + MT_INT_SOURCE_CSR; wed->wlan.wpdma_mask = wed->wlan.phy_base + MT_INT_MASK_CSR; wed->wlan.wpdma_tx = wed->wlan.phy_base + MT_TXQ_RING_BASE(0) + @@ -600,23 +590,15 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, wed->wlan.tx_tbit[0] = ffs(MT_INT_TX_DONE_BAND0) - 1; wed->wlan.tx_tbit[1] = ffs(MT_INT_TX_DONE_BAND1) - 1; if (is_mt7996(&dev->mt76)) { - if (mt7996_has_hwrro(dev)) { - wed->wlan.wpdma_txfree = wed->wlan.phy_base + - MT_RXQ_RING_BASE(0) + - MT7996_RXQ_TXFREE0 * MT_RING_SIZE; - wed->wlan.txfree_tbit = ffs(MT_INT_RX_TXFREE_MAIN) - 1; - } else { - wed->wlan.wpdma_txfree = wed->wlan.phy_base + - MT_RXQ_RING_BASE(0) + - MT7996_RXQ_MCU_WA_MAIN * MT_RING_SIZE; - wed->wlan.txfree_tbit = ffs(MT_INT_RX_DONE_WA_MAIN) - 1; - } + wed->wlan.wpdma_txfree = wed->wlan.phy_base + + MT_RXQ_RING_BASE(0) + + MT7996_RXQ_TXFREE0 * MT_RING_SIZE; + wed->wlan.txfree_tbit = ffs(MT_INT_RX_TXFREE_MAIN) - 1; } else { wed->wlan.txfree_tbit = ffs(MT_INT_RX_DONE_WA_MAIN) - 1; wed->wlan.wpdma_txfree = wed->wlan.phy_base + MT_RXQ_RING_BASE(0) + MT7996_RXQ_MCU_WA_MAIN * MT_RING_SIZE; } - dev->mt76.rx_token_size = MT7996_TOKEN_SIZE + wed->wlan.rx_npkt; if (dev->hif2 && is_mt7992(&dev->mt76)) wed->wlan.id = 0x7992; @@ -639,9 +621,14 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, wed->wlan.reset_complete = mt76_wed_reset_complete; } - if (mtk_wed_device_attach(wed)) { - dev->mt76.hwrro_mode = MT76_HWRRO_OFF; + if (mtk_wed_device_attach(wed)) return 0; + + if (!hif2) { + dev->mt76.hwrro_mode = is_mt7996(&dev->mt76) ? MT76_HWRRO_V3 + : MT76_HWRRO_V3_1; + dev->mt76.rx_token_size = MT7996_TOKEN_SIZE + + wed->wlan.rx_npkt; } *irq = wed->irq; From ea891799eccc9e43ebba0dc157d4b197ad6c1a0e Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 27 Jul 2026 15:04:27 +0000 Subject: [PATCH 0848/1433] wifi: mt76: mt7915: fix chainmask handling for non-dbdc phys on band 1 On single-adie mt7986 the only phy is bound to band 1, but its chainmask is stored unshifted, because dev->chainshift is still zero while the eeprom is parsed for the main phy. mt7915_set_antenna() on the other hand shifts by chainshift * band_idx, so the representation of the chainmask changed as soon as the antenna configuration was touched. Until then, mt7915_mcu_set_chan_info() passed rx_path = 0 to the firmware, since shifting the unshifted mask down clears all bits. Keep the unshifted form for that case and add helpers for the band local chainmask, so that only the band 1 phy of a dbdc device uses the shifted form. Fixes: 3eb50cc90534 ("wifi: mt76: mt7915: rely on band_idx of mt76_phy") Link: https://patch.msgid.link/20260727150434.1778520-8-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7915/eeprom.c | 2 +- .../net/wireless/mediatek/mt76/mt7915/main.c | 6 +++--- .../net/wireless/mediatek/mt76/mt7915/mcu.c | 2 +- .../net/wireless/mediatek/mt76/mt7915/mt7915.h | 18 ++++++++++++++++++ .../wireless/mediatek/mt76/mt7915/testmode.c | 5 +---- 5 files changed, 24 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c b/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c index eb92cbf1a284..fe7b29ebc0bf 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c @@ -257,7 +257,7 @@ void mt7915_eeprom_parse_hw_cap(struct mt7915_dev *dev, nss = min_t(u8, min_t(u8, nss_max, nss), path); mphy->chainmask = BIT(path) - 1; - if (band) + if (band && dev->dbdc_support) mphy->chainmask <<= dev->chainshift; mphy->antenna_mask = BIT(nss) - 1; dev->chainmask |= mphy->chainmask; diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c index d2130226de64..4783e5f52d22 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c @@ -1138,7 +1138,7 @@ mt7915_set_antenna(struct ieee80211_hw *hw, int radio_idx, u32 tx_ant, u32 rx_an struct mt7915_dev *dev = mt7915_hw_dev(hw); struct mt7915_phy *phy = mt7915_hw_phy(hw); int max_nss = hweight8(hw->wiphy->available_antennas_tx); - u8 chainshift = dev->chainshift; + u8 shift = mt7915_band_chainshift(phy); u8 band = phy->mt76->band_idx; if (!tx_ant || tx_ant != rx_ant || ffs(tx_ant) > max_nss) @@ -1151,9 +1151,9 @@ mt7915_set_antenna(struct ieee80211_hw *hw, int radio_idx, u32 tx_ant, u32 rx_an /* handle a variant of mt7916/mt7981 which has 3T3R but nss2 on 5 GHz band */ if ((is_mt7916(&dev->mt76) || is_mt7981(&dev->mt76)) && band && hweight8(tx_ant) == max_nss) - phy->mt76->chainmask = (dev->chainmask >> chainshift) << chainshift; + phy->mt76->chainmask = (dev->chainmask >> shift) << shift; else - phy->mt76->chainmask = tx_ant << (chainshift * band); + phy->mt76->chainmask = tx_ant << shift; mt76_set_stream_caps(phy->mt76, true); mt7915_set_stream_vht_txbf_caps(phy); diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c index 75eb6d261033..88955aed62e2 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c @@ -2804,7 +2804,7 @@ int mt7915_mcu_set_chan_info(struct mt7915_phy *phy, int cmd) .center_ch = ieee80211_frequency_to_channel(freq1), .bw = mt76_connac_chan_bw(chandef), .tx_path_num = hweight16(phy->mt76->chainmask), - .rx_path = phy->mt76->chainmask >> (dev->chainshift * band), + .rx_path = mt7915_band_chainmask(phy), .band_idx = band, .channel_band = ch_band[chandef->chan->band], }; diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h b/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h index 0f7e871dc7a1..30fb119ce872 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h @@ -399,6 +399,24 @@ mt7915_ext_phy(struct mt7915_dev *dev) return phy->priv; } +/* without dbdc, the chainmask is stored unshifted, even if the phy is + * bound to band 1 + */ +static inline u8 mt7915_band_chainshift(struct mt7915_phy *phy) +{ + struct mt7915_dev *dev = phy->dev; + + if (!dev->dbdc_support) + return 0; + + return phy->mt76->band_idx * dev->chainshift; +} + +static inline u16 mt7915_band_chainmask(struct mt7915_phy *phy) +{ + return phy->mt76->chainmask >> mt7915_band_chainshift(phy); +} + static inline u32 mt7915_check_adie(struct mt7915_dev *dev, bool sku) { u32 mask = sku ? MT_CONNINFRA_SKU_MASK : MT_ADIE_TYPE_MASK; diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/testmode.c b/drivers/net/wireless/mediatek/mt76/mt7915/testmode.c index 618a5c2bdd29..7576973f4d4e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/testmode.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/testmode.c @@ -694,9 +694,7 @@ mt7915_tm_set_params(struct mt76_phy *mphy, struct nlattr **tb, { struct mt76_testmode_data *td = &mphy->test; struct mt7915_phy *phy = mphy->priv; - struct mt7915_dev *dev = phy->dev; - u32 chainmask = mphy->chainmask, changed = 0; - bool ext_phy = phy != &dev->phy; + u32 chainmask = mt7915_band_chainmask(phy), changed = 0; int i; BUILD_BUG_ON(NUM_TM_CHANGED >= 32); @@ -705,7 +703,6 @@ mt7915_tm_set_params(struct mt76_phy *mphy, struct nlattr **tb, td->state == MT76_TM_STATE_OFF) return 0; - chainmask = ext_phy ? chainmask >> dev->chainshift : chainmask; if (td->tx_antenna_mask > chainmask) return -EINVAL; From b53c44fe65792608f58028c7b0953e610ad652ee Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 27 Jul 2026 15:04:28 +0000 Subject: [PATCH 0849/1433] wifi: mt76: mt7915: report RX chain signal for all RX paths status->chains was set from the antenna mask, which is derived from the number of spatial streams, while the chain_signal array is filled from all RCPI fields. On boards where the number of RX paths exceeds the stream count, e.g. the 3T3R mt7916/mt7981 variant with 2 streams on the 5 GHz band, the RSSI of the extra chains was never reported. Use the band local RX path chainmask instead. Fixes: e57b7901469f ("mt76: add mac80211 driver for MT7915 PCIe-based chipsets") Link: https://patch.msgid.link/20260727150434.1778520-9-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/mac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c index df6359c88b18..b3d4abf6a9ed 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c @@ -437,7 +437,7 @@ mt7915_mac_fill_rx(struct mt7915_dev *dev, struct sk_buff *skb, if (v0 & MT_PRXV_HT_AD_CODE) status->enc_flags |= RX_ENC_FLAG_LDPC; - status->chains = mphy->antenna_mask; + status->chains = mt7915_band_chainmask(phy); status->chain_signal[0] = to_rssi(MT_PRXV_RCPI0, v1); status->chain_signal[1] = to_rssi(MT_PRXV_RCPI1, v1); status->chain_signal[2] = to_rssi(MT_PRXV_RCPI2, v1); From d22f7f29c6baec1d7ececef4936f250962813959 Mon Sep 17 00:00:00 2001 From: StanleyYP Wang Date: Mon, 27 Jul 2026 15:04:29 +0000 Subject: [PATCH 0850/1433] wifi: mt76: mt7996: remove repeater muar config Repeater mode is not supported currently, so remove it. get_omac_idx() only hands out HW_BSSID/EXT_BSSID indices, so the omac_idx >= REPEATER_BSSID_START call sites are unreachable. Signed-off-by: StanleyYP Wang Link: https://patch.msgid.link/20260727150434.1778520-10-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/mcu.c | 45 ------------------- 1 file changed, 45 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 24ce87d928e8..8aab91810135 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -1044,43 +1044,6 @@ mt7996_mcu_bss_sec_tlv(struct sk_buff *skb, struct mt76_vif_link *mlink) sec->cipher = mlink->cipher; } -static int -mt7996_mcu_muar_config(struct mt7996_dev *dev, struct mt76_vif_link *mlink, - const u8 *addr, bool bssid, bool enable) -{ -#define UNI_MUAR_ENTRY 2 - u32 idx = mlink->omac_idx - REPEATER_BSSID_START; - struct { - struct { - u8 band; - u8 __rsv[3]; - } hdr; - - __le16 tag; - __le16 len; - - bool smesh; - u8 bssid; - u8 index; - u8 entry_add; - u8 addr[ETH_ALEN]; - u8 __rsv[2]; - } __packed req = { - .hdr.band = mlink->band_idx, - .tag = cpu_to_le16(UNI_MUAR_ENTRY), - .len = cpu_to_le16(sizeof(req) - sizeof(req.hdr)), - .smesh = false, - .index = idx * 2 + bssid, - .entry_add = true, - }; - - if (enable) - memcpy(req.addr, addr, ETH_ALEN); - - return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(REPT_MUAR), &req, - sizeof(req), true); -} - static void mt7996_mcu_bss_ifs_timing_tlv(struct sk_buff *skb, struct mt7996_phy *phy) { @@ -1210,11 +1173,6 @@ int mt7996_mcu_add_bss_info(struct mt7996_phy *phy, struct ieee80211_vif *vif, struct mt7996_dev *dev = phy->dev; struct sk_buff *skb; - if (mlink->omac_idx >= REPEATER_BSSID_START) { - mt7996_mcu_muar_config(dev, mlink, link_conf->addr, false, enable); - mt7996_mcu_muar_config(dev, mlink, link_conf->bssid, true, enable); - } - skb = __mt7996_mcu_alloc_bss_req(&dev->mt76, mlink, MT7996_BSS_UPDATE_MAX_SIZE); if (IS_ERR(skb)) @@ -2996,9 +2954,6 @@ int mt7996_mcu_add_dev_info(struct mt7996_phy *phy, struct ieee80211_vif *vif, }, }; - if (mlink->omac_idx >= REPEATER_BSSID_START) - return mt7996_mcu_muar_config(dev, mlink, link_conf->addr, false, enable); - memcpy(data.tlv.omac_addr, link_conf->addr, ETH_ALEN); return mt76_mcu_send_msg(&dev->mt76, MCU_WMWA_UNI_CMD(DEV_INFO_UPDATE), &data, sizeof(data), true); From 3999d15cfcc72a946ec419c4059b0e0cd7860053 Mon Sep 17 00:00:00 2001 From: Peter Chiu Date: Mon, 27 Jul 2026 15:04:30 +0000 Subject: [PATCH 0851/1433] wifi: mt76: fix queue assignment for disassoc packets Like deauth, a disassoc frame sent to a client in powersave mode can get stuck in a tx queue along with other buffered frames, filling up hardware queues with frames that are only released after the WTBL slot is reused for another client. Move disassoc packets to the ALTX queue, matching the existing deauth handling. Fixes: dedf2ec30fe4 ("wifi: mt76: fix queue assignment for deauth packets") Signed-off-by: Peter Chiu Link: https://patch.msgid.link/20260727150434.1778520-11-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/tx.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index dc8407be2891..b03be0eb4712 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -631,6 +631,7 @@ mt76_txq_schedule_pending_wcid(struct mt76_phy *phy, struct mt76_wcid *wcid, !ieee80211_is_data_present(hdr->frame_control) && (!ieee80211_is_bufferable_mmpdu(skb) || ieee80211_is_deauth(hdr->frame_control) || + ieee80211_is_disassoc(hdr->frame_control) || head == &wcid->tx_offchannel)) qid = MT_TXQ_PSD; From de3afeafbc5397711eb1c22367d286bd02dbcaab Mon Sep 17 00:00:00 2001 From: Howard Hsu Date: Mon, 27 Jul 2026 15:04:31 +0000 Subject: [PATCH 0852/1433] wifi: mt76: mt7996: reject iTWT setup requests from MLD stations The firmware does not support individual TWT agreements with non-AP MLDs, and the driver only tracks TWT flow state on the default link, which may not be the link the agreement was negotiated on. Reject TWT setup requests from MLD stations instead of programming an unsupported configuration. Signed-off-by: Howard Hsu Link: https://patch.msgid.link/20260727150434.1778520-12-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 4794dacb3756..1cba381a78b8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -3218,6 +3218,12 @@ void mt7996_mac_add_twt_setup(struct ieee80211_hw *hw, struct mt7996_twt_flow *flow; u8 flowid, table_id, exp; + /* the firmware does not support iTWT agreements with MLD peers, and + * driver TWT state is only tracked on the default link + */ + if (sta->mlo) + goto out; + if (mt7996_mac_check_twt_req(twt)) goto out; From 6613d2104f12046ae64c6611a3519d622935cf4e Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 27 Jul 2026 15:04:32 +0000 Subject: [PATCH 0853/1433] wifi: mt76: mt7915: update SKU power limits after changing antennas Changing the antenna configuration updates the stream capabilities but left the per-path SKU power limits and path delta compensation stale until the next channel switch. Reapply the SKU table like mt7996 already does. Link: https://patch.msgid.link/20260727150434.1778520-13-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c index 4783e5f52d22..a8286f8becf9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c @@ -1158,6 +1158,7 @@ mt7915_set_antenna(struct ieee80211_hw *hw, int radio_idx, u32 tx_ant, u32 rx_an mt76_set_stream_caps(phy->mt76, true); mt7915_set_stream_vht_txbf_caps(phy); mt7915_set_stream_he_caps(phy); + mt7915_mcu_set_txpower_sku(phy); mutex_unlock(&dev->mt76.mutex); From d8eb7952fa1e35a350037aa351ff3c637285c735 Mon Sep 17 00:00:00 2001 From: StanleyYP Wang Date: Mon, 27 Jul 2026 15:04:33 +0000 Subject: [PATCH 0854/1433] wifi: mt76: mt7996: add scan dwell time hw cap Add NL80211_EXT_FEATURE_SET_SCAN_DWELL support in driver. This allows user to specify channel dwell time during scanning. Signed-off-by: StanleyYP Wang Link: https://patch.msgid.link/20260727150434.1778520-14-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index ee1f7a9f4851..70902395bc96 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -527,6 +527,7 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed) wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_ACK_SIGNAL_SUPPORT); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_CAN_REPLACE_PTK0); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_MU_MIMO_AIR_SNIFFER); + wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SET_SCAN_DWELL); if (mt7996_eeprom_has_background_radar(dev) && (!mdev->dev->of_node || From fc7801b7f11d99abdc2fa0e77e2682dede3e7f56 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 27 Jul 2026 15:04:34 +0000 Subject: [PATCH 0855/1433] wifi: mt76: mt7925: fix infinite loop in UNI event TLV parsing The event TLV loops accept a zero-length TLV, which advances neither the cursor nor the remaining length, so a malformed event hangs the caller. mt7925_mcu_uni_roc_event() additionally walked past the end of the skb, since it never checked the declared length against the remainder. Replace the five open-coded loops with a shared iterator that rejects lengths below the TLV header and beyond the remaining buffer, and check the per-tag payload sizes before dereferencing them. While here, make the RSSI monitor event read from the current TLV rather than from the start of the list. Link: https://patch.msgid.link/20260727150434.1778520-15-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/main.c | 10 ++--- .../net/wireless/mediatek/mt76/mt7925/mcu.c | 38 +++++++++++-------- .../net/wireless/mediatek/mt76/mt7925/mcu.h | 19 ++++++++++ 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 2d79a895713c..475a580b348d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -1562,7 +1562,7 @@ void mt7925_scan_work(struct work_struct *work) while (true) { struct sk_buff *skb; struct tlv *tlv; - int tlv_len; + u32 tlv_len; spin_lock_bh(&phy->dev->mt76.lock); skb = __skb_dequeue(&phy->scan_event_list); @@ -1575,7 +1575,7 @@ void mt7925_scan_work(struct work_struct *work) tlv = (struct tlv *)skb->data; tlv_len = skb->len; - while (tlv_len > 0 && le16_to_cpu(tlv->len) <= tlv_len) { + mt7925_for_each_tlv(tlv, tlv_len) { struct mt7925_mcu_scan_chinfo_event *evt; switch (le16_to_cpu(tlv->tag)) { @@ -1588,6 +1588,9 @@ void mt7925_scan_work(struct work_struct *work) } break; case UNI_EVENT_SCAN_DONE_CHNLINFO: + if (le16_to_cpu(tlv->len) < sizeof(*tlv) + sizeof(*evt)) + break; + evt = (struct mt7925_mcu_scan_chinfo_event *)tlv->data; mt7925_regd_change(phy, evt->alpha2); @@ -1599,9 +1602,6 @@ void mt7925_scan_work(struct work_struct *work) default: break; } - - tlv_len -= le16_to_cpu(tlv->len); - tlv = (struct tlv *)((char *)(tlv) + le16_to_cpu(tlv->len)); } dev_kfree_skb(skb); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index a6f28aa51a2f..fa29c486a455 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -409,16 +409,18 @@ mt7925_mcu_uni_hif_ctrl_event(struct mt792x_dev *dev, struct sk_buff *skb) tlv = (struct tlv *)skb->data; tlv_len = skb->len; - while (tlv_len > 0 && le16_to_cpu(tlv->len) <= tlv_len) { + mt7925_for_each_tlv(tlv, tlv_len) { switch (le16_to_cpu(tlv->tag)) { case UNI_EVENT_HIF_CTRL_BASIC: + if (le16_to_cpu(tlv->len) < + sizeof(struct mt7925_mcu_hif_ctrl_basic_tlv)) + break; + mt7925_mcu_handle_hif_ctrl_basic(dev, tlv); break; default: break; } - tlv_len -= le16_to_cpu(tlv->len); - tlv = (struct tlv *)((char *)(tlv) + le16_to_cpu(tlv->len)); } } @@ -426,22 +428,24 @@ static void mt7925_mcu_uni_roc_event(struct mt792x_dev *dev, struct sk_buff *skb) { struct tlv *tlv; - int i = 0; + u32 tlv_len; skb_pull(skb, sizeof(struct mt7925_mcu_rxd) + 4); + tlv = (struct tlv *)skb->data; + tlv_len = skb->len; - while (i < skb->len) { - tlv = (struct tlv *)(skb->data + i); - + mt7925_for_each_tlv(tlv, tlv_len) { switch (le16_to_cpu(tlv->tag)) { case UNI_EVENT_ROC_GRANT: + if (le16_to_cpu(tlv->len) < + sizeof(struct mt7925_roc_grant_tlv)) + break; + mt7925_mcu_roc_handle_grant(dev, tlv); break; case UNI_EVENT_ROC_GRANT_SUB_LINK: break; } - - i += le16_to_cpu(tlv->len); } } @@ -477,12 +481,15 @@ mt7925_mcu_tx_done_event(struct mt792x_dev *dev, struct sk_buff *skb) tlv = (struct tlv *)skb->data; tlv_len = skb->len; - while (tlv_len > 0 && le16_to_cpu(tlv->len) <= tlv_len) { + mt7925_for_each_tlv(tlv, tlv_len) { switch (le16_to_cpu(tlv->tag)) { case UNI_EVENT_TX_DONE_MSG: if (!is_mt7928(&dev->mt76)) break; + if (le16_to_cpu(tlv->len) < sizeof(*evt)) + break; + evt = (struct mt7928_uni_txdone_event *)tlv; if (evt->status) { dev_info(dev->mt76.dev, @@ -509,8 +516,6 @@ mt7925_mcu_tx_done_event(struct mt792x_dev *dev, struct sk_buff *skb) default: break; } - tlv_len -= le16_to_cpu(tlv->len); - tlv = (struct tlv *)((char *)(tlv) + le16_to_cpu(tlv->len)); } } @@ -547,10 +552,13 @@ mt7925_mcu_rssi_monitor_event(struct mt792x_dev *dev, struct sk_buff *skb) tlv = (struct tlv *)skb->data; tlv_len = skb->len; - while (tlv_len > 0 && le16_to_cpu(tlv->len) <= tlv_len) { + mt7925_for_each_tlv(tlv, tlv_len) { switch (le16_to_cpu(tlv->tag)) { case UNI_EVENT_RSSI_MONITOR_INFO: - event = (struct mt7925_uni_rssi_monitor_event *)skb->data; + if (le16_to_cpu(tlv->len) < sizeof(*event)) + break; + + event = (struct mt7925_uni_rssi_monitor_event *)tlv; ieee80211_iterate_active_interfaces_atomic(dev->mt76.hw, IEEE80211_IFACE_ITER_RESUME_ALL, mt7925_mcu_rssi_monitor_iter, @@ -559,8 +567,6 @@ mt7925_mcu_rssi_monitor_event(struct mt792x_dev *dev, struct sk_buff *skb) default: break; } - tlv_len -= le16_to_cpu(tlv->len); - tlv = (struct tlv *)((char *)(tlv) + le16_to_cpu(tlv->len)); } } diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h index 154f792a56bc..11f9eac13ffc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h @@ -699,6 +699,25 @@ mt7925_mcu_get_cipher(int cipher) } } +static inline bool +mt7925_mcu_tlv_valid(struct tlv *tlv, u32 rem) +{ + u16 len; + + if (rem < sizeof(*tlv)) + return false; + + len = le16_to_cpu(tlv->len); + + /* a length below the header size would not advance the cursor */ + return len >= sizeof(*tlv) && len <= rem; +} + +#define mt7925_for_each_tlv(tlv, rem) \ + for (; mt7925_mcu_tlv_valid(tlv, rem); \ + (rem) -= le16_to_cpu((tlv)->len), \ + (tlv) = (struct tlv *)((u8 *)(tlv) + le16_to_cpu((tlv)->len))) + int mt7925_mcu_set_dbdc(struct mt76_phy *phy, bool enable); int mt7925_mcu_hw_scan(struct mt76_phy *phy, struct ieee80211_vif *vif, struct ieee80211_scan_request *scan_req); From 5f23b939a3f7a70bfd552800077ca1110fc55022 Mon Sep 17 00:00:00 2001 From: Jeff Hsu Date: Mon, 6 Jul 2026 10:43:06 +0800 Subject: [PATCH 0856/1433] wifi: mt76: mt7925: skip DMA re-init selectively Add a flag to skip it on chips that don't need it. Signed-off-by: Jeff Hsu Link: https://patch.msgid.link/20260706024306.47806-1-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/pci.c | 3 +++ drivers/net/wireless/mediatek/mt76/mt792x.h | 1 + drivers/net/wireless/mediatek/mt76/mt792x_dma.c | 3 +++ 3 files changed, 7 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c index 4e734f4f65d1..02ef09dd797d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c @@ -650,6 +650,9 @@ static int mt7925_pci_probe(struct pci_dev *pdev, if (!mt7925_disable_aspm && mt76_pci_aspm_supported(pdev)) dev->aspm_supported = true; + if (is_mt7928(&dev->mt76)) + dev->skip_wpdma_reinit = true; + ret = __mt792x_mcu_fw_pmctrl(dev); if (ret) goto err_free_dev; diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index 9567ff883b28..9efc251cb745 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -312,6 +312,7 @@ struct mt792x_dev { bool hif_idle:1; bool hif_resumed:1; bool regd_change:1; + bool skip_wpdma_reinit:1; wait_queue_head_t wait; struct work_struct init_work; diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c index 8ad94fa58340..e74b95fdc767 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c @@ -418,6 +418,9 @@ int mt792x_wpdma_reinit_cond(struct mt792x_dev *dev) struct mt76_connac_pm *pm = &dev->pm; int err; + if (dev->skip_wpdma_reinit) + return 0; + /* check if the wpdma must be reinitialized */ if (mt792x_dma_need_reinit(dev)) { /* disable interrutpts */ From f0be077b7b66aa4e01b07b8c67ee1f347fe9c9f7 Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Thu, 23 Jul 2026 21:04:30 +0300 Subject: [PATCH 0857/1433] wifi: mt76: mt7915: simplify mt7915_sys_recovery_set() Use convenient ' kstrtou16_from_user()' to simplify 'mt7915_sys_recovery_set()'. Signed-off-by: Dmitry Antipov Link: https://patch.msgid.link/20260723180430.747789-1-dmantipov@yandex.ru Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7915/debugfs.c | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7915/debugfs.c index 4d0854fe785b..0413b4fd2d9c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/debugfs.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/debugfs.c @@ -52,23 +52,12 @@ mt7915_sys_recovery_set(struct file *file, const char __user *user_buf, struct mt7915_phy *phy = file->private_data; struct mt7915_dev *dev = phy->dev; bool band = phy->mt76->band_idx; - char buf[16]; int ret = 0; u16 val; - if (count >= sizeof(buf)) - return -EINVAL; - - if (copy_from_user(buf, user_buf, count)) - return -EFAULT; - - if (count && buf[count - 1] == '\n') - buf[count - 1] = '\0'; - else - buf[count] = '\0'; - - if (kstrtou16(buf, 0, &val)) - return -EINVAL; + ret = kstrtou16_from_user(user_buf, count, 0, &val); + if (ret) + return ret; switch (val) { /* From 404c4e564f6b1eeffd10bf2b2d3b86620f5794c3 Mon Sep 17 00:00:00 2001 From: "shengwei.lu" Date: Thu, 23 Jul 2026 11:11:08 +0800 Subject: [PATCH 0858/1433] wifi: mt76: mt7925: Fix EHT Beamformee SS subfields to meet 802.11be minimum Per IEEE 802.11be, the Beamformee SS <= 80/160/320 MHz 3-bit subfields in the EHT PHY Capabilities are encoded as (Nss - 1) and are required to be >= 3 (i.e. at least 4 SS receive capability) whenever SU Beamformee is advertised. MT7925 is a 2x2 STA (sts = 2), so directly filling (sts - 1) = 1 violates the spec minimum. Clamp the encoded value to 3 when sts <= 3, otherwise use (sts - 1). This is applied consistently to the BEAMFORMEE_SS <= 80 MHz (split across phy_cap_info[0]/[1]), <= 160 MHz and <= 320 MHz (6 GHz only) subfields. Fixes: c948b5da6bbe ("wifi: mt76: mt7925: add Mediatek Wi-Fi7 driver for mt7925 chips") Signed-off-by: shengwei.lu Link: https://patch.msgid.link/20260723031108.2017653-1-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 475a580b348d..84b55f008b3d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -188,19 +188,21 @@ mt7925_init_eht_caps(struct mt792x_phy *phy, enum nl80211_band band, eht_cap_elem->phy_cap_info[0] |= IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ; + val = (sts > 3) ? sts - 1 : 3; + eht_cap_elem->phy_cap_info[0] |= - u8_encode_bits(u8_get_bits(sts - 1, BIT(0)), + u8_encode_bits(u8_get_bits(val, BIT(0)), IEEE80211_EHT_PHY_CAP0_BEAMFORMEE_SS_80MHZ_MASK); eht_cap_elem->phy_cap_info[1] = - u8_encode_bits(u8_get_bits(sts - 1, GENMASK(2, 1)), + u8_encode_bits(u8_get_bits(val, GENMASK(2, 1)), IEEE80211_EHT_PHY_CAP1_BEAMFORMEE_SS_80MHZ_MASK) | - u8_encode_bits(sts - 1, + u8_encode_bits(val, IEEE80211_EHT_PHY_CAP1_BEAMFORMEE_SS_160MHZ_MASK); if (band == NL80211_BAND_6GHZ && is_320mhz_supported(&phy->dev->mt76)) eht_cap_elem->phy_cap_info[1] |= - u8_encode_bits(sts - 1, + u8_encode_bits(val, IEEE80211_EHT_PHY_CAP1_BEAMFORMEE_SS_320MHZ_MASK); eht_cap_elem->phy_cap_info[2] = From a547b414bf7ea9877456b5630573293cc0e2cae7 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sat, 1 Aug 2026 14:53:25 +0000 Subject: [PATCH 0859/1433] wifi: mt76: add PS buffering support for HW-managed TIM drivers Add MT_DRV_HW_PS_BUFFERING flag for drivers where firmware controls the TIM bit based on buffered frames. Instead of blocking all TX to PS stations (which starves firmware and prevents TIM from being set), allow limited frame delivery using AQL pending airtime as the throttle. Replenish the firmware buffer on TX completion. Add mt76_sta_ps_transition() helper for drivers to call from MCU PS sync events. Link: https://patch.msgid.link/20260801145334.1166751-1-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76.h | 3 + .../wireless/mediatek/mt76/mt76_connac_mcu.h | 1 + drivers/net/wireless/mediatek/mt76/tx.c | 77 +++++++++++++++++-- 3 files changed, 75 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 640061276d76..dc160d9ae537 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -539,6 +539,7 @@ struct mt76_hw_cap { #define MT_DRV_HW_MGMT_TXQ BIT(4) #define MT_DRV_AMSDU_OFFLOAD BIT(5) #define MT_DRV_IGNORE_TXS_FAILED BIT(6) +#define MT_DRV_HW_PS_BUFFERING BIT(7) struct mt76_driver_ops { u32 drv_flags; @@ -1541,6 +1542,8 @@ void mt76_release_buffered_frames(struct ieee80211_hw *hw, u16 tids, int nframes, enum ieee80211_frame_release_type reason, bool more_data); +void mt76_sta_ps_transition(struct mt76_dev *dev, struct mt76_wcid *wcid, + bool ps); bool mt76_has_tx_pending(struct mt76_phy *phy); int mt76_update_channel(struct mt76_phy *phy); void mt76_update_survey(struct mt76_phy *phy); diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h index 51380849d24e..0ededa569e9a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h @@ -1094,6 +1094,7 @@ enum { MCU_UNI_EVENT_IE_COUNTDOWN = 0x09, MCU_UNI_EVENT_COREDUMP = 0x0a, MCU_UNI_EVENT_BSS_BEACON_LOSS = 0x0c, + MCU_UNI_EVENT_PS_SYNC = 0x0d, MCU_UNI_EVENT_SCAN_DONE = 0x0e, MCU_UNI_EVENT_RDD_REPORT = 0x11, MCU_UNI_EVENT_ROC = 0x27, diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index b03be0eb4712..12c615b1294b 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -267,6 +267,21 @@ void __mt76_tx_complete_skb(struct mt76_dev *dev, u16 wcid_idx, struct sk_buff * wcid = __mt76_wcid_ptr(dev, wcid_idx); mt76_tx_check_non_aql(dev, wcid, skb); + if (wcid && (dev->drv->drv_flags & MT_DRV_HW_PS_BUFFERING) && + test_bit(MT_WCID_FLAG_PS, &wcid->flags)) { + struct ieee80211_sta *sta = wcid_to_sta(wcid); + + if (sta) { + struct ieee80211_hw *hw = mt76_phy_hw(dev, wcid->phy_idx); + int i; + + for (i = 0; i < ARRAY_SIZE(sta->txq); i++) + if (sta->txq[i]) + ieee80211_schedule_txq(hw, sta->txq[i]); + mt76_worker_schedule(&dev->tx_worker); + } + } + #ifdef CONFIG_NL80211_TESTMODE if (mt76_is_testmode_skb(dev, skb, &hw)) { struct mt76_phy *phy = hw->priv; @@ -476,8 +491,12 @@ mt76_txq_send_burst(struct mt76_phy *phy, struct mt76_queue *q, bool stop = false; int idx; - if (test_bit(MT_WCID_FLAG_PS, &wcid->flags)) - return 0; + if (test_bit(MT_WCID_FLAG_PS, &wcid->flags)) { + if (!(dev->drv->drv_flags & MT_DRV_HW_PS_BUFFERING)) + return 0; + if (ieee80211_txq_aql_pending(phy->hw, txq)) + return 0; + } if (atomic_read(&wcid->non_aql_packets) >= MT_MAX_NON_AQL_PKT) return 0; @@ -497,6 +516,9 @@ mt76_txq_send_burst(struct mt76_phy *phy, struct mt76_queue *q, if (idx < 0) return idx; + if (test_bit(MT_WCID_FLAG_PS, &wcid->flags)) + goto out; + do { if (test_bit(MT76_RESET, &phy->state) || phy->offchannel) break; @@ -522,6 +544,7 @@ mt76_txq_send_burst(struct mt76_phy *phy, struct mt76_queue *q, n_frames++; } while (1); +out: spin_lock(&q->lock); dev->queue_ops->kick(dev, q); spin_unlock(&q->lock); @@ -548,10 +571,7 @@ mt76_txq_schedule_list(struct mt76_phy *phy, enum mt76_txq_id qid) mtxq = (struct mt76_txq *)txq->drv_priv; wcid = __mt76_wcid_ptr(dev, mtxq->wcid); - if (!wcid || test_bit(MT_WCID_FLAG_PS, &wcid->flags)) - continue; - - if (atomic_read(&wcid->non_aql_packets) >= MT_MAX_NON_AQL_PKT) + if (!wcid) continue; phy = mt76_dev_phy(dev, wcid->phy_idx); @@ -559,6 +579,24 @@ mt76_txq_schedule_list(struct mt76_phy *phy, enum mt76_txq_id qid) continue; q = phy->q_tx[qid]; + + if (test_bit(MT_WCID_FLAG_PS, &wcid->flags)) { + if (!(dev->drv->drv_flags & MT_DRV_HW_PS_BUFFERING)) + continue; + + if (!mt76_txq_stopped(q)) + n_frames = mt76_txq_send_burst(phy, q, mtxq, wcid); + + ieee80211_return_txq(phy->hw, txq, false); + + if (unlikely(n_frames < 0)) + return n_frames; + ret += n_frames; + continue; + } + + if (atomic_read(&wcid->non_aql_packets) >= MT_MAX_NON_AQL_PKT) + continue; if (dev->queue_ops->tx_cleanup && q->queued + 2 * MT_TXQ_FREE_THR >= q->ndesc) { dev->queue_ops->tx_cleanup(dev, q, false); @@ -765,6 +803,33 @@ void mt76_stop_tx_queues(struct mt76_phy *phy, struct ieee80211_sta *sta, } EXPORT_SYMBOL_GPL(mt76_stop_tx_queues); +void mt76_sta_ps_transition(struct mt76_dev *dev, struct mt76_wcid *wcid, + bool ps) +{ + struct ieee80211_sta *sta; + struct ieee80211_hw *hw; + int i; + + if (ps) { + set_bit(MT_WCID_FLAG_PS, &wcid->flags); + mt76_worker_schedule(&dev->tx_worker); + return; + } + + clear_bit(MT_WCID_FLAG_PS, &wcid->flags); + + sta = wcid_to_sta(wcid); + if (!sta) + return; + + hw = mt76_phy_hw(dev, wcid->phy_idx); + for (i = 0; i < ARRAY_SIZE(sta->txq); i++) + if (sta->txq[i]) + ieee80211_schedule_txq(hw, sta->txq[i]); + mt76_worker_schedule(&dev->tx_worker); +} +EXPORT_SYMBOL_GPL(mt76_sta_ps_transition); + void mt76_wake_tx_queue(struct ieee80211_hw *hw, struct ieee80211_txq *txq) { struct mt76_phy *phy = hw->priv; From dc930cf883c81799f11f4da33a3711cad983de24 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sat, 1 Aug 2026 14:53:26 +0000 Subject: [PATCH 0860/1433] wifi: mt76: mt7915: handle MCU PS sync events Enable MT_DRV_HW_PS_BUFFERING and handle MCU_EXT_EVENT_PS_SYNC to track station power-save state via firmware notifications. Link: https://patch.msgid.link/20260801145334.1166751-2-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7915/mcu.c | 18 ++++++++++++++++++ .../net/wireless/mediatek/mt76/mt7915/mcu.h | 9 +++++++++ .../net/wireless/mediatek/mt76/mt7915/mmio.c | 3 ++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c index 88955aed62e2..5bfe0366562f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c @@ -402,6 +402,21 @@ mt7915_mcu_rx_bcc_notify(struct mt7915_dev *dev, struct sk_buff *skb) mt7915_mcu_cca_finish, mphy->hw); } +static void +mt7915_mcu_rx_ps_sync(struct mt7915_dev *dev, struct sk_buff *skb) +{ + struct mt7915_mcu_ps_notify *p = (void *)skb->data; + struct mt76_wcid *wcid; + u16 wcid_idx; + + wcid_idx = p->wtbl_lower | (p->wtbl_higher << 8); + wcid = mt76_wcid_ptr(dev, wcid_idx); + if (!wcid || !wcid_to_sta(wcid)) + return; + + mt76_sta_ps_transition(&dev->mt76, wcid, !!p->ps_bit); +} + static void mt7915_mcu_rx_ext_event(struct mt7915_dev *dev, struct sk_buff *skb) { @@ -424,6 +439,9 @@ mt7915_mcu_rx_ext_event(struct mt7915_dev *dev, struct sk_buff *skb) case MCU_EXT_EVENT_BCC_NOTIFY: mt7915_mcu_rx_bcc_notify(dev, skb); break; + case MCU_EXT_EVENT_PS_SYNC: + mt7915_mcu_rx_ps_sync(dev, skb); + break; default: break; } diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h index 7c472062a90e..76e39e899797 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h @@ -54,6 +54,15 @@ struct mt7915_mcu_bcc_notify { u8 rsv; } __packed; +struct mt7915_mcu_ps_notify { + struct mt76_connac2_mcu_rxd_hdr rxd; + + u8 wtbl_lower; + u8 ps_bit; + u8 wtbl_higher; + u8 rsv; +} __packed; + struct mt7915_mcu_rdd_report { struct mt76_connac2_mcu_rxd_hdr rxd; diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mmio.c b/drivers/net/wireless/mediatek/mt76/mt7915/mmio.c index 2708b1556f40..28377c964843 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mmio.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mmio.c @@ -921,7 +921,8 @@ struct mt7915_dev *mt7915_mmio_probe(struct device *pdev, /* txwi_size = txd size + txp size */ .txwi_size = MT_TXD_SIZE + sizeof(struct mt76_connac_fw_txp), .drv_flags = MT_DRV_TXWI_NO_FREE | MT_DRV_HW_MGMT_TXQ | - MT_DRV_AMSDU_OFFLOAD, + MT_DRV_AMSDU_OFFLOAD | + MT_DRV_HW_PS_BUFFERING, .survey_flags = SURVEY_INFO_TIME_TX | SURVEY_INFO_TIME_RX | SURVEY_INFO_TIME_BSS_RX, From 2d16caccd278bdad4299a7093104f29ac73ebed2 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sat, 1 Aug 2026 14:53:27 +0000 Subject: [PATCH 0861/1433] wifi: mt76: mt7996: handle UNI PS sync events Enable MT_DRV_HW_PS_BUFFERING and handle MCU_UNI_EVENT_PS_SYNC with all three TLV formats (single client, multi-client packed entries, and bitmap). Link: https://patch.msgid.link/20260801145334.1166751-3-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/mcu.c | 86 +++++++++++++++++++ .../net/wireless/mediatek/mt76/mt7996/mcu.h | 33 +++++++ .../net/wireless/mediatek/mt76/mt7996/mmio.c | 3 +- 3 files changed, 121 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 8aab91810135..cd7587ce5b8a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -826,6 +826,89 @@ mt7996_mcu_wed_rro_event(struct mt7996_dev *dev, struct sk_buff *skb) } } +static void +mt7996_mcu_ps_transition(struct mt7996_dev *dev, u16 wcid_idx, bool ps) +{ + struct mt76_wcid *wcid; + + wcid = mt76_wcid_ptr(dev, wcid_idx); + if (!wcid || !wcid_to_sta(wcid)) + return; + + mt76_sta_ps_transition(&dev->mt76, wcid, ps); +} + +static void +mt7996_mcu_rx_ps_sync(struct mt7996_dev *dev, struct sk_buff *skb) +{ + struct mt7996_mcu_ps_sync_event *event = (void *)skb->data; + struct tlv *tlv; + int len; + + if (skb->len < sizeof(*event)) + return; + + skb_pull(skb, sizeof(*event)); + + len = skb->len; + while (len >= sizeof(*tlv)) { + u16 tag, tag_len; + + tlv = (struct tlv *)skb->data; + tag = le16_to_cpu(tlv->tag); + tag_len = le16_to_cpu(tlv->len); + /* a tag_len below the header size would not advance the buffer */ + if (tag_len < sizeof(*tlv) || tag_len > len) + break; + + switch (tag) { + case UNI_PS_CLIENT_INFO: { + struct mt7996_mcu_ps_client_info *info = (void *)tlv; + + if (tag_len < sizeof(*info)) + break; + + mt7996_mcu_ps_transition(dev, + le16_to_cpu(info->wlan_idx), + info->ps_bit); + break; + } + case UNI_PS_MULTI_CLIENT_INFO: { + struct mt7996_mcu_ps_multi_client_info *info = (void *)tlv; + u16 cnt; + int i; + + if (tag_len < sizeof(*info)) + break; + + cnt = min_t(u16, le16_to_cpu(info->sta_cnt), + (tag_len - sizeof(*info)) / sizeof(info->sta_ps_info[0])); + for (i = 0; i < cnt; i++) { + u16 entry = le16_to_cpu(info->sta_ps_info[i]); + + mt7996_mcu_ps_transition(dev, + FIELD_GET(MT7996_PS_MULTI_WCID, entry), + !!(entry & MT7996_PS_MULTI_PS_BIT)); + } + break; + } + case UNI_PS_MULTI_CLIENT_INFO_BITMAP: { + u8 *bitmap = tlv->data; + int bitmap_len = tag_len - sizeof(*tlv); + int i; + + for (i = 0; i < bitmap_len * 8; i++) + mt7996_mcu_ps_transition(dev, i, + !!(bitmap[i / 8] & BIT(i % 8))); + break; + } + } + + skb_pull(skb, tag_len); + len -= tag_len; + } +} + static void mt7996_mcu_uni_rx_unsolicited_event(struct mt7996_dev *dev, struct sk_buff *skb) { @@ -847,6 +930,9 @@ mt7996_mcu_uni_rx_unsolicited_event(struct mt7996_dev *dev, struct sk_buff *skb) case MCU_UNI_EVENT_WED_RRO: mt7996_mcu_wed_rro_event(dev, skb); break; + case MCU_UNI_EVENT_PS_SYNC: + mt7996_mcu_rx_ps_sync(dev, skb); + break; default: break; } diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index 1487d33a8dbb..74b70fb6da3d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -293,6 +293,39 @@ enum { UNI_WED_RRO_BA_SESSION_DELETE, }; +struct mt7996_mcu_ps_sync_event { + struct mt7996_mcu_rxd rxd; + + u8 bss_idx; + u8 __rsv[3]; +} __packed; + +struct mt7996_mcu_ps_client_info { + __le16 tag; + __le16 len; + u8 ps_bit; + u8 __rsv; + __le16 wlan_idx; + u8 buffer_size; + u8 __rsv2[3]; +} __packed; + +struct mt7996_mcu_ps_multi_client_info { + __le16 tag; + __le16 len; + __le16 sta_cnt; + __le16 sta_ps_info[]; +} __packed; + +#define MT7996_PS_MULTI_WCID GENMASK(10, 0) +#define MT7996_PS_MULTI_PS_BIT BIT(15) + +enum { + UNI_PS_CLIENT_INFO = 0, + UNI_PS_MULTI_CLIENT_INFO = 1, + UNI_PS_MULTI_CLIENT_INFO_BITMAP = 2, +}; + struct mt7996_mcu_thermal_notify { struct mt7996_mcu_rxd rxd; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c index ac81be5fe023..fbfbb96a742f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c @@ -838,7 +838,8 @@ struct mt7996_dev *mt7996_mmio_probe(struct device *pdev, .link_data_size = sizeof(struct mt7996_vif_link), .drv_flags = MT_DRV_TXWI_NO_FREE | MT_DRV_AMSDU_OFFLOAD | - MT_DRV_HW_MGMT_TXQ, + MT_DRV_HW_MGMT_TXQ | + MT_DRV_HW_PS_BUFFERING, .survey_flags = SURVEY_INFO_TIME_TX | SURVEY_INFO_TIME_RX | SURVEY_INFO_TIME_BSS_RX, From 0b8d2524716099ea4743fa65bc364f94b3106a31 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sat, 1 Aug 2026 14:53:28 +0000 Subject: [PATCH 0862/1433] wifi: mt76: set the EOSP bit in the QoS header of the last released frame When the driver implements .release_buffered_frames, mac80211 leaves the U-APSD signalling entirely to the driver: "In this case it is also responsible for setting the EOSP flag in the QoS header of the frames" (include/net/mac80211.h). Only IEEE80211_TX_STATUS_EOSP was being set, which merely ends the service period inside mac80211, so on air the service period was never terminated. Clients that wait for EOSP before going back to doze keep the SP open and stop triggering, which stalls all downlink traffic for that station. Set the wire EOSP bit on the last frame of a U-APSD service period. EOSP has no meaning for a PS-Poll response, so pass the release reason down and leave those frames alone. Link: https://patch.msgid.link/20260801145334.1166751-4-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/tx.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index 12c615b1294b..3707ee19e4ae 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -412,16 +412,23 @@ mt76_txq_dequeue(struct mt76_phy *phy, struct mt76_txq *mtxq) static void mt76_queue_ps_skb(struct mt76_phy *phy, struct ieee80211_sta *sta, - struct sk_buff *skb, bool last) + struct sk_buff *skb, bool last, + enum ieee80211_frame_release_type reason) { struct mt76_wcid *wcid = (struct mt76_wcid *)sta->drv_priv; struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; info->control.flags |= IEEE80211_TX_CTRL_PS_RESPONSE; - if (last) + if (last) { info->flags |= IEEE80211_TX_STATUS_EOSP | IEEE80211_TX_CTL_REQ_TX_STATUS; + if (reason == IEEE80211_FRAME_RELEASE_UAPSD && + ieee80211_is_data_qos(hdr->frame_control)) + *ieee80211_get_qos_ctl(hdr) |= IEEE80211_QOS_CTL_EOSP; + } + mt76_skb_set_moredata(skb, !last); __mt76_tx_queue_skb(phy, MT_TXQ_PSD, skb, wcid, sta, NULL); } @@ -454,14 +461,15 @@ mt76_release_buffered_frames(struct ieee80211_hw *hw, struct ieee80211_sta *sta, nframes--; if (last_skb) - mt76_queue_ps_skb(phy, sta, last_skb, false); + mt76_queue_ps_skb(phy, sta, last_skb, false, + reason); last_skb = skb; } while (nframes); } if (last_skb) { - mt76_queue_ps_skb(phy, sta, last_skb, true); + mt76_queue_ps_skb(phy, sta, last_skb, true, reason); dev->queue_ops->kick(dev, hwq); } else { ieee80211_sta_eosp(sta); From 697badc27a9b3b2a8d86092abeed767714f735a2 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sat, 1 Aug 2026 14:53:29 +0000 Subject: [PATCH 0863/1433] wifi: mt76: mt7603: fix U-APSD service period termination Frames released from the driver PS queue were all tagged with MORE_DATA and none of them ever carried the EOSP bit, so from the client's point of view a U-APSD service period was started but never finished. Clients that keep their receiver on until EOSP arrives stop sending trigger frames, and all downlink traffic for that station stalls until they give up. ieee80211_sta_eosp() only cleared the service period state inside mac80211, which is why the mismatch went unnoticed. Assign MORE_DATA per frame and set the wire EOSP bit on the last one. If the last released frame is a bufferable MMPDU it has no QoS control field to carry EOSP, so let mac80211 append a QoS-Null frame instead. Also stop handing the remaining frame budget to mt76_release_buffered_frames() once frames have been released from the PS queue: both would signal the end of the same service period. Releasing fewer frames than requested is allowed, and MORE_DATA tells the client to trigger again. Link: https://patch.msgid.link/20260801145334.1166751-5-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7603/main.c | 76 ++++++++++++++++--- 1 file changed, 64 insertions(+), 12 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/main.c b/drivers/net/wireless/mediatek/mt76/mt7603/main.c index 0f3a7508996c..7c231995392b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7603/main.c @@ -423,13 +423,33 @@ mt7603_sta_ps(struct mt76_dev *mdev, struct ieee80211_sta *sta, bool ps) mt7603_ps_tx_list(dev, &list); } -static void -mt7603_ps_set_more_data(struct sk_buff *skb) +static struct ieee80211_hdr * +mt7603_ps_skb_hdr(struct sk_buff *skb) { - struct ieee80211_hdr *hdr; + return (struct ieee80211_hdr *)&skb->data[MT_TXD_SIZE]; +} - hdr = (struct ieee80211_hdr *)&skb->data[MT_TXD_SIZE]; - hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_MOREDATA); +/* + * Buffered frames can be recycled into the PS queue by mt7603_filter_tx(), so + * both bits have to be assigned, not just set. + */ +static void +mt7603_ps_set_flags(struct sk_buff *skb, bool more_data, bool eosp) +{ + struct ieee80211_hdr *hdr = mt7603_ps_skb_hdr(skb); + + if (more_data) + hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_MOREDATA); + else + hdr->frame_control &= ~cpu_to_le16(IEEE80211_FCTL_MOREDATA); + + if (!ieee80211_is_data_qos(hdr->frame_control)) + return; + + if (eosp) + *ieee80211_get_qos_ctl(hdr) |= IEEE80211_QOS_CTL_EOSP; + else + *ieee80211_get_qos_ctl(hdr) &= ~IEEE80211_QOS_CTL_EOSP; } static void @@ -442,7 +462,10 @@ mt7603_release_buffered_frames(struct ieee80211_hw *hw, struct mt7603_dev *dev = hw->priv; struct mt7603_sta *msta = (struct mt7603_sta *)sta->drv_priv; struct sk_buff_head list; - struct sk_buff *skb, *tmp; + struct sk_buff *skb, *tmp, *last; + bool eosp_null, uapsd; + u16 pending = 0; + u8 last_tid; __skb_queue_head_init(&list); @@ -458,20 +481,49 @@ mt7603_release_buffered_frames(struct ieee80211_hw *hw, skb_set_queue_mapping(skb, MT_TXQ_PSD); __skb_unlink(skb, &msta->psq); - mt7603_ps_set_more_data(skb); __skb_queue_tail(&list, skb); nframes--; } + + skb_queue_walk(&msta->psq, skb) + pending |= BIT(skb->priority); spin_unlock_bh(&dev->ps_lock); - if (!skb_queue_empty(&list)) - ieee80211_sta_eosp(sta); + last = skb_peek_tail(&list); + if (!last) { + mt76_release_buffered_frames(hw, sta, tids, nframes, reason, + more_data); + return; + } + + /* + * End the service period here instead of passing the remaining frame + * budget on to mt76_release_buffered_frames(), which would signal the + * end of the same service period a second time. + */ + uapsd = reason == IEEE80211_FRAME_RELEASE_UAPSD; + more_data |= !!(pending & tids); + + skb_queue_walk(&list, skb) + mt7603_ps_set_flags(skb, skb != last || more_data, + skb == last && uapsd); + + /* + * EOSP lives in the QoS control field, so a bufferable MMPDU cannot + * terminate a U-APSD service period on its own. In that case mac80211 + * has to append a QoS-Null frame, which ends the SP through its tx + * status. + */ + eosp_null = uapsd && + !ieee80211_is_data_qos(mt7603_ps_skb_hdr(last)->frame_control); + last_tid = __fls(tids); mt7603_ps_tx_list(dev, &list); - if (nframes) - mt76_release_buffered_frames(hw, sta, tids, nframes, reason, - more_data); + if (eosp_null) + ieee80211_send_eosp_nullfunc(sta, last_tid); + else + ieee80211_sta_eosp(sta); } static int From d5001f493fefdefd7322839384625f2d73812f93 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sat, 1 Aug 2026 14:53:30 +0000 Subject: [PATCH 0864/1433] wifi: mt76: mt7603: tell mac80211 when the PS queue has run empty mt7603_rx_loopback_skb() marks a TID as buffered when the hardware redirects a frame into the driver PS queue, but nothing ever clears that state again. mac80211 therefore keeps the TIM bit set for the station and keeps routing every service period to the driver, which also prevents it from releasing frames it has buffered itself. Clear the buffered state for every requested TID that has no frames left in the PS queue. Link: https://patch.msgid.link/20260801145334.1166751-6-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7603/main.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/main.c b/drivers/net/wireless/mediatek/mt76/mt7603/main.c index 7c231995392b..c968ee3d591a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7603/main.c @@ -464,8 +464,10 @@ mt7603_release_buffered_frames(struct ieee80211_hw *hw, struct sk_buff_head list; struct sk_buff *skb, *tmp, *last; bool eosp_null, uapsd; + unsigned long drained; u16 pending = 0; u8 last_tid; + int i; __skb_queue_head_init(&list); @@ -489,6 +491,15 @@ mt7603_release_buffered_frames(struct ieee80211_hw *hw, pending |= BIT(skb->priority); spin_unlock_bh(&dev->ps_lock); + /* + * Without this, mac80211 keeps the TIM bit set for the station and + * keeps routing every service period to the driver, even though there + * is nothing left to release. + */ + drained = tids & ~pending; + for_each_set_bit(i, &drained, IEEE80211_NUM_TIDS) + ieee80211_sta_set_buffered(sta, i, false); + last = skb_peek_tail(&list); if (!last) { mt76_release_buffered_frames(hw, sta, tids, nframes, reason, From 636a883e2feef5e4ff95695d3c372ceff03960f5 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sat, 1 Aug 2026 14:53:31 +0000 Subject: [PATCH 0865/1433] wifi: mt76: mt7603: restore hardware PS buffering after a service period Releasing buffered frames has to turn off the PSE redirect for the station, otherwise the released frames are looped straight back into the driver PS queue. Nothing ever turns it back on: mt7603_sta_ps() only runs on an observed PM bit transition, and MT_WCID_FLAG_PS keeps mt76 from reporting the same state twice. After the first service period the hardware therefore treats a dozing station as awake and transmits at it directly, which is where the retry storms and the packet loss reported against U-APSD clients come from. Re-arm hardware buffering from mt7603_mac_work() for every station that is still known to be asleep, once the PSD queue has drained and the released frames have passed the redirect stage. Frames that are still queued belong to the service period that was just served, so unlike on a sleep transition they must not be pulled back with mt7603_filter_tx(). Track the sleep state separately from the WTBL state, so a station that wakes up while the re-arm is pending is not put back to sleep. Link: https://patch.msgid.link/20260801145334.1166751-7-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7603/mac.c | 73 +++++++++++++++++-- .../net/wireless/mediatek/mt76/mt7603/main.c | 2 +- .../wireless/mediatek/mt76/mt7603/mt7603.h | 4 + 3 files changed, 72 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/mac.c b/drivers/net/wireless/mediatek/mt76/mt7603/mac.c index d3110eeb45d7..fa0bf9a201ce 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7603/mac.c @@ -235,16 +235,17 @@ void mt7603_wtbl_set_smps(struct mt7603_dev *dev, struct mt7603_sta *sta, sta->smps = enabled; } -void mt7603_wtbl_set_ps(struct mt7603_dev *dev, struct mt7603_sta *sta, - bool enabled) +static void +__mt7603_wtbl_set_ps(struct mt7603_dev *dev, struct mt7603_sta *sta, + bool enabled, bool filter) { int idx = sta->wcid.idx; u32 addr; - spin_lock_bh(&dev->ps_lock); + lockdep_assert_held(&dev->ps_lock); if (sta->ps == enabled) - goto out; + return; mt76_wr(dev, MT_PSE_RTA, FIELD_PREP(MT_PSE_RTA_TAG_ID, idx) | @@ -255,7 +256,7 @@ void mt7603_wtbl_set_ps(struct mt7603_dev *dev, struct mt7603_sta *sta, mt76_poll(dev, MT_PSE_RTA, MT_PSE_RTA_BUSY, 0, 5000); - if (enabled) + if (enabled && filter) mt7603_filter_tx(dev, sta->vif->idx, idx, false); addr = mt7603_wtbl1_addr(idx); @@ -264,8 +265,34 @@ void mt7603_wtbl_set_ps(struct mt7603_dev *dev, struct mt7603_sta *sta, enabled * MT_WTBL1_W3_POWER_SAVE); mt76_clear(dev, MT_WTBL1_OR, MT_WTBL1_OR_PSM_WRITE); sta->ps = enabled; +} -out: +void mt7603_wtbl_set_ps(struct mt7603_dev *dev, struct mt7603_sta *sta, + bool enabled) +{ + spin_lock_bh(&dev->ps_lock); + __mt7603_wtbl_set_ps(dev, sta, enabled, enabled); + spin_unlock_bh(&dev->ps_lock); +} + +void mt7603_wtbl_sta_ps(struct mt7603_dev *dev, struct mt7603_sta *sta, bool ps) +{ + spin_lock_bh(&dev->ps_lock); + sta->ps_sleeping = ps; + __mt7603_wtbl_set_ps(dev, sta, ps, ps); + spin_unlock_bh(&dev->ps_lock); +} + +void mt7603_wtbl_restore_ps(struct mt7603_dev *dev, struct mt7603_sta *sta) +{ + spin_lock_bh(&dev->ps_lock); + /* + * Frames that are already queued for the station belong to the service + * period that has just been served, so unlike on a sleep transition + * they must not be pulled back into the PS queue. + */ + if (sta->ps_sleeping) + __mt7603_wtbl_set_ps(dev, sta, true, false); spin_unlock_bh(&dev->ps_lock); } @@ -1816,6 +1843,39 @@ mt7603_false_cca_check(struct mt7603_dev *dev) mt7603_adjust_sensitivity(dev); } +/* + * Releasing buffered frames turns off the PSE redirect for a station, since + * the released frames would otherwise be looped back into the driver PS queue + * again. mac80211 never tells us when the service period is over, so hardware + * buffering has to be re-armed here for every station that is still known to + * be asleep. Waiting for the PSD queue to drain makes sure that the released + * frames have already passed the redirect stage. + */ +static void +mt7603_mac_ps_check(struct mt7603_dev *dev) +{ + int i; + + if (dev->mphy.q_tx[MT_TXQ_PSD]->queued) + return; + + rcu_read_lock(); + for (i = 0; i < MT7603_WTBL_STA; i++) { + struct mt76_wcid *wcid = mt76_wcid_ptr(dev, i); + struct mt7603_sta *msta; + + if (!wcid || !wcid->sta) + continue; + + msta = container_of(wcid, struct mt7603_sta, wcid); + if (msta->ps || !msta->ps_sleeping) + continue; + + mt7603_wtbl_restore_ps(dev, msta); + } + rcu_read_unlock(); +} + void mt7603_mac_work(struct work_struct *work) { struct mt7603_dev *dev = container_of(work, struct mt7603_dev, @@ -1830,6 +1890,7 @@ void mt7603_mac_work(struct work_struct *work) dev->mphy.mac_work_count++; mt76_update_survey(&dev->mphy); mt7603_edcca_check(dev); + mt7603_mac_ps_check(dev); for (i = 0, idx = 0; i < 2; i++) { u32 val = mt76_rr(dev, MT_TX_AGG_CNT(i)); diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/main.c b/drivers/net/wireless/mediatek/mt76/mt7603/main.c index c968ee3d591a..757664e9e1f2 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7603/main.c @@ -410,7 +410,7 @@ mt7603_sta_ps(struct mt76_dev *mdev, struct ieee80211_sta *sta, bool ps) struct sk_buff_head list; mt76_stop_tx_queues(&dev->mphy, sta, true); - mt7603_wtbl_set_ps(dev, msta, ps); + mt7603_wtbl_sta_ps(dev, msta, ps); if (ps) return; diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/mt7603.h b/drivers/net/wireless/mediatek/mt76/mt7603/mt7603.h index 071bfab3af7c..5b0ecd01aad9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/mt7603.h +++ b/drivers/net/wireless/mediatek/mt76/mt7603/mt7603.h @@ -80,6 +80,7 @@ struct mt7603_sta { u8 smps; u8 ps; + u8 ps_sleeping; }; struct mt7603_vif { @@ -229,6 +230,9 @@ int mt7603_wtbl_set_key(struct mt7603_dev *dev, int wcid, struct ieee80211_key_conf *key); void mt7603_wtbl_set_ps(struct mt7603_dev *dev, struct mt7603_sta *sta, bool enabled); +void mt7603_wtbl_sta_ps(struct mt7603_dev *dev, struct mt7603_sta *sta, + bool ps); +void mt7603_wtbl_restore_ps(struct mt7603_dev *dev, struct mt7603_sta *sta); void mt7603_wtbl_set_smps(struct mt7603_dev *dev, struct mt7603_sta *sta, bool enabled); void mt7603_filter_tx(struct mt7603_dev *dev, int mac_idx, int idx, bool abort); From 0bbd6c52dfff62b319963f54cd9370f9486e59d2 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sat, 1 Aug 2026 14:53:32 +0000 Subject: [PATCH 0866/1433] wifi: mt76: mt7603: file buffered frames under the TID reported to mac80211 The PS queue filed redirected frames under the TID taken from the TXD, while mac80211 was told about the TID taken from the QoS header, or TID 0 for anything that is not a QoS data frame. When the two disagree, mt7603_release_buffered_frames() skips the frame because it does not match the requested TIDs, so it stays in the PS queue until the station wakes up. Buffered MMPDUs hit this whenever they were sent on a non-zero TID. Use one TID for both. Link: https://patch.msgid.link/20260801145334.1166751-8-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7603/dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/dma.c b/drivers/net/wireless/mediatek/mt76/mt7603/dma.c index 3a16851524a0..477a9b7a8cf7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt7603/dma.c @@ -39,7 +39,6 @@ mt7603_rx_loopback_skb(struct mt7603_dev *dev, struct sk_buff *skb) val = le32_to_cpu(txd[1]); idx = FIELD_GET(MT_TXD1_WLAN_IDX, val); - skb->priority = FIELD_GET(MT_TXD1_TID, val); if (idx >= MT7603_WTBL_STA - 1) goto free; @@ -72,6 +71,7 @@ mt7603_rx_loopback_skb(struct mt7603_dev *dev, struct sk_buff *skb) hwq = MT_TX_HW_QUEUE_MGMT; } + skb->priority = tid; ieee80211_sta_set_buffered(sta, tid, true); val = le32_to_cpu(txd[0]); From 9ba744a28c26eaa5cae930688a22e01888395308 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sat, 1 Aug 2026 14:53:33 +0000 Subject: [PATCH 0867/1433] wifi: mt76: reject out-of-range link ids in mt76_vif_link() mt76_vif_link() indexes mvif->link[] without validating link_id, but callers pass mvif->deflink_id / msta->deflink_id, which hold IEEE80211_LINK_UNSPECIFIED (0xf) until the first link has been added. Since IEEE80211_MLD_MAX_NUM_LINKS is 15, that reads one element past the end of the array, aliasing mt76_vif_data.offchannel_link. Reachable via mt7996_set_tsf()/mt7996_offset_tsf() and mt7996_net_fill_forward_path(). Bounds check link_id and return NULL, matching mt7996_sta_link() and mt7996_sta_link_protected(). Fixes: a9384b36a42a ("wifi: mt76: mt7996: rework set/get_tsf callabcks to support MLO") Link: https://patch.msgid.link/20260801145334.1166751-9-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index dc160d9ae537..61ef1b80feb8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -2131,6 +2131,9 @@ mt76_vif_link(struct mt76_dev *dev, struct ieee80211_vif *vif, int link_id) if (!link_id) return mlink; + if (link_id >= IEEE80211_MLD_MAX_NUM_LINKS) + return NULL; + return mt76_dereference(mvif->link[link_id], dev); } From 4330a0ef9f75a54fde3548432a9a698f06bab635 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sat, 1 Aug 2026 14:53:34 +0000 Subject: [PATCH 0868/1433] wifi: mt76: mt7996: fix out-of-bounds link array access in mt7996_tx() When mac80211 leaves the link unspecified, mt7996_tx() substitutes the primary link id of the station or vif. That value is IEEE80211_LINK_UNSPECIFIED (0xf) until the first link has been added, and it is then used unchecked to index vif->link_conf[], mvif->mt76.link[] and sta->link[], all of which hold IEEE80211_MLD_MAX_NUM_LINKS (15) entries. Clamp the primary link id to the default link before using it, and use the clamped value for the link_sta fallback as well. Fixes: 1609b014aa29 ("wifi: mt76: mt7996: Overwrite unspecified link_id in mt7996_tx()") Link: https://patch.msgid.link/20260801145334.1166751-10-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 54e79bd25995..e218856b0c45 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1515,20 +1515,26 @@ static void mt7996_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif = info->control.vif; struct mt7996_vif *mvif = vif ? (void *)vif->drv_priv : NULL; struct mt76_wcid *wcid = &dev->mt76.global_wcid; + u8 deflink_id = IEEE80211_LINK_UNSPECIFIED; u8 link_id = u32_get_bits(info->control.flags, IEEE80211_TX_CTRL_MLO_LINK); rcu_read_lock(); + if (msta) + deflink_id = msta->deflink_id; + else if (mvif) + deflink_id = mvif->mt76.deflink_id; + + /* the primary link is unset until the first link has been added */ + if (deflink_id >= IEEE80211_MLD_MAX_NUM_LINKS) + deflink_id = 0; + /* Use primary link_id if the value from mac80211 is set to * IEEE80211_LINK_UNSPECIFIED. */ - if (link_id == IEEE80211_LINK_UNSPECIFIED) { - if (msta) - link_id = msta->deflink_id; - else if (mvif) - link_id = mvif->mt76.deflink_id; - } + if (link_id == IEEE80211_LINK_UNSPECIFIED) + link_id = deflink_id; if (vif && ieee80211_vif_is_mld(vif)) { struct ieee80211_bss_conf *link_conf; @@ -1538,7 +1544,7 @@ static void mt7996_tx(struct ieee80211_hw *hw, link_sta = rcu_dereference(sta->link[link_id]); if (!link_sta) - link_sta = rcu_dereference(sta->link[msta->deflink_id]); + link_sta = rcu_dereference(sta->link[deflink_id]); if (link_sta) { memcpy(hdr->addr1, link_sta->addr, ETH_ALEN); From 1e33f8acd837420160ea088160d8648a3db54c3b Mon Sep 17 00:00:00 2001 From: Reshma Immaculate Rajkumar Date: Wed, 29 Jul 2026 22:47:32 +0530 Subject: [PATCH 0869/1433] wifi: ath12k: fix encrypted EAPOL TX in encap offload mode When a vif operates with IEEE80211_OFFLOAD_ENCAP_ENABLED, mac80211 delivers EAPOL frames to ath12k in native-WiFi format. Unencrypted EAPOL frames used during the initial 4-way handshake are already handled through the existing is_diff_encap path. However, EAPOL frames transmitted during GTK rekeying carry ATH12K_SKB_CIPHER_SET and continue through the normal native-WiFi transmit path. Firmware encryption requires RAW frames with cipher-specific IV and ICV fields correctly provisioned in the skb. Passing encrypted EAPOL frames in native-WiFi format results in incorrect IV provisioning, leading to an invalid ICV and frame drop. Fix this by detecting the EAPOL frames that need HW encryption and converting them to firmware-encrypted RAW frames before transmission. Reserve IV space after the MAC header, append ICV space at the tail, select the appropriate firmware encryption type and request firmware-side encryption. Introduce ath12k_dp_tx_crypto_iv_len() and ath12k_dp_tx_crypto_icv_len() helpers in the TX path to obtain cipher-specific IV and ICV lengths. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01270-QCAHKSWPL_SILICONZ-1 Fixes: d29591d5b52e ("wifi: ath12k: Advertise encapsulation/decapsulation offload support to mac80211") Signed-off-by: Reshma Immaculate Rajkumar Reviewed-by: Aishwarya R Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260729171732.668367-1-reshma.rajkumar@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp_tx.c | 46 +++++++++++++ drivers/net/wireless/ath/ath12k/dp_tx.h | 2 + drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c | 64 ++++++++++++++++++- 3 files changed, 111 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/dp_tx.c b/drivers/net/wireless/ath/ath12k/dp_tx.c index c10da6195c9c..9644f9ef2c74 100644 --- a/drivers/net/wireless/ath/ath12k/dp_tx.c +++ b/drivers/net/wireless/ath/ath12k/dp_tx.c @@ -82,6 +82,52 @@ enum hal_encrypt_type ath12k_dp_tx_get_encrypt_type(u32 cipher) } EXPORT_SYMBOL(ath12k_dp_tx_get_encrypt_type); +u8 ath12k_dp_tx_crypto_iv_len(enum hal_encrypt_type enc_type) +{ + switch (enc_type) { + case HAL_ENCRYPT_TYPE_TKIP_NO_MIC: + case HAL_ENCRYPT_TYPE_TKIP_MIC: + return IEEE80211_TKIP_IV_LEN; + case HAL_ENCRYPT_TYPE_CCMP_128: + return IEEE80211_CCMP_HDR_LEN; + case HAL_ENCRYPT_TYPE_CCMP_256: + return IEEE80211_CCMP_256_HDR_LEN; + case HAL_ENCRYPT_TYPE_GCMP_128: + case HAL_ENCRYPT_TYPE_AES_GCMP_256: + return IEEE80211_GCMP_HDR_LEN; + case HAL_ENCRYPT_TYPE_WEP_40: + case HAL_ENCRYPT_TYPE_WEP_104: + case HAL_ENCRYPT_TYPE_WEP_128: + return IEEE80211_WEP_IV_LEN; + default: + return 0; + } +} +EXPORT_SYMBOL(ath12k_dp_tx_crypto_iv_len); + +u8 ath12k_dp_tx_crypto_icv_len(enum hal_encrypt_type enc_type) +{ + switch (enc_type) { + case HAL_ENCRYPT_TYPE_CCMP_128: + return IEEE80211_CCMP_MIC_LEN; + case HAL_ENCRYPT_TYPE_CCMP_256: + return IEEE80211_CCMP_256_MIC_LEN; + case HAL_ENCRYPT_TYPE_GCMP_128: + case HAL_ENCRYPT_TYPE_AES_GCMP_256: + return IEEE80211_GCMP_MIC_LEN; + case HAL_ENCRYPT_TYPE_TKIP_NO_MIC: + case HAL_ENCRYPT_TYPE_TKIP_MIC: + return IEEE80211_TKIP_ICV_LEN; + case HAL_ENCRYPT_TYPE_WEP_40: + case HAL_ENCRYPT_TYPE_WEP_104: + case HAL_ENCRYPT_TYPE_WEP_128: + return IEEE80211_WEP_ICV_LEN; + default: + return 0; + } +} +EXPORT_SYMBOL(ath12k_dp_tx_crypto_icv_len); + void ath12k_dp_tx_release_txbuf(struct ath12k_dp *dp, struct ath12k_tx_desc_info *tx_desc, u8 pool_id) diff --git a/drivers/net/wireless/ath/ath12k/dp_tx.h b/drivers/net/wireless/ath/ath12k/dp_tx.h index 7cef20540179..1af79af2ada2 100644 --- a/drivers/net/wireless/ath/ath12k/dp_tx.h +++ b/drivers/net/wireless/ath/ath12k/dp_tx.h @@ -19,6 +19,8 @@ enum hal_tcl_encap_type ath12k_dp_tx_get_encap_type(struct ath12k_base *ab, struct sk_buff *skb); void ath12k_dp_tx_encap_nwifi(struct sk_buff *skb); u8 ath12k_dp_tx_get_tid(struct sk_buff *skb); +u8 ath12k_dp_tx_crypto_iv_len(enum hal_encrypt_type enc_type); +u8 ath12k_dp_tx_crypto_icv_len(enum hal_encrypt_type enc_type); void *ath12k_dp_metadata_align_skb(struct sk_buff *skb, u8 tail_len); int ath12k_dp_tx_align_payload(struct ath12k_dp *dp, struct sk_buff **pskb); void ath12k_dp_tx_release_txbuf(struct ath12k_dp *dp, diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c index d2749de44553..587d58eeccfa 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c @@ -13,6 +13,49 @@ #include "hal.h" #include "hal_tx.h" +/* + * Convert an encrypted EAPOL frame from native-WiFi format to + * the layout expected by the firmware RAW encrypt pipeline: + * + * [802.11 hdr][IV (zeroed)][LLC/SNAP][EAPOL payload][ICV (zeroed)] + * + * mac80211 delivers the frame as [802.11 hdr][LLC/SNAP][EAPOL payload]. + * The MAC header length is read from the unmodified skb and is safe because + * ieee80211_hdrlen() only inspects the 2-byte frame_control field. + * pskb_expand_head() is used to grow both head (for the IV) and tail + * (for the ICV) in a single call and allocation. + */ +static int +ath12k_wifi7_dp_tx_encap_eapol(struct sk_buff *skb, + struct hal_tx_info *ti, + struct ath12k_skb_cb *skb_cb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + enum hal_encrypt_type enc_type = + ath12k_dp_tx_get_encrypt_type(skb_cb->cipher); + u16 mac_hdr_len = ieee80211_hdrlen(hdr->frame_control); + u8 iv_len = ath12k_dp_tx_crypto_iv_len(enc_type); + u8 icv_len = ath12k_dp_tx_crypto_icv_len(enc_type); + + if (pskb_expand_head(skb, iv_len, icv_len, GFP_ATOMIC)) + return -ENOMEM; + + if (iv_len) { + skb_push(skb, iv_len); + memmove(skb->data, skb->data + iv_len, mac_hdr_len); + memset(skb->data + mac_hdr_len, 0, iv_len); + } + + if (icv_len) + memset(skb_put(skb, icv_len), 0, icv_len); + + ti->flags0 |= u32_encode_bits(1, HAL_TCL_DATA_CMD_INFO2_TO_FW); + ti->encap_type = HAL_TCL_ENCAP_TYPE_RAW; + ti->encrypt_type = enc_type; + + return 0; +} + static void ath12k_wifi7_hal_tx_cmd_ext_desc_setup(struct ath12k_base *ab, struct hal_tx_msdu_ext_desc *tcl_ext_cmd, @@ -91,6 +134,7 @@ int ath12k_wifi7_dp_tx(struct ath12k_pdev_dp *dp_pdev, struct ath12k_link_vif *a u32 iova_mask = dp->hw_params->iova_mask; bool is_diff_encap = false; bool is_null_frame = false; + bool eapol_encap_done = false; if (test_bit(ATH12K_FLAG_CRASH_FLUSH, &ab->dev_flags)) return -ESHUTDOWN; @@ -211,9 +255,27 @@ int ath12k_wifi7_dp_tx(struct ath12k_pdev_dp *dp_pdev, struct ath12k_link_vif *a case HAL_TCL_ENCAP_TYPE_NATIVE_WIFI: is_null_frame = ieee80211_is_nullfunc(hdr->frame_control); if (ahvif->vif->offload_flags & IEEE80211_OFFLOAD_ENCAP_ENABLED) { - if (skb->protocol == cpu_to_be16(ETH_P_PAE) || is_null_frame) + if ((skb->protocol == cpu_to_be16(ETH_P_PAE) && + !(skb_cb->flags & ATH12K_SKB_CIPHER_SET)) || is_null_frame) is_diff_encap = true; + if (skb->protocol == cpu_to_be16(ETH_P_PAE) && + (skb_cb->flags & ATH12K_SKB_CIPHER_SET)) { + if (!eapol_encap_done) { + ret = ath12k_wifi7_dp_tx_encap_eapol(skb, &ti, + skb_cb); + if (ret) + goto fail_remove_tx_buf; + hdr = (void *)skb->data; + eapol_encap_done = true; + } else { + ti.flags0 |= u32_encode_bits(1, + HAL_TCL_DATA_CMD_INFO2_TO_FW); + ti.encap_type = HAL_TCL_ENCAP_TYPE_RAW; + ti.encrypt_type = + ath12k_dp_tx_get_encrypt_type(skb_cb->cipher); + } + } /* Firmware expects msdu ext descriptor for nwifi/raw packets * received in ETH mode. Without this, observed tx fail for * Multicast packets in ETH mode. From 35a3da9fe1b212a9012952a04f383b8dc3708dd7 Mon Sep 17 00:00:00 2001 From: Linghui Wu Date: Thu, 30 Jul 2026 08:02:26 +0530 Subject: [PATCH 0870/1433] wifi: ath10k: filter non-UTF testmode events When UTF monitor is enabled, ath10k forwards WMI events to nl80211 testmode. Non-UTF events can therefore be delivered to userspace and confuse FTM tools which expect only UTF responses. Only forward known UTF event IDs from WMI event namespaces that route events through ath10k_tm_event_wmi(), and drop other WMI events while UTF monitor is active. READY events are still handled by the normal WMI receive path. Tested-on: WCN3990 hw1.0 SNOC WLAN.HL.3.3.7.c5-00093.2-QCAHLSWMTPL-1 Signed-off-by: Linghui Wu Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260730023226.707008-1-linghui.wu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath10k/testmode.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/net/wireless/ath/ath10k/testmode.c b/drivers/net/wireless/ath/ath10k/testmode.c index d3bd385694d6..282ae6e20c8e 100644 --- a/drivers/net/wireless/ath/ath10k/testmode.c +++ b/drivers/net/wireless/ath/ath10k/testmode.c @@ -156,6 +156,14 @@ static void ath10k_tm_event_segmented(struct ath10k *ar, u32 cmd_id, struct sk_b cfg80211_testmode_event(nl_skb, GFP_ATOMIC); } +static bool ath10k_tm_is_utf_event(u32 cmd_id) +{ + return cmd_id == WMI_10X_PDEV_UTF_EVENTID || + cmd_id == WMI_10_2_PDEV_UTF_EVENTID || + cmd_id == WMI_10_4_PDEV_UTF_EVENTID || + cmd_id == WMI_TLV_PDEV_UTF_EVENTID; +} + /* Returns true if callee consumes the skb and the skb should be discarded. * Returns false if skb is not used. Does not sleep. */ @@ -182,6 +190,12 @@ bool ath10k_tm_event_wmi(struct ath10k *ar, u32 cmd_id, struct sk_buff *skb) */ consumed = true; + if (!ath10k_tm_is_utf_event(cmd_id)) { + ath10k_dbg(ar, ATH10K_DBG_TESTMODE, + "testmode drop non-utf event cmd_id %u\n", cmd_id); + goto out; + } + if (ar->testmode.expected_seq != ATH10K_FTM_SEG_NONE) ath10k_tm_event_segmented(ar, cmd_id, skb); else From 4f25071afe9218aaae1c63fbf75e229aa6405319 Mon Sep 17 00:00:00 2001 From: Linghui Wu Date: Mon, 27 Jul 2026 12:56:29 +0530 Subject: [PATCH 0871/1433] wifi: ath10k: snoc: use memcpy_fromio() for MSA ramdump On WCN3990/SNOC the MSA region is mapped with devm_memremap(MEMREMAP_WT). On arm64 such a mapping is not Normal-cacheable, so unaligned accesses to it are not permitted. ath10k_msa_dump_memory() copies the region with a plain memcpy(), whose optimized __pi_memcpy_generic implementation issues wide/unaligned loads. This triggers an alignment fault (FSC=0x21) Oops in ath10k_snoc_fw_crashed_dump() while collecting the devcoredump: Unable to handle kernel paging request ... FSC=0x21: alignment fault pc : __pi_memcpy_generic lr : ath10k_snoc_fw_crashed_dump [ath10k_snoc] The Oops both leaves the firmware RAM dump buffer zeroed (no dump is captured) and crashes the kernel, which in turn breaks modem SSR recovery. Use memcpy_fromio(), which only performs accesses that are valid for such a device-memory mapping. The generic memcpy_fromio() implementation aligns the source before issuing word-sized reads and stores the destination with put_unaligned(), so it is also safe for the coherent DMA allocation used on the non-reserved-memory path. ath11k and ath12k use the same pattern when copying target memory into crash dumps, so call it unconditionally here too. The MEMREMAP_WT pointer is a plain void *, so an explicit __iomem cast is needed; use __force to keep sparse happy. Tested-on: WCN3990 hw1.0 SNOC WLAN.HL.3.3.7.c5-00107-QCAHLSWMTPL-1 Fixes: 3f14b73c3843 ("ath10k: Enable MSA region dump support for WCN3990") Signed-off-by: Linghui Wu Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260727072629.2297208-1-linghui.wu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath10k/snoc.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath10k/snoc.c b/drivers/net/wireless/ath/ath10k/snoc.c index 310650227578..33c98927e8fe 100644 --- a/drivers/net/wireless/ath/ath10k/snoc.c +++ b/drivers/net/wireless/ath/ath10k/snoc.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -1475,11 +1476,15 @@ static void ath10k_msa_dump_memory(struct ath10k *ar, hdr->length = cpu_to_le32(ar->msa.mem_size); if (current_region->len < ar->msa.mem_size) { - memcpy(buf, ar->msa.vaddr, current_region->len); + memcpy_fromio(buf, + (const void __iomem __force *)ar->msa.vaddr, + current_region->len); ath10k_warn(ar, "msa dump length is less than msa size %x, %x\n", current_region->len, ar->msa.mem_size); } else { - memcpy(buf, ar->msa.vaddr, ar->msa.mem_size); + memcpy_fromio(buf, + (const void __iomem __force *)ar->msa.vaddr, + ar->msa.mem_size); } } From 6f6c9800e54cc7a9c5530797892b8b877335cf3c Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Tue, 21 Jul 2026 18:13:02 -0700 Subject: [PATCH 0872/1433] wifi: mt76: mt792x: do not advertise active monitor mt76_phy_init() sets NL80211_FEATURE_ACTIVE_MONITOR for every mt76 device, but mt792x firmware does not honor it: entering active monitor mode stops RX. Gate the feature behind a new per-phy no_active_monitor flag and set it for mt792x. Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260722011302.113060-1-lucid_duck@justthetip.ca Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mac80211.c | 5 +++-- drivers/net/wireless/mediatek/mt76/mt76.h | 1 + drivers/net/wireless/mediatek/mt76/mt792x_core.c | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index f92277770488..abbe65cbcd89 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -439,8 +439,9 @@ mt76_phy_init(struct mt76_phy *phy, struct ieee80211_hw *hw) SET_IEEE80211_DEV(hw, dev->dev); SET_IEEE80211_PERM_ADDR(hw, phy->macaddr); - wiphy->features |= NL80211_FEATURE_ACTIVE_MONITOR | - NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE; + wiphy->features |= NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE; + if (!phy->no_active_monitor) + wiphy->features |= NL80211_FEATURE_ACTIVE_MONITOR; wiphy->flags |= WIPHY_FLAG_HAS_CHANNEL_SWITCH | WIPHY_FLAG_SUPPORTS_TDLS | WIPHY_FLAG_AP_UAPSD; diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 61ef1b80feb8..62b41c8bb7c0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -873,6 +873,7 @@ struct mt76_phy { struct cfg80211_chan_def main_chandef; bool offchannel; bool radar_enabled; + bool no_active_monitor; struct delayed_work roc_work; struct ieee80211_vif *roc_vif; diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c index 837d7af984e8..0ad33f74c228 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c @@ -820,6 +820,7 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw) wiphy->features |= NL80211_FEATURE_SCHED_SCAN_RANDOM_MAC_ADDR | NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR; + phy->mt76->no_active_monitor = true; wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SET_SCAN_DWELL); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_BEACON_RATE_LEGACY); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_BEACON_RATE_HT); From 4df22710a77d2365e56d720bc4106e54c1dfa2ff Mon Sep 17 00:00:00 2001 From: Jose Ignacio Tornos Martinez Date: Thu, 2 Jul 2026 12:43:37 +0200 Subject: [PATCH 0873/1433] wifi: mt76: mt7996: remove beacon_int_min_gcd from ADHOC interface combinations The driver fails to register with error -22 (EINVAL) due to a cfg80211 validation failure in wiphy_verify_iface_combinations(). Commit 5ef0e8e2653b ("wifi: mt76: mt7996: fix iface combination for different chipsets") added beacon_int_min_gcd to if_comb_global and if_comb_global_7992, but these combinations include ADHOC (IBSS) interface type. This violates a cfg80211 rule from commit 56271da29c52 ("cfg80211: disallow beacon_int_min_gcd with IBSS") that explicitly forbids combining ADHOC with beacon_int_min_gcd. The restriction exists because beacon_int_min_gcd requires static, predictable beacon intervals to coordinate multiple beaconing interfaces, but ADHOC interfaces have dynamic beacon intervals that change when joining different networks, making the GCD constraint unenforceable. Remove beacon_int_min_gcd from the interface combinations that include ADHOC because they are not necessary for ADHOC operation. The if_comb combination (AP/MESH/STA only, without ADHOC) correctly retains beacon_int_min_gcd for multi-AP coordination. Fixes: 5ef0e8e2653b ("wifi: mt76: mt7996: fix iface combination for different chipsets") Signed-off-by: Jose Ignacio Tornos Martinez Tested-by: Alex Gavin Link: https://patch.msgid.link/20260702104337.679536-1-jtornosm@redhat.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 70902395bc96..fb635a092584 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -34,7 +34,6 @@ static const struct ieee80211_iface_combination if_comb_global = { BIT(NL80211_CHAN_WIDTH_40) | BIT(NL80211_CHAN_WIDTH_80) | BIT(NL80211_CHAN_WIDTH_160), - .beacon_int_min_gcd = 100, }; static const struct ieee80211_iface_combination if_comb_global_7992 = { @@ -47,7 +46,6 @@ static const struct ieee80211_iface_combination if_comb_global_7992 = { BIT(NL80211_CHAN_WIDTH_40) | BIT(NL80211_CHAN_WIDTH_80) | BIT(NL80211_CHAN_WIDTH_160), - .beacon_int_min_gcd = 100, }; static const struct ieee80211_iface_limit if_limits[] = { From 667c12782aaf8dd3cb2213e528fe63a73cb63345 Mon Sep 17 00:00:00 2001 From: "Yuhang.chen" Date: Wed, 29 Jul 2026 09:41:42 +0800 Subject: [PATCH 0874/1433] wifi: rtw89: pci: add .shutdown callback to stop rfkill polling on reboot Since the hardware rfkill polling was introduced, arm64 platforms can panic with an asynchronous SError during warm reboot: SError Interrupt on CPU8, code 0x00000000be000011 -- SError Workqueue: events_power_efficient rfkill_poll [rfkill] rtw89_pci_ops_read8+0x94/0x160 [rtw89_pci] rtw89_core_rfkill_poll+0x50/0x1e0 [rtw89_core] rtw89_ops_rfkill_poll+0x40/0x68 [rtw89_core] ieee80211_rfkill_poll+0x3c/0x70 [mac80211] cfg80211_rfkill_poll+0x40/0x2a0 [cfg80211] rfkill_poll+0x30/0x88 [rfkill] Kernel panic - not syncing: Asynchronous SError Interrupt On the reboot path the kernel only runs device_shutdown(), which calls each driver's .shutdown callback; .remove is not invoked. The rtw89 PCI driver had no .shutdown callback, so nothing stopped the rfkill polling work while the platform was tearing the PCIe link down. Once the link is gone, the next MMIO read from the poll handler targets a non-responding device and is reported as a fatal asynchronous SError on arm64. Add rtw89_pci_shutdown(), wired to all rtw89 PCI device drivers, which sets a new RTW89_FLAG_SHUTDOWN flag (mirroring the USB RTW89_FLAG_UNPLUGGED pattern). When the flag is set, rtw89_ops_rfkill_poll() returns early, so no MMIO read is issued to the chip after shutdown begins and the SError no longer occurs. This does not call the full .remove path from .shutdown, to keep the shutdown handler minimal and avoid running the non-idempotent teardown twice. Fixes: 0b38e6277aed ("wifi: rtw89: add support for hardware rfkill") Cc: stable@vger.kernel.org Suggested-by: Ping-Ke Shih Signed-off-by: Yuhang.chen Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260729014142.2746777-1-yhchen312@gmail.com --- drivers/net/wireless/realtek/rtw89/core.h | 1 + drivers/net/wireless/realtek/rtw89/mac80211.c | 3 ++- drivers/net/wireless/realtek/rtw89/pci.c | 13 +++++++++++++ drivers/net/wireless/realtek/rtw89/pci.h | 1 + drivers/net/wireless/realtek/rtw89/rtw8851be.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8852ae.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8852be.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8852bte.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8852ce.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8922ae.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8922de.c | 1 + 11 files changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 09c19a6b9058..889a94d7f534 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -6453,6 +6453,7 @@ enum rtw89_flags { RTW89_FLAG_CHANGING_INTERFACE, RTW89_FLAG_HW_RFKILL_STATE, RTW89_FLAG_UNPLUGGED, + RTW89_FLAG_SHUTDOWN, NUM_OF_RTW89_FLAGS, }; diff --git a/drivers/net/wireless/realtek/rtw89/mac80211.c b/drivers/net/wireless/realtek/rtw89/mac80211.c index e381aacda667..c1be69a3c192 100644 --- a/drivers/net/wireless/realtek/rtw89/mac80211.c +++ b/drivers/net/wireless/realtek/rtw89/mac80211.c @@ -2004,7 +2004,8 @@ static void rtw89_ops_rfkill_poll(struct ieee80211_hw *hw) lockdep_assert_wiphy(hw->wiphy); /* wl_disable GPIO get floating when entering LPS */ - if (test_bit(RTW89_FLAG_RUNNING, rtwdev->flags)) + if (test_bit(RTW89_FLAG_RUNNING, rtwdev->flags) || + test_bit(RTW89_FLAG_SHUTDOWN, rtwdev->flags)) return; rtw89_core_rfkill_poll(rtwdev, false); diff --git a/drivers/net/wireless/realtek/rtw89/pci.c b/drivers/net/wireless/realtek/rtw89/pci.c index c5b82fc46d06..8c3f4eb52cc5 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.c +++ b/drivers/net/wireless/realtek/rtw89/pci.c @@ -4878,6 +4878,19 @@ void rtw89_pci_remove(struct pci_dev *pdev) } EXPORT_SYMBOL(rtw89_pci_remove); +void rtw89_pci_shutdown(struct pci_dev *pdev) +{ + struct ieee80211_hw *hw = pci_get_drvdata(pdev); + struct rtw89_dev *rtwdev; + + if (!hw) + return; + + rtwdev = hw->priv; + set_bit(RTW89_FLAG_SHUTDOWN, rtwdev->flags); +} +EXPORT_SYMBOL(rtw89_pci_shutdown); + MODULE_AUTHOR("Realtek Corporation"); MODULE_DESCRIPTION("Realtek PCI 802.11ax wireless driver"); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/wireless/realtek/rtw89/pci.h b/drivers/net/wireless/realtek/rtw89/pci.h index 0e1557cedd20..cba52584ea68 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.h +++ b/drivers/net/wireless/realtek/rtw89/pci.h @@ -1752,6 +1752,7 @@ struct pci_device_id; int rtw89_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id); void rtw89_pci_remove(struct pci_dev *pdev); +void rtw89_pci_shutdown(struct pci_dev *pdev); void rtw89_pci_basic_cfg(struct rtw89_dev *rtwdev, bool resume); void rtw89_pci_ops_reset(struct rtw89_dev *rtwdev); int rtw89_pci_ltr_set(struct rtw89_dev *rtwdev, bool en); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851be.c b/drivers/net/wireless/realtek/rtw89/rtw8851be.c index 459d2e778b83..81c62296c32d 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851be.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851be.c @@ -94,6 +94,7 @@ static struct pci_driver rtw89_8851be_driver = { .id_table = rtw89_8851be_id_table, .probe = rtw89_pci_probe, .remove = rtw89_pci_remove, + .shutdown = rtw89_pci_shutdown, .driver.pm = &rtw89_pm_ops, .err_handler = &rtw89_pci_err_handler, }; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852ae.c b/drivers/net/wireless/realtek/rtw89/rtw8852ae.c index 142a45a2e718..3defb0748648 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852ae.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852ae.c @@ -96,6 +96,7 @@ static struct pci_driver rtw89_8852ae_driver = { .id_table = rtw89_8852ae_id_table, .probe = rtw89_pci_probe, .remove = rtw89_pci_remove, + .shutdown = rtw89_pci_shutdown, .driver.pm = &rtw89_pm_ops, .err_handler = &rtw89_pci_err_handler, }; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852be.c b/drivers/net/wireless/realtek/rtw89/rtw8852be.c index 1c622a99c070..9b48e70c2b56 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852be.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852be.c @@ -98,6 +98,7 @@ static struct pci_driver rtw89_8852be_driver = { .id_table = rtw89_8852be_id_table, .probe = rtw89_pci_probe, .remove = rtw89_pci_remove, + .shutdown = rtw89_pci_shutdown, .driver.pm = &rtw89_pm_ops, .err_handler = &rtw89_pci_err_handler, }; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bte.c b/drivers/net/wireless/realtek/rtw89/rtw8852bte.c index 73c7ebd33ab4..850ae9cbac18 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852bte.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852bte.c @@ -100,6 +100,7 @@ static struct pci_driver rtw89_8852bte_driver = { .id_table = rtw89_8852bte_id_table, .probe = rtw89_pci_probe, .remove = rtw89_pci_remove, + .shutdown = rtw89_pci_shutdown, .driver.pm = &rtw89_pm_ops, .err_handler = &rtw89_pci_err_handler, }; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852ce.c b/drivers/net/wireless/realtek/rtw89/rtw8852ce.c index bb5d5745aa62..daff152f66bc 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852ce.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852ce.c @@ -123,6 +123,7 @@ static struct pci_driver rtw89_8852ce_driver = { .id_table = rtw89_8852ce_id_table, .probe = rtw89_pci_probe, .remove = rtw89_pci_remove, + .shutdown = rtw89_pci_shutdown, .driver.pm = &rtw89_pm_ops, .err_handler = &rtw89_pci_err_handler, }; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922ae.c b/drivers/net/wireless/realtek/rtw89/rtw8922ae.c index 8f3a1cd463d9..e82c866dfa36 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922ae.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922ae.c @@ -113,6 +113,7 @@ static struct pci_driver rtw89_8922ae_driver = { .id_table = rtw89_8922ae_id_table, .probe = rtw89_pci_probe, .remove = rtw89_pci_remove, + .shutdown = rtw89_pci_shutdown, .driver.pm = &rtw89_pm_ops_be, .err_handler = &rtw89_pci_err_handler, }; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922de.c b/drivers/net/wireless/realtek/rtw89/rtw8922de.c index 09400a0efcfd..a71e41f28544 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922de.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922de.c @@ -113,6 +113,7 @@ static struct pci_driver rtw89_8922de_driver = { .id_table = rtw89_8922de_id_table, .probe = rtw89_pci_probe, .remove = rtw89_pci_remove, + .shutdown = rtw89_pci_shutdown, .driver.pm = &rtw89_pm_ops_be, .err_handler = &rtw89_pci_err_handler, }; From 304720870f4cf24042bcad3ed6d7144710926d72 Mon Sep 17 00:00:00 2001 From: Dian-Syuan Yang Date: Wed, 29 Jul 2026 20:43:52 +0800 Subject: [PATCH 0875/1433] wifi: rtw89: refine RX filter configuration for WiFi 7 generation When an AP interface is registered and starts AP, mac80211 calls the configure_filter() to clear B_AX_A_UC_CAM_MATCH and B_AX_A_BC_CAM_MATCH so that frames from un-associated stations can be received. However, for a dedicated AP interface created via iw command, configure_filter() is only triggered on the initial AP startup. Since the interface remains up even after hostapd stops, and it isn't triggered again on later restarts. Additionally, each AP start causes IPS leave, reverting the RX filter to its default value. For WiFi 7 chips, the default value is hardcoded in rx_fltr_init_be(), so the reverted value does not match hal.rx_fltr. Therefore, refine the behavior of WiFi 7 chips to align with the WiFi 6 implementation. Signed-off-by: Dian-Syuan Yang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260729124354.3231368-2-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.c | 3 ++- drivers/net/wireless/realtek/rtw89/mac.c | 1 + drivers/net/wireless/realtek/rtw89/mac.h | 1 + drivers/net/wireless/realtek/rtw89/mac_be.c | 7 ++----- drivers/net/wireless/realtek/rtw89/reg.h | 6 +++++- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index 77a0e2582dbe..097e9b0312a1 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -6960,6 +6960,7 @@ void rtw89_sta_unset_link(struct rtw89_sta *rtwsta, unsigned int link_id) int rtw89_core_init(struct rtw89_dev *rtwdev) { + const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; struct rtw89_btc *btc = &rtwdev->btc; u8 band; @@ -7016,7 +7017,7 @@ int rtw89_core_init(struct rtw89_dev *rtwdev) rtw89_core_ppdu_sts_init(rtwdev); rtw89_traffic_stats_init(rtwdev, &rtwdev->stats); - rtwdev->hal.rx_fltr = DEFAULT_AX_RX_FLTR; + rtwdev->hal.rx_fltr = mac->default_rx_fltr; rtwdev->dbcc_en = false; rtwdev->mlo_dbcc_mode = MLO_DBCC_NOT_SUPPORT; rtwdev->mac.qta_mode = RTW89_QTA_SCC; diff --git a/drivers/net/wireless/realtek/rtw89/mac.c b/drivers/net/wireless/realtek/rtw89/mac.c index 2cbff2be9bbb..df396fbfca26 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.c +++ b/drivers/net/wireless/realtek/rtw89/mac.c @@ -7454,6 +7454,7 @@ const struct rtw89_mac_gen_def rtw89_mac_gen_ax = { .mem_base_addrs = rtw89_mac_mem_base_addrs_ax, .mem_page_size = MAC_MEM_DUMP_PAGE_SIZE_AX, .rx_fltr = R_AX_RX_FLTR_OPT, + .default_rx_fltr = DEFAULT_AX_RX_FLTR, .port_base = &rtw89_port_base_ax, .agg_len_ht = R_AX_AGG_LEN_HT_0, .ps_status = R_AX_PPWRBIT_SETTING, diff --git a/drivers/net/wireless/realtek/rtw89/mac.h b/drivers/net/wireless/realtek/rtw89/mac.h index 493d2b9626a6..8419bcd3956a 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.h +++ b/drivers/net/wireless/realtek/rtw89/mac.h @@ -1063,6 +1063,7 @@ struct rtw89_mac_gen_def { const u32 *mem_base_addrs; u32 mem_page_size; u32 rx_fltr; + u32 default_rx_fltr; const struct rtw89_port_reg *port_base; u32 agg_len_ht; u32 ps_status; diff --git a/drivers/net/wireless/realtek/rtw89/mac_be.c b/drivers/net/wireless/realtek/rtw89/mac_be.c index dfa0973e367c..c8f343c8d545 100644 --- a/drivers/net/wireless/realtek/rtw89/mac_be.c +++ b/drivers/net/wireless/realtek/rtw89/mac_be.c @@ -1328,11 +1328,7 @@ static int rx_fltr_init_be(struct rtw89_dev *rtwdev, u8 mac_idx) rtw89_mac_typ_fltr_opt_be(rtwdev, RTW89_DATA, RTW89_FWD_TO_HOST, mac_idx); reg = rtw89_mac_reg_by_idx(rtwdev, R_BE_RX_FLTR_OPT, mac_idx); - val = B_BE_A_BC_CAM_MATCH | B_BE_A_UC_CAM_MATCH | B_BE_A_MC | - B_BE_A_BC | B_BE_A_A1_MATCH | - u32_encode_bits(15, B_BE_UID_FILTER_MASK); - rtw89_write32(rtwdev, reg, val); - u32p_replace_bits(&rtwdev->hal.rx_fltr, 15, B_BE_UID_FILTER_MASK); + rtw89_write32(rtwdev, reg, rtwdev->hal.rx_fltr); reg = rtw89_mac_reg_by_idx(rtwdev, R_BE_PLCP_HDR_FLTR, mac_idx); val = B_BE_HE_SIGB_CRC_CHK | B_BE_VHT_MU_SIGB_CRC_CHK | @@ -3276,6 +3272,7 @@ const struct rtw89_mac_gen_def rtw89_mac_gen_be = { .mem_base_addrs = rtw89_mac_mem_base_addrs_be, .mem_page_size = MAC_MEM_DUMP_PAGE_SIZE_BE, .rx_fltr = R_BE_RX_FLTR_OPT, + .default_rx_fltr = DEFAULT_BE_RX_FLTR, .port_base = &rtw89_port_base_be, .agg_len_ht = R_BE_AGG_LEN_HT_0, .ps_status = R_BE_WMTX_POWER_BE_BIT_CTL, diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h index 756b94dcd475..abeeb61007dc 100644 --- a/drivers/net/wireless/realtek/rtw89/reg.h +++ b/drivers/net/wireless/realtek/rtw89/reg.h @@ -3348,7 +3348,7 @@ #define DEFAULT_AX_RX_FLTR (B_AX_A_A1_MATCH | B_AX_A_BC | B_AX_A_MC | \ B_AX_A_UC_CAM_MATCH | B_AX_A_BC_CAM_MATCH | \ B_AX_A_PWR_MGNT | B_AX_A_FTM_REQ | \ - u32_encode_bits(3, B_AX_UID_FILTER_MASK) | \ + FIELD_PREP_CONST(B_AX_UID_FILTER_MASK, 3) | \ B_AX_A_BCN_CHK_EN) #define B_AX_RX_FLTR_CFG_MASK ((u32)~B_AX_RX_MPDU_MAX_LEN_MASK) @@ -8209,6 +8209,10 @@ #define B_BE_A_BC BIT(2) #define B_BE_A_A1_MATCH BIT(1) #define B_BE_SNIFFER_MODE BIT(0) +#define DEFAULT_BE_RX_FLTR (B_BE_A_BC_CAM_MATCH | B_BE_A_UC_CAM_MATCH | \ + B_BE_A_MC | B_BE_A_BC | B_BE_A_A1_MATCH | \ + FIELD_PREP_CONST(B_BE_UID_FILTER_MASK, 15) | \ + B_BE_A_FTM_REQ | B_BE_A_BCN_CHK_EN) #define R_BE_CTRL_FLTR 0x11424 #define R_BE_CTRL_FLTR_C1 0x15424 From 58c0d447c53b4e5a50fdeaa30aa207df1406951f Mon Sep 17 00:00:00 2001 From: Po-Hao Huang Date: Wed, 29 Jul 2026 20:43:53 +0800 Subject: [PATCH 0876/1433] wifi: rtw89: fix scan offload version check logic The version check with less than should check for smallest upper-bound first, or some branch will be unreachable. Signed-off-by: Po-Hao Huang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260729124354.3231368-3-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/fw.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 5ab80bda3ae5..a3d72fbaba54 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -7748,12 +7748,12 @@ int rtw89_fw_h2c_scan_offload_be(struct rtw89_dev *rtwdev, rtw89_scan_get_6g_disabled_chan(rtwdev, option); - if (RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V1, &rtwdev->fw)) { - cfg_len = offsetofend(typeof(*h2c), w9); - scan_offload_ver = 1; - } else if (RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V0, &rtwdev->fw)) { + if (RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V0, &rtwdev->fw)) { cfg_len = offsetofend(typeof(*h2c), w8); scan_offload_ver = 0; + } else if (RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V1, &rtwdev->fw)) { + cfg_len = offsetofend(typeof(*h2c), w9); + scan_offload_ver = 1; } len = cfg_len + macc_role_size + opch_size; From d946a4e031167736c5f4446e5c55231d3543ef29 Mon Sep 17 00:00:00 2001 From: Po-Hao Huang Date: Wed, 29 Jul 2026 20:43:54 +0800 Subject: [PATCH 0877/1433] wifi: rtw89: change scan offload format to V3 for WiFi 7 IC Adapt H2C format to fit firmware version after 0.35.113.2, which only supports active scanning with a provided SSID list. The wildcard_6ghz field is no longer required for WiFi 7 chips' new format, but not removed since WiFi 6 still requires it. Keep original H2C style also to support older firmwares. Signed-off-by: Po-Hao Huang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260729124354.3231368-4-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.h | 4 + drivers/net/wireless/realtek/rtw89/fw.c | 160 +++++++++++++++++++--- drivers/net/wireless/realtek/rtw89/fw.h | 6 + drivers/net/wireless/realtek/rtw89/wow.c | 20 +++ 4 files changed, 172 insertions(+), 18 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 889a94d7f534..d82de04352e6 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -5989,6 +5989,7 @@ enum rtw89_fw_feature { RTW89_FW_FEATURE_MACID_PAUSE_SLEEP, RTW89_FW_FEATURE_SCAN_OFFLOAD_BE_V0, RTW89_FW_FEATURE_SCAN_OFFLOAD_BE_V1, + RTW89_FW_FEATURE_SCAN_OFFLOAD_BE_V2, RTW89_FW_FEATURE_WOW_REASON_V1, RTW89_FW_FEATURE_GROUP(WITH_RFK_PRE_NOTIFY, RTW89_FW_FEATURE_RFK_PRE_NOTIFY_V0, @@ -7184,6 +7185,9 @@ struct rtw89_hw_scan_info { struct list_head chan_list; struct rtw89_chan op_chan; struct rtw89_hw_scan_extra_op extra_op; + u8 wildcard_pkt_id[NUM_NL80211_BANDS]; + u16 ssid_total_len; + int n_ssids; bool connected; bool abort; u16 delay; /* in unit of ms */ diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index a3d72fbaba54..0099c5c03e7a 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -934,6 +934,7 @@ static const struct __fw_feat_cfg fw_feat_tbl[] = { __CFG_FW_FEAT(RTL8922A, ge, 0, 35, 100, 0, SER_POST_RECOVER_DMAC), __CFG_FW_FEAT(RTL8922A, ge, 0, 35, 108, 0, SIM_SER_L0L1_BY_HALT_H2C), __CFG_FW_FEAT(RTL8922A, lt, 0, 35, 109, 1, SCAN_OFFLOAD_BE_V1), + __CFG_FW_FEAT(RTL8922A, lt, 0, 35, 113, 2, SCAN_OFFLOAD_BE_V2), __CFG_FW_FEAT(RTL8922D, ge, 0, 0, 0, 0, MACID_PAUSE_SLEEP), __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 75, 2, SCAN_OFFLOAD), __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 75, 2, BEACON_FILTER), @@ -949,6 +950,7 @@ static const struct __fw_feat_cfg fw_feat_tbl[] = { __CFG_FW_FEAT(RTL8922D, ge, 0, 35, 108, 0, SIM_SER_L0L1_BY_HALT_H2C), __CFG_FW_FEAT(RTL8922D, lt, 0, 35, 109, 1, SCAN_OFFLOAD_BE_V1), __CFG_FW_FEAT(RTL8922D, lt, 0, 35, 113, 0, RFK_TXIQK_V0), + __CFG_FW_FEAT(RTL8922D, lt, 0, 35, 113, 2, SCAN_OFFLOAD_BE_V2), }; static void rtw89_fw_iterate_feature_cfg(struct rtw89_fw_info *fw, @@ -7498,6 +7500,7 @@ int rtw89_fw_h2c_scan_list_offload_be(struct rtw89_dev *rtwdev, int ch_num, struct rtw89_h2c_chinfo_elem_be *elem; struct rtw89_mac_chinfo_be *ch_info; struct rtw89_h2c_chinfo_be *h2c; + bool wildcard_by_drv; struct sk_buff *skb; unsigned int cond; u8 ver = U8_MAX; @@ -7516,6 +7519,10 @@ int rtw89_fw_h2c_scan_list_offload_be(struct rtw89_dev *rtwdev, int ch_num, if (RTW89_CHK_FW_FEATURE(CH_INFO_BE_V0, &rtwdev->fw)) ver = 0; + wildcard_by_drv = !(RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V0, &rtwdev->fw) || + RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V1, &rtwdev->fw) || + RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V2, &rtwdev->fw)); + skb_put(skb, sizeof(*h2c)); h2c = (struct rtw89_h2c_chinfo_be *)skb->data; @@ -7525,6 +7532,8 @@ int rtw89_fw_h2c_scan_list_offload_be(struct rtw89_dev *rtwdev, int ch_num, RTW89_H2C_CHINFO_ARG_MAC_IDX_MASK); list_for_each_entry(ch_info, chan_list, list) { + bool with_probe_id = ch_info->probe_id != RTW89_SCANOFLD_PKT_NONE; + elem = (struct rtw89_h2c_chinfo_elem_be *)skb_put(skb, sizeof(*elem)); elem->w0 = le32_encode_bits(ch_info->dwell_time, RTW89_H2C_CHINFO_BE_W0_DWELL) | @@ -7542,7 +7551,7 @@ int rtw89_fw_h2c_scan_list_offload_be(struct rtw89_dev *rtwdev, int ch_num, RTW89_H2C_CHINFO_BE_W1_RANDOM) | le32_encode_bits(ch_info->notify_action, RTW89_H2C_CHINFO_BE_W1_NOTIFY) | - le32_encode_bits(ch_info->probe_id != 0xff ? 1 : 0, + le32_encode_bits(wildcard_by_drv || with_probe_id, RTW89_H2C_CHINFO_BE_W1_PROBE) | le32_encode_bits(ch_info->leave_crit, RTW89_H2C_CHINFO_BE_W1_EARLY_LEAVE_CRIT) | @@ -7707,6 +7716,34 @@ static void rtw89_scan_get_6g_disabled_chan(struct rtw89_dev *rtwdev, } } +void rtw89_hw_scan_calc_req_ssid(struct rtw89_dev *rtwdev, + struct rtw89_vif_link *rtwvif_link, bool wowlan) +{ + struct rtw89_hw_scan_info *scan_info = &rtwdev->scan_info; + struct rtw89_vif *rtwvif = rtwvif_link->rtwvif; + struct cfg80211_scan_request *req = rtwvif->scan_req; + struct rtw89_wow_param *rtw_wow = &rtwdev->wow; + struct cfg80211_sched_scan_request *nd_config; + struct cfg80211_ssid *s; + u16 sum = 0, n_ssids; + int i, num = 0; + + nd_config = rtw_wow->nd_config; + n_ssids = wowlan ? nd_config->n_match_sets : req->n_ssids; + + for (i = 0; i < n_ssids; i++) { + s = wowlan ? &nd_config->match_sets[i].ssid : &req->ssids[i]; + if (s->ssid_len == 0) + continue; + + num++; + sum += s->ssid_len + 1; + } + + scan_info->n_ssids = num; + scan_info->ssid_total_len = sum; +} + int rtw89_fw_h2c_scan_offload_be(struct rtw89_dev *rtwdev, struct rtw89_scan_option *option, struct rtw89_vif_link *rtwvif_link, @@ -7721,18 +7758,21 @@ int rtw89_fw_h2c_scan_offload_be(struct rtw89_dev *rtwdev, struct rtw89_hw_scan_extra_op scan_op[2] = {}; struct rtw89_chan *op = &scan_info->op_chan; struct rtw89_h2c_scanofld_be_opch *opch; - struct rtw89_pktofld_info *pkt_info; struct rtw89_h2c_scanofld_be *h2c; struct ieee80211_vif *vif; struct sk_buff *skb; u8 macc_role_size = sizeof(*macc_role) * option->num_macc_role; u8 opch_size = sizeof(*opch) * option->num_opch; + struct rtw89_wow_param *rtw_wow = &rtwdev->wow; + struct cfg80211_sched_scan_request *nd_config; + u8 *probe_id = scan_info->wildcard_pkt_id; enum rtw89_scan_be_opmode opmode; - u8 probe_id[NUM_NL80211_BANDS]; u8 scan_offload_ver = U8_MAX; u8 cfg_len = sizeof(*h2c); + struct cfg80211_ssid *s; unsigned int cond; u8 ver = U8_MAX; + u8 n_ssids = 0; u8 policy_val; void *ptr; u8 txnull; @@ -7754,9 +7794,14 @@ int rtw89_fw_h2c_scan_offload_be(struct rtw89_dev *rtwdev, } else if (RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V1, &rtwdev->fw)) { cfg_len = offsetofend(typeof(*h2c), w9); scan_offload_ver = 1; + } else if (RTW89_CHK_FW_FEATURE(SCAN_OFFLOAD_BE_V2, &rtwdev->fw)) { + scan_offload_ver = 2; } len = cfg_len + macc_role_size + opch_size; + if (scan_offload_ver > 2) + len += scan_info->ssid_total_len; + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); if (!skb) { rtw89_err(rtwdev, "failed to alloc skb for h2c scan offload\n"); @@ -7767,21 +7812,9 @@ int rtw89_fw_h2c_scan_offload_be(struct rtw89_dev *rtwdev, h2c = (struct rtw89_h2c_scanofld_be *)skb->data; ptr = skb->data; - memset(probe_id, RTW89_SCANOFLD_PKT_NONE, sizeof(probe_id)); - if (RTW89_CHK_FW_FEATURE(CH_INFO_BE_V0, &rtwdev->fw)) ver = 0; - if (!wowlan) { - list_for_each_entry(pkt_info, &scan_info->pkt_list[NL80211_BAND_6GHZ], list) { - if (pkt_info->wildcard_6ghz) { - /* Provide wildcard as template */ - probe_id[NL80211_BAND_6GHZ] = pkt_info->id; - break; - } - } - } - h2c->w0 = le32_encode_bits(option->operation, RTW89_H2C_SCANOFLD_BE_W0_OP) | le32_encode_bits(option->scan_mode, RTW89_H2C_SCANOFLD_BE_W0_SCAN_MODE) | @@ -7800,7 +7833,10 @@ int rtw89_fw_h2c_scan_offload_be(struct rtw89_dev *rtwdev, le32_encode_bits(option->norm_cy, RTW89_H2C_SCANOFLD_BE_W2_NORM_CY) | le32_encode_bits(option->opch_end, RTW89_H2C_SCANOFLD_BE_W2_OPCH_END); - h2c->w3 = le32_encode_bits(0, RTW89_H2C_SCANOFLD_BE_W3_NUM_SSID) | + if (scan_offload_ver > 2) + n_ssids = scan_info->n_ssids; + + h2c->w3 = le32_encode_bits(n_ssids, RTW89_H2C_SCANOFLD_BE_W3_NUM_SSID) | le32_encode_bits(0, RTW89_H2C_SCANOFLD_BE_W3_NUM_SHORT_SSID) | le32_encode_bits(0, RTW89_H2C_SCANOFLD_BE_W3_NUM_BSSID) | le32_encode_bits(probe_id[NL80211_BAND_2GHZ], RTW89_H2C_SCANOFLD_BE_W3_PROBEID); @@ -7927,6 +7963,24 @@ int rtw89_fw_h2c_scan_offload_be(struct rtw89_dev *rtwdev, ptr += sizeof(*opch); } + if (scan_offload_ver <= 2) + goto set_hdr; + + nd_config = rtw_wow->nd_config; + n_ssids = wowlan ? nd_config->n_match_sets : req->n_ssids; + + for (i = 0; i < n_ssids; i++) { + s = wowlan ? &nd_config->match_sets[i].ssid : &req->ssids[i]; + if (s->ssid_len == 0) + continue; + + memcpy(ptr, &s->ssid_len, 1); + ptr++; + memcpy(ptr, s->ssid, s->ssid_len); + ptr += s->ssid_len; + } + +set_hdr: rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, H2C_CAT_MAC, H2C_CL_MAC_FW_OFLD, H2C_FUNC_SCANOFLD_BE, 1, 1, @@ -9256,14 +9310,19 @@ void rtw89_fw_st_dbg_dump(struct rtw89_dev *rtwdev) static void rtw89_hw_scan_release_pkt_list(struct rtw89_dev *rtwdev) { + struct rtw89_hw_scan_info *scan_info = &rtwdev->scan_info; struct list_head *pkt_list = rtwdev->scan_info.pkt_list; struct rtw89_pktofld_info *info, *tmp; - u8 idx; + u8 idx, wildcard_pkt_id; for (idx = NL80211_BAND_2GHZ; idx < NUM_NL80211_BANDS; idx++) { if (!(rtwdev->chip->support_bands & BIT(idx))) continue; + wildcard_pkt_id = scan_info->wildcard_pkt_id[idx]; + if (wildcard_pkt_id != RTW89_SCANOFLD_PKT_NONE) + rtw89_fw_h2c_del_pkt_offload(rtwdev, wildcard_pkt_id); + list_for_each_entry_safe(info, tmp, &pkt_list[idx], list) { if (test_bit(info->id, rtwdev->pkt_offload)) rtw89_fw_h2c_del_pkt_offload(rtwdev, info->id); @@ -9358,16 +9417,74 @@ static int rtw89_append_probe_req_ie(struct rtw89_dev *rtwdev, return ret; } +int rtw89_hw_scan_append_wildcard_probe_req(struct rtw89_dev *rtwdev, + struct rtw89_vif_link *rtwvif_link, + const u8 *mac_addr, bool wowlan) +{ + static const u8 basic_rate_ie[] = {WLAN_EID_SUPP_RATES, 0x08, 0x0c, 0x12, + 0x18, 0x24, 0x30, 0x48, 0x60, 0x6c}; + struct rtw89_hw_scan_info *scan_info = &rtwdev->scan_info; + struct rtw89_vif *rtwvif = rtwvif_link->rtwvif; + struct cfg80211_scan_request *req = rtwvif->scan_req; + struct ieee80211_scan_ies *ies = rtwvif->scan_ies; + struct rtw89_wow_param *rtw_wow = &rtwdev->wow; + struct cfg80211_sched_scan_request *nd_config; + enum nl80211_band band; + struct sk_buff *skb; + int ret; + u8 id; + + for (band = NL80211_BAND_2GHZ; band < NUM_NL80211_BANDS; band++) { + if (!(rtwdev->chip->support_bands & BIT(band))) + continue; + + if (wowlan) { + nd_config = rtw_wow->nd_config; + + skb = ieee80211_probereq_get(rtwdev->hw, rtwvif_link->mac_addr, NULL, 0, + nd_config->ie_len + + sizeof(basic_rate_ie)); + if (!skb) + return -ENOMEM; + + skb_put_data(skb, basic_rate_ie, sizeof(basic_rate_ie)); + skb_put_data(skb, nd_config->ie, nd_config->ie_len); + } else { + skb = ieee80211_probereq_get(rtwdev->hw, mac_addr, + NULL, 0, req->ie_len); + if (!skb) + return -ENOMEM; + + skb_put_data(skb, ies->ies[band], ies->len[band]); + skb_put_data(skb, ies->common_ies, ies->common_ie_len); + } + + ret = rtw89_fw_h2c_add_pkt_offload(rtwdev, &id, skb); + kfree_skb(skb); + + if (ret) + return ret; + + scan_info->wildcard_pkt_id[band] = id; + } + + return 0; +} + static int rtw89_hw_scan_update_probe_req(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link, const u8 *mac_addr) { struct rtw89_vif *rtwvif = rtwvif_link->rtwvif; struct cfg80211_scan_request *req = rtwvif->scan_req; - struct sk_buff *skb; u8 num = req->n_ssids, i; + struct sk_buff *skb; int ret; + ret = rtw89_hw_scan_append_wildcard_probe_req(rtwdev, rtwvif_link, mac_addr, false); + if (ret) + return ret; + for (i = 0; i < num; i++) { skb = ieee80211_probereq_get(rtwdev->hw, mac_addr, req->ssids[i].ssid, @@ -9675,6 +9792,9 @@ static void rtw89_hw_scan_add_chan_be(struct rtw89_dev *rtwdev, int chan_type, if (probe_count >= RTW89_SCANOFLD_MAX_SSID) break; } + + if (scan_info->n_ssids) + ch_info->fw_probe0_ssids = GENMASK(scan_info->n_ssids - 1, 0); } if (ch_info->ch_band == RTW89_BAND_6G) { @@ -10277,6 +10397,10 @@ int rtw89_hw_scan_start(struct rtw89_dev *rtwdev, rtwdev->scan_info.delay = 0; rtwvif->scan_ies = &scan_req->ies; rtwvif->scan_req = req; + rtw89_hw_scan_calc_req_ssid(rtwdev, rtwvif_link, false); + + memset(rtwdev->scan_info.wildcard_pkt_id, RTW89_SCANOFLD_PKT_NONE, + sizeof(rtwdev->scan_info.wildcard_pkt_id)); if (req->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) get_random_mask_addr(mac_addr, req->mac_addr, diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index 6147960521ee..375ca0a243a6 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -3119,6 +3119,7 @@ struct rtw89_h2c_scanofld_be { __le32 w11; /* Added after SCAN_OFFLOAD_BE_V2 */ /* struct rtw89_h2c_scanofld_be_macc_role (flexible number) */ /* struct rtw89_h2c_scanofld_be_opch (flexible number) */ + /* probe SSID list (flexible number); Added after SCAN_OFFLOAD_BE_V3 */ } __packed; #define RTW89_H2C_SCANOFLD_BE_W0_OP GENMASK(1, 0) @@ -5599,6 +5600,11 @@ int rtw89_hw_scan_add_chan_list_be(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link); int rtw89_pno_scan_add_chan_list_be(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link); +int rtw89_hw_scan_append_wildcard_probe_req(struct rtw89_dev *rtwdev, + struct rtw89_vif_link *rtwvif_link, + const u8 *mac_addr, bool wowlan); +void rtw89_hw_scan_calc_req_ssid(struct rtw89_dev *rtwdev, + struct rtw89_vif_link *rtwvif_link, bool wowlan); int rtw89_fw_h2c_trigger_cpu_exception(struct rtw89_dev *rtwdev); int rtw89_fw_h2c_pkt_drop(struct rtw89_dev *rtwdev, const struct rtw89_pkt_drop_params *params); diff --git a/drivers/net/wireless/realtek/rtw89/wow.c b/drivers/net/wireless/realtek/rtw89/wow.c index d6bf5cd8be3b..2dfa24c54e7d 100644 --- a/drivers/net/wireless/realtek/rtw89/wow.c +++ b/drivers/net/wireless/realtek/rtw89/wow.c @@ -1457,9 +1457,20 @@ static int rtw89_wow_disable_trx_post(struct rtw89_dev *rtwdev) static void rtw89_fw_release_pno_pkt_list(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link) { + struct rtw89_hw_scan_info *scan_info = &rtwdev->scan_info; struct rtw89_wow_param *rtw_wow = &rtwdev->wow; struct list_head *pkt_list = &rtw_wow->pno_pkt_list; struct rtw89_pktofld_info *info, *tmp; + u8 idx, wildcard_pkt_id; + + for (idx = NL80211_BAND_2GHZ; idx < NUM_NL80211_BANDS; idx++) { + if (!(rtwdev->chip->support_bands & BIT(idx))) + continue; + + wildcard_pkt_id = scan_info->wildcard_pkt_id[idx]; + if (wildcard_pkt_id != RTW89_SCANOFLD_PKT_NONE) + rtw89_fw_h2c_del_pkt_offload(rtwdev, wildcard_pkt_id); + } list_for_each_entry_safe(info, tmp, pkt_list, list) { rtw89_fw_h2c_del_pkt_offload(rtwdev, info->id); @@ -1480,6 +1491,11 @@ static int rtw89_pno_scan_update_probe_req(struct rtw89_dev *rtwdev, struct sk_buff *skb; int ret; + ret = rtw89_hw_scan_append_wildcard_probe_req(rtwdev, rtwvif_link, + rtwvif_link->mac_addr, true); + if (ret) + return ret; + for (i = 0; i < num; i++) { skb = ieee80211_probereq_get(rtwdev->hw, rtwvif_link->mac_addr, nd_config->match_sets[i].ssid.ssid, @@ -1523,6 +1539,10 @@ static int rtw89_pno_scan_offload(struct rtw89_dev *rtwdev, bool enable) int ret; if (enable) { + rtw89_hw_scan_calc_req_ssid(rtwdev, rtwvif_link, true); + memset(rtwdev->scan_info.wildcard_pkt_id, RTW89_SCANOFLD_PKT_NONE, + sizeof(rtwdev->scan_info.wildcard_pkt_id)); + ret = rtw89_pno_scan_update_probe_req(rtwdev, rtwvif_link); if (ret) { rtw89_err(rtwdev, "Update probe request failed\n"); From e62f0baca06e1fc9a54e86d95cfcd3c7d5ff0325 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Thu, 30 Jul 2026 14:02:14 +0800 Subject: [PATCH 0878/1433] wifi: rtw89: coex: Fix Bluetooth link weight not updated on profile change _update_bt_link_cnt() was missing the bt->link_weight[]. Without it, link_weight stays at the initial value of 5 (set on BT re-enable) regardless of the active BT profile. _set_bind_info() uses link_weight to compute b2g_score/b5g_score, which determines tdd_bind.rf_band. With a stale weight of 5 (below the active profile threshold), tdd_bind.rf_band may not reflect the actual RF band, causing mode_v0 in _run_coex() to remain 0 (BTC_WLINK_NOLINK), which incorrectly triggers _action_wl_nc() instead of the BT-profile action. Add the link_weight calculation to _update_bt_link_cnt() using the same weighting table as Formal (BIS/A2DP-sink: 70, A2DP: 40-60, PAN/active: 30, no-profile: 5, else: 9) and call _update_bt_link_cnt() from _update_bt_info() after parsing the profile exist flags, replacing the open-coded link_cnt increment that omitted le-audio profiles. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260730060220.55844-2-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 30 ++++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 0e42c720819a..fd9ddd55c192 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -7973,6 +7973,8 @@ static void _update_bt_link_cnt(struct rtw89_dev *rtwdev, struct rtw89_btc_bt_hfp_desc *hfp = &b->hfp_desc; struct rtw89_btc_bt_hid_desc *hid = &b->hid_desc; struct rtw89_btc_bt_pan_desc *pan = &b->pan_desc; + u8 bt_rf_band = b56g ? BTC_BT_B5G : BTC_BT_B2G; + u8 val; b->link_cnt.last = b->link_cnt.now; b->link_cnt.now = 0; @@ -7984,6 +7986,27 @@ static void _update_bt_link_cnt(struct rtw89_dev *rtwdev, b->link_cnt.now += pan->exist; b->link_cnt.now += leaudio->bis_exist; b->link_cnt.now += leaudio->cis_exist; + + if (leaudio->bis_exist || a2dp->sink) { + val = 70; + } else if (a2dp->exist) { + val = 40; + if (a2dp->vendor_id == 0x4c) + val += 10; + if (hid->exist) + val += 10; + } else if (pan->exist || pan->active || a2dp->active || + b->status.map.inq_pag) { + val = 30; + } else if (b->link_cnt.now == 0) { + val = 5; + } else { + val = 9; + } + bt->link_weight[bt_rf_band] = val; + + if (b->link_cnt.last != b->link_cnt.now) + b->link_cnt.chg = 1; } static void _update_bt_leaudio_info(struct rtw89_dev *rtwdev, u8 bid, @@ -9012,8 +9035,6 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) "[BTC], %s(): bt_info[2]=0x%02x\n", __func__, bt->raw_info[2]); - b->link_cnt.last = b->link_cnt.now; - b->link_cnt.now = 0; hid->type = 0; /* parse raw info low-Byte2 */ @@ -9026,13 +9047,10 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) bt->bcnt[BTC_BCNT_INQPAG] += !!(bt->inq_pag.now && !bt->inq_pag.last); hfp->exist = btinfo.lb2.hfp; - b->link_cnt.now += (u8)hfp->exist; hid->exist = btinfo.lb2.hid; - b->link_cnt.now += (u8)hid->exist; a2dp->exist = btinfo.lb2.a2dp; - b->link_cnt.now += (u8)a2dp->exist; pan->exist = btinfo.lb2.pan; - b->link_cnt.now += (u8)pan->exist; + _update_bt_link_cnt(rtwdev, bt, 0); btc->dm.trx_info.bt_profile = u32_get_bits(btinfo.val, BT_PROFILE_PROTOCOL_MASK); /* parse raw info low-Byte3 */ From cf7f33eb04618cb5e34f2397de209db5736acbc6 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Thu, 30 Jul 2026 14:02:15 +0800 Subject: [PATCH 0879/1433] wifi: rtw89: coex: Fix BT-info parsing for 5/6 GHz band & dual Bluetooth The _update_bt_info() function always parsed BT-info for bt0 and used a single raw_info buffer regardless of which BT device sent the packet or which RF band it belongs to. This caused two bugs: 1. BT-info packets from bt1 were incorrectly parsed into bt0's state. 2. BT-info packets from 5/6 GHz BT (L1 bit7=1) overwrote the 2.4 GHz link_info and raw_info, breaking duplicate detection across bands. Fix by adding the bid parameter to select bt0/bt1, detecting the 5/6 GHz band flag (L1 bit7) to route packets into link_info_56g with a dedicated raw_info_56g buffer, and updating the early-return mask to ignore bit7 when checking the length field. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260730060220.55844-3-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 61 ++++++++++++++++------- drivers/net/wireless/realtek/rtw89/core.h | 3 +- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index fd9ddd55c192..d5b1c68539d9 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -9005,23 +9005,46 @@ static u8 _update_bt_rssi_level(struct rtw89_dev *rtwdev, u8 rssi) #define BT_PROFILE_PROTOCOL_MASK GENMASK(7, 4) -static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) +static void _update_bt_info(struct rtw89_dev *rtwdev, u8 bid, u8 *buf, u32 len) { const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_bt_a2dp_desc *a2dp; struct rtw89_btc_cx *cx = &btc->cx; - struct rtw89_btc_bt_info *bt = &cx->bt0; - struct rtw89_btc_bt_link_info *b = &bt->link_info; - struct rtw89_btc_bt_hfp_desc *hfp = &b->hfp_desc; - struct rtw89_btc_bt_hid_desc *hid = &b->hid_desc; - struct rtw89_btc_bt_a2dp_desc *a2dp = &b->a2dp_desc; - struct rtw89_btc_bt_pan_desc *pan = &b->pan_desc; + struct rtw89_btc_bt_hfp_desc *hfp; + struct rtw89_btc_bt_hid_desc *hid; + struct rtw89_btc_bt_pan_desc *pan; + struct rtw89_btc_bt_link_info *b; + struct rtw89_btc_bt_info *bt; union btc_btinfo btinfo; + u8 is_bt_56g = 0; + u8 *raw_info; - if (buf[BTC_BTINFO_L1] != 6) + /* Bit7 is used for RF-band: 0:BT_2.4 GHz, 1:BT_5/6 GHz */ + if ((buf[BTC_BTINFO_L1] & 0x7f) != 6) return; - if (!memcmp(bt->raw_info, buf, BTC_BTINFO_MAX)) { + bt = (bid == BTC_BT_1ST) ? &cx->bt0 : &cx->bt1; + + if ((buf[BTC_BTINFO_L1] & BIT(7)) && bt->band_56G_support) { + b = &bt->link_info_56g; + is_bt_56g = 1; + if (b->status.map.connect) + bt->rf_band_map |= BIT(RTW89_BAND_5G); + else + bt->rf_band_map &= ~BIT(RTW89_BAND_5G); + } else { + b = &bt->link_info; + bt->rf_band_map |= BIT(RTW89_BAND_2G); + } + + hfp = &b->hfp_desc; + hid = &b->hid_desc; + a2dp = &b->a2dp_desc; + pan = &b->pan_desc; + raw_info = is_bt_56g ? bt->raw_info_56g : bt->raw_info; + + if (!memcmp(raw_info, buf, BTC_BTINFO_MAX)) { rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): return by bt-info duplicate!!\n", __func__); @@ -9029,16 +9052,16 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) return; } - memcpy(bt->raw_info, buf, BTC_BTINFO_MAX); + memcpy(raw_info, buf, BTC_BTINFO_MAX); rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s(): bt_info[2]=0x%02x\n", - __func__, bt->raw_info[2]); + __func__, raw_info[2]); hid->type = 0; /* parse raw info low-Byte2 */ - btinfo.val = bt->raw_info[BTC_BTINFO_L2]; + btinfo.val = raw_info[BTC_BTINFO_L2]; b->status.map.connect = btinfo.lb2.connect; b->status.map.sco_busy = btinfo.lb2.sco_busy; b->status.map.acl_busy = btinfo.lb2.acl_busy; @@ -9050,11 +9073,11 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) hid->exist = btinfo.lb2.hid; a2dp->exist = btinfo.lb2.a2dp; pan->exist = btinfo.lb2.pan; - _update_bt_link_cnt(rtwdev, bt, 0); + _update_bt_link_cnt(rtwdev, bt, is_bt_56g); btc->dm.trx_info.bt_profile = u32_get_bits(btinfo.val, BT_PROFILE_PROTOCOL_MASK); /* parse raw info low-Byte3 */ - btinfo.val = bt->raw_info[BTC_BTINFO_L3]; + btinfo.val = raw_info[BTC_BTINFO_L3]; if (btinfo.lb3.retry != 0) bt->bcnt[BTC_BCNT_RETRY]++; b->cqddr = btinfo.lb3.cqddr; @@ -9065,14 +9088,14 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) b->status.map.mesh_busy = btinfo.lb3.mesh_busy; /* parse raw info high-Byte0 */ - btinfo.val = bt->raw_info[BTC_BTINFO_H0]; + btinfo.val = raw_info[BTC_BTINFO_H0]; /* raw val is dBm unit, translate from -100~ 0dBm to 0~100%*/ b->rssi = chip->ops->btc_get_bt_rssi(rtwdev, btinfo.hb0.rssi); bt->rssi_level = _update_bt_rssi_level(rtwdev, b->rssi); btc->dm.trx_info.bt_rssi = bt->rssi_level; /* parse raw info high-Byte1 */ - btinfo.val = bt->raw_info[BTC_BTINFO_H1]; + btinfo.val = raw_info[BTC_BTINFO_H1]; b->status.map.ble_connect = btinfo.hb1.ble_connect; if (btinfo.hb1.ble_connect) { if (hid->exist) @@ -9101,7 +9124,7 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) b->multi_link.now = btinfo.hb1.multi_link; /* parse raw info high-Byte2 */ - btinfo.val = bt->raw_info[BTC_BTINFO_H2]; + btinfo.val = raw_info[BTC_BTINFO_H2]; pan->active = !!btinfo.hb2.pan_active; bt->bcnt[BTC_BCNT_AFH] += !!(btinfo.hb2.afh_update && !b->afh_update); @@ -9114,7 +9137,7 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 *buf, u32 len) hid->type |= (hid->slot_info == BTC_HID_218 ? BTC_HID_218 : BTC_HID_418); /* parse raw info high-Byte3 */ - btinfo.val = bt->raw_info[BTC_BTINFO_H3]; + btinfo.val = raw_info[BTC_BTINFO_H3]; a2dp->bitpool = btinfo.hb3.a2dp_bitpool; if (b->tx_3m != (u32)btinfo.hb3.tx_3m) @@ -9785,7 +9808,7 @@ void rtw89_btc_c2h_handle(struct rtw89_dev *rtwdev, struct sk_buff *skb, rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], handle C2H BT INFO with data %8ph\n", buf); bt->bcnt[BTC_BCNT_INFOUPDATE]++; - _update_bt_info(rtwdev, buf, len); + _update_bt_info(rtwdev, bid, buf, len); break; case BTF_EVNT_BT_SCBD: bt->bcnt[BTC_BCNT_SCBDUPDATE]++; diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index d82de04352e6..7f114c9a98ad 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -2705,7 +2705,8 @@ struct rtw89_btc_bt_info { struct rtw89_btc_rf_para rf_para; union rtw89_btc_bt_rfk_info_map rfk_info; - u8 raw_info[BTC_BTINFO_MAX]; /* raw bt info from mailbox */ + u8 raw_info[BTC_BTINFO_MAX]; /* raw bt info from mailbox (2.4G) */ + u8 raw_info_56g[BTC_BTINFO_MAX]; /* raw bt info from mailbox (5/6G) */ u8 txpwr_info[BTC_BTINFO_MAX]; u8 link_weight[BTC_BT_BMAX]; /* Link Weight for RF-band/HWB selection */ u8 rssi_level; From 8847c1b12bdd501a188039d4f4ab53bed519f235 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Thu, 30 Jul 2026 14:02:16 +0800 Subject: [PATCH 0880/1433] wifi: rtw89: coex: Clear BT link weight when BT is disabled When a BT device is disabled, _update_bt_link_cnt() is no longer called for that device, so its link_weight[] retains stale values from the last active period. _set_bind_info() then computes a non-zero band score for the disabled device, which can lead to an incorrect tdd_bind.rf_band selection and ultimately wrong coexistence policy. Clear link_weight[] for any disabled BT device at the start of the _set_bind_info() loop so that stale scores are not carried forward. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260730060220.55844-4-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index d5b1c68539d9..ac3d0c63f930 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -8167,6 +8167,10 @@ static void _set_bind_info(struct rtw89_btc *btc, u8 type) memset(bd, 0, sizeof(*bd)); /* compare BT 2GHz/5GHz profile by link-weighting */ for (i = 0; i < BTC_BT_BMAX; i++) { + if (!cx->bt0.enable.now) + cx->bt0.link_weight[i] = 0; + if (!cx->bt1.enable.now) + cx->bt1.link_weight[i] = 0; link_weight[BTC_BT_1ST][i] = cx->bt0.link_weight[i]; link_weight[BTC_BT_2ND][i] = cx->bt1.link_weight[i]; link_weight[BTC_BT_EXT][i] = cx->bt_ext.link_weight[i]; From 4d9f08115092651fe5d99c1ea27b6600944ba03a Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Thu, 30 Jul 2026 14:02:17 +0800 Subject: [PATCH 0881/1433] wifi: rtw89: coex: Change Wi-Fi link_mode_v0 translating timing Logic should only get explicit link_mode only, to cover the old branch link_mode_v0 using (old branch firmware needed) should do translating after link_mode is settled. And the coexistence logic has already updated to new branch style, so the logic should use new link_mode, don't need to consider link_mode_v0. To prevent unexpected logic bug, refine the related logic. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260730060220.55844-5-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 247 ++++++++++------------ 1 file changed, 112 insertions(+), 135 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index ac3d0c63f930..2f06fcf0d67f 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -4095,15 +4095,10 @@ static void _set_rf_trx_para(struct rtw89_dev *rtwdev) if (bmode == BTC_WLINK_NOLINK) { return; - } else if (btc->ver->fwlrole == 10) { - if (rf_band == RTW89_BAND_5G) { - mode = BTC_WLINK_V0_5G; - } else if (rf_band == RTW89_BAND_2G && - bmode == BTC_WLINK_STA) { - mode = BTC_WLINK_V0_2G_STA; - } - } else { - mode = r->link_mode_v0; + } else if (rf_band == RTW89_BAND_5G) { + mode = BTC_WLINK_V0_5G; + } else if (rf_band == RTW89_BAND_2G && bmode == BTC_WLINK_STA) { + mode = BTC_WLINK_V0_2G_STA; } /* decide trx_para_level */ @@ -6212,14 +6207,15 @@ static void _set_btg_ctrl(struct rtw89_dev *rtwdev) if (btc->ant_type == BTC_ANT_SHARED) { if (!(bt->run_patch_code && bt->enable.now)) is_btg = BTC_BTGCTRL_DISABLE; - else if (wl_rinfo->link_mode_v0 != BTC_WLINK_V0_5G) + else if (wl->rf_band_map[RTW89_MAC_0] & BIT(RTW89_BAND_2G) || + wl->rf_band_map[RTW89_MAC_1] & BIT(RTW89_BAND_2G)) is_btg = BTC_BTGCTRL_ENABLE; else is_btg = BTC_BTGCTRL_DISABLE; /* bb call ctrl_btg() in WL FW by slot */ if (!btc->ver->fcxosi && - wl_rinfo->link_mode_v0 == BTC_WLINK_V0_25G_MCC) + wl_rinfo->link_mode == BTC_WLINK_DB_MCC) is_btg = BTC_BTGCTRL_BB_GNT_FWCTRL; } @@ -7107,25 +7103,17 @@ void _update_dbcc_band(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx) #define BTC_CHK_HANG_MAX 3 #define BTC_SCB_INV_VALUE GENMASK(31, 0) -static u8 _get_role_link_mode(struct rtw89_btc_wl_role_info *r, u8 role, bool notv10) +static u8 _get_role_link_mode(u8 role) { switch (role) { case RTW89_WIFI_ROLE_STATION: default: - if (notv10) - r->link_mode_v0 = BTC_WLINK_V0_2G_STA; return BTC_WLINK_STA; case RTW89_WIFI_ROLE_P2P_GO: - if (notv10) - r->link_mode_v0 = BTC_WLINK_V0_2G_GO; return BTC_WLINK_GO; case RTW89_WIFI_ROLE_P2P_CLIENT: - if (notv10) - r->link_mode_v0 = BTC_WLINK_V0_2G_GC; return BTC_WLINK_GC; case RTW89_WIFI_ROLE_AP: - if (notv10) - r->link_mode_v0 = BTC_WLINK_V0_2G_AP; return BTC_WLINK_AP; } } @@ -7144,13 +7132,12 @@ static bool _chk_role_ch_group(const struct rtw89_btc_chdef *r1, } static u8 _chk_dbcc(struct rtw89_dev *rtwdev, struct rtw89_btc_chdef *ch, - u8 *phy, u8 *role, u8 link_cnt, bool notv10) + u8 *phy, u8 *role, u8 link_cnt) { struct rtw89_btc_wl_info *wl = &rtwdev->btc.cx.wl; struct rtw89_btc_wl_role_info *rinfo = &wl->role_info; bool is_2g_ch_exist = false, is_multi_role_in_2g_phy = false; u8 j, k, dbcc_2g_cid = 0, dbcc_2g_cid2 = 0; - u8 mode; /* find out the 2G-PHY by connect-id ->ch */ for (j = 0; j < link_cnt; j++) { @@ -7161,12 +7148,8 @@ static u8 _chk_dbcc(struct rtw89_dev *rtwdev, struct rtw89_btc_chdef *ch, } /* If no any 2G-port exist, it's impossible because 5G-exclude */ - if (!is_2g_ch_exist) { - mode = BTC_WLINK_SB_MCC; - if (notv10) - rinfo->link_mode_v0 = BTC_WLINK_V0_5G; - return mode; - } + if (!is_2g_ch_exist) + return BTC_WLINK_SB_MCC; dbcc_2g_cid = j; rinfo->dbcc_2g_phy = phy[dbcc_2g_cid]; @@ -7178,7 +7161,7 @@ static u8 _chk_dbcc(struct rtw89_dev *rtwdev, struct rtw89_btc_chdef *ch, /* connect_cnt <= 2 */ if (link_cnt < BTC_TDMA_WLROLE_MAX) - return _get_role_link_mode(rinfo, role[dbcc_2g_cid], notv10); + return _get_role_link_mode(role[dbcc_2g_cid]); /* find the other-port in the 2G-PHY, ex: PHY-0:6G, PHY1: mcc/scc */ for (k = 0; k < link_cnt; k++) { @@ -7194,33 +7177,24 @@ static u8 _chk_dbcc(struct rtw89_dev *rtwdev, struct rtw89_btc_chdef *ch, /* Single-role in 2G-PHY */ if (!is_multi_role_in_2g_phy) - return _get_role_link_mode(rinfo, role[dbcc_2g_cid], notv10); + return _get_role_link_mode(role[dbcc_2g_cid]); /* 2-role in 2G-PHY */ - if (ch[dbcc_2g_cid2].center_ch > 14) { - if (notv10) - rinfo->link_mode_v0 = BTC_WLINK_V0_25G_MCC; + if (ch[dbcc_2g_cid2].center_ch > 14) return BTC_WLINK_DB_MCC; - } else if (_chk_role_ch_group(&ch[dbcc_2g_cid], &ch[dbcc_2g_cid2])) { - if (notv10) - rinfo->link_mode_v0 = BTC_WLINK_V0_2G_SCC; + else if (_chk_role_ch_group(&ch[dbcc_2g_cid], &ch[dbcc_2g_cid2])) return BTC_WLINK_SCC; - } else { - if (notv10) - rinfo->link_mode_v0 = BTC_WLINK_V0_2G_MCC; + else return BTC_WLINK_SB_MCC; - } } static void _update_wl_link_mode(struct rtw89_dev *rtwdev, u8 hw_band, u8 type) { struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_wl_mlo_info *mlo_info = &wl->mlo_info; struct rtw89_btc_wl_role_info *r = &wl->role_info; u8 p2p_exist = wl->role_info.p2p_exist; - u8 rver = ver->fwlrole; if (hw_band == RTW89_PHY_1) p2p_exist = wl->role_info.p2p_exist_hb1; @@ -7240,20 +7214,6 @@ static void _update_wl_link_mode(struct rtw89_dev *rtwdev, u8 hw_band, u8 type) } else { r->link_mode = BTC_WLINK_STA; } - - if (rver >= 10 && rver < 100) - break; - - if (mlo_info->hwb_rf_band[hw_band] != RTW89_BAND_2G) { - r->link_mode_v0 = BTC_WLINK_V0_5G; - } else if (mlo_info->wmode[hw_band] == RTW89_MR_WMODE_1AP) { - r->link_mode_v0 = BTC_WLINK_V0_2G_GO; - } else if (mlo_info->wmode[hw_band] == RTW89_MR_WMODE_1CLIENT) { - if (wl->role_info.p2p_2g) - r->link_mode_v0 = BTC_WLINK_V0_2G_GC; - else - r->link_mode_v0 = BTC_WLINK_V0_2G_STA; - } break; case RTW89_MR_WTYPE_NONMLD_NONMLD: /* Non_MLO 2-role 2+0/0+2 */ case RTW89_MR_WTYPE_MLD1L1R_NONMLD: /* MLO only-1 link + P2P 2+0/0+2 */ @@ -7268,36 +7228,12 @@ static void _update_wl_link_mode(struct rtw89_dev *rtwdev, u8 hw_band, u8 type) } else { r->link_mode = BTC_WLINK_SCC; } - - if (rver >= 10 && rver < 100) - break; - - if (mlo_info->hwb_rf_band[hw_band] != RTW89_BAND_2G) { - r->link_mode_v0 = BTC_WLINK_V0_5G; - } else if (mlo_info->ch_type[hw_band] == RTW89_MR_CTX2_2GHZ_5GHZ || - mlo_info->ch_type[hw_band] == RTW89_MR_CTX2_2GHZ_6GHZ) { - r->link_mode_v0 = BTC_WLINK_V0_25G_MCC; - } else if (mlo_info->ch_type[hw_band] == RTW89_MR_CTX2_2GHZ) { - r->link_mode_v0 = BTC_WLINK_V0_2G_MCC; - } else if (mlo_info->ch_type[hw_band] == RTW89_MR_CTX1_2GHZ) { - r->link_mode_v0 = BTC_WLINK_V0_2G_SCC; - } break; case RTW89_MR_WTYPE_MLD2L1R: /* MLO_MLSR 2+0/0+2 */ if (p2p_exist) /* MLO_MLSR only support STA/GC */ r->link_mode = BTC_WLINK_GC; else r->link_mode = BTC_WLINK_STA; - - if (rver >= 10 && rver < 100) - break; - - if (mlo_info->hwb_rf_band[hw_band] != RTW89_BAND_2G) - r->link_mode_v0 = BTC_WLINK_V0_5G; - else if (wl->role_info.p2p_2g) - r->link_mode_v0 = BTC_WLINK_V0_2G_GC; - else - r->link_mode_v0 = BTC_WLINK_V0_2G_STA; break; case RTW89_MR_WTYPE_MLD2L1R_NONMLD: /* MLO_MLSR + P2P 2+0/0+2 */ case RTW89_MR_WTYPE_MLD2L2R_NONMLD: /* MLO_MLMR + P2P 1+1/2+2*/ @@ -7306,29 +7242,10 @@ static void _update_wl_link_mode(struct rtw89_dev *rtwdev, u8 hw_band, u8 type) * 5G only -> TDMA slot switch by E5G */ r->link_mode = BTC_WLINK_DB_MCC; - - if (rver >= 10 && rver < 100) - break; - - r->link_mode_v0 = BTC_WLINK_V0_25G_MCC; break; case RTW89_MR_WTYPE_MLD2L2R: /* MLO_MLMR 1+1/2+2 */ /* MLMR only support STA now (2024) */ r->link_mode = BTC_WLINK_STA; - - if (rver >= 10 && rver < 100) - break; - - if (mlo_info->hwb_rf_band[hw_band] != RTW89_BAND_2G) { - r->link_mode_v0 = BTC_WLINK_V0_5G; - } else if (mlo_info->wmode[hw_band] == RTW89_MR_WMODE_1AP) { - r->link_mode_v0 = BTC_WLINK_V0_2G_GO; - } else if (mlo_info->wmode[hw_band] == RTW89_MR_WMODE_1CLIENT) { - if (wl->role_info.p2p_2g) - r->link_mode_v0 = BTC_WLINK_V0_2G_GC; - else - r->link_mode_v0 = BTC_WLINK_V0_2G_STA; - } break; } } @@ -7494,8 +7411,6 @@ static void _update_wl_non_mlo_info(struct rtw89_dev *rtwdev) u8 cid_role[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER] = {}; u8 cid_phy[RTW89_BE_BTC_WL_MAX_ROLE_NUMBER] = {}; bool b2g = false, b5g = false, outloop = false; - bool notv10 = rtwdev->btc.ver->fwlrole != 10; - u8 mode_v0 = BTC_WLINK_V0_NOLINK; u8 mode = BTC_WLINK_NOLINK; u8 cnt_2g = 0, cnt_5g = 0; u8 i, j, cnt = 0; @@ -7540,33 +7455,72 @@ static void _update_wl_non_mlo_info(struct rtw89_dev *rtwdev) /* Be careful to change the following sequence!! */ if (cnt == 0) { mode = BTC_WLINK_NOLINK; - mode_v0 = BTC_WLINK_V0_NOLINK; } else if (wl_rinfo->dbcc_en) { /* update pta_req_band to 2.4GHz HW-BAND for DBCC = 1*/ - mode = _chk_dbcc(rtwdev, cid_ch, cid_phy, cid_role, cnt, notv10); - mode_v0 = wl_rinfo->link_mode_v0; - } else if (!b2g && b5g && notv10) { - mode = _get_role_link_mode(wl_rinfo, cid_role[0], notv10); - mode_v0 = BTC_WLINK_V0_5G; + mode = _chk_dbcc(rtwdev, cid_ch, cid_phy, cid_role, cnt); + } else if (!b2g && b5g) { + mode = _get_role_link_mode(cid_role[0]); } else if (b2g && b5g) { mode = BTC_WLINK_DB_MCC; - mode_v0 = BTC_WLINK_V0_25G_MCC; } else if (cnt >= 2) { - if (_chk_role_ch_group(&cid_ch[0], &cid_ch[1])) { + if (_chk_role_ch_group(&cid_ch[0], &cid_ch[1])) mode = BTC_WLINK_SCC; - mode_v0 = BTC_WLINK_V0_2G_SCC; - } else { + else mode = BTC_WLINK_SB_MCC; - mode_v0 = BTC_WLINK_V0_2G_MCC; - } } else { - mode = _get_role_link_mode(wl_rinfo, cid_role[0], notv10); - mode_v0 = wl_rinfo->link_mode_v0; + mode = _get_role_link_mode(cid_role[0]); } wl_rinfo->link_mode = mode; wl_rinfo->link_mode_hb1 = mode; - wl_rinfo->link_mode_v0 = mode_v0; +} + +static void _sync_wl_link_mode_v0(struct rtw89_dev *rtwdev) +{ + struct rtw89_btc_wl_role_info *r = &rtwdev->btc.cx.wl.role_info; + bool has_2g = false; + int i, j; + + for (i = 0; i < RTW89_BE_BTC_WL_MAX_ROLE_NUMBER && !has_2g; i++) + for (j = 0; j < RTW89_MAC_NUM && !has_2g; j++) + if (r->rlink[i][j].connected && + r->rlink[i][j].rf_band == RTW89_BAND_2G) + has_2g = true; + + if (!has_2g && r->link_mode != BTC_WLINK_NOLINK) { + r->link_mode_v0 = BTC_WLINK_V0_5G; + return; + } + + switch (r->link_mode) { + case BTC_WLINK_NOLINK: + r->link_mode_v0 = BTC_WLINK_V0_NOLINK; + break; + case BTC_WLINK_STA: + r->link_mode_v0 = BTC_WLINK_V0_2G_STA; + break; + case BTC_WLINK_AP: + r->link_mode_v0 = BTC_WLINK_V0_2G_AP; + break; + case BTC_WLINK_GO: + r->link_mode_v0 = BTC_WLINK_V0_2G_GO; + break; + case BTC_WLINK_GC: + r->link_mode_v0 = BTC_WLINK_V0_2G_GC; + break; + case BTC_WLINK_SCC: + r->link_mode_v0 = BTC_WLINK_V0_2G_SCC; + break; + case BTC_WLINK_SB_MCC: + r->link_mode_v0 = BTC_WLINK_V0_2G_MCC; + break; + case BTC_WLINK_DB_MCC: + r->link_mode_v0 = BTC_WLINK_V0_25G_MCC; + break; + default: + r->link_mode_v0 = BTC_WLINK_V0_OTHER; + break; + } } static void _modify_role_link_mode(struct rtw89_dev *rtwdev, u8 hw_band) @@ -7746,6 +7700,7 @@ static void _update_wl_info(struct rtw89_dev *rtwdev, struct rtw89_btc_wl_link_i } _modify_role_link_mode(rtwdev, rlink_id); + _sync_wl_link_mode_v0(rtwdev); if ((rlink_id == RTW89_PHY_0 && wl_rinfo->link_mode != link_mode_ori) || (rlink_id == RTW89_PHY_1 && wl_rinfo->link_mode_hb1 != link_mode_ori)) { @@ -7827,28 +7782,16 @@ static void _update_bt_scbd(struct rtw89_dev *rtwdev, u8 bid) { struct rtw89_btc_bt_link_info *bt_2g, *bt_56g; struct rtw89_btc *btc = &rtwdev->btc; - const struct rtw89_btc_ver *ver = btc->ver; struct rtw89_btc_cx *cx = &btc->cx; - struct rtw89_btc_wl_info *wl = &cx->wl; struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_bt_info *bt; u32 val, any_bt_connect, any_bt_6g_connect = 0; - u8 id, id_start, id_stop, mode = 0; - bool bt_link_change = false; - bool lps_ctrl = false; + bool bt_link_change = false, lps_ctrl = false; + u8 id, id_start, id_stop; if (!rtwdev->chip->scbd || bid > BTC_ALL_BT) return; - if (ver->fwlrole == 10) { - if (wl->role_info.link_mode == BTC_WLINK_STA && - (dm->tdd_en && wl->rf_band_map[RTW89_MAC_0] & - BIT(RTW89_BAND_2G))) - mode = BTC_WLINK_V0_2G_STA; - } else { - mode = wl->role_info.link_mode_v0; - } - if (bid == BTC_ALL_BT) { id_start = BTC_BT_1ST; id_stop = BTC_BT_2ND; @@ -7937,10 +7880,9 @@ static void _update_bt_scbd(struct rtw89_dev *rtwdev, u8 bid) } /* if specific profile exist */ - if (((bt->link_info.a2dp_desc.exist || - bt->link_info.pan_desc.exist || - bt->link_info.hfp_desc.exist) && - mode == BTC_WLINK_V0_2G_STA) || + if (bt->link_info.a2dp_desc.exist || + bt->link_info.pan_desc.exist || + bt->link_info.hfp_desc.exist || bt->whql_test) lps_ctrl = true; @@ -8468,7 +8410,8 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) struct rtw89_btc_wl_info *wl = &btc->cx.wl; struct rtw89_btc_bt_info *bt = &btc->cx.bt0; u8 mode = btc->dm.tdd_bind.wl_link_mode; - u8 mode_v0 = wl->role_info.link_mode_v0; + u8 mode_v0 = BTC_WLINK_V0_OTHER; + bool has_2ghz; lockdep_assert_wiphy(rtwdev->hw->wiphy); @@ -8601,6 +8544,40 @@ void _run_coex(struct rtw89_dev *rtwdev, enum btc_reason_and_action reason) mode_v0 = BTC_WLINK_V0_OTHER; break; } + } else { + /* No binding (out_of_band): derive mode_v0 from link_mode + rf_band_map */ + has_2ghz = wl->rf_band_map[RTW89_MAC_0] & BIT(RTW89_BAND_2G) || + wl->rf_band_map[RTW89_MAC_1] & BIT(RTW89_BAND_2G); + + if (!has_2ghz) { + mode_v0 = BTC_WLINK_V0_5G; + } else if (mode == BTC_WLINK_DB_MCC) { + mode_v0 = BTC_WLINK_V0_25G_MCC; + } else { + switch (mode) { + case BTC_WLINK_STA: + mode_v0 = BTC_WLINK_V0_2G_STA; + break; + case BTC_WLINK_AP: + mode_v0 = BTC_WLINK_V0_2G_AP; + break; + case BTC_WLINK_GO: + mode_v0 = BTC_WLINK_V0_2G_GO; + break; + case BTC_WLINK_GC: + mode_v0 = BTC_WLINK_V0_2G_GC; + break; + case BTC_WLINK_SCC: + mode_v0 = BTC_WLINK_V0_2G_SCC; + break; + case BTC_WLINK_SB_MCC: + mode_v0 = BTC_WLINK_V0_2G_MCC; + break; + default: + mode_v0 = BTC_WLINK_V0_OTHER; + break; + } + } } switch (mode_v0) { From dd7e70e76bf632c04a824e16fed034df74020109 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Thu, 30 Jul 2026 14:02:18 +0800 Subject: [PATCH 0882/1433] wifi: rtw89: coex: Port _update_bt_ctrl_lps() for BT profile-based LPS control The old lps_ctrl_scbd logic in _update_bt_scbd() blocked WiFi LPS only when mode == BTC_WLINK_V0_2G_STA, which created a dead-lock while WiFi already in LPS suppressed TDD binding so mode was never set to BTC_WLINK_V0_2G_STA, lps_ctrl_scbd stayed 0, and WiFi remained stuck in LPS. Port _update_bt_ctrl_lps() from the reference implementation to check BT profile existence (A2DP, HFP, PAN) for both radios directly, with out_of_band and freerun guards so LPS is only blocked when WiFi and Bluetooth actually share a band. Call it from both _update_bt_scbd() and _update_bt_info() so profile changes reported via either SCBD or BT info C2H correctly update lps_ctrl_scbd. Also remove the dead lps_ctrl_scbd_last field. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260730060220.55844-6-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 51 +++++++++++++++-------- drivers/net/wireless/realtek/rtw89/core.h | 1 - 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 2f06fcf0d67f..39dff9fdc1cd 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -3238,8 +3238,6 @@ static void _fw_set_policy(struct rtw89_dev *rtwdev, u16 policy_type, else btc->btc_ctrl_lps = dm->lps_ctrl_scbd; - dm->lps_ctrl_scbd_last = dm->lps_ctrl_scbd; - if (btc->btc_ctrl_lps == 1) rtw89_set_coex_ctrl_lps(rtwdev, btc->btc_ctrl_lps); @@ -7778,6 +7776,35 @@ void rtw89_coex_rfk_chk_work(struct wiphy *wiphy, struct wiphy_work *work) } } +static void _update_bt_ctrl_lps(struct rtw89_dev *rtwdev) +{ + struct rtw89_btc *btc = &rtwdev->btc; + struct rtw89_btc_bt_info *bt0 = &btc->cx.bt0; + struct rtw89_btc_bt_info *bt1 = &btc->cx.bt1; + struct rtw89_btc_dm *dm = &btc->dm; + bool lps_ctrl = false; + + if (dm->out_of_band || dm->freerun) + lps_ctrl = false; + else if (bt0->whql_test || bt1->whql_test) + lps_ctrl = true; + else if (bt0->link_info.a2dp_desc.exist || + bt0->link_info.hfp_desc.exist || + bt0->link_info.pan_desc.exist) + lps_ctrl = true; + else if (bt1->link_info.a2dp_desc.exist || + bt1->link_info.hfp_desc.exist || + bt1->link_info.pan_desc.exist) + lps_ctrl = true; + + if (dm->lps_ctrl_scbd != lps_ctrl) { + dm->lps_ctrl_scbd = lps_ctrl; + dm->lps_ctrl_change = true; + } else { + dm->lps_ctrl_change = false; + } +} + static void _update_bt_scbd(struct rtw89_dev *rtwdev, u8 bid) { struct rtw89_btc_bt_link_info *bt_2g, *bt_56g; @@ -7786,7 +7813,7 @@ static void _update_bt_scbd(struct rtw89_dev *rtwdev, u8 bid) struct rtw89_btc_dm *dm = &btc->dm; struct rtw89_btc_bt_info *bt; u32 val, any_bt_connect, any_bt_6g_connect = 0; - bool bt_link_change = false, lps_ctrl = false; + bool bt_link_change = false; u8 id, id_start, id_stop; if (!rtwdev->chip->scbd || bid > BTC_ALL_BT) @@ -7879,25 +7906,12 @@ static void _update_bt_scbd(struct rtw89_dev *rtwdev, u8 bid) bt_56g->status.map.connect = any_bt_6g_connect; } - /* if specific profile exist */ - if (bt->link_info.a2dp_desc.exist || - bt->link_info.pan_desc.exist || - bt->link_info.hfp_desc.exist || - bt->whql_test) - lps_ctrl = true; - - if (dm->lps_ctrl_scbd != lps_ctrl) { - dm->lps_ctrl_scbd = lps_ctrl; - bt_link_change = true; - dm->lps_ctrl_change = true; - } else { - dm->lps_ctrl_change = false; - } - bt->run_patch_code = !!(val & BTC_BSCB_PATCH_CODE); bt->scbd = val; } + _update_bt_ctrl_lps(rtwdev); + if (bt_link_change) { rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s: bt status change!!\n", __func__); @@ -9136,6 +9150,7 @@ static void _update_bt_info(struct rtw89_dev *rtwdev, u8 bid, u8 *buf, u32 len) RTW89_COEX_BT_DEVINFO_WORK_PERIOD); } + _update_bt_ctrl_lps(rtwdev); _run_coex(rtwdev, BTC_RSN_UPDATE_BT_INFO); } diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 7f114c9a98ad..2b21d969ece7 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -4116,7 +4116,6 @@ struct rtw89_btc_dm { u8 fdd_en: 1; u8 tdd_en: 1; u8 lps_ctrl_scbd: 1; - u8 lps_ctrl_scbd_last: 1; u8 lps_ctrl_change: 1; u8 bis_tdma: 1; /* BIS TDMA mode active */ u8 scbd_write_instant; From 97b81c10fe77bebcd4bc638f92e7afc8f8f96248 Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Thu, 30 Jul 2026 14:02:19 +0800 Subject: [PATCH 0883/1433] wifi: rtw89: coex: Update Wi-Fi/Bluetooth coexistence version to 9.24.1 The 9.24.1 coexistence version included firmware 0.35.111.X support for RTL8922A/D, 0.24.97.X support for RTL8852C, 0.29.133.X support for RTL8852B chip family. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260730060220.55844-7-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index 39dff9fdc1cd..5a9dc4d8a00b 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -11,7 +11,7 @@ #include "ps.h" #include "reg.h" -#define RTW89_COEX_VERSION 0x09180013 +#define RTW89_COEX_VERSION 0x09180113 #define FCXDEF_STEP 50 /* MUST <= FCXMAX_STEP and match with wl fw*/ #define BTC_E2G_LIMIT_DEF 80 From 86c3125c75d79ee7fb4d045bb9ada4c5b067eacc Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Thu, 30 Jul 2026 14:02:20 +0800 Subject: [PATCH 0884/1433] wifi: rtw89: 8922d: enable Makefile and Kconfig for RTL8922DE RTL8922DE is a WiFi 7 chipset, supporting 2x2 2GHz/5GHz/6GHz 4096/1024-QAM 160MHz channels. As STA, AP and P2P modes work well, enable this chipset. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260730060220.55844-8-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/Kconfig | 15 +++++++++++++++ drivers/net/wireless/realtek/rtw89/Makefile | 7 +++++++ 2 files changed, 22 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/Kconfig b/drivers/net/wireless/realtek/rtw89/Kconfig index 7200a944260f..7c678dd1f6b3 100644 --- a/drivers/net/wireless/realtek/rtw89/Kconfig +++ b/drivers/net/wireless/realtek/rtw89/Kconfig @@ -41,6 +41,9 @@ config RTW89_8852C config RTW89_8922A tristate +config RTW89_8922D + tristate + config RTW89_8851BE tristate "Realtek 8851BE PCI wireless network (Wi-Fi 6) adapter" depends on PCI @@ -169,6 +172,18 @@ config RTW89_8922AU 802.11be USB wireless network (Wi-Fi 7) adapter supporting 2x2 2GHz/5GHz/6GHz 4096-QAM 160MHz channels. +config RTW89_8922DE + tristate "Realtek 8922DE/8922DE-VS PCI wireless network (Wi-Fi 7) adapter" + depends on PCI + select RTW89_CORE + select RTW89_PCI + select RTW89_8922D + help + Select this option will enable support for 8922DE/8922DE-VS chipset + + 802.11be PCIe wireless network (Wi-Fi 7) adapter + supporting 2x2 2GHz/5GHz/6GHz 4096/1024-QAM 160MHz channels. + config RTW89_DEBUG bool diff --git a/drivers/net/wireless/realtek/rtw89/Makefile b/drivers/net/wireless/realtek/rtw89/Makefile index 5536714e0268..a85b7785330c 100644 --- a/drivers/net/wireless/realtek/rtw89/Makefile +++ b/drivers/net/wireless/realtek/rtw89/Makefile @@ -91,6 +91,13 @@ rtw89_8922ae-objs := rtw8922ae.o obj-$(CONFIG_RTW89_8922AU) += rtw89_8922au.o rtw89_8922au-objs := rtw8922au.o +obj-$(CONFIG_RTW89_8922D) += rtw89_8922d.o +rtw89_8922d-objs := rtw8922d.o \ + rtw8922d_rfk.o + +obj-$(CONFIG_RTW89_8922DE) += rtw89_8922de.o +rtw89_8922de-objs := rtw8922de.o + rtw89_core-$(CONFIG_RTW89_DEBUG) += debug.o rtw89_core-$(CONFIG_RTW89_LEDS) += led.o rtw89_core-$(CONFIG_RTW89_LEDS_MC) += led_mc.o From 0ca7040f20998089d7004660ec819d4fef822fdf Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 2 Aug 2026 17:33:39 +0200 Subject: [PATCH 0885/1433] Revert "wifi: mac80211: don't encrypt pre-auth (ETH_P_PREAUTH) frames" This reverts commit dd406779999fa2065ec6b7c4f80906b727041d2c. Never encrypting the frames broke a number of tests that do additional pre-authentication while already connected, and then the frames didn't go out correctly. Whatever this was intended to fix, this wasn't the right fix. Fixes: dd406779999f ("wifi: mac80211: don't encrypt pre-auth (ETH_P_PREAUTH) frames") Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 0cf5f6ec75e6..14311aab7d18 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -557,9 +557,6 @@ ieee80211_tx_h_check_control_port_protocol(struct ieee80211_tx_data *tx) info->flags |= IEEE80211_TX_CTL_USE_MINRATE; } - if (tx->skb->protocol == htons(ETH_P_PREAUTH)) - info->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT; - return TX_CONTINUE; } From 996753804410e1eb6dee4a1481b8eb6bc389e337 Mon Sep 17 00:00:00 2001 From: Arend van Spriel Date: Fri, 31 Jul 2026 14:34:57 +0200 Subject: [PATCH 0886/1433] wifi: cfg80211: pre-assign cookie for driver callbacks Having a single place for cookie assignment and keeping that responsibility in the cfg80211 subsystem is a logical choice as it handles the userspace nl80211 API. add_nan_func already does this: cfg80211 calls cfg80211_assign_cookie() before invoking the driver. Apply the same pattern to remain_on_channel, mgmt_tx, probe_peer and tx_control_port by pre-assigning the cookie in the nl80211 command handlers before the rdev_* call. For tx_control_port the cookie is only pre-assigned when the caller requests an ack (cookie pointer non-NULL). Drivers may still overwrite the value for now; subsequent patches will remove per-driver cookie generation. Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Arend van Spriel Link: https://patch.msgid.link/20260731123509.1975281-2-arend.vanspriel@broadcom.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 6 ++++-- net/wireless/nl80211.c | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index dcbb0e55c32e..ee395e3a021a 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -5108,7 +5108,8 @@ struct mgmt_frame_regs { * @tdls_oper: Perform a high-level TDLS operation (e.g. TDLS link setup). * * @probe_peer: probe a connected peer (AP: STA MAC required; STA: no MAC), - * must return a cookie that is later passed to cfg80211_probe_status(). + * must use the @cookie as provided which is later passed to + * cfg80211_probe_status(). * * @set_noack_map: Set the NoAck Map for the TIDs. * @@ -5219,7 +5220,8 @@ struct mgmt_frame_regs { * user space * * @tx_control_port: TX a control port frame (EAPoL). The noencrypt parameter - * tells the driver that the frame should not be encrypted. + * tells the driver that the frame should not be encrypted. When @cookie + * is non-NULL it is pre-assigned by cfg80211; drivers must not modify it. * * @get_ftm_responder_stats: Retrieve FTM responder statistics, if available. * Statistics should be cumulative, currently no way to reset is provided. diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 8adbef5f0442..6ad1f9cf2dda 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -14642,6 +14642,7 @@ static int nl80211_remain_on_channel(struct sk_buff *skb, goto free_msg; } + cookie = cfg80211_assign_cookie(rdev); err = rdev_remain_on_channel(rdev, wdev, chandef.chan, duration, &cookie, rx_addr); @@ -14883,6 +14884,7 @@ static int nl80211_tx_mgmt(struct sk_buff *skb, struct genl_info *info) } params.chan = chandef.chan; + cookie = cfg80211_assign_cookie(rdev); err = cfg80211_mlme_mgmt_tx(rdev, wdev, ¶ms, &cookie); if (err) goto free_msg; @@ -16371,6 +16373,7 @@ static int nl80211_probe_peer(struct sk_buff *skb, struct genl_info *info) goto free_msg; } + cookie = cfg80211_assign_cookie(rdev); err = rdev_probe_peer(rdev, dev, addr, &cookie); if (err) goto free_msg; @@ -18586,6 +18589,8 @@ static int nl80211_tx_control_port(struct sk_buff *skb, struct genl_info *info) link_id = nl80211_link_id_or_invalid(info->attrs); + if (!dont_wait_for_ack) + cookie = cfg80211_assign_cookie(rdev); err = rdev_tx_control_port(rdev, dev, buf, len, dest, cpu_to_be16(proto), noencrypt, link_id, dont_wait_for_ack ? NULL : &cookie); From fd6856890f3e90426b93c52161e22e8732a55606 Mon Sep 17 00:00:00 2001 From: Arend van Spriel Date: Fri, 31 Jul 2026 14:34:58 +0200 Subject: [PATCH 0887/1433] wifi: mac80211: stop using ieee80211_mgmt_tx_cookie() Now that cfg80211 pre-assigns the cookie before calling into mac80211, stop calling ieee80211_mgmt_tx_cookie() in all affected paths: - ieee80211_start_roc_work(): for normal ROC use the pre-assigned value directly instead of generating a new one. - ieee80211_attach_ack_skb(): the cookie is already set by the caller; remove the ieee80211_mgmt_tx_cookie() call and store it in the ack SKB as-is. This covers both mgmt_tx and probe_peer since both call ieee80211_attach_ack_skb(). - ieee80211_mgmt_tx(): the dummy 0xffffffff assignment for the dont_wait_for_ack case is no longer needed; cfg80211_assign_cookie() guarantees a non-zero value which is sufficient for the internal ROC vs mgmt-tx distinction. - ieee80211_store_ack_skb(): same fix for the tx_control_port path. With no remaining callers, remove ieee80211_mgmt_tx_cookie() and the roc_cookie_counter field from struct ieee80211_local. Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Arend van Spriel Link: https://patch.msgid.link/20260731123509.1975281-3-arend.vanspriel@broadcom.com Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 14 -------------- net/mac80211/ieee80211_i.h | 3 --- net/mac80211/offchannel.c | 17 ++++------------- net/mac80211/tx.c | 4 +--- 4 files changed, 5 insertions(+), 33 deletions(-) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 0a9247be26af..b6d02d2b28f5 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -4831,19 +4831,6 @@ int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev, return __ieee80211_channel_switch(wiphy, dev, params); } -u64 ieee80211_mgmt_tx_cookie(struct ieee80211_local *local) -{ - lockdep_assert_wiphy(local->hw.wiphy); - - local->roc_cookie_counter++; - - /* wow, you wrapped 64 bits ... more likely a bug */ - if (WARN_ON(local->roc_cookie_counter == 0)) - local->roc_cookie_counter++; - - return local->roc_cookie_counter; -} - int ieee80211_attach_ack_skb(struct ieee80211_local *local, struct sk_buff *skb, u64 *cookie, gfp_t gfp) { @@ -4868,7 +4855,6 @@ int ieee80211_attach_ack_skb(struct ieee80211_local *local, struct sk_buff *skb, IEEE80211_SKB_CB(skb)->status_data_idr = 1; IEEE80211_SKB_CB(skb)->status_data = id; - *cookie = ieee80211_mgmt_tx_cookie(local); IEEE80211_SKB_CB(ack_skb)->ack.cookie = *cookie; return 0; diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index a1ef88fe846d..3760319ab079 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -1710,8 +1710,6 @@ struct ieee80211_local { struct list_head roc_list; struct wiphy_work hw_roc_start, hw_roc_done; unsigned long hw_roc_start_time; - u64 roc_cookie_counter; - struct idr ack_status_frames; spinlock_t ack_status_lock; @@ -1991,7 +1989,6 @@ u64 ieee80211_reset_erp_info(struct ieee80211_sub_if_data *sdata); void ieee80211_handle_queued_frames(struct ieee80211_local *local); -u64 ieee80211_mgmt_tx_cookie(struct ieee80211_local *local); int ieee80211_attach_ack_skb(struct ieee80211_local *local, struct sk_buff *skb, u64 *cookie, gfp_t gfp); diff --git a/net/mac80211/offchannel.c b/net/mac80211/offchannel.c index 2bceb73717c6..94be6497f259 100644 --- a/net/mac80211/offchannel.c +++ b/net/mac80211/offchannel.c @@ -602,14 +602,12 @@ static int ieee80211_start_roc_work(struct ieee80211_local *local, /* * cookie is either the roc cookie (for normal roc) - * or the SKB (for mgmt TX) + * or the mgmt_tx cookie; both are pre-assigned by cfg80211 */ - if (!txskb) { - roc->cookie = ieee80211_mgmt_tx_cookie(local); - *cookie = roc->cookie; - } else { + if (!txskb) + roc->cookie = *cookie; + else roc->mgmt_tx_cookie = *cookie; - } req = wiphy_dereference(local->hw.wiphy, local->scan_req); @@ -1021,13 +1019,6 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, kfree_skb(skb); goto out_unlock; } - } else { - /* Assign a dummy non-zero cookie, it's not sent to - * userspace in this case but we rely on its value - * internally in the need_offchan case to distinguish - * mgmt-tx from remain-on-channel. - */ - *cookie = 0xffffffff; } if (!need_offchan) { diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 14311aab7d18..b62a41bda49d 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2602,10 +2602,8 @@ static u16 ieee80211_store_ack_skb(struct ieee80211_local *local, if (id >= 0) { info_id = id; *info_flags |= IEEE80211_TX_CTL_REQ_TX_STATUS; - if (cookie) { - *cookie = ieee80211_mgmt_tx_cookie(local); + if (cookie) IEEE80211_SKB_CB(ack_skb)->ack.cookie = *cookie; - } } else { kfree_skb(ack_skb); } From 5c4b13cb50cae52afb34d043b8599ff767135017 Mon Sep 17 00:00:00 2001 From: Arend van Spriel Date: Fri, 31 Jul 2026 14:34:59 +0200 Subject: [PATCH 0888/1433] wifi: ath6kl: use pre-assigned cookie for remain_on_channel and mgmt_tx Stop generating cookies in ath6kl_remain_on_channel() and ath6kl_mgmt_tx(). cfg80211 now pre-assigns the cookie before calling into the driver. For remain_on_channel, store the pre-assigned cookie in vif->last_roc_id. Widen last_roc_id and last_cancel_roc_id from u32 to u64 to hold the full 64-bit cookie value. For mgmt_tx, store the pre-assigned cookie in wmi->last_mgmt_tx_cookie so the firmware TX status event handler can pass the correct cookie to cfg80211_mgmt_tx_status(). Thread the cookie through the powersave queue (ath6kl_mgmt_buff) so it is available when the frame is eventually dequeued and sent. Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Arend van Spriel Link: https://patch.msgid.link/20260731123509.1975281-4-arend.vanspriel@broadcom.com Signed-off-by: Johannes Berg --- drivers/net/wireless/ath/ath6kl/cfg80211.c | 18 ++++++------------ drivers/net/wireless/ath/ath6kl/core.h | 5 +++-- drivers/net/wireless/ath/ath6kl/main.c | 1 + drivers/net/wireless/ath/ath6kl/txrx.c | 1 + drivers/net/wireless/ath/ath6kl/wmi.c | 2 +- drivers/net/wireless/ath/ath6kl/wmi.h | 1 + 6 files changed, 13 insertions(+), 15 deletions(-) diff --git a/drivers/net/wireless/ath/ath6kl/cfg80211.c b/drivers/net/wireless/ath/ath6kl/cfg80211.c index ecde91159b54..e9d6e6a53d7d 100644 --- a/drivers/net/wireless/ath/ath6kl/cfg80211.c +++ b/drivers/net/wireless/ath/ath6kl/cfg80211.c @@ -3038,16 +3038,10 @@ static int ath6kl_remain_on_channel(struct wiphy *wiphy, { struct ath6kl_vif *vif = ath6kl_vif_from_wdev(wdev); struct ath6kl *ar = ath6kl_priv(vif->ndev); - u32 id; /* TODO: if already pending or ongoing remain-on-channel, * return -EBUSY */ - id = ++vif->last_roc_id; - if (id == 0) { - /* Do not use 0 as the cookie value */ - id = ++vif->last_roc_id; - } - *cookie = id; + vif->last_roc_id = *cookie; return ath6kl_wmi_remain_on_chnl_cmd(ar->wmi, vif->fw_vif_idx, chan->center_freq, duration); @@ -3106,6 +3100,7 @@ static int ath6kl_send_go_probe_resp(struct ath6kl_vif *vif, static bool ath6kl_mgmt_powersave_ap(struct ath6kl_vif *vif, u32 id, + u64 cookie, u32 freq, u32 wait, const u8 *buf, @@ -3138,6 +3133,7 @@ static bool ath6kl_mgmt_powersave_ap(struct ath6kl_vif *vif, INIT_LIST_HEAD(&mgmt_buf->list); mgmt_buf->id = id; + mgmt_buf->cookie = cookie; mgmt_buf->freq = freq; mgmt_buf->wait = wait; mgmt_buf->len = len; @@ -3222,7 +3218,6 @@ static int ath6kl_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, * Send Probe Response frame in GO mode using a separate WMI * command to allow the target to fill in the generic IEs. */ - *cookie = 0; /* TX status not supported */ return ath6kl_send_go_probe_resp(vif, buf, len, freq); } @@ -3235,16 +3230,15 @@ static int ath6kl_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, id = vif->send_action_id++; } - *cookie = id; - /* AP mode Power saving processing */ if (vif->nw_type == AP_NETWORK) { - queued = ath6kl_mgmt_powersave_ap(vif, id, freq, wait, buf, len, - &more_data, no_cck); + queued = ath6kl_mgmt_powersave_ap(vif, id, *cookie, freq, wait, + buf, len, &more_data, no_cck); if (queued) return 0; } + ar->wmi->last_mgmt_tx_cookie = *cookie; return ath6kl_wmi_send_mgmt_cmd(ar->wmi, vif->fw_vif_idx, id, freq, wait, buf, len, no_cck); } diff --git a/drivers/net/wireless/ath/ath6kl/core.h b/drivers/net/wireless/ath/ath6kl/core.h index 77e052336eb5..a0b236eeeab5 100644 --- a/drivers/net/wireless/ath/ath6kl/core.h +++ b/drivers/net/wireless/ath/ath6kl/core.h @@ -404,6 +404,7 @@ struct ath6kl_mgmt_buff { u32 freq; u32 wait; u32 id; + u64 cookie; bool no_cck; size_t len; u8 buf[]; @@ -631,8 +632,8 @@ struct ath6kl_vif { struct cfg80211_scan_request *scan_req; enum sme_state sme_state; int reconnect_flag; - u32 last_roc_id; - u32 last_cancel_roc_id; + u64 last_roc_id; + u64 last_cancel_roc_id; u32 send_action_id; bool probe_req_report; u16 assoc_bss_beacon_int; diff --git a/drivers/net/wireless/ath/ath6kl/main.c b/drivers/net/wireless/ath/ath6kl/main.c index 8afc6589fc51..9c023d7ce305 100644 --- a/drivers/net/wireless/ath/ath6kl/main.c +++ b/drivers/net/wireless/ath/ath6kl/main.c @@ -898,6 +898,7 @@ void ath6kl_pspoll_event(struct ath6kl_vif *vif, u8 aid) spin_unlock_bh(&conn->psq_lock); conn->sta_flags |= STA_PS_POLLED; + ar->wmi->last_mgmt_tx_cookie = mgmt_buf->cookie; ath6kl_wmi_send_mgmt_cmd(ar->wmi, vif->fw_vif_idx, mgmt_buf->id, mgmt_buf->freq, mgmt_buf->wait, mgmt_buf->buf, diff --git a/drivers/net/wireless/ath/ath6kl/txrx.c b/drivers/net/wireless/ath/ath6kl/txrx.c index d81825413906..609d458ca6c5 100644 --- a/drivers/net/wireless/ath/ath6kl/txrx.c +++ b/drivers/net/wireless/ath/ath6kl/txrx.c @@ -1469,6 +1469,7 @@ void ath6kl_rx(struct htc_target *target, struct htc_packet *packet) spin_unlock_bh(&conn->psq_lock); idx = vif->fw_vif_idx; + ar->wmi->last_mgmt_tx_cookie = mgmt->cookie; ath6kl_wmi_send_mgmt_cmd(ar->wmi, idx, mgmt->id, diff --git a/drivers/net/wireless/ath/ath6kl/wmi.c b/drivers/net/wireless/ath/ath6kl/wmi.c index 6c29f0bcec9f..2140fddba8ba 100644 --- a/drivers/net/wireless/ath/ath6kl/wmi.c +++ b/drivers/net/wireless/ath/ath6kl/wmi.c @@ -596,7 +596,7 @@ static int ath6kl_wmi_tx_status_event_rx(struct wmi *wmi, u8 *datap, int len, ath6kl_dbg(ATH6KL_DBG_WMI, "tx_status: id=%x ack_status=%u\n", id, ev->ack_status); if (wmi->last_mgmt_tx_frame) { - cfg80211_mgmt_tx_status(&vif->wdev, id, + cfg80211_mgmt_tx_status(&vif->wdev, wmi->last_mgmt_tx_cookie, wmi->last_mgmt_tx_frame, wmi->last_mgmt_tx_frame_len, !!ev->ack_status, GFP_ATOMIC); diff --git a/drivers/net/wireless/ath/ath6kl/wmi.h b/drivers/net/wireless/ath/ath6kl/wmi.h index 8fbece3fdad9..89ce2abbb1cd 100644 --- a/drivers/net/wireless/ath/ath6kl/wmi.h +++ b/drivers/net/wireless/ath/ath6kl/wmi.h @@ -125,6 +125,7 @@ struct wmi { u8 *last_mgmt_tx_frame; size_t last_mgmt_tx_frame_len; + u64 last_mgmt_tx_cookie; u8 saved_pwr_mode; }; From 70557cadc828cbfd17d79481f5bcfbace7dc648c Mon Sep 17 00:00:00 2001 From: Arend van Spriel Date: Fri, 31 Jul 2026 14:35:00 +0200 Subject: [PATCH 0889/1433] wifi: wil6210: use pre-assigned cookie for remain_on_channel, mgmt_tx and probe_peer Stop overwriting the pre-assigned cookie in wil_p2p_listen(), wil_cfg80211_mgmt_tx(), and wil_cfg80211_probe_peer(). For remain_on_channel, store the pre-assigned cookie in p2p->cookie instead of incrementing it. All cancel and expiry callbacks already read from p2p->cookie so they pick up the correct value. For mgmt_tx, remove the defensive "cookie ? *cookie : 0" guard; cfg80211 guarantees a non-NULL cookie pointer. For probe_peer, store the pre-assigned cookie in req->cookie instead of the CID value. The CID is still available via req->cid for STA lookup in wil_probe_client_handle(). Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Arend van Spriel Link: https://patch.msgid.link/20260731123509.1975281-5-arend.vanspriel@broadcom.com Signed-off-by: Johannes Berg --- drivers/net/wireless/ath/wil6210/cfg80211.c | 6 ++---- drivers/net/wireless/ath/wil6210/p2p.c | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/ath/wil6210/cfg80211.c b/drivers/net/wireless/ath/wil6210/cfg80211.c index 5f2bd9a31faf..6ebd340c8ef7 100644 --- a/drivers/net/wireless/ath/wil6210/cfg80211.c +++ b/drivers/net/wireless/ath/wil6210/cfg80211.c @@ -1488,8 +1488,7 @@ int wil_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, */ tx_status = (rc == 0); rc = (rc == -EAGAIN) ? 0 : rc; - cfg80211_mgmt_tx_status(wdev, cookie ? *cookie : 0, buf, len, - tx_status, GFP_KERNEL); + cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, tx_status, GFP_KERNEL); return rc; } @@ -2399,13 +2398,12 @@ static int wil_cfg80211_probe_peer(struct wiphy *wiphy, return -ENOMEM; req->cid = cid; - req->cookie = cid; + req->cookie = *cookie; mutex_lock(&vif->probe_client_mutex); list_add_tail(&req->list, &vif->probe_client_pending); mutex_unlock(&vif->probe_client_mutex); - *cookie = req->cookie; queue_work(wil->wq_service, &vif->probe_client_worker); return 0; } diff --git a/drivers/net/wireless/ath/wil6210/p2p.c b/drivers/net/wireless/ath/wil6210/p2p.c index f20caf1a3905..6d2cadca259f 100644 --- a/drivers/net/wireless/ath/wil6210/p2p.c +++ b/drivers/net/wireless/ath/wil6210/p2p.c @@ -144,7 +144,7 @@ int wil_p2p_listen(struct wil6210_priv *wil, struct wireless_dev *wdev, } memcpy(&p2p->listen_chan, chan, sizeof(*chan)); - *cookie = ++p2p->cookie; + p2p->cookie = *cookie; p2p->listen_duration = duration; mutex_lock(&wil->vif_mutex); From c277330183a766e9c4531e56d54e391e980a0fc2 Mon Sep 17 00:00:00 2001 From: Arend van Spriel Date: Fri, 31 Jul 2026 14:35:01 +0200 Subject: [PATCH 0890/1433] wifi: brcmfmac: use pre-assigned cookie for remain_on_channel and mgmt_tx Stop generating cookies in brcmf_p2p_remain_on_channel() and brcmf_cfg80211_mgmt_tx(). For remain_on_channel, remove the cookie increment from brcmf_p2p_discover_listen() and store the pre-assigned cookie in p2p->remain_on_channel_cookie. The expiry callback in brcmf_p2p_notify_listen_complete() already reads from that field. For mgmt_tx, remove the "*cookie = 0" assignments in brcmf_cfg80211_mgmt_tx() and the cyw extension. The pre-assigned cookie is then correctly passed to cfg80211_mgmt_tx_status() which already uses *cookie. Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Arend van Spriel Link: https://patch.msgid.link/20260731123509.1975281-6-arend.vanspriel@broadcom.com Signed-off-by: Johannes Berg --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c | 2 -- drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c | 1 - drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c | 6 ++---- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 6b2c34e3a796..03a7a4c18279 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -5573,8 +5573,6 @@ brcmf_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, brcmf_dbg(TRACE, "Enter\n"); - *cookie = 0; - mgmt = (const struct ieee80211_mgmt *)buf; if (!ieee80211_is_mgmt(mgmt->frame_control)) { diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c index 873754be5174..5c7a475cb5e5 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c @@ -123,7 +123,6 @@ int brcmf_cyw_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, if (!ieee80211_is_auth(mgmt->frame_control)) return brcmf_cfg80211_mgmt_tx(wiphy, wdev, params, cookie); - *cookie = 0; vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); reinit_completion(&vif->mgmt_tx); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c index c7d7b35ab125..3d7bf25f1985 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c @@ -979,10 +979,8 @@ brcmf_p2p_discover_listen(struct brcmf_p2p_info *p2p, u16 channel, u32 duration) p2p->cfg->d11inf.encchspec(&ch); err = brcmf_p2p_set_discover_state(vif->ifp, WL_P2P_DISC_ST_LISTEN, ch.chspec, (u16)duration); - if (!err) { + if (!err) set_bit(BRCMF_P2P_STATUS_DISCOVER_LISTEN, &p2p->status); - p2p->remain_on_channel_cookie++; - } exit: return err; } @@ -1020,7 +1018,7 @@ int brcmf_p2p_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, goto exit; memcpy(&p2p->remain_on_channel, channel, sizeof(*channel)); - *cookie = p2p->remain_on_channel_cookie; + p2p->remain_on_channel_cookie = *cookie; cfg80211_ready_on_channel(wdev, *cookie, channel, duration, GFP_KERNEL); exit: From 63d42f17296c6fef30bd4ff069440c691e435c0b Mon Sep 17 00:00:00 2001 From: Arend van Spriel Date: Fri, 31 Jul 2026 14:35:02 +0200 Subject: [PATCH 0891/1433] wifi: mwifiex: use pre-assigned cookie for remain_on_channel and mgmt_tx Stop generating cookies via get_random_u32() in mwifiex_cfg80211_remain_on_channel() and mwifiex_cfg80211_mgmt_tx(). Use the pre-assigned cookie from cfg80211 instead. Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Arend van Spriel Link: https://patch.msgid.link/20260731123509.1975281-7-arend.vanspriel@broadcom.com Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c index 8ec2d22a8c33..102321cf8542 100644 --- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c +++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c @@ -259,7 +259,6 @@ mwifiex_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, tx_info->pkt_len = pkt_len; mwifiex_form_mgmt_frame(skb, buf, len); - *cookie = get_random_u32() | 1; if (ieee80211_is_action(mgmt->frame_control)) skb = mwifiex_clone_skb_for_tx_status(priv, @@ -326,7 +325,6 @@ mwifiex_cfg80211_remain_on_channel(struct wiphy *wiphy, duration); if (!ret) { - *cookie = get_random_u32() | 1; priv->roc_cfg.cookie = *cookie; priv->roc_cfg.chan = *chan; From cdda9756ee41197da77721d1715aea2df833cb96 Mon Sep 17 00:00:00 2001 From: Arend van Spriel Date: Fri, 31 Jul 2026 14:35:03 +0200 Subject: [PATCH 0892/1433] wifi: wilc1000: use pre-assigned cookie for remain_on_channel and mgmt_tx Stop generating cookies by incrementing inc_roc_cookie in remain_on_channel() and via get_random_u32() in mgmt_tx(). Use the pre-assigned cookie from cfg80211 instead. Remove the now-unused id local variable from remain_on_channel(). Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Arend van Spriel Link: https://patch.msgid.link/20260731123509.1975281-8-arend.vanspriel@broadcom.com Signed-off-by: Johannes Berg --- drivers/net/wireless/microchip/wilc1000/cfg80211.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/microchip/wilc1000/cfg80211.c b/drivers/net/wireless/microchip/wilc1000/cfg80211.c index 6654fce4ded8..7c19ad34fda5 100644 --- a/drivers/net/wireless/microchip/wilc1000/cfg80211.c +++ b/drivers/net/wireless/microchip/wilc1000/cfg80211.c @@ -1106,18 +1106,13 @@ static int remain_on_channel(struct wiphy *wiphy, int ret = 0; struct wilc_vif *vif = netdev_priv(wdev->netdev); struct wilc_priv *priv = &vif->priv; - u64 id; if (wdev->iftype == NL80211_IFTYPE_AP) { netdev_dbg(vif->ndev, "Required while in AP mode\n"); return ret; } - id = ++priv->inc_roc_cookie; - if (id == 0) - id = ++priv->inc_roc_cookie; - - ret = wilc_remain_on_channel(vif, id, chan->hw_value, + ret = wilc_remain_on_channel(vif, *cookie, chan->hw_value, wilc_wfi_remain_on_channel_expired); if (ret) return ret; @@ -1125,8 +1120,7 @@ static int remain_on_channel(struct wiphy *wiphy, vif->wilc->op_ch = chan->hw_value; priv->remain_on_ch_params.listen_ch = chan; - priv->remain_on_ch_params.listen_cookie = id; - *cookie = id; + priv->remain_on_ch_params.listen_cookie = *cookie; priv->p2p_listen_state = true; priv->remain_on_ch_params.listen_duration = duration; @@ -1170,7 +1164,6 @@ static int mgmt_tx(struct wiphy *wiphy, const u8 *vendor_ie; int ret = 0; - *cookie = get_random_u32(); priv->tx_cookie = *cookie; mgmt = (const struct ieee80211_mgmt *)buf; From ef15c65f89bd5af397cd762b1bdc50a46edbedb1 Mon Sep 17 00:00:00 2001 From: Arend van Spriel Date: Fri, 31 Jul 2026 14:35:04 +0200 Subject: [PATCH 0893/1433] wifi: nxpwifi: use pre-assigned cookie for remain_on_channel and mgmt_tx Stop calling nxpwifi_roc_cookie() to generate cookies in nxpwifi_cfg80211_remain_on_channel() and nxpwifi_cfg80211_mgmt_tx(). Use the pre-assigned cookie from cfg80211 instead. Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Arend van Spriel Link: https://patch.msgid.link/20260731123509.1975281-9-arend.vanspriel@broadcom.com Signed-off-by: Johannes Berg --- drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c index 4f9e20f72811..198174ac754e 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -212,7 +212,6 @@ nxpwifi_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, tx_info->pkt_len = pkt_len; nxpwifi_form_mgmt_frame(skb, buf, len); - *cookie = nxpwifi_roc_cookie(priv->adapter); if (ieee80211_is_action(mgmt->frame_control)) skb = nxpwifi_clone_skb_for_tx_status(priv, @@ -277,7 +276,6 @@ nxpwifi_cfg80211_remain_on_channel(struct wiphy *wiphy, duration); if (!ret) { - *cookie = nxpwifi_roc_cookie(adapter); priv->roc_cfg.cookie = *cookie; priv->roc_cfg.chan = *chan; From b6dfce6c0b97cc0c791c3fe384f68d384482023e Mon Sep 17 00:00:00 2001 From: Arend van Spriel Date: Fri, 31 Jul 2026 14:35:05 +0200 Subject: [PATCH 0894/1433] wifi: qtnfmac: use pre-assigned cookie for mgmt_tx Stop overwriting *cookie with a random value in qtnf_mgmt_tx(). The internal firmware frame identifier (short_cookie) is unchanged. Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Arend van Spriel Link: https://patch.msgid.link/20260731123509.1975281-10-arend.vanspriel@broadcom.com Signed-off-by: Johannes Berg --- drivers/net/wireless/quantenna/qtnfmac/cfg80211.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c index 9e44c85d2051..284bc61c46d0 100644 --- a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c +++ b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c @@ -454,8 +454,6 @@ qtnf_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, u16 flags = 0; u16 freq; - *cookie = short_cookie; - if (params->offchan) flags |= QLINK_FRAME_TX_FLAG_OFFCHAN; From 51a87a1cb61cde446429e355cb66b3dbe4851a8e Mon Sep 17 00:00:00 2001 From: Arend van Spriel Date: Fri, 31 Jul 2026 14:35:06 +0200 Subject: [PATCH 0895/1433] wifi: rtl8723bs: use pre-assigned cookie for mgmt_tx Stop using params->buf address as cookie value and simply pass the pre-assigned cookie in frame tx status. This implementation seems to fire-and-forget the transmitted frame as the cookie is not used in any other way. Signed-off-by: Arend van Spriel Link: https://patch.msgid.link/20260731123509.1975281-11-arend.vanspriel@broadcom.com Signed-off-by: Johannes Berg --- drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c b/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c index 6a97afd89dc7..0e6b40336123 100644 --- a/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c +++ b/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c @@ -2552,9 +2552,6 @@ static int cfg80211_rtw_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, padapter = rtw_netdev_priv(ndev); - /* cookie generation */ - *cookie = (unsigned long)buf; - /* indicate ack before issue frame to avoid racing with rsp frame */ rtw_cfg80211_mgmt_tx_status(padapter, *cookie, buf, len, ack, GFP_KERNEL); From 914781c72813989b512609a51f8abb68417f722d Mon Sep 17 00:00:00 2001 From: Arend van Spriel Date: Fri, 31 Jul 2026 14:35:07 +0200 Subject: [PATCH 0896/1433] wifi: cfg80211: convert cookie output to input parameter The remain_on_channel, mgmt_tx, and probe_peer ops previously used a u64 *cookie output parameter. Now that cfg80211 pre-assigns the cookie value before invoking drivers, the parameter conveys a value from caller to driver, not the other way around. Convert it to a plain u64 input parameter across the ops struct (cfg80211.h), rdev-ops.h wrappers, nl80211.c/mlme.c call sites, mac80211, and all driver implementations. The tx_control_port op is excluded: its cookie pointer is nullable (passed as NULL when dont_wait_for_ack is set), so the nullable pointer semantics are still required. Internal mac80211 helpers ieee80211_start_roc_work() and ieee80211_attach_ack_skb() still take u64 *cookie because they assign to the pointee; their callers now pass &cookie to take the address of the local value parameter. wil6210's internal wil_p2p_listen() is also updated to take u64 cookie since it is called directly from the remain_on_channel callback. Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Arend van Spriel Link: https://patch.msgid.link/20260731123509.1975281-12-arend.vanspriel@broadcom.com Signed-off-by: Johannes Berg --- drivers/net/wireless/ath/ath6kl/cfg80211.c | 10 +++++----- drivers/net/wireless/ath/wil6210/cfg80211.c | 10 +++++----- drivers/net/wireless/ath/wil6210/debugfs.c | 2 +- drivers/net/wireless/ath/wil6210/p2p.c | 6 +++--- drivers/net/wireless/ath/wil6210/wil6210.h | 4 ++-- .../broadcom/brcm80211/brcmfmac/cfg80211.c | 10 +++++----- .../broadcom/brcm80211/brcmfmac/cfg80211.h | 2 +- .../broadcom/brcm80211/brcmfmac/cyw/core.c | 7 +++---- .../wireless/broadcom/brcm80211/brcmfmac/p2p.c | 6 +++--- .../wireless/broadcom/brcm80211/brcmfmac/p2p.h | 2 +- .../net/wireless/marvell/mwifiex/cfg80211.c | 18 +++++++++--------- .../net/wireless/microchip/wilc1000/cfg80211.c | 12 ++++++------ drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 18 +++++++++--------- .../net/wireless/quantenna/qtnfmac/cfg80211.c | 2 +- .../staging/rtl8723bs/os_dep/ioctl_cfg80211.c | 4 ++-- include/net/cfg80211.h | 6 +++--- net/mac80211/cfg.c | 4 ++-- net/mac80211/ieee80211_i.h | 4 ++-- net/mac80211/offchannel.c | 10 +++++----- net/wireless/core.h | 2 +- net/wireless/mlme.c | 2 +- net/wireless/nl80211.c | 6 +++--- net/wireless/rdev-ops.h | 12 ++++++------ 23 files changed, 79 insertions(+), 80 deletions(-) diff --git a/drivers/net/wireless/ath/ath6kl/cfg80211.c b/drivers/net/wireless/ath/ath6kl/cfg80211.c index e9d6e6a53d7d..34025d64d615 100644 --- a/drivers/net/wireless/ath/ath6kl/cfg80211.c +++ b/drivers/net/wireless/ath/ath6kl/cfg80211.c @@ -3034,14 +3034,14 @@ static int ath6kl_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *chan, unsigned int duration, - u64 *cookie, const u8 *rx_addr) + u64 cookie, const u8 *rx_addr) { struct ath6kl_vif *vif = ath6kl_vif_from_wdev(wdev); struct ath6kl *ar = ath6kl_priv(vif->ndev); /* TODO: if already pending or ongoing remain-on-channel, * return -EBUSY */ - vif->last_roc_id = *cookie; + vif->last_roc_id = cookie; return ath6kl_wmi_remain_on_chnl_cmd(ar->wmi, vif->fw_vif_idx, chan->center_freq, duration); @@ -3186,7 +3186,7 @@ static bool ath6kl_is_p2p_go_ssid(const u8 *buf, size_t len) } static int ath6kl_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, - struct cfg80211_mgmt_tx_params *params, u64 *cookie) + struct cfg80211_mgmt_tx_params *params, u64 cookie) { struct ath6kl_vif *vif = ath6kl_vif_from_wdev(wdev); struct ath6kl *ar = ath6kl_priv(vif->ndev); @@ -3232,13 +3232,13 @@ static int ath6kl_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, /* AP mode Power saving processing */ if (vif->nw_type == AP_NETWORK) { - queued = ath6kl_mgmt_powersave_ap(vif, id, *cookie, freq, wait, + queued = ath6kl_mgmt_powersave_ap(vif, id, cookie, freq, wait, buf, len, &more_data, no_cck); if (queued) return 0; } - ar->wmi->last_mgmt_tx_cookie = *cookie; + ar->wmi->last_mgmt_tx_cookie = cookie; return ath6kl_wmi_send_mgmt_cmd(ar->wmi, vif->fw_vif_idx, id, freq, wait, buf, len, no_cck); } diff --git a/drivers/net/wireless/ath/wil6210/cfg80211.c b/drivers/net/wireless/ath/wil6210/cfg80211.c index 6ebd340c8ef7..9ac33d827f80 100644 --- a/drivers/net/wireless/ath/wil6210/cfg80211.c +++ b/drivers/net/wireless/ath/wil6210/cfg80211.c @@ -1432,7 +1432,7 @@ static int wil_cfg80211_set_wiphy_params(struct wiphy *wiphy, int radio_idx, int wil_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, struct cfg80211_mgmt_tx_params *params, - u64 *cookie) + u64 cookie) { const u8 *buf = params->buf; size_t len = params->len; @@ -1488,7 +1488,7 @@ int wil_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, */ tx_status = (rc == 0); rc = (rc == -EAGAIN) ? 0 : rc; - cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, tx_status, GFP_KERNEL); + cfg80211_mgmt_tx_status(wdev, cookie, buf, len, tx_status, GFP_KERNEL); return rc; } @@ -1734,7 +1734,7 @@ static int wil_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *chan, unsigned int duration, - u64 *cookie, const u8 *rx_addr) + u64 cookie, const u8 *rx_addr) { struct wil6210_priv *wil = wiphy_to_wil(wiphy); int rc; @@ -2380,7 +2380,7 @@ void wil_probe_client_flush(struct wil6210_vif *vif) static int wil_cfg80211_probe_peer(struct wiphy *wiphy, struct net_device *dev, - const u8 *peer, u64 *cookie) + const u8 *peer, u64 cookie) { struct wil6210_priv *wil = wiphy_to_wil(wiphy); struct wil6210_vif *vif = ndev_to_vif(dev); @@ -2398,7 +2398,7 @@ static int wil_cfg80211_probe_peer(struct wiphy *wiphy, return -ENOMEM; req->cid = cid; - req->cookie = *cookie; + req->cookie = cookie; mutex_lock(&vif->probe_client_mutex); list_add_tail(&req->list, &vif->probe_client_pending); diff --git a/drivers/net/wireless/ath/wil6210/debugfs.c b/drivers/net/wireless/ath/wil6210/debugfs.c index b8cb736a7185..e06e45323272 100644 --- a/drivers/net/wireless/ath/wil6210/debugfs.c +++ b/drivers/net/wireless/ath/wil6210/debugfs.c @@ -985,7 +985,7 @@ static ssize_t wil_write_file_txmgmt(struct file *file, const char __user *buf, params.buf = frame; params.len = len; - rc = wil_cfg80211_mgmt_tx(wiphy, wdev, ¶ms, NULL); + rc = wil_cfg80211_mgmt_tx(wiphy, wdev, ¶ms, 0); kfree(frame); wil_info(wil, "-> %d\n", rc); diff --git a/drivers/net/wireless/ath/wil6210/p2p.c b/drivers/net/wireless/ath/wil6210/p2p.c index 6d2cadca259f..acd8a2e35103 100644 --- a/drivers/net/wireless/ath/wil6210/p2p.c +++ b/drivers/net/wireless/ath/wil6210/p2p.c @@ -124,7 +124,7 @@ int wil_p2p_search(struct wil6210_vif *vif, int wil_p2p_listen(struct wil6210_priv *wil, struct wireless_dev *wdev, unsigned int duration, struct ieee80211_channel *chan, - u64 *cookie) + u64 cookie) { struct wil6210_vif *vif = wdev_to_vif(wil, wdev); struct wil_p2p_info *p2p = &vif->p2p; @@ -144,7 +144,7 @@ int wil_p2p_listen(struct wil6210_priv *wil, struct wireless_dev *wdev, } memcpy(&p2p->listen_chan, chan, sizeof(*chan)); - p2p->cookie = *cookie; + p2p->cookie = cookie; p2p->listen_duration = duration; mutex_lock(&wil->vif_mutex); @@ -166,7 +166,7 @@ int wil_p2p_listen(struct wil6210_priv *wil, struct wireless_dev *wdev, if (vif->mid == 0) wil->radio_wdev = wdev; - cfg80211_ready_on_channel(wdev, *cookie, chan, duration, + cfg80211_ready_on_channel(wdev, cookie, chan, duration, GFP_KERNEL); out: diff --git a/drivers/net/wireless/ath/wil6210/wil6210.h b/drivers/net/wireless/ath/wil6210/wil6210.h index 31e107c81e2d..f8e60be631ca 100644 --- a/drivers/net/wireless/ath/wil6210/wil6210.h +++ b/drivers/net/wireless/ath/wil6210/wil6210.h @@ -1301,7 +1301,7 @@ int wil_p2p_search(struct wil6210_vif *vif, struct cfg80211_scan_request *request); int wil_p2p_listen(struct wil6210_priv *wil, struct wireless_dev *wdev, unsigned int duration, struct ieee80211_channel *chan, - u64 *cookie); + u64 cookie); u8 wil_p2p_stop_discovery(struct wil6210_vif *vif); int wil_p2p_cancel_listen(struct wil6210_vif *vif, u64 cookie); void wil_p2p_listen_expired(struct work_struct *work); @@ -1317,7 +1317,7 @@ int wmi_stop_discovery(struct wil6210_vif *vif); int wil_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, struct cfg80211_mgmt_tx_params *params, - u64 *cookie); + u64 cookie); void wil_cfg80211_ap_recovery(struct wil6210_priv *wil); int wil_cfg80211_iface_combinations_from_fw( struct wil6210_priv *wil, diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 03a7a4c18279..872c48806d09 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -5554,7 +5554,7 @@ brcmf_cfg80211_update_mgmt_frame_registrations(struct wiphy *wiphy, int brcmf_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, - struct cfg80211_mgmt_tx_params *params, u64 *cookie) + struct cfg80211_mgmt_tx_params *params, u64 cookie) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct ieee80211_channel *chan = params->chan; @@ -5603,7 +5603,7 @@ brcmf_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, BRCMF_VNDR_IE_PRBRSP_FLAG, &buf[ie_offset], ie_len); - cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, true, + cfg80211_mgmt_tx_status(wdev, cookie, buf, len, true, GFP_KERNEL); } else if (ieee80211_is_action(mgmt->frame_control)) { if (len > BRCMF_FIL_ACTION_FRAME_SIZE + DOT11_MGMT_HDR_LEN) { @@ -5619,7 +5619,7 @@ brcmf_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, } action_frame = &af_params->action_frame; /* Add the packet Id */ - action_frame->packet_id = cpu_to_le32(*cookie); + action_frame->packet_id = cpu_to_le32(cookie); /* Add BSSID */ memcpy(&action_frame->da[0], &mgmt->da[0], ETH_ALEN); memcpy(&af_params->bssid[0], &mgmt->bssid[0], ETH_ALEN); @@ -5647,12 +5647,12 @@ brcmf_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, le16_to_cpu(action_frame->len)); brcmf_dbg(TRACE, "Action frame, cookie=%lld, len=%d, channel=%d\n", - *cookie, le16_to_cpu(action_frame->len), + cookie, le16_to_cpu(action_frame->len), le32_to_cpu(af_params->channel)); ack = brcmf_p2p_send_action_frame(vif->ifp, af_params); - cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, ack, + cfg80211_mgmt_tx_status(wdev, cookie, buf, len, ack, GFP_KERNEL); free: kfree(af_params); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h index 6ceb30142905..63e534523f51 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h @@ -497,6 +497,6 @@ void brcmf_cfg80211_free_vif(struct net_device *ndev); int brcmf_set_wsec(struct brcmf_if *ifp, const u8 *key, u16 key_len, u16 flags); int brcmf_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, - struct cfg80211_mgmt_tx_params *params, u64 *cookie); + struct cfg80211_mgmt_tx_params *params, u64 cookie); #endif /* BRCMFMAC_CFG80211_H */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c index 5c7a475cb5e5..545eb9aae966 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c @@ -100,7 +100,7 @@ static int brcmf_cyw_activate_events(struct brcmf_if *ifp) static int brcmf_cyw_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, - struct cfg80211_mgmt_tx_params *params, u64 *cookie) + struct cfg80211_mgmt_tx_params *params, u64 cookie) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct ieee80211_channel *chan = params->chan; @@ -154,7 +154,7 @@ int brcmf_cyw_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, memcpy(&mf_params->da[0], &mgmt->da[0], ETH_ALEN); memcpy(&mf_params->bssid[0], &mgmt->bssid[0], ETH_ALEN); - mf_params->packet_id = cpu_to_le32(*cookie); + mf_params->packet_id = cpu_to_le32(cookie); memcpy(mf_params->data, &buf[DOT11_MGMT_HDR_LEN], le16_to_cpu(mf_params->len)); @@ -186,8 +186,7 @@ int brcmf_cyw_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, } tx_status: - cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, ack, - GFP_KERNEL); + cfg80211_mgmt_tx_status(wdev, cookie, buf, len, ack, GFP_KERNEL); free: kfree(mf_params); return err; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c index 3d7bf25f1985..32a015d6b769 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c @@ -998,7 +998,7 @@ brcmf_p2p_discover_listen(struct brcmf_p2p_info *p2p, u16 channel, u32 duration) */ int brcmf_p2p_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *channel, - unsigned int duration, u64 *cookie, + unsigned int duration, u64 cookie, const u8 *rx_addr) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); @@ -1018,8 +1018,8 @@ int brcmf_p2p_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, goto exit; memcpy(&p2p->remain_on_channel, channel, sizeof(*channel)); - p2p->remain_on_channel_cookie = *cookie; - cfg80211_ready_on_channel(wdev, *cookie, channel, duration, GFP_KERNEL); + p2p->remain_on_channel_cookie = cookie; + cfg80211_ready_on_channel(wdev, cookie, channel, duration, GFP_KERNEL); exit: return err; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.h index 9f3f01ade2b7..707800949170 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.h @@ -157,7 +157,7 @@ int brcmf_p2p_scan_prep(struct wiphy *wiphy, struct brcmf_cfg80211_vif *vif); int brcmf_p2p_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *channel, - unsigned int duration, u64 *cookie, + unsigned int duration, u64 cookie, const u8 *rx_addr); int brcmf_p2p_notify_listen_complete(struct brcmf_if *ifp, const struct brcmf_event_msg *e, diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c index 102321cf8542..7a1ba32f1fb3 100644 --- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c +++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c @@ -196,7 +196,7 @@ mwifiex_form_mgmt_frame(struct sk_buff *skb, const u8 *buf, size_t len) */ static int mwifiex_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, - struct cfg80211_mgmt_tx_params *params, u64 *cookie) + struct cfg80211_mgmt_tx_params *params, u64 cookie) { const u8 *buf = params->buf; size_t len = params->len; @@ -263,9 +263,9 @@ mwifiex_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, if (ieee80211_is_action(mgmt->frame_control)) skb = mwifiex_clone_skb_for_tx_status(priv, skb, - MWIFIEX_BUF_FLAG_ACTION_TX_STATUS, cookie); + MWIFIEX_BUF_FLAG_ACTION_TX_STATUS, &cookie); else - cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, true, + cfg80211_mgmt_tx_status(wdev, cookie, buf, len, true, GFP_ATOMIC); mwifiex_queue_tx_pkt(priv, skb); @@ -303,13 +303,13 @@ static int mwifiex_cfg80211_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *chan, - unsigned int duration, u64 *cookie, + unsigned int duration, u64 cookie, const u8 *rx_addr) { struct mwifiex_private *priv = mwifiex_netdev_get_priv(wdev->netdev); int ret; - if (!chan || !cookie) { + if (!chan) { mwifiex_dbg(priv->adapter, ERROR, "Invalid parameter for ROC\n"); return -EINVAL; } @@ -325,14 +325,14 @@ mwifiex_cfg80211_remain_on_channel(struct wiphy *wiphy, duration); if (!ret) { - priv->roc_cfg.cookie = *cookie; + priv->roc_cfg.cookie = cookie; priv->roc_cfg.chan = *chan; - cfg80211_ready_on_channel(wdev, *cookie, chan, + cfg80211_ready_on_channel(wdev, cookie, chan, duration, GFP_ATOMIC); mwifiex_dbg(priv->adapter, INFO, - "info: ROC, cookie = 0x%llx\n", *cookie); + "info: ROC, cookie = 0x%llx\n", cookie); } return ret; @@ -4558,7 +4558,7 @@ mwifiex_cfg80211_disassociate(struct wiphy *wiphy, static int mwifiex_cfg80211_probe_peer(struct wiphy *wiphy, struct net_device *dev, const u8 *peer, - u64 *cookie) + u64 cookie) { /* hostapd looks for NL80211_CMD_PROBE_CLIENT support; otherwise, * it requires monitor-mode support (which mwifiex doesn't support). diff --git a/drivers/net/wireless/microchip/wilc1000/cfg80211.c b/drivers/net/wireless/microchip/wilc1000/cfg80211.c index 7c19ad34fda5..bb2748a19329 100644 --- a/drivers/net/wireless/microchip/wilc1000/cfg80211.c +++ b/drivers/net/wireless/microchip/wilc1000/cfg80211.c @@ -1100,7 +1100,7 @@ static void wilc_wfi_remain_on_channel_expired(struct wilc_vif *vif, u64 cookie) static int remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *chan, - unsigned int duration, u64 *cookie, + unsigned int duration, u64 cookie, const u8 *rx_addr) { int ret = 0; @@ -1112,7 +1112,7 @@ static int remain_on_channel(struct wiphy *wiphy, return ret; } - ret = wilc_remain_on_channel(vif, *cookie, chan->hw_value, + ret = wilc_remain_on_channel(vif, cookie, chan->hw_value, wilc_wfi_remain_on_channel_expired); if (ret) return ret; @@ -1120,11 +1120,11 @@ static int remain_on_channel(struct wiphy *wiphy, vif->wilc->op_ch = chan->hw_value; priv->remain_on_ch_params.listen_ch = chan; - priv->remain_on_ch_params.listen_cookie = *cookie; + priv->remain_on_ch_params.listen_cookie = cookie; priv->p2p_listen_state = true; priv->remain_on_ch_params.listen_duration = duration; - cfg80211_ready_on_channel(wdev, *cookie, chan, duration, GFP_KERNEL); + cfg80211_ready_on_channel(wdev, cookie, chan, duration, GFP_KERNEL); mod_timer(&vif->hif_drv->remain_on_ch_timer, jiffies + msecs_to_jiffies(duration + 1000)); @@ -1147,7 +1147,7 @@ static int cancel_remain_on_channel(struct wiphy *wiphy, static int mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, struct cfg80211_mgmt_tx_params *params, - u64 *cookie) + u64 cookie) { struct ieee80211_channel *chan = params->chan; unsigned int wait = params->wait; @@ -1164,7 +1164,7 @@ static int mgmt_tx(struct wiphy *wiphy, const u8 *vendor_ie; int ret = 0; - priv->tx_cookie = *cookie; + priv->tx_cookie = cookie; mgmt = (const struct ieee80211_mgmt *)buf; if (!ieee80211_is_mgmt(mgmt->frame_control)) diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c index 198174ac754e..c820f08d2835 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -150,7 +150,7 @@ nxpwifi_form_mgmt_frame(struct sk_buff *skb, const u8 *buf, size_t len) /* cfg80211 operation handler to transmit a management frame. */ static int nxpwifi_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, - struct cfg80211_mgmt_tx_params *params, u64 *cookie) + struct cfg80211_mgmt_tx_params *params, u64 cookie) { const u8 *buf = params->buf; size_t len = params->len; @@ -216,9 +216,9 @@ nxpwifi_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, if (ieee80211_is_action(mgmt->frame_control)) skb = nxpwifi_clone_skb_for_tx_status(priv, skb, - NXPWIFI_BUF_FLAG_ACTION_TX_STATUS, cookie); + NXPWIFI_BUF_FLAG_ACTION_TX_STATUS, &cookie); else - cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, true, + cfg80211_mgmt_tx_status(wdev, cookie, buf, len, true, GFP_ATOMIC); nxpwifi_queue_tx_pkt(priv, skb); @@ -253,14 +253,14 @@ static int nxpwifi_cfg80211_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *chan, - unsigned int duration, u64 *cookie, + unsigned int duration, u64 cookie, const u8 *rx_addr) { struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); struct nxpwifi_adapter *adapter = priv->adapter; int ret; - if (!chan || !cookie) { + if (!chan) { nxpwifi_dbg(adapter, ERROR, "Invalid parameter for ROC\n"); return -EINVAL; } @@ -276,14 +276,14 @@ nxpwifi_cfg80211_remain_on_channel(struct wiphy *wiphy, duration); if (!ret) { - priv->roc_cfg.cookie = *cookie; + priv->roc_cfg.cookie = cookie; priv->roc_cfg.chan = *chan; - cfg80211_ready_on_channel(wdev, *cookie, chan, + cfg80211_ready_on_channel(wdev, cookie, chan, duration, GFP_ATOMIC); nxpwifi_dbg(adapter, INFO, - "info: ROC, cookie = 0x%llx\n", *cookie); + "info: ROC, cookie = 0x%llx\n", cookie); } return ret; @@ -3616,7 +3616,7 @@ nxpwifi_cfg80211_disassociate(struct wiphy *wiphy, static int nxpwifi_cfg80211_probe_peer(struct wiphy *wiphy, struct net_device *dev, const u8 *peer, - u64 *cookie) + u64 cookie) { /* * hostapd looks for NL80211_CMD_PROBE_CLIENT support; otherwise, diff --git a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c index 284bc61c46d0..45e2b7ae9633 100644 --- a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c +++ b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c @@ -446,7 +446,7 @@ qtnf_update_mgmt_frame_registrations(struct wiphy *wiphy, static int qtnf_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, - struct cfg80211_mgmt_tx_params *params, u64 *cookie) + struct cfg80211_mgmt_tx_params *params, u64 cookie) { struct qtnf_vif *vif = qtnf_netdev_get_priv(wdev->netdev); const struct ieee80211_mgmt *mgmt_frame = (void *)params->buf; diff --git a/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c b/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c index 0e6b40336123..b58dda129ff6 100644 --- a/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c +++ b/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c @@ -2530,7 +2530,7 @@ static int _cfg80211_rtw_mgmt_tx(struct adapter *padapter, u8 tx_ch, const u8 *b static int cfg80211_rtw_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, struct cfg80211_mgmt_tx_params *params, - u64 *cookie) + u64 cookie) { struct net_device *ndev = wdev_to_ndev(wdev); struct ieee80211_channel *chan = params->chan; @@ -2553,7 +2553,7 @@ static int cfg80211_rtw_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, padapter = rtw_netdev_priv(ndev); /* indicate ack before issue frame to avoid racing with rsp frame */ - rtw_cfg80211_mgmt_tx_status(padapter, *cookie, buf, len, ack, GFP_KERNEL); + rtw_cfg80211_mgmt_tx_status(padapter, cookie, buf, len, ack, GFP_KERNEL); if (!rtw_action_frame_parse(buf, len, &category, &action)) goto exit; diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index ee395e3a021a..74e267f72c19 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -5458,14 +5458,14 @@ struct cfg80211_ops { struct wireless_dev *wdev, struct ieee80211_channel *chan, unsigned int duration, - u64 *cookie, const u8 *rx_addr); + u64 cookie, const u8 *rx_addr); int (*cancel_remain_on_channel)(struct wiphy *wiphy, struct wireless_dev *wdev, u64 cookie); int (*mgmt_tx)(struct wiphy *wiphy, struct wireless_dev *wdev, struct cfg80211_mgmt_tx_params *params, - u64 *cookie); + u64 cookie); int (*mgmt_tx_cancel_wait)(struct wiphy *wiphy, struct wireless_dev *wdev, u64 cookie); @@ -5512,7 +5512,7 @@ struct cfg80211_ops { const u8 *peer, enum nl80211_tdls_operation oper); int (*probe_peer)(struct wiphy *wiphy, struct net_device *dev, - const u8 *peer, u64 *cookie); + const u8 *peer, u64 cookie); int (*set_noack_map)(struct wiphy *wiphy, struct net_device *dev, diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index b6d02d2b28f5..0c2c7afd59c4 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -4940,7 +4940,7 @@ static int ieee80211_set_rekey_data(struct wiphy *wiphy, } static int ieee80211_probe_peer(struct wiphy *wiphy, struct net_device *dev, - const u8 *peer, u64 *cookie) + const u8 *peer, u64 cookie) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; @@ -5047,7 +5047,7 @@ static int ieee80211_probe_peer(struct wiphy *wiphy, struct net_device *dev, if (qos) nullfunc->qos_ctrl = cpu_to_le16(7); - ret = ieee80211_attach_ack_skb(local, skb, cookie, GFP_ATOMIC); + ret = ieee80211_attach_ack_skb(local, skb, &cookie, GFP_ATOMIC); if (ret) { kfree_skb(skb); return ret; diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 3760319ab079..d74da4e9ebf3 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -2150,12 +2150,12 @@ void ieee80211_roc_purge(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata); int ieee80211_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *chan, - unsigned int duration, u64 *cookie, + unsigned int duration, u64 cookie, const u8 *rx_addr); int ieee80211_cancel_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, u64 cookie); int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, - struct cfg80211_mgmt_tx_params *params, u64 *cookie); + struct cfg80211_mgmt_tx_params *params, u64 cookie); int ieee80211_mgmt_tx_cancel_wait(struct wiphy *wiphy, struct wireless_dev *wdev, u64 cookie); diff --git a/net/mac80211/offchannel.c b/net/mac80211/offchannel.c index 94be6497f259..7acef80d5f1f 100644 --- a/net/mac80211/offchannel.c +++ b/net/mac80211/offchannel.c @@ -702,7 +702,7 @@ static int ieee80211_start_roc_work(struct ieee80211_local *local, int ieee80211_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *chan, - unsigned int duration, u64 *cookie, + unsigned int duration, u64 cookie, const u8 *rx_addr) { struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); @@ -711,7 +711,7 @@ int ieee80211_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, lockdep_assert_wiphy(local->hw.wiphy); return ieee80211_start_roc_work(local, sdata, chan, - duration, cookie, NULL, + duration, &cookie, NULL, IEEE80211_ROC_TYPE_NORMAL); } @@ -808,7 +808,7 @@ int ieee80211_cancel_remain_on_channel(struct wiphy *wiphy, } int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, - struct cfg80211_mgmt_tx_params *params, u64 *cookie) + struct cfg80211_mgmt_tx_params *params, u64 cookie) { struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); struct ieee80211_local *local = sdata->local; @@ -1014,7 +1014,7 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, /* make a copy to preserve the frame contents * in case of encryption. */ - ret = ieee80211_attach_ack_skb(local, skb, cookie, GFP_KERNEL); + ret = ieee80211_attach_ack_skb(local, skb, &cookie, GFP_KERNEL); if (ret) { kfree_skb(skb); goto out_unlock; @@ -1035,7 +1035,7 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, /* This will handle all kinds of coalescing and immediate TX */ ret = ieee80211_start_roc_work(local, sdata, params->chan, - params->wait, cookie, skb, + params->wait, &cookie, skb, IEEE80211_ROC_TYPE_MGMT_TX); if (ret) ieee80211_free_txskb(&local->hw, skb); diff --git a/net/wireless/core.h b/net/wireless/core.h index 15d9f7eb58b4..b4610f6685dc 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -402,7 +402,7 @@ void cfg80211_mlme_purge_registrations(struct wireless_dev *wdev); int cfg80211_mlme_mgmt_tx(struct cfg80211_registered_device *rdev, struct wireless_dev *wdev, struct cfg80211_mgmt_tx_params *params, - u64 *cookie); + u64 cookie); void cfg80211_oper_and_ht_capa(struct ieee80211_ht_cap *ht_capa, const struct ieee80211_ht_cap *ht_capa_mask); void cfg80211_oper_and_vht_capa(struct ieee80211_vht_cap *vht_capa, diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c index 7824b7ac2770..a0d1cde26f0c 100644 --- a/net/wireless/mlme.c +++ b/net/wireless/mlme.c @@ -894,7 +894,7 @@ static bool cfg80211_allowed_random_address(struct wireless_dev *wdev, int cfg80211_mlme_mgmt_tx(struct cfg80211_registered_device *rdev, struct wireless_dev *wdev, - struct cfg80211_mgmt_tx_params *params, u64 *cookie) + struct cfg80211_mgmt_tx_params *params, u64 cookie) { const struct ieee80211_mgmt *mgmt; u16 stype; diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 6ad1f9cf2dda..8a46101bfdd0 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -14644,7 +14644,7 @@ static int nl80211_remain_on_channel(struct sk_buff *skb, cookie = cfg80211_assign_cookie(rdev); err = rdev_remain_on_channel(rdev, wdev, chandef.chan, - duration, &cookie, rx_addr); + duration, cookie, rx_addr); if (err) goto free_msg; @@ -14885,7 +14885,7 @@ static int nl80211_tx_mgmt(struct sk_buff *skb, struct genl_info *info) params.chan = chandef.chan; cookie = cfg80211_assign_cookie(rdev); - err = cfg80211_mlme_mgmt_tx(rdev, wdev, ¶ms, &cookie); + err = cfg80211_mlme_mgmt_tx(rdev, wdev, ¶ms, cookie); if (err) goto free_msg; @@ -16374,7 +16374,7 @@ static int nl80211_probe_peer(struct sk_buff *skb, struct genl_info *info) } cookie = cfg80211_assign_cookie(rdev); - err = rdev_probe_peer(rdev, dev, addr, &cookie); + err = rdev_probe_peer(rdev, dev, addr, cookie); if (err) goto free_msg; diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h index 6c3bad8b2d6f..c46e97c90fdc 100644 --- a/net/wireless/rdev-ops.h +++ b/net/wireless/rdev-ops.h @@ -736,14 +736,14 @@ static inline int rdev_remain_on_channel(struct cfg80211_registered_device *rdev, struct wireless_dev *wdev, struct ieee80211_channel *chan, - unsigned int duration, u64 *cookie, const u8 *rx_addr) + unsigned int duration, u64 cookie, const u8 *rx_addr) { int ret; trace_rdev_remain_on_channel(&rdev->wiphy, wdev, chan, duration, rx_addr); ret = rdev->ops->remain_on_channel(&rdev->wiphy, wdev, chan, duration, cookie, rx_addr); - trace_rdev_return_int_cookie(&rdev->wiphy, ret, *cookie); + trace_rdev_return_int_cookie(&rdev->wiphy, ret, cookie); return ret; } @@ -761,12 +761,12 @@ rdev_cancel_remain_on_channel(struct cfg80211_registered_device *rdev, static inline int rdev_mgmt_tx(struct cfg80211_registered_device *rdev, struct wireless_dev *wdev, struct cfg80211_mgmt_tx_params *params, - u64 *cookie) + u64 cookie) { int ret; trace_rdev_mgmt_tx(&rdev->wiphy, wdev, params); ret = rdev->ops->mgmt_tx(&rdev->wiphy, wdev, params, cookie); - trace_rdev_return_int_cookie(&rdev->wiphy, ret, *cookie); + trace_rdev_return_int_cookie(&rdev->wiphy, ret, cookie); return ret; } @@ -950,12 +950,12 @@ static inline int rdev_tdls_oper(struct cfg80211_registered_device *rdev, static inline int rdev_probe_peer(struct cfg80211_registered_device *rdev, struct net_device *dev, const u8 *peer, - u64 *cookie) + u64 cookie) { int ret; trace_rdev_probe_peer(&rdev->wiphy, dev, peer); ret = rdev->ops->probe_peer(&rdev->wiphy, dev, peer, cookie); - trace_rdev_return_int_cookie(&rdev->wiphy, ret, *cookie); + trace_rdev_return_int_cookie(&rdev->wiphy, ret, cookie); return ret; } From 51ba92052ebd84c36f93d6a17e779b5d024c8732 Mon Sep 17 00:00:00 2001 From: Arend van Spriel Date: Fri, 31 Jul 2026 14:35:08 +0200 Subject: [PATCH 0897/1433] wifi: cfg80211: convert tx_control_port cookie to input parameter The tx_control_port op was excluded from the previous commit because a NULL cookie was affecting different behavior, ie. signalling that no TX status is wanted. Since cfg80211_assign_cookie() guarantees a non-zero value, cookie value 0 can be used instead. So pass 0 when dont_wait_for_ack is set, otherwise pass value returned from cfg80211_assign_cookie() call. Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Arend van Spriel Link: https://patch.msgid.link/20260731123509.1975281-13-arend.vanspriel@broadcom.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 6 +++--- net/mac80211/ieee80211_i.h | 4 ++-- net/mac80211/tdls.c | 2 +- net/mac80211/tx.c | 27 +++++++++++++-------------- net/wireless/nl80211.c | 7 +++---- net/wireless/rdev-ops.h | 4 ++-- 6 files changed, 24 insertions(+), 26 deletions(-) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 74e267f72c19..97c16d4ff127 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -5220,8 +5220,8 @@ struct mgmt_frame_regs { * user space * * @tx_control_port: TX a control port frame (EAPoL). The noencrypt parameter - * tells the driver that the frame should not be encrypted. When @cookie - * is non-NULL it is pre-assigned by cfg80211; drivers must not modify it. + * tells the driver that the frame should not be encrypted. A @cookie + * value of 0 means the caller does not want TX status reporting. * * @get_ftm_responder_stats: Retrieve FTM responder statistics, if available. * Statistics should be cumulative, currently no way to reset is provided. @@ -5610,7 +5610,7 @@ struct cfg80211_ops { const u8 *buf, size_t len, const u8 *dest, const __be16 proto, const bool noencrypt, int link_id, - u64 *cookie); + u64 cookie); int (*get_ftm_responder_stats)(struct wiphy *wiphy, struct net_device *dev, diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index d74da4e9ebf3..5761e9621491 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -2234,7 +2234,7 @@ void __ieee80211_subif_start_xmit(struct sk_buff *skb, struct net_device *dev, u32 info_flags, u32 ctrl_flags, - u64 *cookie); + u64 cookie); struct sk_buff * ieee80211_build_data_template(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, u32 info_flags); @@ -2248,7 +2248,7 @@ void ieee80211_clear_fast_xmit(struct sta_info *sta); int ieee80211_tx_control_port(struct wiphy *wiphy, struct net_device *dev, const u8 *buf, size_t len, const u8 *dest, __be16 proto, bool unencrypted, - int link_id, u64 *cookie); + int link_id, u64 cookie); int ieee80211_probe_mesh_link(struct wiphy *wiphy, struct net_device *dev, const u8 *buf, size_t len); void __ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata, diff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c index ffd575a8d188..dc2f662fe4c4 100644 --- a/net/mac80211/tdls.c +++ b/net/mac80211/tdls.c @@ -1121,7 +1121,7 @@ ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev, /* disable bottom halves when entering the Tx path */ local_bh_disable(); __ieee80211_subif_start_xmit(skb, dev, flags, - IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, NULL); + IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, 0); local_bh_enable(); return ret; diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index b62a41bda49d..1ee2b1cfc756 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2580,7 +2580,7 @@ int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata, static u16 ieee80211_store_ack_skb(struct ieee80211_local *local, struct sk_buff *skb, u32 *info_flags, - u64 *cookie) + u64 cookie) { struct sk_buff *ack_skb; u16 info_id = 0; @@ -2603,7 +2603,7 @@ static u16 ieee80211_store_ack_skb(struct ieee80211_local *local, info_id = id; *info_flags |= IEEE80211_TX_CTL_REQ_TX_STATUS; if (cookie) - IEEE80211_SKB_CB(ack_skb)->ack.cookie = *cookie; + IEEE80211_SKB_CB(ack_skb)->ack.cookie = cookie; } else { kfree_skb(ack_skb); } @@ -2648,7 +2648,7 @@ static void ieee80211_remove_ack_skb(struct ieee80211_local *local, u16 info_id) static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, u32 info_flags, struct sta_info *sta, u32 ctrl_flags, - u64 *cookie) + u64 cookie) { struct ieee80211_local *local = sdata->local; struct ieee80211_tx_info *info; @@ -4369,7 +4369,7 @@ void __ieee80211_subif_start_xmit(struct sk_buff *skb, struct net_device *dev, u32 info_flags, u32 ctrl_flags, - u64 *cookie) + u64 cookie) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; @@ -4564,7 +4564,7 @@ static void ieee80211_mlo_multicast_tx_one(struct ieee80211_sub_if_data *sdata, return; ctrl_flags |= u32_encode_bits(link_id, IEEE80211_TX_CTRL_MLO_LINK); - __ieee80211_subif_start_xmit(out, sdata->dev, 0, ctrl_flags, NULL); + __ieee80211_subif_start_xmit(out, sdata->dev, 0, ctrl_flags, 0); } static void ieee80211_mlo_multicast_tx(struct net_device *dev, @@ -4579,8 +4579,7 @@ static void ieee80211_mlo_multicast_tx(struct net_device *dev, ctrl_flags |= u32_encode_bits(__ffs(links), IEEE80211_TX_CTRL_MLO_LINK); - __ieee80211_subif_start_xmit(skb, sdata->dev, 0, ctrl_flags, - NULL); + __ieee80211_subif_start_xmit(skb, sdata->dev, 0, ctrl_flags, 0); return; } @@ -4622,7 +4621,7 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, while ((skb = __skb_dequeue(&queue))) __ieee80211_subif_start_xmit(skb, dev, 0, IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, - NULL); + 0); } else if (ieee80211_vif_is_mld(&sdata->vif) && ((sdata->vif.type == NL80211_IFTYPE_AP && !ieee80211_hw_check(&sdata->local->hw, MLO_MCAST_MULTI_LINK_TX)) || @@ -4633,7 +4632,7 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, normal: __ieee80211_subif_start_xmit(skb, dev, 0, IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, - NULL); + 0); } return NETDEV_TX_OK; @@ -4732,7 +4731,7 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata, /* fall back to non-offload slow path */ __ieee80211_subif_start_xmit(skb, dev, 0, IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, - NULL); + 0); return; } @@ -4768,7 +4767,7 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata, if (unlikely(sk_requests_wifi_status(skb->sk))) { info->status_data = ieee80211_store_ack_skb(local, skb, - &info->flags, NULL); + &info->flags, 0); if (info->status_data) info->status_data_idr = 1; } @@ -4911,7 +4910,7 @@ ieee80211_build_data_template(struct ieee80211_sub_if_data *sdata, } skb = ieee80211_build_hdr(sdata, skb, info_flags, sta, - IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, NULL); + IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, 0); if (IS_ERR(skb)) goto out; @@ -6526,7 +6525,7 @@ void ieee80211_tx_skb_tid(struct ieee80211_sub_if_data *sdata, int ieee80211_tx_control_port(struct wiphy *wiphy, struct net_device *dev, const u8 *buf, size_t len, const u8 *dest, __be16 proto, bool unencrypted, - int link_id, u64 *cookie) + int link_id, u64 cookie) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; @@ -6661,7 +6660,7 @@ int ieee80211_probe_mesh_link(struct wiphy *wiphy, struct net_device *dev, local_bh_disable(); __ieee80211_subif_start_xmit(skb, skb->dev, 0, IEEE80211_TX_CTRL_SKIP_MPATH_LOOKUP, - NULL); + 0); local_bh_enable(); return 0; diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 8a46101bfdd0..d12bef11b94f 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -18589,12 +18589,11 @@ static int nl80211_tx_control_port(struct sk_buff *skb, struct genl_info *info) link_id = nl80211_link_id_or_invalid(info->attrs); - if (!dont_wait_for_ack) - cookie = cfg80211_assign_cookie(rdev); + cookie = dont_wait_for_ack ? 0 : cfg80211_assign_cookie(rdev); err = rdev_tx_control_port(rdev, dev, buf, len, dest, cpu_to_be16(proto), noencrypt, link_id, - dont_wait_for_ack ? NULL : &cookie); - if (!err && !dont_wait_for_ack) + cookie); + if (!err && cookie) nl_set_extack_cookie_u64(info->extack, cookie); return err; } diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h index c46e97c90fdc..46849fe8d0b3 100644 --- a/net/wireless/rdev-ops.h +++ b/net/wireless/rdev-ops.h @@ -775,7 +775,7 @@ static inline int rdev_tx_control_port(struct cfg80211_registered_device *rdev, const void *buf, size_t len, const u8 *dest, __be16 proto, const bool noencrypt, int link, - u64 *cookie) + u64 cookie) { int ret; trace_rdev_tx_control_port(&rdev->wiphy, dev, buf, len, @@ -783,7 +783,7 @@ static inline int rdev_tx_control_port(struct cfg80211_registered_device *rdev, ret = rdev->ops->tx_control_port(&rdev->wiphy, dev, buf, len, dest, proto, noencrypt, link, cookie); if (cookie) - trace_rdev_return_int_cookie(&rdev->wiphy, ret, *cookie); + trace_rdev_return_int_cookie(&rdev->wiphy, ret, cookie); else trace_rdev_return_int(&rdev->wiphy, ret); return ret; From 69ab1d978efba9a242c7c7a92ccf1b643f137630 Mon Sep 17 00:00:00 2001 From: Arend van Spriel Date: Fri, 31 Jul 2026 14:35:09 +0200 Subject: [PATCH 0898/1433] wifi: nl80211: send frame tx status event only for non-zero cookie The cookie value assigned by cfg80211_assign_cookie() is guaranteed to be non-zero. So the zero cookie value has special use in tx_control_port where userspace can indicate dont_wait_for_ack, ie. not interested in status. The wil6210 driver also uses the zero cookie when wil_cfg80211_mgmt_tx() is invoked from debugfs api the driver provides so the event is also redundant in that scenario. Signed-off-by: Arend van Spriel Link: https://patch.msgid.link/20260731123509.1975281-14-arend.vanspriel@broadcom.com Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index d12bef11b94f..ac895e02cd41 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -22046,6 +22046,10 @@ static void nl80211_frame_tx_status(struct wireless_dev *wdev, struct sk_buff *msg; void *hdr; + /* userspace not interested in zero-cookie status */ + if (!status->cookie) + return; + if (command == NL80211_CMD_FRAME_TX_STATUS) trace_cfg80211_mgmt_tx_status(wdev, status->cookie, status->ack); From cc3e7fdbf7c0fe8a78195e53d322126546b6212b Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Wed, 29 Jul 2026 11:37:15 -0700 Subject: [PATCH 0899/1433] wifi: nxpwifi: embed rx_reorder_ptr rx_reorder_ptr is a dynamically allocated array which is done near the main struct allocation. Combine the two to avoid freeing separately. Also fix the type to what it actually is. void is normally used to avoid casting but there's no need here. Signed-off-by: Rosen Penev Tested-by: Jeff Chen Reviewed-by: Jeff Chen Link: https://patch.msgid.link/20260729183715.691287-1-rosenp@gmail.com Signed-off-by: Johannes Berg --- .../net/wireless/nxp/nxpwifi/11n_rxreorder.c | 23 +++++-------------- drivers/net/wireless/nxp/nxpwifi/main.h | 2 +- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c index c5819f89b08c..65b628411543 100644 --- a/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c +++ b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c @@ -171,7 +171,6 @@ nxpwifi_del_rx_reorder_entry(struct nxpwifi_private *priv, list_del_rcu(&tbl->list); spin_unlock_bh(&priv->rx_reorder_tbl_lock[tid]); - kfree(tbl->rx_reorder_ptr); kfree_rcu(tbl, rcu); atomic_set(&priv->adapter->rx_ba_teardown_pending, 0); @@ -262,7 +261,6 @@ static void nxpwifi_11n_create_rx_reorder_tbl(struct nxpwifi_private *priv, u8 *ta, int tid, int win_size, int seq_num) { - int i; struct nxpwifi_rx_reorder_tbl *tbl, *new_node; u16 last_seq = 0; struct nxpwifi_sta_node *node; @@ -273,11 +271,16 @@ nxpwifi_11n_create_rx_reorder_tbl(struct nxpwifi_private *priv, u8 *ta, nxpwifi_11n_dispatch_pkt_until_start_win(priv, tbl, seq_num); return; } + + if (win_size <= 0) + return; + /* if !tbl then create one */ - new_node = kzalloc_obj(*new_node, GFP_KERNEL); + new_node = kzalloc_flex(*new_node, rx_reorder_ptr, win_size); if (!new_node) return; + new_node->win_size = win_size; INIT_LIST_HEAD(&new_node->list); new_node->tid = tid; memcpy(new_node->ta, ta, ETH_ALEN); @@ -311,26 +314,12 @@ nxpwifi_11n_create_rx_reorder_tbl(struct nxpwifi_private *priv, u8 *ta, new_node->flags |= RXREOR_INIT_WINDOW_SHIFT; } - new_node->win_size = win_size; - - new_node->rx_reorder_ptr = kcalloc(win_size, sizeof(void *), - GFP_KERNEL); - if (!new_node->rx_reorder_ptr) { - kfree(new_node); - nxpwifi_dbg(priv->adapter, ERROR, - "%s: failed to alloc reorder_ptr\n", __func__); - return; - } - new_node->timer_context.ptr = new_node; new_node->timer_context.priv = priv; new_node->timer_context.timer_is_set = false; timer_setup(&new_node->timer_context.timer, nxpwifi_flush_data, 0); - for (i = 0; i < win_size; ++i) - new_node->rx_reorder_ptr[i] = NULL; - spin_lock_bh(&priv->rx_reorder_tbl_lock[tid]); list_add_tail_rcu(&new_node->list, &priv->rx_reorder_tbl_ptr[tid]); spin_unlock_bh(&priv->rx_reorder_tbl_lock[tid]); diff --git a/drivers/net/wireless/nxp/nxpwifi/main.h b/drivers/net/wireless/nxp/nxpwifi/main.h index 4abf80771be2..349dfa4d3f85 100644 --- a/drivers/net/wireless/nxp/nxpwifi/main.h +++ b/drivers/net/wireless/nxp/nxpwifi/main.h @@ -656,10 +656,10 @@ struct nxpwifi_rx_reorder_tbl { int init_win; int start_win; int win_size; - void **rx_reorder_ptr; struct reorder_tmr_cnxt timer_context; u8 amsdu; u8 flags; + struct sk_buff *rx_reorder_ptr[] __counted_by(win_size); }; struct nxpwifi_bss_prio_node { From a28fcce6ee74be8a4526e6cfa16dc7786d62a784 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Thu, 30 Jul 2026 01:36:07 +0800 Subject: [PATCH 0900/1433] wifi: mac80211: send TWT teardown to peer after setup TX failure When an AP's TWT Setup response is not acknowledged, ieee80211_s1g_tx_twt_setup_fail() asks the driver to tear down the local agreement and sends a TWT teardown action as the peer notification. It uses the response SA as the destination, but ieee80211_s1g_send_twt_setup() built that response with SA set to the AP's address. The teardown is therefore queued with DA, SA and BSSID all set to the AP address and never reaches the station. The in-tree driver callbacks update local hardware state and emit no action frame. The station receives no notification that mac80211 asked the driver to remove the agreement and can keep following the TWT schedule, leaving the peers' power-save state desynchronized. Address the teardown to the response DA, the station to which the failed response was sent. This also matches the station lookup the transmit status path already performs on the same frame. Fixes: f5a4c24e689f ("mac80211: introduce individual TWT support in AP mode") Assisted-by: Codex:gpt-5.6-sol Assisted-by: Kimi:K3 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260729173607.13340-1-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/s1g.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/mac80211/s1g.c b/net/mac80211/s1g.c index 5af4a0c6c642..825fcf3f909b 100644 --- a/net/mac80211/s1g.c +++ b/net/mac80211/s1g.c @@ -143,7 +143,7 @@ ieee80211_s1g_tx_twt_setup_fail(struct ieee80211_sub_if_data *sdata, drv_twt_teardown_request(sdata->local, sdata, &sta->sta, flowid); - ieee80211_s1g_send_twt_teardown(sdata, mgmt->sa, sdata->vif.addr, + ieee80211_s1g_send_twt_teardown(sdata, mgmt->da, sdata->vif.addr, flowid); } From 927ee844c47ac2aef22c8f7a35f098ff576b398b Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 31 Jul 2026 12:02:44 +0800 Subject: [PATCH 0901/1433] wifi: nl80211: clean up color-change beacon data on errors nl80211_color_change() calls nl80211_parse_beacon() for the beacon_next template, which can allocate params.beacon_next.mbssid_ies and .rnr_ies. A parsing failure returned directly instead of using the out: cleanup, leaking any allocations completed before the error. Allocate the nested attribute table before parsing beacon_next. Its allocation failure can then return before beacon data exists, while a later parsing failure uses out: to release the parsed data. Fixes: dc1e3cb8da8b ("nl80211: MBSSID and EMA support in AP mode") Assisted-by: Codex:gpt-5 Assisted-by: Claude:opus-4.8 Assisted-by: Kimi:K3 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260731120244.82628-1-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index ac895e02cd41..44f2bad08670 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -18928,15 +18928,15 @@ static int nl80211_color_change(struct sk_buff *skb, struct genl_info *info) if (!wdev->links[params.link_id].ap.beacon_interval) return -EINVAL; + tb = kzalloc_objs(*tb, NL80211_ATTR_MAX + 1); + if (!tb) + return -ENOMEM; + err = nl80211_parse_beacon(rdev, info->attrs, ¶ms.beacon_next, wdev->links[params.link_id].ap.chandef.chan, info->extack); if (err) - return err; - - tb = kzalloc_objs(*tb, NL80211_ATTR_MAX + 1); - if (!tb) - return -ENOMEM; + goto out; err = nla_parse_nested(tb, NL80211_ATTR_MAX, info->attrs[NL80211_ATTR_COLOR_CHANGE_ELEMS], From 0e4532ec658606f76f62eb277e7a933919d36cbb Mon Sep 17 00:00:00 2001 From: Slawomir Stepien Date: Thu, 30 Jul 2026 08:52:31 +0200 Subject: [PATCH 0902/1433] wifi: zd1211rw: reject secondary interfaces to prevent conflicts The zd1211rw driver is designed for single-function Wi-Fi dongles and hardcodes its USB endpoints. When a malformed USB device exposes multiple interfaces that match the driver's device ID, the driver blindly binds to all of them. During probe(), the driver calls usb_reset_device(), which iterates over all interfaces and invokes the pre_reset() callback for each bound interface. Since multiple interfaces are bound to zd1211rw, pre_reset() is called sequentially for each instance, acquiring their respective &mac->chip.mutex. Because all instances initialize their mutexes with the same lock class, lockdep detects a task acquiring a lock of the same class it already holds and flags it as a possible recursive deadlock: WARNING: possible recursive locking detected kworker/0:1/11 is trying to acquire lock: ffff88810371dde0 (&chip->mutex){+.+.}-{4:4}, at: zd_chip_disable_rxtx+0x20/0x50 drivers/net/wireless/zydas/zd1211rw/zd_chip.c:1465 but task is already holding lock: ffff8881138ddde0 (&chip->mutex){+.+.}-{4:4}, at: pre_reset+0x28c/0x380 drivers/net/wireless/zydas/zd1211rw/zd_usb.c:1505 Fix this by explicitly rejecting secondary interfaces (bInterfaceNumber != 0) during probe(). This ensures that only a single instance of the driver binds to the device, eliminating the recursive locking scenario. Fixes: e85d0918b54f ("[PATCH] ZyDAS ZD1211 USB-WLAN driver") Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+0ec3d1a6cf1fbe79c153@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=0ec3d1a6cf1fbe79c153 Link: https://syzkaller.appspot.com/ai_job?id=00724ef7-fd77-4cde-9779-895b8f63c2f6 Signed-off-by: Slawomir Stepien Link: https://patch.msgid.link/20260730065231.1644030-1-sst@poczta.fm Signed-off-by: Johannes Berg --- drivers/net/wireless/zydas/zd1211rw/zd_usb.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/wireless/zydas/zd1211rw/zd_usb.c b/drivers/net/wireless/zydas/zd1211rw/zd_usb.c index 966d8ccb0dbc..98102c663434 100644 --- a/drivers/net/wireless/zydas/zd1211rw/zd_usb.c +++ b/drivers/net/wireless/zydas/zd1211rw/zd_usb.c @@ -1353,6 +1353,14 @@ static int probe(struct usb_interface *intf, const struct usb_device_id *id) struct zd_usb *usb; struct ieee80211_hw *hw = NULL; + /* + * ZD1211 devices are single-function. Reject secondary interfaces + * to prevent multiple instances from conflicting on hardcoded endpoints + * and triggering recursive locking warnings. + */ + if (intf->cur_altsetting->desc.bInterfaceNumber != 0) + return -ENODEV; + print_id(udev); if (id->driver_info & DEVICE_INSTALLER) From fd2bf5e718108c00732eb07fd94a5d8830f62a9f Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Thu, 23 Jul 2026 09:10:01 +0800 Subject: [PATCH 0903/1433] wifi: mac80211: skip unused probe response countdown offsets mac80211 copies cfg80211's variable-length countdown offset list into a zero-initialized fixed-size array, leaving unused entries at zero. The beacon branch already skips those zero entries, but the AP probe-response branch writes through them unconditionally. When a probe-response template has no countdown offset, the write through an unused zero entry overwrites resp->data[0], corrupting the first byte of the template. cfg80211 already bounds explicitly supplied non-zero offsets in nl80211_parse_counter_offsets(), so this is a zero-sentinel bug, not an out-of-bounds write. Skip zero probe-response offsets, matching the beacon path. Fixes: af296bdb8da4 ("mac80211: move csa counters from sdata to beacon/presp") Link: https://lore.kernel.org/all/20260708195911.84365-6-enderaoelyther@gmail.com/ Assisted-by: Codex:gpt-5 Assisted-by: Claude:opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260723011001.76851-1-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 1ee2b1cfc756..3a1e2c9e1565 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -5275,7 +5275,8 @@ static void ieee80211_set_beacon_cntdwn(struct ieee80211_sub_if_data *sdata, if (sdata->vif.type == NL80211_IFTYPE_AP && resp) { u16 *resp_offsets = resp->cntdwn_counter_offsets; - resp->data[resp_offsets[i]] = count; + if (resp_offsets[i]) + resp->data[resp_offsets[i]] = count; } } } From 9d96e037ff90a2fc81a841b2002d500a5a4a5351 Mon Sep 17 00:00:00 2001 From: Mariano Baragiola Date: Tue, 28 Jul 2026 16:26:10 -0300 Subject: [PATCH 0904/1433] wifi: wilc1000: validate monitor transmit frame headers wilc_wfi_mon_xmit() reads the radiotap length before ensuring that the fixed header is present. After stripping that header, it reads the frame type and all three 802.11 addresses without checking how much frame data remains. A truncated monitor injection can therefore cause out-of-bounds reads. Validate the radiotap header first, use the common 802.11 helper to check the variable header length, and require a complete three-address header before using the addresses. This covers QoS and four-address data headers while rejecting short control headers that this path cannot classify. Signed-off-by: Mariano Baragiola Link: https://patch.msgid.link/20260728192610.2236361-1-mbaragiola@linux.com Signed-off-by: Johannes Berg --- drivers/net/wireless/microchip/wilc1000/mon.c | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/microchip/wilc1000/mon.c b/drivers/net/wireless/microchip/wilc1000/mon.c index b5cf6fa7a851..9b8c083b403d 100644 --- a/drivers/net/wireless/microchip/wilc1000/mon.c +++ b/drivers/net/wireless/microchip/wilc1000/mon.c @@ -142,6 +142,9 @@ static int mon_mgmt_tx(struct net_device *dev, const u8 *buf, size_t len) static netdev_tx_t wilc_wfi_mon_xmit(struct sk_buff *skb, struct net_device *dev) { + struct ieee80211_radiotap_header_fixed *rtap_hdr; + struct ieee80211_hdr_3addr *hdr; + unsigned int hdr_len; u32 rtap_len, ret = 0; struct wilc_wfi_mon_priv *mon_priv; struct sk_buff *skb2; @@ -153,13 +156,26 @@ static netdev_tx_t wilc_wfi_mon_xmit(struct sk_buff *skb, if (!mon_priv) return -EFAULT; + if (skb->len < sizeof(*rtap_hdr)) + goto drop; + + rtap_hdr = (void *)skb->data; + if (rtap_hdr->it_version) + goto drop; + rtap_len = ieee80211_get_radiotap_len(skb->data); - if (skb->len < rtap_len) - return -1; + if (rtap_len < sizeof(*rtap_hdr) || skb->len < rtap_len) + goto drop; skb_pull(skb, rtap_len); + hdr_len = ieee80211_get_hdrlen_from_skb(skb); + if (hdr_len < sizeof(*hdr)) + goto drop; - if (skb->data[0] == 0xc0 && is_broadcast_ether_addr(&skb->data[4])) { + hdr = (void *)skb->data; + + if (ieee80211_is_deauth(hdr->frame_control) && + is_broadcast_ether_addr(hdr->addr1)) { skb2 = dev_alloc_skb(skb->len + sizeof(*cb_hdr)); if (!skb2) return -ENOMEM; @@ -191,8 +207,8 @@ static netdev_tx_t wilc_wfi_mon_xmit(struct sk_buff *skb, } skb->dev = mon_priv->real_ndev; - ether_addr_copy(srcadd, &skb->data[10]); - ether_addr_copy(bssid, &skb->data[16]); + ether_addr_copy(srcadd, hdr->addr2); + ether_addr_copy(bssid, hdr->addr3); /* * Identify if data or mgmt packet, if source address and bssid * fields are equal send it to mgmt frames handler @@ -207,6 +223,10 @@ static netdev_tx_t wilc_wfi_mon_xmit(struct sk_buff *skb, } return ret; + +drop: + dev_kfree_skb(skb); + return NETDEV_TX_OK; } static const struct net_device_ops wilc_wfi_netdev_ops = { From aa0069bd920a0bc02d40d84fe39e7ef478c229e8 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 2 Aug 2026 10:40:09 +0200 Subject: [PATCH 0905/1433] wifi: mac80211: fix RCU dereference in throughput estimate This is invoked with the wiphy mutex held, not in an RCU critical section, fix the dereference accordingly. Fixes: 2f925427e27a ("wifi: mac80211: estimate expected throughput if not provided by driver/rc") Link: https://patch.msgid.link/20260802104010.94bf0862c329.I0a05bf8ab999cb737c487d79082425257e10132a@changeid Signed-off-by: Johannes Berg --- net/mac80211/sta_info.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index d12aed9c1756..fdf00cbf49d8 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -2821,7 +2821,7 @@ static u32 sta_estimate_expected_throughput(struct sta_info *sta, u32 duration; u8 band; - conf = rcu_dereference(bss_conf->chanctx_conf); + conf = sdata_dereference(bss_conf->chanctx_conf, sta->sdata); if (!conf) return 0; band = conf->def.chan->band; From b17e206e99598e51eb7862a719620647b35347c0 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 2 Aug 2026 10:40:10 +0200 Subject: [PATCH 0906/1433] wifi: mac80211: fix RCU usage in peer probing Converting the station and chanctx lookups to wiphy_dereference() was correct for the function itself but removed the rcu_read_lock() for the later transmit, which requires it, as well. Fix that. Found with the ap_open_poll_sta hwsim test, which reports net/mac80211/tx.c:608 suspicious rcu_dereference_check() usage! (and four more like it). Fixes: 1c3f880ed00e ("wifi: mac80211: implement STA-mode peer probing") Link: https://patch.msgid.link/20260802104010.6c09477032c4.If024b480b96bf9fe7baa821ed48b80be322d1e44@changeid Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 0c2c7afd59c4..23f4f9ec86d0 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -5054,7 +5054,9 @@ static int ieee80211_probe_peer(struct wiphy *wiphy, struct net_device *dev, } local_bh_disable(); + rcu_read_lock(); ieee80211_xmit(sdata, sta, skb); + rcu_read_unlock(); local_bh_enable(); return 0; From 32856f39fefdc75bd253cab999f996750628648e Mon Sep 17 00:00:00 2001 From: Can Peng Date: Thu, 23 Jul 2026 13:56:17 +0800 Subject: [PATCH 0907/1433] wifi: brcmfmac: validate msgbuf flowring IDs before use Firmware messages carry flow_ring_id values which brcmfmac converts to an internal flowid by subtracting BRCMF_H2D_MSGRING_FLOWRING_IDSTART. The resulting value is used as a bit index in txstatus_done_map and as an array index into msgbuf->flowrings and the flowring state. Validate the firmware supplied flow_ring_id before using it. This prevents flow_ring_id values below BRCMF_H2D_MSGRING_FLOWRING_IDSTART from underflowing and rejects values outside msgbuf->max_flowrings. In the tx status path, complete the packet with an error after removing a valid packet id so the skb is not leaked when the flow ring id is invalid. Signed-off-by: Can Peng Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260723055618.550834-1-pengcan@kylinos.cn Signed-off-by: Johannes Berg --- .../broadcom/brcm80211/brcmfmac/msgbuf.c | 46 ++++++++++++++++--- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c index ba1ce1552e0f..8146d50d6f07 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c @@ -560,6 +560,28 @@ brcmf_msgbuf_remove_flowring(struct brcmf_msgbuf *msgbuf, u16 flowid) brcmf_flowring_delete(msgbuf->flow, flowid); } +static bool brcmf_msgbuf_get_flowid(struct brcmf_msgbuf *msgbuf, + u16 flow_ring_id, u16 *flowid) +{ + u32 id = flow_ring_id; + + if (id < BRCMF_H2D_MSGRING_FLOWRING_IDSTART) { + bphy_err(msgbuf->drvr, "invalid flowring id %u\n", + flow_ring_id); + return false; + } + + id -= BRCMF_H2D_MSGRING_FLOWRING_IDSTART; + if (id >= msgbuf->max_flowrings) { + bphy_err(msgbuf->drvr, "invalid flowring id %u\n", + flow_ring_id); + return false; + } + + *flowid = id; + return true; +} + static struct brcmf_msgbuf_work_item * brcmf_msgbuf_dequeue_work(struct brcmf_msgbuf *msgbuf) @@ -880,17 +902,23 @@ brcmf_msgbuf_process_txstatus(struct brcmf_msgbuf *msgbuf, void *buf) struct msgbuf_tx_status *tx_status; u32 idx; struct sk_buff *skb; + u16 flow_ring_id; u16 flowid; tx_status = (struct msgbuf_tx_status *)buf; idx = le32_to_cpu(tx_status->msg.request_id) - 1; - flowid = le16_to_cpu(tx_status->compl_hdr.flow_ring_id); - flowid -= BRCMF_H2D_MSGRING_FLOWRING_IDSTART; + flow_ring_id = le16_to_cpu(tx_status->compl_hdr.flow_ring_id); skb = brcmf_msgbuf_get_pktid(msgbuf->drvr->bus_if->dev, msgbuf->tx_pktids, idx); if (!skb) return; + if (!brcmf_msgbuf_get_flowid(msgbuf, flow_ring_id, &flowid)) { + brcmf_txfinalize(brcmf_get_ifp(msgbuf->drvr, tx_status->msg.ifidx), + skb, false); + return; + } + set_bit(flowid, msgbuf->txstatus_done_map); commonring = msgbuf->flowrings[flowid]; atomic_dec(&commonring->outstanding_tx); @@ -1237,13 +1265,16 @@ brcmf_msgbuf_process_flow_ring_create_response(struct brcmf_msgbuf *msgbuf, struct msgbuf_flowring_create_resp *flowring_create_resp; u16 status; u16 flowid; + u16 flow_ring_id; flowring_create_resp = (struct msgbuf_flowring_create_resp *)buf; - flowid = le16_to_cpu(flowring_create_resp->compl_hdr.flow_ring_id); - flowid -= BRCMF_H2D_MSGRING_FLOWRING_IDSTART; + flow_ring_id = le16_to_cpu(flowring_create_resp->compl_hdr.flow_ring_id); status = le16_to_cpu(flowring_create_resp->compl_hdr.status); + if (!brcmf_msgbuf_get_flowid(msgbuf, flow_ring_id, &flowid)) + return; + if (status) { bphy_err(drvr, "Flowring creation failed, code %d\n", status); brcmf_msgbuf_remove_flowring(msgbuf, flowid); @@ -1266,13 +1297,16 @@ brcmf_msgbuf_process_flow_ring_delete_response(struct brcmf_msgbuf *msgbuf, struct msgbuf_flowring_delete_resp *flowring_delete_resp; u16 status; u16 flowid; + u16 flow_ring_id; flowring_delete_resp = (struct msgbuf_flowring_delete_resp *)buf; - flowid = le16_to_cpu(flowring_delete_resp->compl_hdr.flow_ring_id); - flowid -= BRCMF_H2D_MSGRING_FLOWRING_IDSTART; + flow_ring_id = le16_to_cpu(flowring_delete_resp->compl_hdr.flow_ring_id); status = le16_to_cpu(flowring_delete_resp->compl_hdr.status); + if (!brcmf_msgbuf_get_flowid(msgbuf, flow_ring_id, &flowid)) + return; + if (status) { bphy_err(drvr, "Flowring deletion failed, code %d\n", status); brcmf_flowring_delete(msgbuf->flow, flowid); From f0ba5fd51ff23077ce66cb9be79d41a4b7c19c5e Mon Sep 17 00:00:00 2001 From: Can Peng Date: Fri, 24 Jul 2026 17:25:30 +0800 Subject: [PATCH 0908/1433] wifi: brcmfmac: Set DMA direction for msgbuf packet IDs brcmf_msgbuf_init_pktids() takes the DMA direction from its callers, but never stores it in the packet ID state. Since the state is zeroed, pktids->direction remains DMA_BIDIRECTIONAL for both the TX and RX packet ID pools. All msgbuf packet ID map and unmap paths use pktids->direction. As a result, TX buffers requested with DMA_TO_DEVICE and RX buffers requested with DMA_FROM_DEVICE are mapped and unmapped as DMA_BIDIRECTIONAL instead. Store the caller-provided direction when initializing the packet ID state. Signed-off-by: Can Peng Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260724092530.674624-1-pengcan@kylinos.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c index 8146d50d6f07..069ba7016654 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c @@ -310,6 +310,7 @@ brcmf_msgbuf_init_pktids(u32 nr_array_entries, } pktids->array = array; pktids->array_size = nr_array_entries; + pktids->direction = direction; return pktids; } From 1b1edb9ebed49099bdc924cef49a9aea8b552199 Mon Sep 17 00:00:00 2001 From: Jason Huang Date: Wed, 22 Jul 2026 16:26:08 +0800 Subject: [PATCH 0909/1433] wifi: brcmfmac: fix P2P action frame handling without device vif Some P2P action frame paths assume the P2P device vif is always available. That is not true when userspace sends non-P2P public action frames through the primary interface, or when action-frame abort runs after the P2P device vif has not been created. Fall back to the primary vif when aborting an action frame without a P2P device vif, and guard P2P device saved IE access before using it for peer channel search. Fixes: 30fb1b272909 ("brcmfmac: use actframe_abort to cancel ongoing action frame") Fixes: 6eda4e2c5425 ("brcmfmac: Add tx p2p off-channel support.") Signed-off-by: Jason Huang Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260722082608.412472-1-Jason.Huang2@infineon.com Signed-off-by: Johannes Berg --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c index 32a015d6b769..813cc15d8b87 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c @@ -1307,6 +1307,9 @@ static s32 brcmf_p2p_abort_action_frame(struct brcmf_cfg80211_info *cfg) brcmf_dbg(TRACE, "Enter\n"); vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif; + if (!vif) + vif = p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif; + err = brcmf_fil_bsscfg_data_set(vif->ifp, "actframe_abort", &int_val, sizeof(s32)); if (err) @@ -1845,6 +1848,7 @@ bool brcmf_p2p_send_action_frame(struct brcmf_if *ifp, /* validate channel and p2p ies */ if (config_af_params.search_channel && IS_P2P_SOCIAL_CHANNEL(le32_to_cpu(af_params->channel)) && + p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif && p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif->saved_ie.probe_req_ie_len) { afx_hdl = &p2p->afx_hdl; afx_hdl->peer_listen_chan = le32_to_cpu(af_params->channel); From cf57f0a674cc3e3cda1a789359cc1238b61b9d7d Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 2 Aug 2026 11:12:17 +0300 Subject: [PATCH 0910/1433] wifi: mac80211: disconnect on CSA to channel 0 The refactor for the CSA parsing erroneously equates channel zero and no information present, leading it to ignore a CSA on an AP that advertises a switch to that (invalid) channel. This leads to not disconnecting, which we should. For Intel devices, this can lead to a firmware crash. Fix this by using an int type for the channel number as well as the opclass, and using a (negative) value that cannot be encoded in the element to indicate it's not present. Fixes: 21c3f8f95554 ("wifi: mac80211: refactor STA CSA parsing flows") Signed-off-by: Johannes Berg Reviewed-by: Emmanuel Grumbach Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260802111213.3bc833515e40.I255c37c31ca8b0b34e351cf254e16b6071dd8fb3@changeid Signed-off-by: Johannes Berg --- net/mac80211/spectmgmt.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/net/mac80211/spectmgmt.c b/net/mac80211/spectmgmt.c index ec622750e1c9..880f4625775d 100644 --- a/net/mac80211/spectmgmt.c +++ b/net/mac80211/spectmgmt.c @@ -227,7 +227,7 @@ int ieee80211_parse_ch_switch_ie(struct ieee80211_sub_if_data *sdata, { enum nl80211_band new_band = current_band; int new_freq; - u8 new_chan_no = 0, new_op_class = 0; + int new_chan_no = -1, new_op_class = -1; struct ieee80211_channel *new_chan; struct cfg80211_chan_def new_chandef = {}; const struct ieee80211_sec_chan_offs_ie *sec_chan_offs; @@ -256,7 +256,7 @@ int ieee80211_parse_ch_switch_ie(struct ieee80211_sub_if_data *sdata, new_op_class = ext_chansw_elem->new_operating_class; if (!ieee80211_operating_class_to_band(new_op_class, &new_band)) { - new_op_class = 0; + new_op_class = -1; if (!unprot_action) sdata_info(sdata, "cannot understand ECSA IE operating class, %d, ignoring\n", @@ -268,14 +268,14 @@ int ieee80211_parse_ch_switch_ie(struct ieee80211_sub_if_data *sdata, } } - if (!new_op_class && elems->ch_switch_ie) { + if (new_op_class < 0 && elems->ch_switch_ie) { new_chan_no = elems->ch_switch_ie->new_ch_num; csa_ie->count = elems->ch_switch_ie->count; csa_ie->mode = elems->ch_switch_ie->mode; } /* nothing here we understand */ - if (!new_chan_no) + if (new_chan_no < 0) return 1; /* Mesh Channel Switch Parameters Element */ @@ -349,7 +349,8 @@ int ieee80211_parse_ch_switch_ie(struct ieee80211_sub_if_data *sdata, get_unaligned_le16(bwi->info.optional); } else if (!wide_bw_chansw_ie || !wbcs_elem_to_chandef(wide_bw_chansw_ie, &new_chandef)) { - if (!ieee80211_operating_class_to_chandef(new_op_class, new_chan, + if (new_op_class < 0 || + !ieee80211_operating_class_to_chandef(new_op_class, new_chan, &new_chandef)) new_chandef = csa_ie->chanreq.oper; } From 6c5fc504d0d6934132637aa3db4b9b58148eaa78 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 31 Jul 2026 15:11:03 +0800 Subject: [PATCH 0911/1433] wifi: cfg80211: stop PMSR before P2P and NAN teardown PMSR request teardown must abort active measurements while the wireless_dev is still present in the driver. cfg80211_leave_locked() and cfg80211_stop_pd() already do this before invoking the driver's stop callback, but cfg80211_stop_p2p_device() and cfg80211_stop_nan() do not. Those helpers are also called directly by nl80211, rfkill shutdown, and wireless_dev unregister paths. If one of these paths stops a P2P device or NAN interface with a pending request, it removes the mac80211 subinterface from the driver first. Subsequent request cleanup cannot reach the lower driver's abort callback, but cfg80211 frees the request regardless. Driver state can then retain a stale request and use it when it later reports a result. Call cfg80211_pmsr_wdev_down() before stopping the P2P device or NAN interface. This keeps lower-driver request state and cfg80211 request ownership in sync for all of the helpers' callers. Fixes: 9bb7e0f24e7e ("cfg80211: add peer measurement with FTM initiator API") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260731071103.73563-1-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/wireless/core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/wireless/core.c b/net/wireless/core.c index 610238d723ff..d13310fef691 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -237,6 +237,7 @@ void cfg80211_stop_p2p_device(struct cfg80211_registered_device *rdev, if (!wdev_running(wdev)) return; + cfg80211_pmsr_wdev_down(wdev); rdev_stop_p2p_device(rdev, wdev); wdev->is_running = false; @@ -264,6 +265,8 @@ void cfg80211_stop_nan(struct cfg80211_registered_device *rdev, if (!wdev_running(wdev)) return; + cfg80211_pmsr_wdev_down(wdev); + /* * If there is a scheduled update pending, mark it as canceled, so the * empty schedule will be accepted From 8c7badd19e13ceeb7ae34ea0b25fc5e4e13c43d3 Mon Sep 17 00:00:00 2001 From: Bobby Eshleman Date: Fri, 31 Jul 2026 16:36:23 -0700 Subject: [PATCH 0912/1433] selftests: drv-net: enable devmem TCP in the test config The config fragment already sets CONFIG_UDMABUF=y, but kconfig silently drops it. UDMABUF/NET_DEVMEM both depend on DMA_SHARED_BUFFER, which we can't enable directly, so we need to enable a config that selects it. We use SYNC_FILE for that purpose here. Additionally, we flip on CONFIG_NET_DEVMEM as well. Suggested-by: Jakub Kicinski Signed-off-by: Bobby Eshleman Reviewed-by: Mina Almasry Link: https://patch.msgid.link/20260731-selftests-devmem-config-v1-1-098014348d9d@meta.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/hw/config | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/testing/selftests/drivers/net/hw/config b/tools/testing/selftests/drivers/net/hw/config index ed8642b68094..d89a9ba17655 100644 --- a/tools/testing/selftests/drivers/net/hw/config +++ b/tools/testing/selftests/drivers/net/hw/config @@ -15,11 +15,13 @@ CONFIG_IPV6_SIT=y CONFIG_IPV6_TUNNEL=y CONFIG_NET_CLS_ACT=y CONFIG_NET_CLS_BPF=y +CONFIG_NET_DEVMEM=y CONFIG_NET_IPGRE=y CONFIG_NET_IPGRE_DEMUX=y CONFIG_NET_IPIP=y CONFIG_NETKIT=y CONFIG_NET_SCH_INGRESS=y +CONFIG_SYNC_FILE=y CONFIG_UDMABUF=y CONFIG_USER_NS=y CONFIG_VXLAN=y From d661abdc30c254649c32ef6e0aa1e621e04ff0a7 Mon Sep 17 00:00:00 2001 From: Jiawen Wu Date: Thu, 30 Jul 2026 14:54:09 +0800 Subject: [PATCH 0913/1433] net: ngbe: correct misleading interrupt comment In ngbe_irq_enable(), the code subsequently calls wx_intr_enable() to enable interrupts. However, the preceding comment incorrectly stated "mask interrupt", which means disabling or blocking interrupts. This patch corrects the comment to "unmask interrupt" to accurately reflect the actual behavior of the code. No functional changes are introduced. Signed-off-by: Jiawen Wu Link: https://patch.msgid.link/147244C2750FF990+20260730065409.50807-1-jiawenwu@trustnetic.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/wangxun/ngbe/ngbe_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c index a16221995909..bfdff6345303 100644 --- a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c +++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c @@ -177,7 +177,7 @@ static void ngbe_irq_enable(struct wx *wx, bool queues) wr32(wx, WX_PX_MISC_IEN, mask); - /* mask interrupt */ + /* unmask interrupt */ if (queues) wx_intr_enable(wx, NGBE_INTR_ALL); else From e8aaf6ba33c7d875523fcbae20fbd70ab57f9c48 Mon Sep 17 00:00:00 2001 From: Can Peng Date: Tue, 28 Jul 2026 11:20:45 +0800 Subject: [PATCH 0914/1433] netxen: unregister notifiers if PCI registration fails netxen_init_module() registers the netdevice and inetaddr notifiers before registering the PCI driver. If pci_register_driver() fails, the function returns the error directly and leaves both notifiers registered. That leaves notifier callbacks installed for a module that failed to load. Mirror the module exit path on this failure and unregister the notifiers before returning the error. Cc: stable+noautosel@kernel.org # untested fix to unlikely driver error path Signed-off-by: Can Peng Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260728032046.121631-2-pengcan@kylinos.cn Signed-off-by: Jakub Kicinski --- .../net/ethernet/qlogic/netxen/netxen_nic_main.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/qlogic/netxen/netxen_nic_main.c b/drivers/net/ethernet/qlogic/netxen/netxen_nic_main.c index 5ee2bd9d6886..67d9bf69f8f2 100644 --- a/drivers/net/ethernet/qlogic/netxen/netxen_nic_main.c +++ b/drivers/net/ethernet/qlogic/netxen/netxen_nic_main.c @@ -3447,13 +3447,24 @@ static struct pci_driver netxen_driver = { static int __init netxen_init_module(void) { + int ret; + printk(KERN_INFO "%s\n", netxen_nic_driver_string); #ifdef CONFIG_INET register_netdevice_notifier(&netxen_netdev_cb); register_inetaddr_notifier(&netxen_inetaddr_cb); #endif - return pci_register_driver(&netxen_driver); + + ret = pci_register_driver(&netxen_driver); +#ifdef CONFIG_INET + if (ret) { + unregister_inetaddr_notifier(&netxen_inetaddr_cb); + unregister_netdevice_notifier(&netxen_netdev_cb); + } +#endif + + return ret; } module_init(netxen_init_module); From 5aed8f404401bfa1570cf0c3ffed7b752f568e83 Mon Sep 17 00:00:00 2001 From: Qingfang Deng Date: Thu, 30 Jul 2026 18:06:51 +0800 Subject: [PATCH 0915/1433] ppp: use netdev_from_priv() Use the new netdev_from_priv() helper to access the net device from struct ppp. Signed-off-by: Qingfang Deng Reviewed-by: Breno Leitao Link: https://patch.msgid.link/20260730100654.745-1-qingfang.deng@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ppp/ppp_generic.c | 137 ++++++++++++++++++---------------- 1 file changed, 72 insertions(+), 65 deletions(-) diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c index 08bb89765487..e1013621eb1d 100644 --- a/drivers/net/ppp/ppp_generic.c +++ b/drivers/net/ppp/ppp_generic.c @@ -139,7 +139,6 @@ struct ppp { void *rc_state; /* its internal state 98 */ unsigned long last_xmit; /* jiffies when last pkt sent 9c */ unsigned long last_recv; /* jiffies when last pkt rcvd a0 */ - struct net_device *dev; /* network interface device a4 */ int closing; /* is device closing down? a8 */ #ifdef CONFIG_PPP_MULTILINK int nxchan; /* next channel to send something on */ @@ -412,7 +411,7 @@ static int ppp_release(struct inode *unused, struct file *file) ppp = PF_TO_PPP(pf); rtnl_lock(); if (file == ppp->owner) - unregister_netdevice(ppp->dev); + unregister_netdevice(netdev_from_priv(ppp)); rtnl_unlock(); ppp_release_interface(ppp); break; @@ -921,7 +920,7 @@ static long ppp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) } else { WRITE_ONCE(ppp->npmode[i], npi.mode); /* we may be able to transmit more packets now (??) */ - netif_wake_queue(ppp->dev); + netif_wake_queue(netdev_from_priv(ppp)); } err = 0; break; @@ -1147,7 +1146,7 @@ static __net_exit void ppp_exit_rtnl_net(struct net *net, int id; idr_for_each_entry(&pn->units_idr, ppp, id) - ppp_nl_dellink(ppp->dev, dev_to_kill); + ppp_nl_dellink(netdev_from_priv(ppp), dev_to_kill); } static __net_exit void ppp_exit_net(struct net *net) @@ -1170,6 +1169,7 @@ static struct pernet_operations ppp_net_ops = { static int ppp_unit_register(struct ppp *ppp, int unit, bool ifname_is_set) { + struct net_device *dev = netdev_from_priv(ppp); struct ppp_net *pn = ppp_pernet(ppp->ppp_net); int ret; @@ -1181,8 +1181,8 @@ static int ppp_unit_register(struct ppp *ppp, int unit, bool ifname_is_set) goto err; if (!ifname_is_set) { while (1) { - snprintf(ppp->dev->name, IFNAMSIZ, "ppp%i", ret); - if (!netdev_name_in_use(ppp->ppp_net, ppp->dev->name)) + snprintf(dev->name, IFNAMSIZ, "ppp%i", ret); + if (!netdev_name_in_use(ppp->ppp_net, dev->name)) break; unit_put(&pn->units_idr, ret); ret = unit_get(&pn->units_idr, ppp, ret + 1); @@ -1210,11 +1210,11 @@ static int ppp_unit_register(struct ppp *ppp, int unit, bool ifname_is_set) ppp->file.index = ret; if (!ifname_is_set) - snprintf(ppp->dev->name, IFNAMSIZ, "ppp%i", ppp->file.index); + snprintf(dev->name, IFNAMSIZ, "ppp%i", ppp->file.index); mutex_unlock(&pn->all_ppp_mutex); - ret = register_netdevice(ppp->dev); + ret = register_netdevice(dev); if (ret < 0) goto err_unit; @@ -1239,7 +1239,6 @@ static int ppp_dev_configure(struct net *src_net, struct net_device *dev, int err; int cpu; - ppp->dev = dev; ppp->ppp_net = src_net; ppp->mru = PPP_MRU; ppp->owner = conf->file; @@ -1658,7 +1657,7 @@ static void ppp_xmit_flush(struct ppp *ppp) /* If there's no work left to do, tell the core net code that we can * accept some more. */ - netif_wake_queue(ppp->dev); + netif_wake_queue(netdev_from_priv(ppp)); } static void __ppp_xmit_process(struct ppp *ppp, struct sk_buff *skb) @@ -1674,7 +1673,7 @@ static void __ppp_xmit_process(struct ppp *ppp, struct sk_buff *skb) if (likely(skb_queue_empty(&ppp->file.xq))) { if (unlikely(!ppp_push(ppp, skb))) { skb_queue_tail(&ppp->file.xq, skb); - netif_stop_queue(ppp->dev); + netif_stop_queue(netdev_from_priv(ppp)); } goto out; } @@ -1712,17 +1711,18 @@ static void ppp_xmit_process(struct ppp *ppp, struct sk_buff *skb) kfree_skb(skb); if (net_ratelimit()) - netdev_err(ppp->dev, "recursion detected\n"); + netdev_err(netdev_from_priv(ppp), "recursion detected\n"); } static inline struct sk_buff * pad_compress_skb(struct ppp *ppp, struct sk_buff *skb) { + struct net_device *dev = netdev_from_priv(ppp); struct sk_buff *new_skb; int len; - int new_skb_size = ppp->dev->mtu + - ppp->xcomp->comp_extra + ppp->dev->hard_header_len; - int compressor_skb_size = ppp->dev->mtu + + int new_skb_size = dev->mtu + + ppp->xcomp->comp_extra + dev->hard_header_len; + int compressor_skb_size = dev->mtu + ppp->xcomp->comp_extra + PPP_HDRLEN; if (skb_linearize(skb)) @@ -1731,12 +1731,11 @@ pad_compress_skb(struct ppp *ppp, struct sk_buff *skb) new_skb = alloc_skb(new_skb_size, GFP_ATOMIC); if (!new_skb) { if (net_ratelimit()) - netdev_err(ppp->dev, "PPP: no memory (comp pkt)\n"); + netdev_err(dev, "PPP: no memory (comp pkt)\n"); return NULL; } - if (ppp->dev->hard_header_len > PPP_HDRLEN) - skb_reserve(new_skb, - ppp->dev->hard_header_len - PPP_HDRLEN); + if (dev->hard_header_len > PPP_HDRLEN) + skb_reserve(new_skb, dev->hard_header_len - PPP_HDRLEN); /* compressor still expects A/C bytes in hdr */ len = ppp->xcomp->compress(ppp->xc_state, skb->data - 2, @@ -1761,7 +1760,7 @@ pad_compress_skb(struct ppp *ppp, struct sk_buff *skb) * the same number. */ if (net_ratelimit()) - netdev_err(ppp->dev, "ppp: compressor dropped pkt\n"); + netdev_err(dev, "ppp: compressor dropped pkt\n"); consume_skb(new_skb); new_skb = NULL; } @@ -1777,13 +1776,14 @@ pad_compress_skb(struct ppp *ppp, struct sk_buff *skb) static int ppp_prepare_tx_skb(struct ppp *ppp, struct sk_buff **pskb) { + struct net_device *dev = netdev_from_priv(ppp); struct sk_buff *skb = *pskb; int proto = PPP_PROTO(skb); struct sk_buff *new_skb; int len; unsigned char *cp; - skb->dev = ppp->dev; + skb->dev = dev; if (proto < 0x8000) { #ifdef CONFIG_PPP_FILTER @@ -1794,7 +1794,7 @@ ppp_prepare_tx_skb(struct ppp *ppp, struct sk_buff **pskb) if (ppp->pass_filter && bpf_prog_run(ppp->pass_filter, skb) == 0) { if (READ_ONCE(ppp->debug) & 1) - netdev_printk(KERN_DEBUG, ppp->dev, + netdev_printk(KERN_DEBUG, dev, "PPP: outbound frame " "not passed\n"); kfree_skb(skb); @@ -1811,7 +1811,7 @@ ppp_prepare_tx_skb(struct ppp *ppp, struct sk_buff **pskb) #endif /* CONFIG_PPP_FILTER */ } - dev_sw_netstats_tx_add(ppp->dev, 1, skb->len - PPP_PROTO_LEN); + dev_sw_netstats_tx_add(dev, 1, skb->len - PPP_PROTO_LEN); switch (proto) { case PPP_IP: @@ -1822,13 +1822,13 @@ ppp_prepare_tx_skb(struct ppp *ppp, struct sk_buff **pskb) goto drop; /* try to do VJ TCP header compression */ - new_skb = alloc_skb(skb->len + ppp->dev->hard_header_len - 2, + new_skb = alloc_skb(skb->len + dev->hard_header_len - 2, GFP_ATOMIC); if (!new_skb) { - netdev_err(ppp->dev, "PPP: no memory (VJ comp pkt)\n"); + netdev_err(dev, "PPP: no memory (VJ comp pkt)\n"); goto drop; } - skb_reserve(new_skb, ppp->dev->hard_header_len - 2); + skb_reserve(new_skb, dev->hard_header_len - 2); cp = skb->data + 2; len = slhc_compress(ppp->vj, cp, skb->len - 2, new_skb->data + 2, &cp, @@ -1864,7 +1864,7 @@ ppp_prepare_tx_skb(struct ppp *ppp, struct sk_buff **pskb) proto != PPP_LCP && proto != PPP_CCP) { if (!(ppp->flags & SC_CCP_UP) && (ppp->flags & SC_MUST_COMP)) { if (net_ratelimit()) - netdev_err(ppp->dev, + netdev_err(dev, "ppp: compression required but " "down - pkt dropped.\n"); goto drop; @@ -1892,7 +1892,7 @@ ppp_prepare_tx_skb(struct ppp *ppp, struct sk_buff **pskb) drop: kfree_skb(skb); - DEV_STATS_INC(ppp->dev, tx_errors); + DEV_STATS_INC(dev, tx_errors); return 1; } @@ -1962,6 +1962,7 @@ MODULE_PARM_DESC(mp_protocol_compress, */ static int ppp_mp_explode(struct ppp *ppp, struct sk_buff *skb) { + struct net_device *dev = netdev_from_priv(ppp); int len, totlen; int i, bits, hdrlen, mtu; int flen; @@ -2158,8 +2159,8 @@ static int ppp_mp_explode(struct ppp *ppp, struct sk_buff *skb) spin_unlock(&pch->downl); err_linearize: if (READ_ONCE(ppp->debug) & 1) - netdev_err(ppp->dev, "PPP: no memory (fragment)\n"); - DEV_STATS_INC(ppp->dev, tx_errors); + netdev_err(dev, "PPP: no memory (fragment)\n"); + DEV_STATS_INC(dev, tx_errors); ++ppp->nxseq; return 1; /* abandon the frame */ } @@ -2332,7 +2333,7 @@ ppp_input(struct ppp_channel *chan, struct sk_buff *skb) if (!ppp_decompress_proto(skb)) { kfree_skb(skb); if (ppp) { - DEV_STATS_INC(ppp->dev, rx_length_errors); + DEV_STATS_INC(netdev_from_priv(ppp), rx_length_errors); ppp_receive_error(ppp); } goto done; @@ -2394,7 +2395,7 @@ ppp_receive_frame(struct ppp *ppp, struct sk_buff *skb, struct channel *pch) static void ppp_receive_error(struct ppp *ppp) { - DEV_STATS_INC(ppp->dev, rx_errors); + DEV_STATS_INC(netdev_from_priv(ppp), rx_errors); if (ppp->vj) slhc_toss(ppp->vj); } @@ -2402,6 +2403,7 @@ ppp_receive_error(struct ppp *ppp) static void ppp_receive_nonmp_frame(struct ppp *ppp, struct sk_buff *skb) { + struct net_device *dev = netdev_from_priv(ppp); struct sk_buff *ns; int proto, len, npi; @@ -2431,8 +2433,7 @@ ppp_receive_nonmp_frame(struct ppp *ppp, struct sk_buff *skb) /* copy to a new sk_buff with more tailroom */ ns = dev_alloc_skb(skb->len + 128); if (!ns) { - netdev_err(ppp->dev, "PPP: no memory " - "(VJ decomp)\n"); + netdev_err(dev, "PPP: no memory (VJ decomp)\n"); goto err; } skb_reserve(ns, 2); @@ -2445,7 +2446,7 @@ ppp_receive_nonmp_frame(struct ppp *ppp, struct sk_buff *skb) len = slhc_uncompress(ppp->vj, skb->data + 2, skb->len - 2); if (len <= 0) { - netdev_printk(KERN_DEBUG, ppp->dev, + netdev_printk(KERN_DEBUG, dev, "PPP: VJ decompression error\n"); goto err; } @@ -2468,7 +2469,7 @@ ppp_receive_nonmp_frame(struct ppp *ppp, struct sk_buff *skb) goto err; if (slhc_remember(ppp->vj, skb->data + 2, skb->len - 2) <= 0) { - netdev_err(ppp->dev, "PPP: VJ uncompressed error\n"); + netdev_err(dev, "PPP: VJ uncompressed error\n"); goto err; } proto = PPP_IP; @@ -2479,7 +2480,7 @@ ppp_receive_nonmp_frame(struct ppp *ppp, struct sk_buff *skb) break; } - dev_sw_netstats_rx_add(ppp->dev, skb->len - PPP_PROTO_LEN); + dev_sw_netstats_rx_add(dev, skb->len - PPP_PROTO_LEN); npi = proto_to_npindex(proto); if (npi < 0) { @@ -2506,7 +2507,7 @@ ppp_receive_nonmp_frame(struct ppp *ppp, struct sk_buff *skb) if (ppp->pass_filter && bpf_prog_run(ppp->pass_filter, skb) == 0) { if (READ_ONCE(ppp->debug) & 1) - netdev_printk(KERN_DEBUG, ppp->dev, + netdev_printk(KERN_DEBUG, dev, "PPP: inbound frame " "not passed\n"); kfree_skb(skb); @@ -2520,17 +2521,17 @@ ppp_receive_nonmp_frame(struct ppp *ppp, struct sk_buff *skb) #endif /* CONFIG_PPP_FILTER */ WRITE_ONCE(ppp->last_recv, jiffies); - if ((ppp->dev->flags & IFF_UP) == 0 || + if ((dev->flags & IFF_UP) == 0 || READ_ONCE(ppp->npmode[npi]) != NPMODE_PASS) { kfree_skb(skb); } else { /* chop off protocol */ skb_pull_rcsum(skb, 2); - skb->dev = ppp->dev; + skb->dev = dev; skb->protocol = htons(npindex_to_ethertype[npi]); skb_reset_mac_header(skb); skb_scrub_packet(skb, !net_eq(ppp->ppp_net, - dev_net(ppp->dev))); + dev_net(dev))); netif_rx(skb); } } @@ -2568,8 +2569,8 @@ ppp_decompress_frame(struct ppp *ppp, struct sk_buff *skb) ns = dev_alloc_skb(obuff_size); if (!ns) { - netdev_err(ppp->dev, "ppp_decompress_frame: " - "no memory\n"); + netdev_err(netdev_from_priv(ppp), + "ppp_decompress_frame: no memory\n"); goto err; } /* the decompressor still expects the A/C bytes in the hdr */ @@ -2617,6 +2618,7 @@ ppp_decompress_frame(struct ppp *ppp, struct sk_buff *skb) static void ppp_receive_mp_frame(struct ppp *ppp, struct sk_buff *skb, struct channel *pch) { + struct net_device *dev = netdev_from_priv(ppp); u32 mask, seq; struct channel *ch; int mphdrlen = (ppp->flags & SC_MP_SHORTSEQ)? MPHDRLEN_SSN: MPHDRLEN; @@ -2661,7 +2663,7 @@ ppp_receive_mp_frame(struct ppp *ppp, struct sk_buff *skb, struct channel *pch) */ if (seq_before(seq, ppp->nextseq)) { kfree_skb(skb); - DEV_STATS_INC(ppp->dev, rx_dropped); + DEV_STATS_INC(dev, rx_dropped); ppp_receive_error(ppp); return; } @@ -2697,7 +2699,7 @@ ppp_receive_mp_frame(struct ppp *ppp, struct sk_buff *skb, struct channel *pch) if (pskb_may_pull(skb, 2)) ppp_receive_nonmp_frame(ppp, skb); else { - DEV_STATS_INC(ppp->dev, rx_length_errors); + DEV_STATS_INC(dev, rx_length_errors); kfree_skb(skb); ppp_receive_error(ppp); } @@ -2739,6 +2741,7 @@ ppp_mp_insert(struct ppp *ppp, struct sk_buff *skb) static struct sk_buff * ppp_mp_reconstruct(struct ppp *ppp) { + struct net_device *dev = netdev_from_priv(ppp); u32 seq = ppp->nextseq; u32 minseq = ppp->minseq; struct sk_buff_head *list = &ppp->mrq; @@ -2755,8 +2758,7 @@ ppp_mp_reconstruct(struct ppp *ppp) again: if (seq_before(PPP_MP_CB(p)->sequence, seq)) { /* this can't happen, anyway ignore the skb */ - netdev_err(ppp->dev, "ppp_mp_reconstruct bad " - "seq %u < %u\n", + netdev_err(dev, "ppp_mp_reconstruct bad seq %u < %u\n", PPP_MP_CB(p)->sequence, seq); __skb_unlink(p, list); kfree_skb(p); @@ -2775,7 +2777,7 @@ ppp_mp_reconstruct(struct ppp *ppp) minseq + 1: PPP_MP_CB(p)->sequence; if (READ_ONCE(ppp->debug) & 1) - netdev_printk(KERN_DEBUG, ppp->dev, + netdev_printk(KERN_DEBUG, dev, "lost frag %u..%u\n", oldseq, seq-1); @@ -2803,8 +2805,8 @@ ppp_mp_reconstruct(struct ppp *ppp) if (lost == 0 && (PPP_MP_CB(p)->BEbits & E) && (PPP_MP_CB(head)->BEbits & B)) { if (len > ppp->mrru + 2) { - DEV_STATS_INC(ppp->dev, rx_length_errors); - netdev_printk(KERN_DEBUG, ppp->dev, + DEV_STATS_INC(dev, rx_length_errors); + netdev_printk(KERN_DEBUG, dev, "PPP: reconstructed packet" " is too long (%d)\n", len); } else { @@ -2824,7 +2826,7 @@ ppp_mp_reconstruct(struct ppp *ppp) skb_queue_reverse_walk_from_safe(list, p, tmp2) { if (READ_ONCE(ppp->debug) & 1) - netdev_printk(KERN_DEBUG, ppp->dev, + netdev_printk(KERN_DEBUG, dev, "discarding frag %u\n", PPP_MP_CB(p)->sequence); __skb_unlink(p, list); @@ -2846,7 +2848,7 @@ ppp_mp_reconstruct(struct ppp *ppp) if (p == head) break; if (READ_ONCE(ppp->debug) & 1) - netdev_printk(KERN_DEBUG, ppp->dev, + netdev_printk(KERN_DEBUG, dev, "discarding frag %u\n", PPP_MP_CB(p)->sequence); __skb_unlink(p, list); @@ -2854,11 +2856,11 @@ ppp_mp_reconstruct(struct ppp *ppp) } if (READ_ONCE(ppp->debug) & 1) - netdev_printk(KERN_DEBUG, ppp->dev, + netdev_printk(KERN_DEBUG, dev, " missed pkts %u..%u\n", ppp->nextseq, PPP_MP_CB(head)->sequence-1); - DEV_STATS_INC(ppp->dev, rx_dropped); + DEV_STATS_INC(dev, rx_dropped); ppp_receive_error(ppp); } @@ -2977,8 +2979,8 @@ char *ppp_dev_name(struct ppp_channel *chan) if (pch) { ppp = rcu_dereference(pch->ppp); - if (ppp && ppp->dev) - name = ppp->dev->name; + if (ppp) + name = netdev_from_priv(ppp)->name; } return name; } @@ -3309,12 +3311,13 @@ find_compressor(int type) static void ppp_get_stats(struct ppp *ppp, struct ppp_stats *st) { + struct net_device *dev = netdev_from_priv(ppp); struct slcompress *vj = ppp->vj; int cpu; memset(st, 0, sizeof(*st)); for_each_possible_cpu(cpu) { - struct pcpu_sw_netstats *p = per_cpu_ptr(ppp->dev->tstats, cpu); + struct pcpu_sw_netstats *p = per_cpu_ptr(dev->tstats, cpu); u64 rx_packets, rx_bytes, tx_packets, tx_bytes; rx_packets = u64_stats_read(&p->rx_packets); @@ -3327,8 +3330,8 @@ ppp_get_stats(struct ppp *ppp, struct ppp_stats *st) st->p.ppp_opackets += tx_packets; st->p.ppp_obytes += tx_bytes; } - st->p.ppp_ierrors = DEV_STATS_READ(ppp->dev, rx_errors); - st->p.ppp_oerrors = DEV_STATS_READ(ppp->dev, tx_errors); + st->p.ppp_ierrors = DEV_STATS_READ(dev, rx_errors); + st->p.ppp_oerrors = DEV_STATS_READ(dev, tx_errors); if (!vj) return; st->vj.vjs_packets = vj->sls_o_compressed + vj->sls_o_uncompressed; @@ -3408,6 +3411,8 @@ init_ppp_file(struct ppp_file *pf, int kind) */ static void ppp_release_interface(struct ppp *ppp) { + struct net_device *dev = netdev_from_priv(ppp); + if (!refcount_dec_and_test(&ppp->file.refcnt)) return; @@ -3415,7 +3420,7 @@ static void ppp_release_interface(struct ppp *ppp) if (!ppp->file.dead || ppp->n_channels) { /* "can't happen" */ - netdev_err(ppp->dev, "ppp: destroying ppp struct %p " + netdev_err(dev, "ppp: destroying ppp struct %p " "but dead=%d n_channels=%d !\n", ppp, ppp->file.dead, ppp->n_channels); return; @@ -3445,7 +3450,7 @@ static void ppp_release_interface(struct ppp *ppp) free_percpu(ppp->xmit_recursion); - free_netdev(ppp->dev); + free_netdev(dev); } /* @@ -3492,6 +3497,7 @@ ppp_find_channel(struct ppp_net *pn, int unit) static int ppp_connect_channel(struct channel *pch, int unit) { + struct net_device *dev; struct ppp *ppp; struct ppp_net *pn; int ret = -ENXIO; @@ -3503,6 +3509,7 @@ ppp_connect_channel(struct channel *pch, int unit) ppp = ppp_find_unit(pn, unit); if (!ppp) goto out; + dev = netdev_from_priv(ppp); spin_lock(&pch->upl); ret = -EINVAL; if (rcu_dereference_protected(pch->ppp, lockdep_is_held(&pch->upl)) || @@ -3519,15 +3526,15 @@ ppp_connect_channel(struct channel *pch, int unit) goto outl; } if (pch->chan->direct_xmit) - ppp->dev->priv_flags |= IFF_NO_QUEUE; + dev->priv_flags |= IFF_NO_QUEUE; else - ppp->dev->priv_flags &= ~IFF_NO_QUEUE; + dev->priv_flags &= ~IFF_NO_QUEUE; spin_unlock_bh(&pch->downl); if (pch->file.hdrlen > ppp->file.hdrlen) ppp->file.hdrlen = pch->file.hdrlen; hdrlen = pch->file.hdrlen + 2; /* for protocol bytes */ - if (hdrlen > ppp->dev->hard_header_len) - ppp->dev->hard_header_len = hdrlen; + if (hdrlen > dev->hard_header_len) + dev->hard_header_len = hdrlen; list_add_tail_rcu(&pch->clist, &ppp->channels); ++ppp->n_channels; rcu_assign_pointer(pch->ppp, ppp); From af4d934164457f0578bf90e92ae0fcc5348260cb Mon Sep 17 00:00:00 2001 From: Zxyan Zhu Date: Wed, 29 Jul 2026 15:42:36 +0800 Subject: [PATCH 0916/1433] net: stmmac: Skip PHY attach if custom PCS is in use When a platform provides a custom PCS via the pcs_init callback, the MAC's phylink_pcs is already configured. In this case, no traditional PHY device is needed. Without this, stmmac_init_phy() falls through to the no-phy-node path and errors out with "no phy found" when the DT has no phy-handle for such interfaces. Skip the PHY attach when priv->hw->phylink_pcs is set and phy_addr is invalid. Fixes: f0ef433fc264 ("net: stmmac: introduce pcs_init/pcs_exit stmmac operations") Signed-off-by: Zxyan Zhu Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260729074237.2624940-2-zxyan0222@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index ee44bd6f4d48..79b71466d5b0 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1330,6 +1330,10 @@ static int stmmac_init_phy(struct net_device *dev) struct phy_device *phydev; if (addr < 0) { + /* If a custom PCS is in use, no PHY is needed */ + if (priv->hw->phylink_pcs) + return 0; + netdev_err(priv->dev, "no phy found\n"); return -ENODEV; } From e5db987f5d7f314892f03ad526003f7703885f4d Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Wed, 29 Jul 2026 15:42:37 +0800 Subject: [PATCH 0917/1433] net: phylink: allow PHYs to be attached in 802.3z inband mode Now that we have proper decision making for inband mode support which makes it a "best efforts" feature based on the capabilities of the PHY and PCS, we can relax whether we expect and permit a PHY to be attached. This is especially true for the 2500BASE-X case which some PHYs use without inband on their host side interface for 2.5G speeds, but use inband for slower speeds switching to SGMII on their host side interface. We already have such a case for some qcom-ethqos setups, although qcom-ethqos overrides phylink's inband settings by accessing the PCS directly at the moment. This should allow qcom-ethqos to transition to defaulting to inband when 2500BASE-X or SGMII is specified in its DTS. Allow PHYs to be attached when inband mode has been specified, which will be necessary to allow inband mode to be used on qcom-ethqos. Signed-off-by: Russell King (Oracle) Signed-off-by: Zxyan Zhu Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260729074237.2624940-3-zxyan0222@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/phylink.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index f40acc0d4133..b241768edbcb 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -1965,9 +1965,7 @@ EXPORT_SYMBOL_GPL(phylink_destroy); */ bool phylink_expects_phy(struct phylink *pl) { - if (pl->cfg_link_an_mode == MLO_AN_FIXED || - (pl->cfg_link_an_mode == MLO_AN_INBAND && - phy_interface_mode_is_8023z(pl->link_interface))) + if (pl->cfg_link_an_mode == MLO_AN_FIXED) return false; return true; } @@ -2206,9 +2204,7 @@ static int phylink_attach_phy(struct phylink *pl, struct phy_device *phy, { u32 flags = 0; - if (WARN_ON(pl->cfg_link_an_mode == MLO_AN_FIXED || - (pl->cfg_link_an_mode == MLO_AN_INBAND && - phy_interface_mode_is_8023z(interface) && !pl->sfp_bus))) + if (WARN_ON(pl->cfg_link_an_mode == MLO_AN_FIXED)) return -EINVAL; if (pl->phydev) From aee44196d2162d53c24474761655030f87eba7e5 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Wed, 29 Jul 2026 02:29:44 -0700 Subject: [PATCH 0918/1433] ipv6: raw: drop unused level argument from do_rawv6_getsockopt do_rawv6_getsockopt() takes a level argument but never uses it; the level dispatch is handled by the caller, rawv6_getsockopt(). Drop it, matching ipv4's do_raw_getsockopt(). No functional change. Reviewed-by: Joe Damato Acked-by: Stanislav Fomichev Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260729-getsockopt_phase4-v4-1-c44576757c17@debian.org Signed-off-by: Jakub Kicinski --- net/ipv6/raw.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/ipv6/raw.c b/net/ipv6/raw.c index b88d364e78aa..cb109ac956cb 100644 --- a/net/ipv6/raw.c +++ b/net/ipv6/raw.c @@ -1051,8 +1051,8 @@ static int rawv6_setsockopt(struct sock *sk, int level, int optname, return do_rawv6_setsockopt(sk, level, optname, optval, optlen); } -static int do_rawv6_getsockopt(struct sock *sk, int level, int optname, - char __user *optval, int __user *optlen) +static int do_rawv6_getsockopt(struct sock *sk, int optname, + char __user *optval, int __user *optlen) { struct raw6_sock *rp = raw6_sk(sk); int val, len; @@ -1109,7 +1109,7 @@ static int rawv6_getsockopt(struct sock *sk, int level, int optname, return ipv6_getsockopt(sk, level, optname, optval, optlen); } - return do_rawv6_getsockopt(sk, level, optname, optval, optlen); + return do_rawv6_getsockopt(sk, optname, optval, optlen); } static int rawv6_ioctl(struct sock *sk, int cmd, int *karg) From 8472b68c2497922c94ff820f95f73d491e0c6a13 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Wed, 29 Jul 2026 02:29:45 -0700 Subject: [PATCH 0919/1433] ipv6: raw: convert do_rawv6_getsockopt to sockopt_t Convert do_rawv6_getsockopt to the new sockopt_t model, mirroring what we have in ipv4. The overall goal is to move these callbacks gradually from __user points to use sockopt_t, and this part touches do_rawv6_getsockopt. No functional change. Reviewed-by: Joe Damato Acked-by: Stanislav Fomichev Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260729-getsockopt_phase4-v4-2-c44576757c17@debian.org Signed-off-by: Jakub Kicinski --- net/ipv6/raw.c | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/net/ipv6/raw.c b/net/ipv6/raw.c index cb109ac956cb..b965258cf9e5 100644 --- a/net/ipv6/raw.c +++ b/net/ipv6/raw.c @@ -1051,14 +1051,12 @@ static int rawv6_setsockopt(struct sock *sk, int level, int optname, return do_rawv6_setsockopt(sk, level, optname, optval, optlen); } -static int do_rawv6_getsockopt(struct sock *sk, int optname, - char __user *optval, int __user *optlen) +static int do_rawv6_getsockopt(struct sock *sk, int optname, sockopt_t *opt) { struct raw6_sock *rp = raw6_sk(sk); int val, len; - if (get_user(len, optlen)) - return -EFAULT; + len = opt->optlen; switch (optname) { case IPV6_HDRINCL: @@ -1080,11 +1078,10 @@ static int do_rawv6_getsockopt(struct sock *sk, int optname, return -ENOPROTOOPT; } - len = min_t(unsigned int, sizeof(int), len); + len = umin(sizeof(int), len); - if (put_user(len, optlen)) - return -EFAULT; - if (copy_to_user(optval, &val, len)) + opt->optlen = len; + if (copy_to_iter(&val, len, &opt->iter_out) != len) return -EFAULT; return 0; } @@ -1092,6 +1089,9 @@ static int do_rawv6_getsockopt(struct sock *sk, int optname, static int rawv6_getsockopt(struct sock *sk, int level, int optname, char __user *optval, int __user *optlen) { + sockopt_t opt; + int err; + switch (level) { case SOL_RAW: break; @@ -1109,7 +1109,18 @@ static int rawv6_getsockopt(struct sock *sk, int level, int optname, return ipv6_getsockopt(sk, level, optname, optval, optlen); } - return do_rawv6_getsockopt(sk, optname, optval, optlen); + err = sockopt_init_user(&opt, optval, optlen); + if (err) + return err; + + err = do_rawv6_getsockopt(sk, optname, &opt); + if (err) + return err; + + if (put_user(opt.optlen, optlen)) + return -EFAULT; + + return 0; } static int rawv6_ioctl(struct sock *sk, int cmd, int *karg) From 6cd4e6c0449e67661937e046851316fa726a873d Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Wed, 29 Jul 2026 02:29:46 -0700 Subject: [PATCH 0920/1433] ieee802154: convert dgram getsockopt to sockopt_t Continue converting the proto-layer getsockopt callbacks to the sockopt_t interface, splitting dgram_getsockopt() into a do_dgram_getsockopt() helper that takes a sockopt_t. No functional change. Reviewed-by: Joe Damato Acked-by: Stanislav Fomichev Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260729-getsockopt_phase4-v4-3-c44576757c17@debian.org Signed-off-by: Jakub Kicinski --- net/ieee802154/socket.c | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/net/ieee802154/socket.c b/net/ieee802154/socket.c index 85dce296d751..5a36e87893f6 100644 --- a/net/ieee802154/socket.c +++ b/net/ieee802154/socket.c @@ -831,20 +831,12 @@ static int ieee802154_dgram_deliver(struct net_device *dev, struct sk_buff *skb) return ret; } -static int dgram_getsockopt(struct sock *sk, int level, int optname, - char __user *optval, int __user *optlen) +static int do_dgram_getsockopt(struct sock *sk, int optname, sockopt_t *opt) { struct dgram_sock *ro = dgram_sk(sk); - int val, len; - if (level != SOL_IEEE802154) - return -EOPNOTSUPP; - - if (get_user(len, optlen)) - return -EFAULT; - - len = min_t(unsigned int, len, sizeof(int)); + len = umin(sizeof(int), opt->optlen); switch (optname) { case WPAN_WANTACK: @@ -871,10 +863,32 @@ static int dgram_getsockopt(struct sock *sk, int level, int optname, return -ENOPROTOOPT; } - if (put_user(len, optlen)) + opt->optlen = len; + if (copy_to_iter(&val, len, &opt->iter_out) != len) return -EFAULT; - if (copy_to_user(optval, &val, len)) + return 0; +} + +static int dgram_getsockopt(struct sock *sk, int level, int optname, + char __user *optval, int __user *optlen) +{ + sockopt_t opt; + int err; + + if (level != SOL_IEEE802154) + return -EOPNOTSUPP; + + err = sockopt_init_user(&opt, optval, optlen); + if (err) + return err; + + err = do_dgram_getsockopt(sk, optname, &opt); + if (err) + return err; + + if (put_user(opt.optlen, optlen)) return -EFAULT; + return 0; } From 77e5eb0e192aec6710c03ca8144582fd2af36ca4 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Wed, 29 Jul 2026 02:29:47 -0700 Subject: [PATCH 0921/1433] phonet: pep: do not write beyond optlen in getsockopt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pep_getsockopt() clamps the reported length to the caller's buffer with min_t(), but then stores the value with put_user(val, (int __user *) optval), which always writes sizeof(int) bytes. A getsockopt() call with an optlen smaller than sizeof(int) thus reports the clamped length yet writes a full int, one to three bytes past the user buffer. Write the value with copy_to_user() bounded by len, so at most optlen bytes are copied, matching the length reported back to userspace. Fixes: 02a47617cdce ("Phonet: implement GPRS virtual interface over PEP socket") Acked-by: Rémi Denis-Courmont Reviewed-by: Joe Damato Acked-by: Stanislav Fomichev Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260729-getsockopt_phase4-v4-4-c44576757c17@debian.org Signed-off-by: Jakub Kicinski --- net/phonet/pep.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/phonet/pep.c b/net/phonet/pep.c index 31b29e3ca7bc..e188c563ee5b 100644 --- a/net/phonet/pep.c +++ b/net/phonet/pep.c @@ -1117,7 +1117,7 @@ static int pep_getsockopt(struct sock *sk, int level, int optname, len = min_t(unsigned int, sizeof(int), len); if (put_user(len, optlen)) return -EFAULT; - if (put_user(val, (int __user *) optval)) + if (copy_to_user(optval, &val, len)) return -EFAULT; return 0; } From d05a7f0ab5508630eed5735ce3cf0cee729ab1c9 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Wed, 29 Jul 2026 02:29:48 -0700 Subject: [PATCH 0922/1433] phonet: pep: convert getsockopt to sockopt_t MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Continue converting the proto-layer getsockopt callbacks to the sockopt_t interface, splitting pep_getsockopt() into a do_pep_getsockopt() helper that takes a sockopt_t. The thin pep_getsockopt() wrapper keeps its __user signature for now: it builds a user-backed sockopt_t with sockopt_init_user(), calls the helper, and writes the returned length back to optlen. The helper uses copy_to_iter() instead of copy_to_user(). No functional change. Acked-by: Rémi Denis-Courmont Reviewed-by: Joe Damato Acked-by: Stanislav Fomichev Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260729-getsockopt_phase4-v4-5-c44576757c17@debian.org Signed-off-by: Jakub Kicinski --- net/phonet/pep.c | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/net/phonet/pep.c b/net/phonet/pep.c index e188c563ee5b..bd1cdd00edfa 100644 --- a/net/phonet/pep.c +++ b/net/phonet/pep.c @@ -1080,17 +1080,11 @@ static int pep_setsockopt(struct sock *sk, int level, int optname, return err; } -static int pep_getsockopt(struct sock *sk, int level, int optname, - char __user *optval, int __user *optlen) +static int do_pep_getsockopt(struct sock *sk, int optname, sockopt_t *opt) { struct pep_sock *pn = pep_sk(sk); int len, val; - if (level != SOL_PNPIPE) - return -ENOPROTOOPT; - if (get_user(len, optlen)) - return -EFAULT; - switch (optname) { case PNPIPE_ENCAP: val = pn->ifindex ? PNPIPE_ENCAP_IP : PNPIPE_ENCAP_NONE; @@ -1114,11 +1108,33 @@ static int pep_getsockopt(struct sock *sk, int level, int optname, return -ENOPROTOOPT; } - len = min_t(unsigned int, sizeof(int), len); - if (put_user(len, optlen)) + len = umin(sizeof(int), opt->optlen); + opt->optlen = len; + if (copy_to_iter(&val, len, &opt->iter_out) != len) return -EFAULT; - if (copy_to_user(optval, &val, len)) + return 0; +} + +static int pep_getsockopt(struct sock *sk, int level, int optname, + char __user *optval, int __user *optlen) +{ + sockopt_t opt; + int err; + + if (level != SOL_PNPIPE) + return -ENOPROTOOPT; + + err = sockopt_init_user(&opt, optval, optlen); + if (err) + return err; + + err = do_pep_getsockopt(sk, optname, &opt); + if (err) + return err; + + if (put_user(opt.optlen, optlen)) return -EFAULT; + return 0; } From c7049902075a33842bc271da0e57e280bfa22996 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Wed, 29 Jul 2026 02:29:49 -0700 Subject: [PATCH 0923/1433] tls: convert getsockopt to sockopt_t Continue converting the proto-layer getsockopt callbacks to the sockopt_t interface, converting do_tls_getsockopt() and its per-option helpers to take a sockopt_t. The thin tls_getsockopt() wrapper keeps its __user signature for now: it builds a user-backed sockopt_t with sockopt_init_user(), calls the helper, and writes the returned length back to optlen. The helpers use copy_to_iter() instead of copy_to_user(); the NULL optval check in the TLS_TX/TLS_RX path is preserved by testing the iterator user buffer. No functional change. Reviewed-by: Sabrina Dubroca Reviewed-by: Joe Damato Acked-by: Stanislav Fomichev Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260729-getsockopt_phase4-v4-6-c44576757c17@debian.org Signed-off-by: Jakub Kicinski --- net/tls/tls_main.c | 80 ++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 42 deletions(-) diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c index 8c588cdab733..fbb274287aa5 100644 --- a/net/tls/tls_main.c +++ b/net/tls/tls_main.c @@ -424,20 +424,16 @@ static __poll_t tls_sk_poll(struct file *file, struct socket *sock, return mask; } -static int do_tls_getsockopt_conf(struct sock *sk, char __user *optval, - int __user *optlen, int tx) +static int do_tls_getsockopt_conf(struct sock *sk, sockopt_t *opt, int tx) { int rc = 0; const struct tls_cipher_desc *cipher_desc; struct tls_context *ctx = tls_get_ctx(sk); struct tls_crypto_info *crypto_info; struct cipher_context *cctx; - int len; + int len = opt->optlen; - if (get_user(len, optlen)) - return -EFAULT; - - if (!optval || (len < sizeof(*crypto_info))) { + if (!opt->iter_out.ubuf || len < sizeof(*crypto_info)) { rc = -EINVAL; goto out; } @@ -462,7 +458,8 @@ static int do_tls_getsockopt_conf(struct sock *sk, char __user *optval, } if (len == sizeof(*crypto_info)) { - if (copy_to_user(optval, crypto_info, sizeof(*crypto_info))) + if (copy_to_iter(crypto_info, sizeof(*crypto_info), + &opt->iter_out) != sizeof(*crypto_info)) rc = -EFAULT; goto out; } @@ -478,44 +475,38 @@ static int do_tls_getsockopt_conf(struct sock *sk, char __user *optval, memcpy(crypto_info_rec_seq(crypto_info, cipher_desc), cctx->rec_seq, cipher_desc->rec_seq); - if (copy_to_user(optval, crypto_info, cipher_desc->crypto_info)) + if (copy_to_iter(crypto_info, cipher_desc->crypto_info, + &opt->iter_out) != cipher_desc->crypto_info) rc = -EFAULT; out: return rc; } -static int do_tls_getsockopt_tx_zc(struct sock *sk, char __user *optval, - int __user *optlen) +static int do_tls_getsockopt_tx_zc(struct sock *sk, sockopt_t *opt) { struct tls_context *ctx = tls_get_ctx(sk); unsigned int value; - int len; - - if (get_user(len, optlen)) - return -EFAULT; + int len = opt->optlen; if (len != sizeof(value)) return -EINVAL; value = ctx->zerocopy_sendfile; - if (copy_to_user(optval, &value, sizeof(value))) + if (copy_to_iter(&value, sizeof(value), &opt->iter_out) != sizeof(value)) return -EFAULT; return 0; } -static int do_tls_getsockopt_no_pad(struct sock *sk, char __user *optval, - int __user *optlen) +static int do_tls_getsockopt_no_pad(struct sock *sk, sockopt_t *opt) { struct tls_context *ctx = tls_get_ctx(sk); - int value, len; + int value, len = opt->optlen; if (ctx->prot_info.version != TLS_1_3_VERSION) return -EINVAL; - if (get_user(len, optlen)) - return -EFAULT; if (len < sizeof(value)) return -EINVAL; @@ -525,38 +516,31 @@ static int do_tls_getsockopt_no_pad(struct sock *sk, char __user *optval, if (value < 0) return value; - if (put_user(sizeof(value), optlen)) - return -EFAULT; - if (copy_to_user(optval, &value, sizeof(value))) + opt->optlen = sizeof(value); + if (copy_to_iter(&value, sizeof(value), &opt->iter_out) != sizeof(value)) return -EFAULT; return 0; } -static int do_tls_getsockopt_tx_payload_len(struct sock *sk, char __user *optval, - int __user *optlen) +static int do_tls_getsockopt_tx_payload_len(struct sock *sk, sockopt_t *opt) { struct tls_context *ctx = tls_get_ctx(sk); u16 payload_len = ctx->tx_max_payload_len; - int len; - - if (get_user(len, optlen)) - return -EFAULT; + int len = opt->optlen; if (len < sizeof(payload_len)) return -EINVAL; - if (put_user(sizeof(payload_len), optlen)) - return -EFAULT; - - if (copy_to_user(optval, &payload_len, sizeof(payload_len))) + opt->optlen = sizeof(payload_len); + if (copy_to_iter(&payload_len, sizeof(payload_len), + &opt->iter_out) != sizeof(payload_len)) return -EFAULT; return 0; } -static int do_tls_getsockopt(struct sock *sk, int optname, - char __user *optval, int __user *optlen) +static int do_tls_getsockopt(struct sock *sk, int optname, sockopt_t *opt) { int rc = 0; @@ -565,17 +549,16 @@ static int do_tls_getsockopt(struct sock *sk, int optname, switch (optname) { case TLS_TX: case TLS_RX: - rc = do_tls_getsockopt_conf(sk, optval, optlen, - optname == TLS_TX); + rc = do_tls_getsockopt_conf(sk, opt, optname == TLS_TX); break; case TLS_TX_ZEROCOPY_RO: - rc = do_tls_getsockopt_tx_zc(sk, optval, optlen); + rc = do_tls_getsockopt_tx_zc(sk, opt); break; case TLS_RX_EXPECT_NO_PAD: - rc = do_tls_getsockopt_no_pad(sk, optval, optlen); + rc = do_tls_getsockopt_no_pad(sk, opt); break; case TLS_TX_MAX_PAYLOAD_LEN: - rc = do_tls_getsockopt_tx_payload_len(sk, optval, optlen); + rc = do_tls_getsockopt_tx_payload_len(sk, opt); break; default: rc = -ENOPROTOOPT; @@ -591,12 +574,25 @@ static int tls_getsockopt(struct sock *sk, int level, int optname, char __user *optval, int __user *optlen) { struct tls_context *ctx = tls_get_ctx(sk); + sockopt_t opt; + int err; if (level != SOL_TLS) return ctx->sk_proto->getsockopt(sk, level, optname, optval, optlen); - return do_tls_getsockopt(sk, optname, optval, optlen); + err = sockopt_init_user(&opt, optval, optlen); + if (err) + return err; + + err = do_tls_getsockopt(sk, optname, &opt); + if (err) + return err; + + if (put_user(opt.optlen, optlen)) + return -EFAULT; + + return 0; } static int validate_crypto_info(const struct tls_crypto_info *crypto_info, From 6b21a3ac84c9ea2c6f9731d0ef480da74461313b Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Wed, 29 Jul 2026 02:29:50 -0700 Subject: [PATCH 0924/1433] selftests: net: getsockopt_iter: cover rawv6 and tls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add fixtures for the newly converted getsockopt leaves: - rawv6: IPV6_HDRINCL / IPV6_CHECKSUM int paths + a SOL_RAW unknown-optname case that reaches do_rawv6_getsockopt(). - tls: TLS_TX_ZEROCOPY_RO, the TLS_TX crypto_info round-trip at the base and full cipher sizes, the NULL-optval and short buffer EINVAL paths, and an unknown optname. It skips when the kernel lacks TLS or AES-GCM. Each fixture pins the returned-length / errno semantics across exact, oversized and short buffers and an unknown optname. The semantics are unchanged by the sockopt_t conversion, so the tests pass both before and after the leaf conversions. ieee802154 and phonet are not covered: their CONFIG options are absent from the net selftest target config, so the cases would only ever skip. Acked-by: Rémi Denis-Courmont Reviewed-by: Sabrina Dubroca Reviewed-by: Joe Damato Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260729-getsockopt_phase4-v4-7-c44576757c17@debian.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/getsockopt_iter.c | 239 ++++++++++++++++++ 1 file changed, 239 insertions(+) diff --git a/tools/testing/selftests/net/getsockopt_iter.c b/tools/testing/selftests/net/getsockopt_iter.c index fe5a5268bc34..6c2408df4612 100644 --- a/tools/testing/selftests/net/getsockopt_iter.c +++ b/tools/testing/selftests/net/getsockopt_iter.c @@ -28,7 +28,10 @@ #include #include #include +#include +#include #include +#include #include "kselftest_harness.h" #ifndef AF_VSOCK @@ -40,6 +43,18 @@ #ifndef ICMP_FILTER #define ICMP_FILTER 1 #endif +#ifndef IPV6_HDRINCL +#define IPV6_HDRINCL 36 +#endif +#ifndef IPV6_CHECKSUM +#define IPV6_CHECKSUM 7 +#endif +#ifndef SOL_TLS +#define SOL_TLS 282 +#endif +#ifndef TCP_ULP +#define TCP_ULP 31 +#endif /* ---------- netlink ---------- */ @@ -394,4 +409,228 @@ TEST_F(raw, bad_optname) ASSERT_EQ(sizeof(val), optlen); } +/* ---------- raw (ipv6) ---------- */ + +FIXTURE(rawv6) +{ + int fd; +}; + +FIXTURE_SETUP(rawv6) +{ + self->fd = socket(AF_INET6, SOCK_RAW, IPPROTO_UDP); + if (self->fd < 0) + SKIP(return, "SOCK_RAW/IPv6 socket: %s", strerror(errno)); +} + +FIXTURE_TEARDOWN(rawv6) +{ + if (self->fd >= 0) + close(self->fd); +} + +TEST_F(rawv6, hdrincl_exact) +{ + socklen_t optlen; + int val = -1; + + optlen = sizeof(val); + + ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_IPV6, IPV6_HDRINCL, + &val, &optlen)); + ASSERT_EQ(sizeof(int), optlen); + ASSERT_TRUE(val == 0 || val == 1); +} + +TEST_F(rawv6, hdrincl_oversize_clamped) +{ + char buf[16] = {}; + socklen_t optlen = sizeof(buf); + + ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_IPV6, IPV6_HDRINCL, + buf, &optlen)); + ASSERT_EQ(sizeof(int), optlen); +} + +/* Raw int options clamp the reported length down to the user buffer + * instead of returning EINVAL on a short buffer. + */ +TEST_F(rawv6, hdrincl_undersize_clamped) +{ + socklen_t optlen = 2; + int val = 0; + + ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_IPV6, IPV6_HDRINCL, + &val, &optlen)); + ASSERT_EQ(2, optlen); +} + +TEST_F(rawv6, checksum_default) +{ + socklen_t optlen; + int val = 0; + + optlen = sizeof(val); + + /* A non-ICMPv6 raw socket has the checksum disabled, reported as -1. */ + ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_IPV6, IPV6_CHECKSUM, + &val, &optlen)); + ASSERT_EQ(sizeof(int), optlen); + ASSERT_EQ(-1, val); +} + +TEST_F(rawv6, bad_optname) +{ + socklen_t optlen; + int val; + + optlen = sizeof(val); + + /* SOL_RAW reaches do_rawv6_getsockopt() directly. */ + ASSERT_EQ(-1, getsockopt(self->fd, SOL_RAW, 0x7fff, &val, &optlen)); + ASSERT_EQ(ENOPROTOOPT, errno); + ASSERT_EQ(sizeof(val), optlen); +} + +/* ---------- tls ---------- */ + +FIXTURE(tls) +{ + int fd; + int sfd; +}; + +FIXTURE_SETUP(tls) +{ + struct sockaddr_in a = { + .sin_family = AF_INET, + .sin_addr.s_addr = htonl(INADDR_LOOPBACK), + }; + socklen_t alen = sizeof(a); + int lfd; + + self->fd = -1; + self->sfd = -1; + + lfd = socket(AF_INET, SOCK_STREAM, 0); + if (lfd < 0) + SKIP(return, "TCP socket: %s", strerror(errno)); + if (bind(lfd, (struct sockaddr *)&a, sizeof(a)) || listen(lfd, 1) || + getsockname(lfd, (struct sockaddr *)&a, &alen)) { + close(lfd); + SKIP(return, "listener setup: %s", strerror(errno)); + } + self->fd = socket(AF_INET, SOCK_STREAM, 0); + if (self->fd < 0) { + close(lfd); + SKIP(return, "TCP socket: %s", strerror(errno)); + } + if (connect(self->fd, (struct sockaddr *)&a, sizeof(a))) { + close(lfd); + SKIP(return, "connect: %s", strerror(errno)); + } + self->sfd = accept(lfd, NULL, NULL); + close(lfd); + if (setsockopt(self->fd, IPPROTO_TCP, TCP_ULP, "tls", sizeof("tls"))) + SKIP(return, "TCP_ULP=tls: %s (built without TLS?)", + strerror(errno)); +} + +FIXTURE_TEARDOWN(tls) +{ + if (self->fd >= 0) + close(self->fd); + if (self->sfd >= 0) + close(self->sfd); +} + +/* do_tls_getsockopt_tx_zc(): fixed-size int, exact length required. */ +TEST_F(tls, tx_zerocopy_exact) +{ + socklen_t optlen = sizeof(int); + int val = -1; + + ASSERT_EQ(0, getsockopt(self->fd, SOL_TLS, TLS_TX_ZEROCOPY_RO, + &val, &optlen)); + ASSERT_EQ(sizeof(int), optlen); + ASSERT_TRUE(val == 0 || val == 1); +} + +TEST_F(tls, tx_zerocopy_wrong_len) +{ + socklen_t optlen = 2; + int val; + + ASSERT_EQ(-1, getsockopt(self->fd, SOL_TLS, TLS_TX_ZEROCOPY_RO, + &val, &optlen)); + ASSERT_EQ(EINVAL, errno); +} + +/* do_tls_getsockopt_conf(): NULL optval still yields EINVAL -- the + * converted code tests opt->iter_out.ubuf in place of optval. + */ +TEST_F(tls, conf_null_optval) +{ + socklen_t optlen = 64; + + ASSERT_EQ(-1, getsockopt(self->fd, SOL_TLS, TLS_TX, NULL, &optlen)); + ASSERT_EQ(EINVAL, errno); +} + +TEST_F(tls, conf_short) +{ + socklen_t optlen = 2; + char buf[2]; + + ASSERT_EQ(-1, getsockopt(self->fd, SOL_TLS, TLS_TX, buf, &optlen)); + ASSERT_EQ(EINVAL, errno); +} + +/* TLS_TX before crypto is set reports not-ready. */ +TEST_F(tls, conf_not_ready) +{ + struct tls_crypto_info info; + socklen_t optlen = sizeof(info); + + ASSERT_EQ(-1, getsockopt(self->fd, SOL_TLS, TLS_TX, &info, &optlen)); + ASSERT_EQ(EBUSY, errno); +} + +/* Set TX crypto, then read it back at the base and full sizes, exercising + * both copy_to_iter() branches. SKIP if AES-GCM is unavailable. + */ +TEST_F(tls, conf_crypto_roundtrip) +{ + struct tls12_crypto_info_aes_gcm_128 tx = { + .info.version = TLS_1_2_VERSION, + .info.cipher_type = TLS_CIPHER_AES_GCM_128, + }; + struct tls12_crypto_info_aes_gcm_128 full; + struct tls_crypto_info base; + socklen_t optlen; + + if (setsockopt(self->fd, SOL_TLS, TLS_TX, &tx, sizeof(tx))) + SKIP(return, "set TLS_TX aes_gcm_128: %s", strerror(errno)); + + optlen = sizeof(base); + ASSERT_EQ(0, getsockopt(self->fd, SOL_TLS, TLS_TX, &base, &optlen)); + ASSERT_EQ(sizeof(base), optlen); + ASSERT_EQ(TLS_1_2_VERSION, base.version); + ASSERT_EQ(TLS_CIPHER_AES_GCM_128, base.cipher_type); + + optlen = sizeof(full); + ASSERT_EQ(0, getsockopt(self->fd, SOL_TLS, TLS_TX, &full, &optlen)); + ASSERT_EQ(sizeof(full), optlen); + ASSERT_EQ(TLS_CIPHER_AES_GCM_128, full.info.cipher_type); +} + +TEST_F(tls, bad_optname) +{ + socklen_t optlen = sizeof(int); + int val; + + ASSERT_EQ(-1, getsockopt(self->fd, SOL_TLS, 0x7fff, &val, &optlen)); + ASSERT_EQ(ENOPROTOOPT, errno); +} + TEST_HARNESS_MAIN From 1528af830077ad6640126385222921d90682f41d Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Tue, 28 Jul 2026 17:57:26 +0200 Subject: [PATCH 0925/1433] net: stmmac: Don't use PHY loopback for selftests Stmmac selftests validate the internal behaviour of the various IPs, using local loopback. The current logic is relies on PHY-side local loopback if a PHY is attached, with a fallback to MAC loopback otherwise. However, PHY loopback is currently fragile especially for stmmac that may require RXC to be provided from the PHY. Some PHYs shutdown RXC while in loopback, while others will report carrier off when in local loopback. This also fails when using SFP setup with a module that embeds a PHY, that may also fail to enter loopback. MAC loopback is done at the GMII level on dwmac, allowing the internal to be just as meaningful as PHY-loopback testing. Let's simplify stmmac selftests by only relying on MAC-side local loopback, which makes the selftests runnable on a wider HW variety. Signed-off-by: Maxime Chevallier Link: https://patch.msgid.link/20260728155728.1193169-2-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- .../stmicro/stmmac/stmmac_selftests.c | 112 ++---------------- 1 file changed, 9 insertions(+), 103 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index 29e824bd90ca..c0e5dee86451 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -374,25 +374,6 @@ static int stmmac_test_mac_loopback(struct stmmac_priv *priv) return __stmmac_test_loopback(priv, &attr); } -static int stmmac_test_phy_loopback(struct stmmac_priv *priv) -{ - struct stmmac_packet_attrs attr = { }; - int ret; - - if (!priv->dev->phydev) - return -EOPNOTSUPP; - - ret = phy_loopback(priv->dev->phydev, true, 0); - if (ret) - return ret; - - attr.dst = priv->dev->dev_addr; - ret = __stmmac_test_loopback(priv, &attr); - - phy_loopback(priv->dev->phydev, false, 0); - return ret; -} - static int stmmac_test_mmc(struct stmmac_priv *priv) { struct stmmac_counters initial, final; @@ -1815,10 +1796,6 @@ static int stmmac_test_tbs(struct stmmac_priv *priv) return ret; } -#define STMMAC_LOOPBACK_NONE 0 -#define STMMAC_LOOPBACK_MAC 1 -#define STMMAC_LOOPBACK_PHY 2 - static const struct stmmac_test { char name[ETH_GSTRING_LEN]; int lb; @@ -1826,131 +1803,96 @@ static const struct stmmac_test { } stmmac_selftests[] = { { .name = "MAC Loopback ", - .lb = STMMAC_LOOPBACK_MAC, .fn = stmmac_test_mac_loopback, - }, { - .name = "PHY Loopback ", - .lb = STMMAC_LOOPBACK_NONE, /* Test will handle it */ - .fn = stmmac_test_phy_loopback, }, { .name = "MMC Counters ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_mmc, }, { .name = "EEE ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_eee, }, { .name = "Hash Filter MC ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_hfilt, }, { .name = "Perfect Filter UC ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_pfilt, }, { .name = "MC Filter ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_mcfilt, }, { .name = "UC Filter ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_ucfilt, }, { .name = "Flow Control ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_flowctrl, }, { .name = "RSS ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_rss, }, { .name = "VLAN Filtering ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_vlanfilt, }, { .name = "VLAN Filtering (perf) ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_vlanfilt_perfect, }, { .name = "Double VLAN Filter ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_dvlanfilt, }, { .name = "Double VLAN Filter (perf) ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_dvlanfilt_perfect, }, { .name = "Flexible RX Parser ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_rxp, }, { .name = "SA Insertion (desc) ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_desc_sai, }, { .name = "SA Replacement (desc) ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_desc_sar, }, { .name = "SA Insertion (reg) ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_reg_sai, }, { .name = "SA Replacement (reg) ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_reg_sar, }, { .name = "VLAN TX Insertion ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_vlanoff, }, { .name = "SVLAN TX Insertion ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_svlanoff, }, { .name = "L3 DA Filtering ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_l3filt_da, }, { .name = "L3 SA Filtering ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_l3filt_sa, }, { .name = "L4 DA TCP Filtering ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_l4filt_da_tcp, }, { .name = "L4 SA TCP Filtering ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_l4filt_sa_tcp, }, { .name = "L4 DA UDP Filtering ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_l4filt_da_udp, }, { .name = "L4 SA UDP Filtering ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_l4filt_sa_udp, }, { .name = "ARP Offload ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_arpoffload, }, { .name = "Jumbo Frame ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_jumbo, }, { .name = "Multichannel Jumbo ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_mjumbo, }, { .name = "Split Header ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_sph, }, { .name = "TBS (ETF Scheduler) ", - .lb = STMMAC_LOOPBACK_PHY, .fn = stmmac_test_tbs, }, }; @@ -1978,57 +1920,21 @@ void stmmac_selftest_run(struct net_device *dev, /* Wait for queues drain */ msleep(200); + ret = stmmac_set_mac_loopback(priv, priv->ioaddr, true); + if (ret) { + netdev_err(priv->dev, "Loopback is not supported\n"); + etest->flags |= ETH_TEST_FL_FAILED; + return; + } + for (i = 0; i < count; i++) { - ret = 0; - - switch (stmmac_selftests[i].lb) { - case STMMAC_LOOPBACK_PHY: - ret = -EOPNOTSUPP; - if (dev->phydev) - ret = phy_loopback(dev->phydev, true, 0); - if (!ret) - break; - fallthrough; - case STMMAC_LOOPBACK_MAC: - ret = stmmac_set_mac_loopback(priv, priv->ioaddr, true); - break; - case STMMAC_LOOPBACK_NONE: - break; - default: - ret = -EOPNOTSUPP; - break; - } - - /* - * First tests will always be MAC / PHY loopback. If any of - * them is not supported we abort earlier. - */ - if (ret) { - netdev_err(priv->dev, "Loopback is not supported\n"); - etest->flags |= ETH_TEST_FL_FAILED; - break; - } - ret = stmmac_selftests[i].fn(priv); if (ret && (ret != -EOPNOTSUPP)) etest->flags |= ETH_TEST_FL_FAILED; buf[i] = ret; - - switch (stmmac_selftests[i].lb) { - case STMMAC_LOOPBACK_PHY: - ret = -EOPNOTSUPP; - if (dev->phydev) - ret = phy_loopback(dev->phydev, false, 0); - if (!ret) - break; - fallthrough; - case STMMAC_LOOPBACK_MAC: - stmmac_set_mac_loopback(priv, priv->ioaddr, false); - break; - default: - break; - } } + + stmmac_set_mac_loopback(priv, priv->ioaddr, false); } void stmmac_selftest_get_strings(struct stmmac_priv *priv, u8 *data) From 48806dbee0c256406a7998fa16edb9a4c6da9982 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Tue, 28 Jul 2026 17:57:27 +0200 Subject: [PATCH 0926/1433] net: stmmac: Don't rely on the PHY for flow-control testing For flow-control testing in loopback mode, we don't need to ask what the PHY is currently using as pause/asym settings. The PHY is no longer involved in selftest, we rely strictly on MAC loopback. We therefore only need to know if the MAC supports Symmetric pause for the test, as we exercise both TX and RX pause support in the selftest. Remove phydev requirement for flowcontrol selftest as well as the AsymPause requirement. With that, we can also drop the linux/phy.h include. Signed-off-by: Maxime Chevallier Reviewed-by: Oleksij Rempel Link: https://patch.msgid.link/20260728155728.1193169-3-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index c0e5dee86451..1df26c217f9a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -716,13 +715,13 @@ static int stmmac_test_flowctrl_validate(struct sk_buff *skb, static int stmmac_test_flowctrl(struct stmmac_priv *priv) { unsigned char paddr[ETH_ALEN] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x01}; - struct phy_device *phydev = priv->dev->phydev; u32 rx_cnt = priv->plat->rx_queues_to_use; + struct mac_device_info *mac = priv->hw; struct stmmac_test_priv *tpriv; unsigned int pkt_count; int i, ret = 0; - if (!phydev || (!phydev->pause && !phydev->asym_pause)) + if (!(mac->link.caps & MAC_SYM_PAUSE)) return -EOPNOTSUPP; tpriv = kzalloc_obj(*tpriv); From dc3b7209b5d32194b9088fb40f1787130f34ea5e Mon Sep 17 00:00:00 2001 From: Jiaxing Hu Date: Fri, 31 Jul 2026 13:38:07 +1200 Subject: [PATCH 0927/1433] net: phy: motorcomm: enable the reference clock for YT8521 Commit 42310a24389c ("net: phy: motorcomm: Enable optional clock for YT8531") enables the SoC-provided reference clock for the YT8531 in its probe. The YT8521 has the same need on crystal-less boards but goes through yt8521_probe(), so enable it there too. The clock is optional, so crystal-clocked boards are unaffected. Reviewed-by: Andrew Lunn Tested-by: Gavin Gao Signed-off-by: Jiaxing Hu Link: https://patch.msgid.link/20260731013807.1488843-1-gahing@gahingwoo.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/motorcomm.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/phy/motorcomm.c b/drivers/net/phy/motorcomm.c index 3396a38cfc0f..c5a2cda8d31b 100644 --- a/drivers/net/phy/motorcomm.c +++ b/drivers/net/phy/motorcomm.c @@ -1065,6 +1065,7 @@ static int yt8521_probe(struct phy_device *phydev) { struct device *dev = &phydev->mdio.dev; struct yt8521_priv *priv; + struct clk *clk; int chip_config; u16 mask, val; u32 freq; @@ -1076,6 +1077,11 @@ static int yt8521_probe(struct phy_device *phydev) phydev->priv = priv; + clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "failed to get and enable PHY clock\n"); + chip_config = ytphy_read_ext_with_lock(phydev, YT8521_CHIP_CONFIG_REG); if (chip_config < 0) return chip_config; From c98610c2eb7002436f6fca55b21c63200644549a Mon Sep 17 00:00:00 2001 From: Mohsin Bashir Date: Thu, 30 Jul 2026 19:15:42 -0700 Subject: [PATCH 0928/1433] selftests: drv-net: Test queue stall upon reconfig Add a reconfig_tx_stall test that detects the possibility of a TX stall after ring reconfiguration. The key observation is that drivers using netif_tx_start_all_queues() are prone to experiencing a stall when reconfiguration completes compared to drivers using netif_tx_wake_all_queues(). start_all_queues only clears DRV_XOFF, while wake_all_queues also calls __netif_schedule() to kick the qdisc. Without the kick, qdisc backlog present at reconfig time can stay stuck until a new trigger is issued. The test caps the TX ring at 64 entries so it fills quickly, then installs FQ on a target TX queue and sends UDP packets with SO_TXTIME scheduled in the future. With napi_defer_hard_irqs slowing completions, the small ring can fill when FQ releases the burst, leaving requeued qdisc backlog with no FQ timer to rescue it. A subsequent ring reconfig must wake the queues to drain the backlog. Simply starting the queues can leave it stuck. Some drivers lack backpressure on the TX path and may not be able to build up the qdisc backlog the test relies on. In that case report an expected failure (xfail) instead of a hard failure. Testing on some of the existing drivers: Driver-A does not have the bug, Driver-B has the bug, Driver-C had the bug but it is fixed now. Driver-A: ./drivers/net/ring_reconfig.py -t reconfig_tx_stall TAP version 13 1..1 Sent 1024 SO_TXTIME packets (+100ms) Backlog before reconfig: 1176378 bytes ok 1 ring_reconfig.reconfig_tx_stall Totals: pass:1 fail:0 xfail:0 xpass:0 skip:0 error:0 Driver-B: TAP version 13 1..1 Sent 128 SO_TXTIME packets (+100ms) Sent 128 SO_TXTIME packets (+200ms) Backlog before reconfig: 148372 bytes Check| At ./drivers/net/ring_reconfig.py, line 397, in reconfig_tx_stall: Check| ksft_eq(0, backlog, Check failed 0 != 148372 qdisc backlog stuck on queue 1 after ring .... not ok 1 ring_reconfig.reconfig_tx_stall Totals: pass:0 fail:1 xfail:0 xpass:0 skip:0 error:0 Driver-C: TAP version 13 1..1 Sent 128 SO_TXTIME packets (+100ms) Backlog before reconfig: 192278 bytes ok 1 ring_reconfig.reconfig_tx_stall Totals: pass:1 fail:0 xfail:0 xpass:0 skip:0 error:0 Signed-off-by: Mohsin Bashir Link: https://patch.msgid.link/20260731021543.1058526-1-mohsin.bashr@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/config | 3 + .../selftests/drivers/net/lib/py/env.py | 11 +- .../selftests/drivers/net/ring_reconfig.py | 255 +++++++++++++++++- 3 files changed, 263 insertions(+), 6 deletions(-) diff --git a/tools/testing/selftests/drivers/net/config b/tools/testing/selftests/drivers/net/config index 2070e890e064..f3933cf3e6be 100644 --- a/tools/testing/selftests/drivers/net/config +++ b/tools/testing/selftests/drivers/net/config @@ -4,8 +4,11 @@ CONFIG_DEBUG_INFO_BTF_MODULES=n CONFIG_INET_PSP=y CONFIG_IPV6=y CONFIG_MACSEC=m +CONFIG_NET_ACT_SKBEDIT=m CONFIG_NET_CLS_ACT=y CONFIG_NET_CLS_BPF=y +CONFIG_NET_CLS_FLOWER=m +CONFIG_NET_CLS_MATCHALL=m CONFIG_NETCONSOLE=m CONFIG_NETCONSOLE_DYNAMIC=y CONFIG_NETCONSOLE_EXTENDED_LOG=y diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py index e4acf3d8333f..b1c6f6cef7dd 100644 --- a/tools/testing/selftests/drivers/net/lib/py/env.py +++ b/tools/testing/selftests/drivers/net/lib/py/env.py @@ -114,10 +114,11 @@ class NetDrvEpEnv(NetDrvEnvBase): nsim_v4_pfx = "192.0.2." nsim_v6_pfx = "2001:db8::" - def __init__(self, src_path, nsim_test=None): + def __init__(self, src_path, nsim_test=None, queue_count=None): super().__init__(src_path) self._stats_settle_time = None + self._queue_count = queue_count # Things we try to destroy self.remote = None @@ -173,9 +174,13 @@ class NetDrvEpEnv(NetDrvEnvBase): self._required_cmd = {} def create_local(self): + nsim_kwargs = {} + if self._queue_count: + nsim_kwargs["queue_count"] = self._queue_count + self._netns = NetNS() - self._ns = NetdevSimDev() - self._ns_peer = NetdevSimDev(ns=self._netns) + self._ns = NetdevSimDev(**nsim_kwargs) + self._ns_peer = NetdevSimDev(ns=self._netns, **nsim_kwargs) with open("/proc/self/ns/net") as nsfd0, \ open("/var/run/netns/" + self._netns.name) as nsfd1: diff --git a/tools/testing/selftests/drivers/net/ring_reconfig.py b/tools/testing/selftests/drivers/net/ring_reconfig.py index f9530a8b0856..2bc329b77134 100755 --- a/tools/testing/selftests/drivers/net/ring_reconfig.py +++ b/tools/testing/selftests/drivers/net/ring_reconfig.py @@ -5,10 +5,25 @@ Test channel and ring size configuration via ethtool (-L / -G). """ +import socket +import struct +import time + from lib.py import ksft_run, ksft_exit, ksft_pr from lib.py import ksft_eq +from lib.py import KsftSkipEx, KsftXfailEx from lib.py import NetDrvEpEnv, EthtoolFamily, GenerateTraffic -from lib.py import defer, NlError +from lib.py import cmd, defer, rand_port, tc, NlError + +# Added in Python 3.13; fallback to 61 for x86/ARM/MIPS +SO_TXTIME = getattr(socket, "SO_TXTIME", 61) + +# Not always exported by the socket module; asm-generic value (x86/ARM/MIPS). +SO_SNDBUFFORCE = getattr(socket, "SO_SNDBUFFORCE", 32) + +# TX ring size the test shrinks to so the ring fills quickly. +MIN_TX_RING = 32 +MAX_TX_RING = 1024 def channels(cfg) -> None: @@ -151,14 +166,248 @@ def ringparam(cfg) -> None: GenerateTraffic(cfg).wait_pkts_and_stop(10000) +def _write_file(path, val): + """Write val to a file.""" + with open(path, "w", encoding="utf-8") as fp: + fp.write(str(val)) + + +def _write_sysfs(path, val): + """Write val to a sysfs file, restoring the original value on exit.""" + with open(path, "r", encoding="utf-8") as fp: + orig_val = fp.read().strip() + if str(val) == orig_val: + return + _write_file(path, val) + defer(_write_file, path, orig_val) + + +def _get_qdisc_backlog(cfg, mq_handle, queue): + """Return the qdisc backlog (bytes) for the given TX queue's leaf.""" + target_parent = f"{mq_handle}{queue + 1:x}" + for q in tc(f"-s qdisc show dev {cfg.ifname}", json=True): + if q.get("parent", "") == target_parent: + return q.get("backlog") or 0 + return 0 + + +def _setup_fq_qdisc(cfg, port, target_queue, other_queue, flow_limit): + """Put an fq qdisc on target_queue's leaf and return the mq handle in use. + + We must not disturb the device's existing TX/RX qdisc policy. On a real + NIC the root mq already has an addressable handle, so we leave the root + and every other queue alone and only swap this one leaf, restoring its + original qdisc afterwards. + + @flow_limit raises fq's per-flow packet limit (default 100) so a single + flow can back up more packets than the Tx ring holds and thus overflow it. + """ + qdiscs = tc(f"qdisc show dev {cfg.ifname}", json=True) + root = next((q for q in qdiscs if q.get("root")), None) + + if root and root["kind"] == "mq" and root["handle"] != "0:": + # Addressable mq (previously-configured): touch only the target queue's + # leaf and restore its original qdisc afterwards. + mq_handle = root["handle"] + parent = f"{mq_handle}{target_queue + 1:x}" + orig = next((q for q in qdiscs if q.get("parent") == parent), None) + orig_kind = orig["kind"] if orig else \ + cmd("sysctl -n net.core.default_qdisc").stdout.strip() + defer(tc, f"qdisc replace dev {cfg.ifname} parent {parent} {orig_kind}") + elif root is None or root["kind"] in ("mq", "noqueue"): + # The auto-attached root mq has handle 0: on any device (real or sim), + # which the kernel rejects as a qdisc parent. A 0: handle means the mq + # is the untouched kernel default - no custom child qdiscs can hang off + # an unaddressable parent - so installing a real handle and restoring + # the default mq on exit preserves the device's effective policy. + mq_handle = "1:" + tc(f"qdisc replace dev {cfg.ifname} root handle {mq_handle} mq") + defer(tc, f"qdisc replace dev {cfg.ifname} root mq") + parent = f"{mq_handle}{target_queue + 1:x}" + else: + raise KsftSkipEx(f"root qdisc '{root['kind']}' is not mq; " + "refusing to disturb existing qdisc policy") + + try: + tc(f"qdisc replace dev {cfg.ifname} parent {parent} fq " + f"flow_limit {flow_limit} limit {flow_limit * 2}") + except Exception as exc: + raise KsftSkipEx( + f"fq not available (CONFIG_NET_SCH_FQ): {exc}") from exc + + qdisc_j = tc(f"qdisc show dev {cfg.ifname}", json=True) + has_clsact = any(q['kind'] == 'clsact' for q in qdisc_j) + if not has_clsact: + tc(f"qdisc add dev {cfg.ifname} clsact") + defer(tc, f"qdisc del dev {cfg.ifname} clsact") + + proto = "ipv6" if int(cfg.addr_ipver) == 6 else "ip" + try: + tc(f"filter add dev {cfg.ifname} egress protocol {proto} " + f"pref 1 flower ip_proto udp dst_port {port} " + f"action skbedit queue_mapping {target_queue}") + except Exception as exc: + raise KsftSkipEx("tc flower/act_skbedit not available") from exc + defer(tc, f"filter del dev {cfg.ifname} egress pref 1") + + tc(f"filter add dev {cfg.ifname} egress pref 101 " + f"matchall action skbedit queue_mapping {other_queue}") + defer(tc, f"filter del dev {cfg.ifname} egress pref 101") + + return mq_handle + + +def _create_sotxtime_socket(cfg, sndbuf): + """Create a UDP socket with SO_TXTIME enabled, bound to the test device.""" + sock = socket.socket(socket.AF_INET6 if cfg.addr_ipver == "6" + else socket.AF_INET, socket.SOCK_DGRAM) + try: + sock.setsockopt(socket.SOL_SOCKET, SO_TXTIME, struct.pack("Ii", 1, 0)) + except OSError as exc: + sock.close() + raise KsftSkipEx("SO_TXTIME not supported") from exc + sock.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, + cfg.ifname.encode()) + # Deferred completions keep every in-flight skb charged to the socket, so + # size the send buffer to hold the whole burst. SO_SNDBUFFORCE bypasses + # net.core.wmem_max (the test runs as root). + try: + sock.setsockopt(socket.SOL_SOCKET, SO_SNDBUFFORCE, sndbuf) + except OSError: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, sndbuf) + return sock + + +def _send_sotxtime_burst(cfg, sock, port, count, delay_ns, pkt_size): + """Send count UDP packets scheduled delay_ns ahead using SO_TXTIME.""" + payload = b'\x00' * pkt_size + txtime_ns = time.clock_gettime_ns(time.CLOCK_MONOTONIC) + delay_ns + + ancdata = [(socket.SOL_SOCKET, SO_TXTIME, struct.pack("Q", txtime_ns))] + if int(cfg.addr_ipver) == 6: + dest = (cfg.remote_addr, port, 0, 0) + else: + dest = (cfg.remote_addr, port) + for _ in range(count): + sock.sendmsg([payload], ancdata, 0, dest) + + +def _set_small_tx_ring(cfg, ehdr): + """Set the Tx ring to the smallest size the driver accepts. + + Start at 32 so the ring fills quickly, then grow exponentially (64, + 128, 256, ...) up to 1024. Some drivers enforce a minimum well above 32 + (e.g. bnxt needs a large ring for software UDP segmentation), so raise + the lower bound until the driver accepts it, giving up past 1024. + """ + size = MIN_TX_RING + while size <= MAX_TX_RING: + try: + cfg.eth.rings_set(ehdr | {'tx': size}) + return size + except NlError: + size = size * 2 + continue + raise KsftSkipEx("driver rejects all tx ring sizes up to 1024") + + +def reconfig_tx_stall(cfg) -> None: + """Test that qdisc backlog drains after ring reconfiguration.""" + target_queue = 1 + other_queue = 0 + + ehdr = {'header': {'dev-index': cfg.ifindex}} + chans = cfg.eth.channels_get(ehdr) + + if "combined-max" not in chans: + raise KsftSkipEx("device does not support combined channels") + if chans.get("combined-max", 0) < 2: + raise KsftSkipEx("device does not support 2+ combined channels") + if chans["combined-count"] < 2: + defer(cfg.eth.channels_set, + ehdr | {"combined-count": chans["combined-count"]}) + cfg.eth.channels_set(ehdr | {"combined-count": 2}) + + rings = cfg.eth.rings_get(ehdr) + if 'rx' not in rings or 'tx' not in rings: + raise KsftSkipEx("device does not expose rx/tx ring params") + tx_cur = rings['tx'] + if tx_cur <= MIN_TX_RING: + raise KsftSkipEx("tx ring size already at minimum") + defer(cfg.eth.rings_set, ehdr | {'tx': tx_cur}) + + # Use the smallest Tx ring the driver accepts (32, growing to 1024). + tx_ring = _set_small_tx_ring(cfg, ehdr) + + # Slow completions so the ring stays full after FQ releases packets + napi_defer = f"/sys/class/net/{cfg.ifname}/napi_defer_hard_irqs" + gro_timeout = f"/sys/class/net/{cfg.ifname}/gro_flush_timeout" + _write_sysfs(napi_defer, 100) + _write_sysfs(gro_timeout, 1000000000) + + port = rand_port() + # A single flow must overflow the ring, so send twice the ring depth and + # let fq hold that many packets for the flow. + pkt_count = tx_ring * 2 + mq_handle = _setup_fq_qdisc(cfg, port, target_queue, other_queue, + tx_ring * 2) + + # Size each packet to one MTU (less L3/L4 headers to avoid fragmentation). + pkt_size = cfg.dev['mtu'] - (48 if int(cfg.addr_ipver) == 6 else 28) + + # Each queued skb charges the socket its truesize (~2x the payload), so + # budget the send buffer for the whole in-flight burst. + sock = _create_sotxtime_socket(cfg, pkt_count * pkt_size * 2) + defer(sock.close) + + for delay_ms in [100, 200, 500]: + _send_sotxtime_burst(cfg, sock, port, pkt_count, + delay_ms * 1_000_000, pkt_size) + ksft_pr(f"Sent {pkt_count} SO_TXTIME packets (+{delay_ms}ms)") + time.sleep(delay_ms / 1000 + 0.3) + + backlog = _get_qdisc_backlog(cfg, mq_handle, target_queue) + if backlog: + break + else: + # A device that completes Tx synchronously (e.g. a software/virtual + # driver like netdevsim) never keeps the ring full long enough for a + # backlog to form, so the wake-vs-start behavior can't be exercised. + # Treat that as an expected failure rather than a hard failure. + raise KsftXfailEx("could not build qdisc backlog") + + ksft_pr(f"Backlog before reconfig: {backlog} bytes") + + # Trigger ring reconfig — driver should call wake, not just start. + # Grow back to the original size so the driver actually switches channels + # (setting the current size is a no-op the driver short-circuits). + cfg.eth.rings_set(ehdr | {'tx': tx_cur}) + + # Let completions proceed normally + _write_sysfs(napi_defer, 0) + _write_sysfs(gro_timeout, 0) + + # Poll for backlog to drain + for _ in range(100): + backlog = _get_qdisc_backlog(cfg, mq_handle, target_queue) + if not backlog: + break + time.sleep(0.1) + + ksft_eq(0, backlog, + comment=f"qdisc backlog stuck on queue {target_queue} " + f"after ring reconfig") + + def main() -> None: """ Ksft boiler plate main """ - with NetDrvEpEnv(__file__) as cfg: + with NetDrvEpEnv(__file__, queue_count=2) as cfg: cfg.eth = EthtoolFamily() ksft_run([channels, - ringparam], + ringparam, + reconfig_tx_stall], args=(cfg, )) ksft_exit() From b6a89c8ef34f84488f877e29b14f0c843bfdff51 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Sun, 2 Aug 2026 13:40:13 +0200 Subject: [PATCH 0929/1433] net: stmmac: ethtool: Comment the magic numbers in RIWT computation Receive Interrupt Watchdog Timer is an RX interrupt coalescing mechanism used by some variants of dwmac. It allows waiting a bit before triggering the rx interrupts, allowing for batch processing. The RIWT is configured with a granularity of 256 stmmac clk ticks. Let's add a comment for that and wrap the raw "1000000" into USEC_PER_SEC, as we're computing "how many clock cycles in one microsec" with that step. Signed-off-by: Maxime Chevallier Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260802114015.214212-2-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c index 92585d27ab88..eebfd6976aaa 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c @@ -759,7 +759,10 @@ static u32 stmmac_usec2riwt(u32 usec, struct stmmac_priv *priv) return 0; } - return (usec * (clk / 1000000)) / 256; + /* Receive Interrupt Watchdog Timer (riwt) has a resolution of 256 + * ticks. + */ + return (usec * (clk / USEC_PER_SEC)) / 256; } static u32 stmmac_riwt2usec(u32 riwt, struct stmmac_priv *priv) @@ -772,7 +775,7 @@ static u32 stmmac_riwt2usec(u32 riwt, struct stmmac_priv *priv) return 0; } - return (riwt * 256) / (clk / 1000000); + return (riwt * 256) / (clk / USEC_PER_SEC); } static int __stmmac_get_coalesce(struct net_device *dev, From ae88f78bc4ebae984a60ec4b4d6610cce5cfce0f Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Sun, 2 Aug 2026 13:40:14 +0200 Subject: [PATCH 0930/1433] net: stmmac: ethtool: Address off-by-one when reading the coal rx-usecs When reading the rx-usecs coalescing parameters on a dwmac variant that uses the RIWT for RX interrupt coalescing, we convert the riwt value to usecs : - One riwt cycle is 256 clock ticks, we compute how many ticks in $riwt cycles - divide that by how many ticks in a microsecond, and we get the rx-usecs. The opposite computation is done when setting the rx-usecs param. Because of the 256 ratio, we're subjected to off-by-one errors in the value read-back, which can be reliably measured on i.mx8MP : $ ethtool -C eth1 rx-usecs 102 $ ethtool -c eth1 Coalesce parameters for eth1: [...] rx-usecs: 101 Let's be more explicit about the rounding for the riwt to usec computations by using DIV_ROUND_CLOSEST, which solves the off-by-one. This does change the boundaries of accepted rx-usecs parameters, as the previously accepted values were in the 16-246 us range, and now fall into the 15-245 range on imx8mp. Signed-off-by: Maxime Chevallier Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260802114015.214212-3-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c index eebfd6976aaa..154cc0c7623d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c @@ -762,7 +762,7 @@ static u32 stmmac_usec2riwt(u32 usec, struct stmmac_priv *priv) /* Receive Interrupt Watchdog Timer (riwt) has a resolution of 256 * ticks. */ - return (usec * (clk / USEC_PER_SEC)) / 256; + return DIV_ROUND_CLOSEST(usec * (clk / USEC_PER_SEC), 256); } static u32 stmmac_riwt2usec(u32 riwt, struct stmmac_priv *priv) @@ -775,7 +775,7 @@ static u32 stmmac_riwt2usec(u32 riwt, struct stmmac_priv *priv) return 0; } - return (riwt * 256) / (clk / USEC_PER_SEC); + return DIV_ROUND_CLOSEST(riwt * 256, clk / USEC_PER_SEC); } static int __stmmac_get_coalesce(struct net_device *dev, From d52f80731055ee43b36bf60088679aca97a484da Mon Sep 17 00:00:00 2001 From: Dipayaan Roy Date: Tue, 28 Jul 2026 23:21:28 -0700 Subject: [PATCH 0931/1433] net: mana: refactor mana_get_strings() and mana_get_sset_count() to use switch Refactor mana_get_strings() and mana_get_sset_count() from if/else to switch statements in preparation for adding ethtool private flags support which requires handling ETH_SS_PRIV_FLAGS. No functional change. Reviewed-by: Haiyang Zhang Signed-off-by: Dipayaan Roy Link: https://patch.msgid.link/20260729063347.3388035-2-dipayanroy@linux.microsoft.com Signed-off-by: Jakub Kicinski --- .../ethernet/microsoft/mana/mana_ethtool.c | 95 +++++++++++-------- 1 file changed, 56 insertions(+), 39 deletions(-) diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c index 9e31e2595ae3..482cd16009ab 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c +++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c @@ -138,53 +138,70 @@ static int mana_get_sset_count(struct net_device *ndev, int stringset) struct mana_port_context *apc = netdev_priv(ndev); unsigned int num_queues = apc->num_queues; - if (stringset != ETH_SS_STATS) + switch (stringset) { + case ETH_SS_STATS: + return ARRAY_SIZE(mana_eth_stats) + + ARRAY_SIZE(mana_phy_stats) + + ARRAY_SIZE(mana_hc_stats) + + num_queues * (MANA_STATS_RX_COUNT + MANA_STATS_TX_COUNT); + default: return -EINVAL; + } +} - return ARRAY_SIZE(mana_eth_stats) + ARRAY_SIZE(mana_phy_stats) + ARRAY_SIZE(mana_hc_stats) + - num_queues * (MANA_STATS_RX_COUNT + MANA_STATS_TX_COUNT); +static void mana_get_strings_stats(struct mana_port_context *apc, u8 **data) +{ + unsigned int num_queues = apc->num_queues; + int i, j; + + for (i = 0; i < ARRAY_SIZE(mana_eth_stats); i++) + ethtool_puts(data, mana_eth_stats[i].name); + + for (i = 0; i < ARRAY_SIZE(mana_hc_stats); i++) + ethtool_puts(data, mana_hc_stats[i].name); + + for (i = 0; i < ARRAY_SIZE(mana_phy_stats); i++) + ethtool_puts(data, mana_phy_stats[i].name); + + for (i = 0; i < num_queues; i++) { + ethtool_sprintf(data, "rx_%d_packets", i); + ethtool_sprintf(data, "rx_%d_bytes", i); + ethtool_sprintf(data, "rx_%d_xdp_drop", i); + ethtool_sprintf(data, "rx_%d_xdp_tx", i); + ethtool_sprintf(data, "rx_%d_xdp_redirect", i); + ethtool_sprintf(data, "rx_%d_pkt_len0_err", i); + for (j = 0; j < MANA_RXCOMP_OOB_NUM_PPI - 1; j++) + ethtool_sprintf(data, + "rx_%d_coalesced_cqe_%d", + i, + j + 2); + } + + for (i = 0; i < num_queues; i++) { + ethtool_sprintf(data, "tx_%d_packets", i); + ethtool_sprintf(data, "tx_%d_bytes", i); + ethtool_sprintf(data, "tx_%d_xdp_xmit", i); + ethtool_sprintf(data, "tx_%d_tso_packets", i); + ethtool_sprintf(data, "tx_%d_tso_bytes", i); + ethtool_sprintf(data, "tx_%d_tso_inner_packets", i); + ethtool_sprintf(data, "tx_%d_tso_inner_bytes", i); + ethtool_sprintf(data, "tx_%d_long_pkt_fmt", i); + ethtool_sprintf(data, "tx_%d_short_pkt_fmt", i); + ethtool_sprintf(data, "tx_%d_csum_partial", i); + ethtool_sprintf(data, "tx_%d_mana_map_err", i); + } } static void mana_get_strings(struct net_device *ndev, u32 stringset, u8 *data) { struct mana_port_context *apc = netdev_priv(ndev); - unsigned int num_queues = apc->num_queues; - int i, j; - if (stringset != ETH_SS_STATS) - return; - for (i = 0; i < ARRAY_SIZE(mana_eth_stats); i++) - ethtool_puts(&data, mana_eth_stats[i].name); - - for (i = 0; i < ARRAY_SIZE(mana_hc_stats); i++) - ethtool_puts(&data, mana_hc_stats[i].name); - - for (i = 0; i < ARRAY_SIZE(mana_phy_stats); i++) - ethtool_puts(&data, mana_phy_stats[i].name); - - for (i = 0; i < num_queues; i++) { - ethtool_sprintf(&data, "rx_%d_packets", i); - ethtool_sprintf(&data, "rx_%d_bytes", i); - ethtool_sprintf(&data, "rx_%d_xdp_drop", i); - ethtool_sprintf(&data, "rx_%d_xdp_tx", i); - ethtool_sprintf(&data, "rx_%d_xdp_redirect", i); - ethtool_sprintf(&data, "rx_%d_pkt_len0_err", i); - for (j = 0; j < MANA_RXCOMP_OOB_NUM_PPI - 1; j++) - ethtool_sprintf(&data, "rx_%d_coalesced_cqe_%d", i, j + 2); - } - - for (i = 0; i < num_queues; i++) { - ethtool_sprintf(&data, "tx_%d_packets", i); - ethtool_sprintf(&data, "tx_%d_bytes", i); - ethtool_sprintf(&data, "tx_%d_xdp_xmit", i); - ethtool_sprintf(&data, "tx_%d_tso_packets", i); - ethtool_sprintf(&data, "tx_%d_tso_bytes", i); - ethtool_sprintf(&data, "tx_%d_tso_inner_packets", i); - ethtool_sprintf(&data, "tx_%d_tso_inner_bytes", i); - ethtool_sprintf(&data, "tx_%d_long_pkt_fmt", i); - ethtool_sprintf(&data, "tx_%d_short_pkt_fmt", i); - ethtool_sprintf(&data, "tx_%d_csum_partial", i); - ethtool_sprintf(&data, "tx_%d_mana_map_err", i); + switch (stringset) { + case ETH_SS_STATS: + mana_get_strings_stats(apc, &data); + break; + default: + break; } } From 5b5cb75fb86af9752f8222ed6ca5aecfa4c8aa45 Mon Sep 17 00:00:00 2001 From: Dipayaan Roy Date: Tue, 28 Jul 2026 23:21:29 -0700 Subject: [PATCH 0932/1433] net: mana: force full-page RX buffers via ethtool private flag On some ARM64 platforms with 4K PAGE_SIZE, page_pool fragment allocation in the RX refill path can cause 15-20% throughput regression under high connection counts (>16 TCP streams). Add an ethtool private flag "full-page-rx" that allows the user to force one RX buffer per page, bypassing the page_pool fragment path. This restores line-rate (180+ Gbps) performance on affected platforms. Usage: ethtool --set-priv-flags eth0 full-page-rx on There is no behavioral change by default. The flag must be explicitly enabled by the user or udev rule. The existing single-buffer-per-page logic for XDP and jumbo frames is consolidated into a new helper mana_use_single_rxbuf_per_page() which is now the single decision point for both the automatic and user-controlled paths. Reviewed-by: Jacob Keller Reviewed-by: Haiyang Zhang Signed-off-by: Dipayaan Roy Link: https://patch.msgid.link/20260729063347.3388035-3-dipayanroy@linux.microsoft.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microsoft/mana/mana_en.c | 22 ++++- .../ethernet/microsoft/mana/mana_ethtool.c | 94 +++++++++++++++++++ include/net/mana/mana.h | 8 ++ 3 files changed, 122 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index a80cf0bc462c..2519a98ad00b 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -755,6 +755,25 @@ static void *mana_get_rxbuf_pre(struct mana_rxq *rxq, dma_addr_t *da) return va; } +static bool +mana_use_single_rxbuf_per_page(struct mana_port_context *apc, u32 mtu) +{ + /* On some platforms with 4K PAGE_SIZE, page_pool fragment allocation + * in the RX refill path (~2kB buffer) can cause significant throughput + * regression under high connection counts. Allow user to force one RX + * buffer per page via ethtool private flag to bypass the fragment + * path. + */ + if (apc->priv_flags & BIT(MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF)) + return true; + + /* For xdp and jumbo frames make sure only one packet fits per page. */ + if (mtu + MANA_RXBUF_PAD > PAGE_SIZE / 2 || mana_xdp_get(apc)) + return true; + + return false; +} + /* Get RX buffer's data size, alloc size, XDP headroom based on MTU */ static void mana_get_rxbuf_cfg(struct mana_port_context *apc, int mtu, u32 *datasize, u32 *alloc_size, @@ -765,8 +784,7 @@ static void mana_get_rxbuf_cfg(struct mana_port_context *apc, /* Calculate datasize first (consistent across all cases) */ *datasize = mtu + ETH_HLEN; - /* For xdp and jumbo frames make sure only one packet fits per page */ - if (mtu + MANA_RXBUF_PAD > PAGE_SIZE / 2 || mana_xdp_get(apc)) { + if (mana_use_single_rxbuf_per_page(apc, mtu)) { if (mana_xdp_get(apc)) { *headroom = XDP_PACKET_HEADROOM; *alloc_size = PAGE_SIZE; diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c index 482cd16009ab..7e441d6ae5dc 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c +++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c @@ -133,6 +133,10 @@ static const struct mana_stats_desc mana_phy_stats[] = { { "hc_tc7_tx_pause_phy", offsetof(struct mana_ethtool_phy_stats, tx_pause_tc7_phy) }, }; +static const char mana_priv_flags[MANA_PRIV_FLAG_MAX][ETH_GSTRING_LEN] = { + [MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF] = "full-page-rx" +}; + static int mana_get_sset_count(struct net_device *ndev, int stringset) { struct mana_port_context *apc = netdev_priv(ndev); @@ -144,6 +148,10 @@ static int mana_get_sset_count(struct net_device *ndev, int stringset) ARRAY_SIZE(mana_phy_stats) + ARRAY_SIZE(mana_hc_stats) + num_queues * (MANA_STATS_RX_COUNT + MANA_STATS_TX_COUNT); + + case ETH_SS_PRIV_FLAGS: + return MANA_PRIV_FLAG_MAX; + default: return -EINVAL; } @@ -192,6 +200,14 @@ static void mana_get_strings_stats(struct mana_port_context *apc, u8 **data) } } +static void mana_get_strings_priv_flags(u8 **data) +{ + int i; + + for (i = 0; i < MANA_PRIV_FLAG_MAX; i++) + ethtool_puts(data, mana_priv_flags[i]); +} + static void mana_get_strings(struct net_device *ndev, u32 stringset, u8 *data) { struct mana_port_context *apc = netdev_priv(ndev); @@ -200,6 +216,9 @@ static void mana_get_strings(struct net_device *ndev, u32 stringset, u8 *data) case ETH_SS_STATS: mana_get_strings_stats(apc, &data); break; + case ETH_SS_PRIV_FLAGS: + mana_get_strings_priv_flags(&data); + break; default: break; } @@ -756,6 +775,78 @@ static int mana_get_link_ksettings(struct net_device *ndev, return 0; } +static u32 mana_get_priv_flags(struct net_device *ndev) +{ + struct mana_port_context *apc = netdev_priv(ndev); + + return apc->priv_flags; +} + +static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags) +{ + struct mana_port_context *apc = netdev_priv(ndev); + u32 changed = apc->priv_flags ^ priv_flags; + u32 old_priv_flags = apc->priv_flags; + int err = 0; + + if (!changed) + return 0; + + /* Reject unknown bits */ + if (priv_flags & ~GENMASK(MANA_PRIV_FLAG_MAX - 1, 0)) + return -EINVAL; + + apc->priv_flags = priv_flags; + + if (changed & BIT(MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF)) { + if (!apc->port_is_up) + return 0; + + /* If XDP is attached or MTU is jumbo, single-buffer-per-page + * is already forced regardless of this flag. Skip the + * expensive detach/attach cycle since nothing changes. + */ + if (ndev->mtu + MANA_RXBUF_PAD > PAGE_SIZE / 2 || + mana_xdp_get(apc)) + return 0; + + /* Block RDMA from grabbing the vport during detach/attach */ + mutex_lock(&apc->vport_mutex); + apc->channel_changing = true; + mutex_unlock(&apc->vport_mutex); + + err = mana_pre_alloc_rxbufs(apc, ndev->mtu, apc->num_queues); + if (err) { + netdev_err(ndev, + "Insufficient memory for new allocations\n"); + apc->priv_flags = old_priv_flags; + goto clear_flag; + } + + err = mana_detach(ndev, false); + if (err) { + netdev_err(ndev, "mana_detach failed: %d\n", err); + apc->priv_flags = old_priv_flags; + goto out; + } + + err = mana_attach(ndev); + if (err) { + netdev_err(ndev, "mana_attach failed: %d\n", err); + apc->priv_flags = old_priv_flags; + } + } + +out: + mana_pre_dealloc_rxbufs(apc); +clear_flag: + mutex_lock(&apc->vport_mutex); + apc->channel_changing = false; + mutex_unlock(&apc->vport_mutex); + + return err; +} + const struct ethtool_ops mana_ethtool_ops = { .supported_coalesce_params = ETHTOOL_COALESCE_RX_CQE_FRAMES | ETHTOOL_COALESCE_RX_USECS | @@ -766,6 +857,7 @@ const struct ethtool_ops mana_ethtool_ops = { ETHTOOL_COALESCE_USE_ADAPTIVE_TX, .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM | + ETHTOOL_OP_NEEDS_RTNL_SPFLAGS | ETHTOOL_OP_NEEDS_RTNL_GLINK, .get_ethtool_stats = mana_get_ethtool_stats, .get_sset_count = mana_get_sset_count, @@ -783,4 +875,6 @@ const struct ethtool_ops mana_ethtool_ops = { .set_ringparam = mana_set_ringparam, .get_link_ksettings = mana_get_link_ksettings, .get_link = ethtool_op_get_link, + .get_priv_flags = mana_get_priv_flags, + .set_priv_flags = mana_set_priv_flags, }; diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h index 4d041fb8437f..9ffbdff5746f 100644 --- a/include/net/mana/mana.h +++ b/include/net/mana/mana.h @@ -31,6 +31,12 @@ enum TRI_STATE { TRI_STATE_TRUE = 1 }; +/* MANA ethtool private flag bit positions */ +enum mana_priv_flag_bits { + MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF = 0, + MANA_PRIV_FLAG_MAX, +}; + /* Number of entries for hardware indirection table must be in power of 2 */ #define MANA_INDIRECT_TABLE_MAX_SIZE 512 #define MANA_INDIRECT_TABLE_DEF_SIZE 64 @@ -568,6 +574,8 @@ struct mana_port_context { u32 rxbpre_headroom; u32 rxbpre_frag_count; + u32 priv_flags; + struct bpf_prog *bpf_prog; /* Create num_queues EQs, SQs, SQ-CQs, RQs and RQ-CQs, respectively. */ From cf31c7f186edc0593081c49c7a4374a59c6e3496 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 31 Jul 2026 16:45:53 +0000 Subject: [PATCH 0933/1433] geneve: Unlink geneve->sock[46].hlist[46].hlist in __geneve_sock_release(). Currently, geneve->sock[46].hlist[46] is unliked from geneve_sock.vni_list in geneve_stop() and geneve_sock.refcnt is decremented for each socket later in __geneve_sock_release(). The following patch will introduce a mutex in geneve_net to protect geneve_sock.{refcnt,vni_list}. However, udp_tunnel_notify_del_rx_port() must be outside of the lock to avoid AB-BA deadlock. To make the change cleaner, let's move hlist_del_init_rcu() from geneve_stop() to __geneve_sock_release(). Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260731164612.2148830-2-kuniyu@google.com Signed-off-by: Paolo Abeni --- drivers/net/geneve.c | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index 542e53f9dc3e..bd4fc6dba2fa 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -1052,9 +1052,30 @@ static struct geneve_sock *geneve_socket_create(struct net *net, return gs; } -static void __geneve_sock_release(struct geneve_sock *gs) +static void __geneve_sock_release(struct geneve_dev *geneve, bool ipv6) { - if (!gs || --gs->refcnt) + struct geneve_dev_node *node; + struct geneve_sock *gs; + +#if IS_ENABLED(CONFIG_IPV6) + if (ipv6) { + gs = rtnl_dereference(geneve->sock6); + rcu_assign_pointer(geneve->sock6, NULL); + node = &geneve->hlist6; + } else +#endif + { + gs = rtnl_dereference(geneve->sock4); + rcu_assign_pointer(geneve->sock4, NULL); + node = &geneve->hlist4; + } + + if (!gs) + return; + + hlist_del_init_rcu(&node->hlist); + + if (--gs->refcnt) return; list_del(&gs->list); @@ -1065,19 +1086,10 @@ static void __geneve_sock_release(struct geneve_sock *gs) static void geneve_sock_release(struct geneve_dev *geneve) { - struct geneve_sock *gs4 = rtnl_dereference(geneve->sock4); #if IS_ENABLED(CONFIG_IPV6) - struct geneve_sock *gs6 = rtnl_dereference(geneve->sock6); - - rcu_assign_pointer(geneve->sock6, NULL); -#endif - - rcu_assign_pointer(geneve->sock4, NULL); - - __geneve_sock_release(gs4); -#if IS_ENABLED(CONFIG_IPV6) - __geneve_sock_release(gs6); + __geneve_sock_release(geneve, true); #endif + __geneve_sock_release(geneve, false); } static struct geneve_sock *geneve_find_sock(struct net *net, @@ -1187,10 +1199,6 @@ static int geneve_stop(struct net_device *dev) { struct geneve_dev *geneve = netdev_priv(dev); - hlist_del_init_rcu(&geneve->hlist4.hlist); -#if IS_ENABLED(CONFIG_IPV6) - hlist_del_init_rcu(&geneve->hlist6.hlist); -#endif geneve_sock_release(geneve); return 0; } From 7df47efd6db1eb3cac2e938ca34b28216db4353f Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 31 Jul 2026 16:45:54 +0000 Subject: [PATCH 0934/1433] geneve: Protect geneve_net and geneve_sock with per-netns mutex. struct geneve_dev.net is the netns where the backend geneve socket resides. struct geneve_dev is linked to the geneve_net.geneve_list of the socket's netns. During netns dismantle or module unload, geneve_exit_rtnl_net() iterates the list and queues devices for destruction regardless of devices' netns. Moreover, a socket can be shared by multiple geneve devices in different netns, and geneve_open() and geneve_stop() modify geneve_sock.vni_list and geneve_net.sock_list. Thus, once RTNL is removed, the three lists can be modified concurrently from different netns due to device removal and link-up/down. Let's protect them with per-netns mutex. geneve_newlink() is still protected by rtnl_net_lock()s, so acquiring gn->lock twice in geneve_find_dev() and geneve_configure() is not a problem. Note that udp_tunnel_notify_add_rx_port() is moved outside of the mutex, otherwise gn->lock -> utn->lock ordering would trigger AB-BA deadlock in geneve_offload_rx_ports(), which acquires gn->lock under utn->lock. Even without gn->lock, geneve_sock_add() and geneve_offload_rx_ports() are still serialised with (per-netns) RTNL, so there is no race. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260731164612.2148830-3-kuniyu@google.com Signed-off-by: Paolo Abeni --- drivers/net/geneve.c | 82 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 14 deletions(-) diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index bd4fc6dba2fa..f456a85dca77 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -69,8 +69,8 @@ struct geneve_skb_cb { /* per-network namespace private data for this module */ struct geneve_net { struct list_head geneve_list; - /* sock_list is protected by rtnl lock */ struct list_head sock_list; + struct mutex lock; }; static unsigned int geneve_net_id; @@ -1035,9 +1035,6 @@ static struct geneve_sock *geneve_socket_create(struct net *net, for (h = 0; h < VNI_HASH_SIZE; ++h) INIT_HLIST_HEAD(&gs->vni_list[h]); - /* Initialize the geneve udp offloads structure */ - udp_tunnel_notify_add_rx_port(sk, UDP_TUNNEL_TYPE_GENEVE); - /* Mark socket as an encapsulation socket */ memset(&tunnel_cfg, 0, sizeof(tunnel_cfg)); tunnel_cfg.sk_user_data = gs; @@ -1056,6 +1053,7 @@ static void __geneve_sock_release(struct geneve_dev *geneve, bool ipv6) { struct geneve_dev_node *node; struct geneve_sock *gs; + struct geneve_net *gn; #if IS_ENABLED(CONFIG_IPV6) if (ipv6) { @@ -1073,12 +1071,19 @@ static void __geneve_sock_release(struct geneve_dev *geneve, bool ipv6) if (!gs) return; + gn = net_generic(sock_net(gs->sk), geneve_net_id); + mutex_lock(&gn->lock); + hlist_del_init_rcu(&node->hlist); - if (--gs->refcnt) + if (--gs->refcnt) { + mutex_unlock(&gn->lock); return; + } list_del(&gs->list); + mutex_unlock(&gn->lock); + udp_tunnel_notify_del_rx_port(gs->sk, UDP_TUNNEL_TYPE_GENEVE); udp_tunnel_sock_release(gs->sk); kfree_rcu(gs, rcu); @@ -1135,22 +1140,31 @@ static int geneve_sock_add(struct geneve_dev *geneve, struct net *net = geneve->net; struct geneve_dev_node *node; struct geneve_sock *gs; + struct geneve_net *gn; + bool created = false; __u8 vni[3]; + int ret = 0; __u32 hash; + gn = net_generic(net, geneve_net_id); + mutex_lock(&gn->lock); + gs = geneve_find_sock(net, geneve, cfg, ipv6); if (gs) { gs->refcnt++; - goto out; + } else { + gs = geneve_socket_create(net, geneve, cfg, ipv6); + if (IS_ERR(gs)) { + ret = PTR_ERR(gs); + goto out; + } + + created = true; } - gs = geneve_socket_create(net, geneve, cfg, ipv6); - if (IS_ERR(gs)) - return PTR_ERR(gs); - -out: gs->collect_md = cfg->collect_md; gs->gro_hint = cfg->gro_hint; + #if IS_ENABLED(CONFIG_IPV6) if (ipv6) { rcu_assign_pointer(geneve->sock6, gs); @@ -1166,7 +1180,16 @@ static int geneve_sock_add(struct geneve_dev *geneve, tunnel_id_to_vni(cfg->info.key.tun_id, vni); hash = geneve_net_vni_hash(vni); hlist_add_head_rcu(&node->hlist, &gs->vni_list[hash]); - return 0; + +out: + mutex_unlock(&gn->lock); + + if (created) { + /* Initialize the geneve udp offloads structure */ + udp_tunnel_notify_add_rx_port(gs->sk, UDP_TUNNEL_TYPE_GENEVE); + } + + return ret; } static int geneve_open(struct net_device *dev) @@ -1743,6 +1766,8 @@ static void geneve_offload_rx_ports(struct net_device *dev, bool push) ASSERT_RTNL(); + mutex_lock(&gn->lock); + list_for_each_entry(gs, &gn->sock_list, list) { if (push) { udp_tunnel_push_rx_port(dev, gs->sk, @@ -1752,6 +1777,8 @@ static void geneve_offload_rx_ports(struct net_device *dev, bool push) UDP_TUNNEL_TYPE_GENEVE); } } + + mutex_unlock(&gn->lock); } static struct geneve_config *geneve_config_alloc(const struct geneve_config *src) @@ -1968,6 +1995,9 @@ static struct geneve_dev *geneve_find_dev(struct geneve_net *gn, *tun_on_same_port = false; *tun_collect_md = false; + + mutex_lock(&gn->lock); + list_for_each_entry(geneve, &gn->geneve_list, next) { const struct geneve_config *gcfg = rtnl_dereference(geneve->cfg); @@ -1982,6 +2012,9 @@ static struct geneve_dev *geneve_find_dev(struct geneve_net *gn, !memcmp(&info->key.u, &gcfg->info.key.u, sizeof(info->key.u))) t = geneve; } + + mutex_unlock(&gn->lock); + return t; } @@ -2076,7 +2109,10 @@ static int geneve_configure(struct net *net, struct net_device *dev, return err; } + mutex_lock(&gn->lock); list_add(&geneve->next, &gn->geneve_list); + mutex_unlock(&gn->lock); + return 0; } @@ -2466,7 +2502,7 @@ static int geneve_changelink(struct net_device *dev, struct nlattr *tb[], return err; } -static void geneve_dellink(struct net_device *dev, struct list_head *head) +static void __geneve_dellink(struct net_device *dev, struct list_head *head) { struct geneve_dev *geneve = netdev_priv(dev); @@ -2474,6 +2510,18 @@ static void geneve_dellink(struct net_device *dev, struct list_head *head) unregister_netdevice_queue(dev, head); } +static void geneve_dellink(struct net_device *dev, struct list_head *head) +{ + struct geneve_dev *geneve = netdev_priv(dev); + struct geneve_net *gn; + + gn = net_generic(geneve->net, geneve_net_id); + + mutex_lock(&gn->lock); + __geneve_dellink(dev, head); + mutex_unlock(&gn->lock); +} + static size_t geneve_get_size(const struct net_device *dev) { return nla_total_size(sizeof(__u32)) + /* IFLA_GENEVE_ID */ @@ -2692,6 +2740,8 @@ static __net_init int geneve_init_net(struct net *net) INIT_LIST_HEAD(&gn->geneve_list); INIT_LIST_HEAD(&gn->sock_list); + mutex_init(&gn->lock); + return 0; } @@ -2701,8 +2751,12 @@ static void __net_exit geneve_exit_rtnl_net(struct net *net, struct geneve_net *gn = net_generic(net, geneve_net_id); struct geneve_dev *geneve, *next; + mutex_lock(&gn->lock); + list_for_each_entry_safe(geneve, next, &gn->geneve_list, next) - geneve_dellink(geneve->dev, dev_to_kill); + __geneve_dellink(geneve->dev, dev_to_kill); + + mutex_unlock(&gn->lock); } static void __net_exit geneve_exit_net(struct net *net) From ccb161b71a1f5ac2cb97d301cd6942e3d1c8908d Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 31 Jul 2026 16:45:55 +0000 Subject: [PATCH 0935/1433] geneve: Support per-netns netdev unregistration. geneve_exit_rtnl_net() iterates geneve devices whose sockets are in the dying netns and queues them for destruction. So the devices may reside in different netns. Let's use unregister_netdevice_queue_net() to support per-netns device unregistration. list_del() is changed to list_del_init() to avoid queueing the same device twice. Even after geneve_exit_rtnl_net() queues a cross-netns geneve device, geneve_dellink() can be called concurrently for it. In such a case, __rtnl_net_unlock() will perform the unregistration. Note that geneve uses register_pernet_subsys() instead of _device(), so default_device_exit_batch() guarantees that the async per-netns works are flushed before ->exit(). Tested: 1. Create geneve device across two netns. # ip netns add ns1 # ip netns add ns2 # ip -n ns1 link add geneve0 link-netns ns2 type geneve external 2. Run bpftrace to check that geneve_uninit() is called between ->exit_rtnl() and ->exit(). # bpftrace -e '#include kprobe:geneve_uninit { $dev = (struct net_device *)arg0; printf("PID: %d | DEV: %s%s\n", pid, $dev->name, kstack()); } kprobe:geneve_exit_rtnl_net, kprobe:geneve_exit_net { printf("PID: %d%s\n", pid, kstack()); }' 3. Remove the netns where the geneve socket resides # ip netns del ns2 Now, we can see geneve0 is unregistered by per-netns work instead of cleanup_net() and it finishes before ->exit() to avoid WARN_ON_ONCE(!list_empty(&gn->sock_list)) there. PID: 571 geneve_exit_rtnl_net+5 ops_undo_list+702 cleanup_net+1122 process_scheduled_works+2538 ... PID: 1047 | DEV: geneve0 geneve_uninit+5 unregister_netdevice_many_notify+7129 unregister_netdevice_many_net+1050 rtnl_net_work_func+136 process_scheduled_works+2538 ... PID: 571 geneve_exit_net+5 ops_undo_list+1064 cleanup_net+1122 process_scheduled_works+2538 ... Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260731164612.2148830-4-kuniyu@google.com Signed-off-by: Paolo Abeni --- drivers/net/geneve.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index f456a85dca77..a6a8978e3b81 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -2502,12 +2502,13 @@ static int geneve_changelink(struct net_device *dev, struct nlattr *tb[], return err; } -static void __geneve_dellink(struct net_device *dev, struct list_head *head) +static void __geneve_dellink(struct net *net, struct net_device *dev, + struct list_head *head) { struct geneve_dev *geneve = netdev_priv(dev); - list_del(&geneve->next); - unregister_netdevice_queue(dev, head); + list_del_init(&geneve->next); + unregister_netdevice_queue_net(net, dev, head); } static void geneve_dellink(struct net_device *dev, struct list_head *head) @@ -2518,7 +2519,8 @@ static void geneve_dellink(struct net_device *dev, struct list_head *head) gn = net_generic(geneve->net, geneve_net_id); mutex_lock(&gn->lock); - __geneve_dellink(dev, head); + if (!list_empty(&geneve->next)) + __geneve_dellink(dev_net(dev), dev, head); mutex_unlock(&gn->lock); } @@ -2754,7 +2756,7 @@ static void __net_exit geneve_exit_rtnl_net(struct net *net, mutex_lock(&gn->lock); list_for_each_entry_safe(geneve, next, &gn->geneve_list, next) - __geneve_dellink(geneve->dev, dev_to_kill); + __geneve_dellink(net, geneve->dev, dev_to_kill); mutex_unlock(&gn->lock); } From 828c4a5a9518117f9f7bdc445a7eeca85fc91bf8 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sat, 1 Aug 2026 21:55:06 +0200 Subject: [PATCH 0936/1433] dt-bindings: net: Correct white-space style Correct a few white-space issues, like double space after '=' or before bracket '{' characters, which will be flagged by dt-check-style. No functional changes. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260801195505.235099-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Paolo Abeni --- Documentation/devicetree/bindings/net/fsl,gianfar.yaml | 2 +- .../devicetree/bindings/net/microchip,lan966x-switch.yaml | 4 ++-- .../devicetree/bindings/net/microchip,sparx5-switch.yaml | 6 +++--- .../devicetree/bindings/net/renesas,rzv2h-gbeth.yaml | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Documentation/devicetree/bindings/net/fsl,gianfar.yaml b/Documentation/devicetree/bindings/net/fsl,gianfar.yaml index 0d8909770ccb..e2543b683bd4 100644 --- a/Documentation/devicetree/bindings/net/fsl,gianfar.yaml +++ b/Documentation/devicetree/bindings/net/fsl,gianfar.yaml @@ -234,7 +234,7 @@ examples: ; }; - queue-group@2d14000 { + queue-group@2d14000 { reg = <0x0 0x2d14000 0x0 0x1000>; interrupts = , , diff --git a/Documentation/devicetree/bindings/net/microchip,lan966x-switch.yaml b/Documentation/devicetree/bindings/net/microchip,lan966x-switch.yaml index 0f0f35865ef4..b4efa2f6036a 100644 --- a/Documentation/devicetree/bindings/net/microchip,lan966x-switch.yaml +++ b/Documentation/devicetree/bindings/net/microchip,lan966x-switch.yaml @@ -140,8 +140,8 @@ examples: #include switch: ethernet-switch@e0000000 { compatible = "microchip,lan966x-switch"; - reg = <0xe0000000 0x0100000>, - <0xe2000000 0x0800000>; + reg = <0xe0000000 0x0100000>, + <0xe2000000 0x0800000>; reg-names = "cpu", "gcb"; interrupts = ; interrupt-names = "xtr"; diff --git a/Documentation/devicetree/bindings/net/microchip,sparx5-switch.yaml b/Documentation/devicetree/bindings/net/microchip,sparx5-switch.yaml index 75c7c8d1f411..9de8eeb8c6a4 100644 --- a/Documentation/devicetree/bindings/net/microchip,sparx5-switch.yaml +++ b/Documentation/devicetree/bindings/net/microchip,sparx5-switch.yaml @@ -210,9 +210,9 @@ examples: #include switch: switch@600000000 { compatible = "microchip,sparx5-switch"; - reg = <0 0x401000>, - <0x10004000 0x7fc000>, - <0x11010000 0xaf0000>; + reg = <0 0x401000>, + <0x10004000 0x7fc000>, + <0x11010000 0xaf0000>; reg-names = "cpu", "devices", "gcb"; interrupts = ; interrupt-names = "xtr"; diff --git a/Documentation/devicetree/bindings/net/renesas,rzv2h-gbeth.yaml b/Documentation/devicetree/bindings/net/renesas,rzv2h-gbeth.yaml index 2125b5ddf73d..c8f76c8e7584 100644 --- a/Documentation/devicetree/bindings/net/renesas,rzv2h-gbeth.yaml +++ b/Documentation/devicetree/bindings/net/renesas,rzv2h-gbeth.yaml @@ -254,10 +254,10 @@ examples: ethernet@15c30000 { compatible = "renesas,r9a09g057-gbeth", "renesas,rzv2h-gbeth", "snps,dwmac-5.20"; reg = <0x15c30000 0x10000>; - clocks = <&cpg CPG_MOD 0xbd>, <&cpg CPG_MOD 0xbc>, - <&ptp_clock>, <&cpg CPG_MOD 0xb8>, - <&cpg CPG_MOD 0xb9>, <&cpg CPG_MOD 0xba>, - <&cpg CPG_MOD 0xbb>; + clocks = <&cpg CPG_MOD 0xbd>, <&cpg CPG_MOD 0xbc>, + <&ptp_clock>, <&cpg CPG_MOD 0xb8>, + <&cpg CPG_MOD 0xb9>, <&cpg CPG_MOD 0xba>, + <&cpg CPG_MOD 0xbb>; clock-names = "stmmaceth", "pclk", "ptp_ref", "tx", "rx", "tx-180", "rx-180"; resets = <&cpg 0xb0>; From 0de27651929b3a3b4f97d982445e30e4f46c1959 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 2 Jul 2026 22:10:02 +0200 Subject: [PATCH 0937/1433] batman-adv: dat: drop non-4addr backwards compatibility The 4addr unicast packet support is mandatory in compat version 15. No older compat version is supported and the kernel doesn't need to keep code to talk to nodes which cannot be in the same mesh. Acked-by: Antonio Quartulli Signed-off-by: Sven Eckelmann --- net/batman-adv/distributed-arp-table.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c index a6fe4820f65b..c5fca53f3074 100644 --- a/net/batman-adv/distributed-arp-table.c +++ b/net/batman-adv/distributed-arp-table.c @@ -1277,17 +1277,9 @@ bool batadv_dat_snoop_incoming_arp_request(struct batadv_priv *bat_priv, if (!skb_new) goto out; - /* To preserve backwards compatibility, the node has choose the outgoing - * format based on the incoming request packet type. The assumption is - * that a node not using the 4addr packet format doesn't support it. - */ - if (hdr_size == sizeof(struct batadv_unicast_4addr_packet)) - err = batadv_send_skb_via_tt_4addr(bat_priv, skb_new, - BATADV_P_DAT_CACHE_REPLY, - NULL, vid); - else - err = batadv_send_skb_via_tt(bat_priv, skb_new, NULL, vid); - + err = batadv_send_skb_via_tt_4addr(bat_priv, skb_new, + BATADV_P_DAT_CACHE_REPLY, + NULL, vid); if (err != NET_XMIT_DROP) { batadv_inc_counter(bat_priv, BATADV_CNT_DAT_CACHED_REPLY_TX); ret = true; From e558ee008810ca24bc85747779673f5aa82ffddb Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 30 Jul 2026 10:57:53 +0200 Subject: [PATCH 0938/1433] batman-adv: tvlv: handle negative tvlv processing return codes batadv_tvlv_containers_process() was implemented with only two return codes from the handlers in mind: * NET_RX_SUCCESS (0) * NET_RX_DROP (1) The multicast handlers broke this convention and are also returning negative return codes. But the processing code was never updated to correctly aggregate them. To handle negative return codes for non-OGM(2) handlers, they are now aggregated to: * NET_RX_SUCCESS when no handlers returned a different return code * the last negative return code when at least one handler returned a negative return code * NET_RX_DROP otherwise With the current callers, the old implementation is not triggering any unexpected behavior. The behavior is only adjusted for new code which might need more reliable return values. Signed-off-by: Sven Eckelmann --- net/batman-adv/routing.c | 4 +++- net/batman-adv/tvlv.c | 22 ++++++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c index bbd40fe3a8e5..a9d657f2a831 100644 --- a/net/batman-adv/routing.c +++ b/net/batman-adv/routing.c @@ -1334,7 +1334,9 @@ int batadv_recv_bcast_packet(struct sk_buff *skb, * contents of its TVLV forwards it and/or decapsulates it to hand it to the * mesh interface. * - * Return: NET_RX_DROP if the skb is not consumed, NET_RX_SUCCESS otherwise. + * Return: NET_RX_SUCCESS if the skb was locally received, NET_RX_DROP otherwise + * or a negative errno code when the multicast tracker TVLV could not be + * processed */ int batadv_recv_mcast_packet(struct sk_buff *skb, struct batadv_hard_iface *recv_if) diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c index 49bf2ed9ecdc..cc14a76582b5 100644 --- a/net/batman-adv/tvlv.c +++ b/net/batman-adv/tvlv.c @@ -383,8 +383,9 @@ int batadv_tvlv_container_ogm_append(struct batadv_priv *bat_priv, * @tvlv_value: tvlv content * @tvlv_value_len: tvlv content length * - * Return: success if the handler was not found or the return value of the - * handler callback. + * Return: NET_RX_SUCCESS if the handler was not found or the return value of + * the handler callback. The latter is NET_RX_SUCCESS or NET_RX_DROP for the + * unicast handler and additionally a negative errno code for the mcast handler. */ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv, struct batadv_tvlv_handler *tvlv_handler, @@ -524,8 +525,9 @@ static bool batadv_tvlv_containers_contain(void *tvlv_value, * @tvlv_value: tvlv content * @tvlv_value_len: tvlv content length * - * Return: success when processing an OGM or the return value of all called - * handler callbacks. + * Return: NET_RX_SUCCESS when processing an OGM or the combined return value of + * all called handler callbacks. The latter is NET_RX_SUCCESS, NET_RX_DROP or, + * for BATADV_MCAST packets, a negative errno code. */ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv, u8 packet_type, @@ -540,6 +542,7 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv, u16 tvlv_value_cont_len; u8 cifnotfound = BATADV_TVLV_HANDLER_OGM_CIFNOTFND; int ret = NET_RX_SUCCESS; + int res; while ((tvlv_hdr = batadv_tvlv_hdr_next(&tvlv_value, &tvlv_value_len))) { tvlv_value_cont_len = ntohs(tvlv_hdr->len); @@ -548,10 +551,13 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv, tvlv_hdr->type, tvlv_hdr->version); - ret |= batadv_tvlv_call_handler(bat_priv, tvlv_handler, - packet_type, orig_node, skb, - tvlv_hdr + 1, - tvlv_value_cont_len); + res = batadv_tvlv_call_handler(bat_priv, tvlv_handler, + packet_type, orig_node, skb, + tvlv_hdr + 1, + tvlv_value_cont_len); + if (ret == NET_RX_SUCCESS || res < 0) + ret = res; + batadv_tvlv_handler_put(tvlv_handler); } From 3ebcaccc8871ba723ea336db9da9084ae3cffcb3 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Mon, 8 Jun 2026 09:56:29 +0200 Subject: [PATCH 0939/1433] batman-adv: add missing kernel-doc comments batman-adv requires kernel-doc for all functions and data types visible outside their own translation unit. Promoting a function from static to module-wide visibility currently requires adding documentation from scratch. However, this burden falls on whoever promotes a function from static to module-wide visibility, rather than its original implementer. Add the missing kernel-doc comments for the remaining undocumented functions and data types to reduce the complexity for new contributions. Signed-off-by: Sven Eckelmann --- include/uapi/linux/batadv_packet.h | 16 ++- net/batman-adv/bat_algo.c | 10 ++ net/batman-adv/bat_iv_ogm.c | 166 ++++++++++++++++++++++- net/batman-adv/bat_v.c | 52 ++++++++ net/batman-adv/bitarray.c | 9 +- net/batman-adv/distributed-arp-table.c | 38 ++++++ net/batman-adv/gateway_client.c | 8 ++ net/batman-adv/hard-interface.c | 82 ++++++++++++ net/batman-adv/hash.c | 5 +- net/batman-adv/hash.h | 34 +++-- net/batman-adv/main.c | 36 ++++- net/batman-adv/mesh-interface.c | 63 +++++++++ net/batman-adv/netlink.c | 8 +- net/batman-adv/originator.c | 7 + net/batman-adv/routing.c | 31 +++++ net/batman-adv/translation-table.c | 177 ++++++++++++++++++++++++- net/batman-adv/types.h | 4 +- 17 files changed, 711 insertions(+), 35 deletions(-) diff --git a/include/uapi/linux/batadv_packet.h b/include/uapi/linux/batadv_packet.h index 1241285b866c..32436560ecc8 100644 --- a/include/uapi/linux/batadv_packet.h +++ b/include/uapi/linux/batadv_packet.h @@ -192,13 +192,19 @@ enum batadv_tvlv_type { }; #pragma pack(2) -/* the destination hardware field in the ARP frame is used to - * transport the claim type and the group id +/** + * struct batadv_bla_claim_dst - layout of the destination MAC of a BLA claim + * frame + * @magic: fixed magic prefix (FF:43:05) identifying claim frames + * @type: claim frame type, see &enum batadv_bla_claimframe + * @group: group identifier of the announcing backbone gateway + * + * used in the destination hardware field of the ARP frame */ struct batadv_bla_claim_dst { - __u8 magic[3]; /* FF:43:05 */ - __u8 type; /* bla_claimframe */ - __be16 group; /* group id */ + __u8 magic[3]; + __u8 type; + __be16 group; }; /** diff --git a/net/batman-adv/bat_algo.c b/net/batman-adv/bat_algo.c index 49e5861b58ec..a040141cdf1a 100644 --- a/net/batman-adv/bat_algo.c +++ b/net/batman-adv/bat_algo.c @@ -116,6 +116,16 @@ int batadv_algo_select(struct batadv_priv *bat_priv, const char *name) return 0; } +/** + * batadv_param_set_ra() - Validate and store routing_algo module parameter + * @val: new value for the routing_algo module parameter + * @kp: kernel parameter description used to store the value + * + * Check that the requested algorithm is known to batman-adv and then store + * the name as the new default routing algorithm. + * + * Return: 0 on success or negative error number in case of failure + */ static int batadv_param_set_ra(const char *val, const struct kernel_param *kp) { struct batadv_algo_ops *bat_algo_ops; diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c index 22622283f59b..a9e80330fcb6 100644 --- a/net/batman-adv/bat_iv_ogm.c +++ b/net/batman-adv/bat_iv_ogm.c @@ -171,6 +171,14 @@ batadv_iv_ogm_orig_get(struct batadv_priv *bat_priv, const u8 *addr) return NULL; } +/** + * batadv_iv_ogm_neigh_new() - retrieve or create a B.A.T.M.A.N. IV neighbour + * @hard_iface: the interface where the neighbour is connected to + * @neigh_addr: the mac address of the neighbour + * @orig_node: originator object representing the neighbour + * + * Return: pointer to the neigh_node or NULL in case of failure + */ static struct batadv_neigh_node * batadv_iv_ogm_neigh_new(struct batadv_hard_iface *hard_iface, const u8 *neigh_addr, @@ -183,6 +191,14 @@ batadv_iv_ogm_neigh_new(struct batadv_hard_iface *hard_iface, return neigh_node; } +/** + * batadv_iv_ogm_iface_enable() - prepare an interface for B.A.T.M.A.N. IV + * @hard_iface: the interface to prepare + * + * Allocate and prepare the per-interface OGM buffer + * + * Return: 0 on success or negative error number in case of failure + */ static int batadv_iv_ogm_iface_enable(struct batadv_hard_iface *hard_iface) { struct batadv_ogm_packet *batadv_ogm_packet; @@ -220,6 +236,14 @@ static int batadv_iv_ogm_iface_enable(struct batadv_hard_iface *hard_iface) return 0; } +/** + * batadv_iv_ogm_iface_disable() - release B.A.T.M.A.N. IV resources of an + * interface + * @hard_iface: the interface which is shutting down + * + * Free the per-interface OGM buffer and cancel a possibly pending OGM + * rescheduling work. + */ static void batadv_iv_ogm_iface_disable(struct batadv_hard_iface *hard_iface) { mutex_lock(&hard_iface->bat_iv.ogm_buff_mutex); @@ -233,6 +257,11 @@ static void batadv_iv_ogm_iface_disable(struct batadv_hard_iface *hard_iface) disable_delayed_work_sync(&hard_iface->bat_iv.reschedule_work); } +/** + * batadv_iv_ogm_iface_update_mac() - update the originator MAC stored in the + * per-interface OGM buffer + * @hard_iface: the interface for which the OGM buffer should be updated + */ static void batadv_iv_ogm_iface_update_mac(struct batadv_hard_iface *hard_iface) { struct batadv_ogm_packet *batadv_ogm_packet; @@ -254,6 +283,14 @@ static void batadv_iv_ogm_iface_update_mac(struct batadv_hard_iface *hard_iface) mutex_unlock(&hard_iface->bat_iv.ogm_buff_mutex); } +/** + * batadv_iv_ogm_primary_iface_set() - apply primary interface state to + * a batadv_hard_iface + * @hard_iface: interface which just became the primary + * + * The primary interface uses the full TTL for its own OGMs, so adjust the TTL + * stored in the OGM template buffer accordingly. + */ static void batadv_iv_ogm_primary_iface_set(struct batadv_hard_iface *hard_iface) { @@ -273,7 +310,17 @@ batadv_iv_ogm_primary_iface_set(struct batadv_hard_iface *hard_iface) mutex_unlock(&hard_iface->bat_iv.ogm_buff_mutex); } -/* when do we schedule our own ogm to be sent */ +/** + * batadv_iv_ogm_emit_send_time() - calculate the jiffies when an own OGM + * should be sent next + * @bat_priv: the bat priv with all the mesh interface information + * + * The next emission point is the configured orig_interval randomised by + * +/- BATADV_JITTER milliseconds to reduce chance of potential collisions + * between neighbours. + * + * Return: jiffies value when the next OGM should be transmitted + */ static unsigned long batadv_iv_ogm_emit_send_time(const struct batadv_priv *bat_priv) { @@ -285,13 +332,24 @@ batadv_iv_ogm_emit_send_time(const struct batadv_priv *bat_priv) return jiffies + msecs_to_jiffies(msecs); } -/* when do we schedule a ogm packet to be sent */ +/** + * batadv_iv_ogm_fwd_send_time() - calculate the jiffies when a forwarded OGM + * should be sent next + * + * Return: jiffies value at which a forwarded OGM should be transmitted + */ static unsigned long batadv_iv_ogm_fwd_send_time(void) { return jiffies + msecs_to_jiffies(get_random_u32_below(BATADV_JITTER / 2)); } -/* apply hop penalty for a normal link */ +/** + * batadv_hop_penalty() - apply the configured hop penalty to a TQ value + * @tq: input TQ value to be reduced + * @bat_priv: the bat priv with all the mesh interface information + * + * Return: the TQ value after the hop penalty has been applied + */ static u8 batadv_hop_penalty(u8 tq, const struct batadv_priv *bat_priv) { int hop_penalty = READ_ONCE(bat_priv->hop_penalty); @@ -337,7 +395,15 @@ batadv_iv_ogm_aggr_packet(int buff_pos, int packet_len, return next_buff_pos <= packet_len; } -/* send a batman ogm to a given interface */ +/** + * batadv_iv_ogm_send_to_if() - send a batman OGM to a given interface + * @forw_packet: forward packet containing the OGM(s) to be transmitted + * @hard_iface: interface to send the OGM out on + * + * Update the direct link flags of each aggregated OGM for the outgoing + * interface, log the transmission and finally hand a clone of the skb to the + * lower layer for broadcast. + */ static void batadv_iv_ogm_send_to_if(struct batadv_forw_packet *forw_packet, struct batadv_hard_iface *hard_iface) { @@ -401,7 +467,13 @@ static void batadv_iv_ogm_send_to_if(struct batadv_forw_packet *forw_packet, } } -/* send a batman ogm packet */ +/** + * batadv_iv_ogm_emit() - emit an (aggregated) OGM packet + * @forw_packet: forward packet which should be emitted + * + * The @forw_packet will be emitted but not consumed. When the interface is + * no longer active, the transmission will be skipped. + */ static void batadv_iv_ogm_emit(struct batadv_forw_packet *forw_packet) { if (!forw_packet->if_incoming) { @@ -603,7 +675,14 @@ static bool batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff, return true; } -/* aggregate a new packet into the existing ogm packet */ +/** + * batadv_iv_ogm_aggregate() - append an OGM to an existing aggregated forward + * packet + * @forw_packet_aggr: aggregated forward packet to extend + * @packet_buff: pointer to the OGM to append + * @packet_len: length of the OGM to append + * @direct_link: true if @packet_buff was received as a direct link OGM + */ static void batadv_iv_ogm_aggregate(struct batadv_forw_packet *forw_packet_aggr, const unsigned char *packet_buff, int packet_len, bool direct_link) @@ -699,6 +778,21 @@ static bool batadv_iv_ogm_queue_add(struct batadv_priv *bat_priv, } } +/** + * batadv_iv_ogm_forward() - rebroadcast a received OGM + * @orig_node: originator that sent the OGM + * @ethhdr: ethernet header of the OGM packet + * @batadv_ogm_packet: OGM packet to be forwarded + * @is_single_hop_neigh: true if the OGM was received via a one-hop neighbour + * @is_from_best_next_hop: true if the sender is the currently selected best + * next hop towards @orig_node + * @if_incoming: interface where the packet was received + * @if_outgoing: interface for which the retransmission should be considered + * + * Decrement the TTL, apply the hop penalty and queue the OGM for + * retransmission. OGMs that do not arrive over the best next hop are only + * forwarded for link-quality measurement reasons (and marked accordingly). + */ static void batadv_iv_ogm_forward(struct batadv_orig_node *orig_node, const struct ethhdr *ethhdr, struct batadv_ogm_packet *batadv_ogm_packet, @@ -899,6 +993,13 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface) batadv_hardif_put(primary_if); } +/** + * batadv_iv_ogm_schedule() - schedule the next OGM transmission on an + * interface + * @hard_iface: interface for which the next OGM should be scheduled + * + * Take the OGM buffer mutex and prepare the next OGM for transmission. + */ static void batadv_iv_ogm_schedule(struct batadv_hard_iface *hard_iface) { if (hard_iface->if_status == BATADV_IF_TO_BE_REMOVED) @@ -909,6 +1010,13 @@ static void batadv_iv_ogm_schedule(struct batadv_hard_iface *hard_iface) mutex_unlock(&hard_iface->bat_iv.ogm_buff_mutex); } +/** + * batadv_iv_ogm_reschedule() - work-queue helper to rerun batadv_iv_ogm_schedule() + * @work: work item embedded in the hard interface + * + * Invoked from the per-interface reschedule_work delayed work when the + * previous attempt to enqueue the own OGM failed. + */ static void batadv_iv_ogm_reschedule(struct work_struct *work) { struct delayed_work *delayed_work = to_delayed_work(work); @@ -1765,6 +1873,14 @@ static void batadv_iv_ogm_process(const struct sk_buff *skb, int ogm_offset, batadv_orig_node_put(orig_node); } +/** + * batadv_iv_send_outstanding_bat_ogm_packet() - work-queue helper to emit a + * queued forward packet + * @work: work item embedded in the forward packet + * + * Emit the queued OGM forward packet and, for own primary-interface packets, + * schedule the next periodic OGM. The forward packet is freed afterwards. + */ static void batadv_iv_send_outstanding_bat_ogm_packet(struct work_struct *work) { struct delayed_work *delayed_work; @@ -1803,6 +1919,17 @@ static void batadv_iv_send_outstanding_bat_ogm_packet(struct work_struct *work) batadv_forw_packet_free(forw_packet, dropped); } +/** + * batadv_iv_ogm_receive() - receive a B.A.T.M.A.N. IV OGM packet + * @skb: skb containing the OGM packet + * @if_incoming: interface where the packet was received + * + * Validate the packet, then split the aggregated OGM packet into individual + * OGMs and hand each of them to batadv_iv_ogm_process(). Ownership of @skb is + * always taken over by this function. + * + * Return: NET_RX_SUCCESS or NET_RX_DROP + */ static int batadv_iv_ogm_receive(struct sk_buff *skb, struct batadv_hard_iface *if_incoming) { @@ -2310,6 +2437,12 @@ batadv_iv_ogm_neigh_is_sob(struct batadv_neigh_node *neigh1, return ret; } +/** + * batadv_iv_iface_enabled() - notification handler for activated interfaces + * @hard_iface: interface that was just activated + * + * Set up the per-interface reschedule work and start sending periodic OGMs. + */ static void batadv_iv_iface_enabled(struct batadv_hard_iface *hard_iface) { INIT_DELAYED_WORK(&hard_iface->bat_iv.reschedule_work, batadv_iv_ogm_reschedule); @@ -2328,6 +2461,14 @@ static void batadv_iv_init_sel_class(struct batadv_priv *bat_priv) WRITE_ONCE(bat_priv->gw.sel_class, 20); } +/** + * batadv_iv_gw_get_best_gw_node() - retrieve the best gateway node based on + * the B.A.T.M.A.N. IV metric and the configured GW selection class + * @bat_priv: the bat priv with all the mesh interface information + * + * Return: gateway node with the highest score for the current selection class, + * or NULL if no eligible gateway exists. + */ static struct batadv_gw_node * batadv_iv_gw_get_best_gw_node(struct batadv_priv *bat_priv) { @@ -2405,6 +2546,19 @@ batadv_iv_gw_get_best_gw_node(struct batadv_priv *bat_priv) return curr_gw; } +/** + * batadv_iv_gw_is_eligible() - check whether a new gateway should replace the + * currently selected one + * @bat_priv: the bat priv with all the mesh interface information + * @curr_gw_orig: originator of the currently selected gateway + * @orig_node: originator of the gateway candidate + * + * Compare the TQ values of @curr_gw_orig and @orig_node, taking the configured + * gateway selection class into account. + * + * Return: true if @orig_node should take over as the active gateway, false + * otherwise + */ static bool batadv_iv_gw_is_eligible(struct batadv_priv *bat_priv, struct batadv_orig_node *curr_gw_orig, struct batadv_orig_node *orig_node) diff --git a/net/batman-adv/bat_v.c b/net/batman-adv/bat_v.c index db6f5bdcaa98..0068f0e238da 100644 --- a/net/batman-adv/bat_v.c +++ b/net/batman-adv/bat_v.c @@ -41,6 +41,13 @@ #include "netlink.h" #include "originator.h" +/** + * batadv_v_iface_activate() - finalise the activation of a hard interface + * @hard_iface: interface to activate + * + * Reuse the currently selected primary interface to seed the ELP packet. + * Immediately activate the interface. + */ static void batadv_v_iface_activate(struct batadv_hard_iface *hard_iface) { struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); @@ -61,6 +68,16 @@ static void batadv_v_iface_activate(struct batadv_hard_iface *hard_iface) hard_iface->if_status = BATADV_IF_ACTIVE; } +/** + * batadv_v_iface_enable() - enable the B.A.T.M.A.N. V protocol on an + * interface + * @hard_iface: interface to enable + * + * Enable the ELP and OGM components for @hard_iface. On error, the partially + * enabled state is rolled back before returning. + * + * Return: 0 on success or negative error number in case of failure + */ static int batadv_v_iface_enable(struct batadv_hard_iface *hard_iface) { int ret; @@ -76,12 +93,21 @@ static int batadv_v_iface_enable(struct batadv_hard_iface *hard_iface) return ret; } +/** + * batadv_v_iface_disable() - disable the B.A.T.M.A.N. V protocol on an + * interface + * @hard_iface: interface to disable + */ static void batadv_v_iface_disable(struct batadv_hard_iface *hard_iface) { batadv_v_ogm_iface_disable(hard_iface); batadv_v_elp_iface_disable(hard_iface); } +/** + * batadv_v_primary_iface_set() - apply primary interface state + * @hard_iface: interface which just became the primary + */ static void batadv_v_primary_iface_set(struct batadv_hard_iface *hard_iface) { batadv_v_elp_primary_iface_set(hard_iface); @@ -109,6 +135,11 @@ static void batadv_v_iface_update_mac(struct batadv_hard_iface *hard_iface) batadv_hardif_put(primary_if); } +/** + * batadv_v_hardif_neigh_init() - initialise the B.A.T.M.A.N. V state of a + * hard interface neighbour + * @hardif_neigh: hard interface neighbour to initialise + */ static void batadv_v_hardif_neigh_init(struct batadv_hardif_neigh_node *hardif_neigh) { @@ -444,6 +475,16 @@ batadv_v_orig_dump(struct sk_buff *msg, struct netlink_callback *cb, cb->args[2] = sub; } +/** + * batadv_v_neigh_cmp() - compare two B.A.T.M.A.N. V neighbours by throughput + * @neigh1: first neighbour to compare + * @if_outgoing1: outgoing interface to use for @neigh1 + * @neigh2: second neighbour to compare + * @if_outgoing2: outgoing interface to use for @neigh2 + * + * Return: a positive value if @neigh1 is better, a negative value if @neigh2 + * is better and 0 if both have equal throughput + */ static int batadv_v_neigh_cmp(struct batadv_neigh_node *neigh1, struct batadv_hard_iface *if_outgoing1, struct batadv_neigh_node *neigh2, @@ -469,6 +510,17 @@ static int batadv_v_neigh_cmp(struct batadv_neigh_node *neigh1, return ret; } +/** + * batadv_v_neigh_is_sob() - check whether two B.A.T.M.A.N. V neighbours have + * a similar or better throughput + * @neigh1: first neighbour to compare + * @if_outgoing1: outgoing interface to use for @neigh1 + * @neigh2: second neighbour to compare + * @if_outgoing2: outgoing interface to use for @neigh2 + * + * Return: true if the throughput of @neigh2 is at least 3/4 of the + * @neigh1 throughput + */ static bool batadv_v_neigh_is_sob(struct batadv_neigh_node *neigh1, struct batadv_hard_iface *if_outgoing1, struct batadv_neigh_node *neigh2, diff --git a/net/batman-adv/bitarray.c b/net/batman-adv/bitarray.c index 67cb356332bf..f814756d9533 100644 --- a/net/batman-adv/bitarray.c +++ b/net/batman-adv/bitarray.c @@ -11,7 +11,14 @@ #include "log.h" -/* shift the packet array by n places. */ +/** + * batadv_bitmap_shift_left() - shift the sequence number bitmap left + * @seq_bits: the sequence number bitmap to shift + * @n: number of positions to shift left + * + * Shift @seq_bits by @n positions. No-op if @n is not within the bounds of + * the bitmap. + */ static void batadv_bitmap_shift_left(unsigned long *seq_bits, s32 n) { if (n <= 0 || n >= BATADV_TQ_LOCAL_WINDOW_SIZE) diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c index c5fca53f3074..d284b090fdf1 100644 --- a/net/batman-adv/distributed-arp-table.c +++ b/net/batman-adv/distributed-arp-table.c @@ -50,27 +50,65 @@ #include "translation-table.h" #include "tvlv.h" +/** + * enum batadv_bootpop - BOOTP/DHCP message op codes + */ enum batadv_bootpop { + /** @BATADV_BOOTREPLY: server-to-client reply */ BATADV_BOOTREPLY = 2, }; +/** + * enum batadv_boothtype - BOOTP/DHCP hardware address types + */ enum batadv_boothtype { + /** @BATADV_HTYPE_ETHERNET: Ethernet (10Mb) */ BATADV_HTYPE_ETHERNET = 1, }; +/** + * enum batadv_dhcpoptioncode - DHCP option codes relevant for batman-adv DAT + */ enum batadv_dhcpoptioncode { + /** @BATADV_DHCP_OPT_PAD: pad option */ BATADV_DHCP_OPT_PAD = 0, + + /** @BATADV_DHCP_OPT_MSG_TYPE: DHCP message type option */ BATADV_DHCP_OPT_MSG_TYPE = 53, + + /** @BATADV_DHCP_OPT_END: end of options marker */ BATADV_DHCP_OPT_END = 255, }; +/** + * enum batadv_dhcptype - DHCP message types relevant for batman-adv DAT + */ enum batadv_dhcptype { + /** @BATADV_DHCPACK: DHCPACK message */ BATADV_DHCPACK = 5, }; /* { 99, 130, 83, 99 } */ #define BATADV_DHCP_MAGIC 1669485411 +/** + * struct batadv_dhcp_packet - BOOTP/DHCP packet header + * @op: message op code / message type + * @htype: hardware address type + * @hlen: hardware address length + * @hops: number of relay hops + * @xid: transaction identifier + * @secs: seconds elapsed since client started trying to boot + * @flags: BOOTP/DHCP flags + * @ciaddr: client IP address + * @yiaddr: "your" (client) IP address as assigned by the server + * @siaddr: IP address of next server to use in bootstrap + * @giaddr: relay agent IP address + * @chaddr: client hardware address + * @sname: optional server host name + * @file: boot file name + * @magic: BOOTP/DHCP magic cookie identifying the start of options + */ struct batadv_dhcp_packet { __u8 op; __u8 htype; diff --git a/net/batman-adv/gateway_client.c b/net/batman-adv/gateway_client.c index a5ac82eabd25..48fc711b8fd6 100644 --- a/net/batman-adv/gateway_client.c +++ b/net/batman-adv/gateway_client.c @@ -125,6 +125,14 @@ batadv_gw_get_selected_orig(struct batadv_priv *bat_priv) return orig_node; } +/** + * batadv_gw_select() - select a new currently active gateway + * @bat_priv: the bat priv with all the mesh interface information + * @new_gw_node: gateway node to be set as the current gateway, may be NULL + * + * Atomically replace the currently active gateway with @new_gw_node and drop + * the reference to the previous one. + */ static void batadv_gw_select(struct batadv_priv *bat_priv, struct batadv_gw_node *new_gw_node) { diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c index b6867576bbaf..1950b8809d99 100644 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@ -197,6 +197,17 @@ static bool batadv_is_on_batman_iface(const struct net_device *net_dev) return ret; } +/** + * batadv_is_valid_iface() - check whether a net_device can be used as a hard + * interface + * @net_dev: the net_device to check + * + * Refuse loopback devices, non-Ethernet devices, devices with a non-Ethernet + * address length and any device that is already part of a batman-adv mesh + * interface stack. + * + * Return: true if @net_dev can be used as a hard interface, false otherwise + */ static bool batadv_is_valid_iface(const struct net_device *net_dev) { if (net_dev->flags & IFF_LOOPBACK) @@ -467,6 +478,14 @@ int batadv_hardif_no_broadcast(struct batadv_hard_iface *if_outgoing, return ret; } +/** + * batadv_hardif_get_active() - retrieve an active hard interface for a mesh + * interface + * @mesh_iface: mesh interface to search + * + * Return: first hard interface in BATADV_IF_ACTIVE state attached to + * @mesh_iface, or NULL if none is active. + */ static struct batadv_hard_iface * batadv_hardif_get_active(struct net_device *mesh_iface) { @@ -487,6 +506,15 @@ batadv_hardif_get_active(struct net_device *mesh_iface) return hard_iface; } +/** + * batadv_primary_if_update_addr() - propagate the new primary interface MAC + * address to all dependent components + * @bat_priv: the bat priv with all the mesh interface information + * @oldif: previously used primary interface, or NULL if none + * + * Inform DAT and BLA that the originator address has changed so they can + * adjust their state accordingly. + */ static void batadv_primary_if_update_addr(struct batadv_priv *bat_priv, struct batadv_hard_iface *oldif) { @@ -502,6 +530,15 @@ static void batadv_primary_if_update_addr(struct batadv_priv *bat_priv, batadv_hardif_put(primary_if); } +/** + * batadv_primary_if_select() - select the new primary interface + * @bat_priv: the bat priv with all the mesh interface information + * @new_hard_iface: new primary interface, may be NULL + * + * Replace the currently selected primary interface with @new_hard_iface, + * invoke the algorithm-specific primary_set hook and update the originator + * MAC address. + */ static void batadv_primary_if_select(struct batadv_priv *bat_priv, struct batadv_hard_iface *new_hard_iface) { @@ -525,6 +562,13 @@ static void batadv_primary_if_select(struct batadv_priv *bat_priv, batadv_hardif_put(curr_hard_iface); } +/** + * batadv_hardif_is_iface_up() - check whether the underlying net_device of a + * hard interface is up + * @hard_iface: the hard interface to check + * + * Return: true if the underlying net_device has the IFF_UP flag set + */ static bool batadv_hardif_is_iface_up(const struct batadv_hard_iface *hard_iface) { @@ -534,6 +578,15 @@ batadv_hardif_is_iface_up(const struct batadv_hard_iface *hard_iface) return false; } +/** + * batadv_check_known_mac_addr() - warn about duplicate hard interface MAC + * addresses + * @hard_iface: hard interface that was just added or had its MAC changed + * + * Iterate over all hard interfaces of the same mesh interface and emit a + * warning if another in-use interface shares the same MAC address as + * @hard_iface. + */ static void batadv_check_known_mac_addr(const struct batadv_hard_iface *hard_iface) { struct net_device *mesh_iface = hard_iface->mesh_iface; @@ -666,6 +719,15 @@ void batadv_update_min_mtu(struct net_device *mesh_iface) batadv_tt_local_resize_to_mtu(mesh_iface); } +/** + * batadv_hardif_activate_interface() - move a hard interface to the active + * state + * @hard_iface: the interface that has come up + * + * Activate an interface, select it as the primary interface if no + * primary is currently active and invoke the algorithm-specific + * activate hook. + */ static void batadv_hardif_activate_interface(struct batadv_hard_iface *hard_iface) { @@ -699,6 +761,14 @@ batadv_hardif_activate_interface(struct batadv_hard_iface *hard_iface) batadv_hardif_put(primary_if); } +/** + * batadv_hardif_deactivate_interface() - move a hard interface to the + * inactive state + * @hard_iface: the interface that went down + * + * Demote @hard_iface to BATADV_IF_INACTIVE and recalculate the mesh minimum + * MTU based on the remaining active interfaces. + */ static void batadv_hardif_deactivate_interface(struct batadv_hard_iface *hard_iface) { @@ -1020,6 +1090,18 @@ static void batadv_wifi_net_device_event(unsigned long event, } } +/** + * batadv_hard_if_event() - netdevice notifier callback for hard interfaces + * @this: notifier block (unused) + * @event: the NETDEV_* event to handle + * @ptr: notifier info pointing to the affected net_device + * + * Dispatch netdevice events that affect potential or existing hard + * interfaces. Mesh interfaces are handled separately by + * batadv_hard_if_event_meshif(). + * + * Return: NOTIFY_DONE + */ static int batadv_hard_if_event(struct notifier_block *this, unsigned long event, void *ptr) { diff --git a/net/batman-adv/hash.c b/net/batman-adv/hash.c index 759fa29176db..b9a652bd523b 100644 --- a/net/batman-adv/hash.c +++ b/net/batman-adv/hash.c @@ -11,7 +11,10 @@ #include #include -/* clears the hash */ +/** + * batadv_hash_init() - clear all buckets of a hashtable + * @hash: hashtable to clear + */ static void batadv_hash_init(struct batadv_hashtable *hash) { u32 i; diff --git a/net/batman-adv/hash.h b/net/batman-adv/hash.h index 86a2c20000dc..89cbb56cdbe1 100644 --- a/net/batman-adv/hash.h +++ b/net/batman-adv/hash.h @@ -18,21 +18,35 @@ #include #include -/* callback to a compare function. should compare 2 element data for their - * keys +/** + * typedef batadv_hashdata_compare_cb - hash element comparison callback + * @node: hlist node of the element currently stored in the bucket + * @key: opaque payload to compare @node's key against * - * Return: true if same and false if not same + * Compare hash element by its keys. + * + * Return: true if both elements are considered equal, false otherwise. */ -typedef bool (*batadv_hashdata_compare_cb)(const struct hlist_node *, - const void *); +typedef bool (*batadv_hashdata_compare_cb)(const struct hlist_node *node, + const void *key); -/* the hashfunction +/** + * typedef batadv_hashdata_choose_cb - hash bucket selection callback + * @key: opaque payload whose key selects the bucket + * @size: number of buckets in the hash table * - * Return: an index based on the key in the data of the first argument and the - * size the second + * Return: bucket index derived from the key in @key and the table @size. */ -typedef u32 (*batadv_hashdata_choose_cb)(const void *, u32); -typedef void (*batadv_hashdata_free_cb)(struct hlist_node *, void *); +typedef u32 (*batadv_hashdata_choose_cb)(const void *key, u32 size); + +/** + * typedef batadv_hashdata_free_cb - hash element free callback + * @node: hlist node of the element being removed + * @arg: opaque caller-supplied argument forwarded from the caller + * + * Release a previously inserted hash element. + */ +typedef void (*batadv_hashdata_free_cb)(struct hlist_node *node, void *arg); /** * struct batadv_hashtable - Wrapper of simple hlist based hashtable diff --git a/net/batman-adv/main.c b/net/batman-adv/main.c index 73becb054948..78b81daaeff9 100644 --- a/net/batman-adv/main.c +++ b/net/batman-adv/main.c @@ -82,6 +82,14 @@ static char *batadv_uev_type_str[] = { "bla", }; +/** + * batadv_init() - batman-adv module init function + * + * Initialise the global state used by all mesh interfaces, register the + * netdevice notifier and the netlink/rtnl family. + * + * Return: 0 on success or negative error number in case of failure + */ static int __init batadv_init(void) { int ret; @@ -128,6 +136,12 @@ static int __init batadv_init(void) return ret; } +/** + * batadv_exit() - batman-adv module exit function + * + * Unregister the netdevice notifier and tear down all global state allocated + * by batadv_init(). + */ static void __exit batadv_exit(void) { batadv_netlink_unregister(); @@ -398,6 +412,17 @@ void batadv_skb_set_priority(struct sk_buff *skb, int offset) skb->priority = prio + 256; } +/** + * batadv_recv_unhandled_packet() - default RX handler for unsupported packet + * types + * @skb: incoming packet + * @recv_if: interface on which the packet was received (unused) + * + * Drop incoming packets whose packet_type has no dedicated RX handler + * registered. + * + * Return: NET_RX_DROP + */ static int batadv_recv_unhandled_packet(struct sk_buff *skb, struct batadv_hard_iface *recv_if) { @@ -406,10 +431,6 @@ static int batadv_recv_unhandled_packet(struct sk_buff *skb, return NET_RX_DROP; } -/* incoming packets with the batman ethertype received on any active hard - * interface - */ - /** * batadv_batman_skb_recv() - Handle incoming message from an hard interface * @skb: the received packet @@ -492,6 +513,13 @@ int batadv_batman_skb_recv(struct sk_buff *skb, struct net_device *dev, return NET_RX_DROP; } +/** + * batadv_recv_handler_init() - initialise the RX handler dispatch table + * + * Initialise all entries of the RX handler table as either "unhandled" or with + * protocol indepentend handlers, and perform compile-time size sanity checks on + * all on-wire packet structs. + */ static void batadv_recv_handler_init(void) { int i; diff --git a/net/batman-adv/mesh-interface.c b/net/batman-adv/mesh-interface.c index 1dc6cfae9e9a..effa37ff3bb1 100644 --- a/net/batman-adv/mesh-interface.c +++ b/net/batman-adv/mesh-interface.c @@ -98,6 +98,15 @@ static u64 batadv_sum_counter(struct batadv_priv *bat_priv, size_t idx) return sum; } +/** + * batadv_interface_stats() - return netdev stats for a mesh interface + * @dev: the mesh interface to query + * + * Aggregate the per-CPU traffic counters into the standard netdev stats + * structure. + * + * Return: pointer to the populated net_device_stats structure + */ static struct net_device_stats *batadv_interface_stats(struct net_device *dev) { struct batadv_priv *bat_priv = netdev_priv(dev); @@ -111,6 +120,18 @@ static struct net_device_stats *batadv_interface_stats(struct net_device *dev) return stats; } +/** + * batadv_interface_set_mac_addr() - change the MAC address of a mesh + * interface + * @dev: the mesh interface to modify + * @p: pointer to a struct sockaddr holding the new MAC address + * + * Replace the MAC address of the mesh interface. If the mesh is already + * active, also update the local translation table entries for all configured + * VLANs so that the new MAC is announced and the old one is removed. + * + * Return: 0 on success or negative error number in case of failure + */ static int batadv_interface_set_mac_addr(struct net_device *dev, void *p) { struct batadv_priv *bat_priv = netdev_priv(dev); @@ -140,6 +161,16 @@ static int batadv_interface_set_mac_addr(struct net_device *dev, void *p) return 0; } +/** + * batadv_interface_change_mtu() - change the MTU of a mesh interface + * @dev: the mesh interface to modify + * @new_mtu: requested new MTU value + * + * Validate that @new_mtu fits within the range supported by the configured + * hard interfaces and remember it as the user-configured MTU. + * + * Return: 0 on success or -EINVAL if @new_mtu is out of range + */ static int batadv_interface_change_mtu(struct net_device *dev, int new_mtu) { struct batadv_priv *bat_priv = netdev_priv(dev); @@ -166,6 +197,13 @@ static void batadv_interface_set_rx_mode(struct net_device *dev) { } +/** + * batadv_interface_tx() - transmit a frame on a mesh interface + * @skb: the frame to send + * @mesh_iface: the mesh interface the frame was queued on + * + * Return: NETDEV_TX_OK on success + */ static netdev_tx_t batadv_interface_tx(struct sk_buff *skb, struct net_device *mesh_iface) { @@ -883,6 +921,11 @@ static const struct net_device_ops batadv_netdev_ops = { .ndo_del_slave = batadv_meshif_slave_del, }; +/** + * batadv_get_drvinfo() - ethtool driver info handler for mesh interfaces + * @dev: the mesh interface (unused) + * @info: ethtool_drvinfo struct to populate + */ static void batadv_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info) { @@ -943,6 +986,12 @@ static const struct { #endif }; +/** + * batadv_get_strings() - ethtool string handler for mesh interfaces + * @dev: the mesh interface (unused) + * @stringset: ethtool string set to retrieve + * @data: buffer to copy the requested string set into + */ static void batadv_get_strings(struct net_device *dev, u32 stringset, u8 *data) { if (stringset == ETH_SS_STATS) @@ -950,6 +999,12 @@ static void batadv_get_strings(struct net_device *dev, u32 stringset, u8 *data) sizeof(batadv_counters_strings)); } +/** + * batadv_get_ethtool_stats() - ethtool stats handler for mesh interfaces + * @dev: the mesh interface to query + * @stats: ethtool_stats struct (unused) + * @data: destination array for the gathered counter values + */ static void batadv_get_ethtool_stats(struct net_device *dev, struct ethtool_stats *stats, u64 *data) { @@ -960,6 +1015,14 @@ static void batadv_get_ethtool_stats(struct net_device *dev, data[i] = batadv_sum_counter(bat_priv, i); } +/** + * batadv_get_sset_count() - ethtool stringset size handler for mesh interfaces + * @dev: the mesh interface (unused) + * @stringset: ethtool string set to query + * + * Return: number of entries in @stringset or -EOPNOTSUPP if @stringset is not + * supported + */ static int batadv_get_sset_count(struct net_device *dev, int stringset) { if (stringset == ETH_SS_STATS) diff --git a/net/batman-adv/netlink.c b/net/batman-adv/netlink.c index d2bc48c70714..f4aa7d205510 100644 --- a/net/batman-adv/netlink.c +++ b/net/batman-adv/netlink.c @@ -52,9 +52,15 @@ struct genl_family batadv_netlink_family; -/* multicast groups */ +/** + * enum batadv_netlink_multicast_groups - batman-adv generic netlink multicast + * groups + */ enum batadv_netlink_multicast_groups { + /** @BATADV_NL_MCGRP_CONFIG: configuration change notifications */ BATADV_NL_MCGRP_CONFIG, + + /** @BATADV_NL_MCGRP_TPMETER: throughput meter result notifications */ BATADV_NL_MCGRP_TPMETER, }; diff --git a/net/batman-adv/originator.c b/net/batman-adv/originator.c index 48f837cf665a..bab1eb61d9ef 100644 --- a/net/batman-adv/originator.c +++ b/net/batman-adv/originator.c @@ -1295,6 +1295,13 @@ void batadv_purge_orig_ref(struct batadv_priv *bat_priv) batadv_gw_election(bat_priv); } +/** + * batadv_purge_orig() - periodic worker to purge stale originator entries + * @work: delayed work embedded in the bat_priv + * + * Invoke batadv_purge_orig_ref() to drop stale originators and reschedule the + * next run after BATADV_ORIG_WORK_PERIOD milliseconds. + */ static void batadv_purge_orig(struct work_struct *work) { struct delayed_work *delayed_work; diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c index a9d657f2a831..b62a984e73dc 100644 --- a/net/batman-adv/routing.c +++ b/net/batman-adv/routing.c @@ -328,6 +328,16 @@ static int batadv_recv_my_icmp_packet(struct batadv_priv *bat_priv, return ret; } +/** + * batadv_recv_icmp_ttl_exceeded() - handle an ICMP packet that hit TTL 0 + * @bat_priv: the bat priv with all the mesh interface information + * @skb: ICMP packet whose TTL has expired + * + * For traceroute-style ICMP echo requests, send a TTL exceeded reply back to + * the source. Other ICMP types are simply dropped. + * + * Return: NET_XMIT_SUCCESS if the reply was queued, NET_RX_DROP otherwise + */ static int batadv_recv_icmp_ttl_exceeded(struct batadv_priv *bat_priv, struct sk_buff *skb) { @@ -706,6 +716,18 @@ batadv_find_router(struct batadv_priv *bat_priv, return router; } +/** + * batadv_route_unicast_packet() - forward a unicast packet towards its + * destination originator + * @skb: the received unicast packet + * @recv_if: interface on which the packet was received + * + * Decrement the TTL, look up the originator for the destination address and + * hand the packet over to batadv_send_skb_to_orig() for transmission. Drop + * the packet when the TTL is exhausted or no route exists. + * + * Return: NET_RX_SUCCESS if the packet was forwarded, NET_RX_DROP otherwise + */ static int batadv_route_unicast_packet(struct sk_buff *skb, struct batadv_hard_iface *recv_if) { @@ -836,6 +858,15 @@ batadv_reroute_unicast_packet(struct batadv_priv *bat_priv, struct sk_buff *skb, return ret; } +/** + * batadv_check_unicast_ttvn() - check and adjust the TTVN of a unicast packet + * @bat_priv: the bat priv with all the mesh interface information + * @skb: the unicast packet to check + * @hdr_len: length of the unicast header preceding the payload + * + * Return: true if the packet may be processed further, false if has to be + * dropped by the caller + */ static bool batadv_check_unicast_ttvn(struct batadv_priv *bat_priv, struct sk_buff *skb, int hdr_len) { diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index dae5e1d8c038..b12ec8d1b545 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -535,6 +535,12 @@ static int batadv_tt_local_table_transmit_size(struct batadv_priv *bat_priv) return hdr_size + batadv_tt_len(tt_local_entries); } +/** + * batadv_tt_local_init() - allocate and initialise the local translation table + * @bat_priv: the bat priv with all the mesh interface information + * + * Return: 0 on success or -ENOMEM in case of allocation failure + */ static int batadv_tt_local_init(struct batadv_priv *bat_priv) { if (bat_priv->tt.local_hash) @@ -551,6 +557,15 @@ static int batadv_tt_local_init(struct batadv_priv *bat_priv) return 0; } +/** + * batadv_tt_global_free() - drop a global translation table entry + * @bat_priv: the bat priv with all the mesh interface information + * @tt_global: the global TT entry to remove + * @message: debug message explaining why the entry is being removed + * + * Remove @tt_global from the global TT hash and drop the reference held by + * the hash. + */ static void batadv_tt_global_free(struct batadv_priv *bat_priv, struct batadv_tt_global_entry *tt_global, const char *message) @@ -1224,6 +1239,17 @@ int batadv_tt_local_dump(struct sk_buff *msg, struct netlink_callback *cb) return ret; } +/** + * batadv_tt_local_set_pending() - mark a local TT entry as pending removal + * @bat_priv: the bat priv with all the mesh interface information + * @tt_local_entry: local TT entry to mark + * @flags: TT change flags to announce together with the pending removal + * @message: debug message describing the reason for the change + * + * Schedule the TT change announcement and set BATADV_TT_CLIENT_PENDING on the + * entry. The entry is kept in the local table until the next TTVN increment + * so that a consistency-check response can still be answered. + */ static void batadv_tt_local_set_pending(struct batadv_priv *bat_priv, struct batadv_tt_local_entry *tt_local_entry, @@ -1367,6 +1393,13 @@ static void batadv_tt_local_purge(struct batadv_priv *bat_priv, } } +/** + * batadv_tt_local_table_free() - release the local translation table + * @bat_priv: the bat priv with all the mesh interface information + * + * Drop every entry of the local TT hash, free their references and finally + * release the hashtable itself. + */ static void batadv_tt_local_table_free(struct batadv_priv *bat_priv) { struct batadv_hashtable *hash; @@ -1404,6 +1437,13 @@ static void batadv_tt_local_table_free(struct batadv_priv *bat_priv) bat_priv->tt.local_hash = NULL; } +/** + * batadv_tt_global_init() - allocate and initialise the global translation + * table + * @bat_priv: the bat priv with all the mesh interface information + * + * Return: 0 on success or -ENOMEM in case of allocation failure + */ static int batadv_tt_global_init(struct batadv_priv *bat_priv) { if (bat_priv->tt.global_hash) @@ -1420,6 +1460,12 @@ static int batadv_tt_global_init(struct batadv_priv *bat_priv) return 0; } +/** + * batadv_tt_changes_list_free() - drop all pending local TT changes + * @bat_priv: the bat priv with all the mesh interface information + * + * Discard every queued local TT change and reset the pending change counter. + */ static void batadv_tt_changes_list_free(struct batadv_priv *bat_priv) { struct batadv_tt_change_node *entry, *safe; @@ -2024,7 +2070,11 @@ _batadv_tt_global_del_orig_entry(struct batadv_tt_global_entry *tt_global_entry, batadv_tt_orig_list_entry_put(orig_entry); } -/* deletes the orig list of a tt_global_entry */ +/** + * batadv_tt_global_del_orig_list() - drop every orig_list_entry of a global + * TT entry + * @tt_global_entry: the global TT entry to clear + */ static void batadv_tt_global_del_orig_list(struct batadv_tt_global_entry *tt_global_entry) { @@ -2077,9 +2127,17 @@ batadv_tt_global_del_orig_node(struct batadv_priv *bat_priv, spin_unlock_bh(&tt_global_entry->list_lock); } -/* If the client is to be deleted, we check if it is the last origantor entry - * within tt_global entry. If yes, we set the BATADV_TT_CLIENT_ROAM flag and the - * timer, otherwise we simply remove the originator scheduled for deletion. +/** + * batadv_tt_global_del_roaming() - remove a roaming client from a global TT + * entry + * @bat_priv: the bat priv with all the mesh interface information + * @tt_global_entry: the global TT entry of the roaming client + * @orig_node: the originator that the client has roamed away from + * @message: debug message describing the reason for the change + * + * If @orig_node was the last announced source for the client, mark the entry + * for roaming so it can be cleaned up after the roaming timer expires. + * Otherwise simply remove the orig_node entry from the announcer list. */ static void batadv_tt_global_del_roaming(struct batadv_priv *bat_priv, @@ -2241,6 +2299,15 @@ void batadv_tt_global_del_orig(struct batadv_priv *bat_priv, clear_bit(BATADV_ORIG_CAPA_HAS_TT, &orig_node->capa_initialized); } +/** + * batadv_tt_global_to_purge() - check whether a global TT entry has to be + * purged + * @tt_global: global TT entry under consideration + * @msg: storage for a pointer to a human readable reason on return + * + * Return: true if the entry should be purged because its roaming or temporary + * timer has elapsed; false otherwise + */ static bool batadv_tt_global_to_purge(struct batadv_tt_global_entry *tt_global, char **msg) { @@ -2263,6 +2330,13 @@ static bool batadv_tt_global_to_purge(struct batadv_tt_global_entry *tt_global, return purge; } +/** + * batadv_tt_global_purge() - purge expired global translation table entries + * @bat_priv: the bat priv with all the mesh interface information + * + * Iterate over the global translation table and drop every entry that the + * roaming or temporary timer has expired for. + */ static void batadv_tt_global_purge(struct batadv_priv *bat_priv) { struct batadv_hashtable *hash = bat_priv->tt.global_hash; @@ -2302,6 +2376,13 @@ static void batadv_tt_global_purge(struct batadv_priv *bat_priv) } } +/** + * batadv_tt_global_table_free() - release the global translation table + * @bat_priv: the bat priv with all the mesh interface information + * + * Drop every entry of the global TT hash, free their references and finally + * release the hashtable itself. + */ static void batadv_tt_global_table_free(struct batadv_priv *bat_priv) { struct batadv_hashtable *hash; @@ -2338,6 +2419,16 @@ static void batadv_tt_global_table_free(struct batadv_priv *bat_priv) bat_priv->tt.global_hash = NULL; } +/** + * _batadv_is_ap_isolated() - check whether two clients are AP-isolated from + * each other + * @tt_local_entry: local TT entry of the sending client + * @tt_global_entry: global TT entry of the destination client + * + * Return: true if traffic between the two clients should be dropped because + * either both are WiFi clients or both carry the ISOLATION flag; false + * otherwise + */ static bool _batadv_is_ap_isolated(struct batadv_tt_local_entry *tt_local_entry, struct batadv_tt_global_entry *tt_global_entry) @@ -2590,6 +2681,10 @@ static void batadv_tt_req_node_put(struct batadv_tt_req_node *tt_req_node) kref_put(&tt_req_node->refcount, batadv_tt_req_node_release); } +/** + * batadv_tt_req_list_free() - drop all pending TT requests + * @bat_priv: the bat priv with all the mesh interface information + */ static void batadv_tt_req_list_free(struct batadv_priv *bat_priv) { struct batadv_tt_req_node *node; @@ -2605,6 +2700,18 @@ static void batadv_tt_req_list_free(struct batadv_priv *bat_priv) spin_unlock_bh(&bat_priv->tt.req_list_lock); } +/** + * batadv_tt_save_orig_buffer() - cache the latest TT TVLV payload of an + * originator + * @bat_priv: the bat priv with all the mesh interface information + * @orig_node: originator for which the buffer should be created + * @tt_buff: pointer to the TT TVLV payload to cache + * @tt_buff_len: length of @tt_buff in bytes + * + * Replace the previously cached TT payload of @orig_node with a copy of + * @tt_buff. The buffer is left untouched when @tt_buff_len is 0 so that + * empty OGM updates do not discard the previously cached data. + */ static void batadv_tt_save_orig_buffer(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node, const void *tt_buff, @@ -2626,6 +2733,10 @@ static void batadv_tt_save_orig_buffer(struct batadv_priv *bat_priv, spin_unlock_bh(&orig_node->tt_buff_lock); } +/** + * batadv_tt_req_purge() - drop timed-out TT requests + * @bat_priv: the bat priv with all the mesh interface information + */ static void batadv_tt_req_purge(struct batadv_priv *bat_priv) { struct batadv_tt_req_node *node; @@ -3253,6 +3364,18 @@ static bool batadv_send_tt_response(struct batadv_priv *bat_priv, req_dst); } +/** + * _batadv_tt_update_changes() - apply a list of TT changes to the global TT + * @bat_priv: the bat priv with all the mesh interface information + * @orig_node: originator announcing the changes + * @tt_change: array of TT change entries to apply + * @tt_num_changes: number of entries in @tt_change + * @ttvn: TTVN of @orig_node corresponding to @tt_change + * + * Walk @tt_change and add/remove the announced clients in the global TT. + * Abort early without marking the @orig_node TT as initialized if adding + * an entry fails, so that the next TT request can re-sync the full table. + */ static void _batadv_tt_update_changes(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node, struct batadv_tvlv_tt_change *tt_change, @@ -3286,6 +3409,18 @@ static void _batadv_tt_update_changes(struct batadv_priv *bat_priv, set_bit(BATADV_ORIG_CAPA_HAS_TT, &orig_node->capa_initialized); } +/** + * batadv_tt_fill_gtable() - replace the cached TT of an originator with a + * full table response + * @bat_priv: the bat priv with all the mesh interface information + * @tt_change: array of TT change entries describing the full table + * @ttvn: TTVN announced together with the full table + * @resp_src: MAC address of the responder + * @num_entries: number of entries in @tt_change + * + * Drop the previously known global TT entries of @resp_src and replace them + * with the entries from a freshly received full TT response. + */ static void batadv_tt_fill_gtable(struct batadv_priv *bat_priv, struct batadv_tvlv_tt_change *tt_change, u8 ttvn, u8 *resp_src, @@ -3316,6 +3451,15 @@ static void batadv_tt_fill_gtable(struct batadv_priv *bat_priv, batadv_orig_node_put(orig_node); } +/** + * batadv_tt_update_changes() - apply an incremental TT changeset to the + * global TT + * @bat_priv: the bat priv with all the mesh interface information + * @orig_node: originator announcing the changes + * @tt_num_changes: number of entries in @tt_change + * @ttvn: TTVN of @orig_node corresponding to @tt_change + * @tt_change: array of TT change entries to apply + */ static void batadv_tt_update_changes(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node, u16 tt_num_changes, u8 ttvn, @@ -3416,6 +3560,10 @@ static void batadv_handle_tt_response(struct batadv_priv *bat_priv, batadv_orig_node_put(orig_node); } +/** + * batadv_tt_roam_list_free() - drop all entries from the roaming clients list + * @bat_priv: the bat priv with all the mesh interface information + */ static void batadv_tt_roam_list_free(struct batadv_priv *bat_priv) { struct batadv_tt_roam_node *node, *safe; @@ -3430,6 +3578,10 @@ static void batadv_tt_roam_list_free(struct batadv_priv *bat_priv) spin_unlock_bh(&bat_priv->tt.roam_list_lock); } +/** + * batadv_tt_roam_purge() - drop timed-out roaming clients + * @bat_priv: the bat priv with all the mesh interface information + */ static void batadv_tt_roam_purge(struct batadv_priv *bat_priv) { struct batadv_tt_roam_node *node, *safe; @@ -3552,6 +3704,14 @@ static void batadv_send_roam_adv(struct batadv_priv *bat_priv, u8 *client, batadv_hardif_put(primary_if); } +/** + * batadv_tt_purge() - periodic worker to maintain the translation table + * @work: delayed work embedded in the per-mesh-interface TT state + * + * Purge timed-out entries from the local and global TT, drop stale TT + * requests and roaming clients, and reschedule the next run after + * BATADV_TT_WORK_PERIOD milliseconds. + */ static void batadv_tt_purge(struct work_struct *work) { struct delayed_work *delayed_work; @@ -3638,7 +3798,14 @@ static void batadv_tt_local_set_flags(struct batadv_priv *bat_priv, u16 flags, } } -/* Purge out all the tt local entries marked with BATADV_TT_CLIENT_PENDING */ +/** + * batadv_tt_local_purge_pending_clients() - finalise removal of pending local + * clients + * @bat_priv: the bat priv with all the mesh interface information + * + * Iterate over the local TT and physically remove every entry that has been + * marked as BATADV_TT_CLIENT_PENDING. + */ static void batadv_tt_local_purge_pending_clients(struct batadv_priv *bat_priv) { struct batadv_hashtable *hash = bat_priv->tt.local_hash; diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h index cd12755d21f3..42b631573512 100644 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@ -1827,10 +1827,10 @@ struct batadv_bla_claim { /** @hash_entry: hlist node for &batadv_priv_bla.claim_hash */ struct hlist_node hash_entry; - /** @refcount: number of contexts the object is used */ + /** @rcu: struct used for freeing in an RCU-safe manner */ struct rcu_head rcu; - /** @rcu: struct used for freeing in an RCU-safe manner */ + /** @refcount: number of contexts the object is used */ struct kref refcount; }; #endif From 104570fe64facb998cbe43ded28d4c6458067148 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Fri, 10 Jul 2026 22:27:36 +0200 Subject: [PATCH 0940/1433] batman-adv: fix kernel-doc for functions holding skb ownership Most functions in batman-adv will take the ownership of an skb when they receive it as argument. Their NET_RX_DROP return value is only indicating whether there was direct visible problem while processing it. The caller must not try to also free the skb when such a negative return code was received. Signed-off-by: Sven Eckelmann --- net/batman-adv/routing.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c index b62a984e73dc..de0848cce98a 100644 --- a/net/batman-adv/routing.c +++ b/net/batman-adv/routing.c @@ -263,8 +263,7 @@ static bool batadv_skb_decrement_ttl(struct sk_buff *skb) * @bat_priv: the bat priv with all the mesh interface information * @skb: icmp packet to process * - * Return: NET_RX_SUCCESS if the packet has been consumed or NET_RX_DROP - * otherwise. + * Return: NET_RX_SUCCESS on success or NET_RX_DROP in case of failure */ static int batadv_recv_my_icmp_packet(struct batadv_priv *bat_priv, struct sk_buff *skb) @@ -986,8 +985,7 @@ static bool batadv_check_unicast_ttvn(struct batadv_priv *bat_priv, * @skb: unicast tvlv packet to process * @recv_if: pointer to interface this packet was received on * - * Return: NET_RX_SUCCESS if the packet has been consumed or NET_RX_DROP - * otherwise. + * Return: NET_RX_SUCCESS on success or NET_RX_DROP in case of failure */ int batadv_recv_unhandled_unicast_packet(struct sk_buff *skb, struct batadv_hard_iface *recv_if) @@ -1120,8 +1118,7 @@ int batadv_recv_unicast_packet(struct sk_buff *skb, * @skb: unicast tvlv packet to process * @recv_if: pointer to interface this packet was received on * - * Return: NET_RX_SUCCESS if the packet has been consumed or NET_RX_DROP - * otherwise. + * Return: NET_RX_SUCCESS on success or NET_RX_DROP in case of failure */ int batadv_recv_unicast_tvlv(struct sk_buff *skb, struct batadv_hard_iface *recv_if) @@ -1177,7 +1174,7 @@ int batadv_recv_unicast_tvlv(struct sk_buff *skb, * the assembled packet will exceed our MTU; 2) Buffer fragment, if we still * lack further fragments; 3) Merge fragments, if we have all needed parts. * - * Return: NET_RX_DROP if the skb is not consumed, NET_RX_SUCCESS otherwise. + * Return: NET_RX_SUCCESS on success or NET_RX_DROP in case of failure */ int batadv_recv_frag_packet(struct sk_buff *skb, struct batadv_hard_iface *recv_if) From ed00ac0be85f76944be60e1d1a6ecfb49c5c65c1 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Sun, 28 Jun 2026 08:12:51 +0200 Subject: [PATCH 0941/1433] batman-adv: annotate functions which may reallocate the skbuff When a function is called which reallocated the skbuff, it is necessary to reacquire the pointers into the skb data. Otherwise they might cause an use-after-free. But is hard to identify such case when it is not clear that helpers are actually using skb-reallocating functions. Signed-off-by: Sven Eckelmann --- net/batman-adv/bridge_loop_avoidance.c | 15 +++++++++- net/batman-adv/distributed-arp-table.c | 40 ++++++++++++++++++++++++++ net/batman-adv/gateway_client.c | 11 +++++-- net/batman-adv/main.c | 5 ++++ net/batman-adv/mesh-interface.c | 5 ++++ net/batman-adv/multicast.c | 10 +++++-- net/batman-adv/multicast_forw.c | 10 +++++++ net/batman-adv/routing.c | 10 +++++++ 8 files changed, 101 insertions(+), 5 deletions(-) diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c index f9a1fadf8de9..247f8bb4d5fc 100644 --- a/net/batman-adv/bridge_loop_avoidance.c +++ b/net/batman-adv/bridge_loop_avoidance.c @@ -1078,6 +1078,11 @@ static int batadv_check_claim_group(struct batadv_priv *bat_priv, * @primary_if: the primary hard interface of this batman mesh interface * @skb: the frame to be checked * + * Warning: This function may reallocate the skb data buffer via + * batadv_get_vid()/... Any pointer into the skb data (e.g. obtained + * from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. + * * Return: true if it was a claim frame, otherwise return false to * tell the callee that it can use the frame on its own. */ @@ -1807,6 +1812,11 @@ bool batadv_bla_is_backbone_gw_orig(struct batadv_priv *bat_priv, u8 *orig, * @orig_node: the orig_node of the frame * @hdr_size: maximum length of the frame * + * Warning: This function may reallocate the skb data buffer via + * pskb_may_pull()/batadv_get_vid()/... Any pointer into the skb data (e.g. + * obtained from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. + * * Return: true if the orig_node is also a gateway on the mesh interface, * otherwise it returns false. */ @@ -2061,7 +2071,10 @@ bool batadv_bla_rx(struct batadv_priv *bat_priv, struct sk_buff *skb, * * in these cases, the skb is further handled by this function. * - * This call might reallocate skb data. + * Warning: This function may reallocate the skb data buffer via + * batadv_bla_process_claim()/... Any pointer into the skb data (e.g. + * obtained from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. * * Return: true if handled, otherwise it returns false and the caller shall * further process the skb. diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c index d284b090fdf1..e1176aa8683c 100644 --- a/net/batman-adv/distributed-arp-table.c +++ b/net/batman-adv/distributed-arp-table.c @@ -1031,6 +1031,11 @@ int batadv_dat_cache_dump(struct sk_buff *msg, struct netlink_callback *cb) * @skb: packet to analyse * @hdr_size: size of the possible header before the ARP packet in the skb * + * Warning: This function may reallocate the skb data buffer via + * pskb_may_pull()/... Any pointer into the skb data (e.g. obtained from skb->data + * or eth_hdr()) before this call must be considered invalid afterwards and has + * to be reacquired. + * * Return: the ARP type if the skb contains a valid ARP packet, 0 otherwise. */ static u16 batadv_arp_get_type(struct batadv_priv *bat_priv, @@ -1107,6 +1112,11 @@ static u16 batadv_arp_get_type(struct batadv_priv *bat_priv, * The caller must ensure that at least @hdr_size + ETH_HLEN bytes are * accessible after skb->data. * + * Warning: This function calls batadv_get_vid() and may therefore reallocate + * the skb data buffer. Any pointer into the skb data (e.g. obtained from + * skb->data or eth_hdr()) before this call must be considered invalid + * afterwards and has to be reacquired. + * * Return: If the packet embedded in the skb is vlan tagged this function * returns the VID with the BATADV_VLAN_HAS_TAG flag. Otherwise BATADV_NO_FLAGS * is returned. @@ -1169,6 +1179,11 @@ batadv_dat_arp_create_reply(struct batadv_priv *bat_priv, __be32 ip_src, * @bat_priv: the bat priv with all the mesh interface information * @skb: packet to check * + * Warning: This function may reallocate the skb data buffer via + * batadv_dat_get_vid()/.... Any pointer into the skb data (e.g. obtained + * from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. + * * Return: true if the message has been sent to the dht candidates, false * otherwise. In case of a positive return value the message has to be enqueued * to permit the fallback. @@ -1271,6 +1286,11 @@ bool batadv_dat_snoop_outgoing_arp_request(struct batadv_priv *bat_priv, * @skb: packet to check * @hdr_size: size of the encapsulation header * + * Warning: This function may reallocate the skb data buffer via + * batadv_dat_get_vid()/... Any pointer into the skb data (e.g. obtained + * from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. + * * Return: true if the request has been answered, false otherwise. */ bool batadv_dat_snoop_incoming_arp_request(struct batadv_priv *bat_priv, @@ -1333,6 +1353,11 @@ bool batadv_dat_snoop_incoming_arp_request(struct batadv_priv *bat_priv, * batadv_dat_snoop_outgoing_arp_reply() - snoop the ARP reply and fill the DHT * @bat_priv: the bat priv with all the mesh interface information * @skb: packet to check + * + * Warning: This function may reallocate the skb data buffer via + * batadv_dat_get_vid()/... Any pointer into the skb data (e.g. obtained + * from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. */ void batadv_dat_snoop_outgoing_arp_reply(struct batadv_priv *bat_priv, struct sk_buff *skb) @@ -1382,6 +1407,11 @@ void batadv_dat_snoop_outgoing_arp_reply(struct batadv_priv *bat_priv, * @skb: packet to check * @hdr_size: size of the encapsulation header * + * Warning: This function may reallocate the skb data buffer via + * batadv_dat_get_vid()/... Any pointer into the skb data (e.g. obtained + * from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. + * * Return: true if the packet was snooped and consumed by DAT. False if the * packet has to be delivered to the interface */ @@ -1788,6 +1818,11 @@ void batadv_dat_snoop_outgoing_dhcp_ack(struct batadv_priv *bat_priv, * This function first checks whether the given skb is a valid DHCPACK. If * so then its source MAC and IP as well as its DHCP Client Hardware Address * field and DHCP Your IP Address field are added to the local DAT cache. + * + * Warning: This function may reallocate the skb data buffer via + * pskb_may_pull()/batadv_dat_get_vid()/... Any pointer into the skb data + * (e.g.obtained from skb->data or eth_hdr()) before this call must be + * considered invalid afterwards and has to be reacquired. */ void batadv_dat_snoop_incoming_dhcp_ack(struct batadv_priv *bat_priv, struct sk_buff *skb, int hdr_size) @@ -1835,6 +1870,11 @@ void batadv_dat_snoop_incoming_dhcp_ack(struct batadv_priv *bat_priv, * @bat_priv: the bat priv with all the mesh interface information * @forw_packet: the broadcast packet * + * Warning: This function may reallocate the skb data buffer via + * batadv_dat_get_vid()/... Any pointer into the skb data (e.g. obtained + * from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. + * * Return: true if the node can drop the packet, false otherwise. */ bool batadv_dat_drop_broadcast_packet(struct batadv_priv *bat_priv, diff --git a/net/batman-adv/gateway_client.c b/net/batman-adv/gateway_client.c index 48fc711b8fd6..971ae3664fa7 100644 --- a/net/batman-adv/gateway_client.c +++ b/net/batman-adv/gateway_client.c @@ -553,7 +553,10 @@ int batadv_gw_dump(struct sk_buff *msg, struct netlink_callback *cb) * @chaddr: buffer where the client address will be stored. Valid * only if the function returns BATADV_DHCP_TO_CLIENT * - * This function may re-allocate the data buffer of the skb passed as argument. + * Warning: This function may reallocate the skb data buffer via + * pskb_may_pull()/... Any pointer into the skb data (e.g. + * obtained from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. * * Return: * - BATADV_DHCP_NO if the packet is not a dhcp message or if there was an error @@ -677,7 +680,11 @@ batadv_gw_dhcp_recipient_get(struct sk_buff *skb, unsigned int *header_len, * server. Due to topology changes it may be the case that the GW server * previously selected is not the best one anymore. * - * This call might reallocate skb data. + * Warning: This function may reallocate the skb data buffer via + * batadv_get_vid()/... Any pointer into the skb data (e.g. obtained + * from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. + * * Must be invoked only when the DHCP packet is going TO a DHCP SERVER. * * Return: true if the packet destination is unicast and it is not the best gw, diff --git a/net/batman-adv/main.c b/net/batman-adv/main.c index 78b81daaeff9..0a0ad9978494 100644 --- a/net/batman-adv/main.c +++ b/net/batman-adv/main.c @@ -610,6 +610,11 @@ void batadv_recv_handler_unregister(u8 packet_type) * The caller must ensure that at least @header_len + ETH_HLEN bytes are * accessible after skb->data. * + * Warning: This function may reallocate the skb data buffer via + * pskb_may_pull()/... Any pointer into the skb data (e.g. obtained from skb->data + * or eth_hdr()) before this call must be considered invalid afterwards and has + * to be reacquired. + * * Return: VID with the BATADV_VLAN_HAS_TAG flag when the packet embedded in the * skb is vlan tagged. Otherwise BATADV_NO_FLAGS. */ diff --git a/net/batman-adv/mesh-interface.c b/net/batman-adv/mesh-interface.c index effa37ff3bb1..cc004343208c 100644 --- a/net/batman-adv/mesh-interface.c +++ b/net/batman-adv/mesh-interface.c @@ -57,6 +57,11 @@ * @skb: packet buffer which should be modified * @len: number of bytes to add * + * Warning: This function may reallocate the skb data buffer via + * skb_cow_head()/... Any pointer into the skb data (e.g. obtained + * from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. + * * Return: 0 on success or negative error number in case of failure */ int batadv_skb_head_push(struct sk_buff *skb, unsigned int len) diff --git a/net/batman-adv/multicast.c b/net/batman-adv/multicast.c index 1c5315e55c04..5c2c19babfd5 100644 --- a/net/batman-adv/multicast.c +++ b/net/batman-adv/multicast.c @@ -951,7 +951,10 @@ static void batadv_mcast_mla_update(struct work_struct *work) * batadv_mcast_is_report_ipv4() - check for IGMP reports * @skb: the ethernet frame destined for the mesh * - * This call might reallocate skb data. + * Warning: This function may reallocate the skb data buffer via + * ip_mc_check_igmp()/... Any pointer into the skb data (e.g. + * obtained from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. * * Checks whether the given frame is a valid IGMP report. * @@ -1017,7 +1020,10 @@ static int batadv_mcast_forw_mode_check_ipv4(struct batadv_priv *bat_priv, * batadv_mcast_is_report_ipv6() - check for MLD reports * @skb: the ethernet frame destined for the mesh * - * This call might reallocate skb data. + * Warning: This function may reallocate the skb data buffer via + * ipv6_mc_check_mld()/... Any pointer into the skb data (e.g. + * obtained from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. * * Checks whether the given frame is a valid MLD report. * diff --git a/net/batman-adv/multicast_forw.c b/net/batman-adv/multicast_forw.c index 1404a3b7adfb..60ec12805742 100644 --- a/net/batman-adv/multicast_forw.c +++ b/net/batman-adv/multicast_forw.c @@ -1080,6 +1080,11 @@ unsigned int batadv_mcast_forw_packet_hdrlen(unsigned int num_dests) * Tries to expand an skb's headroom so that its head to tail is 1298 * bytes (minimum IPv6 MTU + vlan ethernet header size) large. * + * Warning: This function may reallocate the skb data buffer via + * skb_cow()/skb_linearize()/... Any pointer into the skb data (e.g. + * obtained from skb->data or eth_hdr()) before this call must be + * considered invalid afterwards and has to be reacquired. + * * Return: -EINVAL if the given skb's length is too large or -ENOMEM on memory * allocation failure. Otherwise, on success, zero is returned. */ @@ -1120,6 +1125,11 @@ static int batadv_mcast_forw_expand_head(struct batadv_priv *bat_priv, * that signaled interest in it, that is either via the translation table or the * according want-all flags, is attached accordingly. * + * Warning: This function may reallocate the skb data buffer via + * batadv_mcast_forw_expand_head()/... Any pointer into the skb data (e.g. + * obtained from skb->data or eth_hdr()) before this call must be + * considered invalid afterwards and has to be reacquired. + * * Return: true on success, false otherwise. */ bool batadv_mcast_forw_push(struct batadv_priv *bat_priv, struct sk_buff *skb, diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c index de0848cce98a..d3b766f39199 100644 --- a/net/batman-adv/routing.c +++ b/net/batman-adv/routing.c @@ -172,6 +172,11 @@ bool batadv_window_protected(struct batadv_priv *bat_priv, s32 seq_num_diff, * @hard_iface: incoming hard interface * @header_len: minimal header length of packet type * + * Warning: This function may reallocate the skb data buffer via + * skb_cow()/skb_linearize()/... Any pointer into the skb data (e.g. + * obtained from skb->data or eth_hdr()) before this call must be + * considered invalid afterwards and has to be reacquired. + * * Return: true when management preconditions are met, false otherwise */ bool batadv_check_management_packet(struct sk_buff *skb, @@ -863,6 +868,11 @@ batadv_reroute_unicast_packet(struct batadv_priv *bat_priv, struct sk_buff *skb, * @skb: the unicast packet to check * @hdr_len: length of the unicast header preceding the payload * + * Warning: This function may reallocate the skb data buffer via + * pskb_may_pull()/batadv_get_vid()/... Any pointer into the skb data (e.g. + * obtained from skb->data or eth_hdr()) before this call must be considered + * invalid afterwards and has to be reacquired. + * * Return: true if the packet may be processed further, false if has to be * dropped by the caller */ From a0082bd0f8519c80682a6797c070229fa3e149bc Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Mon, 8 Jun 2026 11:41:36 +0200 Subject: [PATCH 0942/1433] batman-adv: split multiple declarations per line The Linux coding style suggests to use single variable declarations per line. This suggestion turned out to make reviewing patches easier when single variable declarations are modified. Instead of having to search for the modified variable, it is directly visible as a line change in the diff. Most functions are already using this style. The remaining ones are just adjusted by splitting the lines without ensuring the reverse x-mas tree order because this makes it easier to check the modification. Signed-off-by: Sven Eckelmann --- net/batman-adv/bat_algo.c | 3 +- net/batman-adv/bat_iv_ogm.c | 38 ++++++++---- net/batman-adv/bat_v.c | 19 ++++-- net/batman-adv/bat_v_elp.c | 3 +- net/batman-adv/bat_v_ogm.c | 13 +++-- net/batman-adv/bridge_loop_avoidance.c | 30 ++++++---- net/batman-adv/distributed-arp-table.c | 55 ++++++++++++------ net/batman-adv/fragmentation.c | 12 ++-- net/batman-adv/gateway_client.c | 9 ++- net/batman-adv/gateway_common.c | 6 +- net/batman-adv/main.c | 12 ++-- net/batman-adv/mesh-interface.c | 15 +++-- net/batman-adv/multicast.c | 15 +++-- net/batman-adv/multicast_forw.c | 9 ++- net/batman-adv/originator.c | 31 ++++++---- net/batman-adv/routing.c | 37 ++++++++---- net/batman-adv/send.c | 3 +- net/batman-adv/tp_meter.c | 24 +++++--- net/batman-adv/translation-table.c | 80 ++++++++++++++++++-------- net/batman-adv/tvlv.c | 12 ++-- 20 files changed, 288 insertions(+), 138 deletions(-) diff --git a/net/batman-adv/bat_algo.c b/net/batman-adv/bat_algo.c index a040141cdf1a..bd094bc793e3 100644 --- a/net/batman-adv/bat_algo.c +++ b/net/batman-adv/bat_algo.c @@ -42,7 +42,8 @@ void batadv_algo_init(void) */ struct batadv_algo_ops *batadv_algo_get(const char *name) { - struct batadv_algo_ops *bat_algo_ops = NULL, *bat_algo_ops_tmp; + struct batadv_algo_ops *bat_algo_ops = NULL; + struct batadv_algo_ops *bat_algo_ops_tmp; hlist_for_each_entry(bat_algo_ops_tmp, &batadv_algo_list, list) { if (strcmp(bat_algo_ops_tmp->name, name) != 0) diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c index a9e80330fcb6..337e8e3554bb 100644 --- a/net/batman-adv/bat_iv_ogm.c +++ b/net/batman-adv/bat_iv_ogm.c @@ -896,7 +896,8 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface) struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); struct batadv_ogm_buf *ogm_buff = &hard_iface->bat_iv.ogm_buff; struct batadv_ogm_packet *batadv_ogm_packet; - struct batadv_hard_iface *primary_if, *tmp_hard_iface; + struct batadv_hard_iface *primary_if; + struct batadv_hard_iface *tmp_hard_iface; struct list_head *iter; u32 seqno; u16 tvlv_len = 0; @@ -1109,7 +1110,8 @@ batadv_iv_ogm_orig_update(struct batadv_priv *bat_priv, struct batadv_neigh_node *neigh_node = NULL; struct batadv_neigh_node *tmp_neigh_node = NULL; struct batadv_neigh_node *router = NULL; - u8 sum_orig, sum_neigh; + u8 sum_orig; + u8 sum_neigh; u8 *neigh_addr; u8 tq_avg; @@ -1241,13 +1243,19 @@ static bool batadv_iv_ogm_calc_tq(struct batadv_orig_node *orig_node, struct batadv_hard_iface *if_outgoing) { struct batadv_priv *bat_priv = netdev_priv(if_incoming->mesh_iface); - struct batadv_neigh_node *neigh_node = NULL, *tmp_neigh_node; + struct batadv_neigh_node *neigh_node = NULL; + struct batadv_neigh_node *tmp_neigh_node; struct batadv_neigh_ifinfo *neigh_ifinfo; u8 total_count; - u8 orig_eq_count, neigh_rq_count, neigh_rq_inv, tq_own; + u8 orig_eq_count; + u8 neigh_rq_count; + u8 neigh_rq_inv; + u8 tq_own; unsigned int tq_iface_hop_penalty = BATADV_TQ_MAX_VALUE; - unsigned int neigh_rq_inv_cube, neigh_rq_max_cube; - unsigned int tq_asym_penalty, inv_asym_penalty; + unsigned int neigh_rq_inv_cube; + unsigned int neigh_rq_max_cube; + unsigned int tq_asym_penalty; + unsigned int inv_asym_penalty; unsigned int combined_tq; bool ret = false; @@ -1521,7 +1529,8 @@ batadv_iv_ogm_process_per_outif(const struct sk_buff *skb, int ogm_offset, enum batadv_dup_status dup_status; bool is_from_best_next_hop = false; bool is_single_hop_neigh = false; - bool sameseq, similar_ttl; + bool sameseq; + bool similar_ttl; struct sk_buff *skb_priv; struct ethhdr *ethhdr; u8 *prev_sender; @@ -1747,7 +1756,8 @@ static void batadv_iv_ogm_process(const struct sk_buff *skb, int ogm_offset, struct batadv_hard_iface *if_incoming) { struct batadv_priv *bat_priv = netdev_priv(if_incoming->mesh_iface); - struct batadv_orig_node *orig_neigh_node, *orig_node; + struct batadv_orig_node *orig_neigh_node; + struct batadv_orig_node *orig_node; struct batadv_hard_iface *hard_iface; struct batadv_ogm_packet *ogm_packet; u32 if_incoming_seqno; @@ -2230,8 +2240,10 @@ static bool batadv_iv_ogm_neigh_diff(struct batadv_neigh_node *neigh1, struct batadv_hard_iface *if_outgoing2, int *diff) { - struct batadv_neigh_ifinfo *neigh1_ifinfo, *neigh2_ifinfo; - u8 tq1, tq2; + struct batadv_neigh_ifinfo *neigh1_ifinfo; + struct batadv_neigh_ifinfo *neigh2_ifinfo; + u8 tq1; + u8 tq2; bool ret = true; neigh1_ifinfo = batadv_neigh_ifinfo_get(neigh1, if_outgoing1); @@ -2474,7 +2486,8 @@ batadv_iv_gw_get_best_gw_node(struct batadv_priv *bat_priv) { struct batadv_neigh_node *router; struct batadv_neigh_ifinfo *router_ifinfo; - struct batadv_gw_node *gw_node, *curr_gw = NULL; + struct batadv_gw_node *gw_node; + struct batadv_gw_node *curr_gw = NULL; u64 max_gw_factor = 0; u64 tmp_gw_factor = 0; u8 max_tq = 0; @@ -2568,7 +2581,8 @@ static bool batadv_iv_gw_is_eligible(struct batadv_priv *bat_priv, u32 sel_class = READ_ONCE(bat_priv->gw.sel_class); struct batadv_neigh_node *router_gw = NULL; struct batadv_neigh_node *router_orig = NULL; - u8 gw_tq_avg, orig_tq_avg; + u8 gw_tq_avg; + u8 orig_tq_avg; bool ret = false; /* dynamic re-election is performed only on fast or late switch */ diff --git a/net/batman-adv/bat_v.c b/net/batman-adv/bat_v.c index 0068f0e238da..be28875c201d 100644 --- a/net/batman-adv/bat_v.c +++ b/net/batman-adv/bat_v.c @@ -490,7 +490,8 @@ static int batadv_v_neigh_cmp(struct batadv_neigh_node *neigh1, struct batadv_neigh_node *neigh2, struct batadv_hard_iface *if_outgoing2) { - struct batadv_neigh_ifinfo *ifinfo1, *ifinfo2; + struct batadv_neigh_ifinfo *ifinfo1; + struct batadv_neigh_ifinfo *ifinfo2; int ret = 0; ifinfo1 = batadv_neigh_ifinfo_get(neigh1, if_outgoing1); @@ -526,7 +527,8 @@ static bool batadv_v_neigh_is_sob(struct batadv_neigh_node *neigh1, struct batadv_neigh_node *neigh2, struct batadv_hard_iface *if_outgoing2) { - struct batadv_neigh_ifinfo *ifinfo1, *ifinfo2; + struct batadv_neigh_ifinfo *ifinfo1; + struct batadv_neigh_ifinfo *ifinfo2; u32 threshold; bool ret = false; @@ -610,8 +612,10 @@ static int batadv_v_gw_throughput_get(struct batadv_gw_node *gw_node, u32 *bw) static struct batadv_gw_node * batadv_v_gw_get_best_gw_node(struct batadv_priv *bat_priv) { - struct batadv_gw_node *gw_node, *curr_gw = NULL; - u32 max_bw = 0, bw; + struct batadv_gw_node *gw_node; + struct batadv_gw_node *curr_gw = NULL; + u32 max_bw = 0; + u32 bw; rcu_read_lock(); hlist_for_each_entry_rcu(gw_node, &bat_priv->gw.gateway_list, list) { @@ -650,8 +654,11 @@ static bool batadv_v_gw_is_eligible(struct batadv_priv *bat_priv, struct batadv_orig_node *curr_gw_orig, struct batadv_orig_node *orig_node) { - struct batadv_gw_node *curr_gw, *orig_gw = NULL; - u32 gw_throughput, orig_throughput, threshold; + struct batadv_gw_node *curr_gw; + struct batadv_gw_node *orig_gw = NULL; + u32 gw_throughput; + u32 orig_throughput; + u32 threshold; bool ret = false; threshold = READ_ONCE(bat_priv->gw.sel_class); diff --git a/net/batman-adv/bat_v_elp.c b/net/batman-adv/bat_v_elp.c index 262e40040007..eb7fb8c14ef3 100644 --- a/net/batman-adv/bat_v_elp.c +++ b/net/batman-adv/bat_v_elp.c @@ -233,7 +233,8 @@ batadv_v_elp_wifi_neigh_probe(struct batadv_hardif_neigh_node *neigh) struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); unsigned long last_tx_diff; struct sk_buff *skb; - int probe_len, i; + int probe_len; + int i; int elp_skb_len; /* this probing routine is for Wifi neighbours only */ diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c index e921d49f7ece..d4527663f76d 100644 --- a/net/batman-adv/bat_v_ogm.c +++ b/net/batman-adv/bat_v_ogm.c @@ -271,7 +271,8 @@ static void batadv_v_ogm_send_meshif(struct batadv_priv *bat_priv) struct batadv_hard_iface *hard_iface; struct batadv_ogm2_packet *ogm_packet; struct batadv_ogm_buf *ogm_buff; - struct sk_buff *skb, *skb_tmp; + struct sk_buff *skb; + struct sk_buff *skb_tmp; struct list_head *iter; u16 tvlv_len; int ret; @@ -706,8 +707,10 @@ static bool batadv_v_ogm_route_update(struct batadv_priv *bat_priv, struct batadv_neigh_node *router = NULL; struct batadv_orig_node *orig_neigh_node; struct batadv_neigh_node *orig_neigh_router = NULL; - struct batadv_neigh_ifinfo *router_ifinfo = NULL, *neigh_ifinfo = NULL; - u32 router_throughput, neigh_throughput; + struct batadv_neigh_ifinfo *router_ifinfo = NULL; + struct batadv_neigh_ifinfo *neigh_ifinfo = NULL; + u32 router_throughput; + u32 neigh_throughput; u32 router_last_seqno; u32 neigh_last_seqno; s32 neigh_seq_diff; @@ -877,7 +880,9 @@ static void batadv_v_ogm_process(const struct sk_buff *skb, int ogm_offset, struct batadv_neigh_node *neigh_node = NULL; struct batadv_hard_iface *hard_iface; struct batadv_ogm2_packet *ogm_packet; - u32 ogm_throughput, link_throughput, path_throughput; + u32 ogm_throughput; + u32 link_throughput; + u32 path_throughput; struct list_head *iter; int ret; diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c index 247f8bb4d5fc..f6faf198217a 100644 --- a/net/batman-adv/bridge_loop_avoidance.c +++ b/net/batman-adv/bridge_loop_avoidance.c @@ -261,7 +261,8 @@ batadv_backbone_hash_find(struct batadv_priv *bat_priv, const u8 *addr, { struct batadv_hashtable *hash = bat_priv->bla.backbone_hash; struct hlist_head *head; - struct batadv_bla_backbone_gw search_entry, *backbone_gw; + struct batadv_bla_backbone_gw search_entry; + struct batadv_bla_backbone_gw *backbone_gw; struct batadv_bla_backbone_gw *backbone_gw_tmp = NULL; int index; @@ -800,7 +801,8 @@ batadv_bla_claim_get_backbone_gw(struct batadv_bla_claim *claim) static void batadv_bla_del_claim(struct batadv_priv *bat_priv, const u8 *mac, const unsigned short vid) { - struct batadv_bla_claim search_claim, *claim; + struct batadv_bla_claim search_claim; + struct batadv_bla_claim *claim; struct batadv_bla_claim *claim_removed_entry; struct hlist_node *claim_removed_node; @@ -842,7 +844,8 @@ static bool batadv_handle_announce(struct batadv_priv *bat_priv, u8 *an_addr, u8 *backbone_addr, unsigned short vid) { struct batadv_bla_backbone_gw *backbone_gw; - u16 backbone_crc, crc; + u16 backbone_crc; + u16 crc; if (memcmp(an_addr, batadv_announce_mac, 4) != 0) return false; @@ -1021,7 +1024,8 @@ static int batadv_check_claim_group(struct batadv_priv *bat_priv, { u8 *backbone_addr; struct batadv_orig_node *orig_node; - struct batadv_bla_claim_dst *bla_dst, *bla_dst_own; + struct batadv_bla_claim_dst *bla_dst; + struct batadv_bla_claim_dst *bla_dst_own; bla_dst = (struct batadv_bla_claim_dst *)hw_dst; bla_dst_own = &bat_priv->bla.claim_dest; @@ -1090,9 +1094,12 @@ static bool batadv_bla_process_claim(struct batadv_priv *bat_priv, struct batadv_hard_iface *primary_if, struct sk_buff *skb) { - struct batadv_bla_claim_dst *bla_dst, *bla_dst_own; - u8 *hw_src, *hw_dst; - struct vlan_hdr *vhdr, vhdr_buf; + struct batadv_bla_claim_dst *bla_dst; + struct batadv_bla_claim_dst *bla_dst_own; + u8 *hw_src; + u8 *hw_dst; + struct vlan_hdr *vhdr; + struct vlan_hdr vhdr_buf; struct ethhdr *ethhdr; struct arphdr *arphdr; unsigned short vid; @@ -1656,7 +1663,8 @@ static bool batadv_bla_check_duplist(struct batadv_priv *bat_priv, struct batadv_bcast_duplist_entry *entry; bool ret = false; int payload_len; - int i, curr; + int i; + int curr; u32 crc; /* calculate the crc ... */ @@ -1947,7 +1955,8 @@ bool batadv_bla_rx(struct batadv_priv *bat_priv, struct sk_buff *skb, { struct batadv_bla_backbone_gw *backbone_gw; struct ethhdr *ethhdr; - struct batadv_bla_claim search_claim, *claim = NULL; + struct batadv_bla_claim search_claim; + struct batadv_bla_claim *claim = NULL; struct batadv_hard_iface *primary_if; bool own_claim; bool ret; @@ -2083,7 +2092,8 @@ bool batadv_bla_tx(struct batadv_priv *bat_priv, struct sk_buff *skb, unsigned short vid) { struct ethhdr *ethhdr; - struct batadv_bla_claim search_claim, *claim = NULL; + struct batadv_bla_claim search_claim; + struct batadv_bla_claim *claim = NULL; struct batadv_bla_backbone_gw *backbone_gw; struct batadv_hard_iface *primary_if; bool client_roamed; diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c index e1176aa8683c..ea48460ac9cb 100644 --- a/net/batman-adv/distributed-arp-table.c +++ b/net/batman-adv/distributed-arp-table.c @@ -368,7 +368,9 @@ batadv_dat_entry_hash_find(struct batadv_priv *bat_priv, __be32 ip, unsigned short vid) { struct hlist_head *head; - struct batadv_dat_entry to_find, *dat_entry, *dat_entry_tmp = NULL; + struct batadv_dat_entry to_find; + struct batadv_dat_entry *dat_entry; + struct batadv_dat_entry *dat_entry_tmp = NULL; struct batadv_hashtable *hash = bat_priv->dat.hash; u32 index; @@ -470,7 +472,8 @@ static void batadv_dbg_arp(struct batadv_priv *bat_priv, struct sk_buff *skb, struct batadv_unicast_4addr_packet *unicast_4addr_packet; struct batadv_bcast_packet *bcast_pkt; u8 *orig_addr; - __be32 ip_src, ip_dst; + __be32 ip_src; + __be32 ip_dst; if (msg) batadv_dbg(BATADV_DBG_DAT, bat_priv, "%s\n", msg); @@ -607,7 +610,8 @@ static void batadv_choose_next_candidate(struct batadv_priv *bat_priv, { batadv_dat_addr_t max = 0; batadv_dat_addr_t tmp_max = 0; - struct batadv_orig_node *orig_node, *max_orig_node = NULL; + struct batadv_orig_node *orig_node; + struct batadv_orig_node *max_orig_node = NULL; struct batadv_hashtable *hash = bat_priv->orig_hash; struct hlist_head *head; int i; @@ -673,7 +677,8 @@ batadv_dat_select_candidates(struct batadv_priv *bat_priv, __be32 ip_dst, unsigned short vid) { int select; - batadv_dat_addr_t last_max = BATADV_DAT_ADDR_MAX, ip_key; + batadv_dat_addr_t last_max = BATADV_DAT_ADDR_MAX; + batadv_dat_addr_t ip_key; struct batadv_dat_candidate *res; struct batadv_dat_entry dat; @@ -1043,8 +1048,10 @@ static u16 batadv_arp_get_type(struct batadv_priv *bat_priv, { struct arphdr *arphdr; struct ethhdr *ethhdr; - __be32 ip_src, ip_dst; - u8 *hw_src, *hw_dst; + __be32 ip_src; + __be32 ip_dst; + u8 *hw_src; + u8 *hw_dst; u16 type = 0; /* pull the ethernet header */ @@ -1192,7 +1199,8 @@ bool batadv_dat_snoop_outgoing_arp_request(struct batadv_priv *bat_priv, struct sk_buff *skb) { u16 type = 0; - __be32 ip_dst, ip_src; + __be32 ip_dst; + __be32 ip_src; u8 *hw_src; bool ret = false; struct batadv_dat_entry *dat_entry = NULL; @@ -1297,7 +1305,8 @@ bool batadv_dat_snoop_incoming_arp_request(struct batadv_priv *bat_priv, struct sk_buff *skb, int hdr_size) { u16 type; - __be32 ip_src, ip_dst; + __be32 ip_src; + __be32 ip_dst; u8 *hw_src; struct sk_buff *skb_new; struct batadv_dat_entry *dat_entry = NULL; @@ -1363,8 +1372,10 @@ void batadv_dat_snoop_outgoing_arp_reply(struct batadv_priv *bat_priv, struct sk_buff *skb) { u16 type; - __be32 ip_src, ip_dst; - u8 *hw_src, *hw_dst; + __be32 ip_src; + __be32 ip_dst; + u8 *hw_src; + u8 *hw_dst; int hdr_size = 0; unsigned short vid; @@ -1420,8 +1431,10 @@ bool batadv_dat_snoop_incoming_arp_reply(struct batadv_priv *bat_priv, { struct batadv_dat_entry *dat_entry = NULL; u16 type; - __be32 ip_src, ip_dst; - u8 *hw_src, *hw_dst; + __be32 ip_src; + __be32 ip_dst; + u8 *hw_src; + u8 *hw_dst; bool dropped = false; unsigned short vid; @@ -1514,8 +1527,10 @@ static bool batadv_dat_check_dhcp_ipudp(struct sk_buff *skb, __be32 *ip_src) { unsigned int offset = skb_network_offset(skb); - struct udphdr *udphdr, _udphdr; - struct iphdr *iphdr, _iphdr; + struct udphdr *udphdr; + struct udphdr _udphdr; + struct iphdr *iphdr; + struct iphdr _iphdr; iphdr = skb_header_pointer(skb, offset, sizeof(_iphdr), &_iphdr); if (!iphdr || iphdr->version != 4 || iphdr->ihl * 4 < sizeof(_iphdr)) @@ -1553,7 +1568,8 @@ batadv_dat_check_dhcp_ipudp(struct sk_buff *skb, __be32 *ip_src) static int batadv_dat_check_dhcp(struct sk_buff *skb, __be16 proto, __be32 *ip_src) { - __be32 *magic, _magic; + __be32 *magic; + __be32 _magic; unsigned int offset; struct { __u8 op; @@ -1601,7 +1617,8 @@ batadv_dat_check_dhcp(struct sk_buff *skb, __be16 proto, __be32 *ip_src) static int batadv_dat_get_dhcp_message_type(struct sk_buff *skb) { unsigned int offset = skb_transport_offset(skb) + sizeof(struct udphdr); - u8 *type, _type; + u8 *type; + u8 _type; struct { u8 type; u8 len; @@ -1797,7 +1814,8 @@ void batadv_dat_snoop_outgoing_dhcp_ack(struct batadv_priv *bat_priv, unsigned short vid) { u8 chaddr[BATADV_DHCP_CHADDR_LEN]; - __be32 ip_src, yiaddr; + __be32 ip_src; + __be32 yiaddr; if (!READ_ONCE(bat_priv->distributed_arp_table)) return; @@ -1829,7 +1847,8 @@ void batadv_dat_snoop_incoming_dhcp_ack(struct batadv_priv *bat_priv, { u8 chaddr[BATADV_DHCP_CHADDR_LEN]; struct ethhdr *ethhdr; - __be32 ip_src, yiaddr; + __be32 ip_src; + __be32 yiaddr; unsigned short vid; int hdr_size_tmp; __be16 proto; diff --git a/net/batman-adv/fragmentation.c b/net/batman-adv/fragmentation.c index 2e20a2cb64cb..f382af8588b5 100644 --- a/net/batman-adv/fragmentation.c +++ b/net/batman-adv/fragmentation.c @@ -140,11 +140,13 @@ static bool batadv_frag_insert_packet(struct batadv_orig_node *orig_node, struct hlist_head *chain_out) { struct batadv_frag_table_entry *chain; - struct batadv_frag_list_entry *frag_entry_new = NULL, *frag_entry_curr; + struct batadv_frag_list_entry *frag_entry_new = NULL; + struct batadv_frag_list_entry *frag_entry_curr; struct batadv_frag_list_entry *frag_entry_last = NULL; struct batadv_frag_packet *frag_packet; u8 bucket; - u16 seqno, hdr_size = sizeof(struct batadv_frag_packet); + u16 seqno; + u16 hdr_size = sizeof(struct batadv_frag_packet); bool overflow = false; bool ret = false; size_t data_len; @@ -261,7 +263,8 @@ batadv_frag_merge_packets(struct hlist_head *chain) struct batadv_frag_packet *packet; struct batadv_frag_list_entry *entry; struct sk_buff *skb_out; - int size, hdr_size = sizeof(struct batadv_frag_packet); + int size; + int hdr_size = sizeof(struct batadv_frag_packet); bool dropped = false; /* Remove first entry, as this is the destination for the rest of the @@ -509,7 +512,8 @@ int batadv_frag_send_packet(struct sk_buff *skb, struct sk_buff *skb_fragment; unsigned int mtu = net_dev->mtu; unsigned int header_size = sizeof(frag_header); - unsigned int max_fragment_size, num_fragments; + unsigned int max_fragment_size; + unsigned int num_fragments; int ret; /* To avoid merge and refragmentation at next-hops we never send diff --git a/net/batman-adv/gateway_client.c b/net/batman-adv/gateway_client.c index 971ae3664fa7..8b9a86cc90fb 100644 --- a/net/batman-adv/gateway_client.c +++ b/net/batman-adv/gateway_client.c @@ -378,7 +378,8 @@ static void batadv_gw_node_add(struct batadv_priv *bat_priv, struct batadv_gw_node *batadv_gw_node_get(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node) { - struct batadv_gw_node *gw_node_tmp, *gw_node = NULL; + struct batadv_gw_node *gw_node_tmp; + struct batadv_gw_node *gw_node = NULL; rcu_read_lock(); hlist_for_each_entry_rcu(gw_node_tmp, &bat_priv->gw.gateway_list, @@ -408,7 +409,8 @@ void batadv_gw_node_update(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node, struct batadv_tvlv_gateway_data *gateway) { - struct batadv_gw_node *gw_node, *curr_gw = NULL; + struct batadv_gw_node *gw_node; + struct batadv_gw_node *curr_gw = NULL; spin_lock_bh(&bat_priv->gw.list_lock); gw_node = batadv_gw_node_get(bat_priv, orig_node); @@ -698,7 +700,8 @@ bool batadv_gw_out_of_range(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_dst_node = NULL; struct batadv_gw_node *gw_node = NULL; struct batadv_gw_node *curr_gw = NULL; - struct batadv_neigh_ifinfo *curr_ifinfo, *old_ifinfo; + struct batadv_neigh_ifinfo *curr_ifinfo; + struct batadv_neigh_ifinfo *old_ifinfo; struct ethhdr *ethhdr; bool out_of_range = false; u8 curr_tq_avg; diff --git a/net/batman-adv/gateway_common.c b/net/batman-adv/gateway_common.c index 675ebf098d4e..b5ebe837bfdd 100644 --- a/net/batman-adv/gateway_common.c +++ b/net/batman-adv/gateway_common.c @@ -26,7 +26,8 @@ void batadv_gw_tvlv_container_update(struct batadv_priv *bat_priv) { struct batadv_tvlv_gateway_data gw; enum batadv_gw_modes gw_mode; - u32 down, up; + u32 down; + u32 up; gw_mode = READ_ONCE(bat_priv->gw.mode); @@ -59,7 +60,8 @@ static void batadv_gw_tvlv_ogm_handler_v1(struct batadv_priv *bat_priv, u8 flags, void *tvlv_value, u16 tvlv_value_len) { - struct batadv_tvlv_gateway_data gateway, *gateway_ptr; + struct batadv_tvlv_gateway_data gateway; + struct batadv_tvlv_gateway_data *gateway_ptr; /* only fetch the tvlv value if the handler wasn't called via the * CIFNOTFND flag and if there is data to fetch diff --git a/net/batman-adv/main.c b/net/batman-adv/main.c index 0a0ad9978494..5aa9f26dc6e2 100644 --- a/net/batman-adv/main.c +++ b/net/batman-adv/main.c @@ -368,10 +368,14 @@ int batadv_max_header_len(void) */ void batadv_skb_set_priority(struct sk_buff *skb, int offset) { - struct iphdr ip_hdr_tmp, *ip_hdr; - struct ipv6hdr ip6_hdr_tmp, *ip6_hdr; - struct ethhdr ethhdr_tmp, *ethhdr; - struct vlan_ethhdr *vhdr, vhdr_tmp; + struct iphdr ip_hdr_tmp; + struct iphdr *ip_hdr; + struct ipv6hdr ip6_hdr_tmp; + struct ipv6hdr *ip6_hdr; + struct ethhdr ethhdr_tmp; + struct ethhdr *ethhdr; + struct vlan_ethhdr *vhdr; + struct vlan_ethhdr vhdr_tmp; u32 prio; /* already set, do nothing */ diff --git a/net/batman-adv/mesh-interface.c b/net/batman-adv/mesh-interface.c index cc004343208c..ab947b9726c1 100644 --- a/net/batman-adv/mesh-interface.c +++ b/net/batman-adv/mesh-interface.c @@ -92,7 +92,8 @@ int batadv_skb_head_push(struct sk_buff *skb, unsigned int len) */ static u64 batadv_sum_counter(struct batadv_priv *bat_priv, size_t idx) { - u64 *counters, sum = 0; + u64 *counters; + u64 sum = 0; int cpu; for_each_possible_cpu(cpu) { @@ -221,12 +222,15 @@ static netdev_tx_t batadv_interface_tx(struct sk_buff *skb, static const u8 ectp_addr[ETH_ALEN] = {0xCF, 0x00, 0x00, 0x00, 0x00, 0x00}; enum batadv_dhcp_recipient dhcp_rcp = BATADV_DHCP_NO; - u8 *dst_hint = NULL, chaddr[ETH_ALEN]; + u8 *dst_hint = NULL; + u8 chaddr[ETH_ALEN]; struct vlan_ethhdr *vhdr; unsigned int header_len = 0; - int data_len = skb->len, ret; + int data_len = skb->len; + int ret; unsigned long brd_delay = 0; - bool do_bcast = false, client_added; + bool do_bcast = false; + bool client_added; unsigned short vid; u32 seqno; int gw_mode; @@ -566,7 +570,8 @@ void batadv_meshif_vlan_release(struct kref *ref) struct batadv_meshif_vlan *batadv_meshif_vlan_get(struct batadv_priv *bat_priv, unsigned short vid) { - struct batadv_meshif_vlan *vlan_tmp, *vlan = NULL; + struct batadv_meshif_vlan *vlan_tmp; + struct batadv_meshif_vlan *vlan = NULL; rcu_read_lock(); hlist_for_each_entry_rcu(vlan_tmp, &bat_priv->meshif_vlan_list, list) { diff --git a/net/batman-adv/multicast.c b/net/batman-adv/multicast.c index 5c2c19babfd5..549c8ed0a557 100644 --- a/net/batman-adv/multicast.c +++ b/net/batman-adv/multicast.c @@ -274,7 +274,8 @@ static struct batadv_mcast_mla_flags batadv_mcast_mla_flags_get(struct batadv_priv *bat_priv) { struct net_device *dev = bat_priv->mesh_iface; - struct batadv_mcast_querier_state *qr4, *qr6; + struct batadv_mcast_querier_state *qr4; + struct batadv_mcast_querier_state *qr6; struct batadv_mcast_mla_flags mla_flags; struct net_device *bridge; @@ -521,7 +522,8 @@ batadv_mcast_mla_meshif_get(struct net_device *dev, struct batadv_mcast_mla_flags *flags) { struct net_device *bridge = batadv_mcast_get_bridge(dev); - int ret4, ret6 = 0; + int ret4; + int ret6 = 0; if (bridge) dev = bridge; @@ -585,7 +587,8 @@ static int batadv_mcast_mla_bridge_get(struct net_device *dev, struct batadv_mcast_mla_flags *flags) { struct list_head bridge_mcast_list = LIST_HEAD_INIT(bridge_mcast_list); - struct br_ip_list *br_ip_entry, *tmp; + struct br_ip_list *br_ip_entry; + struct br_ip_list *tmp; u8 tvlv_flags = flags->tvlv_flags; struct batadv_hw_addr *new; u8 mcast_addr[ETH_ALEN]; @@ -1229,7 +1232,11 @@ enum batadv_forw_mode batadv_mcast_forw_mode(struct batadv_priv *bat_priv, struct sk_buff *skb, unsigned short vid, int *is_routable) { - int ret, tt_count, ip_count, unsnoop_count, total_count; + int ret; + int tt_count; + int ip_count; + int unsnoop_count; + int total_count; bool is_unsnoopable = false; struct ethhdr *ethhdr; int rtr_count = 0; diff --git a/net/batman-adv/multicast_forw.c b/net/batman-adv/multicast_forw.c index 60ec12805742..4dcaaadc81b9 100644 --- a/net/batman-adv/multicast_forw.c +++ b/net/batman-adv/multicast_forw.c @@ -368,7 +368,8 @@ static void batadv_mcast_forw_scrape(struct sk_buff *skb, unsigned short offset, unsigned short len) { - char *to, *from; + char *to; + char *from; SKB_LINEAR_ASSERT(skb); @@ -411,7 +412,8 @@ static bool batadv_mcast_forw_push_insert_padding(struct sk_buff *skb, unsigned short *tvlv_len) { unsigned short offset = *tvlv_len; - char *to, *from = skb->data; + char *to; + char *from = skb->data; to = batadv_mcast_forw_push_padding(skb, tvlv_len); if (!to) @@ -933,7 +935,8 @@ static int batadv_mcast_forw_packet(struct batadv_priv *bat_priv, unsigned int tvlv_len; unsigned long offset; bool xmitted = false; - u8 *dest, *next_dest; + u8 *dest; + u8 *next_dest; u16 num_dests; int ret; diff --git a/net/batman-adv/originator.c b/net/batman-adv/originator.c index bab1eb61d9ef..57bb4a0131b0 100644 --- a/net/batman-adv/originator.c +++ b/net/batman-adv/originator.c @@ -54,7 +54,8 @@ batadv_orig_hash_find(struct batadv_priv *bat_priv, const void *data) { struct batadv_hashtable *hash = bat_priv->orig_hash; struct hlist_head *head; - struct batadv_orig_node *orig_node, *orig_node_tmp = NULL; + struct batadv_orig_node *orig_node; + struct batadv_orig_node *orig_node_tmp = NULL; int index; if (!hash) @@ -108,7 +109,8 @@ struct batadv_orig_node_vlan * batadv_orig_node_vlan_get(struct batadv_orig_node *orig_node, unsigned short vid) { - struct batadv_orig_node_vlan *vlan = NULL, *tmp; + struct batadv_orig_node_vlan *vlan = NULL; + struct batadv_orig_node_vlan *tmp; rcu_read_lock(); hlist_for_each_entry_rcu(tmp, &orig_node->vlan_list, list) { @@ -373,7 +375,8 @@ struct batadv_orig_ifinfo * batadv_orig_ifinfo_get(struct batadv_orig_node *orig_node, struct batadv_hard_iface *if_outgoing) { - struct batadv_orig_ifinfo *tmp, *orig_ifinfo = NULL; + struct batadv_orig_ifinfo *tmp; + struct batadv_orig_ifinfo *orig_ifinfo = NULL; rcu_read_lock(); hlist_for_each_entry_rcu(tmp, &orig_node->ifinfo_list, @@ -451,8 +454,8 @@ struct batadv_neigh_ifinfo * batadv_neigh_ifinfo_get(struct batadv_neigh_node *neigh, struct batadv_hard_iface *if_outgoing) { - struct batadv_neigh_ifinfo *neigh_ifinfo = NULL, - *tmp_neigh_ifinfo; + struct batadv_neigh_ifinfo *neigh_ifinfo = NULL; + struct batadv_neigh_ifinfo *tmp_neigh_ifinfo; rcu_read_lock(); hlist_for_each_entry_rcu(tmp_neigh_ifinfo, &neigh->ifinfo_list, @@ -530,7 +533,8 @@ batadv_neigh_node_get(const struct batadv_orig_node *orig_node, const struct batadv_hard_iface *hard_iface, const u8 *addr) { - struct batadv_neigh_node *tmp_neigh_node, *res = NULL; + struct batadv_neigh_node *tmp_neigh_node; + struct batadv_neigh_node *res = NULL; rcu_read_lock(); hlist_for_each_entry_rcu(tmp_neigh_node, &orig_node->neigh_list, list) { @@ -634,7 +638,8 @@ struct batadv_hardif_neigh_node * batadv_hardif_neigh_get(const struct batadv_hard_iface *hard_iface, const u8 *neigh_addr) { - struct batadv_hardif_neigh_node *tmp_hardif_neigh, *hardif_neigh = NULL; + struct batadv_hardif_neigh_node *tmp_hardif_neigh; + struct batadv_hardif_neigh_node *hardif_neigh = NULL; rcu_read_lock(); hlist_for_each_entry_rcu(tmp_hardif_neigh, @@ -753,7 +758,8 @@ batadv_neigh_node_get_or_create(struct batadv_orig_node *orig_node, */ int batadv_hardif_neigh_dump(struct sk_buff *msg, struct netlink_callback *cb) { - struct batadv_hard_iface *primary_if, *hard_iface; + struct batadv_hard_iface *primary_if; + struct batadv_hard_iface *hard_iface; struct net_device *mesh_iface; struct batadv_priv *bat_priv; int ret; @@ -1167,7 +1173,8 @@ batadv_find_best_neighbor(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node, struct batadv_hard_iface *if_outgoing) { - struct batadv_neigh_node *best = NULL, *neigh; + struct batadv_neigh_node *best = NULL; + struct batadv_neigh_node *neigh; struct batadv_algo_ops *bao = bat_priv->algo_ops; rcu_read_lock(); @@ -1203,7 +1210,8 @@ static bool batadv_purge_orig_node(struct batadv_priv *bat_priv, { struct batadv_neigh_node *best_neigh_node; struct batadv_hard_iface *hard_iface; - bool changed_ifinfo, changed_neigh; + bool changed_ifinfo; + bool changed_neigh; struct list_head *iter; if (batadv_has_timed_out(orig_node->last_seen, @@ -1325,7 +1333,8 @@ static void batadv_purge_orig(struct work_struct *work) */ int batadv_orig_dump(struct sk_buff *msg, struct netlink_callback *cb) { - struct batadv_hard_iface *primary_if, *hard_iface; + struct batadv_hard_iface *primary_if; + struct batadv_hard_iface *hard_iface; struct net_device *mesh_iface; struct batadv_priv *bat_priv; int ret; diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c index d3b766f39199..3e4486094b75 100644 --- a/net/batman-adv/routing.c +++ b/net/batman-adv/routing.c @@ -276,7 +276,8 @@ static int batadv_recv_my_icmp_packet(struct batadv_priv *bat_priv, struct batadv_hard_iface *primary_if = NULL; struct batadv_orig_node *orig_node = NULL; struct batadv_icmp_header *icmph; - int res, ret = NET_RX_DROP; + int res; + int ret = NET_RX_DROP; icmph = (struct batadv_icmp_header *)skb->data; @@ -348,7 +349,8 @@ static int batadv_recv_icmp_ttl_exceeded(struct batadv_priv *bat_priv, struct batadv_hard_iface *primary_if = NULL; struct batadv_orig_node *orig_node = NULL; struct batadv_icmp_packet *icmp_packet; - int res, ret = NET_RX_DROP; + int res; + int ret = NET_RX_DROP; icmp_packet = (struct batadv_icmp_packet *)skb->data; @@ -411,7 +413,8 @@ int batadv_recv_icmp_packet(struct sk_buff *skb, struct ethhdr *ethhdr; struct batadv_orig_node *orig_node = NULL; int hdr_size = sizeof(struct batadv_icmp_header); - int res, ret = NET_RX_DROP; + int res; + int ret = NET_RX_DROP; /* drop packet if it has not necessary minimum size */ if (unlikely(!pskb_may_pull(skb, hdr_size))) @@ -593,9 +596,11 @@ batadv_find_router(struct batadv_priv *bat_priv, struct batadv_algo_ops *bao = bat_priv->algo_ops; struct batadv_neigh_node *first_candidate_router = NULL; struct batadv_neigh_node *next_candidate_router = NULL; - struct batadv_neigh_node *router, *cand_router = NULL; + struct batadv_neigh_node *router; + struct batadv_neigh_node *cand_router = NULL; struct batadv_neigh_node *last_cand_router = NULL; - struct batadv_orig_ifinfo *cand, *first_candidate = NULL; + struct batadv_orig_ifinfo *cand; + struct batadv_orig_ifinfo *first_candidate = NULL; struct batadv_orig_ifinfo *next_candidate = NULL; struct batadv_orig_ifinfo *last_candidate; bool last_candidate_found = false; @@ -739,7 +744,9 @@ static int batadv_route_unicast_packet(struct sk_buff *skb, struct batadv_orig_node *orig_node = NULL; struct batadv_unicast_packet *unicast_packet; struct ethhdr *ethhdr = eth_hdr(skb); - int res, hdr_len, ret = NET_RX_DROP; + int res; + int hdr_len; + int ret = NET_RX_DROP; unsigned int len; unicast_packet = (struct batadv_unicast_packet *)skb->data; @@ -882,7 +889,8 @@ static bool batadv_check_unicast_ttvn(struct batadv_priv *bat_priv, struct batadv_unicast_packet *unicast_packet; struct batadv_hard_iface *primary_if; struct batadv_orig_node *orig_node; - u8 curr_ttvn, old_ttvn; + u8 curr_ttvn; + u8 old_ttvn; struct ethhdr *ethhdr; unsigned short vid; int is_old_ttvn; @@ -1002,7 +1010,8 @@ int batadv_recv_unhandled_unicast_packet(struct sk_buff *skb, { struct batadv_unicast_packet *unicast_packet; struct batadv_priv *bat_priv = netdev_priv(recv_if->mesh_iface); - int check, hdr_size = sizeof(*unicast_packet); + int check; + int hdr_size = sizeof(*unicast_packet); check = batadv_check_unicast_packet(bat_priv, skb, hdr_size); if (check < 0) @@ -1033,12 +1042,16 @@ int batadv_recv_unicast_packet(struct sk_buff *skb, struct batadv_priv *bat_priv = netdev_priv(recv_if->mesh_iface); struct batadv_unicast_packet *unicast_packet; struct batadv_unicast_4addr_packet *unicast_4addr_packet; - u8 *orig_addr, *orig_addr_gw; - struct batadv_orig_node *orig_node = NULL, *orig_node_gw = NULL; - int check, hdr_size = sizeof(*unicast_packet); + u8 *orig_addr; + u8 *orig_addr_gw; + struct batadv_orig_node *orig_node = NULL; + struct batadv_orig_node *orig_node_gw = NULL; + int check; + int hdr_size = sizeof(*unicast_packet); enum batadv_subtype subtype; int ret = NET_RX_DROP; - bool is4addr, is_gw; + bool is4addr; + bool is_gw; unicast_packet = (struct batadv_unicast_packet *)skb->data; is4addr = unicast_packet->packet_type == BATADV_UNICAST_4ADDR; diff --git a/net/batman-adv/send.c b/net/batman-adv/send.c index 7f449338a490..29f2cbc61285 100644 --- a/net/batman-adv/send.c +++ b/net/batman-adv/send.c @@ -394,7 +394,8 @@ int batadv_send_skb_via_tt_generic(struct batadv_priv *bat_priv, { struct ethhdr *ethhdr = (struct ethhdr *)skb->data; struct batadv_orig_node *orig_node; - u8 *src, *dst; + u8 *src; + u8 *dst; int ret; src = ethhdr->h_source; diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c index 00467aa79de9..b957a59dcf26 100644 --- a/net/batman-adv/tp_meter.c +++ b/net/batman-adv/tp_meter.c @@ -267,7 +267,8 @@ static void batadv_tp_batctl_error_notify(enum batadv_tp_meter_reason reason, static struct batadv_tp_sender * batadv_tp_list_find_sender(struct batadv_priv *bat_priv, const u8 *dst) { - struct batadv_tp_sender *pos, *tp_vars = NULL; + struct batadv_tp_sender *pos; + struct batadv_tp_sender *tp_vars = NULL; rcu_read_lock(); hlist_for_each_entry_rcu(pos, &bat_priv->tp_sender_list, common.list) { @@ -332,7 +333,8 @@ static struct batadv_tp_sender * batadv_tp_list_find_sender_session(struct batadv_priv *bat_priv, const u8 *dst, const u8 *session) { - struct batadv_tp_sender *pos, *tp_vars = NULL; + struct batadv_tp_sender *pos; + struct batadv_tp_sender *tp_vars = NULL; rcu_read_lock(); hlist_for_each_entry_rcu(pos, &bat_priv->tp_sender_list, common.list) { @@ -864,7 +866,8 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv, static bool batadv_tp_avail(struct batadv_tp_sender *tp_vars, size_t payload_len) { - u32 win_left, win_limit; + u32 win_left; + u32 win_limit; spin_lock_bh(&tp_vars->cc_lock); @@ -914,7 +917,8 @@ static int batadv_tp_send(void *arg) struct batadv_priv *bat_priv = tp_vars->common.bat_priv; struct batadv_hard_iface *primary_if = NULL; struct batadv_orig_node *orig_node = NULL; - size_t payload_len, packet_len; + size_t payload_len; + size_t packet_len; u32 last_sent; int err = 0; @@ -1211,7 +1215,8 @@ static struct batadv_tp_receiver * batadv_tp_list_find_receiver_session(struct batadv_priv *bat_priv, const u8 *dst, const u8 *session) { - struct batadv_tp_receiver *pos, *tp_vars = NULL; + struct batadv_tp_receiver *pos; + struct batadv_tp_receiver *tp_vars = NULL; rcu_read_lock(); hlist_for_each_entry_rcu(pos, &bat_priv->tp_receiver_list, common.list) { @@ -1296,7 +1301,8 @@ static void batadv_tp_reset_receiver_timer(struct batadv_tp_receiver *tp_vars) static void batadv_tp_receiver_shutdown(struct timer_list *t) { struct batadv_tp_receiver *tp_vars = timer_container_of(tp_vars, t, common.timer); - struct batadv_tp_unacked *un, *safe; + struct batadv_tp_unacked *un; + struct batadv_tp_unacked *safe; struct batadv_priv *bat_priv; bat_priv = tp_vars->common.bat_priv; @@ -1351,7 +1357,8 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst, struct batadv_orig_node *orig_node; struct batadv_icmp_tp_packet *icmp; struct sk_buff *skb; - int r, ret; + int r; + int ret; orig_node = batadv_orig_hash_find(bat_priv, dst); if (unlikely(!orig_node)) { @@ -1528,7 +1535,8 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, static void batadv_tp_ack_unordered(struct batadv_tp_receiver *tp_vars) __must_hold(&tp_vars->ack_seqno_lock) { - struct batadv_tp_unacked *un, *safe; + struct batadv_tp_unacked *un; + struct batadv_tp_unacked *safe; u32 to_ack; /* go through the unacked packet list and possibly ACK them as diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index b12ec8d1b545..75ec829139a2 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -129,7 +129,9 @@ batadv_tt_hash_find(struct batadv_hashtable *hash, const u8 *addr, unsigned short vid) { struct hlist_head *head; - struct batadv_tt_common_entry to_search, *tt, *tt_tmp = NULL; + struct batadv_tt_common_entry to_search; + struct batadv_tt_common_entry *tt; + struct batadv_tt_common_entry *tt_tmp = NULL; u32 index; if (!hash) @@ -421,10 +423,13 @@ static void batadv_tt_local_event(struct batadv_priv *bat_priv, struct batadv_tt_local_entry *tt_local_entry, u8 event_flags) { - struct batadv_tt_change_node *tt_change_node, *entry, *safe; + struct batadv_tt_change_node *tt_change_node; + struct batadv_tt_change_node *entry; + struct batadv_tt_change_node *safe; struct batadv_tt_common_entry *common = &tt_local_entry->common; u8 flags = common->flags | event_flags; - bool del_op_requested, del_op_entry; + bool del_op_requested; + bool del_op_entry; size_t changes; tt_change_node = kmem_cache_alloc(batadv_tt_change_cache, GFP_ATOMIC); @@ -616,7 +621,9 @@ bool batadv_tt_local_add(struct net_device *mesh_iface, const u8 *addr, struct net_device *in_dev = NULL; struct hlist_head *head; struct batadv_tt_orig_list_entry *orig_entry; - int hash_added, table_size, packet_size_max; + int hash_added; + int table_size; + int packet_size_max; bool ret = false; bool roamed_back = false; bool iif_is_wifi = false; @@ -1002,10 +1009,12 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv, */ static void batadv_tt_tvlv_container_update(struct batadv_priv *bat_priv) { - struct batadv_tt_change_node *entry, *safe; + struct batadv_tt_change_node *entry; + struct batadv_tt_change_node *safe; struct batadv_tvlv_tt_data *tt_data; struct batadv_tvlv_tt_change *tt_change; - int tt_diff_len, tt_change_len = 0; + int tt_diff_len; + int tt_change_len = 0; int tt_diff_entries_num = 0; int tt_diff_entries_count = 0; bool drop_changes = false; @@ -1285,7 +1294,8 @@ u16 batadv_tt_local_remove(struct batadv_priv *bat_priv, const u8 *addr, { struct batadv_tt_local_entry *tt_removed_entry; struct batadv_tt_local_entry *tt_local_entry; - u16 flags, curr_flags = BATADV_NO_FLAGS; + u16 flags; + u16 curr_flags = BATADV_NO_FLAGS; struct hlist_node *tt_removed_node; tt_local_entry = batadv_tt_local_hash_find(bat_priv, addr, vid); @@ -1468,7 +1478,8 @@ static int batadv_tt_global_init(struct batadv_priv *bat_priv) */ static void batadv_tt_changes_list_free(struct batadv_priv *bat_priv) { - struct batadv_tt_change_node *entry, *safe; + struct batadv_tt_change_node *entry; + struct batadv_tt_change_node *safe; spin_lock_bh(&bat_priv->tt.changes_list_lock); @@ -1497,7 +1508,8 @@ static struct batadv_tt_orig_list_entry * batadv_tt_global_orig_entry_find(const struct batadv_tt_global_entry *entry, const struct batadv_orig_node *orig_node) { - struct batadv_tt_orig_list_entry *tmp_orig_entry, *orig_entry = NULL; + struct batadv_tt_orig_list_entry *tmp_orig_entry; + struct batadv_tt_orig_list_entry *orig_entry = NULL; const struct hlist_head *head; rcu_read_lock(); @@ -1809,10 +1821,12 @@ static struct batadv_tt_orig_list_entry * batadv_transtable_best_orig(struct batadv_priv *bat_priv, struct batadv_tt_global_entry *tt_global_entry) { - struct batadv_neigh_node *router, *best_router = NULL; + struct batadv_neigh_node *router; + struct batadv_neigh_node *best_router = NULL; struct batadv_algo_ops *bao = bat_priv->algo_ops; struct hlist_head *head; - struct batadv_tt_orig_list_entry *orig_entry, *best_entry = NULL; + struct batadv_tt_orig_list_entry *orig_entry; + struct batadv_tt_orig_list_entry *best_entry = NULL; head = &tt_global_entry->orig_list; hlist_for_each_entry_rcu(orig_entry, head, list) { @@ -1919,7 +1933,8 @@ batadv_tt_global_dump_entry(struct sk_buff *msg, u32 portid, u32 seq, struct batadv_priv *bat_priv, struct batadv_tt_common_entry *common, int *sub_s) { - struct batadv_tt_orig_list_entry *orig_entry, *best_entry; + struct batadv_tt_orig_list_entry *orig_entry; + struct batadv_tt_orig_list_entry *best_entry; struct batadv_tt_global_entry *global; struct hlist_head *head; int sub = 0; @@ -2536,7 +2551,9 @@ static u32 batadv_tt_global_crc(struct batadv_priv *bat_priv, struct batadv_tt_common_entry *tt_common; struct batadv_tt_global_entry *tt_global; struct hlist_head *head; - u32 i, crc_tmp, crc = 0; + u32 i; + u32 crc_tmp; + u32 crc = 0; u8 flags; __be16 tmp_vid; @@ -2614,7 +2631,9 @@ static u32 batadv_tt_local_crc(struct batadv_priv *bat_priv, struct batadv_hashtable *hash = bat_priv->tt.local_hash; struct batadv_tt_common_entry *tt_common; struct hlist_head *head; - u32 i, crc_tmp, crc = 0; + u32 i; + u32 crc_tmp; + u32 crc = 0; u8 flags; __be16 tmp_vid; @@ -2765,7 +2784,8 @@ static struct batadv_tt_req_node * batadv_tt_req_node_new(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node) { - struct batadv_tt_req_node *tt_req_node_tmp, *tt_req_node = NULL; + struct batadv_tt_req_node *tt_req_node_tmp; + struct batadv_tt_req_node *tt_req_node = NULL; spin_lock_bh(&bat_priv->tt.req_list_lock); hlist_for_each_entry(tt_req_node_tmp, &bat_priv->tt.req_list, list) { @@ -2874,7 +2894,8 @@ static u16 batadv_tt_tvlv_generate(struct batadv_priv *bat_priv, struct batadv_tt_common_entry *tt_common_entry; struct batadv_tvlv_tt_change *tt_change; struct hlist_head *head; - u16 tt_tot, tt_num_entries = 0; + u16 tt_tot; + u16 tt_num_entries = 0; u8 flags; bool ret; u32 i; @@ -2928,7 +2949,8 @@ static bool batadv_tt_global_check_crc(struct batadv_orig_node *orig_node, { struct batadv_tvlv_tt_vlan_data *tt_vlan_tmp; struct batadv_orig_node_vlan *vlan; - int i, orig_num_vlan; + int i; + int orig_num_vlan; u32 crc; /* check if each received CRC matches the locally stored one */ @@ -3035,7 +3057,8 @@ static bool batadv_send_tt_request(struct batadv_priv *bat_priv, struct batadv_tt_req_node *tt_req_node = NULL; struct batadv_hard_iface *primary_if; bool ret = false; - int i, size; + int i; + int size; primary_if = batadv_primary_if_get_selected(bat_priv); if (!primary_if) @@ -3115,8 +3138,10 @@ static bool batadv_send_other_tt_response(struct batadv_priv *bat_priv, struct batadv_orig_node *res_dst_orig_node = NULL; struct batadv_tvlv_tt_change *tt_change; struct batadv_tvlv_tt_data *tvlv_tt_data = NULL; - bool ret = false, full_table; - u8 orig_ttvn, req_ttvn; + bool ret = false; + bool full_table; + u8 orig_ttvn; + u8 req_ttvn; u16 tvlv_len; s32 tt_len; @@ -3244,7 +3269,8 @@ static bool batadv_send_my_tt_response(struct batadv_priv *bat_priv, struct batadv_hard_iface *primary_if = NULL; struct batadv_tvlv_tt_change *tt_change; struct batadv_orig_node *orig_node; - u8 my_ttvn, req_ttvn; + u8 my_ttvn; + u8 req_ttvn; u16 tvlv_len; bool full_table; s32 tt_len; @@ -3566,7 +3592,8 @@ static void batadv_handle_tt_response(struct batadv_priv *bat_priv, */ static void batadv_tt_roam_list_free(struct batadv_priv *bat_priv) { - struct batadv_tt_roam_node *node, *safe; + struct batadv_tt_roam_node *node; + struct batadv_tt_roam_node *safe; spin_lock_bh(&bat_priv->tt.roam_list_lock); @@ -3584,7 +3611,8 @@ static void batadv_tt_roam_list_free(struct batadv_priv *bat_priv) */ static void batadv_tt_roam_purge(struct batadv_priv *bat_priv) { - struct batadv_tt_roam_node *node, *safe; + struct batadv_tt_roam_node *node; + struct batadv_tt_roam_node *safe; spin_lock_bh(&bat_priv->tt.roam_list_lock); list_for_each_entry_safe(node, safe, &bat_priv->tt.roam_list, list) { @@ -4115,7 +4143,8 @@ void batadv_tt_local_resize_to_mtu(struct net_device *mesh_iface) { struct batadv_priv *bat_priv = netdev_priv(mesh_iface); int packet_size_max = READ_ONCE(bat_priv->packet_size_max); - int table_size, timeout = BATADV_TT_LOCAL_TIMEOUT / 2; + int table_size; + int timeout = BATADV_TT_LOCAL_TIMEOUT / 2; bool reduced = false; spin_lock_bh(&bat_priv->tt.commit_lock); @@ -4159,7 +4188,8 @@ static void batadv_tt_tvlv_ogm_handler_v1(struct batadv_priv *bat_priv, { struct batadv_tvlv_tt_change *tt_change; struct batadv_tvlv_tt_data *tt_data; - u16 num_entries, num_vlan; + u16 num_entries; + u16 num_vlan; size_t tt_data_sz; if (tvlv_value_len < sizeof(*tt_data)) diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c index cc14a76582b5..332dda09fe4c 100644 --- a/net/batman-adv/tvlv.c +++ b/net/batman-adv/tvlv.c @@ -72,7 +72,8 @@ static void batadv_tvlv_handler_put(struct batadv_tvlv_handler *tvlv_handler) static struct batadv_tvlv_handler * batadv_tvlv_handler_get(struct batadv_priv *bat_priv, u8 type, u8 version) { - struct batadv_tvlv_handler *tvlv_handler_tmp, *tvlv_handler = NULL; + struct batadv_tvlv_handler *tvlv_handler_tmp; + struct batadv_tvlv_handler *tvlv_handler = NULL; rcu_read_lock(); hlist_for_each_entry_rcu(tvlv_handler_tmp, @@ -134,7 +135,8 @@ static void batadv_tvlv_container_put(struct batadv_tvlv_container *tvlv) static struct batadv_tvlv_container * batadv_tvlv_container_get(struct batadv_priv *bat_priv, u8 type, u8 version) { - struct batadv_tvlv_container *tvlv_tmp, *tvlv = NULL; + struct batadv_tvlv_container *tvlv_tmp; + struct batadv_tvlv_container *tvlv = NULL; lockdep_assert_held(&bat_priv->tvlv.container_list_lock); @@ -236,7 +238,8 @@ void batadv_tvlv_container_register(struct batadv_priv *bat_priv, u8 type, u8 version, void *tvlv_value, u16 tvlv_value_len) { - struct batadv_tvlv_container *tvlv_old, *tvlv_new; + struct batadv_tvlv_container *tvlv_old; + struct batadv_tvlv_container *tvlv_new; if (!tvlv_value) tvlv_value_len = 0; @@ -395,7 +398,8 @@ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv, u16 tvlv_value_len) { unsigned int tvlv_offset; - u8 *src, *dst; + u8 *src; + u8 *dst; if (!tvlv_handler) return NET_RX_SUCCESS; From 4549e49abbced072eefa1238b5a73ce093551ac9 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Mon, 8 Jun 2026 13:39:25 +0200 Subject: [PATCH 0943/1433] batman-adv: switch var declarations to reverse x-mas tree order The network related code should use for local variable declarations an ordering scheme which orders lines longest to shortest. Initializations should only be kept in the declarations when the dependencies between them are not preventing the reverse x-mas tree order. Many functions are already using this order. The remaining ones were supposed to slowly convert to the x-mas tree order when working on them. But this never happened because the patches tried to only modify the relevant lines. Instead of getting better, the order often just became worse. Just fix the remaining offending functions to finally solve this coding style (minor) problem. The anonymous dhcp structs were only extracted to have a clean reverse x-mas tree in functions and are not yet meant as an opportunity for further cleanups. Signed-off-by: Sven Eckelmann --- net/batman-adv/bat_algo.c | 3 +- net/batman-adv/bat_iv_ogm.c | 137 +++++++++--------- net/batman-adv/bat_v.c | 28 ++-- net/batman-adv/bat_v_elp.c | 18 +-- net/batman-adv/bat_v_ogm.c | 29 ++-- net/batman-adv/bridge_loop_avoidance.c | 94 ++++++------- net/batman-adv/distributed-arp-table.c | 124 +++++++++------- net/batman-adv/fragmentation.c | 37 ++--- net/batman-adv/gateway_client.c | 28 ++-- net/batman-adv/gateway_common.c | 2 +- net/batman-adv/hard-interface.c | 12 +- net/batman-adv/hash.h | 8 +- net/batman-adv/log.h | 16 ++- net/batman-adv/main.c | 20 +-- net/batman-adv/mesh-interface.c | 40 +++--- net/batman-adv/multicast.c | 36 +++-- net/batman-adv/multicast_forw.c | 13 +- net/batman-adv/netlink.c | 8 +- net/batman-adv/originator.c | 38 ++--- net/batman-adv/routing.c | 56 ++++---- net/batman-adv/send.c | 4 +- net/batman-adv/tp_meter.c | 31 ++-- net/batman-adv/translation-table.c | 188 +++++++++++++------------ net/batman-adv/tvlv.c | 16 +-- 24 files changed, 510 insertions(+), 476 deletions(-) diff --git a/net/batman-adv/bat_algo.c b/net/batman-adv/bat_algo.c index bd094bc793e3..93cd77680e64 100644 --- a/net/batman-adv/bat_algo.c +++ b/net/batman-adv/bat_algo.c @@ -131,8 +131,9 @@ static int batadv_param_set_ra(const char *val, const struct kernel_param *kp) { struct batadv_algo_ops *bat_algo_ops; char *algo_name = (char *)val; - size_t name_len = strlen(algo_name); + size_t name_len; + name_len = strlen(algo_name); if (name_len > 0 && algo_name[name_len - 1] == '\n') algo_name[name_len - 1] = '\0'; diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c index 337e8e3554bb..aff279b83da4 100644 --- a/net/batman-adv/bat_iv_ogm.c +++ b/net/batman-adv/bat_iv_ogm.c @@ -106,8 +106,8 @@ static u8 batadv_ring_buffer_avg(const u8 lq_recv[]) { const u8 *ptr; u16 count = 0; - u16 i = 0; u16 sum = 0; + u16 i = 0; ptr = lq_recv; @@ -408,12 +408,12 @@ static void batadv_iv_ogm_send_to_if(struct batadv_forw_packet *forw_packet, struct batadv_hard_iface *hard_iface) { struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); - const char *fwd_str; - u8 packet_num; - int buff_pos; struct batadv_ogm_packet *batadv_ogm_packet; + const char *fwd_str; struct sk_buff *skb; u8 *packet_pos; + u8 packet_num; + int buff_pos; if (hard_iface->if_status != BATADV_IF_ACTIVE) return; @@ -514,13 +514,13 @@ batadv_iv_ogm_can_aggregate(const struct batadv_ogm_packet *new_bat_ogm_packet, const struct batadv_hard_iface *if_outgoing, const struct batadv_forw_packet *forw_packet) { - struct batadv_ogm_packet *batadv_ogm_packet; unsigned int aggregated_bytes = forw_packet->packet_len + packet_len; + struct batadv_ogm_packet *batadv_ogm_packet; struct batadv_hard_iface *primary_if = NULL; u8 packet_num = forw_packet->num_packets; - bool res = false; unsigned long aggregation_end_time; unsigned int max_bytes; + bool res = false; batadv_ogm_packet = (struct batadv_ogm_packet *)forw_packet->skb->data; aggregation_end_time = send_time; @@ -627,10 +627,10 @@ static bool batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff, { struct batadv_priv *bat_priv = netdev_priv(if_incoming->mesh_iface); struct batadv_forw_packet *forw_packet_aggr; - struct sk_buff *skb; unsigned char *skb_buff; unsigned int skb_size; - atomic_t *queue_left = own_packet ? NULL : &bat_priv->batman_queue_left; + atomic_t *queue_left; + struct sk_buff *skb; if (READ_ONCE(bat_priv->aggregated_ogms)) skb_size = max_t(unsigned int, BATADV_MAX_AGGREGATION_BYTES, @@ -644,6 +644,7 @@ static bool batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff, if (!skb) return false; + queue_left = own_packet ? NULL : &bat_priv->batman_queue_left; forw_packet_aggr = batadv_forw_packet_alloc(if_incoming, if_outgoing, queue_left, bat_priv, skb); if (!forw_packet_aggr) { @@ -723,9 +724,9 @@ static bool batadv_iv_ogm_queue_add(struct batadv_priv *bat_priv, struct batadv_forw_packet *forw_packet_aggr = NULL; struct batadv_forw_packet *forw_packet_pos = NULL; struct batadv_ogm_packet *batadv_ogm_packet; - bool direct_link; unsigned long max_aggregation_jiffies; bool aggregated_ogms; + bool direct_link; batadv_ogm_packet = (struct batadv_ogm_packet *)packet_buff; direct_link = !!(batadv_ogm_packet->flags & BATADV_DIRECTLINK); @@ -856,9 +857,9 @@ batadv_iv_ogm_slide_own_bcast_window(struct batadv_hard_iface *hard_iface) { struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); struct batadv_hashtable *hash = bat_priv->orig_hash; - struct hlist_head *head; - struct batadv_orig_node *orig_node; struct batadv_orig_ifinfo *orig_ifinfo; + struct batadv_orig_node *orig_node; + struct hlist_head *head; unsigned long *word; u32 i; u8 *w; @@ -896,14 +897,14 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface) struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); struct batadv_ogm_buf *ogm_buff = &hard_iface->bat_iv.ogm_buff; struct batadv_ogm_packet *batadv_ogm_packet; - struct batadv_hard_iface *primary_if; struct batadv_hard_iface *tmp_hard_iface; - struct list_head *iter; - u32 seqno; - u16 tvlv_len = 0; + struct batadv_hard_iface *primary_if; unsigned long send_time; bool reschedule = false; + struct list_head *iter; + u16 tvlv_len = 0; bool scheduled; + u32 seqno; int ret; lockdep_assert_held(&hard_iface->bat_iv.ogm_buff_mutex); @@ -1105,14 +1106,14 @@ batadv_iv_ogm_orig_update(struct batadv_priv *bat_priv, struct batadv_hard_iface *if_outgoing, enum batadv_dup_status dup_status) { - struct batadv_neigh_ifinfo *neigh_ifinfo = NULL; struct batadv_neigh_ifinfo *router_ifinfo = NULL; - struct batadv_neigh_node *neigh_node = NULL; + struct batadv_neigh_ifinfo *neigh_ifinfo = NULL; struct batadv_neigh_node *tmp_neigh_node = NULL; + struct batadv_neigh_node *neigh_node = NULL; struct batadv_neigh_node *router = NULL; - u8 sum_orig; - u8 sum_neigh; u8 *neigh_addr; + u8 sum_neigh; + u8 sum_orig; u8 tq_avg; batadv_dbg(BATADV_DBG_BATMAN, bat_priv, @@ -1243,21 +1244,21 @@ static bool batadv_iv_ogm_calc_tq(struct batadv_orig_node *orig_node, struct batadv_hard_iface *if_outgoing) { struct batadv_priv *bat_priv = netdev_priv(if_incoming->mesh_iface); + unsigned int tq_iface_hop_penalty = BATADV_TQ_MAX_VALUE; struct batadv_neigh_node *neigh_node = NULL; struct batadv_neigh_node *tmp_neigh_node; struct batadv_neigh_ifinfo *neigh_ifinfo; - u8 total_count; - u8 orig_eq_count; - u8 neigh_rq_count; - u8 neigh_rq_inv; - u8 tq_own; - unsigned int tq_iface_hop_penalty = BATADV_TQ_MAX_VALUE; unsigned int neigh_rq_inv_cube; unsigned int neigh_rq_max_cube; - unsigned int tq_asym_penalty; unsigned int inv_asym_penalty; + unsigned int tq_asym_penalty; unsigned int combined_tq; + u8 neigh_rq_count; + u8 orig_eq_count; bool ret = false; + u8 neigh_rq_inv; + u8 total_count; + u8 tq_own; /* find corresponding one hop neighbor */ rcu_read_lock(); @@ -1390,19 +1391,19 @@ batadv_iv_ogm_update_seqnos(const struct ethhdr *ethhdr, struct batadv_hard_iface *if_outgoing) { struct batadv_priv *bat_priv = netdev_priv(if_incoming->mesh_iface); - struct batadv_orig_node *orig_node; struct batadv_orig_ifinfo *orig_ifinfo = NULL; - struct batadv_neigh_node *neigh_node; - struct batadv_neigh_ifinfo *neigh_ifinfo; - bool is_dup; - s32 seq_diff; - bool need_update = false; - int set_mark; - enum batadv_dup_status ret = BATADV_NO_DUP; u32 seqno = ntohl(batadv_ogm_packet->seqno); - u8 *neigh_addr; - u8 packet_count; + enum batadv_dup_status ret = BATADV_NO_DUP; + struct batadv_neigh_ifinfo *neigh_ifinfo; + struct batadv_neigh_node *neigh_node; + struct batadv_orig_node *orig_node; + bool need_update = false; unsigned long *bitmap; + u8 packet_count; + u8 *neigh_addr; + s32 seq_diff; + int set_mark; + bool is_dup; orig_node = batadv_iv_ogm_orig_get(bat_priv, batadv_ogm_packet->orig); if (!orig_node) @@ -1519,22 +1520,22 @@ batadv_iv_ogm_process_per_outif(const struct sk_buff *skb, int ogm_offset, { struct batadv_priv *bat_priv = netdev_priv(if_incoming->mesh_iface); struct batadv_hardif_neigh_node *hardif_neigh = NULL; - struct batadv_neigh_node *router = NULL; - struct batadv_neigh_node *router_router = NULL; - struct batadv_orig_node *orig_neigh_node; - struct batadv_orig_ifinfo *orig_ifinfo; struct batadv_neigh_node *orig_neigh_router = NULL; struct batadv_neigh_ifinfo *router_ifinfo = NULL; + struct batadv_neigh_node *router_router = NULL; + struct batadv_orig_node *orig_neigh_node; + struct batadv_neigh_node *router = NULL; + struct batadv_orig_ifinfo *orig_ifinfo; struct batadv_ogm_packet *ogm_packet; - enum batadv_dup_status dup_status; bool is_from_best_next_hop = false; + enum batadv_dup_status dup_status; bool is_single_hop_neigh = false; - bool sameseq; - bool similar_ttl; struct sk_buff *skb_priv; struct ethhdr *ethhdr; - u8 *prev_sender; + bool similar_ttl; bool is_bidirect; + u8 *prev_sender; + bool sameseq; /* create a private copy of the skb, as some functions change tq value * and/or flags. @@ -1757,16 +1758,16 @@ static void batadv_iv_ogm_process(const struct sk_buff *skb, int ogm_offset, { struct batadv_priv *bat_priv = netdev_priv(if_incoming->mesh_iface); struct batadv_orig_node *orig_neigh_node; - struct batadv_orig_node *orig_node; struct batadv_hard_iface *hard_iface; struct batadv_ogm_packet *ogm_packet; - u32 if_incoming_seqno; - bool has_directlink_flag; - struct ethhdr *ethhdr; + struct batadv_orig_node *orig_node; bool is_my_oldorig = false; + bool has_directlink_flag; bool is_my_addr = false; bool is_my_orig = false; struct list_head *iter; + u32 if_incoming_seqno; + struct ethhdr *ethhdr; ogm_packet = (struct batadv_ogm_packet *)(skb->data + ogm_offset); ethhdr = eth_hdr(skb); @@ -1893,8 +1894,8 @@ static void batadv_iv_ogm_process(const struct sk_buff *skb, int ogm_offset, */ static void batadv_iv_send_outstanding_bat_ogm_packet(struct work_struct *work) { - struct delayed_work *delayed_work; struct batadv_forw_packet *forw_packet; + struct delayed_work *delayed_work; struct batadv_priv *bat_priv; bool dropped = false; @@ -1945,10 +1946,10 @@ static int batadv_iv_ogm_receive(struct sk_buff *skb, { struct batadv_priv *bat_priv = netdev_priv(if_incoming->mesh_iface); struct batadv_ogm_packet *ogm_packet; + int ret = NET_RX_DROP; u8 *packet_pos; int ogm_offset; bool res; - int ret = NET_RX_DROP; res = batadv_check_management_packet(skb, if_incoming, BATADV_OGM_HLEN); if (!res) @@ -2038,9 +2039,9 @@ batadv_iv_ogm_orig_dump_subentry(struct sk_buff *msg, u32 portid, u32 seq, struct batadv_neigh_node *neigh_node, bool best) { + unsigned int last_seen_msecs; void *hdr; u8 tq_avg; - unsigned int last_seen_msecs; last_seen_msecs = jiffies_to_msecs(jiffies - orig_node->last_seen); @@ -2102,9 +2103,9 @@ batadv_iv_ogm_orig_dump_entry(struct sk_buff *msg, u32 portid, u32 seq, { struct batadv_neigh_node *neigh_node_best; struct batadv_neigh_node *neigh_node; + u8 tq_avg_best; int sub = 0; bool best; - u8 tq_avg_best; neigh_node_best = batadv_orig_router_get(orig_node, if_outgoing); if (!neigh_node_best) @@ -2197,11 +2198,11 @@ batadv_iv_ogm_orig_dump(struct sk_buff *msg, struct netlink_callback *cb, struct batadv_hard_iface *if_outgoing) { struct batadv_hashtable *hash = bat_priv->orig_hash; - struct hlist_head *head; + int portid = NETLINK_CB(cb->skb).portid; int bucket = cb->args[0]; + struct hlist_head *head; int idx = cb->args[1]; int sub = cb->args[2]; - int portid = NETLINK_CB(cb->skb).portid; while (bucket < hash->size) { head = &hash->table[bucket]; @@ -2242,9 +2243,9 @@ static bool batadv_iv_ogm_neigh_diff(struct batadv_neigh_node *neigh1, { struct batadv_neigh_ifinfo *neigh1_ifinfo; struct batadv_neigh_ifinfo *neigh2_ifinfo; + bool ret = true; u8 tq1; u8 tq2; - bool ret = true; neigh1_ifinfo = batadv_neigh_ifinfo_get(neigh1, if_outgoing1); neigh2_ifinfo = batadv_neigh_ifinfo_get(neigh2, if_outgoing2); @@ -2278,8 +2279,8 @@ static int batadv_iv_ogm_neigh_dump_neigh(struct sk_buff *msg, u32 portid, u32 seq, struct batadv_hardif_neigh_node *hardif_neigh) { - void *hdr; unsigned int last_seen_msecs; + void *hdr; last_seen_msecs = jiffies_to_msecs(jiffies - hardif_neigh->last_seen); @@ -2357,12 +2358,12 @@ batadv_iv_ogm_neigh_dump(struct sk_buff *msg, struct netlink_callback *cb, struct batadv_priv *bat_priv, struct batadv_hard_iface *single_hardif) { - struct batadv_hard_iface *hard_iface; - struct list_head *iter; - int i_hardif = 0; - int i_hardif_s = cb->args[0]; - int idx = cb->args[1]; int portid = NETLINK_CB(cb->skb).portid; + struct batadv_hard_iface *hard_iface; + int i_hardif_s = cb->args[0]; + struct list_head *iter; + int idx = cb->args[1]; + int i_hardif = 0; rcu_read_lock(); if (single_hardif) { @@ -2484,15 +2485,15 @@ static void batadv_iv_init_sel_class(struct batadv_priv *bat_priv) static struct batadv_gw_node * batadv_iv_gw_get_best_gw_node(struct batadv_priv *bat_priv) { - struct batadv_neigh_node *router; struct batadv_neigh_ifinfo *router_ifinfo; - struct batadv_gw_node *gw_node; struct batadv_gw_node *curr_gw = NULL; + struct batadv_orig_node *orig_node; + struct batadv_neigh_node *router; + struct batadv_gw_node *gw_node; u64 max_gw_factor = 0; u64 tmp_gw_factor = 0; u8 max_tq = 0; u8 tq_avg; - struct batadv_orig_node *orig_node; rcu_read_lock(); hlist_for_each_entry_rcu(gw_node, &bat_priv->gw.gateway_list, list) { @@ -2579,11 +2580,11 @@ static bool batadv_iv_gw_is_eligible(struct batadv_priv *bat_priv, struct batadv_neigh_ifinfo *router_orig_ifinfo = NULL; struct batadv_neigh_ifinfo *router_gw_ifinfo = NULL; u32 sel_class = READ_ONCE(bat_priv->gw.sel_class); - struct batadv_neigh_node *router_gw = NULL; struct batadv_neigh_node *router_orig = NULL; - u8 gw_tq_avg; - u8 orig_tq_avg; + struct batadv_neigh_node *router_gw = NULL; bool ret = false; + u8 orig_tq_avg; + u8 gw_tq_avg; /* dynamic re-election is performed only on fast or late switch */ if (sel_class <= 2) @@ -2654,8 +2655,8 @@ static int batadv_iv_gw_dump_entry(struct sk_buff *msg, u32 portid, struct batadv_gw_node *gw_node) { struct batadv_neigh_ifinfo *router_ifinfo = NULL; - struct batadv_neigh_node *router; struct batadv_gw_node *curr_gw = NULL; + struct batadv_neigh_node *router; int ret = 0; void *hdr; diff --git a/net/batman-adv/bat_v.c b/net/batman-adv/bat_v.c index be28875c201d..ee372fc9d44e 100644 --- a/net/batman-adv/bat_v.c +++ b/net/batman-adv/bat_v.c @@ -159,9 +159,9 @@ static int batadv_v_neigh_dump_neigh(struct sk_buff *msg, u32 portid, u32 seq, struct batadv_hardif_neigh_node *hardif_neigh) { - void *hdr; unsigned int last_seen_msecs; u32 throughput; + void *hdr; last_seen_msecs = jiffies_to_msecs(jiffies - hardif_neigh->last_seen); throughput = ewma_throughput_read(&hardif_neigh->bat_v.throughput); @@ -242,12 +242,12 @@ batadv_v_neigh_dump(struct sk_buff *msg, struct netlink_callback *cb, struct batadv_priv *bat_priv, struct batadv_hard_iface *single_hardif) { - struct batadv_hard_iface *hard_iface; - struct list_head *iter; - int i_hardif = 0; - int i_hardif_s = cb->args[0]; - int idx = cb->args[1]; int portid = NETLINK_CB(cb->skb).portid; + struct batadv_hard_iface *hard_iface; + int i_hardif_s = cb->args[0]; + struct list_head *iter; + int idx = cb->args[1]; + int i_hardif = 0; rcu_read_lock(); if (single_hardif) { @@ -452,11 +452,11 @@ batadv_v_orig_dump(struct sk_buff *msg, struct netlink_callback *cb, struct batadv_hard_iface *if_outgoing) { struct batadv_hashtable *hash = bat_priv->orig_hash; - struct hlist_head *head; + int portid = NETLINK_CB(cb->skb).portid; int bucket = cb->args[0]; + struct hlist_head *head; int idx = cb->args[1]; int sub = cb->args[2]; - int portid = NETLINK_CB(cb->skb).portid; while (bucket < hash->size) { head = &hash->table[bucket]; @@ -529,8 +529,8 @@ static bool batadv_v_neigh_is_sob(struct batadv_neigh_node *neigh1, { struct batadv_neigh_ifinfo *ifinfo1; struct batadv_neigh_ifinfo *ifinfo2; - u32 threshold; bool ret = false; + u32 threshold; ifinfo1 = batadv_neigh_ifinfo_get(neigh1, if_outgoing1); if (!ifinfo1) @@ -612,8 +612,8 @@ static int batadv_v_gw_throughput_get(struct batadv_gw_node *gw_node, u32 *bw) static struct batadv_gw_node * batadv_v_gw_get_best_gw_node(struct batadv_priv *bat_priv) { - struct batadv_gw_node *gw_node; struct batadv_gw_node *curr_gw = NULL; + struct batadv_gw_node *gw_node; u32 max_bw = 0; u32 bw; @@ -654,12 +654,12 @@ static bool batadv_v_gw_is_eligible(struct batadv_priv *bat_priv, struct batadv_orig_node *curr_gw_orig, struct batadv_orig_node *orig_node) { - struct batadv_gw_node *curr_gw; struct batadv_gw_node *orig_gw = NULL; - u32 gw_throughput; + struct batadv_gw_node *curr_gw; u32 orig_throughput; - u32 threshold; + u32 gw_throughput; bool ret = false; + u32 threshold; threshold = READ_ONCE(bat_priv->gw.sel_class); @@ -715,8 +715,8 @@ static int batadv_v_gw_dump_entry(struct sk_buff *msg, u32 portid, struct batadv_gw_node *gw_node) { struct batadv_neigh_ifinfo *router_ifinfo = NULL; - struct batadv_neigh_node *router; struct batadv_gw_node *curr_gw = NULL; + struct batadv_neigh_node *router; int ret = 0; void *hdr; diff --git a/net/batman-adv/bat_v_elp.c b/net/batman-adv/bat_v_elp.c index eb7fb8c14ef3..6ad6042a5d9b 100644 --- a/net/batman-adv/bat_v_elp.c +++ b/net/batman-adv/bat_v_elp.c @@ -230,12 +230,14 @@ static bool batadv_v_elp_wifi_neigh_probe(struct batadv_hardif_neigh_node *neigh) { struct batadv_hard_iface *hard_iface = neigh->if_incoming; - struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); + struct batadv_priv *bat_priv; unsigned long last_tx_diff; struct sk_buff *skb; + int elp_skb_len; int probe_len; int i; - int elp_skb_len; + + bat_priv = netdev_priv(hard_iface->mesh_iface); /* this probing routine is for Wifi neighbours only */ if (!batadv_is_wifi_hardif(hard_iface)) @@ -290,8 +292,8 @@ static void batadv_v_elp_periodic_work(struct work_struct *work) struct batadv_v_metric_queue_entry *metric_entry; struct batadv_v_metric_queue_entry *metric_safe; struct batadv_hardif_neigh_node *hardif_neigh; - struct batadv_hard_iface *hard_iface; struct batadv_hard_iface_bat_v *bat_v; + struct batadv_hard_iface *hard_iface; struct batadv_elp_packet *elp_packet; struct list_head metric_queue; struct batadv_priv *bat_priv; @@ -396,9 +398,9 @@ int batadv_v_elp_iface_enable(struct batadv_hard_iface *hard_iface) static const size_t tvlv_padding = sizeof(__be32); struct batadv_elp_packet *elp_packet; unsigned char *elp_buff; + int res = -ENOMEM; u32 random_seqno; size_t size; - int res = -ENOMEM; size = ETH_HLEN + NET_IP_ALIGN + BATADV_ELP_HLEN + tvlv_padding; hard_iface->bat_v.elp_skb = dev_alloc_skb(size); @@ -501,11 +503,11 @@ static void batadv_v_elp_neigh_update(struct batadv_priv *bat_priv, struct batadv_elp_packet *elp_packet) { - struct batadv_neigh_node *neigh; - struct batadv_orig_node *orig_neigh; struct batadv_hardif_neigh_node *hardif_neigh; - s32 seqno_diff; + struct batadv_orig_node *orig_neigh; + struct batadv_neigh_node *neigh; s32 elp_latest_seqno; + s32 seqno_diff; orig_neigh = batadv_v_ogm_orig_get(bat_priv, elp_packet->orig); if (!orig_neigh) @@ -557,8 +559,8 @@ int batadv_v_elp_packet_recv(struct sk_buff *skb, struct batadv_elp_packet *elp_packet; struct batadv_hard_iface *primary_if; struct ethhdr *ethhdr; - bool res; int ret = NET_RX_DROP; + bool res; res = batadv_check_management_packet(skb, if_incoming, BATADV_ELP_HLEN); if (!res) diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c index d4527663f76d..70846c997410 100644 --- a/net/batman-adv/bat_v_ogm.c +++ b/net/batman-adv/bat_v_ogm.c @@ -101,6 +101,7 @@ static void batadv_v_ogm_start_queue_timer(struct batadv_hard_iface *hard_iface) static void batadv_v_ogm_start_timer(struct batadv_priv *bat_priv) { unsigned long msecs; + /* this function may be invoked in different contexts (ogm rescheduling * or hard_iface activation), but the work timer should not be reset */ @@ -268,12 +269,12 @@ static void batadv_v_ogm_queue_on_if(struct sk_buff *skb, */ static void batadv_v_ogm_send_meshif(struct batadv_priv *bat_priv) { - struct batadv_hard_iface *hard_iface; struct batadv_ogm2_packet *ogm_packet; + struct batadv_hard_iface *hard_iface; struct batadv_ogm_buf *ogm_buff; - struct sk_buff *skb; struct sk_buff *skb_tmp; struct list_head *iter; + struct sk_buff *skb; u16 tvlv_len; int ret; @@ -622,11 +623,11 @@ static int batadv_v_ogm_metric_update(struct batadv_priv *bat_priv, struct batadv_hard_iface *if_incoming, struct batadv_hard_iface *if_outgoing) { - struct batadv_orig_ifinfo *orig_ifinfo; struct batadv_neigh_ifinfo *neigh_ifinfo = NULL; + struct batadv_orig_ifinfo *orig_ifinfo; bool protection_started = false; - int ret = -EINVAL; u32 path_throughput; + int ret = -EINVAL; s32 seq_diff; orig_ifinfo = batadv_orig_ifinfo_new(orig_node, if_outgoing); @@ -704,17 +705,17 @@ static bool batadv_v_ogm_route_update(struct batadv_priv *bat_priv, struct batadv_hard_iface *if_incoming, struct batadv_hard_iface *if_outgoing) { - struct batadv_neigh_node *router = NULL; - struct batadv_orig_node *orig_neigh_node; struct batadv_neigh_node *orig_neigh_router = NULL; struct batadv_neigh_ifinfo *router_ifinfo = NULL; struct batadv_neigh_ifinfo *neigh_ifinfo = NULL; + struct batadv_orig_node *orig_neigh_node; + struct batadv_neigh_node *router = NULL; u32 router_throughput; - u32 neigh_throughput; u32 router_last_seqno; + u32 neigh_throughput; u32 neigh_last_seqno; - s32 neigh_seq_diff; bool forward = false; + s32 neigh_seq_diff; orig_neigh_node = batadv_v_ogm_orig_get(bat_priv, ethhdr->h_source); if (!orig_neigh_node) @@ -874,16 +875,16 @@ static void batadv_v_ogm_process(const struct sk_buff *skb, int ogm_offset, struct batadv_hard_iface *if_incoming) { struct batadv_priv *bat_priv = netdev_priv(if_incoming->mesh_iface); - struct ethhdr *ethhdr; - struct batadv_orig_node *orig_node = NULL; struct batadv_hardif_neigh_node *hardif_neigh = NULL; struct batadv_neigh_node *neigh_node = NULL; - struct batadv_hard_iface *hard_iface; + struct batadv_orig_node *orig_node = NULL; struct batadv_ogm2_packet *ogm_packet; - u32 ogm_throughput; + struct batadv_hard_iface *hard_iface; + struct list_head *iter; + struct ethhdr *ethhdr; u32 link_throughput; u32 path_throughput; - struct list_head *iter; + u32 ogm_throughput; int ret; ethhdr = eth_hdr(skb); @@ -1009,9 +1010,9 @@ int batadv_v_ogm_packet_recv(struct sk_buff *skb, struct batadv_priv *bat_priv = netdev_priv(if_incoming->mesh_iface); struct batadv_ogm2_packet *ogm_packet; struct ethhdr *ethhdr; + int ret = NET_RX_DROP; int ogm_offset; u8 *packet_pos; - int ret = NET_RX_DROP; /* did we receive a OGM2 packet on an interface that does not have * B.A.T.M.A.N. V enabled ? diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c index f6faf198217a..94e074235e15 100644 --- a/net/batman-adv/bridge_loop_avoidance.c +++ b/net/batman-adv/bridge_loop_avoidance.c @@ -177,8 +177,8 @@ static void batadv_backbone_gw_put(struct batadv_bla_backbone_gw *backbone_gw) */ static void batadv_claim_release(struct kref *ref) { - struct batadv_bla_claim *claim; struct batadv_bla_backbone_gw *old_backbone_gw; + struct batadv_bla_claim *claim; claim = container_of(ref, struct batadv_bla_claim, refcount); @@ -220,9 +220,9 @@ batadv_claim_hash_find(struct batadv_priv *bat_priv, struct batadv_bla_claim *data) { struct batadv_hashtable *hash = bat_priv->bla.claim_hash; - struct hlist_head *head; - struct batadv_bla_claim *claim; struct batadv_bla_claim *claim_tmp = NULL; + struct batadv_bla_claim *claim; + struct hlist_head *head; int index; if (!hash) @@ -260,10 +260,10 @@ batadv_backbone_hash_find(struct batadv_priv *bat_priv, const u8 *addr, unsigned short vid) { struct batadv_hashtable *hash = bat_priv->bla.backbone_hash; - struct hlist_head *head; + struct batadv_bla_backbone_gw *backbone_gw_tmp = NULL; struct batadv_bla_backbone_gw search_entry; struct batadv_bla_backbone_gw *backbone_gw; - struct batadv_bla_backbone_gw *backbone_gw_tmp = NULL; + struct hlist_head *head; int index; if (!hash) @@ -299,12 +299,12 @@ batadv_backbone_hash_find(struct batadv_priv *bat_priv, const u8 *addr, static void batadv_bla_del_backbone_claims(struct batadv_bla_backbone_gw *backbone_gw) { + spinlock_t *list_lock; /* protects write access to the hash lists */ + struct batadv_bla_claim *claim; struct batadv_hashtable *hash; struct hlist_node *node_tmp; struct hlist_head *head; - struct batadv_bla_claim *claim; int i; - spinlock_t *list_lock; /* protects write access to the hash lists */ hash = backbone_gw->bat_priv->bla.claim_hash; if (!hash) @@ -342,12 +342,12 @@ batadv_bla_del_backbone_claims(struct batadv_bla_backbone_gw *backbone_gw) static void batadv_bla_send_claim(struct batadv_priv *bat_priv, const u8 *mac, unsigned short vid, int claimtype) { - struct sk_buff *skb; - struct ethhdr *ethhdr; - struct batadv_hard_iface *primary_if; - u8 *hw_src; struct batadv_bla_claim_dst local_claim_dest; + struct batadv_hard_iface *primary_if; + struct ethhdr *ethhdr; + struct sk_buff *skb; __be32 zeroip = 0; + u8 *hw_src; primary_if = batadv_primary_if_get_selected(bat_priv); if (!primary_if) @@ -594,10 +594,10 @@ static void batadv_bla_answer_request(struct batadv_priv *bat_priv, struct batadv_hard_iface *primary_if, unsigned short vid) { - struct hlist_head *head; - struct batadv_hashtable *hash; - struct batadv_bla_claim *claim; struct batadv_bla_backbone_gw *backbone_gw; + struct batadv_bla_claim *claim; + struct batadv_hashtable *hash; + struct hlist_head *head; int i; batadv_dbg(BATADV_DBG_BLA, bat_priv, @@ -693,8 +693,8 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv, struct batadv_bla_backbone_gw *backbone_gw) { struct batadv_bla_backbone_gw *old_backbone_gw; - struct batadv_bla_claim *claim; struct batadv_bla_claim search_claim; + struct batadv_bla_claim *claim; bool remove_crc = false; int hash_added; @@ -801,10 +801,10 @@ batadv_bla_claim_get_backbone_gw(struct batadv_bla_claim *claim) static void batadv_bla_del_claim(struct batadv_priv *bat_priv, const u8 *mac, const unsigned short vid) { - struct batadv_bla_claim search_claim; - struct batadv_bla_claim *claim; struct batadv_bla_claim *claim_removed_entry; struct hlist_node *claim_removed_node; + struct batadv_bla_claim search_claim; + struct batadv_bla_claim *claim; ether_addr_copy(search_claim.addr, mac); search_claim.vid = vid; @@ -1022,10 +1022,10 @@ static int batadv_check_claim_group(struct batadv_priv *bat_priv, u8 *hw_src, u8 *hw_dst, struct ethhdr *ethhdr) { - u8 *backbone_addr; - struct batadv_orig_node *orig_node; - struct batadv_bla_claim_dst *bla_dst; struct batadv_bla_claim_dst *bla_dst_own; + struct batadv_bla_claim_dst *bla_dst; + struct batadv_orig_node *orig_node; + u8 *backbone_addr; bla_dst = (struct batadv_bla_claim_dst *)hw_dst; bla_dst_own = &bat_priv->bla.claim_dest; @@ -1094,18 +1094,18 @@ static bool batadv_bla_process_claim(struct batadv_priv *bat_priv, struct batadv_hard_iface *primary_if, struct sk_buff *skb) { - struct batadv_bla_claim_dst *bla_dst; struct batadv_bla_claim_dst *bla_dst_own; - u8 *hw_src; - u8 *hw_dst; - struct vlan_hdr *vhdr; + struct batadv_bla_claim_dst *bla_dst; struct vlan_hdr vhdr_buf; + struct vlan_hdr *vhdr; struct ethhdr *ethhdr; struct arphdr *arphdr; unsigned short vid; int vlan_depth = 0; __be16 proto; int headlen; + u8 *hw_src; + u8 *hw_dst; int ret; vid = batadv_get_vid(skb, 0); @@ -1237,11 +1237,11 @@ static bool batadv_bla_process_claim(struct batadv_priv *bat_priv, */ static void batadv_bla_purge_backbone_gw(struct batadv_priv *bat_priv, int now) { + spinlock_t *list_lock; /* protects write access to the hash lists */ struct batadv_bla_backbone_gw *backbone_gw; + struct batadv_hashtable *hash; struct hlist_node *node_tmp; struct hlist_head *head; - struct batadv_hashtable *hash; - spinlock_t *list_lock; /* protects write access to the hash lists */ bool purged; int i; @@ -1314,8 +1314,8 @@ static void batadv_bla_purge_claims(struct batadv_priv *bat_priv, { struct batadv_bla_backbone_gw *backbone_gw; struct batadv_bla_claim *claim; - struct hlist_head *head; struct batadv_hashtable *hash; + struct hlist_head *head; int i; hash = bat_priv->bla.claim_hash; @@ -1377,8 +1377,8 @@ void batadv_bla_update_orig_address(struct batadv_priv *bat_priv, struct batadv_hard_iface *oldif) { struct batadv_bla_backbone_gw *backbone_gw; - struct hlist_head *head; struct batadv_hashtable *hash; + struct hlist_head *head; __be16 group; int i; @@ -1471,14 +1471,14 @@ void batadv_bla_status_update(struct net_device *net_dev) */ static void batadv_bla_periodic_work(struct work_struct *work) { - struct delayed_work *delayed_work; - struct batadv_priv *bat_priv; - struct batadv_priv_bla *priv_bla; - struct hlist_head *head; struct batadv_bla_backbone_gw *backbone_gw; - struct batadv_hashtable *hash; struct batadv_hard_iface *primary_if; + struct delayed_work *delayed_work; + struct batadv_priv_bla *priv_bla; + struct batadv_hashtable *hash; + struct batadv_priv *bat_priv; bool send_loopdetect = false; + struct hlist_head *head; int i; delayed_work = to_delayed_work(work); @@ -1580,11 +1580,11 @@ static struct lock_class_key batadv_backbone_hash_lock_class_key; */ int batadv_bla_init(struct batadv_priv *bat_priv) { - int i; u8 claim_dest[ETH_ALEN] = {0xff, 0x43, 0x05, 0x00, 0x00, 0x00}; struct batadv_hard_iface *primary_if; - u16 crc; unsigned long entrytime; + u16 crc; + int i; spin_lock_init(&bat_priv->bla.bcast_duplist_lock); @@ -1663,9 +1663,9 @@ static bool batadv_bla_check_duplist(struct batadv_priv *bat_priv, struct batadv_bcast_duplist_entry *entry; bool ret = false; int payload_len; - int i; int curr; u32 crc; + int i; /* calculate the crc ... */ payload_len = skb->len - payload_offset; @@ -1787,8 +1787,8 @@ bool batadv_bla_is_backbone_gw_orig(struct batadv_priv *bat_priv, u8 *orig, unsigned short vid) { struct batadv_hashtable *hash = bat_priv->bla.backbone_hash; - struct hlist_head *head; struct batadv_bla_backbone_gw *backbone_gw; + struct hlist_head *head; int i; if (!READ_ONCE(bat_priv->bridge_loop_avoidance)) @@ -1954,10 +1954,10 @@ bool batadv_bla_rx(struct batadv_priv *bat_priv, struct sk_buff *skb, unsigned short vid, int packet_type) { struct batadv_bla_backbone_gw *backbone_gw; - struct ethhdr *ethhdr; - struct batadv_bla_claim search_claim; struct batadv_bla_claim *claim = NULL; + struct batadv_bla_claim search_claim; struct batadv_hard_iface *primary_if; + struct ethhdr *ethhdr; bool own_claim; bool ret; @@ -2091,11 +2091,11 @@ bool batadv_bla_rx(struct batadv_priv *bat_priv, struct sk_buff *skb, bool batadv_bla_tx(struct batadv_priv *bat_priv, struct sk_buff *skb, unsigned short vid) { - struct ethhdr *ethhdr; - struct batadv_bla_claim search_claim; - struct batadv_bla_claim *claim = NULL; struct batadv_bla_backbone_gw *backbone_gw; + struct batadv_bla_claim *claim = NULL; + struct batadv_bla_claim search_claim; struct batadv_hard_iface *primary_if; + struct ethhdr *ethhdr; bool client_roamed; bool ret = false; @@ -2196,10 +2196,10 @@ batadv_bla_claim_dump_entry(struct sk_buff *msg, u32 portid, { const u8 *primary_addr = primary_if->net_dev->dev_addr; struct batadv_bla_backbone_gw *backbone_gw; + int ret = -EINVAL; u16 backbone_crc; bool is_own; void *hdr; - int ret = -EINVAL; hdr = genlmsg_put(msg, portid, cb->nlh->nlmsg_seq, &batadv_netlink_family, NLM_F_MULTI, @@ -2358,11 +2358,11 @@ batadv_bla_backbone_dump_entry(struct sk_buff *msg, u32 portid, struct batadv_bla_backbone_gw *backbone_gw) { const u8 *primary_addr = primary_if->net_dev->dev_addr; + int ret = -EINVAL; u16 backbone_crc; bool is_own; int msecs; void *hdr; - int ret = -EINVAL; hdr = genlmsg_put(msg, portid, cb->nlh->nlmsg_seq, &batadv_netlink_family, NLM_F_MULTI, @@ -2517,10 +2517,10 @@ int batadv_bla_backbone_dump(struct sk_buff *msg, struct netlink_callback *cb) bool batadv_bla_check_claim(struct batadv_priv *bat_priv, u8 *addr, unsigned short vid) { - struct batadv_bla_backbone_gw *backbone_gw; - struct batadv_bla_claim search_claim; - struct batadv_bla_claim *claim = NULL; struct batadv_hard_iface *primary_if = NULL; + struct batadv_bla_backbone_gw *backbone_gw; + struct batadv_bla_claim *claim = NULL; + struct batadv_bla_claim search_claim; bool ret = true; if (!READ_ONCE(bat_priv->bridge_loop_avoidance)) diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c index ea48460ac9cb..0d5a9cb0affe 100644 --- a/net/batman-adv/distributed-arp-table.c +++ b/net/batman-adv/distributed-arp-table.c @@ -128,6 +128,34 @@ struct batadv_dhcp_packet { /* __u8 options[]; */ }; +/** + * struct batadv_dhcp_header - minimal BOOTP/DHCP packet header + */ +struct batadv_dhcp_header { + /** @op: message op code / message type */ + __u8 op; + + /** @htype: hardware address type */ + __u8 htype; + + /** @hlen: hardware address length */ + __u8 hlen; + + /** @hops: number of relay hops */ + __u8 hops; +}; + +/** + * struct batadv_dhcp_option_header - BOOTP/DHCP option header + */ +struct batadv_dhcp_option_header { + /** @type: type of option */ + __u8 type; + + /** @len: length of option */ + __u8 len; +}; + #define BATADV_DHCP_YIADDR_LEN sizeof(((struct batadv_dhcp_packet *)0)->yiaddr) #define BATADV_DHCP_CHADDR_LEN sizeof(((struct batadv_dhcp_packet *)0)->chaddr) @@ -326,9 +354,9 @@ static __be32 batadv_arp_ip_dst(struct sk_buff *skb, int hdr_size) */ static u32 batadv_hash_dat(const void *data, u32 size) { - u32 hash = 0; const struct batadv_dat_entry *dat = data; const unsigned char *key; + u32 hash = 0; __be16 vid; u32 i; @@ -367,11 +395,11 @@ static struct batadv_dat_entry * batadv_dat_entry_hash_find(struct batadv_priv *bat_priv, __be32 ip, unsigned short vid) { - struct hlist_head *head; - struct batadv_dat_entry to_find; - struct batadv_dat_entry *dat_entry; - struct batadv_dat_entry *dat_entry_tmp = NULL; struct batadv_hashtable *hash = bat_priv->dat.hash; + struct batadv_dat_entry *dat_entry_tmp = NULL; + struct batadv_dat_entry *dat_entry; + struct batadv_dat_entry to_find; + struct hlist_head *head; u32 index; if (!hash) @@ -608,11 +636,11 @@ static void batadv_choose_next_candidate(struct batadv_priv *bat_priv, int select, batadv_dat_addr_t ip_key, batadv_dat_addr_t *last_max) { - batadv_dat_addr_t max = 0; - batadv_dat_addr_t tmp_max = 0; - struct batadv_orig_node *orig_node; - struct batadv_orig_node *max_orig_node = NULL; struct batadv_hashtable *hash = bat_priv->orig_hash; + struct batadv_orig_node *max_orig_node = NULL; + struct batadv_orig_node *orig_node; + batadv_dat_addr_t tmp_max = 0; + batadv_dat_addr_t max = 0; struct hlist_head *head; int i; @@ -676,11 +704,11 @@ static struct batadv_dat_candidate * batadv_dat_select_candidates(struct batadv_priv *bat_priv, __be32 ip_dst, unsigned short vid) { - int select; batadv_dat_addr_t last_max = BATADV_DAT_ADDR_MAX; - batadv_dat_addr_t ip_key; struct batadv_dat_candidate *res; struct batadv_dat_entry dat; + batadv_dat_addr_t ip_key; + int select; if (!bat_priv->orig_hash) return NULL; @@ -723,12 +751,12 @@ static bool batadv_dat_forward_data(struct batadv_priv *bat_priv, struct sk_buff *skb, __be32 ip, unsigned short vid, int packet_subtype) { - int i; + struct batadv_neigh_node *neigh_node = NULL; + struct batadv_dat_candidate *cand; + struct sk_buff *tmp_skb; bool ret = false; int send_status; - struct batadv_neigh_node *neigh_node = NULL; - struct sk_buff *tmp_skb; - struct batadv_dat_candidate *cand; + int i; cand = batadv_dat_select_candidates(bat_priv, ip, vid); if (!cand) @@ -1050,9 +1078,9 @@ static u16 batadv_arp_get_type(struct batadv_priv *bat_priv, struct ethhdr *ethhdr; __be32 ip_src; __be32 ip_dst; + u16 type = 0; u8 *hw_src; u8 *hw_dst; - u16 type = 0; /* pull the ethernet header */ if (unlikely(!pskb_may_pull(skb, hdr_size + ETH_HLEN))) @@ -1198,16 +1226,16 @@ batadv_dat_arp_create_reply(struct batadv_priv *bat_priv, __be32 ip_src, bool batadv_dat_snoop_outgoing_arp_request(struct batadv_priv *bat_priv, struct sk_buff *skb) { - u16 type = 0; - __be32 ip_dst; - __be32 ip_src; - u8 *hw_src; - bool ret = false; + struct net_device *mesh_iface = bat_priv->mesh_iface; struct batadv_dat_entry *dat_entry = NULL; struct sk_buff *skb_new; - struct net_device *mesh_iface = bat_priv->mesh_iface; - int hdr_size = 0; unsigned short vid; + bool ret = false; + int hdr_size = 0; + __be32 ip_dst; + __be32 ip_src; + u16 type = 0; + u8 *hw_src; if (!READ_ONCE(bat_priv->distributed_arp_table)) goto out; @@ -1304,14 +1332,14 @@ bool batadv_dat_snoop_outgoing_arp_request(struct batadv_priv *bat_priv, bool batadv_dat_snoop_incoming_arp_request(struct batadv_priv *bat_priv, struct sk_buff *skb, int hdr_size) { - u16 type; + struct batadv_dat_entry *dat_entry = NULL; + struct sk_buff *skb_new; + unsigned short vid; + bool ret = false; __be32 ip_src; __be32 ip_dst; u8 *hw_src; - struct sk_buff *skb_new; - struct batadv_dat_entry *dat_entry = NULL; - bool ret = false; - unsigned short vid; + u16 type; int err; if (!READ_ONCE(bat_priv->distributed_arp_table)) @@ -1371,13 +1399,13 @@ bool batadv_dat_snoop_incoming_arp_request(struct batadv_priv *bat_priv, void batadv_dat_snoop_outgoing_arp_reply(struct batadv_priv *bat_priv, struct sk_buff *skb) { - u16 type; + unsigned short vid; + int hdr_size = 0; __be32 ip_src; __be32 ip_dst; u8 *hw_src; u8 *hw_dst; - int hdr_size = 0; - unsigned short vid; + u16 type; if (!READ_ONCE(bat_priv->distributed_arp_table)) return; @@ -1430,13 +1458,13 @@ bool batadv_dat_snoop_incoming_arp_reply(struct batadv_priv *bat_priv, struct sk_buff *skb, int hdr_size) { struct batadv_dat_entry *dat_entry = NULL; - u16 type; + bool dropped = false; + unsigned short vid; __be32 ip_src; __be32 ip_dst; u8 *hw_src; u8 *hw_dst; - bool dropped = false; - unsigned short vid; + u16 type; if (!READ_ONCE(bat_priv->distributed_arp_table)) goto out; @@ -1568,15 +1596,11 @@ batadv_dat_check_dhcp_ipudp(struct sk_buff *skb, __be32 *ip_src) static int batadv_dat_check_dhcp(struct sk_buff *skb, __be16 proto, __be32 *ip_src) { + struct batadv_dhcp_header *dhcp_h; + struct batadv_dhcp_header _dhcp_h; + unsigned int offset; __be32 *magic; __be32 _magic; - unsigned int offset; - struct { - __u8 op; - __u8 htype; - __u8 hlen; - __u8 hops; - } *dhcp_h, _dhcp_h; if (proto != htons(ETH_P_IP)) return -EINVAL; @@ -1617,12 +1641,10 @@ batadv_dat_check_dhcp(struct sk_buff *skb, __be16 proto, __be32 *ip_src) static int batadv_dat_get_dhcp_message_type(struct sk_buff *skb) { unsigned int offset = skb_transport_offset(skb) + sizeof(struct udphdr); + struct batadv_dhcp_option_header *tl; + struct batadv_dhcp_option_header _tl; u8 *type; u8 _type; - struct { - u8 type; - u8 len; - } *tl, _tl; offset += sizeof(struct batadv_dhcp_packet); @@ -1847,10 +1869,10 @@ void batadv_dat_snoop_incoming_dhcp_ack(struct batadv_priv *bat_priv, { u8 chaddr[BATADV_DHCP_CHADDR_LEN]; struct ethhdr *ethhdr; - __be32 ip_src; - __be32 yiaddr; unsigned short vid; int hdr_size_tmp; + __be32 ip_src; + __be32 yiaddr; __be16 proto; u8 *hw_src; @@ -1899,12 +1921,12 @@ void batadv_dat_snoop_incoming_dhcp_ack(struct batadv_priv *bat_priv, bool batadv_dat_drop_broadcast_packet(struct batadv_priv *bat_priv, struct batadv_forw_packet *forw_packet) { - u16 type; - __be32 ip_dst; - struct batadv_dat_entry *dat_entry = NULL; - bool ret = false; int hdr_size = sizeof(struct batadv_bcast_packet); + struct batadv_dat_entry *dat_entry = NULL; unsigned short vid; + bool ret = false; + __be32 ip_dst; + u16 type; if (!READ_ONCE(bat_priv->distributed_arp_table)) goto out; diff --git a/net/batman-adv/fragmentation.c b/net/batman-adv/fragmentation.c index f382af8588b5..493af3091aa8 100644 --- a/net/batman-adv/fragmentation.c +++ b/net/batman-adv/fragmentation.c @@ -139,17 +139,17 @@ static bool batadv_frag_insert_packet(struct batadv_orig_node *orig_node, struct sk_buff *skb, struct hlist_head *chain_out) { - struct batadv_frag_table_entry *chain; - struct batadv_frag_list_entry *frag_entry_new = NULL; - struct batadv_frag_list_entry *frag_entry_curr; struct batadv_frag_list_entry *frag_entry_last = NULL; - struct batadv_frag_packet *frag_packet; - u8 bucket; - u16 seqno; + struct batadv_frag_list_entry *frag_entry_new = NULL; u16 hdr_size = sizeof(struct batadv_frag_packet); + struct batadv_frag_list_entry *frag_entry_curr; + struct batadv_frag_packet *frag_packet; + struct batadv_frag_table_entry *chain; bool overflow = false; bool ret = false; size_t data_len; + u8 bucket; + u16 seqno; /* Linearize packet to avoid linearizing 16 packets in a row when doing * the later merge. Non-linear merge should be added to remove this @@ -260,12 +260,12 @@ static bool batadv_frag_insert_packet(struct batadv_orig_node *orig_node, static struct sk_buff * batadv_frag_merge_packets(struct hlist_head *chain) { - struct batadv_frag_packet *packet; - struct batadv_frag_list_entry *entry; - struct sk_buff *skb_out; - int size; int hdr_size = sizeof(struct batadv_frag_packet); + struct batadv_frag_list_entry *entry; + struct batadv_frag_packet *packet; + struct sk_buff *skb_out; bool dropped = false; + int size; /* Remove first entry, as this is the destination for the rest of the * fragments. @@ -351,8 +351,8 @@ static bool batadv_skb_is_frag(struct sk_buff *skb) bool batadv_frag_skb_buffer(struct sk_buff **skb, struct batadv_orig_node *orig_node_src) { - struct sk_buff *skb_out = NULL; struct hlist_head head = HLIST_HEAD_INIT; + struct sk_buff *skb_out = NULL; bool ret = false; /* Add packet to buffer and table entry if merge is possible. */ @@ -406,8 +406,8 @@ bool batadv_frag_skb_fwd(struct sk_buff *skb, struct batadv_priv *bat_priv = netdev_priv(recv_if->mesh_iface); struct batadv_neigh_node *neigh_node = NULL; struct batadv_frag_packet *packet; - u16 total_size; bool ret = false; + u16 total_size; packet = (struct batadv_frag_packet *)skb->data; @@ -471,10 +471,11 @@ static struct sk_buff *batadv_frag_create(struct net_device *net_dev, { unsigned int ll_reserved = LL_RESERVED_SPACE(net_dev); unsigned int tailroom = net_dev->needed_tailroom; - struct sk_buff *skb_fragment; unsigned int header_size = sizeof(*frag_head); - unsigned int mtu = fragment_size + header_size; + struct sk_buff *skb_fragment; + unsigned int mtu; + mtu = fragment_size + header_size; skb_fragment = dev_alloc_skb(ll_reserved + mtu + tailroom); if (!skb_fragment) goto err; @@ -506,16 +507,18 @@ int batadv_frag_send_packet(struct sk_buff *skb, struct batadv_neigh_node *neigh_node) { struct net_device *net_dev = neigh_node->if_incoming->net_dev; - struct batadv_priv *bat_priv; struct batadv_hard_iface *primary_if = NULL; struct batadv_frag_packet frag_header; - struct sk_buff *skb_fragment; unsigned int mtu = net_dev->mtu; - unsigned int header_size = sizeof(frag_header); unsigned int max_fragment_size; + struct batadv_priv *bat_priv; + struct sk_buff *skb_fragment; unsigned int num_fragments; + unsigned int header_size; int ret; + header_size = sizeof(frag_header); + /* To avoid merge and refragmentation at next-hops we never send * fragments larger than BATADV_FRAG_MAX_FRAG_SIZE */ diff --git a/net/batman-adv/gateway_client.c b/net/batman-adv/gateway_client.c index 8b9a86cc90fb..e6e97c0478cc 100644 --- a/net/batman-adv/gateway_client.c +++ b/net/batman-adv/gateway_client.c @@ -103,8 +103,8 @@ batadv_gw_get_selected_gw_node(struct batadv_priv *bat_priv) struct batadv_orig_node * batadv_gw_get_selected_orig(struct batadv_priv *bat_priv) { - struct batadv_gw_node *gw_node; struct batadv_orig_node *orig_node = NULL; + struct batadv_gw_node *gw_node; gw_node = batadv_gw_get_selected_gw_node(bat_priv); if (!gw_node) @@ -205,10 +205,10 @@ void batadv_gw_check_client_stop(struct batadv_priv *bat_priv) */ void batadv_gw_election(struct batadv_priv *bat_priv) { + struct batadv_neigh_ifinfo *router_ifinfo = NULL; + struct batadv_neigh_node *router = NULL; struct batadv_gw_node *curr_gw = NULL; struct batadv_gw_node *next_gw = NULL; - struct batadv_neigh_node *router = NULL; - struct batadv_neigh_ifinfo *router_ifinfo = NULL; char gw_addr[18] = { '\0' }; if (READ_ONCE(bat_priv->gw.mode) != BATADV_GW_MODE_CLIENT) @@ -378,8 +378,8 @@ static void batadv_gw_node_add(struct batadv_priv *bat_priv, struct batadv_gw_node *batadv_gw_node_get(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node) { - struct batadv_gw_node *gw_node_tmp; struct batadv_gw_node *gw_node = NULL; + struct batadv_gw_node *gw_node_tmp; rcu_read_lock(); hlist_for_each_entry_rcu(gw_node_tmp, &bat_priv->gw.gateway_list, @@ -409,8 +409,8 @@ void batadv_gw_node_update(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node, struct batadv_tvlv_gateway_data *gateway) { - struct batadv_gw_node *gw_node; struct batadv_gw_node *curr_gw = NULL; + struct batadv_gw_node *gw_node; spin_lock_bh(&bat_priv->gw.list_lock); gw_node = batadv_gw_node_get(bat_priv, orig_node); @@ -571,11 +571,11 @@ batadv_gw_dhcp_recipient_get(struct sk_buff *skb, unsigned int *header_len, u8 *chaddr) { enum batadv_dhcp_recipient ret = BATADV_DHCP_NO; - struct ethhdr *ethhdr; - struct iphdr *iphdr; - struct ipv6hdr *ipv6hdr; - struct udphdr *udphdr; struct vlan_ethhdr *vhdr; + struct ipv6hdr *ipv6hdr; + struct ethhdr *ethhdr; + struct udphdr *udphdr; + struct iphdr *iphdr; int chaddr_offset; __be16 proto; u8 *p; @@ -695,17 +695,17 @@ batadv_gw_dhcp_recipient_get(struct sk_buff *skb, unsigned int *header_len, bool batadv_gw_out_of_range(struct batadv_priv *bat_priv, struct sk_buff *skb) { + struct batadv_orig_node *orig_dst_node = NULL; struct batadv_neigh_node *neigh_curr = NULL; struct batadv_neigh_node *neigh_old = NULL; - struct batadv_orig_node *orig_dst_node = NULL; - struct batadv_gw_node *gw_node = NULL; - struct batadv_gw_node *curr_gw = NULL; struct batadv_neigh_ifinfo *curr_ifinfo; struct batadv_neigh_ifinfo *old_ifinfo; - struct ethhdr *ethhdr; + struct batadv_gw_node *gw_node = NULL; + struct batadv_gw_node *curr_gw = NULL; bool out_of_range = false; - u8 curr_tq_avg; + struct ethhdr *ethhdr; unsigned short vid; + u8 curr_tq_avg; vid = batadv_get_vid(skb, 0); ethhdr = (struct ethhdr *)skb->data; diff --git a/net/batman-adv/gateway_common.c b/net/batman-adv/gateway_common.c index b5ebe837bfdd..65d339205090 100644 --- a/net/batman-adv/gateway_common.c +++ b/net/batman-adv/gateway_common.c @@ -60,8 +60,8 @@ static void batadv_gw_tvlv_ogm_handler_v1(struct batadv_priv *bat_priv, u8 flags, void *tvlv_value, u16 tvlv_value_len) { - struct batadv_tvlv_gateway_data gateway; struct batadv_tvlv_gateway_data *gateway_ptr; + struct batadv_tvlv_gateway_data gateway; /* only fetch the tvlv value if the handler wasn't called via the * CIFNOTFND flag and if there is data to fetch diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c index 1950b8809d99..e7ad295504e4 100644 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@ -350,8 +350,8 @@ static bool batadv_is_cfg80211_netdev(struct net_device *net_device) */ static u32 batadv_wifi_flags_evaluate(struct net_device *net_device) { - u32 wifi_flags = 0; struct net_device *real_netdev; + u32 wifi_flags = 0; if (batadv_is_wext_netdev(net_device)) wifi_flags |= BATADV_HARDIF_WIFI_WEXT_DIRECT; @@ -445,8 +445,8 @@ int batadv_hardif_no_broadcast(struct batadv_hard_iface *if_outgoing, u8 *orig_addr, u8 *orig_neigh) { struct batadv_hardif_neigh_node *hardif_neigh; - struct hlist_node *first; int ret = BATADV_HARDIF_BCAST_OK; + struct hlist_node *first; rcu_read_lock(); @@ -731,8 +731,8 @@ void batadv_update_min_mtu(struct net_device *mesh_iface) static void batadv_hardif_activate_interface(struct batadv_hard_iface *hard_iface) { - struct batadv_priv *bat_priv; struct batadv_hard_iface *primary_if = NULL; + struct batadv_priv *bat_priv; if (hard_iface->if_status != BATADV_IF_INACTIVE) goto out; @@ -794,10 +794,10 @@ batadv_hardif_deactivate_interface(struct batadv_hard_iface *hard_iface) int batadv_hardif_enable_interface(struct net_device *net_dev, struct net_device *mesh_iface) { - struct batadv_priv *bat_priv; - __be16 ethertype = htons(ETH_P_BATMAN); int max_header_len = batadv_max_header_len(); + __be16 ethertype = htons(ETH_P_BATMAN); struct batadv_hard_iface *hard_iface; + struct batadv_priv *bat_priv; unsigned int required_mtu; unsigned int hardif_mtu; bool fragmentation; @@ -1106,8 +1106,8 @@ static int batadv_hard_if_event(struct notifier_block *this, unsigned long event, void *ptr) { struct net_device *net_dev = netdev_notifier_info_to_dev(ptr); - struct batadv_hard_iface *hard_iface; struct batadv_hard_iface *primary_if = NULL; + struct batadv_hard_iface *hard_iface; struct batadv_priv *bat_priv; if (batadv_meshif_is_valid(net_dev)) diff --git a/net/batman-adv/hash.h b/net/batman-adv/hash.h index 89cbb56cdbe1..e67afe01304c 100644 --- a/net/batman-adv/hash.h +++ b/net/batman-adv/hash.h @@ -92,11 +92,11 @@ static inline int batadv_hash_add(struct batadv_hashtable *hash, const void *data, struct hlist_node *data_node) { - u32 index; - int ret = -1; + spinlock_t *list_lock; /* spinlock to protect write access */ struct hlist_head *head; struct hlist_node *node; - spinlock_t *list_lock; /* spinlock to protect write access */ + int ret = -1; + u32 index; if (!hash) goto out; @@ -145,10 +145,10 @@ static inline void *batadv_hash_remove(struct batadv_hashtable *hash, batadv_hashdata_choose_cb choose, void *data) { - u32 index; struct hlist_node *node; struct hlist_head *head; void *data_save = NULL; + u32 index; index = choose(data, hash->size); head = &hash->table[index]; diff --git a/net/batman-adv/log.h b/net/batman-adv/log.h index a0d2b0d64b2b..9f3aed826dea 100644 --- a/net/batman-adv/log.h +++ b/net/batman-adv/log.h @@ -117,8 +117,12 @@ static inline void _batadv_dbg(int type __always_unused, */ #define batadv_info(net_dev, fmt, arg...) \ do { \ - struct net_device *_netdev = (net_dev); \ - struct batadv_priv *_batpriv = netdev_priv(_netdev); \ + struct batadv_priv *_batpriv; \ + struct net_device *_netdev; \ + \ + _netdev = (net_dev); \ + _batpriv = netdev_priv(_netdev); \ + \ batadv_dbg(BATADV_DBG_ALL, _batpriv, fmt, ## arg); \ pr_info("%s: " fmt, _netdev->name, ## arg); \ } while (0) @@ -131,8 +135,12 @@ static inline void _batadv_dbg(int type __always_unused, */ #define batadv_err(net_dev, fmt, arg...) \ do { \ - struct net_device *_netdev = (net_dev); \ - struct batadv_priv *_batpriv = netdev_priv(_netdev); \ + struct batadv_priv *_batpriv; \ + struct net_device *_netdev; \ + \ + _netdev = (net_dev); \ + _batpriv = netdev_priv(_netdev); \ + \ batadv_dbg(BATADV_DBG_ALL, _batpriv, fmt, ## arg); \ pr_err("%s: " fmt, _netdev->name, ## arg); \ } while (0) diff --git a/net/batman-adv/main.c b/net/batman-adv/main.c index 5aa9f26dc6e2..c2d5b39b02f4 100644 --- a/net/batman-adv/main.c +++ b/net/batman-adv/main.c @@ -368,14 +368,14 @@ int batadv_max_header_len(void) */ void batadv_skb_set_priority(struct sk_buff *skb, int offset) { - struct iphdr ip_hdr_tmp; - struct iphdr *ip_hdr; - struct ipv6hdr ip6_hdr_tmp; - struct ipv6hdr *ip6_hdr; - struct ethhdr ethhdr_tmp; - struct ethhdr *ethhdr; - struct vlan_ethhdr *vhdr; struct vlan_ethhdr vhdr_tmp; + struct ipv6hdr ip6_hdr_tmp; + struct ethhdr ethhdr_tmp; + struct vlan_ethhdr *vhdr; + struct iphdr ip_hdr_tmp; + struct ipv6hdr *ip6_hdr; + struct ethhdr *ethhdr; + struct iphdr *ip_hdr; u32 prio; /* already set, do nothing */ @@ -448,9 +448,9 @@ int batadv_batman_skb_recv(struct sk_buff *skb, struct net_device *dev, struct packet_type *ptype, struct net_device *orig_dev) { - struct batadv_priv *bat_priv; struct batadv_ogm_packet *batadv_ogm_packet; struct batadv_hard_iface *hard_iface; + struct batadv_priv *bat_priv; u8 idx; hard_iface = container_of(ptype, struct batadv_hard_iface, @@ -687,9 +687,9 @@ bool batadv_vlan_ap_isola_get(struct batadv_priv *bat_priv, unsigned short vid) int batadv_throw_uevent(struct batadv_priv *bat_priv, enum batadv_uev_type type, enum batadv_uev_action action, const char *data) { - int ret = -ENOMEM; - struct kobject *bat_kobj; char *uevent_env[4] = { NULL, NULL, NULL, NULL }; + struct kobject *bat_kobj; + int ret = -ENOMEM; bat_kobj = &bat_priv->mesh_iface->dev.kobj; diff --git a/net/batman-adv/mesh-interface.c b/net/batman-adv/mesh-interface.c index ab947b9726c1..8e55b61dd2a6 100644 --- a/net/batman-adv/mesh-interface.c +++ b/net/batman-adv/mesh-interface.c @@ -213,31 +213,29 @@ static void batadv_interface_set_rx_mode(struct net_device *dev) static netdev_tx_t batadv_interface_tx(struct sk_buff *skb, struct net_device *mesh_iface) { - struct ethhdr *ethhdr; + static const u8 ectp_addr[ETH_ALEN] = {0xCF, 0x00, 0x00, 0x00, 0x00, 0x00}; + static const u8 stp_addr[ETH_ALEN] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x00}; struct batadv_priv *bat_priv = netdev_priv(mesh_iface); + enum batadv_dhcp_recipient dhcp_rcp = BATADV_DHCP_NO; + enum batadv_forw_mode forw_mode = BATADV_FORW_BCAST; struct batadv_hard_iface *primary_if = NULL; struct batadv_bcast_packet *bcast_packet; - static const u8 stp_addr[ETH_ALEN] = {0x01, 0x80, 0xC2, 0x00, - 0x00, 0x00}; - static const u8 ectp_addr[ETH_ALEN] = {0xCF, 0x00, 0x00, 0x00, - 0x00, 0x00}; - enum batadv_dhcp_recipient dhcp_rcp = BATADV_DHCP_NO; + int network_offset = ETH_HLEN; + unsigned int header_len = 0; + unsigned long brd_delay = 0; + int mcast_is_routable = 0; + struct vlan_ethhdr *vhdr; + int data_len = skb->len; + struct ethhdr *ethhdr; + bool do_bcast = false; u8 *dst_hint = NULL; u8 chaddr[ETH_ALEN]; - struct vlan_ethhdr *vhdr; - unsigned int header_len = 0; - int data_len = skb->len; - int ret; - unsigned long brd_delay = 0; - bool do_bcast = false; - bool client_added; unsigned short vid; - u32 seqno; - int gw_mode; - enum batadv_forw_mode forw_mode = BATADV_FORW_BCAST; - int mcast_is_routable = 0; - int network_offset = ETH_HLEN; + bool client_added; __be16 proto; + int gw_mode; + u32 seqno; + int ret; if (READ_ONCE(bat_priv->mesh_state) != BATADV_MESH_ACTIVE) goto dropped; @@ -455,8 +453,8 @@ void batadv_interface_rx(struct net_device *mesh_iface, struct sk_buff *skb, int hdr_size, struct batadv_orig_node *orig_node) { - struct batadv_bcast_packet *batadv_bcast_packet; struct batadv_priv *bat_priv = netdev_priv(mesh_iface); + struct batadv_bcast_packet *batadv_bcast_packet; struct vlan_ethhdr *vhdr; struct ethhdr *ethhdr; unsigned short vid; @@ -570,8 +568,8 @@ void batadv_meshif_vlan_release(struct kref *ref) struct batadv_meshif_vlan *batadv_meshif_vlan_get(struct batadv_priv *bat_priv, unsigned short vid) { - struct batadv_meshif_vlan *vlan_tmp; struct batadv_meshif_vlan *vlan = NULL; + struct batadv_meshif_vlan *vlan_tmp; rcu_read_lock(); hlist_for_each_entry_rcu(vlan_tmp, &bat_priv->meshif_vlan_list, list) { @@ -789,10 +787,10 @@ static void batadv_set_lockdep_class(struct net_device *dev) */ static int batadv_meshif_init_late(struct net_device *dev) { + size_t cnt_len = sizeof(u64) * BATADV_CNT_NUM; struct batadv_priv *bat_priv; u32 random_seqno; int ret; - size_t cnt_len = sizeof(u64) * BATADV_CNT_NUM; batadv_set_lockdep_class(dev); diff --git a/net/batman-adv/multicast.c b/net/batman-adv/multicast.c index 549c8ed0a557..82fd527b61c0 100644 --- a/net/batman-adv/multicast.c +++ b/net/batman-adv/multicast.c @@ -274,9 +274,9 @@ static struct batadv_mcast_mla_flags batadv_mcast_mla_flags_get(struct batadv_priv *bat_priv) { struct net_device *dev = bat_priv->mesh_iface; + struct batadv_mcast_mla_flags mla_flags; struct batadv_mcast_querier_state *qr4; struct batadv_mcast_querier_state *qr6; - struct batadv_mcast_mla_flags mla_flags; struct net_device *bridge; bridge = batadv_mcast_get_bridge(dev); @@ -522,8 +522,8 @@ batadv_mcast_mla_meshif_get(struct net_device *dev, struct batadv_mcast_mla_flags *flags) { struct net_device *bridge = batadv_mcast_get_bridge(dev); - int ret4; int ret6 = 0; + int ret4; if (bridge) dev = bridge; @@ -587,11 +587,11 @@ static int batadv_mcast_mla_bridge_get(struct net_device *dev, struct batadv_mcast_mla_flags *flags) { struct list_head bridge_mcast_list = LIST_HEAD_INIT(bridge_mcast_list); - struct br_ip_list *br_ip_entry; - struct br_ip_list *tmp; u8 tvlv_flags = flags->tvlv_flags; + struct br_ip_list *br_ip_entry; struct batadv_hw_addr *new; u8 mcast_addr[ETH_ALEN]; + struct br_ip_list *tmp; int ret; /* we don't need to detect these devices/listeners, the IGMP/MLD @@ -938,8 +938,8 @@ static void __batadv_mcast_mla_update(struct batadv_priv *bat_priv) */ static void batadv_mcast_mla_update(struct work_struct *work) { - struct delayed_work *delayed_work; struct batadv_priv_mcast *priv_mcast; + struct delayed_work *delayed_work; struct batadv_priv *bat_priv; delayed_work = to_delayed_work(work); @@ -1232,14 +1232,14 @@ enum batadv_forw_mode batadv_mcast_forw_mode(struct batadv_priv *bat_priv, struct sk_buff *skb, unsigned short vid, int *is_routable) { - int ret; - int tt_count; - int ip_count; - int unsnoop_count; - int total_count; bool is_unsnoopable = false; struct ethhdr *ethhdr; + int unsnoop_count; int rtr_count = 0; + int total_count; + int tt_count; + int ip_count; + int ret; ret = batadv_mcast_forw_mode_check(bat_priv, skb, &is_unsnoopable, is_routable); @@ -1314,13 +1314,11 @@ static int batadv_mcast_forw_tt(struct batadv_priv *bat_priv, struct sk_buff *skb, unsigned short vid) { - int ret = NET_XMIT_SUCCESS; - struct sk_buff *newskb; - struct batadv_tt_orig_list_entry *orig_entry; - struct batadv_tt_global_entry *tt_global; const u8 *addr = eth_hdr(skb)->h_dest; + int ret = NET_XMIT_SUCCESS; + struct sk_buff *newskb; tt_global = batadv_tt_global_hash_find(bat_priv, addr, vid); if (!tt_global) @@ -1615,8 +1613,8 @@ static void batadv_mcast_want_unsnoop_update(struct batadv_priv *bat_priv, struct batadv_orig_node *orig, u8 mcast_flags) { - struct hlist_node *node = &orig->mcast_want_all_unsnoopables_node; struct hlist_head *head = &bat_priv->mcast.want_all_unsnoopables_list; + struct hlist_node *node = &orig->mcast_want_all_unsnoopables_node; lockdep_assert_held(&orig->mcast_handler_lock); @@ -1660,8 +1658,8 @@ static void batadv_mcast_want_ipv4_update(struct batadv_priv *bat_priv, struct batadv_orig_node *orig, u8 mcast_flags) { - struct hlist_node *node = &orig->mcast_want_all_ipv4_node; struct hlist_head *head = &bat_priv->mcast.want_all_ipv4_list; + struct hlist_node *node = &orig->mcast_want_all_ipv4_node; lockdep_assert_held(&orig->mcast_handler_lock); @@ -1705,8 +1703,8 @@ static void batadv_mcast_want_ipv6_update(struct batadv_priv *bat_priv, struct batadv_orig_node *orig, u8 mcast_flags) { - struct hlist_node *node = &orig->mcast_want_all_ipv6_node; struct hlist_head *head = &bat_priv->mcast.want_all_ipv6_list; + struct hlist_node *node = &orig->mcast_want_all_ipv6_node; lockdep_assert_held(&orig->mcast_handler_lock); @@ -1750,8 +1748,8 @@ static void batadv_mcast_want_rtr4_update(struct batadv_priv *bat_priv, struct batadv_orig_node *orig, u8 mcast_flags) { - struct hlist_node *node = &orig->mcast_want_all_rtr4_node; struct hlist_head *head = &bat_priv->mcast.want_all_rtr4_list; + struct hlist_node *node = &orig->mcast_want_all_rtr4_node; lockdep_assert_held(&orig->mcast_handler_lock); @@ -1795,8 +1793,8 @@ static void batadv_mcast_want_rtr6_update(struct batadv_priv *bat_priv, struct batadv_orig_node *orig, u8 mcast_flags) { - struct hlist_node *node = &orig->mcast_want_all_rtr6_node; struct hlist_head *head = &bat_priv->mcast.want_all_rtr6_list; + struct hlist_node *node = &orig->mcast_want_all_rtr6_node; lockdep_assert_held(&orig->mcast_handler_lock); diff --git a/net/batman-adv/multicast_forw.c b/net/batman-adv/multicast_forw.c index 4dcaaadc81b9..bae2a8110976 100644 --- a/net/batman-adv/multicast_forw.c +++ b/net/batman-adv/multicast_forw.c @@ -195,8 +195,8 @@ static int batadv_mcast_forw_push_dests_list(struct batadv_priv *bat_priv, unsigned short *num_dests, unsigned short *tvlv_len) { - struct hlist_node *node; struct batadv_orig_node *orig_node; + struct hlist_node *node; rcu_read_lock(); __hlist_for_each_rcu(node, head) { @@ -232,12 +232,9 @@ batadv_mcast_forw_push_tt(struct batadv_priv *bat_priv, struct sk_buff *skb, unsigned short *tvlv_len) { struct batadv_tt_orig_list_entry *orig_entry; - struct batadv_tt_global_entry *tt_global; const u8 *addr = eth_hdr(skb)->h_dest; - - /* ok */ - int ret = true; + int ret = true; /* ok */ tt_global = batadv_tt_global_hash_find(bat_priv, addr, vid); if (!tt_global) @@ -368,8 +365,8 @@ static void batadv_mcast_forw_scrape(struct sk_buff *skb, unsigned short offset, unsigned short len) { - char *to; char *from; + char *to; SKB_LINEAR_ASSERT(skb); @@ -412,8 +409,8 @@ static bool batadv_mcast_forw_push_insert_padding(struct sk_buff *skb, unsigned short *tvlv_len) { unsigned short offset = *tvlv_len; - char *to; char *from = skb->data; + char *to; to = batadv_mcast_forw_push_padding(skb, tvlv_len); if (!to) @@ -935,9 +932,9 @@ static int batadv_mcast_forw_packet(struct batadv_priv *bat_priv, unsigned int tvlv_len; unsigned long offset; bool xmitted = false; - u8 *dest; u8 *next_dest; u16 num_dests; + u8 *dest; int ret; /* (at least) TVLV part needs to be linearized */ diff --git a/net/batman-adv/netlink.c b/net/batman-adv/netlink.c index f4aa7d205510..926210a67d64 100644 --- a/net/batman-adv/netlink.c +++ b/net/batman-adv/netlink.c @@ -752,8 +752,8 @@ static int batadv_netlink_tp_meter_cancel(struct sk_buff *skb, struct genl_info *info) { struct batadv_priv *bat_priv = info->user_ptr[0]; - u8 *dst; int ret = 0; + u8 *dst; if (!info->attrs[BATADV_ATTR_ORIG_ADDRESS]) return -EINVAL; @@ -959,10 +959,10 @@ static int batadv_netlink_set_hardif(struct sk_buff *skb, static int batadv_netlink_dump_hardif(struct sk_buff *msg, struct netlink_callback *cb) { - struct net_device *mesh_iface; - struct batadv_hard_iface *hard_iface; - struct batadv_priv *bat_priv; int portid = NETLINK_CB(cb->skb).portid; + struct batadv_hard_iface *hard_iface; + struct net_device *mesh_iface; + struct batadv_priv *bat_priv; int skip = cb->args[0]; struct list_head *iter; int i = 0; diff --git a/net/batman-adv/originator.c b/net/batman-adv/originator.c index 57bb4a0131b0..f06583ef9b3f 100644 --- a/net/batman-adv/originator.c +++ b/net/batman-adv/originator.c @@ -53,9 +53,9 @@ struct batadv_orig_node * batadv_orig_hash_find(struct batadv_priv *bat_priv, const void *data) { struct batadv_hashtable *hash = bat_priv->orig_hash; - struct hlist_head *head; - struct batadv_orig_node *orig_node; struct batadv_orig_node *orig_node_tmp = NULL; + struct batadv_orig_node *orig_node; + struct hlist_head *head; int index; if (!hash) @@ -284,9 +284,9 @@ void batadv_hardif_neigh_release(struct kref *ref) */ void batadv_neigh_node_release(struct kref *ref) { - struct hlist_node *node_tmp; - struct batadv_neigh_node *neigh_node; struct batadv_neigh_ifinfo *neigh_ifinfo; + struct batadv_neigh_node *neigh_node; + struct hlist_node *node_tmp; neigh_node = container_of(ref, struct batadv_neigh_node, refcount); @@ -316,8 +316,8 @@ struct batadv_neigh_node * batadv_orig_router_get(struct batadv_orig_node *orig_node, const struct batadv_hard_iface *if_outgoing) { - struct batadv_orig_ifinfo *orig_ifinfo; struct batadv_neigh_node *router = NULL; + struct batadv_orig_ifinfo *orig_ifinfo; rcu_read_lock(); hlist_for_each_entry_rcu(orig_ifinfo, &orig_node->ifinfo_list, list) { @@ -375,8 +375,8 @@ struct batadv_orig_ifinfo * batadv_orig_ifinfo_get(struct batadv_orig_node *orig_node, struct batadv_hard_iface *if_outgoing) { - struct batadv_orig_ifinfo *tmp; struct batadv_orig_ifinfo *orig_ifinfo = NULL; + struct batadv_orig_ifinfo *tmp; rcu_read_lock(); hlist_for_each_entry_rcu(tmp, &orig_node->ifinfo_list, @@ -638,8 +638,8 @@ struct batadv_hardif_neigh_node * batadv_hardif_neigh_get(const struct batadv_hard_iface *hard_iface, const u8 *neigh_addr) { - struct batadv_hardif_neigh_node *tmp_hardif_neigh; struct batadv_hardif_neigh_node *hardif_neigh = NULL; + struct batadv_hardif_neigh_node *tmp_hardif_neigh; rcu_read_lock(); hlist_for_each_entry_rcu(tmp_hardif_neigh, @@ -673,8 +673,8 @@ batadv_neigh_node_create(struct batadv_orig_node *orig_node, struct batadv_hard_iface *hard_iface, const u8 *neigh_addr) { - struct batadv_neigh_node *neigh_node; struct batadv_hardif_neigh_node *hardif_neigh = NULL; + struct batadv_neigh_node *neigh_node; spin_lock_bh(&orig_node->neigh_list_lock); @@ -856,12 +856,12 @@ static void batadv_orig_node_free_rcu(struct rcu_head *rcu) */ void batadv_orig_node_release(struct kref *ref) { - struct hlist_node *node_tmp; + struct batadv_orig_ifinfo *last_candidate; + struct batadv_orig_ifinfo *orig_ifinfo; struct batadv_neigh_node *neigh_node; struct batadv_orig_node *orig_node; - struct batadv_orig_ifinfo *orig_ifinfo; struct batadv_orig_node_vlan *vlan; - struct batadv_orig_ifinfo *last_candidate; + struct hlist_node *node_tmp; orig_node = container_of(ref, struct batadv_orig_node, refcount); @@ -904,11 +904,11 @@ void batadv_orig_node_release(struct kref *ref) */ void batadv_originator_free(struct batadv_priv *bat_priv) { + spinlock_t *list_lock; /* spinlock to protect write access */ struct batadv_hashtable *hash = bat_priv->orig_hash; + struct batadv_orig_node *orig_node; struct hlist_node *node_tmp; struct hlist_head *head; - spinlock_t *list_lock; /* spinlock to protect write access */ - struct batadv_orig_node *orig_node; u32 i; if (!hash) @@ -1115,11 +1115,11 @@ static bool batadv_purge_orig_neighbors(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node) { - struct hlist_node *node_tmp; + struct batadv_hard_iface *if_incoming; struct batadv_neigh_node *neigh_node; + struct hlist_node *node_tmp; bool neigh_purged = false; unsigned long last_seen; - struct batadv_hard_iface *if_incoming; spin_lock_bh(&orig_node->neigh_list_lock); @@ -1173,9 +1173,9 @@ batadv_find_best_neighbor(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node, struct batadv_hard_iface *if_outgoing) { + struct batadv_algo_ops *bao = bat_priv->algo_ops; struct batadv_neigh_node *best = NULL; struct batadv_neigh_node *neigh; - struct batadv_algo_ops *bao = bat_priv->algo_ops; rcu_read_lock(); hlist_for_each_entry_rcu(neigh, &orig_node->neigh_list, list) { @@ -1210,9 +1210,9 @@ static bool batadv_purge_orig_node(struct batadv_priv *bat_priv, { struct batadv_neigh_node *best_neigh_node; struct batadv_hard_iface *hard_iface; + struct list_head *iter; bool changed_ifinfo; bool changed_neigh; - struct list_head *iter; if (batadv_has_timed_out(orig_node->last_seen, 2 * BATADV_PURGE_TIMEOUT)) { @@ -1264,11 +1264,11 @@ static bool batadv_purge_orig_node(struct batadv_priv *bat_priv, */ void batadv_purge_orig_ref(struct batadv_priv *bat_priv) { + spinlock_t *list_lock; /* spinlock to protect write access */ struct batadv_hashtable *hash = bat_priv->orig_hash; + struct batadv_orig_node *orig_node; struct hlist_node *node_tmp; struct hlist_head *head; - spinlock_t *list_lock; /* spinlock to protect write access */ - struct batadv_orig_node *orig_node; u32 i; if (!hash) diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c index 3e4486094b75..af0543a4d346 100644 --- a/net/batman-adv/routing.c +++ b/net/batman-adv/routing.c @@ -276,8 +276,8 @@ static int batadv_recv_my_icmp_packet(struct batadv_priv *bat_priv, struct batadv_hard_iface *primary_if = NULL; struct batadv_orig_node *orig_node = NULL; struct batadv_icmp_header *icmph; - int res; int ret = NET_RX_DROP; + int res; icmph = (struct batadv_icmp_header *)skb->data; @@ -349,8 +349,8 @@ static int batadv_recv_icmp_ttl_exceeded(struct batadv_priv *bat_priv, struct batadv_hard_iface *primary_if = NULL; struct batadv_orig_node *orig_node = NULL; struct batadv_icmp_packet *icmp_packet; - int res; int ret = NET_RX_DROP; + int res; icmp_packet = (struct batadv_icmp_packet *)skb->data; @@ -408,13 +408,13 @@ int batadv_recv_icmp_packet(struct sk_buff *skb, struct batadv_hard_iface *recv_if) { struct batadv_priv *bat_priv = netdev_priv(recv_if->mesh_iface); - struct batadv_icmp_header *icmph; - struct batadv_icmp_packet_rr *icmp_packet_rr; - struct ethhdr *ethhdr; - struct batadv_orig_node *orig_node = NULL; int hdr_size = sizeof(struct batadv_icmp_header); - int res; + struct batadv_icmp_packet_rr *icmp_packet_rr; + struct batadv_orig_node *orig_node = NULL; + struct batadv_icmp_header *icmph; + struct ethhdr *ethhdr; int ret = NET_RX_DROP; + int res; /* drop packet if it has not necessary minimum size */ if (unlikely(!pskb_may_pull(skb, hdr_size))) @@ -593,17 +593,17 @@ batadv_find_router(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node, struct batadv_hard_iface *recv_if) { - struct batadv_algo_ops *bao = bat_priv->algo_ops; struct batadv_neigh_node *first_candidate_router = NULL; struct batadv_neigh_node *next_candidate_router = NULL; - struct batadv_neigh_node *router; - struct batadv_neigh_node *cand_router = NULL; struct batadv_neigh_node *last_cand_router = NULL; - struct batadv_orig_ifinfo *cand; struct batadv_orig_ifinfo *first_candidate = NULL; + struct batadv_algo_ops *bao = bat_priv->algo_ops; struct batadv_orig_ifinfo *next_candidate = NULL; + struct batadv_neigh_node *cand_router = NULL; struct batadv_orig_ifinfo *last_candidate; bool last_candidate_found = false; + struct batadv_neigh_node *router; + struct batadv_orig_ifinfo *cand; if (!orig_node) return NULL; @@ -741,13 +741,13 @@ static int batadv_route_unicast_packet(struct sk_buff *skb, struct batadv_hard_iface *recv_if) { struct batadv_priv *bat_priv = netdev_priv(recv_if->mesh_iface); - struct batadv_orig_node *orig_node = NULL; struct batadv_unicast_packet *unicast_packet; + struct batadv_orig_node *orig_node = NULL; struct ethhdr *ethhdr = eth_hdr(skb); - int res; - int hdr_len; int ret = NET_RX_DROP; unsigned int len; + int hdr_len; + int res; unicast_packet = (struct batadv_unicast_packet *)skb->data; @@ -830,10 +830,10 @@ batadv_reroute_unicast_packet(struct batadv_priv *bat_priv, struct sk_buff *skb, struct batadv_unicast_packet *unicast_packet, u8 *dst_addr, unsigned short vid) { - struct batadv_orig_node *orig_node = NULL; struct batadv_hard_iface *primary_if = NULL; - bool ret = false; + struct batadv_orig_node *orig_node = NULL; const u8 *orig_addr; + bool ret = false; u8 orig_ttvn; if (batadv_is_my_client(bat_priv, dst_addr, vid)) { @@ -889,11 +889,11 @@ static bool batadv_check_unicast_ttvn(struct batadv_priv *bat_priv, struct batadv_unicast_packet *unicast_packet; struct batadv_hard_iface *primary_if; struct batadv_orig_node *orig_node; - u8 curr_ttvn; - u8 old_ttvn; struct ethhdr *ethhdr; unsigned short vid; int is_old_ttvn; + u8 curr_ttvn; + u8 old_ttvn; /* check if there is enough data before accessing it */ if (!pskb_may_pull(skb, hdr_len + ETH_HLEN)) @@ -1008,10 +1008,10 @@ static bool batadv_check_unicast_ttvn(struct batadv_priv *bat_priv, int batadv_recv_unhandled_unicast_packet(struct sk_buff *skb, struct batadv_hard_iface *recv_if) { - struct batadv_unicast_packet *unicast_packet; struct batadv_priv *bat_priv = netdev_priv(recv_if->mesh_iface); - int check; + struct batadv_unicast_packet *unicast_packet; int hdr_size = sizeof(*unicast_packet); + int check; check = batadv_check_unicast_packet(bat_priv, skb, hdr_size); if (check < 0) @@ -1040,18 +1040,18 @@ int batadv_recv_unicast_packet(struct sk_buff *skb, struct batadv_hard_iface *recv_if) { struct batadv_priv *bat_priv = netdev_priv(recv_if->mesh_iface); - struct batadv_unicast_packet *unicast_packet; struct batadv_unicast_4addr_packet *unicast_4addr_packet; - u8 *orig_addr; - u8 *orig_addr_gw; - struct batadv_orig_node *orig_node = NULL; + struct batadv_unicast_packet *unicast_packet; struct batadv_orig_node *orig_node_gw = NULL; - int check; + struct batadv_orig_node *orig_node = NULL; int hdr_size = sizeof(*unicast_packet); enum batadv_subtype subtype; int ret = NET_RX_DROP; + u8 *orig_addr_gw; + u8 *orig_addr; bool is4addr; bool is_gw; + int check; unicast_packet = (struct batadv_unicast_packet *)skb->data; is4addr = unicast_packet->packet_type == BATADV_UNICAST_4ADDR; @@ -1148,10 +1148,10 @@ int batadv_recv_unicast_tvlv(struct sk_buff *skb, { struct batadv_priv *bat_priv = netdev_priv(recv_if->mesh_iface); struct batadv_unicast_tvlv_packet *unicast_tvlv_packet; - unsigned char *tvlv_buff; - u16 tvlv_buff_len; int hdr_size = sizeof(*unicast_tvlv_packet); + unsigned char *tvlv_buff; int ret = NET_RX_DROP; + u16 tvlv_buff_len; if (batadv_check_unicast_packet(bat_priv, skb, hdr_size) < 0) goto free_skb; @@ -1266,8 +1266,8 @@ int batadv_recv_bcast_packet(struct sk_buff *skb, struct batadv_priv *bat_priv = netdev_priv(recv_if->mesh_iface); struct batadv_orig_node *orig_node = NULL; struct batadv_bcast_packet *bcast_packet; - struct ethhdr *ethhdr; int hdr_size = sizeof(*bcast_packet); + struct ethhdr *ethhdr; s32 seq_diff; u32 seqno; int ret; diff --git a/net/batman-adv/send.c b/net/batman-adv/send.c index 29f2cbc61285..2122560c90e5 100644 --- a/net/batman-adv/send.c +++ b/net/batman-adv/send.c @@ -271,8 +271,8 @@ bool batadv_send_skb_prepare_unicast_4addr(struct batadv_priv *bat_priv, struct batadv_orig_node *orig, int packet_subtype) { - struct batadv_hard_iface *primary_if; struct batadv_unicast_4addr_packet *uc_4addr_packet; + struct batadv_hard_iface *primary_if; bool ret = false; primary_if = batadv_primary_if_get_selected(bat_priv); @@ -322,8 +322,8 @@ int batadv_send_skb_unicast(struct batadv_priv *bat_priv, unsigned short vid) { struct batadv_unicast_packet *unicast_packet; - struct ethhdr *ethhdr; int ret = NET_XMIT_DROP; + struct ethhdr *ethhdr; if (!orig_node) goto out; diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c index b957a59dcf26..022eb13e8ec4 100644 --- a/net/batman-adv/tp_meter.c +++ b/net/batman-adv/tp_meter.c @@ -221,9 +221,9 @@ static void batadv_tp_batctl_notify(enum batadv_tp_meter_reason reason, unsigned long start_time, u64 total_sent, u32 cookie) { + u32 total_bytes; u32 test_time; u8 result; - u32 total_bytes; if (!batadv_tp_is_error(reason)) { result = BATADV_TP_REASON_COMPLETE; @@ -267,8 +267,8 @@ static void batadv_tp_batctl_error_notify(enum batadv_tp_meter_reason reason, static struct batadv_tp_sender * batadv_tp_list_find_sender(struct batadv_priv *bat_priv, const u8 *dst) { - struct batadv_tp_sender *pos; struct batadv_tp_sender *tp_vars = NULL; + struct batadv_tp_sender *pos; rcu_read_lock(); hlist_for_each_entry_rcu(pos, &bat_priv->tp_sender_list, common.list) { @@ -333,8 +333,8 @@ static struct batadv_tp_sender * batadv_tp_list_find_sender_session(struct batadv_priv *bat_priv, const u8 *dst, const u8 *session) { - struct batadv_tp_sender *pos; struct batadv_tp_sender *tp_vars = NULL; + struct batadv_tp_sender *pos; rcu_read_lock(); hlist_for_each_entry_rcu(pos, &bat_priv->tp_sender_list, common.list) { @@ -580,8 +580,8 @@ static void batadv_tp_sender_timeout(struct timer_list *t) static void batadv_tp_fill_prerandom(struct batadv_tp_sender *tp_vars, u8 *buf, size_t nbytes) { - u32 local_offset; size_t bytes_inbuf; + u32 local_offset; size_t to_copy; size_t pos = 0; @@ -627,9 +627,9 @@ static int batadv_tp_send_msg(struct batadv_tp_sender *tp_vars, const u8 *src, { struct batadv_icmp_tp_packet *icmp; struct sk_buff *skb; - int r; - u8 *data; size_t data_len; + u8 *data; + int r; skb = netdev_alloc_skb_ip_align(NULL, len + ETH_HLEN); if (unlikely(!skb)) @@ -866,8 +866,8 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv, static bool batadv_tp_avail(struct batadv_tp_sender *tp_vars, size_t payload_len) { - u32 win_left; u32 win_limit; + u32 win_left; spin_lock_bh(&tp_vars->cc_lock); @@ -913,15 +913,16 @@ static int batadv_tp_wait_available(struct batadv_tp_sender *tp_vars, size_t ple */ static int batadv_tp_send(void *arg) { - struct batadv_tp_sender *tp_vars = arg; - struct batadv_priv *bat_priv = tp_vars->common.bat_priv; struct batadv_hard_iface *primary_if = NULL; struct batadv_orig_node *orig_node = NULL; + struct batadv_tp_sender *tp_vars = arg; + struct batadv_priv *bat_priv; size_t payload_len; size_t packet_len; u32 last_sent; int err = 0; + bat_priv = tp_vars->common.bat_priv; orig_node = batadv_orig_hash_find(bat_priv, tp_vars->common.other_end); if (unlikely(!orig_node)) { err = BATADV_TP_REASON_DST_UNREACHABLE; @@ -1009,8 +1010,8 @@ static int batadv_tp_send(void *arg) */ static void batadv_tp_start_kthread(struct batadv_tp_sender *tp_vars) { - struct task_struct *kthread; struct batadv_priv *bat_priv = tp_vars->common.bat_priv; + struct task_struct *kthread; u32 session_cookie; kref_get(&tp_vars->common.refcount); @@ -1046,9 +1047,9 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst, u32 test_length, u32 *cookie) { struct batadv_tp_sender *tp_vars; + u32 session_cookie; u8 session_id[2]; u8 icmp_uid; - u32 session_cookie; get_random_bytes(session_id, sizeof(session_id)); get_random_bytes(&icmp_uid, 1); @@ -1215,8 +1216,8 @@ static struct batadv_tp_receiver * batadv_tp_list_find_receiver_session(struct batadv_priv *bat_priv, const u8 *dst, const u8 *session) { - struct batadv_tp_receiver *pos; struct batadv_tp_receiver *tp_vars = NULL; + struct batadv_tp_receiver *pos; rcu_read_lock(); hlist_for_each_entry_rcu(pos, &bat_priv->tp_receiver_list, common.list) { @@ -1301,8 +1302,8 @@ static void batadv_tp_reset_receiver_timer(struct batadv_tp_receiver *tp_vars) static void batadv_tp_receiver_shutdown(struct timer_list *t) { struct batadv_tp_receiver *tp_vars = timer_container_of(tp_vars, t, common.timer); - struct batadv_tp_unacked *un; struct batadv_tp_unacked *safe; + struct batadv_tp_unacked *un; struct batadv_priv *bat_priv; bat_priv = tp_vars->common.bat_priv; @@ -1357,8 +1358,8 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst, struct batadv_orig_node *orig_node; struct batadv_icmp_tp_packet *icmp; struct sk_buff *skb; - int r; int ret; + int r; orig_node = batadv_orig_hash_find(bat_priv, dst); if (unlikely(!orig_node)) { @@ -1535,8 +1536,8 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, static void batadv_tp_ack_unordered(struct batadv_tp_receiver *tp_vars) __must_hold(&tp_vars->ack_seqno_lock) { - struct batadv_tp_unacked *un; struct batadv_tp_unacked *safe; + struct batadv_tp_unacked *un; u32 to_ack; /* go through the unacked packet list and possibly ACK them as diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index 75ec829139a2..68f79853032e 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -128,10 +128,10 @@ static struct batadv_tt_common_entry * batadv_tt_hash_find(struct batadv_hashtable *hash, const u8 *addr, unsigned short vid) { - struct hlist_head *head; + struct batadv_tt_common_entry *tt_tmp = NULL; struct batadv_tt_common_entry to_search; struct batadv_tt_common_entry *tt; - struct batadv_tt_common_entry *tt_tmp = NULL; + struct hlist_head *head; u32 index; if (!hash) @@ -175,8 +175,8 @@ static struct batadv_tt_local_entry * batadv_tt_local_hash_find(struct batadv_priv *bat_priv, const u8 *addr, unsigned short vid) { - struct batadv_tt_common_entry *tt_common_entry; struct batadv_tt_local_entry *tt_local_entry = NULL; + struct batadv_tt_common_entry *tt_common_entry; tt_common_entry = batadv_tt_hash_find(bat_priv->tt.local_hash, addr, vid); @@ -200,8 +200,8 @@ struct batadv_tt_global_entry * batadv_tt_global_hash_find(struct batadv_priv *bat_priv, const u8 *addr, unsigned short vid) { - struct batadv_tt_common_entry *tt_common_entry; struct batadv_tt_global_entry *tt_global_entry = NULL; + struct batadv_tt_common_entry *tt_common_entry; tt_common_entry = batadv_tt_hash_find(bat_priv->tt.global_hash, addr, vid); @@ -423,11 +423,11 @@ static void batadv_tt_local_event(struct batadv_priv *bat_priv, struct batadv_tt_local_entry *tt_local_entry, u8 event_flags) { + struct batadv_tt_common_entry *common = &tt_local_entry->common; struct batadv_tt_change_node *tt_change_node; + u8 flags = common->flags | event_flags; struct batadv_tt_change_node *entry; struct batadv_tt_change_node *safe; - struct batadv_tt_common_entry *common = &tt_local_entry->common; - u8 flags = common->flags | event_flags; bool del_op_requested; bool del_op_entry; size_t changes; @@ -519,9 +519,9 @@ static u16 batadv_tt_entries(u16 tt_len) */ static int batadv_tt_local_table_transmit_size(struct batadv_priv *bat_priv) { - u16 num_vlan = 0; - u16 tt_local_entries = 0; struct batadv_meshif_vlan *vlan; + u16 tt_local_entries = 0; + u16 num_vlan = 0; int hdr_size; rcu_read_lock(); @@ -614,20 +614,20 @@ bool batadv_tt_local_add(struct net_device *mesh_iface, const u8 *addr, unsigned short vid, int ifindex, u32 mark) { struct batadv_priv *bat_priv = netdev_priv(mesh_iface); - struct batadv_tt_local_entry *tt_local; struct batadv_tt_global_entry *tt_global = NULL; - struct net *net = dev_net(mesh_iface); - struct batadv_meshif_vlan *vlan; - struct net_device *in_dev = NULL; - struct hlist_head *head; struct batadv_tt_orig_list_entry *orig_entry; - int hash_added; - int table_size; - int packet_size_max; - bool ret = false; + struct batadv_tt_local_entry *tt_local; + struct net *net = dev_net(mesh_iface); + struct net_device *in_dev = NULL; + struct batadv_meshif_vlan *vlan; bool roamed_back = false; bool iif_is_wifi = false; + struct hlist_head *head; + int packet_size_max; + bool ret = false; u8 remote_flags; + int hash_added; + int table_size; u32 match_mark; if (ifindex != BATADV_NULL_IFINDEX) @@ -1009,16 +1009,16 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv, */ static void batadv_tt_tvlv_container_update(struct batadv_priv *bat_priv) { - struct batadv_tt_change_node *entry; - struct batadv_tt_change_node *safe; - struct batadv_tvlv_tt_data *tt_data; struct batadv_tvlv_tt_change *tt_change; - int tt_diff_len; - int tt_change_len = 0; - int tt_diff_entries_num = 0; + struct batadv_tt_change_node *entry; + struct batadv_tvlv_tt_data *tt_data; + struct batadv_tt_change_node *safe; int tt_diff_entries_count = 0; + int tt_diff_entries_num = 0; bool drop_changes = false; size_t tt_extra_len = 0; + int tt_change_len = 0; + int tt_diff_len; u16 tvlv_len; tt_diff_entries_num = READ_ONCE(bat_priv->tt.local_changes); @@ -1111,10 +1111,10 @@ batadv_tt_local_dump_entry(struct sk_buff *msg, u32 portid, struct batadv_priv *bat_priv, struct batadv_tt_common_entry *common) { - void *hdr; - struct batadv_meshif_vlan *vlan; struct batadv_tt_local_entry *local; + struct batadv_meshif_vlan *vlan; unsigned int last_seen_msecs; + void *hdr; u32 crc; local = container_of(common, struct batadv_tt_local_entry, common); @@ -1205,14 +1205,14 @@ batadv_tt_local_dump_bucket(struct sk_buff *msg, u32 portid, */ int batadv_tt_local_dump(struct sk_buff *msg, struct netlink_callback *cb) { - struct net_device *mesh_iface; - struct batadv_priv *bat_priv; struct batadv_hard_iface *primary_if = NULL; + int portid = NETLINK_CB(cb->skb).portid; + struct net_device *mesh_iface; struct batadv_hashtable *hash; - int ret; + struct batadv_priv *bat_priv; int bucket = cb->args[0]; int idx = cb->args[1]; - int portid = NETLINK_CB(cb->skb).portid; + int ret; mesh_iface = batadv_netlink_get_meshif(cb); if (IS_ERR(mesh_iface)) @@ -1294,9 +1294,9 @@ u16 batadv_tt_local_remove(struct batadv_priv *bat_priv, const u8 *addr, { struct batadv_tt_local_entry *tt_removed_entry; struct batadv_tt_local_entry *tt_local_entry; - u16 flags; - u16 curr_flags = BATADV_NO_FLAGS; struct hlist_node *tt_removed_node; + u16 curr_flags = BATADV_NO_FLAGS; + u16 flags; tt_local_entry = batadv_tt_local_hash_find(bat_priv, addr, vid); if (!tt_local_entry) @@ -1355,8 +1355,8 @@ static void batadv_tt_local_purge_list(struct batadv_priv *bat_priv, struct hlist_head *head, int timeout) { - struct batadv_tt_local_entry *tt_local_entry; struct batadv_tt_common_entry *tt_common_entry; + struct batadv_tt_local_entry *tt_local_entry; struct hlist_node *node_tmp; hlist_for_each_entry_safe(tt_common_entry, node_tmp, head, @@ -1388,9 +1388,9 @@ static void batadv_tt_local_purge_list(struct batadv_priv *bat_priv, static void batadv_tt_local_purge(struct batadv_priv *bat_priv, int timeout) { + spinlock_t *list_lock; /* protects write access to the hash lists */ struct batadv_hashtable *hash = bat_priv->tt.local_hash; struct hlist_head *head; - spinlock_t *list_lock; /* protects write access to the hash lists */ u32 i; for (i = 0; i < hash->size; i++) { @@ -1412,10 +1412,10 @@ static void batadv_tt_local_purge(struct batadv_priv *bat_priv, */ static void batadv_tt_local_table_free(struct batadv_priv *bat_priv) { - struct batadv_hashtable *hash; spinlock_t *list_lock; /* protects write access to the hash lists */ struct batadv_tt_common_entry *tt_common_entry; struct batadv_tt_local_entry *tt_local; + struct batadv_hashtable *hash; struct hlist_node *node_tmp; struct hlist_head *head; u32 i; @@ -1508,8 +1508,8 @@ static struct batadv_tt_orig_list_entry * batadv_tt_global_orig_entry_find(const struct batadv_tt_global_entry *entry, const struct batadv_orig_node *orig_node) { - struct batadv_tt_orig_list_entry *tmp_orig_entry; struct batadv_tt_orig_list_entry *orig_entry = NULL; + struct batadv_tt_orig_list_entry *tmp_orig_entry; const struct hlist_head *head; rcu_read_lock(); @@ -1662,10 +1662,10 @@ static bool batadv_tt_global_add(struct batadv_priv *bat_priv, { struct batadv_tt_global_entry *tt_global_entry; struct batadv_tt_local_entry *tt_local_entry; - bool ret = false; - int hash_added; struct batadv_tt_common_entry *common; + bool ret = false; u16 local_flags; + int hash_added; /* ignore global entries from backbone nodes */ if (batadv_bla_is_backbone_gw_orig(bat_priv, orig_node->orig, vid)) @@ -1821,12 +1821,12 @@ static struct batadv_tt_orig_list_entry * batadv_transtable_best_orig(struct batadv_priv *bat_priv, struct batadv_tt_global_entry *tt_global_entry) { - struct batadv_neigh_node *router; - struct batadv_neigh_node *best_router = NULL; - struct batadv_algo_ops *bao = bat_priv->algo_ops; - struct hlist_head *head; - struct batadv_tt_orig_list_entry *orig_entry; struct batadv_tt_orig_list_entry *best_entry = NULL; + struct batadv_algo_ops *bao = bat_priv->algo_ops; + struct batadv_neigh_node *best_router = NULL; + struct batadv_tt_orig_list_entry *orig_entry; + struct batadv_neigh_node *router; + struct hlist_head *head; head = &tt_global_entry->orig_list; hlist_for_each_entry_rcu(orig_entry, head, list) { @@ -1872,9 +1872,9 @@ batadv_tt_global_dump_subentry(struct sk_buff *msg, u32 portid, u32 seq, bool best) { u16 flags = (common->flags & (~BATADV_TT_SYNC_MASK)) | orig->flags; - void *hdr; struct batadv_orig_node_vlan *vlan; u8 last_ttvn; + void *hdr; u32 crc; vlan = batadv_orig_node_vlan_get(orig->orig_node, @@ -2009,16 +2009,16 @@ batadv_tt_global_dump_bucket(struct sk_buff *msg, u32 portid, u32 seq, */ int batadv_tt_global_dump(struct sk_buff *msg, struct netlink_callback *cb) { - struct net_device *mesh_iface; - struct batadv_priv *bat_priv; struct batadv_hard_iface *primary_if = NULL; + int portid = NETLINK_CB(cb->skb).portid; + struct net_device *mesh_iface; struct batadv_hashtable *hash; - struct hlist_head *head; - int ret; + struct batadv_priv *bat_priv; int bucket = cb->args[0]; + struct hlist_head *head; int idx = cb->args[1]; int sub = cb->args[2]; - int portid = NETLINK_CB(cb->skb).portid; + int ret; mesh_iface = batadv_netlink_get_meshif(cb); if (IS_ERR(mesh_iface)) @@ -2093,9 +2093,9 @@ _batadv_tt_global_del_orig_entry(struct batadv_tt_global_entry *tt_global_entry, static void batadv_tt_global_del_orig_list(struct batadv_tt_global_entry *tt_global_entry) { + struct batadv_tt_orig_list_entry *orig_entry; struct hlist_head *head; struct hlist_node *safe; - struct batadv_tt_orig_list_entry *orig_entry; spin_lock_bh(&tt_global_entry->list_lock); head = &tt_global_entry->orig_list; @@ -2120,9 +2120,9 @@ batadv_tt_global_del_orig_node(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node, const char *message) { + struct batadv_tt_orig_list_entry *orig_entry; struct hlist_head *head; struct hlist_node *safe; - struct batadv_tt_orig_list_entry *orig_entry; unsigned short vid; spin_lock_bh(&tt_global_entry->list_lock); @@ -2160,9 +2160,9 @@ batadv_tt_global_del_roaming(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node, const char *message) { - bool last_entry = true; - struct hlist_head *head; struct batadv_tt_orig_list_entry *orig_entry; + struct hlist_head *head; + bool last_entry = true; /* no local entry exists, case 1: * Check if this is the last one or if other entries exist. @@ -2206,8 +2206,8 @@ static void batadv_tt_global_del(struct batadv_priv *bat_priv, const unsigned char *addr, unsigned short vid, const char *message, bool roaming) { - struct batadv_tt_global_entry *tt_global_entry; struct batadv_tt_local_entry *local_entry = NULL; + struct batadv_tt_global_entry *tt_global_entry; tt_global_entry = batadv_tt_global_hash_find(bat_priv, addr, vid); if (!tt_global_entry) @@ -2269,14 +2269,14 @@ void batadv_tt_global_del_orig(struct batadv_priv *bat_priv, s32 match_vid, const char *message) { - struct batadv_tt_global_entry *tt_global; - struct batadv_tt_common_entry *tt_common_entry; - u32 i; + spinlock_t *list_lock; /* protects write access to the hash lists */ struct batadv_hashtable *hash = bat_priv->tt.global_hash; + struct batadv_tt_common_entry *tt_common_entry; + struct batadv_tt_global_entry *tt_global; struct hlist_node *safe; struct hlist_head *head; - spinlock_t *list_lock; /* protects write access to the hash lists */ unsigned short vid; + u32 i; if (!hash) return; @@ -2326,9 +2326,9 @@ void batadv_tt_global_del_orig(struct batadv_priv *bat_priv, static bool batadv_tt_global_to_purge(struct batadv_tt_global_entry *tt_global, char **msg) { - bool purge = false; unsigned long roam_timeout = BATADV_TT_CLIENT_ROAM_TIMEOUT; unsigned long temp_timeout = BATADV_TT_CLIENT_TEMP_TIMEOUT; + bool purge = false; if ((tt_global->common.flags & BATADV_TT_CLIENT_ROAM) && batadv_has_timed_out(tt_global->roam_at, roam_timeout)) { @@ -2354,14 +2354,14 @@ static bool batadv_tt_global_to_purge(struct batadv_tt_global_entry *tt_global, */ static void batadv_tt_global_purge(struct batadv_priv *bat_priv) { - struct batadv_hashtable *hash = bat_priv->tt.global_hash; - struct hlist_head *head; - struct hlist_node *node_tmp; spinlock_t *list_lock; /* protects write access to the hash lists */ - u32 i; - char *msg = NULL; + struct batadv_hashtable *hash = bat_priv->tt.global_hash; struct batadv_tt_common_entry *tt_common; struct batadv_tt_global_entry *tt_global; + struct hlist_node *node_tmp; + struct hlist_head *head; + char *msg = NULL; + u32 i; for (i = 0; i < hash->size; i++) { head = &hash->table[i]; @@ -2400,10 +2400,10 @@ static void batadv_tt_global_purge(struct batadv_priv *bat_priv) */ static void batadv_tt_global_table_free(struct batadv_priv *bat_priv) { - struct batadv_hashtable *hash; spinlock_t *list_lock; /* protects write access to the hash lists */ struct batadv_tt_common_entry *tt_common_entry; struct batadv_tt_global_entry *tt_global; + struct batadv_hashtable *hash; struct hlist_node *node_tmp; struct hlist_head *head; u32 i; @@ -2479,10 +2479,10 @@ struct batadv_orig_node *batadv_transtable_search(struct batadv_priv *bat_priv, const u8 *addr, unsigned short vid) { - struct batadv_tt_local_entry *tt_local_entry = NULL; struct batadv_tt_global_entry *tt_global_entry = NULL; - struct batadv_orig_node *orig_node = NULL; + struct batadv_tt_local_entry *tt_local_entry = NULL; struct batadv_tt_orig_list_entry *best_entry; + struct batadv_orig_node *orig_node = NULL; if (src && batadv_vlan_ap_isola_get(bat_priv, vid)) { tt_local_entry = batadv_tt_local_hash_find(bat_priv, src, vid); @@ -2551,11 +2551,11 @@ static u32 batadv_tt_global_crc(struct batadv_priv *bat_priv, struct batadv_tt_common_entry *tt_common; struct batadv_tt_global_entry *tt_global; struct hlist_head *head; - u32 i; + __be16 tmp_vid; u32 crc_tmp; u32 crc = 0; u8 flags; - __be16 tmp_vid; + u32 i; for (i = 0; i < hash->size; i++) { head = &hash->table[i]; @@ -2631,11 +2631,11 @@ static u32 batadv_tt_local_crc(struct batadv_priv *bat_priv, struct batadv_hashtable *hash = bat_priv->tt.local_hash; struct batadv_tt_common_entry *tt_common; struct hlist_head *head; - u32 i; + __be16 tmp_vid; u32 crc_tmp; u32 crc = 0; u8 flags; - __be16 tmp_vid; + u32 i; for (i = 0; i < hash->size; i++) { head = &hash->table[i]; @@ -2784,8 +2784,8 @@ static struct batadv_tt_req_node * batadv_tt_req_node_new(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node) { - struct batadv_tt_req_node *tt_req_node_tmp; struct batadv_tt_req_node *tt_req_node = NULL; + struct batadv_tt_req_node *tt_req_node_tmp; spin_lock_bh(&bat_priv->tt.req_list_lock); hlist_for_each_entry(tt_req_node_tmp, &bat_priv->tt.req_list, list) { @@ -2894,8 +2894,8 @@ static u16 batadv_tt_tvlv_generate(struct batadv_priv *bat_priv, struct batadv_tt_common_entry *tt_common_entry; struct batadv_tvlv_tt_change *tt_change; struct hlist_head *head; - u16 tt_tot; u16 tt_num_entries = 0; + u16 tt_tot; u8 flags; bool ret; u32 i; @@ -2949,9 +2949,9 @@ static bool batadv_tt_global_check_crc(struct batadv_orig_node *orig_node, { struct batadv_tvlv_tt_vlan_data *tt_vlan_tmp; struct batadv_orig_node_vlan *vlan; - int i; int orig_num_vlan; u32 crc; + int i; /* check if each received CRC matches the locally stored one */ for (i = 0; i < num_vlan; i++) { @@ -3057,8 +3057,8 @@ static bool batadv_send_tt_request(struct batadv_priv *bat_priv, struct batadv_tt_req_node *tt_req_node = NULL; struct batadv_hard_iface *primary_if; bool ret = false; - int i; int size; + int i; primary_if = batadv_primary_if_get_selected(bat_priv); if (!primary_if) @@ -3134,15 +3134,15 @@ static bool batadv_send_other_tt_response(struct batadv_priv *bat_priv, struct batadv_tvlv_tt_data *tt_data, u8 *req_src, u8 *req_dst) { - struct batadv_orig_node *req_dst_orig_node; struct batadv_orig_node *res_dst_orig_node = NULL; - struct batadv_tvlv_tt_change *tt_change; struct batadv_tvlv_tt_data *tvlv_tt_data = NULL; + struct batadv_orig_node *req_dst_orig_node; + struct batadv_tvlv_tt_change *tt_change; bool ret = false; bool full_table; u8 orig_ttvn; - u8 req_ttvn; u16 tvlv_len; + u8 req_ttvn; s32 tt_len; batadv_dbg(BATADV_DBG_TT, bat_priv, @@ -3269,10 +3269,10 @@ static bool batadv_send_my_tt_response(struct batadv_priv *bat_priv, struct batadv_hard_iface *primary_if = NULL; struct batadv_tvlv_tt_change *tt_change; struct batadv_orig_node *orig_node; - u8 my_ttvn; - u8 req_ttvn; - u16 tvlv_len; bool full_table; + u16 tvlv_len; + u8 req_ttvn; + u8 my_ttvn; s32 tt_len; batadv_dbg(BATADV_DBG_TT, bat_priv, @@ -3407,8 +3407,8 @@ static void _batadv_tt_update_changes(struct batadv_priv *bat_priv, struct batadv_tvlv_tt_change *tt_change, u16 tt_num_changes, u8 ttvn) { - int i; int roams; + int i; for (i = 0; i < tt_num_changes; i++) { if ((tt_change + i)->flags & BATADV_TT_CLIENT_DEL) { @@ -3539,11 +3539,11 @@ static void batadv_handle_tt_response(struct batadv_priv *bat_priv, struct batadv_tvlv_tt_data *tt_data, u8 *resp_src, u16 num_entries) { - struct batadv_tt_req_node *node; - struct hlist_node *safe; struct batadv_orig_node *orig_node = NULL; struct batadv_tvlv_tt_change *tt_change; + struct batadv_tt_req_node *node; u8 *tvlv_ptr = (u8 *)tt_data; + struct hlist_node *safe; batadv_dbg(BATADV_DBG_TT, bat_priv, "Received TT_RESPONSE from %pM for ttvn %d t_size: %d [%c]\n", @@ -3702,8 +3702,8 @@ static void batadv_send_roam_adv(struct batadv_priv *bat_priv, u8 *client, unsigned short vid, struct batadv_orig_node *orig_node) { - struct batadv_hard_iface *primary_if; struct batadv_tvlv_roam_adv tvlv_roam; + struct batadv_hard_iface *primary_if; primary_if = batadv_primary_if_get_selected(bat_priv); if (!primary_if) @@ -3836,12 +3836,12 @@ static void batadv_tt_local_set_flags(struct batadv_priv *bat_priv, u16 flags, */ static void batadv_tt_local_purge_pending_clients(struct batadv_priv *bat_priv) { + spinlock_t *list_lock; /* protects write access to the hash lists */ struct batadv_hashtable *hash = bat_priv->tt.local_hash; struct batadv_tt_common_entry *tt_common; struct batadv_tt_local_entry *tt_local; struct hlist_node *node_tmp; struct hlist_head *head; - spinlock_t *list_lock; /* protects write access to the hash lists */ u32 i; if (!hash) @@ -3931,8 +3931,8 @@ void batadv_tt_local_commit_changes(struct batadv_priv *bat_priv) bool batadv_is_ap_isolated(struct batadv_priv *bat_priv, u8 *src, u8 *dst, unsigned short vid) { - struct batadv_tt_local_entry *tt_local_entry; struct batadv_tt_global_entry *tt_global_entry; + struct batadv_tt_local_entry *tt_local_entry; struct batadv_meshif_vlan *vlan; bool ret = false; @@ -4142,10 +4142,12 @@ bool batadv_tt_add_temporary_global_entry(struct batadv_priv *bat_priv, void batadv_tt_local_resize_to_mtu(struct net_device *mesh_iface) { struct batadv_priv *bat_priv = netdev_priv(mesh_iface); - int packet_size_max = READ_ONCE(bat_priv->packet_size_max); - int table_size; int timeout = BATADV_TT_LOCAL_TIMEOUT / 2; bool reduced = false; + int packet_size_max; + int table_size; + + packet_size_max = READ_ONCE(bat_priv->packet_size_max); spin_lock_bh(&bat_priv->tt.commit_lock); @@ -4188,9 +4190,9 @@ static void batadv_tt_tvlv_ogm_handler_v1(struct batadv_priv *bat_priv, { struct batadv_tvlv_tt_change *tt_change; struct batadv_tvlv_tt_data *tt_data; + size_t tt_data_sz; u16 num_entries; u16 num_vlan; - size_t tt_data_sz; if (tvlv_value_len < sizeof(*tt_data)) return; @@ -4312,8 +4314,8 @@ static int batadv_roam_tvlv_unicast_handler_v1(struct batadv_priv *bat_priv, void *tvlv_value, u16 tvlv_value_len) { - struct batadv_tvlv_roam_adv *roaming_adv; struct batadv_orig_node *orig_node = NULL; + struct batadv_tvlv_roam_adv *roaming_adv; /* If this node is not the intended recipient of the * roaming advertisement the packet is forwarded @@ -4416,12 +4418,12 @@ bool batadv_tt_global_is_isolated(struct batadv_priv *bat_priv, */ int __init batadv_tt_cache_init(void) { - size_t tl_size = sizeof(struct batadv_tt_local_entry); - size_t tg_size = sizeof(struct batadv_tt_global_entry); size_t tt_orig_size = sizeof(struct batadv_tt_orig_list_entry); size_t tt_change_size = sizeof(struct batadv_tt_change_node); - size_t tt_req_size = sizeof(struct batadv_tt_req_node); size_t tt_roam_size = sizeof(struct batadv_tt_roam_node); + size_t tg_size = sizeof(struct batadv_tt_global_entry); + size_t tt_req_size = sizeof(struct batadv_tt_req_node); + size_t tl_size = sizeof(struct batadv_tt_local_entry); batadv_tl_cache = kmem_cache_create("batadv_tl_cache", tl_size, 0, SLAB_HWCACHE_ALIGN, NULL); diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c index 332dda09fe4c..de907c07fa15 100644 --- a/net/batman-adv/tvlv.c +++ b/net/batman-adv/tvlv.c @@ -72,8 +72,8 @@ static void batadv_tvlv_handler_put(struct batadv_tvlv_handler *tvlv_handler) static struct batadv_tvlv_handler * batadv_tvlv_handler_get(struct batadv_priv *bat_priv, u8 type, u8 version) { - struct batadv_tvlv_handler *tvlv_handler_tmp; struct batadv_tvlv_handler *tvlv_handler = NULL; + struct batadv_tvlv_handler *tvlv_handler_tmp; rcu_read_lock(); hlist_for_each_entry_rcu(tvlv_handler_tmp, @@ -135,8 +135,8 @@ static void batadv_tvlv_container_put(struct batadv_tvlv_container *tvlv) static struct batadv_tvlv_container * batadv_tvlv_container_get(struct batadv_priv *bat_priv, u8 type, u8 version) { - struct batadv_tvlv_container *tvlv_tmp; struct batadv_tvlv_container *tvlv = NULL; + struct batadv_tvlv_container *tvlv_tmp; lockdep_assert_held(&bat_priv->tvlv.container_list_lock); @@ -539,13 +539,13 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv, struct sk_buff *skb, void *tvlv_value, u16 tvlv_value_len) { + u8 cifnotfound = BATADV_TVLV_HANDLER_OGM_CIFNOTFND; u16 tvlv_value_start_len = tvlv_value_len; struct batadv_tvlv_handler *tvlv_handler; void *tvlv_value_start = tvlv_value; struct batadv_tvlv_hdr *tvlv_hdr; - u16 tvlv_value_cont_len; - u8 cifnotfound = BATADV_TVLV_HANDLER_OGM_CIFNOTFND; int ret = NET_RX_SUCCESS; + u16 tvlv_value_cont_len; int res; while ((tvlv_hdr = batadv_tvlv_hdr_next(&tvlv_value, &tvlv_value_len))) { @@ -606,8 +606,8 @@ void batadv_tvlv_ogm_receive(struct batadv_priv *bat_priv, struct batadv_ogm_packet *batadv_ogm_packet, struct batadv_orig_node *orig_node) { - void *tvlv_value; u16 tvlv_value_len; + void *tvlv_value; if (!batadv_ogm_packet) return; @@ -727,12 +727,12 @@ void batadv_tvlv_unicast_send(struct batadv_priv *bat_priv, const u8 *src, void *tvlv_value, u16 tvlv_value_len) { struct batadv_unicast_tvlv_packet *unicast_tvlv_packet; - struct batadv_tvlv_hdr *tvlv_hdr; + ssize_t hdr_len = sizeof(*unicast_tvlv_packet); struct batadv_orig_node *orig_node; - struct sk_buff *skb; + struct batadv_tvlv_hdr *tvlv_hdr; unsigned char *tvlv_buff; unsigned int tvlv_len; - ssize_t hdr_len = sizeof(*unicast_tvlv_packet); + struct sk_buff *skb; orig_node = batadv_orig_hash_find(bat_priv, dst); if (!orig_node) From 95a390ce6aee1a3927523d5221d290a71b514f7e Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 3 Aug 2026 21:57:45 +0800 Subject: [PATCH 0944/1433] net: stmmac: remove ptpaddr/mmcaddr/estaddr "safe" initialization These so called "safe" initializations aren't needed any more from sometime, but the unnecessaries are obvious after recent clean up by Russell. The code will correctly initialize them after getting the correct stmmac_hwif_entry by calling stmmac_hwif_find(). Signed-off-by: Jisheng Zhang Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260803135745.12600-1-jszhang@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/hwif.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.c b/drivers/net/ethernet/stmicro/stmmac/hwif.c index 511b0fd5e834..265671170bf6 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.c +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.c @@ -328,18 +328,6 @@ int stmmac_hwif_init(struct stmmac_priv *priv) /* Save ID for later use */ priv->synopsys_id = version.snpsver; - /* Lets assume some safe values first */ - if (core_type == DWMAC_CORE_GMAC4) { - priv->ptpaddr = priv->ioaddr + PTP_GMAC4_OFFSET; - priv->mmcaddr = priv->ioaddr + MMC_GMAC4_OFFSET; - priv->estaddr = priv->ioaddr + EST_GMAC4_OFFSET; - } else { - priv->ptpaddr = priv->ioaddr + PTP_GMAC3_X_OFFSET; - priv->mmcaddr = priv->ioaddr + MMC_GMAC3_X_OFFSET; - if (core_type == DWMAC_CORE_XGMAC) - priv->estaddr = priv->ioaddr + EST_XGMAC_OFFSET; - } - mac = devm_kzalloc(priv->device, sizeof(*mac), GFP_KERNEL); if (!mac) return -ENOMEM; From 1a930d5734b77ceb0813bb7ecf99d1a89fbe0164 Mon Sep 17 00:00:00 2001 From: "Xiang Mei (Microsoft)" Date: Wed, 29 Jul 2026 20:06:21 +0000 Subject: [PATCH 0945/1433] macvlan: require init-userns CAP_NET_ADMIN to raise bc_queue_len IFLA_MACVLAN_BC_QUEUE_LEN accepts any u32 and becomes port->bc_queue_len_used, the only bound on port->bc_queue. rtnetlink checks CAP_NET_ADMIN against the target netns only, so a user who unshares a user+net namespace, creates a veth and puts a macvlan on it can set the backlog to 0xffffffff and flood broadcast frames until the host dies: Out of memory: Killed process 141 (su) UID:0 Kernel panic - not syncing: System is deadlocked on memory Call Trace: vpanic (kernel/panic.c:650) panic (kernel/panic.c:787) out_of_memory (mm/oom_kill.c:1166) __alloc_frozen_pages_noprof (mm/page_alloc.c:4914) alloc_pages_mpol (mm/mempolicy.c:2490) folio_alloc_noprof (mm/mempolicy.c:2591) filemap_fault (mm/filemap.c:3565) A fixed upper bound does not work. Deployments carrying 600-800 real-time audio streams run bc_queue_len=100000, and no constant serves both cases: the queue counts skbs, not bytes, and the frame size is attacker-chosen too (up to ETH_MAX_MTU on a veth the caller creates). Gate the elevated range on CAP_NET_ADMIN in the initial user namespace instead. A backlog of that size is a host-wide tuning decision, and an unprivileged owner of a namespace it created itself should not be able to make it; privileged configurations keep working unchanged.. Cc: stable+noautosel@kernel.org # local DoS by userns are a dime a dozen Reported-by: AutonomousCodeSecurity@microsoft.com Link: https://lore.kernel.org/r/20260706212556.3199234-1-xmei5@asu.edu Signed-off-by: Xiang Mei (Microsoft) Link: https://patch.msgid.link/20260729200621.2521588-1-xmei5@asu.edu Signed-off-by: Jakub Kicinski --- drivers/net/macvlan.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c index 9a4bc99dbf53..42169f3614c4 100644 --- a/drivers/net/macvlan.c +++ b/drivers/net/macvlan.c @@ -1339,6 +1339,15 @@ static int macvlan_validate(struct nlattr *tb[], struct nlattr *data[], if (!data) return 0; + if (data[IFLA_MACVLAN_BC_QUEUE_LEN] && + nla_get_u32(data[IFLA_MACVLAN_BC_QUEUE_LEN]) > + MACVLAN_DEFAULT_BC_QUEUE_LEN && + !capable(CAP_NET_ADMIN)) { + NL_SET_ERR_MSG_ATTR(extack, data[IFLA_MACVLAN_BC_QUEUE_LEN], + "bc_queue_len above the default requires CAP_NET_ADMIN in the initial user namespace"); + return -EPERM; + } + if (data[IFLA_MACVLAN_FLAGS] && nla_get_u16(data[IFLA_MACVLAN_FLAGS]) & ~(MACVLAN_FLAG_NOPROMISC | MACVLAN_FLAG_NODST)) From c509971352a6e6e6dcedb9222133c0ce0e54c8d2 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 31 Jul 2026 17:47:38 +0200 Subject: [PATCH 0946/1433] net: airoha: fix ARRAY_SIZE() division by zero on UP builds airoha_alloc_gdm_device() initializes the txq_lock[] array iterating over ARRAY_SIZE(dev->txq_lock). ARRAY_SIZE() expands to sizeof(dev->txq_lock) / sizeof((dev->txq_lock)[0]), but on UP builds (CONFIG_SMP unset, CONFIG_DEBUG_SPINLOCK unset) arch_spinlock_t is an empty struct, so sizeof(spinlock_t) is zero and the expression is a compile-time division by zero (undefined behavior), reported by clang as "division by zero is undefined [-Wdivision-by-zero]". Since the array is statically sized with AIROHA_NUM_NETDEV_TX_RINGS, use the named constant as loop bound instead of ARRAY_SIZE(). Fixes: 78a35725e533 ("net: airoha: defer GDM3/GDM4 WAN mode and GDM2 loopback to QoS offload") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202607311850.6p0ZUVq4-lkp@intel.com/ Signed-off-by: Lorenzo Bianconi Reviewed-by: Nick Desaulniers Link: https://patch.msgid.link/20260731-airoha-spinlock-array-fix-v1-1-863a7e239a5f@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/airoha/airoha_eth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c index dba7c52c0896..64619e9a704d 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.c +++ b/drivers/net/ethernet/airoha/airoha_eth.c @@ -3467,7 +3467,7 @@ static int airoha_alloc_gdm_device(struct airoha_eth *eth, netdev->dev.of_node = of_node_get(np); dev = netdev_priv(netdev); u64_stats_init(&dev->stats.syncp); - for (i = 0; i < ARRAY_SIZE(dev->txq_lock); i++) + for (i = 0; i < AIROHA_NUM_NETDEV_TX_RINGS; i++) spin_lock_init(&dev->txq_lock[i]); dev->port = port; dev->eth = eth; From 6d356e408670e2c0919e32b2958d1947fdf104f2 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 31 Jul 2026 23:06:06 +0200 Subject: [PATCH 0947/1433] net: dsa: realtek: rtl8366rb: Fix up port isolation Sashiko reports that we incorrectly disable isolation in the setup loop while what we want to do is to enable it. Enable it by unconditionally setting the enable bit 0 in rtl8366rb_port_set_isolation() so a mask of 0 when passed in will enable isolation and isolate from ALL ports. Fix up the comments so it is clear what is going on, including a missing word in the helper function. Reported-by: Paolo Abeni Closes: https://sashiko.dev/#/patchset/20260630-rtl8366rb-improvements-v2-0-05eb9d6a37f5%40kernel.org Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260731-rtl8366rb-fixes-v4-1-fbf0c95b829a@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/dsa/realtek/rtl8366rb.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/net/dsa/realtek/rtl8366rb.c b/drivers/net/dsa/realtek/rtl8366rb.c index d2fa8ff6a5d0..f11831b66de8 100644 --- a/drivers/net/dsa/realtek/rtl8366rb.c +++ b/drivers/net/dsa/realtek/rtl8366rb.c @@ -794,11 +794,10 @@ static int rtl8366rb_setup_all_leds_off(struct realtek_priv *priv) static int rtl8366rb_port_set_isolation(struct realtek_priv *priv, int port, u32 mask) { - /* Bit 0 enables isolation so set this if we enable isolation - * any of the ports an clear it if we disable on all of them. + /* Bit 0 enables isolation, the mask indicates allowed forwarding + * ports */ - if (mask) - mask = RTL8366RB_PORT_ISO_PORTS(mask) | RTL8366RB_PORT_ISO_EN; + mask = RTL8366RB_PORT_ISO_PORTS(mask) | RTL8366RB_PORT_ISO_EN; return regmap_write(priv->map, RTL8366RB_PORT_ISO(port), mask); @@ -974,7 +973,7 @@ static int rtl8366rb_setup(struct dsa_switch *ds) if (!dsa_port_is_user(dp)) continue; - /* Forward only to the CPU */ + /* Forward only to the CPU(s), isolate from all other ports */ ret = rtl8366rb_port_set_isolation(priv, dp->index, upports_mask); if (ret) return ret; From d100966325f782aa7775e5f0e8fb04f66f56308e Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Wed, 29 Jul 2026 21:16:26 -0700 Subject: [PATCH 0948/1433] net/rds: don't use unpin_user_pages_dirty_lock() from atomic context rds_rdma_free_op() and rds_atomic_free_op() are reached from the IB send completion path via rds_ib_tasklet_fn_send() rds_ib_send_cqe_handler() rds_message_put() rds_message_purge() rds_rdma_free_op() / rds_atomic_free_op() which runs in tasklet (softirq) context. Both functions unpin the user pages of the op with unpin_user_pages_dirty_lock(), which uses set_page_dirty_lock() and thus may take the folio lock and sleep. Sleeping in softirq context is not allowed and can deadlock or crash. Dirtying the pages with the non-sleeping set_page_dirty() instead would just trade one bug for another, as pointed out during review: the pinned range can be file-backed. rds_pin_pages() pins with FOLL_LONGTERM, which refuses fs-dax but takes the page-cache pages of a MAP_SHARED file mapping just fine, and RDS does not restrict what memory the caller registers as an RDMA destination. For a file-backed page, set_page_dirty() from a tasklet can take non-irq-safe filesystem locks (e.g. mapping->i_private_lock and inode->i_lock in block_dirty_folio()) and deadlock against the task it interrupted. Without the folio lock, it races with truncation clearing folio->mapping, which is the race set_page_dirty_lock() exists to close. The pre-pin_user_pages() version of this code dirtied pages that way from the tasklet, so that bug is older than the sleeping unpin. The page dirtying therefore has to move to process context, not merely avoid the folio lock. When the final rds_message_put() runs in atomic context, rds_rdma_free_op() and rds_atomic_free_op() now leave the op's pages pinned and flag the op. Later, rds_message_put() hands the message to a work item that unpins the flagged ops' pages and frees the message from process context. Here, unpin_user_pages_dirty_lock() is safe outside the atomic context. Everything else keeps running in the caller's context exactly as before: the rest of the purge - the zerocopy completion, the socket put and the MR reference drops - as well as RDMA writes, whose pages the remote side only reads and which unpin without dirtying, everything on rds_tcp, and final puts that already happen in process context (socket close, connection teardown). Deferring only the unpin means the work item touches nothing but the pinned pages and the rds module's own memory: it cannot call back into a transport module, so it changes nothing about the transports' shutdown and unload ordering. rds_exit() drains any pending unpin work via destroy_workqueue(rds_wq) before the module goes away. The Oracle UEK kernel avoids the sleeping unpin by calling set_page_dirty() directly from the tasklet, which is subject to the file-backed page problem above, so this deliberately does not follow UEK here. Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260730041629.3512480-2-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/message.c | 26 +++++++++++++++++++++++++ net/rds/rdma.c | 49 ++++++++++++++++++++++++++++++++++++----------- net/rds/rds.h | 10 ++++++++++ 3 files changed, 74 insertions(+), 11 deletions(-) diff --git a/net/rds/message.c b/net/rds/message.c index 7feb0eb6537d..f25f2592586f 100644 --- a/net/rds/message.c +++ b/net/rds/message.c @@ -182,6 +182,19 @@ static void rds_message_purge(struct rds_message *rm) kref_put(&rm->atomic.op_rdma_mr->r_kref, __rds_put_mr_final); } +static void rds_message_unpin_worker(struct work_struct *work) +{ + struct rds_message *rm = container_of(work, struct rds_message, + m_unpin_work); + + if (rm->rdma.op_unpin_deferred) + rds_rdma_op_unpin_pages(&rm->rdma); + if (rm->atomic.op_unpin_deferred) + rds_atomic_op_unpin_page(&rm->atomic); + + kfree(rm); +} + void rds_message_put(struct rds_message *rm) { rdsdebug("put rm %p ref %d\n", rm, refcount_read(&rm->m_refcount)); @@ -189,8 +202,21 @@ void rds_message_put(struct rds_message *rm) if (refcount_dec_and_test(&rm->m_refcount)) { BUG_ON(!list_empty(&rm->m_sock_item)); BUG_ON(!list_empty(&rm->m_conn_item)); + rds_message_purge(rm); + /* A final put in atomic context cannot dirty the ops' + * user pages on unpin, so rds_rdma_free_op() and + * rds_atomic_free_op() deferred it. Finish the unpin, + * and the free, from process context. + */ + if (rm->rdma.op_unpin_deferred || + rm->atomic.op_unpin_deferred) { + INIT_WORK(&rm->m_unpin_work, rds_message_unpin_worker); + queue_work(rds_wq, &rm->m_unpin_work); + return; + } + kfree(rm); } } diff --git a/net/rds/rdma.c b/net/rds/rdma.c index 61fb6e45281b..f360a7b3b5fe 100644 --- a/net/rds/rdma.c +++ b/net/rds/rdma.c @@ -483,22 +483,36 @@ void rds_rdma_unuse(struct rds_sock *rs, u32 r_key, int force) kref_put(&mr->r_kref, __rds_put_mr_final); } -void rds_rdma_free_op(struct rm_rdma_op *ro) +void rds_rdma_op_unpin_pages(struct rm_rdma_op *ro) { unsigned int i; + for (i = 0; i < ro->op_nents; i++) { + struct page *page = sg_page(&ro->op_sg[i]); + + /* Mark page dirty if it was possibly modified, which + * is the case for a RDMA_READ which copies from remote + * to local memory + */ + unpin_user_pages_dirty_lock(&page, 1, !ro->op_write); + } +} + +void rds_rdma_free_op(struct rm_rdma_op *ro) +{ if (ro->op_odp_mr) { kref_put(&ro->op_odp_mr->r_kref, __rds_put_mr_final); + } else if (in_task() || ro->op_write) { + /* An RDMA write's pages are only read by the remote + * side; unpinning without dirtying does not sleep. + */ + rds_rdma_op_unpin_pages(ro); } else { - for (i = 0; i < ro->op_nents; i++) { - struct page *page = sg_page(&ro->op_sg[i]); - - /* Mark page dirty if it was possibly modified, which - * is the case for a RDMA_READ which copies from remote - * to local memory - */ - unpin_user_pages_dirty_lock(&page, 1, !ro->op_write); - } + /* Dirtying the pages on unpin can sleep; leave them + * pinned and have rds_message_put() finish the unpin + * from process context. + */ + ro->op_unpin_deferred = 1; } kfree(ro->op_notifier); @@ -507,7 +521,7 @@ void rds_rdma_free_op(struct rm_rdma_op *ro) ro->op_odp_mr = NULL; } -void rds_atomic_free_op(struct rm_atomic_op *ao) +void rds_atomic_op_unpin_page(struct rm_atomic_op *ao) { struct page *page = sg_page(ao->op_sg); @@ -515,6 +529,19 @@ void rds_atomic_free_op(struct rm_atomic_op *ao) * is the case for a RDMA_READ which copies from remote * to local memory */ unpin_user_pages_dirty_lock(&page, 1, true); +} + +void rds_atomic_free_op(struct rm_atomic_op *ao) +{ + if (in_task()) { + rds_atomic_op_unpin_page(ao); + } else { + /* Dirtying the page on unpin can sleep; leave it + * pinned and have rds_message_put() finish the unpin + * from process context. + */ + ao->op_unpin_deferred = 1; + } kfree(ao->op_notifier); ao->op_notifier = NULL; diff --git a/net/rds/rds.h b/net/rds/rds.h index 6e0790e4b570..14bff7440b79 100644 --- a/net/rds/rds.h +++ b/net/rds/rds.h @@ -445,6 +445,12 @@ struct rds_message { void *m_final_op; + /* Unpins the ops' user pages and frees the message from + * process context when the final put happens in atomic + * context: dirtying the pages on unpin can sleep. + */ + struct work_struct m_unpin_work; + struct { struct rm_atomic_op { int op_type; @@ -468,6 +474,7 @@ struct rds_message { unsigned int op_mapped:1; unsigned int op_silent:1; unsigned int op_active:1; + unsigned int op_unpin_deferred:1; struct scatterlist *op_sg; struct rds_notifier *op_notifier; @@ -483,6 +490,7 @@ struct rds_message { unsigned int op_mapped:1; unsigned int op_silent:1; unsigned int op_active:1; + unsigned int op_unpin_deferred:1; unsigned int op_bytes; unsigned int op_nents; unsigned int op_count; @@ -972,6 +980,8 @@ int rds_cmsg_rdma_map(struct rds_sock *rs, struct rds_message *rm, struct cmsghdr *cmsg); void rds_rdma_free_op(struct rm_rdma_op *ro); void rds_atomic_free_op(struct rm_atomic_op *ao); +void rds_rdma_op_unpin_pages(struct rm_rdma_op *ro); +void rds_atomic_op_unpin_page(struct rm_atomic_op *ao); void rds_rdma_send_complete(struct rds_message *rm, int wc_status); void rds_atomic_send_complete(struct rds_message *rm, int wc_status); int rds_cmsg_atomic(struct rds_sock *rs, struct rds_message *rm, From 9079adef042c1e7a6141dcdfd3041acd30607297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bugge?= Date: Wed, 29 Jul 2026 21:16:27 -0700 Subject: [PATCH 0949/1433] net/rds: hold the socket while an rds_mr references it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each rds_mr stores a bare back pointer to the socket that created it (mr->r_sock) but takes no reference on it. When the mr is destroyed it references the rs. Hence, provisions must be made to avoid the rs being destroyed before all mrs referencing it have been destroyed. The MR itself is refcounted, and in-flight messages legitimately hold MR krefs that can outlive the socket: rds_release() drops the rb-tree references via rds_rdma_drop_keys(), but a send completion arriving afterwards drops the final message reference from the CQ handler and ends up in rds_message_purge() __rds_put_mr_final() rds_destroy_mr() -> takes rs->rs_rdma_lock dereferencing a socket that may already have been freed. Oracle UEK fixed the same use-after-free ("rds: Add proper refcnt when an RDS MR references an RDS Socket") after seeing crashes of the form: PF: supervisor write access in kernel mode _raw_spin_lock_irqsave+0x4a/0x6a __rds_put_mr_final+0x2c/0xe0 [rds] rds_message_purge+0x13c/0x150 [rds] rds_message_put+0x39/0x54 [rds] rds_ib_send_cqe_handler+0x147/0x3dd [rds_rdma] To fix this, take a socket reference when an MR is created and drop it when the final MR kref goes away. The reference cycle is broken by rds_release(), which always runs rds_rdma_drop_keys() on close. So the socket reference held by an MR never prevents release, it only delays sk_free() until the last MR user is done. The hold sits next to kref_init() at both allocation sites - __rds_rdma_map() and the on-demand-paging path in rds_cmsg_rdma_args() - so every MR owns exactly one socket reference from the moment it becomes kref-managed. For that to work on the ODP path, its get_mr() error handling is converted from a bare kfree() to kref_put(..., __rds_put_mr_final), with r_trans_private cleared first since it holds an ERR_PTR there; both sites then tear down through the same path and a future error-path change cannot silently leak or double-drop the reference. Signed-off-by: HÃ¥kon Bugge [achender: port to net-next (sock_hold/sock_put in place of the UEK rds_sock_addref/rds_sock_put helpers); also balance the reference on the rds_cmsg_rdma_args() ODP path and unify its error path with __rds_put_mr_final(); update commit message] Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260730041629.3512480-3-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/rdma.c | 14 +++++++++++++- net/rds/rds.h | 5 ++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/net/rds/rdma.c b/net/rds/rdma.c index f360a7b3b5fe..078090d292fa 100644 --- a/net/rds/rdma.c +++ b/net/rds/rdma.c @@ -117,6 +117,7 @@ void __rds_put_mr_final(struct kref *kref) struct rds_mr *mr = container_of(kref, struct rds_mr, r_kref); rds_destroy_mr(mr); + sock_put(rds_rs_to_sk(mr->r_sock)); kfree(mr); } @@ -243,7 +244,11 @@ static int __rds_rdma_map(struct rds_sock *rs, struct rds_get_mr_args *args, kref_init(&mr->r_kref); RB_CLEAR_NODE(&mr->r_rb_node); mr->r_trans = rs->rs_transport; + /* The MR can outlive its socket: a socket reference is held + * until the final kref is dropped in __rds_put_mr_final(). + */ mr->r_sock = rs; + sock_hold(rds_rs_to_sk(rs)); if (args->flags & RDS_RDMA_USE_ONCE) mr->r_use_once = 1; @@ -759,7 +764,12 @@ int rds_cmsg_rdma_args(struct rds_sock *rs, struct rds_message *rm, RB_CLEAR_NODE(&local_odp_mr->r_rb_node); kref_init(&local_odp_mr->r_kref); local_odp_mr->r_trans = rs->rs_transport; + /* The MR can outlive its socket: a socket + * reference is held until the final kref is + * dropped in __rds_put_mr_final(). + */ local_odp_mr->r_sock = rs; + sock_hold(rds_rs_to_sk(rs)); local_odp_mr->r_trans_private = rs->rs_transport->get_mr( NULL, 0, rs, &local_odp_mr->r_key, NULL, @@ -768,7 +778,9 @@ int rds_cmsg_rdma_args(struct rds_sock *rs, struct rds_message *rm, ret = PTR_ERR(local_odp_mr->r_trans_private); rdsdebug("get_mr ret %d %p\"", ret, local_odp_mr->r_trans_private); - kfree(local_odp_mr); + local_odp_mr->r_trans_private = NULL; + kref_put(&local_odp_mr->r_kref, + __rds_put_mr_final); ret = -EOPNOTSUPP; goto out_pages; } diff --git a/net/rds/rds.h b/net/rds/rds.h index 14bff7440b79..2db49573dacd 100644 --- a/net/rds/rds.h +++ b/net/rds/rds.h @@ -320,7 +320,10 @@ struct rds_mr { unsigned int r_invalidate:1; unsigned int r_write:1; - struct rds_sock *r_sock; /* back pointer to the socket that owns us */ + struct rds_sock *r_sock; /* socket that owns us; counted + * reference, dropped by + * __rds_put_mr_final() + */ struct rds_transport *r_trans; void *r_trans_private; }; From eb8a59a17f4a8b6f2afb4a8063e99dd96cc3fab0 Mon Sep 17 00:00:00 2001 From: Sharath Srinivasan Date: Wed, 29 Jul 2026 21:16:28 -0700 Subject: [PATCH 0950/1433] net/rds: fix rds_message leak in the rds_send_xmit() drop path When rds_send_xmit() picks the next message off cp_send_queue it takes its own reference with rds_message_addref(). If the message then hits the never-retransmit check (RDS_MSG_FLUSH, or an RDMA op that was already retransmitted), it is moved to the local to_be_dropped list and that reference is dropped after the batch. However, if RDS_MSG_ON_CONN has already been cleared, the message is not added to to_be_dropped and the reference taken above is never dropped: cp_xmit_rm has not been set at this point, so the loop simply abandons rm and the rds_message (and everything it pins: pages, MRs, notifiers) leaks after an RDMA error. The only other places that clear RDS_MSG_ON_CONN are rds_send_path_drop_acked() and rds_send_drop_to(), and both can run while rds_send_xmit() has dropped cp_lock between moving the message to cp_retrans and re-taking the lock in the never-retransmit check: rds_send_path_drop_acked() can ack away a message that already sat on cp_retrans - the RDS_MSG_RETRANSMITTED case above - and rds_send_drop_to() runs on socket close. Both unlink the message under cp_lock and put their own reference, leaving the xmit-path reference stranded. Drop the reference directly in that case. This mirrors Oracle UEK commit "net/rds: fix rds_message memleak in rds_send_xmit". Signed-off-by: Gerd Rausch Signed-off-by: Sharath Srinivasan [achender: port to net-next; update commit message, checkpatch nits] Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260730041629.3512480-4-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/send.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/net/rds/send.c b/net/rds/send.c index 6a567c97a999..309021e0cc9b 100644 --- a/net/rds/send.c +++ b/net/rds/send.c @@ -339,9 +339,21 @@ int rds_send_xmit(struct rds_conn_path *cp) (rm->rdma.op_active && test_bit(RDS_MSG_RETRANSMITTED, &rm->m_flags))) { spin_lock_irqsave(&cp->cp_lock, flags); - if (test_and_clear_bit(RDS_MSG_ON_CONN, &rm->m_flags)) - list_move(&rm->m_conn_item, &to_be_dropped); - spin_unlock_irqrestore(&cp->cp_lock, flags); + if (test_and_clear_bit(RDS_MSG_ON_CONN, + &rm->m_flags)) { + /* our ref is put after the batch */ + list_move(&rm->m_conn_item, + &to_be_dropped); + spin_unlock_irqrestore(&cp->cp_lock, + flags); + } else { + /* already off the conn list; drop + * the ref taken above ourselves + */ + spin_unlock_irqrestore(&cp->cp_lock, + flags); + rds_message_put(rm); + } continue; } From a507023e6ff1d0b4e7aac49b2f547fed0c21ea4e Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Wed, 29 Jul 2026 21:16:29 -0700 Subject: [PATCH 0951/1433] net/rds: unpin MR pages with unpin_user_pages_dirty_lock() The pages backing an RDS memory region are pinned in __rds_rdma_map() with rds_pin_pages(), which uses pin_user_pages_fast(): each page's refcount is biased by GUP_PIN_COUNTING_BIAS to account the pin. The scatterlist is then handed to the IB transport, and the transport releases the pages in __rds_ib_teardown_mr() with set_page_dirty(page); put_page(page); put_page() drops a single reference instead of removing the pin bias, so every MR teardown permanently strands the remaining references and the pages are never freed - a userspace-triggerable memory leak of up to RDS_MAX_MSG_SIZE per RDS_GET_MR/RDS_GET_MR_FOR_DEST call. The conversion to the pin API updated the unpin sites in rdma.c but missed this one on the transport side. Release the pages with unpin_user_pages_dirty_lock(), which removes the pin bias and also dirties the page under the folio lock, closing the truncation race that a bare set_page_dirty() leaves open. Dirtying under the folio lock can sleep, which is safe in every path that reaches __rds_ib_teardown_mr(): the registration-reuse path (rds_ib_map_frmr()) runs in syscall context, and the pool flush (rds_ib_unreg_frmr()) runs under pool->flush_lock, a mutex, and already sleeps in rds_ib_post_inv(). The WARN_ON that guarded the old irq-context set_page_dirty() case is dropped along with it. Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260730041629.3512480-5-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/ib_rdma.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/net/rds/ib_rdma.c b/net/rds/ib_rdma.c index 9594ea245f7f..db7e92e7bd29 100644 --- a/net/rds/ib_rdma.c +++ b/net/rds/ib_rdma.c @@ -251,9 +251,7 @@ void __rds_ib_teardown_mr(struct rds_ib_mr *ibmr) /* FIXME we need a way to tell a r/w MR * from a r/o MR */ - WARN_ON(!page->mapping && irqs_disabled()); - set_page_dirty(page); - put_page(page); + unpin_user_pages_dirty_lock(&page, 1, true); } kfree(ibmr->sg); From 30ce0cb576b622f8fb5c5c9ebe43e28365374a3a Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Sat, 1 Aug 2026 11:25:05 +0530 Subject: [PATCH 0952/1433] net: microchip: vcap api: Fix possible memory leak in vcap_decode_rule() The memory allocated for struct vcap_rule_internal, keyfields and actionfields inside vcap_dup_rule() are not freed in some of the error paths in vcap_decode_rule(). Fix that by calling vcap_free_rule(). Compile tested only. Issue found using a prototype static analysis tool built on top of the LLVM compiler infrastructure. Cc: stable+noautosel@kernel.org # untested fix to unlikely driver error path Reviewed-by: Joe Damato Signed-off-by: Abdun Nihaal Link: https://patch.msgid.link/20260801055507.47534-1-nihaal@cse.iitm.ac.in Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microchip/vcap/vcap_api.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/microchip/vcap/vcap_api.c b/drivers/net/ethernet/microchip/vcap/vcap_api.c index ff86cde11a32..788c0728d763 100644 --- a/drivers/net/ethernet/microchip/vcap/vcap_api.c +++ b/drivers/net/ethernet/microchip/vcap/vcap_api.c @@ -2427,18 +2427,21 @@ struct vcap_rule *vcap_decode_rule(struct vcap_rule_internal *elem) err = vcap_read_rule(ri); if (err) - return ERR_PTR(err); + goto err_free_rule; err = vcap_decode_keyset(ri); if (err) - return ERR_PTR(err); + goto err_free_rule; err = vcap_decode_actionset(ri); if (err) - return ERR_PTR(err); + goto err_free_rule; out: return &ri->data; +err_free_rule: + vcap_free_rule(&ri->data); + return ERR_PTR(err); } struct vcap_rule *vcap_get_rule(struct vcap_control *vctrl, u32 id) From c0fd47726c9460fbdd1bf26cd0bfcd7dcce8f80b Mon Sep 17 00:00:00 2001 From: Minhong He Date: Fri, 31 Jul 2026 10:52:49 +0800 Subject: [PATCH 0953/1433] ipv4: nexthop: handle errors in nexthop_init() nexthop_init() ignores errors from register_pernet_subsys() and register_netdevice_notifier(), so a partial initialization can appear successful. Check those steps and unwind prior registrations on failure. Do not check rtnl_register_many(): for built-in code it panics on failure, so the call cannot return an error to nexthop_init(). Cc: stable+noautosel@kernel.org # untested fix to unlikely error path Signed-off-by: Minhong He Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260731025249.80026-1-heminhong@kylinos.cn Signed-off-by: Jakub Kicinski --- net/ipv4/nexthop.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/net/ipv4/nexthop.c b/net/ipv4/nexthop.c index af1dcb8ea427..a7c2b8dced4e 100644 --- a/net/ipv4/nexthop.c +++ b/net/ipv4/nexthop.c @@ -4219,12 +4219,22 @@ static const struct rtnl_msg_handler nexthop_rtnl_msg_handlers[] __initconst = { static int __init nexthop_init(void) { - register_pernet_subsys(&nexthop_net_ops); + int err; - register_netdevice_notifier(&nh_netdev_notifier); + err = register_pernet_subsys(&nexthop_net_ops); + if (err) + return err; + + err = register_netdevice_notifier(&nh_netdev_notifier); + if (err) + goto err_unregister_pernet; rtnl_register_many(nexthop_rtnl_msg_handlers); return 0; + +err_unregister_pernet: + unregister_pernet_subsys(&nexthop_net_ops); + return err; } subsys_initcall(nexthop_init); From d13bb65dd2dd0cbfeb8a7aeca9a4afd526eab3ee Mon Sep 17 00:00:00 2001 From: Minhong He Date: Fri, 31 Jul 2026 11:03:38 +0800 Subject: [PATCH 0954/1433] net: failover: check register_netdevice_notifier() error in failover_init() failover_init() ignores register_netdevice_notifier() errors and always returns success, which can leave the failover module loaded without its netdev notifier registered. Return the notifier registration result directly so module initialization fails when registration fails. This is a future looking check, register_netdevice_notifier() only fails on double registration or if the registered notifier itself returns an error. Signed-off-by: Minhong He Link: https://patch.msgid.link/20260731030338.82508-1-heminhong@kylinos.cn Signed-off-by: Jakub Kicinski --- net/core/failover.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/net/core/failover.c b/net/core/failover.c index e43c59cd6868..4c3894a02cb7 100644 --- a/net/core/failover.c +++ b/net/core/failover.c @@ -302,9 +302,7 @@ EXPORT_SYMBOL_GPL(failover_unregister); static __init int failover_init(void) { - register_netdevice_notifier(&failover_notifier); - - return 0; + return register_netdevice_notifier(&failover_notifier); } module_init(failover_init); From e43e9d7cabace0307685196114fc5d3ae6669120 Mon Sep 17 00:00:00 2001 From: Minhong He Date: Mon, 3 Aug 2026 16:59:36 +0800 Subject: [PATCH 0955/1433] net: lapbether: check register_netdevice_notifier() error in lapbeth_init_driver() lapbeth_init_driver() ignores register_netdevice_notifier() errors and always returns success, which can leave the module loaded without its netdev notifier registered. Check the error and remove the packet type on failure. This is a future looking check, register_netdevice_notifier() only fails on double registration or if the registered notifier itself returns an error. Signed-off-by: Minhong He Link: https://patch.msgid.link/20260803085936.142160-1-heminhong@kylinos.cn Signed-off-by: Jakub Kicinski --- drivers/net/wan/lapbether.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/net/wan/lapbether.c b/drivers/net/wan/lapbether.c index 9861c99ea56c..c3630a82913b 100644 --- a/drivers/net/wan/lapbether.c +++ b/drivers/net/wan/lapbether.c @@ -497,9 +497,15 @@ static const char banner[] __initconst = static int __init lapbeth_init_driver(void) { + int err; + dev_add_pack(&lapbeth_packet_type); - register_netdevice_notifier(&lapbeth_dev_notifier); + err = register_netdevice_notifier(&lapbeth_dev_notifier); + if (err) { + dev_remove_pack(&lapbeth_packet_type); + return err; + } printk(banner); From c1293e4a34e2db9f491f2509f812d5c999a2b002 Mon Sep 17 00:00:00 2001 From: Minhong He Date: Mon, 3 Aug 2026 16:59:43 +0800 Subject: [PATCH 0956/1433] net: team: check register_netdevice_notifier() error in team_module_init() team_module_init() ignores register_netdevice_notifier() errors and continues module initialization, which can leave the team module loaded without its netdev notifier registered. Check the error and fail module initialization early. This is a future looking check, register_netdevice_notifier() only fails on double registration or if the registered notifier itself returns an error. Signed-off-by: Minhong He Link: https://patch.msgid.link/20260803085943.142261-1-heminhong@kylinos.cn Signed-off-by: Jakub Kicinski --- drivers/net/team/team_core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/team/team_core.c b/drivers/net/team/team_core.c index feaa75fbf8fc..beffbe450612 100644 --- a/drivers/net/team/team_core.c +++ b/drivers/net/team/team_core.c @@ -3229,7 +3229,9 @@ static int __init team_module_init(void) { int err; - register_netdevice_notifier(&team_notifier_block); + err = register_netdevice_notifier(&team_notifier_block); + if (err) + return err; err = rtnl_link_register(&team_link_ops); if (err) From 82167f2f0fdec3ce2c418b2dd35408ba74b1f6e3 Mon Sep 17 00:00:00 2001 From: Minhong He Date: Mon, 3 Aug 2026 16:59:50 +0800 Subject: [PATCH 0957/1433] net: macvlan: check register_netdevice_notifier() error in macvlan_init_module() macvlan_init_module() ignores register_netdevice_notifier() errors and continues module initialization, which can leave macvlan loaded without its netdev notifier registered. Check the error and fail module initialization early. This is a future looking check, register_netdevice_notifier() only fails on double registration or if the registered notifier itself returns an error. Signed-off-by: Minhong He Link: https://patch.msgid.link/20260803085950.142325-1-heminhong@kylinos.cn Signed-off-by: Jakub Kicinski --- drivers/net/macvlan.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c index 42169f3614c4..8d71a832cb17 100644 --- a/drivers/net/macvlan.c +++ b/drivers/net/macvlan.c @@ -1915,7 +1915,9 @@ static int __init macvlan_init_module(void) { int err; - register_netdevice_notifier(&macvlan_notifier_block); + err = register_netdevice_notifier(&macvlan_notifier_block); + if (err) + return err; err = macvlan_link_register(&macvlan_link_ops); if (err < 0) From ac072a89cba261fc1f7dd31b633d15c460323699 Mon Sep 17 00:00:00 2001 From: Minhong He Date: Mon, 3 Aug 2026 17:00:02 +0800 Subject: [PATCH 0958/1433] net: vrf: check register_netdevice_notifier() error in vrf_init_module() vrf_init_module() ignores register_netdevice_notifier() errors and continues module initialization, which can leave VRF loaded without its netdev notifier registered. Check the error and fail module initialization early. This is a future looking check, register_netdevice_notifier() only fails on double registration or if the registered notifier itself returns an error. Signed-off-by: Minhong He Reviewed-by: David Ahern Link: https://patch.msgid.link/20260803090002.142453-1-heminhong@kylinos.cn Signed-off-by: Jakub Kicinski --- drivers/net/vrf.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/vrf.c b/drivers/net/vrf.c index 46209917ae4d..a0557a3a7026 100644 --- a/drivers/net/vrf.c +++ b/drivers/net/vrf.c @@ -1932,7 +1932,9 @@ static int __init vrf_init_module(void) { int rc; - register_netdevice_notifier(&vrf_notifier_block); + rc = register_netdevice_notifier(&vrf_notifier_block); + if (rc < 0) + return rc; rc = register_pernet_subsys(&vrf_net_ops); if (rc < 0) From b16bab325801c7be004222cad0ae296aa5982e79 Mon Sep 17 00:00:00 2001 From: Minhong He Date: Mon, 3 Aug 2026 17:00:12 +0800 Subject: [PATCH 0959/1433] net: bonding: check register_netdevice_notifier() error in bonding_init() bonding_init() ignores register_netdevice_notifier() errors and still returns success, which can leave the bonding module loaded without its netdev notifier registered. Check the error and unwind prior initialization on failure. This is a future looking check, register_netdevice_notifier() only fails on double registration or if the registered notifier itself returns an error. Signed-off-by: Minhong He Acked-by: Jay Vosburgh Link: https://patch.msgid.link/20260803090012.142638-1-heminhong@kylinos.cn Signed-off-by: Jakub Kicinski --- drivers/net/bonding/bond_main.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index a925577c8253..ef9eb0c53c66 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -6638,7 +6638,9 @@ static int __init bonding_init(void) flow_keys_bonding_keys, ARRAY_SIZE(flow_keys_bonding_keys)); - register_netdevice_notifier(&bond_netdev_notifier); + res = register_netdevice_notifier(&bond_netdev_notifier); + if (res) + goto err; out: return res; err: From d5d7b17b3f9b0fb5eb5a755a9b34d89a39813d50 Mon Sep 17 00:00:00 2001 From: Vikas Gupta Date: Fri, 31 Jul 2026 22:07:10 +0530 Subject: [PATCH 0960/1433] bnge: refactor rx mode helpers to accept explicit address lists Rename bnge_cfg_def_vnic() to bnge_cfg_rx_mode() and update bnge_mc_list_updated() and bnge_uc_list_updated() to accept explicit netdev_hw_addr_list pointers rather than deriving them from the netdev. Add a snapshot parameter to bnge_cfg_rx_mode() to skip netif_addr_lock_bh() when the caller provides a pre-snapshotted list. On the open path (snapshot=false), the live netdev UC list is passed and the addr lock is taken as before. Signed-off-by: Vikas Gupta Reviewed-by: Dharmender Garg Reviewed-by: Rahul Gupta Link: https://patch.msgid.link/20260731163712.3463362-2-vikas.gupta@broadcom.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/broadcom/bnge/bnge_netdev.c | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c index 6f7ef506d4e1..db0e616fc46c 100644 --- a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c +++ b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c @@ -2144,16 +2144,16 @@ static int bnge_hwrm_set_vnic_filter(struct bnge_net *bn, u16 vnic_id, u16 idx, return rc; } -static bool bnge_mc_list_updated(struct bnge_net *bn, u32 *rx_mask) +static bool bnge_mc_list_updated(struct bnge_net *bn, u32 *rx_mask, + const struct netdev_hw_addr_list *mc) { struct bnge_vnic_info *vnic = &bn->vnic_info[BNGE_VNIC_DEFAULT]; - struct net_device *dev = bn->netdev; struct netdev_hw_addr *ha; int mc_count = 0, off = 0; bool update = false; u8 *haddr; - netdev_for_each_mc_addr(ha, dev) { + netdev_hw_addr_list_for_each(ha, mc) { if (mc_count >= BNGE_MAX_MC_ADDRS) { *rx_mask |= CFA_L2_SET_RX_MASK_REQ_MASK_ALL_MCAST; vnic->mc_list_count = 0; @@ -2177,17 +2177,17 @@ static bool bnge_mc_list_updated(struct bnge_net *bn, u32 *rx_mask) return update; } -static bool bnge_uc_list_updated(struct bnge_net *bn) +static bool bnge_uc_list_updated(struct bnge_net *bn, + const struct netdev_hw_addr_list *uc) { struct bnge_vnic_info *vnic = &bn->vnic_info[BNGE_VNIC_DEFAULT]; - struct net_device *dev = bn->netdev; struct netdev_hw_addr *ha; int off = 0; - if (netdev_uc_count(dev) != (vnic->uc_filter_count - 1)) + if (netdev_hw_addr_list_count(uc) != (vnic->uc_filter_count - 1)) return true; - netdev_for_each_uc_addr(ha, dev) { + netdev_hw_addr_list_for_each(ha, uc) { if (!ether_addr_equal(ha->addr, vnic->uc_list + off)) return true; @@ -2201,7 +2201,8 @@ static bool bnge_promisc_ok(struct bnge_net *bn) return true; } -static int bnge_cfg_def_vnic(struct bnge_net *bn) +static int bnge_cfg_rx_mode(struct bnge_net *bn, struct netdev_hw_addr_list *uc, + bool snapshot) { struct bnge_vnic_info *vnic = &bn->vnic_info[BNGE_VNIC_DEFAULT]; struct net_device *dev = bn->netdev; @@ -2211,7 +2212,7 @@ static int bnge_cfg_def_vnic(struct bnge_net *bn) bool uc_update; netif_addr_lock_bh(dev); - uc_update = bnge_uc_list_updated(bn); + uc_update = bnge_uc_list_updated(bn, uc); netif_addr_unlock_bh(dev); if (!uc_update) @@ -2226,22 +2227,28 @@ static int bnge_cfg_def_vnic(struct bnge_net *bn) vnic->uc_filter_count = 1; - netif_addr_lock_bh(dev); - if (netdev_uc_count(dev) > (BNGE_MAX_UC_ADDRS - 1)) { + if (!snapshot) + netif_addr_lock_bh(dev); + if (netdev_hw_addr_list_count(uc) > (BNGE_MAX_UC_ADDRS - 1)) { vnic->rx_mask |= CFA_L2_SET_RX_MASK_REQ_MASK_PROMISCUOUS; } else { - netdev_for_each_uc_addr(ha, dev) { + netdev_hw_addr_list_for_each(ha, uc) { memcpy(vnic->uc_list + off, ha->addr, ETH_ALEN); off += ETH_ALEN; vnic->uc_filter_count++; } } - netif_addr_unlock_bh(dev); + if (!snapshot) + netif_addr_unlock_bh(dev); for (i = 1, off = 0; i < vnic->uc_filter_count; i++, off += ETH_ALEN) { rc = bnge_hwrm_set_vnic_filter(bn, 0, i, vnic->uc_list + off); if (rc) { - netdev_err(dev, "HWRM vnic filter failure rc: %d\n", rc); + if (rc == -EAGAIN) + netdev_warn(dev, "FW busy while setting vnic filter, will retry\n"); + else + netdev_err(dev, "HWRM vnic filter failure rc: %d\n", + rc); vnic->uc_filter_count = i; return rc; } @@ -2695,11 +2702,11 @@ static int bnge_init_chip(struct bnge_net *bn) } else if (bn->netdev->flags & IFF_MULTICAST) { u32 mask = 0; - bnge_mc_list_updated(bn, &mask); + bnge_mc_list_updated(bn, &mask, &bn->netdev->mc); vnic->rx_mask |= mask; } - rc = bnge_cfg_def_vnic(bn); + rc = bnge_cfg_rx_mode(bn, &bn->netdev->uc, false); if (rc) goto err_out; return 0; From fbe3647fd464e8b4051543c5b16368e877c2d37c Mon Sep 17 00:00:00 2001 From: Vikas Gupta Date: Fri, 31 Jul 2026 22:07:11 +0530 Subject: [PATCH 0961/1433] bnge: add ndo_set_rx_mode_async support Register bnge_set_rx_mode() as ndo_set_rx_mode_async to handle unicast, multicast, broadcast, and promiscuous filter updates via CFA_L2_SET_RX_MASK. The async variant receives pre-snapshotted address lists from the kernel, allowing the driver to issue sleepable HWRM firmware commands without holding the addr lock. Move uc_update detection to the caller so the async path can compute it directly from the snapshotted UC list before calling bnge_cfg_rx_mode(). Handle -EAGAIN from bnge_hwrm_set_vnic_filter() and bnge_hwrm_cfa_l2_set_rx_mask() on the open path by scheduling a retry via netif_rx_mode_schedule_retry() rather than failing the open. Signed-off-by: Vikas Gupta Reviewed-by: Dharmender Garg Reviewed-by: Rahul Gupta Link: https://patch.msgid.link/20260731163712.3463362-3-vikas.gupta@broadcom.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/broadcom/bnge/bnge_netdev.c | 67 ++++++++++++++++--- .../net/ethernet/broadcom/bnge/bnge_netdev.h | 6 ++ 2 files changed, 62 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c index db0e616fc46c..d47eb9bc5b8d 100644 --- a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c +++ b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c @@ -2202,18 +2202,13 @@ static bool bnge_promisc_ok(struct bnge_net *bn) } static int bnge_cfg_rx_mode(struct bnge_net *bn, struct netdev_hw_addr_list *uc, - bool snapshot) + bool uc_update, bool snapshot) { struct bnge_vnic_info *vnic = &bn->vnic_info[BNGE_VNIC_DEFAULT]; struct net_device *dev = bn->netdev; struct bnge_dev *bd = bn->bd; struct netdev_hw_addr *ha; int i, off = 0, rc; - bool uc_update; - - netif_addr_lock_bh(dev); - uc_update = bnge_uc_list_updated(bn, uc); - netif_addr_unlock_bh(dev); if (!uc_update) goto skip_uc; @@ -2267,13 +2262,58 @@ static int bnge_cfg_rx_mode(struct bnge_net *bn, struct netdev_hw_addr_list *uc, vnic->mc_list_count = 0; rc = bnge_hwrm_cfa_l2_set_rx_mask(bd, vnic); } - if (rc) - netdev_err(dev, "HWRM cfa l2 rx mask failure rc: %d\n", - rc); + if (rc) { + if (rc == -EAGAIN) { + netdev_warn(dev, "FW busy while setting l2 rx mask in CFA, will retry\n"); + vnic->rx_mask &= ~BNGE_RX_MASK_CFG_FLAGS; + } else { + netdev_err(dev, "HWRM CFA L2 rx mask failure rc: %d\n", + rc); + } + } return rc; } +static int bnge_set_rx_mode(struct net_device *dev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) +{ + struct bnge_net *bn = netdev_priv(dev); + struct bnge_vnic_info *vnic; + bool mc_update = false; + bool uc_update; + u32 mask; + + if (!test_bit(BNGE_STATE_OPEN, &bn->bd->state)) + return 0; + + vnic = &bn->vnic_info[BNGE_VNIC_DEFAULT]; + mask = vnic->rx_mask; + mask &= ~BNGE_RX_MASK_CFG_FLAGS; + + if (dev->flags & IFF_PROMISC) + mask |= CFA_L2_SET_RX_MASK_REQ_MASK_PROMISCUOUS; + + uc_update = bnge_uc_list_updated(bn, uc); + + if (dev->flags & IFF_BROADCAST) + mask |= CFA_L2_SET_RX_MASK_REQ_MASK_BCAST; + if (dev->flags & IFF_ALLMULTI) { + mask |= CFA_L2_SET_RX_MASK_REQ_MASK_ALL_MCAST; + vnic->mc_list_count = 0; + } else if (dev->flags & IFF_MULTICAST) { + mc_update = bnge_mc_list_updated(bn, &mask, mc); + } + + if (mask != vnic->rx_mask || uc_update || mc_update) { + vnic->rx_mask = mask; + return bnge_cfg_rx_mode(bn, uc, uc_update, true); + } + + return 0; +} + static void bnge_disable_int(struct bnge_net *bn) { struct bnge_dev *bd = bn->bd; @@ -2706,9 +2746,13 @@ static int bnge_init_chip(struct bnge_net *bn) vnic->rx_mask |= mask; } - rc = bnge_cfg_rx_mode(bn, &bn->netdev->uc, false); - if (rc) + rc = bnge_cfg_rx_mode(bn, &bn->netdev->uc, true, false); + if (rc == -EAGAIN) { + netif_rx_mode_schedule_retry(bn->netdev); + rc = 0; + } else if (rc) { goto err_out; + } return 0; err_out: @@ -3202,6 +3246,7 @@ static const struct net_device_ops bnge_netdev_ops = { .ndo_stop = bnge_close, .ndo_start_xmit = bnge_start_xmit, .ndo_get_stats64 = bnge_get_stats64, + .ndo_set_rx_mode_async = bnge_set_rx_mode, .ndo_features_check = bnge_features_check, }; diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.h b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.h index d177919c2e11..476b5bab96fe 100644 --- a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.h +++ b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.h @@ -561,6 +561,12 @@ struct bnge_napi { #define BNGE_VNIC_DEFAULT 0 #define BNGE_MAX_UC_ADDRS 4 +#define BNGE_RX_MASK_CFG_FLAGS \ + (CFA_L2_SET_RX_MASK_REQ_MASK_PROMISCUOUS | \ + CFA_L2_SET_RX_MASK_REQ_MASK_MCAST | \ + CFA_L2_SET_RX_MASK_REQ_MASK_ALL_MCAST | \ + CFA_L2_SET_RX_MASK_REQ_MASK_BCAST) + struct bnge_vnic_info { u16 fw_vnic_id; #define BNGE_MAX_CTX_PER_VNIC 8 From 1b1e855e4306e227b610c5c2c7d475281a912f29 Mon Sep 17 00:00:00 2001 From: Vikas Gupta Date: Fri, 31 Jul 2026 22:07:12 +0530 Subject: [PATCH 0962/1433] bnge: send hwrm for interface down/up transitions Firmware expects HWRM_FUNC_DRV_IF_CHANGE on interface down/up transitions to coordinate resource management. Add bnge_hwrm_if_change() to send this notification. Signed-off-by: Vikas Gupta Reviewed-by: Dharmender Garg Reviewed-by: Rahul Gupta Link: https://patch.msgid.link/20260731163712.3463362-4-vikas.gupta@broadcom.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/broadcom/bnge/bnge_netdev.c | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c index d47eb9bc5b8d..c2e865f8d9c4 100644 --- a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c +++ b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c @@ -20,6 +20,7 @@ #include #include "bnge.h" +#include "bnge_hwrm.h" #include "bnge_hwrm_lib.h" #include "bnge_ethtool.h" #include "bnge_rmem.h" @@ -2864,6 +2865,24 @@ static void bnge_tx_enable(struct bnge_net *bn) netif_carrier_on(bn->netdev); } +static int bnge_hwrm_if_change(struct bnge_dev *bd, bool up) +{ + struct hwrm_func_drv_if_change_input *req; + int rc; + + if (!(bd->fw_cap & BNGE_FW_CAP_IF_CHANGE)) + return 0; + + rc = bnge_hwrm_req_init(bd, req, HWRM_FUNC_DRV_IF_CHANGE); + if (rc) + return rc; + + if (up) + req->flags = cpu_to_le32(FUNC_DRV_IF_CHANGE_REQ_FLAGS_UP); + + return bnge_hwrm_req_send(bd, req); +} + static int bnge_open_core(struct bnge_net *bn) { struct bnge_dev *bd = bn->bd; @@ -2871,16 +2890,22 @@ static int bnge_open_core(struct bnge_net *bn) netif_carrier_off(bn->netdev); + rc = bnge_hwrm_if_change(bd, true); + if (rc) { + netdev_err(bn->netdev, "bnge_hwrm_if_change err: %d\n", rc); + return rc; + } + rc = bnge_reserve_rings(bd); if (rc) { netdev_err(bn->netdev, "bnge_reserve_rings err: %d\n", rc); - return rc; + goto err_if_change; } rc = bnge_alloc_core(bn); if (rc) { netdev_err(bn->netdev, "bnge_alloc_core err: %d\n", rc); - return rc; + goto err_if_change; } bnge_init_napi(bn); @@ -2927,6 +2952,8 @@ static int bnge_open_core(struct bnge_net *bn) err_del_napi: bnge_del_napi(bn); bnge_free_core(bn); +err_if_change: + bnge_hwrm_if_change(bd, false); return rc; } @@ -3157,6 +3184,7 @@ static int bnge_close(struct net_device *dev) bnge_close_core(bn); bnge_hwrm_shutdown_link(bn->bd); + bnge_hwrm_if_change(bn->bd, false); bn->sp_event = 0; return 0; From 085a4112165bf311f487fe07c5e801e44aff5999 Mon Sep 17 00:00:00 2001 From: Minhong He Date: Fri, 31 Jul 2026 10:33:33 +0800 Subject: [PATCH 0963/1433] batman-adv: handle errors in batadv_init() batadv_init() ignores errors from several initialization helpers, so the module can load without those registrations in place. Check the fallible init steps and unwind prior initialization in reverse order of acquisition on failure. Signed-off-by: Minhong He Signed-off-by: Sven Eckelmann --- net/batman-adv/bat_iv_ogm.c | 8 +++++++ net/batman-adv/bat_iv_ogm.h | 1 + net/batman-adv/bat_v.c | 9 ++++++++ net/batman-adv/bat_v.h | 5 +++++ net/batman-adv/main.c | 45 ++++++++++++++++++++++++++++--------- net/batman-adv/netlink.c | 6 ++++- net/batman-adv/netlink.h | 2 +- 7 files changed, 63 insertions(+), 13 deletions(-) diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c index aff279b83da4..53fbdbbe8f4f 100644 --- a/net/batman-adv/bat_iv_ogm.c +++ b/net/batman-adv/bat_iv_ogm.c @@ -2802,3 +2802,11 @@ int __init batadv_iv_init(void) out: return ret; } + +/** + * batadv_iv_deinit() - B.A.T.M.A.N. IV deinitialization function + */ +void batadv_iv_deinit(void) +{ + batadv_recv_handler_unregister(BATADV_IV_OGM); +} diff --git a/net/batman-adv/bat_iv_ogm.h b/net/batman-adv/bat_iv_ogm.h index 04b01bd684e8..90318801aeaf 100644 --- a/net/batman-adv/bat_iv_ogm.h +++ b/net/batman-adv/bat_iv_ogm.h @@ -10,5 +10,6 @@ #include "main.h" int batadv_iv_init(void); +void batadv_iv_deinit(void); #endif /* _NET_BATMAN_ADV_BAT_IV_OGM_H_ */ diff --git a/net/batman-adv/bat_v.c b/net/batman-adv/bat_v.c index ee372fc9d44e..0c27447cf688 100644 --- a/net/batman-adv/bat_v.c +++ b/net/batman-adv/bat_v.c @@ -947,3 +947,12 @@ int __init batadv_v_init(void) return ret; } + +/** + * batadv_v_deinit() - B.A.T.M.A.N. V deinitialization function + */ +void batadv_v_deinit(void) +{ + batadv_recv_handler_unregister(BATADV_OGM2); + batadv_recv_handler_unregister(BATADV_ELP); +} diff --git a/net/batman-adv/bat_v.h b/net/batman-adv/bat_v.h index 964431f4dc8d..76bd969e79a2 100644 --- a/net/batman-adv/bat_v.h +++ b/net/batman-adv/bat_v.h @@ -12,6 +12,7 @@ #ifdef CONFIG_BATMAN_ADV_BATMAN_V int batadv_v_init(void); +void batadv_v_deinit(void); void batadv_v_hardif_init(struct batadv_hard_iface *hardif); int batadv_v_mesh_init(struct batadv_priv *bat_priv); void batadv_v_mesh_free(struct batadv_priv *bat_priv); @@ -23,6 +24,10 @@ static inline int batadv_v_init(void) return 0; } +static inline void batadv_v_deinit(void) +{ +} + static inline void batadv_v_hardif_init(struct batadv_hard_iface *hardif) { } diff --git a/net/batman-adv/main.c b/net/batman-adv/main.c index c2d5b39b02f4..77597171d637 100644 --- a/net/batman-adv/main.c +++ b/net/batman-adv/main.c @@ -102,35 +102,58 @@ static int __init batadv_init(void) batadv_recv_handler_init(); - batadv_v_init(); - batadv_iv_init(); + ret = batadv_v_init(); + if (ret < 0) + goto err_tt; + + ret = batadv_iv_init(); + if (ret < 0) + goto err_v; + batadv_tp_meter_init(); + ret = batadv_wifi_net_devices_init(); + if (ret < 0) + goto err_iv; + batadv_event_workqueue = create_singlethread_workqueue("bat_events"); if (!batadv_event_workqueue) { ret = -ENOMEM; - goto err_create_wq; + goto err_wifi; } - ret = batadv_wifi_net_devices_init(); + ret = register_netdevice_notifier(&batadv_hard_if_notifier); if (ret < 0) - goto err_init_wifi; + goto err_wq; - register_netdevice_notifier(&batadv_hard_if_notifier); - rtnl_link_register(&batadv_link_ops); - batadv_netlink_register(); + ret = rtnl_link_register(&batadv_link_ops); + if (ret < 0) + goto err_notifier; + + ret = batadv_netlink_register(); + if (ret < 0) + goto err_rtnl; pr_info("B.A.T.M.A.N. advanced %s (compatibility version %i) loaded\n", init_utsname()->release, BATADV_COMPAT_VERSION); return 0; -err_init_wifi: +err_rtnl: + rtnl_link_unregister(&batadv_link_ops); +err_notifier: + unregister_netdevice_notifier(&batadv_hard_if_notifier); +err_wq: destroy_workqueue(batadv_event_workqueue); batadv_event_workqueue = NULL; rcu_barrier(); - -err_create_wq: +err_wifi: + batadv_wifi_net_devices_deinit(); +err_iv: + batadv_iv_deinit(); +err_v: + batadv_v_deinit(); +err_tt: batadv_tt_cache_destroy(); return ret; diff --git a/net/batman-adv/netlink.c b/net/batman-adv/netlink.c index 926210a67d64..107647c9bada 100644 --- a/net/batman-adv/netlink.c +++ b/net/batman-adv/netlink.c @@ -1556,14 +1556,18 @@ struct genl_family batadv_netlink_family __ro_after_init = { /** * batadv_netlink_register() - register batadv genl netlink family + * + * Return: 0 on success or negative error number in case of failure */ -void __init batadv_netlink_register(void) +int __init batadv_netlink_register(void) { int ret; ret = genl_register_family(&batadv_netlink_family); if (ret) pr_warn("unable to register netlink family\n"); + + return ret; } /** diff --git a/net/batman-adv/netlink.h b/net/batman-adv/netlink.h index 4eae9e5ff135..f92d3ea7d06c 100644 --- a/net/batman-adv/netlink.h +++ b/net/batman-adv/netlink.h @@ -12,7 +12,7 @@ #include #include -void batadv_netlink_register(void); +int batadv_netlink_register(void); void batadv_netlink_unregister(void); struct net_device *batadv_netlink_get_meshif(struct netlink_callback *cb); struct batadv_hard_iface * From 1c852e4a99c732dc7e93a0aee195d10eba1f6339 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 30 Jul 2026 08:43:53 +0200 Subject: [PATCH 0964/1433] batman-adv: correct NET_RX_* NET_XMIT_* confusion batadv_recv_icmp_ttl_exceeded() is a receive function. It must therefore return NET_RX_* and not NET_XMIT_*. And batadv_send_skb_to_orig() is an xmit function and is returning NET_XMIT_*. This doesn't change the behavior because both NET_RX_SUCCESS and NET_RX_SUCCESS are using the same underlying value (0). Signed-off-by: Sven Eckelmann --- net/batman-adv/routing.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c index af0543a4d346..6442f5d0cc93 100644 --- a/net/batman-adv/routing.c +++ b/net/batman-adv/routing.c @@ -341,7 +341,7 @@ static int batadv_recv_my_icmp_packet(struct batadv_priv *bat_priv, * For traceroute-style ICMP echo requests, send a TTL exceeded reply back to * the source. Other ICMP types are simply dropped. * - * Return: NET_XMIT_SUCCESS if the reply was queued, NET_RX_DROP otherwise + * Return: NET_RX_SUCCESS if the reply was queued, NET_RX_DROP otherwise */ static int batadv_recv_icmp_ttl_exceeded(struct batadv_priv *bat_priv, struct sk_buff *skb) @@ -382,8 +382,8 @@ static int batadv_recv_icmp_ttl_exceeded(struct batadv_priv *bat_priv, icmp_packet->ttl = BATADV_TTL; res = batadv_send_skb_to_orig(skb, orig_node, NULL); - if (res == NET_RX_SUCCESS) - ret = NET_XMIT_SUCCESS; + if (res == NET_XMIT_SUCCESS) + ret = NET_RX_SUCCESS; /* skb was consumed */ skb = NULL; From 02aee8ebea3a714d92b27da9a9d8791d8c8c9a4f Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 30 Jul 2026 08:23:28 +0200 Subject: [PATCH 0965/1433] batman-adv: remove negative returns for batadv_send_skb_unicast The kernel documentation for batadv_send_skb_unicast() states that only the return values NET_XMIT_DROP and NET_XMIT_SUCCESS are valid. Functions like batadv_dat_snoop_incoming_arp_request() are only checking if the return is not NET_XMIT_DROP to check if send was successful or not. Negative values were therefore also handled as success. Similar functions are not returning the batadv_send_skb_to_orig() return value directly but are checking if it is a direct success and only then marking the return as such. This must also be adopted for batadv_send_skb_unicast(). The callers of this function are mostly not affected. Only packet counting in batadv_dat_snoop_incoming_arp_request() will now work as expected in case of a negative return value from batadv_send_skb_to_orig(). Signed-off-by: Sven Eckelmann --- net/batman-adv/send.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/batman-adv/send.c b/net/batman-adv/send.c index 2122560c90e5..929b6dd34c10 100644 --- a/net/batman-adv/send.c +++ b/net/batman-adv/send.c @@ -324,6 +324,7 @@ int batadv_send_skb_unicast(struct batadv_priv *bat_priv, struct batadv_unicast_packet *unicast_packet; int ret = NET_XMIT_DROP; struct ethhdr *ethhdr; + int res; if (!orig_node) goto out; @@ -360,7 +361,10 @@ int batadv_send_skb_unicast(struct batadv_priv *bat_priv, if (batadv_tt_global_client_is_roaming(bat_priv, ethhdr->h_dest, vid)) unicast_packet->ttvn = unicast_packet->ttvn - 1; - ret = batadv_send_skb_to_orig(skb, orig_node, NULL); + res = batadv_send_skb_to_orig(skb, orig_node, NULL); + if (res == NET_XMIT_SUCCESS) + ret = NET_XMIT_SUCCESS; + /* skb was consumed */ skb = NULL; From cb59bfd419d0c560b9f7273522f9a4799062aad5 Mon Sep 17 00:00:00 2001 From: Shay Drory Date: Mon, 3 Aug 2026 12:00:12 +0300 Subject: [PATCH 0966/1433] devlink: Expose external flag for PCI SF ports The external flag is part of the PCI SF port attributes, but unlike the PCI PF and PCI VF flavours it was never filled into the port dump, so userspace could not query it directly. Reporting of the external flag was missed for SF ports. Hence, put DEVLINK_ATTR_PORT_EXTERNAL for the PCI SF flavour as well, matching what PCI PF and PCI VF ports already report. $ devlink port show pci/0033:01:00.0/163840 pci/0033:01:00.0/163840: type eth netdev eth1 flavour pcisf controller 1 pfnum 0 sfnum 77 external true splittable false Reviewed-by: Parav Pandit Signed-off-by: Shay Drory Link: https://patch.msgid.link/20260803090012.257242-1-shayd@nvidia.com Signed-off-by: Jakub Kicinski --- net/devlink/port.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/devlink/port.c b/net/devlink/port.c index dc82cac68e7d..1528f2d148df 100644 --- a/net/devlink/port.c +++ b/net/devlink/port.c @@ -267,6 +267,8 @@ static int devlink_nl_port_attrs_put(struct sk_buff *msg, nla_put_u32(msg, DEVLINK_ATTR_PORT_PCI_SF_NUMBER, attrs->pci_sf.sf)) return -EMSGSIZE; + if (nla_put_u8(msg, DEVLINK_ATTR_PORT_EXTERNAL, attrs->pci_sf.external)) + return -EMSGSIZE; break; case DEVLINK_PORT_FLAVOUR_PHYSICAL: case DEVLINK_PORT_FLAVOUR_CPU: From 481e86a82219369ad19898013b11f47aeaee7ab9 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 4 Aug 2026 08:10:40 -0700 Subject: [PATCH 0967/1433] selftests: drv-net: hw: reset HDS mode after netkit devmem tests HDS mode has confusing semantics. On GET kernel reports effective mode. On SET kernel expects explicit config. Effective mode on GET means that we know the current state, but we don't know if it's a driver default or user setting. This matter because driver default can change automatically when e.g. XDP is attached. Explicit user setting must not be lost. With that in mind, we can't restore the HDS setting like we restore other NIC config. We should always reset to default ("unknown"). This fixes an issue with tests running after the devmem test not being able to attach XDP, e.g. Exception| File "./xdp_metadata.py", line 105, in test_xdp_rss_hash [...] Exception| net.lib.py.utils.CmdExitFailure: Command failed Exception| CMD: ip link set dev ens9np0 xdpdrv pinned /sys/fs/bpf/xdp_metadata_test/xdp_rss_hash Exception| EXIT: 2 Exception| STDERR: Error: unable to install XDP to device using tcp-data-split. not ok 1 xdp_metadata.test_xdp_rss_hash.tcp Reviewed-by: Simon Horman Reviewed-by: Breno Leitao Reviewed-by: Bobby Eshleman Link: https://patch.msgid.link/20260804151040.2755153-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/hw/devmem_lib.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/testing/selftests/drivers/net/hw/devmem_lib.py b/tools/testing/selftests/drivers/net/hw/devmem_lib.py index 0921ff03eb81..4e6316c7de96 100644 --- a/tools/testing/selftests/drivers/net/hw/devmem_lib.py +++ b/tools/testing/selftests/drivers/net/hw/devmem_lib.py @@ -37,14 +37,13 @@ def configure_nic(cfg): rings = ethnl.rings_get({'header': {'dev-index': cfg.ifindex}}) orig_rx_rings = rings['rx'] orig_hds_thresh = rings.get('hds-thresh', 0) - orig_data_split = rings.get('tcp-data-split', 'unknown') ethnl.rings_set({'header': {'dev-index': cfg.ifindex}, 'tcp-data-split': 'enabled', 'hds-thresh': 0, 'rx': min(64, orig_rx_rings)}) defer(ethnl.rings_set, {'header': {'dev-index': cfg.ifindex}, - 'tcp-data-split': orig_data_split, + 'tcp-data-split': 'unknown', 'hds-thresh': orig_hds_thresh, 'rx': orig_rx_rings}) From 8980f3363128a7179f2c289c95449bdb0f95bf76 Mon Sep 17 00:00:00 2001 From: Vineeth Karumanchi Date: Mon, 3 Aug 2026 11:58:34 +0530 Subject: [PATCH 0968/1433] net: macb: remove unused ENST Q0/Q1 time register defines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ENST start/on/off time register definitions for Q0 and Q1 are not referenced anywhere in the driver. The driver calculates these register addresses from the ENST base offset and the queue index instead of using fixed defines, removing the unused macros. Signed-off-by: Vineeth Karumanchi Reviewed-by: Théo Lebrun Reviewed-by: Nicolai Buchwitz Reviewed-by: Breno Leitao Link: https://patch.msgid.link/20260803062834.3865755-1-vineeth.karumanchi@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb.h b/drivers/net/ethernet/cadence/macb.h index 2de56017ee0d..a11052565436 100644 --- a/drivers/net/ethernet/cadence/macb.h +++ b/drivers/net/ethernet/cadence/macb.h @@ -184,12 +184,6 @@ #define GEM_DCFG8 0x029C /* Design Config 8 */ #define GEM_DCFG10 0x02A4 /* Design Config 10 */ #define GEM_DCFG12 0x02AC /* Design Config 12 */ -#define GEM_ENST_START_TIME_Q0 0x0800 /* ENST Q0 start time */ -#define GEM_ENST_START_TIME_Q1 0x0804 /* ENST Q1 start time */ -#define GEM_ENST_ON_TIME_Q0 0x0820 /* ENST Q0 on time */ -#define GEM_ENST_ON_TIME_Q1 0x0824 /* ENST Q1 on time */ -#define GEM_ENST_OFF_TIME_Q0 0x0840 /* ENST Q0 off time */ -#define GEM_ENST_OFF_TIME_Q1 0x0844 /* ENST Q1 off time */ #define GEM_ENST_CONTROL 0x0880 /* ENST control register */ #define GEM_USX_CONTROL 0x0A80 /* High speed PCS control register */ #define GEM_USX_STATUS 0x0A88 /* High speed PCS status register */ From 376608d9210ab81225bd0f30e113d303bf8dbc77 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sat, 1 Aug 2026 21:09:51 +0200 Subject: [PATCH 0969/1433] qlcnic: drop unneeded semicolon When a function-like macro expands to an expression, that expression doesn't need a semicolon after it. All uses have been verified to have their own semicolons. This was found using the following Coccinelle semantic patch: @r@ identifier i : script:ocaml() { String.lowercase_ascii i = i }; expression e; @@ *#define i(...) e; Signed-off-by: Julia Lawall Link: https://patch.msgid.link/20260801191002.1383835-5-Julia.Lawall@inria.fr Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/qlogic/qlcnic/qlcnic_io.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_io.c b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_io.c index 537fd26da904..761ef3bc8193 100644 --- a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_io.c +++ b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_io.c @@ -29,7 +29,7 @@ #define QLCNIC_FLAGS_VLAN_OOB 0x40 #define qlcnic_set_tx_vlan_tci(cmd_desc, v) \ - (cmd_desc)->vlan_TCI = cpu_to_le16(v); + (cmd_desc)->vlan_TCI = cpu_to_le16(v) #define qlcnic_set_cmd_desc_port(cmd_desc, var) \ ((cmd_desc)->port_ctxid |= ((var) & 0x0F)) #define qlcnic_set_cmd_desc_ctxid(cmd_desc, var) \ From ced18ba79509c4408f961b48949a2c2e0ad55c58 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sat, 1 Aug 2026 21:09:56 +0200 Subject: [PATCH 0970/1433] netlink: drop unneeded semicolon When a function-like macro expands to an expression, that expression doesn't need a semicolon after it. All uses have been verified to have their own semicolons. This was found using the following Coccinelle semantic patch: @r@ identifier i : script:ocaml() { String.lowercase_ascii i = i }; expression e; @@ *#define i(...) e; Signed-off-by: Julia Lawall Link: https://patch.msgid.link/20260801191002.1383835-10-Julia.Lawall@inria.fr Signed-off-by: Jakub Kicinski --- net/netlink/af_netlink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c index 5202fe0b0867..e6b1d9758c9c 100644 --- a/net/netlink/af_netlink.c +++ b/net/netlink/af_netlink.c @@ -145,7 +145,7 @@ DEFINE_RWLOCK(nl_table_lock); EXPORT_SYMBOL_GPL(nl_table_lock); static atomic_t nl_table_users = ATOMIC_INIT(0); -#define nl_deref_protected(X) rcu_dereference_protected(X, lockdep_is_held(&nl_table_lock)); +#define nl_deref_protected(X) rcu_dereference_protected(X, lockdep_is_held(&nl_table_lock)) static BLOCKING_NOTIFIER_HEAD(netlink_chain); From f4418e64e61a54dd473e9a1208e1f88c871a719e Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 31 Jul 2026 22:43:40 +0000 Subject: [PATCH 0971/1433] pfcp: Protect pfcp_net.pfcp_dev_list with mutex. struct pfcp_dev.net is the netns where the backend pfcp socket resides. struct pfcp_dev is linked to the pfcp_net.pfcp_dev_list of the socket's netns. During netns dismantle or module unload, pfcp_net_exit_rtnl() iterates the list and queues devices for destruction regardless of the devices' netns. Thus, once RTNL is removed, the list can be modified concurrently from different netns due to device removal. Let's protect it with per-netns mutex. Signed-off-by: Kuniyuki Iwashima Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260731224406.2444121-2-kuniyu@google.com Signed-off-by: Jakub Kicinski --- drivers/net/pfcp.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/drivers/net/pfcp.c b/drivers/net/pfcp.c index d8e4d60f5834..45f9c5d3cb20 100644 --- a/drivers/net/pfcp.c +++ b/drivers/net/pfcp.c @@ -29,6 +29,7 @@ static unsigned int pfcp_net_id __read_mostly; struct pfcp_net { struct list_head pfcp_dev_list; + struct mutex lock; }; static void @@ -209,7 +210,10 @@ static int pfcp_newlink(struct net_device *dev, } pn = net_generic(link_net, pfcp_net_id); + + mutex_lock(&pn->lock); list_add(&pfcp->list, &pn->pfcp_dev_list); + mutex_unlock(&pn->lock); netdev_dbg(dev, "registered new PFCP interface\n"); @@ -223,7 +227,7 @@ static int pfcp_newlink(struct net_device *dev, return err; } -static void pfcp_dellink(struct net_device *dev, struct list_head *head) +static void __pfcp_dellink(struct net_device *dev, struct list_head *head) { struct pfcp_dev *pfcp = netdev_priv(dev); @@ -231,6 +235,18 @@ static void pfcp_dellink(struct net_device *dev, struct list_head *head) unregister_netdevice_queue(dev, head); } +static void pfcp_dellink(struct net_device *dev, struct list_head *head) +{ + struct pfcp_dev *pfcp = netdev_priv(dev); + struct pfcp_net *pn; + + pn = net_generic(pfcp->net, pfcp_net_id); + + mutex_lock(&pn->lock); + __pfcp_dellink(dev, head); + mutex_unlock(&pn->lock); +} + static struct rtnl_link_ops pfcp_link_ops __read_mostly = { .kind = "pfcp", .priv_size = sizeof(struct pfcp_dev), @@ -244,6 +260,8 @@ static int __net_init pfcp_net_init(struct net *net) struct pfcp_net *pn = net_generic(net, pfcp_net_id); INIT_LIST_HEAD(&pn->pfcp_dev_list); + mutex_init(&pn->lock); + return 0; } @@ -253,8 +271,12 @@ static void __net_exit pfcp_net_exit_rtnl(struct net *net, struct pfcp_net *pn = net_generic(net, pfcp_net_id); struct pfcp_dev *pfcp, *pfcp_next; + mutex_lock(&pn->lock); + list_for_each_entry_safe(pfcp, pfcp_next, &pn->pfcp_dev_list, list) - pfcp_dellink(pfcp->dev, dev_to_kill); + __pfcp_dellink(pfcp->dev, dev_to_kill); + + mutex_unlock(&pn->lock); } static struct pernet_operations pfcp_net_ops = { From 23aff4ed78c248d0e86966606c465d5505469eb5 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 31 Jul 2026 22:43:41 +0000 Subject: [PATCH 0972/1433] pfcp: Support per-netns netdev unregistration. pfcp_net_exit_rtnl() iterates pfcp devices whose sockets are in the dying netns and queues them for destruction. So the devices may reside in different netns. Let's use unregister_netdevice_queue_net() to support per-netns device unregistration. list_del() is changed to list_del_init() to avoid queueing the same device twice. Even after pfcp_net_exit_rtnl() queues a cross-netns pfcp device, pfcp_dellink() could be called concurrently for it (once RTNL is removed). In such a case, __rtnl_net_unlock() will perform the unregistration. We can see pfcp0 below is unregistered by the per-netns work instead of cleanup_net(). # bpftrace -e '#include kprobe:pfcp_dev_uninit { $dev = (struct net_device *)arg0; printf("PID: %d | DEV: %s%s\n", pid, $dev->name, kstack()); } kprobe:pfcp_net_exit_rtnl { printf("PID: %d%s\n", pid, kstack()); }' & # ip netns add ns1 # ip netns add ns2 # ip -n ns1 link add pfcp0 link-netns ns2 type pfcp # ip netns del ns2 PID: 12 pfcp_net_exit_rtnl+5 ops_undo_list+702 cleanup_net+1122 process_scheduled_works+2538 ... PID: 462 | DEV: pfcp0 pfcp_dev_uninit+5 unregister_netdevice_many_notify+7129 unregister_netdevice_many_net+1050 rtnl_net_work_func+136 process_scheduled_works+2538 Signed-off-by: Kuniyuki Iwashima Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260731224406.2444121-3-kuniyu@google.com Signed-off-by: Jakub Kicinski --- drivers/net/pfcp.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/net/pfcp.c b/drivers/net/pfcp.c index 45f9c5d3cb20..e1cca779d2ec 100644 --- a/drivers/net/pfcp.c +++ b/drivers/net/pfcp.c @@ -227,12 +227,13 @@ static int pfcp_newlink(struct net_device *dev, return err; } -static void __pfcp_dellink(struct net_device *dev, struct list_head *head) +static void __pfcp_dellink(struct net *net, struct net_device *dev, + struct list_head *head) { struct pfcp_dev *pfcp = netdev_priv(dev); - list_del(&pfcp->list); - unregister_netdevice_queue(dev, head); + list_del_init(&pfcp->list); + unregister_netdevice_queue_net(net, dev, head); } static void pfcp_dellink(struct net_device *dev, struct list_head *head) @@ -243,7 +244,8 @@ static void pfcp_dellink(struct net_device *dev, struct list_head *head) pn = net_generic(pfcp->net, pfcp_net_id); mutex_lock(&pn->lock); - __pfcp_dellink(dev, head); + if (!list_empty(&pfcp->list)) + __pfcp_dellink(dev_net(dev), dev, head); mutex_unlock(&pn->lock); } @@ -274,7 +276,7 @@ static void __net_exit pfcp_net_exit_rtnl(struct net *net, mutex_lock(&pn->lock); list_for_each_entry_safe(pfcp, pfcp_next, &pn->pfcp_dev_list, list) - __pfcp_dellink(pfcp->dev, dev_to_kill); + __pfcp_dellink(net, pfcp->dev, dev_to_kill); mutex_unlock(&pn->lock); } From 1aae367b1646fb68a39a4c5aa8ac2e1f1512e0af Mon Sep 17 00:00:00 2001 From: Hongyan Xu Date: Sat, 1 Aug 2026 22:06:43 +0800 Subject: [PATCH 0973/1433] net: phy: nxp-tja11xx: cancel registration work on remove tja1102_p0_probe() schedules work to register the second port. That work uses the Port 0 private data and phydev. The private data is devm-allocated, but the driver does not wait for the pending work on remove. Store the Port 0 private data in phydev->priv and add a remove callback. The callback cancels the registration work before devres teardown frees the state. This issue was found by a static analysis tool. Reviewed-by: Andrew Lunn Signed-off-by: Hongyan Xu Link: https://patch.msgid.link/20260801140643.1871-1-getshell@seu.edu.cn Signed-off-by: Jakub Kicinski --- drivers/net/phy/nxp-tja11xx.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/net/phy/nxp-tja11xx.c b/drivers/net/phy/nxp-tja11xx.c index 3c38a8ddae2f..079d912a1357 100644 --- a/drivers/net/phy/nxp-tja11xx.c +++ b/drivers/net/phy/nxp-tja11xx.c @@ -620,6 +620,7 @@ static int tja1102_p0_probe(struct phy_device *phydev) return -ENOMEM; priv->phydev = phydev; + phydev->priv = priv; INIT_WORK(&priv->phy_register_work, tja1102_p1_register); ret = tja11xx_hwmon_register(phydev, priv); @@ -631,6 +632,13 @@ static int tja1102_p0_probe(struct phy_device *phydev) return 0; } +static void tja1102_p0_remove(struct phy_device *phydev) +{ + struct tja11xx_priv *priv = phydev->priv; + + cancel_work_sync(&priv->phy_register_work); +} + static int tja1102_match_phy_device(struct phy_device *phydev, bool port0) { int ret; @@ -849,6 +857,7 @@ static struct phy_driver tja11xx_driver[] = { .features = PHY_BASIC_T1_FEATURES, .flags = PHY_POLL_CABLE_TEST, .probe = tja1102_p0_probe, + .remove = tja1102_p0_remove, .soft_reset = tja11xx_soft_reset, .config_aneg = tja11xx_config_aneg, .config_init = tja11xx_config_init, From 2bb824660ef8116ca74bbbc265bfc2d716cd4708 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Sat, 1 Aug 2026 13:42:34 +0800 Subject: [PATCH 0974/1433] rds: synchronize info callbacks with module unload rds_info_getsockopt() reads a callback from rds_info_funcs and invokes it without protecting the callback's lifetime. Transport modules register functions stored in this array. For example, rds_tcp.ko registers rds_tcp_tc_info() for RDS_INFO_TCP_SOCKETS. This permits the following interleaving: CPU0 CPU1 rds_info_getsockopt() func = rds_tcp_tc_info rmmod rds_tcp rds_tcp_exit() rds_info_deregister_func() rds_info_funcs[offset] = NULL free rds_tcp module text func() The reader can therefore branch to an address in unloaded module text. Protect callback invocation with SRCU. Enter the SRCU read-side critical section before loading the callback and leave it only after the callback returns. Clear the callback with WRITE_ONCE() and call synchronize_srcu() before deregistration returns, preventing module unload from freeing its text while an old reader is still executing it. SRCU is required because callbacks such as RDS_INFO_COUNTERS can sleep. Keep the callback array unannotated and use READ_ONCE() and WRITE_ONCE() for concurrent slot access so sparse does not have to apply __rcu through the function-pointer typedef. Replace the two callback-slot BUG_ON() checks with WARN_ON_ONCE() and return without changing the slot on mismatch. Link: https://lore.kernel.org/netdev/20260720184955.3008978-1-nicoyip.dev@gmail.com/ Suggested-by: Allison Henderson Suggested-by: Kuniyuki Iwashima Reviewed-by: Allison Henderson Signed-off-by: Chengfeng Ye Link: https://patch.msgid.link/20260801054234.3535077-1-nicoyip.dev@gmail.com Signed-off-by: Jakub Kicinski --- net/rds/info.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/net/rds/info.c b/net/rds/info.c index 21b32eb16559..31e7ad108459 100644 --- a/net/rds/info.c +++ b/net/rds/info.c @@ -32,6 +32,7 @@ */ #include #include +#include #include #include #include @@ -68,6 +69,7 @@ struct rds_info_iterator { unsigned long offset; }; +DEFINE_STATIC_SRCU(rds_info_srcu); static DEFINE_SPINLOCK(rds_info_lock); static rds_info_func rds_info_funcs[RDS_INFO_LAST - RDS_INFO_FIRST + 1]; @@ -78,8 +80,11 @@ void rds_info_register_func(int optname, rds_info_func func) BUG_ON(optname < RDS_INFO_FIRST || optname > RDS_INFO_LAST); spin_lock(&rds_info_lock); - BUG_ON(rds_info_funcs[offset]); - rds_info_funcs[offset] = func; + if (WARN_ON_ONCE(rds_info_funcs[offset])) { + spin_unlock(&rds_info_lock); + return; + } + WRITE_ONCE(rds_info_funcs[offset], func); spin_unlock(&rds_info_lock); } EXPORT_SYMBOL_GPL(rds_info_register_func); @@ -91,9 +96,13 @@ void rds_info_deregister_func(int optname, rds_info_func func) BUG_ON(optname < RDS_INFO_FIRST || optname > RDS_INFO_LAST); spin_lock(&rds_info_lock); - BUG_ON(rds_info_funcs[offset] != func); - rds_info_funcs[offset] = NULL; + if (WARN_ON_ONCE(rds_info_funcs[offset] != func)) { + spin_unlock(&rds_info_lock); + return; + } + WRITE_ONCE(rds_info_funcs[offset], NULL); spin_unlock(&rds_info_lock); + synchronize_srcu(&rds_info_srcu); } EXPORT_SYMBOL_GPL(rds_info_deregister_func); @@ -162,6 +171,7 @@ int rds_info_getsockopt(struct socket *sock, int optname, sockopt_t *opt) rds_info_func func; struct page **pages = NULL; size_t offset0 = 0; + int srcu_idx; int npages = 0; int ret; int len; @@ -214,8 +224,10 @@ int rds_info_getsockopt(struct socket *sock, int optname, sockopt_t *opt) rdsdebug("len %d nr_pages %lu\n", len, nr_pages); call_func: - func = rds_info_funcs[optname - RDS_INFO_FIRST]; + srcu_idx = srcu_read_lock(&rds_info_srcu); + func = READ_ONCE(rds_info_funcs[optname - RDS_INFO_FIRST]); if (!func) { + srcu_read_unlock(&rds_info_srcu, srcu_idx); ret = -ENOPROTOOPT; goto out; } @@ -225,6 +237,7 @@ int rds_info_getsockopt(struct socket *sock, int optname, sockopt_t *opt) iter.offset = offset0; func(sock, len, &iter, &lens); + srcu_read_unlock(&rds_info_srcu, srcu_idx); BUG_ON(lens.each == 0); total = lens.nr * lens.each; From 4b5137bfc07b2b45ccc964b80006602a048ab925 Mon Sep 17 00:00:00 2001 From: Brett Creeley Date: Thu, 30 Jul 2026 04:25:18 +0000 Subject: [PATCH 0975/1433] pds_core: add support for quiet devcmd failures Currently there aren't any use-cases that require special handling on whether or not to print devcmd failures. Specifically non-generic failures, i.e. not supported failures. Add support to allow these messages to be suppressed. This will be used when adding support to negotiate PDS_CORE_IDENTITY_VERSION_2. Signed-off-by: Brett Creeley Link: https://patch.msgid.link/20260730-upstream_v8-v12-1-136cd174ee85@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/pds_core/dev.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/amd/pds_core/dev.c b/drivers/net/ethernet/amd/pds_core/dev.c index bded6b33289c..dd9989cfe6b3 100644 --- a/drivers/net/ethernet/amd/pds_core/dev.c +++ b/drivers/net/ethernet/amd/pds_core/dev.c @@ -126,7 +126,8 @@ static const char *pdsc_devcmd_str(int opcode) } } -static int pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds) +static int __pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds, + const bool do_msg) { struct device *dev = pdsc->dev; unsigned long start_time; @@ -179,7 +180,7 @@ static int pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds) status = pdsc_devcmd_status(pdsc); err = pdsc_err_to_errno(status); - if (err && err != -EAGAIN) + if (do_msg && err && err != -EAGAIN) dev_err(dev, "DEVCMD %d %s failed, status=%d err %d %pe\n", opcode, pdsc_devcmd_str(opcode), status, err, ERR_PTR(err)); @@ -187,8 +188,9 @@ static int pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds) return err; } -int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, - union pds_core_dev_comp *comp, int max_seconds) +static int __pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, + union pds_core_dev_comp *comp, int max_seconds, + const bool do_msg) { int err; @@ -197,7 +199,7 @@ int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, memcpy_toio(&pdsc->cmd_regs->cmd, cmd, sizeof(*cmd)); pdsc_devcmd_dbell(pdsc); - err = pdsc_devcmd_wait(pdsc, cmd->opcode, max_seconds); + err = __pdsc_devcmd_wait(pdsc, cmd->opcode, max_seconds, do_msg); if ((err == -ENXIO || err == -ETIMEDOUT) && pdsc->wq) queue_work(pdsc->wq, &pdsc->health_work); @@ -207,6 +209,12 @@ int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, return err; } +int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, + union pds_core_dev_comp *comp, int max_seconds) +{ + return __pdsc_devcmd_locked(pdsc, cmd, comp, max_seconds, true); +} + int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, union pds_core_dev_comp *comp, int max_seconds) { From e7960459d97764e1493f926b8e43b8f913cec229 Mon Sep 17 00:00:00 2001 From: Brett Creeley Date: Thu, 30 Jul 2026 04:25:19 +0000 Subject: [PATCH 0976/1433] pds_core: add support for identity version 2 Add a new capabilities field in struct pds_core_dev_identity, which requires bumping the identity version to 2, i.e. PDS_CORE_IDENTITY_VERSION_2. If version 2 negotiation fails, then quietly fall back to version 1. If version 1 negotiation fails, then driver load will fail. Another patch in the series will make use of the capabilities field. Signed-off-by: Brett Creeley Link: https://patch.msgid.link/20260730-upstream_v8-v12-2-136cd174ee85@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/pds_core/dev.c | 44 ++++++++++++++++++++----- include/linux/pds/pds_core_if.h | 4 +++ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/amd/pds_core/dev.c b/drivers/net/ethernet/amd/pds_core/dev.c index dd9989cfe6b3..2ebb5360e0e9 100644 --- a/drivers/net/ethernet/amd/pds_core/dev.c +++ b/drivers/net/ethernet/amd/pds_core/dev.c @@ -250,15 +250,17 @@ int pdsc_devcmd_reset(struct pdsc *pdsc) return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); } -static int pdsc_devcmd_identify_locked(struct pdsc *pdsc) +static int pdsc_devcmd_identify_locked(struct pdsc *pdsc, u8 drv_ident_ver, + bool do_msg) { union pds_core_dev_comp comp = {}; union pds_core_dev_cmd cmd = { .identify.opcode = PDS_CORE_CMD_IDENTIFY, - .identify.ver = PDS_CORE_IDENTITY_VERSION_1, + .identify.ver = drv_ident_ver, }; - return pdsc_devcmd_locked(pdsc, &cmd, &comp, pdsc->devcmd_timeout); + return __pdsc_devcmd_locked(pdsc, &cmd, &comp, pdsc->devcmd_timeout, + do_msg); } static void pdsc_init_devinfo(struct pdsc *pdsc) @@ -281,8 +283,9 @@ static void pdsc_init_devinfo(struct pdsc *pdsc) dev_dbg(pdsc->dev, "fw_version %s\n", pdsc->dev_info.fw_version); } -static int pdsc_identify(struct pdsc *pdsc) +static int pdsc_identify_ver(struct pdsc *pdsc, u8 drv_ident_ver) { + bool do_msg = drv_ident_ver == PDS_CORE_IDENTITY_VERSION_1; struct pds_core_drv_identity drv = {}; size_t sz; int err; @@ -305,19 +308,22 @@ static int pdsc_identify(struct pdsc *pdsc) sz = min_t(size_t, sizeof(drv), sizeof(pdsc->cmd_regs->data)); memcpy_toio(&pdsc->cmd_regs->data, &drv, sz); - err = pdsc_devcmd_identify_locked(pdsc); + err = pdsc_devcmd_identify_locked(pdsc, drv_ident_ver, do_msg); if (!err) { sz = min_t(size_t, sizeof(pdsc->dev_ident), sizeof(pdsc->cmd_regs->data)); memcpy_fromio(&pdsc->dev_ident, &pdsc->cmd_regs->data, sz); + + /* V1 firmware doesn't set capabilities, so the field may + * contain garbage from the outgoing driver identity. + */ + if (pdsc->dev_ident.version < PDS_CORE_IDENTITY_VERSION_2) + pdsc->dev_ident.capabilities = 0; } mutex_unlock(&pdsc->devcmd_lock); - if (err) { - dev_err(pdsc->dev, "Cannot identify device: %pe\n", - ERR_PTR(err)); + if (err) return err; - } if (isprint(pdsc->dev_info.fw_version[0]) && isascii(pdsc->dev_info.fw_version[0])) @@ -334,6 +340,26 @@ static int pdsc_identify(struct pdsc *pdsc) return 0; } +static int pdsc_identify(struct pdsc *pdsc) +{ + int err; + + /* Older firmware rejects anything but PDS_CORE_IDENTITY_VERSION_1 + * with PDS_RC_EVERSION (-EINVAL), so retry with V1 on version + * rejection. Don't retry on other errors like -ENXIO/-ETIMEDOUT + * which indicate firmware is not running or hung. + */ + err = pdsc_identify_ver(pdsc, PDS_CORE_IDENTITY_VERSION_2); + if (err == -EINVAL) + err = pdsc_identify_ver(pdsc, PDS_CORE_IDENTITY_VERSION_1); + + if (err) + dev_err(pdsc->dev, "Cannot identify device: %pe\n", + ERR_PTR(err)); + + return err; +} + void pdsc_dev_uninit(struct pdsc *pdsc) { if (pdsc->intr_info) { diff --git a/include/linux/pds/pds_core_if.h b/include/linux/pds/pds_core_if.h index 17a87c1a55d7..619186f26b5b 100644 --- a/include/linux/pds/pds_core_if.h +++ b/include/linux/pds/pds_core_if.h @@ -119,6 +119,8 @@ struct pds_core_drv_identity { * value in usecs to device units using: * device units = usecs * mult / div * @vif_types: How many of each VIF device type is supported + * @capabilities: Device capabilities + * only supported on version >= PDS_CORE_IDENTITY_VERSION_2 */ struct pds_core_dev_identity { u8 version; @@ -131,9 +133,11 @@ struct pds_core_dev_identity { __le32 intr_coal_mult; __le32 intr_coal_div; __le16 vif_types[PDS_DEV_TYPE_MAX]; + __le64 capabilities; }; #define PDS_CORE_IDENTITY_VERSION_1 1 +#define PDS_CORE_IDENTITY_VERSION_2 2 /** * struct pds_core_dev_identify_cmd - Driver/device identify command From fb918581d433ac475b0407d9c6ede03579a3c811 Mon Sep 17 00:00:00 2001 From: Brett Creeley Date: Thu, 30 Jul 2026 04:25:20 +0000 Subject: [PATCH 0977/1433] pds_core: add PLDM firmware update support via devlink flash Implement PLDM FW Update in the pds_core driver using the upstream pldmfw API. This allows updating an entire PLDM FW package at once or updating specific firmware components by name. Flash the entire image: devlink dev flash pci/0000:b5:00.0 file firmware.pldmfw Flash a specific component from the PLDM FW package: devlink dev flash pci/0000:b5:00.0 \ file firmware.pldmfw component fw.cpld Per-component update uses driver-defined component names (fw, fw.cpld, etc.). Not all components support per-component update - devlink will reject the request if the specified component cannot be updated. Signed-off-by: Brett Creeley Signed-off-by: Nikhil P. Rao Link: https://patch.msgid.link/20260730-upstream_v8-v12-3-136cd174ee85@amd.com Signed-off-by: Jakub Kicinski --- .../device_drivers/ethernet/amd/pds_core.rst | 89 ++ drivers/net/ethernet/amd/Kconfig | 1 + drivers/net/ethernet/amd/pds_core/core.c | 9 + drivers/net/ethernet/amd/pds_core/core.h | 31 +- drivers/net/ethernet/amd/pds_core/dev.c | 82 ++ drivers/net/ethernet/amd/pds_core/devlink.c | 8 +- drivers/net/ethernet/amd/pds_core/fw.c | 796 +++++++++++++++++- drivers/net/ethernet/amd/pds_core/main.c | 5 + include/linux/pds/pds_core_if.h | 401 +++++++++ 9 files changed, 1411 insertions(+), 11 deletions(-) diff --git a/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst b/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst index 9e8a16c44102..49487a35c163 100644 --- a/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst +++ b/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst @@ -102,6 +102,95 @@ currently in use, and that bank will used for the next boot:: # devlink dev flash pci/0000:b5:00.0 \ file pensando/dsc_fw_1.63.0-22.tar +Firmware Management (PLDM) +========================== + +Firmware that supports PLDM can be updated using the devlink flash command +with a PLDM firmware package. The entire package can be updated at once:: + + # devlink dev flash pci/0000:b5:00.0 file firmware.pldmfw + +Individual components can also be updated by specifying the component name:: + + # devlink dev flash pci/0000:b5:00.0 \ + file firmware.pldmfw component fw.cpld + +Per-component update uses driver-defined component names (fw, fw.cpld, +etc.). Not all components support per-component update - +devlink will reject the request if the specified component cannot +be updated. + +Gold (recovery) components can be updated by specifying the base component +name (e.g., ``fw`` for ``fw.gold``) with a goldfw package file when the +device supports per-component update. The ``.gold`` suffix in devlink info +output indicates the gold slot version, not a flash target. + +Info versions (PLDM) +==================== + +Firmware that supports PLDM reports component versions using driver-defined +names. The driver reports the following component versions: + +.. list-table:: devlink info versions for PLDM-capable firmware + :widths: 5 5 90 + + * - Name + - Type + - Description + * - ``fw`` + - running, stored + - Main firmware + * - ``fw.gold`` + - stored + - Gold (recovery) firmware + * - ``fw.bootloader`` + - running, stored + - Boot loader + * - ``fw.cpld`` + - running, stored + - CPLD + * - ``fw.secure`` + - running, stored + - Secure boot firmware + * - ``fw.fpga`` + - running, stored + - FPGA configuration + * - ``fw.suc`` + - running, stored + - System Unit Controller firmware + * - ``fw.suc.bootloader`` + - running, stored + - System Unit Controller bootloader + * - ``fw.uboot`` + - running, stored + - U-Boot bootloader + * - ``asic.id`` + - fixed + - The ASIC type for this device + * - ``asic.rev`` + - fixed + - The revision of the ASIC for this device + +Example output:: + + $ devlink dev info pci/0000:00:05.0 + pci/0000:00:05.0: + driver pds_core + serial_number FLM18420073 + versions: + fixed: + asic.id 0x0 + asic.rev 0x0 + running: + fw.bootloader 1.2.3 + fw 1.3.0 + fw.cpld 3.18 + stored: + fw.bootloader 1.2.3 + fw.gold 1.2.0 + fw 1.3.0 + fw.cpld 3.18 + Health Reporters ================ diff --git a/drivers/net/ethernet/amd/Kconfig b/drivers/net/ethernet/amd/Kconfig index e35991141a1a..743e3d4b6b94 100644 --- a/drivers/net/ethernet/amd/Kconfig +++ b/drivers/net/ethernet/amd/Kconfig @@ -171,6 +171,7 @@ config PDS_CORE depends on 64BIT && PCI select AUXILIARY_BUS select NET_DEVLINK + select PLDMFW help This enables the support for the AMD/Pensando Core device family of adapters. More specific information on this driver can be diff --git a/drivers/net/ethernet/amd/pds_core/core.c b/drivers/net/ethernet/amd/pds_core/core.c index 04ec2569c61c..d13b727dee42 100644 --- a/drivers/net/ethernet/amd/pds_core/core.c +++ b/drivers/net/ethernet/amd/pds_core/core.c @@ -489,6 +489,15 @@ void pdsc_teardown(struct pdsc *pdsc, bool removing) pdsc_devcmd_reset(pdsc); pci_clear_master(pdsc->pdev); + if (!pdsc->pdev->is_virtfn) { + u16 val; + + /* Flush any in-flight DMA before freeing buffers. + * A config read completion cannot return until all prior + * device-initiated memory writes have completed. + */ + pci_read_config_word(pdsc->pdev, PCI_VENDOR_ID, &val); + } pdsc_core_uninit(pdsc); diff --git a/drivers/net/ethernet/amd/pds_core/core.h b/drivers/net/ethernet/amd/pds_core/core.h index b7fe9ad73349..73356c74bb9f 100644 --- a/drivers/net/ethernet/amd/pds_core/core.h +++ b/drivers/net/ethernet/amd/pds_core/core.h @@ -23,6 +23,14 @@ #define PDSC_SETUP_RECOVERY false #define PDSC_SETUP_INIT true +struct pdsc_deferred_dma { + struct list_head list; + dma_addr_t dma_addr; + void *va; + size_t size; + enum dma_data_direction dir; +}; + struct pdsc_dev_bar { void __iomem *vaddr; phys_addr_t bus_addr; @@ -185,6 +193,8 @@ struct pdsc { struct mutex devcmd_lock; /* lock for dev_cmd operations */ struct mutex config_lock; /* lock for configuration operations */ spinlock_t adminq_lock; /* lock for adminq operations */ + struct list_head deferred_dma_list; + spinlock_t deferred_dma_lock; /* lock for deferred DMA list */ refcount_t adminq_refcnt; struct pds_core_dev_info_regs __iomem *info_regs; struct pds_core_dev_cmd_regs __iomem *cmd_regs; @@ -199,6 +209,8 @@ struct pdsc { u64 last_eid; struct pdsc_viftype *viftype_status; struct work_struct pci_reset_work; + + struct pds_core_component_list_info fw_components; }; /** enum pds_core_dbell_bits - bitwise composition of dbell values. @@ -281,8 +293,16 @@ bool pdsc_is_fw_running(struct pdsc *pdsc); bool pdsc_is_fw_good(struct pdsc *pdsc); int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, union pds_core_dev_comp *comp, int max_seconds); +int pdsc_devcmd_with_data(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, + const void *data, size_t data_len, + union pds_core_dev_comp *comp, int max_seconds); +int pdsc_devcmd_with_data_nomsg(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, + const void *data, size_t data_len, + union pds_core_dev_comp *comp, int max_seconds); int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, union pds_core_dev_comp *comp, int max_seconds); +int pdsc_devcmd_locked_nomsg(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, + union pds_core_dev_comp *comp, int max_seconds); int pdsc_devcmd_init(struct pdsc *pdsc); int pdsc_devcmd_reset(struct pdsc *pdsc); int pdsc_dev_init(struct pdsc *pdsc); @@ -315,11 +335,20 @@ void pdsc_process_adminq(struct pdsc_qcq *qcq); void pdsc_work_thread(struct work_struct *work); irqreturn_t pdsc_adminq_isr(int irq, void *data); -int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw, +int pdsc_firmware_update(struct pdsc *pdsc, + struct devlink_flash_update_params *params, struct netlink_ext_ack *extack); +int pdsc_get_component_info(struct pdsc *pdsc); +const char *pdsc_fw_type_to_name(u8 type); +void pdsc_fw_components_invalidate(struct pdsc *pdsc); void pdsc_fw_down(struct pdsc *pdsc); void pdsc_fw_up(struct pdsc *pdsc); void pdsc_pci_reset_thread(struct work_struct *work); +void pdsc_deferred_dma_add(struct pdsc *pdsc, struct pdsc_deferred_dma *entry, + dma_addr_t dma_addr, void *va, size_t size, + enum dma_data_direction dir); +void pdsc_deferred_dma_free(struct pdsc *pdsc); + #endif /* _PDSC_H_ */ diff --git a/drivers/net/ethernet/amd/pds_core/dev.c b/drivers/net/ethernet/amd/pds_core/dev.c index 2ebb5360e0e9..c50778175e2f 100644 --- a/drivers/net/ethernet/amd/pds_core/dev.c +++ b/drivers/net/ethernet/amd/pds_core/dev.c @@ -206,15 +206,56 @@ static int __pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, else memcpy_fromio(comp, &pdsc->cmd_regs->comp, sizeof(*comp)); + if (err != -ETIMEDOUT && err != -EAGAIN) + pdsc_deferred_dma_free(pdsc); + return err; } +void pdsc_deferred_dma_add(struct pdsc *pdsc, struct pdsc_deferred_dma *entry, + dma_addr_t dma_addr, void *va, size_t size, + enum dma_data_direction dir) +{ + entry->dma_addr = dma_addr; + entry->va = va; + entry->size = size; + entry->dir = dir; + + spin_lock(&pdsc->deferred_dma_lock); + list_add_tail(&entry->list, &pdsc->deferred_dma_list); + spin_unlock(&pdsc->deferred_dma_lock); +} + +void pdsc_deferred_dma_free(struct pdsc *pdsc) +{ + struct pdsc_deferred_dma *entry, *tmp; + LIST_HEAD(local_list); + + spin_lock(&pdsc->deferred_dma_lock); + list_splice_init(&pdsc->deferred_dma_list, &local_list); + spin_unlock(&pdsc->deferred_dma_lock); + + list_for_each_entry_safe(entry, tmp, &local_list, list) { + dma_unmap_single(pdsc->dev, entry->dma_addr, + entry->size, entry->dir); + kfree(entry->va); + list_del(&entry->list); + kfree(entry); + } +} + int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, union pds_core_dev_comp *comp, int max_seconds) { return __pdsc_devcmd_locked(pdsc, cmd, comp, max_seconds, true); } +int pdsc_devcmd_locked_nomsg(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, + union pds_core_dev_comp *comp, int max_seconds) +{ + return __pdsc_devcmd_locked(pdsc, cmd, comp, max_seconds, false); +} + int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, union pds_core_dev_comp *comp, int max_seconds) { @@ -227,6 +268,47 @@ int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, return err; } +static int __pdsc_devcmd_with_data(struct pdsc *pdsc, + union pds_core_dev_cmd *cmd, + const void *data, size_t data_len, + union pds_core_dev_comp *comp, + int max_seconds, bool do_msg) +{ + int err; + + mutex_lock(&pdsc->devcmd_lock); + if (!pdsc->cmd_regs) { + err = -ENXIO; + goto unlock; + } + if (data_len > sizeof(pdsc->cmd_regs->data)) { + err = -ENOSPC; + goto unlock; + } + memcpy_toio(&pdsc->cmd_regs->data, data, data_len); + err = __pdsc_devcmd_locked(pdsc, cmd, comp, max_seconds, do_msg); +unlock: + mutex_unlock(&pdsc->devcmd_lock); + + return err; +} + +int pdsc_devcmd_with_data(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, + const void *data, size_t data_len, + union pds_core_dev_comp *comp, int max_seconds) +{ + return __pdsc_devcmd_with_data(pdsc, cmd, data, data_len, + comp, max_seconds, true); +} + +int pdsc_devcmd_with_data_nomsg(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, + const void *data, size_t data_len, + union pds_core_dev_comp *comp, int max_seconds) +{ + return __pdsc_devcmd_with_data(pdsc, cmd, data, data_len, + comp, max_seconds, false); +} + int pdsc_devcmd_init(struct pdsc *pdsc) { union pds_core_dev_comp comp = {}; diff --git a/drivers/net/ethernet/amd/pds_core/devlink.c b/drivers/net/ethernet/amd/pds_core/devlink.c index 8adae7b18898..3b763ee1715e 100644 --- a/drivers/net/ethernet/amd/pds_core/devlink.c +++ b/drivers/net/ethernet/amd/pds_core/devlink.c @@ -90,13 +90,7 @@ int pdsc_dl_flash_update(struct devlink *dl, { struct pdsc *pdsc = devlink_priv(dl); - if (params->component) { - NL_SET_ERR_MSG_MOD(extack, - "Component update not supported by this device"); - return -EOPNOTSUPP; - } - - return pdsc_firmware_update(pdsc, params->fw, extack); + return pdsc_firmware_update(pdsc, params, extack); } static char *fw_slotnames[] = { diff --git a/drivers/net/ethernet/amd/pds_core/fw.c b/drivers/net/ethernet/amd/pds_core/fw.c index fa626719e68d..5ccf017f6af4 100644 --- a/drivers/net/ethernet/amd/pds_core/fw.c +++ b/drivers/net/ethernet/amd/pds_core/fw.c @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 /* Copyright(c) 2023 Advanced Micro Devices, Inc */ +#include +#include + #include "core.h" /* The worst case wait for the install activity is about 25 minutes when @@ -14,6 +17,58 @@ /* Number of periodic log updates during fw file download */ #define PDSC_FW_INTERVAL_FRACTION 32 +#define PDSC_FW_COMPONENT_PREFIX "fw." +#define PDSC_FW_COMPONENT_FULL_NAME_BUFLEN \ + (sizeof(PDSC_FW_COMPONENT_PREFIX) + PDS_CORE_FW_COMPONENT_NAME_BUFLEN) + +/* Driver-defined component type to name mapping. + * PDS_CORE_FW_TYPE_MAIN is NULL - handled specially as "fw" without prefix. + */ +static const char * const pdsc_fw_type_names[] = { + [PDS_CORE_FW_TYPE_MAIN] = NULL, + [PDS_CORE_FW_TYPE_BOOT] = "bootloader", + [PDS_CORE_FW_TYPE_CPLD] = "cpld", + [PDS_CORE_FW_TYPE_SECURE] = "secure", + [PDS_CORE_FW_TYPE_FPGA] = "fpga", + [PDS_CORE_FW_TYPE_SUC_MAIN] = "suc", + [PDS_CORE_FW_TYPE_SUC_BOOT] = "suc.bootloader", + [PDS_CORE_FW_TYPE_UBOOT] = "uboot", +}; + +const char *pdsc_fw_type_to_name(u8 type) +{ + if (type < ARRAY_SIZE(pdsc_fw_type_names) && pdsc_fw_type_names[type]) + return pdsc_fw_type_names[type]; + return NULL; +} + +void pdsc_fw_components_invalidate(struct pdsc *pdsc) +{ + /* Pairs with READ_ONCE in pdsc_dl_component_info_get() */ + WRITE_ONCE(pdsc->fw_components.num_components, 0); +} + +static u8 pdsc_name_to_fw_type(const char *name) +{ + size_t prefix_len; + int i; + + /* "fw" without suffix maps to main firmware */ + if (!strcmp(name, "fw")) + return PDS_CORE_FW_TYPE_MAIN; + + prefix_len = str_has_prefix(name, PDSC_FW_COMPONENT_PREFIX); + if (prefix_len) + name += prefix_len; + + for (i = 1; i < ARRAY_SIZE(pdsc_fw_type_names); i++) { + if (pdsc_fw_type_names[i] && + !strcmp(name, pdsc_fw_type_names[i])) + return i; + } + return 0; +} + static int pdsc_devcmd_fw_download_locked(struct pdsc *pdsc, u64 addr, u32 offset, u32 length) { @@ -23,7 +78,7 @@ static int pdsc_devcmd_fw_download_locked(struct pdsc *pdsc, u64 addr, .fw_download.addr = cpu_to_le64(addr), .fw_download.length = cpu_to_le32(length), }; - union pds_core_dev_comp comp; + union pds_core_dev_comp comp = {}; return pdsc_devcmd_locked(pdsc, &cmd, &comp, pdsc->devcmd_timeout); } @@ -95,9 +150,12 @@ static int pdsc_fw_status_long_wait(struct pdsc *pdsc, return err; } -int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw, - struct netlink_ext_ack *extack) +static int +pdsc_legacy_firmware_update(struct pdsc *pdsc, + struct devlink_flash_update_params *params, + struct netlink_ext_ack *extack) { + const struct firmware *fw = params->fw; u32 buf_sz, copy_sz, offset; struct devlink *dl; int next_interval; @@ -105,6 +163,12 @@ int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw, int err = 0; int fw_slot; + if (params->component) { + NL_SET_ERR_MSG_MOD(extack, + "Component update not supported by this device"); + return -EOPNOTSUPP; + } + dev_info(pdsc->dev, "Installing firmware\n"); if (!pdsc->cmd_regs) @@ -195,3 +259,729 @@ int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw, NULL, 0, 0); return err; } + +struct pdsc_component_priv { + u16 component_id; + bool skip; + struct list_head list_entry; +}; + +struct pds_core_fwu_priv { + struct pldmfw context; + struct devlink_flash_update_params *params; + struct netlink_ext_ack *extack; + struct pdsc *pdsc; + struct list_head components; + bool component_found; +}; + +static void pdsc_free_fwu_priv(struct pds_core_fwu_priv *priv) +{ + struct pdsc_component_priv *component_priv, *tmp; + + list_for_each_entry_safe(component_priv, tmp, &priv->components, + list_entry) { + list_del(&component_priv->list_entry); + kfree(component_priv); + } +} + +static int pdsc_devcmd_match_record_desc(struct pdsc *pdsc, u16 desc_type, + u16 desc_size, const u8 *desc_data, + u8 *match) +{ + union pds_core_dev_cmd cmd = { + .match_record_desc.opcode = PDS_CORE_CMD_MATCH_RECORD_DESC, + .match_record_desc.ver = 1, + .match_record_desc.type = cpu_to_le16(desc_type), + .match_record_desc.size = cpu_to_le16(desc_size), + }; + union pds_core_dev_comp comp = {}; + int err; + + err = pdsc_devcmd_with_data(pdsc, &cmd, desc_data, desc_size, + &comp, pdsc->devcmd_timeout); + *match = comp.match_record_desc.match; + + return err; +} + +static bool pdsc_match_record_descs(struct pldmfw *context, + struct pldmfw_record *record) +{ + struct pds_core_fwu_priv *priv = + container_of(context, struct pds_core_fwu_priv, context); + struct pdsc *pdsc = priv->pdsc; + struct pldmfw_desc_tlv *desc; + + if (!pldmfw_op_pci_match_record(context, record)) + return false; + + list_for_each_entry(desc, &record->descs, entry) { + u8 match; + int err; + + switch (desc->type) { + /* skip types checked in pldmfw_op_pci_match_record */ + case PLDM_DESC_ID_PCI_VENDOR_ID: + case PLDM_DESC_ID_PCI_DEVICE_ID: + case PLDM_DESC_ID_PCI_SUBVENDOR_ID: + case PLDM_DESC_ID_PCI_SUBDEV_ID: + continue; + } + + if (!desc->size) + return false; + + err = pdsc_devcmd_match_record_desc(pdsc, desc->type, + desc->size, desc->data, + &match); + if (err) { + dev_err(pdsc->dev, + "match_record_desc failed type: 0x%04x size: %u, err %d\n", + desc->type, desc->size, err); + return false; + } + /* all record descriptors must match */ + if (!match) + return false; + } + + return true; +} + +static int pdsc_devcmd_send_package_data(struct pdsc *pdsc, u64 addr, + u16 length, u16 offset, u16 total_len) +{ + union pds_core_dev_cmd cmd = { + .send_pkg_data.opcode = PDS_CORE_CMD_SEND_PKG_DATA, + .send_pkg_data.ver = 1, + .send_pkg_data.data_pa = cpu_to_le64(addr), + .send_pkg_data.data_len = cpu_to_le16(length), + .send_pkg_data.offset = cpu_to_le16(offset), + .send_pkg_data.total_len = cpu_to_le16(total_len), + }; + union pds_core_dev_comp comp = {}; + + return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); +} + +static int pdsc_send_package_data(struct pldmfw *context, const u8 *data, + u16 length) +{ + struct pds_core_fwu_priv *priv = + container_of(context, struct pds_core_fwu_priv, context); + struct pdsc_deferred_dma *deferred; + struct device *dev = context->dev; + struct pdsc *pdsc = priv->pdsc; + dma_addr_t dma_addr; + u8 *package_data; + u32 offset; + int err; + + if (!length) + return 0; + + deferred = kmalloc_obj(*deferred, GFP_KERNEL); + if (!deferred) + return -ENOMEM; + + package_data = kmemdup(data, length, GFP_KERNEL); + if (!package_data) { + kfree(deferred); + return -ENOMEM; + } + + dma_addr = dma_map_single(dev, package_data, length, DMA_TO_DEVICE); + if (dma_mapping_error(dev, dma_addr)) { + dev_err(dev, "Failed to dma_map package_data length 0x%x\n", + length); + kfree(package_data); + kfree(deferred); + return -ENOMEM; + } + + for (offset = 0; offset < length; offset += PDS_PAGE_SIZE) { + u32 copy_sz; + + copy_sz = min_t(unsigned int, PDS_PAGE_SIZE, length - offset); + err = pdsc_devcmd_send_package_data(pdsc, dma_addr + offset, + copy_sz, offset, length); + if (err) { + NL_SET_ERR_MSG_MOD(priv->extack, + "Failed to send package data"); + break; + } + } + + if (err == -ETIMEDOUT || err == -EAGAIN) { + pdsc_deferred_dma_add(pdsc, deferred, dma_addr, + package_data, length, DMA_TO_DEVICE); + return err; + } + + kfree(deferred); + dma_unmap_single(dev, dma_addr, length, DMA_TO_DEVICE); + kfree(package_data); + return err; +} + +static bool pdsc_component_type_exists(struct pdsc *pdsc, u8 type) +{ + int i; + + for (i = 0; i < pdsc->fw_components.num_components; i++) { + if (pdsc->fw_components.info[i].component_type == type) + return true; + } + return false; +} + +static bool pdsc_component_id_matches_type(struct pdsc *pdsc, + u8 component_id, u8 type) +{ + int i; + + for (i = 0; i < pdsc->fw_components.num_components; i++) { + struct pds_core_fw_component_info *info = + &pdsc->fw_components.info[i]; + + if (info->identifier == component_id && + info->component_type == type) + return true; + } + return false; +} + +static u8 pdsc_get_component_type_by_id(struct pdsc *pdsc, u16 component_id) +{ + int i; + + for (i = 0; i < pdsc->fw_components.num_components; i++) { + struct pds_core_fw_component_info *info = + &pdsc->fw_components.info[i]; + + if (info->identifier == component_id) + return info->component_type; + } + return 0; +} + +static bool pdsc_skip_component(struct pds_core_fwu_priv *priv, + u16 component_id) +{ + struct pdsc_component_priv *component_priv; + + list_for_each_entry(component_priv, &priv->components, list_entry) { + if (component_priv->component_id == component_id) + return component_priv->skip; + } + + return false; +} + +static int pdsc_send_component_table(struct pldmfw *context, + struct pldmfw_component *component, + u8 transfer_flag) +{ + struct pds_core_fwu_priv *priv = + container_of(context, struct pds_core_fwu_priv, context); + struct pds_core_component_tbl *component_tbl; + struct pdsc_component_priv *component_priv; + struct device *dev = context->dev; + union pds_core_dev_comp comp = {}; + union pds_core_dev_cmd cmd = {}; + struct pdsc *pdsc = priv->pdsc; + bool skip_component = false; + u8 requested_type = 0; + u16 buf_sz, tbl_sz; + int err = 0; + + dev_dbg(dev, + "component name %s classification %u id %u activation_method %u ver_len %d ver_str %.*s index %u size %u transfer_flag 0x%02x\n", + priv->params->component, component->classification, + component->identifier, component->activation_method, + component->version_len, component->version_len, + component->version_string, component->index, + component->component_size, transfer_flag); + + component_priv = kzalloc_obj(*component_priv, GFP_KERNEL); + if (!component_priv) + return -ENOMEM; + + if (priv->params->component) { + requested_type = pdsc_name_to_fw_type(priv->params->component); + if (component->identifier > U8_MAX || + !pdsc_component_id_matches_type(pdsc, + component->identifier, + requested_type)) { + skip_component = true; + goto add_component_priv; + } + priv->component_found = true; + } + + buf_sz = sizeof(pdsc->cmd_regs->data); + tbl_sz = struct_size(component_tbl, version_str, + component->version_len); + if (tbl_sz > buf_sz) { + dev_err(dev, "component_tbl size %d too big, max size: %d\n", + tbl_sz, buf_sz); + err = -ENOSPC; + goto free_component_priv; + } + component_tbl = kzalloc(tbl_sz, GFP_KERNEL); + if (!component_tbl) { + err = -ENOMEM; + goto free_component_priv; + } + + component_tbl->comparison_stamp = + cpu_to_le32(component->comparison_stamp); + component_tbl->classification = cpu_to_le16(component->classification); + component_tbl->identifier = cpu_to_le16(component->identifier); + component_tbl->transfer_flag = transfer_flag; + component_tbl->version_str_type = component->version_type; + component_tbl->version_str_len = component->version_len; + memcpy(component_tbl->version_str, component->version_string, + component->version_len); + + cmd.send_component_tbl.opcode = PDS_CORE_CMD_SEND_COMPONENT_TBL; + cmd.send_component_tbl.ver = 1; + cmd.send_component_tbl.slot_id = PDS_CORE_FW_SLOT_INVALID; + + err = pdsc_devcmd_with_data(pdsc, &cmd, component_tbl, tbl_sz, + &comp, pdsc->devcmd_timeout); + kfree(component_tbl); + if (err) { + dev_err(dev, "Failed sending component table: %pe\n", + ERR_PTR(err)); + goto free_component_priv; + } + + skip_component = comp.send_component_tbl.response == 1; + +add_component_priv: + component_priv->skip = skip_component; + component_priv->component_id = component->identifier; + list_add(&component_priv->list_entry, &priv->components); + + return 0; + +free_component_priv: + kfree(component_priv); + return err; +} + +int pdsc_get_component_info(struct pdsc *pdsc) +{ + union pds_core_dev_cmd cmd = { + .get_component_info.opcode = PDS_CORE_CMD_GET_COMPONENT_INFO, + .get_component_info.ver = 1, + }; + struct pds_core_component_list_info *list_info; + struct pdsc_deferred_dma *deferred; + union pds_core_dev_comp comp = {}; + dma_addr_t dma_addr; + u8 num_components; + int err, i; + + deferred = kmalloc_obj(*deferred); + if (!deferred) + return -ENOMEM; + + list_info = kzalloc(PDS_PAGE_SIZE, GFP_KERNEL); + if (!list_info) { + kfree(deferred); + return -ENOMEM; + } + + dma_addr = dma_map_single(pdsc->dev, list_info, PDS_PAGE_SIZE, + DMA_FROM_DEVICE); + if (dma_mapping_error(pdsc->dev, dma_addr)) { + dev_err(pdsc->dev, + "Failed to dma_map component_list_info length %d\n", + PDS_PAGE_SIZE); + kfree(list_info); + kfree(deferred); + return -ENOMEM; + } + + cmd.get_component_info.data_len = cpu_to_le16(PDS_PAGE_SIZE); + cmd.get_component_info.data_pa = cpu_to_le64(dma_addr); + + err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout * 2); + if (err == -ETIMEDOUT || err == -EAGAIN) { + pdsc_deferred_dma_add(pdsc, deferred, dma_addr, list_info, + PDS_PAGE_SIZE, DMA_FROM_DEVICE); + return err; + } + + kfree(deferred); + dma_unmap_single(pdsc->dev, dma_addr, PDS_PAGE_SIZE, DMA_FROM_DEVICE); + if (err) + goto out; + + if (comp.get_component_info.ver == 0) { + /* Don't support backward compatibility as version 0 has + * alignment issues, so give a hint to users to update + * their firmware + */ + dev_warn_once(pdsc->dev, + "Incompatible get_component_info version %u reported by firmware\n", + comp.get_component_info.ver); + err = 0; + goto out; + } + + num_components = list_info->num_components; + if (num_components > PDS_CORE_FW_COMPONENT_LIST_LEN) { + err = -ENOMEM; + goto out; + } + + pdsc->fw_components.num_components = num_components; + for (i = 0; i < num_components; i++) { + struct pds_core_fw_component_info *info = + &pdsc->fw_components.info[i]; + + memcpy(info, &list_info->info[i], sizeof(*info)); + info->version[PDS_CORE_FW_COMPONENT_VER_BUFLEN - 1] = 0; + info->name[PDS_CORE_FW_COMPONENT_NAME_BUFLEN - 1] = 0; + } + +out: + kfree(list_info); + return err; +} + +static int pdsc_devcmd_send_component(struct pdsc *pdsc, + struct pds_core_flash_component *info, + u16 info_sz, dma_addr_t addr, u32 length, + u32 offset, u16 slot_id, + union pds_core_dev_comp *comp) +{ + union pds_core_dev_cmd cmd = { + .send_component.opcode = PDS_CORE_CMD_SEND_COMPONENT, + .send_component.ver = 1, + .send_component.operation = PDS_CORE_SEND_COMPONENT_START, + .send_component.data_pa = cpu_to_le64(addr), + .send_component.data_len = cpu_to_le32(length), + .send_component.offset = cpu_to_le32(offset), + .send_component.slot_id = slot_id, + }; + unsigned long timeout = 300 * HZ; + unsigned long start_time; + unsigned long end_time; + int err; + + start_time = jiffies; + end_time = start_time + timeout; + do { + /* prevent noisy/benign devcmd failures */ + err = pdsc_devcmd_with_data_nomsg(pdsc, &cmd, info, info_sz, + comp, 60); + if (err != -EAGAIN) + break; + + /* if required, subsequent commands check status of + * PDS_CORE_CMD_SEND_COMPONENT command, which returns + * EAGAIN while the command is still running, + * else we get the final command status. + */ + cmd.send_component.operation = PDS_CORE_SEND_COMPONENT_STATUS; + msleep(20); + } while (time_before(jiffies, end_time)); + + if (err == -EAGAIN || err == -ETIMEDOUT) + dev_err(pdsc->dev, "PDS_CORE_CMD_SEND_COMPONENT timed out\n"); + + return err; +} + +static int pdsc_flash_component_chunk(struct pdsc *pdsc, struct device *dev, + struct pds_core_flash_component *info, + u16 info_sz, const u8 *data, u16 copy_sz, + u32 offset, u8 slot_id, + union pds_core_dev_comp *comp) +{ + struct pdsc_deferred_dma *deferred; + dma_addr_t dma_addr; + u8 *component_data; + int err; + + deferred = kmalloc_obj(*deferred, GFP_KERNEL); + if (!deferred) + return -ENOMEM; + + component_data = kmemdup(data, copy_sz, GFP_KERNEL); + if (!component_data) { + kfree(deferred); + return -ENOMEM; + } + + dma_addr = dma_map_single(dev, component_data, copy_sz, DMA_TO_DEVICE); + if (dma_mapping_error(dev, dma_addr)) { + dev_err(dev, + "Failed to dma_map component_data at offset 0x%x copy_sz 0x%x\n", + offset, copy_sz); + kfree(component_data); + kfree(deferred); + return -ENOMEM; + } + + err = pdsc_devcmd_send_component(pdsc, info, info_sz, dma_addr, + copy_sz, offset, slot_id, comp); + if (err == -ETIMEDOUT || err == -EAGAIN) { + pdsc_deferred_dma_add(pdsc, deferred, dma_addr, + component_data, copy_sz, DMA_TO_DEVICE); + return err; + } + + kfree(deferred); + dma_unmap_single(dev, dma_addr, copy_sz, DMA_TO_DEVICE); + kfree(component_data); + + return err; +} + +static int pdsc_flash_component(struct pldmfw *context, + struct pldmfw_component *component) +{ + char component_name_buf[PDSC_FW_COMPONENT_FULL_NAME_BUFLEN]; + struct pds_core_fwu_priv *priv = + container_of(context, struct pds_core_fwu_priv, context); + struct pds_core_flash_component *component_info; + const char *component_name = NULL; + struct device *dev = context->dev; + struct pdsc *pdsc = priv->pdsc; + u16 buf_sz, info_sz; + struct devlink *dl; + u8 component_type; + u32 total_len; + u32 offset; + int err; + + component_type = pdsc_get_component_type_by_id(pdsc, + component->identifier); + if (component_type) { + const char *type_name = pdsc_fw_type_to_name(component_type); + + if (component_type == PDS_CORE_FW_TYPE_MAIN) { + component_name = "fw"; + } else if (type_name) { + snprintf(component_name_buf, sizeof(component_name_buf), + "%s%s", PDSC_FW_COMPONENT_PREFIX, type_name); + component_name = component_name_buf; + } + } + + dl = priv_to_devlink(pdsc); + + if (pdsc_skip_component(priv, component->identifier)) { + devlink_flash_update_status_notify(dl, "Skipped", + component_name, 0, 0); + return 0; + } + + total_len = component->component_size; + dev_dbg(dev, + "component name %s class %u id %u act_meth %u ver_str %.*s index %u size %u\n", + component_name ?: "(unknown)", component->classification, + component->identifier, component->activation_method, + component->version_len, component->version_string, + component->index, component->component_size); + + buf_sz = sizeof(pdsc->cmd_regs->data); + info_sz = struct_size(component_info, version_str, + component->version_len); + if (info_sz > buf_sz) { + dev_err(dev, "component_info size %d too big, max size: %d\n", + info_sz, buf_sz); + return -ENOSPC; + } + component_info = vzalloc(info_sz); + if (!component_info) + return -ENOMEM; + + component_info->comparison_stamp = + cpu_to_le32(component->comparison_stamp); + component_info->image_size = cpu_to_le32(total_len); + component_info->classification = cpu_to_le16(component->classification); + component_info->identifier = cpu_to_le16(component->identifier); + component_info->options = cpu_to_le16(component->options); + component_info->version_str_type = component->version_type; + component_info->version_str_len = component->version_len; + memcpy(component_info->version_str, component->version_string, + component->version_len); + + offset = 0; + while (offset < total_len) { + union pds_core_dev_comp comp = {}; + u16 copy_sz; + + copy_sz = min_t(unsigned int, PDS_PAGE_SIZE, + total_len - offset); + + err = pdsc_flash_component_chunk(pdsc, dev, component_info, + info_sz, + component->component_data + + offset, copy_sz, offset, + PDS_CORE_FW_SLOT_INVALID, + &comp); + if (err && + comp.send_component.compat_response && + (comp.send_component.compat_response_code == + PDS_CORE_COMPONENT_STAMP_IDENTICAL || + comp.send_component.compat_response_code == + PDS_CORE_COMPONENT_STAMP_LOWER)) { + err = 0; + devlink_flash_update_status_notify(dl, "Skipped", + component_name, + 0, 0); + goto skip_component; + } + + if (err) { + NL_SET_ERR_MSG_MOD(priv->extack, + "Failed to flash component"); + goto err_out; + } + + offset += copy_sz; + devlink_flash_update_status_notify(dl, + "Erasing/Flashing", + component_name, offset, + total_len); + } + + vfree(component_info); + return 0; + +err_out: + devlink_flash_update_status_notify(dl, + "Erasing/Flashing Component Failed", + component_name, 0, 0); +skip_component: + vfree(component_info); + return err; +} + +static int pdsc_devcmd_finalize_update(struct pdsc *pdsc) +{ + union pds_core_dev_cmd cmd = { + .finalize_update.opcode = PDS_CORE_CMD_FINALIZE_UPDATE, + .finalize_update.ver = 1, + }; + union pds_core_dev_comp comp = {}; + + return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); +} + +static int pdsc_finalize_update(struct pldmfw *context) +{ + struct pds_core_fwu_priv *priv = + container_of(context, struct pds_core_fwu_priv, context); + const char *component_name = priv->params->component; + unsigned long start_time, end_time; + struct device *dev = context->dev; + struct pdsc *pdsc = priv->pdsc; + struct devlink *dl; + int err; + + dl = priv_to_devlink(pdsc); + + start_time = jiffies; + end_time = start_time + (PDSC_FW_INSTALL_TIMEOUT * HZ); + do { + err = pdsc_devcmd_finalize_update(pdsc); + if (err != -EAGAIN) + break; + + dev_dbg(dev, "retrying finalize_update: %pe\n", ERR_PTR(err)); + msleep(20); + } while (time_before(jiffies, end_time) && err == -EAGAIN); + + if (err) { + devlink_flash_update_status_notify(dl, "Finalize Update Failed", + component_name, 0, 0); + NL_SET_ERR_MSG_MOD(priv->extack, "Finalize update failed"); + return err; + } + + devlink_flash_update_status_notify(dl, "Finalized Update", + component_name, 0, 0); + return 0; +} + +static const struct pldmfw_ops pdsc_pldmfw_ops = { + .match_record = pdsc_match_record_descs, + .send_package_data = pdsc_send_package_data, + .send_component_table = pdsc_send_component_table, + .flash_component = pdsc_flash_component, + .finalize_update = pdsc_finalize_update +}; + +static int pdsc_pldm_firmware_update(struct pdsc *pdsc, + struct devlink_flash_update_params *params, + struct netlink_ext_ack *extack, + const struct firmware *fw) +{ + struct pds_core_fwu_priv priv = {}; + int err; + + if (!pdsc->fw_components.num_components) { + err = pdsc_get_component_info(pdsc); + if (err) { + NL_SET_ERR_MSG_MOD(extack, + "Failed to get component info"); + return err; + } + } + + if (params->component) { + u8 type = pdsc_name_to_fw_type(params->component); + + if (!type || !pdsc_component_type_exists(pdsc, type)) { + NL_SET_ERR_MSG_MOD(extack, "Unknown component name"); + return -ENOENT; + } + } + + INIT_LIST_HEAD(&priv.components); + priv.context.ops = &pdsc_pldmfw_ops; + priv.context.dev = pdsc->dev; + priv.params = params; + priv.extack = extack; + priv.pdsc = pdsc; + + err = pldmfw_flash_image(&priv.context, fw); + if (!err && params->component && !priv.component_found) { + NL_SET_ERR_MSG_MOD(extack, + "Requested component not present in firmware package"); + err = -ENOENT; + } + pdsc_free_fwu_priv(&priv); + + return err; +} + +int pdsc_firmware_update(struct pdsc *pdsc, + struct devlink_flash_update_params *params, + struct netlink_ext_ack *extack) +{ + int err; + + if (pdsc->dev_ident.version >= PDS_CORE_IDENTITY_VERSION_2 && + pdsc->dev_ident.capabilities & + cpu_to_le64(PDS_CORE_DEV_CAP_PLDM_FW_UPDATE)) + err = pdsc_pldm_firmware_update(pdsc, params, extack, + params->fw); + else + err = pdsc_legacy_firmware_update(pdsc, params, extack); + + /* Invalidate cached component info so next info_get refreshes */ + pdsc_fw_components_invalidate(pdsc); + + return err; +} diff --git a/drivers/net/ethernet/amd/pds_core/main.c b/drivers/net/ethernet/amd/pds_core/main.c index 8d94a4d70395..c15a1e376353 100644 --- a/drivers/net/ethernet/amd/pds_core/main.c +++ b/drivers/net/ethernet/amd/pds_core/main.c @@ -315,6 +315,7 @@ static int pdsc_init_pf(struct pdsc *pdsc) destroy_workqueue(pdsc->wq); mutex_destroy(&pdsc->config_lock); mutex_destroy(&pdsc->devcmd_lock); + pdsc_deferred_dma_free(pdsc); pci_free_irq_vectors(pdsc->pdev); err_out_unmap_bars: pdsc_unmap_bars(pdsc); @@ -353,6 +354,8 @@ static int pdsc_probe(struct pci_dev *pdev, const struct pci_device_id *ent) pdsc->pdev = pdev; pdsc->dev = &pdev->dev; set_bit(PDSC_S_INITING_DRIVER, &pdsc->state); + INIT_LIST_HEAD(&pdsc->deferred_dma_list); + spin_lock_init(&pdsc->deferred_dma_lock); pci_set_drvdata(pdev, pdsc); pdsc_debugfs_add_dev(pdsc); @@ -458,6 +461,7 @@ static void pdsc_remove(struct pci_dev *pdev) } pci_disable_device(pdev); + pdsc_deferred_dma_free(pdsc); ida_free(&pdsc_ida, pdsc->uid); pdsc_debugfs_del_dev(pdsc); @@ -505,6 +509,7 @@ static void pdsc_reset_prepare(struct pci_dev *pdev) pci_release_regions(pdev); if (pci_is_enabled(pdev)) pci_disable_device(pdev); + pdsc_deferred_dma_free(pdsc); } static void pdsc_reset_done(struct pci_dev *pdev) diff --git a/include/linux/pds/pds_core_if.h b/include/linux/pds/pds_core_if.h index 619186f26b5b..5a1fafaccf20 100644 --- a/include/linux/pds/pds_core_if.h +++ b/include/linux/pds/pds_core_if.h @@ -40,6 +40,13 @@ enum pds_core_cmd_opcode { PDS_CORE_CMD_FW_DOWNLOAD = 4, PDS_CORE_CMD_FW_CONTROL = 5, + PDS_CORE_CMD_GET_COMPONENT_INFO = 6, + PDS_CORE_CMD_SEND_PKG_DATA = 7, + PDS_CORE_CMD_SEND_COMPONENT_TBL = 8, + PDS_CORE_CMD_SEND_COMPONENT = 9, + PDS_CORE_CMD_FINALIZE_UPDATE = 10, + PDS_CORE_CMD_MATCH_RECORD_DESC = 11, + /* SR/IOV commands */ PDS_CORE_CMD_VF_GETATTR = 60, PDS_CORE_CMD_VF_SETATTR = 61, @@ -100,6 +107,14 @@ struct pds_core_drv_identity { char driver_ver_str[32]; }; +/** + * enum pds_core_dev_capability - Device capabilities + * @PDS_CORE_DEV_CAP_PLDM_FW_UPDATE: Device only supports FW update via PLDM + */ +enum pds_core_dev_capability { + PDS_CORE_DEV_CAP_PLDM_FW_UPDATE = BIT(0), +}; + #define PDS_DEV_TYPE_MAX 16 /** * struct pds_core_dev_identity - Device identity information @@ -119,6 +134,9 @@ struct pds_core_drv_identity { * value in usecs to device units using: * device units = usecs * mult / div * @vif_types: How many of each VIF device type is supported + * @max_fw_slots: Number of firmware components reported by device + * only supported on version >= PDS_CORE_IDENTITY_VERSION_2 + * @rsvd2: Word boundary padding * @capabilities: Device capabilities * only supported on version >= PDS_CORE_IDENTITY_VERSION_2 */ @@ -133,6 +151,8 @@ struct pds_core_dev_identity { __le32 intr_coal_mult; __le32 intr_coal_div; __le16 vif_types[PDS_DEV_TYPE_MAX]; + __le16 max_fw_slots; + u8 rsvd2[6]; __le64 capabilities; }; @@ -279,11 +299,20 @@ enum pds_core_fw_control_oper { PDS_CORE_FW_GET_LIST = 7, }; +/** + * enum pds_core_fw_slot - Firmware slot identifiers + * @PDS_CORE_FW_SLOT_INVALID: Let firmware select slot based on package metadata + * @PDS_CORE_FW_SLOT_A: Primary firmware slot A + * @PDS_CORE_FW_SLOT_B: Primary firmware slot B + * @PDS_CORE_FW_SLOT_GOLD: Gold/recovery firmware slot + * @PDS_CORE_FW_SLOT_MAX: Sentinel value indicating no slot resolved + */ enum pds_core_fw_slot { PDS_CORE_FW_SLOT_INVALID = 0, PDS_CORE_FW_SLOT_A = 1, PDS_CORE_FW_SLOT_B = 2, PDS_CORE_FW_SLOT_GOLD = 3, + PDS_CORE_FW_SLOT_MAX = 0xff, }; /** @@ -450,6 +479,364 @@ struct pds_core_vf_ctrl_comp { u8 status; }; +/** + * struct pds_core_send_pkg_data_cmd - Send package data command + * @opcode: Opcode PDS_CORE_CMD_SEND_PKG_DATA + * @ver: Driver's max support version of this command + * @total_len: Total length of the package data + * @offset: Offset in the package data, non-zero if multiple commands are + * needed for sending the package data + * @data_len: Length of data stored at data_pa + * @data_pa: Data physical address for DMA to device + * + * The package data may be too large to store in a single buffer, so multiple + * PDS_CORE_CMD_SEND_PKG_DATA devcmds may be needed. + */ +struct pds_core_send_pkg_data_cmd { + u8 opcode; + u8 ver; + __le16 total_len; + __le16 offset; + __le16 data_len; + __le64 data_pa; +}; + +/** + * struct pds_core_send_pkg_data_comp - Send package data completion + * @status: Status of the command (enum pds_core_status_code) + * @ver: Device's max supported version of this command + * @rsvd: Word boundary padding + */ +struct pds_core_send_pkg_data_comp { + u8 status; + u8 ver; + u8 rsvd[2]; +}; + +/** + * struct pds_core_component_tbl - Component table details + * @comparison_stamp: Comparison stamp used for component version checks + * @classification: Vendor specific classification info + * @identifier: Component's ID + * @transfer_flag: Part of the component table this request represents + * @version_str_type: The types of strings used + * @version_str_len: Length of @version_str + * @version_str: Component version information + */ +struct pds_core_component_tbl { + __le32 comparison_stamp; + __le16 classification; + __le16 identifier; + u8 transfer_flag; + u8 version_str_type; + u8 version_str_len; + u8 version_str[]; +}; + +/** + * struct pds_core_send_component_tbl_cmd - Send component table command + * @opcode: Opcode PDS_CORE_CMD_SEND_COMPONENT_TBL + * @ver: Driver's max support version of this command + * @slot_id: enum pds_core_fw_slot + * @rsvd: Word boundary padding + * + * Expects to find component table info (struct pds_core_component_tbl) + * in cmd_regs->data. Driver should keep the devcmd interface locked + * while preparing the component table info. + */ +struct pds_core_send_component_tbl_cmd { + u8 opcode; + u8 ver; + u8 slot_id; + u8 rsvd; +}; + +enum pds_core_component_resp_code { + PDS_CORE_COMPONENT_VALID = 0x0, + PDS_CORE_COMPONENT_STAMP_IDENTICAL = 0x1, + PDS_CORE_COMPONENT_STAMP_LOWER = 0x2, + PDS_CORE_COMPONENT_STAMP_OR_VERSION_INVALID = 0x3, + PDS_CORE_COMPONENT_CONFLICT = 0x4, + PDS_CORE_COMPONENT_PREREQS_NOT_MET = 0x5, + PDS_CORE_COMPONENT_NOT_SUPPORTED = 0x6, + PDS_CORE_COMPONENT_FW_TYPE_INVALID = 0xd0, +}; + +/** + * struct pds_core_send_component_tbl_comp - Send component table completion + * @status: Status of the command (enum pds_core_status_code) + * @ver: Device's max supported version of this command + * @completion_code: Component completion code + * @response: Component response + * @response_code: Component response code + * @slot_id: Actual slot_id of the component (enum pds_core_fw_slot) + * @rsvd: Word boundary padding + */ +struct pds_core_send_component_tbl_comp { + u8 status; + u8 ver; + u8 completion_code; + u8 response; + u8 response_code; + u8 slot_id; + u8 rsvd[2]; +}; + +/** + * enum pds_core_send_component_op - PDS_CORE_CMD_SEND_COMPONENT operation + * @PDS_CORE_SEND_COMPONENT_START: Initial operation to start transfer + * @PDS_CORE_SEND_COMPONENT_STATUS: Subsequent calls to check on status + */ +enum pds_core_send_component_op { + PDS_CORE_SEND_COMPONENT_START = 0, + PDS_CORE_SEND_COMPONENT_STATUS = 1, +}; + +#define PDS_CORE_FW_COMPONENT_ID_INVALID 0xFFFF +/** + * struct pds_core_flash_component - Component details + * @comparison_stamp: Comparison stamp used for component version checks + * @image_size: Component image size + * @classification: Vendor specific classification info + * @identifier: Component's ID + * @options: Component options + * @rsvd: Word boundary padding + * @version_str_type: The types of strings used + * @version_str_len: Length of @version_str + * @version_str: Component version information + */ +struct pds_core_flash_component { + __le32 comparison_stamp; + __le32 image_size; + __le16 classification; + __le16 identifier; + __le16 options; + u8 rsvd[3]; + u8 version_str_type; + u8 version_str_len; + u8 version_str[]; +}; + +/** + * struct pds_core_send_component_cmd - Send component command + * @opcode: Opcode PDS_CORE_CMD_SEND_COMPONENT + * @ver: Driver's max supported version of this command + * @slot_id: enum pds_core_fw_slot + * @operation: enum pds_core_send_component_op + * @offset: Offset into the component, non-zero if multiple commands + * are needed for a single component + * @data_len: Length of this part of the component stored at @data_pa + * @rsvd: Word boundary padding + * @data_pa: DMA address of the component + * + * A component may be too large to store in a single buffer, so multiple + * PDS_CORE_CMD_SEND_COMPONENT devcmds may be needed. + * + * Expects to find flash component info (struct pds_core_flash_component) + * in cmd_regs->data. Driver should keep the devcmd interface locked + * while preparing and sending the flash component info. + */ +struct pds_core_send_component_cmd { + u8 opcode; + u8 ver; + u8 slot_id; + u8 operation; + __le32 offset; + __le32 data_len; + u8 rsvd[4]; + __le64 data_pa; +}; + +/** + * struct pds_core_send_component_comp - Send component completion + * @status: Status of the command (enum pds_core_status_code) + * @ver: Device's max supported version of this command + * @completion_code: Completion code + * @compat_response: Compatibility response (0 = Component can be updated) + * @compat_response_code: Compatibility response code + * @rsvd: Word boundary padding + */ +struct pds_core_send_component_comp { + u8 status; + u8 ver; + u8 completion_code; + u8 compat_response; + u8 compat_response_code; + u8 rsvd[3]; +}; + +/** + * enum pds_core_fw_component_type - Firmware component type + * @PDS_CORE_FW_TYPE_UNKNOWN: Unknown component type + * @PDS_CORE_FW_TYPE_MAIN: Main firmware + * @PDS_CORE_FW_TYPE_BOOT: Boot loader + * @PDS_CORE_FW_TYPE_CPLD: CPLD firmware + * @PDS_CORE_FW_TYPE_SECURE: Secure firmware + * @PDS_CORE_FW_TYPE_FPGA: FPGA configuration + * @PDS_CORE_FW_TYPE_SUC_MAIN: System Unit Controller firmware + * @PDS_CORE_FW_TYPE_SUC_BOOT: System Unit Controller bootloader + * @PDS_CORE_FW_TYPE_UBOOT: U-Boot bootloader + * + * Gold/recovery variants are identified by slot_id == PDS_CORE_FW_SLOT_GOLD + * and reported with a ".gold" suffix (e.g., fw.gold). + */ +enum pds_core_fw_component_type { + PDS_CORE_FW_TYPE_UNKNOWN = 0, + PDS_CORE_FW_TYPE_MAIN = 1, + PDS_CORE_FW_TYPE_BOOT = 2, + PDS_CORE_FW_TYPE_CPLD = 3, + PDS_CORE_FW_TYPE_SECURE = 4, + PDS_CORE_FW_TYPE_FPGA = 5, + PDS_CORE_FW_TYPE_SUC_MAIN = 6, + PDS_CORE_FW_TYPE_SUC_BOOT = 7, + PDS_CORE_FW_TYPE_UBOOT = 8, +}; + +/** + * enum pds_core_component_info_flags - Component info flags + * @PDS_CORE_FW_COMPONENT_INFO_F_RUNNING: Component is currently running + * @PDS_CORE_FW_COMPONENT_INFO_F_STARTUP: Component version on next FW boot + * @PDS_CORE_FW_COMPONENT_INFO_F_FIXED: Component is fixed and cannot be updated + * @PDS_CORE_FW_COMPONENT_INFO_F_UPDATE_BY_NAME: Component can be updated + * by name + */ +enum pds_core_component_info_flags { + PDS_CORE_FW_COMPONENT_INFO_F_RUNNING = BIT(0), + PDS_CORE_FW_COMPONENT_INFO_F_STARTUP = BIT(1), + PDS_CORE_FW_COMPONENT_INFO_F_FIXED = BIT(2), + PDS_CORE_FW_COMPONENT_INFO_F_UPDATE_BY_NAME = BIT(3), +}; + +/** + * struct pds_core_fw_component_info - GET_COMPONENT_INFO entry + * @name: Component's name + * @component_type: enum pds_core_fw_component_type + * @rsvd: Word boundary padding + * @flags: enum pds_core_component_info_flags + * @identifier: Component's identifier + * @slot_id: Component's slot identifier + * @version: Component's version + */ +struct pds_core_fw_component_info { +#define PDS_CORE_FW_COMPONENT_NAME_BUFLEN 24 + char name[PDS_CORE_FW_COMPONENT_NAME_BUFLEN]; + u8 component_type; + u8 rsvd[3]; + __le16 flags; + u8 identifier; + u8 slot_id; +#define PDS_CORE_FW_COMPONENT_VER_BUFLEN 32 + char version[PDS_CORE_FW_COMPONENT_VER_BUFLEN]; +}; + +#define PDS_CORE_FW_COMPONENT_LIST_LEN ((PDS_PAGE_SIZE - 8) / \ + sizeof(struct pds_core_fw_component_info)) + +/** + * struct pds_core_component_list_info - GET_COMPONENT_INFO completion data + * @num_components: Number of valid components + * @rsvd: Word boundary padding + * @info: List of valid components + */ +struct pds_core_component_list_info { + u8 num_components; + u8 rsvd[7]; + struct pds_core_fw_component_info info[PDS_CORE_FW_COMPONENT_LIST_LEN]; +}; + +/** + * struct pds_core_get_component_info_cmd - GET_COMPONENT_INFO command + * @opcode: PDS_CORE_CMD_GET_COMPONENT_INFO + * @ver: Driver's max supported version of this command + * @data_len: Length of data at data_pa + * @rsvd: Word boundary padding + * @data_pa: DMA address of data + * + * FW populates struct pds_core_component_list_info pointed to by @data_pa + */ +struct pds_core_get_component_info_cmd { + u8 opcode; + u8 ver; + __le16 data_len; + u8 rsvd[4]; + __le64 data_pa; +}; + +/** + * struct pds_core_get_component_info_comp - GET_COMPONENT_INFO completion + * @status: enum pds_core_status_code + * @ver: Device's max supported version of this command + * @rsvd: Word boundary padding + */ +struct pds_core_get_component_info_comp { + u8 status; + u8 ver; + u8 rsvd[2]; +}; + +/** + * struct pds_core_finalize_update_cmd - FINALIZE_UPDATE command + * @opcode: PDS_CORE_CMD_FINALIZE_UPDATE + * @ver: Driver's max support version of this command + * @rsvd: Word boundary padding + * + * Driver sends at the end of updating all components to finalize the update + */ +struct pds_core_finalize_update_cmd { + u8 opcode; + u8 ver; + u8 rsvd[2]; +}; + +/** + * struct pds_core_finalize_update_comp - FINALIZE_UPDATE completion + * @status: enum pds_core_status_code + * @ver: Device's max supported version of this command + * @rsvd: Word boundary padding + */ +struct pds_core_finalize_update_comp { + u8 status; + u8 ver; + u8 rsvd[2]; +}; + +/** + * struct pds_core_match_record_desc_cmd - MATCH_RECORD_DESC command + * @opcode: PDS_CORE_CMD_MATCH_RECORD_DESC + * @ver: Driver's max supported version of this command + * @type: PLDM Descriptor Identifier Type + * @size: Length of the Descriptor Identifier Value + * @rsvd: Word boundary padding + * + * Expects to find the Descriptor Identifier Data in cmd_regs->data. Driver + * should keep the devcmd interface locked while preparing and sending this + * command. + */ +struct pds_core_match_record_desc_cmd { + u8 opcode; + u8 ver; + __le16 type; + __le16 size; + u8 rsvd[2]; +}; + +/** + * struct pds_core_match_record_desc_comp - MATCH_RECORD_DESC completion + * @status: enum pds_core_status_code + * @ver: Device's max supported version of this command + * @match: Whether or not the Record Descriptor matches the device + * @rsvd: Word boundary padding + * + * When status is PDS_RC_SUCCESS, then @match is valid, otherwise it's + * undefined. + */ +struct pds_core_match_record_desc_comp { + u8 status; + u8 ver; + u8 match; + u8 rsvd; +}; + /* * union pds_core_dev_cmd - Overlay of core device command structures */ @@ -466,6 +853,13 @@ union pds_core_dev_cmd { struct pds_core_vf_setattr_cmd vf_setattr; struct pds_core_vf_getattr_cmd vf_getattr; struct pds_core_vf_ctrl_cmd vf_ctrl; + + struct pds_core_get_component_info_cmd get_component_info; + struct pds_core_send_pkg_data_cmd send_pkg_data; + struct pds_core_send_component_tbl_cmd send_component_tbl; + struct pds_core_send_component_cmd send_component; + struct pds_core_finalize_update_cmd finalize_update; + struct pds_core_match_record_desc_cmd match_record_desc; }; /* @@ -484,6 +878,13 @@ union pds_core_dev_comp { struct pds_core_vf_setattr_comp vf_setattr; struct pds_core_vf_getattr_comp vf_getattr; struct pds_core_vf_ctrl_comp vf_ctrl; + + struct pds_core_get_component_info_comp get_component_info; + struct pds_core_send_pkg_data_comp send_pkg_data; + struct pds_core_send_component_tbl_comp send_component_tbl; + struct pds_core_send_component_comp send_component; + struct pds_core_finalize_update_comp finalize_update; + struct pds_core_match_record_desc_comp match_record_desc; }; /** From 5df545b3a5ee485fb366bba544063996d45d64af Mon Sep 17 00:00:00 2001 From: Brett Creeley Date: Thu, 30 Jul 2026 04:25:21 +0000 Subject: [PATCH 0978/1433] pds_core: add PLDM component info display Add detailed component information display via devlink info. This allows users to see individual firmware components and their versions. Components are reported as fixed, running, or stored based on their firmware-provided flags. Example output: $ devlink dev info pci/0000:00:05.0 versions: fixed: asic.id 0x0 asic.rev 0x0 running: fw.bootloader 1.2.3 fw.uboot 1.60.0-73 fw 1.60.0-73 fw.cpld 3.18 stored: fw.bootloader 1.2.3 fw.uboot 1.60.0-73 fw.uboot.gold 1.50.0-22 fw.gold 1.50.0-22 fw 1.60.0-73 fw.cpld 3.18 Signed-off-by: Brett Creeley Link: https://patch.msgid.link/20260730-upstream_v8-v12-4-136cd174ee85@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/pds_core/core.c | 2 + drivers/net/ethernet/amd/pds_core/devlink.c | 145 +++++++++++++++++++- 2 files changed, 142 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/amd/pds_core/core.c b/drivers/net/ethernet/amd/pds_core/core.c index d13b727dee42..51d21328c73d 100644 --- a/drivers/net/ethernet/amd/pds_core/core.c +++ b/drivers/net/ethernet/amd/pds_core/core.c @@ -596,6 +596,8 @@ void pdsc_fw_up(struct pdsc *pdsc) return; } + pdsc_fw_components_invalidate(pdsc); + err = pdsc_setup(pdsc, PDSC_SETUP_RECOVERY); if (err) goto err_out; diff --git a/drivers/net/ethernet/amd/pds_core/devlink.c b/drivers/net/ethernet/amd/pds_core/devlink.c index 3b763ee1715e..63fe45e91f71 100644 --- a/drivers/net/ethernet/amd/pds_core/devlink.c +++ b/drivers/net/ethernet/amd/pds_core/devlink.c @@ -93,14 +93,120 @@ int pdsc_dl_flash_update(struct devlink *dl, return pdsc_firmware_update(pdsc, params, extack); } +static int pdsc_dl_report_component(struct devlink_info_req *req, + struct pds_core_fw_component_info *info) +{ + enum devlink_info_version_type ver_type; + u16 flags = le16_to_cpu(info->flags); + char *ver = info->version; + const char *name; + char buf[32]; + + /* Main firmware is reported as generic "fw" */ + if (info->component_type == PDS_CORE_FW_TYPE_MAIN) { + if (info->slot_id == PDS_CORE_FW_SLOT_GOLD) + snprintf(buf, sizeof(buf), "fw.gold"); + else + snprintf(buf, sizeof(buf), "fw"); + } else { + name = pdsc_fw_type_to_name(info->component_type); + if (!name) + return 0; + + if (info->slot_id == PDS_CORE_FW_SLOT_GOLD) + snprintf(buf, sizeof(buf), "fw.%s.gold", name); + else + snprintf(buf, sizeof(buf), "fw.%s", name); + } + + ver_type = DEVLINK_INFO_VERSION_TYPE_NONE; + if (flags & PDS_CORE_FW_COMPONENT_INFO_F_UPDATE_BY_NAME) + ver_type = DEVLINK_INFO_VERSION_TYPE_COMPONENT; + + if (flags & PDS_CORE_FW_COMPONENT_INFO_F_FIXED) { + int err; + + err = devlink_info_version_fixed_put(req, buf, ver); + if (err) + return err; + } + + if (flags & PDS_CORE_FW_COMPONENT_INFO_F_RUNNING) { + int err; + + err = devlink_info_version_running_put_ext(req, buf, + ver, ver_type); + if (err) + return err; + } + + if (flags & PDS_CORE_FW_COMPONENT_INFO_F_STARTUP) { + int err; + + err = devlink_info_version_stored_put_ext(req, buf, + ver, ver_type); + if (err) + return err; + } + + return 0; +} + +static int pdsc_dl_report_fw_ver(struct devlink_info_req *req, char *fw_ver) +{ + return devlink_info_version_running_put(req, + DEVLINK_INFO_VERSION_GENERIC_FW, + fw_ver); +} + +static int pdsc_dl_component_info_get(struct devlink *dl, + struct devlink_info_req *req, + struct netlink_ext_ack *extack) +{ + struct pdsc *pdsc = devlink_priv(dl); + u8 num_components; + int err; + int i; + + /* Pairs with WRITE_ONCE in pdsc_fw_components_invalidate(). + * Use READ_ONCE to get a consistent snapshot of num_components. + * pdsc_fw_components_invalidate() can zero it concurrently during + * firmware recovery; using the local copy avoids iterating zero + * times when we already decided the cache was valid. + */ + num_components = READ_ONCE(pdsc->fw_components.num_components); + if (!num_components) { + err = pdsc_get_component_info(pdsc); + if (err) + return pdsc_dl_report_fw_ver(req, + pdsc->dev_info.fw_version); + num_components = READ_ONCE(pdsc->fw_components.num_components); + if (!num_components) + return pdsc_dl_report_fw_ver(req, + pdsc->dev_info.fw_version); + } + + num_components = min_t(u16, num_components, + le16_to_cpu(pdsc->dev_ident.max_fw_slots)); + for (i = 0; i < num_components; i++) { + err = pdsc_dl_report_component(req, + &pdsc->fw_components.info[i]); + if (err) + return err; + } + + return 0; +} + static char *fw_slotnames[] = { "fw.goldfw", "fw.mainfwa", "fw.mainfwb", }; -int pdsc_dl_info_get(struct devlink *dl, struct devlink_info_req *req, - struct netlink_ext_ack *extack) +static int pdsc_dl_fw_list_info_get(struct devlink *dl, + struct devlink_info_req *req, + struct netlink_ext_ack *extack) { union pds_core_dev_cmd cmd = { .fw_control.opcode = PDS_CORE_CMD_FW_CONTROL, @@ -134,12 +240,41 @@ int pdsc_dl_info_get(struct devlink *dl, struct devlink_info_req *req, return err; } - err = devlink_info_version_running_put(req, - DEVLINK_INFO_VERSION_GENERIC_FW, - pdsc->dev_info.fw_version); + return 0; +} + +static int pdsc_dl_info_get_v1(struct devlink *dl, + struct devlink_info_req *req, + struct netlink_ext_ack *extack) +{ + struct pdsc *pdsc = devlink_priv(dl); + int err; + + err = pdsc_dl_fw_list_info_get(dl, req, extack); if (err) return err; + /* Version 1: report fw from dev_info (running only) */ + return pdsc_dl_report_fw_ver(req, pdsc->dev_info.fw_version); +} + +int pdsc_dl_info_get(struct devlink *dl, struct devlink_info_req *req, + struct netlink_ext_ack *extack) +{ + struct pdsc *pdsc = devlink_priv(dl); + char buf[32]; + int err; + + if (pdsc->dev_ident.version >= PDS_CORE_IDENTITY_VERSION_2) { + err = pdsc_dl_component_info_get(dl, req, extack); + if (err) + return err; + } else { + err = pdsc_dl_info_get_v1(dl, req, extack); + if (err) + return err; + } + snprintf(buf, sizeof(buf), "0x%x", pdsc->dev_info.asic_type); err = devlink_info_version_fixed_put(req, DEVLINK_INFO_VERSION_GENERIC_ASIC_ID, From b311af86b58f56ce81b85bd789c98e474851de90 Mon Sep 17 00:00:00 2001 From: "Nikhil P. Rao" Date: Thu, 30 Jul 2026 04:25:22 +0000 Subject: [PATCH 0979/1433] pds_core: add host backed memory support for firmware Some newer AMD/Pensando cards have minimal memory and there are cases where components, specifically in the control plane, need more memory. This series adds support for host backed DMA memory that can be used by the firmware for the previously mentioned cases. Host memory allocation is best-effort: if some allocations fail, the driver continues with whatever succeeded. Firmware gracefully degrades when less memory is available than requested. Signed-off-by: Vamsi Atluri Signed-off-by: Nikhil P. Rao Link: https://patch.msgid.link/20260730-upstream_v8-v12-5-136cd174ee85@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/pds_core/core.c | 160 +++++++++++++++++++++++ drivers/net/ethernet/amd/pds_core/core.h | 22 ++++ drivers/net/ethernet/amd/pds_core/main.c | 2 + include/linux/pds/pds_core_if.h | 64 +++++++++ 4 files changed, 248 insertions(+) diff --git a/drivers/net/ethernet/amd/pds_core/core.c b/drivers/net/ethernet/amd/pds_core/core.c index 51d21328c73d..26ddcbbfc14c 100644 --- a/drivers/net/ethernet/amd/pds_core/core.c +++ b/drivers/net/ethernet/amd/pds_core/core.c @@ -506,6 +506,7 @@ void pdsc_teardown(struct pdsc *pdsc, bool removing) pdsc->viftype_status = NULL; } + pdsc_host_mem_free(pdsc); pdsc_dev_uninit(pdsc); set_bit(PDSC_S_FW_DEAD, &pdsc->state); @@ -515,6 +516,7 @@ int pdsc_start(struct pdsc *pdsc) { pds_core_intr_mask(&pdsc->intr_ctrl[pdsc->adminqcq.intx], PDS_CORE_INTR_MASK_CLEAR); + pdsc_host_mem_add(pdsc); return 0; } @@ -681,3 +683,161 @@ void pdsc_health_thread(struct work_struct *work) out_unlock: mutex_unlock(&pdsc->config_lock); } + +static void pdsc_host_mem_del_one(struct pdsc *pdsc, u16 tag, u8 reason) +{ + union pds_core_dev_comp comp = {}; + union pds_core_dev_cmd cmd = { + .host_mem.opcode = PDS_CORE_CMD_HOST_MEM, + .host_mem.oper = PDS_CORE_HOST_MEM_DEL, + .host_mem.tag = cpu_to_le16(tag), + .host_mem.reason = reason, + }; + + dev_dbg(pdsc->dev, "Sending devcmd for mem del tag %d\n", tag); + pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); +} + +static int pdsc_host_mem_add_one(struct pdsc *pdsc, int index) +{ + struct pdsc_host_mem *hm = &pdsc->host_mem_reqs[index]; + union pds_core_dev_comp comp = {}; + union pds_core_dev_cmd cmd = {}; + int err; + + cmd.host_mem.opcode = PDS_CORE_CMD_HOST_MEM; + cmd.host_mem.oper = PDS_CORE_HOST_MEM_QUERY; + cmd.host_mem.index = cpu_to_le16(index); + dev_dbg(pdsc->dev, "Sending devcmd for mem query index %d\n", index); + err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); + if (err || comp.status != PDS_RC_SUCCESS) { + dev_err(pdsc->dev, "mem query failed err %d status %d\n", + err, comp.status); + return err ? err : -EIO; + } + hm->size = le32_to_cpu(comp.host_mem.size); + hm->tag = le16_to_cpu(comp.host_mem.tag); + dev_dbg(pdsc->dev, "mem query returned size %d tag %d\n", + hm->size, hm->tag); + + if (!hm->size || hm->size > PDSC_HOST_MEM_MAX_CONTIG) { + dev_err(pdsc->dev, "invalid size %d for tag %d\n", + hm->size, hm->tag); + err = -EINVAL; + goto err_del; + } + + hm->order = get_order(hm->size); + hm->pg = alloc_pages(GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN, hm->order); + if (!hm->pg) { + dev_warn(pdsc->dev, "alloc order %d failed for tag %d\n", + hm->order, hm->tag); + err = -ENOMEM; + goto err_del; + } + + hm->pa = dma_map_page(pdsc->dev, hm->pg, 0, hm->size, + DMA_BIDIRECTIONAL); + if (dma_mapping_error(pdsc->dev, hm->pa)) { + dev_err(pdsc->dev, "dma map failed for tag %d size %d\n", + hm->tag, hm->size); + __free_pages(hm->pg, hm->order); + hm->pg = NULL; + err = -EIO; + goto err_del; + } + + /* Track this allocation so pdsc_host_mem_free() can clean it up */ + pdsc->num_host_mem_reqs++; + + memset(&cmd, 0, sizeof(cmd)); + memset(&comp, 0, sizeof(comp)); + cmd.host_mem.opcode = PDS_CORE_CMD_HOST_MEM; + cmd.host_mem.oper = PDS_CORE_HOST_MEM_ADD; + cmd.host_mem.tag = cpu_to_le16(hm->tag); + cmd.host_mem.size = cpu_to_le32(hm->size); + cmd.host_mem.buf_pa = cpu_to_le64(hm->pa); + + dev_dbg(pdsc->dev, "Sending devcmd for mem add tag %d size %d pa %pad\n", + hm->tag, hm->size, &hm->pa); + err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); + if (err || comp.status != PDS_RC_SUCCESS) { + dev_err(pdsc->dev, "mem add failed err %d status %d for tag %d\n", + err, comp.status, hm->tag); + err = err ? err : -EIO; + goto err_del; + } + dev_dbg(pdsc->dev, "mem add completed for tag %d\n", hm->tag); + + return 0; + +err_del: + /* After MEM_QUERY succeeds, firmware expects MEM_ADD or MEM_DEL */ + pdsc_host_mem_del_one(pdsc, hm->tag, PDS_RC_ENOMEM); + return err; +} + +void pdsc_host_mem_add(struct pdsc *pdsc) +{ + union pds_core_dev_comp comp = {}; + union pds_core_dev_cmd cmd = {}; + u16 count; + int err; + int i; + + if (!(pdsc->dev_ident.capabilities & + cpu_to_le64(PDS_CORE_DEV_CAP_HOST_MEM))) + return; + + cmd.host_mem.opcode = PDS_CORE_CMD_HOST_MEM; + cmd.host_mem.oper = PDS_CORE_HOST_MEM_GET_COUNT; + cmd.host_mem.index = cpu_to_le16(PDSC_HOST_MEM_MAX_COUNT); + cmd.host_mem.max_contig = cpu_to_le32(PDSC_HOST_MEM_MAX_CONTIG); + dev_dbg(pdsc->dev, "Sending devcmd for mem get count max_contig %u\n", + PDSC_HOST_MEM_MAX_CONTIG); + err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); + if (err || comp.status != PDS_RC_SUCCESS) { + dev_err(pdsc->dev, "mem get count failed err %d status %d\n", + err, comp.status); + return; + } + + count = min(le16_to_cpu(comp.host_mem.count), + PDSC_HOST_MEM_MAX_COUNT); + dev_dbg(pdsc->dev, "mem get count returned count %d\n", count); + if (count == 0) + return; + + pdsc->host_mem_reqs = kzalloc_objs(*pdsc->host_mem_reqs, count, + GFP_KERNEL); + if (!pdsc->host_mem_reqs) { + dev_err(pdsc->dev, "failed to alloc host_mem_reqs array\n"); + return; + } + + for (i = 0; i < count; i++) { + err = pdsc_host_mem_add_one(pdsc, i); + if (err) + break; + } +} + +void pdsc_host_mem_free(struct pdsc *pdsc) +{ + int i; + + if (!pdsc->host_mem_reqs) + return; + + for (i = 0; i < pdsc->num_host_mem_reqs; i++) { + dma_unmap_page(pdsc->dev, pdsc->host_mem_reqs[i].pa, + pdsc->host_mem_reqs[i].size, + DMA_BIDIRECTIONAL); + __free_pages(pdsc->host_mem_reqs[i].pg, + pdsc->host_mem_reqs[i].order); + } + + kfree(pdsc->host_mem_reqs); + pdsc->host_mem_reqs = NULL; + pdsc->num_host_mem_reqs = 0; +} diff --git a/drivers/net/ethernet/amd/pds_core/core.h b/drivers/net/ethernet/amd/pds_core/core.h index 73356c74bb9f..085f2e988aa0 100644 --- a/drivers/net/ethernet/amd/pds_core/core.h +++ b/drivers/net/ethernet/amd/pds_core/core.h @@ -5,6 +5,7 @@ #define _PDSC_H_ #include +#include #include #include @@ -23,6 +24,12 @@ #define PDSC_SETUP_RECOVERY false #define PDSC_SETUP_INIT true +/* Use fixed 4MB instead of PAGE_SIZE << MAX_PAGE_ORDER to avoid + * cpu_to_le32() truncation on large-page configs + */ +#define PDSC_HOST_MEM_MAX_CONTIG (4 * 1024 * 1024) +#define PDSC_HOST_MEM_MAX_COUNT 256 + struct pdsc_deferred_dma { struct list_head list; dma_addr_t dma_addr; @@ -149,6 +156,14 @@ struct pdsc_viftype { struct pds_auxiliary_dev *padev; }; +struct pdsc_host_mem { + u32 size; + u16 tag; + u8 order; + struct page *pg; + dma_addr_t pa; +}; + /* No state flags set means we are in a steady running state */ enum pdsc_state_flags { PDSC_S_FW_DEAD, /* stopped, wait on startup or recovery */ @@ -210,6 +225,9 @@ struct pdsc { struct pdsc_viftype *viftype_status; struct work_struct pci_reset_work; + struct pdsc_host_mem *host_mem_reqs; + u16 num_host_mem_reqs; + struct pds_core_component_list_info fw_components; }; @@ -287,6 +305,7 @@ void pdsc_debugfs_add_viftype(struct pdsc *pdsc); void pdsc_debugfs_add_irqs(struct pdsc *pdsc); void pdsc_debugfs_add_qcq(struct pdsc *pdsc, struct pdsc_qcq *qcq); void pdsc_debugfs_del_qcq(struct pdsc_qcq *qcq); +void pdsc_debugfs_add_host_mem(struct pdsc *pdsc); int pdsc_err_to_errno(enum pds_core_status_code code); bool pdsc_is_fw_running(struct pdsc *pdsc); @@ -346,6 +365,9 @@ void pdsc_fw_down(struct pdsc *pdsc); void pdsc_fw_up(struct pdsc *pdsc); void pdsc_pci_reset_thread(struct work_struct *work); +void pdsc_host_mem_add(struct pdsc *pdsc); +void pdsc_host_mem_free(struct pdsc *pdsc); + void pdsc_deferred_dma_add(struct pdsc *pdsc, struct pdsc_deferred_dma *entry, dma_addr_t dma_addr, void *va, size_t size, enum dma_data_direction dir); diff --git a/drivers/net/ethernet/amd/pds_core/main.c b/drivers/net/ethernet/amd/pds_core/main.c index c15a1e376353..5e1b07850163 100644 --- a/drivers/net/ethernet/amd/pds_core/main.c +++ b/drivers/net/ethernet/amd/pds_core/main.c @@ -21,6 +21,8 @@ static const struct pci_device_id pdsc_id_table[] = { }; MODULE_DEVICE_TABLE(pci, pdsc_id_table); +static void pdsc_stop_health_thread(struct pdsc *pdsc); + static void pdsc_wdtimer_cb(struct timer_list *t) { struct pdsc *pdsc = timer_container_of(pdsc, t, wdtimer); diff --git a/include/linux/pds/pds_core_if.h b/include/linux/pds/pds_core_if.h index 5a1fafaccf20..901e1e628f89 100644 --- a/include/linux/pds/pds_core_if.h +++ b/include/linux/pds/pds_core_if.h @@ -46,6 +46,7 @@ enum pds_core_cmd_opcode { PDS_CORE_CMD_SEND_COMPONENT = 9, PDS_CORE_CMD_FINALIZE_UPDATE = 10, PDS_CORE_CMD_MATCH_RECORD_DESC = 11, + PDS_CORE_CMD_HOST_MEM = 12, /* SR/IOV commands */ PDS_CORE_CMD_VF_GETATTR = 60, @@ -110,9 +111,11 @@ struct pds_core_drv_identity { /** * enum pds_core_dev_capability - Device capabilities * @PDS_CORE_DEV_CAP_PLDM_FW_UPDATE: Device only supports FW update via PLDM + * @PDS_CORE_DEV_CAP_HOST_MEM: Device supports host memory for fw use */ enum pds_core_dev_capability { PDS_CORE_DEV_CAP_PLDM_FW_UPDATE = BIT(0), + PDS_CORE_DEV_CAP_HOST_MEM = BIT(1), }; #define PDS_DEV_TYPE_MAX 16 @@ -837,6 +840,65 @@ struct pds_core_match_record_desc_comp { u8 rsvd; }; +/** + * enum pds_core_host_mem_oper - HOST_MEM sub-operations + * @PDS_CORE_HOST_MEM_GET_COUNT: Query number of memory requests + * @PDS_CORE_HOST_MEM_QUERY: Query details of a memory request + * @PDS_CORE_HOST_MEM_ADD: Provide allocated memory to firmware + * @PDS_CORE_HOST_MEM_DEL: Notify firmware of memory deallocation + */ +enum pds_core_host_mem_oper { + PDS_CORE_HOST_MEM_GET_COUNT = 0, + PDS_CORE_HOST_MEM_QUERY = 1, + PDS_CORE_HOST_MEM_ADD = 2, + PDS_CORE_HOST_MEM_DEL = 3, +}; + +/** + * struct pds_core_host_mem_cmd - HOST_MEM command + * @opcode: Opcode PDS_CORE_CMD_HOST_MEM + * @oper: Operation (enum pds_core_host_mem_oper) + * @index: Memory request index (GET_COUNT: max_count, QUERY: index) + * @tag: Tag for this memory request (ADD/DEL) + * @reason: Reason for deletion (DEL only) + * @rsvd: Reserved + * @max_contig: Maximum contiguous memory size (GET_COUNT only) + * @size: Size of memory in bytes (ADD only) + * @buf_pa: DMA address of memory (ADD only) + * + * Unified command for all host memory operations. Fields are reused + * across operations to minimize opcode space usage. + */ +struct pds_core_host_mem_cmd { + u8 opcode; + u8 oper; + __le16 index; + __le16 tag; + u8 reason; + u8 rsvd; + __le32 max_contig; + __le32 size; + __le64 buf_pa; +}; + +/** + * struct pds_core_host_mem_comp - HOST_MEM completion + * @status: Status of the command (enum pds_core_status_code) + * @oper: Operation that was performed + * @count: Number of memory requests (GET_COUNT) + * @size: Size of memory request in bytes (QUERY) + * @tag: Tag for this memory request (QUERY/DEL) + * @rsvd: Reserved + */ +struct pds_core_host_mem_comp { + u8 status; + u8 oper; + __le16 count; + __le32 size; + __le16 tag; + u8 rsvd[6]; +}; + /* * union pds_core_dev_cmd - Overlay of core device command structures */ @@ -860,6 +922,7 @@ union pds_core_dev_cmd { struct pds_core_send_component_cmd send_component; struct pds_core_finalize_update_cmd finalize_update; struct pds_core_match_record_desc_cmd match_record_desc; + struct pds_core_host_mem_cmd host_mem; }; /* @@ -885,6 +948,7 @@ union pds_core_dev_comp { struct pds_core_send_component_comp send_component; struct pds_core_finalize_update_comp finalize_update; struct pds_core_match_record_desc_comp match_record_desc; + struct pds_core_host_mem_comp host_mem; }; /** From 0b5091b64ad6f870c2484211cd02f96056e64515 Mon Sep 17 00:00:00 2001 From: "Nikhil P. Rao" Date: Thu, 30 Jul 2026 04:25:23 +0000 Subject: [PATCH 0980/1433] pds_core: add debugfs support for host backed memory Add debugfs entry to dump host backed memory allocations for debug purposes. Signed-off-by: Vamsi Atluri Signed-off-by: Nikhil P. Rao Link: https://patch.msgid.link/20260730-upstream_v8-v12-6-136cd174ee85@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/pds_core/core.c | 2 + drivers/net/ethernet/amd/pds_core/core.h | 1 + drivers/net/ethernet/amd/pds_core/debugfs.c | 45 +++++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/drivers/net/ethernet/amd/pds_core/core.c b/drivers/net/ethernet/amd/pds_core/core.c index 26ddcbbfc14c..922e3ec8af1b 100644 --- a/drivers/net/ethernet/amd/pds_core/core.c +++ b/drivers/net/ethernet/amd/pds_core/core.c @@ -506,6 +506,7 @@ void pdsc_teardown(struct pdsc *pdsc, bool removing) pdsc->viftype_status = NULL; } + pdsc_debugfs_del_host_mem(pdsc); pdsc_host_mem_free(pdsc); pdsc_dev_uninit(pdsc); @@ -517,6 +518,7 @@ int pdsc_start(struct pdsc *pdsc) pds_core_intr_mask(&pdsc->intr_ctrl[pdsc->adminqcq.intx], PDS_CORE_INTR_MASK_CLEAR); pdsc_host_mem_add(pdsc); + pdsc_debugfs_add_host_mem(pdsc); return 0; } diff --git a/drivers/net/ethernet/amd/pds_core/core.h b/drivers/net/ethernet/amd/pds_core/core.h index 085f2e988aa0..791756a50870 100644 --- a/drivers/net/ethernet/amd/pds_core/core.h +++ b/drivers/net/ethernet/amd/pds_core/core.h @@ -306,6 +306,7 @@ void pdsc_debugfs_add_irqs(struct pdsc *pdsc); void pdsc_debugfs_add_qcq(struct pdsc *pdsc, struct pdsc_qcq *qcq); void pdsc_debugfs_del_qcq(struct pdsc_qcq *qcq); void pdsc_debugfs_add_host_mem(struct pdsc *pdsc); +void pdsc_debugfs_del_host_mem(struct pdsc *pdsc); int pdsc_err_to_errno(enum pds_core_status_code code); bool pdsc_is_fw_running(struct pdsc *pdsc); diff --git a/drivers/net/ethernet/amd/pds_core/debugfs.c b/drivers/net/ethernet/amd/pds_core/debugfs.c index 810a0cd9bcac..ef0a1b7d159b 100644 --- a/drivers/net/ethernet/amd/pds_core/debugfs.c +++ b/drivers/net/ethernet/amd/pds_core/debugfs.c @@ -178,3 +178,48 @@ void pdsc_debugfs_del_qcq(struct pdsc_qcq *qcq) debugfs_remove_recursive(qcq->dentry); qcq->dentry = NULL; } + +static int host_mem_show(struct seq_file *seq, void *v) +{ + struct pdsc *pdsc = seq->private; + struct pdsc_host_mem *hm; + int i; + + if (!pdsc->host_mem_reqs || pdsc->num_host_mem_reqs == 0) { + seq_puts(seq, "No host memory allocated\n"); + return 0; + } + + seq_printf(seq, "Host memory requests: %u\n\n", + pdsc->num_host_mem_reqs); + seq_puts(seq, "Tag Size Order PA\n"); + seq_puts(seq, "--- ---- ----- --\n"); + + for (i = 0; i < pdsc->num_host_mem_reqs; i++) { + hm = &pdsc->host_mem_reqs[i]; + + if (!hm->pg) + continue; + + seq_printf(seq, "%-6u %-12u %-6u %pad\n", + hm->tag, hm->size, hm->order, &hm->pa); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(host_mem); + +void pdsc_debugfs_add_host_mem(struct pdsc *pdsc) +{ + if (!(pdsc->dev_ident.capabilities & + cpu_to_le64(PDS_CORE_DEV_CAP_HOST_MEM))) + return; + + debugfs_create_file("host_mem", 0400, pdsc->dentry, + pdsc, &host_mem_fops); +} + +void pdsc_debugfs_del_host_mem(struct pdsc *pdsc) +{ + debugfs_lookup_and_remove("host_mem", pdsc->dentry); +} From 089ca284afb0f3b2870a5ce41524cbc91ed350d4 Mon Sep 17 00:00:00 2001 From: Jinhui Guo Date: Thu, 30 Jul 2026 13:13:41 +0800 Subject: [PATCH 0981/1433] net: usb: cdc_ether: add quirk for AMI BMC stale link events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On AMD Genoa/Turin platforms the BMC-provided USB-Ethernet gadget (American Megatrends, VID 0x046b PID 0xffb0) intermittently fails to respond to ARP after AC cold boot. usbmon captures a stale NETWORK_CONNECTION(off) immediately followed by NETWORK_CONNECTION(on) on the interrupt endpoint (~130us apart) after enumeration. Because alloc_netdev() leaves __LINK_STATE_NOCARRIER cleared, netif_carrier_ok() returns true when the spurious OFF arrives, so usbnet_cdc_status() cannot recognise it as redundant and schedules EVENT_LINK_CHANGE. __handle_link_change() then calls unlink_urbs(), killing ~60 rx URBs whose payload has already been DMA'd into memory — xHCI trace confirms them completing as -ECONNRESET with non-zero residual length. rx_complete() drops these unconditionally. The following ON restores the carrier and re-submits URBs, but the ARP reply is already lost; the interface looks "up but silent" until ifdown/ifup. Fix this by adding a device-specific quirk with FLAG_LINK_INTR set, which makes usbnet_probe() call netif_carrier_off() after bind. With initial carrier == OFF, usbnet_cdc_status() recognises the spurious OFF as matching the current state and drops it; the subsequent ON is the first real event and brings the link up cleanly without ever tearing down the rx queue. The scheduled link-change kevent is harmless because EVENT_DEV_OPEN is not yet set at probe time. This is applied as a device-specific quirk rather than a change to the shared cdc_info driver_info because some CDC devices never send NETWORK_CONNECTION notifications; forcing carrier off for them would leave the link permanently DOWN. Restricting the change to this VID/PID keeps that class of device untouched. Tested on Genoa and Turin across 100+ AC cold boot cycles; ping first-packet success rate went from intermittent to 100%. Signed-off-by: Jinhui Guo Link: https://patch.msgid.link/20260730051341.24930-1-guojinhui.liam@bytedance.com Signed-off-by: Jakub Kicinski --- drivers/net/usb/cdc_ether.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/net/usb/cdc_ether.c b/drivers/net/usb/cdc_ether.c index a0a5740590b9..b5e4b195b69a 100644 --- a/drivers/net/usb/cdc_ether.c +++ b/drivers/net/usb/cdc_ether.c @@ -538,6 +538,16 @@ static const struct driver_info cdc_info = { .manage_power = usbnet_manage_power, }; +static const struct driver_info ami_bmc_info = { + .description = "AMI BMC USB Ethernet", + .flags = FLAG_ETHER | FLAG_POINTTOPOINT | FLAG_LINK_INTR, + .bind = usbnet_cdc_bind, + .unbind = usbnet_cdc_unbind, + .status = usbnet_cdc_status, + .set_rx_mode = usbnet_cdc_update_filter, + .manage_power = usbnet_manage_power, +}; + static const struct driver_info zte_cdc_info = { .description = "ZTE CDC Ethernet Device", .flags = FLAG_ETHER | FLAG_POINTTOPOINT, @@ -946,6 +956,12 @@ static const struct usb_device_id products[] = { USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), .driver_info = (unsigned long)&wwan_info, +}, { + /* AMI BMC gadget */ + USB_DEVICE_AND_INTERFACE_INFO(0x046b, 0xffb0, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, + USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&ami_bmc_info, }, { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), From 9cb1d062b09437ec83d2651d21719381c47ea4ac Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 3 Aug 2026 14:19:44 -0700 Subject: [PATCH 0982/1433] selftests: drv-net: print device info at the start When a reviewer asks a developer to run an upstream test during code review, it's often ambiguous whether the test was actually run against a real device, or just against netdevsim. Print the driver name and ifname at the start of the test, e.g.: # Interface: enp0s13f0u1u4, driver: r8152 TAP version 13 1..1 ok 1 ... Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260803211944.2166211-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- .../selftests/drivers/net/lib/py/env.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py index b1c6f6cef7dd..25903f580b40 100644 --- a/tools/testing/selftests/drivers/net/lib/py/env.py +++ b/tools/testing/selftests/drivers/net/lib/py/env.py @@ -7,7 +7,7 @@ import time import json from pathlib import Path from lib.py import KsftSkipEx, KsftXfailEx -from lib.py import ksft_setup, wait_file +from lib.py import ksft_pr, ksft_setup, wait_file from lib.py import cmd, ethtool, ip, CmdExitFailure from lib.py import NetNS, NetdevSimDev, UserNetNS from .remote import Remote @@ -31,6 +31,7 @@ class NetDrvEnvBase: # Following attrs must be set be inheriting classes self.dev = None + self.ifname = None def _load_env_file(self): env = os.environ.copy() @@ -58,6 +59,22 @@ class NetDrvEnvBase: def __del__(self): pass + def _print_dev_info(self): + """ + Show whether the test ran on real hardware or netdevsim. + Useful to confirm when results are shared on the mailing list. + """ + driver = "unknown" + try: + info = ethtool(f"-i {self.ifname}").stdout + for line in info.splitlines(): + if line.startswith("driver:"): + driver = line.split(':', 1)[1].strip() or driver + break + except (CmdExitFailure, FileNotFoundError): + pass + ksft_pr(f"Interface: {self.ifname}, driver: {driver}") + def __enter__(self): ip(f"link set dev {self.dev['ifname']} up") wait_file(f"/sys/class/net/{self.dev['ifname']}/carrier", @@ -94,6 +111,7 @@ class NetDrvEnv(NetDrvEnvBase): self.dev = self._ns.nsims[0].dev self.ifname = self.dev['ifname'] self.ifindex = self.dev['ifindex'] + self._print_dev_info() def __del__(self): if self._ns: @@ -164,6 +182,7 @@ class NetDrvEpEnv(NetDrvEnvBase): self.ifname = self.dev['ifname'] self.ifindex = self.dev['ifindex'] + self._print_dev_info() # resolve remote interface name self.remote_ifname = self.resolve_remote_ifc() From 504ef04e8674c918c22ebc16356424ac1346dd60 Mon Sep 17 00:00:00 2001 From: Deep Shah Date: Sat, 1 Aug 2026 22:29:22 +0000 Subject: [PATCH 0983/1433] ptp: reject frequency adjustments that overflow scaled_ppm_to_ppb() ptp_clock_adjtime() validates an ADJ_FREQUENCY request by converting the requested scaled ppm to ppb and comparing it against ops->max_adj: long ppb = scaled_ppm_to_ppb(tx->freq); if (ppb > ops->max_adj || ppb < -ops->max_adj) return -ERANGE; scaled_ppm_to_ppb() computes (1 + ppm) * 125 >> 13 in s64. For a sufficiently large tx->freq the multiplication overflows s64 and wraps, so the resulting ppb can fall back within [-max_adj, max_adj] and pass the check. The unclamped tx->freq is then handed to ->adjfine(), where drivers scale it again (e.g. scaled_ppm * 762939453125 in ptp_idt82p33) and program a bogus frequency word. For example tx->freq = 147573952589676412 makes (1 + ppm) * 125 equal 2^64 + 9, which wraps to ppb == 0 and is accepted. The caller already has write access to the PHC, so this hardens the max_adj sanity check rather than crossing a privilege boundary, and well-behaved user space (e.g. ptp4l) never requests such values. It is a follow-up to commit 475b92f93216 ("ptp: improve max_adj check against unreasonable values"), which handled the analogous s32 narrowing but not this multiplication overflow. Detect the overflow with check_*_overflow() and reject the request in ptp_clock_adjtime() instead of acting on the wrapped value. Signed-off-by: Deep Shah Reviewed-by: Vadim Fedorenko Acked-by: Richard Cochran Link: https://patch.msgid.link/20260801222923.39017-2-deepshah146@gmail.com Signed-off-by: Jakub Kicinski --- drivers/ptp/ptp_clock.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/ptp/ptp_clock.c b/drivers/ptp/ptp_clock.c index d6f54ccaf93b..4111342d64f0 100644 --- a/drivers/ptp/ptp_clock.c +++ b/drivers/ptp/ptp_clock.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -159,7 +160,18 @@ static int ptp_clock_adjtime(struct posix_clock *pc, struct __kernel_timex *tx) delta = ktime_to_ns(kt); err = ops->adjtime(ops, delta); } else if (tx->modes & ADJ_FREQUENCY) { - long ppb = scaled_ppm_to_ppb(tx->freq); + long ppb; + s64 tmp; + + /* + * scaled_ppm_to_ppb() multiplies (1 + freq) by 125 in s64; + * reject a ->freq large enough to overflow that, which would + * otherwise wrap the result back into the max_adj range. + */ + if (check_add_overflow((s64)tx->freq, (s64)1, &tmp) || + check_mul_overflow(tmp, (s64)125, &tmp)) + return -ERANGE; + ppb = scaled_ppm_to_ppb(tx->freq); if (ppb > ops->max_adj || ppb < -ops->max_adj) return -ERANGE; err = ops->adjfine(ops, tx->freq); From b8180f7977aa6f3a4e36de692356dca7b3d95141 Mon Sep 17 00:00:00 2001 From: Junyang Han Date: Sun, 2 Aug 2026 16:21:04 +0800 Subject: [PATCH 0984/1433] dinghai: add ZTE network driver support Add basic framework for ZTE DingHai ethernet PF driver, including Kconfig/Makefile build support and PCIe device probe/remove skeleton. Signed-off-by: Junyang Han Link: https://patch.msgid.link/202608021621043761zZMwCny1e6y0TRFLQHxx@zte.com.cn Signed-off-by: Jakub Kicinski --- MAINTAINERS | 6 + drivers/net/ethernet/Kconfig | 1 + drivers/net/ethernet/Makefile | 1 + drivers/net/ethernet/zte/Kconfig | 20 +++ drivers/net/ethernet/zte/Makefile | 6 + drivers/net/ethernet/zte/dinghai/Kconfig | 34 +++++ drivers/net/ethernet/zte/dinghai/Makefile | 7 + drivers/net/ethernet/zte/dinghai/en_pf.c | 174 ++++++++++++++++++++++ drivers/net/ethernet/zte/dinghai/en_pf.h | 29 ++++ 9 files changed, 278 insertions(+) create mode 100644 drivers/net/ethernet/zte/Kconfig create mode 100644 drivers/net/ethernet/zte/Makefile create mode 100644 drivers/net/ethernet/zte/dinghai/Kconfig create mode 100644 drivers/net/ethernet/zte/dinghai/Makefile create mode 100644 drivers/net/ethernet/zte/dinghai/en_pf.c create mode 100644 drivers/net/ethernet/zte/dinghai/en_pf.h diff --git a/MAINTAINERS b/MAINTAINERS index 932ea1db048e..03bfb79fca2a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29869,6 +29869,12 @@ S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git F: sound/hda/codecs/senarytech.c +ZTE DINGHAI ETHERNET DRIVER +M: Junyang Han +L: netdev@vger.kernel.org +S: Maintained +F: drivers/net/ethernet/zte/ + THE REST M: Linus Torvalds L: linux-kernel@vger.kernel.org diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig index 78c79ad7bba5..8581ccba1505 100644 --- a/drivers/net/ethernet/Kconfig +++ b/drivers/net/ethernet/Kconfig @@ -189,5 +189,6 @@ source "drivers/net/ethernet/wangxun/Kconfig" source "drivers/net/ethernet/wiznet/Kconfig" source "drivers/net/ethernet/xilinx/Kconfig" source "drivers/net/ethernet/xircom/Kconfig" +source "drivers/net/ethernet/zte/Kconfig" endif # ETHERNET diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile index bba55d9af387..2b1153d35b52 100644 --- a/drivers/net/ethernet/Makefile +++ b/drivers/net/ethernet/Makefile @@ -105,3 +105,4 @@ obj-$(CONFIG_NET_VENDOR_XIRCOM) += xircom/ obj-$(CONFIG_NET_VENDOR_SYNOPSYS) += synopsys/ obj-$(CONFIG_NET_VENDOR_PENSANDO) += pensando/ obj-$(CONFIG_OA_TC6) += oa_tc6.o +obj-$(CONFIG_NET_VENDOR_ZTE) += zte/ diff --git a/drivers/net/ethernet/zte/Kconfig b/drivers/net/ethernet/zte/Kconfig new file mode 100644 index 000000000000..b95c2fc7db77 --- /dev/null +++ b/drivers/net/ethernet/zte/Kconfig @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# ZTE driver configuration +# + +config NET_VENDOR_ZTE + bool "ZTE devices" + default y + help + If you have a network (Ethernet) card belonging to this class, say Y. + Note that the answer to this question doesn't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about Zte cards. If you say Y, you will be asked + for your specific card in the following questions. + +if NET_VENDOR_ZTE + +source "drivers/net/ethernet/zte/dinghai/Kconfig" + +endif # NET_VENDOR_ZTE diff --git a/drivers/net/ethernet/zte/Makefile b/drivers/net/ethernet/zte/Makefile new file mode 100644 index 000000000000..cd9929b61559 --- /dev/null +++ b/drivers/net/ethernet/zte/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the ZTE device drivers +# + +obj-$(CONFIG_DINGHAI) += dinghai/ diff --git a/drivers/net/ethernet/zte/dinghai/Kconfig b/drivers/net/ethernet/zte/dinghai/Kconfig new file mode 100644 index 000000000000..121be3bf7707 --- /dev/null +++ b/drivers/net/ethernet/zte/dinghai/Kconfig @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# ZTE DingHai Ethernet driver configuration +# + +config DINGHAI + bool "ZTE DingHai Ethernet driver" + depends on PCI + select NET_DEVLINK + help + This driver supports ZTE DingHai Ethernet devices. + + DingHai is a high-performance Ethernet controller that supports + multiple features including hardware offloading, SR-IOV, and + advanced virtualization capabilities. + + If you say Y here, you can select specific driver variants below. + + If unsure, say N. + +if DINGHAI + +config DINGHAI_PF + tristate "ZTE DingHai PF (Physical Function) driver" + help + This driver supports ZTE DingHai PCI Express Ethernet + adapters (PF). + + To compile this driver as a module, choose M here. The module + will be named dinghai10e. + + If unsure, say N. + +endif # DINGHAI diff --git a/drivers/net/ethernet/zte/dinghai/Makefile b/drivers/net/ethernet/zte/dinghai/Makefile new file mode 100644 index 000000000000..e2f4da33df59 --- /dev/null +++ b/drivers/net/ethernet/zte/dinghai/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for ZTE DingHai Ethernet driver +# + +obj-$(CONFIG_DINGHAI_PF) += dinghai10e.o +dinghai10e-y := en_pf.o diff --git a/drivers/net/ethernet/zte/dinghai/en_pf.c b/drivers/net/ethernet/zte/dinghai/en_pf.c new file mode 100644 index 000000000000..e7ff6954e745 --- /dev/null +++ b/drivers/net/ethernet/zte/dinghai/en_pf.c @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ZTE DingHai Ethernet driver + * Copyright (c) 2022-2026, ZTE Corporation. + */ + +#include +#include +#include +#include +#include "en_pf.h" + +MODULE_AUTHOR("Junyang Han "); +MODULE_DESCRIPTION("ZTE DingHai series Ethernet driver"); +MODULE_LICENSE("GPL"); + +static const struct devlink_ops zxdh_pf_devlink_ops = {}; + +static const struct pci_device_id zxdh_pf_pci_table[] = { + { PCI_DEVICE(ZXDH_PF_VENDOR_ID, ZXDH_PF_DEVICE_ID) }, + { PCI_DEVICE(ZXDH_PF_VENDOR_ID, ZXDH_VF_DEVICE_ID) }, + { } +}; + +MODULE_DEVICE_TABLE(pci, zxdh_pf_pci_table); + +void *zxdh_core_alloc_priv(struct zxdh_core_dev *zxdh_dev, size_t size) +{ + void *priv = kzalloc(size, GFP_KERNEL); + + if (priv) + zxdh_dev->priv = priv; + return priv; +} + +void zxdh_core_free_priv(struct zxdh_core_dev *zxdh_dev) +{ + kfree(zxdh_dev->priv); +} + +static int zxdh_pf_pci_init(struct zxdh_core_dev *zxdh_dev) +{ + struct zxdh_pf_dev *pf_dev = zxdh_dev->priv; + int ret; + + pci_set_drvdata(zxdh_dev->pdev, zxdh_dev); + + ret = pci_enable_device(zxdh_dev->pdev); + if (ret) { + dev_err(zxdh_dev->device, "pci_enable_device failed: %d\n", ret); + return ret; + } + + dma_set_mask_and_coherent(zxdh_dev->device, DMA_BIT_MASK(64)); + + ret = pci_request_selected_regions(zxdh_dev->pdev, + pci_select_bars(zxdh_dev->pdev, IORESOURCE_MEM), + "dh-pf"); + if (ret) { + dev_err(zxdh_dev->device, "pci_request_selected_regions failed: %d\n", ret); + goto err_pci; + } + + pci_set_master(zxdh_dev->pdev); + ret = pci_save_state(zxdh_dev->pdev); + if (ret) { + dev_err(zxdh_dev->device, "pci_save_state failed: %d\n", ret); + goto err_pci_save_state; + } + + if (!(pci_resource_flags(zxdh_dev->pdev, 0) & IORESOURCE_MEM)) { + ret = -ENODEV; + dev_err(zxdh_dev->device, "BAR 0 is not an MMIO resource\n"); + goto err_pci_save_state; + } + + pf_dev->pci_ioremap_addr[0] = + ioremap(pci_resource_start(zxdh_dev->pdev, 0), + pci_resource_len(zxdh_dev->pdev, 0)); + if (!pf_dev->pci_ioremap_addr[0]) { + ret = -ENOMEM; + dev_err(zxdh_dev->device, "dh pf pci ioremap failed\n"); + goto err_pci_save_state; + } + + return 0; + +err_pci_save_state: + pci_release_selected_regions(zxdh_dev->pdev, + pci_select_bars(zxdh_dev->pdev, IORESOURCE_MEM)); +err_pci: + pci_disable_device(zxdh_dev->pdev); + return ret; +} + +void zxdh_pf_pci_close(struct zxdh_core_dev *zxdh_dev) +{ + struct zxdh_pf_dev *pf_dev = zxdh_dev->priv; + + iounmap(pf_dev->pci_ioremap_addr[0]); + pci_release_selected_regions(zxdh_dev->pdev, + pci_select_bars(zxdh_dev->pdev, IORESOURCE_MEM)); + pci_disable_device(zxdh_dev->pdev); +} + +static int zxdh_pf_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct zxdh_core_dev *zxdh_dev; + struct zxdh_pf_dev *pf_dev; + struct devlink *devlink; + int ret; + + devlink = devlink_alloc(&zxdh_pf_devlink_ops, sizeof(struct zxdh_core_dev), + &pdev->dev); + if (!devlink) + return -ENOMEM; + + zxdh_dev = devlink_priv(devlink); + zxdh_dev->device = &pdev->dev; + zxdh_dev->pdev = pdev; + zxdh_dev->devlink = devlink; + + pf_dev = zxdh_core_alloc_priv(zxdh_dev, sizeof(*pf_dev)); + if (!pf_dev) { + dev_err(&pdev->dev, "zxdh_pf_dev alloc failed\n"); + ret = -ENOMEM; + goto err_pf_dev; + } + + ret = zxdh_pf_pci_init(zxdh_dev); + if (ret) { + dev_err(&pdev->dev, "zxdh_pf_pci_init failed: %d\n", ret); + goto err_pci_init; + } + + devlink_register(devlink); + + return 0; + +err_pci_init: + zxdh_core_free_priv(zxdh_dev); +err_pf_dev: + devlink_free(devlink); + return ret; +} + +static void zxdh_pf_remove(struct pci_dev *pdev) +{ + struct zxdh_core_dev *zxdh_dev = pci_get_drvdata(pdev); + struct devlink *devlink = priv_to_devlink(zxdh_dev); + + devlink_unregister(devlink); + zxdh_pf_pci_close(zxdh_dev); + zxdh_core_free_priv(zxdh_dev); + devlink_free(devlink); + pci_set_drvdata(pdev, NULL); +} + +static void zxdh_pf_shutdown(struct pci_dev *pdev) +{ + if (system_state == SYSTEM_POWER_OFF) + pci_set_power_state(pdev, PCI_D3hot); + pci_disable_device(pdev); +} + +static struct pci_driver zxdh_pf_driver = { + .name = "dinghai10e", + .id_table = zxdh_pf_pci_table, + .probe = zxdh_pf_probe, + .remove = zxdh_pf_remove, + .shutdown = zxdh_pf_shutdown, +}; + +module_pci_driver(zxdh_pf_driver); diff --git a/drivers/net/ethernet/zte/dinghai/en_pf.h b/drivers/net/ethernet/zte/dinghai/en_pf.h new file mode 100644 index 000000000000..f6b9dc94a57d --- /dev/null +++ b/drivers/net/ethernet/zte/dinghai/en_pf.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * ZTE DingHai Ethernet driver - PF header + * Copyright (c) 2022-2026, ZTE Corporation. + */ + +#ifndef __ZXDH_EN_PF_H__ +#define __ZXDH_EN_PF_H__ + +#define ZXDH_PF_VENDOR_ID 0x1cf2 +#define ZXDH_PF_DEVICE_ID 0x8040 +#define ZXDH_VF_DEVICE_ID 0x8041 + +struct zxdh_core_dev { + struct device *device; + struct pci_dev *pdev; + struct devlink *devlink; + void *priv; +}; + +struct zxdh_pf_dev { + void __iomem *pci_ioremap_addr[6]; +}; + +void *zxdh_core_alloc_priv(struct zxdh_core_dev *zxdh_dev, size_t size); +void zxdh_core_free_priv(struct zxdh_core_dev *zxdh_dev); +void zxdh_pf_pci_close(struct zxdh_core_dev *zxdh_dev); + +#endif /* __ZXDH_EN_PF_H__ */ From 19df707eee6ea5f6b7664b917d874b981898016a Mon Sep 17 00:00:00 2001 From: Junyang Han Date: Sun, 2 Aug 2026 16:24:31 +0800 Subject: [PATCH 0985/1433] =?UTF-8?q?dinghai:=20add=20hardware=20register?= =?UTF-8?q?=20access=20and=C2=A0PCI=20capability=20scanning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement PCI configuration space access, BAR mapping, capability scanning (common/notify/device), and hardware queue register definitions for DingHai PF device. Signed-off-by: Junyang Han Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/zte/dinghai/dh_queue.h | 56 ++++ drivers/net/ethernet/zte/dinghai/en_pf.c | 275 ++++++++++++++++++++ drivers/net/ethernet/zte/dinghai/en_pf.h | 46 ++++ 3 files changed, 377 insertions(+) create mode 100644 drivers/net/ethernet/zte/dinghai/dh_queue.h diff --git a/drivers/net/ethernet/zte/dinghai/dh_queue.h b/drivers/net/ethernet/zte/dinghai/dh_queue.h new file mode 100644 index 000000000000..5b63895c1ea1 --- /dev/null +++ b/drivers/net/ethernet/zte/dinghai/dh_queue.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * ZTE DingHai Ethernet driver - PCI capability definitions + * Copyright (c) 2022-2026, ZTE Corporation. + */ + +#ifndef __DH_QUEUE_H__ +#define __DH_QUEUE_H__ + +#include + +/* This is the PCI capability header: */ +struct zxdh_pf_pci_cap { + __u8 cap_vndr; /* Generic PCI field: PCI_CAP_ID_VNDR */ + __u8 cap_next; /* Generic PCI field: next ptr. */ + __u8 cap_len; /* Generic PCI field: capability length */ + __u8 cfg_type; /* Identifies the structure. */ + __u8 bar; /* Where to find it. */ + __u8 id; /* Multiple capabilities of the same type */ + __u8 padding[2]; /* Pad to full dword. */ + __le32 offset; /* Offset within bar. */ + __le32 length; /* Length of the structure, in bytes. */ +}; + +/* Fields in ZXDH_PF_PCI_CAP_COMMON_CFG: */ +struct zxdh_pf_pci_common_cfg { + /* About the whole device. */ + __le32 device_feature_select; /* read-write */ + __le32 device_feature; /* read-only */ + __le32 guest_feature_select; /* read-write */ + __le32 guest_feature; /* read-write */ + __le16 msix_config; /* read-write */ + __le16 num_queues; /* read-only */ + __u8 device_status; /* read-write */ + __u8 config_generation; /* read-only */ + + /* About a specific virtqueue. */ + __le16 queue_select; /* read-write */ + __le16 queue_size; /* read-write, power of 2. */ + __le16 queue_msix_vector; /* read-write */ + __le16 queue_enable; /* read-write */ + __le16 queue_notify_off; /* read-only */ + __le32 queue_desc_lo; /* read-write */ + __le32 queue_desc_hi; /* read-write */ + __le32 queue_avail_lo; /* read-write */ + __le32 queue_avail_hi; /* read-write */ + __le32 queue_used_lo; /* read-write */ + __le32 queue_used_hi; /* read-write */ +}; + +struct zxdh_pf_pci_notify_cap { + struct zxdh_pf_pci_cap cap; + __le32 notify_off_multiplier; /* Multiplier for queue_notify_off. */ +}; + +#endif /* __DH_QUEUE_H__ */ diff --git a/drivers/net/ethernet/zte/dinghai/en_pf.c b/drivers/net/ethernet/zte/dinghai/en_pf.c index e7ff6954e745..86d437408820 100644 --- a/drivers/net/ethernet/zte/dinghai/en_pf.c +++ b/drivers/net/ethernet/zte/dinghai/en_pf.c @@ -9,6 +9,7 @@ #include #include #include "en_pf.h" +#include "dh_queue.h" MODULE_AUTHOR("Junyang Han "); MODULE_DESCRIPTION("ZTE DingHai series Ethernet driver"); @@ -103,6 +104,271 @@ void zxdh_pf_pci_close(struct zxdh_core_dev *zxdh_dev) pci_disable_device(zxdh_dev->pdev); } +int zxdh_pf_pci_find_capability(struct pci_dev *pdev, u8 cfg_type, + u32 ioresource_types, int *bars) +{ + int pos; + u8 type; + u8 bar; + + for (pos = pci_find_capability(pdev, PCI_CAP_ID_VNDR); pos > 0; + pos = pci_find_next_capability(pdev, pos, PCI_CAP_ID_VNDR)) { + pci_read_config_byte(pdev, + pos + offsetof(struct zxdh_pf_pci_cap, + cfg_type), &type); + pci_read_config_byte(pdev, + pos + offsetof(struct zxdh_pf_pci_cap, bar), &bar); + + /* ignore structures with reserved BAR values */ + if (bar > ZXDH_PF_MAX_BAR_VAL) + continue; + + if (type == cfg_type) { + if (pci_resource_len(pdev, bar) && + pci_resource_flags(pdev, bar) & ioresource_types) { + *bars |= (1 << bar); + return pos; + } + } + } + + return 0; +} + +void __iomem *zxdh_pf_map_capability(struct zxdh_core_dev *zxdh_dev, int off, + size_t minlen, u32 align, + u32 start, u32 size, + size_t *len, resource_size_t *pa, + u32 *bar_off) +{ + struct pci_dev *pdev = zxdh_dev->pdev; + void __iomem *p; + u32 offset; + u32 length; + u8 bar; + + pci_read_config_byte(pdev, + off + offsetof(struct zxdh_pf_pci_cap, bar), &bar); + + if (bar > ZXDH_PF_MAX_BAR_VAL) { + dev_err(zxdh_dev->device, "invalid bar %u\n", bar); + return NULL; + } + + pci_read_config_dword(pdev, + off + offsetof(struct zxdh_pf_pci_cap, + offset), &offset); + pci_read_config_dword(pdev, + off + offsetof(struct zxdh_pf_pci_cap, + length), &length); + + if (bar_off) + *bar_off = offset; + + if (length <= start) { + dev_err(zxdh_dev->device, "bad capability len %u (>%u expected)\n", + length, start); + return NULL; + } + + if (length - start < minlen) { + dev_err(zxdh_dev->device, "bad capability len %u (>=%zu expected)\n", + length, minlen); + return NULL; + } + + length -= start; + if (start + offset < offset) { + dev_err(zxdh_dev->device, "map wrap-around %u+%u\n", start, offset); + return NULL; + } + + offset += start; + if (offset & (align - 1)) { + dev_err(zxdh_dev->device, "offset %u not aligned to %u\n", offset, align); + return NULL; + } + + if (length > size) + length = size; + + if (len) + *len = length; + + if (length + offset < offset || + length + offset > pci_resource_len(pdev, bar)) { + dev_err(zxdh_dev->device, + "map %u@%u out of range on bar %u length %lu\n", + length, offset, bar, + (unsigned long)pci_resource_len(pdev, bar)); + return NULL; + } + + p = pci_iomap_range(pdev, bar, offset, length); + if (!p) { + dev_err(zxdh_dev->device, "unable to map custom queue %u@%u on bar %u\n", + length, offset, bar); + } else if (pa) { + *pa = pci_resource_start(pdev, bar) + offset; + } + + return p; +} + +int zxdh_pf_common_cfg_init(struct zxdh_core_dev *zxdh_dev) +{ + struct zxdh_pf_dev *pf_dev = zxdh_dev->priv; + struct pci_dev *pdev = zxdh_dev->pdev; + int common; + + /* check for a common config: if not, use legacy mode (bar 0). */ + common = zxdh_pf_pci_find_capability(pdev, ZXDH_PCI_CAP_COMMON_CFG, + IORESOURCE_MEM, + &pf_dev->modern_bars); + if (!common) { + dev_err(zxdh_dev->device, + "missing capabilities, leaving for legacy driver\n"); + return -ENODEV; + } + + pf_dev->common = zxdh_pf_map_capability(zxdh_dev, common, + sizeof(struct zxdh_pf_pci_common_cfg), + ZXDH_PF_ALIGN4, 0, + sizeof(struct zxdh_pf_pci_common_cfg), + NULL, NULL, NULL); + if (!pf_dev->common) { + dev_err(zxdh_dev->device, "pf_dev->common is null\n"); + return -EINVAL; + } + + return 0; +} + +int zxdh_pf_notify_cfg_init(struct zxdh_core_dev *zxdh_dev) +{ + struct zxdh_pf_dev *pf_dev = zxdh_dev->priv; + struct pci_dev *pdev = zxdh_dev->pdev; + u32 notify_length; + u32 notify_offset; + int notify; + + /* If common is there, these should be too... */ + notify = zxdh_pf_pci_find_capability(pdev, ZXDH_PCI_CAP_NOTIFY_CFG, + IORESOURCE_MEM, + &pf_dev->modern_bars); + if (!notify) { + dev_err(zxdh_dev->device, "missing notify cfg capability\n"); + return -EINVAL; + } + + pci_read_config_dword(pdev, + notify + offsetof(struct zxdh_pf_pci_notify_cap, + notify_off_multiplier), + &pf_dev->notify_offset_multiplier); + pci_read_config_dword(pdev, + notify + offsetof(struct zxdh_pf_pci_notify_cap, + cap.length), ¬ify_length); + pci_read_config_dword(pdev, + notify + offsetof(struct zxdh_pf_pci_notify_cap, + cap.offset), ¬ify_offset); + + /* We don't know how many VQs we'll map, ahead of the time. + * If notify length is small, map it all now. Otherwise, + * map each VQ individually later. + */ + if (notify_length <= PAGE_SIZE - (notify_offset % PAGE_SIZE)) { + pf_dev->notify_base = zxdh_pf_map_capability(zxdh_dev, notify, + ZXDH_PF_MAP_MINLEN2, + ZXDH_PF_ALIGN2, 0, + notify_length, + &pf_dev->notify_len, + &pf_dev->notify_pa, NULL); + if (!pf_dev->notify_base) { + dev_err(zxdh_dev->device, "pf_dev->notify_base is null\n"); + return -EINVAL; + } + } else { + pf_dev->notify_map_cap = notify; + } + + return 0; +} + +int zxdh_pf_device_cfg_init(struct zxdh_core_dev *zxdh_dev) +{ + struct zxdh_pf_dev *pf_dev = zxdh_dev->priv; + struct pci_dev *pdev = zxdh_dev->pdev; + int device; + + /* Device capability is only mandatory for + * devices that have device-specific configuration. + */ + device = zxdh_pf_pci_find_capability(pdev, ZXDH_PCI_CAP_DEVICE_CFG, + IORESOURCE_MEM, + &pf_dev->modern_bars); + + /* we don't know how much we should map, + * but PAGE_SIZE is more than enough for all existing devices. + */ + if (device) { + pf_dev->device = zxdh_pf_map_capability(zxdh_dev, device, 0, + ZXDH_PF_ALIGN4, 0, PAGE_SIZE, + &pf_dev->device_len, NULL, + &pf_dev->dev_cfg_bar_off); + if (!pf_dev->device) { + dev_err(zxdh_dev->device, "pf_dev->device is null\n"); + return -EINVAL; + } + } + return 0; +} + +void zxdh_pf_modern_cfg_uninit(struct zxdh_core_dev *zxdh_dev) +{ + struct zxdh_pf_dev *pf_dev = zxdh_dev->priv; + struct pci_dev *pdev = zxdh_dev->pdev; + + if (pf_dev->device) + pci_iounmap(pdev, pf_dev->device); + if (pf_dev->notify_base) + pci_iounmap(pdev, pf_dev->notify_base); + pci_iounmap(pdev, pf_dev->common); +} + +int zxdh_pf_modern_cfg_init(struct zxdh_core_dev *zxdh_dev) +{ + struct zxdh_pf_dev *pf_dev = zxdh_dev->priv; + struct pci_dev *pdev = zxdh_dev->pdev; + int ret; + + ret = zxdh_pf_common_cfg_init(zxdh_dev); + if (ret) { + dev_err(zxdh_dev->device, "zxdh_pf_common_cfg_init failed: %d\n", ret); + return ret; + } + + ret = zxdh_pf_notify_cfg_init(zxdh_dev); + if (ret) { + dev_err(zxdh_dev->device, "zxdh_pf_notify_cfg_init failed: %d\n", ret); + goto err_map_notify; + } + + ret = zxdh_pf_device_cfg_init(zxdh_dev); + if (ret) { + dev_err(zxdh_dev->device, "zxdh_pf_device_cfg_init failed: %d\n", ret); + goto err_map_device; + } + + return 0; + +err_map_device: + if (pf_dev->notify_base) + pci_iounmap(pdev, pf_dev->notify_base); +err_map_notify: + pci_iounmap(pdev, pf_dev->common); + return ret; +} + static int zxdh_pf_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct zxdh_core_dev *zxdh_dev; @@ -133,10 +399,18 @@ static int zxdh_pf_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto err_pci_init; } + ret = zxdh_pf_modern_cfg_init(zxdh_dev); + if (ret) { + dev_err(&pdev->dev, "zxdh_pf_modern_cfg_init failed: %d\n", ret); + goto err_cfg_init; + } + devlink_register(devlink); return 0; +err_cfg_init: + zxdh_pf_pci_close(zxdh_dev); err_pci_init: zxdh_core_free_priv(zxdh_dev); err_pf_dev: @@ -150,6 +424,7 @@ static void zxdh_pf_remove(struct pci_dev *pdev) struct devlink *devlink = priv_to_devlink(zxdh_dev); devlink_unregister(devlink); + zxdh_pf_modern_cfg_uninit(zxdh_dev); zxdh_pf_pci_close(zxdh_dev); zxdh_core_free_priv(zxdh_dev); devlink_free(devlink); diff --git a/drivers/net/ethernet/zte/dinghai/en_pf.h b/drivers/net/ethernet/zte/dinghai/en_pf.h index f6b9dc94a57d..7373dee8d1a9 100644 --- a/drivers/net/ethernet/zte/dinghai/en_pf.h +++ b/drivers/net/ethernet/zte/dinghai/en_pf.h @@ -7,10 +7,28 @@ #ifndef __ZXDH_EN_PF_H__ #define __ZXDH_EN_PF_H__ +#include + #define ZXDH_PF_VENDOR_ID 0x1cf2 #define ZXDH_PF_DEVICE_ID 0x8040 #define ZXDH_VF_DEVICE_ID 0x8041 +/* Common configuration */ +#define ZXDH_PCI_CAP_COMMON_CFG 1 +/* Notifications */ +#define ZXDH_PCI_CAP_NOTIFY_CFG 2 +/* ISR access */ +#define ZXDH_PCI_CAP_ISR_CFG 3 +/* Device specific configuration */ +#define ZXDH_PCI_CAP_DEVICE_CFG 4 +/* PCI configuration access */ +#define ZXDH_PCI_CAP_PCI_CFG 5 + +#define ZXDH_PF_MAX_BAR_VAL 0x5 +#define ZXDH_PF_ALIGN4 4 +#define ZXDH_PF_ALIGN2 2 +#define ZXDH_PF_MAP_MINLEN2 2 + struct zxdh_core_dev { struct device *device; struct pci_dev *pdev; @@ -19,11 +37,39 @@ struct zxdh_core_dev { }; struct zxdh_pf_dev { + struct zxdh_pf_pci_common_cfg __iomem *common; + /* Device-specific data (non-legacy mode) */ + /* Base of vq notifications (non-legacy mode). */ + void __iomem *device; + void __iomem *notify_base; + /* Physical base of vq notifications */ + resource_size_t notify_pa; + /* So we can sanity-check accesses. */ + size_t notify_len; + size_t device_len; + /* Capability for when we need to map notifications per-vq. */ + s32 notify_map_cap; + u32 notify_offset_multiplier; + /* Multiply queue_notify_off by this value. (non-legacy mode). */ + s32 modern_bars; void __iomem *pci_ioremap_addr[6]; + u32 dev_cfg_bar_off; }; void *zxdh_core_alloc_priv(struct zxdh_core_dev *zxdh_dev, size_t size); void zxdh_core_free_priv(struct zxdh_core_dev *zxdh_dev); void zxdh_pf_pci_close(struct zxdh_core_dev *zxdh_dev); +int zxdh_pf_pci_find_capability(struct pci_dev *pdev, u8 cfg_type, + u32 ioresource_types, int *bars); +void __iomem *zxdh_pf_map_capability(struct zxdh_core_dev *zxdh_dev, int off, + size_t minlen, u32 align, + u32 start, u32 size, + size_t *len, resource_size_t *pa, + u32 *bar_off); +int zxdh_pf_common_cfg_init(struct zxdh_core_dev *zxdh_dev); +int zxdh_pf_notify_cfg_init(struct zxdh_core_dev *zxdh_dev); +int zxdh_pf_device_cfg_init(struct zxdh_core_dev *zxdh_dev); +void zxdh_pf_modern_cfg_uninit(struct zxdh_core_dev *zxdh_dev); +int zxdh_pf_modern_cfg_init(struct zxdh_core_dev *zxdh_dev); #endif /* __ZXDH_EN_PF_H__ */ From ff3a8694c2e8800272e020c97bd1a7d0fe89297e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Vallesp=C3=ADn=20Aranguren?= Date: Sat, 1 Aug 2026 20:33:49 +0200 Subject: [PATCH 0986/1433] net: tulip: remove xircom_cb driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A possible bug was found in investigate_read_descriptor() and a fix was proposed. Since this is an orphan driver for hardware that is old, removing the driver was suggested instead. This patch removes the driver. Jakub: clean up the Kconfig and platform configs Link: https://lore.kernel.org/netdev/2026080158-next-diligent-b4ce@gregkh Signed-off-by: Pablo Vallespín Aranguren Link: https://patch.msgid.link/am48DR5FC-xTY3-D@ThinkPad-P15 Signed-off-by: Jakub Kicinski --- arch/mips/configs/mtx1_defconfig | 1 - arch/powerpc/configs/ppc6xx_defconfig | 1 - drivers/net/ethernet/dec/tulip/Kconfig | 16 +- drivers/net/ethernet/dec/tulip/Makefile | 1 - drivers/net/ethernet/dec/tulip/xircom_cb.c | 1172 -------------------- 5 files changed, 2 insertions(+), 1189 deletions(-) delete mode 100644 drivers/net/ethernet/dec/tulip/xircom_cb.c diff --git a/arch/mips/configs/mtx1_defconfig b/arch/mips/configs/mtx1_defconfig index 46b40784a828..e28a0b1e6d52 100644 --- a/arch/mips/configs/mtx1_defconfig +++ b/arch/mips/configs/mtx1_defconfig @@ -234,7 +234,6 @@ CONFIG_TULIP=m CONFIG_WINBOND_840=m CONFIG_DM9102=m CONFIG_ULI526X=m -CONFIG_PCMCIA_XIRCOM=m CONFIG_DL2K=m CONFIG_SUNDANCE=m CONFIG_E100=m diff --git a/arch/powerpc/configs/ppc6xx_defconfig b/arch/powerpc/configs/ppc6xx_defconfig index 06e3cc55cb0d..33ef33ffc756 100644 --- a/arch/powerpc/configs/ppc6xx_defconfig +++ b/arch/powerpc/configs/ppc6xx_defconfig @@ -412,7 +412,6 @@ CONFIG_TULIP_MMIO=y CONFIG_WINBOND_840=m CONFIG_DM9102=m CONFIG_ULI526X=m -CONFIG_PCMCIA_XIRCOM=m CONFIG_DL2K=m CONFIG_SUNDANCE=m CONFIG_FEC_MPC52xx=m diff --git a/drivers/net/ethernet/dec/tulip/Kconfig b/drivers/net/ethernet/dec/tulip/Kconfig index 078a12f07e96..324608611a63 100644 --- a/drivers/net/ethernet/dec/tulip/Kconfig +++ b/drivers/net/ethernet/dec/tulip/Kconfig @@ -5,9 +5,9 @@ config NET_TULIP bool "DEC - Tulip devices" - depends on (PCI || EISA || CARDBUS) + depends on PCI help - This selects the "Tulip" family of EISA/PCI network cards. + This selects the "Tulip" family of PCI network cards. if NET_TULIP @@ -139,16 +139,4 @@ config ULI526X To compile this driver as a module, choose M here. The module will be called uli526x. -config PCMCIA_XIRCOM - tristate "Xircom CardBus support" - depends on CARDBUS - help - This driver is for the Digital "Tulip" Ethernet CardBus adapters. - It should work with most DEC 21*4*-based chips/ethercards, as well - as with work-alike chips from Lite-On (PNIC) and Macronix (MXIC) and - ASIX. - - To compile this driver as a module, choose M here. The module will - be called xircom_cb. If unsure, say N. - endif # NET_TULIP diff --git a/drivers/net/ethernet/dec/tulip/Makefile b/drivers/net/ethernet/dec/tulip/Makefile index d4f1d21d29a0..5d999d72840f 100644 --- a/drivers/net/ethernet/dec/tulip/Makefile +++ b/drivers/net/ethernet/dec/tulip/Makefile @@ -5,7 +5,6 @@ ccflags-$(CONFIG_NET_TULIP) := -DDEBUG -obj-$(CONFIG_PCMCIA_XIRCOM) += xircom_cb.o obj-$(CONFIG_DM9102) += dmfe.o obj-$(CONFIG_WINBOND_840) += winbond-840.o obj-$(CONFIG_DE2104X) += de2104x.o diff --git a/drivers/net/ethernet/dec/tulip/xircom_cb.c b/drivers/net/ethernet/dec/tulip/xircom_cb.c deleted file mode 100644 index e5d2ede13845..000000000000 --- a/drivers/net/ethernet/dec/tulip/xircom_cb.c +++ /dev/null @@ -1,1172 +0,0 @@ -/* - * xircom_cb: A driver for the (tulip-like) Xircom Cardbus ethernet cards - * - * This software is (C) by the respective authors, and licensed under the GPL - * License. - * - * Written by Arjan van de Ven for Red Hat, Inc. - * Based on work by Jeff Garzik, Doug Ledford and Donald Becker - * - * This software may be used and distributed according to the terms - * of the GNU General Public License, incorporated herein by reference. - * - * - * $Id: xircom_cb.c,v 1.33 2001/03/19 14:02:07 arjanv Exp $ - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#ifdef CONFIG_NET_POLL_CONTROLLER -#include -#endif - -MODULE_DESCRIPTION("Xircom Cardbus ethernet driver"); -MODULE_AUTHOR("Arjan van de Ven "); -MODULE_LICENSE("GPL"); - -#define xw32(reg, val) iowrite32(val, ioaddr + (reg)) -#define xr32(reg) ioread32(ioaddr + (reg)) -#define xr8(reg) ioread8(ioaddr + (reg)) - -/* IO registers on the card, offsets */ -#define CSR0 0x00 -#define CSR1 0x08 -#define CSR2 0x10 -#define CSR3 0x18 -#define CSR4 0x20 -#define CSR5 0x28 -#define CSR6 0x30 -#define CSR7 0x38 -#define CSR8 0x40 -#define CSR9 0x48 -#define CSR10 0x50 -#define CSR11 0x58 -#define CSR12 0x60 -#define CSR13 0x68 -#define CSR14 0x70 -#define CSR15 0x78 -#define CSR16 0x80 - -/* PCI registers */ -#define PCI_POWERMGMT 0x40 - -/* Offsets of the buffers within the descriptor pages, in bytes */ - -#define NUMDESCRIPTORS 4 - -static int bufferoffsets[NUMDESCRIPTORS] = {128,2048,4096,6144}; - - -struct xircom_private { - /* Send and receive buffers, kernel-addressable and dma addressable forms */ - - __le32 *rx_buffer; - __le32 *tx_buffer; - - dma_addr_t rx_dma_handle; - dma_addr_t tx_dma_handle; - - struct sk_buff *tx_skb[4]; - - void __iomem *ioaddr; - int open; - - /* transmit_used is the rotating counter that indicates which transmit - descriptor has to be used next */ - int transmit_used; - - /* Spinlock to serialize register operations. - It must be helt while manipulating the following registers: - CSR0, CSR6, CSR7, CSR9, CSR10, CSR15 - */ - spinlock_t lock; - - struct pci_dev *pdev; - struct net_device *dev; -}; - - -/* Function prototypes */ -static int xircom_probe(struct pci_dev *pdev, const struct pci_device_id *id); -static void xircom_remove(struct pci_dev *pdev); -static irqreturn_t xircom_interrupt(int irq, void *dev_instance); -static netdev_tx_t xircom_start_xmit(struct sk_buff *skb, - struct net_device *dev); -static int xircom_open(struct net_device *dev); -static int xircom_close(struct net_device *dev); -static void xircom_up(struct xircom_private *card); -#ifdef CONFIG_NET_POLL_CONTROLLER -static void xircom_poll_controller(struct net_device *dev); -#endif - -static void investigate_read_descriptor(struct net_device *dev,struct xircom_private *card, int descnr, unsigned int bufferoffset); -static void investigate_write_descriptor(struct net_device *dev, struct xircom_private *card, int descnr, unsigned int bufferoffset); -static void read_mac_address(struct xircom_private *card); -static void transceiver_voodoo(struct xircom_private *card); -static void initialize_card(struct xircom_private *card); -static void trigger_transmit(struct xircom_private *card); -static void trigger_receive(struct xircom_private *card); -static void setup_descriptors(struct xircom_private *card); -static void remove_descriptors(struct xircom_private *card); -static int link_status_changed(struct xircom_private *card); -static void activate_receiver(struct xircom_private *card); -static void deactivate_receiver(struct xircom_private *card); -static void activate_transmitter(struct xircom_private *card); -static void deactivate_transmitter(struct xircom_private *card); -static void enable_transmit_interrupt(struct xircom_private *card); -static void enable_receive_interrupt(struct xircom_private *card); -static void enable_link_interrupt(struct xircom_private *card); -static void disable_all_interrupts(struct xircom_private *card); -static int link_status(struct xircom_private *card); - - - -static const struct pci_device_id xircom_pci_table[] = { - { PCI_VDEVICE(XIRCOM, 0x0003), }, - {0,}, -}; -MODULE_DEVICE_TABLE(pci, xircom_pci_table); - -static struct pci_driver xircom_driver = { - .name = "xircom_cb", - .id_table = xircom_pci_table, - .probe = xircom_probe, - .remove = xircom_remove, -}; - - -#if defined DEBUG && DEBUG > 1 -static void print_binary(unsigned int number) -{ - int i,i2; - char buffer[64]; - memset(buffer,0,64); - i2=0; - for (i=31;i>=0;i--) { - if (number & (1<dev; - struct net_device *dev = NULL; - struct xircom_private *private; - unsigned long flags; - unsigned short tmp16; - int rc; - - /* First do the PCI initialisation */ - - rc = pci_enable_device(pdev); - if (rc < 0) - goto out; - - /* disable all powermanagement */ - pci_write_config_dword(pdev, PCI_POWERMGMT, 0x0000); - - pci_set_master(pdev); /* Why isn't this done by pci_enable_device ?*/ - - /* clear PCI status, if any */ - pci_read_config_word (pdev,PCI_STATUS, &tmp16); - pci_write_config_word (pdev, PCI_STATUS,tmp16); - - rc = pci_request_regions(pdev, "xircom_cb"); - if (rc < 0) { - pr_err("%s: failed to allocate io-region\n", __func__); - goto err_disable; - } - - rc = -ENOMEM; - /* - Before changing the hardware, allocate the memory. - This way, we can fail gracefully if not enough memory - is available. - */ - dev = alloc_etherdev(sizeof(struct xircom_private)); - if (!dev) - goto err_release; - - private = netdev_priv(dev); - - /* Allocate the send/receive buffers */ - private->rx_buffer = dma_alloc_coherent(d, 8192, - &private->rx_dma_handle, - GFP_KERNEL); - if (private->rx_buffer == NULL) - goto rx_buf_fail; - - private->tx_buffer = dma_alloc_coherent(d, 8192, - &private->tx_dma_handle, - GFP_KERNEL); - if (private->tx_buffer == NULL) - goto tx_buf_fail; - - SET_NETDEV_DEV(dev, &pdev->dev); - - - private->dev = dev; - private->pdev = pdev; - - /* IO range. */ - private->ioaddr = pci_iomap(pdev, 0, 0); - if (!private->ioaddr) - goto reg_fail; - - spin_lock_init(&private->lock); - - initialize_card(private); - read_mac_address(private); - setup_descriptors(private); - - dev->netdev_ops = &netdev_ops; - pci_set_drvdata(pdev, dev); - - rc = register_netdev(dev); - if (rc < 0) { - pr_err("%s: netdevice registration failed\n", __func__); - goto err_unmap; - } - - netdev_info(dev, "Xircom cardbus revision %i at irq %i\n", - pdev->revision, pdev->irq); - /* start the transmitter to get a heartbeat */ - /* TODO: send 2 dummy packets here */ - transceiver_voodoo(private); - - spin_lock_irqsave(&private->lock,flags); - activate_transmitter(private); - activate_receiver(private); - spin_unlock_irqrestore(&private->lock,flags); - - trigger_receive(private); -out: - return rc; - -err_unmap: - pci_iounmap(pdev, private->ioaddr); -reg_fail: - dma_free_coherent(d, 8192, private->tx_buffer, private->tx_dma_handle); -tx_buf_fail: - dma_free_coherent(d, 8192, private->rx_buffer, private->rx_dma_handle); -rx_buf_fail: - free_netdev(dev); -err_release: - pci_release_regions(pdev); -err_disable: - pci_disable_device(pdev); - goto out; -} - - -/* - xircom_remove is called on module-unload or on device-eject. - it unregisters the irq, io-region and network device. - Interrupts and such are already stopped in the "ifconfig ethX down" - code. - */ -static void xircom_remove(struct pci_dev *pdev) -{ - struct net_device *dev = pci_get_drvdata(pdev); - struct xircom_private *card = netdev_priv(dev); - struct device *d = &pdev->dev; - - unregister_netdev(dev); - pci_iounmap(pdev, card->ioaddr); - dma_free_coherent(d, 8192, card->tx_buffer, card->tx_dma_handle); - dma_free_coherent(d, 8192, card->rx_buffer, card->rx_dma_handle); - free_netdev(dev); - pci_release_regions(pdev); - pci_disable_device(pdev); -} - -static irqreturn_t xircom_interrupt(int irq, void *dev_instance) -{ - struct net_device *dev = (struct net_device *) dev_instance; - struct xircom_private *card = netdev_priv(dev); - void __iomem *ioaddr = card->ioaddr; - unsigned int status; - int i; - - spin_lock(&card->lock); - status = xr32(CSR5); - -#if defined DEBUG && DEBUG > 1 - print_binary(status); - pr_debug("tx status 0x%08x 0x%08x\n", - card->tx_buffer[0], card->tx_buffer[4]); - pr_debug("rx status 0x%08x 0x%08x\n", - card->rx_buffer[0], card->rx_buffer[4]); -#endif - /* Handle shared irq and hotplug */ - if (status == 0 || status == 0xffffffff) { - spin_unlock(&card->lock); - return IRQ_NONE; - } - - if (link_status_changed(card)) { - int newlink; - netdev_dbg(dev, "Link status has changed\n"); - newlink = link_status(card); - netdev_info(dev, "Link is %d mbit\n", newlink); - if (newlink) - netif_carrier_on(dev); - else - netif_carrier_off(dev); - - } - - /* Clear all remaining interrupts */ - status |= 0xffffffff; /* FIXME: make this clear only the - real existing bits */ - xw32(CSR5, status); - - - for (i=0;ilock); - return IRQ_HANDLED; -} - -static netdev_tx_t xircom_start_xmit(struct sk_buff *skb, - struct net_device *dev) -{ - struct xircom_private *card; - unsigned long flags; - int nextdescriptor; - int desc; - - card = netdev_priv(dev); - spin_lock_irqsave(&card->lock,flags); - - /* First see if we can free some descriptors */ - for (desc=0;desctransmit_used +1) % (NUMDESCRIPTORS); - desc = card->transmit_used; - - /* only send the packet if the descriptor is free */ - if (card->tx_buffer[4*desc]==0) { - /* Copy the packet data; zero the memory first as the card - sometimes sends more than you ask it to. */ - - memset(&card->tx_buffer[bufferoffsets[desc]/4],0,1536); - skb_copy_from_linear_data(skb, - &(card->tx_buffer[bufferoffsets[desc] / 4]), - skb->len); - /* FIXME: The specification tells us that the length we send HAS to be a multiple of - 4 bytes. */ - - card->tx_buffer[4*desc+1] = cpu_to_le32(skb->len); - if (desc == NUMDESCRIPTORS - 1) /* bit 25: last descriptor of the ring */ - card->tx_buffer[4*desc+1] |= cpu_to_le32(1<<25); - - card->tx_buffer[4*desc+1] |= cpu_to_le32(0xF0000000); - /* 0xF0... means want interrupts*/ - card->tx_skb[desc] = skb; - - wmb(); - /* This gives the descriptor to the card */ - card->tx_buffer[4*desc] = cpu_to_le32(0x80000000); - trigger_transmit(card); - if (card->tx_buffer[nextdescriptor*4] & cpu_to_le32(0x8000000)) { - /* next descriptor is occupied... */ - netif_stop_queue(dev); - } - card->transmit_used = nextdescriptor; - spin_unlock_irqrestore(&card->lock,flags); - return NETDEV_TX_OK; - } - - /* Uh oh... no free descriptor... drop the packet */ - netif_stop_queue(dev); - spin_unlock_irqrestore(&card->lock,flags); - trigger_transmit(card); - - return NETDEV_TX_BUSY; -} - - - - -static int xircom_open(struct net_device *dev) -{ - struct xircom_private *xp = netdev_priv(dev); - const int irq = xp->pdev->irq; - int retval; - - netdev_info(dev, "xircom cardbus adaptor found, using irq %i\n", irq); - retval = request_irq(irq, xircom_interrupt, IRQF_SHARED, dev->name, dev); - if (retval) - return retval; - - xircom_up(xp); - xp->open = 1; - - return 0; -} - -static int xircom_close(struct net_device *dev) -{ - struct xircom_private *card; - unsigned long flags; - - card = netdev_priv(dev); - netif_stop_queue(dev); /* we don't want new packets */ - - - spin_lock_irqsave(&card->lock,flags); - - disable_all_interrupts(card); -#if 0 - /* We can enable this again once we send dummy packets on ifconfig ethX up */ - deactivate_receiver(card); - deactivate_transmitter(card); -#endif - remove_descriptors(card); - - spin_unlock_irqrestore(&card->lock,flags); - - card->open = 0; - free_irq(card->pdev->irq, dev); - - return 0; - -} - - -#ifdef CONFIG_NET_POLL_CONTROLLER -static void xircom_poll_controller(struct net_device *dev) -{ - struct xircom_private *xp = netdev_priv(dev); - const int irq = xp->pdev->irq; - - disable_irq(irq); - xircom_interrupt(irq, dev); - enable_irq(irq); -} -#endif - - -static void initialize_card(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned long flags; - u32 val; - - spin_lock_irqsave(&card->lock, flags); - - /* First: reset the card */ - val = xr32(CSR0); - val |= 0x01; /* Software reset */ - xw32(CSR0, val); - - udelay(100); /* give the card some time to reset */ - - val = xr32(CSR0); - val &= ~0x01; /* disable Software reset */ - xw32(CSR0, val); - - - val = 0; /* Value 0x00 is a safe and conservative value - for the PCI configuration settings */ - xw32(CSR0, val); - - - disable_all_interrupts(card); - deactivate_receiver(card); - deactivate_transmitter(card); - - spin_unlock_irqrestore(&card->lock, flags); -} - -/* -trigger_transmit causes the card to check for frames to be transmitted. -This is accomplished by writing to the CSR1 port. The documentation -claims that the act of writing is sufficient and that the value is -ignored; I chose zero. -*/ -static void trigger_transmit(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - - xw32(CSR1, 0); -} - -/* -trigger_receive causes the card to check for empty frames in the -descriptor list in which packets can be received. -This is accomplished by writing to the CSR2 port. The documentation -claims that the act of writing is sufficient and that the value is -ignored; I chose zero. -*/ -static void trigger_receive(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - - xw32(CSR2, 0); -} - -/* -setup_descriptors initializes the send and receive buffers to be valid -descriptors and programs the addresses into the card. -*/ -static void setup_descriptors(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - u32 address; - int i; - - BUG_ON(card->rx_buffer == NULL); - BUG_ON(card->tx_buffer == NULL); - - /* Receive descriptors */ - memset(card->rx_buffer, 0, 128); /* clear the descriptors */ - for (i=0;i 0x80000000 */ - card->rx_buffer[i*4 + 0] = cpu_to_le32(0x80000000); - /* Rx Descr1: buffer 1 is 1536 bytes, buffer 2 is 0 bytes */ - card->rx_buffer[i*4 + 1] = cpu_to_le32(1536); - if (i == NUMDESCRIPTORS - 1) /* bit 25 is "last descriptor" */ - card->rx_buffer[i*4 + 1] |= cpu_to_le32(1 << 25); - - /* Rx Descr2: address of the buffer - we store the buffer at the 2nd half of the page */ - - address = card->rx_dma_handle; - card->rx_buffer[i*4 + 2] = cpu_to_le32(address + bufferoffsets[i]); - /* Rx Desc3: address of 2nd buffer -> 0 */ - card->rx_buffer[i*4 + 3] = 0; - } - - wmb(); - /* Write the receive descriptor ring address to the card */ - address = card->rx_dma_handle; - xw32(CSR3, address); /* Receive descr list address */ - - - /* transmit descriptors */ - memset(card->tx_buffer, 0, 128); /* clear the descriptors */ - - for (i=0;i 0x00000000 */ - card->tx_buffer[i*4 + 0] = 0x00000000; - /* Tx Descr1: buffer 1 is 1536 bytes, buffer 2 is 0 bytes */ - card->tx_buffer[i*4 + 1] = cpu_to_le32(1536); - if (i == NUMDESCRIPTORS - 1) /* bit 25 is "last descriptor" */ - card->tx_buffer[i*4 + 1] |= cpu_to_le32(1 << 25); - - /* Tx Descr2: address of the buffer - we store the buffer at the 2nd half of the page */ - address = card->tx_dma_handle; - card->tx_buffer[i*4 + 2] = cpu_to_le32(address + bufferoffsets[i]); - /* Tx Desc3: address of 2nd buffer -> 0 */ - card->tx_buffer[i*4 + 3] = 0; - } - - wmb(); - /* wite the transmit descriptor ring to the card */ - address = card->tx_dma_handle; - xw32(CSR4, address); /* xmit descr list address */ -} - -/* -remove_descriptors informs the card the descriptors are no longer -valid by setting the address in the card to 0x00. -*/ -static void remove_descriptors(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - - val = 0; - xw32(CSR3, val); /* Receive descriptor address */ - xw32(CSR4, val); /* Send descriptor address */ -} - -/* -link_status_changed returns 1 if the card has indicated that -the link status has changed. The new link status has to be read from CSR12. - -This function also clears the status-bit. -*/ -static int link_status_changed(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - - val = xr32(CSR5); /* Status register */ - if (!(val & (1 << 27))) /* no change */ - return 0; - - /* clear the event by writing a 1 to the bit in the - status register. */ - val = (1 << 27); - xw32(CSR5, val); - - return 1; -} - - -/* -transmit_active returns 1 if the transmitter on the card is -in a non-stopped state. -*/ -static int transmit_active(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - - if (!(xr32(CSR5) & (7 << 20))) /* transmitter disabled */ - return 0; - - return 1; -} - -/* -receive_active returns 1 if the receiver on the card is -in a non-stopped state. -*/ -static int receive_active(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - - if (!(xr32(CSR5) & (7 << 17))) /* receiver disabled */ - return 0; - - return 1; -} - -/* -activate_receiver enables the receiver on the card. -Before being allowed to active the receiver, the receiver -must be completely de-activated. To achieve this, -this code actually disables the receiver first; then it waits for the -receiver to become inactive, then it activates the receiver and then -it waits for the receiver to be active. - -must be called with the lock held and interrupts disabled. -*/ -static void activate_receiver(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - int counter; - - val = xr32(CSR6); /* Operation mode */ - - /* If the "active" bit is set and the receiver is already - active, no need to do the expensive thing */ - if ((val&2) && (receive_active(card))) - return; - - - val = val & ~2; /* disable the receiver */ - xw32(CSR6, val); - - counter = 10; - while (counter > 0) { - if (!receive_active(card)) - break; - /* wait a while */ - udelay(50); - counter--; - if (counter <= 0) - netdev_err(card->dev, "Receiver failed to deactivate\n"); - } - - /* enable the receiver */ - val = xr32(CSR6); /* Operation mode */ - val = val | 2; /* enable the receiver */ - xw32(CSR6, val); - - /* now wait for the card to activate again */ - counter = 10; - while (counter > 0) { - if (receive_active(card)) - break; - /* wait a while */ - udelay(50); - counter--; - if (counter <= 0) - netdev_err(card->dev, - "Receiver failed to re-activate\n"); - } -} - -/* -deactivate_receiver disables the receiver on the card. -To achieve this this code disables the receiver first; -then it waits for the receiver to become inactive. - -must be called with the lock held and interrupts disabled. -*/ -static void deactivate_receiver(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - int counter; - - val = xr32(CSR6); /* Operation mode */ - val = val & ~2; /* disable the receiver */ - xw32(CSR6, val); - - counter = 10; - while (counter > 0) { - if (!receive_active(card)) - break; - /* wait a while */ - udelay(50); - counter--; - if (counter <= 0) - netdev_err(card->dev, "Receiver failed to deactivate\n"); - } -} - - -/* -activate_transmitter enables the transmitter on the card. -Before being allowed to active the transmitter, the transmitter -must be completely de-activated. To achieve this, -this code actually disables the transmitter first; then it waits for the -transmitter to become inactive, then it activates the transmitter and then -it waits for the transmitter to be active again. - -must be called with the lock held and interrupts disabled. -*/ -static void activate_transmitter(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - int counter; - - val = xr32(CSR6); /* Operation mode */ - - /* If the "active" bit is set and the receiver is already - active, no need to do the expensive thing */ - if ((val&(1<<13)) && (transmit_active(card))) - return; - - val = val & ~(1 << 13); /* disable the transmitter */ - xw32(CSR6, val); - - counter = 10; - while (counter > 0) { - if (!transmit_active(card)) - break; - /* wait a while */ - udelay(50); - counter--; - if (counter <= 0) - netdev_err(card->dev, - "Transmitter failed to deactivate\n"); - } - - /* enable the transmitter */ - val = xr32(CSR6); /* Operation mode */ - val = val | (1 << 13); /* enable the transmitter */ - xw32(CSR6, val); - - /* now wait for the card to activate again */ - counter = 10; - while (counter > 0) { - if (transmit_active(card)) - break; - /* wait a while */ - udelay(50); - counter--; - if (counter <= 0) - netdev_err(card->dev, - "Transmitter failed to re-activate\n"); - } -} - -/* -deactivate_transmitter disables the transmitter on the card. -To achieve this this code disables the transmitter first; -then it waits for the transmitter to become inactive. - -must be called with the lock held and interrupts disabled. -*/ -static void deactivate_transmitter(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - int counter; - - val = xr32(CSR6); /* Operation mode */ - val = val & ~2; /* disable the transmitter */ - xw32(CSR6, val); - - counter = 20; - while (counter > 0) { - if (!transmit_active(card)) - break; - /* wait a while */ - udelay(50); - counter--; - if (counter <= 0) - netdev_err(card->dev, - "Transmitter failed to deactivate\n"); - } -} - - -/* -enable_transmit_interrupt enables the transmit interrupt - -must be called with the lock held and interrupts disabled. -*/ -static void enable_transmit_interrupt(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - - val = xr32(CSR7); /* Interrupt enable register */ - val |= 1; /* enable the transmit interrupt */ - xw32(CSR7, val); -} - - -/* -enable_receive_interrupt enables the receive interrupt - -must be called with the lock held and interrupts disabled. -*/ -static void enable_receive_interrupt(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - - val = xr32(CSR7); /* Interrupt enable register */ - val = val | (1 << 6); /* enable the receive interrupt */ - xw32(CSR7, val); -} - -/* -enable_link_interrupt enables the link status change interrupt - -must be called with the lock held and interrupts disabled. -*/ -static void enable_link_interrupt(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - - val = xr32(CSR7); /* Interrupt enable register */ - val = val | (1 << 27); /* enable the link status chage interrupt */ - xw32(CSR7, val); -} - - - -/* -disable_all_interrupts disables all interrupts - -must be called with the lock held and interrupts disabled. -*/ -static void disable_all_interrupts(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - - xw32(CSR7, 0); -} - -/* -enable_common_interrupts enables several weird interrupts - -must be called with the lock held and interrupts disabled. -*/ -static void enable_common_interrupts(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - - val = xr32(CSR7); /* Interrupt enable register */ - val |= (1<<16); /* Normal Interrupt Summary */ - val |= (1<<15); /* Abnormal Interrupt Summary */ - val |= (1<<13); /* Fatal bus error */ - val |= (1<<8); /* Receive Process Stopped */ - val |= (1<<7); /* Receive Buffer Unavailable */ - val |= (1<<5); /* Transmit Underflow */ - val |= (1<<2); /* Transmit Buffer Unavailable */ - val |= (1<<1); /* Transmit Process Stopped */ - xw32(CSR7, val); -} - -/* -enable_promisc starts promisc mode - -must be called with the lock held and interrupts disabled. -*/ -static int enable_promisc(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned int val; - - val = xr32(CSR6); - val = val | (1 << 6); - xw32(CSR6, val); - - return 1; -} - - - - -/* -link_status() checks the links status and will return 0 for no link, 10 for 10mbit link and 100 for.. guess what. - -Must be called in locked state with interrupts disabled -*/ -static int link_status(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - u8 val; - - val = xr8(CSR12); - - /* bit 2 is 0 for 10mbit link, 1 for not an 10mbit link */ - if (!(val & (1 << 2))) - return 10; - /* bit 1 is 0 for 100mbit link, 1 for not an 100mbit link */ - if (!(val & (1 << 1))) - return 100; - - /* If we get here -> no link at all */ - - return 0; -} - - - - - -/* - read_mac_address() reads the MAC address from the NIC and stores it in the "dev" structure. - - This function will take the spinlock itself and can, as a result, not be called with the lock helt. - */ -static void read_mac_address(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned long flags; - u8 link; - int i; - - spin_lock_irqsave(&card->lock, flags); - - xw32(CSR9, 1 << 12); /* enable boot rom access */ - for (i = 0x100; i < 0x1f7; i += link + 2) { - u8 tuple, data_id, data_count; - - xw32(CSR10, i); - tuple = xr32(CSR9); - xw32(CSR10, i + 1); - link = xr32(CSR9); - xw32(CSR10, i + 2); - data_id = xr32(CSR9); - xw32(CSR10, i + 3); - data_count = xr32(CSR9); - if ((tuple == 0x22) && (data_id == 0x04) && (data_count == 0x06)) { - u8 addr[ETH_ALEN]; - int j; - - for (j = 0; j < 6; j++) { - xw32(CSR10, i + j + 4); - addr[j] = xr32(CSR9) & 0xff; - } - eth_hw_addr_set(card->dev, addr); - break; - } else if (link == 0) { - break; - } - } - spin_unlock_irqrestore(&card->lock, flags); - pr_debug(" %pM\n", card->dev->dev_addr); -} - - -/* - transceiver_voodoo() enables the external UTP plug thingy. - it's called voodoo as I stole this code and cannot cross-reference - it with the specification. - */ -static void transceiver_voodoo(struct xircom_private *card) -{ - void __iomem *ioaddr = card->ioaddr; - unsigned long flags; - - /* disable all powermanagement */ - pci_write_config_dword(card->pdev, PCI_POWERMGMT, 0x0000); - - setup_descriptors(card); - - spin_lock_irqsave(&card->lock, flags); - - xw32(CSR15, 0x0008); - udelay(25); - xw32(CSR15, 0xa8050000); - udelay(25); - xw32(CSR15, 0xa00f0000); - udelay(25); - - spin_unlock_irqrestore(&card->lock, flags); - - netif_start_queue(card->dev); -} - - -static void xircom_up(struct xircom_private *card) -{ - unsigned long flags; - int i; - - /* disable all powermanagement */ - pci_write_config_dword(card->pdev, PCI_POWERMGMT, 0x0000); - - setup_descriptors(card); - - spin_lock_irqsave(&card->lock, flags); - - - enable_link_interrupt(card); - enable_transmit_interrupt(card); - enable_receive_interrupt(card); - enable_common_interrupts(card); - enable_promisc(card); - - /* The card can have received packets already, read them away now */ - for (i=0;idev,card,i,bufferoffsets[i]); - - - spin_unlock_irqrestore(&card->lock, flags); - trigger_receive(card); - trigger_transmit(card); - netif_start_queue(card->dev); -} - -/* Bufferoffset is in BYTES */ -static void -investigate_read_descriptor(struct net_device *dev, struct xircom_private *card, - int descnr, unsigned int bufferoffset) -{ - int status; - - status = le32_to_cpu(card->rx_buffer[4*descnr]); - - if (status > 0) { /* packet received */ - - /* TODO: discard error packets */ - - short pkt_len = ((status >> 16) & 0x7ff) - 4; - /* minus 4, we don't want the CRC */ - struct sk_buff *skb; - - if (pkt_len > 1518) { - netdev_err(dev, "Packet length %i is bogus\n", pkt_len); - pkt_len = 1518; - } - - skb = netdev_alloc_skb(dev, pkt_len + 2); - if (skb == NULL) { - dev->stats.rx_dropped++; - goto out; - } - skb_reserve(skb, 2); - skb_copy_to_linear_data(skb, - &card->rx_buffer[bufferoffset / 4], - pkt_len); - skb_put(skb, pkt_len); - skb->protocol = eth_type_trans(skb, dev); - netif_rx(skb); - dev->stats.rx_packets++; - dev->stats.rx_bytes += pkt_len; - -out: - /* give the buffer back to the card */ - card->rx_buffer[4*descnr] = cpu_to_le32(0x80000000); - trigger_receive(card); - } -} - - -/* Bufferoffset is in BYTES */ -static void -investigate_write_descriptor(struct net_device *dev, - struct xircom_private *card, - int descnr, unsigned int bufferoffset) -{ - int status; - - status = le32_to_cpu(card->tx_buffer[4*descnr]); -#if 0 - if (status & 0x8000) { /* Major error */ - pr_err("Major transmit error status %x\n", status); - card->tx_buffer[4*descnr] = 0; - netif_wake_queue (dev); - } -#endif - if (status > 0) { /* bit 31 is 0 when done */ - if (card->tx_skb[descnr]!=NULL) { - dev->stats.tx_bytes += card->tx_skb[descnr]->len; - dev_kfree_skb_irq(card->tx_skb[descnr]); - } - card->tx_skb[descnr] = NULL; - /* Bit 8 in the status field is 1 if there was a collision */ - if (status & (1 << 8)) - dev->stats.collisions++; - card->tx_buffer[4*descnr] = 0; /* descriptor is free again */ - netif_wake_queue (dev); - dev->stats.tx_packets++; - } -} - -module_pci_driver(xircom_driver); From eaebed6b2f50d8cf230af1b536a752e5bf512633 Mon Sep 17 00:00:00 2001 From: Qingfang Deng Date: Tue, 4 Aug 2026 17:43:34 +0800 Subject: [PATCH 0987/1433] pppoe: remove redundant xmit wrapper Merge __pppoe_xmit() into pppoe_xmit(), its only caller. Signed-off-by: Qingfang Deng Link: https://patch.msgid.link/20260804094336.109364-1-qingfang.deng@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ppp/pppoe.c | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/drivers/net/ppp/pppoe.c b/drivers/net/ppp/pppoe.c index 6874a1a8edaf..bf7414b46a26 100644 --- a/drivers/net/ppp/pppoe.c +++ b/drivers/net/ppp/pppoe.c @@ -85,8 +85,6 @@ #define PPPOE_HASH_SIZE (1 << PPPOE_HASH_BITS) #define PPPOE_HASH_MASK (PPPOE_HASH_SIZE - 1) -static int __pppoe_xmit(struct sock *sk, struct sk_buff *skb); - static const struct proto_ops pppoe_ops; static const struct ppp_channel_ops pppoe_chan_ops; @@ -839,11 +837,13 @@ static int pppoe_sendmsg(struct socket *sock, struct msghdr *m, /************************************************************************ * - * xmit function for internal use. + * xmit function called by generic PPP driver + * sends PPP frame over PPPoE socket * ***********************************************************************/ -static int __pppoe_xmit(struct sock *sk, struct sk_buff *skb) +static int pppoe_xmit(struct ppp_channel *chan, struct sk_buff *skb) { + struct sock *sk = chan->private; struct pppox_sock *po = pppox_sk(sk); struct net_device *dev = po->pppoe_dev; struct pppoe_hdr *ph; @@ -893,18 +893,6 @@ static int __pppoe_xmit(struct sock *sk, struct sk_buff *skb) return 1; } -/************************************************************************ - * - * xmit function called by generic PPP driver - * sends PPP frame over PPPoE socket - * - ***********************************************************************/ -static int pppoe_xmit(struct ppp_channel *chan, struct sk_buff *skb) -{ - struct sock *sk = chan->private; - return __pppoe_xmit(sk, skb); -} - static int pppoe_fill_forward_path(struct net_device_path_ctx *ctx, struct net_device_path *path, const struct ppp_channel *chan) From d4b0ad9a81746d8415ca57197b2344b0b8b7ee08 Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Thu, 30 Jul 2026 19:35:35 +0200 Subject: [PATCH 0988/1433] dt-bindings: dpll: zl3073x: ZL30643 is compatible The Microchip ZL30643 (chip ID 0x0E3B) is a member of the ZL3064x line card timing family. It is register compatible with the 3-channel ZL30733 (chip ID 0x0E95) of the ZL3073x family: both datasheets describe the same register map and use the same chip-ID encoding. Describe it with a fallback to microchip,zl30733 rather than a new standalone compatible. Signed-off-by: Vincent Jardin Acked-by: Conor Dooley Link: https://patch.msgid.link/20260730-for-upstream-zl30643-v2-1-0ea0bbd03755@free.fr Signed-off-by: Jakub Kicinski --- .../bindings/dpll/microchip,zl30731.yaml | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Documentation/devicetree/bindings/dpll/microchip,zl30731.yaml b/Documentation/devicetree/bindings/dpll/microchip,zl30731.yaml index fa5a8f8e390c..17983e6c3597 100644 --- a/Documentation/devicetree/bindings/dpll/microchip,zl30731.yaml +++ b/Documentation/devicetree/bindings/dpll/microchip,zl30731.yaml @@ -14,15 +14,22 @@ description: provides up to 5 independent DPLL channels, up to 10 differential or single-ended inputs and 10 differential or 20 single-ended outputs. These devices support both I2C and SPI interfaces. + The ZL3064x line card timing ICs share the ZL3073x register map + and are described with a fallback to the register equivalent ZL3073x + part. properties: compatible: - enum: - - microchip,zl30731 - - microchip,zl30732 - - microchip,zl30733 - - microchip,zl30734 - - microchip,zl30735 + oneOf: + - enum: + - microchip,zl30731 + - microchip,zl30732 + - microchip,zl30733 + - microchip,zl30734 + - microchip,zl30735 + - items: + - const: microchip,zl30643 + - const: microchip,zl30733 reg: maxItems: 1 From 24e4aff8983fe663a85b5b157476f87ae0819e2c Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Thu, 30 Jul 2026 19:35:36 +0200 Subject: [PATCH 0989/1433] dpll: zl3073x: recognize the ZL30643 chip ID (0x0E3B) The Microchip ZL30643 is a 3-channel ZL3064x line-card part that is register compatible with the ZL30733. Only the runtime chip-ID table needs the 0x0E3B entry so the probe resolves the channel count (3) and flags. The ZL3073X_FLAG_REF_PHASE_COMP_32 flag applies unchanged: the ref_phase path dpll_meas_ctrl::en -> ref_phase_0P/0N -> ref_phase_offset_compensation -> ref_phase_err_read_rqst is identical between ZL3064x and ZL3073x. No new device flag is needed. Test: once register, for instance, we get: devlink dev param set spi/spi0.0 name clock_id value 3733 cmode driverinit devlink dev reload spi/spi0.0 devlink dev param set spi/spi2.1 name clock_id value 3643 cmode driverinit devlink dev reload spi/spi2.1 dpll device show | grep clock-id clock-id: 3733 clock-id: 3733 clock-id: 3733 clock-id: 3643 clock-id: 3643 clock-id: 3643 Signed-off-by: Vincent Jardin Link: https://patch.msgid.link/20260730-for-upstream-zl30643-v2-2-0ea0bbd03755@free.fr Signed-off-by: Jakub Kicinski --- drivers/dpll/zl3073x/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c index 7f5afaaae634..5b2d77f2c228 100644 --- a/drivers/dpll/zl3073x/core.c +++ b/drivers/dpll/zl3073x/core.c @@ -25,6 +25,7 @@ static const struct zl3073x_chip_info zl3073x_chip_ids[] = { ZL_CHIP_INFO(0x0E30, 2, ZL3073X_FLAG_REF_PHASE_COMP_32), + ZL_CHIP_INFO(0x0E3B, 3, ZL3073X_FLAG_REF_PHASE_COMP_32), ZL_CHIP_INFO(0x0E93, 1, ZL3073X_FLAG_REF_PHASE_COMP_32), ZL_CHIP_INFO(0x0E94, 2, ZL3073X_FLAG_REF_PHASE_COMP_32), ZL_CHIP_INFO(0x0E95, 3, ZL3073X_FLAG_REF_PHASE_COMP_32), From 46f636cd620e2ff5a941fe8c6af19f8d82122b40 Mon Sep 17 00:00:00 2001 From: Nimrod Oren Date: Mon, 3 Aug 2026 16:25:18 +0300 Subject: [PATCH 0990/1433] net/mlx5: initialize doorbell dma pools Add per-node doorbell dma pool creation and cleanup to mdev lifecycle. Signed-off-by: Nimrod Oren Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260803132520.2891860-2-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/mellanox/mlx5/core/alloc.c | 37 +++++++++++++++++++ .../net/ethernet/mellanox/mlx5/core/main.c | 7 ++++ .../ethernet/mellanox/mlx5/core/mlx5_core.h | 2 + include/linux/mlx5/driver.h | 2 + 4 files changed, 48 insertions(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/alloc.c b/drivers/net/ethernet/mellanox/mlx5/core/alloc.c index 4fe9d7d4f143..3c9938068c56 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/alloc.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/alloc.c @@ -444,6 +444,43 @@ void mlx5_frag_buf_free(struct mlx5_core_dev *dev, struct mlx5_frag_buf *buf) } EXPORT_SYMBOL_GPL(mlx5_frag_buf_free); +void mlx5_db_pools_cleanup(struct mlx5_core_dev *dev) +{ + struct mlx5_priv *priv = &dev->priv; + int node; + + for_each_node_state(node, N_POSSIBLE) + if (priv->db_node_pools[node]) + mlx5_dma_pool_destroy(priv->db_node_pools[node]); + + kfree(priv->db_node_pools); + priv->db_node_pools = NULL; +} + +int mlx5_db_pools_init(struct mlx5_core_dev *dev) +{ + struct mlx5_priv *priv = &dev->priv; + int node; + + priv->db_node_pools = kzalloc_objs(*priv->db_node_pools, nr_node_ids); + if (!priv->db_node_pools) + return -ENOMEM; + + for_each_node_state(node, N_POSSIBLE) { + struct mlx5_dma_pool *pool; + + pool = mlx5_dma_pool_create(dev, node, + order_base_2(cache_line_size())); + if (!pool) { + mlx5_db_pools_cleanup(dev); + return -ENOMEM; + } + priv->db_node_pools[node] = pool; + } + + return 0; +} + static struct mlx5_db_pgdir *mlx5_alloc_db_pgdir(struct mlx5_core_dev *dev, int node) { diff --git a/drivers/net/ethernet/mellanox/mlx5/core/main.c b/drivers/net/ethernet/mellanox/mlx5/core/main.c index 643b4aac2033..b3cb090b5677 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/main.c @@ -1830,6 +1830,10 @@ int mlx5_mdev_init(struct mlx5_core_dev *dev, int profile_idx) if (err) goto err_frag_buf_pools_init; + err = mlx5_db_pools_init(dev); + if (err) + goto err_db_pools_init; + INIT_LIST_HEAD(&priv->traps); err = mlx5_cmd_init(dev); @@ -1891,6 +1895,8 @@ int mlx5_mdev_init(struct mlx5_core_dev *dev, int profile_idx) err_timeout_init: mlx5_cmd_cleanup(dev); err_cmd_init: + mlx5_db_pools_cleanup(dev); +err_db_pools_init: mlx5_frag_buf_pools_cleanup(dev); err_frag_buf_pools_init: debugfs_remove(dev->priv.dbg.dbg_root); @@ -1917,6 +1923,7 @@ void mlx5_mdev_uninit(struct mlx5_core_dev *dev) mlx5_health_cleanup(dev); mlx5_tout_cleanup(dev); mlx5_cmd_cleanup(dev); + mlx5_db_pools_cleanup(dev); mlx5_frag_buf_pools_cleanup(dev); debugfs_remove_recursive(dev->priv.dbg.dbg_root); mutex_destroy(&priv->pgdir_mutex); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.h b/drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.h index 09e669f83dba..d6713a2ce676 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.h @@ -438,6 +438,8 @@ int mlx5_mdev_init(struct mlx5_core_dev *dev, int profile_idx); void mlx5_mdev_uninit(struct mlx5_core_dev *dev); int mlx5_frag_buf_pools_init(struct mlx5_core_dev *dev); void mlx5_frag_buf_pools_cleanup(struct mlx5_core_dev *dev); +int mlx5_db_pools_init(struct mlx5_core_dev *dev); +void mlx5_db_pools_cleanup(struct mlx5_core_dev *dev); int mlx5_init_one(struct mlx5_core_dev *dev); int mlx5_init_one_devl_locked(struct mlx5_core_dev *dev); void mlx5_uninit_one(struct mlx5_core_dev *dev); diff --git a/include/linux/mlx5/driver.h b/include/linux/mlx5/driver.h index b1871c0821d0..4246d6d904ba 100644 --- a/include/linux/mlx5/driver.h +++ b/include/linux/mlx5/driver.h @@ -569,6 +569,7 @@ enum mlx5_page_mgt_mode { }; struct mlx5_frag_buf_node_pools; +struct mlx5_dma_pool; struct mlx5_ft_pool; struct mlx5_priv { /* IRQ table valid only for real pci devices PF or VF */ @@ -602,6 +603,7 @@ struct mlx5_priv { struct list_head pgdir_list; struct mlx5_frag_buf_node_pools **frag_buf_node_pools; + struct mlx5_dma_pool **db_node_pools; /* end: alloc stuff */ struct mlx5_adev **adev; From c29235677f5e3645e7d7324c5d51aaa78ee9b006 Mon Sep 17 00:00:00 2001 From: Nimrod Oren Date: Mon, 3 Aug 2026 16:25:19 +0300 Subject: [PATCH 0991/1433] net/mlx5: allocate doorbells from dma pools Allocate doorbells from dma pools instead of the pgdir allocator. Doorbell records remain cache-line sized coherent DMA allocations, but their sub-allocation is now handled by the common mlx5 DMA pool infrastructure. This also makes doorbell allocation honor the requested NUMA node when reusing existing backing pages. The old pgdir allocator used the requested node only when allocating a new pgdir page; later allocations scanned one global pgdir list and could take any pgdir with a free entry, even if that page had been allocated for a different NUMA node. Selecting the per-node DMA pool before sub-allocation keeps reused doorbell records on pages allocated for the requested node. Signed-off-by: Nimrod Oren Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260803132520.2891860-3-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/mellanox/mlx5/core/alloc.c | 113 +++--------------- .../net/ethernet/mellanox/mlx5/core/main.c | 4 - include/linux/mlx5/driver.h | 5 +- 3 files changed, 19 insertions(+), 103 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/alloc.c b/drivers/net/ethernet/mellanox/mlx5/core/alloc.c index 3c9938068c56..3975c726d35c 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/alloc.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/alloc.c @@ -48,13 +48,6 @@ #define MLX5_FRAG_BUF_POOLS_NUM \ (PAGE_SHIFT - MLX5_FRAG_BUF_POOL_MIN_BLOCK_SHIFT + 1) -struct mlx5_db_pgdir { - struct list_head list; - unsigned long *bitmap; - __be32 *db_page; - dma_addr_t db_dma; -}; - struct mlx5_dma_pool { /* Protects page_list and per-page allocation bitmaps. */ struct mutex lock; @@ -481,106 +474,36 @@ int mlx5_db_pools_init(struct mlx5_core_dev *dev) return 0; } -static struct mlx5_db_pgdir *mlx5_alloc_db_pgdir(struct mlx5_core_dev *dev, - int node) -{ - u32 db_per_page = PAGE_SIZE / cache_line_size(); - struct mlx5_db_pgdir *pgdir; - - pgdir = kzalloc_node(sizeof(*pgdir), GFP_KERNEL, node); - if (!pgdir) - return NULL; - - pgdir->bitmap = bitmap_zalloc_node(db_per_page, GFP_KERNEL, node); - if (!pgdir->bitmap) { - kfree(pgdir); - return NULL; - } - - bitmap_fill(pgdir->bitmap, db_per_page); - - pgdir->db_page = mlx5_dma_zalloc_coherent_node(dev, PAGE_SIZE, - &pgdir->db_dma, node); - if (!pgdir->db_page) { - bitmap_free(pgdir->bitmap); - kfree(pgdir); - return NULL; - } - - return pgdir; -} - -static int mlx5_alloc_db_from_pgdir(struct mlx5_db_pgdir *pgdir, - struct mlx5_db *db) -{ - u32 db_per_page = PAGE_SIZE / cache_line_size(); - int offset; - int i; - - i = find_first_bit(pgdir->bitmap, db_per_page); - if (i >= db_per_page) - return -ENOMEM; - - __clear_bit(i, pgdir->bitmap); - - db->u.pgdir = pgdir; - db->index = i; - offset = db->index * cache_line_size(); - db->db = pgdir->db_page + offset / sizeof(*pgdir->db_page); - db->dma = pgdir->db_dma + offset; - - db->db[0] = 0; - db->db[1] = 0; - - return 0; -} - int mlx5_db_alloc_node(struct mlx5_core_dev *dev, struct mlx5_db *db, int node) { - struct mlx5_db_pgdir *pgdir; - int ret = 0; + struct mlx5_dma_pool_page *page; + struct mlx5_dma_pool *pool; + unsigned long idx; + int offset; - mutex_lock(&dev->priv.pgdir_mutex); + node = node == NUMA_NO_NODE ? numa_mem_id() : node; - list_for_each_entry(pgdir, &dev->priv.pgdir_list, list) - if (!mlx5_alloc_db_from_pgdir(pgdir, db)) - goto out; + pool = dev->priv.db_node_pools[node]; + page = mlx5_dma_pool_alloc(pool, &idx); + if (!page) + return -ENOMEM; - pgdir = mlx5_alloc_db_pgdir(dev, node); - if (!pgdir) { - ret = -ENOMEM; - goto out; - } + offset = idx << pool->block_shift; + db->u.pool_page = page; + db->index = idx; + db->db = (__be32 *)((u8 *)page->buf + offset); + db->dma = page->dma + offset; - list_add(&pgdir->list, &dev->priv.pgdir_list); - - /* This should never fail -- we just allocated an empty page: */ - WARN_ON(mlx5_alloc_db_from_pgdir(pgdir, db)); - -out: - mutex_unlock(&dev->priv.pgdir_mutex); - - return ret; + return 0; } EXPORT_SYMBOL_GPL(mlx5_db_alloc_node); void mlx5_db_free(struct mlx5_core_dev *dev, struct mlx5_db *db) { - u32 db_per_page = PAGE_SIZE / cache_line_size(); + struct mlx5_dma_pool_page *page = db->u.pool_page; + struct mlx5_dma_pool *pool = page->pool; - mutex_lock(&dev->priv.pgdir_mutex); - - __set_bit(db->index, db->u.pgdir->bitmap); - - if (bitmap_full(db->u.pgdir->bitmap, db_per_page)) { - dma_free_coherent(mlx5_core_dma_dev(dev), PAGE_SIZE, - db->u.pgdir->db_page, db->u.pgdir->db_dma); - list_del(&db->u.pgdir->list); - bitmap_free(db->u.pgdir->bitmap); - kfree(db->u.pgdir); - } - - mutex_unlock(&dev->priv.pgdir_mutex); + mlx5_dma_pool_free(pool, page, db->index); } EXPORT_SYMBOL_GPL(mlx5_db_free); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/main.c b/drivers/net/ethernet/mellanox/mlx5/core/main.c index b3cb090b5677..5f28d906c35b 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/main.c @@ -1819,8 +1819,6 @@ int mlx5_mdev_init(struct mlx5_core_dev *dev, int profile_idx) INIT_LIST_HEAD(&priv->bfregs.wc_head.list); mutex_init(&priv->alloc_mutex); - mutex_init(&priv->pgdir_mutex); - INIT_LIST_HEAD(&priv->pgdir_list); priv->numa_node = dev_to_node(mlx5_core_dma_dev(dev)); priv->dbg.dbg_root = debugfs_create_dir(dev_name(dev->device), @@ -1900,7 +1898,6 @@ int mlx5_mdev_init(struct mlx5_core_dev *dev, int profile_idx) mlx5_frag_buf_pools_cleanup(dev); err_frag_buf_pools_init: debugfs_remove(dev->priv.dbg.dbg_root); - mutex_destroy(&priv->pgdir_mutex); mutex_destroy(&priv->alloc_mutex); mutex_destroy(&priv->bfregs.wc_head.lock); mutex_destroy(&priv->bfregs.reg_head.lock); @@ -1926,7 +1923,6 @@ void mlx5_mdev_uninit(struct mlx5_core_dev *dev) mlx5_db_pools_cleanup(dev); mlx5_frag_buf_pools_cleanup(dev); debugfs_remove_recursive(dev->priv.dbg.dbg_root); - mutex_destroy(&priv->pgdir_mutex); mutex_destroy(&priv->alloc_mutex); mutex_destroy(&priv->bfregs.wc_head.lock); mutex_destroy(&priv->bfregs.reg_head.lock); diff --git a/include/linux/mlx5/driver.h b/include/linux/mlx5/driver.h index 4246d6d904ba..2c56bb1d676d 100644 --- a/include/linux/mlx5/driver.h +++ b/include/linux/mlx5/driver.h @@ -599,9 +599,6 @@ struct mlx5_priv { struct mutex alloc_mutex; int numa_node; - struct mutex pgdir_mutex; - struct list_head pgdir_list; - struct mlx5_frag_buf_node_pools **frag_buf_node_pools; struct mlx5_dma_pool **db_node_pools; /* end: alloc stuff */ @@ -808,7 +805,7 @@ struct mlx5_core_dev { struct mlx5_db { __be32 *db; union { - struct mlx5_db_pgdir *pgdir; + struct mlx5_dma_pool_page *pool_page; struct mlx5_ib_user_db_page *user_page; } u; dma_addr_t dma; From b7cfcc9d3dfd5e1ad4f00abe066726da4c8552cd Mon Sep 17 00:00:00 2001 From: Nimrod Oren Date: Mon, 3 Aug 2026 16:25:20 +0300 Subject: [PATCH 0992/1433] net/mlx5: add debugfs stats for doorbell dma pools Add a debugfs file exposing per-node DMA pool usage for doorbell allocations. # cat /sys/kernel/debug/mlx5//db_dma_pools node block_size used_blocks allocated_blocks 0 64 0 0 1 64 0 0 Signed-off-by: Nimrod Oren Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260803132520.2891860-4-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/mellanox/mlx5/core/alloc.c | 27 +++++++++++++++++++ include/linux/mlx5/driver.h | 1 + 2 files changed, 28 insertions(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/alloc.c b/drivers/net/ethernet/mellanox/mlx5/core/alloc.c index 3975c726d35c..a92cf545bdaf 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/alloc.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/alloc.c @@ -437,11 +437,34 @@ void mlx5_frag_buf_free(struct mlx5_core_dev *dev, struct mlx5_frag_buf *buf) } EXPORT_SYMBOL_GPL(mlx5_frag_buf_free); +static int mlx5_db_dma_pools_debugfs_show(struct seq_file *file, void *priv) +{ + struct mlx5_core_dev *dev = file->private; + int node; + + mlx5_dma_pools_debugfs_print_header(file); + + for_each_node_state(node, N_POSSIBLE) { + struct mlx5_dma_pool *pool = dev->priv.db_node_pools[node]; + + if (!pool) + continue; + + mlx5_dma_pool_debugfs_stats_print(file, pool); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(mlx5_db_dma_pools_debugfs); + void mlx5_db_pools_cleanup(struct mlx5_core_dev *dev) { struct mlx5_priv *priv = &dev->priv; int node; + debugfs_remove(priv->dbg.db_dma_pools_debugfs); + priv->dbg.db_dma_pools_debugfs = NULL; + for_each_node_state(node, N_POSSIBLE) if (priv->db_node_pools[node]) mlx5_dma_pool_destroy(priv->db_node_pools[node]); @@ -471,6 +494,10 @@ int mlx5_db_pools_init(struct mlx5_core_dev *dev) priv->db_node_pools[node] = pool; } + priv->dbg.db_dma_pools_debugfs = + debugfs_create_file("db_dma_pools", 0444, priv->dbg.dbg_root, + dev, &mlx5_db_dma_pools_debugfs_fops); + return 0; } diff --git a/include/linux/mlx5/driver.h b/include/linux/mlx5/driver.h index 2c56bb1d676d..83d0a83bbfbc 100644 --- a/include/linux/mlx5/driver.h +++ b/include/linux/mlx5/driver.h @@ -548,6 +548,7 @@ struct mlx5_debugfs_entries { struct dentry *cq_debugfs; struct dentry *cmdif_debugfs; struct dentry *frag_buf_dma_pools_debugfs; + struct dentry *db_dma_pools_debugfs; struct dentry *pages_debugfs; struct dentry *lag_debugfs; }; From d21a374995df9d7cfd193427748a19dcc87956a9 Mon Sep 17 00:00:00 2001 From: Johan Alvarado Date: Fri, 31 Jul 2026 14:45:22 -0500 Subject: [PATCH 0993/1433] net: stmmac: raise TX completion interrupt at the end of an xmit burst The TX mitigation logic only sets the Interrupt on Completion bit once every tx_coal_frames descriptors (STMMAC_TX_FRAMES = 25), with the tx_coal_timer hrtimer (STMMAC_COAL_TX_TIMER = 5000 us) as the only fallback. TX skbs are freed exclusively from the TX completion path, so any flow that keeps fewer than 25 frames in flight has all of its skbs held for up to 5 ms after transmission. Paced flows never queue enough frames to reach the frame threshold: TCP Small Queues caps the amount of unfreed data at roughly two pacing intervals worth, which at moderate pacing rates is only a couple of packets. Every small burst then stalls until the coalesce timer fires, and throughput collapses to approximately tsq_limit / tx_coal_timer regardless of link capacity. This is easily reproducible with BBR, which paces its output and thus keeps only a few frames in flight at a time. On a YT6801 (dwmac-motorcomm) equipped Orange Pi 5 Pro, a BBR upload over a ~23 ms RTT path is capped at 5.24 Mbit/s, while CUBIC reaches 207 Mbit/s on the same path. BBR measures the stalled send rate as the path bandwidth and locks its estimate near the floor, so the connection never recovers. Lowering the coalesce settings with ethtool -C (tx-usecs 100 tx-frames 1) lifts the same transfer to 447 Mbit/s, confirming the mechanism. Fix this by setting the IC bit on the last descriptor of every xmit burst, i.e. whenever netdev_xmit_more() reports that no further frames are pending in the current dequeue batch. Frame-based coalescing still applies within a burst, bulk traffic keeps batching through qdisc bulk dequeue and NAPI polling, and the coalesce timer becomes a pure fallback instead of the primary completion mechanism for lightly queued flows. tx-frames 0 keeps its meaning of timer-based mitigation only. Signed-off-by: Johan Alvarado Link: https://patch.msgid.link/20260731194522.55069-1-contact@c127.dev Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 79b71466d5b0..c729ab127afd 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -4617,6 +4617,8 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) set_ic = true; else if (!priv->tx_coal_frames[queue]) set_ic = false; + else if (!netdev_xmit_more()) + set_ic = true; else if (tx_packets > priv->tx_coal_frames[queue]) set_ic = true; else if ((tx_q->tx_count_frames % @@ -4901,6 +4903,8 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) set_ic = true; else if (!priv->tx_coal_frames[queue]) set_ic = false; + else if (!netdev_xmit_more()) + set_ic = true; else if (tx_packets > priv->tx_coal_frames[queue]) set_ic = true; else if ((tx_q->tx_count_frames % From b0057c68df711bf6a62033c072ac61c4f9d3cbc1 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Fri, 31 Jul 2026 16:46:23 +0000 Subject: [PATCH 0994/1433] xsk: account ring allocations to memcg AF_XDP rings are allocated from setsockopt() and can be mapped into user space. The shared xskq_create() helper allocates the ring backing memory, but the user-controlled and long-lived allocation is not charged as kmem to the allocating memory cgroup. The current implementation uses vmalloc_user(), which allocates the backing pages with GFP_KERNEL | __GFP_ZERO. Use the same VM_USERMAP vmalloc path, but pass GFP_KERNEL_ACCOUNT so the ring backing pages are attributed to memcg/kmem and can be constrained by existing cgroup memory limits. This keeps the existing zeroing and mmap semantics while avoiding AF_XDP-specific optmem or RLIMIT_MEMLOCK accounting. Signed-off-by: Zihan Xi Acked-by: Stanislav Fomichev Link: https://patch.msgid.link/20260731164623.4694-2-zihanx@nebusec.ai Signed-off-by: Jakub Kicinski --- net/xdp/xsk_queue.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/net/xdp/xsk_queue.c b/net/xdp/xsk_queue.c index 4dd01b7d858e..d95b6d0d94aa 100644 --- a/net/xdp/xsk_queue.c +++ b/net/xdp/xsk_queue.c @@ -9,6 +9,8 @@ #include #include +#include + #include "xsk_queue.h" static size_t xskq_get_ring_size(struct xsk_queue *q, bool umem_queue) @@ -21,6 +23,14 @@ static size_t xskq_get_ring_size(struct xsk_queue *q, bool umem_queue) return struct_size(rxtx_ring, desc, q->nentries); } +static void *xskq_vmalloc_user(unsigned long size) +{ + return __vmalloc_node_range(size, SHMLBA, VMALLOC_START, VMALLOC_END, + GFP_KERNEL_ACCOUNT | __GFP_ZERO, PAGE_KERNEL, + VM_USERMAP, NUMA_NO_NODE, + __builtin_return_address(0)); +} + struct xsk_queue *xskq_create(u32 nentries, bool umem_queue) { struct xsk_queue *q; @@ -46,7 +56,7 @@ struct xsk_queue *xskq_create(u32 nentries, bool umem_queue) size = PAGE_ALIGN(size); - q->ring = vmalloc_user(size); + q->ring = xskq_vmalloc_user(size); if (!q->ring) { kfree(q); return NULL; From 51e15308c6ae634ddae8f241d711ff5866909b58 Mon Sep 17 00:00:00 2001 From: Artem Lytkin Date: Sat, 1 Aug 2026 14:49:44 +0300 Subject: [PATCH 0995/1433] rtnetlink: cap IFLA_VFINFO_LIST at a documented number of VFs rtnl_fill_vf() emits one IFLA_VF_INFO per VF into the IFLA_VFINFO_LIST nest and closes it with nla_nest_end(), which stores the accumulated length into nla_len. That field is a u16, so a nest larger than 65535 bytes is written truncated modulo 65536. The list dates back to commit c02db8c6290b ("rtnetlink: make SR-IOV VF interface symmetric") in 2010 and has never been able to describe an arbitrary number of VFs; nothing regressed, the encoding simply cannot represent it. Nothing catches it on the way. if_nlmsg_size() adds rtnl_vfinfo_size() for every VF, so the skb really is large enough and none of the nla_put() calls fails. Userspace then walks the message with RTA_NEXT(), which advances by the stored length, so parsing resumes inside VF payload and the attributes after the nest are read out of VF data: IFLA_VF_PORTS, IFLA_XDP, IFLA_LINKINFO, IFLA_PERM_ADDRESS, IFLA_AF_SPEC. iproute2 prints "!!!Deficit" and strictly validating parsers reject the message. On CONFIG_DEBUG_NET kernels nla_nest_end() also splats, via the DEBUG_NET_WARN_ON_ONCE() added in commit ff205bf8c554 ("netlink: add one debug check in nla_nest_end()"). Where the wrap falls depends on what was asked for and on the host. A VF costs 196 bytes, 296 with statistics, 236 with GUIDs and 336 with both, and on a kernel without CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS the statistics carry a padding attribute each and cost 32 bytes more, making those two 328 and 368. The nest therefore overflows somewhere between 179 and 335 VFs, and ice allows 256 per PF (ICE_MAX_SRIOV_VFS), which reaches it. Statistics are included unless the request sets RTEXT_FILTER_SKIP_STATS, so the common case is the one that wraps first. A limit that moves with the requested attribute set and with the host's alignment requirements is not something userspace can be told, so use fixed numbers instead and document them as what the interface supports: 256 VFs, or 128 when statistics are included. Both stay well inside U16_MAX even in the largest per-VF encoding, at 60416 and 47104 bytes respectively. rtnl_vfinfo_cap() applies the cap in both places, so rtnl_vfinfo_size() does not size the skb for VFs that will not be emitted. A device with more VFs than the limit reports a shorter IFLA_VFINFO_LIST. IFLA_NUM_VF keeps carrying the real count, and everything after the nest stays parsable, which is the part that is broken today. An empty nest is already emitted for a PF with no VFs, so a list shorter than IFLA_NUM_VF is not a new encoding. Returning -EMSGSIZE instead, which is what nla_nest_end_safe() would give, is not an option here: a nest that does not fit in a u16 will not fit in a retried skb either, so it would turn a link dump on such a device into a hard failure. The other large nests in rtnl_fill_ifinfo() were audited and cannot overflow. IFLA_AF_SPEC is bounded by a handful of address families at about a kilobyte each, and IFLA_VF_PORTS would need more than 560 VFs, which no in-tree driver allows. Reported-by: Jacob Keller Link: https://lore.kernel.org/netdev/16b289f6-b025-5dd3-443d-92d4c167e79c@intel.com/ Assisted-by: Claude:claude-fable-5 Signed-off-by: Artem Lytkin Link: https://patch.msgid.link/20260801114944.115272-1-iprintercanon@gmail.com Signed-off-by: Paolo Abeni --- Documentation/netlink/specs/rt-link.yaml | 5 +++++ net/core/rtnetlink.c | 22 +++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Documentation/netlink/specs/rt-link.yaml b/Documentation/netlink/specs/rt-link.yaml index 68c26a70bb64..b80c2ac3ac31 100644 --- a/Documentation/netlink/specs/rt-link.yaml +++ b/Documentation/netlink/specs/rt-link.yaml @@ -928,6 +928,11 @@ attribute-sets: name: vfinfo-list type: nest nested-attributes: vfinfo-list-attrs + doc: | + Per-VF details. The list holds at most 256 VFs, or 128 when + statistics are included, because it is one attribute and has to fit + in a u16 length. A device with more VFs than that reports a + truncated list; num-vf still carries the real count. - name: stats64 type: binary diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index 31c65a545a10..81c5a6104dea 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -1174,12 +1174,27 @@ static void copy_rtnl_link_stats(struct rtnl_link_stats *a, a->rx_nohandler = b->rx_nohandler; } +/* Cap the number of VFs that IFLA_VFINFO_LIST describes. The nest is one + * netlink attribute, so everything inside it has to fit in the u16 nla_len. + * The cap is a fixed number rather than whatever happens to fit, so that the + * limit is a property of the interface instead of one of the requested + * attribute set and the host's alignment requirements. The largest per-VF + * encoding is 368 bytes with statistics and GUIDs and 236 bytes without + * statistics, so both values keep the nest well inside U16_MAX. + */ +static int rtnl_vfinfo_cap(int num_vfs, u32 ext_filter_mask) +{ + return min(num_vfs, + ext_filter_mask & RTEXT_FILTER_SKIP_STATS ? 256 : 128); +} + /* All VF info */ static inline int rtnl_vfinfo_size(const struct net_device *dev, u32 ext_filter_mask) { if (dev->dev.parent && (ext_filter_mask & RTEXT_FILTER_VF)) { - int num_vfs = dev_num_vf(dev->dev.parent); + int num_vfs = rtnl_vfinfo_cap(dev_num_vf(dev->dev.parent), + ext_filter_mask); size_t size = nla_total_size(0); size += num_vfs * (nla_total_size(0) + @@ -1717,6 +1732,11 @@ static noinline_for_stack int rtnl_fill_vf(struct sk_buff *skb, if (!vfinfo) return -EMSGSIZE; + /* IFLA_NUM_VF above stays the device's VF count; the list itself is + * capped so that its length cannot overflow nla_len. + */ + num_vfs = rtnl_vfinfo_cap(num_vfs, ext_filter_mask); + for (i = 0; i < num_vfs; i++) { if (rtnl_fill_vfinfo(skb, dev, i, ext_filter_mask)) { nla_nest_cancel(skb, vfinfo); From f0b48e031d84feeace2313625f02887277759d71 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Thu, 6 Aug 2026 12:06:43 +0200 Subject: [PATCH 0996/1433] wifi: nxp: NXPWIFI should be invisible and selected by its users All supported NXP WiFi wireless adapters have an SDIO interface. Hence there is no point in asking the user about these adapters when configuring a kernel without MMC support. Fix this by making the core driver symbol invisible, and selecting it by its user when needed. Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/aefb37d8398175cb2fb520cb5f725a85bcd3049d.1786010763.git.geert+renesas@glider.be Signed-off-by: Johannes Berg --- drivers/net/wireless/nxp/nxpwifi/Kconfig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/Kconfig b/drivers/net/wireless/nxp/nxpwifi/Kconfig index 3637068574b8..38a8820084dd 100644 --- a/drivers/net/wireless/nxp/nxpwifi/Kconfig +++ b/drivers/net/wireless/nxp/nxpwifi/Kconfig @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only config NXPWIFI - tristate "NXP WiFi Driver" + tristate "NXP WiFi Driver" if COMPILE_TEST depends on CFG80211 help This adds support for wireless adapters based on NXP @@ -11,7 +11,8 @@ config NXPWIFI config NXPWIFI_SDIO tristate "NXP WiFi Driver for IW61x" - depends on NXPWIFI && MMC + depends on CFG80211 && MMC + select NXPWIFI select FW_LOADER select WANT_DEV_COREDUMP help From ea21b21ca0536306adf811b1d4bad1139cf54759 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Thu, 6 Aug 2026 12:05:42 +0200 Subject: [PATCH 0997/1433] wifi: morsemicro: MM81X should be invisible and selected by its users Morse Micro MM81x wireless devices can have either SDIO or USB interfaces. Hence there is no point in asking the user about these devices when configuring a kernel without MMC or USB support. Fix this by making the core driver symbol invisible, and selecting it by its users when needed. Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/3415bda97c2faf7c56eff7fe79a91b218d0d6731.1786010705.git.geert+renesas@glider.be Signed-off-by: Johannes Berg --- drivers/net/wireless/morsemicro/mm81x/Kconfig | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/morsemicro/mm81x/Kconfig b/drivers/net/wireless/morsemicro/mm81x/Kconfig index 33cdcc0df4de..4c4fae77ff67 100644 --- a/drivers/net/wireless/morsemicro/mm81x/Kconfig +++ b/drivers/net/wireless/morsemicro/mm81x/Kconfig @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 config MM81X - tristate "Morse Micro MM81x wireless devices" + tristate "Morse Micro MM81x wireless devices" if COMPILE_TEST depends on MAC80211 select FW_LOADER select CRC7 @@ -11,14 +11,16 @@ config MM81X config MM81X_USB tristate "Morse Micro MM81x USB support" - depends on MM81X && USB + depends on MAC80211 && USB + select MM81X help This module adds support for the USB interface of devices using the Morse Micro MM81x chipset. config MM81X_SDIO tristate "Morse Micro MM81x SDIO support" - depends on MM81X && MMC + depends on MAC80211 && MMC + select MM81X help This module adds support for the SDIO interface of devices using the Morse Micro MM81x chipset. From 00c786a7581e62243608592543bca3c0ec3514cc Mon Sep 17 00:00:00 2001 From: Jeff Chen Date: Tue, 4 Aug 2026 00:27:41 +0800 Subject: [PATCH 0998/1433] wifi: nxpwifi: fix multiple static analysis errors and warnings Fix various development-phase bugs, code quality, and logical issues reported by the kernel test robot (using the Smatch static analysis tool). The following addressable fixes are included: - 11n.c & 11ax.c: Fix potential NULL pointer dereferences by correcting logical operators (&& to ||) in 11n.c and hoisting the bss_desc verification to the top of the function in 11ax.c. - 11n.c: Fix a severe Use-After-Free (UAF) memory corruption during RCU list traversal. Restore the proper list_for_each_entry_safe() loop structure along with the required array index [i] within the locked writer path. - sdio.c: Fix a missing unwind resource cleanup pathway where a protocol error branch returned directly via -EINVAL instead of using 'goto term_cmd', leaving the SDIO hardware state machine out of sync. - main.h: Fix a signedness mismatch bug where nxpwifi_get_unused_bss_num() could return -2 as an unsigned integer fallback. - util.c: Remove a redundant and dead condition check (position <= 15) which was always true for a 4-bit unsigned bit-field member variable. - cfg80211.c: Clean up a dead unreachable 'return 0' at the bottom of the switch-case logic. - uap_txrx.c: Clean up mismatched and inconsistent indentations within the handling of multicast RX forward paths. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202608020855.QwN5n7i5-lkp@intel.com/ Assisted-by: Gemini:unknown-model Signed-off-by: Jeff Chen Link: https://patch.msgid.link/20260803162741.438820-1-chunfan.chen@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/nxp/nxpwifi/11ax.c | 5 +- drivers/net/wireless/nxp/nxpwifi/11n.c | 7 +-- drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 14 ++++-- drivers/net/wireless/nxp/nxpwifi/main.c | 2 +- drivers/net/wireless/nxp/nxpwifi/main.h | 21 ++++++--- drivers/net/wireless/nxp/nxpwifi/sdio.c | 3 +- drivers/net/wireless/nxp/nxpwifi/uap_txrx.c | 4 +- drivers/net/wireless/nxp/nxpwifi/util.c | 51 ++++++++++----------- 8 files changed, 62 insertions(+), 45 deletions(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/11ax.c b/drivers/net/wireless/nxp/nxpwifi/11ax.c index cc47c435eb70..96540914f3cf 100644 --- a/drivers/net/wireless/nxp/nxpwifi/11ax.c +++ b/drivers/net/wireless/nxp/nxpwifi/11ax.c @@ -413,7 +413,10 @@ bool nxpwifi_is_11ax_twt_supported(struct nxpwifi_private *priv, struct nxpwifi_ie_types_he_cap *user_he_cap; struct nxpwifi_ie_types_he_cap *hw_he_cap; - if (bss_desc && (!nxpwifi_is_ap_11ax_twt_supported(bss_desc))) { + if (!bss_desc) + return false; + + if (!nxpwifi_is_ap_11ax_twt_supported(bss_desc)) { nxpwifi_dbg(priv->adapter, MSG, "AP don't support twt feature\n"); return false; diff --git a/drivers/net/wireless/nxp/nxpwifi/11n.c b/drivers/net/wireless/nxp/nxpwifi/11n.c index e46c5053d509..c2a54d781b42 100644 --- a/drivers/net/wireless/nxp/nxpwifi/11n.c +++ b/drivers/net/wireless/nxp/nxpwifi/11n.c @@ -451,7 +451,7 @@ void nxpwifi_11n_delete_tx_ba_stream_tbl_entry(struct nxpwifi_private *priv, struct nxpwifi_tx_ba_stream_tbl *tbl) { - if (!tbl && nxpwifi_is_tx_ba_stream_ptr_valid(priv, tbl)) + if (!tbl || nxpwifi_is_tx_ba_stream_ptr_valid(priv, tbl)) return; nxpwifi_dbg(priv->adapter, INFO, @@ -694,7 +694,7 @@ int nxpwifi_get_tx_ba_stream_tbl(struct nxpwifi_private *priv, /* Delete Tx BA stream entry by RA. */ void nxpwifi_del_tx_ba_stream_tbl_by_ra(struct nxpwifi_private *priv, u8 *ra) { - struct nxpwifi_tx_ba_stream_tbl *tbl; + struct nxpwifi_tx_ba_stream_tbl *tbl, *tmp; int i; if (!ra) @@ -702,7 +702,8 @@ void nxpwifi_del_tx_ba_stream_tbl_by_ra(struct nxpwifi_private *priv, u8 *ra) for (i = 0; i < MAX_NUM_TID; i++) { spin_lock_bh(&priv->tx_ba_stream_tbl_lock[i]); - list_for_each_entry_rcu(tbl, &priv->tx_ba_stream_tbl_ptr[i], list) + list_for_each_entry_safe(tbl, tmp, + &priv->tx_ba_stream_tbl_ptr[i], list) if (!memcmp(tbl->ra, ra, ETH_ALEN)) nxpwifi_11n_delete_tx_ba_stream_tbl_entry(priv, tbl); diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c index c820f08d2835..5cc8cdf594d3 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -717,6 +717,7 @@ nxpwifi_init_new_priv_params(struct nxpwifi_private *priv, enum nl80211_iftype type) { struct nxpwifi_adapter *adapter = priv->adapter; + int ret; nxpwifi_init_priv(priv); @@ -740,7 +741,15 @@ nxpwifi_init_new_priv_params(struct nxpwifi_private *priv, return -EOPNOTSUPP; } - priv->bss_num = nxpwifi_get_unused_bss_num(adapter, priv->bss_type); + ret = nxpwifi_get_unused_bss_num(adapter, priv->bss_type, + &priv->bss_num); + + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "%s: no unused bss_num for type %d\n", + dev->name, priv->bss_type); + return ret; + } flush_workqueue(adapter->workqueue); atomic_set(&adapter->iface_changing, 0); @@ -943,7 +952,6 @@ nxpwifi_cfg80211_change_virtual_intf(struct wiphy *wiphy, case NL80211_IFTYPE_STATION: return nxpwifi_change_vif_to_sta(dev, curr_iftype, type, params); - break; default: goto errnotsupp; } @@ -952,8 +960,6 @@ nxpwifi_cfg80211_change_virtual_intf(struct wiphy *wiphy, goto errnotsupp; } - return 0; - errnotsupp: nxpwifi_dbg(priv->adapter, ERROR, "unsupported interface type transition: %d to %d\n", diff --git a/drivers/net/wireless/nxp/nxpwifi/main.c b/drivers/net/wireless/nxp/nxpwifi/main.c index 4e01f45f3a00..b4c63829024a 100644 --- a/drivers/net/wireless/nxp/nxpwifi/main.c +++ b/drivers/net/wireless/nxp/nxpwifi/main.c @@ -204,7 +204,7 @@ static bool nxpwifi_drain_tx(struct nxpwifi_adapter *adapter) NXPWIFI_ASYNC_CMD); adapter->hs_activated_manually = false; } - nxpwifi_process_bypass_tx(adapter); + nxpwifi_process_bypass_tx(adapter); if (adapter->hs_activated) { clear_bit(NXPWIFI_IS_HS_CONFIGURED, &adapter->work_flags); diff --git a/drivers/net/wireless/nxp/nxpwifi/main.h b/drivers/net/wireless/nxp/nxpwifi/main.h index 349dfa4d3f85..b25a6a4f2936 100644 --- a/drivers/net/wireless/nxp/nxpwifi/main.h +++ b/drivers/net/wireless/nxp/nxpwifi/main.h @@ -1166,8 +1166,9 @@ nxpwifi_get_priv(struct nxpwifi_adapter *adapter, } /* find unused BSS number for new interface */ -static inline u8 -nxpwifi_get_unused_bss_num(struct nxpwifi_adapter *adapter, u8 bss_type) +static inline int +nxpwifi_get_unused_bss_num(struct nxpwifi_adapter *adapter, u8 bss_type, + u8 *bss_num) { u8 i, j; int index[NXPWIFI_MAX_BSS_NUM]; @@ -1179,9 +1180,14 @@ nxpwifi_get_unused_bss_num(struct nxpwifi_adapter *adapter, u8 bss_type) NL80211_IFTYPE_UNSPECIFIED)) { index[adapter->priv[i]->bss_num] = 1; } - for (j = 0; j < NXPWIFI_MAX_BSS_NUM; j++) - if (!index[j]) - return j; + + for (j = 0; j < NXPWIFI_MAX_BSS_NUM; j++) { + if (!index[j]) { + *bss_num = j; + return 0; + } + } + return -ENOENT; } @@ -1195,8 +1201,9 @@ nxpwifi_get_unused_priv_by_bss_type(struct nxpwifi_adapter *adapter, for (i = 0; i < adapter->priv_num; i++) if (adapter->priv[i]->bss_mode == NL80211_IFTYPE_UNSPECIFIED) { - adapter->priv[i]->bss_num = - nxpwifi_get_unused_bss_num(adapter, bss_type); + if (nxpwifi_get_unused_bss_num(adapter, bss_type, + &adapter->priv[i]->bss_num)) + return NULL; break; } diff --git a/drivers/net/wireless/nxp/nxpwifi/sdio.c b/drivers/net/wireless/nxp/nxpwifi/sdio.c index d8536354f093..8ef0f6eb49e2 100644 --- a/drivers/net/wireless/nxp/nxpwifi/sdio.c +++ b/drivers/net/wireless/nxp/nxpwifi/sdio.c @@ -1347,7 +1347,8 @@ static int nxpwifi_process_int_status(struct nxpwifi_adapter *adapter, u8 sdio_i ((rx_blocks * NXPWIFI_SDIO_BLOCK_SIZE) > card->mpa_rx.buf_size))) { nxpwifi_dbg(adapter, ERROR, "invalid rx_len=%d\n", rx_len); - return -EINVAL; + ret = -EINVAL; + goto term_cmd; } rx_len = (u16)(rx_blocks * NXPWIFI_SDIO_BLOCK_SIZE); diff --git a/drivers/net/wireless/nxp/nxpwifi/uap_txrx.c b/drivers/net/wireless/nxp/nxpwifi/uap_txrx.c index f3d24bf861ca..840dddfc4f5a 100644 --- a/drivers/net/wireless/nxp/nxpwifi/uap_txrx.c +++ b/drivers/net/wireless/nxp/nxpwifi/uap_txrx.c @@ -246,8 +246,8 @@ int nxpwifi_handle_uap_rx_forward(struct nxpwifi_private *priv, } else { nxpwifi_dbg(adapter, ERROR, "failed to copy skb for uAP\n"); - priv->stats.rx_dropped++; - dev_kfree_skb_any(skb); + priv->stats.rx_dropped++; + dev_kfree_skb_any(skb); return -ENOMEM; } } else { diff --git a/drivers/net/wireless/nxp/nxpwifi/util.c b/drivers/net/wireless/nxp/nxpwifi/util.c index 29ef031f8ec9..bbfefb81d8d3 100644 --- a/drivers/net/wireless/nxp/nxpwifi/util.c +++ b/drivers/net/wireless/nxp/nxpwifi/util.c @@ -799,34 +799,33 @@ int nxpwifi_recv_packet_to_monif(struct nxpwifi_private *priv, __le16 acc_le; u8 flags = 0; - if (ext.timestamp.position <= 15) { - hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_TIMESTAMP)); - off = ALIGN(off, 8); + hdr->it_present |= + cpu_to_le32(BIT(IEEE80211_RADIOTAP_TIMESTAMP)); + off = ALIGN(off, 8); - if (ext.timestamp.flags & 0x01) { - flags |= IEEE80211_RADIOTAP_TIMESTAMP_FLAG_32BIT; - ts = (u32)ext.timestamp.device_timestamp; - } else { - flags |= IEEE80211_RADIOTAP_TIMESTAMP_FLAG_64BIT; - ts = ext.timestamp.device_timestamp; - } - - ts_le = cpu_to_le64(ts); - memcpy(rthdr + off, &ts_le, sizeof(ts_le)); - off += sizeof(ts_le); - - if (ext.timestamp.flags & 0x02) { - accuracy = ext.timestamp.accuracy; - flags |= IEEE80211_RADIOTAP_TIMESTAMP_FLAG_ACCURACY; - } - - acc_le = cpu_to_le16(accuracy); - memcpy(rthdr + off, &acc_le, sizeof(acc_le)); - off += sizeof(acc_le); - rthdr[off++] = (ext.timestamp.unit & 0x0f) | - ((ext.timestamp.position & 0x0f) << 4); - rthdr[off++] = flags; + if (ext.timestamp.flags & 0x01) { + flags |= IEEE80211_RADIOTAP_TIMESTAMP_FLAG_32BIT; + ts = (u32)ext.timestamp.device_timestamp; + } else { + flags |= IEEE80211_RADIOTAP_TIMESTAMP_FLAG_64BIT; + ts = ext.timestamp.device_timestamp; } + + ts_le = cpu_to_le64(ts); + memcpy(rthdr + off, &ts_le, sizeof(ts_le)); + off += sizeof(ts_le); + + if (ext.timestamp.flags & 0x02) { + accuracy = ext.timestamp.accuracy; + flags |= IEEE80211_RADIOTAP_TIMESTAMP_FLAG_ACCURACY; + } + + acc_le = cpu_to_le16(accuracy); + memcpy(rthdr + off, &acc_le, sizeof(acc_le)); + off += sizeof(acc_le); + rthdr[off++] = (ext.timestamp.unit & 0x0f) | + ((ext.timestamp.position & 0x0f) << 4); + rthdr[off++] = flags; } if (format == NXPWIFI_RATE_FORMAT_HE && has_ext) { From 4d8cfff012aaa971d50b01823b1015c1073c3ff6 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 4 Aug 2026 08:26:08 +0000 Subject: [PATCH 0999/1433] wifi: mac80211: skip default WMM setup for AP_VLAN links AP_VLAN interfaces are never passed to the driver, so setting default WMM parameters on their links trips the check-sdata-in-driver warning in drv_conf_tx(), as well as in the BSS_CHANGED_QOS link info notification. Skip it, matching the existing AP_VLAN handling in this function. Fixes: 2259d14499d1 ("wifi: mac80211: set default WMM parameters on all links") Signed-off-by: Felix Fietkau Link: https://patch.msgid.link/20260804082608.2011433-1-nbd@nbd.name Signed-off-by: Johannes Berg --- net/mac80211/link.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/mac80211/link.c b/net/mac80211/link.c index dc68144dc363..931950a10508 100644 --- a/net/mac80211/link.c +++ b/net/mac80211/link.c @@ -360,7 +360,8 @@ static int ieee80211_vif_update_links(struct ieee80211_sub_if_data *sdata, link = links[link_id]; ieee80211_link_init(sdata, link_id, &link->data, &link->conf); ieee80211_link_setup(&link->data); - ieee80211_set_wmm_default(&link->data, true, non_sta); + if (sdata->vif.type != NL80211_IFTYPE_AP_VLAN) + ieee80211_set_wmm_default(&link->data, true, non_sta); } if (new_links == 0) From da308f6e471252ae5e8009dcea16bc5e9c9dc5dd Mon Sep 17 00:00:00 2001 From: Stefan Hansson Date: Tue, 4 Aug 2026 10:55:41 +0200 Subject: [PATCH 1000/1433] wifi: rsi: Fix types to appease CFI Avoids errors like: CFI failure at kthread+0x124/0x1cc (target: rsi_coex_scheduler_thread+0x0/0x1b4 [redpine_91x]; expected type: 0x89fb613d) As seen in the aforementioned error this was tested using the downstream redpine_91x driver found in the Librem 5's downstream source tree. However, it appears that this driver is a modified version of the rsi driver found in mainline Linux and as such I decided to port the changes here too. Signed-off-by: Stefan Hansson Link: https://patch.msgid.link/20260804-rsi-cfi-fix-v2-1-59679a520240@postmarketos.org Signed-off-by: Johannes Berg --- drivers/net/wireless/rsi/rsi_91x_coex.c | 3 ++- drivers/net/wireless/rsi/rsi_91x_main.c | 7 ++++--- drivers/net/wireless/rsi/rsi_91x_sdio_ops.c | 3 ++- drivers/net/wireless/rsi/rsi_91x_usb_ops.c | 7 ++++--- drivers/net/wireless/rsi/rsi_common.h | 2 +- drivers/net/wireless/rsi/rsi_sdio.h | 2 +- drivers/net/wireless/rsi/rsi_usb.h | 2 +- 7 files changed, 15 insertions(+), 11 deletions(-) diff --git a/drivers/net/wireless/rsi/rsi_91x_coex.c b/drivers/net/wireless/rsi/rsi_91x_coex.c index ee603a5173fb..023c539fc384 100644 --- a/drivers/net/wireless/rsi/rsi_91x_coex.c +++ b/drivers/net/wireless/rsi/rsi_91x_coex.c @@ -50,8 +50,9 @@ static void rsi_coex_sched_tx_pkts(struct rsi_coex_ctrl_block *coex_cb) } while (coex_q != RSI_COEX_Q_INVALID); } -static void rsi_coex_scheduler_thread(struct rsi_common *common) +static int rsi_coex_scheduler_thread(void *data) { + struct rsi_common *common = data; struct rsi_coex_ctrl_block *coex_cb = common->coex_cb; u32 timeout = EVENT_WAIT_FOREVER; diff --git a/drivers/net/wireless/rsi/rsi_91x_main.c b/drivers/net/wireless/rsi/rsi_91x_main.c index 662e42d1e5e8..2ce514766620 100644 --- a/drivers/net/wireless/rsi/rsi_91x_main.c +++ b/drivers/net/wireless/rsi/rsi_91x_main.c @@ -246,12 +246,13 @@ EXPORT_SYMBOL_GPL(rsi_read_pkt); /** * rsi_tx_scheduler_thread() - This function is a kernel thread to send the * packets to the device. - * @common: Pointer to the driver private structure. + * @data: Pointer to the driver private structure. * - * Return: None. + * Return: 0. */ -static void rsi_tx_scheduler_thread(struct rsi_common *common) +static int rsi_tx_scheduler_thread(void *data) { + struct rsi_common *common = data; struct rsi_hw *adapter = common->priv; u32 timeout = EVENT_WAIT_FOREVER; diff --git a/drivers/net/wireless/rsi/rsi_91x_sdio_ops.c b/drivers/net/wireless/rsi/rsi_91x_sdio_ops.c index 597b238e2294..18a28aa97446 100644 --- a/drivers/net/wireless/rsi/rsi_91x_sdio_ops.c +++ b/drivers/net/wireless/rsi/rsi_91x_sdio_ops.c @@ -62,8 +62,9 @@ int rsi_sdio_master_access_msword(struct rsi_hw *adapter, u16 ms_word) static void rsi_rx_handler(struct rsi_hw *adapter); -void rsi_sdio_rx_thread(struct rsi_common *common) +int rsi_sdio_rx_thread(void *data) { + struct rsi_common *common = data; struct rsi_hw *adapter = common->priv; struct rsi_91x_sdiodev *sdev = adapter->rsi_dev; diff --git a/drivers/net/wireless/rsi/rsi_91x_usb_ops.c b/drivers/net/wireless/rsi/rsi_91x_usb_ops.c index 25c2b232394a..e899631b9aed 100644 --- a/drivers/net/wireless/rsi/rsi_91x_usb_ops.c +++ b/drivers/net/wireless/rsi/rsi_91x_usb_ops.c @@ -21,12 +21,13 @@ /** * rsi_usb_rx_thread() - This is a kernel thread to receive the packets from * the USB device. - * @common: Pointer to the driver private structure. + * @data: Pointer to the driver private structure. * - * Return: None. + * Return: 0. */ -void rsi_usb_rx_thread(struct rsi_common *common) +int rsi_usb_rx_thread(void *data) { + struct rsi_common *common = data; struct rsi_hw *adapter = common->priv; struct rsi_91x_usbdev *dev = adapter->rsi_dev; int status; diff --git a/drivers/net/wireless/rsi/rsi_common.h b/drivers/net/wireless/rsi/rsi_common.h index 3cdf9ded876d..2a33a81f71a3 100644 --- a/drivers/net/wireless/rsi/rsi_common.h +++ b/drivers/net/wireless/rsi/rsi_common.h @@ -58,7 +58,7 @@ static inline void rsi_reset_event(struct rsi_event *event) static inline int rsi_create_kthread(struct rsi_common *common, struct rsi_thread *thread, - void *func_ptr, + int (*func_ptr)(void *data), u8 *name) { init_completion(&thread->completion); diff --git a/drivers/net/wireless/rsi/rsi_sdio.h b/drivers/net/wireless/rsi/rsi_sdio.h index 7c91b126b350..eb2b7f36a7e4 100644 --- a/drivers/net/wireless/rsi/rsi_sdio.h +++ b/drivers/net/wireless/rsi/rsi_sdio.h @@ -134,5 +134,5 @@ int rsi_sdio_master_access_msword(struct rsi_hw *adapter, u16 ms_word); void rsi_sdio_ack_intr(struct rsi_hw *adapter, u8 int_bit); int rsi_sdio_determine_event_timeout(struct rsi_hw *adapter); int rsi_sdio_check_buffer_status(struct rsi_hw *adapter, u8 q_num); -void rsi_sdio_rx_thread(struct rsi_common *common); +int rsi_sdio_rx_thread(void *data); #endif diff --git a/drivers/net/wireless/rsi/rsi_usb.h b/drivers/net/wireless/rsi/rsi_usb.h index 961851748bc4..78067eaffe8b 100644 --- a/drivers/net/wireless/rsi/rsi_usb.h +++ b/drivers/net/wireless/rsi/rsi_usb.h @@ -81,5 +81,5 @@ static inline int rsi_usb_event_timeout(struct rsi_hw *adapter) return EVENT_WAIT_FOREVER; } -void rsi_usb_rx_thread(struct rsi_common *common); +int rsi_usb_rx_thread(void *data); #endif From 0d10db8e94fcb23a799789aaa696b4d8f937e207 Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Mon, 3 Aug 2026 11:35:06 +0200 Subject: [PATCH 1001/1433] wifi: brcmfmac: Fix memory leak in brcmf_sdio_read_control() The memory allocated for buf is not freed in some of the error paths in brcmf_sdio_read_control(). Fix that by adding vfree() calls. Cc: stable@vger.kernel.org Fixes: dd43a01c5cdb ("brcmfmac: use dynamically allocated control frame buffer") Signed-off-by: Abdun Nihaal [arend: rework as suggested by Johannes] Signed-off-by: Arend van Spriel Link: https://patch.msgid.link/20260803093506.1647790-1-arend.vanspriel@broadcom.com Signed-off-by: Johannes Berg --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c index 9f7ed1d293a0..381801af3ac9 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c @@ -1827,17 +1827,18 @@ brcmf_sdio_read_control(struct brcmf_sdio *bus, u8 *hdr, uint len, uint doff) if (bus->rxctl) { brcmf_err("last control frame is being processed.\n"); spin_unlock_bh(&bus->rxctl_lock); - vfree(buf); goto done; } bus->rxctl = buf + doff; bus->rxctl_orig = buf; bus->rxlen = len - doff; spin_unlock_bh(&bus->rxctl_lock); + buf = NULL; done: /* Awake any waiters */ brcmf_sdio_dcmd_resp_wake(bus); + vfree(buf); } /* Pad read to blocksize for efficiency */ From 068986fd6f2a5f337d3ef18dda7821dc0b80c778 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Wed, 29 Jul 2026 20:47:13 +0800 Subject: [PATCH 1002/1433] wifi: nxpwifi: detach sync command buffer on interrupted wait nxpwifi synchronous commands keep the caller-provided data buffer in cmd_node->data_buf. Several callers pass stack-allocated objects there, for example nxpwifi_get_chan_type() and the timeshare_coex debugfs handlers. If wait_event_interruptible_timeout() is interrupted or times out, the caller can return and release that stack object while the command is still current. nxpwifi_cancel_all_pending_cmd() deliberately keeps the current command because a response may still arrive. A late firmware response can then write through cmd_node->data_buf into the stale stack address. After cancelling pending commands, detach the caller-owned buffer from the still-current command under nxpwifi_cmd_lock. Unlike the host command response path, several command response callbacks do not tolerate a NULL data buffer. Most of them ignore it or check it already, but nxpwifi_ret_sta_get_chan_info(), nxpwifi_ret_sta_hs_wakeup_reason() and nxpwifi_ret_sta_robust_coex() dereference it unconditionally, so let them discard a detached response. No caller passes a NULL buffer to these commands today, so this only affects the newly introduced detached state. nxpwifi was derived from mwifiex before commit ef06882c7d8a ("wifi: mwifiex: Detach sync cmd buffer on interrupted wait") and retains the same lifetime bug. Apply the equivalent buffer detachment here. Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Link: https://patch.msgid.link/20260729124713.2849018-1-lilinmao@kylinos.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/nxp/nxpwifi/sta_cfg.c | 12 ++++++++++++ drivers/net/wireless/nxp/nxpwifi/sta_cmd.c | 10 ++++++++++ 2 files changed, 22 insertions(+) diff --git a/drivers/net/wireless/nxp/nxpwifi/sta_cfg.c b/drivers/net/wireless/nxp/nxpwifi/sta_cfg.c index 502c96dc4016..702fa1531da1 100644 --- a/drivers/net/wireless/nxp/nxpwifi/sta_cfg.c +++ b/drivers/net/wireless/nxp/nxpwifi/sta_cfg.c @@ -45,6 +45,18 @@ int nxpwifi_wait_queue_complete(struct nxpwifi_adapter *adapter, nxpwifi_dbg(adapter, ERROR, "cmd_wait_q terminated: %d\n", status); nxpwifi_cancel_all_pending_cmd(adapter); + + /* + * The response path writes through cmd_node->data_buf. The + * caller can release a stack-allocated data_buf after an + * interrupted wait while a late response is still pending. + * Detach it from the current command before returning. + */ + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + if (adapter->curr_cmd == cmd_queued) + adapter->curr_cmd->data_buf = NULL; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + return status; } diff --git a/drivers/net/wireless/nxp/nxpwifi/sta_cmd.c b/drivers/net/wireless/nxp/nxpwifi/sta_cmd.c index 0b140e84916f..5e8ffd306b31 100644 --- a/drivers/net/wireless/nxp/nxpwifi/sta_cmd.c +++ b/drivers/net/wireless/nxp/nxpwifi/sta_cmd.c @@ -2133,6 +2133,9 @@ nxpwifi_ret_sta_robust_coex(struct nxpwifi_private *priv, u16 action = le16_to_cpu(coex->action); u32 mode; + if (!is_timeshare) + return 0; + coex_tlv = (struct nxpwifi_ie_types_robust_coex *)((u8 *)coex + sizeof(struct host_cmd_ds_robust_coex)); if (action == HOST_ACT_GEN_GET) { @@ -2679,6 +2682,10 @@ nxpwifi_ret_sta_hs_wakeup_reason(struct nxpwifi_private *priv, { struct host_cmd_ds_wakeup_reason *wakeup_reason = (struct host_cmd_ds_wakeup_reason *)data_buf; + + if (!wakeup_reason) + return 0; + wakeup_reason->wakeup_reason = resp->params.hs_wakeup_reason.wakeup_reason; @@ -2771,6 +2778,9 @@ nxpwifi_ret_sta_get_chan_info(struct nxpwifi_private *priv, (struct nxpwifi_channel_band *)data_buf; struct host_cmd_tlv_channel_band *tlv_band_channel; + if (!channel_band) + return 0; + tlv_band_channel = (struct host_cmd_tlv_channel_band *)sta_cfg_cmd->tlv_buffer; memcpy(&channel_band->band_config, &tlv_band_channel->band_config, From ca800a9302764c445de0da0e84d2252400a770ee Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Wed, 29 Jul 2026 16:24:57 +0800 Subject: [PATCH 1003/1433] wifi: nxpwifi: bound uAP association event IEs to the event buffer nxpwifi_uap_event_sta_assoc() exposes the association request IEs that the firmware reports in the uAP association event, which the driver copies into the fixed-size event_body[] buffer. event->len is supplied by firmware and is not validated. A value smaller than the header underflows the subtraction used for assoc_req_ies_len, while a larger value can make the IE range extend beyond event_body[]. Subsequent IE parsing can then read past the adapter object. Validate both bounds before using the firmware-reported length. nxpwifi was derived from mwifiex before commit f0858bfc7d3c ("wifi: mwifiex: bound uAP association event IEs to the event buffer") and retains the same unchecked length. Apply the equivalent bounds check here. Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Reviewed-by: Jeff Chen Link: https://patch.msgid.link/20260729082457.1897303-1-lilinmao@kylinos.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/nxp/nxpwifi/uap_event.c | 22 ++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/uap_event.c b/drivers/net/wireless/nxp/nxpwifi/uap_event.c index ed8e24ae9c0a..9f717a3d7ec5 100644 --- a/drivers/net/wireless/nxp/nxpwifi/uap_event.c +++ b/drivers/net/wireless/nxp/nxpwifi/uap_event.c @@ -107,11 +107,29 @@ nxpwifi_uap_event_sta_assoc(struct nxpwifi_private *priv) len = ETH_ALEN; if (len != -1) { + u16 evt_len = le16_to_cpu(event->len); + sinfo->assoc_req_ies = &event->data[len]; len = (u8 *)sinfo->assoc_req_ies - (u8 *)&event->frame_control; - sinfo->assoc_req_ies_len = - le16_to_cpu(event->len) - (u16)len; + + /* + * event->len is reported by the device firmware + * and is not otherwise validated. Reject a length + * that underflows the header or that would place + * the association request IEs outside the fixed + * event_body[] buffer. + */ + if (evt_len < len || + (u8 *)&event->frame_control + evt_len > + adapter->event_body + MAX_EVENT_SIZE) { + nxpwifi_dbg(adapter, ERROR, + "invalid STA assoc event length\n"); + kfree(sinfo); + return -EINVAL; + } + + sinfo->assoc_req_ies_len = evt_len - (u16)len; } } cfg80211_new_sta(priv->netdev->ieee80211_ptr, event->sta_addr, sinfo, From d26dcf73a8f15d1091ba0d31a3d2380f52bd5d92 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Tue, 4 Aug 2026 20:20:35 +0200 Subject: [PATCH 1004/1433] openvswitch: remove support for legacy tunnel types ovs-vswitchd doesn't use OVS_VPORT_TYPE_GRE/VXLAN/GENEVE with the Linux kernel module since adding support for standard tunnel devices with COLLECT_METADATA back in 2017. The code to use them was only activated as a fallback for old kernels, so not used in practice. And it is now fully removed in the upcoming OVS 4.0 release. Modern way to use tunnels with OVS is to create standard tunnel ports with RTM_NEWLINK + COLLECT_METADATA and add them as OVS_VPORT_TYPE_NETDEV. Device reference management and the netlink options parsing for these legacy port types is complicated and was a CVE magnet in the previous release cycles. Existence of these modules also makes locking analysis for geneve module and other core tunnel devices unnecessarily more complicated, especially in light of migration to per-netns locking. Since there are no actual users for these port types for a very long time, let's just remove the support entirely. There is no practical reason to run OVS from 2017 on a recent kernel. While it's technically a uAPI change in some sense, from the user's perspective this removal looks indistinguishable from the kernel built with CONFIG_OPENVSWITCH_GENEVE/VXLAN/GRE disabled. And it seems like removal of unused drivers/modules is not a rare event these days. A comment is added to the uAPI header noting that standard RTM_NEWLINK with COLLECT_METADATA followed by OVS_VPORT_CMD_NEW with the simple OVS_VPORT_TYPE_NETDEV should be used instead. Modules responsible for these tunnel ports are removed as well as selftests covering this functionality. Further cleanups will follow. Signed-off-by: Ilya Maximets Link: https://patch.msgid.link/20260804182049.2289754-2-i.maximets@ovn.org Signed-off-by: Paolo Abeni --- include/uapi/linux/openvswitch.h | 31 +++- net/openvswitch/Kconfig | 35 ---- net/openvswitch/Makefile | 4 - net/openvswitch/datapath.c | 6 +- net/openvswitch/vport-geneve.c | 143 --------------- net/openvswitch/vport-gre.c | 106 ----------- net/openvswitch/vport-netdev.c | 29 +-- net/openvswitch/vport-netdev.h | 2 - net/openvswitch/vport-vxlan.c | 172 ------------------ tools/testing/selftests/net/config | 3 - .../testing/selftests/net/openvswitch/config | 3 - .../selftests/net/openvswitch/openvswitch.sh | 38 ---- .../selftests/net/openvswitch/ovs-dpctl.py | 91 +++------ 13 files changed, 51 insertions(+), 612 deletions(-) delete mode 100644 net/openvswitch/vport-geneve.c delete mode 100644 net/openvswitch/vport-gre.c delete mode 100644 net/openvswitch/vport-vxlan.c diff --git a/include/uapi/linux/openvswitch.h b/include/uapi/linux/openvswitch.h index aa2acdbda8f8..440825e65837 100644 --- a/include/uapi/linux/openvswitch.h +++ b/include/uapi/linux/openvswitch.h @@ -244,13 +244,33 @@ enum ovs_vport_cmd { OVS_VPORT_CMD_SET }; +/** + * enum ovs_vport_type - OVS vport types for %OVS_VPORT_ATTR_TYPE. + * @OVS_VPORT_TYPE_NETDEV: Existing network device attached as a vport. + * @OVS_VPORT_TYPE_INTERNAL: Network device implemented by the OVS datapath. + * @OVS_VPORT_TYPE_GRE: Legacy GRE tunnel. Not supported, see below. + * @OVS_VPORT_TYPE_VXLAN: Legacy VXLAN tunnel. Not supported, see below. + * @OVS_VPORT_TYPE_GENEVE: Legacy Geneve tunnel. Not supported, see below. + * + * The tunnel vport types are not supported. Instead, create the tunnel device + * using %RTM_NEWLINK with the appropriate %IFLA_INFO_KIND (e.g. ``gre``, + * ``gretap``, ``vxlan``, ``geneve``, or other tunnel types) and add it as + * %OVS_VPORT_TYPE_NETDEV. To match and set tunnel parameters on a per-flow + * basis, the tunnel device should collect metadata. To do that, some tunnel + * types require an explicit flag such as %IFLA_VXLAN_COLLECT_METADATA for + * ``vxlan``, while others such as ``bareudp`` collect metadata + * unconditionally. + */ enum ovs_vport_type { + /* private: */ OVS_VPORT_TYPE_UNSPEC, + /* public: */ OVS_VPORT_TYPE_NETDEV, /* network device */ OVS_VPORT_TYPE_INTERNAL, /* network device implemented by datapath */ - OVS_VPORT_TYPE_GRE, /* GRE tunnel. */ - OVS_VPORT_TYPE_VXLAN, /* VXLAN tunnel. */ - OVS_VPORT_TYPE_GENEVE, /* Geneve tunnel. */ + OVS_VPORT_TYPE_GRE, /* GRE tunnel (legacy, not supported). */ + OVS_VPORT_TYPE_VXLAN, /* VXLAN tunnel (legacy, not supported). */ + OVS_VPORT_TYPE_GENEVE, /* Geneve tunnel (legacy, not supported). */ + /* private: */ __OVS_VPORT_TYPE_MAX }; @@ -284,7 +304,7 @@ enum ovs_vport_type { * %OVS_VPORT_ATTR_NAME attributes are required. %OVS_VPORT_ATTR_PORT_NO is * optional; if not specified a free port number is automatically selected. * Whether %OVS_VPORT_ATTR_OPTIONS is required or optional depends on the type - * of vport. + * of vport. None of currently supported vport types support options. * * For other requests, if %OVS_VPORT_ATTR_NAME is specified then it is used to * look up the vport to operate on; otherwise dp_idx from the &struct @@ -336,7 +356,8 @@ enum { #define OVS_VXLAN_EXT_MAX (__OVS_VXLAN_EXT_MAX - 1) -/* OVS_VPORT_ATTR_OPTIONS attributes for tunnels. +/* OVS_VPORT_ATTR_OPTIONS attributes for legacy tunnel vports. + * Not supported, see the note for enum ovs_vport_type. */ enum { OVS_TUNNEL_ATTR_UNSPEC, diff --git a/net/openvswitch/Kconfig b/net/openvswitch/Kconfig index e6aaee92dba4..19ac9ae18f1e 100644 --- a/net/openvswitch/Kconfig +++ b/net/openvswitch/Kconfig @@ -40,38 +40,3 @@ config OPENVSWITCH called openvswitch. If unsure, say N. - -config OPENVSWITCH_GRE - tristate "Open vSwitch GRE tunneling support" - depends on OPENVSWITCH - depends on NET_IPGRE - default OPENVSWITCH - help - If you say Y here, then the Open vSwitch will be able create GRE - vport. - - Say N to exclude this support and reduce the binary size. - - If unsure, say Y. - -config OPENVSWITCH_VXLAN - tristate "Open vSwitch VXLAN tunneling support" - depends on OPENVSWITCH - depends on VXLAN - default OPENVSWITCH - help - If you say Y here, then the Open vSwitch will be able create vxlan vport. - - Say N to exclude this support and reduce the binary size. - - If unsure, say Y. - -config OPENVSWITCH_GENEVE - tristate "Open vSwitch Geneve tunneling support" - depends on OPENVSWITCH - depends on GENEVE - default OPENVSWITCH - help - If you say Y here, then the Open vSwitch will be able create geneve vport. - - Say N to exclude this support and reduce the binary size. diff --git a/net/openvswitch/Makefile b/net/openvswitch/Makefile index 28982630bef3..46a27ab369f9 100644 --- a/net/openvswitch/Makefile +++ b/net/openvswitch/Makefile @@ -22,8 +22,4 @@ ifneq ($(CONFIG_NF_CONNTRACK),) openvswitch-y += conntrack.o endif -obj-$(CONFIG_OPENVSWITCH_VXLAN)+= vport-vxlan.o -obj-$(CONFIG_OPENVSWITCH_GENEVE)+= vport-geneve.o -obj-$(CONFIG_OPENVSWITCH_GRE) += vport-gre.o - CFLAGS_openvswitch_trace.o = -I$(src) diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c index eaf332b156d7..0506770341af 100644 --- a/net/openvswitch/datapath.c +++ b/net/openvswitch/datapath.c @@ -2210,11 +2210,9 @@ static size_t ovs_vport_cmd_msg_size(void) /* OVS_VPORT_ATTR_UPCALL_PID */ msgsize += nla_total_size(nr_cpu_ids * sizeof(u32)); - /* OVS_VPORT_ATTR_OPTIONS(OVS_TUNNEL_ATTR_DST_PORT + - * OVS_TUNNEL_ATTR_EXTENSION(OVS_VXLAN_EXT_GBP)) + /* There are no vports supporting OVS_VPORT_ATTR_OPTIONS, so it is + * not included in the message size calculation. */ - msgsize += nla_total_size(nla_total_size(sizeof(u16)) + - nla_total_size(nla_total_size(0))); return msgsize; } diff --git a/net/openvswitch/vport-geneve.c b/net/openvswitch/vport-geneve.c deleted file mode 100644 index cb5ea4424ffc..000000000000 --- a/net/openvswitch/vport-geneve.c +++ /dev/null @@ -1,143 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (c) 2014 Nicira, Inc. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "datapath.h" -#include "vport.h" -#include "vport-netdev.h" - -static struct vport_ops ovs_geneve_vport_ops; -/** - * struct geneve_port - Keeps track of open UDP ports - * @dst_port: destination port. - */ -struct geneve_port { - u16 dst_port; -}; - -static inline struct geneve_port *geneve_vport(const struct vport *vport) -{ - return vport_priv(vport); -} - -static int geneve_get_options(const struct vport *vport, - struct sk_buff *skb) -{ - struct geneve_port *geneve_port = geneve_vport(vport); - - if (nla_put_u16(skb, OVS_TUNNEL_ATTR_DST_PORT, geneve_port->dst_port)) - return -EMSGSIZE; - return 0; -} - -static struct vport *geneve_tnl_create(const struct vport_parms *parms) -{ - struct net *net = ovs_dp_get_net(parms->dp); - struct nlattr *options = parms->options; - struct geneve_port *geneve_port; - struct net_device *dev; - struct vport *vport; - struct nlattr *a; - u16 dst_port; - int err; - - if (!options) { - err = -EINVAL; - goto error; - } - - a = nla_find_nested(options, OVS_TUNNEL_ATTR_DST_PORT); - if (a && nla_len(a) == sizeof(u16)) { - dst_port = nla_get_u16(a); - } else { - /* Require destination port from userspace. */ - err = -EINVAL; - goto error; - } - - vport = ovs_vport_alloc(sizeof(struct geneve_port), - &ovs_geneve_vport_ops, parms); - if (IS_ERR(vport)) - return vport; - - geneve_port = geneve_vport(vport); - geneve_port->dst_port = dst_port; - - rtnl_lock(); - dev = geneve_dev_create_fb(net, parms->name, NET_NAME_USER, dst_port); - if (IS_ERR(dev)) { - rtnl_unlock(); - ovs_vport_free(vport); - return ERR_CAST(dev); - } - - err = dev_change_flags(dev, dev->flags | IFF_UP, NULL); - if (err < 0) { - rtnl_delete_link(dev, 0, NULL); - rtnl_unlock(); - ovs_vport_free(vport); - goto error; - } - - vport->dev = dev; - netdev_hold(vport->dev, &vport->dev_tracker, GFP_KERNEL); - - rtnl_unlock(); - return vport; -error: - return ERR_PTR(err); -} - -static struct vport *geneve_create(const struct vport_parms *parms) -{ - struct vport *vport; - - vport = geneve_tnl_create(parms); - if (IS_ERR(vport)) - return vport; - - return ovs_netdev_link(vport, true); -} - -static struct vport_ops ovs_geneve_vport_ops = { - .type = OVS_VPORT_TYPE_GENEVE, - .create = geneve_create, - .destroy = ovs_netdev_tunnel_destroy, - .get_options = geneve_get_options, - .send = dev_queue_xmit, -}; - -static int __init ovs_geneve_tnl_init(void) -{ - return ovs_vport_ops_register(&ovs_geneve_vport_ops); -} - -static void __exit ovs_geneve_tnl_exit(void) -{ - ovs_vport_ops_unregister(&ovs_geneve_vport_ops); -} - -module_init(ovs_geneve_tnl_init); -module_exit(ovs_geneve_tnl_exit); - -MODULE_DESCRIPTION("OVS: Geneve switching port"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("vport-type-5"); diff --git a/net/openvswitch/vport-gre.c b/net/openvswitch/vport-gre.c deleted file mode 100644 index 6cb5a697b396..000000000000 --- a/net/openvswitch/vport-gre.c +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (c) 2007-2014 Nicira, Inc. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "datapath.h" -#include "vport.h" -#include "vport-netdev.h" - -static struct vport_ops ovs_gre_vport_ops; - -static struct vport *gre_tnl_create(const struct vport_parms *parms) -{ - struct net *net = ovs_dp_get_net(parms->dp); - struct net_device *dev; - struct vport *vport; - int err; - - vport = ovs_vport_alloc(0, &ovs_gre_vport_ops, parms); - if (IS_ERR(vport)) - return vport; - - rtnl_lock(); - dev = gretap_fb_dev_create(net, parms->name, NET_NAME_USER); - if (IS_ERR(dev)) { - rtnl_unlock(); - ovs_vport_free(vport); - return ERR_CAST(dev); - } - - err = dev_change_flags(dev, dev->flags | IFF_UP, NULL); - if (err < 0) { - rtnl_delete_link(dev, 0, NULL); - rtnl_unlock(); - ovs_vport_free(vport); - return ERR_PTR(err); - } - - vport->dev = dev; - netdev_hold(vport->dev, &vport->dev_tracker, GFP_KERNEL); - - rtnl_unlock(); - return vport; -} - -static struct vport *gre_create(const struct vport_parms *parms) -{ - struct vport *vport; - - vport = gre_tnl_create(parms); - if (IS_ERR(vport)) - return vport; - - return ovs_netdev_link(vport, true); -} - -static struct vport_ops ovs_gre_vport_ops = { - .type = OVS_VPORT_TYPE_GRE, - .create = gre_create, - .send = dev_queue_xmit, - .destroy = ovs_netdev_tunnel_destroy, -}; - -static int __init ovs_gre_tnl_init(void) -{ - return ovs_vport_ops_register(&ovs_gre_vport_ops); -} - -static void __exit ovs_gre_tnl_exit(void) -{ - ovs_vport_ops_unregister(&ovs_gre_vport_ops); -} - -module_init(ovs_gre_tnl_init); -module_exit(ovs_gre_tnl_exit); - -MODULE_DESCRIPTION("OVS: GRE switching port"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("vport-type-3"); diff --git a/net/openvswitch/vport-netdev.c b/net/openvswitch/vport-netdev.c index e7e8490a53d8..44808cd0fcff 100644 --- a/net/openvswitch/vport-netdev.c +++ b/net/openvswitch/vport-netdev.c @@ -73,7 +73,7 @@ static struct net_device *get_dpdev(const struct datapath *dp) return local->dev; } -struct vport *ovs_netdev_link(struct vport *vport, bool tunnel) +static struct vport *ovs_netdev_link(struct vport *vport) { int err; @@ -112,15 +112,12 @@ struct vport *ovs_netdev_link(struct vport *vport, bool tunnel) error_master_upper_dev_unlink: netdev_upper_dev_unlink(vport->dev, get_dpdev(vport->dp)); error_put_unlock: - if (tunnel && vport->dev->reg_state == NETREG_REGISTERED) - rtnl_delete_link(vport->dev, 0, NULL); netdev_put(vport->dev, &vport->dev_tracker); rtnl_unlock(); error_free_vport: ovs_vport_free(vport); return ERR_PTR(err); } -EXPORT_SYMBOL_GPL(ovs_netdev_link); static struct vport *netdev_create(const struct vport_parms *parms) { @@ -152,7 +149,7 @@ static struct vport *netdev_create(const struct vport_parms *parms) goto error_put; } - return ovs_netdev_link(vport, false); + return ovs_netdev_link(vport); error_put: netdev_put(vport->dev, &vport->dev_tracker); error_free_vport: @@ -204,28 +201,6 @@ static void netdev_destroy(struct vport *vport) call_rcu(&vport->rcu, vport_netdev_free); } -void ovs_netdev_tunnel_destroy(struct vport *vport) -{ - rtnl_lock(); - if (netif_is_ovs_port(vport->dev)) - ovs_netdev_detach_dev(vport); - - /* We can be invoked by both explicit vport deletion and - * underlying netdev deregistration; delete the link only - * if it's not already shutting down. - */ - if (vport->dev->reg_state == NETREG_REGISTERED) - rtnl_delete_link(vport->dev, 0, NULL); - - /* We can't put the device reference yet, since it can still be in - * use, but rtnl_unlock()->netdev_run_todo() will block until all - * the references are released, so the RCU call must be before it. - */ - call_rcu(&vport->rcu, vport_netdev_free); - rtnl_unlock(); -} -EXPORT_SYMBOL_GPL(ovs_netdev_tunnel_destroy); - /* Returns null if this device is not attached to a datapath. */ struct vport *ovs_netdev_get_vport(struct net_device *dev) { diff --git a/net/openvswitch/vport-netdev.h b/net/openvswitch/vport-netdev.h index 6c0d7366f986..880506f8317e 100644 --- a/net/openvswitch/vport-netdev.h +++ b/net/openvswitch/vport-netdev.h @@ -13,11 +13,9 @@ struct vport *ovs_netdev_get_vport(struct net_device *dev); -struct vport *ovs_netdev_link(struct vport *vport, bool tunnel); void ovs_netdev_detach_dev(struct vport *); int __init ovs_netdev_init(void); void ovs_netdev_exit(void); -void ovs_netdev_tunnel_destroy(struct vport *vport); #endif /* vport_netdev.h */ diff --git a/net/openvswitch/vport-vxlan.c b/net/openvswitch/vport-vxlan.c deleted file mode 100644 index c1b37b50d29e..000000000000 --- a/net/openvswitch/vport-vxlan.c +++ /dev/null @@ -1,172 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (c) 2014 Nicira, Inc. - * Copyright (c) 2013 Cisco Systems, Inc. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "datapath.h" -#include "vport.h" -#include "vport-netdev.h" - -static struct vport_ops ovs_vxlan_netdev_vport_ops; - -static int vxlan_get_options(const struct vport *vport, struct sk_buff *skb) -{ - struct vxlan_dev *vxlan = netdev_priv(vport->dev); - __be16 dst_port = vxlan->cfg.dst_port; - - if (nla_put_u16(skb, OVS_TUNNEL_ATTR_DST_PORT, ntohs(dst_port))) - return -EMSGSIZE; - - if (vxlan->cfg.flags & VXLAN_F_GBP) { - struct nlattr *exts; - - exts = nla_nest_start_noflag(skb, OVS_TUNNEL_ATTR_EXTENSION); - if (!exts) - return -EMSGSIZE; - - if (vxlan->cfg.flags & VXLAN_F_GBP && - nla_put_flag(skb, OVS_VXLAN_EXT_GBP)) - return -EMSGSIZE; - - nla_nest_end(skb, exts); - } - - return 0; -} - -static const struct nla_policy exts_policy[OVS_VXLAN_EXT_MAX + 1] = { - [OVS_VXLAN_EXT_GBP] = { .type = NLA_FLAG, }, -}; - -static int vxlan_configure_exts(struct vport *vport, struct nlattr *attr, - struct vxlan_config *conf) -{ - struct nlattr *exts[OVS_VXLAN_EXT_MAX + 1]; - int err; - - if (nla_len(attr) < sizeof(struct nlattr)) - return -EINVAL; - - err = nla_parse_nested_deprecated(exts, OVS_VXLAN_EXT_MAX, attr, - exts_policy, NULL); - if (err < 0) - return err; - - if (exts[OVS_VXLAN_EXT_GBP]) - conf->flags |= VXLAN_F_GBP; - - return 0; -} - -static struct vport *vxlan_tnl_create(const struct vport_parms *parms) -{ - struct net *net = ovs_dp_get_net(parms->dp); - struct nlattr *options = parms->options; - struct net_device *dev; - struct vport *vport; - struct nlattr *a; - int err; - struct vxlan_config conf = { - .no_share = true, - .flags = VXLAN_F_COLLECT_METADATA | VXLAN_F_UDP_ZERO_CSUM6_RX, - /* Don't restrict the packets that can be sent by MTU */ - .mtu = IP_MAX_MTU, - }; - - if (!options) { - err = -EINVAL; - goto error; - } - - a = nla_find_nested(options, OVS_TUNNEL_ATTR_DST_PORT); - if (a && nla_len(a) == sizeof(u16)) { - conf.dst_port = htons(nla_get_u16(a)); - } else { - /* Require destination port from userspace. */ - err = -EINVAL; - goto error; - } - - vport = ovs_vport_alloc(0, &ovs_vxlan_netdev_vport_ops, parms); - if (IS_ERR(vport)) - return vport; - - a = nla_find_nested(options, OVS_TUNNEL_ATTR_EXTENSION); - if (a) { - err = vxlan_configure_exts(vport, a, &conf); - if (err) { - ovs_vport_free(vport); - goto error; - } - } - - rtnl_lock(); - dev = vxlan_dev_create(net, parms->name, NET_NAME_USER, &conf); - if (IS_ERR(dev)) { - rtnl_unlock(); - ovs_vport_free(vport); - return ERR_CAST(dev); - } - - err = dev_change_flags(dev, dev->flags | IFF_UP, NULL); - if (err < 0) { - rtnl_delete_link(dev, 0, NULL); - rtnl_unlock(); - ovs_vport_free(vport); - goto error; - } - - vport->dev = dev; - netdev_hold(vport->dev, &vport->dev_tracker, GFP_KERNEL); - - rtnl_unlock(); - return vport; -error: - return ERR_PTR(err); -} - -static struct vport *vxlan_create(const struct vport_parms *parms) -{ - struct vport *vport; - - vport = vxlan_tnl_create(parms); - if (IS_ERR(vport)) - return vport; - - return ovs_netdev_link(vport, true); -} - -static struct vport_ops ovs_vxlan_netdev_vport_ops = { - .type = OVS_VPORT_TYPE_VXLAN, - .create = vxlan_create, - .destroy = ovs_netdev_tunnel_destroy, - .get_options = vxlan_get_options, - .send = dev_queue_xmit, -}; - -static int __init ovs_vxlan_tnl_init(void) -{ - return ovs_vport_ops_register(&ovs_vxlan_netdev_vport_ops); -} - -static void __exit ovs_vxlan_tnl_exit(void) -{ - ovs_vport_ops_unregister(&ovs_vxlan_netdev_vport_ops); -} - -module_init(ovs_vxlan_tnl_init); -module_exit(ovs_vxlan_tnl_exit); - -MODULE_DESCRIPTION("OVS: VXLAN switching port"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("vport-type-4"); diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config index a2d14ec9df1a..d83e11298b01 100644 --- a/tools/testing/selftests/net/config +++ b/tools/testing/selftests/net/config @@ -117,9 +117,6 @@ CONFIG_NFT_COMPAT=m CONFIG_NFT_NAT=m CONFIG_NUMA=y CONFIG_OPENVSWITCH=m -CONFIG_OPENVSWITCH_GENEVE=m -CONFIG_OPENVSWITCH_GRE=m -CONFIG_OPENVSWITCH_VXLAN=m CONFIG_PAGE_POOL_STATS=y CONFIG_PROC_SYSCTL=y CONFIG_PSAMPLE=m diff --git a/tools/testing/selftests/net/openvswitch/config b/tools/testing/selftests/net/openvswitch/config index c659749cd086..05ca6affb510 100644 --- a/tools/testing/selftests/net/openvswitch/config +++ b/tools/testing/selftests/net/openvswitch/config @@ -7,9 +7,6 @@ CONFIG_NET_IPGRE_DEMUX=m CONFIG_NF_CONNTRACK=m CONFIG_NF_CONNTRACK_OVS=y CONFIG_OPENVSWITCH=m -CONFIG_OPENVSWITCH_GENEVE=m -CONFIG_OPENVSWITCH_GRE=m -CONFIG_OPENVSWITCH_VXLAN=m CONFIG_PSAMPLE=m CONFIG_VETH=y CONFIG_VLAN_8021Q=y diff --git a/tools/testing/selftests/net/openvswitch/openvswitch.sh b/tools/testing/selftests/net/openvswitch/openvswitch.sh index 853dbc1b00d7..f63001dc2510 100755 --- a/tools/testing/selftests/net/openvswitch/openvswitch.sh +++ b/tools/testing/selftests/net/openvswitch/openvswitch.sh @@ -26,7 +26,6 @@ tests=" netlink_checks ovsnl: validate netlink attrs and settings upcall_interfaces ovs: test the upcall interfaces tunnel_metadata ovs: test extraction of tunnel metadata - tunnel_refcount ovs: test tunnel vport reference cleanup drop_reason drop: test drop reasons are emitted pop_vlan vlan: POP_VLAN action strips tag dec_ttl ttl: dec_ttl decrements IP TTL @@ -1210,43 +1209,6 @@ test_tunnel_metadata() { return 0 } -test_tunnel_refcount() { - sbxname="test_tunnel_refcount" - sbx_add "${sbxname}" || return 1 - - ovs_sbx "${sbxname}" ip netns add trefns || return 1 - on_exit "ovs_sbx ${sbxname} ip netns del trefns" - - for tun_type in gre vxlan geneve; do - info "testing ${tun_type} tunnel vport refcount" - - ovs_sbx "${sbxname}" ip netns exec trefns \ - python3 $ovs_base/ovs-dpctl.py \ - add-dp dp-${tun_type} || return 1 - - ovs_sbx "${sbxname}" ip netns exec trefns \ - python3 $ovs_base/ovs-dpctl.py \ - add-if --no-lwt -t ${tun_type} \ - dp-${tun_type} ovs-${tun_type}0 || return 1 - - ovs_wait ip -netns trefns link show \ - ovs-${tun_type}0 >/dev/null 2>&1 || return 1 - - info "deleting dp - may hang if reference counting is broken" - ovs_sbx "${sbxname}" ip netns exec trefns \ - python3 $ovs_base/ovs-dpctl.py \ - del-dp dp-${tun_type} & - - dev_removed() { - ! ip -netns trefns link show "$1" >/dev/null 2>&1 - } - ovs_wait dev_removed dp-${tun_type} || return 1 - ovs_wait dev_removed ovs-${tun_type}0 || return 1 - done - - return 0 -} - test_pop_vlan() { local sbx="test_pop_vlan" sbx_add "$sbx" || return $? diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py index f3edd198223f..3ece07d47281 100644 --- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py +++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py @@ -2364,9 +2364,6 @@ class OvsDatapath(GenericNetlinkSocket): class OvsVport(GenericNetlinkSocket): OVS_VPORT_TYPE_NETDEV = 1 OVS_VPORT_TYPE_INTERNAL = 2 - OVS_VPORT_TYPE_GRE = 3 - OVS_VPORT_TYPE_VXLAN = 4 - OVS_VPORT_TYPE_GENEVE = 5 class ovs_vport_msg(ovs_dp_msg): nla_map = ( @@ -2374,7 +2371,7 @@ class OvsVport(GenericNetlinkSocket): ("OVS_VPORT_ATTR_PORT_NO", "uint32"), ("OVS_VPORT_ATTR_TYPE", "uint32"), ("OVS_VPORT_ATTR_NAME", "asciiz"), - ("OVS_VPORT_ATTR_OPTIONS", "vportopts"), + ("OVS_VPORT_ATTR_OPTIONS", "none"), ("OVS_VPORT_ATTR_UPCALL_PID", "array(uint32)"), ("OVS_VPORT_ATTR_STATS", "vportstats"), ("OVS_VPORT_ATTR_PAD", "none"), @@ -2382,13 +2379,6 @@ class OvsVport(GenericNetlinkSocket): ("OVS_VPORT_ATTR_NETNSID", "uint32"), ) - class vportopts(nla): - nla_map = ( - ("OVS_TUNNEL_ATTR_UNSPEC", "none"), - ("OVS_TUNNEL_ATTR_DST_PORT", "uint16"), - ("OVS_TUNNEL_ATTR_EXTENSION", "none"), - ) - class vportstats(nla): fields = ( ("rx_packets", "=Q"), @@ -2406,25 +2396,13 @@ class OvsVport(GenericNetlinkSocket): return "netdev" elif vport_type == OvsVport.OVS_VPORT_TYPE_INTERNAL: return "internal" - elif vport_type == OvsVport.OVS_VPORT_TYPE_GRE: - return "gre" - elif vport_type == OvsVport.OVS_VPORT_TYPE_VXLAN: - return "vxlan" - elif vport_type == OvsVport.OVS_VPORT_TYPE_GENEVE: - return "geneve" raise ValueError("Unknown vport type:%d" % vport_type) def str_to_type(vport_type): - if vport_type == "netdev": + if vport_type in ["netdev", "gre", "vxlan", "geneve"]: return OvsVport.OVS_VPORT_TYPE_NETDEV elif vport_type == "internal": return OvsVport.OVS_VPORT_TYPE_INTERNAL - elif vport_type == "gre": - return OvsVport.OVS_VPORT_TYPE_GRE - elif vport_type == "vxlan": - return OvsVport.OVS_VPORT_TYPE_VXLAN - elif vport_type == "geneve": - return OvsVport.OVS_VPORT_TYPE_GENEVE raise ValueError("Unknown vport type: '%s'" % vport_type) def __init__(self, packet=OvsPacket()): @@ -2457,16 +2435,18 @@ class OvsVport(GenericNetlinkSocket): raise ne return reply - def attach(self, dpindex, vport_ifname, ptype, dport, lwt): + def attach(self, dpindex, vport_ifname, ptype, dport): msg = OvsVport.ovs_vport_msg() msg["cmd"] = OVS_VPORT_CMD_NEW msg["version"] = OVS_DATAPATH_VERSION msg["reserved"] = 0 msg["dpifindex"] = dpindex - port_type = OvsVport.str_to_type(ptype) msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname]) + msg["attrs"].append( + ["OVS_VPORT_ATTR_TYPE", OvsVport.str_to_type(ptype)] + ) msg["attrs"].append( ["OVS_VPORT_ATTR_UPCALL_PID", [self.upcall_packet.epid]] ) @@ -2480,36 +2460,21 @@ class OvsVport(GenericNetlinkSocket): if not dport: dport = tnl[1] - if not lwt: - if tnl[0] == "gre": - # GRE tunnels have no options. - break + ipr = pyroute2.iproute.IPRoute() - vportopt = OvsVport.ovs_vport_msg.vportopts() - vportopt["attrs"].append( - ["OVS_TUNNEL_ATTR_DST_PORT", dport] - ) - msg["attrs"].append( - ["OVS_VPORT_ATTR_OPTIONS", vportopt] - ) - else: - port_type = OvsVport.OVS_VPORT_TYPE_NETDEV - ipr = pyroute2.iproute.IPRoute() - - if tnl[0] == "geneve": - ipr.link("add", ifname=vport_ifname, kind=tnl[0], - geneve_port=dport, - geneve_collect_metadata=True, - geneve_udp_zero_csum6_rx=1) - elif tnl[0] == "gre": - ipr.link("add", ifname=vport_ifname, kind="gretap", - gre_collect_metadata=True) - elif tnl[0] == "vxlan": - ipr.link("add", ifname=vport_ifname, kind=tnl[0], - vxlan_learning=0, vxlan_collect_metadata=1, - vxlan_udp_zero_csum6_rx=1, vxlan_port=dport) + if tnl[0] == "geneve": + ipr.link("add", ifname=vport_ifname, kind=tnl[0], + geneve_port=dport, + geneve_collect_metadata=True, + geneve_udp_zero_csum6_rx=1) + elif tnl[0] == "gre": + ipr.link("add", ifname=vport_ifname, kind="gretap", + gre_collect_metadata=True) + elif tnl[0] == "vxlan": + ipr.link("add", ifname=vport_ifname, kind=tnl[0], + vxlan_learning=0, vxlan_collect_metadata=1, + vxlan_udp_zero_csum6_rx=1, vxlan_port=dport) break - msg["attrs"].append(["OVS_VPORT_ATTR_TYPE", port_type]) try: reply = self.nlm_request( @@ -2937,19 +2902,12 @@ def print_ovsdp_full(dp_lookup_rep, ifindex, ndb=NDB(), vpl=OvsVport()): for iface in ndb.interfaces: rep = vpl.info(iface.ifname, ifindex) if rep is not None: - opts = "" - vpo = rep.get_attr("OVS_VPORT_ATTR_OPTIONS") - if vpo: - dpo = vpo.get_attr("OVS_TUNNEL_ATTR_DST_PORT") - if dpo: - opts += " tnl-dport:%s" % dpo print( - " port %d: %s (%s%s)" + " port %d: %s (%s)" % ( rep.get_attr("OVS_VPORT_ATTR_PORT_NO"), rep.get_attr("OVS_VPORT_ATTR_NAME"), OvsVport.type_to_str(rep.get_attr("OVS_VPORT_ATTR_TYPE")), - opts, ) ) @@ -3022,13 +2980,6 @@ def main(argv): default=0, help="Destination port (0 for default)" ) - addifcmd.add_argument( - "-l", - "--lwt", - action=argparse.BooleanOptionalAction, - default=True, - help="Use LWT infrastructure instead of vport (default true)." - ) delifcmd = subparsers.add_parser("del-if") delifcmd.add_argument("dpname", help="Datapath Name") delifcmd.add_argument("delif", help="Interface name for adding") @@ -3108,7 +3059,7 @@ def main(argv): return 1 dpindex = rep["dpifindex"] rep = ovsvp.attach(rep["dpifindex"], args.addif, args.ptype, - args.dport, args.lwt) + args.dport) msg = "vport '%s'" % args.addif if rep and rep["header"]["error"] is None: msg += " added." From 38aecd1a1973806524b183e21cedf52a088607f2 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Tue, 4 Aug 2026 20:20:36 +0200 Subject: [PATCH 1005/1433] openvswitch: vport: remove infrastructure for vport options Since removal of tunnel vport types, there aren't any vports that support options. Let's remove the options-related infrastructure. Can be reinstated if we ever need a new vport type or if we need extra options for the existing ones. The uAPI attribute remains. Clarification comment is added to highlight that none of the supported vports support options at the moment. If the options are provided, the code now directly replies with -EOPNOTSUPP to keep the behavior the same for remaining vport types. Note: It is technically possible that someone has an out-of-tree module named vport-type-N that implements a different vport type and they have options for this vport type. However, our message size calculations do not account for whatever options such a port would have and so it is dangerous to load such a module without modifying the code in the main datapath.c, unless the options are smaller than the ones we had for vxlan. A more robust solution would be to have a different version of the entire openvswitch module instead, so the use case of a separate vport-type-N loaded with the upstream openvswitch module is unlikely. At this time we're not aware of anyone doing that. Signed-off-by: Ilya Maximets Link: https://patch.msgid.link/20260804182049.2289754-3-i.maximets@ovn.org Signed-off-by: Paolo Abeni --- net/openvswitch/datapath.c | 20 +++----------- net/openvswitch/vport.c | 54 -------------------------------------- net/openvswitch/vport.h | 14 ---------- 3 files changed, 4 insertions(+), 84 deletions(-) diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c index 0506770341af..57c83f05fead 100644 --- a/net/openvswitch/datapath.c +++ b/net/openvswitch/datapath.c @@ -1856,7 +1856,6 @@ static int ovs_dp_cmd_new(struct sk_buff *skb, struct genl_info *info) /* Set up our datapath device. */ parms.name = nla_data(a[OVS_DP_ATTR_NAME]); parms.type = OVS_VPORT_TYPE_INTERNAL; - parms.options = NULL; parms.dp = dp; parms.port_no = OVSP_LOCAL; parms.upcall_portids = a[OVS_DP_ATTR_UPCALL_PID]; @@ -2172,10 +2171,6 @@ static int ovs_vport_cmd_fill_info(struct vport *vport, struct sk_buff *skb, if (ovs_vport_get_upcall_portids(vport, skb)) goto nla_put_failure; - err = ovs_vport_get_options(vport, skb); - if (err == -EMSGSIZE) - goto error; - genlmsg_end(skb, ovs_header); return 0; @@ -2183,7 +2178,6 @@ static int ovs_vport_cmd_fill_info(struct vport *vport, struct sk_buff *skb, rcu_read_unlock(); nla_put_failure: err = -EMSGSIZE; -error: genlmsg_cancel(skb, ovs_header); return err; } @@ -2210,10 +2204,6 @@ static size_t ovs_vport_cmd_msg_size(void) /* OVS_VPORT_ATTR_UPCALL_PID */ msgsize += nla_total_size(nr_cpu_ids * sizeof(u32)); - /* There are no vports supporting OVS_VPORT_ATTR_OPTIONS, so it is - * not included in the message size calculation. - */ - return msgsize; } @@ -2365,7 +2355,6 @@ static int ovs_vport_cmd_new(struct sk_buff *skb, struct genl_info *info) } parms.name = nla_data(a[OVS_VPORT_ATTR_NAME]); - parms.options = a[OVS_VPORT_ATTR_OPTIONS]; parms.dp = dp; parms.port_no = port_no; parms.upcall_portids = a[OVS_VPORT_ATTR_UPCALL_PID]; @@ -2427,12 +2416,11 @@ static int ovs_vport_cmd_set(struct sk_buff *skb, struct genl_info *info) } if (a[OVS_VPORT_ATTR_OPTIONS]) { - err = ovs_vport_set_options(vport, a[OVS_VPORT_ATTR_OPTIONS]); - if (err) - goto exit_unlock_free; + /* There are no vport types that support legacy options. */ + err = -EOPNOTSUPP; + goto exit_unlock_free; } - if (a[OVS_VPORT_ATTR_UPCALL_PID]) { struct nlattr *ids = a[OVS_VPORT_ATTR_UPCALL_PID]; @@ -2606,7 +2594,7 @@ static const struct nla_policy vport_policy[OVS_VPORT_ATTR_MAX + 1] = { [OVS_VPORT_ATTR_PORT_NO] = { .type = NLA_U32 }, [OVS_VPORT_ATTR_TYPE] = { .type = NLA_U32 }, [OVS_VPORT_ATTR_UPCALL_PID] = { .type = NLA_UNSPEC }, - [OVS_VPORT_ATTR_OPTIONS] = { .type = NLA_NESTED }, + [OVS_VPORT_ATTR_OPTIONS] = { .type = NLA_NESTED }, /* Unused. */ [OVS_VPORT_ATTR_IFINDEX] = NLA_POLICY_MIN(NLA_S32, 0), [OVS_VPORT_ATTR_NETNSID] = { .type = NLA_S32 }, [OVS_VPORT_ATTR_UPCALL_STATS] = { .type = NLA_NESTED }, diff --git a/net/openvswitch/vport.c b/net/openvswitch/vport.c index 12741485c939..ada316a61726 100644 --- a/net/openvswitch/vport.c +++ b/net/openvswitch/vport.c @@ -239,22 +239,6 @@ struct vport *ovs_vport_add(const struct vport_parms *parms) return ERR_PTR(-EAGAIN); } -/** - * ovs_vport_set_options - modify existing vport device (for kernel callers) - * - * @vport: vport to modify. - * @options: New configuration. - * - * Modifies an existing device with the specified configuration (which is - * dependent on device type). ovs_mutex must be held. - */ -int ovs_vport_set_options(struct vport *vport, struct nlattr *options) -{ - if (!vport->ops->set_options) - return -EOPNOTSUPP; - return vport->ops->set_options(vport, options); -} - /** * ovs_vport_del - delete existing vport device * @@ -348,44 +332,6 @@ int ovs_vport_get_upcall_stats(struct vport *vport, struct sk_buff *skb) return 0; } -/** - * ovs_vport_get_options - retrieve device options - * - * @vport: vport from which to retrieve the options. - * @skb: sk_buff where options should be appended. - * - * Retrieves the configuration of the given device, appending an - * %OVS_VPORT_ATTR_OPTIONS attribute that in turn contains nested - * vport-specific attributes to @skb. - * - * Returns 0 if successful, -EMSGSIZE if @skb has insufficient room, or another - * negative error code if a real error occurred. If an error occurs, @skb is - * left unmodified. - * - * Must be called with ovs_mutex or rcu_read_lock. - */ -int ovs_vport_get_options(const struct vport *vport, struct sk_buff *skb) -{ - struct nlattr *nla; - int err; - - if (!vport->ops->get_options) - return 0; - - nla = nla_nest_start_noflag(skb, OVS_VPORT_ATTR_OPTIONS); - if (!nla) - return -EMSGSIZE; - - err = vport->ops->get_options(vport, skb); - if (err) { - nla_nest_cancel(skb, nla); - return err; - } - - nla_nest_end(skb, nla); - return 0; -} - /** * ovs_vport_set_upcall_portids - set upcall portids of @vport. * diff --git a/net/openvswitch/vport.h b/net/openvswitch/vport.h index 9f67b9dd49f9..636788b59907 100644 --- a/net/openvswitch/vport.h +++ b/net/openvswitch/vport.h @@ -34,9 +34,6 @@ void ovs_vport_get_stats(struct vport *, struct ovs_vport_stats *); int ovs_vport_get_upcall_stats(struct vport *vport, struct sk_buff *skb); -int ovs_vport_set_options(struct vport *, struct nlattr *options); -int ovs_vport_get_options(const struct vport *, struct sk_buff *); - int ovs_vport_set_upcall_portids(struct vport *, const struct nlattr *pids); int ovs_vport_get_upcall_portids(const struct vport *, struct sk_buff *); u32 ovs_vport_find_upcall_portid(const struct vport *, struct sk_buff *); @@ -92,8 +89,6 @@ struct vport { * * @name: New vport's name. * @type: New vport's type. - * @options: %OVS_VPORT_ATTR_OPTIONS attribute from Netlink message, %NULL if - * none was supplied. * @desired_ifindex: New vport's ifindex. * @dp: New vport's datapath. * @port_no: New vport's port number. @@ -104,7 +99,6 @@ struct vport_parms { const char *name; enum ovs_vport_type type; int desired_ifindex; - struct nlattr *options; /* For ovs_vport_alloc(). */ struct datapath *dp; @@ -120,11 +114,6 @@ struct vport_parms { * a new vport allocated with ovs_vport_alloc(), otherwise an ERR_PTR() value. * @destroy: Destroys a vport. Must call vport_free() on the vport but not * before an RCU grace period has elapsed. - * @set_options: Modify the configuration of an existing vport. May be %NULL - * if modification is not supported. - * @get_options: Appends vport-specific attributes for the configuration of an - * existing vport to a &struct sk_buff. May be %NULL for a vport that does not - * have any configuration. * @send: Send a packet on the device. * zero for dropped packets or negative for error. * @owner: Module that implements this vport type. @@ -137,9 +126,6 @@ struct vport_ops { struct vport *(*create)(const struct vport_parms *); void (*destroy)(struct vport *); - int (*set_options)(struct vport *, struct nlattr *); - int (*get_options)(const struct vport *, struct sk_buff *); - int (*send)(struct sk_buff *skb); struct module *owner; struct list_head list; From 2425eba0f8225fa808e2c2f0d5bc5f492c88ef88 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Tue, 4 Aug 2026 20:20:37 +0200 Subject: [PATCH 1006/1433] openvswitch: vport: remove infrastructure for separate modules Since removal of legacy tunnel vport types only the built-in ones remain. So, there is no need for the extra infrastructure for dynamic module loading. Can be reinstated in the future if we need a new vport type. Note: It is technically possible that someone has an out-of-tree module named vport-type-N that implements a different vport type. At this time we're not aware of anyone doing that. People running out-of-tree modules normally just have an out-of-tree openvswitch module as a whole. And there are actually no supported out-of-tree implementations of the openvswitch module known to the community. Signed-off-by: Ilya Maximets Link: https://patch.msgid.link/20260804182049.2289754-4-i.maximets@ovn.org Signed-off-by: Paolo Abeni --- net/openvswitch/datapath.c | 6 +----- net/openvswitch/vport.c | 25 +++---------------------- net/openvswitch/vport.h | 9 +-------- 3 files changed, 5 insertions(+), 35 deletions(-) diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c index 57c83f05fead..34a15ef76a70 100644 --- a/net/openvswitch/datapath.c +++ b/net/openvswitch/datapath.c @@ -2331,7 +2331,6 @@ static int ovs_vport_cmd_new(struct sk_buff *skb, struct genl_info *info) return -ENOMEM; ovs_lock(); -restart: dp = get_dp(sock_net(skb->sk), ovs_header->dp_ifindex); err = -ENODEV; if (!dp) @@ -2363,11 +2362,8 @@ static int ovs_vport_cmd_new(struct sk_buff *skb, struct genl_info *info) vport = new_vport(&parms); err = PTR_ERR(vport); - if (IS_ERR(vport)) { - if (err == -EAGAIN) - goto restart; + if (IS_ERR(vport)) goto exit_unlock_free; - } err = ovs_vport_cmd_fill_info(vport, reply, genl_info_net(info), info->snd_portid, info->snd_seq, 0, diff --git a/net/openvswitch/vport.c b/net/openvswitch/vport.c index ada316a61726..29ebeb164bdc 100644 --- a/net/openvswitch/vport.c +++ b/net/openvswitch/vport.c @@ -57,7 +57,7 @@ static struct hlist_head *hash_bucket(const struct net *net, const char *name) return &dev_table[hash & (VPORT_HASH_BUCKETS - 1)]; } -int __ovs_vport_ops_register(struct vport_ops *ops) +int ovs_vport_ops_register(struct vport_ops *ops) { int err = -EEXIST; struct vport_ops *o; @@ -73,7 +73,6 @@ int __ovs_vport_ops_register(struct vport_ops *ops) ovs_unlock(); return err; } -EXPORT_SYMBOL_GPL(__ovs_vport_ops_register); void ovs_vport_ops_unregister(struct vport_ops *ops) { @@ -81,7 +80,6 @@ void ovs_vport_ops_unregister(struct vport_ops *ops) list_del(&ops->list); ovs_unlock(); } -EXPORT_SYMBOL_GPL(ovs_vport_ops_unregister); /** * ovs_vport_locate - find a port that has already been created @@ -210,14 +208,9 @@ struct vport *ovs_vport_add(const struct vport_parms *parms) if (ops) { struct hlist_head *bucket; - if (!try_module_get(ops->owner)) - return ERR_PTR(-EAFNOSUPPORT); - vport = ops->create(parms); - if (IS_ERR(vport)) { - module_put(ops->owner); + if (IS_ERR(vport)) return vport; - } bucket = hash_bucket(ovs_dp_get_net(vport->dp), ovs_vport_name(vport)); @@ -225,18 +218,7 @@ struct vport *ovs_vport_add(const struct vport_parms *parms) return vport; } - /* Unlock to attempt module load and return -EAGAIN if load - * was successful as we need to restart the port addition - * workflow. - */ - ovs_unlock(); - request_module("vport-type-%d", parms->type); - ovs_lock(); - - if (!ovs_vport_lookup(parms)) - return ERR_PTR(-EAFNOSUPPORT); - else - return ERR_PTR(-EAGAIN); + return ERR_PTR(-EAFNOSUPPORT); } /** @@ -250,7 +232,6 @@ struct vport *ovs_vport_add(const struct vport_parms *parms) void ovs_vport_del(struct vport *vport) { hlist_del_rcu(&vport->hash_node); - module_put(vport->ops->owner); vport->ops->destroy(vport); } diff --git a/net/openvswitch/vport.h b/net/openvswitch/vport.h index 636788b59907..930f1ccc8558 100644 --- a/net/openvswitch/vport.h +++ b/net/openvswitch/vport.h @@ -116,7 +116,6 @@ struct vport_parms { * before an RCU grace period has elapsed. * @send: Send a packet on the device. * zero for dropped packets or negative for error. - * @owner: Module that implements this vport type. * @list: List entry in the global list of vport types. */ struct vport_ops { @@ -127,7 +126,6 @@ struct vport_ops { void (*destroy)(struct vport *); int (*send)(struct sk_buff *skb); - struct module *owner; struct list_head list; }; @@ -191,12 +189,7 @@ static inline const char *ovs_vport_name(struct vport *vport) return vport->dev->name; } -int __ovs_vport_ops_register(struct vport_ops *ops); -#define ovs_vport_ops_register(ops) \ - ({ \ - (ops)->owner = THIS_MODULE; \ - __ovs_vport_ops_register(ops); \ - }) +int ovs_vport_ops_register(struct vport_ops *ops); void ovs_vport_ops_unregister(struct vport_ops *ops); void ovs_vport_send(struct vport *vport, struct sk_buff *skb, u8 mac_proto); From e5ba33295201d79a63a204c16db9d51caa963a6c Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Tue, 4 Aug 2026 20:20:38 +0200 Subject: [PATCH 1007/1433] net: geneve: remove unused geneve_dev_create_fb The only user was vport-geneve in openvswitch and now it is gone. This also removes the last exported function in geneve module, significantly reducing complexity of the locking analysis. Signed-off-by: Ilya Maximets Link: https://patch.msgid.link/20260804182049.2289754-5-i.maximets@ovn.org Signed-off-by: Paolo Abeni --- drivers/net/geneve.c | 49 -------------------------------------------- include/net/geneve.h | 5 ----- 2 files changed, 54 deletions(-) diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index a6a8978e3b81..0ab729f055ea 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -2670,55 +2670,6 @@ static struct rtnl_link_ops geneve_link_ops __read_mostly = { .fill_info = geneve_fill_info, }; -struct net_device *geneve_dev_create_fb(struct net *net, const char *name, - u8 name_assign_type, u16 dst_port) -{ - struct nlattr *tb[IFLA_MAX + 1]; - struct net_device *dev; - LIST_HEAD(list_kill); - int err; - struct geneve_config cfg = { - .df = GENEVE_DF_UNSET, - .use_udp6_rx_checksums = true, - .ttl_inherit = false, - .collect_md = true, - .dualstack = true, - .port_min = 1, - .port_max = USHRT_MAX, - }; - - memset(tb, 0, sizeof(tb)); - dev = rtnl_create_link(net, name, name_assign_type, - &geneve_link_ops, tb, NULL); - if (IS_ERR(dev)) - return dev; - - init_tnl_info(&cfg.info, dst_port); - err = geneve_configure(net, dev, NULL, &cfg); - if (err) { - free_netdev(dev); - return ERR_PTR(err); - } - - /* openvswitch users expect packet sizes to be unrestricted, - * so set the largest MTU we can. - */ - err = geneve_change_mtu(dev, IP_MAX_MTU); - if (err) - goto err; - - err = rtnl_configure_link(dev, NULL, 0, NULL); - if (err < 0) - goto err; - - return dev; -err: - geneve_dellink(dev, &list_kill); - unregister_netdevice_many(&list_kill); - return ERR_PTR(err); -} -EXPORT_SYMBOL_GPL(geneve_dev_create_fb); - static int geneve_netdevice_event(struct notifier_block *unused, unsigned long event, void *ptr) { diff --git a/include/net/geneve.h b/include/net/geneve.h index 5c96827a487e..ba2c14d61e90 100644 --- a/include/net/geneve.h +++ b/include/net/geneve.h @@ -68,9 +68,4 @@ static inline bool netif_is_geneve(const struct net_device *dev) !strcmp(dev->rtnl_link_ops->kind, "geneve"); } -#ifdef CONFIG_INET -struct net_device *geneve_dev_create_fb(struct net *net, const char *name, - u8 name_assign_type, u16 dst_port); -#endif /*ifdef CONFIG_INET */ - #endif /*ifdef__NET_GENEVE_H */ From 9646825a51c49802df3f2fbcb230c50b00fc5780 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Tue, 4 Aug 2026 20:20:39 +0200 Subject: [PATCH 1008/1433] net: gre: remove unused gretap_fb_dev_create The only user was vport-gre in openvswitch and now it is gone. Signed-off-by: Ilya Maximets Link: https://patch.msgid.link/20260804182049.2289754-6-i.maximets@ovn.org Signed-off-by: Paolo Abeni --- include/net/gre.h | 2 -- net/ipv4/ip_gre.c | 47 ----------------------------------------------- 2 files changed, 49 deletions(-) diff --git a/include/net/gre.h b/include/net/gre.h index ccd293203284..b55f67ecd2fc 100644 --- a/include/net/gre.h +++ b/include/net/gre.h @@ -32,8 +32,6 @@ struct gre_protocol { int gre_add_protocol(const struct gre_protocol *proto, u8 version); int gre_del_protocol(const struct gre_protocol *proto, u8 version); -struct net_device *gretap_fb_dev_create(struct net *net, const char *name, - u8 name_assign_type); int gre_parse_header(struct sk_buff *skb, struct tnl_ptk_info *tpi, bool *csum_err, __be16 proto, int nhs); diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c index 0ba1e94e9012..6a92607401e8 100644 --- a/net/ipv4/ip_gre.c +++ b/net/ipv4/ip_gre.c @@ -1716,53 +1716,6 @@ static struct rtnl_link_ops erspan_link_ops __read_mostly = { .get_link_net = ip_tunnel_get_link_net, }; -struct net_device *gretap_fb_dev_create(struct net *net, const char *name, - u8 name_assign_type) -{ - struct rtnl_newlink_params params = { .src_net = net }; - struct nlattr *tb[IFLA_MAX + 1]; - struct net_device *dev; - LIST_HEAD(list_kill); - struct ip_tunnel *t; - int err; - - memset(&tb, 0, sizeof(tb)); - params.tb = tb; - - dev = rtnl_create_link(net, name, name_assign_type, - &ipgre_tap_ops, tb, NULL); - if (IS_ERR(dev)) - return dev; - - /* Configure flow based GRE device. */ - t = netdev_priv(dev); - t->collect_md = true; - - err = ipgre_newlink(dev, ¶ms, NULL); - if (err < 0) { - free_netdev(dev); - return ERR_PTR(err); - } - - /* openvswitch users expect packet sizes to be unrestricted, - * so set the largest MTU we can. - */ - err = __ip_tunnel_change_mtu(dev, IP_MAX_MTU, false); - if (err) - goto out; - - err = rtnl_configure_link(dev, NULL, 0, NULL); - if (err < 0) - goto out; - - return dev; -out: - ip_tunnel_dellink(dev, &list_kill); - unregister_netdevice_many(&list_kill); - return ERR_PTR(err); -} -EXPORT_SYMBOL_GPL(gretap_fb_dev_create); - static int __net_init ipgre_tap_init_net(struct net *net) { return ip_tunnel_init_net(net, gre_tap_net_id, &ipgre_tap_ops, "gretap0"); From a5ae637f863dc3a5f14e6b478064715ec4c1ac87 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Tue, 4 Aug 2026 20:20:40 +0200 Subject: [PATCH 1009/1433] net: vxlan: remove unused vxlan_dev_create The vport-vxlan in openvswitch was the last user and it is now gone. And we can now rename the internal function to have a better name. Signed-off-by: Ilya Maximets Link: https://patch.msgid.link/20260804182049.2289754-7-i.maximets@ovn.org Signed-off-by: Paolo Abeni --- drivers/net/vxlan/vxlan_core.c | 42 ++++------------------------------ include/net/vxlan.h | 3 --- 2 files changed, 4 insertions(+), 41 deletions(-) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index 25e3fe1ee751..fd5505153d0b 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -3966,9 +3966,9 @@ static int vxlan_dev_configure(struct net *src_net, struct net_device *dev, return 0; } -static int __vxlan_dev_create(struct net *net, struct net_device *dev, - struct vxlan_config *conf, - struct netlink_ext_ack *extack) +static int vxlan_dev_create(struct net *net, struct net_device *dev, + struct vxlan_config *conf, + struct netlink_ext_ack *extack) { struct vxlan_net *vn = net_generic(net, vxlan_net_id); struct vxlan_dev *vxlan = netdev_priv(dev); @@ -4416,7 +4416,7 @@ static int vxlan_newlink(struct net_device *dev, if (err) return err; - return __vxlan_dev_create(link_net, dev, &conf, extack); + return vxlan_dev_create(link_net, dev, &conf, extack); } static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[], @@ -4700,40 +4700,6 @@ static struct rtnl_link_ops vxlan_link_ops __read_mostly = { .get_link_net = vxlan_get_link_net, }; -struct net_device *vxlan_dev_create(struct net *net, const char *name, - u8 name_assign_type, - struct vxlan_config *conf) -{ - struct nlattr *tb[IFLA_MAX + 1]; - struct net_device *dev; - int err; - - memset(&tb, 0, sizeof(tb)); - - dev = rtnl_create_link(net, name, name_assign_type, - &vxlan_link_ops, tb, NULL); - if (IS_ERR(dev)) - return dev; - - err = __vxlan_dev_create(net, dev, conf, NULL); - if (err < 0) { - free_netdev(dev); - return ERR_PTR(err); - } - - err = rtnl_configure_link(dev, NULL, 0, NULL); - if (err < 0) { - LIST_HEAD(list_kill); - - vxlan_dellink(dev, &list_kill); - unregister_netdevice_many(&list_kill); - return ERR_PTR(err); - } - - return dev; -} -EXPORT_SYMBOL_GPL(vxlan_dev_create); - static void vxlan_handle_lowerdev_unregister(struct vxlan_net *vn, struct net_device *dev) { diff --git a/include/net/vxlan.h b/include/net/vxlan.h index 6e64757151b8..7b8207505523 100644 --- a/include/net/vxlan.h +++ b/include/net/vxlan.h @@ -359,9 +359,6 @@ struct vxlan_dev { VXLAN_F_MC_ROUTE | \ 0) -struct net_device *vxlan_dev_create(struct net *net, const char *name, - u8 name_assign_type, struct vxlan_config *conf); - static inline netdev_features_t vxlan_features_check(struct sk_buff *skb, netdev_features_t features) { From 762137ff748fa0c920f82ce718163691cb02f907 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Mon, 3 Aug 2026 22:59:42 +0900 Subject: [PATCH 1010/1433] ptp: fc3: register PTP clock after initialization ptp_clock_register() exposes the clock to userspace. If either following initialization operation fails, probe returns and devres frees idtfc3 while the registered clock still refers to the clock information embedded in it. Complete the fallible initialization before registering the clock. Schedule the worker after registration because it requires the registered clock. This removes post-registration failures and avoids exposing a partially initialized clock. Cc: stable+noautosel@kernel.org # untested fix to a driver init path race Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Link: https://patch.msgid.link/20260803135942.48383-1-mhun512@gmail.com Signed-off-by: Jakub Kicinski --- drivers/ptp/ptp_fc3.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/ptp/ptp_fc3.c b/drivers/ptp/ptp_fc3.c index f0e000428a3f..02b973995d7c 100644 --- a/drivers/ptp/ptp_fc3.c +++ b/drivers/ptp/ptp_fc3.c @@ -665,8 +665,6 @@ static int idtfc3_init_timecounter(struct idtfc3 *idtfc3) if (err) return err; - ptp_schedule_worker(idtfc3->ptp_clock, idtfc3->tc_update_period); - return 0; } @@ -825,6 +823,14 @@ static int idtfc3_enable_ptp(struct idtfc3 *idtfc3) idtfc3->caps = idtfc3_caps; snprintf(idtfc3->caps.name, sizeof(idtfc3->caps.name), "IDT FC3W"); + err = idtfc3_set_overhead(idtfc3); + if (err) + return err; + + err = idtfc3_init_timecounter(idtfc3); + if (err) + return err; + idtfc3->ptp_clock = ptp_clock_register(&idtfc3->caps, NULL); if (IS_ERR(idtfc3->ptp_clock)) { @@ -833,13 +839,7 @@ static int idtfc3_enable_ptp(struct idtfc3 *idtfc3) return err; } - err = idtfc3_set_overhead(idtfc3); - if (err) - return err; - - err = idtfc3_init_timecounter(idtfc3); - if (err) - return err; + ptp_schedule_worker(idtfc3->ptp_clock, idtfc3->tc_update_period); dev_info(idtfc3->dev, "TIME_SYNC_CHANNEL registered as ptp%d", idtfc3->ptp_clock->index); From 37a5e120118afaef5ca36f2127f23edac644c99a Mon Sep 17 00:00:00 2001 From: Subasri S Date: Sun, 2 Aug 2026 11:59:24 +0530 Subject: [PATCH 1011/1433] usb: atm: ueagle-atm: fix array-index-out-of-bounds in uea_bind() Add a bounds check on the global variable modem_index before using it as an index in sync_wait[] array whose size is NB_MODEM. Cc: stable+noautosel@kernel.org # untested fix to a driver init path race Reported-by: syzbot+92f5bf49bf4ac75223ca@syzkaller.appspotmail.com Tested-by: syzbot+92f5bf49bf4ac75223ca@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=92f5bf49bf4ac75223ca Signed-off-by: Subasri S Link: https://patch.msgid.link/20260802-usb-ueagble-atm-v1-1-340f085b04aa@gmail.com Signed-off-by: Jakub Kicinski --- drivers/usb/atm/ueagle-atm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/atm/ueagle-atm.c b/drivers/usb/atm/ueagle-atm.c index 4266a0cb7e3b..61723e7ab351 100644 --- a/drivers/usb/atm/ueagle-atm.c +++ b/drivers/usb/atm/ueagle-atm.c @@ -2463,7 +2463,8 @@ static int uea_bind(struct usbatm_data *usbatm, struct usb_interface *intf, if (ifnum != UEA_INTR_IFACE_NO) return -ENODEV; - usbatm->flags = (sync_wait[modem_index] ? 0 : UDSL_SKIP_HEAVY_INIT); + usbatm->flags = (modem_index < NB_MODEM && sync_wait[modem_index]) ? + 0 : UDSL_SKIP_HEAVY_INIT; /* interface 1 is for outbound traffic */ ret = claim_interface(usb, usbatm, UEA_US_IFACE_NO); From 5b782a811e7314fe5cc6ac2b924eede2c60fa2df Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Sun, 2 Aug 2026 15:01:37 +0200 Subject: [PATCH 1012/1433] macvlan: require lower-netns admin for shared port settings struct macvlan_port is per lower device and is shared by every macvlan upper on it, including uppers that live in other network namespaces. Two of its fields are settable over rtnetlink by any upper on the port: port->bc_cutoff, written by IFLA_MACVLAN_BC_CUTOFF, and port->bc_queue_len_used, recomputed from IFLA_MACVLAN_BC_QUEUE_LEN. (port->flags and port->perm_addr are also rtnetlink-settable, but only in passthru mode, which requires port->count == 0 and so cannot be reached from a second upper.) rtnetlink checks CAP_NET_ADMIN against the network namespace the configured device lives in and nothing else, so once a macvlan has been moved into a child network namespace, an administrator of that namespace alone reaches macvlan_changelink(), which applies both attributes without considering who owns the lower device. The create path has the same gap. macvlan_common_newlink() resolves a lower device that is itself a macvlan to the real lower device: if (netif_is_macvlan(lowerdev)) lowerdev = macvlan_dev_real_dev(lowerdev); That real device may sit in a network namespace that was never capability-checked. The new upper then joins its macvlan_port and runs update_port_bc_queue_len() on it, and, when IFLA_MACVLAN_BC_CUTOFF is present, update_port_bc_cutoff(). port->bc_cutoff is not a local tuning knob. update_port_bc_cutoff() recomputes port->bc_filter, which macvlan_handle_frame() tests to decide whether a multicast frame is deferred to the port broadcast work queue or flooded inline from the RX softirq, and a negative cutoff clears bc_filter outright. A namespace that administers none of the other uppers can therefore change how all of them receive multicast. Reproduced on 6.8 with a dummy lower device and two macvlan uppers, one left in the initial namespace and one moved into a child user and network namespace. From the child, both a changelink and a nested newlink carrying IFLA_MACVLAN_BC_CUTOFF were accepted, and the value read back on the initial-namespace sibling followed them, changing from 1 to -7 and then to -42. Require CAP_NET_ADMIN in the lower device network namespace before applying a shared port setting or creating a macvlan on a flattened lower device. rtnl_dev_link_net_capable() short-circuits when the lower device shares the macvlan network namespace, so an ordinary single-namespace configuration is unaffected, and per-upper settings such as mode and flags stay available to an administrator of the macvlan's own namespace. This is the model ipvlan has used since commit 7cc9f7003a96 ("ipvlan: disallow userns cap_net_admin to change global mode/flags"). Found by 0sec automated security-research tooling (https://0sec.ai). The newlink gate is unconditional rather than keyed on a BC attribute being present, because joining another namespace's macvlan_port is itself a mutation of shared state; ipvlan gates ipvlan_link_new() the same way. IFLA_MACVLAN_BC_QUEUE_LEN is gated here as well as by any magnitude check, because the two address different things: a magnitude check bounds how large a value any caller may request, while this bounds who may write the shared port at all. update_port_bc_queue_len() takes the maximum across uppers, so a cross-namespace lowering has no security effect and this over-rejects it; that is accepted in exchange for one rule covering every writer of the shared struct. Cc: stable+noautosel@kernel.org # local DoS by userns are a dime a dozen Signed-off-by: Doruk Tan Ozturk Link: https://patch.msgid.link/20260802130137.98105-1-doruk@0sec.ai Signed-off-by: Jakub Kicinski --- drivers/net/macvlan.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c index 8d71a832cb17..aa5add519625 100644 --- a/drivers/net/macvlan.c +++ b/drivers/net/macvlan.c @@ -1492,8 +1492,14 @@ int macvlan_common_newlink(struct net_device *dev, /* When creating macvlans or macvtaps on top of other macvlans - use * the real device as the lowerdev. */ - if (netif_is_macvlan(lowerdev)) + if (netif_is_macvlan(lowerdev)) { lowerdev = macvlan_dev_real_dev(lowerdev); + if (!rtnl_dev_link_net_capable(dev, dev_net(lowerdev))) { + NL_SET_ERR_MSG(extack, + "Creating a macvlan on a lower device in another network namespace requires CAP_NET_ADMIN in that namespace"); + return -EPERM; + } + } if (!tb[IFLA_MTU]) dev->mtu = lowerdev->mtu; @@ -1632,6 +1638,14 @@ static int macvlan_changelink(struct net_device *dev, enum macvlan_macaddr_mode macmode; int ret; + if (data && + (data[IFLA_MACVLAN_BC_QUEUE_LEN] || data[IFLA_MACVLAN_BC_CUTOFF]) && + !rtnl_dev_link_net_capable(dev, dev_net(vlan->lowerdev))) { + NL_SET_ERR_MSG(extack, + "Changing shared macvlan port settings requires CAP_NET_ADMIN in the lower device network namespace"); + return -EPERM; + } + /* Validate mode, but don't set yet: setting flags may fail. */ if (data && data[IFLA_MACVLAN_MODE]) { set_mode = true; From d87cabf0b6739dfe4c2b3ec2ab3b7f7646fe5bc5 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Fri, 31 Jul 2026 10:15:20 +0000 Subject: [PATCH 1013/1433] usb: atm: cxacru: properly kill rcv_urb on error in cxacru_cm() If cxacru_cm() encounters an error while submitting or waiting for snd_urb, it aborts and returns the error without killing the already submitted rcv_urb. This leaves the rcv_urb active. When this happens during initialization (e.g., in cxacru_atm_start()), the driver may ignore the error and proceed to call cxacru_poll_status(), which invokes cxacru_cm() again. Attempting to submit the still-active rcv_urb triggers a warning in usb_submit_urb(): cxacru 1-1:1.0: send of cm 0x84 failed (-104) ATM dev 0: cxacru_atm_start: CHIP_ADSL_LINE_START returned -104 ------------[ cut here ]------------ URB ffff88812658d200 submitted while active WARNING: drivers/usb/core/urb.c:379 at usb_submit_urb+0x79/0x18b0 drivers/usb/core/urb.c:379 ... Call Trace: cxacru_cm+0x21a/0xf10 drivers/usb/atm/cxacru.c:631 cxacru_cm_get_array drivers/usb/atm/cxacru.c:722 [inline] cxacru_poll_status+0x178/0x1110 drivers/usb/atm/cxacru.c:828 cxacru_atm_start+0x185/0x360 drivers/usb/atm/cxacru.c:814 usbatm_atm_init+0x144/0x3a0 drivers/usb/atm/usbatm.c:927 usbatm_usb_probe+0x15cb/0x1db0 drivers/usb/atm/usbatm.c:1178 cxacru_usb_probe+0x17f/0x220 drivers/usb/atm/cxacru.c:1370 ... To fix this, ensure that rcv_urb is properly killed if cxacru_cm() aborts early. We can safely call usb_kill_urb() on rcv_urb in the error path, as it is safe to call even if the URB is not active (e.g., if it failed to submit in the first place, or if it already completed). Cc: stable+noautosel@kernel.org # untested fix to unlikely driver error path Reported-by: syzbot+c9dff578c3a41775176a@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=c9dff578c3a41775176a Link: https://syzkaller.appspot.com/ai_job?id=75fec6f2-c8a6-43b1-b184-4d26baba86cc Signed-off-by: Aleksandr Nogikh Link: https://patch.msgid.link/91edfa4c-a63d-400c-9f00-31f3e1f98c00@mail.kernel.org Signed-off-by: Jakub Kicinski --- drivers/usb/atm/cxacru.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/atm/cxacru.c b/drivers/usb/atm/cxacru.c index f1900c567ba4..429ac20a8999 100644 --- a/drivers/usb/atm/cxacru.c +++ b/drivers/usb/atm/cxacru.c @@ -700,6 +700,8 @@ static int cxacru_cm(struct cxacru_data *instance, enum cxacru_cm_request cm, ret = offd; usb_dbg(instance->usbatm, "cm %#x\n", cm); fail: + if (ret < 0) + usb_kill_urb(instance->rcv_urb); mutex_unlock(&instance->cm_serialize); err: return ret; From b5b02ce657772beeff9a8f8b1df3985377124bb1 Mon Sep 17 00:00:00 2001 From: Nguyen Quang Le Kien Date: Mon, 3 Aug 2026 18:17:16 +0800 Subject: [PATCH 1014/1433] usb: atm: cxacru: fix use-after-free in cxacru_poll_status In cxacru_unbind(), cancel_delayed_work_sync() was conditionally skipped when poll_state was CXPOLL_STOPPED. However, a work item previously scheduled when poll_state was CXPOLL_POLLING may still be pending in the workqueue at the time poll_state transitions to CXPOLL_STOPPED. Skipping cancel_delayed_work_sync() in this case allows the work to fire after cxacru_data is freed, causing a use-after-free when cxacru_poll_status() attempts to acquire instance->poll_state_serialize. Fix this by always calling cancel_delayed_work_sync() regardless of poll_state, ensuring no pending or in-flight work can access the freed instance. Cc: stable+noautosel@kernel.org # untested fix to a driver init path race Reported-by: syzbot+24eb38c789655fc43663@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=24eb38c789655fc43663 Signed-off-by: Nguyen Quang Le Kien Link: https://patch.msgid.link/20260803101716.2592486-1-khiemtranzo532001@gmail.com Signed-off-by: Jakub Kicinski --- drivers/usb/atm/cxacru.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/drivers/usb/atm/cxacru.c b/drivers/usb/atm/cxacru.c index 429ac20a8999..636f7886fc26 100644 --- a/drivers/usb/atm/cxacru.c +++ b/drivers/usb/atm/cxacru.c @@ -1233,8 +1233,6 @@ static void cxacru_unbind(struct usbatm_data *usbatm_instance, struct usb_interface *intf) { struct cxacru_data *instance = usbatm_instance->driver_data; - int is_polling = 1; - usb_dbg(usbatm_instance, "cxacru_unbind entered\n"); if (!instance) { @@ -1245,17 +1243,11 @@ static void cxacru_unbind(struct usbatm_data *usbatm_instance, mutex_lock(&instance->poll_state_serialize); BUG_ON(instance->poll_state == CXPOLL_SHUTDOWN); - /* ensure that status polling continues unless - * it has already stopped */ - if (instance->poll_state == CXPOLL_STOPPED) - is_polling = 0; - /* stop polling from being stopped or started */ instance->poll_state = CXPOLL_SHUTDOWN; mutex_unlock(&instance->poll_state_serialize); - if (is_polling) - cancel_delayed_work_sync(&instance->poll_work); + cancel_delayed_work_sync(&instance->poll_work); usb_kill_urb(instance->snd_urb); usb_kill_urb(instance->rcv_urb); From f2473fbfc3fd8d07142069bcd68316e26cdad59e Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Wed, 5 Aug 2026 01:14:09 +0000 Subject: [PATCH 1015/1433] fjes: unregister the netdev before destroying the workqueues fjes_remove() destroys the driver workqueues before unregistering the netdev. The interrupt handler queues work on them, but the IRQ is only freed from fjes_close() under unregister_netdev(), so an interrupt in that window can queue work once the workqueues are gone. Unregister the netdev first so fjes_close() frees the IRQ and cancels the workers before the workqueues are destroyed. force_close_task, which the workers arm on the system workqueue, is handled in the next patch. This issue was found by an in-house static analysis tool. Cc: stable+noautosel@kernel.org # untested fix to a driver init path race Signed-off-by: Fan Wu Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260805011410.414431-1-fanwu01@zju.edu.cn Signed-off-by: Jakub Kicinski --- drivers/net/fjes/fjes_main.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/net/fjes/fjes_main.c b/drivers/net/fjes/fjes_main.c index 1f0f38980549..cddabc9653b9 100644 --- a/drivers/net/fjes/fjes_main.c +++ b/drivers/net/fjes/fjes_main.c @@ -1394,17 +1394,14 @@ static void fjes_remove(struct platform_device *plat_dev) fjes_dbg_adapter_exit(adapter); - cancel_delayed_work_sync(&adapter->interrupt_watch_task); - cancel_work_sync(&adapter->unshare_watch_task); - cancel_work_sync(&adapter->raise_intr_rxdata_task); - cancel_work_sync(&adapter->tx_stall_task); + /* Unregister first: .ndo_stop frees the IRQ and cancels the workers. */ + unregister_netdev(netdev); + if (adapter->control_wq) destroy_workqueue(adapter->control_wq); if (adapter->txrx_wq) destroy_workqueue(adapter->txrx_wq); - unregister_netdev(netdev); - fjes_hw_exit(hw); netif_napi_del(&adapter->napi); From c206fc0705d1050d9bce22ac4eb94bb062443cd6 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Wed, 5 Aug 2026 01:23:37 +0000 Subject: [PATCH 1016/1433] fjes: cancel force_close_task in fjes_remove() force_close_task runs on the system workqueue, which destroy_workqueue() does not drain, so it can run after free_netdev() and touch freed memory. Cancel it after destroying the workqueues, before free_netdev(). This issue was found by an in-house static analysis tool. Cc: stable+noautosel@kernel.org # untested fix to a driver init path race Signed-off-by: Fan Wu Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260805012337.416908-1-fanwu01@zju.edu.cn Signed-off-by: Jakub Kicinski --- drivers/net/fjes/fjes_main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/fjes/fjes_main.c b/drivers/net/fjes/fjes_main.c index cddabc9653b9..a2c77ca0d155 100644 --- a/drivers/net/fjes/fjes_main.c +++ b/drivers/net/fjes/fjes_main.c @@ -1402,6 +1402,8 @@ static void fjes_remove(struct platform_device *plat_dev) if (adapter->txrx_wq) destroy_workqueue(adapter->txrx_wq); + cancel_work_sync(&adapter->force_close_task); + fjes_hw_exit(hw); netif_napi_del(&adapter->napi); From ca10634ebcca850dec2f1507ad5bfe70b82a9c00 Mon Sep 17 00:00:00 2001 From: Tejas Birajdar Date: Thu, 30 Jul 2026 15:00:55 -0700 Subject: [PATCH 1017/1433] tcp: honor BPF_SOCK_OPS_RWND_INIT on the active connect path BPF_SOCK_OPS_RWND_INIT lets a sockops BPF program pick the initial TCP receive window, e.g. to advertise a larger window up front in environments where that is known to be safe. Today it is only effective for the passive (listener) side; on the active (connect) side the value is computed and then silently discarded. On the passive path tcp_openreq_init_rwin() inflates full_space when the program returns a non-zero window, so tcp_select_initial_window() can offer it: else if (full_space < (u64)rcv_wnd * mss) full_space = min_t(u64, (u64)rcv_wnd * mss, INT_MAX); tcp_select_initial_window() only clamps the requested window *down* to the available space, so without inflating the space first the BPF reply can never raise the offered window above tcp_full_space(sk). tcp_connect_init() calls tcp_rwnd_init_bpf() but never inflates full_space, so on connect() the requested window is clamped back to tcp_full_space(sk) (~64KB at the default rcvbuf) and the program's value is ignored. Inflate full_space in tcp_connect_init() as well; tp->advmss is the mss the listener path uses (both are tcp_mss_clamp(tp, dst_metric_advmss(dst))). Read full_space after tcp_rwnd_init_bpf() so a program that also adjusts SO_RCVBUF is still reflected. Compute the inflated value in u64 and clamp to INT_MAX to avoid overflow (full_space is int, rcv_wnd is u32), and apply the same overflow fix to the existing listener-side computation. tcp_select_initial_window() itself also computes init_rcv_wnd * mss in 32-bit when clamping the offered window down to the requested value. A large requested window (init_rcv_wnd greater than ~2.9M segments at 1460 mss) wraps this multiply and collapses the offered window to a tiny value, so compute it in u64 as well. Signed-off-by: Tejas Birajdar Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260730220055.2946171-1-tejasbirajdar@meta.com Signed-off-by: Jakub Kicinski --- net/ipv4/tcp_minisocks.c | 4 ++-- net/ipv4/tcp_output.c | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/net/ipv4/tcp_minisocks.c b/net/ipv4/tcp_minisocks.c index 6ab3e3a0b431..12254e6eb2f3 100644 --- a/net/ipv4/tcp_minisocks.c +++ b/net/ipv4/tcp_minisocks.c @@ -453,8 +453,8 @@ void tcp_openreq_init_rwin(struct request_sock *req, rcv_wnd = tcp_rwnd_init_bpf((struct sock *)req); if (rcv_wnd == 0) rcv_wnd = dst_metric(dst, RTAX_INITRWND); - else if (full_space < rcv_wnd * mss) - full_space = rcv_wnd * mss; + else if (full_space < (u64)rcv_wnd * mss) + full_space = min_t(u64, (u64)rcv_wnd * mss, INT_MAX); /* tcp_full_space because it is guaranteed to be the first packet */ tcp_select_initial_window(sk_listener, full_space, diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index d7c1444b5e30..fcaa04e65189 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -251,7 +251,7 @@ void tcp_select_initial_window(const struct sock *sk, int __space, __u32 mss, (*rcv_wnd) = space; if (init_rcv_wnd) - *rcv_wnd = min(*rcv_wnd, init_rcv_wnd * mss); + *rcv_wnd = min_t(u64, *rcv_wnd, (u64)init_rcv_wnd * mss); *rcv_wscale = 0; if (wscale_ok) { @@ -4103,6 +4103,7 @@ static void tcp_connect_init(struct sock *sk) const struct dst_entry *dst = __sk_dst_get(sk); struct tcp_sock *tp = tcp_sk(sk); __u8 rcv_wscale; + int full_space; u16 user_mss; u32 rcv_wnd; @@ -4137,10 +4138,13 @@ static void tcp_connect_init(struct sock *sk) WRITE_ONCE(tp->window_clamp, tcp_full_space(sk)); rcv_wnd = tcp_rwnd_init_bpf(sk); + full_space = tcp_full_space(sk); if (rcv_wnd == 0) rcv_wnd = dst_metric(dst, RTAX_INITRWND); + else if (full_space < (u64)rcv_wnd * tp->advmss) + full_space = min_t(u64, (u64)rcv_wnd * tp->advmss, INT_MAX); - tcp_select_initial_window(sk, tcp_full_space(sk), + tcp_select_initial_window(sk, full_space, tp->advmss - (tp->rx_opt.ts_recent_stamp ? tp->tcp_header_len - sizeof(struct tcphdr) : 0), &tp->rcv_wnd, &tp->window_clamp, From 8ec03dcf98f985c5c9b512d7e8d04911bce1cf21 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sun, 14 Jun 2026 17:12:58 +0300 Subject: [PATCH 1018/1433] Bluetooth: btusb: Add new VID/PID 0x0489/0xe156 for MT7902 Add VID 0489 & PID e156 for MediaTek MT7902 USB Bluetooth chip. The information in /sys/kernel/debug/usb/devices about the Bluetooth device is listed as the below. T: Bus=01 Lev=01 Prnt=01 Port=09 Cnt=05 Dev#= 6 Spd=480 MxCh= 0 D: Ver= 2.10 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=0489 ProdID=e156 Rev= 1.00 S: Manufacturer=MediaTek Inc. S: Product=Wireless_Device S: SerialNumber=000000000 C:* #Ifs= 3 Cfg#= 1 Atr=e0 MxPwr=100mA A: FirstIf#= 0 IfCount= 3 Cls=e0(wlcon) Sub=01 Prot=01 I:* If#= 0 Alt= 0 #EPs= 3 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=81(I) Atr=03(Int.) MxPS= 16 Ivl=125us E: Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 0 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 0 Ivl=1ms I: If#= 1 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 9 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 9 Ivl=1ms I: If#= 1 Alt= 2 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 17 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 17 Ivl=1ms I: If#= 1 Alt= 3 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 25 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 25 Ivl=1ms I: If#= 1 Alt= 4 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 33 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 33 Ivl=1ms I: If#= 1 Alt= 5 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 49 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 49 Ivl=1ms I: If#= 1 Alt= 6 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 63 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 63 Ivl=1ms I: If#= 2 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=8a(I) Atr=03(Int.) MxPS= 64 Ivl=125us E: Ad=0a(O) Atr=03(Int.) MxPS= 64 Ivl=125us I:* If#= 2 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=8a(I) Atr=03(Int.) MxPS= 512 Ivl=125us E: Ad=0a(O) Atr=03(Int.) MxPS= 512 Ivl=125us Co-developed-by: Kirill Shubin Signed-off-by: Kirill Shubin Signed-off-by: Sean Wang Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 184e95c1625e..915623b4b4ec 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -679,6 +679,8 @@ static const struct usb_device_id quirks_table[] = { { USB_DEVICE(0x13d3, 0x3606), .driver_info = BTUSB_MEDIATEK | BTUSB_WIDEBAND_SPEECH }, /* MediaTek MT7902 Bluetooth devices */ + { USB_DEVICE(0x0489, 0xe156), .driver_info = BTUSB_MEDIATEK | + BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x0e8d, 0x1ede), .driver_info = BTUSB_MEDIATEK | BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x13d3, 0x3579), .driver_info = BTUSB_MEDIATEK | From 252862a031d90e89e706d17107b3a72fafbd84fd Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 14 Jun 2026 13:27:02 +0300 Subject: [PATCH 1019/1433] Bluetooth: af_bluetooth: Add minimal context analysis annotations Add minimal compiler context analysis annotations, required for compilation to pass. Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/af_bluetooth.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/net/bluetooth/af_bluetooth.c b/net/bluetooth/af_bluetooth.c index a2290ffdc2c1..411d66f24393 100644 --- a/net/bluetooth/af_bluetooth.c +++ b/net/bluetooth/af_bluetooth.c @@ -209,6 +209,7 @@ bool bt_sock_linked(struct bt_sock_list *l, struct sock *s) EXPORT_SYMBOL(bt_sock_linked); void bt_accept_enqueue(struct sock *parent, struct sock *sk, bool bh) + __context_unsafe(/* conditional locking */) { const struct cred *old_cred; struct pid *old_pid; @@ -815,7 +816,8 @@ EXPORT_SYMBOL(bt_sock_wait_ready); #ifdef CONFIG_PROC_FS static void *bt_seq_start(struct seq_file *seq, loff_t *pos) - __acquires(seq->private->l->lock) + __acquires_shared(&((struct bt_sock_list *) + pde_data(file_inode(seq->file)))->lock) { struct bt_sock_list *l = pde_data(file_inode(seq->file)); @@ -831,7 +833,8 @@ static void *bt_seq_next(struct seq_file *seq, void *v, loff_t *pos) } static void bt_seq_stop(struct seq_file *seq, void *v) - __releases(seq->private->l->lock) + __releases_shared(&((struct bt_sock_list *) + pde_data(file_inode(seq->file)))->lock) { struct bt_sock_list *l = pde_data(file_inode(seq->file)); From 2c704850f1c7c625729e6f7e16db74ffad4b6752 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 14 Jun 2026 13:27:03 +0300 Subject: [PATCH 1020/1433] Bluetooth: hci_core: Add minimal context analysis annotations Add minimal compiler context analysis annotations, required for compilation to pass. compiler-context-analysis.h doesn't have tools to deal with the conditional SRCU locking on return value used here, so just disable the analysis in places instead of refactoring, in order to not make code changes here. Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 5ba9fe8261ec..d1e78ae7728e 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -62,6 +62,7 @@ static DEFINE_IDA(hci_index_ida); /* Get HCI device by index. * Device is held on return. */ static struct hci_dev *__hci_dev_get(int index, int *srcu_index) + __context_unsafe(/* conditional locking */) { struct hci_dev *hdev = NULL, *d; @@ -89,11 +90,13 @@ struct hci_dev *hci_dev_get(int index) } static struct hci_dev *hci_dev_get_srcu(int index, int *srcu_index) + __context_unsafe(/* conditional locking vs return */) { return __hci_dev_get(index, srcu_index); } static void hci_dev_put_srcu(struct hci_dev *hdev, int srcu_index) + __context_unsafe(/* conditional locking vs return */) { srcu_read_unlock(&hdev->srcu, srcu_index); hci_dev_put(hdev); From 20b9e52c3035aa20d96d44eef70226eef6bd9402 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 14 Jun 2026 13:27:04 +0300 Subject: [PATCH 1021/1433] Bluetooth: L2CAP: Add minimal context analysis annotations Add minimal compiler context analysis annotations, required for compilation to pass. Don't check complex conn->lock usage in l2cap_sock_shutdown(). The analysis cannot know that chan->conn pointer is never replaced by a different l2cap_conn. Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/l2cap_sock.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index 4058ff50cc27..735167f73f31 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -1365,6 +1365,7 @@ static int __l2cap_wait_ack(struct sock *sk, struct l2cap_chan *chan) } static int l2cap_sock_shutdown(struct socket *sock, int how) + __context_unsafe(/* complex chan->conn locking */) { struct sock *sk = sock->sk; struct l2cap_chan *chan; From 6e06750c45cead7a72fffb492099640ad7691dec Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 14 Jun 2026 13:27:05 +0300 Subject: [PATCH 1022/1433] Bluetooth: RFCOMM: Add minimal context analysis annotations Add minimal compiler context analysis annotations, required for compilation to pass. Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/rfcomm/sock.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/bluetooth/rfcomm/sock.c b/net/bluetooth/rfcomm/sock.c index feb302a491fa..958081adb9b5 100644 --- a/net/bluetooth/rfcomm/sock.c +++ b/net/bluetooth/rfcomm/sock.c @@ -60,6 +60,7 @@ static void rfcomm_sk_data_ready(struct rfcomm_dlc *d, struct sk_buff *skb) } static void rfcomm_sk_state_change(struct rfcomm_dlc *d, int err) + __must_hold(&d->lock) { struct sock *sk = d->owner, *parent; From e01e99a5ec2143d441c6ca0ce89abf3adb61cc12 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 14 Jun 2026 13:27:06 +0300 Subject: [PATCH 1023/1433] Bluetooth: enable context analysis Enable compiler context analysis for Bluetooth subsystem and drivers. Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/Makefile | 2 ++ net/bluetooth/Makefile | 2 ++ net/bluetooth/bnep/Makefile | 2 ++ net/bluetooth/hidp/Makefile | 2 ++ net/bluetooth/rfcomm/Makefile | 2 ++ 5 files changed, 10 insertions(+) diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile index bafc26250b63..e6b1c1180d1d 100644 --- a/drivers/bluetooth/Makefile +++ b/drivers/bluetooth/Makefile @@ -50,3 +50,5 @@ hci_uart-$(CONFIG_BT_HCIUART_AG6XX) += hci_ag6xx.o hci_uart-$(CONFIG_BT_HCIUART_MRVL) += hci_mrvl.o hci_uart-$(CONFIG_BT_HCIUART_AML) += hci_aml.o hci_uart-objs := $(hci_uart-y) + +CONTEXT_ANALYSIS := y diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile index 41049b280887..ff466ea97436 100644 --- a/net/bluetooth/Makefile +++ b/net/bluetooth/Makefile @@ -25,3 +25,5 @@ bluetooth-$(CONFIG_BT_MSFTEXT) += msft.o bluetooth-$(CONFIG_BT_AOSPEXT) += aosp.o bluetooth-$(CONFIG_BT_DEBUGFS) += hci_debugfs.o bluetooth-$(CONFIG_BT_SELFTEST) += selftest.o + +CONTEXT_ANALYSIS := y diff --git a/net/bluetooth/bnep/Makefile b/net/bluetooth/bnep/Makefile index 8af9d56bb012..f42015cc3245 100644 --- a/net/bluetooth/bnep/Makefile +++ b/net/bluetooth/bnep/Makefile @@ -6,3 +6,5 @@ obj-$(CONFIG_BT_BNEP) += bnep.o bnep-objs := core.o sock.o netdev.o + +CONTEXT_ANALYSIS := y diff --git a/net/bluetooth/hidp/Makefile b/net/bluetooth/hidp/Makefile index f41b0aa02b23..53e139e41bdc 100644 --- a/net/bluetooth/hidp/Makefile +++ b/net/bluetooth/hidp/Makefile @@ -6,3 +6,5 @@ obj-$(CONFIG_BT_HIDP) += hidp.o hidp-objs := core.o sock.o + +CONTEXT_ANALYSIS := y diff --git a/net/bluetooth/rfcomm/Makefile b/net/bluetooth/rfcomm/Makefile index 593e5c48c131..15f909f40f25 100644 --- a/net/bluetooth/rfcomm/Makefile +++ b/net/bluetooth/rfcomm/Makefile @@ -7,3 +7,5 @@ obj-$(CONFIG_BT_RFCOMM) += rfcomm.o rfcomm-y := core.o sock.o rfcomm-$(CONFIG_BT_RFCOMM_TTY) += tty.o + +CONTEXT_ANALYSIS := y From 288f31431be58d8d8b5f8524a8dd8e2aa2713fc0 Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Wed, 17 Jun 2026 18:30:19 +0300 Subject: [PATCH 1024/1433] Bluetooth: simplify force_no_mitm_write() with kstrtobool_from_user() Simplify 'force_no_mitm_write()' by using the convenient 'kstrtobool_from_user()'. Signed-off-by: Dmitry Antipov Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_debugfs.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/net/bluetooth/hci_debugfs.c b/net/bluetooth/hci_debugfs.c index 0635e4641db4..aadffaaff20e 100644 --- a/net/bluetooth/hci_debugfs.c +++ b/net/bluetooth/hci_debugfs.c @@ -1161,16 +1161,12 @@ static ssize_t force_no_mitm_write(struct file *file, size_t count, loff_t *ppos) { struct hci_dev *hdev = file->private_data; - char buf[32]; - size_t buf_size = min(count, (sizeof(buf) - 1)); bool enable; + int err; - if (copy_from_user(buf, user_buf, buf_size)) - return -EFAULT; - - buf[buf_size] = '\0'; - if (kstrtobool(buf, &enable)) - return -EINVAL; + err = kstrtobool_from_user(user_buf, count, &enable); + if (err) + return err; if (enable == hci_dev_test_flag(hdev, HCI_FORCE_NO_MITM)) return -EALREADY; From dc7b9893a3a0dd7a72537d55816fd4eb7e5457f6 Mon Sep 17 00:00:00 2001 From: Siwei Zhang Date: Mon, 15 Jun 2026 11:33:06 -0400 Subject: [PATCH 1025/1433] Bluetooth: hci_sync: Remove unused hci_cmd_sync_dequeue_once() hci_cmd_sync_dequeue_once() had a single in-tree caller, hci_cancel_connect_sync(), which now holds cmd_sync_work_lock across the in-flight create flag test and the dequeue and so open-codes the lookup and cancel under that lock. That leaves the exported hci_cmd_sync_dequeue_once() with no in-tree user, so remove it along with its declaration. Signed-off-by: Siwei Zhang Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/hci_sync.h | 3 --- net/bluetooth/hci_sync.c | 26 -------------------------- 2 files changed, 29 deletions(-) diff --git a/include/net/bluetooth/hci_sync.h b/include/net/bluetooth/hci_sync.h index 73e494b2591d..818e62d9fe9e 100644 --- a/include/net/bluetooth/hci_sync.h +++ b/include/net/bluetooth/hci_sync.h @@ -84,9 +84,6 @@ void hci_cmd_sync_cancel_entry(struct hci_dev *hdev, struct hci_cmd_sync_work_entry *entry); bool hci_cmd_sync_dequeue(struct hci_dev *hdev, hci_cmd_sync_work_func_t func, void *data, hci_cmd_sync_work_destroy_t destroy); -bool hci_cmd_sync_dequeue_once(struct hci_dev *hdev, - hci_cmd_sync_work_func_t func, void *data, - hci_cmd_sync_work_destroy_t destroy); int hci_update_eir_sync(struct hci_dev *hdev); int hci_update_class_sync(struct hci_dev *hdev); diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index c8d14128c363..3660120b26a6 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -860,32 +860,6 @@ void hci_cmd_sync_cancel_entry(struct hci_dev *hdev, } EXPORT_SYMBOL(hci_cmd_sync_cancel_entry); -/* Dequeue one HCI command entry: - * - * - Lookup and cancel first entry that matches. - */ -bool hci_cmd_sync_dequeue_once(struct hci_dev *hdev, - hci_cmd_sync_work_func_t func, - void *data, hci_cmd_sync_work_destroy_t destroy) -{ - struct hci_cmd_sync_work_entry *entry; - - mutex_lock(&hdev->cmd_sync_work_lock); - - entry = _hci_cmd_sync_lookup_entry(hdev, func, data, destroy); - if (!entry) { - mutex_unlock(&hdev->cmd_sync_work_lock); - return false; - } - - _hci_cmd_sync_cancel_entry(hdev, entry, -ECANCELED); - - mutex_unlock(&hdev->cmd_sync_work_lock); - - return true; -} -EXPORT_SYMBOL(hci_cmd_sync_dequeue_once); - /* Dequeue HCI command entry: * * - Lookup and cancel any entry that matches by function callback or data or From 849a3bf1489093996859ba0c3c1c0cd314d6ff96 Mon Sep 17 00:00:00 2001 From: Gustavo Evgucci Date: Thu, 25 Jun 2026 11:32:30 +0300 Subject: [PATCH 1026/1433] Bluetooth: btusb: Add USB ID 13d3:3625 for MediaTek MT7922 The IMC Networks MT7922 Bluetooth adapter with USB ID 13d3:3625 is not recognized as a MediaTek device because it is missing from the btusb device ID table. As a result, btmtk firmware loading is never triggered and the HCI reset command times out with -ETIMEDOUT. Add the device with BTUSB_MEDIATEK | BTUSB_WIDEBAND_SPEECH flags, consistent with the neighboring 13d3:3627, 13d3:3628 and 13d3:3630 entries which use the same chip. Tested on: MediaTek MT7922 (Wi-Fi 6E combo card, IMC Networks BT USB interface), kernel 7.0.11-arch1-1. /sys/kernel/debug/usb/devices: T: Bus=01 Lev=01 Prnt=01 Port=12 Cnt=03 Dev#= 4 Spd=480 MxCh= 0 D: Ver= 2.10 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=13d3 ProdID=3625 Rev= 1.00 S: Manufacturer=MediaTek Inc. S: Product=Wireless_Device S: SerialNumber=000000000 C:* #Ifs= 3 Cfg#= 1 Atr=e0 MxPwr=100mA A: FirstIf#= 0 IfCount= 3 Cls=e0(wlcon) Sub=01 Prot=01 I:* If#= 0 Alt= 0 #EPs= 3 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=81(I) Atr=03(Int.) MxPS= 16 Ivl=125us E: Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 0 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 0 Ivl=1ms I: If#= 1 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 9 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 9 Ivl=1ms I: If#= 1 Alt= 2 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 17 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 17 Ivl=1ms I: If#= 1 Alt= 3 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 25 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 25 Ivl=1ms I: If#= 1 Alt= 4 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 33 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 33 Ivl=1ms I: If#= 1 Alt= 5 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 49 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 49 Ivl=1ms I: If#= 1 Alt= 6 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 63 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 63 Ivl=1ms I: If#= 2 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=8a(I) Atr=03(Int.) MxPS= 64 Ivl=125us E: Ad=0a(O) Atr=03(Int.) MxPS= 64 Ivl=125us I:* If#= 2 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=8a(I) Atr=03(Int.) MxPS= 512 Ivl=125us E: Ad=0a(O) Atr=03(Int.) MxPS= 512 Ivl=125us Signed-off-by: Gustavo Evgucci Reviewed-by: Paul Menzel Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 915623b4b4ec..fada268a78f2 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -798,6 +798,8 @@ static const struct usb_device_id quirks_table[] = { BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x13d3, 0x3613), .driver_info = BTUSB_MEDIATEK | BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x13d3, 0x3625), .driver_info = BTUSB_MEDIATEK | + BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x13d3, 0x3627), .driver_info = BTUSB_MEDIATEK | BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x13d3, 0x3628), .driver_info = BTUSB_MEDIATEK | From cf81f0a3db2a5c34ee6e4ea379c631fab6d13e01 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Thu, 25 Jun 2026 22:19:46 -0700 Subject: [PATCH 1027/1433] Bluetooth: btqca: Fix qca_set_bdaddr() waiting for wrong HCI event qca_set_bdaddr() waits for HCI_EV_VENDOR when sending EDL_WRITE_BD_ADDR_OPCODE (0xFC14), but the controller responds with Command Complete event as confirmed by btmon on WCN7850: < HCI Command: Vendor (0x3f|0x0014) plen 6 #3 [hci0] 11 22 33 44 55 66 > HCI Event: Command Complete (0x0e) plen 4 #4 [hci0] Vendor (0x3f|0x0014) ncmd 1 Status: Success (0x00) Fix by passing 0 as the event parameter to __hci_cmd_sync_ev() to wait for the command complete event instead. Fixes: 5c0a1001c8be ("Bluetooth: hci_qca: Add helper to set device address") Reviewed-by: Bartosz Golaszewski Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btqca.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/bluetooth/btqca.c b/drivers/bluetooth/btqca.c index 10c496eaea2c..4b0d83858229 100644 --- a/drivers/bluetooth/btqca.c +++ b/drivers/bluetooth/btqca.c @@ -1029,8 +1029,7 @@ int qca_set_bdaddr(struct hci_dev *hdev, const bdaddr_t *bdaddr) baswap(&bdaddr_swapped, bdaddr); skb = __hci_cmd_sync_ev(hdev, EDL_WRITE_BD_ADDR_OPCODE, 6, - &bdaddr_swapped, HCI_EV_VENDOR, - HCI_INIT_TIMEOUT); + &bdaddr_swapped, 0, HCI_INIT_TIMEOUT); if (IS_ERR(skb)) { err = PTR_ERR(skb); bt_dev_err(hdev, "QCA Change address cmd failed (%d)", err); From d0b15d812688d3f0f3fe1c4426e12814d0c294dc Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Thu, 25 Jun 2026 22:19:47 -0700 Subject: [PATCH 1028/1433] Bluetooth: btusb: Fix BD_ADDR byte order in btusb_set_bdaddr_wcn6855() btusb_set_bdaddr_wcn6855() sends the address without swapping byte order for VSC 0xFC14, but the command expects the address in reversed byte order compared to other HCI commands like HCI_Create_Connection, resulting in a wrong BD_ADDR being set. btmon log on WCN6855 shows VSC 0xFC14 is sent with swapped bytes 11 22 33 44 55 66, and Read BD ADDR returns the expected address 11:22:33:44:55:66: < HCI Command: Vendor (0x3f|0x0014) plen 6 #3 [hci0] 11 22 33 44 55 66 > HCI Event: Command Complete (0x0e) plen 4 #4 [hci0] Vendor (0x3f|0x0014) ncmd 1 Status: Success (0x00) < HCI Command: Read BD ADDR (0x04|0x0009) plen 0 #11 [hci0] > HCI Event: Command Complete (0x0e) plen 10 #12 [hci0] Read BD ADDR (0x04|0x0009) ncmd 1 Status: Success (0x00) Address: 11:22:33:44:55:66 (OUI 11-22-33) Fix by swapping the input address before issuing the command. Fixes: b40f58b97386 ("Bluetooth: btusb: Add Qualcomm Bluetooth SoC WCN6855 support") Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index fada268a78f2..d02d3a9950de 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -3076,14 +3076,15 @@ static int btusb_set_bdaddr_ath3012(struct hci_dev *hdev, static int btusb_set_bdaddr_wcn6855(struct hci_dev *hdev, const bdaddr_t *bdaddr) { + bdaddr_t bdaddr_swapped; struct sk_buff *skb; - u8 buf[6]; long ret; - memcpy(buf, bdaddr, sizeof(bdaddr_t)); + baswap(&bdaddr_swapped, bdaddr); - skb = __hci_cmd_sync_ev(hdev, 0xfc14, sizeof(buf), buf, - HCI_EV_CMD_COMPLETE, HCI_INIT_TIMEOUT); + skb = __hci_cmd_sync_ev(hdev, 0xfc14, sizeof(bdaddr_swapped), + &bdaddr_swapped, HCI_EV_CMD_COMPLETE, + HCI_INIT_TIMEOUT); if (IS_ERR(skb)) { ret = PTR_ERR(skb); bt_dev_err(hdev, "Change address command failed (%ld)", ret); From ff50db7a522e7bd3bd1c4db6da715e4bdb53af97 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Thu, 25 Jun 2026 22:19:48 -0700 Subject: [PATCH 1029/1433] Bluetooth: btusb: Record matched usb_device_id into btusb_data Add @match_id to btusb_data to record the matched usb_device_id which will be used later. Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index d02d3a9950de..f3eee864ef19 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -1014,6 +1014,7 @@ struct btusb_data { bool usb_alt6_packet_flow; int isoc_altsetting; int suspend_count; + const struct usb_device_id *match_id; int (*recv_event)(struct hci_dev *hdev, struct sk_buff *skb); int (*recv_acl)(struct hci_dev *hdev, struct sk_buff *skb); @@ -4112,6 +4113,7 @@ static int btusb_probe(struct usb_interface *intf, if (!data) return -ENOMEM; + data->match_id = id; err = usb_find_common_endpoints(intf->cur_altsetting, &data->bulk_rx_ep, &data->bulk_tx_ep, &data->intr_ep, NULL); if (err) From 33c6a8d01889a84cc773c0c20c8323bce84af27d Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Thu, 25 Jun 2026 22:19:49 -0700 Subject: [PATCH 1030/1433] Bluetooth: btusb: QCA: Fix populating devcoredump fields on unenabled devices Devcoredump is not enabled for ATH3012 or QCA_ROME, but they unconditionally populate devcoredump fields in btusb_setup_qca(). Fix by populating devcoredump fields only when BTUSB_QCA_WCN6855 is set, which marks the first generation of QCA BT SoCs for which devcoredump is enabled. Fixes: 20981ce2d5a5 ("Bluetooth: btusb: Add WCN6855 devcoredump support") Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index f3eee864ef19..2e4847c362b3 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -3701,8 +3701,10 @@ static int btusb_setup_qca(struct hci_dev *hdev) if (err) return err; - btdata->qca_dump.fw_version = le32_to_cpu(ver.patch_version); - btdata->qca_dump.controller_id = le32_to_cpu(ver.rom_version); + if (btdata->match_id->driver_info & BTUSB_QCA_WCN6855) { + btdata->qca_dump.fw_version = le32_to_cpu(ver.patch_version); + btdata->qca_dump.controller_id = le32_to_cpu(ver.rom_version); + } if (!(status & QCA_SYSCFG_UPDATED)) { err = btusb_setup_qca_load_nvm(hdev, &ver, info); From 01f9b34e4a2c82d981dfbc17048b03929b9b0380 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Thu, 25 Jun 2026 22:19:50 -0700 Subject: [PATCH 1031/1433] Bluetooth: btusb: QCA: move qca_dump out of struct btusb_data 'struct btusb_data' ideally should not include vendor specific fields, but it currently includes the QCA devcoredump member 'struct qca_dump_info qca_dump'. Fix by moving it into hci_dev private area accessed by hci_get_priv(). Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 56 ++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 2e4847c362b3..78bc3f3adc77 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -941,6 +941,10 @@ struct qca_dump_info { u16 ram_dump_seqno; }; +struct btqca_data { + struct qca_dump_info qca_dump; +}; + #define BTUSB_MAX_ISOC_FRAMES 10 #define BTUSB_INTR_RUNNING 0 @@ -1027,8 +1031,6 @@ struct btusb_data { int (*disconnect)(struct hci_dev *hdev); int oob_wake_irq; /* irq for out-of-band wake-on-bt */ - - struct qca_dump_info qca_dump; }; static void btusb_reset(struct hci_dev *hdev) @@ -3121,14 +3123,15 @@ struct qca_dump_hdr { static void btusb_dump_hdr_qca(struct hci_dev *hdev, struct sk_buff *skb) { char buf[128]; - struct btusb_data *btdata = hci_get_drvdata(hdev); + struct btqca_data *btqca_data = hci_get_priv(hdev); + struct qca_dump_info *qca_dump_ptr = &btqca_data->qca_dump; snprintf(buf, sizeof(buf), "Controller Name: 0x%x\n", - btdata->qca_dump.controller_id); + qca_dump_ptr->controller_id); skb_put_data(skb, buf, strlen(buf)); snprintf(buf, sizeof(buf), "Firmware Version: 0x%x\n", - btdata->qca_dump.fw_version); + qca_dump_ptr->fw_version); skb_put_data(skb, buf, strlen(buf)); snprintf(buf, sizeof(buf), "Driver: %s\nVendor: qca\n", @@ -3136,7 +3139,7 @@ static void btusb_dump_hdr_qca(struct hci_dev *hdev, struct sk_buff *skb) skb_put_data(skb, buf, strlen(buf)); snprintf(buf, sizeof(buf), "VID: 0x%x\nPID:0x%x\n", - btdata->qca_dump.id_vendor, btdata->qca_dump.id_product); + qca_dump_ptr->id_vendor, qca_dump_ptr->id_product); skb_put_data(skb, buf, strlen(buf)); snprintf(buf, sizeof(buf), "Lmp Subversion: 0x%x\n", @@ -3165,6 +3168,8 @@ static int handle_dump_pkt_qca(struct hci_dev *hdev, struct sk_buff *skb) struct qca_dump_hdr *dump_hdr; struct btusb_data *btdata = hci_get_drvdata(hdev); + struct btqca_data *btqca_data = hci_get_priv(hdev); + struct qca_dump_info *qca_dump_ptr = &btqca_data->qca_dump; struct usb_device *udev = btdata->udev; pkt_type = hci_skb_pkt_type(skb); @@ -3192,8 +3197,8 @@ static int handle_dump_pkt_qca(struct hci_dev *hdev, struct sk_buff *skb) goto out; } - btdata->qca_dump.ram_dump_size = dump_size; - btdata->qca_dump.ram_dump_seqno = 0; + qca_dump_ptr->ram_dump_size = dump_size; + qca_dump_ptr->ram_dump_seqno = 0; skb_pull(skb, offsetof(struct qca_dump_hdr, data0)); @@ -3205,29 +3210,29 @@ static int handle_dump_pkt_qca(struct hci_dev *hdev, struct sk_buff *skb) skb_pull(skb, offsetof(struct qca_dump_hdr, data)); } - if (!btdata->qca_dump.ram_dump_size) { + if (!qca_dump_ptr->ram_dump_size) { ret = -EINVAL; bt_dev_err(hdev, "memdump is not active"); goto out; } - if ((seqno > btdata->qca_dump.ram_dump_seqno + 1) && (seqno != QCA_LAST_SEQUENCE_NUM)) { - dump_size = QCA_MEMDUMP_PKT_SIZE * (seqno - btdata->qca_dump.ram_dump_seqno - 1); + if ((seqno > qca_dump_ptr->ram_dump_seqno + 1) && seqno != QCA_LAST_SEQUENCE_NUM) { + dump_size = QCA_MEMDUMP_PKT_SIZE * (seqno - qca_dump_ptr->ram_dump_seqno - 1); hci_devcd_append_pattern(hdev, 0x0, dump_size); bt_dev_err(hdev, "expected memdump seqno(%u) is not received(%u)\n", - btdata->qca_dump.ram_dump_seqno, seqno); - btdata->qca_dump.ram_dump_seqno = seqno; + qca_dump_ptr->ram_dump_seqno, seqno); + qca_dump_ptr->ram_dump_seqno = seqno; kfree_skb(skb); return ret; } hci_devcd_append(hdev, skb); - btdata->qca_dump.ram_dump_seqno++; + qca_dump_ptr->ram_dump_seqno++; if (seqno == QCA_LAST_SEQUENCE_NUM) { bt_dev_info(hdev, "memdump done: pkts(%u), total(%u)\n", - btdata->qca_dump.ram_dump_seqno, btdata->qca_dump.ram_dump_size); + qca_dump_ptr->ram_dump_seqno, qca_dump_ptr->ram_dump_size); hci_devcd_complete(hdev); goto out; @@ -3235,10 +3240,10 @@ static int handle_dump_pkt_qca(struct hci_dev *hdev, struct sk_buff *skb) return ret; out: - if (btdata->qca_dump.ram_dump_size) + if (qca_dump_ptr->ram_dump_size) usb_enable_autosuspend(udev); - btdata->qca_dump.ram_dump_size = 0; - btdata->qca_dump.ram_dump_seqno = 0; + qca_dump_ptr->ram_dump_size = 0; + qca_dump_ptr->ram_dump_seqno = 0; clear_bit(BTUSB_HW_SSR_ACTIVE, &btdata->flags); if (ret < 0) @@ -3702,8 +3707,10 @@ static int btusb_setup_qca(struct hci_dev *hdev) return err; if (btdata->match_id->driver_info & BTUSB_QCA_WCN6855) { - btdata->qca_dump.fw_version = le32_to_cpu(ver.patch_version); - btdata->qca_dump.controller_id = le32_to_cpu(ver.rom_version); + struct btqca_data *btqca_data = hci_get_priv(hdev); + + btqca_data->qca_dump.fw_version = le32_to_cpu(ver.patch_version); + btqca_data->qca_dump.controller_id = le32_to_cpu(ver.rom_version); } if (!(status & QCA_SYSCFG_UPDATED)) { @@ -4169,6 +4176,9 @@ static int btusb_probe(struct usb_interface *intf, } else if (id->driver_info & BTUSB_MEDIATEK) { /* Allocate extra space for Mediatek device */ priv_size += sizeof(struct btmtk_data); + } else if (id->driver_info & BTUSB_QCA_WCN6855) { + /* Allocate extra space for QCA WCN6855 device */ + priv_size += sizeof(struct btqca_data); } data->recv_acl = hci_recv_frame; @@ -4311,8 +4321,10 @@ static int btusb_probe(struct usb_interface *intf, } if (id->driver_info & BTUSB_QCA_WCN6855) { - data->qca_dump.id_vendor = id->idVendor; - data->qca_dump.id_product = id->idProduct; + struct btqca_data *btqca_data = hci_get_priv(hdev); + + btqca_data->qca_dump.id_vendor = id->idVendor; + btqca_data->qca_dump.id_product = id->idProduct; data->recv_event = btusb_recv_evt_qca; data->recv_acl = btusb_recv_acl_qca; hci_devcd_register(hdev, btusb_coredump_qca, btusb_dump_hdr_qca, NULL); From faeaddd353fe8e2299dff417e2018cba0edbf03b Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Thu, 25 Jun 2026 22:19:51 -0700 Subject: [PATCH 1032/1433] Bluetooth: hci_sync: Introduce __hci_reset_sync() for device drivers Several vendor drivers have a requirement to send a synchronous raw HCI reset with HCI_INIT_TIMEOUT. Add a dedicated __hci_reset_sync() for them to use. Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/hci_sync.h | 1 + net/bluetooth/hci_sync.c | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/include/net/bluetooth/hci_sync.h b/include/net/bluetooth/hci_sync.h index 818e62d9fe9e..0756d6fe77d4 100644 --- a/include/net/bluetooth/hci_sync.h +++ b/include/net/bluetooth/hci_sync.h @@ -59,6 +59,7 @@ int __hci_cmd_sync_status(struct hci_dev *hdev, u16 opcode, u32 plen, int __hci_cmd_sync_status_sk(struct hci_dev *hdev, u16 opcode, u32 plen, const void *param, u8 event, u32 timeout, struct sock *sk); +int __hci_reset_sync(struct hci_dev *hdev); int hci_cmd_sync_status(struct hci_dev *hdev, u16 opcode, u32 plen, const void *param, u32 timeout); diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 3660120b26a6..00857fc3235b 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -3766,6 +3766,14 @@ int hci_reset_sync(struct hci_dev *hdev) return 0; } +/* Send a raw HCI reset for use by vendor drivers */ +int __hci_reset_sync(struct hci_dev *hdev) +{ + return __hci_cmd_sync_status(hdev, HCI_OP_RESET, 0, NULL, + HCI_INIT_TIMEOUT); +} +EXPORT_SYMBOL(__hci_reset_sync); + static int hci_init0_sync(struct hci_dev *hdev) { int err; From 2c0a1aaede9e82b54195756d64ff0eea15387809 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Thu, 25 Jun 2026 22:19:52 -0700 Subject: [PATCH 1033/1433] Bluetooth: btqca: Simplify qca_send_reset() by using __hci_reset_sync() qca_send_reset() is functionally equivalent to the newly added __hci_reset_sync(). Drop qca_send_reset() and call __hci_reset_sync() directly. Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btqca.c | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/drivers/bluetooth/btqca.c b/drivers/bluetooth/btqca.c index 4b0d83858229..22b08ab05b82 100644 --- a/drivers/bluetooth/btqca.c +++ b/drivers/bluetooth/btqca.c @@ -190,25 +190,6 @@ static int qca_send_patch_config_cmd(struct hci_dev *hdev) return err; } -static int qca_send_reset(struct hci_dev *hdev) -{ - struct sk_buff *skb; - int err; - - bt_dev_dbg(hdev, "QCA HCI_RESET"); - - skb = __hci_cmd_sync(hdev, HCI_OP_RESET, 0, NULL, HCI_INIT_TIMEOUT); - if (IS_ERR(skb)) { - err = PTR_ERR(skb); - bt_dev_err(hdev, "QCA Reset failed (%d)", err); - return err; - } - - kfree_skb(skb); - - return 0; -} - static int qca_read_fw_board_id(struct hci_dev *hdev, u16 *bid) { u8 cmd; @@ -990,11 +971,12 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate, } /* Perform HCI reset */ - err = qca_send_reset(hdev); + err = __hci_reset_sync(hdev); if (err < 0) { bt_dev_err(hdev, "QCA Failed to run HCI_RESET (%d)", err); return err; } + bt_dev_dbg(hdev, "QCA HCI_RESET succeed"); switch (soc_type) { case QCA_WCN3991: From 5b31fab9387520d9192661d67371f2881d3119a4 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Thu, 25 Jun 2026 22:19:53 -0700 Subject: [PATCH 1034/1433] Bluetooth: btusb: Simplify btusb_shutdown_qca() by using __hci_reset_sync() btusb_shutdown_qca() open-codes a synchronous raw HCI reset that is functionally equivalent to the newly added __hci_reset_sync(). Replace it with __hci_reset_sync() and return its result directly. Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 78bc3f3adc77..2a58a82a3ece 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -3892,16 +3892,13 @@ static bool btusb_wakeup(struct hci_dev *hdev) static int btusb_shutdown_qca(struct hci_dev *hdev) { - struct sk_buff *skb; + int err; - skb = __hci_cmd_sync(hdev, HCI_OP_RESET, 0, NULL, HCI_INIT_TIMEOUT); - if (IS_ERR(skb)) { + err = __hci_reset_sync(hdev); + if (err) bt_dev_err(hdev, "HCI reset during shutdown failed"); - return PTR_ERR(skb); - } - kfree_skb(skb); - return 0; + return err; } static ssize_t force_poll_sync_read(struct file *file, char __user *user_buf, From 9c23bb412cfa8625c5e86518d89a2337c742226b Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Thu, 25 Jun 2026 22:19:54 -0700 Subject: [PATCH 1035/1433] Bluetooth: hci_sync: Simplify hci_reset_sync() Return the reset command status directly instead of storing it in a local variable and using an if/return pattern. Reviewed-by: Bartosz Golaszewski Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 00857fc3235b..7779d9d1663a 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -3754,16 +3754,10 @@ static const struct hci_init_stage hci_init0[] = { int hci_reset_sync(struct hci_dev *hdev) { - int err; - set_bit(HCI_RESET, &hdev->flags); - err = __hci_cmd_sync_status(hdev, HCI_OP_RESET, 0, NULL, - HCI_CMD_TIMEOUT); - if (err) - return err; - - return 0; + return __hci_cmd_sync_status(hdev, HCI_OP_RESET, 0, NULL, + HCI_CMD_TIMEOUT); } /* Send a raw HCI reset for use by vendor drivers */ From c3bd57b9be300913a45a9446e34ad4438a220bea Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Thu, 25 Jun 2026 22:19:55 -0700 Subject: [PATCH 1036/1433] Bluetooth: hci_event: Log error for HCI reset status error in hci_cc_reset() HCI_Reset is a critical command, but hci_cc_reset() uses bt_dev_dbg() to log it, so a non-zero error status response may not be noticed. Fix by using bt_dev_err() when a status error occurs. Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_event.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 741d658e9630..ea858391c789 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -269,7 +269,10 @@ static u8 hci_cc_reset(struct hci_dev *hdev, void *data, struct sk_buff *skb) { struct hci_ev_status *rp = data; - bt_dev_dbg(hdev, "status 0x%2.2x", rp->status); + if (rp->status) + bt_dev_err(hdev, "status 0x%2.2x", rp->status); + else + bt_dev_dbg(hdev, "status 0x%2.2x", rp->status); clear_bit(HCI_RESET, &hdev->flags); From 92db4555c73f2aab9954dc7d2bc84c0563e6db7d Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Thu, 25 Jun 2026 22:19:56 -0700 Subject: [PATCH 1037/1433] Bluetooth: btusb: Reduce a redundant assignment in btusb_probe() Initialize @priv_size at declaration rather than separately: - Simpler: one statement completes both declaration and assignment. - More flexible: the variable is immediately usable from that point, so any new priv_size += can be freely inserted without caring about where the separate priv_size = 0 sits. Reviewed-by: Bartosz Golaszewski Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 2a58a82a3ece..bb5e1c2a6bc4 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -4082,7 +4082,7 @@ static int btusb_probe(struct usb_interface *intf, struct btusb_data *data; struct hci_dev *hdev; unsigned ifnum_base; - int err, priv_size; + int err, priv_size = 0; BT_DBG("intf %p id %p", intf, id); @@ -4153,8 +4153,6 @@ static int btusb_probe(struct usb_interface *intf, init_usb_anchor(&data->ctrl_anchor); spin_lock_init(&data->rxlock); - priv_size = 0; - data->recv_event = hci_recv_frame; data->recv_bulk = btusb_recv_bulk; From e22eb379a92ae8093e7aa4296b1cc93409a362f8 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Thu, 25 Jun 2026 22:19:57 -0700 Subject: [PATCH 1038/1433] Bluetooth: btusb: Use & instead of == to test bitflag BTUSB_IGNORE The driver_info field is a bitmask, so use & instead of == to test the BTUSB_IGNORE bitflag against it, which is consistent with how the other flags are tested. Reviewed-by: Bartosz Golaszewski Reviewed-by: Dmitry Baryshkov Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index bb5e1c2a6bc4..88b87e50fac2 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -4101,7 +4101,7 @@ static int btusb_probe(struct usb_interface *intf, id = match; } - if (id->driver_info == BTUSB_IGNORE) + if (id->driver_info & BTUSB_IGNORE) return -ENODEV; if (id->driver_info & BTUSB_ATH3012) { From dc16388d45ecbd3be0d8c9424dbbaa2c81806578 Mon Sep 17 00:00:00 2001 From: Tibor Harcsa Date: Mon, 29 Jun 2026 22:34:20 +0200 Subject: [PATCH 1039/1433] Bluetooth: btusb: Add IMC Networks QCA9377 to quirks table Add the USB ID (13d3:3503) for the IMC Networks Qualcomm Atheros QCA9377 Bluetooth controller to the btusb quirks table. This device requires Qualcomm Rome firmware and wideband speech support to function properly; otherwise, BLE scanning fails with HCI unexpected event opcode 0x2005 errors. The device reports the following in /sys/kernel/debug/usb/devices: P: Vendor=13d3 ProdID=3503 Rev= 0.01 C:* #Ifs= 2 Cfg#= 1 Atr=e0 MxPwr=100mA I:* If#= 0 Alt= 0 #EPs= 3 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=81(I) Atr=03(Int.) MxPS= 16 Ivl=1ms E: Ad=82(I) Atr=02(Bulk) MxPS= 64 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS= 64 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 0 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 0 Ivl=1ms I: If#= 1 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 9 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 9 Ivl=1ms I: If#= 1 Alt= 2 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 17 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 17 Ivl=1ms I: If#= 1 Alt= 3 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 25 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 25 Ivl=1ms I: If#= 1 Alt= 4 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 33 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 33 Ivl=1ms I: If#= 1 Alt= 5 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 49 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 49 Ivl=1ms Signed-off-by: Tibor Harcsa Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 88b87e50fac2..fc5424d89dac 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -297,6 +297,8 @@ static const struct usb_device_id quirks_table[] = { BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x13d3, 0x3501), .driver_info = BTUSB_QCA_ROME | BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x13d3, 0x3503), .driver_info = BTUSB_QCA_ROME | + BTUSB_WIDEBAND_SPEECH }, /* QCA WCN6855 chipset */ { USB_DEVICE(0x0489, 0xe0c7), .driver_info = BTUSB_QCA_WCN6855 | From 45640627e3ddc5f4d3a0d08618a596a8c65d48a5 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Mon, 6 Jul 2026 17:17:01 +0800 Subject: [PATCH 1040/1433] Bluetooth: hci_nokia: validate firmware packet bounds nokia_setup_fw() walks a length-prefixed firmware stream and decodes HCI command packets from each record. Check that each record fits in the remaining firmware image, that command records contain the HCI command header, and that the payload length is covered before submitting the command. Signed-off-by: Pengpeng Hou Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/hci_nokia.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/bluetooth/hci_nokia.c b/drivers/bluetooth/hci_nokia.c index 1e65b541f8ad..be2923231e71 100644 --- a/drivers/bluetooth/hci_nokia.c +++ b/drivers/bluetooth/hci_nokia.c @@ -354,9 +354,29 @@ static int nokia_setup_fw(struct hci_uart *hu) u16 opcode; struct sk_buff *skb; + if (pkt_size > fw_size - 2) { + err = -EINVAL; + dev_err(dev, "%s: Malformed firmware packet\n", + hu->hdev->name); + goto done; + } + switch (pkt_type) { case HCI_COMMAND_PKT: + if (pkt_size < 1 + HCI_COMMAND_HDR_SIZE) { + err = -EINVAL; + dev_err(dev, "%s: Malformed firmware command\n", + hu->hdev->name); + goto done; + } + cmd = (struct hci_command_hdr *)(fw_ptr + 3); + if (cmd->plen > pkt_size - 1 - HCI_COMMAND_HDR_SIZE) { + err = -EINVAL; + dev_err(dev, "%s: Truncated firmware command\n", + hu->hdev->name); + goto done; + } opcode = le16_to_cpu(cmd->opcode); skb = __hci_cmd_sync(hu->hdev, opcode, cmd->plen, From 980084de4d9b25193398d89a1c0430ba3501b683 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Jul 2026 11:28:56 +0200 Subject: [PATCH 1041/1433] Bluetooth: btusb: Add ASUS USB-BT540 for Realtek 8761CU Add the vendor/product ID (0x0b05, 0x1bef) to the usb_device_id table for the Realtek RTL8761CU-based ASUS USB-BT540 adapter. It binds via the generic Bluetooth class today, so BTUSB_REALTEK is never set and the rtl8761cu firmware is not loaded, leaving the controller non-functional. With the entry the driver loads rtl_bt/rtl8761cu_fw.bin (already shipped by linux-firmware) and the adapter works (tested: A2DP and ASHA). Similar to commit bc597f0cc44f ("Bluetooth: btusb: Add TP-Link UB600 for Realtek 8761BUV"). Device info from /sys/kernel/debug/usb/devices: T: Bus=01 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#= 22 Spd=12 MxCh= 0 D: Ver= 1.10 Cls=e0(wlcon) Sub=01 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=0b05 ProdID=1bef Rev= 2.00 S: Manufacturer=Realtek S: Product=Bluetooth Controller C:* #Ifs= 2 Cfg#= 1 Atr=e0 MxPwr=100mA I:* If#= 0 Alt= 0 #EPs= 3 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=81(I) Atr=03(Int.) MxPS= 64 Ivl=1ms E: Ad=02(O) Atr=02(Bulk) MxPS= 64 Ivl=0ms E: Ad=82(I) Atr=02(Bulk) MxPS= 64 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 0 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 0 Ivl=1ms I: If#= 1 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 9 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 9 Ivl=1ms I: If#= 1 Alt= 2 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 17 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 17 Ivl=1ms I: If#= 1 Alt= 3 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 25 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 25 Ivl=1ms I: If#= 1 Alt= 4 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 33 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 33 Ivl=1ms I: If#= 1 Alt= 5 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 49 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 49 Ivl=1ms I: If#= 1 Alt= 6 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 63 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 63 Ivl=1ms Cc: stable@vger.kernel.org Signed-off-by: Christoph Zwerschke Reviewed-by: Paul Menzel Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index fc5424d89dac..5ef79cc0469e 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -856,6 +856,10 @@ static const struct usb_device_id quirks_table[] = { { USB_DEVICE(0x37ad, 0x0600), .driver_info = BTUSB_REALTEK | BTUSB_WIDEBAND_SPEECH }, + /* Additional Realtek 8761CU Bluetooth devices */ + { USB_DEVICE(0x0b05, 0x1bef), .driver_info = BTUSB_REALTEK | + BTUSB_WIDEBAND_SPEECH }, + /* Additional Realtek 8821AE Bluetooth devices */ { USB_DEVICE(0x0b05, 0x17dc), .driver_info = BTUSB_REALTEK }, { USB_DEVICE(0x13d3, 0x3414), .driver_info = BTUSB_REALTEK }, From 6f0624b4427e38c3bb63a951c536cf8adaee1238 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Jul 2026 11:28:57 +0200 Subject: [PATCH 1042/1433] Bluetooth: btusb: Add ASUS USB-BT600 for Realtek 8761CU Add the vendor/product ID (0x0b05, 0x1d70) to the usb_device_id table for the Realtek RTL8761CU-based ASUS USB-BT600 adapter. It binds via the generic Bluetooth class today, so BTUSB_REALTEK is never set and the rtl8761cu firmware is not loaded, leaving the controller non-functional. With the entry the driver loads rtl_bt/rtl8761cu_fw.bin (already shipped by linux-firmware) and the adapter works (tested: A2DP and ASHA). Similar to commit bc597f0cc44f ("Bluetooth: btusb: Add TP-Link UB600 for Realtek 8761BUV"). Device info from /sys/kernel/debug/usb/devices: T: Bus=01 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#= 23 Spd=12 MxCh= 0 D: Ver= 1.10 Cls=e0(wlcon) Sub=01 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=0b05 ProdID=1d70 Rev= 2.00 S: Manufacturer=Realtek S: Product=Bluetooth Controller C:* #Ifs= 2 Cfg#= 1 Atr=e0 MxPwr=100mA I:* If#= 0 Alt= 0 #EPs= 3 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=81(I) Atr=03(Int.) MxPS= 64 Ivl=1ms E: Ad=02(O) Atr=02(Bulk) MxPS= 64 Ivl=0ms E: Ad=82(I) Atr=02(Bulk) MxPS= 64 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 0 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 0 Ivl=1ms I: If#= 1 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 9 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 9 Ivl=1ms I: If#= 1 Alt= 2 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 17 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 17 Ivl=1ms I: If#= 1 Alt= 3 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 25 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 25 Ivl=1ms I: If#= 1 Alt= 4 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 33 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 33 Ivl=1ms I: If#= 1 Alt= 5 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 49 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 49 Ivl=1ms I: If#= 1 Alt= 6 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 63 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 63 Ivl=1ms Cc: stable@vger.kernel.org Signed-off-by: Christoph Zwerschke Reviewed-by: Paul Menzel Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 5ef79cc0469e..e4063131822f 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -859,6 +859,8 @@ static const struct usb_device_id quirks_table[] = { /* Additional Realtek 8761CU Bluetooth devices */ { USB_DEVICE(0x0b05, 0x1bef), .driver_info = BTUSB_REALTEK | BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x0b05, 0x1d70), .driver_info = BTUSB_REALTEK | + BTUSB_WIDEBAND_SPEECH }, /* Additional Realtek 8821AE Bluetooth devices */ { USB_DEVICE(0x0b05, 0x17dc), .driver_info = BTUSB_REALTEK }, From a2b3b4f00403a3a40c5627d1bf537c8fb166e221 Mon Sep 17 00:00:00 2001 From: Zhao Dongdong Date: Fri, 15 May 2026 08:46:07 +0800 Subject: [PATCH 1043/1433] Bluetooth: btnxpuart: Fix use-after-free in probe error path In nxp_serdev_probe(), if hci_register_dev() succeeds but ps_setup() fails, the error path jumps to 'probe_fail' which only calls hci_free_dev() and asserts the reset GPIO, but does NOT call hci_unregister_dev() first. This leaves the HCI device registered in the system with its backing memory freed, leading to a use-after-free when userspace subsequently accesses the device (e.g. via hciconfig or bluetoothd). Fix by adding a 'probe_fail_unregister' label that calls hci_unregister_dev() before falling through to the existing 'probe_fail' label. The original 'probe_fail' label is preserved for the case where hci_register_dev() itself fails (device was never registered, so no unregister is needed). Signed-off-by: Zhao Dongdong Reviewed-by: Neeraj Sanjay Kale Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btnxpuart.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btnxpuart.c b/drivers/bluetooth/btnxpuart.c index 6a1cffe08d5f..0bb300eef157 100644 --- a/drivers/bluetooth/btnxpuart.c +++ b/drivers/bluetooth/btnxpuart.c @@ -1913,13 +1913,15 @@ static int nxp_serdev_probe(struct serdev_device *serdev) } if (ps_setup(hdev)) - goto probe_fail; + goto probe_fail_unregister; hci_devcd_register(hdev, nxp_coredump, nxp_coredump_hdr, nxp_coredump_notify); return 0; +probe_fail_unregister: + hci_unregister_dev(hdev); probe_fail: reset_control_assert(nxpdev->pdn); hci_free_dev(hdev); From 86f8661893613c68b6e9945bc093b0021381314b Mon Sep 17 00:00:00 2001 From: Kiran K Date: Thu, 2 Jul 2026 22:33:59 +0530 Subject: [PATCH 1044/1433] Bluetooth: btintel_pcie: split coredump worker into per-trigger works btintel_pcie_coredump_worker() handled three unrelated jobs in one work item: collect a DRAM trace coredump, read the hardware exception event, and read the firmware-trigger event. The worker walked three flag bits at runtime and each interrupt path mutated multiple bits to communicate which sub-jobs the worker should run, which made the ownership rules for those bits hard to reason about and entangled the trigger reason with the in-progress accounting. Replace the single combined worker with three single-purpose ones, each owning exactly one flag: coredump_work -> btintel_pcie_dump_traces() guarded by COREDUMP_INPROGRESS hwexp_work -> btintel_pcie_read_hwexp() guarded by CORE_HALTED (already permanent until re-probe; HWEXP_INPROGRESS is now redundant and removed) fwtrigger_work -> btintel_pcie_dump_fwtrigger_event() guarded by FWTRIGGER_DUMP_INPROGRESS All three workers are queued on a shared ordered workqueue (renamed coredump_workqueue -> dump_workqueue) so a companion event reader (hwexp/fwtrigger) and the coredump always run FIFO. Companion work is queued before coredump_work so dmp_hdr.event_type/event_id are populated by the time dump_traces() consumes them, preserving the original ordering. Introduce btintel_pcie_queue_coredump() to centralize the coredump trigger contract: it is the single writer of COREDUMP_INPROGRESS and of dmp_hdr.trigger_reason, sets both atomically against concurrent triggers, and rolls back the bit if the workqueue is disabled (reset/remove in progress) so a later trigger after re-probe can succeed. All four trigger sites (HWEXP IRQ, FW-trigger IRQ, devcoredump user trigger, resume() D0 error path) go through the helper. Per-work guard bits are now cleared at the tail of each worker rather than in the middle of the combined worker, which closes a subtle race where a duplicate IRQ could observe a cleared bit and requeue while the previous pass was still finalizing dev_coredumpv(). reset_work() and remove() now disable_work_sync() all three workers and, on the FLR-failure path, enable_work() all three to keep their disable counters balanced. The PLDR/FLR-success contract (re-probe re-INIT_WORKs everything with counter 0) is preserved. No functional change to the dump payloads; this is a pure restructuring of the worker dispatch and its synchronization. Signed-off-by: Kiran K Assisted-by: GitHub-Copilot:claude-4.7-opus Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 185 ++++++++++++++++++++----------- drivers/bluetooth/btintel_pcie.h | 12 +- 2 files changed, 130 insertions(+), 67 deletions(-) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 2b7231be5973..013568197a39 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -1446,72 +1446,134 @@ static int btintel_pcie_dump_fwtrigger_event(struct btintel_pcie_data *data) return err; } +/* Queue a coredump dump_traces() pass. + * + * Returns true if a new coredump was queued, false if one was already + * in-flight (the BTINTEL_PCIE_COREDUMP_INPROGRESS bit serves as the + * single-writer guard for the @coredump_work item) or the workqueue is + * disabled (reset / remove in progress). + * + * Always queue this AFTER any companion event-reader work (hwexp / + * fwtrigger) so that, on the ordered @dump_workqueue, the event reader + * runs first and populates dmp_hdr.event_type / event_id before + * dump_traces consumes them. + */ +static bool btintel_pcie_queue_coredump(struct btintel_pcie_data *data, + u16 trigger_reason) +{ + if (test_and_set_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags)) + return false; + + data->dmp_hdr.trigger_reason = trigger_reason; + + if (queue_work(data->dump_workqueue, &data->coredump_work)) + return true; + + /* Workqueue is disabled (reset/remove drained it). Release the + * guard so a later trigger, after re-probe, can succeed. + */ + clear_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags); + return false; +} + static void btintel_pcie_msix_fw_trigger_handler(struct btintel_pcie_data *data) { bt_dev_dbg(data->hdev, "Received firmware smart trigger cause"); - if (test_and_set_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, &data->flags)) + /* Per-work guard: deduplicate concurrent FW-trigger interrupts. + * Cleared at the tail of btintel_pcie_fwtrigger_worker(). + */ + if (test_and_set_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, + &data->flags)) return; - /* Trigger device core dump when there is FW assert */ - if (!test_and_set_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags)) - data->dmp_hdr.trigger_reason = BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT; + if (!queue_work(data->dump_workqueue, &data->fwtrigger_work)) { + clear_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, &data->flags); + return; + } - queue_work(data->coredump_workqueue, &data->coredump_work); + /* Queue coredump after the fwtrigger event reader so dmp_hdr.event_* + * is populated before dump_traces consumes it. + */ + btintel_pcie_queue_coredump(data, BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT); } static void btintel_pcie_msix_hw_exp_handler(struct btintel_pcie_data *data) { bt_dev_err(data->hdev, "Received hw exception interrupt"); + /* CORE_HALTED is the single-writer guard for this handler. It is + * set once on first HW exception and cleared only by re-probe + * (data is reallocated), so it also serializes hwexp_work + * scheduling without needing a separate bit. + */ if (test_and_set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags)) return; - if (test_and_set_bit(BTINTEL_PCIE_HWEXP_INPROGRESS, &data->flags)) - return; + /* Queue companion coredump first so it is appended after hwexp_work + * on the ordered @dump_workqueue (preserves the original + * coredump-then-hwexp ordering). + */ + btintel_pcie_queue_coredump(data, BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT); - /* Trigger device core dump when there is HW exception */ - if (!test_and_set_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags)) - data->dmp_hdr.trigger_reason = BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT; - - queue_work(data->coredump_workqueue, &data->coredump_work); + queue_work(data->dump_workqueue, &data->hwexp_work); } static void btintel_pcie_coredump_worker(struct work_struct *work) { struct btintel_pcie_data *data = container_of(work, struct btintel_pcie_data, coredump_work); - int err; /* hdev is NULL until setup_hdev() succeeds, and is cleared on * teardown after disable_work_sync() drains us; bail in that case. */ + if (!data->hdev) + goto out; + + btintel_pcie_dump_traces(data->hdev); +out: + /* Release guard last so a new trigger can run only after this + * pass has fully completed (including dev_coredumpv()). + */ + clear_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags); +} + +static void btintel_pcie_hwexp_worker(struct work_struct *work) +{ + struct btintel_pcie_data *data = container_of(work, + struct btintel_pcie_data, hwexp_work); + if (!data->hdev) return; - if (test_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, &data->flags)) { - err = btintel_pcie_dump_fwtrigger_event(data); - if (err) - bt_dev_warn(data->hdev, "failed to log fwtrigger event"); - clear_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, &data->flags); - } + /* Unlike usb products, controller will not send hardware exception + * event on exception. Instead controller writes the hardware event + * to device memory along with optional debug events, raises MSIX + * and halts. Driver shall read the exception event from device + * memory and passes it to the stack for further processing. + * + * Re-entry is gated by BTINTEL_PCIE_CORE_HALTED in the IRQ + * handler, which is only cleared by re-probe; no per-work bit + * is needed here. + */ + btintel_pcie_read_hwexp(data); +} - if (test_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags)) { - btintel_pcie_dump_traces(data->hdev); - clear_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags); - } +static void btintel_pcie_fwtrigger_worker(struct work_struct *work) +{ + struct btintel_pcie_data *data = container_of(work, + struct btintel_pcie_data, fwtrigger_work); + int err; - if (test_bit(BTINTEL_PCIE_HWEXP_INPROGRESS, &data->flags)) { - /* Unlike usb products, controller will not send hardware - * exception event on exception. Instead controller writes the - * hardware event to device memory along with optional debug - * events, raises MSIX and halts. Driver shall read the - * exception event from device memory and passes it stack for - * further processing. - */ - btintel_pcie_read_hwexp(data); - clear_bit(BTINTEL_PCIE_HWEXP_INPROGRESS, &data->flags); - } + if (!data->hdev) + goto out; + + err = btintel_pcie_dump_fwtrigger_event(data); + if (err) + bt_dev_warn(data->hdev, "failed to log fwtrigger event"); +out: + /* Release guard last; matches set in fw_trigger handler. */ + clear_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, &data->flags); } static void btintel_pcie_rx_work(struct work_struct *work) @@ -2650,20 +2712,22 @@ static void btintel_pcie_reset_work(struct work_struct *wk) btintel_pcie_synchronize_irqs(data); flush_work(&data->rx_work); - /* Drain any in-flight coredump and block new ones across reset. - * Safe from self-deadlock: coredump_work runs on a separate wq. + /* Drain any in-flight dump workers and block new ones across reset. + * Safe from self-deadlock: they all run on a separate wq. */ disable_work_sync(&data->coredump_work); + disable_work_sync(&data->hwexp_work); + disable_work_sync(&data->fwtrigger_work); bt_dev_dbg(data->hdev, "Release bluetooth interface"); /* Both reset paths follow the same contract: on success they * destroy 'data' via device_reprobe() (a fresh probe re-INIT_WORKs - * the coredump_work with disable count 0), so enable_work() must + * the dump workers with disable count 0), so enable_work() must * NOT be called on the success path. Only the FLR path can fail * with 'data' still alive, in which case we balance the - * disable_work_sync() above so a later successful reset is not - * permanently blocked. + * disable_work_sync() calls above so a later successful reset is + * not permanently blocked. * * pci_lock_rescan_remove() (held above) serializes against PCI * device addition/removal (hotplug), so no device can be added to @@ -2674,8 +2738,11 @@ static void btintel_pcie_reset_work(struct work_struct *wk) goto out; } - if (btintel_pcie_perform_flr(data)) + if (btintel_pcie_perform_flr(data)) { enable_work(&data->coredump_work); + enable_work(&data->hwexp_work); + enable_work(&data->fwtrigger_work); + } out: pci_dev_put(pdev); @@ -2869,8 +2936,8 @@ static int btintel_pcie_probe(struct pci_dev *pdev, if (!data->workqueue) return -ENOMEM; - data->coredump_workqueue = alloc_ordered_workqueue(KBUILD_MODNAME "_cd", 0); - if (!data->coredump_workqueue) { + data->dump_workqueue = alloc_ordered_workqueue(KBUILD_MODNAME "_cd", 0); + if (!data->dump_workqueue) { destroy_workqueue(data->workqueue); return -ENOMEM; } @@ -2879,6 +2946,8 @@ static int btintel_pcie_probe(struct pci_dev *pdev, INIT_WORK(&data->rx_work, btintel_pcie_rx_work); INIT_WORK(&data->reset_work, btintel_pcie_reset_work); INIT_WORK(&data->coredump_work, btintel_pcie_coredump_worker); + INIT_WORK(&data->hwexp_work, btintel_pcie_hwexp_worker); + INIT_WORK(&data->fwtrigger_work, btintel_pcie_fwtrigger_worker); data->boot_stage_cache = 0x00; data->img_resp_cache = 0x00; @@ -2921,7 +2990,7 @@ static int btintel_pcie_probe(struct pci_dev *pdev, /* reset device before exit */ btintel_pcie_reset_bt(data); - destroy_workqueue(data->coredump_workqueue); + destroy_workqueue(data->dump_workqueue); pci_clear_master(pdev); @@ -2940,12 +3009,14 @@ static void btintel_pcie_remove(struct pci_dev *pdev) return; } - /* Permanently block coredump triggers and drain the worker before - * tearing down. Must run before cancel_work_sync(&reset_work) so - * the disable counter stays >= 1 even after reset_work()'s + /* Permanently block all dump triggers and drain the workers before + * tearing down. Must run before disable_work_sync(&reset_work) so + * the disable counters stay >= 1 even after reset_work()'s * balanced enable_work() (counter 2 -> 1, never reaching 0). */ disable_work_sync(&data->coredump_work); + disable_work_sync(&data->hwexp_work); + disable_work_sync(&data->fwtrigger_work); /* Cancel pending reset work. Skip only when remove() is called from * within the reset work itself (PLDR device_reprobe path) to avoid @@ -2973,7 +3044,7 @@ static void btintel_pcie_remove(struct pci_dev *pdev) btintel_pcie_release_hdev(data); - destroy_workqueue(data->coredump_workqueue); + destroy_workqueue(data->dump_workqueue); destroy_workqueue(data->workqueue); btintel_pcie_free(data); @@ -2992,16 +3063,8 @@ static void btintel_pcie_coredump(struct device *dev) if (!data) return; - if (test_and_set_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags)) - return; - - data->dmp_hdr.trigger_reason = BTINTEL_PCIE_TRIGGER_REASON_USER_TRIGGER; - /* queue_work() returns false if the work is disabled (reset or - * remove in progress); clear the in-progress bit so a later - * trigger can succeed once the work is re-enabled. - */ - if (!queue_work(data->coredump_workqueue, &data->coredump_work)) - clear_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags); + btintel_pcie_queue_coredump(data, + BTINTEL_PCIE_TRIGGER_REASON_USER_TRIGGER); } #endif @@ -3138,12 +3201,8 @@ static int btintel_pcie_resume(struct device *dev) if (btintel_pcie_in_error(data) || btintel_pcie_in_device_halt(data)) { bt_dev_err(data->hdev, "Controller in error state for D0 entry"); - if (!test_and_set_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, - &data->flags)) { - data->dmp_hdr.trigger_reason = - BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT; - queue_work(data->coredump_workqueue, &data->coredump_work); - } + btintel_pcie_queue_coredump(data, + BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT); set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags); btintel_pcie_reset(data->hdev); } diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h index 7caee093e316..749369b24031 100644 --- a/drivers/bluetooth/btintel_pcie.h +++ b/drivers/bluetooth/btintel_pcie.h @@ -118,7 +118,6 @@ enum { enum { BTINTEL_PCIE_CORE_HALTED, - BTINTEL_PCIE_HWEXP_INPROGRESS, BTINTEL_PCIE_COREDUMP_INPROGRESS, BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, BTINTEL_PCIE_RECOVERY_IN_PROGRESS, @@ -466,8 +465,11 @@ struct btintel_pcie_dump_header { * @workqueue: workqueue for RX work * @rx_skb_q: SKB queue for RX packet * @rx_work: RX work struct to process the RX packet in @rx_skb_q - * @coredump_workqueue: dedicated workqueue for coredump collection - * @coredump_work: work struct for coredump trace collection + * @dump_workqueue: dedicated ordered workqueue serializing the coredump, + * hardware exception, and firmware-trigger dump workers + * @coredump_work: work struct for DRAM trace coredump collection + * @hwexp_work: work struct for hardware exception event read + * @fwtrigger_work: work struct for firmware-triggered diagnostic event read * @dma_pool: DMA pool for descriptors, index array and ci * @dma_p_addr: DMA address for pool * @dma_v_addr: address of pool @@ -516,8 +518,10 @@ struct btintel_pcie_data { struct work_struct rx_work; struct work_struct reset_work; - struct workqueue_struct *coredump_workqueue; + struct workqueue_struct *dump_workqueue; struct work_struct coredump_work; + struct work_struct hwexp_work; + struct work_struct fwtrigger_work; struct dma_pool *dma_pool; dma_addr_t dma_p_addr; From 335d7bd554a20f33acf762655cafe1089f7f5822 Mon Sep 17 00:00:00 2001 From: Pavel Zverev Date: Wed, 8 Jul 2026 01:15:49 +0300 Subject: [PATCH 1045/1433] Bluetooth: btusb: Add support for 1357:c123 Realtek 8852BE device Wiko Hi MateBook 14 Ryzen 200 laptops (DMI system-product-name "MNCA-XX", board "M1060") are equipped with an RTL8852BE Wi-Fi/BT combo chip (rtw89_8852be), whose Bluetooth radio enumerates as 1357:c123 instead of one of the already-supported 1358:c123 / 0bda:c123 identifiers, presumably due to OEM rebranding. Without a matching entry it only matches the generic USB Bluetooth class fallback, so the Realtek firmware/config (rtl8852btu_fw.bin / rtl8852btu_config.bin) is never loaded and the adapter cannot discover or connect to any device, even though hciconfig reports it as powered and scanning. Device descriptor: idVendor 0x1357 idProduct 0xc123 bcdDevice 0.00 iManufacturer 1 Realtek iProduct 2 Bluetooth Radio bDeviceClass 224 Wireless bDeviceSubClass 1 Radio Frequency bDeviceProtocol 1 Bluetooth Adding the same BTUSB_REALTEK | BTUSB_WIDEBAND_SPEECH quirk already used for 1358:c123 and 0bda:c123 fixes firmware loading and normal operation. Signed-off-by: Pavel Zverev Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index e4063131822f..518e44dcd304 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -894,6 +894,8 @@ static const struct usb_device_id quirks_table[] = { BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x0bda, 0xc123), .driver_info = BTUSB_REALTEK | BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x1357, 0xc123), .driver_info = BTUSB_REALTEK | + BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x0cb5, 0xc547), .driver_info = BTUSB_REALTEK | BTUSB_WIDEBAND_SPEECH }, From e6b4232bde0219adb11334e65fe7577a21e86458 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Wed, 8 Jul 2026 21:14:53 -0700 Subject: [PATCH 1046/1433] Bluetooth: coredump: Do not export hci_devcd_rx() and hci_devcd_timeout() Do not export both functions since they are only used internally within the bluetooth module. Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/coredump.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/net/bluetooth/coredump.c b/net/bluetooth/coredump.c index 720cb79adf96..c0f027fab583 100644 --- a/net/bluetooth/coredump.c +++ b/net/bluetooth/coredump.c @@ -390,7 +390,6 @@ void hci_devcd_rx(struct work_struct *work) hci_dev_unlock(hdev); } } -EXPORT_SYMBOL(hci_devcd_rx); void hci_devcd_timeout(struct work_struct *work) { @@ -416,7 +415,6 @@ void hci_devcd_timeout(struct work_struct *work) hci_dev_unlock(hdev); } -EXPORT_SYMBOL(hci_devcd_timeout); int hci_devcd_register(struct hci_dev *hdev, coredump_t coredump, dmp_hdr_t dmp_hdr, notify_change_t notify_change) From e0650618ee8395cdefde1686e31d3ff5c253955e Mon Sep 17 00:00:00 2001 From: Kiran K Date: Wed, 15 Jul 2026 21:47:53 +0530 Subject: [PATCH 1047/1433] Bluetooth: btintel_pcie: serialize reset_type with RECOVERY_IN_PROGRESS The reset path had two concurrency holes. Both are reachable in practice when btintel_pcie_hw_error() is invoked from the HCI rx path while another reset is being requested or is already in flight. 1. data->reset_type was a plain shared field. The hw_error path wrote it BEFORE the test_and_set_bit(RECOVERY_IN_PROGRESS) guard inside btintel_pcie_reset(), so a second hw_error could clobber the type chosen by an earlier in-flight request: CPU0 (reset_work) CPU1 (hw_error #2) dev_data->reset_type = PLDR T2: read reset_type dev_data->reset_type = FLR reset() test_and_set sees 1 -> drops, but type already clobbered The hdev->reset callback (.reset = btintel_pcie_reset, invoked via the sysfs reset attribute /sys/class/bluetooth/hciX/reset and from hci_cmd_timeout()) compounded this by not writing reset_type at all -- it inherited whatever value a previous hw_error / resume() had left, which could be PLDR. 2. btintel_pcie_dump_debug_registers() was called unconditionally at the top of hw_error(). When reset_work was already running pci_try_reset_function(), the BT MMIO window can read all-1s or trigger AER for the duration of the FLR, polluting the debug dump with no useful information. Refactor the reset path to make RECOVERY_IN_PROGRESS the sole serializer for both the type write and the work scheduling: - Replace btintel_pcie_reset(hdev) with btintel_pcie_request_reset(data, type). The helper takes the desired reset variant as a parameter and writes data->reset_type only after winning test_and_set_bit(); losers return without touching the field, so concurrent triggers can no longer clobber an in-flight reset's type. reset_work()'s read of reset_type is now ordered after the bit transition via schedule_work()'s memory barrier. - Add a thin btintel_pcie_hci_reset() wrapper for the hdev->reset callback (invoked via the sysfs reset attribute /sys/class/bluetooth/hciX/reset and from hci_cmd_timeout()) that always requests FLR explicitly, so these paths no longer inherit stale state from prior error events. - Add an early test_bit(RECOVERY_IN_PROGRESS) gate at the top of hw_error() so dump_debug_registers() and the recovery-counter bookkeeping are skipped when a reset is already in flight; the authoritative test_and_set lives in request_reset() and races cleanly against any caller that passes the optimistic check. - Convert the two resume() reset sites (FREEZE/HIBERNATE and the D0-error path) to request_reset(data, FLR), removing the redundant manual reset_type writes. Assisted-by: GitHub-Copilot:claude-4.7-opus Signed-off-by: Kiran K Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 75 ++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 24 deletions(-) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 013568197a39..2e28847263ab 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -2550,8 +2550,6 @@ static void btintel_pcie_inc_recovery_count(struct pci_dev *pdev, } } -static void btintel_pcie_reset(struct hci_dev *hdev); - static int btintel_pcie_acpi_reset_method(struct btintel_pcie_data *data) { union acpi_object *obj, argv4; @@ -2749,56 +2747,86 @@ static void btintel_pcie_reset_work(struct work_struct *wk) pci_unlock_rescan_remove(); } -static void btintel_pcie_reset(struct hci_dev *hdev) +/* Schedule a device reset of the requested type. + * + * BTINTEL_PCIE_RECOVERY_IN_PROGRESS serializes all reset requesters + * (sysfs reset attribute, hci_cmd_timeout(), hw_error, resume error + * path, etc.) so that: + * + * - dev_data->reset_type is written by exactly one caller (the + * thread that wins test_and_set_bit), eliminating the race where + * a second hw_error could clobber an already-scheduled reset's + * type; + * - the write happens AFTER the bit is set, so reset_work observes + * it through schedule_work()'s memory ordering; + * - losers return without touching reset_type or scheduling the + * work, so concurrent triggers are silently coalesced into the + * in-flight one (whose recovery will reinitialize the device + * regardless of the dropped trigger's variant). + * + * The bit is cleared only by .remove() / re-probe via fresh devm + * allocation, which is the intended one-shot semantics: a reset + * tears down and re-probes 'data', so there is no "in-flight" + * reset to follow up after device_reprobe() succeeds. + */ +static void btintel_pcie_request_reset(struct btintel_pcie_data *data, + enum btintel_pcie_reset_type type) { - struct btintel_pcie_data *data; - - data = hci_get_drvdata(hdev); - if (!test_bit(BTINTEL_PCIE_SETUP_DONE, &data->flags)) return; if (test_and_set_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags)) return; + data->reset_type = type; + pci_dev_get(data->pdev); schedule_work(&data->reset_work); } +static void btintel_pcie_hci_reset(struct hci_dev *hdev) +{ + struct btintel_pcie_data *data = hci_get_drvdata(hdev); + + btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); +} + static void btintel_pcie_hw_error(struct hci_dev *hdev, u8 code) { - struct btintel_pcie_dev_recovery *data; + struct btintel_pcie_dev_recovery *rec; struct btintel_pcie_data *dev_data = hci_get_drvdata(hdev); struct pci_dev *pdev = dev_data->pdev; + enum btintel_pcie_reset_type type; time64_t retry_window; + if (test_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &dev_data->flags)) + return; + btintel_pcie_dump_debug_registers(hdev); - data = btintel_pcie_get_recovery(pdev, &hdev->dev); - if (!data) + rec = btintel_pcie_get_recovery(pdev, &hdev->dev); + if (!rec) return; - if (code == 0x13) - dev_data->reset_type = BTINTEL_PCIE_IOSF_PRR_PLDR; - else - dev_data->reset_type = BTINTEL_PCIE_IOSF_PRR_FLR; + type = (code == 0x13) ? BTINTEL_PCIE_IOSF_PRR_PLDR + : BTINTEL_PCIE_IOSF_PRR_FLR; bt_dev_err(hdev, "Encountered exception err:0x%x triggering: %s", code, - dev_data->reset_type == BTINTEL_PCIE_IOSF_PRR_PLDR ? "PLDR" : "FLR"); - retry_window = ktime_get_boottime_seconds() - data->last_error; + type == BTINTEL_PCIE_IOSF_PRR_PLDR ? "PLDR" : "FLR"); + retry_window = ktime_get_boottime_seconds() - rec->last_error; if (retry_window < BTINTEL_PCIE_RESET_WINDOW_SECS && - data->count >= BTINTEL_PCIE_FLR_MAX_RETRY) { + rec->count >= BTINTEL_PCIE_FLR_MAX_RETRY) { bt_dev_err(hdev, "Exhausted maximum: %d recovery attempts: %d", - BTINTEL_PCIE_FLR_MAX_RETRY, data->count); + BTINTEL_PCIE_FLR_MAX_RETRY, rec->count); bt_dev_dbg(hdev, "Boot time: %lld seconds", ktime_get_boottime_seconds()); bt_dev_dbg(hdev, "last error at: %lld seconds", - data->last_error); + rec->last_error); return; } btintel_pcie_inc_recovery_count(pdev, &hdev->dev); - btintel_pcie_reset(hdev); + btintel_pcie_request_reset(dev_data, type); } static bool btintel_pcie_wakeup(struct hci_dev *hdev) @@ -2888,7 +2916,7 @@ static int btintel_pcie_setup_hdev(struct btintel_pcie_data *data) hdev->hw_error = btintel_pcie_hw_error; hdev->set_diag = btintel_set_diag; hdev->set_bdaddr = btintel_set_bdaddr; - hdev->reset = btintel_pcie_reset; + hdev->reset = btintel_pcie_hci_reset; hdev->wakeup = btintel_pcie_wakeup; hdev->hci_drv = &btintel_pcie_hci_drv; @@ -3176,8 +3204,7 @@ static int btintel_pcie_resume(struct device *dev) if (data->pm_sx_event == PM_EVENT_FREEZE || data->pm_sx_event == PM_EVENT_HIBERNATE) { set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags); - data->reset_type = BTINTEL_PCIE_IOSF_PRR_FLR; - btintel_pcie_reset(data->hdev); + btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); return 0; } @@ -3204,7 +3231,7 @@ static int btintel_pcie_resume(struct device *dev) btintel_pcie_queue_coredump(data, BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT); set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags); - btintel_pcie_reset(data->hdev); + btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); } return err; } From 6afadcff79b157a4770c4e987a6005bff40a670b Mon Sep 17 00:00:00 2001 From: Li Qiang Date: Thu, 16 Jul 2026 16:47:28 +0800 Subject: [PATCH 1048/1433] Bluetooth: bfusb: validate received block boundaries The USB receive path trusts the block header to contain the required number of bytes and passes it to the reassembly routine. The routine also trusts a malformed HCI packet type and can append more data than the skb allocated from the advertised packet length. A malformed USB transfer can therefore cause out-of-bounds reads or an skb tail overwrite. Validate block header availability, declared block size, packet type, and reassembly tailroom. Drop the partial frame on an invalid block. Signed-off-by: Li Qiang Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/bfusb.c | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/drivers/bluetooth/bfusb.c b/drivers/bluetooth/bfusb.c index 8df310983bf6..d31d797639b5 100644 --- a/drivers/bluetooth/bfusb.c +++ b/drivers/bluetooth/bfusb.c @@ -301,6 +301,11 @@ static inline int bfusb_recv_block(struct bfusb_data *data, int hdr, unsigned ch return -EILSEQ; } break; + + default: + bt_dev_err(data->hdev, "unknown packet type 0x%02x", + pkt_type); + return -EILSEQ; } skb = bt_skb_alloc(pkt_len, GFP_ATOMIC); @@ -319,6 +324,13 @@ static inline int bfusb_recv_block(struct bfusb_data *data, int hdr, unsigned ch } } + if (len > skb_tailroom(data->reassembly)) { + bt_dev_err(data->hdev, "block exceeds packet length"); + kfree_skb(data->reassembly); + data->reassembly = NULL; + return -EILSEQ; + } + if (len > 0) skb_put_data(data->reassembly, buf, len); @@ -353,6 +365,13 @@ static void bfusb_rx_complete(struct urb *urb) skb_put(skb, count); while (count) { + if (count < 2) { + bt_dev_err(data->hdev, "short block header"); + kfree_skb(data->reassembly); + data->reassembly = NULL; + break; + } + hdr = buf[0] | (buf[1] << 8); if (hdr & 0x4000) { @@ -360,16 +379,28 @@ static void bfusb_rx_complete(struct urb *urb) count -= 2; buf += 2; } else { + if (count < 3) { + bt_dev_err(data->hdev, "short block header"); + kfree_skb(data->reassembly); + data->reassembly = NULL; + break; + } + len = (buf[2] == 0) ? 256 : buf[2]; count -= 3; buf += 3; } - if (count < len) + if (count < len) { bt_dev_err(data->hdev, "block extends over URB buffer ranges"); + kfree_skb(data->reassembly); + data->reassembly = NULL; + break; + } - if ((hdr & 0xe1) == 0xc1) - bfusb_recv_block(data, hdr, buf, len); + if ((hdr & 0xe1) == 0xc1 && + bfusb_recv_block(data, hdr, buf, len) < 0) + data->hdev->stat.err_rx++; count -= len; buf += len; From 65be90af275675a65deed133ef67c81168e12bc9 Mon Sep 17 00:00:00 2001 From: Li Qiang Date: Thu, 16 Jul 2026 16:47:29 +0800 Subject: [PATCH 1049/1433] Bluetooth: btmrvl: validate event packet lengths The Marvell event handlers access the HCI event header, command complete payload, and driver-specific event header before validating that the received skb contains them. A truncated event can consequently cause an out-of-bounds read. Validate each header and the command-complete payload length before dereferencing the corresponding fields. Signed-off-by: Li Qiang Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmrvl_main.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/bluetooth/btmrvl_main.c b/drivers/bluetooth/btmrvl_main.c index d6f0ad0b4b6e..aaf1614ccfd7 100644 --- a/drivers/bluetooth/btmrvl_main.c +++ b/drivers/bluetooth/btmrvl_main.c @@ -43,10 +43,17 @@ bool btmrvl_check_evtpkt(struct btmrvl_private *priv, struct sk_buff *skb) { struct hci_event_hdr *hdr = (void *) skb->data; + if (skb->len < sizeof(*hdr)) + return true; + if (hdr->evt == HCI_EV_CMD_COMPLETE) { struct hci_ev_cmd_complete *ec; u16 opcode; + if (hdr->plen < sizeof(*ec) || + skb->len < HCI_EVENT_HDR_SIZE + sizeof(*ec)) + return true; + ec = (void *) (skb->data + HCI_EVENT_HDR_SIZE); opcode = __le16_to_cpu(ec->opcode); @@ -74,6 +81,9 @@ int btmrvl_process_event(struct btmrvl_private *priv, struct sk_buff *skb) struct btmrvl_event *event; int ret = 0; + if (skb->len < sizeof(*event)) + return -EINVAL; + event = (struct btmrvl_event *) skb->data; if (event->ec != 0xff) { BT_DBG("Not Marvell Event=%x", event->ec); From ceea75ad8925425ee6520ead964b55888fe871ec Mon Sep 17 00:00:00 2001 From: Li Qiang Date: Thu, 16 Jul 2026 16:47:30 +0800 Subject: [PATCH 1050/1433] Bluetooth: hci_bcsp: validate received packet lengths The BCSP transmit path reads an HCI command header when an extension packet has only been tested for a nonzero length. Its LE configuration packet handler also indexes bytes through offset seven without a length check. Validate the complete command and LE configuration packet headers before accessing their fields. Signed-off-by: Li Qiang Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/hci_bcsp.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/hci_bcsp.c b/drivers/bluetooth/hci_bcsp.c index db56eead27ce..0323db21c428 100644 --- a/drivers/bluetooth/hci_bcsp.c +++ b/drivers/bluetooth/hci_bcsp.c @@ -194,7 +194,7 @@ static struct sk_buff *bcsp_prepare_pkt(struct bcsp_struct *bcsp, u8 *data, return NULL; } - if (hciextn && chan == 5) { + if (hciextn && chan == 5 && len > HCI_COMMAND_HDR_SIZE) { __le16 opcode = ((struct hci_command_hdr *)data)->opcode; /* Vendor specific commands */ @@ -402,6 +402,9 @@ static void bcsp_handle_le_pkt(struct hci_uart *hu) u8 sync_pkt[4] = { 0xda, 0xdc, 0xed, 0xed }; /* spot "conf" pkts and reply with a "conf rsp" pkt */ + if (bcsp->rx_skb->len < 8) + return; + if (bcsp->rx_skb->data[1] >> 4 == 4 && bcsp->rx_skb->data[2] == 0 && !memcmp(&bcsp->rx_skb->data[4], conf_pkt, 4)) { struct sk_buff *nskb = alloc_skb(4, GFP_ATOMIC); From 0fdb6ca821170c0c80a42b7dcb34cc75b55c2f1b Mon Sep 17 00:00:00 2001 From: Li Qiang Date: Thu, 16 Jul 2026 16:47:31 +0800 Subject: [PATCH 1051/1433] Bluetooth: hci_ldisc: reject invalid tty write lengths The HCI UART write worker assumes that a tty write callback returns a value in the range from zero through the skb length. A negative value or a value larger than the skb length is passed to accounting and skb_pull, which can corrupt skb state. Treat either return value as a transmit error and discard the skb. Signed-off-by: Li Qiang Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/hci_ldisc.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c index 2ad42c3bbaac..46dfbe6f1c2e 100644 --- a/drivers/bluetooth/hci_ldisc.c +++ b/drivers/bluetooth/hci_ldisc.c @@ -163,6 +163,12 @@ static void hci_uart_write_work(struct work_struct *work) set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); len = tty->ops->write(tty, skb->data, skb->len); + if (len < 0 || len > skb->len) { + hdev->stat.err_tx++; + kfree_skb(skb); + continue; + } + hdev->stat.byte_tx += len; skb_pull(skb, len); From 47386974699370c015feb3fd5c1e7b33f78fa6ae Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Tue, 21 Jul 2026 03:14:40 -0700 Subject: [PATCH 1052/1433] Bluetooth: hci_qca: Replace HCI_VENDOR_PKT usage with HCI_EV_VENDOR The macros below have different meanings even though they share the same value 0xff: HCI_VENDOR_PKT: HCI packet indicator or type HCI_EV_VENDOR: event code of a VSE This usage of HCI_VENDOR_PKT is wrongly checking an event code. Fix by using HCI_EV_VENDOR for event code. Also fix warning "CHECK: Unnecessary parentheses around comparison" given by checkpatch.pl. Acked-by: Bartosz Golaszewski Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/hci_qca.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c index 1222f97800f4..e6d107f67759 100644 --- a/drivers/bluetooth/hci_qca.c +++ b/drivers/bluetooth/hci_qca.c @@ -1239,8 +1239,8 @@ static int qca_recv_event(struct hci_dev *hdev, struct sk_buff *skb) * received we store dump into a file before closing hci. This * dump will help in triaging the issues. */ - if ((skb->data[0] == HCI_VENDOR_PKT) && - (get_unaligned_be16(skb->data + 2) == QCA_SSR_DUMP_HANDLE)) + if (skb->data[0] == HCI_EV_VENDOR && + get_unaligned_be16(skb->data + 2) == QCA_SSR_DUMP_HANDLE) return qca_controller_memdump_event(hdev, skb); return hci_recv_frame(hdev, skb); From 1341d23d053323ac8c6936b69c1d085056cae69e Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Tue, 21 Jul 2026 03:14:41 -0700 Subject: [PATCH 1053/1433] Bluetooth: btusb: QCA: Replace HCI_VENDOR_PKT usages with HCI_EV_VENDOR The macros below have different meanings even though they share the same value 0xff: HCI_VENDOR_PKT: HCI packet indicator or type HCI_EV_VENDOR: event code of a VSE These usages of HCI_VENDOR_PKT are wrongly checking an event code. Fix by using HCI_EV_VENDOR for event code. Also fix warning "CHECK: Unnecessary parentheses around comparison" given by checkpatch.pl. Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 518e44dcd304..e28c5daeba3f 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -3278,7 +3278,7 @@ static bool acl_pkt_is_dump_qca(struct hci_dev *hdev, struct sk_buff *skb) goto out; event_hdr = skb_pull_data(clone, sizeof(*event_hdr)); - if (!event_hdr || (event_hdr->evt != HCI_VENDOR_PKT)) + if (!event_hdr || event_hdr->evt != HCI_EV_VENDOR) goto out; dump_hdr = skb_pull_data(clone, sizeof(*dump_hdr)); @@ -3304,7 +3304,7 @@ static bool evt_pkt_is_dump_qca(struct hci_dev *hdev, struct sk_buff *skb) return false; event_hdr = skb_pull_data(clone, sizeof(*event_hdr)); - if (!event_hdr || (event_hdr->evt != HCI_VENDOR_PKT)) + if (!event_hdr || event_hdr->evt != HCI_EV_VENDOR) goto out; dump_hdr = skb_pull_data(clone, sizeof(*dump_hdr)); From a68f8bdc00e67fbb1763538d0101e2a5467237c0 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Tue, 21 Jul 2026 03:14:42 -0700 Subject: [PATCH 1054/1433] Bluetooth: btusb: Realtek: Replace HCI_VENDOR_PKT usage with HCI_EV_VENDOR The macros below have different meanings even though they share the same value 0xff: HCI_VENDOR_PKT: HCI packet indicator or type HCI_EV_VENDOR: event code of a VSE This usage of HCI_VENDOR_PKT is wrongly checking an event code. Fix by using HCI_EV_VENDOR for event code. Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index e28c5daeba3f..e4c451e8f268 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -2800,7 +2800,7 @@ static int btusb_setup_realtek(struct hci_dev *hdev) static int btusb_recv_event_realtek(struct hci_dev *hdev, struct sk_buff *skb) { if (skb->len >= HCI_EVENT_HDR_SIZE + 1 && - skb->data[0] == HCI_VENDOR_PKT && + skb->data[0] == HCI_EV_VENDOR && skb->data[2] == RTK_SUB_EVENT_CODE_COREDUMP) { struct rtk_dev_coredump_hdr hdr = { .code = RTK_DEVCOREDUMP_CODE_MEMDUMP, From 874ca6bdde050fde1fcfae9e8d860c95ac959f2a Mon Sep 17 00:00:00 2001 From: Chen Changcheng Date: Wed, 22 Jul 2026 15:43:15 +0800 Subject: [PATCH 1055/1433] Bluetooth: btintel_pcie: Remove unreachable break after goto In the switch-case block for hardware variant detection, the default case has an unreachable 'break' statement following 'goto exit_error'. Remove the dead code. Signed-off-by: Chen Changcheng Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 2e28847263ab..ef42b8d11d4d 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -2425,7 +2425,6 @@ static int btintel_pcie_setup_internal(struct hci_dev *hdev) INTEL_HW_VARIANT(ver_tlv.cnvi_bt)); err = -EINVAL; goto exit_error; - break; } data->dmp_hdr.cnvi_top = ver_tlv.cnvi_top; From 290a3644609526f7ac12529cd1618042fa5a32db Mon Sep 17 00:00:00 2001 From: Chen Changcheng Date: Wed, 22 Jul 2026 16:31:36 +0800 Subject: [PATCH 1056/1433] Bluetooth: btrsi: Move set_bt_context after successful HCI registration In rsi_hci_attach(), ops->set_bt_context() stores the newly allocated h_adapter into common->bt_adapter before hci_alloc_dev() and hci_register_dev() are called. If either of these fails, h_adapter is freed but common->bt_adapter remains a non-NULL dangling pointer. This causes a deterministically reachable use-after-free when the device operates in a BT+WiFi coexistence mode and CONFIG_RSI_COEX is enabled. The following software-only trigger paths exist: 1. SDIO driver .remove (rsi_disconnect) 2. USB driver .disconnect (rsi_disconnect) 3. SDIO driver .shutdown (rsi_shutdown) 4. Hibernation .freeze (rsi_freeze) All four paths check: if (IS_ENABLED(CONFIG_RSI_COEX) && coex_mode > 1 && bt_adapter) rsi_bt_ops.detach(bt_adapter); // use-after-free coex_mode is set during rsi_91x_init(), before rsi_hci_attach() is called, and is not cleared on attach failure. Since set_bt_context() already wrote bt_adapter before the failure, the deinit paths see a non-NULL dangling pointer and proceed to detach it. Fix this by moving set_bt_context() after hci_register_dev() succeeds. On failure paths bt_adapter stays NULL, and the deinit callers correctly skip the detach call. Signed-off-by: Chen Changcheng Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btrsi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btrsi.c b/drivers/bluetooth/btrsi.c index 59ad0b9b14c3..3f802b7c83f7 100644 --- a/drivers/bluetooth/btrsi.c +++ b/drivers/bluetooth/btrsi.c @@ -107,7 +107,6 @@ static int rsi_hci_attach(void *priv, struct rsi_proto_ops *ops) return -ENOMEM; h_adapter->priv = priv; - ops->set_bt_context(priv, h_adapter); h_adapter->proto_ops = ops; hdev = hci_alloc_dev(); @@ -136,6 +135,8 @@ static int rsi_hci_attach(void *priv, struct rsi_proto_ops *ops) goto err; } + ops->set_bt_context(priv, h_adapter); + return 0; err: h_adapter->hdev = NULL; From 6ec1896b5331563fedf3f01014aba9c9e35d7f29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=20Kasprzakov=C3=A1?= Date: Wed, 22 Jul 2026 15:08:49 +0200 Subject: [PATCH 1057/1433] Bluetooth: fix BT dependency for submodules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The modules rfcomm (BT_RFCOMM), bnep (BT_BNEP), hidp (BT_HIDP), and bluetooth_6lowpan (BT_6LOWPAN) are dependent on the bluetooth module (BT, tristate) only transitively through the boolean BT_BREDR for the first three and through the boolean BT_LE for the bluetooth_6lowpan. Therefore, the modules can be selected as built-in even if the BT=m. The combination of BT=m and =y for the said modules leads to the kernel build system silently ignoring those modules, without ever compiling them as built-in or as loadable modules. Add BT as a direct dependency to the Kconfig of rfcomm, bnep, hidp, and bluetooth_6lowpan. The modules set to =y when BT=m will default to =m, rather then getting silently ignored by the build system. Signed-off-by: Iva Kasprzaková Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/Kconfig | 2 +- net/bluetooth/bnep/Kconfig | 2 +- net/bluetooth/hidp/Kconfig | 2 +- net/bluetooth/rfcomm/Kconfig | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/net/bluetooth/Kconfig b/net/bluetooth/Kconfig index d250e94e90eb..1cda01614efe 100644 --- a/net/bluetooth/Kconfig +++ b/net/bluetooth/Kconfig @@ -76,7 +76,7 @@ config BT_LE_L2CAP_ECRED config BT_6LOWPAN tristate "Bluetooth 6LoWPAN support" - depends on BT_LE && 6LOWPAN + depends on BT && BT_LE && 6LOWPAN help IPv6 compression over Bluetooth Low Energy. diff --git a/net/bluetooth/bnep/Kconfig b/net/bluetooth/bnep/Kconfig index aac02b5b0d17..f8087e2d2c00 100644 --- a/net/bluetooth/bnep/Kconfig +++ b/net/bluetooth/bnep/Kconfig @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only config BT_BNEP tristate "BNEP protocol support" - depends on BT_BREDR + depends on BT && BT_BREDR select CRC32 help BNEP (Bluetooth Network Encapsulation Protocol) is Ethernet diff --git a/net/bluetooth/hidp/Kconfig b/net/bluetooth/hidp/Kconfig index e08aae35351a..ba52c7296f18 100644 --- a/net/bluetooth/hidp/Kconfig +++ b/net/bluetooth/hidp/Kconfig @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only config BT_HIDP tristate "HIDP protocol support" - depends on BT_BREDR && HID + depends on BT && BT_BREDR && HID help HIDP (Human Interface Device Protocol) is a transport layer for HID reports. HIDP is required for the Bluetooth Human diff --git a/net/bluetooth/rfcomm/Kconfig b/net/bluetooth/rfcomm/Kconfig index 9b9953ebf4c0..e7af2d565cea 100644 --- a/net/bluetooth/rfcomm/Kconfig +++ b/net/bluetooth/rfcomm/Kconfig @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only config BT_RFCOMM tristate "RFCOMM protocol support" - depends on BT_BREDR + depends on BT && BT_BREDR help RFCOMM provides connection oriented stream transport. RFCOMM support is required for Dialup Networking, OBEX and other Bluetooth From 3914dc880317a7fb77f0b18907f3b29913884340 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Sat, 25 Jul 2026 01:54:41 -0700 Subject: [PATCH 1058/1433] Bluetooth: coredump: Introduce and apply hci_devcd_state_name() Introduce hci_devcd_state_name() to describe the devcoredump state by a string name instead of a plain number, for several reasons: 1) Applying it in coredump.c makes the devcoredump state in log messages more readable than a plain number. 2) Transport drivers may need to show the devcoredump state name too. 3) In future, the universal state name could be notified to userspace via uevent, allowing a universal application (e.g. a daemon) to be developed to save the coredump, which is otherwise discarded by the device coredump core after 5 minutes (DEVCD_TIMEOUT); see nxp_coredump_notify(). Also drop a trailing space from two bt_dev_dbg() format strings while applying it in coredump.c. Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/coredump.h | 7 +++++ net/bluetooth/coredump.c | 45 +++++++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/include/net/bluetooth/coredump.h b/include/net/bluetooth/coredump.h index 72f51b587a04..ab85a6adfffd 100644 --- a/include/net/bluetooth/coredump.h +++ b/include/net/bluetooth/coredump.h @@ -60,6 +60,8 @@ struct hci_devcoredump { #ifdef CONFIG_DEV_COREDUMP +const char *hci_devcd_state_name(enum devcoredump_state state); + void hci_devcd_reset(struct hci_dev *hdev); void hci_devcd_rx(struct work_struct *work); void hci_devcd_timeout(struct work_struct *work); @@ -74,6 +76,11 @@ int hci_devcd_abort(struct hci_dev *hdev); #else +static inline const char *hci_devcd_state_name(enum devcoredump_state state) +{ + return ""; +} + static inline void hci_devcd_reset(struct hci_dev *hdev) {} static inline void hci_devcd_rx(struct work_struct *work) {} static inline void hci_devcd_timeout(struct work_struct *work) {} diff --git a/net/bluetooth/coredump.c b/net/bluetooth/coredump.c index c0f027fab583..913bbba559f8 100644 --- a/net/bluetooth/coredump.c +++ b/net/bluetooth/coredump.c @@ -30,8 +30,9 @@ struct hci_devcoredump_skb_pattern { #define DBG_UNEXPECTED_STATE() \ bt_dev_dbg(hdev, \ - "Unexpected packet (%d) for state (%d). ", \ - hci_dmp_cb(skb)->pkt_type, hdev->dump.state) + "Unexpected packet (%d) for state %s.", \ + hci_dmp_cb(skb)->pkt_type, \ + hci_devcd_state_name(hdev->dump.state)) #define MAX_DEVCOREDUMP_HDR_SIZE 512 /* bytes */ @@ -50,8 +51,9 @@ static int hci_devcd_update_hdr_state(char *buf, size_t size, int state) /* Call with hci_dev_lock only. */ static int hci_devcd_update_state(struct hci_dev *hdev, int state) { - bt_dev_dbg(hdev, "Updating devcoredump state from %d to %d.", - hdev->dump.state, state); + bt_dev_dbg(hdev, "Updating devcoredump state from %s to %s.", + hci_devcd_state_name(hdev->dump.state), + hci_devcd_state_name(state)); hdev->dump.state = state; @@ -245,7 +247,7 @@ static void hci_devcd_dump(struct hci_dev *hdev) struct sk_buff *skb; u32 size; - bt_dev_dbg(hdev, "state %d", hdev->dump.state); + bt_dev_dbg(hdev, "state %s", hci_devcd_state_name(hdev->dump.state)); size = hdev->dump.tail - hdev->dump.head; @@ -368,8 +370,9 @@ void hci_devcd_rx(struct work_struct *work) break; default: - bt_dev_dbg(hdev, "Unknown packet (%d) for state (%d). ", - hci_dmp_cb(skb)->pkt_type, hdev->dump.state); + bt_dev_dbg(hdev, "Unknown packet (%d) for state %s.", + hci_dmp_cb(skb)->pkt_type, + hci_devcd_state_name(hdev->dump.state)); break; } @@ -549,3 +552,31 @@ int hci_devcd_abort(struct hci_dev *hdev) return 0; } EXPORT_SYMBOL(hci_devcd_abort); + +const char *hci_devcd_state_name(enum devcoredump_state state) +{ + const char *state_name = "Unknown"; + + switch (state) { + case HCI_DEVCOREDUMP_IDLE: + state_name = "IDLE"; + break; + case HCI_DEVCOREDUMP_ACTIVE: + state_name = "ACTIVE"; + break; + case HCI_DEVCOREDUMP_DONE: + state_name = "DONE"; + break; + case HCI_DEVCOREDUMP_ABORT: + state_name = "ABORT"; + break; + case HCI_DEVCOREDUMP_TIMEOUT: + state_name = "TIMEOUT"; + break; + default: + break; + } + + return state_name; +} +EXPORT_SYMBOL(hci_devcd_state_name); From d6d15018c8e1527841e39a6a80c60997e594ca70 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Sat, 25 Jul 2026 01:54:42 -0700 Subject: [PATCH 1059/1433] Bluetooth: btusb: Make btusb_recv_{event,acl}() take struct hci_dev * Both helpers currently take struct btusb_data *, which is private to btusb.c, as parameter type as below: int btusb_recv_event(struct btusb_data *data, struct sk_buff *skb) int btusb_recv_acl(struct btusb_data *data, struct sk_buff *skb) To allow vendor USB-transport-specific source files to share them as well, change the type to struct hci_dev *. Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index e4c451e8f268..03048e7eef21 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -1252,14 +1252,16 @@ static inline void btusb_free_frags(struct btusb_data *data) spin_unlock_irqrestore(&data->rxlock, flags); } -static int btusb_recv_event(struct btusb_data *data, struct sk_buff *skb) +static int btusb_recv_event(struct hci_dev *hdev, struct sk_buff *skb) { + struct btusb_data *data = hci_get_drvdata(hdev); + if (data->intr_interval) { /* Trigger dequeue immediately if an event is received */ schedule_delayed_work(&data->rx_work, 0); } - return data->recv_event(data->hdev, skb); + return data->recv_event(hdev, skb); } static int btusb_recv_intr(struct btusb_data *data, void *buffer, int count) @@ -1319,7 +1321,7 @@ static int btusb_recv_intr(struct btusb_data *data, void *buffer, int count) } /* Complete frame */ - btusb_recv_event(data, skb); + btusb_recv_event(data->hdev, skb); skb = NULL; } } @@ -1330,13 +1332,15 @@ static int btusb_recv_intr(struct btusb_data *data, void *buffer, int count) return err; } -static int btusb_recv_acl(struct btusb_data *data, struct sk_buff *skb) +static int btusb_recv_acl(struct hci_dev *hdev, struct sk_buff *skb) { + struct btusb_data *data = hci_get_drvdata(hdev); + /* Only queue ACL packet if intr_interval is set as it means * force_poll_sync has been enabled. */ if (!data->intr_interval) - return data->recv_acl(data->hdev, skb); + return data->recv_acl(hdev, skb); skb_queue_tail(&data->acl_q, skb); schedule_delayed_work(&data->rx_work, data->intr_interval); @@ -1391,7 +1395,7 @@ static int btusb_recv_bulk(struct btusb_data *data, void *buffer, int count) if (!hci_skb_expect(skb)) { /* Complete frame */ - btusb_recv_acl(data, skb); + btusb_recv_acl(data->hdev, skb); skb = NULL; } } From 33971338ef4c5e596bf402b63fb61e3aae1ec6a2 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Sat, 25 Jul 2026 01:54:43 -0700 Subject: [PATCH 1060/1433] Bluetooth: btusb: Add a simple static btusb_prepare_reset() Add btusb_prepare_reset() to do cleanup before a reset, and apply it to btusb_mtk_reset() as well. Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 03048e7eef21..b06784ad955c 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -2074,6 +2074,14 @@ static void btusb_stop_traffic(struct btusb_data *data) usb_kill_anchored_urbs(&data->ctrl_anchor); } +static void btusb_prepare_reset(struct hci_dev *hdev) +{ + struct btusb_data *data = hci_get_drvdata(hdev); + + btusb_stop_traffic(data); + usb_kill_anchored_urbs(&data->tx_anchor); +} + static int btusb_close(struct hci_dev *hdev) { struct btusb_data *data = hci_get_drvdata(hdev); @@ -2918,8 +2926,7 @@ static int btusb_mtk_reset(struct hci_dev *hdev, void *rst_data) /* Release MediaTek ISO data interface */ btusb_mtk_release_iso_intf(hdev); - btusb_stop_traffic(data); - usb_kill_anchored_urbs(&data->tx_anchor); + btusb_prepare_reset(hdev); /* Toggle the hard reset line. The MediaTek device is going to * yank itself off the USB and then replug. The cleanup is handled From 22fd0fbcf720a99acdd2bef66d57f4f3c95ed9cb Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Sat, 25 Jul 2026 01:54:44 -0700 Subject: [PATCH 1061/1433] Bluetooth: hci: Introduce hci_acl_handle() and hci_acl_dlen() helpers Introduce both helpers for ACL packet since: both core and transport drivers extract the handle and data length from its header in several places. Both will be used later. Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/hci.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index 50f0eef71fb1..d557bdf9ae57 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -3407,6 +3407,16 @@ static inline struct hci_iso_hdr *hci_iso_hdr(const struct sk_buff *skb) #define hci_handle(h) (h & 0x0fff) #define hci_flags(h) (h >> 12) +static inline __u16 hci_acl_handle(const struct sk_buff *skb) +{ + return hci_handle(__le16_to_cpu(hci_acl_hdr(skb)->handle)); +} + +static inline __u16 hci_acl_dlen(const struct sk_buff *skb) +{ + return __le16_to_cpu(hci_acl_hdr(skb)->dlen); +} + /* ISO handle and flags pack/unpack */ #define hci_iso_flags_pb(f) (f & 0x0003) #define hci_iso_flags_ts(f) ((f >> 2) & 0x0001) From b1ffea37f7349ca12166812669a554d1191d26cc Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Sat, 25 Jul 2026 01:54:45 -0700 Subject: [PATCH 1062/1433] Bluetooth: hci_core: Simplify hci_recv_frame() by hci_acl_handle() Simplify hci_recv_frame() by using hci_acl_handle() instead of: __u16 handle = __le16_to_cpu(hci_acl_hdr(skb)->handle); ... hci_handle(handle) ... Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_core.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index d1e78ae7728e..9d5adf882509 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -2904,10 +2904,9 @@ int hci_recv_frame(struct hci_dev *hdev, struct sk_buff *skb) if (hci_conn_num(hdev, CIS_LINK) || hci_conn_num(hdev, BIS_LINK) || hci_conn_num(hdev, PA_LINK)) { - __u16 handle = __le16_to_cpu(hci_acl_hdr(skb)->handle); __u8 type; - type = hci_conn_lookup_type(hdev, hci_handle(handle)); + type = hci_conn_lookup_type(hdev, hci_acl_handle(skb)); if (type == CIS_LINK || type == BIS_LINK || type == PA_LINK) hci_skb_pkt_type(skb) = HCI_ISODATA_PKT; From 16ca59d36bce02de9ad57b939cd6741ca3cd91e9 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Sat, 25 Jul 2026 01:54:46 -0700 Subject: [PATCH 1063/1433] Bluetooth: btusb: Simplify btusb_recv_bulk() by hci_acl_dlen() Simplify btusb_recv_bulk() by using hci_acl_dlen() instead of: __le16 dlen = hci_acl_hdr(skb)->dlen; ... __le16_to_cpu(dlen) ... Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index b06784ad955c..be82bbbc1b5c 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -1379,10 +1379,8 @@ static int btusb_recv_bulk(struct btusb_data *data, void *buffer, int count) hci_skb_expect(skb) -= len; if (skb->len == HCI_ACL_HDR_SIZE) { - __le16 dlen = hci_acl_hdr(skb)->dlen; - /* Complete ACL header */ - hci_skb_expect(skb) = __le16_to_cpu(dlen); + hci_skb_expect(skb) = hci_acl_dlen(skb); if (skb_tailroom(skb) < hci_skb_expect(skb)) { kfree_skb(skb); From 6e53a37acd22469a785bfc4a050c4ff7f73c43c8 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Sat, 25 Jul 2026 01:54:47 -0700 Subject: [PATCH 1064/1433] Bluetooth: btintel: Simplify btintel_classify_pkt_type() by hci_acl_handle() Simplify btintel_classify_pkt_type() by using hci_acl_handle() instead of: __u16 handle = __le16_to_cpu(hci_acl_hdr(skb)->handle); ... hci_handle(handle) ... Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index bf567b7c5f00..680f96c188d4 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -2745,9 +2745,7 @@ static u8 btintel_classify_pkt_type(struct hci_dev *hdev, struct sk_buff *skb) * based on their connection handle value range. */ if (iso_capable(hdev) && hci_skb_pkt_type(skb) == HCI_ACLDATA_PKT) { - __u16 handle = __le16_to_cpu(hci_acl_hdr(skb)->handle); - - if (hci_handle(handle) >= BTINTEL_ISODATA_HANDLE_BASE) + if (hci_acl_handle(skb) >= BTINTEL_ISODATA_HANDLE_BASE) return HCI_ISODATA_PKT; } From 760163572bb0ab79857c11aafb6eb33e697a22a0 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Sat, 25 Jul 2026 01:54:48 -0700 Subject: [PATCH 1065/1433] Bluetooth: btmrvl_sdio: Do not free HCI_VENDOR_PKT frame by hci_recv_frame() For a HCI_VENDOR_PKT frame, hci_recv_frame() does not accept it and will kfree_skb() it directly. But btmrvl_sdio_card_to_host() is still calling hci_recv_frame() for the frame. Fix by freeing it with kfree_skb() directly. Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmrvl_sdio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/bluetooth/btmrvl_sdio.c b/drivers/bluetooth/btmrvl_sdio.c index 93932a0d8625..b91fc63bc9fe 100644 --- a/drivers/bluetooth/btmrvl_sdio.c +++ b/drivers/bluetooth/btmrvl_sdio.c @@ -799,7 +799,7 @@ static int btmrvl_sdio_card_to_host(struct btmrvl_private *priv) skb_pull(skb, SDIO_HEADER_LEN); if (btmrvl_process_event(priv, skb)) - hci_recv_frame(hdev, skb); + kfree_skb(skb); hdev->stat.byte_rx += buf_len; break; From 683fa31f4e1f2ac46e1aa5ad3482a382c7d39391 Mon Sep 17 00:00:00 2001 From: "Pawel Zalewski (The Capable Hub)" Date: Mon, 27 Jul 2026 16:51:51 +0100 Subject: [PATCH 1066/1433] Bluetooth: use a named initializer for acpi_device_id Use a named initializer for the acpi_device_id fields which makes the code more readable and consistent with how lists are initialized in the rest of the kernel code base. While we are at it - unify the list terminator to have a single space between the brackets without a trailing coma. Signed-off-by: Pawel Zalewski (The Capable Hub) Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/hci_bcm.c | 336 ++++++++++++++++++------------------ drivers/bluetooth/hci_h5.c | 6 +- drivers/bluetooth/hci_qca.c | 12 +- 3 files changed, 177 insertions(+), 177 deletions(-) diff --git a/drivers/bluetooth/hci_bcm.c b/drivers/bluetooth/hci_bcm.c index 1a4fc3882fd2..01da3fecb536 100644 --- a/drivers/bluetooth/hci_bcm.c +++ b/drivers/bluetooth/hci_bcm.c @@ -1314,174 +1314,174 @@ static struct bcm_device_data bcm43430_device_data = { }; static const struct acpi_device_id bcm_acpi_match[] = { - { "BCM2E00" }, - { "BCM2E01" }, - { "BCM2E02" }, - { "BCM2E03" }, - { "BCM2E04" }, - { "BCM2E05" }, - { "BCM2E06" }, - { "BCM2E07" }, - { "BCM2E08" }, - { "BCM2E09" }, - { "BCM2E0A" }, - { "BCM2E0B" }, - { "BCM2E0C" }, - { "BCM2E0D" }, - { "BCM2E0E" }, - { "BCM2E0F" }, - { "BCM2E10" }, - { "BCM2E11" }, - { "BCM2E12" }, - { "BCM2E13" }, - { "BCM2E14" }, - { "BCM2E15" }, - { "BCM2E16" }, - { "BCM2E17" }, - { "BCM2E18" }, - { "BCM2E19" }, - { "BCM2E1A" }, - { "BCM2E1B" }, - { "BCM2E1C" }, - { "BCM2E1D" }, - { "BCM2E1F" }, - { "BCM2E20" }, - { "BCM2E21" }, - { "BCM2E22" }, - { "BCM2E23" }, - { "BCM2E24" }, - { "BCM2E25" }, - { "BCM2E26" }, - { "BCM2E27" }, - { "BCM2E28" }, - { "BCM2E29" }, - { "BCM2E2A" }, - { "BCM2E2B" }, - { "BCM2E2C" }, - { "BCM2E2D" }, - { "BCM2E2E" }, - { "BCM2E2F" }, - { "BCM2E30" }, - { "BCM2E31" }, - { "BCM2E32" }, - { "BCM2E33" }, - { "BCM2E34" }, - { "BCM2E35" }, - { "BCM2E36" }, - { "BCM2E37" }, - { "BCM2E38" }, - { "BCM2E39" }, - { "BCM2E3A" }, - { "BCM2E3B" }, - { "BCM2E3C" }, - { "BCM2E3D" }, - { "BCM2E3E" }, - { "BCM2E3F" }, - { "BCM2E40" }, - { "BCM2E41" }, - { "BCM2E42" }, - { "BCM2E43" }, - { "BCM2E44" }, - { "BCM2E45" }, - { "BCM2E46" }, - { "BCM2E47" }, - { "BCM2E48" }, - { "BCM2E49" }, - { "BCM2E4A" }, - { "BCM2E4B" }, - { "BCM2E4C" }, - { "BCM2E4D" }, - { "BCM2E4E" }, - { "BCM2E4F" }, - { "BCM2E50" }, - { "BCM2E51" }, - { "BCM2E52" }, - { "BCM2E53" }, - { "BCM2E54" }, - { "BCM2E55" }, - { "BCM2E56" }, - { "BCM2E57" }, - { "BCM2E58" }, - { "BCM2E59" }, - { "BCM2E5A" }, - { "BCM2E5B" }, - { "BCM2E5C" }, - { "BCM2E5D" }, - { "BCM2E5E" }, - { "BCM2E5F" }, - { "BCM2E60" }, - { "BCM2E61" }, - { "BCM2E62" }, - { "BCM2E63" }, - { "BCM2E64" }, - { "BCM2E65" }, - { "BCM2E66" }, - { "BCM2E67" }, - { "BCM2E68" }, - { "BCM2E69" }, - { "BCM2E6B" }, - { "BCM2E6D" }, - { "BCM2E6E" }, - { "BCM2E6F" }, - { "BCM2E70" }, - { "BCM2E71" }, - { "BCM2E72" }, - { "BCM2E73" }, - { "BCM2E74", (long)&bcm43430_device_data }, - { "BCM2E75", (long)&bcm43430_device_data }, - { "BCM2E76" }, - { "BCM2E77" }, - { "BCM2E78" }, - { "BCM2E79" }, - { "BCM2E7A" }, - { "BCM2E7B", (long)&bcm43430_device_data }, - { "BCM2E7C" }, - { "BCM2E7D" }, - { "BCM2E7E" }, - { "BCM2E7F" }, - { "BCM2E80", (long)&bcm43430_device_data }, - { "BCM2E81" }, - { "BCM2E82" }, - { "BCM2E83" }, - { "BCM2E84" }, - { "BCM2E85" }, - { "BCM2E86" }, - { "BCM2E87" }, - { "BCM2E88" }, - { "BCM2E89", (long)&bcm43430_device_data }, - { "BCM2E8A" }, - { "BCM2E8B" }, - { "BCM2E8C" }, - { "BCM2E8D" }, - { "BCM2E8E" }, - { "BCM2E90" }, - { "BCM2E92" }, - { "BCM2E93" }, - { "BCM2E94", (long)&bcm43430_device_data }, - { "BCM2E95" }, - { "BCM2E96" }, - { "BCM2E97" }, - { "BCM2E98" }, - { "BCM2E99", (long)&bcm43430_device_data }, - { "BCM2E9A" }, - { "BCM2E9B", (long)&bcm43430_device_data }, - { "BCM2E9C" }, - { "BCM2E9D" }, - { "BCM2E9F", (long)&bcm43430_device_data }, - { "BCM2EA0" }, - { "BCM2EA1" }, - { "BCM2EA2", (long)&bcm43430_device_data }, - { "BCM2EA3", (long)&bcm43430_device_data }, - { "BCM2EA4", (long)&bcm43430_device_data }, /* bcm43455 */ - { "BCM2EA5" }, - { "BCM2EA6" }, - { "BCM2EA7" }, - { "BCM2EA8" }, - { "BCM2EA9" }, - { "BCM2EAA", (long)&bcm43430_device_data }, - { "BCM2EAB", (long)&bcm43430_device_data }, - { "BCM2EAC", (long)&bcm43430_device_data }, - { }, + { .id = "BCM2E00" }, + { .id = "BCM2E01" }, + { .id = "BCM2E02" }, + { .id = "BCM2E03" }, + { .id = "BCM2E04" }, + { .id = "BCM2E05" }, + { .id = "BCM2E06" }, + { .id = "BCM2E07" }, + { .id = "BCM2E08" }, + { .id = "BCM2E09" }, + { .id = "BCM2E0A" }, + { .id = "BCM2E0B" }, + { .id = "BCM2E0C" }, + { .id = "BCM2E0D" }, + { .id = "BCM2E0E" }, + { .id = "BCM2E0F" }, + { .id = "BCM2E10" }, + { .id = "BCM2E11" }, + { .id = "BCM2E12" }, + { .id = "BCM2E13" }, + { .id = "BCM2E14" }, + { .id = "BCM2E15" }, + { .id = "BCM2E16" }, + { .id = "BCM2E17" }, + { .id = "BCM2E18" }, + { .id = "BCM2E19" }, + { .id = "BCM2E1A" }, + { .id = "BCM2E1B" }, + { .id = "BCM2E1C" }, + { .id = "BCM2E1D" }, + { .id = "BCM2E1F" }, + { .id = "BCM2E20" }, + { .id = "BCM2E21" }, + { .id = "BCM2E22" }, + { .id = "BCM2E23" }, + { .id = "BCM2E24" }, + { .id = "BCM2E25" }, + { .id = "BCM2E26" }, + { .id = "BCM2E27" }, + { .id = "BCM2E28" }, + { .id = "BCM2E29" }, + { .id = "BCM2E2A" }, + { .id = "BCM2E2B" }, + { .id = "BCM2E2C" }, + { .id = "BCM2E2D" }, + { .id = "BCM2E2E" }, + { .id = "BCM2E2F" }, + { .id = "BCM2E30" }, + { .id = "BCM2E31" }, + { .id = "BCM2E32" }, + { .id = "BCM2E33" }, + { .id = "BCM2E34" }, + { .id = "BCM2E35" }, + { .id = "BCM2E36" }, + { .id = "BCM2E37" }, + { .id = "BCM2E38" }, + { .id = "BCM2E39" }, + { .id = "BCM2E3A" }, + { .id = "BCM2E3B" }, + { .id = "BCM2E3C" }, + { .id = "BCM2E3D" }, + { .id = "BCM2E3E" }, + { .id = "BCM2E3F" }, + { .id = "BCM2E40" }, + { .id = "BCM2E41" }, + { .id = "BCM2E42" }, + { .id = "BCM2E43" }, + { .id = "BCM2E44" }, + { .id = "BCM2E45" }, + { .id = "BCM2E46" }, + { .id = "BCM2E47" }, + { .id = "BCM2E48" }, + { .id = "BCM2E49" }, + { .id = "BCM2E4A" }, + { .id = "BCM2E4B" }, + { .id = "BCM2E4C" }, + { .id = "BCM2E4D" }, + { .id = "BCM2E4E" }, + { .id = "BCM2E4F" }, + { .id = "BCM2E50" }, + { .id = "BCM2E51" }, + { .id = "BCM2E52" }, + { .id = "BCM2E53" }, + { .id = "BCM2E54" }, + { .id = "BCM2E55" }, + { .id = "BCM2E56" }, + { .id = "BCM2E57" }, + { .id = "BCM2E58" }, + { .id = "BCM2E59" }, + { .id = "BCM2E5A" }, + { .id = "BCM2E5B" }, + { .id = "BCM2E5C" }, + { .id = "BCM2E5D" }, + { .id = "BCM2E5E" }, + { .id = "BCM2E5F" }, + { .id = "BCM2E60" }, + { .id = "BCM2E61" }, + { .id = "BCM2E62" }, + { .id = "BCM2E63" }, + { .id = "BCM2E64" }, + { .id = "BCM2E65" }, + { .id = "BCM2E66" }, + { .id = "BCM2E67" }, + { .id = "BCM2E68" }, + { .id = "BCM2E69" }, + { .id = "BCM2E6B" }, + { .id = "BCM2E6D" }, + { .id = "BCM2E6E" }, + { .id = "BCM2E6F" }, + { .id = "BCM2E70" }, + { .id = "BCM2E71" }, + { .id = "BCM2E72" }, + { .id = "BCM2E73" }, + { .id = "BCM2E74", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2E75", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2E76" }, + { .id = "BCM2E77" }, + { .id = "BCM2E78" }, + { .id = "BCM2E79" }, + { .id = "BCM2E7A" }, + { .id = "BCM2E7B", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2E7C" }, + { .id = "BCM2E7D" }, + { .id = "BCM2E7E" }, + { .id = "BCM2E7F" }, + { .id = "BCM2E80", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2E81" }, + { .id = "BCM2E82" }, + { .id = "BCM2E83" }, + { .id = "BCM2E84" }, + { .id = "BCM2E85" }, + { .id = "BCM2E86" }, + { .id = "BCM2E87" }, + { .id = "BCM2E88" }, + { .id = "BCM2E89", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2E8A" }, + { .id = "BCM2E8B" }, + { .id = "BCM2E8C" }, + { .id = "BCM2E8D" }, + { .id = "BCM2E8E" }, + { .id = "BCM2E90" }, + { .id = "BCM2E92" }, + { .id = "BCM2E93" }, + { .id = "BCM2E94", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2E95" }, + { .id = "BCM2E96" }, + { .id = "BCM2E97" }, + { .id = "BCM2E98" }, + { .id = "BCM2E99", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2E9A" }, + { .id = "BCM2E9B", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2E9C" }, + { .id = "BCM2E9D" }, + { .id = "BCM2E9F", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2EA0" }, + { .id = "BCM2EA1" }, + { .id = "BCM2EA2", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2EA3", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2EA4", .driver_data = (long)&bcm43430_device_data }, /* bcm43455 */ + { .id = "BCM2EA5" }, + { .id = "BCM2EA6" }, + { .id = "BCM2EA7" }, + { .id = "BCM2EA8" }, + { .id = "BCM2EA9" }, + { .id = "BCM2EAA", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2EAB", .driver_data = (long)&bcm43430_device_data }, + { .id = "BCM2EAC", .driver_data = (long)&bcm43430_device_data }, + { } }; MODULE_DEVICE_TABLE(acpi, bcm_acpi_match); #endif diff --git a/drivers/bluetooth/hci_h5.c b/drivers/bluetooth/hci_h5.c index 93cdde981840..60b90f1e11fc 100644 --- a/drivers/bluetooth/hci_h5.c +++ b/drivers/bluetooth/hci_h5.c @@ -1124,10 +1124,10 @@ static const struct h5_device_data h5_data_rtl8723bs = { #ifdef CONFIG_ACPI static const struct acpi_device_id h5_acpi_match[] = { #ifdef CONFIG_BT_HCIUART_RTL - { "OBDA0623", (kernel_ulong_t)&h5_data_rtl8723bs }, - { "OBDA8723", (kernel_ulong_t)&h5_data_rtl8723bs }, + { .id = "OBDA0623", .driver_data = (kernel_ulong_t)&h5_data_rtl8723bs }, + { .id = "OBDA8723", .driver_data = (kernel_ulong_t)&h5_data_rtl8723bs }, #endif - { }, + { } }; MODULE_DEVICE_TABLE(acpi, h5_acpi_match); #endif diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c index e6d107f67759..345f602e9ce2 100644 --- a/drivers/bluetooth/hci_qca.c +++ b/drivers/bluetooth/hci_qca.c @@ -2792,12 +2792,12 @@ MODULE_DEVICE_TABLE(of, qca_bluetooth_of_match); #ifdef CONFIG_ACPI static const struct acpi_device_id qca_bluetooth_acpi_match[] = { - { "QCOM2066", (kernel_ulong_t)&qca_soc_data_qca2066 }, - { "QCOM6390", (kernel_ulong_t)&qca_soc_data_qca6390 }, - { "DLA16390", (kernel_ulong_t)&qca_soc_data_qca6390 }, - { "DLB16390", (kernel_ulong_t)&qca_soc_data_qca6390 }, - { "DLB26390", (kernel_ulong_t)&qca_soc_data_qca6390 }, - { }, + { .id = "QCOM2066", .driver_data = (kernel_ulong_t)&qca_soc_data_qca2066 }, + { .id = "QCOM6390", .driver_data = (kernel_ulong_t)&qca_soc_data_qca6390 }, + { .id = "DLA16390", .driver_data = (kernel_ulong_t)&qca_soc_data_qca6390 }, + { .id = "DLB16390", .driver_data = (kernel_ulong_t)&qca_soc_data_qca6390 }, + { .id = "DLB26390", .driver_data = (kernel_ulong_t)&qca_soc_data_qca6390 }, + { } }; MODULE_DEVICE_TABLE(acpi, qca_bluetooth_acpi_match); #endif From 80718e2e0fbce54d57146564455f123daebfe11a Mon Sep 17 00:00:00 2001 From: "Pawel Zalewski (The Capable Hub)" Date: Mon, 27 Jul 2026 16:51:52 +0100 Subject: [PATCH 1067/1433] Bluetooth: hci_intel: drop unused assignment of acpi_device_id::driver_data This module sets the acpi_device_id::driver_data to 0 but the field is not actually used within the module, we can just drop it from the table. While we are at it - use a named initializer for the acpi_device_id::id field and drop setting the list terminator fields explicitly as well. Signed-off-by: Pawel Zalewski (The Capable Hub) Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/hci_intel.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/hci_intel.c b/drivers/bluetooth/hci_intel.c index c31105b91e47..ecf597f3e201 100644 --- a/drivers/bluetooth/hci_intel.c +++ b/drivers/bluetooth/hci_intel.c @@ -1057,8 +1057,8 @@ static const struct hci_uart_proto intel_proto = { #ifdef CONFIG_ACPI static const struct acpi_device_id intel_acpi_match[] = { - { "INT33E1", 0 }, - { "INT33E3", 0 }, + { .id = "INT33E1" }, + { .id = "INT33E3" }, { } }; MODULE_DEVICE_TABLE(acpi, intel_acpi_match); From 39bbe7b7386fb3cdbb1c02a240e40d45f1435778 Mon Sep 17 00:00:00 2001 From: Chandrashekar Devegowda Date: Mon, 27 Jul 2026 10:51:02 +0530 Subject: [PATCH 1068/1433] Bluetooth: btintel_pcie: Add vendor_reset PCI sysfs for PLDR Add a read-write sysfs entry at /sys/bus/pci/devices//vendor_reset to allow userspace to trigger PLDR (Product Level Device Reset). Reading the attribute displays supported reset types. Writing integer 0 triggers PLDR. Any other input is rejected with -EINVAL and a warning log. Signed-off-by: Chandrashekar Devegowda Signed-off-by: Luiz Augusto von Dentz --- .../sysfs-bus-pci-drivers-btintel_pcie | 15 ++++++++ MAINTAINERS | 1 + drivers/bluetooth/btintel_pcie.c | 38 +++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-pci-drivers-btintel_pcie diff --git a/Documentation/ABI/testing/sysfs-bus-pci-drivers-btintel_pcie b/Documentation/ABI/testing/sysfs-bus-pci-drivers-btintel_pcie new file mode 100644 index 000000000000..cceec6ac96bc --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-pci-drivers-btintel_pcie @@ -0,0 +1,15 @@ +What: /sys/bus/pci/devices//vendor_reset +Date: 22-Jul-2026 +KernelVersion: 6.17 +Contact: linux-bluetooth@vger.kernel.org +Description: This read-write attribute allows userspace to trigger a + Product Level Device Reset (PLDR) on Intel PCIe Bluetooth + controllers. Reading the attribute displays the supported + reset type. Writing integer 0 triggers PLDR. Any other + input is rejected with -EINVAL. + + PLDR resets the entire on-chip platform shared between + Bluetooth and WiFi. This means any driver attached to + the WiFi device that shares hardware with this Bluetooth + device will be released, the platform will be reset, and + both the Bluetooth and WiFi devices will be re-probed. diff --git a/MAINTAINERS b/MAINTAINERS index 08e43bc09735..891c064a881b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4719,6 +4719,7 @@ S: Supported W: http://www.bluez.org/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth.git T: git git://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git +F: Documentation/ABI/testing/sysfs-bus-pci-drivers-btintel_pcie F: Documentation/devicetree/bindings/net/bluetooth/ F: drivers/bluetooth/ diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index ef42b8d11d4d..005c77a4f5eb 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -2790,6 +2790,43 @@ static void btintel_pcie_hci_reset(struct hci_dev *hdev) btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); } +static ssize_t vendor_reset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int val; + struct pci_dev *pdev = to_pci_dev(dev); + struct btintel_pcie_data *data = pci_get_drvdata(pdev); + + if (!data || !data->hdev) + return -ENODEV; + + if (kstrtouint(buf, 10, &val) || val != 0) { + bt_dev_warn(data->hdev, "PLDR rejected: invalid input"); + return -EINVAL; + } + + bt_dev_info(data->hdev, "PLDR triggered via sysfs"); + btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_PLDR); + + return count; +} + +static ssize_t vendor_reset_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "0 - PLDR\n"); +} + +static DEVICE_ATTR_RW(vendor_reset); + +static struct attribute *btintel_pcie_attrs[] = { + &dev_attr_vendor_reset.attr, + NULL, +}; + +ATTRIBUTE_GROUPS(btintel_pcie); + static void btintel_pcie_hw_error(struct hci_dev *hdev, u8 code) { struct btintel_pcie_dev_recovery *rec; @@ -3250,6 +3287,7 @@ static struct pci_driver btintel_pcie_driver = { .probe = btintel_pcie_probe, .remove = btintel_pcie_remove, .driver.pm = pm_sleep_ptr(&btintel_pcie_pm_ops), + .dev_groups = btintel_pcie_groups, #ifdef CONFIG_DEV_COREDUMP .driver.coredump = btintel_pcie_coredump #endif From 2f8784cfe8a961e5f0d85210c5175ebf5d438d93 Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Wed, 24 Jun 2026 14:47:34 -0400 Subject: [PATCH 1069/1433] Bluetooth: Add support for Shorter Connection Interval (SCI) feature Add HCI command, event and feature bit definitions for the Bluetooth 6.2 Shorter Connection Interval feature: Commands: - HCI_OP_LE_CONN_RATE (0x20a1) - Connection Rate Request - HCI_OP_LE_SET_DEF_RATE (0x20a2) - Set Default Rate Parameters - HCI_OP_LE_READ_CONN_INTERVAL (0x20a3) - Read Min Supported Connection Interval Events: - HCI_EVT_LE_CONN_RATE_CHANGE (0x37) - Connection Rate Change Feature bits: - HCI_LE_SCI - Shorter Connection Intervals - HCI_LE_SCI_HOST - Shorter Connection Intervals (Host Support) During controller init, when SCI is supported: - Set Shorter Connection Intervals (Host Support) feature via LE Set Host Feature - Read Minimum Supported Connection Interval - Set Default Rate Parameters The Connection Rate Change event handler updates the connection interval, latency and supervision timeout on the hci_conn. Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/hci.h | 53 +++++++++++++++++++++++++ include/net/bluetooth/hci_core.h | 9 +++++ net/bluetooth/hci_event.c | 64 +++++++++++++++++++++++++++++++ net/bluetooth/hci_sync.c | 66 +++++++++++++++++++++++++++++++- 4 files changed, 190 insertions(+), 2 deletions(-) diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index d557bdf9ae57..cd3520a29131 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -653,6 +653,8 @@ enum { #define HCI_LE_LL_EXT_FEATURE 0x80 #define HCI_LE_CS 0x40 #define HCI_LE_CS_HOST 0x80 +#define HCI_LE_SCI 0x01 /* byte 9 - Shorter Connection Intervals */ +#define HCI_LE_SCI_HOST 0x02 /* byte 9 - Shorter Connection Intervals (Host) */ /* Connection modes */ #define HCI_CM_ACTIVE 0x0000 @@ -2489,6 +2491,46 @@ struct hci_cp_le_set_host_feature_v2 { __u8 bit_value; } __packed; +#define HCI_OP_LE_CONN_RATE 0x20a1 +struct hci_cp_le_conn_rate { + __le16 handle; + __le16 interval_min; + __le16 interval_max; + __le16 subrate_min; + __le16 subrate_max; + __le16 max_latency; + __le16 cont_num; + __le16 supv_timeout; + __le16 min_ce_len; + __le16 max_ce_len; +} __packed; + +#define HCI_OP_LE_SET_DEF_RATE 0x20a2 +struct hci_cp_le_set_def_rate { + __le16 interval_min; + __le16 interval_max; + __le16 subrate_min; + __le16 subrate_max; + __le16 max_latency; + __le16 cont_num; + __le16 supv_timeout; + __le16 min_ce_len; + __le16 max_ce_len; +} __packed; + +#define HCI_OP_LE_READ_CONN_INTERVAL 0x20a3 +struct hci_le_conn_interval_group { + __le16 min; + __le16 max; + __le16 stride; +} __packed; + +struct hci_rp_le_read_conn_interval { + __u8 status; + __u8 num_grps; + struct hci_le_conn_interval_group grps[]; +} __packed; + /* ---- HCI Events ---- */ struct hci_ev_status { __u8 status; @@ -3303,6 +3345,17 @@ struct hci_evt_le_cs_test_end_complete { __u8 status; } __packed; +#define HCI_EVT_LE_CONN_RATE_CHANGE 0x37 +struct hci_evt_le_conn_rate_change { + __u8 status; + __le16 handle; + __le16 interval; + __le16 subrate; + __le16 latency; + __le16 cont_number; + __le16 supv_timeout; +} __packed; + #define HCI_EV_VENDOR 0xff /* Internal events generated by Bluetooth stack */ diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 3df59849dcbe..f48f875022d4 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -416,6 +416,7 @@ struct hci_dev { __u16 le_conn_max_interval; __u16 le_conn_latency; __u16 le_supv_timeout; + __u16 le_min_rate_interval; __u16 le_def_tx_len; __u16 le_def_tx_time; __u16 le_max_tx_len; @@ -720,6 +721,11 @@ struct hci_conn { __u16 le_conn_interval; __u16 le_conn_latency; __u16 le_supv_timeout; + __u16 le_rate_interval; + __u16 le_subrate; + __u16 le_rate_latency; + __u16 le_cont_num; + __u16 le_rate_supv_timeout; __u8 le_adv_data[HCI_MAX_EXT_AD_LENGTH]; __u8 le_adv_data_len; __u8 le_per_adv_data[HCI_MAX_PER_AD_TOT_LEN]; @@ -2079,6 +2085,9 @@ void hci_conn_del_sysfs(struct hci_conn *conn); #define le_cs_host_capable(dev) \ ((dev)->le_features[5] & HCI_LE_CS_HOST) +#define le_sci_capable(dev) \ + ((dev)->le_features[9] & HCI_LE_SCI) + #define mws_transport_config_capable(dev) (((dev)->commands[30] & 0x08) && \ (!hci_test_quirk((dev), HCI_QUIRK_BROKEN_MWS_TRANSPORT_CONFIG))) diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index ea858391c789..7b539c7602b0 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -1241,6 +1241,39 @@ static u8 hci_cc_le_read_local_features(struct hci_dev *hdev, void *data, return rp->status; } +static u8 hci_cc_le_read_conn_interval(struct hci_dev *hdev, void *data, + struct sk_buff *skb) +{ + struct hci_rp_le_read_conn_interval *rp = data; + u16 min_interval = 0; + int i; + + bt_dev_dbg(hdev, "status 0x%2.2x", rp->status); + + if (rp->status) + return rp->status; + + if (skb->len < flex_array_size(rp, grps, rp->num_grps)) { + bt_dev_err(hdev, "Invalid response length for 0x%4.4x", + HCI_OP_LE_READ_CONN_INTERVAL); + return HCI_ERROR_UNSPECIFIED; + } + + /* Store the smallest minimum supported connection interval reported by + * the controller so the default rate parameters can be clamped to it. + */ + for (i = 0; i < rp->num_grps; i++) { + u16 min = le16_to_cpu(rp->grps[i].min); + + if (!min_interval || min < min_interval) + min_interval = min; + } + + hdev->le_min_rate_interval = min_interval; + + return rp->status; +} + static u8 hci_cc_le_read_adv_tx_power(struct hci_dev *hdev, void *data, struct sk_buff *skb) { @@ -4153,6 +4186,9 @@ static const struct hci_cc { sizeof(struct hci_rp_le_read_buffer_size)), HCI_CC(HCI_OP_LE_READ_LOCAL_FEATURES, hci_cc_le_read_local_features, sizeof(struct hci_rp_le_read_local_features)), + HCI_CC_VL(HCI_OP_LE_READ_CONN_INTERVAL, hci_cc_le_read_conn_interval, + sizeof(struct hci_rp_le_read_conn_interval), + HCI_MAX_EVENT_SIZE), HCI_CC(HCI_OP_LE_READ_ADV_TX_POWER, hci_cc_le_read_adv_tx_power, sizeof(struct hci_rp_le_read_adv_tx_power)), HCI_CC(HCI_OP_USER_CONFIRM_REPLY, hci_cc_user_confirm_reply, @@ -7363,6 +7399,31 @@ static void hci_le_read_all_remote_features_evt(struct hci_dev *hdev, hci_dev_unlock(hdev); } +static void hci_le_conn_rate_change_evt(struct hci_dev *hdev, void *data, + struct sk_buff *skb) +{ + struct hci_evt_le_conn_rate_change *ev = data; + struct hci_conn *conn; + + bt_dev_dbg(hdev, "status 0x%2.2x", ev->status); + + if (ev->status) + return; + + hci_dev_lock(hdev); + + conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(ev->handle)); + if (conn) { + conn->le_rate_interval = le16_to_cpu(ev->interval); + conn->le_subrate = le16_to_cpu(ev->subrate); + conn->le_rate_latency = le16_to_cpu(ev->latency); + conn->le_cont_num = le16_to_cpu(ev->cont_number); + conn->le_rate_supv_timeout = le16_to_cpu(ev->supv_timeout); + } + + hci_dev_unlock(hdev); +} + #define HCI_LE_EV_VL(_op, _func, _min_len, _max_len) \ [_op] = { \ .func = _func, \ @@ -7474,6 +7535,9 @@ static const struct hci_le_ev { sizeof(struct hci_evt_le_read_all_remote_features_complete), HCI_MAX_EVENT_SIZE), + /* [0x37 = HCI_EVT_LE_CONN_RATE_CHANGE] */ + HCI_LE_EV(HCI_EVT_LE_CONN_RATE_CHANGE, hci_le_conn_rate_change_evt, + sizeof(struct hci_evt_le_conn_rate_change)), }; static void hci_le_meta_evt(struct hci_dev *hdev, void *data, diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 7779d9d1663a..576a93d6ed2b 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -4541,6 +4541,10 @@ static int hci_le_set_event_mask_sync(struct hci_dev *hdev) events[6] |= 0x02; /* LE CS Subevent Result Continue event */ events[6] |= 0x04; /* LE CS Test End Complete event */ } + + if (le_sci_capable(hdev)) + events[6] |= 0x40; /* LE Connection Rate Change event */ + return __hci_cmd_sync_status(hdev, HCI_OP_LE_SET_EVENT_MASK, sizeof(events), events, HCI_CMD_TIMEOUT); } @@ -4709,12 +4713,59 @@ static int hci_le_set_host_feature_sync(struct hci_dev *hdev, u16 bit, u8 value) sizeof(cp), &cp, HCI_CMD_TIMEOUT); } +static int hci_le_read_conn_interval_sync(struct hci_dev *hdev) +{ + if (!le_sci_capable(hdev)) + return 0; + + return __hci_cmd_sync_status(hdev, HCI_OP_LE_READ_CONN_INTERVAL, + 0, NULL, HCI_CMD_TIMEOUT); +} + +static int hci_le_set_def_rate_sync(struct hci_dev *hdev) +{ + struct hci_cp_le_set_def_rate cp; + u16 interval_min = 0x000a; /* 1.25 ms */ + u16 interval_max = 0x0078; /* 15 ms */ + + if (!le_sci_capable(hdev)) + return 0; + + /* Clamp the interval range to the controller's minimum supported + * connection interval (read via HCI_OP_LE_READ_CONN_INTERVAL) so the + * default rate parameters are not rejected. The maximum is raised as + * well if needed to keep interval_min <= interval_max. + */ + if (hdev->le_min_rate_interval > interval_min) { + interval_min = hdev->le_min_rate_interval; + if (interval_min > interval_max) + interval_max = interval_min; + } + + memset(&cp, 0, sizeof(cp)); + + /* Use the HIDS 1.2 recommended Full Range mode values as the default + * rate parameters (see HOGP v1.2 spec). Connection intervals are in + * units of 0.125 ms and the supervision timeout is in units of 10 ms. + */ + cp.interval_min = cpu_to_le16(interval_min); + cp.interval_max = cpu_to_le16(interval_max); + cp.subrate_min = cpu_to_le16(0x0001); + cp.subrate_max = cpu_to_le16(0x0004); + cp.max_latency = cpu_to_le16(0x0000); + cp.cont_num = cpu_to_le16(0x0001); + cp.supv_timeout = cpu_to_le16(0x000c); /* 120 ms */ + + return __hci_cmd_sync_status(hdev, HCI_OP_LE_SET_DEF_RATE, + sizeof(cp), &cp, HCI_CMD_TIMEOUT); +} + /* Set Host Features, each feature needs to be sent separately since * HCI_OP_LE_SET_HOST_FEATURE doesn't support setting all of them at once. */ static int hci_le_set_host_features_sync(struct hci_dev *hdev) { - int err; + int err = 0; if (cis_capable(hdev)) { /* Connected Isochronous Channels (Host Support) */ @@ -4725,9 +4776,16 @@ static int hci_le_set_host_features_sync(struct hci_dev *hdev) return err; } - if (le_cs_capable(hdev)) + if (le_cs_capable(hdev)) { /* Channel Sounding (Host Support) */ err = hci_le_set_host_feature_sync(hdev, 47, 0x01); + if (err) + return err; + } + + if (le_sci_capable(hdev)) + /* Shorter Connection Intervals (Host Support) */ + err = hci_le_set_host_feature_sync(hdev, 73, 0x01); return err; } @@ -4760,6 +4818,10 @@ static const struct hci_init_stage le_init3[] = { HCI_INIT(hci_set_le_support_sync), /* HCI_OP_LE_SET_HOST_FEATURE */ HCI_INIT(hci_le_set_host_features_sync), + /* HCI_OP_LE_READ_CONN_INTERVAL */ + HCI_INIT(hci_le_read_conn_interval_sync), + /* HCI_OP_LE_SET_DEF_RATE */ + HCI_INIT(hci_le_set_def_rate_sync), {} }; From 5ec6f300e26ee1c7d34e0d7e9acd9083af124b55 Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Fri, 24 Jul 2026 10:57:25 -0400 Subject: [PATCH 1070/1433] Bluetooth: Add MGMT Shorter Connection Interval setting Add MGMT_SETTING_SCI (bit 25) to advertise support for the Shorter Connection Interval (SCI) feature. It is reported in the supported settings whenever the controller is SCI capable, and in the current settings whenever LE is enabled and the controller is SCI capable (SCI has no separate enable command, so it is a passive capability). Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/hci_core.h | 2 ++ include/net/bluetooth/mgmt.h | 1 + net/bluetooth/mgmt.c | 6 ++++++ 3 files changed, 9 insertions(+) diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index f48f875022d4..b1f531b3b6ab 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -2087,6 +2087,8 @@ void hci_conn_del_sysfs(struct hci_conn *conn); #define le_sci_capable(dev) \ ((dev)->le_features[9] & HCI_LE_SCI) +#define le_sci_enabled(dev) \ + (le_enabled(dev) && le_sci_capable(dev)) #define mws_transport_config_capable(dev) (((dev)->commands[30] & 0x08) && \ (!hci_test_quirk((dev), HCI_QUIRK_BROKEN_MWS_TRANSPORT_CONFIG))) diff --git a/include/net/bluetooth/mgmt.h b/include/net/bluetooth/mgmt.h index 08daed7a96d5..b285441db55b 100644 --- a/include/net/bluetooth/mgmt.h +++ b/include/net/bluetooth/mgmt.h @@ -118,6 +118,7 @@ struct mgmt_rp_read_index_list { #define MGMT_SETTING_LL_PRIVACY BIT(22) #define MGMT_SETTING_PAST_SENDER BIT(23) #define MGMT_SETTING_PAST_RECEIVER BIT(24) +#define MGMT_SETTING_SCI BIT(25) #define MGMT_OP_READ_INFO 0x0004 #define MGMT_READ_INFO_SIZE 0 diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 167d75e34526..fec41af69d62 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -861,6 +861,9 @@ static u32 get_supported_settings(struct hci_dev *hdev) if (past_receiver_capable(hdev)) settings |= MGMT_SETTING_PAST_RECEIVER; + if (le_sci_capable(hdev)) + settings |= MGMT_SETTING_SCI; + settings |= MGMT_SETTING_PHY_CONFIGURATION; return settings; @@ -952,6 +955,9 @@ static u32 get_current_settings(struct hci_dev *hdev) if (past_receiver_enabled(hdev)) settings |= MGMT_SETTING_PAST_RECEIVER; + if (le_sci_enabled(hdev)) + settings |= MGMT_SETTING_SCI; + return settings; } From 19129d7037beafe16507d6616d8a54eaa49b2eda Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Fri, 24 Jul 2026 10:58:06 -0400 Subject: [PATCH 1071/1433] Bluetooth: Add MGMT Load Connection Subrate command Add MGMT_OP_LOAD_CONN_SUBRATE (0x005C) command to load per-device connection subrate parameters when the SCI feature is supported. Add MGMT_EV_CONN_SUBRATE (0x0033) event to notify userspace when connection rate changes occur via the LE Connection Rate Change HCI event. Add subrate fields (subrate_min, subrate_max, max_latency, cont_num) to struct hci_conn_params to store the loaded subrate parameters, and the corresponding le_rate_* fields to struct hci_conn to track the parameters currently in use. When a single entry is loaded for an already-connected central, or on connection completion, the LE Connection Rate Request procedure is initiated to apply the parameters. Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/hci_core.h | 10 +++ include/net/bluetooth/hci_sync.h | 1 + include/net/bluetooth/mgmt.h | 28 +++++++ net/bluetooth/hci_event.c | 32 ++++++-- net/bluetooth/hci_sync.c | 69 ++++++++++++++++ net/bluetooth/mgmt.c | 131 +++++++++++++++++++++++++++++++ 6 files changed, 263 insertions(+), 8 deletions(-) diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index b1f531b3b6ab..01b938c4b24a 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -818,6 +818,14 @@ struct hci_conn_params { u16 conn_latency; u16 supervision_timeout; + u16 rate_min_interval; + u16 rate_max_interval; + u16 subrate_min; + u16 subrate_max; + u16 max_latency; + u16 cont_num; + u16 rate_supv_timeout; + enum { HCI_AUTO_CONN_DISABLED, HCI_AUTO_CONN_REPORT, @@ -2505,6 +2513,8 @@ void mgmt_advertising_removed(struct sock *sk, struct hci_dev *hdev, int mgmt_phy_configuration_changed(struct hci_dev *hdev, struct sock *skip); void mgmt_adv_monitor_device_lost(struct hci_dev *hdev, u16 handle, bdaddr_t *bdaddr, u8 addr_type); +void mgmt_conn_subrate_notify(struct hci_dev *hdev, struct hci_conn *conn, + u8 status); int hci_abort_conn(struct hci_conn *conn, u8 reason); void hci_le_conn_update(struct hci_conn *conn, u16 min, u16 max, u16 latency, diff --git a/include/net/bluetooth/hci_sync.h b/include/net/bluetooth/hci_sync.h index 0756d6fe77d4..a6579a868678 100644 --- a/include/net/bluetooth/hci_sync.h +++ b/include/net/bluetooth/hci_sync.h @@ -183,6 +183,7 @@ int hci_connect_le_sync(struct hci_dev *hdev, struct hci_conn *conn); int hci_cancel_connect_sync(struct hci_dev *hdev, struct hci_conn *conn); int hci_le_conn_update_sync(struct hci_dev *hdev, struct hci_conn *conn, struct hci_conn_params *params); +int hci_le_conn_rate_request(struct hci_dev *hdev, struct hci_conn *conn); int hci_connect_pa_sync(struct hci_dev *hdev, struct hci_conn *conn); int hci_connect_big_sync(struct hci_dev *hdev, struct hci_conn *conn); diff --git a/include/net/bluetooth/mgmt.h b/include/net/bluetooth/mgmt.h index b285441db55b..1e22eab1081c 100644 --- a/include/net/bluetooth/mgmt.h +++ b/include/net/bluetooth/mgmt.h @@ -894,6 +894,23 @@ struct mgmt_cp_hci_cmd_sync { } __packed; #define MGMT_HCI_CMD_SYNC_SIZE 6 +#define MGMT_OP_LOAD_CONN_SUBRATE 0x005C +struct mgmt_conn_subrate { + struct mgmt_addr_info addr; + __le16 min_interval; + __le16 max_interval; + __le16 subrate_min; + __le16 subrate_max; + __le16 max_latency; + __le16 cont_num; + __le16 supv_timeout; +} __packed; +struct mgmt_cp_load_conn_subrate { + __le16 param_count; + struct mgmt_conn_subrate params[] __counted_by_le(param_count); +} __packed; +#define MGMT_LOAD_CONN_SUBRATE_SIZE 2 + #define MGMT_EV_CMD_COMPLETE 0x0001 struct mgmt_ev_cmd_complete { __le16 opcode; @@ -1193,3 +1210,14 @@ struct mgmt_ev_mesh_device_found { struct mgmt_ev_mesh_pkt_cmplt { __u8 handle; } __packed; + +#define MGMT_EV_CONN_SUBRATE 0x0033 +struct mgmt_ev_conn_subrate { + struct mgmt_addr_info addr; + __u8 status; + __le16 interval; + __le16 subrate; + __le16 latency; + __le16 cont_num; + __le16 supv_timeout; +} __packed; diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 7b539c7602b0..9764efb9e293 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -5903,6 +5903,17 @@ static void le_conn_complete_evt(struct hci_dev *hdev, u8 status, } } + /* If we are central and have subrate parameters stored, queue a + * connection rate request to apply them. + */ + if (conn->role == HCI_ROLE_MASTER && le_sci_capable(hdev)) { + struct hci_conn_params *p; + + p = hci_conn_params_lookup(hdev, &conn->dst, conn->dst_type); + if (p && p->subrate_max) + hci_le_conn_rate_request(hdev, conn); + } + unlock: hci_update_passive_scan(hdev); hci_dev_unlock(hdev); @@ -7407,18 +7418,23 @@ static void hci_le_conn_rate_change_evt(struct hci_dev *hdev, void *data, bt_dev_dbg(hdev, "status 0x%2.2x", ev->status); - if (ev->status) - return; - hci_dev_lock(hdev); conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(ev->handle)); if (conn) { - conn->le_rate_interval = le16_to_cpu(ev->interval); - conn->le_subrate = le16_to_cpu(ev->subrate); - conn->le_rate_latency = le16_to_cpu(ev->latency); - conn->le_cont_num = le16_to_cpu(ev->cont_number); - conn->le_rate_supv_timeout = le16_to_cpu(ev->supv_timeout); + /* Only update the stored rate parameters on success; on + * failure the values in the event are not valid. Userspace is + * notified either way. + */ + if (!ev->status) { + conn->le_rate_interval = le16_to_cpu(ev->interval); + conn->le_subrate = le16_to_cpu(ev->subrate); + conn->le_rate_latency = le16_to_cpu(ev->latency); + conn->le_cont_num = le16_to_cpu(ev->cont_number); + conn->le_rate_supv_timeout = + le16_to_cpu(ev->supv_timeout); + } + mgmt_conn_subrate_notify(hdev, conn, ev->status); } hci_dev_unlock(hdev); diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 576a93d6ed2b..307fd47f8459 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -7355,6 +7355,75 @@ int hci_le_conn_update_sync(struct hci_dev *hdev, struct hci_conn *conn, sizeof(cp), &cp, HCI_CMD_TIMEOUT); } +static int hci_le_conn_rate_request_sync(struct hci_dev *hdev, void *data) +{ + struct hci_conn *conn = data; + struct hci_conn_params *params; + struct hci_cp_le_conn_rate cp; + + hci_dev_lock(hdev); + + /* The request was queued asynchronously so re-validate the connection + * and its parameters under hdev->lock. The connection may have been + * torn down, or may not have a valid handle yet (still connecting), + * and the parameters may have been removed in the meantime (e.g. by + * Load Connection Parameters). Snapshot the rate values so the + * blocking command below can run without holding hdev->lock. + */ + if (!hci_conn_valid(hdev, conn) || + HCI_CONN_HANDLE_UNSET(conn->handle)) { + hci_dev_unlock(hdev); + return -ECANCELED; + } + + params = hci_conn_params_lookup(hdev, &conn->dst, conn->dst_type); + if (!params) { + hci_dev_unlock(hdev); + return -ECANCELED; + } + + memset(&cp, 0, sizeof(cp)); + cp.handle = cpu_to_le16(conn->handle); + cp.interval_min = cpu_to_le16(params->rate_min_interval); + cp.interval_max = cpu_to_le16(params->rate_max_interval); + cp.subrate_min = cpu_to_le16(params->subrate_min); + cp.subrate_max = cpu_to_le16(params->subrate_max); + cp.max_latency = cpu_to_le16(params->max_latency); + cp.cont_num = cpu_to_le16(params->cont_num); + cp.supv_timeout = cpu_to_le16(params->rate_supv_timeout); + cp.min_ce_len = cpu_to_le16(0x0000); + cp.max_ce_len = cpu_to_le16(0x0000); + + hci_dev_unlock(hdev); + + return __hci_cmd_sync_status(hdev, HCI_OP_LE_CONN_RATE, + sizeof(cp), &cp, HCI_CMD_TIMEOUT); +} + +static void hci_le_conn_rate_request_destroy(struct hci_dev *hdev, void *data, + int err) +{ + struct hci_conn *conn = data; + + hci_conn_put(conn); +} + +int hci_le_conn_rate_request(struct hci_dev *hdev, struct hci_conn *conn) +{ + int err; + + /* Hold a reference to the connection so it cannot be freed while the + * request is pending or running on the cmd_sync worker. + */ + err = hci_cmd_sync_queue(hdev, hci_le_conn_rate_request_sync, + hci_conn_get(conn), + hci_le_conn_rate_request_destroy); + if (err < 0) + hci_conn_put(conn); + + return err; +} + static void create_pa_complete(struct hci_dev *hdev, void *data, int err) { struct hci_conn *conn = data; diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index fec41af69d62..09edd72acc22 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -8176,6 +8176,118 @@ static int load_conn_param(struct sock *sk, struct hci_dev *hdev, void *data, NULL, 0); } +static int load_conn_subrate(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) +{ + struct mgmt_cp_load_conn_subrate *cp = data; + const u16 max_param_count = ((U16_MAX - sizeof(*cp)) / + sizeof(struct mgmt_conn_subrate)); + u16 param_count, expected_len; + int i; + + if (!lmp_le_capable(hdev) || !le_sci_capable(hdev)) + return mgmt_cmd_status(sk, hdev->id, MGMT_OP_LOAD_CONN_SUBRATE, + MGMT_STATUS_NOT_SUPPORTED); + + param_count = __le16_to_cpu(cp->param_count); + if (param_count > max_param_count) { + bt_dev_err(hdev, "too big param_count value %u", param_count); + return mgmt_cmd_status(sk, hdev->id, MGMT_OP_LOAD_CONN_SUBRATE, + MGMT_STATUS_INVALID_PARAMS); + } + + expected_len = struct_size(cp, params, param_count); + if (expected_len != len) { + bt_dev_err(hdev, "expected %u bytes, got %u bytes", + expected_len, len); + return mgmt_cmd_status(sk, hdev->id, MGMT_OP_LOAD_CONN_SUBRATE, + MGMT_STATUS_INVALID_PARAMS); + } + + bt_dev_dbg(hdev, "param_count %u", param_count); + + hci_dev_lock(hdev); + + for (i = 0; i < param_count; i++) { + struct mgmt_conn_subrate *param = &cp->params[i]; + struct hci_conn_params *hci_param; + u16 min, max, subrate_min, subrate_max; + u16 max_latency, cont_num, supv_timeout; + u8 addr_type; + + bt_dev_dbg(hdev, "Adding subrate %pMR (type %u)", + ¶m->addr.bdaddr, param->addr.type); + + if (param->addr.type == BDADDR_LE_PUBLIC) { + addr_type = ADDR_LE_DEV_PUBLIC; + } else if (param->addr.type == BDADDR_LE_RANDOM) { + addr_type = ADDR_LE_DEV_RANDOM; + } else { + bt_dev_err(hdev, "ignoring invalid connection subrate parameters"); + continue; + } + + min = le16_to_cpu(param->min_interval); + max = le16_to_cpu(param->max_interval); + subrate_min = le16_to_cpu(param->subrate_min); + subrate_max = le16_to_cpu(param->subrate_max); + max_latency = le16_to_cpu(param->max_latency); + cont_num = le16_to_cpu(param->cont_num); + supv_timeout = le16_to_cpu(param->supv_timeout); + + /* Validate the parameters before storing them. Reject + * logically inconsistent values instead of forwarding them to + * the controller. + */ + if (min > max || subrate_min > subrate_max || + subrate_min < 1 || supv_timeout < 1) { + bt_dev_err(hdev, "ignoring invalid connection subrate parameters"); + continue; + } + + hci_param = hci_conn_params_add(hdev, ¶m->addr.bdaddr, + addr_type); + if (!hci_param) { + bt_dev_err(hdev, "failed to add connection parameters"); + continue; + } + + hci_param->rate_min_interval = min; + hci_param->rate_max_interval = max; + hci_param->subrate_min = subrate_min; + hci_param->subrate_max = subrate_max; + hci_param->max_latency = max_latency; + hci_param->cont_num = cont_num; + hci_param->rate_supv_timeout = supv_timeout; + + /* If the device is connected as central check if the + * connection rate parameters need to be updated. + */ + if (!i && param_count == 1) { + struct hci_conn *conn; + + conn = hci_conn_hash_lookup_le(hdev, + &hci_param->addr, + addr_type); + if (conn && conn->state == BT_CONNECTED && + conn->role == HCI_ROLE_MASTER && + (conn->le_rate_interval < min || + conn->le_rate_interval > max || + conn->le_subrate < subrate_min || + conn->le_subrate > subrate_max || + conn->le_rate_latency != max_latency || + conn->le_cont_num != cont_num || + conn->le_rate_supv_timeout != supv_timeout)) + hci_le_conn_rate_request(hdev, conn); + } + } + + hci_dev_unlock(hdev); + + return mgmt_cmd_complete(sk, hdev->id, MGMT_OP_LOAD_CONN_SUBRATE, 0, + NULL, 0); +} + static int set_external_config(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) { @@ -9593,6 +9705,8 @@ static const struct hci_mgmt_handler mgmt_handlers[] = { HCI_MGMT_VAR_LEN }, { mesh_send_cancel, MGMT_MESH_SEND_CANCEL_SIZE }, { mgmt_hci_cmd_sync, MGMT_HCI_CMD_SYNC_SIZE, HCI_MGMT_VAR_LEN }, + { load_conn_subrate, MGMT_LOAD_CONN_SUBRATE_SIZE, + HCI_MGMT_VAR_LEN }, }; void mgmt_index_added(struct hci_dev *hdev) @@ -10741,6 +10855,23 @@ int mgmt_init(void) return hci_mgmt_chan_register(&chan); } +void mgmt_conn_subrate_notify(struct hci_dev *hdev, struct hci_conn *conn, + u8 status) +{ + struct mgmt_ev_conn_subrate ev; + + bacpy(&ev.addr.bdaddr, &conn->dst); + ev.addr.type = link_to_bdaddr(conn->type, conn->dst_type); + ev.status = mgmt_status(status); + ev.interval = cpu_to_le16(conn->le_rate_interval); + ev.subrate = cpu_to_le16(conn->le_subrate); + ev.latency = cpu_to_le16(conn->le_rate_latency); + ev.cont_num = cpu_to_le16(conn->le_cont_num); + ev.supv_timeout = cpu_to_le16(conn->le_rate_supv_timeout); + + mgmt_event(MGMT_EV_CONN_SUBRATE, hdev, &ev, sizeof(ev), NULL); +} + void mgmt_exit(void) { hci_mgmt_chan_unregister(&chan); From ad28b52441bc0b89ebf7ecb79a2765aa23db4d42 Mon Sep 17 00:00:00 2001 From: Kiran K Date: Thu, 23 Jul 2026 06:43:32 +0530 Subject: [PATCH 1072/1433] Bluetooth: btintel: Add Bluetooth SAR revision 2 support BRDS revision 2 introduces per-chain (Chain A and Chain B) TX power limits across five sub-bands (2.4G, 5.2G, 5.8/5.9G, 6G-low, 6G-high), replacing the single-chain per-modulation model of revisions 0 and 1. - Add btintel_set_sar_rev2() which sends the full Rev2 DDC sequence: 0x019e inc-power-mode enable flag (1 byte) 0x0311 2.4 GHz sub-band limits (2 bytes) 0x0312 5.2 GHz sub-band limits (2 bytes) 0x0313 5.8/5.9 GHz sub-band limits (2 bytes) 0x0314 5.8/5.9 GHz sub-band limits again (2 bytes, duplicate FW reg) 0x0315 6 GHz low sub-band limits (2 bytes) 0x0316 6 GHz high sub-band limits (2 bytes) followed by the SAR-init-complete command (0xfe25). logs from dmesg when BTSAR2 is enabled in Coreboot/BIOS: Bluetooth: hci0: BT SAR Rev2: revision=2 bt_sar_bios=1 inc_power_mode=1 Bluetooth: hci0: BT SAR Rev2 Chain A: 2g4=76 5g2=0 5g8_5g9=0 6g1=0 6g3=0 Bluetooth: hci0: BT SAR Rev2 Chain B: 2g4=102 5g2=0 5g8_5g9=0 6g1=0 6g3=0 Signed-off-by: Ravindra Signed-off-by: Kiran K Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel.c | 191 +++++++++++++++++++++++++++++++++++- drivers/bluetooth/btintel.h | 18 ++++ 2 files changed, 208 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index 680f96c188d4..770b1fb371c7 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -51,6 +51,7 @@ enum { #define BTINTEL_BT_DOMAIN 0x12 #define BTINTEL_SAR_LEGACY 0 #define BTINTEL_SAR_INC_PWR 1 +#define BTINTEL_SAR_REV2 2 #define BTINTEL_SAR_INC_PWR_SUPPORTED 0 #define CMD_WRITE_BOOT_PARAMS 0xfc0e @@ -3102,6 +3103,111 @@ static int btintel_set_mutual_sar(struct hci_dev *hdev, struct btintel_sar_inc_p return 0; } +/* btintel_send_sar_rev2_band - send DDC command for one Rev2 sub-band + * + * Each DDC 0x0311-0x0316 carries 2 bytes: [ChainA_value, ChainB_value]. + * cmd->len = 4 (2 id + 2 data) + * HCI total = 5 bytes (1 len + 4) + */ +static int btintel_send_sar_rev2_band(struct hci_dev *hdev, + struct btintel_cp_ddc_write *cmd, + u16 id, u8 chain_a, u8 chain_b) +{ + cmd->len = 4; + cmd->id = cpu_to_le16(id); + cmd->data[0] = chain_a; + cmd->data[1] = chain_b; + return btintel_send_sar_ddc(hdev, cmd, 5); +} + +static int btintel_set_sar_rev2(struct hci_dev *hdev, + struct btintel_sar_rev2 *sar) +{ + struct btintel_cp_ddc_write *cmd; + struct sk_buff *skb; + u8 buffer[64]; + u8 enable; + int ret; + + cmd = (void *)buffer; + + /* DDC 0x019e: enable/disable increased power mode SAR (1 byte) */ + cmd->len = 3; + cmd->id = cpu_to_le16(0x019e); + cmd->data[0] = (sar->inc_power_mode == BTINTEL_SAR_INC_PWR_SUPPORTED) ? + 0x01 : 0x00; + ret = btintel_send_sar_ddc(hdev, cmd, 4); + if (ret) + return ret; + + /* DDC 0x0311-0x0316: per sub-band ChainA + ChainB limits */ + ret = btintel_send_sar_rev2_band(hdev, cmd, 0x0311, + sar->chain_a.subband_2g4, + sar->chain_b.subband_2g4); + if (ret) + return ret; + + ret = btintel_send_sar_rev2_band(hdev, cmd, 0x0312, + sar->chain_a.subband_5g2, + sar->chain_b.subband_5g2); + if (ret) + return ret; + + /* 0x0313 and 0x0314 both carry the 5G8/5G9 value */ + ret = btintel_send_sar_rev2_band(hdev, cmd, 0x0313, + sar->chain_a.subband_5g8_5g9, + sar->chain_b.subband_5g8_5g9); + if (ret) + return ret; + + ret = btintel_send_sar_rev2_band(hdev, cmd, 0x0314, + sar->chain_a.subband_5g8_5g9, + sar->chain_b.subband_5g8_5g9); + if (ret) + return ret; + + ret = btintel_send_sar_rev2_band(hdev, cmd, 0x0315, + sar->chain_a.subband_6g1, + sar->chain_b.subband_6g1); + if (ret) + return ret; + + ret = btintel_send_sar_rev2_band(hdev, cmd, 0x0316, + sar->chain_a.subband_6g3, + sar->chain_b.subband_6g3); + if (ret) + return ret; + + /* Notify firmware that SAR initialisation is complete */ + enable = 0x01; + skb = __hci_cmd_sync(hdev, 0xfe25, sizeof(enable), &enable, HCI_CMD_TIMEOUT); + if (IS_ERR(skb)) { + bt_dev_warn(hdev, "Failed to send Intel SAR Rev2 Enable (%ld)", + PTR_ERR(skb)); + return PTR_ERR(skb); + } + + kfree_skb(skb); + return 0; +} + +static int btintel_sar_rev2_send_to_device(struct hci_dev *hdev, + struct btintel_sar_rev2 *sar, + struct intel_version_tlv *ver) +{ + u16 cnvi = ver->cnvi_top & 0xfff; + u16 cnvr = ver->cnvr_top & 0xfff; + + if (cnvi < BTINTEL_CNVI_BLAZARI || cnvr != BTINTEL_CNVR_WHP2) { + bt_dev_dbg(hdev, "BT SAR Rev2 not supported on this platform (cnvi=0x%x cnvr=0x%x)", + cnvi, cnvr); + return -EOPNOTSUPP; + } + + bt_dev_info(hdev, "Applying Bluetooth SAR Rev2"); + return btintel_set_sar_rev2(hdev, sar); +} + static int btintel_sar_send_to_device(struct hci_dev *hdev, struct btintel_sar_inc_pwr *sar, struct intel_version_tlv *ver) { @@ -3128,6 +3234,7 @@ static int btintel_acpi_set_sar(struct hci_dev *hdev, struct intel_version_tlv * { union acpi_object *bt_pkg, *buffer = NULL; struct btintel_sar_inc_pwr sar; + struct btintel_sar_rev2 sar_rev2; acpi_status status; u8 revision; int ret; @@ -3148,14 +3255,96 @@ static int btintel_acpi_set_sar(struct hci_dev *hdev, struct intel_version_tlv * goto error; } + if (buffer->package.elements[0].type != ACPI_TYPE_INTEGER) { + bt_dev_warn(hdev, "BT_SAR: unexpected ACPI type for revision field"); + ret = -EINVAL; + goto error; + } + revision = buffer->package.elements[0].integer.value; - if (revision > BTINTEL_SAR_INC_PWR) { + if (revision > BTINTEL_SAR_REV2) { bt_dev_dbg(hdev, "BT_SAR: revision: 0x%2.2x not supported", revision); ret = -EOPNOTSUPP; goto error; } + if (revision == BTINTEL_SAR_REV2 && bt_pkg->package.count == 13) { + /* Element layout: 0 = domain ID (BTINTEL_BT_DOMAIN, 0x12), + * 1 = bt_sar_bios (u32), 2 = inc_power_mode (u32), + * 3..12 = per-chain sub-band limits (u8 each). + */ + static const u64 rev2_max[13] = { + U8_MAX, /* domain ID */ + U32_MAX, U32_MAX, /* bt_sar_bios, inc_power_mode */ + U8_MAX, U8_MAX, U8_MAX, U8_MAX, U8_MAX, /* chain A */ + U8_MAX, U8_MAX, U8_MAX, U8_MAX, U8_MAX, /* chain B */ + }; + union acpi_object *e; + int i; + + for (i = 0; i < 13; i++) { + e = &bt_pkg->package.elements[i]; + if (e->type != ACPI_TYPE_INTEGER) { + bt_dev_warn(hdev, "BT SAR Rev2: unexpected ACPI type at element %d", + i); + ret = -EINVAL; + goto error; + } + if (e->integer.value > rev2_max[i]) { + bt_dev_warn(hdev, "BT SAR Rev2: element %d value 0x%llx out of range", + i, e->integer.value); + ret = -ERANGE; + goto error; + } + } + + memset(&sar_rev2, 0, sizeof(sar_rev2)); + sar_rev2.revision = revision; + sar_rev2.bt_sar_bios = bt_pkg->package.elements[1].integer.value; + + if (sar_rev2.bt_sar_bios != 1) { + bt_dev_warn(hdev, "Bluetooth SAR Rev2 is not enabled"); + ret = -EOPNOTSUPP; + goto error; + } + + sar_rev2.inc_power_mode = bt_pkg->package.elements[2].integer.value; + + sar_rev2.chain_a.subband_2g4 = bt_pkg->package.elements[3].integer.value; + sar_rev2.chain_a.subband_5g2 = bt_pkg->package.elements[4].integer.value; + sar_rev2.chain_a.subband_5g8_5g9 = bt_pkg->package.elements[5].integer.value; + sar_rev2.chain_a.subband_6g1 = bt_pkg->package.elements[6].integer.value; + sar_rev2.chain_a.subband_6g3 = bt_pkg->package.elements[7].integer.value; + + sar_rev2.chain_b.subband_2g4 = bt_pkg->package.elements[8].integer.value; + sar_rev2.chain_b.subband_5g2 = bt_pkg->package.elements[9].integer.value; + sar_rev2.chain_b.subband_5g8_5g9 = bt_pkg->package.elements[10].integer.value; + sar_rev2.chain_b.subband_6g1 = bt_pkg->package.elements[11].integer.value; + sar_rev2.chain_b.subband_6g3 = bt_pkg->package.elements[12].integer.value; + + bt_dev_dbg(hdev, "BT SAR Rev2: revision=%u bt_sar_bios=%u inc_power_mode=%u", + sar_rev2.revision, sar_rev2.bt_sar_bios, sar_rev2.inc_power_mode); + bt_dev_dbg(hdev, "BT SAR Rev2 Chain A: 2g4=%u 5g2=%u 5g8_5g9=%u 6g1=%u 6g3=%u", + sar_rev2.chain_a.subband_2g4, sar_rev2.chain_a.subband_5g2, + sar_rev2.chain_a.subband_5g8_5g9, sar_rev2.chain_a.subband_6g1, + sar_rev2.chain_a.subband_6g3); + bt_dev_dbg(hdev, "BT SAR Rev2 Chain B: 2g4=%u 5g2=%u 5g8_5g9=%u 6g1=%u 6g3=%u", + sar_rev2.chain_b.subband_2g4, sar_rev2.chain_b.subband_5g2, + sar_rev2.chain_b.subband_5g8_5g9, sar_rev2.chain_b.subband_6g1, + sar_rev2.chain_b.subband_6g3); + + ret = btintel_sar_rev2_send_to_device(hdev, &sar_rev2, ver); + goto error; + } + + if (revision == BTINTEL_SAR_REV2) { + bt_dev_warn(hdev, "BT SAR Rev2: unexpected ACPI package count %d (expected 13)", + bt_pkg->package.count); + ret = -EINVAL; + goto error; + } + memset(&sar, 0, sizeof(sar)); if (revision == BTINTEL_SAR_LEGACY && bt_pkg->package.count == 8) { diff --git a/drivers/bluetooth/btintel.h b/drivers/bluetooth/btintel.h index 37d93abdd5a3..966ec1b02be2 100644 --- a/drivers/bluetooth/btintel.h +++ b/drivers/bluetooth/btintel.h @@ -65,6 +65,7 @@ struct intel_tlv { /* CNVR */ #define BTINTEL_CNVR_FMP2 0x910 +#define BTINTEL_CNVR_WHP2 0xA10 /* Whale Peak2 - Panther Lake */ #define BTINTEL_IMG_BOOTLOADER 0x01 /* Bootloader image */ #define BTINTEL_IMG_IML 0x02 /* Intermediate image */ @@ -204,6 +205,23 @@ struct btintel_sar_inc_pwr { u8 le_lr; }; +/* Bluetooth SAR feature (BRDS), Revision 2 - per-chain sub-band power limits */ +struct btintel_sar_band_limits { + u8 subband_2g4; + u8 subband_5g2; + u8 subband_5g8_5g9; + u8 subband_6g1; + u8 subband_6g3; +}; + +struct btintel_sar_rev2 { + u8 revision; + u32 bt_sar_bios; /* 1: BIOS-managed SAR enabled */ + u32 inc_power_mode; /* 0: supported, 1: disabled */ + struct btintel_sar_band_limits chain_a; + struct btintel_sar_band_limits chain_b; +}; + #define INTEL_HW_PLATFORM(cnvx_bt) ((u8)(((cnvx_bt) & 0x0000ff00) >> 8)) #define INTEL_HW_VARIANT(cnvx_bt) ((u8)(((cnvx_bt) & 0x003f0000) >> 16)) #define INTEL_CNVX_TOP_TYPE(cnvx_top) ((cnvx_top) & 0x00000fff) From 2bf6b9baca9372ea51b6d0f2820dc9bf29a83ef4 Mon Sep 17 00:00:00 2001 From: Laxman Acharya Padhya Date: Thu, 30 Jul 2026 18:05:28 +0545 Subject: [PATCH 1073/1433] Bluetooth: hci_aml: validate firmware segment lengths aml_download_firmware() reads two lengths from the firmware header and uses them to build pointers before checking that the header and segment data are present. A truncated or inconsistent firmware image can make the driver read past firmware->data while constructing TCI commands. Reject images shorter than the header and ensure that the ICCM and DCCM ranges fit within the loaded firmware before downloading either segment. Fixes: 37bac77e4649 ("Bluetooth: hci_uart: Add support for Amlogic HCI UART") Cc: stable@vger.kernel.org Signed-off-by: Laxman Acharya Padhya Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/hci_aml.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/hci_aml.c b/drivers/bluetooth/hci_aml.c index 959d9e67b669..067fbf278b44 100644 --- a/drivers/bluetooth/hci_aml.c +++ b/drivers/bluetooth/hci_aml.c @@ -247,7 +247,7 @@ static int aml_download_firmware(struct hci_dev *hdev, const char *fw_name) struct hci_uart *hu = hci_get_drvdata(hdev); struct aml_serdev *amldev = serdev_device_get_drvdata(hu->serdev); const struct firmware *firmware = NULL; - struct aml_fw_len *fw_len = NULL; + const struct aml_fw_len *fw_len = NULL; u8 *iccm_start = NULL, *dccm_start = NULL; u32 iccm_len, dccm_len; u32 value = 0; @@ -281,7 +281,21 @@ static int aml_download_firmware(struct hci_dev *hdev, const char *fw_name) goto exit; } - fw_len = (struct aml_fw_len *)firmware->data; + if (firmware->size < sizeof(*fw_len)) { + bt_dev_err(hdev, "Firmware is too small for its header"); + ret = -EINVAL; + goto exit; + } + + fw_len = (const struct aml_fw_len *)firmware->data; + if (fw_len->iccm_len < amldev->aml_dev_data->iccm_offset || + fw_len->iccm_len > firmware->size - sizeof(*fw_len) || + fw_len->dccm_len > firmware->size - sizeof(*fw_len) - + fw_len->iccm_len) { + bt_dev_err(hdev, "Invalid firmware segment lengths"); + ret = -EINVAL; + goto exit; + } /* Download ICCM */ iccm_start = (u8 *)(firmware->data) + sizeof(struct aml_fw_len) From 33af47e847fe4a28b109673affb5874015d54f5a Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Thu, 30 Jul 2026 16:32:02 +0800 Subject: [PATCH 1074/1433] Bluetooth: hci_event: fix LE list UAF on reset hci_cc_reset() clears the LE accept and resolving lists without taking hdev->lock. Other command-complete handlers serialize updates to these lists with that lock, and the debugfs readers hold it while walking them. This permits the reset completion and a debugfs read to interleave as follows: hci_rx_work debugfs reader ----------- -------------- lock hdev->lock fetch current entry list_del(entry) kfree(entry) read entry fields The reader then dereferences a freed list entry and may follow its stale next pointer. KASAN reported: BUG: KASAN: slab-use-after-free in white_list_show+0x15f/0x180 Read of size 1 at addr ffff8881015dab16 by task poc/95 Call Trace: white_list_show+0x15f/0x180 seq_read_iter+0x3ff/0x1190 seq_read+0x267/0x3d0 vfs_read+0x177/0xa20 ksys_read+0xf7/0x1c0 Allocated by task 91: hci_bdaddr_list_add+0x1a6/0x3a0 hci_cc_le_add_to_accept_list+0xab/0x140 hci_cmd_complete_evt+0x26c/0x9a0 hci_event_packet+0x454/0xb20 hci_rx_work+0x293/0x730 Freed by task 90: kfree+0x131/0x3c0 hci_bdaddr_list_clear+0xd8/0x160 hci_cc_reset+0x28a/0x370 hci_cmd_complete_evt+0x26c/0x9a0 hci_event_packet+0x454/0xb20 hci_rx_work+0x293/0x730 Take hdev->lock around both list clears. This matches the existing mutation and traversal locking convention. Fixes: a4d5504d5c39 ("Bluetooth: Clear LE white list when resetting controller") Fixes: cfdb0c2d095a ("Bluetooth: Store Resolv list size") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_event.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 9764efb9e293..9c8bf6708356 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -297,8 +297,10 @@ static u8 hci_cc_reset(struct hci_dev *hdev, void *data, struct sk_buff *skb) hdev->ssp_debug_mode = 0; + hci_dev_lock(hdev); hci_bdaddr_list_clear(&hdev->le_accept_list); hci_bdaddr_list_clear(&hdev->le_resolv_list); + hci_dev_unlock(hdev); return rp->status; } From eb7e88e359882def910ccdc061d981a60d43f8ff Mon Sep 17 00:00:00 2001 From: oshada imalka Date: Thu, 30 Jul 2026 16:55:09 +0530 Subject: [PATCH 1075/1433] Bluetooth: hcli_ldisc: Remove reduntant braces Removed a redundant braces for a single if statement Signed-off-by: Oshada Imalka Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/hci_ldisc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c index 46dfbe6f1c2e..58f5504a336e 100644 --- a/drivers/bluetooth/hci_ldisc.c +++ b/drivers/bluetooth/hci_ldisc.c @@ -762,9 +762,9 @@ static int hci_uart_set_proto(struct hci_uart *hu, int id) hu->proto = p; err = hci_uart_register_dev(hu); - if (err) { + if (err) return err; - } + set_bit(HCI_UART_PROTO_READY, &hu->flags); clear_bit(HCI_UART_PROTO_INIT, &hu->flags); From 502adc06ba76dee19c292ae4a07d74d202fe734d Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Thu, 30 Jul 2026 10:57:28 +0900 Subject: [PATCH 1076/1433] Bluetooth: virtio_bt: avoid OOB read of build info string The virtbt_setup_zephyr() sends the Zephyr vendor command 0xfc08 (Read Build Information) and hands the response to bt_dev_info() and hci_set_fw_info() as a "%s" string starting at skb->data + 1, without checking the length. A backend that answers with status only leaves that pointer past the end of the received data, so the walk reads adjacent slab memory until it meets a NUL. Those bytes reach the kernel log and the firmware-info debugfs file. To fix this, print the string with a bounded "%.*s" limited to skb->len - 1. A short or unterminated response then prints as much as arrived instead of failing setup. This mirrors commit dd068ef04412 ("Bluetooth: bpa10x: avoid OOB read of revision string in bpa10x_setup()"), which fixed the identical pattern. Fixes: afd2daa26c7a ("Bluetooth: Add support for virtio transport driver") Signed-off-by: HyeongJun An Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/virtio_bt.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/virtio_bt.c b/drivers/bluetooth/virtio_bt.c index 140ab55c9fc5..c20d54088c8c 100644 --- a/drivers/bluetooth/virtio_bt.c +++ b/drivers/bluetooth/virtio_bt.c @@ -120,9 +120,13 @@ static int virtbt_setup_zephyr(struct hci_dev *hdev) if (IS_ERR(skb)) return PTR_ERR(skb); - bt_dev_info(hdev, "%s", (char *)(skb->data + 1)); + /* Bounded print: the backend controls skb->len. */ + if (skb->len > 1) { + int len = skb->len - 1; - hci_set_fw_info(hdev, "%s", skb->data + 1); + bt_dev_info(hdev, "%.*s", len, (char *)(skb->data + 1)); + hci_set_fw_info(hdev, "%.*s", len, skb->data + 1); + } kfree_skb(skb); return 0; From 2b66c83ff1751d6bd3201b3017206262ab46dc05 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 1 Aug 2026 21:37:23 +0300 Subject: [PATCH 1077/1433] Bluetooth: L2CAP: use proto_lock for l2cap_data to fix l2cap_disconn_ind hci_conn::l2cap_data is accessed without locks in l2cap_disconn_ind via hci_conn_timeout (disc_work) -> hci_proto_disconn_ind -> l2cap_disconn_ind. This is UAF if the l2cap_conn is deleted concurrently. disc_work is disabled sync in hci_conn_del(), so we cannot take hci_dev_lock in disc_work. Fix by using proto_lock to guard l2cap_data, in addition to hdev->lock which is held in other access paths. Fixes: ab4eedb790ca ("Bluetooth: L2CAP: Fix corrupted list in hci_chan_del") Reported-by: syzbot+9c40ad7c6ed7165e46e8@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=9c40ad7c6ed7165e46e8 Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/l2cap_core.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 1156aba4e83c..30d7120d3a15 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -1833,7 +1833,10 @@ static void l2cap_conn_del(struct hci_conn *hcon, int err) hci_chan_del(conn->hchan); conn->hchan = NULL; + spin_lock(&hcon->proto_lock); hcon->l2cap_data = NULL; + spin_unlock(&hcon->proto_lock); + mutex_unlock(&conn->lock); l2cap_conn_put(conn); } @@ -7168,8 +7171,6 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon) } kref_init(&conn->ref); - hcon->l2cap_data = conn; - conn->hcon = hci_conn_get(hcon); conn->hchan = hchan; BT_DBG("hcon %p conn %p hchan %p", hcon, conn, hchan); @@ -7198,6 +7199,11 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon) conn->disc_reason = HCI_ERROR_REMOTE_USER_TERM; + spin_lock(&hcon->proto_lock); + conn->hcon = hci_conn_get(hcon); + hcon->l2cap_data = conn; + spin_unlock(&hcon->proto_lock); + return conn; } @@ -7582,13 +7588,18 @@ static void l2cap_connect_cfm(struct hci_conn *hcon, u8 status) int l2cap_disconn_ind(struct hci_conn *hcon) { - struct l2cap_conn *conn = hcon->l2cap_data; + struct l2cap_conn *conn; + int ret = HCI_ERROR_REMOTE_USER_TERM; BT_DBG("hcon %p", hcon); - if (!conn) - return HCI_ERROR_REMOTE_USER_TERM; - return conn->disc_reason; + spin_lock(&hcon->proto_lock); + conn = hcon->l2cap_data; + if (conn) + ret = conn->disc_reason; + spin_unlock(&hcon->proto_lock); + + return ret; } static void l2cap_disconn_cfm(struct hci_conn *hcon, u8 reason) From b80de2cbb1c8c9352f2bed879dd9427e18f9b34b Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 1 Aug 2026 21:37:24 +0300 Subject: [PATCH 1078/1433] Bluetooth: add annotations for l2cap_data locking context Add context analysis annotations for hci_conn::l2cap_data locking. Also add necessary lockdep_assert_held() and __must_hold annotations to prove the access is safe. The access in smp_conn_security() is supposed to be guarded by the caller holding lock that blocks concurrent l2cap_conn_del() eg. hdev->lock, conn->lock or chan->lock. Mark unsafe as can't be automatically checked now. Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/hci_core.h | 2 +- net/bluetooth/6lowpan.c | 2 ++ net/bluetooth/l2cap_core.c | 9 +++++++++ net/bluetooth/mgmt.c | 2 ++ net/bluetooth/smp.c | 7 ++++++- net/bluetooth/smp.h | 6 ++++-- 6 files changed, 24 insertions(+), 4 deletions(-) diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 01b938c4b24a..c299daac7fbe 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -775,7 +775,7 @@ struct hci_conn { struct hci_dev *hdev; spinlock_t proto_lock; /* lock guarding protocol data */ - void *l2cap_data; + void *l2cap_data __guarded_by(&proto_lock, &hdev->lock); void *sco_data; void *iso_data __guarded_by(&proto_lock); diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c index d504a363a30f..30f4afa18bc8 100644 --- a/net/bluetooth/6lowpan.c +++ b/net/bluetooth/6lowpan.c @@ -1007,6 +1007,8 @@ static int get_l2cap_conn(char *buf, bdaddr_t *addr, u8 *addr_type, return -ENOENT; } + lockdep_assert_held(&hcon->hdev->lock); + *conn = l2cap_conn_hold_unless_zero(hcon->l2cap_data); BT_DBG("conn %p dst %pMR type %u", *conn, &hcon->dst, hcon->dst_type); diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 30d7120d3a15..ee459dd411f5 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -1791,6 +1791,7 @@ static void l2cap_unregister_all_users(struct l2cap_conn *conn) } static void l2cap_conn_del(struct hci_conn *hcon, int err) + __must_hold(&hcon->hdev->lock) { struct l2cap_conn *conn = hcon->l2cap_data; struct l2cap_chan *chan, *l; @@ -7153,6 +7154,7 @@ static void process_pending_rx(struct work_struct *work) } static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon) + __must_hold(&hcon->hdev->lock) { struct l2cap_conn *conn = hcon->l2cap_data; struct hci_chan *hchan; @@ -7358,6 +7360,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid, goto done; } + lockdep_assert_held(&hcon->hdev->lock); + conn = l2cap_conn_add(hcon); if (!conn) { hci_conn_drop(hcon); @@ -7528,6 +7532,7 @@ static struct l2cap_chan *l2cap_global_fixed_chan(struct l2cap_chan *c, } static void l2cap_connect_cfm(struct hci_conn *hcon, u8 status) + __must_hold(&hcon->hdev->lock) { struct hci_dev *hdev = hcon->hdev; struct l2cap_conn *conn; @@ -7603,6 +7608,7 @@ int l2cap_disconn_ind(struct hci_conn *hcon) } static void l2cap_disconn_cfm(struct hci_conn *hcon, u8 reason) + __must_hold(&hcon->hdev->lock) { if (hcon->type != ACL_LINK && hcon->type != LE_LINK) return; @@ -7630,6 +7636,7 @@ static inline void l2cap_check_encryption(struct l2cap_chan *chan, u8 encrypt) } static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt) + __must_hold(&hcon->hdev->lock) { struct l2cap_conn *conn = hcon->l2cap_data; struct l2cap_chan *chan; @@ -7814,6 +7821,8 @@ int l2cap_recv_acldata(struct hci_dev *hdev, u16 handle, return -ENOENT; } + lockdep_assert_held(&hcon->hdev->lock); + hci_conn_enter_active_mode(hcon, BT_POWER_FORCE_ACTIVE_OFF); conn = hcon->l2cap_data; diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 09edd72acc22..0d6b41fe0b34 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -3876,6 +3876,8 @@ static int user_pairing_resp(struct sock *sk, struct hci_dev *hdev, } if (addr->type == BDADDR_LE_PUBLIC || addr->type == BDADDR_LE_RANDOM) { + lockdep_assert_held(&conn->hdev->lock); + err = smp_user_confirm_reply(conn, mgmt_op, passkey); if (!err) err = mgmt_cmd_complete(sk, hdev->id, mgmt_op, diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c index c4470958b0d5..f23b695c487b 100644 --- a/net/bluetooth/smp.c +++ b/net/bluetooth/smp.c @@ -2327,12 +2327,15 @@ static void smp_send_security_req(struct smp_chan *smp, __u8 auth) int smp_conn_security(struct hci_conn *hcon, __u8 sec_level) { - struct l2cap_conn *conn = hcon->l2cap_data; + struct l2cap_conn *conn; struct l2cap_chan *chan; struct smp_chan *smp; __u8 authreq; int ret; + /* Caller shall ensure there can be no race with l2cap_conn_del() */ + conn = context_unsafe(hcon->l2cap_data); + bt_dev_dbg(hcon->hdev, "conn %p hcon %p level 0x%2.2x", conn, hcon, sec_level); @@ -2421,6 +2424,8 @@ int smp_cancel_and_remove_pairing(struct hci_dev *hdev, bdaddr_t *bdaddr, if (!hcon) goto done; + lockdep_assert_held(&hcon->hdev->lock); + conn = hcon->l2cap_data; if (!conn) goto done; diff --git a/net/bluetooth/smp.h b/net/bluetooth/smp.h index eac27bd541bb..c86c46389007 100644 --- a/net/bluetooth/smp.h +++ b/net/bluetooth/smp.h @@ -180,11 +180,13 @@ enum smp_key_pref { /* SMP Commands */ int smp_cancel_and_remove_pairing(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 addr_type); + u8 addr_type) + __must_hold(&hdev->lock); bool smp_sufficient_security(struct hci_conn *hcon, u8 sec_level, enum smp_key_pref key_pref); int smp_conn_security(struct hci_conn *hcon, __u8 sec_level); -int smp_user_confirm_reply(struct hci_conn *conn, u16 mgmt_op, __le32 passkey); +int smp_user_confirm_reply(struct hci_conn *conn, u16 mgmt_op, __le32 passkey) + __must_hold(&conn->hdev->lock); bool smp_irk_matches(struct hci_dev *hdev, const u8 irk[16], const bdaddr_t *bdaddr); From bd76a28a739dd4d15ac119c7f9cfe3f7cf8a85c0 Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Tue, 4 Aug 2026 11:46:30 +0200 Subject: [PATCH 1079/1433] Bluetooth: btmrvl: fix event packet length validation The event length validation added by 65be90af2756 commit used a single check against sizeof(*event), which assumed every event type uses the maximum payload size. Unfortunately event packet length depends on the type of the received event, so it must be checked separately for each event type to avoid rejecting some known well-formed events. Fixes: 65be90af2756 ("Bluetooth: btmrvl: validate event packet lengths") Signed-off-by: Marek Szyprowski Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmrvl_main.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btmrvl_main.c b/drivers/bluetooth/btmrvl_main.c index aaf1614ccfd7..e25930351f64 100644 --- a/drivers/bluetooth/btmrvl_main.c +++ b/drivers/bluetooth/btmrvl_main.c @@ -81,7 +81,7 @@ int btmrvl_process_event(struct btmrvl_private *priv, struct sk_buff *skb) struct btmrvl_event *event; int ret = 0; - if (skb->len < sizeof(*event)) + if (skb->len <= offsetof(typeof(*event), data[0])) return -EINVAL; event = (struct btmrvl_event *) skb->data; @@ -93,6 +93,8 @@ int btmrvl_process_event(struct btmrvl_private *priv, struct sk_buff *skb) switch (event->data[0]) { case BT_EVENT_AUTO_SLEEP_MODE: + if (skb->len <= offsetof(typeof(*event), data[2])) + return -EINVAL; if (!event->data[2]) { if (event->data[1] == BT_PS_ENABLE) adapter->psmode = 1; @@ -106,6 +108,8 @@ int btmrvl_process_event(struct btmrvl_private *priv, struct sk_buff *skb) break; case BT_EVENT_HOST_SLEEP_CONFIG: + if (skb->len <= offsetof(typeof(*event), data[3])) + return -EINVAL; if (!event->data[3]) BT_DBG("gpio=%x, gap=%x", event->data[1], event->data[2]); @@ -114,6 +118,8 @@ int btmrvl_process_event(struct btmrvl_private *priv, struct sk_buff *skb) break; case BT_EVENT_HOST_SLEEP_ENABLE: + if (skb->len <= offsetof(typeof(*event), data[1])) + return -EINVAL; if (!event->data[1]) { adapter->hs_state = HS_ACTIVATED; if (adapter->psmode) @@ -126,6 +132,8 @@ int btmrvl_process_event(struct btmrvl_private *priv, struct sk_buff *skb) break; case BT_EVENT_MODULE_CFG_REQ: + if (skb->len <= offsetof(typeof(*event), data[2])) + return -EINVAL; if (priv->btmrvl_dev.sendcmdflag && event->data[1] == MODULE_BRINGUP_REQ) { BT_DBG("EVENT:%s", @@ -143,6 +151,8 @@ int btmrvl_process_event(struct btmrvl_private *priv, struct sk_buff *skb) break; case BT_EVENT_POWER_STATE: + if (skb->len <= offsetof(typeof(*event), data[1])) + return -EINVAL; if (event->data[1] == BT_PS_SLEEP) adapter->ps_state = PS_SLEEP; BT_DBG("EVENT:%s", From 0acd4eeb4b225b9bebbf9ef96cc10cdd79b94899 Mon Sep 17 00:00:00 2001 From: Laxman Acharya Padhya Date: Sat, 1 Aug 2026 23:54:52 +0545 Subject: [PATCH 1080/1433] Bluetooth: hci_event: validate LE Set CIG Parameters response The Command Complete dispatch validates only the fixed part of the LE Set CIG Parameters response. After that part is pulled from the skb, hci_cc_le_set_cig_params() trusts num_handles and reads each entry in the trailing handle array. Matching num_handles against the command's num_cis does not guarantee that the response contains the advertised handles. A truncated response from a malfunctioning controller can therefore make the handler read beyond the skb data. Validate that the remaining skb data contains all advertised handles. Include this in the existing response validation so malformed responses also follow the established CIG failure handling. Fixes: 26afbd826ee3 ("Bluetooth: Add initial implementation of CIS connections") Cc: stable@vger.kernel.org Signed-off-by: Laxman Acharya Padhya Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_event.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 9c8bf6708356..6890d60ade93 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -3865,8 +3865,10 @@ static u8 hci_cc_le_set_cig_params(struct hci_dev *hdev, void *data, bt_dev_dbg(hdev, "status 0x%2.2x", rp->status); cp = hci_sent_cmd_data(hdev, HCI_OP_LE_SET_CIG_PARAMS); - if (!rp->status && (!cp || rp->num_handles != cp->num_cis || - rp->cig_id != cp->cig_id)) { + if (!rp->status && + (!cp || rp->num_handles != cp->num_cis || + rp->cig_id != cp->cig_id || + skb->len < array_size(rp->num_handles, sizeof(*rp->handle)))) { bt_dev_err(hdev, "unexpected Set CIG Parameters response data"); status = HCI_ERROR_UNSPECIFIED; } From ad0e7ac7da9a9a0095570bd6add3e27f259de104 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Sat, 1 Aug 2026 23:31:36 -0700 Subject: [PATCH 1081/1433] Bluetooth: btintel: Fix diagnostics event detection For a diagnostics VSE, diagnostics_hdr[] sits at the start of the event payload, skb->data[2], but btintel_recv_event() wrongly guards its memcmp with @len, which is measured from skb->data[3] for the earlier INTEL_BOOTLOADER check. Fix by using (@len + 1) instead, which == (skb->len - HCI_EVENT_HDR_SIZE) exactly. Fixes: af395330abed ("Bluetooth: btintel: Add Intel devcoredump support") Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index 770b1fb371c7..d06a335ff1db 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -4021,7 +4021,7 @@ int btintel_recv_event(struct hci_dev *hdev, struct sk_buff *skb) /* Handle all diagnostics events separately. May still call * hci_recv_frame. */ - if (len >= sizeof(diagnostics_hdr) && + if (len + 1 >= sizeof(diagnostics_hdr) && memcmp(&skb->data[2], diagnostics_hdr, sizeof(diagnostics_hdr)) == 0) { return btintel_diagnostics(hdev, skb); From d39667cb0472843d25d414ee1cb8f02cbb263be3 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Sat, 1 Aug 2026 23:31:37 -0700 Subject: [PATCH 1082/1433] Bluetooth: btintel: Remove redundant (hdr->plen > 0) in btintel_recv_event() Drop the check since: - it is already implied by the existing (skb->len > HCI_EVENT_HDR_SIZE) - hdr->plen is then not used by the function at all Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index d06a335ff1db..bcb2514b7bc0 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -3991,8 +3991,7 @@ int btintel_recv_event(struct hci_dev *hdev, struct sk_buff *skb) struct hci_event_hdr *hdr = (void *)skb->data; const char diagnostics_hdr[] = { 0x87, 0x80, 0x03 }; - if (skb->len > HCI_EVENT_HDR_SIZE && hdr->evt == 0xff && - hdr->plen > 0) { + if (skb->len > HCI_EVENT_HDR_SIZE && hdr->evt == 0xff) { const void *ptr = skb->data + HCI_EVENT_HDR_SIZE + 1; unsigned int len = skb->len - HCI_EVENT_HDR_SIZE - 1; From 8824e13fb5dcc40ae78862a92d2f8bf48a934477 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Sat, 1 Aug 2026 23:31:38 -0700 Subject: [PATCH 1083/1433] Bluetooth: coredump: Expose header size and end marker to drivers To separate the coredump header and data far more easily, give a vendor driver the option to pad its header to a fixed size, by moving the header size limit and ending marker to coredump.h: - HCI_DEVCD_HDR_SIZE_MAX: the max header size - HCI_DEVCD_HDR_END_MARKER: the header-ending marker Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/coredump.h | 7 +++++++ net/bluetooth/coredump.c | 7 ++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/include/net/bluetooth/coredump.h b/include/net/bluetooth/coredump.h index ab85a6adfffd..1f071ab55416 100644 --- a/include/net/bluetooth/coredump.h +++ b/include/net/bluetooth/coredump.h @@ -8,6 +8,13 @@ #define DEVCOREDUMP_TIMEOUT msecs_to_jiffies(10000) /* 10 sec */ +/* + * Max header size, shared by both the devcoredump core and + * the dmp_hdr() registered by driver via hci_devcd_register() + */ +#define HCI_DEVCD_HDR_SIZE_MAX 512 +#define HCI_DEVCD_HDR_END_MARKER "--- Start dump ---\n" + typedef void (*coredump_t)(struct hci_dev *hdev); typedef void (*dmp_hdr_t)(struct hci_dev *hdev, struct sk_buff *skb); typedef void (*notify_change_t)(struct hci_dev *hdev, int state); diff --git a/net/bluetooth/coredump.c b/net/bluetooth/coredump.c index 913bbba559f8..5bee863bd6d2 100644 --- a/net/bluetooth/coredump.c +++ b/net/bluetooth/coredump.c @@ -34,8 +34,6 @@ struct hci_devcoredump_skb_pattern { hci_dmp_cb(skb)->pkt_type, \ hci_devcd_state_name(hdev->dump.state)) -#define MAX_DEVCOREDUMP_HDR_SIZE 512 /* bytes */ - static int hci_devcd_update_hdr_state(char *buf, size_t size, int state) { int len = 0; @@ -63,7 +61,6 @@ static int hci_devcd_update_state(struct hci_dev *hdev, int state) static int hci_devcd_mkheader(struct hci_dev *hdev, struct sk_buff *skb) { - char dump_start[] = "--- Start dump ---\n"; char hdr[80]; int hdr_len; @@ -74,7 +71,7 @@ static int hci_devcd_mkheader(struct hci_dev *hdev, struct sk_buff *skb) if (hdev->dump.dmp_hdr) hdev->dump.dmp_hdr(hdev, skb); - skb_put_data(skb, dump_start, strlen(dump_start)); + skb_put_data(skb, HCI_DEVCD_HDR_END_MARKER, strlen(HCI_DEVCD_HDR_END_MARKER)); return skb->len; } @@ -154,7 +151,7 @@ static int hci_devcd_prepare(struct hci_dev *hdev, u32 dump_size) int dump_hdr_size; int err = 0; - skb = alloc_skb(MAX_DEVCOREDUMP_HDR_SIZE, GFP_ATOMIC); + skb = alloc_skb(HCI_DEVCD_HDR_SIZE_MAX, GFP_ATOMIC); if (!skb) return -ENOMEM; From e6997c120c62381700f8fdf0c3bdc46d2d6fe698 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Sat, 1 Aug 2026 23:31:39 -0700 Subject: [PATCH 1084/1433] Bluetooth: hci_core: Introduce __hci_reset_dev() with a hardware error code hci_reset_dev() injects a constant hardware error code 0x00 to restart the device. But a transport driver may need a different error code. Fix by introducing __hci_reset_dev(hdev, hw_err_code), which will be used by a follow-up patch. Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/hci_core.h | 8 +++++++- net/bluetooth/hci_core.c | 6 +++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index c299daac7fbe..6ff47f9bf758 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -1785,7 +1785,13 @@ int hci_register_suspend_notifier(struct hci_dev *hdev); int hci_unregister_suspend_notifier(struct hci_dev *hdev); int hci_suspend_dev(struct hci_dev *hdev); int hci_resume_dev(struct hci_dev *hdev); -int hci_reset_dev(struct hci_dev *hdev); +int __hci_reset_dev(struct hci_dev *hdev, u8 hw_err_code); + +static inline int hci_reset_dev(struct hci_dev *hdev) +{ + return __hci_reset_dev(hdev, 0); +} + int hci_recv_frame(struct hci_dev *hdev, struct sk_buff *skb); int hci_recv_diag(struct hci_dev *hdev, struct sk_buff *skb); __printf(2, 3) void hci_set_hw_info(struct hci_dev *hdev, const char *fmt, ...); diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 9d5adf882509..509c820a693d 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -2852,9 +2852,9 @@ int hci_resume_dev(struct hci_dev *hdev) EXPORT_SYMBOL(hci_resume_dev); /* Reset HCI device */ -int hci_reset_dev(struct hci_dev *hdev) +int __hci_reset_dev(struct hci_dev *hdev, u8 hw_err_code) { - static const u8 hw_err[] = { HCI_EV_HARDWARE_ERROR, 0x01, 0x00 }; + const u8 hw_err[] = { HCI_EV_HARDWARE_ERROR, 0x01, hw_err_code }; struct sk_buff *skb; skb = bt_skb_alloc(3, GFP_ATOMIC); @@ -2869,7 +2869,7 @@ int hci_reset_dev(struct hci_dev *hdev) /* Send Hardware Error to upper stack */ return hci_recv_frame(hdev, skb); } -EXPORT_SYMBOL(hci_reset_dev); +EXPORT_SYMBOL(__hci_reset_dev); static u8 hci_dev_classify_pkt_type(struct hci_dev *hdev, struct sk_buff *skb) { From 73c4c035ae25108ad98ff03ccdd0c763a42bb08f Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Sat, 1 Aug 2026 23:31:40 -0700 Subject: [PATCH 1085/1433] Bluetooth: btnxpuart: Simplify nxp_set_ind_reset() by __hci_reset_dev() nxp_set_ind_reset() injects the non-zero hardware error code BTNXPUART_IR_HW_ERR. Simplify it by __hci_reset_dev(hdev, BTNXPUART_IR_HW_ERR). Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btnxpuart.c | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/drivers/bluetooth/btnxpuart.c b/drivers/bluetooth/btnxpuart.c index 0bb300eef157..993c48b5f593 100644 --- a/drivers/bluetooth/btnxpuart.c +++ b/drivers/bluetooth/btnxpuart.c @@ -1331,19 +1331,7 @@ static int nxp_check_boot_sign(struct btnxpuart_dev *nxpdev) static int nxp_set_ind_reset(struct hci_dev *hdev, void *data) { - static const u8 ir_hw_err[] = { HCI_EV_HARDWARE_ERROR, - 0x01, BTNXPUART_IR_HW_ERR }; - struct sk_buff *skb; - - skb = bt_skb_alloc(3, GFP_ATOMIC); - if (!skb) - return -ENOMEM; - - hci_skb_pkt_type(skb) = HCI_EVENT_PKT; - skb_put_data(skb, ir_hw_err, 3); - - /* Inject Hardware Error to upper stack */ - return hci_recv_frame(hdev, skb); + return __hci_reset_dev(hdev, BTNXPUART_IR_HW_ERR); } /* Firmware dump */ From 0bd606b31d40dceb718bf22e3ce7b4cff7e34bf6 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Sat, 1 Aug 2026 23:31:41 -0700 Subject: [PATCH 1086/1433] Bluetooth: hci_event: Introduce handle_ev_vendor() for HCI_EV_VENDOR Introduce the hook to solve issues below: msft_vendor_evt(), the current handler for all VSEs, is unsuitable since: - many VSEs are not MSFT ones; - it always corrupts the non-MSFT VSEs by calling skb_pull_data() once the MSFT extension is enabled. Several issues are caused by many transport drivers pre-processing VSEs in their RX path, often an IRQ-disabled atomic context. Take the two typical cases below as examples: Case 1: // no btmon log, no way to reach userspace Step 1: handle and free @original_skb directly Case 2: // hurts performance and consumes GFP_ATOMIC memory Step 1: cloned_skb = skb_clone(original_skb, GFP_ATOMIC); // the VSE is handled here Step 2: handle and free @cloned_skb Step 3: hci_recv_frame(hdev, original_skb); // already handled, but re-enters the stack's event-handling path Step 4: hci_event_packet(hdev, original_skb); Fix by introducing the hook with usage: 1) the transport driver registers the hook for VSEs of interest; 2) the stack calls it in process context, handling the VSE like any other event: - if interested, handle the VSE - no need to free it - and return true; - otherwise return false. Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/hci_core.h | 2 ++ net/bluetooth/hci_event.c | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 6ff47f9bf758..e07418a5adce 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -646,6 +646,8 @@ struct hci_dev { int (*setup)(struct hci_dev *hdev); int (*shutdown)(struct hci_dev *hdev); int (*send)(struct hci_dev *hdev, struct sk_buff *skb); + /* Handle HCI_EV_VENDOR; return true if handled, false otherwise */ + bool (*handle_ev_vendor)(struct hci_dev *hdev, struct sk_buff *skb); void (*notify)(struct hci_dev *hdev, unsigned int evt); void (*hw_error)(struct hci_dev *hdev, u8 code); int (*post_init)(struct hci_dev *hdev); diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 6890d60ade93..d8e9125ae74a 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -7604,6 +7604,14 @@ static void hci_le_meta_evt(struct hci_dev *hdev, void *data, subev->func(hdev, data, skb); } +static void hci_vendor_evt(struct hci_dev *hdev, void *data, struct sk_buff *skb) +{ + if (hdev->handle_ev_vendor && hdev->handle_ev_vendor(hdev, skb)) + return; + + msft_vendor_evt(hdev, data, skb); +} + static bool hci_get_cmd_complete(struct hci_dev *hdev, u16 opcode, u8 event, struct sk_buff *skb) { @@ -7831,7 +7839,7 @@ static const struct hci_ev { HCI_EV_REQ_VL(HCI_EV_LE_META, hci_le_meta_evt, sizeof(struct hci_ev_le_meta), HCI_MAX_EVENT_SIZE), /* [0xff = HCI_EV_VENDOR] */ - HCI_EV_VL(HCI_EV_VENDOR, msft_vendor_evt, 0, HCI_MAX_EVENT_SIZE), + HCI_EV_VL(HCI_EV_VENDOR, hci_vendor_evt, 0, HCI_MAX_EVENT_SIZE), }; static void hci_event_func(struct hci_dev *hdev, u8 event, struct sk_buff *skb, From 9a4fa3cddc692efb47515c45fe05217369448bde Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Sat, 1 Aug 2026 23:31:42 -0700 Subject: [PATCH 1087/1433] Bluetooth: hci_event: Use 255 as max event payload length in hci_ev_table[] hci_event_func() validates skb->len against ev->max_len from the entry in hci_ev_table[]. By then, the header has already been stripped by skb_pull(). So the max event payload is 255, but hci_ev_table[] still uses HCI_MAX_EVENT_SIZE (260) for it, which is imprecise. Fix by introducing HCI_MAX_EVENT_PLEN (255) and using it instead. Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/hci.h | 1 + net/bluetooth/hci_event.c | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index cd3520a29131..1641d879dbda 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -3382,6 +3382,7 @@ struct hci_ev_si_security { /* ---- HCI Packet structures ---- */ #define HCI_COMMAND_HDR_SIZE 3 #define HCI_EVENT_HDR_SIZE 2 +#define HCI_MAX_EVENT_PLEN 255 #define HCI_ACL_HDR_SIZE 4 #define HCI_SCO_HDR_SIZE 3 #define HCI_ISO_HDR_SIZE 4 diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index d8e9125ae74a..371ca8236bc5 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -7728,7 +7728,7 @@ static const struct hci_ev { HCI_EV_STATUS(HCI_EV_INQUIRY_COMPLETE, hci_inquiry_complete_evt), /* [0x02 = HCI_EV_INQUIRY_RESULT] */ HCI_EV_VL(HCI_EV_INQUIRY_RESULT, hci_inquiry_result_evt, - sizeof(struct hci_ev_inquiry_result), HCI_MAX_EVENT_SIZE), + sizeof(struct hci_ev_inquiry_result), HCI_MAX_EVENT_PLEN), /* [0x03 = HCI_EV_CONN_COMPLETE] */ HCI_EV(HCI_EV_CONN_COMPLETE, hci_conn_complete_evt, sizeof(struct hci_ev_conn_complete)), @@ -7756,7 +7756,7 @@ static const struct hci_ev { sizeof(struct hci_ev_remote_features)), /* [0x0e = HCI_EV_CMD_COMPLETE] */ HCI_EV_REQ_VL(HCI_EV_CMD_COMPLETE, hci_cmd_complete_evt, - sizeof(struct hci_ev_cmd_complete), HCI_MAX_EVENT_SIZE), + sizeof(struct hci_ev_cmd_complete), HCI_MAX_EVENT_PLEN), /* [0x0f = HCI_EV_CMD_STATUS] */ HCI_EV_REQ(HCI_EV_CMD_STATUS, hci_cmd_status_evt, sizeof(struct hci_ev_cmd_status)), @@ -7768,7 +7768,7 @@ static const struct hci_ev { sizeof(struct hci_ev_role_change)), /* [0x13 = HCI_EV_NUM_COMP_PKTS] */ HCI_EV_VL(HCI_EV_NUM_COMP_PKTS, hci_num_comp_pkts_evt, - sizeof(struct hci_ev_num_comp_pkts), HCI_MAX_EVENT_SIZE), + sizeof(struct hci_ev_num_comp_pkts), HCI_MAX_EVENT_PLEN), /* [0x14 = HCI_EV_MODE_CHANGE] */ HCI_EV(HCI_EV_MODE_CHANGE, hci_mode_change_evt, sizeof(struct hci_ev_mode_change)), @@ -7794,7 +7794,7 @@ static const struct hci_ev { HCI_EV_VL(HCI_EV_INQUIRY_RESULT_WITH_RSSI, hci_inquiry_result_with_rssi_evt, sizeof(struct hci_ev_inquiry_result_rssi), - HCI_MAX_EVENT_SIZE), + HCI_MAX_EVENT_PLEN), /* [0x23 = HCI_EV_REMOTE_EXT_FEATURES] */ HCI_EV(HCI_EV_REMOTE_EXT_FEATURES, hci_remote_ext_features_evt, sizeof(struct hci_ev_remote_ext_features)), @@ -7804,7 +7804,7 @@ static const struct hci_ev { /* [0x2f = HCI_EV_EXTENDED_INQUIRY_RESULT] */ HCI_EV_VL(HCI_EV_EXTENDED_INQUIRY_RESULT, hci_extended_inquiry_result_evt, - sizeof(struct hci_ev_ext_inquiry_result), HCI_MAX_EVENT_SIZE), + sizeof(struct hci_ev_ext_inquiry_result), HCI_MAX_EVENT_PLEN), /* [0x30 = HCI_EV_KEY_REFRESH_COMPLETE] */ HCI_EV(HCI_EV_KEY_REFRESH_COMPLETE, hci_key_refresh_complete_evt, sizeof(struct hci_ev_key_refresh_complete)), @@ -7837,9 +7837,9 @@ static const struct hci_ev { sizeof(struct hci_ev_remote_host_features)), /* [0x3e = HCI_EV_LE_META] */ HCI_EV_REQ_VL(HCI_EV_LE_META, hci_le_meta_evt, - sizeof(struct hci_ev_le_meta), HCI_MAX_EVENT_SIZE), + sizeof(struct hci_ev_le_meta), HCI_MAX_EVENT_PLEN), /* [0xff = HCI_EV_VENDOR] */ - HCI_EV_VL(HCI_EV_VENDOR, hci_vendor_evt, 0, HCI_MAX_EVENT_SIZE), + HCI_EV_VL(HCI_EV_VENDOR, hci_vendor_evt, 0, HCI_MAX_EVENT_PLEN), }; static void hci_event_func(struct hci_dev *hdev, u8 event, struct sk_buff *skb, From f57b399c4fa1501b2d5451f52d861ece86bcf3db Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Sat, 1 Aug 2026 15:05:24 +0800 Subject: [PATCH 1088/1433] Bluetooth: hci_sync: Fix accept list UAF during suspend hci_update_event_filter_sync() walks hdev->accept_list while sending a synchronous HCI command for each remote-wakeup device. The suspend path holds hdev->req_lock, but accept-list updates are serialized by hdev->lock. Consequently, remove_device() can free the current list entry during the controller wait. The following interleaving causes the use-after-free: hci_update_event_filter_sync() remove_device() fetch accept-list entry hci_set_event_filter_sync() wait for controller response hci_dev_lock() list_del() kfree() hci_dev_unlock() read the freed list.next KASAN reported: BUG: KASAN: slab-use-after-free in hci_suspend_sync+0x835/0x910 Read of size 8 at addr ffff88810bec8440 by task kworker/0:1/10 Workqueue: events vhci_suspend_work Call Trace: hci_suspend_sync+0x835/0x910 hci_suspend_dev+0x182/0x450 process_one_work+0x661/0x1090 worker_thread+0x45b/0xd10 Allocated by task 86: hci_bdaddr_list_add_with_flags+0x1a8/0x400 add_device+0x381/0x820 hci_sock_sendmsg+0x1033/0x1ea0 Freed by task 91: kfree+0x131/0x3c0 remove_device+0x429/0xb70 hci_sock_sendmsg+0x1033/0x1ea0 Snapshot the remote-wakeup addresses under hdev->lock. Release the lock before sending HCI commands. Clear the controller event filter before building the snapshot, and skip allocation and the second list traversal when there are no matching entries. This preserves the original filter and scan-state updates without retaining an accept-list node across a controller wait. Fixes: 182ee45da083 ("Bluetooth: hci_sync: Rework hci_suspend_notifier") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/linux-bluetooth/20260730092331.2069741-1-nicoyip.dev@gmail.com/ Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 46 ++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 307fd47f8459..a661634a63aa 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -6351,6 +6351,8 @@ static int hci_pause_discovery_sync(struct hci_dev *hdev) static int hci_update_event_filter_sync(struct hci_dev *hdev) { struct bdaddr_list_with_flags *b; + bdaddr_t *accept_list; + size_t i, num_entries = 0; u8 scan = SCAN_DISABLED; bool scanning = test_bit(HCI_PSCAN, &hdev->flags); int err; @@ -6367,23 +6369,49 @@ static int hci_update_event_filter_sync(struct hci_dev *hdev) /* Always clear event filter when starting */ hci_clear_event_filter_sync(hdev); - list_for_each_entry(b, &hdev->accept_list, list) { - if (!(b->flags & HCI_CONN_FLAG_REMOTE_WAKEUP)) - continue; + hci_dev_lock(hdev); - bt_dev_dbg(hdev, "Adding event filters for %pMR", &b->bdaddr); + list_for_each_entry(b, &hdev->accept_list, list) + if (b->flags & HCI_CONN_FLAG_REMOTE_WAKEUP) + num_entries++; - err = hci_set_event_filter_sync(hdev, HCI_FLT_CONN_SETUP, - HCI_CONN_SETUP_ALLOW_BDADDR, - &b->bdaddr, - HCI_CONN_SETUP_AUTO_ON); + if (!num_entries) { + hci_dev_unlock(hdev); + goto update_scan; + } + + accept_list = kmalloc_array(num_entries, sizeof(*accept_list), + GFP_KERNEL); + if (!accept_list) { + hci_dev_unlock(hdev); + return -ENOMEM; + } + + i = 0; + list_for_each_entry(b, &hdev->accept_list, list) + if (b->flags & HCI_CONN_FLAG_REMOTE_WAKEUP) + bacpy(&accept_list[i++], &b->bdaddr); + + hci_dev_unlock(hdev); + + for (i = 0; i < num_entries; i++) { + bt_dev_dbg(hdev, "Adding event filters for %pMR", + &accept_list[i]); + + err = hci_set_event_filter_sync(hdev, HCI_FLT_CONN_SETUP, + HCI_CONN_SETUP_ALLOW_BDADDR, + &accept_list[i], + HCI_CONN_SETUP_AUTO_ON); if (err) bt_dev_err(hdev, "Failed to set event filter for %pMR", - &b->bdaddr); + &accept_list[i]); else scan = SCAN_PAGE; } + kfree(accept_list); + +update_scan: if (scan && !scanning) hci_write_scan_enable_sync(hdev, scan); else if (!scan && scanning) From 42de40abe25db9211107af8896d0fd741f10648d Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 6 Aug 2026 20:59:54 +0800 Subject: [PATCH 1089/1433] Bluetooth: hci_conn: fix the SCO setup context lifetime hci_setup_sync() queues a conn_handle_t with a NULL destroy callback, so the context is only freed if hci_enhanced_setup_sync() actually runs. An entry that is cancelled instead is leaked, as _hci_cmd_sync_cancel_entry() does not release entry->data when there is no destroy callback, and hci_cmd_sync_clear() cancels every pending entry when the controller is unregistered. The context also stores a bare hci_conn pointer, so the connection can be freed while the work is queued. The dequeue in hci_conn_del() does not cover it either, as it matches on entry->data == conn and entry->data is the wrapper here. Same problem as commit 2f5d635ad590 ("Bluetooth: hci_sync: hold conn in hci_connect_acl/le_sync() callbacks"). Hold the connection and release both from a destroy callback. The submission failure path drops both, since hci_cmd_sync_submit() does not call the destroy callback when it fails to queue. Fixes: e07a06b4eb41 ("Bluetooth: Convert SCO configure_datapath to hci_sync") Signed-off-by: Linmao Li Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_conn.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index b1f911fd4ad6..19b7629b1cc1 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -283,8 +283,6 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) struct hci_cp_enhanced_setup_sync_conn cp; const struct sco_param *param; - kfree(conn_handle); - if (!hci_conn_valid(hdev, conn)) return -ECANCELED; @@ -453,6 +451,15 @@ static bool hci_setup_sync_conn(struct hci_conn *conn, __u16 handle) return true; } +static void hci_enhanced_setup_sync_destroy(struct hci_dev *hdev, void *data, + int err) +{ + struct conn_handle_t *conn_handle = data; + + hci_conn_put(conn_handle->conn); + kfree(conn_handle); +} + bool hci_setup_sync(struct hci_conn *conn, __u16 handle) { int result; @@ -464,12 +471,15 @@ bool hci_setup_sync(struct hci_conn *conn, __u16 handle) if (!conn_handle) return false; - conn_handle->conn = conn; + conn_handle->conn = hci_conn_get(conn); conn_handle->handle = handle; result = hci_cmd_sync_queue(conn->hdev, hci_enhanced_setup_sync, - conn_handle, NULL); - if (result < 0) + conn_handle, + hci_enhanced_setup_sync_destroy); + if (result < 0) { + hci_conn_put(conn); kfree(conn_handle); + } return result == 0; } From 120d8dc042e3d45073bb6e50ee7b058a0b182627 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 6 Aug 2026 20:59:55 +0800 Subject: [PATCH 1090/1433] Bluetooth: hci_sync: free the advertising instance on the failure and cancel paths adv_timeout_expire() hands a kmalloc()ed instance byte to hci_cmd_sync_queue() with a NULL destroy callback, and only adv_timeout_expire_sync() frees it. That leaks on two paths: - the return value is not checked, and hci_cmd_sync_queue() does not take ownership when it fails (-ENETDOWN, -ENODEV, -ENOMEM); - a cancelled entry is not released, as _hci_cmd_sync_cancel_entry() does not free entry->data when there is no destroy callback. hci_cmd_sync_clear() cancels every pending entry when the controller is unregistered. Free the buffer from a destroy callback, and in the caller when the entry could not be queued at all. Fixes: c249ea9b4309 ("Bluetooth: Move Adv Instance timer to hci_sync") Signed-off-by: Linmao Li Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index a661634a63aa..df2d037970f9 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -540,8 +540,6 @@ static int adv_timeout_expire_sync(struct hci_dev *hdev, void *data) { u8 instance = *(u8 *)data; - kfree(data); - hci_clear_adv_instance_sync(hdev, NULL, instance, false); if (list_empty(&hdev->adv_instances)) @@ -550,6 +548,12 @@ static int adv_timeout_expire_sync(struct hci_dev *hdev, void *data) return 0; } +static void adv_timeout_expire_destroy(struct hci_dev *hdev, void *data, + int err) +{ + kfree(data); +} + static void adv_timeout_expire(struct work_struct *work) { u8 *inst_ptr; @@ -570,7 +574,9 @@ static void adv_timeout_expire(struct work_struct *work) goto unlock; *inst_ptr = hdev->cur_adv_instance; - hci_cmd_sync_queue(hdev, adv_timeout_expire_sync, inst_ptr, NULL); + if (hci_cmd_sync_queue(hdev, adv_timeout_expire_sync, inst_ptr, + adv_timeout_expire_destroy) < 0) + kfree(inst_ptr); unlock: hci_dev_unlock(hdev); From 3c742feda8fcabf741a17bcf668b63c8f606f9c5 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 6 Aug 2026 20:59:56 +0800 Subject: [PATCH 1091/1433] Bluetooth: MGMT: free the mesh send cancel command when it is cancelled mesh_send_cancel() queues the pending command with a NULL destroy callback, so it is only freed if send_cancel() runs. A cancelled entry is leaked, as _hci_cmd_sync_cancel_entry() does not release entry->data when there is no destroy callback, and hci_cmd_sync_clear() cancels every pending entry when the controller is unregistered. Nothing else reclaims it either: mgmt_pending_new() does not put the command on hdev->mgmt_pending. The leak also pins the socket reference taken by mgmt_pending_new(), so the mgmt socket is never released. Free the command from a destroy callback. Fixes: b338d91703fa ("Bluetooth: Implement support for Mesh") Signed-off-by: Linmao Li Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/mgmt.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 0d6b41fe0b34..bd56830f07ea 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -2437,11 +2437,15 @@ static int send_cancel(struct hci_dev *hdev, void *data) mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_MESH_SEND_CANCEL, 0, NULL, 0); - mgmt_pending_free(cmd); return 0; } +static void send_cancel_destroy(struct hci_dev *hdev, void *data, int err) +{ + mgmt_pending_free(data); +} + static int mesh_send_cancel(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) { @@ -2462,7 +2466,8 @@ static int mesh_send_cancel(struct sock *sk, struct hci_dev *hdev, if (!cmd) err = -ENOMEM; else - err = hci_cmd_sync_queue(hdev, send_cancel, cmd, NULL); + err = hci_cmd_sync_queue(hdev, send_cancel, cmd, + send_cancel_destroy); if (err < 0) { err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_MESH_SEND_CANCEL, From 414b365ecea6c30357adee6b8a7c5edc03a03575 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 6 Aug 2026 20:59:57 +0800 Subject: [PATCH 1092/1433] Bluetooth: MGMT: free the HCI command when it is cancelled mgmt_hci_cmd_sync() queues the pending command with a NULL destroy callback, so it is only freed if send_hci_cmd_sync() runs. A cancelled entry is leaked, as _hci_cmd_sync_cancel_entry() does not release entry->data when there is no destroy callback, and hci_cmd_sync_clear() cancels every pending entry when the controller is unregistered. Nothing else reclaims it either: mgmt_pending_new() does not put the command on hdev->mgmt_pending. The leak also pins the socket reference taken by mgmt_pending_new(), so the mgmt socket is never released. Free the command from a destroy callback. The now-empty done label is replaced by a direct return. Fixes: 827af4787e74 ("Bluetooth: MGMT: Add initial implementation of MGMT_OP_HCI_CMD_SYNC") Signed-off-by: Linmao Li Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/mgmt.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index bd56830f07ea..c4ba845f7e5d 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -2653,7 +2653,7 @@ static int send_hci_cmd_sync(struct hci_dev *hdev, void *data) if (IS_ERR(skb)) { mgmt_cmd_status(cmd->sk, hdev->id, MGMT_OP_HCI_CMD_SYNC, mgmt_status(PTR_ERR(skb))); - goto done; + return 0; } mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_HCI_CMD_SYNC, 0, @@ -2661,12 +2661,14 @@ static int send_hci_cmd_sync(struct hci_dev *hdev, void *data) kfree_skb(skb); -done: - mgmt_pending_free(cmd); - return 0; } +static void send_hci_cmd_sync_destroy(struct hci_dev *hdev, void *data, int err) +{ + mgmt_pending_free(data); +} + static int mgmt_hci_cmd_sync(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) { @@ -2684,7 +2686,8 @@ static int mgmt_hci_cmd_sync(struct sock *sk, struct hci_dev *hdev, if (!cmd) err = -ENOMEM; else - err = hci_cmd_sync_queue(hdev, send_hci_cmd_sync, cmd, NULL); + err = hci_cmd_sync_queue(hdev, send_hci_cmd_sync, cmd, + send_hci_cmd_sync_destroy); if (err < 0) { err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_HCI_CMD_SYNC, From e48e332d84d8df9bc615530beaa3ece9240da2d6 Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Tue, 21 Jul 2026 11:04:58 +0800 Subject: [PATCH 1093/1433] Bluetooth: btnxpuart: Add M.2 Bluetooth device support using pwrseq Power supply to the M.2 Bluetooth device attached to the host using M.2 connector is controlled using the 'uart' pwrseq device. So add support for getting the pwrseq device if the OF graph link is present. Once obtained, pwrseq_power_on() is called to power up the M.2 Bluetooth card. The power sequencer descriptor is obtained via pwrseq_get() with the UART controller device (serdev->ctrl->dev), since the OF graph link is defined on the UART controller node. Also add the explicit pwrseq_put() call in all exit paths, pwrseq_put() already calls pwrseq_power_off() internally, so no separate pwrseq_power_off() call is needed. Signed-off-by: Sherry Sun Reviewed-by: Bartosz Golaszewski Reviewed-by: Frank Li Reviewed-by: Manivannan Sadhasivam Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btnxpuart.c | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/drivers/bluetooth/btnxpuart.c b/drivers/bluetooth/btnxpuart.c index 993c48b5f593..81cdd8da5636 100644 --- a/drivers/bluetooth/btnxpuart.c +++ b/drivers/bluetooth/btnxpuart.c @@ -9,6 +9,8 @@ #include #include +#include +#include #include #include #include @@ -211,6 +213,7 @@ struct btnxpuart_dev { struct ps_data psdata; struct btnxpuart_data *nxp_data; + struct pwrseq_desc *pwrseq; struct reset_control *pdn; struct hci_uart hu; }; @@ -1860,11 +1863,26 @@ static int nxp_serdev_probe(struct serdev_device *serdev) return err; } + if (of_graph_is_present(dev_of_node(&serdev->ctrl->dev))) { + struct pwrseq_desc *pwrseq; + + pwrseq = pwrseq_get(&serdev->ctrl->dev, "uart"); + if (IS_ERR(pwrseq)) + return dev_err_probe(&serdev->dev, PTR_ERR(pwrseq), + "failed to get pwrseq\n"); + + nxpdev->pwrseq = pwrseq; + err = pwrseq_power_on(pwrseq); + if (err) + goto err_pwrseq_put; + } + /* Initialize and register HCI device */ hdev = hci_alloc_dev(); if (!hdev) { dev_err(&serdev->dev, "Can't allocate HCI device\n"); - return -ENOMEM; + err = -ENOMEM; + goto err_pwrseq_put; } reset_control_deassert(nxpdev->pdn); @@ -1895,13 +1913,16 @@ static int nxp_serdev_probe(struct serdev_device *serdev) if (bacmp(&ba, BDADDR_ANY)) hci_set_quirk(hdev, HCI_QUIRK_USE_BDADDR_PROPERTY); - if (hci_register_dev(hdev) < 0) { + err = hci_register_dev(hdev); + if (err < 0) { dev_err(&serdev->dev, "Can't register HCI device\n"); goto probe_fail; } - if (ps_setup(hdev)) + if (ps_setup(hdev)) { + err = -ENODEV; goto probe_fail_unregister; + } hci_devcd_register(hdev, nxp_coredump, nxp_coredump_hdr, nxp_coredump_notify); @@ -1913,7 +1934,10 @@ static int nxp_serdev_probe(struct serdev_device *serdev) probe_fail: reset_control_assert(nxpdev->pdn); hci_free_dev(hdev); - return -ENODEV; +err_pwrseq_put: + if (nxpdev->pwrseq) + pwrseq_put(nxpdev->pwrseq); + return err; } static void nxp_serdev_remove(struct serdev_device *serdev) @@ -1940,6 +1964,8 @@ static void nxp_serdev_remove(struct serdev_device *serdev) ps_cleanup(nxpdev); hci_unregister_dev(hdev); reset_control_assert(nxpdev->pdn); + if (nxpdev->pwrseq) + pwrseq_put(nxpdev->pwrseq); hci_free_dev(hdev); } From 5d95286b6d6e8f1d304da7522bfa6860fc017e48 Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Thu, 6 Aug 2026 17:39:53 +0000 Subject: [PATCH 1094/1433] Bluetooth: MGMT: reject HCI_CMD_SYNC params_len above 255 mgmt_hci_cmd_sync() checks that the message length agrees with params_len but puts no upper bound on it. params_len is __le16 while the parameter length in the HCI command header is a u8: struct hci_command_hdr { __le16 opcode; __u8 plen; } __packed; hci_cmd_sync_alloc() assigns one to the other: hdr->plen = plen; if (plen) skb_put_data(skb, param, plen); so a params_len of 256 leaves plen at 0 while all 256 bytes are still appended. The frame handed to the driver then declares no parameters and carries 256 of them. On a length framed transport such as H:4 the controller takes the trailing bytes as the start of the next packet. The mgmt socket MTU is HCI_MAX_FRAME_SIZE, so params_len can reach about 1KB this way. Commit 03f1700b9b4d ("Bluetooth: MGMT: reject malformed HCI_CMD_SYNC commands") only made params_len agree with the message length, a value that fits the message but not the header field is still accepted. Reject params_len that does not fit the header field. Fixes: 827af4787e74 ("Bluetooth: MGMT: Add initial implementation of MGMT_OP_HCI_CMD_SYNC") Cc: stable@vger.kernel.org Signed-off-by: Ali Ahmet Memis Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/mgmt.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index c4ba845f7e5d..860c086011b7 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -2681,6 +2681,14 @@ static int mgmt_hci_cmd_sync(struct sock *sk, struct hci_dev *hdev, return mgmt_cmd_status(sk, hdev->id, MGMT_OP_HCI_CMD_SYNC, MGMT_STATUS_INVALID_PARAMS); + /* The HCI command header carries the parameter length in a u8, a + * larger value would be truncated there while the parameters are + * still appended to the frame in full. + */ + if (le16_to_cpu(cp->params_len) > U8_MAX) + return mgmt_cmd_status(sk, hdev->id, MGMT_OP_HCI_CMD_SYNC, + MGMT_STATUS_INVALID_PARAMS); + hci_dev_lock(hdev); cmd = mgmt_pending_new(sk, MGMT_OP_HCI_CMD_SYNC, hdev, data, len); if (!cmd) From b0c0b37940115383e7ea65d4d988f9b9e613ab92 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Fri, 7 Aug 2026 23:14:47 +0800 Subject: [PATCH 1095/1433] Bluetooth: btmtksdio: fix usage_count leak when autosuspend_delay is negative btmtksdio_setup() calls pm_runtime_use_autosuspend() when runtime PM is supported, but btmtksdio_remove() does not call the matching pm_runtime_dont_use_autosuspend() when removing the device. If the autosuspend delay is set to a negative value while autosuspend is enabled, the runtime PM core increments usage_count to prevent runtime suspend. Without calling pm_runtime_dont_use_autosuspend() during driver teardown, this reference is not dropped and usage_count remains unbalanced. Add the missing pm_runtime_dont_use_autosuspend() call in the remove path before restoring the runtime PM usage reference. This issue was found by manual code inspection. Fixes: 7f3c563c575e ("Bluetooth: btmtksdio: Add runtime PM support to SDIO based Bluetooth") Signed-off-by: Guangshuo Li Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtksdio.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/bluetooth/btmtksdio.c b/drivers/bluetooth/btmtksdio.c index c6f80c419e90..4e1012e90979 100644 --- a/drivers/bluetooth/btmtksdio.c +++ b/drivers/bluetooth/btmtksdio.c @@ -1480,6 +1480,9 @@ static void btmtksdio_remove(struct sdio_func *func) if (test_bit(BTMTKSDIO_FUNC_ENABLED, &bdev->tx_state)) btmtksdio_close(hdev); + if (bdev->data->pm_runtime_supported) + pm_runtime_dont_use_autosuspend(bdev->dev); + /* Be consistent the state in btmtksdio_probe */ pm_runtime_get_noresume(bdev->dev); From e3643fbddb257c928c075cab05bbd929106b56ee Mon Sep 17 00:00:00 2001 From: Laxman Acharya Date: Wed, 5 Aug 2026 23:22:01 +0545 Subject: [PATCH 1096/1433] Bluetooth: hci_event: fix out-of-bounds read in LE PA report reassembly hci_le_per_adv_report_evt() is dispatched with a minimum length of sizeof(struct hci_ev_le_per_adv_report), which only covers the fixed part of the event and not the trailing data[] array: struct hci_ev_le_per_adv_report { __le16 sync_handle; __u8 tx_power; __u8 rssi; __u8 cte_type; __u8 data_status; __u8 length; __u8 data[]; } __packed; The handler notifies the ISO layer via hci_proto_connect_ind(), which reaches iso_connect_ind(). That function retrieves the stored event with hci_recv_event_data() and, while reassembling the periodic advertising data, does: memcpy(hcon->le_per_adv_data + hcon->le_per_adv_data_offset, ev->data, ev->length); ev->length is taken directly from the event and is never validated against the amount of data the event actually carries. A controller that reports a length larger than the received event therefore causes the memcpy() to read past the end of the event buffer. The leaked bytes are stored in hcon->le_per_adv_data and can subsequently be read back from user space via getsockopt(BT_ISO_BASE). Validate that the event contains ev->length data bytes before it is consumed, mirroring the check already performed by hci_le_ext_adv_report_evt() and hci_le_adv_report_evt(). Signed-off-by: Laxman Acharya Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_event.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 371ca8236bc5..3eb1eaf6e6a0 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -6658,6 +6658,13 @@ static void hci_le_per_adv_report_evt(struct hci_dev *hdev, void *data, bt_dev_dbg(hdev, "sync_handle 0x%4.4x", le16_to_cpu(ev->sync_handle)); + /* The reassembly in iso_connect_ind() copies ev->length bytes from the + * stored event, so make sure the event actually carries that many data + * bytes before it is consumed. + */ + if (!hci_le_ev_skb_pull(hdev, skb, HCI_EV_LE_PER_ADV_REPORT, ev->length)) + return; + hci_dev_lock(hdev); mask |= hci_proto_connect_ind(hdev, BDADDR_ANY, PA_LINK, &flags); From 75722cde87ee24029e93e4e32d85309989f55991 Mon Sep 17 00:00:00 2001 From: Muhammad Saheed Date: Wed, 5 Aug 2026 01:40:51 +0530 Subject: [PATCH 1097/1433] Bluetooth: hci_sync: Disable legacy instance's ext adv before setup snapshot hci_setup_ext_adv_instance_sync(...) only disabled HCI_OP_LE_SET_EXT_ADV_ENABLE before setup snapshot in case of non-legacy instances (instance > 0) and never disabled the same for legacy instance (instance == 0). This would lead to failure in setting ext adv params with HCI_ERROR_COMMAND_DISALLOWED (0x0c) error like below, when toggling the discoverable/connectable property of a controller with advertising enabled. ``` $ btmgmt advertising off hci0 Set Advertising complete, settings: powered ssp br/edr le secure-conn wide-band-speech cis-central cis-peripheral $ btmgmt connectable on hci0 Set Connectable complete, settings: powered connectable ssp br/edr le secure-conn wide-band-speech cis-central cis-peripheral $ btmgmt connectable off hci0 Set Connectable complete, settings: powered ssp br/edr le secure-conn wide-band-speech cis-central cis-peripheral $ btmgmt advertising on hci0 Set Advertising complete, settings: powered connectable ssp br/edr le advertising secure-conn wide-band-speech cis-central cis-peripheral $ btmgmt connectable on Set Connectable for hci0 failed with status 0x0a (Busy) $ btmgmt connectable off Set Connectable for hci0 failed with status 0x0a (Busy) $ dmesg ... [ 21.970527] hci0: Opcode 0x2036 [ 21.970529] hci0: opcode 0x2036 plen 25 [ 21.970537] hci0: skb len 28 [ 21.970539] hci0: length 1 [ 21.976099] hci0: result 0x0c [ 21.976105] hci0: end: err -16 [ 21.976114] Bluetooth: hci0: Opcode 0x2036 failed: -16 ``` Signed-off-by: Muhammad Saheed Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index df2d037970f9..b5897545d795 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -1150,6 +1150,32 @@ int hci_update_random_address_sync(struct hci_dev *hdev, bool require_privacy, return 0; } +static int hci_disable_ext_adv_legacy_instance_sync(struct hci_dev *hdev) +{ + struct hci_cp_le_set_ext_adv_enable *cp; + struct hci_cp_ext_adv_set *set; + u8 data[sizeof(*cp) + sizeof(*set) * 1]; + u8 size; + + if (!hci_dev_test_flag(hdev, HCI_LE_ADV_0)) + return 0; + + memset(data, 0, sizeof(data)); + + cp = (void *)data; + set = (void *)cp->data; + + cp->num_of_sets = 0x01; + cp->enable = 0x00; + + set->handle = 0x00; + + size = sizeof(*cp) + sizeof(*set) * cp->num_of_sets; + + return __hci_cmd_sync_status(hdev, HCI_OP_LE_SET_EXT_ADV_ENABLE, + size, data, HCI_CMD_TIMEOUT); +} + static int hci_disable_ext_adv_instance_sync(struct hci_dev *hdev, u8 instance) { struct hci_cp_le_set_ext_adv_enable *cp; @@ -1375,6 +1401,10 @@ int hci_setup_ext_adv_instance_sync(struct hci_dev *hdev, u8 instance) return -EINVAL; } } else { + err = hci_disable_ext_adv_legacy_instance_sync(hdev); + if (err) + return err; + adv = NULL; } From 9838a80096ba472d5e03057136a112631aabae6e Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Fri, 7 Aug 2026 00:59:55 +0000 Subject: [PATCH 1098/1433] Bluetooth: ISO: do not force BT_LISTEN after a failed BIG sync iso_sock_recvmsg() handles the deferred setup of a broadcast sink by dropping the socket lock, calling iso_conn_big_sync() and taking the lock again: release_sock(sk); iso_conn_big_sync(sk); lock_sock(sk); sk->sk_state = BT_LISTEN; The state is written unconditionally, but iso_conn_big_sync() returns void and has paths that do nothing at all: hci_get_route() may fail, and after re-acquiring the socket lock the connection may already be gone, in which case it bails out without ever issuing an LE BIG Create Sync. While the lock is dropped the connection can be torn down, for example when the controller reports HCI_EV_LE_PA_SYNC_LOST: hci_le_pa_sync_lost_evt() hci_disconn_cfm() -> iso_disconn_cfm() -> iso_conn_del() iso_chan_del() iso_pi(sk)->conn = NULL sk->sk_state = BT_CLOSED sock_set_flag(sk, SOCK_ZAPPED) iso_conn_big_sync() then finds conn == NULL and returns, but the caller still overwrites the BT_CLOSED that iso_chan_del() has just set. The socket ends up marked BT_LISTEN with no connection, so recvmsg() reports success for a setup that never happened and a later accept() waits for BIS connections that can never arrive instead of failing. A concurrent shutdown() reaches the same write by another route: __iso_sock_close() takes the BT_CONNECT2 PA sync path to iso_sock_disconn(), which sets BT_DISCONN but leaves conn and conn->hcon in place, so iso_conn_big_sync() succeeds and BT_LISTEN is written over BT_DISCONN. Both the BT_CONNECT2 and the BT_CONNECTED case write the state the same way. Let iso_conn_big_sync() report whether the BIG sync was started, and only move the socket to BT_LISTEN when it was and when the state has not changed while the lock was dropped, mirroring what the BT_CONNECT case of the same switch already does with iso_connect_cis(). Both conditions are needed, the error alone does not cover the shutdown() race. This corrupts the socket state machine only, it is not a memory safety issue. KASAN and lockdep stayed quiet in all of the runs below. Reproduced with an emulated controller over /dev/vhci on a KASAN + PROVE_LOCKING kernel. A PA sync broadcast sink socket is driven to BT_CONNECT2 and recvmsg() on it is raced against teardown, with a debug delay inside the lock-dropped section to widen the window: - HCI_EV_LE_PA_SYNC_LOST injected: 64 of 64 rounds left the socket in BT_LISTEN with the connection gone, recvmsg() returned 0 and accept() on that fd returned EAGAIN, which iso_sock_accept() can only do while the socket is BT_LISTEN. With this patch, 0 of 64, recvmsg() returns an error and accept() returns EBADFD. - shutdown() instead of a controller event: 24 of 32 rounds wedged in BT_LISTEN, 0 of 32 with this patch. With only the error check in place and a short window, one round still wedged while recvmsg() returned 0, which is the case the state re-check covers. An unraced control round behaves the same before and after: recvmsg() returns 0, the socket reaches BT_LISTEN and an LE BIG Create Sync is issued. Fixes: 7a17308c1788 ("Bluetooth: iso: Fix circular lock in iso_conn_big_sync") Cc: stable@vger.kernel.org Signed-off-by: Ali Ahmet Memis Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index a461c8a4efed..e46242d4455d 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -1658,9 +1658,9 @@ static void iso_conn_defer_accept(struct hci_conn *conn) hci_send_cmd(hdev, HCI_OP_LE_ACCEPT_CIS, sizeof(cp), &cp); } -static void iso_conn_big_sync(struct sock *sk) +static int iso_conn_big_sync(struct sock *sk) { - int err; + int err = 0; struct hci_dev *hdev; struct iso_conn *conn; bdaddr_t src, dst; @@ -1675,7 +1675,7 @@ static void iso_conn_big_sync(struct sock *sk) hdev = hci_get_route(&dst, &src, src_type); if (!hdev) - return; + return -EHOSTUNREACH; /* hci_le_big_create_sync requires hdev lock to be held, since * it enqueues the HCI LE BIG Create Sync command via @@ -1691,8 +1691,10 @@ static void iso_conn_big_sync(struct sock *sk) * both before dereferencing conn->hcon. */ conn = iso_pi(sk)->conn; - if (!conn || !conn->hcon) + if (!conn || !conn->hcon) { + err = -ENOTCONN; goto unlock; + } if (!test_and_set_bit(BT_SK_BIG_SYNC, &iso_pi(sk)->flags)) { err = hci_conn_big_create_sync(hdev, conn->hcon, @@ -1708,6 +1710,8 @@ static void iso_conn_big_sync(struct sock *sk) release_sock(sk); hci_dev_unlock(hdev); hci_dev_put(hdev); + + return err; } static int iso_sock_recvmsg(struct socket *sock, struct msghdr *msg, @@ -1732,10 +1736,19 @@ static int iso_sock_recvmsg(struct socket *sock, struct msghdr *msg, case BT_CONNECT2: if (test_bit(BT_SK_PA_SYNC, &pi->flags)) { release_sock(sk); - iso_conn_big_sync(sk); + err = iso_conn_big_sync(sk); lock_sock(sk); - sk->sk_state = BT_LISTEN; + /* The socket lock was dropped, so the + * connection may have been torn down + * meanwhile and iso_chan_del() may have + * already moved the socket to BT_CLOSED. + * Only move on to BT_LISTEN if the BIG sync + * was actually started and nothing else has + * changed the state. + */ + if (!err && sk->sk_state == BT_CONNECT2) + sk->sk_state = BT_LISTEN; } else { iso_conn_defer_accept(pi->conn->hcon); sk->sk_state = BT_CONFIG; @@ -1746,10 +1759,11 @@ static int iso_sock_recvmsg(struct socket *sock, struct msghdr *msg, case BT_CONNECTED: if (test_bit(BT_SK_PA_SYNC, &iso_pi(sk)->flags)) { release_sock(sk); - iso_conn_big_sync(sk); + err = iso_conn_big_sync(sk); lock_sock(sk); - sk->sk_state = BT_LISTEN; + if (!err && sk->sk_state == BT_CONNECTED) + sk->sk_state = BT_LISTEN; early_ret = true; } From 884cf2cc957da7ac178a0e6c6c69ddfec0481cc8 Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Thu, 6 Aug 2026 23:06:21 +0000 Subject: [PATCH 1099/1433] Bluetooth: ISO: zero the sockaddr before returning it in getname iso_sock_getname() fills a struct sockaddr_iso in place and returns its size without clearing it first, so bytes it does not write are copied to user space from the kernel stack. The getsockname(2) and getpeername(2) paths both run through do_getsockname(), which hands getname() an uninitialized sockaddr_storage on the stack and copies back up to the number of bytes getname() returns, so the driver has to initialize every byte it accounts for. Two ranges are left uninitialized: - struct sockaddr_iso is 10 bytes but only 9 are written (family, iso_bdaddr, iso_bdaddr_type), leaking the trailing pad byte on every call. - for a broadcast peer (BIS_LINK or PA_LINK) the returned length grows by sizeof(struct sockaddr_iso_bc), but only bc_sid, bc_num_bis and bc_bis are filled; bc_bdaddr and bc_bdaddr_type, the first 7 bytes of that structure, are never written. An unprivileged process can open a BTPROTO_ISO socket and reach the pad leak with getsockname(); the broadcast leak needs an established BIS/PA connection. l2cap and rfcomm already memset their sockaddr in getname for the same reason; do the same here. Fixes: ccf74f2390d6 ("Bluetooth: Add BTPROTO_ISO socket type") Fixes: 0a766a0affb5 ("Bluetooth: ISO: Fix getpeername not returning sockaddr_iso_bc fields") Cc: stable@vger.kernel.org Signed-off-by: Ali Ahmet Memis Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index e46242d4455d..aa2ce78f56a2 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -1536,6 +1536,7 @@ static int iso_sock_getname(struct socket *sock, struct sockaddr *addr, lock_sock(sk); + memset(sa, 0, sizeof(struct sockaddr_iso)); addr->sa_family = AF_BLUETOOTH; if (peer) { @@ -1546,6 +1547,7 @@ static int iso_sock_getname(struct socket *sock, struct sockaddr *addr, sa->iso_bdaddr_type = iso_pi(sk)->dst_type; if (hcon && (hcon->type == BIS_LINK || hcon->type == PA_LINK)) { + memset(sa->iso_bc, 0, sizeof(struct sockaddr_iso_bc)); sa->iso_bc->bc_sid = iso_pi(sk)->bc_sid; sa->iso_bc->bc_num_bis = iso_pi(sk)->bc_num_bis; memcpy(sa->iso_bc->bc_bis, iso_pi(sk)->bc_bis, From 0079e1a944634ab2dc1c7cdec1144486d096407e Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Fri, 7 Aug 2026 01:25:54 +0000 Subject: [PATCH 1100/1433] Bluetooth: MSFT: validate evt_prefix_len against the response length read_supported_features() only checks that the response covers the fixed part of struct msft_rp_read_supported_features, which is 11 bytes: if (skb->len < sizeof(*rp)) { bt_dev_err(hdev, "MSFT supported features length mismatch"); goto failed; } evt_prefix[] is a flexible array member and rp->evt_prefix_len is an unvalidated u8 taken straight out of that response, so msft->evt_prefix = kmemdup(rp->evt_prefix, rp->evt_prefix_len, GFP_KERNEL); copies up to 255 bytes from a reply that may have carried none of them. What is copied is data the controller never sent, and it is then used to match incoming vendor events in msft_vendor_evt(). This is not an out-of-bounds access. An skb data allocation always has at least SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) bytes past the payload, which is more than the 255 byte maximum, so the read stays inside the allocation and KASAN does not report it. It is still a read of bytes the host was never given, with the length fully controlled by the controller. Reject a response that is too short for the prefix it declares. Verified with an emulated controller over /dev/vhci on a KASAN kernel, with vhci made to advertise an MSFT opcode the way btintel, btqca, btmtk and btrtl do unconditionally. A reply of exactly 11 bytes declaring evt_prefix_len = 255 reaches kmemdup and copies 255 bytes ("skb->len=11 evt_prefix_len=255", with the copied buffer dumped); since the reply ends at the fixed part, all 255 come from past the end of the response. No KASAN report is produced, as expected from the allocation slack described above. With this patch the response is rejected with "MSFT event prefix length mismatch" and msft->evt_prefix is left unset. Fixes: 145373cb1b1f ("Bluetooth: Add framework for Microsoft vendor extension") Signed-off-by: Ali Ahmet Memis Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/msft.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/bluetooth/msft.c b/net/bluetooth/msft.c index d7badce8746c..ded68568e6c9 100644 --- a/net/bluetooth/msft.c +++ b/net/bluetooth/msft.c @@ -165,6 +165,11 @@ static bool read_supported_features(struct hci_dev *hdev, if (rp->sub_opcode != MSFT_OP_READ_SUPPORTED_FEATURES) goto failed; + if (skb->len < sizeof(*rp) + rp->evt_prefix_len) { + bt_dev_err(hdev, "MSFT event prefix length mismatch"); + goto failed; + } + if (rp->evt_prefix_len > 0) { msft->evt_prefix = kmemdup(rp->evt_prefix, rp->evt_prefix_len, GFP_KERNEL); From 43a556b2fd43f2df6dded59c2e26560a27874c24 Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Fri, 7 Aug 2026 02:03:44 +0000 Subject: [PATCH 1101/1433] Bluetooth: RFCOMM: take rfcomm_mutex for the deferred setup accept rfcomm_sock_recvmsg() completes a deferred setup by calling rfcomm_dlc_accept() without holding any RFCOMM lock: if (test_and_clear_bit(RFCOMM_DEFER_SETUP, &d->flags)) { rfcomm_dlc_accept(d); return 0; } and rfcomm_dlc_accept() dereferences the session on its first line: struct sock *sk = d->session->sock->sk; Every other path that touches d->session runs under rfcomm_mutex: rfcomm_dlc_open(), rfcomm_dlc_close(), rfcomm_dlc_exists(), rfcomm_dlc_send_rpn(), and the RFCOMM thread through rfcomm_process_sessions(). rfcomm_connect_ind() is even documented as "called under rfcomm_lock()". This call site is the only one that skips it. The RFCOMM_DEFER_SETUP bit looks like it serialises the accept against teardown, since __rfcomm_dlc_close() returns early when it wins the test_and_clear. But rfcomm_recv_disc() forces the state first: d->state = BT_CLOSED; __rfcomm_dlc_close(d, err); and the early return only covers BT_CONNECT, BT_CONFIG, BT_OPEN and BT_CONNECT2. With the state already BT_CLOSED that switch does not match, the bit is never consulted, and __rfcomm_dlc_close() falls through to rfcomm_dlc_unlink(), which sets d->session = NULL. So a remote DISC on a deferred dlc clears the session while leaving RFCOMM_DEFER_SETUP set. The next recvmsg() then passes the test_and_clear and dereferences a NULL session. No timing window is needed: once the DISC has been processed, the dereference is unconditional. Give rfcomm_dlc_accept() the same shape as rfcomm_dlc_open() and rfcomm_dlc_close(): an exported wrapper that takes rfcomm_mutex and re-checks the session, around a __rfcomm_dlc_accept() that the two in-core callers, which already hold the mutex, keep using. Reproduced on a KASAN + PROVE_LOCKING kernel with a BR/EDR peer emulated over /dev/vhci: the peer brings up an ACL link, opens L2CAP on the RFCOMM PSM, starts a session, opens a dlc on a channel bound with BT_DEFER_SETUP, and sends DISC after the socket is accepted. recv() on the accepted socket then hits: Oops: general protection fault KASAN: null-ptr-deref in range [0x0000000000000010-0x0000000000000017] RIP: 0010:rfcomm_dlc_accept+0x54/0x350 Call Trace: rfcomm_sock_recvmsg+0x1cd/0x230 sock_recvmsg+0x166/0x1c0 __sys_recvfrom+0x20d/0x300 0x10 is the offset of sock in struct rfcomm_session. With this patch the same run completes with recv() returning 0 and no report, and lockdep stays quiet, confirming rfcomm_mutex is still taken before lock_sock on this path as it is on the thread side. Fixes: bb23c0ab8246 ("Bluetooth: Add support for deferring RFCOMM connection setup") Cc: stable@vger.kernel.org Signed-off-by: Ali Ahmet Memis Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/rfcomm/core.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index 2e8c080b4d9e..9cdfea666a2c 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -1331,7 +1331,10 @@ static struct rfcomm_session *rfcomm_recv_disc(struct rfcomm_session *s, return s; } -void rfcomm_dlc_accept(struct rfcomm_dlc *d) +/* Must be called with rfcomm_mutex held, so that the session cannot be + * unlinked from under us. + */ +static void __rfcomm_dlc_accept(struct rfcomm_dlc *d) { struct sock *sk = d->session->sock->sk; struct l2cap_conn *conn = l2cap_pi(sk)->chan->conn; @@ -1353,6 +1356,21 @@ void rfcomm_dlc_accept(struct rfcomm_dlc *d) rfcomm_send_msc(d->session, 1, d->dlci, d->v24_sig); } +void rfcomm_dlc_accept(struct rfcomm_dlc *d) +{ + rfcomm_lock(); + + /* rfcomm_recv_disc() sets the dlc state to BT_CLOSED before calling + * __rfcomm_dlc_close(), so the RFCOMM_DEFER_SETUP handshake there is + * skipped and the session can already be unlinked by the time the + * deferred accept runs from rfcomm_sock_recvmsg(). + */ + if (d->session) + __rfcomm_dlc_accept(d); + + rfcomm_unlock(); +} + static void rfcomm_check_accept(struct rfcomm_dlc *d) { if (rfcomm_check_security(d)) { @@ -1365,7 +1383,7 @@ static void rfcomm_check_accept(struct rfcomm_dlc *d) d->state_change(d, 0); rfcomm_dlc_unlock(d); } else - rfcomm_dlc_accept(d); + __rfcomm_dlc_accept(d); } else { set_bit(RFCOMM_AUTH_PENDING, &d->flags); rfcomm_dlc_set_timer(d, RFCOMM_AUTH_TIMEOUT); @@ -1958,7 +1976,7 @@ static void rfcomm_process_dlcs(struct rfcomm_session *s) d->state_change(d, 0); rfcomm_dlc_unlock(d); } else - rfcomm_dlc_accept(d); + __rfcomm_dlc_accept(d); } continue; } else if (test_and_clear_bit(RFCOMM_AUTH_REJECT, &d->flags)) { From b6e2649fff15a17689851b30e5415a3aae2b516e Mon Sep 17 00:00:00 2001 From: Ahmed Naseef Date: Tue, 4 Aug 2026 14:33:21 +0400 Subject: [PATCH 1102/1433] net: phy: mediatek: add EcoNet EN7528 PHY support The EcoNet EN7528 MIPS SoC embeds four Gigabit Ethernet PHYs (PHY ID 0x03a29491) behind its built-in MT7530 switch. They use the same LED register layout as the other SoC PHYs handled by this driver, but their LED controller powers up with its external control disabled, so the LED pins stay dark regardless of what is programmed into the LED control registers. Add a phy_driver entry for it, modelled on the Airoha AN7583 one. Its config_init callback enables the LED controller through the LED basic control register, which this driver does not program for its other PHYs, but which the air_en8811h driver already handles as AIR_PHY_LED_BCR. LED behaviour is then controlled through the phylib LED operations shared with the other PHYs of this driver. The LED block is shared by the four PHYs of the EN7528: the LED configuration programmed through any one of them applies to all four, while each PHY still drives its own LED pin from its own link state. The EN7528 PHYs need no efuse calibration data, so relax the MEDIATEK_GE_SOC_PHY dependencies to allow building the driver on the ECONET platform. Signed-off-by: Ahmed Naseef Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260804103321.3331802-1-naseefkm@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/mediatek/Kconfig | 8 ++++-- drivers/net/phy/mediatek/mtk-ge-soc.c | 35 +++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/drivers/net/phy/mediatek/Kconfig b/drivers/net/phy/mediatek/Kconfig index bb7dc876271e..b6d41bbbbc27 100644 --- a/drivers/net/phy/mediatek/Kconfig +++ b/drivers/net/phy/mediatek/Kconfig @@ -23,9 +23,9 @@ config MEDIATEK_GE_PHY config MEDIATEK_GE_SOC_PHY tristate "MediaTek SoC Ethernet PHYs" - depends on ARM64 || COMPILE_TEST + depends on ARM64 || ECONET || COMPILE_TEST depends on ARCH_AIROHA || (ARCH_MEDIATEK && NVMEM_MTK_EFUSE) || \ - COMPILE_TEST + ECONET || COMPILE_TEST select MTK_NET_PHYLIB select PHY_PACKAGE help @@ -36,5 +36,9 @@ config MEDIATEK_GE_SOC_PHY present in the SoCs efuse and will dynamically calibrate VCM (common-mode voltage) during startup. + Also include support for the built-in Gigabit Ethernet PHYs of + the Airoha AN7581 and AN7583 SoCs and of the EcoNet EN7528 SoC, + which need no efuse calibration data. + config MTK_NET_PHYLIB tristate diff --git a/drivers/net/phy/mediatek/mtk-ge-soc.c b/drivers/net/phy/mediatek/mtk-ge-soc.c index 9a54949644d5..0041300d4f10 100644 --- a/drivers/net/phy/mediatek/mtk-ge-soc.c +++ b/drivers/net/phy/mediatek/mtk-ge-soc.c @@ -16,6 +16,7 @@ #define MTK_GPHY_ID_MT7981 0x03a29461 #define MTK_GPHY_ID_MT7988 0x03a29481 +#define MTK_GPHY_ID_EN7528 0x03a29491 #define MTK_GPHY_ID_AN7581 0x03a294c1 #define MTK_GPHY_ID_AN7583 0xc0ff0420 @@ -320,6 +321,14 @@ /* Registers on MDIO_MMD_VEND2 */ #define MTK_PHY_LED1_DEFAULT_POLARITIES BIT(1) +/* LED basic control register, part of the same LED block as the LED0/LED1 + * control registers above. The air_en8811h driver describes the same + * register as AIR_PHY_LED_BCR. + */ +#define MTK_PHY_LED_BCR 0x21 +#define MTK_PHY_LED_BCR_CLK_EN BIT(3) +#define MTK_PHY_LED_BCR_EXT_CTRL BIT(15) + #define MTK_PHY_RG_BG_RASEL 0x115 #define MTK_PHY_RG_BG_RASEL_MASK GENMASK(2, 0) @@ -1470,6 +1479,19 @@ static int an7583_phy_config_init(struct phy_device *phydev) return phy_clear_bits(phydev, MII_BMCR, BMCR_PDOWN); } +static int en7528_phy_config_init(struct phy_device *phydev) +{ + /* The LED controller of the EN7528 powers up with its external + * control disabled, leaving the LED pins dark regardless of what is + * programmed into the LED control registers. Hand the pins over to + * the LED control registers the same way the air_en8811h driver + * does; the mode field of this register is already set out of reset. + */ + return phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, MTK_PHY_LED_BCR, + MTK_PHY_LED_BCR_CLK_EN | + MTK_PHY_LED_BCR_EXT_CTRL); +} + static struct phy_driver mtk_socphy_driver[] = { { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7981), @@ -1505,6 +1527,18 @@ static struct phy_driver mtk_socphy_driver[] = { .led_hw_control_set = mt798x_phy_led_hw_control_set, .led_hw_control_get = mt798x_phy_led_hw_control_get, }, + { + PHY_ID_MATCH_EXACT(MTK_GPHY_ID_EN7528), + .name = "EcoNet EN7528 PHY", + .config_init = en7528_phy_config_init, + .probe = an7581_phy_probe, + .led_blink_set = mt798x_phy_led_blink_set, + .led_brightness_set = mt798x_phy_led_brightness_set, + .led_hw_is_supported = mt798x_phy_led_hw_is_supported, + .led_hw_control_set = mt798x_phy_led_hw_control_set, + .led_hw_control_get = mt798x_phy_led_hw_control_get, + .led_polarity_set = an7581_phy_led_polarity_set, + }, { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_AN7581), .name = "Airoha AN7581 PHY", @@ -1537,6 +1571,7 @@ module_phy_driver(mtk_socphy_driver); static const struct mdio_device_id __maybe_unused mtk_socphy_tbl[] = { { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7981) }, { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7988) }, + { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_EN7528) }, { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_AN7581) }, { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_AN7583) }, { } From 2cad8e3d9d94ff2b1083ee261767f0ebdc77d7ce Mon Sep 17 00:00:00 2001 From: Danielle Ratson Date: Mon, 3 Aug 2026 14:25:01 +0300 Subject: [PATCH 1103/1433] bridge: Use direct pointer in br_is_nd_neigh_msg() Both callers of br_is_nd_neigh_msg() already call pskb_may_pull() to ensure sizeof(struct ipv6hdr) + sizeof(struct nd_msg) bytes are in the linear area before invoking this function. The skb_header_pointer() call and its fallback buffer are therefore unnecessary. Replace skb_header_pointer() with a direct cast to ipv6_hdr(skb) + 1 and drop the now-unused 'msg' parameter and its corresponding stack buffer from all callers. Reviewed-by: Petr Machata Acked-by: Nikolay Aleksandrov Signed-off-by: Danielle Ratson Link: https://patch.msgid.link/20260803112505.613873-2-danieller@nvidia.com Signed-off-by: Jakub Kicinski --- net/bridge/br_arp_nd_proxy.c | 9 ++------- net/bridge/br_device.c | 4 ++-- net/bridge/br_input.c | 4 ++-- net/bridge/br_private.h | 2 +- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c index 23eb6931a2b4..db08c3272001 100644 --- a/net/bridge/br_arp_nd_proxy.c +++ b/net/bridge/br_arp_nd_proxy.c @@ -234,14 +234,9 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br, #endif #if IS_ENABLED(CONFIG_IPV6) -struct nd_msg *br_is_nd_neigh_msg(const struct sk_buff *skb, struct nd_msg *msg) +struct nd_msg *br_is_nd_neigh_msg(const struct sk_buff *skb) { - struct nd_msg *m; - - m = skb_header_pointer(skb, skb_network_offset(skb) + - sizeof(struct ipv6hdr), sizeof(*msg), msg); - if (!m) - return NULL; + struct nd_msg *m = (struct nd_msg *)(ipv6_hdr(skb) + 1); if (m->icmph.icmp6_code != 0 || (m->icmph.icmp6_type != NDISC_NEIGHBOUR_SOLICITATION && diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c index e7f343ab22d3..ff55dab73632 100644 --- a/net/bridge/br_device.c +++ b/net/bridge/br_device.c @@ -80,9 +80,9 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev) pskb_may_pull(skb, sizeof(struct ipv6hdr) + sizeof(struct nd_msg)) && ipv6_hdr(skb)->nexthdr == IPPROTO_ICMPV6) { - struct nd_msg *msg, _msg; + struct nd_msg *msg; - msg = br_is_nd_neigh_msg(skb, &_msg); + msg = br_is_nd_neigh_msg(skb); if (msg) br_do_suppress_nd(skb, br, vid, NULL, msg); } diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c index ddb8f002a40e..d87a5f9fa92b 100644 --- a/net/bridge/br_input.c +++ b/net/bridge/br_input.c @@ -176,9 +176,9 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb pskb_may_pull(skb, sizeof(struct ipv6hdr) + sizeof(struct nd_msg)) && ipv6_hdr(skb)->nexthdr == IPPROTO_ICMPV6) { - struct nd_msg *msg, _msg; + struct nd_msg *msg; - msg = br_is_nd_neigh_msg(skb, &_msg); + msg = br_is_nd_neigh_msg(skb); if (msg) br_do_suppress_nd(skb, br, vid, p, msg); } diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h index 4cf8b1ab9047..e31439cb4420 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h @@ -2367,7 +2367,7 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br, u16 vid, struct net_bridge_port *p); void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br, u16 vid, struct net_bridge_port *p, struct nd_msg *msg); -struct nd_msg *br_is_nd_neigh_msg(const struct sk_buff *skb, struct nd_msg *m); +struct nd_msg *br_is_nd_neigh_msg(const struct sk_buff *skb); bool br_is_neigh_suppress_enabled(const struct net_bridge_port *p, u16 vid); bool br_is_neigh_forward_grat_enabled(const struct net_bridge_port *p, u16 vid); #endif From 9dfa6cca8959dd203dc9de012126cf80bc4a680e Mon Sep 17 00:00:00 2001 From: Danielle Ratson Date: Mon, 3 Aug 2026 14:25:02 +0300 Subject: [PATCH 1104/1433] ipv6: ndisc: Add ndisc_check_ns_na() validation helper Add ndisc_check_ns_na(), a standalone NS/NA packet validator modeled after ipv6_mc_check_mld(). It performs the RFC 4861 section 7.1.1 (Neighbor Solicitation) and 7.1.2 (Neighbor Advertisement) mandatory checks that are relevant for software operating at the bridge level, where packets bypass the normal IPv6 stack path: - Hop Limit must be 255 (packet was not forwarded by a router) - ICMPv6 checksum is valid - ICMP Code is 0 - ICMP length is at least 24 octets (sizeof(struct nd_msg)) - Target Address must not be a multicast address - All included options have a length that is greater than zero - NS/DAD: destination must be a solicited-node multicast address - NS/DAD: no Source Link-Layer Address option when source is unspecified - NA: Solicited flag must be 0 when IP Destination is multicast On success the function sets the skb transport header and returns 0, matching the convention of ipv6_mc_check_mld(). Reviewed-by: Petr Machata Acked-by: Nikolay Aleksandrov Signed-off-by: Danielle Ratson Link: https://patch.msgid.link/20260803112505.613873-3-danieller@nvidia.com Signed-off-by: Jakub Kicinski --- include/net/ndisc.h | 2 + net/ipv6/Makefile | 2 +- net/ipv6/ndisc_snoop.c | 190 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 net/ipv6/ndisc_snoop.c diff --git a/include/net/ndisc.h b/include/net/ndisc.h index 3da1a6f8d3f9..9e5379ad2d8e 100644 --- a/include/net/ndisc.h +++ b/include/net/ndisc.h @@ -430,6 +430,8 @@ void ndisc_update(const struct net_device *dev, struct neighbour *neigh, const u8 *lladdr, u8 new, u32 flags, u8 icmp6_type, struct ndisc_options *ndopts); +int ndisc_check_ns_na(struct sk_buff *skb); + /* * IGMP */ diff --git a/net/ipv6/Makefile b/net/ipv6/Makefile index 5b0cd6488021..cf5e01f83ce3 100644 --- a/net/ipv6/Makefile +++ b/net/ipv6/Makefile @@ -51,7 +51,7 @@ obj-$(subst m,y,$(CONFIG_IPV6)) += inet6_hashtables.o ifneq ($(CONFIG_IPV6),) obj-$(CONFIG_NET_UDP_TUNNEL) += ip6_udp_tunnel.o -obj-y += mcast_snoop.o +obj-y += mcast_snoop.o ndisc_snoop.o obj-$(CONFIG_TCP_AO) += tcp_ao.o endif diff --git a/net/ipv6/ndisc_snoop.c b/net/ipv6/ndisc_snoop.c new file mode 100644 index 000000000000..fa86528d5cfe --- /dev/null +++ b/net/ipv6/ndisc_snoop.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include + +static int ndisc_check_ip6hdr(struct sk_buff *skb) +{ + const struct ipv6hdr *ip6h; + unsigned int offset, len; + + offset = skb_network_offset(skb) + sizeof(*ip6h); + if (!pskb_may_pull(skb, offset)) + return -EINVAL; + + ip6h = ipv6_hdr(skb); + + if (ip6h->version != 6) + return -EINVAL; + + if (ip6h->nexthdr != IPPROTO_ICMPV6) + return -ENOMSG; + + /* RFC 4861 7.1.1 / 7.1.2: must not have been forwarded by a router */ + if (ip6h->hop_limit != 255) + return -EINVAL; + + len = offset + ntohs(ip6h->payload_len); + if (skb->len < len || len <= offset) + return -EINVAL; + + skb_set_transport_header(skb, offset); + + return 0; +} + +static __sum16 ndisc_validate_checksum(struct sk_buff *skb) +{ + return skb_checksum_validate(skb, IPPROTO_ICMPV6, ip6_compute_pseudo); +} + +static int ndisc_check_icmpv6(struct sk_buff *skb) +{ + unsigned int len = skb_transport_offset(skb) + sizeof(struct icmp6hdr); + unsigned int transport_len = ipv6_transport_len(skb); + struct sk_buff *skb_chk; + struct icmp6hdr *hdr; + + if (!pskb_may_pull(skb, len)) + return -EINVAL; + + /* RFC 4861 7.1.1 / 7.1.2: the ICMPv6 checksum must be valid */ + skb_chk = skb_checksum_trimmed(skb, transport_len, + ndisc_validate_checksum); + if (!skb_chk) + return -EINVAL; + + if (skb_chk != skb) + kfree_skb(skb_chk); + + /* RFC 4861 7.1.1 / 7.1.2: Code must be 0 */ + hdr = (struct icmp6hdr *)skb_transport_header(skb); + if (hdr->icmp6_code != 0) + return -EINVAL; + + return 0; +} + +static int ndisc_check_options(struct sk_buff *skb, unsigned int opts_len, + bool reject_slla) +{ + unsigned int offset = skb_transport_offset(skb) + sizeof(struct nd_msg); + struct nd_opt_hdr *opt, _opt; + + while (opts_len > 0) { + if (opts_len < sizeof(*opt)) + return -EINVAL; + + opt = skb_header_pointer(skb, offset, sizeof(_opt), &_opt); + if (!opt) + return -EINVAL; + + /* RFC 4861 7.1.1 / 7.1.2: all option lengths must be > 0 */ + if (!opt->nd_opt_len) + return -EINVAL; + + /* RFC 4861 7.1.1: DAD NS must not contain a source link-layer + * address option + */ + if (reject_slla && opt->nd_opt_type == ND_OPT_SOURCE_LL_ADDR) + return -EINVAL; + + if (opt->nd_opt_len * 8 > opts_len) + return -EINVAL; + + offset += opt->nd_opt_len * 8; + opts_len -= opt->nd_opt_len * 8; + } + + return 0; +} + +static int ndisc_check_nd_msg(struct sk_buff *skb) +{ + unsigned int len = skb_transport_offset(skb) + sizeof(struct nd_msg); + unsigned int transport_len = ipv6_transport_len(skb); + bool reject_slla = false; + const struct nd_msg *msg; + + if (!pskb_may_pull(skb, len)) + return -EINVAL; + + /* RFC 4861 7.1.1 / 7.1.2: ICMP length is at least sizeof(nd_msg) */ + if (transport_len < sizeof(struct nd_msg)) + return -EINVAL; + + msg = (struct nd_msg *)skb_transport_header(skb); + + /* RFC 4861 7.1.1 / 7.1.2: Target Address must not be a + * multicast address + */ + if (ipv6_addr_is_multicast(&msg->target)) + return -EINVAL; + + switch (msg->icmph.icmp6_type) { + case NDISC_NEIGHBOUR_SOLICITATION: + if (ipv6_addr_any(&ipv6_hdr(skb)->saddr)) { + /* RFC 4861 7.1.1: DAD NS destination must be a + * solicited-node multicast address + */ + if (!ipv6_addr_is_solict_mult(&ipv6_hdr(skb)->daddr)) + return -EINVAL; + /* RFC 4861 7.1.1: DAD NS must not contain a source + * link-layer address option + */ + reject_slla = true; + } + break; + case NDISC_NEIGHBOUR_ADVERTISEMENT: + /* RFC 4861 7.1.2: Solicited flag must be 0 for + * multicast destinations + */ + if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr) && + msg->icmph.icmp6_solicited) + return -EINVAL; + break; + default: + return -ENODATA; + } + + return ndisc_check_options(skb, transport_len - sizeof(struct nd_msg), + reject_slla); +} + +/** + * ndisc_check_ns_na - validate an NS/NA packet and set its transport header + * @skb: the skb to validate + * + * Validates an IPv6 packet for compliance with RFC 4861 sections 7.1.1 + * (Neighbor Solicitation) and 7.1.2 (Neighbor Advertisement). If valid, + * sets the skb transport header. + * + * Caller needs to set the skb network header. + * + * Return: + * * 0 - valid NS/NA; the skb transport header has been set. + * * -EINVAL - a broken packet was detected, i.e. it violates some + * internet standard. + * * -ENOMSG - IP header validation succeeded but it is not an ICMPv6 + * packet. + * * -ENODATA - IP+ICMPv6 header validation succeeded but it is not a + * Neighbor Solicitation or Neighbor Advertisement. + */ +int ndisc_check_ns_na(struct sk_buff *skb) +{ + int ret; + + ret = ndisc_check_ip6hdr(skb); + if (ret < 0) + return ret; + + ret = ndisc_check_icmpv6(skb); + if (ret < 0) + return ret; + + return ndisc_check_nd_msg(skb); +} +EXPORT_SYMBOL_GPL(ndisc_check_ns_na); From 18668f4747c9e159ef582046657ced5696a36265 Mon Sep 17 00:00:00 2001 From: Danielle Ratson Date: Mon, 3 Aug 2026 14:25:03 +0300 Subject: [PATCH 1105/1433] bridge: Validate NS/NA messages using ndisc_check_ns_na() The bridge performs neighbor suppression by snooping NS/NA messages, but previously only checked the ICMPv6 type and code. This leaves it open to acting on malformed or spoofed packets that any RFC-compliant node should reject. Wire br_is_nd_neigh_msg() into the new ndisc_check_ns_na() helper, which enforces the full RFC 4861 section 7.1.1/7.1.2 receive validation: hop limit of 255, valid checksum, correct code, and type-specific rules (NS target not multicast; NA solicited flag clear for multicast destinations). MLD messages are already validated by ipv6_mc_check_mld() before the bridge acts on them; this brings NS/NA to the same standard. As a side effect, the skb parameter of br_is_nd_neigh_msg() changes from const to non-const, since ndisc_check_ns_na() may reallocate the skb head via pskb_may_pull() and sets the transport header. The returned pointer is now derived from skb_transport_header() rather than a direct cast. Reviewed-by: Petr Machata Acked-by: Nikolay Aleksandrov Signed-off-by: Danielle Ratson Link: https://patch.msgid.link/20260803112505.613873-4-danieller@nvidia.com Signed-off-by: Jakub Kicinski --- net/bridge/br_arp_nd_proxy.c | 11 ++++------- net/bridge/br_private.h | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c index db08c3272001..445c930ed59b 100644 --- a/net/bridge/br_arp_nd_proxy.c +++ b/net/bridge/br_arp_nd_proxy.c @@ -19,6 +19,7 @@ #include #if IS_ENABLED(CONFIG_IPV6) #include +#include #endif #include "br_private.h" @@ -234,16 +235,12 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br, #endif #if IS_ENABLED(CONFIG_IPV6) -struct nd_msg *br_is_nd_neigh_msg(const struct sk_buff *skb) +struct nd_msg *br_is_nd_neigh_msg(struct sk_buff *skb) { - struct nd_msg *m = (struct nd_msg *)(ipv6_hdr(skb) + 1); - - if (m->icmph.icmp6_code != 0 || - (m->icmph.icmp6_type != NDISC_NEIGHBOUR_SOLICITATION && - m->icmph.icmp6_type != NDISC_NEIGHBOUR_ADVERTISEMENT)) + if (ndisc_check_ns_na(skb)) return NULL; - return m; + return (struct nd_msg *)skb_transport_header(skb); } static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p, diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h index e31439cb4420..d337b1cfb980 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h @@ -2367,7 +2367,7 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br, u16 vid, struct net_bridge_port *p); void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br, u16 vid, struct net_bridge_port *p, struct nd_msg *msg); -struct nd_msg *br_is_nd_neigh_msg(const struct sk_buff *skb); +struct nd_msg *br_is_nd_neigh_msg(struct sk_buff *skb); bool br_is_neigh_suppress_enabled(const struct net_bridge_port *p, u16 vid); bool br_is_neigh_forward_grat_enabled(const struct net_bridge_port *p, u16 vid); #endif From 67b14d6e36cf53aefe2f324ca2dbe02f3362c835 Mon Sep 17 00:00:00 2001 From: Danielle Ratson Date: Mon, 3 Aug 2026 14:25:04 +0300 Subject: [PATCH 1106/1433] bridge: Linearize skb once the ND message type is validated br_nd_send() parses ND options from ns->opt[] and therefore needs the skb to be linear. Commit a01aee7cafc5 ("bridge: br_nd_send: linearize skb before parsing ND options") ensured that by linearizing inside br_nd_send() itself. Move the linearization up into br_is_nd_neigh_msg(), right after ndisc_check_ns_na() has validated the message as an NS/NA. This makes a linear buffer a property of every recognized ND message, so that this and any future ND message handling operate on a linear skb and cannot reintroduce that class of bug by forgetting to linearize. Since the skb is now linear by the time br_nd_send() runs, drop the linearization there and derive ns from the transport header set by ndisc_check_ns_na(), instead of recomputing it from the network header. If linearization fails under memory pressure, br_is_nd_neigh_msg() returns NULL and the packet falls back to normal forwarding rather than being suppressed. Reviewed-by: Petr Machata Signed-off-by: Danielle Ratson Acked-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260803112505.613873-5-danieller@nvidia.com Signed-off-by: Jakub Kicinski --- net/bridge/br_arp_nd_proxy.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c index 445c930ed59b..6b6de0eff38c 100644 --- a/net/bridge/br_arp_nd_proxy.c +++ b/net/bridge/br_arp_nd_proxy.c @@ -235,11 +235,17 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br, #endif #if IS_ENABLED(CONFIG_IPV6) +/* Validate skb as an NS/NA and linearize it for br_nd_send()'s ND + * option parsing; returns the nd_msg, or NULL on failure. + */ struct nd_msg *br_is_nd_neigh_msg(struct sk_buff *skb) { if (ndisc_check_ns_na(skb)) return NULL; + if (skb_linearize(skb)) + return NULL; + return (struct nd_msg *)skb_transport_header(skb); } @@ -259,7 +265,7 @@ static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p, bool dad; u16 pvid; - if (!dev || skb_linearize(request)) + if (!dev) return; len = LL_RESERVED_SPACE(dev) + sizeof(struct ipv6hdr) + @@ -276,8 +282,7 @@ static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p, skb_set_mac_header(reply, 0); daddr = eth_hdr(request)->h_source; - ns = (struct nd_msg *)(skb_network_header(request) + - sizeof(struct ipv6hdr)); + ns = (struct nd_msg *)skb_transport_header(request); /* Do we need option processing ? */ ns_olen = request->len - (skb_network_offset(request) + From 7445aaa9fe6d37542421bf56beca98e44ab635b4 Mon Sep 17 00:00:00 2001 From: Danielle Ratson Date: Mon, 3 Aug 2026 14:25:05 +0300 Subject: [PATCH 1107/1433] bridge: Use ndisc_parse_options() to parse ND options in br_nd_send() Replace the manual ND option parsing loop in br_nd_send() with ndisc_parse_options(), which provides proper validation and avoids the class of bugs that were fixed by commit 53fc685243bd ("bridge: Avoid infinite loop when suppressing NS messages with invalid options") and commit 850837965af1 ("bridge: br_nd_send: validate ND option lengths"). Use ndisc_opt_addr_data() to extract the source link-layer address from the parsed options, which correctly validates the option length for the underlying device type. Export ndisc_parse_options() so that it can be resolved from the bridge when it is built as a module (CONFIG_BRIDGE=m); otherwise modpost fails with an undefined symbol. Reviewed-by: Petr Machata Acked-by: Nikolay Aleksandrov Signed-off-by: Danielle Ratson Link: https://patch.msgid.link/20260803112505.613873-6-danieller@nvidia.com Signed-off-by: Jakub Kicinski --- net/bridge/br_arp_nd_proxy.c | 32 +++++++++++++++++--------------- net/ipv6/ndisc.c | 1 + 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c index 6b6de0eff38c..b6e5a86b6a92 100644 --- a/net/bridge/br_arp_nd_proxy.c +++ b/net/bridge/br_arp_nd_proxy.c @@ -255,15 +255,16 @@ static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p, { struct net_device *dev = request->dev; struct net_bridge_vlan_group *vg; + struct ndisc_options ndopts; struct nd_msg *na, *ns; struct sk_buff *reply; struct ipv6hdr *pip6; int na_olen = 8; /* opt hdr + ETH_ALEN for target */ int ns_olen; - int i, len; u8 *daddr; bool dad; u16 pvid; + int len; if (!dev) return; @@ -284,20 +285,21 @@ static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p, daddr = eth_hdr(request)->h_source; ns = (struct nd_msg *)skb_transport_header(request); - /* Do we need option processing ? */ - ns_olen = request->len - (skb_network_offset(request) + - sizeof(struct ipv6hdr)) - sizeof(*ns); - for (i = 0; i < ns_olen - 1; i += (ns->opt[i + 1] << 3)) { - if (!ns->opt[i + 1] || i + (ns->opt[i + 1] << 3) > ns_olen) { - kfree_skb(reply); - return; - } - if (ns->opt[i] == ND_OPT_SOURCE_LL_ADDR) { - if ((ns->opt[i + 1] << 3) >= - sizeof(struct nd_opt_hdr) + ETH_ALEN) - daddr = ns->opt + i + sizeof(struct nd_opt_hdr); - break; - } + /* Derive the option length from the IPv6 payload length so that any + * trailing L2 padding in the skb is not parsed as ND options. + */ + ns_olen = ntohs(ipv6_hdr(request)->payload_len) - sizeof(*ns); + if (!ndisc_parse_options(dev, ns->opt, ns_olen, &ndopts)) { + kfree_skb(reply); + return; + } + + if (ndopts.nd_opts_src_lladdr) { + u8 *lladdr; + + lladdr = ndisc_opt_addr_data(ndopts.nd_opts_src_lladdr, dev); + if (lladdr) + daddr = lladdr; } dad = ipv6_addr_any(&ipv6_hdr(request)->saddr); diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c index fe36b3f51285..2ceb655c4229 100644 --- a/net/ipv6/ndisc.c +++ b/net/ipv6/ndisc.c @@ -283,6 +283,7 @@ struct ndisc_options *ndisc_parse_options(const struct net_device *dev, } return ndopts; } +EXPORT_SYMBOL_GPL(ndisc_parse_options); int ndisc_mc_map(const struct in6_addr *addr, char *buf, struct net_device *dev, int dir) { From 0023e4c6171dc71ec6d0ee4cab45e67d5f5e2ea4 Mon Sep 17 00:00:00 2001 From: Nagamani PV Date: Mon, 3 Aug 2026 20:27:36 +0200 Subject: [PATCH 1108/1433] s390/ctcm: Convert fsm.h to proper kernel-doc format drivers/s390/net/fsm.h contains comments starting with '/**' that don't follow kernel-doc syntax, triggering warnings when running: scripts/kernel-doc -none -Wall drivers/s390/net/fsm* Example warning: Warning: drivers/s390/net/fsm.h:14 This comment starts with '/**', but isn't a kernel-doc comment. Refer to Documentation/doc-guide/kernel-doc.rst * Define this to get debugging messages. Convert function declarations to proper kernel-doc format per Documentation/doc-guide/kernel-doc.rst. Change debug macros and internal structure comments from '/**' to '/*' since they are not part of the public API. Also add missing parameter name in fsm_settimer() declaration to match the implementation. Remove redundant extern keywords from all function declarations. No functional change. Reviewed-by: Aswin Karuvally Reviewed-by: Alexandra Winter Signed-off-by: Nagamani PV Link: https://patch.msgid.link/20260803182736.2356374-1-nagamani@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/s390/net/fsm.h | 152 +++++++++++++++++++++-------------------- 1 file changed, 78 insertions(+), 74 deletions(-) diff --git a/drivers/s390/net/fsm.h b/drivers/s390/net/fsm.h index 16dc071a2973..6a0b47ca87f0 100644 --- a/drivers/s390/net/fsm.h +++ b/drivers/s390/net/fsm.h @@ -11,18 +11,18 @@ #include #include -/** +/* * Define this to get debugging messages. */ #define FSM_DEBUG 0 -/** +/* * Define this to get debugging massages for * timer handling. */ #define FSM_TIMER_DEBUG 0 -/** +/* * Define these to record a history of * Events/Statechanges and print it if a * action_function is not found. @@ -32,12 +32,12 @@ struct fsm_instance_t; -/** +/* * Definition of an action function, called by a FSM */ typedef void (*fsm_function_t)(struct fsm_instance_t *, int, void *); -/** +/* * Internal jump table for a FSM */ typedef struct { @@ -49,7 +49,7 @@ typedef struct { } fsm; #if FSM_DEBUG_HISTORY -/** +/* * Element of State/Event history used for debugging. */ typedef struct { @@ -58,7 +58,7 @@ typedef struct { } fsm_history; #endif -/** +/* * Representation of a FSM */ typedef struct fsm_instance_t { @@ -75,7 +75,7 @@ typedef struct fsm_instance_t { #endif } fsm_instance; -/** +/* * Description of a state-event combination */ typedef struct { @@ -84,7 +84,7 @@ typedef struct { fsm_function_t function; } fsm_node; -/** +/* * Description of a FSM Timer. */ typedef struct { @@ -95,50 +95,52 @@ typedef struct { } fsm_timer; /** - * Creates an FSM + * init_fsm - Creates a finite state machine + * @name: Name of this instance for logging purposes + * @state_names: Array of names for all states for logging purposes + * @event_names: Array of names for all events for logging purposes + * @nr_states: Number of states for this instance + * @nr_events: Number of events for this instance + * @tmpl: Pointer to fsm_node array describing this FSM + * @tmpl_len: Number of entries in the tmpl array + * @order: GFP flags for memory allocation (e.g. GFP_KERNEL) * - * @param name Name of this instance for logging purposes. - * @param state_names An array of names for all states for logging purposes. - * @param event_names An array of names for all events for logging purposes. - * @param nr_states Number of states for this instance. - * @param nr_events Number of events for this instance. - * @param tmpl An array of fsm_nodes, describing this FSM. - * @param tmpl_len Length of the describing array. - * @param order Parameter for allocation of the FSM data structs. + * Allocates and initializes a finite state machine instance with the + * specified states, events, and transition table. + * + * Return: Pointer to initialized FSM instance, or NULL on failure */ -extern fsm_instance * -init_fsm(char *name, const char **state_names, - const char **event_names, - int nr_states, int nr_events, const fsm_node *tmpl, - int tmpl_len, gfp_t order); +fsm_instance *init_fsm(char *name, const char **state_names, + const char **event_names, int nr_states, + int nr_events, const fsm_node *tmpl, + int tmpl_len, gfp_t order); /** - * Releases an FSM + * kfree_fsm - Releases a finite state machine + * @fi: Pointer to FSM instance, previously created with init_fsm() * - * @param fi Pointer to an FSM, previously created with init_fsm. + * Frees all memory associated with the FSM instance. */ -extern void kfree_fsm(fsm_instance *fi); +void kfree_fsm(fsm_instance *fi); #if FSM_DEBUG_HISTORY -extern void -fsm_print_history(fsm_instance *fi); +void fsm_print_history(fsm_instance *fi); -extern void -fsm_record_history(fsm_instance *fi, int state, int event); +void fsm_record_history(fsm_instance *fi, int state, int event); #endif /** - * Emits an event to a FSM. - * If an action function is defined for the current state/event combination, - * this function is called. + * fsm_event - Emits an event to a finite state machine + * @fi: Pointer to FSM which should receive the event + * @event: The event to be delivered + * @arg: Generic argument, passed to the action function * - * @param fi Pointer to FSM which should receive the event. - * @param event The event do be delivered. - * @param arg A generic argument, handed to the action function. + * If an action function is defined for the current state/event + * combination, that function is called with the provided arguments. * - * @return 0 on success, - * 1 if current state or event is out of range - * !0 if state and event in range, but no action defined. + * Return: + * * 0 - Success, action function was called + * * 1 - State/event out of range, or no action function defined */ static inline int fsm_event(fsm_instance *fi, int event, void *arg) @@ -182,11 +184,12 @@ fsm_event(fsm_instance *fi, int event, void *arg) } /** - * Modifies the state of an FSM. - * This does not trigger an event or calls an action function. + * fsm_newstate - Modifies the state of a finite state machine + * @fi: Pointer to FSM + * @newstate: The new state for this FSM * - * @param fi Pointer to FSM - * @param state The new state for this FSM. + * This does not trigger an event or call an action function. + * Wakes up any processes waiting on the FSM's wait queue. */ static inline void fsm_newstate(fsm_instance *fi, int newstate) @@ -203,11 +206,10 @@ fsm_newstate(fsm_instance *fi, int newstate) } /** - * Retrieves the state of an FSM + * fsm_getstate - Retrieves the current state of a finite state machine + * @fi: Pointer to FSM * - * @param fi Pointer to FSM - * - * @return The current state of the FSM. + * Return: Current state number */ static inline int fsm_getstate(fsm_instance *fi) @@ -216,51 +218,53 @@ fsm_getstate(fsm_instance *fi) } /** - * Retrieves the name of the state of an FSM + * fsm_getstate_str - Retrieves the name of the current FSM state + * @fi: Pointer to FSM * - * @param fi Pointer to FSM - * - * @return The current state of the FSM in a human readable form. + * Return: State name string, or "Invalid" if state is out of range */ -extern const char *fsm_getstate_str(fsm_instance *fi); +const char *fsm_getstate_str(fsm_instance *fi); /** - * Initializes a timer for an FSM. - * This prepares an fsm_timer for usage with fsm_addtimer. + * fsm_settimer - Initializes a timer for a finite state machine + * @fi: Pointer to FSM + * @this: The timer to be initialized * - * @param fi Pointer to FSM - * @param timer The timer to be initialized. + * Prepares an fsm_timer for usage with fsm_addtimer(). */ -extern void fsm_settimer(fsm_instance *fi, fsm_timer *); +void fsm_settimer(fsm_instance *fi, fsm_timer *this); /** - * Clears a pending timer of an FSM instance. + * fsm_deltimer - Clears a pending timer of an FSM instance + * @timer: The timer to clear * - * @param timer The timer to clear. + * Stops and removes the timer. Safe to call on an inactive timer. */ -extern void fsm_deltimer(fsm_timer *timer); +void fsm_deltimer(fsm_timer *timer); /** - * Adds and starts a timer to an FSM instance. + * fsm_addtimer - Adds and starts a timer for an FSM instance + * @timer: The timer to be added (timer->fi must point to the FSM instance) + * @millisec: Duration in milliseconds after which the timer expires + * @event: Event to trigger when timer expires + * @arg: Generic argument provided to the event handler * - * @param timer The timer to be added. The field fi of that timer - * must have been set to point to the instance. - * @param millisec Duration, after which the timer should expire. - * @param event Event, to trigger if timer expires. - * @param arg Generic argument, provided to expiry function. + * Starts a timer that will trigger the specified event after the given + * duration. The timer must have been initialized with fsm_settimer(). * - * @return 0 on success, -1 if timer is already active. + * Return: Always returns 0 */ -extern int fsm_addtimer(fsm_timer *timer, int millisec, int event, void *arg); +int fsm_addtimer(fsm_timer *timer, int millisec, int event, void *arg); /** - * Modifies a timer of an FSM. + * fsm_modtimer - Modifies a timer of a finite state machine + * @timer: The timer to modify + * @millisec: New duration in milliseconds after which the timer expires + * @event: Event to trigger when timer expires + * @arg: Generic argument provided to the event handler * - * @param timer The timer to modify. - * @param millisec Duration, after which the timer should expire. - * @param event Event, to trigger if timer expires. - * @param arg Generic argument, provided to expiry function. + * Stops the existing timer and restarts it with new parameters. */ -extern void fsm_modtimer(fsm_timer *timer, int millisec, int event, void *arg); +void fsm_modtimer(fsm_timer *timer, int millisec, int event, void *arg); #endif /* _FSM_H_ */ From 5cc65c01cdc577621d1320f4aa03a1382c5a4a45 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Tue, 4 Aug 2026 04:10:26 +0100 Subject: [PATCH 1109/1433] net: pcs: mtk-lynxi: check regmap reads in mtk_pcs_lynxi_get_state() mtk_pcs_lynxi_get_state() ignores regmap_read()'s return value; a failed read leaves bm and adv holding uninitialized stack values which are then decoded into the reported link state. The regmaps backing the MT7531 SGMII PCS instances sit on an MDIO bus where reads can fail. Check both reads and report the link as down on error; phylink presets state->link before the callback, so a bare return would leave a failed read reported as link-up. Signed-off-by: Daniel Golle Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/fce70657fc03bbaf60a04c0fbf2f418531135c4f.1785811140.git.daniel@makrotopia.org Signed-off-by: Jakub Kicinski --- drivers/net/pcs/pcs-mtk-lynxi.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/net/pcs/pcs-mtk-lynxi.c b/drivers/net/pcs/pcs-mtk-lynxi.c index a753bd88cbc2..7290fc3e5d18 100644 --- a/drivers/net/pcs/pcs-mtk-lynxi.c +++ b/drivers/net/pcs/pcs-mtk-lynxi.c @@ -113,8 +113,11 @@ static void mtk_pcs_lynxi_get_state(struct phylink_pcs *pcs, unsigned int bm, adv; /* Read the BMSR and LPA */ - regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &bm); - regmap_read(mpcs->regmap, SGMSYS_PCS_ADVERTISE, &adv); + if (regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &bm) || + regmap_read(mpcs->regmap, SGMSYS_PCS_ADVERTISE, &adv)) { + state->link = false; + return; + } phylink_mii_c22_pcs_decode_state(state, neg_mode, FIELD_GET(SGMII_BMSR, bm), From 573d6e3afe3d01e34dc82c77bf22ca96a74d805e Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Tue, 4 Aug 2026 04:10:33 +0100 Subject: [PATCH 1110/1433] net: dsa: mt7530: check bus->read() error in core_rmw() core_rmw() accesses the MMD core registers directly rather than through the regmap and has the same unchecked bus->read() as the one just fixed in the MDIO regmap backend: a negative errno is consumed as register data, modified and written back to the switch. Check the read and bail out like the surrounding bus accesses do. Signed-off-by: Daniel Golle Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/48bb9f0b311a9efeda2a6b24a7e05d4792393a3b.1785811140.git.daniel@makrotopia.org Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mt7530.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index 55131bfd11f6..5ff203fba37e 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -124,8 +124,12 @@ core_rmw(struct mt7530_priv *priv, u32 reg, u32 mask, u32 set) goto err; /* Read the content of the MMD's selected register */ - val = bus->read(bus, MT753X_CTRL_PHY_ADDR(priv->mdiodev->addr), + ret = bus->read(bus, MT753X_CTRL_PHY_ADDR(priv->mdiodev->addr), MII_MMD_DATA); + if (ret < 0) + goto err; + val = ret; + val &= ~mask; val |= set; /* Write the data into MMD's selected register */ From 1c95dcb7e923beebccdc79adc3dc1ec8c52295d0 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Tue, 4 Aug 2026 04:10:40 +0100 Subject: [PATCH 1111/1433] net: dsa: mt7530: error out on failed PHY_IAC command writes MT7531_PHY_ACS_ST is only ever set by the command write that precedes each poll in the MT7531 indirect PHY access functions, and that write's return value is discarded. A failed write leaves ACS_ST at 0 from the previous access, so the poll succeeds on its first iteration and the functions return stale IAC contents as if they were fresh PHY data. Check the writes and bail out before polling. Signed-off-by: Daniel Golle Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/c34602e63a20ebbfb97babd145c82832d7a0b523.1785811140.git.daniel@makrotopia.org Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mt7530.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index 5ff203fba37e..f60a5136799b 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -565,7 +565,9 @@ mt7531_ind_c45_phy_read(struct mt7530_priv *priv, int port, int devad, reg = MT7531_MDIO_CL45_ADDR | MT7531_MDIO_PHY_ADDR(port) | MT7531_MDIO_DEV_ADDR(devad) | regnum; - mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); + ret = mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); + if (ret < 0) + goto out; ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, val, !(val & MT7531_PHY_ACS_ST), 20, 100000); @@ -576,7 +578,9 @@ mt7531_ind_c45_phy_read(struct mt7530_priv *priv, int port, int devad, reg = MT7531_MDIO_CL45_READ | MT7531_MDIO_PHY_ADDR(port) | MT7531_MDIO_DEV_ADDR(devad); - mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); + ret = mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); + if (ret < 0) + goto out; ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, val, !(val & MT7531_PHY_ACS_ST), 20, 100000); @@ -610,7 +614,9 @@ mt7531_ind_c45_phy_write(struct mt7530_priv *priv, int port, int devad, reg = MT7531_MDIO_CL45_ADDR | MT7531_MDIO_PHY_ADDR(port) | MT7531_MDIO_DEV_ADDR(devad) | regnum; - mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); + ret = mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); + if (ret < 0) + goto out; ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, val, !(val & MT7531_PHY_ACS_ST), 20, 100000); @@ -621,7 +627,9 @@ mt7531_ind_c45_phy_write(struct mt7530_priv *priv, int port, int devad, reg = MT7531_MDIO_CL45_WRITE | MT7531_MDIO_PHY_ADDR(port) | MT7531_MDIO_DEV_ADDR(devad) | data; - mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); + ret = mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); + if (ret < 0) + goto out; ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, val, !(val & MT7531_PHY_ACS_ST), 20, 100000); @@ -654,7 +662,9 @@ mt7531_ind_c22_phy_read(struct mt7530_priv *priv, int port, int regnum) val = MT7531_MDIO_CL22_READ | MT7531_MDIO_PHY_ADDR(port) | MT7531_MDIO_REG_ADDR(regnum); - mt7530_mii_write(priv, MT7531_PHY_IAC, val | MT7531_PHY_ACS_ST); + ret = mt7530_mii_write(priv, MT7531_PHY_IAC, val | MT7531_PHY_ACS_ST); + if (ret < 0) + goto out; ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, val, !(val & MT7531_PHY_ACS_ST), 20, 100000); @@ -689,7 +699,9 @@ mt7531_ind_c22_phy_write(struct mt7530_priv *priv, int port, int regnum, reg = MT7531_MDIO_CL22_WRITE | MT7531_MDIO_PHY_ADDR(port) | MT7531_MDIO_REG_ADDR(regnum) | data; - mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); + ret = mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); + if (ret < 0) + goto out; ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, reg, !(reg & MT7531_PHY_ACS_ST), 20, 100000); From 1ae63b018d3d70e96d420b59a1334860cf232bc6 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Tue, 4 Aug 2026 04:10:46 +0100 Subject: [PATCH 1112/1433] net: dsa: mt7530: check CORE_PLL_GROUP4 access in mt7531_setup() mt7531_setup() reads CORE_PLL_GROUP4 through the MT7531 indirect c45 PHY access, modifies it and writes it back to enable the PHY core PLL, but checks neither the read nor the write. Now that the indirect access functions propagate command-write failures, a failed read returns a negative errno that would be bit-modified and written back into the PLL register, and a failed write-back would go unnoticed. Check both and bail out. The adjacent EEE advertisement writes push a constant value and cannot corrupt state, so they are left as is. Signed-off-by: Daniel Golle Link: https://patch.msgid.link/a7dfe3b66ea6ac1ae7915034de0527060e6ddcd4.1785811140.git.daniel@makrotopia.org Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mt7530.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index f60a5136799b..c60e22ea270e 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -2781,14 +2781,20 @@ mt7531_setup(struct dsa_switch *ds) * phy_[read,write]_mmd_indirect is called, we provide our own * mt7531_ind_mmd_phy_[read,write] to complete this function. */ - val = mt7531_ind_c45_phy_read(priv, + ret = mt7531_ind_c45_phy_read(priv, MT753X_CTRL_PHY_ADDR(priv->mdiodev->addr), MDIO_MMD_VEND2, CORE_PLL_GROUP4); + if (ret < 0) + return ret; + + val = ret; val |= MT7531_RG_SYSPLL_DMY2 | MT7531_PHY_PLL_BYPASS_MODE; val &= ~MT7531_PHY_PLL_OFF; - mt7531_ind_c45_phy_write(priv, - MT753X_CTRL_PHY_ADDR(priv->mdiodev->addr), - MDIO_MMD_VEND2, CORE_PLL_GROUP4, val); + ret = mt7531_ind_c45_phy_write(priv, + MT753X_CTRL_PHY_ADDR(priv->mdiodev->addr), + MDIO_MMD_VEND2, CORE_PLL_GROUP4, val); + if (ret < 0) + return ret; /* Disable EEE advertisement on the switch PHYs. */ for (i = MT753X_CTRL_PHY_ADDR(priv->mdiodev->addr); From f67b0bae07fe7eddf919ee1f03fc66d351547a83 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Tue, 4 Aug 2026 04:10:53 +0100 Subject: [PATCH 1113/1433] net: dsa: mt7530: check command register writes in fdb and vlan cmd mt7530_fdb_cmd() and mt7530_vlan_cmd() start a command by writing the BUSY bit to MT7530_ATC / MT7530_VTCR, then poll for it to clear. mt7530_write() discards the write's return value, so a failed command write leaves BUSY unset and the poll succeeds on its first read, reporting a command that never ran as done -- returning stale FDB data or silently dropping a VLAN table update. Return mt7530_mii_write()'s error from mt7530_write() and check it in both command helpers. Signed-off-by: Daniel Golle Link: https://patch.msgid.link/0e5d65a672313286e5a8ce28a9faba9c8972dbb6.1785811140.git.daniel@makrotopia.org Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mt7530.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index c60e22ea270e..d66d926a71e1 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -185,14 +185,18 @@ mt7530_mii_read(struct mt7530_priv *priv, u32 reg) return val; } -static void +static int mt7530_write(struct mt7530_priv *priv, u32 reg, u32 val) { + int ret; + mt7530_mutex_lock(priv); - mt7530_mii_write(priv, reg, val); + ret = mt7530_mii_write(priv, reg, val); mt7530_mutex_unlock(priv); + + return ret; } static u32 @@ -249,7 +253,9 @@ mt7530_fdb_cmd(struct mt7530_priv *priv, enum mt7530_fdb_cmd cmd, u32 *rsp) /* Set the command operating upon the MAC address entries */ val = ATC_BUSY | ATC_MAT(0) | cmd; - mt7530_write(priv, MT7530_ATC, val); + ret = mt7530_write(priv, MT7530_ATC, val); + if (ret) + return ret; mt7530_mutex_lock(priv); @@ -1632,7 +1638,9 @@ mt7530_vlan_cmd(struct mt7530_priv *priv, enum mt7530_vlan_cmd cmd, u16 vid) int ret; val = VTCR_BUSY | VTCR_FUNC(cmd) | vid; - mt7530_write(priv, MT7530_VTCR, val); + ret = mt7530_write(priv, MT7530_VTCR, val); + if (ret) + return ret; mt7530_mutex_lock(priv); From dd52b3df25ed25a7a881d45d9fbfaa0ac7dec5d5 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Tue, 4 Aug 2026 04:11:01 +0100 Subject: [PATCH 1114/1433] net: dsa: mt7530: serialize the regmap IRQ chip like every other user The switch register regmap is created with .disable_locking = true; every other user in this driver calls mt7530_mutex_lock()/unlock() around it, which takes priv->bus->mdio_lock, since the underlying mt7530_regmap_read()/write() issue raw, unserialized bus->read()/ write() MDIO transactions. mt7530_setup_irq() hands this same unlocked regmap straight to devm_regmap_add_irq_chip_fwnode(), whose threaded IRQ handler then calls regmap_read()/regmap_update_bits() on it without ever calling mt7530_mutex_lock(). An interrupt firing while another thread is mid-transaction on the same regmap (e.g. a paged register access, or an indirect PHY access) can interleave with the IRQ handler's own paged access and corrupt page selection on either side. Use struct regmap_irq_chip's handle_mask_sync hook to call mt7530_mutex_lock()/unlock() around the mask register write regmap-irq issues whenever a consumer of one of the mapped sub-IRQs enables, disables, requests or frees its line. This needs a per-device copy of mt7530_regmap_irq_chip, since devm_regmap_add_irq_chip_fwnode() keeps a pointer to it rather than copying it. handle_pre_irq/handle_post_irq, which would additionally cover the status read and ack write the threaded handler does directly, bracket the whole handler including its handle_nested_irq() calls. Lockdep caught this on hardware: those calls reach phy_interrupt() for the per-port PHY IRQ lines mapped through this chip, which takes phydev->lock, while phy_attach_direct() and this driver's own indirect PHY access already establish the opposite order (phydev->lock, then priv->bus->mdio_lock) elsewhere. Using them here would close that cycle, so they are not used. regmap_irq_sync_unlock() also has its own init_ack_masked path, used by this chip, which unconditionally does its own regmap_write() to ack currently-masked IRQs; that path has no per-driver hook. Together with the threaded handler's own status read and ack write, these stay unprotected -- a narrower, harder-to-hit gap than the recurring mask sync above -- and will be closed once the switch regmap moves to regmap's own locking in the driver-wide register access cleanup. Signed-off-by: Daniel Golle Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/818840879e9cd20f8d568789da29b3474c8f3ab9.1785811140.git.daniel@makrotopia.org Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mt7530.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index d66d926a71e1..2b7be091c056 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -2319,6 +2319,21 @@ static const struct regmap_irq mt7530_irqs[] = { REGMAP_IRQ_REG_LINE(31, 32), /* ACL */ }; +/* Serialize regmap-irq's mask sync like every other regmap user */ +static int mt7530_irq_mask_sync(int index, unsigned int mask_buf_def, + unsigned int mask_buf, void *irq_drv_data) +{ + struct mt7530_priv *priv = irq_drv_data; + int ret; + + mt7530_mutex_lock(priv); + ret = regmap_update_bits(priv->regmap, MT7530_SYS_INT_EN, + mask_buf_def, ~mask_buf); + mt7530_mutex_unlock(priv); + + return ret; +} + static const struct regmap_irq_chip mt7530_regmap_irq_chip = { .name = KBUILD_MODNAME, .status_base = MT7530_SYS_INT_STS, @@ -2328,12 +2343,14 @@ static const struct regmap_irq_chip mt7530_regmap_irq_chip = { .irqs = mt7530_irqs, .num_irqs = ARRAY_SIZE(mt7530_irqs), .num_regs = 1, + .handle_mask_sync = mt7530_irq_mask_sync, }; static int mt7530_setup_irq(struct mt7530_priv *priv) { struct regmap_irq_chip_data *irq_data; + struct regmap_irq_chip *chip; struct device *dev = priv->dev; struct device_node *np = dev->of_node; int irq, ret; @@ -2353,10 +2370,17 @@ mt7530_setup_irq(struct mt7530_priv *priv) if (priv->id == ID_MT7530 || priv->id == ID_MT7621) mt7530_set(priv, MT7530_TOP_SIG_CTRL, TOP_SIG_CTRL_NORMAL); + chip = devm_kmemdup(dev, &mt7530_regmap_irq_chip, sizeof(*chip), + GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->irq_drv_data = priv; + ret = devm_regmap_add_irq_chip_fwnode(dev, dev_fwnode(dev), priv->regmap, irq, IRQF_ONESHOT, - 0, &mt7530_regmap_irq_chip, + 0, chip, &irq_data); if (ret) return ret; From 4d8e0becfd341d749b8c4f51ac6d758f2ecd55c0 Mon Sep 17 00:00:00 2001 From: Ronan Marchal Date: Mon, 3 Aug 2026 23:11:49 +0200 Subject: [PATCH 1115/1433] net: niu: fix potential buffer overflow/truncation in irq names Building with W=1 reports a -Wformat-truncation warning on niu_set_irq_name(): the "%s:SYSERR" format could be truncated because irq_name[] was one byte too small for the worst case interface name length (IFNAMSIZ-1) plus the ":SYSERR" suffix. Increase the irq_name buffer size to account for the suffix and replace the remaining sprintf() calls in the same function with snprintf() to avoid possible buffer overflows. Tested: - Built the kernel with W=1 and confirmed the warning is no longer reported. - No NIU hardware was available for runtime testing. Signed-off-by: Ronan Marchal Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260803211149.10585-1-ronanmarchal29@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/sun/niu.c | 6 +++--- drivers/net/ethernet/sun/niu.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/sun/niu.c b/drivers/net/ethernet/sun/niu.c index 88df15e6dd74..54dd7281191d 100644 --- a/drivers/net/ethernet/sun/niu.c +++ b/drivers/net/ethernet/sun/niu.c @@ -6021,11 +6021,11 @@ static void niu_set_irq_name(struct niu *np) int port = np->port; int i, j = 1; - sprintf(np->irq_name[0], "%s:MAC", np->dev->name); + snprintf(np->irq_name[0], sizeof(np->irq_name[0]), "%s:MAC", np->dev->name); if (port == 0) { - sprintf(np->irq_name[1], "%s:MIF", np->dev->name); - sprintf(np->irq_name[2], "%s:SYSERR", np->dev->name); + snprintf(np->irq_name[1], sizeof(np->irq_name[1]), "%s:MIF", np->dev->name); + snprintf(np->irq_name[2], sizeof(np->irq_name[2]), "%s:SYSERR", np->dev->name); j = 3; } diff --git a/drivers/net/ethernet/sun/niu.h b/drivers/net/ethernet/sun/niu.h index d8368043fc3b..676d31499ac3 100644 --- a/drivers/net/ethernet/sun/niu.h +++ b/drivers/net/ethernet/sun/niu.h @@ -3262,7 +3262,7 @@ struct niu { #define NIU_FLAGS_XMAC 0x00010000 /* 0=BMAC 1=XMAC */ u32 msg_enable; - char irq_name[NIU_NUM_RXCHAN+NIU_NUM_TXCHAN+3][IFNAMSIZ + 6]; + char irq_name[NIU_NUM_RXCHAN + NIU_NUM_TXCHAN + 3][IFNAMSIZ + 7]; /* Protects hw programming, and ring state. */ spinlock_t lock; From 9515a13829dfa98e3c462e179337e7b9cc3ca841 Mon Sep 17 00:00:00 2001 From: Mohsin Bashir Date: Tue, 4 Aug 2026 20:09:23 -0700 Subject: [PATCH 1116/1433] selftests: net: shaper: Drop redundant command timeouts Commit 57bb59ab6fa3 ("selftests: net: bump default cmd() timeout to 20 seconds") raised the default cmd() timeout to 20 seconds, so the explicit timeout=10 passed to the ethtool channel commands in queue_update() is now redundant and, in fact, shorter than the default. Drop it and rely on the default timeout. Signed-off-by: Mohsin Bashir Link: https://patch.msgid.link/20260805030936.1092907-2-mohsin.bashr@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/shaper.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py index e39d270e688d..c80a4bf8cc05 100755 --- a/tools/testing/selftests/drivers/net/shaper.py +++ b/tools/testing/selftests/drivers/net/shaper.py @@ -388,7 +388,7 @@ def queue_update(cfg, nl_shaper) -> None: 'bw-max': (i + 1) * 1000}) # Delete a channel, with no shapers configured on top of the related # queue: no changes expected - cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} 3", timeout=10) + cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} 3") shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(shapers, [{'ifindex': cfg.ifindex, 'parent': {'scope': 'netdev'}, @@ -408,7 +408,7 @@ def queue_update(cfg, nl_shaper) -> None: # Delete a channel, with a shaper configured on top of the related # queue: the shaper must be deleted, too - cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} 2", timeout=10) + cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} 2") shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(shapers, [{'ifindex': cfg.ifindex, @@ -423,7 +423,7 @@ def queue_update(cfg, nl_shaper) -> None: 'bw-max': 2000}]) # Restore the original channels number, no expected changes - cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} {cfg.nr_queues}", timeout=10) + cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} {cfg.nr_queues}") shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(shapers, [{'ifindex': cfg.ifindex, 'parent': {'scope': 'netdev'}, From 7ea7db704f51c71253996fa8ee19670516d61f67 Mon Sep 17 00:00:00 2001 From: Mohsin Bashir Date: Tue, 4 Aug 2026 20:09:24 -0700 Subject: [PATCH 1117/1433] selftests: net: shaper: Prepare helpers for group tests dup_leaves expects the kernel to reject a group request that lists the same queue twice. When that rejection does not happen, ksft_raises only records a failed check and leaves cm.exception as None, so the following errno check raises AttributeError. Worse, the accepted group request leaves a node shaper and queue 0 behind, which makes later tests fail for an unrelated reason. Handle the negative test explicitly instead. If group fails, verify that the errno is EINVAL and return. If group succeeds, delete the node returned by the operation and queue 0 before reporting the failure. Give the duplicate leaves different weights so the request still contains two distinct leaf entries while exercising duplicate handle validation. This also introduces _delete_shaper(), cached _cap_get(), and _require_caps() helpers as preparation for the following shaper group tests. The follow-on tests need the same capability checks for node and queue scope support. Keeping that logic in one place avoids repeating raw EOPNOTSUPP handling in each test. Signed-off-by: Mohsin Bashir Link: https://patch.msgid.link/20260805030936.1092907-3-mohsin.bashr@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/shaper.py | 145 +++++++++++------- 1 file changed, 86 insertions(+), 59 deletions(-) diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py index c80a4bf8cc05..1954f3263f25 100755 --- a/tools/testing/selftests/drivers/net/shaper.py +++ b/tools/testing/selftests/drivers/net/shaper.py @@ -2,13 +2,52 @@ # SPDX-License-Identifier: GPL-2.0 import errno +import glob from lib.py import ksft_run, ksft_exit -from lib.py import ksft_eq, ksft_raises, ksft_true, KsftSkipEx +from lib.py import ksft_eq, ksft_true, ksft_raises, KsftSkipEx from lib.py import EthtoolFamily, NetshaperFamily from lib.py import NetDrvEnv from lib.py import NlError -from lib.py import cmd +from lib.py import cmd, defer + +def _delete_shaper(cfg, nl_shaper, handle) -> None: + """ Delete the shaper identified by handle, ignoring a missing-shaper error. """ + try: + nl_shaper.delete({'ifindex': cfg.ifindex, + 'handle': handle}) + except NlError as e: + if e.error != errno.ENOENT: + raise + +def _require_queues(cfg, count): + """ Return the netdev TX queue count, skipping the test if fewer than count exist. """ + qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/tx-*")) + if qcnt < count: + raise KsftSkipEx(f"netdev has {qcnt} queues, {count} required") + return qcnt + +def _cap_get(cfg, nl_shaper, scope): + """ Return the shaper capabilities for the given scope, caching them on cfg. """ + if not hasattr(cfg, 'cap_cache'): + cfg.cap_cache = {} + if scope not in cfg.cap_cache: + cfg.cap_cache[scope] = nl_shaper.cap_get({'ifindex': cfg.ifindex, + 'scope': scope}) + + return cfg.cap_cache[scope] + +def _require_caps(cfg, nl_shaper, scope, caps, msg) -> None: + """ Skip the test unless the given scope advertises all the required caps. """ + try: + supported = _cap_get(cfg, nl_shaper, scope) + except NlError as e: + if e.error == errno.EOPNOTSUPP: + raise KsftSkipEx(f"{scope} scope shapers not supported by the device") + raise + + if not set(caps).issubset(supported): + raise KsftSkipEx(msg) def get_shapers(cfg, nl_shaper) -> None: try: @@ -44,17 +83,8 @@ def set_qshapers(cfg, nl_shaper) -> None: if not 'support-bw-max' in caps or not 'support-metric-bps' in caps: raise KsftSkipEx("device does not support queue scope shapers with bw_max and metric bps") - cfg.queues = True; - netnl = EthtoolFamily() - channels = netnl.channels_get({'header': {'dev-index': cfg.ifindex}}) - if channels['combined-count'] == 0: - cfg.rx_type = 'rx' - cfg.nr_queues = channels['rx-count'] - else: - cfg.rx_type = 'combined' - cfg.nr_queues = channels['combined-count'] - if cfg.nr_queues < 3: - raise KsftSkipEx(f"device does not support enough queues min 3 found {cfg.nr_queues}") + _require_queues(cfg, 3) + cfg.queues = True nl_shaper.set({'ifindex': cfg.ifindex, 'handle': {'scope': 'queue', 'id': 1}, @@ -140,8 +170,7 @@ def del_nshapers(cfg, nl_shaper) -> None: def basic_groups(cfg, nl_shaper) -> None: if not cfg.netdev: raise KsftSkipEx("netdev shaper not supported by the device") - if cfg.nr_queues < 3: - raise KsftSkipEx(f"netdev does not have enough queues min 3 reported {cfg.nr_queues}") + _require_queues(cfg, 3) try: caps = nl_shaper.cap_get({'ifindex': cfg.ifindex, @@ -186,28 +215,14 @@ def basic_groups(cfg, nl_shaper) -> None: 'handle': {'scope': 'netdev'}}) def qgroups(cfg, nl_shaper) -> None: - if cfg.nr_queues < 4: - raise KsftSkipEx(f"netdev does not have enough queues min 4 reported {cfg.nr_queues}") - try: - caps = nl_shaper.cap_get({'ifindex': cfg.ifindex, - 'scope':'node'}) - except NlError as e: - if e.error == 95: - raise KsftSkipEx("shapers not supported by the device") - raise - if not 'support-bw-max' in caps or not 'support-metric-bps' in caps: - raise KsftSkipEx("device does not support node scope shapers with bw_max and metric bps") - try: - caps = nl_shaper.cap_get({'ifindex': cfg.ifindex, - 'scope':'queue'}) - except NlError as e: - if e.error == 95: - raise KsftSkipEx("shapers not supported by the device") - raise - if not 'support-nesting' in caps or not 'support-weight' in caps or not 'support-metric-bps' in caps: - raise KsftSkipEx("device does not support nested queue scope shapers with weight") + _require_queues(cfg, 4) + _require_caps(cfg, nl_shaper, 'node', + ['support-bw-max', 'support-metric-bps'], + "device does not support node scope shapers with bw_max and metric bps") + _require_caps(cfg, nl_shaper, 'queue', + ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") - cfg.groups = True; node_handle = nl_shaper.group({ 'ifindex': cfg.ifindex, 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, @@ -285,17 +300,12 @@ def qgroups(cfg, nl_shaper) -> None: ksft_eq(len(shapers), 0) def delegation(cfg, nl_shaper) -> None: - if not cfg.groups: - raise KsftSkipEx("device does not support node scope") - try: - caps = nl_shaper.cap_get({'ifindex': cfg.ifindex, - 'scope':'node'}) - except NlError as e: - if e.error == 95: - raise KsftSkipEx("node scope shapers not supported by the device") - raise - if not 'support-nesting' in caps: - raise KsftSkipEx("device does not support node scope shapers nesting") + _require_queues(cfg, 4) + _require_caps(cfg, nl_shaper, 'node', + ['support-bw-max', 'support-metric-bps', 'support-nesting'], + "device does not support node scope shapers with bw_max, metric bps and nesting") + _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") node_handle = nl_shaper.group({ 'ifindex': cfg.ifindex, @@ -376,19 +386,24 @@ def delegation(cfg, nl_shaper) -> None: ksft_eq(len(shapers), 0) def queue_update(cfg, nl_shaper) -> None: - if cfg.nr_queues < 4: - raise KsftSkipEx(f"netdev does not have enough queues min 4 reported {cfg.nr_queues}") + nq = _require_queues(cfg, 4) if not cfg.queues: raise KsftSkipEx("device does not support queue scope") + netnl = EthtoolFamily() + channels = netnl.channels_get({'header': {'dev-index': cfg.ifindex}}) + ch_type = 'combined' if channels['combined-count'] else 'tx' + for i in range(3): nl_shaper.set({'ifindex': cfg.ifindex, 'handle': {'scope': 'queue', 'id': i}, 'metric': 'bps', 'bw-max': (i + 1) * 1000}) + defer(cmd, f"ethtool -L {cfg.dev['ifname']} {ch_type} {nq}") + # Delete a channel, with no shapers configured on top of the related # queue: no changes expected - cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} 3") + cmd(f"ethtool -L {cfg.dev['ifname']} {ch_type} 3") shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(shapers, [{'ifindex': cfg.ifindex, 'parent': {'scope': 'netdev'}, @@ -408,7 +423,7 @@ def queue_update(cfg, nl_shaper) -> None: # Delete a channel, with a shaper configured on top of the related # queue: the shaper must be deleted, too - cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} 2") + cmd(f"ethtool -L {cfg.dev['ifname']} {ch_type} 2") shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(shapers, [{'ifindex': cfg.ifindex, @@ -423,7 +438,7 @@ def queue_update(cfg, nl_shaper) -> None: 'bw-max': 2000}]) # Restore the original channels number, no expected changes - cmd(f"ethtool -L {cfg.dev['ifname']} {cfg.rx_type} {cfg.nr_queues}") + cmd(f"ethtool -L {cfg.dev['ifname']} {ch_type} {nq}") shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(shapers, [{'ifindex': cfg.ifindex, 'parent': {'scope': 'netdev'}, @@ -443,25 +458,37 @@ def queue_update(cfg, nl_shaper) -> None: def dup_leaves(cfg, nl_shaper) -> None: """ Ensure that the kernel rejects duplicate leaves. """ - if not cfg.groups: - raise KsftSkipEx("device does not support node scope") + _require_caps(cfg, nl_shaper, 'node', ['support-bw-max', 'support-metric-bps'], + "device does not support node scope shapers with bw_max and metric bps") + _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") + node_handle = None with ksft_raises(NlError) as cm: - nl_shaper.group({ + node_handle = nl_shaper.group({ 'ifindex': cfg.ifindex, - 'leaves':[{'handle': {'scope': 'queue', 'id': 0}}, - {'handle': {'scope': 'queue', 'id': 0}}], + 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, + 'weight': 1}, + {'handle': {'scope': 'queue', 'id': 0}, + 'weight': 2}], 'handle': {'scope':'node'}, 'metric': 'bps', 'bw-max': 10000}) + + # Clean up in case the kernel wrongly accepted the request. + if node_handle: + _delete_shaper(cfg, nl_shaper, node_handle['handle']) + _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': 0}) + + # ksft_raises() has already recorded the failure if nothing was raised. + if cm.exception is None: + return ksft_eq(cm.exception.error, errno.EINVAL) def main() -> None: with NetDrvEnv(__file__, queue_count=4) as cfg: cfg.queues = False cfg.netdev = False - cfg.groups = False - cfg.nr_queues = 0 ksft_run([get_shapers, get_caps, set_qshapers, From 1b5c2eb00e596002c9414ca2cf90c51053159f00 Mon Sep 17 00:00:00 2001 From: Mohsin Bashir Date: Tue, 4 Aug 2026 20:09:25 -0700 Subject: [PATCH 1118/1433] selftests: net: shaper: Decouple basic_groups from netdev rate limiting Decouple basic_groups from the set_nshapers test dependency. The test was gated on cfg.netdev which is set by set_nshapers. Replace with direct capability checks: netdev scope support (required for grouping under netdev handle) and queue scope nesting + weight. Remove bw-max and metric from the .group call so the test validates pure queue grouping without rate limiting. The rate-limited variant is restored in the following patch, which adds a dedicated basic_groups_with_rate test. Signed-off-by: Mohsin Bashir Link: https://patch.msgid.link/20260805030936.1092907-4-mohsin.bashr@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/shaper.py | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py index 1954f3263f25..45a4bf42995e 100755 --- a/tools/testing/selftests/drivers/net/shaper.py +++ b/tools/testing/selftests/drivers/net/shaper.py @@ -168,19 +168,11 @@ def del_nshapers(cfg, nl_shaper) -> None: ksft_eq(len(shapers), 0) def basic_groups(cfg, nl_shaper) -> None: - if not cfg.netdev: - raise KsftSkipEx("netdev shaper not supported by the device") _require_queues(cfg, 3) - try: - caps = nl_shaper.cap_get({'ifindex': cfg.ifindex, - 'scope':'queue'}) - except NlError as e: - if e.error == 95: - raise KsftSkipEx("shapers not supported by the device") - raise - if not 'support-weight' in caps: - raise KsftSkipEx("device does not support queue scope shapers with weight") + _require_caps(cfg, nl_shaper, 'netdev', [], "netdev scope not supported by the device") + _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], + "queue scope not supported with nesting and weight") node_handle = nl_shaper.group({ 'ifindex': cfg.ifindex, @@ -188,31 +180,31 @@ def basic_groups(cfg, nl_shaper) -> None: 'weight': 1}, {'handle': {'scope': 'queue', 'id': 2}, 'weight': 2}], - 'handle': {'scope':'netdev'}, - 'metric': 'bps', - 'bw-max': 10000}) + 'handle': {'scope':'netdev'}}) ksft_eq(node_handle, {'ifindex': cfg.ifindex, 'handle': {'scope': 'netdev'}}) + del_node = defer(_delete_shaper, cfg, nl_shaper, {'scope': 'netdev'}) + del_queues = [defer(_delete_shaper, cfg, nl_shaper, + {'scope': 'queue', 'id': qid}) + for qid in (1, 2)] + shaper = nl_shaper.get({'ifindex': cfg.ifindex, 'handle': {'scope': 'queue', 'id': 1}}) ksft_eq(shaper, {'ifindex': cfg.ifindex, 'parent': {'scope': 'netdev'}, 'handle': {'scope': 'queue', 'id': 1}, 'weight': 1 }) + for dq in del_queues: + dq.exec() - nl_shaper.delete({'ifindex': cfg.ifindex, - 'handle': {'scope': 'queue', 'id': 2}}) - nl_shaper.delete({'ifindex': cfg.ifindex, - 'handle': {'scope': 'queue', 'id': 1}}) - - # Deleting all the leaves shaper does not affect the node one - # when the latter has 'netdev' scope. shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) - ksft_eq(len(shapers), 1) + ksft_eq(shapers, [{'ifindex': cfg.ifindex, + 'handle': {'scope': 'netdev'}}]) - nl_shaper.delete({'ifindex': cfg.ifindex, - 'handle': {'scope': 'netdev'}}) + del_node.exec() + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 0) def qgroups(cfg, nl_shaper) -> None: _require_queues(cfg, 4) From ff0c37b8c134ace868ea34a0560a9187edd10704 Mon Sep 17 00:00:00 2001 From: Mohsin Bashir Date: Tue, 4 Aug 2026 20:09:26 -0700 Subject: [PATCH 1119/1433] selftests: net: shaper: Add basic_groups_with_rate test Add a test that groups queues under the netdev parent with rate limiting enabled. Extract the common group-under-netdev flow into _group_under_netdev helper to share with basic_groups. The test independently checks for netdev scope bw_max and metric capabilities before proceeding, and verifies that the netdev shaper persists after leaf deletion. Signed-off-by: Mohsin Bashir Link: https://patch.msgid.link/20260805030936.1092907-5-mohsin.bashr@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/shaper.py | 79 ++++++++++++++++--- 1 file changed, 66 insertions(+), 13 deletions(-) diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py index 45a4bf42995e..168a8dd057e5 100755 --- a/tools/testing/selftests/drivers/net/shaper.py +++ b/tools/testing/selftests/drivers/net/shaper.py @@ -167,20 +167,25 @@ def del_nshapers(cfg, nl_shaper) -> None: shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(len(shapers), 0) -def basic_groups(cfg, nl_shaper) -> None: - _require_queues(cfg, 3) +def _group_under_netdev(cfg, nl_shaper, bw_max=None): + r"""Group queues under a netdev-scope node; caller owns node teardown. - _require_caps(cfg, nl_shaper, 'netdev', [], "netdev scope not supported by the device") - _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], - "queue scope not supported with nesting and weight") + netdev netdev + / \ del Q1,Q2 + Q1 Q2 -------> (netdev node persists) + """ + group_args = { + 'ifindex': cfg.ifindex, + 'leaves': [{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 1}, + {'handle': {'scope': 'queue', 'id': 2}, + 'weight': 2}], + 'handle': {'scope': 'netdev'}} + if bw_max: + group_args['metric'] = 'bps' + group_args['bw-max'] = bw_max - node_handle = nl_shaper.group({ - 'ifindex': cfg.ifindex, - 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, - 'weight': 1}, - {'handle': {'scope': 'queue', 'id': 2}, - 'weight': 2}], - 'handle': {'scope':'netdev'}}) + node_handle = nl_shaper.group(group_args) ksft_eq(node_handle, {'ifindex': cfg.ifindex, 'handle': {'scope': 'netdev'}}) @@ -194,10 +199,29 @@ def basic_groups(cfg, nl_shaper) -> None: ksft_eq(shaper, {'ifindex': cfg.ifindex, 'parent': {'scope': 'netdev'}, 'handle': {'scope': 'queue', 'id': 1}, - 'weight': 1 }) + 'weight': 1}) for dq in del_queues: dq.exec() + # Caller owns the node teardown so it can verify the netdev-scope node + # survives leaf deletion before removing it. + return del_node + +def basic_groups(cfg, nl_shaper) -> None: + r"""Group queues under a netdev-scope node, then tear it down. + + netdev + / \ + Q1 Q2 + """ + _require_queues(cfg, 3) + + _require_caps(cfg, nl_shaper, 'netdev', [], "netdev scope not supported by the device") + _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], + "queue scope not supported with nesting and weight") + + del_node = _group_under_netdev(cfg, nl_shaper) + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(shapers, [{'ifindex': cfg.ifindex, 'handle': {'scope': 'netdev'}}]) @@ -206,6 +230,34 @@ def basic_groups(cfg, nl_shaper) -> None: shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(len(shapers), 0) +def basic_groups_with_rate(cfg, nl_shaper) -> None: + r"""Rate-limited netdev-scope node outlives deletion of its leaves. + + netdev[10kbps] netdev[10kbps] + / \ del Q1,Q2 + Q1 Q2 -------> (node persists) + """ + bw_max = 10000 + + _require_queues(cfg, 3) + + _require_caps(cfg, nl_shaper, 'netdev', ['support-bw-max', 'support-metric-bps'], + "device does not support netdev scope rate limiting") + _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], + "device does not support queue scope shapers with nesting and weight") + + del_node = _group_under_netdev(cfg, nl_shaper, bw_max=bw_max) + + # Deleting all the leaves shaper does not affect the node one + # when the latter has 'netdev' scope. + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(shapers, [{'ifindex': cfg.ifindex, + 'handle': {'scope': 'netdev'}, + 'metric': 'bps', + 'bw-max': bw_max}]) + + del_node.exec() + def qgroups(cfg, nl_shaper) -> None: _require_queues(cfg, 4) _require_caps(cfg, nl_shaper, 'node', @@ -488,6 +540,7 @@ def main() -> None: set_nshapers, del_nshapers, basic_groups, + basic_groups_with_rate, qgroups, delegation, dup_leaves, From 212410dc81df028a99064b3415b2c1f0af5018de Mon Sep 17 00:00:00 2001 From: Mohsin Bashir Date: Tue, 4 Aug 2026 20:09:27 -0700 Subject: [PATCH 1120/1433] selftests: net: shaper: Add node scope .set rate update test Add set_node_shaper to test updating a NODE scope shaper's rate via the .set callback. Creates a node group with bw_max=10000, updates to 20000 via .set, and verifies the change. Signed-off-by: Mohsin Bashir Link: https://patch.msgid.link/20260805030936.1092907-6-mohsin.bashr@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/shaper.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py index 168a8dd057e5..62ac83b7701c 100755 --- a/tools/testing/selftests/drivers/net/shaper.py +++ b/tools/testing/selftests/drivers/net/shaper.py @@ -343,6 +343,43 @@ def qgroups(cfg, nl_shaper) -> None: shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(len(shapers), 0) +def set_node_shaper(cfg, nl_shaper) -> None: + """ Verify a node-scope shaper rate can be updated via .set. """ + _require_queues(cfg, 2) + _require_caps(cfg, nl_shaper, 'node', ['support-bw-max', 'support-metric-bps'], + "device does not support node scope shapers with bw_max and metric bps") + _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") + + node_handle = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': 10000}) + node_id = node_handle['handle']['id'] + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 1}) + + # Update the node's rate via .set + nl_shaper.set({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node_id}, + 'metric': 'bps', + 'bw-max': 20000}) + + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node_id}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node_id}, + 'parent': {'scope': 'netdev'}, + 'metric': 'bps', + 'bw-max': 20000}) + + # Cleanup + _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': 1}) + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 0) + def delegation(cfg, nl_shaper) -> None: _require_queues(cfg, 4) _require_caps(cfg, nl_shaper, 'node', @@ -542,6 +579,7 @@ def main() -> None: basic_groups, basic_groups_with_rate, qgroups, + set_node_shaper, delegation, dup_leaves, queue_update], From 047735744dedecde78d06a50ed74fb76ad739556 Mon Sep 17 00:00:00 2001 From: Mohsin Bashir Date: Tue, 4 Aug 2026 20:09:28 -0700 Subject: [PATCH 1121/1433] selftests: net: shaper: Add .group rate update test Add group_update_rate to test updating an existing node's rate via the .group callback. Creates a node with bw_max=10000, re-groups with bw_max=50000, and verifies the rate changed while leaves remain under the same node. Signed-off-by: Mohsin Bashir Link: https://patch.msgid.link/20260805030936.1092907-7-mohsin.bashr@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/shaper.py | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py index 62ac83b7701c..5eccbe437ba3 100755 --- a/tools/testing/selftests/drivers/net/shaper.py +++ b/tools/testing/selftests/drivers/net/shaper.py @@ -380,6 +380,72 @@ def set_node_shaper(cfg, nl_shaper) -> None: shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(len(shapers), 0) +def group_update_rate(cfg, nl_shaper) -> None: + """ Verify re-grouping a node updates its rate while leaving the leaves untouched. """ + _require_queues(cfg, 3) + _require_caps(cfg, nl_shaper, 'node', ['support-bw-max', 'support-metric-bps'], + "device does not support node scope shapers with bw_max and metric bps") + _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") + + # Create node with Q1, Q2 at bw_max=10000 + node_handle = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 1}, + {'handle': {'scope': 'queue', 'id': 2}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': 10000}) + node_id = node_handle['handle']['id'] + for i in range(1, 3): + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': i}) + + # Update rate via .group on the same node + nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 1}, + {'handle': {'scope': 'queue', 'id': 2}, + 'weight': 1}], + 'handle': {'scope':'node', 'id': node_id}, + 'metric': 'bps', + 'bw-max': 50000}) + + # Verify rate updated + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node_id}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node_id}, + 'parent': {'scope': 'netdev'}, + 'metric': 'bps', + 'bw-max': 50000}) + + # Verify leaves unchanged + shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 1}}) + ksft_eq(shaper_q1, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': node_id}, + 'handle': {'scope': 'queue', 'id': 1}, + 'weight': 1}) + shaper_q2 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 2}}) + ksft_eq(shaper_q2, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': node_id}, + 'handle': {'scope': 'queue', 'id': 2}, + 'weight': 1}) + + # Make sure we only have 3 shapers including 2 queues and the node + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 3) + + # Cleanup + for i in range(1, 3): + _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i}) + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 0) + def delegation(cfg, nl_shaper) -> None: _require_queues(cfg, 4) _require_caps(cfg, nl_shaper, 'node', @@ -580,6 +646,7 @@ def main() -> None: basic_groups_with_rate, qgroups, set_node_shaper, + group_update_rate, delegation, dup_leaves, queue_update], From 1e89d0d7430c42b2eb819fceaa284b705d5cf783 Mon Sep 17 00:00:00 2001 From: Mohsin Bashir Date: Tue, 4 Aug 2026 20:09:29 -0700 Subject: [PATCH 1122/1433] selftests: net: shaper: Add nested depth limit discovery test Add nested_depth_limit to incrementally create deeper nesting levels until the driver rejects. Reports the maximum supported nesting depth on both pass and fail. A device advertising nesting support must support at least depth 2, otherwise nesting is meaningless. Signed-off-by: Mohsin Bashir Link: https://patch.msgid.link/20260805030936.1092907-8-mohsin.bashr@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/shaper.py | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py index 5eccbe437ba3..3b72661202f9 100755 --- a/tools/testing/selftests/drivers/net/shaper.py +++ b/tools/testing/selftests/drivers/net/shaper.py @@ -532,6 +532,122 @@ def delegation(cfg, nl_shaper) -> None: shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(len(shapers), 0) +def nested_depth_limit(cfg, nl_shaper) -> None: + r"""Nest nodes as deep as the device allows to find the max depth. + + netdev + | + N1 -- Q1 + | + N2 -- Q2 + | + N3 -- Q3 + : (deepen until the driver rejects) + """ + bw_max = 10000 + + _require_caps(cfg, nl_shaper, 'node', + ['support-bw-max', 'support-metric-bps', 'support-nesting'], + "device does not support node scope shapers with bw_max, metric bps and nesting") + _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") + + nq = _require_queues(cfg, 3) + + node_ids = [] + cleanups = [] + queue_id = 1 + max_depth = 0 + limit_err = None + + # Create initial node with a queue leaf + node_id = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves': [{'handle': {'scope': 'queue', 'id': queue_id}, + 'weight': 1}], + 'handle': {'scope': 'node'}, + 'metric': 'bps', + 'bw-max': bw_max})['handle']['id'] + node_ids.append(node_id) + cleanups.append(defer(_delete_shaper, cfg, nl_shaper, + {'scope': 'node', 'id': node_id})) + cleanups.append(defer(_delete_shaper, cfg, nl_shaper, + {'scope': 'queue', 'id': queue_id})) + max_depth = 1 + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node_id}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node_id}, + 'parent': {'scope': 'netdev'}, + 'metric': 'bps', + 'bw-max': bw_max}) + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': queue_id}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': node_id}, + 'handle': {'scope': 'queue', 'id': queue_id}, + 'weight': 1}) + queue_id += 1 + + # Keep nesting deeper until the driver rejects or queues run out. + while queue_id < nq: + parent_id = node_ids[-1] + try: + node_id = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves': [{'handle': {'scope': 'queue', + 'id': queue_id}, + 'weight': 1}], + 'handle': {'scope': 'node'}, + 'parent': {'scope': 'node', + 'id': parent_id}, + 'metric': 'bps', + 'bw-max': bw_max})['handle']['id'] + except NlError as e: + # Only treat "cannot nest deeper" errors as the depth limit; + # drivers report it differently (EOPNOTSUPP/ENOSPC/E2BIG/EINVAL). + # Anything else (ENOMEM, EIO, EPERM, driver bug) is a real failure. + if e.error not in (errno.EOPNOTSUPP, errno.ENOSPC, + errno.E2BIG, errno.EINVAL): + raise + limit_err = e + break + + node_ids.append(node_id) + cleanups.append(defer(_delete_shaper, cfg, nl_shaper, + {'scope': 'node', 'id': node_id})) + cleanups.append(defer(_delete_shaper, cfg, nl_shaper, + {'scope': 'queue', 'id': queue_id})) + max_depth += 1 + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node_id}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node_id}, + 'parent': {'scope': 'node', 'id': parent_id}, + 'metric': 'bps', + 'bw-max': bw_max}) + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', + 'id': queue_id}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': node_id}, + 'handle': {'scope': 'queue', 'id': queue_id}, + 'weight': 1}) + queue_id += 1 + + if limit_err: + print(f"# max nesting depth supported: {max_depth} (errno {limit_err.error})") + else: + print(f"# max nesting depth tested: {max_depth}") + ksft_true(max_depth >= 2, + f"max nesting depth: {max_depth}") + + # Cleanup: exec the deferred deletes in reverse creation order, so each + # queue leaf and deeper node is removed before its parent node. + for cleanup in reversed(cleanups): + cleanup.exec() + ksft_eq(len(nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)), 0) + def queue_update(cfg, nl_shaper) -> None: nq = _require_queues(cfg, 4) if not cfg.queues: @@ -648,6 +764,7 @@ def main() -> None: set_node_shaper, group_update_rate, delegation, + nested_depth_limit, dup_leaves, queue_update], args=(cfg, NetshaperFamily())) From 5c84926ef73c855ebcf8d4cee26dcf5844012ca1 Mon Sep 17 00:00:00 2001 From: Mohsin Bashir Date: Tue, 4 Aug 2026 20:09:30 -0700 Subject: [PATCH 1123/1433] selftests: net: shaper: Add child node deletion reparent test Add delete_child_reparent to verify that deleting a child node reparents its queue leaves to the parent node. Creates a two-level hierarchy (N1 with Q1,Q2 and child N2 with Q3), deletes N2, and verifies Q3's parent becomes N1. Signed-off-by: Mohsin Bashir Link: https://patch.msgid.link/20260805030936.1092907-9-mohsin.bashr@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/shaper.py | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py index 3b72661202f9..1b88e183f1b9 100755 --- a/tools/testing/selftests/drivers/net/shaper.py +++ b/tools/testing/selftests/drivers/net/shaper.py @@ -648,6 +648,82 @@ def nested_depth_limit(cfg, nl_shaper) -> None: cleanup.exec() ksft_eq(len(nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)), 0) +def delete_child_reparent(cfg, nl_shaper) -> None: + r"""Deleting a child node reparents its queue leaf to the parent. + + netdev netdev + | | + N1 del N2 N1 + / | \ -----> / | \ + Q1 Q2 N2 Q1 Q2 Q3 + | + Q3 + """ + n1_bw_max = 10000 + n2_bw_max = 5000 + + _require_caps(cfg, nl_shaper, 'node', + ['support-bw-max', 'support-metric-bps', 'support-nesting'], + "device does not support node scope shapers with bw_max, metric bps and nesting") + _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") + + _require_queues(cfg, 4) + + # Create parent node N1 with Q1, Q2 + n1_handle = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 1}, + {'handle': {'scope': 'queue', 'id': 2}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': n1_bw_max}) + n1_id = n1_handle['handle']['id'] + for i in range(1, 3): + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': i}) + + # Create child node N2 under N1 with Q3 + n2_handle = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 3}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'parent': {'scope': 'node', 'id': n1_id}, + 'metric': 'bps', + 'bw-max': n2_bw_max}) + n2_id = n2_handle['handle']['id'] + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 3}) + + # Delete child N2 - Q3 should reparent to N1 + nl_shaper.delete({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': n2_id}}) + + with ksft_raises(NlError): + nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': n2_id}}) + + shaper_n1 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': n1_id}}) + ksft_eq(shaper_n1, {'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': n1_id}, + 'parent': {'scope': 'netdev'}, + 'metric': 'bps', + 'bw-max': n1_bw_max}) + shaper_q3 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 3}}) + ksft_eq(shaper_q3, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': n1_id}, + 'handle': {'scope': 'queue', 'id': 3}, + 'weight': 1}) + + # Cleanup + for i in range(1, 4): + _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i}) + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 0) + def queue_update(cfg, nl_shaper) -> None: nq = _require_queues(cfg, 4) if not cfg.queues: @@ -765,6 +841,7 @@ def main() -> None: group_update_rate, delegation, nested_depth_limit, + delete_child_reparent, dup_leaves, queue_update], args=(cfg, NetshaperFamily())) From aa05d8096a9bc45dc0fbeca29dd41d25c3b1dbc0 Mon Sep 17 00:00:00 2001 From: Mohsin Bashir Date: Tue, 4 Aug 2026 20:09:31 -0700 Subject: [PATCH 1124/1433] selftests: net: shaper: Add queue migration between nodes test Add move_queue_between_nodes to verify that a queue can be moved from one node to another via re-grouping. Creates N1 with Q1,Q2 and N2 with Q3, then re-groups N2 with Q1,Q3 to steal Q1 from N1. Verifies Q1 moved to N2 and Q2 remains under N1. Signed-off-by: Mohsin Bashir Link: https://patch.msgid.link/20260805030936.1092907-10-mohsin.bashr@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/shaper.py | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py index 1b88e183f1b9..62c74a0c0563 100755 --- a/tools/testing/selftests/drivers/net/shaper.py +++ b/tools/testing/selftests/drivers/net/shaper.py @@ -724,6 +724,107 @@ def delete_child_reparent(cfg, nl_shaper) -> None: shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(len(shapers), 0) +def move_queue_between_nodes(cfg, nl_shaper) -> None: + r"""Move a queue between nodes by re-grouping the destination node. + + netdev netdev + / \ .group N2 / \ + N1 N2 {Q1,Q3} N1 N2 + / \ | -------> | / \ + Q1 Q2 Q3 Q2 Q1 Q3 + """ + n1_bw_max = 10000 + n2_bw_max = 20000 + + _require_caps(cfg, nl_shaper, 'node', + ['support-bw-max', 'support-metric-bps', 'support-nesting'], + "device does not support node scope shapers with bw_max, metric bps and nesting") + _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") + + _require_queues(cfg, 4) + + # Create N1 with Q1, Q2 + n1_handle = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 1}, + {'handle': {'scope': 'queue', 'id': 2}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': n1_bw_max}) + n1_id = n1_handle['handle']['id'] + for i in range(1, 3): + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': i}) + + # Create N2 with Q3 + n2_handle = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 3}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': n2_bw_max}) + n2_id = n2_handle['handle']['id'] + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 3}) + + # Move Q1 from N1 to N2 by re-grouping N2 with Q1, Q3 + nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 2}, + {'handle': {'scope': 'queue', 'id': 3}, + 'weight': 1}], + 'handle': {'scope':'node', 'id': n2_id}, + 'metric': 'bps', + 'bw-max': n2_bw_max}) + + shaper_n1 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': n1_id}}) + ksft_eq(shaper_n1, {'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': n1_id}, + 'parent': {'scope': 'netdev'}, + 'metric': 'bps', + 'bw-max': n1_bw_max}) + shaper_n2 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': n2_id}}) + ksft_eq(shaper_n2, {'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': n2_id}, + 'parent': {'scope': 'netdev'}, + 'metric': 'bps', + 'bw-max': n2_bw_max}) + + # Verify Q1 moved to N2 + shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 1}}) + ksft_eq(shaper_q1, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': n2_id}, + 'handle': {'scope': 'queue', 'id': 1}, + 'weight': 2}) + + # Verify Q2 still under N1 + shaper_q2 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 2}}) + ksft_eq(shaper_q2, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': n1_id}, + 'handle': {'scope': 'queue', 'id': 2}, + 'weight': 1}) + + # Verify Q3 remained under N2 + shaper_q3 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 3}}) + ksft_eq(shaper_q3, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': n2_id}, + 'handle': {'scope': 'queue', 'id': 3}, + 'weight': 1}) + + # Cleanup + for i in range(1, 4): + _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i}) + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 0) + def queue_update(cfg, nl_shaper) -> None: nq = _require_queues(cfg, 4) if not cfg.queues: @@ -842,6 +943,7 @@ def main() -> None: delegation, nested_depth_limit, delete_child_reparent, + move_queue_between_nodes, dup_leaves, queue_update], args=(cfg, NetshaperFamily())) From 4f197c44981f829243236c99dc2c7b94874daba1 Mon Sep 17 00:00:00 2001 From: Mohsin Bashir Date: Tue, 4 Aug 2026 20:09:32 -0700 Subject: [PATCH 1125/1433] selftests: net: shaper: Add reparenting rejection test Add reject_reparenting to verify that the group operation rejects attempts to change an existing node's parent. The test creates two node shapers under netdev and verifies that re-grouping the first node under the second fails with EOPNOTSUPP. It also verifies that updating the node with the same parent succeeds, and that updating the node without specifying a parent keeps the queue leaves under the original node while updating their weights. Signed-off-by: Mohsin Bashir Link: https://patch.msgid.link/20260805030936.1092907-11-mohsin.bashr@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/shaper.py | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py index 62c74a0c0563..e7af94264409 100755 --- a/tools/testing/selftests/drivers/net/shaper.py +++ b/tools/testing/selftests/drivers/net/shaper.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0 +# pylint: disable=too-many-lines import errno import glob @@ -825,6 +826,154 @@ def move_queue_between_nodes(cfg, nl_shaper) -> None: shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(len(shapers), 0) +def reject_reparenting(cfg, nl_shaper) -> None: + r"""Reject reparenting an existing node; the hierarchy stays intact. + + netdev + / \ rejected: N3 -> netdev + N1 N2 rejected: N1 -> N2 + / \ | (both EOPNOTSUPP) + Q1 N3 Q2 + | + Q3 + """ + node1_bw_max = 10000 + node2_bw_max = 5000 + node3_bw_max = 20000 + + _require_caps(cfg, nl_shaper, 'node', + ['support-bw-max', 'support-metric-bps', 'support-nesting'], + "device does not support node scope shapers with bw_max, metric bps and nesting") + _require_caps(cfg, nl_shaper, 'queue', ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") + + _require_queues(cfg, 4) + + # Create Node1 under netdev with Q1. + node1_id = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': node1_bw_max})['handle']['id'] + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 1}) + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'node', 'id': node1_id}) + + # Create Node2 under netdev with Q2. + node2_id = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 2}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': node2_bw_max})['handle']['id'] + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 2}) + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'node', 'id': node2_id}) + + # Create Node3 nested under Node1 with Q3. + node3_id = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 3}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': node3_bw_max, + 'parent': {'scope': 'node', 'id': node1_id}})['handle']['id'] + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 3}) + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'node', 'id': node3_id}) + + # Reparenting a nested node up to netdev must fail. + with ksft_raises(NlError) as cm: + nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 3}, + 'weight': 1}], + 'handle': {'scope':'node', 'id': node3_id}, + 'parent': {'scope': 'netdev'}}) + if cm.exception: + ksft_eq(cm.exception.error, errno.EOPNOTSUPP) + + # Reparenting a node under another node must fail as well. + with ksft_raises(NlError) as cm: + nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 1}], + 'handle': {'scope':'node', 'id': node1_id}, + 'parent': {'scope': 'node', 'id': node2_id}}) + if cm.exception: + ksft_eq(cm.exception.error, errno.EOPNOTSUPP) + + # Updating a node with the same parent must succeed. + nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 5}], + 'handle': {'scope':'node', 'id': node1_id}, + 'parent': {'scope': 'netdev'}}) + + # Updating a node without specifying the parent must succeed. + nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 2}, + 'weight': 7}], + 'handle': {'scope':'node', 'id': node2_id}}) + + # The rejected reparents must have left the hierarchy intact. + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node1_id}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node1_id}, + 'parent': {'scope': 'netdev'}, + 'metric': 'bps', + 'bw-max': node1_bw_max}) + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node2_id}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node2_id}, + 'parent': {'scope': 'netdev'}, + 'metric': 'bps', + 'bw-max': node2_bw_max}) + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node3_id}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': node3_id}, + 'parent': {'scope': 'node', 'id': node1_id}, + 'metric': 'bps', + 'bw-max': node3_bw_max}) + + # Verify the leaf weights were updated and parents unchanged. + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 1}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': node1_id}, + 'handle': {'scope': 'queue', 'id': 1}, + 'weight': 5}) + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 2}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': node2_id}, + 'handle': {'scope': 'queue', 'id': 2}, + 'weight': 7}) + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 3}}) + ksft_eq(shaper, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': node3_id}, + 'handle': {'scope': 'queue', 'id': 3}, + 'weight': 1}) + + # Cleanup. Delete the nodes explicitly instead of relying on the + # empty-node auto-delete: a kernel that wrongly accepts a reparent may + # mishandle the leaf accounting and leave a node behind. Removing them + # by handle keeps a failing run from leaking state into later tests. + for i in range(1, 4): + _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i}) + for nid in (node1_id, node2_id, node3_id): + _delete_shaper(cfg, nl_shaper, {'scope': 'node', 'id': nid}) + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 0) + def queue_update(cfg, nl_shaper) -> None: nq = _require_queues(cfg, 4) if not cfg.queues: @@ -944,6 +1093,7 @@ def main() -> None: nested_depth_limit, delete_child_reparent, move_queue_between_nodes, + reject_reparenting, dup_leaves, queue_update], args=(cfg, NetshaperFamily())) From 83731be09057349559f588f11dcf4619198c018b Mon Sep 17 00:00:00 2001 From: Mohsin Bashir Date: Tue, 4 Aug 2026 20:09:33 -0700 Subject: [PATCH 1126/1433] selftests: net: shaper: Cover scalar attributes Exercise queue-scope scalar shaper attributes reported by the device, including rate limits, burst, priority and weight. Build the set request from advertised capabilities so devices are tested for the attributes they claim rather than skipped for missing unrelated fields. Signed-off-by: Mohsin Bashir Link: https://patch.msgid.link/20260805030936.1092907-12-mohsin.bashr@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/shaper.py | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py index e7af94264409..8dd4897e999e 100755 --- a/tools/testing/selftests/drivers/net/shaper.py +++ b/tools/testing/selftests/drivers/net/shaper.py @@ -168,6 +168,73 @@ def del_nshapers(cfg, nl_shaper) -> None: shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(len(shapers), 0) +def set_all_supported_attrs(cfg, nl_shaper) -> None: + """ Set every queue-scope attribute the device advertises and verify the read-back. """ + _require_queues(cfg, 1) + + _require_caps(cfg, nl_shaper, 'queue', [], + "queue scope shapers not supported by the device") + caps = _cap_get(cfg, nl_shaper, 'queue') + + attrs = {'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 0}} + expected = {'ifindex': cfg.ifindex, + 'parent': {'scope': 'netdev'}, + 'handle': {'scope': 'queue', 'id': 0}} + + rate_attrs = {'support-bw-min': ('bw-min', 10000, 100), + 'support-bw-max': ('bw-max', 20000, 200), + 'support-burst': ('burst', 3000, 30)} + rate_attr_supported = any(cap in caps for cap in rate_attrs) + bps_supported = 'support-metric-bps' in caps + pps_supported = 'support-metric-pps' in caps + + def add_rate_attrs(metric, value_idx) -> None: + attrs['metric'] = metric + expected['metric'] = metric + for cap, (attr, bps_value, pps_value) in rate_attrs.items(): + if cap not in caps: + continue + + value = bps_value if value_idx == 0 else pps_value + attrs[attr] = value + expected[attr] = value + + if rate_attr_supported: + if bps_supported: + add_rate_attrs('bps', 0) + elif pps_supported: + add_rate_attrs('pps', 1) + + if 'support-priority' in caps: + attrs['priority'] = 1 + expected['priority'] = 1 + if 'support-weight' in caps: + attrs['weight'] = 2 + expected['weight'] = 2 + + if len(attrs) == 2: + raise KsftSkipEx("device does not advertise any supported queue shaper attributes") + + nl_shaper.set(attrs) + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 0}) + + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 0}}) + ksft_eq(shaper, expected) + + if rate_attr_supported and bps_supported and pps_supported: + add_rate_attrs('pps', 1) + nl_shaper.set(attrs) + + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 0}}) + ksft_eq(shaper, expected) + + _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': 0}) + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 0) + def _group_under_netdev(cfg, nl_shaper, bw_max=None): r"""Group queues under a netdev-scope node; caller owns node teardown. @@ -1084,6 +1151,7 @@ def main() -> None: del_qshapers, set_nshapers, del_nshapers, + set_all_supported_attrs, basic_groups, basic_groups_with_rate, qgroups, From 3651c7e18c04d65cd467202b80628cdb445b58a6 Mon Sep 17 00:00:00 2001 From: Mohsin Bashir Date: Tue, 4 Aug 2026 20:09:34 -0700 Subject: [PATCH 1127/1433] selftests: net: shaper: Reject invalid set requests Verify that invalid set requests fail without corrupting existing queue shaper state. The test covers invalid node creation through set and invalid queue identifiers, then confirms the original queue configuration remains unchanged. Signed-off-by: Mohsin Bashir Link: https://patch.msgid.link/20260805030936.1092907-13-mohsin.bashr@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/shaper.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py index 8dd4897e999e..02a11e6b9a05 100755 --- a/tools/testing/selftests/drivers/net/shaper.py +++ b/tools/testing/selftests/drivers/net/shaper.py @@ -235,6 +235,46 @@ def set_all_supported_attrs(cfg, nl_shaper) -> None: shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(len(shapers), 0) +def invalid_set_preserves_state(cfg, nl_shaper) -> None: + """ Verify a rejected .set leaves the existing shaper configuration unchanged. """ + nq = _require_queues(cfg, 1) + _require_caps(cfg, nl_shaper, 'queue', + ['support-bw-max', 'support-metric-bps'], + "device does not support queue scope bw_max with bps metric") + + initial = {'ifindex': cfg.ifindex, + 'parent': {'scope': 'netdev'}, + 'handle': {'scope': 'queue', 'id': 0}, + 'metric': 'bps', + 'bw-max': 10000} + nl_shaper.set({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 0}, + 'metric': 'bps', + 'bw-max': 10000}) + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 0}) + + with ksft_raises(NlError): + nl_shaper.set({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': 0}, + 'metric': 'bps', + 'bw-max': 20000}) + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 0}}) + ksft_eq(shaper, initial) + + with ksft_raises(NlError): + nl_shaper.set({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': nq}, + 'metric': 'bps', + 'bw-max': 20000}) + shaper = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 0}}) + ksft_eq(shaper, initial) + + _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': 0}) + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 0) + def _group_under_netdev(cfg, nl_shaper, bw_max=None): r"""Group queues under a netdev-scope node; caller owns node teardown. @@ -1152,6 +1192,7 @@ def main() -> None: set_nshapers, del_nshapers, set_all_supported_attrs, + invalid_set_preserves_state, basic_groups, basic_groups_with_rate, qgroups, From ca157503b5b82e9aceef148c22f6ad7c24aff466 Mon Sep 17 00:00:00 2001 From: Mohsin Bashir Date: Tue, 4 Aug 2026 20:09:35 -0700 Subject: [PATCH 1128/1433] selftests: net: shaper: Cover mixed-parent grouping Add coverage for grouping leaves that currently belong to different parent nodes. The test verifies that an implicit parent is rejected, an explicit parent succeeds, and the old empty parent nodes are cleaned up. Signed-off-by: Mohsin Bashir Link: https://patch.msgid.link/20260805030936.1092907-14-mohsin.bashr@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/shaper.py | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py index 02a11e6b9a05..9264aeb74a7a 100755 --- a/tools/testing/selftests/drivers/net/shaper.py +++ b/tools/testing/selftests/drivers/net/shaper.py @@ -275,6 +275,105 @@ def invalid_set_preserves_state(cfg, nl_shaper) -> None: shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(len(shapers), 0) +def mixed_parent_group_requires_parent(cfg, nl_shaper) -> None: + r"""Grouping leaves from different nodes requires an explicit parent. + + netdev netdev + / \ parent=netdev + N1 N2 group N + | | {Q0,Q1} / \ + Q0 Q1 -------> Q0 Q1 + + Without an explicit parent the group is rejected; parent=netdev + collapses the leaves into one new node. + """ + _require_queues(cfg, 2) + _require_caps(cfg, nl_shaper, 'node', + ['support-bw-max', 'support-metric-bps'], + "device does not support node scope shapers with bw_max and metric bps") + _require_caps(cfg, nl_shaper, 'queue', + ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") + + n1_handle = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': 10000}) + n1_id = n1_handle['handle']['id'] + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 0}) + + n2_handle = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 1}, + 'weight': 2}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': 20000}) + n2_id = n2_handle['handle']['id'] + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 1}) + + with ksft_raises(NlError): + nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, + 'weight': 3}, + {'handle': {'scope': 'queue', 'id': 1}, + 'weight': 4}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': 30000}) + + shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 0}}) + ksft_eq(shaper_q0, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': n1_id}, + 'handle': {'scope': 'queue', 'id': 0}, + 'weight': 1}) + shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 1}}) + ksft_eq(shaper_q1, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': n2_id}, + 'handle': {'scope': 'queue', 'id': 1}, + 'weight': 2}) + + node_handle = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, + 'weight': 3}, + {'handle': {'scope': 'queue', 'id': 1}, + 'weight': 4}], + 'handle': {'scope':'node'}, + 'parent': {'scope': 'netdev'}, + 'metric': 'bps', + 'bw-max': 30000}) + node_id = node_handle['handle']['id'] + + for old_id in (n1_id, n2_id): + with ksft_raises(NlError): + nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'node', 'id': old_id}}) + + shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 0}}) + ksft_eq(shaper_q0, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': node_id}, + 'handle': {'scope': 'queue', 'id': 0}, + 'weight': 3}) + shaper_q1 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 1}}) + ksft_eq(shaper_q1, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': node_id}, + 'handle': {'scope': 'queue', 'id': 1}, + 'weight': 4}) + + for i in range(2): + _delete_shaper(cfg, nl_shaper, {'scope': 'queue', 'id': i}) + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 0) + def _group_under_netdev(cfg, nl_shaper, bw_max=None): r"""Group queues under a netdev-scope node; caller owns node teardown. @@ -1193,6 +1292,7 @@ def main() -> None: del_nshapers, set_all_supported_attrs, invalid_set_preserves_state, + mixed_parent_group_requires_parent, basic_groups, basic_groups_with_rate, qgroups, From e09d72c1c80d43f0785b4158864e90963915bb20 Mon Sep 17 00:00:00 2001 From: Mohsin Bashir Date: Tue, 4 Aug 2026 20:09:36 -0700 Subject: [PATCH 1129/1433] selftests: net: shaper: Cover recursive node cleanup Exercise cleanup of nested nodes after deleting their last queue leaf. The test builds a two-level node hierarchy and checks that removing the queue also removes both now-empty node shapers. Signed-off-by: Mohsin Bashir Link: https://patch.msgid.link/20260805030936.1092907-15-mohsin.bashr@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/shaper.py | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py index 9264aeb74a7a..a53316726f69 100755 --- a/tools/testing/selftests/drivers/net/shaper.py +++ b/tools/testing/selftests/drivers/net/shaper.py @@ -374,6 +374,64 @@ def mixed_parent_group_requires_parent(cfg, nl_shaper) -> None: shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) ksft_eq(len(shapers), 0) +def recursive_empty_node_cleanup(cfg, nl_shaper) -> None: + r"""Deleting the last leaf recursively removes the emptied ancestors. + + netdev netdev + | del Q0 + N1 ------> (N1 and N2 removed too) + | + N2 + | + Q0 + """ + _require_queues(cfg, 1) + _require_caps(cfg, nl_shaper, 'node', + ['support-bw-max', 'support-metric-bps', 'support-nesting'], + "device does not support nested node scope shapers") + _require_caps(cfg, nl_shaper, 'queue', + ['support-nesting', 'support-weight'], + "device does not support nested queue scope shapers with weight") + + n1_handle = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'metric': 'bps', + 'bw-max': 10000}) + n1_id = n1_handle['handle']['id'] + defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 0}) + + n2_handle = nl_shaper.group({ + 'ifindex': cfg.ifindex, + 'leaves':[{'handle': {'scope': 'queue', 'id': 0}, + 'weight': 1}], + 'handle': {'scope':'node'}, + 'parent': {'scope': 'node', 'id': n1_id}, + 'metric': 'bps', + 'bw-max': 5000}) + n2_id = n2_handle['handle']['id'] + + shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 0}}) + ksft_eq(shaper_q0, {'ifindex': cfg.ifindex, + 'parent': {'scope': 'node', 'id': n2_id}, + 'handle': {'scope': 'queue', 'id': 0}, + 'weight': 1}) + + nl_shaper.delete({'ifindex': cfg.ifindex, + 'handle': {'scope': 'queue', 'id': 0}}) + + for handle in ({'scope': 'queue', 'id': 0}, + {'scope': 'node', 'id': n2_id}, + {'scope': 'node', 'id': n1_id}): + with ksft_raises(NlError): + nl_shaper.get({'ifindex': cfg.ifindex, 'handle': handle}) + + shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True) + ksft_eq(len(shapers), 0) + def _group_under_netdev(cfg, nl_shaper, bw_max=None): r"""Group queues under a netdev-scope node; caller owns node teardown. @@ -1293,6 +1351,7 @@ def main() -> None: set_all_supported_attrs, invalid_set_preserves_state, mixed_parent_group_requires_parent, + recursive_empty_node_cleanup, basic_groups, basic_groups_with_rate, qgroups, From d4e359b3608a0e184bbe8d61a5c3b50d0831c44a Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Wed, 5 Aug 2026 10:40:49 -0300 Subject: [PATCH 1130/1433] net/sched: cls_api: fix teardown of an adopted proto on insert-race loss In tc_new_tfilter() the create branch sets tp_created = 1 before calling tcf_chain_tp_insert_unique(). When the caller loses the race (another request inserted a proto at the same chain/prio first), insert_unique() destroys the caller's own tp_new and returns the winner's proto with an extra reference. tp_created was never cleared, so the loser's errout path treated the winner's live proto as its own and called tcf_chain_tp_delete_empty() on it, silently unlinking an active classifier that the winning request already advertised via RTM_NEWTFILTER. Track the outcome of the insert step in a single tri-state variable so each errout path reacts correctly: - TP_NOT_CREATED: no proto created; pursue the old path. - TP_CREATED: proto inserted successfully; same code path as before. - TP_NOT_OWNED: New - lost the insert race; tp is another request's proto (chain ref already released by tp_new's destroy) Both errout reactions are single expressions derived from the state. This fix is motivated by the Sashiko's automated review of Patch (net/sched: cls_api: Always acquire rtnl_lock when destroying locked classifiers) [1][2]. The review identified the silent-unlink behaviour of an adopted proto's teardown when a request loses the tcf_chain_tp_insert_unique() race. [1] https://sashiko.dev/#/patchset/20260801125632.360365-1-jhs%40mojatatu.com [2] https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260801125632.360365-1-jhs%40mojatatu.com Fixes: 8b64678e0af8 ("net: sched: refactor tp insert/delete for concurrent execution") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260801125632.360365-1-jhs%40mojatatu.com Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260801125632.360365-1-jhs%40mojatatu.com Acked-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Reported-by: TencentOS Corvus AI Tested-by: Aohan Mei Link: https://patch.msgid.link/20260805134049.927864-1-victor@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/cls_api.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c index 4e6a2812a4f3..3271963c945d 100644 --- a/net/sched/cls_api.c +++ b/net/sched/cls_api.c @@ -2248,6 +2248,12 @@ static bool is_ingress_or_clsact(struct tcf_block *block, struct Qdisc *q) return tcf_block_shared(block) || (q && !!(q->flags & TCQ_F_INGRESS)); } +enum tcf_tp_insert_state { + TP_NOT_CREATED = 0, /* did not create and insert a new tp */ + TP_CREATED, /* created and inserted a new tp */ + TP_NOT_OWNED, /* created a proto but failed to insert */ +}; + static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n, struct netlink_ext_ack *extack) { @@ -2268,12 +2274,12 @@ static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n, unsigned long cl; void *fh; int err; - int tp_created; + enum tcf_tp_insert_state tp_state; bool rtnl_held = false; u32 flags; replay: - tp_created = 0; + tp_state = TP_NOT_CREATED; err = nlmsg_parse_deprecated(n, sizeof(*t), tca, TCA_MAX, rtm_tca_policy, extack); @@ -2395,13 +2401,15 @@ static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n, goto errout_tp; } - tp_created = 1; + tp_state = TP_CREATED; tp = tcf_chain_tp_insert_unique(chain, tp_new, protocol, prio, rtnl_held); if (IS_ERR(tp)) { err = PTR_ERR(tp); goto errout_tp; } + if (tp != tp_new) + tp_state = TP_NOT_OWNED; } else { mutex_unlock(&chain->filter_chain_lock); } @@ -2455,13 +2463,13 @@ static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n, } errout: - if (err && tp_created) + if (err && tp_state == TP_CREATED) tcf_chain_tp_delete_empty(chain, tp, rtnl_held, NULL); errout_tp: if (chain) { if (tp && !IS_ERR(tp)) tcf_proto_put(tp, rtnl_held, NULL); - if (!tp_created) + if (tp_state == TP_NOT_CREATED) tcf_chain_put(chain); } tcf_block_release(q, block, rtnl_held); From 485e38995cc3b39f1aa91c1d340cc66affcea39a Mon Sep 17 00:00:00 2001 From: Simon Schippers Date: Mon, 3 Aug 2026 20:36:37 +0200 Subject: [PATCH 1131/1433] tun/tap: add IFF_BACKPRESSURE flag Add the IFF_BACKPRESSURE flag to the UAPI header and to its tools/ copy. The flag has no effect yet, it is the opt-in switch for the qdisc backpressure logic added by the following patches. It is added to TUN_FEATURES only in the last patch of the series, once the implementation is complete. Until then TUNSETIFF silently masks it off, as it does for any flag outside TUN_FEATURES. Keeping the flag and its users in separate patches would either leave a window where backpressure is unconditional, or make the opt-in a later add-on. Adding the flag first lets every following patch be a no-op unless it is set. Signed-off-by: Simon Schippers Link: https://patch.msgid.link/20260803183641.96882-2-simon.schippers@tu-dortmund.de Signed-off-by: Jakub Kicinski --- include/uapi/linux/if_tun.h | 4 ++++ tools/include/uapi/linux/if_tun.h | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/include/uapi/linux/if_tun.h b/include/uapi/linux/if_tun.h index 79d53c7a1ebd..a0ddc50a7534 100644 --- a/include/uapi/linux/if_tun.h +++ b/include/uapi/linux/if_tun.h @@ -69,6 +69,10 @@ #define IFF_NAPI_FRAGS 0x0020 /* Used in TUNSETIFF to bring up tun/tap without carrier */ #define IFF_NO_CARRIER 0x0040 +/* Stop the queue instead of dropping when the internal ring is full, so an + * attached qdisc applies backpressure instead of being bypassed. + */ +#define IFF_BACKPRESSURE 0x0080 #define IFF_NO_PI 0x1000 /* This flag has no real effect */ #define IFF_ONE_QUEUE 0x2000 diff --git a/tools/include/uapi/linux/if_tun.h b/tools/include/uapi/linux/if_tun.h index 2ec07de1d73b..2c85525704c1 100644 --- a/tools/include/uapi/linux/if_tun.h +++ b/tools/include/uapi/linux/if_tun.h @@ -67,6 +67,10 @@ #define IFF_TAP 0x0002 #define IFF_NAPI 0x0010 #define IFF_NAPI_FRAGS 0x0020 +/* Stop the queue instead of dropping when the internal ring is full, so an + * attached qdisc applies backpressure instead of being bypassed. + */ +#define IFF_BACKPRESSURE 0x0080 #define IFF_NO_PI 0x1000 /* This flag has no real effect */ #define IFF_ONE_QUEUE 0x2000 From 9b990ae358b38e52f61336f6c42191be567606b3 Mon Sep 17 00:00:00 2001 From: Simon Schippers Date: Mon, 3 Aug 2026 20:36:38 +0200 Subject: [PATCH 1132/1433] tun/tap: add ptr_ring consume helper with netdev queue wakeup Introduce tun_ring_consume() that wraps ptr_ring_consume() and calls __tun_wake_queue(). The latter wakes the stopped netdev subqueue once half of the ring capacity has been consumed, tracked via the new cons_cnt field in tun_file. As a safety net, the queue is also woken on the last consumed entry if it leaves the ring empty. The point is to allow the queue to be stopped when it gets full, which is required for traffic shaping, implemented by the following "stop tail-drop when IFF_BACKPRESSURE is set". __tun_wake_queue() returns early unless IFF_BACKPRESSURE is set, so for a tun/tap device that does not opt in only the added check on the consume path remains. Every site that clears __QUEUE_STATE_DRV_XOFF now checks netif_running() under a ring lock that tun_net_close() takes, so that none of them undoes its stop. The core sets it before it calls ndo_open() and clears it before it calls ndo_stop(), so it is false for exactly as long as the device is down. IFF_UP would not do, it is only cleared after ndo_stop() returns. Some implementation details: - tun_ring_recv() replaces ptr_ring_consume() with tun_ring_consume() to properly wake the queue. - __tun_wake_queue() returns early for a device that is not running, so a stop from tun_net_close() is not mistaken for backpressure, and it only wakes if the tfile still owns its slot in tun->tfiles[]. A detached tfile keeps its queue_index, which __tun_detach() may already have handed to the tfile that took over the slot. - lockdep_assert_held() enforces the documented consumer_lock precondition of __tun_wake_queue(). - __tun_detach() locks the tx_ring.consumer_lock to avoid races with the consumer on the queue_index, and that of tfile across the hand-over of the slot, which makes the ownership check above exact. - The ptr_ring_consume() call in tun_queue_purge() is not replaced with tun_ring_consume(). Instead __tun_detach() wakes the netdev queue for the ntfile taking it over, to avoid a possible stall. The queue is only woken if the ring of the ntfile is empty, as otherwise the consumer wakes it after consuming the remaining entries. This does not matter for tun_detach_all(), as it is called during device teardown and no tfile takes over any queue. - That wake sits after synchronize_net() and tun_queue_purge(), so it can not be undone by a concurrent tun_net_xmit() or __tun_wake_queue(). - Ensure detached queues are woken on re-attach by calling the new tun_force_wake_queue() helper from tun_attach(), and reuse it across the existing wake paths. Unlike __tun_wake_queue() it ignores IFF_BACKPRESSURE, so a queue can not stay stopped after the flag is cleared. It does honour netif_running(), but it always clears cons_cnt, so no old count is left over when the queue is stopped again. - tun_net_close() takes and releases both ring locks of every tfile before netif_tx_stop_all_queues(), so that its stop is the last write to __QUEUE_STATE_DRV_XOFF. - The aforementioned upcoming patch explains the pairing of the smp_mb() of __tun_wake_queue(). Co-developed-by: Tim Gebauer Signed-off-by: Tim Gebauer Signed-off-by: Simon Schippers Link: https://patch.msgid.link/20260803183641.96882-3-simon.schippers@tu-dortmund.de Signed-off-by: Jakub Kicinski --- drivers/net/tun.c | 118 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 114 insertions(+), 4 deletions(-) diff --git a/drivers/net/tun.c b/drivers/net/tun.c index 51e80000bd0e..d49b6bfd104d 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -145,6 +145,8 @@ struct tun_file { struct list_head next; struct tun_struct *detached; struct ptr_ring tx_ring; + /* Protected by tx_ring.consumer_lock */ + int cons_cnt; struct xdp_rxq_info xdp_rxq; }; @@ -585,11 +587,16 @@ static void __tun_detach(struct tun_file *tfile, bool clean) u16 index = tfile->queue_index; BUG_ON(index >= tun->numqueues); + spin_lock(&tfile->tx_ring.consumer_lock); rcu_assign_pointer(tun->tfiles[index], tun->tfiles[tun->numqueues - 1]); + spin_unlock(&tfile->tx_ring.consumer_lock); ntfile = rtnl_dereference(tun->tfiles[index]); + spin_lock(&ntfile->tx_ring.consumer_lock); ntfile->queue_index = index; ntfile->xdp_rxq.queue_index = index; + ntfile->cons_cnt = 0; + spin_unlock(&ntfile->tx_ring.consumer_lock); rcu_assign_pointer(tun->tfiles[tun->numqueues - 1], NULL); @@ -606,6 +613,14 @@ static void __tun_detach(struct tun_file *tfile, bool clean) tun_flow_delete_by_queue(tun, tun->numqueues + 1); /* Drop read queue */ tun_queue_purge(tfile); + spin_lock_bh(&ntfile->tx_ring.consumer_lock); + spin_lock(&ntfile->tx_ring.producer_lock); + ntfile->cons_cnt = 0; + if (netif_running(tun->dev) && + __ptr_ring_empty(&ntfile->tx_ring)) + netif_wake_subqueue(tun->dev, index); + spin_unlock(&ntfile->tx_ring.producer_lock); + spin_unlock_bh(&ntfile->tx_ring.consumer_lock); tun_set_real_num_queues(tun); } else if (tfile->detached && clean) { tun = tun_enable_queue(tfile); @@ -687,6 +702,25 @@ static void tun_detach_all(struct net_device *dev) module_put(THIS_MODULE); } +static void tun_force_wake_queue(struct tun_struct *tun, + struct tun_file *tfile) +{ + /* Ensure that the producer can not stop the + * queue concurrently by taking locks. + */ + spin_lock_bh(&tfile->tx_ring.consumer_lock); + spin_lock(&tfile->tx_ring.producer_lock); + tfile->cons_cnt = 0; + /* Tested under the locks that tun_net_close() takes, so this can not + * undo its stop. tun_net_open() wakes the queues of a device that + * comes back up. + */ + if (netif_running(tun->dev)) + netif_wake_subqueue(tun->dev, tfile->queue_index); + spin_unlock(&tfile->tx_ring.producer_lock); + spin_unlock_bh(&tfile->tx_ring.consumer_lock); +} + static int tun_attach(struct tun_struct *tun, struct file *file, bool skip_filter, bool napi, bool napi_frags, bool publish_tun) @@ -730,8 +764,11 @@ static int tun_attach(struct tun_struct *tun, struct file *file, goto out; } + spin_lock(&tfile->tx_ring.consumer_lock); tfile->queue_index = tun->numqueues; + spin_unlock(&tfile->tx_ring.consumer_lock); tfile->socket.sk->sk_shutdown &= ~RCV_SHUTDOWN; + tun_force_wake_queue(tun, tfile); if (tfile->detached) { /* Re-attach detached tfile, updating XDP queue_index */ @@ -964,6 +1001,24 @@ static int tun_net_open(struct net_device *dev) /* Net device close. */ static int tun_net_close(struct net_device *dev) { + struct tun_struct *tun = netdev_priv(dev); + struct tun_file *tfile; + int i; + + /* netif_running() is already false: take both ring locks to keep the + * wake sites out, so the stop below is the last write to + * __QUEUE_STATE_DRV_XOFF. + */ + for (i = 0; i < tun->numqueues; i++) { + tfile = rtnl_dereference(tun->tfiles[i]); + + spin_lock_bh(&tfile->tx_ring.consumer_lock); + spin_lock(&tfile->tx_ring.producer_lock); + tfile->cons_cnt = 0; + spin_unlock(&tfile->tx_ring.producer_lock); + spin_unlock_bh(&tfile->tx_ring.consumer_lock); + } + netif_tx_stop_all_queues(dev); return 0; } @@ -2116,13 +2171,61 @@ static ssize_t tun_put_user(struct tun_struct *tun, return total; } -static void *tun_ring_recv(struct tun_file *tfile, int noblock, int *err) +/* Callers must hold ring.consumer_lock */ +static void __tun_wake_queue(struct tun_struct *tun, + struct tun_file *tfile, int consumed) +{ + u16 queue_index = tfile->queue_index; + struct netdev_queue *txq; + + lockdep_assert_held(&tfile->tx_ring.consumer_lock); + + if (!(tun->flags & IFF_BACKPRESSURE)) + return; + + /* A stop from tun_net_close() is not backpressure, leave it alone. */ + if (unlikely(!netif_running(tun->dev))) + return; + + /* Only the current owner of the slot may wake its subqueue. */ + if (unlikely(rcu_access_pointer(tun->tfiles[queue_index]) != tfile)) + return; + + txq = netdev_get_tx_queue(tun->dev, queue_index); + + /* Paired with smp_mb__after_atomic() in tun_net_xmit() */ + smp_mb(); + if (netif_tx_queue_stopped(txq)) { + tfile->cons_cnt += consumed; + if (tfile->cons_cnt >= tfile->tx_ring.size / 2 || + __ptr_ring_empty(&tfile->tx_ring)) { + netif_tx_wake_queue(txq); + tfile->cons_cnt = 0; + } + } +} + +static void *tun_ring_consume(struct tun_struct *tun, struct tun_file *tfile) +{ + void *ptr; + + spin_lock(&tfile->tx_ring.consumer_lock); + ptr = __ptr_ring_consume(&tfile->tx_ring); + if (ptr) + __tun_wake_queue(tun, tfile, 1); + + spin_unlock(&tfile->tx_ring.consumer_lock); + return ptr; +} + +static void *tun_ring_recv(struct tun_struct *tun, struct tun_file *tfile, + int noblock, int *err) { DECLARE_WAITQUEUE(wait, current); void *ptr = NULL; int error = 0; - ptr = ptr_ring_consume(&tfile->tx_ring); + ptr = tun_ring_consume(tun, tfile); if (ptr) goto out; if (noblock) { @@ -2134,7 +2237,7 @@ static void *tun_ring_recv(struct tun_file *tfile, int noblock, int *err) while (1) { set_current_state(TASK_INTERRUPTIBLE); - ptr = ptr_ring_consume(&tfile->tx_ring); + ptr = tun_ring_consume(tun, tfile); if (ptr) break; if (signal_pending(current)) { @@ -2171,7 +2274,7 @@ static ssize_t tun_do_read(struct tun_struct *tun, struct tun_file *tfile, if (!ptr) { /* Read frames from ring */ - ptr = tun_ring_recv(tfile, noblock, &err); + ptr = tun_ring_recv(tun, tfile, noblock, &err); if (!ptr) return err; } @@ -3630,6 +3733,13 @@ static int tun_queue_resize(struct tun_struct *tun) dev->tx_queue_len, GFP_KERNEL, tun_ptr_free); + if (!ret) { + for (i = 0; i < tun->numqueues; i++) { + tfile = rtnl_dereference(tun->tfiles[i]); + tun_force_wake_queue(tun, tfile); + } + } + kfree(rings); return ret; } From f65c1fb427aa5726cfe7a1b903f733f3a27099bb Mon Sep 17 00:00:00 2001 From: Simon Schippers Date: Mon, 3 Aug 2026 20:36:39 +0200 Subject: [PATCH 1133/1433] vhost-net: wake queue of tun/tap after ptr_ring consume Add tun_wake_queue() to tun.c and export it for use by vhost-net. The function validates that the file belongs to a device implemented by drivers/net/tun.c, in IFF_TUN as well as in IFF_TAP mode, and that the tfile exists, dereferences the tun_struct under RCU, and delegates to __tun_wake_queue(). vhost_net_buf_produce() now calls tun_wake_queue() after a successful batched consume of the ring to allow the netdev subqueue to be woken up. The point is to allow the queue to be stopped when it gets full, which is required for traffic shaping, implemented by the following "stop tail-drop when IFF_BACKPRESSURE is set". As __tun_wake_queue() returns early unless IFF_BACKPRESSURE is set, a tun/tap device that does not opt in only pays for the added check. macvtap and ipvtap rings, which get_tap_ptr_ring() accepts too, are unaffected: their producer is the tap_handle_frame() rx_handler and not ndo_start_xmit, so stopping a netdev TX queue would not hold it back. drivers/net/tap.c has no netdev_ops of its own either. No tap_wake_queue() is needed. cons_cnt and the wake decision are best-effort and are not reverted by ptr_ring_unconsume(), so vhost_net_buf_unproduce() can leave the subqueue woken over a full ring. The producer re-stops it on the next packet, and that path only runs from vhost_net_stop_vq() and vhost_net_set_backend(), when the consumer is going away, so a stopped queue is the correct end state rather than a stall. Co-developed-by: Tim Gebauer Signed-off-by: Tim Gebauer Signed-off-by: Simon Schippers Link: https://patch.msgid.link/20260803183641.96882-4-simon.schippers@tu-dortmund.de Signed-off-by: Jakub Kicinski --- drivers/net/tun.c | 25 +++++++++++++++++++++++++ drivers/vhost/net.c | 21 +++++++++++++++------ include/linux/if_tun.h | 4 ++++ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/drivers/net/tun.c b/drivers/net/tun.c index d49b6bfd104d..dc32566588d1 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -3848,6 +3848,31 @@ struct ptr_ring *tun_get_tx_ring(struct file *file) } EXPORT_SYMBOL_GPL(tun_get_tx_ring); +/* Callers must hold ring.consumer_lock */ +void tun_wake_queue(struct file *file, int consumed) +{ + struct tun_file *tfile; + struct tun_struct *tun; + + if (file->f_op != &tun_fops) + return; + + tfile = file->private_data; + if (!tfile) + return; + + lockdep_assert_held(&tfile->tx_ring.consumer_lock); + + rcu_read_lock(); + + tun = rcu_dereference(tfile->tun); + if (tun) + __tun_wake_queue(tun, tfile, consumed); + + rcu_read_unlock(); +} +EXPORT_SYMBOL_GPL(tun_wake_queue); + module_init(tun_init); module_exit(tun_cleanup); MODULE_DESCRIPTION(DRV_DESCRIPTION); diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c index 6949b704166d..3e72b9c6af0c 100644 --- a/drivers/vhost/net.c +++ b/drivers/vhost/net.c @@ -176,13 +176,21 @@ static void *vhost_net_buf_consume(struct vhost_net_buf *rxq) return ret; } -static int vhost_net_buf_produce(struct vhost_net_virtqueue *nvq) +static int vhost_net_buf_produce(struct sock *sk, + struct vhost_net_virtqueue *nvq) { + struct file *file = sk->sk_socket->file; struct vhost_net_buf *rxq = &nvq->rxq; rxq->head = 0; - rxq->tail = ptr_ring_consume_batched(nvq->rx_ring, rxq->queue, - VHOST_NET_BATCH); + spin_lock(&nvq->rx_ring->consumer_lock); + rxq->tail = __ptr_ring_consume_batched(nvq->rx_ring, rxq->queue, + VHOST_NET_BATCH); + + if (rxq->tail) + tun_wake_queue(file, rxq->tail); + + spin_unlock(&nvq->rx_ring->consumer_lock); return rxq->tail; } @@ -209,14 +217,15 @@ static int vhost_net_buf_peek_len(void *ptr) return __skb_array_len_with_tag(ptr); } -static int vhost_net_buf_peek(struct vhost_net_virtqueue *nvq) +static int vhost_net_buf_peek(struct sock *sk, + struct vhost_net_virtqueue *nvq) { struct vhost_net_buf *rxq = &nvq->rxq; if (!vhost_net_buf_is_empty(rxq)) goto out; - if (!vhost_net_buf_produce(nvq)) + if (!vhost_net_buf_produce(sk, nvq)) return 0; out: @@ -1004,7 +1013,7 @@ static int peek_head_len(struct vhost_net_virtqueue *rvq, struct sock *sk) unsigned long flags; if (rvq->rx_ring) - return vhost_net_buf_peek(rvq); + return vhost_net_buf_peek(sk, rvq); spin_lock_irqsave(&sk->sk_receive_queue.lock, flags); head = skb_peek(&sk->sk_receive_queue); diff --git a/include/linux/if_tun.h b/include/linux/if_tun.h index 80166eb62f41..eeb9ed3c5a23 100644 --- a/include/linux/if_tun.h +++ b/include/linux/if_tun.h @@ -22,6 +22,8 @@ struct tun_msg_ctl { #if defined(CONFIG_TUN) || defined(CONFIG_TUN_MODULE) struct socket *tun_get_socket(struct file *); struct ptr_ring *tun_get_tx_ring(struct file *file); +/* Callers must hold the consumer_lock of the ring of file */ +void tun_wake_queue(struct file *file, int consumed); static inline bool tun_is_xdp_frame(void *ptr) { @@ -55,6 +57,8 @@ static inline struct ptr_ring *tun_get_tx_ring(struct file *f) return ERR_PTR(-EINVAL); } +static inline void tun_wake_queue(struct file *f, int consumed) {} + static inline bool tun_is_xdp_frame(void *ptr) { return false; From 43be21ec2efdeb53d5e46ae77b3f7ce91539a5d9 Mon Sep 17 00:00:00 2001 From: Simon Schippers Date: Mon, 3 Aug 2026 20:36:40 +0200 Subject: [PATCH 1134/1433] ptr_ring: move free-space check into separate helper This patch moves the check for available free space for a new entry into a separate function. Existing callers that only check for a non-zero return value are unaffected. __ptr_ring_produce() now returns -EINVAL for a zero-size ring and -ENOSPC when full, whereas before both cases returned -ENOSPC. The new helper allows callers to determine in advance whether a single subsequent __ptr_ring_produce() call will succeed. This information can, for example, be used to temporarily stop producing until __ptr_ring_check_produce() indicates that space is available again. The return values are documented above the helper, as a caller that waits for space must distinguish the transient -ENOSPC from the permanent -EINVAL. Co-developed-by: Tim Gebauer Signed-off-by: Tim Gebauer Signed-off-by: Simon Schippers Link: https://patch.msgid.link/20260803183641.96882-5-simon.schippers@tu-dortmund.de Signed-off-by: Jakub Kicinski --- include/linux/ptr_ring.h | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/include/linux/ptr_ring.h b/include/linux/ptr_ring.h index d2c3629bbe45..631c43fde440 100644 --- a/include/linux/ptr_ring.h +++ b/include/linux/ptr_ring.h @@ -96,6 +96,26 @@ static inline bool ptr_ring_full_bh(struct ptr_ring *r) return ret; } +/* Report whether the next __ptr_ring_produce() has room for one entry: + * 0 means the single slot at r->queue[r->producer] is free, -ENOSPC means + * the ring is full, which is transient, and -EINVAL means r->size is 0, + * which is permanent. A caller that stops producing and waits for space + * must therefore do so only for -ENOSPC. + * + * Note: callers invoking this in a loop must use a compiler barrier, + * for example cpu_relax(). Callers must hold producer_lock. + */ +static inline int __ptr_ring_check_produce(struct ptr_ring *r) +{ + if (unlikely(!r->size)) + return -EINVAL; + + if (data_race(r->queue[r->producer])) + return -ENOSPC; + + return 0; +} + /* Note: callers invoking this in a loop must use a compiler barrier, * for example cpu_relax(). Callers must hold producer_lock. * Callers are responsible for making sure pointer that is being queued @@ -103,8 +123,10 @@ static inline bool ptr_ring_full_bh(struct ptr_ring *r) */ static inline int __ptr_ring_produce(struct ptr_ring *r, void *ptr) { - if (unlikely(!r->size) || data_race(r->queue[r->producer])) - return -ENOSPC; + int ret = __ptr_ring_check_produce(r); + + if (ret) + return ret; /* Make sure the pointer we are storing points to a valid data. */ /* Pairs with the dependency ordering in __ptr_ring_consume. */ From d00c7369ef24ac8e0383de0fd8ef3384de20bcfa Mon Sep 17 00:00:00 2001 From: Simon Schippers Date: Mon, 3 Aug 2026 20:36:41 +0200 Subject: [PATCH 1135/1433] tun/tap & vhost-net: stop tail-drop when IFF_BACKPRESSURE is set This commit prevents tail-drop when IFF_BACKPRESSURE is set, a qdisc is present and the ptr_ring becomes full. Once the ring reaches capacity after a produce attempt, the netdev queue is stopped instead of dropping subsequent packets. Without the flag, or if no qdisc is present, the previous tail-drop behavior is preserved. IFF_BACKPRESSURE is added to TUN_FEATURES here and not in the patch that defines it, so that TUNSETIFF honours the flag only once the implementation behind it is complete. The unconditional version of this behavior was reverted because it caused a significant throughput drop in an IPv6 multicast testcase on Brett Sheffield's librecast testbed [1]: with 8 iperf3 TCP threads sending, the throughput dropped from 13.5 Gbit/s to 9.13 Gbit/s. This is why the queue stopping is now gated on IFF_BACKPRESSURE. If producing an entry fails anyway due to a race, tun_net_xmit() drops the packet. Such rare races are expected because LLTX is enabled and the transmit path operates without the usual locking. The queue state is only touched while the device is running. The stop itself would be harmless during teardown, as tun_net_close() sets the same bit, but the re-check below it wakes the queue again and must not clear that stop. A later TUNSETIFF can clear the flag again while the device has at most one queue. Past that point tun_set_iff() returns before it writes tun->flags, which is how it already treats every other TUN_FEATURES bit. For the case where the flag does change, tun_set_iff() calls tun_force_wake_queue() for the attached tfiles, so that no queue stays stopped without a consumer that would wake it. The __tun_wake_queue() function of the consumer races with the producer for waking/stopping the netdev queue, which could result in a stalled queue. Therefore, an smp_mb__after_atomic() is introduced that pairs with the smp_mb() of the consumer. It follows the principle of store buffering described in tools/memory-model/Documentation/recipes.txt: - The producer in tun_net_xmit() first sets __QUEUE_STATE_DRV_XOFF, followed by an smp_mb__after_atomic() (= smp_mb()), and then reads the ring with __ptr_ring_check_produce(). - The consumer in __tun_wake_queue() first writes zero to the ring in __ptr_ring_consume(), followed by an smp_mb(), and then reads the queue status with netif_tx_queue_stopped(). => Following the aforementioned principle, it is impossible for the producer to see a full ring (and therefore not wake the queue on the re-check) while the consumer simultaneously fails to see a stopped queue (and therefore also does not wake it). tun_net_xmit() holds only the producer_lock and can not reset cons_cnt, which the consumer_lock protects, so the wake on the re-check leaves stale credit behind. That is accepted as best-effort, the re-check rarely succeeds and the next drain corrects the count. The documentation in tuntap.rst is updated accordingly. Benchmarks: My own benchmarks show a slight regression in raw transmission performance when using two sending threads. Packet loss also occurs only in the two-thread sending case; no packet loss was observed with a single sending thread. Test setup: AMD Ryzen 5 5600X at 4.3 GHz, 3200 MHz RAM, isolated QEMU threads; Average over 50 runs @ 100,000,000 packets. SRSO and spectre v2 mitigations disabled. Note for tap+vhost-net: XDP drop program active in VM -> ~2.5x faster; slower for tap due to more syscalls (high utilization of entry_SYSRETQ_unsafe_stack in perf) +--------------------------+--------------+----------------+----------+ | 1 thread | Stock | Patched with | diff | | sending | | fq_codel qdisc | | +------------+-------------+--------------+----------------+----------+ | TAP | Received | 1.132 Mpps | 1.123 Mpps | -0.8% | | +-------------+--------------+----------------+----------+ | | Lost/s | 3.765 Mpps | 0 pps | | +------------+-------------+--------------+----------------+----------+ | TAP | Received | 3.857 Mpps | 3.901 Mpps | +1.1% | | +-------------+--------------+----------------+----------+ | +vhost-net | Lost/s | 0.802 Mpps | 0 pps | | +------------+-------------+--------------+----------------+----------+ +--------------------------+--------------+----------------+----------+ | 2 threads | Stock | Patched with | diff | | sending | | fq_codel qdisc | | +------------+-------------+--------------+----------------+----------+ | TAP | Received | 1.115 Mpps | 1.081 Mpps | -3.0% | | +-------------+--------------+----------------+----------+ | | Lost/s | 8.490 Mpps | 391 pps | | +------------+-------------+--------------+----------------+----------+ | TAP | Received | 3.664 Mpps | 3.555 Mpps | -3.0% | | +-------------+--------------+----------------+----------+ | +vhost-net | Lost/s | 5.330 Mpps | 938 pps | | +------------+-------------+--------------+----------------+----------+ [1] https://lore.kernel.org/netdev/akVnoOYQOrt8k-Gu@karahi.librecast.net/ Co-developed-by: Tim Gebauer Signed-off-by: Tim Gebauer Signed-off-by: Simon Schippers Link: https://lore.kernel.org/netdev/akVnoOYQOrt8k-Gu@karahi.librecast.net/ Link: https://patch.msgid.link/20260803183641.96882-6-simon.schippers@tu-dortmund.de Signed-off-by: Jakub Kicinski --- Documentation/networking/tuntap.rst | 32 +++++++++++++++++++++++ drivers/net/tun.c | 39 ++++++++++++++++++++++++----- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/Documentation/networking/tuntap.rst b/Documentation/networking/tuntap.rst index 4d7087f727be..56c9dc7b96af 100644 --- a/Documentation/networking/tuntap.rst +++ b/Documentation/networking/tuntap.rst @@ -206,6 +206,38 @@ enable is true we enable it, otherwise we disable it:: return ioctl(fd, TUNSETQUEUE, (void *)&ifr); } +3.4 qdisc backpressure +---------------------- + +IFF_BACKPRESSURE can be set to enable qdisc backpressure. Without it, TX +drops occur when the internal ring buffer is full, so any attached qdisc +is effectively bypassed and applications only learn about congestion +through those drops. + +With it, the kernel stops the queue instead, letting the qdisc hold and +schedule packets, so its AQM, shaping and fairness actually apply. This +helps protocols like TCP, which cut throughput in reaction to packet +drops. With IFF_BACKPRESSURE, drops then only occur as a rare race. +Backpressure requires a qdisc to be attached and has no effect with +noqueue. + +The flag is a property of the TUN/TAP device rather than of the file +descriptor it was set on, so it applies to all queues of the device, +regardless of which process opened which queue. + +The flag can only be changed while the device has at most one queue. On a +multiqueue device that already has a second queue attached or detached, a +later TUNSETIFF succeeds but leaves the flag as it is. All the other +TUNSETIFF flags behave the same way. + +The txqueuelen can be reduced alongside this flag to further shift +buffering into the qdisc and reduce bufferbloat, at a possible +performance cost. + +When running multiple network streams in parallel through a single +TUN/TAP queue, the flag may reduce performance due to the extra overhead +of the backpressure mechanism. + Universal TUN/TAP device driver Frequently Asked Question ========================================================= diff --git a/drivers/net/tun.c b/drivers/net/tun.c index dc32566588d1..ec90fef4a42f 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -98,7 +98,8 @@ static void tun_default_link_ksettings(struct net_device *dev, #define TUN_FASYNC IFF_ATTACH_QUEUE #define TUN_FEATURES (IFF_NO_PI | IFF_ONE_QUEUE | IFF_VNET_HDR | \ - IFF_MULTI_QUEUE | IFF_NAPI | IFF_NAPI_FRAGS) + IFF_MULTI_QUEUE | IFF_NAPI | IFF_NAPI_FRAGS | \ + IFF_BACKPRESSURE) #define GOODCOPY_LEN 128 @@ -1063,6 +1064,7 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev) struct netdev_queue *queue; struct tun_file *tfile; int len = skb->len; + int ret; rcu_read_lock(); tfile = rcu_dereference(tun->tfiles[txq]); @@ -1117,13 +1119,35 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev) nf_reset_ct(skb); - if (ptr_ring_produce(&tfile->tx_ring, skb)) { + queue = netdev_get_tx_queue(dev, txq); + + spin_lock(&tfile->tx_ring.producer_lock); + ret = __ptr_ring_produce(&tfile->tx_ring, skb); + /* Do not touch the queue state of a device that is going down. */ + if ((tun->flags & IFF_BACKPRESSURE) && netif_running(dev) && + !qdisc_txq_has_no_queue(queue) && + __ptr_ring_check_produce(&tfile->tx_ring) == -ENOSPC) { + netif_tx_stop_queue(queue); + /* Paired with smp_mb() in __tun_wake_queue() */ + smp_mb__after_atomic(); + if (!__ptr_ring_check_produce(&tfile->tx_ring)) + netif_tx_wake_queue(queue); + } + spin_unlock(&tfile->tx_ring.producer_lock); + + if (ret) { + /* This should be a rare case if IFF_BACKPRESSURE is enabled and + * a qdisc is present, but can happen due to lltx. + * Since skb_tx_timestamp(), skb_orphan(), + * run_ebpf_filter() and pskb_trim() could have tinkered + * with the SKB, returning NETDEV_TX_BUSY is unsafe and + * we must drop instead. + */ drop_reason = SKB_DROP_REASON_FULL_RING; goto drop; } /* dev->lltx requires to do our own update of trans_start */ - queue = netdev_get_tx_queue(dev, txq); txq_trans_cond_update(queue); /* Notify and wake up reader process */ @@ -2806,8 +2830,9 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) { struct tun_struct *tun; struct tun_file *tfile = file->private_data; + struct tun_file *ntfile; struct net_device *dev; - int err; + int err, i; if (tfile->detached) return -EINVAL; @@ -2936,8 +2961,10 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) /* Make sure persistent devices do not get stuck in * xoff state. */ - if (netif_running(tun->dev)) - netif_tx_wake_all_queues(tun->dev); + for (i = 0; i < tun->numqueues; i++) { + ntfile = rtnl_dereference(tun->tfiles[i]); + tun_force_wake_queue(tun, ntfile); + } strscpy(ifr->ifr_name, tun->dev->name); return 0; From 7d942a7bd924203aee42d1b44e97629dce3c6b25 Mon Sep 17 00:00:00 2001 From: Jiawen Wu Date: Mon, 3 Aug 2026 14:43:30 +0800 Subject: [PATCH 1136/1433] net: ngbe: implement libwx reset ops Implement wx->do_reset() for library module calling. Signed-off-by: Jiawen Wu Reviewed-by: Larysa Zaremba Reviewed-by: Aleksandr Loktionov Reviewed-by: Breno Leitao Link: https://patch.msgid.link/20260803064334.21876-2-jiawenwu@trustnetic.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/wangxun/ngbe/ngbe_ethtool.c | 1 - drivers/net/ethernet/wangxun/ngbe/ngbe_main.c | 37 ++++++++++++++++++- drivers/net/ethernet/wangxun/ngbe/ngbe_type.h | 1 + 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c index b2e191982803..1960f7154151 100644 --- a/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c +++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c @@ -59,7 +59,6 @@ static int ngbe_set_ringparam(struct net_device *netdev, wx_set_ring(wx, new_tx_count, new_rx_count, temp_ring); kvfree(temp_ring); - wx_configure(wx); ngbe_up(wx); clear_reset: diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c index bfdff6345303..add8fce4c109 100644 --- a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c +++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c @@ -133,6 +133,7 @@ static int ngbe_sw_init(struct wx *wx) wx->mbx.size = WX_VXMAILBOX_SIZE; wx->setup_tc = ngbe_setup_tc; + wx->do_reset = ngbe_do_reset; set_bit(0, &wx->fwd_bitmask); return 0; @@ -423,7 +424,7 @@ void ngbe_down(struct wx *wx) wx_clean_all_rx_rings(wx); } -void ngbe_up(struct wx *wx) +static void ngbe_up_complete(struct wx *wx) { wx_configure_vectors(wx); @@ -490,7 +491,7 @@ static int ngbe_open(struct net_device *netdev) wx_ptp_init(wx); - ngbe_up(wx); + ngbe_up_complete(wx); return 0; err_dis_phy: @@ -503,6 +504,12 @@ static int ngbe_open(struct net_device *netdev) return err; } +void ngbe_up(struct wx *wx) +{ + wx_configure(wx); + ngbe_up_complete(wx); +} + /** * ngbe_close - Disables a network interface * @netdev: network interface device structure @@ -590,6 +597,8 @@ int ngbe_setup_tc(struct net_device *dev, u8 tc) */ if (netif_running(dev)) ngbe_close(dev); + else + ngbe_reset(wx); wx_clear_interrupt_scheme(wx); @@ -606,6 +615,30 @@ int ngbe_setup_tc(struct net_device *dev, u8 tc) return 0; } +static void ngbe_reinit_locked(struct wx *wx) +{ + netif_trans_update(wx->netdev); + + mutex_lock(&wx->reset_lock); + set_bit(WX_STATE_RESETTING, wx->state); + + ngbe_down(wx); + ngbe_up(wx); + + clear_bit(WX_STATE_RESETTING, wx->state); + mutex_unlock(&wx->reset_lock); +} + +void ngbe_do_reset(struct net_device *netdev) +{ + struct wx *wx = netdev_priv(netdev); + + if (netif_running(netdev)) + ngbe_reinit_locked(wx); + else + ngbe_reset(wx); +} + static const struct net_device_ops ngbe_netdev_ops = { .ndo_open = ngbe_open, .ndo_stop = ngbe_close, diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h b/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h index 7077a0da4c98..4f648f272c08 100644 --- a/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h +++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h @@ -125,5 +125,6 @@ extern char ngbe_driver_name[]; void ngbe_down(struct wx *wx); void ngbe_up(struct wx *wx); int ngbe_setup_tc(struct net_device *dev, u8 tc); +void ngbe_do_reset(struct net_device *netdev); #endif /* _NGBE_TYPE_H_ */ From 22d95e93c05b0e4af35b94cef004254306a63a2b Mon Sep 17 00:00:00 2001 From: Jiawen Wu Date: Mon, 3 Aug 2026 14:43:31 +0800 Subject: [PATCH 1137/1433] net: wangxun: add Tx timeout process Implement .ndo_tx_timeout to handle Tx side timeout event. When a Tx timeout event occur, it will trigger driver into reset process. And allocate a separate work queue for reset process. The WX_HANG_CHECK_ARMED bit is set to indicate a potential hang. It will be cleared if a pause frame is received to avoid false hang detection caused by pause frames. Signed-off-by: Jiawen Wu Link: https://patch.msgid.link/20260803064334.21876-3-jiawenwu@trustnetic.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/wangxun/libwx/Makefile | 2 +- drivers/net/ethernet/wangxun/libwx/wx_err.c | 175 ++++++++++++++++++ drivers/net/ethernet/wangxun/libwx/wx_err.h | 16 ++ drivers/net/ethernet/wangxun/libwx/wx_hw.c | 17 +- drivers/net/ethernet/wangxun/libwx/wx_lib.c | 37 ++++ drivers/net/ethernet/wangxun/libwx/wx_type.h | 18 +- drivers/net/ethernet/wangxun/ngbe/ngbe_main.c | 13 ++ .../net/ethernet/wangxun/txgbe/txgbe_main.c | 13 ++ 8 files changed, 286 insertions(+), 5 deletions(-) create mode 100644 drivers/net/ethernet/wangxun/libwx/wx_err.c create mode 100644 drivers/net/ethernet/wangxun/libwx/wx_err.h diff --git a/drivers/net/ethernet/wangxun/libwx/Makefile b/drivers/net/ethernet/wangxun/libwx/Makefile index a71b0ad77de3..c8724bb129aa 100644 --- a/drivers/net/ethernet/wangxun/libwx/Makefile +++ b/drivers/net/ethernet/wangxun/libwx/Makefile @@ -4,5 +4,5 @@ obj-$(CONFIG_LIBWX) += libwx.o -libwx-objs := wx_hw.o wx_lib.o wx_ethtool.o wx_ptp.o wx_mbx.o wx_sriov.o +libwx-objs := wx_hw.o wx_lib.o wx_ethtool.o wx_ptp.o wx_mbx.o wx_sriov.o wx_err.o libwx-objs += wx_vf.o wx_vf_lib.o wx_vf_common.o diff --git a/drivers/net/ethernet/wangxun/libwx/wx_err.c b/drivers/net/ethernet/wangxun/libwx/wx_err.c new file mode 100644 index 000000000000..4c45e97bf9e3 --- /dev/null +++ b/drivers/net/ethernet/wangxun/libwx/wx_err.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2015 - 2026 Beijing WangXun Technology Co., Ltd. */ +/* Copyright (c) 1999 - 2026 Intel Corporation. */ + +#include +#include + +#include "wx_type.h" +#include "wx_lib.h" +#include "wx_err.h" + +static void wx_pf_reset_subtask(struct wx *wx) +{ + if (!test_and_clear_bit(WX_FLAG_NEED_DO_RESET, wx->flags)) + return; + + wx_warn(wx, "Reset adapter.\n"); + if (wx->do_reset) + wx->do_reset(wx->netdev); +} + +static void wx_reset_task(struct work_struct *work) +{ + struct wx *wx = container_of(work, struct wx, reset_task); + + rtnl_lock(); + + if (test_bit(WX_STATE_DOWN, wx->state) || + test_bit(WX_STATE_RESETTING, wx->state)) + goto out; + + wx_pf_reset_subtask(wx); + +out: + rtnl_unlock(); +} + +void wx_check_err_subtask(struct wx *wx) +{ + if (test_bit(WX_FLAG_NEED_DO_RESET, wx->flags)) + queue_work(wx->reset_wq, &wx->reset_task); +} +EXPORT_SYMBOL(wx_check_err_subtask); + +int wx_init_err_task(struct wx *wx) +{ + wx->reset_wq = alloc_workqueue("%s_reset_wq_%x", WQ_UNBOUND | WQ_HIGHPRI, + 1, wx->driver_name, pci_dev_id(wx->pdev)); + if (!wx->reset_wq) { + wx_err(wx, "Failed to create wx_reset_wq workqueue\n"); + return -ENOMEM; + } + + INIT_WORK(&wx->reset_task, wx_reset_task); + return 0; +} +EXPORT_SYMBOL(wx_init_err_task); + +static bool wx_ring_tx_pending(struct wx *wx) +{ + int i; + + for (i = 0; i < wx->num_tx_queues; i++) { + struct wx_ring *tx_ring = wx->tx_ring[i]; + + if (tx_ring->next_to_use != tx_ring->next_to_clean) + return true; + } + + return false; +} + +static bool wx_vf_tx_pending(struct wx *wx) +{ + struct wx_ring_feature *vmdq = &wx->ring_feature[RING_F_VMDQ]; + u32 q_per_pool = __ALIGN_MASK(1, ~vmdq->mask); + u32 i, j; + + if (!wx->num_vfs) + return false; + + for (i = 0; i < wx->num_vfs; i++) { + for (j = 0; j < q_per_pool; j++) { + u32 h, t; + + h = rd32(wx, WX_PX_TR_RP_PV(q_per_pool, i, j)); + t = rd32(wx, WX_PX_TR_WP_PV(q_per_pool, i, j)); + + if (h != t) + return true; + } + } + + return false; +} + +static void wx_watchdog_flush_tx(struct wx *wx) +{ + if (!netif_running(wx->netdev)) + return; + if (netif_carrier_ok(wx->netdev)) + return; + + if (wx_ring_tx_pending(wx) || wx_vf_tx_pending(wx)) { + /* We've lost link, so the controller stops DMA, + * but we've got queued Tx work that's never going + * to get done, so reset controller to flush Tx. + * (Do the reset outside of interrupt context). + */ + wx_warn(wx, "initiating reset due to lost link with pending Tx work\n"); + set_bit(WX_FLAG_NEED_DO_RESET, wx->flags); + } +} + +static void wx_detect_tx_hang(struct wx *wx) +{ + int i; + + /* If we're down or resetting, just bail */ + if (!netif_running(wx->netdev) || + test_bit(WX_STATE_RESETTING, wx->state)) + return; + + /* Force detection of hung controller */ + if (netif_carrier_ok(wx->netdev)) { + for (i = 0; i < wx->num_tx_queues; i++) + set_bit(WX_TX_DETECT_HANG, wx->tx_ring[i]->state); + } +} + +void wx_check_hang_subtask(struct wx *wx) +{ + if (test_bit(WX_STATE_DOWN, wx->state) || + test_bit(WX_STATE_RESETTING, wx->state)) + return; + + wx_watchdog_flush_tx(wx); + wx_detect_tx_hang(wx); +} +EXPORT_SYMBOL(wx_check_hang_subtask); + +static void wx_tx_timeout_reset(struct wx *wx) +{ + if (test_bit(WX_STATE_DOWN, wx->state)) + return; + + set_bit(WX_FLAG_NEED_DO_RESET, wx->flags); + wx_warn(wx, "initiating reset due to tx timeout\n"); + wx_service_event_schedule(wx); +} + +void wx_tx_timeout(struct net_device *netdev, unsigned int __always_unused txqueue) +{ + struct wx *wx = netdev_priv(netdev); + + wx_tx_timeout_reset(wx); +} +EXPORT_SYMBOL(wx_tx_timeout); + +void wx_handle_tx_hang(struct wx_ring *tx_ring, unsigned int next) +{ + struct wx *wx = netdev_priv(tx_ring->netdev); + + wx_warn(wx, + "Detected Tx Unit Hang: Queue %d, TDH %x, TDT %x, ntu %x, ntc %x, ntc.time_stamp %lx, jiffies %lx\n", + tx_ring->queue_index, + rd32(wx, WX_PX_TR_RP(tx_ring->reg_idx)), + rd32(wx, WX_PX_TR_WP(tx_ring->reg_idx)), + tx_ring->next_to_use, next, + tx_ring->tx_buffer_info[next].time_stamp, jiffies); + + netif_stop_subqueue(tx_ring->netdev, tx_ring->queue_index); + + wx_tx_timeout_reset(wx); +} diff --git a/drivers/net/ethernet/wangxun/libwx/wx_err.h b/drivers/net/ethernet/wangxun/libwx/wx_err.h new file mode 100644 index 000000000000..1eed13e48095 --- /dev/null +++ b/drivers/net/ethernet/wangxun/libwx/wx_err.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * WangXun Gigabit PCI Express Linux driver + * Copyright (c) 2015 - 2026 Beijing WangXun Technology Co., Ltd. + */ + +#ifndef _WX_ERR_H_ +#define _WX_ERR_H_ + +void wx_check_err_subtask(struct wx *wx); +int wx_init_err_task(struct wx *wx); +void wx_check_hang_subtask(struct wx *wx); +void wx_tx_timeout(struct net_device *netdev, unsigned int txqueue); +void wx_handle_tx_hang(struct wx_ring *tx_ring, unsigned int next); + +#endif /* _WX_ERR_H_ */ diff --git a/drivers/net/ethernet/wangxun/libwx/wx_hw.c b/drivers/net/ethernet/wangxun/libwx/wx_hw.c index 260e14d5d541..122c4952d203 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_hw.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_hw.c @@ -1932,6 +1932,7 @@ static void wx_configure_tx_ring(struct wx *wx, else ring->atr_sample_rate = 0; + bitmap_zero(ring->state, WX_RING_STATE_NBITS); /* reinitialize tx_buffer_info */ memset(ring->tx_buffer_info, 0, sizeof(struct wx_tx_buffer) * ring->count); @@ -2851,16 +2852,26 @@ EXPORT_SYMBOL(wx_fc_enable); static void wx_update_xoff_rx_lfc(struct wx *wx) { struct wx_hw_stats *hwstats = &wx->stats; + u64 data; + int i; if (wx->fc.mode != wx_fc_full && wx->fc.mode != wx_fc_rx_pause) return; if (wx->mac.type >= wx_mac_aml) - hwstats->lxoffrxc += rd32_wrap(wx, WX_MAC_LXOFFRXC_AML, - &wx->last_stats.lxoffrxc); + data = rd32_wrap(wx, WX_MAC_LXOFFRXC_AML, + &wx->last_stats.lxoffrxc); else - hwstats->lxoffrxc += rd64(wx, WX_MAC_LXOFFRXC); + data = rd64(wx, WX_MAC_LXOFFRXC); + hwstats->lxoffrxc += data; + + /* refill credits (no tx hang) if we received xoff */ + if (!data) + return; + + for (i = 0; i < wx->num_tx_queues; i++) + clear_bit(WX_HANG_CHECK_ARMED, wx->tx_ring[i]->state); } /** diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.c b/drivers/net/ethernet/wangxun/libwx/wx_lib.c index 476b71049f7e..633ab5c61a05 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_lib.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.c @@ -14,6 +14,7 @@ #include "wx_type.h" #include "wx_lib.h" +#include "wx_err.h" #include "wx_ptp.h" #include "wx_hw.h" #include "wx_vf_lib.h" @@ -742,6 +743,37 @@ static struct netdev_queue *wx_txring_txq(const struct wx_ring *ring) return netdev_get_tx_queue(ring->netdev, ring->queue_index); } +static u32 wx_get_tx_pending(struct wx_ring *ring) +{ + unsigned int head, tail; + + head = ring->next_to_clean; + tail = ring->next_to_use; + + return ((head <= tail) ? tail : tail + ring->count) - head; +} + +static bool wx_check_tx_hang(struct wx_ring *ring) +{ + u32 tx_done_old = ring->tx_stats.tx_done_old; + u32 tx_pending = wx_get_tx_pending(ring); + u32 tx_done = ring->stats.packets; + + if (!test_and_clear_bit(WX_TX_DETECT_HANG, ring->state)) + return false; + + if (tx_done_old == tx_done && tx_pending) + /* make sure it is true for two checks in a row */ + return test_and_set_bit(WX_HANG_CHECK_ARMED, ring->state); + + /* update completed stats and continue */ + ring->tx_stats.tx_done_old = tx_done; + /* reset the countdown */ + clear_bit(WX_HANG_CHECK_ARMED, ring->state); + + return false; +} + /** * wx_clean_tx_irq - Reclaim resources after transmit completes * @q_vector: structure containing interrupt and ring information @@ -866,6 +898,11 @@ static bool wx_clean_tx_irq(struct wx_q_vector *q_vector, netdev_tx_completed_queue(wx_txring_txq(tx_ring), total_packets, total_bytes); + if (wx_check_tx_hang(tx_ring)) { + wx_handle_tx_hang(tx_ring, i); + return true; + } + #define TX_WAKE_THRESHOLD (DESC_NEEDED * 2) if (unlikely(total_packets && netif_carrier_ok(tx_ring->netdev) && (wx_desc_unused(tx_ring) >= TX_WAKE_THRESHOLD))) { diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h index 65e3e55db1cf..f0ebc0acf0f3 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_type.h +++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h @@ -450,6 +450,11 @@ enum WX_MSCA_CMD_value { #define WX_PX_TR_CFG_THRE_SHIFT 8 #define WX_PX_TR_CFG_HEAD_WB BIT(27) +#define WX_PX_TR_RP_PV(q_per_pool, vf_number, vf_q_index) \ + (WX_PX_TR_RP((q_per_pool) * (vf_number) + (vf_q_index))) +#define WX_PX_TR_WP_PV(q_per_pool, vf_number, vf_q_index) \ + (WX_PX_TR_WP((q_per_pool) * (vf_number) + (vf_q_index))) + /* Receive DMA Registers */ #define WX_PX_RR_BAL(_i) (0x01000 + ((_i) * 0x40)) #define WX_PX_RR_BAH(_i) (0x01004 + ((_i) * 0x40)) @@ -1040,6 +1045,7 @@ struct wx_queue_stats { struct wx_tx_queue_stats { u64 restart_queue; u64 tx_busy; + u32 tx_done_old; }; struct wx_rx_queue_stats { @@ -1055,6 +1061,12 @@ struct wx_rx_queue_stats { #define wx_for_each_ring(posm, headm) \ for (posm = (headm).ring; posm; posm = posm->next) +enum wx_ring_state { + WX_TX_DETECT_HANG, + WX_HANG_CHECK_ARMED, + WX_RING_STATE_NBITS +}; + struct wx_ring_container { struct wx_ring *ring; /* pointer to linked list of rings */ unsigned int total_bytes; /* total bytes processed this int */ @@ -1074,6 +1086,7 @@ struct wx_ring { struct wx_tx_buffer *tx_buffer_info; struct wx_rx_buffer *rx_buffer_info; }; + DECLARE_BITMAP(state, WX_RING_STATE_NBITS); u8 __iomem *tail; dma_addr_t dma; /* phys. address of descriptor ring */ dma_addr_t headwb_dma; @@ -1423,6 +1436,8 @@ struct wx { struct timer_list service_timer; struct work_struct service_task; + struct work_struct reset_task; + struct workqueue_struct *reset_wq; struct mutex reset_lock; /* mutex for reset */ }; @@ -1505,7 +1520,8 @@ rd32_wrap(struct wx *wx, u32 reg, u32 *last) #define wx_err(wx, fmt, arg...) \ dev_err(&(wx)->pdev->dev, fmt, ##arg) - +#define wx_warn(wx, fmt, arg...) \ + dev_warn(&(wx)->pdev->dev, fmt, ##arg) #define wx_dbg(wx, fmt, arg...) \ dev_dbg(&(wx)->pdev->dev, fmt, ##arg) diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c index add8fce4c109..7c3a9da493d8 100644 --- a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c +++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c @@ -14,6 +14,7 @@ #include "../libwx/wx_type.h" #include "../libwx/wx_hw.h" #include "../libwx/wx_lib.h" +#include "../libwx/wx_err.h" #include "../libwx/wx_ptp.h" #include "../libwx/wx_mbx.h" #include "../libwx/wx_sriov.h" @@ -148,6 +149,8 @@ static void ngbe_service_task(struct work_struct *work) struct wx *wx = container_of(work, struct wx, service_task); wx_update_stats(wx); + wx_check_hang_subtask(wx); + wx_check_err_subtask(wx); wx_service_event_complete(wx); } @@ -393,6 +396,7 @@ static void ngbe_disable_device(struct wx *wx) netif_tx_stop_all_queues(netdev); netif_tx_disable(netdev); + clear_bit(WX_FLAG_NEED_DO_RESET, wx->flags); timer_delete_sync(&wx->service_timer); cancel_work_sync(&wx->service_task); @@ -644,6 +648,7 @@ static const struct net_device_ops ngbe_netdev_ops = { .ndo_stop = ngbe_close, .ndo_change_mtu = wx_change_mtu, .ndo_start_xmit = wx_xmit_frame, + .ndo_tx_timeout = wx_tx_timeout, .ndo_set_rx_mode = wx_set_rx_mode, .ndo_set_features = wx_set_features, .ndo_fix_features = wx_fix_features, @@ -829,6 +834,10 @@ static int ngbe_probe(struct pci_dev *pdev, eth_hw_addr_set(netdev, wx->mac.perm_addr); wx_mac_set_default_filter(wx, wx->mac.perm_addr); + err = wx_init_err_task(wx); + if (err) + goto err_free_mac_table; + ngbe_init_service(wx); err = wx_init_interrupt_scheme(wx); @@ -856,6 +865,8 @@ static int ngbe_probe(struct pci_dev *pdev, err_cancel_service: timer_delete_sync(&wx->service_timer); cancel_work_sync(&wx->service_task); + cancel_work_sync(&wx->reset_task); + destroy_workqueue(wx->reset_wq); err_free_mac_table: kfree(wx->rss_key); kfree(wx->mac_table); @@ -887,6 +898,8 @@ static void ngbe_remove(struct pci_dev *pdev) timer_shutdown_sync(&wx->service_timer); cancel_work_sync(&wx->service_task); + cancel_work_sync(&wx->reset_task); + destroy_workqueue(wx->reset_wq); phylink_destroy(wx->phylink); pci_release_selected_regions(pdev, diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c index c277863baf67..9fac2369a55c 100644 --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c @@ -14,6 +14,7 @@ #include "../libwx/wx_type.h" #include "../libwx/wx_lib.h" +#include "../libwx/wx_err.h" #include "../libwx/wx_ptp.h" #include "../libwx/wx_hw.h" #include "../libwx/wx_mbx.h" @@ -123,6 +124,8 @@ static void txgbe_service_task(struct work_struct *work) txgbe_module_detection_subtask(wx); txgbe_link_config_subtask(wx); wx_update_stats(wx); + wx_check_hang_subtask(wx); + wx_check_err_subtask(wx); wx_service_event_complete(wx); } @@ -224,6 +227,7 @@ static void txgbe_disable_device(struct wx *wx) wx_irq_disable(wx); wx_napi_disable_all(wx); + clear_bit(WX_FLAG_NEED_DO_RESET, wx->flags); timer_delete_sync(&wx->service_timer); cancel_work_sync(&wx->service_task); @@ -654,6 +658,7 @@ static const struct net_device_ops txgbe_netdev_ops = { .ndo_stop = txgbe_close, .ndo_change_mtu = wx_change_mtu, .ndo_start_xmit = wx_xmit_frame, + .ndo_tx_timeout = wx_tx_timeout, .ndo_set_rx_mode = wx_set_rx_mode, .ndo_set_features = wx_set_features, .ndo_fix_features = wx_fix_features, @@ -814,6 +819,10 @@ static int txgbe_probe(struct pci_dev *pdev, eth_hw_addr_set(netdev, wx->mac.perm_addr); wx_mac_set_default_filter(wx, wx->mac.perm_addr); + err = wx_init_err_task(wx); + if (err) + goto err_free_mac_table; + txgbe_init_service(wx); err = wx_init_interrupt_scheme(wx); @@ -916,6 +925,8 @@ static int txgbe_probe(struct pci_dev *pdev, err_cancel_service: timer_delete_sync(&wx->service_timer); cancel_work_sync(&wx->service_task); + cancel_work_sync(&wx->reset_task); + destroy_workqueue(wx->reset_wq); err_free_mac_table: kfree(wx->rss_key); kfree(wx->mac_table); @@ -949,6 +960,8 @@ static void txgbe_remove(struct pci_dev *pdev) timer_shutdown_sync(&wx->service_timer); cancel_work_sync(&wx->service_task); + cancel_work_sync(&wx->reset_task); + destroy_workqueue(wx->reset_wq); txgbe_remove_phy(txgbe); wx_free_isb_resources(wx); From c656a3b75c31b9eaabf0908900e1f8e5dba2b76d Mon Sep 17 00:00:00 2001 From: Jiawen Wu Date: Mon, 3 Aug 2026 14:43:32 +0800 Subject: [PATCH 1138/1433] net: wangxun: add reinit parameter to wx->do_reset callback To implement a simple hardware reset without tearing down the network interface state, introduce a boolean 'reinit' parameter to wx->do_reset callback. Signed-off-by: Jiawen Wu Reviewed-by: Aleksandr Loktionov Link: https://patch.msgid.link/20260803064334.21876-4-jiawenwu@trustnetic.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/wangxun/libwx/wx_err.c | 2 +- drivers/net/ethernet/wangxun/libwx/wx_ethtool.c | 2 +- drivers/net/ethernet/wangxun/libwx/wx_lib.c | 4 ++-- drivers/net/ethernet/wangxun/libwx/wx_type.h | 2 +- drivers/net/ethernet/wangxun/ngbe/ngbe_main.c | 4 ++-- drivers/net/ethernet/wangxun/ngbe/ngbe_type.h | 2 +- drivers/net/ethernet/wangxun/txgbe/txgbe_main.c | 4 ++-- drivers/net/ethernet/wangxun/txgbe/txgbe_type.h | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/wangxun/libwx/wx_err.c b/drivers/net/ethernet/wangxun/libwx/wx_err.c index 4c45e97bf9e3..4c59a1110120 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_err.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_err.c @@ -16,7 +16,7 @@ static void wx_pf_reset_subtask(struct wx *wx) wx_warn(wx, "Reset adapter.\n"); if (wx->do_reset) - wx->do_reset(wx->netdev); + wx->do_reset(wx->netdev, true); } static void wx_reset_task(struct work_struct *work) diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c index 22037f015ded..940d2e59876c 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c @@ -397,7 +397,7 @@ static void wx_update_rsc(struct wx *wx) /* reset the device to apply the new RSC setting */ if (need_reset && wx->do_reset) - wx->do_reset(netdev); + wx->do_reset(netdev, true); } int wx_set_coalesce(struct net_device *netdev, diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.c b/drivers/net/ethernet/wangxun/libwx/wx_lib.c index 633ab5c61a05..67af8640a49e 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_lib.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.c @@ -3151,7 +3151,7 @@ int wx_set_features(struct net_device *netdev, netdev_features_t features) netdev->features = features; if (changed & NETIF_F_HW_VLAN_CTAG_RX && wx->do_reset) - wx->do_reset(netdev); + wx->do_reset(netdev, true); else if (changed & (NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_CTAG_FILTER)) wx_set_rx_mode(netdev); @@ -3201,7 +3201,7 @@ int wx_set_features(struct net_device *netdev, netdev_features_t features) out: if (need_reset && wx->do_reset) - wx->do_reset(netdev); + wx->do_reset(netdev, true); return 0; } diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h index f0ebc0acf0f3..158e8611f3d6 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_type.h +++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h @@ -1408,7 +1408,7 @@ struct wx { void (*atr)(struct wx_ring *ring, struct wx_tx_buffer *first, u8 ptype); void (*configure_fdir)(struct wx *wx); int (*setup_tc)(struct net_device *netdev, u8 tc); - void (*do_reset)(struct net_device *netdev); + void (*do_reset)(struct net_device *netdev, bool reinit); int (*ptp_setup_sdp)(struct wx *wx); void (*set_num_queues)(struct wx *wx); diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c index 7c3a9da493d8..adf7bb32d938 100644 --- a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c +++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c @@ -633,11 +633,11 @@ static void ngbe_reinit_locked(struct wx *wx) mutex_unlock(&wx->reset_lock); } -void ngbe_do_reset(struct net_device *netdev) +void ngbe_do_reset(struct net_device *netdev, bool reinit) { struct wx *wx = netdev_priv(netdev); - if (netif_running(netdev)) + if (netif_running(netdev) && reinit) ngbe_reinit_locked(wx); else ngbe_reset(wx); diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h b/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h index 4f648f272c08..c9233dc7ae50 100644 --- a/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h +++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h @@ -125,6 +125,6 @@ extern char ngbe_driver_name[]; void ngbe_down(struct wx *wx); void ngbe_up(struct wx *wx); int ngbe_setup_tc(struct net_device *dev, u8 tc); -void ngbe_do_reset(struct net_device *netdev); +void ngbe_do_reset(struct net_device *netdev, bool reinit); #endif /* _NGBE_TYPE_H_ */ diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c index 9fac2369a55c..f25249ddf2c6 100644 --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c @@ -610,11 +610,11 @@ static void txgbe_reinit_locked(struct wx *wx) mutex_unlock(&wx->reset_lock); } -void txgbe_do_reset(struct net_device *netdev) +void txgbe_do_reset(struct net_device *netdev, bool reinit) { struct wx *wx = netdev_priv(netdev); - if (netif_running(netdev)) + if (netif_running(netdev) && reinit) txgbe_reinit_locked(wx); else txgbe_reset(wx); diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h b/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h index 877234e3fdc2..3e93a3f309c1 100644 --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h @@ -313,7 +313,7 @@ extern char txgbe_driver_name[]; void txgbe_down(struct wx *wx); void txgbe_up(struct wx *wx); int txgbe_setup_tc(struct net_device *dev, u8 tc); -void txgbe_do_reset(struct net_device *netdev); +void txgbe_do_reset(struct net_device *netdev, bool reinit); #define DECLARE_PHY_INTERFACE_MASK_ZERO(name) \ unsigned long name[PHY_INTERFACE_MODE_MAX] = { 0, } From c023e9769de94cb7b7897297e71f50b0f436c473 Mon Sep 17 00:00:00 2001 From: Jiawen Wu Date: Mon, 3 Aug 2026 14:43:33 +0800 Subject: [PATCH 1139/1433] net: wangxun: implement soft quiesce for PCIe error recovery Function wx_soft_quiesce() provide a lightweight shutdown path during PCIe error recovery. It avoids MMIO-dependent operations in PCIe error status. Waiting for the service task to complete may unnecessarily delay PCIe error recovery, especially if the work item is already blocked by the hardware failure that triggered AER. So the service task is not explicitly cancelled in quiesce path. As a measure to block the service task, the checking of WX_STATE_DOWN and WX_STATE_RESETTING is added at the entry of relevant work item. Signed-off-by: Jiawen Wu Reviewed-by: Aleksandr Loktionov Link: https://patch.msgid.link/20260803064334.21876-5-jiawenwu@trustnetic.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/wangxun/libwx/wx_lib.c | 18 +++++++++++++ drivers/net/ethernet/wangxun/libwx/wx_lib.h | 1 + drivers/net/ethernet/wangxun/libwx/wx_ptp.c | 27 +++++++++++++++++++ drivers/net/ethernet/wangxun/libwx/wx_ptp.h | 1 + .../net/ethernet/wangxun/txgbe/txgbe_main.c | 16 +++++++++++ 5 files changed, 63 insertions(+) diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.c b/drivers/net/ethernet/wangxun/libwx/wx_lib.c index 67af8640a49e..ed5aad7857bd 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_lib.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.c @@ -3410,5 +3410,23 @@ void wx_service_timer(struct timer_list *t) } EXPORT_SYMBOL(wx_service_timer); +void wx_soft_quiesce(struct wx *wx) +{ + if (!netif_running(wx->netdev) || + test_and_set_bit(WX_STATE_DOWN, wx->state)) + return; + + pci_clear_master(wx->pdev); + netif_tx_stop_all_queues(wx->netdev); + netif_carrier_off(wx->netdev); + netif_tx_disable(wx->netdev); + wx_napi_disable_all(wx); + wx_ptp_quiesce(wx); + + clear_bit(WX_FLAG_NEED_DO_RESET, wx->flags); + timer_delete_sync(&wx->service_timer); +} +EXPORT_SYMBOL(wx_soft_quiesce); + MODULE_DESCRIPTION("Common library for Wangxun(R) Ethernet drivers."); MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.h b/drivers/net/ethernet/wangxun/libwx/wx_lib.h index bc671786978e..9d85d399e17f 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_lib.h +++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.h @@ -41,5 +41,6 @@ int wx_set_ring(struct wx *wx, u32 new_tx_count, void wx_service_event_schedule(struct wx *wx); void wx_service_event_complete(struct wx *wx); void wx_service_timer(struct timer_list *t); +void wx_soft_quiesce(struct wx *wx); #endif /* _WX_LIB_H_ */ diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c index 44f3e6505246..3eea647c4742 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c @@ -321,6 +321,9 @@ static long wx_ptp_do_aux_work(struct ptp_clock_info *ptp) struct wx *wx = container_of(ptp, struct wx, ptp_caps); int ts_done; + if (!test_bit(WX_STATE_PTP_RUNNING, wx->state)) + return HZ; + ts_done = wx_ptp_tx_hwtstamp_work(wx); wx_ptp_overflow_check(wx); @@ -842,6 +845,30 @@ void wx_ptp_stop(struct wx *wx) } EXPORT_SYMBOL(wx_ptp_stop); +void wx_ptp_quiesce(struct wx *wx) +{ + if (!test_and_clear_bit(WX_STATE_PTP_RUNNING, wx->state)) + return; + + clear_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags); + + if (wx->ptp_clock) + ptp_cancel_worker_sync(wx->ptp_clock); + + if (wx->ptp_tx_skb) { + dev_kfree_skb_any(wx->ptp_tx_skb); + wx->ptp_tx_skb = NULL; + } + clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); + + if (wx->ptp_clock) { + ptp_clock_unregister(wx->ptp_clock); + wx->ptp_clock = NULL; + dev_info(&wx->pdev->dev, "removed PHC on %s\n", wx->netdev->name); + } +} +EXPORT_SYMBOL(wx_ptp_quiesce); + /** * wx_ptp_rx_hwtstamp - utility function which checks for RX time stamp * @wx: pointer to wx struct diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.h b/drivers/net/ethernet/wangxun/libwx/wx_ptp.h index 50db90a6e3ee..ad2f824875d5 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_ptp.h +++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.h @@ -10,6 +10,7 @@ void wx_ptp_reset(struct wx *wx); void wx_ptp_init(struct wx *wx); void wx_ptp_suspend(struct wx *wx); void wx_ptp_stop(struct wx *wx); +void wx_ptp_quiesce(struct wx *wx); void wx_ptp_rx_hwtstamp(struct wx *wx, struct sk_buff *skb); int wx_hwtstamp_get(struct net_device *dev, struct kernel_hwtstamp_config *cfg); diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c index f25249ddf2c6..414b2ba8dfc4 100644 --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c @@ -94,12 +94,24 @@ static void txgbe_module_detection_subtask(struct wx *wx) { int err; + if (test_bit(WX_STATE_DOWN, wx->state) || + test_bit(WX_STATE_RESETTING, wx->state)) + return; + if (!test_and_clear_bit(WX_FLAG_NEED_MODULE_RESET, wx->flags)) return; /* wait for SFF module ready */ msleep(200); + /* Re-check state to avoid racing with down/reset paths. + * Module identification is deferred to the next up event, + * so it is safe to bail out here. + */ + if (test_bit(WX_STATE_DOWN, wx->state) || + test_bit(WX_STATE_RESETTING, wx->state)) + return; + err = txgbe_identify_module(wx); if (err == -ENODEV) set_bit(WX_FLAG_NEED_MODULE_RESET, wx->flags); @@ -107,6 +119,10 @@ static void txgbe_module_detection_subtask(struct wx *wx) static void txgbe_link_config_subtask(struct wx *wx) { + if (test_bit(WX_STATE_DOWN, wx->state) || + test_bit(WX_STATE_RESETTING, wx->state)) + return; + if (!test_and_clear_bit(WX_FLAG_NEED_LINK_CONFIG, wx->flags)) return; From e73e4d187a1f52ad5cbc10b2b8e07bd7863d7acf Mon Sep 17 00:00:00 2001 From: Jiawen Wu Date: Mon, 3 Aug 2026 14:43:34 +0800 Subject: [PATCH 1140/1433] net: wangxun: add pcie error handler Support AER driver to handle the PCIe errors. Sometimes netdev watchdog Tx timeout happens before the AER error report when a PCIe error occurs, CPU blocking would be caused by MMIO during the reset process. To prevent it, check PCIe error status in .ndo_tx_timeout. The current function of ngbe is not yet fully developed, it will be completed in the future. Signed-off-by: Jiawen Wu Link: https://patch.msgid.link/20260803064334.21876-6-jiawenwu@trustnetic.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/wangxun/libwx/wx_err.c | 159 +++++++++++++++++- drivers/net/ethernet/wangxun/libwx/wx_err.h | 2 + drivers/net/ethernet/wangxun/libwx/wx_type.h | 4 + drivers/net/ethernet/wangxun/ngbe/ngbe_main.c | 32 +++- .../net/ethernet/wangxun/txgbe/txgbe_main.c | 31 +++- 5 files changed, 223 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/wangxun/libwx/wx_err.c b/drivers/net/ethernet/wangxun/libwx/wx_err.c index 4c59a1110120..b56fbdc959de 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_err.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_err.c @@ -4,11 +4,136 @@ #include #include +#include #include "wx_type.h" #include "wx_lib.h" #include "wx_err.h" +/** + * wx_io_error_detected - called when PCI error is detected + * @pdev: Pointer to PCI device + * @state: The current pci connection state + * + * Return: pci_ers_result_t. + * + * This function is called after a PCI bus error affecting + * this device has been detected. + */ +static pci_ers_result_t wx_io_error_detected(struct pci_dev *pdev, + pci_channel_state_t state) +{ + struct wx *wx = pci_get_drvdata(pdev); + struct net_device *netdev; + + if (!wx) + return PCI_ERS_RESULT_DISCONNECT; + + netdev = wx->netdev; + if (!netif_device_present(netdev)) + return PCI_ERS_RESULT_DISCONNECT; + + rtnl_lock(); + netif_device_detach(netdev); + set_bit(WX_FLAG_NEED_PCIE_RECOVERY, wx->flags); + wx_soft_quiesce(wx); + + if (state == pci_channel_io_perm_failure) { + rtnl_unlock(); + return PCI_ERS_RESULT_DISCONNECT; + } + + if (!test_and_set_bit(WX_STATE_DISABLED, wx->state)) + pci_disable_device(pdev); + rtnl_unlock(); + + /* Request a slot reset. */ + return PCI_ERS_RESULT_NEED_RESET; +} + +/** + * wx_io_slot_reset - called after the pci bus has been reset. + * @pdev: Pointer to PCI device + * + * Return: pci_ers_result_t. + * + * Restart the card from scratch, as if from a cold-boot. + */ +static pci_ers_result_t wx_io_slot_reset(struct pci_dev *pdev) +{ + struct wx *wx = pci_get_drvdata(pdev); + + if (pci_enable_device_mem(pdev)) { + wx_err(wx, "Cannot re-enable PCI device after reset.\n"); + return PCI_ERS_RESULT_DISCONNECT; + } + + /* make all memory operations done before clearing the flag */ + smp_mb__before_atomic(); + clear_bit(WX_STATE_DISABLED, wx->state); + clear_bit(WX_FLAG_NEED_PCIE_RECOVERY, wx->flags); + pci_set_master(pdev); + pci_restore_state(pdev); + pci_wake_from_d3(pdev, false); + + rtnl_lock(); + if (netif_running(wx->netdev) && wx->down_suspend) + wx->down_suspend(wx); + if (wx->do_reset) + wx->do_reset(wx->netdev, false); + rtnl_unlock(); + + return PCI_ERS_RESULT_RECOVERED; +} + +/** + * wx_io_resume - called when traffic can start flowing again. + * @pdev: Pointer to PCI device + * + * This callback is called when the error recovery driver tells us that + * its OK to resume normal operation. + */ +static void wx_io_resume(struct pci_dev *pdev) +{ + struct wx *wx = pci_get_drvdata(pdev); + struct net_device *netdev; + int err; + + netdev = wx->netdev; + rtnl_lock(); + if (netif_running(netdev)) { + err = netdev->netdev_ops->ndo_open(netdev); + if (err) { + wx_err(wx, "Failed to open netdev after reset\n"); + goto out; + } + } + netif_device_attach(netdev); +out: + rtnl_unlock(); +} + +const struct pci_error_handlers wx_err_handler = { + .error_detected = wx_io_error_detected, + .slot_reset = wx_io_slot_reset, + .resume = wx_io_resume, +}; +EXPORT_SYMBOL(wx_err_handler); + +static bool wx_check_pcie_error(struct wx *wx) +{ + u16 vid, pci_cmd; + + pci_read_config_word(wx->pdev, PCI_VENDOR_ID, &vid); + pci_read_config_word(wx->pdev, PCI_COMMAND, &pci_cmd); + + /* PCIe link loss or memory space can't access */ + if (vid == U16_MAX || !(pci_cmd & PCI_COMMAND_MEMORY)) + return true; + + return false; +} + static void wx_pf_reset_subtask(struct wx *wx) { if (!test_and_clear_bit(WX_FLAG_NEED_DO_RESET, wx->flags)) @@ -25,6 +150,22 @@ static void wx_reset_task(struct work_struct *work) rtnl_lock(); + /* If the device has been detached (e.g., due to AER error handling), + * abort the reset task to prevent operating on a dead or unmanaged + * hardware. + */ + if (!netif_device_present(wx->netdev)) + goto out; + + if (test_bit(WX_FLAG_NEED_PCIE_RECOVERY, wx->flags)) { + /* Double check: Verify if the PCIe error is still present. */ + if (wx_check_pcie_error(wx)) + wx_soft_quiesce(wx); + else + clear_bit(WX_FLAG_NEED_PCIE_RECOVERY, wx->flags); + goto out; + } + if (test_bit(WX_STATE_DOWN, wx->state) || test_bit(WX_STATE_RESETTING, wx->state)) goto out; @@ -139,6 +280,19 @@ void wx_check_hang_subtask(struct wx *wx) } EXPORT_SYMBOL(wx_check_hang_subtask); +static void wx_tx_timeout_recovery(struct wx *wx) +{ + /* + * When a PCIe hardware error occurs, the driver should initiate a PCIe + * recovery mechanism. However, this recovery flow relies on the AER + * driver for current kernel policy. Therefore, a self-contained + * recovery mechanism is not implemented yet. + */ + set_bit(WX_FLAG_NEED_PCIE_RECOVERY, wx->flags); + wx_err(wx, "PCIe error detected during tx timeout\n"); + queue_work(wx->reset_wq, &wx->reset_task); +} + static void wx_tx_timeout_reset(struct wx *wx) { if (test_bit(WX_STATE_DOWN, wx->state)) @@ -153,7 +307,10 @@ void wx_tx_timeout(struct net_device *netdev, unsigned int __always_unused txque { struct wx *wx = netdev_priv(netdev); - wx_tx_timeout_reset(wx); + if (wx_check_pcie_error(wx)) + wx_tx_timeout_recovery(wx); + else + wx_tx_timeout_reset(wx); } EXPORT_SYMBOL(wx_tx_timeout); diff --git a/drivers/net/ethernet/wangxun/libwx/wx_err.h b/drivers/net/ethernet/wangxun/libwx/wx_err.h index 1eed13e48095..a6a82a263528 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_err.h +++ b/drivers/net/ethernet/wangxun/libwx/wx_err.h @@ -7,6 +7,8 @@ #ifndef _WX_ERR_H_ #define _WX_ERR_H_ +extern const struct pci_error_handlers wx_err_handler; + void wx_check_err_subtask(struct wx *wx); int wx_init_err_task(struct wx *wx); void wx_check_hang_subtask(struct wx *wx); diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h index 158e8611f3d6..2eba5ab59925 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_type.h +++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h @@ -1222,6 +1222,8 @@ enum wx_state { WX_STATE_PTP_RUNNING, WX_STATE_PTP_TX_IN_PROGRESS, WX_STATE_SERVICE_SCHED, + WX_STATE_DISABLED, + WX_STATE_RES_FREED, WX_STATE_NBITS /* must be last */ }; @@ -1288,6 +1290,7 @@ enum wx_pf_flags { WX_FLAG_NEED_DO_RESET, WX_FLAG_RX_MERGE_ENABLED, WX_FLAG_TXHEAD_WB_ENABLED, + WX_FLAG_NEED_PCIE_RECOVERY, WX_PF_FLAGS_NBITS /* must be last */ }; @@ -1409,6 +1412,7 @@ struct wx { void (*configure_fdir)(struct wx *wx); int (*setup_tc)(struct net_device *netdev, u8 tc); void (*do_reset)(struct net_device *netdev, bool reinit); + void (*down_suspend)(struct wx *wx); int (*ptp_setup_sdp)(struct wx *wx); void (*set_num_queues)(struct wx *wx); diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c index adf7bb32d938..0e4db5875a63 100644 --- a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c +++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c @@ -47,6 +47,20 @@ static const struct pci_device_id ngbe_pci_tbl[] = { { } }; +static void ngbe_down_suspend(struct wx *wx) +{ + if (test_and_set_bit(WX_STATE_RES_FREED, wx->state)) + return; + + phylink_stop(wx->phylink); + phylink_disconnect_phy(wx->phylink); + wx_clean_all_tx_rings(wx); + wx_clean_all_rx_rings(wx); + wx_free_irq(wx); + wx_free_isb_resources(wx); + wx_free_resources(wx); +} + /** * ngbe_init_type_code - Initialize the shared code * @wx: pointer to hardware structure @@ -135,6 +149,7 @@ static int ngbe_sw_init(struct wx *wx) wx->mbx.size = WX_VXMAILBOX_SIZE; wx->setup_tc = ngbe_setup_tc; wx->do_reset = ngbe_do_reset; + wx->down_suspend = ngbe_down_suspend; set_bit(0, &wx->fwd_bitmask); return 0; @@ -413,6 +428,9 @@ static void ngbe_disable_device(struct wx *wx) static void ngbe_reset(struct wx *wx) { + if (test_bit(WX_FLAG_NEED_PCIE_RECOVERY, wx->flags)) + return; + wx_flush_sw_mac_table(wx); wx_mac_set_default_filter(wx, wx->mac.addr); if (test_bit(WX_STATE_PTP_RUNNING, wx->state)) @@ -435,6 +453,7 @@ static void ngbe_up_complete(struct wx *wx) /* make sure to complete pre-operations */ smp_mb__before_atomic(); clear_bit(WX_STATE_DOWN, wx->state); + clear_bit(WX_STATE_RES_FREED, wx->state); wx_napi_enable_all(wx); /* enable transmits */ netif_tx_start_all_queues(wx->netdev); @@ -529,12 +548,16 @@ static int ngbe_close(struct net_device *netdev) { struct wx *wx = netdev_priv(netdev); + if (test_bit(WX_STATE_RES_FREED, wx->state)) + goto out; + wx_ptp_stop(wx); ngbe_down(wx); wx_free_irq(wx); wx_free_isb_resources(wx); wx_free_resources(wx); phylink_disconnect_phy(wx->phylink); +out: wx_control_hw(wx, false); return 0; @@ -566,7 +589,8 @@ static void ngbe_dev_shutdown(struct pci_dev *pdev, bool *enable_wake) *enable_wake = !!wufc; wx_control_hw(wx, false); - pci_disable_device(pdev); + if (!test_and_set_bit(WX_STATE_DISABLED, wx->state)) + pci_disable_device(pdev); } static void ngbe_shutdown(struct pci_dev *pdev) @@ -854,6 +878,7 @@ static int ngbe_probe(struct pci_dev *pdev, goto err_register; pci_set_drvdata(pdev, wx); + pci_save_state(pdev); return 0; @@ -909,7 +934,8 @@ static void ngbe_remove(struct pci_dev *pdev) kfree(wx->mac_table); wx_clear_interrupt_scheme(wx); - pci_disable_device(pdev); + if (!test_and_set_bit(WX_STATE_DISABLED, wx->state)) + pci_disable_device(pdev); } static int ngbe_suspend(struct pci_dev *pdev, pm_message_t state) @@ -936,6 +962,7 @@ static int ngbe_resume(struct pci_dev *pdev) wx_err(wx, "Cannot enable PCI device from suspend\n"); return err; } + clear_bit(WX_STATE_DISABLED, wx->state); pci_set_master(pdev); device_wakeup_disable(&pdev->dev); @@ -960,6 +987,7 @@ static struct pci_driver ngbe_driver = { .resume = ngbe_resume, .shutdown = ngbe_shutdown, .sriov_configure = wx_pci_sriov_configure, + .err_handler = &wx_err_handler, }; module_pci_driver(ngbe_driver); diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c index 414b2ba8dfc4..eb91c4f28ecd 100644 --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c @@ -163,6 +163,7 @@ static void txgbe_up_complete(struct wx *wx) /* make sure to complete pre-operations */ smp_mb__before_atomic(); clear_bit(WX_STATE_DOWN, wx->state); + clear_bit(WX_STATE_RES_FREED, wx->state); wx_napi_enable_all(wx); switch (wx->mac.type) { @@ -206,6 +207,9 @@ static void txgbe_reset(struct wx *wx) u8 old_addr[ETH_ALEN]; int err; + if (test_bit(WX_FLAG_NEED_PCIE_RECOVERY, wx->flags)) + return; + err = txgbe_reset_hw(wx); if (err != 0) wx_err(wx, "Hardware Error: %d\n", err); @@ -312,6 +316,20 @@ void txgbe_up(struct wx *wx) txgbe_up_complete(wx); } +static void txgbe_down_suspend(struct wx *wx) +{ + if (test_and_set_bit(WX_STATE_RES_FREED, wx->state)) + return; + + phylink_stop(wx->phylink); + wx_clean_all_tx_rings(wx); + wx_clean_all_rx_rings(wx); + wx_free_irq(wx); + txgbe_free_misc_irq(wx->priv); + wx_free_resources(wx); + txgbe_fdir_filter_exit(wx); +} + /** * txgbe_init_type_code - Initialize the shared code * @wx: pointer to hardware structure @@ -428,6 +446,7 @@ static int txgbe_sw_init(struct wx *wx) wx->setup_tc = txgbe_setup_tc; wx->do_reset = txgbe_do_reset; + wx->down_suspend = txgbe_down_suspend; set_bit(0, &wx->fwd_bitmask); switch (wx->mac.type) { @@ -538,12 +557,16 @@ static int txgbe_close(struct net_device *netdev) { struct wx *wx = netdev_priv(netdev); + if (test_bit(WX_STATE_RES_FREED, wx->state)) + goto out; + wx_ptp_stop(wx); txgbe_down(wx); wx_free_irq(wx); txgbe_free_misc_irq(wx->priv); wx_free_resources(wx); txgbe_fdir_filter_exit(wx); +out: wx_control_hw(wx, false); return 0; @@ -564,7 +587,8 @@ static void txgbe_dev_shutdown(struct pci_dev *pdev) wx_control_hw(wx, false); - pci_disable_device(pdev); + if (!test_and_set_bit(WX_STATE_DISABLED, wx->state)) + pci_disable_device(pdev); } static void txgbe_shutdown(struct pci_dev *pdev) @@ -914,6 +938,7 @@ static int txgbe_probe(struct pci_dev *pdev, goto err_remove_phy; pci_set_drvdata(pdev, wx); + pci_save_state(pdev); netif_tx_stop_all_queues(netdev); @@ -989,7 +1014,8 @@ static void txgbe_remove(struct pci_dev *pdev) kfree(wx->mac_table); wx_clear_interrupt_scheme(wx); - pci_disable_device(pdev); + if (!test_and_set_bit(WX_STATE_DISABLED, wx->state)) + pci_disable_device(pdev); } static struct pci_driver txgbe_driver = { @@ -999,6 +1025,7 @@ static struct pci_driver txgbe_driver = { .remove = txgbe_remove, .shutdown = txgbe_shutdown, .sriov_configure = wx_pci_sriov_configure, + .err_handler = &wx_err_handler, }; module_pci_driver(txgbe_driver); From 1dd5bc0b9a07417ca9ec15d22f20708226eca793 Mon Sep 17 00:00:00 2001 From: Jiri Pirko Date: Thu, 6 Aug 2026 11:44:32 +0200 Subject: [PATCH 1141/1433] MAINTAINERS: add Ivan Vecera as DPLL reviewer Ivan has been continuously active in DPLL development and discussion since April 2025. His contributions cover the DPLL core and API, netlink, bindings, ICE/SyncE integration, and the ZL3073x driver. He also regularly reviews and tests DPLL patches from other contributors and already maintains the Microchip ZL3073x driver. Add him as a reviewer to reflect his ongoing involvement across the subsystem. Signed-off-by: Jiri Pirko Acked-by: Vadim Fedorenko Acked-by: Arkadiusz Kubalewski Link: https://patch.msgid.link/20260806094432.163833-1-jiri@resnulli.us Signed-off-by: Jakub Kicinski --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 891c064a881b..a82b6ec9c567 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7862,6 +7862,7 @@ DPLL SUBSYSTEM M: Vadim Fedorenko M: Arkadiusz Kubalewski M: Jiri Pirko +R: Ivan Vecera L: netdev@vger.kernel.org S: Supported F: Documentation/devicetree/bindings/dpll/dpll-device.yaml From edabb0da7387c46f9a21d98f0cd0489fe6182668 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 6 Aug 2026 17:19:18 -0700 Subject: [PATCH 1142/1433] tools/ynl: add ovs_packet uapi header in Makefile.deps ovs_packet spec needs to fetch the right uAPI header, like other ovs specs already do. Otherwise build breaks on very old distros (Ubuntu 22.04). Spec was added by commit b82bfddc46e2 ("netlink: specs: add OVS packet family specification"). Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260807001918.61957-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- tools/net/ynl/Makefile.deps | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/net/ynl/Makefile.deps b/tools/net/ynl/Makefile.deps index 43d06ecbae93..2771375339d9 100644 --- a/tools/net/ynl/Makefile.deps +++ b/tools/net/ynl/Makefile.deps @@ -35,6 +35,7 @@ CFLAGS_nfsd:=$(call get_hdr_inc,_LINUX_NFSD_NETLINK_H,nfsd_netlink.h) CFLAGS_ovpn:=$(call get_hdr_inc,_LINUX_OVPN_H,ovpn.h) CFLAGS_ovs_datapath:=$(call get_hdr_inc,__LINUX_OPENVSWITCH_H,openvswitch.h) CFLAGS_ovs_flow:=$(call get_hdr_inc,__LINUX_OPENVSWITCH_H,openvswitch.h) +CFLAGS_ovs_packet:=$(call get_hdr_inc,__LINUX_OPENVSWITCH_H,openvswitch.h) CFLAGS_ovs_vport:=$(call get_hdr_inc,__LINUX_OPENVSWITCH_H,openvswitch.h) CFLAGS_psp:=$(call get_hdr_inc,_LINUX_PSP_H,psp.h) CFLAGS_rt-addr:=$(call get_hdr_inc,__LINUX_RTNETLINK_H,rtnetlink.h) \ From 55d20f50a221bdd7b19befc6e3685a2ef5d4c07b Mon Sep 17 00:00:00 2001 From: Haiyang Zhang Date: Wed, 5 Aug 2026 11:53:51 -0700 Subject: [PATCH 1143/1433] net: mana: Extend RX CQE coalescing up to 8 packets To support up to 8 packets per CQE, update related CQE processing code and structures. Update ethtool handlers to set this feature. Update per queue stat to show the coalesced CQE counters. This feature is supported on NIC hardware showing the relevant PF flag. Signed-off-by: Haiyang Zhang Reviewed-by: Simon Horman Reviewed-by: Breno Leitao Link: https://patch.msgid.link/20260805185404.1052177-1-haiyangz@linux.microsoft.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microsoft/mana/mana_en.c | 139 ++++++++++++------ .../ethernet/microsoft/mana/mana_ethtool.c | 29 +++- include/net/mana/gdma.h | 4 + include/net/mana/mana.h | 44 ++++-- 4 files changed, 151 insertions(+), 65 deletions(-) diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index 2519a98ad00b..3c96e6fc3d81 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -1259,6 +1259,9 @@ int mana_gd_query_device_cfg(struct gdma_context *gc, u32 proto_major_ver, return err; } + gc->cqe8_coalescing_sup = !!(resp.pf_cap_flags1 & + MANA_PF_FLAG_1_CQE_8_COALESCING_SUPPORTED); + *max_num_vports = resp.max_num_vports; if (resp.hdr.response.msg_version >= GDMA_MESSAGE_V2) { @@ -1444,7 +1447,11 @@ static int mana_cfg_vport_steering(struct mana_port_context *apc, mana_gd_init_req_hdr(&req->hdr, MANA_CONFIG_VPORT_RX, req_buf_size, sizeof(resp)); - req->hdr.req.msg_version = GDMA_MESSAGE_V2; + /* Request & response versions can be different. + * HW can handle newer msg versions, by skipping + * new fields. + */ + req->hdr.req.msg_version = GDMA_MESSAGE_V5; req->hdr.resp.msg_version = GDMA_MESSAGE_V2; req->vport = apc->port_handle; @@ -1458,8 +1465,13 @@ static int mana_cfg_vport_steering(struct mana_port_context *apc, req->update_indir_tab = update_tab; req->default_rxobj = apc->default_rxobj; - if (rx != TRI_STATE_FALSE) + /* Request-msg v5 requires this field */ + req->rss_hash_types = MANA_HASH_ENABLE_SUPPORTED; + + if (rx != TRI_STATE_FALSE) { req->cqe_coalescing_enable = apc->cqe_coalescing_enable; + req->cqe8_coalescing_enable = apc->cqe8_coalescing_enable; + } if (update_key) memcpy(&req->hashkey, apc->hashkey, MANA_HASH_KEY_SIZE); @@ -2064,16 +2076,14 @@ static struct sk_buff *mana_build_skb(struct mana_rxq *rxq, void *buf_va, static void mana_rx_skb(void *buf_va, bool from_pool, struct mana_rxcomp_oob *cqe, struct mana_rxq *rxq, - int i) + u32 pkt_len, u32 pkt_hash) { struct mana_stats_rx *rx_stats = &rxq->stats; struct net_device *ndev = rxq->ndev; - uint pkt_len = cqe->ppi[i].pkt_len; u16 rxq_idx = rxq->rxq_idx; struct napi_struct *napi; struct xdp_buff xdp = {}; struct sk_buff *skb; - u32 hash_value; u32 act; rxq->rx_cq.work_done++; @@ -2112,12 +2122,10 @@ static void mana_rx_skb(void *buf_va, bool from_pool, } if (cqe->rx_hashtype != 0 && (ndev->features & NETIF_F_RXHASH)) { - hash_value = cqe->ppi[i].pkt_hash; - if (cqe->rx_hashtype & MANA_HASH_L4) - skb_set_hash(skb, hash_value, PKT_HASH_TYPE_L4); + skb_set_hash(skb, pkt_hash, PKT_HASH_TYPE_L4); else - skb_set_hash(skb, hash_value, PKT_HASH_TYPE_L3); + skb_set_hash(skb, pkt_hash, PKT_HASH_TYPE_L3); } if (cqe->rx_vlantag_present) { @@ -2258,6 +2266,43 @@ static void mana_refill_rx_oob(struct device *dev, struct mana_rxq *rxq, rxoob->dma_sync_offset = dma_sync_offset; } +static void mana_process_one_rx_pkt(struct device *dev, struct mana_rxq *rxq, + struct mana_rxcomp_oob *oob, + u32 pktlen, u32 pkt_hash) +{ + struct mana_recv_buf_oob *rxbuf_oob; + struct net_device *ndev = rxq->ndev; + void *old_buf = NULL; + bool old_fp; + + rxbuf_oob = &rxq->rx_oobs[rxq->buf_index]; + WARN_ON_ONCE(rxbuf_oob->wqe_inf.wqe_size_in_bu != 1); + + if (unlikely(pktlen > rxq->datasize)) { + /* Increase it even if mana_rx_skb() isn't called. */ + rxq->rx_cq.work_done++; + + ++ndev->stats.rx_dropped; + netdev_warn_once(ndev, + "Dropped oversized RX packet: len=%u, datasize=%u\n", + pktlen, rxq->datasize); + + /* Reuse the RX buffer since rxbuf_oob is unchanged. */ + } else { + mana_refill_rx_oob(dev, rxq, rxbuf_oob, pktlen, + &old_buf, &old_fp); + + /* Unsuccessful refill will have old_buf == NULL. + * In this case, mana_rx_skb() will drop the packet. + */ + mana_rx_skb(old_buf, old_fp, oob, rxq, pktlen, pkt_hash); + } + + mana_move_wq_tail(rxq->gdma_rq, rxbuf_oob->wqe_inf.wqe_size_in_bu); + + mana_post_pkt_rxq(rxq); +} + static void mana_process_rx_cqe(struct mana_rxq *rxq, struct mana_cq *cq, struct gdma_comp *cqe) { @@ -2267,10 +2312,10 @@ static void mana_process_rx_cqe(struct mana_rxq *rxq, struct mana_cq *cq, struct mana_recv_buf_oob *rxbuf_oob; struct mana_port_context *apc; struct device *dev = gc->dev; + bool coalesced_8 = false; bool coalesced = false; - void *old_buf = NULL; - u32 curr, pktlen; - bool old_fp; + u32 pktlen; + int pkt_i; int i; apc = netdev_priv(ndev); @@ -2293,6 +2338,11 @@ static void mana_process_rx_cqe(struct mana_rxq *rxq, struct mana_cq *cq, coalesced = true; break; + case CQE_RX_COALESCED_8: + coalesced = true; + coalesced_8 = true; + break; + case CQE_RX_OBJECT_FENCE: complete(&rxq->fence_event); return; @@ -2304,54 +2354,48 @@ static void mana_process_rx_cqe(struct mana_rxq *rxq, struct mana_cq *cq, return; } + pkt_i = 0; for (i = 0; i < MANA_RXCOMP_OOB_NUM_PPI; i++) { - old_buf = NULL; - pktlen = oob->ppi[i].pkt_len; + u32 pkt_hash; + + if (coalesced_8) { + /* 8-pkt mode: 2 packets per PPI entry */ + pktlen = oob->ppi[i].pkt_len0; + pkt_hash = oob->ppi[i].pkt_hash0; + } else { + pktlen = oob->ppi[i].pkt_len; + pkt_hash = oob->ppi[i].pkt_hash; + } if (pktlen == 0) break; - curr = rxq->buf_index; - rxbuf_oob = &rxq->rx_oobs[curr]; - WARN_ON_ONCE(rxbuf_oob->wqe_inf.wqe_size_in_bu != 1); - - if (unlikely(pktlen > rxq->datasize)) { - /* Increase it even if mana_rx_skb() isn't called. */ - rxq->rx_cq.work_done++; - - ++ndev->stats.rx_dropped; - netdev_warn_once(ndev, - "Dropped oversized RX packet: len=%u, datasize=%u\n", - pktlen, rxq->datasize); - - /* Reuse the RX buffer since rxbuf_oob is unchanged. */ - } else { - - mana_refill_rx_oob(dev, rxq, rxbuf_oob, pktlen, - &old_buf, &old_fp); - - /* Unsuccessful refill will have old_buf == NULL. - * In this case, mana_rx_skb() will drop the packet. - */ - mana_rx_skb(old_buf, old_fp, oob, rxq, i); - } - - mana_move_wq_tail(rxq->gdma_rq, - rxbuf_oob->wqe_inf.wqe_size_in_bu); - - mana_post_pkt_rxq(rxq); + mana_process_one_rx_pkt(dev, rxq, oob, pktlen, pkt_hash); + pkt_i++; if (!coalesced) break; + + /* Process 2nd packet from the same PPI in 8-pkt mode */ + if (coalesced_8) { + pktlen = oob->ppi[i].pkt_len1; + pkt_hash = oob->ppi[i].pkt_hash1; + if (pktlen == 0) + break; + + mana_process_one_rx_pkt(dev, rxq, oob, pktlen, + pkt_hash); + pkt_i++; + } } /* Collect coalesced CQE count based on packets processed. - * Coalesced CQEs have at least 2 packets, so index is i - 2. + * Coalesced CQEs have at least 2 packets, so index is pkt_i - 2. */ - if (i > 1) { + if (pkt_i > 1) { u64_stats_update_begin(&rxq->stats.syncp); - rxq->stats.coalesced_cqe[i - 2]++; + rxq->stats.coalesced_cqe[pkt_i - 2]++; u64_stats_update_end(&rxq->stats.syncp); - } else if (!i && !pktlen) { + } else if (!pkt_i && !pktlen) { u64_stats_update_begin(&rxq->stats.syncp); rxq->stats.pkt_len0_err++; u64_stats_update_end(&rxq->stats.syncp); @@ -3781,6 +3825,7 @@ static int mana_probe_port(struct mana_context *ac, int port_idx, apc->port_idx = port_idx; apc->link_cfg_error = 1; apc->cqe_coalescing_enable = 0; + apc->cqe8_coalescing_enable = 0; /* Initialize interrupt moderation settings if supported by HW */ if (gc->pf_cap_flags1 & GDMA_PF_CAP_FLAG_1_DYN_INTERRUPT_MODERATION) { diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c index 7e441d6ae5dc..ece7ff9cc409 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c +++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c @@ -178,7 +178,7 @@ static void mana_get_strings_stats(struct mana_port_context *apc, u8 **data) ethtool_sprintf(data, "rx_%d_xdp_tx", i); ethtool_sprintf(data, "rx_%d_xdp_redirect", i); ethtool_sprintf(data, "rx_%d_pkt_len0_err", i); - for (j = 0; j < MANA_RXCOMP_OOB_NUM_PPI - 1; j++) + for (j = 0; j < MANA_CQE_COAL_PKTS_8 - 1; j++) ethtool_sprintf(data, "rx_%d_coalesced_cqe_%d", i, @@ -241,7 +241,7 @@ static void mana_get_ethtool_stats(struct net_device *ndev, u64 xdp_drop; u64 xdp_tx; u64 pkt_len0_err; - u64 coalesced_cqe[MANA_RXCOMP_OOB_NUM_PPI - 1]; + u64 coalesced_cqe[MANA_CQE_COAL_PKTS_8 - 1]; u64 tso_packets; u64 tso_bytes; u64 tso_inner_packets; @@ -281,7 +281,7 @@ static void mana_get_ethtool_stats(struct net_device *ndev, xdp_tx = rx_stats->xdp_tx; xdp_redirect = rx_stats->xdp_redirect; pkt_len0_err = rx_stats->pkt_len0_err; - for (j = 0; j < MANA_RXCOMP_OOB_NUM_PPI - 1; j++) + for (j = 0; j < MANA_CQE_COAL_PKTS_8 - 1; j++) coalesced_cqe[j] = rx_stats->coalesced_cqe[j]; } while (u64_stats_fetch_retry(&rx_stats->syncp, start)); @@ -291,7 +291,7 @@ static void mana_get_ethtool_stats(struct net_device *ndev, data[i++] = xdp_tx; data[i++] = xdp_redirect; data[i++] = pkt_len0_err; - for (j = 0; j < MANA_RXCOMP_OOB_NUM_PPI - 1; j++) + for (j = 0; j < MANA_CQE_COAL_PKTS_8 - 1; j++) data[i++] = coalesced_cqe[j]; } @@ -444,6 +444,7 @@ static int mana_get_coalesce(struct net_device *ndev, struct mana_port_context *apc = netdev_priv(ndev); kernel_coal->rx_cqe_frames = + apc->cqe8_coalescing_enable ? MANA_CQE_COAL_PKTS_8 : apc->cqe_coalescing_enable ? MANA_RXCOMP_OOB_NUM_PPI : 1; kernel_coal->rx_cqe_nsecs = apc->cqe_coalescing_timeout_ns; @@ -479,15 +480,19 @@ static int mana_set_coalesce(struct net_device *ndev, u16 intr_modr_tx_usec; u16 intr_modr_tx_comp; u8 cqe_coalescing_enable; + u8 cqe8_coalescing_enable; bool rx_dim_enabled; bool tx_dim_enabled; } saved; bool modr_changed = false; bool dim_changed = false; struct gdma_context *gc; + u32 max_cqe_frames; int err; gc = apc->ac->gdma_dev->gdma_context; + max_cqe_frames = gc->cqe8_coalescing_sup ? MANA_CQE_COAL_PKTS_8 : + MANA_RXCOMP_OOB_NUM_PPI; /* Both static and dynamic interrupt moderation (DIM) rely on the * same HW capability advertised by the PF. @@ -502,10 +507,12 @@ static int mana_set_coalesce(struct net_device *ndev, } if (kernel_coal->rx_cqe_frames != 1 && - kernel_coal->rx_cqe_frames != MANA_RXCOMP_OOB_NUM_PPI) { + kernel_coal->rx_cqe_frames != MANA_RXCOMP_OOB_NUM_PPI && + kernel_coal->rx_cqe_frames != max_cqe_frames) { NL_SET_ERR_MSG_FMT(extack, - "rx-frames must be 1 or %u, got %u", + "rx-frames must be 1 or %u%s, got %u", MANA_RXCOMP_OOB_NUM_PPI, + gc->cqe8_coalescing_sup ? " or 8" : "", kernel_coal->rx_cqe_frames); return -EINVAL; } @@ -550,8 +557,11 @@ static int mana_set_coalesce(struct net_device *ndev, saved.tx_dim_enabled = apc->tx_dim_enabled; saved.cqe_coalescing_enable = apc->cqe_coalescing_enable; + saved.cqe8_coalescing_enable = apc->cqe8_coalescing_enable; apc->cqe_coalescing_enable = - kernel_coal->rx_cqe_frames == MANA_RXCOMP_OOB_NUM_PPI; + kernel_coal->rx_cqe_frames >= MANA_RXCOMP_OOB_NUM_PPI; + apc->cqe8_coalescing_enable = + kernel_coal->rx_cqe_frames == MANA_CQE_COAL_PKTS_8; if (!apc->port_is_up) { WRITE_ONCE(apc->rx_dim_enabled, !!ec->use_adaptive_rx_coalesce); @@ -559,7 +569,8 @@ static int mana_set_coalesce(struct net_device *ndev, return 0; } - if (apc->cqe_coalescing_enable != saved.cqe_coalescing_enable) { + if (apc->cqe_coalescing_enable != saved.cqe_coalescing_enable || + apc->cqe8_coalescing_enable != saved.cqe8_coalescing_enable) { /* CQE coalescing setting is applied via RSS configuration. */ err = mana_config_rss(apc, TRI_STATE_TRUE, false, false); if (err) { @@ -567,6 +578,8 @@ static int mana_set_coalesce(struct net_device *ndev, err); apc->cqe_coalescing_enable = saved.cqe_coalescing_enable; + apc->cqe8_coalescing_enable = + saved.cqe8_coalescing_enable; apc->intr_modr_rx_usec = saved.intr_modr_rx_usec; apc->intr_modr_rx_comp = saved.intr_modr_rx_comp; apc->intr_modr_tx_usec = saved.intr_modr_tx_usec; diff --git a/include/net/mana/gdma.h b/include/net/mana/gdma.h index 8529cef0d7c4..70a7f1fee5d3 100644 --- a/include/net/mana/gdma.h +++ b/include/net/mana/gdma.h @@ -182,6 +182,7 @@ struct gdma_general_req { #define GDMA_MESSAGE_V2 2 #define GDMA_MESSAGE_V3 3 #define GDMA_MESSAGE_V4 4 +#define GDMA_MESSAGE_V5 5 struct gdma_general_resp { struct gdma_resp_hdr hdr; @@ -428,6 +429,9 @@ struct gdma_context { /* L2 MTU */ u16 adapter_mtu; + /* NIC supports CQE x8 coalescing */ + bool cqe8_coalescing_sup; + /* This maps a CQ index to the queue structure. */ unsigned int max_num_cqs; struct gdma_queue **cq_table; diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h index 9ffbdff5746f..83b7eff4646e 100644 --- a/include/net/mana/mana.h +++ b/include/net/mana/mana.h @@ -68,9 +68,12 @@ enum mana_priv_flag_bits { #define MAX_PORTS_IN_MANA_DEV 256 -/* Maximum number of packets per coalesced CQE */ +/* Maximum number of PPIs per coalesced CQE */ #define MANA_RXCOMP_OOB_NUM_PPI 4 +/* 8-pkt mode packs up to 2 packets per PPI entry */ +#define MANA_CQE_COAL_PKTS_8 8 + /* Default/max interrupt moderation settings */ #define MANA_INTR_MODR_USEC_DEF 0 #define MANA_INTR_MODR_COMP_DEF 0 @@ -85,7 +88,7 @@ enum mana_priv_flag_bits { #define MANA_INTR_MODR_COMP_MASK GENMASK(23, 16) /* Update this count whenever the respective structures are changed */ -#define MANA_STATS_RX_COUNT (6 + MANA_RXCOMP_OOB_NUM_PPI - 1) +#define MANA_STATS_RX_COUNT (6 + MANA_CQE_COAL_PKTS_8 - 1) #define MANA_STATS_TX_COUNT 11 #define MANA_RX_FRAG_ALIGNMENT 64 @@ -97,7 +100,7 @@ struct mana_stats_rx { u64 xdp_tx; u64 xdp_redirect; u64 pkt_len0_err; - u64 coalesced_cqe[MANA_RXCOMP_OOB_NUM_PPI - 1]; + u64 coalesced_cqe[MANA_CQE_COAL_PKTS_8 - 1]; struct u64_stats_sync syncp; }; @@ -208,6 +211,7 @@ enum mana_cqe_type { CQE_RX_COALESCED_4 = 2, CQE_RX_OBJECT_FENCE = 3, CQE_RX_TRUNCATED = 4, + CQE_RX_COALESCED_8 = 7, CQE_TX_OKAY = 32, CQE_TX_SA_DROP = 33, @@ -244,12 +248,26 @@ struct mana_cqe_header { #define MANA_HASH_L4 \ (NDIS_HASH_TCP_IPV4 | NDIS_HASH_UDP_IPV4 | NDIS_HASH_TCP_IPV6 | \ NDIS_HASH_UDP_IPV6 | NDIS_HASH_TCP_IPV6_EX | NDIS_HASH_UDP_IPV6_EX) +#define MANA_HASH_ENABLE_SUPPORTED \ + (NDIS_HASH_IPV4 | NDIS_HASH_TCP_IPV4 | NDIS_HASH_UDP_IPV4 | \ + NDIS_HASH_IPV6 | NDIS_HASH_TCP_IPV6 | NDIS_HASH_UDP_IPV6) -struct mana_rxcomp_perpkt_info { - u32 pkt_len : 16; - u32 reserved1 : 16; - u32 reserved2; - u32 pkt_hash; +/* Read PPI in two different layouts based on cqe_type */ +union mana_rxcomp_perpkt_info { + struct { + u32 pkt_len : 16; + u32 reserved1 : 16; + u32 reserved2; + u32 pkt_hash; + }; + + /* Up to two pkts per PPI entry */ + struct { + u32 pkt_hash0; + u16 pkt_len0; + u16 pkt_len1; + u32 pkt_hash1; + }; }; /* HW DATA */ /* Receive completion OOB */ @@ -270,7 +288,7 @@ struct mana_rxcomp_oob { u32 rx_udp_csum_fail : 1; u32 reserved2 : 1; - struct mana_rxcomp_perpkt_info ppi[MANA_RXCOMP_OOB_NUM_PPI]; + union mana_rxcomp_perpkt_info ppi[MANA_RXCOMP_OOB_NUM_PPI]; u32 rx_wqe_offset; }; /* HW DATA */ @@ -615,6 +633,7 @@ struct mana_port_context { bool port_st_save; /* Saved port state */ u8 cqe_coalescing_enable; + u8 cqe8_coalescing_enable; u32 cqe_coalescing_timeout_ns; /* Interrupt moderation settings */ @@ -759,6 +778,8 @@ struct mana_query_device_cfg_req { u32 reserved; }; /* HW DATA */ +#define MANA_PF_FLAG_1_CQE_8_COALESCING_SUPPORTED BIT(5) + struct mana_query_device_cfg_resp { struct gdma_resp_hdr hdr; @@ -994,7 +1015,10 @@ struct mana_cfg_rx_steer_req_v2 { mana_handle_t default_rxobj; u8 hashkey[MANA_HASH_KEY_SIZE]; u8 cqe_coalescing_enable; - u8 reserved2[7]; + u8 reserved2[3]; + u16 rss_hash_types; + u8 cqe8_coalescing_enable; /* v5 message */ + u8 reserved3; mana_handle_t indir_tab[] __counted_by(num_indir_entries); }; /* HW DATA */ From b27a8560eec935df48eae4d4fb9cc79fc02cb0e0 Mon Sep 17 00:00:00 2001 From: Bobby Eshleman Date: Wed, 5 Aug 2026 13:42:43 -0700 Subject: [PATCH 1144/1433] net: devmem: allow rx-page-size > PAGE_SIZE per dmabuf binding Every devmem dmabuf binding today hands the page_pool PAGE_SIZE niovs. This caps a single RX descriptor at PAGE_SIZE, burning CPU on buffer churn for large flows. Add a bind-time netlink attribute, NETDEV_A_DMABUF_RX_PAGE_SIZE, that lets userspace request a larger niov size. The value must be a power of two >= PAGE_SIZE. The TX path is changed to always pass PAGE_SIZE. Measurements: Setup: kperf in devmem RX/TX cuda mode, 4 flows, 64 MB messages, 60s, dctcp, num-rx-queues=4, dmabuf-rx/tx-size-mb=2048, 10 runs per niov size, mlx5. CPU Util: niov net sirq % net idle % app sys % app idle % ----- ---------------- ---------------- ---------------- ---------------- 4K 62.38 +/- 8.27 33.40 +/- 7.51 54.15 +/- 10.23 43.67 +/- 10.53 16K 58.91 +/- 5.35 35.23 +/- 5.88 41.05 +/- 8.87 56.42 +/- 9.24 32K 64.12 +/- 0.68 31.09 +/- 1.48 44.54 +/- 3.51 52.63 +/- 3.65 64K 54.69 +/- 5.54 39.67 +/- 5.81 35.47 +/- 3.11 61.97 +/- 3.27 RX app sys % drops ~19% from 4K to 64K. Throughput: niov RX dev Gbps RX flow avg Gbps ----- ---------------- ----------------- 4K 300.63 +/- 53.21 75.16 +/- 13.30 16K 321.35 +/- 28.20 80.34 +/- 7.05 32K 347.63 +/- 2.20 86.91 +/- 0.55 64K 332.11 +/- 14.26 83.03 +/- 3.56 Throughput seems to increase, but the stdev is pretty wide so could just be noise. kperf support (not yet merged): https://github.com/facebookexperimental/kperf/commit/8837577f920876bce6986ec18869ac04439ebcd2 Acked-by: Stanislav Fomichev Reviewed-by: Mina Almasry Reviewed-by: Nikolay Aleksandrov Signed-off-by: Bobby Eshleman Link: https://patch.msgid.link/20260805-tcpdm-large-niovs-v8-1-3e0225e2808c@meta.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/netdev.yaml | 19 +++++++++++++ include/uapi/linux/netdev.h | 1 + net/core/devmem.c | 37 +++++++++++++++---------- net/core/devmem.h | 13 ++++++--- net/core/netdev-genl-gen.c | 11 ++++++-- net/core/netdev-genl-gen.h | 1 + net/core/netdev-genl.c | 18 ++++++++++-- tools/include/uapi/linux/netdev.h | 1 + 8 files changed, 78 insertions(+), 23 deletions(-) diff --git a/Documentation/netlink/specs/netdev.yaml b/Documentation/netlink/specs/netdev.yaml index 5f143da7458c..3e3f03bd5c29 100644 --- a/Documentation/netlink/specs/netdev.yaml +++ b/Documentation/netlink/specs/netdev.yaml @@ -6,6 +6,14 @@ doc: >- netdev configuration over generic netlink. definitions: + - + type: const + name: page-size + # Dummy value, codegen needs a number. The real value comes from + # the PAGE_SIZE macro in the header below. + value: 0 + header: asm/page.h + scope: kernel - type: flags name: xdp-act @@ -598,6 +606,16 @@ attribute-sets: type: u32 checks: min: 1 + - + name: rx-page-size + doc: | + Size in bytes of each device page the NIC writes into from the bound + dmabuf. Must be a power of two and >= PAGE_SIZE; defaults to + PAGE_SIZE. + type: u32 + checks: + min: page-size + max: u32-max operations: list: @@ -812,6 +830,7 @@ operations: - ifindex - fd - queues + - rx-page-size reply: attributes: - id diff --git a/include/uapi/linux/netdev.h b/include/uapi/linux/netdev.h index 2f3ab75e8cc0..35ff083221c7 100644 --- a/include/uapi/linux/netdev.h +++ b/include/uapi/linux/netdev.h @@ -219,6 +219,7 @@ enum { NETDEV_A_DMABUF_QUEUES, NETDEV_A_DMABUF_FD, NETDEV_A_DMABUF_ID, + NETDEV_A_DMABUF_RX_PAGE_SIZE, __NETDEV_A_DMABUF_MAX, NETDEV_A_DMABUF_MAX = (__NETDEV_A_DMABUF_MAX - 1) diff --git a/net/core/devmem.c b/net/core/devmem.c index 957d6b96216b..f4d60654ce7f 100644 --- a/net/core/devmem.c +++ b/net/core/devmem.c @@ -46,7 +46,7 @@ static dma_addr_t net_devmem_get_dma_addr(const struct net_iov *niov) owner = net_devmem_iov_to_chunk_owner(niov); return owner->base_dma_addr + - ((dma_addr_t)net_iov_idx(niov) << PAGE_SHIFT); + ((dma_addr_t)net_iov_idx(niov) << owner->binding->niov_shift); } static void net_devmem_dmabuf_binding_release(struct percpu_ref *ref) @@ -93,13 +93,14 @@ net_devmem_alloc_dmabuf(struct net_devmem_dmabuf_binding *binding) ssize_t offset; ssize_t index; - dma_addr = gen_pool_alloc_owner(binding->chunk_pool, PAGE_SIZE, + dma_addr = gen_pool_alloc_owner(binding->chunk_pool, + 1UL << binding->niov_shift, (void **)&owner); if (!dma_addr) return NULL; offset = dma_addr - owner->base_dma_addr; - index = offset / PAGE_SIZE; + index = offset >> binding->niov_shift; niov = &owner->area.niovs[index]; niov->desc.pp_magic = 0; @@ -113,12 +114,13 @@ void net_devmem_free_dmabuf(struct net_iov *niov) { struct net_devmem_dmabuf_binding *binding = net_devmem_iov_binding(niov); unsigned long dma_addr = net_devmem_get_dma_addr(niov); + size_t niov_size = 1UL << binding->niov_shift; if (WARN_ON(!gen_pool_has_addr(binding->chunk_pool, dma_addr, - PAGE_SIZE))) + niov_size))) return; - gen_pool_free(binding->chunk_pool, dma_addr, PAGE_SIZE); + gen_pool_free(binding->chunk_pool, dma_addr, niov_size); } void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding) @@ -163,6 +165,9 @@ int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx, u32 xa_idx; int err; + if (binding->niov_shift != PAGE_SHIFT) + mp_params.rx_page_size = 1U << binding->niov_shift; + err = netif_mp_open_rxq(dev, rxq_idx, &mp_params, extack); if (err) return err; @@ -184,10 +189,12 @@ struct net_devmem_dmabuf_binding * net_devmem_bind_dmabuf(struct net_device *dev, void *vdev, struct device *dma_dev, enum dma_data_direction direction, - unsigned int dmabuf_fd, struct netdev_nl_sock *priv, + unsigned int dmabuf_fd, unsigned int niov_shift, + struct netdev_nl_sock *priv, struct netlink_ext_ack *extack) { struct net_devmem_dmabuf_binding *binding; + size_t niov_size = 1UL << niov_shift; static u32 id_alloc_next; struct scatterlist *sg; struct dma_buf *dmabuf; @@ -213,6 +220,7 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev, binding->dev = dev; binding->vdev = vdev; + binding->niov_shift = niov_shift; xa_init_flags(&binding->bound_rxqs, XA_FLAGS_ALLOC); err = percpu_ref_init(&binding->ref, @@ -255,11 +263,7 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev, } } - /* For simplicity we expect to make PAGE_SIZE allocations, but the - * binding can be much more flexible than that. We may be able to - * allocate MTU sized chunks here. Leave that for future work... - */ - binding->chunk_pool = gen_pool_create(PAGE_SHIFT, + binding->chunk_pool = gen_pool_create(niov_shift, dev_to_node(&dev->dev)); if (!binding->chunk_pool) { err = -ENOMEM; @@ -273,9 +277,12 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev, size_t len = sg_dma_len(sg); struct net_iov *niov; - if (!IS_ALIGNED(len, PAGE_SIZE)) { + if (!IS_ALIGNED(dma_addr, niov_size) || + !IS_ALIGNED(len, niov_size)) { err = -EINVAL; - NL_SET_ERR_MSG(extack, "dma-buf SG length must be PAGE_SIZE aligned"); + NL_SET_ERR_MSG_FMT(extack, + "dmabuf sg entry (addr=%pad, len=%zu) not aligned to niov size %zu", + &dma_addr, len, niov_size); goto err_free_chunks; } @@ -288,7 +295,7 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev, owner->area.base_virtual = virtual; owner->base_dma_addr = dma_addr; - owner->area.num_niovs = len / PAGE_SIZE; + owner->area.num_niovs = len >> niov_shift; owner->binding = binding; err = gen_pool_add_owner(binding->chunk_pool, dma_addr, @@ -454,7 +461,7 @@ int mp_dmabuf_devmem_init(struct page_pool *pool) pool->dma_sync = false; pool->dma_sync_for_cpu = false; - if (pool->p.order != 0) + if (pool->p.order != binding->niov_shift - PAGE_SHIFT) return -E2BIG; net_devmem_dmabuf_binding_get(binding); diff --git a/net/core/devmem.h b/net/core/devmem.h index 3852a56036cb..4a293a7d1149 100644 --- a/net/core/devmem.h +++ b/net/core/devmem.h @@ -71,6 +71,8 @@ struct net_devmem_dmabuf_binding { */ struct net_iov **tx_vec; + unsigned int niov_shift; + struct work_struct unbind_w; }; @@ -93,7 +95,8 @@ struct net_devmem_dmabuf_binding * net_devmem_bind_dmabuf(struct net_device *dev, void *vdev, struct device *dma_dev, enum dma_data_direction direction, - unsigned int dmabuf_fd, struct netdev_nl_sock *priv, + unsigned int dmabuf_fd, unsigned int niov_shift, + struct netdev_nl_sock *priv, struct netlink_ext_ack *extack); struct net_devmem_dmabuf_binding *net_devmem_lookup_dmabuf(u32 id); void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding); @@ -122,10 +125,11 @@ static inline u32 net_devmem_iov_binding_id(const struct net_iov *niov) static inline unsigned long net_iov_virtual_addr(const struct net_iov *niov) { - struct net_iov_area *owner = net_iov_owner(niov); + struct dmabuf_genpool_chunk_owner *co = + net_devmem_iov_to_chunk_owner(niov); - return owner->base_virtual + - ((unsigned long)net_iov_idx(niov) << PAGE_SHIFT); + return net_iov_owner(niov)->base_virtual + + ((unsigned long)net_iov_idx(niov) << co->binding->niov_shift); } static inline bool @@ -175,6 +179,7 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev, struct device *dma_dev, enum dma_data_direction direction, unsigned int dmabuf_fd, + unsigned int niov_shift, struct netdev_nl_sock *priv, struct netlink_ext_ack *extack) { diff --git a/net/core/netdev-genl-gen.c b/net/core/netdev-genl-gen.c index d18c89b5a6c7..f83790341eae 100644 --- a/net/core/netdev-genl-gen.c +++ b/net/core/netdev-genl-gen.c @@ -11,6 +11,7 @@ #include #include +#include /* Integer value ranges */ static const struct netlink_range_validation netdev_a_page_pool_id_range = { @@ -27,6 +28,11 @@ static const struct netlink_range_validation netdev_a_napi_defer_hard_irqs_range .max = S32_MAX, }; +static const struct netlink_range_validation netdev_a_dmabuf_rx_page_size_range = { + .min = PAGE_SIZE, + .max = U32_MAX, +}; + /* Common nested types */ const struct nla_policy netdev_lease_nl_policy[NETDEV_A_LEASE_NETNS_ID + 1] = { [NETDEV_A_LEASE_IFINDEX] = NLA_POLICY_MIN(NLA_U32, 1), @@ -106,10 +112,11 @@ static const struct nla_policy netdev_qstats_get_nl_policy[NETDEV_A_QSTATS_SCOPE }; /* NETDEV_CMD_BIND_RX - do */ -static const struct nla_policy netdev_bind_rx_nl_policy[NETDEV_A_DMABUF_FD + 1] = { +static const struct nla_policy netdev_bind_rx_nl_policy[NETDEV_A_DMABUF_RX_PAGE_SIZE + 1] = { [NETDEV_A_DMABUF_IFINDEX] = NLA_POLICY_MIN(NLA_U32, 1), [NETDEV_A_DMABUF_FD] = { .type = NLA_U32, }, [NETDEV_A_DMABUF_QUEUES] = NLA_POLICY_NESTED(netdev_queue_id_nl_policy), + [NETDEV_A_DMABUF_RX_PAGE_SIZE] = NLA_POLICY_FULL_RANGE(NLA_U32, &netdev_a_dmabuf_rx_page_size_range), }; /* NETDEV_CMD_NAPI_SET - do */ @@ -219,7 +226,7 @@ static const struct genl_split_ops netdev_nl_ops[] = { .cmd = NETDEV_CMD_BIND_RX, .doit = netdev_nl_bind_rx_doit, .policy = netdev_bind_rx_nl_policy, - .maxattr = NETDEV_A_DMABUF_FD, + .maxattr = NETDEV_A_DMABUF_RX_PAGE_SIZE, .flags = GENL_UNS_ADMIN_PERM | GENL_CMD_CAP_DO, }, { diff --git a/net/core/netdev-genl-gen.h b/net/core/netdev-genl-gen.h index d71b435d72c1..3fae88e8f5c5 100644 --- a/net/core/netdev-genl-gen.h +++ b/net/core/netdev-genl-gen.h @@ -12,6 +12,7 @@ #include #include +#include /* Common nested types */ extern const struct nla_policy netdev_lease_nl_policy[NETDEV_A_LEASE_NETNS_ID + 1]; diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c index c15d8d4ca1f8..0eea4ee22f24 100644 --- a/net/core/netdev-genl.c +++ b/net/core/netdev-genl.c @@ -1013,6 +1013,7 @@ netdev_nl_get_dma_dev(struct net_device *netdev, unsigned long *rxq_bitmap, int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info) { struct net_devmem_dmabuf_binding *binding; + unsigned int niov_shift = PAGE_SHIFT; u32 ifindex, dmabuf_fd, rxq_idx; struct netdev_nl_sock *priv; struct net_device *netdev; @@ -1030,6 +1031,18 @@ int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info) ifindex = nla_get_u32(info->attrs[NETDEV_A_DEV_IFINDEX]); dmabuf_fd = nla_get_u32(info->attrs[NETDEV_A_DMABUF_FD]); + if (info->attrs[NETDEV_A_DMABUF_RX_PAGE_SIZE]) { + u32 rx_page_size = nla_get_u32(info->attrs[NETDEV_A_DMABUF_RX_PAGE_SIZE]); + + if (!is_power_of_2(rx_page_size)) { + NL_SET_ERR_MSG_ATTR(info->extack, + info->attrs[NETDEV_A_DMABUF_RX_PAGE_SIZE], + "rx-page-size must be a power of 2"); + return -EINVAL; + } + niov_shift = ilog2(rx_page_size); + } + priv = genl_sk_priv_get(&netdev_nl_family, NETLINK_CB(skb).sk); if (IS_ERR(priv)) return PTR_ERR(priv); @@ -1080,7 +1093,8 @@ int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info) } binding = net_devmem_bind_dmabuf(netdev, NULL, dma_dev, DMA_FROM_DEVICE, - dmabuf_fd, priv, info->extack); + dmabuf_fd, niov_shift, priv, + info->extack); if (IS_ERR(binding)) { err = PTR_ERR(binding); goto err_rxq_bitmap; @@ -1221,7 +1235,7 @@ int netdev_nl_bind_tx_doit(struct sk_buff *skb, struct genl_info *info) binding = net_devmem_bind_dmabuf(bind_dev, bind_dev != netdev ? netdev : NULL, dma_dev, DMA_TO_DEVICE, dmabuf_fd, - priv, info->extack); + PAGE_SHIFT, priv, info->extack); if (IS_ERR(binding)) { err = PTR_ERR(binding); goto err_unlock_bind_dev; diff --git a/tools/include/uapi/linux/netdev.h b/tools/include/uapi/linux/netdev.h index 2f3ab75e8cc0..35ff083221c7 100644 --- a/tools/include/uapi/linux/netdev.h +++ b/tools/include/uapi/linux/netdev.h @@ -219,6 +219,7 @@ enum { NETDEV_A_DMABUF_QUEUES, NETDEV_A_DMABUF_FD, NETDEV_A_DMABUF_ID, + NETDEV_A_DMABUF_RX_PAGE_SIZE, __NETDEV_A_DMABUF_MAX, NETDEV_A_DMABUF_MAX = (__NETDEV_A_DMABUF_MAX - 1) From 3e8c9ec4eb75b60cd03bcebb275553df09417e2e Mon Sep 17 00:00:00 2001 From: Bobby Eshleman Date: Wed, 5 Aug 2026 13:42:44 -0700 Subject: [PATCH 1145/1433] selftests/net: ncdevmem: add -b option to set rx-page-size on bind Add -b to request a non-default niov size via NETDEV_A_DMABUF_RX_PAGE_SIZE. When the value exceeds PAGE_SIZE, udmabuf_alloc() switches to an MFD_HUGETLB-backed memfd so each 2 MB hugepage produces one naturally-aligned sg entry. Acked-by: Stanislav Fomichev Reviewed-by: Nikolay Aleksandrov Signed-off-by: Bobby Eshleman Link: https://patch.msgid.link/20260805-tcpdm-large-niovs-v8-2-3e0225e2808c@meta.com Signed-off-by: Jakub Kicinski --- .../selftests/drivers/net/hw/ncdevmem.c | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/drivers/net/hw/ncdevmem.c b/tools/testing/selftests/drivers/net/hw/ncdevmem.c index ffe1d5c1fa4e..918e3b51f3b8 100644 --- a/tools/testing/selftests/drivers/net/hw/ncdevmem.c +++ b/tools/testing/selftests/drivers/net/hw/ncdevmem.c @@ -40,6 +40,7 @@ #include #include +#include #include #include #include @@ -61,6 +62,7 @@ #include #include +#include #include #include #include @@ -79,6 +81,7 @@ #define PAGE_SHIFT 12 #define TEST_PREFIX "ncdevmem" #define NUM_PAGES 16000 +#define MB(x) ((x) << 20) #ifndef MSG_SOCK_DEVMEM #define MSG_SOCK_DEVMEM 0x2000000 @@ -100,6 +103,7 @@ static unsigned int dmabuf_id; static uint32_t tx_dmabuf_id; static int waittime_ms = 500; static bool fail_on_linear; +static uint32_t rx_page_size; /* System state loaded by current_config_load() */ #define MAX_FLOWS 8 @@ -142,6 +146,7 @@ static struct memory_buffer *udmabuf_alloc(size_t size) { struct udmabuf_create create; struct memory_buffer *ctx; + unsigned int memfd_flags; int ret; ctx = malloc(sizeof(*ctx)); @@ -156,9 +161,14 @@ static struct memory_buffer *udmabuf_alloc(size_t size) goto err_free_ctx; } - ctx->memfd = memfd_create("udmabuf-test", MFD_ALLOW_SEALING); + memfd_flags = MFD_ALLOW_SEALING; + if (rx_page_size > getpagesize()) + memfd_flags |= MFD_HUGETLB | MFD_HUGE_2MB; + + ctx->memfd = memfd_create("udmabuf-test", memfd_flags); if (ctx->memfd < 0) { - pr_err("[skip,no-memfd]"); + pr_err("[skip,no-memfd%s]", + (memfd_flags & MFD_HUGETLB) ? " (need hugepages)" : ""); goto err_close_dev; } @@ -168,6 +178,11 @@ static struct memory_buffer *udmabuf_alloc(size_t size) goto err_close_memfd; } + if (memfd_flags & MFD_HUGETLB) { + size = roundup(size, MB(2)); + ctx->size = size; + } + ret = ftruncate(ctx->memfd, size); if (ret == -1) { pr_err("[FAIL,memfd-truncate]"); @@ -699,6 +714,8 @@ static int bind_rx_queue(unsigned int ifindex, unsigned int dmabuf_fd, netdev_bind_rx_req_set_ifindex(req, ifindex); netdev_bind_rx_req_set_fd(req, dmabuf_fd); __netdev_bind_rx_req_set_queues(req, queues, n_queue_index); + if (rx_page_size) + netdev_bind_rx_req_set_rx_page_size(req, rx_page_size); rsp = netdev_bind_rx(*ys, req); if (!rsp) { @@ -1411,7 +1428,7 @@ int main(int argc, char *argv[]) int is_server = 0, opt; int ret, err = 1; - while ((opt = getopt(argc, argv, "Lls:c:p:v:q:t:f:z:n")) != -1) { + while ((opt = getopt(argc, argv, "Lls:c:p:v:q:t:f:z:nb:")) != -1) { switch (opt) { case 'L': fail_on_linear = true; @@ -1446,6 +1463,19 @@ int main(int argc, char *argv[]) case 'n': skip_config = 1; break; + case 'b': { + unsigned long val; + + errno = 0; + val = strtoul(optarg, NULL, 0); + if ((val == ULONG_MAX && errno == ERANGE) || + val > UINT32_MAX) { + pr_err("invalid rx_page_size: %s", optarg); + return 1; + } + rx_page_size = val; + break; + } case '?': fprintf(stderr, "unknown option: %c\n", optopt); break; From 8ac4255c1e0c83d2e1559a18b8918673116fc8d6 Mon Sep 17 00:00:00 2001 From: Bobby Eshleman Date: Wed, 5 Aug 2026 13:42:45 -0700 Subject: [PATCH 1146/1433] selftests/net: devmem.py: add check_rx_large_niov Add a new devmem test case for binding the dmabuf with rx-page-size=16K. The test sweeps RX payload sizes straddling the niov boundary to cover the sub-niov, exact-niov, and multi-niov RX paths. Silence pylint invalid-name (`with open() as f`) and too-many-arguments (ncdevmem_rx grew to 6 args) at file scope. Acked-by: Stanislav Fomichev Reviewed-by: Nikolay Aleksandrov Signed-off-by: Bobby Eshleman Link: https://patch.msgid.link/20260805-tcpdm-large-niovs-v8-3-3e0225e2808c@meta.com Signed-off-by: Jakub Kicinski --- .../selftests/drivers/net/hw/devmem.py | 11 +- .../selftests/drivers/net/hw/devmem_lib.py | 111 ++++++++++++++++-- .../selftests/drivers/net/hw/nk_devmem.py | 10 +- 3 files changed, 120 insertions(+), 12 deletions(-) diff --git a/tools/testing/selftests/drivers/net/hw/devmem.py b/tools/testing/selftests/drivers/net/hw/devmem.py index 031cf9905f65..82c11ffc4add 100755 --- a/tools/testing/selftests/drivers/net/hw/devmem.py +++ b/tools/testing/selftests/drivers/net/hw/devmem.py @@ -2,7 +2,8 @@ # SPDX-License-Identifier: GPL-2.0 from os import path -from devmem_lib import setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds +from devmem_lib import (setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds, + run_rx_large_niov) from lib.py import ksft_run, ksft_exit, ksft_disruptive from lib.py import NetDrvEpEnv @@ -30,11 +31,17 @@ def check_rx_hds(cfg) -> None: run_rx_hds(cfg) +def check_rx_large_niov(cfg) -> None: + """Run the devmem RX test with rx-page-size = 16 KiB.""" + run_rx_large_niov(cfg) + + def main() -> None: """Run the devmem test cases.""" with NetDrvEpEnv(__file__) as cfg: setup_test(cfg, path.abspath(path.dirname(__file__) + "/ncdevmem")) - ksft_run([check_rx, check_tx, check_tx_chunks, check_rx_hds], + ksft_run([check_rx, check_tx, check_tx_chunks, check_rx_hds, + check_rx_large_niov], args=(cfg,)) ksft_exit() diff --git a/tools/testing/selftests/drivers/net/hw/devmem_lib.py b/tools/testing/selftests/drivers/net/hw/devmem_lib.py index 4e6316c7de96..3554954a6691 100644 --- a/tools/testing/selftests/drivers/net/hw/devmem_lib.py +++ b/tools/testing/selftests/drivers/net/hw/devmem_lib.py @@ -1,6 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 +# pylint: disable=invalid-name,too-many-arguments """Shared helpers for devmem TCP selftests.""" +import os import re from lib.py import (bkg, cmd, defer, ethtool, rand_port, wait_port_listen, @@ -8,19 +10,82 @@ from lib.py import (bkg, cmd, defer, ethtool, rand_port, wait_port_listen, NetdevFamily) -def require_devmem(cfg): - """Probe ncdevmem on cfg.ifname and SKIP the test if devmem isn't supported.""" - if not hasattr(cfg, "devmem_probed"): - probe_command = f"{cfg.bin_local} -f {cfg.ifname}" - cfg.devmem_supported = cmd(probe_command, fail=False, shell=True).ret == 0 - cfg.devmem_probed = True +RX_PAGE_SIZE_DEFAULT = 0 +RX_PAGE_SIZE_16K = 16384 - if not cfg.devmem_supported: +PROBE_RX_PAGE_SIZES = (RX_PAGE_SIZE_DEFAULT, RX_PAGE_SIZE_16K) + +NR_HUGEPAGES_FILE = "/proc/sys/vm/nr_hugepages" + + +def _is_aligned(value, alignment): + """Equivalent of the kernel IS_ALIGNED(value, alignment). + + alignment must be a power of two. + """ + return (value & (alignment - 1)) == 0 + + +def _restore_nr_hugepages(nr_hugepages): + with open(NR_HUGEPAGES_FILE, 'w', encoding='utf-8') as f: + f.write(str(nr_hugepages)) + + +def _reserve_hugepages(want=64): + """Raise nr_hugepages to @want and arrange for it to be restored.""" + with open(NR_HUGEPAGES_FILE, 'r+', encoding='utf-8') as f: + nr_hugepages = int(f.read().strip()) + if nr_hugepages >= want: + return + f.seek(0) + f.write(str(want)) + defer(_restore_nr_hugepages, nr_hugepages) + + +def _probe_devmem(cfg, rx_page_size): + """Return True if ncdevmem can bind cfg.ifname at @rx_page_size.""" + probe_command = f"{cfg.bin_local} -f {cfg.ifname}" + if rx_page_size != RX_PAGE_SIZE_DEFAULT: + probe_command += f" -b {rx_page_size}" + return cmd(probe_command, fail=False, shell=True).ret == 0 + + +def require_devmem(cfg, rx_page_size=RX_PAGE_SIZE_DEFAULT): + """Probe ncdevmem on cfg.ifname and SKIP the test if devmem isn't supported.""" + if rx_page_size not in PROBE_RX_PAGE_SIZES: + raise RuntimeError( + f"rx-page-size={rx_page_size} is missing from " + f"PROBE_RX_PAGE_SIZES, so it was never probed.") + + if not hasattr(cfg, "devmem_supported"): + _reserve_hugepages() + # Probe every size upfront: in nk tests a leased queue may land in + # ncdevmem's queue range and cause the probe to fail. + cfg.devmem_supported = {size: _probe_devmem(cfg, size) + for size in PROBE_RX_PAGE_SIZES} + + if not cfg.devmem_supported[RX_PAGE_SIZE_DEFAULT]: raise KsftSkipEx("Test requires devmem support") + if rx_page_size != RX_PAGE_SIZE_DEFAULT: + page_size = os.sysconf("SC_PAGE_SIZE") + if not _is_aligned(rx_page_size, page_size): + raise KsftSkipEx( + f"rx-page-size={rx_page_size} is invalid for this platform " + f"(must be a multiple of PAGE_SIZE={page_size})") + + if not cfg.devmem_supported[rx_page_size]: + raise KsftSkipEx( + f"Test requires devmem rx-page-size={rx_page_size} support") + def configure_nic(cfg): """Channels, rings, RSS, queue lease for netkit devmem.""" + if not hasattr(cfg, "devmem_supported"): + raise RuntimeError( + "require_devmem() must be called before configure_nic(), which " + "may lease a queue away and make later probes fail.") + if not hasattr(cfg, 'netns'): return @@ -75,7 +140,8 @@ def set_flow_rule(cfg, port): return int(re.search(r'ID (\d+)', output).group(1)) -def ncdevmem_rx(cfg, port, verify=True, fail_on_linear=False, flow_steer=False): +def ncdevmem_rx(cfg, port, verify=True, fail_on_linear=False, flow_steer=False, + rx_page_size=RX_PAGE_SIZE_DEFAULT): """Build the ncdevmem RX listener command.""" if hasattr(cfg, 'netns'): flow_rule_id = set_flow_rule(cfg, port) @@ -95,6 +161,8 @@ def ncdevmem_rx(cfg, port, verify=True, fail_on_linear=False, flow_steer=False): extras.append("-v 7") if fail_on_linear: extras.append("-L") + if rx_page_size != RX_PAGE_SIZE_DEFAULT: + extras.append(f"-b {rx_page_size}") parts = [cfg.bin_local, "-l", f"-f {ifname}", f"-s {addr}", f"-p {port}", *extras] @@ -201,6 +269,33 @@ def run_tx_chunks(cfg): ksft_eq(socat.stdout.strip(), "hello\nworld") +def run_rx_large_niov(cfg): + """Run the devmem RX test with a large niov (rx-page-size > PAGE_SIZE). + + Sweep payload sizes that straddle the niov boundary: below, equal to, + and above rx_page_size, to exercise sub-niov, exact-niov, and multi-niov + RX paths. + """ + require_devmem(cfg, rx_page_size=RX_PAGE_SIZE_16K) + _reserve_hugepages() + configure_nic(cfg) + netns = getattr(cfg, "netns", None) + + for size in [1024, 4096, 8192, 16384, 32768, 65536]: + port = rand_port() + socat = socat_send(cfg, port) + listen_cmd = ncdevmem_rx(cfg, port, + flow_steer=not netns, + rx_page_size=RX_PAGE_SIZE_16K) + data_pipe = (f"yes $(echo -e \x01\x02\x03\x04\x05\x06) | " + f"head -c {size} | {socat}") + with bkg(listen_cmd, exit_wait=True, ns=netns) as ncdevmem: + wait_port_listen(port, proto="tcp", ns=netns) + cmd(data_pipe, host=cfg.remote, shell=True) + ksft_eq(ncdevmem.ret, 0, + f"large-niov failed for payload size {size}") + + def run_rx_hds(cfg): """Run the HDS test by running devmem RX across a segment size sweep.""" require_devmem(cfg) diff --git a/tools/testing/selftests/drivers/net/hw/nk_devmem.py b/tools/testing/selftests/drivers/net/hw/nk_devmem.py index 300ed2a70ab4..61c6f31f01e5 100755 --- a/tools/testing/selftests/drivers/net/hw/nk_devmem.py +++ b/tools/testing/selftests/drivers/net/hw/nk_devmem.py @@ -3,7 +3,8 @@ """Test devmem TCP with netkit.""" import os -from devmem_lib import setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds +from devmem_lib import (setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds, + run_rx_large_niov) from lib.py import ksft_run, ksft_exit, ksft_disruptive from lib.py import NetDrvContEnv @@ -31,6 +32,11 @@ def check_nk_rx_hds(cfg) -> None: run_rx_hds(cfg) +def check_nk_rx_large_niov(cfg) -> None: + """Run the devmem RX large-niov test through netkit.""" + run_rx_large_niov(cfg) + + def main() -> None: """Run the netkit devmem test cases.""" with NetDrvContEnv(__file__, rxqueues=2, primary_rx_redirect=True) as cfg: @@ -38,7 +44,7 @@ def main() -> None: os.path.join(os.path.dirname(os.path.abspath(__file__)), "ncdevmem")) ksft_run([check_nk_rx, check_nk_tx, check_nk_tx_chunks, - check_nk_rx_hds], args=(cfg,)) + check_nk_rx_hds, check_nk_rx_large_niov], args=(cfg,)) ksft_exit() From 5546b082fa7c58dd0d0d2a694c12098764645765 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Wed, 5 Aug 2026 23:16:34 +0200 Subject: [PATCH 1147/1433] netfilter: add DEBUG_NET_WARN_ON_ONCE to skb_set_nfct() Trigger a warning if nf_ct_set() overlaps an existing ct object leading to refcount leak. Add this warning to skb_set_nfct() whose only user is nf_ct_set() instead. Update existing nf_ct_set() callers to use nf_reset_ct() first to clean up stale pointer to conntrack object which migh trigger false positive warnings. Reviewed-by: Fernando Fernandez Mancera Signed-off-by: Pablo Neira Ayuso --- include/linux/skbuff.h | 1 + include/net/ip_vs.h | 2 +- net/netfilter/nf_conntrack_core.c | 2 +- net/openvswitch/conntrack.c | 12 +++--------- net/sched/act_ct.c | 6 +++--- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index 22eda1d54a0e..95184183180f 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -5004,6 +5004,7 @@ static inline unsigned long skb_get_nfct(const struct sk_buff *skb) static inline void skb_set_nfct(struct sk_buff *skb, unsigned long nfct) { #if IS_ENABLED(CONFIG_NF_CONNTRACK) + DEBUG_NET_WARN_ON_ONCE(skb->_nfct & NFCT_PTRMASK); skb->slow_gro |= !!nfct; skb->_nfct = nfct; #endif diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index b3bb228ad75c..3dca7d387dd0 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -2121,7 +2121,7 @@ static inline void ip_vs_notrack(struct sk_buff *skb) struct nf_conn *ct = nf_ct_get(skb, &ctinfo); if (ct) { - nf_conntrack_put(&ct->ct_general); + nf_reset_ct(skb); nf_ct_set(skb, NULL, IP_CT_UNTRACKED); } #endif diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c index 784bd1d7a9bf..d0d9e5ea84a0 100644 --- a/net/netfilter/nf_conntrack_core.c +++ b/net/netfilter/nf_conntrack_core.c @@ -1031,7 +1031,7 @@ static int __nf_ct_resolve_clash(struct sk_buff *skb, nf_conntrack_get(&ct->ct_general); nf_ct_acct_merge(ct, ctinfo, loser_ct); - nf_ct_put(loser_ct); + nf_reset_ct(skb); nf_ct_set(skb, ct, ctinfo); NF_CT_STAT_INC(net, clash_resolve); diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c index 95697d4e16e6..4dd82c4e87d3 100644 --- a/net/openvswitch/conntrack.c +++ b/net/openvswitch/conntrack.c @@ -603,7 +603,7 @@ static bool skb_nfct_cached(struct net *net, if (nf_ct_is_confirmed(ct)) nf_ct_delete(ct, 0, 0); - nf_ct_put(ct); + nf_reset_ct(skb); nf_ct_set(skb, NULL, 0); return false; } @@ -745,8 +745,7 @@ static int __ovs_ct_lookup(struct net *net, struct sw_flow_key *key, /* Associate skb with specified zone. */ if (tmpl) { - ct = nf_ct_get(skb, &ctinfo); - nf_ct_put(ct); + nf_reset_ct(skb); nf_conntrack_get(&tmpl->ct_general); nf_ct_set(skb, tmpl, IP_CT_NEW); } @@ -1075,12 +1074,7 @@ int ovs_ct_execute(struct net *net, struct sk_buff *skb, int ovs_ct_clear(struct sk_buff *skb, struct sw_flow_key *key) { - enum ip_conntrack_info ctinfo; - struct nf_conn *ct; - - ct = nf_ct_get(skb, &ctinfo); - - nf_ct_put(ct); + nf_reset_ct(skb); nf_ct_set(skb, NULL, IP_CT_UNTRACKED); if (key) diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c index 4ca7964e83c8..7f54fb4e4ec9 100644 --- a/net/sched/act_ct.c +++ b/net/sched/act_ct.c @@ -782,7 +782,7 @@ static bool tcf_ct_skb_nfct_cached(struct net *net, struct sk_buff *skb, return true; drop_ct: - nf_ct_put(ct); + nf_reset_ct(skb); nf_ct_set(skb, NULL, IP_CT_UNTRACKED); return false; @@ -996,7 +996,7 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, qdisc_skb_cb(skb)->post_ct = false; ct = nf_ct_get(skb, &ctinfo); if (ct) { - nf_ct_put(ct); + nf_reset_ct(skb); nf_ct_set(skb, NULL, IP_CT_UNTRACKED); } @@ -1034,7 +1034,7 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, /* Associate skb with specified zone. */ if (tmpl) { - nf_conntrack_put(skb_nfct(skb)); + nf_reset_ct(skb); nf_conntrack_get(&tmpl->ct_general); nf_ct_set(skb, tmpl, IP_CT_NEW); } From 95133a416809c7e822da4023b7f4193ef2620796 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 6 Aug 2026 23:09:03 +0200 Subject: [PATCH 1148/1433] net: pass net_device_path_ctx to dev_fill_forward_path() Refactor dev_fill_forward_path() to take a struct net_device_path_ctx pointer instead of a (dev, daddr) pair, so the caller can build and populate the context up front and keep it after the forward path walk. This allows additional fields (e.g. vlan and ether_type) to be carried in the context and shared with ndo_fill_forward_path implementations, instead of being reconstructed on the stack inside the core helper. Update the mtk_ppe_offload, airoha_ppe and nf_flow_table_path callers to allocate and fill the context before invoking dev_fill_forward_path(). The network topology resolution behaviour is unchanged. This is a preliminary patch to enable HW flowtable offload for IPv4 over IPv6 tunnels. Signed-off-by: Lorenzo Bianconi Reviewed-by: Simon Horman Signed-off-by: Pablo Neira Ayuso --- drivers/net/ethernet/airoha/airoha_ppe.c | 7 ++++++- .../net/ethernet/mediatek/mtk_ppe_offload.c | 7 ++++++- include/linux/netdevice.h | 2 +- net/core/dev.c | 18 +++++++----------- net/netfilter/nf_flow_table_path.c | 7 ++++++- 5 files changed, 26 insertions(+), 15 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c index a03af9750573..92611802801e 100644 --- a/drivers/net/ethernet/airoha/airoha_ppe.c +++ b/drivers/net/ethernet/airoha/airoha_ppe.c @@ -283,14 +283,19 @@ static int airoha_ppe_get_wdma_info(struct net_device *dev, const u8 *addr, struct airoha_wdma_info *info) { struct net_device_path_stack stack; + struct net_device_path_ctx ctx = { + .dev = dev, + }; struct net_device_path *path; int err; if (!dev) return -ENODEV; + ether_addr_copy(ctx.daddr, addr); + rcu_read_lock(); - err = dev_fill_forward_path(dev, addr, &stack); + err = dev_fill_forward_path(&ctx, &stack); rcu_read_unlock(); if (err) return err; diff --git a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c index 771d9118f94a..99b28aaa7cc4 100644 --- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c +++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c @@ -92,6 +92,9 @@ static int mtk_flow_get_wdma_info(struct net_device *dev, const u8 *addr, struct mtk_wdma_info *info) { struct net_device_path_stack stack; + struct net_device_path_ctx ctx = { + .dev = dev, + }; struct net_device_path *path; int err; @@ -101,8 +104,10 @@ mtk_flow_get_wdma_info(struct net_device *dev, const u8 *addr, struct mtk_wdma_i if (!IS_ENABLED(CONFIG_NET_MEDIATEK_SOC_WED)) return -1; + ether_addr_copy(ctx.daddr, addr); + rcu_read_lock(); - err = dev_fill_forward_path(dev, addr, &stack); + err = dev_fill_forward_path(&ctx, &stack); rcu_read_unlock(); if (err) return err; diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index db9dce7f0aa6..17d28adb029b 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -3428,7 +3428,7 @@ void dev_remove_offload(struct packet_offload *po); int dev_get_iflink(const struct net_device *dev); int dev_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb); -int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr, +int dev_fill_forward_path(struct net_device_path_ctx *ctx, struct net_device_path_stack *stack); void dev_fill_forward_path_release(struct net_device_path_stack *stack); struct net_device *dev_get_by_name(struct net *net, const char *name); diff --git a/net/core/dev.c b/net/core/dev.c index fd0b445f5d38..1755dd0b2a92 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -769,35 +769,31 @@ void dev_fill_forward_path_release(struct net_device_path_stack *stack) } EXPORT_SYMBOL_GPL(dev_fill_forward_path_release); -int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr, +int dev_fill_forward_path(struct net_device_path_ctx *ctx, struct net_device_path_stack *stack) { const struct net_device *last_dev; - struct net_device_path_ctx ctx = { - .dev = dev, - }; struct net_device_path *path; int ret = 0; - memcpy(ctx.daddr, daddr, sizeof(ctx.daddr)); stack->num_paths = 0; - while (ctx.dev && ctx.dev->netdev_ops->ndo_fill_forward_path) { - last_dev = ctx.dev; + while (ctx->dev && ctx->dev->netdev_ops->ndo_fill_forward_path) { + last_dev = ctx->dev; path = dev_fwd_path(stack); if (!path) goto err_out; memset(path, 0, sizeof(struct net_device_path)); - ret = ctx.dev->netdev_ops->ndo_fill_forward_path(&ctx, path); + ret = ctx->dev->netdev_ops->ndo_fill_forward_path(ctx, path); if (ret < 0) goto err_out; stack->num_paths++; - if (WARN_ON_ONCE(last_dev == ctx.dev)) + if (WARN_ON_ONCE(last_dev == ctx->dev)) goto err_out; } - if (!ctx.dev) + if (!ctx->dev) return ret; path = dev_fwd_path(stack); @@ -805,7 +801,7 @@ int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr, goto err_out; path->type = DEV_PATH_ETHERNET; - path->dev = ctx.dev; + path->dev = ctx->dev; stack->num_paths++; return 0; diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c index 56219b02e122..0cbde535b8ba 100644 --- a/net/netfilter/nf_flow_table_path.c +++ b/net/netfilter/nf_flow_table_path.c @@ -49,6 +49,9 @@ static int nft_dev_fill_forward_path(const struct dst_entry *dst_cache, { const void *daddr = &ct->tuplehash[!dir].tuple.src.u3; struct net_device *dev = dst_cache->dev; + struct net_device_path_ctx ctx = { + .dev = dev, + }; struct neighbour *n; u8 nud_state; @@ -71,7 +74,9 @@ static int nft_dev_fill_forward_path(const struct dst_entry *dst_cache, return -1; out: - return dev_fill_forward_path(dev, ha, stack); + ether_addr_copy(ctx.daddr, ha); + + return dev_fill_forward_path(&ctx, stack); } struct nft_forward_info { From 5592223f3d4e45fb5e98ea7fe9be075725f40ff7 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 6 Aug 2026 23:09:06 +0200 Subject: [PATCH 1149/1433] net: netfilter: add ether_type to net_device_path_ctx and use it Add an ether_type field to struct net_device_path_ctx to reject IPv4 over IPv6 and vice-versa, this is currently not support. Otherwise, incorrect dst_entry family can be reached from datapath. Acked-by: Lorenzo Bianconi Signed-off-by: Pablo Neira Ayuso --- include/linux/netdevice.h | 1 + net/ipv4/ipip.c | 3 +++ net/ipv6/ip6_tunnel.c | 3 +++ net/netfilter/nf_flow_table_path.c | 6 ++++-- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 17d28adb029b..2327a2703b83 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -941,6 +941,7 @@ struct net_device_path_stack { struct net_device_path_ctx { const struct net_device *dev; u8 daddr[ETH_ALEN]; + __be16 ether_type; int num_vlans; struct { diff --git a/net/ipv4/ipip.c b/net/ipv4/ipip.c index fb7d96f99b06..62a374079bfc 100644 --- a/net/ipv4/ipip.c +++ b/net/ipv4/ipip.c @@ -360,6 +360,9 @@ static int ipip_fill_forward_path(struct net_device_path_ctx *ctx, const struct iphdr *tiph = &tunnel->parms.iph; struct rtable *rt; + if (ctx->ether_type != cpu_to_be16(ETH_P_IP)) + return -EOPNOTSUPP; + if (tunnel->collect_md) return -EOPNOTSUPP; diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index 042d743edb6c..d063add01f52 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -1852,6 +1852,9 @@ static int ip6_tnl_fill_forward_path(struct net_device_path_ctx *ctx, struct flowi6 fl6; int err; + if (ctx->ether_type != cpu_to_be16(ETH_P_IPV6)) + return -EOPNOTSUPP; + if (t->parms.flags & (IP6_TNL_F_USE_ORIG_TCLASS | IP6_TNL_F_USE_ORIG_FLOWLABEL | IP6_TNL_F_USE_ORIG_FWMARK)) diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c index 0cbde535b8ba..5f166da3b09b 100644 --- a/net/netfilter/nf_flow_table_path.c +++ b/net/netfilter/nf_flow_table_path.c @@ -44,13 +44,15 @@ static bool nft_is_valid_ether_device(const struct net_device *dev) static int nft_dev_fill_forward_path(const struct dst_entry *dst_cache, const struct nf_conn *ct, - enum ip_conntrack_dir dir, u8 *ha, + enum ip_conntrack_dir dir, + u8 *ha, __be16 ether_type, struct net_device_path_stack *stack) { const void *daddr = &ct->tuplehash[!dir].tuple.src.u3; struct net_device *dev = dst_cache->dev; struct net_device_path_ctx ctx = { .dev = dev, + .ether_type = ether_type, }; struct neighbour *n; u8 nud_state; @@ -228,7 +230,7 @@ static int nft_dev_forward_path(const struct nft_pktinfo *pkt, unsigned char ha[ETH_ALEN]; int i; - if (nft_dev_fill_forward_path(dst, ct, dir, ha, &stack) < 0 || + if (nft_dev_fill_forward_path(dst, ct, dir, ha, pkt->ethertype, &stack) < 0 || nft_dev_path_info(&stack, &info, ha, ft) < 0) return -ENOENT; From cb1d3ae6a7852e41a14b9645b44133b74bc19337 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 6 Aug 2026 23:09:14 +0200 Subject: [PATCH 1150/1433] netfilter: flowtable: rename tun.l3_proto to tun.inner_proto This field refers to the inner protocol that is encapsulated by the tunnel header, just a comestic change. No functional changes are expected. Acked-by: Lorenzo Bianconi Signed-off-by: Pablo Neira Ayuso --- include/linux/netdevice.h | 2 +- include/net/netfilter/nf_flow_table.h | 2 +- net/ipv4/ipip.c | 2 +- net/ipv6/ip6_tunnel.c | 2 +- net/netfilter/nf_flow_table_ip.c | 6 +++--- net/netfilter/nf_flow_table_path.c | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 2327a2703b83..7f5c2323146d 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -904,7 +904,7 @@ struct net_device_path { struct in6_addr dst_v6; }; - u8 l3_proto; + u8 inner_proto; } tun; struct { enum { diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h index a090ec3ffef2..f2e2771f188f 100644 --- a/include/net/netfilter/nf_flow_table.h +++ b/include/net/netfilter/nf_flow_table.h @@ -117,7 +117,7 @@ struct flow_offload_tunnel { struct in6_addr dst_v6; }; - u8 l3_proto; + u8 inner_proto; }; struct flow_offload_tuple { diff --git a/net/ipv4/ipip.c b/net/ipv4/ipip.c index 62a374079bfc..1630325c77d3 100644 --- a/net/ipv4/ipip.c +++ b/net/ipv4/ipip.c @@ -378,7 +378,7 @@ static int ipip_fill_forward_path(struct net_device_path_ctx *ctx, path->type = DEV_PATH_TUN; path->tun.src_v4.s_addr = tiph->saddr; path->tun.dst_v4.s_addr = tiph->daddr; - path->tun.l3_proto = IPPROTO_IPIP; + path->tun.inner_proto = IPPROTO_IPIP; path->tun.dst = &rt->dst; path->dev = ctx->dev; diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index d063add01f52..143d061fb48c 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -1875,7 +1875,7 @@ static int ip6_tnl_fill_forward_path(struct net_device_path_ctx *ctx, path->type = DEV_PATH_TUN; path->tun.src_v6 = fl6.saddr; path->tun.dst_v6 = fl6.daddr; - path->tun.l3_proto = IPPROTO_IPV6; + path->tun.inner_proto = IPPROTO_IPV6; path->tun.dst = dst; path->dev = ctx->dev; ctx->dev = dst->dev; diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c index 78d1862ce783..a03946a5c2e7 100644 --- a/net/netfilter/nf_flow_table_ip.c +++ b/net/netfilter/nf_flow_table_ip.c @@ -197,7 +197,7 @@ static void nf_flow_tuple_encap(struct nf_flowtable_ctx *ctx, if (ctx->tun.proto == IPPROTO_IPIP) { tuple->tun.dst_v4.s_addr = iph->daddr; tuple->tun.src_v4.s_addr = iph->saddr; - tuple->tun.l3_proto = IPPROTO_IPIP; + tuple->tun.inner_proto = IPPROTO_IPIP; } break; case htons(ETH_P_IPV6): @@ -205,7 +205,7 @@ static void nf_flow_tuple_encap(struct nf_flowtable_ctx *ctx, if (ctx->tun.proto == IPPROTO_IPV6) { tuple->tun.dst_v6 = ip6h->daddr; tuple->tun.src_v6 = ip6h->saddr; - tuple->tun.l3_proto = IPPROTO_IPV6; + tuple->tun.inner_proto = IPPROTO_IPV6; } break; default: @@ -612,7 +612,7 @@ static int nf_flow_tunnel_ipip_push(struct net *net, struct sk_buff *skb, iph->version = 4; iph->ihl = sizeof(*iph) >> 2; iph->frag_off = ip_mtu_locked(&rt->dst) ? 0 : frag_off; - iph->protocol = tuple->tun.l3_proto; + iph->protocol = tuple->tun.inner_proto; iph->tos = tos; iph->daddr = tuple->tun.src_v4.s_addr; iph->saddr = tuple->tun.dst_v4.s_addr; diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c index 5f166da3b09b..1e55644f2edb 100644 --- a/net/netfilter/nf_flow_table_path.c +++ b/net/netfilter/nf_flow_table_path.c @@ -133,7 +133,7 @@ static int nft_dev_path_info(struct net_device_path_stack *stack, info->tun.src_v6 = path->tun.src_v6; info->tun.dst_v6 = path->tun.dst_v6; - info->tun.l3_proto = path->tun.l3_proto; + info->tun.inner_proto = path->tun.inner_proto; info->tun_dst = path->tun.dst; info->num_tuns++; } else { @@ -245,7 +245,7 @@ static int nft_dev_forward_path(const struct nft_pktinfo *pkt, if (info.num_tuns) { route->tuple[!dir].in.tun.src_v6 = info.tun.dst_v6; route->tuple[!dir].in.tun.dst_v6 = info.tun.src_v6; - route->tuple[!dir].in.tun.l3_proto = info.tun.l3_proto; + route->tuple[!dir].in.tun.inner_proto = info.tun.inner_proto; route->tuple[!dir].in.num_tuns = info.num_tuns; dst_release(route->tuple[dir].dst); route->tuple[dir].dst = info.tun_dst; From 548c0fbcc381f4265e7423dddb2965d194153eb3 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 6 Aug 2026 23:09:28 +0200 Subject: [PATCH 1151/1433] netfilter: flowtable: rename ctx.tun.proto to ctx.tun.inner_proto For consistency with the tun.l3proto rename, use same name field. No functional changes are intended. Acked-by: Lorenzo Bianconi Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_flow_table_ip.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c index a03946a5c2e7..7692ae7aa853 100644 --- a/net/netfilter/nf_flow_table_ip.c +++ b/net/netfilter/nf_flow_table_ip.c @@ -153,7 +153,7 @@ struct nf_flowtable_ctx { /* Tunnel IP header size */ u32 hdr_size; /* IP tunnel protocol */ - u8 proto; + u8 inner_proto; } tun; }; @@ -194,7 +194,7 @@ static void nf_flow_tuple_encap(struct nf_flowtable_ctx *ctx, switch (inner_proto) { case htons(ETH_P_IP): iph = (struct iphdr *)(skb_network_header(skb) + offset); - if (ctx->tun.proto == IPPROTO_IPIP) { + if (ctx->tun.inner_proto == IPPROTO_IPIP) { tuple->tun.dst_v4.s_addr = iph->daddr; tuple->tun.src_v4.s_addr = iph->saddr; tuple->tun.inner_proto = IPPROTO_IPIP; @@ -202,7 +202,7 @@ static void nf_flow_tuple_encap(struct nf_flowtable_ctx *ctx, break; case htons(ETH_P_IPV6): ip6h = (struct ipv6hdr *)(skb_network_header(skb) + offset); - if (ctx->tun.proto == IPPROTO_IPV6) { + if (ctx->tun.inner_proto == IPPROTO_IPV6) { tuple->tun.dst_v6 = ip6h->daddr; tuple->tun.src_v6 = ip6h->saddr; tuple->tun.inner_proto = IPPROTO_IPV6; @@ -329,7 +329,7 @@ static bool nf_flow_ip4_tunnel_proto(struct nf_flowtable_ctx *ctx, return false; if (iph->protocol == IPPROTO_IPIP) { - ctx->tun.proto = iph->protocol; + ctx->tun.inner_proto = iph->protocol; ctx->tun.hdr_size = size; ctx->offset += ctx->tun.hdr_size; } @@ -354,7 +354,7 @@ static bool nf_flow_ip6_tunnel_proto(struct nf_flowtable_ctx *ctx, return false; if (ip6h->nexthdr == IPPROTO_IPV6) { - ctx->tun.proto = ip6h->nexthdr; + ctx->tun.inner_proto = ip6h->nexthdr; ctx->tun.hdr_size = sizeof(*ip6h); ctx->offset += ctx->tun.hdr_size; } @@ -368,8 +368,8 @@ static bool nf_flow_ip6_tunnel_proto(struct nf_flowtable_ctx *ctx, static void nf_flow_ip_tunnel_pop(struct nf_flowtable_ctx *ctx, struct sk_buff *skb) { - if (ctx->tun.proto != IPPROTO_IPIP && - ctx->tun.proto != IPPROTO_IPV6) + if (ctx->tun.inner_proto != IPPROTO_IPIP && + ctx->tun.inner_proto != IPPROTO_IPV6) return; skb_pull(skb, ctx->tun.hdr_size); From ec7a0f285041859d74f7ab7e2e6573d53df6b185 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 6 Aug 2026 23:10:38 +0200 Subject: [PATCH 1152/1433] netfilter: flowtable: store ethertype in flowtable context Add a new field to store the ethertype of the packet, skipping layer 2 encapsulation. Store the ether_type in the context after parsing the layer 2 header for the first time and then use it later on. Acked-by: Lorenzo Bianconi Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_flow_table_ip.c | 47 +++++++++++++++++++------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c index 7692ae7aa853..3f417a43bd12 100644 --- a/net/netfilter/nf_flow_table_ip.c +++ b/net/netfilter/nf_flow_table_ip.c @@ -147,6 +147,7 @@ static bool ip_has_options(unsigned int thoff) struct nf_flowtable_ctx { const struct net_device *in; + __be16 ether_type; u32 offset; u32 hdrsize; struct { @@ -161,7 +162,6 @@ static void nf_flow_tuple_encap(struct nf_flowtable_ctx *ctx, struct sk_buff *skb, struct flow_offload_tuple *tuple) { - __be16 inner_proto = skb->protocol; struct vlan_ethhdr *veth; struct pppoe_hdr *phdr; struct ipv6hdr *ip6h; @@ -179,19 +179,17 @@ static void nf_flow_tuple_encap(struct nf_flowtable_ctx *ctx, veth = (struct vlan_ethhdr *)skb_mac_header(skb); tuple->encap[i].id = ntohs(veth->h_vlan_TCI); tuple->encap[i].proto = skb->protocol; - inner_proto = veth->h_vlan_encapsulated_proto; offset += VLAN_HLEN; break; case htons(ETH_P_PPP_SES): phdr = (struct pppoe_hdr *)skb_network_header(skb); tuple->encap[i].id = ntohs(phdr->sid); tuple->encap[i].proto = skb->protocol; - inner_proto = *((__be16 *)(phdr + 1)); offset += PPPOE_SES_HLEN; break; } - switch (inner_proto) { + switch (ctx->ether_type) { case htons(ETH_P_IP): iph = (struct iphdr *)(skb_network_header(skb) + offset); if (ctx->tun.inner_proto == IPPROTO_IPIP) { @@ -377,10 +375,10 @@ static void nf_flow_ip_tunnel_pop(struct nf_flowtable_ctx *ctx, } static bool nf_flow_skb_encap_protocol(struct nf_flowtable_ctx *ctx, - struct sk_buff *skb, __be16 proto) + struct sk_buff *skb) { - __be16 inner_proto = skb->protocol; struct vlan_ethhdr *veth; + __be16 ether_type; bool ret = false; switch (skb->protocol) { @@ -389,22 +387,27 @@ static bool nf_flow_skb_encap_protocol(struct nf_flowtable_ctx *ctx, return false; veth = (struct vlan_ethhdr *)skb_mac_header(skb); - if (veth->h_vlan_encapsulated_proto == proto) { - ctx->offset += VLAN_HLEN; - inner_proto = proto; - ret = true; - } + ctx->ether_type = veth->h_vlan_encapsulated_proto; + ctx->offset += VLAN_HLEN; + ret = true; break; case htons(ETH_P_PPP_SES): - if (nf_flow_pppoe_proto(skb, &inner_proto) && - inner_proto == proto) { - ctx->offset += PPPOE_SES_HLEN; - ret = true; - } + if (!nf_flow_pppoe_proto(skb, ðer_type)) + return false; + + ctx->ether_type = ether_type; + ctx->offset += PPPOE_SES_HLEN; + ret = true; break; + case htons(ETH_P_IP): + case htons(ETH_P_IPV6): + ctx->ether_type = skb->protocol; + break; + default: + return false; } - switch (inner_proto) { + switch (ctx->ether_type) { case htons(ETH_P_IP): ret = nf_flow_ip4_tunnel_proto(ctx, skb); break; @@ -456,7 +459,10 @@ nf_flow_offload_lookup(struct nf_flowtable_ctx *ctx, { struct flow_offload_tuple tuple = {}; - if (!nf_flow_skb_encap_protocol(ctx, skb, htons(ETH_P_IP))) + if (!nf_flow_skb_encap_protocol(ctx, skb)) + return NULL; + + if (unlikely(ctx->ether_type != htons(ETH_P_IP))) return NULL; if (nf_flow_tuple_ip(ctx, skb, &tuple) < 0) @@ -1103,7 +1109,10 @@ nf_flow_offload_ipv6_lookup(struct nf_flowtable_ctx *ctx, { struct flow_offload_tuple tuple = {}; - if (!nf_flow_skb_encap_protocol(ctx, skb, htons(ETH_P_IPV6))) + if (!nf_flow_skb_encap_protocol(ctx, skb)) + return NULL; + + if (unlikely(ctx->ether_type != htons(ETH_P_IPV6))) return NULL; if (nf_flow_tuple_ipv6(ctx, skb, &tuple) < 0) From 609268d93dd1c64723f49c5c21fe530d8129801c Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 6 Aug 2026 23:19:56 +0200 Subject: [PATCH 1153/1433] netfilter: flowtable: move ipv4 and ipv6 xmit path to function Move the existing ipv4 and ipv6 transmit path to functions in preparation of the IPv4 over IPv6 and SIT support. Acked-by: Lorenzo Bianconi Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_flow_table_ip.c | 92 +++++++++++++++++++------------- 1 file changed, 54 insertions(+), 38 deletions(-) diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c index 3f417a43bd12..ff45f17f3c4a 100644 --- a/net/netfilter/nf_flow_table_ip.c +++ b/net/netfilter/nf_flow_table_ip.c @@ -801,33 +801,17 @@ static unsigned int nf_flow_queue_xmit(struct net *net, struct sk_buff *skb, return NF_STOLEN; } -unsigned int -nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb, - const struct nf_hook_state *state) +static int nf_flow_queue_xmit4(struct sk_buff *skb, + struct flow_offload_tuple_rhash *tuplehash, + const struct nf_hook_state *state) { - struct flow_offload_tuple_rhash *tuplehash; - struct nf_flowtable *flow_table = priv; struct flow_offload_tuple *other_tuple; enum flow_offload_tuple_dir dir; - struct nf_flowtable_ctx ctx = { - .in = state->in, - }; struct nf_flow_xmit xmit = {}; struct flow_offload *flow; struct neighbour *neigh; struct rtable *rt; __be32 ip_daddr; - int ret; - - tuplehash = nf_flow_offload_lookup(&ctx, flow_table, skb); - if (!tuplehash) - return NF_ACCEPT; - - ret = nf_flow_offload_forward(&ctx, flow_table, tuplehash, skb); - if (ret < 0) - return NF_DROP; - else if (ret == 0) - return NF_ACCEPT; if (unlikely(tuplehash->tuple.xmit_type == FLOW_OFFLOAD_XMIT_XFRM)) { rt = dst_rtable(tuplehash->tuple.dst_cache); @@ -881,6 +865,30 @@ nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb, return nf_flow_queue_xmit(state->net, skb, &xmit); } + +unsigned int +nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb, + const struct nf_hook_state *state) +{ + struct flow_offload_tuple_rhash *tuplehash; + struct nf_flowtable *flow_table = priv; + struct nf_flowtable_ctx ctx = { + .in = state->in, + }; + int ret; + + tuplehash = nf_flow_offload_lookup(&ctx, flow_table, skb); + if (!tuplehash) + return NF_ACCEPT; + + ret = nf_flow_offload_forward(&ctx, flow_table, tuplehash, skb); + if (ret < 0) + return NF_DROP; + else if (ret == 0) + return NF_ACCEPT; + + return nf_flow_queue_xmit4(skb, tuplehash, state); +} EXPORT_SYMBOL_GPL(nf_flow_offload_ip_hook); static void nf_flow_nat_ipv6_tcp(struct sk_buff *skb, unsigned int thoff, @@ -1121,33 +1129,17 @@ nf_flow_offload_ipv6_lookup(struct nf_flowtable_ctx *ctx, return flow_offload_lookup(flow_table, &tuple); } -unsigned int -nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb, - const struct nf_hook_state *state) +static int nf_flow_queue_xmit6(struct sk_buff *skb, + struct flow_offload_tuple_rhash *tuplehash, + const struct nf_hook_state *state) { - struct flow_offload_tuple_rhash *tuplehash; - struct nf_flowtable *flow_table = priv; struct flow_offload_tuple *other_tuple; enum flow_offload_tuple_dir dir; - struct nf_flowtable_ctx ctx = { - .in = state->in, - }; struct nf_flow_xmit xmit = {}; struct in6_addr *ip6_daddr; struct flow_offload *flow; struct neighbour *neigh; struct rt6_info *rt; - int ret; - - tuplehash = nf_flow_offload_ipv6_lookup(&ctx, flow_table, skb); - if (tuplehash == NULL) - return NF_ACCEPT; - - ret = nf_flow_offload_ipv6_forward(&ctx, flow_table, tuplehash, skb); - if (ret < 0) - return NF_DROP; - else if (ret == 0) - return NF_ACCEPT; if (unlikely(tuplehash->tuple.xmit_type == FLOW_OFFLOAD_XMIT_XFRM)) { rt = dst_rt6_info(tuplehash->tuple.dst_cache); @@ -1202,4 +1194,28 @@ nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb, return nf_flow_queue_xmit(state->net, skb, &xmit); } + +unsigned int +nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb, + const struct nf_hook_state *state) +{ + struct flow_offload_tuple_rhash *tuplehash; + struct nf_flowtable *flow_table = priv; + struct nf_flowtable_ctx ctx = { + .in = state->in, + }; + int ret; + + tuplehash = nf_flow_offload_ipv6_lookup(&ctx, flow_table, skb); + if (!tuplehash) + return NF_ACCEPT; + + ret = nf_flow_offload_ipv6_forward(&ctx, flow_table, tuplehash, skb); + if (ret < 0) + return NF_DROP; + else if (ret == 0) + return NF_ACCEPT; + + return nf_flow_queue_xmit6(skb, tuplehash, state); +} EXPORT_SYMBOL_GPL(nf_flow_offload_ipv6_hook); From 0e42d4039cb41ae3101fc8df361df418e6ce59eb Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 6 Aug 2026 23:54:06 +0200 Subject: [PATCH 1154/1433] netfilter: flowtable: detach layer 2 encapsulation parser from lookup Move the layer 2 encapsulation header parser out of the lookup function to prepare for IPv4 over IPv6 and SIT. Acked-by: Lorenzo Bianconi Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_flow_table_ip.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c index ff45f17f3c4a..c8c29a9a1684 100644 --- a/net/netfilter/nf_flow_table_ip.c +++ b/net/netfilter/nf_flow_table_ip.c @@ -459,12 +459,6 @@ nf_flow_offload_lookup(struct nf_flowtable_ctx *ctx, { struct flow_offload_tuple tuple = {}; - if (!nf_flow_skb_encap_protocol(ctx, skb)) - return NULL; - - if (unlikely(ctx->ether_type != htons(ETH_P_IP))) - return NULL; - if (nf_flow_tuple_ip(ctx, skb, &tuple) < 0) return NULL; @@ -877,6 +871,12 @@ nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb, }; int ret; + if (!nf_flow_skb_encap_protocol(&ctx, skb)) + return NF_ACCEPT; + + if (unlikely(ctx.ether_type != htons(ETH_P_IP))) + return NF_ACCEPT; + tuplehash = nf_flow_offload_lookup(&ctx, flow_table, skb); if (!tuplehash) return NF_ACCEPT; @@ -1117,12 +1117,6 @@ nf_flow_offload_ipv6_lookup(struct nf_flowtable_ctx *ctx, { struct flow_offload_tuple tuple = {}; - if (!nf_flow_skb_encap_protocol(ctx, skb)) - return NULL; - - if (unlikely(ctx->ether_type != htons(ETH_P_IPV6))) - return NULL; - if (nf_flow_tuple_ipv6(ctx, skb, &tuple) < 0) return NULL; @@ -1206,6 +1200,12 @@ nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb, }; int ret; + if (!nf_flow_skb_encap_protocol(&ctx, skb)) + return NF_ACCEPT; + + if (unlikely(ctx.ether_type != htons(ETH_P_IPV6))) + return NF_ACCEPT; + tuplehash = nf_flow_offload_ipv6_lookup(&ctx, flow_table, skb); if (!tuplehash) return NF_ACCEPT; From 3679da4ad8be84cddaf40bc307fef1fe13e051ff Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Mon, 10 Aug 2026 00:17:41 +0200 Subject: [PATCH 1155/1433] netfilter: nft_ct: move custom expectation support to helper Originally, the ct expectation support called nf_ct_helper_ext_add() for confirmed conntracks, which is invalid, triggering a splat. This was fixed by commit 1710eb913bdc ("netfilter: nft_ct: skip expectations for confirmed conntrack") which restricted it to unconfirmed conntracks. However, early insertion of expectations into the expectations list when the conntrack is unconfirmed leads to stale entries pointing to the wrong hlist_head through .pprev due to ct extension reallocation. Commit 7c9664351980 ("netfilter: move nat hlist_head to nf_conn") moved the nat hlist_head to nf_conn for this reason: 1. ... 2. When reallocation of extension area occurs we need to fixup the bysource hash head via hlist_replace_rcu. I'd rather not increase the size of the struct nf_conn for this feature has very limited scope: only one expectation can be created at a time given expect_clash() will make nf_ct_expect_related() reports EBUSY. For this reason, relax nf_ct_expect_related() not to drop packets in case expectation creation fails, therefore, expectation creation becomes best effort. To address this issue, add an internal ct helper and attach it to the conntrack entry to streamline the custom ct expectation support with existing ct helpers. Expose a new nf_conntrack_helper_release() function to release the internal helper that is allocated and attached to the conntrack entry to create the custom expectations. The nft_ct module removal always waits for rcu grace period, then the NULL helper callback is observed after this. This patch also restricts the creation of expectations to different helpers other than this custom helper that is created for this type of expectations. Fixes: 857b46027d6f ("netfilter: nft_ct: add ct expectations support") Reported-by: Jaeyeong Lee Link: https://patch.msgid.link/20260715144755.00ea7dfcd9f@proton.me Signed-off-by: Pablo Neira Ayuso --- include/net/netfilter/nf_conntrack_helper.h | 1 + net/netfilter/nf_conntrack_helper.c | 14 +- net/netfilter/nft_ct.c | 169 +++++++++++++++----- 3 files changed, 136 insertions(+), 48 deletions(-) diff --git a/include/net/netfilter/nf_conntrack_helper.h b/include/net/netfilter/nf_conntrack_helper.h index bc5427d239f4..335b8c43694f 100644 --- a/include/net/netfilter/nf_conntrack_helper.h +++ b/include/net/netfilter/nf_conntrack_helper.h @@ -106,6 +106,7 @@ void nf_ct_helper_init(struct nf_conntrack_helper *helper, int nf_conntrack_helper_register(struct nf_conntrack_helper *, struct nf_conntrack_helper **); int __nf_conntrack_helper_register(struct nf_conntrack_helper *); void nf_conntrack_helper_unregister(struct nf_conntrack_helper *); +void nf_conntrack_helper_release(struct nf_conntrack_helper *); int nf_conntrack_helpers_register(struct nf_conntrack_helper *, unsigned int, struct nf_conntrack_helper **); diff --git a/net/netfilter/nf_conntrack_helper.c b/net/netfilter/nf_conntrack_helper.c index 506c58034761..c30ae3f203be 100644 --- a/net/netfilter/nf_conntrack_helper.c +++ b/net/netfilter/nf_conntrack_helper.c @@ -448,6 +448,15 @@ static bool expect_iter_me(struct nf_conntrack_expect *exp, void *data) return this == me; } +void nf_conntrack_helper_release(struct nf_conntrack_helper *me) +{ + nf_ct_expect_iterate_destroy(expect_iter_me, me); + + if (refcount_dec_and_test(&me->ct_refcnt)) + kfree_rcu(me, rcu); +} +EXPORT_SYMBOL_GPL(nf_conntrack_helper_release); + void nf_conntrack_helper_unregister(struct nf_conntrack_helper *me) { mutex_lock(&nf_ct_helper_mutex); @@ -463,10 +472,7 @@ void nf_conntrack_helper_unregister(struct nf_conntrack_helper *me) */ synchronize_rcu(); - nf_ct_expect_iterate_destroy(expect_iter_me, me); - - if (refcount_dec_and_test(&me->ct_refcnt)) - kfree_rcu(me, rcu); + nf_conntrack_helper_release(me); } EXPORT_SYMBOL_GPL(nf_conntrack_helper_unregister); diff --git a/net/netfilter/nft_ct.c b/net/netfilter/nft_ct.c index 358b9287e12e..9dbf127df9c8 100644 --- a/net/netfilter/nft_ct.c +++ b/net/netfilter/nft_ct.c @@ -1213,6 +1213,8 @@ struct nft_ct_expect_obj { u8 l4proto; u8 size; u32 timeout; + + struct nf_conntrack_helper *helper; }; static int nft_ct_expect_timeout_get(const struct nlattr *attr, u32 *val) @@ -1226,6 +1228,93 @@ static int nft_ct_expect_timeout_get(const struct nlattr *attr, u32 *val) return 0; } +#if IS_ENABLED(CONFIG_NF_NAT) +static void nft_ct_nat_follow_master(struct nf_conn *ct, struct nf_conntrack_expect *this) +{ + const struct nf_ct_helper_expectfn *expfn; + + expfn = nf_ct_helper_expectfn_find_by_name("nat-follow-master"); + if (expfn) + expfn->expectfn(ct, this); +} +#endif + +struct nft_ct_expect_data { + struct nft_ct_expect_obj obj; + enum ip_conntrack_dir dir; +}; + +static int ct_expect_help(struct sk_buff *skb, unsigned int protoff, + struct nf_conn *ct, enum ip_conntrack_info ctinfo) +{ + enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); + struct nft_ct_expect_data *expect_data; + struct nf_conntrack_expect *exp; + int ret = NF_ACCEPT; + u16 l3num; + + if (nf_ct_is_confirmed(ct)) + return NF_ACCEPT; + + expect_data = nfct_help_data(ct); + if (!expect_data) + return NF_ACCEPT; + + if (expect_data->dir != dir) + return NF_ACCEPT; + + exp = nf_ct_expect_alloc(ct); + if (!exp) + return NF_DROP; + + if (expect_data->obj.l3num == NFPROTO_INET) + l3num = nf_ct_l3num(ct); + else + l3num = expect_data->obj.l3num; + + nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, l3num, + &ct->tuplehash[!dir].tuple.src.u3, + &ct->tuplehash[!dir].tuple.dst.u3, + expect_data->obj.l4proto, NULL, &expect_data->obj.dport); + exp->timeout += expect_data->obj.timeout; + +#if IS_ENABLED(CONFIG_NF_NAT) + if (ct->status & IPS_NAT_MASK) { + exp->saved_proto.tcp.port = expect_data->obj.dport; + exp->dir = !dir; + exp->expectfn = nft_ct_nat_follow_master; + } +#endif + if (nf_ct_expect_related(exp, 0) != 0) + ret = NF_ACCEPT; + + nf_ct_expect_put(exp); + + return ret; +} + +static int nft_ct_expect_helper_alloc(struct nft_ct_expect_obj *priv) +{ + struct nf_conntrack_helper *ct_expect_helper; + + ct_expect_helper = kzalloc_obj(struct nf_conntrack_helper, + GFP_KERNEL_ACCOUNT); + if (!ct_expect_helper) + return -ENOMEM; + + snprintf(ct_expect_helper->name, sizeof(ct_expect_helper->name), "%s", + "nft_ct_expect"); + ct_expect_helper->me = THIS_MODULE; + ct_expect_helper->expect_policy[NF_CT_EXPECT_CLASS_DEFAULT].max_expected = priv->size; + rcu_assign_pointer(ct_expect_helper->help, ct_expect_help); + refcount_set(&ct_expect_helper->ct_refcnt, 1); + + /* No need to register this helper, this is internal. */ + priv->helper = ct_expect_helper; + + return 0; +} + static int nft_ct_expect_obj_init(const struct nft_ctx *ctx, const struct nlattr * const tb[], struct nft_object *obj) @@ -1233,6 +1322,8 @@ static int nft_ct_expect_obj_init(const struct nft_ctx *ctx, struct nft_ct_expect_obj *priv = nft_obj_data(obj); int err; + NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nft_ct_expect_data)); + if (!tb[NFTA_CT_EXPECT_L4PROTO] || !tb[NFTA_CT_EXPECT_DPORT] || !tb[NFTA_CT_EXPECT_TIMEOUT] || @@ -1272,13 +1363,31 @@ static int nft_ct_expect_obj_init(const struct nft_ctx *ctx, priv->dport = nla_get_be16(tb[NFTA_CT_EXPECT_DPORT]); priv->size = nla_get_u8(tb[NFTA_CT_EXPECT_SIZE]); + if (!priv->size) + priv->size = NF_CT_EXPECT_MAX_CNT; - return nf_ct_netns_get(ctx->net, ctx->family); + err = nf_ct_netns_get(ctx->net, ctx->family); + if (err < 0) + return err; + + err = nft_ct_expect_helper_alloc(priv); + if (err < 0) { + nf_ct_netns_put(ctx->net, ctx->family); + return err; + } + + return err; } static void nft_ct_expect_obj_destroy(const struct nft_ctx *ctx, - struct nft_object *obj) + struct nft_object *obj) { + const struct nft_ct_expect_obj *priv = nft_obj_data(obj); + struct nf_conntrack_helper *me = priv->helper; + + /* This helper is going away, disable it. */ + rcu_assign_pointer(me->help, NULL); + nf_conntrack_helper_release(me); nf_ct_netns_put(ctx->net, ctx->family); } @@ -1297,27 +1406,14 @@ static int nft_ct_expect_obj_dump(struct sk_buff *skb, return 0; } -#if IS_ENABLED(CONFIG_NF_NAT) -static void nft_ct_nat_follow_master(struct nf_conn *ct, struct nf_conntrack_expect *this) -{ - const struct nf_ct_helper_expectfn *expfn; - - expfn = nf_ct_helper_expectfn_find_by_name("nat-follow-master"); - if (expfn) - expfn->expectfn(ct, this); -} -#endif - static void nft_ct_expect_obj_eval(struct nft_object *obj, struct nft_regs *regs, const struct nft_pktinfo *pkt) { const struct nft_ct_expect_obj *priv = nft_obj_data(obj); - struct nf_conntrack_expect *exp; + struct nft_ct_expect_data *expect_data; enum ip_conntrack_info ctinfo; struct nf_conn_help *help; - enum ip_conntrack_dir dir; - u16 l3num = priv->l3num; struct nf_conn *ct; ct = nf_ct_get(pkt->skb, &ctinfo); @@ -1325,45 +1421,30 @@ static void nft_ct_expect_obj_eval(struct nft_object *obj, regs->verdict.code = NFT_BREAK; return; } - dir = CTINFO2DIR(ctinfo); help = nfct_help(ct); - if (!help) - help = nf_ct_helper_ext_add(ct, GFP_ATOMIC); + if (help) { + regs->verdict.code = NFT_BREAK; + return; + } + + help = nf_ct_helper_ext_add(ct, GFP_ATOMIC); if (!help) { regs->verdict.code = NF_DROP; return; } - if (help->expecting[NF_CT_EXPECT_CLASS_DEFAULT] >= priv->size) { + expect_data = nfct_help_data(ct); + if (!expect_data) { regs->verdict.code = NFT_BREAK; return; } - if (l3num == NFPROTO_INET) - l3num = nf_ct_l3num(ct); + expect_data->obj = *priv; + expect_data->obj.helper = NULL; + expect_data->dir = CTINFO2DIR(ctinfo); - exp = nf_ct_expect_alloc(ct); - if (exp == NULL) { - regs->verdict.code = NF_DROP; - return; - } - nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, l3num, - &ct->tuplehash[!dir].tuple.src.u3, - &ct->tuplehash[!dir].tuple.dst.u3, - priv->l4proto, NULL, &priv->dport); - exp->timeout += priv->timeout; - -#if IS_ENABLED(CONFIG_NF_NAT) - if (ct->status & IPS_NAT_MASK) { - exp->saved_proto.tcp.port = priv->dport; - exp->dir = !dir; - exp->expectfn = nft_ct_nat_follow_master; - } -#endif - if (nf_ct_expect_related(exp, 0) != 0) - regs->verdict.code = NF_DROP; - - nf_ct_expect_put(exp); + if (help && refcount_inc_not_zero(&priv->helper->ct_refcnt)) + rcu_assign_pointer(help->helper, priv->helper); } static const struct nla_policy nft_ct_expect_policy[NFTA_CT_EXPECT_MAX + 1] = { From 73df81b38cb7f70a347a97370386378a86e53019 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Mon, 10 Aug 2026 10:18:22 +0200 Subject: [PATCH 1156/1433] netfilter: conntrack: always lower timeout for non-closing RST packets The existing check might extend the timeout if the ESTABLISHED timeout has been tuned to be lower than UNACK via sysctl. Reported by sashiko. Fixes: bf80e6802273 ("netfilter: conntrack: tcp: use UNACK timeout for non-closing RST packets") Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_conntrack_proto_tcp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/netfilter/nf_conntrack_proto_tcp.c b/net/netfilter/nf_conntrack_proto_tcp.c index 723e946a78f4..1300c236dd41 100644 --- a/net/netfilter/nf_conntrack_proto_tcp.c +++ b/net/netfilter/nf_conntrack_proto_tcp.c @@ -1282,7 +1282,8 @@ int nf_conntrack_tcp_packet(struct nf_conn *ct, timeouts[new_state] > timeouts[TCP_CONNTRACK_RETRANS]) timeout = timeouts[TCP_CONNTRACK_RETRANS]; else if (unlikely(index == TCP_RST_SET && - new_state == TCP_CONNTRACK_ESTABLISHED)) + new_state == TCP_CONNTRACK_ESTABLISHED) && + timeouts[new_state] > timeouts[TCP_CONNTRACK_UNACK]) timeout = timeouts[TCP_CONNTRACK_UNACK]; else if ((ct->proto.tcp.seen[0].flags | ct->proto.tcp.seen[1].flags) & IP_CT_TCP_FLAG_DATA_UNACKNOWLEDGED && From e765c95faa10a8b4e6e9ac7d338eca9662dc56da Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Mon, 10 Aug 2026 12:31:24 +0200 Subject: [PATCH 1157/1433] netfilter: nf_conntrack_expect: bail out on insert dead expectations If the NF_CT_EXPECT_DEAD expectation flag is set on, bail out on insertion. Moreover, add also DEBUG_NET_WARN_ON_ONCE() since this should not ever happen. This is hardening commit b8b09dc2bf35 ("netfilter: nf_conntrack_expect: use conntrack GC to reap expectations"). Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_conntrack_expect.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c index 10b130a7b230..f1f0c582db5d 100644 --- a/net/netfilter/nf_conntrack_expect.c +++ b/net/netfilter/nf_conntrack_expect.c @@ -528,6 +528,12 @@ int nf_ct_expect_related_report(struct nf_conntrack_expect *expect, int ret; spin_lock_bh(&nf_conntrack_expect_lock); + if (expect->flags & NF_CT_EXPECT_DEAD) { + DEBUG_NET_WARN_ON_ONCE(1); + ret = -EINVAL; + goto out; + } + master_help = nfct_help(expect->master); if (!master_help) { ret = -ESHUTDOWN; From 736fb8632217bd27da6b2e3f1f8cbbe3193fc2d8 Mon Sep 17 00:00:00 2001 From: Qingshuang Fu Date: Fri, 7 Aug 2026 15:59:28 +0800 Subject: [PATCH 1158/1433] selftests: netfilter: conntrack_dump_flush: remove unused variables and fix typo Remove unused 'rplnlh' in conntrack_data_insert(), and remove unused 'rplnlh' and 'nest' variables in conntrack_count_zone() and conntrack_flush_zone(). These variables were declared but never used since their introduction. Also fix typo: rename misspelled conntracK_count_zone() to conntrack_count_zone(). Signed-off-by: Qingshuang Fu Reviewed-by: Fernando Fernandez Mancera Reviewed-by: Hangbin Liu Signed-off-by: Pablo Neira Ayuso --- .../net/netfilter/conntrack_dump_flush.c | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/tools/testing/selftests/net/netfilter/conntrack_dump_flush.c b/tools/testing/selftests/net/netfilter/conntrack_dump_flush.c index 5cecb8a1bc94..31b8250ddc53 100644 --- a/tools/testing/selftests/net/netfilter/conntrack_dump_flush.c +++ b/tools/testing/selftests/net/netfilter/conntrack_dump_flush.c @@ -102,7 +102,6 @@ static int conntrack_data_insert(struct mnl_socket *sock, struct nlmsghdr *nlh, uint16_t zone) { char buf[MNL_SOCKET_BUFFER_SIZE]; - struct nlmsghdr *rplnlh; unsigned int portid; int ret; @@ -216,12 +215,11 @@ static int count_entries(const struct nlmsghdr *nlh, void *data) return MNL_CB_OK; } -static int conntracK_count_zone(struct mnl_socket *sock, uint16_t zone) +static int conntrack_count_zone(struct mnl_socket *sock, uint16_t zone) { char buf[MNL_SOCKET_BUFFER_SIZE]; - struct nlmsghdr *nlh, *rplnlh; + struct nlmsghdr *nlh; struct nfgenmsg *nfh; - struct nlattr *nest; unsigned int portid; int ret; @@ -266,9 +264,8 @@ static int conntracK_count_zone(struct mnl_socket *sock, uint16_t zone) static int conntrack_flush_zone(struct mnl_socket *sock, uint16_t zone) { char buf[MNL_SOCKET_BUFFER_SIZE]; - struct nlmsghdr *nlh, *rplnlh; + struct nlmsghdr *nlh; struct nfgenmsg *nfh; - struct nlattr *nest; unsigned int portid; int ret; @@ -326,7 +323,7 @@ FIXTURE_SETUP(conntrack_dump_flush) ret = mnl_socket_bind(self->sock, 0, MNL_SOCKET_AUTOPID); EXPECT_EQ(ret, 0); - ret = conntracK_count_zone(self->sock, TEST_ZONE_ID); + ret = conntrack_count_zone(self->sock, TEST_ZONE_ID); if (ret < 0 && errno == EPERM) SKIP(return, "Needs to be run as root"); else if (ret < 0 && errno == EOPNOTSUPP) @@ -423,7 +420,7 @@ FIXTURE_SETUP(conntrack_dump_flush) NF_CT_DEFAULT_ZONE_ID); EXPECT_EQ(ret, 0); - ret = conntracK_count_zone(self->sock, TEST_ZONE_ID); + ret = conntrack_count_zone(self->sock, TEST_ZONE_ID); EXPECT_GE(ret, 2); if (ret > 2) SKIP(return, "kernel does not support filtering by zone"); @@ -437,7 +434,7 @@ TEST_F(conntrack_dump_flush, test_dump_by_zone) { int ret; - ret = conntracK_count_zone(self->sock, TEST_ZONE_ID); + ret = conntrack_count_zone(self->sock, TEST_ZONE_ID); EXPECT_EQ(ret, 2); } @@ -447,13 +444,13 @@ TEST_F(conntrack_dump_flush, test_flush_by_zone) ret = conntrack_flush_zone(self->sock, TEST_ZONE_ID); EXPECT_EQ(ret, 0); - ret = conntracK_count_zone(self->sock, TEST_ZONE_ID); + ret = conntrack_count_zone(self->sock, TEST_ZONE_ID); EXPECT_EQ(ret, 0); - ret = conntracK_count_zone(self->sock, TEST_ZONE_ID + 1); + ret = conntrack_count_zone(self->sock, TEST_ZONE_ID + 1); EXPECT_EQ(ret, 2); - ret = conntracK_count_zone(self->sock, TEST_ZONE_ID + 2); + ret = conntrack_count_zone(self->sock, TEST_ZONE_ID + 2); EXPECT_EQ(ret, 2); - ret = conntracK_count_zone(self->sock, NF_CT_DEFAULT_ZONE_ID); + ret = conntrack_count_zone(self->sock, NF_CT_DEFAULT_ZONE_ID); EXPECT_EQ(ret, 2); } @@ -463,13 +460,13 @@ TEST_F(conntrack_dump_flush, test_flush_by_zone_default) ret = conntrack_flush_zone(self->sock, NF_CT_DEFAULT_ZONE_ID); EXPECT_EQ(ret, 0); - ret = conntracK_count_zone(self->sock, TEST_ZONE_ID); + ret = conntrack_count_zone(self->sock, TEST_ZONE_ID); EXPECT_EQ(ret, 2); - ret = conntracK_count_zone(self->sock, TEST_ZONE_ID + 1); + ret = conntrack_count_zone(self->sock, TEST_ZONE_ID + 1); EXPECT_EQ(ret, 2); - ret = conntracK_count_zone(self->sock, TEST_ZONE_ID + 2); + ret = conntrack_count_zone(self->sock, TEST_ZONE_ID + 2); EXPECT_EQ(ret, 2); - ret = conntracK_count_zone(self->sock, NF_CT_DEFAULT_ZONE_ID); + ret = conntrack_count_zone(self->sock, NF_CT_DEFAULT_ZONE_ID); EXPECT_EQ(ret, 0); } From 2373107782a98ba15931a37bbd9dd46d7213e5aa Mon Sep 17 00:00:00 2001 From: Brian Grech Date: Thu, 6 Aug 2026 10:16:45 -0500 Subject: [PATCH 1159/1433] selftests/net: fin_ack_lat: fix latency threshold typo The commit message for af8c8a450bf4 ("selftests: net: Add FIN_ACK processing order related latency spike test") states: "if the latency is larger than 1 second (spike), print a message". However the code uses a threshold of 100000 us (100 ms), not 1000000 us (1 s). The lower threshold causes false positives on slower hardware where normal connection latency occasionally exceeds 100 ms but never approaches the 1 s spike that indicates the actual FIN/ACK race bug. Fix the threshold to match the documented intent. Reviewed-by: Simon Horman Signed-off-by: Brian Grech Link: https://patch.msgid.link/20260806151645.4172900-1-bgrech@redhat.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/fin_ack_lat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/net/fin_ack_lat.c b/tools/testing/selftests/net/fin_ack_lat.c index 70187494b57a..4117332eb1a9 100644 --- a/tools/testing/selftests/net/fin_ack_lat.c +++ b/tools/testing/selftests/net/fin_ack_lat.c @@ -69,7 +69,7 @@ static void client(int port) lat = timediff(start, end); sum_lat += lat; nr_lat++; - if (lat < 100000) + if (lat < 1000000) goto close; if (getsockname(sock, (struct sockaddr *)&laddr, &len) == -1) From 9e0cd2906c9018f470b16f41c36b169c6ced16cc Mon Sep 17 00:00:00 2001 From: Artem Shimko Date: Wed, 5 Aug 2026 11:55:38 +0300 Subject: [PATCH 1160/1433] dt-bindings: vendor-prefixes: add Guangdong Dapu Telecom Co., Ltd. Add vendor prefix for Guangdong Dapu Telecom Co., Ltd. [1], a manufacturer of Ethernet PHYs, networking and other equipment. The prefix will be used in the DAP8211R(I) Gigabit Ethernet PHY binding. [1] https://www.dptel.com/ Signed-off-by: Artem Shimko Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260805085540.452260-2-a.shimko.dev@gmail.com Signed-off-by: Jakub Kicinski --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 396044f368e7..f8efecb560b6 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -457,6 +457,8 @@ patternProperties: description: Dongwoon Anatech "^dptechnics,.*": description: DPTechnics + "^dptel,.*": + description: Guangdong Dapu Telecom Co., Ltd. "^dragino,.*": description: Dragino Technology Co., Limited "^dream,.*": From 0a5a6487aecf4884735b86cd7e73f103c494407a Mon Sep 17 00:00:00 2001 From: Artem Shimko Date: Wed, 5 Aug 2026 11:55:39 +0300 Subject: [PATCH 1161/1433] dt-bindings: net: add DAPU Telecom DAP8211R(I) PHY binding Add device tree binding documentation for the DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY. The PHY supports TX and RX clock delays in 150 ps steps from 0 to 2250 ps, with a default of 1950 ps if not specified. Signed-off-by: Artem Shimko Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260805085540.452260-3-a.shimko.dev@gmail.com Signed-off-by: Jakub Kicinski --- .../bindings/net/dptel,dap8211r.yaml | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/dptel,dap8211r.yaml diff --git a/Documentation/devicetree/bindings/net/dptel,dap8211r.yaml b/Documentation/devicetree/bindings/net/dptel,dap8211r.yaml new file mode 100644 index 000000000000..4cd0f9730d95 --- /dev/null +++ b/Documentation/devicetree/bindings/net/dptel,dap8211r.yaml @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/dptel,dap8211r.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY + +maintainers: + - Artem Shimko + +description: | + The DAP8211R(I) is a Gigabit Ethernet PHY with RGMII interface, + supporting IEEE 802.3az Energy Efficient Ethernet, IEEE 1588 SyncE, + and an internal packet generator for diagnostics. + + Specifications: + - 10BASE-Te, 100BASE-TX, 1000BASE-T + - RGMII with configurable TX/RX clock delays (150 ps steps, 0-2250 ps) + - IEEE 802.3az-2010 Energy Efficient Ethernet + - IEEE 1588 SyncE support + - Internal packet generator and checker for link diagnostics + +allOf: + - $ref: ethernet-phy.yaml# + +properties: + compatible: + const: ethernet-phy-id0008.011b + + reg: + maxItems: 1 + + rx-internal-delay-ps: + description: + RGMII RX clock delay in picoseconds (0 to maximum). + multipleOf: 150 + maximum: 2250 + default: 1950 + + tx-internal-delay-ps: + description: + RGMII TX clock delay in picoseconds (0 to maximum). + multipleOf: 150 + maximum: 2250 + default: 1950 + +unevaluatedProperties: false + +examples: + - | + mdio { + #address-cells = <1>; + #size-cells = <0>; + + ethernet-phy@1 { + compatible = "ethernet-phy-id0008.011b"; + reg = <1>; + rx-internal-delay-ps = <1950>; + tx-internal-delay-ps = <1950>; + }; + }; From d90265e75584d0767aada5b900e6c82ac0f9d5b9 Mon Sep 17 00:00:00 2001 From: Artem Shimko Date: Wed, 5 Aug 2026 11:55:40 +0300 Subject: [PATCH 1162/1433] net: phy: add DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY driver Add a new PHY driver for the DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY, which is commonly used in enterprise and industrial networking applications. The driver implements extended register access via indirect addressing through corresponding registers, and provides comprehensive device tree support for RGMII delay configuration. The rx-internal-delay-ps and tx-internal-delay-ps properties allow precise tuning of clock delays in 150 ps steps from 0 to 2250 ps. Signed-off-by: Artem Shimko Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260805085540.452260-4-a.shimko.dev@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/Kconfig | 9 ++ drivers/net/phy/Makefile | 1 + drivers/net/phy/dap8211r.c | 220 +++++++++++++++++++++++++++++++++++++ 3 files changed, 230 insertions(+) create mode 100644 drivers/net/phy/dap8211r.c diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index a29d3fed8a05..b4ef927fd4a6 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -238,6 +238,15 @@ config CORTINA_PHY help Currently supports the CS4340 phy. +config DAP8211R_PHY + tristate "DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY" + depends on OF + help + Support for the DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY. + This PHY is designed for enterprise and industrial networking + applications, supporting 10/100/1000 Mbps operation. Supports + RGMII interface with configurable TX/RX clock delays. + config DAVICOM_PHY tristate "Davicom PHYs" help diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index e23df5e836e9..25c4a3c2429f 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -53,6 +53,7 @@ obj-$(CONFIG_BCM_NET_PHYPTP) += bcm-phy-ptp.o obj-$(CONFIG_BROADCOM_PHY) += broadcom.o obj-$(CONFIG_CICADA_PHY) += cicada.o obj-$(CONFIG_CORTINA_PHY) += cortina.o +obj-$(CONFIG_DAP8211R_PHY) += dap8211r.o obj-$(CONFIG_DAVICOM_PHY) += davicom.o obj-$(CONFIG_DP83640_PHY) += dp83640.o obj-$(CONFIG_DP83822_PHY) += dp83822.o diff --git a/drivers/net/phy/dap8211r.c b/drivers/net/phy/dap8211r.c new file mode 100644 index 000000000000..0bf770f8ebbb --- /dev/null +++ b/drivers/net/phy/dap8211r.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY. + * + * Specifications: + * - IEEE 802.3 10BASE-Te, 100BASE-TX, 1000BASE-T + * - IEEE 802.3az-2010 Energy Efficient Ethernet + * - IEEE 1588 SyncE support + * - RGMII + * + * Author: Artem Shimko + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DAP8211R_PHY_ID 0x0008011B +#define DAP8211R_PHY_ID_MASK 0xFFFFFFFF + +#define DAP8211R_EXT_ADD 0x1E +#define DAP8211R_EXT_DATA 0x1F + +#define DAP8211R_PHY_CON 0xA001 +#define DAP8211R_PHY_SW_RST BIT(15) + +#define DAP8211R_RGMII_CON 0xA003 +#define DAP8211R_RGMII_TX_DEL_MASK GENMASK(3, 0) +#define DAP8211R_RGMII_RX_DEL_MASK GENMASK(13, 10) + +#define DAP8211R_RGMII_CONFIG_MASK (DAP8211R_RGMII_RX_DEL_MASK | DAP8211R_RGMII_TX_DEL_MASK) + +/* + * Hardware reset RX RGMII default delay from the datasheet: 0 * 150ps == 0.00ns + * Used when rx-internal-delay-ps is not specified in DT for RGMII mode and + * in RGMII_TXID to set rx delay. + */ +#define DAP8211R_INITIAL_RX_DEL_VAL 0 + +/* + * Hardware reset TX RGMII default delay from the datasheet: 1 * 150ps == 0.15ns + * Used when tx-internal-delay-ps is not specified in DT for RGMII mode and + * in RGMII_RXID to set tx delay. + */ +#define DAP8211R_INITIAL_TX_DEL_VAL 1 + +/* + * Default RGMII delay: 13 * 150 == 1.95ns + * Used when rx-internal-delay-ps or tx-internal-delay-ps are not specified in DT + * for RGMII ID modes to set tx or rx delay. + */ +#define DAP8211R_DEFAULT_DEL_SEL 0xD + +static const int dap8211r_internal_delay[] = {0, 150, 300, 450, 600, 750, 900, + 1050, 1200, 1350, 1500, 1650, 1800, + 1950, 2100, 2250}; + +#define DAP8211R_DELAY_SIZE ARRAY_SIZE(dap8211r_internal_delay) + +/** + * dap8211r_read_ext() - Read extended register + * @phydev: PHY device structure + * @reg: Extended register address + * + * Reads a PHY extended register using the indirect access method. + * The caller must hold the MDIO bus lock. + * + * Return: Register value on success, or negative error code + */ +static int dap8211r_read_ext(struct phy_device *phydev, u16 reg) +{ + int ret; + + phy_lock_mdio_bus(phydev); + ret = __phy_write(phydev, DAP8211R_EXT_ADD, reg); + if (ret < 0) + goto out; + + ret = __phy_read(phydev, DAP8211R_EXT_DATA); +out: + phy_unlock_mdio_bus(phydev); + return ret; +} + +/** + * dap8211r_modify_ext() - Modify extended register bits + * @phydev: PHY device structure + * @reg: Extended register address + * @mask: Bit mask of bits to clear + * @set: Bit mask of bits to set + * + * Modifies a PHY extended register using the indirect access method. + * New value = (old value & ~mask) | set. + * The caller must hold the MDIO bus lock. + * + * Return: 0 on success, or negative error code + */ +static int dap8211r_modify_ext(struct phy_device *phydev, u16 reg, u16 mask, u16 set) +{ + int ret; + + phy_lock_mdio_bus(phydev); + ret = __phy_write(phydev, DAP8211R_EXT_ADD, reg); + if (ret < 0) + goto out; + + ret = __phy_modify(phydev, DAP8211R_EXT_DATA, mask, set); +out: + phy_unlock_mdio_bus(phydev); + return ret; +} + +/** + * dap8211r_config_init() - Initialize PHY + * @phydev: PHY device structure + * + * Configures the PHY during initialization: + * - RGMII delays based on interface mode + * - Software reset to apply settings (low active, self clear) + * + * Return: 0 on success, or negative error code + */ +static int dap8211r_config_init(struct phy_device *phydev) +{ + u16 set = 0; + int ret, val; + s32 rx_internal_delay = DAP8211R_INITIAL_RX_DEL_VAL; + s32 tx_internal_delay = DAP8211R_INITIAL_TX_DEL_VAL; + + if (!phy_interface_is_rgmii(phydev)) + return 0; + + if (phydev->interface != PHY_INTERFACE_MODE_RGMII_TXID) + rx_internal_delay = phy_get_internal_delay(phydev, dap8211r_internal_delay, + DAP8211R_DELAY_SIZE, true); + + if (phydev->interface != PHY_INTERFACE_MODE_RGMII_RXID) + tx_internal_delay = phy_get_internal_delay(phydev, dap8211r_internal_delay, + DAP8211R_DELAY_SIZE, false); + + switch (phydev->interface) { + case PHY_INTERFACE_MODE_RGMII: + if (rx_internal_delay < 0) + rx_internal_delay = DAP8211R_INITIAL_RX_DEL_VAL; + + if (tx_internal_delay < 0) + tx_internal_delay = DAP8211R_INITIAL_TX_DEL_VAL; + break; + case PHY_INTERFACE_MODE_RGMII_RXID: + if (rx_internal_delay < 0) + rx_internal_delay = DAP8211R_DEFAULT_DEL_SEL; + break; + case PHY_INTERFACE_MODE_RGMII_ID: + if (rx_internal_delay < 0) + rx_internal_delay = DAP8211R_DEFAULT_DEL_SEL; + fallthrough; + case PHY_INTERFACE_MODE_RGMII_TXID: + if (tx_internal_delay < 0) + tx_internal_delay = DAP8211R_DEFAULT_DEL_SEL; + break; + default: + phydev_err(phydev, "Unsupported interface: %d\n", + phydev->interface); + return -EINVAL; + } + + set |= FIELD_PREP(DAP8211R_RGMII_RX_DEL_MASK, rx_internal_delay); + set |= FIELD_PREP(DAP8211R_RGMII_TX_DEL_MASK, tx_internal_delay); + + ret = dap8211r_modify_ext(phydev, DAP8211R_PHY_CON, DAP8211R_PHY_SW_RST, 0); + if (ret) + return ret; + + /* Wait for reset self-clear (from low active to high) */ + ret = read_poll_timeout(dap8211r_read_ext, val, + (val & DAP8211R_PHY_SW_RST), + 20, 200, false, phydev, DAP8211R_PHY_CON); + if (ret) + return ret; + if (val < 0) + return val; + + ret = dap8211r_modify_ext(phydev, DAP8211R_RGMII_CON, DAP8211R_RGMII_CONFIG_MASK, set); + if (ret) + return ret; + + return 0; +} + +static struct phy_driver dap8211r_driver[] = { + { + PHY_ID_MATCH_EXACT(DAP8211R_PHY_ID), + .name = "DAP8211R Gigabit Ethernet", + .soft_reset = genphy_soft_reset, + .config_init = dap8211r_config_init, + .read_status = genphy_read_status, + .set_loopback = genphy_loopback, + .config_aneg = genphy_config_aneg, + .suspend = genphy_suspend, + .resume = genphy_resume, + }, +}; +module_phy_driver(dap8211r_driver); + +MODULE_DESCRIPTION("DAP8211R Gigabit Ethernet PHY driver"); +MODULE_AUTHOR("Artem Shimko "); +MODULE_LICENSE("GPL"); + +static const struct mdio_device_id __maybe_unused dap8211r_tb[] = { + { DAP8211R_PHY_ID, DAP8211R_PHY_ID_MASK }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(mdio, dap8211r_tb); + From e99ecc3046ea5f5c6b5e1f8b4ef854c6c6998e06 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 7 Aug 2026 02:03:23 +0000 Subject: [PATCH 1163/1433] amt: Don't support cross-netns setup. When a lower device is unregistered, amt_device_event() tries to unregister its upper AMT device, but it has two problems. 1. amt_lookup_upper_dev() looks up an upper device in the lower device's netns only 2. amt_device_event() unregisters a single upper device only If AMT device is created on a lower device in another netns, removing the lower device triggers the splat below and gets stuck until all upper devices are removed. [0] The cross-netns setup seems unintentional considering 1. and the following points: * amt_link_setup() sets dev->netns_immutable to true * skb_scrub_packet() is not called in the fast path * iproute2 binary fails to find cross-netns lower device via link-netns: # ip -n ns1 link add amt0 link-netns ns2 type amt dev veth1 Cannot find device "veth1" Instead of supporting it properly and preparing for per-netns netdev unreg, let's forbid cross-netns setup. Note that the problem 2. needs a separate fix. [0]: WARNING: net/core/dev.c:12518 at unregister_netdevice_many_notify+0x1cce/0x2250, CPU#48: ip/2031 Modules linked in: CPU: 48 UID: 0 PID: 2031 Comm: ip Not tainted 7.2.0-rc5+ #27 PREEMPT(full) Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014 RIP: 0010:unregister_netdevice_many_notify (net/core/dev.c:12518) Code: 89 ef e8 d5 52 ae fe e9 d0 f4 ff ff 48 8d 3d f9 3b 9c 02 48 c7 c6 c0 0b 63 84 ba ab 1f 00 00 67 48 0f b9 3a e9 65 ff ff ff 90 <0f> 0b 90 eb 81 48 8d 3d f6 3b 9c 02 48 c7 c6 c0 0b 63 84 ba e2 1f RSP: 0018:ffffc90004abf160 EFLAGS: 00010212 RAX: ffff888104d38260 RBX: ffff88800b0911b8 RCX: dffffc0000000000 RDX: 0000000000000000 RSI: 0000000000000008 RDI: ffffffff85b9f880 RBP: ffffc90004abf2d0 R08: ffffffff85b9f887 R09: 1ffffffff0b73f10 R10: dffffc0000000000 R11: fffffbfff0b73f11 R12: ffff88800b091d08 R13: ffff88800b091178 R14: dffffc0000000000 R15: ffff88800b091000 FS: 00007f555b86c600(0000) GS:ffff8881942a0000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000562107d489c0 CR3: 0000000109a40002 CR4: 0000000000372ef0 Call Trace: rtnl_dellink (net/core/rtnetlink.c:3632 net/core/rtnetlink.c:3674) rtnetlink_rcv_msg (net/core/rtnetlink.c:7112) netlink_rcv_skb (net/netlink/af_netlink.c:2556) netlink_unicast (net/netlink/af_netlink.c:1319) netlink_sendmsg (net/netlink/af_netlink.c:1900) ____sys_sendmsg (net/socket.c:775) __sys_sendmsg (net/socket.c:2738) do_syscall_64 (arch/x86/entry/syscall_64.c:63) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) ... unregister_netdevice: waiting for veth0 to become free. Usage count = 7 ref_tracker: netdev@ffff88800d7496d8 has 3/3 users at __netdev_adjacent_dev_insert (./include/linux/netdevice.h:4525 ./include/linux/netdevice.h:4554 net/core/dev.c:8791) __netdev_upper_dev_link (net/core/dev.c:8879 net/core/dev.c:8963) netdev_upper_dev_link (net/core/dev.c:9009) amt_newlink (drivers/net/amt.c:3321) Fixes: b9022b53adad ("amt: add control plane of amt interface") Signed-off-by: Kuniyuki Iwashima Reviewed-by: Taehee Yoo Link: https://patch.msgid.link/20260807020326.2519445-1-kuniyu@google.com Signed-off-by: Jakub Kicinski --- drivers/net/amt.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/amt.c b/drivers/net/amt.c index 1f293a962b21..bddc24e1856d 100644 --- a/drivers/net/amt.c +++ b/drivers/net/amt.c @@ -3226,6 +3226,9 @@ static int amt_newlink(struct net_device *dev, struct nlattr **tb = params->tb; int err = -EINVAL; + if (!net_eq(link_net, dev_net(dev))) + return err; + amt->net = link_net; amt->mode = nla_get_u32(data[IFLA_AMT_MODE]); From fd23a7c973174a320ae2b560d1caa69ef111c0c2 Mon Sep 17 00:00:00 2001 From: Hangbin Liu Date: Thu, 6 Aug 2026 11:29:28 +0800 Subject: [PATCH 1164/1433] bonding: fix wrong extack attribute in ARP validate netlink error path The attribute of netlink error message should be IFLA_BOND_ARP_VALIDATE when ARP validation setting fails. Added by commit 2bff369b2354 ("bonding: netlink error message support for options"). Signed-off-by: Hangbin Liu Reviewed-by: Fernando Fernandez Mancera Link: https://patch.msgid.link/20260806-bond_arp_validate-v1-1-3ae005657ef9@kylinos.cn Signed-off-by: Jakub Kicinski --- drivers/net/bonding/bond_netlink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/bonding/bond_netlink.c b/drivers/net/bonding/bond_netlink.c index 55d2f8a539d4..23ccf4d03137 100644 --- a/drivers/net/bonding/bond_netlink.c +++ b/drivers/net/bonding/bond_netlink.c @@ -372,7 +372,7 @@ static int bond_changelink(struct net_device *bond_dev, struct nlattr *tb[], int arp_validate = nla_get_u32(data[IFLA_BOND_ARP_VALIDATE]); if (arp_validate && miimon) { - NL_SET_ERR_MSG_ATTR(extack, data[IFLA_BOND_ARP_INTERVAL], + NL_SET_ERR_MSG_ATTR(extack, data[IFLA_BOND_ARP_VALIDATE], "ARP validating cannot be used with MII monitoring"); return -EINVAL; } From f1529936c0b65fb343f62f50e5313078719fc336 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Thu, 6 Aug 2026 22:04:54 +0200 Subject: [PATCH 1165/1433] keys, dns: Drop unused NUL terminator from upayload->data upayload->data includes an extra NUL terminator even though it is never used as a C string. In-tree users access only the first upayload->datalen bytes. Remove the redundant NUL terminator and allocate one byte less for upayload->data in dns_resolver_preparse(). Signed-off-by: Thorsten Blum Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260806200454.245444-3-thorsten.blum@linux.dev Signed-off-by: Jakub Kicinski --- net/dns_resolver/dns_key.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/net/dns_resolver/dns_key.c b/net/dns_resolver/dns_key.c index c3c8c3240ef9..451247864a63 100644 --- a/net/dns_resolver/dns_key.c +++ b/net/dns_resolver/dns_key.c @@ -203,7 +203,7 @@ dns_resolver_preparse(struct key_preparsed_payload *prep) kdebug("store result"); prep->quotalen = result_len; - upayload = kmalloc_flex(*upayload, data, result_len + 1); + upayload = kmalloc_flex(*upayload, data, result_len); if (!upayload) { kleave(" = -ENOMEM"); return -ENOMEM; @@ -211,7 +211,6 @@ dns_resolver_preparse(struct key_preparsed_payload *prep) upayload->datalen = result_len; memcpy(upayload->data, data, result_len); - upayload->data[result_len] = '\0'; prep->payload.data[dns_key_data] = upayload; kleave(" = 0"); From 34b270e7893f4a1e2257051e98fcb8cd8ef872c9 Mon Sep 17 00:00:00 2001 From: Suraj Gupta Date: Thu, 6 Aug 2026 22:32:53 +0530 Subject: [PATCH 1166/1433] net: xilinx: axienet: Treat xlnx,rxmem as a required property "xlnx,rxmem" device-tree property is used to learn the size of the Rx/Tx packet buffer built into the ethernet IP, but return value of of_property_read_u32() is ignored. When the property is absent lp->rxmem is left at 0, which silently limits the interface to the default MTU and disables jumbo frames with no indication of the misconfiguration. "xlnx,rxmem" has been documented as a required property since the binding was introduced. Check the return value of of_property_read_u32() and fail probe when the property is missing, so a misconfigured device tree is reported rather than silently degrading functionality. Signed-off-by: Suraj Gupta Reviewed-by: Radhey Shyam Pandey Link: https://patch.msgid.link/20260806170253.1199749-1-suraj.gupta2@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index fcf517069d16..1722b7038f34 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -2898,7 +2898,10 @@ static int axienet_probe(struct platform_device *pdev) * Here we check for memory allocated for Rx/Tx in the hardware from * the device-tree and accordingly set flags. */ - of_property_read_u32(pdev->dev.of_node, "xlnx,rxmem", &lp->rxmem); + ret = of_property_read_u32(pdev->dev.of_node, "xlnx,rxmem", &lp->rxmem); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to read xlnx,rxmem property\n"); lp->switch_x_sgmii = of_property_read_bool(pdev->dev.of_node, "xlnx,switch-x-sgmii"); From fac7973f004de6dd51fb15a33e33ed37718e58bb Mon Sep 17 00:00:00 2001 From: Rongguang Wei Date: Fri, 7 Aug 2026 15:09:14 +0800 Subject: [PATCH 1167/1433] tap: fix incorrect variable used for USO check in set_offload() The USO features in set_offload() incorrectly uses feature_mask and features argument. The USO feature was written to the local features variable instead of feature_mask. All other offload bits (TSO, TSO_ECN) are stored in feature_mask which becomes tap->tap_features and is used by tap_handle_frame() for GSO segmentation. Without NETIF_F_GSO_UDP_L4 in tap->tap_features, making USO on tap effectively non-functional. Keeping the USO handling inside the TUN_F_CSUM block avoids enabling GRO/LRO when userspace requests USO without CSUM. This has not worked since the beginning, so commit 399e0827642f ("driver/net/tun: Added features for USO.") Signed-off-by: Rongguang Wei Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260807070914.112698-1-clementwei90@163.com Signed-off-by: Jakub Kicinski --- drivers/net/tap.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/net/tap.c b/drivers/net/tap.c index 5d2d34d24ce8..d4ca2fee538b 100644 --- a/drivers/net/tap.c +++ b/drivers/net/tap.c @@ -883,7 +883,7 @@ static int set_offload(struct tap_queue *q, unsigned long arg) /* TODO: for now USO4 and USO6 should work simultaneously */ if ((arg & (TUN_F_USO4 | TUN_F_USO6)) == (TUN_F_USO4 | TUN_F_USO6)) - features |= NETIF_F_GSO_UDP_L4; + feature_mask |= NETIF_F_GSO_UDP_L4; } /* tun/tap driver inverts the usage for TSO offloads, where @@ -894,8 +894,7 @@ static int set_offload(struct tap_queue *q, unsigned long arg) * When user space turns off TSO, we turn off GSO/LRO so that * user-space will not receive TSO frames. */ - if (feature_mask & (NETIF_F_TSO | NETIF_F_TSO6) || - (feature_mask & (TUN_F_USO4 | TUN_F_USO6)) == (TUN_F_USO4 | TUN_F_USO6)) + if (feature_mask & (NETIF_F_TSO | NETIF_F_TSO6 | NETIF_F_GSO_UDP_L4)) features |= RX_OFFLOADS; else features &= ~RX_OFFLOADS; From ae2998ab62e40b3e134590dc5a233ae3367a7a2b Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Sat, 8 Aug 2026 17:06:09 -0700 Subject: [PATCH 1168/1433] netdev: check for nla_put_u32() failures Make sure we check if nla_put_u32(id) was successful after creating objects. This is theoretical today, the skbs are large enough to always fit the ID. Acked-by: Daniel Borkmann Reviewed-by: Nikolay Aleksandrov Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260809000609.327659-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- net/core/netdev-genl.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c index 0eea4ee22f24..cb18db681640 100644 --- a/net/core/netdev-genl.c +++ b/net/core/netdev-genl.c @@ -1107,7 +1107,9 @@ int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info) goto err_unbind; } - nla_put_u32(rsp, NETDEV_A_DMABUF_ID, binding->id); + /* rsp was allocated large enough */ + WARN_ON_ONCE(nla_put_u32(rsp, NETDEV_A_DMABUF_ID, binding->id)); + genlmsg_end(rsp, hdr); err = genlmsg_reply(rsp, info); @@ -1241,7 +1243,9 @@ int netdev_nl_bind_tx_doit(struct sk_buff *skb, struct genl_info *info) goto err_unlock_bind_dev; } - nla_put_u32(rsp, NETDEV_A_DMABUF_ID, binding->id); + /* rsp was allocated large enough */ + WARN_ON_ONCE(nla_put_u32(rsp, NETDEV_A_DMABUF_ID, binding->id)); + genlmsg_end(rsp, hdr); if (bind_dev != netdev) @@ -1408,7 +1412,9 @@ int netdev_nl_queue_create_doit(struct sk_buff *skb, struct genl_info *info) netdev_rx_queue_lease(rxq, rxq_lease); - nla_put_u32(rsp, NETDEV_A_QUEUE_ID, queue_id); + /* rsp was allocated large enough */ + WARN_ON_ONCE(nla_put_u32(rsp, NETDEV_A_QUEUE_ID, queue_id)); + genlmsg_end(rsp, hdr); netdev_unlock(dev_lease); From 4d2cbd620a7f3a02e59a4e65eaf30acee56e613c Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Sat, 8 Aug 2026 09:34:16 -0700 Subject: [PATCH 1169/1433] selftests: netdevsim: fix SIGPIPE flake in ethtool-coalesce The adaptive-rx and adaptive-tx checks use 'ethtool -c | grep -q' under 'set -o pipefail'. grep -q exits as soon as it finds a match, which can happen before ethtool finishes writing its output. When that occurs, ethtool receives SIGPIPE causing (uninformative): # selftests: drivers/net/netdevsim: ethtool-coalesce.sh # FAILED 1/22 checks not ok 1 selftests: drivers/net/netdevsim: ethtool-coalesce.sh # exit=1 This happens on debug kernels in NIPA, ~4% of the time. Link: https://patch.msgid.link/20260808163416.2456810-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- .../drivers/net/netdevsim/ethtool-coalesce.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/drivers/net/netdevsim/ethtool-coalesce.sh b/tools/testing/selftests/drivers/net/netdevsim/ethtool-coalesce.sh index 9adfba8f87e6..b9fcafad4258 100755 --- a/tools/testing/selftests/drivers/net/netdevsim/ethtool-coalesce.sh +++ b/tools/testing/selftests/drivers/net/netdevsim/ethtool-coalesce.sh @@ -116,12 +116,14 @@ done # bool settings which ethtool displays on the same line ethtool -C $NSIM_NETDEV adaptive-rx on -s=$(ethtool -c $NSIM_NETDEV | grep -q "Adaptive RX: on TX: off") -check $? "$s" "" +s=$(ethtool -c $NSIM_NETDEV) +echo "$s" | grep -q "Adaptive RX: on TX: off" +check $? "" "" ethtool -C $NSIM_NETDEV adaptive-tx on -s=$(ethtool -c $NSIM_NETDEV | grep -q "Adaptive RX: on TX: on") -check $? "$s" "" +s=$(ethtool -c $NSIM_NETDEV) +echo "$s" | grep -q "Adaptive RX: on TX: on" +check $? "" "" if [ $num_errors -eq 0 ]; then echo "PASSED all $((num_passes)) checks" From f4cf60568747edddb255106154684f8d56b1aa1b Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Sat, 8 Aug 2026 09:36:52 -0700 Subject: [PATCH 1170/1433] selftests: drv-net: pace ethtool_std_stats packet generation mausezahn defaults to sending packets back to back at the maximum rate, which can cause packet loss, especially if receiver is running a debug kernel. Space the generated packets out (-d 10usec), like ethtool_rmon already does. Reviewed-by: Petr Machata Link: https://patch.msgid.link/20260808163653.2460381-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/hw/ethtool_std_stats.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/drivers/net/hw/ethtool_std_stats.sh b/tools/testing/selftests/drivers/net/hw/ethtool_std_stats.sh index c085d2a4c989..1b329b3f60c2 100755 --- a/tools/testing/selftests/drivers/net/hw/ethtool_std_stats.sh +++ b/tools/testing/selftests/drivers/net/hw/ethtool_std_stats.sh @@ -43,10 +43,10 @@ traffic_test() done # shellcheck disable=SC2086 # needs split options - run_on "$iface" "$MZ" "$iface" -q -c "$num_tx" $pkt_format + run_on "$iface" "$MZ" "$iface" -q -d 10usec -c "$num_tx" $pkt_format # shellcheck disable=SC2086 # needs split options - run_on "$neigh" "$MZ" "$neigh" -q -c "$num_rx" $pkt_format + run_on "$neigh" "$MZ" "$neigh" -q -d 10usec -c "$num_rx" $pkt_format for i in "${!counters[@]}"; do read -r int grp cnt target exact_check xfail_message \ From 855631afe0f5a1fac96abdd8a30e3ccda966e15f Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Sat, 8 Aug 2026 09:36:53 -0700 Subject: [PATCH 1171/1433] selftests: drv-net: let ethtool stats settle before reading Some devices refresh the statistics exposed via ethtool only periodically, every stats-block-usecs (as reported by ethtool -c). ethtool_std_stats and ethtool_rmon sample the counters immediately after generating traffic, so on such devices they can read stale values and fail with a delta short of the packets just sent. Add a hw_stats_settle() helper which sleeps for 1.25x the configured stats-block-usecs (defaulting to 20ms when the device reports no, or a zero, period). Use it for ethtool std stats and RMON. The 1.25x/20msec heuristic matches what the Python tests do. Reviewed-by: Petr Machata Link: https://patch.msgid.link/20260808163653.2460381-2-kuba@kernel.org Signed-off-by: Jakub Kicinski --- .../selftests/drivers/net/hw/ethtool_rmon.sh | 2 ++ .../selftests/drivers/net/hw/ethtool_std_stats.sh | 2 ++ tools/testing/selftests/net/forwarding/lib.sh | 15 +++++++++++++++ 3 files changed, 19 insertions(+) diff --git a/tools/testing/selftests/drivers/net/hw/ethtool_rmon.sh b/tools/testing/selftests/drivers/net/hw/ethtool_rmon.sh index 2ec19edddfaa..a074834cbe59 100755 --- a/tools/testing/selftests/drivers/net/hw/ethtool_rmon.sh +++ b/tools/testing/selftests/drivers/net/hw/ethtool_rmon.sh @@ -65,6 +65,8 @@ bucket_test() run_on "$iface" \ "$MZ" "$iface" -q -c "$num_tx" -p "$len" -a own -b bcast -d 10us + hw_stats_settle "$iface" + after=$(run_on "$iface" ethtool --json -S "$iface" --groups rmon | \ jq -r ".[0].rmon[\"${set}-pktsNtoM\"][$bucket].val") diff --git a/tools/testing/selftests/drivers/net/hw/ethtool_std_stats.sh b/tools/testing/selftests/drivers/net/hw/ethtool_std_stats.sh index 1b329b3f60c2..09f8128c51f3 100755 --- a/tools/testing/selftests/drivers/net/hw/ethtool_std_stats.sh +++ b/tools/testing/selftests/drivers/net/hw/ethtool_std_stats.sh @@ -48,6 +48,8 @@ traffic_test() # shellcheck disable=SC2086 # needs split options run_on "$neigh" "$MZ" "$neigh" -q -d 10usec -c "$num_rx" $pkt_format + hw_stats_settle "$int" + for i in "${!counters[@]}"; do read -r int grp cnt target exact_check xfail_message \ <<< "${counters[$i]}" diff --git a/tools/testing/selftests/net/forwarding/lib.sh b/tools/testing/selftests/net/forwarding/lib.sh index ac8358bcb22c..05acd4011456 100644 --- a/tools/testing/selftests/net/forwarding/lib.sh +++ b/tools/testing/selftests/net/forwarding/lib.sh @@ -406,6 +406,21 @@ get_ifname_by_ip() __run_on "$target" ip -j addr show to "$ip_addr" | jq -r '.[].ifname' } +# Wait for the device to refresh its HW statistics. Devices latch the stats +# reported via ethtool only every stats-block-usecs, so sample after that. +hw_stats_settle() +{ + local iface=$1; shift + local usecs + + # Match only a non-zero integer; 0 or "n/a" use default (20msec) + usecs=$(run_on "$iface" ethtool -c "$iface" 2>/dev/null | \ + sed -n 's/^stats-block-usecs:[[:space:]]*\([1-9][0-9]*\)$/\1/p') + usecs=${usecs:-20000} + + sleep "$(echo "$usecs * 1.25 / 1000 / 1000" | bc -l)" +} + # Whether the test is conforming to the requirements and usage described in # drivers/net/README.rst. : "${DRIVER_TEST_CONFORMANT:=no}" From 4d422c526fcf1c45ad15d83f976ea5a881cb5ce1 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Wed, 5 Aug 2026 16:50:31 +0200 Subject: [PATCH 1172/1433] s390/ctcm: Add __context_unsafe() attribute to various functions Disable context analysis for various functions to get rid of context analysis compile time warnings using clang caused by conditional locking like e.g.: drivers/s390/net/ctcm_fsms.c:1457:8: warning: spinlock 'arg->cdev->ccwlock' is not held on every path through here drivers/s390/net/ctcm_fsms.c:1459:4: warning: releasing spinlock 'arg->cdev->ccwlock' that was not held Use __context_unsafe() to provide a short comment why context analysis is disabled for each function. Each of those functions already contains a comment that the (previous) sparse context analysis warnings due to conditional locking should be ignored. Remove those comments everywhere and use the __context_unsafe() attribute instead. Signed-off-by: Heiko Carstens Acked-by: Alexandra Winter Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260805145032.1409325-2-hca@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/s390/net/ctcm_fsms.c | 20 +++++++------------- drivers/s390/net/ctcm_mpc.c | 6 ++---- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/drivers/s390/net/ctcm_fsms.c b/drivers/s390/net/ctcm_fsms.c index bf917f426453..84fd394d3525 100644 --- a/drivers/s390/net/ctcm_fsms.c +++ b/drivers/s390/net/ctcm_fsms.c @@ -545,6 +545,7 @@ static void chx_rxidle(fsm_instance *fi, int event, void *arg) * arg Generic pointer, casted from channel * upon call. */ static void ctcm_chx_setmode(fsm_instance *fi, int event, void *arg) +__context_unsafe(/* Conditional locking */) { struct channel *ch = arg; int rc; @@ -563,8 +564,6 @@ static void ctcm_chx_setmode(fsm_instance *fi, int event, void *arg) if (event == CTC_EVENT_TIMER) /* only for timer not yet locked */ spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); - /* Such conditional locking is undeterministic in - * static view. => ignore sparse warnings here. */ rc = ccw_device_start(ch->cdev, &ch->ccw[6], 0, 0xff, 0); if (event == CTC_EVENT_TIMER) /* see above comments */ @@ -648,6 +647,7 @@ static void ctcm_chx_start(fsm_instance *fi, int event, void *arg) * arg Generic pointer, casted from channel * upon call. */ static void ctcm_chx_haltio(fsm_instance *fi, int event, void *arg) +__context_unsafe(/* Conditional locking */) { struct channel *ch = arg; unsigned long saveflags = 0; @@ -662,15 +662,12 @@ static void ctcm_chx_haltio(fsm_instance *fi, int event, void *arg) if (event == CTC_EVENT_STOP) /* only for STOP not yet locked */ spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); - /* Such conditional locking is undeterministic in - * static view. => ignore sparse warnings here. */ oldstate = fsm_getstate(fi); fsm_newstate(fi, CTC_STATE_TERM); rc = ccw_device_halt(ch->cdev, 0); if (event == CTC_EVENT_STOP) spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); - /* see remark above about conditional locking */ if (rc != 0 && rc != -EBUSY) { fsm_deltimer(&ch->timer); @@ -824,6 +821,7 @@ static void ctcm_chx_setuperr(fsm_instance *fi, int event, void *arg) * arg Generic pointer, casted from channel * upon call. */ static void ctcm_chx_restart(fsm_instance *fi, int event, void *arg) +__context_unsafe(/* Conditional locking */) { struct channel *ch = arg; struct net_device *dev = ch->netdev; @@ -842,9 +840,6 @@ static void ctcm_chx_restart(fsm_instance *fi, int event, void *arg) fsm_newstate(fi, CTC_STATE_STARTWAIT); if (event == CTC_EVENT_TIMER) /* only for timer not yet locked */ spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); - /* Such conditional locking is a known problem for - * sparse because its undeterministic in static view. - * Warnings should be ignored here. */ rc = ccw_device_halt(ch->cdev, 0); if (event == CTC_EVENT_TIMER) spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); @@ -999,6 +994,7 @@ static void ctcm_chx_txiniterr(fsm_instance *fi, int event, void *arg) * arg Generic pointer, casted from channel * upon call. */ static void ctcm_chx_txretry(fsm_instance *fi, int event, void *arg) +__context_unsafe(/* Conditional locking */) { struct channel *ch = arg; struct net_device *dev = ch->netdev; @@ -1042,9 +1038,6 @@ static void ctcm_chx_txretry(fsm_instance *fi, int event, void *arg) fsm_addtimer(&ch->timer, 1000, CTC_EVENT_TIMER, ch); if (event == CTC_EVENT_TIMER) /* for TIMER not yet locked */ spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); - /* Such conditional locking is a known problem for - * sparse because its undeterministic in static view. - * Warnings should be ignored here. */ if (do_debug_ccw) ctcmpc_dumpit((char *)&ch->ccw[3], sizeof(struct ccw1) * 3); @@ -1383,6 +1376,7 @@ static void ctcmpc_chx_txdone(fsm_instance *fi, int event, void *arg) * arg Generic pointer, casted from channel * upon call. */ static void ctcmpc_chx_rx(fsm_instance *fi, int event, void *arg) +__context_unsafe(/* Conditional locking */) { struct channel *ch = arg; struct net_device *dev = ch->netdev; @@ -1462,7 +1456,7 @@ static void ctcmpc_chx_rx(fsm_instance *fi, int event, void *arg) spin_lock_irqsave( get_ccwdev_lock(ch->cdev), saveflags); rc = ccw_device_start(ch->cdev, &ch->ccw[0], 0, 0xff, 0); - if (dolock) /* see remark about conditional locking */ + if (dolock) spin_unlock_irqrestore( get_ccwdev_lock(ch->cdev), saveflags); if (rc != 0) @@ -1539,6 +1533,7 @@ static void ctcmpc_chx_firstio(fsm_instance *fi, int event, void *arg) * arg Generic pointer, casted from channel * upon call. */ void ctcmpc_chx_rxidle(fsm_instance *fi, int event, void *arg) +__context_unsafe(/* Conditional locking */) { struct channel *ch = arg; struct net_device *dev = ch->netdev; @@ -1566,7 +1561,6 @@ void ctcmpc_chx_rxidle(fsm_instance *fi, int event, void *arg) ch->ccw[1].count = ch->max_bufsize; CTCM_CCW_DUMP((char *)&ch->ccw[0], sizeof(struct ccw1) * 3); if (event == CTC_EVENT_START) - /* see remark about conditional locking */ spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); rc = ccw_device_start(ch->cdev, &ch->ccw[0], 0, 0xff, 0); if (event == CTC_EVENT_START) diff --git a/drivers/s390/net/ctcm_mpc.c b/drivers/s390/net/ctcm_mpc.c index aeb102537e7f..08e36685e578 100644 --- a/drivers/s390/net/ctcm_mpc.c +++ b/drivers/s390/net/ctcm_mpc.c @@ -1647,6 +1647,7 @@ static int mpc_validate_xid(struct mpcg_info *mpcginfo) * CTCM_PROTO_MPC only */ static void mpc_action_side_xid(fsm_instance *fsm, void *arg, int side) +__context_unsafe(/* Conditional locking */) { struct channel *ch = arg; int rc = 0; @@ -1774,9 +1775,6 @@ static void mpc_action_side_xid(fsm_instance *fsm, void *arg, int side) CTCM_D3_DUMP((char *)ch->xid_id, 4); if (!in_hardirq()) { - /* Such conditional locking is a known problem for - * sparse because its static undeterministic. - * Warnings should be ignored here. */ spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); gotlock = 1; } @@ -1784,7 +1782,7 @@ static void mpc_action_side_xid(fsm_instance *fsm, void *arg, int side) fsm_addtimer(&ch->timer, 5000 , CTC_EVENT_TIMER, ch); rc = ccw_device_start(ch->cdev, &ch->ccw[8], 0, 0xff, 0); - if (gotlock) /* see remark above about conditional locking */ + if (gotlock) spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); if (rc != 0) { From 5da9639bdd2003374465cd5f21fe012b6d63fca2 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Wed, 5 Aug 2026 16:50:32 +0200 Subject: [PATCH 1173/1433] drivers/s390/net: Enable CONTEXT_ANALYSIS All drivers in drivers/s390/net pass clang's compile time context analysis. Therefore enable CONTEXT_ANALYSIS. Signed-off-by: Heiko Carstens Acked-by: Alexandra Winter Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260805145032.1409325-3-hca@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/s390/net/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/s390/net/Makefile b/drivers/s390/net/Makefile index 537514cc52fb..038ba3e4005d 100644 --- a/drivers/s390/net/Makefile +++ b/drivers/s390/net/Makefile @@ -3,6 +3,8 @@ # S/390 network devices # +CONTEXT_ANALYSIS := y + ctcm-y += ctcm_main.o ctcm_fsms.o ctcm_mpc.o ctcm_sysfs.o ctcm_dbug.o obj-$(CONFIG_CTCM) += ctcm.o fsm.o obj-$(CONFIG_SMSGIUCV) += smsgiucv.o From 20232e99e8bfa44a6d77e8c3e0da2358f617e653 Mon Sep 17 00:00:00 2001 From: Krishan Singh Date: Sun, 9 Aug 2026 12:15:04 +0530 Subject: [PATCH 1174/1433] net: sfp: fix hwmon_name memory leak on hwmon registration failure hwmon_sanitize_name() allocates sfp->hwmon_name before hwmon_device_register_with_info() is called. If the registration fails, sfp->hwmon_dev is left pointing to an error while sfp->hwmon_name remains allocated. Later, when the SFP module is removed, sfp_hwmon_remove() only frees hwmon_name when hwmon_dev is valid. As a result, hwmon_name is leaked if hwmon_device_register_with_info() fails. Free hwmon_name independently of hwmon_dev. Continue to unregister the hwmon device only when hwmon_dev was successfully registered. Cc: stable+noautosel@kernel.org # untested fix to unlikely driver error path Suggested-by: Andrew Lunn Signed-off-by: Krishan Singh Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260809064504.70579-1-krishanmohan298@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/sfp.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 6c25b73c668a..508b6cc8eddc 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -1919,7 +1919,11 @@ static void sfp_hwmon_remove(struct sfp *sfp) if (!IS_ERR_OR_NULL(sfp->hwmon_dev)) { hwmon_device_unregister(sfp->hwmon_dev); sfp->hwmon_dev = NULL; + } + + if (!IS_ERR_OR_NULL(sfp->hwmon_name)) { kfree(sfp->hwmon_name); + sfp->hwmon_name = NULL; } } From 341d8aff93e2179de330c87a16be2eae0eba2ddc Mon Sep 17 00:00:00 2001 From: Avi Weiss Date: Sat, 8 Aug 2026 22:43:47 +0300 Subject: [PATCH 1175/1433] et131x: propagate EEPROM readiness errors eeprom_wait_ready() returns a negative error when the LBCIF status cannot be read or the device does not become ready for some other reason. eeprom_write() propagates this error before starting a write, but currently returns 0 when the same readiness check fails after the write begins. This behavior was introduced when the EEPROM code was refactored to use Linux error-return conventions (from 0 = failure to 0 = success). Return the error so callers do not treat a failed EEPROM write as successful and the function contract is maintained. Cc: stable+noautosel@kernel.org # untested fix to unlikely driver error path Signed-off-by: Avi Weiss Acked-by: Mark Einon Link: https://patch.msgid.link/20260808194347.813242-1-thnkslprpt@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/agere/et131x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/agere/et131x.c b/drivers/net/ethernet/agere/et131x.c index 1b465a167672..4b6a579e9c66 100644 --- a/drivers/net/ethernet/agere/et131x.c +++ b/drivers/net/ethernet/agere/et131x.c @@ -567,7 +567,7 @@ static int eeprom_write(struct et131x_adapter *adapter, u32 addr, u8 data) */ err = eeprom_wait_ready(pdev, &status); if (err < 0) - return 0; + return err; /* Check bit 3 of the LBCIF Status Register. If equal to 1, * an error has occurred.Don't break here if we are revision From ef3d6cca02c8a7b6465fb1691823524b86d1cb99 Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Sat, 8 Aug 2026 12:00:44 -0400 Subject: [PATCH 1176/1433] selftests: drv-net: so_txtime: only send test traffic to sch_etf The ETF qdiscs drops traffic without a socket or txtime. Even with parameter skip_sock_check regular traffic is affected by ETF. This test ran fine when run manually in a pure software environment. But with drv-net across two hosts tests fail as early as when calling cfg.remote.deploy due to effectively losing connectivity. Isolate the intended test traffic: - mark that with SO_MARK 100 - install a regular permissive root prio qdisc for background traffic - install the ETF qdisc as leaf - install a filter that only directs SO_MARK 100 traffic to this leaf Technically other high prio traffic will map onto this leaf based on ToS band mapping too. But that is immaterial in practice. Fixes: 5c6baef3885c ("selftests: drv-net: convert so_txtime to drv-net") Signed-off-by: Willem de Bruijn Link: https://patch.msgid.link/20260808160129.890119-1-willemdebruijn.kernel@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/config | 2 ++ tools/testing/selftests/drivers/net/so_txtime.py | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/drivers/net/config b/tools/testing/selftests/drivers/net/config index f3933cf3e6be..b6989c7d3d9d 100644 --- a/tools/testing/selftests/drivers/net/config +++ b/tools/testing/selftests/drivers/net/config @@ -8,6 +8,7 @@ CONFIG_NET_ACT_SKBEDIT=m CONFIG_NET_CLS_ACT=y CONFIG_NET_CLS_BPF=y CONFIG_NET_CLS_FLOWER=m +CONFIG_NET_CLS_FW=m CONFIG_NET_CLS_MATCHALL=m CONFIG_NETCONSOLE=m CONFIG_NETCONSOLE_DYNAMIC=y @@ -17,6 +18,7 @@ CONFIG_NETKIT=y CONFIG_NET_SCH_ETF=m CONFIG_NET_SCH_FQ=m CONFIG_NET_SCH_INGRESS=y +CONFIG_NET_SCH_PRIO=m CONFIG_PPP=y CONFIG_PPPOE=y CONFIG_VLAN_8021Q=m diff --git a/tools/testing/selftests/drivers/net/so_txtime.py b/tools/testing/selftests/drivers/net/so_txtime.py index adf6c848d6d8..9fbc0278d28b 100755 --- a/tools/testing/selftests/drivers/net/so_txtime.py +++ b/tools/testing/selftests/drivers/net/so_txtime.py @@ -27,7 +27,7 @@ def test_so_txtime(cfg, clockid, ipver, args_tx, args_rx, expect_success): cmd_addr = f"-S {cfg.addr_v[ipver]} -D {cfg.remote_addr_v[ipver]}" cmd_args = f"-{ipver} -c {clockid} -t {tstart} {cmd_addr}" cmd_rx = f"{cfg.bin_remote} {cmd_args} {args_rx} -r" - cmd_tx = f"{cfg.bin_local} {cmd_args} {args_tx}" + cmd_tx = f"{cfg.bin_local} -m 100 {cmd_args} {args_tx}" expect_fail = not expect_success if slow_machine: @@ -45,7 +45,7 @@ def _qdisc_setup(ifname, qdisc, optargs=""): """ orig = tc(f"qdisc show dev {ifname} root", json=True)[0].get("kind", None) defer(tc, f"qdisc replace dev {ifname} root {orig}") - tc(f"qdisc replace dev {ifname} root {qdisc} {optargs}") + tc(f"qdisc replace dev {ifname} root handle 1: {qdisc} {optargs}") def _test_variants_fq(): @@ -96,11 +96,21 @@ def _test_variants_etf(): def test_so_txtime_etf(cfg, ipver, args_tx, args_rx, expect_fail): """Run all variants of etf tests.""" cfg.require_ipver(ipver) + + # root qdisc for background traffic (e.g., bkg()) + _qdisc_setup(cfg.ifname, "prio") + + # leaf ETF qdisc only for intended packets try: - _qdisc_setup(cfg.ifname, "etf", "clockid CLOCK_TAI delta 400000") + etf_args = "clockid CLOCK_TAI delta 400000" + tc(f"qdisc add dev {cfg.ifname} parent 1:1 handle 10: etf {etf_args}") except Exception as e: raise KsftSkipEx("tc does not support qdisc etf. skipping") from e + # redirect mark 100 to leaf + filter_args = "protocol all handle 100 fw flowid 1:1" + tc(f"filter add dev {cfg.ifname} parent 1: {filter_args}") + test_so_txtime(cfg, "tai", ipver, args_tx, args_rx, expect_fail) From 30dbc21f637522e9d6a0e6786fa44a12025c805b Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Sat, 8 Aug 2026 09:23:45 -0700 Subject: [PATCH 1177/1433] selftests: bonding: disable DAD for IPv6 addresses bond_reset() waits up to 2 seconds for IPv6 connectivity. With default settings DAD itself may take almost 2 seconds, causing flakes on debug builds. It used to flake once or twice a week, recently it started failing once a day. Probably some downstream changes to scheduler, or our machines go busier. A lot of selftests already use nodad, let's use nodad in bonding, too. I don't see an obvious reason why DAD would be important to the test. Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260808162345.2442594-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- .../selftests/drivers/net/bonding/bond_topo_2d1c.sh | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tools/testing/selftests/drivers/net/bonding/bond_topo_2d1c.sh b/tools/testing/selftests/drivers/net/bonding/bond_topo_2d1c.sh index 167aa4a4a12a..903c7a6c7287 100644 --- a/tools/testing/selftests/drivers/net/bonding/bond_topo_2d1c.sh +++ b/tools/testing/selftests/drivers/net/bonding/bond_topo_2d1c.sh @@ -48,7 +48,7 @@ gateway_create() ip -n ${g_ns} link add br0 type bridge ip -n ${g_ns} link set br0 up ip -n ${g_ns} addr add ${g_ip4}/24 dev br0 - ip -n ${g_ns} addr add ${g_ip6}/24 dev br0 + ip -n ${g_ns} addr add ${g_ip6}/24 dev br0 nodad } gateway_destroy() @@ -75,7 +75,7 @@ server_create() ip -n ${s_ns} link set bond0 up ip -n ${s_ns} addr add ${s_ip4}/24 dev bond0 - ip -n ${s_ns} addr add ${s_ip6}/24 dev bond0 + ip -n ${s_ns} addr add ${s_ip6}/24 dev bond0 nodad } # Reset bond with new mode and options @@ -97,9 +97,7 @@ bond_reset() ip -n ${s_ns} link set bond0 up ip -n ${s_ns} addr add ${s_ip4}/24 dev bond0 - ip -n ${s_ns} addr add ${s_ip6}/24 dev bond0 - # Wait for IPv6 address ready as it needs DAD - slowwait 2 ip netns exec ${s_ns} ping6 ${c_ip6} -c 1 -W 0.1 &> /dev/null + ip -n ${s_ns} addr add ${s_ip6}/24 dev bond0 nodad } server_destroy() @@ -124,7 +122,7 @@ client_create() ip -n ${c_ns} link set eth0 up ip -n ${c_ns} addr add ${c_ip4}/24 dev eth0 - ip -n ${c_ns} addr add ${c_ip6}/24 dev eth0 + ip -n ${c_ns} addr add ${c_ip6}/24 dev eth0 nodad } client_destroy() From 7c62c481bb43488147b8755cd92e467085ab106b Mon Sep 17 00:00:00 2001 From: Thaison Phan Date: Fri, 7 Aug 2026 17:14:59 +0000 Subject: [PATCH 1178/1433] tools: ynl: check for null ptr on dump free Static analysis detected code paths where freeing a dump list after early errors when creating the corresponding dump list like in ynl_exec_dump() can result in a null pointer dereference since the first node in the ynl_dump_state would still be zero initialized. To prevent this potential problem updated the ynl c generation script to check for a NULL pointer before continuing to free the nodes in a dump list. Signed-off-by: Thaison Phan Link: https://patch.msgid.link/20260807171500.7188-2-thaisonphan@google.com Signed-off-by: Jakub Kicinski --- tools/net/ynl/pyynl/ynl_gen_c.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py index cdc3646f2642..95502dbaec94 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -2747,6 +2747,9 @@ def print_dump_type_free(ri): ri.cw.block_start() ri.cw.p(f"{sub_type} *next = rsp;") ri.cw.nl() + ri.cw.p('if (!next)') + ri.cw.p('return;') + ri.cw.nl() ri.cw.block_start(line='while ((void *)next != YNL_LIST_END)') _free_type_members_iter(ri, ri.struct['reply']) ri.cw.p('rsp = next;') From 153f709c86394919ddf42a9a76c3bce064841c8d Mon Sep 17 00:00:00 2001 From: Thaison Phan Date: Fri, 7 Aug 2026 17:15:00 +0000 Subject: [PATCH 1179/1433] tools: ynl: check alloc fails in generated getter code Generated YNL getter code does not check the return value of malloc() and calloc() before passing the resulting pointer to memcpy(). This could lead to a NULL pointer dereference on memory allocation failure. Updated the C code generator to check for allocation failures and to return an error code in getters. Signed-off-by: Thaison Phan Link: https://patch.msgid.link/20260807171500.7188-3-thaisonphan@google.com Signed-off-by: Jakub Kicinski --- tools/net/ynl/pyynl/ynl_gen_c.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py index 95502dbaec94..2b3483db1b60 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -526,8 +526,10 @@ class TypeString(Type): def _attr_get(self, ri, var): len_mem = var + '->_len.' + self.c_name - return [f"{len_mem} = len;", - f"{var}->{self.c_name} = malloc(len + 1);", + return [f"{var}->{self.c_name} = malloc(len + 1);", + f"if (!{var}->{self.c_name})", + "return YNL_PARSE_CB_ERROR;", + f"{len_mem} = len;", f"memcpy({var}->{self.c_name}, ynl_attr_get_str(attr), len);", f"{var}->{self.c_name}[len] = 0;"], \ ['len = strnlen(ynl_attr_get_str(attr), ynl_attr_data_len(attr));'], \ @@ -582,8 +584,10 @@ class TypeBinary(Type): def _attr_get(self, ri, var): len_mem = var + '->_len.' + self.c_name - return [f"{len_mem} = len;", - f"{var}->{self.c_name} = malloc(len);", + return [f"{var}->{self.c_name} = malloc(len);", + f"if (!{var}->{self.c_name})", + "return YNL_PARSE_CB_ERROR;", + f"{len_mem} = len;", f"memcpy({var}->{self.c_name}, ynl_attr_data(attr), len);"], \ ['len = ynl_attr_data_len(attr);'], \ ['unsigned int len;'] @@ -601,11 +605,13 @@ class TypeBinaryStruct(TypeBinary): def _attr_get(self, ri, var): struct_sz = 'sizeof(struct ' + c_lower(self.get("struct")) + ')' len_mem = var + '->_' + self.presence_type() + '.' + self.c_name - return [f"{len_mem} = len;", - f"if (len < {struct_sz})", + return [f"if (len < {struct_sz})", f"{var}->{self.c_name} = calloc(1, {struct_sz});", "else", f"{var}->{self.c_name} = malloc(len);", + f"if (!{var}->{self.c_name})", + "return YNL_PARSE_CB_ERROR;", + f"{len_mem} = len;", f"memcpy({var}->{self.c_name}, ynl_attr_data(attr), len);"], \ ['len = ynl_attr_data_len(attr);'], \ ['unsigned int len;'] @@ -631,9 +637,11 @@ class TypeBinaryScalarArray(TypeBinary): def _attr_get(self, ri, var): len_mem = var + '->_count.' + self.c_name - return [f"{len_mem} = len / sizeof(__{self.get('sub-type')});", - f"len = {len_mem} * sizeof(__{self.get('sub-type')});", + return [f"len = (len / sizeof(__{self.get('sub-type')})) * sizeof(__{self.get('sub-type')});", f"{var}->{self.c_name} = malloc(len);", + f"if (!{var}->{self.c_name})", + "return YNL_PARSE_CB_ERROR;", + f"{len_mem} = len / sizeof(__{self.get('sub-type')});", f"memcpy({var}->{self.c_name}, ynl_attr_data(attr), len);"], \ ['len = ynl_attr_data_len(attr);'], \ ['unsigned int len;'] @@ -2227,6 +2235,8 @@ def _multi_parse(ri, struct, init_lines, local_vars): ri.cw.block_start(line=f"if (n_{aspec.c_name})") ri.cw.p(f"dst->{aspec.c_name} = calloc(n_{aspec.c_name}, sizeof(*dst->{aspec.c_name}));") + ri.cw.p(f"if (!dst->{aspec.c_name})") + ri.cw.p("return YNL_PARSE_CB_ERROR;") ri.cw.p(f"dst->_count.{aspec.c_name} = n_{aspec.c_name};") ri.cw.p('i = 0;') if 'nested-attributes' in aspec: @@ -2252,6 +2262,8 @@ def _multi_parse(ri, struct, init_lines, local_vars): aspec = struct[arg] ri.cw.block_start(line=f"if (n_{aspec.c_name})") ri.cw.p(f"dst->{aspec.c_name} = calloc(n_{aspec.c_name}, sizeof(*dst->{aspec.c_name}));") + ri.cw.p(f"if (!dst->{aspec.c_name})") + ri.cw.p("return YNL_PARSE_CB_ERROR;") ri.cw.p(f"dst->_count.{aspec.c_name} = n_{aspec.c_name};") ri.cw.p('i = 0;') if 'nested-attributes' in aspec: @@ -2275,6 +2287,8 @@ def _multi_parse(ri, struct, init_lines, local_vars): ri.cw.nl() ri.cw.p('len = strnlen(ynl_attr_get_str(attr), ynl_attr_data_len(attr));') ri.cw.p(f'dst->{aspec.c_name}[i] = malloc(sizeof(struct ynl_string) + len + 1);') + ri.cw.p(f"if (!dst->{aspec.c_name}[i])") + ri.cw.p("return YNL_PARSE_CB_ERROR;") ri.cw.p(f"dst->{aspec.c_name}[i]->len = len;") ri.cw.p(f"memcpy(dst->{aspec.c_name}[i]->str, ynl_attr_get_str(attr), len);") ri.cw.p(f"dst->{aspec.c_name}[i]->str[len] = 0;") @@ -2434,6 +2448,8 @@ def print_req(ri): if 'reply' in ri.op[ri.op_mode]: ri.cw.p('rsp = calloc(1, sizeof(*rsp));') + ri.cw.p('if (!rsp)') + ri.cw.p(f'return {ret_err};') ri.cw.p('yrs.yarg.data = rsp;') ri.cw.p(f"yrs.cb = {op_prefix(ri, 'reply')}_parse;") if ri.op.value is not None: From 5d3ae80ecddeb82b492a2cf31ac3e44412b426f4 Mon Sep 17 00:00:00 2001 From: Qing Luo Date: Fri, 7 Aug 2026 14:43:14 +0800 Subject: [PATCH 1180/1433] sctp: auth: propagate HMAC calculation errors to callers sctp_auth_calculate_hmac() can fail when building the association secret under memory pressure, but its void return silently leaves the HMAC digest zeroed. On the receive path, sctp_sf_authenticate() compares this zeroed digest against the peer-supplied one using crypto_memneq(), potentially accepting an all-zero HMAC from the peer if the allocation failed. On the send path, sctp_packet_pack() transmits a packet with a zeroed HMAC that the peer would reject. Improve error handling by making sctp_auth_calculate_hmac() return int: - sctp_sf_authenticate() returns SCTP_IERROR_NOMEM instead of accepting a zero HMAC. - sctp_packet_pack() drops the packet on failure instead of transmitting a zeroed HMAC. Update the declaration in auth.h accordingly. Assisted-by: LLM Signed-off-by: Qing Luo Acked-by: Xin Long Link: https://patch.msgid.link/20260807064314.500742-1-l1138897701@163.com Signed-off-by: Paolo Abeni --- include/net/sctp/auth.h | 6 +++--- net/sctp/auth.c | 10 ++++++---- net/sctp/output.c | 12 +++++++++--- net/sctp/sm_statefuns.c | 9 ++++++--- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/include/net/sctp/auth.h b/include/net/sctp/auth.h index 6f2cd562b1de..eeb3297fe97d 100644 --- a/include/net/sctp/auth.h +++ b/include/net/sctp/auth.h @@ -83,9 +83,9 @@ int sctp_auth_send_cid(enum sctp_cid chunk, const struct sctp_association *asoc); int sctp_auth_recv_cid(enum sctp_cid chunk, const struct sctp_association *asoc); -void sctp_auth_calculate_hmac(const struct sctp_association *asoc, - struct sk_buff *skb, struct sctp_auth_chunk *auth, - struct sctp_shared_key *ep_key, gfp_t gfp); +int sctp_auth_calculate_hmac(const struct sctp_association *asoc, + struct sk_buff *skb, struct sctp_auth_chunk *auth, + struct sctp_shared_key *ep_key, gfp_t gfp); void sctp_auth_shkey_release(struct sctp_shared_key *sh_key); void sctp_auth_shkey_hold(struct sctp_shared_key *sh_key); diff --git a/net/sctp/auth.c b/net/sctp/auth.c index c901d373af80..6de66f56c41c 100644 --- a/net/sctp/auth.c +++ b/net/sctp/auth.c @@ -613,9 +613,9 @@ int sctp_auth_recv_cid(enum sctp_cid chunk, const struct sctp_association *asoc) * zero (as shown in Figure 6) followed by all chunks that are placed * after the AUTH chunk in the SCTP packet. */ -void sctp_auth_calculate_hmac(const struct sctp_association *asoc, - struct sk_buff *skb, struct sctp_auth_chunk *auth, - struct sctp_shared_key *ep_key, gfp_t gfp) +int sctp_auth_calculate_hmac(const struct sctp_association *asoc, + struct sk_buff *skb, struct sctp_auth_chunk *auth, + struct sctp_shared_key *ep_key, gfp_t gfp) { struct sctp_auth_bytes *asoc_key; __u16 key_id, hmac_id; @@ -636,7 +636,7 @@ void sctp_auth_calculate_hmac(const struct sctp_association *asoc, /* ep_key can't be NULL here */ asoc_key = sctp_auth_asoc_create_secret(asoc, ep_key, gfp); if (!asoc_key) - return; + return -ENOMEM; free_key = 1; } @@ -654,6 +654,8 @@ void sctp_auth_calculate_hmac(const struct sctp_association *asoc, if (free_key) sctp_auth_key_put(asoc_key); + + return 0; } /* API Helpers */ diff --git a/net/sctp/output.c b/net/sctp/output.c index 23e96305cad7..3d7ead9d40e1 100644 --- a/net/sctp/output.c +++ b/net/sctp/output.c @@ -517,8 +517,14 @@ static int sctp_packet_pack(struct sctp_packet *packet, } if (auth) { - sctp_auth_calculate_hmac(tp->asoc, nskb, auth, - packet->auth->shkey, gfp); + if (sctp_auth_calculate_hmac(tp->asoc, nskb, auth, + packet->auth->shkey, gfp)) { + sctp_chunk_free(packet->auth); + packet->auth = NULL; + if (gso) + kfree_skb(nskb); + return -ENOMEM; + } /* free auth if no more chunks, or add it back */ if (list_empty(&packet->chunk_list)) sctp_chunk_free(packet->auth); @@ -619,7 +625,7 @@ int sctp_packet_transmit(struct sctp_packet *packet, gfp_t gfp) /* pack up chunks */ pkt_count = sctp_packet_pack(packet, head, gso, gfp); - if (!pkt_count) { + if (pkt_count <= 0) { kfree_skb(head); goto out; } diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c index 708fa07d5fff..bb89c9b52e0b 100644 --- a/net/sctp/sm_statefuns.c +++ b/net/sctp/sm_statefuns.c @@ -4455,9 +4455,12 @@ static enum sctp_ierror sctp_sf_authenticate( memset(digest, 0, sig_len); - sctp_auth_calculate_hmac(asoc, chunk->skb, - (struct sctp_auth_chunk *)chunk->chunk_hdr, - sh_key, GFP_ATOMIC); + if (sctp_auth_calculate_hmac(asoc, chunk->skb, + (struct sctp_auth_chunk *)chunk->chunk_hdr, + sh_key, GFP_ATOMIC)) { + kfree(save_digest); + return SCTP_IERROR_NOMEM; + } /* Discard the packet if the digests do not match */ if (crypto_memneq(save_digest, digest, sig_len)) { From 88b8d85e889e2fe1cce3e66e50c3029dda94842a Mon Sep 17 00:00:00 2001 From: Feng Tang Date: Fri, 7 Aug 2026 08:24:36 +0800 Subject: [PATCH 1181/1433] selftests: net: reuseport_bpf_numa: consider cpuless numa node reuseport_bpf_numa case failed when testing on a platform with CXL memory: #./reuseport_bpf_numa ---- IPv4 UDP ---- send node 0, receive socket 0 ./reuseport_bpf_numa: failed to pin to node: Invalid argument The root cause is that the platform has 2 numa nodes: node 0 has both cpu and memory, while node 1 is a CXL node which only has memory, and caused numa_run_on_node() to fail. Add sanity check to skip cpuless numa node for the numa binding test. Signed-off-by: Feng Tang Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260807002436.43991-1-feng.tang@linux.alibaba.com Signed-off-by: Paolo Abeni --- .../selftests/net/reuseport_bpf_numa.c | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tools/testing/selftests/net/reuseport_bpf_numa.c b/tools/testing/selftests/net/reuseport_bpf_numa.c index 8ec52fc5ef41..6e4817ef57c5 100644 --- a/tools/testing/selftests/net/reuseport_bpf_numa.c +++ b/tools/testing/selftests/net/reuseport_bpf_numa.c @@ -104,6 +104,26 @@ static void attach_bpf(int fd) close(bpf_fd); } +/* + * Return true if it is a cpuless node. Return false if it isn't or any + * error (very unlikely) happens during the libnuma calls. + */ +static bool is_cpuless_node(int node_id) +{ + struct bitmask *cpumask; + bool ret = false; + + cpumask = numa_allocate_cpumask(); + if (!cpumask) + return ret; + + if (!numa_node_to_cpus(node_id, cpumask) && !numa_bitmask_weight(cpumask)) + ret = true; + + numa_bitmask_free(cpumask); + return ret; +} + static void send_from_node(int node_id, int family, int proto) { struct sockaddr_storage saddr, daddr; @@ -213,6 +233,8 @@ static void test(int *rcv_fd, int len, int family, int proto) for (node = 0; node < len; ++node) { if (!numa_bitmask_isbitset(numa_nodes_ptr, node)) continue; + if (is_cpuless_node(node)) + continue; send_from_node(node, family, proto); receive_on_node(rcv_fd, len, epfd, node, proto); } @@ -221,6 +243,8 @@ static void test(int *rcv_fd, int len, int family, int proto) for (node = len - 1; node >= 0; --node) { if (!numa_bitmask_isbitset(numa_nodes_ptr, node)) continue; + if (is_cpuless_node(node)) + continue; send_from_node(node, family, proto); receive_on_node(rcv_fd, len, epfd, node, proto); } From 346630e46b387ad6db7b3b254ba6f6d513d64d14 Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Fri, 7 Aug 2026 11:59:25 +0200 Subject: [PATCH 1182/1433] dpll: zl3073x: update all DPLL channels on ref_sync_set zl3073x_dpll_input_pin_ref_sync_set() excludes the sync source from automatic reference selection by setting its priority to NONE, but currently only does this on the single DPLL channel whose pin_priv was passed to the callback. Since input pins are registered with every DPLL channel, the datasheet recommends covering all channels to prevent the sync source from remaining a selectable candidate on the other channels. This is a preparation for the following patch which changes the DPLL core to invoke pin-level set callbacks only through the pin owner's reference instead of iterating over all registered DPLL devices. Replace the single-channel priority write with a list_for_each_entry() loop over all DPLL channels. Each channel's lock is acquired individually for its read-modify-write sequence. The guard(mutex) is replaced with explicit mutex_lock/mutex_unlock to allow releasing the owner's lock before iterating, avoiding nested locking of the same mutex class. A change notification is sent for the sync pin if any channel's priority was actually modified. Signed-off-by: Ivan Vecera Reviewed-by: Petr Oros Link: https://patch.msgid.link/20260807095926.386923-2-ivecera@redhat.com Signed-off-by: Paolo Abeni --- drivers/dpll/zl3073x/dpll.c | 58 ++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c index 0488ae6ac486..83bd3027dbaa 100644 --- a/drivers/dpll/zl3073x/dpll.c +++ b/drivers/dpll/zl3073x/dpll.c @@ -263,9 +263,10 @@ zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin, u8 mode, ref_id, sync_ref_id; struct zl3073x_chan chan; struct zl3073x_ref ref; + bool sync_ntf = false; int rc; - guard(mutex)(&zldpll->lock); + mutex_lock(&zldpll->lock); ref_id = zl3073x_input_pin_ref_get(pin->id); sync_ref_id = zl3073x_input_pin_ref_get(sync_pin->id); @@ -285,17 +286,20 @@ zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin, if (sync_freq > 8000) { NL_SET_ERR_MSG(extack, "sync frequency must be 8 kHz or less"); - return -EINVAL; + rc = -EINVAL; + goto unlock; } if (ref_freq < 1000) { NL_SET_ERR_MSG(extack, "clock frequency must be 1 kHz or more"); - return -EINVAL; + rc = -EINVAL; + goto unlock; } if (ref_freq <= sync_freq) { NL_SET_ERR_MSG(extack, "clock frequency must be higher than sync frequency"); - return -EINVAL; + rc = -EINVAL; + goto unlock; } zl3073x_ref_sync_pair_set(&ref, sync_ref_id); @@ -308,20 +312,54 @@ zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin, rc = zl3073x_ref_state_set(zldev, ref_id, &ref); if (rc) - return rc; + goto unlock; - /* Exclude sync source from automatic reference selection by setting - * its priority to NONE. On disconnect the priority is left as NONE - * and the user must explicitly make the pin selectable again. + /* All code paths accessing per-channel reference priorities are + * serialized by the subsystem dpll_lock, so it is safe to release + * our lock here before iterating over the other channels. */ - if (state == DPLL_PIN_STATE_CONNECTED) { + mutex_unlock(&zldpll->lock); + + if (state != DPLL_PIN_STATE_CONNECTED) + return 0; + + /* The datasheet recommends excluding the sync source from automatic + * reference selection by setting its priority to NONE on all DPLL + * channels. This is advisory - the ref sync pair is already + * configured, so a failure here is not fatal. On disconnect the + * priority is left as NONE and the user must explicitly make the + * pin selectable again. + */ + list_for_each_entry(zldpll, &zldev->dplls, list) { + u8 prio; + + mutex_lock(&zldpll->lock); + chan = *zl3073x_chan_state_get(zldev, zldpll->id); + prio = zl3073x_chan_ref_prio_get(&chan, sync_ref_id); + if (prio == ZL_DPLL_REF_PRIO_NONE) { + mutex_unlock(&zldpll->lock); + continue; /* Ref is already non-selectable */ + } + zl3073x_chan_ref_prio_set(&chan, sync_ref_id, ZL_DPLL_REF_PRIO_NONE); - return zl3073x_chan_state_set(zldev, zldpll->id, &chan); + if (zl3073x_chan_state_set(zldev, zldpll->id, &chan)) + dev_warn(zldev->dev, + "Failed to set ref prio on DPLL%u\n", + zldpll->id); + else + sync_ntf = true; + + mutex_unlock(&zldpll->lock); } + if (sync_ntf) + __dpll_pin_change_ntf(sync_pin->dpll_pin); return 0; +unlock: + mutex_unlock(&zldpll->lock); + return rc; } static int From 84e85c325e5ed6781758685bf236021cc6aeed17 Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Fri, 7 Aug 2026 11:59:26 +0200 Subject: [PATCH 1183/1433] dpll: use pin owner's dpll ref for pin-level attribute setting Pin-level attributes (frequency, phase adjust, embedded sync, reference sync) are properties of the pin itself, not of a particular DPLL device. The get callbacks already use only the pin owner's DPLL reference (via dpll_pin_own_dpll_ref_first()), but the set callbacks iterate over all registered DPLL references and invoke the set operation on each one. This is redundant because a pin is a single physical entity - setting its frequency or phase adjust once through the owner's ops is sufficient. Calling set on every registered DPLL just results in duplicate HW writes for drivers that share a pin across multiple DPLL devices (e.g. ice registers each input pin with both the EEC and PPS DPLL, zl3073x registers input pins with every DPLL channel). Simplify dpll_pin_freq_set(), dpll_pin_esync_set(), dpll_pin_ref_sync_state_set() and dpll_pin_phase_adj_set() to call the set callback only through the owner's DPLL reference, matching the existing get-side behavior. This removes the xa_for_each iteration loops, the now-unnecessary rollback logic, and several local variables. The -EOPNOTSUPP validation loop, which checked ops support across all owner-matching references, is replaced with a direct check on the single owner reference returned by dpll_pin_own_dpll_ref_first(). The documentation in dpll.rst is updated to reflect that pin-level attributes are set through the pin owner's dpll reference only. No existing driver is affected: - ptp_ocp and mlx5 register each pin with a single DPLL. - ice registers input pins with two DPLLs (EEC and PPS) using identical ops and pin_priv; the set callbacks address the HW by pin index, not by DPLL, so the second call was a no-op. - zl3073x registers input pins with every DPLL channel; the set callbacks address HW by pin/ref ID regardless of DPLL. The ref_sync_set callback was the only one with per-channel behavior, addressed by the preceding patch. Signed-off-by: Ivan Vecera Reviewed-by: Jiri Pirko Link: https://patch.msgid.link/20260807095926.386923-3-ivecera@redhat.com Signed-off-by: Paolo Abeni --- Documentation/driver-api/dpll.rst | 11 +- drivers/dpll/dpll_netlink.c | 213 +++++++----------------------- 2 files changed, 56 insertions(+), 168 deletions(-) diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst index f83150917814..7c117ae37cc1 100644 --- a/Documentation/driver-api/dpll.rst +++ b/Documentation/driver-api/dpll.rst @@ -116,8 +116,9 @@ Shared pins A single pin object can be attached to multiple dpll devices. Then there are two groups of configuration knobs: -1) Set on a pin - the configuration affects all dpll devices pin is - registered to (i.e., ``DPLL_A_PIN_FREQUENCY``), +1) Set on a pin - the configuration is a property of the pin itself and + applies to all dpll devices the pin is registered with + (i.e., ``DPLL_A_PIN_FREQUENCY``), 2) Set on a pin-dpll tuple - the configuration affects only selected dpll device (i.e., ``DPLL_A_PIN_PRIO``, ``DPLL_A_PIN_STATE``, ``DPLL_A_PIN_DIRECTION``). @@ -507,9 +508,9 @@ as well as parameter being configured (``DPLL_A_MODE``). ``DPLL_CMD_PIN_SET`` - to target a pin user must provide a ``DPLL_A_PIN_ID``, which is unique identifier of a pin in the system. Also configured pin parameters must be added. -If ``DPLL_A_PIN_FREQUENCY`` is configured, this affects all the dpll -devices that are connected with the pin, that is why frequency attribute -shall not be enclosed in ``DPLL_A_PIN_PARENT_DEVICE``. +If ``DPLL_A_PIN_FREQUENCY`` is configured, it is a property of the pin +itself and applies to all dpll devices the pin is registered with, so the +frequency attribute shall not be enclosed in ``DPLL_A_PIN_PARENT_DEVICE``. Other attributes: ``DPLL_A_PIN_PRIO``, ``DPLL_A_PIN_STATE`` or ``DPLL_A_PIN_DIRECTION`` must be enclosed in ``DPLL_A_PIN_PARENT_DEVICE`` as their configuration relates to only one diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index afb31c004038..a909cd4451b0 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -1079,10 +1079,9 @@ dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, struct netlink_ext_ack *extack) { u64 freq = nla_get_u64(a), old_freq; - struct dpll_pin_ref *ref, *failed; const struct dpll_pin_ops *ops; + struct dpll_pin_ref *ref; struct dpll_device *dpll; - unsigned long i; int ret; if (!dpll_pin_is_freq_supported(pin, freq)) { @@ -1090,22 +1089,17 @@ dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, return -EINVAL; } - xa_for_each(&pin->dpll_refs, i, ref) { - ops = dpll_pin_ops(ref); - if ((!ops->frequency_set || !ops->frequency_get) && - ref->dpll->module == pin->module && - ref->dpll->clock_id == pin->clock_id) { - NL_SET_ERR_MSG(extack, - "frequency set not supported by the device"); - return -EOPNOTSUPP; - } - } ref = dpll_pin_own_dpll_ref_first(pin); if (!ref) { NL_SET_ERR_MSG(extack, "pin owner dpll not found"); return -ENODEV; } ops = dpll_pin_ops(ref); + if (!ops->frequency_set || !ops->frequency_get) { + NL_SET_ERR_MSG(extack, + "frequency set not supported by the device"); + return -EOPNOTSUPP; + } dpll = ref->dpll; ret = ops->frequency_get(pin, dpll_pin_on_dpll_priv(dpll, pin), dpll, dpll_priv(dpll), &old_freq, extack); @@ -1116,68 +1110,42 @@ dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, if (freq == old_freq) return 0; - xa_for_each(&pin->dpll_refs, i, ref) { - ops = dpll_pin_ops(ref); - if (!ops->frequency_set) - continue; - dpll = ref->dpll; - ret = ops->frequency_set(pin, dpll_pin_on_dpll_priv(dpll, pin), - dpll, dpll_priv(dpll), freq, extack); - if (ret) { - failed = ref; - NL_SET_ERR_MSG_FMT(extack, "frequency set failed for dpll_id:%u", - dpll->id); - goto rollback; - } + ret = ops->frequency_set(pin, dpll_pin_on_dpll_priv(dpll, pin), + dpll, dpll_priv(dpll), freq, extack); + if (ret) { + NL_SET_ERR_MSG_FMT(extack, + "frequency set failed for dpll_id:%u", + dpll->id); + return ret; } __dpll_pin_change_ntf(pin); return 0; - -rollback: - xa_for_each(&pin->dpll_refs, i, ref) { - if (ref == failed) - break; - ops = dpll_pin_ops(ref); - if (!ops->frequency_set) - continue; - dpll = ref->dpll; - if (ops->frequency_set(pin, dpll_pin_on_dpll_priv(dpll, pin), - dpll, dpll_priv(dpll), old_freq, extack)) - NL_SET_ERR_MSG(extack, "set frequency rollback failed"); - } - return ret; } static int dpll_pin_esync_set(struct dpll_pin *pin, struct nlattr *a, struct netlink_ext_ack *extack) { - struct dpll_pin_ref *ref, *failed; const struct dpll_pin_ops *ops; struct dpll_pin_esync esync; u64 freq = nla_get_u64(a); + struct dpll_pin_ref *ref; struct dpll_device *dpll; bool supported = false; - unsigned long i; - int ret; + int ret, i; - xa_for_each(&pin->dpll_refs, i, ref) { - ops = dpll_pin_ops(ref); - if ((!ops->esync_set || !ops->esync_get) && - ref->dpll->module == pin->module && - ref->dpll->clock_id == pin->clock_id) { - NL_SET_ERR_MSG(extack, - "embedded sync feature is not supported by this device"); - return -EOPNOTSUPP; - } - } ref = dpll_pin_own_dpll_ref_first(pin); if (!ref) { NL_SET_ERR_MSG(extack, "pin owner dpll not found"); return -ENODEV; } ops = dpll_pin_ops(ref); + if (!ops->esync_set || !ops->esync_get) { + NL_SET_ERR_MSG(extack, + "embedded sync feature is not supported by this device"); + return -EOPNOTSUPP; + } dpll = ref->dpll; ret = ops->esync_get(pin, dpll_pin_on_dpll_priv(dpll, pin), dpll, dpll_priv(dpll), &esync, extack); @@ -1196,44 +1164,17 @@ dpll_pin_esync_set(struct dpll_pin *pin, struct nlattr *a, return -EINVAL; } - xa_for_each(&pin->dpll_refs, i, ref) { - void *pin_dpll_priv; - - ops = dpll_pin_ops(ref); - if (!ops->esync_set) - continue; - dpll = ref->dpll; - pin_dpll_priv = dpll_pin_on_dpll_priv(dpll, pin); - ret = ops->esync_set(pin, pin_dpll_priv, dpll, dpll_priv(dpll), - freq, extack); - if (ret) { - failed = ref; - NL_SET_ERR_MSG_FMT(extack, - "embedded sync frequency set failed for dpll_id: %u", - dpll->id); - goto rollback; - } + ret = ops->esync_set(pin, dpll_pin_on_dpll_priv(dpll, pin), dpll, + dpll_priv(dpll), freq, extack); + if (ret) { + NL_SET_ERR_MSG_FMT(extack, + "embedded sync frequency set failed for dpll_id: %u", + dpll->id); + return ret; } __dpll_pin_change_ntf(pin); return 0; - -rollback: - xa_for_each(&pin->dpll_refs, i, ref) { - void *pin_dpll_priv; - - if (ref == failed) - break; - ops = dpll_pin_ops(ref); - if (!ops->esync_set) - continue; - dpll = ref->dpll; - pin_dpll_priv = dpll_pin_on_dpll_priv(dpll, pin); - if (ops->esync_set(pin, pin_dpll_priv, dpll, dpll_priv(dpll), - esync.freq, extack)) - NL_SET_ERR_MSG(extack, "set embedded sync frequency rollback failed"); - } - return ret; } static int @@ -1241,14 +1182,12 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, unsigned long ref_sync_pin_idx, const enum dpll_pin_state state, struct netlink_ext_ack *extack) - { - struct dpll_pin_ref *ref, *failed; const struct dpll_pin_ops *ops; enum dpll_pin_state old_state; struct dpll_pin *ref_sync_pin; + struct dpll_pin_ref *ref; struct dpll_device *dpll; - unsigned long i; int ret; ref_sync_pin = xa_find(&pin->ref_sync_pins, &ref_sync_pin_idx, @@ -1282,42 +1221,20 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, } if (state == old_state) return 0; - xa_for_each(&pin->dpll_refs, i, ref) { - ops = dpll_pin_ops(ref); - if (!ops->ref_sync_set) - continue; - dpll = ref->dpll; - ret = ops->ref_sync_set(pin, dpll_pin_on_dpll_priv(dpll, pin), - ref_sync_pin, - dpll_pin_on_dpll_priv(dpll, - ref_sync_pin), - state, extack); - if (ret) { - failed = ref; - NL_SET_ERR_MSG_FMT(extack, "reference sync set failed for dpll_id:%u", - dpll->id); - goto rollback; - } + + ret = ops->ref_sync_set(pin, dpll_pin_on_dpll_priv(dpll, pin), + ref_sync_pin, + dpll_pin_on_dpll_priv(dpll, ref_sync_pin), + state, extack); + if (ret) { + NL_SET_ERR_MSG_FMT(extack, + "reference sync set failed for dpll_id:%u", + dpll->id); + return ret; } __dpll_pin_change_ntf(pin); return 0; - -rollback: - xa_for_each(&pin->dpll_refs, i, ref) { - if (ref == failed) - break; - ops = dpll_pin_ops(ref); - if (!ops->ref_sync_set) - continue; - dpll = ref->dpll; - if (ops->ref_sync_set(pin, dpll_pin_on_dpll_priv(dpll, pin), - ref_sync_pin, - dpll_pin_on_dpll_priv(dpll, ref_sync_pin), - old_state, extack)) - NL_SET_ERR_MSG(extack, "set reference sync rollback failed"); - } - return ret; } static int @@ -1478,11 +1395,10 @@ static int dpll_pin_phase_adj_set(struct dpll_pin *pin, struct nlattr *phase_adj_attr, struct netlink_ext_ack *extack) { - struct dpll_pin_ref *ref, *failed; const struct dpll_pin_ops *ops; s32 phase_adj, old_phase_adj; + struct dpll_pin_ref *ref; struct dpll_device *dpll; - unsigned long i; int ret; phase_adj = nla_get_s32(phase_adj_attr); @@ -1499,21 +1415,16 @@ dpll_pin_phase_adj_set(struct dpll_pin *pin, struct nlattr *phase_adj_attr, return -EINVAL; } - xa_for_each(&pin->dpll_refs, i, ref) { - ops = dpll_pin_ops(ref); - if ((!ops->phase_adjust_set || !ops->phase_adjust_get) && - ref->dpll->module == pin->module && - ref->dpll->clock_id == pin->clock_id) { - NL_SET_ERR_MSG(extack, "phase adjust not supported"); - return -EOPNOTSUPP; - } - } ref = dpll_pin_own_dpll_ref_first(pin); if (!ref) { NL_SET_ERR_MSG(extack, "pin owner dpll not found"); return -ENODEV; } ops = dpll_pin_ops(ref); + if (!ops->phase_adjust_set || !ops->phase_adjust_get) { + NL_SET_ERR_MSG(extack, "phase adjust not supported"); + return -EOPNOTSUPP; + } dpll = ref->dpll; ret = ops->phase_adjust_get(pin, dpll_pin_on_dpll_priv(dpll, pin), dpll, dpll_priv(dpll), &old_phase_adj, @@ -1525,41 +1436,17 @@ dpll_pin_phase_adj_set(struct dpll_pin *pin, struct nlattr *phase_adj_attr, if (phase_adj == old_phase_adj) return 0; - xa_for_each(&pin->dpll_refs, i, ref) { - ops = dpll_pin_ops(ref); - if (!ops->phase_adjust_set) - continue; - dpll = ref->dpll; - ret = ops->phase_adjust_set(pin, - dpll_pin_on_dpll_priv(dpll, pin), - dpll, dpll_priv(dpll), phase_adj, - extack); - if (ret) { - failed = ref; - NL_SET_ERR_MSG_FMT(extack, - "phase adjust set failed for dpll_id:%u", - dpll->id); - goto rollback; - } + ret = ops->phase_adjust_set(pin, dpll_pin_on_dpll_priv(dpll, pin), + dpll, dpll_priv(dpll), phase_adj, extack); + if (ret) { + NL_SET_ERR_MSG_FMT(extack, + "phase adjust set failed for dpll_id:%u", + dpll->id); + return ret; } __dpll_pin_change_ntf(pin); return 0; - -rollback: - xa_for_each(&pin->dpll_refs, i, ref) { - if (ref == failed) - break; - ops = dpll_pin_ops(ref); - if (!ops->phase_adjust_set) - continue; - dpll = ref->dpll; - if (ops->phase_adjust_set(pin, dpll_pin_on_dpll_priv(dpll, pin), - dpll, dpll_priv(dpll), old_phase_adj, - extack)) - NL_SET_ERR_MSG(extack, "set phase adjust rollback failed"); - } - return ret; } static int From 4f20c628b62eb86babdc28cbd1befa6bd858a62d Mon Sep 17 00:00:00 2001 From: Jian Shen Date: Fri, 7 Aug 2026 17:54:33 +0800 Subject: [PATCH 1184/1433] net: hns3: set msg->desc to NULL after kfree in hclge_query_reg_info() In hclge_query_reg_info(), msg->desc is freed by kfree(), but the caller continues to use msg across loop iterations. Set msg->desc to NULL to avoid leaving a dangling pointer in the reused struct. Signed-off-by: Jian Shen Signed-off-by: Jijie Shao Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260807095435.2959246-2-shaojijie@huawei.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_err.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_err.c b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_err.c index dac051e798da..7e124e2c718d 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_err.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_err.c @@ -1592,6 +1592,7 @@ hclge_query_reg_info(struct hclge_dev *hdev, } kfree(msg->desc); + msg->desc = NULL; } static void hclge_query_reg_info_of_ssu(struct hclge_dev *hdev) From b8f554e13899fe2635a59f0260eec9a047247009 Mon Sep 17 00:00:00 2001 From: Jijie Shao Date: Fri, 7 Aug 2026 17:54:34 +0800 Subject: [PATCH 1185/1433] net: hns3: add missing const qualifier to hclge_log_error() reg parameter The reg parameter of hclge_log_error() is never modified within the function, but is declared as 'char *'. Callers pass const strings, causing a compiler warning about discarding the 'const' qualifier. Add the missing const to fix the warning. Signed-off-by: Jijie Shao Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260807095435.2959246-3-shaojijie@huawei.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_err.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_err.c b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_err.c index 7e124e2c718d..6093a60d257b 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_err.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_err.c @@ -1762,7 +1762,7 @@ static const struct hclge_hw_type_id hclge_hw_type_id_st[] = { }, }; -static void hclge_log_error(struct device *dev, char *reg, +static void hclge_log_error(struct device *dev, const char *reg, const struct hclge_hw_error *err, u32 err_sts, unsigned long *reset_requests) { From f57b277e8b6f6e6d3bc082be6b67c6bec02d5cbd Mon Sep 17 00:00:00 2001 From: Jian Shen Date: Fri, 7 Aug 2026 17:54:35 +0800 Subject: [PATCH 1186/1433] net: hns3: use txqueue parameter directly in ndo_tx_timeout The ndo_tx_timeout callback already provides the timed out txqueue index. Use it directly instead of iterating all tx queues to find the timed out one. Use h->kinfo.num_tqps for the bounds check instead of ndev->num_tx_queues, as the ring array is allocated with num_tqps entries and num_tx_queues may be larger. This issue has not been encountered in practice, so it is folded into this cleanup rather than tracked as a separate bugfix. Signed-off-by: Jian Shen Signed-off-by: Jijie Shao Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260807095435.2959246-4-shaojijie@huawei.com Signed-off-by: Paolo Abeni --- .../net/ethernet/hisilicon/hns3/hns3_enet.c | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3_enet.c b/drivers/net/ethernet/hisilicon/hns3/hns3_enet.c index 6ecb32e28e79..47788be64be6 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3_enet.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3_enet.c @@ -2825,32 +2825,28 @@ static int hns3_nic_change_mtu(struct net_device *netdev, int new_mtu) return ret; } -static int hns3_get_timeout_queue(struct net_device *ndev) +static bool hns3_dump_timeout_queue(struct net_device *ndev, + unsigned int txqueue) { - unsigned int i; + unsigned int timedout_ms; + struct netdev_queue *q; - /* Find the stopped queue the same way the stack does */ - for (i = 0; i < ndev->num_tx_queues; i++) { - unsigned int timedout_ms; - struct netdev_queue *q; - - q = netdev_get_tx_queue(ndev, i); - timedout_ms = netif_xmit_timeout_ms(q); - if (timedout_ms) { + q = netdev_get_tx_queue(ndev, txqueue); + timedout_ms = netif_xmit_timeout_ms(q); + if (timedout_ms) { #ifdef CONFIG_BQL - struct dql *dql = &q->dql; + struct dql *dql = &q->dql; - netdev_info(ndev, "DQL info last_cnt: %u, queued: %u, adj_limit: %u, completed: %u\n", - dql->last_obj_cnt, dql->num_queued, - dql->adj_limit, dql->num_completed); + netdev_info(ndev, "DQL info last_cnt: %u, queued: %u, adj_limit: %u, completed: %u\n", + dql->last_obj_cnt, dql->num_queued, + dql->adj_limit, dql->num_completed); #endif - netdev_info(ndev, "queue state: 0x%lx, delta msecs: %u\n", - q->state, timedout_ms); - break; - } + netdev_info(ndev, "queue state: 0x%lx, delta msecs: %u\n", + q->state, timedout_ms); + return true; } - return i; + return false; } static void hns3_dump_queue_stats(struct net_device *ndev, @@ -2900,15 +2896,15 @@ static void hns3_dump_queue_reg(struct net_device *ndev, HNS3_RING_TX_RING_EBD_OFFSET_REG)); } -static bool hns3_get_tx_timeo_queue_info(struct net_device *ndev) +static bool hns3_get_tx_timeo_queue_info(struct net_device *ndev, + unsigned int txqueue) { struct hns3_nic_priv *priv = netdev_priv(ndev); struct hnae3_handle *h = hns3_get_handle(ndev); struct hns3_enet_ring *tx_ring; - u32 timeout_queue; - timeout_queue = hns3_get_timeout_queue(ndev); - if (timeout_queue >= ndev->num_tx_queues) { + if (txqueue >= h->kinfo.num_tqps || + !hns3_dump_timeout_queue(ndev, txqueue)) { netdev_info(ndev, "no netdev TX timeout queue found, timeout count: %llu\n", priv->tx_timeout_count); @@ -2917,8 +2913,8 @@ static bool hns3_get_tx_timeo_queue_info(struct net_device *ndev) priv->tx_timeout_count++; - tx_ring = &priv->ring[timeout_queue]; - hns3_dump_queue_stats(ndev, tx_ring, timeout_queue); + tx_ring = &priv->ring[txqueue]; + hns3_dump_queue_stats(ndev, tx_ring, txqueue); /* When mac received many pause frames continuous, it's unable to send * packets, which may cause tx timeout @@ -2941,7 +2937,7 @@ static void hns3_nic_net_timeout(struct net_device *ndev, unsigned int txqueue) struct hns3_nic_priv *priv = netdev_priv(ndev); struct hnae3_handle *h = priv->ae_handle; - if (!hns3_get_tx_timeo_queue_info(ndev)) + if (!hns3_get_tx_timeo_queue_info(ndev, txqueue)) return; /* request the reset, and let the hclge to determine From 99985bfa8336fadcc69190ba2dcbd5386af3d661 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 21 May 2026 07:32:09 -0700 Subject: [PATCH 1187/1433] nfc: llcp: avoid userspace overflow on invalid optlen nfc_llcp_getsockopt() casts optval to (u32 __user *) for put_user(), so the kernel always stores 4 bytes regardless of the caller-supplied optlen. The existing min_t(u32, len, sizeof(u32)) only clamps the length reported back to userspace; it does not constrain the store. A call with optlen < 4 therefore writes past the user buffer, violating the getsockopt(2) contract for all five supported optnames. Reject any call with optlen < sizeof(u32) up front. 'len' is int, so a plain size comparison would promote a negative optlen to size_t and slip past the check; an explicit 'len < 0' test is added first to catch negative values before the size compare. Fixes: 26fd76cab2e6 ("NFC: llcp: Implement socket options") Signed-off-by: Breno Leitao Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260521-fix_llc-v2-1-ab44cc09179c@debian.org Signed-off-by: David Heidelberg --- net/nfc/llcp_sock.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/nfc/llcp_sock.c b/net/nfc/llcp_sock.c index feab29fc62f4..4b162df0c3fc 100644 --- a/net/nfc/llcp_sock.c +++ b/net/nfc/llcp_sock.c @@ -319,6 +319,12 @@ static int nfc_llcp_getsockopt(struct socket *sock, int level, int optname, if (get_user(len, optlen)) return -EFAULT; + if (len < 0) + return -EINVAL; + + if (len < sizeof(u32)) + return -EINVAL; + local = llcp_sock->local; if (!local) return -ENODEV; From 36812527052c5bfb1ec6c1e292d67a5bf76b750f Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 21 May 2026 07:32:10 -0700 Subject: [PATCH 1188/1433] nfc: llcp: read llcp_sock->local under the socket lock in getsockopt nfc_llcp_getsockopt() read llcp_sock->local before lock_sock(sk) and then dereferenced the cached pointer inside the locked region. llcp_sock_bind() assigns and clears llcp_sock->local under the same socket lock, dropping the last reference on its error path. A getsockopt() racing an in-flight bind() can observe the pointer, block on lock_sock(), and then dereference a freed nfc_llcp_local once bind() has unwound. Move the llcp_sock->local read and the NULL check inside the lock_sock(sk) region so bind() cannot mutate or free the pointer between the load and the use. Fixes: 26fd76cab2e6 ("NFC: llcp: Implement socket options") Signed-off-by: Breno Leitao Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260521-fix_llc-v2-2-ab44cc09179c@debian.org Signed-off-by: David Heidelberg --- net/nfc/llcp_sock.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/net/nfc/llcp_sock.c b/net/nfc/llcp_sock.c index 4b162df0c3fc..5558d8a4d48b 100644 --- a/net/nfc/llcp_sock.c +++ b/net/nfc/llcp_sock.c @@ -325,14 +325,16 @@ static int nfc_llcp_getsockopt(struct socket *sock, int level, int optname, if (len < sizeof(u32)) return -EINVAL; - local = llcp_sock->local; - if (!local) - return -ENODEV; - len = min_t(u32, len, sizeof(u32)); lock_sock(sk); + local = llcp_sock->local; + if (!local) { + release_sock(sk); + return -ENODEV; + } + switch (optname) { case NFC_LLCP_RW: rw = llcp_sock->rw > LLCP_MAX_RW ? local->rw : llcp_sock->rw; From 8265a626cc14a48e46e6dc8c47667e72b4232ac2 Mon Sep 17 00:00:00 2001 From: Zhenghang Xiao Date: Tue, 26 May 2026 18:31:21 +0800 Subject: [PATCH 1189/1433] nfc: nci: fix double completion race in nci_data_exchange_complete nci_close_device() and nci_rx_work can both call nci_data_exchange_complete() concurrently. After commit 4527025d440ce8 ("nfc: nci: fix circular locking dependency in nci_close_device") moved flush_workqueue(ndev->rx_wq) after mutex_unlock(&ndev->req_lock), rx_work is no longer serialized with the explicit completion call in the close path. Both callers read the non-NULL callback pointer and invoke rawsock_data_exchange_complete(), which calls sock_put() -- but only one sock_hold() was taken, so the second sock_put() underflows the refcount and frees the socket while it is still in use. Replace the bare clear_bit(NCI_DATA_EXCHANGE) with test_and_clear_bit() so that only the first caller proceeds to invoke the callback. Fixes: 4527025d440c ("nfc: nci: fix circular locking dependency in nci_close_device") Signed-off-by: Zhenghang Xiao Link: https://patch.msgid.link/20260526103121.47957-1-kipreyyy@gmail.com Signed-off-by: David Heidelberg --- net/nfc/nci/data.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/net/nfc/nci/data.c b/net/nfc/nci/data.c index 5f98c73db5af..4253edea5c8d 100644 --- a/net/nfc/nci/data.c +++ b/net/nfc/nci/data.c @@ -46,11 +46,11 @@ void nci_data_exchange_complete(struct nci_dev *ndev, struct sk_buff *skb, timer_delete_sync(&ndev->data_timer); clear_bit(NCI_DATA_EXCHANGE_TO, &ndev->flags); - /* Mark the exchange as done before calling the callback. - * The callback (e.g. rawsock_data_exchange_complete) may - * want to immediately queue another data exchange. - */ - clear_bit(NCI_DATA_EXCHANGE, &ndev->flags); + /* Claim completion atomically -- both close and rx_work may race here */ + if (!test_and_clear_bit(NCI_DATA_EXCHANGE, &ndev->flags)) { + kfree_skb(skb); + return; + } if (cb) { /* forward skb to nfc core */ From 344a56d7c8e0f3cbaff0bcb1bcd95a1a1db24b16 Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Wed, 3 Jun 2026 16:13:55 +0200 Subject: [PATCH 1190/1433] nfc: digital: clamp SENSF_RES length to the destination buffer digital_in_recv_sensf_res() memcpy()s resp->len bytes from a remote NFC-F device response into the NFC_SENSF_RES_MAXSIZE-byte target.sensf_res field without an upper-bound check. A nearby malicious NFC-F device can send an oversized SENSF_RES response to overflow the stack-local struct nfc_target. Clamp resp->len to NFC_SENSF_RES_MAXSIZE before the copy. Found by 0sec automated security-research tooling (https://0sec.ai). Fixes: 8c0695e4998d ("NFC Digital: Add NFC-F technology support") Cc: stable@vger.kernel.org Signed-off-by: Doruk Tan Ozturk Reviewed-by: Alexander Lobakin Link: https://patch.msgid.link/20260603141355.68156-1-doruk@0sec.ai Signed-off-by: David Heidelberg --- net/nfc/digital_technology.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/nfc/digital_technology.c b/net/nfc/digital_technology.c index ae63c5eb06fa..ae6487c10a25 100644 --- a/net/nfc/digital_technology.c +++ b/net/nfc/digital_technology.c @@ -778,6 +778,8 @@ static void digital_in_recv_sensf_res(struct nfc_digital_dev *ddev, void *arg, sensf_res = (struct digital_sensf_res *)resp->data; + resp->len = min_t(unsigned int, resp->len, NFC_SENSF_RES_MAXSIZE); + memcpy(target.sensf_res, sensf_res, resp->len); target.sensf_res_len = resp->len; From f4c7f37f0ab990952539dc68d931d65c3657600a Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Tue, 9 Jun 2026 22:25:43 +0200 Subject: [PATCH 1191/1433] nfc: llcp: bound SNL TLV parsing to the skb and add length checks nfc_llcp_recv_snl() walked the SNL TLV list using a u16 offset/length pair derived from skb->len, without bounding reads to the actual skb data. Three problems followed: - For a short frame (skb->len < LLCP_HEADER_SIZE), tlv_len underflowed. - The per-TLV header (type, length) was read without checking that two bytes remained. - A declared TLV length could run past the end of the buffer, and an SDREQ with length == 0 made "service_name_len = length - 1" underflow (size_t), driving an out-of-bounds read in the following strncmp() / nfc_llcp_sock_from_sn(). The SDRES case likewise read tlv[2]/tlv[3] without a length check. A nearby NFC device can reach this without authentication; LLCP link activation happens automatically after NFC-DEP. Walk the TLV list by pointer, bounded by skb_tail_pointer() over the linear skb data, and validate each TLV declared length before use. Add explicit length checks for SDREQ (>= 1) and SDRES (exactly 2). Found by 0sec automated security-research tooling (https://0sec.ai). Fixes: 19cfe5843e86 ("NFC: Initial SNL support") Signed-off-by: Doruk Tan Ozturk Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260609202543.42282-1-doruk@0sec.ai Signed-off-by: David Heidelberg --- net/nfc/llcp_core.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index dc65c719f35f..aed5fe1afef0 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -1286,10 +1286,9 @@ static void nfc_llcp_recv_snl(struct nfc_llcp_local *local, { struct nfc_llcp_sock *llcp_sock; u8 dsap, ssap, type, length, tid, sap; - const u8 *tlv; - u16 tlv_len, offset; + const u8 *tlv, *tlv_end; const char *service_name; - size_t service_name_len; + int service_name_len; struct nfc_llcp_sdp_tlv *sdp; HLIST_HEAD(llc_sdres_list); size_t sdres_tlvs_len; @@ -1305,22 +1304,34 @@ static void nfc_llcp_recv_snl(struct nfc_llcp_local *local, return; } + /* + * Walk the SNL TLV list in the linear part of the skb only, + * bounded by skb_tail_pointer(). Each TLV needs a two-byte + * header (type, length) and its declared length must fit before + * the end; this also keeps the walk safe for very short frames. + */ tlv = &skb->data[LLCP_HEADER_SIZE]; - tlv_len = skb->len - LLCP_HEADER_SIZE; - offset = 0; + tlv_end = skb_tail_pointer(skb); sdres_tlvs_len = 0; - while (offset < tlv_len) { + while (tlv + 2 < tlv_end) { type = tlv[0]; length = tlv[1]; + if (tlv + 2 + length > tlv_end) + break; + switch (type) { case LLCP_TLV_SDREQ: + if (length < 1) + break; + tid = tlv[2]; service_name = (char *) &tlv[3]; service_name_len = length - 1; - pr_debug("Looking for %.16s\n", service_name); + pr_debug("Looking for %.*s\n", service_name_len, + service_name); if (service_name_len == strlen("urn:nfc:sn:sdp") && !strncmp(service_name, "urn:nfc:sn:sdp", @@ -1380,6 +1391,9 @@ static void nfc_llcp_recv_snl(struct nfc_llcp_local *local, break; case LLCP_TLV_SDRES: + if (length != 2) + break; + mutex_lock(&local->sdreq_lock); pr_debug("LLCP_TLV_SDRES: searching tid %d\n", tlv[2]); @@ -1408,7 +1422,6 @@ static void nfc_llcp_recv_snl(struct nfc_llcp_local *local, break; } - offset += length + 2; tlv += length + 2; } From 78b20c8eeacd2e44a2d8a4cb5316d3c521d90911 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Mon, 22 Jun 2026 18:18:02 +0500 Subject: [PATCH 1192/1433] nfc: llcp: fix OOB read and u8 offset wrap in TLV parsers nfc_llcp_parse_gb_tlv() and nfc_llcp_parse_connection_tlv() contain three related bugs in their TLV parsing loops: 1. 'offset' is declared u8 but tlv_array_len is u16. When TLV data advances offset past 255 it silently wraps to zero, causing infinite loops or double-processing of buffer data. 2. Before reading tlv[0] (type) and tlv[1] (length) there is no check that offset+2 <= tlv_array_len. A truncated TLV causes an OOB read of one byte past the buffer end. 3. After reading the length field, the value bytes are accessed without checking offset+2+length <= tlv_array_len. A crafted length=0xFF on a short buffer causes up to 255 bytes of OOB read past the buffer end. Both functions are reachable without authentication via nfc_llcp_set_remote_gb() which feeds remote LLCP general bytes directly into nfc_llcp_parse_gb_tlv() with no additional validation. Fix all three issues by widening offset from u8 to u16 and adding bounds checks for both the TLV header and value field before each access. Fixes: 3df40eb3a2ea ("nfc: constify several pointers to u8, char and sk_buff") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260622131802.239035-1-meatuni001@gmail.com Signed-off-by: David Heidelberg --- net/nfc/llcp_commands.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/net/nfc/llcp_commands.c b/net/nfc/llcp_commands.c index 291f26facbf3..ca89fe967d6a 100644 --- a/net/nfc/llcp_commands.c +++ b/net/nfc/llcp_commands.c @@ -193,7 +193,8 @@ int nfc_llcp_parse_gb_tlv(struct nfc_llcp_local *local, const u8 *tlv_array, u16 tlv_array_len) { const u8 *tlv = tlv_array; - u8 type, length, offset = 0; + u8 type, length; + u16 offset = 0; pr_debug("TLV array length %d\n", tlv_array_len); @@ -201,9 +202,15 @@ int nfc_llcp_parse_gb_tlv(struct nfc_llcp_local *local, return -ENODEV; while (offset < tlv_array_len) { + if (offset + 2 > tlv_array_len) + return -EINVAL; + type = tlv[0]; length = tlv[1]; + if (offset + 2 + length > tlv_array_len) + return -EINVAL; + pr_debug("type 0x%x length %d\n", type, length); switch (type) { @@ -243,7 +250,8 @@ int nfc_llcp_parse_connection_tlv(struct nfc_llcp_sock *sock, const u8 *tlv_array, u16 tlv_array_len) { const u8 *tlv = tlv_array; - u8 type, length, offset = 0; + u8 type, length; + u16 offset = 0; pr_debug("TLV array length %d\n", tlv_array_len); @@ -251,9 +259,15 @@ int nfc_llcp_parse_connection_tlv(struct nfc_llcp_sock *sock, return -ENOTCONN; while (offset < tlv_array_len) { + if (offset + 2 > tlv_array_len) + return -EINVAL; + type = tlv[0]; length = tlv[1]; + if (offset + 2 + length > tlv_array_len) + return -EINVAL; + pr_debug("type 0x%x length %d\n", type, length); switch (type) { From 0428fa2c22e2ba0cff766d3b80d461e149102045 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 12 Jun 2026 12:50:25 -0500 Subject: [PATCH 1193/1433] nfc: nci: add data_len bound checks to activation parameter extractors nci_extract_activation_params_iso_dep() and nci_extract_activation_params_nfc_dep() read an inner length byte from the NCI RF_INTF_ACTIVATED_NTF payload and use it to memcpy() into fixed kernel buffers, but neither function receives the caller-validated activation_params_len. A crafted NCI notification with activation_params_len=1 and an inner length byte of up to 20 (NFC-A) or 50 (NFC-B) causes memcpy() to read that many bytes past the one valid byte in the activation params region -- a slab out-of-bounds read of kernel memory adjacent to the NCI skb. The sibling nci_extract_rf_params_*() family was given equivalent protection by commit 571dcbeb8e63 ("net: nfc: nci: Fix parameter validation for packet data"), but the two activation parameter extractors were not updated at that time. Add a data_len parameter to both functions, guard against an empty region before consuming the inner length byte, decrement the remaining count after consuming it, and clamp the copy length to what is actually available. Update both call sites to pass ntf.activation_params_len, which is already validated against the skb at ntf.c:801. Fixes: e8c0dacd9836 ("NFC: Update names and structs to NCI spec 1.0 d18") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Link: https://patch.msgid.link/20260612-b4-disp-6d52d8b0-v3-1-e26221f8826d@proton.me Signed-off-by: David Heidelberg --- net/nfc/nci/ntf.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/net/nfc/nci/ntf.c b/net/nfc/nci/ntf.c index c96512bb8653..8bc3adbc5b6b 100644 --- a/net/nfc/nci/ntf.c +++ b/net/nfc/nci/ntf.c @@ -525,15 +525,19 @@ static int nci_rf_discover_ntf_packet(struct nci_dev *ndev, static int nci_extract_activation_params_iso_dep(struct nci_dev *ndev, struct nci_rf_intf_activated_ntf *ntf, - const __u8 *data) + const __u8 *data, __u8 data_len) { struct activation_params_nfca_poll_iso_dep *nfca_poll; struct activation_params_nfcb_poll_iso_dep *nfcb_poll; switch (ntf->activation_rf_tech_and_mode) { case NCI_NFC_A_PASSIVE_POLL_MODE: + if (data_len < 1) + return NCI_STATUS_RF_PROTOCOL_ERROR; nfca_poll = &ntf->activation_params.nfca_poll_iso_dep; nfca_poll->rats_res_len = min_t(__u8, *data++, NFC_ATS_MAXSIZE); + data_len--; + nfca_poll->rats_res_len = min_t(__u8, nfca_poll->rats_res_len, data_len); pr_debug("rats_res_len %d\n", nfca_poll->rats_res_len); if (nfca_poll->rats_res_len > 0) { memcpy(nfca_poll->rats_res, @@ -542,8 +546,12 @@ static int nci_extract_activation_params_iso_dep(struct nci_dev *ndev, break; case NCI_NFC_B_PASSIVE_POLL_MODE: + if (data_len < 1) + return NCI_STATUS_RF_PROTOCOL_ERROR; nfcb_poll = &ntf->activation_params.nfcb_poll_iso_dep; nfcb_poll->attrib_res_len = min_t(__u8, *data++, 50); + data_len--; + nfcb_poll->attrib_res_len = min_t(__u8, nfcb_poll->attrib_res_len, data_len); pr_debug("attrib_res_len %d\n", nfcb_poll->attrib_res_len); if (nfcb_poll->attrib_res_len > 0) { memcpy(nfcb_poll->attrib_res, @@ -562,7 +570,7 @@ static int nci_extract_activation_params_iso_dep(struct nci_dev *ndev, static int nci_extract_activation_params_nfc_dep(struct nci_dev *ndev, struct nci_rf_intf_activated_ntf *ntf, - const __u8 *data) + const __u8 *data, __u8 data_len) { struct activation_params_poll_nfc_dep *poll; struct activation_params_listen_nfc_dep *listen; @@ -570,9 +578,13 @@ static int nci_extract_activation_params_nfc_dep(struct nci_dev *ndev, switch (ntf->activation_rf_tech_and_mode) { case NCI_NFC_A_PASSIVE_POLL_MODE: case NCI_NFC_F_PASSIVE_POLL_MODE: + if (data_len < 1) + return NCI_STATUS_RF_PROTOCOL_ERROR; poll = &ntf->activation_params.poll_nfc_dep; poll->atr_res_len = min_t(__u8, *data++, NFC_ATR_RES_MAXSIZE - 2); + data_len--; + poll->atr_res_len = min_t(__u8, poll->atr_res_len, data_len); pr_debug("atr_res_len %d\n", poll->atr_res_len); if (poll->atr_res_len > 0) memcpy(poll->atr_res, data, poll->atr_res_len); @@ -580,9 +592,13 @@ static int nci_extract_activation_params_nfc_dep(struct nci_dev *ndev, case NCI_NFC_A_PASSIVE_LISTEN_MODE: case NCI_NFC_F_PASSIVE_LISTEN_MODE: + if (data_len < 1) + return NCI_STATUS_RF_PROTOCOL_ERROR; listen = &ntf->activation_params.listen_nfc_dep; listen->atr_req_len = min_t(__u8, *data++, NFC_ATR_REQ_MAXSIZE - 2); + data_len--; + listen->atr_req_len = min_t(__u8, listen->atr_req_len, data_len); pr_debug("atr_req_len %d\n", listen->atr_req_len); if (listen->atr_req_len > 0) memcpy(listen->atr_req, data, listen->atr_req_len); @@ -806,12 +822,14 @@ static int nci_rf_intf_activated_ntf_packet(struct nci_dev *ndev, switch (ntf.rf_interface) { case NCI_RF_INTERFACE_ISO_DEP: err = nci_extract_activation_params_iso_dep(ndev, - &ntf, data); + &ntf, data, + ntf.activation_params_len); break; case NCI_RF_INTERFACE_NFC_DEP: err = nci_extract_activation_params_nfc_dep(ndev, - &ntf, data); + &ntf, data, + ntf.activation_params_len); break; case NCI_RF_INTERFACE_FRAME: From ac200079db50af81e6b04d058b33ec92901d8edd Mon Sep 17 00:00:00 2001 From: Samuel Page Date: Mon, 22 Jun 2026 16:52:43 +0200 Subject: [PATCH 1194/1433] nfc: nci: fix out-of-bounds write in nci_target_auto_activated() nci_target_auto_activated() appends a target to the fixed-size array ndev->targets[NCI_MAX_DISCOVERED_TARGETS] and increments ndev->n_targets without first checking the array is full; unlike its sibling nci_add_new_target(), which bails out when n_targets already equals NCI_MAX_DISCOVERED_TARGETS. ndev->n_targets is only cleared by nci_clear_target_list(), so an NFCC that repeatedly re-runs discovery (RF_DISCOVER_RSP, which re-enters NCI_DISCOVERY without clearing the target list) and reports an auto-activated target (RF_INTF_ACTIVATED_NTF) drives n_targets past the limit. The append then writes a struct nfc_target past the end of the array (a slab out-of-bounds write), and nfc_targets_found() goes on to walk the array with the inflated count: BUG: KASAN: slab-out-of-bounds in nci_add_new_protocol+0x94/0x2ac [nci] Write of size 2 at addr ffff0000c7299a18 by task kworker/u8:0/12 Workqueue: nfc0_nci_rx_wq nci_rx_work [nci] Call trace: nci_add_new_protocol+0x94/0x2ac [nci] nci_ntf_packet+0xddc/0x11a0 [nci] nci_rx_work+0x15c/0x1e0 [nci] process_one_work+0x2dc/0x500 worker_thread+0x240/0x460 kthread+0x1c0/0x1d0 ret_from_fork+0x10/0x20 The buggy address belongs to the cache kmalloc-2k of size 2048 The buggy address is located 1024 bytes to the right of allocated 1560-byte region [ffff0000c7299000, ffff0000c7299618) Guard nci_target_auto_activated() with the same check used by nci_add_new_target(). Fixes: 019c4fbaa790 ("NFC: Add NCI multiple targets support") Cc: stable@vger.kernel.org Assisted-by: Bynario AI Signed-off-by: Samuel Page Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260622145243.3167276-1-sam@bynar.io Signed-off-by: David Heidelberg --- net/nfc/nci/ntf.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/nfc/nci/ntf.c b/net/nfc/nci/ntf.c index 8bc3adbc5b6b..87f76a29f0ec 100644 --- a/net/nfc/nci/ntf.c +++ b/net/nfc/nci/ntf.c @@ -619,6 +619,12 @@ static void nci_target_auto_activated(struct nci_dev *ndev, struct nfc_target *target; int rc; + /* This is a new target, check if we've enough room */ + if (ndev->n_targets == NCI_MAX_DISCOVERED_TARGETS) { + pr_debug("not enough room, ignoring new target...\n"); + return; + } + target = &ndev->targets[ndev->n_targets]; rc = nci_add_new_protocol(ndev, target, ntf->rf_protocol, From 7ad21dcfeb5181af0c3ee2608808c0c0a5283aa1 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Tue, 16 Jun 2026 23:33:35 -0500 Subject: [PATCH 1195/1433] nfc: fdp: bound the device-reported read length and fix an skb leak fdp_nci_i2c_read() takes the next packet length from two device-supplied bytes and never validates it. The value is a u16 used as the i2c_master_recv() count into a 261-byte on-stack buffer: a malicious, counterfeit or malfunctioning controller (or an i2c bus interposer) can drive it far past the buffer for a stack out-of-bounds write that clobbers the canary and return address, or below the minimum frame size (directly, or by truncating the computed sum) so the header/LRC strip and the next length read run past a short receive. Reject a length outside [FDP_NCI_I2C_MIN_PAYLOAD, FDP_NCI_I2C_MAX_PAYLOAD], as a corrupted packet already is, and force resynchronization. The same loop allocates one data skb per iteration and assumes a length packet followed by a data packet; a device that sends two data packets in one call leaks the first skb when the second allocation overwrites it. Free a previously allocated skb before allocating the next. Fixes: a06347c04c13 ("NFC: Add Intel Fields Peak NFC solution driver") Cc: stable@vger.kernel.org Suggested-by: Simon Horman Signed-off-by: Bryam Vargas Link: https://patch.msgid.link/20260616-b4-disp-b1f8ab4c-v2-1-2d1fe5955325@proton.me Signed-off-by: David Heidelberg --- drivers/nfc/fdp/i2c.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/drivers/nfc/fdp/i2c.c b/drivers/nfc/fdp/i2c.c index c1896a1d978c..f292e7f37456 100644 --- a/drivers/nfc/fdp/i2c.c +++ b/drivers/nfc/fdp/i2c.c @@ -166,9 +166,36 @@ static int fdp_nci_i2c_read(struct fdp_i2c_phy *phy, struct sk_buff **skb) /* Packet that contains a length */ if (tmp[0] == 0 && tmp[1] == 0) { phy->next_read_size = (tmp[2] << 8) + tmp[3] + 3; + + /* + * next_read_size is taken from the device and is used + * as the i2c_master_recv() count for the next packet + * and as the data skb size. A value above the receive + * buffer overflows tmp[]; one below the minimum frame + * size runs the header/LRC strip and the length-field + * read past a short receive. Either way the packet is + * corrupt: drop it and force resynchronization. + */ + if (phy->next_read_size < FDP_NCI_I2C_MIN_PAYLOAD || + phy->next_read_size > FDP_NCI_I2C_MAX_PAYLOAD) { + dev_dbg(&client->dev, "%s: corrupted packet\n", + __func__); + phy->next_read_size = FDP_NCI_I2C_MIN_PAYLOAD; + goto flush; + } } else { phy->next_read_size = FDP_NCI_I2C_MIN_PAYLOAD; + /* + * Only one data packet is delivered per call; if the + * device sends another, do not overwrite and leak the + * skb allocated for the previous one. + */ + if (*skb) { + kfree_skb(*skb); + *skb = NULL; + } + *skb = alloc_skb(len, GFP_KERNEL); if (*skb == NULL) { r = -ENOMEM; From 8cbe06c1e699c0a165dae5093a2550e65f914818 Mon Sep 17 00:00:00 2001 From: Samuel Page Date: Fri, 26 Jun 2026 10:03:01 +0100 Subject: [PATCH 1196/1433] nfc: nci: fix uninit-value in the RF discover/activated NTF handlers nci_rf_discover_ntf_packet() and nci_rf_intf_activated_ntf_packet() each parse a notification into an on-stack struct (nci_rf_discover_ntf / nci_rf_intf_activated_ntf) that is not initialised. The RF technology-specific parameters are only extracted when rf_tech_specific_params_len is non-zero, so a notification that reports a zero length leaves the rf_tech_specific_params union uninitialised - and both handlers then pass it to nci_add_new_protocol(), which reads it: - discover: nci_add_new_target() -> nci_add_new_protocol(); - activated: nci_target_auto_activated() -> nci_add_new_protocol(). nci_add_new_protocol() uses nfca_poll->nfcid1_len as both a branch condition and a memcpy() length and copies nfcid1/sens_res/sel_res into ndev->targets, which is later exposed to user space via NFC_CMD_GET_TARGET. BUG: KMSAN: uninit-value in nci_add_new_protocol+0x624/0x6c0 nci_add_new_protocol+0x624/0x6c0 nci_ntf_packet+0x25b2/0x3c30 nci_rx_work+0x318/0x5d0 process_scheduled_works+0x84b/0x17a0 worker_thread+0xc10/0x11b0 kthread+0x376/0x500 Local variable ntf.i created at: nci_ntf_packet+0xbc2/0x3c30 Zero-initialise both on-stack notifications so the union reads back as zero when no technology-specific parameters are present. Fixes: 019c4fbaa790 ("NFC: Add NCI multiple targets support") Fixes: e8c0dacd9836 ("NFC: Update names and structs to NCI spec 1.0 d18") Link: https://lore.kernel.org/netdev/20260623172109.1105965-2-horms@kernel.org/ Cc: stable@vger.kernel.org Assisted-by: Bynario AI Signed-off-by: Samuel Page Link: https://patch.msgid.link/20260626090301.2139500-1-sam@bynar.io Signed-off-by: David Heidelberg --- net/nfc/nci/ntf.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/nfc/nci/ntf.c b/net/nfc/nci/ntf.c index 87f76a29f0ec..f5c9a8ab7ec1 100644 --- a/net/nfc/nci/ntf.c +++ b/net/nfc/nci/ntf.c @@ -440,7 +440,7 @@ void nci_clear_target_list(struct nci_dev *ndev) static int nci_rf_discover_ntf_packet(struct nci_dev *ndev, const struct sk_buff *skb) { - struct nci_rf_discover_ntf ntf; + struct nci_rf_discover_ntf ntf = {}; const __u8 *data; bool add_target = true; @@ -710,7 +710,7 @@ static int nci_rf_intf_activated_ntf_packet(struct nci_dev *ndev, const struct sk_buff *skb) { struct nci_conn_info *conn_info; - struct nci_rf_intf_activated_ntf ntf; + struct nci_rf_intf_activated_ntf ntf = {}; const __u8 *data; int err = NCI_STATUS_OK; From 47792358a624ea066455ef86b744159928cd7716 Mon Sep 17 00:00:00 2001 From: Yinhao Hu Date: Fri, 26 Jun 2026 00:34:34 -0700 Subject: [PATCH 1197/1433] nfc: pn533: hold a reference to the request skb during send_frame __pn533_send_async() publishes the command and then calls dev->phy_ops->send_frame(). Once dev->cmd is set, an incoming frame can be matched to this command: the I2C threaded IRQ runs pn533_recv_frame(), which queues cmd_complete_work, and pn533_send_async_complete() frees cmd->req with consume_skb(). On the I2C transport, pn533_i2c_send_frame() still dereferences the same skb after i2c_master_send() returns, so a completion that races the send can free the skb while the transport is still using it. The request skb is owned by the command object and may be freed by command completion at any time after dev->cmd is published, so the transport send path must not assume it stays alive. Hold a temporary reference to the request skb across the send_frame() call so the transport always sees a live skb even if completion races the send. Add a pn533_send_cmd_frame() helper and use it from all three send paths. Fixes: 9815c7cf22da ("NFC: pn533: Separate physical layer from the core implementation") Signed-off-by: Yinhao Hu Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260626073434.3977525-1-dddddd@hust.edu.cn Signed-off-by: David Heidelberg --- drivers/nfc/pn533/pn533.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/nfc/pn533/pn533.c b/drivers/nfc/pn533/pn533.c index d7bdbc82e2ba..55bbfa32d695 100644 --- a/drivers/nfc/pn533/pn533.c +++ b/drivers/nfc/pn533/pn533.c @@ -434,6 +434,18 @@ static int pn533_send_async_complete(struct pn533 *dev) return rc; } +static int pn533_send_cmd_frame(struct pn533 *dev, struct pn533_cmd *cmd) +{ + struct sk_buff *req = cmd->req; + int rc; + + skb_get(req); + dev->cmd = cmd; + rc = dev->phy_ops->send_frame(dev, req); + dev_kfree_skb(req); + return rc; +} + static int __pn533_send_async(struct pn533 *dev, u8 cmd_code, struct sk_buff *req, pn533_send_async_complete_t complete_cb, @@ -458,8 +470,7 @@ static int __pn533_send_async(struct pn533 *dev, u8 cmd_code, mutex_lock(&dev->cmd_lock); if (!dev->cmd_pending) { - dev->cmd = cmd; - rc = dev->phy_ops->send_frame(dev, req); + rc = pn533_send_cmd_frame(dev, cmd); if (rc) { dev->cmd = NULL; goto error; @@ -529,8 +540,7 @@ static int pn533_send_cmd_direct_async(struct pn533 *dev, u8 cmd_code, pn533_build_cmd_frame(dev, cmd_code, req); - dev->cmd = cmd; - rc = dev->phy_ops->send_frame(dev, req); + rc = pn533_send_cmd_frame(dev, cmd); if (rc < 0) { dev->cmd = NULL; kfree(cmd); @@ -569,8 +579,7 @@ static void pn533_wq_cmd(struct work_struct *work) mutex_unlock(&dev->cmd_lock); - dev->cmd = cmd; - rc = dev->phy_ops->send_frame(dev, cmd->req); + rc = pn533_send_cmd_frame(dev, cmd); if (rc < 0) { dev->cmd = NULL; dev_kfree_skb(cmd->req); From 1c7dd70c0adfa58fd66b5cbd03efb747ad6d8d8d Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Fri, 10 Jul 2026 14:12:54 +0800 Subject: [PATCH 1198/1433] nfc: digital: Do not dump a NULL response in command completion digital_wq_cmd_complete() dumps the response data whenever cmd->resp is not an error pointer. However, a driver can legitimately complete a command with no response skb at all. digital_tg_send_psl_res() is the only caller that passes timeout=0, meaning no response is expected once the command has been transmitted. On that path trf7970a completes the command with trf->rx_skb = ERR_PTR(0); which evaluates to NULL. IS_ERR(NULL) is false, so the NULL response passes the !IS_ERR() check and cmd->resp->data and cmd->resp->len are dereferenced whenever the debug print site is enabled. The driver guards its own dump with "trf->rx_skb && !IS_ERR(trf->rx_skb)"; the digital layer is missing the NULL half of that test. Use IS_ERR_OR_NULL() so that NULL responses are skipped as well. The callback on that path, digital_tg_send_psl_res_complete(), never dereferences resp and dev_kfree_skb() accepts NULL, so only the debug dump needs fixing. Fixes: 59ee2361c924 ("NFC Digital: Implement driver commands mechanism") Signed-off-by: Linmao Li Reviewed-by: Przemek Kitszel Link: https://patch.msgid.link/20260710061254.80975-1-lilinmao@kylinos.cn Signed-off-by: David Heidelberg --- net/nfc/digital_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/nfc/digital_core.c b/net/nfc/digital_core.c index 7cb1e6aaae90..18236221d898 100644 --- a/net/nfc/digital_core.c +++ b/net/nfc/digital_core.c @@ -127,7 +127,7 @@ static void digital_wq_cmd_complete(struct work_struct *work) mutex_unlock(&ddev->cmd_lock); - if (!IS_ERR(cmd->resp)) + if (!IS_ERR_OR_NULL(cmd->resp)) print_hex_dump_debug("DIGITAL RX: ", DUMP_PREFIX_NONE, 16, 1, cmd->resp->data, cmd->resp->len, false); From 95674f506c6376d6722a23144c9acd26609771ed Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Tue, 14 Jul 2026 18:46:31 +0200 Subject: [PATCH 1199/1433] nfc: llcp: reject PDUs shorter than the LLCP header Every LLCP PDU begins with a two-byte header (DSAP/SSAP + PTYPE), but the receive path never checked that a frame is at least LLCP_HEADER_SIZE bytes before parsing it. nfc_llcp_rx_skb() reads the header via nfc_llcp_ptype()/nfc_llcp_dsap()/ nfc_llcp_ssap(), which dereference pdu->data[0] and pdu->data[1], and a CONNECT or CC PDU then computes tlv_array_len = skb->len - LLCP_HEADER_SIZE; as a size_t and hands it to the TLV walk. When the frame is shorter than the header the subtraction wraps to a huge value and the walk runs far past the buffer, an out-of-bounds read. A nearby NFC device can reach this without authentication; LLCP link activation happens automatically after NFC-DEP. Guard the common receive choke point __nfc_llcp_recv(), shared by both the target (nfc_llcp_data_received()) and initiator (nfc_llcp_recv()) paths, so a short skb is dropped before the rx_work worker parses it. Use pskb_may_pull() rather than a skb->len test so the two header bytes are guaranteed to sit in the skb linear area even for a non-linear skb, matching how the sibling NCI and HCI receive paths validate their headers. Reproduced with a KFENCE out-of-bounds read via /dev/virtual_nci on linux-next. Found by 0sec automated security-research tooling (https://0sec.ai). Fixes: d646960f7986 ("NFC: Initial LLCP support") Cc: stable@vger.kernel.org Suggested-by: David Laight Signed-off-by: Doruk Tan Ozturk Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260714164631.75068-1-doruk@0sec.ai Signed-off-by: David Heidelberg --- net/nfc/llcp_core.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index aed5fe1afef0..e3b2627cb089 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -1565,6 +1565,11 @@ static void nfc_llcp_rx_work(struct work_struct *work) static void __nfc_llcp_recv(struct nfc_llcp_local *local, struct sk_buff *skb) { + if (!pskb_may_pull(skb, LLCP_HEADER_SIZE)) { + kfree_skb(skb); + return; + } + local->rx_pending = skb; timer_delete(&local->link_timer); schedule_work(&local->rx_work); From 55c68ac93e7dacc0f5f608b9c39dd4ff48cf28e8 Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Thu, 9 Jul 2026 15:12:29 +0200 Subject: [PATCH 1200/1433] nfc: llcp: bound the connect_sn TLV walk to the skb Commit 27256cdb290e ("nfc: llcp: bound SNL TLV parsing to the skb and add length checks") fixed the unbounded TLV walk in nfc_llcp_recv_snl(), and commit d8bd2dedbde5 ("nfc: llcp: fix OOB read and u8 offset wrap in TLV parsers") subsequently bounded nfc_llcp_parse_gb_tlv() and nfc_llcp_parse_connection_tlv(). One sibling parser sharing the same pattern remains unbounded: nfc_llcp_connect_sn(). nfc_llcp_connect_sn() walks a TLV list, reading a two-byte header (type, length) followed by length bytes of value, without checking that the two header bytes or the declared length stay within the buffer. It returns a pointer to a service name of up to 255 bytes that may point past the end of the skb; it is subsequently consumed by memcmp() in nfc_llcp_sock_from_sn(). In addition tlv_array_len was computed as "skb->len - LLCP_HEADER_SIZE" in size_t, so a CONNECT/CC frame shorter than the LLCP header underflows to a huge length and the walk runs far past the buffer. nfc_llcp_connect_sn() is reachable from nfc_llcp_recv_connect() and nfc_llcp_recv_cc(), i.e. from received CONNECT and CC PDUs. A nearby NFC device can reach this without authentication; LLCP link activation happens automatically after NFC-DEP, and the nfc_llcp_rx_skb() dispatcher applies no minimum-length guard. Walk the TLV list by pointer, bounded by skb_tail_pointer(skb), and validate each declared length before use, matching the approach already used for nfc_llcp_recv_snl(). Starting the walk at &skb->data[LLCP_HEADER_SIZE] against the tail pointer also removes the size_t underflow for short frames. Found by 0sec automated security-research tooling (https://0sec.ai). Fixes: d646960f7986 ("NFC: Initial LLCP support") Cc: stable@vger.kernel.org Assisted-by: 0sec:claude-opus-4-8 Signed-off-by: Doruk Tan Ozturk Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260709131229.44477-1-doruk@0sec.ai Signed-off-by: David Heidelberg --- net/nfc/llcp_core.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index e3b2627cb089..cac1b5487064 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -849,13 +849,16 @@ static struct nfc_llcp_sock *nfc_llcp_sock_get_sn(struct nfc_llcp_local *local, static const u8 *nfc_llcp_connect_sn(const struct sk_buff *skb, size_t *sn_len) { u8 type, length; - const u8 *tlv = &skb->data[2]; - size_t tlv_array_len = skb->len - LLCP_HEADER_SIZE, offset = 0; + const u8 *tlv = &skb->data[LLCP_HEADER_SIZE]; + const u8 *tlv_end = skb_tail_pointer(skb); - while (offset < tlv_array_len) { + while (tlv + 2 < tlv_end) { type = tlv[0]; length = tlv[1]; + if (tlv + 2 + length > tlv_end) + break; + pr_debug("type 0x%x length %d\n", type, length); if (type == LLCP_TLV_SN) { @@ -863,7 +866,6 @@ static const u8 *nfc_llcp_connect_sn(const struct sk_buff *skb, size_t *sn_len) return &tlv[2]; } - offset += length + 2; tlv += length + 2; } From 5cdcca5d62a66eda6b774110a44cba67bc1a8d1d Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Sat, 11 Jul 2026 09:13:01 +0200 Subject: [PATCH 1201/1433] nfc: st21nfca: validate ATR_REQ length against the received frame st21nfca_tm_recv_atr_req() checks that the received ATR_REQ frame is at least ST21NFCA_ATR_REQ_MIN_SIZE and that the self-declared atr_req->length is at least sizeof(struct st21nfca_atr_req), but never checks that atr_req->length does not exceed the actual received length (skb->len). st21nfca_tm_send_atr_res() then trusts the declared length: gb_len = atr_req->length - sizeof(struct st21nfca_atr_req); ... memcpy(atr_res->gbi, atr_req->gbi, gb_len); so an RF peer that sends a short frame but sets atr_req->length larger than the frame makes gb_len exceed the general bytes actually present, and the memcpy reads out of bounds past the received skb. Those bytes are placed in the ATR_RES and sent back to the peer (kernel-memory disclosure to a proximity attacker); a larger declared length is an out-of-bounds read (DoS). Reject frames whose declared length exceeds the received length. The adjacent nfc_tm_activated() path in the same function already derives its general-bytes length from skb->len rather than the declared field. Found by 0sec (https://0sec.ai) using automated source analysis; the missing bound is evident from source. Compile-tested. Fixes: 1892bf844ea0 ("NFC: st21nfca: Adding P2P support to st21nfca in Initiator & Target mode") Cc: stable@vger.kernel.org Assisted-by: 0sec:claude-opus-4-8 Signed-off-by: Doruk Tan Ozturk Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260711071301.58071-1-doruk@0sec.ai Signed-off-by: David Heidelberg --- drivers/nfc/st21nfca/dep.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/nfc/st21nfca/dep.c b/drivers/nfc/st21nfca/dep.c index 3425b68f0ddc..a5fab4fd5129 100644 --- a/drivers/nfc/st21nfca/dep.c +++ b/drivers/nfc/st21nfca/dep.c @@ -205,6 +205,9 @@ static int st21nfca_tm_recv_atr_req(struct nfc_hci_dev *hdev, if (atr_req->length < sizeof(struct st21nfca_atr_req)) return -EPROTO; + if (atr_req->length > skb->len) + return -EPROTO; + r = st21nfca_tm_send_atr_res(hdev, atr_req); if (r) return r; From 5718fc62198c38c2de5316020a90506f9e75e0bb Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Mon, 20 Jul 2026 10:14:44 +0800 Subject: [PATCH 1202/1433] nfc: pn533: purge fragmented skbs during cleanup pn53x_common_clean() purges resp_q before freeing the common PN533 state, but it leaves fragment_skb untouched. The fragmentation helpers queue transmit fragments there while sending large initiator or target-mode frames, and those skbs remain owned by the driver until they are sent or discarded. If the device is removed while fragments are still queued, the common cleanup path frees the PN533 state without releasing the queued fragment skbs, leaking them. Purge fragment_skb during cleanup alongside resp_q. Fixes: 963a82e07d4e ("NFC: pn533: Split large Tx frames in chunks") Cc: stable@vger.kernel.org Signed-off-by: Xu Rao Link: https://patch.msgid.link/2D896607CAE4408E+20260720021444.3362044-1-raoxu@uniontech.com Signed-off-by: David Heidelberg --- drivers/nfc/pn533/pn533.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/nfc/pn533/pn533.c b/drivers/nfc/pn533/pn533.c index 55bbfa32d695..76081a99b450 100644 --- a/drivers/nfc/pn533/pn533.c +++ b/drivers/nfc/pn533/pn533.c @@ -2808,6 +2808,7 @@ void pn53x_common_clean(struct pn533 *priv) destroy_workqueue(priv->wq); skb_queue_purge(&priv->resp_q); + skb_queue_purge(&priv->fragment_skb); list_for_each_entry_safe(cmd, n, &priv->cmd_queue, queue) { list_del(&cmd->queue); From d56575a2595ee1f597f39e8a1cfb67ed3501678d Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Wed, 27 May 2026 13:26:25 +0800 Subject: [PATCH 1203/1433] nfc: nci: fix use of uninitialized memory in CORE_INIT_RSP parsing nci_core_init_rsp_packet_v1() and nci_core_init_rsp_packet_v2() parse the CORE_INIT_RSP packet without validating that the skb contains enough data. A malformed response (e.g. injected via virtual_ncidev) can declare a large num_supported_rf_interfaces while providing insufficient data, causing reads of uninitialized slab memory. This is later used in nci_init_complete_req(), triggering a KMSAN uninit-value warning. Add skb length checks before accessing packet fields: - Validate the skb has at least 1 byte for the status field. - Validate the skb can hold the fixed-size header before parsing. - In v2, bounds-check each variable-length rf_interface entry and its extension parameters within the parsing loop. - In v1, verify the skb is large enough for both the variable-length rf_interfaces array and the trailing rsp_2 structure. Reported-by: syzbot+46ca2592193f2fb3debc@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=46ca2592193f2fb3debc Fixes: bcd684aace34 ("net/nfc/nci: Support NCI 2.x initial sequence") Signed-off-by: Yun Zhou Link: https://patch.msgid.link/20260527052625.3309581-1-yun.zhou@windriver.com Signed-off-by: David Heidelberg --- net/nfc/nci/rsp.c | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/net/nfc/nci/rsp.c b/net/nfc/nci/rsp.c index 9eeb862825c5..85c3ff2b78cd 100644 --- a/net/nfc/nci/rsp.c +++ b/net/nfc/nci/rsp.c @@ -50,11 +50,27 @@ static u8 nci_core_init_rsp_packet_v1(struct nci_dev *ndev, const struct nci_core_init_rsp_1 *rsp_1 = (void *)skb->data; const struct nci_core_init_rsp_2 *rsp_2; + /* Ensure that the status field can be accessed. */ + if (skb_headlen(skb) < 1) + return NCI_STATUS_SYNTAX_ERROR; + pr_debug("status 0x%x\n", rsp_1->status); if (rsp_1->status != NCI_STATUS_OK) return rsp_1->status; + /* Success response must contain the full fixed-size header */ + if (skb_headlen(skb) < sizeof(*rsp_1)) + return NCI_STATUS_SYNTAX_ERROR; + + /* Ensure the variable-length rf_interfaces array and trailing + * rsp_2 structure are fully contained within the skb. + */ + if (skb_headlen(skb) < sizeof(*rsp_1) + + rsp_1->num_supported_rf_interfaces + + sizeof(*rsp_2)) + return NCI_STATUS_SYNTAX_ERROR; + ndev->nfcc_features = __le32_to_cpu(rsp_1->nfcc_features); ndev->num_supported_rf_interfaces = rsp_1->num_supported_rf_interfaces; @@ -87,15 +103,25 @@ static u8 nci_core_init_rsp_packet_v2(struct nci_dev *ndev, const struct sk_buff *skb) { const struct nci_core_init_rsp_nci_ver2 *rsp = (void *)skb->data; - const u8 *supported_rf_interface = rsp->supported_rf_interfaces; + const u8 *supported_rf_interface; u8 rf_interface_idx = 0; u8 rf_extension_cnt = 0; + /* Ensure that the status field can be accessed. */ + if (skb_headlen(skb) < 1) + return NCI_STATUS_SYNTAX_ERROR; + pr_debug("status %x\n", rsp->status); if (rsp->status != NCI_STATUS_OK) return rsp->status; + /* Success response must contain the full fixed-size header */ + if (skb_headlen(skb) < sizeof(*rsp)) + return NCI_STATUS_SYNTAX_ERROR; + + supported_rf_interface = rsp->supported_rf_interfaces; + ndev->nfcc_features = __le32_to_cpu(rsp->nfcc_features); ndev->num_supported_rf_interfaces = rsp->num_supported_rf_interfaces; @@ -104,13 +130,22 @@ static u8 nci_core_init_rsp_packet_v2(struct nci_dev *ndev, NCI_MAX_SUPPORTED_RF_INTERFACES); while (rf_interface_idx < ndev->num_supported_rf_interfaces) { - ndev->supported_rf_interfaces[rf_interface_idx++] = *supported_rf_interface++; + /* Each entry: [rf_interface_type (1B)] [ext_count (1B)] [ext...] */ + if (supported_rf_interface + 2 > skb_tail_pointer(skb)) + break; + ndev->supported_rf_interfaces[rf_interface_idx] = *supported_rf_interface++; - /* skip rf extension parameters */ rf_extension_cnt = *supported_rf_interface++; + if (supported_rf_interface + rf_extension_cnt > skb_tail_pointer(skb)) + break; + + /* Only count the entry after full validation */ + rf_interface_idx++; supported_rf_interface += rf_extension_cnt; } + ndev->num_supported_rf_interfaces = rf_interface_idx; + ndev->max_logical_connections = rsp->max_logical_connections; ndev->max_routing_table_size = __le16_to_cpu(rsp->max_routing_table_size); From 2e65bafdfd3a8bba972b3d17b6a57816557530fc Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Tue, 21 Jul 2026 10:35:18 +0800 Subject: [PATCH 1204/1433] nfc: nci: free destination parameters when closing a connection When a connection is closed, nci_core_conn_close_rsp_packet() frees conn_info but not conn_info->dest_params, which is a separate devm allocation. Each connect/close cycle leaks one dest_params until the NFC device is removed. Free dest_params along with conn_info. Fixes: 9b8d1a4cf2aa ("nfc: nci: Add an additional parameter to identify a connection id") Cc: stable@vger.kernel.org Signed-off-by: Linmao Li Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260721023518.1697625-1-lilinmao@kylinos.cn Signed-off-by: David Heidelberg --- net/nfc/nci/rsp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/nfc/nci/rsp.c b/net/nfc/nci/rsp.c index 85c3ff2b78cd..b0ab4f5acbce 100644 --- a/net/nfc/nci/rsp.c +++ b/net/nfc/nci/rsp.c @@ -371,6 +371,7 @@ static void nci_core_conn_close_rsp_packet(struct nci_dev *ndev, list_del(&conn_info->list); if (conn_info == ndev->rf_conn_info) ndev->rf_conn_info = NULL; + devm_kfree(&ndev->nfc_dev->dev, conn_info->dest_params); devm_kfree(&ndev->nfc_dev->dev, conn_info); } } From 25519469972ef57c3edb1805dabd6c5612b90211 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Thu, 23 Jul 2026 10:37:20 +0800 Subject: [PATCH 1205/1433] nfc: microread: validate target discovery payload lengths microread_target_discovered() parses target discovery payloads from skb->data according to the HCI gate. The fixed field offsets and UID copies were checked only against the destination nfc_target buffers, not against the actual skb length. Validate that each gate-specific payload contains the fixed fields and UID bytes before reading or copying them. Fixes: cfad1ba87150 ("NFC: Initial support for Inside Secure microread") Cc: stable@vger.kernel.org Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260723103508.1-microread-v2-pengpeng@iscas.ac.cn Signed-off-by: David Heidelberg --- drivers/nfc/microread/microread.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/drivers/nfc/microread/microread.c b/drivers/nfc/microread/microread.c index 4149c5d735bd..dfa2490db545 100644 --- a/drivers/nfc/microread/microread.c +++ b/drivers/nfc/microread/microread.c @@ -483,13 +483,19 @@ static void microread_target_discovered(struct nfc_hci_dev *hdev, u8 gate, switch (gate) { case MICROREAD_GATE_ID_MREAD_ISO_A: + if (skb->len <= MICROREAD_EMCF_A_LEN) { + r = -EINVAL; + goto exit_free; + } + targets->supported_protocols = nfc_hci_sak_to_protocol(skb->data[MICROREAD_EMCF_A_SAK]); targets->sens_res = be16_to_cpu(*(u16 *)&skb->data[MICROREAD_EMCF_A_ATQA]); targets->sel_res = skb->data[MICROREAD_EMCF_A_SAK]; targets->nfcid1_len = skb->data[MICROREAD_EMCF_A_LEN]; - if (targets->nfcid1_len > sizeof(targets->nfcid1)) { + if (targets->nfcid1_len > sizeof(targets->nfcid1) || + targets->nfcid1_len > skb->len - MICROREAD_EMCF_A_UID) { r = -EINVAL; goto exit_free; } @@ -497,13 +503,19 @@ static void microread_target_discovered(struct nfc_hci_dev *hdev, u8 gate, targets->nfcid1_len); break; case MICROREAD_GATE_ID_MREAD_ISO_A_3: + if (skb->len <= MICROREAD_EMCF_A3_LEN) { + r = -EINVAL; + goto exit_free; + } + targets->supported_protocols = nfc_hci_sak_to_protocol(skb->data[MICROREAD_EMCF_A3_SAK]); targets->sens_res = be16_to_cpu(*(u16 *)&skb->data[MICROREAD_EMCF_A3_ATQA]); targets->sel_res = skb->data[MICROREAD_EMCF_A3_SAK]; targets->nfcid1_len = skb->data[MICROREAD_EMCF_A3_LEN]; - if (targets->nfcid1_len > sizeof(targets->nfcid1)) { + if (targets->nfcid1_len > sizeof(targets->nfcid1) || + targets->nfcid1_len > skb->len - MICROREAD_EMCF_A3_UID) { r = -EINVAL; goto exit_free; } @@ -511,11 +523,21 @@ static void microread_target_discovered(struct nfc_hci_dev *hdev, u8 gate, targets->nfcid1_len); break; case MICROREAD_GATE_ID_MREAD_ISO_B: + if (skb->len < MICROREAD_EMCF_B_UID + 4) { + r = -EINVAL; + goto exit_free; + } + targets->supported_protocols = NFC_PROTO_ISO14443_B_MASK; memcpy(targets->nfcid1, &skb->data[MICROREAD_EMCF_B_UID], 4); targets->nfcid1_len = 4; break; case MICROREAD_GATE_ID_MREAD_NFC_T1: + if (skb->len < MICROREAD_EMCF_T1_UID + 4) { + r = -EINVAL; + goto exit_free; + } + targets->supported_protocols = NFC_PROTO_JEWEL_MASK; targets->sens_res = le16_to_cpu(*(u16 *)&skb->data[MICROREAD_EMCF_T1_ATQA]); @@ -523,6 +545,11 @@ static void microread_target_discovered(struct nfc_hci_dev *hdev, u8 gate, targets->nfcid1_len = 4; break; case MICROREAD_GATE_ID_MREAD_NFC_T3: + if (skb->len < MICROREAD_EMCF_T3_UID + 8) { + r = -EINVAL; + goto exit_free; + } + targets->supported_protocols = NFC_PROTO_FELICA_MASK; memcpy(targets->nfcid1, &skb->data[MICROREAD_EMCF_T3_UID], 8); targets->nfcid1_len = 8; From 347c1481c122aa78af7a41500af9ad04d11e57b1 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 7 Jun 2026 22:00:34 -0700 Subject: [PATCH 1206/1433] nfc: pn533: fix memcpy overflow warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit error: call to ‘__read_overflow2_field’ declared with attribute warning: detected read beyond size of field (2nd parameter); maybe use struct_group()? [-Werror=attribute-warning] As suggested, add a struct_group and memcpy that. Also replace 9 with sizeof for clarify. Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260608050034.5679-1-rosenp@gmail.com Signed-off-by: David Heidelberg --- drivers/nfc/pn533/pn533.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/nfc/pn533/pn533.c b/drivers/nfc/pn533/pn533.c index d7bdbc82e2ba..4e721a8dfd8b 100644 --- a/drivers/nfc/pn533/pn533.c +++ b/drivers/nfc/pn533/pn533.c @@ -740,8 +740,10 @@ static int pn533_target_found_type_a(struct nfc_target *nfc_tgt, u8 *tgt_data, struct pn533_target_felica { u8 pol_res; - u8 opcode; - u8 nfcid2[NFC_NFCID2_MAXSIZE]; + struct_group(sensf_res, + u8 opcode; + u8 nfcid2[NFC_NFCID2_MAXSIZE]; + ); u8 pad[8]; /* optional */ u8 syst_code[]; @@ -778,8 +780,8 @@ static int pn533_target_found_felica(struct nfc_target *nfc_tgt, u8 *tgt_data, else nfc_tgt->supported_protocols = NFC_PROTO_FELICA_MASK; - memcpy(nfc_tgt->sensf_res, &tgt_felica->opcode, 9); - nfc_tgt->sensf_res_len = 9; + memcpy(nfc_tgt->sensf_res, &tgt_felica->sensf_res, sizeof(tgt_felica->sensf_res)); + nfc_tgt->sensf_res_len = sizeof(tgt_felica->sensf_res); memcpy(nfc_tgt->nfcid2, tgt_felica->nfcid2, NFC_NFCID2_MAXSIZE); nfc_tgt->nfcid2_len = NFC_NFCID2_MAXSIZE; From 9efd35acb385d8cba179ac833430596f1674a2dc Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Mon, 6 Jul 2026 15:57:42 +0800 Subject: [PATCH 1207/1433] nfc: trf7970a: Use NULL when no response is expected When a command's timeout is zero, no response is expected, and the TX interrupt handler completes the command by passing ERR_PTR(0) to the digital callback. ERR_PTR(0) evaluates to NULL, so no errno is encoded here. Use NULL directly to avoid suggesting that this is an error-pointer path. Signed-off-by: Linmao Li Link: https://patch.msgid.link/20260706075743.564658-1-lilinmao@kylinos.cn Signed-off-by: David Heidelberg --- drivers/nfc/trf7970a.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nfc/trf7970a.c b/drivers/nfc/trf7970a.c index f22e091019de..eba00a8cb5c0 100644 --- a/drivers/nfc/trf7970a.c +++ b/drivers/nfc/trf7970a.c @@ -938,7 +938,7 @@ static irqreturn_t trf7970a_irq(int irq, void *dev_id) if (!trf->timeout) { trf->ignore_timeout = !cancel_delayed_work(&trf->timeout_work); - trf->rx_skb = ERR_PTR(0); + trf->rx_skb = NULL; trf7970a_send_upstream(trf); break; } From d832b95697384a2b67f20b35106bbf925358cde9 Mon Sep 17 00:00:00 2001 From: Griffin Kroah-Hartman Date: Tue, 7 Jul 2026 16:22:17 +0200 Subject: [PATCH 1208/1433] nfc: mrvl: spi: Unregister dev on allocation fail Call nfcmrvl_nci_unregister_dev() if nci_spi_allocate_spi() fails, unwrapping the previous call to nfcmrvl_nci_register_dev() during the nfcmrvl_spi_probe() function. Assisted-by: gkh-clanker-2000 Cc: David Heidelberg Signed-off-by: Griffin Kroah-Hartman Signed-off-by: Greg Kroah-Hartman Link: https://patch.msgid.link/2026070716-crucial-slouchy-b8a9@gregkh Signed-off-by: David Heidelberg --- drivers/nfc/nfcmrvl/spi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/nfc/nfcmrvl/spi.c b/drivers/nfc/nfcmrvl/spi.c index 9c8cde1250fb..dad07c8e13b8 100644 --- a/drivers/nfc/nfcmrvl/spi.c +++ b/drivers/nfc/nfcmrvl/spi.c @@ -168,6 +168,10 @@ static int nfcmrvl_spi_probe(struct spi_device *spi) drv_data->nci_spi = nci_spi_allocate_spi(drv_data->spi, 0, 10, drv_data->priv->ndev); + if (!drv_data->nci_spi) { + nfcmrvl_nci_unregister_dev(drv_data->priv); + return -ENOMEM; + } /* Init completion for slave handshake */ init_completion(&drv_data->handshake_completion); From 56345cea6a30dc7a1b036d5fb7900434b4a4c5e8 Mon Sep 17 00:00:00 2001 From: Griffin Kroah-Hartman Date: Tue, 7 Jul 2026 16:23:27 +0200 Subject: [PATCH 1209/1433] nfc: nxp-nci: Add remove on IRQ error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a call to nxp_nci_remove() in nxp_nci_i2c_probe() when the request_threaded_irq() fails. Previously, IRQ resources were not being freed upon error. Assisted-by: gkh_clanker_2000 Cc: David Heidelberg Cc: Carl Lee Cc: Jakub Kicinski Cc: Krzysztof Kozlowski Cc: Ian Ray Cc: "Uwe Kleine-König (The Capable Hub)" Signed-off-by: Griffin Kroah-Hartman Signed-off-by: Greg Kroah-Hartman Reviewed-by: Ian Ray Link: https://patch.msgid.link/2026070726-observer-fang-9716@gregkh Signed-off-by: David Heidelberg --- drivers/nfc/nxp-nci/i2c.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/nfc/nxp-nci/i2c.c b/drivers/nfc/nxp-nci/i2c.c index faebc89a7ef5..564f38735510 100644 --- a/drivers/nfc/nxp-nci/i2c.c +++ b/drivers/nfc/nxp-nci/i2c.c @@ -334,8 +334,10 @@ static int nxp_nci_i2c_probe(struct i2c_client *client) nxp_nci_i2c_irq_thread_fn, irqflags | IRQF_ONESHOT, NXP_NCI_I2C_DRIVER_NAME, phy); - if (r < 0) + if (r < 0) { nfc_err(&client->dev, "Unable to register IRQ handler\n"); + nxp_nci_remove(phy->ndev); + } return r; } From b65365e14098f0aa6fd0e3ff53caca343e575ab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 3 Jul 2026 17:46:15 +0200 Subject: [PATCH 1210/1433] nfc: Drop __maybe_unused from acpi_device_id tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Referencing these arrays in MODULE_DEVICE_TABLE() is enough to convince the compiler that they are used even if the drivers are built-in (since 5ab23c7923a1 ("modpost: Create modalias for builtin modules"). So the __maybe_unused marking can be removed without introducing a compiler warning. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/4c25d7a7f81d5117cd5d0de4a9f06ed0552e8793.1783091699.git.u.kleine-koenig@baylibre.com Signed-off-by: David Heidelberg --- drivers/nfc/pn544/i2c.c | 2 +- drivers/nfc/st-nci/i2c.c | 2 +- drivers/nfc/st-nci/spi.c | 2 +- drivers/nfc/st21nfca/i2c.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/nfc/pn544/i2c.c b/drivers/nfc/pn544/i2c.c index dcfa96bd4345..419b014d232e 100644 --- a/drivers/nfc/pn544/i2c.c +++ b/drivers/nfc/pn544/i2c.c @@ -50,7 +50,7 @@ static const struct i2c_device_id pn544_hci_i2c_id_table[] = { MODULE_DEVICE_TABLE(i2c, pn544_hci_i2c_id_table); -static const struct acpi_device_id pn544_hci_i2c_acpi_match[] __maybe_unused = { +static const struct acpi_device_id pn544_hci_i2c_acpi_match[] = { {"NXP5440", 0}, {} }; diff --git a/drivers/nfc/st-nci/i2c.c b/drivers/nfc/st-nci/i2c.c index 9ae839a6f5cc..4ddf0cc2a259 100644 --- a/drivers/nfc/st-nci/i2c.c +++ b/drivers/nfc/st-nci/i2c.c @@ -262,7 +262,7 @@ static const struct i2c_device_id st_nci_i2c_id_table[] = { }; MODULE_DEVICE_TABLE(i2c, st_nci_i2c_id_table); -static const struct acpi_device_id st_nci_i2c_acpi_match[] __maybe_unused = { +static const struct acpi_device_id st_nci_i2c_acpi_match[] = { {"SMO2101"}, {"SMO2102"}, {} diff --git a/drivers/nfc/st-nci/spi.c b/drivers/nfc/st-nci/spi.c index 169eacc0a32a..42f5f477e807 100644 --- a/drivers/nfc/st-nci/spi.c +++ b/drivers/nfc/st-nci/spi.c @@ -277,7 +277,7 @@ static struct spi_device_id st_nci_spi_id_table[] = { }; MODULE_DEVICE_TABLE(spi, st_nci_spi_id_table); -static const struct acpi_device_id st_nci_spi_acpi_match[] __maybe_unused = { +static const struct acpi_device_id st_nci_spi_acpi_match[] = { {"SMO2101", 0}, {} }; diff --git a/drivers/nfc/st21nfca/i2c.c b/drivers/nfc/st21nfca/i2c.c index aa5f4922b6b0..577434df5ff3 100644 --- a/drivers/nfc/st21nfca/i2c.c +++ b/drivers/nfc/st21nfca/i2c.c @@ -577,7 +577,7 @@ static const struct i2c_device_id st21nfca_hci_i2c_id_table[] = { }; MODULE_DEVICE_TABLE(i2c, st21nfca_hci_i2c_id_table); -static const struct acpi_device_id st21nfca_hci_i2c_acpi_match[] __maybe_unused = { +static const struct acpi_device_id st21nfca_hci_i2c_acpi_match[] = { {"SMO2100", 0}, {} }; From 23096d984885af49ac64b18b2d245fbd71b6c0ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 3 Jul 2026 17:46:16 +0200 Subject: [PATCH 1211/1433] nfc: Drop unused assignment of acpi_device_id driver data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The drivers explicitly set the .driver_data member of struct acpi_device_id to zero without relying on that value. Drop these unused assignments. This patch doesn't modify the compiled arrays, only their representation in source form benefits. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/c77f6376214001297f28d3ec48f0a853985f1847.1783091699.git.u.kleine-koenig@baylibre.com Signed-off-by: David Heidelberg --- drivers/nfc/fdp/i2c.c | 2 +- drivers/nfc/pn544/i2c.c | 3 +-- drivers/nfc/st-nci/spi.c | 2 +- drivers/nfc/st21nfca/i2c.c | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/nfc/fdp/i2c.c b/drivers/nfc/fdp/i2c.c index c1896a1d978c..9d589b388432 100644 --- a/drivers/nfc/fdp/i2c.c +++ b/drivers/nfc/fdp/i2c.c @@ -349,7 +349,7 @@ static void fdp_nci_i2c_remove(struct i2c_client *client) } static const struct acpi_device_id fdp_nci_i2c_acpi_match[] = { - {"INT339A", 0}, + { "INT339A" }, {} }; MODULE_DEVICE_TABLE(acpi, fdp_nci_i2c_acpi_match); diff --git a/drivers/nfc/pn544/i2c.c b/drivers/nfc/pn544/i2c.c index 419b014d232e..66d070b18b4d 100644 --- a/drivers/nfc/pn544/i2c.c +++ b/drivers/nfc/pn544/i2c.c @@ -51,10 +51,9 @@ static const struct i2c_device_id pn544_hci_i2c_id_table[] = { MODULE_DEVICE_TABLE(i2c, pn544_hci_i2c_id_table); static const struct acpi_device_id pn544_hci_i2c_acpi_match[] = { - {"NXP5440", 0}, + { "NXP5440" }, {} }; - MODULE_DEVICE_TABLE(acpi, pn544_hci_i2c_acpi_match); #define PN544_HCI_I2C_DRIVER_NAME "pn544_hci_i2c" diff --git a/drivers/nfc/st-nci/spi.c b/drivers/nfc/st-nci/spi.c index 42f5f477e807..c94d67f33184 100644 --- a/drivers/nfc/st-nci/spi.c +++ b/drivers/nfc/st-nci/spi.c @@ -278,7 +278,7 @@ static struct spi_device_id st_nci_spi_id_table[] = { MODULE_DEVICE_TABLE(spi, st_nci_spi_id_table); static const struct acpi_device_id st_nci_spi_acpi_match[] = { - {"SMO2101", 0}, + { "SMO2101" }, {} }; MODULE_DEVICE_TABLE(acpi, st_nci_spi_acpi_match); diff --git a/drivers/nfc/st21nfca/i2c.c b/drivers/nfc/st21nfca/i2c.c index 577434df5ff3..d6cb74a89f93 100644 --- a/drivers/nfc/st21nfca/i2c.c +++ b/drivers/nfc/st21nfca/i2c.c @@ -578,7 +578,7 @@ static const struct i2c_device_id st21nfca_hci_i2c_id_table[] = { MODULE_DEVICE_TABLE(i2c, st21nfca_hci_i2c_id_table); static const struct acpi_device_id st21nfca_hci_i2c_acpi_match[] = { - {"SMO2100", 0}, + { "SMO2100" }, {} }; MODULE_DEVICE_TABLE(acpi, st21nfca_hci_i2c_acpi_match); From 42d470a98e22e370262d8227c65f16a3b317a7f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 3 Jul 2026 17:46:17 +0200 Subject: [PATCH 1212/1433] nfc: Initialize acpi_device_id arrays using member names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While being less compact, using named initializers allows to more easily see which members of the structs are assigned which value without having to lookup the declaration of the struct. And it's also more robust against changes to the struct definition. The mentioned robustness is relevant for a planned change to struct acpi_device_id that replaces .driver_data by an anonymous union. This patch doesn't modify the compiled arrays, only their representation in source form benefits. The former was confirmed with x86 and arm64 builds. Signed-off-by: Uwe Kleine-König (The Capable Hub) Reviewed-by: Ian Ray Link: https://patch.msgid.link/5f60cd3e9831aac3995ed1a1b074ca1ae32e5286.1783091699.git.u.kleine-koenig@baylibre.com Signed-off-by: David Heidelberg --- drivers/nfc/fdp/i2c.c | 2 +- drivers/nfc/nxp-nci/i2c.c | 6 +++--- drivers/nfc/pn544/i2c.c | 2 +- drivers/nfc/st-nci/i2c.c | 4 ++-- drivers/nfc/st-nci/spi.c | 2 +- drivers/nfc/st21nfca/i2c.c | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/nfc/fdp/i2c.c b/drivers/nfc/fdp/i2c.c index 9d589b388432..47f3838ac9d4 100644 --- a/drivers/nfc/fdp/i2c.c +++ b/drivers/nfc/fdp/i2c.c @@ -349,7 +349,7 @@ static void fdp_nci_i2c_remove(struct i2c_client *client) } static const struct acpi_device_id fdp_nci_i2c_acpi_match[] = { - { "INT339A" }, + { .id = "INT339A" }, {} }; MODULE_DEVICE_TABLE(acpi, fdp_nci_i2c_acpi_match); diff --git a/drivers/nfc/nxp-nci/i2c.c b/drivers/nfc/nxp-nci/i2c.c index 564f38735510..3fa24f540802 100644 --- a/drivers/nfc/nxp-nci/i2c.c +++ b/drivers/nfc/nxp-nci/i2c.c @@ -364,9 +364,9 @@ MODULE_DEVICE_TABLE(of, of_nxp_nci_i2c_match); #ifdef CONFIG_ACPI static const struct acpi_device_id acpi_id[] = { - { "NXP1001" }, - { "NXP1002" }, - { "NXP7471" }, + { .id = "NXP1001" }, + { .id = "NXP1002" }, + { .id = "NXP7471" }, { } }; MODULE_DEVICE_TABLE(acpi, acpi_id); diff --git a/drivers/nfc/pn544/i2c.c b/drivers/nfc/pn544/i2c.c index 66d070b18b4d..ccca07eb828c 100644 --- a/drivers/nfc/pn544/i2c.c +++ b/drivers/nfc/pn544/i2c.c @@ -51,7 +51,7 @@ static const struct i2c_device_id pn544_hci_i2c_id_table[] = { MODULE_DEVICE_TABLE(i2c, pn544_hci_i2c_id_table); static const struct acpi_device_id pn544_hci_i2c_acpi_match[] = { - { "NXP5440" }, + { .id = "NXP5440" }, {} }; MODULE_DEVICE_TABLE(acpi, pn544_hci_i2c_acpi_match); diff --git a/drivers/nfc/st-nci/i2c.c b/drivers/nfc/st-nci/i2c.c index 4ddf0cc2a259..3906a806ec8b 100644 --- a/drivers/nfc/st-nci/i2c.c +++ b/drivers/nfc/st-nci/i2c.c @@ -263,8 +263,8 @@ static const struct i2c_device_id st_nci_i2c_id_table[] = { MODULE_DEVICE_TABLE(i2c, st_nci_i2c_id_table); static const struct acpi_device_id st_nci_i2c_acpi_match[] = { - {"SMO2101"}, - {"SMO2102"}, + { .id = "SMO2101" }, + { .id = "SMO2102" }, {} }; MODULE_DEVICE_TABLE(acpi, st_nci_i2c_acpi_match); diff --git a/drivers/nfc/st-nci/spi.c b/drivers/nfc/st-nci/spi.c index c94d67f33184..8fc60a972d98 100644 --- a/drivers/nfc/st-nci/spi.c +++ b/drivers/nfc/st-nci/spi.c @@ -278,7 +278,7 @@ static struct spi_device_id st_nci_spi_id_table[] = { MODULE_DEVICE_TABLE(spi, st_nci_spi_id_table); static const struct acpi_device_id st_nci_spi_acpi_match[] = { - { "SMO2101" }, + { .id = "SMO2101" }, {} }; MODULE_DEVICE_TABLE(acpi, st_nci_spi_acpi_match); diff --git a/drivers/nfc/st21nfca/i2c.c b/drivers/nfc/st21nfca/i2c.c index d6cb74a89f93..e5109f701795 100644 --- a/drivers/nfc/st21nfca/i2c.c +++ b/drivers/nfc/st21nfca/i2c.c @@ -578,7 +578,7 @@ static const struct i2c_device_id st21nfca_hci_i2c_id_table[] = { MODULE_DEVICE_TABLE(i2c, st21nfca_hci_i2c_id_table); static const struct acpi_device_id st21nfca_hci_i2c_acpi_match[] = { - { "SMO2100" }, + { .id = "SMO2100" }, {} }; MODULE_DEVICE_TABLE(acpi, st21nfca_hci_i2c_acpi_match); From e440889dfd25c5d4328cf4624f0379e065f895fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 3 Jul 2026 17:46:18 +0200 Subject: [PATCH 1213/1433] nfc: Unify style of acpi_device_id arrays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unify the style of the list terminator in acpi_device_id arrays, that is use a single space between { and }. This is the most common and generally recommended style for these. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/30cd94a6821917f16daffdce5fabf145432c13eb.1783091699.git.u.kleine-koenig@baylibre.com Signed-off-by: David Heidelberg --- drivers/nfc/fdp/i2c.c | 2 +- drivers/nfc/pn544/i2c.c | 2 +- drivers/nfc/st-nci/i2c.c | 2 +- drivers/nfc/st-nci/spi.c | 2 +- drivers/nfc/st21nfca/i2c.c | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/nfc/fdp/i2c.c b/drivers/nfc/fdp/i2c.c index 47f3838ac9d4..13d4387e79a0 100644 --- a/drivers/nfc/fdp/i2c.c +++ b/drivers/nfc/fdp/i2c.c @@ -350,7 +350,7 @@ static void fdp_nci_i2c_remove(struct i2c_client *client) static const struct acpi_device_id fdp_nci_i2c_acpi_match[] = { { .id = "INT339A" }, - {} + { } }; MODULE_DEVICE_TABLE(acpi, fdp_nci_i2c_acpi_match); diff --git a/drivers/nfc/pn544/i2c.c b/drivers/nfc/pn544/i2c.c index ccca07eb828c..9ed1cde1de2e 100644 --- a/drivers/nfc/pn544/i2c.c +++ b/drivers/nfc/pn544/i2c.c @@ -52,7 +52,7 @@ MODULE_DEVICE_TABLE(i2c, pn544_hci_i2c_id_table); static const struct acpi_device_id pn544_hci_i2c_acpi_match[] = { { .id = "NXP5440" }, - {} + { } }; MODULE_DEVICE_TABLE(acpi, pn544_hci_i2c_acpi_match); diff --git a/drivers/nfc/st-nci/i2c.c b/drivers/nfc/st-nci/i2c.c index 3906a806ec8b..f43ae8e92070 100644 --- a/drivers/nfc/st-nci/i2c.c +++ b/drivers/nfc/st-nci/i2c.c @@ -265,7 +265,7 @@ MODULE_DEVICE_TABLE(i2c, st_nci_i2c_id_table); static const struct acpi_device_id st_nci_i2c_acpi_match[] = { { .id = "SMO2101" }, { .id = "SMO2102" }, - {} + { } }; MODULE_DEVICE_TABLE(acpi, st_nci_i2c_acpi_match); diff --git a/drivers/nfc/st-nci/spi.c b/drivers/nfc/st-nci/spi.c index 8fc60a972d98..9303217acd7b 100644 --- a/drivers/nfc/st-nci/spi.c +++ b/drivers/nfc/st-nci/spi.c @@ -279,7 +279,7 @@ MODULE_DEVICE_TABLE(spi, st_nci_spi_id_table); static const struct acpi_device_id st_nci_spi_acpi_match[] = { { .id = "SMO2101" }, - {} + { } }; MODULE_DEVICE_TABLE(acpi, st_nci_spi_acpi_match); diff --git a/drivers/nfc/st21nfca/i2c.c b/drivers/nfc/st21nfca/i2c.c index e5109f701795..13fb6f5533e0 100644 --- a/drivers/nfc/st21nfca/i2c.c +++ b/drivers/nfc/st21nfca/i2c.c @@ -579,7 +579,7 @@ MODULE_DEVICE_TABLE(i2c, st21nfca_hci_i2c_id_table); static const struct acpi_device_id st21nfca_hci_i2c_acpi_match[] = { { .id = "SMO2100" }, - {} + { } }; MODULE_DEVICE_TABLE(acpi, st21nfca_hci_i2c_acpi_match); From dc0650569a466b234bf3bf06d9c238614015fb39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 3 Jul 2026 17:46:19 +0200 Subject: [PATCH 1214/1433] nfc: pn544: Drop empty line between i2c_device_id array and MODULE_DEVICE_TABLE() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Usually there is no empty line between a module device table and the respective MODULE_DEVICE_TABLE(): $ git grep -h -B1 ^MODULE_DEVICE_TABLE v7.1-rc1 | sort | uniq -c | sort -n ... 1388 8129 }; 9784 -- (The `--` is part of grep output to separate the matches with their context from each other, that's not the most usual line before MODULE_DEVICE_TABLE(...).) Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/7562d0062948a474957d8d733c0e8a70de502624.1783091699.git.u.kleine-koenig@baylibre.com Signed-off-by: David Heidelberg --- drivers/nfc/pn544/i2c.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/nfc/pn544/i2c.c b/drivers/nfc/pn544/i2c.c index 9ed1cde1de2e..b731d0b02f52 100644 --- a/drivers/nfc/pn544/i2c.c +++ b/drivers/nfc/pn544/i2c.c @@ -47,7 +47,6 @@ static const struct i2c_device_id pn544_hci_i2c_id_table[] = { { .name = "pn544" }, { } }; - MODULE_DEVICE_TABLE(i2c, pn544_hci_i2c_id_table); static const struct acpi_device_id pn544_hci_i2c_acpi_match[] = { From 15aa65539aa768c7acd9ae3a845df305caa72688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 3 Jul 2026 17:46:20 +0200 Subject: [PATCH 1215/1433] nfc: Initialize mei_cl_device_idarrays using member names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While being less compact, using named initializers allows to more easily see which members of the structs are assigned which value without having to lookup the declaration of the struct. And it's also more robust against changes to the struct definition. The mentioned robustness is relevant for a planned change to struct mei_cl_device_id that replaces .driver_data by an anonymous union. This patch doesn't modify the compiled arrays, only their representation in source form benefits. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/cdc9bbac2e0743550970e565f57996c8a833446f.1783091699.git.u.kleine-koenig@baylibre.com Signed-off-by: David Heidelberg --- drivers/nfc/microread/mei.c | 10 ++++++---- drivers/nfc/pn544/mei.c | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/drivers/nfc/microread/mei.c b/drivers/nfc/microread/mei.c index c256ae92d6b1..484e3ae0e875 100644 --- a/drivers/nfc/microread/mei.c +++ b/drivers/nfc/microread/mei.c @@ -48,10 +48,12 @@ static void microread_mei_remove(struct mei_cl_device *cldev) } static struct mei_cl_device_id microread_mei_tbl[] = { - { MICROREAD_DRIVER_NAME, MEI_NFC_UUID, MEI_CL_VERSION_ANY}, - - /* required last entry */ - { } + { + .name = MICROREAD_DRIVER_NAME, + .uuid = MEI_NFC_UUID, + .version = MEI_CL_VERSION_ANY, + }, + { /* required last entry */ } }; MODULE_DEVICE_TABLE(mei, microread_mei_tbl); diff --git a/drivers/nfc/pn544/mei.c b/drivers/nfc/pn544/mei.c index 3d3755cfa71e..7ca117186d3e 100644 --- a/drivers/nfc/pn544/mei.c +++ b/drivers/nfc/pn544/mei.c @@ -47,10 +47,12 @@ static void pn544_mei_remove(struct mei_cl_device *cldev) } static struct mei_cl_device_id pn544_mei_tbl[] = { - { PN544_DRIVER_NAME, MEI_NFC_UUID, MEI_CL_VERSION_ANY}, - - /* required last entry */ - { } + { + .name = PN544_DRIVER_NAME, + .uuid = MEI_NFC_UUID, + .version = MEI_CL_VERSION_ANY, + }, + { /* required last entry */ } }; MODULE_DEVICE_TABLE(mei, pn544_mei_tbl); From 777fd2dab44601ea7f59a104e9553e3eb7329782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 3 Jul 2026 17:46:21 +0200 Subject: [PATCH 1216/1433] nfc: Drop __maybe_unused from of_device_id tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Referencing these arrays in MODULE_DEVICE_TABLE() is enough to convince the compiler that they are used even if the drivers are built-in (since 5ab23c7923a1 ("modpost: Create modalias for builtin modules"). So the __maybe_unused marking can be removed without introducing a compiler warning. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/031ea0ae38838df3261f844eb13e9841769b49a7.1783091699.git.u.kleine-koenig@baylibre.com Signed-off-by: David Heidelberg --- drivers/nfc/nfcmrvl/i2c.c | 2 +- drivers/nfc/nfcmrvl/spi.c | 2 +- drivers/nfc/pn533/i2c.c | 2 +- drivers/nfc/pn544/i2c.c | 2 +- drivers/nfc/s3fwrn5/i2c.c | 2 +- drivers/nfc/st-nci/i2c.c | 2 +- drivers/nfc/st-nci/spi.c | 2 +- drivers/nfc/st21nfca/i2c.c | 2 +- drivers/nfc/st95hf/core.c | 2 +- drivers/nfc/trf7970a.c | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/nfc/nfcmrvl/i2c.c b/drivers/nfc/nfcmrvl/i2c.c index 66877a7d03f2..687d2979b881 100644 --- a/drivers/nfc/nfcmrvl/i2c.c +++ b/drivers/nfc/nfcmrvl/i2c.c @@ -245,7 +245,7 @@ static void nfcmrvl_i2c_remove(struct i2c_client *client) } -static const struct of_device_id of_nfcmrvl_i2c_match[] __maybe_unused = { +static const struct of_device_id of_nfcmrvl_i2c_match[] = { { .compatible = "marvell,nfc-i2c", }, {}, }; diff --git a/drivers/nfc/nfcmrvl/spi.c b/drivers/nfc/nfcmrvl/spi.c index dad07c8e13b8..5c04c2489603 100644 --- a/drivers/nfc/nfcmrvl/spi.c +++ b/drivers/nfc/nfcmrvl/spi.c @@ -185,7 +185,7 @@ static void nfcmrvl_spi_remove(struct spi_device *spi) nfcmrvl_nci_unregister_dev(drv_data->priv); } -static const struct of_device_id of_nfcmrvl_spi_match[] __maybe_unused = { +static const struct of_device_id of_nfcmrvl_spi_match[] = { { .compatible = "marvell,nfc-spi", }, {}, }; diff --git a/drivers/nfc/pn533/i2c.c b/drivers/nfc/pn533/i2c.c index 94aca9119f0f..2128083f0297 100644 --- a/drivers/nfc/pn533/i2c.c +++ b/drivers/nfc/pn533/i2c.c @@ -236,7 +236,7 @@ static void pn533_i2c_remove(struct i2c_client *client) pn53x_common_clean(phy->priv); } -static const struct of_device_id of_pn533_i2c_match[] __maybe_unused = { +static const struct of_device_id of_pn533_i2c_match[] = { { .compatible = "nxp,pn532", }, /* * NOTE: The use of the compatibles with the trailing "...-i2c" is diff --git a/drivers/nfc/pn544/i2c.c b/drivers/nfc/pn544/i2c.c index b731d0b02f52..50907a1974cd 100644 --- a/drivers/nfc/pn544/i2c.c +++ b/drivers/nfc/pn544/i2c.c @@ -937,7 +937,7 @@ static void pn544_hci_i2c_remove(struct i2c_client *client) pn544_hci_i2c_disable(phy); } -static const struct of_device_id of_pn544_i2c_match[] __maybe_unused = { +static const struct of_device_id of_pn544_i2c_match[] = { { .compatible = "nxp,pn544-i2c", }, {}, }; diff --git a/drivers/nfc/s3fwrn5/i2c.c b/drivers/nfc/s3fwrn5/i2c.c index e9a34d27a369..499301a6fa3f 100644 --- a/drivers/nfc/s3fwrn5/i2c.c +++ b/drivers/nfc/s3fwrn5/i2c.c @@ -210,7 +210,7 @@ static const struct i2c_device_id s3fwrn5_i2c_id_table[] = { }; MODULE_DEVICE_TABLE(i2c, s3fwrn5_i2c_id_table); -static const struct of_device_id of_s3fwrn5_i2c_match[] __maybe_unused = { +static const struct of_device_id of_s3fwrn5_i2c_match[] = { { .compatible = "samsung,s3fwrn5-i2c", }, {} }; diff --git a/drivers/nfc/st-nci/i2c.c b/drivers/nfc/st-nci/i2c.c index f43ae8e92070..ceb7d7450e47 100644 --- a/drivers/nfc/st-nci/i2c.c +++ b/drivers/nfc/st-nci/i2c.c @@ -269,7 +269,7 @@ static const struct acpi_device_id st_nci_i2c_acpi_match[] = { }; MODULE_DEVICE_TABLE(acpi, st_nci_i2c_acpi_match); -static const struct of_device_id of_st_nci_i2c_match[] __maybe_unused = { +static const struct of_device_id of_st_nci_i2c_match[] = { { .compatible = "st,st21nfcb-i2c", }, { .compatible = "st,st21nfcb_i2c", }, { .compatible = "st,st21nfcc-i2c", }, diff --git a/drivers/nfc/st-nci/spi.c b/drivers/nfc/st-nci/spi.c index 9303217acd7b..8632cc0cb305 100644 --- a/drivers/nfc/st-nci/spi.c +++ b/drivers/nfc/st-nci/spi.c @@ -283,7 +283,7 @@ static const struct acpi_device_id st_nci_spi_acpi_match[] = { }; MODULE_DEVICE_TABLE(acpi, st_nci_spi_acpi_match); -static const struct of_device_id of_st_nci_spi_match[] __maybe_unused = { +static const struct of_device_id of_st_nci_spi_match[] = { { .compatible = "st,st21nfcb-spi", }, {} }; diff --git a/drivers/nfc/st21nfca/i2c.c b/drivers/nfc/st21nfca/i2c.c index 13fb6f5533e0..4e70f591af55 100644 --- a/drivers/nfc/st21nfca/i2c.c +++ b/drivers/nfc/st21nfca/i2c.c @@ -583,7 +583,7 @@ static const struct acpi_device_id st21nfca_hci_i2c_acpi_match[] = { }; MODULE_DEVICE_TABLE(acpi, st21nfca_hci_i2c_acpi_match); -static const struct of_device_id of_st21nfca_i2c_match[] __maybe_unused = { +static const struct of_device_id of_st21nfca_i2c_match[] = { { .compatible = "st,st21nfca-i2c", }, { .compatible = "st,st21nfca_i2c", }, {} diff --git a/drivers/nfc/st95hf/core.c b/drivers/nfc/st95hf/core.c index ffe5b4eab457..1ecd47c6518e 100644 --- a/drivers/nfc/st95hf/core.c +++ b/drivers/nfc/st95hf/core.c @@ -1054,7 +1054,7 @@ static const struct spi_device_id st95hf_id[] = { }; MODULE_DEVICE_TABLE(spi, st95hf_id); -static const struct of_device_id st95hf_spi_of_match[] __maybe_unused = { +static const struct of_device_id st95hf_spi_of_match[] = { { .compatible = "st,st95hf" }, {}, }; diff --git a/drivers/nfc/trf7970a.c b/drivers/nfc/trf7970a.c index eba00a8cb5c0..a2f0d1fd3b92 100644 --- a/drivers/nfc/trf7970a.c +++ b/drivers/nfc/trf7970a.c @@ -2303,7 +2303,7 @@ static const struct dev_pm_ops trf7970a_pm_ops = { trf7970a_pm_runtime_resume, NULL) }; -static const struct of_device_id trf7970a_of_match[] __maybe_unused = { +static const struct of_device_id trf7970a_of_match[] = { {.compatible = "ti,trf7970a",}, {}, }; From ce2d85e3d293b392ab8f1729a71788fae83ef254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 3 Jul 2026 17:46:22 +0200 Subject: [PATCH 1217/1433] nfc: Unify style of of_device_id arrays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The most common style treewide is: - A single space in the list terminator and no trailing , - No comma after a named initializers iff the closing } is on the same line Adapt the of_device_id arrays accordingly. Signed-off-by: Uwe Kleine-König (The Capable Hub) Reviewed-by: Ian Ray Link: https://patch.msgid.link/583375dcd834f5edf6241b09cdd75ad4f32af668.1783091699.git.u.kleine-koenig@baylibre.com Signed-off-by: David Heidelberg --- drivers/nfc/nfcmrvl/i2c.c | 4 ++-- drivers/nfc/nfcmrvl/spi.c | 4 ++-- drivers/nfc/nxp-nci/i2c.c | 4 ++-- drivers/nfc/pn533/i2c.c | 8 ++++---- drivers/nfc/pn533/uart.c | 4 ++-- drivers/nfc/pn544/i2c.c | 4 ++-- drivers/nfc/s3fwrn5/i2c.c | 4 ++-- drivers/nfc/s3fwrn5/uart.c | 4 ++-- drivers/nfc/st-nci/i2c.c | 8 ++++---- drivers/nfc/st-nci/spi.c | 4 ++-- drivers/nfc/st21nfca/i2c.c | 6 +++--- drivers/nfc/st95hf/core.c | 2 +- drivers/nfc/trf7970a.c | 5 ++--- 13 files changed, 30 insertions(+), 31 deletions(-) diff --git a/drivers/nfc/nfcmrvl/i2c.c b/drivers/nfc/nfcmrvl/i2c.c index 687d2979b881..068c5d278a35 100644 --- a/drivers/nfc/nfcmrvl/i2c.c +++ b/drivers/nfc/nfcmrvl/i2c.c @@ -246,8 +246,8 @@ static void nfcmrvl_i2c_remove(struct i2c_client *client) static const struct of_device_id of_nfcmrvl_i2c_match[] = { - { .compatible = "marvell,nfc-i2c", }, - {}, + { .compatible = "marvell,nfc-i2c" }, + { } }; MODULE_DEVICE_TABLE(of, of_nfcmrvl_i2c_match); diff --git a/drivers/nfc/nfcmrvl/spi.c b/drivers/nfc/nfcmrvl/spi.c index 5c04c2489603..05d6ee7d4d2a 100644 --- a/drivers/nfc/nfcmrvl/spi.c +++ b/drivers/nfc/nfcmrvl/spi.c @@ -186,8 +186,8 @@ static void nfcmrvl_spi_remove(struct spi_device *spi) } static const struct of_device_id of_nfcmrvl_spi_match[] = { - { .compatible = "marvell,nfc-spi", }, - {}, + { .compatible = "marvell,nfc-spi" }, + { } }; MODULE_DEVICE_TABLE(of, of_nfcmrvl_spi_match); diff --git a/drivers/nfc/nxp-nci/i2c.c b/drivers/nfc/nxp-nci/i2c.c index 3fa24f540802..92ce096e9b18 100644 --- a/drivers/nfc/nxp-nci/i2c.c +++ b/drivers/nfc/nxp-nci/i2c.c @@ -357,8 +357,8 @@ static const struct i2c_device_id nxp_nci_i2c_id_table[] = { MODULE_DEVICE_TABLE(i2c, nxp_nci_i2c_id_table); static const struct of_device_id of_nxp_nci_i2c_match[] = { - { .compatible = "nxp,nxp-nci-i2c", }, - {} + { .compatible = "nxp,nxp-nci-i2c" }, + { } }; MODULE_DEVICE_TABLE(of, of_nxp_nci_i2c_match); diff --git a/drivers/nfc/pn533/i2c.c b/drivers/nfc/pn533/i2c.c index 2128083f0297..66d201c14a40 100644 --- a/drivers/nfc/pn533/i2c.c +++ b/drivers/nfc/pn533/i2c.c @@ -237,14 +237,14 @@ static void pn533_i2c_remove(struct i2c_client *client) } static const struct of_device_id of_pn533_i2c_match[] = { - { .compatible = "nxp,pn532", }, + { .compatible = "nxp,pn532" }, /* * NOTE: The use of the compatibles with the trailing "...-i2c" is * deprecated and will be removed. */ - { .compatible = "nxp,pn533-i2c", }, - { .compatible = "nxp,pn532-i2c", }, - {}, + { .compatible = "nxp,pn533-i2c" }, + { .compatible = "nxp,pn532-i2c" }, + { } }; MODULE_DEVICE_TABLE(of, of_pn533_i2c_match); diff --git a/drivers/nfc/pn533/uart.c b/drivers/nfc/pn533/uart.c index e0d67cd2ac9b..83c1ccda0af6 100644 --- a/drivers/nfc/pn533/uart.c +++ b/drivers/nfc/pn533/uart.c @@ -238,8 +238,8 @@ static const struct serdev_device_ops pn532_serdev_ops = { }; static const struct of_device_id pn532_uart_of_match[] = { - { .compatible = "nxp,pn532", }, - {}, + { .compatible = "nxp,pn532" }, + { } }; MODULE_DEVICE_TABLE(of, pn532_uart_of_match); diff --git a/drivers/nfc/pn544/i2c.c b/drivers/nfc/pn544/i2c.c index 50907a1974cd..7fde3aefae70 100644 --- a/drivers/nfc/pn544/i2c.c +++ b/drivers/nfc/pn544/i2c.c @@ -938,8 +938,8 @@ static void pn544_hci_i2c_remove(struct i2c_client *client) } static const struct of_device_id of_pn544_i2c_match[] = { - { .compatible = "nxp,pn544-i2c", }, - {}, + { .compatible = "nxp,pn544-i2c" }, + { } }; MODULE_DEVICE_TABLE(of, of_pn544_i2c_match); diff --git a/drivers/nfc/s3fwrn5/i2c.c b/drivers/nfc/s3fwrn5/i2c.c index 499301a6fa3f..4ba762611711 100644 --- a/drivers/nfc/s3fwrn5/i2c.c +++ b/drivers/nfc/s3fwrn5/i2c.c @@ -211,8 +211,8 @@ static const struct i2c_device_id s3fwrn5_i2c_id_table[] = { MODULE_DEVICE_TABLE(i2c, s3fwrn5_i2c_id_table); static const struct of_device_id of_s3fwrn5_i2c_match[] = { - { .compatible = "samsung,s3fwrn5-i2c", }, - {} + { .compatible = "samsung,s3fwrn5-i2c" }, + { } }; MODULE_DEVICE_TABLE(of, of_s3fwrn5_i2c_match); diff --git a/drivers/nfc/s3fwrn5/uart.c b/drivers/nfc/s3fwrn5/uart.c index e17c599a2da5..8f142a255101 100644 --- a/drivers/nfc/s3fwrn5/uart.c +++ b/drivers/nfc/s3fwrn5/uart.c @@ -85,8 +85,8 @@ static const struct serdev_device_ops s3fwrn82_serdev_ops = { }; static const struct of_device_id s3fwrn82_uart_of_match[] = { - { .compatible = "samsung,s3fwrn82", }, - {}, + { .compatible = "samsung,s3fwrn82" }, + { } }; MODULE_DEVICE_TABLE(of, s3fwrn82_uart_of_match); diff --git a/drivers/nfc/st-nci/i2c.c b/drivers/nfc/st-nci/i2c.c index ceb7d7450e47..152c20b6bb01 100644 --- a/drivers/nfc/st-nci/i2c.c +++ b/drivers/nfc/st-nci/i2c.c @@ -270,10 +270,10 @@ static const struct acpi_device_id st_nci_i2c_acpi_match[] = { MODULE_DEVICE_TABLE(acpi, st_nci_i2c_acpi_match); static const struct of_device_id of_st_nci_i2c_match[] = { - { .compatible = "st,st21nfcb-i2c", }, - { .compatible = "st,st21nfcb_i2c", }, - { .compatible = "st,st21nfcc-i2c", }, - {} + { .compatible = "st,st21nfcb-i2c" }, + { .compatible = "st,st21nfcb_i2c" }, + { .compatible = "st,st21nfcc-i2c" }, + { } }; MODULE_DEVICE_TABLE(of, of_st_nci_i2c_match); diff --git a/drivers/nfc/st-nci/spi.c b/drivers/nfc/st-nci/spi.c index 8632cc0cb305..5e0b94050f90 100644 --- a/drivers/nfc/st-nci/spi.c +++ b/drivers/nfc/st-nci/spi.c @@ -284,8 +284,8 @@ static const struct acpi_device_id st_nci_spi_acpi_match[] = { MODULE_DEVICE_TABLE(acpi, st_nci_spi_acpi_match); static const struct of_device_id of_st_nci_spi_match[] = { - { .compatible = "st,st21nfcb-spi", }, - {} + { .compatible = "st,st21nfcb-spi" }, + { } }; MODULE_DEVICE_TABLE(of, of_st_nci_spi_match); diff --git a/drivers/nfc/st21nfca/i2c.c b/drivers/nfc/st21nfca/i2c.c index 4e70f591af55..a4c93ff7c5b0 100644 --- a/drivers/nfc/st21nfca/i2c.c +++ b/drivers/nfc/st21nfca/i2c.c @@ -584,9 +584,9 @@ static const struct acpi_device_id st21nfca_hci_i2c_acpi_match[] = { MODULE_DEVICE_TABLE(acpi, st21nfca_hci_i2c_acpi_match); static const struct of_device_id of_st21nfca_i2c_match[] = { - { .compatible = "st,st21nfca-i2c", }, - { .compatible = "st,st21nfca_i2c", }, - {} + { .compatible = "st,st21nfca-i2c" }, + { .compatible = "st,st21nfca_i2c" }, + { } }; MODULE_DEVICE_TABLE(of, of_st21nfca_i2c_match); diff --git a/drivers/nfc/st95hf/core.c b/drivers/nfc/st95hf/core.c index 1ecd47c6518e..265ab10bbb61 100644 --- a/drivers/nfc/st95hf/core.c +++ b/drivers/nfc/st95hf/core.c @@ -1056,7 +1056,7 @@ MODULE_DEVICE_TABLE(spi, st95hf_id); static const struct of_device_id st95hf_spi_of_match[] = { { .compatible = "st,st95hf" }, - {}, + { } }; MODULE_DEVICE_TABLE(of, st95hf_spi_of_match); diff --git a/drivers/nfc/trf7970a.c b/drivers/nfc/trf7970a.c index a2f0d1fd3b92..5ba4e3bd1bf6 100644 --- a/drivers/nfc/trf7970a.c +++ b/drivers/nfc/trf7970a.c @@ -2304,10 +2304,9 @@ static const struct dev_pm_ops trf7970a_pm_ops = { }; static const struct of_device_id trf7970a_of_match[] = { - {.compatible = "ti,trf7970a",}, - {}, + { .compatible = "ti,trf7970a" }, + { } }; - MODULE_DEVICE_TABLE(of, trf7970a_of_match); static const struct spi_device_id trf7970a_id_table[] = { From 5be3e23a47950a169bc2ea97d02b485d81b5c7c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 3 Jul 2026 17:46:23 +0200 Subject: [PATCH 1218/1433] nfc: Drop unused assignment of spi_device_id driver data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The drivers explicitly set the .driver_data member of struct spi_device_id to zero without relying on that value. Drop these unused assignments. This patch doesn't modify the compiled arrays, only their representation in source form benefits. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/c645d5855d26307d6164122412335533febbf8b9.1783091699.git.u.kleine-koenig@baylibre.com Signed-off-by: David Heidelberg --- drivers/nfc/nfcmrvl/spi.c | 2 +- drivers/nfc/st-nci/spi.c | 4 ++-- drivers/nfc/st95hf/core.c | 2 +- drivers/nfc/trf7970a.c | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/nfc/nfcmrvl/spi.c b/drivers/nfc/nfcmrvl/spi.c index 05d6ee7d4d2a..6faf3fbd6911 100644 --- a/drivers/nfc/nfcmrvl/spi.c +++ b/drivers/nfc/nfcmrvl/spi.c @@ -192,7 +192,7 @@ static const struct of_device_id of_nfcmrvl_spi_match[] = { MODULE_DEVICE_TABLE(of, of_nfcmrvl_spi_match); static const struct spi_device_id nfcmrvl_spi_id_table[] = { - { "nfcmrvl_spi", 0 }, + { "nfcmrvl_spi" }, { } }; MODULE_DEVICE_TABLE(spi, nfcmrvl_spi_id_table); diff --git a/drivers/nfc/st-nci/spi.c b/drivers/nfc/st-nci/spi.c index 5e0b94050f90..1bbda3d0a7dc 100644 --- a/drivers/nfc/st-nci/spi.c +++ b/drivers/nfc/st-nci/spi.c @@ -271,8 +271,8 @@ static void st_nci_spi_remove(struct spi_device *dev) } static struct spi_device_id st_nci_spi_id_table[] = { - {ST_NCI_SPI_DRIVER_NAME, 0}, - {"st21nfcb-spi", 0}, + { ST_NCI_SPI_DRIVER_NAME }, + { "st21nfcb-spi" }, {} }; MODULE_DEVICE_TABLE(spi, st_nci_spi_id_table); diff --git a/drivers/nfc/st95hf/core.c b/drivers/nfc/st95hf/core.c index 265ab10bbb61..52fe81a557a0 100644 --- a/drivers/nfc/st95hf/core.c +++ b/drivers/nfc/st95hf/core.c @@ -1049,7 +1049,7 @@ static const struct nfc_digital_ops st95hf_nfc_digital_ops = { }; static const struct spi_device_id st95hf_id[] = { - { "st95hf", 0 }, + { "st95hf" }, {} }; MODULE_DEVICE_TABLE(spi, st95hf_id); diff --git a/drivers/nfc/trf7970a.c b/drivers/nfc/trf7970a.c index 5ba4e3bd1bf6..bb3f83adf7db 100644 --- a/drivers/nfc/trf7970a.c +++ b/drivers/nfc/trf7970a.c @@ -2310,7 +2310,7 @@ static const struct of_device_id trf7970a_of_match[] = { MODULE_DEVICE_TABLE(of, trf7970a_of_match); static const struct spi_device_id trf7970a_id_table[] = { - {"trf7970a", 0}, + { "trf7970a" }, {} }; From d9d8172a641af38397a5750db0dbfb41e4504727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 3 Jul 2026 17:46:24 +0200 Subject: [PATCH 1219/1433] nfc: Initialize spi_device_idarrays using member names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While being less compact, using named initializers allows to more easily see which members of the structs are assigned which value without having to lookup the declaration of the struct. And it's also more robust against changes to the struct definition. The mentioned robustness is relevant for a planned change to struct spi_device_id that replaces .driver_data by an anonymous union. This patch doesn't modify the compiled arrays, only their representation in source form benefits. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/308a0d43ef042566ca595f1afa803cac592a4643.1783091699.git.u.kleine-koenig@baylibre.com Signed-off-by: David Heidelberg --- drivers/nfc/nfcmrvl/spi.c | 2 +- drivers/nfc/st-nci/spi.c | 4 ++-- drivers/nfc/st95hf/core.c | 2 +- drivers/nfc/trf7970a.c | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/nfc/nfcmrvl/spi.c b/drivers/nfc/nfcmrvl/spi.c index 6faf3fbd6911..8b8f00dac0f8 100644 --- a/drivers/nfc/nfcmrvl/spi.c +++ b/drivers/nfc/nfcmrvl/spi.c @@ -192,7 +192,7 @@ static const struct of_device_id of_nfcmrvl_spi_match[] = { MODULE_DEVICE_TABLE(of, of_nfcmrvl_spi_match); static const struct spi_device_id nfcmrvl_spi_id_table[] = { - { "nfcmrvl_spi" }, + { .name = "nfcmrvl_spi" }, { } }; MODULE_DEVICE_TABLE(spi, nfcmrvl_spi_id_table); diff --git a/drivers/nfc/st-nci/spi.c b/drivers/nfc/st-nci/spi.c index 1bbda3d0a7dc..1b97b2f3f441 100644 --- a/drivers/nfc/st-nci/spi.c +++ b/drivers/nfc/st-nci/spi.c @@ -271,8 +271,8 @@ static void st_nci_spi_remove(struct spi_device *dev) } static struct spi_device_id st_nci_spi_id_table[] = { - { ST_NCI_SPI_DRIVER_NAME }, - { "st21nfcb-spi" }, + { .name = ST_NCI_SPI_DRIVER_NAME }, + { .name = "st21nfcb-spi" }, {} }; MODULE_DEVICE_TABLE(spi, st_nci_spi_id_table); diff --git a/drivers/nfc/st95hf/core.c b/drivers/nfc/st95hf/core.c index 52fe81a557a0..d4e3049d138a 100644 --- a/drivers/nfc/st95hf/core.c +++ b/drivers/nfc/st95hf/core.c @@ -1049,7 +1049,7 @@ static const struct nfc_digital_ops st95hf_nfc_digital_ops = { }; static const struct spi_device_id st95hf_id[] = { - { "st95hf" }, + { .name = "st95hf" }, {} }; MODULE_DEVICE_TABLE(spi, st95hf_id); diff --git a/drivers/nfc/trf7970a.c b/drivers/nfc/trf7970a.c index bb3f83adf7db..b9ea2b61c588 100644 --- a/drivers/nfc/trf7970a.c +++ b/drivers/nfc/trf7970a.c @@ -2310,7 +2310,7 @@ static const struct of_device_id trf7970a_of_match[] = { MODULE_DEVICE_TABLE(of, trf7970a_of_match); static const struct spi_device_id trf7970a_id_table[] = { - { "trf7970a" }, + { .name = "trf7970a" }, {} }; From ebfff6a5e512c92c436a70d91772413534500c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 3 Jul 2026 17:46:25 +0200 Subject: [PATCH 1220/1433] nfc: Unify style of spi_device_id arrays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unify the style of the list terminator in spi_device_id arrays, that is use a single space between { and }. This is the most common and generally recommended style for these. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/78d632098fd42dbf2846cb89d66ec83bb9e1de99.1783091699.git.u.kleine-koenig@baylibre.com Signed-off-by: David Heidelberg --- drivers/nfc/st-nci/spi.c | 2 +- drivers/nfc/st95hf/core.c | 2 +- drivers/nfc/trf7970a.c | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/nfc/st-nci/spi.c b/drivers/nfc/st-nci/spi.c index 1b97b2f3f441..7948c7e0c88c 100644 --- a/drivers/nfc/st-nci/spi.c +++ b/drivers/nfc/st-nci/spi.c @@ -273,7 +273,7 @@ static void st_nci_spi_remove(struct spi_device *dev) static struct spi_device_id st_nci_spi_id_table[] = { { .name = ST_NCI_SPI_DRIVER_NAME }, { .name = "st21nfcb-spi" }, - {} + { } }; MODULE_DEVICE_TABLE(spi, st_nci_spi_id_table); diff --git a/drivers/nfc/st95hf/core.c b/drivers/nfc/st95hf/core.c index d4e3049d138a..321fbe8aeca8 100644 --- a/drivers/nfc/st95hf/core.c +++ b/drivers/nfc/st95hf/core.c @@ -1050,7 +1050,7 @@ static const struct nfc_digital_ops st95hf_nfc_digital_ops = { static const struct spi_device_id st95hf_id[] = { { .name = "st95hf" }, - {} + { } }; MODULE_DEVICE_TABLE(spi, st95hf_id); diff --git a/drivers/nfc/trf7970a.c b/drivers/nfc/trf7970a.c index b9ea2b61c588..60883001fa5d 100644 --- a/drivers/nfc/trf7970a.c +++ b/drivers/nfc/trf7970a.c @@ -2311,9 +2311,8 @@ MODULE_DEVICE_TABLE(of, trf7970a_of_match); static const struct spi_device_id trf7970a_id_table[] = { { .name = "trf7970a" }, - {} + { } }; - MODULE_DEVICE_TABLE(spi, trf7970a_id_table); static struct spi_driver trf7970a_spi_driver = { From 4f3436117435d853057b608fa0ba5c1c204956be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 3 Jul 2026 17:46:26 +0200 Subject: [PATCH 1221/1433] nfc: Unify style of usb_device_id arrays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The usual coding style is to skip the comma after a initializer iff the closing } is on the same line. Also there is usually no empty line between the array and the MODULE_DEVICE_TABLE() macro. Adapt two drivers accordingly to match this common style. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/8a186cb0376deb3d4f4264e6ed351562b79bb53d.1783091699.git.u.kleine-koenig@baylibre.com Signed-off-by: David Heidelberg --- drivers/nfc/nfcmrvl/usb.c | 1 - drivers/nfc/port100.c | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/nfc/nfcmrvl/usb.c b/drivers/nfc/nfcmrvl/usb.c index 4babde8e4249..c7f2afe00b93 100644 --- a/drivers/nfc/nfcmrvl/usb.c +++ b/drivers/nfc/nfcmrvl/usb.c @@ -17,7 +17,6 @@ static struct usb_device_id nfcmrvl_table[] = { USB_CLASS_VENDOR_SPEC, 4, 1) }, { } /* Terminating entry */ }; - MODULE_DEVICE_TABLE(usb, nfcmrvl_table); #define NFCMRVL_USB_BULK_RUNNING 1 diff --git a/drivers/nfc/port100.c b/drivers/nfc/port100.c index 5ae61d7ebcfe..b613f5e2fd57 100644 --- a/drivers/nfc/port100.c +++ b/drivers/nfc/port100.c @@ -1480,8 +1480,8 @@ static const struct nfc_digital_ops port100_digital_ops = { }; static const struct usb_device_id port100_table[] = { - { USB_DEVICE(SONY_VENDOR_ID, RCS380S_PRODUCT_ID), }, - { USB_DEVICE(SONY_VENDOR_ID, RCS380P_PRODUCT_ID), }, + { USB_DEVICE(SONY_VENDOR_ID, RCS380S_PRODUCT_ID) }, + { USB_DEVICE(SONY_VENDOR_ID, RCS380P_PRODUCT_ID) }, { } }; MODULE_DEVICE_TABLE(usb, port100_table); From 11a8f09e7f2313383851453285479f5c6843b39d Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Mon, 20 Jul 2026 12:06:59 +0200 Subject: [PATCH 1222/1433] MAINTAINERS: Add Matrix channel to the NFC subsystem Community hang out there. Signed-off-by: David Heidelberg --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 8014b9f8253e..728228f1147e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19100,6 +19100,7 @@ F: include/net/net_failover.h NFC SUBSYSTEM M: David Heidelberg L: oe-linux-nfc@lists.linux.dev +C: https://matrix.to/#/#linux-nfc:ixit.cz S: Maintained T: git https://codeberg.org/linux-nfc/linux.git F: Documentation/devicetree/bindings/net/nfc/ From 9f69d05b5a85c417c73fa2d5c7a2d507ac81cf4b Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Fri, 24 Jul 2026 16:00:15 -0700 Subject: [PATCH 1223/1433] nfc: st95hf: switch to using sleeping variants of gpiod API The driver does not use gpiod API calls in an atomic context. Switch to gpiod_set_value_cansleep() calls to allow using the driver with GPIO controllers that might need process context to operate. Signed-off-by: Dmitry Torokhov Link: https://patch.msgid.link/amPsnh9wDIG2CeSi@google.com Signed-off-by: David Heidelberg --- drivers/nfc/st95hf/core.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/nfc/st95hf/core.c b/drivers/nfc/st95hf/core.c index 321fbe8aeca8..4d772a308bff 100644 --- a/drivers/nfc/st95hf/core.c +++ b/drivers/nfc/st95hf/core.c @@ -450,19 +450,19 @@ static int st95hf_select_protocol(struct st95hf_context *stcontext, int type) static void st95hf_send_st95enable_negativepulse(struct st95hf_context *st95con) { /* First make irq_in pin high */ - gpiod_set_value(st95con->enable_gpiod, HIGH); + gpiod_set_value_cansleep(st95con->enable_gpiod, HIGH); /* wait for 1 milisecond */ usleep_range(1000, 2000); /* Make irq_in pin low */ - gpiod_set_value(st95con->enable_gpiod, LOW); + gpiod_set_value_cansleep(st95con->enable_gpiod, LOW); /* wait for minimum interrupt pulse to make st95 active */ usleep_range(1000, 2000); /* At end make it high */ - gpiod_set_value(st95con->enable_gpiod, HIGH); + gpiod_set_value_cansleep(st95con->enable_gpiod, HIGH); } /* From 6959fbdc940f62d8eef2a171d3a3342d7c248855 Mon Sep 17 00:00:00 2001 From: Przemyslaw Korba Date: Fri, 5 Jun 2026 14:06:26 +0200 Subject: [PATCH 1224/1433] ice: fall back to SBQ when LL PHY timer interface times out The low-latency (LL) PHY timer interface relies on a tight, atomic poll of the PF_SB_ATQBAL register with a 2ms timeout. After an NVM update / EMPR, FW may need significantly longer than 2ms to start responding to ATQBAL commands. The first PHY adjust or incval write issued by ice_ptp_rebuild_owner() fails with -ETIMEDOUT. Fix this by falling back to the existing SBQ-based PHY register write path when LL times out. This makes sure PTP is initialized when FW takes longer than expected to come back online. Steps to reproduce: ./nvmupdate64e -if devlink -f Update E810 card with nvmupdate64e, and observe dmesg errors: Failed to write PHC increment value, status -110 PTP reset failed, error: -110 (-ETIMEDOUT) Fixes: ef9a64c07294 ("ice: implement low latency PHY timer updates") Signed-off-by: Przemyslaw Korba Reviewed-by: Simon Horman Tested-by: Rinitha S (A Contingent worker at Intel) Reviewed-by: Aleksandr Loktionov Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 38 +++++++++++---------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c index 8e5f97835954..3a41c711e751 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c +++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c @@ -4808,15 +4808,12 @@ static int ice_ptp_prep_phy_adj_ll_e810(struct ice_hw *hw, s32 adj) !FIELD_GET(REG_LL_PROXY_H_EXEC, val), 10, REG_LL_PROXY_H_TIMEOUT_US, false, hw, REG_LL_PROXY_H); - if (err) { - ice_debug(hw, ICE_DBG_PTP, "Failed to prepare PHY timer adjustment using low latency interface\n"); - spin_unlock_irq(¶ms->atqbal_wq.lock); - return err; - } - spin_unlock_irq(¶ms->atqbal_wq.lock); - return 0; + if (err) + ice_debug(hw, ICE_DBG_PTP, "Failed to prepare PHY timer adjustment using low latency interface\n"); + + return err; } /** @@ -4837,8 +4834,12 @@ static int ice_ptp_prep_phy_adj_e810(struct ice_hw *hw, s32 adj) u8 tmr_idx; int err; - if (hw->dev_caps.ts_dev_info.ll_phy_tmr_update) - return ice_ptp_prep_phy_adj_ll_e810(hw, adj); + if (hw->dev_caps.ts_dev_info.ll_phy_tmr_update) { + err = ice_ptp_prep_phy_adj_ll_e810(hw, adj); + if (err != -ETIMEDOUT) + return err; + ice_debug(hw, ICE_DBG_PTP, "LL adj timed out, falling back to SBQ\n"); + } tmr_idx = hw->func_caps.ts_func_info.tmr_index_owned; @@ -4901,15 +4902,12 @@ static int ice_ptp_prep_phy_incval_ll_e810(struct ice_hw *hw, u64 incval) !FIELD_GET(REG_LL_PROXY_H_EXEC, val), 10, REG_LL_PROXY_H_TIMEOUT_US, false, hw, REG_LL_PROXY_H); - if (err) { - ice_debug(hw, ICE_DBG_PTP, "Failed to prepare PHY timer increment using low latency interface\n"); - spin_unlock_irq(¶ms->atqbal_wq.lock); - return err; - } - spin_unlock_irq(¶ms->atqbal_wq.lock); - return 0; + if (err) + ice_debug(hw, ICE_DBG_PTP, "Failed to prepare PHY timer increment using low latency interface\n"); + + return err; } /** @@ -4927,8 +4925,12 @@ static int ice_ptp_prep_phy_incval_e810(struct ice_hw *hw, u64 incval) u8 tmr_idx; int err; - if (hw->dev_caps.ts_dev_info.ll_phy_tmr_update) - return ice_ptp_prep_phy_incval_ll_e810(hw, incval); + if (hw->dev_caps.ts_dev_info.ll_phy_tmr_update) { + err = ice_ptp_prep_phy_incval_ll_e810(hw, incval); + if (err != -ETIMEDOUT) + return err; + ice_debug(hw, ICE_DBG_PTP, "LL incval timed out, falling back to SBQ\n"); + } tmr_idx = hw->func_caps.ts_func_info.tmr_index_owned; low = lower_32_bits(incval); From d04287e27bf1c0b879a10d929c163f2da69715b6 Mon Sep 17 00:00:00 2001 From: Petr Oros Date: Mon, 22 Jun 2026 10:10:30 +0200 Subject: [PATCH 1225/1433] ice: clear the default forwarding VSI rule when releasing a VSI When a VSI is configured as the switch's default forwarding VSI (ICE_SW_LKUP_DFLT) and is then torn down, the rule is left behind in the switch. ice_vsi_release() no longer removes it, and the SR-IOV VF free path (ice_free_vfs() -> ice_free_vf_res() -> ice_vf_vsi_release() -> ice_vsi_release()) does not disable promiscuous mode either, which only happens on VF reset in ice_vf_clear_all_promisc_modes(). A trusted VF that enters unicast promiscuous mode becomes the default forwarding VSI (this is the default mode, when the PF does not have VF true-promiscuous mode enabled). If the VFs are then destroyed without the VF first leaving promiscuous mode, the ICE_SW_LKUP_DFLT rule for the now-freed VSI is leaked. When VFs are recreated, a VSI reuses the freed hw_vsi_id. If it is assigned a different VSI handle than the leaked rule holds, ice_set_dflt_vsi() does not recognize it as already-default, and ice_add_update_vsi_list() folds the dangling (freed) handle into a VSI list, which the firmware rejects. The VSI handle assigned on re-creation varies, so the failure is intermittent rather than every cycle. Reproduce by repeatedly running the cycle below on the two ports of the same card, where $VF0 and $VF1 are the netdevs of vf 15 once they appear. The VF must be brought up so iavf actually pushes the unicast promiscuous request, and the rule must settle before the VFs are torn down again: echo 16 > /sys/class/net/$PF0/device/sriov_numvfs echo 16 > /sys/class/net/$PF1/device/sriov_numvfs ip link set $PF0 vf 15 trust on ip link set $PF1 vf 15 trust on ip link set $VF0 up ip link set $VF1 up ip link set $VF0 promisc on ip link set $VF1 promisc on sleep 1 echo 0 > /sys/class/net/$PF0/device/sriov_numvfs echo 0 > /sys/class/net/$PF1/device/sriov_numvfs Within a few cycles the ice PF and iavf VF log: Failed to set VSI 25 as the default forwarding VSI, error -22 Turning on/off promiscuous mode for VF 63 failed, error: -22 PF returned error -53 (IAVF_ERR_ADMIN_QUEUE_ERROR) to our request 14 This cleanup used to live in ice_vsi_release() but was dropped by the referenced refactor. Restore it. Clear the default forwarding VSI rule in ice_vsi_release() when this VSI owns it, which covers every teardown path. Fixes: 6624e780a577 ("ice: split ice_vsi_setup into smaller functions") Signed-off-by: Petr Oros Reviewed-by: Marcin Szycik Tested-by: Rafal Romanowski Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/ice/ice_lib.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/intel/ice/ice_lib.c b/drivers/net/ethernet/intel/ice/ice_lib.c index 8cdc4fda89e9..9e08db376d3d 100644 --- a/drivers/net/ethernet/intel/ice/ice_lib.c +++ b/drivers/net/ethernet/intel/ice/ice_lib.c @@ -2871,6 +2871,9 @@ int ice_vsi_release(struct ice_vsi *vsi) return -ENODEV; pf = vsi->back; + if (ice_is_vsi_dflt_vsi(vsi)) + ice_clear_dflt_vsi(vsi); + if (test_bit(ICE_FLAG_RSS_ENA, pf->flags)) ice_rss_clean(vsi); From df88d6f1ed653993bd5c8647aef0e6498f4b1647 Mon Sep 17 00:00:00 2001 From: Robert Malz Date: Tue, 4 Aug 2026 10:35:36 +0200 Subject: [PATCH 1226/1433] ice: acquire NVM lock around each flash read FW caps the NVM read lock at a maximum of 3000ms regardless of the timeout requested via ice_acquire_nvm(). ice_read_flat_nvm() splits a read into multiple ice_aq_read_nvm() commands, one per 4KB sector, all issued under a single lock taken by the caller. Reading a large region can exceed 3000ms, so FW reclaims the lock mid-read and the remaining commands might fail. Move the lock acquire/release into ice_read_flat_nvm() so it brackets each individual ice_aq_read_nvm() command, ensuring the lock is never held across more than one FW read. ice_release_nvm() issues its own AQ command and overwrites hw->adminq.sq_last_status, which some callers inspect after a failed read. Add an optional read_aq_err output parameter to ice_read_flat_nvm() to capture the failing read's AQ error before the release; callers that need it (ice_discover_flash_size() and the ethtool/devlink log paths) use it instead of sq_last_status, others pass NULL. Callers that previously took the lock around ice_read_flat_nvm(), ice_read_sr_word() or ice_read_flash_module() now call them without it. The now-redundant per-block locking in ice_devlink_nvm_snapshot() is dropped. ice_read_sr_word() is now a thin wrapper, so ice_read_sr_word_aq() is folded into it. Fixes: e94509906d6b ("ice: create function to read a section of the NVM and Shadow RAM") Signed-off-by: Robert Malz Reviewed-by: Przemek Kitszel Reviewed-by: Marcin Szycik Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen --- .../net/ethernet/intel/ice/devlink/devlink.c | 32 ++----- drivers/net/ethernet/intel/ice/ice_ethtool.c | 18 ++-- drivers/net/ethernet/intel/ice/ice_nvm.c | 90 ++++++++++--------- drivers/net/ethernet/intel/ice/ice_nvm.h | 2 +- 4 files changed, 59 insertions(+), 83 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/devlink/devlink.c b/drivers/net/ethernet/intel/ice/devlink/devlink.c index 22b7d8e6bd9e..8c2b63eef82b 100644 --- a/drivers/net/ethernet/intel/ice/devlink/devlink.c +++ b/drivers/net/ethernet/intel/ice/devlink/devlink.c @@ -1890,27 +1890,18 @@ static int ice_devlink_nvm_snapshot(struct devlink *devlink, */ for (i = 0; i < num_blks; i++) { u32 read_sz = min_t(u32, ICE_DEVLINK_READ_BLK_SIZE, left); - - status = ice_acquire_nvm(hw, ICE_RES_READ); - if (status) { - dev_dbg(dev, "ice_acquire_nvm failed, err %d aq_err %d\n", - status, hw->adminq.sq_last_status); - NL_SET_ERR_MSG_MOD(extack, "Failed to acquire NVM semaphore"); - vfree(nvm_data); - return -EIO; - } + enum libie_aq_err read_aq_err = LIBIE_AQ_RC_OK; status = ice_read_flat_nvm(hw, i * ICE_DEVLINK_READ_BLK_SIZE, - &read_sz, tmp, read_shadow_ram); + &read_sz, tmp, read_shadow_ram, + &read_aq_err); if (status) { dev_dbg(dev, "ice_read_flat_nvm failed after reading %u bytes, err %d aq_err %d\n", - read_sz, status, hw->adminq.sq_last_status); + read_sz, status, read_aq_err); NL_SET_ERR_MSG_MOD(extack, "Failed to read NVM contents"); - ice_release_nvm(hw); vfree(nvm_data); return -EIO; } - ice_release_nvm(hw); tmp += read_sz; left -= read_sz; @@ -1943,6 +1934,7 @@ static int ice_devlink_nvm_read(struct devlink *devlink, struct netlink_ext_ack *extack, u64 offset, u32 size, u8 *data) { + enum libie_aq_err read_aq_err = LIBIE_AQ_RC_OK; struct ice_pf *pf = devlink_priv(devlink); struct device *dev = ice_pf_to_dev(pf); struct ice_hw *hw = &pf->hw; @@ -1966,24 +1958,14 @@ static int ice_devlink_nvm_read(struct devlink *devlink, return -ERANGE; } - status = ice_acquire_nvm(hw, ICE_RES_READ); - if (status) { - dev_dbg(dev, "ice_acquire_nvm failed, err %d aq_err %d\n", - status, hw->adminq.sq_last_status); - NL_SET_ERR_MSG_MOD(extack, "Failed to acquire NVM semaphore"); - return -EIO; - } - status = ice_read_flat_nvm(hw, (u32)offset, &size, data, - read_shadow_ram); + read_shadow_ram, &read_aq_err); if (status) { dev_dbg(dev, "ice_read_flat_nvm failed after reading %u bytes, err %d aq_err %d\n", - size, status, hw->adminq.sq_last_status); + size, status, read_aq_err); NL_SET_ERR_MSG_MOD(extack, "Failed to read NVM contents"); - ice_release_nvm(hw); return -EIO; } - ice_release_nvm(hw); return 0; } diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool.c b/drivers/net/ethernet/intel/ice/ice_ethtool.c index 7eb380be7ed2..bf9a821c543b 100644 --- a/drivers/net/ethernet/intel/ice/ice_ethtool.c +++ b/drivers/net/ethernet/intel/ice/ice_ethtool.c @@ -853,6 +853,7 @@ static int ice_get_eeprom(struct net_device *netdev, struct ethtool_eeprom *eeprom, u8 *bytes) { + enum libie_aq_err read_aq_err = LIBIE_AQ_RC_OK; struct ice_pf *pf = ice_netdev_to_pf(netdev); struct ice_hw *hw = &pf->hw; struct device *dev; @@ -869,24 +870,15 @@ ice_get_eeprom(struct net_device *netdev, struct ethtool_eeprom *eeprom, if (!buf) return -ENOMEM; - ret = ice_acquire_nvm(hw, ICE_RES_READ); + ret = ice_read_flat_nvm(hw, eeprom->offset, &eeprom->len, buf, + false, &read_aq_err); if (ret) { - dev_err(dev, "ice_acquire_nvm failed, err %d aq_err %s\n", - ret, libie_aq_str(hw->adminq.sq_last_status)); + dev_err(dev, "ice_read_flat_nvm failed, err %d aq_err %s\n", + ret, libie_aq_str(read_aq_err)); goto out; } - ret = ice_read_flat_nvm(hw, eeprom->offset, &eeprom->len, buf, - false); - if (ret) { - dev_err(dev, "ice_read_flat_nvm failed, err %d aq_err %s\n", - ret, libie_aq_str(hw->adminq.sq_last_status)); - goto release; - } - memcpy(bytes, buf, eeprom->len); -release: - ice_release_nvm(hw); out: kfree(buf); return ret; diff --git a/drivers/net/ethernet/intel/ice/ice_nvm.c b/drivers/net/ethernet/intel/ice/ice_nvm.c index 7e187a804dfa..21f3b615dbbf 100644 --- a/drivers/net/ethernet/intel/ice/ice_nvm.c +++ b/drivers/net/ethernet/intel/ice/ice_nvm.c @@ -53,17 +53,27 @@ int ice_aq_read_nvm(struct ice_hw *hw, u16 module_typeid, u32 offset, * @length: (in) number of bytes to read; (out) number of bytes actually read * @data: buffer to return data in (sized to fit the specified length) * @read_shadow_ram: if true, read from shadow RAM instead of NVM + * @read_aq_err: if non-NULL, receives the AQ error status of the failing read * * Reads a portion of the NVM, as a flat memory space. This function correctly * breaks read requests across Shadow RAM sectors and ensures that no single * read request exceeds the maximum 4KB read for a single AdminQ command. * + * FW caps the read lock at a maximum of 3000ms, so a read spanning multiple + * 4KB sectors cannot be done under a single lock without FW reclaiming it + * mid-read. The NVM lock is therefore acquired and released around each AQ + * read, so this function must be called without the lock held. + * + * Since ice_release_nvm() issues an AQ command that overwrites + * hw->adminq.sq_last_status, callers that need the failing read's AQ error + * must use @read_aq_err rather than inspecting sq_last_status afterwards. + * * Returns a status code on failure. Note that the data pointer may be * partially updated if some reads succeed before a failure. */ int ice_read_flat_nvm(struct ice_hw *hw, u32 offset, u32 *length, u8 *data, - bool read_shadow_ram) + bool read_shadow_ram, enum libie_aq_err *read_aq_err) { u32 inlen = *length; u32 bytes_read = 0; @@ -92,12 +102,30 @@ ice_read_flat_nvm(struct ice_hw *hw, u32 offset, u32 *length, u8 *data, last_cmd = !(bytes_read + read_size < inlen); + status = ice_acquire_nvm(hw, ICE_RES_READ); + if (status) { + ice_debug(hw, ICE_DBG_NVM, "Failed to acquire NVM lock, err %d aq_err %s\n", + status, libie_aq_str(hw->adminq.sq_last_status)); + break; + } + status = ice_aq_read_nvm(hw, ICE_AQC_NVM_START_POINT, offset, read_size, data + bytes_read, last_cmd, read_shadow_ram, NULL); - if (status) + if (status) { + /* Capture the read's AQ error before ice_release_nvm() + * issues its own AQ command and overwrites + * sq_last_status. + */ + if (read_aq_err) + *read_aq_err = hw->adminq.sq_last_status; + + ice_release_nvm(hw); break; + } + + ice_release_nvm(hw); bytes_read += read_size; offset += read_size; @@ -177,14 +205,19 @@ int ice_aq_erase_nvm(struct ice_hw *hw, u16 module_typeid, struct ice_sq_cd *cd) } /** - * ice_read_sr_word_aq - Reads Shadow RAM via AQ + * ice_read_sr_word - Reads Shadow RAM word * @hw: pointer to the HW structure * @offset: offset of the Shadow RAM word to read (0x000000 - 0x001FFF) * @data: word read from the Shadow RAM * * Reads one 16 bit word from the Shadow RAM using ice_read_flat_nvm. + * + * The NVM lock is acquired and released internally by ice_read_flat_nvm() + * around the FW read, so this function must be called without the lock held. + * + * Return: zero on success, or a negative error code on failure. */ -static int ice_read_sr_word_aq(struct ice_hw *hw, u16 offset, u16 *data) +int ice_read_sr_word(struct ice_hw *hw, u16 offset, u16 *data) { u32 bytes = sizeof(u16); __le16 data_local; @@ -194,7 +227,7 @@ static int ice_read_sr_word_aq(struct ice_hw *hw, u16 offset, u16 *data) * Shadow RAM sector restrictions necessary when reading from the NVM. */ status = ice_read_flat_nvm(hw, offset * sizeof(u16), &bytes, - (__force u8 *)&data_local, true); + (__force u8 *)&data_local, true, NULL); if (status) return status; @@ -330,13 +363,8 @@ ice_read_flash_module(struct ice_hw *hw, enum ice_bank_select bank, u16 module, return -EINVAL; } - status = ice_acquire_nvm(hw, ICE_RES_READ); - if (status) - return status; - - status = ice_read_flat_nvm(hw, start + offset, &length, data, false); - - ice_release_nvm(hw); + status = ice_read_flat_nvm(hw, start + offset, &length, data, false, + NULL); return status; } @@ -418,27 +446,6 @@ ice_read_netlist_module(struct ice_hw *hw, enum ice_bank_select bank, u32 offset return status; } -/** - * ice_read_sr_word - Reads Shadow RAM word and acquire NVM if necessary - * @hw: pointer to the HW structure - * @offset: offset of the Shadow RAM word to read (0x000000 - 0x001FFF) - * @data: word read from the Shadow RAM - * - * Reads one 16 bit word from the Shadow RAM using the ice_read_sr_word_aq. - */ -int ice_read_sr_word(struct ice_hw *hw, u16 offset, u16 *data) -{ - int status; - - status = ice_acquire_nvm(hw, ICE_RES_READ); - if (!status) { - status = ice_read_sr_word_aq(hw, offset, data); - ice_release_nvm(hw); - } - - return status; -} - /** * ice_get_pfa_module_tlv - Reads sub module TLV from NVM PFA * @hw: pointer to hardware structure @@ -856,20 +863,18 @@ int ice_get_inactive_netlist_ver(struct ice_hw *hw, struct ice_netlist_info *net static int ice_discover_flash_size(struct ice_hw *hw) { u32 min_size = 0, max_size = ICE_AQC_NVM_MAX_OFFSET + 1; - int status; - - status = ice_acquire_nvm(hw, ICE_RES_READ); - if (status) - return status; + int status = 0; while ((max_size - min_size) > 1) { + enum libie_aq_err read_aq_err = LIBIE_AQ_RC_OK; u32 offset = (max_size + min_size) / 2; u32 len = 1; u8 data; - status = ice_read_flat_nvm(hw, offset, &len, &data, false); + status = ice_read_flat_nvm(hw, offset, &len, &data, false, + &read_aq_err); if (status == -EIO && - hw->adminq.sq_last_status == LIBIE_AQ_RC_EINVAL) { + read_aq_err == LIBIE_AQ_RC_EINVAL) { ice_debug(hw, ICE_DBG_NVM, "%s: New upper bound of %u bytes\n", __func__, offset); status = 0; @@ -880,7 +885,7 @@ static int ice_discover_flash_size(struct ice_hw *hw) min_size = offset; } else { /* an unexpected error occurred */ - goto err_read_flat_nvm; + return status; } } @@ -888,9 +893,6 @@ static int ice_discover_flash_size(struct ice_hw *hw) hw->flash.flash_size = max_size; -err_read_flat_nvm: - ice_release_nvm(hw); - return status; } diff --git a/drivers/net/ethernet/intel/ice/ice_nvm.h b/drivers/net/ethernet/intel/ice/ice_nvm.h index 63cdc6bdac58..e1d1a11f5ca4 100644 --- a/drivers/net/ethernet/intel/ice/ice_nvm.h +++ b/drivers/net/ethernet/intel/ice/ice_nvm.h @@ -19,7 +19,7 @@ int ice_aq_read_nvm(struct ice_hw *hw, u16 module_typeid, u32 offset, bool read_shadow_ram, struct ice_sq_cd *cd); int ice_read_flat_nvm(struct ice_hw *hw, u32 offset, u32 *length, u8 *data, - bool read_shadow_ram); + bool read_shadow_ram, enum libie_aq_err *read_aq_err); int ice_get_pfa_module_tlv(struct ice_hw *hw, u16 *module_tlv, u16 *module_tlv_len, u16 module_type); From b802a8c1ca16f9490fa0cb3110c00c10d52b392d Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Mon, 3 Aug 2026 17:06:23 -0400 Subject: [PATCH 1227/1433] idpf: add missing cpu_to_le32 in idpf_tx_splitq_build_flow_desc idpf_tx_splitq_build_flow_desc performs a 32-bit store to &cmd_dtype to set the 8-bit cmd_dtype and zero the adjacent 3-byte timestamp field in a single operation. Descriptors are in little endian. Add missing cpu_to_le32 and cast to __le32 to ensure the fields are written correctly also on big endian platforms. Fixes: 1a49cf814fe1 ("idpf: add Tx timestamp flows") Signed-off-by: Willem de Bruijn Reviewed-by: Jason Xing Reviewed-by: Aleksandr Loktionov Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/idpf/idpf_txrx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.c b/drivers/net/ethernet/intel/idpf/idpf_txrx.c index c724d429a7aa..91ca75e45463 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.c +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.c @@ -2408,7 +2408,7 @@ void idpf_tx_splitq_build_flow_desc(union idpf_tx_flex_desc *desc, struct idpf_tx_splitq_params *params, u16 td_cmd, u16 size) { - *(u32 *)&desc->flow.qw1.cmd_dtype = (u8)(params->dtype | td_cmd); + *(__le32 *)&desc->flow.qw1.cmd_dtype = cpu_to_le32((u8)(params->dtype | td_cmd)); desc->flow.qw1.rxr_bufsize = cpu_to_le16((u16)size); desc->flow.qw1.compl_tag = cpu_to_le16(params->compl_tag); } From e6802833990725e855f1a4bb78b08ad67b2599a2 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 10 Aug 2026 11:01:48 -0700 Subject: [PATCH 1228/1433] MAINTAINERS: make Tung an official TIPC maintainer Tung Quang Nguyen has been working as the de facto TIPC maintainer for a few years now. Make sure the MAINTAINERS file reflects this reality. Dealing with the flood of AI patches is a significant effort, and Tung's work and responsiveness is exemplary. Link: https://patch.msgid.link/20260810180148.680425-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index a82b6ec9c567..ee4a9da0eb83 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -27257,6 +27257,7 @@ F: tools/testing/selftests/timers/ TIPC NETWORK LAYER M: Jon Maloy +M: Tung Quang Nguyen L: netdev@vger.kernel.org (core kernel code) L: tipc-discussion@lists.sourceforge.net (user apps, general discussion) S: Maintained From 6266eeb24fd7028f1ae871e7592a7c1b150629aa Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Mon, 10 Aug 2026 16:37:05 +0200 Subject: [PATCH 1229/1433] MAINTAINERS: add myself as QCA8K maintainer List all the files of the QCA8K DSA Switch driver and add myself as maintainer. Signed-off-by: Christian Marangi Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260810143740.652804-1-ansuelsmth@gmail.com Signed-off-by: Jakub Kicinski --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index ee4a9da0eb83..e0e7fae5b92f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22108,6 +22108,14 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/ath/ath.git F: Documentation/devicetree/bindings/net/wireless/qca,ath9k.yaml F: drivers/net/wireless/ath/ath9k/ +QUALCOMM ATHEROS QCA8K DSA SWITCH DRIVER +M: Christian Marangi +L: netdev@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/net/dsa/qca8k.yaml +F: drivers/net/dsa/qca/qca8k* +F: net/dsa/tag_qca.c + QUALCOMM ATHEROS QCA7K ETHERNET DRIVER M: Stefan Wahren L: netdev@vger.kernel.org From 26ba30221c03364d6ed9910be8da4c1fd871b07b Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Thu, 6 Aug 2026 10:30:36 +0300 Subject: [PATCH 1230/1433] devlink: add generic device max_sfs parameter Add a new generic devlink device parameter (max_sfs) to control if and how many light-weight NIC subfunctions can be created. Subfunctions are a light-weight network functions backed by an underlying PCI function. Their lifecycle can already be managed by devlink, but currently users cannot enable them in the device. They can be enabled/disabled only via external vendor tools. This parameter allows subfunctions to be enabled (>0) or disabled (0) via devlink. A subsequent patch will add support for max_sfs to the mlx5 driver. Signed-off-by: Nikolay Aleksandrov Reviewed-by: David Ahern Reviewed-by: Jiri Pirko Reviewed-by: Aleksandr Loktionov Reviewed-by: Alexander Lobakin Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260806073037.3001886-2-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- Documentation/networking/devlink/devlink-params.rst | 6 ++++++ include/net/devlink.h | 4 ++++ net/devlink/param.c | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/Documentation/networking/devlink/devlink-params.rst b/Documentation/networking/devlink/devlink-params.rst index ca19ee3e63c8..13eb8aa84c38 100644 --- a/Documentation/networking/devlink/devlink-params.rst +++ b/Documentation/networking/devlink/devlink-params.rst @@ -165,3 +165,9 @@ own name. - u32 - Controls the maximum number of MAC address filters that can be assigned to a Virtual Function (VF). + * - ``max_sfs`` + - u32 + - The maximum number of subfunctions which can be created on the device. + Modifying this parameter may require a device restart and PCI bus + rescanning because the BAR layout may change. A value of 0 disables + subfunction creation. diff --git a/include/net/devlink.h b/include/net/devlink.h index 4830aba4087a..7abd23376319 100644 --- a/include/net/devlink.h +++ b/include/net/devlink.h @@ -554,6 +554,7 @@ enum devlink_param_generic_id { DEVLINK_PARAM_GENERIC_ID_TOTAL_VFS, DEVLINK_PARAM_GENERIC_ID_NUM_DOORBELLS, DEVLINK_PARAM_GENERIC_ID_MAX_MAC_PER_VF, + DEVLINK_PARAM_GENERIC_ID_MAX_SFS, /* add new param generic ids above here*/ __DEVLINK_PARAM_GENERIC_ID_MAX, @@ -627,6 +628,9 @@ enum devlink_param_generic_id { #define DEVLINK_PARAM_GENERIC_MAX_MAC_PER_VF_NAME "max_mac_per_vf" #define DEVLINK_PARAM_GENERIC_MAX_MAC_PER_VF_TYPE DEVLINK_PARAM_TYPE_U32 +#define DEVLINK_PARAM_GENERIC_MAX_SFS_NAME "max_sfs" +#define DEVLINK_PARAM_GENERIC_MAX_SFS_TYPE DEVLINK_PARAM_TYPE_U32 + #define DEVLINK_PARAM_GENERIC(_id, _cmodes, _get, _set, _validate) \ { \ .id = DEVLINK_PARAM_GENERIC_ID_##_id, \ diff --git a/net/devlink/param.c b/net/devlink/param.c index 1cc562a6ebfd..8ca0f3ed646c 100644 --- a/net/devlink/param.c +++ b/net/devlink/param.c @@ -117,6 +117,11 @@ static const struct devlink_param devlink_param_generic[] = { .name = DEVLINK_PARAM_GENERIC_MAX_MAC_PER_VF_NAME, .type = DEVLINK_PARAM_GENERIC_MAX_MAC_PER_VF_TYPE, }, + { + .id = DEVLINK_PARAM_GENERIC_ID_MAX_SFS, + .name = DEVLINK_PARAM_GENERIC_MAX_SFS_NAME, + .type = DEVLINK_PARAM_GENERIC_MAX_SFS_TYPE, + }, }; static int devlink_param_generic_verify(const struct devlink_param *param) From 38c35fdd801eaa67b83ea3b4d317f3fd2b87de63 Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Thu, 6 Aug 2026 10:30:37 +0300 Subject: [PATCH 1231/1433] net/mlx5: implement max_sfs parameter Implement max_sfs generic parameter to allow users to control the total light-weight NIC subfunctions that can be created using devlink instead of external vendor tools. A value of 0 will effectively disable creation of new subfunction devices. A warning is sent to user-space via extack (returning extack without error code is interpreted as a warning by user-space tools). The maximum value is capped at U16_MAX. Signed-off-by: Nikolay Aleksandrov Reviewed-by: David Ahern Reviewed-by: Alexander Lobakin Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260806073037.3001886-3-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- Documentation/networking/devlink/mlx5.rst | 7 +- .../mellanox/mlx5/core/lib/nv_param.c | 125 +++++++++++++++++- 2 files changed, 128 insertions(+), 4 deletions(-) diff --git a/Documentation/networking/devlink/mlx5.rst b/Documentation/networking/devlink/mlx5.rst index cf1dffa67669..9421c89951a0 100644 --- a/Documentation/networking/devlink/mlx5.rst +++ b/Documentation/networking/devlink/mlx5.rst @@ -45,8 +45,13 @@ Parameters - The range is between 1 and a device-specific max. - Applies to each physical function (PF) independently, if the device supports it. Otherwise, it applies symmetrically to all PFs. + * - ``max_sfs`` + - permanent + - The range is between 0 and a device-specific max. + - Applies to each physical function (PF) independently. -Note: permanent parameters such as ``enable_sriov`` and ``total_vfs`` require FW reset to take effect +Note: permanent parameters such as ``enable_sriov``, ``total_vfs`` and ``max_sfs`` + require FW reset to take effect .. code-block:: bash diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/nv_param.c b/drivers/net/ethernet/mellanox/mlx5/core/lib/nv_param.c index 4a7275e8b62e..72cc8da5f119 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lib/nv_param.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/nv_param.c @@ -68,7 +68,9 @@ struct mlx5_ifc_mnvda_reg_bits { struct mlx5_ifc_nv_global_pci_conf_bits { u8 sriov_valid[0x1]; - u8 reserved_at_1[0x10]; + u8 reserved_at_1[0xa]; + u8 per_pf_num_sf[0x1]; + u8 reserved_at_c[0x5]; u8 per_pf_total_vf[0x1]; u8 reserved_at_12[0xe]; @@ -93,9 +95,11 @@ struct mlx5_ifc_nv_global_pci_cap_bits { }; struct mlx5_ifc_nv_pf_pci_conf_bits { - u8 reserved_at_0[0x9]; + u8 log_sf_bar_size[0x8]; + u8 pf_total_sf_en[0x1]; u8 pf_total_vf_en[0x1]; - u8 reserved_at_a[0x16]; + u8 reserved_at_a[0x6]; + u8 total_sf[0x10]; u8 reserved_at_20[0x20]; @@ -158,6 +162,8 @@ struct mlx5_ifc_nv_sw_accelerate_conf_bits { #define MLX5_GET_CFG_HDR_LEN(_mnvda_ptr) \ MLX5_GET(mnvda_reg, _mnvda_ptr, configuration_item_header.length) +#define MLX5_DEFAULT_LOG_SF_BAR_SIZE 12 + static int mlx5_nv_param_read(struct mlx5_core_dev *dev, void *mnvda, size_t len) { @@ -755,6 +761,115 @@ static int mlx5_devlink_total_vfs_validate(struct devlink *devlink, u32 id, return 0; } +static int mlx5_devlink_max_sfs_get(struct devlink *devlink, u32 id, + struct devlink_param_gset_ctx *ctx, + struct netlink_ext_ack *extack) +{ + struct mlx5_core_dev *dev = devlink_priv(devlink); + u32 mnvda[MLX5_ST_SZ_DW(mnvda_reg)] = {}; + void *data; + int err; + + err = mlx5_nv_param_read_global_pci_conf(dev, mnvda, sizeof(mnvda)); + if (err) { + NL_SET_ERR_MSG_MOD(extack, + "Failed to read global PCI configuration"); + return err; + } + + data = MLX5_ADDR_OF(mnvda_reg, mnvda, configuration_item_data); + if (!MLX5_GET(nv_global_pci_conf, data, per_pf_num_sf)) { + ctx->val.vu32 = 0; + return 0; + } + + memset(mnvda, 0, sizeof(mnvda)); + err = mlx5_nv_param_read_per_host_pf_conf(dev, mnvda, sizeof(mnvda)); + if (err) { + NL_SET_ERR_MSG_MOD(extack, "Failed to read PF configuration"); + return err; + } + + data = MLX5_ADDR_OF(mnvda_reg, mnvda, configuration_item_data); + if (MLX5_GET(nv_pf_pci_conf, data, pf_total_sf_en)) + ctx->val.vu32 = MLX5_GET(nv_pf_pci_conf, data, total_sf); + else + ctx->val.vu32 = 0; + + return 0; +} + +static int mlx5_devlink_max_sfs_validate(struct devlink *devlink, u32 id, + union devlink_param_value *val, + struct netlink_ext_ack *extack) +{ + if (val->vu32 > U16_MAX) { + NL_SET_ERR_MSG_FMT_MOD(extack, + "Max SFs allowed value is %u", U16_MAX); + return -EINVAL; + } + + return 0; +} + +static int mlx5_devlink_max_sfs_set(struct devlink *devlink, u32 id, + struct devlink_param_gset_ctx *ctx, + struct netlink_ext_ack *extack) +{ + struct mlx5_core_dev *dev = devlink_priv(devlink); + u32 mnvda[MLX5_ST_SZ_DW(mnvda_reg)] = {}; + void *data; + int err; + + /* we don't explicitly disable per_pf_num_sf when max_sfs is 0 because + * another PF may be using SFs + */ + if (ctx->val.vu32) { + err = mlx5_nv_param_read_global_pci_conf(dev, mnvda, + sizeof(mnvda)); + if (err) { + NL_SET_ERR_MSG_MOD(extack, + "Failed to read global PCI configuration"); + return err; + } + + data = MLX5_ADDR_OF(mnvda_reg, mnvda, configuration_item_data); + /* always enable per_pf_num_sf because another PF may use SFs */ + MLX5_SET(nv_global_pci_conf, data, per_pf_num_sf, 1); + + err = mlx5_nv_param_write(dev, mnvda, sizeof(mnvda)); + if (err) { + NL_SET_ERR_MSG_MOD(extack, + "Failed to change per_pf_num_sf global PCI configuration"); + return err; + } + memset(mnvda, 0, sizeof(mnvda)); + } + + err = mlx5_nv_param_read_per_host_pf_conf(dev, mnvda, sizeof(mnvda)); + if (err) { + NL_SET_ERR_MSG_MOD(extack, "Failed to read PF configuration"); + return err; + } + + data = MLX5_ADDR_OF(mnvda_reg, mnvda, configuration_item_data); + MLX5_SET(nv_pf_pci_conf, data, log_sf_bar_size, + ctx->val.vu32 ? MLX5_DEFAULT_LOG_SF_BAR_SIZE : 0); + MLX5_SET(nv_pf_pci_conf, data, pf_total_sf_en, !!ctx->val.vu32); + MLX5_SET(nv_pf_pci_conf, data, total_sf, ctx->val.vu32); + + err = mlx5_nv_param_write(dev, mnvda, sizeof(mnvda)); + if (err) { + NL_SET_ERR_MSG_MOD(extack, + "Failed to change PF PCI configuration"); + return err; + } + NL_SET_ERR_MSG_MOD(extack, + "Modifying max_sfs requires a FW reset and PCI bus rescan"); + + return 0; +} + static const struct devlink_param mlx5_nv_param_devlink_params[] = { DEVLINK_PARAM_GENERIC(ENABLE_SRIOV, BIT(DEVLINK_PARAM_CMODE_PERMANENT), mlx5_devlink_enable_sriov_get, @@ -763,6 +878,10 @@ static const struct devlink_param mlx5_nv_param_devlink_params[] = { mlx5_devlink_total_vfs_get, mlx5_devlink_total_vfs_set, mlx5_devlink_total_vfs_validate), + DEVLINK_PARAM_GENERIC(MAX_SFS, BIT(DEVLINK_PARAM_CMODE_PERMANENT), + mlx5_devlink_max_sfs_get, + mlx5_devlink_max_sfs_set, + mlx5_devlink_max_sfs_validate), DEVLINK_PARAM_DRIVER(MLX5_DEVLINK_PARAM_ID_CQE_COMPRESSION_TYPE, "cqe_compress_type", DEVLINK_PARAM_TYPE_STRING, BIT(DEVLINK_PARAM_CMODE_PERMANENT), From bfad9937de98755fa73bba4181e1de05069e7d54 Mon Sep 17 00:00:00 2001 From: Marcelo Mendes Spessoto Junior Date: Fri, 7 Aug 2026 19:09:38 -0300 Subject: [PATCH 1232/1433] selftests: net: test IPV6_FL_A_RENEW RENEW was the only flow label action without selftests coverage. Assert renew returns no error on correct usage and fails for labels that do not exist. This test is based on the previously implemented EXCL share test, which demonstrates that a new flow label with the same value can be created after the linger period. Renew is used here to show that a flow label can last longer and block a new flow label creation after the previous linger time. This test, however, demands sleep during execution, and should be placed as a conditional test under the -l option. The addition of the expect_fail_errno helper is necessary to assert the corresponding error when a function can fail in multiple ways. Signed-off-by: Marcelo Mendes Spessoto Junior Link: https://patch.msgid.link/20260807220942.421382-2-marcelomspessoto@gmail.com Signed-off-by: Jakub Kicinski --- .../selftests/net/ipv6_flowlabel_mgr.c | 58 ++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/net/ipv6_flowlabel_mgr.c b/tools/testing/selftests/net/ipv6_flowlabel_mgr.c index af95b48acea9..ec187890a1ed 100644 --- a/tools/testing/selftests/net/ipv6_flowlabel_mgr.c +++ b/tools/testing/selftests/net/ipv6_flowlabel_mgr.c @@ -42,6 +42,23 @@ #define expect_pass(x) __expect(x) #define expect_fail(x) __expect(!(x)) +#define expect_fail_errno(x, e) \ + do { \ + int __exp = (e); \ + int __ret = (x); \ + int __err = errno; \ + if (__ret && __err == __exp) \ + fprintf(stderr, "[OK] " #x "\n"); \ + else if (!__ret) \ + error(1, 0, "[ERR] " #x \ + " (line %d): unexpectedly succeeded", \ + __LINE__); \ + else \ + error(1, 0, "[ERR] " #x \ + " (line %d): expected errno %d, got %d", \ + __LINE__, __exp, __err); \ + } while (0) + static bool cfg_long_running; static bool cfg_verbose; @@ -71,6 +88,19 @@ static int flowlabel_put(int fd, uint32_t label) return setsockopt(fd, SOL_IPV6, IPV6_FLOWLABEL_MGR, &req, sizeof(req)); } +static int flowlabel_renew(int fd, uint32_t label, uint8_t share, + uint16_t linger) +{ + struct in6_flowlabel_req req = { + .flr_action = IPV6_FL_A_RENEW, + .flr_label = htonl(label), + .flr_share = share, + .flr_linger = linger, + }; + + return setsockopt(fd, SOL_IPV6, IPV6_FLOWLABEL_MGR, &req, sizeof(req)); +} + static void run_tests(int fd) { int wstatus; @@ -94,7 +124,7 @@ static void run_tests(int fd) expect_pass(flowlabel_get(fd, 1, IPV6_FL_S_ANY, IPV6_FL_F_CREATE)); explain("cannot get it again with the exclusive (FL_FL_EXCL) flag"); expect_fail(flowlabel_get(fd, 1, IPV6_FL_S_ANY, - IPV6_FL_F_CREATE | IPV6_FL_F_EXCL)); + IPV6_FL_F_CREATE | IPV6_FL_F_EXCL)); explain("can now put exactly three references"); expect_pass(flowlabel_put(fd, 1)); expect_pass(flowlabel_put(fd, 1)); @@ -160,6 +190,32 @@ static void run_tests(int fd) error(1, errno, "wait"); if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) error(1, errno, "wait: unexpected child result"); + + explain("It is not possible to renew a label that does not exist"); + expect_fail_errno(flowlabel_renew(fd, 5, IPV6_FL_S_EXCL, + 2 * (FL_MIN_LINGER * 2 + 1)), + ESRCH); + + explain("Create a label for basic renew validation"); + expect_pass(flowlabel_get(fd, 5, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE)); + explain("renew does not error for an existing, valid label"); + expect_pass(flowlabel_renew(fd, 5, IPV6_FL_S_EXCL, + 2 * (FL_MIN_LINGER * 2 + 1))); + + if (cfg_long_running) { + explain("create a new label with FL_MIN_LINGER linger time"); + expect_pass(flowlabel_get(fd, 6, IPV6_FL_S_EXCL, + IPV6_FL_F_CREATE)); + explain("renew the label to extend linger, then put it"); + expect_pass(flowlabel_renew(fd, 6, IPV6_FL_S_EXCL, + 2 * (FL_MIN_LINGER * 2 + 1))); + expect_pass(flowlabel_put(fd, 6)); + sleep(FL_MIN_LINGER * 2 + 1); + explain("cannot create: new linger time not over yet"); + expect_fail_errno(flowlabel_get(fd, 6, IPV6_FL_S_ANY, + IPV6_FL_F_CREATE), + EPERM); + } } static void parse_opts(int argc, char **argv) From 39dd045c15143b9a40473e4b79263b3f94d5f3a0 Mon Sep 17 00:00:00 2001 From: Marcelo Mendes Spessoto Junior Date: Fri, 7 Aug 2026 19:09:39 -0300 Subject: [PATCH 1233/1433] selftests: net: test IPV6_FL_F_REMOTE This flag retrieves the flow label seen by the socket at connection setup via a getsockopt query. Therefore, the validation of this flag requires a brief connection setup (source code for flow label shows it must be TCP). The simple TCP connection logic was wrapped inside two simple helpers, because there are other uncovered features of flow label mgr that could benefit from it (such as IPV6_FL_F_REFLECT). Signed-off-by: Marcelo Mendes Spessoto Junior Link: https://patch.msgid.link/20260807220942.421382-3-marcelomspessoto@gmail.com Signed-off-by: Jakub Kicinski --- .../selftests/net/ipv6_flowlabel_mgr.c | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/tools/testing/selftests/net/ipv6_flowlabel_mgr.c b/tools/testing/selftests/net/ipv6_flowlabel_mgr.c index ec187890a1ed..51541e792257 100644 --- a/tools/testing/selftests/net/ipv6_flowlabel_mgr.c +++ b/tools/testing/selftests/net/ipv6_flowlabel_mgr.c @@ -24,6 +24,9 @@ #ifndef IPV6_FLOWLABEL_MGR #define IPV6_FLOWLABEL_MGR 32 #endif +#ifndef IPV6_FLOWINFO_SEND +#define IPV6_FLOWINFO_SEND 33 +#endif /* from net/ipv6/ip6_flowlabel.c */ #define FL_MIN_LINGER 6 @@ -101,6 +104,67 @@ static int flowlabel_renew(int fd, uint32_t label, uint8_t share, return setsockopt(fd, SOL_IPV6, IPV6_FLOWLABEL_MGR, &req, sizeof(req)); } +static struct sockaddr_in6 loopback_addr(void) +{ + struct sockaddr_in6 addr = { + .sin6_family = AF_INET6, + .sin6_addr = IN6ADDR_LOOPBACK_INIT, + .sin6_port = htons(8888), + }; + + return addr; +} + +static int tcp_listen(void) +{ + struct sockaddr_in6 addr = loopback_addr(); + const int one = 1; + int fd; + + fd = socket(PF_INET6, SOCK_STREAM, 0); + if (fd == -1) + error(1, errno, "socket listener"); + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) + error(1, errno, "setsockopt SO_REUSEADDR"); + if (bind(fd, (void *)&addr, sizeof(addr))) + error(1, errno, "bind"); + if (listen(fd, 1)) + error(1, errno, "listen"); + + return fd; +} + +static void tcp_connect(int listener, uint32_t flowlabel, + int *client, int *accepted) +{ + struct sockaddr_in6 addr = loopback_addr(); + const int one = 1; + int cfd, afd; + + cfd = socket(PF_INET6, SOCK_STREAM, 0); + if (cfd == -1) + error(1, errno, "socket client"); + + if (flowlabel_get(cfd, flowlabel, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE)) + error(1, errno, "flowlabel_get"); + if (setsockopt(cfd, SOL_IPV6, IPV6_FLOWINFO_SEND, &one, sizeof(one))) + error(1, errno, "setsockopt flowinfo_send"); + addr.sin6_flowinfo = htonl(flowlabel); + + if (connect(cfd, (void *)&addr, sizeof(addr))) + error(1, errno, "connect"); + + afd = accept(listener, NULL, NULL); + if (afd == -1) + error(1, errno, "accept"); + + if (flowlabel_put(cfd, flowlabel)) + error(1, errno, "flowlabel_put"); + + *client = cfd; + *accepted = afd; +} + static void run_tests(int fd) { int wstatus; @@ -216,6 +280,30 @@ static void run_tests(int fd) IPV6_FL_F_CREATE), EPERM); } + + { + struct in6_flowlabel_req freq = { + .flr_action = IPV6_FL_A_GET, + .flr_flags = IPV6_FL_F_REMOTE, + }; + int remote_listener = tcp_listen(); + socklen_t freq_len = sizeof(freq); + int remote_cfd, remote_afd; + + explain("Prepare TCP SYN for REMOTE flag validation"); + tcp_connect(remote_listener, 7, &remote_cfd, &remote_afd); + + explain("Query for label sent by client with IPV6_FL_F_REMOTE"); + expect_pass(getsockopt(remote_afd, SOL_IPV6, IPV6_FLOWLABEL_MGR, + &freq, &freq_len)); + if (ntohl(freq.flr_label) != 7) + error(1, 0, "unexpected remote flowlabel %u", + ntohl(freq.flr_label)); + + close(remote_afd); + close(remote_cfd); + close(remote_listener); + } } static void parse_opts(int argc, char **argv) From 03df0d155ba11ec37e0b48bb93fede143ffef556 Mon Sep 17 00:00:00 2001 From: Marcelo Mendes Spessoto Junior Date: Fri, 7 Aug 2026 19:09:40 -0300 Subject: [PATCH 1234/1433] selftests: net: create own netns in ipv6_flowlabel_mgr Have ipv6_flowlabel_mgr create and configure its own network namespace (unshare(CLONE_NEWNET) + bring up lo), the same way ipv6_fragmentation.c and icmp_rfc4884.c already do, instead of relying on the in_netns.sh wrapper script. The setup can then be reused across tests through fixtures and provide isolated network environments for each test in the case of a future adoption of kselftest_harness. It also avoids the leak of modifications to the netns in case the user runs the test file directly, outside the wrapper and without the in_netns.sh file. Signed-off-by: Marcelo Mendes Spessoto Junior Link: https://patch.msgid.link/20260807220942.421382-4-marcelomspessoto@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/ipv6_flowlabel.sh | 2 +- .../selftests/net/ipv6_flowlabel_mgr.c | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/net/ipv6_flowlabel.sh b/tools/testing/selftests/net/ipv6_flowlabel.sh index cee95e252bee..2eeda39bf64c 100755 --- a/tools/testing/selftests/net/ipv6_flowlabel.sh +++ b/tools/testing/selftests/net/ipv6_flowlabel.sh @@ -8,7 +8,7 @@ set -e echo "TEST management" -./in_netns.sh ./ipv6_flowlabel_mgr +./ipv6_flowlabel_mgr echo "TEST datapath" ./in_netns.sh \ diff --git a/tools/testing/selftests/net/ipv6_flowlabel_mgr.c b/tools/testing/selftests/net/ipv6_flowlabel_mgr.c index 51541e792257..f8d8b09b9d86 100644 --- a/tools/testing/selftests/net/ipv6_flowlabel_mgr.c +++ b/tools/testing/selftests/net/ipv6_flowlabel_mgr.c @@ -8,11 +8,14 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include #include #include @@ -306,6 +309,30 @@ static void run_tests(int fd) } } +static void setup(void) +{ + struct ifreq ifr = { + .ifr_name = "lo" + }; + int ctl; + + if (unshare(CLONE_NEWNET)) + error(1, errno, "unshare"); + + ctl = socket(AF_LOCAL, SOCK_STREAM, 0); + if (ctl == -1) + error(1, errno, "socket"); + + if (ioctl(ctl, SIOCGIFFLAGS, &ifr)) + error(1, errno, "ioctl SIOCGIFFLAGS"); + ifr.ifr_flags |= IFF_UP; + if (ioctl(ctl, SIOCSIFFLAGS, &ifr)) + error(1, errno, "ioctl: bring lo up"); + + if (close(ctl)) + error(1, errno, "close"); +} + static void parse_opts(int argc, char **argv) { int c; @@ -329,6 +356,7 @@ int main(int argc, char **argv) int fd; parse_opts(argc, argv); + setup(); fd = socket(PF_INET6, SOCK_DGRAM, 0); if (fd == -1) From b2690523a71fedf92c0bd1f8908c1cd7b62e26f6 Mon Sep 17 00:00:00 2001 From: Marcelo Mendes Spessoto Junior Date: Fri, 7 Aug 2026 19:09:41 -0300 Subject: [PATCH 1235/1433] selftests: net: test IPV6_FL_F_REFLECT According to the source code, flowlabel_consistency must be deactivated for the IPV6_FL_F_REFLECT flag to work. Since ipv6_flowlabel_mgr now runs in its own network namespace, do this directly from the test binary. Attempt to disable net.ipv6.flowlabel_consistency and skip the reflect test if that fails. A disabled flowlabel_consistency does not affect the remaining features being tested on the file, and failing to disable is not fatal and skips the reflect test only. The previously defined tcp_listen and tcp_connect helpers were reused, since the connection flow required for REFLECT validation is very similar to REMOTE. Signed-off-by: Marcelo Mendes Spessoto Junior Link: https://patch.msgid.link/20260807220942.421382-5-marcelomspessoto@gmail.com Signed-off-by: Jakub Kicinski --- .../selftests/net/ipv6_flowlabel_mgr.c | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/tools/testing/selftests/net/ipv6_flowlabel_mgr.c b/tools/testing/selftests/net/ipv6_flowlabel_mgr.c index f8d8b09b9d86..d32150abd8ff 100644 --- a/tools/testing/selftests/net/ipv6_flowlabel_mgr.c +++ b/tools/testing/selftests/net/ipv6_flowlabel_mgr.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -168,6 +169,23 @@ static void tcp_connect(int listener, uint32_t flowlabel, *accepted = afd; } +static bool disable_flowlabel_consistency(void) +{ + int fd; + + fd = open("/proc/sys/net/ipv6/flowlabel_consistency", O_WRONLY); + if (fd == -1) + return false; + + if (write(fd, "0", 1) != 1) { + close(fd); + return false; + } + close(fd); + + return true; +} + static void run_tests(int fd) { int wstatus; @@ -307,6 +325,54 @@ static void run_tests(int fd) close(remote_cfd); close(remote_listener); } + + if (!disable_flowlabel_consistency()) { + fprintf(stderr, + "[INFO] skip REFLECT: cannot disable net.ipv6.flowlabel_consistency\n"); + } else { + struct in6_flowlabel_req reflect_query = { + .flr_action = IPV6_FL_A_GET, + }; + struct in6_flowlabel_req reflect_off = { + .flr_action = IPV6_FL_A_PUT, + .flr_flags = IPV6_FL_F_REFLECT, + }; + struct in6_flowlabel_req reflect_on = { + .flr_action = IPV6_FL_A_GET, + .flr_flags = IPV6_FL_F_REFLECT, + }; + socklen_t reflect_query_len = sizeof(reflect_query); + int reflect_listener = tcp_listen(); + int reflect_cfd, reflect_afd; + + explain("Enable REFLECT on listener before client connects"); + expect_pass(setsockopt(reflect_listener, SOL_IPV6, + IPV6_FLOWLABEL_MGR, &reflect_on, + sizeof(reflect_on))); + + tcp_connect(reflect_listener, 8, &reflect_cfd, &reflect_afd); + + explain("accepted socket's label should be reflected"); + expect_pass(getsockopt(reflect_afd, SOL_IPV6, + IPV6_FLOWLABEL_MGR, &reflect_query, + &reflect_query_len)); + if (ntohl(reflect_query.flr_label) != 8) + error(1, 0, "unexpected reflected flowlabel %u", + ntohl(reflect_query.flr_label)); + + explain("PUT+REFLECT disables reflection on accepted socket"); + expect_pass(setsockopt(reflect_afd, SOL_IPV6, + IPV6_FLOWLABEL_MGR, &reflect_off, + sizeof(reflect_off))); + explain("cannot disable reflection twice"); + expect_fail(setsockopt(reflect_afd, SOL_IPV6, + IPV6_FLOWLABEL_MGR, &reflect_off, + sizeof(reflect_off))); + + close(reflect_afd); + close(reflect_cfd); + close(reflect_listener); + } } static void setup(void) From b5d24f604506e75bd6eb606f116e03976d89b642 Mon Sep 17 00:00:00 2001 From: Marcelo Mendes Spessoto Junior Date: Fri, 7 Aug 2026 19:09:42 -0300 Subject: [PATCH 1236/1433] selftests: net: adopt harness for flow label mgr The kselftest_harness.h file contains modern helpers to build tests for kselftest. Dropping the custom test helpers in ipv6_flowlabel_mgr in favor of the harness makes tests more legible and conforms to the structure of the latest selftests. It also enforces the TAP standard. Another change made to the structure of the ipv6_flowlabel_mgr test file was the removal of parse_opts. The supported opts were already unused: the binary is listed in TEST_GEN_FILES, and is driven solely by ipv6_flowlabel.sh via "./ipv6_flowlabel_mgr", which never passed -l or -v. Dropping the -l gate means the two checks it previously guarded (each with a 13-second sleep, ~26 seconds total) are now unconditionally enabled on every run instead of never running at all. The TH_LOG calls and code comments now cover the information that the removed, custom -v flag used to print. Finally, FIXTURE_SETUP(flowlabel) ensures each test gets its own isolated network namespace. The previously added setup() helper was dropped to conform to the netns setup pattern used in icmp_rfc4884.c. disable_flowlabel_consistency() was moved next to reflect_flag, the only test that calls it, and now uses SKIP() instead of an ad hoc [INFO] message when the sysctl cannot be disabled. Signed-off-by: Marcelo Mendes Spessoto Junior Link: https://patch.msgid.link/20260807220942.421382-6-marcelomspessoto@gmail.com Signed-off-by: Jakub Kicinski --- .../selftests/net/ipv6_flowlabel_mgr.c | 658 ++++++++++-------- 1 file changed, 383 insertions(+), 275 deletions(-) diff --git a/tools/testing/selftests/net/ipv6_flowlabel_mgr.c b/tools/testing/selftests/net/ipv6_flowlabel_mgr.c index d32150abd8ff..072fb3a9b121 100644 --- a/tools/testing/selftests/net/ipv6_flowlabel_mgr.c +++ b/tools/testing/selftests/net/ipv6_flowlabel_mgr.c @@ -23,6 +23,7 @@ #include #include #include +#include "kselftest_harness.h" /* uapi/glibc weirdness may leave this undefined */ #ifndef IPV6_FLOWLABEL_MGR @@ -35,40 +36,6 @@ /* from net/ipv6/ip6_flowlabel.c */ #define FL_MIN_LINGER 6 -#define explain(x) \ - do { if (cfg_verbose) fprintf(stderr, " " x "\n"); } while (0) - -#define __expect(x) \ - do { \ - if (!(x)) \ - fprintf(stderr, "[OK] " #x "\n"); \ - else \ - error(1, 0, "[ERR] " #x " (line %d)", __LINE__); \ - } while (0) - -#define expect_pass(x) __expect(x) -#define expect_fail(x) __expect(!(x)) - -#define expect_fail_errno(x, e) \ - do { \ - int __exp = (e); \ - int __ret = (x); \ - int __err = errno; \ - if (__ret && __err == __exp) \ - fprintf(stderr, "[OK] " #x "\n"); \ - else if (!__ret) \ - error(1, 0, "[ERR] " #x \ - " (line %d): unexpectedly succeeded", \ - __LINE__); \ - else \ - error(1, 0, "[ERR] " #x \ - " (line %d): expected errno %d, got %d", \ - __LINE__, __exp, __err); \ - } while (0) - -static bool cfg_long_running; -static bool cfg_verbose; - static int flowlabel_get(int fd, uint32_t label, uint8_t share, uint16_t flags) { struct in6_flowlabel_req req = { @@ -169,6 +136,342 @@ static void tcp_connect(int listener, uint32_t flowlabel, *accepted = afd; } +static int bringup_loopback(void) +{ + struct ifreq ifr = { + .ifr_name = "lo" + }; + int fd; + + fd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (fd < 0) + return -1; + + if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) + goto err; + + ifr.ifr_flags = ifr.ifr_flags | IFF_UP; + + if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0) + goto err; + + close(fd); + return 0; + +err: + close(fd); + return -1; +} + +FIXTURE(flowlabel) {}; + +FIXTURE_SETUP(flowlabel) +{ + int ret; + + ret = unshare(CLONE_NEWNET); + ASSERT_EQ(ret, 0) { + TH_LOG("unshare(CLONE_NEWNET) failed: %s", strerror(errno)); + } + + ret = bringup_loopback(); + ASSERT_EQ(ret, 0) TH_LOG("Failed to bring up loopback interface"); +} + +FIXTURE_TEARDOWN(flowlabel) +{ +} + +TEST_F(flowlabel, cannot_get_non_existent_label) +{ + int fd, err; + + fd = socket(PF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(fd, 0) TH_LOG("socket failed"); + + err = flowlabel_get(fd, 9, IPV6_FL_S_ANY, 0); + EXPECT_TRUE(err) TH_LOG("expected get of a non-existent label to fail"); + EXPECT_EQ(ENOENT, errno) TH_LOG("expected ENOENT, got %d", errno); + + EXPECT_EQ(0, close(fd)); +} + +TEST_F(flowlabel, cannot_put_non_existent_label) +{ + int fd, err; + + fd = socket(PF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(fd, 0) TH_LOG("socket failed"); + + err = flowlabel_put(fd, 10); + EXPECT_TRUE(err) TH_LOG("expected put of a non-existent label to fail"); + EXPECT_EQ(ESRCH, errno) TH_LOG("expected ESRCH, got %d", errno); + + EXPECT_EQ(0, close(fd)); +} + +TEST_F(flowlabel, cannot_create_label_greater_than_20_bits) +{ + int fd, err; + + fd = socket(PF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(fd, 0) TH_LOG("socket failed"); + + err = flowlabel_get(fd, 0x1FFFFF, IPV6_FL_S_ANY, IPV6_FL_F_CREATE); + EXPECT_TRUE(err) TH_LOG("expected label > 20 bits to be rejected"); + EXPECT_EQ(EINVAL, errno) TH_LOG("expected EINVAL, got %d", errno); + + EXPECT_EQ(0, close(fd)); +} + +TEST_F(flowlabel, can_create_and_get_and_put_labels) +{ + int fd, err; + + fd = socket(PF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(fd, 0) TH_LOG("socket failed"); + + err = flowlabel_get(fd, 1, IPV6_FL_S_ANY, IPV6_FL_F_CREATE); + EXPECT_TRUE(!err) TH_LOG("failed to create label (FL_F_CREATE)"); + + err = flowlabel_get(fd, 1, IPV6_FL_S_ANY, 0); + EXPECT_TRUE(!err) TH_LOG("failed to get the label without FL_F_CREATE"); + + err = flowlabel_get(fd, 1, IPV6_FL_S_ANY, IPV6_FL_F_CREATE); + EXPECT_TRUE(!err) + TH_LOG("failed to get it again with create flag set, too"); + + err = flowlabel_get(fd, 1, IPV6_FL_S_ANY, + IPV6_FL_F_CREATE | IPV6_FL_F_EXCL); + EXPECT_TRUE(err) + TH_LOG("expected FL_F_EXCL to reject existing label"); + EXPECT_EQ(EEXIST, errno) TH_LOG("expected EEXIST, got %d", errno); + + err = flowlabel_put(fd, 1); + EXPECT_TRUE(!err) TH_LOG("failed to put first reference"); + err = flowlabel_put(fd, 1); + EXPECT_TRUE(!err) TH_LOG("failed to put second reference"); + err = flowlabel_put(fd, 1); + EXPECT_TRUE(!err) TH_LOG("failed to put third reference"); + err = flowlabel_put(fd, 1); + EXPECT_TRUE(err) + TH_LOG("expected fourth put to fail, no references left"); + EXPECT_EQ(ESRCH, errno) TH_LOG("expected ESRCH, got %d", errno); + + EXPECT_EQ(0, close(fd)); +} + +TEST_F(flowlabel, exclusive_label_share) +{ + int fd, err; + + fd = socket(PF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(fd, 0) TH_LOG("socket failed"); + + err = flowlabel_get(fd, 2, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE); + EXPECT_TRUE(!err) + TH_LOG("failed to create a new exclusive label (FL_S_EXCL)"); + + err = flowlabel_get(fd, 2, IPV6_FL_S_ANY, IPV6_FL_F_CREATE); + EXPECT_TRUE(err) TH_LOG("expected reuse in non-exclusive mode to fail"); + EXPECT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno); + + err = flowlabel_get(fd, 2, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE); + EXPECT_TRUE(err) TH_LOG("expected reuse in exclusive mode to fail too"); + EXPECT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno); + + err = flowlabel_put(fd, 2); + EXPECT_TRUE(!err) TH_LOG("failed to put the exclusive label"); + + err = flowlabel_get(fd, 2, IPV6_FL_S_ANY, IPV6_FL_F_CREATE); + EXPECT_TRUE(err) TH_LOG("expected reuse to fail, due to linger"); + EXPECT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno); + + sleep(FL_MIN_LINGER * 2 + 1); + + err = flowlabel_get(fd, 2, IPV6_FL_S_ANY, IPV6_FL_F_CREATE); + EXPECT_TRUE(!err) TH_LOG("expected reuse to succeed after linger"); + + EXPECT_EQ(0, close(fd)); +} + +TEST_F(flowlabel, user_private_label_share) +{ + int fd, err, wstatus; + pid_t pid; + + fd = socket(PF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(fd, 0) TH_LOG("socket failed"); + + err = flowlabel_get(fd, 3, IPV6_FL_S_USER, IPV6_FL_F_CREATE); + EXPECT_TRUE(!err) + TH_LOG("failed to create a new user-private label (FL_S_USER)"); + + err = flowlabel_get(fd, 3, IPV6_FL_S_ANY, 0); + EXPECT_TRUE(err) TH_LOG("expected get in non-exclusive mode to fail"); + EXPECT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno); + + err = flowlabel_get(fd, 3, IPV6_FL_S_EXCL, 0); + EXPECT_TRUE(err) TH_LOG("expected get in exclusive mode to fail"); + EXPECT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno); + + err = flowlabel_get(fd, 3, IPV6_FL_S_USER, 0); + EXPECT_TRUE(!err) TH_LOG("failed to get it again in user mode"); + + pid = fork(); + ASSERT_NE(-1, pid) TH_LOG("fork failed"); + if (!pid) { + err = flowlabel_get(fd, 3, IPV6_FL_S_USER, 0); + EXPECT_TRUE(!err) + TH_LOG("child failed to get the user-private label"); + + if (setuid(USHRT_MAX)) + exit(KSFT_SKIP); + + err = flowlabel_get(fd, 3, IPV6_FL_S_USER, 0); + EXPECT_TRUE(err) + TH_LOG("child unexpectedly got label after setuid"); + EXPECT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno); + exit(0); + } + ASSERT_EQ(pid, wait(&wstatus)) TH_LOG("wait failed"); + ASSERT_TRUE(WIFEXITED(wstatus)) TH_LOG("child did not exit normally"); + if (WEXITSTATUS(wstatus) == KSFT_SKIP) + SKIP(return, + "setuid(USHRT_MAX) unavailable (no CAP_SETUID or uid unmapped)"); + EXPECT_EQ(0, WEXITSTATUS(wstatus)) + TH_LOG("child reported unexpected result"); + + EXPECT_EQ(0, close(fd)); +} + +TEST_F(flowlabel, process_private_label_share) +{ + int fd, err, wstatus; + pid_t pid; + + fd = socket(PF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(fd, 0) TH_LOG("socket failed"); + + err = flowlabel_get(fd, 4, IPV6_FL_S_PROCESS, IPV6_FL_F_CREATE); + EXPECT_TRUE(!err) + TH_LOG("failed to create a new process-private label"); + + err = flowlabel_get(fd, 4, IPV6_FL_S_PROCESS, 0); + EXPECT_TRUE(!err) TH_LOG("failed to get it again"); + + pid = fork(); + ASSERT_NE(-1, pid) TH_LOG("fork failed"); + if (!pid) { + err = flowlabel_get(fd, 4, IPV6_FL_S_PROCESS, 0); + EXPECT_TRUE(err) + TH_LOG("child unexpectedly got process-private label"); + EXPECT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno); + exit(0); + } + ASSERT_EQ(pid, wait(&wstatus)) TH_LOG("wait failed"); + ASSERT_TRUE(WIFEXITED(wstatus)) TH_LOG("child did not exit normally"); + EXPECT_EQ(0, WEXITSTATUS(wstatus)) + TH_LOG("child reported unexpected result"); + + EXPECT_EQ(0, close(fd)); +} + +TEST_F(flowlabel, cannot_renew_non_existent_label) +{ + int fd, err; + + fd = socket(PF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(fd, 0) TH_LOG("socket failed"); + + err = flowlabel_renew(fd, 5, IPV6_FL_S_EXCL, + 2 * (FL_MIN_LINGER * 2 + 1)); + EXPECT_TRUE(err) + TH_LOG("expected renew of a non-existent label to fail"); + EXPECT_EQ(ESRCH, errno) TH_LOG("expected ESRCH, got %d", errno); + + EXPECT_EQ(0, close(fd)); +} + +TEST_F(flowlabel, can_renew_existing_label) +{ + int fd, err; + + fd = socket(PF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(fd, 0) TH_LOG("socket failed"); + + err = flowlabel_get(fd, 5, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE); + EXPECT_TRUE(!err) + TH_LOG("failed to create a new label for renew validation"); + + err = flowlabel_renew(fd, 5, IPV6_FL_S_EXCL, + 2 * (FL_MIN_LINGER * 2 + 1)); + EXPECT_TRUE(!err) TH_LOG("failed to renew an existing valid label"); + + err = flowlabel_put(fd, 5); + EXPECT_TRUE(!err) TH_LOG("failed to put the label"); + + EXPECT_EQ(0, close(fd)); +} + +TEST_F(flowlabel, renew_label_linger) +{ + /* RENEW must extend a label's linger period: putting a renewed + * label and waiting out its original linger time must not be + * enough to allow the label to be recreated. + */ + int fd, err; + + fd = socket(PF_INET6, SOCK_DGRAM, 0); + ASSERT_GE(fd, 0) TH_LOG("socket failed"); + + err = flowlabel_get(fd, 6, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE); + EXPECT_TRUE(!err) + TH_LOG("failed to create label with FL_MIN_LINGER linger time"); + + err = flowlabel_renew(fd, 6, IPV6_FL_S_EXCL, + 2 * (FL_MIN_LINGER * 2 + 1)); + EXPECT_TRUE(!err) + TH_LOG("failed to renew the label to increase its linger time"); + + err = flowlabel_put(fd, 6); + EXPECT_TRUE(!err) TH_LOG("failed to put the label"); + + sleep(FL_MIN_LINGER * 2 + 1); + + err = flowlabel_get(fd, 6, IPV6_FL_S_ANY, IPV6_FL_F_CREATE); + EXPECT_TRUE(err) + TH_LOG("expected reuse to fail, new linger time not over yet"); + EXPECT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno); + + EXPECT_EQ(0, close(fd)); +} + +TEST_F(flowlabel, remote_flag) +{ + /* The REMOTE flag, used for getsockopt, is expected to retrieve the + * label from the latest received header. + */ + struct in6_flowlabel_req freq = { + .flr_action = IPV6_FL_A_GET, + .flr_flags = IPV6_FL_F_REMOTE, + }; + socklen_t freq_len = sizeof(freq); + int listener, cfd, afd, err; + + listener = tcp_listen(); + tcp_connect(listener, 7, &cfd, &afd); + + err = getsockopt(afd, SOL_IPV6, IPV6_FLOWLABEL_MGR, &freq, &freq_len); + EXPECT_TRUE(!err) TH_LOG("getsockopt with IPV6_FL_F_REMOTE failed"); + EXPECT_EQ(7, ntohl(freq.flr_label)) + TH_LOG("unexpected remote flow label"); + + EXPECT_EQ(0, close(afd)); + EXPECT_EQ(0, close(cfd)); + EXPECT_EQ(0, close(listener)); +} + static bool disable_flowlabel_consistency(void) { int fd; @@ -186,252 +489,57 @@ static bool disable_flowlabel_consistency(void) return true; } -static void run_tests(int fd) +TEST_F(flowlabel, reflect_flag) { - int wstatus; - pid_t pid; - - explain("cannot get non-existent label"); - expect_fail(flowlabel_get(fd, 1, IPV6_FL_S_ANY, 0)); - - explain("cannot put non-existent label"); - expect_fail(flowlabel_put(fd, 1)); - - explain("cannot create label greater than 20 bits"); - expect_fail(flowlabel_get(fd, 0x1FFFFF, IPV6_FL_S_ANY, - IPV6_FL_F_CREATE)); - - explain("create a new label (FL_F_CREATE)"); - expect_pass(flowlabel_get(fd, 1, IPV6_FL_S_ANY, IPV6_FL_F_CREATE)); - explain("can get the label (without FL_F_CREATE)"); - expect_pass(flowlabel_get(fd, 1, IPV6_FL_S_ANY, 0)); - explain("can get it again with create flag set, too"); - expect_pass(flowlabel_get(fd, 1, IPV6_FL_S_ANY, IPV6_FL_F_CREATE)); - explain("cannot get it again with the exclusive (FL_FL_EXCL) flag"); - expect_fail(flowlabel_get(fd, 1, IPV6_FL_S_ANY, - IPV6_FL_F_CREATE | IPV6_FL_F_EXCL)); - explain("can now put exactly three references"); - expect_pass(flowlabel_put(fd, 1)); - expect_pass(flowlabel_put(fd, 1)); - expect_pass(flowlabel_put(fd, 1)); - expect_fail(flowlabel_put(fd, 1)); - - explain("create a new exclusive label (FL_S_EXCL)"); - expect_pass(flowlabel_get(fd, 2, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE)); - explain("cannot get it again in non-exclusive mode"); - expect_fail(flowlabel_get(fd, 2, IPV6_FL_S_ANY, IPV6_FL_F_CREATE)); - explain("cannot get it again in exclusive mode either"); - expect_fail(flowlabel_get(fd, 2, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE)); - expect_pass(flowlabel_put(fd, 2)); - - if (cfg_long_running) { - explain("cannot reuse the label, due to linger"); - expect_fail(flowlabel_get(fd, 2, IPV6_FL_S_ANY, - IPV6_FL_F_CREATE)); - explain("after sleep, can reuse"); - sleep(FL_MIN_LINGER * 2 + 1); - expect_pass(flowlabel_get(fd, 2, IPV6_FL_S_ANY, - IPV6_FL_F_CREATE)); - } - - explain("create a new user-private label (FL_S_USER)"); - expect_pass(flowlabel_get(fd, 3, IPV6_FL_S_USER, IPV6_FL_F_CREATE)); - explain("cannot get it again in non-exclusive mode"); - expect_fail(flowlabel_get(fd, 3, IPV6_FL_S_ANY, 0)); - explain("cannot get it again in exclusive mode"); - expect_fail(flowlabel_get(fd, 3, IPV6_FL_S_EXCL, 0)); - explain("can get it again in user mode"); - expect_pass(flowlabel_get(fd, 3, IPV6_FL_S_USER, 0)); - explain("child process can get it too, but not after setuid(nobody)"); - pid = fork(); - if (pid == -1) - error(1, errno, "fork"); - if (!pid) { - expect_pass(flowlabel_get(fd, 3, IPV6_FL_S_USER, 0)); - if (setuid(USHRT_MAX)) - fprintf(stderr, "[INFO] skip setuid child test\n"); - else - expect_fail(flowlabel_get(fd, 3, IPV6_FL_S_USER, 0)); - exit(0); - } - if (wait(&wstatus) == -1) - error(1, errno, "wait"); - if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) - error(1, errno, "wait: unexpected child result"); - - explain("create a new process-private label (FL_S_PROCESS)"); - expect_pass(flowlabel_get(fd, 4, IPV6_FL_S_PROCESS, IPV6_FL_F_CREATE)); - explain("can get it again"); - expect_pass(flowlabel_get(fd, 4, IPV6_FL_S_PROCESS, 0)); - explain("child process cannot can get it"); - pid = fork(); - if (pid == -1) - error(1, errno, "fork"); - if (!pid) { - expect_fail(flowlabel_get(fd, 4, IPV6_FL_S_PROCESS, 0)); - exit(0); - } - if (wait(&wstatus) == -1) - error(1, errno, "wait"); - if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) - error(1, errno, "wait: unexpected child result"); - - explain("It is not possible to renew a label that does not exist"); - expect_fail_errno(flowlabel_renew(fd, 5, IPV6_FL_S_EXCL, - 2 * (FL_MIN_LINGER * 2 + 1)), - ESRCH); - - explain("Create a label for basic renew validation"); - expect_pass(flowlabel_get(fd, 5, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE)); - explain("renew does not error for an existing, valid label"); - expect_pass(flowlabel_renew(fd, 5, IPV6_FL_S_EXCL, - 2 * (FL_MIN_LINGER * 2 + 1))); - - if (cfg_long_running) { - explain("create a new label with FL_MIN_LINGER linger time"); - expect_pass(flowlabel_get(fd, 6, IPV6_FL_S_EXCL, - IPV6_FL_F_CREATE)); - explain("renew the label to extend linger, then put it"); - expect_pass(flowlabel_renew(fd, 6, IPV6_FL_S_EXCL, - 2 * (FL_MIN_LINGER * 2 + 1))); - expect_pass(flowlabel_put(fd, 6)); - sleep(FL_MIN_LINGER * 2 + 1); - explain("cannot create: new linger time not over yet"); - expect_fail_errno(flowlabel_get(fd, 6, IPV6_FL_S_ANY, - IPV6_FL_F_CREATE), - EPERM); - } - - { - struct in6_flowlabel_req freq = { - .flr_action = IPV6_FL_A_GET, - .flr_flags = IPV6_FL_F_REMOTE, - }; - int remote_listener = tcp_listen(); - socklen_t freq_len = sizeof(freq); - int remote_cfd, remote_afd; - - explain("Prepare TCP SYN for REMOTE flag validation"); - tcp_connect(remote_listener, 7, &remote_cfd, &remote_afd); - - explain("Query for label sent by client with IPV6_FL_F_REMOTE"); - expect_pass(getsockopt(remote_afd, SOL_IPV6, IPV6_FLOWLABEL_MGR, - &freq, &freq_len)); - if (ntohl(freq.flr_label) != 7) - error(1, 0, "unexpected remote flowlabel %u", - ntohl(freq.flr_label)); - - close(remote_afd); - close(remote_cfd); - close(remote_listener); - } - - if (!disable_flowlabel_consistency()) { - fprintf(stderr, - "[INFO] skip REFLECT: cannot disable net.ipv6.flowlabel_consistency\n"); - } else { - struct in6_flowlabel_req reflect_query = { - .flr_action = IPV6_FL_A_GET, - }; - struct in6_flowlabel_req reflect_off = { - .flr_action = IPV6_FL_A_PUT, - .flr_flags = IPV6_FL_F_REFLECT, - }; - struct in6_flowlabel_req reflect_on = { - .flr_action = IPV6_FL_A_GET, - .flr_flags = IPV6_FL_F_REFLECT, - }; - socklen_t reflect_query_len = sizeof(reflect_query); - int reflect_listener = tcp_listen(); - int reflect_cfd, reflect_afd; - - explain("Enable REFLECT on listener before client connects"); - expect_pass(setsockopt(reflect_listener, SOL_IPV6, - IPV6_FLOWLABEL_MGR, &reflect_on, - sizeof(reflect_on))); - - tcp_connect(reflect_listener, 8, &reflect_cfd, &reflect_afd); - - explain("accepted socket's label should be reflected"); - expect_pass(getsockopt(reflect_afd, SOL_IPV6, - IPV6_FLOWLABEL_MGR, &reflect_query, - &reflect_query_len)); - if (ntohl(reflect_query.flr_label) != 8) - error(1, 0, "unexpected reflected flowlabel %u", - ntohl(reflect_query.flr_label)); - - explain("PUT+REFLECT disables reflection on accepted socket"); - expect_pass(setsockopt(reflect_afd, SOL_IPV6, - IPV6_FLOWLABEL_MGR, &reflect_off, - sizeof(reflect_off))); - explain("cannot disable reflection twice"); - expect_fail(setsockopt(reflect_afd, SOL_IPV6, - IPV6_FLOWLABEL_MGR, &reflect_off, - sizeof(reflect_off))); - - close(reflect_afd); - close(reflect_cfd); - close(reflect_listener); - } -} - -static void setup(void) -{ - struct ifreq ifr = { - .ifr_name = "lo" + /* The REFLECT flag acts as a trigger to the REPFLOW bit. When REPFLOW + * is triggered for a socket, it adopts the label received from the + * connected socket. + */ + struct in6_flowlabel_req reflect_on = { + .flr_action = IPV6_FL_A_GET, + .flr_flags = IPV6_FL_F_REFLECT, }; - int ctl; + struct in6_flowlabel_req reflect_query = { + .flr_action = IPV6_FL_A_GET, + }; + struct in6_flowlabel_req reflect_off = { + .flr_action = IPV6_FL_A_PUT, + .flr_flags = IPV6_FL_F_REFLECT, + }; + socklen_t reflect_query_len = sizeof(reflect_query); + int listener, cfd, afd, err; - if (unshare(CLONE_NEWNET)) - error(1, errno, "unshare"); + if (!disable_flowlabel_consistency()) + SKIP(return, + "cannot disable net.ipv6.flowlabel_consistency"); - ctl = socket(AF_LOCAL, SOCK_STREAM, 0); - if (ctl == -1) - error(1, errno, "socket"); + listener = tcp_listen(); + err = setsockopt(listener, SOL_IPV6, IPV6_FLOWLABEL_MGR, + &reflect_on, sizeof(reflect_on)); + EXPECT_TRUE(!err) TH_LOG("failed to enable REFLECT on the listener"); - if (ioctl(ctl, SIOCGIFFLAGS, &ifr)) - error(1, errno, "ioctl SIOCGIFFLAGS"); - ifr.ifr_flags |= IFF_UP; - if (ioctl(ctl, SIOCSIFFLAGS, &ifr)) - error(1, errno, "ioctl: bring lo up"); + tcp_connect(listener, 8, &cfd, &afd); - if (close(ctl)) - error(1, errno, "close"); + err = getsockopt(afd, SOL_IPV6, IPV6_FLOWLABEL_MGR, + &reflect_query, &reflect_query_len); + EXPECT_TRUE(!err) + TH_LOG("failed to query the accepted socket's outgoing label"); + EXPECT_EQ(8, ntohl(reflect_query.flr_label)) + TH_LOG("accepted socket did not reflect client's label"); + + err = setsockopt(afd, SOL_IPV6, IPV6_FLOWLABEL_MGR, + &reflect_off, sizeof(reflect_off)); + EXPECT_TRUE(!err) + TH_LOG("failed to disable REFLECT on the accepted socket"); + + err = setsockopt(afd, SOL_IPV6, IPV6_FLOWLABEL_MGR, + &reflect_off, sizeof(reflect_off)); + EXPECT_TRUE(err) TH_LOG("expected disabling REFLECT twice to fail"); + EXPECT_EQ(ESRCH, errno) TH_LOG("expected ESRCH, got %d", errno); + + EXPECT_EQ(0, close(afd)); + EXPECT_EQ(0, close(cfd)); + EXPECT_EQ(0, close(listener)); } -static void parse_opts(int argc, char **argv) -{ - int c; - - while ((c = getopt(argc, argv, "lv")) != -1) { - switch (c) { - case 'l': - cfg_long_running = true; - break; - case 'v': - cfg_verbose = true; - break; - default: - error(1, 0, "%s: parse error", argv[0]); - } - } -} - -int main(int argc, char **argv) -{ - int fd; - - parse_opts(argc, argv); - setup(); - - fd = socket(PF_INET6, SOCK_DGRAM, 0); - if (fd == -1) - error(1, errno, "socket"); - - run_tests(fd); - - if (close(fd)) - error(1, errno, "close"); - - return 0; -} +TEST_HARNESS_MAIN From 5838193edccab7810d5dc51c165a316089272dc6 Mon Sep 17 00:00:00 2001 From: Vadim Fedorenko Date: Thu, 6 Aug 2026 20:18:49 +0000 Subject: [PATCH 1237/1433] bnxt_en: enable PTM function The patch mentioned in Fixes missed one main point of implementing proper PTM support. To make it fully operational it has to be explicitly enabled. Add missing call in probe callback and disable it in teardown callback. Signed-off-by: Vadim Fedorenko Reviewed-by: Pavan Chebbi Link: https://patch.msgid.link/20260806201849.3161402-1-vadim.fedorenko@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 25099077fe4f..8a9c7646b53f 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -14978,6 +14978,7 @@ static void bnxt_unmap_bars(struct bnxt *bp, struct pci_dev *pdev) static void bnxt_cleanup_pci(struct bnxt *bp) { + pci_disable_ptm(bp->pdev); bnxt_unmap_bars(bp, bp->pdev); pci_release_regions(bp->pdev); if (pci_is_enabled(bp->pdev)) @@ -15546,6 +15547,8 @@ static int bnxt_init_board(struct pci_dev *pdev, struct net_device *dev) goto init_err_release; } + pci_enable_ptm(pdev); + INIT_WORK(&bp->sp_task, bnxt_sp_task); INIT_DELAYED_WORK(&bp->fw_reset_task, bnxt_fw_reset_task); From 878b56de01e255172745775cd8afcec2bd80268a Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 10 Aug 2026 17:46:45 -0700 Subject: [PATCH 1238/1433] selftests: drv-net: hide the devlink port_split test The devlink port_split test has limited applicability. NICs (as opposed to switches) require at least a re-probe to apply the split configuration. On top of that the test is not compatible with our driver env, it just splits all ports on the system, not only what NETIF points at. Long term we may want to add some indication in devlink whether the port splitting is runtime (cmode of sorts), and fix the test to follow driver env. But since no (known) NIC driver can support runtime anyway let's just hide the test from the selftest framework by moving it to extra files. Having this test randomly break unrelated NICs within the DUT makes people implement allow-lists for ksft, which then means their setups don't run new tests. It's very useful during test review to see whether the test works across all the runners. Reviewed-by: Petr Machata Link: https://patch.msgid.link/20260811004645.1072124-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/hw/Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile index 234db5c2c90c..78bb0169350b 100644 --- a/tools/testing/selftests/drivers/net/hw/Makefile +++ b/tools/testing/selftests/drivers/net/hw/Makefile @@ -19,7 +19,6 @@ TEST_GEN_FILES := \ TEST_PROGS = \ csum.py \ - devlink_port_split.py \ devlink_rate_cross_esw.py \ devlink_rate_tc_bw.py \ devmem.py \ @@ -54,6 +53,10 @@ TEST_PROGS = \ xsk_reconfig.py \ # +TEST_PROGS_EXTENDED := \ + devlink_port_split.py \ +# end of TEST_PROGS_EXTENDED + TEST_FILES := \ devmem_lib.py \ ethtool_lib.sh \ From 789e6a844b51bf6362a7e7f551553ba988f3dc92 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Fri, 7 Aug 2026 15:49:01 +0200 Subject: [PATCH 1239/1433] mptcp: move the retrans loop to a separate helper This is a cleanup in order to make the next patch simpler. No functional change intended. Tested-by: Gang Yan Tested-by: Geliang Tang Acked-by: Geliang Tang Signed-off-by: Paolo Abeni Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260807-net-next-mptcp-oooq-pruning-v3-1-dbc1eb853cc3@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 74 +++++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 7c8180d8d5ef..a21b10a8c5d3 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -2791,41 +2791,14 @@ static void mptcp_check_fastclose(struct mptcp_sock *msk) sk_error_report(sk); } -static void __mptcp_retrans(struct sock *sk) +/* Retransmit the specified data fragment on all the selected subflows. */ +static int __mptcp_push_retrans(struct sock *sk, struct mptcp_data_frag *dfrag) { struct mptcp_sendmsg_info info = { .data_lock_held = true, }; struct mptcp_sock *msk = mptcp_sk(sk); struct mptcp_subflow_context *subflow; - struct mptcp_data_frag *dfrag; struct sock *ssk; - int ret, err; - u16 len = 0; - - mptcp_clean_una_wakeup(sk); - - /* first check ssk: need to kick "stale" logic */ - err = mptcp_sched_get_retrans(msk); - dfrag = mptcp_rtx_head(sk); - if (!dfrag) { - if (mptcp_data_fin_enabled(msk)) { - struct inet_connection_sock *icsk = inet_csk(sk); - - WRITE_ONCE(icsk->icsk_retransmits, - icsk->icsk_retransmits + 1); - mptcp_set_datafin_timeout(sk); - mptcp_send_ack(msk); - - goto reset_timer; - } - - if (!mptcp_send_head(sk)) - goto clear_scheduled; - - goto reset_timer; - } - - if (err) - goto reset_timer; + int ret, len = 0; mptcp_for_each_subflow(msk, subflow) { if (READ_ONCE(subflow->scheduled)) { @@ -2853,7 +2826,7 @@ static void __mptcp_retrans(struct sock *sk) !msk->allow_subflows) { spin_unlock_bh(&msk->fallback_lock); release_sock(ssk); - goto clear_scheduled; + return -1; } while (info.sent < info.limit) { @@ -2876,6 +2849,45 @@ static void __mptcp_retrans(struct sock *sk) release_sock(ssk); } } + return len; +} + +static void __mptcp_retrans(struct sock *sk) +{ + struct mptcp_sock *msk = mptcp_sk(sk); + struct mptcp_subflow_context *subflow; + struct mptcp_data_frag *dfrag; + int err, len; + + mptcp_clean_una_wakeup(sk); + + /* first check ssk: need to kick "stale" logic */ + err = mptcp_sched_get_retrans(msk); + dfrag = mptcp_rtx_head(sk); + if (!dfrag) { + if (mptcp_data_fin_enabled(msk)) { + struct inet_connection_sock *icsk = inet_csk(sk); + + WRITE_ONCE(icsk->icsk_retransmits, + icsk->icsk_retransmits + 1); + mptcp_set_datafin_timeout(sk); + mptcp_send_ack(msk); + + goto reset_timer; + } + + if (!mptcp_send_head(sk)) + goto clear_scheduled; + + goto reset_timer; + } + + if (err) + goto reset_timer; + + len = __mptcp_push_retrans(sk, dfrag); + if (len < 0) + goto clear_scheduled; msk->bytes_retrans += len; dfrag->already_sent = max(dfrag->already_sent, len); From 6cafe51e0f98fe60a106783d30b2f4c4b6039f4c Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Fri, 7 Aug 2026 15:49:02 +0200 Subject: [PATCH 1240/1433] mptcp: move the stale logic out of retrans scheduler This allow separating the stale logic invocation and the retrans scheduler, and will simplify the next patch. It's also a cleaner design as the retrans scheduler has currently too many side effects. As a possible downside, the retrans work will now traverse the subflows list additional times; that does not matter much, as this is slowpath. While at it, pick more accurate names for the involved helpers and explicitly note that the per subflow stale data is under msk socket lock protection. The scheduler and the stale logic may observe different subflow statues, as no subflow lock is acquired. This is intentional and not harmful, worst case leading to slower retransmissions. Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260807-net-next-mptcp-oooq-pruning-v3-2-dbc1eb853cc3@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/pm.c | 41 +++++++++++++++++++++++++++-------------- net/mptcp/protocol.c | 4 ++-- net/mptcp/protocol.h | 11 +++++++---- 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/net/mptcp/pm.c b/net/mptcp/pm.c index 64a1236aabee..d1f73c3e39fa 100644 --- a/net/mptcp/pm.c +++ b/net/mptcp/pm.c @@ -1065,7 +1065,8 @@ bool mptcp_pm_is_backup(struct mptcp_sock *msk, struct sock_common *skc) return mptcp_pm_nl_is_backup(msk, &skc_local); } -static void mptcp_pm_subflows_chk_stale(const struct mptcp_sock *msk, struct sock *ssk) +static void +mptcp_pm_subflow_chk_stale(const struct mptcp_sock *msk, struct sock *ssk) { struct mptcp_subflow_context *iter, *subflow = mptcp_subflow_ctx(ssk); struct sock *sk = (struct sock *)msk; @@ -1102,22 +1103,34 @@ static void mptcp_pm_subflows_chk_stale(const struct mptcp_sock *msk, struct soc } } -void mptcp_pm_subflow_chk_stale(const struct mptcp_sock *msk, struct sock *ssk) +void mptcp_pm_chk_stale(const struct mptcp_sock *msk) { - struct mptcp_subflow_context *subflow = mptcp_subflow_ctx(ssk); - u32 rcv_tstamp = READ_ONCE(tcp_sk(ssk)->rcv_tstamp); + struct mptcp_subflow_context *subflow; - /* keep track of rtx periods with no progress */ - if (!subflow->stale_count) { - subflow->stale_rcv_tstamp = rcv_tstamp; - subflow->stale_count++; - } else if (subflow->stale_rcv_tstamp == rcv_tstamp) { - if (subflow->stale_count < U8_MAX) + mptcp_for_each_subflow(msk, subflow) { + struct sock *ssk = mptcp_subflow_tcp_sock(subflow); + u32 rcv_tstamp; + + if (!__mptcp_subflow_active(subflow)) + continue; + + /* No data outstanding at TCP level? not stale */ + if (tcp_rtx_and_write_queues_empty(ssk)) + continue; + + /* keep track of rtx periods with no progress */ + rcv_tstamp = READ_ONCE(tcp_sk(ssk)->rcv_tstamp); + if (!subflow->stale_count) { + subflow->stale_rcv_tstamp = rcv_tstamp; subflow->stale_count++; - mptcp_pm_subflows_chk_stale(msk, ssk); - } else { - subflow->stale_count = 0; - mptcp_subflow_set_active(subflow); + } else if (subflow->stale_rcv_tstamp == rcv_tstamp) { + if (subflow->stale_count < U8_MAX) + subflow->stale_count++; + mptcp_pm_subflow_chk_stale(msk, ssk); + } else { + subflow->stale_count = 0; + mptcp_subflow_set_active(subflow); + } } } diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index a21b10a8c5d3..88167edc6598 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -2469,7 +2469,6 @@ struct sock *mptcp_subflow_get_retrans(struct mptcp_sock *msk) /* still data outstanding at TCP level? skip this */ if (!tcp_rtx_and_write_queues_empty(ssk)) { - mptcp_pm_subflow_chk_stale(msk, ssk); min_stale_count = min_t(int, min_stale_count, subflow->stale_count); continue; } @@ -2859,9 +2858,10 @@ static void __mptcp_retrans(struct sock *sk) struct mptcp_data_frag *dfrag; int err, len; + mptcp_pm_chk_stale(msk); + mptcp_clean_una_wakeup(sk); - /* first check ssk: need to kick "stale" logic */ err = mptcp_sched_get_retrans(msk); dfrag = mptcp_rtx_head(sk); if (!dfrag) { diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index 1b80f2d6ec5a..b3af3462bdd1 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -580,12 +580,11 @@ struct mptcp_subflow_context { remote_key_valid : 1, /* received the peer key from */ disposable : 1, /* ctx can be free at ulp release time */ closing : 1, /* must not pass rx data to msk anymore */ - stale : 1, /* unable to snd/rcv data, do not use for xmit */ valid_csum_seen : 1, /* at least one csum validated */ is_mptfo : 1, /* subflow is doing TFO */ close_event_done : 1, /* has done the post-closed part */ mpc_drop : 1, /* the MPC option has been dropped in a rtx */ - __unused : 8; + __unused : 9; bool data_avail; bool scheduled; bool pm_listener; /* a listener managed by the kernel PM? */ @@ -604,7 +603,11 @@ struct mptcp_subflow_context { u8 reset_seen:1; u8 reset_transient:1; u8 reset_reason:4; - u8 stale_count; + u8 stale_count; /* Protected by the msk socket lock */ + u8 stale; /* Protected by the msk socket lock, + * if set the subflow is unable to snd/rcv + * data, the schedule should skip it + */ u32 subflow_id; @@ -1103,7 +1106,7 @@ int mptcp_pm_parse_entry(struct nlattr *attr, struct genl_info *info, bool mptcp_pm_addr_families_match(const struct sock *sk, const struct mptcp_addr_info *loc, const struct mptcp_addr_info *rem); -void mptcp_pm_subflow_chk_stale(const struct mptcp_sock *msk, struct sock *ssk); +void mptcp_pm_chk_stale(const struct mptcp_sock *msk); void mptcp_pm_new_connection(struct mptcp_sock *msk, const struct sock *ssk, int server_side); void mptcp_pm_fully_established(struct mptcp_sock *msk, const struct sock *ssk); bool mptcp_pm_allow_new_subflow(struct mptcp_sock *msk); From 96d846e3e2a7ea01ff8a584a0b5e2a42fa9ccc1e Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Fri, 7 Aug 2026 15:49:03 +0200 Subject: [PATCH 1241/1433] mptcp: let the retrans scheduler do its job Currently the MPTCP core enforces that when MPTCP-level retrans timer fires, at most a single dfrag is retransmitted. In some corner-cases, it may be necessary to retransmit multiple dfrags, and the MPTCP socket will need to wait multiple retrans timeout to accomplish that. Remove the mentioned constraint, allowing to transmit multiple dfrags per retrans period, as long as the scheduler keeps selecting subflows for retransmissions and pending data is available in the rtx queue. The default scheduler will transmit a dfrag per available subflow. Tested-by: Gang Yan Tested-by: Geliang Tang Acked-by: Geliang Tang Signed-off-by: Paolo Abeni Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260807-net-next-mptcp-oooq-pruning-v3-3-dbc1eb853cc3@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 106 ++++++++++++++++++++++++++++++------------- 1 file changed, 75 insertions(+), 31 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 88167edc6598..09cd3c1f3cdb 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -1142,13 +1142,6 @@ static void __mptcp_clean_una_wakeup(struct sock *sk) mptcp_write_space(sk); } -static void mptcp_clean_una_wakeup(struct sock *sk) -{ - mptcp_data_lock(sk); - __mptcp_clean_una_wakeup(sk); - mptcp_data_unlock(sk); -} - static void mptcp_enter_memory_pressure(struct sock *sk) { struct mptcp_subflow_context *subflow; @@ -2790,8 +2783,12 @@ static void mptcp_check_fastclose(struct mptcp_sock *msk) sk_error_report(sk); } -/* Retransmit the specified data fragment on all the selected subflows. */ -static int __mptcp_push_retrans(struct sock *sk, struct mptcp_data_frag *dfrag) +/* + * Retransmit the specified data fragment on all the selected subflows, + * starting from the specified sequence + */ +static int __mptcp_push_retrans(struct sock *sk, struct mptcp_data_frag *dfrag, + u64 sent_seq) { struct mptcp_sendmsg_info info = { .data_lock_held = true, }; struct mptcp_sock *msk = mptcp_sk(sk); @@ -2801,6 +2798,7 @@ static int __mptcp_push_retrans(struct sock *sk, struct mptcp_data_frag *dfrag) mptcp_for_each_subflow(msk, subflow) { if (READ_ONCE(subflow->scheduled)) { + u16 offset = sent_seq - dfrag->data_seq; u16 copied = 0; mptcp_subflow_set_scheduled(subflow, false); @@ -2810,7 +2808,7 @@ static int __mptcp_push_retrans(struct sock *sk, struct mptcp_data_frag *dfrag) lock_sock(ssk); /* limit retransmission to the bytes already sent on some subflows */ - info.sent = 0; + info.sent = offset; info.limit = READ_ONCE(msk->csum_enabled) ? dfrag->data_len : dfrag->already_sent; @@ -2856,15 +2854,78 @@ static void __mptcp_retrans(struct sock *sk) struct mptcp_sock *msk = mptcp_sk(sk); struct mptcp_subflow_context *subflow; struct mptcp_data_frag *dfrag; + u64 retrans_seq, sent_seq; + bool need_retrans; int err, len; mptcp_pm_chk_stale(msk); - mptcp_clean_una_wakeup(sk); - - err = mptcp_sched_get_retrans(msk); + /* Get an updated and consistent rtx queue status. */ + mptcp_data_lock(sk); + __mptcp_clean_una_wakeup(sk); + retrans_seq = msk->snd_una; dfrag = mptcp_rtx_head(sk); - if (!dfrag) { + need_retrans = !!dfrag; + mptcp_data_unlock(sk); + + for (;;) { + bool already_acked; + + err = mptcp_sched_get_retrans(msk); + if (err) + break; + + /* `already_sent` can be 0 for `dfrag` belonging to the RTX + * queue due to __mptcp_retransmit_pending_data(). + */ + if (!dfrag || !dfrag->already_sent) + break; + + /* Can fail only in case of fallback. */ + len = __mptcp_push_retrans(sk, dfrag, retrans_seq); + if (len < 0) + goto clear_scheduled; + + retrans_seq += len; + msk->bytes_retrans += len; + dfrag->already_sent = max_t(u16, dfrag->already_sent, + retrans_seq - dfrag->data_seq); + + /* With csum enabled, retransmission can send new data. */ + sent_seq = dfrag->already_sent + dfrag->data_seq; + if (after64(sent_seq, msk->snd_nxt)) + WRITE_ONCE(msk->snd_nxt, sent_seq); + + /* Attempt the next fragment only if the current one is + * completely retransmitted. + */ + if (before64(retrans_seq, dfrag->data_seq + dfrag->data_len)) + break; + + dfrag = list_is_last(&dfrag->list, &msk->rtx_queue) ? + NULL : list_next_entry(dfrag, list); + if (!dfrag) + break; + + /* Incoming acks can move snd_una after the current dfrag + * across loop iterations, if so start again from RTX head. + */ + mptcp_data_lock(sk); + already_acked = !before64(msk->snd_una, dfrag->data_seq + + dfrag->already_sent); + if (already_acked) { + __mptcp_clean_una_wakeup(sk); + retrans_seq = msk->snd_una; + dfrag = mptcp_rtx_head(sk); + need_retrans = !!dfrag; + } else if (after64(msk->snd_una, retrans_seq)) { + retrans_seq = msk->snd_una; + } + mptcp_data_unlock(sk); + } + + /* Attempt data-fin retransmission only when the RTX queue is empty. */ + if (!need_retrans) { if (mptcp_data_fin_enabled(msk)) { struct inet_connection_sock *icsk = inet_csk(sk); @@ -2872,30 +2933,13 @@ static void __mptcp_retrans(struct sock *sk) icsk->icsk_retransmits + 1); mptcp_set_datafin_timeout(sk); mptcp_send_ack(msk); - goto reset_timer; } if (!mptcp_send_head(sk)) goto clear_scheduled; - - goto reset_timer; } - if (err) - goto reset_timer; - - len = __mptcp_push_retrans(sk, dfrag); - if (len < 0) - goto clear_scheduled; - - msk->bytes_retrans += len; - dfrag->already_sent = max(dfrag->already_sent, len); - - /* With csum enabled retransmission can send new data. */ - if (after64(dfrag->already_sent + dfrag->data_seq, msk->snd_nxt)) - WRITE_ONCE(msk->snd_nxt, dfrag->already_sent + dfrag->data_seq); - reset_timer: mptcp_check_and_set_pending(sk); From e0e4d56b050597a690b4dd1c281af532469b0636 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Fri, 7 Aug 2026 15:49:04 +0200 Subject: [PATCH 1242/1433] mptcp: explicitly drop over memory limits Currently the enforcement of the rcvbuf constraint is implemented when moving the skbs into the msk receive or OoO queue, keeping the incoming skbs in the subflow queue when over limits. Under significant memory pressure the above can cause permanent data transfer stalls, as the skb needed to make forward progress can be stuck in a subflow queue. Over memory limits, drop the incoming skb, relying on MPTCP-level retransmissions. Note that fallback socket must perform the limit before the skb reaches the subflow-level queue, as dropping an in-sequence already acked skb would break the stream. This is not a complete fix for the stall issue, as the drop strategy needs refinements that will come in the next patches. Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260807-net-next-mptcp-oooq-pruning-v3-4-dbc1eb853cc3@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/mib.c | 2 ++ net/mptcp/mib.h | 2 ++ net/mptcp/options.c | 32 +++++++++++++++++++++++++++++--- net/mptcp/protocol.c | 31 +++++++++++++++++++++++-------- 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/net/mptcp/mib.c b/net/mptcp/mib.c index f23fda0c55a7..ef65e2df709f 100644 --- a/net/mptcp/mib.c +++ b/net/mptcp/mib.c @@ -85,6 +85,8 @@ static const struct snmp_mib mptcp_snmp_list[] = { SNMP_MIB_ITEM("SimultConnectFallback", MPTCP_MIB_SIMULTCONNFALLBACK), SNMP_MIB_ITEM("FallbackFailed", MPTCP_MIB_FALLBACKFAILED), SNMP_MIB_ITEM("WinProbe", MPTCP_MIB_WINPROBE), + SNMP_MIB_ITEM("BacklogDrop", MPTCP_MIB_BACKLOGDROP), + SNMP_MIB_ITEM("RcvPruned", MPTCP_MIB_RCVPRUNED), }; /* mptcp_mib_alloc - allocate percpu mib counters diff --git a/net/mptcp/mib.h b/net/mptcp/mib.h index 812218b5ed2b..9271205f682e 100644 --- a/net/mptcp/mib.h +++ b/net/mptcp/mib.h @@ -88,6 +88,8 @@ enum linux_mptcp_mib_field { MPTCP_MIB_SIMULTCONNFALLBACK, /* Simultaneous connect */ MPTCP_MIB_FALLBACKFAILED, /* Can't fallback due to msk status */ MPTCP_MIB_WINPROBE, /* MPTCP-level zero window probe */ + MPTCP_MIB_BACKLOGDROP, /* Backlog over memory limit */ + MPTCP_MIB_RCVPRUNED, /* Dropped due to memory constraints */ __MPTCP_MIB_MAX }; diff --git a/net/mptcp/options.c b/net/mptcp/options.c index 1057d500577b..b8318e030138 100644 --- a/net/mptcp/options.c +++ b/net/mptcp/options.c @@ -1190,8 +1190,34 @@ static bool add_addr_hmac_valid(struct mptcp_sock *msk, return hmac == mp_opt->ahmac; } -/* Return false in case of error (or subflow has been reset), - * else return true. +static bool mptcp_over_limit(struct sock *sk, struct sock *ssk, + const struct sk_buff *skb) +{ + struct mptcp_sock *msk = mptcp_sk(sk); + u32 rcvbuf = READ_ONCE(sk->sk_rcvbuf); + + if (likely((u32)sk_rmem_alloc_get(sk) <= rcvbuf && + READ_ONCE(msk->backlog_len) <= rcvbuf)) + return false; + + /* Avoid silently dropping pure acks, fin, rst or already-acked segm. */ + if (TCP_SKB_CB(skb)->seq == TCP_SKB_CB(skb)->end_seq || + TCP_SKB_CB(skb)->tcp_flags & (TCPHDR_FIN | TCPHDR_RST) || + !after(TCP_SKB_CB(skb)->end_seq, tcp_sk(ssk)->rcv_nxt)) + return false; + + /* Dropped due to memory constraints, schedule an ack. */ + inet_csk(ssk)->icsk_ack.pending |= ICSK_ACK_NOMEM | ICSK_ACK_NOW; + inet_csk_schedule_ack(ssk); + + /* Plain TCP (fallback) and skb is dropped before the TCP recv queue. */ + NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPRCVQDROP); + + return true; +} + +/* Return false when the caller must drop the packet, i.e. in case of error, + * subflow has been reset, or over memory limits. */ bool mptcp_incoming_options(struct sock *sk, struct sk_buff *skb) { @@ -1217,7 +1243,7 @@ bool mptcp_incoming_options(struct sock *sk, struct sk_buff *skb) __mptcp_data_acked(subflow->conn); mptcp_data_unlock(subflow->conn); - return true; + return !mptcp_over_limit(subflow->conn, sk, skb); } mptcp_get_options(skb, &mp_opt); diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 09cd3c1f3cdb..cfb77a32518a 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -387,6 +387,16 @@ static bool __mptcp_move_skb(struct sock *sk, struct sk_buff *skb) mptcp_borrow_fwdmem(sk, skb); + /* Can't drop packets for fallback socket this late, or the stream + * will break. + */ + if (unlikely(sk_rmem_alloc_get(sk) > READ_ONCE(sk->sk_rcvbuf)) && + !__mptcp_check_fallback(msk)) { + MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_RCVPRUNED); + mptcp_drop(sk, skb); + return false; + } + if (MPTCP_SKB_CB(skb)->map_seq == msk->ack_seq) { /* in sequence */ msk->bytes_received += copy_len; @@ -681,6 +691,7 @@ static void __mptcp_add_backlog(struct sock *sk, struct sk_buff *tail = NULL; struct sock *ssk = skb->sk; bool fragstolen; + u64 limit; int delta; if (unlikely(sk->sk_state == TCP_CLOSE)) { @@ -688,6 +699,16 @@ static void __mptcp_add_backlog(struct sock *sk, return; } + /* Similar additional allowance as plain TCP. */ + limit = READ_ONCE(sk->sk_rcvbuf); + limit += (limit >> 1) + 64 * 1024; + limit = min_t(u64, limit, UINT_MAX); + if (msk->backlog_len > limit && !__mptcp_check_fallback(msk)) { + __MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_BACKLOGDROP); + kfree_skb_reason(skb, SKB_DROP_REASON_SOCKET_BACKLOG); + return; + } + /* Try to coalesce with the last skb in our backlog */ if (!list_empty(&msk->backlog_list)) tail = list_last_entry(&msk->backlog_list, struct sk_buff, list); @@ -759,7 +780,7 @@ static bool __mptcp_move_skbs_from_subflow(struct mptcp_sock *msk, mptcp_init_skb(ssk, skb, offset, len); - if (own_msk && sk_rmem_alloc_get(sk) < sk->sk_rcvbuf) { + if (own_msk) { mptcp_subflow_lend_fwdmem(subflow, skb); ret |= __mptcp_move_skb(sk, skb); } else { @@ -2210,10 +2231,6 @@ static bool __mptcp_move_skbs(struct sock *sk, struct list_head *skbs, u32 *delt *delta = 0; while (1) { - /* If the msk recvbuf is full stop, don't drop */ - if (sk_rmem_alloc_get(sk) > sk->sk_rcvbuf) - break; - prefetch(skb->next); list_del(&skb->list); *delta += skb->truesize; @@ -2241,9 +2258,7 @@ static bool mptcp_can_spool_backlog(struct sock *sk, struct list_head *skbs) DEBUG_NET_WARN_ON_ONCE(msk->backlog_unaccounted && sk->sk_socket && mem_cgroup_from_sk(sk)); - /* Don't spool the backlog if the rcvbuf is full. */ - if (list_empty(&msk->backlog_list) || - sk_rmem_alloc_get(sk) > sk->sk_rcvbuf) + if (list_empty(&msk->backlog_list)) return false; INIT_LIST_HEAD(skbs); From b1224c4b40f6dfc10fa5654e8f0b690b83bb2905 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Fri, 7 Aug 2026 15:49:05 +0200 Subject: [PATCH 1243/1433] mptcp: enforce hard limit on backlog flushing Currently a wild producer could keep the backlog flushing operation spinning for an unbound time. Since the previous patch, the amount of data present in the backlog is hard-limited. Move the backlog len update at the end of the flush loop to prevent it spinning forever. Also, no need to splice back the remaining skbs list into the backlog, as such list is always empty after each backlog processing loop. Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260807-net-next-mptcp-oooq-pruning-v3-5-dbc1eb853cc3@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index cfb77a32518a..d96fbdc34dbb 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -2229,7 +2229,6 @@ static bool __mptcp_move_skbs(struct sock *sk, struct list_head *skbs, u32 *delt struct mptcp_sock *msk = mptcp_sk(sk); bool moved = false; - *delta = 0; while (1) { prefetch(skb->next); list_del(&skb->list); @@ -2266,20 +2265,12 @@ static bool mptcp_can_spool_backlog(struct sock *sk, struct list_head *skbs) return true; } -static void mptcp_backlog_spooled(struct sock *sk, u32 moved, - struct list_head *skbs) -{ - struct mptcp_sock *msk = mptcp_sk(sk); - - WRITE_ONCE(msk->backlog_len, msk->backlog_len - moved); - list_splice(skbs, &msk->backlog_list); -} - static bool mptcp_move_skbs(struct sock *sk) { + struct mptcp_sock *msk = mptcp_sk(sk); struct list_head skbs; bool enqueued = false; - u32 moved; + u32 moved = 0; mptcp_data_lock(sk); while (mptcp_can_spool_backlog(sk, &skbs)) { @@ -2287,8 +2278,8 @@ static bool mptcp_move_skbs(struct sock *sk) enqueued |= __mptcp_move_skbs(sk, &skbs, &moved); mptcp_data_lock(sk); - mptcp_backlog_spooled(sk, moved, &skbs); } + WRITE_ONCE(msk->backlog_len, msk->backlog_len - moved); mptcp_data_unlock(sk); if (enqueued && mptcp_epollin_ready(sk)) @@ -3747,12 +3738,12 @@ static void mptcp_release_cb(struct sock *sk) __must_hold(&sk->sk_lock.slock) { struct mptcp_sock *msk = mptcp_sk(sk); + u32 moved = 0; for (;;) { unsigned long flags = (msk->cb_flags & MPTCP_FLAGS_PROCESS_CTX_NEED); struct list_head join_list, skbs; bool spool_bl; - u32 moved; spool_bl = mptcp_can_spool_backlog(sk, &skbs); if (!flags && !spool_bl) @@ -3785,9 +3776,9 @@ static void mptcp_release_cb(struct sock *sk) cond_resched(); spin_lock_bh(&sk->sk_lock.slock); - if (spool_bl) - mptcp_backlog_spooled(sk, moved, &skbs); } + if (moved) + WRITE_ONCE(msk->backlog_len, msk->backlog_len - moved); if (__test_and_clear_bit(MPTCP_CLEAN_UNA, &msk->cb_flags)) __mptcp_clean_una_wakeup(sk); From 996643574cc8fc05ee70f78081f5c2af3601ecf5 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Fri, 7 Aug 2026 15:49:06 +0200 Subject: [PATCH 1244/1433] mptcp: avoid code duplication in __mptcp_move_skb() Alike TCP, MPTCP handles in-sequence packets and partially overlapping ones in a very similar way: we can use the same path to handle both, avoiding some code duplication. This will also make the next patch simpler. Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260807-net-next-mptcp-oooq-pruning-v3-6-dbc1eb853cc3@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index d96fbdc34dbb..63f2c18bc01f 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -399,6 +399,7 @@ static bool __mptcp_move_skb(struct sock *sk, struct sk_buff *skb) if (MPTCP_SKB_CB(skb)->map_seq == msk->ack_seq) { /* in sequence */ +insert: msk->bytes_received += copy_len; WRITE_ONCE(msk->ack_seq, msk->ack_seq + copy_len); tail = skb_peek_tail(&sk->sk_receive_queue); @@ -413,26 +414,20 @@ static bool __mptcp_move_skb(struct sock *sk, struct sk_buff *skb) return false; } - /* Completely old data? */ - if (!after64(MPTCP_SKB_CB(skb)->end_seq, msk->ack_seq)) { - MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_DUPDATA); - mptcp_drop(sk, skb); - return false; + /* Partial packet */ + if (after64(MPTCP_SKB_CB(skb)->end_seq, msk->ack_seq)) { + copy_len = MPTCP_SKB_CB(skb)->end_seq - msk->ack_seq; + MPTCP_SKB_CB(skb)->offset += msk->ack_seq - + MPTCP_SKB_CB(skb)->map_seq; + MPTCP_SKB_CB(skb)->map_seq += msk->ack_seq - + MPTCP_SKB_CB(skb)->map_seq; + goto insert; } - /* Partial packet: map_seq < ack_seq < end_seq. - * Skip the already-acked bytes and enqueue the new data. - */ - copy_len = MPTCP_SKB_CB(skb)->end_seq - msk->ack_seq; - MPTCP_SKB_CB(skb)->offset += msk->ack_seq - MPTCP_SKB_CB(skb)->map_seq; - MPTCP_SKB_CB(skb)->map_seq += msk->ack_seq - - MPTCP_SKB_CB(skb)->map_seq; - msk->bytes_received += copy_len; - WRITE_ONCE(msk->ack_seq, msk->ack_seq + copy_len); - - skb_set_owner_r(skb, sk); - __skb_queue_tail(&sk->sk_receive_queue, skb); - return true; + /* Completely old data */ + MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_DUPDATA); + mptcp_drop(sk, skb); + return false; } static void mptcp_stop_rtx_timer(struct sock *sk) From e468d371180d3c5b3333660bd742103b88703adf Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Fri, 7 Aug 2026 15:49:07 +0200 Subject: [PATCH 1245/1433] mptcp: implemented OoO queue pruning When moving incoming skbs in the msk receive queue and the latter is above limits, prune it as needed quite alike what TCP is doing at the subflow level. The main difference relies in the stop condition: since MPTCP does not perform collapsing, it's better off dropping the bare minimum to fit the (newer) incoming packet. Signed-off-by: Paolo Abeni Tested-by: Gang Yan Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260807-net-next-mptcp-oooq-pruning-v3-7-dbc1eb853cc3@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/mib.c | 1 + net/mptcp/mib.h | 1 + net/mptcp/protocol.c | 81 ++++++++++++++++++++++++++++++++++++++------ 3 files changed, 73 insertions(+), 10 deletions(-) diff --git a/net/mptcp/mib.c b/net/mptcp/mib.c index ef65e2df709f..2569385bab7c 100644 --- a/net/mptcp/mib.c +++ b/net/mptcp/mib.c @@ -87,6 +87,7 @@ static const struct snmp_mib mptcp_snmp_list[] = { SNMP_MIB_ITEM("WinProbe", MPTCP_MIB_WINPROBE), SNMP_MIB_ITEM("BacklogDrop", MPTCP_MIB_BACKLOGDROP), SNMP_MIB_ITEM("RcvPruned", MPTCP_MIB_RCVPRUNED), + SNMP_MIB_ITEM("OFOPruned", MPTCP_MIB_OFOPRUNED), }; /* mptcp_mib_alloc - allocate percpu mib counters diff --git a/net/mptcp/mib.h b/net/mptcp/mib.h index 9271205f682e..3a3425e258a7 100644 --- a/net/mptcp/mib.h +++ b/net/mptcp/mib.h @@ -90,6 +90,7 @@ enum linux_mptcp_mib_field { MPTCP_MIB_WINPROBE, /* MPTCP-level zero window probe */ MPTCP_MIB_BACKLOGDROP, /* Backlog over memory limit */ MPTCP_MIB_RCVPRUNED, /* Dropped due to memory constraints */ + MPTCP_MIB_OFOPRUNED, /* MPTCP-level OoO queue pruned */ __MPTCP_MIB_MAX }; diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 63f2c18bc01f..ec874d2ead6a 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -242,6 +242,65 @@ static bool mptcp_rcvbuf_grow(struct sock *sk, u32 newval) return false; } +/* "Inspired" from the TCP version; main difference: stop as soon as the MPTCP + * socket is under memory limit. + */ +static void mptcp_prune_ofo_queue(struct sock *sk, + const struct sk_buff *in_skb) +{ + struct mptcp_sock *msk = mptcp_sk(sk); + struct rb_node *node, *prev; + bool pruned = false; + u64 mem; + + if (RB_EMPTY_ROOT(&msk->out_of_order_queue)) + return; + + node = &msk->ooo_last_skb->rbnode; + + do { + struct sk_buff *skb = rb_to_skb(node); + + /* Stop pruning if the incoming skb would land in OoO tail. */ + if (after64(MPTCP_SKB_CB(in_skb)->map_seq, + MPTCP_SKB_CB(skb)->map_seq)) + break; + + pruned = true; + prev = rb_prev(node); + rb_erase(node, &msk->out_of_order_queue); + mptcp_drop(sk, skb); + msk->ooo_last_skb = rb_to_skb(prev); + + mem = (unsigned int)sk_rmem_alloc_get(sk); + if (mem <= sk->sk_rcvbuf) + break; + + node = prev; + } while (node); + + if (pruned) + MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_OFOPRUNED); +} + +/* The stack can't drop packets for fallback socket at the msk level, or the + * stream will break. + */ +static bool mptcp_can_ingest(const struct sock *sk) +{ + return unlikely(sk_rmem_alloc_get(sk) <= READ_ONCE(sk->sk_rcvbuf)) || + __mptcp_check_fallback(mptcp_sk(sk)); +} + +static bool mptcp_try_rmem_schedule(struct sock *sk, const struct sk_buff *skb) +{ + if (!mptcp_can_ingest(sk)) { + mptcp_prune_ofo_queue(sk, skb); + return mptcp_can_ingest(sk); + } + return true; +} + /* "inspired" by tcp_data_queue_ofo(), main differences: * - use mptcp seqs * - don't cope with sacks @@ -253,6 +312,12 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb) u64 seq, end_seq, max_seq; struct sk_buff *skb1; + if (!mptcp_try_rmem_schedule(sk, skb)) { + MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_RCVPRUNED); + mptcp_drop(sk, skb); + return; + } + seq = MPTCP_SKB_CB(skb)->map_seq; end_seq = MPTCP_SKB_CB(skb)->end_seq; max_seq = atomic64_read(&msk->rcv_wnd_sent); @@ -387,19 +452,15 @@ static bool __mptcp_move_skb(struct sock *sk, struct sk_buff *skb) mptcp_borrow_fwdmem(sk, skb); - /* Can't drop packets for fallback socket this late, or the stream - * will break. - */ - if (unlikely(sk_rmem_alloc_get(sk) > READ_ONCE(sk->sk_rcvbuf)) && - !__mptcp_check_fallback(msk)) { - MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_RCVPRUNED); - mptcp_drop(sk, skb); - return false; - } - if (MPTCP_SKB_CB(skb)->map_seq == msk->ack_seq) { /* in sequence */ insert: + if (!mptcp_try_rmem_schedule(sk, skb)) { + MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_RCVPRUNED); + mptcp_drop(sk, skb); + return false; + } + msk->bytes_received += copy_len; WRITE_ONCE(msk->ack_seq, msk->ack_seq + copy_len); tail = skb_peek_tail(&sk->sk_receive_queue); From e44908175d50111f5283583b0c7606fbc2bed364 Mon Sep 17 00:00:00 2001 From: Victor Raj Date: Thu, 25 Jun 2026 18:01:55 +0200 Subject: [PATCH 1246/1433] virtchnl: move virtchnl and virtchnl2 headers to 'include/linux/net/intel' virtchnl2 headers will be used by both idpf and ixd drivers, so they have to be moved to an include directory. On top of that, it would be useful to place all iavf headers together with other intel networking headers. Move abovementioned intel header files into 'include/linux/net/intel'. While at it, remove the self-include from iavf_types.h. Suggested-by: Alexander Lobakin Reviewed-by: Sridhar Samudrala Signed-off-by: Victor Raj Tested-by: Samuel Salin Signed-off-by: Larysa Zaremba Signed-off-by: Tony Nguyen --- MAINTAINERS | 3 +-- drivers/net/ethernet/intel/i40e/i40e.h | 2 +- drivers/net/ethernet/intel/i40e/i40e_common.c | 2 +- drivers/net/ethernet/intel/i40e/i40e_prototype.h | 2 +- drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.h | 2 +- drivers/net/ethernet/intel/iavf/iavf.h | 2 +- drivers/net/ethernet/intel/iavf/iavf_common.c | 2 +- drivers/net/ethernet/intel/iavf/iavf_prototype.h | 3 ++- drivers/net/ethernet/intel/iavf/iavf_types.h | 4 +--- drivers/net/ethernet/intel/ice/ice.h | 2 +- drivers/net/ethernet/intel/ice/ice_common.h | 2 +- drivers/net/ethernet/intel/ice/ice_vf_lib.h | 2 +- drivers/net/ethernet/intel/ice/virt/virtchnl.h | 2 +- drivers/net/ethernet/intel/idpf/idpf.h | 2 +- drivers/net/ethernet/intel/idpf/idpf_txrx.h | 2 +- drivers/net/ethernet/intel/idpf/idpf_virtchnl.h | 2 +- include/linux/{avf => net/intel}/virtchnl.h | 0 .../intel/idpf => include/linux/net/intel}/virtchnl2.h | 0 .../idpf => include/linux/net/intel}/virtchnl2_lan_desc.h | 0 19 files changed, 17 insertions(+), 19 deletions(-) rename include/linux/{avf => net/intel}/virtchnl.h (100%) rename {drivers/net/ethernet/intel/idpf => include/linux/net/intel}/virtchnl2.h (100%) rename {drivers/net/ethernet/intel/idpf => include/linux/net/intel}/virtchnl2_lan_desc.h (100%) diff --git a/MAINTAINERS b/MAINTAINERS index e0e7fae5b92f..0f29ea109db5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13033,8 +13033,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/tnguy/next-queue.git F: Documentation/networking/device_drivers/ethernet/intel/ F: drivers/net/ethernet/intel/ F: drivers/net/ethernet/intel/*/ -F: include/linux/avf/virtchnl.h -F: include/linux/net/intel/*/ +F: include/linux/net/intel/ INTEL ETHERNET PROTOCOL DRIVER FOR RDMA M: Tatyana Nikolova diff --git a/drivers/net/ethernet/intel/i40e/i40e.h b/drivers/net/ethernet/intel/i40e/i40e.h index 83e780919ac9..1b6a8fbaa648 100644 --- a/drivers/net/ethernet/intel/i40e/i40e.h +++ b/drivers/net/ethernet/intel/i40e/i40e.h @@ -8,8 +8,8 @@ #include #include #include -#include #include +#include #include #include #include diff --git a/drivers/net/ethernet/intel/i40e/i40e_common.c b/drivers/net/ethernet/intel/i40e/i40e_common.c index 59f5c1e810eb..8dadfef2c09f 100644 --- a/drivers/net/ethernet/intel/i40e/i40e_common.c +++ b/drivers/net/ethernet/intel/i40e/i40e_common.c @@ -1,10 +1,10 @@ // SPDX-License-Identifier: GPL-2.0 /* Copyright(c) 2013 - 2021 Intel Corporation. */ -#include #include #include #include +#include #include #include "i40e_adminq_cmd.h" #include "i40e_devids.h" diff --git a/drivers/net/ethernet/intel/i40e/i40e_prototype.h b/drivers/net/ethernet/intel/i40e/i40e_prototype.h index 26bb7bffe361..e3d57550090e 100644 --- a/drivers/net/ethernet/intel/i40e/i40e_prototype.h +++ b/drivers/net/ethernet/intel/i40e/i40e_prototype.h @@ -5,7 +5,7 @@ #define _I40E_PROTOTYPE_H_ #include -#include +#include #include "i40e_debug.h" #include "i40e_type.h" diff --git a/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.h b/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.h index f558b45725c8..4e119c0502f3 100644 --- a/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.h +++ b/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.h @@ -4,7 +4,7 @@ #ifndef _I40E_VIRTCHNL_PF_H_ #define _I40E_VIRTCHNL_PF_H_ -#include +#include #include #include "i40e_type.h" diff --git a/drivers/net/ethernet/intel/iavf/iavf.h b/drivers/net/ethernet/intel/iavf/iavf.h index 050f8241ef5e..dc31202b2a94 100644 --- a/drivers/net/ethernet/intel/iavf/iavf.h +++ b/drivers/net/ethernet/intel/iavf/iavf.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -37,7 +38,6 @@ #include #include "iavf_type.h" -#include #include "iavf_txrx.h" #include "iavf_fdir.h" #include "iavf_adv_rss.h" diff --git a/drivers/net/ethernet/intel/iavf/iavf_common.c b/drivers/net/ethernet/intel/iavf/iavf_common.c index 614a886bca99..277193a97d91 100644 --- a/drivers/net/ethernet/intel/iavf/iavf_common.c +++ b/drivers/net/ethernet/intel/iavf/iavf_common.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* Copyright(c) 2013 - 2018 Intel Corporation. */ -#include +#include #include #include "iavf_type.h" #include "iavf_adminq.h" diff --git a/drivers/net/ethernet/intel/iavf/iavf_prototype.h b/drivers/net/ethernet/intel/iavf/iavf_prototype.h index 7f9f9dbf959a..1b1f6ede3920 100644 --- a/drivers/net/ethernet/intel/iavf/iavf_prototype.h +++ b/drivers/net/ethernet/intel/iavf/iavf_prototype.h @@ -4,9 +4,10 @@ #ifndef _IAVF_PROTOTYPE_H_ #define _IAVF_PROTOTYPE_H_ +#include + #include "iavf_type.h" #include "iavf_alloc.h" -#include /* Prototypes for shared code functions that are not in * the standard function pointer structures. These are diff --git a/drivers/net/ethernet/intel/iavf/iavf_types.h b/drivers/net/ethernet/intel/iavf/iavf_types.h index a095855122bf..35d6d8fcca04 100644 --- a/drivers/net/ethernet/intel/iavf/iavf_types.h +++ b/drivers/net/ethernet/intel/iavf/iavf_types.h @@ -4,9 +4,7 @@ #ifndef _IAVF_TYPES_H_ #define _IAVF_TYPES_H_ -#include "iavf_types.h" - -#include +#include #include /* structure used to queue PTP commands for processing */ diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h index fc91b6665f90..db3c7015c56c 100644 --- a/drivers/net/ethernet/intel/ice/ice.h +++ b/drivers/net/ethernet/intel/ice/ice.h @@ -36,7 +36,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/drivers/net/ethernet/intel/ice/ice_common.h b/drivers/net/ethernet/intel/ice/ice_common.h index 9f5344212195..d1d674ca644f 100644 --- a/drivers/net/ethernet/intel/ice/ice_common.h +++ b/drivers/net/ethernet/intel/ice/ice_common.h @@ -5,13 +5,13 @@ #define _ICE_COMMON_H_ #include +#include #include "ice.h" #include "ice_type.h" #include "ice_nvm.h" #include "ice_flex_pipe.h" #include "ice_parser.h" -#include #include "ice_switch.h" #include "ice_fdir.h" diff --git a/drivers/net/ethernet/intel/ice/ice_vf_lib.h b/drivers/net/ethernet/intel/ice/ice_vf_lib.h index 7a9c75d1d07c..fa436b3b1eac 100644 --- a/drivers/net/ethernet/intel/ice/ice_vf_lib.h +++ b/drivers/net/ethernet/intel/ice/ice_vf_lib.h @@ -8,9 +8,9 @@ #include #include #include +#include #include #include -#include #include "ice_type.h" #include "ice_flow.h" #include "virt/fdir.h" diff --git a/drivers/net/ethernet/intel/ice/virt/virtchnl.h b/drivers/net/ethernet/intel/ice/virt/virtchnl.h index 71bb456e2d71..d11789b3ae1f 100644 --- a/drivers/net/ethernet/intel/ice/virt/virtchnl.h +++ b/drivers/net/ethernet/intel/ice/virt/virtchnl.h @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include "ice_vf_lib.h" /* Restrict number of MAC Addr and VLAN that non-trusted VF can programmed */ diff --git a/drivers/net/ethernet/intel/idpf/idpf.h b/drivers/net/ethernet/intel/idpf/idpf.h index ec1b75f039bb..984944bab28b 100644 --- a/drivers/net/ethernet/intel/idpf/idpf.h +++ b/drivers/net/ethernet/intel/idpf/idpf.h @@ -23,8 +23,8 @@ struct idpf_rss_data; #include #include +#include -#include "virtchnl2.h" #include "idpf_txrx.h" #include "idpf_controlq.h" diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.h b/drivers/net/ethernet/intel/idpf/idpf_txrx.h index 908dfa28674e..9103580c0a3d 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.h +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.h @@ -5,6 +5,7 @@ #define _IDPF_TXRX_H_ #include +#include #include #include @@ -13,7 +14,6 @@ #include #include "idpf_lan_txrx.h" -#include "virtchnl2_lan_desc.h" #define IDPF_LARGE_MAX_Q 256 #define IDPF_MAX_Q 16 diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h index 9b1c9c86f6ea..5f8ae8c5be20 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h @@ -4,7 +4,7 @@ #ifndef _IDPF_VIRTCHNL_H_ #define _IDPF_VIRTCHNL_H_ -#include "virtchnl2.h" +#include #define IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC (60 * 1000) #define IDPF_VC_XN_IDX_M GENMASK(7, 0) diff --git a/include/linux/avf/virtchnl.h b/include/linux/net/intel/virtchnl.h similarity index 100% rename from include/linux/avf/virtchnl.h rename to include/linux/net/intel/virtchnl.h diff --git a/drivers/net/ethernet/intel/idpf/virtchnl2.h b/include/linux/net/intel/virtchnl2.h similarity index 100% rename from drivers/net/ethernet/intel/idpf/virtchnl2.h rename to include/linux/net/intel/virtchnl2.h diff --git a/drivers/net/ethernet/intel/idpf/virtchnl2_lan_desc.h b/include/linux/net/intel/virtchnl2_lan_desc.h similarity index 100% rename from drivers/net/ethernet/intel/idpf/virtchnl2_lan_desc.h rename to include/linux/net/intel/virtchnl2_lan_desc.h From dbe9759c215a3d273f8a6e5ad3c8f579645a5759 Mon Sep 17 00:00:00 2001 From: Phani R Burra Date: Thu, 25 Jun 2026 18:01:56 +0200 Subject: [PATCH 1247/1433] libie: add PCI device initialization helpers to libie idpf and ixd drivers serve different PCI functions on the same device, therefore their PCI configuration flow is very similar. Add support functions for idpf and ixd to configure PCI functionality and access MMIO space. Add a mapping list which can be traversed by a driver, e.g. to pass certain I/O mappings to the auxbus devices. Such list is also traversed by the libie_pci_get_mmio_addr() helper, which allows for easier memory access. Reviewed-by: Maciej Fijalkowski Signed-off-by: Phani R Burra Co-developed-by: Victor Raj Signed-off-by: Victor Raj Co-developed-by: Sridhar Samudrala Signed-off-by: Sridhar Samudrala Co-developed-by: Pavan Kumar Linga Signed-off-by: Pavan Kumar Linga Tested-by: Bharath R Tested-by: Samuel Salin Co-developed-by: Larysa Zaremba Signed-off-by: Larysa Zaremba Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/libie/Kconfig | 6 + drivers/net/ethernet/intel/libie/Makefile | 4 + drivers/net/ethernet/intel/libie/pci.c | 229 ++++++++++++++++++++++ include/linux/net/intel/libie/pci.h | 56 ++++++ 4 files changed, 295 insertions(+) create mode 100644 drivers/net/ethernet/intel/libie/pci.c create mode 100644 include/linux/net/intel/libie/pci.h diff --git a/drivers/net/ethernet/intel/libie/Kconfig b/drivers/net/ethernet/intel/libie/Kconfig index 70831c7e336e..500a95c944a8 100644 --- a/drivers/net/ethernet/intel/libie/Kconfig +++ b/drivers/net/ethernet/intel/libie/Kconfig @@ -23,3 +23,9 @@ config LIBIE_FWLOG for it. Firmware logging is using admin queue interface to communicate with the device. Debugfs is a user interface used to config logging and dump all collected logs. + +config LIBIE_PCI + tristate + help + Helper functions for management of PCI resources belonging + to networking devices. diff --git a/drivers/net/ethernet/intel/libie/Makefile b/drivers/net/ethernet/intel/libie/Makefile index db57fc6780ea..a28509cb9086 100644 --- a/drivers/net/ethernet/intel/libie/Makefile +++ b/drivers/net/ethernet/intel/libie/Makefile @@ -12,3 +12,7 @@ libie_adminq-y := adminq.o obj-$(CONFIG_LIBIE_FWLOG) += libie_fwlog.o libie_fwlog-y := fwlog.o + +obj-$(CONFIG_LIBIE_PCI) += libie_pci.o + +libie_pci-y := pci.o diff --git a/drivers/net/ethernet/intel/libie/pci.c b/drivers/net/ethernet/intel/libie/pci.c new file mode 100644 index 000000000000..b756c1186ce9 --- /dev/null +++ b/drivers/net/ethernet/intel/libie/pci.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (C) 2025 Intel Corporation */ + +#include + +/** + * libie_find_mmio_region - find MMIO region containing a range + * @mmio_list: list that contains MMIO region info + * @offset: range start offset + * @size: range size + * @bar_idx: BAR index containing the range to search + * + * MMIO regions mappings are traversed from oldest to newest. + * + * Return: pointer to a MMIO region overlapping with the range in any way or + * NULL if no such region is mapped. + */ +static struct libie_pci_mmio_region * +libie_find_mmio_region(const struct list_head *mmio_list, + resource_size_t offset, resource_size_t size, + int bar_idx) +{ + resource_size_t end_offset = offset + size; + struct libie_pci_mmio_region *mr; + + list_for_each_entry(mr, mmio_list, list) { + resource_size_t mr_end = mr->offset + mr->size; + resource_size_t mr_start = mr->offset; + + if (mr->bar_idx != bar_idx) + continue; + if (offset < mr_end && end_offset > mr_start) + return mr; + } + + return NULL; +} + +/** + * __libie_pci_get_mmio_addr - get the MMIO virtual address + * @mmio_info: contains list of MMIO regions + * @offset: register offset to find + * @num_args: number of additional arguments present + * @...: optional BAR index (0 by default) + * + * This function finds the virtual address of a register offset by iterating + * through the non-linear MMIO regions that are mapped by the driver. + * + * The list is traversed oldest to newest, so accessing an older mapping + * via this function while deleting a newer one is allowed. + * + * Return: valid MMIO virtual address or NULL. + */ +void __iomem *__libie_pci_get_mmio_addr(struct libie_mmio_info *mmio_info, + resource_size_t offset, + int num_args, ...) +{ + struct libie_pci_mmio_region *mr; + int bar_idx = 0; + va_list args; + + if (num_args) { + va_start(args, num_args); + bar_idx = va_arg(args, int); + va_end(args); + } + + list_for_each_entry(mr, &mmio_info->mmio_list, list) + if (bar_idx == mr->bar_idx && offset >= mr->offset && + offset < mr->offset + mr->size) { + offset -= mr->offset; + + return mr->addr + offset; + } + + WARN_ONCE(true, "Access to an unmapped MMIO region (BAR%d, offset %pa)", + bar_idx, &offset); + + return NULL; +} +EXPORT_SYMBOL_NS_GPL(__libie_pci_get_mmio_addr, "LIBIE_PCI"); + +/** + * __libie_pci_map_mmio_region - map PCI device MMIO region + * @mmio_info: struct to store the mapped MMIO region + * @offset: MMIO region start offset + * @size: MMIO region size + * @num_args: number of additional arguments present + * @...: optional BAR index (0 by default) + * + * Return: true if the requested address range is accessible through + * new or existing mapping, false otherwise. + */ +bool __libie_pci_map_mmio_region(struct libie_mmio_info *mmio_info, + resource_size_t offset, + resource_size_t size, int num_args, ...) +{ + struct pci_dev *pdev = mmio_info->pdev; + struct libie_pci_mmio_region *mr; + resource_size_t end_offset; + void __iomem *va; + int bar_idx = 0; + va_list args; + + if (num_args) { + va_start(args, num_args); + bar_idx = va_arg(args, int); + va_end(args); + } + + if (bar_idx >= PCI_STD_NUM_BARS || bar_idx < 0 || + !pci_resource_is_mem(pdev, bar_idx)) + return false; + + /* pci_iomap_range() silently maps less + * if the requested length is too big + */ + if (!size || check_add_overflow(offset, size, &end_offset) || + end_offset > pci_resource_len(pdev, bar_idx)) + return false; + + mr = libie_find_mmio_region(&mmio_info->mmio_list, offset, size, + bar_idx); + if (mr) { + pci_warn(pdev, + "Mapping of BAR%u (offset=%llu, size=%llu) intersecting region (offset=%llu, size=%llu) already exists\n", + bar_idx, (unsigned long long)mr->offset, + (unsigned long long)mr->size, + (unsigned long long)offset, (unsigned long long)size); + return mr->offset <= offset && + mr->offset + mr->size >= end_offset; + } + + va = pci_iomap_range(mmio_info->pdev, bar_idx, offset, size); + if (!va) { + pci_err(pdev, "Failed to map BAR%u region\n", bar_idx); + return false; + } + + mr = kvzalloc_obj(*mr); + if (!mr) { + pci_iounmap(pdev, va); + return false; + } + + mr->addr = va; + mr->offset = offset; + mr->size = size; + mr->bar_idx = bar_idx; + + list_add_tail(&mr->list, &mmio_info->mmio_list); + + return true; +} +EXPORT_SYMBOL_NS_GPL(__libie_pci_map_mmio_region, "LIBIE_PCI"); + +/** + * libie_pci_unmap_fltr_regs - unmap selected PCI device MMIO regions + * @mmio_info: contains list of MMIO regions to unmap + * @fltr: returns true, if region is to be unmapped + */ +void libie_pci_unmap_fltr_regs(struct libie_mmio_info *mmio_info, + bool (*fltr)(struct libie_mmio_info *mmio_info, + struct libie_pci_mmio_region *reg)) +{ + struct libie_pci_mmio_region *mr, *tmp; + + list_for_each_entry_safe(mr, tmp, &mmio_info->mmio_list, list) { + if (!fltr(mmio_info, mr)) + continue; + list_del(&mr->list); + pci_iounmap(mmio_info->pdev, mr->addr); + kvfree(mr); + } +} +EXPORT_SYMBOL_NS_GPL(libie_pci_unmap_fltr_regs, "LIBIE_PCI"); + +/** + * libie_pci_unmap_all_mmio_regions - unmap all PCI device MMIO regions + * @mmio_info: contains list of MMIO regions to unmap + */ +void libie_pci_unmap_all_mmio_regions(struct libie_mmio_info *mmio_info) +{ + struct libie_pci_mmio_region *mr, *tmp; + + list_for_each_entry_safe(mr, tmp, &mmio_info->mmio_list, list) { + list_del(&mr->list); + pci_iounmap(mmio_info->pdev, mr->addr); + kvfree(mr); + } +} +EXPORT_SYMBOL_NS_GPL(libie_pci_unmap_all_mmio_regions, "LIBIE_PCI"); + +/** + * libie_pci_init_dev - enable and configure the device + * @pdev: PCI device information + * + * Enable the device, request memory regions, set 64-bit DMA mask + * and coherent DMA mask, and enable bus-mastering + * + * Return: %0 on success, -%errno on failure. + */ +int libie_pci_init_dev(struct pci_dev *pdev) +{ + int err; + + err = pcim_enable_device(pdev); + if (err) + return err; + + for (int bar = 0; bar < PCI_STD_NUM_BARS; bar++) + if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM) { + err = pcim_request_region(pdev, bar, pci_name(pdev)); + if (err) + return err; + } + + err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + if (err) + return err; + + pci_set_master(pdev); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(libie_pci_init_dev, "LIBIE_PCI"); + +MODULE_DESCRIPTION("Common Ethernet PCI library"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/net/intel/libie/pci.h b/include/linux/net/intel/libie/pci.h new file mode 100644 index 000000000000..effd072c55c8 --- /dev/null +++ b/include/linux/net/intel/libie/pci.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2025 Intel Corporation */ + +#ifndef __LIBIE_PCI_H +#define __LIBIE_PCI_H + +#include + +/** + * struct libie_pci_mmio_region - structure for MMIO region info + * @list: used to add a MMIO region to the list of MMIO regions in + * libie_mmio_info + * @addr: virtual address of MMIO region start + * @offset: start offset of the MMIO region + * @size: size of the MMIO region + * @bar_idx: BAR index to which the MMIO region belongs to + */ +struct libie_pci_mmio_region { + struct list_head list; + void __iomem *addr; + resource_size_t offset; + resource_size_t size; + u16 bar_idx; +}; + +/** + * struct libie_mmio_info - contains list of MMIO regions + * @pdev: PCI device pointer + * @mmio_list: list of MMIO regions + */ +struct libie_mmio_info { + struct pci_dev *pdev; + struct list_head mmio_list; +}; + +#define libie_pci_map_mmio_region(mmio_info, offset, size, ...) \ + __libie_pci_map_mmio_region(mmio_info, offset, size, \ + COUNT_ARGS(__VA_ARGS__), ##__VA_ARGS__) + +#define libie_pci_get_mmio_addr(mmio_info, offset, ...) \ + __libie_pci_get_mmio_addr(mmio_info, offset, \ + COUNT_ARGS(__VA_ARGS__), ##__VA_ARGS__) + +bool __libie_pci_map_mmio_region(struct libie_mmio_info *mmio_info, + resource_size_t offset, resource_size_t size, + int num_args, ...); +void __iomem *__libie_pci_get_mmio_addr(struct libie_mmio_info *mmio_info, + resource_size_t offset, + int num_args, ...); +void libie_pci_unmap_all_mmio_regions(struct libie_mmio_info *mmio_info); +void libie_pci_unmap_fltr_regs(struct libie_mmio_info *mmio_info, + bool (*fltr)(struct libie_mmio_info *mmio_info, + struct libie_pci_mmio_region *reg)); +int libie_pci_init_dev(struct pci_dev *pdev); + +#endif /* __LIBIE_PCI_H */ From 354c28830add98017193e61f1d87f106e2514117 Mon Sep 17 00:00:00 2001 From: Pavan Kumar Linga Date: Thu, 25 Jun 2026 18:01:57 +0200 Subject: [PATCH 1248/1433] libeth: allow to create fill queues without NAPI Control queues can utilize libeth_rx fill queues, despite working outside of NAPI context. The only problem is standard fill queues requiring NAPI that provides them with the device pointer. Introduce a way to provide the device directly without using NAPI. Suggested-by: Alexander Lobakin Reviewed-by: Maciej Fijalkowski Signed-off-by: Pavan Kumar Linga Tested-by: Bharath R Tested-by: Samuel Salin Signed-off-by: Larysa Zaremba Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/libeth/rx.c | 12 ++++++++---- include/net/libeth/rx.h | 4 +++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/intel/libeth/rx.c b/drivers/net/ethernet/intel/libeth/rx.c index 62521a1f4ec9..0c1a565a1b3a 100644 --- a/drivers/net/ethernet/intel/libeth/rx.c +++ b/drivers/net/ethernet/intel/libeth/rx.c @@ -145,25 +145,29 @@ static bool libeth_rx_page_pool_params_zc(struct libeth_fq *fq, /** * libeth_rx_fq_create - create a PP with the default libeth settings * @fq: buffer queue struct to fill - * @napi: &napi_struct covering this PP (no usage outside its poll loops) + * @napi_dev: &napi_struct for NAPI (data) queues, &device for others * * Return: %0 on success, -%errno on failure. */ -int libeth_rx_fq_create(struct libeth_fq *fq, struct napi_struct *napi) +int libeth_rx_fq_create(struct libeth_fq *fq, void *napi_dev) { + struct napi_struct *napi = fq->no_napi ? NULL : napi_dev; struct page_pool_params pp = { .flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV, .order = LIBETH_RX_PAGE_ORDER, .pool_size = fq->count, .nid = fq->nid, - .dev = napi->dev->dev.parent, - .netdev = napi->dev, + .dev = napi ? napi->dev->dev.parent : napi_dev, + .netdev = napi ? napi->dev : NULL, .napi = napi, }; struct libeth_fqe *fqes; struct page_pool *pool; int ret; + if (!pp.netdev && fq->type == LIBETH_FQE_MTU) + return -EINVAL; + pp.dma_dir = fq->xdp ? DMA_BIDIRECTIONAL : DMA_FROM_DEVICE; if (!fq->hsplit) diff --git a/include/net/libeth/rx.h b/include/net/libeth/rx.h index 5d991404845e..0e736846c5e8 100644 --- a/include/net/libeth/rx.h +++ b/include/net/libeth/rx.h @@ -69,6 +69,7 @@ enum libeth_fqe_type { * @type: type of the buffers this queue has * @hsplit: flag whether header split is enabled * @xdp: flag indicating whether XDP is enabled + * @no_napi: the queue is not a data queue and does not have NAPI * @buf_len: HW-writeable length per each buffer * @nid: ID of the closest NUMA node with memory */ @@ -85,12 +86,13 @@ struct libeth_fq { enum libeth_fqe_type type:2; bool hsplit:1; bool xdp:1; + bool no_napi:1; u32 buf_len; int nid; }; -int libeth_rx_fq_create(struct libeth_fq *fq, struct napi_struct *napi); +int libeth_rx_fq_create(struct libeth_fq *fq, void *napi_dev); void libeth_rx_fq_destroy(struct libeth_fq *fq); /** From caed480f29914c3ce2a83107f6e0da4f0c5974b3 Mon Sep 17 00:00:00 2001 From: Phani R Burra Date: Thu, 25 Jun 2026 18:01:58 +0200 Subject: [PATCH 1249/1433] libie: add control queue support Libie will now support control queue setup and configuration APIs. These are mainly used for mailbox communication between drivers and control plane. Make use of the libeth_rx page pool support for managing controlq buffers. Reviewed-by: Maciej Fijalkowski Signed-off-by: Phani R Burra Co-developed-by: Victor Raj Signed-off-by: Victor Raj Co-developed-by: Sridhar Samudrala Signed-off-by: Sridhar Samudrala Co-developed-by: Pavan Kumar Linga Signed-off-by: Pavan Kumar Linga Tested-by: Samuel Salin Tested-by: Bharath R Co-developed-by: Larysa Zaremba Signed-off-by: Larysa Zaremba Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/libie/Kconfig | 8 + drivers/net/ethernet/intel/libie/Makefile | 4 + drivers/net/ethernet/intel/libie/controlq.c | 672 ++++++++++++++++++++ include/linux/net/intel/libie/controlq.h | 276 ++++++++ 4 files changed, 960 insertions(+) create mode 100644 drivers/net/ethernet/intel/libie/controlq.c create mode 100644 include/linux/net/intel/libie/controlq.h diff --git a/drivers/net/ethernet/intel/libie/Kconfig b/drivers/net/ethernet/intel/libie/Kconfig index 500a95c944a8..9c5fdebb6766 100644 --- a/drivers/net/ethernet/intel/libie/Kconfig +++ b/drivers/net/ethernet/intel/libie/Kconfig @@ -15,6 +15,14 @@ config LIBIE_ADMINQ Helper functions used by Intel Ethernet drivers for administration queue command interface (aka adminq). +config LIBIE_CP + tristate + select LIBETH + select LIBIE_PCI + help + Common helper routines to communicate with the device Control Plane + using virtchnl2 or related mailbox protocols. + config LIBIE_FWLOG tristate select LIBIE_ADMINQ diff --git a/drivers/net/ethernet/intel/libie/Makefile b/drivers/net/ethernet/intel/libie/Makefile index a28509cb9086..3065aa057798 100644 --- a/drivers/net/ethernet/intel/libie/Makefile +++ b/drivers/net/ethernet/intel/libie/Makefile @@ -9,6 +9,10 @@ obj-$(CONFIG_LIBIE_ADMINQ) += libie_adminq.o libie_adminq-y := adminq.o +obj-$(CONFIG_LIBIE_CP) += libie_cp.o + +libie_cp-y := controlq.o + obj-$(CONFIG_LIBIE_FWLOG) += libie_fwlog.o libie_fwlog-y := fwlog.o diff --git a/drivers/net/ethernet/intel/libie/controlq.c b/drivers/net/ethernet/intel/libie/controlq.c new file mode 100644 index 000000000000..f0ff10f99bc2 --- /dev/null +++ b/drivers/net/ethernet/intel/libie/controlq.c @@ -0,0 +1,672 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (C) 2025 Intel Corporation */ + +#include +#include + +#include + +#define LIBIE_CTLQ_DESC_QWORD0(sz) \ + (LIBIE_CTLQ_DESC_FLAG_BUF | \ + LIBIE_CTLQ_DESC_FLAG_RD | \ + FIELD_PREP(LIBIE_CTLQ_DESC_DATA_LEN, sz)) + +/** + * libie_ctlq_free_fq - free fill queue resources, including buffers + * @ctlq: Rx control queue whose resources need to be freed + */ +static void libie_ctlq_free_fq(struct libie_ctlq_info *ctlq) +{ + struct libeth_fq fq = { + .fqes = ctlq->rx_fqes, + .pp = ctlq->pp, + }; + + for (u32 ntc = ctlq->next_to_clean; ntc != ctlq->next_to_post; ) { + page_pool_put_full_netmem(fq.pp, fq.fqes[ntc].netmem, false); + + if (++ntc >= ctlq->ring_len) + ntc = 0; + } + + libeth_rx_fq_destroy(&fq); +} + +/** + * libie_ctlq_init_fq - initialize fill queue for an Rx controlq + * @ctlq: control queue that needs Rx buffer allocation + * + * Return: %0 on success, -%errno on failure + */ +static int libie_ctlq_init_fq(struct libie_ctlq_info *ctlq) +{ + struct libeth_fq fq = { + .count = ctlq->ring_len, + .truesize = LIBIE_CTLQ_MAX_BUF_LEN, + .nid = NUMA_NO_NODE, + .type = LIBETH_FQE_SHORT, + .hsplit = true, + .no_napi = true, + }; + int err; + + err = libeth_rx_fq_create(&fq, ctlq->dev); + if (err) + return err; + + ctlq->pp = fq.pp; + ctlq->rx_fqes = fq.fqes; + ctlq->truesize = fq.truesize; + + return 0; +} + +/** + * libie_ctlq_prep_rx_desc - prepare the descriptor with a new address + * @desc: descriptor to (re)initialize + * @addr: physical address to put into descriptor + * @mem_truesize: size of the accessible memory + */ +static void libie_ctlq_prep_rx_desc(struct libie_ctlq_desc *desc, + dma_addr_t addr, u32 mem_truesize) +{ + u64 qword; + + qword = LIBIE_CTLQ_DESC_QWORD0(mem_truesize); + desc->qword0 = cpu_to_le64(qword); + + qword = FIELD_PREP(LIBIE_CTLQ_DESC_DATA_ADDR_HIGH, + upper_32_bits(addr)) | + FIELD_PREP(LIBIE_CTLQ_DESC_DATA_ADDR_LOW, + lower_32_bits(addr)); + desc->qword3 = cpu_to_le64(qword); +} + +/** + * libie_ctlq_post_rx_buffs - post buffers to descriptor ring + * @ctlq: control queue that requires Rx descriptor ring to be initialized with + * new Rx buffers + * + * The caller must make sure that calls to libie_ctlq_post_rx_buffs() + * and libie_ctlq_recv() for each queue are either serialized + * or used under ctlq->lock. + * + * Return: %0 on success, -%ENOMEM if any buffer could not be allocated + */ +int libie_ctlq_post_rx_buffs(struct libie_ctlq_info *ctlq) +{ + u32 ntp = ctlq->next_to_post, ntc = ctlq->next_to_clean, num_to_post; + const struct libeth_fq_fp fq = { + .pp = ctlq->pp, + .fqes = ctlq->rx_fqes, + .truesize = ctlq->truesize, + .count = ctlq->ring_len, + }; + int ret = 0; + + num_to_post = (ntc > ntp ? 0 : ctlq->ring_len) + ntc - ntp - 1; + + while (num_to_post--) { + dma_addr_t addr; + + ctlq->descs[ntp] = (struct libie_ctlq_desc) {}; + + addr = libeth_rx_alloc(&fq, ntp); + if (unlikely(addr == DMA_MAPPING_ERROR)) { + ret = -ENOMEM; + goto post_bufs; + } + + libie_ctlq_prep_rx_desc(&ctlq->descs[ntp], addr, fq.truesize); + + if (unlikely(++ntp == ctlq->ring_len)) + ntp = 0; + } + +post_bufs: + if (likely(ctlq->next_to_post != ntp)) { + ctlq->next_to_post = ntp; + + dma_wmb(); + writel(ntp, ctlq->reg.tail); + } + + return ret; +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_post_rx_buffs, "LIBIE_CP"); + +/** + * libie_ctlq_free_tx_msgs - Free Tx control queue messages + * @ctlq: Tx control queue being destroyed + * @num_msgs: number of messages allocated so far + */ +static void libie_ctlq_free_tx_msgs(struct libie_ctlq_info *ctlq, + u32 num_msgs) +{ + for (u32 i = 0; i < num_msgs; i++) + kfree(ctlq->tx_msg[i]); + + kvfree(ctlq->tx_msg); +} + +/** + * libie_ctlq_alloc_tx_msgs - Allocate Tx control queue messages + * @ctlq: Tx control queue being created + * + * Return: %0 on success, -%ENOMEM on allocation error + */ +static int libie_ctlq_alloc_tx_msgs(struct libie_ctlq_info *ctlq) +{ + ctlq->tx_msg = kvzalloc_objs(*ctlq->tx_msg, ctlq->ring_len, + GFP_KERNEL); + if (!ctlq->tx_msg) + return -ENOMEM; + + for (u32 i = 0; i < ctlq->ring_len; i++) { + ctlq->tx_msg[i] = kzalloc_obj(*ctlq->tx_msg[i]); + if (!ctlq->tx_msg[i]) { + libie_ctlq_free_tx_msgs(ctlq, i); + return -ENOMEM; + } + } + + return 0; +} + +/** + * libie_cp_free_desc_mem - free the previously allocated descriptor DMA memory + * @dev: device information + * @mem: DMA memory information + */ +static void libie_cp_free_desc_mem(struct device *dev, + struct libie_cp_dma_mem *mem) +{ + dma_free_coherent(dev, mem->size, mem->va, mem->pa); + mem->va = NULL; +} + +/** + * libie_ctlq_dealloc_ring_res - free memory allocated for control queue + * @ctlq: control queue that requires its ring memory to be freed + * + * Free the memory used by the ring, buffers and other related structures. + */ +static void libie_ctlq_dealloc_ring_res(struct libie_ctlq_info *ctlq) +{ + struct libie_cp_dma_mem *dma = &ctlq->ring_mem; + + if (ctlq->type == LIBIE_CTLQ_TYPE_TX) + libie_ctlq_free_tx_msgs(ctlq, ctlq->ring_len); + else + libie_ctlq_free_fq(ctlq); + + libie_cp_free_desc_mem(ctlq->dev, dma); +} + +/** + * libie_cp_alloc_desc_mem - allocate DMA memory for descriptor ring + * @dev: device information + * @mem: memory for DMA information to be stored + * @size: size of the memory to allocate + * + * Return: virtual address of DMA memory or NULL. + */ +static void *libie_cp_alloc_desc_mem(struct device *dev, + struct libie_cp_dma_mem *mem, u32 size) +{ + size = LARGEST_ALIGN(size); + + mem->va = dma_alloc_coherent(dev, size, &mem->pa, GFP_KERNEL); + mem->size = size; + mem->direction = DMA_BIDIRECTIONAL; + + return mem->va; +} + +/** + * libie_ctlq_alloc_queue_res - allocate memory for descriptor ring and bufs + * @ctlq: control queue that requires its ring resources to be allocated + * + * Return: %0 on success, -%errno on failure + */ +static int libie_ctlq_alloc_queue_res(struct libie_ctlq_info *ctlq) +{ + size_t size = array_size(ctlq->ring_len, sizeof(*ctlq->descs)); + struct libie_cp_dma_mem *dma = &ctlq->ring_mem; + int err = -ENOMEM; + + if (!libie_cp_alloc_desc_mem(ctlq->dev, dma, size)) + return -ENOMEM; + + ctlq->descs = dma->va; + + if (ctlq->type == LIBIE_CTLQ_TYPE_TX) { + if (libie_ctlq_alloc_tx_msgs(ctlq)) + goto free_dma_mem; + } else { + err = libie_ctlq_init_fq(ctlq); + if (err) + goto free_dma_mem; + + err = libie_ctlq_post_rx_buffs(ctlq); + if (err) { + libie_ctlq_free_fq(ctlq); + goto free_dma_mem; + } + } + + return 0; + +free_dma_mem: + libie_cp_free_desc_mem(ctlq->dev, dma); + + return err; +} + +/** + * libie_ctlq_init_regs - Initialize control queue registers + * @ctlq: control queue that needs to be initialized + * + * Initialize registers. The caller is expected to have already initialized the + * descriptor ring memory and buffer memory. + */ +static void libie_ctlq_init_regs(struct libie_ctlq_info *ctlq) +{ + u32 dword; + + if (ctlq->type == LIBIE_CTLQ_TYPE_RX) + writel(ctlq->ring_len - 1, ctlq->reg.tail); + else + writel(0, ctlq->reg.tail); + + writel(0, ctlq->reg.head); + writel(lower_32_bits(ctlq->ring_mem.pa), ctlq->reg.addr_low); + writel(upper_32_bits(ctlq->ring_mem.pa), ctlq->reg.addr_high); + + dword = FIELD_PREP(LIBIE_CTLQ_MBX_ATQ_LEN, ctlq->ring_len) | + ctlq->reg.len_ena_mask; + writel(dword, ctlq->reg.len); +} + +/** + * libie_find_ctlq - find the controlq for the given id and type + * @ctx: libie CP context information + * @type: type of controlq to find + * @id: controlq id to find + * + * Return: control queue info pointer on success, NULL on failure + */ +struct libie_ctlq_info *libie_find_ctlq(struct libie_ctlq_ctx *ctx, + enum libie_ctlq_type type, + int id) +{ + struct libie_ctlq_info *cq; + + guard(spinlock)(&ctx->ctlqs_lock); + + list_for_each_entry(cq, &ctx->ctlqs, list) + if (cq->qid == id && cq->type == type) + return cq; + + return NULL; +} +EXPORT_SYMBOL_NS_GPL(libie_find_ctlq, "LIBIE_CP"); + +/** + * libie_ctlq_add - add one control queue + * @ctx: libie CP context information + * @qinfo: information required for queue creation + * + * Allocate and initialize a control queue and add it to the control queue list. + * libie_ctlq_init() must be called prior to any calls to libie_ctlq_add. + * + * Return: added control queue info pointer on success, error pointer on failure + */ +static struct libie_ctlq_info * +libie_ctlq_add(struct libie_ctlq_ctx *ctx, + const struct libie_ctlq_create_info *qinfo) +{ + struct libie_ctlq_info *ctlq; + int err; + + if (qinfo->id != LIBIE_CTLQ_MBX_ID) + return ERR_PTR(-EOPNOTSUPP); + + if (qinfo->len > FIELD_MAX(LIBIE_CTLQ_MBX_ATQ_LEN) || !qinfo->len) + return ERR_PTR(-EINVAL); + + ctlq = kvzalloc_obj(*ctlq); + if (!ctlq) + return ERR_PTR(-ENOMEM); + + ctlq->type = qinfo->type; + ctlq->qid = qinfo->id; + ctlq->ring_len = qinfo->len; + ctlq->dev = &ctx->mmio_info.pdev->dev; + ctlq->reg = qinfo->reg; + + err = libie_ctlq_alloc_queue_res(ctlq); + if (err) { + kvfree(ctlq); + return ERR_PTR(err); + } + + libie_ctlq_init_regs(ctlq); + + spin_lock_init(&ctlq->lock); + + scoped_guard(spinlock, &ctx->ctlqs_lock) + list_add(&ctlq->list, &ctx->ctlqs); + + return ctlq; +} + +/** + * libie_ctlq_remove - deallocate and remove specified control queue + * @ctx: libie CP context information + * @ctlq: specific control queue that needs to be removed + */ +static void libie_ctlq_remove(struct libie_ctlq_ctx *ctx, + struct libie_ctlq_info *ctlq) +{ + scoped_guard(spinlock, &ctx->ctlqs_lock) + list_del(&ctlq->list); + + libie_ctlq_dealloc_ring_res(ctlq); + kvfree(ctlq); +} + +/** + * libie_ctlq_init - main initialization routine for all control queues + * @ctx: libie CP context information + * @qinfo: array of structs containing info for each queue to be initialized + * @numq: number of queues to initialize + * + * This initializes queue list and adds any number and any type of control + * queues. This is an all or nothing routine; if one fails, all previously + * allocated queues will be destroyed. + * + * Please note that any control queue send/receive functions are not + * softirq/NAPI safe, and therefore API can be used in process context only. + * + * Return: %0 on success, -%errno on failure + */ +int libie_ctlq_init(struct libie_ctlq_ctx *ctx, + const struct libie_ctlq_create_info *qinfo, + u32 numq) +{ + INIT_LIST_HEAD(&ctx->ctlqs); + spin_lock_init(&ctx->ctlqs_lock); + + for (u32 i = 0; i < numq; i++) { + struct libie_ctlq_info *ctlq; + + ctlq = libie_ctlq_add(ctx, &qinfo[i]); + if (IS_ERR(ctlq)) { + libie_ctlq_deinit(ctx); + return PTR_ERR(ctlq); + } + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_init, "LIBIE_CP"); + +/** + * libie_ctlq_deinit - destroy all control queues + * @ctx: libie CP context information + */ +void libie_ctlq_deinit(struct libie_ctlq_ctx *ctx) +{ + struct libie_ctlq_info *ctlq, *tmp; + + list_for_each_entry_safe(ctlq, tmp, &ctx->ctlqs, list) + libie_ctlq_remove(ctx, ctlq); +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_deinit, "LIBIE_CP"); + +/** + * libie_ctlq_tx_desc_from_msg - initialize a Tx descriptor from a message + * @desc: descriptor to be initialized + * @msg: filled control queue message + */ +static void libie_ctlq_tx_desc_from_msg(struct libie_ctlq_desc *desc, + const struct libie_ctlq_msg *msg) +{ + const struct libie_cp_dma_mem *dma = &msg->send_mem; + u64 qword; + + qword = FIELD_PREP(LIBIE_CTLQ_DESC_FLAGS, msg->flags) | + FIELD_PREP(LIBIE_CTLQ_DESC_INFRA_OPCODE, msg->opcode) | + FIELD_PREP(LIBIE_CTLQ_DESC_PFID_VFID, msg->func_id); + desc->qword0 = cpu_to_le64(qword); + + qword = FIELD_PREP(LIBIE_CTLQ_DESC_VIRTCHNL_OPCODE, + msg->chnl_opcode) | + FIELD_PREP(LIBIE_CTLQ_DESC_VIRTCHNL_MSG_RET_VAL, + msg->chnl_retval); + desc->qword1 = cpu_to_le64(qword); + + qword = FIELD_PREP(LIBIE_CTLQ_DESC_MSG_PARAM0, msg->param0) | + FIELD_PREP(LIBIE_CTLQ_DESC_SW_COOKIE, + msg->sw_cookie) | + FIELD_PREP(LIBIE_CTLQ_DESC_VIRTCHNL_FLAGS, + msg->virt_flags); + desc->qword2 = cpu_to_le64(qword); + + if (likely(msg->data_len)) { + desc->qword0 |= + cpu_to_le64(LIBIE_CTLQ_DESC_QWORD0(msg->data_len)); + qword = FIELD_PREP(LIBIE_CTLQ_DESC_DATA_ADDR_HIGH, + upper_32_bits(dma->pa)) | + FIELD_PREP(LIBIE_CTLQ_DESC_DATA_ADDR_LOW, + lower_32_bits(dma->pa)); + } else { + qword = msg->addr_param; + } + + desc->qword3 = cpu_to_le64(qword); +} + +/** + * libie_ctlq_send_desc_avail - get number of free descriptors on a Tx ctlq + * @ctlq: specific control queue which is going be used for sending messages + * + * The caller must hold ctlq->lock. Any dependent sending must be done + * in the same critical section. + * + * Return: number of available descriptors/messages on a given control queue. + */ +u32 libie_ctlq_send_desc_avail(const struct libie_ctlq_info *ctlq) +{ + u32 ntu = ctlq->next_to_use, ntc = ctlq->next_to_clean; + + lockdep_assert_held(&ctlq->lock); + + return (ntc > ntu ? 0 : ctlq->ring_len) + ntc - ntu - 1; +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_send_desc_avail, "LIBIE_CP"); + +/** + * libie_ctlq_send - send a message to Control Plane or Peer + * @ctlq: specific control queue which is used for sending a message + * @num_q_msg: number of messages present to send on @ctlq, + * positive and no greater than the number of available descriptors + * + * The caller must fill in @num_q_msg Tx messages starting at ntu beforehand. + * + * The caller must hold ctlq->lock. The intended pattern is to first check + * the number of descriptors available, then fill in the messages and perform + * send within a single critical section. + */ +void libie_ctlq_send(struct libie_ctlq_info *ctlq, u32 num_q_msg) +{ + u32 ntu = ctlq->next_to_use; + + lockdep_assert_held(&ctlq->lock); + + for (int i = 0; i < num_q_msg; i++) { + struct libie_ctlq_msg *msg = ctlq->tx_msg[ntu]; + struct libie_ctlq_desc *desc; + + desc = &ctlq->descs[ntu]; + libie_ctlq_tx_desc_from_msg(desc, msg); + + if (unlikely(++ntu == ctlq->ring_len)) + ntu = 0; + } + dma_wmb(); + writel(ntu, ctlq->reg.tail); + ctlq->next_to_use = ntu; +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_send, "LIBIE_CP"); + +/** + * libie_ctlq_send_clean - cleanup the send control queue message buffers + * @params: information for handling of Tx completions + * + * Cleanup the send buffers for the given control queue, if force is set, then + * clear all the outstanding send messages irrespective of their send status, + * until a zero-length message is encountered, which is either a message that + * is already cleared, or a VF reset message, which is always last. + * Force should be used during deinit or reset. + * + * Return: number of send buffers cleaned. + */ +u32 libie_ctlq_send_clean(const struct libie_ctlq_clean_params *params) +{ + struct libie_ctlq_info *ctlq = params->ctlq; + u32 ntc, i; + + spin_lock(&ctlq->lock); + ntc = ctlq->next_to_clean; + + for (i = 0; i < params->num_msgs; i++) { + struct libie_ctlq_msg *msg = ctlq->tx_msg[ntc]; + struct libie_ctlq_desc *desc; + u64 qword; + + desc = &ctlq->descs[ntc]; + qword = le64_to_cpu(desc->qword0); + + if (!FIELD_GET(LIBIE_CTLQ_DESC_FLAG_DD, qword) && + !(unlikely(params->force) && msg->data_len)) + break; + + /* This cannot be reordered and lock is taken, so no barriers */ + desc->qword0 = 0; + + params->rel_dma_mem(params->rel_ctx, &msg->send_mem); + memset(msg, 0, sizeof(*msg)); + + if (unlikely(++ntc == ctlq->ring_len)) + ntc = 0; + } + + ctlq->next_to_clean = ntc; + spin_unlock(&ctlq->lock); + + return i; +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_send_clean, "LIBIE_CP"); + +/** + * libie_ctlq_fill_rx_msg - fill in a message from Rx descriptor and buffer + * @msg: message to be filled in + * @desc: received descriptor + * @rx_buf: fill queue buffer associated with the descriptor + */ +static void libie_ctlq_fill_rx_msg(struct libie_ctlq_msg *msg, + const struct libie_ctlq_desc *desc, + struct libeth_fqe *rx_buf) +{ + u64 qword = le64_to_cpu(desc->qword0); + + msg->flags = FIELD_GET(LIBIE_CTLQ_DESC_FLAGS, qword); + msg->opcode = FIELD_GET(LIBIE_CTLQ_DESC_INFRA_OPCODE, qword); + msg->data_len = FIELD_GET(LIBIE_CTLQ_DESC_DATA_LEN, qword); + msg->hw_retval = FIELD_GET(LIBIE_CTLQ_DESC_HW_RETVAL, qword); + + qword = le64_to_cpu(desc->qword1); + msg->chnl_opcode = + FIELD_GET(LIBIE_CTLQ_DESC_VIRTCHNL_OPCODE, qword); + msg->chnl_retval = + FIELD_GET(LIBIE_CTLQ_DESC_VIRTCHNL_MSG_RET_VAL, qword); + + qword = le64_to_cpu(desc->qword2); + msg->param0 = + FIELD_GET(LIBIE_CTLQ_DESC_MSG_PARAM0, qword); + msg->sw_cookie = + FIELD_GET(LIBIE_CTLQ_DESC_SW_COOKIE, qword); + msg->virt_flags = + FIELD_GET(LIBIE_CTLQ_DESC_VIRTCHNL_FLAGS, qword); + + if (likely(msg->data_len)) { + if (unlikely(msg->data_len > LIBIE_CTLQ_MAX_BUF_LEN)) { + msg->data_len = LIBIE_CTLQ_MAX_BUF_LEN; + msg->chnl_retval = U32_MAX; + } + msg->recv_mem = (struct kvec) { + .iov_base = netmem_address(rx_buf->netmem) + + rx_buf->offset, + .iov_len = msg->data_len, + }; + libeth_rx_sync_for_cpu(rx_buf, msg->data_len); + } else { + msg->recv_mem = (struct kvec) {}; + msg->addr_param = le64_to_cpu(desc->qword3); + page_pool_put_full_netmem(netmem_get_pp(rx_buf->netmem), + rx_buf->netmem, false); + } +} + +/** + * libie_ctlq_recv - receive control queue messages + * @ctlq: control queue that needs to processed for receive + * @msg: array of received control queue messages on this q; + * needs to be pre-allocated by caller for as many messages as requested + * @num_q_msg: number of messages that can be stored in msg buffer, + * no greater than number of posted buffers + * + * Caller is expected to return buffers via libie_ctlq_release_rx_buf(). + * + * The caller must make sure that calls to libie_ctlq_post_rx_buffs() + * and libie_ctlq_recv() for each queue are either serialized + * or used under ctlq->lock. + * + * Return: number of messages received + */ +u32 libie_ctlq_recv(struct libie_ctlq_info *ctlq, struct libie_ctlq_msg *msg, + u32 num_q_msg) +{ + u32 ntc, i; + + ntc = ctlq->next_to_clean; + + for (i = 0; i < num_q_msg; i++) { + struct libie_ctlq_desc *desc = &ctlq->descs[ntc]; + struct libeth_fqe *rx_buf = &ctlq->rx_fqes[ntc]; + u64 qword; + + qword = le64_to_cpu(desc->qword0); + if (!FIELD_GET(LIBIE_CTLQ_DESC_FLAG_DD, qword)) + break; + + dma_rmb(); + + libie_ctlq_fill_rx_msg(&msg[i], desc, rx_buf); + desc->qword0 = 0; + + if (unlikely(++ntc == ctlq->ring_len)) + ntc = 0; + } + + ctlq->next_to_clean = ntc; + + return i; +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_recv, "LIBIE_CP"); + +MODULE_DESCRIPTION("Control Plane communication API"); +MODULE_IMPORT_NS("LIBETH"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/net/intel/libie/controlq.h b/include/linux/net/intel/libie/controlq.h new file mode 100644 index 000000000000..92b393df3db6 --- /dev/null +++ b/include/linux/net/intel/libie/controlq.h @@ -0,0 +1,276 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2025 Intel Corporation */ + +#ifndef __LIBIE_CONTROLQ_H +#define __LIBIE_CONTROLQ_H + +#include + +#include +#include + +/* Default mailbox control queue */ +#define LIBIE_CTLQ_MBX_ID -1 +#define LIBIE_CTLQ_MAX_BUF_LEN SZ_4K + +/** + * enum libie_ctlq_type - control queue type + * @LIBIE_CTLQ_TYPE_TX: basic Tx control queue + * @LIBIE_CTLQ_TYPE_RX: basic Rx control queue + */ +enum libie_ctlq_type { + LIBIE_CTLQ_TYPE_TX = 0, + LIBIE_CTLQ_TYPE_RX = 1, +}; + +/* Opcode used to send controlq message to the control plane */ +#define LIBIE_CTLQ_SEND_MSG_TO_CP 0x801 +#define LIBIE_CTLQ_SEND_MSG_TO_PEER 0x804 + +/** + * struct libie_ctlq_ctx - contains controlq info and MMIO region info + * @mmio_info: MMIO region info structure + * @ctlqs: list that stores all the control queues + * @ctlqs_lock: lock for control queue list + */ +struct libie_ctlq_ctx { + struct libie_mmio_info mmio_info; + struct list_head ctlqs; + spinlock_t ctlqs_lock; /* protects the ctlqs list */ +}; + +/** + * struct libie_ctlq_reg - structure representing virtual addresses of the + * controlq registers and masks + * @head: controlq head register address + * @tail: controlq tail register address + * @len: register address to write controlq length and enable bit + * @addr_high: register address to write the upper 32b of ring physical address + * @addr_low: register address to write the lower 32b of ring physical address + * @len_mask: mask to read the controlq length + * @len_ena_mask: mask to write the controlq enable bit + * @head_mask: mask to read the head value + */ +struct libie_ctlq_reg { + void __iomem *head; + void __iomem *tail; + void __iomem *len; + void __iomem *addr_high; + void __iomem *addr_low; + u32 len_mask; + u32 len_ena_mask; + u32 head_mask; +}; + +/** + * struct libie_cp_dma_mem - structure for DMA memory + * @va: virtual address + * @pa: physical address + * @size: memory size + * @direction: memory to device or device to memory + */ +struct libie_cp_dma_mem { + void *va; + dma_addr_t pa; + size_t size; + int direction; +}; + +/** + * struct libie_ctlq_msg - control queue message data + * @flags: refer to 'Flags sub-structure' definitions + * @opcode: infrastructure message opcode + * @data_len: size of the payload + * @func_id: queue id for mailbox selection, 0 for default mailbox (Tx) + * @hw_retval: execution status from the HW (Rx) + * @chnl_opcode: virtchnl message opcode + * @chnl_retval: virtchnl return value + * @param0: indirect message raw parameter0 + * @sw_cookie: used to verify the response of the sent virtchnl message + * @virt_flags: virtchnl capability flags + * @addr_param: additional parameters in place of the address, given no buffer + * @recv_mem: virtual address and size of the buffer that contains + * the indirect response + * @send_mem: physical and virtual address of the DMA buffer, + * used for sending + */ +struct libie_ctlq_msg { + u16 flags; + u16 opcode; + u16 data_len; + union { + u16 func_id; + u16 hw_retval; + }; + u32 chnl_opcode; + u32 chnl_retval; + u32 param0; + u16 sw_cookie; + u16 virt_flags; + u64 addr_param; + union { + struct kvec recv_mem; + struct libie_cp_dma_mem send_mem; + }; +}; + +/** + * struct libie_ctlq_create_info - control queue create information + * @type: control queue type (Rx or Tx) + * @id: queue offset passed as input, -1 for default mailbox + * @reg: registers accessed by control queue + * @len: controlq length + */ +struct libie_ctlq_create_info { + enum libie_ctlq_type type; + int id; + struct libie_ctlq_reg reg; + u16 len; +}; + +/** + * struct libie_ctlq_info - control queue information + * @list: used to add a controlq to the list of queues in libie_ctlq_ctx + * @type: control queue type + * @qid: queue identifier + * @lock: control queue lock + * @ring_mem: descriptor ring DMA memory + * @descs: array of descriptors + * @rx_fqes: array of controlq Rx buffers + * @tx_msg: Tx messages sent to hardware + * @reg: registers used by control queue + * @dev: device that owns this control queue + * @pp: page pool for controlq Rx buffers + * @truesize: size to allocate per buffer + * @next_to_clean: next descriptor to be cleaned + * @next_to_use: next available slot to send buffer (Tx queue) + * @next_to_post: next available slot to post buffers to (Rx queue) + * @ring_len: length of the descriptor ring + */ +struct libie_ctlq_info { + struct list_head list; + enum libie_ctlq_type type; + int qid; + spinlock_t lock; /* for concurrent processing */ + struct libie_cp_dma_mem ring_mem; + struct libie_ctlq_desc *descs; + union { + struct libeth_fqe *rx_fqes; + struct libie_ctlq_msg **tx_msg; + }; + struct libie_ctlq_reg reg; + struct device *dev; + struct page_pool *pp; + u32 truesize; + u32 next_to_clean; + union { + u32 next_to_use; + u32 next_to_post; + }; + u32 ring_len; +}; + +#define LIBIE_CTLQ_MBX_ATQ_LEN GENMASK(9, 0) + +/* libie controlq descriptor qword0 details */ + +/* Flags sub-structure + * |0 |1 |2 |3 |4 |5 |6 |7 |8 |9 |10 |11 |12 |13 |14 |15 | + * |DD |CMP|ERR| * RSV * |FTYPE | *RSV* |RD |VFC|BUF| HOST_ID | + */ +#define LIBIE_CTLQ_DESC_FLAG_DD BIT(0) +#define LIBIE_CTLQ_DESC_FLAG_CMP BIT(1) +#define LIBIE_CTLQ_DESC_FLAG_ERR BIT(2) +#define LIBIE_CTLQ_DESC_FLAG_FTYPE_VM BIT(6) +#define LIBIE_CTLQ_DESC_FLAG_FTYPE_PF BIT(7) +#define LIBIE_CTLQ_DESC_FLAG_FTYPE GENMASK(7, 6) +#define LIBIE_CTLQ_DESC_FLAG_RD BIT(10) +#define LIBIE_CTLQ_DESC_FLAG_VFC BIT(11) +#define LIBIE_CTLQ_DESC_FLAG_BUF BIT(12) +#define LIBIE_CTLQ_DESC_FLAG_HOST_ID GENMASK(15, 13) + +#define LIBIE_CTLQ_DESC_FLAGS GENMASK(15, 0) +#define LIBIE_CTLQ_DESC_INFRA_OPCODE GENMASK_ULL(31, 16) +#define LIBIE_CTLQ_DESC_DATA_LEN GENMASK_ULL(47, 32) +#define LIBIE_CTLQ_DESC_HW_RETVAL GENMASK_ULL(63, 48) + +#define LIBIE_CTLQ_DESC_PFID_VFID GENMASK_ULL(63, 48) + +/* libie controlq descriptor qword1 details */ +#define LIBIE_CTLQ_DESC_VIRTCHNL_OPCODE GENMASK(27, 0) +#define LIBIE_CTLQ_DESC_VIRTCHNL_DESC_TYPE GENMASK_ULL(31, 28) +#define LIBIE_CTLQ_DESC_VIRTCHNL_MSG_RET_VAL GENMASK_ULL(63, 32) + +/* libie controlq descriptor qword2 details */ +#define LIBIE_CTLQ_DESC_MSG_PARAM0 GENMASK_ULL(31, 0) +#define LIBIE_CTLQ_DESC_SW_COOKIE GENMASK_ULL(47, 32) +#define LIBIE_CTLQ_DESC_VIRTCHNL_FLAGS GENMASK_ULL(63, 48) + +/* libie controlq descriptor qword3 details */ +#define LIBIE_CTLQ_DESC_DATA_ADDR_HIGH GENMASK_ULL(31, 0) +#define LIBIE_CTLQ_DESC_DATA_ADDR_LOW GENMASK_ULL(63, 32) + +/** + * struct libie_ctlq_desc - control queue descriptor format + * @qword0: flags, message opcode, data length etc + * @qword1: virtchnl opcode, descriptor type and return value + * @qword2: indirect message parameters + * @qword3: indirect message buffer address + */ +struct libie_ctlq_desc { + __le64 qword0; + __le64 qword1; + __le64 qword2; + __le64 qword3; +}; + +/** + * struct libie_ctlq_clean_params - cleaning parameters for Tx messages + * @rel_dma_mem: non-sleeping callback to put the DMA buffer after send + * @rel_ctx: additional context for release callback + * @ctlq: control queue information + * @num_msgs: number of messages to be cleaned + * @force: clean even if DD is not yet set, use only for final cleanup + */ +struct libie_ctlq_clean_params { + void (*rel_dma_mem)(const void *ctx, struct libie_cp_dma_mem *dma_mem); + const void *rel_ctx; + struct libie_ctlq_info *ctlq; + u16 num_msgs; + bool force; +}; + +/** + * libie_ctlq_release_rx_buf - Release Rx buffer for a specific control queue + * @rx_buf: Rx buffer to be freed + * + * Driver uses this function to post back the Rx buffer after the usage. + */ +static inline void libie_ctlq_release_rx_buf(struct kvec *rx_buf) +{ + netmem_ref netmem; + + if (!rx_buf->iov_base) + return; + + netmem = virt_to_netmem(rx_buf->iov_base); + page_pool_put_full_netmem(netmem_get_pp(netmem), netmem, false); +} + +int libie_ctlq_init(struct libie_ctlq_ctx *ctx, + const struct libie_ctlq_create_info *qinfo, u32 numq); +void libie_ctlq_deinit(struct libie_ctlq_ctx *ctx); + +struct libie_ctlq_info *libie_find_ctlq(struct libie_ctlq_ctx *ctx, + enum libie_ctlq_type type, + int id); + +u32 libie_ctlq_send_desc_avail(const struct libie_ctlq_info *ctlq); +void libie_ctlq_send(struct libie_ctlq_info *ctlq, u32 num_q_msg); +u32 libie_ctlq_send_clean(const struct libie_ctlq_clean_params *params); +u32 libie_ctlq_recv(struct libie_ctlq_info *ctlq, struct libie_ctlq_msg *msg, + u32 num_q_msg); + +int libie_ctlq_post_rx_buffs(struct libie_ctlq_info *ctlq); + +#endif /* __LIBIE_CONTROLQ_H */ From b4ff4e626a7c7f10152c0895d51ff60123041e0e Mon Sep 17 00:00:00 2001 From: Phani R Burra Date: Thu, 25 Jun 2026 18:01:59 +0200 Subject: [PATCH 1250/1433] libie: add bookkeeping support for control queue messages Small send control queue message buffers are managed and reused by libie itself, bigger send buffers are consumed. All are tracked with the unique transaction (Xn) ids until they receive response or time out. Responses can be received out of order, therefore transactions are stored in an array and tracked though a bitmap. Rx buffers utilize page_pool. Pre-allocated DMA memory is used where possible. It reduces the driver overhead in handling memory allocation/free and message timeouts. Reviewed-by: Maciej Fijalkowski Signed-off-by: Phani R Burra Co-developed-by: Victor Raj Signed-off-by: Victor Raj Co-developed-by: Pavan Kumar Linga Signed-off-by: Pavan Kumar Linga Tested-by: Bharath R Tested-by: Samuel Salin Co-developed-by: Larysa Zaremba Signed-off-by: Larysa Zaremba Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/libie/controlq.c | 623 ++++++++++++++++++++ include/linux/net/intel/libie/controlq.h | 160 +++++ 2 files changed, 783 insertions(+) diff --git a/drivers/net/ethernet/intel/libie/controlq.c b/drivers/net/ethernet/intel/libie/controlq.c index f0ff10f99bc2..45a49eba6a82 100644 --- a/drivers/net/ethernet/intel/libie/controlq.c +++ b/drivers/net/ethernet/intel/libie/controlq.c @@ -667,6 +667,629 @@ u32 libie_ctlq_recv(struct libie_ctlq_info *ctlq, struct libie_ctlq_msg *msg, } EXPORT_SYMBOL_NS_GPL(libie_ctlq_recv, "LIBIE_CP"); +/** + * libie_ctlq_xn_pop_free - get a free Xn entry from the free list + * @xnm: Xn transaction manager + * + * Retrieve a free Xn entry from the free list. + * + * Return: valid Xn entry pointer or NULL if there are no free Xn entries. + */ +static struct libie_ctlq_xn * +libie_ctlq_xn_pop_free(struct libie_ctlq_xn_manager *xnm) +{ + struct libie_ctlq_xn *xn; + u32 free_idx; + + guard(spinlock)(&xnm->free_xns_bm_lock); + + if (unlikely(xnm->shutdown)) + return NULL; + + for_each_set_bit(free_idx, xnm->free_xns_bm, + LIBIE_CTLQ_MAX_XN_ENTRIES) { + xn = &xnm->ring[free_idx]; + + /* Torn read of the physical address is possible, the worst case + * scenario is a transient spurious skip. If the physical + * address is dirty in any way, reuse is already safe. + */ + if (xn->tx_msg && + data_race(xn->tx_msg->send_mem.pa) == xn->small_dma_mem.pa) + continue; + + clear_bit(free_idx, xnm->free_xns_bm); + + return xn; + } + + return NULL; +} + +/** + * __libie_ctlq_xn_push_free - unsafely push an xn entry into the free list + * @xnm: Xn transaction manager + * @xn: xn entry to be added into the free list + * + * Return: whether xnm destruction can be triggered by the caller + */ +static bool __libie_ctlq_xn_push_free(struct libie_ctlq_xn_manager *xnm, + struct libie_ctlq_xn *xn) +{ + xn->cookie++; + set_bit(xn->index, xnm->free_xns_bm); + + if (unlikely(xnm->shutdown) && + bitmap_full(xnm->free_xns_bm, LIBIE_CTLQ_MAX_XN_ENTRIES)) + return true; + + return false; +} + +/** + * libie_ctlq_xn_push_free - push a Xn entry into the free list + * @xnm: Xn transaction manager + * @xn: xn entry to be added into the free list, not locked + * + * Safely add a used Xn entry back to the free list. + */ +static void libie_ctlq_xn_push_free(struct libie_ctlq_xn_manager *xnm, + struct libie_ctlq_xn *xn) +{ + bool can_destroy; + + scoped_guard(spinlock, &xnm->free_xns_bm_lock) + can_destroy = __libie_ctlq_xn_push_free(xnm, xn); + + if (can_destroy) + complete(&xnm->can_destroy); +} + +/** + * libie_ctlq_xn_deinit_dma - free the DMA memory allocated for send messages + * @xnm: pointer to the transaction manager + * @num_entries: number of Xn entries to free the DMA for + */ +static void libie_ctlq_xn_deinit_dma(struct libie_ctlq_xn_manager *xnm, + u32 num_entries) +{ + for (u32 i = 0; i < num_entries; i++) { + struct libie_ctlq_xn *xn = &xnm->ring[i]; + + dma_pool_free(xnm->small_buff_pool, xn->small_dma_mem.va, + xn->small_dma_mem.pa); + } + + dma_pool_destroy(xnm->small_buff_pool); +} + +/** + * libie_ctlq_xn_init_dma - pre-allocate DMA memory for send messages that use + * stack variables + * @dev: device pointer + * @xnm: pointer to transaction manager + * + * Return: %0 on success or error if memory allocation fails + */ +static int libie_ctlq_xn_init_dma(struct device *dev, + struct libie_ctlq_xn_manager *xnm) +{ + u32 i; + + xnm->small_buff_pool = + dma_pool_create("libie_ctlq_xn_tx", dev, LIBIE_CP_TX_COPYBREAK, + LIBIE_CP_TX_COPYBREAK, 0); + if (!xnm->small_buff_pool) + return -ENOMEM; + + for (i = 0; i < LIBIE_CTLQ_MAX_XN_ENTRIES; i++) { + struct libie_cp_dma_mem *mem = &xnm->ring[i].small_dma_mem; + + mem->va = dma_pool_zalloc(xnm->small_buff_pool, GFP_KERNEL, + &mem->pa); + if (!mem->va) + goto dealloc_dma; + + mem->direction = DMA_BIDIRECTIONAL; + mem->size = LIBIE_CP_TX_COPYBREAK; + } + + return 0; + +dealloc_dma: + libie_ctlq_xn_deinit_dma(xnm, i); + + return -ENOMEM; +} + +/** + * libie_ctlq_xn_process_recv - process Xn data in receive message + * @params: Xn receive param information to handle a receive message + * @ctlq_msg: received control queue message + * + * Process a control queue receive message and send a complete event + * notification. + * + * Return: true if a message has been processed, false otherwise. + */ +static bool +libie_ctlq_xn_process_recv(struct libie_ctlq_xn_recv_params *params, + struct libie_ctlq_msg *ctlq_msg) +{ + struct libie_ctlq_xn_manager *xnm = params->xnm; + struct libie_ctlq_xn *xn; + u16 msg_cookie, xn_index; + struct kvec *response; + int status; + u16 data; + + data = ctlq_msg->sw_cookie; + xn_index = FIELD_GET(LIBIE_CTLQ_XN_INDEX_M, data); + msg_cookie = FIELD_GET(LIBIE_CTLQ_XN_COOKIE_M, data); + status = ctlq_msg->chnl_retval ? -EFAULT : 0; + + xn = &xnm->ring[xn_index]; + spin_lock(&xn->xn_lock); + if (ctlq_msg->chnl_opcode != xn->virtchnl_opcode || + msg_cookie != xn->cookie) { + spin_unlock(&xn->xn_lock); + return false; + } + + if (xn->state != LIBIE_CTLQ_XN_ASYNC && + xn->state != LIBIE_CTLQ_XN_WAITING) { + spin_unlock(&xn->xn_lock); + return false; + } + + response = &ctlq_msg->recv_mem; + if (xn->state == LIBIE_CTLQ_XN_ASYNC) { + xn->resp_cb(xn->send_ctx, response, status); + libie_ctlq_release_rx_buf(response); + xn->state = LIBIE_CTLQ_XN_IDLE; + spin_unlock(&xn->xn_lock); + libie_ctlq_xn_push_free(xnm, xn); + + return true; + } + + xn->recv_mem = *response; + xn->state = status ? LIBIE_CTLQ_XN_COMPLETED_FAILED : + LIBIE_CTLQ_XN_COMPLETED_SUCCESS; + + complete(&xn->cmd_completion_event); + spin_unlock(&xn->xn_lock); + + return true; +} + +/** + * libie_xn_check_async_timeout - Check for asynchronous message timeouts + * @xnm: Xn transaction manager + * + * Call the corresponding callback to notify the caller about the timeout. + * Iterates free_xns_bm locklessly, potential races are caught under + * xn->xn_lock. + */ +static void libie_xn_check_async_timeout(struct libie_ctlq_xn_manager *xnm) +{ + u32 idx; + + for_each_clear_bit(idx, xnm->free_xns_bm, LIBIE_CTLQ_MAX_XN_ENTRIES) { + struct libie_ctlq_xn *xn = &xnm->ring[idx]; + u64 timeout_ms; + + spin_lock(&xn->xn_lock); + + timeout_ms = ktime_ms_delta(ktime_get(), xn->timestamp); + if (xn->state != LIBIE_CTLQ_XN_ASYNC || + timeout_ms < xn->timeout_ms) { + spin_unlock(&xn->xn_lock); + continue; + } + + xn->resp_cb(xn->send_ctx, NULL, -ETIMEDOUT); + xn->state = LIBIE_CTLQ_XN_IDLE; + spin_unlock(&xn->xn_lock); + libie_ctlq_xn_push_free(xnm, xn); + } +} + +/** + * libie_ctlq_xn_recv - process control queue receive message + * @params: Xn receive param information to handle a receive message + * + * Process a receive message and update the receive queue buffer. + * Also terminates async transactions for which it failed to receive a response + * within a given timeframe. + * Function is intended to be called periodically from a single task. + * + * Return: remaining budget. + */ +u32 libie_ctlq_xn_recv(struct libie_ctlq_xn_recv_params *params) +{ + struct libie_ctlq_msg ctlq_msg; + u32 budget = params->budget; + + while (budget && libie_ctlq_recv(params->ctlq, &ctlq_msg, 1)) { + budget--; + if (!libie_ctlq_xn_process_recv(params, &ctlq_msg)) + params->ctlq_msg_handler(params->xnm->ctx, &ctlq_msg); + } + + libie_ctlq_post_rx_buffs(params->ctlq); + libie_xn_check_async_timeout(params->xnm); + + return budget; +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_xn_recv, "LIBIE_CP"); + +/** + * libie_cp_map_dma_mem - map a given virtual address for DMA + * @dev: device information + * @va: virtual address to be mapped + * @size: size of the memory + * @direction: DMA direction either from/to device + * @dma_mem: memory for DMA information to be stored + * + * Return: true on success, false on DMA map failure. + */ +static bool libie_cp_map_dma_mem(struct device *dev, void *va, size_t size, + int direction, + struct libie_cp_dma_mem *dma_mem) +{ + dma_mem->pa = dma_map_single(dev, va, size, direction); + + return dma_mapping_error(dev, dma_mem->pa) ? false : true; +} + +/** + * libie_cp_unmap_dma_mem - unmap previously mapped DMA address + * @dev: device information + * @dma_mem: DMA memory information + */ +static void libie_cp_unmap_dma_mem(struct device *dev, + const struct libie_cp_dma_mem *dma_mem) +{ + dma_unmap_single(dev, dma_mem->pa, dma_mem->size, + dma_mem->direction); +} + +/** + * libie_ctlq_xn_process_send - process and send a control queue message + * @params: Xn send param information for sending a control queue message + * @xn: Assigned Xn entry for tracking the control queue message + * + * Return: %0 on success, -%errno on failure. + */ +static +int libie_ctlq_xn_process_send(struct libie_ctlq_xn_send_params *params, + struct libie_ctlq_xn *xn) +{ + size_t buf_len = params->send_buf.iov_len; + struct device *dev = params->ctlq->dev; + void *buf = params->send_buf.iov_base; + struct libie_cp_dma_mem *dma_mem; + u16 cookie; + + if (!buf || !buf_len) + return -EOPNOTSUPP; + + if (libie_cp_can_send_onstack(buf_len)) { + dma_mem = &xn->small_dma_mem; + memcpy(dma_mem->va, buf, buf_len); + } else { + dma_mem = &xn->send_dma_mem; + dma_mem->va = buf; + dma_mem->size = buf_len; + dma_mem->direction = DMA_TO_DEVICE; + + if (!libie_cp_map_dma_mem(dev, buf, buf_len, DMA_TO_DEVICE, + dma_mem)) + return -ENOMEM; + } + + cookie = FIELD_PREP(LIBIE_CTLQ_XN_COOKIE_M, xn->cookie) | + FIELD_PREP(LIBIE_CTLQ_XN_INDEX_M, xn->index); + + scoped_guard(spinlock, ¶ms->ctlq->lock) { + struct libie_ctlq_info *ctlq = params->ctlq; + struct libie_ctlq_msg *ctlq_msg; + + if (!libie_ctlq_send_desc_avail(ctlq)) { + if (!libie_cp_can_send_onstack(buf_len)) + libie_cp_unmap_dma_mem(dev, dma_mem); + + return -EBUSY; + } + + ctlq_msg = ctlq->tx_msg[ctlq->next_to_use]; + xn->tx_msg = dma_mem == &xn->small_dma_mem ? ctlq_msg : NULL; + if (params->ctlq_msg) + *ctlq_msg = *params->ctlq_msg; + else + /* Unused ctlq messages are already zeroed */ + ctlq_msg->opcode = LIBIE_CTLQ_SEND_MSG_TO_CP; + + ctlq_msg->sw_cookie = cookie; + ctlq_msg->send_mem = *dma_mem; + ctlq_msg->data_len = buf_len; + ctlq_msg->chnl_opcode = params->chnl_opcode; + libie_ctlq_send(params->ctlq, 1); + } + + return 0; +} + +/** + * libie_ctlq_xn_send - send a control queue message, initiating a transaction + * @params: Xn send param information for sending a control queue message + * + * Send a control queue (mailbox or config) message. + * Based on the params value, the call can be completed synchronously or + * asynchronously. + * + * Return: %0 on success, -%errno on failure. + */ +int libie_ctlq_xn_send(struct libie_ctlq_xn_send_params *params) +{ + bool free_send = !libie_cp_can_send_onstack(params->send_buf.iov_len); + struct libie_ctlq_xn *xn; + int ret; + + if (params->send_buf.iov_len > LIBIE_CTLQ_MAX_BUF_LEN) { + ret = -EINVAL; + goto free_buf; + } + + xn = libie_ctlq_xn_pop_free(params->xnm); + /* no free transactions available */ + if (unlikely(!xn)) { + ret = -EAGAIN; + goto free_buf; + } + + spin_lock(&xn->xn_lock); + if (xn->state == LIBIE_CTLQ_XN_SHUTDOWN) { + ret = -ENXIO; + goto unlock_xn; + } + + xn->state = params->resp_cb ? LIBIE_CTLQ_XN_ASYNC : + LIBIE_CTLQ_XN_WAITING; + xn->virtchnl_opcode = params->chnl_opcode; + + if (params->resp_cb) { + xn->send_ctx = params->send_ctx; + xn->resp_cb = params->resp_cb; + xn->timeout_ms = params->timeout_ms; + xn->timestamp = ktime_get(); + } + + ret = libie_ctlq_xn_process_send(params, xn); + if (ret) + goto release_xn; + else + free_send = false; + + spin_unlock(&xn->xn_lock); + + if (params->resp_cb) + return 0; + + wait_for_completion_timeout(&xn->cmd_completion_event, + msecs_to_jiffies(params->timeout_ms)); + + spin_lock(&xn->xn_lock); + switch (xn->state) { + case LIBIE_CTLQ_XN_WAITING: + ret = -ETIMEDOUT; + break; + case LIBIE_CTLQ_XN_COMPLETED_SUCCESS: + params->recv_mem = xn->recv_mem; + break; + default: + ret = -EBADMSG; + break; + } + + /* Free the receive buffer in case of failure. On timeout, receive + * buffer is not allocated. + */ + if (ret && ret != -ETIMEDOUT) + libie_ctlq_release_rx_buf(&xn->recv_mem); + +release_xn: + xn->state = LIBIE_CTLQ_XN_IDLE; + reinit_completion(&xn->cmd_completion_event); +unlock_xn: + spin_unlock(&xn->xn_lock); + libie_ctlq_xn_push_free(params->xnm, xn); +free_buf: + if (free_send) + params->rel_tx_buf(params->send_buf.iov_base); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_xn_send, "LIBIE_CP"); + +/** + * struct libie_ctlq_xn_rel_tx_ctx - context needed to release xn Tx message + * @dev: device for which DMA was mapped + * @rel_tx_buf: freeing function for non-small buffers + */ +struct libie_ctlq_xn_rel_tx_ctx { + struct device *dev; + void (*rel_tx_buf)(const void *buf_va); +}; + +/** + * libie_ctlq_xn_rel_tx_buf - release xn-controlled Tx message buffer + * @ctx: context, namely DMA device and freeing function + * @dma_mem: DMA memory to reclaim/unmap + */ +static void libie_ctlq_xn_rel_tx_buf(const void *ctx, + struct libie_cp_dma_mem *dma_mem) +{ + const struct libie_ctlq_xn_rel_tx_ctx *rel_ctx = ctx; + + if (!libie_cp_can_send_onstack(dma_mem->size)) { + libie_cp_unmap_dma_mem(rel_ctx->dev, dma_mem); + rel_ctx->rel_tx_buf(dma_mem->va); + } +} + +/** + * libie_ctlq_xn_send_clean - clean xn-controlled Tx messages + * @ctlq: control queue to clean + * @rel_tx_buf: driver callback to free the buffer + * @force: clean regardless of DD + * + * Return: number of completed/released messages. + */ +u32 libie_ctlq_xn_send_clean(struct libie_ctlq_info *ctlq, + void (*rel_tx_buf)(const void *buf_va), + bool force) +{ + struct libie_ctlq_xn_rel_tx_ctx rel_ctx = { + .dev = ctlq->dev, + .rel_tx_buf = rel_tx_buf, + }; + struct libie_ctlq_clean_params params = { + .ctlq = ctlq, + .force = force, + .num_msgs = ctlq->ring_len, + .rel_ctx = &rel_ctx, + .rel_dma_mem = libie_ctlq_xn_rel_tx_buf, + }; + + return libie_ctlq_send_clean(¶ms); +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_xn_send_clean, "LIBIE_CP"); + +/** + * libie_ctlq_xn_shutdown - terminate control queue transactions + * @xnm: pointer to the transaction manager + * + * Synchronously terminate existing transactions and stop accepting new ones. + * Async transactions are discarded without invoking resp_cb. + */ +void libie_ctlq_xn_shutdown(struct libie_ctlq_xn_manager *xnm) +{ + bool must_wait = false; + u32 i; + + /* Should be no new clear bits after this */ + spin_lock(&xnm->free_xns_bm_lock); + xnm->shutdown = true; + + for_each_clear_bit(i, xnm->free_xns_bm, LIBIE_CTLQ_MAX_XN_ENTRIES) { + struct libie_ctlq_xn *xn = &xnm->ring[i]; + + spin_lock(&xn->xn_lock); + + switch (xn->state) { + /* if an idle xn is not free, it is about to be either + * freed or initialized, prevent the latter and wait + */ + case LIBIE_CTLQ_XN_IDLE: + xn->state = LIBIE_CTLQ_XN_SHUTDOWN; + fallthrough; + /* waiting thread possibly needs a push to return the xn, + * transaction will be reported as timed out + */ + case LIBIE_CTLQ_XN_WAITING: + complete(&xn->cmd_completion_event); + fallthrough; + /* these states will return the xn soon */ + case LIBIE_CTLQ_XN_COMPLETED_SUCCESS: + case LIBIE_CTLQ_XN_COMPLETED_FAILED: + case LIBIE_CTLQ_XN_SHUTDOWN: + must_wait = true; + break; + /* no thread should reference async xns at this point */ + case LIBIE_CTLQ_XN_ASYNC: + xn->state = LIBIE_CTLQ_XN_IDLE; + __libie_ctlq_xn_push_free(xnm, xn); + break; + } + + spin_unlock(&xn->xn_lock); + } + + spin_unlock(&xnm->free_xns_bm_lock); + + if (must_wait) + wait_for_completion(&xnm->can_destroy); +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_xn_shutdown, "LIBIE_CP"); + +/** + * libie_ctlq_xn_deinit - deallocate and free the transaction manager resources + * @xnm: pointer to the transaction manager + * @ctx: libie CP context information + * + * Rx processing must be stopped beforehand via cancelling tasks. + * Tx processing must be stopped beforehand via libie_ctlq_xn_shutdown(), + * all buffers must be force-cleaned from the send queue. + */ +void libie_ctlq_xn_deinit(struct libie_ctlq_xn_manager *xnm, + struct libie_ctlq_ctx *ctx) +{ + libie_ctlq_xn_deinit_dma(xnm, LIBIE_CTLQ_MAX_XN_ENTRIES); + kvfree(xnm); + libie_ctlq_deinit(ctx); +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_xn_deinit, "LIBIE_CP"); + +/** + * libie_ctlq_xn_init - initialize the Xn transaction manager + * @params: Xn init param information for allocating Xn manager resources + * + * Return: %0 on success, -%errno on failure. + */ +int libie_ctlq_xn_init(struct libie_ctlq_xn_init_params *params) +{ + struct libie_ctlq_xn_manager *xnm; + int ret; + + ret = libie_ctlq_init(params->ctx, params->cctlq_info, params->num_qs); + if (ret) + return ret; + + xnm = kvzalloc_obj(*xnm); + if (!xnm) + goto ctlq_deinit; + + ret = libie_ctlq_xn_init_dma(¶ms->ctx->mmio_info.pdev->dev, xnm); + if (ret) + goto free_xnm; + + spin_lock_init(&xnm->free_xns_bm_lock); + init_completion(&xnm->can_destroy); + bitmap_fill(xnm->free_xns_bm, LIBIE_CTLQ_MAX_XN_ENTRIES); + + for (u32 i = 0; i < LIBIE_CTLQ_MAX_XN_ENTRIES; i++) { + struct libie_ctlq_xn *xn = &xnm->ring[i]; + + xn->index = i; + init_completion(&xn->cmd_completion_event); + spin_lock_init(&xn->xn_lock); + } + xnm->ctx = params->ctx; + params->xnm = xnm; + + return 0; + +free_xnm: + kvfree(xnm); +ctlq_deinit: + libie_ctlq_deinit(params->ctx); + + return -ENOMEM; +} +EXPORT_SYMBOL_NS_GPL(libie_ctlq_xn_init, "LIBIE_CP"); + MODULE_DESCRIPTION("Control Plane communication API"); MODULE_IMPORT_NS("LIBETH"); MODULE_LICENSE("GPL"); diff --git a/include/linux/net/intel/libie/controlq.h b/include/linux/net/intel/libie/controlq.h index 92b393df3db6..175227d4eef1 100644 --- a/include/linux/net/intel/libie/controlq.h +++ b/include/linux/net/intel/libie/controlq.h @@ -4,6 +4,7 @@ #ifndef __LIBIE_CONTROLQ_H #define __LIBIE_CONTROLQ_H +#include #include #include @@ -27,6 +28,8 @@ enum libie_ctlq_type { #define LIBIE_CTLQ_SEND_MSG_TO_CP 0x801 #define LIBIE_CTLQ_SEND_MSG_TO_PEER 0x804 +#define LIBIE_CP_TX_COPYBREAK 128 + /** * struct libie_ctlq_ctx - contains controlq info and MMIO region info * @mmio_info: MMIO region info structure @@ -273,4 +276,161 @@ u32 libie_ctlq_recv(struct libie_ctlq_info *ctlq, struct libie_ctlq_msg *msg, int libie_ctlq_post_rx_buffs(struct libie_ctlq_info *ctlq); +/* Only 8 bits are available in descriptor for Xn index */ +#define LIBIE_CTLQ_MAX_XN_ENTRIES 256 +#define LIBIE_CTLQ_XN_COOKIE_M GENMASK(15, 8) +#define LIBIE_CTLQ_XN_INDEX_M GENMASK(7, 0) + +/** + * enum libie_ctlq_xn_state - Transaction state of a virtchnl message + * @LIBIE_CTLQ_XN_IDLE: transaction is available to use + * @LIBIE_CTLQ_XN_WAITING: waiting for transaction to complete + * @LIBIE_CTLQ_XN_COMPLETED_SUCCESS: transaction completed with success + * @LIBIE_CTLQ_XN_COMPLETED_FAILED: transaction completed with failure + * @LIBIE_CTLQ_XN_ASYNC: asynchronous virtchnl message transaction type + * @LIBIE_CTLQ_XN_SHUTDOWN: transaction cannot be used anymore + */ +enum libie_ctlq_xn_state { + LIBIE_CTLQ_XN_IDLE = 0, + LIBIE_CTLQ_XN_WAITING, + LIBIE_CTLQ_XN_COMPLETED_SUCCESS, + LIBIE_CTLQ_XN_COMPLETED_FAILED, + LIBIE_CTLQ_XN_ASYNC, + LIBIE_CTLQ_XN_SHUTDOWN, +}; + +/** + * struct libie_ctlq_xn - structure representing a virtchnl transaction entry + * @resp_cb: non-sleeping callback to handle the response to an async message + * @xn_lock: lock to protect the transaction entry state + * @cmd_completion_event: wait until reply is received or xn is terminated + * @small_dma_mem: DMA memory for copying small send buffers from stack, + * is recycled when response is received or on timeout + * @send_dma_mem: DMA memory of send buffer + * @recv_mem: receive buffer + * @send_ctx: context for callback function + * @timeout_ms: Xn transaction timeout in msecs + * @timestamp: timestamp to record the Xn send + * @tx_msg: control queue Tx message slot to track small DMA usage + * @virtchnl_opcode: virtchnl command opcode used for Xn transaction + * @state: transaction state of a virtchnl message + * @cookie: unique message identifier, incremented every time the slot is used + * @index: index of the transaction entry + */ +struct libie_ctlq_xn { + void (*resp_cb)(void *ctx, struct kvec *mem, int status); + spinlock_t xn_lock; /* protects state */ + struct completion cmd_completion_event; + struct libie_cp_dma_mem small_dma_mem; + struct libie_cp_dma_mem send_dma_mem; + struct kvec recv_mem; + void *send_ctx; + u64 timeout_ms; + ktime_t timestamp; + struct libie_ctlq_msg *tx_msg; + u32 virtchnl_opcode; + enum libie_ctlq_xn_state state; + u8 cookie; + u8 index; +}; + +/** + * struct libie_ctlq_xn_manager - structure representing the array of virtchnl + * transaction entries + * @ctx: pointer to controlq context structure + * @free_xns_bm_lock: lock to protect the free Xn entries bit map + * @free_xns_bm: bitmap that represents the free Xn entries + * @ring: array of Xn entries + * @small_buff_pool: DMA pool for small send buffers + * @can_destroy: completion, triggered by the last released transaction + * @shutdown: shutdown process has been started, no new transactions allowed + */ +struct libie_ctlq_xn_manager { + struct libie_ctlq_ctx *ctx; + spinlock_t free_xns_bm_lock; /* get/check entries */ + DECLARE_BITMAP(free_xns_bm, LIBIE_CTLQ_MAX_XN_ENTRIES); + struct libie_ctlq_xn ring[LIBIE_CTLQ_MAX_XN_ENTRIES]; + struct dma_pool *small_buff_pool; + struct completion can_destroy; + bool shutdown; +}; + +/** + * struct libie_ctlq_xn_send_params - structure representing send Xn entry + * @resp_cb: non-sleeping callback to handle the response to an async message + * @rel_tx_buf: non-sleeping callback for freeing the send buffer + * @xnm: Xn manager to process Xn entries + * @ctlq: send control queue information + * @ctlq_msg: control queue message information + * @send_buf: buffer that carries outgoing message data, buffers larger than + * LIBIE_CP_TX_COPYBREAK bytes will always be consumed + * @recv_mem: receive buffer + * @send_ctx: context for callback function + * @timeout_ms: virtchnl transaction timeout in msecs + * @chnl_opcode: virtchnl message opcode + */ +struct libie_ctlq_xn_send_params { + void (*resp_cb)(void *ctx, struct kvec *mem, int status); + void (*rel_tx_buf)(const void *buf_va); + struct libie_ctlq_xn_manager *xnm; + struct libie_ctlq_info *ctlq; + struct libie_ctlq_msg *ctlq_msg; + struct kvec send_buf; + struct kvec recv_mem; + void *send_ctx; + u64 timeout_ms; + u32 chnl_opcode; +}; + +/** + * libie_cp_can_send_onstack - can a message be sent using a stack variable + * @size: ctlq data buffer size + * + * Return: %true if the message size is small enough for caller to pass + * an on-stack buffer, %false if kmalloc is needed + */ +static inline bool libie_cp_can_send_onstack(u32 size) +{ + return size <= LIBIE_CP_TX_COPYBREAK; +} + +/** + * struct libie_ctlq_xn_recv_params - request to receive xn responses + * @ctlq_msg_handler: handler for Rx messages with no matching xn (mandatory) + * @xnm: Xn manager to process Xn entries + * @ctlq: control queue information + * @budget: maximum number of messages to process + */ +struct libie_ctlq_xn_recv_params { + void (*ctlq_msg_handler)(struct libie_ctlq_ctx *ctx, + struct libie_ctlq_msg *msg); + struct libie_ctlq_xn_manager *xnm; + struct libie_ctlq_info *ctlq; + u32 budget; +}; + +/** + * struct libie_ctlq_xn_init_params - xn transaction manager parameters + * @cctlq_info: control queue information + * @ctx: pointer to controlq context structure + * @xnm: Xn manager to process Xn entries + * @num_qs: number of control queues to be initialized + */ +struct libie_ctlq_xn_init_params { + struct libie_ctlq_create_info *cctlq_info; + struct libie_ctlq_ctx *ctx; + struct libie_ctlq_xn_manager *xnm; + u32 num_qs; +}; + +int libie_ctlq_xn_init(struct libie_ctlq_xn_init_params *params); +void libie_ctlq_xn_deinit(struct libie_ctlq_xn_manager *xnm, + struct libie_ctlq_ctx *ctx); +void libie_ctlq_xn_shutdown(struct libie_ctlq_xn_manager *xnm); +int libie_ctlq_xn_send(struct libie_ctlq_xn_send_params *params); +u32 libie_ctlq_xn_recv(struct libie_ctlq_xn_recv_params *params); +u32 libie_ctlq_xn_send_clean(struct libie_ctlq_info *ctlq, + void (*rel_tx_buf)(const void *buf_va), + bool force); + #endif /* __LIBIE_CONTROLQ_H */ From fb2bebcffbbdbf080ebb49d4191730aaac4136b3 Mon Sep 17 00:00:00 2001 From: Pavan Kumar Linga Date: Thu, 25 Jun 2026 18:02:00 +0200 Subject: [PATCH 1251/1433] idpf: remove 'vport_params_reqd' field While sending a create vport message to the device control plane, a create vport virtchnl message is prepared with all the required info to initialize the vport. This info is stored in the adapter struct but never used thereafter. So, remove the said field. Signed-off-by: Pavan Kumar Linga Reviewed-by: Maciej Fijalkowski Reviewed-by: Madhu Chittim Tested-by: Samuel Salin Signed-off-by: Larysa Zaremba Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/idpf/idpf.h | 2 -- drivers/net/ethernet/intel/idpf/idpf_lib.c | 2 -- .../net/ethernet/intel/idpf/idpf_virtchnl.c | 30 +++++++------------ 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf.h b/drivers/net/ethernet/intel/idpf/idpf.h index 984944bab28b..c5e47e79a641 100644 --- a/drivers/net/ethernet/intel/idpf/idpf.h +++ b/drivers/net/ethernet/intel/idpf/idpf.h @@ -638,7 +638,6 @@ struct idpf_vc_xn_manager; * @avail_queues: Device given queue limits * @vports: Array to store vports created by the driver * @netdevs: Associated Vport netdevs - * @vport_params_reqd: Vport params requested * @vport_params_recvd: Vport params received * @vport_ids: Array of device given vport identifiers * @singleq_pt_lkup: Lookup table for singleq RX ptypes @@ -697,7 +696,6 @@ struct idpf_adapter { struct idpf_avail_queue_info avail_queues; struct idpf_vport **vports; struct net_device **netdevs; - struct virtchnl2_create_vport **vport_params_reqd; struct virtchnl2_create_vport **vport_params_recvd; u32 *vport_ids; diff --git a/drivers/net/ethernet/intel/idpf/idpf_lib.c b/drivers/net/ethernet/intel/idpf/idpf_lib.c index bb81e620c5c8..810d220c229a 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_lib.c +++ b/drivers/net/ethernet/intel/idpf/idpf_lib.c @@ -1109,8 +1109,6 @@ static void idpf_vport_rel(struct idpf_vport *vport) kfree(adapter->vport_params_recvd[idx]); adapter->vport_params_recvd[idx] = NULL; - kfree(adapter->vport_params_reqd[idx]); - adapter->vport_params_reqd[idx] = NULL; kfree(vport); adapter->num_alloc_vports--; diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c index 8bd6cca64c9b..c4a128a114a5 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c @@ -1558,14 +1558,10 @@ int idpf_send_create_vport_msg(struct idpf_adapter *adapter, ssize_t reply_sz; buf_size = sizeof(struct virtchnl2_create_vport); - if (!adapter->vport_params_reqd[idx]) { - adapter->vport_params_reqd[idx] = kzalloc(buf_size, - GFP_KERNEL); - if (!adapter->vport_params_reqd[idx]) - return -ENOMEM; - } + vport_msg = kzalloc(buf_size, GFP_KERNEL); + if (!vport_msg) + return -ENOMEM; - vport_msg = adapter->vport_params_reqd[idx]; vport_msg->vport_type = cpu_to_le16(VIRTCHNL2_VPORT_TYPE_DEFAULT); vport_msg->vport_index = cpu_to_le16(idx); @@ -1582,8 +1578,7 @@ int idpf_send_create_vport_msg(struct idpf_adapter *adapter, err = idpf_vport_calc_total_qs(adapter, idx, vport_msg, max_q); if (err) { dev_err(&adapter->pdev->dev, "Enough queues are not available"); - - return err; + goto rel_buf; } if (!adapter->vport_params_recvd[idx]) { @@ -1591,7 +1586,7 @@ int idpf_send_create_vport_msg(struct idpf_adapter *adapter, GFP_KERNEL); if (!adapter->vport_params_recvd[idx]) { err = -ENOMEM; - goto free_vport_params; + goto rel_buf; } } @@ -1607,13 +1602,15 @@ int idpf_send_create_vport_msg(struct idpf_adapter *adapter, goto free_vport_params; } + kfree(vport_msg); + return 0; free_vport_params: kfree(adapter->vport_params_recvd[idx]); adapter->vport_params_recvd[idx] = NULL; - kfree(adapter->vport_params_reqd[idx]); - adapter->vport_params_reqd[idx] = NULL; +rel_buf: + kfree(vport_msg); return err; } @@ -3419,8 +3416,6 @@ static void idpf_vport_params_buf_rel(struct idpf_adapter *adapter) { kfree(adapter->vport_params_recvd); adapter->vport_params_recvd = NULL; - kfree(adapter->vport_params_reqd); - adapter->vport_params_reqd = NULL; kfree(adapter->vport_ids); adapter->vport_ids = NULL; } @@ -3435,15 +3430,10 @@ static int idpf_vport_params_buf_alloc(struct idpf_adapter *adapter) { u16 num_max_vports = idpf_get_max_vports(adapter); - adapter->vport_params_reqd = kzalloc_objs(*adapter->vport_params_reqd, - num_max_vports); - if (!adapter->vport_params_reqd) - return -ENOMEM; - adapter->vport_params_recvd = kzalloc_objs(*adapter->vport_params_recvd, num_max_vports); if (!adapter->vport_params_recvd) - goto err_mem; + return -ENOMEM; adapter->vport_ids = kcalloc(num_max_vports, sizeof(u32), GFP_KERNEL); if (!adapter->vport_ids) From 8760a06e4553e1375da5189b61ea992143d5eace Mon Sep 17 00:00:00 2001 From: Larysa Zaremba Date: Thu, 25 Jun 2026 18:02:01 +0200 Subject: [PATCH 1252/1433] idpf: remove unused code for getting RSS info from device idpf_send_get_set_rss_lut_msg() and idpf_send_get_set_rss_key_msg() do not handle the get=true path properly. Response validation is insufficient, memcpy size is wrong, LE-to-CPU conversion is missing. Fortunately, those functions are never used with get=true. Given how broken this dead code is, it is unlikely to be useful in the future. Rename idpf_send_get_set_rss_lut_msg() to idpf_send_set_rss_lut_msg(), idpf_send_get_set_rss_key_msg() to idpf_send_set_rss_key_msg(), remove the get parameter and remove all get=true cases from the function. Reviewed-by: Alexander Lobakin Signed-off-by: Larysa Zaremba Tested-by: Samuel Salin Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/idpf/idpf_txrx.c | 4 +- .../net/ethernet/intel/idpf/idpf_virtchnl.c | 107 +++--------------- .../net/ethernet/intel/idpf/idpf_virtchnl.h | 10 +- 3 files changed, 22 insertions(+), 99 deletions(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.c b/drivers/net/ethernet/intel/idpf/idpf_txrx.c index 99fcd8e298d6..c3ffb445297f 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.c +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.c @@ -4676,11 +4676,11 @@ int idpf_config_rss(struct idpf_vport *vport, struct idpf_rss_data *rss_data) u32 vport_id = vport->vport_id; int err; - err = idpf_send_get_set_rss_key_msg(adapter, rss_data, vport_id, false); + err = idpf_send_set_rss_key_msg(adapter, rss_data, vport_id); if (err) return err; - return idpf_send_get_set_rss_lut_msg(adapter, rss_data, vport_id, false); + return idpf_send_set_rss_lut_msg(adapter, rss_data, vport_id); } /** diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c index c4a128a114a5..21cf9bbb917b 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c @@ -2848,29 +2848,26 @@ int idpf_send_get_stats_msg(struct idpf_netdev_priv *np, } /** - * idpf_send_get_set_rss_lut_msg - Send virtchnl get or set RSS lut message + * idpf_send_set_rss_lut_msg - Send virtchnl set RSS lut message * @adapter: adapter pointer used to send virtchnl message * @rss_data: pointer to RSS key and lut info * @vport_id: vport identifier used while preparing the virtchnl message - * @get: flag to set or get RSS look up table * - * When rxhash is disabled, RSS LUT will be configured with zeros. If rxhash + * When rxhash is disabled, RSS LUT will be configured with zeros. If rxhash * is enabled, the LUT values stored in driver's soft copy will be used to setup * the HW. * * Return: 0 on success, negative on failure. */ -int idpf_send_get_set_rss_lut_msg(struct idpf_adapter *adapter, - struct idpf_rss_data *rss_data, - u32 vport_id, bool get) +int idpf_send_set_rss_lut_msg(struct idpf_adapter *adapter, + struct idpf_rss_data *rss_data, u32 vport_id) { - struct virtchnl2_rss_lut *recv_rl __free(kfree) = NULL; struct virtchnl2_rss_lut *rl __free(kfree) = NULL; struct idpf_vc_xn_params xn_params = {}; - int buf_size, lut_buf_size; struct idpf_vport *vport; ssize_t reply_sz; bool rxhash_ena; + int buf_size; int i; vport = idpf_vid_to_vport(adapter, vport_id); @@ -2889,72 +2886,34 @@ int idpf_send_get_set_rss_lut_msg(struct idpf_adapter *adapter, xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; xn_params.send_buf.iov_base = rl; xn_params.send_buf.iov_len = buf_size; + xn_params.vc_op = VIRTCHNL2_OP_SET_RSS_LUT; - if (get) { - recv_rl = kzalloc(IDPF_CTLQ_MAX_BUF_LEN, GFP_KERNEL); - if (!recv_rl) - return -ENOMEM; - xn_params.vc_op = VIRTCHNL2_OP_GET_RSS_LUT; - xn_params.recv_buf.iov_base = recv_rl; - xn_params.recv_buf.iov_len = IDPF_CTLQ_MAX_BUF_LEN; - } else { - rl->lut_entries = cpu_to_le16(rss_data->rss_lut_size); - for (i = 0; i < rss_data->rss_lut_size; i++) - rl->lut[i] = rxhash_ena ? - cpu_to_le32(rss_data->rss_lut[i]) : 0; + rl->lut_entries = cpu_to_le16(rss_data->rss_lut_size); + for (i = 0; i < rss_data->rss_lut_size; i++) + rl->lut[i] = rxhash_ena ? cpu_to_le32(rss_data->rss_lut[i]) : 0; - xn_params.vc_op = VIRTCHNL2_OP_SET_RSS_LUT; - } reply_sz = idpf_vc_xn_exec(adapter, &xn_params); if (reply_sz < 0) return reply_sz; - if (!get) - return 0; - if (reply_sz < sizeof(struct virtchnl2_rss_lut)) - return -EIO; - - lut_buf_size = le16_to_cpu(recv_rl->lut_entries) * sizeof(u32); - if (reply_sz < lut_buf_size) - return -EIO; - - /* size didn't change, we can reuse existing lut buf */ - if (rss_data->rss_lut_size == le16_to_cpu(recv_rl->lut_entries)) - goto do_memcpy; - - rss_data->rss_lut_size = le16_to_cpu(recv_rl->lut_entries); - kfree(rss_data->rss_lut); - - rss_data->rss_lut = kzalloc(lut_buf_size, GFP_KERNEL); - if (!rss_data->rss_lut) { - rss_data->rss_lut_size = 0; - return -ENOMEM; - } - -do_memcpy: - memcpy(rss_data->rss_lut, recv_rl->lut, rss_data->rss_lut_size); return 0; } /** - * idpf_send_get_set_rss_key_msg - Send virtchnl get or set RSS key message + * idpf_send_set_rss_key_msg - Send virtchnl set RSS key message * @adapter: adapter pointer used to send virtchnl message * @rss_data: pointer to RSS key and lut info * @vport_id: vport identifier used while preparing the virtchnl message - * @get: flag to set or get RSS look up table * * Return: 0 on success, negative on failure */ -int idpf_send_get_set_rss_key_msg(struct idpf_adapter *adapter, - struct idpf_rss_data *rss_data, - u32 vport_id, bool get) +int idpf_send_set_rss_key_msg(struct idpf_adapter *adapter, + struct idpf_rss_data *rss_data, u32 vport_id) { - struct virtchnl2_rss_key *recv_rk __free(kfree) = NULL; struct virtchnl2_rss_key *rk __free(kfree) = NULL; struct idpf_vc_xn_params xn_params = {}; ssize_t reply_sz; int i, buf_size; - u16 key_size; buf_size = struct_size(rk, key_flex, rss_data->rss_key_size); rk = kzalloc(buf_size, GFP_KERNEL); @@ -2965,49 +2924,15 @@ int idpf_send_get_set_rss_key_msg(struct idpf_adapter *adapter, xn_params.send_buf.iov_base = rk; xn_params.send_buf.iov_len = buf_size; xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - if (get) { - recv_rk = kzalloc(IDPF_CTLQ_MAX_BUF_LEN, GFP_KERNEL); - if (!recv_rk) - return -ENOMEM; + xn_params.vc_op = VIRTCHNL2_OP_SET_RSS_KEY; - xn_params.vc_op = VIRTCHNL2_OP_GET_RSS_KEY; - xn_params.recv_buf.iov_base = recv_rk; - xn_params.recv_buf.iov_len = IDPF_CTLQ_MAX_BUF_LEN; - } else { - rk->key_len = cpu_to_le16(rss_data->rss_key_size); - for (i = 0; i < rss_data->rss_key_size; i++) - rk->key_flex[i] = rss_data->rss_key[i]; - - xn_params.vc_op = VIRTCHNL2_OP_SET_RSS_KEY; - } + rk->key_len = cpu_to_le16(rss_data->rss_key_size); + for (i = 0; i < rss_data->rss_key_size; i++) + rk->key_flex[i] = rss_data->rss_key[i]; reply_sz = idpf_vc_xn_exec(adapter, &xn_params); if (reply_sz < 0) return reply_sz; - if (!get) - return 0; - if (reply_sz < sizeof(struct virtchnl2_rss_key)) - return -EIO; - - key_size = min_t(u16, NETDEV_RSS_KEY_LEN, - le16_to_cpu(recv_rk->key_len)); - if (reply_sz < key_size) - return -EIO; - - /* key len didn't change, reuse existing buf */ - if (rss_data->rss_key_size == key_size) - goto do_memcpy; - - rss_data->rss_key_size = key_size; - kfree(rss_data->rss_key); - rss_data->rss_key = kzalloc(key_size, GFP_KERNEL); - if (!rss_data->rss_key) { - rss_data->rss_key_size = 0; - return -ENOMEM; - } - -do_memcpy: - memcpy(rss_data->rss_key, recv_rk->key_flex, rss_data->rss_key_size); return 0; } diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h index 5f8ae8c5be20..e0c319bd9f38 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h @@ -208,12 +208,10 @@ int idpf_send_ena_dis_loopback_msg(struct idpf_adapter *adapter, u32 vport_id, int idpf_send_get_stats_msg(struct idpf_netdev_priv *np, struct idpf_port_stats *port_stats); int idpf_send_set_sriov_vfs_msg(struct idpf_adapter *adapter, u16 num_vfs); -int idpf_send_get_set_rss_key_msg(struct idpf_adapter *adapter, - struct idpf_rss_data *rss_data, - u32 vport_id, bool get); -int idpf_send_get_set_rss_lut_msg(struct idpf_adapter *adapter, - struct idpf_rss_data *rss_data, - u32 vport_id, bool get); +int idpf_send_set_rss_key_msg(struct idpf_adapter *adapter, + struct idpf_rss_data *rss_data, u32 vport_id); +int idpf_send_set_rss_lut_msg(struct idpf_adapter *adapter, + struct idpf_rss_data *rss_data, u32 vport_id); void idpf_vc_xn_shutdown(struct idpf_vc_xn_manager *vcxn_mngr); int idpf_idc_rdma_vc_send_sync(struct iidc_rdma_core_dev_info *cdev_info, u8 *send_msg, u16 msg_size, From 6b284aa2ddf36bd1e728a25de1c8c0793003c528 Mon Sep 17 00:00:00 2001 From: Pavan Kumar Linga Date: Thu, 25 Jun 2026 18:02:02 +0200 Subject: [PATCH 1253/1433] idpf: refactor idpf to use libie_pci APIs Use libie_pci init and MMIO APIs where possible, struct idpf_hw cannot be deleted for now as it also houses control queues that will be refactored later. Memory regions are added and removed in layers, so e.g. mailbox and rstat are added first and not removed until teardown. libie_pci stores the regions in the order of addition, so no new locks/checks are required, despite the data structure change. Use libie_cp header for libie_ctlq_ctx that contains mmio info from the start in order to not increase the diff later. Reviewed-by: Madhu Chittim Reviewed-by: Sridhar Samudrala Signed-off-by: Pavan Kumar Linga Tested-by: Samuel Salin Co-developed-by: Larysa Zaremba Signed-off-by: Larysa Zaremba Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/idpf/Kconfig | 1 + drivers/net/ethernet/intel/idpf/idpf.h | 70 +------ .../net/ethernet/intel/idpf/idpf_controlq.c | 26 ++- .../net/ethernet/intel/idpf/idpf_controlq.h | 2 - drivers/net/ethernet/intel/idpf/idpf_dev.c | 61 ++++--- drivers/net/ethernet/intel/idpf/idpf_idc.c | 32 +++- drivers/net/ethernet/intel/idpf/idpf_lib.c | 7 +- drivers/net/ethernet/intel/idpf/idpf_main.c | 114 ++++++------ drivers/net/ethernet/intel/idpf/idpf_vf_dev.c | 57 +++--- .../net/ethernet/intel/idpf/idpf_virtchnl.c | 171 +++++++++--------- .../net/ethernet/intel/idpf/idpf_virtchnl.h | 2 + .../ethernet/intel/idpf/idpf_virtchnl_ptp.c | 58 +++--- 12 files changed, 285 insertions(+), 316 deletions(-) diff --git a/drivers/net/ethernet/intel/idpf/Kconfig b/drivers/net/ethernet/intel/idpf/Kconfig index adab2154125b..586df3a4afe9 100644 --- a/drivers/net/ethernet/intel/idpf/Kconfig +++ b/drivers/net/ethernet/intel/idpf/Kconfig @@ -6,6 +6,7 @@ config IDPF depends on PCI_MSI depends on PTP_1588_CLOCK_OPTIONAL select DIMLIB + select LIBIE_CP select LIBETH_XDP help This driver supports Intel(R) Infrastructure Data Path Function diff --git a/drivers/net/ethernet/intel/idpf/idpf.h b/drivers/net/ethernet/intel/idpf/idpf.h index c5e47e79a641..92a120aadfcd 100644 --- a/drivers/net/ethernet/intel/idpf/idpf.h +++ b/drivers/net/ethernet/intel/idpf/idpf.h @@ -23,6 +23,7 @@ struct idpf_rss_data; #include #include +#include #include #include "idpf_txrx.h" @@ -625,6 +626,7 @@ struct idpf_vc_xn_manager; * @flags: See enum idpf_flags * @reset_reg: See struct idpf_reset_reg * @hw: Device access data + * @ctlq_ctx: controlq context * @num_avail_msix: Available number of MSIX vectors * @num_msix_entries: Number of entries in MSIX table * @msix_entries: MSIX table @@ -682,6 +684,7 @@ struct idpf_adapter { DECLARE_BITMAP(flags, IDPF_FLAGS_NBITS); struct idpf_reset_reg reset_reg; struct idpf_hw hw; + struct libie_ctlq_ctx ctlq_ctx; u16 num_avail_msix; u16 num_msix_entries; struct msix_entry *msix_entries; @@ -870,70 +873,6 @@ static inline u8 idpf_get_min_tx_pkt_len(struct idpf_adapter *adapter) return pkt_len ? pkt_len : IDPF_TX_MIN_PKT_LEN; } -/** - * idpf_get_mbx_reg_addr - Get BAR0 mailbox register address - * @adapter: private data struct - * @reg_offset: register offset value - * - * Return: BAR0 mailbox register address based on register offset. - */ -static inline void __iomem *idpf_get_mbx_reg_addr(struct idpf_adapter *adapter, - resource_size_t reg_offset) -{ - return adapter->hw.mbx.vaddr + reg_offset; -} - -/** - * idpf_get_rstat_reg_addr - Get BAR0 rstat register address - * @adapter: private data struct - * @reg_offset: register offset value - * - * Return: BAR0 rstat register address based on register offset. - */ -static inline void __iomem *idpf_get_rstat_reg_addr(struct idpf_adapter *adapter, - resource_size_t reg_offset) -{ - reg_offset -= adapter->dev_ops.static_reg_info[1].start; - - return adapter->hw.rstat.vaddr + reg_offset; -} - -/** - * idpf_get_reg_addr - Get BAR0 register address - * @adapter: private data struct - * @reg_offset: register offset value - * - * Based on the register offset, return the actual BAR0 register address - */ -static inline void __iomem *idpf_get_reg_addr(struct idpf_adapter *adapter, - resource_size_t reg_offset) -{ - struct idpf_hw *hw = &adapter->hw; - - for (int i = 0; i < hw->num_lan_regs; i++) { - struct idpf_mmio_reg *region = &hw->lan_regs[i]; - - if (reg_offset >= region->addr_start && - reg_offset < (region->addr_start + region->addr_len)) { - /* Convert the offset so that it is relative to the - * start of the region. Then add the base address of - * the region to get the final address. - */ - reg_offset -= region->addr_start; - - return region->vaddr + reg_offset; - } - } - - /* It's impossible to hit this case with offsets from the CP. But if we - * do for any other reason, the kernel will panic on that register - * access. Might as well do it here to make it clear what's happening. - */ - BUG(); - - return NULL; -} - /** * idpf_is_reset_detected - check if we were reset at some point * @adapter: driver specific private structure @@ -945,7 +884,8 @@ static inline bool idpf_is_reset_detected(struct idpf_adapter *adapter) if (!adapter->hw.arq) return true; - return !(readl(idpf_get_mbx_reg_addr(adapter, adapter->hw.arq->reg.len)) & + return !(readl(libie_pci_get_mmio_addr(&adapter->ctlq_ctx.mmio_info, + adapter->hw.arq->reg.len)) & adapter->hw.arq->reg.len_mask); } diff --git a/drivers/net/ethernet/intel/idpf/idpf_controlq.c b/drivers/net/ethernet/intel/idpf/idpf_controlq.c index d2dde43269e9..020b08367e18 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_controlq.c +++ b/drivers/net/ethernet/intel/idpf/idpf_controlq.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only /* Copyright (C) 2023 Intel Corporation */ -#include "idpf_controlq.h" +#include "idpf.h" /** * idpf_ctlq_setup_regs - initialize control queue registers @@ -34,21 +34,27 @@ static void idpf_ctlq_setup_regs(struct idpf_ctlq_info *cq, static void idpf_ctlq_init_regs(struct idpf_hw *hw, struct idpf_ctlq_info *cq, bool is_rxq) { + struct libie_mmio_info *mmio = &hw->back->ctlq_ctx.mmio_info; + /* Update tail to post pre-allocated buffers for rx queues */ if (is_rxq) - idpf_mbx_wr32(hw, cq->reg.tail, (u32)(cq->ring_size - 1)); + writel((u32)(cq->ring_size - 1), + libie_pci_get_mmio_addr(mmio, cq->reg.tail)); /* For non-Mailbox control queues only TAIL need to be set */ if (cq->q_id != -1) return; /* Clear Head for both send or receive */ - idpf_mbx_wr32(hw, cq->reg.head, 0); + writel(0, libie_pci_get_mmio_addr(mmio, cq->reg.head)); /* set starting point */ - idpf_mbx_wr32(hw, cq->reg.bal, lower_32_bits(cq->desc_ring.pa)); - idpf_mbx_wr32(hw, cq->reg.bah, upper_32_bits(cq->desc_ring.pa)); - idpf_mbx_wr32(hw, cq->reg.len, (cq->ring_size | cq->reg.len_ena_mask)); + writel(lower_32_bits(cq->desc_ring.pa), + libie_pci_get_mmio_addr(mmio, cq->reg.bal)); + writel(upper_32_bits(cq->desc_ring.pa), + libie_pci_get_mmio_addr(mmio, cq->reg.bah)); + writel((cq->ring_size | cq->reg.len_ena_mask), + libie_pci_get_mmio_addr(mmio, cq->reg.len)); } /** @@ -326,7 +332,9 @@ int idpf_ctlq_send(struct idpf_hw *hw, struct idpf_ctlq_info *cq, */ dma_wmb(); - idpf_mbx_wr32(hw, cq->reg.tail, cq->next_to_use); + writel(cq->next_to_use, + libie_pci_get_mmio_addr(&hw->back->ctlq_ctx.mmio_info, + cq->reg.tail)); err_unlock: spin_unlock(&cq->cq_lock); @@ -518,7 +526,9 @@ int idpf_ctlq_post_rx_buffs(struct idpf_hw *hw, struct idpf_ctlq_info *cq, dma_wmb(); - idpf_mbx_wr32(hw, cq->reg.tail, cq->next_to_post); + writel(cq->next_to_post, + libie_pci_get_mmio_addr(&hw->back->ctlq_ctx.mmio_info, + cq->reg.tail)); } spin_unlock(&cq->cq_lock); diff --git a/drivers/net/ethernet/intel/idpf/idpf_controlq.h b/drivers/net/ethernet/intel/idpf/idpf_controlq.h index de4ece40c2ff..acf595e9265f 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_controlq.h +++ b/drivers/net/ethernet/intel/idpf/idpf_controlq.h @@ -109,8 +109,6 @@ struct idpf_mmio_reg { * Align to ctlq_hw_info */ struct idpf_hw { - struct idpf_mmio_reg mbx; - struct idpf_mmio_reg rstat; /* Array of remaining LAN BAR regions */ int num_lan_regs; struct idpf_mmio_reg *lan_regs; diff --git a/drivers/net/ethernet/intel/idpf/idpf_dev.c b/drivers/net/ethernet/intel/idpf/idpf_dev.c index 4079a787657f..64cd751fbd71 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_dev.c +++ b/drivers/net/ethernet/intel/idpf/idpf_dev.c @@ -16,7 +16,6 @@ static void idpf_ctlq_reg_init(struct idpf_adapter *adapter, struct idpf_ctlq_create_info *cq) { - resource_size_t mbx_start = adapter->dev_ops.static_reg_info[0].start; int i; for (i = 0; i < IDPF_NUM_DFLT_MBX_Q; i++) { @@ -25,22 +24,22 @@ static void idpf_ctlq_reg_init(struct idpf_adapter *adapter, switch (ccq->type) { case IDPF_CTLQ_TYPE_MAILBOX_TX: /* set head and tail registers in our local struct */ - ccq->reg.head = PF_FW_ATQH - mbx_start; - ccq->reg.tail = PF_FW_ATQT - mbx_start; - ccq->reg.len = PF_FW_ATQLEN - mbx_start; - ccq->reg.bah = PF_FW_ATQBAH - mbx_start; - ccq->reg.bal = PF_FW_ATQBAL - mbx_start; + ccq->reg.head = PF_FW_ATQH; + ccq->reg.tail = PF_FW_ATQT; + ccq->reg.len = PF_FW_ATQLEN; + ccq->reg.bah = PF_FW_ATQBAH; + ccq->reg.bal = PF_FW_ATQBAL; ccq->reg.len_mask = PF_FW_ATQLEN_ATQLEN_M; ccq->reg.len_ena_mask = PF_FW_ATQLEN_ATQENABLE_M; ccq->reg.head_mask = PF_FW_ATQH_ATQH_M; break; case IDPF_CTLQ_TYPE_MAILBOX_RX: /* set head and tail registers in our local struct */ - ccq->reg.head = PF_FW_ARQH - mbx_start; - ccq->reg.tail = PF_FW_ARQT - mbx_start; - ccq->reg.len = PF_FW_ARQLEN - mbx_start; - ccq->reg.bah = PF_FW_ARQBAH - mbx_start; - ccq->reg.bal = PF_FW_ARQBAL - mbx_start; + ccq->reg.head = PF_FW_ARQH; + ccq->reg.tail = PF_FW_ARQT; + ccq->reg.len = PF_FW_ARQLEN; + ccq->reg.bah = PF_FW_ARQBAH; + ccq->reg.bal = PF_FW_ARQBAL; ccq->reg.len_mask = PF_FW_ARQLEN_ARQLEN_M; ccq->reg.len_ena_mask = PF_FW_ARQLEN_ARQENABLE_M; ccq->reg.head_mask = PF_FW_ARQH_ARQH_M; @@ -57,13 +56,14 @@ static void idpf_ctlq_reg_init(struct idpf_adapter *adapter, */ static void idpf_mb_intr_reg_init(struct idpf_adapter *adapter) { + struct libie_mmio_info *mmio = &adapter->ctlq_ctx.mmio_info; struct idpf_intr_reg *intr = &adapter->mb_vector.intr_reg; u32 dyn_ctl = le32_to_cpu(adapter->caps.mailbox_dyn_ctl); - intr->dyn_ctl = idpf_get_reg_addr(adapter, dyn_ctl); + intr->dyn_ctl = libie_pci_get_mmio_addr(mmio, dyn_ctl); intr->dyn_ctl_intena_m = PF_GLINT_DYN_CTL_INTENA_M; intr->dyn_ctl_itridx_m = PF_GLINT_DYN_CTL_ITR_INDX_M; - intr->icr_ena = idpf_get_reg_addr(adapter, PF_INT_DIR_OICR_ENA); + intr->icr_ena = libie_pci_get_mmio_addr(mmio, PF_INT_DIR_OICR_ENA); intr->icr_ena_ctlq_m = PF_INT_DIR_OICR_ENA_M; } @@ -78,6 +78,7 @@ static int idpf_intr_reg_init(struct idpf_vport *vport, struct idpf_adapter *adapter = vport->adapter; u16 num_vecs = rsrc->num_q_vectors; struct idpf_vec_regs *reg_vals; + struct libie_mmio_info *mmio; int num_regs, i, err = 0; u32 rx_itr, tx_itr, val; u16 total_vecs; @@ -93,14 +94,17 @@ static int idpf_intr_reg_init(struct idpf_vport *vport, goto free_reg_vals; } + mmio = &adapter->ctlq_ctx.mmio_info; + for (i = 0; i < num_vecs; i++) { struct idpf_q_vector *q_vector = &rsrc->q_vectors[i]; u16 vec_id = rsrc->q_vector_idxs[i] - IDPF_MBX_Q_VEC; struct idpf_intr_reg *intr = &q_vector->intr_reg; + struct idpf_vec_regs *reg = ®_vals[vec_id]; u32 spacing; - intr->dyn_ctl = idpf_get_reg_addr(adapter, - reg_vals[vec_id].dyn_ctl_reg); + intr->dyn_ctl = libie_pci_get_mmio_addr(mmio, + reg->dyn_ctl_reg); intr->dyn_ctl_intena_m = PF_GLINT_DYN_CTL_INTENA_M; intr->dyn_ctl_intena_msk_m = PF_GLINT_DYN_CTL_INTENA_MSK_M; intr->dyn_ctl_itridx_s = PF_GLINT_DYN_CTL_ITR_INDX_S; @@ -110,22 +114,21 @@ static int idpf_intr_reg_init(struct idpf_vport *vport, intr->dyn_ctl_sw_itridx_ena_m = PF_GLINT_DYN_CTL_SW_ITR_INDX_ENA_M; - spacing = IDPF_ITR_IDX_SPACING(reg_vals[vec_id].itrn_index_spacing, + spacing = IDPF_ITR_IDX_SPACING(reg->itrn_index_spacing, IDPF_PF_ITR_IDX_SPACING); rx_itr = PF_GLINT_ITR_ADDR(VIRTCHNL2_ITR_IDX_0, - reg_vals[vec_id].itrn_reg, - spacing); + reg->itrn_reg, spacing); tx_itr = PF_GLINT_ITR_ADDR(VIRTCHNL2_ITR_IDX_1, - reg_vals[vec_id].itrn_reg, - spacing); - intr->rx_itr = idpf_get_reg_addr(adapter, rx_itr); - intr->tx_itr = idpf_get_reg_addr(adapter, tx_itr); + reg->itrn_reg, spacing); + intr->rx_itr = libie_pci_get_mmio_addr(mmio, rx_itr); + intr->tx_itr = libie_pci_get_mmio_addr(mmio, tx_itr); } /* Data vector for NOIRQ queues */ val = reg_vals[rsrc->q_vector_idxs[i] - IDPF_MBX_Q_VEC].dyn_ctl_reg; - rsrc->noirq_dyn_ctl = idpf_get_reg_addr(adapter, val); + rsrc->noirq_dyn_ctl = + libie_pci_get_mmio_addr(&adapter->ctlq_ctx.mmio_info, val); val = PF_GLINT_DYN_CTL_WB_ON_ITR_M | PF_GLINT_DYN_CTL_INTENA_MSK_M | FIELD_PREP(PF_GLINT_DYN_CTL_ITR_INDX_M, IDPF_NO_ITR_UPDATE_IDX); @@ -143,7 +146,9 @@ static int idpf_intr_reg_init(struct idpf_vport *vport, */ static void idpf_reset_reg_init(struct idpf_adapter *adapter) { - adapter->reset_reg.rstat = idpf_get_rstat_reg_addr(adapter, PFGEN_RSTAT); + adapter->reset_reg.rstat = + libie_pci_get_mmio_addr(&adapter->ctlq_ctx.mmio_info, + PFGEN_RSTAT); adapter->reset_reg.rstat_m = PFGEN_RSTAT_PFR_STATE_M; } @@ -155,11 +160,11 @@ static void idpf_reset_reg_init(struct idpf_adapter *adapter) static void idpf_trigger_reset(struct idpf_adapter *adapter, enum idpf_flags __always_unused trig_cause) { - u32 reset_reg; + void __iomem *addr; - reset_reg = readl(idpf_get_rstat_reg_addr(adapter, PFGEN_CTRL)); - writel(reset_reg | PFGEN_CTRL_PFSWR, - idpf_get_rstat_reg_addr(adapter, PFGEN_CTRL)); + addr = libie_pci_get_mmio_addr(&adapter->ctlq_ctx.mmio_info, + PFGEN_CTRL); + writel(readl(addr) | PFGEN_CTRL_PFSWR, addr); } /** diff --git a/drivers/net/ethernet/intel/idpf/idpf_idc.c b/drivers/net/ethernet/intel/idpf/idpf_idc.c index b7d6b08fc89e..b6cd1c25ae5d 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_idc.c +++ b/drivers/net/ethernet/intel/idpf/idpf_idc.c @@ -416,9 +416,12 @@ idpf_idc_init_msix_data(struct idpf_adapter *adapter) int idpf_idc_init_aux_core_dev(struct idpf_adapter *adapter, enum iidc_function_type ftype) { + struct libie_mmio_info *mmio = &adapter->ctlq_ctx.mmio_info; struct iidc_rdma_core_dev_info *cdev_info; struct iidc_rdma_priv_dev_info *privd; - int err, i; + struct libie_pci_mmio_region *mr; + size_t num_mem_regions; + int err, i = 0; adapter->cdev_info = kzalloc_obj(*cdev_info); if (!adapter->cdev_info) @@ -436,22 +439,31 @@ int idpf_idc_init_aux_core_dev(struct idpf_adapter *adapter, cdev_info->rdma_protocol = IIDC_RDMA_PROTOCOL_ROCEV2; privd->ftype = ftype; + num_mem_regions = list_count_nodes(&mmio->mmio_list); + if (num_mem_regions <= IDPF_MMIO_REG_NUM_STATIC) { + err = -EINVAL; + goto err_plug_aux_dev; + } + + num_mem_regions -= IDPF_MMIO_REG_NUM_STATIC; privd->mapped_mem_regions = kzalloc_objs(struct iidc_rdma_lan_mapped_mem_region, - adapter->hw.num_lan_regs); + num_mem_regions); if (!privd->mapped_mem_regions) { err = -ENOMEM; goto err_plug_aux_dev; } - privd->num_memory_regions = cpu_to_le16(adapter->hw.num_lan_regs); - for (i = 0; i < adapter->hw.num_lan_regs; i++) { - privd->mapped_mem_regions[i].region_addr = - adapter->hw.lan_regs[i].vaddr; - privd->mapped_mem_regions[i].size = - cpu_to_le64(adapter->hw.lan_regs[i].addr_len); - privd->mapped_mem_regions[i].start_offset = - cpu_to_le64(adapter->hw.lan_regs[i].addr_start); + privd->num_memory_regions = cpu_to_le16(num_mem_regions); + list_for_each_entry(mr, &mmio->mmio_list, list) { + if (!idpf_mmio_region_non_static(&adapter->ctlq_ctx.mmio_info, + mr)) + continue; + + privd->mapped_mem_regions[i].region_addr = mr->addr; + privd->mapped_mem_regions[i].size = cpu_to_le64(mr->size); + privd->mapped_mem_regions[i++].start_offset = + cpu_to_le64(mr->offset); } idpf_idc_init_msix_data(adapter); diff --git a/drivers/net/ethernet/intel/idpf/idpf_lib.c b/drivers/net/ethernet/intel/idpf/idpf_lib.c index 810d220c229a..8001732eb45b 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_lib.c +++ b/drivers/net/ethernet/intel/idpf/idpf_lib.c @@ -1847,15 +1847,14 @@ void idpf_deinit_task(struct idpf_adapter *adapter) /** * idpf_check_reset_complete - check that reset is complete - * @hw: pointer to hw struct + * @adapter: adapter to check * @reset_reg: struct with reset registers * * Returns 0 if device is ready to use, or -EBUSY if it's in reset. **/ -static int idpf_check_reset_complete(struct idpf_hw *hw, +static int idpf_check_reset_complete(struct idpf_adapter *adapter, struct idpf_reset_reg *reset_reg) { - struct idpf_adapter *adapter = hw->back; int i; for (i = 0; i < 2000; i++) { @@ -1918,7 +1917,7 @@ static void idpf_init_hard_reset(struct idpf_adapter *adapter) } /* Wait for reset to complete */ - err = idpf_check_reset_complete(&adapter->hw, &adapter->reset_reg); + err = idpf_check_reset_complete(adapter, &adapter->reset_reg); if (err) { dev_err(dev, "The driver was unable to contact the device's firmware. Check that the FW is running. Driver state= 0x%x\n", adapter->state); diff --git a/drivers/net/ethernet/intel/idpf/idpf_main.c b/drivers/net/ethernet/intel/idpf/idpf_main.c index ab3c409e587b..10dfc4cf4fa4 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_main.c +++ b/drivers/net/ethernet/intel/idpf/idpf_main.c @@ -15,6 +15,8 @@ MODULE_DESCRIPTION(DRV_SUMMARY); MODULE_IMPORT_NS("LIBETH"); +MODULE_IMPORT_NS("LIBIE_CP"); +MODULE_IMPORT_NS("LIBIE_PCI"); MODULE_IMPORT_NS("LIBETH_XDP"); MODULE_LICENSE("GPL"); @@ -56,8 +58,16 @@ static int idpf_get_device_type(struct pci_dev *pdev) static int idpf_dev_init(struct idpf_adapter *adapter, const struct pci_device_id *ent) { + struct libie_mmio_info *mmio_info = &adapter->ctlq_ctx.mmio_info; int ret; + ret = libie_pci_init_dev(adapter->pdev); + if (ret) + return ret; + + mmio_info->pdev = adapter->pdev; + INIT_LIST_HEAD(&mmio_info->mmio_list); + if (ent->class == IDPF_CLASS_NETWORK_ETHERNET_PROGIF) { ret = idpf_get_device_type(adapter->pdev); switch (ret) { @@ -90,6 +100,15 @@ static int idpf_dev_init(struct idpf_adapter *adapter, return 0; } +/** + * idpf_decfg_device - deconfigure device and device specific resources + * @adapter: driver specific private structure + */ +static void idpf_decfg_device(struct idpf_adapter *adapter) +{ + libie_pci_unmap_all_mmio_regions(&adapter->ctlq_ctx.mmio_info); +} + /** * idpf_remove - Device removal routine * @pdev: PCI device information struct @@ -159,6 +178,7 @@ static void idpf_remove(struct pci_dev *pdev) mutex_destroy(&adapter->queue_lock); mutex_destroy(&adapter->vc_buf_lock); + idpf_decfg_device(adapter); pci_set_drvdata(pdev, NULL); kfree(adapter); } @@ -181,46 +201,45 @@ static void idpf_shutdown(struct pci_dev *pdev) } /** - * idpf_cfg_hw - Initialize HW struct - * @adapter: adapter to setup hw struct for + * idpf_cfg_device - configure device and device specific resources + * @adapter: driver specific private structure * - * Returns 0 on success, negative on failure + * Return: %0 on success, -%errno on failure. */ -static int idpf_cfg_hw(struct idpf_adapter *adapter) +static int idpf_cfg_device(struct idpf_adapter *adapter) { - resource_size_t res_start, mbx_start, rstat_start; + struct libie_mmio_info *mmio_info = &adapter->ctlq_ctx.mmio_info; struct pci_dev *pdev = adapter->pdev; - struct idpf_hw *hw = &adapter->hw; - struct device *dev = &pdev->dev; - long len; - - res_start = pci_resource_start(pdev, 0); + struct resource *region; + bool mapped; + int err; /* Map mailbox space for virtchnl communication */ - mbx_start = res_start + adapter->dev_ops.static_reg_info[0].start; - len = resource_size(&adapter->dev_ops.static_reg_info[0]); - hw->mbx.vaddr = devm_ioremap(dev, mbx_start, len); - if (!hw->mbx.vaddr) { - pci_err(pdev, "failed to allocate BAR0 mbx region\n"); - + region = &adapter->dev_ops.static_reg_info[0]; + mapped = libie_pci_map_mmio_region(mmio_info, region->start, + resource_size(region)); + if (!mapped) { + pci_err(pdev, "failed to map BAR0 mbx region\n"); return -ENOMEM; } - hw->mbx.addr_start = adapter->dev_ops.static_reg_info[0].start; - hw->mbx.addr_len = len; /* Map rstat space for resets */ - rstat_start = res_start + adapter->dev_ops.static_reg_info[1].start; - len = resource_size(&adapter->dev_ops.static_reg_info[1]); - hw->rstat.vaddr = devm_ioremap(dev, rstat_start, len); - if (!hw->rstat.vaddr) { - pci_err(pdev, "failed to allocate BAR0 rstat region\n"); + region = &adapter->dev_ops.static_reg_info[1]; + mapped = libie_pci_map_mmio_region(mmio_info, region->start, + resource_size(region)); + if (!mapped) { + pci_err(pdev, "failed to map BAR0 rstat region\n"); + libie_pci_unmap_all_mmio_regions(mmio_info); return -ENOMEM; } - hw->rstat.addr_start = adapter->dev_ops.static_reg_info[1].start; - hw->rstat.addr_len = len; - hw->back = adapter; + err = pci_enable_ptm(pdev); + if (err) + pci_dbg(pdev, "PCIe PTM is not supported by PCIe bus/controller\n"); + + pci_set_drvdata(pdev, adapter); + adapter->hw.back = adapter; return 0; } @@ -246,32 +265,21 @@ static int idpf_probe(struct pci_dev *pdev, const struct pci_device_id *ent) adapter->req_rx_splitq = true; adapter->pdev = pdev; - err = pcim_enable_device(pdev); - if (err) - goto err_free; - err = pcim_request_region(pdev, 0, pci_name(pdev)); + err = idpf_dev_init(adapter, ent); if (err) { - pci_err(pdev, "pcim_request_region failed %pe\n", ERR_PTR(err)); - + dev_err(&pdev->dev, "Failed to initialize device (ID 0x%x): %d\n", + ent->device, err); goto err_free; } - err = pci_enable_ptm(pdev); - if (err) - pci_dbg(pdev, "PCIe PTM is not supported by PCIe bus/controller\n"); - - /* set up for high or low dma */ - err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); + err = idpf_cfg_device(adapter); if (err) { - pci_err(pdev, "DMA configuration failed: %pe\n", ERR_PTR(err)); - + pci_err(pdev, "Failed to configure device specific resources: %pe\n", + ERR_PTR(err)); goto err_free; } - pci_set_master(pdev); - pci_set_drvdata(pdev, adapter); - adapter->init_wq = alloc_workqueue("%s-%s-init", WQ_UNBOUND | WQ_MEM_RECLAIM, 0, dev_driver_string(dev), @@ -279,7 +287,7 @@ static int idpf_probe(struct pci_dev *pdev, const struct pci_device_id *ent) if (!adapter->init_wq) { dev_err(dev, "Failed to allocate init workqueue\n"); err = -ENOMEM; - goto err_free; + goto err_init_wq; } adapter->serv_wq = alloc_workqueue("%s-%s-service", @@ -324,20 +332,6 @@ static int idpf_probe(struct pci_dev *pdev, const struct pci_device_id *ent) /* setup msglvl */ adapter->msg_enable = netif_msg_init(-1, IDPF_AVAIL_NETIF_M); - err = idpf_dev_init(adapter, ent); - if (err) { - dev_err(&pdev->dev, "Unexpected dev ID 0x%x in idpf probe\n", - ent->device); - goto destroy_vc_event_wq; - } - - err = idpf_cfg_hw(adapter); - if (err) { - dev_err(dev, "Failed to configure HW structure for adapter: %d\n", - err); - goto destroy_vc_event_wq; - } - mutex_init(&adapter->vport_ctrl_lock); mutex_init(&adapter->vector_lock); mutex_init(&adapter->queue_lock); @@ -356,8 +350,6 @@ static int idpf_probe(struct pci_dev *pdev, const struct pci_device_id *ent) return 0; -destroy_vc_event_wq: - destroy_workqueue(adapter->vc_event_wq); err_vc_event_wq_alloc: destroy_workqueue(adapter->stats_wq); err_stats_wq_alloc: @@ -366,6 +358,8 @@ static int idpf_probe(struct pci_dev *pdev, const struct pci_device_id *ent) destroy_workqueue(adapter->serv_wq); err_serv_wq_alloc: destroy_workqueue(adapter->init_wq); +err_init_wq: + idpf_decfg_device(adapter); err_free: kfree(adapter); return err; diff --git a/drivers/net/ethernet/intel/idpf/idpf_vf_dev.c b/drivers/net/ethernet/intel/idpf/idpf_vf_dev.c index 6726084f6cfa..6cfa5edab4f6 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_vf_dev.c +++ b/drivers/net/ethernet/intel/idpf/idpf_vf_dev.c @@ -15,31 +15,28 @@ static void idpf_vf_ctlq_reg_init(struct idpf_adapter *adapter, struct idpf_ctlq_create_info *cq) { - resource_size_t mbx_start = adapter->dev_ops.static_reg_info[0].start; - int i; - - for (i = 0; i < IDPF_NUM_DFLT_MBX_Q; i++) { + for (int i = 0; i < IDPF_NUM_DFLT_MBX_Q; i++) { struct idpf_ctlq_create_info *ccq = cq + i; switch (ccq->type) { case IDPF_CTLQ_TYPE_MAILBOX_TX: /* set head and tail registers in our local struct */ - ccq->reg.head = VF_ATQH - mbx_start; - ccq->reg.tail = VF_ATQT - mbx_start; - ccq->reg.len = VF_ATQLEN - mbx_start; - ccq->reg.bah = VF_ATQBAH - mbx_start; - ccq->reg.bal = VF_ATQBAL - mbx_start; + ccq->reg.head = VF_ATQH; + ccq->reg.tail = VF_ATQT; + ccq->reg.len = VF_ATQLEN; + ccq->reg.bah = VF_ATQBAH; + ccq->reg.bal = VF_ATQBAL; ccq->reg.len_mask = VF_ATQLEN_ATQLEN_M; ccq->reg.len_ena_mask = VF_ATQLEN_ATQENABLE_M; ccq->reg.head_mask = VF_ATQH_ATQH_M; break; case IDPF_CTLQ_TYPE_MAILBOX_RX: /* set head and tail registers in our local struct */ - ccq->reg.head = VF_ARQH - mbx_start; - ccq->reg.tail = VF_ARQT - mbx_start; - ccq->reg.len = VF_ARQLEN - mbx_start; - ccq->reg.bah = VF_ARQBAH - mbx_start; - ccq->reg.bal = VF_ARQBAL - mbx_start; + ccq->reg.head = VF_ARQH; + ccq->reg.tail = VF_ARQT; + ccq->reg.len = VF_ARQLEN; + ccq->reg.bah = VF_ARQBAH; + ccq->reg.bal = VF_ARQBAL; ccq->reg.len_mask = VF_ARQLEN_ARQLEN_M; ccq->reg.len_ena_mask = VF_ARQLEN_ARQENABLE_M; ccq->reg.head_mask = VF_ARQH_ARQH_M; @@ -56,13 +53,14 @@ static void idpf_vf_ctlq_reg_init(struct idpf_adapter *adapter, */ static void idpf_vf_mb_intr_reg_init(struct idpf_adapter *adapter) { + struct libie_mmio_info *mmio = &adapter->ctlq_ctx.mmio_info; struct idpf_intr_reg *intr = &adapter->mb_vector.intr_reg; u32 dyn_ctl = le32_to_cpu(adapter->caps.mailbox_dyn_ctl); - intr->dyn_ctl = idpf_get_reg_addr(adapter, dyn_ctl); + intr->dyn_ctl = libie_pci_get_mmio_addr(mmio, dyn_ctl); intr->dyn_ctl_intena_m = VF_INT_DYN_CTL0_INTENA_M; intr->dyn_ctl_itridx_m = VF_INT_DYN_CTL0_ITR_INDX_M; - intr->icr_ena = idpf_get_reg_addr(adapter, VF_INT_ICR0_ENA1); + intr->icr_ena = libie_pci_get_mmio_addr(mmio, VF_INT_ICR0_ENA1); intr->icr_ena_ctlq_m = VF_INT_ICR0_ENA1_ADMINQ_M; } @@ -77,6 +75,7 @@ static int idpf_vf_intr_reg_init(struct idpf_vport *vport, struct idpf_adapter *adapter = vport->adapter; u16 num_vecs = rsrc->num_q_vectors; struct idpf_vec_regs *reg_vals; + struct libie_mmio_info *mmio; int num_regs, i, err = 0; u32 rx_itr, tx_itr, val; u16 total_vecs; @@ -92,14 +91,17 @@ static int idpf_vf_intr_reg_init(struct idpf_vport *vport, goto free_reg_vals; } + mmio = &adapter->ctlq_ctx.mmio_info; + for (i = 0; i < num_vecs; i++) { struct idpf_q_vector *q_vector = &rsrc->q_vectors[i]; u16 vec_id = rsrc->q_vector_idxs[i] - IDPF_MBX_Q_VEC; struct idpf_intr_reg *intr = &q_vector->intr_reg; + struct idpf_vec_regs *reg = ®_vals[vec_id]; u32 spacing; - intr->dyn_ctl = idpf_get_reg_addr(adapter, - reg_vals[vec_id].dyn_ctl_reg); + intr->dyn_ctl = libie_pci_get_mmio_addr(mmio, + reg->dyn_ctl_reg); intr->dyn_ctl_intena_m = VF_INT_DYN_CTLN_INTENA_M; intr->dyn_ctl_intena_msk_m = VF_INT_DYN_CTLN_INTENA_MSK_M; intr->dyn_ctl_itridx_s = VF_INT_DYN_CTLN_ITR_INDX_S; @@ -109,22 +111,21 @@ static int idpf_vf_intr_reg_init(struct idpf_vport *vport, intr->dyn_ctl_sw_itridx_ena_m = VF_INT_DYN_CTLN_SW_ITR_INDX_ENA_M; - spacing = IDPF_ITR_IDX_SPACING(reg_vals[vec_id].itrn_index_spacing, + spacing = IDPF_ITR_IDX_SPACING(reg->itrn_index_spacing, IDPF_VF_ITR_IDX_SPACING); rx_itr = VF_INT_ITRN_ADDR(VIRTCHNL2_ITR_IDX_0, - reg_vals[vec_id].itrn_reg, - spacing); + reg->itrn_reg, spacing); tx_itr = VF_INT_ITRN_ADDR(VIRTCHNL2_ITR_IDX_1, - reg_vals[vec_id].itrn_reg, - spacing); - intr->rx_itr = idpf_get_reg_addr(adapter, rx_itr); - intr->tx_itr = idpf_get_reg_addr(adapter, tx_itr); + reg->itrn_reg, spacing); + intr->rx_itr = libie_pci_get_mmio_addr(mmio, rx_itr); + intr->tx_itr = libie_pci_get_mmio_addr(mmio, tx_itr); } /* Data vector for NOIRQ queues */ val = reg_vals[rsrc->q_vector_idxs[i] - IDPF_MBX_Q_VEC].dyn_ctl_reg; - rsrc->noirq_dyn_ctl = idpf_get_reg_addr(adapter, val); + rsrc->noirq_dyn_ctl = + libie_pci_get_mmio_addr(&adapter->ctlq_ctx.mmio_info, val); val = VF_INT_DYN_CTLN_WB_ON_ITR_M | VF_INT_DYN_CTLN_INTENA_MSK_M | FIELD_PREP(VF_INT_DYN_CTLN_ITR_INDX_M, IDPF_NO_ITR_UPDATE_IDX); @@ -142,7 +143,9 @@ static int idpf_vf_intr_reg_init(struct idpf_vport *vport, */ static void idpf_vf_reset_reg_init(struct idpf_adapter *adapter) { - adapter->reset_reg.rstat = idpf_get_rstat_reg_addr(adapter, VFGEN_RSTAT); + adapter->reset_reg.rstat = + libie_pci_get_mmio_addr(&adapter->ctlq_ctx.mmio_info, + VFGEN_RSTAT); adapter->reset_reg.rstat_m = VFGEN_RSTAT_VFR_STATE_M; } diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c index 21cf9bbb917b..a44207162b57 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c @@ -2,6 +2,7 @@ /* Copyright (C) 2023 Intel Corporation */ #include +#include #include #include "idpf.h" @@ -1020,12 +1021,46 @@ static int idpf_send_get_caps_msg(struct idpf_adapter *adapter) } /** - * idpf_send_get_lan_memory_regions - Send virtchnl get LAN memory regions msg + * idpf_mmio_region_non_static - Check if region is not static + * @mmio_info: PCI resources info + * @reg: region to check + * + * Return: %true if region can be received though virtchnl command, + * %false if region is related to mailbox or resetting + */ +bool idpf_mmio_region_non_static(struct libie_mmio_info *mmio_info, + struct libie_pci_mmio_region *reg) +{ + struct idpf_adapter *adapter = + container_of(mmio_info, struct idpf_adapter, + ctlq_ctx.mmio_info); + + for (uint i = 0; i < IDPF_MMIO_REG_NUM_STATIC; i++) { + if (reg->bar_idx == 0 && + reg->offset == adapter->dev_ops.static_reg_info[i].start) + return false; + } + + return true; +} + +/** + * idpf_decfg_lan_memory_regions - Unmap non-static memory regions + * @adapter: Driver specific private structure + */ +static void idpf_decfg_lan_memory_regions(struct idpf_adapter *adapter) +{ + libie_pci_unmap_fltr_regs(&adapter->ctlq_ctx.mmio_info, + idpf_mmio_region_non_static); +} + +/** + * idpf_cfg_lan_memory_regions - Get (via virtchnl) and map LAN memory regions * @adapter: Driver specific private struct * * Return: 0 on success or error code on failure. */ -static int idpf_send_get_lan_memory_regions(struct idpf_adapter *adapter) +static int idpf_cfg_lan_memory_regions(struct idpf_adapter *adapter) { struct virtchnl2_get_lan_memory_regions *rcvd_regions __free(kfree); struct idpf_vc_xn_params xn_params = { @@ -1037,7 +1072,6 @@ static int idpf_send_get_lan_memory_regions(struct idpf_adapter *adapter) .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, }; int num_regions, size; - struct idpf_hw *hw; ssize_t reply_sz; int err = 0; @@ -1060,86 +1094,56 @@ static int idpf_send_get_lan_memory_regions(struct idpf_adapter *adapter) if (size > IDPF_CTLQ_MAX_BUF_LEN) return -EINVAL; - hw = &adapter->hw; - hw->lan_regs = kzalloc_objs(*hw->lan_regs, num_regions); - if (!hw->lan_regs) - return -ENOMEM; - for (int i = 0; i < num_regions; i++) { - hw->lan_regs[i].addr_len = - le64_to_cpu(rcvd_regions->mem_reg[i].size); - hw->lan_regs[i].addr_start = - le64_to_cpu(rcvd_regions->mem_reg[i].start_offset); + struct libie_mmio_info *mmio = &adapter->ctlq_ctx.mmio_info; + resource_size_t offset, len; + + offset = le64_to_cpu(rcvd_regions->mem_reg[i].start_offset); + len = le64_to_cpu(rcvd_regions->mem_reg[i].size); + if (len && !libie_pci_map_mmio_region(mmio, offset, len)) { + idpf_decfg_lan_memory_regions(adapter); + return -EIO; + } } - hw->num_lan_regs = num_regions; return err; } /** - * idpf_calc_remaining_mmio_regs - calculate MMIO regions outside mbx and rstat + * idpf_map_remaining_mmio_regs - map MMIO regions outside mbx and rstat * @adapter: Driver specific private structure * - * Called when idpf_send_get_lan_memory_regions is not supported. This will + * Called when idpf_cfg_lan_memory_regions is not supported. This will * calculate the offsets and sizes for the regions before, in between, and - * after the mailbox and rstat MMIO mappings. + * after the mailbox and rstat MMIO mappings, and map those ranges. * * Return: 0 on success or error code on failure. */ -static int idpf_calc_remaining_mmio_regs(struct idpf_adapter *adapter) +static int idpf_map_remaining_mmio_regs(struct idpf_adapter *adapter) { struct resource *rstat_reg = &adapter->dev_ops.static_reg_info[1]; struct resource *mbx_reg = &adapter->dev_ops.static_reg_info[0]; - struct idpf_hw *hw = &adapter->hw; - - hw->num_lan_regs = IDPF_MMIO_MAP_FALLBACK_MAX_REMAINING; - hw->lan_regs = kzalloc_objs(*hw->lan_regs, hw->num_lan_regs); - if (!hw->lan_regs) - return -ENOMEM; + struct libie_mmio_info *mmio = &adapter->ctlq_ctx.mmio_info; + resource_size_t reg_start, size; + bool ok = true; /* Region preceding mailbox */ - hw->lan_regs[0].addr_start = 0; - hw->lan_regs[0].addr_len = mbx_reg->start; + size = mbx_reg->start; + ok &= !size || libie_pci_map_mmio_region(mmio, 0, size); + /* Region between mailbox and rstat */ - hw->lan_regs[1].addr_start = mbx_reg->end + 1; - hw->lan_regs[1].addr_len = rstat_reg->start - - hw->lan_regs[1].addr_start; + reg_start = mbx_reg->end + 1; + size = rstat_reg->start - reg_start; + ok &= !size || libie_pci_map_mmio_region(mmio, reg_start, size); + /* Region after rstat */ - hw->lan_regs[2].addr_start = rstat_reg->end + 1; - hw->lan_regs[2].addr_len = pci_resource_len(adapter->pdev, 0) - - hw->lan_regs[2].addr_start; + reg_start = rstat_reg->end + 1; + size = pci_resource_len(adapter->pdev, 0) - reg_start; + ok &= !size || libie_pci_map_mmio_region(mmio, reg_start, size); - return 0; -} - -/** - * idpf_map_lan_mmio_regs - map remaining LAN BAR regions - * @adapter: Driver specific private structure - * - * Return: 0 on success or error code on failure. - */ -static int idpf_map_lan_mmio_regs(struct idpf_adapter *adapter) -{ - struct pci_dev *pdev = adapter->pdev; - struct idpf_hw *hw = &adapter->hw; - resource_size_t res_start; - - res_start = pci_resource_start(pdev, 0); - - for (int i = 0; i < hw->num_lan_regs; i++) { - resource_size_t start; - long len; - - len = hw->lan_regs[i].addr_len; - if (!len) - continue; - start = hw->lan_regs[i].addr_start + res_start; - - hw->lan_regs[i].vaddr = devm_ioremap(&pdev->dev, start, len); - if (!hw->lan_regs[i].vaddr) { - pci_err(pdev, "failed to allocate BAR0 region\n"); - return -ENOMEM; - } + if (!ok) { + idpf_decfg_lan_memory_regions(adapter); + return -ENOMEM; } return 0; @@ -1414,7 +1418,7 @@ static int __idpf_queue_reg_init(struct idpf_vport *vport, struct idpf_q_vec_rsrc *rsrc, u32 *reg_vals, int num_regs, u32 q_type) { - struct idpf_adapter *adapter = vport->adapter; + struct libie_mmio_info *mmio = &vport->adapter->ctlq_ctx.mmio_info; int i, j, k = 0; switch (q_type) { @@ -1424,7 +1428,8 @@ static int __idpf_queue_reg_init(struct idpf_vport *vport, for (j = 0; j < tx_qgrp->num_txq && k < num_regs; j++, k++) tx_qgrp->txqs[j]->tail = - idpf_get_reg_addr(adapter, reg_vals[k]); + libie_pci_get_mmio_addr(mmio, + reg_vals[k]); } break; case VIRTCHNL2_QUEUE_TYPE_RX: @@ -1436,8 +1441,8 @@ static int __idpf_queue_reg_init(struct idpf_vport *vport, struct idpf_rx_queue *q; q = rx_qgrp->singleq.rxqs[j]; - q->tail = idpf_get_reg_addr(adapter, - reg_vals[k]); + q->tail = libie_pci_get_mmio_addr(mmio, + reg_vals[k]); } } break; @@ -1450,8 +1455,8 @@ static int __idpf_queue_reg_init(struct idpf_vport *vport, struct idpf_buf_queue *q; q = &rx_qgrp->splitq.bufq_sets[j].bufq; - q->tail = idpf_get_reg_addr(adapter, - reg_vals[k]); + q->tail = libie_pci_get_mmio_addr(mmio, + reg_vals[k]); } } break; @@ -3446,34 +3451,29 @@ int idpf_vc_core_init(struct idpf_adapter *adapter) } if (idpf_is_cap_ena(adapter, IDPF_OTHER_CAPS, VIRTCHNL2_CAP_LAN_MEMORY_REGIONS)) { - err = idpf_send_get_lan_memory_regions(adapter); + err = idpf_cfg_lan_memory_regions(adapter); if (err) { - dev_err(&adapter->pdev->dev, "Failed to get LAN memory regions: %d\n", + dev_err(&adapter->pdev->dev, "Failed to configure LAN memory regions: %d\n", err); return -EINVAL; } } else { /* Fallback to mapping the remaining regions of the entire BAR */ - err = idpf_calc_remaining_mmio_regs(adapter); + err = idpf_map_remaining_mmio_regs(adapter); if (err) { - dev_err(&adapter->pdev->dev, "Failed to allocate BAR0 region(s): %d\n", + dev_err(&adapter->pdev->dev, "Failed to configure BAR0 region(s): %d\n", err); - return -ENOMEM; + return err; } } - err = idpf_map_lan_mmio_regs(adapter); - if (err) { - dev_err(&adapter->pdev->dev, "Failed to map BAR0 region(s): %d\n", - err); - return -ENOMEM; - } - pci_sriov_set_totalvfs(adapter->pdev, idpf_get_max_vfs(adapter)); num_max_vports = idpf_get_max_vports(adapter); adapter->vports = kzalloc_objs(*adapter->vports, num_max_vports); - if (!adapter->vports) - return -ENOMEM; + if (!adapter->vports) { + err = -ENOMEM; + goto decfg_regions; + } if (!adapter->netdevs) { adapter->netdevs = kzalloc_objs(struct net_device *, @@ -3545,6 +3545,8 @@ int idpf_vc_core_init(struct idpf_adapter *adapter) err_netdev_alloc: kfree(adapter->vports); adapter->vports = NULL; +decfg_regions: + idpf_decfg_lan_memory_regions(adapter); return err; init_failed: @@ -3578,7 +3580,6 @@ int idpf_vc_core_init(struct idpf_adapter *adapter) */ void idpf_vc_core_deinit(struct idpf_adapter *adapter) { - struct idpf_hw *hw = &adapter->hw; bool remove_in_prog; if (!test_bit(IDPF_VC_CORE_INIT, adapter->flags)) @@ -3603,12 +3604,10 @@ void idpf_vc_core_deinit(struct idpf_adapter *adapter) idpf_vport_params_buf_rel(adapter); - kfree(hw->lan_regs); - hw->lan_regs = NULL; - kfree(adapter->vports); adapter->vports = NULL; + idpf_decfg_lan_memory_regions(adapter); clear_bit(IDPF_VC_CORE_INIT, adapter->flags); } diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h index e0c319bd9f38..5c634cbf1e07 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h @@ -127,6 +127,8 @@ unsigned int idpf_fsteer_max_rules(struct idpf_vport *vport); int idpf_recv_mb_msg(struct idpf_adapter *adapter, struct idpf_ctlq_info *arq); int idpf_send_mb_msg(struct idpf_adapter *adapter, struct idpf_ctlq_info *asq, u32 op, u16 msg_size, u8 *msg, u16 cookie); +bool idpf_mmio_region_non_static(struct libie_mmio_info *mmio_info, + struct libie_pci_mmio_region *reg); struct idpf_queue_ptr { enum virtchnl2_queue_type type; diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl_ptp.c b/drivers/net/ethernet/intel/idpf/idpf_virtchnl_ptp.c index d9bcc3f61c65..8d8fb498e092 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl_ptp.c +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl_ptp.c @@ -31,6 +31,7 @@ int idpf_ptp_get_caps(struct idpf_adapter *adapter) .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, }; struct virtchnl2_ptp_cross_time_reg_offsets cross_tstamp_offsets; + struct libie_mmio_info *mmio = &adapter->ctlq_ctx.mmio_info; struct virtchnl2_ptp_clk_adj_reg_offsets clk_adj_offsets; struct virtchnl2_ptp_clk_reg_offsets clock_offsets; struct idpf_ptp_secondary_mbx *scnd_mbx; @@ -76,19 +77,20 @@ int idpf_ptp_get_caps(struct idpf_adapter *adapter) clock_offsets = recv_ptp_caps_msg->clk_offsets; temp_offset = le32_to_cpu(clock_offsets.dev_clk_ns_l); - ptp->dev_clk_regs.dev_clk_ns_l = idpf_get_reg_addr(adapter, - temp_offset); + ptp->dev_clk_regs.dev_clk_ns_l = + libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clock_offsets.dev_clk_ns_h); - ptp->dev_clk_regs.dev_clk_ns_h = idpf_get_reg_addr(adapter, - temp_offset); + ptp->dev_clk_regs.dev_clk_ns_h = + libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clock_offsets.phy_clk_ns_l); - ptp->dev_clk_regs.phy_clk_ns_l = idpf_get_reg_addr(adapter, - temp_offset); + ptp->dev_clk_regs.phy_clk_ns_l = + libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clock_offsets.phy_clk_ns_h); - ptp->dev_clk_regs.phy_clk_ns_h = idpf_get_reg_addr(adapter, - temp_offset); + ptp->dev_clk_regs.phy_clk_ns_h = + libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clock_offsets.cmd_sync_trigger); - ptp->dev_clk_regs.cmd_sync = idpf_get_reg_addr(adapter, temp_offset); + ptp->dev_clk_regs.cmd_sync = + libie_pci_get_mmio_addr(mmio, temp_offset); cross_tstamp: access_type = ptp->get_cross_tstamp_access; @@ -98,13 +100,14 @@ int idpf_ptp_get_caps(struct idpf_adapter *adapter) cross_tstamp_offsets = recv_ptp_caps_msg->cross_time_offsets; temp_offset = le32_to_cpu(cross_tstamp_offsets.sys_time_ns_l); - ptp->dev_clk_regs.sys_time_ns_l = idpf_get_reg_addr(adapter, - temp_offset); + ptp->dev_clk_regs.sys_time_ns_l = + libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(cross_tstamp_offsets.sys_time_ns_h); - ptp->dev_clk_regs.sys_time_ns_h = idpf_get_reg_addr(adapter, - temp_offset); + ptp->dev_clk_regs.sys_time_ns_h = + libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(cross_tstamp_offsets.cmd_sync_trigger); - ptp->dev_clk_regs.cmd_sync = idpf_get_reg_addr(adapter, temp_offset); + ptp->dev_clk_regs.cmd_sync = + libie_pci_get_mmio_addr(mmio, temp_offset); discipline_clock: access_type = ptp->adj_dev_clk_time_access; @@ -115,29 +118,32 @@ int idpf_ptp_get_caps(struct idpf_adapter *adapter) /* Device clock offsets */ temp_offset = le32_to_cpu(clk_adj_offsets.dev_clk_cmd_type); - ptp->dev_clk_regs.cmd = idpf_get_reg_addr(adapter, temp_offset); + ptp->dev_clk_regs.cmd = libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clk_adj_offsets.dev_clk_incval_l); - ptp->dev_clk_regs.incval_l = idpf_get_reg_addr(adapter, temp_offset); + ptp->dev_clk_regs.incval_l = libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clk_adj_offsets.dev_clk_incval_h); - ptp->dev_clk_regs.incval_h = idpf_get_reg_addr(adapter, temp_offset); + ptp->dev_clk_regs.incval_h = libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clk_adj_offsets.dev_clk_shadj_l); - ptp->dev_clk_regs.shadj_l = idpf_get_reg_addr(adapter, temp_offset); + ptp->dev_clk_regs.shadj_l = libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clk_adj_offsets.dev_clk_shadj_h); - ptp->dev_clk_regs.shadj_h = idpf_get_reg_addr(adapter, temp_offset); + ptp->dev_clk_regs.shadj_h = libie_pci_get_mmio_addr(mmio, temp_offset); /* PHY clock offsets */ temp_offset = le32_to_cpu(clk_adj_offsets.phy_clk_cmd_type); - ptp->dev_clk_regs.phy_cmd = idpf_get_reg_addr(adapter, temp_offset); + ptp->dev_clk_regs.phy_cmd = + libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clk_adj_offsets.phy_clk_incval_l); - ptp->dev_clk_regs.phy_incval_l = idpf_get_reg_addr(adapter, - temp_offset); + ptp->dev_clk_regs.phy_incval_l = + libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clk_adj_offsets.phy_clk_incval_h); - ptp->dev_clk_regs.phy_incval_h = idpf_get_reg_addr(adapter, - temp_offset); + ptp->dev_clk_regs.phy_incval_h = + libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clk_adj_offsets.phy_clk_shadj_l); - ptp->dev_clk_regs.phy_shadj_l = idpf_get_reg_addr(adapter, temp_offset); + ptp->dev_clk_regs.phy_shadj_l = + libie_pci_get_mmio_addr(mmio, temp_offset); temp_offset = le32_to_cpu(clk_adj_offsets.phy_clk_shadj_h); - ptp->dev_clk_regs.phy_shadj_h = idpf_get_reg_addr(adapter, temp_offset); + ptp->dev_clk_regs.phy_shadj_h = + libie_pci_get_mmio_addr(mmio, temp_offset); return 0; } From deaada4f18072181faf2bc1665fed0db9569b923 Mon Sep 17 00:00:00 2001 From: Pavan Kumar Linga Date: Thu, 25 Jun 2026 18:02:03 +0200 Subject: [PATCH 1254/1433] idpf: refactor idpf to use libie control queues Support to initialize and configure controlqs, and manage their transactions was introduced in libie. As part of it, most of the existing controlq structures are renamed and modified. Use those APIs in idpf and make all the necessary changes. Previously for the send and receive virtchnl messages, there used to be a memcpy involved in controlq code to copy the buffer info passed by the send function into the controlq specific buffers. There was no restriction to use automatic memory in that case. The new implementation in libie removed copying of the send buffer info and introduced DMA mapping of the send buffer itself. To accommodate it, use dynamic memory for the larger send buffers. For smaller ones (<= 128 bytes) libie still can copy them into the pre-allocated message memory. Those changes result in a pretty big diff, but the changes are fairly trivial and localized. In case of receive, idpf receives a page pool buffer allocated by the libie and care should be taken to release it after use in the idpf. idpf_idc_rdma_vc_send_sync() no longer truncates oversized responses or zeroes *recv_len on error, but this was confirmed to have no practical impact for any existing callers. This refactoring introduces roughly additional 40KB of module storage used for systems that only run idpf, so idpf + libie_cp + libie_pci takes about 7% more storage than just idpf before refactoring. We now pre-allocate small TX buffers, so that does increase the memory usage, but reduces the need to allocate. This results in additional 256 * 128B of memory permanently used, increasing the worst-case memory usage by 32KB but our ctlq RX buffers need to be of size 4096B anyway (not changed by the patchset), so this is hardly noticeable. As for the timings, the fact that we are mostly limited by the HW response time which is far from instant, is not changed by this refactor. Reviewed-by: Aleksandr Loktionov Signed-off-by: Pavan Kumar Linga Tested-by: Samuel Salin Co-developed-by: Larysa Zaremba Signed-off-by: Larysa Zaremba Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/idpf/Makefile | 2 - drivers/net/ethernet/intel/idpf/idpf.h | 28 +- .../net/ethernet/intel/idpf/idpf_controlq.c | 631 ------- .../net/ethernet/intel/idpf/idpf_controlq.h | 142 -- .../ethernet/intel/idpf/idpf_controlq_api.h | 177 -- .../ethernet/intel/idpf/idpf_controlq_setup.c | 169 -- drivers/net/ethernet/intel/idpf/idpf_dev.c | 56 +- .../net/ethernet/intel/idpf/idpf_ethtool.c | 28 +- drivers/net/ethernet/intel/idpf/idpf_lib.c | 51 +- drivers/net/ethernet/intel/idpf/idpf_main.c | 3 - drivers/net/ethernet/intel/idpf/idpf_mem.h | 20 - drivers/net/ethernet/intel/idpf/idpf_txrx.h | 2 +- drivers/net/ethernet/intel/idpf/idpf_vf_dev.c | 59 +- .../net/ethernet/intel/idpf/idpf_virtchnl.c | 1625 +++++++---------- .../net/ethernet/intel/idpf/idpf_virtchnl.h | 113 +- .../ethernet/intel/idpf/idpf_virtchnl_ptp.c | 252 ++- 16 files changed, 859 insertions(+), 2499 deletions(-) delete mode 100644 drivers/net/ethernet/intel/idpf/idpf_controlq.c delete mode 100644 drivers/net/ethernet/intel/idpf/idpf_controlq.h delete mode 100644 drivers/net/ethernet/intel/idpf/idpf_controlq_api.h delete mode 100644 drivers/net/ethernet/intel/idpf/idpf_controlq_setup.c delete mode 100644 drivers/net/ethernet/intel/idpf/idpf_mem.h diff --git a/drivers/net/ethernet/intel/idpf/Makefile b/drivers/net/ethernet/intel/idpf/Makefile index 651ddee942bd..4aaafa175ec3 100644 --- a/drivers/net/ethernet/intel/idpf/Makefile +++ b/drivers/net/ethernet/intel/idpf/Makefile @@ -6,8 +6,6 @@ obj-$(CONFIG_IDPF) += idpf.o idpf-y := \ - idpf_controlq.o \ - idpf_controlq_setup.o \ idpf_dev.o \ idpf_ethtool.o \ idpf_idc.o \ diff --git a/drivers/net/ethernet/intel/idpf/idpf.h b/drivers/net/ethernet/intel/idpf/idpf.h index 92a120aadfcd..d7d751e2a781 100644 --- a/drivers/net/ethernet/intel/idpf/idpf.h +++ b/drivers/net/ethernet/intel/idpf/idpf.h @@ -27,7 +27,6 @@ struct idpf_rss_data; #include #include "idpf_txrx.h" -#include "idpf_controlq.h" #define GETMAXVAL(num_bits) GENMASK((num_bits) - 1, 0) @@ -37,11 +36,10 @@ struct idpf_rss_data; #define IDPF_NUM_FILTERS_PER_MSG 20 #define IDPF_NUM_DFLT_MBX_Q 2 /* includes both TX and RX */ #define IDPF_DFLT_MBX_Q_LEN 64 -#define IDPF_DFLT_MBX_ID -1 /* maximum number of times to try before resetting mailbox */ #define IDPF_MB_MAX_ERR 20 #define IDPF_NUM_CHUNKS_PER_MSG(struct_sz, chunk_sz) \ - ((IDPF_CTLQ_MAX_BUF_LEN - (struct_sz)) / (chunk_sz)) + ((LIBIE_CTLQ_MAX_BUF_LEN - (struct_sz)) / (chunk_sz)) #define IDPF_WAIT_FOR_MARKER_TIMEO 500 #define IDPF_MAX_WAIT 500 @@ -202,8 +200,8 @@ struct idpf_vport_max_q { * @ptp_reg_init: PTP register initialization */ struct idpf_reg_ops { - void (*ctlq_reg_init)(struct idpf_adapter *adapter, - struct idpf_ctlq_create_info *cq); + void (*ctlq_reg_init)(struct libie_mmio_info *mmio, + struct libie_ctlq_create_info *cctlq_info); int (*intr_reg_init)(struct idpf_vport *vport, struct idpf_q_vec_rsrc *rsrc); void (*mb_intr_reg_init)(struct idpf_adapter *adapter); @@ -606,8 +604,6 @@ struct idpf_vport_config { DECLARE_BITMAP(flags, IDPF_VPORT_CONFIG_FLAGS_NBITS); }; -struct idpf_vc_xn_manager; - #define idpf_for_each_vport(adapter, iter) \ for (struct idpf_vport **__##iter = &(adapter)->vports[0], \ *iter = (adapter)->max_vports ? *__##iter : NULL; \ @@ -625,8 +621,10 @@ struct idpf_vc_xn_manager; * @state: Init state machine * @flags: See enum idpf_flags * @reset_reg: See struct idpf_reset_reg - * @hw: Device access data * @ctlq_ctx: controlq context + * @asq: Send control queue info + * @arq: Receive control queue info + * @xnm: Xn transaction manager * @num_avail_msix: Available number of MSIX vectors * @num_msix_entries: Number of entries in MSIX table * @msix_entries: MSIX table @@ -659,7 +657,6 @@ struct idpf_vc_xn_manager; * @stats_task: Periodic statistics retrieval task * @stats_wq: Workqueue for statistics task * @caps: Negotiated capabilities with device - * @vcxn_mngr: Virtchnl transaction manager * @dev_ops: See idpf_dev_ops * @cdev_info: IDC core device info pointer * @num_vfs: Number of allocated VFs through sysfs. PF does not directly talk @@ -683,8 +680,10 @@ struct idpf_adapter { enum idpf_state state; DECLARE_BITMAP(flags, IDPF_FLAGS_NBITS); struct idpf_reset_reg reset_reg; - struct idpf_hw hw; struct libie_ctlq_ctx ctlq_ctx; + struct libie_ctlq_info *asq; + struct libie_ctlq_info *arq; + struct libie_ctlq_xn_manager *xnm; u16 num_avail_msix; u16 num_msix_entries; struct msix_entry *msix_entries; @@ -721,7 +720,6 @@ struct idpf_adapter { struct delayed_work stats_task; struct workqueue_struct *stats_wq; struct virtchnl2_get_capabilities caps; - struct idpf_vc_xn_manager *vcxn_mngr; struct idpf_dev_ops dev_ops; struct iidc_rdma_core_dev_info *cdev_info; @@ -881,12 +879,12 @@ static inline u8 idpf_get_min_tx_pkt_len(struct idpf_adapter *adapter) */ static inline bool idpf_is_reset_detected(struct idpf_adapter *adapter) { - if (!adapter->hw.arq) + struct libie_ctlq_info *arq = adapter->arq; + + if (!arq) return true; - return !(readl(libie_pci_get_mmio_addr(&adapter->ctlq_ctx.mmio_info, - adapter->hw.arq->reg.len)) & - adapter->hw.arq->reg.len_mask); + return !(readl(arq->reg.len) & arq->reg.len_mask); } /** diff --git a/drivers/net/ethernet/intel/idpf/idpf_controlq.c b/drivers/net/ethernet/intel/idpf/idpf_controlq.c deleted file mode 100644 index 020b08367e18..000000000000 --- a/drivers/net/ethernet/intel/idpf/idpf_controlq.c +++ /dev/null @@ -1,631 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* Copyright (C) 2023 Intel Corporation */ - -#include "idpf.h" - -/** - * idpf_ctlq_setup_regs - initialize control queue registers - * @cq: pointer to the specific control queue - * @q_create_info: structs containing info for each queue to be initialized - */ -static void idpf_ctlq_setup_regs(struct idpf_ctlq_info *cq, - struct idpf_ctlq_create_info *q_create_info) -{ - /* set control queue registers in our local struct */ - cq->reg.head = q_create_info->reg.head; - cq->reg.tail = q_create_info->reg.tail; - cq->reg.len = q_create_info->reg.len; - cq->reg.bah = q_create_info->reg.bah; - cq->reg.bal = q_create_info->reg.bal; - cq->reg.len_mask = q_create_info->reg.len_mask; - cq->reg.len_ena_mask = q_create_info->reg.len_ena_mask; - cq->reg.head_mask = q_create_info->reg.head_mask; -} - -/** - * idpf_ctlq_init_regs - Initialize control queue registers - * @hw: pointer to hw struct - * @cq: pointer to the specific Control queue - * @is_rxq: true if receive control queue, false otherwise - * - * Initialize registers. The caller is expected to have already initialized the - * descriptor ring memory and buffer memory - */ -static void idpf_ctlq_init_regs(struct idpf_hw *hw, struct idpf_ctlq_info *cq, - bool is_rxq) -{ - struct libie_mmio_info *mmio = &hw->back->ctlq_ctx.mmio_info; - - /* Update tail to post pre-allocated buffers for rx queues */ - if (is_rxq) - writel((u32)(cq->ring_size - 1), - libie_pci_get_mmio_addr(mmio, cq->reg.tail)); - - /* For non-Mailbox control queues only TAIL need to be set */ - if (cq->q_id != -1) - return; - - /* Clear Head for both send or receive */ - writel(0, libie_pci_get_mmio_addr(mmio, cq->reg.head)); - - /* set starting point */ - writel(lower_32_bits(cq->desc_ring.pa), - libie_pci_get_mmio_addr(mmio, cq->reg.bal)); - writel(upper_32_bits(cq->desc_ring.pa), - libie_pci_get_mmio_addr(mmio, cq->reg.bah)); - writel((cq->ring_size | cq->reg.len_ena_mask), - libie_pci_get_mmio_addr(mmio, cq->reg.len)); -} - -/** - * idpf_ctlq_init_rxq_bufs - populate receive queue descriptors with buf - * @cq: pointer to the specific Control queue - * - * Record the address of the receive queue DMA buffers in the descriptors. - * The buffers must have been previously allocated. - */ -static void idpf_ctlq_init_rxq_bufs(struct idpf_ctlq_info *cq) -{ - int i; - - for (i = 0; i < cq->ring_size; i++) { - struct idpf_ctlq_desc *desc = IDPF_CTLQ_DESC(cq, i); - struct idpf_dma_mem *bi = cq->bi.rx_buff[i]; - - /* No buffer to post to descriptor, continue */ - if (!bi) - continue; - - desc->flags = - cpu_to_le16(IDPF_CTLQ_FLAG_BUF | IDPF_CTLQ_FLAG_RD); - desc->opcode = 0; - desc->datalen = cpu_to_le16(bi->size); - desc->ret_val = 0; - desc->v_opcode_dtype = 0; - desc->v_retval = 0; - desc->params.indirect.addr_high = - cpu_to_le32(upper_32_bits(bi->pa)); - desc->params.indirect.addr_low = - cpu_to_le32(lower_32_bits(bi->pa)); - desc->params.indirect.param0 = 0; - desc->params.indirect.sw_cookie = 0; - desc->params.indirect.v_flags = 0; - } -} - -/** - * idpf_ctlq_shutdown - shutdown the CQ - * @hw: pointer to hw struct - * @cq: pointer to the specific Control queue - * - * The main shutdown routine for any controq queue - */ -static void idpf_ctlq_shutdown(struct idpf_hw *hw, struct idpf_ctlq_info *cq) -{ - spin_lock(&cq->cq_lock); - - /* free ring buffers and the ring itself */ - idpf_ctlq_dealloc_ring_res(hw, cq); - - /* Set ring_size to 0 to indicate uninitialized queue */ - cq->ring_size = 0; - - spin_unlock(&cq->cq_lock); -} - -/** - * idpf_ctlq_add - add one control queue - * @hw: pointer to hardware struct - * @qinfo: info for queue to be created - * @cq_out: (output) double pointer to control queue to be created - * - * Allocate and initialize a control queue and add it to the control queue list. - * The cq parameter will be allocated/initialized and passed back to the caller - * if no errors occur. - * - * Note: idpf_ctlq_init must be called prior to any calls to idpf_ctlq_add - */ -int idpf_ctlq_add(struct idpf_hw *hw, - struct idpf_ctlq_create_info *qinfo, - struct idpf_ctlq_info **cq_out) -{ - struct idpf_ctlq_info *cq; - bool is_rxq = false; - int err; - - cq = kzalloc_obj(*cq); - if (!cq) - return -ENOMEM; - - cq->cq_type = qinfo->type; - cq->q_id = qinfo->id; - cq->buf_size = qinfo->buf_size; - cq->ring_size = qinfo->len; - - cq->next_to_use = 0; - cq->next_to_clean = 0; - cq->next_to_post = cq->ring_size - 1; - - switch (qinfo->type) { - case IDPF_CTLQ_TYPE_MAILBOX_RX: - is_rxq = true; - fallthrough; - case IDPF_CTLQ_TYPE_MAILBOX_TX: - err = idpf_ctlq_alloc_ring_res(hw, cq); - break; - default: - err = -EBADR; - break; - } - - if (err) - goto init_free_q; - - if (is_rxq) { - idpf_ctlq_init_rxq_bufs(cq); - } else { - /* Allocate the array of msg pointers for TX queues */ - cq->bi.tx_msg = kzalloc_objs(struct idpf_ctlq_msg *, qinfo->len); - if (!cq->bi.tx_msg) { - err = -ENOMEM; - goto init_dealloc_q_mem; - } - } - - idpf_ctlq_setup_regs(cq, qinfo); - - idpf_ctlq_init_regs(hw, cq, is_rxq); - - spin_lock_init(&cq->cq_lock); - - list_add(&cq->cq_list, &hw->cq_list_head); - - *cq_out = cq; - - return 0; - -init_dealloc_q_mem: - /* free ring buffers and the ring itself */ - idpf_ctlq_dealloc_ring_res(hw, cq); -init_free_q: - kfree(cq); - - return err; -} - -/** - * idpf_ctlq_remove - deallocate and remove specified control queue - * @hw: pointer to hardware struct - * @cq: pointer to control queue to be removed - */ -void idpf_ctlq_remove(struct idpf_hw *hw, - struct idpf_ctlq_info *cq) -{ - list_del(&cq->cq_list); - idpf_ctlq_shutdown(hw, cq); - kfree(cq); -} - -/** - * idpf_ctlq_init - main initialization routine for all control queues - * @hw: pointer to hardware struct - * @num_q: number of queues to initialize - * @q_info: array of structs containing info for each queue to be initialized - * - * This initializes any number and any type of control queues. This is an all - * or nothing routine; if one fails, all previously allocated queues will be - * destroyed. This must be called prior to using the individual add/remove - * APIs. - */ -int idpf_ctlq_init(struct idpf_hw *hw, u8 num_q, - struct idpf_ctlq_create_info *q_info) -{ - struct idpf_ctlq_info *cq, *tmp; - int err; - int i; - - INIT_LIST_HEAD(&hw->cq_list_head); - - for (i = 0; i < num_q; i++) { - struct idpf_ctlq_create_info *qinfo = q_info + i; - - err = idpf_ctlq_add(hw, qinfo, &cq); - if (err) - goto init_destroy_qs; - } - - return 0; - -init_destroy_qs: - list_for_each_entry_safe(cq, tmp, &hw->cq_list_head, cq_list) - idpf_ctlq_remove(hw, cq); - - return err; -} - -/** - * idpf_ctlq_deinit - destroy all control queues - * @hw: pointer to hw struct - */ -void idpf_ctlq_deinit(struct idpf_hw *hw) -{ - struct idpf_ctlq_info *cq, *tmp; - - list_for_each_entry_safe(cq, tmp, &hw->cq_list_head, cq_list) - idpf_ctlq_remove(hw, cq); -} - -/** - * idpf_ctlq_send - send command to Control Queue (CTQ) - * @hw: pointer to hw struct - * @cq: handle to control queue struct to send on - * @num_q_msg: number of messages to send on control queue - * @q_msg: pointer to array of queue messages to be sent - * - * The caller is expected to allocate DMAable buffers and pass them to the - * send routine via the q_msg struct / control queue specific data struct. - * The control queue will hold a reference to each send message until - * the completion for that message has been cleaned. - */ -int idpf_ctlq_send(struct idpf_hw *hw, struct idpf_ctlq_info *cq, - u16 num_q_msg, struct idpf_ctlq_msg q_msg[]) -{ - struct idpf_ctlq_desc *desc; - int num_desc_avail; - int err = 0; - int i; - - spin_lock(&cq->cq_lock); - - /* Ensure there are enough descriptors to send all messages */ - num_desc_avail = IDPF_CTLQ_DESC_UNUSED(cq); - if (num_desc_avail == 0 || num_desc_avail < num_q_msg) { - err = -ENOSPC; - goto err_unlock; - } - - for (i = 0; i < num_q_msg; i++) { - struct idpf_ctlq_msg *msg = &q_msg[i]; - - desc = IDPF_CTLQ_DESC(cq, cq->next_to_use); - - desc->opcode = cpu_to_le16(msg->opcode); - desc->pfid_vfid = cpu_to_le16(msg->func_id); - - desc->v_opcode_dtype = cpu_to_le32(msg->cookie.mbx.chnl_opcode); - desc->v_retval = cpu_to_le32(msg->cookie.mbx.chnl_retval); - - desc->flags = cpu_to_le16((msg->host_id & IDPF_HOST_ID_MASK) << - IDPF_CTLQ_FLAG_HOST_ID_S); - if (msg->data_len) { - struct idpf_dma_mem *buff = msg->ctx.indirect.payload; - - desc->datalen |= cpu_to_le16(msg->data_len); - desc->flags |= cpu_to_le16(IDPF_CTLQ_FLAG_BUF); - desc->flags |= cpu_to_le16(IDPF_CTLQ_FLAG_RD); - - /* Update the address values in the desc with the pa - * value for respective buffer - */ - desc->params.indirect.addr_high = - cpu_to_le32(upper_32_bits(buff->pa)); - desc->params.indirect.addr_low = - cpu_to_le32(lower_32_bits(buff->pa)); - - memcpy(&desc->params, msg->ctx.indirect.context, - IDPF_INDIRECT_CTX_SIZE); - } else { - memcpy(&desc->params, msg->ctx.direct, - IDPF_DIRECT_CTX_SIZE); - } - - /* Store buffer info */ - cq->bi.tx_msg[cq->next_to_use] = msg; - - (cq->next_to_use)++; - if (cq->next_to_use == cq->ring_size) - cq->next_to_use = 0; - } - - /* Force memory write to complete before letting hardware - * know that there are new descriptors to fetch. - */ - dma_wmb(); - - writel(cq->next_to_use, - libie_pci_get_mmio_addr(&hw->back->ctlq_ctx.mmio_info, - cq->reg.tail)); - -err_unlock: - spin_unlock(&cq->cq_lock); - - return err; -} - -/** - * idpf_ctlq_clean_sq - reclaim send descriptors on HW write back for the - * requested queue - * @cq: pointer to the specific Control queue - * @clean_count: (input|output) number of descriptors to clean as input, and - * number of descriptors actually cleaned as output - * @msg_status: (output) pointer to msg pointer array to be populated; needs - * to be allocated by caller - * - * Returns an array of message pointers associated with the cleaned - * descriptors. The pointers are to the original ctlq_msgs sent on the cleaned - * descriptors. The status will be returned for each; any messages that failed - * to send will have a non-zero status. The caller is expected to free original - * ctlq_msgs and free or reuse the DMA buffers. - */ -int idpf_ctlq_clean_sq(struct idpf_ctlq_info *cq, u16 *clean_count, - struct idpf_ctlq_msg *msg_status[]) -{ - struct idpf_ctlq_desc *desc; - u16 i, num_to_clean; - u16 ntc, desc_err; - - if (*clean_count == 0) - return 0; - if (*clean_count > cq->ring_size) - return -EBADR; - - spin_lock(&cq->cq_lock); - - ntc = cq->next_to_clean; - - num_to_clean = *clean_count; - - for (i = 0; i < num_to_clean; i++) { - /* Fetch next descriptor and check if marked as done */ - desc = IDPF_CTLQ_DESC(cq, ntc); - if (!(le16_to_cpu(desc->flags) & IDPF_CTLQ_FLAG_DD)) - break; - - /* Ensure no other fields are read until DD flag is checked */ - dma_rmb(); - - /* strip off FW internal code */ - desc_err = le16_to_cpu(desc->ret_val) & 0xff; - - msg_status[i] = cq->bi.tx_msg[ntc]; - msg_status[i]->status = desc_err; - - cq->bi.tx_msg[ntc] = NULL; - - /* Zero out any stale data */ - memset(desc, 0, sizeof(*desc)); - - ntc++; - if (ntc == cq->ring_size) - ntc = 0; - } - - cq->next_to_clean = ntc; - - spin_unlock(&cq->cq_lock); - - /* Return number of descriptors actually cleaned */ - *clean_count = i; - - return 0; -} - -/** - * idpf_ctlq_post_rx_buffs - post buffers to descriptor ring - * @hw: pointer to hw struct - * @cq: pointer to control queue handle - * @buff_count: (input|output) input is number of buffers caller is trying to - * return; output is number of buffers that were not posted - * @buffs: array of pointers to dma mem structs to be given to hardware - * - * Caller uses this function to return DMA buffers to the descriptor ring after - * consuming them; buff_count will be the number of buffers. - * - * Note: this function needs to be called after a receive call even - * if there are no DMA buffers to be returned, i.e. buff_count = 0, - * buffs = NULL to support direct commands - */ -int idpf_ctlq_post_rx_buffs(struct idpf_hw *hw, struct idpf_ctlq_info *cq, - u16 *buff_count, struct idpf_dma_mem **buffs) -{ - struct idpf_ctlq_desc *desc; - u16 ntp = cq->next_to_post; - bool buffs_avail = false; - u16 tbp = ntp + 1; - int i = 0; - - if (*buff_count > cq->ring_size) - return -EBADR; - - if (*buff_count > 0) - buffs_avail = true; - - spin_lock(&cq->cq_lock); - - if (tbp >= cq->ring_size) - tbp = 0; - - if (tbp == cq->next_to_clean) - /* Nothing to do */ - goto post_buffs_out; - - /* Post buffers for as many as provided or up until the last one used */ - while (ntp != cq->next_to_clean) { - desc = IDPF_CTLQ_DESC(cq, ntp); - - if (cq->bi.rx_buff[ntp]) - goto fill_desc; - if (!buffs_avail) { - /* If the caller hasn't given us any buffers or - * there are none left, search the ring itself - * for an available buffer to move to this - * entry starting at the next entry in the ring - */ - tbp = ntp + 1; - - /* Wrap ring if necessary */ - if (tbp >= cq->ring_size) - tbp = 0; - - while (tbp != cq->next_to_clean) { - if (cq->bi.rx_buff[tbp]) { - cq->bi.rx_buff[ntp] = - cq->bi.rx_buff[tbp]; - cq->bi.rx_buff[tbp] = NULL; - - /* Found a buffer, no need to - * search anymore - */ - break; - } - - /* Wrap ring if necessary */ - tbp++; - if (tbp >= cq->ring_size) - tbp = 0; - } - - if (tbp == cq->next_to_clean) - goto post_buffs_out; - } else { - /* Give back pointer to DMA buffer */ - cq->bi.rx_buff[ntp] = buffs[i]; - i++; - - if (i >= *buff_count) - buffs_avail = false; - } - -fill_desc: - desc->flags = - cpu_to_le16(IDPF_CTLQ_FLAG_BUF | IDPF_CTLQ_FLAG_RD); - - /* Post buffers to descriptor */ - desc->datalen = cpu_to_le16(cq->bi.rx_buff[ntp]->size); - desc->params.indirect.addr_high = - cpu_to_le32(upper_32_bits(cq->bi.rx_buff[ntp]->pa)); - desc->params.indirect.addr_low = - cpu_to_le32(lower_32_bits(cq->bi.rx_buff[ntp]->pa)); - - ntp++; - if (ntp == cq->ring_size) - ntp = 0; - } - -post_buffs_out: - /* Only update tail if buffers were actually posted */ - if (cq->next_to_post != ntp) { - if (ntp) - /* Update next_to_post to ntp - 1 since current ntp - * will not have a buffer - */ - cq->next_to_post = ntp - 1; - else - /* Wrap to end of end ring since current ntp is 0 */ - cq->next_to_post = cq->ring_size - 1; - - dma_wmb(); - - writel(cq->next_to_post, - libie_pci_get_mmio_addr(&hw->back->ctlq_ctx.mmio_info, - cq->reg.tail)); - } - - spin_unlock(&cq->cq_lock); - - /* return the number of buffers that were not posted */ - *buff_count = *buff_count - i; - - return 0; -} - -/** - * idpf_ctlq_recv - receive control queue message call back - * @cq: pointer to control queue handle to receive on - * @num_q_msg: (input|output) input number of messages that should be received; - * output number of messages actually received - * @q_msg: (output) array of received control queue messages on this q; - * needs to be pre-allocated by caller for as many messages as requested - * - * Called by interrupt handler or polling mechanism. Caller is expected - * to free buffers - */ -int idpf_ctlq_recv(struct idpf_ctlq_info *cq, u16 *num_q_msg, - struct idpf_ctlq_msg *q_msg) -{ - u16 num_to_clean, ntc, flags; - struct idpf_ctlq_desc *desc; - int err = 0; - u16 i; - - /* take the lock before we start messing with the ring */ - spin_lock(&cq->cq_lock); - - ntc = cq->next_to_clean; - - num_to_clean = *num_q_msg; - - for (i = 0; i < num_to_clean; i++) { - /* Fetch next descriptor and check if marked as done */ - desc = IDPF_CTLQ_DESC(cq, ntc); - flags = le16_to_cpu(desc->flags); - - if (!(flags & IDPF_CTLQ_FLAG_DD)) - break; - - /* Ensure no other fields are read until DD flag is checked */ - dma_rmb(); - - q_msg[i].vmvf_type = (flags & - (IDPF_CTLQ_FLAG_FTYPE_VM | - IDPF_CTLQ_FLAG_FTYPE_PF)) >> - IDPF_CTLQ_FLAG_FTYPE_S; - - if (flags & IDPF_CTLQ_FLAG_ERR) - err = -EBADMSG; - - q_msg[i].cookie.mbx.chnl_opcode = - le32_to_cpu(desc->v_opcode_dtype); - q_msg[i].cookie.mbx.chnl_retval = - le32_to_cpu(desc->v_retval); - - q_msg[i].opcode = le16_to_cpu(desc->opcode); - q_msg[i].data_len = le16_to_cpu(desc->datalen); - q_msg[i].status = le16_to_cpu(desc->ret_val); - - if (desc->datalen) { - memcpy(q_msg[i].ctx.indirect.context, - &desc->params.indirect, IDPF_INDIRECT_CTX_SIZE); - - /* Assign pointer to dma buffer to ctlq_msg array - * to be given to upper layer - */ - q_msg[i].ctx.indirect.payload = cq->bi.rx_buff[ntc]; - - /* Zero out pointer to DMA buffer info; - * will be repopulated by post buffers API - */ - cq->bi.rx_buff[ntc] = NULL; - } else { - memcpy(q_msg[i].ctx.direct, desc->params.raw, - IDPF_DIRECT_CTX_SIZE); - } - - /* Zero out stale data in descriptor */ - memset(desc, 0, sizeof(struct idpf_ctlq_desc)); - - ntc++; - if (ntc == cq->ring_size) - ntc = 0; - } - - cq->next_to_clean = ntc; - - spin_unlock(&cq->cq_lock); - - *num_q_msg = i; - if (*num_q_msg == 0) - err = -ENOMSG; - - return err; -} diff --git a/drivers/net/ethernet/intel/idpf/idpf_controlq.h b/drivers/net/ethernet/intel/idpf/idpf_controlq.h deleted file mode 100644 index acf595e9265f..000000000000 --- a/drivers/net/ethernet/intel/idpf/idpf_controlq.h +++ /dev/null @@ -1,142 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* Copyright (C) 2023 Intel Corporation */ - -#ifndef _IDPF_CONTROLQ_H_ -#define _IDPF_CONTROLQ_H_ - -#include - -#include "idpf_controlq_api.h" - -/* Maximum buffer length for all control queue types */ -#define IDPF_CTLQ_MAX_BUF_LEN 4096 - -#define IDPF_CTLQ_DESC(R, i) \ - (&(((struct idpf_ctlq_desc *)((R)->desc_ring.va))[i])) - -#define IDPF_CTLQ_DESC_UNUSED(R) \ - ((u16)((((R)->next_to_clean > (R)->next_to_use) ? 0 : (R)->ring_size) + \ - (R)->next_to_clean - (R)->next_to_use - 1)) - -/* Control Queue default settings */ -#define IDPF_CTRL_SQ_CMD_TIMEOUT 250 /* msecs */ - -struct idpf_ctlq_desc { - /* Control queue descriptor flags */ - __le16 flags; - /* Control queue message opcode */ - __le16 opcode; - __le16 datalen; /* 0 for direct commands */ - union { - __le16 ret_val; - __le16 pfid_vfid; -#define IDPF_CTLQ_DESC_VF_ID_S 0 -#define IDPF_CTLQ_DESC_VF_ID_M (0x7FF << IDPF_CTLQ_DESC_VF_ID_S) -#define IDPF_CTLQ_DESC_PF_ID_S 11 -#define IDPF_CTLQ_DESC_PF_ID_M (0x1F << IDPF_CTLQ_DESC_PF_ID_S) - }; - - /* Virtchnl message opcode and virtchnl descriptor type - * v_opcode=[27:0], v_dtype=[31:28] - */ - __le32 v_opcode_dtype; - /* Virtchnl return value */ - __le32 v_retval; - union { - struct { - __le32 param0; - __le32 param1; - __le32 param2; - __le32 param3; - } direct; - struct { - __le32 param0; - __le16 sw_cookie; - /* Virtchnl flags */ - __le16 v_flags; - __le32 addr_high; - __le32 addr_low; - } indirect; - u8 raw[16]; - } params; -}; - -/* Flags sub-structure - * |0 |1 |2 |3 |4 |5 |6 |7 |8 |9 |10 |11 |12 |13 |14 |15 | - * |DD |CMP|ERR| * RSV * |FTYPE | *RSV* |RD |VFC|BUF| HOST_ID | - */ -/* command flags and offsets */ -#define IDPF_CTLQ_FLAG_DD_S 0 -#define IDPF_CTLQ_FLAG_CMP_S 1 -#define IDPF_CTLQ_FLAG_ERR_S 2 -#define IDPF_CTLQ_FLAG_FTYPE_S 6 -#define IDPF_CTLQ_FLAG_RD_S 10 -#define IDPF_CTLQ_FLAG_VFC_S 11 -#define IDPF_CTLQ_FLAG_BUF_S 12 -#define IDPF_CTLQ_FLAG_HOST_ID_S 13 - -#define IDPF_CTLQ_FLAG_DD BIT(IDPF_CTLQ_FLAG_DD_S) /* 0x1 */ -#define IDPF_CTLQ_FLAG_CMP BIT(IDPF_CTLQ_FLAG_CMP_S) /* 0x2 */ -#define IDPF_CTLQ_FLAG_ERR BIT(IDPF_CTLQ_FLAG_ERR_S) /* 0x4 */ -#define IDPF_CTLQ_FLAG_FTYPE_VM BIT(IDPF_CTLQ_FLAG_FTYPE_S) /* 0x40 */ -#define IDPF_CTLQ_FLAG_FTYPE_PF BIT(IDPF_CTLQ_FLAG_FTYPE_S + 1) /* 0x80 */ -#define IDPF_CTLQ_FLAG_RD BIT(IDPF_CTLQ_FLAG_RD_S) /* 0x400 */ -#define IDPF_CTLQ_FLAG_VFC BIT(IDPF_CTLQ_FLAG_VFC_S) /* 0x800 */ -#define IDPF_CTLQ_FLAG_BUF BIT(IDPF_CTLQ_FLAG_BUF_S) /* 0x1000 */ - -/* Host ID is a special field that has 3b and not a 1b flag */ -#define IDPF_CTLQ_FLAG_HOST_ID_M MAKE_MASK(0x7000UL, IDPF_CTLQ_FLAG_HOST_ID_S) - -struct idpf_mbxq_desc { - u8 pad[8]; /* CTLQ flags/opcode/len/retval fields */ - u32 chnl_opcode; /* avoid confusion with desc->opcode */ - u32 chnl_retval; /* ditto for desc->retval */ - u32 pf_vf_id; /* used by CP when sending to PF */ -}; - -/* Max number of MMIO regions not including the mailbox and rstat regions in - * the fallback case when the whole bar is mapped. - */ -#define IDPF_MMIO_MAP_FALLBACK_MAX_REMAINING 3 - -struct idpf_mmio_reg { - void __iomem *vaddr; - resource_size_t addr_start; - resource_size_t addr_len; -}; - -/* Define the driver hardware struct to replace other control structs as needed - * Align to ctlq_hw_info - */ -struct idpf_hw { - /* Array of remaining LAN BAR regions */ - int num_lan_regs; - struct idpf_mmio_reg *lan_regs; - - struct idpf_adapter *back; - - /* control queue - send and receive */ - struct idpf_ctlq_info *asq; - struct idpf_ctlq_info *arq; - - /* pci info */ - u16 device_id; - u16 vendor_id; - u16 subsystem_device_id; - u16 subsystem_vendor_id; - u8 revision_id; - bool adapter_stopped; - - struct list_head cq_list_head; -}; - -int idpf_ctlq_alloc_ring_res(struct idpf_hw *hw, - struct idpf_ctlq_info *cq); - -void idpf_ctlq_dealloc_ring_res(struct idpf_hw *hw, struct idpf_ctlq_info *cq); - -/* prototype for functions used for dynamic memory allocation */ -void *idpf_alloc_dma_mem(struct idpf_hw *hw, struct idpf_dma_mem *mem, - u64 size); -void idpf_free_dma_mem(struct idpf_hw *hw, struct idpf_dma_mem *mem); -#endif /* _IDPF_CONTROLQ_H_ */ diff --git a/drivers/net/ethernet/intel/idpf/idpf_controlq_api.h b/drivers/net/ethernet/intel/idpf/idpf_controlq_api.h deleted file mode 100644 index 3414c5f9a831..000000000000 --- a/drivers/net/ethernet/intel/idpf/idpf_controlq_api.h +++ /dev/null @@ -1,177 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* Copyright (C) 2023 Intel Corporation */ - -#ifndef _IDPF_CONTROLQ_API_H_ -#define _IDPF_CONTROLQ_API_H_ - -#include "idpf_mem.h" - -struct idpf_hw; - -/* Used for queue init, response and events */ -enum idpf_ctlq_type { - IDPF_CTLQ_TYPE_MAILBOX_TX = 0, - IDPF_CTLQ_TYPE_MAILBOX_RX = 1, - IDPF_CTLQ_TYPE_CONFIG_TX = 2, - IDPF_CTLQ_TYPE_CONFIG_RX = 3, - IDPF_CTLQ_TYPE_EVENT_RX = 4, - IDPF_CTLQ_TYPE_RDMA_TX = 5, - IDPF_CTLQ_TYPE_RDMA_RX = 6, - IDPF_CTLQ_TYPE_RDMA_COMPL = 7 -}; - -/* Generic Control Queue Structures */ -struct idpf_ctlq_reg { - /* used for queue tracking */ - u32 head; - u32 tail; - /* Below applies only to default mb (if present) */ - u32 len; - u32 bah; - u32 bal; - u32 len_mask; - u32 len_ena_mask; - u32 head_mask; -}; - -/* Generic queue msg structure */ -struct idpf_ctlq_msg { - u8 vmvf_type; /* represents the source of the message on recv */ -#define IDPF_VMVF_TYPE_VF 0 -#define IDPF_VMVF_TYPE_VM 1 -#define IDPF_VMVF_TYPE_PF 2 - u8 host_id; - /* 3b field used only when sending a message to CP - to be used in - * combination with target func_id to route the message - */ -#define IDPF_HOST_ID_MASK 0x7 - - u16 opcode; - u16 data_len; /* data_len = 0 when no payload is attached */ - union { - u16 func_id; /* when sending a message */ - u16 status; /* when receiving a message */ - }; - union { - struct { - u32 chnl_opcode; - u32 chnl_retval; - } mbx; - } cookie; - union { -#define IDPF_DIRECT_CTX_SIZE 16 -#define IDPF_INDIRECT_CTX_SIZE 8 - /* 16 bytes of context can be provided or 8 bytes of context - * plus the address of a DMA buffer - */ - u8 direct[IDPF_DIRECT_CTX_SIZE]; - struct { - u8 context[IDPF_INDIRECT_CTX_SIZE]; - struct idpf_dma_mem *payload; - } indirect; - struct { - u32 rsvd; - u16 data; - u16 flags; - } sw_cookie; - } ctx; -}; - -/* Generic queue info structures */ -/* MB, CONFIG and EVENT q do not have extended info */ -struct idpf_ctlq_create_info { - enum idpf_ctlq_type type; - int id; /* absolute queue offset passed as input - * -1 for default mailbox if present - */ - u16 len; /* Queue length passed as input */ - u16 buf_size; /* buffer size passed as input */ - u64 base_address; /* output, HPA of the Queue start */ - struct idpf_ctlq_reg reg; /* registers accessed by ctlqs */ - - int ext_info_size; - void *ext_info; /* Specific to q type */ -}; - -/* Control Queue information */ -struct idpf_ctlq_info { - struct list_head cq_list; - - enum idpf_ctlq_type cq_type; - int q_id; - spinlock_t cq_lock; /* control queue lock */ - /* used for interrupt processing */ - u16 next_to_use; - u16 next_to_clean; - u16 next_to_post; /* starting descriptor to post buffers - * to after recev - */ - - struct idpf_dma_mem desc_ring; /* descriptor ring memory - * idpf_dma_mem is defined in OSdep.h - */ - union { - struct idpf_dma_mem **rx_buff; - struct idpf_ctlq_msg **tx_msg; - } bi; - - u16 buf_size; /* queue buffer size */ - u16 ring_size; /* Number of descriptors */ - struct idpf_ctlq_reg reg; /* registers accessed by ctlqs */ -}; - -/** - * enum idpf_mbx_opc - PF/VF mailbox commands - * @idpf_mbq_opc_send_msg_to_cp: used by PF or VF to send a message to its CP - * @idpf_mbq_opc_send_msg_to_peer_drv: used by PF or VF to send a message to - * any peer driver - */ -enum idpf_mbx_opc { - idpf_mbq_opc_send_msg_to_cp = 0x0801, - idpf_mbq_opc_send_msg_to_peer_drv = 0x0804, -}; - -/* API supported for control queue management */ -/* Will init all required q including default mb. "q_info" is an array of - * create_info structs equal to the number of control queues to be created. - */ -int idpf_ctlq_init(struct idpf_hw *hw, u8 num_q, - struct idpf_ctlq_create_info *q_info); - -/* Allocate and initialize a single control queue, which will be added to the - * control queue list; returns a handle to the created control queue - */ -int idpf_ctlq_add(struct idpf_hw *hw, - struct idpf_ctlq_create_info *qinfo, - struct idpf_ctlq_info **cq); - -/* Deinitialize and deallocate a single control queue */ -void idpf_ctlq_remove(struct idpf_hw *hw, - struct idpf_ctlq_info *cq); - -/* Sends messages to HW and will also free the buffer*/ -int idpf_ctlq_send(struct idpf_hw *hw, - struct idpf_ctlq_info *cq, - u16 num_q_msg, - struct idpf_ctlq_msg q_msg[]); - -/* Receives messages and called by interrupt handler/polling - * initiated by app/process. Also caller is supposed to free the buffers - */ -int idpf_ctlq_recv(struct idpf_ctlq_info *cq, u16 *num_q_msg, - struct idpf_ctlq_msg *q_msg); - -/* Reclaims send descriptors on HW write back */ -int idpf_ctlq_clean_sq(struct idpf_ctlq_info *cq, u16 *clean_count, - struct idpf_ctlq_msg *msg_status[]); - -/* Indicate RX buffers are done being processed */ -int idpf_ctlq_post_rx_buffs(struct idpf_hw *hw, - struct idpf_ctlq_info *cq, - u16 *buff_count, - struct idpf_dma_mem **buffs); - -/* Will destroy all q including the default mb */ -void idpf_ctlq_deinit(struct idpf_hw *hw); - -#endif /* _IDPF_CONTROLQ_API_H_ */ diff --git a/drivers/net/ethernet/intel/idpf/idpf_controlq_setup.c b/drivers/net/ethernet/intel/idpf/idpf_controlq_setup.c deleted file mode 100644 index d4d488c7cfd6..000000000000 --- a/drivers/net/ethernet/intel/idpf/idpf_controlq_setup.c +++ /dev/null @@ -1,169 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* Copyright (C) 2023 Intel Corporation */ - -#include "idpf_controlq.h" - -/** - * idpf_ctlq_alloc_desc_ring - Allocate Control Queue (CQ) rings - * @hw: pointer to hw struct - * @cq: pointer to the specific Control queue - */ -static int idpf_ctlq_alloc_desc_ring(struct idpf_hw *hw, - struct idpf_ctlq_info *cq) -{ - size_t size = cq->ring_size * sizeof(struct idpf_ctlq_desc); - - cq->desc_ring.va = idpf_alloc_dma_mem(hw, &cq->desc_ring, size); - if (!cq->desc_ring.va) - return -ENOMEM; - - return 0; -} - -/** - * idpf_ctlq_alloc_bufs - Allocate Control Queue (CQ) buffers - * @hw: pointer to hw struct - * @cq: pointer to the specific Control queue - * - * Allocate the buffer head for all control queues, and if it's a receive - * queue, allocate DMA buffers - */ -static int idpf_ctlq_alloc_bufs(struct idpf_hw *hw, - struct idpf_ctlq_info *cq) -{ - int i; - - /* Do not allocate DMA buffers for transmit queues */ - if (cq->cq_type == IDPF_CTLQ_TYPE_MAILBOX_TX) - return 0; - - /* We'll be allocating the buffer info memory first, then we can - * allocate the mapped buffers for the event processing - */ - cq->bi.rx_buff = kzalloc_objs(struct idpf_dma_mem *, cq->ring_size); - if (!cq->bi.rx_buff) - return -ENOMEM; - - /* allocate the mapped buffers (except for the last one) */ - for (i = 0; i < cq->ring_size - 1; i++) { - struct idpf_dma_mem *bi; - int num = 1; /* number of idpf_dma_mem to be allocated */ - - cq->bi.rx_buff[i] = kzalloc_objs(struct idpf_dma_mem, num); - if (!cq->bi.rx_buff[i]) - goto unwind_alloc_cq_bufs; - - bi = cq->bi.rx_buff[i]; - - bi->va = idpf_alloc_dma_mem(hw, bi, cq->buf_size); - if (!bi->va) { - /* unwind will not free the failed entry */ - kfree(cq->bi.rx_buff[i]); - goto unwind_alloc_cq_bufs; - } - } - - return 0; - -unwind_alloc_cq_bufs: - /* don't try to free the one that failed... */ - i--; - for (; i >= 0; i--) { - idpf_free_dma_mem(hw, cq->bi.rx_buff[i]); - kfree(cq->bi.rx_buff[i]); - } - kfree(cq->bi.rx_buff); - - return -ENOMEM; -} - -/** - * idpf_ctlq_free_desc_ring - Free Control Queue (CQ) rings - * @hw: pointer to hw struct - * @cq: pointer to the specific Control queue - * - * This assumes the posted send buffers have already been cleaned - * and de-allocated - */ -static void idpf_ctlq_free_desc_ring(struct idpf_hw *hw, - struct idpf_ctlq_info *cq) -{ - idpf_free_dma_mem(hw, &cq->desc_ring); -} - -/** - * idpf_ctlq_free_bufs - Free CQ buffer info elements - * @hw: pointer to hw struct - * @cq: pointer to the specific Control queue - * - * Free the DMA buffers for RX queues, and DMA buffer header for both RX and TX - * queues. The upper layers are expected to manage freeing of TX DMA buffers - */ -static void idpf_ctlq_free_bufs(struct idpf_hw *hw, struct idpf_ctlq_info *cq) -{ - void *bi; - - if (cq->cq_type == IDPF_CTLQ_TYPE_MAILBOX_RX) { - int i; - - /* free DMA buffers for rx queues*/ - for (i = 0; i < cq->ring_size; i++) { - if (cq->bi.rx_buff[i]) { - idpf_free_dma_mem(hw, cq->bi.rx_buff[i]); - kfree(cq->bi.rx_buff[i]); - } - } - - bi = (void *)cq->bi.rx_buff; - } else { - bi = (void *)cq->bi.tx_msg; - } - - /* free the buffer header */ - kfree(bi); -} - -/** - * idpf_ctlq_dealloc_ring_res - Free memory allocated for control queue - * @hw: pointer to hw struct - * @cq: pointer to the specific Control queue - * - * Free the memory used by the ring, buffers and other related structures - */ -void idpf_ctlq_dealloc_ring_res(struct idpf_hw *hw, struct idpf_ctlq_info *cq) -{ - /* free ring buffers and the ring itself */ - idpf_ctlq_free_bufs(hw, cq); - idpf_ctlq_free_desc_ring(hw, cq); -} - -/** - * idpf_ctlq_alloc_ring_res - allocate memory for descriptor ring and bufs - * @hw: pointer to hw struct - * @cq: pointer to control queue struct - * - * Do *NOT* hold cq_lock when calling this as the memory allocation routines - * called are not going to be atomic context safe - */ -int idpf_ctlq_alloc_ring_res(struct idpf_hw *hw, struct idpf_ctlq_info *cq) -{ - int err; - - /* allocate the ring memory */ - err = idpf_ctlq_alloc_desc_ring(hw, cq); - if (err) - return err; - - /* allocate buffers in the rings */ - err = idpf_ctlq_alloc_bufs(hw, cq); - if (err) - goto idpf_init_cq_free_ring; - - /* success! */ - return 0; - -idpf_init_cq_free_ring: - idpf_free_dma_mem(hw, &cq->desc_ring); - - return err; -} diff --git a/drivers/net/ethernet/intel/idpf/idpf_dev.c b/drivers/net/ethernet/intel/idpf/idpf_dev.c index 64cd751fbd71..083cf6319d26 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_dev.c +++ b/drivers/net/ethernet/intel/idpf/idpf_dev.c @@ -10,44 +10,32 @@ /** * idpf_ctlq_reg_init - initialize default mailbox registers - * @adapter: adapter structure - * @cq: pointer to the array of create control queues + * @mmio: struct that contains MMIO region info + * @cci: struct where the register offset pointer to be copied to */ -static void idpf_ctlq_reg_init(struct idpf_adapter *adapter, - struct idpf_ctlq_create_info *cq) +static void idpf_ctlq_reg_init(struct libie_mmio_info *mmio, + struct libie_ctlq_create_info *cci) { - int i; + struct libie_ctlq_reg *tx_reg = &cci[LIBIE_CTLQ_TYPE_TX].reg; + struct libie_ctlq_reg *rx_reg = &cci[LIBIE_CTLQ_TYPE_RX].reg; - for (i = 0; i < IDPF_NUM_DFLT_MBX_Q; i++) { - struct idpf_ctlq_create_info *ccq = cq + i; + tx_reg->head = libie_pci_get_mmio_addr(mmio, PF_FW_ATQH); + tx_reg->tail = libie_pci_get_mmio_addr(mmio, PF_FW_ATQT); + tx_reg->len = libie_pci_get_mmio_addr(mmio, PF_FW_ATQLEN); + tx_reg->addr_high = libie_pci_get_mmio_addr(mmio, PF_FW_ATQBAH); + tx_reg->addr_low = libie_pci_get_mmio_addr(mmio, PF_FW_ATQBAL); + tx_reg->len_mask = PF_FW_ATQLEN_ATQLEN_M; + tx_reg->len_ena_mask = PF_FW_ATQLEN_ATQENABLE_M; + tx_reg->head_mask = PF_FW_ATQH_ATQH_M; - switch (ccq->type) { - case IDPF_CTLQ_TYPE_MAILBOX_TX: - /* set head and tail registers in our local struct */ - ccq->reg.head = PF_FW_ATQH; - ccq->reg.tail = PF_FW_ATQT; - ccq->reg.len = PF_FW_ATQLEN; - ccq->reg.bah = PF_FW_ATQBAH; - ccq->reg.bal = PF_FW_ATQBAL; - ccq->reg.len_mask = PF_FW_ATQLEN_ATQLEN_M; - ccq->reg.len_ena_mask = PF_FW_ATQLEN_ATQENABLE_M; - ccq->reg.head_mask = PF_FW_ATQH_ATQH_M; - break; - case IDPF_CTLQ_TYPE_MAILBOX_RX: - /* set head and tail registers in our local struct */ - ccq->reg.head = PF_FW_ARQH; - ccq->reg.tail = PF_FW_ARQT; - ccq->reg.len = PF_FW_ARQLEN; - ccq->reg.bah = PF_FW_ARQBAH; - ccq->reg.bal = PF_FW_ARQBAL; - ccq->reg.len_mask = PF_FW_ARQLEN_ARQLEN_M; - ccq->reg.len_ena_mask = PF_FW_ARQLEN_ARQENABLE_M; - ccq->reg.head_mask = PF_FW_ARQH_ARQH_M; - break; - default: - break; - } - } + rx_reg->head = libie_pci_get_mmio_addr(mmio, PF_FW_ARQH); + rx_reg->tail = libie_pci_get_mmio_addr(mmio, PF_FW_ARQT); + rx_reg->len = libie_pci_get_mmio_addr(mmio, PF_FW_ARQLEN); + rx_reg->addr_high = libie_pci_get_mmio_addr(mmio, PF_FW_ARQBAH); + rx_reg->addr_low = libie_pci_get_mmio_addr(mmio, PF_FW_ARQBAL); + rx_reg->len_mask = PF_FW_ARQLEN_ARQLEN_M; + rx_reg->len_ena_mask = PF_FW_ARQLEN_ARQENABLE_M; + rx_reg->head_mask = PF_FW_ARQH_ARQH_M; } /** diff --git a/drivers/net/ethernet/intel/idpf/idpf_ethtool.c b/drivers/net/ethernet/intel/idpf/idpf_ethtool.c index bb99d9e7c65d..95c45f12b0f9 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_ethtool.c +++ b/drivers/net/ethernet/intel/idpf/idpf_ethtool.c @@ -225,7 +225,7 @@ static int idpf_add_flow_steer(struct net_device *netdev, spin_unlock_bh(&vport_config->flow_steer_list_lock); if (err) - goto out; + goto out_free_fltr; rule->vport_id = cpu_to_le32(vport->vport_id); rule->count = cpu_to_le32(1); @@ -252,17 +252,15 @@ static int idpf_add_flow_steer(struct net_device *netdev, break; default: err = -EINVAL; - goto out; + goto out_free_fltr; } err = idpf_add_del_fsteer_filters(vport->adapter, rule, VIRTCHNL2_OP_ADD_FLOW_RULE); - if (err) - goto out; - - if (info->status != cpu_to_le32(VIRTCHNL2_FLOW_RULE_SUCCESS)) { - err = -EIO; - goto out; + if (err) { + /* virtchnl2 rule is already consumed */ + kfree(fltr); + return err; } /* Save a copy of the user's flow spec so ethtool can later retrieve it */ @@ -274,9 +272,10 @@ static int idpf_add_flow_steer(struct net_device *netdev, user_config->num_fsteer_fltrs++; spin_unlock_bh(&vport_config->flow_steer_list_lock); - goto out_free_rule; -out: + return 0; + +out_free_fltr: kfree(fltr); out_free_rule: kfree(rule); @@ -319,12 +318,7 @@ static int idpf_del_flow_steer(struct net_device *netdev, err = idpf_add_del_fsteer_filters(vport->adapter, rule, VIRTCHNL2_OP_DEL_FLOW_RULE); if (err) - goto out; - - if (info->status != cpu_to_le32(VIRTCHNL2_FLOW_RULE_SUCCESS)) { - err = -EIO; - goto out; - } + return err; spin_lock_bh(&vport_config->flow_steer_list_lock); list_for_each_entry_safe(f, iter, @@ -340,8 +334,6 @@ static int idpf_del_flow_steer(struct net_device *netdev, out_unlock: spin_unlock_bh(&vport_config->flow_steer_list_lock); -out: - kfree(rule); return err; } diff --git a/drivers/net/ethernet/intel/idpf/idpf_lib.c b/drivers/net/ethernet/intel/idpf/idpf_lib.c index 8001732eb45b..a945e62c27d7 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_lib.c +++ b/drivers/net/ethernet/intel/idpf/idpf_lib.c @@ -1371,6 +1371,7 @@ void idpf_statistics_task(struct work_struct *work) */ void idpf_mbx_task(struct work_struct *work) { + struct libie_ctlq_xn_recv_params xn_params; struct idpf_adapter *adapter; adapter = container_of(work, struct idpf_adapter, mbx_task.work); @@ -1381,7 +1382,14 @@ void idpf_mbx_task(struct work_struct *work) queue_delayed_work(adapter->mbx_wq, &adapter->mbx_task, usecs_to_jiffies(300)); - idpf_recv_mb_msg(adapter, adapter->hw.arq); + xn_params = (struct libie_ctlq_xn_recv_params) { + .xnm = adapter->xnm, + .ctlq = adapter->arq, + .ctlq_msg_handler = idpf_recv_event_msg, + .budget = LIBIE_CTLQ_MAX_XN_ENTRIES, + }; + + libie_ctlq_xn_recv(&xn_params); } /** @@ -1984,7 +1992,8 @@ void idpf_vc_event_task(struct work_struct *work) return; func_reset: - idpf_vc_xn_shutdown(adapter->vcxn_mngr); + if (adapter->xnm) + libie_ctlq_xn_shutdown(adapter->xnm); drv_load: set_bit(IDPF_HR_RESET_IN_PROG, adapter->flags); idpf_init_hard_reset(adapter); @@ -2567,44 +2576,6 @@ static int idpf_set_mac(struct net_device *netdev, void *p) return err; } -/** - * idpf_alloc_dma_mem - Allocate dma memory - * @hw: pointer to hw struct - * @mem: pointer to dma_mem struct - * @size: size of the memory to allocate - */ -void *idpf_alloc_dma_mem(struct idpf_hw *hw, struct idpf_dma_mem *mem, u64 size) -{ - struct idpf_adapter *adapter = hw->back; - size_t sz = ALIGN(size, 4096); - - /* The control queue resources are freed under a spinlock, contiguous - * pages will avoid IOMMU remapping and the use vmap (and vunmap in - * dma_free_*() path. - */ - mem->va = dma_alloc_attrs(&adapter->pdev->dev, sz, &mem->pa, - GFP_KERNEL, DMA_ATTR_FORCE_CONTIGUOUS); - mem->size = sz; - - return mem->va; -} - -/** - * idpf_free_dma_mem - Free the allocated dma memory - * @hw: pointer to hw struct - * @mem: pointer to dma_mem struct - */ -void idpf_free_dma_mem(struct idpf_hw *hw, struct idpf_dma_mem *mem) -{ - struct idpf_adapter *adapter = hw->back; - - dma_free_attrs(&adapter->pdev->dev, mem->size, - mem->va, mem->pa, DMA_ATTR_FORCE_CONTIGUOUS); - mem->size = 0; - mem->va = NULL; - mem->pa = 0; -} - static int idpf_hwtstamp_set(struct net_device *netdev, struct kernel_hwtstamp_config *config, struct netlink_ext_ack *extack) diff --git a/drivers/net/ethernet/intel/idpf/idpf_main.c b/drivers/net/ethernet/intel/idpf/idpf_main.c index 10dfc4cf4fa4..9840580fbe51 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_main.c +++ b/drivers/net/ethernet/intel/idpf/idpf_main.c @@ -170,8 +170,6 @@ static void idpf_remove(struct pci_dev *pdev) adapter->vport_config = NULL; kfree(adapter->netdevs); adapter->netdevs = NULL; - kfree(adapter->vcxn_mngr); - adapter->vcxn_mngr = NULL; mutex_destroy(&adapter->vport_ctrl_lock); mutex_destroy(&adapter->vector_lock); @@ -239,7 +237,6 @@ static int idpf_cfg_device(struct idpf_adapter *adapter) pci_dbg(pdev, "PCIe PTM is not supported by PCIe bus/controller\n"); pci_set_drvdata(pdev, adapter); - adapter->hw.back = adapter; return 0; } diff --git a/drivers/net/ethernet/intel/idpf/idpf_mem.h b/drivers/net/ethernet/intel/idpf/idpf_mem.h deleted file mode 100644 index 2aaabdc02dd2..000000000000 --- a/drivers/net/ethernet/intel/idpf/idpf_mem.h +++ /dev/null @@ -1,20 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* Copyright (C) 2023 Intel Corporation */ - -#ifndef _IDPF_MEM_H_ -#define _IDPF_MEM_H_ - -#include - -struct idpf_dma_mem { - void *va; - dma_addr_t pa; - size_t size; -}; - -#define idpf_mbx_wr32(a, reg, value) writel((value), ((a)->mbx.vaddr + (reg))) -#define idpf_mbx_rd32(a, reg) readl((a)->mbx.vaddr + (reg)) -#define idpf_mbx_wr64(a, reg, value) writeq((value), ((a)->mbx.vaddr + (reg))) -#define idpf_mbx_rd64(a, reg) readq((a)->mbx.vaddr + (reg)) - -#endif /* _IDPF_MEM_H_ */ diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.h b/drivers/net/ethernet/intel/idpf/idpf_txrx.h index 9103580c0a3d..93547597efd2 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.h +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.h @@ -236,7 +236,7 @@ enum idpf_tx_ctx_desc_eipt_offload { (sizeof(u16) * IDPF_RX_MAX_PTYPE_PROTO_IDS)) #define IDPF_RX_PTYPE_HDR_SZ sizeof(struct virtchnl2_get_ptype_info) #define IDPF_RX_MAX_PTYPES_PER_BUF \ - DIV_ROUND_DOWN_ULL((IDPF_CTLQ_MAX_BUF_LEN - IDPF_RX_PTYPE_HDR_SZ), \ + DIV_ROUND_DOWN_ULL(LIBIE_CTLQ_MAX_BUF_LEN - IDPF_RX_PTYPE_HDR_SZ, \ IDPF_RX_MAX_PTYPE_SZ) #define IDPF_GET_PTYPE_SIZE(p) struct_size((p), proto_id, (p)->proto_id_count) diff --git a/drivers/net/ethernet/intel/idpf/idpf_vf_dev.c b/drivers/net/ethernet/intel/idpf/idpf_vf_dev.c index 6cfa5edab4f6..b537de3592f4 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_vf_dev.c +++ b/drivers/net/ethernet/intel/idpf/idpf_vf_dev.c @@ -9,42 +9,32 @@ /** * idpf_vf_ctlq_reg_init - initialize default mailbox registers - * @adapter: adapter structure - * @cq: pointer to the array of create control queues + * @mmio: struct that contains MMIO region info + * @cci: struct where the register offset pointer to be copied to */ -static void idpf_vf_ctlq_reg_init(struct idpf_adapter *adapter, - struct idpf_ctlq_create_info *cq) +static void idpf_vf_ctlq_reg_init(struct libie_mmio_info *mmio, + struct libie_ctlq_create_info *cci) { - for (int i = 0; i < IDPF_NUM_DFLT_MBX_Q; i++) { - struct idpf_ctlq_create_info *ccq = cq + i; + struct libie_ctlq_reg *tx_reg = &cci[LIBIE_CTLQ_TYPE_TX].reg; + struct libie_ctlq_reg *rx_reg = &cci[LIBIE_CTLQ_TYPE_RX].reg; - switch (ccq->type) { - case IDPF_CTLQ_TYPE_MAILBOX_TX: - /* set head and tail registers in our local struct */ - ccq->reg.head = VF_ATQH; - ccq->reg.tail = VF_ATQT; - ccq->reg.len = VF_ATQLEN; - ccq->reg.bah = VF_ATQBAH; - ccq->reg.bal = VF_ATQBAL; - ccq->reg.len_mask = VF_ATQLEN_ATQLEN_M; - ccq->reg.len_ena_mask = VF_ATQLEN_ATQENABLE_M; - ccq->reg.head_mask = VF_ATQH_ATQH_M; - break; - case IDPF_CTLQ_TYPE_MAILBOX_RX: - /* set head and tail registers in our local struct */ - ccq->reg.head = VF_ARQH; - ccq->reg.tail = VF_ARQT; - ccq->reg.len = VF_ARQLEN; - ccq->reg.bah = VF_ARQBAH; - ccq->reg.bal = VF_ARQBAL; - ccq->reg.len_mask = VF_ARQLEN_ARQLEN_M; - ccq->reg.len_ena_mask = VF_ARQLEN_ARQENABLE_M; - ccq->reg.head_mask = VF_ARQH_ARQH_M; - break; - default: - break; - } - } + tx_reg->head = libie_pci_get_mmio_addr(mmio, VF_ATQH); + tx_reg->tail = libie_pci_get_mmio_addr(mmio, VF_ATQT); + tx_reg->len = libie_pci_get_mmio_addr(mmio, VF_ATQLEN); + tx_reg->addr_high = libie_pci_get_mmio_addr(mmio, VF_ATQBAH); + tx_reg->addr_low = libie_pci_get_mmio_addr(mmio, VF_ATQBAL); + tx_reg->len_mask = VF_ATQLEN_ATQLEN_M; + tx_reg->len_ena_mask = VF_ATQLEN_ATQENABLE_M; + tx_reg->head_mask = VF_ATQH_ATQH_M; + + rx_reg->head = libie_pci_get_mmio_addr(mmio, VF_ARQH); + rx_reg->tail = libie_pci_get_mmio_addr(mmio, VF_ARQT); + rx_reg->len = libie_pci_get_mmio_addr(mmio, VF_ARQLEN); + rx_reg->addr_high = libie_pci_get_mmio_addr(mmio, VF_ARQBAH); + rx_reg->addr_low = libie_pci_get_mmio_addr(mmio, VF_ARQBAL); + rx_reg->len_mask = VF_ARQLEN_ARQLEN_M; + rx_reg->len_ena_mask = VF_ARQLEN_ARQENABLE_M; + rx_reg->head_mask = VF_ARQH_ARQH_M; } /** @@ -160,8 +150,7 @@ static void idpf_vf_trigger_reset(struct idpf_adapter *adapter, /* Do not send VIRTCHNL2_OP_RESET_VF message on driver unload */ if (trig_cause == IDPF_HR_FUNC_RESET && !test_bit(IDPF_REMOVE_IN_PROG, adapter->flags)) - idpf_send_mb_msg(adapter, adapter->hw.asq, - VIRTCHNL2_OP_RESET_VF, 0, NULL, 0); + idpf_send_vf_reset_msg(adapter); } /** diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c index a44207162b57..5f8fb148f976 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c @@ -9,20 +9,6 @@ #include "idpf_virtchnl.h" #include "idpf_ptp.h" -/** - * struct idpf_vc_xn_manager - Manager for tracking transactions - * @ring: backing and lookup for transactions - * @free_xn_bm: bitmap for free transactions - * @xn_bm_lock: make bitmap access synchronous where necessary - * @salt: used to make cookie unique every message - */ -struct idpf_vc_xn_manager { - struct idpf_vc_xn ring[IDPF_VC_XN_RING_LEN]; - DECLARE_BITMAP(free_xn_bm, IDPF_VC_XN_RING_LEN); - spinlock_t xn_bm_lock; - u8 salt; -}; - /** * idpf_vid_to_vport - Translate vport id to vport pointer * @adapter: private data struct @@ -83,79 +69,55 @@ static void idpf_handle_event_link(struct idpf_adapter *adapter, /** * idpf_recv_event_msg - Receive virtchnl event message - * @adapter: Driver specific private structure + * @ctx: control queue context * @ctlq_msg: message to copy from * * Receive virtchnl event message */ -static void idpf_recv_event_msg(struct idpf_adapter *adapter, - struct idpf_ctlq_msg *ctlq_msg) +void idpf_recv_event_msg(struct libie_ctlq_ctx *ctx, + struct libie_ctlq_msg *ctlq_msg) { - int payload_size = ctlq_msg->ctx.indirect.payload->size; + struct kvec *buff = &ctlq_msg->recv_mem; + int payload_size = buff->iov_len; + struct idpf_adapter *adapter; struct virtchnl2_event *v2e; u32 event; + adapter = container_of(ctx, struct idpf_adapter, ctlq_ctx); if (payload_size < sizeof(*v2e)) { dev_err_ratelimited(&adapter->pdev->dev, "Failed to receive valid payload for event msg (op %d len %d)\n", - ctlq_msg->cookie.mbx.chnl_opcode, + ctlq_msg->chnl_opcode, payload_size); - return; + goto free_rx_buf; } - v2e = (struct virtchnl2_event *)ctlq_msg->ctx.indirect.payload->va; + v2e = (struct virtchnl2_event *)buff->iov_base; event = le32_to_cpu(v2e->event); switch (event) { case VIRTCHNL2_EVENT_LINK_CHANGE: idpf_handle_event_link(adapter, v2e); - return; + break; default: dev_err(&adapter->pdev->dev, "Unknown event %d from PF\n", event); break; } + +free_rx_buf: + libie_ctlq_release_rx_buf(buff); } /** * idpf_mb_clean - Reclaim the send mailbox queue entries - * @adapter: driver specific private structure * @asq: send control queue info + * @deinit: release all buffers before destroying the queue * - * Reclaim the send mailbox queue entries to be used to send further messages - * - * Return: 0 on success, negative on failure + * This is a helper function to clean the send mailbox queue entries. */ -static int idpf_mb_clean(struct idpf_adapter *adapter, - struct idpf_ctlq_info *asq) +static void idpf_mb_clean(struct libie_ctlq_info *asq, bool deinit) { - u16 i, num_q_msg = IDPF_DFLT_MBX_Q_LEN; - struct idpf_ctlq_msg **q_msg; - struct idpf_dma_mem *dma_mem; - int err; - - q_msg = kzalloc_objs(struct idpf_ctlq_msg *, num_q_msg, GFP_ATOMIC); - if (!q_msg) - return -ENOMEM; - - err = idpf_ctlq_clean_sq(asq, &num_q_msg, q_msg); - if (err) - goto err_kfree; - - for (i = 0; i < num_q_msg; i++) { - if (!q_msg[i]) - continue; - dma_mem = q_msg[i]->ctx.indirect.payload; - if (dma_mem) - dma_free_coherent(&adapter->pdev->dev, dma_mem->size, - dma_mem->va, dma_mem->pa); - kfree(q_msg[i]); - kfree(dma_mem); - } - -err_kfree: - kfree(q_msg); - - return err; + libie_ctlq_xn_send_clean(asq, kfree, deinit); } #if IS_ENABLED(CONFIG_PTP_1588_CLOCK) @@ -189,7 +151,7 @@ static bool idpf_ptp_is_mb_msg(u32 op) * @ctlq_msg: Corresponding control queue message */ static void idpf_prepare_ptp_mb_msg(struct idpf_adapter *adapter, u32 op, - struct idpf_ctlq_msg *ctlq_msg) + struct libie_ctlq_msg *ctlq_msg) { /* If the message is PTP-related and the secondary mailbox is available, * send the message through the secondary mailbox. @@ -197,532 +159,108 @@ static void idpf_prepare_ptp_mb_msg(struct idpf_adapter *adapter, u32 op, if (!idpf_ptp_is_mb_msg(op) || !adapter->ptp->secondary_mbx.valid) return; - ctlq_msg->opcode = idpf_mbq_opc_send_msg_to_peer_drv; + ctlq_msg->opcode = LIBIE_CTLQ_SEND_MSG_TO_PEER; ctlq_msg->func_id = adapter->ptp->secondary_mbx.peer_mbx_q_id; - ctlq_msg->host_id = adapter->ptp->secondary_mbx.peer_id; + ctlq_msg->flags = FIELD_PREP(LIBIE_CTLQ_DESC_FLAG_HOST_ID, + adapter->ptp->secondary_mbx.peer_id); } #else /* !CONFIG_PTP_1588_CLOCK */ static void idpf_prepare_ptp_mb_msg(struct idpf_adapter *adapter, u32 op, - struct idpf_ctlq_msg *ctlq_msg) + struct libie_ctlq_msg *ctlq_msg) { } #endif /* CONFIG_PTP_1588_CLOCK */ /** - * idpf_send_mb_msg - Send message over mailbox + * idpf_send_mb_msg - send mailbox message to the device control plane * @adapter: driver specific private structure - * @asq: control queue to send message to - * @op: virtchnl opcode - * @msg_size: size of the payload - * @msg: pointer to buffer holding the payload - * @cookie: unique SW generated cookie per message + * @xn_params: Xn send parameters to fill + * @send_buf: buffer to send + * @send_buf_size: size of the send buffer * - * Will prepare the control queue message and initiates the send api + * Fill the Xn parameters with the required info to send a virtchnl message. + * The send buffer is DMA mapped in the libie to avoid memcpy. * - * Return: 0 on success, negative on failure - */ -int idpf_send_mb_msg(struct idpf_adapter *adapter, struct idpf_ctlq_info *asq, - u32 op, u16 msg_size, u8 *msg, u16 cookie) -{ - struct idpf_ctlq_msg *ctlq_msg; - struct idpf_dma_mem *dma_mem; - int err; - - /* If we are here and a reset is detected nothing much can be - * done. This thread should silently abort and expected to - * be corrected with a new run either by user or driver - * flows after reset - */ - if (idpf_is_reset_detected(adapter)) - return 0; - - err = idpf_mb_clean(adapter, asq); - if (err) - return err; - - ctlq_msg = kzalloc_obj(*ctlq_msg, GFP_ATOMIC); - if (!ctlq_msg) - return -ENOMEM; - - dma_mem = kzalloc_obj(*dma_mem, GFP_ATOMIC); - if (!dma_mem) { - err = -ENOMEM; - goto dma_mem_error; - } - - ctlq_msg->opcode = idpf_mbq_opc_send_msg_to_cp; - ctlq_msg->func_id = 0; - - idpf_prepare_ptp_mb_msg(adapter, op, ctlq_msg); - - ctlq_msg->data_len = msg_size; - ctlq_msg->cookie.mbx.chnl_opcode = op; - ctlq_msg->cookie.mbx.chnl_retval = 0; - dma_mem->size = IDPF_CTLQ_MAX_BUF_LEN; - dma_mem->va = dma_alloc_coherent(&adapter->pdev->dev, dma_mem->size, - &dma_mem->pa, GFP_ATOMIC); - if (!dma_mem->va) { - err = -ENOMEM; - goto dma_alloc_error; - } - - /* It's possible we're just sending an opcode but no buffer */ - if (msg && msg_size) - memcpy(dma_mem->va, msg, msg_size); - ctlq_msg->ctx.indirect.payload = dma_mem; - ctlq_msg->ctx.sw_cookie.data = cookie; - - err = idpf_ctlq_send(&adapter->hw, asq, 1, ctlq_msg); - if (err) - goto send_error; - - return 0; - -send_error: - dma_free_coherent(&adapter->pdev->dev, dma_mem->size, dma_mem->va, - dma_mem->pa); -dma_alloc_error: - kfree(dma_mem); -dma_mem_error: - kfree(ctlq_msg); - - return err; -} - -/* API for virtchnl "transaction" support ("xn" for short). */ - -/** - * idpf_vc_xn_lock - Request exclusive access to vc transaction - * @xn: struct idpf_vc_xn* to access - */ -#define idpf_vc_xn_lock(xn) \ - spin_lock(&(xn)->lock) - -/** - * idpf_vc_xn_unlock - Release exclusive access to vc transaction - * @xn: struct idpf_vc_xn* to access - */ -#define idpf_vc_xn_unlock(xn) \ - spin_unlock(&(xn)->lock) - -/** - * idpf_vc_xn_release_bufs - Release reference to reply buffer(s) and - * reset the transaction state. - * @xn: struct idpf_vc_xn to update - */ -static void idpf_vc_xn_release_bufs(struct idpf_vc_xn *xn) -{ - xn->reply.iov_base = NULL; - xn->reply.iov_len = 0; - - if (xn->state != IDPF_VC_XN_SHUTDOWN) - xn->state = IDPF_VC_XN_IDLE; -} - -/** - * idpf_vc_xn_init - Initialize virtchnl transaction object - * @vcxn_mngr: pointer to vc transaction manager struct - */ -static void idpf_vc_xn_init(struct idpf_vc_xn_manager *vcxn_mngr) -{ - int i; - - spin_lock_init(&vcxn_mngr->xn_bm_lock); - - for (i = 0; i < ARRAY_SIZE(vcxn_mngr->ring); i++) { - struct idpf_vc_xn *xn = &vcxn_mngr->ring[i]; - - xn->state = IDPF_VC_XN_IDLE; - xn->idx = i; - idpf_vc_xn_release_bufs(xn); - spin_lock_init(&xn->lock); - init_completion(&xn->completed); - } - - bitmap_fill(vcxn_mngr->free_xn_bm, IDPF_VC_XN_RING_LEN); -} - -/** - * idpf_vc_xn_shutdown - Uninitialize virtchnl transaction object - * @vcxn_mngr: pointer to vc transaction manager struct + * Cleanup the mailbox queue entries of the previously sent message to + * unmap and release the buffer. * - * All waiting threads will be woken-up and their transaction aborted. Further - * operations on that object will fail. + * Return: 0 if the request was successful, -%EBUSY if reset is detected + * or Tx control queue is full, other negative error code on failure. */ -void idpf_vc_xn_shutdown(struct idpf_vc_xn_manager *vcxn_mngr) +int idpf_send_mb_msg(struct idpf_adapter *adapter, + struct libie_ctlq_xn_send_params *xn_params, + void *send_buf, size_t send_buf_size) { - int i; + struct libie_ctlq_msg ctlq_msg = {}; - spin_lock_bh(&vcxn_mngr->xn_bm_lock); - bitmap_zero(vcxn_mngr->free_xn_bm, IDPF_VC_XN_RING_LEN); - spin_unlock_bh(&vcxn_mngr->xn_bm_lock); + if (idpf_is_reset_detected(adapter)) { + if (!libie_cp_can_send_onstack(send_buf_size)) + kfree(send_buf); - for (i = 0; i < ARRAY_SIZE(vcxn_mngr->ring); i++) { - struct idpf_vc_xn *xn = &vcxn_mngr->ring[i]; - - idpf_vc_xn_lock(xn); - xn->state = IDPF_VC_XN_SHUTDOWN; - idpf_vc_xn_release_bufs(xn); - idpf_vc_xn_unlock(xn); - complete_all(&xn->completed); + return -EBUSY; } + + idpf_prepare_ptp_mb_msg(adapter, xn_params->chnl_opcode, &ctlq_msg); + xn_params->ctlq_msg = ctlq_msg.opcode ? &ctlq_msg : NULL; + + xn_params->send_buf.iov_base = send_buf; + xn_params->send_buf.iov_len = send_buf_size; + xn_params->xnm = adapter->xnm; + xn_params->ctlq = xn_params->ctlq ? xn_params->ctlq : adapter->asq; + xn_params->rel_tx_buf = kfree; + + idpf_mb_clean(xn_params->ctlq, false); + + return libie_ctlq_xn_send(xn_params); } /** - * idpf_vc_xn_pop_free - Pop a free transaction from free list - * @vcxn_mngr: transaction manager to pop from - * - * Returns NULL if no free transactions - */ -static -struct idpf_vc_xn *idpf_vc_xn_pop_free(struct idpf_vc_xn_manager *vcxn_mngr) -{ - struct idpf_vc_xn *xn = NULL; - unsigned long free_idx; - - spin_lock_bh(&vcxn_mngr->xn_bm_lock); - free_idx = find_first_bit(vcxn_mngr->free_xn_bm, IDPF_VC_XN_RING_LEN); - if (free_idx == IDPF_VC_XN_RING_LEN) - goto do_unlock; - - clear_bit(free_idx, vcxn_mngr->free_xn_bm); - xn = &vcxn_mngr->ring[free_idx]; - xn->salt = vcxn_mngr->salt++; - -do_unlock: - spin_unlock_bh(&vcxn_mngr->xn_bm_lock); - - return xn; -} - -/** - * idpf_vc_xn_push_free - Push a free transaction to free list - * @vcxn_mngr: transaction manager to push to - * @xn: transaction to push - */ -static void idpf_vc_xn_push_free(struct idpf_vc_xn_manager *vcxn_mngr, - struct idpf_vc_xn *xn) -{ - idpf_vc_xn_release_bufs(xn); - spin_lock_bh(&vcxn_mngr->xn_bm_lock); - set_bit(xn->idx, vcxn_mngr->free_xn_bm); - spin_unlock_bh(&vcxn_mngr->xn_bm_lock); -} - -/** - * idpf_vc_xn_exec - Perform a send/recv virtchnl transaction - * @adapter: driver specific private structure with vcxn_mngr - * @params: parameters for this particular transaction including - * -vc_op: virtchannel operation to send - * -send_buf: kvec iov for send buf and len - * -recv_buf: kvec iov for recv buf and len (ignored if NULL) - * -timeout_ms: timeout waiting for a reply (milliseconds) - * -async: don't wait for message reply, will lose caller context - * -async_handler: callback to handle async replies - * - * @returns >= 0 for success, the size of the initial reply (may or may not be - * >= @recv_buf.iov_len, but we never overflow @@recv_buf_iov_base). < 0 for - * error. - */ -ssize_t idpf_vc_xn_exec(struct idpf_adapter *adapter, - const struct idpf_vc_xn_params *params) -{ - const struct kvec *send_buf = ¶ms->send_buf; - struct idpf_vc_xn *xn; - ssize_t retval; - u16 cookie; - - xn = idpf_vc_xn_pop_free(adapter->vcxn_mngr); - /* no free transactions available */ - if (!xn) - return -ENOSPC; - - idpf_vc_xn_lock(xn); - if (xn->state == IDPF_VC_XN_SHUTDOWN) { - retval = -ENXIO; - goto only_unlock; - } else if (xn->state != IDPF_VC_XN_IDLE) { - /* We're just going to clobber this transaction even though - * it's not IDLE. If we don't reuse it we could theoretically - * eventually leak all the free transactions and not be able to - * send any messages. At least this way we make an attempt to - * remain functional even though something really bad is - * happening that's corrupting what was supposed to be free - * transactions. - */ - WARN_ONCE(1, "There should only be idle transactions in free list (idx %d op %d)\n", - xn->idx, xn->vc_op); - } - - xn->reply = params->recv_buf; - xn->reply_sz = 0; - xn->state = params->async ? IDPF_VC_XN_ASYNC : IDPF_VC_XN_WAITING; - xn->vc_op = params->vc_op; - xn->async_handler = params->async_handler; - idpf_vc_xn_unlock(xn); - - if (!params->async) - reinit_completion(&xn->completed); - cookie = FIELD_PREP(IDPF_VC_XN_SALT_M, xn->salt) | - FIELD_PREP(IDPF_VC_XN_IDX_M, xn->idx); - - retval = idpf_send_mb_msg(adapter, adapter->hw.asq, params->vc_op, - send_buf->iov_len, send_buf->iov_base, - cookie); - if (retval) { - idpf_vc_xn_lock(xn); - goto release_and_unlock; - } - - if (params->async) - return 0; - - wait_for_completion_timeout(&xn->completed, - msecs_to_jiffies(params->timeout_ms)); - - /* No need to check the return value; we check the final state of the - * transaction below. It's possible the transaction actually gets more - * timeout than specified if we get preempted here but after - * wait_for_completion_timeout returns. This should be non-issue - * however. - */ - idpf_vc_xn_lock(xn); - switch (xn->state) { - case IDPF_VC_XN_SHUTDOWN: - retval = -ENXIO; - goto only_unlock; - case IDPF_VC_XN_WAITING: - dev_notice_ratelimited(&adapter->pdev->dev, - "Transaction timed-out (op:%d cookie:%04x vc_op:%d salt:%02x timeout:%dms)\n", - params->vc_op, cookie, xn->vc_op, - xn->salt, params->timeout_ms); - retval = -ETIME; - break; - case IDPF_VC_XN_COMPLETED_SUCCESS: - retval = xn->reply_sz; - break; - case IDPF_VC_XN_COMPLETED_FAILED: - dev_notice_ratelimited(&adapter->pdev->dev, "Transaction failed (op %d)\n", - params->vc_op); - retval = -EIO; - break; - default: - /* Invalid state. */ - WARN_ON_ONCE(1); - retval = -EIO; - break; - } - -release_and_unlock: - idpf_vc_xn_push_free(adapter->vcxn_mngr, xn); - /* If we receive a VC reply after here, it will be dropped. */ -only_unlock: - idpf_vc_xn_unlock(xn); - - return retval; -} - -/** - * idpf_vc_xn_forward_async - Handle async reply receives - * @adapter: private data struct - * @xn: transaction to handle - * @ctlq_msg: corresponding ctlq_msg - * - * For async sends we're going to lose the caller's context so, if an - * async_handler was provided, it can deal with the reply, otherwise we'll just - * check and report if there is an error. - */ -static int -idpf_vc_xn_forward_async(struct idpf_adapter *adapter, struct idpf_vc_xn *xn, - const struct idpf_ctlq_msg *ctlq_msg) -{ - int err = 0; - - if (ctlq_msg->cookie.mbx.chnl_opcode != xn->vc_op) { - dev_err_ratelimited(&adapter->pdev->dev, "Async message opcode does not match transaction opcode (msg: %d) (xn: %d)\n", - ctlq_msg->cookie.mbx.chnl_opcode, xn->vc_op); - xn->reply_sz = 0; - err = -EINVAL; - goto release_bufs; - } - - if (xn->async_handler) { - err = xn->async_handler(adapter, xn, ctlq_msg); - goto release_bufs; - } - - if (ctlq_msg->cookie.mbx.chnl_retval) { - xn->reply_sz = 0; - dev_err_ratelimited(&adapter->pdev->dev, "Async message failure (op %d)\n", - ctlq_msg->cookie.mbx.chnl_opcode); - err = -EINVAL; - } - -release_bufs: - idpf_vc_xn_push_free(adapter->vcxn_mngr, xn); - - return err; -} - -/** - * idpf_vc_xn_forward_reply - copy a reply back to receiving thread - * @adapter: driver specific private structure with vcxn_mngr - * @ctlq_msg: controlq message to send back to receiving thread - */ -static int -idpf_vc_xn_forward_reply(struct idpf_adapter *adapter, - const struct idpf_ctlq_msg *ctlq_msg) -{ - const void *payload = NULL; - size_t payload_size = 0; - struct idpf_vc_xn *xn; - u16 msg_info; - int err = 0; - u16 xn_idx; - u16 salt; - - msg_info = ctlq_msg->ctx.sw_cookie.data; - xn_idx = FIELD_GET(IDPF_VC_XN_IDX_M, msg_info); - if (xn_idx >= ARRAY_SIZE(adapter->vcxn_mngr->ring)) { - dev_err_ratelimited(&adapter->pdev->dev, "Out of bounds cookie received: %02x\n", - xn_idx); - return -EINVAL; - } - xn = &adapter->vcxn_mngr->ring[xn_idx]; - idpf_vc_xn_lock(xn); - salt = FIELD_GET(IDPF_VC_XN_SALT_M, msg_info); - if (xn->salt != salt) { - dev_err_ratelimited(&adapter->pdev->dev, "Transaction salt does not match (exp:%d@%02x(%d) != got:%d@%02x)\n", - xn->vc_op, xn->salt, xn->state, - ctlq_msg->cookie.mbx.chnl_opcode, salt); - idpf_vc_xn_unlock(xn); - return -EINVAL; - } - - switch (xn->state) { - case IDPF_VC_XN_WAITING: - /* success */ - break; - case IDPF_VC_XN_IDLE: - dev_err_ratelimited(&adapter->pdev->dev, "Unexpected or belated VC reply (op %d)\n", - ctlq_msg->cookie.mbx.chnl_opcode); - err = -EINVAL; - goto out_unlock; - case IDPF_VC_XN_SHUTDOWN: - /* ENXIO is a bit special here as the recv msg loop uses that - * know if it should stop trying to clean the ring if we lost - * the virtchnl. We need to stop playing with registers and - * yield. - */ - err = -ENXIO; - goto out_unlock; - case IDPF_VC_XN_ASYNC: - /* Set reply_sz from the actual payload so that async_handler - * can evaluate the response. - */ - xn->reply_sz = ctlq_msg->data_len; - err = idpf_vc_xn_forward_async(adapter, xn, ctlq_msg); - idpf_vc_xn_unlock(xn); - return err; - default: - dev_err_ratelimited(&adapter->pdev->dev, "Overwriting VC reply (op %d)\n", - ctlq_msg->cookie.mbx.chnl_opcode); - err = -EBUSY; - goto out_unlock; - } - - if (ctlq_msg->cookie.mbx.chnl_opcode != xn->vc_op) { - dev_err_ratelimited(&adapter->pdev->dev, "Message opcode does not match transaction opcode (msg: %d) (xn: %d)\n", - ctlq_msg->cookie.mbx.chnl_opcode, xn->vc_op); - xn->reply_sz = 0; - xn->state = IDPF_VC_XN_COMPLETED_FAILED; - err = -EINVAL; - goto out_unlock; - } - - if (ctlq_msg->cookie.mbx.chnl_retval) { - xn->reply_sz = 0; - xn->state = IDPF_VC_XN_COMPLETED_FAILED; - err = -EINVAL; - goto out_unlock; - } - - if (ctlq_msg->data_len) { - payload = ctlq_msg->ctx.indirect.payload->va; - payload_size = ctlq_msg->data_len; - } - - xn->reply_sz = payload_size; - xn->state = IDPF_VC_XN_COMPLETED_SUCCESS; - - if (xn->reply.iov_base && xn->reply.iov_len && payload_size) - memcpy(xn->reply.iov_base, payload, - min_t(size_t, xn->reply.iov_len, payload_size)); - -out_unlock: - idpf_vc_xn_unlock(xn); - /* we _cannot_ hold lock while calling complete */ - complete(&xn->completed); - - return err; -} - -/** - * idpf_recv_mb_msg - Receive message over mailbox + * idpf_send_mb_msg_kfree - send mailbox message and free the send buffer * @adapter: driver specific private structure - * @arq: control queue to receive message from + * @xn_params: Xn send parameters to fill + * @send_buf: buffer to send, can be released with kfree() + * @send_buf_size: size of the send buffer * - * Will receive control queue message and posts the receive buffer. + * libie_cp functions consume only buffers above certain size, + * smaller buffers are assumed to be on the stack. However, for some + * commands with variable message size it makes sense to always use kzalloc(), + * which means we have to free smaller buffers ourselves. * - * Return: 0 on success and negative on failure. + * Return: 0 if no unexpected errors were encountered, + * negative error code otherwise. */ -int idpf_recv_mb_msg(struct idpf_adapter *adapter, struct idpf_ctlq_info *arq) +int idpf_send_mb_msg_kfree(struct idpf_adapter *adapter, + struct libie_ctlq_xn_send_params *xn_params, + void *send_buf, size_t send_buf_size) { - struct idpf_ctlq_msg ctlq_msg; - struct idpf_dma_mem *dma_mem; - int post_err, err; - u16 num_recv; + int err = idpf_send_mb_msg(adapter, xn_params, send_buf, send_buf_size); - while (1) { - /* This will get <= num_recv messages and output how many - * actually received on num_recv. - */ - num_recv = 1; - err = idpf_ctlq_recv(arq, &num_recv, &ctlq_msg); - if (err || !num_recv) - break; - - if (ctlq_msg.data_len) { - dma_mem = ctlq_msg.ctx.indirect.payload; - } else { - dma_mem = NULL; - num_recv = 0; - } - - if (ctlq_msg.cookie.mbx.chnl_opcode == VIRTCHNL2_OP_EVENT) - idpf_recv_event_msg(adapter, &ctlq_msg); - else - err = idpf_vc_xn_forward_reply(adapter, &ctlq_msg); - - post_err = idpf_ctlq_post_rx_buffs(&adapter->hw, arq, - &num_recv, &dma_mem); - - /* If post failed clear the only buffer we supplied */ - if (post_err) { - if (dma_mem) - dma_free_coherent(&adapter->pdev->dev, - dma_mem->size, dma_mem->va, - dma_mem->pa); - break; - } - - /* virtchnl trying to shutdown, stop cleaning */ - if (err == -ENXIO) - break; - } + if (libie_cp_can_send_onstack(send_buf_size)) + kfree(send_buf); return err; } +/** + * idpf_send_vf_reset_msg - send one way VF reset message + * @adapter: driver specific private structure + */ +void idpf_send_vf_reset_msg(struct idpf_adapter *adapter) +{ + struct libie_ctlq_info *ctlq = adapter->asq; + + /* Forcefully claim send queue slot */ + idpf_mb_clean(ctlq, true); + + scoped_guard(spinlock, &ctlq->lock) { + *ctlq->tx_msg[ctlq->next_to_use] = (struct libie_ctlq_msg) { + .opcode = LIBIE_CTLQ_SEND_MSG_TO_CP, + .chnl_opcode = VIRTCHNL2_OP_RESET_VF, + }; + + libie_ctlq_send(adapter->asq, 1); + } +} + struct idpf_chunked_msg_params { u32 (*prepare_msg)(u32 vport_id, void *buf, const void *pos, u32 num); @@ -768,45 +306,43 @@ struct idpf_queue_set *idpf_alloc_queue_set(struct idpf_adapter *adapter, static int idpf_send_chunked_msg(struct idpf_adapter *adapter, const struct idpf_chunked_msg_params *params) { - struct idpf_vc_xn_params xn_params = { - .vc_op = params->vc_op, + struct libie_ctlq_xn_send_params xn_params = { .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = params->vc_op, }; const void *pos = params->chunks; - u32 num_chunks, num_msgs, buf_sz; - void *buf __free(kfree) = NULL; u32 totqs = params->num_chunks; u32 vid = params->vport_id; + u32 num_chunks, num_msgs; - num_chunks = min(IDPF_NUM_CHUNKS_PER_MSG(params->config_sz, - params->chunk_sz), totqs); + num_chunks = IDPF_NUM_CHUNKS_PER_MSG(params->config_sz, + params->chunk_sz); num_msgs = DIV_ROUND_UP(totqs, num_chunks); - buf_sz = params->config_sz + num_chunks * params->chunk_sz; - buf = kzalloc(buf_sz, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - xn_params.send_buf.iov_base = buf; - for (u32 i = 0; i < num_msgs; i++) { - ssize_t reply_sz; - - memset(buf, 0, buf_sz); - xn_params.send_buf.iov_len = buf_sz; - - if (params->prepare_msg(vid, buf, pos, num_chunks) != buf_sz) - return -EINVAL; - - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - - pos += num_chunks * params->chunk_sz; - totqs -= num_chunks; + u32 buf_sz; + void *buf; + int err; num_chunks = min(num_chunks, totqs); buf_sz = params->config_sz + num_chunks * params->chunk_sz; + buf = kzalloc(buf_sz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (params->prepare_msg(vid, buf, pos, num_chunks) != buf_sz) { + kfree(buf); + return -EINVAL; + } + + err = idpf_send_mb_msg_kfree(adapter, &xn_params, buf, buf_sz); + if (err) + return err; + + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + xn_params.recv_mem = (struct kvec) {}; + pos += num_chunks * params->chunk_sz; + totqs -= num_chunks; } return 0; @@ -881,11 +417,14 @@ static int idpf_wait_for_marker_event(struct idpf_vport *vport) */ static int idpf_send_ver_msg(struct idpf_adapter *adapter) { - struct idpf_vc_xn_params xn_params = {}; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_VERSION, + }; + struct virtchnl2_version_info *vvi_recv; struct virtchnl2_version_info vvi; - ssize_t reply_sz; u32 major, minor; - int err = 0; + int err; if (adapter->virt_ver_maj) { vvi.major = cpu_to_le32(adapter->virt_ver_maj); @@ -895,24 +434,23 @@ static int idpf_send_ver_msg(struct idpf_adapter *adapter) vvi.minor = cpu_to_le32(IDPF_VIRTCHNL_VERSION_MINOR); } - xn_params.vc_op = VIRTCHNL2_OP_VERSION; - xn_params.send_buf.iov_base = &vvi; - xn_params.send_buf.iov_len = sizeof(vvi); - xn_params.recv_buf = xn_params.send_buf; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; + err = idpf_send_mb_msg_stack(adapter, &xn_params, &vvi); + if (err) + return err; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - if (reply_sz < sizeof(vvi)) - return -EIO; + if (xn_params.recv_mem.iov_len < sizeof(*vvi_recv)) { + err = -EIO; + goto free_rx_buf; + } - major = le32_to_cpu(vvi.major); - minor = le32_to_cpu(vvi.minor); + vvi_recv = xn_params.recv_mem.iov_base; + major = le32_to_cpu(vvi_recv->major); + minor = le32_to_cpu(vvi_recv->minor); if (major > IDPF_VIRTCHNL_VERSION_MAJOR) { dev_warn(&adapter->pdev->dev, "Virtchnl major version greater than supported\n"); - return -EINVAL; + err = -EINVAL; + goto free_rx_buf; } if (major == IDPF_VIRTCHNL_VERSION_MAJOR && @@ -930,6 +468,9 @@ static int idpf_send_ver_msg(struct idpf_adapter *adapter) adapter->virt_ver_maj = major; adapter->virt_ver_min = minor; +free_rx_buf: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + return err; } @@ -942,9 +483,12 @@ static int idpf_send_ver_msg(struct idpf_adapter *adapter) */ static int idpf_send_get_caps_msg(struct idpf_adapter *adapter) { + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_GET_CAPS, + }; struct virtchnl2_get_capabilities caps = {}; - struct idpf_vc_xn_params xn_params = {}; - ssize_t reply_sz; + int err; caps.csum_caps = cpu_to_le32(VIRTCHNL2_CAP_TX_CSUM_L3_IPV4 | @@ -1004,20 +548,22 @@ static int idpf_send_get_caps_msg(struct idpf_adapter *adapter) VIRTCHNL2_CAP_LOOPBACK | VIRTCHNL2_CAP_PTP); - xn_params.vc_op = VIRTCHNL2_OP_GET_CAPS; - xn_params.send_buf.iov_base = ∩︀ - xn_params.send_buf.iov_len = sizeof(caps); - xn_params.recv_buf.iov_base = &adapter->caps; - xn_params.recv_buf.iov_len = sizeof(adapter->caps); - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; + err = idpf_send_mb_msg_stack(adapter, &xn_params, &caps); + if (err) + return err; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - if (reply_sz < sizeof(adapter->caps)) - return -EIO; + if (xn_params.recv_mem.iov_len < sizeof(adapter->caps)) { + err = -EIO; + goto free_rx_buf; + } - return 0; + memcpy(&adapter->caps, xn_params.recv_mem.iov_base, + sizeof(adapter->caps)); + +free_rx_buf: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + + return err; } /** @@ -1062,37 +608,39 @@ static void idpf_decfg_lan_memory_regions(struct idpf_adapter *adapter) */ static int idpf_cfg_lan_memory_regions(struct idpf_adapter *adapter) { - struct virtchnl2_get_lan_memory_regions *rcvd_regions __free(kfree); - struct idpf_vc_xn_params xn_params = { - .vc_op = VIRTCHNL2_OP_GET_LAN_MEMORY_REGIONS, - .recv_buf.iov_len = IDPF_CTLQ_MAX_BUF_LEN, - .send_buf.iov_len = - sizeof(struct virtchnl2_get_lan_memory_regions) + - sizeof(struct virtchnl2_mem_region), + struct virtchnl2_get_lan_memory_regions *send_regions, *rcvd_regions; + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = VIRTCHNL2_OP_GET_LAN_MEMORY_REGIONS, .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, }; - int num_regions, size; - ssize_t reply_sz; + size_t send_sz, reply_sz, size; + int num_regions; int err = 0; - rcvd_regions = kzalloc(IDPF_CTLQ_MAX_BUF_LEN, GFP_KERNEL); - if (!rcvd_regions) + send_sz = sizeof(struct virtchnl2_get_lan_memory_regions) + + sizeof(struct virtchnl2_mem_region); + send_regions = kzalloc(send_sz, GFP_KERNEL); + if (!send_regions) return -ENOMEM; - xn_params.recv_buf.iov_base = rcvd_regions; - rcvd_regions->num_memory_regions = cpu_to_le16(1); - xn_params.send_buf.iov_base = rcvd_regions; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; + send_regions->num_memory_regions = cpu_to_le16(1); + err = idpf_send_mb_msg_kfree(adapter, &xn_params, send_regions, + send_sz); + if (err) + return err; + rcvd_regions = xn_params.recv_mem.iov_base; + reply_sz = xn_params.recv_mem.iov_len; + if (reply_sz < sizeof(*rcvd_regions)) { + err = -EIO; + goto rel_rx_buf; + } num_regions = le16_to_cpu(rcvd_regions->num_memory_regions); size = struct_size(rcvd_regions, mem_reg, num_regions); - if (reply_sz < size) - return -EIO; - - if (size > IDPF_CTLQ_MAX_BUF_LEN) - return -EINVAL; + if (reply_sz < size) { + err = -EIO; + goto rel_rx_buf; + } for (int i = 0; i < num_regions; i++) { struct libie_mmio_info *mmio = &adapter->ctlq_ctx.mmio_info; @@ -1102,10 +650,14 @@ static int idpf_cfg_lan_memory_regions(struct idpf_adapter *adapter) len = le64_to_cpu(rcvd_regions->mem_reg[i].size); if (len && !libie_pci_map_mmio_region(mmio, offset, len)) { idpf_decfg_lan_memory_regions(adapter); - return -EIO; + err = -EIO; + goto rel_rx_buf; } } +rel_rx_buf: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + return err; } @@ -1164,24 +716,43 @@ int idpf_add_del_fsteer_filters(struct idpf_adapter *adapter, struct virtchnl2_flow_rule_add_del *rule, enum virtchnl2_op opcode) { + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = opcode, + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + }; + struct virtchnl2_flow_rule_add_del *rx_rule; int rule_count = le32_to_cpu(rule->count); - struct idpf_vc_xn_params xn_params = {}; - ssize_t reply_sz; + size_t send_sz; + int err; if (opcode != VIRTCHNL2_OP_ADD_FLOW_RULE && - opcode != VIRTCHNL2_OP_DEL_FLOW_RULE) + opcode != VIRTCHNL2_OP_DEL_FLOW_RULE) { + kfree(rule); return -EINVAL; + } - xn_params.vc_op = opcode; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - xn_params.async = false; - xn_params.send_buf.iov_base = rule; - xn_params.send_buf.iov_len = struct_size(rule, rule_info, rule_count); - xn_params.recv_buf.iov_base = rule; - xn_params.recv_buf.iov_len = struct_size(rule, rule_info, rule_count); + send_sz = struct_size(rule, rule_info, rule_count); + err = idpf_send_mb_msg_kfree(adapter, &xn_params, rule, send_sz); + if (err) + return err; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - return reply_sz < 0 ? reply_sz : 0; + if (xn_params.recv_mem.iov_len < send_sz) { + err = -EIO; + goto rel_rx; + } + + rx_rule = xn_params.recv_mem.iov_base; + for (int i = 0; i < rule_count; i++) { + if (rx_rule->rule_info[i].status != + cpu_to_le32(VIRTCHNL2_FLOW_RULE_SUCCESS)) { + err = -EIO; + goto rel_rx; + } + } + +rel_rx: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + return err; } /** @@ -1556,11 +1127,13 @@ int idpf_queue_reg_init(struct idpf_vport *vport, int idpf_send_create_vport_msg(struct idpf_adapter *adapter, struct idpf_vport_max_q *max_q) { + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_CREATE_VPORT, + }; struct virtchnl2_create_vport *vport_msg; - struct idpf_vc_xn_params xn_params = {}; u16 idx = adapter->next_vport; int err, buf_size; - ssize_t reply_sz; buf_size = sizeof(struct virtchnl2_create_vport); vport_msg = kzalloc(buf_size, GFP_KERNEL); @@ -1587,33 +1160,29 @@ int idpf_send_create_vport_msg(struct idpf_adapter *adapter, } if (!adapter->vport_params_recvd[idx]) { - adapter->vport_params_recvd[idx] = kzalloc(IDPF_CTLQ_MAX_BUF_LEN, - GFP_KERNEL); + adapter->vport_params_recvd[idx] = + kzalloc(LIBIE_CTLQ_MAX_BUF_LEN, GFP_KERNEL); if (!adapter->vport_params_recvd[idx]) { err = -ENOMEM; goto rel_buf; } } - xn_params.vc_op = VIRTCHNL2_OP_CREATE_VPORT; - xn_params.send_buf.iov_base = vport_msg; - xn_params.send_buf.iov_len = buf_size; - xn_params.recv_buf.iov_base = adapter->vport_params_recvd[idx]; - xn_params.recv_buf.iov_len = IDPF_CTLQ_MAX_BUF_LEN; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) { - err = reply_sz; - goto free_vport_params; + err = idpf_send_mb_msg_kfree(adapter, &xn_params, vport_msg, + sizeof(*vport_msg)); + if (err) { + kfree(adapter->vport_params_recvd[idx]); + adapter->vport_params_recvd[idx] = NULL; + return err; } - kfree(vport_msg); + memcpy(adapter->vport_params_recvd[idx], xn_params.recv_mem.iov_base, + xn_params.recv_mem.iov_len); + + libie_ctlq_release_rx_buf(&xn_params.recv_mem); return 0; -free_vport_params: - kfree(adapter->vport_params_recvd[idx]); - adapter->vport_params_recvd[idx] = NULL; rel_buf: kfree(vport_msg); @@ -1675,19 +1244,22 @@ int idpf_check_supported_desc_ids(struct idpf_vport *vport) */ int idpf_send_destroy_vport_msg(struct idpf_adapter *adapter, u32 vport_id) { - struct idpf_vc_xn_params xn_params = {}; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_DESTROY_VPORT, + }; struct virtchnl2_vport v_id; - ssize_t reply_sz; + int err; v_id.vport_id = cpu_to_le32(vport_id); - xn_params.vc_op = VIRTCHNL2_OP_DESTROY_VPORT; - xn_params.send_buf.iov_base = &v_id; - xn_params.send_buf.iov_len = sizeof(v_id); - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); + err = idpf_send_mb_msg_stack(adapter, &xn_params, &v_id); + if (err) + return err; - return reply_sz < 0 ? reply_sz : 0; + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + + return 0; } /** @@ -1699,19 +1271,22 @@ int idpf_send_destroy_vport_msg(struct idpf_adapter *adapter, u32 vport_id) */ int idpf_send_enable_vport_msg(struct idpf_adapter *adapter, u32 vport_id) { - struct idpf_vc_xn_params xn_params = {}; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_ENABLE_VPORT, + }; struct virtchnl2_vport v_id; - ssize_t reply_sz; + int err; v_id.vport_id = cpu_to_le32(vport_id); - xn_params.vc_op = VIRTCHNL2_OP_ENABLE_VPORT; - xn_params.send_buf.iov_base = &v_id; - xn_params.send_buf.iov_len = sizeof(v_id); - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); + err = idpf_send_mb_msg_stack(adapter, &xn_params, &v_id); + if (err) + return err; - return reply_sz < 0 ? reply_sz : 0; + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + + return 0; } /** @@ -1723,19 +1298,22 @@ int idpf_send_enable_vport_msg(struct idpf_adapter *adapter, u32 vport_id) */ int idpf_send_disable_vport_msg(struct idpf_adapter *adapter, u32 vport_id) { - struct idpf_vc_xn_params xn_params = {}; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_DISABLE_VPORT, + }; struct virtchnl2_vport v_id; - ssize_t reply_sz; + int err; v_id.vport_id = cpu_to_le32(vport_id); - xn_params.vc_op = VIRTCHNL2_OP_DISABLE_VPORT; - xn_params.send_buf.iov_base = &v_id; - xn_params.send_buf.iov_len = sizeof(v_id); - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); + err = idpf_send_mb_msg_stack(adapter, &xn_params, &v_id); + if (err) + return err; - return reply_sz < 0 ? reply_sz : 0; + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + + return 0; } /** @@ -2574,11 +2152,14 @@ int idpf_send_delete_queues_msg(struct idpf_adapter *adapter, struct idpf_queue_id_reg_info *chunks, u32 vport_id) { - struct virtchnl2_del_ena_dis_queues *eq __free(kfree) = NULL; - struct idpf_vc_xn_params xn_params = {}; - ssize_t reply_sz; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_DEL_QUEUES, + }; + struct virtchnl2_del_ena_dis_queues *eq; + ssize_t buf_size; u16 num_chunks; - int buf_size; + int err; num_chunks = chunks->num_chunks; buf_size = struct_size(eq, chunks.chunks, num_chunks); @@ -2593,13 +2174,13 @@ int idpf_send_delete_queues_msg(struct idpf_adapter *adapter, idpf_convert_reg_to_queue_chunks(eq->chunks.chunks, chunks->queue_chunks, num_chunks); - xn_params.vc_op = VIRTCHNL2_OP_DEL_QUEUES; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - xn_params.send_buf.iov_base = eq; - xn_params.send_buf.iov_len = buf_size; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); + err = idpf_send_mb_msg_kfree(adapter, &xn_params, eq, buf_size); + if (err) + return err; - return reply_sz < 0 ? reply_sz : 0; + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + + return 0; } /** @@ -2637,15 +2218,14 @@ int idpf_send_add_queues_msg(struct idpf_adapter *adapter, struct idpf_q_vec_rsrc *rsrc, u32 vport_id) { - struct virtchnl2_add_queues *vc_msg __free(kfree) = NULL; - struct idpf_vc_xn_params xn_params = {}; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_ADD_QUEUES, + }; + struct virtchnl2_add_queues *vc_msg; struct virtchnl2_add_queues aq = {}; - ssize_t reply_sz; - int size; - - vc_msg = kzalloc(IDPF_CTLQ_MAX_BUF_LEN, GFP_KERNEL); - if (!vc_msg) - return -ENOMEM; + size_t size; + int err; aq.vport_id = cpu_to_le32(vport_id); aq.num_tx_q = cpu_to_le16(rsrc->num_txq); @@ -2653,29 +2233,38 @@ int idpf_send_add_queues_msg(struct idpf_adapter *adapter, aq.num_rx_q = cpu_to_le16(rsrc->num_rxq); aq.num_rx_bufq = cpu_to_le16(rsrc->num_bufq); - xn_params.vc_op = VIRTCHNL2_OP_ADD_QUEUES; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - xn_params.send_buf.iov_base = &aq; - xn_params.send_buf.iov_len = sizeof(aq); - xn_params.recv_buf.iov_base = vc_msg; - xn_params.recv_buf.iov_len = IDPF_CTLQ_MAX_BUF_LEN; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; + err = idpf_send_mb_msg_stack(adapter, &xn_params, &aq); + if (err) + return err; + + vc_msg = xn_params.recv_mem.iov_base; + if (xn_params.recv_mem.iov_len < sizeof(*vc_msg)) { + err = -EIO; + goto free_rx_buf; + } /* compare vc_msg num queues with vport num queues */ if (le16_to_cpu(vc_msg->num_tx_q) != rsrc->num_txq || le16_to_cpu(vc_msg->num_rx_q) != rsrc->num_rxq || le16_to_cpu(vc_msg->num_tx_complq) != rsrc->num_complq || - le16_to_cpu(vc_msg->num_rx_bufq) != rsrc->num_bufq) - return -EINVAL; + le16_to_cpu(vc_msg->num_rx_bufq) != rsrc->num_bufq) { + err = -EINVAL; + goto free_rx_buf; + } size = struct_size(vc_msg, chunks.chunks, le16_to_cpu(vc_msg->chunks.num_chunks)); - if (reply_sz < size) - return -EIO; + if (xn_params.recv_mem.iov_len < size) { + err = -EIO; + goto free_rx_buf; + } - return idpf_vport_init_queue_reg_chunks(vport_config, &vc_msg->chunks); + err = idpf_vport_init_queue_reg_chunks(vport_config, &vc_msg->chunks); + +free_rx_buf: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + + return err; } /** @@ -2687,49 +2276,51 @@ int idpf_send_add_queues_msg(struct idpf_adapter *adapter, */ int idpf_send_alloc_vectors_msg(struct idpf_adapter *adapter, u16 num_vectors) { - struct virtchnl2_alloc_vectors *rcvd_vec __free(kfree) = NULL; - struct idpf_vc_xn_params xn_params = {}; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_ALLOC_VECTORS, + }; + struct virtchnl2_alloc_vectors *rcvd_vec; struct virtchnl2_alloc_vectors ac = {}; - ssize_t reply_sz; u16 num_vchunks; - int size; + int size, err; ac.num_vectors = cpu_to_le16(num_vectors); - rcvd_vec = kzalloc(IDPF_CTLQ_MAX_BUF_LEN, GFP_KERNEL); - if (!rcvd_vec) - return -ENOMEM; + err = idpf_send_mb_msg_stack(adapter, &xn_params, &ac); + if (err) + return err; - xn_params.vc_op = VIRTCHNL2_OP_ALLOC_VECTORS; - xn_params.send_buf.iov_base = ∾ - xn_params.send_buf.iov_len = sizeof(ac); - xn_params.recv_buf.iov_base = rcvd_vec; - xn_params.recv_buf.iov_len = IDPF_CTLQ_MAX_BUF_LEN; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; + rcvd_vec = xn_params.recv_mem.iov_base; + if (xn_params.recv_mem.iov_len < sizeof(*rcvd_vec)) { + err = -EIO; + goto free_rx_buf; + } num_vchunks = le16_to_cpu(rcvd_vec->vchunks.num_vchunks); size = struct_size(rcvd_vec, vchunks.vchunks, num_vchunks); - if (reply_sz < size) - return -EIO; - - if (size > IDPF_CTLQ_MAX_BUF_LEN) - return -EINVAL; + if (xn_params.recv_mem.iov_len < size) { + err = -EIO; + goto free_rx_buf; + } kfree(adapter->req_vec_chunks); adapter->req_vec_chunks = kmemdup(rcvd_vec, size, GFP_KERNEL); - if (!adapter->req_vec_chunks) - return -ENOMEM; + if (!adapter->req_vec_chunks) { + err = -ENOMEM; + goto free_rx_buf; + } if (le16_to_cpu(adapter->req_vec_chunks->num_vectors) < num_vectors) { kfree(adapter->req_vec_chunks); adapter->req_vec_chunks = NULL; - return -EINVAL; + err = -EINVAL; } - return 0; +free_rx_buf: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + + return err; } /** @@ -2741,24 +2332,28 @@ int idpf_send_alloc_vectors_msg(struct idpf_adapter *adapter, u16 num_vectors) int idpf_send_dealloc_vectors_msg(struct idpf_adapter *adapter) { struct virtchnl2_alloc_vectors *ac = adapter->req_vec_chunks; - struct virtchnl2_vector_chunks *vcs = &ac->vchunks; - struct idpf_vc_xn_params xn_params = {}; - ssize_t reply_sz; - int buf_size; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_DEALLOC_VECTORS, + }; + struct virtchnl2_vector_chunks *vcs; + int buf_size, err; - buf_size = struct_size(vcs, vchunks, le16_to_cpu(vcs->num_vchunks)); + buf_size = struct_size(&ac->vchunks, vchunks, + le16_to_cpu(ac->vchunks.num_vchunks)); + vcs = kmemdup(&ac->vchunks, buf_size, GFP_KERNEL); + if (!vcs) + return -ENOMEM; - xn_params.vc_op = VIRTCHNL2_OP_DEALLOC_VECTORS; - xn_params.send_buf.iov_base = vcs; - xn_params.send_buf.iov_len = buf_size; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; + err = idpf_send_mb_msg_kfree(adapter, &xn_params, vcs, buf_size); + if (err) + return err; kfree(adapter->req_vec_chunks); adapter->req_vec_chunks = NULL; + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + return 0; } @@ -2782,18 +2377,22 @@ static int idpf_get_max_vfs(struct idpf_adapter *adapter) */ int idpf_send_set_sriov_vfs_msg(struct idpf_adapter *adapter, u16 num_vfs) { + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_SET_SRIOV_VFS, + }; struct virtchnl2_sriov_vfs_info svi = {}; - struct idpf_vc_xn_params xn_params = {}; - ssize_t reply_sz; + int err; svi.num_vfs = cpu_to_le16(num_vfs); - xn_params.vc_op = VIRTCHNL2_OP_SET_SRIOV_VFS; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - xn_params.send_buf.iov_base = &svi; - xn_params.send_buf.iov_len = sizeof(svi); - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - return reply_sz < 0 ? reply_sz : 0; + err = idpf_send_mb_msg_stack(adapter, &xn_params, &svi); + if (err) + return err; + + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + + return 0; } /** @@ -2806,10 +2405,14 @@ int idpf_send_set_sriov_vfs_msg(struct idpf_adapter *adapter, u16 num_vfs) int idpf_send_get_stats_msg(struct idpf_netdev_priv *np, struct idpf_port_stats *port_stats) { + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_GET_STATS, + }; struct rtnl_link_stats64 *netstats = &np->netstats; + struct virtchnl2_vport_stats *stats_recv; struct virtchnl2_vport_stats stats_msg = {}; - struct idpf_vc_xn_params xn_params = {}; - ssize_t reply_sz; + int err; /* Don't send get_stats message if the link is down */ @@ -2818,38 +2421,40 @@ int idpf_send_get_stats_msg(struct idpf_netdev_priv *np, stats_msg.vport_id = cpu_to_le32(np->vport_id); - xn_params.vc_op = VIRTCHNL2_OP_GET_STATS; - xn_params.send_buf.iov_base = &stats_msg; - xn_params.send_buf.iov_len = sizeof(stats_msg); - xn_params.recv_buf = xn_params.send_buf; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; + err = idpf_send_mb_msg_stack(np->adapter, &xn_params, &stats_msg); + if (err) + return err; - reply_sz = idpf_vc_xn_exec(np->adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - if (reply_sz < sizeof(stats_msg)) - return -EIO; + if (xn_params.recv_mem.iov_len < sizeof(*stats_recv)) { + err = -EIO; + goto free_rx_buf; + } + + stats_recv = xn_params.recv_mem.iov_base; spin_lock_bh(&np->stats_lock); - netstats->rx_packets = le64_to_cpu(stats_msg.rx_unicast) + - le64_to_cpu(stats_msg.rx_multicast) + - le64_to_cpu(stats_msg.rx_broadcast); - netstats->tx_packets = le64_to_cpu(stats_msg.tx_unicast) + - le64_to_cpu(stats_msg.tx_multicast) + - le64_to_cpu(stats_msg.tx_broadcast); - netstats->rx_bytes = le64_to_cpu(stats_msg.rx_bytes); - netstats->tx_bytes = le64_to_cpu(stats_msg.tx_bytes); - netstats->rx_errors = le64_to_cpu(stats_msg.rx_errors); - netstats->tx_errors = le64_to_cpu(stats_msg.tx_errors); - netstats->rx_dropped = le64_to_cpu(stats_msg.rx_discards); - netstats->tx_dropped = le64_to_cpu(stats_msg.tx_discards); + netstats->rx_packets = le64_to_cpu(stats_recv->rx_unicast) + + le64_to_cpu(stats_recv->rx_multicast) + + le64_to_cpu(stats_recv->rx_broadcast); + netstats->tx_packets = le64_to_cpu(stats_recv->tx_unicast) + + le64_to_cpu(stats_recv->tx_multicast) + + le64_to_cpu(stats_recv->tx_broadcast); + netstats->rx_bytes = le64_to_cpu(stats_recv->rx_bytes); + netstats->tx_bytes = le64_to_cpu(stats_recv->tx_bytes); + netstats->rx_errors = le64_to_cpu(stats_recv->rx_errors); + netstats->tx_errors = le64_to_cpu(stats_recv->tx_errors); + netstats->rx_dropped = le64_to_cpu(stats_recv->rx_discards); + netstats->tx_dropped = le64_to_cpu(stats_recv->tx_discards); - port_stats->vport_stats = stats_msg; + port_stats->vport_stats = *stats_recv; spin_unlock_bh(&np->stats_lock); - return 0; +free_rx_buf: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + + return err; } /** @@ -2867,13 +2472,14 @@ int idpf_send_get_stats_msg(struct idpf_netdev_priv *np, int idpf_send_set_rss_lut_msg(struct idpf_adapter *adapter, struct idpf_rss_data *rss_data, u32 vport_id) { - struct virtchnl2_rss_lut *rl __free(kfree) = NULL; - struct idpf_vc_xn_params xn_params = {}; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_SET_RSS_LUT, + }; + struct virtchnl2_rss_lut *rl; struct idpf_vport *vport; - ssize_t reply_sz; + int buf_size, i, err; bool rxhash_ena; - int buf_size; - int i; vport = idpf_vid_to_vport(adapter, vport_id); if (!vport) @@ -2887,21 +2493,17 @@ int idpf_send_set_rss_lut_msg(struct idpf_adapter *adapter, return -ENOMEM; rl->vport_id = cpu_to_le32(vport_id); - - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - xn_params.send_buf.iov_base = rl; - xn_params.send_buf.iov_len = buf_size; - xn_params.vc_op = VIRTCHNL2_OP_SET_RSS_LUT; - rl->lut_entries = cpu_to_le16(rss_data->rss_lut_size); for (i = 0; i < rss_data->rss_lut_size; i++) rl->lut[i] = rxhash_ena ? cpu_to_le32(rss_data->rss_lut[i]) : 0; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; + err = idpf_send_mb_msg_kfree(adapter, &xn_params, rl, buf_size); + if (err) + return err; - return 0; + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + + return err; } /** @@ -2915,10 +2517,12 @@ int idpf_send_set_rss_lut_msg(struct idpf_adapter *adapter, int idpf_send_set_rss_key_msg(struct idpf_adapter *adapter, struct idpf_rss_data *rss_data, u32 vport_id) { - struct virtchnl2_rss_key *rk __free(kfree) = NULL; - struct idpf_vc_xn_params xn_params = {}; - ssize_t reply_sz; - int i, buf_size; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_SET_RSS_KEY, + }; + struct virtchnl2_rss_key *rk; + int i, buf_size, err; buf_size = struct_size(rk, key_flex, rss_data->rss_key_size); rk = kzalloc(buf_size, GFP_KERNEL); @@ -2926,20 +2530,17 @@ int idpf_send_set_rss_key_msg(struct idpf_adapter *adapter, return -ENOMEM; rk->vport_id = cpu_to_le32(vport_id); - xn_params.send_buf.iov_base = rk; - xn_params.send_buf.iov_len = buf_size; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - xn_params.vc_op = VIRTCHNL2_OP_SET_RSS_KEY; - rk->key_len = cpu_to_le16(rss_data->rss_key_size); for (i = 0; i < rss_data->rss_key_size; i++) rk->key_flex[i] = rss_data->rss_key[i]; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; + err = idpf_send_mb_msg_kfree(adapter, &xn_params, rk, buf_size); + if (err) + return err; - return 0; + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + + return err; } /** @@ -3116,60 +2717,63 @@ static void idpf_parse_protocol_ids(struct virtchnl2_ptype *ptype, */ static int idpf_send_get_rx_ptype_msg(struct idpf_adapter *adapter) { - struct virtchnl2_get_ptype_info *get_ptype_info __free(kfree) = NULL; - struct virtchnl2_get_ptype_info *ptype_info __free(kfree) = NULL; - struct libeth_rx_pt *singleq_pt_lkup __free(kfree) = NULL; - struct libeth_rx_pt *splitq_pt_lkup __free(kfree) = NULL; - struct idpf_vc_xn_params xn_params = {}; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_GET_PTYPE_INFO, + }; + struct virtchnl2_get_ptype_info *get_ptype_info; + struct virtchnl2_get_ptype_info *ptype_info; + int err = 0, max_ptype = IDPF_RX_MAX_PTYPE; + int buf_size = sizeof(*get_ptype_info); + struct libeth_rx_pt *singleq_pt_lkup; + struct libeth_rx_pt *splitq_pt_lkup; int ptypes_recvd = 0, ptype_offset; - u32 max_ptype = IDPF_RX_MAX_PTYPE; u16 next_ptype_id = 0; - ssize_t reply_sz; singleq_pt_lkup = kzalloc_objs(*singleq_pt_lkup, IDPF_RX_MAX_BASE_PTYPE); if (!singleq_pt_lkup) return -ENOMEM; splitq_pt_lkup = kzalloc_objs(*splitq_pt_lkup, max_ptype); - if (!splitq_pt_lkup) - return -ENOMEM; - - get_ptype_info = kzalloc_obj(*get_ptype_info); - if (!get_ptype_info) - return -ENOMEM; - - ptype_info = kzalloc(IDPF_CTLQ_MAX_BUF_LEN, GFP_KERNEL); - if (!ptype_info) - return -ENOMEM; - - xn_params.vc_op = VIRTCHNL2_OP_GET_PTYPE_INFO; - xn_params.send_buf.iov_base = get_ptype_info; - xn_params.send_buf.iov_len = sizeof(*get_ptype_info); - xn_params.recv_buf.iov_base = ptype_info; - xn_params.recv_buf.iov_len = IDPF_CTLQ_MAX_BUF_LEN; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; + if (!splitq_pt_lkup) { + err = -ENOMEM; + goto free_singleq; + } while (next_ptype_id < max_ptype) { + u16 num_ptypes; + + get_ptype_info = kzalloc(buf_size, GFP_KERNEL); + if (!get_ptype_info) { + err = -ENOMEM; + goto free_splitq; + } + get_ptype_info->start_ptype_id = cpu_to_le16(next_ptype_id); if ((next_ptype_id + IDPF_RX_MAX_PTYPES_PER_BUF) > max_ptype) - get_ptype_info->num_ptypes = - cpu_to_le16(max_ptype - next_ptype_id); + num_ptypes = max_ptype - next_ptype_id; else - get_ptype_info->num_ptypes = - cpu_to_le16(IDPF_RX_MAX_PTYPES_PER_BUF); + num_ptypes = IDPF_RX_MAX_PTYPES_PER_BUF; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; + get_ptype_info->num_ptypes = cpu_to_le16(num_ptypes); + err = idpf_send_mb_msg_kfree(adapter, &xn_params, + get_ptype_info, buf_size); + if (err) + goto free_splitq; + ptype_info = xn_params.recv_mem.iov_base; + if (xn_params.recv_mem.iov_len < sizeof(*ptype_info)) { + err = -EIO; + goto free_rx_buf; + } ptypes_recvd += le16_to_cpu(ptype_info->num_ptypes); - if (ptypes_recvd > max_ptype) - return -EINVAL; - - next_ptype_id = le16_to_cpu(get_ptype_info->start_ptype_id) + - le16_to_cpu(get_ptype_info->num_ptypes); + if (ptypes_recvd > max_ptype) { + err = -EINVAL; + goto free_rx_buf; + } + next_ptype_id = next_ptype_id + num_ptypes; ptype_offset = IDPF_RX_PTYPE_HDR_SZ; for (u16 i = 0; i < le16_to_cpu(ptype_info->num_ptypes); i++) { @@ -3179,19 +2783,28 @@ static int idpf_send_get_rx_ptype_msg(struct idpf_adapter *adapter) ptype = (struct virtchnl2_ptype *) ((u8 *)ptype_info + ptype_offset); + if (xn_params.recv_mem.iov_len < + ptype_offset + sizeof(struct virtchnl2_ptype)) { + err = -EINVAL; + goto free_rx_buf; + } pt_10 = le16_to_cpu(ptype->ptype_id_10); pt_8 = ptype->ptype_id_8; ptype_offset += IDPF_GET_PTYPE_SIZE(ptype); - if (ptype_offset > IDPF_CTLQ_MAX_BUF_LEN) - return -EINVAL; + if (xn_params.recv_mem.iov_len < ptype_offset) { + err = -EINVAL; + goto free_rx_buf; + } /* 0xFFFF indicates end of ptypes */ if (pt_10 == IDPF_INVALID_PTYPE_ID) goto out; - if (pt_10 >= max_ptype) - return -EINVAL; + if (pt_10 >= max_ptype) { + err = -EINVAL; + goto free_rx_buf; + } idpf_parse_protocol_ids(ptype, &rx_pt); idpf_finalize_ptype_lookup(&rx_pt); @@ -3205,13 +2818,24 @@ static int idpf_send_get_rx_ptype_msg(struct idpf_adapter *adapter) if (!singleq_pt_lkup[pt_8].outer_ip) singleq_pt_lkup[pt_8] = rx_pt; } + + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + xn_params.recv_mem = (struct kvec) {}; } out: - adapter->splitq_pt_lkup = no_free_ptr(splitq_pt_lkup); - adapter->singleq_pt_lkup = no_free_ptr(singleq_pt_lkup); + adapter->splitq_pt_lkup = splitq_pt_lkup; + adapter->singleq_pt_lkup = singleq_pt_lkup; + splitq_pt_lkup = NULL; + singleq_pt_lkup = NULL; +free_rx_buf: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); +free_splitq: + kfree(splitq_pt_lkup); +free_singleq: + kfree(singleq_pt_lkup); - return 0; + return err; } /** @@ -3239,40 +2863,23 @@ static void idpf_rel_rx_pt_lkup(struct idpf_adapter *adapter) int idpf_send_ena_dis_loopback_msg(struct idpf_adapter *adapter, u32 vport_id, bool loopback_ena) { - struct idpf_vc_xn_params xn_params = {}; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_LOOPBACK, + }; struct virtchnl2_loopback loopback; - ssize_t reply_sz; + int err; loopback.vport_id = cpu_to_le32(vport_id); loopback.enable = loopback_ena; - xn_params.vc_op = VIRTCHNL2_OP_LOOPBACK; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - xn_params.send_buf.iov_base = &loopback; - xn_params.send_buf.iov_len = sizeof(loopback); - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); + err = idpf_send_mb_msg_stack(adapter, &xn_params, &loopback); + if (err) + return err; - return reply_sz < 0 ? reply_sz : 0; -} + libie_ctlq_release_rx_buf(&xn_params.recv_mem); -/** - * idpf_find_ctlq - Given a type and id, find ctlq info - * @hw: hardware struct - * @type: type of ctrlq to find - * @id: ctlq id to find - * - * Returns pointer to found ctlq info struct, NULL otherwise. - */ -static struct idpf_ctlq_info *idpf_find_ctlq(struct idpf_hw *hw, - enum idpf_ctlq_type type, int id) -{ - struct idpf_ctlq_info *cq, *tmp; - - list_for_each_entry_safe(cq, tmp, &hw->cq_list_head, cq_list) - if (cq->q_id == id && cq->cq_type == type) - return cq; - - return NULL; + return 0; } /** @@ -3283,40 +2890,45 @@ static struct idpf_ctlq_info *idpf_find_ctlq(struct idpf_hw *hw, */ int idpf_init_dflt_mbx(struct idpf_adapter *adapter) { - struct idpf_ctlq_create_info ctlq_info[] = { + struct libie_ctlq_ctx *ctx = &adapter->ctlq_ctx; + struct libie_ctlq_create_info ctlq_info[] = { { - .type = IDPF_CTLQ_TYPE_MAILBOX_TX, - .id = IDPF_DFLT_MBX_ID, + .type = LIBIE_CTLQ_TYPE_TX, + .id = LIBIE_CTLQ_MBX_ID, .len = IDPF_DFLT_MBX_Q_LEN, - .buf_size = IDPF_CTLQ_MAX_BUF_LEN }, { - .type = IDPF_CTLQ_TYPE_MAILBOX_RX, - .id = IDPF_DFLT_MBX_ID, + .type = LIBIE_CTLQ_TYPE_RX, + .id = LIBIE_CTLQ_MBX_ID, .len = IDPF_DFLT_MBX_Q_LEN, - .buf_size = IDPF_CTLQ_MAX_BUF_LEN } }; - struct idpf_hw *hw = &adapter->hw; + struct libie_ctlq_xn_init_params params = { + .num_qs = IDPF_NUM_DFLT_MBX_Q, + .cctlq_info = ctlq_info, + .ctx = ctx, + }; int err; - adapter->dev_ops.reg_ops.ctlq_reg_init(adapter, ctlq_info); + adapter->dev_ops.reg_ops.ctlq_reg_init(&ctx->mmio_info, + params.cctlq_info); - err = idpf_ctlq_init(hw, IDPF_NUM_DFLT_MBX_Q, ctlq_info); + err = libie_ctlq_xn_init(¶ms); if (err) return err; - hw->asq = idpf_find_ctlq(hw, IDPF_CTLQ_TYPE_MAILBOX_TX, - IDPF_DFLT_MBX_ID); - hw->arq = idpf_find_ctlq(hw, IDPF_CTLQ_TYPE_MAILBOX_RX, - IDPF_DFLT_MBX_ID); - - if (!hw->asq || !hw->arq) { - idpf_ctlq_deinit(hw); - + adapter->asq = libie_find_ctlq(ctx, LIBIE_CTLQ_TYPE_TX, + LIBIE_CTLQ_MBX_ID); + adapter->arq = libie_find_ctlq(ctx, LIBIE_CTLQ_TYPE_RX, + LIBIE_CTLQ_MBX_ID); + if (!adapter->asq || !adapter->arq) { + adapter->asq = NULL; + adapter->arq = NULL; + libie_ctlq_xn_deinit(params.xnm, ctx); return -ENOENT; } + adapter->xnm = params.xnm; adapter->state = __IDPF_VER_CHECK; return 0; @@ -3328,12 +2940,15 @@ int idpf_init_dflt_mbx(struct idpf_adapter *adapter) */ void idpf_deinit_dflt_mbx(struct idpf_adapter *adapter) { - if (adapter->hw.arq && adapter->hw.asq) { - idpf_mb_clean(adapter, adapter->hw.asq); - idpf_ctlq_deinit(&adapter->hw); + if (adapter->xnm) { + libie_ctlq_xn_shutdown(adapter->xnm); + idpf_mb_clean(adapter->asq, true); + libie_ctlq_xn_deinit(adapter->xnm, &adapter->ctlq_ctx); } - adapter->hw.arq = NULL; - adapter->hw.asq = NULL; + + adapter->arq = NULL; + adapter->asq = NULL; + adapter->xnm = NULL; } /** @@ -3404,15 +3019,6 @@ int idpf_vc_core_init(struct idpf_adapter *adapter) u16 num_max_vports; int err = 0; - if (!adapter->vcxn_mngr) { - adapter->vcxn_mngr = kzalloc_obj(*adapter->vcxn_mngr); - if (!adapter->vcxn_mngr) { - err = -ENOMEM; - goto init_failed; - } - } - idpf_vc_xn_init(adapter->vcxn_mngr); - while (adapter->state != __IDPF_INIT_SW) { switch (adapter->state) { case __IDPF_VER_CHECK: @@ -3564,8 +3170,7 @@ int idpf_vc_core_init(struct idpf_adapter *adapter) * the mailbox again */ adapter->state = __IDPF_VER_CHECK; - if (adapter->vcxn_mngr) - idpf_vc_xn_shutdown(adapter->vcxn_mngr); + libie_ctlq_xn_shutdown(adapter->xnm); set_bit(IDPF_HR_DRV_LOAD, adapter->flags); queue_delayed_work(adapter->vc_event_wq, &adapter->vc_event_task, msecs_to_jiffies(task_delay)); @@ -3588,7 +3193,7 @@ void idpf_vc_core_deinit(struct idpf_adapter *adapter) /* Avoid transaction timeouts when called during reset */ remove_in_prog = test_bit(IDPF_REMOVE_IN_PROG, adapter->flags); if (!remove_in_prog) - idpf_vc_xn_shutdown(adapter->vcxn_mngr); + libie_ctlq_xn_shutdown(adapter->xnm); idpf_ptp_release(adapter); idpf_deinit_task(adapter); @@ -3597,7 +3202,7 @@ void idpf_vc_core_deinit(struct idpf_adapter *adapter) idpf_intr_rel(adapter); if (remove_in_prog) - idpf_vc_xn_shutdown(adapter->vcxn_mngr); + libie_ctlq_xn_shutdown(adapter->xnm); cancel_delayed_work_sync(&adapter->serv_task); cancel_delayed_work_sync(&adapter->mbx_task); @@ -4134,9 +3739,9 @@ static void idpf_set_mac_type(const u8 *default_mac_addr, /** * idpf_mac_filter_async_handler - Async callback for mac filters - * @adapter: private data struct - * @xn: transaction for message - * @ctlq_msg: received message + * @ctx: controlq context structure + * @buff: response buffer pointer and size + * @status: async call return value * * In some scenarios driver can't sleep and wait for a reply (e.g.: stack is * holding rtnl_lock) when adding a new mac filter. It puts us in a difficult @@ -4144,13 +3749,14 @@ static void idpf_set_mac_type(const u8 *default_mac_addr, * ultimately do is remove it from our list of mac filters and report the * error. */ -static int idpf_mac_filter_async_handler(struct idpf_adapter *adapter, - struct idpf_vc_xn *xn, - const struct idpf_ctlq_msg *ctlq_msg) +static void idpf_mac_filter_async_handler(void *ctx, + struct kvec *buff, + int status) { struct virtchnl2_mac_addr_list *ma_list; struct idpf_vport_config *vport_config; struct virtchnl2_mac_addr *mac_addr; + struct idpf_adapter *adapter = ctx; struct idpf_mac_filter *f, *tmp; struct list_head *ma_list_head; struct idpf_vport *vport; @@ -4158,18 +3764,18 @@ static int idpf_mac_filter_async_handler(struct idpf_adapter *adapter, int i; /* if success we're done, we're only here if something bad happened */ - if (!ctlq_msg->cookie.mbx.chnl_retval) - return 0; + if (!status || status == -ETIMEDOUT) + return; + ma_list = buff->iov_base; /* make sure at least struct is there */ - if (xn->reply_sz < sizeof(*ma_list)) + if (buff->iov_len < sizeof(*ma_list)) goto invalid_payload; - ma_list = ctlq_msg->ctx.indirect.payload->va; mac_addr = ma_list->mac_addr_list; num_entries = le16_to_cpu(ma_list->num_mac_addr); /* we should have received a buffer at least this big */ - if (xn->reply_sz < struct_size(ma_list, mac_addr_list, num_entries)) + if (buff->iov_len < struct_size(ma_list, mac_addr_list, num_entries)) goto invalid_payload; vport = idpf_vid_to_vport(adapter, le32_to_cpu(ma_list->vport_id)); @@ -4189,16 +3795,13 @@ static int idpf_mac_filter_async_handler(struct idpf_adapter *adapter, if (ether_addr_equal(mac_addr[i].addr, f->macaddr)) list_del(&f->list); spin_unlock_bh(&vport_config->mac_filter_list_lock); - dev_err_ratelimited(&adapter->pdev->dev, "Received error sending MAC filter request (op %d)\n", - xn->vc_op); - - return 0; + dev_err_ratelimited(&adapter->pdev->dev, "Received error %d on sending MAC filter request\n", + status); + return; invalid_payload: - dev_err_ratelimited(&adapter->pdev->dev, "Received invalid MAC filter payload (op %d) (len %zd)\n", - xn->vc_op, xn->reply_sz); - - return -EINVAL; + dev_err_ratelimited(&adapter->pdev->dev, "Received invalid MAC filter payload (len %zd)\n", + buff->iov_len); } /** @@ -4217,19 +3820,21 @@ int idpf_add_del_mac_filters(struct idpf_adapter *adapter, const u8 *default_mac_addr, u32 vport_id, bool add, bool async) { - struct virtchnl2_mac_addr_list *ma_list __free(kfree) = NULL; struct virtchnl2_mac_addr *mac_addr __free(kfree) = NULL; - struct idpf_vc_xn_params xn_params = {}; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = add ? VIRTCHNL2_OP_ADD_MAC_ADDR : + VIRTCHNL2_OP_DEL_MAC_ADDR, + }; + struct virtchnl2_mac_addr_list *ma_list; u32 num_msgs, total_filters = 0; struct idpf_mac_filter *f; - ssize_t reply_sz; - int i = 0, k; + int i = 0; - xn_params.vc_op = add ? VIRTCHNL2_OP_ADD_MAC_ADDR : - VIRTCHNL2_OP_DEL_MAC_ADDR; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - xn_params.async = async; - xn_params.async_handler = idpf_mac_filter_async_handler; + if (async) { + xn_params.resp_cb = idpf_mac_filter_async_handler; + xn_params.send_ctx = adapter; + } spin_lock_bh(&vport_config->mac_filter_list_lock); @@ -4284,32 +3889,31 @@ int idpf_add_del_mac_filters(struct idpf_adapter *adapter, */ num_msgs = DIV_ROUND_UP(total_filters, IDPF_NUM_FILTERS_PER_MSG); - for (i = 0, k = 0; i < num_msgs; i++) { - u32 entries_size, buf_size, num_entries; + for (u32 i = 0, k = 0; i < num_msgs; i++) { + u32 entries_size, num_entries; + size_t buf_size; + int err; num_entries = min_t(u32, total_filters, IDPF_NUM_FILTERS_PER_MSG); entries_size = sizeof(struct virtchnl2_mac_addr) * num_entries; buf_size = struct_size(ma_list, mac_addr_list, num_entries); - if (!ma_list || num_entries != IDPF_NUM_FILTERS_PER_MSG) { - kfree(ma_list); - ma_list = kzalloc(buf_size, GFP_ATOMIC); - if (!ma_list) - return -ENOMEM; - } else { - memset(ma_list, 0, buf_size); - } + ma_list = kzalloc(buf_size, GFP_ATOMIC); + if (!ma_list) + return -ENOMEM; ma_list->vport_id = cpu_to_le32(vport_id); ma_list->num_mac_addr = cpu_to_le16(num_entries); memcpy(ma_list->mac_addr_list, &mac_addr[k], entries_size); - xn_params.send_buf.iov_base = ma_list; - xn_params.send_buf.iov_len = buf_size; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; + err = idpf_send_mb_msg_kfree(adapter, &xn_params, ma_list, + buf_size); + if (err) + return err; + + if (!async) + libie_ctlq_release_rx_buf(&xn_params.recv_mem); k += num_entries; total_filters -= num_entries; @@ -4318,6 +3922,26 @@ int idpf_add_del_mac_filters(struct idpf_adapter *adapter, return 0; } +/** + * idpf_promiscuous_async_handler - async callback for promiscuous mode + * @ctx: controlq context structure + * @buff: response buffer pointer and size + * @status: async call return value + * + * Nobody is waiting for the promiscuous virtchnl message response. Print + * an error message if something went wrong and return. + */ +static void idpf_promiscuous_async_handler(void *ctx, + struct kvec *buff, + int status) +{ + struct idpf_adapter *adapter = ctx; + + if (status) + dev_err_ratelimited(&adapter->pdev->dev, "Failed to set promiscuous mode: %d\n", + status); +} + /** * idpf_set_promiscuous - set promiscuous and send message to mailbox * @adapter: Driver specific private structure @@ -4332,9 +3956,13 @@ int idpf_set_promiscuous(struct idpf_adapter *adapter, struct idpf_vport_user_config_data *config_data, u32 vport_id) { - struct idpf_vc_xn_params xn_params = {}; + struct libie_ctlq_xn_send_params xn_params = { + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + .chnl_opcode = VIRTCHNL2_OP_CONFIG_PROMISCUOUS_MODE, + .resp_cb = idpf_promiscuous_async_handler, + .send_ctx = adapter, + }; struct virtchnl2_promisc_info vpi; - ssize_t reply_sz; u16 flags = 0; if (test_bit(__IDPF_PROMISC_UC, config_data->user_flags)) @@ -4345,15 +3973,7 @@ int idpf_set_promiscuous(struct idpf_adapter *adapter, vpi.vport_id = cpu_to_le32(vport_id); vpi.flags = cpu_to_le16(flags); - xn_params.vc_op = VIRTCHNL2_OP_CONFIG_PROMISCUOUS_MODE; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - xn_params.send_buf.iov_base = &vpi; - xn_params.send_buf.iov_len = sizeof(vpi); - /* setting promiscuous is only ever done asynchronously */ - xn_params.async = true; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - - return reply_sz < 0 ? reply_sz : 0; + return idpf_send_mb_msg_stack(adapter, &xn_params, &vpi); } /** @@ -4362,7 +3982,7 @@ int idpf_set_promiscuous(struct idpf_adapter *adapter, * @send_msg: message to send * @msg_size: size of message to send * @recv_msg: message to populate on reception of response - * @recv_len: length of message copied into recv_msg or 0 on error + * @recv_len: on input, maximum response size; on success, actual response size * * Return: 0 on success or error code on failure. */ @@ -4371,26 +3991,39 @@ int idpf_idc_rdma_vc_send_sync(struct iidc_rdma_core_dev_info *cdev_info, u8 *recv_msg, u16 *recv_len) { struct idpf_adapter *adapter = pci_get_drvdata(cdev_info->pdev); - struct idpf_vc_xn_params xn_params = { }; - ssize_t reply_sz; - u16 recv_size; + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = VIRTCHNL2_OP_RDMA, + .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, + }; + u8 on_stack_buf[LIBIE_CP_TX_COPYBREAK]; + void *send_buf; + int err; - if (!recv_msg || !recv_len || msg_size > IDPF_CTLQ_MAX_BUF_LEN) + if (!recv_msg || !recv_len || msg_size > LIBIE_CTLQ_MAX_BUF_LEN) return -EINVAL; - recv_size = min_t(u16, *recv_len, IDPF_CTLQ_MAX_BUF_LEN); - *recv_len = 0; - xn_params.vc_op = VIRTCHNL2_OP_RDMA; - xn_params.timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC; - xn_params.send_buf.iov_base = send_msg; - xn_params.send_buf.iov_len = msg_size; - xn_params.recv_buf.iov_base = recv_msg; - xn_params.recv_buf.iov_len = recv_size; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - *recv_len = reply_sz; + if (!libie_cp_can_send_onstack(msg_size)) { + send_buf = kzalloc(msg_size, GFP_KERNEL); + if (!send_buf) + return -ENOMEM; + } else { + send_buf = on_stack_buf; + } - return 0; + memcpy(send_buf, send_msg, msg_size); + err = idpf_send_mb_msg(adapter, &xn_params, send_buf, msg_size); + if (err) + return err; + + if (xn_params.recv_mem.iov_len > *recv_len) { + err = -EINVAL; + goto rel_buf; + } + + *recv_len = xn_params.recv_mem.iov_len; + memcpy(recv_msg, xn_params.recv_mem.iov_base, *recv_len); +rel_buf: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + return err; } EXPORT_SYMBOL_GPL(idpf_idc_rdma_vc_send_sync); diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h index 5c634cbf1e07..5d27805ff40f 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h @@ -7,86 +7,6 @@ #include #define IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC (60 * 1000) -#define IDPF_VC_XN_IDX_M GENMASK(7, 0) -#define IDPF_VC_XN_SALT_M GENMASK(15, 8) -#define IDPF_VC_XN_RING_LEN U8_MAX - -/** - * enum idpf_vc_xn_state - Virtchnl transaction status - * @IDPF_VC_XN_IDLE: not expecting a reply, ready to be used - * @IDPF_VC_XN_WAITING: expecting a reply, not yet received - * @IDPF_VC_XN_COMPLETED_SUCCESS: a reply was expected and received, buffer - * updated - * @IDPF_VC_XN_COMPLETED_FAILED: a reply was expected and received, but there - * was an error, buffer not updated - * @IDPF_VC_XN_SHUTDOWN: transaction object cannot be used, VC torn down - * @IDPF_VC_XN_ASYNC: transaction sent asynchronously and doesn't have the - * return context; a callback may be provided to handle - * return - */ -enum idpf_vc_xn_state { - IDPF_VC_XN_IDLE = 1, - IDPF_VC_XN_WAITING, - IDPF_VC_XN_COMPLETED_SUCCESS, - IDPF_VC_XN_COMPLETED_FAILED, - IDPF_VC_XN_SHUTDOWN, - IDPF_VC_XN_ASYNC, -}; - -struct idpf_vc_xn; -/* Callback for asynchronous messages */ -typedef int (*async_vc_cb) (struct idpf_adapter *, struct idpf_vc_xn *, - const struct idpf_ctlq_msg *); - -/** - * struct idpf_vc_xn - Data structure representing virtchnl transactions - * @completed: virtchnl event loop uses that to signal when a reply is - * available, uses kernel completion API - * @lock: protects the transaction state fields below - * @state: virtchnl event loop stores the data below, protected by @lock - * @reply_sz: Original size of reply, may be > reply_buf.iov_len; it will be - * truncated on its way to the receiver thread according to - * reply_buf.iov_len. - * @reply: Reference to the buffer(s) where the reply data should be written - * to. May be 0-length (then NULL address permitted) if the reply data - * should be ignored. - * @async_handler: if sent asynchronously, a callback can be provided to handle - * the reply when it's received - * @vc_op: corresponding opcode sent with this transaction - * @idx: index used as retrieval on reply receive, used for cookie - * @salt: changed every message to make unique, used for cookie - */ -struct idpf_vc_xn { - struct completion completed; - spinlock_t lock; - enum idpf_vc_xn_state state; - size_t reply_sz; - struct kvec reply; - async_vc_cb async_handler; - u32 vc_op; - u8 idx; - u8 salt; -}; - -/** - * struct idpf_vc_xn_params - Parameters for executing transaction - * @send_buf: kvec for send buffer - * @recv_buf: kvec for recv buffer, may be NULL, must then have zero length - * @timeout_ms: timeout to wait for reply - * @async: send message asynchronously, will not wait on completion - * @async_handler: If sent asynchronously, optional callback handler. The user - * must be careful when using async handlers as the memory for - * the recv_buf _cannot_ be on stack if this is async. - * @vc_op: virtchnl op to send - */ -struct idpf_vc_xn_params { - struct kvec send_buf; - struct kvec recv_buf; - int timeout_ms; - bool async; - async_vc_cb async_handler; - u32 vc_op; -}; struct idpf_adapter; struct idpf_netdev_priv; @@ -96,8 +16,6 @@ struct idpf_vport_max_q; struct idpf_vport_config; struct idpf_vport_user_config_data; -ssize_t idpf_vc_xn_exec(struct idpf_adapter *adapter, - const struct idpf_vc_xn_params *params); int idpf_init_dflt_mbx(struct idpf_adapter *adapter); void idpf_deinit_dflt_mbx(struct idpf_adapter *adapter); int idpf_vc_core_init(struct idpf_adapter *adapter); @@ -124,12 +42,36 @@ bool idpf_sideband_action_ena(struct idpf_vport *vport, struct ethtool_rx_flow_spec *fsp); unsigned int idpf_fsteer_max_rules(struct idpf_vport *vport); -int idpf_recv_mb_msg(struct idpf_adapter *adapter, struct idpf_ctlq_info *arq); -int idpf_send_mb_msg(struct idpf_adapter *adapter, struct idpf_ctlq_info *asq, - u32 op, u16 msg_size, u8 *msg, u16 cookie); +void idpf_recv_event_msg(struct libie_ctlq_ctx *ctx, + struct libie_ctlq_msg *ctlq_msg); +int idpf_send_mb_msg(struct idpf_adapter *adapter, + struct libie_ctlq_xn_send_params *xn_params, + void *send_buf, size_t send_buf_size); +int idpf_send_mb_msg_kfree(struct idpf_adapter *adapter, + struct libie_ctlq_xn_send_params *xn_params, + void *send_buf, size_t send_buf_size); +void idpf_send_vf_reset_msg(struct idpf_adapter *adapter); bool idpf_mmio_region_non_static(struct libie_mmio_info *mmio_info, struct libie_pci_mmio_region *reg); +/** + * idpf_send_mb_msg_stack - send a mailbox message from an on-stack buffer + * @adapter: driver specific private structure + * @xn_params: Xn send parameters to fill + * @ptr: pointer to the on-stack message object to send + * + * Send size is deduced based on the pointer type. + * + * Return: %0 on success, -%errno on failure. + */ +#define idpf_send_mb_msg_stack(adapter, xn_params, ptr) \ +({ \ + typeof(ptr) __ptr = (ptr); \ + \ + static_assert(sizeof(*__ptr) <= LIBIE_CP_TX_COPYBREAK); \ + idpf_send_mb_msg(adapter, xn_params, __ptr, sizeof(*__ptr)); \ +}) + struct idpf_queue_ptr { enum virtchnl2_queue_type type; union { @@ -214,7 +156,6 @@ int idpf_send_set_rss_key_msg(struct idpf_adapter *adapter, struct idpf_rss_data *rss_data, u32 vport_id); int idpf_send_set_rss_lut_msg(struct idpf_adapter *adapter, struct idpf_rss_data *rss_data, u32 vport_id); -void idpf_vc_xn_shutdown(struct idpf_vc_xn_manager *vcxn_mngr); int idpf_idc_rdma_vc_send_sync(struct iidc_rdma_core_dev_info *cdev_info, u8 *send_msg, u16 msg_size, u8 *recv_msg, u16 *recv_len); diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl_ptp.c b/drivers/net/ethernet/intel/idpf/idpf_virtchnl_ptp.c index 8d8fb498e092..14dba40a993f 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl_ptp.c +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl_ptp.c @@ -15,7 +15,6 @@ */ int idpf_ptp_get_caps(struct idpf_adapter *adapter) { - struct virtchnl2_ptp_get_caps *recv_ptp_caps_msg __free(kfree) = NULL; struct virtchnl2_ptp_get_caps send_ptp_caps_msg = { .caps = cpu_to_le32(VIRTCHNL2_CAP_PTP_GET_DEVICE_CLK_TIME | VIRTCHNL2_CAP_PTP_GET_DEVICE_CLK_TIME_MB | @@ -24,34 +23,33 @@ int idpf_ptp_get_caps(struct idpf_adapter *adapter) VIRTCHNL2_CAP_PTP_ADJ_DEVICE_CLK_MB | VIRTCHNL2_CAP_PTP_TX_TSTAMPS_MB) }; - struct idpf_vc_xn_params xn_params = { - .vc_op = VIRTCHNL2_OP_PTP_GET_CAPS, - .send_buf.iov_base = &send_ptp_caps_msg, - .send_buf.iov_len = sizeof(send_ptp_caps_msg), + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = VIRTCHNL2_OP_PTP_GET_CAPS, .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, }; struct virtchnl2_ptp_cross_time_reg_offsets cross_tstamp_offsets; struct libie_mmio_info *mmio = &adapter->ctlq_ctx.mmio_info; struct virtchnl2_ptp_clk_adj_reg_offsets clk_adj_offsets; struct virtchnl2_ptp_clk_reg_offsets clock_offsets; + struct virtchnl2_ptp_get_caps *recv_ptp_caps_msg; struct idpf_ptp_secondary_mbx *scnd_mbx; struct idpf_ptp *ptp = adapter->ptp; enum idpf_ptp_access access_type; u32 temp_offset; - int reply_sz; + size_t reply_sz; + int err; - recv_ptp_caps_msg = kzalloc_obj(struct virtchnl2_ptp_get_caps); - if (!recv_ptp_caps_msg) - return -ENOMEM; + err = idpf_send_mb_msg_stack(adapter, &xn_params, &send_ptp_caps_msg); + if (err) + return err; - xn_params.recv_buf.iov_base = recv_ptp_caps_msg; - xn_params.recv_buf.iov_len = sizeof(*recv_ptp_caps_msg); + reply_sz = xn_params.recv_mem.iov_len; + if (reply_sz != sizeof(*recv_ptp_caps_msg)) { + err = -EIO; + goto free_resp; + } - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - else if (reply_sz != sizeof(*recv_ptp_caps_msg)) - return -EIO; + recv_ptp_caps_msg = xn_params.recv_mem.iov_base; ptp->caps = le32_to_cpu(recv_ptp_caps_msg->caps); ptp->base_incval = le64_to_cpu(recv_ptp_caps_msg->base_incval); @@ -112,7 +110,7 @@ int idpf_ptp_get_caps(struct idpf_adapter *adapter) discipline_clock: access_type = ptp->adj_dev_clk_time_access; if (access_type != IDPF_PTP_DIRECT) - return 0; + goto free_resp; clk_adj_offsets = recv_ptp_caps_msg->clk_adj_offsets; @@ -145,7 +143,9 @@ int idpf_ptp_get_caps(struct idpf_adapter *adapter) ptp->dev_clk_regs.phy_shadj_h = libie_pci_get_mmio_addr(mmio, temp_offset); - return 0; +free_resp: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + return err; } /** @@ -160,28 +160,34 @@ int idpf_ptp_get_caps(struct idpf_adapter *adapter) int idpf_ptp_get_dev_clk_time(struct idpf_adapter *adapter, struct idpf_ptp_dev_timers *dev_clk_time) { + struct virtchnl2_ptp_get_dev_clk_time *get_dev_clk_time_resp; struct virtchnl2_ptp_get_dev_clk_time get_dev_clk_time_msg; - struct idpf_vc_xn_params xn_params = { - .vc_op = VIRTCHNL2_OP_PTP_GET_DEV_CLK_TIME, - .send_buf.iov_base = &get_dev_clk_time_msg, - .send_buf.iov_len = sizeof(get_dev_clk_time_msg), - .recv_buf.iov_base = &get_dev_clk_time_msg, - .recv_buf.iov_len = sizeof(get_dev_clk_time_msg), + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = VIRTCHNL2_OP_PTP_GET_DEV_CLK_TIME, .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, }; - int reply_sz; + size_t reply_sz; u64 dev_time; + int err; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - if (reply_sz != sizeof(get_dev_clk_time_msg)) - return -EIO; + err = idpf_send_mb_msg_stack(adapter, &xn_params, + &get_dev_clk_time_msg); + if (err) + return err; - dev_time = le64_to_cpu(get_dev_clk_time_msg.dev_time_ns); + reply_sz = xn_params.recv_mem.iov_len; + if (reply_sz != sizeof(*get_dev_clk_time_resp)) { + err = -EIO; + goto free_resp; + } + + get_dev_clk_time_resp = xn_params.recv_mem.iov_base; + dev_time = le64_to_cpu(get_dev_clk_time_resp->dev_time_ns); dev_clk_time->dev_clk_time_ns = dev_time; - return 0; +free_resp: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + return err; } /** @@ -197,27 +203,29 @@ int idpf_ptp_get_dev_clk_time(struct idpf_adapter *adapter, int idpf_ptp_get_cross_time(struct idpf_adapter *adapter, struct idpf_ptp_dev_timers *cross_time) { - struct virtchnl2_ptp_get_cross_time cross_time_msg; - struct idpf_vc_xn_params xn_params = { - .vc_op = VIRTCHNL2_OP_PTP_GET_CROSS_TIME, - .send_buf.iov_base = &cross_time_msg, - .send_buf.iov_len = sizeof(cross_time_msg), - .recv_buf.iov_base = &cross_time_msg, - .recv_buf.iov_len = sizeof(cross_time_msg), + struct virtchnl2_ptp_get_cross_time cross_time_send, *cross_time_recv; + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = VIRTCHNL2_OP_PTP_GET_CROSS_TIME, .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, }; - int reply_sz; + int err = 0; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - if (reply_sz != sizeof(cross_time_msg)) - return -EIO; + err = idpf_send_mb_msg_stack(adapter, &xn_params, &cross_time_send); + if (err) + return err; - cross_time->dev_clk_time_ns = le64_to_cpu(cross_time_msg.dev_time_ns); - cross_time->sys_time_ns = le64_to_cpu(cross_time_msg.sys_time_ns); + if (xn_params.recv_mem.iov_len != sizeof(*cross_time_recv)) { + err = -EIO; + goto free_resp; + } - return 0; + cross_time_recv = xn_params.recv_mem.iov_base; + cross_time->dev_clk_time_ns = le64_to_cpu(cross_time_recv->dev_time_ns); + cross_time->sys_time_ns = le64_to_cpu(cross_time_recv->sys_time_ns); + +free_resp: + libie_ctlq_release_rx_buf(&xn_params.recv_mem); + return err; } /** @@ -234,23 +242,18 @@ int idpf_ptp_set_dev_clk_time(struct idpf_adapter *adapter, u64 time) struct virtchnl2_ptp_set_dev_clk_time set_dev_clk_time_msg = { .dev_time_ns = cpu_to_le64(time), }; - struct idpf_vc_xn_params xn_params = { - .vc_op = VIRTCHNL2_OP_PTP_SET_DEV_CLK_TIME, - .send_buf.iov_base = &set_dev_clk_time_msg, - .send_buf.iov_len = sizeof(set_dev_clk_time_msg), - .recv_buf.iov_base = &set_dev_clk_time_msg, - .recv_buf.iov_len = sizeof(set_dev_clk_time_msg), + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = VIRTCHNL2_OP_PTP_SET_DEV_CLK_TIME, .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, }; - int reply_sz; + int err; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - if (reply_sz != sizeof(set_dev_clk_time_msg)) - return -EIO; + err = idpf_send_mb_msg_stack(adapter, &xn_params, + &set_dev_clk_time_msg); + if (!err) + libie_ctlq_release_rx_buf(&xn_params.recv_mem); - return 0; + return err; } /** @@ -267,23 +270,18 @@ int idpf_ptp_adj_dev_clk_time(struct idpf_adapter *adapter, s64 delta) struct virtchnl2_ptp_adj_dev_clk_time adj_dev_clk_time_msg = { .delta = cpu_to_le64(delta), }; - struct idpf_vc_xn_params xn_params = { - .vc_op = VIRTCHNL2_OP_PTP_ADJ_DEV_CLK_TIME, - .send_buf.iov_base = &adj_dev_clk_time_msg, - .send_buf.iov_len = sizeof(adj_dev_clk_time_msg), - .recv_buf.iov_base = &adj_dev_clk_time_msg, - .recv_buf.iov_len = sizeof(adj_dev_clk_time_msg), + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = VIRTCHNL2_OP_PTP_ADJ_DEV_CLK_TIME, .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, }; - int reply_sz; + int err; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - if (reply_sz != sizeof(adj_dev_clk_time_msg)) - return -EIO; + err = idpf_send_mb_msg_stack(adapter, &xn_params, + &adj_dev_clk_time_msg); + if (!err) + libie_ctlq_release_rx_buf(&xn_params.recv_mem); - return 0; + return err; } /** @@ -301,23 +299,18 @@ int idpf_ptp_adj_dev_clk_fine(struct idpf_adapter *adapter, u64 incval) struct virtchnl2_ptp_adj_dev_clk_fine adj_dev_clk_fine_msg = { .incval = cpu_to_le64(incval), }; - struct idpf_vc_xn_params xn_params = { - .vc_op = VIRTCHNL2_OP_PTP_ADJ_DEV_CLK_FINE, - .send_buf.iov_base = &adj_dev_clk_fine_msg, - .send_buf.iov_len = sizeof(adj_dev_clk_fine_msg), - .recv_buf.iov_base = &adj_dev_clk_fine_msg, - .recv_buf.iov_len = sizeof(adj_dev_clk_fine_msg), + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = VIRTCHNL2_OP_PTP_ADJ_DEV_CLK_FINE, .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, }; - int reply_sz; + int err; - reply_sz = idpf_vc_xn_exec(adapter, &xn_params); - if (reply_sz < 0) - return reply_sz; - if (reply_sz != sizeof(adj_dev_clk_fine_msg)) - return -EIO; + err = idpf_send_mb_msg_stack(adapter, &xn_params, + &adj_dev_clk_fine_msg); + if (!err) + libie_ctlq_release_rx_buf(&xn_params.recv_mem); - return 0; + return err; } /** @@ -336,18 +329,16 @@ int idpf_ptp_get_vport_tstamps_caps(struct idpf_vport *vport) struct virtchnl2_ptp_tx_tstamp_latch_caps tx_tstamp_latch_caps; struct idpf_ptp_vport_tx_tstamp_caps *tstamp_caps; struct idpf_ptp_tx_tstamp *ptp_tx_tstamp, *tmp; - struct idpf_vc_xn_params xn_params = { - .vc_op = VIRTCHNL2_OP_PTP_GET_VPORT_TX_TSTAMP_CAPS, - .send_buf.iov_base = &send_tx_tstamp_caps, - .send_buf.iov_len = sizeof(send_tx_tstamp_caps), - .recv_buf.iov_len = IDPF_CTLQ_MAX_BUF_LEN, + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = VIRTCHNL2_OP_PTP_GET_VPORT_TX_TSTAMP_CAPS, .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, }; enum idpf_ptp_access tstamp_access, get_dev_clk_access; struct idpf_ptp *ptp = vport->adapter->ptp; struct list_head *head; - int err = 0, reply_sz; + size_t reply_sz; u16 num_latches; + int err = 0; u32 size; if (!ptp) @@ -359,19 +350,19 @@ int idpf_ptp_get_vport_tstamps_caps(struct idpf_vport *vport) get_dev_clk_access == IDPF_PTP_NONE) return -EOPNOTSUPP; - rcv_tx_tstamp_caps = kzalloc(IDPF_CTLQ_MAX_BUF_LEN, GFP_KERNEL); - if (!rcv_tx_tstamp_caps) - return -ENOMEM; - send_tx_tstamp_caps.vport_id = cpu_to_le32(vport->vport_id); - xn_params.recv_buf.iov_base = rcv_tx_tstamp_caps; - reply_sz = idpf_vc_xn_exec(vport->adapter, &xn_params); - if (reply_sz < 0) { - err = reply_sz; + err = idpf_send_mb_msg_stack(vport->adapter, &xn_params, + &send_tx_tstamp_caps); + if (err) + return err; + + rcv_tx_tstamp_caps = xn_params.recv_mem.iov_base; + reply_sz = xn_params.recv_mem.iov_len; + if (reply_sz < sizeof(*rcv_tx_tstamp_caps)) { + err = -EIO; goto get_tstamp_caps_out; } - num_latches = le16_to_cpu(rcv_tx_tstamp_caps->num_latches); size = struct_size(rcv_tx_tstamp_caps, tstamp_latches, num_latches); if (reply_sz != size) { @@ -426,7 +417,7 @@ int idpf_ptp_get_vport_tstamps_caps(struct idpf_vport *vport) } vport->tx_tstamp_caps = tstamp_caps; - kfree(rcv_tx_tstamp_caps); + libie_ctlq_release_rx_buf(&xn_params.recv_mem); return 0; @@ -439,7 +430,7 @@ int idpf_ptp_get_vport_tstamps_caps(struct idpf_vport *vport) kfree(tstamp_caps); get_tstamp_caps_out: - kfree(rcv_tx_tstamp_caps); + libie_ctlq_release_rx_buf(&xn_params.recv_mem); return err; } @@ -536,9 +527,9 @@ idpf_ptp_get_tstamp_value(struct idpf_vport *vport, /** * idpf_ptp_get_tx_tstamp_async_handler - Async callback for getting Tx tstamps - * @adapter: Driver specific private structure - * @xn: transaction for message - * @ctlq_msg: received message + * @ctx: adapter pointer + * @mem: address and size of the response + * @status: return value of the request * * Read the tstamps Tx tstamp values from a received message and put them * directly to the skb. The number of timestamps to read is specified by @@ -546,22 +537,26 @@ idpf_ptp_get_tstamp_value(struct idpf_vport *vport, * * Return: 0 on success, -errno otherwise. */ -static int -idpf_ptp_get_tx_tstamp_async_handler(struct idpf_adapter *adapter, - struct idpf_vc_xn *xn, - const struct idpf_ctlq_msg *ctlq_msg) +static void +idpf_ptp_get_tx_tstamp_async_handler(void *ctx, struct kvec *mem, int status) { struct virtchnl2_ptp_get_vport_tx_tstamp_latches *recv_tx_tstamp_msg; struct idpf_ptp_vport_tx_tstamp_caps *tx_tstamp_caps; struct virtchnl2_ptp_tx_tstamp_latch tstamp_latch; struct idpf_ptp_tx_tstamp *tx_tstamp, *tmp; struct idpf_vport *tstamp_vport = NULL; + struct idpf_adapter *adapter = ctx; struct list_head *head; u16 num_latches; u32 vport_id; - int err = 0; - recv_tx_tstamp_msg = ctlq_msg->ctx.indirect.payload->va; + if (status) + return; + + recv_tx_tstamp_msg = mem->iov_base; + if (mem->iov_len < sizeof(*recv_tx_tstamp_msg)) + return; + vport_id = le32_to_cpu(recv_tx_tstamp_msg->vport_id); idpf_for_each_vport(adapter, vport) { @@ -575,10 +570,13 @@ idpf_ptp_get_tx_tstamp_async_handler(struct idpf_adapter *adapter, } if (!tstamp_vport || !tstamp_vport->tx_tstamp_caps) - return -EINVAL; + return; tx_tstamp_caps = tstamp_vport->tx_tstamp_caps; num_latches = le16_to_cpu(recv_tx_tstamp_msg->num_latches); + if (mem->iov_len < struct_size(recv_tx_tstamp_msg, tstamp_latches, + num_latches)) + return; spin_lock_bh(&tx_tstamp_caps->latches_lock); head = &tx_tstamp_caps->latches_in_use; @@ -589,13 +587,13 @@ idpf_ptp_get_tx_tstamp_async_handler(struct idpf_adapter *adapter, if (!tstamp_latch.valid) continue; - if (list_empty(head)) { - err = -ENOBUFS; + if (list_empty(head)) goto unlock; - } list_for_each_entry_safe(tx_tstamp, tmp, head, list_member) { if (tstamp_latch.index == tx_tstamp->idx) { + int err; + list_del(&tx_tstamp->list_member); err = idpf_ptp_get_tstamp_value(tstamp_vport, &tstamp_latch, @@ -610,8 +608,6 @@ idpf_ptp_get_tx_tstamp_async_handler(struct idpf_adapter *adapter, unlock: spin_unlock_bh(&tx_tstamp_caps->latches_lock); - - return err; } /** @@ -627,15 +623,15 @@ int idpf_ptp_get_tx_tstamp(struct idpf_vport *vport) { struct virtchnl2_ptp_get_vport_tx_tstamp_latches *send_tx_tstamp_msg; struct idpf_ptp_vport_tx_tstamp_caps *tx_tstamp_caps; - struct idpf_vc_xn_params xn_params = { - .vc_op = VIRTCHNL2_OP_PTP_GET_VPORT_TX_TSTAMP, + struct libie_ctlq_xn_send_params xn_params = { + .chnl_opcode = VIRTCHNL2_OP_PTP_GET_VPORT_TX_TSTAMP, .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, - .async = true, - .async_handler = idpf_ptp_get_tx_tstamp_async_handler, + .resp_cb = idpf_ptp_get_tx_tstamp_async_handler, + .send_ctx = vport->adapter, }; struct idpf_ptp_tx_tstamp *ptp_tx_tstamp; - int reply_sz, size, msg_size; struct list_head *head; + int size, msg_size; bool state_upd; u16 id = 0; @@ -668,11 +664,7 @@ int idpf_ptp_get_tx_tstamp(struct idpf_vport *vport) msg_size = struct_size(send_tx_tstamp_msg, tstamp_latches, id); send_tx_tstamp_msg->vport_id = cpu_to_le32(vport->vport_id); send_tx_tstamp_msg->num_latches = cpu_to_le16(id); - xn_params.send_buf.iov_base = send_tx_tstamp_msg; - xn_params.send_buf.iov_len = msg_size; - reply_sz = idpf_vc_xn_exec(vport->adapter, &xn_params); - kfree(send_tx_tstamp_msg); - - return min(reply_sz, 0); + return idpf_send_mb_msg_kfree(vport->adapter, &xn_params, + send_tx_tstamp_msg, msg_size); } From 047cdea66752feefc6cbf1dfa97a78dba48dbb66 Mon Sep 17 00:00:00 2001 From: Larysa Zaremba Date: Thu, 25 Jun 2026 18:02:04 +0200 Subject: [PATCH 1255/1433] idpf: make mbx_task queueing and cancelling more consistent One of the assumptions of libie_cp and pre-refactor idpf control queue handling is such that all Rx processing is handled by a single task, which is to be cancelled before the mailbox destruction. Aside from cancelling, it is also important to make sure that idpf_intr_rel() never reschedules it afterwards. In order to comply, in the init path, do the first queueing of mbx_task in idpf_init_dflt_mbx(), and in deinit and reset, always cancel the task in idpf_deinit_dflt_mbx(), in every single flow call idpf_mb_intr_rel_irq() beforehand. Reviewed-by: Emil Tantilov Reviewed-by: Michal Kubiak Tested-by: Samuel Salin Signed-off-by: Larysa Zaremba Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/idpf/idpf.h | 1 + drivers/net/ethernet/intel/idpf/idpf_lib.c | 9 ++++----- drivers/net/ethernet/intel/idpf/idpf_virtchnl.c | 5 +++++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf.h b/drivers/net/ethernet/intel/idpf/idpf.h index d7d751e2a781..470bc23c844c 100644 --- a/drivers/net/ethernet/intel/idpf/idpf.h +++ b/drivers/net/ethernet/intel/idpf/idpf.h @@ -984,6 +984,7 @@ void idpf_vc_event_task(struct work_struct *work); void idpf_dev_ops_init(struct idpf_adapter *adapter); void idpf_vf_dev_ops_init(struct idpf_adapter *adapter); int idpf_intr_req(struct idpf_adapter *adapter); +void idpf_mb_intr_rel_irq(struct idpf_adapter *adapter); void idpf_intr_rel(struct idpf_adapter *adapter); u16 idpf_get_max_tx_hdr_size(struct idpf_adapter *adapter); int idpf_initiate_soft_reset(struct idpf_vport *vport, diff --git a/drivers/net/ethernet/intel/idpf/idpf_lib.c b/drivers/net/ethernet/intel/idpf/idpf_lib.c index a945e62c27d7..827c795afcb6 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_lib.c +++ b/drivers/net/ethernet/intel/idpf/idpf_lib.c @@ -68,9 +68,11 @@ static void idpf_deinit_vector_stack(struct idpf_adapter *adapter) * This will also disable interrupt mode and queue up mailbox task. Mailbox * task will reschedule itself if not in interrupt mode. */ -static void idpf_mb_intr_rel_irq(struct idpf_adapter *adapter) +void idpf_mb_intr_rel_irq(struct idpf_adapter *adapter) { - clear_bit(IDPF_MB_INTR_MODE, adapter->flags); + if (!test_and_clear_bit(IDPF_MB_INTR_MODE, adapter->flags)) + return; + kfree(free_irq(adapter->msix_entries[0].vector, adapter)); queue_delayed_work(adapter->mbx_wq, &adapter->mbx_task, 0); } @@ -1939,14 +1941,11 @@ static void idpf_init_hard_reset(struct idpf_adapter *adapter) goto unlock_mutex; } - queue_delayed_work(adapter->mbx_wq, &adapter->mbx_task, 0); - /* Initialize the state machine, also allocate memory and request * resources */ err = idpf_vc_core_init(adapter); if (err) { - cancel_delayed_work_sync(&adapter->mbx_task); idpf_deinit_dflt_mbx(adapter); goto unlock_mutex; } diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c index 5f8fb148f976..03b371a6aa2b 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c @@ -2931,6 +2931,8 @@ int idpf_init_dflt_mbx(struct idpf_adapter *adapter) adapter->xnm = params.xnm; adapter->state = __IDPF_VER_CHECK; + queue_delayed_work(adapter->mbx_wq, &adapter->mbx_task, 0); + return 0; } @@ -2940,6 +2942,9 @@ int idpf_init_dflt_mbx(struct idpf_adapter *adapter) */ void idpf_deinit_dflt_mbx(struct idpf_adapter *adapter) { + idpf_mb_intr_rel_irq(adapter); + cancel_delayed_work_sync(&adapter->mbx_task); + if (adapter->xnm) { libie_ctlq_xn_shutdown(adapter->xnm); idpf_mb_clean(adapter->asq, true); From 1150ca4085afc2a42efb914dc990c68e0b3fbcb4 Mon Sep 17 00:00:00 2001 From: Larysa Zaremba Date: Thu, 25 Jun 2026 18:02:05 +0200 Subject: [PATCH 1256/1433] idpf: print a debug message and bail in case of non-event ctlq message Unlike previous internal idpf ctlq implementation, libie_cp calls the default message handler for all received messages that do not have a matching xn transaction, not only for VIRTCHNL2_OP_EVENT. This leads to many error messages printing garbage, because the parsing expected a valid event message, but got e.g. a delayed response for a timed-out transaction. The information about timed-out transactions and otherwise unhandleable messages can still be valuable for developers, so print the information with dynamic debug and exit the function, so the following functions can parse valid events in peace. Reviewed-by: Aleksandr Loktionov Reviewed-by: Michal Kubiak Tested-by: Samuel Salin Signed-off-by: Larysa Zaremba Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/idpf/idpf_virtchnl.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c index 03b371a6aa2b..1caf52706973 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c @@ -84,6 +84,13 @@ void idpf_recv_event_msg(struct libie_ctlq_ctx *ctx, u32 event; adapter = container_of(ctx, struct idpf_adapter, ctlq_ctx); + if (ctlq_msg->chnl_opcode != VIRTCHNL2_OP_EVENT) { + dev_dbg(&adapter->pdev->dev, + "Unhandled message with opcode %u from CP\n", + ctlq_msg->chnl_opcode); + goto free_rx_buf; + } + if (payload_size < sizeof(*v2e)) { dev_err_ratelimited(&adapter->pdev->dev, "Failed to receive valid payload for event msg (op %d len %d)\n", ctlq_msg->chnl_opcode, From bea44e3892a027eb84fcad82dfbdd616680fe3e8 Mon Sep 17 00:00:00 2001 From: Larysa Zaremba Date: Thu, 25 Jun 2026 18:02:06 +0200 Subject: [PATCH 1257/1433] ixd: add basic driver framework for Intel(R) Control Plane Function Add module register and probe functionality. Add the required support to register IXD PCI driver, as well as probe, remove and shutdown callbacks. Enable the PCI device and request to reserve the memory resources that will be used by the driver. Finally map the BAR0 address space. For now, use devm_kzalloc() to allocate adapter, as it requires the least amount of code. In a later commit, it will be replaced with a devlink alternative. Co-developed-by: Amritha Nambiar Signed-off-by: Amritha Nambiar Reviewed-by: Maciej Fijalkowski Tested-by: Bharath R Signed-off-by: Larysa Zaremba Signed-off-by: Tony Nguyen --- .../device_drivers/ethernet/index.rst | 1 + .../device_drivers/ethernet/intel/ixd.rst | 39 ++++++ drivers/net/ethernet/intel/Kconfig | 2 + drivers/net/ethernet/intel/Makefile | 1 + drivers/net/ethernet/intel/ixd/Kconfig | 12 ++ drivers/net/ethernet/intel/ixd/Makefile | 8 ++ drivers/net/ethernet/intel/ixd/ixd.h | 28 +++++ drivers/net/ethernet/intel/ixd/ixd_lan_regs.h | 28 +++++ drivers/net/ethernet/intel/ixd/ixd_main.c | 112 ++++++++++++++++++ 9 files changed, 231 insertions(+) create mode 100644 Documentation/networking/device_drivers/ethernet/intel/ixd.rst create mode 100644 drivers/net/ethernet/intel/ixd/Kconfig create mode 100644 drivers/net/ethernet/intel/ixd/Makefile create mode 100644 drivers/net/ethernet/intel/ixd/ixd.h create mode 100644 drivers/net/ethernet/intel/ixd/ixd_lan_regs.h create mode 100644 drivers/net/ethernet/intel/ixd/ixd_main.c diff --git a/Documentation/networking/device_drivers/ethernet/index.rst b/Documentation/networking/device_drivers/ethernet/index.rst index 786a23c84b90..d9980c84487a 100644 --- a/Documentation/networking/device_drivers/ethernet/index.rst +++ b/Documentation/networking/device_drivers/ethernet/index.rst @@ -35,6 +35,7 @@ Contents: intel/idpf intel/igb intel/igbvf + intel/ixd intel/ixgbe intel/ixgbevf intel/i40e diff --git a/Documentation/networking/device_drivers/ethernet/intel/ixd.rst b/Documentation/networking/device_drivers/ethernet/intel/ixd.rst new file mode 100644 index 000000000000..1387626e5d20 --- /dev/null +++ b/Documentation/networking/device_drivers/ethernet/intel/ixd.rst @@ -0,0 +1,39 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +========================================================================== +iXD Linux* Base Driver for the Intel(R) Control Plane Function +========================================================================== + +Intel iXD Linux driver. +Copyright(C) 2025 Intel Corporation. + +.. contents:: + +For questions related to hardware requirements, refer to the documentation +supplied with your Intel adapter. All hardware requirements listed apply to use +with Linux. + + +Identifying Your Adapter +======================== +For information on how to identify your adapter, and for the latest Intel +network drivers, refer to the Intel Support website: +http://www.intel.com/support + + +Support +======= +For general information, go to the Intel support website at: +http://www.intel.com/support/ + +If an issue is identified with the released source code on a supported kernel +with a supported adapter, email the specific information related to the issue +to intel-wired-lan@lists.osuosl.org. + + +Trademarks +========== +Intel is a trademark or registered trademark of Intel Corporation or its +subsidiaries in the United States and/or other countries. + +* Other names and brands may be claimed as the property of others. diff --git a/drivers/net/ethernet/intel/Kconfig b/drivers/net/ethernet/intel/Kconfig index 288fa8ce53af..780f113986ea 100644 --- a/drivers/net/ethernet/intel/Kconfig +++ b/drivers/net/ethernet/intel/Kconfig @@ -398,4 +398,6 @@ config IGC_LEDS source "drivers/net/ethernet/intel/idpf/Kconfig" +source "drivers/net/ethernet/intel/ixd/Kconfig" + endif # NET_VENDOR_INTEL diff --git a/drivers/net/ethernet/intel/Makefile b/drivers/net/ethernet/intel/Makefile index 9a37dc76aef0..08b29f3b6801 100644 --- a/drivers/net/ethernet/intel/Makefile +++ b/drivers/net/ethernet/intel/Makefile @@ -19,3 +19,4 @@ obj-$(CONFIG_IAVF) += iavf/ obj-$(CONFIG_FM10K) += fm10k/ obj-$(CONFIG_ICE) += ice/ obj-$(CONFIG_IDPF) += idpf/ +obj-$(CONFIG_IXD) += ixd/ diff --git a/drivers/net/ethernet/intel/ixd/Kconfig b/drivers/net/ethernet/intel/ixd/Kconfig new file mode 100644 index 000000000000..5279cee2a050 --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Copyright (C) 2025 Intel Corporation + +config IXD + tristate "Intel(R) Control Plane Function Support" + depends on PCI_MSI + select LIBIE_PCI + help + This driver supports Intel(R) Control Plane PCI Function + of Intel E2100 and later IPUs and FNICs. + It facilitates a centralized control over multiple IDPF PFs/VFs/SFs + exposed by the same card. diff --git a/drivers/net/ethernet/intel/ixd/Makefile b/drivers/net/ethernet/intel/ixd/Makefile new file mode 100644 index 000000000000..3849bc240600 --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Copyright (C) 2025 Intel Corporation + +# Intel(R) Control Plane Function Linux Driver + +obj-$(CONFIG_IXD) += ixd.o + +ixd-y := ixd_main.o diff --git a/drivers/net/ethernet/intel/ixd/ixd.h b/drivers/net/ethernet/intel/ixd/ixd.h new file mode 100644 index 000000000000..1b918c5d31cd --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2025 Intel Corporation */ + +#ifndef _IXD_H_ +#define _IXD_H_ + +#include + +/** + * struct ixd_adapter - Data structure representing a CPF + * @hw: Device access data + */ +struct ixd_adapter { + struct libie_mmio_info hw; +}; + +/** + * ixd_to_dev - Get the corresponding device struct from an adapter + * @adapter: PCI device driver-specific private data + * + * Return: struct device corresponding to the given adapter + */ +static inline struct device *ixd_to_dev(struct ixd_adapter *adapter) +{ + return &adapter->hw.pdev->dev; +} + +#endif /* _IXD_H_ */ diff --git a/drivers/net/ethernet/intel/ixd/ixd_lan_regs.h b/drivers/net/ethernet/intel/ixd/ixd_lan_regs.h new file mode 100644 index 000000000000..fbb88929d0de --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd_lan_regs.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2025 Intel Corporation */ + +#ifndef _IXD_LAN_REGS_H_ +#define _IXD_LAN_REGS_H_ + +/* Control Plane Function PCI ID */ +#define IXD_DEV_ID_CPF 0x1efe + +/* Control Queue (Mailbox) */ +#define PF_FW_MBX_REG_LEN 4096 +#define PF_FW_MBX 0x08400000 + +/* Reset registers */ +#define PFGEN_RTRIG_REG_LEN 2048 +#define PFGEN_RTRIG 0x08407000 /* Device resets */ + +/** + * struct ixd_bar_region - BAR region description + * @offset: BAR region offset + * @size: BAR region size + */ +struct ixd_bar_region { + resource_size_t offset; + resource_size_t size; +}; + +#endif /* _IXD_LAN_REGS_H_ */ diff --git a/drivers/net/ethernet/intel/ixd/ixd_main.c b/drivers/net/ethernet/intel/ixd/ixd_main.c new file mode 100644 index 000000000000..75ee53152e61 --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd_main.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (C) 2025 Intel Corporation */ + +#include "ixd.h" +#include "ixd_lan_regs.h" + +MODULE_DESCRIPTION("Intel(R) Control Plane Function Device Driver"); +MODULE_IMPORT_NS("LIBIE_PCI"); +MODULE_LICENSE("GPL"); + +/** + * ixd_remove - remove a CPF PCI device + * @pdev: PCI device being removed + */ +static void ixd_remove(struct pci_dev *pdev) +{ + struct ixd_adapter *adapter = pci_get_drvdata(pdev); + + libie_pci_unmap_all_mmio_regions(&adapter->hw); +} + +/** + * ixd_shutdown - shut down a CPF PCI device + * @pdev: PCI device being shut down + */ +static void ixd_shutdown(struct pci_dev *pdev) +{ + ixd_remove(pdev); + + if (system_state == SYSTEM_POWER_OFF) + pci_set_power_state(pdev, PCI_D3hot); +} + +/** + * ixd_iomap_regions - iomap PCI BARs + * @adapter: adapter to map memory regions for + * + * Returns: %0 on success, negative on failure + */ +static int ixd_iomap_regions(struct ixd_adapter *adapter) +{ + const struct ixd_bar_region regions[] = { + { + .offset = PFGEN_RTRIG, + .size = PFGEN_RTRIG_REG_LEN, + }, + { + .offset = PF_FW_MBX, + .size = PF_FW_MBX_REG_LEN, + }, + }; + + for (int i = 0; i < ARRAY_SIZE(regions); i++) { + struct libie_mmio_info *mmio_info = &adapter->hw; + bool map_ok; + + map_ok = libie_pci_map_mmio_region(mmio_info, + regions[i].offset, + regions[i].size); + if (!map_ok) { + dev_err(ixd_to_dev(adapter), + "Failed to map PCI device MMIO region\n"); + + libie_pci_unmap_all_mmio_regions(mmio_info); + return -EIO; + } + } + + return 0; +} + +/** + * ixd_probe - probe a CPF PCI device + * @pdev: corresponding PCI device + * @ent: entry in ixd_pci_tbl + * + * Returns: %0 on success, negative errno code on failure + */ +static int ixd_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct ixd_adapter *adapter; + int err; + + adapter = devm_kzalloc(&pdev->dev, sizeof(*adapter), GFP_KERNEL); + if (!adapter) + return -ENOMEM; + adapter->hw.pdev = pdev; + INIT_LIST_HEAD(&adapter->hw.mmio_list); + + err = libie_pci_init_dev(pdev); + if (err) + return err; + + pci_set_drvdata(pdev, adapter); + + return ixd_iomap_regions(adapter); +} + +static const struct pci_device_id ixd_pci_tbl[] = { + { PCI_VDEVICE(INTEL, IXD_DEV_ID_CPF) }, + { } +}; +MODULE_DEVICE_TABLE(pci, ixd_pci_tbl); + +static struct pci_driver ixd_driver = { + .name = KBUILD_MODNAME, + .id_table = ixd_pci_tbl, + .probe = ixd_probe, + .remove = ixd_remove, + .shutdown = ixd_shutdown, +}; +module_pci_driver(ixd_driver); From 31ddf7965a927d7f7a0b800c81135a0dcd9c79c9 Mon Sep 17 00:00:00 2001 From: Larysa Zaremba Date: Thu, 25 Jun 2026 18:02:07 +0200 Subject: [PATCH 1258/1433] ixd: add reset checks and initialize the mailbox At the end of the probe, trigger hard reset, initialize and schedule the after-reset task. If the reset is complete in a pre-determined time, initialize the default mailbox, through which other resources will be negotiated. Co-developed-by: Amritha Nambiar Signed-off-by: Amritha Nambiar Reviewed-by: Maciej Fijalkowski Reviewed-by: Aleksandr Loktionov Tested-by: Bharath R Signed-off-by: Larysa Zaremba Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/ixd/Kconfig | 1 + drivers/net/ethernet/intel/ixd/Makefile | 2 + drivers/net/ethernet/intel/ixd/ixd.h | 32 ++++- drivers/net/ethernet/intel/ixd/ixd_dev.c | 89 ++++++++++++ drivers/net/ethernet/intel/ixd/ixd_lan_regs.h | 40 ++++++ drivers/net/ethernet/intel/ixd/ixd_lib.c | 136 ++++++++++++++++++ drivers/net/ethernet/intel/ixd/ixd_main.c | 31 +++- 7 files changed, 322 insertions(+), 9 deletions(-) create mode 100644 drivers/net/ethernet/intel/ixd/ixd_dev.c create mode 100644 drivers/net/ethernet/intel/ixd/ixd_lib.c diff --git a/drivers/net/ethernet/intel/ixd/Kconfig b/drivers/net/ethernet/intel/ixd/Kconfig index 5279cee2a050..2895f9723bdf 100644 --- a/drivers/net/ethernet/intel/ixd/Kconfig +++ b/drivers/net/ethernet/intel/ixd/Kconfig @@ -4,6 +4,7 @@ config IXD tristate "Intel(R) Control Plane Function Support" depends on PCI_MSI + select LIBIE_CP select LIBIE_PCI help This driver supports Intel(R) Control Plane PCI Function diff --git a/drivers/net/ethernet/intel/ixd/Makefile b/drivers/net/ethernet/intel/ixd/Makefile index 3849bc240600..164b2c86952f 100644 --- a/drivers/net/ethernet/intel/ixd/Makefile +++ b/drivers/net/ethernet/intel/ixd/Makefile @@ -6,3 +6,5 @@ obj-$(CONFIG_IXD) += ixd.o ixd-y := ixd_main.o +ixd-y += ixd_dev.o +ixd-y += ixd_lib.o diff --git a/drivers/net/ethernet/intel/ixd/ixd.h b/drivers/net/ethernet/intel/ixd/ixd.h index 1b918c5d31cd..33eee0b7f0f9 100644 --- a/drivers/net/ethernet/intel/ixd/ixd.h +++ b/drivers/net/ethernet/intel/ixd/ixd.h @@ -4,14 +4,29 @@ #ifndef _IXD_H_ #define _IXD_H_ -#include +#include + +#define IXD_INIT_TASK_DELAY_JIFFIES msecs_to_jiffies(500) /** * struct ixd_adapter - Data structure representing a CPF - * @hw: Device access data + * @cp_ctx: Control plane communication context + * @init_task: Delayed initialization after reset + * @init_task.init_work: Delayed initialization work + * @init_task.reset_retries: How many times to check, whether reset is completed + * @xnm: virtchnl transaction manager + * @asq: Send control queue info + * @arq: Receive control queue info */ struct ixd_adapter { - struct libie_mmio_info hw; + struct libie_ctlq_ctx cp_ctx; + struct { + struct delayed_work init_work; + u8 reset_retries; + } init_task; + struct libie_ctlq_xn_manager *xnm; + struct libie_ctlq_info *asq; + struct libie_ctlq_info *arq; }; /** @@ -22,7 +37,16 @@ struct ixd_adapter { */ static inline struct device *ixd_to_dev(struct ixd_adapter *adapter) { - return &adapter->hw.pdev->dev; + return &adapter->cp_ctx.mmio_info.pdev->dev; } +void ixd_ctlq_reg_init(struct ixd_adapter *adapter, + struct libie_ctlq_reg *ctlq_reg_tx, + struct libie_ctlq_reg *ctlq_reg_rx); +void ixd_trigger_reset(struct ixd_adapter *adapter); +bool ixd_check_reset_complete(struct ixd_adapter *adapter); +void ixd_init_task(struct work_struct *work); +int ixd_init_dflt_mbx(struct ixd_adapter *adapter); +void ixd_deinit_dflt_mbx(struct ixd_adapter *adapter); + #endif /* _IXD_H_ */ diff --git a/drivers/net/ethernet/intel/ixd/ixd_dev.c b/drivers/net/ethernet/intel/ixd/ixd_dev.c new file mode 100644 index 000000000000..cdd5477cc1f4 --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd_dev.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (C) 2025 Intel Corporation */ + +#include "ixd.h" +#include "ixd_lan_regs.h" + +/** + * ixd_ctlq_reg_init - Initialize default mailbox registers + * @adapter: PCI device driver-specific private data + * @ctlq_reg_tx: Transmit queue registers info to be filled + * @ctlq_reg_rx: Receive queue registers info to be filled + */ +void ixd_ctlq_reg_init(struct ixd_adapter *adapter, + struct libie_ctlq_reg *ctlq_reg_tx, + struct libie_ctlq_reg *ctlq_reg_rx) +{ + struct libie_mmio_info *mmio_info = &adapter->cp_ctx.mmio_info; + *ctlq_reg_tx = (struct libie_ctlq_reg) { + .head = libie_pci_get_mmio_addr(mmio_info, PF_FW_ATQH), + .tail = libie_pci_get_mmio_addr(mmio_info, PF_FW_ATQT), + .len = libie_pci_get_mmio_addr(mmio_info, PF_FW_ATQLEN), + .addr_high = libie_pci_get_mmio_addr(mmio_info, PF_FW_ATQBAH), + .addr_low = libie_pci_get_mmio_addr(mmio_info, PF_FW_ATQBAL), + .len_mask = PF_FW_ATQLEN_ATQLEN_M, + .len_ena_mask = PF_FW_ATQLEN_ATQENABLE_M, + .head_mask = PF_FW_ATQH_ATQH_M, + }; + + *ctlq_reg_rx = (struct libie_ctlq_reg) { + .head = libie_pci_get_mmio_addr(mmio_info, PF_FW_ARQH), + .tail = libie_pci_get_mmio_addr(mmio_info, PF_FW_ARQT), + .len = libie_pci_get_mmio_addr(mmio_info, PF_FW_ARQLEN), + .addr_high = libie_pci_get_mmio_addr(mmio_info, PF_FW_ARQBAH), + .addr_low = libie_pci_get_mmio_addr(mmio_info, PF_FW_ARQBAL), + .len_mask = PF_FW_ARQLEN_ARQLEN_M, + .len_ena_mask = PF_FW_ARQLEN_ARQENABLE_M, + .head_mask = PF_FW_ARQH_ARQH_M, + }; +} + +static const struct ixd_reset_reg ixd_reset_reg = { + .rstat = PFGEN_RSTAT, + .rstat_m = PFGEN_RSTAT_PFR_STATE_M, + .rstat_ok_v = 0b01, + .rtrigger = PFGEN_CTRL, + .rtrigger_m = PFGEN_CTRL_PFSWR, +}; + +/** + * ixd_trigger_reset - Trigger PFR reset + * @adapter: the device with mapped reset register + */ +void ixd_trigger_reset(struct ixd_adapter *adapter) +{ + void __iomem *addr; + u32 reg_val; + + addr = libie_pci_get_mmio_addr(&adapter->cp_ctx.mmio_info, + ixd_reset_reg.rtrigger); + reg_val = readl(addr); + writel(reg_val | ixd_reset_reg.rtrigger_m, addr); +} + +/** + * ixd_check_reset_complete - Check if the PFR reset is completed + * @adapter: CPF being reset + * + * Return: %true if the register read indicates reset has been finished, + * %false otherwise + */ +bool ixd_check_reset_complete(struct ixd_adapter *adapter) +{ + u32 reg_val, reset_status; + void __iomem *addr; + + addr = libie_pci_get_mmio_addr(&adapter->cp_ctx.mmio_info, + ixd_reset_reg.rstat); + reg_val = readl(addr); + reset_status = reg_val & ixd_reset_reg.rstat_m; + + /* 0xFFFFFFFF might be read if the other side hasn't cleared + * the register for us yet. + */ + if (reg_val != GENMASK(31, 0) && + reset_status == ixd_reset_reg.rstat_ok_v) + return true; + + return false; +} diff --git a/drivers/net/ethernet/intel/ixd/ixd_lan_regs.h b/drivers/net/ethernet/intel/ixd/ixd_lan_regs.h index fbb88929d0de..58e58c75981b 100644 --- a/drivers/net/ethernet/intel/ixd/ixd_lan_regs.h +++ b/drivers/net/ethernet/intel/ixd/ixd_lan_regs.h @@ -11,9 +11,33 @@ #define PF_FW_MBX_REG_LEN 4096 #define PF_FW_MBX 0x08400000 +#define PF_FW_ARQBAL (PF_FW_MBX) +#define PF_FW_ARQBAH (PF_FW_MBX + 0x4) +#define PF_FW_ARQLEN (PF_FW_MBX + 0x8) +#define PF_FW_ARQLEN_ARQLEN_M GENMASK(12, 0) +#define PF_FW_ARQLEN_ARQENABLE_S 31 +#define PF_FW_ARQLEN_ARQENABLE_M BIT(PF_FW_ARQLEN_ARQENABLE_S) +#define PF_FW_ARQH_ARQH_M GENMASK(12, 0) +#define PF_FW_ARQH (PF_FW_MBX + 0xC) +#define PF_FW_ARQT (PF_FW_MBX + 0x10) + +#define PF_FW_ATQBAL (PF_FW_MBX + 0x14) +#define PF_FW_ATQBAH (PF_FW_MBX + 0x18) +#define PF_FW_ATQLEN (PF_FW_MBX + 0x1C) +#define PF_FW_ATQLEN_ATQLEN_M GENMASK(9, 0) +#define PF_FW_ATQLEN_ATQENABLE_S 31 +#define PF_FW_ATQLEN_ATQENABLE_M BIT(PF_FW_ATQLEN_ATQENABLE_S) +#define PF_FW_ATQH_ATQH_M GENMASK(9, 0) +#define PF_FW_ATQH (PF_FW_MBX + 0x20) +#define PF_FW_ATQT (PF_FW_MBX + 0x24) + /* Reset registers */ #define PFGEN_RTRIG_REG_LEN 2048 #define PFGEN_RTRIG 0x08407000 /* Device resets */ +#define PFGEN_RSTAT 0x08407008 /* PFR status */ +#define PFGEN_RSTAT_PFR_STATE_M GENMASK(1, 0) +#define PFGEN_CTRL 0x0840700C /* PFR trigger */ +#define PFGEN_CTRL_PFSWR BIT(0) /** * struct ixd_bar_region - BAR region description @@ -25,4 +49,20 @@ struct ixd_bar_region { resource_size_t size; }; +/** + * struct ixd_reset_reg - structure for reset registers + * @rstat: offset of status in register + * @rstat_m: status mask + * @rstat_ok_v: value that indicates PFR completed status + * @rtrigger: offset of reset trigger in register + * @rtrigger_m: reset trigger mask + */ +struct ixd_reset_reg { + u32 rstat; + u32 rstat_m; + u32 rstat_ok_v; + u32 rtrigger; + u32 rtrigger_m; +}; + #endif /* _IXD_LAN_REGS_H_ */ diff --git a/drivers/net/ethernet/intel/ixd/ixd_lib.c b/drivers/net/ethernet/intel/ixd/ixd_lib.c new file mode 100644 index 000000000000..0d601fc091a6 --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd_lib.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (C) 2025 Intel Corporation */ + +#include "ixd.h" + +#define IXD_DFLT_MBX_Q_LEN 64 + +/** + * ixd_init_ctlq_create_info - Initialize control queue info for creation + * @info: destination + * @type: type of the queue to create + * @ctlq_reg: register assigned to the control queue + */ +static void ixd_init_ctlq_create_info(struct libie_ctlq_create_info *info, + enum libie_ctlq_type type, + const struct libie_ctlq_reg *ctlq_reg) +{ + *info = (struct libie_ctlq_create_info) { + .type = type, + .id = -1, + .reg = *ctlq_reg, + .len = IXD_DFLT_MBX_Q_LEN, + }; +} + +/** + * ixd_init_libie_xn_params - Initialize xn transaction manager creation info + * @params: destination + * @adapter: adapter info struct + * @ctlqs: list of the managed queues to create + * @num_queues: length of the queue list + */ +static void ixd_init_libie_xn_params(struct libie_ctlq_xn_init_params *params, + struct ixd_adapter *adapter, + struct libie_ctlq_create_info *ctlqs, + uint num_queues) +{ + *params = (struct libie_ctlq_xn_init_params){ + .cctlq_info = ctlqs, + .ctx = &adapter->cp_ctx, + .num_qs = num_queues, + }; +} + +/** + * ixd_adapter_fill_dflt_ctlqs - Find default control queues and store them + * @adapter: adapter info struct + */ +static void ixd_adapter_fill_dflt_ctlqs(struct ixd_adapter *adapter) +{ + adapter->arq = libie_find_ctlq(&adapter->cp_ctx, LIBIE_CTLQ_TYPE_RX, + LIBIE_CTLQ_MBX_ID); + adapter->asq = libie_find_ctlq(&adapter->cp_ctx, LIBIE_CTLQ_TYPE_TX, + LIBIE_CTLQ_MBX_ID); +} + +/** + * ixd_deinit_dflt_mbx - Deinitialize default mailbox + * @adapter: adapter info struct + */ +void ixd_deinit_dflt_mbx(struct ixd_adapter *adapter) +{ + if (adapter->xnm) + libie_ctlq_xn_deinit(adapter->xnm, &adapter->cp_ctx); + + adapter->arq = NULL; + adapter->asq = NULL; + adapter->xnm = NULL; +} + +/** + * ixd_init_dflt_mbx - Setup default mailbox parameters and make request + * @adapter: adapter info struct + * + * Return: %0 on success, negative errno code on failure + */ +int ixd_init_dflt_mbx(struct ixd_adapter *adapter) +{ + struct libie_ctlq_create_info ctlqs_info[2]; + struct libie_ctlq_xn_init_params xn_params; + struct libie_ctlq_reg ctlq_reg_tx; + struct libie_ctlq_reg ctlq_reg_rx; + int err; + + ixd_ctlq_reg_init(adapter, &ctlq_reg_tx, &ctlq_reg_rx); + ixd_init_ctlq_create_info(&ctlqs_info[0], LIBIE_CTLQ_TYPE_TX, + &ctlq_reg_tx); + ixd_init_ctlq_create_info(&ctlqs_info[1], LIBIE_CTLQ_TYPE_RX, + &ctlq_reg_rx); + ixd_init_libie_xn_params(&xn_params, adapter, ctlqs_info, + ARRAY_SIZE(ctlqs_info)); + err = libie_ctlq_xn_init(&xn_params); + if (err) + return err; + adapter->xnm = xn_params.xnm; + + ixd_adapter_fill_dflt_ctlqs(adapter); + + if (!adapter->asq || !adapter->arq) { + ixd_deinit_dflt_mbx(adapter); + return -ENOENT; + } + + return 0; +} + +/** + * ixd_init_task - Initialize after reset + * @work: init work struct + */ +void ixd_init_task(struct work_struct *work) +{ + struct ixd_adapter *adapter; + int err; + + adapter = container_of(work, struct ixd_adapter, + init_task.init_work.work); + + if (!ixd_check_reset_complete(adapter)) { + if (++adapter->init_task.reset_retries < 10) + queue_delayed_work(system_dfl_wq, + &adapter->init_task.init_work, + IXD_INIT_TASK_DELAY_JIFFIES); + else + dev_err(ixd_to_dev(adapter), + "Device reset failed. The driver was unable to contact the device's firmware. Check that the FW is running.\n"); + return; + } + + adapter->init_task.reset_retries = 0; + err = ixd_init_dflt_mbx(adapter); + if (err) + dev_err(ixd_to_dev(adapter), + "Failed to initialize the default mailbox: %pe\n", + ERR_PTR(err)); +} diff --git a/drivers/net/ethernet/intel/ixd/ixd_main.c b/drivers/net/ethernet/intel/ixd/ixd_main.c index 75ee53152e61..a085b40dc75a 100644 --- a/drivers/net/ethernet/intel/ixd/ixd_main.c +++ b/drivers/net/ethernet/intel/ixd/ixd_main.c @@ -5,6 +5,7 @@ #include "ixd_lan_regs.h" MODULE_DESCRIPTION("Intel(R) Control Plane Function Device Driver"); +MODULE_IMPORT_NS("LIBIE_CP"); MODULE_IMPORT_NS("LIBIE_PCI"); MODULE_LICENSE("GPL"); @@ -16,7 +17,15 @@ static void ixd_remove(struct pci_dev *pdev) { struct ixd_adapter *adapter = pci_get_drvdata(pdev); - libie_pci_unmap_all_mmio_regions(&adapter->hw); + /* Do not mix removal with (re)initialization */ + cancel_delayed_work_sync(&adapter->init_task.init_work); + /* Leave the device clean on exit */ + if (adapter->xnm) + libie_ctlq_xn_shutdown(adapter->xnm); + ixd_trigger_reset(adapter); + ixd_deinit_dflt_mbx(adapter); + + libie_pci_unmap_all_mmio_regions(&adapter->cp_ctx.mmio_info); } /** @@ -51,7 +60,7 @@ static int ixd_iomap_regions(struct ixd_adapter *adapter) }; for (int i = 0; i < ARRAY_SIZE(regions); i++) { - struct libie_mmio_info *mmio_info = &adapter->hw; + struct libie_mmio_info *mmio_info = &adapter->cp_ctx.mmio_info; bool map_ok; map_ok = libie_pci_map_mmio_region(mmio_info, @@ -84,8 +93,9 @@ static int ixd_probe(struct pci_dev *pdev, const struct pci_device_id *ent) adapter = devm_kzalloc(&pdev->dev, sizeof(*adapter), GFP_KERNEL); if (!adapter) return -ENOMEM; - adapter->hw.pdev = pdev; - INIT_LIST_HEAD(&adapter->hw.mmio_list); + + adapter->cp_ctx.mmio_info.pdev = pdev; + INIT_LIST_HEAD(&adapter->cp_ctx.mmio_info.mmio_list); err = libie_pci_init_dev(pdev); if (err) @@ -93,7 +103,18 @@ static int ixd_probe(struct pci_dev *pdev, const struct pci_device_id *ent) pci_set_drvdata(pdev, adapter); - return ixd_iomap_regions(adapter); + err = ixd_iomap_regions(adapter); + if (err) + return err; + + INIT_DELAYED_WORK(&adapter->init_task.init_work, + ixd_init_task); + + ixd_trigger_reset(adapter); + queue_delayed_work(system_dfl_wq, &adapter->init_task.init_work, + IXD_INIT_TASK_DELAY_JIFFIES); + + return 0; } static const struct pci_device_id ixd_pci_tbl[] = { From 29735d99c383d5fbc922aa84eb34f5bc08fbed1e Mon Sep 17 00:00:00 2001 From: Larysa Zaremba Date: Thu, 25 Jun 2026 18:02:08 +0200 Subject: [PATCH 1259/1433] ixd: add the core initialization As the mailbox is setup, initialize the core. This makes use of the send and receive mailbox message framework for virtchnl communication between the driver and device Control Plane (CP). To start with, driver confirms the virtchnl version with the CP. Once that is done, it requests and gets the required capabilities and resources needed such as max vectors, queues, vports etc. Use a unified way of handling the virtchnl messages, where a single function handles all related memory management and the caller only provides the callbacks to fill the send buffer and to handle the response. Place generic control queue message handling separately to facilitate the addition of protocols other than virtchannel in the future. Co-developed-by: Amritha Nambiar Signed-off-by: Amritha Nambiar Reviewed-by: Maciej Fijalkowski Tested-by: Bharath R Signed-off-by: Larysa Zaremba Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/ixd/Makefile | 2 + drivers/net/ethernet/intel/ixd/ixd.h | 13 ++ drivers/net/ethernet/intel/ixd/ixd_ctlq.c | 141 +++++++++++++ drivers/net/ethernet/intel/ixd/ixd_ctlq.h | 34 ++++ drivers/net/ethernet/intel/ixd/ixd_lib.c | 36 +++- drivers/net/ethernet/intel/ixd/ixd_main.c | 3 + drivers/net/ethernet/intel/ixd/ixd_virtchnl.c | 190 ++++++++++++++++++ drivers/net/ethernet/intel/ixd/ixd_virtchnl.h | 12 ++ 8 files changed, 430 insertions(+), 1 deletion(-) create mode 100644 drivers/net/ethernet/intel/ixd/ixd_ctlq.c create mode 100644 drivers/net/ethernet/intel/ixd/ixd_ctlq.h create mode 100644 drivers/net/ethernet/intel/ixd/ixd_virtchnl.c create mode 100644 drivers/net/ethernet/intel/ixd/ixd_virtchnl.h diff --git a/drivers/net/ethernet/intel/ixd/Makefile b/drivers/net/ethernet/intel/ixd/Makefile index 164b2c86952f..90abf231fb16 100644 --- a/drivers/net/ethernet/intel/ixd/Makefile +++ b/drivers/net/ethernet/intel/ixd/Makefile @@ -6,5 +6,7 @@ obj-$(CONFIG_IXD) += ixd.o ixd-y := ixd_main.o +ixd-y += ixd_ctlq.o ixd-y += ixd_dev.o ixd-y += ixd_lib.o +ixd-y += ixd_virtchnl.o diff --git a/drivers/net/ethernet/intel/ixd/ixd.h b/drivers/net/ethernet/intel/ixd/ixd.h index 33eee0b7f0f9..4c970031451a 100644 --- a/drivers/net/ethernet/intel/ixd/ixd.h +++ b/drivers/net/ethernet/intel/ixd/ixd.h @@ -14,19 +14,32 @@ * @init_task: Delayed initialization after reset * @init_task.init_work: Delayed initialization work * @init_task.reset_retries: How many times to check, whether reset is completed + * @init_task.vc_retries: Number of retries to establish mailbox communication + * @mbx_task: Control queue Rx handling * @xnm: virtchnl transaction manager * @asq: Send control queue info * @arq: Receive control queue info + * @vc_ver: Negotiated virtchnl version + * @vc_ver.major: Negotiated major virtchnl version + * @vc_ver.minor: Negotiated minor virtchnl version + * @caps: Negotiated virtchnl capabilities */ struct ixd_adapter { struct libie_ctlq_ctx cp_ctx; struct { struct delayed_work init_work; u8 reset_retries; + u8 vc_retries; } init_task; + struct delayed_work mbx_task; struct libie_ctlq_xn_manager *xnm; struct libie_ctlq_info *asq; struct libie_ctlq_info *arq; + struct { + u32 major; + u32 minor; + } vc_ver; + struct virtchnl2_get_capabilities caps; }; /** diff --git a/drivers/net/ethernet/intel/ixd/ixd_ctlq.c b/drivers/net/ethernet/intel/ixd/ixd_ctlq.c new file mode 100644 index 000000000000..8712e10c8c50 --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd_ctlq.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (C) 2025 Intel Corporation */ + +#include "ixd.h" +#include "ixd_ctlq.h" +#include "ixd_virtchnl.h" + +#define IXD_CTLQ_RX_TASK_DELAY_JIFFIES msecs_to_jiffies(300) + +/** + * ixd_ctlq_clean_sq - Clean the send control queue after sending the message + * @adapter: The adapter that sent the messages + * @force: Clean regardless of send status + * + * Free the libie send resources after sending the message and handling + * the response. + */ +void ixd_ctlq_clean_sq(struct ixd_adapter *adapter, bool force) +{ + libie_ctlq_xn_send_clean(adapter->asq, kfree, force); +} + +/** + * ixd_ctlq_init_sparams - Initialize control queue send parameters + * @adapter: The adapter with initialized mailbox + * @sparams: Parameters to initialize + * @msg_buf: DMA-mappable pointer to the message being sent + * @msg_size: Message size + */ +static void ixd_ctlq_init_sparams(struct ixd_adapter *adapter, + struct libie_ctlq_xn_send_params *sparams, + void *msg_buf, size_t msg_size) +{ + *sparams = (struct libie_ctlq_xn_send_params) { + .rel_tx_buf = kfree, + .xnm = adapter->xnm, + .ctlq = adapter->asq, + .timeout_ms = IXD_CTLQ_TIMEOUT, + .send_buf = (struct kvec) { + .iov_base = msg_buf, + .iov_len = msg_size, + }, + }; +} + +/** + * ixd_ctlq_do_req - Perform a standard virtchnl request + * @adapter: The adapter with initialized mailbox + * @req: virtchnl request description + * + * Return: %0 if a message was sent and received a response + * that was successfully handled by the custom callback, + * negative error otherwise. + */ +int ixd_ctlq_do_req(struct ixd_adapter *adapter, const struct ixd_ctlq_req *req) +{ + u8 onstack_send_buff[LIBIE_CP_TX_COPYBREAK] __aligned_largest = {}; + struct libie_ctlq_xn_send_params send_params = {}; + struct kvec *recv_mem; + void *send_buff; + int err; + + send_buff = libie_cp_can_send_onstack(req->send_size) ? + &onstack_send_buff : kzalloc(req->send_size, GFP_KERNEL); + if (!send_buff) + return -ENOMEM; + + ixd_ctlq_init_sparams(adapter, &send_params, send_buff, + req->send_size); + + send_params.chnl_opcode = req->opcode; + + if (req->send_buff_init) + req->send_buff_init(adapter, send_buff, req->ctx); + + ixd_ctlq_clean_sq(adapter, false); + err = libie_ctlq_xn_send(&send_params); + if (err) + return err; + + recv_mem = &send_params.recv_mem; + if (req->recv_process) + err = req->recv_process(adapter, recv_mem->iov_base, + recv_mem->iov_len, req->ctx); + + libie_ctlq_release_rx_buf(recv_mem); + + return err; +} + +/** + * ixd_ctlq_handle_msg - Default control queue message handler + * @ctx: Control plane communication context + * @msg: Message received + */ +static void ixd_ctlq_handle_msg(struct libie_ctlq_ctx *ctx, + struct libie_ctlq_msg *msg) +{ + struct ixd_adapter *adapter = pci_get_drvdata(ctx->mmio_info.pdev); + + if (ixd_vc_can_handle_msg(msg)) + ixd_vc_recv_event_msg(adapter, msg); + else + dev_dbg_ratelimited(ixd_to_dev(adapter), + "Received an unsupported opcode 0x%x from the CP\n", + msg->chnl_opcode); + + libie_ctlq_release_rx_buf(&msg->recv_mem); +} + +/** + * ixd_ctlq_recv_mb_msg - Receive a potential message over mailbox periodically + * @adapter: The adapter with initialized mailbox + */ +static void ixd_ctlq_recv_mb_msg(struct ixd_adapter *adapter) +{ + struct libie_ctlq_xn_recv_params xn_params = { + .xnm = adapter->xnm, + .ctlq = adapter->arq, + .ctlq_msg_handler = ixd_ctlq_handle_msg, + .budget = LIBIE_CTLQ_MAX_XN_ENTRIES, + }; + + libie_ctlq_xn_recv(&xn_params); +} + +/** + * ixd_ctlq_rx_task - Periodically check for mailbox responses and events + * @work: work handle + */ +void ixd_ctlq_rx_task(struct work_struct *work) +{ + struct ixd_adapter *adapter; + + adapter = container_of(work, struct ixd_adapter, mbx_task.work); + + queue_delayed_work(system_dfl_wq, &adapter->mbx_task, + IXD_CTLQ_RX_TASK_DELAY_JIFFIES); + + ixd_ctlq_recv_mb_msg(adapter); +} diff --git a/drivers/net/ethernet/intel/ixd/ixd_ctlq.h b/drivers/net/ethernet/intel/ixd/ixd_ctlq.h new file mode 100644 index 000000000000..8839f9f8f6d5 --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd_ctlq.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2025 Intel Corporation */ + +#ifndef _IXD_CTLQ_H_ +#define _IXD_CTLQ_H_ + +#include + +#define IXD_CTLQ_TIMEOUT 2000 + +/** + * struct ixd_ctlq_req - Standard virtchnl request description + * @opcode: protocol opcode, only virtchnl2 is needed for now + * @send_size: required length of the send buffer + * @send_buff_init: function to initialize the allocated send buffer + * @recv_process: function to handle the CP response + * @ctx: additional context for callbacks + */ +struct ixd_ctlq_req { + enum virtchnl2_op opcode; + size_t send_size; + void (*send_buff_init)(struct ixd_adapter *adapter, void *send_buff, + void *ctx); + int (*recv_process)(struct ixd_adapter *adapter, void *recv_buff, + size_t recv_size, void *ctx); + void *ctx; +}; + +void ixd_ctlq_clean_sq(struct ixd_adapter *adapter, bool force); +int ixd_ctlq_do_req(struct ixd_adapter *adapter, + const struct ixd_ctlq_req *req); +void ixd_ctlq_rx_task(struct work_struct *work); + +#endif /* _IXD_CTLQ_H_ */ diff --git a/drivers/net/ethernet/intel/ixd/ixd_lib.c b/drivers/net/ethernet/intel/ixd/ixd_lib.c index 0d601fc091a6..481edccc35ff 100644 --- a/drivers/net/ethernet/intel/ixd/ixd_lib.c +++ b/drivers/net/ethernet/intel/ixd/ixd_lib.c @@ -2,6 +2,8 @@ /* Copyright (C) 2025 Intel Corporation */ #include "ixd.h" +#include "ixd_ctlq.h" +#include "ixd_virtchnl.h" #define IXD_DFLT_MBX_Q_LEN 64 @@ -60,6 +62,14 @@ static void ixd_adapter_fill_dflt_ctlqs(struct ixd_adapter *adapter) */ void ixd_deinit_dflt_mbx(struct ixd_adapter *adapter) { + cancel_delayed_work_sync(&adapter->mbx_task); + + if (adapter->xnm) + libie_ctlq_xn_shutdown(adapter->xnm); + + if (adapter->asq) + ixd_ctlq_clean_sq(adapter, true); + if (adapter->xnm) libie_ctlq_xn_deinit(adapter->xnm, &adapter->cp_ctx); @@ -101,6 +111,8 @@ int ixd_init_dflt_mbx(struct ixd_adapter *adapter) return -ENOENT; } + queue_delayed_work(system_dfl_wq, &adapter->mbx_task, 0); + return 0; } @@ -129,8 +141,30 @@ void ixd_init_task(struct work_struct *work) adapter->init_task.reset_retries = 0; err = ixd_init_dflt_mbx(adapter); - if (err) + if (err) { dev_err(ixd_to_dev(adapter), "Failed to initialize the default mailbox: %pe\n", ERR_PTR(err)); + return; + } + + err = ixd_vc_dev_init(adapter); + if (!err) { + adapter->init_task.vc_retries = 0; + return; + } + + libie_ctlq_xn_shutdown(adapter->xnm); + ixd_trigger_reset(adapter); + ixd_deinit_dflt_mbx(adapter); + if (++adapter->init_task.vc_retries > 5 || + (err != -ETIMEDOUT && err != -EAGAIN && err != -EBUSY)) { + dev_err(ixd_to_dev(adapter), + "Failed to establish mailbox communication with the hardware: %pe\n", + ERR_PTR(err)); + return; + } + + queue_delayed_work(system_dfl_wq, &adapter->init_task.init_work, + IXD_INIT_TASK_DELAY_JIFFIES); } diff --git a/drivers/net/ethernet/intel/ixd/ixd_main.c b/drivers/net/ethernet/intel/ixd/ixd_main.c index a085b40dc75a..32046e4ba553 100644 --- a/drivers/net/ethernet/intel/ixd/ixd_main.c +++ b/drivers/net/ethernet/intel/ixd/ixd_main.c @@ -2,6 +2,7 @@ /* Copyright (C) 2025 Intel Corporation */ #include "ixd.h" +#include "ixd_ctlq.h" #include "ixd_lan_regs.h" MODULE_DESCRIPTION("Intel(R) Control Plane Function Device Driver"); @@ -19,6 +20,7 @@ static void ixd_remove(struct pci_dev *pdev) /* Do not mix removal with (re)initialization */ cancel_delayed_work_sync(&adapter->init_task.init_work); + /* Leave the device clean on exit */ if (adapter->xnm) libie_ctlq_xn_shutdown(adapter->xnm); @@ -109,6 +111,7 @@ static int ixd_probe(struct pci_dev *pdev, const struct pci_device_id *ent) INIT_DELAYED_WORK(&adapter->init_task.init_work, ixd_init_task); + INIT_DELAYED_WORK(&adapter->mbx_task, ixd_ctlq_rx_task); ixd_trigger_reset(adapter); queue_delayed_work(system_dfl_wq, &adapter->init_task.init_work, diff --git a/drivers/net/ethernet/intel/ixd/ixd_virtchnl.c b/drivers/net/ethernet/intel/ixd/ixd_virtchnl.c new file mode 100644 index 000000000000..fc3b6d2e28c5 --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd_virtchnl.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (C) 2025 Intel Corporation */ + +#include "ixd.h" +#include "ixd_ctlq.h" +#include "ixd_virtchnl.h" + +/** + * ixd_vc_recv_event_msg - Handle virtchnl event message + * @adapter: The adapter handling the message + * @ctlq_msg: Message received + */ +void ixd_vc_recv_event_msg(struct ixd_adapter *adapter, + struct libie_ctlq_msg *ctlq_msg) +{ + int payload_size = ctlq_msg->data_len; + struct virtchnl2_event *v2e; + + if (payload_size < sizeof(*v2e)) { + dev_warn_ratelimited(ixd_to_dev(adapter), + "Failed to receive valid payload for event msg (op 0x%X len %u)\n", + ctlq_msg->chnl_opcode, + payload_size); + return; + } + + v2e = (struct virtchnl2_event *)ctlq_msg->recv_mem.iov_base; + + dev_dbg(ixd_to_dev(adapter), "Got event 0x%X from the CP\n", + le32_to_cpu(v2e->event)); +} + +/** + * ixd_vc_can_handle_msg - Decide if an event has to be handled by virtchnl code + * @ctlq_msg: Message received + * + * Return: %true if virtchnl code can handle the event, %false otherwise + */ +bool ixd_vc_can_handle_msg(struct libie_ctlq_msg *ctlq_msg) +{ + return ctlq_msg->chnl_opcode == VIRTCHNL2_OP_EVENT; +} + +/** + * ixd_handle_caps - Handle VIRTCHNL2_OP_GET_CAPS response + * @adapter: The adapter for which the capabilities are being updated + * @recv_buff: Buffer containing the response + * @recv_size: Response buffer size + * @ctx: unused + * + * Return: %0 if the response format is correct and was handled as expected, + * negative error otherwise. + */ +static int ixd_handle_caps(struct ixd_adapter *adapter, void *recv_buff, + size_t recv_size, void *ctx) +{ + if (recv_size < sizeof(adapter->caps)) + return -EBADMSG; + + adapter->caps = *(typeof(adapter->caps) *)recv_buff; + + return 0; +} + +/** + * ixd_req_vc_caps - Request and save device capability + * @adapter: The adapter to get the capabilities for + * + * Return: success or error if sending the get capability message fails + */ +static int ixd_req_vc_caps(struct ixd_adapter *adapter) +{ + const struct ixd_ctlq_req req = { + .opcode = VIRTCHNL2_OP_GET_CAPS, + .send_size = sizeof(struct virtchnl2_get_capabilities), + .ctx = NULL, + .send_buff_init = NULL, + .recv_process = ixd_handle_caps, + }; + + return ixd_ctlq_do_req(adapter, &req); +} + +/** + * ixd_get_vc_ver - Get version info from adapter + * + * Return: filled in virtchannel2 version info, ready for sending + */ +static struct virtchnl2_version_info ixd_get_vc_ver(void) +{ + return (struct virtchnl2_version_info) { + .major = cpu_to_le32(VIRTCHNL2_VERSION_MAJOR_2), + .minor = cpu_to_le32(VIRTCHNL2_VERSION_MINOR_0), + }; +} + +static void ixd_fill_vc_ver(struct ixd_adapter *adapter, void *send_buff, + void *ctx) +{ + *(struct virtchnl2_version_info *)send_buff = ixd_get_vc_ver(); +} + +/** + * ixd_handle_vc_ver - Handle VIRTCHNL2_OP_VERSION response + * @adapter: The adapter for which the version is being updated + * @recv_buff: Buffer containing the response + * @recv_size: Response buffer size + * @ctx: Unused + * + * Return: %0 if the response format is correct and was handled as expected, + * negative error otherwise. + */ +static int ixd_handle_vc_ver(struct ixd_adapter *adapter, void *recv_buff, + size_t recv_size, void *ctx) +{ + struct virtchnl2_version_info need_ver = ixd_get_vc_ver(); + struct virtchnl2_version_info *recv_ver; + + if (recv_size < sizeof(need_ver)) + return -EBADMSG; + + recv_ver = recv_buff; + if (le32_to_cpu(need_ver.major) != le32_to_cpu(recv_ver->major) || + le32_to_cpu(need_ver.minor) != le32_to_cpu(recv_ver->minor)) + dev_warn(ixd_to_dev(adapter), + "Virtchnl version does not match (expected %u.%u, received %u.%u)\n", + le32_to_cpu(need_ver.major), + le32_to_cpu(need_ver.minor), + le32_to_cpu(recv_ver->major), + le32_to_cpu(recv_ver->minor)); + + if (le32_to_cpu(need_ver.major) != le32_to_cpu(recv_ver->major)) { + dev_err(ixd_to_dev(adapter), + "Device initialization failed due to virtchnl major version mismatch\n"); + return -EOPNOTSUPP; + } + + adapter->vc_ver.major = le32_to_cpu(recv_ver->major); + adapter->vc_ver.minor = le32_to_cpu(recv_ver->minor); + + return 0; +} + +/** + * ixd_req_vc_version - Request and save Virtchannel2 version + * @adapter: The adapter to get the version for + * + * Return: success or error if sending fails or the response was not as expected + */ +static int ixd_req_vc_version(struct ixd_adapter *adapter) +{ + const struct ixd_ctlq_req req = { + .opcode = VIRTCHNL2_OP_VERSION, + .send_size = sizeof(struct virtchnl2_version_info), + .ctx = NULL, + .send_buff_init = ixd_fill_vc_ver, + .recv_process = ixd_handle_vc_ver, + }; + + return ixd_ctlq_do_req(adapter, &req); +} + +/** + * ixd_vc_dev_init - virtchnl device core initialization + * @adapter: device information + * + * Return: %0 on success or error if any step of the initialization fails + */ +int ixd_vc_dev_init(struct ixd_adapter *adapter) +{ + int err; + + err = ixd_req_vc_version(adapter); + if (err) { + dev_warn(ixd_to_dev(adapter), + "Getting virtchnl version failed, error=%pe\n", + ERR_PTR(err)); + return err; + } + + err = ixd_req_vc_caps(adapter); + if (err) { + dev_warn(ixd_to_dev(adapter), + "Getting virtchnl capabilities failed, error=%pe\n", + ERR_PTR(err)); + return err; + } + + return err; +} diff --git a/drivers/net/ethernet/intel/ixd/ixd_virtchnl.h b/drivers/net/ethernet/intel/ixd/ixd_virtchnl.h new file mode 100644 index 000000000000..1a53da8b545c --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd_virtchnl.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2025 Intel Corporation */ + +#ifndef _IXD_VIRTCHNL_H_ +#define _IXD_VIRTCHNL_H_ + +int ixd_vc_dev_init(struct ixd_adapter *adapter); +bool ixd_vc_can_handle_msg(struct libie_ctlq_msg *ctlq_msg); +void ixd_vc_recv_event_msg(struct ixd_adapter *adapter, + struct libie_ctlq_msg *ctlq_msg); + +#endif /* _IXD_VIRTCHNL_H_ */ From f290d48f0299ced7f2cc939579396e79019a1b7c Mon Sep 17 00:00:00 2001 From: Amritha Nambiar Date: Thu, 25 Jun 2026 18:02:09 +0200 Subject: [PATCH 1260/1433] ixd: add devlink support Enable initial support for the devlink interface with the ixd driver. The ixd hardware is a single function PCIe device. So, the PCIe adapter gets its own devlink instance to manage device-wide resources or configuration. $ devlink dev show pci/0000:83:00.6 $ devlink dev info pci/0000:83:00.6 pci/0000:83:00.6: driver ixd serial_number 00-a0-c9-ff-ff-23-45-67 versions: fixed: device.type MEV running: fw.mgmt.api 2.0 Signed-off-by: Amritha Nambiar Reviewed-by: Michal Swiatkowski Reviewed-by: Maciej Fijalkowski Reviewed-by: Przemek Kitszel Tested-by: Bharath R Signed-off-by: Larysa Zaremba Signed-off-by: Tony Nguyen --- Documentation/networking/devlink/index.rst | 1 + Documentation/networking/devlink/ixd.rst | 30 ++++++ drivers/net/ethernet/intel/ixd/Kconfig | 1 + drivers/net/ethernet/intel/ixd/Makefile | 1 + drivers/net/ethernet/intel/ixd/ixd.h | 2 + drivers/net/ethernet/intel/ixd/ixd_devlink.c | 99 ++++++++++++++++++++ drivers/net/ethernet/intel/ixd/ixd_devlink.h | 50 ++++++++++ drivers/net/ethernet/intel/ixd/ixd_lib.c | 3 + drivers/net/ethernet/intel/ixd/ixd_main.c | 14 ++- 9 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 Documentation/networking/devlink/ixd.rst create mode 100644 drivers/net/ethernet/intel/ixd/ixd_devlink.c create mode 100644 drivers/net/ethernet/intel/ixd/ixd_devlink.h diff --git a/Documentation/networking/devlink/index.rst b/Documentation/networking/devlink/index.rst index 4745148fecf4..d4a83fdcff7f 100644 --- a/Documentation/networking/devlink/index.rst +++ b/Documentation/networking/devlink/index.rst @@ -87,6 +87,7 @@ parameters, info versions, and other features it supports. ice ionic iosm + ixd ixgbe kvaser_pciefd kvaser_usb diff --git a/Documentation/networking/devlink/ixd.rst b/Documentation/networking/devlink/ixd.rst new file mode 100644 index 000000000000..cba4f6f65013 --- /dev/null +++ b/Documentation/networking/devlink/ixd.rst @@ -0,0 +1,30 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=================== +ixd devlink support +=================== + +This document describes the devlink features implemented by the ``ixd`` +device driver. + +Info versions +============= + +The ``ixd`` driver reports the following versions + +.. list-table:: devlink info versions implemented + :widths: 5 5 5 90 + + * - Name + - Type + - Example + - Description + * - ``device.type`` + - fixed + - MEV + - The hardware type for this device + * - ``fw.mgmt.api`` + - running + - 2.0 + - 2-digit version number (major.minor) of the communication channel + (virtchnl) used by the device. diff --git a/drivers/net/ethernet/intel/ixd/Kconfig b/drivers/net/ethernet/intel/ixd/Kconfig index 2895f9723bdf..0a48b3bb7bc2 100644 --- a/drivers/net/ethernet/intel/ixd/Kconfig +++ b/drivers/net/ethernet/intel/ixd/Kconfig @@ -6,6 +6,7 @@ config IXD depends on PCI_MSI select LIBIE_CP select LIBIE_PCI + select NET_DEVLINK help This driver supports Intel(R) Control Plane PCI Function of Intel E2100 and later IPUs and FNICs. diff --git a/drivers/net/ethernet/intel/ixd/Makefile b/drivers/net/ethernet/intel/ixd/Makefile index 90abf231fb16..03760a2580b9 100644 --- a/drivers/net/ethernet/intel/ixd/Makefile +++ b/drivers/net/ethernet/intel/ixd/Makefile @@ -8,5 +8,6 @@ obj-$(CONFIG_IXD) += ixd.o ixd-y := ixd_main.o ixd-y += ixd_ctlq.o ixd-y += ixd_dev.o +ixd-y += ixd_devlink.o ixd-y += ixd_lib.o ixd-y += ixd_virtchnl.o diff --git a/drivers/net/ethernet/intel/ixd/ixd.h b/drivers/net/ethernet/intel/ixd/ixd.h index 4c970031451a..2a09ccba13d5 100644 --- a/drivers/net/ethernet/intel/ixd/ixd.h +++ b/drivers/net/ethernet/intel/ixd/ixd.h @@ -15,6 +15,7 @@ * @init_task.init_work: Delayed initialization work * @init_task.reset_retries: How many times to check, whether reset is completed * @init_task.vc_retries: Number of retries to establish mailbox communication + * @init_task.success: init_work completion status * @mbx_task: Control queue Rx handling * @xnm: virtchnl transaction manager * @asq: Send control queue info @@ -30,6 +31,7 @@ struct ixd_adapter { struct delayed_work init_work; u8 reset_retries; u8 vc_retries; + bool success; } init_task; struct delayed_work mbx_task; struct libie_ctlq_xn_manager *xnm; diff --git a/drivers/net/ethernet/intel/ixd/ixd_devlink.c b/drivers/net/ethernet/intel/ixd/ixd_devlink.c new file mode 100644 index 000000000000..828132db8323 --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd_devlink.c @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025, Intel Corporation. */ + +#include "ixd.h" +#include "ixd_devlink.h" + +#define IXD_DEVLINK_INFO_LEN 128 + +/** + * ixd_fill_dsn - Get the serial number for the ixd device + * @adapter: adapter to query + * @buf: storage buffer for the info request + */ +static void ixd_fill_dsn(struct ixd_adapter *adapter, char *buf) +{ + u8 dsn[8]; + + /* Copy the DSN into an array in Big Endian format */ + put_unaligned_be64(pci_get_dsn(adapter->cp_ctx.mmio_info.pdev), dsn); + + snprintf(buf, IXD_DEVLINK_INFO_LEN, "%8phD", dsn); +} + +/** + * ixd_fill_device_name - Get the name of the underlying hardware + * @adapter: adapter to query + * @buf: storage buffer for the info request + * @buf_size: size of the storage buffer + */ +static void ixd_fill_device_name(struct ixd_adapter *adapter, char *buf, + size_t buf_size) +{ + if (adapter->caps.device_type == cpu_to_le32(VIRTCHNL2_MEV_DEVICE)) + snprintf(buf, buf_size, "%s", "MEV"); + else + snprintf(buf, buf_size, "%s", "UNKNOWN"); +} + +/** + * ixd_devlink_info_get - .info_get devlink handler + * @devlink: devlink instance structure + * @req: the devlink info request + * @extack: extended netdev ack structure + * + * Callback for the devlink .info_get operation. Reports information about the + * device. + * + * Return: zero on success or an error code on failure. + */ +static int ixd_devlink_info_get(struct devlink *devlink, + struct devlink_info_req *req, + struct netlink_ext_ack *extack) +{ + struct ixd_adapter *adapter = devlink_priv(devlink); + char buf[IXD_DEVLINK_INFO_LEN]; + int err; + + ixd_fill_dsn(adapter, buf); + err = devlink_info_serial_number_put(req, buf); + if (err) + return err; + + ixd_fill_device_name(adapter, buf, IXD_DEVLINK_INFO_LEN); + err = devlink_info_version_fixed_put(req, "device.type", buf); + if (err) + return err; + + snprintf(buf, sizeof(buf), "%u.%u", + adapter->vc_ver.major, adapter->vc_ver.minor); + + return devlink_info_version_running_put(req, + DEVLINK_INFO_VERSION_GENERIC_FW_MGMT_API, + buf); +} + +static const struct devlink_ops ixd_devlink_ops = { + .info_get = ixd_devlink_info_get, +}; + +/** + * ixd_adapter_alloc - Allocate devlink and return adapter pointer + * @dev: the device to allocate for + * + * Allocate a devlink instance for this device and return the private area as + * the adapter structure. + * + * Return: adapter structure on success, NULL on failure + */ +struct ixd_adapter *ixd_adapter_alloc(struct device *dev) +{ + struct devlink *devlink; + + devlink = devlink_alloc(&ixd_devlink_ops, sizeof(struct ixd_adapter), + dev); + if (!devlink) + return NULL; + + return devlink_priv(devlink); +} diff --git a/drivers/net/ethernet/intel/ixd/ixd_devlink.h b/drivers/net/ethernet/intel/ixd/ixd_devlink.h new file mode 100644 index 000000000000..b23a1b37aebc --- /dev/null +++ b/drivers/net/ethernet/intel/ixd/ixd_devlink.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2025, Intel Corporation. */ + +#ifndef _IXD_DEVLINK_H_ +#define _IXD_DEVLINK_H_ + +#include + +#include "ixd.h" + +struct ixd_adapter *ixd_adapter_alloc(struct device *dev); + +/** + * ixd_devlink_free - teardown the devlink + * @adapter: the adapter structure to free + * + */ +static inline void ixd_devlink_free(struct ixd_adapter *adapter) +{ + struct devlink *devlink = priv_to_devlink(adapter); + + devlink_free(devlink); +} + +/** + * ixd_devlink_unregister - Unregister devlink for this adapter. + * @adapter: the adapter structure to cleanup + * + * Init task must be completed or cancelled beforehand. + */ +static inline void ixd_devlink_unregister(struct ixd_adapter *adapter) +{ + if (!adapter->init_task.success) + return; + + devlink_unregister(priv_to_devlink(adapter)); +} + +/** + * ixd_devlink_register - Register devlink interface for this adapter + * @adapter: pointer to ixd adapter structure to be associated with devlink + * + * Register the devlink instance associated with this adapter + */ +static inline void ixd_devlink_register(struct ixd_adapter *adapter) +{ + devlink_register(priv_to_devlink(adapter)); +} + +#endif /* _IXD_DEVLINK_H_ */ diff --git a/drivers/net/ethernet/intel/ixd/ixd_lib.c b/drivers/net/ethernet/intel/ixd/ixd_lib.c index 481edccc35ff..8311f7590666 100644 --- a/drivers/net/ethernet/intel/ixd/ixd_lib.c +++ b/drivers/net/ethernet/intel/ixd/ixd_lib.c @@ -3,6 +3,7 @@ #include "ixd.h" #include "ixd_ctlq.h" +#include "ixd_devlink.h" #include "ixd_virtchnl.h" #define IXD_DFLT_MBX_Q_LEN 64 @@ -151,6 +152,8 @@ void ixd_init_task(struct work_struct *work) err = ixd_vc_dev_init(adapter); if (!err) { adapter->init_task.vc_retries = 0; + adapter->init_task.success = true; + ixd_devlink_register(adapter); return; } diff --git a/drivers/net/ethernet/intel/ixd/ixd_main.c b/drivers/net/ethernet/intel/ixd/ixd_main.c index 32046e4ba553..5db992d4f9fb 100644 --- a/drivers/net/ethernet/intel/ixd/ixd_main.c +++ b/drivers/net/ethernet/intel/ixd/ixd_main.c @@ -4,6 +4,7 @@ #include "ixd.h" #include "ixd_ctlq.h" #include "ixd_lan_regs.h" +#include "ixd_devlink.h" MODULE_DESCRIPTION("Intel(R) Control Plane Function Device Driver"); MODULE_IMPORT_NS("LIBIE_CP"); @@ -21,6 +22,8 @@ static void ixd_remove(struct pci_dev *pdev) /* Do not mix removal with (re)initialization */ cancel_delayed_work_sync(&adapter->init_task.init_work); + ixd_devlink_unregister(adapter); + /* Leave the device clean on exit */ if (adapter->xnm) libie_ctlq_xn_shutdown(adapter->xnm); @@ -28,6 +31,7 @@ static void ixd_remove(struct pci_dev *pdev) ixd_deinit_dflt_mbx(adapter); libie_pci_unmap_all_mmio_regions(&adapter->cp_ctx.mmio_info); + ixd_devlink_free(adapter); } /** @@ -92,7 +96,7 @@ static int ixd_probe(struct pci_dev *pdev, const struct pci_device_id *ent) struct ixd_adapter *adapter; int err; - adapter = devm_kzalloc(&pdev->dev, sizeof(*adapter), GFP_KERNEL); + adapter = ixd_adapter_alloc(&pdev->dev); if (!adapter) return -ENOMEM; @@ -101,13 +105,13 @@ static int ixd_probe(struct pci_dev *pdev, const struct pci_device_id *ent) err = libie_pci_init_dev(pdev); if (err) - return err; + goto free_adapter; pci_set_drvdata(pdev, adapter); err = ixd_iomap_regions(adapter); if (err) - return err; + goto free_adapter; INIT_DELAYED_WORK(&adapter->init_task.init_work, ixd_init_task); @@ -118,6 +122,10 @@ static int ixd_probe(struct pci_dev *pdev, const struct pci_device_id *ent) IXD_INIT_TASK_DELAY_JIFFIES); return 0; + +free_adapter: + ixd_devlink_free(adapter); + return err; } static const struct pci_device_id ixd_pci_tbl[] = { From 095887cb96a36b917dbdf163c165e2f294d8eefa Mon Sep 17 00:00:00 2001 From: Qingfang Deng Date: Tue, 11 Aug 2026 14:02:29 +0800 Subject: [PATCH 1261/1433] ppp: annotate lockless queue empty check ppp_poll() checks whether pf->rq contains a packet without holding the queue lock. skb_peek() requires appropriate locking or a private queue, neither of which applies because ppp_input() can enqueue concurrently. Only queue emptiness is needed, so use skb_queue_empty_lockless() instead. Cc: stable+noautosel@kernel.org # race annotation Signed-off-by: Qingfang Deng Reviewed-by: Breno Leitao --- drivers/net/ppp/ppp_generic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c index e1013621eb1d..1a610a18893b 100644 --- a/drivers/net/ppp/ppp_generic.c +++ b/drivers/net/ppp/ppp_generic.c @@ -556,7 +556,7 @@ static __poll_t ppp_poll(struct file *file, poll_table *wait) return 0; poll_wait(file, &pf->rwait, wait); mask = EPOLLOUT | EPOLLWRNORM; - if (skb_peek(&pf->rq)) + if (!skb_queue_empty_lockless(&pf->rq)) mask |= EPOLLIN | EPOLLRDNORM; if (pf->dead) mask |= EPOLLHUP; From a0d6255b4adcd5b903c289921284c3c362fc1fd6 Mon Sep 17 00:00:00 2001 From: Qingfang Deng Date: Tue, 11 Aug 2026 15:49:47 +0800 Subject: [PATCH 1262/1433] pptp: drop packets received before connect pptp_bind() publishes the socket by its local call ID before it is connected, so GRE packets can reach pptp_rcv_core() while PPPOX_CONNECTED is clear. Such packets are queued on sk_receive_queue, but PPTP provides no recvmsg operation and never drains the queue after connect. The packets therefore remain there until socket destruction. Drop such packets immediately instead. Since PPTP no longer queues packets on sk_receive_queue, remove the corresponding destructor purge. Signed-off-by: Qingfang Deng Link: https://patch.msgid.link/20260811074948.345834-1-qingfang.deng@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ppp/pptp.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/net/ppp/pptp.c b/drivers/net/ppp/pptp.c index cc8c102122d8..a797a0606f6b 100644 --- a/drivers/net/ppp/pptp.c +++ b/drivers/net/ppp/pptp.c @@ -278,11 +278,8 @@ static int pptp_rcv_core(struct sock *sk, struct sk_buff *skb) __u8 *payload; struct pptp_gre_header *header; - if (!(sk->sk_state & PPPOX_CONNECTED)) { - if (sock_queue_rcv_skb(sk, skb)) - goto drop; - return NET_RX_SUCCESS; - } + if (!(sk->sk_state & PPPOX_CONNECTED)) + goto drop; header = (struct pptp_gre_header *)(skb->data); headersize = sizeof(*header); @@ -539,7 +536,6 @@ static void pptp_sock_destruct(struct sock *sk) del_chan(pppox_sk(sk)); pppox_unbind_sock(sk); } - skb_queue_purge(&sk->sk_receive_queue); dst_release(rcu_dereference_protected(sk->sk_dst_cache, 1)); } From dbf34acdfb29781dd4a8a86f7111d99c016fc7e8 Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Tue, 11 Aug 2026 14:26:50 -0400 Subject: [PATCH 1263/1433] selftests: drv-net: so_txtime: fix qdisc replace with handle The blamed commit updated a tc replace command by adding a handle. - tc(f"qdisc replace dev {ifname} root {qdisc} {optargs}") + tc(f"qdisc replace dev {ifname} root handle 1: {qdisc} {optargs}") This breaks the test if the root qdisc already has that handle and is of different kind, with "Invalid qdisc name: must match existing qdisc." If no handle is asked, or the kind differs, tc replace removes the old qdisc and grafts a new one. If a handle is asked and exists, tc replace tries to change the qdisc in place, for which the kind must be the same. It does not trigger in all setups, like netdevsim or debian 13, which do not have root handle 1:. But it is a common root handle. Solve the bug by first deleting the existing root qdisc if one exists. Wrap that command in a try block, because it will fail for default qdiscs with handle 0: with "Error: Cannot delete qdisc with handle of zero." Reported-by: Jakub Kicinski Closes: https://lore.kernel.org/netdev/20260810183118.32d5c06a@kernel.org/ Fixes: ef3d6cca02c8 ("selftests: drv-net: so_txtime: only send test traffic to sch_etf") Signed-off-by: Willem de Bruijn Link: https://patch.msgid.link/20260811182856.2702163-1-willemdebruijn.kernel@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/so_txtime.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/testing/selftests/drivers/net/so_txtime.py b/tools/testing/selftests/drivers/net/so_txtime.py index 9fbc0278d28b..a097fae0b335 100755 --- a/tools/testing/selftests/drivers/net/so_txtime.py +++ b/tools/testing/selftests/drivers/net/so_txtime.py @@ -12,6 +12,7 @@ import time from lib.py import ksft_exit, ksft_run, ksft_variants from lib.py import KsftNamedVariant, KsftSkipEx from lib.py import NetDrvEpEnv, bkg, cmd, defer, tc +from lib.py import CmdExitFailure def test_so_txtime(cfg, clockid, ipver, args_tx, args_rx, expect_success): @@ -45,6 +46,10 @@ def _qdisc_setup(ifname, qdisc, optargs=""): """ orig = tc(f"qdisc show dev {ifname} root", json=True)[0].get("kind", None) defer(tc, f"qdisc replace dev {ifname} root {orig}") + try: + tc(f"qdisc del dev {ifname} root") + except CmdExitFailure: + pass tc(f"qdisc replace dev {ifname} root handle 1: {qdisc} {optargs}") From d77f3f01682688ff5db18f027f66c34b4b39e1ac Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 8 Aug 2026 12:19:41 +0200 Subject: [PATCH 1264/1433] r8169: give RTL_GIGA_MAC_VER_EXTENDED a distinct value RTL_GIGA_MAC_VER_EXTENDED implicitly follows RTL_GIGA_MAC_VER_LAST = RTL_GIGA_MAC_NONE - 1, so it has the same value as RTL_GIGA_MAC_NONE. rtl_init_one() therefore sends unknown chips through extended detection. If TX_CONFIG_V2 reads as zero, they are misidentified as RTL9151AS instead of being rejected. Give RTL_GIGA_MAC_VER_EXTENDED a distinct value. It is only a detection marker and is never stored in tp->mac_version. Found by Clang's -Wduplicate-enum and verified with a QEMU stub. Cc: stable+noautosel@kernel.org # untested fix to unlikely driver error path Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260808101941.57666-1-kmehltretter@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/realtek/r8169.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/realtek/r8169.h b/drivers/net/ethernet/realtek/r8169.h index 0b9c1d4eb48b..fb772cc04310 100644 --- a/drivers/net/ethernet/realtek/r8169.h +++ b/drivers/net/ethernet/realtek/r8169.h @@ -74,7 +74,7 @@ enum mac_version { RTL_GIGA_MAC_VER_80, RTL_GIGA_MAC_NONE, RTL_GIGA_MAC_VER_LAST = RTL_GIGA_MAC_NONE - 1, - RTL_GIGA_MAC_VER_EXTENDED + RTL_GIGA_MAC_VER_EXTENDED = RTL_GIGA_MAC_NONE + 1 }; struct rtl8169_private; From 9b20885f147287bc13deef55effe7a400a9561aa Mon Sep 17 00:00:00 2001 From: Ziran Zhang Date: Wed, 5 Aug 2026 21:19:27 +0800 Subject: [PATCH 1265/1433] tcp: clarify comment for mdev_us in struct tcp_sock The existing comment for mdev_us says "medium deviation", but this term is inaccurate. The field stores the "mean deviation" of RTT, as originally defined in Van Jacobson's paper "Congestion Avoidance and Control", and it is scaled by 4 (<< 2) in the Linux implementation. Update the comment to reflect the correct terminology and storage format. Signed-off-by: Ziran Zhang Reviewed-by: Fernando Fernandez Mancera Link: https://patch.msgid.link/20260805131927.27661-1-zhangcoder@yeah.net Signed-off-by: Jakub Kicinski --- include/linux/tcp.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/tcp.h b/include/linux/tcp.h index 8a6807082672..6a8c77719322 100644 --- a/include/linux/tcp.h +++ b/include/linux/tcp.h @@ -274,7 +274,7 @@ struct tcp_sock { u32 write_seq; /* Tail(+1) of data held in tcp send buffer */ u32 pushed_seq; /* Last pushed seq, required to talk to windows */ u32 lsndtime; /* timestamp of last sent data packet (for restart window) */ - u32 mdev_us; /* medium deviation */ + u32 mdev_us; /* mean deviation of RTT, scaled by 4 (<< 2) in usecs */ u32 rtt_seq; /* sequence number to update rttvar */ u32 max_packets_out; /* max packets_out in last window */ u32 cwnd_usage_seq; /* right edge of cwnd usage tracking flight */ From cc17e386f8a5fc23a3bf49a45d17cd45b49fbb01 Mon Sep 17 00:00:00 2001 From: Yuyang Huang Date: Sat, 8 Aug 2026 09:56:33 +0900 Subject: [PATCH 1266/1433] ipv6: add ip6_del_rt_reason() Add RTA_DEL_REASON and enum rt_del_reason to the rtnetlink uAPI, and add ip6_del_rt_reason(), which takes the reason a route is being deleted. It has no skip_notify argument: a caller that records a deletion reason wants the notification that carries it. The reason is unused for now. Subsequent patches propagate it to the deletion path and report it on RTM_DELROUTE. Signed-off-by: Yuyang Huang Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260808005642.26901-2-sigefriedhyy@gmail.com Signed-off-by: Paolo Abeni --- include/net/ip6_route.h | 2 ++ include/uapi/linux/rtnetlink.h | 25 +++++++++++++++++++++++++ net/ipv6/route.c | 8 ++++++++ 3 files changed, 35 insertions(+) diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h index 09ffe0f13ce7..cc045705862d 100644 --- a/include/net/ip6_route.h +++ b/include/net/ip6_route.h @@ -126,6 +126,8 @@ int ipv6_route_ioctl(struct net *net, unsigned int cmd, int ip6_route_add(struct fib6_config *cfg, gfp_t gfp_flags, struct netlink_ext_ack *extack); int ip6_ins_rt(struct net *net, struct fib6_info *f6i); +int ip6_del_rt_reason(struct net *net, struct fib6_info *f6i, + enum rt_del_reason del_reason); #if IS_ENABLED(CONFIG_IPV6) int ip6_del_rt(struct net *net, struct fib6_info *f6i, bool skip_notify); #else diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h index 27265fd31e5f..3988b51db539 100644 --- a/include/uapi/linux/rtnetlink.h +++ b/include/uapi/linux/rtnetlink.h @@ -399,6 +399,7 @@ enum rtattr_type_t { RTA_DPORT, RTA_NH_ID, RTA_FLOWLABEL, + RTA_DEL_REASON, __RTA_MAX }; @@ -407,6 +408,30 @@ enum rtattr_type_t { #define RTM_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg)))) #define RTM_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct rtmsg)) +/* RTA_DEL_REASON: why the kernel deleted the route. u32. + * Emitted only on RTM_DELROUTE notifications, and only when the deletion + * path records a cause. Absence means either an older kernel or a + * deletion path that does not (yet) record its cause - consumers must + * treat "absent" and "unspec" identically. New causes may be appended. + * Currently only IPv6 deletion paths record a cause. + * + * The attribute is notification-only: the kernel rejects it in + * requests, so a notification must not be echoed back verbatim. + * + * The value space is family-agnostic: a value must never be + * reinterpreted per address family. A cause that only one family can + * produce still gets its own value rather than reusing another + * family's. + */ +enum rt_del_reason { + RT_DEL_REASON_UNSPEC, /* cause not recorded */ + RT_DEL_REASON_EXPIRED, /* RTF_EXPIRES lifetime ran out (GC) */ + RT_DEL_REASON_RA_WITHDRAWN, /* zero-lifetime RA / PIO / RIO */ + __RT_DEL_REASON_MAX +}; + +#define RT_DEL_REASON_MAX (__RT_DEL_REASON_MAX - 1) + /* RTM_MULTIPATH --- array of struct rtnexthop. * * "struct rtnexthop" describes all necessary nexthop information, diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 5968ce5ad150..9898578cb418 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -4004,6 +4004,14 @@ int ip6_del_rt(struct net *net, struct fib6_info *rt, bool skip_notify) return __ip6_del_rt(rt, &info); } +int ip6_del_rt_reason(struct net *net, struct fib6_info *rt, + enum rt_del_reason del_reason) +{ + struct nl_info info = { .nl_net = net }; + + return __ip6_del_rt(rt, &info); +} + static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg) { struct nl_info *info = &cfg->fc_nlinfo; From 352c6732ffb2c5a45a10096bfef13c343484c22f Mon Sep 17 00:00:00 2001 From: Yuyang Huang Date: Sat, 8 Aug 2026 09:56:34 +0900 Subject: [PATCH 1267/1433] ipv6: propagate the route deletion reason to fib6_del_route() Pass the deletion reason from ip6_del_rt_reason() down through __ip6_del_rt(), fib6_del() and into fib6_del_route(). All existing callers pass RT_DEL_REASON_UNSPEC. fib6_del_route() ignores the reason until the notification path learns to report it. Signed-off-by: Yuyang Huang Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260808005642.26901-3-sigefriedhyy@gmail.com Signed-off-by: Paolo Abeni --- include/net/ip6_fib.h | 3 ++- net/ipv6/ip6_fib.c | 12 +++++++----- net/ipv6/route.c | 19 +++++++++++-------- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/include/net/ip6_fib.h b/include/net/ip6_fib.h index 9cd27e1b9b69..0ba5d3c56543 100644 --- a/include/net/ip6_fib.h +++ b/include/net/ip6_fib.h @@ -468,7 +468,8 @@ void fib6_clean_all_skip_notify(struct net *net, int fib6_add(struct fib6_node *root, struct fib6_info *rt, struct nl_info *info, struct netlink_ext_ack *extack); -int fib6_del(struct fib6_info *rt, struct nl_info *info); +int fib6_del(struct fib6_info *rt, struct nl_info *info, + enum rt_del_reason del_reason); static inline void rt6_get_prefsrc(const struct rt6_info *rt, struct in6_addr *addr) diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index e9fc692d4f3b..1f6f6f33ded1 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -1967,7 +1967,8 @@ static struct fib6_node *fib6_repair_tree(struct net *net, } static void fib6_del_route(struct fib6_table *table, struct fib6_node *fn, - struct fib6_info __rcu **rtp, struct nl_info *info) + struct fib6_info __rcu **rtp, struct nl_info *info, + enum rt_del_reason del_reason) { struct fib6_info *leaf, *replace_rt = NULL; struct fib6_walker *w; @@ -2062,7 +2063,8 @@ static void fib6_del_route(struct fib6_table *table, struct fib6_node *fn, } /* Need to own table->tb6_lock */ -int fib6_del(struct fib6_info *rt, struct nl_info *info) +int fib6_del(struct fib6_info *rt, struct nl_info *info, + enum rt_del_reason del_reason) { struct net *net = info->nl_net; struct fib6_info __rcu **rtp; @@ -2091,7 +2093,7 @@ int fib6_del(struct fib6_info *rt, struct nl_info *info) if (rt == cur) { if (fib6_requires_src(cur)) fib6_routes_require_src_dec(info->nl_net); - fib6_del_route(table, fn, rtp, info); + fib6_del_route(table, fn, rtp, info, del_reason); return 0; } rtp_next = &cur->fib6_next; @@ -2253,7 +2255,7 @@ static int fib6_clean_node(struct fib6_walker *w) res = c->func(rt, c->arg); if (res == -1) { w->leaf = rt; - res = fib6_del(rt, &info); + res = fib6_del(rt, &info, RT_DEL_REASON_UNSPEC); if (res) { #if RT6_DEBUG >= 2 pr_debug("%s: del failed: rt=%p@%p err=%d\n", @@ -2401,7 +2403,7 @@ static void fib6_gc_table(struct net *net, hlist_for_each_entry_safe(rt, n, &tb6->tb6_gc_hlist, gc_link) if (fib6_age(rt, gc_args) == -1) - fib6_del(rt, &info); + fib6_del(rt, &info, RT_DEL_REASON_UNSPEC); } static void fib6_gc_all(struct net *net, struct fib6_gc_args *gc_args) diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 9898578cb418..26cdf141666c 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -3973,7 +3973,8 @@ int ip6_route_add(struct fib6_config *cfg, gfp_t gfp_flags, return err; } -static int __ip6_del_rt(struct fib6_info *rt, struct nl_info *info) +static int __ip6_del_rt(struct fib6_info *rt, struct nl_info *info, + enum rt_del_reason del_reason) { struct net *net = info->nl_net; struct fib6_table *table; @@ -3986,7 +3987,7 @@ static int __ip6_del_rt(struct fib6_info *rt, struct nl_info *info) table = rt->fib6_table; spin_lock_bh(&table->tb6_lock); - err = fib6_del(rt, info); + err = fib6_del(rt, info, del_reason); spin_unlock_bh(&table->tb6_lock); out: @@ -4001,7 +4002,7 @@ int ip6_del_rt(struct net *net, struct fib6_info *rt, bool skip_notify) .skip_notify = skip_notify }; - return __ip6_del_rt(rt, &info); + return __ip6_del_rt(rt, &info, RT_DEL_REASON_UNSPEC); } int ip6_del_rt_reason(struct net *net, struct fib6_info *rt, @@ -4009,7 +4010,7 @@ int ip6_del_rt_reason(struct net *net, struct fib6_info *rt, { struct nl_info info = { .nl_net = net }; - return __ip6_del_rt(rt, &info); + return __ip6_del_rt(rt, &info, del_reason); } static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg) @@ -4072,13 +4073,13 @@ static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg) list_for_each_entry_safe(sibling, next_sibling, &rt->fib6_siblings, fib6_siblings) { - err = fib6_del(sibling, info); + err = fib6_del(sibling, info, RT_DEL_REASON_UNSPEC); if (err) goto out_unlock; } } - err = fib6_del(rt, info); + err = fib6_del(rt, info, RT_DEL_REASON_UNSPEC); out_unlock: spin_unlock_bh(&table->tb6_lock); out_put: @@ -4204,7 +4205,8 @@ static int ip6_route_del(struct fib6_config *cfg, if (!fib6_info_hold_safe(rt)) continue; - err = __ip6_del_rt(rt, &cfg->fc_nlinfo); + err = __ip6_del_rt(rt, &cfg->fc_nlinfo, + RT_DEL_REASON_UNSPEC); break; } if (cfg->fc_nh_id) @@ -4223,7 +4225,8 @@ static int ip6_route_del(struct fib6_config *cfg, /* if gateway was specified only delete the one hop */ if (cfg->fc_flags & RTF_GATEWAY) - err = __ip6_del_rt(rt, &cfg->fc_nlinfo); + err = __ip6_del_rt(rt, &cfg->fc_nlinfo, + RT_DEL_REASON_UNSPEC); else err = __ip6_del_rt_siblings(rt, cfg); break; From e8636445b771c0936cf3c2fe3bbdb281b4cf6acf Mon Sep 17 00:00:00 2001 From: Yuyang Huang Date: Sat, 8 Aug 2026 09:56:35 +0900 Subject: [PATCH 1268/1433] ipv6: record the reason for kernel-initiated route deletions Record why the kernel deletes an IPv6 route on its own: - RT_DEL_REASON_EXPIRED for routes reaped by the FIB6 garbage collector after their RTF_EXPIRES lifetime ran out. - RT_DEL_REASON_RA_WITHDRAWN for default routes, prefix routes and RFC 4191 route information routes withdrawn by a zero-lifetime Router Advertisement. Deleting a default route because its metric changed is not a withdrawal, so it keeps RT_DEL_REASON_UNSPEC. Signed-off-by: Yuyang Huang Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260808005642.26901-4-sigefriedhyy@gmail.com Signed-off-by: Paolo Abeni --- net/ipv6/addrconf.c | 3 ++- net/ipv6/ip6_fib.c | 2 +- net/ipv6/ndisc.c | 4 +++- net/ipv6/route.c | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index f6fa2715b450..9d89be7e0544 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -2874,7 +2874,8 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao) if (rt) { /* Autoconf prefix route */ if (valid_lft == 0) { - ip6_del_rt(net, rt, false); + ip6_del_rt_reason(net, rt, + RT_DEL_REASON_RA_WITHDRAWN); rt = NULL; } else { table = rt->fib6_table; diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index 1f6f6f33ded1..f8a9ec7fa616 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -2403,7 +2403,7 @@ static void fib6_gc_table(struct net *net, hlist_for_each_entry_safe(rt, n, &tb6->tb6_gc_hlist, gc_link) if (fib6_age(rt, gc_args) == -1) - fib6_del(rt, &info, RT_DEL_REASON_UNSPEC); + fib6_del(rt, &info, RT_DEL_REASON_EXPIRED); } static void fib6_gc_all(struct net *net, struct fib6_gc_args *gc_args) diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c index 2ceb655c4229..75515fd99383 100644 --- a/net/ipv6/ndisc.c +++ b/net/ipv6/ndisc.c @@ -1362,7 +1362,9 @@ static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb) defrtr_usr_metric = in6_dev->cnf.ra_defrtr_metric; /* delete the route if lifetime is 0 or if metric needs change */ if (rt && (lifetime == 0 || rt->fib6_metric != defrtr_usr_metric)) { - ip6_del_rt(net, rt, false); + ip6_del_rt_reason(net, rt, + lifetime == 0 ? RT_DEL_REASON_RA_WITHDRAWN : + RT_DEL_REASON_UNSPEC); rt = NULL; } diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 26cdf141666c..4f1e521b89ae 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -1020,7 +1020,7 @@ int rt6_route_rcv(struct net_device *dev, u8 *opt, int len, gwaddr, dev); if (rt && !lifetime) { - ip6_del_rt(net, rt, false); + ip6_del_rt_reason(net, rt, RT_DEL_REASON_RA_WITHDRAWN); rt = NULL; } From b0215102356bef144e1375aeb1633b37ec7999ad Mon Sep 17 00:00:00 2001 From: Yuyang Huang Date: Sat, 8 Aug 2026 09:56:36 +0900 Subject: [PATCH 1269/1433] ipv6: add a deletion reason argument to rt6_fill_node() Add the deletion reason to rt6_fill_node() so that it can report it to user space. All callers pass RT_DEL_REASON_UNSPEC for now. Signed-off-by: Yuyang Huang Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260808005642.26901-5-sigefriedhyy@gmail.com Signed-off-by: Paolo Abeni --- net/ipv6/route.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 4f1e521b89ae..f0071c645939 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -111,7 +111,7 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb, struct fib6_info *rt, struct dst_entry *dst, struct in6_addr *dest, struct in6_addr *src, int iif, int type, u32 portid, u32 seq, - unsigned int flags); + unsigned int flags, enum rt_del_reason del_reason); static struct rt6_info *rt6_find_cached_rt(const struct fib6_result *res, const struct in6_addr *daddr, const struct in6_addr *saddr); @@ -4037,7 +4037,8 @@ static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg) if (rt6_fill_node(net, skb, rt, NULL, NULL, NULL, 0, RTM_DELROUTE, - info->portid, seq, 0) < 0) { + info->portid, seq, 0, + RT_DEL_REASON_UNSPEC) < 0) { kfree_skb(skb); skb = NULL; } else @@ -5786,7 +5787,7 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb, struct fib6_info *rt, struct dst_entry *dst, struct in6_addr *dest, struct in6_addr *src, int iif, int type, u32 portid, u32 seq, - unsigned int flags) + unsigned int flags, enum rt_del_reason del_reason) { struct rt6_info *rt6 = dst_rt6_info(dst); struct rt6key *rt6_dst, *rt6_src; @@ -6066,7 +6067,8 @@ static int rt6_nh_dump_exceptions(struct fib6_nh *nh, void *arg) &rt6_ex->rt6i->dst, NULL, NULL, 0, RTM_NEWROUTE, NETLINK_CB(dump->cb->skb).portid, - dump->cb->nlh->nlmsg_seq, w->flags); + dump->cb->nlh->nlmsg_seq, w->flags, + RT_DEL_REASON_UNSPEC); if (err) return err; @@ -6114,7 +6116,8 @@ int rt6_dump_route(struct fib6_info *rt, void *p_arg, unsigned int skip) if (rt6_fill_node(net, arg->skb, rt, NULL, NULL, NULL, 0, RTM_NEWROUTE, NETLINK_CB(arg->cb->skb).portid, - arg->cb->nlh->nlmsg_seq, flags)) { + arg->cb->nlh->nlmsg_seq, flags, + RT_DEL_REASON_UNSPEC)) { return 0; } count++; @@ -6347,12 +6350,14 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh, err = rt6_fill_node(net, skb, from, NULL, NULL, NULL, iif, RTM_NEWROUTE, NETLINK_CB(in_skb).portid, - nlh->nlmsg_seq, 0); + nlh->nlmsg_seq, 0, + RT_DEL_REASON_UNSPEC); else err = rt6_fill_node(net, skb, from, dst, &fl6.daddr, &fl6.saddr, iif, RTM_NEWROUTE, NETLINK_CB(in_skb).portid, - nlh->nlmsg_seq, 0); + nlh->nlmsg_seq, 0, + RT_DEL_REASON_UNSPEC); } else { err = -ENETUNREACH; } @@ -6388,7 +6393,8 @@ void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info, goto errout; err = rt6_fill_node(net, skb, rt, NULL, NULL, NULL, 0, - event, info->portid, seq, nlm_flags); + event, info->portid, seq, nlm_flags, + RT_DEL_REASON_UNSPEC); if (err < 0) { kfree_skb(skb); /* -EMSGSIZE implies needed space grew under us. */ @@ -6421,7 +6427,8 @@ void fib6_rt_update(struct net *net, struct fib6_info *rt, goto errout; err = rt6_fill_node(net, skb, rt, NULL, NULL, NULL, 0, - RTM_NEWROUTE, info->portid, seq, NLM_F_REPLACE); + RTM_NEWROUTE, info->portid, seq, NLM_F_REPLACE, + RT_DEL_REASON_UNSPEC); if (err < 0) { /* -EMSGSIZE implies BUG in rt6_nlmsg_size() */ WARN_ON(err == -EMSGSIZE); @@ -6474,7 +6481,7 @@ void fib6_info_hw_flags_set(struct net *net, struct fib6_info *f6i, } err = rt6_fill_node(net, skb, f6i, NULL, NULL, NULL, 0, RTM_NEWROUTE, 0, - 0, 0); + 0, 0, RT_DEL_REASON_UNSPEC); if (err < 0) { /* -EMSGSIZE implies BUG in rt6_nlmsg_size() */ WARN_ON(err == -EMSGSIZE); From 1e6a83af59141b9caabb86a9d7d069ab696101e2 Mon Sep 17 00:00:00 2001 From: Yuyang Huang Date: Sat, 8 Aug 2026 09:56:37 +0900 Subject: [PATCH 1270/1433] ipv6: expose the route deletion reason in RTM_DELROUTE Emit RTA_DEL_REASON from rt6_fill_node() when the deletion reason is not RT_DEL_REASON_UNSPEC, and reserve room for it in rt6_nlmsg_size(). Every caller still passes RT_DEL_REASON_UNSPEC. Signed-off-by: Yuyang Huang Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260808005642.26901-6-sigefriedhyy@gmail.com Signed-off-by: Paolo Abeni --- net/ipv6/route.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/ipv6/route.c b/net/ipv6/route.c index f0071c645939..1c48332bd193 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -5751,6 +5751,7 @@ static size_t rt6_nlmsg_size(struct fib6_info *f6i) + nla_total_size(sizeof(struct rta_cacheinfo)) + nla_total_size(TCP_CA_NAME_MAX) /* RTAX_CC_ALGO */ + nla_total_size(1) /* RTA_PREF */ + + nla_total_size(4) /* RTA_DEL_REASON */ + nexthop_len; } @@ -5966,6 +5967,10 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb, if (rtnl_put_cacheinfo(skb, dst, 0, expires, dst ? dst->error : 0) < 0) goto nla_put_failure; + if (del_reason != RT_DEL_REASON_UNSPEC && + nla_put_u32(skb, RTA_DEL_REASON, del_reason)) + goto nla_put_failure; + if (nla_put_u8(skb, RTA_PREF, IPV6_EXTRACT_PREF(rt6_flags))) goto nla_put_failure; From 09f19ce3de67deb859cd95315d55a69a6ca45b40 Mon Sep 17 00:00:00 2001 From: Yuyang Huang Date: Sat, 8 Aug 2026 09:56:38 +0900 Subject: [PATCH 1271/1433] ipv6: add inet6_rt_del_notify() Move the body of inet6_rt_notify() to __inet6_rt_notify() and give it the deletion reason. inet6_rt_notify() keeps its prototype, so the route addition path does not change. Add inet6_rt_del_notify() and call it from fib6_del_route(). RTA_DEL_REASON now reaches user space on RTM_DELROUTE for routes the kernel deleted on its own. Signed-off-by: Yuyang Huang Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260808005642.26901-7-sigefriedhyy@gmail.com Signed-off-by: Paolo Abeni --- include/net/ip6_fib.h | 2 ++ net/ipv6/ip6_fib.c | 2 +- net/ipv6/route.c | 20 ++++++++++++++++---- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/include/net/ip6_fib.h b/include/net/ip6_fib.h index 0ba5d3c56543..232289b439c3 100644 --- a/include/net/ip6_fib.h +++ b/include/net/ip6_fib.h @@ -533,6 +533,8 @@ static inline void fib6_rt_update(struct net *net, struct fib6_info *rt, #endif void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info, unsigned int flags); +void inet6_rt_del_notify(struct fib6_info *rt, struct nl_info *info, + enum rt_del_reason del_reason); void fib6_age_exceptions(struct fib6_info *rt, struct fib6_gc_args *gc_args, unsigned long now); diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index f8a9ec7fa616..3e382ba1573e 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -2057,7 +2057,7 @@ static void fib6_del_route(struct fib6_table *table, struct fib6_node *fn, call_fib6_entry_notifiers_replace(net, replace_rt); } if (!info->skip_notify) - inet6_rt_notify(RTM_DELROUTE, rt, info, 0); + inet6_rt_del_notify(rt, info, del_reason); fib6_info_release(rt); } diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 1c48332bd193..ae2f93a19dd9 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -6378,8 +6378,9 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh, return err; } -void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info, - unsigned int nlm_flags) +static void __inet6_rt_notify(int event, struct fib6_info *rt, + struct nl_info *info, unsigned int nlm_flags, + enum rt_del_reason del_reason) { struct net *net = info->nl_net; struct sk_buff *skb; @@ -6398,8 +6399,7 @@ void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info, goto errout; err = rt6_fill_node(net, skb, rt, NULL, NULL, NULL, 0, - event, info->portid, seq, nlm_flags, - RT_DEL_REASON_UNSPEC); + event, info->portid, seq, nlm_flags, del_reason); if (err < 0) { kfree_skb(skb); /* -EMSGSIZE implies needed space grew under us. */ @@ -6420,6 +6420,18 @@ void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info, rtnl_set_sk_err(net, RTNLGRP_IPV6_ROUTE, err); } +void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info, + unsigned int nlm_flags) +{ + __inet6_rt_notify(event, rt, info, nlm_flags, RT_DEL_REASON_UNSPEC); +} + +void inet6_rt_del_notify(struct fib6_info *rt, struct nl_info *info, + enum rt_del_reason del_reason) +{ + __inet6_rt_notify(RTM_DELROUTE, rt, info, 0, del_reason); +} + void fib6_rt_update(struct net *net, struct fib6_info *rt, struct nl_info *info) { From bf517422fb26d36f0bdcc4a8e38837b4de675535 Mon Sep 17 00:00:00 2001 From: Yuyang Huang Date: Sat, 8 Aug 2026 09:56:39 +0900 Subject: [PATCH 1272/1433] netlink: specs: rt-route: add route notifications Declare the RTM_NEWROUTE and RTM_DELROUTE notifications and the route multicast groups, so that generated clients can subscribe to route changes. Both notifications reuse the getroute reply attributes. Signed-off-by: Yuyang Huang Link: https://patch.msgid.link/20260808005642.26901-8-sigefriedhyy@gmail.com Signed-off-by: Paolo Abeni --- Documentation/netlink/specs/rt-route.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Documentation/netlink/specs/rt-route.yaml b/Documentation/netlink/specs/rt-route.yaml index 33195db96746..62b215614210 100644 --- a/Documentation/netlink/specs/rt-route.yaml +++ b/Documentation/netlink/specs/rt-route.yaml @@ -322,3 +322,22 @@ operations: request: value: 25 attributes: *all-route-attrs + - + name: newroute-ntf + doc: Notification about a created route. + value: 24 + notify: getroute + - + name: delroute-ntf + doc: Notification about a deleted route. + value: 25 + notify: getroute + +mcast-groups: + list: + - + name: rtnlgrp-ipv4-route + value: 7 + - + name: rtnlgrp-ipv6-route + value: 11 From 8621a7ed80f00f83a6741ad8bd1e1c500d2c5b74 Mon Sep 17 00:00:00 2001 From: Yuyang Huang Date: Sat, 8 Aug 2026 09:56:40 +0900 Subject: [PATCH 1273/1433] netlink: specs: rt-route: split out the request attribute list The newroute and delroute requests alias the same attribute list as the getroute reply, but requests and replies do not carry the same attributes. Give the requests their own list. The two lists are identical today, so the generated code does not change. Signed-off-by: Yuyang Huang Link: https://patch.msgid.link/20260808005642.26901-9-sigefriedhyy@gmail.com Signed-off-by: Paolo Abeni --- Documentation/netlink/specs/rt-route.yaml | 32 +++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/Documentation/netlink/specs/rt-route.yaml b/Documentation/netlink/specs/rt-route.yaml index 62b215614210..9f4a1a253676 100644 --- a/Documentation/netlink/specs/rt-route.yaml +++ b/Documentation/netlink/specs/rt-route.yaml @@ -313,7 +313,35 @@ operations: do: request: value: 24 - attributes: *all-route-attrs + attributes: &route-req-attrs + - dst + - src + - iif + - oif + - gateway + - priority + - prefsrc + - metrics + - multipath + - flow + - cacheinfo + - table + - mark + - mfc-stats + - via + - newdst + - pref + - encap-type + - encap + - expires + - pad + - uid + - ttl-propagate + - ip-proto + - sport + - dport + - nh-id + - flowlabel - name: delroute doc: Delete an existing route @@ -321,7 +349,7 @@ operations: do: request: value: 25 - attributes: *all-route-attrs + attributes: *route-req-attrs - name: newroute-ntf doc: Notification about a created route. From b13ba4e82bfd5ab8318ae75d545cf3c23abf7a6b Mon Sep 17 00:00:00 2001 From: Yuyang Huang Date: Sat, 8 Aug 2026 09:56:41 +0900 Subject: [PATCH 1274/1433] netlink: specs: rt-route: add the route deletion reason Add the del-reason attribute and its enum to the route attribute set, and to the getroute reply, which the route notifications reuse. The attribute is absent from the newroute and delroute request lists. RTA_DEL_REASON is above strict_start_type in rtm_ipv6_policy, so encoding it in a request is rejected with -EINVAL. Signed-off-by: Yuyang Huang Link: https://patch.msgid.link/20260808005642.26901-10-sigefriedhyy@gmail.com Signed-off-by: Paolo Abeni --- Documentation/netlink/specs/rt-route.yaml | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Documentation/netlink/specs/rt-route.yaml b/Documentation/netlink/specs/rt-route.yaml index 9f4a1a253676..253037ea5176 100644 --- a/Documentation/netlink/specs/rt-route.yaml +++ b/Documentation/netlink/specs/rt-route.yaml @@ -78,6 +78,27 @@ definitions: - name: rta-used type: u32 + - + name: del-reason + type: enum + name-prefix: rt-del-reason- + enum-name: rt-del-reason + doc: | + Why the kernel deleted a route. New causes may be appended. The + value space is family-agnostic, a value is never reinterpreted + per address family. + entries: + - + name: unspec + doc: The deletion path does not record a cause. + - + name: expired + doc: The RTF_EXPIRES lifetime ran out and the route was garbage + collected. + - + name: ra-withdrawn + doc: A Router Advertisement withdrew the route with a zero + lifetime. attribute-sets: - @@ -185,6 +206,18 @@ attribute-sets: type: u32 byte-order: big-endian display-hint: hex + - + name: del-reason + type: u32 + enum: del-reason + doc: | + Emitted only on RTM_DELROUTE notifications, and only when the + deletion path records a cause. An absent attribute means + either an older kernel or a deletion path that does not + record its cause, consumers must treat absent and unspec + identically. Currently only IPv6 deletion paths record a + cause. The attribute is notification-only, the kernel rejects + it in requests. - name: metrics name-prefix: rtax- @@ -299,6 +332,7 @@ operations: - dport - nh-id - flowlabel + - del-reason dump: request: value: 26 From 046d883265068cac3a5122335cd4abf1b9be38c1 Mon Sep 17 00:00:00 2001 From: Yuyang Huang Date: Sat, 8 Aug 2026 09:56:42 +0900 Subject: [PATCH 1275/1433] selftests: net: verify RTA_DEL_REASON on route deletion Extend rtnetlink.py to check the reason reported in RTM_DELROUTE: - expired: route with a 2s lifetime collected by the fib6 GC (gc_interval lowered like fib_tests.sh fib6_gc_test does); - ra-withdrawn: a single RA advertises a default route (router lifetime), an on-link prefix route (RFC 4861 prefix information option) and a route information option route (RFC 4191), then a second RA withdraws all three with zero lifetimes; the RAs are crafted over a raw ICMPv6 socket so the test does not depend on an external RA tool; - absence: a userspace deletion request records no cause and must not carry the attribute at all. Signed-off-by: Yuyang Huang Link: https://patch.msgid.link/20260808005642.26901-11-sigefriedhyy@gmail.com Signed-off-by: Paolo Abeni --- .../testing/selftests/net/lib/py/__init__.py | 4 +- tools/testing/selftests/net/lib/py/ynl.py | 7 +- tools/testing/selftests/net/rtnetlink.py | 187 +++++++++++++++++- 3 files changed, 193 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py index e58bdbdc58ee..34935886b6ad 100644 --- a/tools/testing/selftests/net/lib/py/__init__.py +++ b/tools/testing/selftests/net/lib/py/__init__.py @@ -17,7 +17,7 @@ from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \ wait_file, tool, tc from .bpf import bpf_map_set, bpf_map_dump, bpf_prog_map_ids from .ynl import NlError, NlctrlFamily, YnlFamily, \ - EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily + EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily, RtnlRouteFamily from .ynl import NetshaperFamily, DevlinkFamily, PSPFamily, Netlink __all__ = ["KSRC", @@ -34,4 +34,4 @@ __all__ = ["KSRC", "NetdevSim", "NetdevSimDev", "NetshaperFamily", "DevlinkFamily", "PSPFamily", "NlError", "YnlFamily", "EthtoolFamily", "NetdevFamily", "RtnlFamily", - "NlctrlFamily", "RtnlAddrFamily", "Netlink"] + "NlctrlFamily", "RtnlAddrFamily", "RtnlRouteFamily", "Netlink"] diff --git a/tools/testing/selftests/net/lib/py/ynl.py b/tools/testing/selftests/net/lib/py/ynl.py index 2e567062aa6c..08deff756f29 100644 --- a/tools/testing/selftests/net/lib/py/ynl.py +++ b/tools/testing/selftests/net/lib/py/ynl.py @@ -29,7 +29,7 @@ except ModuleNotFoundError as e: __all__ = [ "NlError", "NlPolicy", "Netlink", "YnlFamily", "SPEC_PATH", - "EthtoolFamily", "RtnlFamily", "RtnlAddrFamily", + "EthtoolFamily", "RtnlFamily", "RtnlAddrFamily", "RtnlRouteFamily", "NetdevFamily", "NetshaperFamily", "NlctrlFamily", "DevlinkFamily", "PSPFamily", ] @@ -54,6 +54,11 @@ class RtnlAddrFamily(YnlFamily): super().__init__((SPEC_PATH / Path('rt-addr.yaml')).as_posix(), schema='', recv_size=recv_size) +class RtnlRouteFamily(YnlFamily): + def __init__(self, recv_size=0): + super().__init__((SPEC_PATH / Path('rt-route.yaml')).as_posix(), + schema='', recv_size=recv_size) + class NetdevFamily(YnlFamily): def __init__(self, recv_size=0): super().__init__((SPEC_PATH / Path('netdev.yaml')).as_posix(), diff --git a/tools/testing/selftests/net/rtnetlink.py b/tools/testing/selftests/net/rtnetlink.py index 0c67c7c00d84..5cc3ebdcf08d 100755 --- a/tools/testing/selftests/net/rtnetlink.py +++ b/tools/testing/selftests/net/rtnetlink.py @@ -5,7 +5,9 @@ import socket import struct import time from lib.py import bkg, ip, ksft_exit, ksft_run, ksft_eq, ksft_ge, ksft_true, KsftSkipEx -from lib.py import CmdExitFailure, NetNS, NetNSEnter, RtnlAddrFamily +from lib.py import ksft_not_in, ksft_not_none +from lib.py import CmdExitFailure, NetNS, NetNSEnter, RtnlAddrFamily, RtnlRouteFamily +from lib.py import defer IPV4_ALL_HOSTS_MULTICAST = b'\xe0\x00\x00\x01' IPV4_TEST_MULTICAST = b'\xef\x01\x01\x01' @@ -134,8 +136,189 @@ def ipv4_devconf_notify() -> None: ksft_true(f"inet {ifname} forwarding on" in cmd_obj.stdout, f"No 'forwarding on' notificiation found for interface {ifname}") +def _rtnl_route_subscribe(ns): + with NetNSEnter(str(ns)): + rtnl = RtnlRouteFamily() + defer(rtnl.close) + rtnl.ntf_subscribe("rtnlgrp-ipv6-route") + return rtnl + + +def _wait_route_ntf(rtnl, name, dst_len, dst=None, deadline=10): + """Return the attrs of the first matching notification, None on timeout.""" + + for msg in rtnl.poll_ntf(duration=deadline): + if msg['name'] != name: + continue + attrs = msg['msg'] + if attrs['rtm-dst-len'] != dst_len: + continue + if dst is not None and attrs.get('dst') != dst: + continue + return attrs + return None + + +def _collect_route_ntfs(rtnl, name, want, deadline=10): + """Gather attrs of matching notifications, keyed by (dst_len, dst).""" + + seen = {} + for msg in rtnl.poll_ntf(duration=deadline): + if msg['name'] != name: + continue + attrs = msg['msg'] + key = (attrs['rtm-dst-len'], attrs.get('dst')) + if key in want: + seen[key] = attrs + if len(seen) == len(want): + break + return seen + + +def _write_ipv6_sysctl(name, value): + with open(f"/proc/sys/net/ipv6/{name}", "w") as f: + f.write(f"{value}\n") + + +def ipv6_route_del_reason_expired() -> None: + """An expired route reports RTA_DEL_REASON == expired.""" + + with NetNS() as ns: + rtnl = _rtnl_route_subscribe(ns) + with NetNSEnter(str(ns)): + _write_ipv6_sysctl("route/gc_interval", 2) + ip("link add name dummy1 type dummy", ns=str(ns)) + ip("link set dev dummy1 up", ns=str(ns)) + ip("-6 route add 2001:db8:2::/64 dev dummy1 expires 2", ns=str(ns)) + + attrs = _wait_route_ntf(rtnl, 'delroute-ntf', 64, '2001:db8:2::', + deadline=15) + ksft_not_none(attrs, "no RTM_DELROUTE for the expired route") + if attrs is not None: + ksft_eq(attrs.get('del-reason'), 'expired') + + +def _send_ra(sock, ifindex, lifetime, rio=None, pio=None): + """The kernel fills in the ICMPv6 checksum on raw ICMPv6 sockets.""" + + # type, code, cksum, hop limit, flags, router lifetime, + # reachable time, retrans timer + ra = struct.pack('!BBHBBHII', 134, 0, 0, 64, 0, lifetime, 0, 0) + if rio is not None: + prefix, plen, rio_lifetime = rio + # RFC 4191 route information option, /64 prefix (8 bytes) + ra += struct.pack('!BBBBI', 24, 2, plen, 0, rio_lifetime) + ra += socket.inet_pton(socket.AF_INET6, prefix)[:8] + if pio is not None: + prefix, plen, valid_lft = pio + # RFC 4861 prefix information option, on-link only (L set, A clear) + ra += struct.pack('!BBBBIII', 3, 4, plen, 0x80, valid_lft, 0, 0) + ra += socket.inet_pton(socket.AF_INET6, prefix) + sock.sendto(ra, ('ff02::1', 0, 0, ifindex)) + + +def _ra_router_sock(ns_r, ifname): + with NetNSEnter(str(ns_r)): + sock = socket.socket(socket.AF_INET6, socket.SOCK_RAW, + socket.IPPROTO_ICMPV6) + sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 255) + defer(sock.close) + return sock, socket.if_nametoindex(ifname) + + +def _ra_advertise_routes(rtnl, sock, ifindex, want, **ra_opts): + """ + Sending fails with EADDRNOTAVAIL while the router's link-local + address is still tentative. addrconf_dad_start() only queues + addrconf_dad_work(), and IFA_F_TENTATIVE is cleared when that work + item runs, so retry until it does. + """ + + seen = {} + for _ in range(10): + try: + _send_ra(sock, ifindex, **ra_opts) + except OSError: + time.sleep(0.2) + continue + seen.update(_collect_route_ntfs(rtnl, 'newroute-ntf', + want - set(seen.keys()), deadline=2)) + if len(seen) == len(want): + break + return seen + + +def ipv6_route_del_reason_ra_withdrawn() -> None: + """ + Routes withdrawn by a zero-lifetime RA (router lifetime, RFC 4861 + PIO, RFC 4191 RIO) report RTA_DEL_REASON == ra-withdrawn. + """ + + # (rtm-dst-len, dst); the default route carries no RTA_DST + routes = {(0, None), (64, '2001:db8:6::'), (64, '2001:db8:5::')} + + with NetNS() as ns_h, NetNS() as ns_r: + ip(f"link add veth0 netns {ns_h} type veth peer name veth1 netns {ns_r}") + with NetNSEnter(str(ns_h)): + _write_ipv6_sysctl("conf/veth0/accept_ra", 2) + _write_ipv6_sysctl("conf/veth0/forwarding", 0) + try: + _write_ipv6_sysctl("conf/veth0/accept_ra_rt_info_max_plen", 64) + except FileNotFoundError: + raise KsftSkipEx("no CONFIG_IPV6_ROUTE_INFO") + with NetNSEnter(str(ns_r)): + # skip the DAD probe so the router's link-local source only + # has to wait for addrconf_dad_work() to clear IFA_F_TENTATIVE + _write_ipv6_sysctl("conf/veth1/accept_dad", 0) + ip("link set dev veth0 up", ns=str(ns_h)) + ip("link set dev veth1 up", ns=str(ns_r)) + + rtnl = _rtnl_route_subscribe(ns_h) + sock, ifindex = _ra_router_sock(ns_r, "veth1") + + seen = _ra_advertise_routes(rtnl, sock, ifindex, routes, + lifetime=1800, + rio=('2001:db8:5::', 64, 600), + pio=('2001:db8:6::', 64, 600)) + ksft_eq(set(seen), routes, "not all RA routes were installed") + if set(seen) != routes: + return + + _send_ra(sock, ifindex, 0, rio=('2001:db8:5::', 64, 0), + pio=('2001:db8:6::', 64, 0)) + seen = _collect_route_ntfs(rtnl, 'delroute-ntf', routes) + for key in routes: + attrs = seen.get(key) + ksft_not_none(attrs, f"no RTM_DELROUTE for {key}") + if attrs is not None: + ksft_eq(attrs.get('del-reason'), 'ra-withdrawn') + + +def ipv6_route_del_reason_absent() -> None: + """ + A deletion path that records no cause (here a userspace request) + must not carry RTA_DEL_REASON at all. + """ + + with NetNS() as ns: + rtnl = _rtnl_route_subscribe(ns) + ip("link add name dummy1 type dummy", ns=str(ns)) + ip("link set dev dummy1 up", ns=str(ns)) + ip("-6 route add 2001:db8:1::/64 dev dummy1", ns=str(ns)) + ip("-6 route del 2001:db8:1::/64 dev dummy1", ns=str(ns)) + + attrs = _wait_route_ntf(rtnl, 'delroute-ntf', 64, '2001:db8:1::') + ksft_not_none(attrs, "no RTM_DELROUTE for 2001:db8:1::/64") + if attrs is not None: + ksft_not_in('del-reason', attrs, + "user deletion must not carry del-reason") + + def main() -> None: - ksft_run([dump_mcaddr_check, dump_mcaddr6_check, ipv4_devconf_notify]) + ksft_run([dump_mcaddr_check, dump_mcaddr6_check, ipv4_devconf_notify, + ipv6_route_del_reason_expired, + ipv6_route_del_reason_ra_withdrawn, + ipv6_route_del_reason_absent]) ksft_exit() if __name__ == "__main__": From a364a7c168d0a0b9fccee01669ceb9bb7b844056 Mon Sep 17 00:00:00 2001 From: Sharath Srinivasan Date: Sat, 8 Aug 2026 17:51:02 -0700 Subject: [PATCH 1276/1433] net/rds: reinitialize to_be_dropped on rds_send_xmit() restart The to_be_dropped list is declared once at the top of rds_send_xmit() but the function can loop via "goto restart" after each batch. The code currently relies on rds_send_remove_from_sock() having emptied the list entry by entry (via list_del_init()) at the end of the previous batch; nothing in rds_send_xmit() itself guarantees the list head is empty when a new batch starts. Re-initialize the list on every restart, and warn once if it is ever found non-empty there: entries left on the list at that point would keep their message reference, their RDS_MSG_ON_SOCK accounting and their pending RDS_RDMA_DROPPED notification, so a silent re-init would orphan them. This is hardening: no user-visible bug is known in the current code. This mirrors Oracle UEK commit "net/rds: rds_send_xmit should INIT_LIST_HEAD(&to_be_dropped) on restart". Signed-off-by: Gerd Rausch Signed-off-by: Sharath Srinivasan [achender: port to net-next (keep the existing LIST_HEAD declaration and add only the restart re-init); warn if the restart invariant is violated; update commit message] Assisted-by: Claude-Code:claude-fable-5 Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260809005103.82371-2-achender@kernel.org Reviewed-by: Simon Horman Signed-off-by: Paolo Abeni --- net/rds/send.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/rds/send.c b/net/rds/send.c index 309021e0cc9b..15a1b97f13e7 100644 --- a/net/rds/send.c +++ b/net/rds/send.c @@ -200,6 +200,14 @@ int rds_send_xmit(struct rds_conn_path *cp) restart: batch_count = 0; + /* The drop processing after over_batch relies on + * rds_send_remove_from_sock() emptying to_be_dropped entry by + * entry; warn if that post-condition ever stops holding, and + * re-initialize the list head. + */ + WARN_ON_ONCE(!list_empty(&to_be_dropped)); + INIT_LIST_HEAD(&to_be_dropped); + /* * sendmsg calls here after having queued its message on the send * queue. We only have one task feeding the connection at a time. If From ff8376b2458d9026a51c615075e2d77e79757f31 Mon Sep 17 00:00:00 2001 From: William Kucharski Date: Sat, 8 Aug 2026 17:51:03 -0700 Subject: [PATCH 1277/1433] net/rds: initialize i_conn_path in rds_inc_init() rds_inc_init() initializes every field of the embedded rds_incoming except i_conn_path, and incomings are not zero-allocated (IB carves them out of a slab cache). The field therefore holds stale garbage for incs created by rds_ib. The loopback transport is different: rds_loop_xmit() re-runs rds_inc_init() on the message's embedded inc after rds_send_queue_rm() has already stored the connection path in it, so there the field holds a live value rather than garbage, and a NULL store would discard it. Switch rds_loop_xmit() to rds_inc_path_init() with the connection's single path, which is exactly the value readers of the field reconstruct for a non-multipath transport. With loopback preserving the field, initialize it to NULL in rds_inc_init() so that any future reader trips over a clean NULL pointer instead of a stale one, and so the two init helpers (rds_inc_init/rds_inc_path_init) leave the structure in an equivalent, fully-initialized state. Hardening only; no reader dereferences i_conn_path for a non-multipath transport today. This mirrors Oracle UEK commit "rds: rds_inc_init() should initialize the inc->i_conn_path field". Signed-off-by: William Kucharski [achender: port to net-next; keep loopback's i_conn_path valid by switching rds_loop_xmit() to rds_inc_path_init(); update commit message] Assisted-by: Claude-Code:claude-fable-5 Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260809005103.82371-3-achender@kernel.org Reviewed-by: Simon Horman Signed-off-by: Paolo Abeni --- net/rds/loop.c | 6 +++++- net/rds/recv.c | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/net/rds/loop.c b/net/rds/loop.c index ac9295a766b1..e6b0750bbeda 100644 --- a/net/rds/loop.c +++ b/net/rds/loop.c @@ -89,7 +89,11 @@ static int rds_loop_xmit(struct rds_connection *conn, struct rds_message *rm, BUG_ON(hdr_off || sg || off); - rds_inc_init(&rm->m_inc, conn, &conn->c_laddr); + /* rds_send_queue_rm() stored the connection path in this embedded + * inc; use the path init so the re-initialization keeps the field + * valid instead of discarding it. + */ + rds_inc_path_init(&rm->m_inc, &conn->c_path[0], &conn->c_laddr); /* For the embedded inc. Matching put is in loop_inc_free() */ rds_message_addref(rm); diff --git a/net/rds/recv.c b/net/rds/recv.c index cf3884d87931..f1513dfb2716 100644 --- a/net/rds/recv.c +++ b/net/rds/recv.c @@ -47,6 +47,7 @@ void rds_inc_init(struct rds_incoming *inc, struct rds_connection *conn, refcount_set(&inc->i_refcount, 1); INIT_LIST_HEAD(&inc->i_item); inc->i_conn = conn; + inc->i_conn_path = NULL; inc->i_saddr = *saddr; inc->i_usercopy.rdma_cookie = 0; inc->i_usercopy.rx_tstamp = ktime_set(0, 0); From 68b3d4dbaf20539fc3268a2def90aa5e3b79bcb9 Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Sun, 9 Aug 2026 22:56:31 -0700 Subject: [PATCH 1278/1433] net/rds: clear i_rx_lat_trace in rds_inc_path_init() The commit that introduced the receive-path latency trace added the clearing of inc->i_rx_lat_trace[] to rds_inc_init() only; rds_inc_path_init() never got it. That asymmetry matters for the one caller that reuses memory: rds_tcp_data_recv() carves its rds_tcp_incoming out of a kmem_cache with no zeroing and no constructor, so after rds_inc_path_init() the array still holds the timestamps of whatever message previously occupied that slab object. No stale value is user-visible today - every message that reaches the socket happens to overwrite all four slots (RX_HDR at allocation, RX_START when the header completes, RX_END at delivery, RX_CMSG at recvmsg time) before RDS_CMSG_RXPATH_LATENCY reads them back as deltas - but that is a property of the current writers, not of the init contract, and a future trace point or an early-exit path would expose another message's timestamps to userspace. Clear the array in rds_inc_path_init() too, so both init helpers leave the inc fully initialized. memset is the form the clearing already takes on the rds_inc_init() side since commit 1635bb548f84 ("net: rds: use memset to optimize the recv"). Hardening only; no user-visible bug in the current code. Assisted-by: Claude-Code:claude-fable-5 Signed-off-by: Allison Henderson Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260810055631.299558-1-achender@kernel.org Signed-off-by: Paolo Abeni --- net/rds/recv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/rds/recv.c b/net/rds/recv.c index f1513dfb2716..6204e577a90a 100644 --- a/net/rds/recv.c +++ b/net/rds/recv.c @@ -66,6 +66,8 @@ void rds_inc_path_init(struct rds_incoming *inc, struct rds_conn_path *cp, inc->i_saddr = *saddr; inc->i_usercopy.rdma_cookie = 0; inc->i_usercopy.rx_tstamp = ktime_set(0, 0); + + memset(inc->i_rx_lat_trace, 0, sizeof(inc->i_rx_lat_trace)); } EXPORT_SYMBOL_GPL(rds_inc_path_init); From ef6cb145e216b0378686bce356f3317284d54231 Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Mon, 10 Aug 2026 15:01:02 +0200 Subject: [PATCH 1279/1433] net: enforce net sysctl registration Replace the warning and file permission change with an error when an "unsafe" net sysctl registration is detected. One of the barriers preventing the const qualification of the ctl_tables in the net directory is the permission (->mode) change in ensure_safe_net_sysctl. This prep commit removes that barrier and ensures that the received ctl_table pointer to the net ctl_table register function is const. Signed-off-by: Joel Granados Link: https://patch.msgid.link/20260810-jag-net_const_qualify-v4-1-77e888237c69@kernel.org Reviewed-by: Simon Horman Signed-off-by: Paolo Abeni --- include/net/net_namespace.h | 5 +++-- net/sysctl_net.c | 23 ++++++++++++----------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h index 501af1999fe8..e5ee673b9fcf 100644 --- a/include/net/net_namespace.h +++ b/include/net/net_namespace.h @@ -525,12 +525,13 @@ struct ctl_table; #ifdef CONFIG_SYSCTL int net_sysctl_init(void); struct ctl_table_header *register_net_sysctl_sz(struct net *net, const char *path, - struct ctl_table *table, size_t table_size); + const struct ctl_table *table, + size_t table_size); void unregister_net_sysctl_table(struct ctl_table_header *header); #else static inline int net_sysctl_init(void) { return 0; } static inline struct ctl_table_header *register_net_sysctl_sz(struct net *net, - const char *path, struct ctl_table *table, size_t table_size) + const char *path, const struct ctl_table *table, size_t table_size) { return NULL; } diff --git a/net/sysctl_net.c b/net/sysctl_net.c index 19e8048241ba..e190a639eef2 100644 --- a/net/sysctl_net.c +++ b/net/sysctl_net.c @@ -114,16 +114,17 @@ __init int net_sysctl_init(void) goto out; } -/* Verify that sysctls for non-init netns are safe by either: +/* Return error when sysctls for non-init netns are unsafe by verifying: * 1) being read-only, or * 2) having a data pointer which points outside of the global kernel/module * data segment, and rather into the heap where a per-net object was * allocated. */ -static void ensure_safe_net_sysctl(struct net *net, const char *path, - struct ctl_table *table, size_t table_size) +static int ensure_safe_net_sysctl(struct net *net, const char *path, + const struct ctl_table *table, + size_t table_size) { - struct ctl_table *ent; + const struct ctl_table *ent; pr_debug("Registering net sysctl (net %p): %s\n", net, path); ent = table; @@ -149,24 +150,24 @@ static void ensure_safe_net_sysctl(struct net *net, const char *path, else continue; - /* If it is writable and points to kernel/module global - * data, then it's probably a netns leak. - */ + /* Warn on netns leak. */ WARN(1, "sysctl %s/%s: data points to %s global data: %ps\n", path, ent->procname, where, ent->data); - /* Make it "safe" by dropping writable perms */ - ent->mode &= ~0222; + return -EACCES; } + + return 0; } struct ctl_table_header *register_net_sysctl_sz(struct net *net, const char *path, - struct ctl_table *table, + const struct ctl_table *table, size_t table_size) { if (!net_eq(net, &init_net)) - ensure_safe_net_sysctl(net, path, table, table_size); + if (ensure_safe_net_sysctl(net, path, table, table_size)) + return NULL; return __register_sysctl_table(&net->sysctls, path, table, table_size); } From 09190c59cd101e0bf87a1c5a32ebae25c91e6d81 Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Mon, 10 Aug 2026 15:01:03 +0200 Subject: [PATCH 1280/1433] net: Const qualify ctl_tables that kmemdup unconditionally Const qualify clt_table arrays in the net directory that always pass a memory duplicate to sysctl register. The template would then be in .rodata and the kmemdup'ed array would be outside. Signed-off-by: Joel Granados Link: https://patch.msgid.link/20260810-jag-net_const_qualify-v4-2-77e888237c69@kernel.org Reviewed-by: Simon Horman Signed-off-by: Paolo Abeni --- net/ipv4/devinet.c | 2 +- net/ipv6/icmp.c | 2 +- net/ipv6/route.c | 2 +- net/ipv6/sysctl_net_ipv6.c | 2 +- net/netfilter/nf_conntrack_standalone.c | 2 +- net/sctp/sysctl.c | 2 +- net/xfrm/xfrm_sysctl.c | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c index 47ded0f607d4..a90be57c63be 100644 --- a/net/ipv4/devinet.c +++ b/net/ipv4/devinet.c @@ -2796,7 +2796,7 @@ static void devinet_sysctl_unregister(struct in_device *idev) neigh_sysctl_unregister(idev->arp_parms); } -static struct ctl_table ctl_forward_entry[] = { +static const struct ctl_table ctl_forward_entry[] = { { .procname = "ip_forward", .data = &ipv4_devconf.data[ diff --git a/net/ipv6/icmp.c b/net/ipv6/icmp.c index efb23807a026..a95b0351824f 100644 --- a/net/ipv6/icmp.c +++ b/net/ipv6/icmp.c @@ -1374,7 +1374,7 @@ EXPORT_SYMBOL(icmpv6_err_convert); static u32 icmpv6_errors_extension_mask_all = GENMASK_U8(ICMP_ERR_EXT_COUNT - 1, 0); -static struct ctl_table ipv6_icmp_table_template[] = { +static const struct ctl_table ipv6_icmp_table_template[] = { { .procname = "ratelimit", .data = &init_net.ipv6.sysctl.icmpv6_time, diff --git a/net/ipv6/route.c b/net/ipv6/route.c index ae2f93a19dd9..16dfac54a259 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -6590,7 +6590,7 @@ static int ipv6_sysctl_rtcache_flush(const struct ctl_table *ctl, int write, return 0; } -static struct ctl_table ipv6_route_table_template[] = { +static const struct ctl_table ipv6_route_table_template[] = { { .procname = "max_size", .data = &init_net.ipv6.sysctl.ip6_rt_max_size, diff --git a/net/ipv6/sysctl_net_ipv6.c b/net/ipv6/sysctl_net_ipv6.c index d2cd33e2698d..1a0a36dcdabc 100644 --- a/net/ipv6/sysctl_net_ipv6.c +++ b/net/ipv6/sysctl_net_ipv6.c @@ -61,7 +61,7 @@ proc_rt6_multipath_hash_fields(const struct ctl_table *table, int write, void *b return ret; } -static struct ctl_table ipv6_table_template[] = { +static const struct ctl_table ipv6_table_template[] = { { .procname = "bindv6only", .data = &init_net.ipv6.sysctl.bindv6only, diff --git a/net/netfilter/nf_conntrack_standalone.c b/net/netfilter/nf_conntrack_standalone.c index be2953c7d702..f4f2d82192d5 100644 --- a/net/netfilter/nf_conntrack_standalone.c +++ b/net/netfilter/nf_conntrack_standalone.c @@ -639,7 +639,7 @@ enum nf_ct_sysctl_index { NF_SYSCTL_CT_LAST_SYSCTL, }; -static struct ctl_table nf_ct_sysctl_table[] = { +static const struct ctl_table nf_ct_sysctl_table[] = { [NF_SYSCTL_CT_MAX] = { .procname = "nf_conntrack_max", .data = &nf_conntrack_max, diff --git a/net/sctp/sysctl.c b/net/sctp/sysctl.c index fca840484ebf..2b94c211427d 100644 --- a/net/sctp/sysctl.c +++ b/net/sctp/sysctl.c @@ -92,7 +92,7 @@ static struct ctl_table sctp_table[] = { #define SCTP_PF_RETRANS_IDX 2 #define SCTP_PS_RETRANS_IDX 3 -static struct ctl_table sctp_net_table[] = { +static const struct ctl_table sctp_net_table[] = { [SCTP_RTO_MIN_IDX] = { .procname = "rto_min", .data = &init_net.sctp.rto_min, diff --git a/net/xfrm/xfrm_sysctl.c b/net/xfrm/xfrm_sysctl.c index ca003e8a0376..357152a50faf 100644 --- a/net/xfrm/xfrm_sysctl.c +++ b/net/xfrm/xfrm_sysctl.c @@ -13,7 +13,7 @@ static void __net_init __xfrm_sysctl_init(struct net *net) } #ifdef CONFIG_SYSCTL -static struct ctl_table xfrm_table[] = { +static const struct ctl_table xfrm_table[] = { { .procname = "xfrm_aevent_etime", .maxlen = sizeof(u32), From 0abc76bc20826e2582c4589e43b7fb8f3612911c Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Mon, 10 Aug 2026 15:01:04 +0200 Subject: [PATCH 1281/1433] net: Const qualify network templated ctl_tables Arrays Add duplication helpers in the cases where the ctl_table array elements are modified after duplication. Helpers return a ctl_table as const pointer allowing the const qualification of the static global ctl_table array. Signed-off-by: Joel Granados Link: https://patch.msgid.link/20260810-jag-net_const_qualify-v4-3-77e888237c69@kernel.org Reviewed-by: Simon Horman Signed-off-by: Paolo Abeni --- net/core/sysctl_net_core.c | 38 ++++++++++++++-------- net/ipv4/sysctl_net_ipv4.c | 54 ++++++++++++++++++------------- net/ipv4/xfrm4_policy.c | 22 ++++++++++--- net/ipv6/xfrm6_policy.c | 22 ++++++++++--- net/netfilter/nf_hooks_lwtunnel.c | 4 +-- net/smc/smc_sysctl.c | 26 +++++++++++---- net/unix/sysctl_net_unix.c | 21 +++++++++--- net/vmw_vsock/af_vsock.c | 25 ++++++++++---- 8 files changed, 146 insertions(+), 66 deletions(-) diff --git a/net/core/sysctl_net_core.c b/net/core/sysctl_net_core.c index b508618bfc12..eb35da3556f4 100644 --- a/net/core/sysctl_net_core.c +++ b/net/core/sysctl_net_core.c @@ -678,7 +678,7 @@ static struct ctl_table net_core_table[] = { }, }; -static struct ctl_table netns_core_table[] = { +static const struct ctl_table netns_core_table[] = { #if IS_ENABLED(CONFIG_RPS) { .procname = "rps_default_mask", @@ -787,26 +787,38 @@ static int __init fb_tunnels_only_for_init_net_sysctl_setup(char *str) } __setup("fb_tunnels=", fb_tunnels_only_for_init_net_sysctl_setup); -static __net_init int sysctl_core_net_init(struct net *net) +static const struct ctl_table *netns_core_table_dup(struct net *net) { size_t table_size = ARRAY_SIZE(netns_core_table); struct ctl_table *tbl; + int i; + + tbl = kmemdup(netns_core_table, sizeof(netns_core_table), GFP_KERNEL); + if (!tbl) + return NULL; + + for (i = 0; i < table_size; ++i) { + if (tbl[i].data == &sysctl_wmem_max) + break; + + tbl[i].data += (char *)net - (char *)&init_net; + } + for (; i < table_size; ++i) + tbl[i].mode &= ~0222; + + return tbl; +} + +static __net_init int sysctl_core_net_init(struct net *net) +{ + size_t table_size = ARRAY_SIZE(netns_core_table); + const struct ctl_table *tbl; tbl = netns_core_table; if (!net_eq(net, &init_net)) { - int i; - tbl = kmemdup(tbl, sizeof(netns_core_table), GFP_KERNEL); + tbl = netns_core_table_dup(net); if (tbl == NULL) goto err_dup; - - for (i = 0; i < table_size; ++i) { - if (tbl[i].data == &sysctl_wmem_max) - break; - - tbl[i].data += (char *)net - (char *)&init_net; - } - for (; i < table_size; ++i) - tbl[i].mode &= ~0222; } net->core.sysctl_hdr = register_net_sysctl_sz(net, "net/core", tbl, table_size); diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c index ca1180dba1de..2f0363bca2a8 100644 --- a/net/ipv4/sysctl_net_ipv4.c +++ b/net/ipv4/sysctl_net_ipv4.c @@ -624,7 +624,7 @@ static struct ctl_table ipv4_table[] = { }, }; -static struct ctl_table ipv4_net_table[] = { +static const struct ctl_table ipv4_net_table[] = { { .procname = "tcp_max_tw_buckets", .data = &init_net.ipv4.tcp_death_row.sysctl_max_tw_buckets, @@ -1654,35 +1654,45 @@ static struct ctl_table ipv4_net_table[] = { }, }; -static __net_init int ipv4_sysctl_init_net(struct net *net) +static const struct ctl_table *ipv4_net_table_dup(struct net *net) { size_t table_size = ARRAY_SIZE(ipv4_net_table); struct ctl_table *table; + int i; + + table = kmemdup(ipv4_net_table, sizeof(ipv4_net_table), GFP_KERNEL); + if (!table) + return NULL; + + for (i = 0; i < table_size; i++) { + if (table[i].data) { + /* Update the variables to point into + * the current struct net + */ + table[i].data += (void *)net - (void *)&init_net; + } else { + /* Entries without data pointer are global; + * Make them read-only in non-init_net ns + */ + table[i].mode &= ~0222; + } + if (table[i].extra2 >= (void *)&init_net.ipv4 && + table[i].extra2 < (void *)(&init_net.ipv4 + 1)) + table[i].extra2 += (void *)net - (void *)&init_net; + } + return table; +} + +static __net_init int ipv4_sysctl_init_net(struct net *net) +{ + size_t table_size = ARRAY_SIZE(ipv4_net_table); + const struct ctl_table *table; table = ipv4_net_table; if (!net_eq(net, &init_net)) { - int i; - - table = kmemdup(table, sizeof(ipv4_net_table), GFP_KERNEL); + table = ipv4_net_table_dup(net); if (!table) goto err_alloc; - - for (i = 0; i < table_size; i++) { - if (table[i].data) { - /* Update the variables to point into - * the current struct net - */ - table[i].data += (void *)net - (void *)&init_net; - } else { - /* Entries without data pointer are global; - * Make them read-only in non-init_net ns - */ - table[i].mode &= ~0222; - } - if (table[i].extra2 >= (void *)&init_net.ipv4 && - table[i].extra2 < (void *)(&init_net.ipv4 + 1)) - table[i].extra2 += (void *)net - (void *)&init_net; - } } net->ipv4.ipv4_hdr = register_net_sysctl_sz(net, "net/ipv4", table, diff --git a/net/ipv4/xfrm4_policy.c b/net/ipv4/xfrm4_policy.c index 58faf1ddd2b1..ab7a01029d49 100644 --- a/net/ipv4/xfrm4_policy.c +++ b/net/ipv4/xfrm4_policy.c @@ -141,7 +141,7 @@ static const struct xfrm_policy_afinfo xfrm4_policy_afinfo = { }; #ifdef CONFIG_SYSCTL -static struct ctl_table xfrm4_policy_table[] = { +static const struct ctl_table xfrm4_policy_table[] = { { .procname = "xfrm4_gc_thresh", .data = &init_net.xfrm.xfrm4_dst_ops.gc_thresh, @@ -151,18 +151,30 @@ static struct ctl_table xfrm4_policy_table[] = { }, }; -static __net_init int xfrm4_net_sysctl_init(struct net *net) +static const struct ctl_table *xfrm4_policy_table_dup(struct net *net) { struct ctl_table *table; + + table = kmemdup(xfrm4_policy_table, sizeof(xfrm4_policy_table), + GFP_KERNEL); + if (!table) + return NULL; + + table[0].data = &net->xfrm.xfrm4_dst_ops.gc_thresh; + + return table; +} + +static __net_init int xfrm4_net_sysctl_init(struct net *net) +{ + const struct ctl_table *table; struct ctl_table_header *hdr; table = xfrm4_policy_table; if (!net_eq(net, &init_net)) { - table = kmemdup(table, sizeof(xfrm4_policy_table), GFP_KERNEL); + table = xfrm4_policy_table_dup(net); if (!table) goto err_alloc; - - table[0].data = &net->xfrm.xfrm4_dst_ops.gc_thresh; } hdr = register_net_sysctl_sz(net, "net/ipv4", table, diff --git a/net/ipv6/xfrm6_policy.c b/net/ipv6/xfrm6_policy.c index 3b749475f6ed..5ec063cb4aa4 100644 --- a/net/ipv6/xfrm6_policy.c +++ b/net/ipv6/xfrm6_policy.c @@ -187,7 +187,7 @@ static void xfrm6_policy_fini(void) } #ifdef CONFIG_SYSCTL -static struct ctl_table xfrm6_policy_table[] = { +static const struct ctl_table xfrm6_policy_table[] = { { .procname = "xfrm6_gc_thresh", .data = &init_net.xfrm.xfrm6_dst_ops.gc_thresh, @@ -197,18 +197,30 @@ static struct ctl_table xfrm6_policy_table[] = { }, }; -static int __net_init xfrm6_net_sysctl_init(struct net *net) +static const struct ctl_table *xfrm6_policy_table_dup(struct net *net) { struct ctl_table *table; + + table = kmemdup(xfrm6_policy_table, sizeof(xfrm6_policy_table), + GFP_KERNEL); + if (!table) + return NULL; + + table[0].data = &net->xfrm.xfrm6_dst_ops.gc_thresh; + + return table; +} + +static int __net_init xfrm6_net_sysctl_init(struct net *net) +{ + const struct ctl_table *table; struct ctl_table_header *hdr; table = xfrm6_policy_table; if (!net_eq(net, &init_net)) { - table = kmemdup(table, sizeof(xfrm6_policy_table), GFP_KERNEL); + table = xfrm6_policy_table_dup(net); if (!table) goto err_alloc; - - table[0].data = &net->xfrm.xfrm6_dst_ops.gc_thresh; } hdr = register_net_sysctl_sz(net, "net/ipv6", table, diff --git a/net/netfilter/nf_hooks_lwtunnel.c b/net/netfilter/nf_hooks_lwtunnel.c index 2d890dd04ff8..4e1eef1ba0f1 100644 --- a/net/netfilter/nf_hooks_lwtunnel.c +++ b/net/netfilter/nf_hooks_lwtunnel.c @@ -54,7 +54,7 @@ int nf_hooks_lwtunnel_sysctl_handler(const struct ctl_table *table, int write, } EXPORT_SYMBOL_GPL(nf_hooks_lwtunnel_sysctl_handler); -static struct ctl_table nf_lwtunnel_sysctl_table[] = { +static const struct ctl_table nf_lwtunnel_sysctl_table[] = { { .procname = "nf_hooks_lwtunnel", .data = NULL, @@ -66,8 +66,8 @@ static struct ctl_table nf_lwtunnel_sysctl_table[] = { static int __net_init nf_lwtunnel_net_init(struct net *net) { + const struct ctl_table *table; struct ctl_table_header *hdr; - struct ctl_table *table; table = nf_lwtunnel_sysctl_table; if (!net_eq(net, &init_net)) { diff --git a/net/smc/smc_sysctl.c b/net/smc/smc_sysctl.c index b1efed546243..09dad48337f6 100644 --- a/net/smc/smc_sysctl.c +++ b/net/smc/smc_sysctl.c @@ -97,7 +97,7 @@ static int proc_smc_hs_ctrl(const struct ctl_table *ctl, int write, } #endif /* CONFIG_SMC_HS_CTRL_BPF */ -static struct ctl_table smc_table[] = { +static const struct ctl_table smc_table[] = { { .procname = "autocorking_size", .data = &init_net.smc.sysctl_autocorking_size, @@ -195,14 +195,29 @@ static struct ctl_table smc_table[] = { #endif /* CONFIG_SMC_HS_CTRL_BPF */ }; -int __net_init smc_sysctl_net_init(struct net *net) +static const struct ctl_table *smc_table_dup(struct net *net) { size_t table_size = ARRAY_SIZE(smc_table); struct ctl_table *table; + int i; + + table = kmemdup(smc_table, sizeof(smc_table), GFP_KERNEL); + if (!table) + return NULL; + + for (i = 0; i < table_size; i++) + table[i].data += (void *)net - (void *)&init_net; + + return table; +} + +int __net_init smc_sysctl_net_init(struct net *net) +{ + size_t table_size = ARRAY_SIZE(smc_table); + const struct ctl_table *table; table = smc_table; if (!net_eq(net, &init_net)) { - int i; #if IS_ENABLED(CONFIG_SMC_HS_CTRL_BPF) struct smc_hs_ctrl *ctrl; @@ -214,12 +229,9 @@ int __net_init smc_sysctl_net_init(struct net *net) rcu_read_unlock(); #endif /* CONFIG_SMC_HS_CTRL_BPF */ - table = kmemdup(table, sizeof(smc_table), GFP_KERNEL); + table = smc_table_dup(net); if (!table) goto err_alloc; - - for (i = 0; i < table_size; i++) - table[i].data += (void *)net - (void *)&init_net; } net->smc.smc_hdr = register_net_sysctl_sz(net, "net/smc", table, diff --git a/net/unix/sysctl_net_unix.c b/net/unix/sysctl_net_unix.c index e02ed6e3955c..47660d5726bb 100644 --- a/net/unix/sysctl_net_unix.c +++ b/net/unix/sysctl_net_unix.c @@ -13,7 +13,7 @@ #include "af_unix.h" -static struct ctl_table unix_table[] = { +static const struct ctl_table unix_table[] = { { .procname = "max_dgram_qlen", .data = &init_net.unx.sysctl_max_dgram_qlen, @@ -23,18 +23,29 @@ static struct ctl_table unix_table[] = { }, }; -int __net_init unix_sysctl_register(struct net *net) +static const struct ctl_table *unix_table_dup(struct net *net) { struct ctl_table *table; + table = kmemdup(unix_table, sizeof(unix_table), GFP_KERNEL); + if (!table) + return NULL; + + table[0].data = &net->unx.sysctl_max_dgram_qlen; + + return table; +} + +int __net_init unix_sysctl_register(struct net *net) +{ + const struct ctl_table *table; + if (net_eq(net, &init_net)) { table = unix_table; } else { - table = kmemdup(unix_table, sizeof(unix_table), GFP_KERNEL); + table = unix_table_dup(net); if (!table) goto err_alloc; - - table[0].data = &net->unx.sysctl_max_dgram_qlen; } net->unx.ctl = register_net_sysctl_sz(net, "net/unix", table, diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index 622dbd046799..caebef73ea58 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -2899,7 +2899,7 @@ static int vsock_net_child_mode_string(const struct ctl_table *table, int write, return 0; } -static struct ctl_table vsock_table[] = { +static const struct ctl_table vsock_table[] = { { .procname = "ns_mode", .data = &init_net.vsock.mode, @@ -2925,20 +2925,31 @@ static struct ctl_table vsock_table[] = { }, }; -static int __net_init vsock_sysctl_register(struct net *net) +static const struct ctl_table *vsock_table_dup(struct net *net) { struct ctl_table *table; + table = kmemdup(vsock_table, sizeof(vsock_table), GFP_KERNEL); + if (!table) + return NULL; + + table[0].data = &net->vsock.mode; + table[1].data = &net->vsock.child_ns_mode; + table[2].data = &net->vsock.g2h_fallback; + + return table; +} + +static int __net_init vsock_sysctl_register(struct net *net) +{ + const struct ctl_table *table; + if (net_eq(net, &init_net)) { table = vsock_table; } else { - table = kmemdup(vsock_table, sizeof(vsock_table), GFP_KERNEL); + table = vsock_table_dup(net); if (!table) goto err_alloc; - - table[0].data = &net->vsock.mode; - table[1].data = &net->vsock.child_ns_mode; - table[2].data = &net->vsock.g2h_fallback; } net->vsock.sysctl_hdr = register_net_sysctl_sz(net, "net/vsock", table, From 03a105c83243a8c9cc147a44a7ec7bfd4c10ee8c Mon Sep 17 00:00:00 2001 From: Michael Guralnik Date: Tue, 11 Aug 2026 09:16:37 +0300 Subject: [PATCH 1282/1433] net/mlx5: rsc_dump and hv_vhca return NULL on create error All callers of these create functions treat NULL and ERR_PTR as equivalent error cases. Align the return convention to NULL-on-failure to simplify the checks at usage sites. Since its return value is never checked and failure is non-fatal, change hv_vhca init function to return void. Signed-off-by: Michael Guralnik Reviewed-by: Shay Drori Signed-off-by: Tariq Toukan Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260811061637.3195320-1-tariqt@nvidia.com Signed-off-by: Paolo Abeni --- .../mellanox/mlx5/core/diag/rsc_dump.c | 12 +++++----- .../ethernet/mellanox/mlx5/core/en/health.c | 2 +- .../ethernet/mellanox/mlx5/core/lib/hv_vhca.c | 22 +++++++++---------- .../ethernet/mellanox/mlx5/core/lib/hv_vhca.h | 5 ++--- 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/diag/rsc_dump.c b/drivers/net/ethernet/mellanox/mlx5/core/diag/rsc_dump.c index e770088de129..8044419fb5eb 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/diag/rsc_dump.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/diag/rsc_dump.c @@ -130,7 +130,7 @@ struct mlx5_rsc_dump_cmd *mlx5_rsc_dump_cmd_create(struct mlx5_core_dev *dev, struct mlx5_rsc_dump_cmd *cmd; int sgmt_type; - if (IS_ERR_OR_NULL(dev->rsc_dump)) + if (!dev->rsc_dump) return ERR_PTR(-EOPNOTSUPP); sgmt_type = dev->rsc_dump->fw_segment_type[key->rsc]; @@ -165,7 +165,7 @@ int mlx5_rsc_dump_next(struct mlx5_core_dev *dev, struct mlx5_rsc_dump_cmd *cmd, bool more_dump; int err; - if (IS_ERR_OR_NULL(dev->rsc_dump)) + if (!dev->rsc_dump) return -EOPNOTSUPP; err = mlx5_rsc_dump_trigger(dev, cmd, page); @@ -257,14 +257,14 @@ struct mlx5_rsc_dump *mlx5_rsc_dump_create(struct mlx5_core_dev *dev) } rsc_dump = kzalloc_obj(*rsc_dump); if (!rsc_dump) - return ERR_PTR(-ENOMEM); + return NULL; return rsc_dump; } void mlx5_rsc_dump_destroy(struct mlx5_core_dev *dev) { - if (IS_ERR_OR_NULL(dev->rsc_dump)) + if (!dev->rsc_dump) return; kfree(dev->rsc_dump); } @@ -274,7 +274,7 @@ int mlx5_rsc_dump_init(struct mlx5_core_dev *dev) struct mlx5_rsc_dump *rsc_dump = dev->rsc_dump; int err; - if (IS_ERR_OR_NULL(dev->rsc_dump)) + if (!dev->rsc_dump) return 0; err = mlx5_core_alloc_pd(dev, &rsc_dump->pdn); @@ -303,7 +303,7 @@ int mlx5_rsc_dump_init(struct mlx5_core_dev *dev) void mlx5_rsc_dump_cleanup(struct mlx5_core_dev *dev) { - if (IS_ERR_OR_NULL(dev->rsc_dump)) + if (!dev->rsc_dump) return; mlx5_core_destroy_mkey(dev, dev->rsc_dump->mkey); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/health.c b/drivers/net/ethernet/mellanox/mlx5/core/en/health.c index cb972b2d46e2..45574f8b10eb 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/health.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/health.c @@ -186,7 +186,7 @@ int mlx5e_health_rsc_fmsg_dump(struct mlx5e_priv *priv, struct mlx5_rsc_key *key struct page *page; int size; - if (IS_ERR_OR_NULL(mdev->rsc_dump)) + if (!mdev->rsc_dump) return -EOPNOTSUPP; page = alloc_page(GFP_KERNEL); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/hv_vhca.c b/drivers/net/ethernet/mellanox/mlx5/core/lib/hv_vhca.c index 305752dab7bd..4c4cf6da519f 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lib/hv_vhca.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/hv_vhca.c @@ -44,12 +44,12 @@ struct mlx5_hv_vhca *mlx5_hv_vhca_create(struct mlx5_core_dev *dev) hv_vhca = kzalloc_obj(*hv_vhca); if (!hv_vhca) - return ERR_PTR(-ENOMEM); + return NULL; hv_vhca->work_queue = create_singlethread_workqueue("mlx5_hv_vhca"); if (!hv_vhca->work_queue) { kfree(hv_vhca); - return ERR_PTR(-ENOMEM); + return NULL; } hv_vhca->dev = dev; @@ -60,7 +60,7 @@ struct mlx5_hv_vhca *mlx5_hv_vhca_create(struct mlx5_core_dev *dev) void mlx5_hv_vhca_destroy(struct mlx5_hv_vhca *hv_vhca) { - if (IS_ERR_OR_NULL(hv_vhca)) + if (!hv_vhca) return; destroy_workqueue(hv_vhca->work_queue); @@ -198,28 +198,26 @@ static void mlx5_hv_vhca_control_agent_destroy(struct mlx5_hv_vhca_agent *agent) mlx5_hv_vhca_agent_destroy(agent); } -int mlx5_hv_vhca_init(struct mlx5_hv_vhca *hv_vhca) +void mlx5_hv_vhca_init(struct mlx5_hv_vhca *hv_vhca) { struct mlx5_hv_vhca_agent *agent; int err; - if (IS_ERR_OR_NULL(hv_vhca)) - return IS_ERR_OR_NULL(hv_vhca); + if (!hv_vhca) + return; err = mlx5_hv_register_invalidate(hv_vhca->dev, hv_vhca, mlx5_hv_vhca_invalidate); if (err) - return err; + return; agent = mlx5_hv_vhca_control_agent_create(hv_vhca); if (IS_ERR_OR_NULL(agent)) { mlx5_hv_unregister_invalidate(hv_vhca->dev); - return IS_ERR_OR_NULL(agent); + return; } hv_vhca->agents[MLX5_HV_VHCA_AGENT_CONTROL] = agent; - - return 0; } void mlx5_hv_vhca_cleanup(struct mlx5_hv_vhca *hv_vhca) @@ -227,7 +225,7 @@ void mlx5_hv_vhca_cleanup(struct mlx5_hv_vhca *hv_vhca) struct mlx5_hv_vhca_agent *agent; int i; - if (IS_ERR_OR_NULL(hv_vhca)) + if (!hv_vhca) return; agent = hv_vhca->agents[MLX5_HV_VHCA_AGENT_CONTROL]; @@ -261,7 +259,7 @@ mlx5_hv_vhca_agent_create(struct mlx5_hv_vhca *hv_vhca, { struct mlx5_hv_vhca_agent *agent; - if (IS_ERR_OR_NULL(hv_vhca)) + if (!hv_vhca) return ERR_PTR(-ENOMEM); if (type >= MLX5_HV_VHCA_AGENT_MAX) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/hv_vhca.h b/drivers/net/ethernet/mellanox/mlx5/core/lib/hv_vhca.h index 8b3974cf0ee4..e0d1c8cd74ef 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lib/hv_vhca.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/hv_vhca.h @@ -31,7 +31,7 @@ struct mlx5_hv_vhca_control_block { struct mlx5_hv_vhca *mlx5_hv_vhca_create(struct mlx5_core_dev *dev); void mlx5_hv_vhca_destroy(struct mlx5_hv_vhca *hv_vhca); -int mlx5_hv_vhca_init(struct mlx5_hv_vhca *hv_vhca); +void mlx5_hv_vhca_init(struct mlx5_hv_vhca *hv_vhca); void mlx5_hv_vhca_cleanup(struct mlx5_hv_vhca *hv_vhca); void mlx5_hv_vhca_invalidate(void *context, u64 block_mask); @@ -63,9 +63,8 @@ static inline void mlx5_hv_vhca_destroy(struct mlx5_hv_vhca *hv_vhca) { } -static inline int mlx5_hv_vhca_init(struct mlx5_hv_vhca *hv_vhca) +static inline void mlx5_hv_vhca_init(struct mlx5_hv_vhca *hv_vhca) { - return 0; } static inline void mlx5_hv_vhca_cleanup(struct mlx5_hv_vhca *hv_vhca) From 1da1a037bc60c3744a6cdcb7610917a20ba1a318 Mon Sep 17 00:00:00 2001 From: Aditya Garg Date: Fri, 7 Aug 2026 13:56:35 -0700 Subject: [PATCH 1283/1433] net: mana: Route ring-buffer access through offset-based helpers In preparation for backing GDMA queue memory with a vector of non-contiguous order-0 coherent pages, route CPU access to a queue's ring buffer through two new helpers: mana_gd_ring_ptr() returns the CPU address of a byte offset into the ring, and mana_gd_ring_contig_avail() the number of bytes left before the ring wraps, so a WQ write that runs past the end of the ring can be split at that point. Convert the EQ, CQ and work-request paths to use them. mana_gd_write_sgl() now takes a byte offset rather than a raw pointer, so mana_gd_post_work_request() derives the SGL position arithmetically. While queue memory is contiguous both helpers are simple arithmetic on the ring base and size, so there is no functional change. Signed-off-by: Aditya Garg Link: https://patch.msgid.link/20260807210002.1695263-2-gargaditya@linux.microsoft.com Signed-off-by: Paolo Abeni --- .../net/ethernet/microsoft/mana/gdma_main.c | 63 ++++++++++++------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c index a38d4bb74621..31a79693db07 100644 --- a/drivers/net/ethernet/microsoft/mana/gdma_main.c +++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c @@ -753,11 +753,24 @@ int mana_schedule_serv_work(struct gdma_context *gc, enum gdma_eqe_type type) return 0; } +/* Return the CPU address of byte @offset within a queue's ring buffer. */ +static void *mana_gd_ring_ptr(const struct gdma_queue *q, u32 offset) +{ + return q->queue_mem_ptr + offset; +} + +/* Number of bytes from @offset to the end of the ring buffer, i.e. the point + * at which ring access wraps back to the start. + */ +static u32 mana_gd_ring_contig_avail(const struct gdma_queue *q, u32 offset) +{ + return q->queue_size - offset; +} + static void mana_gd_process_eqe(struct gdma_queue *eq) { u32 head = eq->head % (eq->queue_size / GDMA_EQE_SIZE); struct gdma_context *gc = eq->gdma_dev->gdma_context; - struct gdma_eqe *eq_eqe_ptr = eq->queue_mem_ptr; union gdma_eqe_info eqe_info; enum gdma_eqe_type type; struct gdma_event event; @@ -765,7 +778,7 @@ static void mana_gd_process_eqe(struct gdma_queue *eq) struct gdma_eqe *eqe; u32 cq_id; - eqe = &eq_eqe_ptr[head]; + eqe = mana_gd_ring_ptr(eq, head * sizeof(*eqe)); eqe_info.as_uint32 = eqe->eqe_info; type = eqe_info.type; @@ -829,7 +842,6 @@ static void mana_gd_process_eq_events(void *arg) { u32 owner_bits, new_bits, old_bits; union gdma_eqe_info eqe_info; - struct gdma_eqe *eq_eqe_ptr; struct gdma_queue *eq = arg; struct gdma_context *gc; struct gdma_eqe *eqe; @@ -839,11 +851,10 @@ static void mana_gd_process_eq_events(void *arg) gc = eq->gdma_dev->gdma_context; num_eqe = eq->queue_size / GDMA_EQE_SIZE; - eq_eqe_ptr = eq->queue_mem_ptr; /* Process up to 5 EQEs at a time, and update the HW head. */ for (i = 0; i < 5; i++) { - eqe = &eq_eqe_ptr[eq->head % num_eqe]; + eqe = mana_gd_ring_ptr(eq, (eq->head % num_eqe) * sizeof(*eqe)); eqe_info.as_uint32 = eqe->eqe_info; owner_bits = eqe_info.owner_bits; @@ -1508,7 +1519,7 @@ u8 *mana_gd_get_wqe_ptr(const struct gdma_queue *wq, u32 wqe_offset) WARN_ON_ONCE((offset + GDMA_WQE_BU_SIZE) > wq->queue_size); - return wq->queue_mem_ptr + offset; + return mana_gd_ring_ptr(wq, offset); } static u32 mana_gd_write_client_oob(const struct gdma_wqe_request *wqe_req, @@ -1554,27 +1565,24 @@ static u32 mana_gd_write_client_oob(const struct gdma_wqe_request *wqe_req, return sizeof(header) + client_oob_size; } -static void mana_gd_write_sgl(struct gdma_queue *wq, u8 *wqe_ptr, +static void mana_gd_write_sgl(struct gdma_queue *wq, u32 sgl_offset, const struct gdma_wqe_request *wqe_req) { + u32 size_to_end = mana_gd_ring_contig_avail(wq, sgl_offset); u32 sgl_size = sizeof(struct gdma_sge) * wqe_req->num_sge; const u8 *address = (u8 *)wqe_req->sgl; - u8 *base_ptr, *end_ptr; - u32 size_to_end; - - base_ptr = wq->queue_mem_ptr; - end_ptr = base_ptr + wq->queue_size; - size_to_end = (u32)(end_ptr - wqe_ptr); if (size_to_end < sgl_size) { - memcpy(wqe_ptr, address, size_to_end); + memcpy(mana_gd_ring_ptr(wq, sgl_offset), address, size_to_end); - wqe_ptr = base_ptr; address += size_to_end; sgl_size -= size_to_end; + sgl_offset += size_to_end; + if (sgl_offset == wq->queue_size) + sgl_offset = 0; } - memcpy(wqe_ptr, address, sgl_size); + memcpy(mana_gd_ring_ptr(wq, sgl_offset), address, sgl_size); } int mana_gd_post_work_request(struct gdma_queue *wq, @@ -1584,8 +1592,12 @@ int mana_gd_post_work_request(struct gdma_queue *wq, u32 client_oob_size = wqe_req->inline_oob_size; u32 sgl_data_size; u32 max_wqe_size; + u32 wqe_offset; + u32 sgl_offset; u32 wqe_size; + u32 oob_len; u8 *wqe_ptr; + u32 head; if (wqe_req->num_sge == 0) return -EINVAL; @@ -1617,13 +1629,17 @@ int mana_gd_post_work_request(struct gdma_queue *wq, if (wqe_info) wqe_info->wqe_size_in_bu = wqe_size / GDMA_WQE_BU_SIZE; - wqe_ptr = mana_gd_get_wqe_ptr(wq, wq->head); - wqe_ptr += mana_gd_write_client_oob(wqe_req, wq->type, client_oob_size, - sgl_data_size, wqe_ptr); - if (wqe_ptr >= (u8 *)wq->queue_mem_ptr + wq->queue_size) - wqe_ptr -= wq->queue_size; + head = wq->head; + wqe_offset = (head * GDMA_WQE_BU_SIZE) & (wq->queue_size - 1); + wqe_ptr = mana_gd_get_wqe_ptr(wq, head); + oob_len = mana_gd_write_client_oob(wqe_req, wq->type, client_oob_size, + sgl_data_size, wqe_ptr); - mana_gd_write_sgl(wq, wqe_ptr, wqe_req); + sgl_offset = wqe_offset + oob_len; + if (sgl_offset >= wq->queue_size) + sgl_offset -= wq->queue_size; + + mana_gd_write_sgl(wq, sgl_offset, wqe_req); wq->head += wqe_size / GDMA_WQE_BU_SIZE; @@ -1653,11 +1669,10 @@ int mana_gd_post_and_ring(struct gdma_queue *queue, static int mana_gd_read_cqe(struct gdma_queue *cq, struct gdma_comp *comp) { unsigned int num_cqe = cq->queue_size / sizeof(struct gdma_cqe); - struct gdma_cqe *cq_cqe = cq->queue_mem_ptr; u32 owner_bits, new_bits, old_bits; struct gdma_cqe *cqe; - cqe = &cq_cqe[cq->head % num_cqe]; + cqe = mana_gd_ring_ptr(cq, (cq->head % num_cqe) * sizeof(*cqe)); owner_bits = cqe->cqe_info.owner_bits; old_bits = (cq->head / num_cqe - 1) & GDMA_CQE_OWNER_MASK; From 23adfc77c22cb959ac84e08dc8a77bac656f1caa Mon Sep 17 00:00:00 2001 From: Aditya Garg Date: Fri, 7 Aug 2026 13:56:36 -0700 Subject: [PATCH 1284/1433] net: mana: Fall back to scattered pages for GDMA queues Each GDMA queue ring is one dma_alloc_coherent() of the whole ring size. Such high-order allocations fail first under memory fragmentation, so queue setup can fail with memory still free. The hardware does not need the ring physically contiguous: mana_gd_create_dma_region() already maps it as a list of MANA_PAGE_SIZE (4K) device addresses. Only the driver's linear CPU view needs contiguity, and it goes through mana_gd_ring_ptr() and mana_gd_ring_contig_avail(); change both to map offsets onto scattered pages. Add a fallback in mana_gd_alloc_memory(): data-path queues pass allow_scatter=true, so when the contiguous allocation fails the ring is backed by a vector of scattered PAGE_SIZE (order-0) coherent pages, presenting the same DMA page-list layout to the device. The HW channel bootstrap keeps allow_scatter=false, and the debugfs ring dumper reads scattered rings through the same helpers. Signed-off-by: Aditya Garg Link: https://patch.msgid.link/20260807210002.1695263-3-gargaditya@linux.microsoft.com Signed-off-by: Paolo Abeni --- .../net/ethernet/microsoft/mana/gdma_main.c | 160 ++++++++++++++++-- .../net/ethernet/microsoft/mana/hw_channel.c | 2 +- drivers/net/ethernet/microsoft/mana/mana_en.c | 3 + include/net/mana/gdma.h | 19 ++- 4 files changed, 168 insertions(+), 16 deletions(-) diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c index 31a79693db07..ed9af314e4ed 100644 --- a/drivers/net/ethernet/microsoft/mana/gdma_main.c +++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -374,28 +375,96 @@ int mana_gd_send_request(struct gdma_context *gc, u32 req_len, const void *req, EXPORT_SYMBOL_NS(mana_gd_send_request, "NET_MANA"); int mana_gd_alloc_memory(struct gdma_context *gc, unsigned int length, - struct gdma_mem_info *gmi) + struct gdma_mem_info *gmi, bool allow_scatter) { + unsigned int npages, i; dma_addr_t dma_handle; + bool can_fallback; void *buf; if (length < MANA_PAGE_SIZE || !is_power_of_2(length)) return -EINVAL; gmi->dev = gc->dev; - buf = dma_alloc_coherent(gmi->dev, length, &dma_handle, GFP_KERNEL); - if (!buf) + + /* An allocation that fits in one page does not benefit from + * fallback. + */ + can_fallback = allow_scatter && length > PAGE_SIZE; + + /* Warn only when there is no fallback to rescue the failure. */ + buf = dma_alloc_coherent(gmi->dev, length, &dma_handle, + GFP_KERNEL | + (can_fallback ? __GFP_NOWARN : 0)); + if (buf) { + gmi->dma_handle = dma_handle; + gmi->virt_addr = buf; + gmi->length = length; + gmi->nr_pages = 0; + return 0; + } + + if (!can_fallback) return -ENOMEM; - gmi->dma_handle = dma_handle; - gmi->virt_addr = buf; + /* length is a power of 2 above PAGE_SIZE, so this divides exactly. */ + npages = length / PAGE_SIZE; + + gmi->pages_va = kvcalloc(npages, sizeof(*gmi->pages_va), GFP_KERNEL); + if (!gmi->pages_va) + return -ENOMEM; + + gmi->pages_dma = kvcalloc(npages, sizeof(*gmi->pages_dma), GFP_KERNEL); + if (!gmi->pages_dma) + goto free_va; + + for (i = 0; i < npages; i++) { + gmi->pages_va[i] = dma_alloc_coherent(gmi->dev, PAGE_SIZE, + &gmi->pages_dma[i], + GFP_KERNEL); + if (!gmi->pages_va[i]) + goto free_pages; + } + + dev_info_ratelimited(gmi->dev, + "contiguous %u-byte DMA alloc failed; using %u scattered pages\n", + length, npages); + + gmi->virt_addr = NULL; + gmi->dma_handle = 0; gmi->length = length; + gmi->nr_pages = npages; return 0; + +free_pages: + while (i--) + dma_free_coherent(gmi->dev, PAGE_SIZE, gmi->pages_va[i], + gmi->pages_dma[i]); + kvfree(gmi->pages_dma); + gmi->pages_dma = NULL; +free_va: + kvfree(gmi->pages_va); + gmi->pages_va = NULL; + return -ENOMEM; } void mana_gd_free_memory(struct gdma_mem_info *gmi) { + unsigned int i; + + if (gmi->nr_pages > 0) { + for (i = 0; i < gmi->nr_pages; i++) + dma_free_coherent(gmi->dev, PAGE_SIZE, gmi->pages_va[i], + gmi->pages_dma[i]); + kvfree(gmi->pages_va); + kvfree(gmi->pages_dma); + gmi->pages_va = NULL; + gmi->pages_dma = NULL; + gmi->nr_pages = 0; + return; + } + dma_free_coherent(gmi->dev, gmi->length, gmi->virt_addr, gmi->dma_handle); } @@ -756,17 +825,66 @@ int mana_schedule_serv_work(struct gdma_context *gc, enum gdma_eqe_type type) /* Return the CPU address of byte @offset within a queue's ring buffer. */ static void *mana_gd_ring_ptr(const struct gdma_queue *q, u32 offset) { + const struct gdma_mem_info *gmi = &q->mem_info; + + if (gmi->nr_pages > 0) + return (u8 *)gmi->pages_va[offset / PAGE_SIZE] + + (offset & (PAGE_SIZE - 1)); + return q->queue_mem_ptr + offset; } -/* Number of bytes from @offset to the end of the ring buffer, i.e. the point - * at which ring access wraps back to the start. +/* Number of bytes from @offset to the end of the CPU-contiguous region: the + * rest of the ring, or the rest of the current page when scattered. */ static u32 mana_gd_ring_contig_avail(const struct gdma_queue *q, u32 offset) { + if (q->mem_info.nr_pages > 0) + return PAGE_SIZE - (offset & (PAGE_SIZE - 1)); + return q->queue_size - offset; } +/* Copy up to @count bytes from ring offset *@pos of @q into user buffer @buf, + * so a scattered ring reads back as if it were contiguous. Returns bytes + * copied, 0 at end of ring, or a negative errno. + */ +ssize_t mana_gd_read_ring(struct gdma_queue *q, char __user *buf, + size_t count, loff_t *pos) +{ + u32 size = q->queue_size; + loff_t off = *pos; + size_t copied = 0; + + if (off < 0) + return -EINVAL; + if (off >= size || !count) + return 0; + count = min_t(size_t, count, size - off); + + while (count) { + u32 offset = off; + u32 avail = mana_gd_ring_contig_avail(q, offset); + size_t chunk = min_t(size_t, count, avail); + size_t left = copy_to_user(buf, mana_gd_ring_ptr(q, offset), + chunk); + + chunk -= left; + buf += chunk; + off += chunk; + copied += chunk; + count -= chunk; + if (left) + break; + } + + if (!copied) + return -EFAULT; + + *pos = off; + return copied; +} + static void mana_gd_process_eqe(struct gdma_queue *eq) { u32 head = eq->head % (eq->queue_size / GDMA_EQE_SIZE); @@ -1118,7 +1236,7 @@ int mana_gd_create_hwc_queue(struct gdma_dev *gd, return -ENOMEM; gmi = &queue->mem_info; - err = mana_gd_alloc_memory(gc, spec->queue_size, gmi); + err = mana_gd_alloc_memory(gc, spec->queue_size, gmi, false); if (err) { dev_err(gc->dev, "GDMA queue type: %d, size: %u, gdma memory allocation err: %d\n", spec->type, spec->queue_size, err); @@ -1193,7 +1311,7 @@ static int mana_gd_create_dma_region(struct gdma_dev *gd, if (length < MANA_PAGE_SIZE || !is_power_of_2(length)) return -EINVAL; - if (!MANA_PAGE_ALIGNED(gmi->virt_addr)) + if (gmi->nr_pages == 0 && !MANA_PAGE_ALIGNED(gmi->virt_addr)) return -EINVAL; hwc = gc->hwc.driver_data; @@ -1213,8 +1331,24 @@ static int mana_gd_create_dma_region(struct gdma_dev *gd, req->page_count = num_page; req->page_addr_list_len = num_page; - for (i = 0; i < num_page; i++) - req->page_addr_list[i] = gmi->dma_handle + i * MANA_PAGE_SIZE; + if (gmi->nr_pages > 0) { + unsigned int subpages = PAGE_SIZE / MANA_PAGE_SIZE; + unsigned int idx = 0; + unsigned int pg, sub; + + /* Each PAGE_SIZE chunk is physically contiguous and contains + * PAGE_SIZE / MANA_PAGE_SIZE consecutive device pages. + */ + for (pg = 0; pg < gmi->nr_pages; pg++) + for (sub = 0; sub < subpages; sub++) + req->page_addr_list[idx++] = + gmi->pages_dma[pg] + + sub * MANA_PAGE_SIZE; + } else { + for (i = 0; i < num_page; i++) + req->page_addr_list[i] = + gmi->dma_handle + i * MANA_PAGE_SIZE; + } err = mana_gd_send_request(gc, req_msg_size, req, sizeof(resp), &resp); if (err) @@ -1257,7 +1391,7 @@ int mana_gd_create_mana_eq(struct gdma_dev *gd, return -ENOMEM; gmi = &queue->mem_info; - err = mana_gd_alloc_memory(gc, spec->queue_size, gmi); + err = mana_gd_alloc_memory(gc, spec->queue_size, gmi, true); if (err) { dev_err(gc->dev, "GDMA queue type: %d, size: %u, gdma memory allocation err: %d\n", spec->type, spec->queue_size, err); @@ -1312,7 +1446,7 @@ int mana_gd_create_mana_wq_cq(struct gdma_dev *gd, queue->id = INVALID_QUEUE_ID; gmi = &queue->mem_info; - err = mana_gd_alloc_memory(gc, spec->queue_size, gmi); + err = mana_gd_alloc_memory(gc, spec->queue_size, gmi, true); if (err) { dev_err(gc->dev, "GDMA queue type: %d, size: %u, memory allocation err: %d\n", spec->type, spec->queue_size, err); diff --git a/drivers/net/ethernet/microsoft/mana/hw_channel.c b/drivers/net/ethernet/microsoft/mana/hw_channel.c index e3c24d50dad0..263e7c4e2934 100644 --- a/drivers/net/ethernet/microsoft/mana/hw_channel.c +++ b/drivers/net/ethernet/microsoft/mana/hw_channel.c @@ -479,7 +479,7 @@ static int mana_hwc_alloc_dma_buf(struct hw_channel_context *hwc, u16 q_depth, buf_size = MANA_PAGE_ALIGN(q_depth * max_msg_size); gmi = &dma_buf->mem_info; - err = mana_gd_alloc_memory(gc, buf_size, gmi); + err = mana_gd_alloc_memory(gc, buf_size, gmi, false); if (err) { dev_err(hwc->dev, "Failed to allocate DMA buffer size: %u, err %d\n", buf_size, err); diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index 3c96e6fc3d81..7a1ac853e3ab 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -40,6 +40,9 @@ static ssize_t mana_dbg_q_read(struct file *filp, char __user *buf, size_t count { struct gdma_queue *gdma_q = filp->private_data; + if (gdma_q->mem_info.nr_pages) + return mana_gd_read_ring(gdma_q, buf, count, pos); + return simple_read_from_buffer(buf, count, pos, gdma_q->queue_mem_ptr, gdma_q->queue_size); } diff --git a/include/net/mana/gdma.h b/include/net/mana/gdma.h index 70a7f1fee5d3..9d57a0ea0e5e 100644 --- a/include/net/mana/gdma.h +++ b/include/net/mana/gdma.h @@ -241,6 +241,14 @@ struct gdma_mem_info { void *virt_addr; u64 length; + /* Scattered fallback: when @nr_pages > 0 the ring is that many + * PAGE_SIZE coherent allocations in @pages_va/@pages_dma, not + * @virt_addr/@dma_handle. + */ + void **pages_va; + dma_addr_t *pages_dma; + unsigned int nr_pages; + /* Allocated by the PF driver */ u64 dma_region_handle; }; @@ -516,6 +524,9 @@ int mana_gd_poll_cq(struct gdma_queue *cq, struct gdma_comp *comp, int num_cqe); void mana_gd_ring_cq(struct gdma_queue *cq, u8 arm_bit); +ssize_t mana_gd_read_ring(struct gdma_queue *q, char __user *buf, + size_t count, loff_t *pos); + int mana_schedule_serv_work(struct gdma_context *gc, enum gdma_eqe_type type); void mana_gd_ring_dim(struct gdma_queue *cq, u32 mod_usec, bool mod_usec_vld, @@ -672,6 +683,9 @@ enum { /* Driver supports dynamic interrupt moderation - DIM */ #define GDMA_DRV_CAP_FLAG_1_DYN_INTERRUPT_MODERATION BIT(28) +/* Driver supports non-contiguous queue buffers */ +#define GDMA_DRV_CAP_FLAG_1_NON_CONTIGUOUS_BUFFERS BIT(30) + #define GDMA_DRV_CAP_FLAGS1 \ (GDMA_DRV_CAP_FLAG_1_EQ_SHARING_MULTI_VPORT | \ GDMA_DRV_CAP_FLAG_1_NAPI_WKDONE_FIX | \ @@ -688,7 +702,8 @@ enum { GDMA_DRV_CAP_FLAG_1_HANDLE_STALL_SQ_RECOVERY | \ GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECOVERY | \ GDMA_DRV_CAP_FLAG_1_EQ_MSI_UNSHARE_MULTI_VPORT | \ - GDMA_DRV_CAP_FLAG_1_DYN_INTERRUPT_MODERATION) + GDMA_DRV_CAP_FLAG_1_DYN_INTERRUPT_MODERATION | \ + GDMA_DRV_CAP_FLAG_1_NON_CONTIGUOUS_BUFFERS) #define GDMA_DRV_CAP_FLAGS2 0 @@ -1049,7 +1064,7 @@ void mana_gd_wq_ring_doorbell(struct gdma_context *gc, struct gdma_queue *queue); int mana_gd_alloc_memory(struct gdma_context *gc, unsigned int length, - struct gdma_mem_info *gmi); + struct gdma_mem_info *gmi, bool allow_scatter); void mana_gd_free_memory(struct gdma_mem_info *gmi); From a9560343d4e9da962616110140afed20249f81c2 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 10 Aug 2026 02:36:11 -0700 Subject: [PATCH 1285/1433] netconsole: publish the userdata payload with RCU update_userdata() takes target_list_lock to swap nt->userdata and nt->userdata_length, then frees the old buffer. Since commit 7eab73b18630 ("netconsole: convert to NBCON console infrastructure") that lock is also the console's device_lock, so writing a userdata value from configfs serialises against the printk core emitting messages. The buffer is immutable once published, which is what RCU is for. Move the string and its length into a single netcons_userdata object and publish it with rcu_replace_pointer(), freeing the old one with kfree_rcu(). New userdata design: 0) Unify the userdata fields into a struct netcons_userdata 1) update_userdata() no longer needs target_list_lock. 2) writers stay serialised by dynamic_netconsole_mutex. 3) reading userdata needs an RCU read lock. No functional change intended. Signed-off-by: Breno Leitao Reviewed-by: Gustavo Luiz Duarte Link: https://patch.msgid.link/20260810-netcons-userdata-rcu-v3-1-f65557f769ce@debian.org Signed-off-by: Paolo Abeni --- drivers/net/netconsole.c | 92 ++++++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 36 deletions(-) diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 03913302328c..b358e5c36735 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -151,13 +151,27 @@ enum target_state { STATE_DEACTIVATED, }; +/** + * struct netcons_userdata - Formatted userdata payload of a target. + * @rcu: Used to free the payload after a grace period. + * @length: Length of @data, excluding the NUL terminator. + * @data: Formatted " key=value\n" entries, NUL terminated. + * + * Immutable once published, so the transmit path never observes @data and + * @length disagreeing. + */ +struct netcons_userdata { + struct rcu_head rcu; + size_t length; + char data[]; +}; + /** * struct netconsole_target - Represents a configured netconsole target. * @list: Links this target into the target_list. * @group: Links us into the configfs subsystem hierarchy. * @userdata_group: Links to the userdata configfs hierarchy - * @userdata: Cached, formatted string of append - * @userdata_length: String length of userdata. + * @userdata: Cached, formatted userdata payload. RCU protected. * @sysdata: Cached, formatted string of append * @sysdata_fields: Sysdata features enabled. * @msgcounter: Message sent counter. @@ -198,8 +212,7 @@ struct netconsole_target { #ifdef CONFIG_NETCONSOLE_DYNAMIC struct config_group group; struct config_group userdata_group; - char *userdata; - size_t userdata_length; + struct netcons_userdata __rcu *userdata; char sysdata[MAX_EXTRADATA_ENTRY_LEN * MAX_SYSDATA_ITEMS]; /* bit-wise with sysdata_feature bits */ @@ -1351,12 +1364,11 @@ static int calc_userdata_len(struct netconsole_target *nt) static int update_userdata(struct netconsole_target *nt) { + struct netcons_userdata *new = NULL; + struct netcons_userdata *old; struct userdatum *udm_item; struct config_item *item; struct list_head *entry; - char *old_buf = NULL; - char *new_buf = NULL; - unsigned long flags; int offset = 0; int len; @@ -1368,8 +1380,8 @@ static int update_userdata(struct netconsole_target *nt) /* Allocate new buffer */ if (len) { - new_buf = kmalloc(len + 1, GFP_KERNEL); - if (!new_buf) + new = kmalloc_flex(*new, data, len + 1); + if (!new) return -ENOMEM; } @@ -1379,22 +1391,21 @@ static int update_userdata(struct netconsole_target *nt) udm_item = to_userdatum(item); /* Skip userdata with no value set */ if (udm_item->value[0]) { - offset += scnprintf(&new_buf[offset], len + 1 - offset, + offset += scnprintf(&new->data[offset], + len + 1 - offset, " %s=%s\n", item->ci_name, udm_item->value); } } WARN_ON_ONCE(offset != len); + if (new) + new->length = offset; - /* Switch to new buffer and free old buffer */ - spin_lock_irqsave(&target_list_lock, flags); - old_buf = nt->userdata; - nt->userdata = new_buf; - nt->userdata_length = offset; - spin_unlock_irqrestore(&target_list_lock, flags); - - kfree(old_buf); + /* Writers are serialized by dynamic_netconsole_mutex. */ + old = rcu_replace_pointer(nt->userdata, new, + lockdep_is_held(&dynamic_netconsole_mutex)); + kfree_rcu(old, rcu); return 0; } @@ -1684,7 +1695,7 @@ static void netconsole_target_release(struct config_item *item) { struct netconsole_target *nt = to_target(item); - kfree(nt->userdata); + kfree(rcu_access_pointer(nt->userdata)); kfree(nt); } @@ -2227,14 +2238,13 @@ static void send_udp(struct netconsole_target *nt, const char *msg, int len) static void send_msg_no_fragmentation(struct netconsole_target *nt, const char *msg, int msg_len, - int release_len) + int release_len, + const struct netcons_userdata *userdata) { - const char *userdata = NULL; const char *sysdata = NULL; const char *release; #ifdef CONFIG_NETCONSOLE_DYNAMIC - userdata = nt->userdata; sysdata = nt->sysdata; #endif @@ -2251,7 +2261,7 @@ static void send_msg_no_fragmentation(struct netconsole_target *nt, if (userdata) msg_len += scnprintf(&nt->buf[msg_len], sizeof(nt->buf) - msg_len, "%s", - userdata); + userdata->data); if (sysdata) msg_len += scnprintf(&nt->buf[msg_len], @@ -2271,7 +2281,8 @@ static void append_release(char *buf) static void send_fragmented_body(struct netconsole_target *nt, const char *msgbody_ptr, int header_len, - int msgbody_len, int sysdata_len) + int msgbody_len, int sysdata_len, + const struct netcons_userdata *userdata) { const char *userdata_ptr = NULL; const char *sysdata_ptr = NULL; @@ -2282,12 +2293,12 @@ static void send_fragmented_body(struct netconsole_target *nt, int userdata_len = 0; #ifdef CONFIG_NETCONSOLE_DYNAMIC - userdata_ptr = nt->userdata; sysdata_ptr = nt->sysdata; - userdata_len = nt->userdata_length; #endif - if (WARN_ON_ONCE(!userdata_ptr && userdata_len != 0)) - return; + if (userdata) { + userdata_ptr = userdata->data; + userdata_len = userdata->length; + } if (WARN_ON_ONCE(!sysdata_ptr && sysdata_len != 0)) return; @@ -2364,7 +2375,8 @@ static void send_msg_fragmented(struct netconsole_target *nt, const char *msg, int msg_len, int release_len, - int sysdata_len) + int sysdata_len, + const struct netcons_userdata *userdata) { int header_len, msgbody_len; const char *msgbody; @@ -2393,7 +2405,7 @@ static void send_msg_fragmented(struct netconsole_target *nt, * will be replaced */ send_fragmented_body(nt, msgbody, header_len, msgbody_len, - sysdata_len); + sysdata_len, userdata); } /** @@ -2408,25 +2420,33 @@ static void send_msg_fragmented(struct netconsole_target *nt, static void send_ext_msg_udp(struct netconsole_target *nt, struct nbcon_write_context *wctxt) { + const struct netcons_userdata *userdata = NULL; int userdata_len = 0; int release_len = 0; int sysdata_len = 0; int len; + /* Keeps the payload picked below alive until the last send_udp(). */ + rcu_read_lock(); + #ifdef CONFIG_NETCONSOLE_DYNAMIC sysdata_len = prepare_sysdata(nt, wctxt); - userdata_len = nt->userdata_length; + userdata = rcu_dereference(nt->userdata); + if (userdata) + userdata_len = userdata->length; #endif if (nt->release) release_len = strlen(init_utsname()->release) + 1; len = wctxt->len + release_len + sysdata_len + userdata_len; if (len <= MAX_PRINT_CHUNK) - return send_msg_no_fragmentation(nt, wctxt->outbuf, - wctxt->len, release_len); + send_msg_no_fragmentation(nt, wctxt->outbuf, wctxt->len, + release_len, userdata); + else + send_msg_fragmented(nt, wctxt->outbuf, wctxt->len, release_len, + sysdata_len, userdata); - return send_msg_fragmented(nt, wctxt->outbuf, wctxt->len, release_len, - sysdata_len); + rcu_read_unlock(); } static void send_msg_udp(struct netconsole_target *nt, const char *msg, @@ -2669,7 +2689,7 @@ static void free_param_target(struct netconsole_target *nt) netconsole_skb_pool_flush(nt); netpoll_cleanup(&nt->np); #ifdef CONFIG_NETCONSOLE_DYNAMIC - kfree(nt->userdata); + kfree(rcu_access_pointer(nt->userdata)); #endif kfree(nt); } From 3d2452c2fb2fe37d8b1eb5b814561e71eb8652f3 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 10 Aug 2026 02:36:12 -0700 Subject: [PATCH 1286/1433] selftests: netconsole: add a userdata torture test The userdata payload is rebuilt and republished on every configfs write, including while the target is enabled and messages are being sent. Add netcons_userdata.sh that runs random tests with userdata. Signed-off-by: Breno Leitao Reviewed-by: Gustavo Luiz Duarte Link: https://patch.msgid.link/20260810-netcons-userdata-rcu-v3-2-f65557f769ce@debian.org Signed-off-by: Paolo Abeni --- .../selftests/drivers/net/netconsole/Makefile | 1 + .../net/netconsole/netcons_userdata.sh | 229 ++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100755 tools/testing/selftests/drivers/net/netconsole/netcons_userdata.sh diff --git a/tools/testing/selftests/drivers/net/netconsole/Makefile b/tools/testing/selftests/drivers/net/netconsole/Makefile index b56c70b7e274..f0674c0017fc 100644 --- a/tools/testing/selftests/drivers/net/netconsole/Makefile +++ b/tools/testing/selftests/drivers/net/netconsole/Makefile @@ -13,6 +13,7 @@ TEST_PROGS := \ netcons_resume.sh \ netcons_sysdata.sh \ netcons_torture.sh \ + netcons_userdata.sh \ # end of TEST_PROGS include ../../../lib.mk diff --git a/tools/testing/selftests/drivers/net/netconsole/netcons_userdata.sh b/tools/testing/selftests/drivers/net/netconsole/netcons_userdata.sh new file mode 100755 index 000000000000..113903f4ce1c --- /dev/null +++ b/tools/testing/selftests/drivers/net/netconsole/netcons_userdata.sh @@ -0,0 +1,229 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-2.0 + +# Exercise the netconsole userdata payload. +# +# The first part checks that the payload the target transmits follows what +# configfs says: a value shows up in the next message, an update replaces the +# previous one, clearing the value drops the entry, and so does removing the +# key. +# +# The second part rewrites values, creates and deletes keys, and clears the +# payload entirely while messages are being sent, so the transmit path keeps +# picking up payloads that are being replaced underneath it. It runs twice, +# once with a payload small enough to fit in a single packet and once large +# enough to be fragmented. +# +# Author: Breno Leitao + +set -euo pipefail + +SCRIPTDIR=$(dirname "$(readlink -e "${BASH_SOURCE[0]}")") + +source "${SCRIPTDIR}"/../lib/sh/lib_netcons.sh + +# Number of times each torture worker loops +ITERATIONS=${1:-200} + +# Keys owned by each torture worker. Workers do not share keys, so a failing +# configfs operation means a real problem and not a lost race. +CHURN_KEY="churnkey" +TRANSIENT_KEY="transientkey" +# Number of keys used to push a message past MAX_PRINT_CHUNK +BULK_KEYS=8 + +USERDATA_DIR="${NETCONS_PATH}/userdata" +# Values are capped at MAX_EXTRADATA_VALUE_LEN(200) bytes, so ${BULK_KEYS} +# entries of this size are enough to force fragmentation +LONG_VALUE=$(printf -- 'v%.0s' {1..190}) + +function write_key() { + local KEY="${1}" + local VALUE="${2}" + + mkdir -p "${USERDATA_DIR}/${KEY}" + echo "${VALUE}" > "${USERDATA_DIR}/${KEY}/value" +} + +# Send a single message and capture it on the destination interface +function send_and_capture() { + rm -f "${OUTPUT_FILE}" + + listen_port_and_save_to "${OUTPUT_FILE}" & + wait_for_port "${NAMESPACE}" "${PORT}" "${IP_VERSION}" + echo "${MSG}: ${TARGET}" > /dev/kmsg + busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" || true + pkill_socat + validate_msg "${OUTPUT_FILE}" +} + +function expect_in_msg() { + local WANTED="${1}" + + if ! grep -q -- "${WANTED}" "${OUTPUT_FILE}"; then + echo "FAIL: '${WANTED}' not found in ${OUTPUT_FILE}" >&2 + cat "${OUTPUT_FILE}" >&2 + exit "${ksft_fail}" + fi +} + +function expect_not_in_msg() { + local UNWANTED="${1}" + + if grep -q -- "${UNWANTED}" "${OUTPUT_FILE}"; then + echo "FAIL: '${UNWANTED}' found in ${OUTPUT_FILE}" >&2 + cat "${OUTPUT_FILE}" >&2 + exit "${ksft_fail}" + fi +} + +# Every write publishes a new payload and frees the previous one. An empty +# value is skipped when the payload is formatted, so this also drives the +# target through having no payload at all. +function churn_value() { + local i + + for i in $(seq "${ITERATIONS}") + do + echo "value${i}" > "${USERDATA_DIR}/${CHURN_KEY}/value" + echo > "${USERDATA_DIR}/${CHURN_KEY}/value" + done +} + +# Create and delete a key underneath the sender +function churn_key() { + local i + + for i in $(seq "${ITERATIONS}") + do + mkdir "${USERDATA_DIR}/${TRANSIENT_KEY}" + echo "transient${i}" > "${USERDATA_DIR}/${TRANSIENT_KEY}/value" + rmdir "${USERDATA_DIR}/${TRANSIENT_KEY}" + done +} + +# Keep the transmit path busy while the payload is being replaced +function send_messages() { + local i + + for i in $(seq "${ITERATIONS}") + do + echo "${MSG}: ${TARGET} ${i}" > /dev/kmsg + done +} + +# Run the workers concurrently and fail if any of them hits an error +function run_workers() { + local PIDS=() + local WORKER + local RET=0 + local PID + + for WORKER in "$@" + do + "${WORKER}" & + PIDS+=("$!") + done + + # Reap every worker before reporting a failure, otherwise a surviving + # worker keeps writing to configfs while the exit trap cleans it up. + for PID in "${PIDS[@]}" + do + wait "${PID}" || RET=1 + done + + if [[ "${RET}" -ne 0 ]] + then + echo "FAIL: userdata torture worker failed" >&2 + exit "${ksft_fail}" + fi +} + +function create_bulk_keys() { + local i + + for i in $(seq "${BULK_KEYS}") + do + write_key "bulk${i}" "${LONG_VALUE}" + done +} + +function delete_bulk_keys() { + local i + + for i in $(seq "${BULK_KEYS}") + do + rmdir "${USERDATA_DIR}/bulk${i}" + done +} + +# ========== # +# Start here # +# ========== # + +modprobe netdevsim 2> /dev/null || true +modprobe netconsole 2> /dev/null || true + +IP_VERSION="ipv4" +# The content of kmsg will be saved to the following file +OUTPUT_FILE="/tmp/${TARGET}" + +# Check for basic system dependency and exit if not found +check_for_dependencies +# Set current loglevel to KERN_INFO(6), and default to KERN_NOTICE(5) +echo "6 5" > /proc/sys/kernel/printk +# Remove the namespace, interfaces and netconsole target on exit +trap cleanup EXIT +# Create one namespace and two interfaces +set_network "${IP_VERSION}" +# Create a dynamic target for netconsole +create_dynamic_target + +# =================================================== +# TEST #1 +# A value written to configfs reaches the destination +# =================================================== +write_key "${USERDATA_KEY}" "first" +send_and_capture +expect_in_msg "${USERDATA_KEY}=first" + +# =================================================== +# TEST #2 +# Updating the value replaces the previous payload +# =================================================== +write_key "${USERDATA_KEY}" "second" +send_and_capture +expect_in_msg "${USERDATA_KEY}=second" +expect_not_in_msg "${USERDATA_KEY}=first" + +# =================================================== +# TEST #3 +# Clearing the value drops the entry +# =================================================== +echo > "${USERDATA_DIR}/${USERDATA_KEY}/value" +send_and_capture +expect_not_in_msg "${USERDATA_KEY}=" + +# =================================================== +# TEST #4 +# Removing the key drops the entry +# =================================================== +write_key "${USERDATA_KEY}" "third" +rmdir "${USERDATA_DIR}/${USERDATA_KEY}" +send_and_capture +expect_not_in_msg "${USERDATA_KEY}=" +rm "${OUTPUT_FILE}" + +# =================================================== +# TEST #5 +# Torture the payload while messages are being sent, +# first unfragmented and then fragmented +# =================================================== +write_key "${CHURN_KEY}" "${USERDATA_VALUE}" +run_workers churn_value churn_key send_messages + +create_bulk_keys +run_workers churn_value churn_key send_messages +delete_bulk_keys + +exit "${ksft_pass}" From ebb16fca011ce08fa710cec42ee43a33bf331893 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Tue, 11 Aug 2026 23:13:42 +0800 Subject: [PATCH 1287/1433] net: phy: add PHY package locking helpers The PHY package API provides private data shared by all PHYs in a package. Drivers are responsible for synchronizing access to this data, but the API does not provide a lock for that purpose. Add phy_package_lock() and phy_package_unlock() for drivers to serialize access to package-private data, including its initialization. Reviewed-by: Andrew Lunn Signed-off-by: Xuanqiang Luo Link: https://patch.msgid.link/20260811151345.73582-2-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/phy/phy_package.c | 23 +++++++++++++++++++++++ drivers/net/phy/phylib.h | 2 ++ 2 files changed, 25 insertions(+) diff --git a/drivers/net/phy/phy_package.c b/drivers/net/phy/phy_package.c index 16ae8d1c1f89..735806c5bea8 100644 --- a/drivers/net/phy/phy_package.c +++ b/drivers/net/phy/phy_package.c @@ -52,6 +52,29 @@ void *phy_package_get_priv(struct phy_device *phydev) } EXPORT_SYMBOL_GPL(phy_package_get_priv); +/** + * phy_package_lock - acquire the PHY package lock + * @phydev: PHY device that has joined the package + * + * Use this to serialize access to package-private data. Release the lock + * with phy_package_unlock(). + */ +void phy_package_lock(struct phy_device *phydev) +{ + mutex_lock(&phydev->mdio.bus->shared_lock); +} +EXPORT_SYMBOL_GPL(phy_package_lock); + +/** + * phy_package_unlock - release the PHY package lock + * @phydev: PHY device that has joined the package + */ +void phy_package_unlock(struct phy_device *phydev) +{ + mutex_unlock(&phydev->mdio.bus->shared_lock); +} +EXPORT_SYMBOL_GPL(phy_package_unlock); + static int phy_package_address(struct phy_device *phydev, unsigned int addr_offset) { diff --git a/drivers/net/phy/phylib.h b/drivers/net/phy/phylib.h index 0fba245f9745..c6e26ac6b28f 100644 --- a/drivers/net/phy/phylib.h +++ b/drivers/net/phy/phylib.h @@ -12,6 +12,8 @@ struct mii_bus; struct device_node *phy_package_get_node(struct phy_device *phydev); void *phy_package_get_priv(struct phy_device *phydev); +void phy_package_lock(struct phy_device *phydev); +void phy_package_unlock(struct phy_device *phydev); int __phy_package_read(struct phy_device *phydev, unsigned int addr_offset, u32 regnum); int __phy_package_write(struct phy_device *phydev, unsigned int addr_offset, From 20663d78f1a1c242ad865967a31ee716dd1e52a1 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Tue, 11 Aug 2026 23:13:43 +0800 Subject: [PATCH 1288/1433] net: phy: dp83640: embed pin configuration in clock The DP83640 has a fixed number of PTP pins, and its pin configuration has the same lifetime as the per-bus clock. Allocating the configuration separately adds an allocation failure path and requires a separate free. Embed the pin configuration in struct dp83640_clock and point the PTP clock information at the embedded array. This changes only the storage; the pin functions remain configurable at runtime. It also allows all per-bus clock storage to be managed as one allocation. Reviewed-by: Andrew Lunn Signed-off-by: Xuanqiang Luo Link: https://patch.msgid.link/20260811151345.73582-3-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/phy/dp83640.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/drivers/net/phy/dp83640.c b/drivers/net/phy/dp83640.c index 98472abdd392..ba39d30b7470 100644 --- a/drivers/net/phy/dp83640.c +++ b/drivers/net/phy/dp83640.c @@ -146,6 +146,8 @@ struct dp83640_clock { struct list_head phylist; /* reference to our PTP hardware clock */ struct ptp_clock *ptp_clock; + /* protected by the PTP core pin configuration lock */ + struct ptp_pin_desc pin_config[DP83640_N_PINS]; }; /* globals */ @@ -960,6 +962,7 @@ static void dp83640_clock_init(struct dp83640_clock *clock, struct mii_bus *bus) mutex_init(&clock->extreg_lock); mutex_init(&clock->clock_lock); INIT_LIST_HEAD(&clock->phylist); + clock->caps.pin_config = clock->pin_config; clock->caps.owner = THIS_MODULE; sprintf(clock->caps.name, "dp83640 timer"); clock->caps.max_adj = 1953124; @@ -977,9 +980,7 @@ static void dp83640_clock_init(struct dp83640_clock *clock, struct mii_bus *bus) clock->caps.settime64 = ptp_dp83640_settime; clock->caps.enable = ptp_dp83640_enable; clock->caps.verify = ptp_dp83640_verify; - /* - * Convert the module param defaults into a dynamic pin configuration. - */ + /* Initialize the runtime pin configuration from gpio_tab. */ dp83640_gpio_defaults(clock->caps.pin_config); /* * Get a reference to this bus instance. @@ -1031,13 +1032,6 @@ static struct dp83640_clock *dp83640_clock_get_bus(struct mii_bus *bus) if (!clock) goto out; - clock->caps.pin_config = kzalloc_objs(struct ptp_pin_desc, - DP83640_N_PINS); - if (!clock->caps.pin_config) { - kfree(clock); - clock = NULL; - goto out; - } dp83640_clock_init(clock, bus); list_add_tail(&clock->list, &phyter_clocks); out: @@ -1509,7 +1503,6 @@ static void dp83640_remove(struct phy_device *phydev) mutex_destroy(&clock->extreg_lock); mutex_destroy(&clock->clock_lock); put_device(&clock->bus->dev); - kfree(clock->caps.pin_config); kfree(clock); } } From e8b166c1f0a53312bd50355ea4db77b2a6728e0f Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Tue, 11 Aug 2026 23:13:44 +0800 Subject: [PATCH 1289/1433] net: phy: dp83640: clear state after PTP registration failure dp83640_probe() publishes its per-PHY state through phydev before registering the PTP clock. If registration fails, the private data is freed while phydev->mii_ts and phydev->priv still point to it, and default_timestamp remains set. Clear the published PHY state and reset the PTP clock pointer before freeing the private data. Cc: stable+noautosel@kernel.org # untested fix to a driver init path Reviewed-by: Andrew Lunn Signed-off-by: Xuanqiang Luo Link: https://patch.msgid.link/20260811151345.73582-4-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/phy/dp83640.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/phy/dp83640.c b/drivers/net/phy/dp83640.c index ba39d30b7470..7aa5cf0a7bb0 100644 --- a/drivers/net/phy/dp83640.c +++ b/drivers/net/phy/dp83640.c @@ -1449,6 +1449,10 @@ static int dp83640_probe(struct phy_device *phydev) no_register: clock->chosen = NULL; + clock->ptp_clock = NULL; + phydev->default_timestamp = false; + phydev->mii_ts = NULL; + phydev->priv = NULL; kfree(dp83640); no_memory: dp83640_clock_put(clock); From 854ac5fde2215a3cb06f6d05b744869a31889064 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Tue, 11 Aug 2026 23:13:45 +0800 Subject: [PATCH 1290/1433] net: phy: dp83640: fix per-bus clock lifetime Commit 42e2a9e11a1d ("net: phy: dp83640: improve phydev and driver removal handling") moved per-bus clock cleanup from module exit to the remove path. This leaves two lifetime problems. dp83640_clock_get_bus() publishes a newly allocated clock before the driver allocates its per-PHY data and registers the PTP clock. If either operation fails, no PHY is bound and the remove callback cannot release the clock, leaking the clock and the MII bus device reference. The remove path can also free a clock after dropping clock_lock. A concurrent probe may already have found the clock under phyter_clocks_lock and be waiting for clock_lock, allowing it to acquire a freed mutex and access the freed clock. Use the PHY package infrastructure for the per-bus clock. PHY packages are tracked per MII bus, and the driver uses BROADCAST_ADDR as the package key so the DP83640 PHYs on the same bus share the same clock storage. Call phy_package_join() during probe and phy_package_leave() on probe errors and in remove. Serialize the one-time clock initialization with the package lock because phy_package_probe_once() elects an initializer but does not wait for initialization to finish. Cc: stable+noautosel@kernel.org # untested fix to a driver init path Reviewed-by: Andrew Lunn Signed-off-by: Xuanqiang Luo Link: https://patch.msgid.link/20260811151345.73582-5-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/phy/dp83640.c | 112 +++++++++----------------------------- drivers/ptp/Kconfig | 1 + 2 files changed, 28 insertions(+), 85 deletions(-) diff --git a/drivers/net/phy/dp83640.c b/drivers/net/phy/dp83640.c index 7aa5cf0a7bb0..6867e7c6f3b7 100644 --- a/drivers/net/phy/dp83640.c +++ b/drivers/net/phy/dp83640.c @@ -21,6 +21,7 @@ #include #include "dp83640_reg.h" +#include "phylib.h" #define DP83640_PHY_ID 0x20005ce1 #define PAGESEL 0x13 @@ -128,10 +129,6 @@ struct dp83640_private { }; struct dp83640_clock { - /* keeps the instance in the 'phyter_clocks' list */ - struct list_head list; - /* we create one clock instance per MII bus */ - struct mii_bus *bus; /* protects extended registers from concurrent access */ struct mutex extreg_lock; /* remembers which page was last selected */ @@ -208,10 +205,6 @@ static void dp83640_gpio_defaults(struct ptp_pin_desc *pd) } } -/* a list of clocks and a mutex to protect it */ -static LIST_HEAD(phyter_clocks); -static DEFINE_MUTEX(phyter_clocks_lock); - static void rx_timestamp_work(struct work_struct *work); /* extended register access functions */ @@ -955,10 +948,8 @@ static void decode_status_frame(struct dp83640_private *dp83640, } } -static void dp83640_clock_init(struct dp83640_clock *clock, struct mii_bus *bus) +static void dp83640_clock_init(struct dp83640_clock *clock) { - INIT_LIST_HEAD(&clock->list); - clock->bus = bus; mutex_init(&clock->extreg_lock); mutex_init(&clock->clock_lock); INIT_LIST_HEAD(&clock->phylist); @@ -982,10 +973,6 @@ static void dp83640_clock_init(struct dp83640_clock *clock, struct mii_bus *bus) clock->caps.verify = ptp_dp83640_verify; /* Initialize the runtime pin configuration from gpio_tab. */ dp83640_gpio_defaults(clock->caps.pin_config); - /* - * Get a reference to this bus instance. - */ - get_device(&bus->dev); } static int choose_this_phy(struct dp83640_clock *clock, @@ -1000,51 +987,6 @@ static int choose_this_phy(struct dp83640_clock *clock, return 0; } -static struct dp83640_clock *dp83640_clock_get(struct dp83640_clock *clock) -{ - if (clock) - mutex_lock(&clock->clock_lock); - return clock; -} - -/* - * Look up and lock a clock by bus instance. - * If there is no clock for this bus, then create it first. - */ -static struct dp83640_clock *dp83640_clock_get_bus(struct mii_bus *bus) -{ - struct dp83640_clock *clock = NULL, *tmp; - struct list_head *this; - - mutex_lock(&phyter_clocks_lock); - - list_for_each(this, &phyter_clocks) { - tmp = list_entry(this, struct dp83640_clock, list); - if (tmp->bus == bus) { - clock = tmp; - break; - } - } - if (clock) - goto out; - - clock = kzalloc_obj(struct dp83640_clock); - if (!clock) - goto out; - - dp83640_clock_init(clock, bus); - list_add_tail(&clock->list, &phyter_clocks); -out: - mutex_unlock(&phyter_clocks_lock); - - return dp83640_clock_get(clock); -} - -static void dp83640_clock_put(struct dp83640_clock *clock) -{ - mutex_unlock(&clock->clock_lock); -} - static int dp83640_soft_reset(struct phy_device *phydev) { int ret; @@ -1394,20 +1336,31 @@ static int dp83640_ts_info(struct mii_timestamper *mii_ts, static int dp83640_probe(struct phy_device *phydev) { - struct dp83640_clock *clock; struct dp83640_private *dp83640; - int err = -ENOMEM, i; + struct dp83640_clock *clock; + int err, i; if (phydev->mdio.addr == BROADCAST_ADDR) return 0; - clock = dp83640_clock_get_bus(phydev->mdio.bus); - if (!clock) - goto no_clock; + err = phy_package_join(phydev, BROADCAST_ADDR, sizeof(*clock)); + if (err) + return err; + + clock = phy_package_get_priv(phydev); + /* Ensure other PHY probes wait for shared clock initialization. */ + phy_package_lock(phydev); + if (phy_package_probe_once(phydev)) + dp83640_clock_init(clock); + phy_package_unlock(phydev); + + mutex_lock(&clock->clock_lock); dp83640 = kzalloc_obj(struct dp83640_private); - if (!dp83640) + if (!dp83640) { + err = -ENOMEM; goto no_memory; + } dp83640->phydev = phydev; dp83640->mii_ts.rxtstamp = dp83640_rxtstamp; @@ -1444,7 +1397,8 @@ static int dp83640_probe(struct phy_device *phydev) } else list_add_tail(&dp83640->list, &clock->phylist); - dp83640_clock_put(clock); + mutex_unlock(&clock->clock_lock); + return 0; no_register: @@ -1455,8 +1409,8 @@ static int dp83640_probe(struct phy_device *phydev) phydev->priv = NULL; kfree(dp83640); no_memory: - dp83640_clock_put(clock); -no_clock: + mutex_unlock(&clock->clock_lock); + phy_package_leave(phydev); return err; } @@ -1465,7 +1419,6 @@ static void dp83640_remove(struct phy_device *phydev) struct dp83640_clock *clock; struct list_head *this, *next; struct dp83640_private *tmp, *dp83640 = phydev->priv; - bool remove_clock = false; if (phydev->mdio.addr == BROADCAST_ADDR) return; @@ -1478,7 +1431,8 @@ static void dp83640_remove(struct phy_device *phydev) skb_queue_purge(&dp83640->rx_queue); skb_queue_purge(&dp83640->tx_queue); - clock = dp83640_clock_get(dp83640->clock); + clock = dp83640->clock; + mutex_lock(&clock->clock_lock); if (dp83640 == clock->chosen) { ptp_clock_unregister(clock->ptp_clock); @@ -1493,22 +1447,10 @@ static void dp83640_remove(struct phy_device *phydev) } } - if (!clock->chosen && list_empty(&clock->phylist)) - remove_clock = true; - - dp83640_clock_put(clock); + mutex_unlock(&clock->clock_lock); kfree(dp83640); - if (remove_clock) { - mutex_lock(&phyter_clocks_lock); - list_del(&clock->list); - mutex_unlock(&phyter_clocks_lock); - - mutex_destroy(&clock->extreg_lock); - mutex_destroy(&clock->clock_lock); - put_device(&clock->bus->dev); - kfree(clock); - } + phy_package_leave(phydev); } static struct phy_driver dp83640_driver[] = { diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig index b93640ca08b7..feb50f8cc406 100644 --- a/drivers/ptp/Kconfig +++ b/drivers/ptp/Kconfig @@ -78,6 +78,7 @@ config DP83640_PHY depends on PHYLIB depends on PTP_1588_CLOCK select CRC32 + select PHY_PACKAGE help Supports the DP83640 PHYTER with IEEE 1588 features. From 36cdf5d48ca191dcd71c28cadbe0981b1d25318d Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Sat, 8 Aug 2026 02:21:23 -0500 Subject: [PATCH 1291/1433] net/smc: unregister the connection before draining the rx tasklet smc_conn_free() calls smc_ism_unset_conn() only while the link group is still on its device list, and never sets conn->killed. smc_lgr_terminate_sched() unlinks the group immediately and defers killing its connections to a work item, so a connection freed in that window keeps its smcd->conn[] slot with both gates in smcd_handle_irq() open, and the device can re-arm the receive tasklet after tasklet_kill() has returned. On the DMB-nocopy path the ghost send buffer is freed right after that drain, so the re-armed tasklet dereferences it. Unregister unconditionally and drain before the detach at both teardown sites, mirroring rmb_desc, which smc_buf_unuse() releases after the drain. Clear conn->sndbuf_desc before freeing it as well, so a reader that samples the pointer cannot get one that is already freed. Fixes: ae2be35cbed2 ("net/smc: {at|de}tach sndbuf to peer DMB if supported") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Reviewed-by: Sidraya Jayagond Reviewed-by: Tony Lu Link: https://patch.msgid.link/20260808-b4-disp-22f119e6-v2-1-61647601a6f3@proton.me Signed-off-by: Jakub Kicinski --- net/smc/smc_core.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/net/smc/smc_core.c b/net/smc/smc_core.c index b4208cb186c5..181647982490 100644 --- a/net/smc/smc_core.c +++ b/net/smc/smc_core.c @@ -1209,14 +1209,16 @@ static void smcd_buf_detach(struct smc_connection *conn) { struct smcd_dev *smcd = conn->lgr->smcd; u64 peer_token = conn->peer_token; + struct smc_buf_desc *buf_desc; if (!conn->sndbuf_desc) return; smc_ism_detach_dmb(smcd, peer_token); - kfree(conn->sndbuf_desc); + buf_desc = conn->sndbuf_desc; conn->sndbuf_desc = NULL; + kfree(buf_desc); } static void smc_buf_unuse(struct smc_connection *conn, @@ -1268,11 +1270,10 @@ void smc_conn_free(struct smc_connection *conn) goto lgr_put; if (lgr->is_smcd) { - if (!list_empty(&lgr->list)) - smc_ism_unset_conn(conn); + smc_ism_unset_conn(conn); + tasklet_kill(&conn->rx_tsklet); if (smc_ism_support_dmb_nocopy(lgr->smcd)) smcd_buf_detach(conn); - tasklet_kill(&conn->rx_tsklet); } else { smc_cdc_wait_pend_tx_wr(conn); if (current_work() != &conn->abort_work) @@ -1525,12 +1526,12 @@ static void smc_conn_kill(struct smc_connection *conn, bool soft) smc_sk_wake_ups(smc); if (conn->lgr->is_smcd) { smc_ism_unset_conn(conn); - if (smc_ism_support_dmb_nocopy(conn->lgr->smcd)) - smcd_buf_detach(conn); if (soft) tasklet_kill(&conn->rx_tsklet); else tasklet_unlock_wait(&conn->rx_tsklet); + if (smc_ism_support_dmb_nocopy(conn->lgr->smcd)) + smcd_buf_detach(conn); } else { smc_cdc_wait_pend_tx_wr(conn); } From b395dd319cea422239cb45b998fb38d7e373af87 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Sat, 8 Aug 2026 02:21:24 -0500 Subject: [PATCH 1292/1433] net/smc: do not dereference an unset send buffer on the SMC-D teardown path smc_close_stream_wait() calls smc_tx_prepared_sends() from inside its sk_wait_event() condition, and sk_wait_event() evaluates that condition once with the socket lock released. smcd_buf_detach() clears conn->sndbuf_desc from smc_conn_kill() under lock_sock(), so a link group terminating while a socket waits there leaves the helper dereferencing NULL, faulting out of close(). SIOCOUTQ reads the field by hand, and smc_close_cancel_work() drops the lock across two cancel_*_sync() calls. Sample the pointer once in the helper, report nothing prepared while it is unset, and bound the ioctl the same way. The receive tasklet dereferences the field directly in smc_cdc_msg_recv_action(), not through this helper; 1/2 is what keeps it from running that late. Fixes: ae2be35cbed2 ("net/smc: {at|de}tach sndbuf to peer DMB if supported") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Reviewed-by: Sidraya Jayagond Reviewed-by: Tony Lu Link: https://patch.msgid.link/20260808-b4-disp-22f119e6-v2-2-61647601a6f3@proton.me Signed-off-by: Jakub Kicinski --- net/smc/af_smc.c | 3 ++- net/smc/smc_tx.h | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c index 00403175b740..cff910cedbfc 100644 --- a/net/smc/af_smc.c +++ b/net/smc/af_smc.c @@ -3233,7 +3233,8 @@ int smc_ioctl(struct socket *sock, unsigned int cmd, return -EINVAL; } if (smc->sk.sk_state == SMC_INIT || - smc->sk.sk_state == SMC_CLOSED) + smc->sk.sk_state == SMC_CLOSED || + !READ_ONCE(smc->conn.sndbuf_desc)) answ = 0; else answ = smc->conn.sndbuf_desc->len - diff --git a/net/smc/smc_tx.h b/net/smc/smc_tx.h index a59f370b8b43..610a945aefd6 100644 --- a/net/smc/smc_tx.h +++ b/net/smc/smc_tx.h @@ -20,11 +20,15 @@ static inline int smc_tx_prepared_sends(struct smc_connection *conn) { + struct smc_buf_desc *sndbuf_desc = READ_ONCE(conn->sndbuf_desc); union smc_host_cursor sent, prep; + if (!sndbuf_desc) + return 0; + smc_curs_copy(&sent, &conn->tx_curs_sent, conn); smc_curs_copy(&prep, &conn->tx_curs_prep, conn); - return smc_curs_diff(conn->sndbuf_desc->len, &sent, &prep); + return smc_curs_diff(sndbuf_desc->len, &sent, &prep); } void smc_tx_pending(struct smc_connection *conn); From ed267f783c0c283171e132bb660f8753b40a2660 Mon Sep 17 00:00:00 2001 From: Maoyi Xie Date: Sun, 9 Aug 2026 17:42:52 +0800 Subject: [PATCH 1293/1433] l2tp: send netlink notifications in the tunnel's net namespace l2tp_tunnel_notify() and l2tp_session_notify() use genlmsg_multicast_allns(), which delivers to listeners in every network namespace. l2tp is per-namespace, and a tunnel records the namespace it belongs to in tunnel->l2tp_net. Each event concerns one namespace, yet every namespace is told about it. A tunnel event carries the tunnel and peer tunnel ids, plus the socket's addresses with both ports for a UDP tunnel. A session event carries the session and peer session ids, the interface name, plus the L2TP cookies where those are set. A listener needs no privilege for any of this, because l2tp_multicast_group[] carries no flags and genl_bind() asks for no capability. The fix is to send to the tunnel's namespace with genlmsg_multicast_netns(). Commit 134e63756d5f ("genetlink: make netns aware") added both helpers and drew the line between them. The netns variant is for an object that lives in a namespace. I found this by auditing the tree's six genlmsg_multicast_allns() call sites for objects that live in a network namespace. Only the two l2tp ones do. I reproduced it on net at dd057113ac7b, in a virtual machine, with no real hardware involved. A process in the initial namespace, running as an ordinary user with an empty capability set, receives the create and delete events of a tunnel. The tunnel was set up inside an unprivileged user and network namespace. tools/testing/selftests/net/l2tp.sh passes before and after. On a container host, any local user and every other tenant can read a tenant's tunnel parameters. Cc: stable+noautosel@kernel.org # high regression risk Signed-off-by: Maoyi Xie Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260809094252.2107242-1-maoyixie.tju@gmail.com Signed-off-by: Jakub Kicinski --- net/l2tp/l2tp_netlink.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/net/l2tp/l2tp_netlink.c b/net/l2tp/l2tp_netlink.c index 59457c0c14aa..c0c4d1ebc7a3 100644 --- a/net/l2tp/l2tp_netlink.c +++ b/net/l2tp/l2tp_netlink.c @@ -116,7 +116,8 @@ static int l2tp_tunnel_notify(struct genl_family *family, NLM_F_ACK, tunnel, cmd); if (ret >= 0) { - ret = genlmsg_multicast_allns(family, msg, 0, 0); + ret = genlmsg_multicast_netns(family, tunnel->l2tp_net, msg, + 0, 0, GFP_KERNEL); /* We don't care if no one is listening */ if (ret == -ESRCH) ret = 0; @@ -144,7 +145,9 @@ static int l2tp_session_notify(struct genl_family *family, NLM_F_ACK, session, cmd); if (ret >= 0) { - ret = genlmsg_multicast_allns(family, msg, 0, 0); + ret = genlmsg_multicast_netns(family, + session->tunnel->l2tp_net, msg, + 0, 0, GFP_KERNEL); /* We don't care if no one is listening */ if (ret == -ESRCH) ret = 0; From 447c9303942c439a117d9b76ce6d6e2116b38ee7 Mon Sep 17 00:00:00 2001 From: Asim Viladi Oglu Manizada Date: Wed, 12 Aug 2026 01:21:53 +0000 Subject: [PATCH 1294/1433] net: tun: bound receive headroom tun_get_user() uses tun->align both as skb headroom and when choosing how much packet data to keep linear. OVS can propagate an oversized headroom request from another port to TUN or TAP. When align is larger than the usable space in a one-page skb head, SKB_MAX_HEAD(align) underflows and the result becomes negative when stored in good_linear. That value later wraps when assigned to the size_t linear variable, and tun_alloc_skb() can place skb->data outside the allocated head. Bound the headroom stored by TUN to the one-page skb-head budget and the largest non-sentinel 16-bit skb header offset. Leave one linear byte for raw TUN and a complete Ethernet header for TAP, including NET_IP_ALIGN. Also pull the raw-TUN protocol byte and the TAP Ethernet header before accessing them, so these checks remain safe for nonlinear skbs supplied by other allocation paths. Fixes: eaea34b23c46 ("net/tun: implement ndo_set_rx_headroom") Cc: stable@vger.kernel.org Signed-off-by: Asim Viladi Oglu Manizada Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260812012139.2134643-1-manizada@pm.me Signed-off-by: Jakub Kicinski --- drivers/net/tun.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/drivers/net/tun.c b/drivers/net/tun.c index fed9dfdfcc3b..5bbe3123979e 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -1107,11 +1107,16 @@ static netdev_features_t tun_net_fix_features(struct net_device *dev, static void tun_set_headroom(struct net_device *dev, int new_hr) { struct tun_struct *tun = netdev_priv(dev); + size_t max_headroom; - if (new_hr < NET_SKB_PAD) - new_hr = NET_SKB_PAD; + max_headroom = min_t(size_t, SKB_MAX_HEAD(0), U16_MAX - 1); - tun->align = new_hr; + if ((tun->flags & TUN_TYPE_MASK) == IFF_TAP) + max_headroom -= ETH_HLEN + NET_IP_ALIGN; + else + max_headroom -= 1; + + tun->align = clamp_t(int, new_hr, NET_SKB_PAD, max_headroom); } static void @@ -1822,7 +1827,13 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, switch (tun->flags & TUN_TYPE_MASK) { case IFF_TUN: if (tun->flags & IFF_NO_PI) { - u8 ip_version = skb->len ? (skb->data[0] >> 4) : 0; + u8 ip_version; + + if (!pskb_may_pull(skb, 1)) { + err = -EINVAL; + goto drop; + } + ip_version = skb->data[0] >> 4; switch (ip_version) { case 4: @@ -1842,7 +1853,7 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, skb->dev = tun->dev; break; case IFF_TAP: - if (frags && !pskb_may_pull(skb, ETH_HLEN)) { + if (!pskb_may_pull(skb, ETH_HLEN)) { err = -ENOMEM; drop_reason = SKB_DROP_REASON_HDR_TRUNC; goto drop; From b3217bdb0091e52887e23896cd82483f7808914a Mon Sep 17 00:00:00 2001 From: Slawomir Stepien Date: Mon, 10 Aug 2026 10:57:17 +0200 Subject: [PATCH 1295/1433] netdevsim: drop the ability to change max_vfs via debugfs This debugfs file isn't used by kernel's selftests, so drop it. Reported-by: syzbot+3147c5de186107ffc7a1@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=3147c5de186107ffc7a1 Suggested-by: Jakub Kicinski Signed-off-by: Slawomir Stepien Link: https://patch.msgid.link/20260810085717.570382-1-sst@poczta.fm Signed-off-by: Jakub Kicinski --- drivers/net/netdevsim/bus.c | 3 -- drivers/net/netdevsim/dev.c | 79 +------------------------------ drivers/net/netdevsim/netdevsim.h | 3 +- 3 files changed, 3 insertions(+), 82 deletions(-) diff --git a/drivers/net/netdevsim/bus.c b/drivers/net/netdevsim/bus.c index 41483e371f05..5c55c308487b 100644 --- a/drivers/net/netdevsim/bus.c +++ b/drivers/net/netdevsim/bus.c @@ -443,8 +443,6 @@ static const struct bus_type nsim_bus = { .num_vf = nsim_num_vf, }; -#define NSIM_BUS_DEV_MAX_VFS 4 - static struct nsim_bus_dev * nsim_bus_dev_new(unsigned int id, unsigned int port_count, unsigned int num_queues) { @@ -464,7 +462,6 @@ nsim_bus_dev_new(unsigned int id, unsigned int port_count, unsigned int num_queu nsim_bus_dev->port_count = port_count; nsim_bus_dev->num_queues = num_queues; nsim_bus_dev->initial_net = current->nsproxy->net_ns; - nsim_bus_dev->max_vfs = NSIM_BUS_DEV_MAX_VFS; /* Disallow using nsim_bus_dev */ smp_store_release(&nsim_bus_dev->init, false); diff --git a/drivers/net/netdevsim/dev.c b/drivers/net/netdevsim/dev.c index f65b4cf4ea39..feb88ccbbced 100644 --- a/drivers/net/netdevsim/dev.c +++ b/drivers/net/netdevsim/dev.c @@ -225,78 +225,6 @@ static const struct file_operations nsim_dev_trap_fa_cookie_fops = { .owner = THIS_MODULE, }; -static ssize_t nsim_bus_dev_max_vfs_read(struct file *file, char __user *data, - size_t count, loff_t *ppos) -{ - struct nsim_dev *nsim_dev = file->private_data; - char buf[11]; - ssize_t len; - - len = scnprintf(buf, sizeof(buf), "%u\n", - READ_ONCE(nsim_dev->nsim_bus_dev->max_vfs)); - - return simple_read_from_buffer(data, count, ppos, buf, len); -} - -static ssize_t nsim_bus_dev_max_vfs_write(struct file *file, - const char __user *data, - size_t count, loff_t *ppos) -{ - struct nsim_vf_config *vfconfigs; - struct nsim_dev *nsim_dev; - char buf[10]; - ssize_t ret; - u32 val; - - if (*ppos != 0) - return 0; - - if (count >= sizeof(buf)) - return -ENOSPC; - - ret = copy_from_user(buf, data, count); - if (ret) - return -EFAULT; - buf[count] = '\0'; - - ret = kstrtouint(buf, 10, &val); - if (ret) - return -EINVAL; - - /* max_vfs limited by the maximum number of provided port indexes */ - if (val > NSIM_DEV_VF_PORT_INDEX_MAX - NSIM_DEV_VF_PORT_INDEX_BASE) - return -ERANGE; - - vfconfigs = kzalloc_objs(struct nsim_vf_config, val, - GFP_KERNEL | __GFP_NOWARN); - if (!vfconfigs) - return -ENOMEM; - - nsim_dev = file->private_data; - devl_lock(priv_to_devlink(nsim_dev)); - /* Reject if VFs are configured */ - if (nsim_dev_get_vfs(nsim_dev)) { - ret = -EBUSY; - } else { - swap(nsim_dev->vfconfigs, vfconfigs); - WRITE_ONCE(nsim_dev->nsim_bus_dev->max_vfs, val); - *ppos += count; - ret = count; - } - devl_unlock(priv_to_devlink(nsim_dev)); - - kfree(vfconfigs); - return ret; -} - -static const struct file_operations nsim_dev_max_vfs_fops = { - .open = simple_open, - .read = nsim_bus_dev_max_vfs_read, - .write = nsim_bus_dev_max_vfs_write, - .llseek = generic_file_llseek, - .owner = THIS_MODULE, -}; - static int nsim_dev_debugfs_init(struct nsim_dev *nsim_dev) { char dev_ddir_name[sizeof(DRV_NAME) + 10]; @@ -343,9 +271,6 @@ static int nsim_dev_debugfs_init(struct nsim_dev *nsim_dev) debugfs_create_bool("fail_trap_policer_counter_get", 0600, nsim_dev->ddir, &nsim_dev->fail_trap_policer_counter_get); - /* caution, dev_max_vfs write takes devlink lock */ - debugfs_create_file("max_vfs", 0600, nsim_dev->ddir, - nsim_dev, &nsim_dev_max_vfs_fops); nsim_dev->nodes_ddir = debugfs_create_dir("rate_nodes", nsim_dev->ddir); if (IS_ERR(nsim_dev->nodes_ddir)) { @@ -1673,7 +1598,7 @@ int nsim_drv_probe(struct nsim_bus_dev *nsim_bus_dev) dev_set_drvdata(&nsim_bus_dev->dev, nsim_dev); nsim_dev->vfconfigs = kzalloc_objs(struct nsim_vf_config, - nsim_bus_dev->max_vfs, + NSIM_BUS_DEV_MAX_VFS, GFP_KERNEL | __GFP_NOWARN); if (!nsim_dev->vfconfigs) { err = -ENOMEM; @@ -1872,7 +1797,7 @@ int nsim_drv_configure_vfs(struct nsim_bus_dev *nsim_bus_dev, ret = -EBUSY; goto exit_unlock; } - if (nsim_bus_dev->max_vfs < num_vfs) { + if (num_vfs > NSIM_BUS_DEV_MAX_VFS) { ret = -ENOMEM; goto exit_unlock; } diff --git a/drivers/net/netdevsim/netdevsim.h b/drivers/net/netdevsim/netdevsim.h index 64f77f93d937..55aec41237b9 100644 --- a/drivers/net/netdevsim/netdevsim.h +++ b/drivers/net/netdevsim/netdevsim.h @@ -292,7 +292,6 @@ enum nsim_dev_port_type { }; #define NSIM_DEV_VF_PORT_INDEX_BASE 128 -#define NSIM_DEV_VF_PORT_INDEX_MAX UINT_MAX struct nsim_dev_port { struct list_head list; @@ -472,6 +471,7 @@ nsim_psp_handle_ext(struct sk_buff *skb, struct skb_ext *psp_ext) {} int nsim_setup_tc(struct net_device *dev, enum tc_setup_type type, void *type_data); +#define NSIM_BUS_DEV_MAX_VFS 4 struct nsim_bus_dev { struct device dev; struct list_head list; @@ -480,7 +480,6 @@ struct nsim_bus_dev { struct net *initial_net; /* Purpose of this is to carry net pointer * during the probe time only. */ - unsigned int max_vfs; unsigned int num_vfs; bool init; }; From a7c44619c6977fd64da99a6d1c2b73e2ad9af873 Mon Sep 17 00:00:00 2001 From: Florian Bezdeka Date: Mon, 10 Aug 2026 15:19:17 +0200 Subject: [PATCH 1296/1433] net: stmmac: intel: Add missing pci_free_irq_vectors() calls The IRQ vectors allocated in stmmac_config_multi_msi() or stmmac_config_single_msi() where never explicitly cleaned up. As pcim_enable_device() is used, all sorts of other functions are switched to managed mode. The missing cleanup here isn't actually missing, it's buried in the depths of PCI code. But: There are some ongoing activities to remove that cleanup magic. See the linked discussions below. This patch prepares the dwmac-intel code for the removal. Link: https://lore.kernel.org/netdev/27fec7d0ed633218a7787be3edce63c3038c63e2.camel@mailbox.org/ Link: https://lore.kernel.org/netdev/7e024db2557a4d5822a0dd409ae678d10d815d9c.camel@mailbox.org/ Signed-off-by: Florian Bezdeka Link: https://patch.msgid.link/20260810-flo-net-stmmac-default-affinity-core-v2-1-d2105780b8ca@siemens.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c index 4d207f41a43b..f5f9fa67ecd7 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c @@ -1348,6 +1348,7 @@ static int intel_eth_pci_probe(struct pci_dev *pdev, err_alloc_irq: clk_disable_unprepare(plat->stmmac_clk); clk_unregister_fixed_rate(plat->stmmac_clk); + pci_free_irq_vectors(pdev); return ret; } @@ -1367,6 +1368,7 @@ static void intel_eth_pci_remove(struct pci_dev *pdev) clk_disable_unprepare(priv->plat->stmmac_clk); clk_unregister_fixed_rate(priv->plat->stmmac_clk); + pci_free_irq_vectors(pdev); } #define PCI_DEVICE_ID_INTEL_QUARK 0x0937 From 77e80af7d2d4dc223716c90e9fc043bc66a8335f Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 12 Aug 2026 09:22:30 -0700 Subject: [PATCH 1297/1433] ethtool: tsconfig: reject zero-valued tx_type and rx_filter bitsets The ffs()/fls() guard in ethnl_set_tsconfig() was meant to enforce that the user selects exactly one tx_type (and one rx_filter) at a time (off / none are explicit types with non-zero values). However, both ffs(0) and fls(0) return 0, so the guard passes a zero-valued bitset through. The subsequent ffs(req_tx_type) - 1 would produce -1, if user selected no bit. net_hwtstamp_validate() catches the invalid -1 downstream, but returns a generic error (-ERANGE) without telling the user what went wrong. Return -EINVAL + extack instead. Replace the ffs()/fls() comparison with a hweight32() == 1 check. Reviewed-by: Andrew Lunn Reviewed-by: Joe Damato Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260812162230.1837788-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- net/ethtool/tsconfig.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/net/ethtool/tsconfig.c b/net/ethtool/tsconfig.c index 24b64862011f..6be3aa5d4bc1 100644 --- a/net/ethtool/tsconfig.c +++ b/net/ethtool/tsconfig.c @@ -359,8 +359,10 @@ static int ethnl_set_tsconfig(struct ethnl_req_info *req_base, if (ret < 0) goto err_free_hwprov; - /* Select only one tx type at a time */ - if (ffs(req_tx_type) != fls(req_tx_type)) { + /* Select exactly one tx type at a time */ + if (hweight32(req_tx_type) != 1) { + NL_SET_BAD_ATTR(info->extack, + tb[ETHTOOL_A_TSCONFIG_TX_TYPES]); ret = -EINVAL; goto err_free_hwprov; } @@ -380,8 +382,10 @@ static int ethnl_set_tsconfig(struct ethnl_req_info *req_base, if (ret < 0) goto err_free_hwprov; - /* Select only one rx filter at a time */ - if (ffs(req_rx_filter) != fls(req_rx_filter)) { + /* Select exactly one rx filter at a time */ + if (hweight32(req_rx_filter) != 1) { + NL_SET_BAD_ATTR(info->extack, + tb[ETHTOOL_A_TSCONFIG_RX_FILTERS]); ret = -EINVAL; goto err_free_hwprov; } From 5ba017f9efef3cf65cc60005aae4cbbf70b9b2b8 Mon Sep 17 00:00:00 2001 From: Sandeep Sondagar Date: Sun, 9 Aug 2026 21:31:41 +0530 Subject: [PATCH 1298/1433] net: phylink: treat PSGMII as an inband capable interface PSGMII (the Qualcomm 5-port SGMII) conveys the link negotiation result from the PHY back to the MAC through per-channel in-band SGMII words, exactly like SGMII and QSGMII. However, PHY_INTERFACE_MODE_PSGMII is missing from phylink_get_inband_type(), so phylink reports INBAND_NONE for it and phylink_pcs_neg_mode() falls back to PHYLINK_PCS_NEG_NONE. The PCS is then programmed in force mode and its control-register speed bits (which default to 1000base) are used, so a slower copper link - e.g. 100base-T - is reported as 1Gbps and cannot pass traffic. Classify PSGMII alongside SGMII and QSGMII as INBAND_CISCO_SGMII so the PCS negotiates in-band and the resolved link speed comes from the PHY in-band word. Also add PSGMII to the generic clause 22 PCS helper functions which handle the SGMII in-band word. Without this, a PCS using these helpers would still fall through to the default handling and force the link state to false in phylink_mii_c22_pcs_decode_state(), fail to encode the SGMII advertisement, and get rejected by phylink_get_link_timer_ns(). Signed-off-by: Sandeep Sondagar Reviewed-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260809-phylink-psgmii-v3-1-908dcd3a9e3d@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/phylink.c | 3 +++ include/linux/phylink.h | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index b241768edbcb..5b8e956902fb 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -1039,6 +1039,7 @@ static enum inband_type phylink_get_inband_type(phy_interface_t interface) { switch (interface) { case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_PSGMII: case PHY_INTERFACE_MODE_QSGMII: case PHY_INTERFACE_MODE_QUSGMII: case PHY_INTERFACE_MODE_USXGMII: @@ -4178,6 +4179,7 @@ void phylink_mii_c22_pcs_decode_state(struct phylink_link_state *state, break; case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_PSGMII: case PHY_INTERFACE_MODE_QSGMII: if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) phylink_decode_sgmii_word(state, lpa); @@ -4258,6 +4260,7 @@ int phylink_mii_c22_pcs_encode_advertisement(phy_interface_t interface, adv |= ADVERTISE_1000XPSE_ASYM; return adv; case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_PSGMII: case PHY_INTERFACE_MODE_QSGMII: return 0x0001; default: diff --git a/include/linux/phylink.h b/include/linux/phylink.h index 2bc0db3d52ac..1dda5c7ed5f1 100644 --- a/include/linux/phylink.h +++ b/include/linux/phylink.h @@ -791,6 +791,7 @@ static inline int phylink_get_link_timer_ns(phy_interface_t interface) { switch (interface) { case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_PSGMII: case PHY_INTERFACE_MODE_QSGMII: case PHY_INTERFACE_MODE_USXGMII: case PHY_INTERFACE_MODE_10G_QXGMII: From 07a9e3975039c099c71f9bc5ceab39c1cd949234 Mon Sep 17 00:00:00 2001 From: Minxi Hou Date: Tue, 11 Aug 2026 14:16:45 -0400 Subject: [PATCH 1299/1433] selftests/net/openvswitch: add SCTP flow key support and test The ovskey flow-string parser has no OVS_KEY_ATTR_SCTP entry, so a flow string containing sctp(src=.../dst=...) parses without error but silently drops the L4 key. The resulting flow carries only ipv4(proto=132), and the kernel rejects it: match_validate() in flow_netlink.c requires OVS_KEY_ATTR_SCTP when the IP protocol is IPPROTO_SCTP and returns -EINVAL for the missing key. Register OVS_KEY_ATTR_SCTP in the parse table and add a matching selftest that verifies SCTP flow key matching (sctp src/dst port). One listener serves the whole test. socat's fork option handles each association in a child, so the flow rules are the only thing that changes between the three phases and the listener is never restarted underneath them. -t 1 bounds how long a forked child lingers after its association closes, and the existing kill -TERM of the captured pid on teardown removes the listener itself. Also enable CONFIG_IP_SCTP in the selftest kernel config. The config checker strips underscores before comparing keys, so the entry sorts before CONFIG_IPV6 rather than after it. Signed-off-by: Minxi Hou Reviewed-by: Aaron Conole Link: https://patch.msgid.link/20260811181645.1918420-1-houminxi@gmail.com Signed-off-by: Jakub Kicinski --- .../testing/selftests/net/openvswitch/config | 1 + .../selftests/net/openvswitch/openvswitch.sh | 90 +++++++++++++++++++ .../selftests/net/openvswitch/ovs-dpctl.py | 5 ++ 3 files changed, 96 insertions(+) diff --git a/tools/testing/selftests/net/openvswitch/config b/tools/testing/selftests/net/openvswitch/config index 05ca6affb510..a825e0b5c88e 100644 --- a/tools/testing/selftests/net/openvswitch/config +++ b/tools/testing/selftests/net/openvswitch/config @@ -1,5 +1,6 @@ CONFIG_GENEVE=m CONFIG_INET_DIAG=y +CONFIG_IP_SCTP=y CONFIG_IPV6=y CONFIG_NETFILTER=y CONFIG_NET_IPGRE=m diff --git a/tools/testing/selftests/net/openvswitch/openvswitch.sh b/tools/testing/selftests/net/openvswitch/openvswitch.sh index f63001dc2510..a31f7fb6882d 100755 --- a/tools/testing/selftests/net/openvswitch/openvswitch.sh +++ b/tools/testing/selftests/net/openvswitch/openvswitch.sh @@ -33,6 +33,7 @@ tests=" action_set set: SET action rewrites fields trunc trunc: output truncation icmpv6 icmpv6: ICMPv6 echo type match + sctp_connect_v4 sctp: SCTP flow key matching psample psample: Sampling packets with psample" info() { @@ -610,6 +611,95 @@ test_icmpv6() { return 0 } +# Check for an SCTP endpoint via /proc, which works without sctp_diag. +sctp_eps_has() { + ip netns exec "$1" awk -v p="$2" '$6==p' /proc/net/sctp/eps | grep -q . +} + +# sctp_connect_v4 test +# - sctp(dst=4443) matches client-to-server INIT +# - sctp(src=4443) matches server-to-client INIT-ACK +# - remove flows and verify connection fails, reinstall and recover +test_sctp_connect_v4() { + local t="test_sctp_connect_v4" + local srv_ip=172.31.110.20 + + modprobe -q sctp 2>/dev/null || return "$ksft_skip" + socat -V 2>&1 | grep -q "define WITH_SCTP" || return "$ksft_skip" + + sbx_add "$t" || return $? + ovs_add_dp "$t" sctp4 || return 1 + + info "create namespaces" + for ns in client server; do + ovs_add_netns_and_veths "$t" "sctp4" "$ns" \ + "${ns:0:1}0" "${ns:0:1}1" || return 1 + done + + ip netns exec client ip addr add 172.31.110.10/24 dev c1 + ip netns exec client ip link set c1 up + ip netns exec server ip addr add "${srv_ip}/24" dev s1 + ip netns exec server ip link set s1 up + + # ARP forwarding + ovs_add_flow "$t" sctp4 \ + 'in_port(1),eth(),eth_type(0x0806),arp()' \ + '2' || return 1 + ovs_add_flow "$t" sctp4 \ + 'in_port(2),eth(),eth_type(0x0806),arp()' \ + '1' || return 1 + + # SCTP port matching: dst for request, src for reply + ovs_add_flow "$t" sctp4 \ + 'in_port(1),eth(),eth_type(0x0800),ipv4(proto=132),sctp(dst=4443)' \ + '2' || return 1 + ovs_add_flow "$t" sctp4 \ + 'in_port(2),eth(),eth_type(0x0800),ipv4(proto=132),sctp(src=4443)' \ + '1' || return 1 + + # The listener forks a child per association, so one instance serves + # the whole test and the flows stay the only variable. -t 1 bounds + # how long a child lingers after its association closes. + ovs_netns_spawn_daemon "$t" "server" \ + socat -u -t 1 SCTP4-LISTEN:4443,fork STDOUT + ovs_wait sctp_eps_has server 4443 || return 1 + + info "verify SCTP association with port-keyed flows" + ovs_sbx "$t" ip netns exec client \ + timeout 3 socat -u STDIN "SCTP4-CONNECT:${srv_ip}:4443" /dev/null 2>&1 \ + && { info "connection should fail without flows" + return 1; } + + info "reinstall flows and verify recovery" + ovs_add_flow "$t" sctp4 \ + 'in_port(1),eth(),eth_type(0x0800),ipv4(proto=132),sctp(dst=4443)' \ + '2' || return 1 + ovs_add_flow "$t" sctp4 \ + 'in_port(2),eth(),eth_type(0x0800),ipv4(proto=132),sctp(src=4443)' \ + '1' || return 1 + + ovs_sbx "$t" ip netns exec client \ + timeout 3 socat -u STDIN "SCTP4-CONNECT:${srv_ip}:4443" Date: Wed, 12 Aug 2026 14:20:06 +0200 Subject: [PATCH 1300/1433] net: openvswitch: unexport ovs_vport_alloc/free Since removal of the legacy tunnel port types, there are no more users for these functions outside the main openvswitch module. Functions to register vport_ops are also not exported. Allocating vports without operations doesn't make a lot of sense. Highlighted by Sashiko as a follow up to the removal of the module infrastructure. Signed-off-by: Ilya Maximets Reviewed-by: Aaron Conole Link: https://patch.msgid.link/20260812122007.457136-1-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- net/openvswitch/vport.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/net/openvswitch/vport.c b/net/openvswitch/vport.c index 29ebeb164bdc..083f7a8d9d4e 100644 --- a/net/openvswitch/vport.c +++ b/net/openvswitch/vport.c @@ -157,7 +157,6 @@ struct vport *ovs_vport_alloc(int priv_size, const struct vport_ops *ops, kfree(vport); return ERR_PTR(err); } -EXPORT_SYMBOL_GPL(ovs_vport_alloc); /** * ovs_vport_free - uninitialize and free vport @@ -178,7 +177,6 @@ void ovs_vport_free(struct vport *vport) free_percpu(vport->upcall_stats); kfree(vport); } -EXPORT_SYMBOL_GPL(ovs_vport_free); static struct vport_ops *ovs_vport_lookup(const struct vport_parms *parms) { From 4f93b12cf7b25fbf8e73d222722805b049f0a6d3 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Mon, 10 Aug 2026 16:44:35 +0800 Subject: [PATCH 1301/1433] net: usb: lg-vl600: fix Ethernet header on fragmented RX packets The LG VL600 RX path can assemble one device frame from multiple USB RX URBs. In the single-URB case, the input skb passed by usbnet is also the buffer being parsed, so @skb and @buf point to the same skb. When a frame is completed from current_rx_buf, however, @buf points to the assembled skb while @skb still points to the last URB fragment. vl600_rx_fixup() returns @buf to the network stack in that path, but it currently obtains the Ethernet header from @skb. As a result, the source/destination address fixups and the IPv6 ethertype fixup can be applied to the final fragment instead of the assembled skb that is actually delivered. Use @buf for the Ethernet header so the fixups are applied to the packet being parsed and returned. This has likely gone unnoticed because the common single-URB path has @skb == @buf and therefore behaves correctly. Cc: stable+noautosel@kernel.org # untested fix to unlikely driver error path Signed-off-by: Xu Rao Reviewed-by: Simon Horman Link: https://patch.msgid.link/30CC616506DE5BC4+20260810084435.2099229-1-raoxu@uniontech.com Signed-off-by: Jakub Kicinski --- drivers/net/usb/lg-vl600.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/usb/lg-vl600.c b/drivers/net/usb/lg-vl600.c index c4ad2d9f6f4f..d6b2e3e3d950 100644 --- a/drivers/net/usb/lg-vl600.c +++ b/drivers/net/usb/lg-vl600.c @@ -172,7 +172,7 @@ static int vl600_rx_fixup(struct usbnet *dev, struct sk_buff *skb) * the h_proto field is in the same place so we just leave it * alone and fill in the remaining fields. */ - ethhdr = (struct ethhdr *) skb->data; + ethhdr = (struct ethhdr *)buf->data; if (be16_to_cpup(ðhdr->h_proto) == ETH_P_ARP && buf->len > 0x26) { /* Copy the addresses from packet contents */ From 24ef02f934eeb48830cff6b739abc3c62b1d107b Mon Sep 17 00:00:00 2001 From: Jijie Shao Date: Fri, 7 Aug 2026 19:48:30 +0800 Subject: [PATCH 1302/1433] net: page_pool: fix UAF in __page_pool_release_netmem_dma on xa_cmpxchg race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This bug was discovered while testing the hns3 driver under channel reconfiguration (`ethtool -L` / `ethtool -G`) with iperf3 traffic on arm64. The race is intermittently triggered when page_pool_destroy() runs page_pool_scrub() concurrently with page return via page_pool_put_netmem() on a different CPU. A WARN in page_pool_clear_pp_info() surfaced the dangling DMA index bits left by the cmpxchg loser, which led to the investigation. page_pool_scrub() iterates pool->dma_mapped via xa_for_each() with no page ref held. __page_pool_release_netmem_dma() currently reads and writes netmem fields (dma_addr, DMA index bits in pp_magic) after xa_cmpxchg() returns. The unref path calls put_page() unconditionally regardless of the cmpxchg outcome; when it loses the cmpxchg, it still frees the page before the scrub winner finishes these netmem accesses, so scrub touches a freed page -- a Use-After-Free. Fix this by splitting the DMA release into two functions: 1. __page_pool_unmap_netmem_dma() caches dma_addr before xa_cmpxchg(), does the cmpxchg to remove the DMA mapping, and calls dma_unmap on the cached address. It never touches netmem fields after the cmpxchg, making it safe for the scrub path which holds no page ref. 2. __page_pool_release_netmem_dma() wraps the above and additionally clears dma_addr and DMA index bits in netmem fields. This is safe only when the caller holds a page ref, so it is used by the return path (page_pool_return_netmem). The scrub path calls __page_pool_unmap_netmem_dma() directly; the return path calls __page_pool_release_netmem_dma(). Fixes: ee62ce7a1d90 ("page_pool: Track DMA-mapped pages and unmap them when destroying the pool") Suggested-by: Mina Almasry Reviewed-by: Mina Almasry Signed-off-by: Jijie Shao Reviewed-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260807114830.344336-1-shaojijie@huawei.com Signed-off-by: Jakub Kicinski --- net/core/page_pool.c | 64 +++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/net/core/page_pool.c b/net/core/page_pool.c index 21dc4a9c8714..50ee550fef73 100644 --- a/net/core/page_pool.c +++ b/net/core/page_pool.c @@ -500,29 +500,40 @@ static int page_pool_register_dma_index(struct page_pool *pool, return err; } -static int page_pool_release_dma_index(struct page_pool *pool, - netmem_ref netmem) +static void __page_pool_unmap_netmem_dma(struct page_pool *pool, + netmem_ref netmem) { struct page *old, *page = netmem_to_page(netmem); unsigned long id; + dma_addr_t dma; - if (unlikely(!PP_DMA_INDEX_BITS)) - return 0; + if (!pool->dma_map) + return; - id = netmem_get_dma_index(netmem); - if (!id) - return -1; + /* Cache dma_addr before xa_cmpxchg. The scrub path holds no page ref; + * the unref path calls put_page() regardless of cmpxchg outcome, so + * after the cmpxchg we cannot safely touch netmem fields. + */ + dma = page_pool_get_dma_addr_netmem(netmem); - if (in_softirq()) - old = xa_cmpxchg(&pool->dma_mapped, id, page, NULL, 0); - else - old = xa_cmpxchg_bh(&pool->dma_mapped, id, page, NULL, 0); - if (old != page) - return -1; + if (likely(PP_DMA_INDEX_BITS)) { + id = netmem_get_dma_index(netmem); + if (!id) + return; - netmem_set_dma_index(netmem, 0); + if (in_softirq()) + old = xa_cmpxchg(&pool->dma_mapped, + id, page, NULL, 0); + else + old = xa_cmpxchg_bh(&pool->dma_mapped, + id, page, NULL, 0); + if (old != page) + return; + } - return 0; + dma_unmap_page_attrs(pool->p.dev, dma, + PAGE_SIZE << pool->p.order, pool->p.dma_dir, + DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_WEAK_ORDERING); } static bool page_pool_dma_map(struct page_pool *pool, netmem_ref netmem, gfp_t gfp) @@ -728,24 +739,16 @@ void page_pool_clear_pp_info(netmem_ref netmem) static __always_inline void __page_pool_release_netmem_dma(struct page_pool *pool, netmem_ref netmem) { - dma_addr_t dma; - + /* Caller must hold a page ref: __page_pool_unmap_netmem_dma() is + * safe without a ref, but the field clears below require it. + */ if (!pool->dma_map) - /* Always account for inflight pages, even if we didn't - * map them - */ return; - if (page_pool_release_dma_index(pool, netmem)) - return; - - dma = page_pool_get_dma_addr_netmem(netmem); - - /* When page is unmapped, it cannot be returned to our pool */ - dma_unmap_page_attrs(pool->p.dev, dma, - PAGE_SIZE << pool->p.order, pool->p.dma_dir, - DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_WEAK_ORDERING); + __page_pool_unmap_netmem_dma(pool, netmem); page_pool_set_dma_addr_netmem(netmem, 0); + if (likely(PP_DMA_INDEX_BITS)) + netmem_set_dma_index(netmem, 0); } /* Disconnects a page (from a page_pool). API users can have a need @@ -1171,8 +1174,9 @@ static void page_pool_scrub(struct page_pool *pool) synchronize_net(); } + /* No page ref, dma-unmap only. */ xa_for_each(&pool->dma_mapped, id, ptr) - __page_pool_release_netmem_dma(pool, page_to_netmem((struct page *)ptr)); + __page_pool_unmap_netmem_dma(pool, page_to_netmem((struct page *)ptr)); } /* No more consumers should exist, but producers could still From 486e5419b7ec357da8f287efef7ccc1ebc1421e9 Mon Sep 17 00:00:00 2001 From: Shay Drory Date: Mon, 10 Aug 2026 12:30:37 +0300 Subject: [PATCH 1303/1433] net/mlx5: SD, prefer sd_group_size from vport context Newer FW reports the SD group size directly in the NIC vport context via the sd_group_size field, gated by the sd_group_size capability. Switch sd_init() to source the group size from there and fall back to the MPIR-based host_buses query only when the cap is absent. sd_group_size might return 1 in some FW configuration. Add explicit check to disable SD creation in this case. While here, rename host_buses to group_size throughout sd.c to follow the new name on capable FW. Signed-off-by: Shay Drory Reviewed-by: Moshe Shemesh Signed-off-by: Tariq Toukan Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260810093037.3138197-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/mellanox/mlx5/core/lib/sd.c | 75 ++++++++++--------- .../net/ethernet/mellanox/mlx5/core/lib/sd.h | 1 + .../net/ethernet/mellanox/mlx5/core/vport.c | 6 +- include/linux/mlx5/vport.h | 3 +- 4 files changed, 47 insertions(+), 38 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/sd.c b/drivers/net/ethernet/mellanox/mlx5/core/lib/sd.c index ee2fdefa1945..4cdc50cd6f03 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lib/sd.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/sd.c @@ -19,7 +19,7 @@ struct mlx5_sd { u32 group_id; - u8 host_buses; + u8 group_size; struct mlx5_devcom_comp_dev *devcom; struct dentry *dfs; u8 state; @@ -46,14 +46,14 @@ enum mlx5_sd_state { MLX5_SD_STATE_UP, }; -static int mlx5_sd_get_host_buses(struct mlx5_core_dev *dev) +static int mlx5_sd_get_group_size(struct mlx5_core_dev *dev) { struct mlx5_sd *sd = mlx5_get_sd(dev); if (!sd) return 1; - return sd->host_buses; + return sd->group_size; } struct mlx5_core_dev *mlx5_sd_get_primary(struct mlx5_core_dev *dev) @@ -107,7 +107,7 @@ int mlx5_sd_pf_num_get(struct mlx5_core_dev *dev) if (pos == dev) break; - return pf_num * sd->host_buses + i; + return pf_num * sd->group_size + i; } struct mlx5_core_dev * @@ -118,7 +118,7 @@ mlx5_sd_primary_get_peer(struct mlx5_core_dev *primary, int idx) if (idx == 0) return primary; - if (idx >= mlx5_sd_get_host_buses(primary)) + if (idx >= mlx5_sd_get_group_size(primary)) return NULL; sd = mlx5_get_sd(primary); @@ -130,7 +130,7 @@ int mlx5_sd_ch_ix_get_dev_ix(struct mlx5_core_dev *dev, int ch_ix) if (is_mdev_switchdev_mode(dev)) return 0; - return ch_ix % mlx5_sd_get_host_buses(dev); + return ch_ix % mlx5_sd_get_group_size(dev); } int mlx5_sd_ch_ix_get_vec_ix(struct mlx5_core_dev *dev, int ch_ix) @@ -138,7 +138,7 @@ int mlx5_sd_ch_ix_get_vec_ix(struct mlx5_core_dev *dev, int ch_ix) if (is_mdev_switchdev_mode(dev)) return ch_ix; - return ch_ix / mlx5_sd_get_host_buses(dev); + return ch_ix / mlx5_sd_get_group_size(dev); } struct mlx5_core_dev *mlx5_sd_ch_ix_get_dev(struct mlx5_core_dev *primary, int ch_ix) @@ -164,7 +164,7 @@ static bool ft_create_alias_supported(struct mlx5_core_dev *dev) } static int mlx5_query_sd(struct mlx5_core_dev *dev, bool *sdm, - u8 *host_buses) + u8 *group_size) { u32 out[MLX5_ST_SZ_DW(mpir_reg)]; int err; @@ -174,7 +174,7 @@ static int mlx5_query_sd(struct mlx5_core_dev *dev, bool *sdm, return err; *sdm = MLX5_GET(mpir_reg, out, sdm); - *host_buses = MLX5_GET(mpir_reg, out, host_buses); + *group_size = MLX5_GET(mpir_reg, out, host_buses); return 0; } @@ -184,10 +184,10 @@ static u32 mlx5_sd_group_id(struct mlx5_core_dev *dev, u8 sd_group) return (u32)((MLX5_CAP_GEN(dev, native_port_num) << 8) | sd_group); } -static bool mlx5_sd_caps_supported(struct mlx5_core_dev *dev, u8 host_buses) +static bool mlx5_sd_caps_supported(struct mlx5_core_dev *dev, u8 group_size) { /* Honor the SW implementation limit */ - if (host_buses > MLX5_SD_MAX_GROUP_SZ) + if (group_size > MLX5_SD_MAX_GROUP_SZ) return false; /* Disconnect secondaries from the network */ @@ -200,7 +200,7 @@ static bool mlx5_sd_caps_supported(struct mlx5_core_dev *dev, u8 host_buses) /* RX steering from primary to secondaries */ if (!MLX5_CAP_GEN(dev, cross_vhca_rqt)) return false; - if (host_buses > MLX5_CAP_GEN_2(dev, max_rqt_vhca_id)) + if (group_size > MLX5_CAP_GEN_2(dev, max_rqt_vhca_id)) return false; /* TX steering from secondaries to primary */ @@ -214,7 +214,7 @@ static bool mlx5_sd_caps_supported(struct mlx5_core_dev *dev, u8 host_buses) bool mlx5_sd_is_supported(struct mlx5_core_dev *dev) { - u8 host_buses, sd_group; + u8 group_size = U8_MAX, sd_group; bool sdm; int err; @@ -222,23 +222,25 @@ bool mlx5_sd_is_supported(struct mlx5_core_dev *dev) if (!mlx5_core_is_pf(dev)) return false; - err = mlx5_query_nic_vport_sd_group(dev, &sd_group); - if (err || !sd_group) + err = mlx5_query_nic_vport_sd_group(dev, &sd_group, &group_size); + if (err || !sd_group || group_size < MLX5_SD_MIN_GROUP_SZ) return false; - if (!MLX5_CAP_MCAM_REG(dev, mpir)) - return false; + if (group_size == U8_MAX) { + if (!MLX5_CAP_MCAM_REG(dev, mpir)) + return false; - err = mlx5_query_sd(dev, &sdm, &host_buses); - if (err || !sdm) - return false; + err = mlx5_query_sd(dev, &sdm, &group_size); + if (err || !sdm) + return false; + } - return mlx5_sd_caps_supported(dev, host_buses); + return mlx5_sd_caps_supported(dev, group_size); } static int sd_init(struct mlx5_core_dev *dev) { - u8 host_buses, sd_group; + u8 group_size = U8_MAX, sd_group; struct mlx5_sd *sd; u32 group_id; bool sdm; @@ -248,26 +250,27 @@ static int sd_init(struct mlx5_core_dev *dev) if (!mlx5_core_is_pf(dev)) return 0; - err = mlx5_query_nic_vport_sd_group(dev, &sd_group); + err = mlx5_query_nic_vport_sd_group(dev, &sd_group, &group_size); if (err) return err; - if (!sd_group) + if (!sd_group || group_size < MLX5_SD_MIN_GROUP_SZ) return 0; - if (!MLX5_CAP_MCAM_REG(dev, mpir)) - return 0; + if (group_size == U8_MAX) { + if (!MLX5_CAP_MCAM_REG(dev, mpir)) + return 0; - err = mlx5_query_sd(dev, &sdm, &host_buses); - if (err) - return err; - - if (!sdm) - return 0; + err = mlx5_query_sd(dev, &sdm, &group_size); + if (err) + return err; + if (!sdm) + return 0; + } group_id = mlx5_sd_group_id(dev, sd_group); - if (!mlx5_sd_caps_supported(dev, host_buses)) { + if (!mlx5_sd_caps_supported(dev, group_size)) { sd_warn(dev, "can't support requested netdev combining for group id 0x%x, skipping\n", group_id); return 0; @@ -277,7 +280,7 @@ static int sd_init(struct mlx5_core_dev *dev) if (!sd) return -ENOMEM; - sd->host_buses = host_buses; + sd->group_size = group_size; sd->group_id = group_id; mlx5_set_sd(dev, sd); @@ -540,7 +543,7 @@ static int sd_register(struct mlx5_core_dev *dev) sd->devcom = devcom; mlx5_devcom_comp_lock(devcom); - if (mlx5_devcom_comp_get_size(devcom) != sd->host_buses || + if (mlx5_devcom_comp_get_size(devcom) != sd->group_size || mlx5_devcom_comp_is_ready(devcom)) goto out; @@ -576,7 +579,7 @@ static int sd_register(struct mlx5_core_dev *dev) DEVCOM_CANT_FAIL, primary); primary_sd = mlx5_get_sd(primary); - if (primary_sd->next_secondary_idx + 1 == sd->host_buses) + if (primary_sd->next_secondary_idx + 1 == sd->group_size) mlx5_devcom_comp_set_ready(devcom, true); out: mlx5_devcom_comp_unlock(devcom); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/sd.h b/drivers/net/ethernet/mellanox/mlx5/core/lib/sd.h index cb88bf34079a..bc8dbc299070 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lib/sd.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/sd.h @@ -6,6 +6,7 @@ #include +#define MLX5_SD_MIN_GROUP_SZ 2 #define MLX5_SD_MAX_GROUP_SZ 2 struct mlx5_sd; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/vport.c b/drivers/net/ethernet/mellanox/mlx5/core/vport.c index 3676e26ac6b0..3d86510af615 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/vport.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/vport.c @@ -550,7 +550,8 @@ int mlx5_query_nic_vport_system_image_guid(struct mlx5_core_dev *mdev, } EXPORT_SYMBOL_GPL(mlx5_query_nic_vport_system_image_guid); -int mlx5_query_nic_vport_sd_group(struct mlx5_core_dev *mdev, u8 *sd_group) +int mlx5_query_nic_vport_sd_group(struct mlx5_core_dev *mdev, u8 *sd_group, + u8 *sd_group_size) { int outlen = MLX5_ST_SZ_BYTES(query_nic_vport_context_out); u32 *out; @@ -566,6 +567,9 @@ int mlx5_query_nic_vport_sd_group(struct mlx5_core_dev *mdev, u8 *sd_group) *sd_group = MLX5_GET(query_nic_vport_context_out, out, nic_vport_context.sd_group); + if (MLX5_CAP_GEN(mdev, sd_group_size)) + *sd_group_size = MLX5_GET(query_nic_vport_context_out, out, + nic_vport_context.sd_group_size); out: kvfree(out); return err; diff --git a/include/linux/mlx5/vport.h b/include/linux/mlx5/vport.h index ee34d3ed335f..577168a4ca0c 100644 --- a/include/linux/mlx5/vport.h +++ b/include/linux/mlx5/vport.h @@ -78,7 +78,8 @@ int mlx5_query_nic_vport_mtu(struct mlx5_core_dev *mdev, u16 *mtu); int mlx5_modify_nic_vport_mtu(struct mlx5_core_dev *mdev, u16 mtu); int mlx5_query_nic_vport_system_image_guid(struct mlx5_core_dev *mdev, u64 *system_image_guid); -int mlx5_query_nic_vport_sd_group(struct mlx5_core_dev *mdev, u8 *sd_group); +int mlx5_query_nic_vport_sd_group(struct mlx5_core_dev *mdev, u8 *sd_group, + u8 *sd_group_size); int mlx5_query_nic_vport_node_guid(struct mlx5_core_dev *mdev, u16 vport, bool other_vport, u64 *node_guid); int mlx5_modify_nic_vport_node_guid(struct mlx5_core_dev *mdev, From 9958e69b98930834a576e156f6458166d1db1c02 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 12 Aug 2026 14:22:57 +0000 Subject: [PATCH 1304/1433] gre: fix ERSPAN o_flags race/corruption in xmit and fill_info For IPv4 ERSPAN: In erspan_xmit(), the driver clears IP_TUNNEL_SEQ_BIT (for version 0) and IP_TUNNEL_KEY_BIT directly in the shared tunnel->parms.o_flags structure. Since transmit paths can run locklessly and concurrently, this leads to a data race. Furthermore, modifying tunnel->parms.o_flags permanently alters the tunnel configuration. To work around this, erspan_fill_info() (which reports config to userspace) was setting IP_TUNNEL_KEY_BIT back. If erspan_fill_info (running under RTNL) and erspan_xmit (running locklessly) race, erspan_xmit might see IP_TUNNEL_KEY_BIT set when it shouldn't, leading to GRE header corruption (injecting a key field into the ERSPAN GRE header). Fix this by: 1) Passing flags as an argument to __gre_xmit(). 2) Using local stack flags in ipgre_xmit(), gre_tap_xmit(), and erspan_xmit() to prevent TOCTOU data races with concurrent configuration updates, and passing them to __gre_xmit(). 3) Removing the racy modification of t->parms.o_flags in erspan_fill_info(). 4) Forcing IP_TUNNEL_KEY_BIT in the reported flags for ERSPAN locally in ipgre_fill_info(). For IPv6 ERSPAN: ip6erspan_tunnel_xmit() was locklessly clearing IP_TUNNEL_KEY_BIT in t->parms.o_flags even though it does not use these flags for building the GRE header (it uses local flags). This permanently corrupts the configuration and races with ip6gre_fill_info() which reads it. Remove the redundant and racy modification. This should remove false sharing in a fast path. Add const qualifiers in ipgre_fill_info(), erspan_fill_info() and ip6gre_fill_info() to clarify that these methods are not supposed to write any live parameters. Signed-off-by: Eric Dumazet Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260812142257.21283-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/ipv4/ip_gre.c | 42 +++++++++++++++++++++++------------------- net/ipv6/ip6_gre.c | 5 ++--- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c index 6a92607401e8..82309efd417e 100644 --- a/net/ipv4/ip_gre.c +++ b/net/ipv4/ip_gre.c @@ -475,12 +475,9 @@ static int gre_rcv(struct sk_buff *skb) static void __gre_xmit(struct sk_buff *skb, struct net_device *dev, const struct iphdr *tnl_params, - __be16 proto) + __be16 proto, const unsigned long *flags) { struct ip_tunnel *tunnel = netdev_priv(dev); - IP_TUNNEL_DECLARE_FLAGS(flags); - - ip_tunnel_flags_copy(flags, tunnel->parms.o_flags); /* Push GRE header. */ gre_build_header(skb, tunnel->tun_hlen, @@ -653,6 +650,7 @@ static netdev_tx_t ipgre_xmit(struct sk_buff *skb, struct net_device *dev) { struct ip_tunnel *tunnel = netdev_priv(dev); + IP_TUNNEL_DECLARE_FLAGS(flags); const struct iphdr *tnl_params; if (!pskb_inet_may_pull(skb)) @@ -688,11 +686,12 @@ static netdev_tx_t ipgre_xmit(struct sk_buff *skb, tnl_params = &tunnel->parms.iph; } - if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT, - tunnel->parms.o_flags))) + ip_tunnel_flags_copy(flags, tunnel->parms.o_flags); + + if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT, flags))) goto free_skb; - __gre_xmit(skb, dev, tnl_params, skb->protocol); + __gre_xmit(skb, dev, tnl_params, skb->protocol, flags); return NETDEV_TX_OK; free_skb: @@ -705,6 +704,7 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb, struct net_device *dev) { struct ip_tunnel *tunnel = netdev_priv(dev); + IP_TUNNEL_DECLARE_FLAGS(flags); bool truncate = false; __be16 proto; @@ -728,10 +728,12 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb, truncate = true; } + ip_tunnel_flags_copy(flags, tunnel->parms.o_flags); + /* Push ERSPAN header */ if (tunnel->erspan_ver == 0) { proto = htons(ETH_P_ERSPAN); - __clear_bit(IP_TUNNEL_SEQ_BIT, tunnel->parms.o_flags); + __clear_bit(IP_TUNNEL_SEQ_BIT, flags); } else if (tunnel->erspan_ver == 1) { erspan_build_header(skb, ntohl(tunnel->parms.o_key), tunnel->index, @@ -746,8 +748,8 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb, goto free_skb; } - __clear_bit(IP_TUNNEL_KEY_BIT, tunnel->parms.o_flags); - __gre_xmit(skb, dev, &tunnel->parms.iph, proto); + __clear_bit(IP_TUNNEL_KEY_BIT, flags); + __gre_xmit(skb, dev, &tunnel->parms.iph, proto, flags); return NETDEV_TX_OK; free_skb: @@ -760,6 +762,7 @@ static netdev_tx_t gre_tap_xmit(struct sk_buff *skb, struct net_device *dev) { struct ip_tunnel *tunnel = netdev_priv(dev); + IP_TUNNEL_DECLARE_FLAGS(flags); if (!pskb_inet_may_pull(skb)) goto free_skb; @@ -769,14 +772,15 @@ static netdev_tx_t gre_tap_xmit(struct sk_buff *skb, return NETDEV_TX_OK; } - if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT, - tunnel->parms.o_flags))) + ip_tunnel_flags_copy(flags, tunnel->parms.o_flags); + + if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT, flags))) goto free_skb; if (skb_cow_head(skb, dev->needed_headroom)) goto free_skb; - __gre_xmit(skb, dev, &tunnel->parms.iph, htons(ETH_P_TEB)); + __gre_xmit(skb, dev, &tunnel->parms.iph, htons(ETH_P_TEB), flags); return NETDEV_TX_OK; free_skb: @@ -1560,12 +1564,15 @@ static size_t ipgre_get_size(const struct net_device *dev) static int ipgre_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct ip_tunnel *t = netdev_priv(dev); - struct ip_tunnel_parm_kern *p = &t->parms; + const struct ip_tunnel *t = netdev_priv(dev); + const struct ip_tunnel_parm_kern *p = &t->parms; IP_TUNNEL_DECLARE_FLAGS(o_flags); ip_tunnel_flags_copy(o_flags, p->o_flags); + if (t->erspan_ver != 0 && !t->collect_md) + __set_bit(IP_TUNNEL_KEY_BIT, o_flags); + if (nla_put_u32(skb, IFLA_GRE_LINK, p->link) || nla_put_be16(skb, IFLA_GRE_IFLAGS, gre_tnl_flags_to_gre_flags(p->i_flags)) || @@ -1608,12 +1615,9 @@ static int ipgre_fill_info(struct sk_buff *skb, const struct net_device *dev) static int erspan_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct ip_tunnel *t = netdev_priv(dev); + const struct ip_tunnel *t = netdev_priv(dev); if (t->erspan_ver <= 2) { - if (t->erspan_ver != 0 && !t->collect_md) - __set_bit(IP_TUNNEL_KEY_BIT, t->parms.o_flags); - if (nla_put_u8(skb, IFLA_GRE_ERSPAN_VER, t->erspan_ver)) goto nla_put_failure; diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index b843116e9b70..0b3f386b51a2 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -964,7 +964,6 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb, if (skb_cow_head(skb, dev->needed_headroom ?: t->hlen)) goto tx_err; - __clear_bit(IP_TUNNEL_KEY_BIT, t->parms.o_flags); IPCB(skb)->flags = 0; /* For collect_md mode, derive fl6 from the tunnel key, @@ -2115,8 +2114,8 @@ static size_t ip6gre_get_size(const struct net_device *dev) static int ip6gre_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct ip6_tnl *t = netdev_priv(dev); - struct __ip6_tnl_parm *p = &t->parms; + const struct ip6_tnl *t = netdev_priv(dev); + const struct __ip6_tnl_parm *p = &t->parms; IP_TUNNEL_DECLARE_FLAGS(o_flags); ip_tunnel_flags_copy(o_flags, p->o_flags); From e6a5d573d24cd375e09d24f136523cb3cc85c9d3 Mon Sep 17 00:00:00 2001 From: Kai Kuang Date: Wed, 12 Aug 2026 14:06:44 +0800 Subject: [PATCH 1305/1433] net: dsa: drop explicit NULL comparisons Replace explicit NULL comparisons with the boolean form to follow the kernel coding style: dev->class != NULL -> dev->class user_dev == NULL -> !user_dev No functional changes intended. Signed-off-by: Kai Kuang Reviewed-by: Andrew Lunn Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260812060644.210997-1-kuangkai@kylinos.cn Signed-off-by: Jakub Kicinski --- net/dsa/dsa.c | 2 +- net/dsa/user.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c index da53a666d4b8..2771c34e8400 100644 --- a/net/dsa/dsa.c +++ b/net/dsa/dsa.c @@ -1383,7 +1383,7 @@ static int dsa_switch_parse_of(struct dsa_switch *ds, struct device_node *dn) static int dev_is_class(struct device *dev, const void *class) { - if (dev->class != NULL && !strcmp(dev->class->name, class)) + if (dev->class && !strcmp(dev->class->name, class)) return 1; return 0; diff --git a/net/dsa/user.c b/net/dsa/user.c index 4065c6ee6fc6..041f9060c8ef 100644 --- a/net/dsa/user.c +++ b/net/dsa/user.c @@ -2779,7 +2779,7 @@ int dsa_user_create(struct dsa_port *port) user_dev = alloc_netdev_mqs(sizeof(struct dsa_user_priv), name, assign_type, ether_setup, ds->num_tx_queues, 1); - if (user_dev == NULL) + if (!user_dev) return -ENOMEM; user_dev->rtnl_link_ops = &dsa_link_ops; From ad27ed7d2309419a129078d781504f486b1b469a Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Wed, 12 Aug 2026 14:36:11 +0800 Subject: [PATCH 1306/1433] bpf, xdp: move offload check into dev_xdp_install() bpf_xdp_link_update() calls dev_xdp_install() directly and skips dev_xdp_attach(), so the checks in dev_xdp_attach() do not run. A user can make an XDP link with a normal program and then swap in an offloaded or device-bound program with BPF_LINK_UPDATE, which puts it on the software path. dev_xdp_install() is the one place all three paths go through: "ip link set xdp" and BPF_LINK_CREATE reach it via dev_xdp_attach(), and BPF_LINK_UPDATE calls it directly. So move the program checks (offloaded, bound to another device, device-bound in generic mode, native vs generic, DEVMAP and CPUMAP) there, and keep only the netlink-flag check (XDP_FLAGS_UPDATE_IF_NOEXIST) in dev_xdp_attach(). Fixes: 026a4c28e1db3 ("bpf, xdp: Implement LINK_UPDATE for BPF XDP link") Signed-off-by: Jiayuan Chen Signed-off-by: Jakub Kicinski --- net/core/dev.c | 59 ++++++++++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/net/core/dev.c b/net/core/dev.c index ece6700536d9..5ac31370df93 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -10333,6 +10333,37 @@ static int dev_xdp_install(struct net_device *dev, enum bpf_xdp_mode mode, netdev_assert_locked_ops_compat(dev); + if (prog) { + enum bpf_xdp_mode other_mode = mode == XDP_MODE_SKB + ? XDP_MODE_DRV : XDP_MODE_SKB; + bool offload = mode == XDP_MODE_HW; + + if (!offload && dev_xdp_prog(dev, other_mode)) { + NL_SET_ERR_MSG(extack, "Native and generic XDP can't be active at the same time"); + return -EEXIST; + } + if (!offload && bpf_prog_is_offloaded(prog->aux)) { + NL_SET_ERR_MSG(extack, "Using offloaded program without HW_MODE flag is not supported"); + return -EINVAL; + } + if (bpf_prog_is_dev_bound(prog->aux) && !bpf_offload_dev_match(prog, dev)) { + NL_SET_ERR_MSG(extack, "Program bound to different device"); + return -EINVAL; + } + if (bpf_prog_is_dev_bound(prog->aux) && mode == XDP_MODE_SKB) { + NL_SET_ERR_MSG(extack, "Can't attach device-bound programs in generic mode"); + return -EINVAL; + } + if (prog->expected_attach_type == BPF_XDP_DEVMAP) { + NL_SET_ERR_MSG(extack, "BPF_XDP_DEVMAP programs can not be attached to a device"); + return -EINVAL; + } + if (prog->expected_attach_type == BPF_XDP_CPUMAP) { + NL_SET_ERR_MSG(extack, "BPF_XDP_CPUMAP programs can not be attached to a device"); + return -EINVAL; + } + } + if (dev->cfg->hds_config == ETHTOOL_TCP_DATA_SPLIT_ENABLED && prog && !prog->aux->xdp_has_frags) { NL_SET_ERR_MSG(extack, "unable to install XDP to device using tcp-data-split"); @@ -10472,38 +10503,10 @@ static int dev_xdp_attach(struct net_device *dev, struct netlink_ext_ack *extack new_prog = link->link.prog; if (new_prog) { - bool offload = mode == XDP_MODE_HW; - enum bpf_xdp_mode other_mode = mode == XDP_MODE_SKB - ? XDP_MODE_DRV : XDP_MODE_SKB; - if ((flags & XDP_FLAGS_UPDATE_IF_NOEXIST) && cur_prog) { NL_SET_ERR_MSG(extack, "XDP program already attached"); return -EBUSY; } - if (!offload && dev_xdp_prog(dev, other_mode)) { - NL_SET_ERR_MSG(extack, "Native and generic XDP can't be active at the same time"); - return -EEXIST; - } - if (!offload && bpf_prog_is_offloaded(new_prog->aux)) { - NL_SET_ERR_MSG(extack, "Using offloaded program without HW_MODE flag is not supported"); - return -EINVAL; - } - if (bpf_prog_is_dev_bound(new_prog->aux) && !bpf_offload_dev_match(new_prog, dev)) { - NL_SET_ERR_MSG(extack, "Program bound to different device"); - return -EINVAL; - } - if (bpf_prog_is_dev_bound(new_prog->aux) && mode == XDP_MODE_SKB) { - NL_SET_ERR_MSG(extack, "Can't attach device-bound programs in generic mode"); - return -EINVAL; - } - if (new_prog->expected_attach_type == BPF_XDP_DEVMAP) { - NL_SET_ERR_MSG(extack, "BPF_XDP_DEVMAP programs can not be attached to a device"); - return -EINVAL; - } - if (new_prog->expected_attach_type == BPF_XDP_CPUMAP) { - NL_SET_ERR_MSG(extack, "BPF_XDP_CPUMAP programs can not be attached to a device"); - return -EINVAL; - } } /* don't call drivers if the effective program didn't change */ From c7d2577bf1358cd8741c91e17be6eed69f47084a Mon Sep 17 00:00:00 2001 From: Sean Nyekjaer Date: Wed, 5 Aug 2026 13:07:07 +0200 Subject: [PATCH 1307/1433] can: tcan4x5x: put tcan into sleep when removing driver Put the tcan4x5x transceiver into sleep mode when the driver is removed, instead of leaving it in its current operating mode. This reduces power consumption(3mA@12V) once the driver is no longer bound to the device. Signed-off-by: Sean Nyekjaer Link: https://patch.msgid.link/20260805110708.3220251-1-sean@geanix.com [mkl: tcan4x5x_power_enable(): reduce scope of ret] Signed-off-by: Marc Kleine-Budde --- drivers/net/can/m_can/tcan4x5x-core.c | 31 +++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/drivers/net/can/m_can/tcan4x5x-core.c b/drivers/net/can/m_can/tcan4x5x-core.c index 31cc9d0abd45..a5b8829aa519 100644 --- a/drivers/net/can/m_can/tcan4x5x-core.c +++ b/drivers/net/can/m_can/tcan4x5x-core.c @@ -211,8 +211,31 @@ static int tcan4x5x_write_fifo(struct m_can_classdev *cdev, return regmap_bulk_write(priv->regmap, TCAN4X5X_MRAM_START + addr_offset, val, val_count); } -static int tcan4x5x_power_enable(struct regulator *reg, int enable) +static int tcan4x5x_power_enable(struct tcan4x5x_priv *priv, int enable) { + struct regulator *reg = priv->power; + + /* + * Put the device into sleep mode if the RST pin is available, + * since a wake-up event, RST pin toggle, or power cycle are the only + * ways to exit sleep mode. + * Redundant if the regulator is exclusive to this device, but that + * can't be determined here. + * + * Datasheet: TCAN4550, section "8.4.3 Sleep Mode" + * https://www.ti.com/lit/gpn/tcan4550 + */ + if (priv->reset_gpio && !enable) { + int ret; + + ret = regmap_update_bits(priv->regmap, TCAN4X5X_CONFIG, + TCAN4X5X_MODE_SEL_MASK, + TCAN4X5X_MODE_SLEEP); + if (ret) + dev_err(&priv->spi->dev, "Setting sleep mode failed %pe\n", + ERR_PTR(ret)); + } + if (IS_ERR_OR_NULL(reg)) return 0; @@ -476,7 +499,7 @@ static int tcan4x5x_can_probe(struct spi_device *spi) goto out_m_can_class_free_dev; } - ret = tcan4x5x_power_enable(priv->power, 1); + ret = tcan4x5x_power_enable(priv, 1); if (ret) { dev_err(&spi->dev, "Enabling regulator failed %pe\n", ERR_PTR(ret)); @@ -531,7 +554,7 @@ static int tcan4x5x_can_probe(struct spi_device *spi) return 0; out_power: - tcan4x5x_power_enable(priv->power, 0); + tcan4x5x_power_enable(priv, 0); out_m_can_class_free_dev: m_can_class_free_dev(mcan_class->net); return ret; @@ -543,7 +566,7 @@ static void tcan4x5x_can_remove(struct spi_device *spi) m_can_class_unregister(&priv->cdev); - tcan4x5x_power_enable(priv->power, 0); + tcan4x5x_power_enable(priv, 0); m_can_class_free_dev(priv->cdev.net); } From 1802936fa03442c5e70c54d5e7d4540f11554812 Mon Sep 17 00:00:00 2001 From: Cunhao Lu <1579567540@qq.com> Date: Thu, 30 Jul 2026 22:34:02 +0800 Subject: [PATCH 1308/1433] dt-bindings: can: rockchip: add rk3588 CAN-FD compatible RK3588 integrates a Rockchip CAN-FD controller variant that is not fully compatible with RK3568v2. The RX FIFO count register field is encoded in bits 7:5 on RK3588, while RK3568v2 uses bits 6:4. Add a dedicated rockchip,rk3588-canfd compatible to describe this variant. Do not use rockchip,rk3568v2-canfd as a fallback, because that would describe a register layout that does not match the hardware. Reviewed-by: Heiko Stuebner Acked-by: Krzysztof Kozlowski Signed-off-by: Cunhao Lu <1579567540@qq.com> Tested-by: Heiko Stuebner Link: https://patch.msgid.link/tencent_AE1A1199FC8C9ADC680C6458134A46B40309@qq.com Signed-off-by: Marc Kleine-Budde --- .../devicetree/bindings/net/can/rockchip,rk3568v2-canfd.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/net/can/rockchip,rk3568v2-canfd.yaml b/Documentation/devicetree/bindings/net/can/rockchip,rk3568v2-canfd.yaml index a077c0330013..81e2b6dfeb02 100644 --- a/Documentation/devicetree/bindings/net/can/rockchip,rk3568v2-canfd.yaml +++ b/Documentation/devicetree/bindings/net/can/rockchip,rk3568v2-canfd.yaml @@ -16,7 +16,9 @@ allOf: properties: compatible: oneOf: - - const: rockchip,rk3568v2-canfd + - enum: + - rockchip,rk3568v2-canfd + - rockchip,rk3588-canfd - items: - const: rockchip,rk3568v3-canfd - const: rockchip,rk3568v2-canfd From b7d51d9b14eea0eb4f4cf3a8293b2f53539ca96f Mon Sep 17 00:00:00 2001 From: Cunhao Lu <1579567540@qq.com> Date: Thu, 30 Jul 2026 22:34:03 +0800 Subject: [PATCH 1309/1433] can: rockchip: add RK3588 CAN support Add support for the RK3588 CAN controller by introducing a dedicated model ID and OF match entry. The block is closely related to the existing RK3568 variants, but it cannot reuse their match data unchanged. In particular, RK3588 encodes RX_FIFO_CNT in bits 7:5 instead of 6:4, so the RX path needs SoC-specific handling. The RX FIFO count bitfield difference was found by comparing Rockchip's vendor kernel 6.1 CAN support for RK3568 and RK3588. Runtime testing on RK3588 also confirms that bits 7:5 are needed. Enable the existing erratum 5 empty-FIFO workaround for RK3588. Heiko reproduced erratum 6 on RK3588, so enable that workaround as well. CAN-FD is enabled for RK3588. The BRS bus-off issue seen in earlier testing was caused by the transmit delay compensation setting. With RKCANFD_REG_TRANSMIT_DELAY_COMPENSATION programmed to 0 on RK3588, CAN-FD with BRS works in local testing. Tested on an embedfire,rk3588-lubancat-5io board with can0/can1 directly connected, no other device on the bus, 60 Ohm bus termination, and a 300 MHz CAN clock. Runtime testing used 500 kbit/s arbitration bitrate and 1, 3 and 5 Mbit/s data bitrates. The 5 Mbit/s data phase test ran for 15 minutes with cangen using BRS and cansequence on the receiver. Both interfaces reported 9528377 packets and 150667356 bytes, with 0 bus-errors, 0 error-warn, 0 error-pass and 0 bus-off events. Co-developed-by: Heiko Stuebner Signed-off-by: Heiko Stuebner Tested-by: Heiko Stuebner Reviewed-by: Heiko Stuebner Signed-off-by: Cunhao Lu <1579567540@qq.com> Link: https://patch.msgid.link/tencent_207E464D12344B3228096E23A001D6882508@qq.com Signed-off-by: Marc Kleine-Budde --- drivers/net/can/rockchip/rockchip_canfd-core.c | 17 +++++++++++++++++ drivers/net/can/rockchip/rockchip_canfd-rx.c | 5 ++++- drivers/net/can/rockchip/rockchip_canfd.h | 14 +++++++++++++- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/drivers/net/can/rockchip/rockchip_canfd-core.c b/drivers/net/can/rockchip/rockchip_canfd-core.c index 29de0c01e4ed..37c1c22c40c9 100644 --- a/drivers/net/can/rockchip/rockchip_canfd-core.c +++ b/drivers/net/can/rockchip/rockchip_canfd-core.c @@ -50,6 +50,12 @@ static const struct rkcanfd_devtype_data rkcanfd_devtype_data_rk3568v3 = { RKCANFD_QUIRK_CANFD_BROKEN, }; +static const struct rkcanfd_devtype_data rkcanfd_devtype_data_rk3588 = { + .model = RKCANFD_MODEL_RK3588, + .quirks = RKCANFD_QUIRK_RK3568_ERRATUM_5 | + RKCANFD_QUIRK_RK3568_ERRATUM_6, +}; + static const char *__rkcanfd_get_model_str(enum rkcanfd_model model) { switch (model) { @@ -57,6 +63,8 @@ static const char *__rkcanfd_get_model_str(enum rkcanfd_model model) return "rk3568v2"; case RKCANFD_MODEL_RK3568V3: return "rk3568v3"; + case RKCANFD_MODEL_RK3588: + return "rk3588"; } return ""; @@ -148,6 +156,12 @@ static int rkcanfd_set_bittiming(struct rkcanfd_priv *priv) rkcanfd_write(priv, RKCANFD_REG_FD_DATA_BITTIMING, reg_dbt); + /* RK3588 CAN-FD BRS works with TDC disabled. */ + if (priv->devtype_data.model == RKCANFD_MODEL_RK3588) { + rkcanfd_write(priv, RKCANFD_REG_TRANSMIT_DELAY_COMPENSATION, 0); + return 0; + } + tdco = (priv->can.clock.freq / dbt->bitrate) * 2 / 3; tdco = min(tdco, FIELD_MAX(RKCANFD_REG_TRANSMIT_DELAY_COMPENSATION_TDC_OFFSET)); @@ -846,6 +860,9 @@ static const struct of_device_id rkcanfd_of_match[] = { }, { .compatible = "rockchip,rk3568v3-canfd", .data = &rkcanfd_devtype_data_rk3568v3, + }, { + .compatible = "rockchip,rk3588-canfd", + .data = &rkcanfd_devtype_data_rk3588, }, { /* sentinel */ }, diff --git a/drivers/net/can/rockchip/rockchip_canfd-rx.c b/drivers/net/can/rockchip/rockchip_canfd-rx.c index 475c0409e215..24e87daa1df0 100644 --- a/drivers/net/can/rockchip/rockchip_canfd-rx.c +++ b/drivers/net/can/rockchip/rockchip_canfd-rx.c @@ -281,7 +281,10 @@ rkcanfd_rx_fifo_get_len(const struct rkcanfd_priv *priv) { const u32 reg = rkcanfd_read(priv, RKCANFD_REG_RX_FIFO_CTRL); - return FIELD_GET(RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_CNT, reg); + if (priv->devtype_data.model == RKCANFD_MODEL_RK3588) + return FIELD_GET(RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_CNT_RK3588, reg); + + return FIELD_GET(RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_CNT_RK3568, reg); } int rkcanfd_handle_rx_int(struct rkcanfd_priv *priv) diff --git a/drivers/net/can/rockchip/rockchip_canfd.h b/drivers/net/can/rockchip/rockchip_canfd.h index 93131c7d7f54..95bea9bfd8a2 100644 --- a/drivers/net/can/rockchip/rockchip_canfd.h +++ b/drivers/net/can/rockchip/rockchip_canfd.h @@ -214,7 +214,8 @@ #define RKCANFD_REG_TXEVENT_FIFO_CTRL_TXE_FIFO_ENABLE BIT(0) #define RKCANFD_REG_RX_FIFO_CTRL 0x118 -#define RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_CNT GENMASK(6, 4) +#define RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_CNT_RK3568 GENMASK(6, 4) +#define RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_CNT_RK3588 GENMASK(7, 5) #define RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_FULL_WATERMARK GENMASK(3, 1) #define RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_ENABLE BIT(0) @@ -331,6 +332,11 @@ * rarely with the standard clock of 300 MHz, but almost immediately * at 80 MHz. * + * Tests on the rk3588 show the same empty FIFO condition. + * In that setup rx_fifo_empty_errors increments when the bus + * transitions from idle to high CAN-FD load and stops growing once + * the bus reaches a steady state. + * * To workaround this problem, check for empty FIFO with * rkcanfd_fifo_header_empty() in rkcanfd_handle_rx_int_one() and exit * early. @@ -344,6 +350,8 @@ /* Erratum 6: The CAN controller's transmission of extended frames may * intermittently change into standard frames * + * Tests on the rk3588 show the same problem. + * * Work around this issue by activating self reception (RXSTX). If we * have pending TX CAN frames, check all RX'ed CAN frames in * rkcanfd_rxstx_filter(). @@ -424,6 +432,9 @@ * cansequence -rv -i 1 * * - TX starvation after repeated Bus-Off + * Tests on the rk3588 show the same problem. In a + * 10-cycle Bus-Off recovery test, 9 cycles failed to send after the + * controller restarted. * To reproduce: * host: * sleep 3 && cangen can0 -I2 -Li -Di -p10 -g 0.0 @@ -434,6 +445,7 @@ enum rkcanfd_model { RKCANFD_MODEL_RK3568V2 = 0x35682, RKCANFD_MODEL_RK3568V3 = 0x35683, + RKCANFD_MODEL_RK3588 = 0x3588, }; struct rkcanfd_devtype_data { From a0d62de8adcea7a4400e2d2a68625f761baf01c1 Mon Sep 17 00:00:00 2001 From: Pavel Pisa Date: Sat, 1 Aug 2026 12:55:52 +0200 Subject: [PATCH 1310/1433] docs: ctucanfd: fix swapped colors in legend for TX buffer FSM of CTU CAN FD The Inaccessible and Accessible legend colors has been swapped for many years. As I have helped to clean the CTU CAN FD section of official documentation for mass produced silicon (multiple models of microcontrollers) with the company representatives, I have found the problem and propagate correction bask to the primary CTU CAN FD sources. Signed-off-by: Pavel Pisa Link: https://patch.msgid.link/d775feefa1c16d7ea7f42482483c81491f75ce6c.1785574572.git.pisa@cmp.felk.cvut.cz Signed-off-by: Marc Kleine-Budde --- .../networking/device_drivers/can/ctu/fsm_txt_buffer_user.svg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/networking/device_drivers/can/ctu/fsm_txt_buffer_user.svg b/Documentation/networking/device_drivers/can/ctu/fsm_txt_buffer_user.svg index 381323423b4c..c8cf0bc49b69 100644 --- a/Documentation/networking/device_drivers/can/ctu/fsm_txt_buffer_user.svg +++ b/Documentation/networking/device_drivers/can/ctu/fsm_txt_buffer_user.svg @@ -93,9 +93,9 @@ - - + + Accessiblefor SW Inaccessiblefor SW From 703b31d21e527d101a3c995b8174c175547b4d07 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 31 Jul 2026 23:17:53 +0000 Subject: [PATCH 1311/1433] can: vxcan: support per-netns device unregistration. Currently, vxcan_dellink() unregisters both local and peer devices synchronously under RTNL. Once RTNL is removed, it can be called concurrently from different netns. Let's use xchg() and unregister_netdevice_queue_net() to support per-netns device unregistration. This way, each device is queued for destruction only once by the winner of the race. Note that the extra netdev_hold() ensures that @peer obtained by the first xchg() is not freed during the subsequent access to netdev_priv(peer). The 2nd xchg() overwrites @dev to balance the refcount. Tested: 1. Create two vxcan pairs (vxcan1-2, vxcan3-4) between two netns (ns1 & ns2). # ip netns add ns1 # ip netns add ns2 # ip -n ns1 link add vxcan1 type vxcan peer vxcan2 netns ns2 # ip -n ns1 link add vxcan3 type vxcan peer vxcan4 netns ns2 2. Run bpftrace to check if the same process does NOT unregister the paired vxcan devices # bpftrace -e '#include kprobe:free_netdev { $dev = (struct net_device *)arg0; printf("PID: %d | DEV: %s%s\n", pid, $dev->name, kstack()); }' 3. Remove vxcan2 in ns2 and check bpftrace output # ip -n ns2 link del vxcan2 PID: 1524 | DEV: vxcan2 free_netdev+5 netdev_run_todo+4798 rtnl_dellink+1507 rtnetlink_rcv_msg+1791 netlink_rcv_skb+504 ... PID: 453 | DEV: vxcan1 free_netdev+5 netdev_run_todo+4798 process_scheduled_works+2538 worker_thread+1906 kthread+806 ret_from_fork+805 ret_from_fork_asm+17 4. Remove ns2 (thus vxcan4) and check bpftrace output # ip netns del ns2 PID: 12 | DEV: vxcan4 free_netdev+5 netdev_run_todo+4798 default_device_exit_batch+2271 ops_undo_list+993 cleanup_net+1122 process_scheduled_works+2538 worker_thread+1906 kthread+806 ret_from_fork+805 ret_from_fork_asm+17 ... PID: 462 | DEV: vxcan3 free_netdev+5 netdev_run_todo+4798 process_scheduled_works+2538 worker_thread+1906 kthread+806 ret_from_fork+805 ret_from_fork_asm+17 Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260731231755.2474376-1-kuniyu@google.com [mkl: fix indention struct vxcan_priv::peer_tracker] Signed-off-by: Marc Kleine-Budde --- drivers/net/can/vxcan.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/drivers/net/can/vxcan.c b/drivers/net/can/vxcan.c index e882250180ef..9e2e25d02471 100644 --- a/drivers/net/can/vxcan.c +++ b/drivers/net/can/vxcan.c @@ -33,6 +33,7 @@ MODULE_ALIAS_RTNL_LINK(DRV_NAME); struct vxcan_priv { struct net_device __rcu *peer; + netdevice_tracker peer_tracker; }; static netdev_tx_t vxcan_xmit(struct sk_buff *oskb, struct net_device *dev) @@ -268,9 +269,11 @@ static int vxcan_newlink(struct net_device *dev, /* cross link the device pair */ priv = netdev_priv(dev); rcu_assign_pointer(priv->peer, peer); + netdev_hold(peer, &priv->peer_tracker, GFP_KERNEL); priv = netdev_priv(peer); rcu_assign_pointer(priv->peer, dev); + netdev_hold(dev, &priv->peer_tracker, GFP_KERNEL); return 0; @@ -281,24 +284,25 @@ static int vxcan_newlink(struct net_device *dev, static void vxcan_dellink(struct net_device *dev, struct list_head *head) { + netdevice_tracker *peer_tracker; struct vxcan_priv *priv; struct net_device *peer; priv = netdev_priv(dev); - peer = rtnl_dereference(priv->peer); + peer_tracker = &priv->peer_tracker; + peer = unrcu_pointer(xchg(&priv->peer, NULL)); + if (!peer) + return; - /* Note : dellink() is called from default_device_exit_batch(), - * before a rcu_synchronize() point. The devices are guaranteed - * not being freed before one RCU grace period. - */ - RCU_INIT_POINTER(priv->peer, NULL); unregister_netdevice_queue(dev, head); - if (peer) { - priv = netdev_priv(peer); - RCU_INIT_POINTER(priv->peer, NULL); - unregister_netdevice_queue(peer, head); - } + priv = netdev_priv(peer); + dev = unrcu_pointer(xchg(&priv->peer, NULL)); + if (dev) + unregister_netdevice_queue_net(dev_net(dev), peer, head); + + netdev_put(peer, peer_tracker); + netdev_put(dev, &priv->peer_tracker); } static const struct nla_policy vxcan_policy[VXCAN_INFO_MAX + 1] = { From 572e27108be5f6c1537a0885dd59a0a0c7c04a6a Mon Sep 17 00:00:00 2001 From: Harini T Date: Fri, 17 Jul 2026 07:44:14 +0530 Subject: [PATCH 1312/1433] MAINTAINERS: Replace maintainer for Xilinx CAN driver Replace Appana Durga Kedareswara rao with Harini T as the maintainer of the Xilinx CAN driver. Kedar is no longer maintaining it. Signed-off-by: Harini T Acked-by: Appana Durga Kedareswara rao Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260717021415.2234865-2-harini.t@amd.com Signed-off-by: Marc Kleine-Budde --- Documentation/devicetree/bindings/net/can/xilinx,can.yaml | 2 +- MAINTAINERS | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/net/can/xilinx,can.yaml b/Documentation/devicetree/bindings/net/can/xilinx,can.yaml index 40835497050a..e705c719f7b5 100644 --- a/Documentation/devicetree/bindings/net/can/xilinx,can.yaml +++ b/Documentation/devicetree/bindings/net/can/xilinx,can.yaml @@ -8,7 +8,7 @@ title: Xilinx CAN and CANFD controller maintainers: - - Appana Durga Kedareswara rao + - Harini T properties: compatible: diff --git a/MAINTAINERS b/MAINTAINERS index 991460050da7..6b49a2c80cd5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29573,7 +29573,7 @@ F: Documentation/devicetree/bindings/net/xlnx,axi-ethernet.yaml F: drivers/net/ethernet/xilinx/xilinx_axienet* XILINX CAN DRIVER -M: Appana Durga Kedareswara rao +M: Harini T L: linux-can@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/net/can/xilinx,can.yaml From ff0fec263c0e374b61ad8fd198f433192208e4d7 Mon Sep 17 00:00:00 2001 From: Eduard Bostina Date: Sun, 19 Jul 2026 14:01:27 +0000 Subject: [PATCH 1313/1433] dt-bindings: net: can: Convert TI HECC to DT schema Convert the Texas Instruments High End CAN Controller (HECC) bindings to DT schema. Signed-off-by: Eduard Bostina Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260719140127.3558941-1-egbostina@gmail.com Signed-off-by: Marc Kleine-Budde --- .../bindings/net/can/ti,am3517-hecc.yaml | 64 +++++++++++++++++++ .../devicetree/bindings/net/can/ti_hecc.txt | 32 ---------- 2 files changed, 64 insertions(+), 32 deletions(-) create mode 100644 Documentation/devicetree/bindings/net/can/ti,am3517-hecc.yaml delete mode 100644 Documentation/devicetree/bindings/net/can/ti_hecc.txt diff --git a/Documentation/devicetree/bindings/net/can/ti,am3517-hecc.yaml b/Documentation/devicetree/bindings/net/can/ti,am3517-hecc.yaml new file mode 100644 index 000000000000..7874e9e49224 --- /dev/null +++ b/Documentation/devicetree/bindings/net/can/ti,am3517-hecc.yaml @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/can/ti,am3517-hecc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments High End CAN Controller (HECC) + +maintainers: + - Eduard Bostina + +allOf: + - $ref: can-controller.yaml# + +properties: + compatible: + const: ti,am3517-hecc + + reg: + maxItems: 3 + + reg-names: + items: + - const: hecc + - const: hecc-ram + - const: mbx + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + + ti,use-hecc1int: + type: boolean + description: + If provided, configures HECC to produce all interrupts on the + HECC1INT interrupt line. By default, the HECC0INT interrupt line + will be used. + default: false + + xceiver-supply: + description: Regulator that powers the CAN transceiver. + +required: + - compatible + - reg + - reg-names + - interrupts + - clocks + +unevaluatedProperties: false + +examples: + - | + can@5c050000 { + compatible = "ti,am3517-hecc"; + reg = <0x5c050000 0x80>, + <0x5c053000 0x180>, + <0x5c052000 0x200>; + reg-names = "hecc", "hecc-ram", "mbx"; + interrupts = <24>; + clocks = <&hecc_ck>; + }; diff --git a/Documentation/devicetree/bindings/net/can/ti_hecc.txt b/Documentation/devicetree/bindings/net/can/ti_hecc.txt deleted file mode 100644 index e0f0a7cfe329..000000000000 --- a/Documentation/devicetree/bindings/net/can/ti_hecc.txt +++ /dev/null @@ -1,32 +0,0 @@ -Texas Instruments High End CAN Controller (HECC) -================================================ - -This file provides information, what the device node -for the hecc interface contains. - -Required properties: -- compatible: "ti,am3517-hecc" -- reg: addresses and lengths of the register spaces for 'hecc', 'hecc-ram' - and 'mbx' -- reg-names :"hecc", "hecc-ram", "mbx" -- interrupts: interrupt mapping for the hecc interrupts sources -- clocks: clock phandles (see clock bindings for details) - -Optional properties: -- ti,use-hecc1int: if provided configures HECC to produce all interrupts - on HECC1INT interrupt line. By default HECC0INT interrupt - line will be used. -- xceiver-supply: regulator that powers the CAN transceiver - -Example: - -For am3517evm board: - hecc: can@5c050000 { - compatible = "ti,am3517-hecc"; - reg = <0x5c050000 0x80>, - <0x5c053000 0x180>, - <0x5c052000 0x200>; - reg-names = "hecc", "hecc-ram", "mbx"; - interrupts = <24>; - clocks = <&hecc_ck>; - }; From d17e20a2dcaffe8354430b99d9297bc908bb8a9b Mon Sep 17 00:00:00 2001 From: Harini T Date: Fri, 17 Jul 2026 07:44:15 +0530 Subject: [PATCH 1314/1433] dt-bindings: can: xilinx_can: Document phys property The Xilinx CAN and CANFD controllers can be connected to an external CAN transceiver on the board. That connection is described with the standard "phys" property on the controller node, pointing to a CAN transceiver PHY node which models the transceiver and its control lines (for example the standby/enable signals). Describe the optional "phys" property (a single transceiver PHY) so the on-board CAN transceiver can be described in the device tree. Signed-off-by: Harini T Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260717021415.2234865-3-harini.t@amd.com Signed-off-by: Marc Kleine-Budde --- Documentation/devicetree/bindings/net/can/xilinx,can.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/net/can/xilinx,can.yaml b/Documentation/devicetree/bindings/net/can/xilinx,can.yaml index e705c719f7b5..18015e60fd6d 100644 --- a/Documentation/devicetree/bindings/net/can/xilinx,can.yaml +++ b/Documentation/devicetree/bindings/net/can/xilinx,can.yaml @@ -53,6 +53,9 @@ properties: $ref: /schemas/types.yaml#/definitions/flag description: CAN TX_OL, TX_TL and RX FIFOs have ECC support(AXI CAN) + phys: + maxItems: 1 + required: - compatible - reg From 966506838a4a8f51abcac8db67e6c2980f366134 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 8 Jul 2026 10:05:12 +0700 Subject: [PATCH 1315/1433] can: m_can: Use of_property_present() for wakeup-source The 'wakeup-source' property is declared as a phandle-array in both YAML bindings and Device Tree source files. However, the driver currently uses of_property_read_bool() to check for its existence. According to the function's documentation, usage on non-boolean property types is deprecated. Switch to of_property_present() to comply with the recommended API for checking the presence of a property. Fixes: 04d5826b074e ("can: m_can: Map WoL to device_set_wakeup_enable") Reviewed-by: Kendall Willis Acked-by: Markus Schneider-Pargmann Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260708030512.8570-1-phucduc.bui@gmail.com Signed-off-by: Marc Kleine-Budde --- drivers/net/can/m_can/m_can.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c index eb856547ae7d..16f80607e150 100644 --- a/drivers/net/can/m_can/m_can.c +++ b/drivers/net/can/m_can/m_can.c @@ -2464,7 +2464,7 @@ struct m_can_classdev *m_can_class_allocate_dev(struct device *dev, return ERR_PTR(ret); } - if (dev->of_node && of_property_read_bool(dev->of_node, "wakeup-source")) + if (dev->of_node && of_property_present(dev->of_node, "wakeup-source")) device_set_wakeup_capable(dev, true); /* Get TX FIFO size From 3392698d3c1db2b27b97ba025324e2b644f419f7 Mon Sep 17 00:00:00 2001 From: Fanbo He Date: Thu, 2 Jul 2026 11:13:06 +0800 Subject: [PATCH 1316/1433] drivers: gs_usb: gs_usb_probe(): fix typo in error message Fix typo in the error messag. Signed-off-by: Fanbo He Link: https://patch.msgid.link/20260702031306.18988-1-hefanbo@gmail.com [mkl: add commit message] Signed-off-by: Marc Kleine-Budde --- drivers/net/can/usb/gs_usb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/can/usb/gs_usb.c b/drivers/net/can/usb/gs_usb.c index 82508a865095..3b9b2f104d86 100644 --- a/drivers/net/can/usb/gs_usb.c +++ b/drivers/net/can/usb/gs_usb.c @@ -1565,7 +1565,7 @@ static int gs_usb_probe(struct usb_interface *intf, if (icount > type_max(parent->channel_cnt)) { dev_err(&intf->dev, - "Driver cannot handle more that %u CAN interfaces\n", + "Driver cannot handle more than %u CAN interfaces\n", type_max(parent->channel_cnt)); return -EINVAL; } From f6a48a7b10be44c470f5ce1037ebaaf343f384f7 Mon Sep 17 00:00:00 2001 From: "Markus Schneider-Pargmann (The Capable Hub)" Date: Fri, 15 May 2026 15:15:32 +0200 Subject: [PATCH 1317/1433] can: m_can: pci: Remove driver_data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit driver_data is set to M_CAN_CLOCK_FREQ_EHL for all models. This change was already five years ago, I don't expect any follow up models that need to set a different frequency through the driver_data at this point. Hardcode the M_CAN_CLOCK_FREQ_EHL. Once there are new models we can evaluate what data needs to be in driver_data. Acked-by: Uwe Kleine-König (The Capable Hub) Signed-off-by: Markus Schneider-Pargmann (The Capable Hub) Reviewed-by: Vincent Mailhol Link: https://patch.msgid.link/20260515-topic-mcan-pci-driverdata-v7-1-v2-1-e33e014ff328@baylibre.com Signed-off-by: Marc Kleine-Budde --- drivers/net/can/m_can/m_can_pci.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/can/m_can/m_can_pci.c b/drivers/net/can/m_can/m_can_pci.c index eb31ed1f9644..d11a7c88fc32 100644 --- a/drivers/net/can/m_can/m_can_pci.c +++ b/drivers/net/can/m_can/m_can_pci.c @@ -126,7 +126,7 @@ static int m_can_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) mcan_class->net->irq = pci_irq_vector(pci, 0); mcan_class->pm_clock_support = 1; mcan_class->pm_wake_source = 0; - mcan_class->can.clock.freq = id->driver_data; + mcan_class->can.clock.freq = M_CAN_CLOCK_FREQ_EHL; mcan_class->irq_edge_triggered = true; mcan_class->ops = &m_can_pci_ops; @@ -183,8 +183,8 @@ static SIMPLE_DEV_PM_OPS(m_can_pci_pm_ops, m_can_pci_suspend, m_can_pci_resume); static const struct pci_device_id m_can_pci_id_table[] = { - { PCI_VDEVICE(INTEL, 0x4bc1), M_CAN_CLOCK_FREQ_EHL, }, - { PCI_VDEVICE(INTEL, 0x4bc2), M_CAN_CLOCK_FREQ_EHL, }, + { PCI_VDEVICE(INTEL, 0x4bc1) }, + { PCI_VDEVICE(INTEL, 0x4bc2) }, { } /* Terminating Entry */ }; MODULE_DEVICE_TABLE(pci, m_can_pci_id_table); From 21ef2d065ad3f0cfbf2ae51260bf962a9fa2c643 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 12 Aug 2026 08:54:38 +0000 Subject: [PATCH 1318/1433] net: prevent torn reads in netdev_tc_txq netdev_set_tc_queue() (and related helpers/drivers such as netdev_bind_sb_channel_queue(), netdev_reset_tc(), and netdev_unbind_sb_channel()) perform separate 16-bit writes to dev->tc_to_txq[tc].count and dev->tc_to_txq[tc].offset. Furthermore, memset() in netdev_reset_tc() and netdev_unbind_sb_channel() provides no guarantee of performing full 32-bit word stores. Concurrent lockless readers (e.g. skb_tx_hash(), netdev_txq_to_tc(), ixgbe_select_queue(), taprio, mqprio, FPE drivers) can observe torn values where offset and count belong to inconsistent configurations. Redefine struct netdev_tc_txq to embed count and offset inside a union with a u32 combined field, allowing atomic manipulation via READ_ONCE() and WRITE_ONCE(). Update all lockless readers and writers across the kernel to use READ_ONCE() and WRITE_ONCE() on the combined field. Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260812085440.3917924-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/igc/igc_tsn.c | 6 ++- drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 7 +-- .../net/ethernet/mellanox/mlx5/core/en_main.c | 2 +- drivers/net/ethernet/sfc/falcon/tx.c | 8 +++- drivers/net/ethernet/sfc/siena/tx.c | 8 +++- .../net/ethernet/stmicro/stmmac/stmmac_fpe.c | 14 ++++-- include/linux/netdevice.h | 9 +++- net/core/dev.c | 46 +++++++++++++------ net/sched/sch_mqprio.c | 4 +- net/sched/sch_mqprio_lib.c | 7 ++- net/sched/sch_taprio.c | 26 ++++++----- 11 files changed, 94 insertions(+), 43 deletions(-) diff --git a/drivers/net/ethernet/intel/igc/igc_tsn.c b/drivers/net/ethernet/intel/igc/igc_tsn.c index 52de2bcbadbe..0c08650d3bb2 100644 --- a/drivers/net/ethernet/intel/igc/igc_tsn.c +++ b/drivers/net/ethernet/intel/igc/igc_tsn.c @@ -183,13 +183,15 @@ static u32 igc_fpe_map_preempt_tc_to_queue(const struct igc_adapter *adapter, u32 i, queue = 0; for (i = 0; i < dev->num_tc; i++) { + struct netdev_tc_txq res; u32 offset, count; if (!(preemptible_tcs & BIT(i))) continue; - offset = dev->tc_to_txq[i].offset; - count = dev->tc_to_txq[i].count; + res.combined = READ_ONCE(dev->tc_to_txq[i].combined); + offset = res.offset; + count = res.count; queue |= GENMASK(offset + count - 1, offset); } diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c index 8873a8cc4a18..f91856498eb2 100644 --- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c +++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c @@ -9273,10 +9273,11 @@ static u16 ixgbe_select_queue(struct net_device *dev, struct sk_buff *skb, if (sb_dev) { u8 tc = netdev_get_prio_tc_map(dev, skb->priority); struct net_device *vdev = sb_dev; + struct netdev_tc_txq res; - txq = vdev->tc_to_txq[tc].offset; - txq += reciprocal_scale(skb_get_hash(skb), - vdev->tc_to_txq[tc].count); + res.combined = READ_ONCE(vdev->tc_to_txq[tc].combined); + txq = res.offset; + txq += reciprocal_scale(skb_get_hash(skb), res.count); return txq; } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index ca3d7c6b5210..8a877891e690 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -3247,7 +3247,7 @@ static int mlx5e_update_tc_and_tx_queues(struct mlx5e_priv *priv) old_num_txqs = netdev->real_num_tx_queues; old_ntc = netdev->num_tc ? : 1; for (i = 0; i < ARRAY_SIZE(old_tc_to_txq); i++) - old_tc_to_txq[i] = netdev->tc_to_txq[i]; + old_tc_to_txq[i].combined = READ_ONCE(netdev->tc_to_txq[i].combined); nch = priv->channels.params.num_channels; ntc = priv->channels.params.mqprio.num_tc; diff --git a/drivers/net/ethernet/sfc/falcon/tx.c b/drivers/net/ethernet/sfc/falcon/tx.c index 9e18aaf44bad..e4d47d26a87a 100644 --- a/drivers/net/ethernet/sfc/falcon/tx.c +++ b/drivers/net/ethernet/sfc/falcon/tx.c @@ -439,8 +439,12 @@ int ef4_setup_tc(struct net_device *net_dev, enum tc_setup_type type, return 0; for (tc = 0; tc < num_tc; tc++) { - net_dev->tc_to_txq[tc].offset = tc * efx->n_tx_channels; - net_dev->tc_to_txq[tc].count = efx->n_tx_channels; + struct netdev_tc_txq res = { + .offset = tc * efx->n_tx_channels, + .count = efx->n_tx_channels, + }; + + WRITE_ONCE(net_dev->tc_to_txq[tc].combined, res.combined); } if (num_tc > net_dev->num_tc) { diff --git a/drivers/net/ethernet/sfc/siena/tx.c b/drivers/net/ethernet/sfc/siena/tx.c index 91e87594ed1e..1ce98f8fdaf8 100644 --- a/drivers/net/ethernet/sfc/siena/tx.c +++ b/drivers/net/ethernet/sfc/siena/tx.c @@ -380,8 +380,12 @@ int efx_siena_setup_tc(struct net_device *net_dev, enum tc_setup_type type, return 0; for (tc = 0; tc < num_tc; tc++) { - net_dev->tc_to_txq[tc].offset = tc * efx->n_tx_channels; - net_dev->tc_to_txq[tc].count = efx->n_tx_channels; + struct netdev_tc_txq res = { + .offset = tc * efx->n_tx_channels, + .count = efx->n_tx_channels, + }; + + WRITE_ONCE(net_dev->tc_to_txq[tc].combined, res.combined); } net_dev->num_tc = num_tc; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c index c54c70224351..c889204a7aa5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c @@ -217,8 +217,11 @@ int dwmac5_fpe_map_preemption_class(struct net_device *ndev, * and is direct one-to-one mapping." */ for (u32 tc = 0; tc < num_tc; tc++) { - count = ndev->tc_to_txq[tc].count; - offset = ndev->tc_to_txq[tc].offset; + struct netdev_tc_txq res; + + res.combined = READ_ONCE(ndev->tc_to_txq[tc].combined); + count = res.count; + offset = res.offset; if (pclass & BIT(tc)) preemptible_txqs |= GENMASK(offset + count - 1, offset); @@ -275,8 +278,11 @@ int dwxgmac3_fpe_map_preemption_class(struct net_device *ndev, * any of the scheduling algorithms." */ for (u32 tc = 0; tc < num_tc; tc++) { - count = ndev->tc_to_txq[tc].count; - offset = ndev->tc_to_txq[tc].offset; + struct netdev_tc_txq res; + + res.combined = READ_ONCE(ndev->tc_to_txq[tc].combined); + count = res.count; + offset = res.offset; if (pclass & BIT(tc)) preemptible_txqs |= GENMASK(offset + count - 1, offset); diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 7f5c2323146d..9d22e6b0df60 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -832,8 +832,13 @@ struct xps_dev_maps { #define TC_BITMASK 15 /* HW offloaded queuing disciplines txq count and offset maps */ struct netdev_tc_txq { - u16 count; - u16 offset; + union { + struct { + u16 count; + u16 offset; + }; + u32 combined; + }; }; #if defined(CONFIG_FCOE) || defined(CONFIG_FCOE_MODULE) diff --git a/net/core/dev.c b/net/core/dev.c index 517ac6a575c4..694a5ab773f7 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -2652,11 +2652,13 @@ EXPORT_SYMBOL_GPL(dev_queue_xmit_nit); */ static void netif_setup_tc(struct net_device *dev, unsigned int txq) { + struct netdev_tc_txq res; int i; - struct netdev_tc_txq *tc = &dev->tc_to_txq[0]; + + res.combined = READ_ONCE(dev->tc_to_txq[0].combined); /* If TC0 is invalidated disable TC mapping */ - if (tc->offset + tc->count > txq) { + if (res.offset + res.count > txq) { netdev_warn(dev, "Number of in use tx queues changed invalidating tc mappings. Priority traffic classification disabled!\n"); dev->num_tc = 0; return; @@ -2666,8 +2668,8 @@ static void netif_setup_tc(struct net_device *dev, unsigned int txq) for (i = 1; i < TC_BITMASK + 1; i++) { int q = netdev_get_prio_tc_map(dev, i); - tc = &dev->tc_to_txq[q]; - if (tc->offset + tc->count > txq) { + res.combined = READ_ONCE(dev->tc_to_txq[q].combined); + if (res.offset + res.count > txq) { netdev_warn(dev, "Number of in use tx queues changed. Priority %i to tc mapping %i is no longer valid. Setting map to 0\n", i, q); netdev_set_prio_tc_map(dev, i, 0); @@ -2683,7 +2685,10 @@ int netdev_txq_to_tc(struct net_device *dev, unsigned int txq) /* walk through the TCs and see if it falls into any of them */ for (i = 0; i < TC_MAX_QUEUE; i++, tc++) { - if ((txq - tc->offset) < tc->count) + struct netdev_tc_txq res; + + res.combined = READ_ONCE(tc->combined); + if ((txq - res.offset) < res.count) return i; } @@ -3103,6 +3108,8 @@ static void netdev_unbind_all_sb_channels(struct net_device *dev) void netdev_reset_tc(struct net_device *dev) { + int i; + #ifdef CONFIG_XPS netif_reset_xps_queues_gt(dev, 0); #endif @@ -3110,21 +3117,26 @@ void netdev_reset_tc(struct net_device *dev) /* Reset TC configuration of device */ dev->num_tc = 0; - memset(dev->tc_to_txq, 0, sizeof(dev->tc_to_txq)); + for (i = 0; i < TC_MAX_QUEUE; i++) + WRITE_ONCE(dev->tc_to_txq[i].combined, 0); memset(dev->prio_tc_map, 0, sizeof(dev->prio_tc_map)); } EXPORT_SYMBOL(netdev_reset_tc); int netdev_set_tc_queue(struct net_device *dev, u8 tc, u16 count, u16 offset) { + struct netdev_tc_txq res = { + .count = count, + .offset = offset, + }; + if (tc >= dev->num_tc) return -EINVAL; #ifdef CONFIG_XPS netif_reset_xps_queues(dev, offset, count); #endif - dev->tc_to_txq[tc].count = count; - dev->tc_to_txq[tc].offset = offset; + WRITE_ONCE(dev->tc_to_txq[tc].combined, res.combined); return 0; } EXPORT_SYMBOL(netdev_set_tc_queue); @@ -3148,11 +3160,13 @@ void netdev_unbind_sb_channel(struct net_device *dev, struct net_device *sb_dev) { struct netdev_queue *txq = &dev->_tx[dev->num_tx_queues]; + int i; #ifdef CONFIG_XPS netif_reset_xps_queues_gt(sb_dev, 0); #endif - memset(sb_dev->tc_to_txq, 0, sizeof(sb_dev->tc_to_txq)); + for (i = 0; i < TC_MAX_QUEUE; i++) + WRITE_ONCE(sb_dev->tc_to_txq[i].combined, 0); memset(sb_dev->prio_tc_map, 0, sizeof(sb_dev->prio_tc_map)); while (txq-- != &dev->_tx[0]) { @@ -3175,8 +3189,12 @@ int netdev_bind_sb_channel_queue(struct net_device *dev, return -EINVAL; /* Record the mapping */ - sb_dev->tc_to_txq[tc].count = count; - sb_dev->tc_to_txq[tc].offset = offset; + struct netdev_tc_txq res = { + .count = count, + .offset = offset, + }; + + WRITE_ONCE(sb_dev->tc_to_txq[tc].combined, res.combined); /* Provide a way for Tx queue to find the tc_to_txq map or * XPS map for itself. @@ -3542,9 +3560,11 @@ static u16 skb_tx_hash(const struct net_device *dev, if (dev->num_tc) { u8 tc = netdev_get_prio_tc_map(dev, skb->priority); + struct netdev_tc_txq res; - qoffset = sb_dev->tc_to_txq[tc].offset; - qcount = sb_dev->tc_to_txq[tc].count; + res.combined = READ_ONCE(sb_dev->tc_to_txq[tc].combined); + qoffset = res.offset; + qcount = res.count; if (unlikely(!qcount)) { net_warn_ratelimited("%s: invalid qcount, qoffset %u for tc %u\n", sb_dev->name, qoffset, tc); diff --git a/net/sched/sch_mqprio.c b/net/sched/sch_mqprio.c index ae991fc25b43..6ced7008ef5c 100644 --- a/net/sched/sch_mqprio.c +++ b/net/sched/sch_mqprio.c @@ -679,12 +679,14 @@ static int mqprio_dump_class_stats(struct Qdisc *sch, unsigned long cl, rcu_read_lock(); if (cl >= TC_H_MIN_PRIORITY) { struct net_device *dev = qdisc_dev(sch); - struct netdev_tc_txq tc = dev->tc_to_txq[cl & TC_BITMASK]; + struct netdev_tc_txq tc; struct gnet_stats_queue qstats = {0}; struct gnet_stats_basic_sync bstats; u32 qlen = 0; int i; + tc.combined = READ_ONCE(dev->tc_to_txq[cl & TC_BITMASK].combined); + gnet_stats_basic_sync_init(&bstats); for (i = tc.offset; i < tc.offset + tc.count; i++) { diff --git a/net/sched/sch_mqprio_lib.c b/net/sched/sch_mqprio_lib.c index b3a5572c167b..b60e130c7078 100644 --- a/net/sched/sch_mqprio_lib.c +++ b/net/sched/sch_mqprio_lib.c @@ -108,8 +108,11 @@ void mqprio_qopt_reconstruct(struct net_device *dev, struct tc_mqprio_qopt *qopt memcpy(qopt->prio_tc_map, dev->prio_tc_map, sizeof(qopt->prio_tc_map)); for (tc = 0; tc < num_tc; tc++) { - qopt->count[tc] = dev->tc_to_txq[tc].count; - qopt->offset[tc] = dev->tc_to_txq[tc].offset; + struct netdev_tc_txq res; + + res.combined = READ_ONCE(dev->tc_to_txq[tc].combined); + qopt->count[tc] = res.count; + qopt->offset[tc] = res.offset; } } EXPORT_SYMBOL_GPL(mqprio_qopt_reconstruct); diff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c index 299234a5f0fe..7d5fe93a4c12 100644 --- a/net/sched/sch_taprio.c +++ b/net/sched/sch_taprio.c @@ -762,12 +762,13 @@ static struct sk_buff *taprio_dequeue_from_txq(struct Qdisc *sch, int txq, static void taprio_next_tc_txq(struct net_device *dev, int tc, int *txq) { - int offset = dev->tc_to_txq[tc].offset; - int count = dev->tc_to_txq[tc].count; + struct netdev_tc_txq res; + + res.combined = READ_ONCE(dev->tc_to_txq[tc].combined); (*txq)++; - if (*txq == offset + count) - *txq = offset; + if (*txq == res.offset + res.count) + *txq = res.offset; } /* Prioritize higher traffic classes, and select among TXQs belonging to the @@ -1441,15 +1442,14 @@ static u32 tc_map_to_queue_mask(struct net_device *dev, u32 tc_mask) u32 i, queue_mask = 0; for (i = 0; i < dev->num_tc; i++) { - u32 offset, count; + struct netdev_tc_txq res; if (!(tc_mask & BIT(i))) continue; - offset = dev->tc_to_txq[i].offset; - count = dev->tc_to_txq[i].count; + res.combined = READ_ONCE(dev->tc_to_txq[i].combined); - queue_mask |= GENMASK(offset + count - 1, offset); + queue_mask |= GENMASK(res.offset + res.count - 1, res.offset); } return queue_mask; @@ -1802,10 +1802,14 @@ static int taprio_mqprio_cmp(const struct net_device *dev, if (!mqprio || mqprio->num_tc != dev->num_tc) return -1; - for (i = 0; i < mqprio->num_tc; i++) - if (dev->tc_to_txq[i].count != mqprio->count[i] || - dev->tc_to_txq[i].offset != mqprio->offset[i]) + for (i = 0; i < mqprio->num_tc; i++) { + struct netdev_tc_txq res; + + res.combined = READ_ONCE(dev->tc_to_txq[i].combined); + if (res.count != mqprio->count[i] || + res.offset != mqprio->offset[i]) return -1; + } for (i = 0; i <= TC_BITMASK; i++) if (dev->prio_tc_map[i] != mqprio->prio_tc_map[i]) From 0c6c32a8c854e570998494b8368d314d526ddbd3 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 12 Aug 2026 08:54:39 +0000 Subject: [PATCH 1319/1433] net: add READ_ONCE()/WRITE_ONCE() annotations for dev->num_tc Several fast-path and control-path lockless readers access dev->num_tc (e.g., skb_tx_hash(), netdev_txq_to_tc(), netdev_get_num_tc(), and qdisc/driver lookups) while concurrent writers update dev->num_tc during TC setup, device reset, or channel configuration. Add READ_ONCE() and WRITE_ONCE() annotations to prevent compiler reordering and load/store tearing when accessing dev->num_tc. Update inline helpers in netdevice.h (netdev_get_num_tc(), netdev_set_prio_tc_map(), and netdev_get_sb_channel()) as well as writers and lockless readers in core networking code and drivers. Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260812085440.3917924-3-edumazet@google.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/chelsio/cxgb4/cxgb4_main.c | 2 +- .../net/ethernet/freescale/dpaa2/dpaa2-eth.c | 12 +++++---- drivers/net/ethernet/intel/igc/igc_tsn.c | 2 +- .../net/ethernet/mellanox/mlx5/core/en_main.c | 2 +- drivers/net/ethernet/sfc/falcon/net_driver.h | 2 +- drivers/net/ethernet/sfc/falcon/tx.c | 8 +++--- drivers/net/ethernet/sfc/siena/tx.c | 4 +-- drivers/net/ethernet/ti/cpsw_priv.c | 2 +- include/linux/netdevice.h | 8 +++--- net/core/dev.c | 25 ++++++++++--------- net/core/net-sysfs.c | 2 +- net/sched/sch_taprio.c | 7 +++--- 12 files changed, 40 insertions(+), 36 deletions(-) diff --git a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c index 9e2c2fa16d7a..1ced6df6eac8 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c +++ b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c @@ -1163,7 +1163,7 @@ static u16 cxgb_select_queue(struct net_device *dev, struct sk_buff *skb, } #endif /* CONFIG_CHELSIO_T4_DCB */ - if (dev->num_tc) { + if (netdev_get_num_tc(dev)) { struct port_info *pi = netdev2pinfo(dev); u8 ver, proto; diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c index 764d2a09668f..6f1046c9cc51 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c @@ -1403,10 +1403,10 @@ static netdev_tx_t __dpaa2_eth_tx(struct sk_buff *skb, struct dpaa2_eth_fq *fq; struct netdev_queue *nq; struct dpaa2_fd *fd; + int err, i, num_tc; u16 queue_mapping; void *swa = NULL; u8 prio = 0; - int err, i; u32 fd_len; percpu_stats = this_cpu_ptr(priv->percpu_stats); @@ -1468,12 +1468,14 @@ static netdev_tx_t __dpaa2_eth_tx(struct sk_buff *skb, */ queue_mapping = skb_get_queue_mapping(skb); - if (net_dev->num_tc) { + num_tc = netdev_get_num_tc(net_dev); + + if (num_tc) { prio = netdev_txq_to_tc(net_dev, queue_mapping); /* Hardware interprets priority level 0 as being the highest, * so we need to do a reverse mapping to the netdev tc index */ - prio = net_dev->num_tc - prio - 1; + prio = num_tc - prio - 1; /* We have only one FQ array entry for all Tx hardware queues * with the same flow id (but different priority levels) */ @@ -2913,7 +2915,7 @@ static int update_xps(struct dpaa2_eth_priv *priv) return -ENOMEM; num_queues = dpaa2_eth_queue_count(priv); - netdev_queues = (net_dev->num_tc ? : 1) * num_queues; + netdev_queues = (netdev_get_num_tc(net_dev) ? : 1) * num_queues; /* The first entries in priv->fq array are Tx/Tx conf * queues, so only process those @@ -2946,7 +2948,7 @@ static int dpaa2_eth_setup_mqprio(struct net_device *net_dev, num_queues = dpaa2_eth_queue_count(priv); num_tc = mqprio->num_tc; - if (num_tc == net_dev->num_tc) + if (num_tc == netdev_get_num_tc(net_dev)) return 0; if (num_tc > dpaa2_eth_tc_count(priv)) { diff --git a/drivers/net/ethernet/intel/igc/igc_tsn.c b/drivers/net/ethernet/intel/igc/igc_tsn.c index 0c08650d3bb2..d23a45a34fa3 100644 --- a/drivers/net/ethernet/intel/igc/igc_tsn.c +++ b/drivers/net/ethernet/intel/igc/igc_tsn.c @@ -182,7 +182,7 @@ static u32 igc_fpe_map_preempt_tc_to_queue(const struct igc_adapter *adapter, struct net_device *dev = adapter->netdev; u32 i, queue = 0; - for (i = 0; i < dev->num_tc; i++) { + for (i = 0; i < netdev_get_num_tc(dev); i++) { struct netdev_tc_txq res; u32 offset, count; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index 8a877891e690..fc110a7d16e8 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -3245,7 +3245,7 @@ static int mlx5e_update_tc_and_tx_queues(struct mlx5e_priv *priv) int i; old_num_txqs = netdev->real_num_tx_queues; - old_ntc = netdev->num_tc ? : 1; + old_ntc = netdev_get_num_tc(netdev) ? : 1; for (i = 0; i < ARRAY_SIZE(old_tc_to_txq); i++) old_tc_to_txq[i].combined = READ_ONCE(netdev->tc_to_txq[i].combined); diff --git a/drivers/net/ethernet/sfc/falcon/net_driver.h b/drivers/net/ethernet/sfc/falcon/net_driver.h index 7ab0db44720d..63016bbae115 100644 --- a/drivers/net/ethernet/sfc/falcon/net_driver.h +++ b/drivers/net/ethernet/sfc/falcon/net_driver.h @@ -1208,7 +1208,7 @@ ef4_channel_get_tx_queue(struct ef4_channel *channel, unsigned type) static inline bool ef4_tx_queue_used(struct ef4_tx_queue *tx_queue) { - return !(tx_queue->efx->net_dev->num_tc < 2 && + return !(netdev_get_num_tc(tx_queue->efx->net_dev) < 2 && tx_queue->queue & EF4_TXQ_TYPE_HIGHPRI); } diff --git a/drivers/net/ethernet/sfc/falcon/tx.c b/drivers/net/ethernet/sfc/falcon/tx.c index e4d47d26a87a..2103b6fdf968 100644 --- a/drivers/net/ethernet/sfc/falcon/tx.c +++ b/drivers/net/ethernet/sfc/falcon/tx.c @@ -435,7 +435,7 @@ int ef4_setup_tc(struct net_device *net_dev, enum tc_setup_type type, mqprio->hw = TC_MQPRIO_HW_OFFLOAD_TCS; - if (num_tc == net_dev->num_tc) + if (num_tc == netdev_get_num_tc(net_dev)) return 0; for (tc = 0; tc < num_tc; tc++) { @@ -447,7 +447,7 @@ int ef4_setup_tc(struct net_device *net_dev, enum tc_setup_type type, WRITE_ONCE(net_dev->tc_to_txq[tc].combined, res.combined); } - if (num_tc > net_dev->num_tc) { + if (num_tc > netdev_get_num_tc(net_dev)) { /* Initialise high-priority queues as necessary */ ef4_for_each_channel(channel, efx) { ef4_for_each_possible_channel_tx_queue(tx_queue, @@ -466,7 +466,7 @@ int ef4_setup_tc(struct net_device *net_dev, enum tc_setup_type type, } } else { /* Reduce number of classes before number of queues */ - net_dev->num_tc = num_tc; + WRITE_ONCE(net_dev->num_tc, num_tc); } rc = netif_set_real_num_tx_queues(net_dev, @@ -481,7 +481,7 @@ int ef4_setup_tc(struct net_device *net_dev, enum tc_setup_type type, * it to ef4_fini_channels(). */ - net_dev->num_tc = num_tc; + WRITE_ONCE(net_dev->num_tc, num_tc); return 0; } diff --git a/drivers/net/ethernet/sfc/siena/tx.c b/drivers/net/ethernet/sfc/siena/tx.c index 1ce98f8fdaf8..67c77d67d984 100644 --- a/drivers/net/ethernet/sfc/siena/tx.c +++ b/drivers/net/ethernet/sfc/siena/tx.c @@ -376,7 +376,7 @@ int efx_siena_setup_tc(struct net_device *net_dev, enum tc_setup_type type, mqprio->hw = TC_MQPRIO_HW_OFFLOAD_TCS; - if (num_tc == net_dev->num_tc) + if (num_tc == netdev_get_num_tc(net_dev)) return 0; for (tc = 0; tc < num_tc; tc++) { @@ -388,7 +388,7 @@ int efx_siena_setup_tc(struct net_device *net_dev, enum tc_setup_type type, WRITE_ONCE(net_dev->tc_to_txq[tc].combined, res.combined); } - net_dev->num_tc = num_tc; + WRITE_ONCE(net_dev->num_tc, num_tc); return netif_set_real_num_tx_queues(net_dev, max_t(int, num_tc, 1) * diff --git a/drivers/net/ethernet/ti/cpsw_priv.c b/drivers/net/ethernet/ti/cpsw_priv.c index 1f6f374551cb..0580a7885d33 100644 --- a/drivers/net/ethernet/ti/cpsw_priv.c +++ b/drivers/net/ethernet/ti/cpsw_priv.c @@ -949,7 +949,7 @@ static int cpsw_set_cbs(struct net_device *ndev, * limited first and for compliance with CPDMA rate limited channels * that also used in bacward order. FIFO0 cannot be rate limited. */ - fifo = cpsw_tc_to_fifo(tc, ndev->num_tc); + fifo = cpsw_tc_to_fifo(tc, netdev_get_num_tc(ndev)); if (!fifo) { dev_err(priv->dev, "Last tc%d can't be rate limited", tc); return -EINVAL; diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 9d22e6b0df60..fd1916261072 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -2678,7 +2678,7 @@ int netdev_get_prio_tc_map(const struct net_device *dev, u32 prio) static inline int netdev_set_prio_tc_map(struct net_device *dev, u8 prio, u8 tc) { - if (tc >= dev->num_tc) + if (tc >= READ_ONCE(dev->num_tc)) return -EINVAL; dev->prio_tc_map[prio & TC_BITMASK] = tc & TC_BITMASK; @@ -2691,9 +2691,9 @@ int netdev_set_tc_queue(struct net_device *dev, u8 tc, u16 count, u16 offset); int netdev_set_num_tc(struct net_device *dev, u8 num_tc); static inline -int netdev_get_num_tc(struct net_device *dev) +int netdev_get_num_tc(const struct net_device *dev) { - return dev->num_tc; + return READ_ONCE(dev->num_tc); } static inline void net_prefetch(void *p) @@ -2720,7 +2720,7 @@ int netdev_bind_sb_channel_queue(struct net_device *dev, int netdev_set_sb_channel(struct net_device *dev, u16 channel); static inline int netdev_get_sb_channel(struct net_device *dev) { - return max_t(int, -dev->num_tc, 0); + return max_t(int, -READ_ONCE(dev->num_tc), 0); } static inline diff --git a/net/core/dev.c b/net/core/dev.c index 694a5ab773f7..9fc6eeb4d066 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -2660,7 +2660,7 @@ static void netif_setup_tc(struct net_device *dev, unsigned int txq) /* If TC0 is invalidated disable TC mapping */ if (res.offset + res.count > txq) { netdev_warn(dev, "Number of in use tx queues changed invalidating tc mappings. Priority traffic classification disabled!\n"); - dev->num_tc = 0; + WRITE_ONCE(dev->num_tc, 0); return; } @@ -2679,7 +2679,7 @@ static void netif_setup_tc(struct net_device *dev, unsigned int txq) int netdev_txq_to_tc(struct net_device *dev, unsigned int txq) { - if (dev->num_tc) { + if (READ_ONCE(dev->num_tc)) { struct netdev_tc_txq *tc = &dev->tc_to_txq[0]; int i; @@ -2881,18 +2881,19 @@ int __netif_set_xps_queue(struct net_device *dev, const unsigned long *mask, u16 index, enum xps_map_type type) { struct xps_dev_maps *dev_maps, *new_dev_maps = NULL, *old_dev_maps = NULL; + int maps_sz, num_tc = 1, tc = 0, dev_num_tc; const unsigned long *online_mask = NULL; bool active = false, copy = false; int i, j, tci, numa_node_id = -2; - int maps_sz, num_tc = 1, tc = 0; struct xps_map *map, *new_map; unsigned int nr_ids; WARN_ON_ONCE(index >= dev->num_tx_queues); - if (dev->num_tc) { + dev_num_tc = READ_ONCE(dev->num_tc); + if (dev_num_tc) { /* Do not allow XPS on subordinate device directly */ - num_tc = dev->num_tc; + num_tc = dev_num_tc; if (num_tc < 0) return -EINVAL; @@ -3116,7 +3117,7 @@ void netdev_reset_tc(struct net_device *dev) netdev_unbind_all_sb_channels(dev); /* Reset TC configuration of device */ - dev->num_tc = 0; + WRITE_ONCE(dev->num_tc, 0); for (i = 0; i < TC_MAX_QUEUE; i++) WRITE_ONCE(dev->tc_to_txq[i].combined, 0); memset(dev->prio_tc_map, 0, sizeof(dev->prio_tc_map)); @@ -3130,7 +3131,7 @@ int netdev_set_tc_queue(struct net_device *dev, u8 tc, u16 count, u16 offset) .offset = offset, }; - if (tc >= dev->num_tc) + if (tc >= READ_ONCE(dev->num_tc)) return -EINVAL; #ifdef CONFIG_XPS @@ -3151,7 +3152,7 @@ int netdev_set_num_tc(struct net_device *dev, u8 num_tc) #endif netdev_unbind_all_sb_channels(dev); - dev->num_tc = num_tc; + WRITE_ONCE(dev->num_tc, num_tc); return 0; } EXPORT_SYMBOL(netdev_set_num_tc); @@ -3181,7 +3182,7 @@ int netdev_bind_sb_channel_queue(struct net_device *dev, u8 tc, u16 count, u16 offset) { /* Make certain the sb_dev and dev are already configured */ - if (sb_dev->num_tc >= 0 || tc >= dev->num_tc) + if (READ_ONCE(sb_dev->num_tc) >= 0 || tc >= READ_ONCE(dev->num_tc)) return -EINVAL; /* We cannot hand out queues we don't have */ @@ -3220,7 +3221,7 @@ int netdev_set_sb_channel(struct net_device *dev, u16 channel) if (channel > S16_MAX) return -EINVAL; - dev->num_tc = -channel; + WRITE_ONCE(dev->num_tc, -channel); return 0; } @@ -3249,7 +3250,7 @@ int netif_set_real_num_tx_queues(struct net_device *dev, unsigned int txq) if (rc) return rc; - if (dev->num_tc) + if (READ_ONCE(dev->num_tc)) netif_setup_tc(dev, txq); net_shaper_set_real_num_tx_queues(dev, txq); @@ -3558,7 +3559,7 @@ static u16 skb_tx_hash(const struct net_device *dev, u16 qoffset = 0; u16 qcount = dev->real_num_tx_queues; - if (dev->num_tc) { + if (READ_ONCE(dev->num_tc)) { u8 tc = netdev_get_prio_tc_map(dev, skb->priority); struct netdev_tc_txq res; diff --git a/net/core/net-sysfs.c b/net/core/net-sysfs.c index 25546deacec8..352173df7578 100644 --- a/net/core/net-sysfs.c +++ b/net/core/net-sysfs.c @@ -1432,7 +1432,7 @@ static ssize_t traffic_class_show(struct kobject *kobj, struct attribute *attr, /* If queue belongs to subordinate dev use its TC mapping */ dev = netdev_get_tx_queue(dev, index)->sb_dev ? : dev; - num_tc = dev->num_tc; + num_tc = READ_ONCE(dev->num_tc); tc = netdev_txq_to_tc(dev, index); rtnl_unlock(); diff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c index 7d5fe93a4c12..18fcb4e78456 100644 --- a/net/sched/sch_taprio.c +++ b/net/sched/sch_taprio.c @@ -1185,7 +1185,7 @@ static int taprio_parse_mqprio_opt(struct net_device *dev, bool allow_overlapping_txqs = TXTIME_ASSIST_IS_ENABLED(taprio_flags); if (!qopt) { - if (!dev->num_tc) { + if (!netdev_get_num_tc(dev)) { NL_SET_ERR_MSG(extack, "'mqprio' configuration is necessary"); return -EINVAL; } @@ -1439,9 +1439,10 @@ static void taprio_offload_config_changed(struct taprio_sched *q) static u32 tc_map_to_queue_mask(struct net_device *dev, u32 tc_mask) { + int num_tc = netdev_get_num_tc(dev); u32 i, queue_mask = 0; - for (i = 0; i < dev->num_tc; i++) { + for (i = 0; i < num_tc; i++) { struct netdev_tc_txq res; if (!(tc_mask & BIT(i))) @@ -1799,7 +1800,7 @@ static int taprio_mqprio_cmp(const struct net_device *dev, { int i; - if (!mqprio || mqprio->num_tc != dev->num_tc) + if (!mqprio || mqprio->num_tc != netdev_get_num_tc(dev)) return -1; for (i = 0; i < mqprio->num_tc; i++) { From 51b0aaafd9ee85adfa7623d6dca37c71e33777e8 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 12 Aug 2026 08:54:40 +0000 Subject: [PATCH 1320/1433] net: add READ_ONCE()/WRITE_ONCE() annotations for dev->prio_tc_map Concurrent fast-path readers access dev->prio_tc_map (e.g. via skb_tx_hash(), netdev_get_prio_tc_map(), and qdiscs) while writers update entries in dev->prio_tc_map or reset/clear the map via netdev_reset_tc() and netdev_unbind_sb_channel(). Furthermore, memset() in netdev_reset_tc() and netdev_unbind_sb_channel() provides no guarantee of performing atomic word/byte stores. Add READ_ONCE() and WRITE_ONCE() annotations to netdev_get_prio_tc_map() and netdev_set_prio_tc_map(), replace memset() in dev.c with explicit WRITE_ONCE() loops, and update direct array accesses in qdiscs to use netdev_get_prio_tc_map(). Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260812085440.3917924-4-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/linux/netdevice.h | 4 ++-- net/core/dev.c | 6 ++++-- net/sched/sch_mqprio_lib.c | 3 ++- net/sched/sch_taprio.c | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index fd1916261072..87cafc932e9e 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -2672,7 +2672,7 @@ static inline bool netif_elide_gro(const struct net_device *dev) static inline int netdev_get_prio_tc_map(const struct net_device *dev, u32 prio) { - return dev->prio_tc_map[prio & TC_BITMASK]; + return READ_ONCE(dev->prio_tc_map[prio & TC_BITMASK]); } static inline @@ -2681,7 +2681,7 @@ int netdev_set_prio_tc_map(struct net_device *dev, u8 prio, u8 tc) if (tc >= READ_ONCE(dev->num_tc)) return -EINVAL; - dev->prio_tc_map[prio & TC_BITMASK] = tc & TC_BITMASK; + WRITE_ONCE(dev->prio_tc_map[prio & TC_BITMASK], tc & TC_BITMASK); return 0; } diff --git a/net/core/dev.c b/net/core/dev.c index 9fc6eeb4d066..e63773beb39b 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -3120,7 +3120,8 @@ void netdev_reset_tc(struct net_device *dev) WRITE_ONCE(dev->num_tc, 0); for (i = 0; i < TC_MAX_QUEUE; i++) WRITE_ONCE(dev->tc_to_txq[i].combined, 0); - memset(dev->prio_tc_map, 0, sizeof(dev->prio_tc_map)); + for (i = 0; i <= TC_BITMASK; i++) + WRITE_ONCE(dev->prio_tc_map[i], 0); } EXPORT_SYMBOL(netdev_reset_tc); @@ -3168,7 +3169,8 @@ void netdev_unbind_sb_channel(struct net_device *dev, #endif for (i = 0; i < TC_MAX_QUEUE; i++) WRITE_ONCE(sb_dev->tc_to_txq[i].combined, 0); - memset(sb_dev->prio_tc_map, 0, sizeof(sb_dev->prio_tc_map)); + for (i = 0; i <= TC_BITMASK; i++) + WRITE_ONCE(sb_dev->prio_tc_map[i], 0); while (txq-- != &dev->_tx[0]) { if (txq->sb_dev == sb_dev) diff --git a/net/sched/sch_mqprio_lib.c b/net/sched/sch_mqprio_lib.c index b60e130c7078..888935e34d43 100644 --- a/net/sched/sch_mqprio_lib.c +++ b/net/sched/sch_mqprio_lib.c @@ -105,7 +105,8 @@ void mqprio_qopt_reconstruct(struct net_device *dev, struct tc_mqprio_qopt *qopt int tc, num_tc = netdev_get_num_tc(dev); qopt->num_tc = num_tc; - memcpy(qopt->prio_tc_map, dev->prio_tc_map, sizeof(qopt->prio_tc_map)); + for (tc = 0; tc <= TC_BITMASK; tc++) + qopt->prio_tc_map[tc] = netdev_get_prio_tc_map(dev, tc); for (tc = 0; tc < num_tc; tc++) { struct netdev_tc_txq res; diff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c index 18fcb4e78456..39ac5b97aa3a 100644 --- a/net/sched/sch_taprio.c +++ b/net/sched/sch_taprio.c @@ -1813,7 +1813,7 @@ static int taprio_mqprio_cmp(const struct net_device *dev, } for (i = 0; i <= TC_BITMASK; i++) - if (dev->prio_tc_map[i] != mqprio->prio_tc_map[i]) + if (netdev_get_prio_tc_map(dev, i) != mqprio->prio_tc_map[i]) return -1; return 0; From 3f33a2d2ea928d6e657dd11e2ac8a3d01785f5e2 Mon Sep 17 00:00:00 2001 From: Zhixing Chen Date: Thu, 13 Aug 2026 18:07:11 +0800 Subject: [PATCH 1321/1433] r8169: keep LED device name valid after setup rtl8168_setup_ldev() and rtl8125_setup_led_ldev() build the LED device name in a stack buffer and assign it to led_cdev->name. The LED class device registration path reads led_cdev->name after it has been assigned, and struct led_classdev stores the name as part of the LED class device state. Do not keep a pointer to a setup function's stack buffer there. Store the name in struct r8169_led_classdev instead, so it remains valid for the lifetime of the LED class device. Signed-off-by: Zhixing Chen Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260813100711.14724-1-running910@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/realtek/r8169_leds.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/realtek/r8169_leds.c b/drivers/net/ethernet/realtek/r8169_leds.c index 1999e81f0bca..5a2067be3095 100644 --- a/drivers/net/ethernet/realtek/r8169_leds.c +++ b/drivers/net/ethernet/realtek/r8169_leds.c @@ -31,6 +31,7 @@ struct r8169_led_classdev { struct led_classdev led; struct net_device *ndev; int index; + char name[LED_MAX_NAME_SIZE]; }; #define lcdev_to_r8169_ldev(lcdev) container_of(lcdev, struct r8169_led_classdev, led) @@ -131,13 +132,12 @@ static void rtl8168_setup_ldev(struct r8169_led_classdev *ldev, { struct rtl8169_private *tp = netdev_priv(ndev); struct led_classdev *led_cdev = &ldev->led; - char led_name[LED_MAX_NAME_SIZE]; ldev->ndev = ndev; ldev->index = index; - r8169_get_led_name(tp, index, led_name, LED_MAX_NAME_SIZE); - led_cdev->name = led_name; + r8169_get_led_name(tp, index, ldev->name, sizeof(ldev->name)); + led_cdev->name = ldev->name; led_cdev->hw_control_trigger = "netdev"; led_cdev->flags |= LED_RETAIN_AT_SHUTDOWN; led_cdev->hw_control_is_supported = rtl8168_led_hw_control_is_supported; @@ -230,13 +230,12 @@ static void rtl8125_setup_led_ldev(struct r8169_led_classdev *ldev, { struct rtl8169_private *tp = netdev_priv(ndev); struct led_classdev *led_cdev = &ldev->led; - char led_name[LED_MAX_NAME_SIZE]; ldev->ndev = ndev; ldev->index = index; - r8169_get_led_name(tp, index, led_name, LED_MAX_NAME_SIZE); - led_cdev->name = led_name; + r8169_get_led_name(tp, index, ldev->name, sizeof(ldev->name)); + led_cdev->name = ldev->name; led_cdev->hw_control_trigger = "netdev"; led_cdev->flags |= LED_RETAIN_AT_SHUTDOWN; led_cdev->hw_control_is_supported = rtl8125_led_hw_control_is_supported; From 2b49709667de8d6a5fbf4c4b7585aa08e9db5582 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 13 Aug 2026 12:32:47 -0700 Subject: [PATCH 1322/1433] eth: bnxt: decrease indent in bnxt_init_int_mode() Handle the IRQ table allocation failure right away instead of wrapping the rest of the function in an if. Purely to make upcoming changes more readable. While refactoring, drop the init of rc which is not necessary. No functional changes. Reviewed-by: Pavan Chebbi Link: https://patch.msgid.link/20260813193248.2578626-2-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 36 +++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 1e4944f3e606..d3531cd283a5 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -11592,7 +11592,7 @@ static int bnxt_get_num_msix(struct bnxt *bp) static int bnxt_init_int_mode(struct bnxt *bp) { - int i, total_vecs, max, rc = 0, min = 1, ulp_msix, tx_cp, tbl_size; + int i, total_vecs, max, rc, min = 1, ulp_msix, tx_cp, tbl_size; total_vecs = bnxt_get_num_msix(bp); max = bnxt_get_max_func_irqs(bp); @@ -11617,26 +11617,26 @@ static int bnxt_init_int_mode(struct bnxt *bp) if (pci_msix_can_alloc_dyn(bp->pdev)) tbl_size = max; bp->irq_tbl = kzalloc_objs(*bp->irq_tbl, tbl_size); - if (bp->irq_tbl) { - for (i = 0; i < total_vecs; i++) - bp->irq_tbl[i].vector = pci_irq_vector(bp->pdev, i); - - bp->total_irqs = total_vecs; - /* Trim rings based upon num of vectors allocated */ - rc = bnxt_trim_rings(bp, &bp->rx_nr_rings, &bp->tx_nr_rings, - total_vecs - ulp_msix, min == 1); - if (rc) - goto msix_setup_exit; - - tx_cp = bnxt_num_tx_to_cp(bp, bp->tx_nr_rings); - bp->cp_nr_rings = (min == 1) ? - max_t(int, tx_cp, bp->rx_nr_rings) : - tx_cp + bp->rx_nr_rings; - - } else { + if (!bp->irq_tbl) { rc = -ENOMEM; goto msix_setup_exit; } + + for (i = 0; i < total_vecs; i++) + bp->irq_tbl[i].vector = pci_irq_vector(bp->pdev, i); + + bp->total_irqs = total_vecs; + /* Trim rings based upon num of vectors allocated */ + rc = bnxt_trim_rings(bp, &bp->rx_nr_rings, &bp->tx_nr_rings, + total_vecs - ulp_msix, min == 1); + if (rc) + goto msix_setup_exit; + + tx_cp = bnxt_num_tx_to_cp(bp, bp->tx_nr_rings); + bp->cp_nr_rings = (min == 1) ? + max_t(int, tx_cp, bp->rx_nr_rings) : + tx_cp + bp->rx_nr_rings; + return 0; msix_setup_exit: From fb05026490430d6370506f50b8dbab0e1da79c4f Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 13 Aug 2026 12:32:48 -0700 Subject: [PATCH 1323/1433] eth: bnxt: preserve IRQ affinity across IRQ reallocation Reconfiguring the rings frees the MSI-X vectors and allocates them again. The IRQ descriptors go away with them, so the affinity user space set is silently replaced by the driver's default NUMA spread. This is painful to deal with for user space as seemingly arbitrary NIC configuration changes lead to loss of configuration. In NIPA (netdev CI) this results in the toeplitz test reporting: Exception| net.lib.py.ksft.KsftFailEx: IRQ170 is not mapped to a single core: 0-31 if the test run after another test which reconfigured the device. We configure the IRQ mapping at boot, but if the driver is not preserving the config - it gets lost. Record the affinity in the notifier and apply it when the IRQs are requested again. The notifier has to be registered unconditionally now, so far it was only installed when TPH was enabled. Drivers which let the core manage the affinity (idpf, ice, iavf via netif_set_affinity_auto()) work exactly like this, napi_restore_config() reapplies napi_config.affinity_mask on every napi_enable(). Note that the affinity is supposed to follow the NAPI / queue, same as the napi_config behavior in drivers mentioned above. If the user changes the affinity when the device is down - we will override it on up. That's expected, the IRQs are not associated with queues when device is down (no name, no entry in /proc/interrupts, no entry in netdev netlink). map_idx is ulp_msix + i, so the slot shifts whenever RoCE takes or releases vectors and the mask would end up on a different ring. Key using the completion ring id, which maps to the NAPI instance. Note2: this restores the side effect fcf42409c6e1 ("bnxt_en: use irq_update_affinity_hint()") removed, but not the problem it was fixing. The complaint there was that reopening the device resets the affinity and can move an IRQ onto a CPU irqbalance was told to stay away from. We now replay what user space or irqbalance last asked for, the driver's own placement is only used for a ring nobody has configured. Note3: the combined irq_set_affinity_and_hint() looks like it may hide the failure from __irq_set_affinity(), but let's assume the IRQ maintainers know what their doing - either this can't happen or is intentional. Reviewed-by: Pavan Chebbi Link: https://patch.msgid.link/20260813193248.2578626-3-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 95 +++++++++++++++++------ drivers/net/ethernet/broadcom/bnxt/bnxt.h | 11 ++- 2 files changed, 80 insertions(+), 26 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index d3531cd283a5..35795374a8dd 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -11792,6 +11792,9 @@ static void bnxt_irq_affinity_notify(struct irq_affinity_notify *notify, irq = container_of(notify, struct bnxt_irq, affinity_notify); + cpumask_copy(irq->bp->ring_cpu_mask[irq->ring_nr], mask); + set_bit(irq->ring_nr, irq->bp->ring_affinity_set); + #ifdef CONFIG_RFS_ACCEL if (irq->bp->dev->rx_cpu_rmap && irq->ring_nr < irq->bp->rx_nr_rings) { int err; @@ -11807,8 +11810,6 @@ static void bnxt_irq_affinity_notify(struct irq_affinity_notify *notify, if (!irq->bp->tph_mode) return; - cpumask_copy(irq->cpu_mask, mask); - if (irq->ring_nr >= irq->bp->rx_nr_rings) return; @@ -11850,10 +11851,6 @@ static void bnxt_register_irq_notifier(struct bnxt *bp, struct bnxt_irq *irq) irq->bp = bp; - /* Nothing to do if TPH is not enabled */ - if (!bp->tph_mode) - return; - /* Register IRQ affinity notifier */ notify = &irq->affinity_notify; notify->irq = irq->vector; @@ -11863,6 +11860,41 @@ static void bnxt_register_irq_notifier(struct bnxt *bp, struct bnxt_irq *irq) irq_set_affinity_notifier(irq->vector, notify); } +static int bnxt_alloc_ring_cpu_masks(struct bnxt *bp) +{ + int i; + + bp->ring_cpu_mask = kzalloc_objs(*bp->ring_cpu_mask, bp->max_irqs); + if (!bp->ring_cpu_mask) + return -ENOMEM; + + bp->ring_affinity_set = bitmap_zalloc(bp->max_irqs, GFP_KERNEL); + if (!bp->ring_affinity_set) + return -ENOMEM; + + for (i = 0; i < bp->max_irqs; i++) + if (!zalloc_cpumask_var(&bp->ring_cpu_mask[i], GFP_KERNEL)) + return -ENOMEM; + + return 0; +} + +static void bnxt_free_ring_cpu_masks(struct bnxt *bp) +{ + int i; + + if (!bp->ring_cpu_mask) + return; + + for (i = 0; i < bp->max_irqs; i++) + free_cpumask_var(bp->ring_cpu_mask[i]); + + bitmap_free(bp->ring_affinity_set); + bp->ring_affinity_set = NULL; + kfree(bp->ring_cpu_mask); + bp->ring_cpu_mask = NULL; +} + static void bnxt_free_irq(struct bnxt *bp) { struct bnxt_irq *irq; @@ -11877,13 +11909,7 @@ static void bnxt_free_irq(struct bnxt *bp) irq = &bp->irq_tbl[map_idx]; if (irq->requested) { bnxt_release_irq_notifier(irq); - - if (irq->have_cpumask) { - irq_update_affinity_hint(irq->vector, NULL); - free_cpumask_var(irq->cpu_mask); - irq->have_cpumask = 0; - } - + irq_update_affinity_hint(irq->vector, NULL); free_irq(irq->vector, bp->bnapi[i]); } @@ -11925,9 +11951,9 @@ static int bnxt_request_irq(struct bnxt *bp) bp->tph_mode = PCI_TPH_ST_IV_MODE; for (i = 0, j = 0; i < bp->cp_nr_rings; i++) { + struct cpumask *cpu_mask = bp->ring_cpu_mask[i]; int map_idx = bnxt_cp_num_to_irq_num(bp, i); struct bnxt_irq *irq = &bp->irq_tbl[map_idx]; - unsigned int cpu_num; u16 tag; if (IS_ENABLED(CONFIG_RFS_ACCEL) && @@ -11946,19 +11972,24 @@ static int bnxt_request_irq(struct bnxt *bp) netif_napi_set_irq_locked(&bp->bnapi[i]->napi, irq->vector); irq->requested = 1; - - if (!zalloc_cpumask_var(&irq->cpu_mask, GFP_KERNEL)) - continue; - - irq->have_cpumask = 1; irq->msix_nr = map_idx; irq->ring_nr = i; - cpu_num = cpumask_local_spread(i, numa_node); - cpumask_set_cpu(cpu_num, irq->cpu_mask); + + /* Reuse the mask recorded before the IRQs were freed. Nothing + * was recorded yet on the very first request, and the mask + * may have gone stale if the CPUs went offline in between. + */ + if (!test_bit(i, bp->ring_affinity_set) || + !cpumask_intersects(cpu_mask, cpu_online_mask)) { + clear_bit(i, bp->ring_affinity_set); + cpumask_clear(cpu_mask); + cpumask_set_cpu(cpumask_local_spread(i, numa_node), + cpu_mask); + } /* Init ST table entry if we can get the mapping */ if (!pcie_tph_get_cpu_st(bp->pdev, TPH_MEM_TYPE_VM, - cpu_num, &tag)) { + cpumask_first(cpu_mask), &tag)) { pcie_tph_set_st_entry(bp->pdev, irq->msix_nr, tag); irq->tag = tag; irq->new_tag = tag; @@ -11966,10 +11997,19 @@ static int bnxt_request_irq(struct bnxt *bp) bnxt_register_irq_notifier(bp, irq); - rc = irq_update_affinity_hint(irq->vector, irq->cpu_mask); + /* Only put the IRQ back where it was configured to be, our own + * placement is just a hint, the core spreads within + * irq_default_affinity which we know nothing about. + * Set after installing the notifier, if we race with the user + * it's better to overwrite than miss the notification. + */ + if (test_bit(i, bp->ring_affinity_set)) + rc = irq_set_affinity_and_hint(irq->vector, cpu_mask); + else + rc = irq_update_affinity_hint(irq->vector, cpu_mask); if (rc) { netdev_warn(bp->dev, - "Update affinity hint failed, IRQ = %d\n", + "Setting IRQ affinity failed, IRQ = %d\n", irq->vector); break; } @@ -16610,6 +16650,7 @@ static void bnxt_remove_one(struct pci_dev *pdev) bnxt_shutdown_tc(bp); bnxt_clear_int_mode(bp); + bnxt_free_ring_cpu_masks(bp); bnxt_hwrm_func_drv_unrgtr(bp); bnxt_free_hwrm_resources(bp); bnxt_hwmon_uninit(bp); @@ -17048,6 +17089,11 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) bp->msg_enable = BNXT_DEF_MSG_ENABLE; bnxt_set_max_func_irqs(bp, max_irqs); + bp->max_irqs = max_irqs; + rc = bnxt_alloc_ring_cpu_masks(bp); + if (rc) + goto init_err_free; + if (bnxt_vf_pciid(bp->board_idx)) bp->flags |= BNXT_FLAG_VF; @@ -17297,6 +17343,7 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) bp->rss_indir_tbl = NULL; init_err_free: + bnxt_free_ring_cpu_masks(bp); free_netdev(dev); return rc; } diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.h b/drivers/net/ethernet/broadcom/bnxt/bnxt.h index dc8ec5e5733e..ab894f8addef 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.h +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.h @@ -1261,9 +1261,7 @@ struct bnxt_irq { irq_handler_t handler; unsigned int vector; u8 requested:1; - u8 have_cpumask:1; char name[IFNAMSIZ + BNXT_IRQ_NAME_EXTRA]; - cpumask_var_t cpu_mask; struct bnxt *bp; int msix_nr; @@ -2482,6 +2480,15 @@ struct bnxt { pci_channel_offline((bp)->pdev)) struct bnxt_irq *irq_tbl; + /* IRQ affinity, indexed by completion ring. Kept across IRQ + * reallocation, the MSI-X vector index is not stable. + */ + cpumask_var_t *ring_cpu_mask; + /* Rings for which the mask above was configured from the outside, + * rather than being our own default placement. + */ + unsigned long *ring_affinity_set; + int max_irqs; int total_irqs; int ulp_num_msix_want; u8 mac_addr[ETH_ALEN]; From 35afa239e5f312c6202a080bffeceec505dea391 Mon Sep 17 00:00:00 2001 From: Michael Chan Date: Fri, 14 Aug 2026 14:56:55 -0700 Subject: [PATCH 1324/1433] bnxt_en: Add missing NETIF_F_TSO_ECN feature flag All bnxt devices support TSO packets with RFC 3168 ECN flags set. The CWR flag is replicated only on the first segment. Reviewed-by: Andy Gospodarek Signed-off-by: Michael Chan Link: https://patch.msgid.link/20260814215655.2331655-1-michael.chan@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 35795374a8dd..9377bf675981 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -17148,7 +17148,7 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) } dev->hw_features = NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | NETIF_F_SG | - NETIF_F_TSO | NETIF_F_TSO6 | + NETIF_F_TSO | NETIF_F_TSO6 | NETIF_F_TSO_ECN | NETIF_F_GSO_UDP_TUNNEL | NETIF_F_GSO_GRE | NETIF_F_GSO_IPXIP4 | NETIF_F_GSO_UDP_TUNNEL_CSUM | NETIF_F_GSO_GRE_CSUM | @@ -17161,7 +17161,7 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) dev->hw_enc_features = NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | NETIF_F_SG | - NETIF_F_TSO | NETIF_F_TSO6 | + NETIF_F_TSO | NETIF_F_TSO6 | NETIF_F_TSO_ECN | NETIF_F_GSO_UDP_TUNNEL | NETIF_F_GSO_GRE | NETIF_F_GSO_UDP_TUNNEL_CSUM | NETIF_F_GSO_GRE_CSUM | NETIF_F_GSO_IPXIP4 | NETIF_F_GSO_PARTIAL; From 2bd4177d964d6128a2b6f06674a6390c55801ae7 Mon Sep 17 00:00:00 2001 From: Satheesh Paul Date: Wed, 12 Aug 2026 11:05:22 +0530 Subject: [PATCH 1325/1433] octeontx2-af: Add mailbox to read default MCAM entry Add support for reading the default unicast MCAM rule associated with a NIX LF on non-CN20K silicon. Signed-off-by: Satheesh Paul Signed-off-by: Nitin Shetty J Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260812053523.3329305-1-nshettyj@marvell.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/marvell/octeontx2/af/mbox.h | 2 + .../ethernet/marvell/octeontx2/af/rvu_npc.c | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h index 73f743e4a83d..cece197d1074 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h +++ b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h @@ -309,6 +309,8 @@ M(NPC_MCAM_GET_DFT_RL_IDXS, 0x601e, npc_get_dft_rl_idxs, \ M(NPC_MCAM_GET_NPC_PFL_INFO, 0x601f, npc_get_pfl_info, \ msg_req, \ npc_get_pfl_info_rsp) \ +M(NPC_MCAM_READ_DEFAULT_RULE, 0x6021, npc_read_default_rule, msg_req, \ + npc_mcam_read_base_rule_rsp) \ /* NIX mbox IDs (range 0x8000 - 0xFFFF) */ \ M(NIX_LF_ALLOC, 0x8000, nix_lf_alloc, \ nix_lf_alloc_req, nix_lf_alloc_rsp) \ diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c index db27ea622f35..60922944675b 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c @@ -4303,6 +4303,43 @@ int rvu_mbox_handler_npc_set_pkind(struct rvu *rvu, struct npc_set_pkind *req, req->skip_size); } +int rvu_mbox_handler_npc_read_default_rule(struct rvu *rvu, + struct msg_req *req, + struct npc_mcam_read_base_rule_rsp *rsp) +{ + struct npc_mcam *mcam = &rvu->hw->mcam; + int index, blkaddr, nixlf, rc; + u16 pcifunc = req->hdr.pcifunc; + u8 intf, enable; + + if (is_cn20k(rvu->pdev)) + return NPC_MCAM_INVALID_REQ; + + blkaddr = rvu_get_blkaddr(rvu, BLKTYPE_NPC, 0); + if (blkaddr < 0) + return NPC_MCAM_INVALID_REQ; + + rc = nix_get_nixlf(rvu, pcifunc, &nixlf, NULL); + if (rc < 0) + return rc; + + /* Read the default ucast entry */ + mutex_lock(&mcam->lock); + index = npc_get_nixlf_mcam_index(mcam, pcifunc, nixlf, + NIXLF_UCAST_ENTRY); + if (index < 0) { + mutex_unlock(&mcam->lock); + return NIX_AF_ERR_AF_LF_INVALID; + } + + /* Read the mcam entry */ + npc_read_mcam_entry(rvu, mcam, blkaddr, index, &rsp->entry, &intf, + &enable); + mutex_unlock(&mcam->lock); + + return 0; +} + int rvu_mbox_handler_npc_read_base_steer_rule(struct rvu *rvu, struct msg_req *req, struct npc_mcam_read_base_rule_rsp *rsp) From 96bf660d4c14948293fe5d8e26d2de536f8eb1d8 Mon Sep 17 00:00:00 2001 From: Marcelo Mendes Spessoto Junior Date: Thu, 13 Aug 2026 00:07:05 -0300 Subject: [PATCH 1326/1433] selftests: net: separate ipv6_flowlabel_mgr test The ipv6_flowlabel_mgr used to be a component of a broader overall flow label test, defined in the ipv6_flowlabel.sh file. This wrapper script called tests defined on ipv6_flowlabel.c and ipv6_flowlabel_mgr.c files, using predefined parameters and enforcing the in_netns.sh helper to set network namespaces for each test env. However, the ipv6_flowlabel_mgr.c was drastically changed recently. These modifications led to the mgr tests becoming a self contained and independent test suite, enforcing netns creation by itself and not relying on the ipv6_flowlabel.sh wrapper for proper test execution anymore. Therefore, remove the mgr tests from the wrapper and update the Makefile to handle it as a standalone test program instead. Signed-off-by: Marcelo Mendes Spessoto Junior Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260813030708.37609-1-marcelomspessoto@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/Makefile | 2 +- tools/testing/selftests/net/ipv6_flowlabel.sh | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index ab890e6f79dd..0f5c178bc224 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -150,7 +150,6 @@ TEST_GEN_FILES := \ ip_local_port_range \ ipsec \ ipv6_flowlabel \ - ipv6_flowlabel_mgr \ msg_zerocopy \ nettest \ psock_fanout \ @@ -183,6 +182,7 @@ TEST_GEN_PROGS := \ epoll_busy_poll \ getsockopt_iter \ icmp_rfc4884 \ + ipv6_flowlabel_mgr \ ipv6_fragmentation \ proc_net_pktgen \ reuseaddr_conflict \ diff --git a/tools/testing/selftests/net/ipv6_flowlabel.sh b/tools/testing/selftests/net/ipv6_flowlabel.sh index 2eeda39bf64c..5d1b5464c54c 100755 --- a/tools/testing/selftests/net/ipv6_flowlabel.sh +++ b/tools/testing/selftests/net/ipv6_flowlabel.sh @@ -7,9 +7,6 @@ set -e -echo "TEST management" -./ipv6_flowlabel_mgr - echo "TEST datapath" ./in_netns.sh \ sh -c 'sysctl -q -w net.ipv6.auto_flowlabels=0 && ./ipv6_flowlabel -l 1' From cf85f810f911234a06a4ef2439e8694b93b717fc Mon Sep 17 00:00:00 2001 From: Daniel Zahka Date: Fri, 14 Aug 2026 04:44:06 -0700 Subject: [PATCH 1327/1433] net: psp: use psp_dev_is_registered() in psp_assoc_free() No functional changes. In code paths that use a psp_dev reference that wasn't obtained from the psp_devs xarray, e.g. not via psp_device_get_and_lock(), there is no guarantee that the psp_dev has not been unregistered. The check here is correct, but it doesn't match other code paths that use psp_dev_is_registered(). Commit b89769f936a8 ("net: psp: check for device unregister when creating assoc") is an example of a fix that adds a check for this after locking a psp_dev. if (psp_dev_is_registered(psd)) vs if (psd->ops) makes it clear what we are really checking for. Signed-off-by: Daniel Zahka Link: https://patch.msgid.link/20260814-psp-dev-is-reg-v1-1-5029e1f1eb01@gmail.com Signed-off-by: Jakub Kicinski --- net/psp/psp_sock.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/psp/psp_sock.c b/net/psp/psp_sock.c index 07dc4cf741f3..1a2a6b7516b0 100644 --- a/net/psp/psp_sock.c +++ b/net/psp/psp_sock.c @@ -96,7 +96,7 @@ static void psp_assoc_free(struct work_struct *work) struct psp_dev *psd = pas->psd; mutex_lock(&psd->lock); - if (psd->ops) + if (psp_dev_is_registered(psd)) psp_dev_tx_key_del(psd, pas); mutex_unlock(&psd->lock); psp_dev_put(psd); From 97a1e65df7eef996693d604ae90aaf2530578912 Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Thu, 13 Aug 2026 12:34:16 -0700 Subject: [PATCH 1328/1433] psp: use unrcu_pointer() for the cmpxchg() on netdev psp_dev sparse reports: net/psp/psp_nl.c:513:13: sparse: sparse: cast removes address space '__rcu' of expression cmpxchg() returns typeof(*ptr) and its internal casts strip the __rcu annotation. Wrap it in unrcu_pointer(), the documented way to use an __rcu pointer with xchg() and friends. This was introduced by commit 06c2dce2d0f6 ("psp: add new netlink cmd for dev-assoc and dev-disassoc"). No functional change intended. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202608080910.l9KvOH7O-lkp@intel.com/ Signed-off-by: Wei Wang Link: https://patch.msgid.link/20260813193416.1544518-1-weibunny.kernel@gmail.com Signed-off-by: Jakub Kicinski --- net/psp/psp_nl.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/psp/psp_nl.c b/net/psp/psp_nl.c index 43b066353c65..f91665748dde 100644 --- a/net/psp/psp_nl.c +++ b/net/psp/psp_nl.c @@ -533,7 +533,8 @@ int psp_nl_dev_assoc_doit(struct sk_buff *skb, struct genl_info *info) } /* Check if device is already associated with a PSP device */ - if (cmpxchg(&assoc_dev->psp_dev, NULL, RCU_INITIALIZER(psd))) { + if (unrcu_pointer(cmpxchg(&assoc_dev->psp_dev, NULL, + RCU_INITIALIZER(psd)))) { NL_SET_ERR_MSG(info->extack, "Device already associated with a PSP device"); err = -EBUSY; From 4b92a3710d4a1d850ed6421c773e735cdca69b07 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Wed, 12 Aug 2026 08:07:30 +0200 Subject: [PATCH 1329/1433] octeontx2-af: initialize lmac_bmap in rvu_mcs_set_lmac_bmap() rvu_mcs_set_lmac_bmap() declares lmac_bmap without initializing it and only sets bits for valid lmacs with set_bit(), which ORs into the word without clearing it first. Bits for invalid or skipped ports keep whatever was on the stack, and the garbage is stored into mcs->hw->lmac_bmap. Initialize lmac_bmap to 0 so only valid lmacs are marked. Found with Clang's -Wconditional-uninitialized. Fixes: ca7f49ff8846 ("octeontx2-af: cn10k: Introduce driver for macsec block.") Signed-off-by: Karl Mehltretter Reviewed-by: Ratheesh Kannoth Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260812060730.6181-1-kmehltretter@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/marvell/octeontx2/af/mcs_rvu_if.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mcs_rvu_if.c b/drivers/net/ethernet/marvell/octeontx2/af/mcs_rvu_if.c index d98b49f47970..fce22e314cac 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/mcs_rvu_if.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/mcs_rvu_if.c @@ -856,7 +856,7 @@ int rvu_mbox_handler_mcs_ctrl_pkt_rule_write(struct rvu *rvu, static void rvu_mcs_set_lmac_bmap(struct rvu *rvu) { struct mcs *mcs = mcs_get_pdata(0); - unsigned long lmac_bmap; + unsigned long lmac_bmap = 0; int cgx, lmac, port; for (port = 0; port < mcs->hw->lmac_cnt; port++) { From 2f1463554d0561a2fead81e3888604e5c1125e29 Mon Sep 17 00:00:00 2001 From: Fan Ye Date: Tue, 11 Aug 2026 13:20:49 +0000 Subject: [PATCH 1330/1433] net: thunderbolt: Release the Rx HopID that was handed out on mismatch tb_xdomain_alloc_in_hopid() passes the wanted HopID to ida_alloc_range() as the lower bound, so a taken id is not an error there: the allocator returns the next free one above it. tbnet_connected_work() asks for the peer's transmit path, treats any other id as a failure and returns without releasing what it got, so that allocation stays live for the rest of the XDomain connection with nothing left holding a reference to it. Release the id when it is not the one we asked for, the same way the error unwind at the end of the function releases the expected one. Fixes: 180b0689425c ("thunderbolt: Allow multiple DMA tunnels over a single XDomain connection") Cc: stable@vger.kernel.org Signed-off-by: Fan Ye Acked-by: Mika Westerberg Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260811-b4-tbnet-hopid-v3-1-9e75d1b51331@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/thunderbolt/main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c index 98893732bc6e..e5199a87ea7a 100644 --- a/drivers/net/thunderbolt/main.c +++ b/drivers/net/thunderbolt/main.c @@ -647,6 +647,8 @@ static void tbnet_connected_work(struct work_struct *work) ret = tb_xdomain_alloc_in_hopid(net->xd, net->remote_transmit_path); if (ret != net->remote_transmit_path) { netdev_err(net->dev, "failed to allocate Rx HopID\n"); + if (ret >= 0) + tb_xdomain_release_in_hopid(net->xd, ret); return; } From 3c8b26ebf525ba5960510f48c6e9936a79ebe76f Mon Sep 17 00:00:00 2001 From: Fan Ye Date: Tue, 11 Aug 2026 13:20:50 +0000 Subject: [PATCH 1331/1433] net: thunderbolt: Mark the connection down when bringing it up fails Every failure path in tbnet_connected_work() undoes its own work and returns without clearing login_sent, so the connection still looks established. The next tbnet_tear_down() therefore takes its main branch and repeats a teardown that already happened: it stops rings that are already stopped, which is a dev_WARN() and fatal under panic_on_warn, and it releases net->remote_transmit_path even on the HopID mismatch path, where this connection never owned that id, silently freeing one that someone else is still using. Clear login_sent on those paths. That is enough for tbnet_tear_down() to leave the unwound state alone, and login_received has to stay set: it records that the peer has logged in and carries the transmit path it gave us, which nothing on this side can make the peer send again. Two things change beyond keeping the teardown out of the way: the logout request in that block is no longer sent, and the peer's next login request now re-queues our login work rather than connected_work, giving the connection a fresh login instead of a retry on stale state. Fixes: e69b6c02b4c3 ("net: Add support for networking over Thunderbolt cable") Cc: # 5.13+ Signed-off-by: Fan Ye Acked-by: Mika Westerberg Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260811-b4-tbnet-hopid-v3-2-9e75d1b51331@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/thunderbolt/main.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c index e5199a87ea7a..2a1728621887 100644 --- a/drivers/net/thunderbolt/main.c +++ b/drivers/net/thunderbolt/main.c @@ -626,6 +626,14 @@ static int tbnet_alloc_tx_buffers(struct tbnet *net) return 0; } +static void tbnet_connect_failed(struct tbnet *net) +{ + /* Leave login_received set: only the peer can make it true again. */ + mutex_lock(&net->connection_lock); + net->login_sent = false; + mutex_unlock(&net->connection_lock); +} + static void tbnet_connected_work(struct work_struct *work) { struct tbnet *net = container_of(work, typeof(*net), connected_work); @@ -649,6 +657,7 @@ static void tbnet_connected_work(struct work_struct *work) netdev_err(net->dev, "failed to allocate Rx HopID\n"); if (ret >= 0) tb_xdomain_release_in_hopid(net->xd, ret); + tbnet_connect_failed(net); return; } @@ -693,6 +702,7 @@ static void tbnet_connected_work(struct work_struct *work) tb_ring_stop(net->rx_ring.ring); tb_ring_stop(net->tx_ring.ring); tb_xdomain_release_in_hopid(net->xd, net->remote_transmit_path); + tbnet_connect_failed(net); } static void tbnet_login_work(struct work_struct *work) From 0c1f9020d2ada55aed8a2e055903ad9cd5d49dab Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Wed, 12 Aug 2026 11:38:30 -0500 Subject: [PATCH 1332/1433] net: stmmac: use dma_addr_t for DMA addresses In jumbo_frm() (implemented in both "chain_mode.c" and "ring_mode.c"), an unsigned integer local variable is used to hold the value returned by dma_map_single(). On systems where a dma_addr_t is 64 bits, the subsequent dma_mapping_error() check of the returned value operates only on the low 32 bits (whose high bit won't be sign-extended). In this case, dma_mapping_error() would return 0 (no error) even if there were one. Fix this in both spots by using a dma_addr_t for the local variable. Reported-by: Sashiko Link: https://lore.kernel.org/linux-devicetree/20260606010122.21A211F00899@smtp.kernel.org/ Reviewed-by: Maxime Chevallier Signed-off-by: Alex Elder Link: https://patch.msgid.link/20260812163832.271742-2-elder@riscstar.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/chain_mode.c | 3 ++- drivers/net/ethernet/stmicro/stmmac/ring_mode.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c index fc04a23342cf..ec25193d287b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c +++ b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c @@ -20,9 +20,10 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, unsigned int nopaged_len = skb_headlen(skb); struct stmmac_priv *priv = tx_q->priv_data; unsigned int entry = tx_q->cur_tx; - unsigned int bmax, buf_len, des2; + unsigned int bmax, buf_len; unsigned int i = 1, len; struct dma_desc *desc; + dma_addr_t des2; desc = tx_q->dma_tx + entry; diff --git a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c index 78fc6aa5bbe9..664d8cfb58cd 100644 --- a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c +++ b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c @@ -20,8 +20,9 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, unsigned int nopaged_len = skb_headlen(skb); struct stmmac_priv *priv = tx_q->priv_data; unsigned int entry = tx_q->cur_tx; - unsigned int bmax, len, des2; + unsigned int bmax, len; struct dma_desc *desc; + dma_addr_t des2; if (priv->extend_desc) desc = (struct dma_desc *)(tx_q->dma_etx + entry); From c11c497af866ccd11c109de0833b2cd89cddd075 Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Wed, 12 Aug 2026 11:38:31 -0500 Subject: [PATCH 1333/1433] net: stmmac: convert DMA address to lower 32 before assignment In jumbo_frm() (implemented in both "chain_mode.c" and "ring_mode.c"), there are places where a DMA descriptor is converted to little-endian byte order in assignment. The DMA descriptor could be a 64-bit value, which makes the 32-bit byte swapping operation seem a little sketchy. Explicitly extract the low-order 32 bits of the dma_addr_t value being converted into a u32 so it's crystal clear that we're doing the right thing. Suggested-by: Maxime Chevallier Signed-off-by: Alex Elder Link: https://patch.msgid.link/20260812163832.271742-3-elder@riscstar.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/chain_mode.c | 6 +++--- drivers/net/ethernet/stmicro/stmmac/ring_mode.c | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c index ec25193d287b..66025e2509e9 100644 --- a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c +++ b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c @@ -37,7 +37,7 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, des2 = dma_map_single(priv->device, skb->data, buf_len, DMA_TO_DEVICE); - desc->des2 = cpu_to_le32(des2); + desc->des2 = cpu_to_le32(lower_32_bits(des2)); if (dma_mapping_error(priv->device, des2)) return -1; tx_q->tx_skbuff_dma[entry].buf = des2; @@ -55,7 +55,7 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, des2 = dma_map_single(priv->device, (skb->data + bmax * i), bmax, DMA_TO_DEVICE); - desc->des2 = cpu_to_le32(des2); + desc->des2 = cpu_to_le32(lower_32_bits(des2)); if (dma_mapping_error(priv->device, des2)) return -1; tx_q->tx_skbuff_dma[entry].buf = des2; @@ -68,7 +68,7 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, des2 = dma_map_single(priv->device, (skb->data + bmax * i), len, DMA_TO_DEVICE); - desc->des2 = cpu_to_le32(des2); + desc->des2 = cpu_to_le32(lower_32_bits(des2)); if (dma_mapping_error(priv->device, des2)) return -1; tx_q->tx_skbuff_dma[entry].buf = des2; diff --git a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c index 664d8cfb58cd..f7949419eb9f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c +++ b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c @@ -40,7 +40,7 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, des2 = dma_map_single(priv->device, skb->data, bmax, DMA_TO_DEVICE); - desc->des2 = cpu_to_le32(des2); + desc->des2 = cpu_to_le32(lower_32_bits(des2)); if (dma_mapping_error(priv->device, des2)) return -1; @@ -48,7 +48,7 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, tx_q->tx_skbuff_dma[entry].len = bmax; tx_q->tx_skbuff_dma[entry].is_jumbo = true; - desc->des3 = cpu_to_le32(des2 + BUF_SIZE_4KiB); + desc->des3 = cpu_to_le32(lower_32_bits(des2) + BUF_SIZE_4KiB); stmmac_prepare_tx_desc(priv, desc, 1, bmax, csum, STMMAC_RING_MODE, 0, false, skb->len); tx_q->tx_skbuff[entry] = NULL; @@ -61,27 +61,27 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, des2 = dma_map_single(priv->device, skb->data + bmax, len, DMA_TO_DEVICE); - desc->des2 = cpu_to_le32(des2); + desc->des2 = cpu_to_le32(lower_32_bits(des2)); if (dma_mapping_error(priv->device, des2)) return -1; tx_q->tx_skbuff_dma[entry].buf = des2; tx_q->tx_skbuff_dma[entry].len = len; tx_q->tx_skbuff_dma[entry].is_jumbo = true; - desc->des3 = cpu_to_le32(des2 + BUF_SIZE_4KiB); + desc->des3 = cpu_to_le32(lower_32_bits(des2) + BUF_SIZE_4KiB); stmmac_prepare_tx_desc(priv, desc, 0, len, csum, STMMAC_RING_MODE, 1, !skb_is_nonlinear(skb), skb->len); } else { des2 = dma_map_single(priv->device, skb->data, nopaged_len, DMA_TO_DEVICE); - desc->des2 = cpu_to_le32(des2); + desc->des2 = cpu_to_le32(lower_32_bits(des2)); if (dma_mapping_error(priv->device, des2)) return -1; tx_q->tx_skbuff_dma[entry].buf = des2; tx_q->tx_skbuff_dma[entry].len = nopaged_len; tx_q->tx_skbuff_dma[entry].is_jumbo = true; - desc->des3 = cpu_to_le32(des2 + BUF_SIZE_4KiB); + desc->des3 = cpu_to_le32(lower_32_bits(des2) + BUF_SIZE_4KiB); stmmac_prepare_tx_desc(priv, desc, 1, nopaged_len, csum, STMMAC_RING_MODE, 0, !skb_is_nonlinear(skb), skb->len); From a085e68b13906a6a4d8b16e3b763e13f05904cc8 Mon Sep 17 00:00:00 2001 From: Vitaliy Sochnev Date: Tue, 11 Aug 2026 19:16:59 +0100 Subject: [PATCH 1334/1433] net: airoha: npu: load the firmware without the sysfs fallback airoha_npu_load_firmware() maps a missing firmware file to -EPROBE_DEFER so that the NPU can be brought up once the rootfs carrying /lib/firmware has been mounted. That mapping holds only as long as request_firmware() reports -ENOENT. It does not when the sysfs fallback is in play. With CONFIG_FW_LOADER_USER_HELPER_FALLBACK set, or with the fallback armed at runtime through /proc/sys/kernel/firmware_config/force_sysfs_fallback, request_firmware() hands the request to a userspace helper, waits out the full loading_timeout and returns -ETIMEDOUT. The -ENOENT test no longer matches, dev_err_probe() turns the result into a hard failure, and the NPU is left unbound after stalling the boot for 60 seconds: airoha-npu 1e900000.npu: Direct firmware load for airoha/en7581_npu_rv32.bin failed with error -2 airoha-npu 1e900000.npu: Falling back to sysfs fallback for: airoha/en7581_npu_rv32.bin airoha-npu 1e900000.npu: error -ETIMEDOUT: failed to run npu firmware airoha-npu 1e900000.npu: probe with driver airoha-npu failed with error -110 Clearing FW_LOADER_USER_HELPER in the configuration is not a dependable guard against this, because unrelated drivers select it. On the affected build the symbol was turned back on by LEDS_LP55XX_COMMON, even though the platform had explicitly disabled it. Use request_firmware_direct() instead. It sets FW_OPT_NOFALLBACK_SYSFS, so a missing file is reported as -ENOENT whatever the firmware loader is configured to do, and the deferred probe path works as intended. Two consequences are worth stating plainly. The helper is not merely bypassed for the boot-before-rootfs case. fw_run_sysfs_fallback() returns early on FW_OPT_NOFALLBACK_SYSFS, so this driver's firmware requests can no longer be served by a usermode helper at all, including on a system where that is the only delivery route; having no second firmware source, the driver would defer forever there. That is a deliberate trade-off: the -ENOENT to -EPROBE_DEFER mapping was written to wait for a filesystem, and the sysfs helper interface has had no in-tree consumer since udev dropped firmware loading. request_firmware_direct() also sets FW_OPT_NO_WARN, which drops the only message naming the file that failed to load. Report it from the driver instead, so the name lands in the deferred probe reason and shows up in the "deferred probe pending" line emitted at driver_deferred_probe_timeout. The generic report in airoha_npu_probe() goes away with it, since it would otherwise overwrite that reason with a message naming nothing; of the paths it covered, devm_ioremap_resource() reports itself and the malformed firmware-name property now does too. Measured on a Nokia XG-040G-MD with FW_LOADER_USER_HELPER=y and FW_LOADER_USER_HELPER_FALLBACK=y forced on, two images from the same tree differing only by this patch: without: fallback at 2.477s -> -ETIMEDOUT at 64.555s -> probe failed with -110, preinit at 69.6s, NPU unbound with: no fallback, NPU fw version 1456.62 at 3.665s, preinit at 7.6s Cc: stable+noautosel@kernel.org # never worked Signed-off-by: Vitaliy Sochnev Reviewed-by: Simon Horman Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/airoha/airoha_npu.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_npu.c b/drivers/net/ethernet/airoha/airoha_npu.c index b679bed952de..de75376db194 100644 --- a/drivers/net/ethernet/airoha/airoha_npu.c +++ b/drivers/net/ethernet/airoha/airoha_npu.c @@ -202,9 +202,10 @@ static int airoha_npu_load_firmware(struct device *dev, void __iomem *addr, const struct firmware *fw; int ret; - ret = request_firmware(&fw, fw_name, dev); + ret = request_firmware_direct(&fw, fw_name, dev); if (ret) - return ret == -ENOENT ? -EPROBE_DEFER : ret; + return dev_err_probe(dev, ret == -ENOENT ? -EPROBE_DEFER : ret, + "failed to load %s\n", fw_name); if (fw->size > fw_max_size) { dev_err(dev, "%s: fw size too overlimit (%zu)\n", @@ -230,7 +231,8 @@ airoha_npu_load_firmware_from_dts(struct device *dev, void __iomem *addr, ret = of_property_read_string_array(dev->of_node, "firmware-name", fw_names, ARRAY_SIZE(fw_names)); if (ret != ARRAY_SIZE(fw_names)) - return -EINVAL; + return dev_err_probe(dev, -EINVAL, + "invalid firmware-name property\n"); ret = airoha_npu_load_firmware(dev, addr, fw_names[0], NPU_EN7581_FIRMWARE_RV32_MAX_SIZE); @@ -772,7 +774,7 @@ static int airoha_npu_probe(struct platform_device *pdev) err = airoha_npu_run_firmware(dev, base, &res); if (err) - return dev_err_probe(dev, err, "failed to run npu firmware\n"); + return err; regmap_write(npu->regmap, REG_CR_NPU_MIB(10), res.start + NPU_EN7581_FIRMWARE_RV32_MAX_SIZE); From d0c2bed6927cbfa2cb51f240b4812bf6916bce0e Mon Sep 17 00:00:00 2001 From: Fan Gong Date: Tue, 11 Aug 2026 19:43:59 +0800 Subject: [PATCH 1335/1433] hinic3: Fix skb linearization mismatch and drop skb when skb_checksum_help() failed Previously, hinic3_send_one_skb() cached the skb fragment count before calling hinic3_tx_offload(). If hinic3_tx_csum() falls back to skb_checksum_help() for unsupported tunnel packets, the skb may be linearized. Continuing to build the TX descriptor with the stale fragment count leads to a descriptor mismatch, which can trigger out-of-bounds DMA reads or IOMMU faults. Furthermore, the old code ignored the return value of skb_checksum_help(), transmitting corrupted packets with incomplete checksums upon failure. Fix this by: 1. Moving the hinic3_tx_offload() call before calculating 'num_sge' to ensure the correct fragment count is used if the SKB is linearized. 2. Propagating skb_checksum_help() errors and returning HINIC3_TX_OFFLOAD_INVALID to properly drop the skb. Fixes: 17fcb3dc12bb ("hinic3: module initialization and tx/rx logic") Co-developed-by: Teng Peisen Signed-off-by: Teng Peisen Co-developed-by: Wu Di Signed-off-by: Wu Di Signed-off-by: Fan Gong Reviewed-by: Simon Horman Link: https://patch.msgid.link/78d8c61cab588240948eaddcb437d59add9f77ae.1786448013.git.tengpeisen@huawei.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/huawei/hinic3/hinic3_tx.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c index 9306bf0020ca..cc541e7a2318 100644 --- a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c +++ b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c @@ -261,8 +261,7 @@ static int hinic3_tx_csum(struct hinic3_txq *txq, struct hinic3_sq_task *task, ((struct udphdr *)skb_transport_header(skb))->dest != VXLAN_OFFLOAD_PORT_LE) { /* Unsupported tunnel packet, disable csum offload */ - skb_checksum_help(skb); - return 0; + return skb_checksum_help(skb); } } @@ -412,6 +411,10 @@ static u32 hinic3_tx_offload(struct sk_buff *skb, struct hinic3_sq_task *task, offload |= HINIC3_TX_OFFLOAD_TSO; } else { tso_cs_en = hinic3_tx_csum(txq, task, skb); + if (tso_cs_en < 0) { + offload = HINIC3_TX_OFFLOAD_INVALID; + return offload; + } if (tso_cs_en) offload |= HINIC3_TX_OFFLOAD_CSUM; } @@ -545,6 +548,7 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb, skb->len = MIN_SKB_LEN; } + offload = hinic3_tx_offload(skb, &task, &queue_info, txq); num_sge = skb_shinfo(skb)->nr_frags + 1; /* assume normal wqe format + 1 wqebb for task info */ wqebb_cnt = num_sge + 1; @@ -560,7 +564,6 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb, return NETDEV_TX_BUSY; } - offload = hinic3_tx_offload(skb, &task, &queue_info, txq); if (unlikely(offload == HINIC3_TX_OFFLOAD_INVALID)) { goto err_drop_pkt; } else if (!offload) { From 43b0213529c6ae2fd4cbf8dbb9baff87a34c27d7 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Tue, 11 Aug 2026 15:08:13 +0800 Subject: [PATCH 1336/1433] net: ibm: emac: mal: fix NAPI locking Since commit 413f0271f396 ("net: protect NAPI enablement with netdev_lock()"), napi_enable() and napi_disable() take netdev_lock(). mal_register_commac() and mal_unregister_commac() call these helpers while holding mal->lock with interrupts disabled. In the unregister path, napi_disable() may also wait for polling to finish, while the poll completion path takes mal->lock. Take netdev_lock() before mal->lock, use the locked NAPI helpers, and drop mal->lock before napi_disable_locked(). Fixes: 413f0271f396 ("net: protect NAPI enablement with netdev_lock()") Cc: stable@vger.kernel.org Signed-off-by: Runyu Xiao Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260811070813.377573-1-runyu.xiao@seu.edu.cn Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/ibm/emac/mal.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/ibm/emac/mal.c b/drivers/net/ethernet/ibm/emac/mal.c index 74526002d52b..42027665f2a9 100644 --- a/drivers/net/ethernet/ibm/emac/mal.c +++ b/drivers/net/ethernet/ibm/emac/mal.c @@ -35,6 +35,7 @@ int mal_register_commac(struct mal_instance *mal, struct mal_commac *commac) { unsigned long flags; + netdev_lock(mal->napi.dev); spin_lock_irqsave(&mal->lock, flags); MAL_DBG(mal, "reg(%08x, %08x)" NL, @@ -44,18 +45,20 @@ int mal_register_commac(struct mal_instance *mal, struct mal_commac *commac) if ((mal->tx_chan_mask & commac->tx_chan_mask) || (mal->rx_chan_mask & commac->rx_chan_mask)) { spin_unlock_irqrestore(&mal->lock, flags); + netdev_unlock(mal->napi.dev); printk(KERN_WARNING "mal%d: COMMAC channels conflict!\n", mal->index); return -EBUSY; } if (list_empty(&mal->list)) - napi_enable(&mal->napi); + napi_enable_locked(&mal->napi); mal->tx_chan_mask |= commac->tx_chan_mask; mal->rx_chan_mask |= commac->rx_chan_mask; list_add(&commac->list, &mal->list); spin_unlock_irqrestore(&mal->lock, flags); + netdev_unlock(mal->napi.dev); return 0; } @@ -64,7 +67,9 @@ void mal_unregister_commac(struct mal_instance *mal, struct mal_commac *commac) { unsigned long flags; + bool disable_napi; + netdev_lock(mal->napi.dev); spin_lock_irqsave(&mal->lock, flags); MAL_DBG(mal, "unreg(%08x, %08x)" NL, @@ -73,10 +78,12 @@ void mal_unregister_commac(struct mal_instance *mal, mal->tx_chan_mask &= ~commac->tx_chan_mask; mal->rx_chan_mask &= ~commac->rx_chan_mask; list_del_init(&commac->list); - if (list_empty(&mal->list)) - napi_disable(&mal->napi); + disable_napi = list_empty(&mal->list); spin_unlock_irqrestore(&mal->lock, flags); + if (disable_napi) + napi_disable_locked(&mal->napi); + netdev_unlock(mal->napi.dev); } int mal_set_rcbs(struct mal_instance *mal, int channel, unsigned long size) From f98ca137c2ddd45562fbddcb6aedf93d4570ff7e Mon Sep 17 00:00:00 2001 From: Qi Zhang Date: Wed, 12 Aug 2026 13:21:30 +0800 Subject: [PATCH 1337/1433] net: pktgen: use a consistent flow count pktgen_if_write() can update cflows while the packet generator thread is inside mod_cur_headers(). The latter first tests cflows, but f_pick() then reloads it when selecting a random flow. This allows the following interleaving: CPU 0 (kpktgend) CPU 1 (proc write) if (pkt_dev->cflows) // 10 pkt_dev->cflows = 0 get_random_u32_below(pkt_dev->cflows) get_random_u32_below(0) returns a full-width random value. Using that value as an index into the fixed-size flows array causes an out-of-bounds access. The kernel reported: BUG: unable to handle page fault for address: ffffc8fe2d2674bc #PF: supervisor read access in kernel mode Oops: Oops: 0000 [#1] SMP KASAN NOPTI CPU: 0 UID: 0 PID: 65 Comm: kpktgend_0 RIP: 0010:mod_cur_headers+0x16f8/0x2840 Call Trace: pktgen_thread_worker+0x305a/0x6bc0 kthread+0x2c6/0x3b0 ret_from_fork+0x36e/0x5a0 ret_from_fork_asm+0x1a/0x30 Read cflows once at the start of mod_cur_headers(), pass the snapshot to f_pick(), and use it for later flow-state decisions in the same packet. Publish proc updates with WRITE_ONCE(). Flow selection then always uses a nonzero count bounded by MAX_CFLOWS, while a concurrent update takes effect on a later packet. Cc: stable+noautosel@kernel.org # needs real net-admin (non-ns) Signed-off-by: Chengfeng Ye Signed-off-by: Qi Zhang Reviewed-by: Simon Horman Signed-off-by: Jakub Kicinski --- net/core/pktgen.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/net/core/pktgen.c b/net/core/pktgen.c index 5403a9f61178..7f81aed46672 100644 --- a/net/core/pktgen.c +++ b/net/core/pktgen.c @@ -566,6 +566,7 @@ static const struct proc_ops pktgen_proc_ops = { static int pktgen_if_show(struct seq_file *seq, void *v) { const struct pktgen_dev *pkt_dev = seq->private; + unsigned int cflows = READ_ONCE(pkt_dev->cflows); ktime_t stopped; unsigned int i; u64 idle; @@ -590,7 +591,7 @@ static int pktgen_if_show(struct seq_file *seq, void *v) pkt_dev->nfrags, (unsigned long long) pkt_dev->delay, pkt_dev->clone_skb, pkt_dev->odevname); - seq_printf(seq, " flows: %u flowlen: %u\n", pkt_dev->cflows, + seq_printf(seq, " flows: %u flowlen: %u\n", cflows, pkt_dev->lflow); seq_printf(seq, @@ -675,7 +676,7 @@ static int pktgen_if_show(struct seq_file *seq, void *v) for (i = 0; i < NR_PKT_FLAGS; i++) { if (i == FLOW_SEQ_SHIFT) - if (!pkt_dev->cflows) + if (!cflows) continue; if (pkt_dev->flags & (1 << i)) { @@ -1632,8 +1633,8 @@ static ssize_t pktgen_if_write(struct file *file, if (value > MAX_CFLOWS) value = MAX_CFLOWS; - pkt_dev->cflows = value; - sprintf(pg_result, "OK: flows=%u", pkt_dev->cflows); + WRITE_ONCE(pkt_dev->cflows, value); + sprintf(pg_result, "OK: flows=%u", (unsigned int)value); return count; } #ifdef CONFIG_XFRM @@ -2373,7 +2374,7 @@ static inline int f_seen(const struct pktgen_dev *pkt_dev, int flow) return !!(pkt_dev->flows[flow].flags & F_INIT); } -static inline int f_pick(struct pktgen_dev *pkt_dev) +static inline int f_pick(struct pktgen_dev *pkt_dev, unsigned int cflows) { int flow = pkt_dev->curfl; @@ -2383,11 +2384,11 @@ static inline int f_pick(struct pktgen_dev *pkt_dev) pkt_dev->flows[flow].count = 0; pkt_dev->flows[flow].flags = 0; pkt_dev->curfl += 1; - if (pkt_dev->curfl >= pkt_dev->cflows) + if (pkt_dev->curfl >= cflows) pkt_dev->curfl = 0; /*reset */ } } else { - flow = get_random_u32_below(pkt_dev->cflows); + flow = get_random_u32_below(cflows); pkt_dev->curfl = flow; if (pkt_dev->flows[flow].count > pkt_dev->lflow) { @@ -2461,12 +2462,14 @@ static void set_cur_queue_map(struct pktgen_dev *pkt_dev) */ static void mod_cur_headers(struct pktgen_dev *pkt_dev) { + unsigned int cflows; __u32 imn; __u32 imx; int flow = 0; - if (pkt_dev->cflows) - flow = f_pick(pkt_dev); + cflows = READ_ONCE(pkt_dev->cflows); + if (cflows) + flow = f_pick(pkt_dev, cflows); /* Deal with source MAC */ if (pkt_dev->src_mac_count > 1) { @@ -2582,7 +2585,7 @@ static void mod_cur_headers(struct pktgen_dev *pkt_dev) pkt_dev->cur_saddr = htonl(t); } - if (pkt_dev->cflows && f_seen(pkt_dev, flow)) { + if (cflows && f_seen(pkt_dev, flow)) { pkt_dev->cur_daddr = pkt_dev->flows[flow].cur_daddr; } else { imn = ntohl(pkt_dev->daddr_min); @@ -2611,7 +2614,7 @@ static void mod_cur_headers(struct pktgen_dev *pkt_dev) pkt_dev->cur_daddr = htonl(t); } } - if (pkt_dev->cflows) { + if (cflows) { pkt_dev->flows[flow].flags |= F_INIT; pkt_dev->flows[flow].cur_daddr = pkt_dev->cur_daddr; From 273480bb836e515353f30a1e70b21a2382de666e Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Tue, 11 Aug 2026 16:36:14 +0800 Subject: [PATCH 1338/1433] ptp: netc: skip PEROUT disable if channel is not enabled When userspace calls ioctl(PTP_PEROUT_REQUEST) with period = 0 to disable a PEROUT channel that is not enabled, the driver incorrectly enters the disable path. Since the channel's struct netc_pp was previously zeroed, pp->alarm_id evaluates to 0, causing priv->fs_alarm_bitmap &= ~BIT(0) to silently revoke the alarm 0 allocation from whichever channel is actively using it. This can cause two channels conflict over the same hardware alarm configuration and corrupt their periodic output signals. Therefore, guard the disable path with a check on pp->enabled and return early if the channel is not enabled. Fixes: 671e266835b8 ("ptp: netc: add periodic pulse output support") Reported-by: Sashiko Closes: https://sashiko.dev/#/message/20260809031908.46EBF1F00A3A%40smtp.kernel.org Signed-off-by: Wei Fang Link: https://patch.msgid.link/20260811083614.3589967-1-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- drivers/ptp/ptp_netc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/ptp/ptp_netc.c b/drivers/ptp/ptp_netc.c index 1c20d7efab92..59db08e189e6 100644 --- a/drivers/ptp/ptp_netc.c +++ b/drivers/ptp/ptp_netc.c @@ -482,6 +482,9 @@ static int net_timer_enable_perout(struct netc_timer *priv, netc_timer_enable_periodic_pulse(priv, channel); } else { + if (!pp->enabled) + goto unlock_spinlock; + netc_timer_disable_periodic_pulse(priv, channel); priv->fs_alarm_bitmap &= ~BIT(pp->alarm_id); memset(pp, 0, sizeof(*pp)); From 1056e79fffd0841f43c6a1b25664b196b3caf1c6 Mon Sep 17 00:00:00 2001 From: Fabio Porcedda Date: Wed, 12 Aug 2026 07:49:11 +0200 Subject: [PATCH 1339/1433] net: usb: qmi_wwan: add Telit Cinterion FE990D50 composition Add the followin Telit Cinterion FE990D50 composition: 0x0991: rmnet + tty (AT/NMEA) + tty (AT) + tty (AT) + tty (AT) + tty (diag) + ADPL + adb T: Bus=01 Lev=01 Prnt=01 Port=06 Cnt=03 Dev#= 10 Spd=480 MxCh= 0 D: Ver= 2.10 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1 P: Vendor=1bc7 ProdID=0991 Rev=06.06 S: Manufacturer=Telit Cinterion S: Product=FE990 S: SerialNumber=2aa802d2 C: #Ifs= 9 Cfg#= 1 Atr=e0 MxPwr=500mA I: If#= 0 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=50 Driver=qmi_wwan E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=82(I) Atr=03(Int.) MxPS= 8 Ivl=32ms I: If#= 1 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=60 Driver=option E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=84(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=86(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=87(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=88(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 4 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=05(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=89(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=8a(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 5 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option E: Ad=06(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=8b(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 6 Alt= 0 #EPs= 1 Cls=ff(vend.) Sub=ff Prot=80 Driver=(none) E: Ad=8c(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 7 Alt= 0 #EPs= 1 Cls=ff(vend.) Sub=ff Prot=70 Driver=(none) E: Ad=8d(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 8 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=42 Prot=01 Driver=(none) E: Ad=07(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=8e(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms Cc: stable@vger.kernel.org Signed-off-by: Fabio Porcedda Reviewed-by: Breno Leitao Link: https://patch.msgid.link/20260812054911.447887-1-Fabio.Porcedda@telit.com Signed-off-by: Jakub Kicinski --- drivers/net/usb/qmi_wwan.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c index 94cdb61dca83..8178a8758cd3 100644 --- a/drivers/net/usb/qmi_wwan.c +++ b/drivers/net/usb/qmi_wwan.c @@ -1360,6 +1360,7 @@ static const struct usb_device_id products[] = { {QMI_FIXED_INTF(0x1bbb, 0x0203, 2)}, /* Alcatel L800MA */ {QMI_FIXED_INTF(0x2357, 0x0201, 4)}, /* TP-LINK HSUPA Modem MA180 */ {QMI_FIXED_INTF(0x2357, 0x9000, 4)}, /* TP-LINK MA260 */ + {QMI_QUIRK_SET_DTR(0x1bc7, 0x0991, 0)}, /* Telit FE990D50 */ {QMI_QUIRK_SET_DTR(0x1bc7, 0x1031, 3)}, /* Telit LE910C1-EUX */ {QMI_QUIRK_SET_DTR(0x1bc7, 0x1034, 2)}, /* Telit LE910C4-WWX */ {QMI_QUIRK_SET_DTR(0x1bc7, 0x1037, 4)}, /* Telit LE910C4-WWX */ From 621f44af8c5b373de2b2e19a4a8db9e45c3c659a Mon Sep 17 00:00:00 2001 From: Eric Joyner Date: Tue, 11 Aug 2026 12:50:38 -0700 Subject: [PATCH 1340/1433] ionic: add missing dma_rmb() after the completion publish check Each completion service routine tests a device-written publish flag and then reads the rest of the descriptor with nothing ordering those loads. A control dependency does not order loads, so a weakly ordered CPU may satisfy the payload reads from a cache line state observed before the flag became valid. Add the barrier to all four completion paths. Fixes: 1d062b7b6f64 ("ionic: Add basic adminq support") Fixes: 0f3154e6bcb3 ("ionic: Add Tx and Rx handling") Fixes: 77ceb68e29cc ("ionic: Add notifyq support") Signed-off-by: Eric Joyner Reviewed-by: Brett Creeley Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260811195039.1315045-2-eric.joyner@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/pensando/ionic/ionic_main.c | 4 ++++ drivers/net/ethernet/pensando/ionic/ionic_txrx.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/drivers/net/ethernet/pensando/ionic/ionic_main.c b/drivers/net/ethernet/pensando/ionic/ionic_main.c index 6e6f3ed07271..10501be9ef95 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_main.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_main.c @@ -269,6 +269,8 @@ bool ionic_notifyq_service(struct ionic_cq *cq) if ((s64)(eid - lif->last_eid) <= 0) return false; + dma_rmb(); + lif->last_eid = eid; dev_dbg(lif->ionic->dev, "notifyq event:\n"); @@ -314,6 +316,8 @@ bool ionic_adminq_service(struct ionic_cq *cq) if (!color_match(comp->color, cq->done_color)) return false; + dma_rmb(); + /* check for empty queue */ if (q->tail_idx == q->head_idx) return false; diff --git a/drivers/net/ethernet/pensando/ionic/ionic_txrx.c b/drivers/net/ethernet/pensando/ionic/ionic_txrx.c index 301ebee2fdc5..5b58460350be 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_txrx.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_txrx.c @@ -734,6 +734,8 @@ static bool __ionic_rx_service(struct ionic_cq *cq, struct bpf_prog *xdp_prog) if (!color_match(comp->pkt_type_color, cq->done_color)) return false; + dma_rmb(); + /* check for empty queue */ if (q->tail_idx == q->head_idx) return false; @@ -1249,6 +1251,8 @@ static bool ionic_tx_service(struct ionic_cq *cq, if (!color_match(comp->color, cq->done_color)) return false; + dma_rmb(); + /* clean the related q entries, there could be * several q entries completed for each cq completion */ From 5da6ec6f06f235166bd084466b3386c638e26675 Mon Sep 17 00:00:00 2001 From: Prabu Thayalan Date: Tue, 11 Aug 2026 12:50:39 -0700 Subject: [PATCH 1341/1433] ionic: fix completion descriptor access with 2x desc size The old ionic_rx_service() and ionic_tx_service() used array indexing to access completion descriptors: comp = &((struct ionic_rxq_comp *)cq->base)[cq->tail_idx]; This assumes the stride is sizeof(struct ionic_rxq_comp) = 16 bytes. However, when the IONIC_Q_F_2X_CQ_DESC flag is set, the actual completion descriptor size is 32 bytes (2 * sizeof(comp)), and the completion itself is located at the end of that 32-byte slot. Array indexing with a 16-byte stride would access the wrong offset. Use pointer arithmetic that accounts for the actual descriptor size from cq->desc_size: comp = cq->base + cq->desc_size * cq->tail_idx + cq->desc_size - sizeof(*comp); This correctly calculates the completion location regardless of descriptor size. For the common case where desc_size equals sizeof(*comp), use array indexing in a likely() fast path to avoid performance regression. Fixes: 65e548f6b0ff ("ionic: remove the cq_info to save more memory") Signed-off-by: Prabu Thayalan Signed-off-by: Eric Joyner Reviewed-by: Brett Creeley Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260811195039.1315045-3-eric.joyner@amd.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/pensando/ionic/ionic_txrx.c | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/drivers/net/ethernet/pensando/ionic/ionic_txrx.c b/drivers/net/ethernet/pensando/ionic/ionic_txrx.c index 5b58460350be..05938689248d 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_txrx.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_txrx.c @@ -701,11 +701,7 @@ static void ionic_rx_clean(struct ionic_queue *q, __le64 *cq_desc_hwstamp; u64 hwstamp; - cq_desc_hwstamp = - (void *)comp + - qcq->cq.desc_size - - sizeof(struct ionic_rxq_comp) - - IONIC_HWSTAMP_CQ_NEGOFFSET; + cq_desc_hwstamp = (void *)comp - IONIC_HWSTAMP_CQ_NEGOFFSET; hwstamp = le64_to_cpu(*cq_desc_hwstamp); @@ -729,7 +725,12 @@ static bool __ionic_rx_service(struct ionic_cq *cq, struct bpf_prog *xdp_prog) struct ionic_queue *q = cq->bound_q; struct ionic_rxq_comp *comp; - comp = &((struct ionic_rxq_comp *)cq->base)[cq->tail_idx]; + if (likely(cq->desc_size == sizeof(*comp))) + comp = &((struct ionic_rxq_comp *)cq->base)[cq->tail_idx]; + else + comp = cq->base + + cq->desc_size * cq->tail_idx + + cq->desc_size - sizeof(*comp); if (!color_match(comp->pkt_type_color, cq->done_color)) return false; @@ -1182,7 +1183,6 @@ static void ionic_tx_clean(struct ionic_queue *q, bool in_napi) { struct ionic_tx_stats *stats = q_to_tx_stats(q); - struct ionic_qcq *qcq = q_to_qcq(q); struct sk_buff *skb; if (desc_info->xdpf) { @@ -1207,11 +1207,7 @@ static void ionic_tx_clean(struct ionic_queue *q, __le64 *cq_desc_hwstamp; u64 hwstamp; - cq_desc_hwstamp = - (void *)comp + - qcq->cq.desc_size - - sizeof(struct ionic_txq_comp) - - IONIC_HWSTAMP_CQ_NEGOFFSET; + cq_desc_hwstamp = (void *)comp - IONIC_HWSTAMP_CQ_NEGOFFSET; hwstamp = le64_to_cpu(*cq_desc_hwstamp); @@ -1246,7 +1242,12 @@ static bool ionic_tx_service(struct ionic_cq *cq, unsigned int pkts = 0; u16 index; - comp = &((struct ionic_txq_comp *)cq->base)[cq->tail_idx]; + if (likely(cq->desc_size == sizeof(*comp))) + comp = &((struct ionic_txq_comp *)cq->base)[cq->tail_idx]; + else + comp = cq->base + + cq->desc_size * cq->tail_idx + + cq->desc_size - sizeof(*comp); if (!color_match(comp->color, cq->done_color)) return false; From d0d48d999b0eee6bb176ef4e39d9be868fa80f7e Mon Sep 17 00:00:00 2001 From: Luxiao Xu Date: Wed, 12 Aug 2026 20:54:38 +0800 Subject: [PATCH 1342/1433] ipv6: fix use-after-free in ip6_finish_output2() ip6_finish_output2() caches a pointer to the IPv6 destination address (daddr) before invoking lwtunnel_xmit(). The LWT-BPF transmit path or other encapsulation operations within lwtunnel_xmit() can reallocate the skb head, freeing the memory that daddr points to. When lwtunnel_xmit() returns LWTUNNEL_XMIT_CONTINUE, the function continues to use the stale daddr pointer to compute the nexthop and to look up or create the neighbour entry. This results in a use-after-free read, which can leak sensitive kernel data, pollute the neighbour table with arbitrary values, misdirect traffic, or crash the system. Fix this by re-fetching the IPv6 header and the destination address pointer after lwtunnel_xmit() returns LWTUNNEL_XMIT_CONTINUE, ensuring that the subsequent nexthop computation and neighbour lookup operate on valid memory. Fixes: e415ed3a4b8b ("ipv6: use skb_expand_head in ip6_finish_output2") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Luxiao Xu Signed-off-by: Ren Wei Reviewed-by: Vadim Fedorenko Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/4aa3f53bc44e79572c6dd2340ec7b68ef1a3d87d.1786516730.git.rakukuip@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_output.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c index 2c44e5ed6171..8fc4766c8da9 100644 --- a/net/ipv6/ip6_output.c +++ b/net/ipv6/ip6_output.c @@ -116,6 +116,8 @@ static int ip6_finish_output2(struct net *net, struct sock *sk, struct sk_buff * if (res != LWTUNNEL_XMIT_CONTINUE) return res; + hdr = ipv6_hdr(skb); + daddr = &hdr->daddr; } IP6_UPD_PO_STATS(net, idev, IPSTATS_MIB_OUT, skb->len); From 87f21b59ddc618eff9670c174842964ad65fdade Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Tue, 11 Aug 2026 21:31:11 +0800 Subject: [PATCH 1343/1433] ip6_tunnel: use skb_cow_head() in ip6_tnl_xmit() ip6_tnl_xmit() may need to expand headroom before it can push the outer IPv6 and optional encap headers. It currently does that with skb_realloc_headroom(), copies skb->sk ownership, consumes the original skb, and then continues processing with the replacement skb kept only in its local variable. That is safe only if the helper cannot fail afterwards. But this helper still has post-reallocation error exits. collect_md tunnels reject non-NONE encap after the replacement, and ip6_tnl_encap() can also fail later. In those cases the helper returns an error to its callers while the caller still only has the original skb pointer. Both ip6_tnl_start_xmit() and the IPv6 GRE paths free the caller skb on error, so they can end up freeing an skb that ip6_tnl_xmit() already consumed. Use skb_cow_head() instead. It provides the required headroom and writability without privately replacing the caller-owned skb, so later error returns cannot leave callers with a stale pointer. The Ethernet users, ip6gretap and ip6erspan, clear IFF_TX_SKB_SHARING and already call skb_cow_head() before entering ip6_tnl_xmit(). They do not rely on the removed skb_shared() reallocation. This also makes the IPv6 tunnel path consistent with ip_tunnel_xmit(). Fixes: 058214a4d1df ("ip6_tun: Add infrastructure for doing encapsulation") Cc: stable@vger.kernel.org Reported-by: Vega Reviewed-by: Ido Schimmel Signed-off-by: Zhiling Zou Link: https://patch.msgid.link/30807a062ccc5c9c8a5ec2c5eb805ef279c50bdd.1786452593.git.zhilinz@nebusec.ai Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_tunnel.c | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index ebf83f090376..6a1b901ecc9b 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -1236,19 +1236,8 @@ int ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield, */ max_headroom += LL_RESERVED_SPACE(tdev); - if (skb_headroom(skb) < max_headroom || skb_shared(skb) || - (skb_cloned(skb) && !skb_clone_writable(skb, 0))) { - struct sk_buff *new_skb; - - new_skb = skb_realloc_headroom(skb, max_headroom); - if (!new_skb) - goto tx_err_dst_release; - - if (skb->sk) - skb_set_owner_w(new_skb, skb->sk); - consume_skb(skb); - skb = new_skb; - } + if (skb_cow_head(skb, max_headroom)) + goto tx_err_dst_release; if (t->parms.collect_md) { if (t->encap.type != TUNNEL_ENCAP_NONE) From d5d4a7b538b52db63927773a8905fcd9f78a42e2 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Mon, 10 Aug 2026 14:41:14 +0000 Subject: [PATCH 1344/1433] vxlan: keep the last remote linked during FDB flush A non-nexthop FDB entry is expected to have at least one remote while it remains reachable through the FDB hash table. A filtered bulk flush violates this invariant when every remote matches: It unlinks the last remote in vxlan_fdb_dst_destroy() and only afterwards tells vxlan_flush() to destroy the parent FDB entry. An RCU reader can find the parent during this interval. first_remote_rcu() then applies list_entry_rcu() to the empty list head, producing an invalid remote pointer that the receive learning path can read from and write to. When a matching remote is the sole remaining remote, leave it linked and ask the caller to destroy the entire FDB entry. vxlan_fdb_destroy() keeps the remote attached while sending the deletion notification and removing the parent from the lookup structures. Fixes: c499fccb71cb ("vxlan: vxlan_core: Support FDB flushing by destination VNI") Cc: stable@vger.kernel.org Signed-off-by: Kyle Zeng Co-developed-by: David Lee Signed-off-by: David Lee Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260810144115.821654-1-david.lee@trailofbits.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index 824144bb7774..fbb6ddbb7f89 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -3058,18 +3058,19 @@ vxlan_fdb_flush_match_remotes(struct vxlan_fdb *f, struct vxlan_dev *vxlan, const struct vxlan_fdb_flush_desc *desc, bool *p_destroy_fdb) { - bool remotes_flushed = false; struct vxlan_rdst *rd, *tmp; list_for_each_entry_safe(rd, tmp, &f->remotes, list) { if (!vxlan_fdb_flush_remote_matches(desc, rd)) continue; - vxlan_fdb_dst_destroy(vxlan, f, rd, true); - remotes_flushed = true; - } + if (list_is_singular(&f->remotes)) { + *p_destroy_fdb = true; + return; + } - *p_destroy_fdb = remotes_flushed && list_empty(&f->remotes); + vxlan_fdb_dst_destroy(vxlan, f, rd, true); + } } /* Purge the forwarding table */ From f53167e29b8e9178ce030f7de54634af1eb0bc0e Mon Sep 17 00:00:00 2001 From: Martino Dell'Ambrogio Date: Wed, 12 Aug 2026 17:47:07 +0200 Subject: [PATCH 1345/1433] net: sfp: allow prefix matching in quirk lookup Some clone SFP modules return EEPROM reads where the vendor PN field contains non-printable garbage past the trailing legitimate characters instead of the SFF-8472 mandated space padding. The current sfp_match() requires an exact full-field length match: sfp_strlen() returns 16 (no trailing spaces or NULs to strip), but strlen() of the quirk string is shorter, so the length comparison rejects the entry before strncmp() is even called and the quirk silently never applies. Add a part_prefix_match flag to struct sfp_quirk and a SFP_QUIRK_F_PREFIX macro. When set, sfp_match() compares only strlen() leading bytes of the quirk part string, ignoring trailing field bytes. The vendor name comparison always stays exact. Existing exact-match quirks are unaffected (part_prefix_match defaults to false via zero-init in the existing SFP_QUIRK macros). This patch only adds the mechanism; the first user is added by the following patch. Signed-off-by: Martino Dell'Ambrogio Link: https://patch.msgid.link/20260812154708.2201266-2-tillo@tillo.ch Signed-off-by: Jakub Kicinski --- drivers/net/phy/sfp.c | 23 ++++++++++++++++++----- drivers/net/phy/sfp.h | 1 + 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 508b6cc8eddc..15033cbf7068 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -516,6 +516,15 @@ static void sfp_quirk_ubnt_uf_instant(const struct sfp_eeprom_id *id, { .vendor = _v, .part = _p, .support = _s, .fixup = _f, } #define SFP_QUIRK_S(_v, _p, _s) SFP_QUIRK(_v, _p, _s, NULL) #define SFP_QUIRK_F(_v, _p, _f) SFP_QUIRK(_v, _p, NULL, _f) +/* Like SFP_QUIRK_F, but matches the part as a prefix; the vendor name + * is still matched exactly. Use for modules whose EEPROM vendor PN + * field reads back with garbage past the legitimate characters instead + * of the SFF-8472-mandated space padding, so sfp_strlen can't trim the + * field down to the legitimate length. + */ +#define SFP_QUIRK_F_PREFIX(_v, _p, _f) \ + { .vendor = _v, .part = _p, .support = NULL, .fixup = _f, \ + .part_prefix_match = true } static const struct sfp_quirk sfp_quirks[] = { // Alcatel Lucent G-010S-P can operate at 2500base-X, but incorrectly @@ -629,13 +638,16 @@ static size_t sfp_strlen(const char *str, size_t maxlen) return size; } -static bool sfp_match(const char *qs, const char *str, size_t len) +static bool sfp_match(const char *qs, const char *str, size_t len, bool prefix) { + size_t qs_len; + if (!qs) return true; - if (strlen(qs) != len) + qs_len = strlen(qs); + if (prefix ? qs_len > len : qs_len != len) return false; - return !strncmp(qs, str, len); + return !strncmp(qs, str, qs_len); } static const struct sfp_quirk *sfp_lookup_quirk(const struct sfp_eeprom_id *id) @@ -648,8 +660,9 @@ static const struct sfp_quirk *sfp_lookup_quirk(const struct sfp_eeprom_id *id) ps = sfp_strlen(id->base.vendor_pn, ARRAY_SIZE(id->base.vendor_pn)); for (i = 0, q = sfp_quirks; i < ARRAY_SIZE(sfp_quirks); i++, q++) - if (sfp_match(q->vendor, id->base.vendor_name, vs) && - sfp_match(q->part, id->base.vendor_pn, ps)) + if (sfp_match(q->vendor, id->base.vendor_name, vs, false) && + sfp_match(q->part, id->base.vendor_pn, ps, + q->part_prefix_match)) return q; return NULL; diff --git a/drivers/net/phy/sfp.h b/drivers/net/phy/sfp.h index 879dff7afe6a..19fe29d844a1 100644 --- a/drivers/net/phy/sfp.h +++ b/drivers/net/phy/sfp.h @@ -12,6 +12,7 @@ struct sfp_quirk { void (*support)(const struct sfp_eeprom_id *id, struct sfp_module_caps *caps); void (*fixup)(struct sfp *sfp); + bool part_prefix_match; }; struct sfp_socket_ops { From 03fa69146f2fe18742c0e12cfbf1d10c23d5b567 Mon Sep 17 00:00:00 2001 From: Martino Dell'Ambrogio Date: Wed, 12 Aug 2026 17:47:08 +0200 Subject: [PATCH 1346/1433] net: sfp: add quirks for OEM XGSPONST2001 and FS XGS-SFP-ONT-MACI Cheap XGS-PON ONT sticks identifying as vendor "OEM", PN "XGSPONST2001" have broken TX_FAULT and LOS indicators (driven by the ONU serial passthrough wires) and need a longer T_START_UP than the SFF-8472 default. The Fiberstore XGS-SFP-ONT-MACI MAC-mode ONT stick has the same ONT-class TX_FAULT/LOS wiring and startup behaviour. Apply the existing sfp_fixup_potron handler to both, which masks both signals and bumps T_START_UP to T_START_UP_BAD_GPON. The XGSPONST2001 returns the 12 legitimate PN characters followed by non-printable garbage on cold power-up reads (the same module reads back clean and space-padded after a warm reseat), which defeats exact-length matching precisely on the boot where the quirk must apply: the kernel honors the spurious TX_FAULT and the SFP state machine eventually disables the module. Match its part as a prefix using SFP_QUIRK_F_PREFIX. The XGS-SFP-ONT-MACI PN is the product name (XGS-SFP-ONT-MAC-I) truncated at the 16-byte field width, so the field is fully occupied by legitimate characters and a plain exact-match SFP_QUIRK_F entry is correct. Signed-off-by: Martino Dell'Ambrogio Link: https://patch.msgid.link/20260812154708.2201266-3-tillo@tillo.ch Signed-off-by: Jakub Kicinski --- drivers/net/phy/sfp.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 15033cbf7068..2ec91466acdf 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -557,6 +557,13 @@ static const struct sfp_quirk sfp_quirks[] = { SFP_QUIRK("FS", "GPON-ONU-34-20BI", sfp_quirk_2500basex, sfp_fixup_ignore_tx_fault), + // Fiberstore XGS-SFP-ONT-MACI is a MAC-mode XGS-PON ONT stick with + // ONT-class serial-passthrough TX_FAULT/LOS wiring and slow startup; + // mask both signals and extend T_START_UP via the potron fixup. The + // PN is the product name (XGS-SFP-ONT-MAC-I) truncated at the 16-byte + // field width, so the field is fully occupied and matches exactly. + SFP_QUIRK_F("FS", "XGS-SFP-ONT-MACI", sfp_fixup_potron), + SFP_QUIRK_F("HALNy", "HL-GSFP", sfp_fixup_halny_gsfp), SFP_QUIRK_F("H-COM", "SPP425H-GAB4", sfp_fixup_potron), @@ -617,6 +624,14 @@ static const struct sfp_quirk sfp_quirks[] = { SFP_QUIRK_S("OEM", "SFP-2.5G-LH20-A", sfp_quirk_2500basex), SFP_QUIRK_F("OEM", "RTSFP-10", sfp_fixup_rollball_cc), SFP_QUIRK_F("OEM", "RTSFP-10G", sfp_fixup_rollball_cc), + + // OEM XGSPONST2001 is an XGS-PON ONT stick with broken TX_FAULT and + // LOS indicators and slow startup, just like potron. On cold + // power-up the EEPROM vendor PN field reads back with non-printable + // garbage past the legitimate string instead of space padding, so + // match the part as a prefix. + SFP_QUIRK_F_PREFIX("OEM", "XGSPONST2001", sfp_fixup_potron), + SFP_QUIRK_F("Turris", "RTSFP-2.5G", sfp_fixup_rollball), SFP_QUIRK_F("Turris", "RTSFP-10", sfp_fixup_rollball), SFP_QUIRK_F("Turris", "RTSFP-10G", sfp_fixup_rollball), From 33f016b23a219fe034213849b51436b8e79df251 Mon Sep 17 00:00:00 2001 From: Petr Oros Date: Thu, 13 Aug 2026 16:08:17 +0200 Subject: [PATCH 1347/1433] dpll: fix NULL deref in dpll_device_ops() during teardown race When the last owner of a dpll device unregisters while a foreign driver still holds a pin on it via dpll_pin_on_pin_register(), the dpll object stays alive with an empty registration list. A pin notification queued before the unregister (e.g. ice reacting to zl3073x_i2c removal) then walks pin->dpll_refs into dpll_device_ops(), which trips the WARN_ON and dereferences the missing registration. dpll_lock cannot help because the notification work was queued before the unregistering driver took the lock. Treat the empty registration list as a legitimate transient state. Make dpll_priv() and dpll_device_ops() return NULL in that case and make every pin netlink path that resolves a device from a pin skip such dplls. dpll_cmd_pin_get_one() picks a ref with a live registration and returns -ENODEV when there is none, the pin dumpit skips such a pin instead of aborting the dump, dpll_msg_add_pin_dplls() and the frequency, esync, reference sync and phase adjust set paths skip dead refs, and dpll_pin_parent_device_set() validates the parent with dpll_device_get_by_id(). dpll_pin_register() is the last caller that dereferenced the device ops without a check, so move its frequency monitor validation under dpll_lock and tolerate a missing registration there as well. The empty registration list is equivalent to a cleared DPLL_REGISTERED mark, both transitions happen under dpll_lock in dpll_device_register() and dpll_device_unregister(). A pin notification for a pin whose dplls are all gone is now dropped with -ENODEV instead of crashing, all callers in the core ignore that return value. WARNING: drivers/dpll/dpll_core.c:1092 at dpll_device_ops+0x24/0x40, CPU#83: kworker/u576:3/23471 Modules linked in: ... ice ... zl3073x_i2c(-) ... zl3073x ... Workqueue: ice_dpll_wq ice_dpll_pin_notify_work [ice] RIP: 0010:dpll_device_ops+0x24/0x40 Call Trace: dpll_cmd_pin_get_one+0x336/0x520 dpll_pin_event_send+0x82/0x140 dpll_pin_on_pin_unregister+0xbb/0x160 ice_dpll_pin_notify_work+0x1bc/0x1f0 [ice] process_one_work+0x19e/0x370 worker_thread+0x1a6/0x310 kthread+0xe4/0x120 ret_from_fork+0x1a1/0x270 ret_from_fork_asm+0x1a/0x30 ---[ end trace 0000000000000000 ]--- BUG: kernel NULL pointer dereference, address: 0000000000000010 #PF: supervisor read access in kernel mode #PF: error_code(0x0000) - not-present page Fixes: 9431063ad323 ("dpll: core: Add DPLL framework base functions") Signed-off-by: Petr Oros Tested-by: Ivan Vecera Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260813140817.1051388-1-poros@redhat.com Signed-off-by: Jakub Kicinski --- drivers/dpll/dpll_core.c | 24 +++++++++------ drivers/dpll/dpll_netlink.c | 59 ++++++++++++++++++++++++++++++++----- 2 files changed, 67 insertions(+), 16 deletions(-) diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c index 43d51d942ead..a320eeb829ad 100644 --- a/drivers/dpll/dpll_core.c +++ b/drivers/dpll/dpll_core.c @@ -876,19 +876,25 @@ int dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin, const struct dpll_pin_ops *ops, void *priv) { + const struct dpll_device_ops *dev_ops; int ret; if (WARN_ON(!ops) || WARN_ON(!ops->state_on_dpll_get) || WARN_ON(!ops->direction_get) || - WARN_ON(ops->measured_freq_get && - (!dpll_device_ops(dpll)->freq_monitor_get || - !dpll_device_ops(dpll)->freq_monitor_set)) || WARN_ON(ops->supported_ffo && !ops->ffo_get)) return -EINVAL; mutex_lock(&dpll_lock); + dev_ops = dpll_device_ops(dpll); + if (WARN_ON(ops->measured_freq_get && + (!dev_ops || !dev_ops->freq_monitor_get || + !dev_ops->freq_monitor_set))) { + ret = -EINVAL; + goto out_unlock; + } + /* * For pins identified via firmware (pin->fwnode), allow registration * even if the pin's (module, clock_id) differs from the target DPLL. @@ -1081,12 +1087,8 @@ EXPORT_SYMBOL_GPL(dpll_pin_ref_sync_pair_add); static struct dpll_device_registration * dpll_device_registration_first(struct dpll_device *dpll) { - struct dpll_device_registration *reg; - - reg = list_first_entry_or_null((struct list_head *)&dpll->registration_list, - struct dpll_device_registration, list); - WARN_ON(!reg); - return reg; + return list_first_entry_or_null((struct list_head *)&dpll->registration_list, + struct dpll_device_registration, list); } void *dpll_priv(struct dpll_device *dpll) @@ -1094,6 +1096,8 @@ void *dpll_priv(struct dpll_device *dpll) struct dpll_device_registration *reg; reg = dpll_device_registration_first(dpll); + if (!reg) + return NULL; return reg->priv; } @@ -1102,6 +1106,8 @@ const struct dpll_device_ops *dpll_device_ops(struct dpll_device *dpll) struct dpll_device_registration *reg; reg = dpll_device_registration_first(dpll); + if (!reg) + return NULL; return reg->ops; } diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index afb31c004038..9e55745e33e4 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -66,6 +66,22 @@ static bool dpll_pin_available(struct dpll_pin *pin) return false; } +static bool dpll_device_registered(struct dpll_device *dpll) +{ + return dpll_device_ops(dpll); +} + +static struct dpll_pin_ref *dpll_pin_first_registered_ref(struct dpll_pin *pin) +{ + struct dpll_pin_ref *ref; + unsigned long i; + + xa_for_each(&pin->dpll_refs, i, ref) + if (dpll_device_registered(ref->dpll)) + return ref; + return NULL; +} + /** * dpll_msg_add_pin_handle - attach pin handle attribute to a given message * @msg: pointer to sk_buff message to attach a pin handle @@ -656,6 +672,8 @@ dpll_msg_add_pin_dplls(struct sk_buff *msg, struct dpll_pin *pin, int ret; xa_for_each(&pin->dpll_refs, index, ref) { + if (!dpll_device_registered(ref->dpll)) + continue; attr = nla_nest_start(msg, DPLL_A_PIN_PARENT_DEVICE); if (!attr) return -EMSGSIZE; @@ -700,9 +718,10 @@ dpll_cmd_pin_get_one(struct sk_buff *msg, struct dpll_pin *pin, int ret; ref = dpll_pin_own_dpll_ref_first(pin); + if (!ref || !dpll_device_registered(ref->dpll)) + ref = dpll_pin_first_registered_ref(pin); if (!ref) - ref = dpll_xa_ref_dpll_first(&pin->dpll_refs); - ASSERT_NOT_NULL(ref); + return -ENODEV; ret = dpll_msg_add_pin_handle(msg, pin); if (ret) @@ -1091,6 +1110,8 @@ dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, } xa_for_each(&pin->dpll_refs, i, ref) { + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if ((!ops->frequency_set || !ops->frequency_get) && ref->dpll->module == pin->module && @@ -1101,7 +1122,7 @@ dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, } } ref = dpll_pin_own_dpll_ref_first(pin); - if (!ref) { + if (!ref || !dpll_device_registered(ref->dpll)) { NL_SET_ERR_MSG(extack, "pin owner dpll not found"); return -ENODEV; } @@ -1117,6 +1138,8 @@ dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, return 0; xa_for_each(&pin->dpll_refs, i, ref) { + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if (!ops->frequency_set) continue; @@ -1138,6 +1161,8 @@ dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, xa_for_each(&pin->dpll_refs, i, ref) { if (ref == failed) break; + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if (!ops->frequency_set) continue; @@ -1163,6 +1188,8 @@ dpll_pin_esync_set(struct dpll_pin *pin, struct nlattr *a, int ret; xa_for_each(&pin->dpll_refs, i, ref) { + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if ((!ops->esync_set || !ops->esync_get) && ref->dpll->module == pin->module && @@ -1173,7 +1200,7 @@ dpll_pin_esync_set(struct dpll_pin *pin, struct nlattr *a, } } ref = dpll_pin_own_dpll_ref_first(pin); - if (!ref) { + if (!ref || !dpll_device_registered(ref->dpll)) { NL_SET_ERR_MSG(extack, "pin owner dpll not found"); return -ENODEV; } @@ -1199,6 +1226,8 @@ dpll_pin_esync_set(struct dpll_pin *pin, struct nlattr *a, xa_for_each(&pin->dpll_refs, i, ref) { void *pin_dpll_priv; + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if (!ops->esync_set) continue; @@ -1224,6 +1253,8 @@ dpll_pin_esync_set(struct dpll_pin *pin, struct nlattr *a, if (ref == failed) break; + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if (!ops->esync_set) continue; @@ -1262,7 +1293,7 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, return -EINVAL; } ref = dpll_pin_own_dpll_ref_first(pin); - if (!ref) { + if (!ref || !dpll_device_registered(ref->dpll)) { NL_SET_ERR_MSG(extack, "pin owner dpll not found"); return -ENODEV; } @@ -1283,6 +1314,8 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, if (state == old_state) return 0; xa_for_each(&pin->dpll_refs, i, ref) { + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if (!ops->ref_sync_set) continue; @@ -1307,6 +1340,8 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, xa_for_each(&pin->dpll_refs, i, ref) { if (ref == failed) break; + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if (!ops->ref_sync_set) continue; @@ -1500,6 +1535,8 @@ dpll_pin_phase_adj_set(struct dpll_pin *pin, struct nlattr *phase_adj_attr, } xa_for_each(&pin->dpll_refs, i, ref) { + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if ((!ops->phase_adjust_set || !ops->phase_adjust_get) && ref->dpll->module == pin->module && @@ -1509,7 +1546,7 @@ dpll_pin_phase_adj_set(struct dpll_pin *pin, struct nlattr *phase_adj_attr, } } ref = dpll_pin_own_dpll_ref_first(pin); - if (!ref) { + if (!ref || !dpll_device_registered(ref->dpll)) { NL_SET_ERR_MSG(extack, "pin owner dpll not found"); return -ENODEV; } @@ -1526,6 +1563,8 @@ dpll_pin_phase_adj_set(struct dpll_pin *pin, struct nlattr *phase_adj_attr, return 0; xa_for_each(&pin->dpll_refs, i, ref) { + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if (!ops->phase_adjust_set) continue; @@ -1550,6 +1589,8 @@ dpll_pin_phase_adj_set(struct dpll_pin *pin, struct nlattr *phase_adj_attr, xa_for_each(&pin->dpll_refs, i, ref) { if (ref == failed) break; + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if (!ops->phase_adjust_set) continue; @@ -1581,7 +1622,7 @@ dpll_pin_parent_device_set(struct dpll_pin *pin, struct nlattr *parent_nest, return -EINVAL; } pdpll_idx = nla_get_u32(tb[DPLL_A_PIN_PARENT_ID]); - dpll = xa_load(&dpll_device_xa, pdpll_idx); + dpll = dpll_device_get_by_id(pdpll_idx); if (!dpll) { NL_SET_ERR_MSG(extack, "parent device not found"); return -EINVAL; @@ -1873,6 +1914,10 @@ int dpll_nl_pin_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb) ret = dpll_cmd_pin_get_one(skb, pin, cb->extack); if (ret) { genlmsg_cancel(skb, hdr); + if (ret == -ENODEV) { + ret = 0; + continue; + } break; } genlmsg_end(skb, hdr); From b0346dd64e4905291cc9c479f2e6cf1884ced4e6 Mon Sep 17 00:00:00 2001 From: Junseo Lim Date: Thu, 13 Aug 2026 12:51:36 +0900 Subject: [PATCH 1348/1433] net: kcm: Hold RCU read lock while running BPF parser kcm_parse_func_strparser() calls bpf_prog_run_pin_on_cpu() which prevents CPU migration, but does not establish an RCU read-side critical section. Consequently, BPF map operations can trigger WARN_ON_ONCE(!bpf_rcu_lock_held()) when called from the KCM strparser program. Hold the RCU read lock while running the program. Fixes: 9b73896a81dc ("kcm: Use stream parser") Reported-by: Sechang Lim Signed-off-by: Junseo Lim Link: https://patch.msgid.link/20260813035136.106167-1-zirajs7@gmail.com Signed-off-by: Jakub Kicinski --- net/kcm/kcmsock.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/kcm/kcmsock.c b/net/kcm/kcmsock.c index d469abcd989b..71af69d442f2 100644 --- a/net/kcm/kcmsock.c +++ b/net/kcm/kcmsock.c @@ -5,6 +5,7 @@ * Copyright (c) 2016 Tom Herbert */ +#include #include #include #include @@ -391,7 +392,9 @@ static int kcm_parse_func_strparser(struct strparser *strp, struct sk_buff *skb) struct bpf_prog *prog = psock->bpf_prog; int res; + rcu_read_lock(); res = bpf_prog_run_pin_on_cpu(prog, skb); + rcu_read_unlock(); return res; } From bda1d74b41dac8e620cf037d7b500cd321ce7213 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Wed, 12 Aug 2026 19:55:36 -0500 Subject: [PATCH 1349/1433] MAINTAINERS: update entry for socfpga dwmac and gmii/sgmii Matthew Gerlach is no longer at Altera, so remove his entry as a maintainer for the SoCFPGA DWMAC ethernet glue layer and the GMII/SGMII dt-bindings files. Maxime Chevallier has volunteered to maintain the dt-bindings YAML files as well. Signed-off-by: Dinh Nguyen Acked-by: Maxime Chevallier Link: https://patch.msgid.link/20260813005536.2068392-1-dinguyen@kernel.org Signed-off-by: Jakub Kicinski --- .../devicetree/bindings/net/altr,gmii-to-sgmii-2.0.yaml | 2 +- .../devicetree/bindings/net/altr,socfpga-stmmac.yaml | 2 +- MAINTAINERS | 8 ++------ 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Documentation/devicetree/bindings/net/altr,gmii-to-sgmii-2.0.yaml b/Documentation/devicetree/bindings/net/altr,gmii-to-sgmii-2.0.yaml index aafb6447b6c2..cfb53080dba2 100644 --- a/Documentation/devicetree/bindings/net/altr,gmii-to-sgmii-2.0.yaml +++ b/Documentation/devicetree/bindings/net/altr,gmii-to-sgmii-2.0.yaml @@ -8,7 +8,7 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Altera GMII to SGMII Converter maintainers: - - Matthew Gerlach + - Maxime Chevallier description: This binding describes the Altera GMII to SGMII converter. diff --git a/Documentation/devicetree/bindings/net/altr,socfpga-stmmac.yaml b/Documentation/devicetree/bindings/net/altr,socfpga-stmmac.yaml index fc445ad5a1f1..8f5d63bb2187 100644 --- a/Documentation/devicetree/bindings/net/altr,socfpga-stmmac.yaml +++ b/Documentation/devicetree/bindings/net/altr,socfpga-stmmac.yaml @@ -7,7 +7,7 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Altera SOCFPGA SoC DWMAC controller maintainers: - - Matthew Gerlach + - Maxime Chevallier description: This binding describes the Altera SOCFPGA SoC implementation of the diff --git a/MAINTAINERS b/MAINTAINERS index 991460050da7..b050c72e2d49 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3603,15 +3603,11 @@ M: Dinh Nguyen S: Maintained F: drivers/clk/socfpga/ -ARM/SOCFPGA DWMAC GLUE LAYER BINDINGS -M: Matthew Gerlach -S: Maintained -F: Documentation/devicetree/bindings/net/altr,gmii-to-sgmii-2.0.yaml -F: Documentation/devicetree/bindings/net/altr,socfpga-stmmac.yaml - ARM/SOCFPGA DWMAC GLUE LAYER M: Maxime Chevallier S: Maintained +F: Documentation/devicetree/bindings/net/altr,gmii-to-sgmii-2.0.yaml +F: Documentation/devicetree/bindings/net/altr,socfpga-stmmac.yaml F: drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c ARM/SOCFPGA EDAC BINDINGS From 4f1d06cf8aaa9d2cb18e5ee8835aff6177256bc6 Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Wed, 12 Aug 2026 23:11:21 +0300 Subject: [PATCH 1350/1433] net: dsa: b53: fix error propagation from b53_fdb_dump() The blamed commit replaced "return ret" statements in b53_fdb_dump() with "break;" which jumps to the mutex_unlock() -> return 0 section. This is notably problematic because it swallows errors from the b53_fdb_copy() -> cb() path, and this will result in FDB dump truncation when the netlink skb overflows - see commit 21b52fed928e ("net: dsa: sja1105: fix broken backpressure in .port_fdb_dump"). Let's go back to "return ret". We don't need to preinitialize "ret" with 0, because the "do {} while" block guarantees we cannot reach the end of the function without at least once calling b53_arl_search_wait(), which will have initialized ret to some valid value. Fixes: f7eb4a1c0864 ("net: dsa: b53: serialize access to the ARL table") Signed-off-by: Vladimir Oltean Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260812201121.2012356-1-vladimir.oltean@nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/b53/b53_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c index 3f5b9592794d..0880310c9ce3 100644 --- a/drivers/net/dsa/b53/b53_common.c +++ b/drivers/net/dsa/b53/b53_common.c @@ -2219,7 +2219,7 @@ int b53_fdb_dump(struct dsa_switch *ds, int port, mutex_unlock(&priv->arl_mutex); - return 0; + return ret; } EXPORT_SYMBOL(b53_fdb_dump); From 09b7c00d55a8c1ec50c44e74d3edba0c0b317910 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 15 Aug 2026 03:32:28 +0200 Subject: [PATCH 1351/1433] net_shaper: fix kernel-doc list indentation Docutils 0.22.4 reports: Documentation/networking/kapi:107: ../include/net/net_shaper.h:82: ERROR: Unexpected indentation. Add the required blank line and correct the list indentation. Signed-off-by: Karl Mehltretter Reviewed-by: Randy Dunlap Tested-by: Randy Dunlap Link: https://patch.msgid.link/64f428350ec1450adcd0607f54f30d27a42f129c.1786751700.git.kmehltretter@gmail.com Signed-off-by: Jakub Kicinski --- include/net/net_shaper.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/include/net/net_shaper.h b/include/net/net_shaper.h index 05cb625b0fe5..330517a1cb5c 100644 --- a/include/net/net_shaper.h +++ b/include/net/net_shaper.h @@ -80,10 +80,11 @@ struct net_shaper { * disallowed at the uAPI level will never be made at the driver level. * The shaper core performs automatic reparenting and cleanup, generating * additional calls. Notably: - * - @group calls in the driver facing API may have nodes as leaves (user is - * only allowed to construct groups with queues as leaves) - * - @group calls may update leaf's parent if the parent is about - * to be removed (re-parenting nodes explicitly is not supported in the uAPI) + * + * - @group calls in the driver facing API may have nodes as leaves (user is + * only allowed to construct groups with queues as leaves) + * - @group calls may update leaf's parent if the parent is about + * to be removed (re-parenting nodes explicitly is not supported in the uAPI) * * Implicit creation * ----------------- From 92c1bf630abf0af646562398eaa36f80b5ff677d Mon Sep 17 00:00:00 2001 From: Qingfang Deng Date: Tue, 11 Aug 2026 11:53:10 +0800 Subject: [PATCH 1352/1433] pppox: drain queued packets on channel handoff PPPIOCGCHAN both returns the channel index and marks a PPPOX socket as bound to generic PPP, despite its getter semantic. Packets received before that transition are queued on sk_receive_queue, but a bound socket is no longer readable. Such packets therefore remain queued until the socket is destroyed. After marking a socket bound, wait for receive paths that observed the old state to finish queueing packets, and then drain the queue into generic PPP. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Qingfang Deng Link: https://patch.msgid.link/20260811035314.302878-1-qingfang.deng@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ppp/pppox.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/net/ppp/pppox.c b/drivers/net/ppp/pppox.c index 5861a2f6ce3e..a6f72c813bef 100644 --- a/drivers/net/ppp/pppox.c +++ b/drivers/net/ppp/pppox.c @@ -74,7 +74,9 @@ int pppox_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) switch (cmd) { case PPPIOCGCHAN: { + struct sk_buff *skb; int index; + rc = -ENOTCONN; if (!(sk->sk_state & PPPOX_CONNECTED)) break; @@ -85,7 +87,22 @@ int pppox_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) break; rc = 0; + /* PPPIOCGCHAN historically marks the userspace handoff to + * generic PPP; pppd then attaches the returned channel to + * /dev/ppp. + */ sk->sk_state |= PPPOX_BOUND; + /* Let lockless receive paths finish queueing against the old + * state. + */ + synchronize_net(); + /* Drain packets queued before the handoff because a bound + * socket is no longer readable. + */ + while ((skb = skb_dequeue(&sk->sk_receive_queue))) { + skb_orphan(skb); + ppp_input(&po->chan, skb); + } break; } default: From 7f16289b91eb316f170a6bd22d32e6c632f6a5b6 Mon Sep 17 00:00:00 2001 From: Xin Xie Date: Sat, 8 Aug 2026 13:08:14 +0200 Subject: [PATCH 1353/1433] net: hsr: free learned nodes on device setup failure hsr_dev_finalize() can fail after a lower-device RX handler has already been registered (slave A is added before the failable slave B and interlink adds). RX handlers run in softirq regardless of the master's state, so frames received in that window can learn dynamic nodes into node_db, and the error unwind never releases them. Free both owned dynamic databases in the unwind, mirroring hsr_dellink(). proxy_node_db is provably empty on every current error exit (only interlink RX feeds it, and the interlink add is the last failable step) and is freed for symmetry. The order is safe: hsr_del_port() unregisters each RX handler with synchronize_net() before hsr_del_nodes() runs, which removes remaining entries with list_del_rcu() and defers their release with call_rcu() for readers already under RCU. Fixes: 81ba6afd6e64 ("net/hsr: Switch from dev_add_pack() to netdev_rx_handler_register()") Signed-off-by: Xin Xie Link: https://patch.msgid.link/20260808110814.1637-1-xiexinet@gmail.com Signed-off-by: Jakub Kicinski --- net/hsr/hsr_device.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/hsr/hsr_device.c b/net/hsr/hsr_device.c index 5555b71ab19b..9c3078dd38c2 100644 --- a/net/hsr/hsr_device.c +++ b/net/hsr/hsr_device.c @@ -820,6 +820,8 @@ int hsr_dev_finalize(struct net_device *hsr_dev, struct net_device *slave[2], hsr_del_ports(hsr); err_add_master: hsr_del_self_node(hsr); + hsr_del_nodes(&hsr->node_db); + hsr_del_nodes(&hsr->proxy_node_db); if (unregister) unregister_netdevice(hsr_dev); From d09c98a6da215bce2173a292e4b95c8de6ea5d51 Mon Sep 17 00:00:00 2001 From: Anton Protopopov Date: Mon, 10 Aug 2026 12:07:25 +0000 Subject: [PATCH 1354/1433] virtio_net: Fix resize of the RX ring When a AF_XDP socket is attached, the virtnet_rx_resize should resize the rq->xsk_buffs XSK buffer array. Otherwise, when the size grows, the virtnet_rx_resume() causes a write past the end of the array. This is easily reproducable with ethtool -G ens3 rx 32 ./xdpsock -i eth0 -q 0 -r -z & ethtool -G eth0 rx 256 Fixes: e9f3962441c0 ("virtio_net: xsk: rx: support fill with xsk buffer") Signed-off-by: Anton Protopopov Link: https://patch.msgid.link/20260810120728.47445-1-a.s.protopopov@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/virtio_net.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index 3e2a5876c6c8..e34c52d059d3 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -3444,17 +3444,31 @@ static void virtnet_rx_resume_all(struct virtnet_info *vi) static int virtnet_rx_resize(struct virtnet_info *vi, struct receive_queue *rq, u32 ring_num) { + unsigned int old_ring_num = virtqueue_get_vring_size(rq->vq); + struct xdp_buff **tmp_xsk_buffs = NULL; int err, qindex; qindex = rq - vi->rq; + if (rq->xsk_pool && ring_num > old_ring_num) { + tmp_xsk_buffs = kvzalloc_objs(*tmp_xsk_buffs, ring_num); + if (!tmp_xsk_buffs) + return -ENOMEM; + } + virtnet_rx_pause(vi, rq); err = virtqueue_resize(rq->vq, ring_num, virtnet_rq_unmap_free_buf, NULL); + + /* virtqueue_resize may have changed the size even if err != 0 */ + if (tmp_xsk_buffs && virtqueue_get_vring_size(rq->vq) > old_ring_num) + swap(rq->xsk_buffs, tmp_xsk_buffs); + if (err) netdev_err(vi->dev, "resize rx fail: rx queue index: %d err: %d\n", qindex, err); virtnet_rx_resume(vi, rq, true); + kvfree(tmp_xsk_buffs); return err; } From 1f77af0aaf277413ff32f6ff8c2c4282bd64c897 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Tue, 11 Aug 2026 18:37:32 +0800 Subject: [PATCH 1355/1433] net: ravb: avoid dereferencing an invalid PTP clock The PTP clock is unavailable before the first open, so querying its index can dereference a NULL pointer. Registration failures can also leave an error pointer in priv->ptp.clock. Cache the PHC index separately and report -1 while no clock is registered. Normalize registration errors to NULL and preserve the static timestamping capabilities. Fixes: a0d2f20650e8 ("Renesas Ethernet AVB PTP clock driver") Cc: stable@vger.kernel.org Reviewed-by: Vadim Fedorenko Signed-off-by: Xuanqiang Luo Link: https://patch.msgid.link/20260811103733.62599-2-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/renesas/ravb.h | 1 + drivers/net/ethernet/renesas/ravb_main.c | 3 ++- drivers/net/ethernet/renesas/ravb_ptp.c | 15 +++++++++++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/renesas/ravb.h b/drivers/net/ethernet/renesas/ravb.h index 5e56ec9b1013..2a4fcb12a63c 100644 --- a/drivers/net/ethernet/renesas/ravb.h +++ b/drivers/net/ethernet/renesas/ravb.h @@ -1028,6 +1028,7 @@ struct ravb_ptp_perout { struct ravb_ptp { struct ptp_clock *clock; struct ptp_clock_info info; + int phc_index; u32 default_addend; u32 current_addend; int extts[N_EXT_TS]; diff --git a/drivers/net/ethernet/renesas/ravb_main.c b/drivers/net/ethernet/renesas/ravb_main.c index 5f88733094d0..db0229e00849 100644 --- a/drivers/net/ethernet/renesas/ravb_main.c +++ b/drivers/net/ethernet/renesas/ravb_main.c @@ -1779,7 +1779,7 @@ static int ravb_get_ts_info(struct net_device *ndev, (1 << HWTSTAMP_FILTER_NONE) | (1 << HWTSTAMP_FILTER_PTP_V2_L2_EVENT) | (1 << HWTSTAMP_FILTER_ALL); - info->phc_index = ptp_clock_index(priv->ptp.clock); + info->phc_index = READ_ONCE(priv->ptp.phc_index); } return 0; @@ -2953,6 +2953,7 @@ static int ravb_probe(struct platform_device *pdev) priv->rstc = rstc; priv->ndev = ndev; priv->pdev = pdev; + priv->ptp.phc_index = -1; priv->num_tx_ring[RAVB_BE] = BE_TX_RING_SIZE; priv->num_rx_ring[RAVB_BE] = BE_RX_RING_SIZE; if (info->nc_queues) { diff --git a/drivers/net/ethernet/renesas/ravb_ptp.c b/drivers/net/ethernet/renesas/ravb_ptp.c index 226c6c0ab945..cbec7c057d71 100644 --- a/drivers/net/ethernet/renesas/ravb_ptp.c +++ b/drivers/net/ethernet/renesas/ravb_ptp.c @@ -315,6 +315,7 @@ void ravb_ptp_interrupt(struct net_device *ndev) void ravb_ptp_init(struct net_device *ndev, struct platform_device *pdev) { struct ravb_private *priv = netdev_priv(ndev); + struct ptp_clock *clock; unsigned long flags; priv->ptp.info = ravb_ptp_info; @@ -327,7 +328,15 @@ void ravb_ptp_init(struct net_device *ndev, struct platform_device *pdev) ravb_modify(ndev, GCCR, GCCR_TCSS, GCCR_TCSS_ADJGPTP); spin_unlock_irqrestore(&priv->lock, flags); - priv->ptp.clock = ptp_clock_register(&priv->ptp.info, &pdev->dev); + clock = ptp_clock_register(&priv->ptp.info, &pdev->dev); + if (IS_ERR(clock)) { + netdev_err(ndev, "failed to register PTP clock: %pe\n", clock); + clock = NULL; + } + + priv->ptp.clock = clock; + if (clock) + WRITE_ONCE(priv->ptp.phc_index, ptp_clock_index(clock)); } void ravb_ptp_stop(struct net_device *ndev) @@ -337,5 +346,7 @@ void ravb_ptp_stop(struct net_device *ndev) ravb_write(ndev, 0, GIC); ravb_write(ndev, 0, GIS); - ptp_clock_unregister(priv->ptp.clock); + WRITE_ONCE(priv->ptp.phc_index, -1); + if (priv->ptp.clock) + ptp_clock_unregister(priv->ptp.clock); } From 1cb9663789c5b7a12fcd419fcca6d6254c398252 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Tue, 11 Aug 2026 18:37:33 +0800 Subject: [PATCH 1356/1433] net: ravb: serialize PTP clock teardown ravb_ptp_interrupt() can race with ravb_ptp_stop() and pass the clock to ptp_clock_event() while ptp_clock_unregister() is freeing it. This can lead to a use-after-free. Use READ_ONCE() and WRITE_ONCE() for lockless access to the clock pointer. Atomically detach it with xchg() before disabling PTP interrupts, then synchronize all IRQs which can invoke ravb_ptp_interrupt() before unregistering the detached clock. A handler which read the old pointer completes before the clock is unregistered, while later handlers read NULL and skip the event. Fixes: a0d2f20650e8 ("Renesas Ethernet AVB PTP clock driver") Cc: stable@vger.kernel.org Signed-off-by: Xuanqiang Luo Link: https://patch.msgid.link/20260811103733.62599-3-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/renesas/ravb.h | 2 ++ drivers/net/ethernet/renesas/ravb_main.c | 6 ++-- drivers/net/ethernet/renesas/ravb_ptp.c | 37 +++++++++++++++++++----- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/drivers/net/ethernet/renesas/ravb.h b/drivers/net/ethernet/renesas/ravb.h index 2a4fcb12a63c..3ee4c6108189 100644 --- a/drivers/net/ethernet/renesas/ravb.h +++ b/drivers/net/ethernet/renesas/ravb.h @@ -1124,6 +1124,8 @@ struct ravb_private { int msg_enable; int speed; int emac_irq; + int err_irq; + int mgmt_irq; unsigned no_avb_link:1; unsigned avb_link_active_low:1; diff --git a/drivers/net/ethernet/renesas/ravb_main.c b/drivers/net/ethernet/renesas/ravb_main.c index db0229e00849..ea1c7e536791 100644 --- a/drivers/net/ethernet/renesas/ravb_main.c +++ b/drivers/net/ethernet/renesas/ravb_main.c @@ -2885,11 +2885,13 @@ static int ravb_setup_irqs(struct ravb_private *priv) return error; if (info->err_mgmt_irqs) { - error = ravb_setup_irq(priv, "err_a", "err_a", NULL, ravb_multi_interrupt); + error = ravb_setup_irq(priv, "err_a", "err_a", &priv->err_irq, + ravb_multi_interrupt); if (error) return error; - error = ravb_setup_irq(priv, "mgmt_a", "mgmt_a", NULL, ravb_multi_interrupt); + error = ravb_setup_irq(priv, "mgmt_a", "mgmt_a", &priv->mgmt_irq, + ravb_multi_interrupt); if (error) return error; } diff --git a/drivers/net/ethernet/renesas/ravb_ptp.c b/drivers/net/ethernet/renesas/ravb_ptp.c index cbec7c057d71..43218bc15b15 100644 --- a/drivers/net/ethernet/renesas/ravb_ptp.c +++ b/drivers/net/ethernet/renesas/ravb_ptp.c @@ -289,16 +289,17 @@ static const struct ptp_clock_info ravb_ptp_info = { void ravb_ptp_interrupt(struct net_device *ndev) { struct ravb_private *priv = netdev_priv(ndev); + struct ptp_clock *clock = READ_ONCE(priv->ptp.clock); u32 gis = ravb_read(ndev, GIS); gis &= ravb_read(ndev, GIC); - if (gis & GIS_PTCF) { + if ((gis & GIS_PTCF) && clock) { struct ptp_clock_event event; event.type = PTP_CLOCK_EXTTS; event.index = 0; event.timestamp = ravb_read(ndev, GCPT); - ptp_clock_event(priv->ptp.clock, &event); + ptp_clock_event(clock, &event); } if (gis & GIS_PTMF) { struct ravb_ptp_perout *perout = priv->ptp.perout; @@ -334,19 +335,39 @@ void ravb_ptp_init(struct net_device *ndev, struct platform_device *pdev) clock = NULL; } - priv->ptp.clock = clock; + WRITE_ONCE(priv->ptp.clock, clock); if (clock) WRITE_ONCE(priv->ptp.phc_index, ptp_clock_index(clock)); } +static void ravb_ptp_disable(struct net_device *ndev) +{ + ravb_write(ndev, 0, GIC); + ravb_write(ndev, 0, GIS); +} + +static void ravb_ptp_sync_irqs(struct net_device *ndev) +{ + struct ravb_private *priv = netdev_priv(ndev); + + synchronize_irq(ndev->irq); + if (priv->info->err_mgmt_irqs) { + synchronize_irq(priv->err_irq); + synchronize_irq(priv->mgmt_irq); + } +} + void ravb_ptp_stop(struct net_device *ndev) { struct ravb_private *priv = netdev_priv(ndev); - - ravb_write(ndev, 0, GIC); - ravb_write(ndev, 0, GIS); + struct ptp_clock *clock; WRITE_ONCE(priv->ptp.phc_index, -1); - if (priv->ptp.clock) - ptp_clock_unregister(priv->ptp.clock); + clock = xchg(&priv->ptp.clock, NULL); + + ravb_ptp_disable(ndev); + ravb_ptp_sync_irqs(ndev); + + if (clock) + ptp_clock_unregister(clock); } From 984f831dda31b3a18f47454cf64989f65402879e Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Wed, 12 Aug 2026 14:53:41 -0700 Subject: [PATCH 1357/1433] vxlan: vnifilter: enforce exact length of GROUP/GROUP6 attributes The VXLAN VNI filter entry policy declares the GROUP/GROUP6 address attributes as NLA_BINARY with only a maximum length, so validate_nla() accepts a payload shorter than the address. The GROUP consumer reads it with nla_get_in_addr(), an unconditional 4-byte load, so a short attribute over-reads up to 3 bytes of uninitialised slab data, which are stored into remote_ip and echoed back via RTM_GETTUNNEL, disclosing kernel memory. Switch both entries to NLA_POLICY_EXACT_LEN() so the validator rejects any GROUP/GROUP6 that is not exactly 4 / 16 bytes; a valid address is always sent at full width. Fixes: f9c4bb0b245c ("vxlan: vni filtering support on collect metadata device") Reported-by: Weiming Shi Signed-off-by: Xiang Mei Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260812215341.763123-1-xmei5@asu.edu Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_vnifilter.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/net/vxlan/vxlan_vnifilter.c b/drivers/net/vxlan/vxlan_vnifilter.c index 3e76f4e21094..dd94085e0886 100644 --- a/drivers/net/vxlan/vxlan_vnifilter.c +++ b/drivers/net/vxlan/vxlan_vnifilter.c @@ -462,10 +462,8 @@ static int vxlan_vnifilter_dump(struct sk_buff *skb, struct netlink_callback *cb static const struct nla_policy vni_filter_entry_policy[VXLAN_VNIFILTER_ENTRY_MAX + 1] = { [VXLAN_VNIFILTER_ENTRY_START] = { .type = NLA_U32 }, [VXLAN_VNIFILTER_ENTRY_END] = { .type = NLA_U32 }, - [VXLAN_VNIFILTER_ENTRY_GROUP] = { .type = NLA_BINARY, - .len = sizeof_field(struct iphdr, daddr) }, - [VXLAN_VNIFILTER_ENTRY_GROUP6] = { .type = NLA_BINARY, - .len = sizeof(struct in6_addr) }, + [VXLAN_VNIFILTER_ENTRY_GROUP] = NLA_POLICY_EXACT_LEN(sizeof_field(struct iphdr, daddr)), + [VXLAN_VNIFILTER_ENTRY_GROUP6] = NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)), }; static const struct nla_policy vni_filter_policy[VXLAN_VNIFILTER_MAX + 1] = { From 7b196e27ad58e612ad1c04b347d0c2135045aa14 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Thu, 13 Aug 2026 23:31:31 +0800 Subject: [PATCH 1358/1433] net: dsa: mv88e6xxx: Fix PCS link check on CMODE read error mv88e6352_pcs_link_check() ignores errors returned by port_get_cmode(). If the port status register read fails, mv88e6352_port_get_cmode() returns without setting cmode. The link check then compares an uninitialized value and may incorrectly treat the PCS as active. Save the return value and fail the link check after releasing the register lock. marvell_c22_pcs_get_state() initializes the reported link state to down before calling the check, so a read failure is handled safely until a later poll succeeds. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 85764555442f ("net: dsa: mv88e6xxx: convert 88e6352 to phylink_pcs") Signed-off-by: Ruoyu Wang Reviewed-by: Vladimir Oltean Link: https://patch.msgid.link/20260813153131.3952970-1-ruoyuw560@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mv88e6xxx/pcs-6352.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/dsa/mv88e6xxx/pcs-6352.c b/drivers/net/dsa/mv88e6xxx/pcs-6352.c index 4228ae5bb9db..437054711a2d 100644 --- a/drivers/net/dsa/mv88e6xxx/pcs-6352.c +++ b/drivers/net/dsa/mv88e6xxx/pcs-6352.c @@ -305,13 +305,16 @@ static bool mv88e6352_pcs_link_check(struct marvell_c22_pcs *mpcs) struct mv88e6xxx_port *port = mpcs->port; struct mv88e6xxx_chip *chip = port->chip; u8 cmode; + int err; /* Port 4 can be in auto-media mode. Check that the port is * associated with the mpcs. */ mv88e6xxx_reg_lock(chip); - chip->info->ops->port_get_cmode(chip, port->port, &cmode); + err = chip->info->ops->port_get_cmode(chip, port->port, &cmode); mv88e6xxx_reg_unlock(chip); + if (err) + return false; return cmode == MV88E6XXX_PORT_STS_CMODE_100BASEX || cmode == MV88E6XXX_PORT_STS_CMODE_1000BASEX || From ba6f76db61fc01ff85b50041daf54bf94be074cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Lebrun?= Date: Wed, 12 Aug 2026 10:03:15 +0200 Subject: [PATCH 1359/1433] net: macb: drop "consistent" from alloc/free function names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 4df95131ea80 ("net/macb: change RX path for GEM") those functions have not been only allocating or freeing consistent memory mappings. Rename from macb_alloc_consistent() to macb_alloc() and from macb_free_consistent() to macb_free(). Acked-by: Conor Dooley Reviewed-by: Nicolai Buchwitz Signed-off-by: Théo Lebrun Link: https://patch.msgid.link/20260812-macb-context-v9-1-7ddbf5f715e0@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb_main.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index d394f1f43b68..ae7d4bb2706b 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -2646,7 +2646,7 @@ static unsigned int macb_rx_ring_size_per_queue(struct macb *bp) return macb_dma_desc_get_size(bp) * bp->rx_ring_size + bp->rx_bd_rd_prefetch; } -static void macb_free_consistent(struct macb *bp) +static void macb_free(struct macb *bp) { struct device *dev = &bp->pdev->dev; struct macb_queue *queue; @@ -2728,7 +2728,7 @@ static int macb_alloc_rx_buffers(struct macb *bp) return 0; } -static int macb_alloc_consistent(struct macb *bp) +static int macb_alloc(struct macb *bp) { struct device *dev = &bp->pdev->dev; dma_addr_t tx_dma, rx_dma; @@ -2786,7 +2786,7 @@ static int macb_alloc_consistent(struct macb *bp) return 0; out_err: - macb_free_consistent(bp); + macb_free(bp); return -ENOMEM; } @@ -3174,7 +3174,7 @@ static int macb_open(struct net_device *dev) /* RX buffers initialization */ macb_init_rx_buffer_size(bp, bufsz); - err = macb_alloc_consistent(bp); + err = macb_alloc(bp); if (err) { netdev_err(dev, "Unable to allocate DMA memory (error %d)\n", err); @@ -3219,7 +3219,7 @@ static int macb_open(struct net_device *dev) napi_disable(&queue->napi_rx); napi_disable(&queue->napi_tx); } - macb_free_consistent(bp); + macb_free(bp); pm_exit: pm_runtime_put_sync(&bp->pdev->dev); return err; @@ -3252,7 +3252,7 @@ static int macb_close(struct net_device *dev) netif_carrier_off(dev); spin_unlock_irqrestore(&bp->lock, flags); - macb_free_consistent(bp); + macb_free(bp); if (bp->ptp_info) bp->ptp_info->ptp_remove(dev); From 07362f68e61d82ee53da0e9ae2c7f981c3538861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Lebrun?= Date: Wed, 12 Aug 2026 10:03:16 +0200 Subject: [PATCH 1360/1433] net: macb: unify device pointer naming convention MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Here are all device pointer variable permutations inside MACB: struct device *dev; struct net_device *dev; struct net_device *ndev; struct net_device *netdev; struct pci_dev *pdev; // inside macb_pci.c struct phy_device *phy; struct phy_device *phydev; struct platform_device *pdev; struct platform_device *plat_dev; // inside macb_pci.c Unify to this convention: struct device *dev; struct net_device *netdev; struct pci_dev *pci; struct phy_device *phydev; struct platform_device *pdev; Ensure nothing slipped through using ctags tooling: ⟩ ctags -o - --kinds-c='{local}{member}{parameter}' \ --fields='{typeref}' drivers/net/ethernet/cadence/* | \ awk -F"\t" ' $NF~/struct:.*(device|dev) / {print $NF, $1}' | \ sort -u typeref:struct:device * dev typeref:struct:in_device * idev // ignored typeref:struct:net_device * netdev typeref:struct:pci_dev * pci typeref:struct:phy_device * phydev typeref:struct:platform_device * pdev Also fix some printk() calls to use __func__ instead of hardcoding. This silences some checkpatch.pl warnings and doesn't deserve a separate commit. Reviewed-by: Conor Dooley Reviewed-by: Nicolai Buchwitz Signed-off-by: Théo Lebrun Link: https://patch.msgid.link/20260812-macb-context-v9-2-7ddbf5f715e0@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb.h | 20 +- drivers/net/ethernet/cadence/macb_main.c | 642 ++++++++++++----------- drivers/net/ethernet/cadence/macb_pci.c | 46 +- drivers/net/ethernet/cadence/macb_ptp.c | 18 +- 4 files changed, 365 insertions(+), 361 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb.h b/drivers/net/ethernet/cadence/macb.h index a11052565436..1e1f52285a39 100644 --- a/drivers/net/ethernet/cadence/macb.h +++ b/drivers/net/ethernet/cadence/macb.h @@ -1201,11 +1201,11 @@ struct macb_or_gem_ops { /* MACB-PTP interface: adapt to platform needs. */ struct macb_ptp_info { - void (*ptp_init)(struct net_device *ndev); - void (*ptp_remove)(struct net_device *ndev); + void (*ptp_init)(struct net_device *netdev); + void (*ptp_remove)(struct net_device *netdev); s32 (*get_ptp_max_adj)(void); unsigned int (*get_tsu_rate)(struct macb *bp); - int (*get_ts_info)(struct net_device *dev, + int (*get_ts_info)(struct net_device *netdev, struct kernel_ethtool_ts_info *info); int (*get_hwtst)(struct net_device *netdev, struct kernel_hwtstamp_config *tstamp_config); @@ -1320,7 +1320,7 @@ struct macb { struct clk *tx_clk; struct clk *rx_clk; struct clk *tsu_clk; - struct net_device *dev; + struct net_device *netdev; /* Protects hw_stats and ethtool_stats */ spinlock_t stats_lock; union { @@ -1400,8 +1400,8 @@ enum macb_bd_control { TSTAMP_ALL_FRAMES, }; -void gem_ptp_init(struct net_device *ndev); -void gem_ptp_remove(struct net_device *ndev); +void gem_ptp_init(struct net_device *netdev); +void gem_ptp_remove(struct net_device *netdev); void gem_ptp_txstamp(struct macb *bp, struct sk_buff *skb, struct macb_dma_desc *desc); void gem_ptp_rxstamp(struct macb *bp, struct sk_buff *skb, struct macb_dma_desc *desc); static inline void gem_ptp_do_txstamp(struct macb *bp, struct sk_buff *skb, struct macb_dma_desc *desc) @@ -1420,14 +1420,14 @@ static inline void gem_ptp_do_rxstamp(struct macb *bp, struct sk_buff *skb, stru gem_ptp_rxstamp(bp, skb, desc); } -int gem_get_hwtst(struct net_device *dev, +int gem_get_hwtst(struct net_device *netdev, struct kernel_hwtstamp_config *tstamp_config); -int gem_set_hwtst(struct net_device *dev, +int gem_set_hwtst(struct net_device *netdev, struct kernel_hwtstamp_config *tstamp_config, struct netlink_ext_ack *extack); #else -static inline void gem_ptp_init(struct net_device *ndev) { } -static inline void gem_ptp_remove(struct net_device *ndev) { } +static inline void gem_ptp_init(struct net_device *netdev) { } +static inline void gem_ptp_remove(struct net_device *netdev) { } static inline void gem_ptp_do_txstamp(struct macb *bp, struct sk_buff *skb, struct macb_dma_desc *desc) { } static inline void gem_ptp_do_rxstamp(struct macb *bp, struct sk_buff *skb, struct macb_dma_desc *desc) { } diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index ae7d4bb2706b..3ae76d1d2a0d 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -252,9 +252,9 @@ static void macb_set_hwaddr(struct macb *bp) u32 bottom; u16 top; - bottom = get_unaligned_le32(bp->dev->dev_addr); + bottom = get_unaligned_le32(bp->netdev->dev_addr); macb_or_gem_writel(bp, SA1B, bottom); - top = get_unaligned_le16(bp->dev->dev_addr + 4); + top = get_unaligned_le16(bp->netdev->dev_addr + 4); macb_or_gem_writel(bp, SA1T, top); if (gem_has_ptp(bp)) { @@ -291,13 +291,13 @@ static void macb_get_hwaddr(struct macb *bp) addr[5] = (top >> 8) & 0xff; if (is_valid_ether_addr(addr)) { - eth_hw_addr_set(bp->dev, addr); + eth_hw_addr_set(bp->netdev, addr); return; } } dev_info(&bp->pdev->dev, "invalid hw address, using random\n"); - eth_hw_addr_random(bp->dev); + eth_hw_addr_random(bp->netdev); } static int macb_mdio_wait_for_idle(struct macb *bp) @@ -509,12 +509,12 @@ static void macb_set_tx_clk(struct macb *bp, int speed) ferr = abs(rate_rounded - rate); ferr = DIV_ROUND_UP(ferr, rate / 100000); if (ferr > 5) - netdev_warn(bp->dev, + netdev_warn(bp->netdev, "unable to generate target frequency: %ld Hz\n", rate); if (clk_set_rate(bp->tx_clk, rate_rounded)) - netdev_err(bp->dev, "adjusting tx_clk failed.\n"); + netdev_err(bp->netdev, "adjusting tx_clk failed.\n"); } static void macb_usx_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode, @@ -697,8 +697,8 @@ static void macb_tx_lpi_wake(struct macb *bp) static void macb_mac_disable_tx_lpi(struct phylink_config *config) { - struct net_device *ndev = to_net_dev(config->dev); - struct macb *bp = netdev_priv(ndev); + struct net_device *netdev = to_net_dev(config->dev); + struct macb *bp = netdev_priv(netdev); unsigned long flags; cancel_delayed_work_sync(&bp->tx_lpi_work); @@ -712,8 +712,8 @@ static void macb_mac_disable_tx_lpi(struct phylink_config *config) static int macb_mac_enable_tx_lpi(struct phylink_config *config, u32 timer, bool tx_clk_stop) { - struct net_device *ndev = to_net_dev(config->dev); - struct macb *bp = netdev_priv(ndev); + struct net_device *netdev = to_net_dev(config->dev); + struct macb *bp = netdev_priv(netdev); unsigned long flags; spin_lock_irqsave(&bp->lock, flags); @@ -732,8 +732,8 @@ static int macb_mac_enable_tx_lpi(struct phylink_config *config, u32 timer, static void macb_mac_config(struct phylink_config *config, unsigned int mode, const struct phylink_link_state *state) { - struct net_device *ndev = to_net_dev(config->dev); - struct macb *bp = netdev_priv(ndev); + struct net_device *netdev = to_net_dev(config->dev); + struct macb *bp = netdev_priv(netdev); unsigned long flags; u32 old_ctrl, ctrl; u32 old_ncr, ncr; @@ -774,8 +774,8 @@ static void macb_mac_config(struct phylink_config *config, unsigned int mode, static void macb_mac_link_down(struct phylink_config *config, unsigned int mode, phy_interface_t interface) { - struct net_device *ndev = to_net_dev(config->dev); - struct macb *bp = netdev_priv(ndev); + struct net_device *netdev = to_net_dev(config->dev); + struct macb *bp = netdev_priv(netdev); struct macb_queue *queue; unsigned int q; u32 ctrl; @@ -789,7 +789,7 @@ static void macb_mac_link_down(struct phylink_config *config, unsigned int mode, ctrl = macb_readl(bp, NCR) & ~(MACB_BIT(RE) | MACB_BIT(TE)); macb_writel(bp, NCR, ctrl); - netif_tx_stop_all_queues(ndev); + netif_tx_stop_all_queues(netdev); } /* Use juggling algorithm to left rotate tx ring and tx skb array */ @@ -884,13 +884,13 @@ static void gem_shuffle_tx_rings(struct macb *bp) } static void macb_mac_link_up(struct phylink_config *config, - struct phy_device *phy, + struct phy_device *phydev, unsigned int mode, phy_interface_t interface, int speed, int duplex, bool tx_pause, bool rx_pause) { - struct net_device *ndev = to_net_dev(config->dev); - struct macb *bp = netdev_priv(ndev); + struct net_device *netdev = to_net_dev(config->dev); + struct macb *bp = netdev_priv(netdev); struct macb_queue *queue; unsigned long flags; unsigned int q; @@ -946,14 +946,14 @@ static void macb_mac_link_up(struct phylink_config *config, macb_writel(bp, NCR, ctrl | MACB_BIT(RE) | MACB_BIT(TE)); - netif_tx_wake_all_queues(ndev); + netif_tx_wake_all_queues(netdev); } static struct phylink_pcs *macb_mac_select_pcs(struct phylink_config *config, phy_interface_t interface) { - struct net_device *ndev = to_net_dev(config->dev); - struct macb *bp = netdev_priv(ndev); + struct net_device *netdev = to_net_dev(config->dev); + struct macb *bp = netdev_priv(netdev); if (interface == PHY_INTERFACE_MODE_10GBASER) return &bp->phylink_usx_pcs; @@ -982,7 +982,7 @@ static bool macb_phy_handle_exists(struct device_node *dn) static int macb_phylink_connect(struct macb *bp) { struct device_node *dn = bp->pdev->dev.of_node; - struct net_device *dev = bp->dev; + struct net_device *netdev = bp->netdev; struct phy_device *phydev; int ret; @@ -992,7 +992,7 @@ static int macb_phylink_connect(struct macb *bp) if (!dn || (ret && !macb_phy_handle_exists(dn))) { phydev = phy_find_first(bp->mii_bus); if (!phydev) { - netdev_err(dev, "no PHY found\n"); + netdev_err(netdev, "no PHY found\n"); return -ENXIO; } @@ -1001,7 +1001,7 @@ static int macb_phylink_connect(struct macb *bp) } if (ret) { - netdev_err(dev, "Could not attach PHY (%d)\n", ret); + netdev_err(netdev, "Could not attach PHY (%d)\n", ret); return ret; } @@ -1013,21 +1013,21 @@ static int macb_phylink_connect(struct macb *bp) static void macb_get_pcs_fixed_state(struct phylink_config *config, struct phylink_link_state *state) { - struct net_device *ndev = to_net_dev(config->dev); - struct macb *bp = netdev_priv(ndev); + struct net_device *netdev = to_net_dev(config->dev); + struct macb *bp = netdev_priv(netdev); state->link = (macb_readl(bp, NSR) & MACB_BIT(NSR_LINK)) != 0; } /* based on au1000_eth. c*/ -static int macb_mii_probe(struct net_device *dev) +static int macb_mii_probe(struct net_device *netdev) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); bp->phylink_sgmii_pcs.ops = &macb_phylink_pcs_ops; bp->phylink_usx_pcs.ops = &macb_phylink_usx_pcs_ops; - bp->phylink_config.dev = &dev->dev; + bp->phylink_config.dev = &netdev->dev; bp->phylink_config.type = PHYLINK_NETDEV; bp->phylink_config.mac_managed_pm = true; @@ -1086,7 +1086,7 @@ static int macb_mii_probe(struct net_device *dev) bp->phylink = phylink_create(&bp->phylink_config, bp->pdev->dev.fwnode, bp->phy_interface, &macb_phylink_ops); if (IS_ERR(bp->phylink)) { - netdev_err(dev, "Could not create a phylink instance (%ld)\n", + netdev_err(netdev, "Could not create a phylink instance (%ld)\n", PTR_ERR(bp->phylink)); return PTR_ERR(bp->phylink); } @@ -1133,7 +1133,7 @@ static int macb_mii_init(struct macb *bp) */ mdio_np = of_get_child_by_name(np, "mdio"); if (!mdio_np && of_phy_is_fixed_link(np)) - return macb_mii_probe(bp->dev); + return macb_mii_probe(bp->netdev); /* Enable management port */ macb_writel(bp, NCR, MACB_BIT(MPE)); @@ -1154,13 +1154,13 @@ static int macb_mii_init(struct macb *bp) bp->mii_bus->priv = bp; bp->mii_bus->parent = &bp->pdev->dev; - dev_set_drvdata(&bp->dev->dev, bp->mii_bus); + dev_set_drvdata(&bp->netdev->dev, bp->mii_bus); err = macb_mdiobus_register(bp, mdio_np); if (err) goto err_out_free_mdiobus; - err = macb_mii_probe(bp->dev); + err = macb_mii_probe(bp->netdev); if (err) goto err_out_unregister_bus; @@ -1268,8 +1268,8 @@ static void macb_tx_error_task(struct work_struct *work) unsigned long flags; queue_index = queue - bp->queues; - netdev_vdbg(bp->dev, "macb_tx_error_task: q = %u, t = %u, h = %u\n", - queue_index, queue->tx_tail, queue->tx_head); + netdev_vdbg(bp->netdev, "%s: q = %u, t = %u, h = %u\n", + __func__, queue_index, queue->tx_tail, queue->tx_head); /* Prevent the queue NAPI TX poll from running, as it calls * macb_tx_complete(), which in turn may call netif_wake_subqueue(). @@ -1281,14 +1281,14 @@ static void macb_tx_error_task(struct work_struct *work) spin_lock_irqsave(&bp->lock, flags); /* Make sure nobody is trying to queue up new packets */ - netif_tx_stop_all_queues(bp->dev); + netif_tx_stop_all_queues(bp->netdev); /* Stop transmission now * (in case we have just queued new packets) * macb/gem must be halted to write TBQP register */ if (macb_halt_tx(bp)) { - netdev_err(bp->dev, "BUG: halt tx timed out\n"); + netdev_err(bp->netdev, "BUG: halt tx timed out\n"); macb_writel(bp, NCR, macb_readl(bp, NCR) & (~MACB_BIT(TE))); halt_timeout = true; } @@ -1317,13 +1317,13 @@ static void macb_tx_error_task(struct work_struct *work) * since it's the only one written back by the hardware */ if (!(ctrl & MACB_BIT(TX_BUF_EXHAUSTED))) { - netdev_vdbg(bp->dev, "txerr skb %u (data %p) TX complete\n", + netdev_vdbg(bp->netdev, "txerr skb %u (data %p) TX complete\n", macb_tx_ring_wrap(bp, tail), skb->data); - bp->dev->stats.tx_packets++; + bp->netdev->stats.tx_packets++; queue->stats.tx_packets++; packets++; - bp->dev->stats.tx_bytes += skb->len; + bp->netdev->stats.tx_bytes += skb->len; queue->stats.tx_bytes += skb->len; bytes += skb->len; } @@ -1333,7 +1333,7 @@ static void macb_tx_error_task(struct work_struct *work) * those. Statistics are updated by hardware. */ if (ctrl & MACB_BIT(TX_BUF_EXHAUSTED)) - netdev_err(bp->dev, + netdev_err(bp->netdev, "BUG: TX buffers exhausted mid-frame\n"); desc->ctrl = ctrl | MACB_BIT(TX_USED); @@ -1342,7 +1342,7 @@ static void macb_tx_error_task(struct work_struct *work) macb_tx_unmap(bp, tx_skb, 0); } - netdev_tx_completed_queue(netdev_get_tx_queue(bp->dev, queue_index), + netdev_tx_completed_queue(netdev_get_tx_queue(bp->netdev, queue_index), packets, bytes); /* Set end of TX queue */ @@ -1367,7 +1367,7 @@ static void macb_tx_error_task(struct work_struct *work) macb_writel(bp, NCR, macb_readl(bp, NCR) | MACB_BIT(TE)); /* Now we are ready to start transmission again */ - netif_tx_start_all_queues(bp->dev); + netif_tx_start_all_queues(bp->netdev); macb_writel(bp, NCR, macb_readl(bp, NCR) | MACB_BIT(TSTART)); spin_unlock_irqrestore(&bp->lock, flags); @@ -1446,12 +1446,12 @@ static int macb_tx_complete(struct macb_queue *queue, int budget) !ptp_one_step_sync(skb)) gem_ptp_do_txstamp(bp, skb, desc); - netdev_vdbg(bp->dev, "skb %u (data %p) TX complete\n", + netdev_vdbg(bp->netdev, "skb %u (data %p) TX complete\n", macb_tx_ring_wrap(bp, tail), skb->data); - bp->dev->stats.tx_packets++; + bp->netdev->stats.tx_packets++; queue->stats.tx_packets++; - bp->dev->stats.tx_bytes += skb->len; + bp->netdev->stats.tx_bytes += skb->len; queue->stats.tx_bytes += skb->len; packets++; bytes += skb->len; @@ -1469,14 +1469,14 @@ static int macb_tx_complete(struct macb_queue *queue, int budget) } } - netdev_tx_completed_queue(netdev_get_tx_queue(bp->dev, queue_index), + netdev_tx_completed_queue(netdev_get_tx_queue(bp->netdev, queue_index), packets, bytes); queue->tx_tail = tail; - if (__netif_subqueue_stopped(bp->dev, queue_index) && + if (__netif_subqueue_stopped(bp->netdev, queue_index) && CIRC_CNT(queue->tx_head, queue->tx_tail, bp->tx_ring_size) <= MACB_TX_WAKEUP_THRESH(bp)) - netif_wake_subqueue(bp->dev, queue_index); + netif_wake_subqueue(bp->netdev, queue_index); spin_unlock_irqrestore(&queue->tx_ptr_lock, flags); if (packets) @@ -1504,9 +1504,9 @@ static void gem_rx_refill(struct macb_queue *queue) if (!queue->rx_skbuff[entry]) { /* allocate sk_buff for this free entry in ring */ - skb = netdev_alloc_skb(bp->dev, bp->rx_buffer_size); + skb = netdev_alloc_skb(bp->netdev, bp->rx_buffer_size); if (unlikely(!skb)) { - netdev_err(bp->dev, + netdev_err(bp->netdev, "Unable to allocate sk_buff\n"); break; } @@ -1555,8 +1555,8 @@ static void gem_rx_refill(struct macb_queue *queue) /* Make descriptor updates visible to hardware */ wmb(); - netdev_vdbg(bp->dev, "rx ring: queue: %p, prepared head %d, tail %d\n", - queue, queue->rx_prepared_head, queue->rx_tail); + netdev_vdbg(bp->netdev, "rx ring: queue: %p, prepared head %d, tail %d\n", + queue, queue->rx_prepared_head, queue->rx_tail); } /* Mark DMA descriptors from begin up to and not including end as unused */ @@ -1616,17 +1616,17 @@ static int gem_rx(struct macb_queue *queue, struct napi_struct *napi, count++; if (!(ctrl & MACB_BIT(RX_SOF) && ctrl & MACB_BIT(RX_EOF))) { - netdev_err(bp->dev, + netdev_err(bp->netdev, "not whole frame pointed by descriptor\n"); - bp->dev->stats.rx_dropped++; + bp->netdev->stats.rx_dropped++; queue->stats.rx_dropped++; break; } skb = queue->rx_skbuff[entry]; if (unlikely(!skb)) { - netdev_err(bp->dev, + netdev_err(bp->netdev, "inconsistent Rx descriptor chain\n"); - bp->dev->stats.rx_dropped++; + bp->netdev->stats.rx_dropped++; queue->stats.rx_dropped++; break; } @@ -1634,28 +1634,29 @@ static int gem_rx(struct macb_queue *queue, struct napi_struct *napi, queue->rx_skbuff[entry] = NULL; len = ctrl & bp->rx_frm_len_mask; - netdev_vdbg(bp->dev, "gem_rx %u (len %u)\n", entry, len); + netdev_vdbg(bp->netdev, "%s %u (len %u)\n", + __func__, entry, len); skb_put(skb, len); dma_unmap_single(&bp->pdev->dev, addr, bp->rx_buffer_size, DMA_FROM_DEVICE); - skb->protocol = eth_type_trans(skb, bp->dev); + skb->protocol = eth_type_trans(skb, bp->netdev); skb_checksum_none_assert(skb); - if (bp->dev->features & NETIF_F_RXCSUM && - !(bp->dev->flags & IFF_PROMISC) && + if (bp->netdev->features & NETIF_F_RXCSUM && + !(bp->netdev->flags & IFF_PROMISC) && GEM_BFEXT(RX_CSUM, ctrl) & GEM_RX_CSUM_CHECKED_MASK) skb->ip_summed = CHECKSUM_UNNECESSARY; - bp->dev->stats.rx_packets++; + bp->netdev->stats.rx_packets++; queue->stats.rx_packets++; - bp->dev->stats.rx_bytes += skb->len; + bp->netdev->stats.rx_bytes += skb->len; queue->stats.rx_bytes += skb->len; gem_ptp_do_rxstamp(bp, skb, desc); #if defined(DEBUG) && defined(VERBOSE_DEBUG) - netdev_vdbg(bp->dev, "received skb of length %u, csum: %08x\n", + netdev_vdbg(bp->netdev, "received skb of length %u, csum: %08x\n", skb->len, skb->csum); print_hex_dump(KERN_DEBUG, " mac: ", DUMP_PREFIX_ADDRESS, 16, 1, skb_mac_header(skb), 16, true); @@ -1684,9 +1685,10 @@ static int macb_rx_frame(struct macb_queue *queue, struct napi_struct *napi, desc = macb_rx_desc(queue, last_frag); len = desc->ctrl & bp->rx_frm_len_mask; - netdev_vdbg(bp->dev, "macb_rx_frame frags %u - %u (len %u)\n", - macb_rx_ring_wrap(bp, first_frag), - macb_rx_ring_wrap(bp, last_frag), len); + netdev_vdbg(bp->netdev, "%s frags %u - %u (len %u)\n", + __func__, + macb_rx_ring_wrap(bp, first_frag), + macb_rx_ring_wrap(bp, last_frag), len); /* The ethernet header starts NET_IP_ALIGN bytes into the * first buffer. Since the header is 14 bytes, this makes the @@ -1696,9 +1698,9 @@ static int macb_rx_frame(struct macb_queue *queue, struct napi_struct *napi, * the two padding bytes into the skb so that we avoid hitting * the slowpath in memcpy(), and pull them off afterwards. */ - skb = netdev_alloc_skb(bp->dev, len + NET_IP_ALIGN); + skb = netdev_alloc_skb(bp->netdev, len + NET_IP_ALIGN); if (!skb) { - bp->dev->stats.rx_dropped++; + bp->netdev->stats.rx_dropped++; for (frag = first_frag; ; frag++) { desc = macb_rx_desc(queue, frag); desc->addr &= ~MACB_BIT(RX_USED); @@ -1742,11 +1744,11 @@ static int macb_rx_frame(struct macb_queue *queue, struct napi_struct *napi, wmb(); __skb_pull(skb, NET_IP_ALIGN); - skb->protocol = eth_type_trans(skb, bp->dev); + skb->protocol = eth_type_trans(skb, bp->netdev); - bp->dev->stats.rx_packets++; - bp->dev->stats.rx_bytes += skb->len; - netdev_vdbg(bp->dev, "received skb of length %u, csum: %08x\n", + bp->netdev->stats.rx_packets++; + bp->netdev->stats.rx_bytes += skb->len; + netdev_vdbg(bp->netdev, "received skb of length %u, csum: %08x\n", skb->len, skb->csum); napi_gro_receive(napi, skb); @@ -1826,7 +1828,7 @@ static int macb_rx(struct macb_queue *queue, struct napi_struct *napi, unsigned long flags; u32 ctrl; - netdev_err(bp->dev, "RX queue corruption: reset it\n"); + netdev_err(bp->netdev, "RX queue corruption: reset it\n"); spin_lock_irqsave(&bp->lock, flags); @@ -1873,7 +1875,7 @@ static int macb_rx_poll(struct napi_struct *napi, int budget) work_done = bp->macbgem_ops.mog_rx(queue, napi, budget); - netdev_vdbg(bp->dev, "RX poll: queue = %u, work_done = %d, budget = %d\n", + netdev_vdbg(bp->netdev, "RX poll: queue = %u, work_done = %d, budget = %d\n", (unsigned int)(queue - bp->queues), work_done, budget); if (work_done < budget && napi_complete_done(napi, work_done)) { @@ -1892,7 +1894,7 @@ static int macb_rx_poll(struct napi_struct *napi, int budget) if (macb_rx_pending(queue)) { queue_writel(queue, IDR, bp->rx_intr_mask); macb_queue_isr_clear(bp, queue, MACB_BIT(RCOMP)); - netdev_vdbg(bp->dev, "poll: packets pending, reschedule\n"); + netdev_vdbg(bp->netdev, "poll: packets pending, reschedule\n"); napi_schedule(napi); } } @@ -1956,11 +1958,11 @@ static int macb_tx_poll(struct napi_struct *napi, int budget) rmb(); // ensure txubr_pending is up to date if (queue->txubr_pending) { queue->txubr_pending = false; - netdev_vdbg(bp->dev, "poll: tx restart\n"); + netdev_vdbg(bp->netdev, "poll: tx restart\n"); macb_tx_restart(queue); } - netdev_vdbg(bp->dev, "TX poll: queue = %u, work_done = %d, budget = %d\n", + netdev_vdbg(bp->netdev, "TX poll: queue = %u, work_done = %d, budget = %d\n", (unsigned int)(queue - bp->queues), work_done, budget); if (work_done < budget && napi_complete_done(napi, work_done)) { @@ -1979,7 +1981,7 @@ static int macb_tx_poll(struct napi_struct *napi, int budget) if (macb_tx_complete_pending(queue)) { queue_writel(queue, IDR, MACB_BIT(TCOMP)); macb_queue_isr_clear(bp, queue, MACB_BIT(TCOMP)); - netdev_vdbg(bp->dev, "TX poll: packets pending, reschedule\n"); + netdev_vdbg(bp->netdev, "TX poll: packets pending, reschedule\n"); napi_schedule(napi); } } @@ -1990,7 +1992,7 @@ static int macb_tx_poll(struct napi_struct *napi, int budget) static void macb_hresp_error_task(struct work_struct *work) { struct macb *bp = from_work(bp, work, hresp_err_bh_work); - struct net_device *dev = bp->dev; + struct net_device *netdev = bp->netdev; struct macb_queue *queue; unsigned int q; u32 ctrl; @@ -2004,8 +2006,8 @@ static void macb_hresp_error_task(struct work_struct *work) ctrl &= ~(MACB_BIT(RE) | MACB_BIT(TE)); macb_writel(bp, NCR, ctrl); - netif_tx_stop_all_queues(dev); - netif_carrier_off(dev); + netif_tx_stop_all_queues(netdev); + netif_carrier_off(netdev); bp->macbgem_ops.mog_init_rings(bp); @@ -2022,8 +2024,8 @@ static void macb_hresp_error_task(struct work_struct *work) ctrl |= MACB_BIT(RE) | MACB_BIT(TE); macb_writel(bp, NCR, ctrl); - netif_carrier_on(dev); - netif_tx_start_all_queues(dev); + netif_carrier_on(netdev); + netif_tx_start_all_queues(netdev); } static void macb_wol_interrupt(struct macb_queue *queue, u32 status) @@ -2032,7 +2034,7 @@ static void macb_wol_interrupt(struct macb_queue *queue, u32 status) queue_writel(queue, IDR, MACB_BIT(WOL)); macb_writel(bp, WOL, 0); - netdev_vdbg(bp->dev, "MACB WoL: queue = %u, isr = 0x%08lx\n", + netdev_vdbg(bp->netdev, "MACB WoL: queue = %u, isr = 0x%08lx\n", (unsigned int)(queue - bp->queues), (unsigned long)status); macb_queue_isr_clear(bp, queue, MACB_BIT(WOL)); @@ -2045,7 +2047,7 @@ static void gem_wol_interrupt(struct macb_queue *queue, u32 status) queue_writel(queue, IDR, GEM_BIT(WOL)); gem_writel(bp, WOL, 0); - netdev_vdbg(bp->dev, "GEM WoL: queue = %u, isr = 0x%08lx\n", + netdev_vdbg(bp->netdev, "GEM WoL: queue = %u, isr = 0x%08lx\n", (unsigned int)(queue - bp->queues), (unsigned long)status); macb_queue_isr_clear(bp, queue, GEM_BIT(WOL)); @@ -2055,10 +2057,10 @@ static void gem_wol_interrupt(struct macb_queue *queue, u32 status) static int macb_interrupt_misc(struct macb_queue *queue, u32 status) { struct macb *bp = queue->bp; - struct net_device *dev; + struct net_device *netdev; u32 ctrl; - dev = bp->dev; + netdev = bp->netdev; if (unlikely(status & (MACB_TX_ERR_FLAGS))) { queue_writel(queue, IDR, MACB_TX_INT_FLAGS); @@ -2099,7 +2101,7 @@ static int macb_interrupt_misc(struct macb_queue *queue, u32 status) if (status & MACB_BIT(HRESP)) { queue_work(system_bh_wq, &bp->hresp_err_bh_work); - netdev_err(dev, "DMA bus error: HRESP not OK\n"); + netdev_err(netdev, "DMA bus error: HRESP not OK\n"); macb_queue_isr_clear(bp, queue, MACB_BIT(HRESP)); } @@ -2118,7 +2120,7 @@ static irqreturn_t macb_interrupt(int irq, void *dev_id) { struct macb_queue *queue = dev_id; struct macb *bp = queue->bp; - struct net_device *dev = bp->dev; + struct net_device *netdev = bp->netdev; u32 status; status = queue_readl(queue, ISR); @@ -2130,13 +2132,13 @@ static irqreturn_t macb_interrupt(int irq, void *dev_id) while (status) { /* close possible race with dev_close */ - if (unlikely(!netif_running(dev))) { + if (unlikely(!netif_running(netdev))) { queue_writel(queue, IDR, -1); macb_queue_isr_clear(bp, queue, -1); break; } - netdev_vdbg(bp->dev, "queue = %u, isr = 0x%08lx\n", + netdev_vdbg(netdev, "queue = %u, isr = 0x%08lx\n", (unsigned int)(queue - bp->queues), (unsigned long)status); @@ -2181,16 +2183,16 @@ static irqreturn_t macb_interrupt(int irq, void *dev_id) /* Polling receive - used by netconsole and other diagnostic tools * to allow network i/o with interrupts disabled. */ -static void macb_poll_controller(struct net_device *dev) +static void macb_poll_controller(struct net_device *netdev) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct macb_queue *queue; unsigned long flags; unsigned int q; local_irq_save(flags); for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) - macb_interrupt(dev->irq, queue); + macb_interrupt(netdev->irq, queue); local_irq_restore(flags); } #endif @@ -2277,7 +2279,7 @@ static unsigned int macb_tx_map(struct macb *bp, /* Should never happen */ if (unlikely(!tx_skb)) { - netdev_err(bp->dev, "BUG! empty skb!\n"); + netdev_err(bp->netdev, "BUG! empty skb!\n"); return 0; } @@ -2328,7 +2330,7 @@ static unsigned int macb_tx_map(struct macb *bp, if (i == queue->tx_head) { ctrl |= MACB_BF(TX_LSO, lso_ctrl); ctrl |= MACB_BF(TX_TCP_SEQ_SRC, seq_ctrl); - if ((bp->dev->features & NETIF_F_HW_CSUM) && + if ((bp->netdev->features & NETIF_F_HW_CSUM) && skb->ip_summed != CHECKSUM_PARTIAL && !lso_ctrl && !ptp_one_step_sync(skb)) ctrl |= MACB_BIT(TX_NOCRC); @@ -2352,7 +2354,7 @@ static unsigned int macb_tx_map(struct macb *bp, return 0; dma_error: - netdev_err(bp->dev, "TX DMA map failed\n"); + netdev_err(bp->netdev, "TX DMA map failed\n"); for (i = queue->tx_head; i != tx_head; i++) { tx_skb = macb_tx_skb(queue, i); @@ -2364,7 +2366,7 @@ static unsigned int macb_tx_map(struct macb *bp, } static netdev_features_t macb_features_check(struct sk_buff *skb, - struct net_device *dev, + struct net_device *netdev, netdev_features_t features) { unsigned int nr_frags, f; @@ -2416,7 +2418,7 @@ static inline int macb_clear_csum(struct sk_buff *skb) return 0; } -static int macb_pad_and_fcs(struct sk_buff **skb, struct net_device *ndev) +static int macb_pad_and_fcs(struct sk_buff **skb, struct net_device *netdev) { bool cloned = skb_cloned(*skb) || skb_header_cloned(*skb) || skb_is_nonlinear(*skb); @@ -2425,7 +2427,7 @@ static int macb_pad_and_fcs(struct sk_buff **skb, struct net_device *ndev) struct sk_buff *nskb; u32 fcs; - if (!(ndev->features & NETIF_F_HW_CSUM) || + if (!(netdev->features & NETIF_F_HW_CSUM) || !((*skb)->ip_summed != CHECKSUM_PARTIAL) || skb_shinfo(*skb)->gso_size || ptp_one_step_sync(*skb)) return 0; @@ -2467,10 +2469,11 @@ static int macb_pad_and_fcs(struct sk_buff **skb, struct net_device *ndev) return 0; } -static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev) +static netdev_tx_t macb_start_xmit(struct sk_buff *skb, + struct net_device *netdev) { u16 queue_index = skb_get_queue_mapping(skb); - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct macb_queue *queue = &bp->queues[queue_index]; unsigned int desc_cnt, nr_frags, frag_size, f; unsigned int hdrlen; @@ -2483,7 +2486,7 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev) return ret; } - if (macb_pad_and_fcs(&skb, dev)) { + if (macb_pad_and_fcs(&skb, netdev)) { dev_kfree_skb_any(skb); return ret; } @@ -2502,7 +2505,7 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev) else hdrlen = skb_tcp_all_headers(skb); if (skb_headlen(skb) < hdrlen) { - netdev_err(bp->dev, "Error - LSO headers fragmented!!!\n"); + netdev_err(bp->netdev, "Error - LSO headers fragmented!!!\n"); /* if this is required, would need to copy to single buffer */ return NETDEV_TX_BUSY; } @@ -2510,7 +2513,7 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev) hdrlen = umin(skb_headlen(skb), bp->max_tx_length); #if defined(DEBUG) && defined(VERBOSE_DEBUG) - netdev_vdbg(bp->dev, + netdev_vdbg(bp->netdev, "start_xmit: queue %hu len %u head %p data %p tail %p end %p\n", queue_index, skb->len, skb->head, skb->data, skb_tail_pointer(skb), skb_end_pointer(skb)); @@ -2538,8 +2541,8 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev) /* This is a hard error, log it. */ if (CIRC_SPACE(queue->tx_head, queue->tx_tail, bp->tx_ring_size) < desc_cnt) { - netif_stop_subqueue(dev, queue_index); - netdev_dbg(bp->dev, "tx_head = %u, tx_tail = %u\n", + netif_stop_subqueue(netdev, queue_index); + netdev_dbg(netdev, "tx_head = %u, tx_tail = %u\n", queue->tx_head, queue->tx_tail); ret = NETDEV_TX_BUSY; goto unlock; @@ -2554,7 +2557,7 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev) /* Make newly initialized descriptor visible to hardware */ wmb(); skb_tx_timestamp(skb); - netdev_tx_sent_queue(netdev_get_tx_queue(bp->dev, queue_index), + netdev_tx_sent_queue(netdev_get_tx_queue(bp->netdev, queue_index), skb->len); spin_lock(&bp->lock); @@ -2563,7 +2566,7 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev) spin_unlock(&bp->lock); if (CIRC_SPACE(queue->tx_head, queue->tx_tail, bp->tx_ring_size) < 1) - netif_stop_subqueue(dev, queue_index); + netif_stop_subqueue(netdev, queue_index); unlock: spin_unlock_irqrestore(&queue->tx_ptr_lock, flags); @@ -2579,7 +2582,7 @@ static void macb_init_rx_buffer_size(struct macb *bp, size_t size) bp->rx_buffer_size = MIN(size, RX_BUFFER_MAX); if (bp->rx_buffer_size % RX_BUFFER_MULTIPLE) { - netdev_dbg(bp->dev, + netdev_dbg(bp->netdev, "RX buffer must be multiple of %d bytes, expanding\n", RX_BUFFER_MULTIPLE); bp->rx_buffer_size = @@ -2587,8 +2590,8 @@ static void macb_init_rx_buffer_size(struct macb *bp, size_t size) } } - netdev_dbg(bp->dev, "mtu [%u] rx_buffer_size [%zu]\n", - bp->dev->mtu, bp->rx_buffer_size); + netdev_dbg(bp->netdev, "mtu [%u] rx_buffer_size [%zu]\n", + bp->netdev->mtu, bp->rx_buffer_size); } static void gem_free_rx_buffers(struct macb *bp) @@ -2679,7 +2682,7 @@ static void macb_free(struct macb *bp) } queue->stats.tx_dropped += dropped; - bp->dev->stats.tx_dropped += dropped; + bp->netdev->stats.tx_dropped += dropped; kfree(queue->tx_skb); queue->tx_skb = NULL; @@ -2704,7 +2707,7 @@ static int gem_alloc_rx_buffers(struct macb *bp) if (!queue->rx_skbuff) return -ENOMEM; else - netdev_dbg(bp->dev, + netdev_dbg(bp->netdev, "Allocated %d RX struct sk_buff entries at %p\n", bp->rx_ring_size, queue->rx_skbuff); } @@ -2722,7 +2725,7 @@ static int macb_alloc_rx_buffers(struct macb *bp) if (!queue->rx_buffers) return -ENOMEM; - netdev_dbg(bp->dev, + netdev_dbg(bp->netdev, "Allocated RX buffers of %d bytes at %08lx (mapped %p)\n", size, (unsigned long)queue->rx_buffers_dma, queue->rx_buffers); return 0; @@ -2748,14 +2751,14 @@ static int macb_alloc(struct macb *bp) tx = dma_alloc_coherent(dev, size, &tx_dma, GFP_KERNEL); if (!tx || upper_32_bits(tx_dma) != upper_32_bits(tx_dma + size - 1)) goto out_err; - netdev_dbg(bp->dev, "Allocated %zu bytes for %u TX rings at %08lx (mapped %p)\n", + netdev_dbg(bp->netdev, "Allocated %zu bytes for %u TX rings at %08lx (mapped %p)\n", size, bp->num_queues, (unsigned long)tx_dma, tx); size = bp->num_queues * macb_rx_ring_size_per_queue(bp); rx = dma_alloc_coherent(dev, size, &rx_dma, GFP_KERNEL); if (!rx || upper_32_bits(rx_dma) != upper_32_bits(rx_dma + size - 1)) goto out_err; - netdev_dbg(bp->dev, "Allocated %zu bytes for %u RX rings at %08lx (mapped %p)\n", + netdev_dbg(bp->netdev, "Allocated %zu bytes for %u RX rings at %08lx (mapped %p)\n", size, bp->num_queues, (unsigned long)rx_dma, rx); for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) { @@ -2983,7 +2986,7 @@ static void macb_configure_dma(struct macb *bp) else dmacfg |= GEM_BIT(ENDIA_DESC); /* CPU in big endian */ - if (bp->dev->features & NETIF_F_HW_CSUM) + if (bp->netdev->features & NETIF_F_HW_CSUM) dmacfg |= GEM_BIT(TXCOEN); else dmacfg &= ~GEM_BIT(TXCOEN); @@ -2993,7 +2996,7 @@ static void macb_configure_dma(struct macb *bp) dmacfg |= GEM_BIT(ADDR64); if (macb_dma_ptp(bp)) dmacfg |= GEM_BIT(RXEXT) | GEM_BIT(TXEXT); - netdev_dbg(bp->dev, "Cadence configure DMA with 0x%08x\n", + netdev_dbg(bp->netdev, "Cadence configure DMA with 0x%08x\n", dmacfg); gem_writel(bp, DMACFG, dmacfg); } @@ -3017,11 +3020,11 @@ static void macb_init_hw(struct macb *bp) config |= MACB_BIT(JFRAME); /* Enable jumbo frames */ else config |= MACB_BIT(BIG); /* Receive oversized frames */ - if (bp->dev->flags & IFF_PROMISC) + if (bp->netdev->flags & IFF_PROMISC) config |= MACB_BIT(CAF); /* Copy All Frames */ - else if (macb_is_gem(bp) && bp->dev->features & NETIF_F_RXCSUM) + else if (macb_is_gem(bp) && bp->netdev->features & NETIF_F_RXCSUM) config |= GEM_BIT(RXCOEN); - if (!(bp->dev->flags & IFF_BROADCAST)) + if (!(bp->netdev->flags & IFF_BROADCAST)) config |= MACB_BIT(NBC); /* No BroadCast */ config |= macb_dbw(bp); macb_writel(bp, NCFGR, config); @@ -3095,17 +3098,17 @@ static int hash_get_index(__u8 *addr) } /* Add multicast addresses to the internal multicast-hash table. */ -static void macb_sethashtable(struct net_device *dev) +static void macb_sethashtable(struct net_device *netdev) { struct netdev_hw_addr *ha; unsigned long mc_filter[2]; unsigned int bitnr; - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); mc_filter[0] = 0; mc_filter[1] = 0; - netdev_for_each_mc_addr(ha, dev) { + netdev_for_each_mc_addr(ha, netdev) { bitnr = hash_get_index(ha->addr); mc_filter[bitnr >> 5] |= 1 << (bitnr & 31); } @@ -3115,14 +3118,14 @@ static void macb_sethashtable(struct net_device *dev) } /* Enable/Disable promiscuous and multicast modes. */ -static void macb_set_rx_mode(struct net_device *dev) +static void macb_set_rx_mode(struct net_device *netdev) { unsigned long cfg; - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); cfg = macb_readl(bp, NCFGR); - if (dev->flags & IFF_PROMISC) { + if (netdev->flags & IFF_PROMISC) { /* Enable promiscuous mode */ cfg |= MACB_BIT(CAF); @@ -3134,20 +3137,20 @@ static void macb_set_rx_mode(struct net_device *dev) cfg &= ~MACB_BIT(CAF); /* Enable RX checksum offload only if requested */ - if (macb_is_gem(bp) && dev->features & NETIF_F_RXCSUM) + if (macb_is_gem(bp) && netdev->features & NETIF_F_RXCSUM) cfg |= GEM_BIT(RXCOEN); } - if (dev->flags & IFF_ALLMULTI) { + if (netdev->flags & IFF_ALLMULTI) { /* Enable all multicast mode */ macb_or_gem_writel(bp, HRB, -1); macb_or_gem_writel(bp, HRT, -1); cfg |= MACB_BIT(NCFGR_MTI); - } else if (!netdev_mc_empty(dev)) { + } else if (!netdev_mc_empty(netdev)) { /* Enable specific multicasts */ - macb_sethashtable(dev); + macb_sethashtable(netdev); cfg |= MACB_BIT(NCFGR_MTI); - } else if (dev->flags & (~IFF_ALLMULTI)) { + } else if (netdev->flags & (~IFF_ALLMULTI)) { /* Disable all multicast mode */ macb_or_gem_writel(bp, HRB, 0); macb_or_gem_writel(bp, HRT, 0); @@ -3157,15 +3160,15 @@ static void macb_set_rx_mode(struct net_device *dev) macb_writel(bp, NCFGR, cfg); } -static int macb_open(struct net_device *dev) +static int macb_open(struct net_device *netdev) { - size_t bufsz = dev->mtu + ETH_HLEN + ETH_FCS_LEN + NET_IP_ALIGN; - struct macb *bp = netdev_priv(dev); + size_t bufsz = netdev->mtu + ETH_HLEN + ETH_FCS_LEN + NET_IP_ALIGN; + struct macb *bp = netdev_priv(netdev); struct macb_queue *queue; unsigned int q; int err; - netdev_dbg(bp->dev, "open\n"); + netdev_dbg(bp->netdev, "open\n"); err = pm_runtime_resume_and_get(&bp->pdev->dev); if (err < 0) @@ -3176,7 +3179,7 @@ static int macb_open(struct net_device *dev) err = macb_alloc(bp); if (err) { - netdev_err(dev, "Unable to allocate DMA memory (error %d)\n", + netdev_err(netdev, "Unable to allocate DMA memory (error %d)\n", err); goto pm_exit; } @@ -3203,10 +3206,10 @@ static int macb_open(struct net_device *dev) if (err) goto phy_off; - netif_tx_start_all_queues(dev); + netif_tx_start_all_queues(netdev); if (bp->ptp_info) - bp->ptp_info->ptp_init(dev); + bp->ptp_info->ptp_init(netdev); return 0; @@ -3225,19 +3228,19 @@ static int macb_open(struct net_device *dev) return err; } -static int macb_close(struct net_device *dev) +static int macb_close(struct net_device *netdev) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct macb_queue *queue; unsigned long flags; unsigned int q; - netif_tx_stop_all_queues(dev); + netif_tx_stop_all_queues(netdev); for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) { napi_disable(&queue->napi_rx); napi_disable(&queue->napi_tx); - netdev_tx_reset_queue(netdev_get_tx_queue(dev, q)); + netdev_tx_reset_queue(netdev_get_tx_queue(netdev, q)); } cancel_delayed_work_sync(&bp->tx_lpi_work); @@ -3249,38 +3252,38 @@ static int macb_close(struct net_device *dev) spin_lock_irqsave(&bp->lock, flags); macb_reset_hw(bp); - netif_carrier_off(dev); + netif_carrier_off(netdev); spin_unlock_irqrestore(&bp->lock, flags); macb_free(bp); if (bp->ptp_info) - bp->ptp_info->ptp_remove(dev); + bp->ptp_info->ptp_remove(netdev); pm_runtime_put(&bp->pdev->dev); return 0; } -static int macb_change_mtu(struct net_device *dev, int new_mtu) +static int macb_change_mtu(struct net_device *netdev, int new_mtu) { - if (netif_running(dev)) + if (netif_running(netdev)) return -EBUSY; - WRITE_ONCE(dev->mtu, new_mtu); + WRITE_ONCE(netdev->mtu, new_mtu); return 0; } -static int macb_set_mac_addr(struct net_device *dev, void *addr) +static int macb_set_mac_addr(struct net_device *netdev, void *addr) { int err; - err = eth_mac_addr(dev, addr); + err = eth_mac_addr(netdev, addr); if (err < 0) return err; - macb_set_hwaddr(netdev_priv(dev)); + macb_set_hwaddr(netdev_priv(netdev)); return 0; } @@ -3318,7 +3321,7 @@ static void gem_get_stats(struct macb *bp, struct rtnl_link_stats64 *nstat) struct gem_stats *hwstat = &bp->hw_stats.gem; spin_lock_irq(&bp->stats_lock); - if (netif_running(bp->dev)) + if (netif_running(bp->netdev)) gem_update_stats(bp); nstat->rx_errors = (hwstat->rx_frame_check_sequence_errors + @@ -3351,10 +3354,10 @@ static void gem_get_stats(struct macb *bp, struct rtnl_link_stats64 *nstat) spin_unlock_irq(&bp->stats_lock); } -static void gem_get_ethtool_stats(struct net_device *dev, +static void gem_get_ethtool_stats(struct net_device *netdev, struct ethtool_stats *stats, u64 *data) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); spin_lock_irq(&bp->stats_lock); gem_update_stats(bp); @@ -3363,9 +3366,9 @@ static void gem_get_ethtool_stats(struct net_device *dev, spin_unlock_irq(&bp->stats_lock); } -static int gem_get_sset_count(struct net_device *dev, int sset) +static int gem_get_sset_count(struct net_device *netdev, int sset) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); switch (sset) { case ETH_SS_STATS: @@ -3375,9 +3378,9 @@ static int gem_get_sset_count(struct net_device *dev, int sset) } } -static void gem_get_ethtool_strings(struct net_device *dev, u32 sset, u8 *p) +static void gem_get_ethtool_strings(struct net_device *netdev, u32 sset, u8 *p) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct macb_queue *queue; unsigned int i; unsigned int q; @@ -3396,13 +3399,13 @@ static void gem_get_ethtool_strings(struct net_device *dev, u32 sset, u8 *p) } } -static void macb_get_stats(struct net_device *dev, +static void macb_get_stats(struct net_device *netdev, struct rtnl_link_stats64 *nstat) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct macb_stats *hwstat = &bp->hw_stats.macb; - netdev_stats_to_stats64(nstat, &bp->dev->stats); + netdev_stats_to_stats64(nstat, &bp->netdev->stats); if (macb_is_gem(bp)) { gem_get_stats(bp, nstat); return; @@ -3446,10 +3449,10 @@ static void macb_get_stats(struct net_device *dev, spin_unlock_irq(&bp->stats_lock); } -static void macb_get_pause_stats(struct net_device *dev, +static void macb_get_pause_stats(struct net_device *netdev, struct ethtool_pause_stats *pause_stats) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct macb_stats *hwstat = &bp->hw_stats.macb; spin_lock_irq(&bp->stats_lock); @@ -3459,10 +3462,10 @@ static void macb_get_pause_stats(struct net_device *dev, spin_unlock_irq(&bp->stats_lock); } -static void gem_get_pause_stats(struct net_device *dev, +static void gem_get_pause_stats(struct net_device *netdev, struct ethtool_pause_stats *pause_stats) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct gem_stats *hwstat = &bp->hw_stats.gem; spin_lock_irq(&bp->stats_lock); @@ -3472,10 +3475,10 @@ static void gem_get_pause_stats(struct net_device *dev, spin_unlock_irq(&bp->stats_lock); } -static void macb_get_eth_mac_stats(struct net_device *dev, +static void macb_get_eth_mac_stats(struct net_device *netdev, struct ethtool_eth_mac_stats *mac_stats) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct macb_stats *hwstat = &bp->hw_stats.macb; spin_lock_irq(&bp->stats_lock); @@ -3497,10 +3500,10 @@ static void macb_get_eth_mac_stats(struct net_device *dev, spin_unlock_irq(&bp->stats_lock); } -static void gem_get_eth_mac_stats(struct net_device *dev, +static void gem_get_eth_mac_stats(struct net_device *netdev, struct ethtool_eth_mac_stats *mac_stats) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct gem_stats *hwstat = &bp->hw_stats.gem; spin_lock_irq(&bp->stats_lock); @@ -3530,10 +3533,10 @@ static void gem_get_eth_mac_stats(struct net_device *dev, } /* TODO: Report SQE test errors when added to phy_stats */ -static void macb_get_eth_phy_stats(struct net_device *dev, +static void macb_get_eth_phy_stats(struct net_device *netdev, struct ethtool_eth_phy_stats *phy_stats) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct macb_stats *hwstat = &bp->hw_stats.macb; spin_lock_irq(&bp->stats_lock); @@ -3542,10 +3545,10 @@ static void macb_get_eth_phy_stats(struct net_device *dev, spin_unlock_irq(&bp->stats_lock); } -static void gem_get_eth_phy_stats(struct net_device *dev, +static void gem_get_eth_phy_stats(struct net_device *netdev, struct ethtool_eth_phy_stats *phy_stats) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct gem_stats *hwstat = &bp->hw_stats.gem; spin_lock_irq(&bp->stats_lock); @@ -3554,11 +3557,11 @@ static void gem_get_eth_phy_stats(struct net_device *dev, spin_unlock_irq(&bp->stats_lock); } -static void macb_get_rmon_stats(struct net_device *dev, +static void macb_get_rmon_stats(struct net_device *netdev, struct ethtool_rmon_stats *rmon_stats, const struct ethtool_rmon_hist_range **ranges) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct macb_stats *hwstat = &bp->hw_stats.macb; spin_lock_irq(&bp->stats_lock); @@ -3580,11 +3583,11 @@ static const struct ethtool_rmon_hist_range gem_rmon_ranges[] = { { }, }; -static void gem_get_rmon_stats(struct net_device *dev, +static void gem_get_rmon_stats(struct net_device *netdev, struct ethtool_rmon_stats *rmon_stats, const struct ethtool_rmon_hist_range **ranges) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct gem_stats *hwstat = &bp->hw_stats.gem; spin_lock_irq(&bp->stats_lock); @@ -3615,10 +3618,10 @@ static int macb_get_regs_len(struct net_device *netdev) return MACB_GREGS_NBR * sizeof(u32); } -static void macb_get_regs(struct net_device *dev, struct ethtool_regs *regs, +static void macb_get_regs(struct net_device *netdev, struct ethtool_regs *regs, void *p) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); unsigned int tail, head; u32 *regs_buff = p; @@ -3735,16 +3738,16 @@ static int macb_set_ringparam(struct net_device *netdev, return 0; } - if (netif_running(bp->dev)) { + if (netif_running(bp->netdev)) { reset = 1; - macb_close(bp->dev); + macb_close(bp->netdev); } bp->rx_ring_size = new_rx_size; bp->tx_ring_size = new_tx_size; if (reset) - macb_open(bp->dev); + macb_open(bp->netdev); return 0; } @@ -3771,13 +3774,13 @@ static s32 gem_get_ptp_max_adj(void) return 64000000; } -static int gem_get_ts_info(struct net_device *dev, +static int gem_get_ts_info(struct net_device *netdev, struct kernel_ethtool_ts_info *info) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); if (!macb_dma_ptp(bp)) { - ethtool_op_get_ts_info(dev, info); + ethtool_op_get_ts_info(netdev, info); return 0; } @@ -3824,7 +3827,7 @@ static int macb_get_ts_info(struct net_device *netdev, static void gem_enable_flow_filters(struct macb *bp, bool enable) { - struct net_device *netdev = bp->dev; + struct net_device *netdev = bp->netdev; struct ethtool_rx_fs_item *item; u32 t2_scr; int num_t2_scr; @@ -4154,16 +4157,16 @@ static const struct ethtool_ops macb_ethtool_ops = { .set_ringparam = macb_set_ringparam, }; -static int macb_get_eee(struct net_device *dev, struct ethtool_keee *eee) +static int macb_get_eee(struct net_device *netdev, struct ethtool_keee *eee) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); return phylink_ethtool_get_eee(bp->phylink, eee); } -static int macb_set_eee(struct net_device *dev, struct ethtool_keee *eee) +static int macb_set_eee(struct net_device *netdev, struct ethtool_keee *eee) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); return phylink_ethtool_set_eee(bp->phylink, eee); } @@ -4194,43 +4197,43 @@ static const struct ethtool_ops gem_ethtool_ops = { .set_eee = macb_set_eee, }; -static int macb_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +static int macb_ioctl(struct net_device *netdev, struct ifreq *rq, int cmd) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); - if (!netif_running(dev)) + if (!netif_running(netdev)) return -EINVAL; return phylink_mii_ioctl(bp->phylink, rq, cmd); } -static int macb_hwtstamp_get(struct net_device *dev, +static int macb_hwtstamp_get(struct net_device *netdev, struct kernel_hwtstamp_config *cfg) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); - if (!netif_running(dev)) + if (!netif_running(netdev)) return -EINVAL; if (!bp->ptp_info) return -EOPNOTSUPP; - return bp->ptp_info->get_hwtst(dev, cfg); + return bp->ptp_info->get_hwtst(netdev, cfg); } -static int macb_hwtstamp_set(struct net_device *dev, +static int macb_hwtstamp_set(struct net_device *netdev, struct kernel_hwtstamp_config *cfg, struct netlink_ext_ack *extack) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); - if (!netif_running(dev)) + if (!netif_running(netdev)) return -EINVAL; if (!bp->ptp_info) return -EOPNOTSUPP; - return bp->ptp_info->set_hwtst(dev, cfg, extack); + return bp->ptp_info->set_hwtst(netdev, cfg, extack); } static inline void macb_set_txcsum_feature(struct macb *bp, @@ -4253,7 +4256,7 @@ static inline void macb_set_txcsum_feature(struct macb *bp, static inline void macb_set_rxcsum_feature(struct macb *bp, netdev_features_t features) { - struct net_device *netdev = bp->dev; + struct net_device *netdev = bp->netdev; u32 val; if (!macb_is_gem(bp)) @@ -4300,7 +4303,7 @@ static int macb_set_features(struct net_device *netdev, static void macb_restore_features(struct macb *bp) { - struct net_device *netdev = bp->dev; + struct net_device *netdev = bp->netdev; netdev_features_t features = netdev->features; struct ethtool_rx_fs_item *item; @@ -4317,14 +4320,14 @@ static void macb_restore_features(struct macb *bp) macb_set_rxflow_feature(bp, features); } -static int macb_taprio_setup_replace(struct net_device *ndev, +static int macb_taprio_setup_replace(struct net_device *netdev, struct tc_taprio_qopt_offload *conf) { u64 total_on_time = 0, start_time_sec = 0, start_time = conf->base_time; u32 configured_queues = 0, speed = 0, start_time_nsec; struct macb_queue_enst_config *enst_queue; struct tc_taprio_sched_entry *entry; - struct macb *bp = netdev_priv(ndev); + struct macb *bp = netdev_priv(netdev); struct ethtool_link_ksettings kset; struct macb_queue *queue; u32 queue_mask; @@ -4333,13 +4336,13 @@ static int macb_taprio_setup_replace(struct net_device *ndev, int err; if (conf->num_entries > bp->num_queues) { - netdev_err(ndev, "Too many TAPRIO entries: %zu > %d queues\n", + netdev_err(netdev, "Too many TAPRIO entries: %zu > %d queues\n", conf->num_entries, bp->num_queues); return -EINVAL; } if (conf->base_time < 0) { - netdev_err(ndev, "Invalid base_time: must be 0 or positive, got %lld\n", + netdev_err(netdev, "Invalid base_time: must be 0 or positive, got %lld\n", conf->base_time); return -ERANGE; } @@ -4347,13 +4350,13 @@ static int macb_taprio_setup_replace(struct net_device *ndev, /* Get the current link speed */ err = phylink_ethtool_ksettings_get(bp->phylink, &kset); if (unlikely(err)) { - netdev_err(ndev, "Failed to get link settings: %d\n", err); + netdev_err(netdev, "Failed to get link settings: %d\n", err); return err; } speed = kset.base.speed; if (unlikely(speed <= 0)) { - netdev_err(ndev, "Invalid speed: %d\n", speed); + netdev_err(netdev, "Invalid speed: %d\n", speed); return -EINVAL; } @@ -4366,7 +4369,7 @@ static int macb_taprio_setup_replace(struct net_device *ndev, entry = &conf->entries[i]; if (entry->command != TC_TAPRIO_CMD_SET_GATES) { - netdev_err(ndev, "Entry %zu: unsupported command %d\n", + netdev_err(netdev, "Entry %zu: unsupported command %d\n", i, entry->command); err = -EOPNOTSUPP; goto cleanup; @@ -4374,7 +4377,7 @@ static int macb_taprio_setup_replace(struct net_device *ndev, /* Validate gate_mask: must be nonzero, single queue, and within range */ if (!is_power_of_2(entry->gate_mask)) { - netdev_err(ndev, "Entry %zu: gate_mask 0x%x is not a power of 2 (only one queue per entry allowed)\n", + netdev_err(netdev, "Entry %zu: gate_mask 0x%x is not a power of 2 (only one queue per entry allowed)\n", i, entry->gate_mask); err = -EINVAL; goto cleanup; @@ -4383,7 +4386,7 @@ static int macb_taprio_setup_replace(struct net_device *ndev, /* gate_mask must not select queues outside the valid queues */ queue_id = order_base_2(entry->gate_mask); if (queue_id >= bp->num_queues) { - netdev_err(ndev, "Entry %zu: gate_mask 0x%x exceeds queue range (max_queues=%d)\n", + netdev_err(netdev, "Entry %zu: gate_mask 0x%x exceeds queue range (max_queues=%d)\n", i, entry->gate_mask, bp->num_queues); err = -EINVAL; goto cleanup; @@ -4393,7 +4396,7 @@ static int macb_taprio_setup_replace(struct net_device *ndev, start_time_sec = start_time; start_time_nsec = do_div(start_time_sec, NSEC_PER_SEC); if (start_time_sec > GENMASK(GEM_START_TIME_SEC_SIZE - 1, 0)) { - netdev_err(ndev, "Entry %zu: Start time %llu s exceeds hardware limit\n", + netdev_err(netdev, "Entry %zu: Start time %llu s exceeds hardware limit\n", i, start_time_sec); err = -ERANGE; goto cleanup; @@ -4401,7 +4404,7 @@ static int macb_taprio_setup_replace(struct net_device *ndev, /* Check for on time limit */ if (entry->interval > enst_max_hw_interval(speed)) { - netdev_err(ndev, "Entry %zu: interval %u ns exceeds hardware limit %llu ns\n", + netdev_err(netdev, "Entry %zu: interval %u ns exceeds hardware limit %llu ns\n", i, entry->interval, enst_max_hw_interval(speed)); err = -ERANGE; goto cleanup; @@ -4409,7 +4412,7 @@ static int macb_taprio_setup_replace(struct net_device *ndev, /* Check for off time limit*/ if ((conf->cycle_time - entry->interval) > enst_max_hw_interval(speed)) { - netdev_err(ndev, "Entry %zu: off_time %llu ns exceeds hardware limit %llu ns\n", + netdev_err(netdev, "Entry %zu: off_time %llu ns exceeds hardware limit %llu ns\n", i, conf->cycle_time - entry->interval, enst_max_hw_interval(speed)); err = -ERANGE; @@ -4432,13 +4435,13 @@ static int macb_taprio_setup_replace(struct net_device *ndev, /* Check total interval doesn't exceed cycle time */ if (total_on_time > conf->cycle_time) { - netdev_err(ndev, "Total ON %llu ns exceeds cycle time %llu ns\n", + netdev_err(netdev, "Total ON %llu ns exceeds cycle time %llu ns\n", total_on_time, conf->cycle_time); err = -EINVAL; goto cleanup; } - netdev_dbg(ndev, "TAPRIO setup: %zu entries, base_time=%lld ns, cycle_time=%llu ns\n", + netdev_dbg(netdev, "TAPRIO setup: %zu entries, base_time=%lld ns, cycle_time=%llu ns\n", conf->num_entries, conf->base_time, conf->cycle_time); /* All validations passed - proceed with hardware configuration */ @@ -4463,7 +4466,7 @@ static int macb_taprio_setup_replace(struct net_device *ndev, gem_writel(bp, ENST_CONTROL, configured_queues); } - netdev_info(ndev, "TAPRIO configuration completed successfully: %zu entries, %d queues configured\n", + netdev_info(netdev, "TAPRIO configuration completed successfully: %zu entries, %d queues configured\n", conf->num_entries, hweight32(configured_queues)); cleanup: @@ -4471,14 +4474,14 @@ static int macb_taprio_setup_replace(struct net_device *ndev, return err; } -static void macb_taprio_destroy(struct net_device *ndev) +static void macb_taprio_destroy(struct net_device *netdev) { - struct macb *bp = netdev_priv(ndev); + struct macb *bp = netdev_priv(netdev); struct macb_queue *queue; u32 queue_mask; unsigned int q; - netdev_reset_tc(ndev); + netdev_reset_tc(netdev); queue_mask = BIT_U32(bp->num_queues) - 1; scoped_guard(spinlock_irqsave, &bp->lock) { @@ -4493,30 +4496,30 @@ static void macb_taprio_destroy(struct net_device *ndev) queue_writel(queue, ENST_OFF_TIME, 0); } } - netdev_info(ndev, "TAPRIO destroy: All gates disabled\n"); + netdev_info(netdev, "TAPRIO destroy: All gates disabled\n"); } -static int macb_setup_taprio(struct net_device *ndev, +static int macb_setup_taprio(struct net_device *netdev, struct tc_taprio_qopt_offload *taprio) { - struct macb *bp = netdev_priv(ndev); + struct macb *bp = netdev_priv(netdev); int err = 0; - if (unlikely(!(ndev->hw_features & NETIF_F_HW_TC))) + if (unlikely(!(netdev->hw_features & NETIF_F_HW_TC))) return -EOPNOTSUPP; /* Check if Device is in runtime suspend */ if (unlikely(pm_runtime_suspended(&bp->pdev->dev))) { - netdev_err(ndev, "Device is in runtime suspend\n"); + netdev_err(netdev, "Device is in runtime suspend\n"); return -EOPNOTSUPP; } switch (taprio->cmd) { case TAPRIO_CMD_REPLACE: - err = macb_taprio_setup_replace(ndev, taprio); + err = macb_taprio_setup_replace(netdev, taprio); break; case TAPRIO_CMD_DESTROY: - macb_taprio_destroy(ndev); + macb_taprio_destroy(netdev); break; default: err = -EOPNOTSUPP; @@ -4525,23 +4528,23 @@ static int macb_setup_taprio(struct net_device *ndev, return err; } -static int macb_setup_tc(struct net_device *dev, enum tc_setup_type type, +static int macb_setup_tc(struct net_device *netdev, enum tc_setup_type type, void *type_data) { - if (!dev || !type_data) + if (!netdev || !type_data) return -EINVAL; switch (type) { case TC_SETUP_QDISC_TAPRIO: - return macb_setup_taprio(dev, type_data); + return macb_setup_taprio(netdev, type_data); default: return -EOPNOTSUPP; } } -static void macb_tx_timeout(struct net_device *dev, unsigned int q) +static void macb_tx_timeout(struct net_device *netdev, unsigned int q) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); macb_tx_restart(&bp->queues[q]); } @@ -4749,9 +4752,9 @@ static int macb_clk_init(struct platform_device *pdev, struct clk **pclk, static int macb_init_dflt(struct platform_device *pdev) { - struct net_device *dev = platform_get_drvdata(pdev); + struct net_device *netdev = platform_get_drvdata(pdev); unsigned int hw_q, q; - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); struct macb_queue *queue; int err; u32 val, reg; @@ -4767,8 +4770,8 @@ static int macb_init_dflt(struct platform_device *pdev) queue = &bp->queues[q]; queue->bp = bp; spin_lock_init(&queue->tx_ptr_lock); - netif_napi_add(dev, &queue->napi_rx, macb_rx_poll); - netif_napi_add_tx(dev, &queue->napi_tx, macb_tx_poll); + netif_napi_add(netdev, &queue->napi_rx, macb_rx_poll); + netif_napi_add_tx(netdev, &queue->napi_tx, macb_tx_poll); if (hw_q) { queue->ISR = GEM_ISR(hw_q - 1); queue->IER = GEM_IER(hw_q - 1); @@ -4798,7 +4801,7 @@ static int macb_init_dflt(struct platform_device *pdev) */ queue->irq = platform_get_irq(pdev, q); err = devm_request_irq(&pdev->dev, queue->irq, macb_interrupt, - IRQF_SHARED, dev->name, queue); + IRQF_SHARED, netdev->name, queue); if (err) { dev_err(&pdev->dev, "Unable to request IRQ %d (error %d)\n", @@ -4810,7 +4813,7 @@ static int macb_init_dflt(struct platform_device *pdev) q++; } - dev->netdev_ops = &macb_netdev_ops; + netdev->netdev_ops = &macb_netdev_ops; /* setup appropriated routines according to adapter type */ if (macb_is_gem(bp)) { @@ -4818,39 +4821,39 @@ static int macb_init_dflt(struct platform_device *pdev) bp->macbgem_ops.mog_free_rx_buffers = gem_free_rx_buffers; bp->macbgem_ops.mog_init_rings = gem_init_rings; bp->macbgem_ops.mog_rx = gem_rx; - dev->ethtool_ops = &gem_ethtool_ops; + netdev->ethtool_ops = &gem_ethtool_ops; } else { bp->macbgem_ops.mog_alloc_rx_buffers = macb_alloc_rx_buffers; bp->macbgem_ops.mog_free_rx_buffers = macb_free_rx_buffers; bp->macbgem_ops.mog_init_rings = macb_init_rings; bp->macbgem_ops.mog_rx = macb_rx; - dev->ethtool_ops = &macb_ethtool_ops; + netdev->ethtool_ops = &macb_ethtool_ops; } - netdev_sw_irq_coalesce_default_on(dev); + netdev_sw_irq_coalesce_default_on(netdev); - dev->priv_flags |= IFF_LIVE_ADDR_CHANGE; + netdev->priv_flags |= IFF_LIVE_ADDR_CHANGE; /* Set features */ - dev->hw_features = NETIF_F_SG; + netdev->hw_features = NETIF_F_SG; /* Check LSO capability; runtime detection can be overridden by a cap * flag if the hardware is known to be buggy */ if (!(bp->caps & MACB_CAPS_NO_LSO) && GEM_BFEXT(PBUF_LSO, gem_readl(bp, DCFG6))) - dev->hw_features |= MACB_NETIF_LSO; + netdev->hw_features |= MACB_NETIF_LSO; /* Checksum offload is only available on gem with packet buffer */ if (macb_is_gem(bp) && !(bp->caps & MACB_CAPS_FIFO_MODE)) - dev->hw_features |= NETIF_F_HW_CSUM | NETIF_F_RXCSUM; + netdev->hw_features |= NETIF_F_HW_CSUM | NETIF_F_RXCSUM; if (bp->caps & MACB_CAPS_SG_DISABLED) - dev->hw_features &= ~NETIF_F_SG; + netdev->hw_features &= ~NETIF_F_SG; /* Enable HW_TC if hardware supports QBV */ if (bp->caps & MACB_CAPS_QBV) - dev->hw_features |= NETIF_F_HW_TC; + netdev->hw_features |= NETIF_F_HW_TC; - dev->features = dev->hw_features; + netdev->features = netdev->hw_features; /* Check RX Flow Filters support. * Max Rx flows set by availability of screeners & compare regs: @@ -4868,7 +4871,7 @@ static int macb_init_dflt(struct platform_device *pdev) reg = GEM_BFINS(ETHTCMP, (uint16_t)ETH_P_IP, reg); gem_writel_n(bp, ETHT, SCRT2_ETHT, reg); /* Filtering is supported in hw but don't enable it in kernel now */ - dev->hw_features |= NETIF_F_NTUPLE; + netdev->hw_features |= NETIF_F_NTUPLE; /* init Rx flow definitions */ bp->rx_fs_list.count = 0; spin_lock_init(&bp->rx_fs_lock); @@ -5078,9 +5081,9 @@ static void at91ether_stop(struct macb *lp) } /* Open the ethernet interface */ -static int at91ether_open(struct net_device *dev) +static int at91ether_open(struct net_device *netdev) { - struct macb *lp = netdev_priv(dev); + struct macb *lp = netdev_priv(netdev); u32 ctl; int ret; @@ -5102,7 +5105,7 @@ static int at91ether_open(struct net_device *dev) if (ret) goto stop; - netif_start_queue(dev); + netif_start_queue(netdev); return 0; @@ -5114,11 +5117,11 @@ static int at91ether_open(struct net_device *dev) } /* Close the interface */ -static int at91ether_close(struct net_device *dev) +static int at91ether_close(struct net_device *netdev) { - struct macb *lp = netdev_priv(dev); + struct macb *lp = netdev_priv(netdev); - netif_stop_queue(dev); + netif_stop_queue(netdev); phylink_stop(lp->phylink); phylink_disconnect_phy(lp->phylink); @@ -5132,14 +5135,14 @@ static int at91ether_close(struct net_device *dev) /* Transmit packet */ static netdev_tx_t at91ether_start_xmit(struct sk_buff *skb, - struct net_device *dev) + struct net_device *netdev) { - struct macb *lp = netdev_priv(dev); + struct macb *lp = netdev_priv(netdev); if (macb_readl(lp, TSR) & MACB_BIT(RM9200_BNQ)) { int desc = 0; - netif_stop_queue(dev); + netif_stop_queue(netdev); /* Store packet information (to free when Tx completed) */ lp->rm9200_txq[desc].skb = skb; @@ -5148,8 +5151,8 @@ static netdev_tx_t at91ether_start_xmit(struct sk_buff *skb, skb->len, DMA_TO_DEVICE); if (dma_mapping_error(&lp->pdev->dev, lp->rm9200_txq[desc].mapping)) { dev_kfree_skb_any(skb); - dev->stats.tx_dropped++; - netdev_err(dev, "%s: DMA mapping error\n", __func__); + netdev->stats.tx_dropped++; + netdev_err(netdev, "%s: DMA mapping error\n", __func__); return NETDEV_TX_OK; } @@ -5159,7 +5162,8 @@ static netdev_tx_t at91ether_start_xmit(struct sk_buff *skb, macb_writel(lp, TCR, skb->len); } else { - netdev_err(dev, "%s called, but device is busy!\n", __func__); + netdev_err(netdev, "%s called, but device is busy!\n", + __func__); return NETDEV_TX_BUSY; } @@ -5169,9 +5173,9 @@ static netdev_tx_t at91ether_start_xmit(struct sk_buff *skb, /* Extract received frame from buffer descriptors and sent to upper layers. * (Called from interrupt context) */ -static void at91ether_rx(struct net_device *dev) +static void at91ether_rx(struct net_device *netdev) { - struct macb *lp = netdev_priv(dev); + struct macb *lp = netdev_priv(netdev); struct macb_queue *q = &lp->queues[0]; struct macb_dma_desc *desc; unsigned char *p_recv; @@ -5182,21 +5186,21 @@ static void at91ether_rx(struct net_device *dev) while (desc->addr & MACB_BIT(RX_USED)) { p_recv = q->rx_buffers + q->rx_tail * AT91ETHER_MAX_RBUFF_SZ; pktlen = MACB_BF(RX_FRMLEN, desc->ctrl); - skb = netdev_alloc_skb(dev, pktlen + 2); + skb = netdev_alloc_skb(netdev, pktlen + 2); if (skb) { skb_reserve(skb, 2); skb_put_data(skb, p_recv, pktlen); - skb->protocol = eth_type_trans(skb, dev); - dev->stats.rx_packets++; - dev->stats.rx_bytes += pktlen; + skb->protocol = eth_type_trans(skb, netdev); + netdev->stats.rx_packets++; + netdev->stats.rx_bytes += pktlen; netif_rx(skb); } else { - dev->stats.rx_dropped++; + netdev->stats.rx_dropped++; } if (desc->ctrl & MACB_BIT(RX_MHASH_MATCH)) - dev->stats.multicast++; + netdev->stats.multicast++; /* reset ownership bit */ desc->addr &= ~MACB_BIT(RX_USED); @@ -5214,8 +5218,8 @@ static void at91ether_rx(struct net_device *dev) /* MAC interrupt handler */ static irqreturn_t at91ether_interrupt(int irq, void *dev_id) { - struct net_device *dev = dev_id; - struct macb *lp = netdev_priv(dev); + struct net_device *netdev = dev_id; + struct macb *lp = netdev_priv(netdev); u32 intstatus, ctl; unsigned int desc; @@ -5226,13 +5230,13 @@ static irqreturn_t at91ether_interrupt(int irq, void *dev_id) /* Receive complete */ if (intstatus & MACB_BIT(RCOMP)) - at91ether_rx(dev); + at91ether_rx(netdev); /* Transmit complete */ if (intstatus & MACB_BIT(TCOMP)) { /* The TCOM bit is set even if the transmission failed */ if (intstatus & (MACB_BIT(ISR_TUND) | MACB_BIT(ISR_RLE))) - dev->stats.tx_errors++; + netdev->stats.tx_errors++; desc = 0; if (lp->rm9200_txq[desc].skb) { @@ -5240,10 +5244,10 @@ static irqreturn_t at91ether_interrupt(int irq, void *dev_id) lp->rm9200_txq[desc].skb = NULL; dma_unmap_single(&lp->pdev->dev, lp->rm9200_txq[desc].mapping, lp->rm9200_txq[desc].size, DMA_TO_DEVICE); - dev->stats.tx_packets++; - dev->stats.tx_bytes += lp->rm9200_txq[desc].size; + netdev->stats.tx_packets++; + netdev->stats.tx_bytes += lp->rm9200_txq[desc].size; } - netif_wake_queue(dev); + netif_wake_queue(netdev); } /* Work-around for EMAC Errata section 41.3.1 */ @@ -5255,18 +5259,18 @@ static irqreturn_t at91ether_interrupt(int irq, void *dev_id) } if (intstatus & MACB_BIT(ISR_ROVR)) - netdev_err(dev, "ROVR error\n"); + netdev_err(netdev, "ROVR error\n"); return IRQ_HANDLED; } #ifdef CONFIG_NET_POLL_CONTROLLER -static void at91ether_poll_controller(struct net_device *dev) +static void at91ether_poll_controller(struct net_device *netdev) { unsigned long flags; local_irq_save(flags); - at91ether_interrupt(dev->irq, dev); + at91ether_interrupt(netdev->irq, netdev); local_irq_restore(flags); } #endif @@ -5313,17 +5317,17 @@ static int at91ether_clk_init(struct platform_device *pdev, struct clk **pclk, static int at91ether_init(struct platform_device *pdev) { - struct net_device *dev = platform_get_drvdata(pdev); - struct macb *bp = netdev_priv(dev); + struct net_device *netdev = platform_get_drvdata(pdev); + struct macb *bp = netdev_priv(netdev); int err; bp->queues[0].bp = bp; - dev->netdev_ops = &at91ether_netdev_ops; - dev->ethtool_ops = &macb_ethtool_ops; + netdev->netdev_ops = &at91ether_netdev_ops; + netdev->ethtool_ops = &macb_ethtool_ops; - err = devm_request_irq(&pdev->dev, dev->irq, at91ether_interrupt, - 0, dev->name, dev); + err = devm_request_irq(&pdev->dev, netdev->irq, at91ether_interrupt, + 0, netdev->name, netdev); if (err) return err; @@ -5452,8 +5456,8 @@ static int fu540_c000_init(struct platform_device *pdev) static int init_reset_optional(struct platform_device *pdev) { - struct net_device *dev = platform_get_drvdata(pdev); - struct macb *bp = netdev_priv(dev); + struct net_device *netdev = platform_get_drvdata(pdev); + struct macb *bp = netdev_priv(netdev); int ret; if (bp->phy_interface == PHY_INTERFACE_MODE_SGMII) { @@ -5761,7 +5765,7 @@ static int macb_probe(struct platform_device *pdev) const struct macb_config *macb_config; struct clk *tsu_clk = NULL; phy_interface_t interface; - struct net_device *dev; + struct net_device *netdev; struct resource *regs; u32 wtrmrk_rst_val; void __iomem *mem; @@ -5796,19 +5800,19 @@ static int macb_probe(struct platform_device *pdev) goto err_disable_clocks; } - dev = alloc_etherdev_mq(sizeof(*bp), num_queues); - if (!dev) { + netdev = alloc_etherdev_mq(sizeof(*bp), num_queues); + if (!netdev) { err = -ENOMEM; goto err_disable_clocks; } - dev->base_addr = regs->start; + netdev->base_addr = regs->start; - SET_NETDEV_DEV(dev, &pdev->dev); + SET_NETDEV_DEV(netdev, &pdev->dev); - bp = netdev_priv(dev); + bp = netdev_priv(netdev); bp->pdev = pdev; - bp->dev = dev; + bp->netdev = netdev; bp->regs = mem; bp->native_io = native_io; if (native_io) { @@ -5881,21 +5885,21 @@ static int macb_probe(struct platform_device *pdev) bp->caps |= MACB_CAPS_DMA_64B; } #endif - platform_set_drvdata(pdev, dev); + platform_set_drvdata(pdev, netdev); - dev->irq = platform_get_irq(pdev, 0); - if (dev->irq < 0) { - err = dev->irq; + netdev->irq = platform_get_irq(pdev, 0); + if (netdev->irq < 0) { + err = netdev->irq; goto err_out_free_netdev; } /* MTU range: 68 - 1518 or 10240 */ - dev->min_mtu = GEM_MTU_MIN_SIZE; + netdev->min_mtu = GEM_MTU_MIN_SIZE; if ((bp->caps & MACB_CAPS_JUMBO) && bp->jumbo_max_len) - dev->max_mtu = MIN(bp->jumbo_max_len, RX_BUFFER_MAX) - + netdev->max_mtu = MIN(bp->jumbo_max_len, RX_BUFFER_MAX) - ETH_HLEN - ETH_FCS_LEN; else - dev->max_mtu = 1536 - ETH_HLEN - ETH_FCS_LEN; + netdev->max_mtu = 1536 - ETH_HLEN - ETH_FCS_LEN; if (bp->caps & MACB_CAPS_BD_RD_PREFETCH) { val = GEM_BFEXT(RXBD_RDBUFF, gem_readl(bp, DCFG10)); @@ -5913,7 +5917,7 @@ static int macb_probe(struct platform_device *pdev) if (bp->caps & MACB_CAPS_NEEDS_RSTONUBR) bp->rx_intr_mask |= MACB_BIT(RXUBR); - err = of_get_ethdev_address(np, bp->dev); + err = of_get_ethdev_address(np, bp->netdev); if (err == -EPROBE_DEFER) goto err_out_free_netdev; else if (err) @@ -5935,9 +5939,9 @@ static int macb_probe(struct platform_device *pdev) if (err) goto err_out_phy_exit; - netif_carrier_off(dev); + netif_carrier_off(netdev); - err = register_netdev(dev); + err = register_netdev(netdev); if (err) { dev_err(&pdev->dev, "Cannot register net device, aborting.\n"); goto err_out_unregister_mdio; @@ -5946,9 +5950,9 @@ static int macb_probe(struct platform_device *pdev) INIT_WORK(&bp->hresp_err_bh_work, macb_hresp_error_task); INIT_DELAYED_WORK(&bp->tx_lpi_work, macb_tx_lpi_work_fn); - netdev_info(dev, "Cadence %s rev 0x%08x at 0x%08lx irq %d (%pM)\n", + netdev_info(netdev, "Cadence %s rev 0x%08x at 0x%08lx irq %d (%pM)\n", macb_is_gem(bp) ? "GEM" : "MACB", macb_readl(bp, MID), - dev->base_addr, dev->irq, dev->dev_addr); + netdev->base_addr, netdev->irq, netdev->dev_addr); pm_runtime_put_autosuspend(&bp->pdev->dev); @@ -5962,7 +5966,7 @@ static int macb_probe(struct platform_device *pdev) phy_exit(bp->phy); err_out_free_netdev: - free_netdev(dev); + free_netdev(netdev); err_disable_clocks: macb_clks_disable(pclk, hclk, tx_clk, rx_clk, tsu_clk); @@ -5975,14 +5979,14 @@ static int macb_probe(struct platform_device *pdev) static void macb_remove(struct platform_device *pdev) { - struct net_device *dev; + struct net_device *netdev; struct macb *bp; - dev = platform_get_drvdata(pdev); + netdev = platform_get_drvdata(pdev); - if (dev) { - bp = netdev_priv(dev); - unregister_netdev(dev); + if (netdev) { + bp = netdev_priv(netdev); + unregister_netdev(netdev); phy_exit(bp->phy); mdiobus_unregister(bp->mii_bus); mdiobus_free(bp->mii_bus); @@ -5994,7 +5998,7 @@ static void macb_remove(struct platform_device *pdev) pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); phylink_destroy(bp->phylink); - free_netdev(dev); + free_netdev(netdev); } } @@ -6009,7 +6013,7 @@ static int __maybe_unused macb_suspend(struct device *dev) u32 tmp, ifa_local; unsigned int q; - if (!device_may_wakeup(&bp->dev->dev)) + if (!device_may_wakeup(&bp->netdev->dev)) phy_exit(bp->phy); if (!netif_running(netdev)) @@ -6019,7 +6023,7 @@ static int __maybe_unused macb_suspend(struct device *dev) if (bp->wolopts & WAKE_ARP) { /* Check for IP address in WOL ARP mode */ rcu_read_lock(); - idev = __in_dev_get_rcu(bp->dev); + idev = __in_dev_get_rcu(bp->netdev); if (idev) ifa = rcu_dereference(idev->ifa_list); if (!ifa) { @@ -6121,7 +6125,7 @@ static int __maybe_unused macb_resume(struct device *dev) unsigned long flags; unsigned int q; - if (!device_may_wakeup(&bp->dev->dev)) + if (!device_may_wakeup(&bp->netdev->dev)) phy_init(bp->phy); if (!netif_running(netdev)) diff --git a/drivers/net/ethernet/cadence/macb_pci.c b/drivers/net/ethernet/cadence/macb_pci.c index b79dec17e6b0..ac009007118f 100644 --- a/drivers/net/ethernet/cadence/macb_pci.c +++ b/drivers/net/ethernet/cadence/macb_pci.c @@ -24,48 +24,48 @@ #define GEM_PCLK_RATE 50000000 #define GEM_HCLK_RATE 50000000 -static int macb_probe(struct pci_dev *pdev, const struct pci_device_id *id) +static int macb_probe(struct pci_dev *pci, const struct pci_device_id *id) { int err; - struct platform_device *plat_dev; + struct platform_device *pdev; struct platform_device_info plat_info; struct macb_platform_data plat_data; struct resource res[2]; /* enable pci device */ - err = pcim_enable_device(pdev); + err = pcim_enable_device(pci); if (err < 0) { - dev_err(&pdev->dev, "Enabling PCI device has failed: %d", err); + dev_err(&pci->dev, "Enabling PCI device has failed: %d", err); return err; } - pci_set_master(pdev); + pci_set_master(pci); /* set up resources */ memset(res, 0x00, sizeof(struct resource) * ARRAY_SIZE(res)); - res[0].start = pci_resource_start(pdev, 0); - res[0].end = pci_resource_end(pdev, 0); + res[0].start = pci_resource_start(pci, 0); + res[0].end = pci_resource_end(pci, 0); res[0].name = PCI_DRIVER_NAME; res[0].flags = IORESOURCE_MEM; - res[1].start = pci_irq_vector(pdev, 0); + res[1].start = pci_irq_vector(pci, 0); res[1].name = PCI_DRIVER_NAME; res[1].flags = IORESOURCE_IRQ; - dev_info(&pdev->dev, "EMAC physical base addr: %pa\n", + dev_info(&pci->dev, "EMAC physical base addr: %pa\n", &res[0].start); /* set up macb platform data */ memset(&plat_data, 0, sizeof(plat_data)); /* initialize clocks */ - plat_data.pclk = clk_register_fixed_rate(&pdev->dev, "pclk", NULL, 0, + plat_data.pclk = clk_register_fixed_rate(&pci->dev, "pclk", NULL, 0, GEM_PCLK_RATE); if (IS_ERR(plat_data.pclk)) { err = PTR_ERR(plat_data.pclk); goto err_pclk_register; } - plat_data.hclk = clk_register_fixed_rate(&pdev->dev, "hclk", NULL, 0, + plat_data.hclk = clk_register_fixed_rate(&pci->dev, "hclk", NULL, 0, GEM_HCLK_RATE); if (IS_ERR(plat_data.hclk)) { err = PTR_ERR(plat_data.hclk); @@ -74,24 +74,24 @@ static int macb_probe(struct pci_dev *pdev, const struct pci_device_id *id) /* set up platform device info */ memset(&plat_info, 0, sizeof(plat_info)); - plat_info.parent = &pdev->dev; - plat_info.fwnode = pdev->dev.fwnode; + plat_info.parent = &pci->dev; + plat_info.fwnode = pci->dev.fwnode; plat_info.name = PLAT_DRIVER_NAME; - plat_info.id = pdev->devfn; + plat_info.id = pci->devfn; plat_info.res = res; plat_info.num_res = ARRAY_SIZE(res); plat_info.data = &plat_data; plat_info.size_data = sizeof(plat_data); - plat_info.dma_mask = pdev->dma_mask; + plat_info.dma_mask = pci->dma_mask; /* register platform device */ - plat_dev = platform_device_register_full(&plat_info); - if (IS_ERR(plat_dev)) { - err = PTR_ERR(plat_dev); + pdev = platform_device_register_full(&plat_info); + if (IS_ERR(pdev)) { + err = PTR_ERR(pdev); goto err_plat_dev_register; } - pci_set_drvdata(pdev, plat_dev); + pci_set_drvdata(pci, pdev); return 0; @@ -105,14 +105,14 @@ static int macb_probe(struct pci_dev *pdev, const struct pci_device_id *id) return err; } -static void macb_remove(struct pci_dev *pdev) +static void macb_remove(struct pci_dev *pci) { - struct platform_device *plat_dev = pci_get_drvdata(pdev); - struct macb_platform_data *plat_data = dev_get_platdata(&plat_dev->dev); + struct platform_device *pdev = pci_get_drvdata(pci); + struct macb_platform_data *plat_data = dev_get_platdata(&pdev->dev); struct clk *pclk = plat_data->pclk; struct clk *hclk = plat_data->hclk; - platform_device_unregister(plat_dev); + platform_device_unregister(pdev); clk_unregister_fixed_rate(pclk); clk_unregister_fixed_rate(hclk); } diff --git a/drivers/net/ethernet/cadence/macb_ptp.c b/drivers/net/ethernet/cadence/macb_ptp.c index d91f7b1aa39c..e5195d7dac1d 100644 --- a/drivers/net/ethernet/cadence/macb_ptp.c +++ b/drivers/net/ethernet/cadence/macb_ptp.c @@ -324,9 +324,9 @@ void gem_ptp_txstamp(struct macb *bp, struct sk_buff *skb, skb_tstamp_tx(skb, &shhwtstamps); } -void gem_ptp_init(struct net_device *dev) +void gem_ptp_init(struct net_device *netdev) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); bp->ptp_clock_info = gem_ptp_caps_template; @@ -334,7 +334,7 @@ void gem_ptp_init(struct net_device *dev) bp->tsu_rate = bp->ptp_info->get_tsu_rate(bp); bp->ptp_clock_info.max_adj = bp->ptp_info->get_ptp_max_adj(); gem_ptp_init_timer(bp); - bp->ptp_clock = ptp_clock_register(&bp->ptp_clock_info, &dev->dev); + bp->ptp_clock = ptp_clock_register(&bp->ptp_clock_info, &netdev->dev); if (IS_ERR(bp->ptp_clock)) { pr_err("ptp clock register failed: %ld\n", PTR_ERR(bp->ptp_clock)); @@ -353,9 +353,9 @@ void gem_ptp_init(struct net_device *dev) GEM_PTP_TIMER_NAME); } -void gem_ptp_remove(struct net_device *ndev) +void gem_ptp_remove(struct net_device *netdev) { - struct macb *bp = netdev_priv(ndev); + struct macb *bp = netdev_priv(netdev); if (bp->ptp_clock) { ptp_clock_unregister(bp->ptp_clock); @@ -378,10 +378,10 @@ static int gem_ptp_set_ts_mode(struct macb *bp, return 0; } -int gem_get_hwtst(struct net_device *dev, +int gem_get_hwtst(struct net_device *netdev, struct kernel_hwtstamp_config *tstamp_config) { - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); *tstamp_config = bp->tstamp_config; if (!macb_dma_ptp(bp)) @@ -402,13 +402,13 @@ static void gem_ptp_set_one_step_sync(struct macb *bp, u8 enable) macb_writel(bp, NCR, reg_val & ~MACB_BIT(OSSMODE)); } -int gem_set_hwtst(struct net_device *dev, +int gem_set_hwtst(struct net_device *netdev, struct kernel_hwtstamp_config *tstamp_config, struct netlink_ext_ack *extack) { enum macb_bd_control tx_bd_control = TSTAMP_DISABLED; enum macb_bd_control rx_bd_control = TSTAMP_DISABLED; - struct macb *bp = netdev_priv(dev); + struct macb *bp = netdev_priv(netdev); u32 regval; if (!macb_dma_ptp(bp)) From 075663a6ceb67d2846436a37196aeff6028c7d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Lebrun?= Date: Wed, 12 Aug 2026 10:03:17 +0200 Subject: [PATCH 1361/1433] net: macb: unify variable naming convention in at91ether functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow MACB naming convention throughout on two aspects: - Always name `struct macb *bp` rather than `lp`. - Always name `struct macb_queue *queue` rather than `q`. The latter is to reserve `q` for queue indexes. Acked-by: Conor Dooley Reviewed-by: Nicolai Buchwitz Signed-off-by: Théo Lebrun Link: https://patch.msgid.link/20260812-macb-context-v9-3-7ddbf5f715e0@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb_main.c | 176 ++++++++++++----------- 1 file changed, 91 insertions(+), 85 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 3ae76d1d2a0d..e01a5e0bf481 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -4965,71 +4965,72 @@ static const struct macb_usrio_config at91_default_usrio = { static struct sifive_fu540_macb_mgmt *mgmt; -static int at91ether_alloc_coherent(struct macb *lp) +static int at91ether_alloc_coherent(struct macb *bp) { - struct macb_queue *q = &lp->queues[0]; + struct macb_queue *queue = &bp->queues[0]; - q->rx_ring = dma_alloc_coherent(&lp->pdev->dev, - (AT91ETHER_MAX_RX_DESCR * - macb_dma_desc_get_size(lp)), - &q->rx_ring_dma, GFP_KERNEL); - if (!q->rx_ring) + queue->rx_ring = dma_alloc_coherent(&bp->pdev->dev, + (AT91ETHER_MAX_RX_DESCR * + macb_dma_desc_get_size(bp)), + &queue->rx_ring_dma, GFP_KERNEL); + if (!queue->rx_ring) return -ENOMEM; - q->rx_buffers = dma_alloc_coherent(&lp->pdev->dev, - AT91ETHER_MAX_RX_DESCR * - AT91ETHER_MAX_RBUFF_SZ, - &q->rx_buffers_dma, GFP_KERNEL); - if (!q->rx_buffers) { - dma_free_coherent(&lp->pdev->dev, + queue->rx_buffers = dma_alloc_coherent(&bp->pdev->dev, + AT91ETHER_MAX_RX_DESCR * + AT91ETHER_MAX_RBUFF_SZ, + &queue->rx_buffers_dma, + GFP_KERNEL); + if (!queue->rx_buffers) { + dma_free_coherent(&bp->pdev->dev, AT91ETHER_MAX_RX_DESCR * - macb_dma_desc_get_size(lp), - q->rx_ring, q->rx_ring_dma); - q->rx_ring = NULL; + macb_dma_desc_get_size(bp), + queue->rx_ring, queue->rx_ring_dma); + queue->rx_ring = NULL; return -ENOMEM; } return 0; } -static void at91ether_free_coherent(struct macb *lp) +static void at91ether_free_coherent(struct macb *bp) { - struct macb_queue *q = &lp->queues[0]; + struct macb_queue *queue = &bp->queues[0]; - if (q->rx_ring) { - dma_free_coherent(&lp->pdev->dev, + if (queue->rx_ring) { + dma_free_coherent(&bp->pdev->dev, AT91ETHER_MAX_RX_DESCR * - macb_dma_desc_get_size(lp), - q->rx_ring, q->rx_ring_dma); - q->rx_ring = NULL; + macb_dma_desc_get_size(bp), + queue->rx_ring, queue->rx_ring_dma); + queue->rx_ring = NULL; } - if (q->rx_buffers) { - dma_free_coherent(&lp->pdev->dev, + if (queue->rx_buffers) { + dma_free_coherent(&bp->pdev->dev, AT91ETHER_MAX_RX_DESCR * AT91ETHER_MAX_RBUFF_SZ, - q->rx_buffers, q->rx_buffers_dma); - q->rx_buffers = NULL; + queue->rx_buffers, queue->rx_buffers_dma); + queue->rx_buffers = NULL; } } /* Initialize and start the Receiver and Transmit subsystems */ -static int at91ether_start(struct macb *lp) +static int at91ether_start(struct macb *bp) { - struct macb_queue *q = &lp->queues[0]; + struct macb_queue *queue = &bp->queues[0]; struct macb_dma_desc *desc; dma_addr_t addr; u32 ctl; int i, ret; - ret = at91ether_alloc_coherent(lp); + ret = at91ether_alloc_coherent(bp); if (ret) return ret; - addr = q->rx_buffers_dma; + addr = queue->rx_buffers_dma; for (i = 0; i < AT91ETHER_MAX_RX_DESCR; i++) { - desc = macb_rx_desc(q, i); - macb_set_addr(lp, desc, addr); + desc = macb_rx_desc(queue, i); + macb_set_addr(bp, desc, addr); desc->ctrl = 0; addr += AT91ETHER_MAX_RBUFF_SZ; } @@ -5038,17 +5039,17 @@ static int at91ether_start(struct macb *lp) desc->addr |= MACB_BIT(RX_WRAP); /* Reset buffer index */ - q->rx_tail = 0; + queue->rx_tail = 0; /* Program address of descriptor list in Rx Buffer Queue register */ - macb_writel(lp, RBQP, q->rx_ring_dma); + macb_writel(bp, RBQP, queue->rx_ring_dma); /* Enable Receive and Transmit */ - ctl = macb_readl(lp, NCR); - macb_writel(lp, NCR, ctl | MACB_BIT(RE) | MACB_BIT(TE)); + ctl = macb_readl(bp, NCR); + macb_writel(bp, NCR, ctl | MACB_BIT(RE) | MACB_BIT(TE)); /* Enable MAC interrupts */ - macb_writel(lp, IER, MACB_BIT(RCOMP) | + macb_writel(bp, IER, MACB_BIT(RCOMP) | MACB_BIT(RXUBR) | MACB_BIT(ISR_TUND) | MACB_BIT(ISR_RLE) | @@ -5059,12 +5060,12 @@ static int at91ether_start(struct macb *lp) return 0; } -static void at91ether_stop(struct macb *lp) +static void at91ether_stop(struct macb *bp) { u32 ctl; /* Disable MAC interrupts */ - macb_writel(lp, IDR, MACB_BIT(RCOMP) | + macb_writel(bp, IDR, MACB_BIT(RCOMP) | MACB_BIT(RXUBR) | MACB_BIT(ISR_TUND) | MACB_BIT(ISR_RLE) | @@ -5073,35 +5074,35 @@ static void at91ether_stop(struct macb *lp) MACB_BIT(HRESP)); /* Disable Receiver and Transmitter */ - ctl = macb_readl(lp, NCR); - macb_writel(lp, NCR, ctl & ~(MACB_BIT(TE) | MACB_BIT(RE))); + ctl = macb_readl(bp, NCR); + macb_writel(bp, NCR, ctl & ~(MACB_BIT(TE) | MACB_BIT(RE))); /* Free resources. */ - at91ether_free_coherent(lp); + at91ether_free_coherent(bp); } /* Open the ethernet interface */ static int at91ether_open(struct net_device *netdev) { - struct macb *lp = netdev_priv(netdev); + struct macb *bp = netdev_priv(netdev); u32 ctl; int ret; - ret = pm_runtime_resume_and_get(&lp->pdev->dev); + ret = pm_runtime_resume_and_get(&bp->pdev->dev); if (ret < 0) return ret; /* Clear internal statistics */ - ctl = macb_readl(lp, NCR); - macb_writel(lp, NCR, ctl | MACB_BIT(CLRSTAT)); + ctl = macb_readl(bp, NCR); + macb_writel(bp, NCR, ctl | MACB_BIT(CLRSTAT)); - macb_set_hwaddr(lp); + macb_set_hwaddr(bp); - ret = at91ether_start(lp); + ret = at91ether_start(bp); if (ret) goto pm_exit; - ret = macb_phylink_connect(lp); + ret = macb_phylink_connect(bp); if (ret) goto stop; @@ -5110,25 +5111,25 @@ static int at91ether_open(struct net_device *netdev) return 0; stop: - at91ether_stop(lp); + at91ether_stop(bp); pm_exit: - pm_runtime_put_sync(&lp->pdev->dev); + pm_runtime_put_sync(&bp->pdev->dev); return ret; } /* Close the interface */ static int at91ether_close(struct net_device *netdev) { - struct macb *lp = netdev_priv(netdev); + struct macb *bp = netdev_priv(netdev); netif_stop_queue(netdev); - phylink_stop(lp->phylink); - phylink_disconnect_phy(lp->phylink); + phylink_stop(bp->phylink); + phylink_disconnect_phy(bp->phylink); - at91ether_stop(lp); + at91ether_stop(bp); - pm_runtime_put(&lp->pdev->dev); + pm_runtime_put(&bp->pdev->dev); return 0; } @@ -5137,19 +5138,21 @@ static int at91ether_close(struct net_device *netdev) static netdev_tx_t at91ether_start_xmit(struct sk_buff *skb, struct net_device *netdev) { - struct macb *lp = netdev_priv(netdev); + struct macb *bp = netdev_priv(netdev); + struct device *dev = &bp->pdev->dev; - if (macb_readl(lp, TSR) & MACB_BIT(RM9200_BNQ)) { + if (macb_readl(bp, TSR) & MACB_BIT(RM9200_BNQ)) { int desc = 0; netif_stop_queue(netdev); /* Store packet information (to free when Tx completed) */ - lp->rm9200_txq[desc].skb = skb; - lp->rm9200_txq[desc].size = skb->len; - lp->rm9200_txq[desc].mapping = dma_map_single(&lp->pdev->dev, skb->data, - skb->len, DMA_TO_DEVICE); - if (dma_mapping_error(&lp->pdev->dev, lp->rm9200_txq[desc].mapping)) { + bp->rm9200_txq[desc].skb = skb; + bp->rm9200_txq[desc].size = skb->len; + bp->rm9200_txq[desc].mapping = dma_map_single(dev, skb->data, + skb->len, + DMA_TO_DEVICE); + if (dma_mapping_error(dev, bp->rm9200_txq[desc].mapping)) { dev_kfree_skb_any(skb); netdev->stats.tx_dropped++; netdev_err(netdev, "%s: DMA mapping error\n", __func__); @@ -5157,9 +5160,9 @@ static netdev_tx_t at91ether_start_xmit(struct sk_buff *skb, } /* Set address of the data in the Transmit Address register */ - macb_writel(lp, TAR, lp->rm9200_txq[desc].mapping); + macb_writel(bp, TAR, bp->rm9200_txq[desc].mapping); /* Set length of the packet in the Transmit Control register */ - macb_writel(lp, TCR, skb->len); + macb_writel(bp, TCR, skb->len); } else { netdev_err(netdev, "%s called, but device is busy!\n", @@ -5175,16 +5178,17 @@ static netdev_tx_t at91ether_start_xmit(struct sk_buff *skb, */ static void at91ether_rx(struct net_device *netdev) { - struct macb *lp = netdev_priv(netdev); - struct macb_queue *q = &lp->queues[0]; + struct macb *bp = netdev_priv(netdev); + struct macb_queue *queue = &bp->queues[0]; struct macb_dma_desc *desc; unsigned char *p_recv; struct sk_buff *skb; unsigned int pktlen; - desc = macb_rx_desc(q, q->rx_tail); + desc = macb_rx_desc(queue, queue->rx_tail); while (desc->addr & MACB_BIT(RX_USED)) { - p_recv = q->rx_buffers + q->rx_tail * AT91ETHER_MAX_RBUFF_SZ; + p_recv = queue->rx_buffers + + queue->rx_tail * AT91ETHER_MAX_RBUFF_SZ; pktlen = MACB_BF(RX_FRMLEN, desc->ctrl); skb = netdev_alloc_skb(netdev, pktlen + 2); if (skb) { @@ -5206,12 +5210,12 @@ static void at91ether_rx(struct net_device *netdev) desc->addr &= ~MACB_BIT(RX_USED); /* wrap after last buffer */ - if (q->rx_tail == AT91ETHER_MAX_RX_DESCR - 1) - q->rx_tail = 0; + if (queue->rx_tail == AT91ETHER_MAX_RX_DESCR - 1) + queue->rx_tail = 0; else - q->rx_tail++; + queue->rx_tail++; - desc = macb_rx_desc(q, q->rx_tail); + desc = macb_rx_desc(queue, queue->rx_tail); } } @@ -5219,14 +5223,14 @@ static void at91ether_rx(struct net_device *netdev) static irqreturn_t at91ether_interrupt(int irq, void *dev_id) { struct net_device *netdev = dev_id; - struct macb *lp = netdev_priv(netdev); + struct macb *bp = netdev_priv(netdev); u32 intstatus, ctl; unsigned int desc; /* MAC Interrupt Status register indicates what interrupts are pending. * It is automatically cleared once read. */ - intstatus = macb_readl(lp, ISR); + intstatus = macb_readl(bp, ISR); /* Receive complete */ if (intstatus & MACB_BIT(RCOMP)) @@ -5239,23 +5243,25 @@ static irqreturn_t at91ether_interrupt(int irq, void *dev_id) netdev->stats.tx_errors++; desc = 0; - if (lp->rm9200_txq[desc].skb) { - dev_consume_skb_irq(lp->rm9200_txq[desc].skb); - lp->rm9200_txq[desc].skb = NULL; - dma_unmap_single(&lp->pdev->dev, lp->rm9200_txq[desc].mapping, - lp->rm9200_txq[desc].size, DMA_TO_DEVICE); + if (bp->rm9200_txq[desc].skb) { + dev_consume_skb_irq(bp->rm9200_txq[desc].skb); + bp->rm9200_txq[desc].skb = NULL; + dma_unmap_single(&bp->pdev->dev, + bp->rm9200_txq[desc].mapping, + bp->rm9200_txq[desc].size, + DMA_TO_DEVICE); netdev->stats.tx_packets++; - netdev->stats.tx_bytes += lp->rm9200_txq[desc].size; + netdev->stats.tx_bytes += bp->rm9200_txq[desc].size; } netif_wake_queue(netdev); } /* Work-around for EMAC Errata section 41.3.1 */ if (intstatus & MACB_BIT(RXUBR)) { - ctl = macb_readl(lp, NCR); - macb_writel(lp, NCR, ctl & ~MACB_BIT(RE)); + ctl = macb_readl(bp, NCR); + macb_writel(bp, NCR, ctl & ~MACB_BIT(RE)); wmb(); - macb_writel(lp, NCR, ctl | MACB_BIT(RE)); + macb_writel(bp, NCR, ctl | MACB_BIT(RE)); } if (intstatus & MACB_BIT(ISR_ROVR)) From 2434dc6e1c9c5c379e090c282f982d277ff094a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Lebrun?= Date: Wed, 12 Aug 2026 10:03:18 +0200 Subject: [PATCH 1362/1433] net: macb: unify queue index variable naming convention and types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Variables are named q or queue_index. Types are int, unsigned int, u32 and u16. Use `unsigned int q` everywhere. Skip over taprio functions. They use `u8 queue_id` which fits with the `struct macb_queue_enst_config` field. Using `queue_id` everywhere would be too verbose. Reviewed-by: Nicolai Buchwitz Signed-off-by: Théo Lebrun Link: https://patch.msgid.link/20260812-macb-context-v9-4-7ddbf5f715e0@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb_main.c | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index e01a5e0bf481..77053cb9d8f7 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -877,7 +877,7 @@ static void gem_shuffle_tx_one_ring(struct macb_queue *queue) static void gem_shuffle_tx_rings(struct macb *bp) { struct macb_queue *queue; - int q; + unsigned int q; for (q = 0, queue = bp->queues; q < bp->num_queues; q++, queue++) gem_shuffle_tx_one_ring(queue); @@ -1258,7 +1258,7 @@ static void macb_tx_error_task(struct work_struct *work) tx_error_task); bool halt_timeout = false; struct macb *bp = queue->bp; - u32 queue_index; + unsigned int q; u32 packets = 0; u32 bytes = 0; struct macb_tx_skb *tx_skb; @@ -1267,9 +1267,9 @@ static void macb_tx_error_task(struct work_struct *work) unsigned int tail; unsigned long flags; - queue_index = queue - bp->queues; + q = queue - bp->queues; netdev_vdbg(bp->netdev, "%s: q = %u, t = %u, h = %u\n", - __func__, queue_index, queue->tx_tail, queue->tx_head); + __func__, q, queue->tx_tail, queue->tx_head); /* Prevent the queue NAPI TX poll from running, as it calls * macb_tx_complete(), which in turn may call netif_wake_subqueue(). @@ -1342,7 +1342,7 @@ static void macb_tx_error_task(struct work_struct *work) macb_tx_unmap(bp, tx_skb, 0); } - netdev_tx_completed_queue(netdev_get_tx_queue(bp->netdev, queue_index), + netdev_tx_completed_queue(netdev_get_tx_queue(bp->netdev, q), packets, bytes); /* Set end of TX queue */ @@ -1407,7 +1407,7 @@ static bool ptp_one_step_sync(struct sk_buff *skb) static int macb_tx_complete(struct macb_queue *queue, int budget) { struct macb *bp = queue->bp; - u16 queue_index = queue - bp->queues; + unsigned int q = queue - bp->queues; unsigned long flags; unsigned int tail; unsigned int head; @@ -1469,14 +1469,14 @@ static int macb_tx_complete(struct macb_queue *queue, int budget) } } - netdev_tx_completed_queue(netdev_get_tx_queue(bp->netdev, queue_index), + netdev_tx_completed_queue(netdev_get_tx_queue(bp->netdev, q), packets, bytes); queue->tx_tail = tail; - if (__netif_subqueue_stopped(bp->netdev, queue_index) && + if (__netif_subqueue_stopped(bp->netdev, q) && CIRC_CNT(queue->tx_head, queue->tx_tail, bp->tx_ring_size) <= MACB_TX_WAKEUP_THRESH(bp)) - netif_wake_subqueue(bp->netdev, queue_index); + netif_wake_subqueue(bp->netdev, q); spin_unlock_irqrestore(&queue->tx_ptr_lock, flags); if (packets) @@ -2472,10 +2472,10 @@ static int macb_pad_and_fcs(struct sk_buff **skb, struct net_device *netdev) static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *netdev) { - u16 queue_index = skb_get_queue_mapping(skb); struct macb *bp = netdev_priv(netdev); - struct macb_queue *queue = &bp->queues[queue_index]; + unsigned int q = skb_get_queue_mapping(skb); unsigned int desc_cnt, nr_frags, frag_size, f; + struct macb_queue *queue = &bp->queues[q]; unsigned int hdrlen; unsigned long flags; bool is_lso; @@ -2514,8 +2514,8 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, #if defined(DEBUG) && defined(VERBOSE_DEBUG) netdev_vdbg(bp->netdev, - "start_xmit: queue %hu len %u head %p data %p tail %p end %p\n", - queue_index, skb->len, skb->head, skb->data, + "start_xmit: queue %u len %u head %p data %p tail %p end %p\n", + q, skb->len, skb->head, skb->data, skb_tail_pointer(skb), skb_end_pointer(skb)); print_hex_dump(KERN_DEBUG, "data: ", DUMP_PREFIX_OFFSET, 16, 1, skb->data, 16, true); @@ -2541,7 +2541,7 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, /* This is a hard error, log it. */ if (CIRC_SPACE(queue->tx_head, queue->tx_tail, bp->tx_ring_size) < desc_cnt) { - netif_stop_subqueue(netdev, queue_index); + netif_stop_subqueue(netdev, q); netdev_dbg(netdev, "tx_head = %u, tx_tail = %u\n", queue->tx_head, queue->tx_tail); ret = NETDEV_TX_BUSY; @@ -2557,7 +2557,7 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, /* Make newly initialized descriptor visible to hardware */ wmb(); skb_tx_timestamp(skb); - netdev_tx_sent_queue(netdev_get_tx_queue(bp->netdev, queue_index), + netdev_tx_sent_queue(netdev_get_tx_queue(bp->netdev, q), skb->len); spin_lock(&bp->lock); @@ -2566,7 +2566,7 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, spin_unlock(&bp->lock); if (CIRC_SPACE(queue->tx_head, queue->tx_tail, bp->tx_ring_size) < 1) - netif_stop_subqueue(netdev, queue_index); + netif_stop_subqueue(netdev, q); unlock: spin_unlock_irqrestore(&queue->tx_ptr_lock, flags); From 2cedfce238c3df83363859ebccc4963e54297612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Lebrun?= Date: Wed, 12 Aug 2026 10:03:19 +0200 Subject: [PATCH 1363/1433] net: macb: enforce reverse christmas tree (RCT) convention MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enforce the reverse christmas tree convention in those functions: macb_tx_error_task() gem_rx_refill() gem_rx() macb_rx_frame() macb_init_rx_ring() macb_rx() macb_rx_pending() macb_start_xmit() The goal is to minimise unrelated diff in future patches. In macb_tx_error_task(), we fold the assignment into the declaration statement. Acked-by: Conor Dooley Reviewed-by: Nicolai Buchwitz Signed-off-by: Théo Lebrun Link: https://patch.msgid.link/20260812-macb-context-v9-5-7ddbf5f715e0@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb_main.c | 61 ++++++++++++------------ 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 77053cb9d8f7..b138b94ea0d8 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -1254,20 +1254,19 @@ static dma_addr_t macb_get_addr(struct macb *bp, struct macb_dma_desc *desc) static void macb_tx_error_task(struct work_struct *work) { - struct macb_queue *queue = container_of(work, struct macb_queue, - tx_error_task); - bool halt_timeout = false; - struct macb *bp = queue->bp; - unsigned int q; - u32 packets = 0; - u32 bytes = 0; - struct macb_tx_skb *tx_skb; - struct macb_dma_desc *desc; - struct sk_buff *skb; - unsigned int tail; - unsigned long flags; + struct macb_queue *queue = container_of(work, struct macb_queue, + tx_error_task); + unsigned int q = queue - queue->bp->queues; + struct macb *bp = queue->bp; + struct macb_tx_skb *tx_skb; + struct macb_dma_desc *desc; + bool halt_timeout = false; + struct sk_buff *skb; + unsigned long flags; + unsigned int tail; + u32 packets = 0; + u32 bytes = 0; - q = queue - bp->queues; netdev_vdbg(bp->netdev, "%s: q = %u, t = %u, h = %u\n", __func__, q, queue->tx_tail, queue->tx_head); @@ -1487,11 +1486,11 @@ static int macb_tx_complete(struct macb_queue *queue, int budget) static void gem_rx_refill(struct macb_queue *queue) { - unsigned int entry; - struct sk_buff *skb; - dma_addr_t paddr; struct macb *bp = queue->bp; struct macb_dma_desc *desc; + struct sk_buff *skb; + unsigned int entry; + dma_addr_t paddr; while (CIRC_SPACE(queue->rx_prepared_head, queue->rx_tail, bp->rx_ring_size) > 0) { @@ -1584,11 +1583,11 @@ static int gem_rx(struct macb_queue *queue, struct napi_struct *napi, int budget) { struct macb *bp = queue->bp; - unsigned int len; - unsigned int entry; - struct sk_buff *skb; - struct macb_dma_desc *desc; - int count = 0; + struct macb_dma_desc *desc; + struct sk_buff *skb; + unsigned int entry; + unsigned int len; + int count = 0; while (count < budget) { u32 ctrl; @@ -1675,12 +1674,12 @@ static int gem_rx(struct macb_queue *queue, struct napi_struct *napi, static int macb_rx_frame(struct macb_queue *queue, struct napi_struct *napi, unsigned int first_frag, unsigned int last_frag) { - unsigned int len; - unsigned int frag; + struct macb *bp = queue->bp; + struct macb_dma_desc *desc; unsigned int offset; struct sk_buff *skb; - struct macb_dma_desc *desc; - struct macb *bp = queue->bp; + unsigned int frag; + unsigned int len; desc = macb_rx_desc(queue, last_frag); len = desc->ctrl & bp->rx_frm_len_mask; @@ -1757,9 +1756,9 @@ static int macb_rx_frame(struct macb_queue *queue, struct napi_struct *napi, static inline void macb_init_rx_ring(struct macb_queue *queue) { + struct macb_dma_desc *desc = NULL; struct macb *bp = queue->bp; dma_addr_t addr; - struct macb_dma_desc *desc = NULL; int i; addr = queue->rx_buffers_dma; @@ -1778,9 +1777,9 @@ static int macb_rx(struct macb_queue *queue, struct napi_struct *napi, { struct macb *bp = queue->bp; bool reset_rx_queue = false; - int received = 0; - unsigned int tail; int first_frag = -1; + unsigned int tail; + int received = 0; for (tail = queue->rx_tail; budget > 0; tail++) { struct macb_dma_desc *desc = macb_rx_desc(queue, tail); @@ -1855,8 +1854,8 @@ static int macb_rx(struct macb_queue *queue, struct napi_struct *napi, static bool macb_rx_pending(struct macb_queue *queue) { struct macb *bp = queue->bp; - unsigned int entry; - struct macb_dma_desc *desc; + struct macb_dma_desc *desc; + unsigned int entry; entry = macb_rx_ring_wrap(bp, queue->rx_tail); desc = macb_rx_desc(queue, entry); @@ -2476,10 +2475,10 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, unsigned int q = skb_get_queue_mapping(skb); unsigned int desc_cnt, nr_frags, frag_size, f; struct macb_queue *queue = &bp->queues[q]; + netdev_tx_t ret = NETDEV_TX_OK; unsigned int hdrlen; unsigned long flags; bool is_lso; - netdev_tx_t ret = NETDEV_TX_OK; if (macb_clear_csum(skb)) { dev_kfree_skb_any(skb); From 5262eab9462adffd73a84409dee0cbf57fe31da3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Lebrun?= Date: Wed, 12 Aug 2026 10:03:20 +0200 Subject: [PATCH 1364/1433] net: macb: allocate tieoff descriptor once across device lifetime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tieoff descriptor is a RX DMA descriptor ring of size one. It gets configured onto queues for Wake-on-LAN during system-wide suspend when hardware does not support disabling individual queues (MACB_CAPS_QUEUE_DISABLE). MACB/GEM driver allocates it alongside the main RX ring inside macb_alloc() at open. Free is done by macb_free() at close. Change to allocate once at probe and free on probe failure or device removal. This makes the tieoff descriptor lifetime much longer, avoiding repeating coherent buffer allocation on each open/close cycle. Main benefit: we dissociate its lifetime from the main ring's lifetime. That way there is less work to be doing on resources (re)alloc. This currently happens on close/open, but will soon also happen on context swap operations (set_ringparam, change_mtu, set_channels, etc). Reviewed-by: Nicolai Buchwitz Signed-off-by: Théo Lebrun Link: https://patch.msgid.link/20260812-macb-context-v9-6-7ddbf5f715e0@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb_main.c | 75 +++++++++++++----------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index b138b94ea0d8..af351d30adab 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -2655,12 +2655,6 @@ static void macb_free(struct macb *bp) unsigned int q; size_t size; - if (bp->rx_ring_tieoff) { - dma_free_coherent(dev, macb_dma_desc_get_size(bp), - bp->rx_ring_tieoff, bp->rx_ring_tieoff_dma); - bp->rx_ring_tieoff = NULL; - } - bp->macbgem_ops.mog_free_rx_buffers(bp); size = bp->num_queues * macb_tx_ring_size_per_queue(bp); @@ -2775,16 +2769,6 @@ static int macb_alloc(struct macb *bp) if (bp->macbgem_ops.mog_alloc_rx_buffers(bp)) goto out_err; - /* Required for tie off descriptor for PM cases */ - if (!(bp->caps & MACB_CAPS_QUEUE_DISABLE)) { - bp->rx_ring_tieoff = dma_alloc_coherent(&bp->pdev->dev, - macb_dma_desc_get_size(bp), - &bp->rx_ring_tieoff_dma, - GFP_KERNEL); - if (!bp->rx_ring_tieoff) - goto out_err; - } - return 0; out_err: @@ -2792,19 +2776,6 @@ static int macb_alloc(struct macb *bp) return -ENOMEM; } -static void macb_init_tieoff(struct macb *bp) -{ - struct macb_dma_desc *desc = bp->rx_ring_tieoff; - - if (bp->caps & MACB_CAPS_QUEUE_DISABLE) - return; - /* Setup a wrapping descriptor with no free slots - * (WRAP and USED) to tie off/disable unused RX queues. - */ - macb_set_addr(bp, desc, MACB_BIT(RX_WRAP) | MACB_BIT(RX_USED)); - desc->ctrl = 0; -} - static void gem_init_rx_ring(struct macb_queue *queue) { queue->rx_tail = 0; @@ -2832,8 +2803,6 @@ static void gem_init_rings(struct macb *bp) gem_init_rx_ring(queue); } - - macb_init_tieoff(bp); } static void macb_init_rings(struct macb *bp) @@ -2851,8 +2820,6 @@ static void macb_init_rings(struct macb *bp) bp->queues[0].tx_head = 0; bp->queues[0].tx_tail = 0; desc->ctrl |= MACB_BIT(TX_WRAP); - - macb_init_tieoff(bp); } static void macb_reset_hw(struct macb *bp) @@ -5537,6 +5504,38 @@ static int eyeq5_init(struct platform_device *pdev) return ret; } +static int macb_alloc_tieoff(struct macb *bp) +{ + /* Tieoff is a workaround in case HW cannot disable queues, for PM. */ + if (bp->caps & MACB_CAPS_QUEUE_DISABLE) + return 0; + + bp->rx_ring_tieoff = dma_alloc_coherent(&bp->pdev->dev, + macb_dma_desc_get_size(bp), + &bp->rx_ring_tieoff_dma, + GFP_KERNEL); + if (!bp->rx_ring_tieoff) + return -ENOMEM; + + macb_set_addr(bp, bp->rx_ring_tieoff, + MACB_BIT(RX_WRAP) | MACB_BIT(RX_USED)); + + bp->rx_ring_tieoff->ctrl = 0; + + return 0; +} + +static void macb_free_tieoff(struct macb *bp) +{ + if (!bp->rx_ring_tieoff) + return; + + dma_free_coherent(&bp->pdev->dev, macb_dma_desc_get_size(bp), + bp->rx_ring_tieoff, + bp->rx_ring_tieoff_dma); + bp->rx_ring_tieoff = NULL; +} + static const struct macb_usrio_config mpfs_usrio = { .tsu_source = 0, }; @@ -5946,10 +5945,14 @@ static int macb_probe(struct platform_device *pdev) netif_carrier_off(netdev); + err = macb_alloc_tieoff(bp); + if (err) + goto err_out_unregister_mdio; + err = register_netdev(netdev); if (err) { dev_err(&pdev->dev, "Cannot register net device, aborting.\n"); - goto err_out_unregister_mdio; + goto err_out_free_tieoff; } INIT_WORK(&bp->hresp_err_bh_work, macb_hresp_error_task); @@ -5963,6 +5966,9 @@ static int macb_probe(struct platform_device *pdev) return 0; +err_out_free_tieoff: + macb_free_tieoff(bp); + err_out_unregister_mdio: mdiobus_unregister(bp->mii_bus); mdiobus_free(bp->mii_bus); @@ -5992,6 +5998,7 @@ static void macb_remove(struct platform_device *pdev) if (netdev) { bp = netdev_priv(netdev); unregister_netdev(netdev); + macb_free_tieoff(bp); phy_exit(bp->phy); mdiobus_unregister(bp->mii_bus); mdiobus_free(bp->mii_bus); From 227fdb2fe6f9d3dcc337b91acb1237582899b0d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Lebrun?= Date: Wed, 12 Aug 2026 10:03:21 +0200 Subject: [PATCH 1365/1433] net: macb: refuse set_ringparam on EMAC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EMAC has never supported changing ring sizes: RX is hardcoded to 9 and TX is the tiniest ring buffer you can imagine. Make sure the operation fails early rather than silently succeed and storing values in bp->configured_{rx,tx}_ring_size that are never read in the EMAC case. Signed-off-by: Théo Lebrun Link: https://patch.msgid.link/20260812-macb-context-v9-7-7ddbf5f715e0@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb_main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index af351d30adab..1476bce77f34 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -3687,6 +3687,9 @@ static int macb_set_ringparam(struct net_device *netdev, u32 new_rx_size, new_tx_size; unsigned int reset = 0; + if (bp->caps & MACB_CAPS_MACB_IS_EMAC) + return -EOPNOTSUPP; + if ((ring->rx_mini_pending) || (ring->rx_jumbo_pending)) return -EINVAL; From e99c1ca890716a45d843f289c617d1bca00ba237 Mon Sep 17 00:00:00 2001 From: Tao Cui Date: Wed, 12 Aug 2026 16:55:36 +0200 Subject: [PATCH 1366/1433] mptcp: pm: add WARN_ON_ONCE guards on extra_subflows underflow extra_subflows is a u8 counter that can underflow if a decrement races with or precedes an increment. While the recently fixed userspace PM subflow creation path eliminated the primary cause, add defensive WARN_ON_ONCE guards at both decrement sites to catch any remaining edge cases rather than silently wrapping to 255. Signed-off-by: Tao Cui Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260812-net-next-mptcp-misc-feat-7-3-v1-1-1905a818f6cb@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/pm.c | 3 ++- net/mptcp/protocol.h | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/net/mptcp/pm.c b/net/mptcp/pm.c index d1f73c3e39fa..8b68868255c5 100644 --- a/net/mptcp/pm.c +++ b/net/mptcp/pm.c @@ -670,7 +670,8 @@ void mptcp_pm_subflow_check_next(struct mptcp_sock *msk, if (mptcp_pm_is_userspace(msk)) { if (update_subflows) { spin_lock_bh(&pm->lock); - pm->extra_subflows--; + if (!WARN_ON_ONCE(pm->extra_subflows == 0)) + pm->extra_subflows--; spin_unlock_bh(&pm->lock); } return; diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index b3af3462bdd1..20627e12c113 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -1254,7 +1254,8 @@ u8 mptcp_pm_get_limit_extra_subflows(const struct mptcp_sock *msk); /* called under PM lock */ static inline void __mptcp_pm_close_subflow(struct mptcp_sock *msk) { - if (--msk->pm.extra_subflows < mptcp_pm_get_limit_extra_subflows(msk)) + if (!WARN_ON_ONCE(msk->pm.extra_subflows == 0) && + --msk->pm.extra_subflows < mptcp_pm_get_limit_extra_subflows(msk)) WRITE_ONCE(msk->pm.accept_subflow, true); } From 91424c4513dd0ce955471f2937aa6295613f6077 Mon Sep 17 00:00:00 2001 From: Geliang Tang Date: Wed, 12 Aug 2026 16:55:37 +0200 Subject: [PATCH 1367/1433] mptcp: remove unused data_ack from struct mptcp_ext The data_ack and data_ack32 fields in struct mptcp_ext are no longer used anywhere. Remove them from the structure and update mptcp_dump_mpext() trace helper accordingly. Drop the data_ack field from the trace entry and the corresponding output in TP_printk(). Signed-off-by: Geliang Tang Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260812-net-next-mptcp-misc-feat-7-3-v1-2-1905a818f6cb@kernel.org Signed-off-by: Jakub Kicinski --- include/net/mptcp.h | 4 ---- include/trace/events/mptcp.h | 6 ++---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/include/net/mptcp.h b/include/net/mptcp.h index 71b9fc5a5796..485d55b66ea6 100644 --- a/include/net/mptcp.h +++ b/include/net/mptcp.h @@ -19,10 +19,6 @@ struct seq_file; /* MPTCP sk_buff extension data */ struct mptcp_ext { - union { - u64 data_ack; - u32 data_ack32; - }; u64 data_seq; u32 subflow_seq; u16 data_len; diff --git a/include/trace/events/mptcp.h b/include/trace/events/mptcp.h index 04521acba483..22882bd03459 100644 --- a/include/trace/events/mptcp.h +++ b/include/trace/events/mptcp.h @@ -75,7 +75,6 @@ DECLARE_EVENT_CLASS(mptcp_dump_mpext, TP_ARGS(mpext), TP_STRUCT__entry( - __field(u64, data_ack) __field(u64, data_seq) __field(u32, subflow_seq) __field(u16, data_len) @@ -94,7 +93,6 @@ DECLARE_EVENT_CLASS(mptcp_dump_mpext, ), TP_fast_assign( - __entry->data_ack = mpext->ack64 ? mpext->data_ack : mpext->data_ack32; __entry->data_seq = mpext->data_seq; __entry->subflow_seq = mpext->subflow_seq; __entry->data_len = mpext->data_len; @@ -112,8 +110,8 @@ DECLARE_EVENT_CLASS(mptcp_dump_mpext, __entry->infinite_map = mpext->infinite_map; ), - TP_printk("data_ack=%llu data_seq=%llu subflow_seq=%u data_len=%u csum=%x use_map=%u dsn64=%u data_fin=%u use_ack=%u ack64=%u mpc_map=%u frozen=%u reset_transient=%u reset_reason=%u csum_reqd=%u infinite_map=%u", - __entry->data_ack, __entry->data_seq, + TP_printk("data_seq=%llu subflow_seq=%u data_len=%u csum=%x use_map=%u dsn64=%u data_fin=%u use_ack=%u ack64=%u mpc_map=%u frozen=%u reset_transient=%u reset_reason=%u csum_reqd=%u infinite_map=%u", + __entry->data_seq, __entry->subflow_seq, __entry->data_len, __entry->csum, __entry->use_map, __entry->dsn64, __entry->data_fin, From bb961fdd1700859ab764bae3f7b0c8f3b14f87dc Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Wed, 12 Aug 2026 16:55:38 +0200 Subject: [PATCH 1368/1433] mptcp: pm: userspace: make remove_addr_entry static Only used in pm_userspace.c. While at it, use the mptcp_userspace_pm_ prefix, like most functions in this file: that makes it clear it is specific to this userspace PM. Reviewed-by: Geliang Tang Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260812-net-next-mptcp-misc-feat-7-3-v1-3-1905a818f6cb@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/pm_userspace.c | 7 ++++--- net/mptcp/protocol.h | 2 -- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/net/mptcp/pm_userspace.c b/net/mptcp/pm_userspace.c index 2203cc2d2748..b94fbb483bf9 100644 --- a/net/mptcp/pm_userspace.c +++ b/net/mptcp/pm_userspace.c @@ -281,8 +281,9 @@ static int mptcp_userspace_pm_remove_id_zero_address(struct mptcp_sock *msk) return err; } -void mptcp_pm_remove_addr_entry(struct mptcp_sock *msk, - struct mptcp_pm_addr_entry *entry) +static void +mptcp_userspace_pm_remove_addr_entry(struct mptcp_sock *msk, + struct mptcp_pm_addr_entry *entry) { struct mptcp_rm_list alist = { .nr = 0 }; int anno_nr = 0; @@ -340,7 +341,7 @@ int mptcp_pm_nl_remove_doit(struct sk_buff *skb, struct genl_info *info) list_del_rcu(&match->list); spin_unlock_bh(&msk->pm.lock); - mptcp_pm_remove_addr_entry(msk, match); + mptcp_userspace_pm_remove_addr_entry(msk, match); release_sock(sk); diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index 20627e12c113..06a107d4e839 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -1149,8 +1149,6 @@ int mptcp_pm_announce_addr(struct mptcp_sock *msk, const struct mptcp_addr_info *addr, bool echo); int mptcp_pm_remove_addr(struct mptcp_sock *msk, const struct mptcp_rm_list *rm_list); -void mptcp_pm_remove_addr_entry(struct mptcp_sock *msk, - struct mptcp_pm_addr_entry *entry); /* the default path manager, used in mptcp_pm_unregister */ extern struct mptcp_pm_ops mptcp_pm_kernel; From ea4eb2adb0d3414abeddaba72dbf8876fa66528f Mon Sep 17 00:00:00 2001 From: Kalpan Jani Date: Wed, 12 Aug 2026 16:55:39 +0200 Subject: [PATCH 1369/1433] mptcp: honour configured min/max RTO in retransmit paths The MPTCP-level retransmit timers (DATA_FIN retransmissions and the fallback timeout) used the hard-coded TCP_RTO_MIN / TCP_RTO_MAX constants, ignoring the tcp_rto_min_us and tcp_rto_max_ms sysctls. Make them follow the sysctls instead: seed icsk_rto_min / icsk_rto_max on the MPTCP socket from the per-netns sysctls in __mptcp_init_sock() -- the msk does not go through tcp_init_sock(), so these fields would otherwise stay zero -- and read them directly where the constants were used: - mptcp_set_datafin_timeout(): both the backoff cap computation and the resulting timer_ival. The two sysctls are validated independently, so rto_min > rto_max is a valid configuration; keep a max_t() guard so ilog2() is never called with 0. - __mptcp_set_timeout(): the fallback when no subflow timeout is available. The icsk fields are read directly instead of using the tcp_rto_min()/tcp_rto_max() helpers: the MPTCP socket does not perform routing lookups in these paths, so the rto_min route metric checked by tcp_rto_min() can never apply here. The TCP_RTO_MIN_US / TCP_RTO_MAX_MS socket options are not supported by MPTCP setsockopt() either; this can be revisited if they get supported on MPTCP sockets. The remaining uses of TCP_RTO_MAX in net/mptcp/ctrl.c (default add_addr_timeout) and net/mptcp/subflow.c (MP_FAIL timeout) are intentionally left unchanged: they use the constant as a default duration, not as an RTO bound on a retransmit timer. Closes: https://github.com/multipath-tcp/mptcp_net-next/issues/618 Signed-off-by: Kalpan Jani Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260812-net-next-mptcp-misc-feat-7-3-v1-4-1905a818f6cb@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index ec874d2ead6a..8f074d757743 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -581,17 +581,23 @@ static bool mptcp_pending_data_fin(struct sock *sk, u64 *seq) static void mptcp_set_datafin_timeout(struct sock *sk) { struct inet_connection_sock *icsk = inet_csk(sk); + u32 rto_min = READ_ONCE(icsk->icsk_rto_min); + u32 rto_max = READ_ONCE(icsk->icsk_rto_max); u32 retransmits; + /* The sysctls are validated independently: rto_min > rto_max is + * possible, guard against ilog2(0). + */ retransmits = min_t(u32, icsk->icsk_retransmits, - ilog2(TCP_RTO_MAX / TCP_RTO_MIN)); + ilog2(max_t(u32, rto_max / rto_min, 1))); - mptcp_sk(sk)->timer_ival = TCP_RTO_MIN << retransmits; + mptcp_sk(sk)->timer_ival = rto_min << retransmits; } static void __mptcp_set_timeout(struct sock *sk, long tout) { - mptcp_sk(sk)->timer_ival = tout > 0 ? tout : TCP_RTO_MIN; + mptcp_sk(sk)->timer_ival = tout > 0 ? tout : + READ_ONCE(inet_csk(sk)->icsk_rto_min); } static long mptcp_timeout_from_subflow(const struct mptcp_subflow_context *subflow) @@ -3161,7 +3167,9 @@ static void mptcp_worker(struct work_struct *work) static void __mptcp_init_sock(struct sock *sk) { + struct inet_connection_sock *icsk = inet_csk(sk); struct mptcp_sock *msk = mptcp_sk(sk); + struct net *net = sock_net(sk); INIT_LIST_HEAD(&msk->conn_list); INIT_LIST_HEAD(&msk->join_list); @@ -3170,7 +3178,13 @@ static void __mptcp_init_sock(struct sock *sk) INIT_WORK(&msk->work, mptcp_worker); msk->out_of_order_queue = RB_ROOT; msk->first_pending = NULL; - msk->timer_ival = TCP_RTO_MIN; + + /* msk does not go through tcp_init_sock(); seed RTO bounds. */ + icsk->icsk_rto_min = + usecs_to_jiffies(READ_ONCE(net->ipv4.sysctl_tcp_rto_min_us)); + icsk->icsk_rto_max = + msecs_to_jiffies(READ_ONCE(net->ipv4.sysctl_tcp_rto_max_ms)); + msk->timer_ival = icsk->icsk_rto_min; msk->scaling_ratio = TCP_DEFAULT_SCALING_RATIO; msk->backlog_len = 0; mptcp_init_rtt_est(msk); From 3420c0fd7e5d89c6fdb4be609ab16bb7814c6176 Mon Sep 17 00:00:00 2001 From: Shardul Bankar Date: Wed, 12 Aug 2026 16:55:40 +0200 Subject: [PATCH 1370/1433] mptcp: add per-event MIB counters for MPTCP_RST_EMPTCP resets MPTCP_RST_EMPTCP (reset reason 1) is used as a catch-all for several distinct error conditions across subflow setup, authentication, and data-path validation. The existing MPRstTx/MPRstRx counters only track aggregate reset volume, making it difficult to diagnose which code path is triggering subflow resets in production. Add per-event MIB counters covering each MPTCP_RST_EMPTCP use site that is not already covered by an existing counter, named after the underlying event or condition rather than the reset action: MD5SigReset MD5SIG enabled on listener (incompatible) MPJoinSynAckNoMPJoin SYN/ACK missing MP_JOIN option MPJoinAckNoMPJoin server-side ACK missing MP_JOIN option (fallback path, MPJoin required) MPJoinAckNoCtx server-side ACK with no subflow context MPJoinNoIdFound MP_JOIN with a valid token but no PM local ID DssReset data mapping invalid (also fires on MAPPING_NODSS / EMIDDLEBOX path) MPJoinNotEstablished JOIN attempted on a not-fully-established msk MPJoinNoIdFound covers the second half of the no-msk MP_JOIN reset: the existing MPJoinNoTokenFound (MPTCP_MIB_JOINNOTOKEN) only counts the missing-token case in subflow_token_join_request(), while a JOIN that carries a valid token but for which the path manager returns no local id reaches the same MPTCP_RST_EMPTCP in subflow_check_req() uncounted. The aggregate MPRstTx/MPRstRx counters are unchanged. Closes: https://github.com/multipath-tcp/mptcp_net-next/issues/511 Signed-off-by: Shardul Bankar Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260812-net-next-mptcp-misc-feat-7-3-v1-5-1905a818f6cb@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/mib.c | 7 +++++++ net/mptcp/mib.h | 7 +++++++ net/mptcp/protocol.c | 1 + net/mptcp/subflow.c | 10 ++++++++++ 4 files changed, 25 insertions(+) diff --git a/net/mptcp/mib.c b/net/mptcp/mib.c index 2569385bab7c..608cb568897c 100644 --- a/net/mptcp/mib.c +++ b/net/mptcp/mib.c @@ -21,14 +21,19 @@ static const struct snmp_mib mptcp_snmp_list[] = { SNMP_MIB_ITEM("MPFallbackTokenInit", MPTCP_MIB_TOKENFALLBACKINIT), SNMP_MIB_ITEM("MPTCPRetrans", MPTCP_MIB_RETRANSSEGS), SNMP_MIB_ITEM("MPJoinNoTokenFound", MPTCP_MIB_JOINNOTOKEN), + SNMP_MIB_ITEM("MPJoinNoIdFound", MPTCP_MIB_MPJOINNOIDFOUND), SNMP_MIB_ITEM("MPJoinSynRx", MPTCP_MIB_JOINSYNRX), SNMP_MIB_ITEM("MPJoinSynBackupRx", MPTCP_MIB_JOINSYNBACKUPRX), SNMP_MIB_ITEM("MPJoinSynAckRx", MPTCP_MIB_JOINSYNACKRX), SNMP_MIB_ITEM("MPJoinSynAckBackupRx", MPTCP_MIB_JOINSYNACKBACKUPRX), SNMP_MIB_ITEM("MPJoinSynAckHMacFailure", MPTCP_MIB_JOINSYNACKMAC), + SNMP_MIB_ITEM("MPJoinSynAckNoMPJoin", MPTCP_MIB_MPJOINSYNACKNOMPJOIN), SNMP_MIB_ITEM("MPJoinAckRx", MPTCP_MIB_JOINACKRX), SNMP_MIB_ITEM("MPJoinAckHMacFailure", MPTCP_MIB_JOINACKMAC), + SNMP_MIB_ITEM("MPJoinAckNoMPJoin", MPTCP_MIB_MPJOINACKNOMPJOIN), + SNMP_MIB_ITEM("MPJoinAckNoCtx", MPTCP_MIB_MPJOINACKNOCTX), SNMP_MIB_ITEM("MPJoinRejected", MPTCP_MIB_JOINREJECTED), + SNMP_MIB_ITEM("MPJoinNotEstablished", MPTCP_MIB_MPJOINNOTESTABLISHED), SNMP_MIB_ITEM("MPJoinSynTx", MPTCP_MIB_JOINSYNTX), SNMP_MIB_ITEM("MPJoinSynTxCreatSkErr", MPTCP_MIB_JOINSYNTXCREATSKERR), SNMP_MIB_ITEM("MPJoinSynTxBindErr", MPTCP_MIB_JOINSYNTXBINDERR), @@ -81,7 +86,9 @@ static const struct snmp_mib mptcp_snmp_list[] = { SNMP_MIB_ITEM("Blackhole", MPTCP_MIB_BLACKHOLE), SNMP_MIB_ITEM("MPCapableDataFallback", MPTCP_MIB_MPCAPABLEDATAFALLBACK), SNMP_MIB_ITEM("MD5SigFallback", MPTCP_MIB_MD5SIGFALLBACK), + SNMP_MIB_ITEM("MD5SigReset", MPTCP_MIB_MD5SIGRESET), SNMP_MIB_ITEM("DssFallback", MPTCP_MIB_DSSFALLBACK), + SNMP_MIB_ITEM("DssReset", MPTCP_MIB_DSSRESET), SNMP_MIB_ITEM("SimultConnectFallback", MPTCP_MIB_SIMULTCONNFALLBACK), SNMP_MIB_ITEM("FallbackFailed", MPTCP_MIB_FALLBACKFAILED), SNMP_MIB_ITEM("WinProbe", MPTCP_MIB_WINPROBE), diff --git a/net/mptcp/mib.h b/net/mptcp/mib.h index 3a3425e258a7..1ebdb55e9534 100644 --- a/net/mptcp/mib.h +++ b/net/mptcp/mib.h @@ -16,14 +16,19 @@ enum linux_mptcp_mib_field { MPTCP_MIB_TOKENFALLBACKINIT, /* Could not init/allocate token */ MPTCP_MIB_RETRANSSEGS, /* Segments retransmitted at the MPTCP-level */ MPTCP_MIB_JOINNOTOKEN, /* Received MP_JOIN but the token was not found */ + MPTCP_MIB_MPJOINNOIDFOUND, /* Received MP_JOIN but no local ID was found */ MPTCP_MIB_JOINSYNRX, /* Received a SYN + MP_JOIN */ MPTCP_MIB_JOINSYNBACKUPRX, /* Received a SYN + MP_JOIN + backup flag */ MPTCP_MIB_JOINSYNACKRX, /* Received a SYN/ACK + MP_JOIN */ MPTCP_MIB_JOINSYNACKBACKUPRX, /* Received a SYN/ACK + MP_JOIN + backup flag */ MPTCP_MIB_JOINSYNACKMAC, /* HMAC was wrong on SYN/ACK + MP_JOIN */ + MPTCP_MIB_MPJOINSYNACKNOMPJOIN, /* MP_RST: missing MP_JOIN in SYN/ACK */ MPTCP_MIB_JOINACKRX, /* Received an ACK + MP_JOIN */ MPTCP_MIB_JOINACKMAC, /* HMAC was wrong on ACK + MP_JOIN */ + MPTCP_MIB_MPJOINACKNOMPJOIN, /* MP_RST: missing MP_JOIN in ACK */ + MPTCP_MIB_MPJOINACKNOCTX, /* MP_RST: no subflow context on ACK */ MPTCP_MIB_JOINREJECTED, /* The PM rejected the JOIN request */ + MPTCP_MIB_MPJOINNOTESTABLISHED, /* MP_RST: JOIN on not-fully-established msk */ MPTCP_MIB_JOINSYNTX, /* Sending a SYN + MP_JOIN */ MPTCP_MIB_JOINSYNTXCREATSKERR, /* Not able to create a socket when sending a SYN + MP_JOIN */ MPTCP_MIB_JOINSYNTXBINDERR, /* Not able to bind() the address when sending a SYN + MP_JOIN */ @@ -84,7 +89,9 @@ enum linux_mptcp_mib_field { * established packet */ MPTCP_MIB_MD5SIGFALLBACK, /* Conflicting TCP option enabled */ + MPTCP_MIB_MD5SIGRESET, /* MP_RST: MD5SIG enabled on listener */ MPTCP_MIB_DSSFALLBACK, /* Bad or missing DSS */ + MPTCP_MIB_DSSRESET, /* MP_RST: bad data mapping */ MPTCP_MIB_SIMULTCONNFALLBACK, /* Simultaneous connect */ MPTCP_MIB_FALLBACKFAILED, /* Can't fallback due to msk status */ MPTCP_MIB_WINPROBE, /* MPTCP-level zero window probe */ diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 8f074d757743..b474d03620a7 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -4002,6 +4002,7 @@ bool mptcp_finish_join(struct sock *ssk) /* mptcp socket already closing? */ if (!mptcp_is_fully_established(parent)) { + MPTCP_INC_STATS(sock_net(parent), MPTCP_MIB_MPJOINNOTESTABLISHED); subflow->reset_reason = MPTCP_RST_EMPTCP; return false; } diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c index e1f20ff8fdb4..af81ad5e699d 100644 --- a/net/mptcp/subflow.c +++ b/net/mptcp/subflow.c @@ -96,6 +96,7 @@ static struct mptcp_sock *subflow_token_join_request(struct request_sock *req) local_id = mptcp_pm_get_local_id(msk, (struct sock_common *)req); if (local_id < 0) { + SUBFLOW_REQ_INC_STATS(req, MPTCP_MIB_MPJOINNOIDFOUND); sock_put((struct sock *)msk); return NULL; } @@ -160,6 +161,7 @@ static int subflow_check_req(struct request_sock *req, * TCP option space. */ if (rcu_access_pointer(tcp_sk(sk_listener)->md5sig_info)) { + MPTCP_INC_STATS(sock_net(sk_listener), MPTCP_MIB_MD5SIGRESET); subflow_add_reset_reason(skb, MPTCP_RST_EMPTCP); return -EINVAL; } @@ -563,6 +565,7 @@ static void subflow_finish_connect(struct sock *sk, const struct sk_buff *skb) u8 hmac[SHA256_DIGEST_SIZE]; if (!(mp_opt.suboptions & OPTION_MPTCP_MPJ_SYNACK)) { + MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_MPJOINSYNACKNOMPJOIN); subflow->reset_reason = MPTCP_RST_EMPTCP; goto do_reset; } @@ -865,6 +868,12 @@ static struct sock *subflow_syn_recv_sock(const struct sock *sk, */ if (!ctx || fallback) { if (fallback_is_fatal) { + if (!ctx) + MPTCP_INC_STATS(sock_net(sk), + MPTCP_MIB_MPJOINACKNOCTX); + else + MPTCP_INC_STATS(sock_net(sk), + MPTCP_MIB_MPJOINACKNOMPJOIN); subflow_add_reset_reason(skb, MPTCP_RST_EMPTCP); goto dispose_child; } @@ -1416,6 +1425,7 @@ static bool subflow_check_data_avail(struct sock *ssk) * subflow_error_report() will introduce the appropriate barriers */ subflow->reset_transient = 0; + MPTCP_INC_STATS(sock_net(ssk), MPTCP_MIB_DSSRESET); subflow->reset_reason = status == MAPPING_NODSS ? MPTCP_RST_EMIDDLEBOX : MPTCP_RST_EMPTCP; From 1aa38a1581c42bd919101bbbdcaa9e4c49d71844 Mon Sep 17 00:00:00 2001 From: Shardul Bankar Date: Wed, 12 Aug 2026 16:55:41 +0200 Subject: [PATCH 1371/1433] selftests: mptcp: check per-event MPTCP_RST_EMPTCP counters Add named env-var expectations for each per-event MPTCP_RST_EMPTCP counter, matching the pattern used by the existing JOIN/RST checks. Each defaults to 0 and is checked silently on success; a mismatch prints a check line and fails the test. Counters absent from the running kernel are skipped silently so older kernels do not false-fail. The JOIN-related counters (MPJoinSynAckNoMPJoin, MPJoinAckNoMPJoin, MPJoinAckNoCtx, MPJoinNotEstablished, MPJoinNoIdFound) are checked in chk_join_nr() on fixed namespaces; the two remaining reset counters (MD5SigReset, DssReset) stay in chk_rst_nr(). Add a test at the end of signal_address_tests that triggers MPJoinSynAckNoMPJoin: ns1 signals an address that is already bound on the client (ns2), where a TCP-only mptcp_connect listener is started. The client's MP_JOIN routes locally to the TCP listener, which responds with a plain SYN/ACK without the MP_JOIN option, and the new counter increments on the client side. Other per-event counters (MD5SigReset, MPJoinAckNoMPJoin, MPJoinAckNoCtx, DssReset, MPJoinNotEstablished, MPJoinNoIdFound) are not currently reachable from mptcp_join.sh; the env-var hooks are in place for future tests to set expectations explicitly. Signed-off-by: Shardul Bankar Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260812-net-next-mptcp-misc-feat-7-3-v1-6-1905a818f6cb@kernel.org Signed-off-by: Jakub Kicinski --- .../testing/selftests/net/mptcp/mptcp_join.sh | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/tools/testing/selftests/net/mptcp/mptcp_join.sh b/tools/testing/selftests/net/mptcp/mptcp_join.sh index 7dc91fac4917..9b9fb3de9da3 100755 --- a/tools/testing/selftests/net/mptcp/mptcp_join.sh +++ b/tools/testing/selftests/net/mptcp/mptcp_join.sh @@ -75,6 +75,14 @@ unset join_syn_tx unset join_create_err unset join_bind_err unset join_connect_err +unset join_synack_no_mpjoin +unset join_ack_no_mpjoin +unset join_ack_no_ctx +unset join_not_established +unset join_no_id_found + +unset rst_md5sig +unset rst_dss unset fb_ns1 unset fb_ns2 @@ -1353,6 +1361,8 @@ chk_rst_nr() local rst_tx=$1 local rst_rx=$2 local ns_invert=${3:-""} + local md5sig=${rst_md5sig:-0} + local dss=${rst_dss:-0} local count local ns_tx=$ns1 local ns_rx=$ns2 @@ -1389,6 +1399,21 @@ chk_rst_nr() else print_ok fi + + # MPTCP_RST_EMPTCP reset-event counters; default 0, gated on + # availability. Fixed namespaces: MD5SigReset fires on the listener + # (server), DssReset on the data receiver (client). + count=$(mptcp_lib_get_counter ${ns1} "MPTcpExtMD5SigReset") + if [ -n "$count" ] && [ "$count" != "$md5sig" ]; then + print_check "MD5SigReset" + fail_test "got $count MD5SigReset expected $md5sig" + fi + + count=$(mptcp_lib_get_counter ${ns2} "MPTcpExtDssReset") + if [ -n "$count" ] && [ "$count" != "$dss" ]; then + print_check "DssReset" + fail_test "got $count DssReset expected $dss" + fi } chk_infi_nr() @@ -1587,6 +1612,11 @@ chk_join_nr() local rst_nr=${join_rst_nr:-0} local infi_nr=${join_infi_nr:-0} local corrupted_pkts=${join_corrupted_pkts:-0} + local synack_no_mpjoin=${join_synack_no_mpjoin:-0} + local ack_no_mpjoin=${join_ack_no_mpjoin:-0} + local ack_no_ctx=${join_ack_no_ctx:-0} + local not_established=${join_not_established:-0} + local no_id_found=${join_no_id_found:-0} local rc=${KSFT_PASS} local count local with_cookie @@ -1655,6 +1685,44 @@ chk_join_nr() fail_test "got $count JOIN[s] syn rejected expected $syn_rej" fi + # Per-event MPTCP_RST_EMPTCP JOIN counters; default 0, gated on + # availability. Fixed namespaces: the *SynAck* one fires on the + # client receiving the SYN/ACK, the others on the server. + count=$(mptcp_lib_get_counter ${ns2} "MPTcpExtMPJoinSynAckNoMPJoin") + if [ -n "$count" ] && [ "$count" != "$synack_no_mpjoin" ]; then + rc=${KSFT_FAIL} + print_check "synack no mpjoin" + fail_test "got $count JOIN[s] synack no mpjoin expected $synack_no_mpjoin" + fi + + count=$(mptcp_lib_get_counter ${ns1} "MPTcpExtMPJoinAckNoMPJoin") + if [ -n "$count" ] && [ "$count" != "$ack_no_mpjoin" ]; then + rc=${KSFT_FAIL} + print_check "ack no mpjoin" + fail_test "got $count JOIN[s] ack no mpjoin expected $ack_no_mpjoin" + fi + + count=$(mptcp_lib_get_counter ${ns1} "MPTcpExtMPJoinAckNoCtx") + if [ -n "$count" ] && [ "$count" != "$ack_no_ctx" ]; then + rc=${KSFT_FAIL} + print_check "ack no ctx" + fail_test "got $count JOIN[s] ack no ctx expected $ack_no_ctx" + fi + + count=$(mptcp_lib_get_counter ${ns1} "MPTcpExtMPJoinNotEstablished") + if [ -n "$count" ] && [ "$count" != "$not_established" ]; then + rc=${KSFT_FAIL} + print_check "join not established" + fail_test "got $count JOIN[s] not established expected $not_established" + fi + + count=$(mptcp_lib_get_counter ${ns1} "MPTcpExtMPJoinNoIdFound") + if [ -n "$count" ] && [ "$count" != "$no_id_found" ]; then + rc=${KSFT_FAIL} + print_check "join no id found" + fail_test "got $count JOIN[s] no id found expected $no_id_found" + fi + print_results "join Rx" ${rc} join_syn_tx="${join_syn_tx:-${syn_nr}}" \ @@ -2359,6 +2427,31 @@ signal_address_tests() chk_add_nr 4 4 fi fi + + # signalled address belongs to the client, where a TCP-only + # listener is bound at it: the client's MP_JOIN routes locally + # to the listener and receives a SYN/ACK without MP_JOIN. + # MPJoinSynAckNoMPJoin increments on the client side. + if reset "signal address, TCP-only listener on client"; then + local extra_bind + local port + + pm_nl_set_limits $ns1 0 1 + pm_nl_set_limits $ns2 1 1 + pm_nl_add_endpoint $ns1 10.0.2.2 flags signal + + port=$(get_port) + ip netns exec ${ns2} ./mptcp_connect -l -t -1 -p "$port" \ + -s TCP 10.0.2.2 & + extra_bind=$! + mptcp_lib_wait_local_port_listen "$ns2" "$port" + + run_tests $ns1 $ns2 10.0.1.1 + join_synack_no_mpjoin=1 join_syn_tx=1 \ + chk_join_nr 0 0 0 + + kill ${extra_bind} 2>/dev/null + fi } laminar_endp_tests() From 158765a0e50d94f5891b3ef560b4610122ab8052 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Wed, 12 Aug 2026 16:55:42 +0200 Subject: [PATCH 1372/1433] selftests: mptcp: connect: test name in pcap file Even if the pcap prefix is printed in the test, it is clearer if this prefix also include the test name: mptcp_connect. With this, it is easily possible to find out which pcap was produced by which test, and easily delete the right ones. Reviewed-by: Mat Martineau Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260812-net-next-mptcp-misc-feat-7-3-v1-7-1905a818f6cb@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/mptcp/mptcp_connect.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/net/mptcp/mptcp_connect.sh b/tools/testing/selftests/net/mptcp/mptcp_connect.sh index d158678fa6ab..5befd8584a4d 100755 --- a/tools/testing/selftests/net/mptcp/mptcp_connect.sh +++ b/tools/testing/selftests/net/mptcp/mptcp_connect.sh @@ -212,8 +212,8 @@ if $checksum; then fi if $capture; then - rndh="${ns1:4}" - mptcp_lib_pr_info "Packet capture files will have this prefix: ${rndh}-" + capprefix="mptcp_connect-${ns1:4}" + mptcp_lib_pr_info "pcap will have this prefix: ${capprefix}-" fi set_ethtool_flags() { @@ -372,7 +372,7 @@ do_transfer() capuser="-Z $SUDO_USER" fi - local capfile="${rndh}-${connector_ns:0:3}-${listener_ns:0:3}-${cl_proto}-${srv_proto}-${connect_addr}-${port}" + local capfile="${capprefix}-${connector_ns:0:3}-${listener_ns:0:3}-${cl_proto}-${srv_proto}-${connect_addr}-${port}" local capopt="-i any -s 65535 -B 32768 ${capuser}" ip netns exec ${listener_ns} tcpdump ${capopt} \ From a574bd9b6101eeb8be2819716d870007e8bbfefd Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Wed, 12 Aug 2026 16:55:43 +0200 Subject: [PATCH 1373/1433] selftests: mptcp: simult_flow: test name in pcap file To be able to easily find out which pcap was produced by which test, the selftest name is now added to the pcap file, similar to the other tests. While at it, print the prefix name to be able to find which capture files have been produced by which test after several runs. This prefix was not printed anywhere before. Reviewed-by: Mat Martineau Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260812-net-next-mptcp-misc-feat-7-3-v1-8-1905a818f6cb@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/mptcp/simult_flows.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/net/mptcp/simult_flows.sh b/tools/testing/selftests/net/mptcp/simult_flows.sh index 7b9aabe10170..d723261bdc62 100755 --- a/tools/testing/selftests/net/mptcp/simult_flows.sh +++ b/tools/testing/selftests/net/mptcp/simult_flows.sh @@ -24,6 +24,7 @@ small="" sout="" cout="" capout="" +capprefix="" size=0 usage() { @@ -70,6 +71,11 @@ setup() mptcp_lib_ns_init ns1 ns2 ns3 + if $capture; then + capprefix="simult_flows-${ns1:4}" + mptcp_lib_pr_info "pcap will have this prefix: ${capprefix}-" + fi + ip link add ns1eth1 netns "$ns1" type veth peer name ns2eth1 netns "$ns2" ip link add ns1eth2 netns "$ns1" type veth peer name ns2eth2 netns "$ns2" ip link add ns2eth3 netns "$ns2" type veth peer name ns3eth1 netns "$ns3" @@ -136,14 +142,13 @@ do_transfer() if $capture; then local capuser - local rndh="${ns1:4}" if [ -z $SUDO_USER ] ; then capuser="" else capuser="-Z $SUDO_USER" fi - local capfile="${rndh}-${port}" + local capfile="${capprefix}-${port}" local capopt="-i any -s 65535 -B 32768 ${capuser}" ip netns exec ${ns3} tcpdump ${capopt} -w "${capfile}-listener.pcap" >> "${capout}" 2>&1 & From 1d206e8e4371ced5ac83516234c0ddd882d7abd2 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Wed, 12 Aug 2026 16:55:44 +0200 Subject: [PATCH 1374/1433] selftests: mptcp: pcap: drop most of the payload Limit the size of each captured packet to 108B (IPv4 only) or 128B (a mix of v4 and v6): this should drop most of the payload that is generally not needed when debugging an issue. 8 bytes are left in this payload, to be able to inspect the beginning, just in case. Please also note that generally, this payload is usually mostly filled with 0, except at the end. This reduces the .pcap sizes, and reduce IO usage, which helps debugging issues. Reviewed-by: Mat Martineau Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260812-net-next-mptcp-misc-feat-7-3-v1-9-1905a818f6cb@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/mptcp/mptcp_connect.sh | 2 +- tools/testing/selftests/net/mptcp/mptcp_join.sh | 2 +- tools/testing/selftests/net/mptcp/simult_flows.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/net/mptcp/mptcp_connect.sh b/tools/testing/selftests/net/mptcp/mptcp_connect.sh index 5befd8584a4d..7a2a851fa0ad 100755 --- a/tools/testing/selftests/net/mptcp/mptcp_connect.sh +++ b/tools/testing/selftests/net/mptcp/mptcp_connect.sh @@ -373,7 +373,7 @@ do_transfer() fi local capfile="${capprefix}-${connector_ns:0:3}-${listener_ns:0:3}-${cl_proto}-${srv_proto}-${connect_addr}-${port}" - local capopt="-i any -s 65535 -B 32768 ${capuser}" + local capopt="-i any -s 128 -B 32768 ${capuser}" ip netns exec ${listener_ns} tcpdump ${capopt} \ -w "${capfile}-listener.pcap" >> "${capout}" 2>&1 & diff --git a/tools/testing/selftests/net/mptcp/mptcp_join.sh b/tools/testing/selftests/net/mptcp/mptcp_join.sh index 9b9fb3de9da3..18ce7136a2b0 100755 --- a/tools/testing/selftests/net/mptcp/mptcp_join.sh +++ b/tools/testing/selftests/net/mptcp/mptcp_join.sh @@ -979,7 +979,7 @@ cond_start_capture() capfile=$(printf "mp_join-%02u-%s.pcap" "$MPTCP_LIB_TEST_COUNTER" "$ns") echo "Capturing traffic for test $MPTCP_LIB_TEST_COUNTER into $capfile" - ip netns exec "$ns" tcpdump -i any -s 65535 -B 32768 $capuser -w "$capfile" > "$capout" 2>&1 & + ip netns exec "$ns" tcpdump -i any -s 128 -B 32768 $capuser -w "$capfile" > "$capout" 2>&1 & cappid=$! sleep 1 diff --git a/tools/testing/selftests/net/mptcp/simult_flows.sh b/tools/testing/selftests/net/mptcp/simult_flows.sh index d723261bdc62..3ea3d1efe32e 100755 --- a/tools/testing/selftests/net/mptcp/simult_flows.sh +++ b/tools/testing/selftests/net/mptcp/simult_flows.sh @@ -149,7 +149,7 @@ do_transfer() fi local capfile="${capprefix}-${port}" - local capopt="-i any -s 65535 -B 32768 ${capuser}" + local capopt="-i any -s 108 -B 32768 ${capuser}" ip netns exec ${ns3} tcpdump ${capopt} -w "${capfile}-listener.pcap" >> "${capout}" 2>&1 & local cappid_listener=$! From f4a1b63ed5202f1bec3d641b2456d97303623a49 Mon Sep 17 00:00:00 2001 From: Geliang Tang Date: Wed, 12 Aug 2026 16:55:45 +0200 Subject: [PATCH 1375/1433] selftests: mptcp: fix const qualifier warnings in strchr usage In mptcp_connect.c, strchr() returns a pointer to a character within the input string, which is declared as const char *. Assigning this return value to a non-const char * discards the const qualifier, triggering compiler warnings: make: Entering directory 'tools/testing/selftests/net/mptcp' CC mptcp_connect mptcp_connect.c: In function 'parse_cmsg_types': mptcp_connect.c:1267:22: warning: initialization discards 'const' qualifier from pointer target type [-Wdiscarded-qualifiers] 1267 | char *next = strchr(type, ','); | ^~~~~~ mptcp_connect.c: In function 'parse_setsock_options': mptcp_connect.c:1295:22: warning: initialization discards 'const' qualifier from pointer target type [-Wdiscarded-qualifiers] 1295 | char *next = strchr(name, ','); | ^~~~~~ make: Leaving directory 'tools/testing/selftests/net/mptcp' Fix these warnings by declaring the 'next' variable as const char *, as it is only used for read-only parsing. Signed-off-by: Geliang Tang Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260812-net-next-mptcp-misc-feat-7-3-v1-10-1905a818f6cb@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/mptcp/mptcp_connect.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/net/mptcp/mptcp_connect.c b/tools/testing/selftests/net/mptcp/mptcp_connect.c index cbe573c4ab3a..ea4cb6c1bd5e 100644 --- a/tools/testing/selftests/net/mptcp/mptcp_connect.c +++ b/tools/testing/selftests/net/mptcp/mptcp_connect.c @@ -1264,7 +1264,7 @@ static void apply_cmsg_types(int fd, const struct cfg_cmsg_types *cmsg) static void parse_cmsg_types(const char *type) { - char *next = strchr(type, ','); + const char *next = strchr(type, ','); unsigned int len = 0; cfg_cmsg_types.cmsg_enabled = 1; @@ -1292,7 +1292,7 @@ static void parse_cmsg_types(const char *type) static void parse_setsock_options(const char *name) { - char *next = strchr(name, ','); + const char *next = strchr(name, ','); unsigned int len = 0; if (next) { From 6e5635a714c8bd59548d79c8e1e97f1755b10fc7 Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Wed, 12 Aug 2026 16:55:46 +0200 Subject: [PATCH 1376/1433] selftests: mptcp: diag: fix stack buffer overflow in get_subflow_info() get_subflow_info() parses the subflow address string with: char saddr[64], daddr[64]; ret = sscanf(subflow_addrs, "%[^:]:%d %[^:]:%d", saddr, &sport, daddr, &dport); The subflow_addrs buffer holds up to 1024 bytes and is taken directly from the command line ("-c" argument). The "%[^:]" conversions have no maximum field width, so if the address substring before the ':' exceeds 63 bytes, sscanf() writes past the end of the 64-byte saddr/daddr stack buffers. This overflows the stack, corrupting adjacent stack data such as the saved return address, and can crash the tool or lead to out-of-bounds writes controlled by user-supplied input. Bound both string conversions to the destination buffer size by adding an explicit maximum field width of 63 (leaving room for the terminating NUL), so at most 63 bytes are written into each 64-byte buffer: ret = sscanf(subflow_addrs, "%63[^:]:%d %63[^:]:%d", saddr, &sport, daddr, &dport); The subflow address can be passed in argument, so fixing this is helpful when the tool is manually used. Reviewed-by: Geliang Tang Signed-off-by: Jiangshan Yi Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260812-net-next-mptcp-misc-feat-7-3-v1-11-1905a818f6cb@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/mptcp/mptcp_diag.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/net/mptcp/mptcp_diag.c b/tools/testing/selftests/net/mptcp/mptcp_diag.c index 5e222ba977e4..3b8d2c8a6216 100644 --- a/tools/testing/selftests/net/mptcp/mptcp_diag.c +++ b/tools/testing/selftests/net/mptcp/mptcp_diag.c @@ -377,7 +377,8 @@ static void get_subflow_info(char *subflow_addrs) int ret; int fd; - ret = sscanf(subflow_addrs, "%[^:]:%d %[^:]:%d", saddr, &sport, daddr, &dport); + ret = sscanf(subflow_addrs, "%63[^:]:%d %63[^:]:%d", + saddr, &sport, daddr, &dport); if (ret != 4) die_perror("IP PORT Pairs has style problems!"); From a16975e2231b4caa5f90551b5fda68b56144b885 Mon Sep 17 00:00:00 2001 From: Satish Kharat Date: Wed, 12 Aug 2026 05:48:01 -0700 Subject: [PATCH 1377/1433] enic: verify firmware supports V2 SR-IOV at probe time During PF probe, query the firmware get-supported-feature interface to verify that the running firmware supports V2 SR-IOV. Firmware version 5.3(4.72) and later report VIC_FEATURE_SRIOV via CMD_GET_SUPP_FEATURE_VER. If the firmware does not support the feature, set vf_type to ENIC_VF_TYPE_NONE and log a warning so the admin knows a firmware upgrade is needed. The V2 admin-channel and MBOX bring-up added later in this series is gated on ENIC_VF_TYPE_V2, so this downgrade keeps those paths from running on firmware that does not support V2 SR-IOV. VIC_FEATURE_SRIOV is assigned the explicit value 4 to match the firmware ABI. Slot 3 (firmware's VIC_FEATURE_PTP) is reserved with a comment rather than a placeholder enum entry, since PTP is not used by the upstream driver. Suggested-by: Breno Leitao Signed-off-by: Satish Kharat Reviewed-by: Breno Leitao Link: https://patch.msgid.link/20260812-enic-sriov-v2-admin-channel-v2-v13-1-b3809e448aba@cisco.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cisco/enic/enic_main.c | 21 ++++++++++++++++++- drivers/net/ethernet/cisco/enic/vnic_devcmd.h | 2 ++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethernet/cisco/enic/enic_main.c index d98f7e7ccab9..b56e5c75ade3 100644 --- a/drivers/net/ethernet/cisco/enic/enic_main.c +++ b/drivers/net/ethernet/cisco/enic/enic_main.c @@ -2641,8 +2641,10 @@ static void enic_iounmap(struct enic *enic) static void enic_sriov_detect_vf_type(struct enic *enic) { struct pci_dev *pdev = enic->pdev; - int pos; + u64 supported_versions, a1 = 0; u16 vf_dev_id; + int pos; + int err; if (enic_is_sriov_vf(enic) || enic_is_dynamic(enic)) return; @@ -2669,6 +2671,23 @@ static void enic_sriov_detect_vf_type(struct enic *enic) enic->vf_type = ENIC_VF_TYPE_NONE; break; } + + if (enic->vf_type != ENIC_VF_TYPE_V2) + return; + + /* A successful command means firmware recognizes + * VIC_FEATURE_SRIOV; supported_versions is available + * for sub-feature versioning in the future. + */ + err = vnic_dev_get_supported_feature_ver(enic->vdev, + VIC_FEATURE_SRIOV, + &supported_versions, + &a1); + if (err) { + dev_warn(&pdev->dev, + "SR-IOV V2 not supported by current firmware. Upgrade to VIC FW 5.3(4.72) or higher.\n"); + enic->vf_type = ENIC_VF_TYPE_NONE; + } } #endif diff --git a/drivers/net/ethernet/cisco/enic/vnic_devcmd.h b/drivers/net/ethernet/cisco/enic/vnic_devcmd.h index 605ef17f967e..3b6efa743dba 100644 --- a/drivers/net/ethernet/cisco/enic/vnic_devcmd.h +++ b/drivers/net/ethernet/cisco/enic/vnic_devcmd.h @@ -734,6 +734,8 @@ enum vic_feature_t { VIC_FEATURE_VXLAN, VIC_FEATURE_RDMA, VIC_FEATURE_VXLAN_PATCH, + /* slot 3 reserved for firmware VIC_FEATURE_PTP */ + VIC_FEATURE_SRIOV = 4, VIC_FEATURE_MAX, }; From 3258931d4052d0f99417ad9abc58d279dcbb2ab2 Mon Sep 17 00:00:00 2001 From: Satish Kharat Date: Wed, 12 Aug 2026 05:48:02 -0700 Subject: [PATCH 1378/1433] enic: add admin channel open and close for SR-IOV The V2 SR-IOV design uses a dedicated admin channel (WQ/RQ/CQ resources plus an MSI-X interrupt) for PF-VF mailbox communication rather than firmware-proxied devcmds. Introduce enic_admin_channel_open() and enic_admin_channel_close(). Open allocates and initialises the admin WQ, RQ, and two CQs (one per direction), then issues CMD_QP_TYPE_SET to tell firmware the queues are admin-type. Close reverses the sequence. enic_admin_wq_buf_clean() unmaps and frees any WQ buffers still held at close time, fixing a DMA mapping leak when a send times out. Add CMD_QP_TYPE_SET (97), QP_TYPE_ADMIN/DATA, and QP_ENABLE/QP_DISABLE defines to vnic_devcmd.h. Add VNIC_CQ_* named constants to vnic_cq.h so CQ initialisation parameters are self-documenting from their first introduction. Signed-off-by: Satish Kharat Link: https://patch.msgid.link/20260812-enic-sriov-v2-admin-channel-v2-v13-2-b3809e448aba@cisco.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cisco/enic/Makefile | 3 +- drivers/net/ethernet/cisco/enic/enic.h | 5 + drivers/net/ethernet/cisco/enic/enic_admin.c | 227 ++++++++++++++++++ drivers/net/ethernet/cisco/enic/enic_admin.h | 15 ++ drivers/net/ethernet/cisco/enic/vnic_cq.h | 9 + drivers/net/ethernet/cisco/enic/vnic_devcmd.h | 11 + 6 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 drivers/net/ethernet/cisco/enic/enic_admin.c create mode 100644 drivers/net/ethernet/cisco/enic/enic_admin.h diff --git a/drivers/net/ethernet/cisco/enic/Makefile b/drivers/net/ethernet/cisco/enic/Makefile index a96b8332e6e2..7ae72fefc99a 100644 --- a/drivers/net/ethernet/cisco/enic/Makefile +++ b/drivers/net/ethernet/cisco/enic/Makefile @@ -3,5 +3,6 @@ obj-$(CONFIG_ENIC) := enic.o enic-y := enic_main.o vnic_cq.o vnic_intr.o vnic_wq.o \ enic_res.o enic_dev.o enic_pp.o vnic_dev.o vnic_rq.o vnic_vic.o \ - enic_ethtool.o enic_api.o enic_clsf.o enic_rq.o enic_wq.o + enic_ethtool.o enic_api.o enic_clsf.o enic_rq.o enic_wq.o \ + enic_admin.o diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h index 08472420f3a1..398227448b37 100644 --- a/drivers/net/ethernet/cisco/enic/enic.h +++ b/drivers/net/ethernet/cisco/enic/enic.h @@ -292,6 +292,11 @@ struct enic { /* Admin channel resources for SR-IOV MBOX */ bool has_admin_channel; + /* true only while the admin WQ/RQ/CQ are allocated and enabled; gates + * enic_admin_channel_close() so it is a no-op after a failed (re)open + * left the resources freed. + */ + bool admin_chan_up; struct vnic_wq admin_wq; struct vnic_rq admin_rq; struct vnic_cq admin_cq[2]; diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.c b/drivers/net/ethernet/cisco/enic/enic_admin.c new file mode 100644 index 000000000000..50b46b92c88f --- /dev/null +++ b/drivers/net/ethernet/cisco/enic/enic_admin.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright 2025 Cisco Systems, Inc. All rights reserved. + +#include +#include + +#include "vnic_dev.h" +#include "vnic_wq.h" +#include "vnic_rq.h" +#include "vnic_cq.h" +#include "vnic_intr.h" +#include "vnic_resource.h" +#include "vnic_devcmd.h" +#include "enic.h" +#include "enic_admin.h" +#include "cq_desc.h" +#include "wq_enet_desc.h" +#include "rq_enet_desc.h" + +/* Clean up any admin WQ buffers still held by hardware at close time. + * Normally buffers are freed inline after send completion, but a timed-out + * send intentionally leaves the buffer live until the queue is stopped. + */ +static void enic_admin_wq_buf_clean(struct vnic_wq *wq, + struct vnic_wq_buf *buf) +{ + struct enic *enic = vnic_dev_priv(wq->vdev); + + if (buf->os_buf) { + dma_unmap_single(&enic->pdev->dev, buf->dma_addr, + buf->len, DMA_TO_DEVICE); + kfree(buf->os_buf); + buf->os_buf = NULL; + } +} + +/* No-op: admin RQ buffer teardown is handled in enic_admin_channel_close */ +static void enic_admin_rq_buf_clean(struct vnic_rq *rq, + struct vnic_rq_buf *buf) +{ +} + +static int enic_admin_qp_type_set(struct enic *enic, u32 enable) +{ + u64 a0 = QP_TYPE_ADMIN, a1 = enable; + int wait = 1000; + int err; + + spin_lock_bh(&enic->devcmd_lock); + err = vnic_dev_cmd(enic->vdev, CMD_QP_TYPE_SET, &a0, &a1, wait); + spin_unlock_bh(&enic->devcmd_lock); + + return err; +} + +static int enic_admin_alloc_resources(struct enic *enic) +{ + int err; + + err = vnic_wq_alloc_with_type(enic->vdev, &enic->admin_wq, 0, + ENIC_ADMIN_DESC_COUNT, + sizeof(struct wq_enet_desc), + RES_TYPE_ADMIN_WQ); + if (err) + return err; + + err = vnic_rq_alloc_with_type(enic->vdev, &enic->admin_rq, 0, + ENIC_ADMIN_DESC_COUNT, + sizeof(struct rq_enet_desc), + RES_TYPE_ADMIN_RQ); + if (err) + goto free_wq; + + /* admin_cq[0] is the WQ completion queue. WQ CQEs are always + * 16 bytes wide; firmware always writes 16-byte CQEs for WQ + * completions on every WQ, including the admin channel WQ. + * Use sizeof(struct cq_desc) accordingly. + */ + err = vnic_cq_alloc_with_type(enic->vdev, &enic->admin_cq[0], 0, + ENIC_ADMIN_DESC_COUNT, + sizeof(struct cq_desc), + RES_TYPE_ADMIN_CQ); + if (err) + goto free_rq; + + /* admin_cq[1] is the RQ completion queue. Its descriptor size + * must match what firmware writes. enic_ext_cq() called earlier + * in probe issues CMD_CQ_ENTRY_SIZE_SET for VNIC_RQ_ALL, + * programming firmware to write CQ entries of (16 << enic->ext_cq) + * bytes for every RQ CQ on the vNIC, including the admin RQ CQ. + * Allocating with the same size keeps the host poller and + * firmware in lockstep: + * + * - The color/valid bit lives at byte (desc_size - 1) of every + * cq_enet_rq_desc[_32|_64] variant, so enic_admin_cq_color() + * reads it from the correct offset. + * - Only the first 15 bytes of the descriptor (vlan, + * bytes_written_flags, ...) are accessed by the admin path; + * these fields are identical across all three variants (see + * comment in enic_rq.c above cq_enet_rq_desc_dec()). + */ + err = vnic_cq_alloc_with_type(enic->vdev, &enic->admin_cq[1], 1, + ENIC_ADMIN_DESC_COUNT, + 16 << enic->ext_cq, + RES_TYPE_ADMIN_CQ); + if (err) + goto free_cq0; + + return 0; + +free_cq0: + vnic_cq_free(&enic->admin_cq[0]); +free_rq: + vnic_rq_free(&enic->admin_rq); +free_wq: + vnic_wq_free(&enic->admin_wq); + return err; +} + +static void enic_admin_free_resources(struct enic *enic) +{ + vnic_cq_free(&enic->admin_cq[1]); + vnic_cq_free(&enic->admin_cq[0]); + vnic_rq_free(&enic->admin_rq); + vnic_wq_free(&enic->admin_wq); +} + +static void enic_admin_init_resources(struct enic *enic) +{ + vnic_wq_init(&enic->admin_wq, + 0, 0, 0); /* cq_index, err_intr_enable, err_intr_offset */ + vnic_rq_init(&enic->admin_rq, + 1, 0, 0); /* cq_index, err_intr_enable, err_intr_offset */ + vnic_cq_init(&enic->admin_cq[0], + VNIC_CQ_FC_DISABLE, + VNIC_CQ_COLOR_ENABLE, + 0, 0, 1, /* cq_head, cq_tail, cq_tail_color */ + VNIC_CQ_INTR_DISABLE, + VNIC_CQ_ENTRY_ENABLE, + VNIC_CQ_MSG_DISABLE, + 0, /* interrupt_offset */ + 0 /* cq_message_addr */); + vnic_cq_init(&enic->admin_cq[1], + VNIC_CQ_FC_DISABLE, + VNIC_CQ_COLOR_ENABLE, + 0, 0, 1, /* cq_head, cq_tail, cq_tail_color */ + VNIC_CQ_INTR_DISABLE, + VNIC_CQ_ENTRY_ENABLE, + VNIC_CQ_MSG_DISABLE, + 0, /* interrupt_offset */ + 0 /* cq_message_addr */); +} + +int enic_admin_channel_open(struct enic *enic) +{ + int err; + + if (!enic->has_admin_channel) + return -ENODEV; + + err = enic_admin_alloc_resources(enic); + if (err) { + netdev_err(enic->netdev, + "Failed to alloc admin channel resources: %d\n", + err); + return err; + } + + enic_admin_init_resources(enic); + + vnic_wq_enable(&enic->admin_wq); + vnic_rq_enable(&enic->admin_rq); + + err = enic_admin_qp_type_set(enic, QP_ENABLE); + if (err) { + netdev_err(enic->netdev, + "Failed to set admin QP type: %d\n", err); + goto disable_queues; + } + + enic->admin_chan_up = true; + + return 0; + +disable_queues: + enic_admin_qp_type_set(enic, QP_DISABLE); + if (vnic_wq_disable(&enic->admin_wq)) + netdev_warn(enic->netdev, "Failed to disable admin WQ\n"); + if (vnic_rq_disable(&enic->admin_rq)) + netdev_warn(enic->netdev, "Failed to disable admin RQ\n"); + enic_admin_free_resources(enic); + return err; +} + +void enic_admin_channel_close(struct enic *enic) +{ + int err; + + if (!enic->has_admin_channel) + return; + + /* Nothing to tear down if the channel was never (re)opened, e.g. a + * failed enic_admin_channel_open() in probe or in the reset path; + * otherwise the disable/clean calls below dereference freed resources. + */ + if (!enic->admin_chan_up) + return; + + enic_admin_qp_type_set(enic, QP_DISABLE); + + err = vnic_wq_disable(&enic->admin_wq); + if (err) + netdev_warn(enic->netdev, + "Failed to disable admin WQ: %d\n", err); + err = vnic_rq_disable(&enic->admin_rq); + if (err) + netdev_warn(enic->netdev, + "Failed to disable admin RQ: %d\n", err); + + vnic_wq_clean(&enic->admin_wq, enic_admin_wq_buf_clean); + vnic_rq_clean(&enic->admin_rq, enic_admin_rq_buf_clean); + vnic_cq_clean(&enic->admin_cq[0]); + vnic_cq_clean(&enic->admin_cq[1]); + enic_admin_free_resources(enic); + + enic->admin_chan_up = false; +} diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.h b/drivers/net/ethernet/cisco/enic/enic_admin.h new file mode 100644 index 000000000000..569aadeb9312 --- /dev/null +++ b/drivers/net/ethernet/cisco/enic/enic_admin.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright 2025 Cisco Systems, Inc. All rights reserved. */ + +#ifndef _ENIC_ADMIN_H_ +#define _ENIC_ADMIN_H_ + +#define ENIC_ADMIN_DESC_COUNT 64 +#define ENIC_ADMIN_BUF_SIZE 2048 + +struct enic; + +int enic_admin_channel_open(struct enic *enic); +void enic_admin_channel_close(struct enic *enic); + +#endif /* _ENIC_ADMIN_H_ */ diff --git a/drivers/net/ethernet/cisco/enic/vnic_cq.h b/drivers/net/ethernet/cisco/enic/vnic_cq.h index d46d4d2ef6bb..35ffa3230713 100644 --- a/drivers/net/ethernet/cisco/enic/vnic_cq.h +++ b/drivers/net/ethernet/cisco/enic/vnic_cq.h @@ -76,6 +76,15 @@ int vnic_cq_alloc(struct vnic_dev *vdev, struct vnic_cq *cq, unsigned int index, int vnic_cq_alloc_with_type(struct vnic_dev *vdev, struct vnic_cq *cq, unsigned int index, unsigned int desc_count, unsigned int desc_size, unsigned int res_type); +#define VNIC_CQ_FC_ENABLE 1 +#define VNIC_CQ_FC_DISABLE 0 +#define VNIC_CQ_COLOR_ENABLE 1 +#define VNIC_CQ_INTR_ENABLE 1 +#define VNIC_CQ_INTR_DISABLE 0 +#define VNIC_CQ_ENTRY_ENABLE 1 +#define VNIC_CQ_MSG_ENABLE 1 +#define VNIC_CQ_MSG_DISABLE 0 + void vnic_cq_init(struct vnic_cq *cq, unsigned int flow_control_enable, unsigned int color_enable, unsigned int cq_head, unsigned int cq_tail, unsigned int cq_tail_color, unsigned int interrupt_enable, diff --git a/drivers/net/ethernet/cisco/enic/vnic_devcmd.h b/drivers/net/ethernet/cisco/enic/vnic_devcmd.h index 3b6efa743dba..90ca06691ebd 100644 --- a/drivers/net/ethernet/cisco/enic/vnic_devcmd.h +++ b/drivers/net/ethernet/cisco/enic/vnic_devcmd.h @@ -455,8 +455,19 @@ enum vnic_devcmd_cmd { */ CMD_CQ_ENTRY_SIZE_SET = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 90), + /* + * Set queue pair type (admin or data) + * in: (u32) a0 = queue pair type (0 = admin, 1 = data) + * in: (u32) a1 = enable (1) / disable (0) + */ + CMD_QP_TYPE_SET = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 97), }; +#define QP_TYPE_ADMIN 0 +#define QP_TYPE_DATA 1 +#define QP_ENABLE 1 +#define QP_DISABLE 0 + /* CMD_ENABLE2 flags */ #define CMD_ENABLE2_STANDBY 0x0 #define CMD_ENABLE2_ACTIVE 0x1 From a0da4ea750a289ac27ba67d198315028b4da5cb4 Mon Sep 17 00:00:00 2001 From: Satish Kharat Date: Wed, 12 Aug 2026 05:48:03 -0700 Subject: [PATCH 1379/1433] enic: add admin RQ buffer management The admin receive queue needs pre-posted DMA buffers for incoming mailbox messages from VFs. Each buffer is a kzalloc'd region mapped for DMA (2048 bytes, sufficient for any MBOX message). Zeroing on allocation ensures that if a completion reports more bytes than hardware actually DMA-wrote, the parser reads zero padding rather than uninitialised heap contents. Add enic_admin_rq_fill(gfp) to post buffers at open time, and enic_admin_rq_drain() to unmap and free them at close time. Wire both into the admin channel open/close paths. The gfp_t parameter lets the caller pass the allocation context; both current callers -- channel open and the CQ-poll work handler that refills after draining (added in the next patch) -- run in process context and use GFP_KERNEL. Signed-off-by: Satish Kharat Link: https://patch.msgid.link/20260812-enic-sriov-v2-admin-channel-v2-v13-3-b3809e448aba@cisco.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cisco/enic/enic_admin.c | 66 +++++++++++++++++++- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.c b/drivers/net/ethernet/cisco/enic/enic_admin.c index 50b46b92c88f..edd0a2d3994b 100644 --- a/drivers/net/ethernet/cisco/enic/enic_admin.c +++ b/drivers/net/ethernet/cisco/enic/enic_admin.c @@ -3,6 +3,7 @@ #include #include +#include #include "vnic_dev.h" #include "vnic_wq.h" @@ -34,10 +35,63 @@ static void enic_admin_wq_buf_clean(struct vnic_wq *wq, } } -/* No-op: admin RQ buffer teardown is handled in enic_admin_channel_close */ static void enic_admin_rq_buf_clean(struct vnic_rq *rq, struct vnic_rq_buf *buf) { + struct enic *enic = vnic_dev_priv(rq->vdev); + + if (!buf->os_buf) + return; + + dma_unmap_single(&enic->pdev->dev, buf->dma_addr, buf->len, + DMA_FROM_DEVICE); + kfree(buf->os_buf); + buf->os_buf = NULL; +} + +static int enic_admin_rq_post_one(struct enic *enic, gfp_t gfp) +{ + struct vnic_rq *rq = &enic->admin_rq; + struct rq_enet_desc *desc; + dma_addr_t dma_addr; + void *buf; + + buf = kzalloc(ENIC_ADMIN_BUF_SIZE, gfp); + if (!buf) + return -ENOMEM; + + dma_addr = dma_map_single(&enic->pdev->dev, buf, ENIC_ADMIN_BUF_SIZE, + DMA_FROM_DEVICE); + if (dma_mapping_error(&enic->pdev->dev, dma_addr)) { + kfree(buf); + return -ENOMEM; + } + + desc = vnic_rq_next_desc(rq); + rq_enet_desc_enc(desc, (u64)dma_addr | VNIC_PADDR_TARGET, + RQ_ENET_TYPE_ONLY_SOP, ENIC_ADMIN_BUF_SIZE); + vnic_rq_post(rq, buf, 0, dma_addr, ENIC_ADMIN_BUF_SIZE, 0); + + return 0; +} + +static int enic_admin_rq_fill(struct enic *enic, gfp_t gfp) +{ + struct vnic_rq *rq = &enic->admin_rq; + int err; + + while (vnic_rq_desc_avail(rq) > 0) { + err = enic_admin_rq_post_one(enic, gfp); + if (err) + return err; + } + + return 0; +} + +static void enic_admin_rq_drain(struct enic *enic) +{ + vnic_rq_clean(&enic->admin_rq, enic_admin_rq_buf_clean); } static int enic_admin_qp_type_set(struct enic *enic, u32 enable) @@ -171,6 +225,13 @@ int enic_admin_channel_open(struct enic *enic) vnic_wq_enable(&enic->admin_wq); vnic_rq_enable(&enic->admin_rq); + err = enic_admin_rq_fill(enic, GFP_KERNEL); + if (err) { + netdev_err(enic->netdev, + "Failed to fill admin RQ buffers: %d\n", err); + goto disable_queues; + } + err = enic_admin_qp_type_set(enic, QP_ENABLE); if (err) { netdev_err(enic->netdev, @@ -188,6 +249,7 @@ int enic_admin_channel_open(struct enic *enic) netdev_warn(enic->netdev, "Failed to disable admin WQ\n"); if (vnic_rq_disable(&enic->admin_rq)) netdev_warn(enic->netdev, "Failed to disable admin RQ\n"); + enic_admin_rq_drain(enic); enic_admin_free_resources(enic); return err; } @@ -218,7 +280,7 @@ void enic_admin_channel_close(struct enic *enic) "Failed to disable admin RQ: %d\n", err); vnic_wq_clean(&enic->admin_wq, enic_admin_wq_buf_clean); - vnic_rq_clean(&enic->admin_rq, enic_admin_rq_buf_clean); + enic_admin_rq_drain(enic); vnic_cq_clean(&enic->admin_cq[0]); vnic_cq_clean(&enic->admin_cq[1]); enic_admin_free_resources(enic); From cb1dba54c7235c32218cd4b56adee806237c2d6c Mon Sep 17 00:00:00 2001 From: Satish Kharat Date: Wed, 12 Aug 2026 05:48:04 -0700 Subject: [PATCH 1380/1433] enic: add admin CQ service with MSI-X interrupt and workqueue polling Add completion queue (CQ) service for the admin channel work queue (WQ) and receive queue (RQ), driven by a dedicated MSI-X interrupt and a workqueue-based CQ poller. The admin WQ CQ service advances the completion ring and returns the number of descriptors consumed. The admin RQ CQ service does the same for receive completions and copies each received message out of its pre-posted DMA buffer into a dynamically allocated queue entry. The pending queue is bounded to ENIC_ADMIN_MSG_MAX (256) entries so a buggy or hostile VF cannot drive the host out of memory; messages are enqueued for deferred dispatch by a separate work_struct so the CQ poller stays short. When the MSI-X interrupt fires, the ISR schedules the CQ poll work. The work handler drains all pending completions, kicks message dispatch if work was done, and returns credits to unmask the interrupt. The admin vector is kept masked from the time the IRQ is requested until the rings are initialised and filled during channel open, so an early or spurious interrupt cannot run the poll handler against uninitialised rings. The poll handler snapshots the pending credit count before draining the CQ so it acknowledges exactly what the hardware reported for this interrupt; any credits that accrue during draining are serviced by the next interrupt. The credit write also sets the mask bit to re-arm the vector, and that unmask is applied independently of the credit count, so the vector is re-armed even when zero credits are returned -- which matters here because the admin channel is not re-polled like the NAPI data path. If an admin RQ buffer refill fails under transient memory pressure, reschedule the CQ poll work itself after a short delay to retry the refill and re-arm the RQ, so the admin channel cannot stall when the ring would otherwise be left empty with no completion to drive the next refill. The poll work is a delayed_work for this reason; routing the retry through it keeps the admin RQ ring owned by a single context so refills never run concurrently. Signed-off-by: Satish Kharat Link: https://patch.msgid.link/20260812-enic-sriov-v2-admin-channel-v2-v13-4-b3809e448aba@cisco.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cisco/enic/enic.h | 8 + drivers/net/ethernet/cisco/enic/enic_admin.c | 345 ++++++++++++++++++- drivers/net/ethernet/cisco/enic/enic_admin.h | 12 + 3 files changed, 361 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h index 398227448b37..0f08de228b36 100644 --- a/drivers/net/ethernet/cisco/enic/enic.h +++ b/drivers/net/ethernet/cisco/enic/enic.h @@ -301,6 +301,14 @@ struct enic { struct vnic_rq admin_rq; struct vnic_cq admin_cq[2]; struct vnic_intr admin_intr; + struct delayed_work admin_poll_work; + unsigned int admin_intr_index; + struct work_struct admin_msg_work; + spinlock_t admin_msg_lock; /* protects admin_msg_list */ + struct list_head admin_msg_list; + unsigned int admin_msg_count; /* current depth of admin_msg_list */ + void (*admin_rq_handler)(struct enic *enic, void *buf, + unsigned int len); }; static inline struct net_device *vnic_get_netdev(struct vnic_dev *vdev) diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.c b/drivers/net/ethernet/cisco/enic/enic_admin.c index edd0a2d3994b..65aec9651a6f 100644 --- a/drivers/net/ethernet/cisco/enic/enic_admin.c +++ b/drivers/net/ethernet/cisco/enic/enic_admin.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "vnic_dev.h" #include "vnic_wq.h" @@ -15,9 +16,15 @@ #include "enic.h" #include "enic_admin.h" #include "cq_desc.h" +#include "cq_enet_desc.h" #include "wq_enet_desc.h" #include "rq_enet_desc.h" +/* Retry interval for a failed admin RQ refill. Short so the control + * channel recovers quickly once memory is available again. + */ +#define ENIC_ADMIN_RQ_REFILL_RETRY_MS 100 + /* Clean up any admin WQ buffers still held by hardware at close time. * Normally buffers are freed inline after send completion, but a timed-out * send intentionally leaves the buffer live until the queue is stopped. @@ -94,6 +101,283 @@ static void enic_admin_rq_drain(struct enic *enic) vnic_rq_clean(&enic->admin_rq, enic_admin_rq_buf_clean); } +static unsigned int enic_admin_cq_color(void *cq_desc, unsigned int desc_size) +{ + u8 type_color = *((u8 *)cq_desc + desc_size - 1); + + return (type_color >> CQ_DESC_COLOR_SHIFT) & CQ_DESC_COLOR_MASK; +} + +unsigned int enic_admin_wq_cq_service(struct enic *enic) +{ + struct vnic_cq *cq = &enic->admin_cq[0]; + unsigned int work = 0; + void *desc; + + desc = vnic_cq_to_clean(cq); + while (enic_admin_cq_color(desc, cq->ring.desc_size) != + cq->last_color) { + vnic_cq_inc_to_clean(cq); + work++; + desc = vnic_cq_to_clean(cq); + } + + return work; +} + +/* Upper bound on pending admin messages. A buggy or hostile VF could flood + * the PF admin channel faster than admin_msg_work drains it; cap the backlog + * so a guest cannot drive the host out of memory. + */ +#define ENIC_ADMIN_MSG_MAX 256 + +static void enic_admin_msg_enqueue(struct enic *enic, void *buf, + unsigned int len) +{ + struct enic_admin_msg *msg; + + msg = kmalloc(struct_size(msg, data, len), GFP_KERNEL); + if (!msg) + return; + + msg->len = len; + memcpy(msg->data, buf, len); + + spin_lock(&enic->admin_msg_lock); + if (enic->admin_msg_count >= ENIC_ADMIN_MSG_MAX) { + spin_unlock(&enic->admin_msg_lock); + kfree(msg); + if (net_ratelimit()) + netdev_warn(enic->netdev, + "admin msg backlog full (%u); dropping\n", + ENIC_ADMIN_MSG_MAX); + return; + } + list_add_tail(&msg->list, &enic->admin_msg_list); + enic->admin_msg_count++; + spin_unlock(&enic->admin_msg_lock); +} + +unsigned int enic_admin_rq_cq_service(struct enic *enic) +{ + struct vnic_cq *cq = &enic->admin_cq[1]; + struct vnic_rq *rq = &enic->admin_rq; + struct cq_enet_rq_desc *rq_desc; + struct vnic_rq_buf *buf; + u16 bwf, bytes_written; + unsigned int work = 0; + void *desc; + + /* The admin RQ and its CQ form a single in-order channel: firmware + * posts exactly one CQE per consumed RQ descriptor, in submission + * order. Each CQE therefore pairs with rq->to_clean below without a + * completed_index cross-check, mirroring the in-order assumption of + * the main enic RX path. + */ + desc = vnic_cq_to_clean(cq); + while (enic_admin_cq_color(desc, cq->ring.desc_size) != + cq->last_color) { + /* Ensure DMA descriptor fields are read after + * the color/valid check. dma_rmb() is the + * correct barrier for DMA-written descriptors. + */ + dma_rmb(); + buf = rq->to_clean; + + /* Decode the actual number of bytes hardware wrote into + * the RX buffer. buf->len is the static allocation size + * (ENIC_ADMIN_BUF_SIZE); copying that many bytes would read + * beyond the actual DMA payload. bytes_written_flags is at + * the same offset in every cq_enet_rq_desc[_32|_64] variant. + */ + rq_desc = desc; + bwf = le16_to_cpu(rq_desc->bytes_written_flags); + bytes_written = bwf & CQ_ENET_RQ_DESC_BYTES_WRITTEN_MASK; + if (bytes_written > buf->len) + goto next_desc; + + dma_sync_single_for_cpu(&enic->pdev->dev, + buf->dma_addr, buf->len, + DMA_FROM_DEVICE); + + /* Drop on hardware error indications. Admin messages + * are internal to the VIC, not received over the wire. + * Firmware sets TRUNCATED when the message does not fit + * in the posted buffer, and FCS_OK is always set on + * healthy admin completions. + */ + if (bwf & CQ_ENET_RQ_DESC_FLAGS_TRUNCATED) { + netdev_warn_once(enic->netdev, + "admin RQ: truncated message dropped\n"); + goto next_desc; + } + if (!(rq_desc->flags & CQ_ENET_RQ_DESC_FLAGS_FCS_OK)) { + netdev_warn_once(enic->netdev, + "admin RQ: bad FCS, dropping message\n"); + goto next_desc; + } + + enic_admin_msg_enqueue(enic, buf->os_buf, bytes_written); + +next_desc: + enic_admin_rq_buf_clean(rq, rq->to_clean); + rq->to_clean = rq->to_clean->next; + rq->ring.desc_avail++; + + vnic_cq_inc_to_clean(cq); + work++; + desc = vnic_cq_to_clean(cq); + } + + if (enic_admin_rq_fill(enic, GFP_KERNEL)) { + /* Some RX buffers could not be reposted (transient memory + * pressure). If the ring is left empty the channel would stall + * with no completion to drive the next refill, so arm a delayed + * re-run of this poll work (the sole owner of the admin RQ ring) + * to repost buffers and re-arm the RQ from a single context. + */ + if (net_ratelimit()) + netdev_warn(enic->netdev, + "admin RQ refill failed; scheduling retry\n"); + schedule_delayed_work(&enic->admin_poll_work, + msecs_to_jiffies(ENIC_ADMIN_RQ_REFILL_RETRY_MS)); + } + + return work; +} + +static irqreturn_t enic_admin_isr_msix(int irq, void *data) +{ + struct enic *enic = data; + + schedule_delayed_work(&enic->admin_poll_work, 0); + + return IRQ_HANDLED; +} + +static void enic_admin_msg_work_handler(struct work_struct *work) +{ + struct enic *enic = container_of(work, struct enic, admin_msg_work); + struct enic_admin_msg *msg, *tmp; + LIST_HEAD(local_list); + + spin_lock_bh(&enic->admin_msg_lock); + list_splice_init(&enic->admin_msg_list, &local_list); + enic->admin_msg_count = 0; + spin_unlock_bh(&enic->admin_msg_lock); + + list_for_each_entry_safe(msg, tmp, &local_list, list) { + if (enic->admin_rq_handler) + enic->admin_rq_handler(enic, msg->data, msg->len); + list_del(&msg->list); + kfree(msg); + } +} + +static void enic_admin_poll_work_handler(struct work_struct *work) +{ + struct enic *enic = container_of(to_delayed_work(work), struct enic, + admin_poll_work); + unsigned int credits; + unsigned int rq_work; + + /* Snapshot the pending credit count before draining so we acknowledge + * exactly what the hardware reported for this interrupt. Credits that + * accrue while enic_admin_rq_cq_service() runs are left for the next + * interrupt, which is harmless on this low-rate control path. + */ + credits = vnic_intr_credits(&enic->admin_intr); + + rq_work = enic_admin_rq_cq_service(enic); + + if (rq_work > 0) + schedule_work(&enic->admin_msg_work); + + /* Acknowledge the snapshotted credits and unmask the vector. Unlike + * the NAPI data path, the admin channel is not re-polled, so the vector + * must be re-armed here to receive the next completion. The unmask is + * applied through the interrupt mask register independently of the + * credit count, so returning zero credits on a spurious wakeup still + * re-arms the vector. + */ + vnic_intr_return_credits(&enic->admin_intr, + credits, + 1 /* unmask */, 0); +} + +static int enic_admin_setup_intr(struct enic *enic) +{ + unsigned int intr_index = enic->intr_count; + int err; + + if (vnic_dev_get_intr_mode(enic->vdev) != VNIC_DEV_INTR_MODE_MSIX || + intr_index >= enic->intr_avail) + return -ENODEV; + + /* The admin INTR uses a slot in the same RES_TYPE_INTR_CTRL + * strided array of per-vector control blocks (mask, coalescing + * timer, credit return) that the data-path IRQs occupy in BAR0. + * vnic_intr_alloc() defaults to RES_TYPE_INTR_CTRL, which is what + * we want here. + */ + err = vnic_intr_alloc(enic->vdev, &enic->admin_intr, intr_index); + if (err) { + netdev_warn(enic->netdev, + "Failed to alloc admin intr at index %u: %d\n", + intr_index, err); + return err; + } + + enic->admin_intr_index = intr_index; + + /* Mask the admin vector before requesting the IRQ so an early or + * spurious completion cannot run the poll handler against the + * not-yet-initialised admin rings. enic_admin_channel_open() unmasks + * it only after the rings are initialised and filled. + */ + vnic_intr_mask(&enic->admin_intr); + + /* A V2 VF opens the admin channel during probe, before + * register_netdev() resolves the "eth%d" name template, so using + * netdev->name here would register the literal "eth%d-admin" in + * /proc/interrupts. Use the already-stable PCI device name instead. + */ + snprintf(enic->msix[intr_index].devname, + sizeof(enic->msix[intr_index].devname), + "%s-admin", pci_name(enic->pdev)); + enic->msix[intr_index].isr = enic_admin_isr_msix; + enic->msix[intr_index].devid = enic; + + err = request_irq(enic->msix_entry[intr_index].vector, + enic->msix[intr_index].isr, 0, + enic->msix[intr_index].devname, + enic->msix[intr_index].devid); + if (err) { + netdev_warn(enic->netdev, + "Failed to request admin MSI-X irq: %d\n", err); + vnic_intr_free(&enic->admin_intr); + return err; + } + + enic->msix[intr_index].requested = 1; + + netdev_dbg(enic->netdev, + "admin channel using MSI-X interrupt (index %u)\n", + intr_index); + + return 0; +} + +static void enic_admin_teardown_intr(struct enic *enic) +{ + unsigned int intr_index = enic->admin_intr_index; + + free_irq(enic->msix_entry[intr_index].vector, + enic->msix[intr_index].devid); + cancel_delayed_work_sync(&enic->admin_poll_work); + enic->msix[intr_index].requested = 0; +} + static int enic_admin_qp_type_set(struct enic *enic, u32 enable) { u64 a0 = QP_TYPE_ADMIN, a1 = enable; @@ -173,6 +457,7 @@ static int enic_admin_alloc_resources(struct enic *enic) static void enic_admin_free_resources(struct enic *enic) { + vnic_intr_free(&enic->admin_intr); vnic_cq_free(&enic->admin_cq[1]); vnic_cq_free(&enic->admin_cq[0]); vnic_rq_free(&enic->admin_rq); @@ -181,6 +466,8 @@ static void enic_admin_free_resources(struct enic *enic) static void enic_admin_init_resources(struct enic *enic) { + unsigned int intr_offset = enic->admin_intr_index; + vnic_wq_init(&enic->admin_wq, 0, 0, 0); /* cq_index, err_intr_enable, err_intr_offset */ vnic_rq_init(&enic->admin_rq, @@ -189,20 +476,35 @@ static void enic_admin_init_resources(struct enic *enic) VNIC_CQ_FC_DISABLE, VNIC_CQ_COLOR_ENABLE, 0, 0, 1, /* cq_head, cq_tail, cq_tail_color */ - VNIC_CQ_INTR_DISABLE, + VNIC_CQ_INTR_DISABLE, /* polled synchronously by mbox send */ VNIC_CQ_ENTRY_ENABLE, VNIC_CQ_MSG_DISABLE, - 0, /* interrupt_offset */ + intr_offset, 0 /* cq_message_addr */); vnic_cq_init(&enic->admin_cq[1], VNIC_CQ_FC_DISABLE, VNIC_CQ_COLOR_ENABLE, 0, 0, 1, /* cq_head, cq_tail, cq_tail_color */ - VNIC_CQ_INTR_DISABLE, + VNIC_CQ_INTR_ENABLE, VNIC_CQ_ENTRY_ENABLE, VNIC_CQ_MSG_DISABLE, - 0, /* interrupt_offset */ + intr_offset, 0 /* cq_message_addr */); + vnic_intr_init(&enic->admin_intr, + 0, 0, 1); /* coalescing_timer, coalescing_type, mask_on_assertion */ +} + +static void enic_admin_msg_drain(struct enic *enic) +{ + struct enic_admin_msg *msg, *tmp; + + spin_lock_bh(&enic->admin_msg_lock); + list_for_each_entry_safe(msg, tmp, &enic->admin_msg_list, list) { + list_del(&msg->list); + kfree(msg); + } + enic->admin_msg_count = 0; + spin_unlock_bh(&enic->admin_msg_lock); } int enic_admin_channel_open(struct enic *enic) @@ -220,6 +522,19 @@ int enic_admin_channel_open(struct enic *enic) return err; } + spin_lock_init(&enic->admin_msg_lock); + INIT_LIST_HEAD(&enic->admin_msg_list); + INIT_WORK(&enic->admin_msg_work, enic_admin_msg_work_handler); + INIT_DELAYED_WORK(&enic->admin_poll_work, enic_admin_poll_work_handler); + + err = enic_admin_setup_intr(enic); + if (err) { + netdev_err(enic->netdev, + "Admin channel requires MSI-X, SR-IOV unavailable: %d\n", + err); + goto free_resources; + } + enic_admin_init_resources(enic); vnic_wq_enable(&enic->admin_wq); @@ -239,17 +554,31 @@ int enic_admin_channel_open(struct enic *enic) goto disable_queues; } + vnic_intr_unmask(&enic->admin_intr); + + netdev_dbg(enic->netdev, + "admin channel open: intr=%u wq_avail=%u rq_avail=%u cq0_color=%u cq1_color=%u\n", + enic->admin_intr_index, + vnic_wq_desc_avail(&enic->admin_wq), + vnic_rq_desc_avail(&enic->admin_rq), + enic->admin_cq[0].last_color, + enic->admin_cq[1].last_color); + enic->admin_chan_up = true; return 0; disable_queues: + enic_admin_teardown_intr(enic); enic_admin_qp_type_set(enic, QP_DISABLE); if (vnic_wq_disable(&enic->admin_wq)) netdev_warn(enic->netdev, "Failed to disable admin WQ\n"); if (vnic_rq_disable(&enic->admin_rq)) netdev_warn(enic->netdev, "Failed to disable admin RQ\n"); + cancel_work_sync(&enic->admin_msg_work); + enic_admin_msg_drain(enic); enic_admin_rq_drain(enic); +free_resources: enic_admin_free_resources(enic); return err; } @@ -268,6 +597,13 @@ void enic_admin_channel_close(struct enic *enic) if (!enic->admin_chan_up) return; + netdev_dbg(enic->netdev, "admin channel close\n"); + + vnic_intr_mask(&enic->admin_intr); + enic_admin_teardown_intr(enic); + cancel_work_sync(&enic->admin_msg_work); + enic_admin_msg_drain(enic); + enic_admin_qp_type_set(enic, QP_DISABLE); err = vnic_wq_disable(&enic->admin_wq); @@ -283,6 +619,7 @@ void enic_admin_channel_close(struct enic *enic) enic_admin_rq_drain(enic); vnic_cq_clean(&enic->admin_cq[0]); vnic_cq_clean(&enic->admin_cq[1]); + vnic_intr_clean(&enic->admin_intr); enic_admin_free_resources(enic); enic->admin_chan_up = false; diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.h b/drivers/net/ethernet/cisco/enic/enic_admin.h index 569aadeb9312..62c80220b0ca 100644 --- a/drivers/net/ethernet/cisco/enic/enic_admin.h +++ b/drivers/net/ethernet/cisco/enic/enic_admin.h @@ -9,7 +9,19 @@ struct enic; +/* Wrapper for received admin messages queued for deferred processing. + * The admin CQ poll work handler enqueues these; a separate work handler + * processes them where sleeping (mutex, GFP_KERNEL) is safe. + */ +struct enic_admin_msg { + struct list_head list; + unsigned int len; + u8 data[] __aligned(8); +}; + int enic_admin_channel_open(struct enic *enic); void enic_admin_channel_close(struct enic *enic); +unsigned int enic_admin_wq_cq_service(struct enic *enic); +unsigned int enic_admin_rq_cq_service(struct enic *enic); #endif /* _ENIC_ADMIN_H_ */ From 1584793311ce059e8bb848db0698738ad2caf73e Mon Sep 17 00:00:00 2001 From: Satish Kharat Date: Wed, 12 Aug 2026 05:48:05 -0700 Subject: [PATCH 1381/1433] enic: define MBOX message types and header structures Define the mailbox protocol structures for PF-VF communication: message header, generic reply, and per-message-type payloads for capability negotiation, VF registration/unregistration, and link state notification/acknowledgment. Include linux/types.h and linux/bits.h for __le16/__le32/__le64 and BIT() used in the header. Message types use an even=request / odd=reply convention. The header carries source and destination VNIC IDs, a per-channel message sequence number, and the total message length. Signed-off-by: Satish Kharat Link: https://patch.msgid.link/20260812-enic-sriov-v2-admin-channel-v2-v13-5-b3809e448aba@cisco.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cisco/enic/enic_mbox.h | 83 +++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 drivers/net/ethernet/cisco/enic/enic_mbox.h diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.h b/drivers/net/ethernet/cisco/enic/enic_mbox.h new file mode 100644 index 000000000000..a52f1d25cb21 --- /dev/null +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.h @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright 2025 Cisco Systems, Inc. All rights reserved. */ + +#ifndef _ENIC_MBOX_H_ +#define _ENIC_MBOX_H_ + +#include +#include + +/* + * Mailbox protocol for PF-VF communication over the admin channel. + * + * Even numbers are requests, odd numbers are replies/acks. + * The prefix indicates the initiator: VF_ = VF-initiated, PF_ = PF-initiated. + */ +enum enic_mbox_msg_type { + ENIC_MBOX_VF_CAPABILITY_REQUEST = 0, + ENIC_MBOX_VF_CAPABILITY_REPLY = 1, + ENIC_MBOX_VF_REGISTER_REQUEST = 2, + ENIC_MBOX_VF_REGISTER_REPLY = 3, + ENIC_MBOX_VF_UNREGISTER_REQUEST = 4, + ENIC_MBOX_VF_UNREGISTER_REPLY = 5, + ENIC_MBOX_PF_LINK_STATE_NOTIF = 6, + ENIC_MBOX_PF_LINK_STATE_ACK = 7, + ENIC_MBOX_MAX +}; + +struct enic_mbox_hdr { + __le16 src_vnic_id; + __le16 dst_vnic_id; + u8 msg_type; + u8 flags; + __le16 msg_len; + __le64 msg_num; +}; + +struct enic_mbox_generic_reply { + __le16 ret_major; + __le16 ret_minor; +}; + +#define ENIC_MBOX_ERR_GENERIC BIT(0) +#define ENIC_MBOX_ERR_VF_NOT_REGISTERED BIT(1) +#define ENIC_MBOX_ERR_MSG_NOT_SUPPORTED BIT(2) + +/* ENIC_MBOX_VF_CAPABILITY_REQUEST / _REPLY */ +#define ENIC_MBOX_CAP_VERSION_0 0 +#define ENIC_MBOX_CAP_VERSION_1 1 + +struct enic_mbox_vf_capability_msg { + __le32 version; + __le32 reserved[32]; +}; + +/* The embedded enic_mbox_generic_reply has 2-byte alignment, but the + * __le32 members give this struct 4-byte natural alignment. Receive + * buffers come from kmalloc (>= 8-byte aligned), so there is no + * misaligned access risk when casting from the receive buffer. + */ +struct enic_mbox_vf_capability_reply_msg { + struct enic_mbox_generic_reply reply; + __le32 version; + __le32 reserved[32]; +}; + +/* ENIC_MBOX_VF_REGISTER / _UNREGISTER */ +struct enic_mbox_vf_register_reply_msg { + struct enic_mbox_generic_reply reply; +}; + +/* ENIC_MBOX_PF_LINK_STATE_NOTIF / _ACK */ +#define ENIC_MBOX_LINK_STATE_DISABLE 0 +#define ENIC_MBOX_LINK_STATE_ENABLE 1 + +struct enic_mbox_pf_link_state_notif_msg { + __le32 link_state; +}; + +struct enic_mbox_pf_link_state_ack_msg { + struct enic_mbox_generic_reply ack; +}; + +#endif /* _ENIC_MBOX_H_ */ From 1f0c856b5963379c57256ec0c99c679d7ca89702 Mon Sep 17 00:00:00 2001 From: Satish Kharat Date: Wed, 12 Aug 2026 05:48:06 -0700 Subject: [PATCH 1382/1433] enic: add MBOX core send and receive for admin channel Implement the mailbox protocol engine used for PF-VF communication over the admin channel. The send path (enic_mbox_send_msg) builds a message with a common header, DMA-maps it, posts a single WQ descriptor with the destination vnic ID encoded in the VLAN tag field, and polls the WQ CQ for completion. The total message length is computed as a size_t, and the payload is bounded before the send lock is taken: a payload larger than the admin buffer minus the header is rejected with -EINVAL. This keeps the length sum from wrapping and stops the on-the-wire u16 length from overflowing or the DMA buffer from being overrun. MBOX sends are gated by enic->mbox_send_disabled: enic_mbox_send_msg() returns early while it is set. It is set at the very start of both enic_admin_channel_open() and enic_admin_channel_close(), and is cleared in enic_admin_channel_open() only once the admin WQ/RQ/CQ and interrupt are fully allocated, programmed and enabled. Keeping it set for the whole open sequence means an early failure that returns before the channel is ready (as well as a not-yet-ready or torn-down channel) leaves sends disabled, so a concurrent sender can never race an MBOX send against a half-open or freed admin_wq. The receive path (enic_mbox_recv_handler) is installed as the admin RQ callback and validates incoming message headers. PF/VF-specific dispatch will be added in subsequent commits. Signed-off-by: Satish Kharat Link: https://patch.msgid.link/20260812-enic-sriov-v2-admin-channel-v2-v13-6-b3809e448aba@cisco.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cisco/enic/Makefile | 2 +- drivers/net/ethernet/cisco/enic/enic.h | 6 + drivers/net/ethernet/cisco/enic/enic_admin.c | 42 ++++- drivers/net/ethernet/cisco/enic/enic_mbox.c | 177 +++++++++++++++++++ drivers/net/ethernet/cisco/enic/enic_mbox.h | 8 + 5 files changed, 232 insertions(+), 3 deletions(-) create mode 100644 drivers/net/ethernet/cisco/enic/enic_mbox.c diff --git a/drivers/net/ethernet/cisco/enic/Makefile b/drivers/net/ethernet/cisco/enic/Makefile index 7ae72fefc99a..e38aaf34c148 100644 --- a/drivers/net/ethernet/cisco/enic/Makefile +++ b/drivers/net/ethernet/cisco/enic/Makefile @@ -4,5 +4,5 @@ obj-$(CONFIG_ENIC) := enic.o enic-y := enic_main.o vnic_cq.o vnic_intr.o vnic_wq.o \ enic_res.o enic_dev.o enic_pp.o vnic_dev.o vnic_rq.o vnic_vic.o \ enic_ethtool.o enic_api.o enic_clsf.o enic_rq.o enic_wq.o \ - enic_admin.o + enic_admin.o enic_mbox.o diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h index 0f08de228b36..525a67414e63 100644 --- a/drivers/net/ethernet/cisco/enic/enic.h +++ b/drivers/net/ethernet/cisco/enic/enic.h @@ -297,6 +297,8 @@ struct enic { * left the resources freed. */ bool admin_chan_up; + /* set on send timeout; cleared on channel re-open */ + bool mbox_send_disabled; struct vnic_wq admin_wq; struct vnic_rq admin_rq; struct vnic_cq admin_cq[2]; @@ -309,6 +311,10 @@ struct enic { unsigned int admin_msg_count; /* current depth of admin_msg_list */ void (*admin_rq_handler)(struct enic *enic, void *buf, unsigned int len); + + /* MBOX protocol state — mbox_lock serializes admin WQ sends */ + struct mutex mbox_lock; + u64 mbox_msg_num; }; static inline struct net_device *vnic_get_netdev(struct vnic_dev *vdev) diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.c b/drivers/net/ethernet/cisco/enic/enic_admin.c index 65aec9651a6f..f4a409fec983 100644 --- a/drivers/net/ethernet/cisco/enic/enic_admin.c +++ b/drivers/net/ethernet/cisco/enic/enic_admin.c @@ -19,6 +19,7 @@ #include "cq_enet_desc.h" #include "wq_enet_desc.h" #include "rq_enet_desc.h" +#include "enic_mbox.h" /* Retry interval for a failed admin RQ refill. Short so the control * channel recovers quickly once memory is available again. @@ -217,7 +218,26 @@ unsigned int enic_admin_rq_cq_service(struct enic *enic) goto next_desc; } - enic_admin_msg_enqueue(enic, buf->os_buf, bytes_written); + if (enic->admin_rq_handler) { + u16 sender_vlan; + + /* Firmware sets the CQ VLAN field to identify the + * sender: 0 = PF, 1-based = VF index. Overwrite + * the untrusted src_vnic_id in the MBOX header with + * the hardware-verified value. + */ + sender_vlan = le16_to_cpu(rq_desc->vlan); + if (bytes_written >= sizeof(struct enic_mbox_hdr)) { + struct enic_mbox_hdr *hdr = buf->os_buf; + + hdr->src_vnic_id = (sender_vlan == 0) ? + cpu_to_le16(ENIC_MBOX_DST_PF) : + cpu_to_le16(sender_vlan - 1); + } + + enic_admin_msg_enqueue(enic, buf->os_buf, + bytes_written); + } next_desc: enic_admin_rq_buf_clean(rq, rq->to_clean); @@ -490,8 +510,9 @@ static void enic_admin_init_resources(struct enic *enic) VNIC_CQ_MSG_DISABLE, intr_offset, 0 /* cq_message_addr */); + /* coalescing_timer, coalescing_type, mask_on_assertion */ vnic_intr_init(&enic->admin_intr, - 0, 0, 1); /* coalescing_timer, coalescing_type, mask_on_assertion */ + 0, 0, 1); } static void enic_admin_msg_drain(struct enic *enic) @@ -514,6 +535,13 @@ int enic_admin_channel_open(struct enic *enic) if (!enic->has_admin_channel) return -ENODEV; + /* Keep MBOX sends disabled for the entire open sequence. It is + * cleared only after every resource is allocated and enabled below, + * so any early error return here leaves sends disabled and a + * concurrent sender cannot touch a half-open or freed admin_wq. + */ + WRITE_ONCE(enic->mbox_send_disabled, true); + err = enic_admin_alloc_resources(enic); if (err) { netdev_err(enic->netdev, @@ -556,6 +584,14 @@ int enic_admin_channel_open(struct enic *enic) vnic_intr_unmask(&enic->admin_intr); + /* Only now that the admin WQ/RQ/CQ and interrupt are fully allocated, + * programmed and enabled is it safe to allow MBOX sends. Clearing this + * earlier opened a window where a concurrent sender (e.g. link-notify + * work scheduled by a post-reset link-up) could call enic_mbox_send_msg() + * against a not-yet-allocated admin_wq and crash. + */ + WRITE_ONCE(enic->mbox_send_disabled, false); + netdev_dbg(enic->netdev, "admin channel open: intr=%u wq_avail=%u rq_avail=%u cq0_color=%u cq1_color=%u\n", enic->admin_intr_index, @@ -597,6 +633,8 @@ void enic_admin_channel_close(struct enic *enic) if (!enic->admin_chan_up) return; + WRITE_ONCE(enic->mbox_send_disabled, true); + netdev_dbg(enic->netdev, "admin channel close\n"); vnic_intr_mask(&enic->admin_intr); diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethernet/cisco/enic/enic_mbox.c new file mode 100644 index 000000000000..c1680f77abac --- /dev/null +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright 2025 Cisco Systems, Inc. All rights reserved. + +#include +#include +#include +#include + +#include "vnic_dev.h" +#include "vnic_wq.h" +#include "vnic_cq.h" +#include "enic.h" +#include "enic_admin.h" +#include "enic_mbox.h" +#include "wq_enet_desc.h" + +#define ENIC_MBOX_POLL_TIMEOUT_US 5000000 +#define ENIC_MBOX_POLL_INTERVAL_US 100 + +static void enic_mbox_fill_hdr(struct enic *enic, struct enic_mbox_hdr *hdr, + u8 msg_type, u16 dst_vnic_id, u16 msg_len) +{ + memset(hdr, 0, sizeof(*hdr)); + hdr->dst_vnic_id = cpu_to_le16(dst_vnic_id); + hdr->msg_type = msg_type; + hdr->msg_len = cpu_to_le16(msg_len); + hdr->msg_num = cpu_to_le64(++enic->mbox_msg_num); +} + +int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id, + void *payload, u16 payload_len) +{ + size_t total_len = sizeof(struct enic_mbox_hdr) + payload_len; + struct vnic_wq *wq = &enic->admin_wq; + struct wq_enet_desc *desc; + unsigned long timeout; + dma_addr_t dma_addr; + u16 vlan_tag; + void *buf; + int err; + + /* Reject payloads that cannot fit in a single admin buffer. Checked + * before taking mbox_lock; total_len is computed as size_t so the + * sizeof() + payload_len sum cannot wrap. + */ + if (payload_len > ENIC_ADMIN_BUF_SIZE - sizeof(struct enic_mbox_hdr)) + return -EINVAL; + + /* Serialize MBOX sends. The admin channel is a low-frequency + * control path; holding the mutex across the poll is acceptable. + */ + mutex_lock(&enic->mbox_lock); + + if (!enic->has_admin_channel || READ_ONCE(enic->mbox_send_disabled)) { + err = -ENODEV; + goto unlock; + } + + if (vnic_wq_desc_avail(wq) == 0) { + err = -ENOSPC; + goto unlock; + } + + buf = kmalloc(total_len, GFP_KERNEL); + if (!buf) { + err = -ENOMEM; + goto unlock; + } + + enic_mbox_fill_hdr(enic, buf, msg_type, dst_vnic_id, total_len); + if (payload_len) { + void *dst = buf + sizeof(struct enic_mbox_hdr); + + memcpy(dst, payload, payload_len); + } + + dma_addr = dma_map_single(&enic->pdev->dev, buf, total_len, + DMA_TO_DEVICE); + if (dma_mapping_error(&enic->pdev->dev, dma_addr)) { + kfree(buf); + err = -ENOMEM; + goto unlock; + } + + /* Firmware uses vlan field for routing: 0 = PF, 1-based = VF index */ + if (dst_vnic_id == ENIC_MBOX_DST_PF) + vlan_tag = 0; + else + vlan_tag = dst_vnic_id + 1; + + desc = vnic_wq_next_desc(wq); + wq_enet_desc_enc(desc, (u64)dma_addr | VNIC_PADDR_TARGET, + total_len, + 0, 0, 0, /* mss, hdr_len, offload_mode */ + 1, 1, /* eop, cq_entry */ + 0, /* fcoe_encap */ + 1, vlan_tag, /* vlan_tag_insert, vlan_tag */ + 0); /* loopback */ + vnic_wq_post(wq, buf, dma_addr, total_len, + 1, 1, /* sop, eop */ + 1, 1, /* desc_skip_cnt, cq_entry */ + 0, 0); /* compressed_send, wrid */ + vnic_wq_doorbell(wq); + + timeout = jiffies + usecs_to_jiffies(ENIC_MBOX_POLL_TIMEOUT_US); + err = -ETIMEDOUT; + while (time_before(jiffies, timeout)) { + if (enic_admin_wq_cq_service(enic)) { + err = 0; + break; + } + usleep_range(ENIC_MBOX_POLL_INTERVAL_US, + ENIC_MBOX_POLL_INTERVAL_US + 50); + } + /* Final check in case completion arrived during the last sleep */ + if (err && enic_admin_wq_cq_service(enic)) + err = 0; + + if (!err) { + wq->to_clean = wq->to_clean->next; + wq->ring.desc_avail++; + dma_unmap_single(&enic->pdev->dev, dma_addr, total_len, + DMA_TO_DEVICE); + kfree(buf); + } else { + netdev_err(enic->netdev, + "MBOX send timed out (type %u dst %u), disabling channel\n", + msg_type, dst_vnic_id); + /* + * The WQ descriptor is still live in hardware. Do not unmap + * or free the buffer: the device may still DMA from dma_addr. + * Mark the channel unusable so no further sends are attempted. + */ + WRITE_ONCE(enic->mbox_send_disabled, true); + } + + netdev_dbg(enic->netdev, + "MBOX send msg_type %u dst %u vlan %u err %d\n", + msg_type, dst_vnic_id, vlan_tag, err); +unlock: + mutex_unlock(&enic->mbox_lock); + return err; +} + +static void enic_mbox_recv_handler(struct enic *enic, void *buf, + unsigned int len) +{ + struct enic_mbox_hdr *hdr = buf; + + if (len < sizeof(*hdr)) { + if (net_ratelimit()) + netdev_warn(enic->netdev, + "MBOX: truncated message (len %u < %zu)\n", + len, sizeof(*hdr)); + return; + } + + if (hdr->msg_type >= ENIC_MBOX_MAX) { + if (net_ratelimit()) + netdev_warn(enic->netdev, + "MBOX: unknown msg type %u\n", + hdr->msg_type); + return; + } + + netdev_dbg(enic->netdev, + "MBOX recv: type %u from vnic %u len %u\n", + hdr->msg_type, le16_to_cpu(hdr->src_vnic_id), + le16_to_cpu(hdr->msg_len)); +} + +void enic_mbox_init(struct enic *enic) +{ + enic->mbox_msg_num = 0; + mutex_init(&enic->mbox_lock); + enic->admin_rq_handler = enic_mbox_recv_handler; +} diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.h b/drivers/net/ethernet/cisco/enic/enic_mbox.h index a52f1d25cb21..73fd7f783ee2 100644 --- a/drivers/net/ethernet/cisco/enic/enic_mbox.h +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.h @@ -80,4 +80,12 @@ struct enic_mbox_pf_link_state_ack_msg { struct enic_mbox_generic_reply ack; }; +#define ENIC_MBOX_DST_PF 0xFFFF + +struct enic; + +void enic_mbox_init(struct enic *enic); +int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id, + void *payload, u16 payload_len); + #endif /* _ENIC_MBOX_H_ */ From 06bdfb16216bd5a4cbc5aced1306d32c7e75bad8 Mon Sep 17 00:00:00 2001 From: Satish Kharat Date: Wed, 12 Aug 2026 05:48:07 -0700 Subject: [PATCH 1383/1433] enic: add MBOX PF handlers for VF register and capability Implement PF-side mailbox message processing for SR-IOV V2 admin channel communication. When the PF receives messages from VFs, the dispatch routes them to type-specific handlers: - VF_CAPABILITY_REQUEST: reply with protocol version 1 - VF_REGISTER_REQUEST: send the register reply, mark the VF registered on success, then send PF_LINK_STATE_NOTIF reflecting the PF's current carrier state - VF_UNREGISTER_REQUEST: mark VF unregistered, send reply - PF_LINK_STATE_ACK: log errors from VF acknowledgment Per-VF state (struct enic_vf_state) is tracked via enic->vf_state which will be allocated when SRIOV V2 is enabled. Remove the CONFIG_PCI_IOV guard from num_vfs in struct enic. The PF handlers reference enic->num_vfs for VF ID bounds checking in enic_mbox.c, which is compiled unconditionally. The field must be visible regardless of CONFIG_PCI_IOV to avoid build failures. Add enic_mbox_send_link_state() helper for PF-initiated link state notifications. Signed-off-by: Satish Kharat Link: https://patch.msgid.link/20260812-enic-sriov-v2-admin-channel-v2-v13-7-b3809e448aba@cisco.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cisco/enic/enic.h | 7 +- drivers/net/ethernet/cisco/enic/enic_mbox.c | 190 +++++++++++++++++++- drivers/net/ethernet/cisco/enic/enic_mbox.h | 1 + 3 files changed, 194 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h index 525a67414e63..6261f3e8e4f8 100644 --- a/drivers/net/ethernet/cisco/enic/enic.h +++ b/drivers/net/ethernet/cisco/enic/enic.h @@ -256,9 +256,7 @@ struct enic { struct enic_rx_coal rx_coalesce_setting; u32 rx_coalesce_usecs; u32 tx_coalesce_usecs; -#ifdef CONFIG_PCI_IOV u16 num_vfs; -#endif enum enic_vf_type vf_type; unsigned int enable_count; spinlock_t enic_api_lock; @@ -315,6 +313,11 @@ struct enic { /* MBOX protocol state — mbox_lock serializes admin WQ sends */ struct mutex mbox_lock; u64 mbox_msg_num; + + /* PF: per-VF MBOX state, allocated when SRIOV V2 is enabled */ + struct enic_vf_state { + bool registered; + } *vf_state; }; static inline struct net_device *vnic_get_netdev(struct vnic_dev *vdev) diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethernet/cisco/enic/enic_mbox.c index c1680f77abac..d1b13737e77d 100644 --- a/drivers/net/ethernet/cisco/enic/enic_mbox.c +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c @@ -142,10 +142,183 @@ int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id, return err; } +int enic_mbox_send_link_state(struct enic *enic, u16 vf_id, u32 link_state) +{ + struct enic_mbox_pf_link_state_notif_msg notif = {}; + + if (!enic->vf_state || vf_id >= enic->num_vfs || + !enic->vf_state[vf_id].registered) { + netdev_dbg(enic->netdev, + "MBOX: skip link state to unregistered VF %u\n", + vf_id); + return 0; + } + + notif.link_state = cpu_to_le32(link_state); + return enic_mbox_send_msg(enic, ENIC_MBOX_PF_LINK_STATE_NOTIF, vf_id, + ¬if, sizeof(notif)); +} + +static int enic_mbox_pf_handle_capability(struct enic *enic, void *msg, + u16 vf_id, u64 msg_num) +{ + struct enic_mbox_vf_capability_reply_msg reply = {}; + + reply.reply.ret_major = cpu_to_le16(0); + reply.version = cpu_to_le32(ENIC_MBOX_CAP_VERSION_1); + + return enic_mbox_send_msg(enic, ENIC_MBOX_VF_CAPABILITY_REPLY, vf_id, + &reply, sizeof(reply)); +} + +static int enic_mbox_pf_handle_register(struct enic *enic, void *msg, + u16 vf_id, u64 msg_num) +{ + struct enic_mbox_vf_register_reply_msg reply = {}; + u32 link_state; + int err; + + if (!enic->vf_state || vf_id >= enic->num_vfs) { + if (net_ratelimit()) + netdev_warn(enic->netdev, + "MBOX: register from invalid VF %u\n", + vf_id); + return -EINVAL; + } + + /* VF re-registering (e.g. guest reboot without clean unregister): + * mark the previous registration inactive before accepting the new one. + */ + if (enic->vf_state[vf_id].registered) { + netdev_dbg(enic->netdev, + "MBOX: VF %u re-register, cleaning previous state\n", + vf_id); + enic->vf_state[vf_id].registered = false; + } + + reply.reply.ret_major = cpu_to_le16(0); + err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_REGISTER_REPLY, vf_id, + &reply, sizeof(reply)); + if (err) + return err; + + enic->vf_state[vf_id].registered = true; + if (net_ratelimit()) + netdev_info(enic->netdev, "VF %u registered via MBOX\n", vf_id); + + link_state = netif_carrier_ok(enic->netdev) ? + ENIC_MBOX_LINK_STATE_ENABLE : + ENIC_MBOX_LINK_STATE_DISABLE; + err = enic_mbox_send_link_state(enic, vf_id, link_state); + if (err && net_ratelimit()) + netdev_warn(enic->netdev, + "VF %u: failed to send initial link state: %d\n", + vf_id, err); + /* Registration succeeded; initial link state notification attempted + * above. Subsequent link state changes are sent from the PF + * when enic_link_check() detects carrier changes. + */ + return 0; +} + +static int enic_mbox_pf_handle_unregister(struct enic *enic, void *msg, + u16 vf_id, u64 msg_num) +{ + struct enic_mbox_vf_register_reply_msg reply = {}; + int err; + + if (!enic->vf_state || vf_id >= enic->num_vfs) { + if (net_ratelimit()) + netdev_warn(enic->netdev, + "MBOX: unregister from invalid VF %u\n", + vf_id); + return -EINVAL; + } + + /* VF is unloading; clear local state regardless of whether + * the reply is successfully delivered to avoid the PF treating + * a dead VF as still registered. + */ + enic->vf_state[vf_id].registered = false; + + reply.reply.ret_major = cpu_to_le16(0); + err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_UNREGISTER_REPLY, vf_id, + &reply, sizeof(reply)); + + if (net_ratelimit()) + netdev_info(enic->netdev, + "VF %u unregistered via MBOX\n", vf_id); + + return err; +} + +static void enic_mbox_pf_process_msg(struct enic *enic, + struct enic_mbox_hdr *hdr, void *payload) +{ + u16 vf_id = le16_to_cpu(hdr->src_vnic_id); + u16 msg_len = le16_to_cpu(hdr->msg_len); + int err = 0; + + if (!enic->vf_state) { + netdev_dbg(enic->netdev, + "MBOX: PF received msg but SRIOV not active\n"); + return; + } + + if (vf_id >= enic->num_vfs) { + if (net_ratelimit()) + netdev_warn(enic->netdev, + "MBOX: PF received msg from invalid VF %u\n", + vf_id); + return; + } + + switch (hdr->msg_type) { + case ENIC_MBOX_VF_CAPABILITY_REQUEST: + err = enic_mbox_pf_handle_capability(enic, payload, vf_id, + le64_to_cpu(hdr->msg_num)); + break; + case ENIC_MBOX_VF_REGISTER_REQUEST: + err = enic_mbox_pf_handle_register(enic, payload, vf_id, + le64_to_cpu(hdr->msg_num)); + break; + case ENIC_MBOX_VF_UNREGISTER_REQUEST: + err = enic_mbox_pf_handle_unregister(enic, payload, vf_id, + le64_to_cpu(hdr->msg_num)); + break; + case ENIC_MBOX_PF_LINK_STATE_ACK: { + struct enic_mbox_pf_link_state_ack_msg *ack = payload; + + if (msg_len < sizeof(*hdr) + sizeof(*ack)) + break; + if (le16_to_cpu(ack->ack.ret_major) && net_ratelimit()) + netdev_warn(enic->netdev, + "MBOX: VF %u link state ACK error %u/%u\n", + vf_id, + le16_to_cpu(ack->ack.ret_major), + le16_to_cpu(ack->ack.ret_minor)); + break; + } + default: + netdev_dbg(enic->netdev, + "MBOX: PF unhandled msg type %u from VF %u\n", + hdr->msg_type, vf_id); + err = -EOPNOTSUPP; + break; + } + + if (err && net_ratelimit()) + netdev_warn(enic->netdev, + "MBOX: PF handler for msg type %u from VF %u failed: %d\n", + hdr->msg_type, vf_id, err); +} + static void enic_mbox_recv_handler(struct enic *enic, void *buf, unsigned int len) { struct enic_mbox_hdr *hdr = buf; + void *payload; + u16 msg_len; if (len < sizeof(*hdr)) { if (net_ratelimit()) @@ -163,10 +336,23 @@ static void enic_mbox_recv_handler(struct enic *enic, void *buf, return; } + msg_len = le16_to_cpu(hdr->msg_len); + if (msg_len < sizeof(*hdr) || msg_len > len) { + if (net_ratelimit()) + netdev_warn(enic->netdev, + "MBOX: invalid msg_len %u (buf len %u)\n", + msg_len, len); + return; + } + netdev_dbg(enic->netdev, "MBOX recv: type %u from vnic %u len %u\n", - hdr->msg_type, le16_to_cpu(hdr->src_vnic_id), - le16_to_cpu(hdr->msg_len)); + hdr->msg_type, le16_to_cpu(hdr->src_vnic_id), msg_len); + + payload = buf + sizeof(*hdr); + + if (enic->vf_state) + enic_mbox_pf_process_msg(enic, hdr, payload); } void enic_mbox_init(struct enic *enic) diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.h b/drivers/net/ethernet/cisco/enic/enic_mbox.h index 73fd7f783ee2..f1de67db1273 100644 --- a/drivers/net/ethernet/cisco/enic/enic_mbox.h +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.h @@ -87,5 +87,6 @@ struct enic; void enic_mbox_init(struct enic *enic); int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id, void *payload, u16 payload_len); +int enic_mbox_send_link_state(struct enic *enic, u16 vf_id, u32 link_state); #endif /* _ENIC_MBOX_H_ */ From 72b65c94058e4ad9a03885d91c7a5f8a5c1d39be Mon Sep 17 00:00:00 2001 From: Satish Kharat Date: Wed, 12 Aug 2026 05:48:08 -0700 Subject: [PATCH 1384/1433] enic: add MBOX VF handlers for capability, register and link state Implement VF-side mailbox message processing for SR-IOV V2 admin channel communication. VF receive handlers: - VF_CAPABILITY_REPLY: store PF protocol version, signal completion - VF_REGISTER_REPLY: mark VF as registered, signal completion - VF_UNREGISTER_REPLY: mark VF as unregistered, signal completion - PF_LINK_STATE_NOTIF: update carrier state via netif_carrier_on/off, send ACK back to PF VF initiation functions for the probe-time handshake: - enic_mbox_vf_capability_check: send capability request, wait for PF reply via completion - enic_mbox_vf_register: send register request, wait for PF confirmation via completion - enic_mbox_vf_unregister: send unregister request, wait for PF confirmation The wait helper (enic_mbox_wait_reply) uses wait_for_completion_timeout, signaled when the admin ISR and CQ-poll/dispatch workqueue pipeline delivers the reply message. mbox_expected_reply is written by the request thread and read by the admin CQ poll/dispatch context that runs the receive handlers; annotate those accesses with READ_ONCE()/WRITE_ONCE() under the single-outstanding-reply invariant. Signed-off-by: Satish Kharat Link: https://patch.msgid.link/20260812-enic-sriov-v2-admin-channel-v2-v13-8-b3809e448aba@cisco.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cisco/enic/enic.h | 12 + drivers/net/ethernet/cisco/enic/enic_mbox.c | 277 +++++++++++++++++++- drivers/net/ethernet/cisco/enic/enic_mbox.h | 3 + 3 files changed, 291 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h index 6261f3e8e4f8..db4d52a4158f 100644 --- a/drivers/net/ethernet/cisco/enic/enic.h +++ b/drivers/net/ethernet/cisco/enic/enic.h @@ -258,6 +258,8 @@ struct enic { u32 tx_coalesce_usecs; u16 num_vfs; enum enic_vf_type vf_type; + bool vf_registered; + u32 pf_cap_version; unsigned int enable_count; spinlock_t enic_api_lock; bool enic_api_busy; @@ -313,6 +315,16 @@ struct enic { /* MBOX protocol state — mbox_lock serializes admin WQ sends */ struct mutex mbox_lock; u64 mbox_msg_num; + /* MBOX request-reply state. mbox_expected_reply is written and + * cleared by the process-context request helpers (capability/register/ + * unregister) and only read by the admin_msg_work receive handlers, so + * it is annotated with READ_ONCE()/WRITE_ONCE() rather than locked: + * only one request is in flight at a time (requesters run under RTNL or + * single-threaded probe/remove), so each request is serialized and its + * reply completes mbox_comp before the next request is issued. + */ + struct completion mbox_comp; + u8 mbox_expected_reply; /* PF: per-VF MBOX state, allocated when SRIOV V2 is enabled */ struct enic_vf_state { diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethernet/cisco/enic/enic_mbox.c index d1b13737e77d..6b8b44c4d96e 100644 --- a/drivers/net/ethernet/cisco/enic/enic_mbox.c +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c @@ -5,6 +5,7 @@ #include #include #include +#include #include "vnic_dev.h" #include "vnic_wq.h" @@ -142,6 +143,16 @@ int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id, return err; } +static int enic_mbox_wait_reply(struct enic *enic, unsigned long timeout_ms) +{ + unsigned long left; + + left = wait_for_completion_timeout(&enic->mbox_comp, + msecs_to_jiffies(timeout_ms)); + + return left ? 0 : -ETIMEDOUT; +} + int enic_mbox_send_link_state(struct enic *enic, u16 vf_id, u32 link_state) { struct enic_mbox_pf_link_state_notif_msg notif = {}; @@ -313,6 +324,166 @@ static void enic_mbox_pf_process_msg(struct enic *enic, hdr->msg_type, vf_id, err); } +static void enic_mbox_vf_handle_capability_reply(struct enic *enic, + void *payload) +{ + struct enic_mbox_vf_capability_reply_msg *reply = payload; + + if (READ_ONCE(enic->mbox_expected_reply) != ENIC_MBOX_VF_CAPABILITY_REPLY) { + netdev_warn(enic->netdev, + "MBOX: stale capability reply (expected %u), drop\n", + READ_ONCE(enic->mbox_expected_reply)); + return; + } + + if (le16_to_cpu(reply->reply.ret_major) == 0) + enic->pf_cap_version = le32_to_cpu(reply->version); + else + netdev_warn(enic->netdev, + "MBOX: PF rejected capability request: %u/%u\n", + le16_to_cpu(reply->reply.ret_major), + le16_to_cpu(reply->reply.ret_minor)); + complete(&enic->mbox_comp); +} + +static void enic_mbox_vf_handle_register_reply(struct enic *enic, + void *payload) +{ + struct enic_mbox_vf_register_reply_msg *reply = payload; + + if (READ_ONCE(enic->mbox_expected_reply) != ENIC_MBOX_VF_REGISTER_REPLY) { + netdev_warn(enic->netdev, + "MBOX: stale register reply (expected %u), drop\n", + READ_ONCE(enic->mbox_expected_reply)); + return; + } + + if (le16_to_cpu(reply->reply.ret_major)) { + netdev_warn(enic->netdev, + "MBOX: VF register rejected by PF: %u/%u\n", + le16_to_cpu(reply->reply.ret_major), + le16_to_cpu(reply->reply.ret_minor)); + } else { + enic->vf_registered = true; + } + complete(&enic->mbox_comp); +} + +static void enic_mbox_vf_handle_unregister_reply(struct enic *enic, + void *payload) +{ + struct enic_mbox_vf_register_reply_msg *reply = payload; + + if (READ_ONCE(enic->mbox_expected_reply) != ENIC_MBOX_VF_UNREGISTER_REPLY) { + netdev_warn(enic->netdev, + "MBOX: stale unregister reply (expected %u), drop\n", + READ_ONCE(enic->mbox_expected_reply)); + return; + } + + if (le16_to_cpu(reply->reply.ret_major)) { + netdev_warn(enic->netdev, + "MBOX: VF unregister rejected by PF: %u/%u\n", + le16_to_cpu(reply->reply.ret_major), + le16_to_cpu(reply->reply.ret_minor)); + } else { + enic->vf_registered = false; + } + complete(&enic->mbox_comp); +} + +static void enic_mbox_vf_handle_link_state(struct enic *enic, void *payload) +{ + struct enic_mbox_pf_link_state_notif_msg *notif = payload; + struct enic_mbox_pf_link_state_ack_msg ack = {}; + int err; + + switch (le32_to_cpu(notif->link_state)) { + case ENIC_MBOX_LINK_STATE_ENABLE: + if (!netif_carrier_ok(enic->netdev)) + netif_carrier_on(enic->netdev); + netdev_dbg(enic->netdev, "MBOX: link state -> UP\n"); + break; + case ENIC_MBOX_LINK_STATE_DISABLE: + if (netif_carrier_ok(enic->netdev)) + netif_carrier_off(enic->netdev); + netdev_dbg(enic->netdev, "MBOX: link state -> DOWN\n"); + break; + default: + netdev_warn(enic->netdev, "MBOX: unknown link state %u\n", + le32_to_cpu(notif->link_state)); + ack.ack.ret_major = cpu_to_le16(ENIC_MBOX_ERR_GENERIC); + break; + } + + err = enic_mbox_send_msg(enic, ENIC_MBOX_PF_LINK_STATE_ACK, + ENIC_MBOX_DST_PF, &ack, sizeof(ack)); + if (err && net_ratelimit()) + netdev_warn(enic->netdev, + "MBOX: failed to send link state ACK: %d\n", err); +} + +static bool enic_mbox_vf_payload_ok(struct enic *enic, u8 msg_type, + u16 payload_len, size_t min_len) +{ + if (payload_len < min_len) { + netdev_warn(enic->netdev, + "MBOX: short payload for type %u (%u < %zu)\n", + msg_type, payload_len, min_len); + return false; + } + return true; +} + +static void enic_mbox_vf_process_msg(struct enic *enic, + struct enic_mbox_hdr *hdr, void *payload, + u16 payload_len) +{ + switch (hdr->msg_type) { + case ENIC_MBOX_VF_CAPABILITY_REPLY: { + size_t exp = sizeof(struct enic_mbox_vf_capability_reply_msg); + + if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type, + payload_len, exp)) + return; + enic_mbox_vf_handle_capability_reply(enic, payload); + break; + } + case ENIC_MBOX_VF_REGISTER_REPLY: { + size_t exp = sizeof(struct enic_mbox_vf_register_reply_msg); + + if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type, + payload_len, exp)) + return; + enic_mbox_vf_handle_register_reply(enic, payload); + break; + } + case ENIC_MBOX_VF_UNREGISTER_REPLY: { + size_t exp = sizeof(struct enic_mbox_vf_register_reply_msg); + + if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type, + payload_len, exp)) + return; + enic_mbox_vf_handle_unregister_reply(enic, payload); + break; + } + case ENIC_MBOX_PF_LINK_STATE_NOTIF: { + size_t exp = sizeof(struct enic_mbox_pf_link_state_notif_msg); + + if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type, + payload_len, exp)) + return; + enic_mbox_vf_handle_link_state(enic, payload); + break; + } + default: + netdev_dbg(enic->netdev, + "MBOX: VF unhandled msg type %u\n", + hdr->msg_type); + break; + } +} + static void enic_mbox_recv_handler(struct enic *enic, void *buf, unsigned int len) { @@ -351,13 +522,117 @@ static void enic_mbox_recv_handler(struct enic *enic, void *buf, payload = buf + sizeof(*hdr); - if (enic->vf_state) + if (enic->vf_state) { enic_mbox_pf_process_msg(enic, hdr, payload); + } else if (le16_to_cpu(hdr->src_vnic_id) == ENIC_MBOX_DST_PF) { + /* src_vnic_id was overwritten from the hardware-verified CQ + * VLAN sender field, so a VF only accepts messages that the + * adapter attributes to the PF. Its sole admin-channel peer is + * the PF; drop anything else as a spoofed notification. + */ + enic_mbox_vf_process_msg(enic, hdr, payload, + msg_len - (u16)sizeof(*hdr)); + } else if (net_ratelimit()) { + netdev_warn(enic->netdev, + "MBOX: VF dropping non-PF message from vnic %u\n", + le16_to_cpu(hdr->src_vnic_id)); + } +} + +int enic_mbox_vf_capability_check(struct enic *enic) +{ + struct enic_mbox_vf_capability_msg req = {}; + int err; + + enic->pf_cap_version = 0; + reinit_completion(&enic->mbox_comp); + WRITE_ONCE(enic->mbox_expected_reply, ENIC_MBOX_VF_CAPABILITY_REPLY); + req.version = cpu_to_le32(ENIC_MBOX_CAP_VERSION_1); + + err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_CAPABILITY_REQUEST, + ENIC_MBOX_DST_PF, &req, sizeof(req)); + if (err) { + WRITE_ONCE(enic->mbox_expected_reply, 0); + return err; + } + + err = enic_mbox_wait_reply(enic, 3000); + WRITE_ONCE(enic->mbox_expected_reply, 0); + if (err) { + netdev_warn(enic->netdev, + "MBOX: no capability reply from PF\n"); + return err; + } + + if (enic->pf_cap_version < ENIC_MBOX_CAP_VERSION_1) { + netdev_warn(enic->netdev, + "MBOX: PF rejected capability request or reported unsupported version %u\n", + enic->pf_cap_version); + return -EOPNOTSUPP; + } + + return 0; +} + +int enic_mbox_vf_register(struct enic *enic) +{ + int err; + + enic->vf_registered = false; + reinit_completion(&enic->mbox_comp); + WRITE_ONCE(enic->mbox_expected_reply, ENIC_MBOX_VF_REGISTER_REPLY); + + err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_REGISTER_REQUEST, + ENIC_MBOX_DST_PF, NULL, 0); + if (err) { + WRITE_ONCE(enic->mbox_expected_reply, 0); + return err; + } + + err = enic_mbox_wait_reply(enic, 3000); + WRITE_ONCE(enic->mbox_expected_reply, 0); + if (err) { + netdev_warn(enic->netdev, + "MBOX: VF registration with PF timed out\n"); + return err; + } + + if (!enic->vf_registered) + return -ENODEV; + + return 0; +} + +int enic_mbox_vf_unregister(struct enic *enic) +{ + int err; + + if (!enic->vf_registered) + return 0; + + reinit_completion(&enic->mbox_comp); + WRITE_ONCE(enic->mbox_expected_reply, ENIC_MBOX_VF_UNREGISTER_REPLY); + + err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_UNREGISTER_REQUEST, + ENIC_MBOX_DST_PF, NULL, 0); + if (err) { + WRITE_ONCE(enic->mbox_expected_reply, 0); + return err; + } + + err = enic_mbox_wait_reply(enic, 3000); + WRITE_ONCE(enic->mbox_expected_reply, 0); + if (err) + return err; + if (enic->vf_registered) + return -EACCES; + return 0; } void enic_mbox_init(struct enic *enic) { enic->mbox_msg_num = 0; mutex_init(&enic->mbox_lock); + init_completion(&enic->mbox_comp); enic->admin_rq_handler = enic_mbox_recv_handler; } diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.h b/drivers/net/ethernet/cisco/enic/enic_mbox.h index f1de67db1273..15e30ee2b0ed 100644 --- a/drivers/net/ethernet/cisco/enic/enic_mbox.h +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.h @@ -88,5 +88,8 @@ void enic_mbox_init(struct enic *enic); int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id, void *payload, u16 payload_len); int enic_mbox_send_link_state(struct enic *enic, u16 vf_id, u32 link_state); +int enic_mbox_vf_capability_check(struct enic *enic); +int enic_mbox_vf_register(struct enic *enic); +int enic_mbox_vf_unregister(struct enic *enic); #endif /* _ENIC_MBOX_H_ */ From 4ff204cef01e45ce8c8283ee078b102493b4fd95 Mon Sep 17 00:00:00 2001 From: Satish Kharat Date: Wed, 12 Aug 2026 05:48:09 -0700 Subject: [PATCH 1385/1433] enic: wire V2 SR-IOV enable with admin channel and MBOX Extend enic_sriov_configure() to handle V2 SR-IOV VFs. When the PF detects V2 VF device IDs, the enable path allocates per-VF MBOX state, initializes the MBOX protocol, opens the admin channel, and then calls pci_enable_sriov(). The admin channel must be ready before VFs are created so that VF drivers can immediately begin the MBOX capability and registration handshake during their probe. The enic_sriov_configure() dispatcher and its V2 helpers (enic_sriov_v2_enable, enic_sriov_v2_disable) are defined here but intentionally not yet wired into struct pci_driver via .sriov_configure -- hence the __maybe_unused annotations. This series introduces only the admin channel and MBOX infrastructure; sysfs-driven V2 enable/disable will be activated in a follow-up patch by adding ".sriov_configure = enic_sriov_configure," to enic_driver. Because .sriov_configure is not registered yet, enic_sriov_configure() cannot run concurrently with the rtnl-protected reset paths (enic_reset(), enic_tx_hang_reset()) in this series, so there is no reachable locking race between SR-IOV enable/disable and reset. The follow-up patch that wires the callback will add the necessary serialization against those paths. Note that simply taking rtnl_lock() around the enable path is not viable, because pci_enable_sriov() triggers VF probe and register_netdev(), which themselves acquire rtnl; the wiring patch therefore uses finer-grained serialization. The disable path first clears ENIC_SRIOV_ENABLED and flushes the link-notify work, so no further VF link-state broadcast can run, then calls pci_disable_sriov() (VF drivers unregister via MBOX), closes the admin channel, and frees per-VF state. Clearing the flag and flushing the work before vf_state is freed closes a use-after-free window against the link-notify path. Notify registered VFs of PF link transitions: enic_link_check() schedules link_notify_work on each carrier up/down edge, and the work handler sends PF_LINK_STATE_NOTIF to the VFs from process context. The broadcast cannot run directly in enic_link_check() because the MBOX send path may sleep and link check runs in the notify timer/ISR context. On a V2 VF the admin-channel (PF) link-state notification is the sole authority for carrier state, so enic_link_check() returns early for such VFs. As a side effect the VF retains the firmware-provided static Rx interrupt coalescing (config.intr_timer_usec) rather than PF-driven speed-adaptive coalescing; this is intentional, as adaptive Rx coalescing is a PF-only responsibility for V2 VFs. Re-establish the admin/MBOX channel across a PF reset. enic_reset() and enic_tx_hang_reset() fully close the admin channel before the soft/hang reset (which wipes all hardware queues, including the admin WQ/RQ), then reopen it and re-run enic_mbox_init() after the data path is back up, and re-push the current link state to registered VFs. Reject VF port profile requests when V2 SR-IOV is active (enic_is_valid_pp_vf), since enic->pp is not reallocated for V2 VFs and the V2 protocol uses MBOX instead of port profiles. Update enic_remove() to run enic_dev_deinit() and vnic_dev_close() after SR-IOV teardown, so the PF device remains functional while VFs are being cleaned up. This ordering applies to both V1 and V2 SR-IOV paths. Restrict the probe-time SR-IOV auto-enable to the legacy VF types (V1 and usNIC). A V2-capable adapter whose firmware lacks V2 support is downgraded to ENIC_VF_TYPE_NONE, and V2 VFs require the admin channel which is only brought up via sysfs enic_sriov_configure(); neither must be auto-enabled through the legacy pci_enable_sriov() path at probe. Signed-off-by: Satish Kharat Link: https://patch.msgid.link/20260812-enic-sriov-v2-admin-channel-v2-v13-9-b3809e448aba@cisco.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cisco/enic/enic.h | 3 + drivers/net/ethernet/cisco/enic/enic_admin.c | 3 + drivers/net/ethernet/cisco/enic/enic_main.c | 288 ++++++++++++++++++- drivers/net/ethernet/cisco/enic/enic_mbox.c | 13 +- drivers/net/ethernet/cisco/enic/enic_pp.c | 5 + drivers/net/ethernet/cisco/enic/enic_res.c | 1 + drivers/net/ethernet/cisco/enic/vnic_enet.h | 4 +- 7 files changed, 301 insertions(+), 16 deletions(-) diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h index db4d52a4158f..4a67947cfb9f 100644 --- a/drivers/net/ethernet/cisco/enic/enic.h +++ b/drivers/net/ethernet/cisco/enic/enic.h @@ -305,6 +305,7 @@ struct enic { struct vnic_intr admin_intr; struct delayed_work admin_poll_work; unsigned int admin_intr_index; + struct work_struct link_notify_work; struct work_struct admin_msg_work; spinlock_t admin_msg_lock; /* protects admin_msg_list */ struct list_head admin_msg_list; @@ -325,6 +326,7 @@ struct enic { */ struct completion mbox_comp; u8 mbox_expected_reply; + bool mbox_initialized; /* PF: per-VF MBOX state, allocated when SRIOV V2 is enabled */ struct enic_vf_state { @@ -451,6 +453,7 @@ void enic_reset_addr_lists(struct enic *enic); int enic_sriov_enabled(struct enic *enic); int enic_is_valid_vf(struct enic *enic, int vf); int enic_is_dynamic(struct enic *enic); +int enic_is_sriov_vf_v2(struct enic *enic); void enic_set_ethtool_ops(struct net_device *netdev); int __enic_set_rsskey(struct enic *enic); void enic_ext_cq(struct enic *enic); diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.c b/drivers/net/ethernet/cisco/enic/enic_admin.c index f4a409fec983..7188f1b81c04 100644 --- a/drivers/net/ethernet/cisco/enic/enic_admin.c +++ b/drivers/net/ethernet/cisco/enic/enic_admin.c @@ -639,6 +639,7 @@ void enic_admin_channel_close(struct enic *enic) vnic_intr_mask(&enic->admin_intr); enic_admin_teardown_intr(enic); + cancel_work_sync(&enic->link_notify_work); cancel_work_sync(&enic->admin_msg_work); enic_admin_msg_drain(enic); @@ -658,6 +659,8 @@ void enic_admin_channel_close(struct enic *enic) vnic_cq_clean(&enic->admin_cq[0]); vnic_cq_clean(&enic->admin_cq[1]); vnic_intr_clean(&enic->admin_intr); + + enic->admin_rq_handler = NULL; enic_admin_free_resources(enic); enic->admin_chan_up = false; diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethernet/cisco/enic/enic_main.c index b56e5c75ade3..27b31d5a95dd 100644 --- a/drivers/net/ethernet/cisco/enic/enic_main.c +++ b/drivers/net/ethernet/cisco/enic/enic_main.c @@ -60,6 +60,8 @@ #include "enic_clsf.h" #include "enic_rq.h" #include "enic_wq.h" +#include "enic_admin.h" +#include "enic_mbox.h" #define ENIC_NOTIFY_TIMER_PERIOD (2 * HZ) @@ -314,6 +316,11 @@ static int enic_is_sriov_vf(struct enic *enic) enic->pdev->device == PCI_DEVICE_ID_CISCO_VIC_ENET_VF_V2; } +int enic_is_sriov_vf_v2(struct enic *enic) +{ + return enic->pdev->device == PCI_DEVICE_ID_CISCO_VIC_ENET_VF_V2; +} + int enic_is_valid_vf(struct enic *enic, int vf) { #ifdef CONFIG_PCI_IOV @@ -411,18 +418,50 @@ static void enic_set_rx_coal_setting(struct enic *enic) rx_coal->use_adaptive_rx_coalesce = 1; } +static void enic_link_notify_work_handler(struct work_struct *work) +{ + struct enic *enic = container_of(work, struct enic, + link_notify_work); + u32 state; + u16 i; + + if (!enic_sriov_enabled(enic) || !enic->vf_state) + return; + + state = netif_carrier_ok(enic->netdev) ? + ENIC_MBOX_LINK_STATE_ENABLE : + ENIC_MBOX_LINK_STATE_DISABLE; + + for (i = 0; i < enic->num_vfs; i++) + enic_mbox_send_link_state(enic, i, state); +} + static void enic_link_check(struct enic *enic) { - int link_status = vnic_dev_link_status(enic->vdev); - int carrier_ok = netif_carrier_ok(enic->netdev); + int link_status; + int carrier_ok; + + /* A V2 SR-IOV VF's carrier is driven by PF link-state MBOX + * notifications, not by its own vnic link status; skip the + * autonomous check so it cannot flap the VF carrier. + */ + if (enic_is_sriov_vf_v2(enic)) + return; + + link_status = vnic_dev_link_status(enic->vdev); + carrier_ok = netif_carrier_ok(enic->netdev); if (link_status && !carrier_ok) { netdev_info(enic->netdev, "Link UP\n"); netif_carrier_on(enic->netdev); enic_set_rx_coal_setting(enic); + if (enic_sriov_enabled(enic) && enic->vf_state) + schedule_work(&enic->link_notify_work); } else if (!link_status && carrier_ok) { netdev_info(enic->netdev, "Link DOWN\n"); netif_carrier_off(enic->netdev); + if (enic_sriov_enabled(enic) && enic->vf_state) + schedule_work(&enic->link_notify_work); } } @@ -2154,15 +2193,47 @@ static void enic_reset(struct work_struct *work) /* Stop any activity from infiniband */ enic_set_api_busy(enic, true); + /* Fully tear down the V2 admin/MBOX channel before the soft reset. + * The reset wipes all hardware queues including the admin WQ/RQ; + * closing first tells firmware to stop the admin QP (so it no longer + * DMAs from the about-to-be-reset rings) and frees the admin resources + * so they are cleanly re-allocated afterwards. + */ + if (enic_sriov_enabled(enic) && + enic->vf_type == ENIC_VF_TYPE_V2) + enic_admin_channel_close(enic); + enic_stop(enic->netdev); + enic_dev_soft_reset(enic); enic_reset_addr_lists(enic); enic_init_vnic_resources(enic); enic_set_rss_nic_cfg(enic); enic_dev_set_ig_vlan_rewrite_mode(enic); enic_ext_cq(enic); + enic_open(enic->netdev); + /* Re-establish the admin/MBOX channel after the data path is back up, + * mirroring the SR-IOV enable path (channel open + mbox init). The + * channel was fully torn down by enic_admin_channel_close() above. + */ + if (enic_sriov_enabled(enic) && + enic->vf_type == ENIC_VF_TYPE_V2) { + if (enic_admin_channel_open(enic)) { + netdev_err(enic->netdev, + "admin channel reopen after reset failed\n"); + } else { + enic_mbox_init(enic); + /* The link came back up during enic_open() above + * while MBOX sends were still disabled (channel not + * yet reopened), so that link-notify was dropped. + * Re-push current link state to registered VFs now. + */ + schedule_work(&enic->link_notify_work); + } + } + /* Allow infiniband to fiddle with the device again */ enic_set_api_busy(enic, false); @@ -2180,16 +2251,46 @@ static void enic_tx_hang_reset(struct work_struct *work) /* Stop any activity from infiniband */ enic_set_api_busy(enic, true); + /* Fully tear down the V2 admin/MBOX channel before the hang reset, for + * the same reason as the soft reset path: stop the admin QP and free + * the admin resources before the hardware queues are wiped. + */ + if (enic_sriov_enabled(enic) && + enic->vf_type == ENIC_VF_TYPE_V2) + enic_admin_channel_close(enic); + enic_dev_hang_notify(enic); enic_stop(enic->netdev); + enic_dev_hang_reset(enic); enic_reset_addr_lists(enic); enic_init_vnic_resources(enic); enic_set_rss_nic_cfg(enic); enic_dev_set_ig_vlan_rewrite_mode(enic); enic_ext_cq(enic); + enic_open(enic->netdev); + /* Re-establish the admin/MBOX channel after the data path is back up, + * mirroring the SR-IOV enable path (channel open + mbox init). The + * channel was fully torn down by enic_admin_channel_close() above. + */ + if (enic_sriov_enabled(enic) && + enic->vf_type == ENIC_VF_TYPE_V2) { + if (enic_admin_channel_open(enic)) { + netdev_err(enic->netdev, + "admin channel reopen after reset failed\n"); + } else { + enic_mbox_init(enic); + /* The link came back up during enic_open() above + * while MBOX sends were still disabled (channel not + * yet reopened), so that link-notify was dropped. + * Re-push current link state to registered VFs now. + */ + schedule_work(&enic->link_notify_work); + } + } + /* Allow infiniband to fiddle with the device again */ enic_set_api_busy(enic, false); @@ -2200,6 +2301,8 @@ static void enic_tx_hang_reset(struct work_struct *work) static int enic_set_intr_mode(struct enic *enic) { + unsigned int admin_reserve = enic->has_admin_channel ? 1 : 0; + unsigned int min_intr = ENIC_MSIX_MIN_INTR + admin_reserve; unsigned int i; int num_intr; @@ -2210,12 +2313,12 @@ static int enic_set_intr_mode(struct enic *enic) */ if (enic->config.intr_mode < 1 && - enic->intr_avail >= ENIC_MSIX_MIN_INTR) { + enic->intr_avail >= min_intr) { for (i = 0; i < enic->intr_avail; i++) enic->msix_entry[i].entry = i; num_intr = pci_enable_msix_range(enic->pdev, enic->msix_entry, - ENIC_MSIX_MIN_INTR, + min_intr, enic->intr_avail); if (num_intr > 0) { vnic_dev_set_intr_mode(enic->vdev, @@ -2310,7 +2413,13 @@ static int enic_adjust_resources(struct enic *enic) enic->cq_count = 2; enic->intr_count = enic->intr_avail; break; - case VNIC_DEV_INTR_MODE_MSIX: + case VNIC_DEV_INTR_MODE_MSIX: { + /* Reserve one MSI-X slot for the admin channel interrupt + * when V2 SR-IOV admin channel resources are present. + */ + unsigned int admin_reserve = + enic->has_admin_channel ? 1 : 0; + /* Adjust the number of wqs/rqs/cqs/interrupts that will be * used based on which resource is the most constrained */ @@ -2319,7 +2428,8 @@ static int enic_adjust_resources(struct enic *enic) ENIC_RQ_MIN_DEFAULT); rq_avail = min3(enic->rq_avail, ENIC_RQ_MAX, rq_default); max_queues = min(enic->cq_avail, - enic->intr_avail - ENIC_MSIX_RESERVED_INTR); + enic->intr_avail - ENIC_MSIX_RESERVED_INTR - + admin_reserve); if (wq_avail + rq_avail <= max_queues) { enic->rq_count = rq_avail; enic->wq_count = wq_avail; @@ -2337,6 +2447,7 @@ static int enic_adjust_resources(struct enic *enic) enic->intr_count = enic->cq_count + ENIC_MSIX_RESERVED_INTR; break; + } default: dev_err(enic_get_dev(enic), "Unknown interrupt mode\n"); return -EINVAL; @@ -2689,6 +2800,144 @@ static void enic_sriov_detect_vf_type(struct enic *enic) enic->vf_type = ENIC_VF_TYPE_NONE; } } + +static int __maybe_unused +enic_sriov_v2_enable(struct enic *enic, int num_vfs) +{ + int err; + + if (!enic->has_admin_channel) { + netdev_err(enic->netdev, + "V2 SR-IOV requires admin channel resources\n"); + return -EOPNOTSUPP; + } + + enic->vf_state = kcalloc(num_vfs, sizeof(*enic->vf_state), GFP_KERNEL); + if (!enic->vf_state) + return -ENOMEM; + + /* Install the MBOX receive handler before the admin interrupt is + * unmasked in enic_admin_channel_open(), so no early completion is + * dropped. + */ + enic_mbox_init(enic); + + err = enic_admin_channel_open(enic); + if (err) { + netdev_err(enic->netdev, + "Failed to open admin channel: %d\n", err); + goto free_vf_state; + } + + enic->num_vfs = num_vfs; + + err = pci_enable_sriov(enic->pdev, num_vfs); + if (err) { + netdev_err(enic->netdev, + "pci_enable_sriov failed: %d\n", err); + goto close_admin; + } + + enic->priv_flags |= ENIC_SRIOV_ENABLED; + return num_vfs; + +close_admin: + enic->num_vfs = 0; + enic_admin_channel_close(enic); +free_vf_state: + kfree(enic->vf_state); + enic->vf_state = NULL; + return err; +} + +static void enic_sriov_v2_disable(struct enic *enic) +{ + /* Stop new VF link-state broadcasts before tearing down vf_state. + * Clearing ENIC_SRIOV_ENABLED makes enic_link_check() (called from + * the notify timer/ISR) skip the VF notify path, and cancelling + * link_notify_work ensures any already-queued broadcast has finished + * before vf_state is freed, closing a use-after-free window. + */ + enic->priv_flags &= ~ENIC_SRIOV_ENABLED; + cancel_work_sync(&enic->link_notify_work); + + pci_disable_sriov(enic->pdev); + enic_admin_channel_close(enic); + kfree(enic->vf_state); + enic->vf_state = NULL; + enic->num_vfs = 0; +} + +/* + * enic_sriov_configure() and its V2 helpers are defined but not yet wired + * into enic_driver via .sriov_configure (see the __maybe_unused annotations); + * V2 enable/disable is activated in a follow-up series. Because the callback + * is not registered, it cannot run concurrently with the rtnl-protected reset + * paths (enic_reset(), enic_tx_hang_reset()) yet. Serialization against those + * paths is added together with the .sriov_configure wiring in that series. + */ +static int __maybe_unused +enic_sriov_configure(struct pci_dev *pdev, int num_vfs) +{ + struct net_device *netdev = pci_get_drvdata(pdev); + struct enic *enic = netdev_priv(netdev); + struct enic_port_profile *pp; + int err; + + if (num_vfs > 0) { + if (enic->config.mq_subvnic_count) { + netdev_err(netdev, + "SR-IOV not supported with multi-queue sub-vnics\n"); + return -EOPNOTSUPP; + } + + if (enic->vf_type == ENIC_VF_TYPE_NONE) { + netdev_err(netdev, + "SR-IOV not supported on this firmware version\n"); + return -EOPNOTSUPP; + } + + if (enic->vf_type == ENIC_VF_TYPE_V2) + return enic_sriov_v2_enable(enic, num_vfs); + + pp = kcalloc(num_vfs, sizeof(*pp), GFP_KERNEL); + if (!pp) + return -ENOMEM; + + err = pci_enable_sriov(pdev, num_vfs); + if (err) { + kfree(pp); + return err; + } + + kfree(enic->pp); + enic->pp = pp; + enic->num_vfs = num_vfs; + enic->priv_flags |= ENIC_SRIOV_ENABLED; + return num_vfs; + } + + if (!enic_sriov_enabled(enic)) + return 0; + + if (enic->vf_type == ENIC_VF_TYPE_V2) { + enic_sriov_v2_disable(enic); + return 0; + } + + pp = kzalloc_obj(*enic->pp, GFP_KERNEL); + if (!pp) + return -ENOMEM; + + pci_disable_sriov(pdev); + enic->num_vfs = 0; + enic->priv_flags &= ~ENIC_SRIOV_ENABLED; + + kfree(enic->pp); + enic->pp = pp; + + return 0; +} #endif static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) @@ -2787,12 +3036,23 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) goto err_out_vnic_unregister; #ifdef CONFIG_PCI_IOV - /* Get number of subvnics */ + enic_sriov_detect_vf_type(enic); + + /* Auto-enable SR-IOV only for the legacy VF types. V2 VFs require + * the admin channel, which is not yet set up at probe time (V2 SR-IOV + * will be enabled through the sysfs .sriov_configure callback once a + * follow-up series wires it up); and a V2-capable device whose + * firmware lacks V2 support is downgraded to ENIC_VF_TYPE_NONE by + * enic_sriov_detect_vf_type() and must not be brought up through the + * legacy pci_enable_sriov() path either. + */ pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_SRIOV); if (pos) { pci_read_config_word(pdev, pos + PCI_SRIOV_TOTAL_VF, &enic->num_vfs); - if (enic->num_vfs) { + if (enic->num_vfs && + (enic->vf_type == ENIC_VF_TYPE_V1 || + enic->vf_type == ENIC_VF_TYPE_USNIC)) { err = pci_enable_sriov(pdev, enic->num_vfs); if (err) { dev_err(dev, "SRIOV enable failed, aborting." @@ -2804,7 +3064,6 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) num_pps = enic->num_vfs; } } - enic_sriov_detect_vf_type(enic); #endif /* Allocate structure for port profiles */ @@ -2881,6 +3140,7 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) INIT_WORK(&enic->reset, enic_reset); INIT_WORK(&enic->tx_hang_reset, enic_tx_hang_reset); INIT_WORK(&enic->change_mtu_work, enic_change_mtu_work); + INIT_WORK(&enic->link_notify_work, enic_link_notify_work_handler); for (i = 0; i < enic->wq_count; i++) spin_lock_init(&enic->wq[i].lock); @@ -3034,14 +3294,16 @@ static void enic_remove(struct pci_dev *pdev) disable_work_sync(&enic->tx_hang_reset); disable_work_sync(&enic->change_mtu_work); unregister_netdev(netdev); - enic_dev_deinit(enic); - vnic_dev_close(enic->vdev); #ifdef CONFIG_PCI_IOV if (enic_sriov_enabled(enic)) { - pci_disable_sriov(pdev); - enic->priv_flags &= ~ENIC_SRIOV_ENABLED; + if (enic->vf_type == ENIC_VF_TYPE_V2) + enic_sriov_v2_disable(enic); + else + pci_disable_sriov(pdev); } #endif + enic_dev_deinit(enic); + vnic_dev_close(enic->vdev); kfree(enic->pp); vnic_dev_unregister(enic->vdev); enic_iounmap(enic); diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethernet/cisco/enic/enic_mbox.c index 6b8b44c4d96e..2fb0f1e2ff50 100644 --- a/drivers/net/ethernet/cisco/enic/enic_mbox.c +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c @@ -631,8 +631,17 @@ int enic_mbox_vf_unregister(struct enic *enic) void enic_mbox_init(struct enic *enic) { + /* mbox_lock and mbox_comp must be initialized exactly once per + * device lifetime; the PF sriov_configure path can re-enter this + * on each enable cycle where these primitives are already set up. + */ + if (!enic->mbox_initialized) { + mutex_init(&enic->mbox_lock); + init_completion(&enic->mbox_comp); + enic->mbox_initialized = true; + } else { + reinit_completion(&enic->mbox_comp); + } enic->mbox_msg_num = 0; - mutex_init(&enic->mbox_lock); - init_completion(&enic->mbox_comp); enic->admin_rq_handler = enic_mbox_recv_handler; } diff --git a/drivers/net/ethernet/cisco/enic/enic_pp.c b/drivers/net/ethernet/cisco/enic/enic_pp.c index 4720a952725d..3f611e240c25 100644 --- a/drivers/net/ethernet/cisco/enic/enic_pp.c +++ b/drivers/net/ethernet/cisco/enic/enic_pp.c @@ -25,6 +25,11 @@ int enic_is_valid_pp_vf(struct enic *enic, int vf, int *err) if (vf != PORT_SELF_VF) { #ifdef CONFIG_PCI_IOV if (enic_sriov_enabled(enic)) { + /* V2 SR-IOV uses MBOX, not port profiles */ + if (enic->vf_type == ENIC_VF_TYPE_V2) { + *err = -EOPNOTSUPP; + goto err_out; + } if (vf < 0 || vf >= enic->num_vfs) { *err = -EINVAL; goto err_out; diff --git a/drivers/net/ethernet/cisco/enic/enic_res.c b/drivers/net/ethernet/cisco/enic/enic_res.c index 2b7545d6a67f..436326ace049 100644 --- a/drivers/net/ethernet/cisco/enic/enic_res.c +++ b/drivers/net/ethernet/cisco/enic/enic_res.c @@ -59,6 +59,7 @@ int enic_get_vnic_config(struct enic *enic) GET_CONFIG(intr_timer_usec); GET_CONFIG(loop_tag); GET_CONFIG(num_arfs); + GET_CONFIG(mq_subvnic_count); GET_CONFIG(max_rq_ring); GET_CONFIG(max_wq_ring); GET_CONFIG(max_cq_ring); diff --git a/drivers/net/ethernet/cisco/enic/vnic_enet.h b/drivers/net/ethernet/cisco/enic/vnic_enet.h index 9e8e86262a3f..519d2969990b 100644 --- a/drivers/net/ethernet/cisco/enic/vnic_enet.h +++ b/drivers/net/ethernet/cisco/enic/vnic_enet.h @@ -21,7 +21,9 @@ struct vnic_enet_config { u16 loop_tag; u16 vf_rq_count; u16 num_arfs; - u8 reserved[66]; + u8 reserved1[32]; + u16 mq_subvnic_count; + u8 reserved2[32]; u32 max_rq_ring; // MAX RQ ring size u32 max_wq_ring; // MAX WQ ring size u32 max_cq_ring; // MAX CQ ring size From b2dfdc966b65d335febbe596b9ffdfaac1187270 Mon Sep 17 00:00:00 2001 From: Satish Kharat Date: Wed, 12 Aug 2026 05:48:10 -0700 Subject: [PATCH 1386/1433] enic: add V2 VF probe with admin channel and PF registration When a V2 SR-IOV VF probes, initialize the MBOX protocol, open the admin channel, perform the capability check with the PF, and register with the PF. This establishes the PF-VF communication path that the PF uses to send link state notifications. The admin channel and MBOX registration happen after enic_dev_init() (which discovers admin channel resources) and before register_netdev() so the VF is fully initialized before the interface is visible to userspace. A V2 VF whose firmware did not provision admin WQ/RQ/CQ resources fails probe with -ENODEV from enic_admin_channel_open(); the admin channel is a hard requirement for V2 VFs. enic_mbox_init() installs the receive handler and resets the message sequence number before enic_admin_channel_open() unmasks the admin interrupt, so a completion can never arrive before the handler is in place. On remove, the VF unregisters from the PF and closes its admin channel before tearing down data path resources. V2 VFs are not provisioned with an RES_TYPE_SRIOV_INTR resource by firmware, so bypass that check in the admin channel capability detection for V2 VFs. The PF still requires this resource. The admin MSI-X vector reserved by enic_set_intr_mode() is used for the admin channel interrupt. enic_adjust_resources() ensures the reserved slot is within intr_avail bounds even at maximum queue configurations. The admin INTR uses a RES_TYPE_INTR_CTRL slot shared with the data path. Signed-off-by: Satish Kharat Link: https://patch.msgid.link/20260812-enic-sriov-v2-admin-channel-v2-v13-10-b3809e448aba@cisco.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cisco/enic/enic_main.c | 94 ++++++++++++++++++--- drivers/net/ethernet/cisco/enic/enic_res.c | 3 +- 2 files changed, 86 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethernet/cisco/enic/enic_main.c index 27b31d5a95dd..537ed5ad7800 100644 --- a/drivers/net/ethernet/cisco/enic/enic_main.c +++ b/drivers/net/ethernet/cisco/enic/enic_main.c @@ -2414,15 +2414,19 @@ static int enic_adjust_resources(struct enic *enic) enic->intr_count = enic->intr_avail; break; case VNIC_DEV_INTR_MODE_MSIX: { - /* Reserve one MSI-X slot for the admin channel interrupt - * when V2 SR-IOV admin channel resources are present. - */ - unsigned int admin_reserve = - enic->has_admin_channel ? 1 : 0; - /* Adjust the number of wqs/rqs/cqs/interrupts that will be - * used based on which resource is the most constrained + * used based on which resource is the most constrained. + * Reserve one extra MSI-X slot for the admin channel INTR + * when has_admin_channel is set so that + * enic_admin_setup_intr() can allocate at intr_count + * within the intr_avail bounds even when the data queue + * count is maxed out. intr_count counts only the data-path + * IRQs (registered by enic_request_intr()); the admin INTR + * lives at msix index intr_count and is set up later by + * enic_admin_setup_intr(). */ + unsigned int admin_reserve = enic->has_admin_channel ? 1 : 0; + wq_avail = min(enic->wq_avail, ENIC_WQ_MAX); rq_default = max(netif_get_num_default_rss_queues(), ENIC_RQ_MIN_DEFAULT); @@ -3128,6 +3132,42 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) goto err_out_dev_close; } + /* Initialise link_notify_work before the V2-VF admin-open block below: + * its error path (err_out_admin_close -> enic_admin_channel_close() -> + * cancel_work_sync()) would otherwise act on an uninitialised work. + */ + INIT_WORK(&enic->link_notify_work, enic_link_notify_work_handler); + + /* V2 VF: open admin channel and register with PF. + * Must happen before register_netdev so the VF is fully + * initialized before the interface is visible to userspace. + * + * enic_mbox_init() installs the receive handler and resets the + * sequence number; it must run before enic_admin_channel_open() + * unmasks the admin interrupt so an early completion is not dropped. + */ + if (enic_is_sriov_vf_v2(enic)) { + enic_mbox_init(enic); + err = enic_admin_channel_open(enic); + if (err) { + dev_err(dev, + "Failed to open admin channel: %d\n", err); + goto err_out_dev_deinit; + } + err = enic_mbox_vf_capability_check(enic); + if (err) { + dev_err(dev, + "MBOX capability check failed: %d\n", err); + goto err_out_admin_close; + } + err = enic_mbox_vf_register(enic); + if (err) { + dev_err(dev, + "MBOX VF registration failed: %d\n", err); + goto err_out_admin_close; + } + } + netif_set_real_num_tx_queues(netdev, enic->wq_count); netif_set_real_num_rx_queues(netdev, enic->rq_count); @@ -3140,7 +3180,6 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) INIT_WORK(&enic->reset, enic_reset); INIT_WORK(&enic->tx_hang_reset, enic_tx_hang_reset); INIT_WORK(&enic->change_mtu_work, enic_change_mtu_work); - INIT_WORK(&enic->link_notify_work, enic_link_notify_work_handler); for (i = 0; i < enic->wq_count; i++) spin_lock_init(&enic->wq[i].lock); @@ -3153,7 +3192,7 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) err = enic_set_mac_addr(netdev, enic->mac_addr); if (err) { dev_err(dev, "Invalid MAC address, aborting\n"); - goto err_out_dev_deinit; + goto err_out_admin_close; } enic->tx_coalesce_usecs = enic->config.intr_timer_usec; @@ -3251,11 +3290,23 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) err = register_netdev(netdev); if (err) { dev_err(dev, "Cannot register net device, aborting\n"); - goto err_out_dev_deinit; + goto err_out_admin_close; } return 0; +err_out_admin_close: + if (enic_is_sriov_vf_v2(enic)) { + if (enic->vf_registered) { + int unreg_err = enic_mbox_vf_unregister(enic); + + if (unreg_err) + netdev_warn(netdev, + "Failed to unregister from PF: %d\n", + unreg_err); + } + enic_admin_channel_close(enic); + } err_out_dev_deinit: enic_dev_deinit(enic); err_out_dev_close: @@ -3293,7 +3344,30 @@ static void enic_remove(struct pci_dev *pdev) disable_work_sync(&enic->reset); disable_work_sync(&enic->tx_hang_reset); disable_work_sync(&enic->change_mtu_work); + + /* Close the admin channel and unregister from the PF before + * unregister_netdev() to prevent a late PF notification from + * touching a netdev that is being torn down. + */ + if (enic_is_sriov_vf_v2(enic)) { + if (enic->vf_registered) { + int unreg_err = enic_mbox_vf_unregister(enic); + + if (unreg_err) + netdev_warn(netdev, + "Failed to unregister from PF: %d\n", + unreg_err); + } + enic_admin_channel_close(enic); + } + unregister_netdev(netdev); + /* unregister_netdev() -> enic_stop() stops the notify timer, so + * no new link_notify_work can be queued past this point. Cancel + * unconditionally to cover the narrow window where + * enic_link_check() scheduled it just as SR-IOV was disabled. + */ + cancel_work_sync(&enic->link_notify_work); #ifdef CONFIG_PCI_IOV if (enic_sriov_enabled(enic)) { if (enic->vf_type == ENIC_VF_TYPE_V2) diff --git a/drivers/net/ethernet/cisco/enic/enic_res.c b/drivers/net/ethernet/cisco/enic/enic_res.c index 436326ace049..74cd2ee3af5c 100644 --- a/drivers/net/ethernet/cisco/enic/enic_res.c +++ b/drivers/net/ethernet/cisco/enic/enic_res.c @@ -211,7 +211,8 @@ void enic_get_res_counts(struct enic *enic) vnic_dev_get_res_count(enic->vdev, RES_TYPE_ADMIN_RQ) >= 1 && vnic_dev_get_res_count(enic->vdev, RES_TYPE_ADMIN_CQ) >= ARRAY_SIZE(enic->admin_cq) && - vnic_dev_get_res_count(enic->vdev, RES_TYPE_SRIOV_INTR) >= 1; + (enic_is_sriov_vf_v2(enic) || + vnic_dev_get_res_count(enic->vdev, RES_TYPE_SRIOV_INTR) >= 1); dev_info(enic_get_dev(enic), "vNIC resources avail: wq %d rq %d cq %d intr %d admin %s\n", From 885f462fe90ebd0c398815763b051ba5fc3fae5f Mon Sep 17 00:00:00 2001 From: Satish Kharat Date: Wed, 12 Aug 2026 05:48:11 -0700 Subject: [PATCH 1387/1433] enic: re-establish V2 VF admin channel and PF registration after reset The reset paths (enic_reset/enic_tx_hang_reset) tore down and re-opened the V2 admin/MBOX channel only for the PF: the close/reopen was gated on enic_sriov_enabled() && vf_type == ENIC_VF_TYPE_V2, which is never true on a VF (vf_type is set only on the PF; VFs are identified by enic_is_sriov_vf_v2()). A VF-initiated reset therefore left the VF admin QP wiped by the reset but never re-opened, and the VF never re-registered with the PF, so VF<->PF MBOX traffic (currently link state) stopped working until the VF was re-probed. Factor the decision into enic_has_admin_chan() (true for a V2 PF while SR-IOV is enabled and for every V2 VF) and the reopen sequence into enic_admin_chan_reopen(). For a VF the helper additionally re-runs the probe-time handshake (enic_mbox_vf_capability_check() + enic_mbox_vf_register()) so the PF learns about the VF again; for a PF it re-pushes the current link state as before. Before reopening, invalidate the VF's local registration flag. The reset only wipes the VF's admin QP, not the PF's software vf_state (that changes only via the register/unregister MBOX handlers), so the PF may still hold a stale "registered" until the VF re-registers. Locally, a failed reopen or re-handshake must not leave a stale registered state that a later teardown would try to unregister over a dead channel. Signed-off-by: Satish Kharat Link: https://patch.msgid.link/20260812-enic-sriov-v2-admin-channel-v2-v13-11-b3809e448aba@cisco.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cisco/enic/enic_main.c | 122 +++++++++++++------- 1 file changed, 82 insertions(+), 40 deletions(-) diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethernet/cisco/enic/enic_main.c index 537ed5ad7800..0baef7a120ec 100644 --- a/drivers/net/ethernet/cisco/enic/enic_main.c +++ b/drivers/net/ethernet/cisco/enic/enic_main.c @@ -2181,6 +2181,74 @@ static void enic_set_api_busy(struct enic *enic, bool busy) spin_unlock(&enic->enic_api_lock); } +/* The admin/MBOX channel exists on a V2 PF while SR-IOV is enabled and on + * every V2 VF. A reset wipes the admin WQ/RQ/CQ, so such devices must tear + * the channel down before the reset and re-establish it afterwards. + */ +static bool enic_has_admin_chan(struct enic *enic) +{ + return enic_is_sriov_vf_v2(enic) || + (enic_sriov_enabled(enic) && enic->vf_type == ENIC_VF_TYPE_V2); +} + +/* Re-establish the admin/MBOX channel after a reset has re-created the data + * path. Mirrors the relevant part of the probe / SR-IOV-enable sequence: + * reinitialise MBOX and reopen the channel, then for a VF re-run the PF + * handshake (the reset wiped the VF's admin QP, so the VF must register + * again), or for a PF re-push the current link state to registered VFs. + */ +static void enic_admin_chan_reopen(struct enic *enic) +{ + int err; + + /* Install the MBOX receive handler and reset the sequence number + * before opening the channel, so the handler is in place before the + * admin interrupt is unmasked and no early completion is dropped. + */ + enic_mbox_init(enic); + + /* A reset destroys the VF's local admin QP, so the VF can no longer + * rely on its previous registration. The PF may retain stale software + * registration state until the VF successfully registers again. + * Clear the local flag before reopening so a failed reopen or + * re-handshake cannot leave the VF believing it has a usable PF + * registration over a dead channel. + */ + if (enic_is_sriov_vf_v2(enic)) + enic->vf_registered = false; + + err = enic_admin_channel_open(enic); + if (err) { + netdev_err(enic->netdev, + "admin channel reopen after reset failed: %d\n", err); + return; + } + + if (enic_is_sriov_vf_v2(enic)) { + err = enic_mbox_vf_capability_check(enic); + if (err) { + netdev_err(enic->netdev, + "MBOX capability check after reset failed: %d\n", + err); + enic_admin_channel_close(enic); + return; + } + err = enic_mbox_vf_register(enic); + if (err) { + netdev_err(enic->netdev, + "MBOX VF re-registration after reset failed: %d\n", + err); + enic_admin_channel_close(enic); + } + } else { + /* The link came back up during enic_open() above while MBOX + * sends were still disabled (channel not yet reopened), so that + * link-notify was dropped. Re-push current link state now. + */ + schedule_work(&enic->link_notify_work); + } +} + static void enic_reset(struct work_struct *work) { struct enic *enic = container_of(work, struct enic, reset); @@ -2199,8 +2267,7 @@ static void enic_reset(struct work_struct *work) * DMAs from the about-to-be-reset rings) and frees the admin resources * so they are cleanly re-allocated afterwards. */ - if (enic_sriov_enabled(enic) && - enic->vf_type == ENIC_VF_TYPE_V2) + if (enic_has_admin_chan(enic)) enic_admin_channel_close(enic); enic_stop(enic->netdev); @@ -2214,25 +2281,13 @@ static void enic_reset(struct work_struct *work) enic_open(enic->netdev); - /* Re-establish the admin/MBOX channel after the data path is back up, - * mirroring the SR-IOV enable path (channel open + mbox init). The - * channel was fully torn down by enic_admin_channel_close() above. + /* Re-establish the admin/MBOX channel after the data path is back up. + * It was fully torn down by enic_admin_channel_close() above; + * enic_admin_chan_reopen() reopens it and, for a PF re-pushes link + * state, or for a VF re-runs the probe-time PF handshake. */ - if (enic_sriov_enabled(enic) && - enic->vf_type == ENIC_VF_TYPE_V2) { - if (enic_admin_channel_open(enic)) { - netdev_err(enic->netdev, - "admin channel reopen after reset failed\n"); - } else { - enic_mbox_init(enic); - /* The link came back up during enic_open() above - * while MBOX sends were still disabled (channel not - * yet reopened), so that link-notify was dropped. - * Re-push current link state to registered VFs now. - */ - schedule_work(&enic->link_notify_work); - } - } + if (enic_has_admin_chan(enic)) + enic_admin_chan_reopen(enic); /* Allow infiniband to fiddle with the device again */ enic_set_api_busy(enic, false); @@ -2255,8 +2310,7 @@ static void enic_tx_hang_reset(struct work_struct *work) * the same reason as the soft reset path: stop the admin QP and free * the admin resources before the hardware queues are wiped. */ - if (enic_sriov_enabled(enic) && - enic->vf_type == ENIC_VF_TYPE_V2) + if (enic_has_admin_chan(enic)) enic_admin_channel_close(enic); enic_dev_hang_notify(enic); @@ -2271,25 +2325,13 @@ static void enic_tx_hang_reset(struct work_struct *work) enic_open(enic->netdev); - /* Re-establish the admin/MBOX channel after the data path is back up, - * mirroring the SR-IOV enable path (channel open + mbox init). The - * channel was fully torn down by enic_admin_channel_close() above. + /* Re-establish the admin/MBOX channel after the data path is back up. + * It was fully torn down by enic_admin_channel_close() above; + * enic_admin_chan_reopen() reopens it and, for a PF re-pushes link + * state, or for a VF re-runs the probe-time PF handshake. */ - if (enic_sriov_enabled(enic) && - enic->vf_type == ENIC_VF_TYPE_V2) { - if (enic_admin_channel_open(enic)) { - netdev_err(enic->netdev, - "admin channel reopen after reset failed\n"); - } else { - enic_mbox_init(enic); - /* The link came back up during enic_open() above - * while MBOX sends were still disabled (channel not - * yet reopened), so that link-notify was dropped. - * Re-push current link state to registered VFs now. - */ - schedule_work(&enic->link_notify_work); - } - } + if (enic_has_admin_chan(enic)) + enic_admin_chan_reopen(enic); /* Allow infiniband to fiddle with the device again */ enic_set_api_busy(enic, false); From 9466ef3ec972bee926731a766f73533dec590065 Mon Sep 17 00:00:00 2001 From: Maximilian Immanuel Brandtner Date: Thu, 13 Aug 2026 14:09:44 +0200 Subject: [PATCH 1388/1433] tls: fix RX desync on overlapping skbs The TCP receive queue can hold adjacent skbs whose sequence ranges overlap. The tls fast-path reads the record header with skb_copy_bits() by byte offset, which assumes skbs do not overlap, so a header split across the overlap is misread and the connection aborts (-EMSGSIZE/-EINVAL). tls_strp_check_queue_ok() detects such overlaps but only ran after the header was parsed, never covering the header itself. Observed with parallel kTLS connections on: - ConnectX-7 + IPsec crypto offload + GRO - VirtIO (8 queues) + GRO Fixes: 84c61fe1a75b ("tls: rx: do not use the standard strparser") Signed-off-by: Maximilian Immanuel Brandtner Link: https://patch.msgid.link/20260813121337.3300688-1-maxbr@linux.ibm.com Signed-off-by: Jakub Kicinski --- net/tls/tls_strp.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/net/tls/tls_strp.c b/net/tls/tls_strp.c index 61b10c697ecc..6cc222008d95 100644 --- a/net/tls/tls_strp.c +++ b/net/tls/tls_strp.c @@ -430,9 +430,10 @@ static int tls_strp_read_copy(struct tls_strparser *strp, bool qshort) return 0; } -static bool tls_strp_check_queue_ok(struct tls_strparser *strp) +static bool tls_strp_check_queue_ok(struct tls_strparser *strp, + unsigned int len) { - unsigned int len = strp->stm.offset + strp->stm.full_len; + unsigned int remaining = strp->stm.offset + len; struct sk_buff *first, *skb; u32 seq; @@ -443,9 +444,9 @@ static bool tls_strp_check_queue_ok(struct tls_strparser *strp) /* Make sure there's no duplicate data in the queue, * and the decrypted status matches. */ - while (skb->len < len) { + while (skb->len < remaining) { seq += skb->len; - len -= skb->len; + remaining -= skb->len; skb = skb->next; if (TCP_SKB_CB(skb)->seq != seq) @@ -525,6 +526,11 @@ static int tls_strp_read_sock(struct tls_strparser *strp) tls_strp_load_anchor_with_queue(strp, inq); if (!strp->stm.full_len) { + if (inq < TLS_HEADER_SIZE) + return tls_strp_read_copy(strp, true); + if (!tls_strp_check_queue_ok(strp, TLS_HEADER_SIZE)) + return tls_strp_read_copy(strp, false); + sz = tls_rx_msg_size(strp, strp->anchor); if (sz < 0) return sz; @@ -535,7 +541,7 @@ static int tls_strp_read_sock(struct tls_strparser *strp) return tls_strp_read_copy(strp, true); } - if (!tls_strp_check_queue_ok(strp)) + if (!tls_strp_check_queue_ok(strp, strp->stm.full_len)) return tls_strp_read_copy(strp, false); WRITE_ONCE(strp->msg_ready, 1); From de1c489b57c11fabc766aeb6777ed5b0038fa3bb Mon Sep 17 00:00:00 2001 From: Jori Koolstra Date: Thu, 13 Aug 2026 12:28:15 -0400 Subject: [PATCH 1389/1433] net: af_unix: enable custom setsockopt for all socket types unix_setsockopt() and the SOCK_CUSTOM_SOCKOPT flag were only wired up for SOCK_STREAM (introduced along with the stream-only SO_INQ). Consequently custom AF_UNIX options are unreachable on SOCK_DGRAM and SOCK_SEQPACKET: those setsockopt() calls bypass unix_setsockopt() and fall through to the generic sock_setsockopt(), failing with -ENOPROTOOPT. Set SOCK_CUSTOM_SOCKOPT for every AF_UNIX socket type in unix_create(), and also for accepted sockets in unix_accept() (reachable for stream and seqpacket). This is a prerequisite for making SO_RIGHTS_NOTRUNC settable on all AF_UNIX socket types. Signed-off-by: Jori Koolstra Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260813162818.149248-2-jkoolstra@xs4all.nl Signed-off-by: Jakub Kicinski --- net/unix/af_unix.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index 10ed9421e43a..51cbf920130d 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -950,7 +950,7 @@ static int unix_setsockopt(struct socket *sock, int level, int optname, switch (optname) { case SO_INQ: if (sk->sk_type != SOCK_STREAM) - return -EINVAL; + return -ENOPROTOOPT; if (val > 1 || val < 0) return -EINVAL; @@ -1006,6 +1006,7 @@ static const struct proto_ops unix_dgram_ops = { #endif .listen = sock_no_listen, .shutdown = unix_shutdown, + .setsockopt = unix_setsockopt, .sendmsg = unix_dgram_sendmsg, .read_skb = unix_read_skb, .recvmsg = unix_dgram_recvmsg, @@ -1030,6 +1031,7 @@ static const struct proto_ops unix_seqpacket_ops = { #endif .listen = unix_listen, .shutdown = unix_shutdown, + .setsockopt = unix_setsockopt, .sendmsg = unix_seqpacket_sendmsg, .recvmsg = unix_seqpacket_recvmsg, .mmap = sock_no_mmap, @@ -1143,9 +1145,10 @@ static int unix_create(struct net *net, struct socket *sock, int protocol, if (protocol && protocol != PF_UNIX) return -EPROTONOSUPPORT; + set_bit(SOCK_CUSTOM_SOCKOPT, &sock->flags); + switch (sock->type) { case SOCK_STREAM: - set_bit(SOCK_CUSTOM_SOCKOPT, &sock->flags); sock->ops = &unix_stream_ops; break; /* @@ -1865,8 +1868,7 @@ static int unix_accept(struct socket *sock, struct socket *newsock, skb_free_datagram(sk, skb); wake_up_interruptible(&unix_sk(sk)->peer_wait); - if (tsk->sk_type == SOCK_STREAM) - set_bit(SOCK_CUSTOM_SOCKOPT, &newsock->flags); + set_bit(SOCK_CUSTOM_SOCKOPT, &newsock->flags); /* attach accepted sock to socket */ unix_state_lock(tsk); From 48b84acc5e18655f2a02fe2a043330d03f939ffe Mon Sep 17 00:00:00 2001 From: Jori Koolstra Date: Thu, 13 Aug 2026 12:28:16 -0400 Subject: [PATCH 1390/1433] net: scm: move scm_detach_fds() from common path to scm_recv_unix() scm->fp can only be set when using UNIX sockets, therefore we should move it out of the common path __scm_recv_common() into scm_recv_unix(). Reviewed-by: Kuniyuki Iwashima Signed-off-by: Jori Koolstra Link: https://patch.msgid.link/20260813162818.149248-3-jkoolstra@xs4all.nl Signed-off-by: Jakub Kicinski --- net/core/scm.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/core/scm.c b/net/core/scm.c index eec13f50ecaf..a73b1eb30fd2 100644 --- a/net/core/scm.c +++ b/net/core/scm.c @@ -523,9 +523,6 @@ static bool __scm_recv_common(struct sock *sk, struct msghdr *msg, scm_passec(sk, msg, scm); - if (scm->fp) - scm_detach_fds(msg, scm); - return true; } @@ -545,6 +542,9 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg, if (!__scm_recv_common(sock->sk, msg, scm, flags)) return; + if (scm->fp) + scm_detach_fds(msg, scm); + if (sock->sk->sk_scm_pidfd) scm_pidfd_recv(msg, scm); From fd8756fa1487577535d6294ae7cc67888b19ae2a Mon Sep 17 00:00:00 2001 From: Jori Koolstra Date: Thu, 13 Aug 2026 12:28:17 -0400 Subject: [PATCH 1391/1433] net: af_unix: useful handling of LSM denials on SCM_RIGHTS Right now if some LSM such as Smack denies an AF_UNIX socket peer to receive an SCM_RIGHTS fd, the SCM_RIGHTS fd array will be cut short at that point, and MSG_CTRUNC is set on return of recvmsg(). This is highly problematic behaviour, because it leaves the receiver wondering what happened. As per man page MSG_CTRUNC is supposed to indicate that the control buffer was sized too short, but suddenly a permission error might result in the exact same flag being set. Moreover, the receiver has no chance to determine how many fds got originally sent and how many were suppressed.[1] Add a SO_RIGHTS_NOTRUNC option to UNIX sockets to enable more useful handling of LSM denials when receiving SCM_RIGHTS messages: instead of truncating the message at the first blocked fd, keep every fd slot and store the LSM errno in the blocked slot. The socket option is inherited by the child accept() socket if set on the listen() socket. [1]: https://github.com/uapi-group/kernel-features#useful-handling-of-lsm-denials-on-scm_rights Reviewed-by: Christian Brauner (Amutable) Signed-off-by: Jori Koolstra Link: https://patch.msgid.link/20260813162818.149248-4-jkoolstra@xs4all.nl Signed-off-by: Jakub Kicinski --- arch/alpha/include/uapi/asm/socket.h | 2 ++ arch/mips/include/uapi/asm/socket.h | 2 ++ arch/parisc/include/uapi/asm/socket.h | 2 ++ arch/sparc/include/uapi/asm/socket.h | 2 ++ include/net/af_unix.h | 1 + include/net/scm.h | 13 +++------ include/uapi/asm-generic/socket.h | 2 ++ net/compat.c | 4 +-- net/core/scm.c | 38 +++++++++++++++++++++++---- net/unix/af_unix.c | 14 ++++++++-- 10 files changed, 62 insertions(+), 18 deletions(-) diff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h index 5ef57f88df6b..946a5fad2691 100644 --- a/arch/alpha/include/uapi/asm/socket.h +++ b/arch/alpha/include/uapi/asm/socket.h @@ -155,6 +155,8 @@ #define SO_INQ 84 #define SCM_INQ SO_INQ +#define SO_RIGHTS_NOTRUNC 85 + #if !defined(__KERNEL__) #if __BITS_PER_LONG == 64 diff --git a/arch/mips/include/uapi/asm/socket.h b/arch/mips/include/uapi/asm/socket.h index 72fb1b006da9..f1641dde135f 100644 --- a/arch/mips/include/uapi/asm/socket.h +++ b/arch/mips/include/uapi/asm/socket.h @@ -166,6 +166,8 @@ #define SO_INQ 84 #define SCM_INQ SO_INQ +#define SO_RIGHTS_NOTRUNC 85 + #if !defined(__KERNEL__) #if __BITS_PER_LONG == 64 diff --git a/arch/parisc/include/uapi/asm/socket.h b/arch/parisc/include/uapi/asm/socket.h index c16ec36dfee6..f3a3815c7dc2 100644 --- a/arch/parisc/include/uapi/asm/socket.h +++ b/arch/parisc/include/uapi/asm/socket.h @@ -147,6 +147,8 @@ #define SO_INQ 0x4052 #define SCM_INQ SO_INQ +#define SO_RIGHTS_NOTRUNC 0x4053 + #if !defined(__KERNEL__) #if __BITS_PER_LONG == 64 diff --git a/arch/sparc/include/uapi/asm/socket.h b/arch/sparc/include/uapi/asm/socket.h index 71befa109e1c..7907f3b1f0ee 100644 --- a/arch/sparc/include/uapi/asm/socket.h +++ b/arch/sparc/include/uapi/asm/socket.h @@ -148,6 +148,8 @@ #define SO_INQ 0x005d #define SCM_INQ SO_INQ +#define SO_RIGHTS_NOTRUNC 0x005e + #if !defined(__KERNEL__) diff --git a/include/net/af_unix.h b/include/net/af_unix.h index 34f53dde65ce..bb1b3dee02e8 100644 --- a/include/net/af_unix.h +++ b/include/net/af_unix.h @@ -49,6 +49,7 @@ struct unix_sock { struct scm_stat scm_stat; int inq_len; bool recvmsg_inq; + bool scm_rights_notrunc; #if IS_ENABLED(CONFIG_AF_UNIX_OOB) struct sk_buff *oob_skb; #endif diff --git a/include/net/scm.h b/include/net/scm.h index c52519669349..86ae6bc109ec 100644 --- a/include/net/scm.h +++ b/include/net/scm.h @@ -50,8 +50,8 @@ struct scm_cookie { #endif }; -void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm); -void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm); +void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm, bool notrunc); +void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, bool notrunc); int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *scm); void __scm_destroy(struct scm_cookie *scm); struct scm_fp_list *scm_fp_dup(struct scm_fp_list *fpl); @@ -107,13 +107,8 @@ void scm_recv(struct socket *sock, struct msghdr *msg, void scm_recv_unix(struct socket *sock, struct msghdr *msg, struct scm_cookie *scm, int flags); -static inline int scm_recv_one_fd(struct file *f, int __user *ufd, - unsigned int flags) -{ - if (!ufd) - return -EFAULT; - return receive_fd(f, ufd, flags); -} +int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags, + bool notrunc); #endif /* __LINUX_NET_SCM_H */ diff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h index 53b5a8c002b1..84ea7b92936e 100644 --- a/include/uapi/asm-generic/socket.h +++ b/include/uapi/asm-generic/socket.h @@ -150,6 +150,8 @@ #define SO_INQ 84 #define SCM_INQ SO_INQ +#define SO_RIGHTS_NOTRUNC 85 + #if !defined(__KERNEL__) #if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__)) diff --git a/net/compat.c b/net/compat.c index d68cf9c3aad5..6bdf4a2c9077 100644 --- a/net/compat.c +++ b/net/compat.c @@ -286,7 +286,7 @@ static int scm_max_fds_compat(struct msghdr *msg) return (msg->msg_controllen - sizeof(struct compat_cmsghdr)) / sizeof(int); } -void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm) +void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, bool notrunc) { struct compat_cmsghdr __user *cm = (struct compat_cmsghdr __user *)msg->msg_control_user; @@ -296,7 +296,7 @@ void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm) int err = 0, i; for (i = 0; i < fdmax; i++) { - err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags); + err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc); if (err < 0) break; } diff --git a/net/core/scm.c b/net/core/scm.c index a73b1eb30fd2..f0d44ecdb11f 100644 --- a/net/core/scm.c +++ b/net/core/scm.c @@ -351,7 +351,31 @@ static int scm_max_fds(struct msghdr *msg) return (msg->msg_controllen - sizeof(struct cmsghdr)) / sizeof(int); } -void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm) +int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags, + bool notrunc) +{ + int error; + + if (!ufd) + return -EFAULT; + + error = security_file_receive(f); + if (error) + return notrunc ? put_user(error, ufd) : error; + + FD_PREPARE(fdf, flags, get_file(f)); + if (fdf.err) + return fdf.err; + + error = put_user(fd_prepare_fd(fdf), ufd); + if (error) + return error; + + __receive_sock(fd_prepare_file(fdf)); + return fd_publish(fdf); +} + +void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm, bool notrunc) { struct cmsghdr __user *cm = (__force struct cmsghdr __user *)msg->msg_control_user; @@ -365,12 +389,12 @@ void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm) return; if (msg->msg_flags & MSG_CMSG_COMPAT) { - scm_detach_fds_compat(msg, scm); + scm_detach_fds_compat(msg, scm, notrunc); return; } for (i = 0; i < fdmax; i++) { - err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags); + err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc); if (err < 0) break; } @@ -542,8 +566,12 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg, if (!__scm_recv_common(sock->sk, msg, scm, flags)) return; - if (scm->fp) - scm_detach_fds(msg, scm); + if (scm->fp) { + struct unix_sock *u; + + u = unix_sk(sock->sk); + scm_detach_fds(msg, scm, READ_ONCE(u->scm_rights_notrunc)); + } if (sock->sk->sk_scm_pidfd) scm_pidfd_recv(msg, scm); diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index 51cbf920130d..5c0549e6784f 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -922,6 +922,7 @@ static bool unix_custom_sockopt(int optname) { switch (optname) { case SO_INQ: + case SO_RIGHTS_NOTRUNC: return true; default: return false; @@ -957,6 +958,14 @@ static int unix_setsockopt(struct socket *sock, int level, int optname, WRITE_ONCE(u->recvmsg_inq, val); break; + + case SO_RIGHTS_NOTRUNC: + if (val > 1 || val < 0) + return -EINVAL; + + WRITE_ONCE(u->scm_rights_notrunc, val); + break; + default: return -ENOPROTOOPT; } @@ -1746,9 +1755,10 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr_unsized *uad init_peercred(newsk, &peercred); newu = unix_sk(newsk); - newu->listener = other; - RCU_INIT_POINTER(newsk->sk_wq, &newu->peer_wq); otheru = unix_sk(other); + newu->listener = other; + newu->scm_rights_notrunc = READ_ONCE(otheru->scm_rights_notrunc); + RCU_INIT_POINTER(newsk->sk_wq, &newu->peer_wq); /* copy address information from listening to new sock * From 5d513ce19de963c7e36904c98d7b02ecd16a30a1 Mon Sep 17 00:00:00 2001 From: Jori Koolstra Date: Fri, 14 Aug 2026 13:28:06 -0400 Subject: [PATCH 1392/1433] selftest: Add tests for useful handling of LSM denials on SCM_RIGHTS Tests SCM_RIGHTS fd passing on a socket with the new socket option SO_RIGHTS_NOTRUNC turned on. To hook into the security_file_receive() call, BPF is used. The BPF program shares a hashmap with userspace that lists the inos to be blocked (of the receiver tgid). Signed-off-by: Jori Koolstra Link: https://patch.msgid.link/20260814172806.158954-1-jkoolstra@xs4all.nl Signed-off-by: Jakub Kicinski --- .../testing/selftests/net/af_unix/.gitignore | 2 + tools/testing/selftests/net/af_unix/Makefile | 8 + tools/testing/selftests/net/af_unix/config | 7 + .../net/af_unix/scm_rights_denial_lsm.bpf.c | 36 +++ .../net/af_unix/scm_rights_denial_lsm.c | 292 ++++++++++++++++++ 5 files changed, 345 insertions(+) create mode 100644 tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.bpf.c create mode 100644 tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c diff --git a/tools/testing/selftests/net/af_unix/.gitignore b/tools/testing/selftests/net/af_unix/.gitignore index 973176644103..954f0958dd03 100644 --- a/tools/testing/selftests/net/af_unix/.gitignore +++ b/tools/testing/selftests/net/af_unix/.gitignore @@ -3,6 +3,8 @@ msg_oob scm_inq scm_pidfd scm_rights +scm_rights_denial_lsm +scm_rights_denial_lsm.bpf.o so_peek_off unix_connect unix_connreset diff --git a/tools/testing/selftests/net/af_unix/Makefile b/tools/testing/selftests/net/af_unix/Makefile index 57d159803a3a..a66f10fb0c23 100644 --- a/tools/testing/selftests/net/af_unix/Makefile +++ b/tools/testing/selftests/net/af_unix/Makefile @@ -11,10 +11,18 @@ TEST_GEN_PROGS := \ scm_inq \ scm_pidfd \ scm_rights \ + scm_rights_denial_lsm \ so_peek_off \ unix_connect \ unix_connreset \ unix_listen \ # end of TEST_GEN_PROGS +TEST_GEN_FILES := scm_rights_denial_lsm.bpf.o + include ../../lib.mk +include ../bpf.mk + +$(OUTPUT)/scm_rights_denial_lsm: $(BPFOBJ) +$(OUTPUT)/scm_rights_denial_lsm: CFLAGS += -I$(SCRATCH_DIR)/include +$(OUTPUT)/scm_rights_denial_lsm: LDLIBS += -lelf -lz diff --git a/tools/testing/selftests/net/af_unix/config b/tools/testing/selftests/net/af_unix/config index 41dbb03c747e..46450fea8407 100644 --- a/tools/testing/selftests/net/af_unix/config +++ b/tools/testing/selftests/net/af_unix/config @@ -1,4 +1,11 @@ CONFIG_AF_UNIX_OOB=y +CONFIG_BPF=y +CONFIG_BPF_EVENTS=y +CONFIG_BPF_JIT=y +CONFIG_BPF_LSM=y +CONFIG_BPF_SYSCALL=y +CONFIG_DEBUG_INFO_BTF=y +CONFIG_SECURITY=y CONFIG_UNIX=y CONFIG_UNIX_DIAG=m CONFIG_USER_NS=y diff --git a/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.bpf.c b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.bpf.c new file mode 100644 index 000000000000..4f2414465bfd --- /dev/null +++ b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.bpf.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include + +char _license[] SEC("license") = "GPL"; + +struct inode { + unsigned long i_ino; +} __attribute__((preserve_access_index)); + +struct file { + struct inode *f_inode; +} __attribute__((preserve_access_index)); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 16); + __type(key, __u64); /* inode number */ + __type(value, __u32); /* tgid of the receiver being tested */ +} denied_inodes SEC(".maps"); + +SEC("lsm/file_receive") +int BPF_PROG(scm_rights_deny, struct file *file) +{ + __u32 tgid = bpf_get_current_pid_tgid() >> 32; + __u64 ino = file->f_inode->i_ino; + __u32 *owner; + + owner = bpf_map_lookup_elem(&denied_inodes, &ino); + if (owner && *owner == tgid) + return -EPERM; + + return 0; +} diff --git a/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c new file mode 100644 index 000000000000..55c7ecdbb5fe --- /dev/null +++ b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "kselftest_harness.h" + +#ifndef SO_RIGHTS_NOTRUNC +#define SO_RIGHTS_NOTRUNC 85 +#endif + +#define NR_FILES 2 + +/* Per-file content, so a received fd can be matched to the file sent */ +#define SECRET(n) "secret %d", (n) + +/* Indices into the socketpair */ +#define SK_SENDER 0 +#define SK_RECEIVER 1 + +FIXTURE(scm_rights_denial_bpf) +{ + struct bpf_object *obj; + struct bpf_link *link; + int map_fd; + int sk[2]; + int files[NR_FILES]; + __u64 inos[NR_FILES]; + char paths[NR_FILES][64]; +}; + +FIXTURE_VARIANT(scm_rights_denial_bpf) +{ + int sock_type; +}; + +FIXTURE_VARIANT_ADD(scm_rights_denial_bpf, stream) +{ + .sock_type = SOCK_STREAM, +}; + +FIXTURE_VARIANT_ADD(scm_rights_denial_bpf, dgram) +{ + .sock_type = SOCK_DGRAM, +}; + +FIXTURE_VARIANT_ADD(scm_rights_denial_bpf, seqpacket) +{ + .sock_type = SOCK_SEQPACKET, +}; + +FIXTURE_SETUP(scm_rights_denial_bpf) +{ + struct bpf_program *prog; + char lsms[256] = {}; + int i, fd; + + if (geteuid() != 0) + SKIP(return, "requires root"); + + fd = open("/sys/kernel/security/lsm", O_RDONLY); + ASSERT_GE(fd, 0); + ASSERT_LT(0, read(fd, lsms, sizeof(lsms) - 1)); + close(fd); + + if (!strstr(lsms, "bpf")) + SKIP(return, "BPF LSM not active (boot with lsm=...,bpf)"); + + self->obj = bpf_object__open_file("scm_rights_denial_lsm.bpf.o", NULL); + ASSERT_NE(NULL, self->obj); + ASSERT_EQ(0, bpf_object__load(self->obj)); + + prog = bpf_object__find_program_by_name(self->obj, "scm_rights_deny"); + ASSERT_NE(NULL, prog); + + self->link = bpf_program__attach_lsm(prog); + ASSERT_NE(NULL, self->link); + + self->map_fd = bpf_object__find_map_fd_by_name(self->obj, + "denied_inodes"); + ASSERT_GE(self->map_fd, 0); + + ASSERT_EQ(0, socketpair(AF_UNIX, variant->sock_type, 0, self->sk)); + + for (i = 0; i < NR_FILES; i++) { + struct stat st; + + snprintf(self->paths[i], sizeof(self->paths[i]), + "/tmp/scm_rights_denial_bpf.%d.XXXXXX", i); + self->files[i] = mkstemp(self->paths[i]); + ASSERT_GE(self->files[i], 0); + + ASSERT_LT(0, dprintf(self->files[i], SECRET(i))); + + ASSERT_EQ(0, fstat(self->files[i], &st)); + self->inos[i] = st.st_ino; + } +} + +FIXTURE_TEARDOWN(scm_rights_denial_bpf) +{ + bpf_link__destroy(self->link); + bpf_object__close(self->obj); + + for (int i = 0; i < NR_FILES; i++) { + if (self->files[i] >= 0) { + close(self->files[i]); + unlink(self->paths[i]); + } + } + + close(self->sk[SK_SENDER]); + close(self->sk[SK_RECEIVER]); +} + +static int deny_inode(int map_fd, __u64 ino) +{ + __u32 tgid = getpid(); + + return bpf_map_update_elem(map_fd, &ino, &tgid, BPF_ANY); +} + +static int set_notrunc(int sk) +{ + int one = 1; + + return setsockopt(sk, SOL_SOCKET, SO_RIGHTS_NOTRUNC, + &one, sizeof(one)); +} + +static int send_fds(int sk, int *fds, int n) +{ + char ctrl[CMSG_SPACE(NR_FILES * sizeof(int))] = {}; + char data = 'x'; + struct iovec iov = { + .iov_base = &data, + .iov_len = sizeof(data), + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = ctrl, + .msg_controllen = CMSG_SPACE(n * sizeof(int)), + }; + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + int ret; + + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(n * sizeof(int)); + memcpy(CMSG_DATA(cmsg), fds, n * sizeof(int)); + + ret = sendmsg(sk, &msg, 0); + if (ret != 1) + return -1; + + return 0; +} + +static int recv_fd_slots(int sk, int *slots, int *msg_flags) +{ + int nr_slots; + char ctrl[CMSG_SPACE(NR_FILES * sizeof(int))]; + char data; + struct iovec iov = { + .iov_base = &data, + .iov_len = sizeof(data), + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = ctrl, + .msg_controllen = sizeof(ctrl), + }; + struct cmsghdr *cmsg; + + if (recvmsg(sk, &msg, 0) < 0) + return -1; + + *msg_flags = msg.msg_flags; + + cmsg = CMSG_FIRSTHDR(&msg); + if (!cmsg) + return 0; + + nr_slots = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + memcpy(slots, CMSG_DATA(cmsg), nr_slots * sizeof(int)); + + return nr_slots; +} + +/* Prove a received fd works by reading back the file's content. */ +static int check_secret(int fd, int idx) +{ + char want[32], got[32] = {}; + + snprintf(want, sizeof(want), SECRET(idx)); + if (pread(fd, got, sizeof(got) - 1, 0) < 0) + return -1; + + return strcmp(want, got); +} + +TEST_F(scm_rights_denial_bpf, all_allowed) +{ + int slots[NR_FILES], nr_slots, flags; + + ASSERT_EQ(0, set_notrunc(self->sk[SK_RECEIVER])); + ASSERT_EQ(0, send_fds(self->sk[SK_SENDER], self->files, NR_FILES)); + nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags); + + ASSERT_EQ(NR_FILES, nr_slots); + EXPECT_EQ(0, flags & MSG_CTRUNC); + + for (int i = 0; i < nr_slots; i++) { + ASSERT_GE(slots[i], 0); + EXPECT_EQ(0, check_secret(slots[i], i)); + close(slots[i]); + } +} + +TEST_F(scm_rights_denial_bpf, first_denied) +{ + int slots[NR_FILES], nr_slots, flags; + + ASSERT_EQ(0, deny_inode(self->map_fd, self->inos[0])); + + ASSERT_EQ(0, set_notrunc(self->sk[SK_RECEIVER])); + ASSERT_EQ(0, send_fds(self->sk[SK_SENDER], self->files, NR_FILES)); + nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags); + + ASSERT_EQ(NR_FILES, nr_slots); + EXPECT_EQ(0, flags & MSG_CTRUNC); + + EXPECT_EQ(-EPERM, slots[0]); + for (int i = 1; i < nr_slots; i++) { + ASSERT_GE(slots[i], 0); + EXPECT_EQ(0, check_secret(slots[i], i)); + close(slots[i]); + } +} + +TEST_F(scm_rights_denial_bpf, all_denied) +{ + int slots[NR_FILES], nr_slots, flags, i; + + for (i = 0; i < NR_FILES; i++) + ASSERT_EQ(0, deny_inode(self->map_fd, self->inos[i])); + + ASSERT_EQ(0, set_notrunc(self->sk[SK_RECEIVER])); + ASSERT_EQ(0, send_fds(self->sk[SK_SENDER], self->files, NR_FILES)); + nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags); + + ASSERT_EQ(NR_FILES, nr_slots); + EXPECT_EQ(0, flags & MSG_CTRUNC); + + for (i = 0; i < nr_slots; i++) + EXPECT_EQ(-EPERM, slots[i]); +} + +TEST_F(scm_rights_denial_bpf, denied_without_notrunc) +{ + int slots[NR_FILES], nr_slots, flags; + + /* + * Baseline behaviour without SO_RIGHTS_NOTRUNC: the fd array is + * truncated at the first denied fd and MSG_CTRUNC is set. + */ + ASSERT_EQ(0, deny_inode(self->map_fd, self->inos[1])); + + ASSERT_EQ(0, send_fds(self->sk[SK_SENDER], self->files, NR_FILES)); + nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags); + + ASSERT_EQ(1, nr_slots); + EXPECT_NE(0, flags & MSG_CTRUNC); + + ASSERT_GE(slots[0], 0); + EXPECT_EQ(0, check_secret(slots[0], 0)); + close(slots[0]); +} + +TEST_HARNESS_MAIN From fb58b6a696b30bcbfbe0cfc0a91b19c816a955fc Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Fri, 14 Aug 2026 13:01:02 +0200 Subject: [PATCH 1393/1433] net: dsa: realtek: use gpiod_set_value_cansleep for reset GPIO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rtl83xx_reset_assert() and rtl83xx_reset_deassert() are only called from the probe path, which may sleep and is not timing-critical. When the reset GPIO is provided by a sleeping controller such as an I2C I/O expander, gpiod_set_value() warns: WARNING: drivers/gpio/gpiolib.c:4030 at gpiod_set_value+0x44/0x80, CPU#1: kworker/u16:4/61 Hardware name: B&O MAP CA33 Rev f (UNKNOWN) (DT) Workqueue: events_unbound deferred_probe_work_func pc : gpiod_set_value+0x44/0x80 lr : rtl83xx_probe+0x1d8/0x3a0 Call trace: gpiod_set_value+0x44/0x80 (P) rtl83xx_probe+0x1d8/0x3a0 realtek_mdio_probe+0x24/0xa0 mdio_probe+0x38/0x78 really_probe+0xc4/0x3e0 __driver_probe_device+0x15c/0x1b8 driver_probe_device+0xb4/0x120 __device_attach_driver+0xb8/0x1a0 bus_for_each_drv+0x88/0xf0 __device_attach+0xa0/0x1d8 device_initial_probe+0x54/0x68 bus_probe_device+0x38/0xa0 deferred_probe_work_func+0xb8/0x120 process_one_work+0x184/0x4e8 worker_thread+0x188/0x308 kthread+0x130/0x150 ret_from_fork+0x10/0x20 Switch both helpers to gpiod_set_value_cansleep() so such a reset GPIO can be used without triggering the warning. The reset GPIO has been driven with the non-sleeping gpiod_set_value() since the driver was added in v4.19. The call has since been refactored across several files - from realtek-smi.c / realtek-mdio.c into the common rtl83xx.c module and then into the rtl83xx_reset_assert() and rtl83xx_reset_deassert() helpers (both in v6.9). This patch therefore applies as-is only to kernels that carry those helpers (v6.9+); older stable kernels need the same gpiod_set_value_cansleep() conversion at the corresponding open-coded call sites. Fixes: d8652956cf37 ("net: dsa: realtek-smi: Add Realtek SMI driver") Cc: # 6.9.x Signed-off-by: Ahmad Fatoum Co-developed-by: Oleksij Rempel Signed-off-by: Oleksij Rempel Reviewed-by: Alvin Å ipraga Reviewed-by: Linus Walleij Reviewed-by: Luiz Angelo Daros de Luca Link: https://patch.msgid.link/20260814110102.2362246-1-o.rempel@pengutronix.de Signed-off-by: Jakub Kicinski --- drivers/net/dsa/realtek/rtl83xx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rtl83xx.c index 9dd50b20c000..d2e92bab55d2 100644 --- a/drivers/net/dsa/realtek/rtl83xx.c +++ b/drivers/net/dsa/realtek/rtl83xx.c @@ -321,7 +321,7 @@ void rtl83xx_reset_assert(struct realtek_priv *priv) "Failed to assert the switch reset control: %pe\n", ERR_PTR(ret)); - gpiod_set_value(priv->reset, true); + gpiod_set_value_cansleep(priv->reset, true); } void rtl83xx_reset_deassert(struct realtek_priv *priv) @@ -334,7 +334,7 @@ void rtl83xx_reset_deassert(struct realtek_priv *priv) "Failed to deassert the switch reset control: %pe\n", ERR_PTR(ret)); - gpiod_set_value(priv->reset, false); + gpiod_set_value_cansleep(priv->reset, false); } /** From 21040c7f931502070dcc66bb0f1aeed07dec032b Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Fri, 14 Aug 2026 17:16:40 +0300 Subject: [PATCH 1394/1433] net: bridge: vlan: fix inverted default vlan notification A notification should be emitted only when the vlan delete was successful and not otherwise. The proper check is if br/nbp_vlan_delete returned 0. Fixes: f545923b4a6b ("net: bridge: vlan: notify on vlan add/delete/change flags") Signed-off-by: Nikolay Aleksandrov Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260814141640.64958-1-razor@blackwall.org Signed-off-by: Jakub Kicinski --- net/bridge/br_vlan.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c index 31c1b2cf75d9..1e0e436629ec 100644 --- a/net/bridge/br_vlan.c +++ b/net/bridge/br_vlan.c @@ -1136,7 +1136,7 @@ int __br_vlan_set_default_pvid(struct net_bridge *br, u16 pvid, if (err) goto out; - if (br_vlan_delete(br, old_pvid)) + if (!br_vlan_delete(br, old_pvid)) br_vlan_notify(br, NULL, old_pvid, 0, RTM_DELVLAN); br_vlan_notify(br, NULL, pvid, 0, RTM_NEWVLAN); __set_bit(0, changed); @@ -1158,7 +1158,7 @@ int __br_vlan_set_default_pvid(struct net_bridge *br, u16 pvid, &vlchange, extack); if (err) goto err_port; - if (nbp_vlan_delete(p, old_pvid)) + if (!nbp_vlan_delete(p, old_pvid)) br_vlan_notify(br, p, old_pvid, 0, RTM_DELVLAN); br_vlan_notify(p->br, p, pvid, 0, RTM_NEWVLAN); __set_bit(p->port_no, changed); From 063aeece6053c48832c4d12cff39fb0645383d28 Mon Sep 17 00:00:00 2001 From: Harshitha Ramamurthy Date: Fri, 14 Aug 2026 02:13:51 +0000 Subject: [PATCH 1395/1433] gve: don't pass in unused parameter to gve_adminq_free Clean up gve_adminq_free to not take in an unused parameter. Reviewed-by: Willem de Bruijn Reviewed-by: Jordan Rhee Signed-off-by: Harshitha Ramamurthy Reviewed-by: Przemek Kitszel Link: https://patch.msgid.link/20260814021406.3044324-2-hramamurthy@google.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/google/gve/gve_adminq.c | 2 +- drivers/net/ethernet/google/gve/gve_adminq.h | 2 +- drivers/net/ethernet/google/gve/gve_main.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/google/gve/gve_adminq.c b/drivers/net/ethernet/google/gve/gve_adminq.c index 08587bf40ed4..70ffed8b52c3 100644 --- a/drivers/net/ethernet/google/gve/gve_adminq.c +++ b/drivers/net/ethernet/google/gve/gve_adminq.c @@ -385,7 +385,7 @@ void gve_adminq_release(struct gve_priv *priv) gve_clear_admin_queue_ok(priv); } -void gve_adminq_free(struct device *dev, struct gve_priv *priv) +void gve_adminq_free(struct gve_priv *priv) { if (!gve_get_admin_queue_ok(priv)) return; diff --git a/drivers/net/ethernet/google/gve/gve_adminq.h b/drivers/net/ethernet/google/gve/gve_adminq.h index 22a74b6aa17e..8e80f36116ec 100644 --- a/drivers/net/ethernet/google/gve/gve_adminq.h +++ b/drivers/net/ethernet/google/gve/gve_adminq.h @@ -620,7 +620,7 @@ union gve_adminq_command { static_assert(sizeof(union gve_adminq_command) == 64); int gve_adminq_alloc(struct device *dev, struct gve_priv *priv); -void gve_adminq_free(struct device *dev, struct gve_priv *priv); +void gve_adminq_free(struct gve_priv *priv); void gve_adminq_release(struct gve_priv *priv); int gve_adminq_describe_device(struct gve_priv *priv); int gve_adminq_configure_device_resources(struct gve_priv *priv, diff --git a/drivers/net/ethernet/google/gve/gve_main.c b/drivers/net/ethernet/google/gve/gve_main.c index e4d78ae52daf..30bf6df4ebc5 100644 --- a/drivers/net/ethernet/google/gve/gve_main.c +++ b/drivers/net/ethernet/google/gve/gve_main.c @@ -2506,14 +2506,14 @@ static int gve_init_priv(struct gve_priv *priv, bool skip_describe_device) bitmap_free(priv->xsk_pools); priv->xsk_pools = NULL; err: - gve_adminq_free(&priv->pdev->dev, priv); + gve_adminq_free(priv); return err; } static void gve_teardown_priv_resources(struct gve_priv *priv) { gve_teardown_device_resources(priv); - gve_adminq_free(&priv->pdev->dev, priv); + gve_adminq_free(priv); bitmap_free(priv->xsk_pools); priv->xsk_pools = NULL; } From 94afe5cebcd2bc32d774bfe603a8259680e9e870 Mon Sep 17 00:00:00 2001 From: Harshitha Ramamurthy Date: Fri, 14 Aug 2026 02:13:52 +0000 Subject: [PATCH 1396/1433] gve: refactor initialization with helper functions In the interest of commonizing code, refactor gve_probe() and gve_init_priv() with a few helper functions that can be expanded and utilized in upcoming patches that add the mailbox ABI to the driver. The helper functions are: - gve_set_num_ntfy_blks() - gve_set_num_queues() Reorder code to combine lines that accomplish a similar objective like setting defaults. Move setting HW-GRO and UDP GSO support out of an Adminq method into gve_init_priv(). These changes are just code movement, no functional change. Reviewed-by: Willem de Bruijn Reviewed-by: Jordan Rhee Signed-off-by: Harshitha Ramamurthy Reviewed-by: Przemek Kitszel Link: https://patch.msgid.link/20260814021406.3044324-3-hramamurthy@google.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/google/gve/gve_adminq.c | 49 ++++++++++++++--- drivers/net/ethernet/google/gve/gve_adminq.h | 2 + drivers/net/ethernet/google/gve/gve_main.c | 58 +++++++------------- 3 files changed, 62 insertions(+), 47 deletions(-) diff --git a/drivers/net/ethernet/google/gve/gve_adminq.c b/drivers/net/ethernet/google/gve/gve_adminq.c index 70ffed8b52c3..fe0dedfb25d7 100644 --- a/drivers/net/ethernet/google/gve/gve_adminq.c +++ b/drivers/net/ethernet/google/gve/gve_adminq.c @@ -1117,14 +1117,6 @@ int gve_adminq_describe_device(struct gve_priv *priv) gve_set_default_rss_sizes(priv); - /* DQO supports HW-GRO and UDP_GSO */ - if (gve_is_dqo(priv)) { - u64 additional_features = NETIF_F_GRO_HW | NETIF_F_GSO_UDP_L4; - - priv->dev->hw_features |= additional_features; - priv->dev->features |= additional_features; - } - priv->max_registered_pages = be64_to_cpu(descriptor->max_registered_pages); mtu = be16_to_cpu(descriptor->mtu); @@ -1600,3 +1592,44 @@ int gve_adminq_query_rss_config(struct gve_priv *priv, struct ethtool_rxfh_param dma_pool_free(priv->adminq_pool, descriptor, descriptor_bus); return err; } + +int gve_set_num_ntfy_blks(struct gve_priv *priv) +{ + int num_ntfy; + + num_ntfy = pci_msix_vec_count(priv->pdev); + if (num_ntfy <= 0) { + dev_err(&priv->pdev->dev, + "could not count MSI-x vectors: err=%d\n", num_ntfy); + return num_ntfy; + } else if (num_ntfy < GVE_MIN_MSIX) { + dev_err(&priv->pdev->dev, "gve needs at least %d MSI-x vectors, but only has %d\n", + GVE_MIN_MSIX, num_ntfy); + return -EINVAL; + } + + /* gvnic has one Notification Block per MSI-x vector, except for the + * management vector + */ + priv->num_ntfy_blks = (num_ntfy - 1) & ~0x1; + priv->mgmt_msix_idx = priv->num_ntfy_blks; + + return 0; +} + +void gve_set_num_queues(struct gve_priv *priv) +{ + priv->tx_cfg.max_queues = + min_t(int, priv->tx_cfg.max_queues, priv->num_ntfy_blks / 2); + priv->rx_cfg.max_queues = + min_t(int, priv->rx_cfg.max_queues, priv->num_ntfy_blks / 2); + + priv->tx_cfg.num_queues = priv->tx_cfg.max_queues; + priv->rx_cfg.num_queues = priv->rx_cfg.max_queues; + if (priv->default_num_queues > 0) { + priv->tx_cfg.num_queues = min_t(int, priv->default_num_queues, + priv->tx_cfg.num_queues); + priv->rx_cfg.num_queues = min_t(int, priv->default_num_queues, + priv->rx_cfg.num_queues); + } +} diff --git a/drivers/net/ethernet/google/gve/gve_adminq.h b/drivers/net/ethernet/google/gve/gve_adminq.h index 8e80f36116ec..82b52424a63f 100644 --- a/drivers/net/ethernet/google/gve/gve_adminq.h +++ b/drivers/net/ethernet/google/gve/gve_adminq.h @@ -656,5 +656,7 @@ int gve_adminq_report_nic_ts(struct gve_priv *priv, struct gve_ptype_lut; int gve_adminq_get_ptype_map_dqo(struct gve_priv *priv, struct gve_ptype_lut *ptype_lut); +int gve_set_num_ntfy_blks(struct gve_priv *priv); +void gve_set_num_queues(struct gve_priv *priv); #endif /* _GVE_ADMINQ_H */ diff --git a/drivers/net/ethernet/google/gve/gve_main.c b/drivers/net/ethernet/google/gve/gve_main.c index 30bf6df4ebc5..37e6205e2eef 100644 --- a/drivers/net/ethernet/google/gve/gve_main.c +++ b/drivers/net/ethernet/google/gve/gve_main.c @@ -2400,7 +2400,6 @@ static const struct xdp_metadata_ops gve_xdp_metadata_ops = { static int gve_init_priv(struct gve_priv *priv, bool skip_describe_device) { - int num_ntfy; int err; /* Set up the adminq */ @@ -2431,57 +2430,38 @@ static int gve_init_priv(struct gve_priv *priv, bool skip_describe_device) "Could not get device information: err=%d\n", err); goto err; } - priv->dev->mtu = priv->dev->max_mtu; - num_ntfy = pci_msix_vec_count(priv->pdev); - if (num_ntfy <= 0) { + + err = gve_set_num_ntfy_blks(priv); + if (err) { dev_err(&priv->pdev->dev, - "could not count MSI-x vectors: err=%d\n", num_ntfy); - err = num_ntfy; - goto err; - } else if (num_ntfy < GVE_MIN_MSIX) { - dev_err(&priv->pdev->dev, "gve needs at least %d MSI-x vectors, but only has %d\n", - GVE_MIN_MSIX, num_ntfy); - err = -EINVAL; + "Could not setup notify blocks: err=%d\n", err); goto err; } - /* Big TCP is only supported on DQO */ - if (!gve_is_gqi(priv)) - netif_set_tso_max_size(priv->dev, GVE_DQO_TX_MAX); - - priv->rx_copybreak = GVE_DEFAULT_RX_COPYBREAK; - /* gvnic has one Notification Block per MSI-x vector, except for the - * management vector - */ - priv->num_ntfy_blks = (num_ntfy - 1) & ~0x1; - priv->mgmt_msix_idx = priv->num_ntfy_blks; - priv->numa_node = dev_to_node(&priv->pdev->dev); - - priv->tx_cfg.max_queues = - min_t(int, priv->tx_cfg.max_queues, priv->num_ntfy_blks / 2); - priv->rx_cfg.max_queues = - min_t(int, priv->rx_cfg.max_queues, priv->num_ntfy_blks / 2); - - priv->tx_cfg.num_queues = priv->tx_cfg.max_queues; - priv->rx_cfg.num_queues = priv->rx_cfg.max_queues; - if (priv->default_num_queues > 0) { - priv->tx_cfg.num_queues = min_t(int, priv->default_num_queues, - priv->tx_cfg.num_queues); - priv->rx_cfg.num_queues = min_t(int, priv->default_num_queues, - priv->rx_cfg.num_queues); - } - priv->tx_cfg.num_xdp_queues = 0; - + gve_set_num_queues(priv); dev_info(&priv->pdev->dev, "TX queues %d, RX queues %d\n", priv->tx_cfg.num_queues, priv->rx_cfg.num_queues); dev_info(&priv->pdev->dev, "Max TX queues %d, Max RX queues %d\n", priv->tx_cfg.max_queues, priv->rx_cfg.max_queues); - if (!gve_is_gqi(priv)) { + if (gve_is_dqo(priv)) { + /* DQO supports HW-GRO and UDP_GSO */ + u64 additional_features = NETIF_F_GRO_HW | NETIF_F_GSO_UDP_L4; + + priv->dev->hw_features |= additional_features; + priv->dev->features |= additional_features; + priv->tx_coalesce_usecs = GVE_TX_IRQ_RATELIMIT_US_DQO; priv->rx_coalesce_usecs = GVE_RX_IRQ_RATELIMIT_US_DQO; + + /* Big TCP is only supported on DQO */ + netif_set_tso_max_size(priv->dev, GVE_DQO_TX_MAX); } + priv->dev->mtu = priv->dev->max_mtu; + priv->numa_node = dev_to_node(&priv->pdev->dev); + priv->tx_cfg.num_xdp_queues = 0; + priv->rx_copybreak = GVE_DEFAULT_RX_COPYBREAK; priv->ts_config.tx_type = HWTSTAMP_TX_OFF; priv->ts_config.rx_filter = HWTSTAMP_FILTER_NONE; From 69886e8085ab66be8e5e1ea1ef3aeaa25c8dfa6c Mon Sep 17 00:00:00 2001 From: Harshitha Ramamurthy Date: Fri, 14 Aug 2026 02:13:53 +0000 Subject: [PATCH 1397/1433] gve: add a few helper functions to set device properties For the mailbox ABI, device properties will come from a different source compared to the AdminQ mode. To accommodate the new source when the mailbox ABI is added, add a few helper functions to set a few device properties. Those functions are: - gve_set_queue_properties() to set no. of pages for QPL mode and number of queues in general - gve_set_mtu() - gve_set_mac() This is just code movement, no functional change. Reviewed-by: Willem de Bruijn Reviewed-by: Jordan Rhee Signed-off-by: Harshitha Ramamurthy Reviewed-by: Przemek Kitszel Link: https://patch.msgid.link/20260814021406.3044324-4-hramamurthy@google.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/google/gve/gve_adminq.c | 38 +++------------ drivers/net/ethernet/google/gve/gve_adminq.h | 7 ++- drivers/net/ethernet/google/gve/gve_main.c | 49 ++++++++++++++++++++ 3 files changed, 62 insertions(+), 32 deletions(-) diff --git a/drivers/net/ethernet/google/gve/gve_adminq.c b/drivers/net/ethernet/google/gve/gve_adminq.c index fe0dedfb25d7..f05f4895f4c7 100644 --- a/drivers/net/ethernet/google/gve/gve_adminq.c +++ b/drivers/net/ethernet/google/gve/gve_adminq.c @@ -920,19 +920,6 @@ int gve_adminq_destroy_rx_queues(struct gve_priv *priv, u32 num_queues) return err; } -static void gve_set_default_desc_cnt(struct gve_priv *priv, - const struct gve_device_descriptor *descriptor) -{ - priv->tx_desc_cnt = be16_to_cpu(descriptor->tx_queue_entries); - priv->rx_desc_cnt = be16_to_cpu(descriptor->rx_queue_entries); - - /* set default ranges */ - priv->max_tx_desc_cnt = priv->tx_desc_cnt; - priv->max_rx_desc_cnt = priv->rx_desc_cnt; - priv->min_tx_desc_cnt = priv->tx_desc_cnt; - priv->min_rx_desc_cnt = priv->rx_desc_cnt; -} - static void gve_set_default_rss_sizes(struct gve_priv *priv) { if (!gve_is_gqi(priv)) { @@ -1049,8 +1036,6 @@ int gve_adminq_describe_device(struct gve_priv *priv) union gve_adminq_command cmd; dma_addr_t descriptor_bus; int err = 0; - u8 *mac; - u16 mtu; memset(&cmd, 0, sizeof(cmd)); descriptor = dma_pool_alloc(priv->adminq_pool, GFP_KERNEL, @@ -1112,26 +1097,17 @@ int gve_adminq_describe_device(struct gve_priv *priv) "Driver is running with GQI QPL queue format.\n"); } - /* set default descriptor counts */ - gve_set_default_desc_cnt(priv, descriptor); - gve_set_default_rss_sizes(priv); - priv->max_registered_pages = - be64_to_cpu(descriptor->max_registered_pages); - mtu = be16_to_cpu(descriptor->mtu); - if (mtu < ETH_MIN_MTU) { - dev_err(&priv->pdev->dev, "MTU %d below minimum MTU\n", mtu); - err = -EINVAL; + err = gve_set_mtu(priv, descriptor); + if (err) goto free_device_descriptor; - } - priv->dev->max_mtu = mtu; + priv->num_event_counters = be16_to_cpu(descriptor->counters); - eth_hw_addr_set(priv->dev, descriptor->mac); - mac = descriptor->mac; - dev_info(&priv->pdev->dev, "MAC addr: %pM\n", mac); - priv->tx_pages_per_qpl = be16_to_cpu(descriptor->tx_pages_per_qpl); - priv->default_num_queues = be16_to_cpu(descriptor->default_num_queues); + + gve_set_mac(priv, descriptor); + + gve_set_queue_properties(priv, descriptor); gve_enable_supported_features(priv, supported_features_mask, dev_op_jumbo_frames, dev_op_dqo_qpl, diff --git a/drivers/net/ethernet/google/gve/gve_adminq.h b/drivers/net/ethernet/google/gve/gve_adminq.h index 82b52424a63f..68c63ce75505 100644 --- a/drivers/net/ethernet/google/gve/gve_adminq.h +++ b/drivers/net/ethernet/google/gve/gve_adminq.h @@ -658,5 +658,10 @@ int gve_adminq_get_ptype_map_dqo(struct gve_priv *priv, struct gve_ptype_lut *ptype_lut); int gve_set_num_ntfy_blks(struct gve_priv *priv); void gve_set_num_queues(struct gve_priv *priv); - +void gve_set_queue_properties(struct gve_priv *priv, + struct gve_device_descriptor *descriptor); +int gve_set_mtu(struct gve_priv *priv, + struct gve_device_descriptor *descriptor); +void gve_set_mac(struct gve_priv *priv, + struct gve_device_descriptor *descriptor); #endif /* _GVE_ADMINQ_H */ diff --git a/drivers/net/ethernet/google/gve/gve_main.c b/drivers/net/ethernet/google/gve/gve_main.c index 37e6205e2eef..9cc343a16271 100644 --- a/drivers/net/ethernet/google/gve/gve_main.c +++ b/drivers/net/ethernet/google/gve/gve_main.c @@ -2398,6 +2398,55 @@ static const struct xdp_metadata_ops gve_xdp_metadata_ops = { .xmo_rx_timestamp = gve_xdp_rx_timestamp, }; +static void gve_set_default_desc_cnt(struct gve_priv *priv, + const struct gve_device_descriptor *descriptor) +{ + priv->tx_desc_cnt = be16_to_cpu(descriptor->tx_queue_entries); + priv->rx_desc_cnt = be16_to_cpu(descriptor->rx_queue_entries); + + /* set default ranges */ + priv->max_tx_desc_cnt = priv->tx_desc_cnt; + priv->max_rx_desc_cnt = priv->rx_desc_cnt; + priv->min_tx_desc_cnt = priv->tx_desc_cnt; + priv->min_rx_desc_cnt = priv->rx_desc_cnt; +} + +void gve_set_queue_properties(struct gve_priv *priv, + struct gve_device_descriptor *descriptor) +{ + /* set default descriptor counts */ + gve_set_default_desc_cnt(priv, descriptor); + + priv->max_registered_pages = be64_to_cpu(descriptor->max_registered_pages); + priv->tx_pages_per_qpl = be16_to_cpu(descriptor->tx_pages_per_qpl); + priv->default_num_queues = be16_to_cpu(descriptor->default_num_queues); +} + +int gve_set_mtu(struct gve_priv *priv, + struct gve_device_descriptor *descriptor) +{ + u16 mtu; + + mtu = be16_to_cpu(descriptor->mtu); + if (mtu < ETH_MIN_MTU) { + dev_err(&priv->pdev->dev, "MTU %d below minimum MTU\n", mtu); + return -EINVAL; + } + priv->dev->max_mtu = mtu; + + return 0; +} + +void gve_set_mac(struct gve_priv *priv, + struct gve_device_descriptor *descriptor) +{ + u8 *mac; + + mac = descriptor->mac; + eth_hw_addr_set(priv->dev, mac); + dev_info(&priv->pdev->dev, "MAC addr: %pM\n", mac); +} + static int gve_init_priv(struct gve_priv *priv, bool skip_describe_device) { int err; From dcb09aae194cc9e39083ebe8c3fbe9e01afaeec5 Mon Sep 17 00:00:00 2001 From: Yevgeny Kliteynik Date: Sun, 16 Aug 2026 17:20:41 +0300 Subject: [PATCH 1398/1433] net/mlx5: HWS, Print more details for bad completion When polling for completion returned completion with error, parse some more details: WQE count and syndrome number. Also, extract all the long value-to-string if conditions to a short value-to-string functions: do it for rule resize state, rule status, and syndrome. v2: removed duplicated QPN print Signed-off-by: Yevgeny Kliteynik Reviewed-by: Erez Shitrit Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260816142045.3289452-2-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../mellanox/mlx5/core/steering/hws/send.c | 63 +++++++++++++------ 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/send.c b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/send.c index aed009aec4fe..6612e58c896a 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/send.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/send.c @@ -344,6 +344,36 @@ hws_send_engine_update_rule_resize(struct mlx5hws_send_engine *queue, } } +static const char *hws_rule_status_to_string(enum mlx5hws_rule_status status) +{ + switch (status) { + case MLX5HWS_RULE_STATUS_CREATING: return "CREATING"; + case MLX5HWS_RULE_STATUS_UPDATING: return "UPDATING"; + case MLX5HWS_RULE_STATUS_DELETING: return "DELETING"; + case MLX5HWS_RULE_STATUS_FAILING: return "FAILING"; + default: return "NA"; + } +} + +static const char *hws_rule_resize_state_to_string(u8 state) +{ + switch (state) { + case MLX5HWS_RULE_RESIZE_STATE_IDLE: return "IDLE"; + case MLX5HWS_RULE_RESIZE_STATE_WRITING: return "WRITING"; + case MLX5HWS_RULE_RESIZE_STATE_DELETING: return "DELETING"; + default: return "UNKNOWN"; + } +} + +static const char *hws_gta_syndrome_to_string(u8 syndrome) +{ + switch (syndrome) { + case 1: return "SET_FLOW_FAIL"; + case 2: return "DISABLE_FLOW_FAIL"; + default: return "UNKNOWN"; + } +} + static void hws_send_engine_dump_error_cqe(struct mlx5hws_send_engine *queue, struct mlx5hws_send_ring_priv *priv, struct mlx5_cqe64 *cqe) @@ -352,6 +382,7 @@ static void hws_send_engine_dump_error_cqe(struct mlx5hws_send_engine *queue, struct mlx5hws_context *ctx = priv->rule->matcher->tbl->ctx; u32 opcode = cqe ? get_cqe_opcode(cqe) : 0; struct mlx5hws_rule *rule = priv->rule; + u8 syndrome; /* If something bad happens and lots of rules are failing, we don't * want to pollute dmesg. Print only the first bad cqe per engine, @@ -364,26 +395,17 @@ static void hws_send_engine_dump_error_cqe(struct mlx5hws_send_engine *queue, if (mlx5hws_rule_move_in_progress(rule)) mlx5hws_err(ctx, - "--- rule 0x%08llx: error completion moving rule: phase %s, wqes left %d\n", + "--- rule 0x%08llx: error completion moving rule: phase %s (%d), wqes left %d\n", HWS_PTR_TO_ID(rule), - rule->resize_info->state == - MLX5HWS_RULE_RESIZE_STATE_WRITING ? "WRITING" : - rule->resize_info->state == - MLX5HWS_RULE_RESIZE_STATE_DELETING ? "DELETING" : - "UNKNOWN", + hws_rule_resize_state_to_string + (rule->resize_info->state), + rule->resize_info->state, rule->pending_wqes); else mlx5hws_err(ctx, "--- rule 0x%08llx: error completion %s (%d), wqes left %d\n", HWS_PTR_TO_ID(rule), - rule->status == - MLX5HWS_RULE_STATUS_CREATING ? "CREATING" : - rule->status == - MLX5HWS_RULE_STATUS_DELETING ? "DELETING" : - rule->status == - MLX5HWS_RULE_STATUS_FAILING ? "FAILING" : - rule->status == - MLX5HWS_RULE_STATUS_UPDATING ? "UPDATING" : "NA", + hws_rule_status_to_string(rule->status), rule->status, rule->pending_wqes); @@ -423,6 +445,10 @@ static void hws_send_engine_dump_error_cqe(struct mlx5hws_send_engine *queue, " rule 0x%08llx: |--- syndrome = 0x%x\n", HWS_PTR_TO_ID(rule), err_cqe->syndrome); + mlx5hws_err(ctx, + " rule 0x%08llx: |--- WQE_CNT = 0x%04x\n", + HWS_PTR_TO_ID(rule), + (u32)be16_to_cpu(err_cqe->wqe_counter)); } mlx5hws_err(ctx, @@ -433,13 +459,12 @@ static void hws_send_engine_dump_error_cqe(struct mlx5hws_send_engine *queue, HWS_PTR_TO_ID(rule), (be32_to_cpu(cqe->byte_cnt) & 0x80000000) ? "FAILURE" : "SUCCESS"); + /* syndrome is in the lower 2 bits of byte_cnt */ + syndrome = be32_to_cpu(cqe->byte_cnt) & 3; mlx5hws_err(ctx, - " rule 0x%08llx: |------- SYNDROME = %s\n", + " rule 0x%08llx: |------- SYNDROME = %s (%u)\n", HWS_PTR_TO_ID(rule), - ((be32_to_cpu(cqe->byte_cnt) & 0x00000003) == 1) ? - "SET_FLOW_FAIL" : - ((be32_to_cpu(cqe->byte_cnt) & 0x00000003) == 2) ? - "DISABLE_FLOW_FAIL" : "UNKNOWN"); + hws_gta_syndrome_to_string(syndrome), syndrome); mlx5hws_err(ctx, " rule 0x%08llx: cqe->sop_drop_qpn = 0x%08x\n", HWS_PTR_TO_ID(rule), be32_to_cpu(cqe->sop_drop_qpn)); From d4c34eebf18bc69ce54f15fde18ed39c9be7d520 Mon Sep 17 00:00:00 2001 From: Yevgeny Kliteynik Date: Sun, 16 Aug 2026 17:20:42 +0300 Subject: [PATCH 1399/1433] net/mlx5: HWS, Log syndrome on STC modify failure When mlx5_cmd_exec fails for STC modify, include the command syndrome from the output buffer in the error message to aid debugging. Signed-off-by: Yevgeny Kliteynik Reviewed-by: Erez Shitrit Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260816142045.3289452-3-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c index 8fae90101653..2cdc03421433 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c @@ -611,9 +611,11 @@ int mlx5hws_cmd_stc_modify(struct mlx5_core_dev *mdev, ret = mlx5_cmd_exec(mdev, in, sizeof(in), out, sizeof(out)); if (ret) - mlx5_core_err(mdev, "Failed to modify STC FW action_type %d\n", - stc_attr->action_type); - + mlx5_core_err(mdev, + "Failed to modify STC action_type %d, err %d, syndrome 0x%x\n", + stc_attr->action_type, ret, + MLX5_GET(general_obj_out_cmd_hdr, + out, syndrome)); return ret; } From 5d3ff6671b6265793390c7f4eadb267f1e9aec0a Mon Sep 17 00:00:00 2001 From: Yevgeny Kliteynik Date: Sun, 16 Aug 2026 17:20:43 +0300 Subject: [PATCH 1400/1433] net/mlx5: HWS, Set the num of queues only when alloc succeeded When initializing send queues, set the number of queues only when allocations are over. Signed-off-by: Yevgeny Kliteynik Reviewed-by: Erez Shitrit Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260816142045.3289452-4-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/steering/hws/send.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/send.c b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/send.c index 6612e58c896a..df3b93499eeb 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/send.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/send.c @@ -1116,8 +1116,6 @@ static int hws_bwc_send_queues_init(struct mlx5hws_context *ctx) if (!mlx5hws_context_bwc_supported(ctx)) return 0; - ctx->queues += bwc_queues; - ctx->bwc_send_queue_locks = kzalloc_objs(*ctx->bwc_send_queue_locks, bwc_queues); @@ -1129,6 +1127,8 @@ static int hws_bwc_send_queues_init(struct mlx5hws_context *ctx) if (!ctx->bwc_lock_class_keys) goto err_lock_class_keys; + ctx->queues += bwc_queues; + for (i = 0; i < bwc_queues; i++) { mutex_init(&ctx->bwc_send_queue_locks[i]); lockdep_register_key(ctx->bwc_lock_class_keys + i); From 7e34d2ada9c2f2b2d7db664766324450aadb7cd9 Mon Sep 17 00:00:00 2001 From: Yevgeny Kliteynik Date: Sun, 16 Aug 2026 17:20:44 +0300 Subject: [PATCH 1401/1433] net/mlx5: HWS, Remove redundant MLX5_SET in RTC creation Remove duplicated setting of field in mlx5hws_cmd_rtc_create(). Signed-off-by: Yevgeny Kliteynik Reviewed-by: Erez Shitrit Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260816142045.3289452-5-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c index 2cdc03421433..3782d793550d 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c @@ -398,7 +398,6 @@ int mlx5hws_cmd_rtc_create(struct mlx5_core_dev *mdev, MLX5_SET(rtc, attr, update_method, rtc_attr->fw_gen_wqe); MLX5_SET(rtc, attr, update_index_mode, rtc_attr->update_index_mode); MLX5_SET(rtc, attr, access_index_mode, rtc_attr->access_index_mode); - MLX5_SET(rtc, attr, num_hash_definer, rtc_attr->num_hash_definer); MLX5_SET(rtc, attr, log_depth, rtc_attr->log_depth); MLX5_SET(rtc, attr, log_hash_size, rtc_attr->log_size); MLX5_SET(rtc, attr, table_type, rtc_attr->table_type); From bc502b3f69a3736cb079f327142c543ae10c5015 Mon Sep 17 00:00:00 2001 From: Yevgeny Kliteynik Date: Sun, 16 Aug 2026 17:20:45 +0300 Subject: [PATCH 1402/1433] net/mlx5: HWS, Remove redundant FW command when reading caps Remove redundant FW query that isn't really in use. Signed-off-by: Yevgeny Kliteynik Reviewed-by: Erez Shitrit Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260816142045.3289452-6-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c index 3782d793550d..775dc5c2d7a6 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/cmd.c @@ -1182,12 +1182,6 @@ int mlx5hws_cmd_query_caps(struct mlx5_core_dev *mdev, capability.e_switch_cap.merged_eswitch); } - ret = mlx5_cmd_exec(mdev, in, sizeof(in), out, out_size); - if (ret) { - mlx5_core_err(mdev, "Failed to query device attributes\n"); - goto out; - } - snprintf(caps->fw_ver, sizeof(caps->fw_ver), "%d.%d.%d", fw_rev_maj(mdev), fw_rev_min(mdev), fw_rev_sub(mdev)); From 29e63b8d9fc150cc191b1c6eb7e16e1247e1b650 Mon Sep 17 00:00:00 2001 From: Qing Ming Date: Fri, 14 Aug 2026 17:54:04 +0800 Subject: [PATCH 1403/1433] mpls: reload header after pskb_may_pull() mpls_select_multipath() calls mpls_multipath_hash() to choose a nexthop when an MPLS route has multiple nexthops. While walking the MPLS label stack, the hash routine caches hdr for the current label. After finding the bottom-of-stack label, it calls pskb_may_pull() before reading the inner IP header. If an skb is constructed with the inner IP header in nonlinear data and insufficient tailroom in the linear head, pskb_may_pull() calls pskb_expand_head() to replace the skb head and free the old one. This leaves hdr pointing to freed memory. The IPv6 path can invalidate hdr again when it performs a second pull for the larger header. The issue was found through static analysis. A reproducer sending a legal Geneve packet through a bareudp/MPLS multipath setup triggered the same KASAN report in 2 of 2 unpatched runs: BUG: KASAN: slab-use-after-free in mpls_select_multipath Read of size 1 at addr ffff88800ecc6e20 by task ksoftirqd/1/23 Call Trace: mpls_select_multipath mpls_forward __netif_receive_skb_list_core netif_receive_skb_list_internal napi_complete_done gro_cell_poll __napi_poll net_rx_action Freed by task 23: kfree pskb_expand_head __pskb_pull_tail mpls_select_multipath Reload hdr from the current skb head after each successful pull before deriving the inner IPv4 or IPv6 header pointer. Fixes: 9f427a0e474a ("net: mpls: Fix multipath selection for LSR use case") Cc: stable@vger.kernel.org Signed-off-by: Qing Ming Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260814095404.7205-1-a0yami@mailbox.org Signed-off-by: Paolo Abeni --- net/mpls/af_mpls.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/mpls/af_mpls.c b/net/mpls/af_mpls.c index 961be5054a03..17b78dcbf8ab 100644 --- a/net/mpls/af_mpls.c +++ b/net/mpls/af_mpls.c @@ -221,6 +221,7 @@ static u32 mpls_multipath_hash(struct mpls_route *rt, struct sk_buff *skb) if (pskb_may_pull(skb, mpls_hdr_len + sizeof(struct iphdr))) { const struct iphdr *v4hdr; + hdr = mpls_hdr(skb) + label_index; v4hdr = (const struct iphdr *)(hdr + 1); if (v4hdr->version == 4) { hash = jhash_3words(ntohl(v4hdr->saddr), @@ -231,6 +232,7 @@ static u32 mpls_multipath_hash(struct mpls_route *rt, struct sk_buff *skb) sizeof(struct ipv6hdr))) { const struct ipv6hdr *v6hdr; + hdr = mpls_hdr(skb) + label_index; v6hdr = (const struct ipv6hdr *)(hdr + 1); hash = __ipv6_addr_jhash(&v6hdr->saddr, hash); hash = __ipv6_addr_jhash(&v6hdr->daddr, hash); From bc2dc66a6693a78f8c1e6ca2dbebd50f16e2c366 Mon Sep 17 00:00:00 2001 From: Baul Lee Date: Sat, 15 Aug 2026 00:35:47 +0900 Subject: [PATCH 1404/1433] vxlan: mdb: Fix use-after-free in vxlan_mdb_flush() vxlan_mdb_flush() iterates over the MDB entries using hlist_for_each_entry_safe(), which only tolerates the removal of the current entry. Contrary to the comment above the loop, the removal of an entry can trigger the removal of another entry. Flushing the remotes of a (*, G) entry also removes the (S, G) entries that were created for its source list, once they are left without remotes: vxlan_mdb_remotes_flush() -> vxlan_mdb_remote_del() -> vxlan_mdb_remote_srcs_del() -> vxlan_mdb_remote_src_del() -> vxlan_mdb_remote_src_fwd_del() -> __vxlan_mdb_del() -> vxlan_mdb_entry_put() Such an entry can be located after the (*, G) entry in the list, as vxlan_mdb_entry_get() returns an existing entry without moving it to the head of the list. This order is obtained by adding the (S, G) entry before the (*, G) entry, the latter with NLM_F_REPLACE, as the addition of the source otherwise fails with -EEXIST. The (S, G) entry is then the entry saved by hlist_for_each_entry_safe() and it is freed while the (*, G) entry is processed. The next iteration calls hlist_del() on it again, writing LIST_POISON1 to LIST_POISON2 [1]. Besides device deletion, the flush is also reachable from RTM_DELMDB with NLM_F_BULK. Fix by re-reading the next entry after the remotes were flushed. The current entry cannot be removed by this flush, as source lists can only be configured on (*, G) entries and the removed entries are (S, G) entries. It is therefore still linked and its next pointer reflects the removals. [1] BUG: KASAN: wild-memory-access in vxlan_mdb_entry_put.part.0+0x328/0x588 Write of size 8 at addr dead000000000122 by task ip/327 CPU: 3 UID: 1000 PID: 327 Comm: ip Not tainted 7.2.0-rc7 #2 PREEMPT Call trace: vxlan_mdb_entry_put.part.0+0x328/0x588 vxlan_mdb_flush+0x1d8/0x25c vxlan_mdb_fini+0x8c/0x100 vxlan_uninit+0x1c/0x7c unregister_netdevice_many_notify+0x954/0xd4c rtnl_dellink+0x210/0x530 rtnetlink_rcv_msg+0x434/0x4d0 netlink_rcv_skb+0xc4/0x204 rtnetlink_rcv+0x18/0x24 netlink_unicast+0x4b8/0x548 netlink_sendmsg+0x29c/0x560 ____sys_sendmsg+0x390/0x3ec ___sys_sendmsg+0x114/0x188 __sys_sendmsg+0xf0/0x178 __arm64_sys_sendmsg+0x48/0x60 invoke_syscall.constprop.0+0x58/0x180 el0_svc_common.constprop.0+0x74/0x140 do_el0_svc+0x30/0x40 el0_svc+0x38/0x98 el0t_64_sync_handler+0xa0/0xe4 el0t_64_sync+0x198/0x19c Fixes: a3a48de5eade ("vxlan: mdb: Add MDB control path support") Signed-off-by: Baul Lee Reviewed-by: Nikolay Aleksandrov Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260814153547.29567-1-baul.lee@xbow.com Signed-off-by: Paolo Abeni --- drivers/net/vxlan/vxlan_mdb.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/net/vxlan/vxlan_mdb.c b/drivers/net/vxlan/vxlan_mdb.c index 9a9038ae90c1..d71e1925ecfd 100644 --- a/drivers/net/vxlan/vxlan_mdb.c +++ b/drivers/net/vxlan/vxlan_mdb.c @@ -1428,14 +1428,17 @@ static void vxlan_mdb_flush(struct vxlan_dev *vxlan, struct vxlan_mdb_entry *mdb_entry; struct hlist_node *tmp; - /* The removal of an entry cannot trigger the removal of another entry - * since entries are always added to the head of the list. - */ hlist_for_each_entry_safe(mdb_entry, tmp, &vxlan->mdb_list, mdb_node) { if (desc->src_vni && desc->src_vni != mdb_entry->key.vni) continue; vxlan_mdb_remotes_flush(vxlan, mdb_entry, desc); + /* The flush can remove the (S, G) entries created for the + * source list of this entry, including the one saved by + * hlist_for_each_entry_safe(), so re-read it while this entry + * is still linked. + */ + tmp = mdb_entry->mdb_node.next; /* Entry will only be removed if its remotes list is empty. */ vxlan_mdb_entry_put(vxlan, mdb_entry); } From cb7643b78d35392f0f434774d78b4c77b80f677f Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Mon, 17 Aug 2026 06:30:57 +0200 Subject: [PATCH 1405/1433] 8139cp: fix Rx and Tx not being disabled in cp_suspend On QEMU rtl8139 model, frames that arrive while the interface is suspended still end up in the stack after resume. With pm_test=devices, which keeps devices suspended for 5s, 200 frames sent to interface during that time and 50 frames after resume, eth0 reports 113 received frames. cp_suspend() is supposed to stop receiver and the transmitter, but the mask is wrong: (~RxOn | ~TxOn) is ~0, nothing is cleared and Cmd still reads 0x0d when cp_suspend() returns. Use ~(RxOn | TxOn) so both bits are actually cleared. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Karl Mehltretter Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260817043057.20099-1-kmehltretter@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/realtek/8139cp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/realtek/8139cp.c b/drivers/net/ethernet/realtek/8139cp.c index 5652da8a178c..9016527e229a 100644 --- a/drivers/net/ethernet/realtek/8139cp.c +++ b/drivers/net/ethernet/realtek/8139cp.c @@ -2066,7 +2066,7 @@ static int __maybe_unused cp_suspend(struct device *device) /* Disable Rx and Tx */ cpw16 (IntrMask, 0); - cpw8 (Cmd, cpr8 (Cmd) & (~RxOn | ~TxOn)); + cpw8 (Cmd, cpr8 (Cmd) & ~(RxOn | TxOn)); spin_unlock_irqrestore (&cp->lock, flags); From 8acf691d8017012e1476c30e7381513c1e929c94 Mon Sep 17 00:00:00 2001 From: Mahanta Jambigi Date: Thu, 13 Aug 2026 09:43:15 +0200 Subject: [PATCH 1406/1433] net/smc: hash socket only after full initialisation in smc_sk_init() smc_sk_init() calls sk->sk_prot->hash(sk) before several fields are fully initialised: clcsock_release_lock, the saved clcsk_* callbacks, use_fallback/fallback_rsn, and conn.close_work. Once hash() returns the socket is visible to concurrent hash walkers, which can then observe uninitialised state. Move hash(sk) to the end of smc_sk_init() so the socket is published only after it is fully constructed. Fixes: d0e35656d834 ("net/smc: refactoring initialization of smc sock") Reviewed-by: Hidayath Khan Reviewed-by: Sidraya Jayagond Signed-off-by: Mahanta Jambigi Link: https://patch.msgid.link/20260813074315.554926-1-mjambigi@linux.ibm.com Signed-off-by: Paolo Abeni --- net/smc/af_smc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c index cff910cedbfc..e9f93b3ab435 100644 --- a/net/smc/af_smc.c +++ b/net/smc/af_smc.c @@ -409,13 +409,13 @@ void smc_sk_init(struct net *net, struct sock *sk, int protocol) "sk_lock-AF_SMC", &smc_key); spin_lock_init(&smc->accept_q_lock); spin_lock_init(&smc->conn.send_lock); - sk->sk_prot->hash(sk); mutex_init(&smc->clcsock_release_lock); smc_init_saved_callbacks(smc); smc->limit_smc_hs = net->smc.limit_smc_hs; smc->use_fallback = false; /* assume rdma capability first */ smc->fallback_rsn = 0; smc_close_init(smc); + sk->sk_prot->hash(sk); } static struct sock *smc_sock_alloc(struct net *net, struct socket *sock, From 505b6d296c486ef7d1274f279d4c43a172f63224 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Thu, 13 Aug 2026 00:22:34 +0800 Subject: [PATCH 1407/1433] ip6_gre: fix hardware header length for NBMA tunnels ip6gre_tnl_link_config_route() accumulates the lower device's hardware header length into dev->hard_header_len whenever header_ops is set. This is incorrect for both users of header_ops. ip6gretap and ip6erspan have a fixed Ethernet hardware header length. For an NBMA ip6gre tunnel, ip6gre_header() creates only the GRE header, the optional FOU or GUE header, and the outer IPv6 header. The lower device header is headroom needed later, not part of the tunnel device's hardware header. Keep the lower device header in needed_headroom. Set hard_header_len to the tunnel header length only for ARPHRD_IP6GRE devices with header_ops, and leave the fixed Ethernet header length unchanged for tap and erspan devices. Fixes: 832ba596494b ("net: ip6_gre: set dev->hard_header_len when using header_ops") Cc: stable@vger.kernel.org Suggested-by: Ido Schimmel Signed-off-by: Zhiling Zou Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/64b46542bbe1701f07702aaa50273e2a87903db5.1786542637.git.zhilinz@nebusec.ai Signed-off-by: Paolo Abeni --- net/ipv6/ip6_gre.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index b843116e9b70..70c171091020 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -1137,13 +1137,8 @@ static void ip6gre_tnl_link_config_route(struct ip6_tnl *t, int set_mtu, return; if (rt->dst.dev) { - unsigned short dst_len = rt->dst.dev->hard_header_len + - t_hlen; - - if (t->dev->header_ops) - dev->hard_header_len = dst_len; - else - dev->needed_headroom = dst_len; + dev->needed_headroom = rt->dst.dev->hard_header_len + + t_hlen; if (set_mtu) { int mtu = rt->dst.dev->mtu - t_hlen; @@ -1171,8 +1166,8 @@ static int ip6gre_calc_hlen(struct ip6_tnl *tunnel) t_hlen = tunnel->hlen + sizeof(struct ipv6hdr); - if (tunnel->dev->header_ops) - tunnel->dev->hard_header_len = LL_MAX_HEADER + t_hlen; + if (tunnel->dev->header_ops && tunnel->dev->type == ARPHRD_IP6GRE) + tunnel->dev->hard_header_len = t_hlen; else tunnel->dev->needed_headroom = LL_MAX_HEADER + t_hlen; From 6b222adeb9340306e2ff97127c76117abb9b3df8 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Thu, 13 Aug 2026 00:22:35 +0800 Subject: [PATCH 1408/1433] net: cap advertised IP tunnel headroom IP tunnel devices derive their advertised needed_headroom from lower output devices. A stack of user-created devices can make the derived value larger than the 16-bit skb header offsets can represent. Once IP output reserves it, skb head expansion can wrap those offsets. The runtime transmit path already caps a growing needed_headroom at 512. Apply the same cap when tunnel configuration publishes needed_headroom derived from a lower output device. Capping the advertised value is safe: IP tunnel transmit still expands the skb when a packet needs more headroom. A nonsensical stacked configuration can therefore incur an extra reallocation, but it cannot publish an unbounded reservation to upper layers. Fixes: 1a37e412a022 ("net: Use 16bits for *_headers fields of struct skbuff") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/ba04a1fd6bfae2377607fad5d8f80f7eb80fd4c4.1786542637.git.zhilinz@nebusec.ai Signed-off-by: Paolo Abeni --- include/net/ip_tunnels.h | 11 +++++++++-- net/ipv4/ip_tunnel.c | 2 +- net/ipv6/ip6_gre.c | 7 +++++-- net/ipv6/ip6_tunnel.c | 7 +++++-- net/ipv6/sit.c | 2 +- 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h index d708b66e55cd..85e3455cea25 100644 --- a/include/net/ip_tunnels.h +++ b/include/net/ip_tunnels.h @@ -629,8 +629,7 @@ struct metadata_dst *iptunnel_metadata_reply(struct metadata_dst *md, int skb_tunnel_check_pmtu(struct sk_buff *skb, struct dst_entry *encap_dst, int headroom, bool reply); -static inline void ip_tunnel_adj_headroom(struct net_device *dev, - unsigned int headroom) +static inline unsigned int ip_tunnel_limit_headroom(unsigned int headroom) { /* we must cap headroom to some upperlimit, else pskb_expand_head * will overflow header offsets in skb_headers_offset_update(). @@ -640,6 +639,14 @@ static inline void ip_tunnel_adj_headroom(struct net_device *dev, if (headroom > max_allowed) headroom = max_allowed; + return headroom; +} + +static inline void ip_tunnel_adj_headroom(struct net_device *dev, + unsigned int headroom) +{ + headroom = ip_tunnel_limit_headroom(headroom); + if (headroom > READ_ONCE(dev->needed_headroom)) WRITE_ONCE(dev->needed_headroom, headroom); } diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c index 9d114bd575f9..5b1f180485d4 100644 --- a/net/ipv4/ip_tunnel.c +++ b/net/ipv4/ip_tunnel.c @@ -317,7 +317,7 @@ static int ip_tunnel_bind_dev(struct net_device *dev) mtu = min(tdev->mtu, IP_MAX_MTU); } - dev->needed_headroom = t_hlen + hlen; + dev->needed_headroom = ip_tunnel_limit_headroom(t_hlen + hlen); mtu -= t_hlen + (dev->type == ARPHRD_ETHER ? dev->hard_header_len : 0); if (mtu < IPV4_MIN_MTU) diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index 70c171091020..200d0ba1a40e 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -1137,8 +1137,11 @@ static void ip6gre_tnl_link_config_route(struct ip6_tnl *t, int set_mtu, return; if (rt->dst.dev) { - dev->needed_headroom = rt->dst.dev->hard_header_len + - t_hlen; + unsigned int headroom; + + headroom = rt->dst.dev->hard_header_len + t_hlen; + headroom = ip_tunnel_limit_headroom(headroom); + dev->needed_headroom = headroom; if (set_mtu) { int mtu = rt->dst.dev->mtu - t_hlen; diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index 6a1b901ecc9b..cc96bb8b706e 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -1514,8 +1514,11 @@ static void ip6_tnl_link_config(struct ip6_tnl *t) tdev = __dev_get_by_index(t->net, p->link); if (tdev) { - dev->needed_headroom = tdev->hard_header_len + - tdev->needed_headroom + t_hlen; + unsigned int headroom; + + headroom = tdev->hard_header_len + tdev->needed_headroom; + headroom += t_hlen; + dev->needed_headroom = ip_tunnel_limit_headroom(headroom); mtu = min_t(unsigned int, tdev->mtu, IP6_MAX_MTU); mtu = mtu - t_hlen; diff --git a/net/ipv6/sit.c b/net/ipv6/sit.c index a38b24fb8384..19b7fa8d1a2a 100644 --- a/net/ipv6/sit.c +++ b/net/ipv6/sit.c @@ -1131,7 +1131,7 @@ static void ipip6_tunnel_bind_dev(struct net_device *dev) WRITE_ONCE(dev->mtu, mtu); hlen = tdev->hard_header_len + tdev->needed_headroom; } - dev->needed_headroom = t_hlen + hlen; + dev->needed_headroom = ip_tunnel_limit_headroom(t_hlen + hlen); } static void ipip6_tunnel_update(struct ip_tunnel *t, From e36ce6e78fe3fc3c071a26750783b7ba081ce10d Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Thu, 13 Aug 2026 00:36:38 +0800 Subject: [PATCH 1409/1433] ip: orphan prefetched skbs before multicast forwarding IPv4 and IPv6 input preserve an skb->sk association installed by bpf_sk_assign() so that local delivery can use the selected socket under RCU. Both address families can also prefetch a socket in UDP early demux. In both paths (BPF and UDP early demux) a reference is not guaranteed to be held on the socket. When a multicast packet is not locally deliverable, IPv6 hands the original skb to ip6_mr_input(). IPv4's ip_mr_input() similarly keeps the original skb when local delivery is not needed. Either path can put the skb on an unresolved multicast route queue or forward it after the receive-side RCU section ends. After the prefetched socket is destroyed, a later skb free invokes sock_pfree() and dereferences the stale skb->sk. Orphan the skb before each non-local multicast forwarding path. Local delivery retains the original skb; the existing skb_clone() calls provide multicast forwarding with a socket-free clone. Fixes: cf7fbe660f2d ("bpf: Add socket assign support") Fixes: 08842c43d016 ("udp: no longer touch sk->sk_refcnt in early demux") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Reported-by: Vega Signed-off-by: Zhiling Zou Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/0c52eb3d7532aaf8bccf37e0f7c922143c639735.1786552223.git.zhilinz@nebusec.ai Signed-off-by: Paolo Abeni --- net/ipv4/ipmr.c | 3 +++ net/ipv6/ip6_input.c | 1 + 2 files changed, 4 insertions(+) diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c index 1d9a4ac14fce..e5f2b1c6150d 100644 --- a/net/ipv4/ipmr.c +++ b/net/ipv4/ipmr.c @@ -2213,6 +2213,9 @@ int ip_mr_input(struct sk_buff *skb) if (IPCB(skb)->flags & IPSKB_FORWARDED) goto dont_forward; + if (!local) + skb_orphan(skb); + mrt = ipmr_rt_fib_lookup(net, skb); if (IS_ERR(mrt)) { kfree_skb(skb); diff --git a/net/ipv6/ip6_input.c b/net/ipv6/ip6_input.c index 8972863c93ee..d332ec60f915 100644 --- a/net/ipv6/ip6_input.c +++ b/net/ipv6/ip6_input.c @@ -622,6 +622,7 @@ int ip6_mc_input(struct sk_buff *skb) if (deliver) { skb2 = skb_clone(skb, GFP_ATOMIC); } else { + skb_orphan(skb); skb2 = skb; skb = NULL; } From d92255b405fb6f5acca408239ccd742e0a42c9cb Mon Sep 17 00:00:00 2001 From: Anand Khoje Date: Thu, 13 Aug 2026 08:37:05 +0000 Subject: [PATCH 1410/1433] net/ionic: avoid OOB TX partner lookup for hwstamp RXQ The dedicated hardware timestamp RX queue is allocated with q->index equal to lif->ionic->nrxqs_per_lif. The normal txqcqs array only contains the regular queue pairs, so using that index to set rxq->partner can read one entry past txqcqs[] and then write through the derived pointer. Only link RX/TX partners for normal queue-pair indexes. Leave the hwstamp RX queue unpaired, and make the XDP_TX path abort cleanly if an RX queue has no TX partner. Fixes: 8eeed8373e1c ("ionic: Add XDP_TX support") Reviewed-by: Si-Wei Liu Reviewed-by: Shannon Nelson Cc: stable@vger.kernel.org Signed-off-by: Anand Khoje Reviewed-by: Simon Horman Reviewed-by: Brett Creeley Link: https://patch.msgid.link/20260813083705.454897-1-anand.a.khoje@oracle.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/pensando/ionic/ionic_lif.c | 17 +++++++++++++++-- .../net/ethernet/pensando/ionic/ionic_txrx.c | 7 ++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/pensando/ionic/ionic_lif.c b/drivers/net/ethernet/pensando/ionic/ionic_lif.c index fd3ee9820531..abc8e3530435 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_lif.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_lif.c @@ -920,8 +920,21 @@ static int ionic_lif_rxq_init(struct ionic_lif *lif, struct ionic_qcq *qcq) }; int err; - q->partner = &lif->txqcqs[q->index]->q; - q->partner->partner = q; + q->partner = NULL; + + /* Only normal RX queues have matching TX queue partners. */ + if (q->index < lif->nxqs) { + if (!lif->txqcqs || + q->index >= lif->ionic->ntxqs_per_lif || + !lif->txqcqs[q->index]) { + dev_err(dev, "missing TX queue partner for RX queue %u\n", + q->index); + return -ENXIO; + } + + q->partner = &lif->txqcqs[q->index]->q; + q->partner->partner = q; + } if (!lif->xdp_prog || (lif->xdp_prog->aux && lif->xdp_prog->aux->xdp_has_frags)) diff --git a/drivers/net/ethernet/pensando/ionic/ionic_txrx.c b/drivers/net/ethernet/pensando/ionic/ionic_txrx.c index 301ebee2fdc5..73998d61593a 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_txrx.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_txrx.c @@ -545,13 +545,18 @@ static bool ionic_run_xdp(struct ionic_rx_stats *stats, break; case XDP_TX: + txq = rxq->partner; + if (unlikely(!txq)) { + err = -EIO; + break; + } + xdpf = xdp_convert_buff_to_frame(&xdp_buf); if (!xdpf) { err = -ENOSPC; break; } - txq = rxq->partner; nq = netdev_get_tx_queue(netdev, txq->index); __netif_tx_lock(nq, smp_processor_id()); txq_trans_cond_update(nq); From 66da914db44e51e612b07305243dc9775f53d825 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Sat, 15 Aug 2026 02:19:32 +0200 Subject: [PATCH 1411/1433] net: ip_tunnel: remove unused non-strict __ip_tunnel_change_mtu The last user of this function was the recently removed vport-gre module from openvswitch. Let's drop the function. All other modules use the strict variant. Signed-off-by: Ilya Maximets Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260815001942.1089545-1-i.maximets@ovn.org Signed-off-by: Paolo Abeni --- include/net/ip_tunnels.h | 1 - net/ipv4/ip_tunnel.c | 17 ++--------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h index d708b66e55cd..a8bbbc5db5cb 100644 --- a/include/net/ip_tunnels.h +++ b/include/net/ip_tunnels.h @@ -412,7 +412,6 @@ bool ip_tunnel_parm_from_user(struct ip_tunnel_parm_kern *kp, bool ip_tunnel_parm_to_user(void __user *data, struct ip_tunnel_parm_kern *kp); int ip_tunnel_siocdevprivate(struct net_device *dev, struct ifreq *ifr, void __user *data, int cmd); -int __ip_tunnel_change_mtu(struct net_device *dev, int new_mtu, bool strict); int ip_tunnel_change_mtu(struct net_device *dev, int new_mtu); struct ip_tunnel *ip_tunnel_lookup(struct ip_tunnel_net *itn, diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c index 9d114bd575f9..609aed527dda 100644 --- a/net/ipv4/ip_tunnel.c +++ b/net/ipv4/ip_tunnel.c @@ -1054,7 +1054,7 @@ int ip_tunnel_siocdevprivate(struct net_device *dev, struct ifreq *ifr, } EXPORT_SYMBOL_GPL(ip_tunnel_siocdevprivate); -int __ip_tunnel_change_mtu(struct net_device *dev, int new_mtu, bool strict) +int ip_tunnel_change_mtu(struct net_device *dev, int new_mtu) { struct ip_tunnel *tunnel = netdev_priv(dev); int t_hlen = tunnel->hlen + sizeof(struct iphdr); @@ -1063,25 +1063,12 @@ int __ip_tunnel_change_mtu(struct net_device *dev, int new_mtu, bool strict) if (dev->type == ARPHRD_ETHER) max_mtu -= dev->hard_header_len; - if (new_mtu < ETH_MIN_MTU) + if (new_mtu < ETH_MIN_MTU || new_mtu > max_mtu) return -EINVAL; - if (new_mtu > max_mtu) { - if (strict) - return -EINVAL; - - new_mtu = max_mtu; - } - WRITE_ONCE(dev->mtu, new_mtu); return 0; } -EXPORT_SYMBOL_GPL(__ip_tunnel_change_mtu); - -int ip_tunnel_change_mtu(struct net_device *dev, int new_mtu) -{ - return __ip_tunnel_change_mtu(dev, new_mtu, true); -} EXPORT_SYMBOL_GPL(ip_tunnel_change_mtu); static void ip_tunnel_dev_free(struct net_device *dev) From b8c899cf5e7be29840a172c183dedd8d3e7a0287 Mon Sep 17 00:00:00 2001 From: Nguyen Dinh Phi Date: Fri, 14 Aug 2026 01:30:18 +0800 Subject: [PATCH 1412/1433] vsock: don't check the listener's sk_err in vsock_accept() Syzbot reported an issue which can be reproduced with these steps: r0 = socket(AF_VSOCK, SOCK_STREAM, 0) bind(r0, {VMADDR_CID_ANY, PORT}) connect(r0, {VMADDR_CID_LOCAL, PORT}) -> -1, EPROTO (self-connect) listen(r0, backlog) -> 0 r1 = socket(AF_VSOCK, SOCK_STREAM, 0) connect(r1, {VMADDR_CID_LOCAL, PORT}) -> 0 accept(r0) -> -1, EPROTO (stale sk_err) Basically, it creates a socket (r0) and triggers a self-connect after binding it. This self-connect fails with EPROTO because it loops back to r0 while the socket is still in the TCP_SYN_SENT state, causing it to be incorrectly dispatched to the connecting-client path. The unexpected packet type encountered there sets sk_err to EPROTO. After that, it invokes a listen() call on the same socket. This listen() call succeeds because the kernel's listening path never inspects or clears sk_err. Then, a new socket (r1) is created as a normal client and connects to r0. However, vsock_accept() rejects this incoming connection because the listener's sk_err still holds the EPROTO error from the earlier failed self-connect. This rejection causes the child socket created for r1's connection to never be freed on virtio or hyperv transports; only the VMCI transport implements pending_work to revisit and clean up a rejected socket. For a non-blocking connect(), vsock_connect() may return -EINPROGRESS immediately, and vsock_connect_timeout() can later set sk->sk_err asynchronously. Since no vsock transport ever sets sk_err on a socket while it is in TCP_LISTEN state, checking it in vsock_accept() serves no purpose and only carries forward errors left behind by earlier, unrelated connection attempts on the same socket. Remove the checks so accept() no longer rejects valid incoming connections because of a stale error, which also avoids the resource leak described above. Fixes: d021c344051a ("VSOCK: Introduce VM Sockets") Reported-by: syzbot+1b2c9c4a0f8708082678@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1b2c9c4a0f8708082678 Suggested-by: Michal Luczaj Signed-off-by: Nguyen Dinh Phi Reviewed-by: Stefano Garzarella Link: https://patch.msgid.link/20260813173024.2362935-2-phind.uet@gmail.com Signed-off-by: Paolo Abeni --- net/vmw_vsock/af_vsock.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index 622dbd046799..3cd5c3561be3 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -1893,7 +1893,7 @@ static int vsock_accept(struct socket *sock, struct socket *newsock, timeout = sock_rcvtimeo(listener, arg->flags & O_NONBLOCK); while ((connected = vsock_dequeue_accept(listener)) == NULL && - listener->sk_err == 0 && timeout != 0) { + timeout != 0) { prepare_to_wait(sk_sleep(listener), &wait, TASK_INTERRUPTIBLE); release_sock(listener); timeout = schedule_timeout(timeout); @@ -1906,13 +1906,9 @@ static int vsock_accept(struct socket *sock, struct socket *newsock, } } - if (listener->sk_err) { - err = -listener->sk_err; - } else if (!connected) { + if (!connected) { err = -EAGAIN; - } - - if (connected) { + } else { sk_acceptq_removed(listener); lock_sock_nested(connected, SINGLE_DEPTH_NESTING); From 81fc0f369637ed6ee615d089d3016ba88b2bab71 Mon Sep 17 00:00:00 2001 From: Nguyen Dinh Phi Date: Fri, 14 Aug 2026 01:30:19 +0800 Subject: [PATCH 1413/1433] vsock: remove the now-unused rejected flag After previous patch, the branch marking a socket rejected in vsock_accept() is unreachable, and nothing ever sets vsk->rejected elsewhere. In fact, since commit d021c344051a ("VSOCK: Introduce VM Sockets"), where `rejected` was introduced, there has never been a path that sets sk_err on a listening socket, so that branch has been dead code since the beginning. Therefore, we can remove the `rejected` field from vsock_sock structure. Suggested-by: Stefano Garzarella Signed-off-by: Nguyen Dinh Phi Reviewed-by: Stefano Garzarella Link: https://patch.msgid.link/20260813173024.2362935-3-phind.uet@gmail.com Signed-off-by: Paolo Abeni --- include/net/af_vsock.h | 5 +---- net/vmw_vsock/af_vsock.c | 46 +++++++++++++--------------------------- 2 files changed, 16 insertions(+), 35 deletions(-) diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h index 30046a3c20f7..3357ee62d10b 100644 --- a/include/net/af_vsock.h +++ b/include/net/af_vsock.h @@ -52,13 +52,10 @@ struct vsock_sock { * The listening socket is the head for both lists. Sockets created * for connection requests are placed in the pending list until they * are connected, at which point they are put in the accept queue list - * so they can be accepted in accept(). If accept() cannot accept the - * connection, it is marked as rejected so the cleanup function knows - * to clean up the socket. + * so they can be accepted in accept(). */ struct list_head pending_links; struct list_head accept_queue; - bool rejected; struct delayed_work connect_work; struct delayed_work pending_work; struct delayed_work close_work; diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index 3cd5c3561be3..62e22c4b13c0 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -38,10 +38,9 @@ * pending socket. When that socket reaches the connected state, it is removed * from the listener socket's pending list and enqueued in the listener * socket's accept queue. Callers of accept(2) will accept connected sockets - * from the listener socket's accept queue. If the socket cannot be accepted - * for some reason then it is marked rejected. Once the connection is - * accepted, it is owned by the user process and the responsibility for cleanup - * falls with that user process. + * from the listener socket's accept queue. Once the connection is accepted, + * it is owned by the user process and the responsibility for cleanup falls + * with that user process. * * - It is possible that these pending sockets will never reach the connected * state; in fact, we may never receive another packet after the connection @@ -49,9 +48,7 @@ * future, after some amount of time passes where a connection should have been * established. This function ensures that the socket is off all lists so it * cannot be retrieved, then drops all references to the socket so it is cleaned - * up (sock_put() -> sk_free() -> our sk_destruct implementation). Note this - * function will also cleanup rejected sockets, those that reach the connected - * state but leave it before they have been accepted. + * up (sock_put() -> sk_free() -> our sk_destruct implementation). * * - Lock ordering for pending or accept queue sockets is: * @@ -774,11 +771,10 @@ static void vsock_pending_work(struct work_struct *work) if (vsock_is_pending(sk)) { vsock_remove_pending(listener, sk); - } else if (!vsk->rejected) { - /* We are not on the pending list and accept() did not reject - * us, so we must have been accepted by our user process. We - * just need to drop our references to the sockets and be on - * our way. + } else { + /* We are not on the pending list so we must have been accepted + * by our user process. We just need to drop our references to + * the sockets and be on our way. */ cleanup = false; goto out; @@ -942,7 +938,6 @@ static struct sock *__vsock_create(struct net *net, vsk->listener = NULL; INIT_LIST_HEAD(&vsk->pending_links); INIT_LIST_HEAD(&vsk->accept_queue); - vsk->rejected = false; vsk->sent_request = false; vsk->ignore_connecting_rst = false; WRITE_ONCE(vsk->peer_shutdown, 0); @@ -1914,27 +1909,16 @@ static int vsock_accept(struct socket *sock, struct socket *newsock, lock_sock_nested(connected, SINGLE_DEPTH_NESTING); vconnected = vsock_sk(connected); - /* If the listener socket has received an error, then we should - * reject this socket and return. Note that we simply mark the - * socket rejected, drop our reference, and let the cleanup - * function handle the cleanup; the fact that we found it in - * the listener's accept queue guarantees that the cleanup - * function hasn't run yet. - */ - if (err) { - vconnected->rejected = true; - } else { - newsock->state = SS_CONNECTED; - sock_graft(connected, newsock); + newsock->state = SS_CONNECTED; + sock_graft(connected, newsock); - set_bit(SOCK_CUSTOM_SOCKOPT, + set_bit(SOCK_CUSTOM_SOCKOPT, + &connected->sk_socket->flags); + + if (vsock_msgzerocopy_allow(vconnected->transport)) + set_bit(SOCK_SUPPORT_ZC, &connected->sk_socket->flags); - if (vsock_msgzerocopy_allow(vconnected->transport)) - set_bit(SOCK_SUPPORT_ZC, - &connected->sk_socket->flags); - } - release_sock(connected); sock_put(connected); } From 96cbf89993091a163bfedec52a3bd683dc94b3b4 Mon Sep 17 00:00:00 2001 From: Nguyen Dinh Phi Date: Fri, 14 Aug 2026 01:30:20 +0800 Subject: [PATCH 1414/1433] vsock: use sock_error() to consume sk_err after a failed connect vsock_connect() returns sk_err to userspace but does not clear it: if (sk->sk_err) { err = -sk->sk_err; For a blocking connect() the error has already been delivered as connect()'s return value, so leaving it set causes subsequent operations like poll()/epoll() to keep reporting POLLERR even though the connect failure was already delivered. The error should be consumed once it has been returned to userspace. Switch to sock_error(), which reads and clears sk_err atomically, matching the behavior of other protocol implementations such as __inet_stream_connect(). Fixes: d021c344051a ("VSOCK: Introduce VM Sockets") Tested-by: Wupeng Ma Reviewed-by: Stefano Garzarella Signed-off-by: Nguyen Dinh Phi Link: https://patch.msgid.link/20260813173024.2362935-4-phind.uet@gmail.com Signed-off-by: Paolo Abeni --- net/vmw_vsock/af_vsock.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index 62e22c4b13c0..e89cb84b8d73 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -1842,12 +1842,10 @@ static int vsock_connect(struct socket *sock, struct sockaddr_unsized *addr, prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE); } - if (sk->sk_err) { - err = -sk->sk_err; + err = sock_error(sk); + if (err) { sk->sk_state = TCP_CLOSE; sock->state = SS_UNCONNECTED; - } else { - err = 0; } out_wait: From 368990e9f6c618a8c81ee11b916667d501bd74d5 Mon Sep 17 00:00:00 2001 From: Jonas Jelonek Date: Thu, 13 Aug 2026 22:20:32 +0000 Subject: [PATCH 1415/1433] dt-bindings: net: pse-pd: add bindings for Realtek PSE MCU Add a binding for the microcontroller (MCU) that fronts the PSE silicon on a range of managed Realtek-based switches. The host talks only to the MCU, over I2C/SMBus or UART, using a fixed message-based protocol; the PSE chips behind it never appear on the bus. The device is the MCU together with its Realtek firmware: the firmware and its host protocol are what the binding describes, not the general-purpose microcontroller they run on. The PSE silicon behind the MCU (Realtek or Broadcom) is reported by the MCU and detected at runtime, so it is not described here - hence the 'realtek' vendor prefix. Two protocol generations exist, both Realtek's, selected by the compatible: gen1 on older boards (fronting Broadcom PSE silicon) and gen2, the altered protocol used with Realtek's own PSE silicon. On an I2C attachment the framing the MCU firmware expects is part of the compatible as well - '-smbus' or raw '-i2c'; a UART attachment carries no framing suffix, as the transport is given by the parent serial node. Each board additionally carries a device-specific compatible that falls back to one of the protocol compatibles above. Drivers bind on the protocol compatible; the device-specific string identifies the board and reserves a place for a future per-board quirk without having to retrofit device trees already in the field. Signed-off-by: Jonas Jelonek Reviewed-by: Oleksij Rempel Reviewed-by: Kory Maincent Link: https://patch.msgid.link/20260813222036.873930-2-jelonek.jonas@gmail.com Signed-off-by: Paolo Abeni --- .../net/pse-pd/realtek,pse-mcu-gen1.yaml | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu-gen1.yaml diff --git a/Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu-gen1.yaml b/Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu-gen1.yaml new file mode 100644 index 000000000000..3bb32349c28c --- /dev/null +++ b/Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu-gen1.yaml @@ -0,0 +1,182 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/pse-pd/realtek,pse-mcu-gen1.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Realtek PSE MCU + +maintainers: + - Jonas Jelonek + +description: | + A microcontroller (MCU) that manages the PSE (Power Sourcing Equipment) + hardware on a range of managed PoE switches. The host CPU talks only to + this MCU - over I2C/SMBus or UART - using a small message-based protocol; + the PSE silicon it drives sits behind the MCU and is never accessed + directly. For example, on the Zyxel GS1900-10HP the SoC reaches the MCU + over UART, and the MCU manages the on-board PSE chip. + + This binding describes the MCU together with its Realtek firmware: the + firmware and its host protocol, which are stable across boards. The + microcontroller silicon is a general-purpose part that varies, and the + PSE silicon behind the MCU (Realtek RTL823x/RTL8239* or Broadcom + BCM59xxx) is reported by the MCU and detected at runtime - neither is + named here. + + Two protocol generations exist, both Realtek's: + gen1 older boards, where the MCU fronts Broadcom PSE silicon + gen2 the altered protocol used with Realtek's own PSE silicon + + On an I2C attachment the framing the MCU firmware expects is part of the + compatible: '-smbus' (reads carry a leading command byte and a repeated + start) or '-i2c' (bare block writes and reads). A UART attachment carries + no framing suffix; the transport is given by the parent 'serial' node. + + Each board additionally carries a device-specific compatible that falls + back to one of the protocol compatibles above. Drivers bind on the + protocol compatible; the device-specific string identifies the board and + reserves a place for a future per-board quirk without having to retrofit + device trees already in the field. + +properties: + compatible: + oneOf: + # UART + - items: + - enum: + - zyxel,gs1900-10hp-a1-pse + - const: realtek,pse-mcu-gen1 + + # I2C, SMBus framing + - items: + - enum: + - zyxel,gs1920-24hp-v2-pse + - const: realtek,pse-mcu-gen1-smbus + + # UART + - items: + - enum: + - zyxel,gs1900-10hp-b1-pse + - zyxel,xmg1915-10ep-pse + - const: realtek,pse-mcu-gen2 + + # I2C, SMBus framing + - items: + - enum: + - zyxel,xs1930-12hp-pse + - const: realtek,pse-mcu-gen2-smbus + + # I2C, raw framing + - items: + - enum: + - linksys,lgs328mpc-v2-pse + - const: realtek,pse-mcu-gen2-i2c + + reg: + maxItems: 1 + + reset-gpios: + description: Reset line of the MCU. + maxItems: 1 + + disable-ports-gpios: + description: + Hardware gate that forces all ports into admin-disabled state while + asserted. + maxItems: 1 + +required: + - compatible + +allOf: + - $ref: pse-controller.yaml# + # A '-smbus'/'-i2c' compatible is an I2C attachment: it has 'reg' and + # cannot carry serial bus properties. A bare gen compatible is a UART + # attachment: no 'reg', the transport comes from the parent serial node. + - if: + properties: + compatible: + contains: + enum: + - realtek,pse-mcu-gen1-smbus + - realtek,pse-mcu-gen2-smbus + - realtek,pse-mcu-gen2-i2c + then: + required: + - reg + properties: + current-speed: false + max-speed: false + else: + allOf: + - $ref: /schemas/serial/serial-peripheral-props.yaml# + + properties: + reg: false + +unevaluatedProperties: false + +examples: + # SMBus-framed I2C attachment + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + ethernet-pse@20 { + compatible = "zyxel,xs1930-12hp-pse", "realtek,pse-mcu-gen2-smbus"; + reg = <0x20>; + + pse-pis { + #address-cells = <1>; + #size-cells = <0>; + + pse-pi@0 { + reg = <0>; + #pse-cells = <0>; + }; + }; + }; + }; + + # Raw-I2C-framed attachment + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + ethernet-pse@20 { + compatible = "linksys,lgs328mpc-v2-pse", "realtek,pse-mcu-gen2-i2c"; + reg = <0x20>; + + pse-pis { + #address-cells = <1>; + #size-cells = <0>; + + pse-pi@0 { + reg = <0>; + #pse-cells = <0>; + }; + }; + }; + }; + + # UART attachment + - | + serial { + ethernet-pse { + compatible = "zyxel,gs1900-10hp-a1-pse", "realtek,pse-mcu-gen1"; + current-speed = <19200>; + + pse-pis { + #address-cells = <1>; + #size-cells = <0>; + + pse-pi@0 { + reg = <0>; + #pse-cells = <0>; + }; + }; + }; + }; From 44566e614d84a43cb35a14c00f088d6c36b7d5c6 Mon Sep 17 00:00:00 2001 From: Jonas Jelonek Date: Thu, 13 Aug 2026 22:20:33 +0000 Subject: [PATCH 1416/1433] net: pse-pd: add Realtek PSE MCU core A range of managed Realtek-based PoE switches use a small microcontroller on the PCB to front the actual PSE silicon. The host CPU talks to that MCU over I2C/SMBus or UART using a fixed 12-byte request/response protocol with a trailing checksum; the PSE chips are managed by the MCU and are not accessed directly. Two generations of the protocol exist - both Realtek's - diverging in opcode numbering and a few response layouts; the driver handles this with a per-dialect opcode table and parser hooks for the responses that differ, selected by the compatible. The specific PSE chip behind the MCU is detected at runtime and only influences per-chip constants (power scaling and the per-port cap). This core module implements the protocol, message framing, the dialect machinery and the pse_controller_ops glue, and exports a registration helper for transport modules. The I2C and UART transports that drive it follow in the next patches; the core (PSE_REALTEK_MCU) is selected automatically by those transports and is not user-selectable on its own. The realtek-pse-mcu-* files and PSE_REALTEK_MCU* symbols match the realtek,pse-mcu-* compatibles (see the binding for the naming rationale). The two protocol generations - gen1 on older Broadcom-PSE boards, gen2 on Realtek's own PSE silicon - are both Realtek's, handled by the same shared core, each selecting its dialect via the compatible. Power budgeting is left to the MCU firmware; the driver advertises PSE_BUDGET_EVAL_STRAT_DYNAMIC accordingly. Signed-off-by: Jonas Jelonek Link: https://patch.msgid.link/20260813222036.873930-3-jelonek.jonas@gmail.com Signed-off-by: Paolo Abeni --- MAINTAINERS | 7 + drivers/net/pse-pd/Kconfig | 6 + drivers/net/pse-pd/Makefile | 1 + drivers/net/pse-pd/realtek-pse-mcu-core.c | 998 ++++++++++++++++++++++ drivers/net/pse-pd/realtek-pse-mcu.h | 94 ++ 5 files changed, 1106 insertions(+) create mode 100644 drivers/net/pse-pd/realtek-pse-mcu-core.c create mode 100644 drivers/net/pse-pd/realtek-pse-mcu.h diff --git a/MAINTAINERS b/MAINTAINERS index 94a468d66abf..f68dd1b99339 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22768,6 +22768,13 @@ S: Maintained F: Documentation/devicetree/bindings/watchdog/realtek,otto-wdt.yaml F: drivers/watchdog/realtek_otto_wdt.c +REALTEK PSE MCU DRIVER +M: Jonas Jelonek +L: netdev@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu-gen1.yaml +F: drivers/net/pse-pd/realtek-pse-mcu* + REALTEK RTL83xx SMI DSA ROUTER CHIPS M: Linus Walleij M: Luiz Angelo Daros de Luca diff --git a/drivers/net/pse-pd/Kconfig b/drivers/net/pse-pd/Kconfig index 7ef29657ee5d..3b0c245a2bc7 100644 --- a/drivers/net/pse-pd/Kconfig +++ b/drivers/net/pse-pd/Kconfig @@ -13,6 +13,12 @@ menuconfig PSE_CONTROLLER if PSE_CONTROLLER +config PSE_REALTEK_MCU + tristate + help + Shared core for the Realtek PSE MCU driver. This is selected + automatically by the transport options below. + config PSE_REGULATOR tristate "Regulator based PSE controller" help diff --git a/drivers/net/pse-pd/Makefile b/drivers/net/pse-pd/Makefile index cc78f7ea7f5f..bf35e2a5b110 100644 --- a/drivers/net/pse-pd/Makefile +++ b/drivers/net/pse-pd/Makefile @@ -3,6 +3,7 @@ obj-$(CONFIG_PSE_CONTROLLER) += pse_core.o +obj-$(CONFIG_PSE_REALTEK_MCU) += realtek-pse-mcu-core.o obj-$(CONFIG_PSE_REGULATOR) += pse_regulator.o obj-$(CONFIG_PSE_PD692X0) += pd692x0.o obj-$(CONFIG_PSE_SI3474) += si3474.o diff --git a/drivers/net/pse-pd/realtek-pse-mcu-core.c b/drivers/net/pse-pd/realtek-pse-mcu-core.c new file mode 100644 index 000000000000..e64f13ea6e4e --- /dev/null +++ b/drivers/net/pse-pd/realtek-pse-mcu-core.c @@ -0,0 +1,998 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the microcontroller (MCU) fronting PSE silicon on various + * Realtek-based managed switches. The MCU speaks a 12-byte fixed-frame + * management protocol; this driver covers two generations of the + * protocol via a per-dialect opcode table and response parsers. + * + * Many PoE switch designs put a dedicated microcontroller in front of the + * actual PSE silicon: the host CPU talks to the MCU over I2C/SMBus or + * UART, and the MCU in turn manages the PSE chips on the board. The MCU + * speaks a small message-based protocol. The PSE chips themselves are not + * accessed directly; everything goes through MCU commands. + * + * This driver targets that architecture for the Realtek-family protocol. + * Two generations are supported: Gen1 being used on older switches where + * the MCU fronts and manages Broadcom PSE silicon; Gen2 being used with + * Realtek PSE silicon. The two share frame format and a sum-mod-256 + * checksum but diverge on opcode numbers and on a few response layouts; + * this is handled by the per-dialect opcode table and parser hooks. + * + * Out of scope: PSE chips that are interfaced directly from the host + * without a management MCU, MCU designs that speak an unrelated protocol + * family, and "dumb PSE" modes where no host control is wired up at all. + * + * This core module implements the protocol, decoding/encoding of MCU + * responses, and the pse_controller_ops integration. Transport modules + * (realtek-pse-mcu-i2c, realtek-pse-mcu-uart) provide the send/recv + * callbacks. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "realtek-pse-mcu.h" + +#define RTPSE_MCU_DEVICE_ID_RTL8238B 0x0138 +#define RTPSE_MCU_DEVICE_ID_RTL8239 0x0039 +#define RTPSE_MCU_DEVICE_ID_RTL8239C 0x0139 +#define RTPSE_MCU_DEVICE_ID_BCM59111 0xe111 +#define RTPSE_MCU_DEVICE_ID_BCM59121 0xe121 + +#define RTPSE_MCU_PORT_STS_DISABLED 0x00 +#define RTPSE_MCU_PORT_STS_SEARCHING 0x01 +#define RTPSE_MCU_PORT_STS_DELIVERING 0x02 +#define RTPSE_MCU_PORT_STS_TEST 0x03 /* Gen1-only; reserved on Gen2 */ +#define RTPSE_MCU_PORT_STS_FAULT 0x04 +#define RTPSE_MCU_PORT_STS_OTHER_FAULT 0x05 /* Gen1-only; reserved on Gen2 */ +#define RTPSE_MCU_PORT_STS_REQUESTING 0x06 + +/* RTPSE_MCU_PORT_SET_POWER_LIMIT_TYPE values */ +#define RTPSE_MCU_PORT_PW_LIMIT_TYPE_USER 0x02 + +#define RTPSE_MCU_MAX_PORTS 48 +#define RTPSE_MCU_PORT_MAX_PRIORITY 3 + +/* Bounded resends when the MCU replies NOT_READY (busy). */ +#define RTPSE_MCU_NOT_READY_RETRIES 3 + +/* Nominal PSE rail; 802.3at/bt operating range. */ +#define RTPSE_MCU_PSE_VOLTAGE_UV 54000000 + +enum rtpse_mcu_cmd { + RTPSE_MCU_CMD_SET_GLOBAL_STATE, + RTPSE_MCU_CMD_GET_SYSTEM_INFO, + RTPSE_MCU_CMD_GET_EXT_CONFIG, + + RTPSE_MCU_CMD_PORT_ENABLE, + RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE, + RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT, + RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT, + RTPSE_MCU_CMD_PORT_SET_PRIORITY, + RTPSE_MCU_CMD_PORT_GET_STATUS, + RTPSE_MCU_CMD_PORT_GET_POWER_STATS, + RTPSE_MCU_CMD_PORT_GET_CONFIG, + RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG, + + RTPSE_MCU_NUM_CMDS, +}; + +struct rtpse_mcu_opcode { + u8 op; + bool valid; +}; + +/* Shorthand for the designated-initializer entries in dialect opcode tables. */ +#define RTPSE_MCU_OP(opc) { .op = (opc), .valid = true } + +/* Parsed MCU response structures (decoded from rtpse_mcu_msg replies) */ + +struct rtpse_mcu_info { + u8 max_ports; + bool system_enable; + u16 device_id; + u8 mcu_type; +}; + +struct rtpse_mcu_ext_config { + u8 num_of_pses; +}; + +struct rtpse_mcu_port_status { + u8 sts1; + u8 sts2; + u8 sts3; +}; + +struct rtpse_mcu_port_measurement { + u16 voltage_raw; /* 64.45mV/LSB */ + u16 current_raw; /* 1mA/LSB */ + u16 temperature_raw; /* T(mC) = 1250 * (220 - raw) */ + u16 power_raw; /* 100mW/LSB */ +}; + +struct rtpse_mcu_port_config { + bool enable; +}; + +struct rtpse_mcu_port_ext_config { + u8 max_power; + u8 priority; +}; + +struct rtpse_mcu_dialect { + struct rtpse_mcu_opcode opcode[RTPSE_MCU_NUM_CMDS]; + + /* + * Response parsers for the fields that differ between dialects; each + * dialect supplies its own. Other responses share one layout and are + * decoded directly - a dialect that diverges there must add a hook, + * as a mismatched layout cannot be detected (the checksum still passes). + */ + void (*parse_system_info)(const u8 *payload, struct rtpse_mcu_info *info); + int (*parse_port_class)(const struct rtpse_mcu_port_status *status); + const char *(*mcu_type_str)(unsigned int mcu_type); +}; + +struct rtpse_mcu_chip_info { + const char *name; + u32 max_mW_per_port; + enum rtpse_mcu_cmd pw_set_cmd; /* command used by set_pw_limit */ + u32 pw_set_lsb_mW; /* LSB of pw_set_cmd value, in mW */ + u32 pw_read_lsb_mW; /* LSB of ext_config.max_power read-back, in mW */ +}; + +static const struct rtpse_mcu_chip_info rtl8238b_info = { + .max_mW_per_port = 30000, + .name = "RTL8238B", + .pw_read_lsb_mW = 200, + .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT, + .pw_set_lsb_mW = 200, +}; + +static const struct rtpse_mcu_chip_info rtl8239_info = { + .max_mW_per_port = 90000, + .name = "RTL8239", + .pw_read_lsb_mW = 400, + .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT, + .pw_set_lsb_mW = 400, +}; + +static const struct rtpse_mcu_chip_info rtl8239c_info = { + .max_mW_per_port = 90000, + .name = "RTL8239C", + .pw_read_lsb_mW = 400, + .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT, + .pw_set_lsb_mW = 400, +}; + +static const struct rtpse_mcu_chip_info bcm59111_info = { + .max_mW_per_port = 30000, + .name = "BCM59111", + .pw_read_lsb_mW = 200, + .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT, + .pw_set_lsb_mW = 200, +}; + +static const struct rtpse_mcu_chip_info bcm59121_info = { + /* + * BCM59121 is a 60W Type-3 part, but known boards run it at 802.3at + * and the Gen1 dialect has only the 8-bit/0.2W set command (<=51W); + * cap at the 30W the hardware actually offers. + */ + .max_mW_per_port = 30000, + .name = "BCM59121", + .pw_read_lsb_mW = 200, + .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT, + .pw_set_lsb_mW = 200, +}; + +/* Helpers and basic functions */ + +static struct rtpse_mcu_ctrl *to_rtpse_mcu_ctrl(struct pse_controller_dev *pcdev) +{ + return container_of(pcdev, struct rtpse_mcu_ctrl, pcdev); +} + +static void rtpse_mcu_msg_init(struct rtpse_mcu_msg *msg, u8 opcode) +{ + memset(msg, 0xff, sizeof(*msg)); + msg->opcode = opcode; +} + +static u8 rtpse_mcu_checksum(const u8 *buf, size_t len) +{ + u8 sum = 0; + + while (len--) + sum += *buf++; + return sum; +} + +static int rtpse_mcu_do_xfer(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_msg *req, + struct rtpse_mcu_msg *resp) +{ + unsigned int tries; + int ret; + + for (tries = 0; ; tries++) { + scoped_guard(mutex, &pse->mutex) { + /* Rolling seq_num (skip 0) so a stale/all-zero reply can't match. */ + if (++pse->seq == 0) + pse->seq = 1; + req->seq_num = pse->seq; + req->checksum = rtpse_mcu_checksum((u8 *)req, RTPSE_MCU_MSG_SIZE - 1); + + ret = pse->transport->send(pse, req); + if (ret) + return ret; + + /* Pace the base reply delay; the transport waits its own way. */ + msleep(RTPSE_MCU_RESPONSE_MS); + + memset(resp, 0, sizeof(*resp)); + ret = pse->transport->recv(pse, req, resp); + if (ret) + return ret; + } + + /* NOT_READY: MCU busy, wants the command resent; bounded retry. */ + if (resp->opcode != RTPSE_MCU_OPCODE_NOT_READY || + tries >= RTPSE_MCU_NOT_READY_RETRIES) + break; + msleep(RTPSE_MCU_RESPONSE_MS); + } + + /* Explicit MCU error opcodes (Gen1); map to a meaningful errno. */ + switch (resp->opcode) { + case RTPSE_MCU_OPCODE_INCOMPLETE: + return -EBADE; + case RTPSE_MCU_OPCODE_BAD_CSUM: + return -EBADMSG; + case RTPSE_MCU_OPCODE_NOT_READY: + return -EAGAIN; + } + + if (resp->opcode != req->opcode || + resp->seq_num != req->seq_num || + resp->checksum != rtpse_mcu_checksum((u8 *)resp, RTPSE_MCU_MSG_SIZE - 1)) + return -EBADMSG; + + return 0; +} + +static int rtpse_mcu_port_query(struct rtpse_mcu_ctrl *pse, unsigned int port, u8 opcode, + struct rtpse_mcu_msg *resp) +{ + struct rtpse_mcu_msg req; + int ret; + + rtpse_mcu_msg_init(&req, opcode); + req.payload[0] = port; + + ret = rtpse_mcu_do_xfer(pse, &req, resp); + if (ret) + return ret; + + if (resp->payload[0] != port) + return -EIO; + + return 0; +} + +static int rtpse_mcu_port_cmd(struct rtpse_mcu_ctrl *pse, unsigned int port, u8 opcode, u8 arg) +{ + struct rtpse_mcu_msg req, resp; + int ret; + + rtpse_mcu_msg_init(&req, opcode); + req.payload[0] = port; + req.payload[1] = arg; + + ret = rtpse_mcu_do_xfer(pse, &req, &resp); + if (ret) + return ret; + + if (resp.payload[0] != port || resp.payload[1] != 0) + return -EIO; + + return 0; +} + +/* Global operations */ + +static int rtpse_mcu_get_info(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_info *info) +{ + struct rtpse_mcu_msg req, resp; + const struct rtpse_mcu_opcode *opc; + int ret; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_GET_SYSTEM_INFO]; + if (!opc->valid) + return -EOPNOTSUPP; + + rtpse_mcu_msg_init(&req, opc->op); + ret = rtpse_mcu_do_xfer(pse, &req, &resp); + if (ret) + return ret; + + pse->dialect->parse_system_info(resp.payload, info); + return 0; +} + +static int rtpse_mcu_get_ext_config(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_ext_config *config) +{ + struct rtpse_mcu_msg req, resp; + const struct rtpse_mcu_opcode *opc; + int ret; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_GET_EXT_CONFIG]; + if (!opc->valid) + return -EOPNOTSUPP; + + rtpse_mcu_msg_init(&req, opc->op); + ret = rtpse_mcu_do_xfer(pse, &req, &resp); + if (ret) + return ret; + + config->num_of_pses = resp.payload[6]; + + return 0; +} + +static int rtpse_mcu_set_global_state(struct rtpse_mcu_ctrl *pse, bool enable) +{ + struct rtpse_mcu_msg req, resp; + const struct rtpse_mcu_opcode *opc; + int ret; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_SET_GLOBAL_STATE]; + if (!opc->valid) + return -EOPNOTSUPP; + + rtpse_mcu_msg_init(&req, opc->op); + req.payload[0] = enable ? 0x1 : 0x0; + + ret = rtpse_mcu_do_xfer(pse, &req, &resp); + if (ret) + return ret; + + return (resp.payload[0] == 0x0) ? 0 : -EIO; +} + +/* Port operations */ + +static int rtpse_mcu_port_get_status(struct rtpse_mcu_ctrl *pse, unsigned int port, + struct rtpse_mcu_port_status *status) +{ + const struct rtpse_mcu_opcode *opc; + struct rtpse_mcu_msg resp; + int ret; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_STATUS]; + if (!opc->valid) + return -EOPNOTSUPP; + + ret = rtpse_mcu_port_query(pse, port, opc->op, &resp); + if (ret) + return ret; + + status->sts1 = resp.payload[1]; + status->sts2 = resp.payload[2]; + status->sts3 = resp.payload[3]; + + return 0; +} + +static int rtpse_mcu_port_get_measurement(struct rtpse_mcu_ctrl *pse, unsigned int port, + struct rtpse_mcu_port_measurement *measurement) +{ + const struct rtpse_mcu_opcode *opc; + struct rtpse_mcu_msg resp; + int ret; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_POWER_STATS]; + if (!opc->valid) + return -EOPNOTSUPP; + + ret = rtpse_mcu_port_query(pse, port, opc->op, &resp); + if (ret) + return ret; + + measurement->voltage_raw = get_unaligned_be16(&resp.payload[1]); + measurement->current_raw = get_unaligned_be16(&resp.payload[3]); + measurement->temperature_raw = get_unaligned_be16(&resp.payload[5]); + measurement->power_raw = get_unaligned_be16(&resp.payload[7]); + + return 0; +} + +static int rtpse_mcu_port_get_config(struct rtpse_mcu_ctrl *pse, unsigned int port, + struct rtpse_mcu_port_config *config) +{ + const struct rtpse_mcu_opcode *opc; + struct rtpse_mcu_msg resp; + int ret; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_CONFIG]; + if (!opc->valid) + return -EOPNOTSUPP; + + ret = rtpse_mcu_port_query(pse, port, opc->op, &resp); + if (ret) + return ret; + + config->enable = (resp.payload[1] == 1); + + return 0; +} + +static int rtpse_mcu_port_get_ext_config(struct rtpse_mcu_ctrl *pse, unsigned int port, + struct rtpse_mcu_port_ext_config *config) +{ + const struct rtpse_mcu_opcode *opc; + struct rtpse_mcu_msg resp; + int ret; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG]; + if (!opc->valid) + return -EOPNOTSUPP; + + ret = rtpse_mcu_port_query(pse, port, opc->op, &resp); + if (ret) + return ret; + + config->max_power = resp.payload[3]; + config->priority = resp.payload[4]; + + return 0; +} + +static int rtpse_mcu_port_set_state(struct rtpse_mcu_ctrl *pse, unsigned int port, bool enable) +{ + const struct rtpse_mcu_opcode *opc; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_ENABLE]; + if (!opc->valid) + return -EOPNOTSUPP; + + return rtpse_mcu_port_cmd(pse, port, opc->op, enable ? 0x1 : 0x0); +} + +/* PSE controller ops */ + +static int rtpse_mcu_port_get_admin_state(struct pse_controller_dev *pcdev, int id, + struct pse_admin_state *admin_state) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct rtpse_mcu_port_config config; + int ret; + + ret = rtpse_mcu_port_get_config(pse, id, &config); + if (ret) + return ret; + + admin_state->c33_admin_state = config.enable ? ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED : + ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED; + return 0; +} + +static int rtpse_mcu_port_get_pw_status(struct pse_controller_dev *pcdev, int id, + struct pse_pw_status *pw_status) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct rtpse_mcu_port_status status; + int ret; + + ret = rtpse_mcu_port_get_status(pse, id, &status); + if (ret) + return ret; + + switch (status.sts1) { + case RTPSE_MCU_PORT_STS_DISABLED: + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED; + break; + case RTPSE_MCU_PORT_STS_SEARCHING: + case RTPSE_MCU_PORT_STS_REQUESTING: + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_SEARCHING; + break; + case RTPSE_MCU_PORT_STS_DELIVERING: + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING; + break; + case RTPSE_MCU_PORT_STS_TEST: + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_TEST; + break; + case RTPSE_MCU_PORT_STS_FAULT: + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_FAULT; + break; + case RTPSE_MCU_PORT_STS_OTHER_FAULT: + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_OTHERFAULT; + break; + default: + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_UNKNOWN; + break; + } + + return 0; +} + +static int rtpse_mcu_port_get_pw_class(struct pse_controller_dev *pcdev, int id) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct rtpse_mcu_port_status status; + int ret; + + ret = rtpse_mcu_port_get_status(pse, id, &status); + if (ret) + return ret; + + /* + * As per datasheet, the classification result is only valid when in + * one of those operational modes, otherwise not. + */ + switch (status.sts1) { + case RTPSE_MCU_PORT_STS_DISABLED: + case RTPSE_MCU_PORT_STS_SEARCHING: + case RTPSE_MCU_PORT_STS_DELIVERING: + case RTPSE_MCU_PORT_STS_REQUESTING: + return pse->dialect->parse_port_class(&status); + default: + /* + * No class to report, return 0 instead. This is indistinguishable + * from a real class-0 PD but userspace disambiguates via the + * power status. + */ + return 0; + } +} + +static int rtpse_mcu_port_get_actual_pw(struct pse_controller_dev *pcdev, int id) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct rtpse_mcu_port_measurement measurement; + int ret; + + ret = rtpse_mcu_port_get_measurement(pse, id, &measurement); + if (ret) + return ret; + + /* 100mW per LSB */ + return measurement.power_raw * 100U; +} + +static int rtpse_mcu_port_get_voltage(struct pse_controller_dev *pcdev, int id) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct rtpse_mcu_port_measurement measurement; + int ret; + u32 uV; + + ret = rtpse_mcu_port_get_measurement(pse, id, &measurement); + if (ret) + return ret; + + /* 64.45mV per LSB */ + uV = measurement.voltage_raw * 64450U; + + /* + * Idle ports measure 0V, which the core rejects when turning a power + * limit into a current limit. Fall back to the nominal rail so a limit + * can be set before a PD is attached. + */ + if (!uV) + return RTPSE_MCU_PSE_VOLTAGE_UV; + + return min_t(u32, uV, INT_MAX); +} + +static int rtpse_mcu_port_enable(struct pse_controller_dev *pcdev, int id) +{ + return rtpse_mcu_port_set_state(to_rtpse_mcu_ctrl(pcdev), id, true); +} + +static int rtpse_mcu_port_disable(struct pse_controller_dev *pcdev, int id) +{ + return rtpse_mcu_port_set_state(to_rtpse_mcu_ctrl(pcdev), id, false); +} + +static int rtpse_mcu_port_get_pw_limit(struct pse_controller_dev *pcdev, int id) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct rtpse_mcu_port_ext_config config; + int ret; + + ret = rtpse_mcu_port_get_ext_config(pse, id, &config); + if (ret) + return ret; + + /* + * The MCU's raw max_power byte can scale above the chip's rated cap; + * clamp to the same bound set_pw_limit() and the advertised range use. + */ + return min_t(u32, config.max_power * pse->chip->pw_read_lsb_mW, + pse->chip->max_mW_per_port); +} + +static int rtpse_mcu_port_set_pw_limit(struct pse_controller_dev *pcdev, int id, int max_mW) +{ + const struct rtpse_mcu_opcode *type_opc, *val_opc; + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + const struct rtpse_mcu_chip_info *chip = pse->chip; + u8 prg_val; + int ret; + + if (max_mW < 0 || max_mW > chip->max_mW_per_port) + return -ERANGE; + + type_opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE]; + val_opc = &pse->dialect->opcode[chip->pw_set_cmd]; + /* pw_set_lsb_mW is the divisor below; reject a chip that lacks it. */ + if (!type_opc->valid || !val_opc->valid || !chip->pw_set_lsb_mW) + return -EOPNOTSUPP; + + /* + * Round up so a sub-LSB request maps to one LSB, not silently to 0; + * an explicit 0 still yields 0, and LSB-aligned maxima can't overshoot. + */ + prg_val = min_t(unsigned int, DIV_ROUND_UP(max_mW, chip->pw_set_lsb_mW), U8_MAX); + + /* + * Program the value before switching to user-defined mode. The two + * commands aren't atomic, but this order never leaves a stale cap: a + * failure keeps the previous cap, or (already user mode) the requested. + */ + ret = rtpse_mcu_port_cmd(pse, id, val_opc->op, prg_val); + if (ret) + return ret; + + return rtpse_mcu_port_cmd(pse, id, type_opc->op, RTPSE_MCU_PORT_PW_LIMIT_TYPE_USER); +} + +static int rtpse_mcu_port_get_pw_limit_ranges(struct pse_controller_dev *pcdev, int id, + struct pse_pw_limit_ranges *out) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct ethtool_c33_pse_pw_limit_range *range; + + range = kzalloc_obj(*range); + if (!range) + return -ENOMEM; + + range[0].min = 0; + range[0].max = pse->chip->max_mW_per_port; + + out->c33_pw_limit_ranges = range; + return 1; +} + +static int rtpse_mcu_port_get_prio(struct pse_controller_dev *pcdev, int id) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct rtpse_mcu_port_ext_config config; + int ret; + + ret = rtpse_mcu_port_get_ext_config(pse, id, &config); + if (ret) + return ret; + + /* Clamp to the advertised max; set_prio() and pis_prio_max use the same bound. */ + return min_t(u8, config.priority, RTPSE_MCU_PORT_MAX_PRIORITY); +} + +static int rtpse_mcu_port_set_prio(struct pse_controller_dev *pcdev, int id, unsigned int prio) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + const struct rtpse_mcu_opcode *opc; + + if (prio > RTPSE_MCU_PORT_MAX_PRIORITY) + return -ERANGE; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_SET_PRIORITY]; + if (!opc->valid) + return -EOPNOTSUPP; + + return rtpse_mcu_port_cmd(pse, id, opc->op, prio); +} + +static const struct pse_controller_ops rtpse_mcu_ops = { + .pi_get_admin_state = rtpse_mcu_port_get_admin_state, + .pi_get_pw_status = rtpse_mcu_port_get_pw_status, + .pi_get_pw_class = rtpse_mcu_port_get_pw_class, + .pi_get_actual_pw = rtpse_mcu_port_get_actual_pw, + .pi_enable = rtpse_mcu_port_enable, + .pi_disable = rtpse_mcu_port_disable, + .pi_get_voltage = rtpse_mcu_port_get_voltage, + .pi_get_pw_limit = rtpse_mcu_port_get_pw_limit, + .pi_set_pw_limit = rtpse_mcu_port_set_pw_limit, + .pi_get_pw_limit_ranges = rtpse_mcu_port_get_pw_limit_ranges, + .pi_get_prio = rtpse_mcu_port_get_prio, + .pi_set_prio = rtpse_mcu_port_set_prio, +}; + +static int rtpse_mcu_discover(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_info *info) +{ + struct rtpse_mcu_ext_config ext_config; + unsigned long deadline; + int ret; + + /* + * A booting MCU may stay silent (-ETIMEDOUT), not ACK its address + * (-ENXIO / -EREMOTEIO), report not-ready (-EAGAIN), or emit a + * corrupt/partial frame (-EBADMSG / -EBADE). Retry those within a + * bounded window; other errors (e.g. -EOPNOTSUPP) are fatal and fail + * immediately. + */ + deadline = jiffies + msecs_to_jiffies(RTPSE_MCU_BOOT_TIMEOUT_MS); + do { + ret = rtpse_mcu_get_info(pse, info); + if (ret != -ETIMEDOUT && ret != -ENXIO && ret != -EREMOTEIO && + ret != -EAGAIN && ret != -EBADMSG && ret != -EBADE) + break; + msleep(RTPSE_MCU_BOOT_RETRY_MS); + } while (time_before(jiffies, deadline)); + if (ret) + return dev_err_probe(pse->dev, ret, "failed to read MCU info\n"); + + switch (info->device_id) { + case RTPSE_MCU_DEVICE_ID_RTL8238B: + pse->chip = &rtl8238b_info; + break; + case RTPSE_MCU_DEVICE_ID_RTL8239: + pse->chip = &rtl8239_info; + break; + case RTPSE_MCU_DEVICE_ID_RTL8239C: + pse->chip = &rtl8239c_info; + break; + case RTPSE_MCU_DEVICE_ID_BCM59111: + pse->chip = &bcm59111_info; + break; + case RTPSE_MCU_DEVICE_ID_BCM59121: + pse->chip = &bcm59121_info; + break; + default: + return dev_err_probe(pse->dev, -EINVAL, "unknown PSE id 0x%x\n", + info->device_id); + } + + if (!info->max_ports || info->max_ports > RTPSE_MCU_MAX_PORTS) + return dev_err_probe(pse->dev, -EINVAL, + "MCU reports invalid port count %u\n", info->max_ports); + + ret = rtpse_mcu_get_ext_config(pse, &ext_config); + if (ret) + return dev_err_probe(pse->dev, ret, "failed to read MCU ext config\n"); + + dev_info(pse->dev, "%s MCU, %s (id 0x%04x), %u ports across %u PSE chip(s)\n", + pse->dialect->mcu_type_str(info->mcu_type), pse->chip->name, + info->device_id, info->max_ports, ext_config.num_of_pses); + return 0; +} + +static void rtpse_mcu_global_disable(void *data) +{ + struct rtpse_mcu_ctrl *pse = data; + + rtpse_mcu_set_global_state(pse, false); +} + +int rtpse_mcu_register(struct rtpse_mcu_ctrl *pse) +{ + const struct rtpse_mcu_match_data *match; + struct rtpse_mcu_info info; + struct gpio_desc *gpiod; + int ret; + + BUILD_BUG_ON(sizeof(struct rtpse_mcu_msg) != RTPSE_MCU_MSG_SIZE); + + ret = devm_mutex_init(pse->dev, &pse->mutex); + if (ret) + return ret; + + match = device_get_match_data(pse->dev); + if (!match) + return dev_err_probe(pse->dev, -ENODEV, "missing match data\n"); + pse->dialect = match->dialect; + + /* + * Catch a dialect that forgot to set one of the required hooks at + * probe time, rather than NULL-deref'ing later from a fast path. + */ + if (!pse->dialect || + !pse->dialect->parse_system_info || + !pse->dialect->parse_port_class || + !pse->dialect->mcu_type_str) + return dev_err_probe(pse->dev, -EINVAL, + "dialect for chip is incomplete\n"); + + /* + * Release the MCU from reset before the first transaction; the + * boot-retry loop in discover() waits for it to answer. + */ + gpiod = devm_gpiod_get_optional(pse->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(gpiod)) + return dev_err_probe(pse->dev, PTR_ERR(gpiod), + "failed to get reset gpio\n"); + + ret = rtpse_mcu_discover(pse, &info); + if (ret) + return ret; + + /* + * Some boards gate all ports through a hardware line; deassert it only + * after the MCU is confirmed, so a discover failure never ungates the + * ports. It is then left to the MCU - not re-gated on unbind or a later + * probe error - so a driver reload doesn't black out PoE. + */ + gpiod = devm_gpiod_get_optional(pse->dev, "disable-ports", GPIOD_OUT_LOW); + if (IS_ERR(gpiod)) + return dev_err_probe(pse->dev, PTR_ERR(gpiod), + "failed to get disable-ports gpio\n"); + + if (!info.system_enable) { + ret = rtpse_mcu_set_global_state(pse, true); + /* Dialects without a global-state concept (e.g. Gen1) return + * -EOPNOTSUPP; treat that as "no separate enable required". + */ + if (ret && ret != -EOPNOTSUPP) + return dev_err_probe(pse->dev, ret, + "failed to enable PSE system\n"); + if (!ret) { + ret = devm_add_action_or_reset(pse->dev, + rtpse_mcu_global_disable, pse); + if (ret) + return ret; + } + } + + /* + * Depending on the MCU firmware configuration (which might be different + * for every board), it isn't known whether the PoE subsystem is active or + * inactive by default. At this stage, the PSE chips might already deliver + * power to PDs without any explicit enable. + */ + + /* pcdev.owner is set by the transport, so the registered controller + * pins the transport module that owns the live device, not the core. + */ + pse->pcdev.ops = &rtpse_mcu_ops; + pse->pcdev.dev = pse->dev; + pse->pcdev.types = ETHTOOL_PSE_C33; + pse->pcdev.nr_lines = info.max_ports; + pse->pcdev.pis_prio_max = RTPSE_MCU_PORT_MAX_PRIORITY; + pse->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_DYNAMIC; + + return devm_pse_controller_register(pse->dev, &pse->pcdev); +} +EXPORT_SYMBOL_GPL(rtpse_mcu_register); + +static void rtpse_mcu_gen2_parse_system_info(const u8 *payload, struct rtpse_mcu_info *info) +{ + info->max_ports = payload[1]; + info->system_enable = (payload[2] == 0x1); + info->device_id = get_unaligned_be16(&payload[3]); + info->mcu_type = payload[6]; +} + +static int rtpse_mcu_gen2_parse_port_class(const struct rtpse_mcu_port_status *status) +{ + /* Class lives in the upper nibble of sts2. */ + return FIELD_GET(GENMASK(7, 4), status->sts2); +} + +static const char *rtpse_mcu_gen2_mcu_type_str(unsigned int mcu_type) +{ + switch (mcu_type) { + case 0x00: return "GigaDevice GD32F310"; + case 0x01: return "GigaDevice GD32F230"; + case 0x02: return "GigaDevice GD32F303"; + case 0x03: return "GigaDevice GD32F103"; + case 0x04: return "GigaDevice GD32E103"; + case 0x10: return "Nuvoton M0516"; + case 0x11: return "Nuvoton M0564"; + case 0x12: return "Nuvoton NUC029"; + default: return "unknown"; + } +} + +static void rtpse_mcu_gen1_parse_system_info(const u8 *payload, struct rtpse_mcu_info *info) +{ + info->max_ports = payload[1]; + /* Gen1 has no explicit system_enable byte; the closest analog is the + * "remote enable" bit in the system-status flags at payload[7]. + */ + info->system_enable = !!(payload[7] & BIT(2)); + info->device_id = get_unaligned_be16(&payload[3]); + info->mcu_type = payload[6]; +} + +static int rtpse_mcu_gen1_parse_port_class(const struct rtpse_mcu_port_status *status) +{ + /* Gen1 puts the detected class in payload[3] (== sts3) directly. + * Mask to the low nibble; class is 0..8 and any high bits would be + * noise. + */ + return status->sts3 & 0x0f; +} + +static const char *rtpse_mcu_gen1_mcu_type_str(unsigned int mcu_type) +{ + switch (mcu_type) { + case 0x00: return "ST Micro ST32F100"; + case 0x01: return "Nuvoton M05xx LAN"; + case 0x02: return "ST Micro STF030C8"; + case 0x03: return "Nuvoton M058SAN"; + case 0x04: return "Nuvoton NUC122"; + default: return "unknown"; + } +} + +/* Map each logical command the core issues to its per-dialect opcode. */ +static const struct rtpse_mcu_dialect rtpse_mcu_dialect_gen2 = { + .parse_system_info = rtpse_mcu_gen2_parse_system_info, + .parse_port_class = rtpse_mcu_gen2_parse_port_class, + .mcu_type_str = rtpse_mcu_gen2_mcu_type_str, + .opcode = { + [RTPSE_MCU_CMD_SET_GLOBAL_STATE] = RTPSE_MCU_OP(0x00), + [RTPSE_MCU_CMD_GET_SYSTEM_INFO] = RTPSE_MCU_OP(0x40), + [RTPSE_MCU_CMD_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x4a), + + [RTPSE_MCU_CMD_PORT_ENABLE] = RTPSE_MCU_OP(0x01), + [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE] = RTPSE_MCU_OP(0x12), + [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT] = RTPSE_MCU_OP(0x13), + [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT] = RTPSE_MCU_OP(0x14), + [RTPSE_MCU_CMD_PORT_SET_PRIORITY] = RTPSE_MCU_OP(0x15), + [RTPSE_MCU_CMD_PORT_GET_STATUS] = RTPSE_MCU_OP(0x42), + [RTPSE_MCU_CMD_PORT_GET_POWER_STATS] = RTPSE_MCU_OP(0x44), + [RTPSE_MCU_CMD_PORT_GET_CONFIG] = RTPSE_MCU_OP(0x48), + [RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x49), + }, +}; + +static const struct rtpse_mcu_dialect rtpse_mcu_dialect_gen1 = { + .parse_system_info = rtpse_mcu_gen1_parse_system_info, + .parse_port_class = rtpse_mcu_gen1_parse_port_class, + .mcu_type_str = rtpse_mcu_gen1_mcu_type_str, + .opcode = { + [RTPSE_MCU_CMD_GET_SYSTEM_INFO] = RTPSE_MCU_OP(0x20), + [RTPSE_MCU_CMD_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x2b), + + [RTPSE_MCU_CMD_PORT_ENABLE] = RTPSE_MCU_OP(0x00), + [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE] = RTPSE_MCU_OP(0x15), + [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT] = RTPSE_MCU_OP(0x16), + [RTPSE_MCU_CMD_PORT_SET_PRIORITY] = RTPSE_MCU_OP(0x1a), + [RTPSE_MCU_CMD_PORT_GET_STATUS] = RTPSE_MCU_OP(0x21), + [RTPSE_MCU_CMD_PORT_GET_POWER_STATS] = RTPSE_MCU_OP(0x30), + [RTPSE_MCU_CMD_PORT_GET_CONFIG] = RTPSE_MCU_OP(0x25), + [RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x26), + }, +}; + +const struct rtpse_mcu_match_data rtpse_mcu_gen1_data = { + .dialect = &rtpse_mcu_dialect_gen1, +}; +EXPORT_SYMBOL_GPL(rtpse_mcu_gen1_data); + +const struct rtpse_mcu_match_data rtpse_mcu_gen2_data = { + .dialect = &rtpse_mcu_dialect_gen2, +}; +EXPORT_SYMBOL_GPL(rtpse_mcu_gen2_data); + +/* Same dialect as gen2, but the MCU expects raw-I2C framing. */ +const struct rtpse_mcu_match_data rtpse_mcu_gen2_i2c_data = { + .dialect = &rtpse_mcu_dialect_gen2, + .native_i2c = true, +}; +EXPORT_SYMBOL_GPL(rtpse_mcu_gen2_i2c_data); + +MODULE_AUTHOR("Jonas Jelonek "); +MODULE_DESCRIPTION("Realtek PSE MCU driver (core)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/pse-pd/realtek-pse-mcu.h b/drivers/net/pse-pd/realtek-pse-mcu.h new file mode 100644 index 000000000000..d75e91669b8f --- /dev/null +++ b/drivers/net/pse-pd/realtek-pse-mcu.h @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _REALTEK_PSE_MCU_H +#define _REALTEK_PSE_MCU_H + +#include +#include +#include + +/* + * Time the MCU itself needs between accepting a request and having a + * response ready. These are properties of the MCU firmware, not of the + * underlying transport: the core paces transactions by RTPSE_MCU_RESPONSE_MS + * and both transports size their per-transaction recv ceiling from + * RTPSE_MCU_RESPONSE_MAX_MS, since some commands are documented as + * needing up to ~1s to produce a reply. + */ +#define RTPSE_MCU_RESPONSE_MS 25 +#define RTPSE_MCU_RESPONSE_MAX_MS 1000 + +/* + * Total time to keep retrying the first MCU read at probe, and the pause + * between attempts. Right after reset-gpios is deasserted the MCU may not + * answer on the bus yet; give it a bounded window to come up before + * declaring the probe failed. + */ +#define RTPSE_MCU_BOOT_TIMEOUT_MS 3000 +#define RTPSE_MCU_BOOT_RETRY_MS 100 + +#define RTPSE_MCU_MSG_SIZE 12 + +struct rtpse_mcu_msg { + u8 opcode; + u8 seq_num; + u8 payload[9]; + u8 checksum; +} __packed; + +/* + * MCU status opcodes (seen on the Gen1 dialect; Gen2 never emits them). + * INCOMPLETE/BAD_CSUM are terminal; NOT_READY is transient. + */ +#define RTPSE_MCU_OPCODE_INCOMPLETE 0xfd /* -EBADE */ +#define RTPSE_MCU_OPCODE_BAD_CSUM 0xfe /* -EBADMSG */ +#define RTPSE_MCU_OPCODE_NOT_READY 0xff /* -EAGAIN */ + +/* + * A polling transport can stop here: the reply to this request (opcode and + * seq_num) or a terminal error. The seq_num rejects a stale normal reply; the + * terminal errors match unconditionally, as a request the MCU couldn't parse + * carries no seq_num to correlate against. + */ +static inline bool rtpse_mcu_resp_is_final(const struct rtpse_mcu_msg *req, + const struct rtpse_mcu_msg *resp) +{ + return (resp->opcode == req->opcode && resp->seq_num == req->seq_num) || + resp->opcode == RTPSE_MCU_OPCODE_INCOMPLETE || + resp->opcode == RTPSE_MCU_OPCODE_BAD_CSUM; +} + +/* Opaque to transports; defined in realtek-pse-mcu-core.c. */ +struct rtpse_mcu_dialect; +struct rtpse_mcu_chip_info; +struct rtpse_mcu_ctrl; + +/* Per-compatible match data (the of_match .data). */ +struct rtpse_mcu_match_data { + const struct rtpse_mcu_dialect *dialect; + bool native_i2c; /* raw-I2C framing (vs SMBus); I2C transport only */ +}; + +struct rtpse_mcu_transport_ops { + int (*send)(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req); + int (*recv)(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req, + struct rtpse_mcu_msg *resp); +}; + +struct rtpse_mcu_ctrl { + struct device *dev; + struct pse_controller_dev pcdev; + struct mutex mutex; /* serializes MCU request/response transactions */ + const struct rtpse_mcu_dialect *dialect; + const struct rtpse_mcu_chip_info *chip; + const struct rtpse_mcu_transport_ops *transport; + u8 seq; /* rolling request seq_num, echoed by the MCU */ +}; + +int rtpse_mcu_register(struct rtpse_mcu_ctrl *pse); + +extern const struct rtpse_mcu_match_data rtpse_mcu_gen1_data; +extern const struct rtpse_mcu_match_data rtpse_mcu_gen2_data; +extern const struct rtpse_mcu_match_data rtpse_mcu_gen2_i2c_data; + +#endif From 4c088d9eb32fe1e935ad651199d1f124f232cfb9 Mon Sep 17 00:00:00 2001 From: Jonas Jelonek Date: Thu, 13 Aug 2026 22:20:34 +0000 Subject: [PATCH 1417/1433] net: pse-pd: realtek-pse-mcu: add I2C transport Add the I2C/SMBus transport for the Realtek PSE MCU core. It registers the MCU on an I2C bus and provides the send/recv callbacks the core uses to exchange the 12-byte frames. The MCU firmware expects one of two framings on the I2C bus, and which one is part of the compatible: '-smbus' (reads carry a leading command byte and a repeated start) or raw '-i2c' (bare block writes and reads). The match data flags the raw-I2C case; SMBus is the default because that's what the majority of devices uses. Signed-off-by: Jonas Jelonek Reviewed-by: Kory Maincent Link: https://patch.msgid.link/20260813222036.873930-4-jelonek.jonas@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/pse-pd/Kconfig | 11 ++ drivers/net/pse-pd/Makefile | 1 + drivers/net/pse-pd/realtek-pse-mcu-i2c.c | 148 +++++++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 drivers/net/pse-pd/realtek-pse-mcu-i2c.c diff --git a/drivers/net/pse-pd/Kconfig b/drivers/net/pse-pd/Kconfig index 3b0c245a2bc7..6d14c8832e8b 100644 --- a/drivers/net/pse-pd/Kconfig +++ b/drivers/net/pse-pd/Kconfig @@ -19,6 +19,17 @@ config PSE_REALTEK_MCU Shared core for the Realtek PSE MCU driver. This is selected automatically by the transport options below. +config PSE_REALTEK_MCU_I2C + tristate "Realtek PSE MCU driver (I2C transport)" + depends on I2C + select PSE_REALTEK_MCU + help + Driver for the microcontroller (MCU) that fronts the PSE + hardware on various Realtek-based managed switches, attached + via I2C/SMBus. The MCU exposes a message-based protocol; the actual + PSE silicon is not accessed directly. To compile this driver as a + module, choose M here: the module will be called realtek-pse-mcu-i2c. + config PSE_REGULATOR tristate "Regulator based PSE controller" help diff --git a/drivers/net/pse-pd/Makefile b/drivers/net/pse-pd/Makefile index bf35e2a5b110..ef869bba5ed9 100644 --- a/drivers/net/pse-pd/Makefile +++ b/drivers/net/pse-pd/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_PSE_CONTROLLER) += pse_core.o obj-$(CONFIG_PSE_REALTEK_MCU) += realtek-pse-mcu-core.o +obj-$(CONFIG_PSE_REALTEK_MCU_I2C) += realtek-pse-mcu-i2c.o obj-$(CONFIG_PSE_REGULATOR) += pse_regulator.o obj-$(CONFIG_PSE_PD692X0) += pd692x0.o obj-$(CONFIG_PSE_SI3474) += si3474.o diff --git a/drivers/net/pse-pd/realtek-pse-mcu-i2c.c b/drivers/net/pse-pd/realtek-pse-mcu-i2c.c new file mode 100644 index 000000000000..b6769ac1d470 --- /dev/null +++ b/drivers/net/pse-pd/realtek-pse-mcu-i2c.c @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include + +#include "realtek-pse-mcu.h" + +/* + * The core has already waited RTPSE_MCU_RESPONSE_MS before calling us, so + * the response is normally ready on the very first read. For commands the + * MCU produces more slowly, keep polling at the typical response cadence + * up to the worst-case ceiling. + */ +#define RTPSE_MCU_I2C_RETRY_MS RTPSE_MCU_RESPONSE_MS +#define RTPSE_MCU_I2C_MAX_TRIES (RTPSE_MCU_RESPONSE_MAX_MS / RTPSE_MCU_I2C_RETRY_MS) + +static int rtpse_mcu_i2c_smbus_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req) +{ + struct i2c_client *client = to_i2c_client(pse->dev); + + /* Send opcode as SMBus command byte; remaining 11 bytes as block data */ + return i2c_smbus_write_i2c_block_data(client, req->opcode, RTPSE_MCU_MSG_SIZE - 1, + (const u8 *)req + 1); +} + +static int rtpse_mcu_i2c_smbus_recv(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req, + struct rtpse_mcu_msg *resp) +{ + struct i2c_client *client = to_i2c_client(pse->dev); + int tries, ret; + + for (tries = 0; tries < RTPSE_MCU_I2C_MAX_TRIES; tries++) { + if (tries > 0) + msleep(RTPSE_MCU_I2C_RETRY_MS); + + /* MCU needs 0x00 as command byte for read */ + ret = i2c_smbus_read_i2c_block_data(client, 0x00, + RTPSE_MCU_MSG_SIZE, + (u8 *)resp); + if (ret < 0) + return ret; + if (ret == RTPSE_MCU_MSG_SIZE && rtpse_mcu_resp_is_final(req, resp)) + return 0; + } + + return -ETIMEDOUT; +} + +static const struct rtpse_mcu_transport_ops rtpse_mcu_i2c_smbus_ops = { + .send = rtpse_mcu_i2c_smbus_send, + .recv = rtpse_mcu_i2c_smbus_recv, +}; + +static int rtpse_mcu_i2c_native_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req) +{ + struct i2c_client *client = to_i2c_client(pse->dev); + int ret; + + ret = i2c_master_send(client, (const u8 *)req, RTPSE_MCU_MSG_SIZE); + if (ret < 0) + return ret; + return ret == RTPSE_MCU_MSG_SIZE ? 0 : -EIO; +} + +static int rtpse_mcu_i2c_native_recv(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req, + struct rtpse_mcu_msg *resp) +{ + struct i2c_client *client = to_i2c_client(pse->dev); + int tries, ret; + + for (tries = 0; tries < RTPSE_MCU_I2C_MAX_TRIES; tries++) { + if (tries > 0) + msleep(RTPSE_MCU_I2C_RETRY_MS); + + ret = i2c_master_recv(client, (u8 *)resp, RTPSE_MCU_MSG_SIZE); + if (ret < 0) + return ret; + if (ret == RTPSE_MCU_MSG_SIZE && rtpse_mcu_resp_is_final(req, resp)) + return 0; + } + + return -ETIMEDOUT; +} + +static const struct rtpse_mcu_transport_ops rtpse_mcu_i2c_native_ops = { + .send = rtpse_mcu_i2c_native_send, + .recv = rtpse_mcu_i2c_native_recv, +}; + +static int rtpse_mcu_i2c_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + const struct rtpse_mcu_match_data *match; + struct rtpse_mcu_ctrl *pse; + bool use_native; + + match = device_get_match_data(dev); + if (!match) + return dev_err_probe(dev, -ENODEV, "missing match data\n"); + + /* The framing (raw I2C vs SMBus) is carried by the match data. */ + use_native = match->native_i2c; + if (use_native) { + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return dev_err_probe(dev, -EOPNOTSUPP, + "plain-I2C MCU protocol requires I2C-capable adapter\n"); + } else { + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_I2C_BLOCK | + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) + return dev_err_probe(dev, -EOPNOTSUPP, + "SMBus MCU protocol requires SMBus I2C-block support\n"); + } + + pse = devm_kzalloc(dev, sizeof(*pse), GFP_KERNEL); + if (!pse) + return -ENOMEM; + + pse->dev = dev; + pse->pcdev.owner = THIS_MODULE; + pse->transport = use_native ? &rtpse_mcu_i2c_native_ops : &rtpse_mcu_i2c_smbus_ops; + + return rtpse_mcu_register(pse); +} + +static const struct of_device_id rtpse_mcu_i2c_of_match[] = { + { .compatible = "realtek,pse-mcu-gen1-smbus", .data = &rtpse_mcu_gen1_data }, + { .compatible = "realtek,pse-mcu-gen2-smbus", .data = &rtpse_mcu_gen2_data }, + { .compatible = "realtek,pse-mcu-gen2-i2c", .data = &rtpse_mcu_gen2_i2c_data }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rtpse_mcu_i2c_of_match); + +static struct i2c_driver rtpse_mcu_i2c_driver = { + .driver = { + .name = "realtek-pse-mcu-i2c", + .of_match_table = rtpse_mcu_i2c_of_match, + }, + .probe = rtpse_mcu_i2c_probe, +}; +module_i2c_driver(rtpse_mcu_i2c_driver); + +MODULE_AUTHOR("Jonas Jelonek "); +MODULE_DESCRIPTION("Realtek PSE MCU driver (I2C transport)"); +MODULE_LICENSE("GPL"); From 5cc93d9897496bcfde6821579907d309b6883ab7 Mon Sep 17 00:00:00 2001 From: Jonas Jelonek Date: Thu, 13 Aug 2026 22:20:35 +0000 Subject: [PATCH 1418/1433] net: pse-pd: realtek-pse-mcu: add UART transport Add the serdev (UART) transport for the Realtek PSE MCU core. It registers the MCU as a serdev device and provides the send/recv callbacks the core uses to exchange the 12-byte frames, receiving asynchronously via the serdev receive_buf callback. The baud rate defaults to 19200 and can be overridden per board with the "current-speed" property. Signed-off-by: Jonas Jelonek Reviewed-by: Kory Maincent Link: https://patch.msgid.link/20260813222036.873930-5-jelonek.jonas@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/pse-pd/Kconfig | 11 ++ drivers/net/pse-pd/Makefile | 1 + drivers/net/pse-pd/realtek-pse-mcu-uart.c | 164 ++++++++++++++++++++++ 3 files changed, 176 insertions(+) create mode 100644 drivers/net/pse-pd/realtek-pse-mcu-uart.c diff --git a/drivers/net/pse-pd/Kconfig b/drivers/net/pse-pd/Kconfig index 6d14c8832e8b..a0f2ae668c67 100644 --- a/drivers/net/pse-pd/Kconfig +++ b/drivers/net/pse-pd/Kconfig @@ -30,6 +30,17 @@ config PSE_REALTEK_MCU_I2C PSE silicon is not accessed directly. To compile this driver as a module, choose M here: the module will be called realtek-pse-mcu-i2c. +config PSE_REALTEK_MCU_UART + tristate "Realtek PSE MCU driver (UART transport)" + depends on SERIAL_DEV_BUS + select PSE_REALTEK_MCU + help + Driver for the microcontroller (MCU) that fronts the PSE + hardware on various Realtek-based managed switches, attached + via UART. The MCU exposes a message-based protocol; the actual PSE + silicon is not accessed directly. To compile this driver as a + module, choose M here: the module will be called realtek-pse-mcu-uart. + config PSE_REGULATOR tristate "Regulator based PSE controller" help diff --git a/drivers/net/pse-pd/Makefile b/drivers/net/pse-pd/Makefile index ef869bba5ed9..9cca5900fe34 100644 --- a/drivers/net/pse-pd/Makefile +++ b/drivers/net/pse-pd/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_PSE_CONTROLLER) += pse_core.o obj-$(CONFIG_PSE_REALTEK_MCU) += realtek-pse-mcu-core.o obj-$(CONFIG_PSE_REALTEK_MCU_I2C) += realtek-pse-mcu-i2c.o +obj-$(CONFIG_PSE_REALTEK_MCU_UART) += realtek-pse-mcu-uart.o obj-$(CONFIG_PSE_REGULATOR) += pse_regulator.o obj-$(CONFIG_PSE_PD692X0) += pd692x0.o obj-$(CONFIG_PSE_SI3474) += si3474.o diff --git a/drivers/net/pse-pd/realtek-pse-mcu-uart.c b/drivers/net/pse-pd/realtek-pse-mcu-uart.c new file mode 100644 index 000000000000..9baa17d8d31f --- /dev/null +++ b/drivers/net/pse-pd/realtek-pse-mcu-uart.c @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "realtek-pse-mcu.h" + +#define RTPSE_MCU_UART_BAUD_DEFAULT 19200 +#define RTPSE_MCU_UART_TX_TIMEOUT msecs_to_jiffies(100) +#define RTPSE_MCU_UART_RX_TIMEOUT msecs_to_jiffies(RTPSE_MCU_RESPONSE_MAX_MS) + +struct rtpse_mcu_uart { + struct rtpse_mcu_ctrl pse; + struct serdev_device *serdev; + struct completion rx_done; + spinlock_t rx_lock; /* protects rx_buf and rx_len */ + size_t rx_len; + u8 rx_buf[RTPSE_MCU_MSG_SIZE]; +}; + +#define to_rtpse_mcu_uart(p) container_of(p, struct rtpse_mcu_uart, pse) + +/* + * No framing is done here: a glitched frame costs one transaction, then + * the next _send re-frames from rx_len 0. Resync works by returning count + * (not take), dropping any overflow so serdev keeps no leftover to bleed + * into the next frame. + */ +static size_t rtpse_mcu_uart_receive(struct serdev_device *serdev, + const u8 *buf, size_t count) +{ + struct rtpse_mcu_uart *ctx = serdev_device_get_drvdata(serdev); + size_t take; + + scoped_guard(spinlock_irqsave, &ctx->rx_lock) { + take = min(count, sizeof(ctx->rx_buf) - ctx->rx_len); + if (take) { + memcpy(ctx->rx_buf + ctx->rx_len, buf, take); + ctx->rx_len += take; + if (ctx->rx_len == sizeof(ctx->rx_buf)) + complete(&ctx->rx_done); + } + } + + /* consume all to avoid desync/misalignment */ + return count; +} + +static const struct serdev_device_ops rtpse_mcu_uart_serdev_ops = { + .receive_buf = rtpse_mcu_uart_receive, + .write_wakeup = serdev_device_write_wakeup, +}; + +static int rtpse_mcu_uart_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req) +{ + struct rtpse_mcu_uart *ctx = to_rtpse_mcu_uart(pse); + int written; + + /* clear any leftover rx state before transmitting */ + scoped_guard(spinlock_irqsave, &ctx->rx_lock) { + reinit_completion(&ctx->rx_done); + ctx->rx_len = 0; + } + + written = serdev_device_write(ctx->serdev, (const u8 *)req, sizeof(*req), + RTPSE_MCU_UART_TX_TIMEOUT); + if (written < 0) + return written; + if (written != sizeof(*req)) + return -EIO; + + return 0; +} + +static int rtpse_mcu_uart_recv(struct rtpse_mcu_ctrl *pse, + const struct rtpse_mcu_msg *req, + struct rtpse_mcu_msg *resp) +{ + struct rtpse_mcu_uart *ctx = to_rtpse_mcu_uart(pse); + + if (!wait_for_completion_timeout(&ctx->rx_done, RTPSE_MCU_UART_RX_TIMEOUT)) + return -ETIMEDOUT; + + scoped_guard(spinlock_irqsave, &ctx->rx_lock) { + if (ctx->rx_len != sizeof(*resp)) + return -EIO; + + memcpy(resp, ctx->rx_buf, sizeof(*resp)); + } + return 0; +} + +static const struct rtpse_mcu_transport_ops rtpse_mcu_uart_transport_ops = { + .send = rtpse_mcu_uart_send, + .recv = rtpse_mcu_uart_recv, +}; + +static int rtpse_mcu_uart_probe(struct serdev_device *serdev) +{ + u32 speed = RTPSE_MCU_UART_BAUD_DEFAULT; + struct device *dev = &serdev->dev; + struct rtpse_mcu_uart *ctx; + unsigned int baud; + int ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->serdev = serdev; + ctx->pse.dev = dev; + ctx->pse.pcdev.owner = THIS_MODULE; + ctx->pse.transport = &rtpse_mcu_uart_transport_ops; + init_completion(&ctx->rx_done); + spin_lock_init(&ctx->rx_lock); + + serdev_device_set_drvdata(serdev, ctx); + serdev_device_set_client_ops(serdev, &rtpse_mcu_uart_serdev_ops); + + ret = devm_serdev_device_open(dev, serdev); + if (ret) + return dev_err_probe(dev, ret, "failed to open serdev\n"); + + fwnode_property_read_u32(dev_fwnode(dev), "current-speed", &speed); + + baud = serdev_device_set_baudrate(serdev, speed); + if (baud != speed) + dev_warn(dev, "could not set baudrate %u, controller uses %u\n", + speed, baud); + + serdev_device_set_flow_control(serdev, false); + + ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); + if (ret) + dev_warn(dev, "could not set parity to none: %d\n", ret); + + return rtpse_mcu_register(&ctx->pse); +} + +static const struct of_device_id rtpse_mcu_uart_of_match[] = { + { .compatible = "realtek,pse-mcu-gen1", .data = &rtpse_mcu_gen1_data }, + { .compatible = "realtek,pse-mcu-gen2", .data = &rtpse_mcu_gen2_data }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rtpse_mcu_uart_of_match); + +static struct serdev_device_driver rtpse_mcu_uart_driver = { + .driver = { + .name = "realtek-pse-mcu-uart", + .of_match_table = rtpse_mcu_uart_of_match, + }, + .probe = rtpse_mcu_uart_probe, +}; +module_serdev_device_driver(rtpse_mcu_uart_driver); + +MODULE_AUTHOR("Jonas Jelonek "); +MODULE_DESCRIPTION("Realtek PSE MCU driver (UART transport)"); +MODULE_LICENSE("GPL"); From 2873d364f40803276fa0fa4b4479988614a135cc Mon Sep 17 00:00:00 2001 From: Joris Vaisvila Date: Thu, 13 Aug 2026 22:02:38 +0300 Subject: [PATCH 1419/1433] dt-bindings: net: dsa: add MT7628 ESW Add device tree bindings for the MediaTek MT7628 embedded Ethernet Switch. The Switch provides 5 external user ports and 1 internal CPU port, with integrated 10/100 PHYs and fixed port to PHY mapping. The CPU port is internally connected and uses port index 6. Signed-off-by: Joris Vaisvila Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260813190241.789323-2-joey@tinyisr.com Signed-off-by: Paolo Abeni --- .../bindings/net/dsa/mediatek,mt7628-esw.yaml | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/dsa/mediatek,mt7628-esw.yaml diff --git a/Documentation/devicetree/bindings/net/dsa/mediatek,mt7628-esw.yaml b/Documentation/devicetree/bindings/net/dsa/mediatek,mt7628-esw.yaml new file mode 100644 index 000000000000..e0e7ffef6648 --- /dev/null +++ b/Documentation/devicetree/bindings/net/dsa/mediatek,mt7628-esw.yaml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/dsa/mediatek,mt7628-esw.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Mediatek MT7628 Embedded Ethernet Switch + +maintainers: + - Joris Vaisvila + +description: + The MT7628 SoC's built-in Ethernet Switch has five user ports and one + internally connected CPU port. The user ports are all connected to the SoC's + integrated Fast Ethernet PHYs. The switch registers are directly mapped in + the SoC's memory. + +allOf: + - $ref: dsa.yaml#/$defs/ethernet-ports + +properties: + compatible: + const: mediatek,mt7628-esw + + reg: + maxItems: 1 + + resets: + items: + - description: internal switch block reset + - description: internal phy package reset + + reset-names: + items: + - const: esw + - const: ephy + +required: + - compatible + - reg + - resets + - reset-names + - ethernet-ports + +unevaluatedProperties: false + +examples: + - | + switch@10110000 { + compatible = "mediatek,mt7628-esw"; + reg = <0x10110000 0x8000>; + + resets = <&sysc 23>, <&sysc 24>; + reset-names = "esw", "ephy"; + + ethernet-ports { + #address-cells = <1>; + #size-cells = <0>; + + ethernet-port@0 { + reg = <0>; + phy-mode = "internal"; + }; + + ethernet-port@1 { + reg = <1>; + phy-mode = "internal"; + }; + + ethernet-port@2 { + reg = <2>; + phy-mode = "internal"; + }; + + ethernet-port@3 { + reg = <3>; + phy-mode = "internal"; + }; + + ethernet-port@4 { + reg = <4>; + phy-mode = "internal"; + }; + + ethernet-port@6 { + reg = <6>; + phy-mode = "internal"; + ethernet = <ðernet>; + + fixed-link { + speed = <1000>; + full-duplex; + }; + }; + }; + }; From c9c235775bc4ca66bf64f2a62161b9a73cd6ec23 Mon Sep 17 00:00:00 2001 From: Joris Vaisvila Date: Thu, 13 Aug 2026 22:02:39 +0300 Subject: [PATCH 1420/1433] net: phy: mediatek: add phy driver for MT7628 built-in Fast Ethernet PHYs The Fast Ethernet PHYs present in the MT7628 SoCs require an undocumented bit to be set before they can establish 100mbps links. This commit adds the Kconfig option MEDIATEK_FE_SOC_PHY and the corresponding driver mtk-fe-soc.c. Signed-off-by: Joris Vaisvila Reviewed-by: Andrew Lunn Reviewed-by: Daniel Golle Link: https://patch.msgid.link/20260813190241.789323-3-joey@tinyisr.com Signed-off-by: Paolo Abeni --- drivers/net/phy/mediatek/Kconfig | 11 +++++- drivers/net/phy/mediatek/Makefile | 1 + drivers/net/phy/mediatek/mtk-fe-soc.c | 52 +++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 drivers/net/phy/mediatek/mtk-fe-soc.c diff --git a/drivers/net/phy/mediatek/Kconfig b/drivers/net/phy/mediatek/Kconfig index b6d41bbbbc27..3b9cf82c0cb2 100644 --- a/drivers/net/phy/mediatek/Kconfig +++ b/drivers/net/phy/mediatek/Kconfig @@ -21,8 +21,17 @@ config MEDIATEK_GE_PHY common operations with MediaTek SoC built-in Gigabit Ethernet PHYs. +config MEDIATEK_FE_SOC_PHY + tristate "MediaTek SoC Fast Ethernet PHYs" + depends on SOC_MT7620 || COMPILE_TEST + help + Support for MediaTek MT7628 built-in Fast Ethernet PHYs. + This driver only sets an initialization bit required for the PHY + to establish 100 Mbps links. All other PHY operations are handled + by the kernel's generic PHY code. + config MEDIATEK_GE_SOC_PHY - tristate "MediaTek SoC Ethernet PHYs" + tristate "MediaTek SoC Gigabit Ethernet PHYs" depends on ARM64 || ECONET || COMPILE_TEST depends on ARCH_AIROHA || (ARCH_MEDIATEK && NVMEM_MTK_EFUSE) || \ ECONET || COMPILE_TEST diff --git a/drivers/net/phy/mediatek/Makefile b/drivers/net/phy/mediatek/Makefile index ac57ecc799fc..6f9cacf7f906 100644 --- a/drivers/net/phy/mediatek/Makefile +++ b/drivers/net/phy/mediatek/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_MEDIATEK_2P5GE_PHY) += mtk-2p5ge.o +obj-$(CONFIG_MEDIATEK_FE_SOC_PHY) += mtk-fe-soc.o obj-$(CONFIG_MEDIATEK_GE_PHY) += mtk-ge.o obj-$(CONFIG_MEDIATEK_GE_SOC_PHY) += mtk-ge-soc.o obj-$(CONFIG_MTK_NET_PHYLIB) += mtk-phy-lib.o diff --git a/drivers/net/phy/mediatek/mtk-fe-soc.c b/drivers/net/phy/mediatek/mtk-fe-soc.c new file mode 100644 index 000000000000..0b0d6c1fefbd --- /dev/null +++ b/drivers/net/phy/mediatek/mtk-fe-soc.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for MT7628 Embedded Switch internal Fast Ethernet PHYs + */ +#include +#include + +#define MTK_FPHY_ID_MT7628 0x03a29410 +#define MTK_EXT_PAGE_ACCESS 0x1f + +static int mt7628_phy_read_page(struct phy_device *phydev) +{ + return __phy_read(phydev, MTK_EXT_PAGE_ACCESS); +} + +static int mt7628_phy_write_page(struct phy_device *phydev, int page) +{ + return __phy_write(phydev, MTK_EXT_PAGE_ACCESS, page); +} + +static int mt7628_phy_config_init(struct phy_device *phydev) +{ + /* + * This undocumented bit is required for the PHYs to be able to + * establish 100mbps links. + */ + return phy_modify_paged(phydev, 0x8000, 30, BIT(13), BIT(13)); +} + +static struct phy_driver mtk_soc_fe_phy_driver[] = { + { + PHY_ID_MATCH_EXACT(MTK_FPHY_ID_MT7628), + .name = "MediaTek MT7628 PHY", + .config_init = mt7628_phy_config_init, + .read_page = mt7628_phy_read_page, + .write_page = mt7628_phy_write_page, + .suspend = genphy_suspend, + .resume = genphy_resume, + }, +}; + +module_phy_driver(mtk_soc_fe_phy_driver); +static const struct mdio_device_id __maybe_unused mtk_soc_fe_phy_tbl[] = { + { PHY_ID_MATCH_EXACT(MTK_FPHY_ID_MT7628) }, + { } +}; + +MODULE_DESCRIPTION("MediaTek SoC Fast Ethernet PHY driver"); +MODULE_AUTHOR("Joris Vaisvila "); +MODULE_LICENSE("GPL"); + +MODULE_DEVICE_TABLE(mdio, mtk_soc_fe_phy_tbl); From 44204fd425afb6ca5e9d6d363c1a2917a2828f27 Mon Sep 17 00:00:00 2001 From: Joris Vaisvila Date: Thu, 13 Aug 2026 22:02:40 +0300 Subject: [PATCH 1421/1433] net: dsa: initial MT7628 tagging driver Add support for the MT7628 embedded switch's tag. The MT7628 tag is merged with the VLAN TPID field when a VLAN is appended by the switch hardware. It is not installed if the VLAN tag is already there on ingress. Due to this hardware quirk the tag cannot be trusted for port 0 if we don't know that the VLAN was added by the hardware. As a workaround for this the switch is configured to always append the port PVID tag even if the incoming packet is already tagged. The tagging driver can then trust that the tag is always accurate and the whole VLAN tag can be removed on ingress as it's only metadata for the tagger. On egress the MT7628 tag allows precise TX, but the correct VLAN tag from tag_8021q is still appended or the switch will not forward the packet. Signed-off-by: Joris Vaisvila Link: https://patch.msgid.link/20260813190241.789323-4-joey@tinyisr.com Signed-off-by: Paolo Abeni --- include/net/dsa.h | 2 + net/dsa/Kconfig | 6 +++ net/dsa/Makefile | 1 + net/dsa/tag_mt7628.c | 93 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+) create mode 100644 net/dsa/tag_mt7628.c diff --git a/include/net/dsa.h b/include/net/dsa.h index 6f7f5c17b532..7507d632e7c6 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -60,6 +60,7 @@ struct tc_action; #define DSA_TAG_PROTO_MXL862_VALUE 32 #define DSA_TAG_PROTO_NETC_VALUE 33 #define DSA_TAG_PROTO_KSZ8463_VALUE 34 +#define DSA_TAG_PROTO_MT7628_VALUE 35 enum dsa_tag_protocol { DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE, @@ -97,6 +98,7 @@ enum dsa_tag_protocol { DSA_TAG_PROTO_MXL862 = DSA_TAG_PROTO_MXL862_VALUE, DSA_TAG_PROTO_NETC = DSA_TAG_PROTO_NETC_VALUE, DSA_TAG_PROTO_KSZ8463 = DSA_TAG_PROTO_KSZ8463_VALUE, + DSA_TAG_PROTO_MT7628 = DSA_TAG_PROTO_MT7628_VALUE, }; struct dsa_switch; diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig index d5e725b90d78..23b4b74004ed 100644 --- a/net/dsa/Kconfig +++ b/net/dsa/Kconfig @@ -98,6 +98,12 @@ config NET_DSA_TAG_EDSA Say Y or M if you want to enable support for tagging frames for the Marvell switches which use EtherType DSA headers. +config NET_DSA_TAG_MT7628 + tristate "Tag driver for the MT7628 embedded switch" + help + Say Y or M if you want to enable support for tagging frames for the + switch embedded in the MT7628 SoC. + config NET_DSA_TAG_MTK tristate "Tag driver for Mediatek switches" help diff --git a/net/dsa/Makefile b/net/dsa/Makefile index b8c2667cd14a..d15bcf5c68f0 100644 --- a/net/dsa/Makefile +++ b/net/dsa/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_NET_DSA_TAG_GSWIP) += tag_gswip.o obj-$(CONFIG_NET_DSA_TAG_HELLCREEK) += tag_hellcreek.o obj-$(CONFIG_NET_DSA_TAG_KSZ) += tag_ksz.o obj-$(CONFIG_NET_DSA_TAG_LAN9303) += tag_lan9303.o +obj-$(CONFIG_NET_DSA_TAG_MT7628) += tag_mt7628.o obj-$(CONFIG_NET_DSA_TAG_MTK) += tag_mtk.o obj-$(CONFIG_NET_DSA_TAG_MXL_862XX) += tag_mxl862xx.o obj-$(CONFIG_NET_DSA_TAG_MXL_GSW1XX) += tag_mxl-gsw1xx.o diff --git a/net/dsa/tag_mt7628.c b/net/dsa/tag_mt7628.c new file mode 100644 index 000000000000..80b50ff08e53 --- /dev/null +++ b/net/dsa/tag_mt7628.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026, Joris Vaisvila + * MT7628 switch tag support + */ + +#include +#include +#include + +#include "tag.h" + +/* + * The MT7628 tag is encoded in the VLAN TPID field. + * On TX the lower 6 bits encode the destination port bitmask. + * On RX the lower 3 bits encode the source port number. + * + * The switch hardware will not modify the TPID of an incoming packet if it is + * already VLAN tagged. To work around this the switch is configured to always + * append a tag_8021q standalone VLAN tag for each port. That means we can + * safely strip the outer VLAN tag after parsing it. + * + * A VLAN tag is constructed on egress to target the standalone VLAN and + * destination port. + */ + +#define MT7628_TAG_NAME "mt7628" + +#define MT7628_TAG_TX_PORT GENMASK(5, 0) +#define MT7628_TAG_RX_PORT GENMASK(2, 0) +#define MT7628_TAG_LEN 4 + +static struct sk_buff *mt7628_tag_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + struct dsa_port *dp; + u16 xmit_vlan; + __be16 *tag; + + dp = dsa_user_to_port(dev); + xmit_vlan = dsa_tag_8021q_standalone_vid(dp); + + skb_push(skb, MT7628_TAG_LEN); + dsa_alloc_etype_header(skb, MT7628_TAG_LEN); + + tag = dsa_etype_header_pos_tx(skb); + + tag[0] = htons(ETH_P_8021Q | + FIELD_PREP(MT7628_TAG_TX_PORT, + dsa_xmit_port_mask(skb, dev))); + tag[1] = htons(xmit_vlan); + + return skb; +} + +static struct sk_buff *mt7628_tag_rcv(struct sk_buff *skb, + struct net_device *dev) +{ + __be16 *phdr; + + if (unlikely(!pskb_may_pull(skb, MT7628_TAG_LEN))) { + kfree_skb(skb); + return NULL; + } + + phdr = dsa_etype_header_pos_rx(skb); + skb->dev = + dsa_conduit_find_user(dev, 0, + FIELD_GET(MT7628_TAG_RX_PORT, ntohs(*phdr))); + if (!skb->dev) { + kfree_skb(skb); + return NULL; + } + + skb_pull_rcsum(skb, MT7628_TAG_LEN); + dsa_strip_etype_header(skb, MT7628_TAG_LEN); + dsa_default_offload_fwd_mark(skb); + return skb; +} + +static const struct dsa_device_ops mt7628_tag_ops = { + .name = MT7628_TAG_NAME, + .proto = DSA_TAG_PROTO_MT7628, + .xmit = mt7628_tag_xmit, + .rcv = mt7628_tag_rcv, + .needed_headroom = MT7628_TAG_LEN, +}; + +module_dsa_tag_driver(mt7628_tag_ops); + +MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_MT7628, MT7628_TAG_NAME); +MODULE_DESCRIPTION("DSA tag driver for MT7628 switch"); +MODULE_LICENSE("GPL"); From 15062bb05e161bb851963c7fce8c2e789056e8f0 Mon Sep 17 00:00:00 2001 From: Joris Vaisvila Date: Thu, 13 Aug 2026 22:02:41 +0300 Subject: [PATCH 1422/1433] net: dsa: initial support for MT7628 embedded switch Add support for the MT7628 embedded switch. The switch has 5 built-in 100Mbps user ports (ports 0-4) and one 1Gbps port that is internally attached to the SoCs CPU MAC and serves as the CPU port. The switch hardware has a very limited 16 entry VLAN table. Configuring VLANs is the only way to control switch forwarding. Currently 6 entries are used by tag_8021q to isolate the ports. Double tag feature is enabled to force the switch to append the VLAN tag even if the incoming packet is already tagged, this simulates VLAN-unaware functionality and simplifies the tagger implementation. Signed-off-by: Joris Vaisvila Reviewed-by: Daniel Golle Link: https://patch.msgid.link/20260813190241.789323-5-joey@tinyisr.com Signed-off-by: Paolo Abeni --- drivers/net/dsa/Kconfig | 10 + drivers/net/dsa/Makefile | 1 + drivers/net/dsa/mt7628.c | 652 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 663 insertions(+) create mode 100644 drivers/net/dsa/mt7628.c diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig index 4ab567c5bbaf..676fb7dffe14 100644 --- a/drivers/net/dsa/Kconfig +++ b/drivers/net/dsa/Kconfig @@ -63,6 +63,16 @@ config NET_DSA_MT7530_MMIO are directly mapped into the SoCs register space rather than being accessible via MDIO. +config NET_DSA_MT7628 + tristate "MediaTek MT7628 Embedded Ethernet switch support" + depends on HAS_IOMEM && (SOC_MT7620 || COMPILE_TEST) + select NET_DSA_TAG_MT7628 + select MEDIATEK_FE_SOC_PHY + select REGMAP_MMIO + help + This enables support for the built-in Ethernet switch found + in the MT7628 SoC. + config NET_DSA_MV88E6060 tristate "Marvell 88E6060 ethernet switch chip support" select NET_DSA_TAG_TRAILER diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile index d2975badffc0..6ceb78a755d7 100644 --- a/drivers/net/dsa/Makefile +++ b/drivers/net/dsa/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_NET_DSA_KS8995) += ks8995.o obj-$(CONFIG_NET_DSA_MT7530) += mt7530.o obj-$(CONFIG_NET_DSA_MT7530_MDIO) += mt7530-mdio.o obj-$(CONFIG_NET_DSA_MT7530_MMIO) += mt7530-mmio.o +obj-$(CONFIG_NET_DSA_MT7628) += mt7628.o obj-$(CONFIG_NET_DSA_MV88E6060) += mv88e6060.o obj-$(CONFIG_NET_DSA_RZN1_A5PSW) += rzn1_a5psw.o obj-$(CONFIG_NET_DSA_SMSC_LAN9303) += lan9303-core.o diff --git a/drivers/net/dsa/mt7628.c b/drivers/net/dsa/mt7628.c new file mode 100644 index 000000000000..fb63f6f644b9 --- /dev/null +++ b/drivers/net/dsa/mt7628.c @@ -0,0 +1,652 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Mediatek MT7628 Embedded Switch (ESW) DSA driver + * Copyright (C) 2026 Joris Vaisvila + * + * Portions derived from OpenWRT esw_rt3050 driver: + * Copyright (C) 2009-2015 John Crispin + * Copyright (C) 2009-2015 Felix Fietkau + * Copyright (C) 2013-2015 Michael Lee + * Copyright (C) 2016 Vittorio Gambaletta + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MT7628_ESW_REG_IMR 0x04 +#define MT7628_ESW_REG_FCT0 0x08 +#define MT7628_ESW_REG_PFC1 0x14 +#define MT7628_ESW_REG_PVIDC(port) (0x40 + 4 * ((port) / 2)) +#define MT7628_ESW_REG_VLANI(vlan) (0x50 + 4 * ((vlan) / 2)) +#define MT7628_ESW_REG_VMSC(vlan) (0x70 + 4 * ((vlan) / 4)) +#define MT7628_ESW_REG_VUB(vlan) (0x100 + 4 * ((vlan) / 4)) +#define MT7628_ESW_REG_SOCPC 0x8c +#define MT7628_ESW_REG_POC0 0x90 +#define MT7628_ESW_REG_POC2 0x98 +#define MT7628_ESW_REG_SGC 0x9c +#define MT7628_ESW_REG_PCR0 0xc0 +#define MT7628_ESW_REG_PCR1 0xc4 +#define MT7628_ESW_REG_FPA2 0xc8 +#define MT7628_ESW_REG_FCT2 0xcc +#define MT7628_ESW_REG_SGC2 0xe4 + +#define MT7628_ESW_FCT0_DROP_SET_TH GENMASK(7, 0) +#define MT7628_ESW_FCT0_DROP_RLS_TH GENMASK(15, 8) +#define MT7628_ESW_FCT0_FC_SET_TH GENMASK(23, 16) +#define MT7628_ESW_FCT0_FC_RLS_TH GENMASK(31, 24) + +#define MT7628_ESW_PFC1_EN_VLAN GENMASK(22, 16) + +#define MT7628_ESW_PVID_S 12 +#define MT7628_ESW_PVID_M GENMASK(11, 0) +#define MT7628_ESW_PVID_SHIFT(port) \ + (MT7628_ESW_PVID_S * ((port) % 2)) +#define MT7628_ESW_PVID_MASK(port) \ + (MT7628_ESW_PVID_M << MT7628_ESW_PVID_SHIFT(port)) +#define MT7628_ESW_PVID_PREP(port, pvid) \ + (((pvid) & MT7628_ESW_PVID_M) << MT7628_ESW_PVID_SHIFT(port)) + +#define MT7628_ESW_VID_S 12 +#define MT7628_ESW_VID_M GENMASK(11, 0) +#define MT7628_ESW_VID_SHIFT(vlan) \ + (MT7628_ESW_VID_S * ((vlan) % 2)) +#define MT7628_ESW_VID_MASK(vlan) \ + (MT7628_ESW_VID_M << MT7628_ESW_VID_SHIFT(vlan)) +#define MT7628_ESW_VID_PREP(vlan, vid) \ + (((vid) & MT7628_ESW_VID_M) << MT7628_ESW_VID_SHIFT(vlan)) + +#define MT7628_ESW_VMSC_S 8 +#define MT7628_ESW_VMSC_M GENMASK(7, 0) +#define MT7628_ESW_VMSC_SHIFT(vlan) \ + (MT7628_ESW_VMSC_S * ((vlan) % 4)) +#define MT7628_ESW_VMSC_MASK(vlan) \ + (MT7628_ESW_VMSC_M << MT7628_ESW_VMSC_SHIFT(vlan)) +#define MT7628_ESW_VMSC_PREP(vlan, vmsc) \ + (((vmsc) & MT7628_ESW_VMSC_M) << MT7628_ESW_VMSC_SHIFT(vlan)) + +#define MT7628_ESW_VUB_S 7 +#define MT7628_ESW_VUB_M GENMASK(6, 0) +#define MT7628_ESW_VUB_SHIFT(vlan) \ + (MT7628_ESW_VUB_S * ((vlan) % 4)) +#define MT7628_ESW_VUB_MASK(vlan) \ + (MT7628_ESW_VUB_M << MT7628_ESW_VUB_SHIFT(vlan)) +#define MT7628_ESW_VUB_PREP(vlan, vub) \ + (((vub) & MT7628_ESW_VUB_M) << MT7628_ESW_VUB_SHIFT(vlan)) + +#define MT7628_ESW_SOCPC_CRC_PADDING BIT(25) +#define MT7628_ESW_SOCPC_DISBC2CPU GENMASK(22, 16) +#define MT7628_ESW_SOCPC_DISMC2CPU GENMASK(14, 8) +#define MT7628_ESW_SOCPC_DISUN2CPU GENMASK(6, 0) + +#define MT7628_ESW_POC0_PORT_DISABLE GENMASK(29, 23) + +#define MT7628_ESW_POC2_PER_VLAN_UNTAG_EN BIT(15) + +#define MT7628_ESW_SGC_AGING_INTERVAL GENMASK(3, 0) +#define MT7628_ESW_BC_STORM_PROT GENMASK(5, 4) +#define MT7628_ESW_PKT_MAX_LEN GENMASK(7, 6) +#define MT7628_ESW_DIS_PKT_ABORT BIT(8) +#define MT7628_ESW_ADDRESS_HASH_ALG GENMASK(10, 9) +#define MT7628_ESW_DISABLE_TX_BACKOFF BIT(11) +#define MT7628_ESW_BP_JAM_CNT GENMASK(15, 12) +#define MT7628_ESW_DISMIIPORT_WASTX GENMASK(17, 16) +#define MT7628_ESW_BP_MODE GENMASK(19, 18) +#define MT7628_ESW_BISH_DIS BIT(20) +#define MT7628_ESW_BISH_TH GENMASK(22, 21) +#define MT7628_ESW_LED_FLASH_TIME GENMASK(24, 23) +#define MT7628_ESW_RMC_RULE GENMASK(26, 25) +#define MT7628_ESW_IP_MULT_RULE GENMASK(28, 27) +#define MT7628_ESW_LEN_ERR_CHK BIT(29) +#define MT7628_ESW_BKOFF_ALG BIT(30) + +#define MT7628_ESW_PCR0_WT_NWAY_DATA GENMASK(31, 16) +#define MT7628_ESW_PCR0_RD_PHY_CMD BIT(14) +#define MT7628_ESW_PCR0_WT_PHY_CMD BIT(13) +#define MT7628_ESW_PCR0_CPU_PHY_REG GENMASK(12, 8) +#define MT7628_ESW_PCR0_CPU_PHY_ADDR GENMASK(4, 0) + +#define MT7628_ESW_PCR1_RD_DATA GENMASK(31, 16) +#define MT7628_ESW_PCR1_RD_DONE BIT(1) +#define MT7628_ESW_PCR1_WT_DONE BIT(0) + +#define MT7628_ESW_FPA2_AP_EN BIT(29) +#define MT7628_ESW_FPA2_EXT_PHY_ADDR_BASE GENMASK(28, 24) +#define MT7628_ESW_FPA2_FORCE_RGMII_LINK1 BIT(13) +#define MT7628_ESW_FPA2_FORCE_RGMII_EN1 BIT(11) + +#define MT7628_ESW_FCT2_MUST_DROP_RLS_TH GENMASK(17, 13) +#define MT7628_ESW_FCT2_MUST_DROP_SET_TH GENMASK(12, 8) +#define MT7628_ESW_FCT2_MC_PER_PORT_TH GENMASK(5, 0) + +#define MT7628_ESW_SGC2_SPECIAL_TAG_EN BIT(23) +#define MT7628_ESW_SGC2_TX_CPU_TPID_BIT_MAP GENMASK(22, 16) +#define MT7628_ESW_SGC2_DOUBLE_TAG_EN GENMASK(6, 0) + +#define MT7628_ESW_PORTS_NOCPU GENMASK(5, 0) +#define MT7628_ESW_PORTS_CPU BIT(6) +#define MT7628_ESW_PORTS_ALL GENMASK(6, 0) + +#define MT7628_ESW_NUM_PORTS 7 +#define MT7628_NUM_VLANS 16 + +static const struct regmap_config mt7628_esw_regmap_cfg = { + .name = "mt7628-esw", + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .fast_io = true, + .reg_format_endian = REGMAP_ENDIAN_LITTLE, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +struct mt7628_vlan { + bool active; + u8 members; + u8 untag; + u16 vid; +}; + +struct mt7628_esw { + struct reset_control *rst_ephy; + struct reset_control *rst_esw; + struct regmap *regmap; + struct dsa_switch *ds; + u16 tag_8021q_pvid[MT7628_ESW_NUM_PORTS]; + struct mt7628_vlan vlans[MT7628_NUM_VLANS]; + struct device *dev; +}; + +static int mt7628_mii_read(struct mii_bus *bus, int port, int regnum) +{ + struct mt7628_esw *esw = bus->priv; + int ret; + u32 val; + + /* + * RD_DONE bit is read to clear. Read PCR1 once to acknowledge any + * stale completion indicator before starting a new transaction. + */ + ret = regmap_read(esw->regmap, MT7628_ESW_REG_PCR1, &val); + if (ret) + goto out; + + ret = regmap_write(esw->regmap, MT7628_ESW_REG_PCR0, + FIELD_PREP(MT7628_ESW_PCR0_CPU_PHY_REG, + regnum) | + FIELD_PREP(MT7628_ESW_PCR0_CPU_PHY_ADDR, + port) | MT7628_ESW_PCR0_RD_PHY_CMD); + if (ret) + goto out; + + ret = regmap_read_poll_timeout(esw->regmap, MT7628_ESW_REG_PCR1, val, + (val & MT7628_ESW_PCR1_RD_DONE), 10, + 5000); + if (ret) + goto out; + + return FIELD_GET(MT7628_ESW_PCR1_RD_DATA, val); + +out: + dev_err(&bus->dev, "read failed. MDIO timeout?\n"); + return ret; +} + +static int mt7628_mii_write(struct mii_bus *bus, int port, int regnum, u16 dat) +{ + struct mt7628_esw *esw = bus->priv; + u32 val; + int ret; + + /* + * WT_DONE bit is read to clear. Read PCR1 once to acknowledge any + * stale completion indicator before starting a new transaction. + */ + ret = regmap_read(esw->regmap, MT7628_ESW_REG_PCR1, &val); + if (ret) + goto out; + + ret = regmap_write(esw->regmap, MT7628_ESW_REG_PCR0, + FIELD_PREP(MT7628_ESW_PCR0_WT_NWAY_DATA, dat) | + FIELD_PREP(MT7628_ESW_PCR0_CPU_PHY_REG, + regnum) | + FIELD_PREP(MT7628_ESW_PCR0_CPU_PHY_ADDR, + port) | MT7628_ESW_PCR0_WT_PHY_CMD); + if (ret) + goto out; + + ret = regmap_read_poll_timeout(esw->regmap, MT7628_ESW_REG_PCR1, val, + (val & MT7628_ESW_PCR1_WT_DONE), 10, + 5000); + if (ret) + goto out; + + return 0; + +out: + dev_err(&bus->dev, "write failed. MDIO timeout?\n"); + return ret; +} + +static int mt7628_setup_internal_mdio(struct dsa_switch *ds) +{ + struct mt7628_esw *esw = ds->priv; + struct device *dev = ds->dev; + struct mii_bus *bus; + + bus = devm_mdiobus_alloc(dev); + if (!bus) + return -ENOMEM; + + bus->name = "MT7628 internal MDIO bus"; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(dev)); + bus->priv = esw; + bus->read = mt7628_mii_read; + bus->write = mt7628_mii_write; + bus->parent = dev; + + ds->user_mii_bus = bus; + bus->phy_mask = ~ds->phys_mii_mask; + + return devm_mdiobus_register(dev, bus); +} + +static void mt7628_switch_init(struct dsa_switch *ds) +{ + struct mt7628_esw *esw = ds->priv; + + regmap_write(esw->regmap, MT7628_ESW_REG_FCT0, + FIELD_PREP(MT7628_ESW_FCT0_DROP_SET_TH, 0x50) | + FIELD_PREP(MT7628_ESW_FCT0_DROP_RLS_TH, 0x78) | + FIELD_PREP(MT7628_ESW_FCT0_FC_SET_TH, 0xa0) | + FIELD_PREP(MT7628_ESW_FCT0_FC_RLS_TH, 0xc8)); + + regmap_write(esw->regmap, MT7628_ESW_REG_FCT2, + FIELD_PREP(MT7628_ESW_FCT2_MC_PER_PORT_TH, 0xc) | + FIELD_PREP(MT7628_ESW_FCT2_MUST_DROP_SET_TH, 0x10) | + FIELD_PREP(MT7628_ESW_FCT2_MUST_DROP_RLS_TH, 0x12)); + + /* + * general switch configuration: + * 300s aging interval + * broadcast storm prevention disabled + * max packet length 1536 bytes + * disable collision 16 packet abort and late collision abort + * use xor48 for address hashing + * disable tx backoff + * 10 packet back pressure jam + * disable was_transmit + * jam until BP condition released + * 30ms LED flash + * rmc tb fault to all ports + * unmatched IGMP as broadcast + */ + regmap_write(esw->regmap, MT7628_ESW_REG_SGC, + FIELD_PREP(MT7628_ESW_SGC_AGING_INTERVAL, 1) | + FIELD_PREP(MT7628_ESW_BC_STORM_PROT, 0) | + FIELD_PREP(MT7628_ESW_PKT_MAX_LEN, 0) | + MT7628_ESW_DIS_PKT_ABORT | + FIELD_PREP(MT7628_ESW_ADDRESS_HASH_ALG, 1) | + MT7628_ESW_DISABLE_TX_BACKOFF | + FIELD_PREP(MT7628_ESW_BP_JAM_CNT, 10) | + FIELD_PREP(MT7628_ESW_DISMIIPORT_WASTX, 0) | + FIELD_PREP(MT7628_ESW_BP_MODE, 0b10) | + FIELD_PREP(MT7628_ESW_LED_FLASH_TIME, 0) | + FIELD_PREP(MT7628_ESW_RMC_RULE, 0) | + FIELD_PREP(MT7628_ESW_IP_MULT_RULE, 0)); + + regmap_write(esw->regmap, MT7628_ESW_REG_SOCPC, + MT7628_ESW_SOCPC_CRC_PADDING | + FIELD_PREP(MT7628_ESW_SOCPC_DISUN2CPU, + MT7628_ESW_PORTS_CPU) | + FIELD_PREP(MT7628_ESW_SOCPC_DISMC2CPU, + MT7628_ESW_PORTS_CPU) | + FIELD_PREP(MT7628_ESW_SOCPC_DISBC2CPU, + MT7628_ESW_PORTS_CPU)); + + regmap_set_bits(esw->regmap, MT7628_ESW_REG_FPA2, + MT7628_ESW_FPA2_FORCE_RGMII_EN1 | + MT7628_ESW_FPA2_FORCE_RGMII_LINK1 | + MT7628_ESW_FPA2_AP_EN); + + regmap_update_bits(esw->regmap, MT7628_ESW_REG_FPA2, + MT7628_ESW_FPA2_EXT_PHY_ADDR_BASE, + FIELD_PREP(MT7628_ESW_FPA2_EXT_PHY_ADDR_BASE, 31)); + + /* disable all interrupts */ + regmap_write(esw->regmap, MT7628_ESW_REG_IMR, 0); + + /* enable MT7628 DSA tag on CPU port */ + regmap_write(esw->regmap, MT7628_ESW_REG_SGC2, + MT7628_ESW_SGC2_SPECIAL_TAG_EN | + FIELD_PREP(MT7628_ESW_SGC2_TX_CPU_TPID_BIT_MAP, + MT7628_ESW_PORTS_CPU)); + + /* + * Double tag feature allows switch to always append the port PVID VLAN tag + * regardless of if the incoming packet already has a VLAN tag. + * This is enabled to simulate VLAN unawareness. + */ + regmap_set_bits(esw->regmap, MT7628_ESW_REG_SGC2, + FIELD_PREP(MT7628_ESW_SGC2_DOUBLE_TAG_EN, + MT7628_ESW_PORTS_NOCPU)); + + regmap_set_bits(esw->regmap, MT7628_ESW_REG_POC2, + MT7628_ESW_POC2_PER_VLAN_UNTAG_EN); + + regmap_update_bits(esw->regmap, MT7628_ESW_REG_PFC1, + MT7628_ESW_PFC1_EN_VLAN, + FIELD_PREP(MT7628_ESW_PFC1_EN_VLAN, + MT7628_ESW_PORTS_ALL)); +} + +static void mt7628_esw_set_pvid(struct mt7628_esw *esw, unsigned int port, + unsigned int pvid) +{ + regmap_update_bits(esw->regmap, MT7628_ESW_REG_PVIDC(port), + MT7628_ESW_PVID_MASK(port), + MT7628_ESW_PVID_PREP(port, pvid)); +} + +static void mt7628_esw_set_vlan_id(struct mt7628_esw *esw, unsigned int vlan, + unsigned int vid) +{ + regmap_update_bits(esw->regmap, MT7628_ESW_REG_VLANI(vlan), + MT7628_ESW_VID_MASK(vlan), + MT7628_ESW_VID_PREP(vlan, vid)); +} + +static void mt7628_esw_set_vmsc(struct mt7628_esw *esw, unsigned int vlan, + unsigned int msc) +{ + regmap_update_bits(esw->regmap, MT7628_ESW_REG_VMSC(vlan), + MT7628_ESW_VMSC_MASK(vlan), + MT7628_ESW_VMSC_PREP(vlan, msc)); +} + +static void mt7628_esw_set_vub(struct mt7628_esw *esw, unsigned int vlan, + unsigned int vub) +{ + regmap_update_bits(esw->regmap, MT7628_ESW_REG_VUB(vlan), + MT7628_ESW_VUB_MASK(vlan), + MT7628_ESW_VUB_PREP(vlan, vub)); +} + +static void mt7628_vlan_sync(struct dsa_switch *ds) +{ + struct mt7628_esw *esw = ds->priv; + int i; + + for (i = 0; i < MT7628_NUM_VLANS; i++) { + struct mt7628_vlan *vlan = &esw->vlans[i]; + + mt7628_esw_set_vmsc(esw, i, vlan->members); + mt7628_esw_set_vlan_id(esw, i, vlan->vid); + mt7628_esw_set_vub(esw, i, vlan->untag); + } + + for (i = 0; i < ds->num_ports; i++) + mt7628_esw_set_pvid(esw, i, esw->tag_8021q_pvid[i]); +} + +static int mt7628_setup(struct dsa_switch *ds) +{ + struct mt7628_esw *esw = ds->priv; + int ret; + + ret = reset_control_reset(esw->rst_esw); + if (ret) + return ret; + usleep_range(1000, 2000); + + ret = reset_control_reset(esw->rst_ephy); + if (ret) + return ret; + usleep_range(1000, 2000); + /* + * all MMIO reads hang if esw is not out of reset + * ephy needs extra time to get out of reset or it ends up misconfigured + */ + + mt7628_switch_init(ds); + + ret = mt7628_setup_internal_mdio(ds); + if (ret) + return ret; + + rtnl_lock(); + ret = dsa_tag_8021q_register(ds, htons(ETH_P_8021Q)); + rtnl_unlock(); + + return ret; +} + +static int mt7628_port_enable(struct dsa_switch *ds, int port, + struct phy_device *phy) +{ + struct mt7628_esw *esw = ds->priv; + + /* + * All switch ports are disabled by esw reset and enabled here by DSA. + */ + regmap_clear_bits(esw->regmap, MT7628_ESW_REG_POC0, + FIELD_PREP(MT7628_ESW_POC0_PORT_DISABLE, BIT(port))); + return 0; +} + +static void mt7628_port_disable(struct dsa_switch *ds, int port) +{ + struct mt7628_esw *esw = ds->priv; + + regmap_set_bits(esw->regmap, MT7628_ESW_REG_POC0, + FIELD_PREP(MT7628_ESW_POC0_PORT_DISABLE, BIT(port))); +} + +static enum dsa_tag_protocol +mt7628_get_tag_proto(struct dsa_switch *ds, int port, enum dsa_tag_protocol mp) +{ + return DSA_TAG_PROTO_MT7628; +} + +static void mt7628_phylink_get_caps(struct dsa_switch *ds, int port, + struct phylink_config *config) +{ + switch (port) { + case 6: + config->mac_capabilities |= MAC_1000; + fallthrough; + case 0 ... 4: + config->mac_capabilities |= MAC_100 | MAC_10; + __set_bit(PHY_INTERFACE_MODE_INTERNAL, + config->supported_interfaces); + break; + default: + break; /* port 5 does not exist on MT7628 */ + } +} + +static int mt7628_dsa_8021q_vlan_add(struct dsa_switch *ds, int port, + u16 vid, u16 flags) +{ + struct mt7628_esw *esw = ds->priv; + struct mt7628_vlan *vlan = NULL; + int i; + + for (i = 0; i < MT7628_NUM_VLANS; i++) { + struct mt7628_vlan *check_vlan = &esw->vlans[i]; + + if (!check_vlan->active && !vlan) + vlan = check_vlan; + + if (check_vlan->active && check_vlan->vid == vid) { + vlan = check_vlan; + break; + } + } + + if (!vlan) + return -ENOSPC; + + vlan->vid = vid; + vlan->active = true; + vlan->members |= BIT(port); + + if (flags & BRIDGE_VLAN_INFO_PVID) + esw->tag_8021q_pvid[port] = vid; + + if (flags & BRIDGE_VLAN_INFO_UNTAGGED) + vlan->untag |= BIT(port); + + mt7628_vlan_sync(ds); + return 0; +} + +static int mt7628_dsa_8021q_vlan_del(struct dsa_switch *ds, int port, u16 vid) +{ + struct mt7628_esw *esw = ds->priv; + struct mt7628_vlan *vlan = NULL; + int i; + + for (i = 0; i < MT7628_NUM_VLANS; i++) { + struct mt7628_vlan *check_vlan = &esw->vlans[i]; + + if (!check_vlan->active || check_vlan->vid != vid) + continue; + vlan = check_vlan; + break; + } + if (!vlan) + return -ENOENT; + + if (esw->tag_8021q_pvid[port] == vid) + esw->tag_8021q_pvid[port] = 0; + + vlan->members &= ~BIT(port); + vlan->untag &= ~BIT(port); + + if (!vlan->members) { + vlan->active = false; + vlan->vid = 0; + } + + mt7628_vlan_sync(ds); + return 0; +} + +static void mt7628_teardown(struct dsa_switch *ds) +{ + rtnl_lock(); + dsa_tag_8021q_unregister(ds); + rtnl_unlock(); +} + +static const struct dsa_switch_ops mt7628_switch_ops = { + .get_tag_protocol = mt7628_get_tag_proto, + .setup = mt7628_setup, + .teardown = mt7628_teardown, + .port_enable = mt7628_port_enable, + .port_disable = mt7628_port_disable, + .phylink_get_caps = mt7628_phylink_get_caps, + .tag_8021q_vlan_add = mt7628_dsa_8021q_vlan_add, + .tag_8021q_vlan_del = mt7628_dsa_8021q_vlan_del, +}; + +static int mt7628_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mt7628_esw *esw; + struct dsa_switch *ds; + void __iomem *base; + + ds = devm_kzalloc(&pdev->dev, sizeof(*ds), GFP_KERNEL); + if (!ds) + return -ENOMEM; + + esw = devm_kzalloc(&pdev->dev, sizeof(*esw), GFP_KERNEL); + if (!esw) + return -ENOMEM; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + esw->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &mt7628_esw_regmap_cfg); + if (IS_ERR(esw->regmap)) + return PTR_ERR(esw->regmap); + + esw->rst_ephy = devm_reset_control_get_exclusive(&pdev->dev, "ephy"); + if (IS_ERR(esw->rst_ephy)) + return dev_err_probe(dev, PTR_ERR(esw->rst_ephy), + "failed to get EPHY reset\n"); + + esw->rst_esw = devm_reset_control_get_exclusive(&pdev->dev, "esw"); + if (IS_ERR(esw->rst_esw)) + return dev_err_probe(dev, PTR_ERR(esw->rst_esw), + "failed to get ESW reset\n"); + + ds->dev = dev; + ds->num_ports = MT7628_ESW_NUM_PORTS; + ds->ops = &mt7628_switch_ops; + ds->priv = esw; + esw->ds = ds; + esw->dev = dev; + dev_set_drvdata(dev, esw); + + return dsa_register_switch(ds); +} + +static void mt7628_remove(struct platform_device *pdev) +{ + struct mt7628_esw *esw = platform_get_drvdata(pdev); + + if (!esw) + return; + + dsa_unregister_switch(esw->ds); +} + +static void mt7628_shutdown(struct platform_device *pdev) +{ + struct mt7628_esw *esw = platform_get_drvdata(pdev); + + if (!esw) + return; + + dsa_switch_shutdown(esw->ds); + dev_set_drvdata(&pdev->dev, NULL); +} + +static const struct of_device_id mt7628_of_match[] = { + { .compatible = "mediatek,mt7628-esw" }, + {} +}; + +MODULE_DEVICE_TABLE(of, mt7628_of_match); + +static struct platform_driver mt7628_driver = { + .driver = { + .name = "mt7628-esw", + .of_match_table = mt7628_of_match, + }, + .probe = mt7628_probe, + .remove = mt7628_remove, + .shutdown = mt7628_shutdown, +}; + +module_platform_driver(mt7628_driver); + +MODULE_AUTHOR("Joris Vaisvila "); +MODULE_DESCRIPTION("Driver for Mediatek MT7628 embedded switch"); +MODULE_LICENSE("GPL"); From 8ccc9bf9afeeb46a437081c07154fbf5964682b2 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Thu, 13 Aug 2026 23:31:26 +0800 Subject: [PATCH 1423/1433] bonding: initialize err for empty target lists Empty NLA_NESTED attributes are valid, and bonding uses them to clear the ARP and NS target lists. When either target attribute is empty, nla_for_each_nested() does not execute, so err retains an uninitialized value before it is tested. The request can consequently return an unpredictable error after clearing the targets. Initialize err to zero so an empty target list completes successfully. Non-empty lists still propagate errors from __bond_opt_set() unchanged. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 4fb0ef585eb2 ("bonding: convert arp_ip_target to use the new option API") Signed-off-by: Ruoyu Wang Reviewed-by: Nikolay Aleksandrov Acked-by: Jay Vosburgh Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260813153126.3952893-1-ruoyuw560@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/bonding/bond_netlink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/bonding/bond_netlink.c b/drivers/net/bonding/bond_netlink.c index 4a11572f663d..87d92d3cce4a 100644 --- a/drivers/net/bonding/bond_netlink.c +++ b/drivers/net/bonding/bond_netlink.c @@ -220,7 +220,7 @@ static int bond_changelink(struct net_device *bond_dev, struct nlattr *tb[], struct bonding *bond = netdev_priv(bond_dev); struct bond_opt_value newval; int miimon = 0; - int err; + int err = 0; if (!data) return 0; From c0726f0caf8c6b3208552949e17d23634a2f3129 Mon Sep 17 00:00:00 2001 From: Yong Wang Date: Fri, 14 Aug 2026 01:35:26 +0800 Subject: [PATCH 1424/1433] ipv4: reject undersized MTUs in ip_do_fragment() ip_do_fragment() subtracts the IPv4 header length from the effective MTU and passes the resulting payload MTU to ip_frag_next(). If the effective MTU is smaller than hlen + 8, ip_frag_next() rounds the fragment payload length down to zero. The fragmentation state then never makes forward progress: state->left, state->ptr and state->offset stay unchanged while ip_do_fragment() keeps allocating and transmitting header-only fragments until the softlockup detector fires. This is reproducible with a route installed using "mtu lock 20", but it is also reproducible without route MTU lock, for example by forwarding a packet to a device whose MTU is 20. Fix it in ip_do_fragment() by rejecting mtu < hlen + 8 with -EMSGSIZE, matching the existing IPv6 fragmentation check. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Yong Wang Signed-off-by: Ren Wei Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/8809ef6314b98913681b0b370a05a85c2b6cd579.1786599079.git.edragain@163.com Signed-off-by: Jakub Kicinski --- net/ipv4/ip_output.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c index e6dd1e5b8c32..74e095b6b7ca 100644 --- a/net/ipv4/ip_output.c +++ b/net/ipv4/ip_output.c @@ -790,6 +790,10 @@ int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, */ hlen = iph->ihl * 4; + if (mtu < hlen + 8) { + err = -EMSGSIZE; + goto fail; + } mtu = mtu - hlen; /* Size of data space */ IPCB(skb)->flags |= IPSKB_FRAG_COMPLETE; ll_rs = LL_RESERVED_SPACE(rt->dst.dev); From a5edadbae57e2298a56cf7a4e774a027905a331f Mon Sep 17 00:00:00 2001 From: Abdifatah Suruur Date: Thu, 13 Aug 2026 20:47:07 +0300 Subject: [PATCH 1425/1433] ptp: vmclock: prevent read-only mappings from becoming writable vmclock_miscdev_mmap() rejects writable mappings of the shared vmclock ABI page with -EROFS, but leaves VM_MAYWRITE set. Userspace can map the page read-only and then upgrade it to writable with mprotect(), after which the guest can corrupt the host-written timekeeping data (sequence counter, UTC time, TSC offset) that the vmclock ABI defines as read-only. Clear VM_MAYWRITE on the read-only path so the mapping cannot be upgraded, as i915 does for its read-only objects and as fixed in drm/vc4 (CVE-2026-68445) and drm/panthor (CVE-2024-53071). Cc: stable@vger.kernel.org Fixes: 205032724226 ("ptp: Add support for the AMZNC10C 'vmclock' device") Signed-off-by: Abdifatah Suruur Link: https://patch.msgid.link/20260813174707.14809-1-suruurism@gmail.com Signed-off-by: Jakub Kicinski --- drivers/ptp/ptp_vmclock.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/ptp/ptp_vmclock.c b/drivers/ptp/ptp_vmclock.c index eebdcd5ebc08..bb0e14bac9f2 100644 --- a/drivers/ptp/ptp_vmclock.c +++ b/drivers/ptp/ptp_vmclock.c @@ -372,6 +372,12 @@ static int vmclock_miscdev_mmap(struct file *fp, struct vm_area_struct *vma) if ((vma->vm_flags & (VM_READ|VM_WRITE)) != VM_READ) return -EROFS; + /* + * Restrict the read-only mapping so it cannot be upgraded to + * writable later with mprotect(). + */ + vm_flags_clear(vma, VM_MAYWRITE); + if (vma->vm_end - vma->vm_start != PAGE_SIZE || vma->vm_pgoff) return -EINVAL; From c36461825469a9ceee2346a2e89286c522525da7 Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Fri, 14 Aug 2026 10:26:54 +0200 Subject: [PATCH 1426/1433] dpll: zl3073x: scale poll interval proportionally to timeout Replace the fixed 10 us poll sleep in zl3073x_poll_zero_u8() with timeout_us / 50, scaling the sleep interval proportionally to the timeout for all callers. Testing showed that existing callers (mailbox, HWREG, DF read, frequency measurement and phase error polls with 25-50 ms timeouts) typically completed in low hundreds of sleep cycles with the fixed 10 us interval. With the scaled interval the cycle count drops to single digits. The longer PTP-related timeouts (up to 3000 ms for phase step) added in the following patches benefit most, avoiding on the order of 10^5 bus transactions per wait. Reviewed-by: Vadim Fedorenko Signed-off-by: Ivan Vecera Link: https://patch.msgid.link/20260814082656.306534-2-ivecera@redhat.com Signed-off-by: Jakub Kicinski --- drivers/dpll/zl3073x/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c index 5b2d77f2c228..2e8b52c8de5e 100644 --- a/drivers/dpll/zl3073x/core.c +++ b/drivers/dpll/zl3073x/core.c @@ -322,7 +322,7 @@ int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val) int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask, unsigned int timeout_us) { -#define ZL_POLL_SLEEP_US 10 + unsigned int sleep_us = timeout_us / 50; unsigned int val; /* Check the register is 8bit */ @@ -336,7 +336,7 @@ int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET; return regmap_read_poll_timeout(zldev->regmap, reg, val, !(val & mask), - ZL_POLL_SLEEP_US, timeout_us); + sleep_us, timeout_us); } int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val, From 2dbf9b75627914f4392ef244718fa888f23bcea4 Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Fri, 14 Aug 2026 10:26:55 +0200 Subject: [PATCH 1427/1433] dpll: zl3073x: add channel ToD, phase step and TIE operations Add low-level DPLL channel operations for ToD read/write/adjust, output phase step, delta frequency offset write and TIE (Time Interval Error) write. These serve as building blocks for the PTP clock callbacks added in the next patch. ToD operations use a wait-before-write pattern to avoid blocking after each operation. The tod_ready_wait helper selects the poll timeout based on the current ToD command - write operations use a longer timeout (1000 ms) than reads (30 ms). The ToD read captures system timestamps (ptp_system_timestamp) around the HW command and completion poll to support cross-timestamping. The TIE write operation provides sub-picosecond resolution phase adjustment for modes where the DPLL is tracking a reference (AUTO and REFLOCK). Add output step-time mask to struct zl3073x_dev and zl3073x_dev_out_is_stepped() helper to check if an output participates in step-time operations. Reviewed-by: Petr Oros Tested-by: Chris du Quesnay Signed-off-by: Ivan Vecera Link: https://patch.msgid.link/20260814082656.306534-3-ivecera@redhat.com Signed-off-by: Jakub Kicinski --- drivers/dpll/zl3073x/chan.c | 321 +++++++++++++++++++++++++++++++++++- drivers/dpll/zl3073x/chan.h | 32 ++++ drivers/dpll/zl3073x/core.c | 13 ++ drivers/dpll/zl3073x/core.h | 23 +++ drivers/dpll/zl3073x/regs.h | 52 ++++++ 5 files changed, 439 insertions(+), 2 deletions(-) diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c index 4ec2cf53dad4..ba4d303d41b4 100644 --- a/drivers/dpll/zl3073x/chan.c +++ b/drivers/dpll/zl3073x/chan.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -162,8 +163,8 @@ int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index) * @zldev: pointer to zl3073x_dev structure * @index: DPLL channel index to fetch state for * - * Reads the mode_refsel register and reference priority registers for - * the given DPLL channel and stores the raw values for later use. + * Reads the mode_refsel, status and reference priority registers for + * the given DPLL channel and stores the values for later use. * * Return: 0 on success, <0 on error */ @@ -234,6 +235,322 @@ const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev, return &zldev->chan[index]; } +/** + * zl3073x_chan_tod_ready_wait - wait for ToD semaphore to clear + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * + * Checks the ToD control register semaphore bit. If clear, returns + * immediately. Otherwise polls until the bit is cleared by the device. + * + * Return: + * * 0 - success + * * %-EBUSY - timeout + * * %-EOPNOTSUPP - unknown command detected + * * negative - other error + */ +int zl3073x_chan_tod_ready_wait(struct zl3073x_dev *zldev, u8 ch) +{ + unsigned int timeout; + u8 tod_ctrl; + int rc; + + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch), &tod_ctrl); + if (rc) + return rc; + + if (!(tod_ctrl & ZL_DPLL_TOD_CTRL_SEM)) + return 0; + + switch (FIELD_GET(ZL_DPLL_TOD_CTRL_CMD, tod_ctrl)) { + case ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ: + timeout = ZL_POLL_TOD_WR_TIMEOUT_US; + break; + case ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT: + case ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ: + timeout = ZL_POLL_TOD_RD_TIMEOUT_US; + break; + default: + return -EOPNOTSUPP; + } + + rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch), + ZL_DPLL_TOD_CTRL_SEM, timeout); + + return rc == -ETIMEDOUT ? -EBUSY : rc; +} + +/** + * zl3073x_chan_tod_ctrl - issue ToD command + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * @cmd: ToD command to execute + * + * Writes the semaphore and command to dpll_tod_ctrl. The caller must + * ensure the device is ready (semaphore clear) before calling and + * must wait for completion if needed. + * + * Return: 0 on success, <0 on error + */ +static int zl3073x_chan_tod_ctrl(struct zl3073x_dev *zldev, u8 ch, u8 cmd) +{ + return zl3073x_write_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch), + ZL_DPLL_TOD_CTRL_SEM | cmd); +} + +/** + * zl3073x_chan_tod_read - read ToD registers after issuing a command + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * @next_hz: if true, read predicted ToD at next 1 Hz; otherwise read current + * @ts: timespec to store the result + * @sts: optional system timestamp pair for cross-timestamping + * + * Context: Caller must serialize all zl3073x_chan_tod_* calls externally. + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_tod_read(struct zl3073x_dev *zldev, u8 ch, + bool next_hz, struct timespec64 *ts, + struct ptp_system_timestamp *sts) +{ + u32 nsec; + u64 sec; + u8 cmd; + int rc; + + if (next_hz) + cmd = ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ; + else + cmd = ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT; + + /* Wait for any previous ToD operation to complete */ + rc = zl3073x_chan_tod_ready_wait(zldev, ch); + if (rc) + return rc; + + ptp_read_system_prets(sts); + rc = zl3073x_chan_tod_ctrl(zldev, ch, cmd); + if (rc) + return rc; + + rc = zl3073x_chan_tod_ready_wait(zldev, ch); + if (rc) + return rc; + ptp_read_system_postts(sts); + + rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), &sec); + if (rc) + return rc; + + /* HW nanoseconds are always in [0, NSEC_PER_SEC) range */ + rc = zl3073x_read_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), &nsec); + if (rc) + return rc; + + ts->tv_sec = sec; + ts->tv_nsec = nsec; + + return 0; +} + +/** + * zl3073x_chan_tod_write - write ToD registers and trigger 1 Hz update + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * @ts: time to set + * + * Context: Caller must serialize all zl3073x_chan_tod_* calls externally. + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_tod_write(struct zl3073x_dev *zldev, u8 ch, + struct timespec64 ts) +{ + int rc; + + /* Wait for any previous ToD operation to complete */ + rc = zl3073x_chan_tod_ready_wait(zldev, ch); + if (rc) + return rc; + + rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), ts.tv_sec); + if (rc) + return rc; + + rc = zl3073x_write_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), ts.tv_nsec); + if (rc) + return rc; + + return zl3073x_chan_tod_ctrl(zldev, ch, + ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ); +} + +/** + * zl3073x_chan_tod_adjust - atomic ToD read-modify-write with rollover guard + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * @delta: time adjustment to apply + * + * Reads the next-Hz ToD and current ToD, then checks whether enough time + * remains before the next 1 Hz rollover to safely complete the write. + * Re-reads if the 1 Hz tick crossed between the two reads or if less + * than 20 ms remains before the next rollover. Applies @delta and writes + * the result back. + * + * Context: Caller must serialize all zl3073x_chan_tod_* calls externally. + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_tod_adjust(struct zl3073x_dev *zldev, u8 ch, + struct timespec64 delta) +{ +#define ZL_TOD_MAX_RETRIES 20 + static const long threshold_ns = 20 * NSEC_PER_MSEC; + struct timespec64 ts_next, ts_cur, diff; + int rc, i; + + for (i = 0; i < ZL_TOD_MAX_RETRIES; i++) { + rc = zl3073x_chan_tod_read(zldev, ch, true, &ts_next, NULL); + if (rc) + return rc; + + rc = zl3073x_chan_tod_read(zldev, ch, false, &ts_cur, NULL); + if (rc) + return rc; + + /* Ensure the 1 Hz tick did not cross between the two reads + * and that enough margin remains to complete the write. + */ + diff = timespec64_sub(ts_next, ts_cur); + if (diff.tv_sec > 0 || + (!diff.tv_sec && diff.tv_nsec >= threshold_ns)) + break; + } + if (i == ZL_TOD_MAX_RETRIES) { + dev_warn(zldev->dev, + "DPLL%u ToD adjust failed to get stable margin\n", + ch); + return -EBUSY; + } + + /* Apply delta to the next-Hz ToD */ + ts_next = timespec64_add(ts_next, delta); + if (!timespec64_valid_settod(&ts_next)) + return -EINVAL; + + return zl3073x_chan_tod_write(zldev, ch, ts_next); +#undef ZL_TOD_MAX_RETRIES +} + +/** + * zl3073x_chan_df_offset_set - write delta frequency offset to hardware + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * @offset: frequency offset in 2^-48 steps + * + * Context: Caller must hold the per-DPLL lock. + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_df_offset_set(struct zl3073x_dev *zldev, u8 ch, s64 offset) +{ + int rc; + + rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_DF_OFFSET(ch), offset); + if (!rc) + zldev->chan[ch].df_offset = offset; + + return rc; +} + +/** + * zl3073x_chan_tie_write - adjust DPLL phase using TIE write + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * @delta_ns: phase adjustment in nanoseconds (must be in (-1s, 1s)) + * + * Converts nanoseconds to TIE units (0.01 ps) and writes TIE data + * to the specified channel. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_tie_write(struct zl3073x_dev *zldev, u8 ch, s64 delta_ns) +{ + s64 tie_data; + int rc; + + guard(mutex)(&zldev->tie_lock); + + /* Wait for any previous TIE operation to complete */ + rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_TIE_CTRL, + ZL_DPLL_TIE_CTRL_OP, + ZL_POLL_TIE_WR_TIMEOUT_US); + if (rc) + return rc; + + /* Convert ns to TIE units (0.01 ps = 10^-14 s) */ + tie_data = delta_ns * 100000LL; + + rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_TIE_DATA(ch), tie_data); + if (rc) + return rc; + + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_TIE_CTRL_MASK, BIT(ch)); + if (rc) + return rc; + + return zl3073x_write_u8(zldev, ZL_REG_DPLL_TIE_CTRL, + ZL_DPLL_TIE_CTRL_OP_WR); +} + +/** + * zl3073x_chan_phase_step - execute one output phase step operation + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * @out_mask: bitmask of outputs to step + * @step_cycles: phase step in synthesizer clock cycles + * @tod_step: also step the ToD counter + * + * All masked outputs must use synthesizers of the same frequency since + * the step value is in synthesizer clock cycles. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_phase_step(struct zl3073x_dev *zldev, u8 ch, + u16 out_mask, s32 step_cycles, + bool tod_step) +{ + u8 ctrl; + int rc; + + guard(mutex)(&zldev->phase_step_lock); + + /* Wait for any previous phase step operation to complete */ + rc = zl3073x_poll_zero_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_CTRL, + ZL_OUTPUT_PHASE_STEP_CTRL_OP, + ZL_POLL_PHASE_STEP_TIMEOUT_US); + if (rc) + return rc; + + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_PHASE_STEP_DATA, + step_cycles); + if (rc) + return rc; + + rc = zl3073x_write_u16(zldev, ZL_REG_OUTPUT_PHASE_STEP_MASK, out_mask); + if (rc) + return rc; + + rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_NUMBER, 1); + if (rc) + return rc; + + ctrl = FIELD_PREP(ZL_OUTPUT_PHASE_STEP_CTRL_DPLL, ch) | + FIELD_PREP(ZL_OUTPUT_PHASE_STEP_CTRL_OP, + ZL_OUTPUT_PHASE_STEP_CTRL_OP_WRITE); + if (tod_step) + ctrl |= ZL_OUTPUT_PHASE_STEP_CTRL_TOD_STEP; + + return zl3073x_write_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_CTRL, ctrl); +} + /** * zl3073x_chan_state_set - commit DPLL channel state changes to hardware * @zldev: pointer to zl3073x_dev structure diff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h index dc9c6d95bdee..99c3b7088f67 100644 --- a/drivers/dpll/zl3073x/chan.h +++ b/drivers/dpll/zl3073x/chan.h @@ -5,10 +5,12 @@ #include #include +#include #include #include "regs.h" +struct ptp_system_timestamp; struct zl3073x_dev; /** @@ -42,6 +44,21 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index, int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index); int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index); +int zl3073x_chan_tod_ready_wait(struct zl3073x_dev *zldev, u8 ch); +int zl3073x_chan_tod_read(struct zl3073x_dev *zldev, u8 ch, + bool next_hz, struct timespec64 *ts, + struct ptp_system_timestamp *sts); +int zl3073x_chan_tod_write(struct zl3073x_dev *zldev, u8 ch, + struct timespec64 ts); +int zl3073x_chan_tod_adjust(struct zl3073x_dev *zldev, u8 ch, + struct timespec64 delta); +int zl3073x_chan_phase_step(struct zl3073x_dev *zldev, u8 ch, + u16 out_mask, s32 step_cycles, bool tod_step); + +int zl3073x_chan_df_offset_set(struct zl3073x_dev *zldev, u8 ch, s64 offset); + +int zl3073x_chan_tie_write(struct zl3073x_dev *zldev, u8 ch, s64 delta_ns); + /** * zl3073x_chan_df_offset_get - get cached df_offset vs tracked reference * @chan: pointer to channel state @@ -200,6 +217,21 @@ static inline bool zl3073x_chan_mode_is_reflock(const struct zl3073x_chan *chan) return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_REFLOCK; } +/** + * zl3073x_chan_mode_supports_tie - check if channel mode supports TIE write + * @chan: pointer to channel state + * + * TIE write is supported in AUTO and REFLOCK modes regardless of lock state. + * + * Return: true if TIE write is supported, false otherwise + */ +static inline bool +zl3073x_chan_mode_supports_tie(const struct zl3073x_chan *chan) +{ + return zl3073x_chan_mode_is_auto(chan) || + zl3073x_chan_mode_is_reflock(chan); +} + /** * zl3073x_chan_is_ho_ready - check if holdover is ready * @chan: pointer to channel state diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c index 2e8b52c8de5e..230df08e27cd 100644 --- a/drivers/dpll/zl3073x/core.c +++ b/drivers/dpll/zl3073x/core.c @@ -511,6 +511,11 @@ zl3073x_dev_state_fetch(struct zl3073x_dev *zldev) int rc; u8 i; + rc = zl3073x_read_u16(zldev, ZL_REG_OUTPUT_STEP_TIME_MASK, + &zldev->out_step_time_mask); + if (rc) + return rc; + for (i = 0; i < ZL3073X_NUM_REFS; i++) { rc = zl3073x_ref_state_fetch(zldev, i); if (rc) { @@ -1034,6 +1039,14 @@ int zl3073x_dev_probe(struct zl3073x_dev *zldev) * and/or polls are required to be done atomically. */ rc = devm_mutex_init(zldev->dev, &zldev->multiop_lock); + if (rc) + return dev_err_probe(zldev->dev, rc, + "Failed to initialize mutex\n"); + rc = devm_mutex_init(zldev->dev, &zldev->phase_step_lock); + if (rc) + return dev_err_probe(zldev->dev, rc, + "Failed to initialize mutex\n"); + rc = devm_mutex_init(zldev->dev, &zldev->tie_lock); if (rc) return dev_err_probe(zldev->dev, rc, "Failed to initialize mutex\n"); diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h index 78dc208f3eea..67c10e259511 100644 --- a/drivers/dpll/zl3073x/core.h +++ b/drivers/dpll/zl3073x/core.h @@ -26,6 +26,10 @@ struct zl3073x_dpll; #define ZL_POLL_HWREG_TIMEOUT_US (50 * USEC_PER_MSEC) #define ZL_POLL_MB_TIMEOUT_US (30 * USEC_PER_MSEC) #define ZL_POLL_PHASE_ERR_TIMEOUT_US (50 * USEC_PER_MSEC) +#define ZL_POLL_PHASE_STEP_TIMEOUT_US (3000 * USEC_PER_MSEC) +#define ZL_POLL_TIE_WR_TIMEOUT_US (1000 * USEC_PER_MSEC) +#define ZL_POLL_TOD_RD_TIMEOUT_US (30 * USEC_PER_MSEC) +#define ZL_POLL_TOD_WR_TIMEOUT_US (1000 * USEC_PER_MSEC) enum zl3073x_flags { ZL3073X_FLAG_REF_PHASE_COMP_32_BIT, @@ -55,6 +59,8 @@ struct zl3073x_chip_info { * @regmap: regmap to access device registers * @info: detected chip info * @multiop_lock: to serialize multiple register operations + * @tie_lock: to serialize TIE write operations + * @phase_step_lock: to serialize output phase step operations * @ref: array of input references' invariants * @out: array of outs' invariants * @synth: array of synths' invariants @@ -63,6 +69,7 @@ struct zl3073x_chip_info { * @kworker: thread for periodic work * @work: periodic work * @clock_id: clock id of the device + * @out_step_time_mask: output step-time mask (device-global) * @phase_avg_factor: phase offset measurement averaging factor * @freq_monitor: is frequency monitor enabled */ @@ -71,6 +78,8 @@ struct zl3073x_dev { struct regmap *regmap; const struct zl3073x_chip_info *info; struct mutex multiop_lock; + struct mutex tie_lock; + struct mutex phase_step_lock; /* Invariants */ struct zl3073x_ref ref[ZL3073X_NUM_REFS]; @@ -87,6 +96,7 @@ struct zl3073x_dev { /* Per-chip parameters */ u64 clock_id; + u16 out_step_time_mask; u8 phase_avg_factor; bool freq_monitor; }; @@ -308,6 +318,19 @@ zl3073x_dev_out_is_enabled(struct zl3073x_dev *zldev, u8 index) return zl3073x_synth_is_enabled(synth) && zl3073x_out_is_enabled(out); } +/** + * zl3073x_dev_out_is_stepped - check if output is in step-time mask + * @zldev: pointer to zl3073x device + * @index: output index + * + * Return: true if output is affected by step-time operations + */ +static inline bool +zl3073x_dev_out_is_stepped(struct zl3073x_dev *zldev, u8 index) +{ + return !!(zldev->out_step_time_mask & BIT(index)); +} + /** * zl3073x_dev_out_dpll_get - get DPLL ID the output is driven by * @zldev: pointer to zl3073x device diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h index b70ead7d4495..f3a5e1215aa3 100644 --- a/drivers/dpll/zl3073x/regs.h +++ b/drivers/dpll/zl3073x/regs.h @@ -179,6 +179,20 @@ #define ZL_DPLL_DF_READ_CMD GENMASK(2, 0) #define ZL_DPLL_DF_READ_CMD_ACC_I 4 +#define ZL_REG_DPLL_TIE_CTRL ZL_REG(5, 0x30, 1) +#define ZL_DPLL_TIE_CTRL_OP GENMASK(2, 0) +#define ZL_DPLL_TIE_CTRL_OP_WR 4 + +#define ZL_REG_DPLL_TIE_CTRL_MASK ZL_REG(5, 0x31, 1) + +#define ZL_REG_DPLL_TOD_CTRL(_idx) \ + ZL_REG_IDX(_idx, 5, 0x38, 1, ZL3073X_MAX_CHANNELS, 1) +#define ZL_DPLL_TOD_CTRL_SEM BIT(4) +#define ZL_DPLL_TOD_CTRL_CMD GENMASK(3, 0) +#define ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ 1 +#define ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT 8 +#define ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ 9 + #define ZL_REG_DPLL_MEAS_CTRL ZL_REG(5, 0x50, 1) #define ZL_DPLL_MEAS_CTRL_EN BIT(0) #define ZL_DPLL_MEAS_CTRL_AVG_FACTOR GENMASK(7, 4) @@ -193,6 +207,9 @@ /******************************* * Register Pages 6-7, DPLL Data + * + * Per-channel registers with stride 0x20. Channels 0-3 reside on page 6, + * channel 4 on page 7. *******************************/ #define ZL_REG_DPLL_DF_OFFSET_03(_idx) \ @@ -202,6 +219,24 @@ ((_idx) < 4 ? ZL_REG_DPLL_DF_OFFSET_03(_idx) : ZL_REG_DPLL_DF_OFFSET_4) #define ZL_DPLL_DF_OFFSET_UNKNOWN S64_MIN +#define ZL_REG_DPLL_TIE_DATA_03(_idx) \ + ZL_REG_IDX(_idx, 6, 0x0C, 6, 4, 0x20) +#define ZL_REG_DPLL_TIE_DATA_4 ZL_REG(7, 0x0C, 6) +#define ZL_REG_DPLL_TIE_DATA(_idx) \ + ((_idx) < 4 ? ZL_REG_DPLL_TIE_DATA_03(_idx) : ZL_REG_DPLL_TIE_DATA_4) + +#define ZL_REG_DPLL_TOD_SEC_03(_idx) \ + ZL_REG_IDX(_idx, 6, 0x12, 6, 4, 0x20) +#define ZL_REG_DPLL_TOD_SEC_4 ZL_REG(7, 0x12, 6) +#define ZL_REG_DPLL_TOD_SEC(_idx) \ + ((_idx) < 4 ? ZL_REG_DPLL_TOD_SEC_03(_idx) : ZL_REG_DPLL_TOD_SEC_4) + +#define ZL_REG_DPLL_TOD_NS_03(_idx) \ + ZL_REG_IDX(_idx, 6, 0x18, 4, 4, 0x20) +#define ZL_REG_DPLL_TOD_NS_4 ZL_REG(7, 0x18, 4) +#define ZL_REG_DPLL_TOD_NS(_idx) \ + ((_idx) < 4 ? ZL_REG_DPLL_TOD_NS_03(_idx) : ZL_REG_DPLL_TOD_NS_4) + /*********************************** * Register Page 9, Synth and Output ***********************************/ @@ -221,6 +256,23 @@ #define ZL_OUTPUT_CTRL_EN BIT(0) #define ZL_OUTPUT_CTRL_SYNTH_SEL GENMASK(6, 4) +#define ZL_REG_OUTPUT_STEP_TIME_MASK ZL_REG(9, 0x36, 2) + +#define ZL_REG_OUTPUT_PHASE_STEP_CTRL ZL_REG(9, 0x38, 1) +#define ZL_OUTPUT_PHASE_STEP_CTRL_DPLL GENMASK(6, 4) +#define ZL_OUTPUT_PHASE_STEP_CTRL_TOD_STEP BIT(3) +#define ZL_OUTPUT_PHASE_STEP_CTRL_OP GENMASK(1, 0) +#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_NONE 0 +#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_RESET 1 +#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_READ 2 +#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_WRITE 3 + +#define ZL_REG_OUTPUT_PHASE_STEP_NUMBER ZL_REG(9, 0x39, 1) + +#define ZL_REG_OUTPUT_PHASE_STEP_MASK ZL_REG(9, 0x3a, 2) + +#define ZL_REG_OUTPUT_PHASE_STEP_DATA ZL_REG(9, 0x3c, 4) + /******************************* * Register Page 10, Ref Mailbox *******************************/ From 1db73ce01856c983bcc7789c4dffc6b5b8f41a23 Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Fri, 14 Aug 2026 10:26:56 +0200 Subject: [PATCH 1428/1433] dpll: zl3073x: add PTP clock support Add PTP clock support for the ZL3073x DPLL driver. A PTP clock device is registered for each DPLL channel regardless of the initial channel state, providing gettimex64, settime64, adjtime, adjfine, adjphase and getmaxphase callbacks. Callback availability depends on the current channel state: - adjfine: when NCO pin is connected (returns -EOPNOTSUPP otherwise) - adjphase: available when tracking a reference, uses TIE write - adjtime: always available and uses * phase step for sub-second deltas when NCO pin is connected * TIE write when tracking a reference * plain ToD read-modify-write otherwise - gettime/settime: always available The adjtime callback splits multi-second adjustments into a ToD read-modify-write for the seconds part and a sub-second mechanism (phase step or TIE write) for the remainder. On partial failure where seconds were already committed, success is returned to prevent the PTP servo from retrying and applying seconds again. All PTP callbacks are serialized by the existing per-DPLL zldpll->lock mutex, which is also used by DPLL pin and device callbacks. Reviewed-by: Petr Oros Reviewed-by: Vadim Fedorenko Tested-by: Chris du Quesnay Signed-off-by: Ivan Vecera Link: https://patch.msgid.link/20260814082656.306534-4-ivecera@redhat.com Signed-off-by: Jakub Kicinski --- drivers/dpll/zl3073x/Kconfig | 6 +- drivers/dpll/zl3073x/dpll.c | 443 ++++++++++++++++++++++++++++++++--- drivers/dpll/zl3073x/dpll.h | 5 + 3 files changed, 424 insertions(+), 30 deletions(-) diff --git a/drivers/dpll/zl3073x/Kconfig b/drivers/dpll/zl3073x/Kconfig index 5bbca1400581..da1731ace15f 100644 --- a/drivers/dpll/zl3073x/Kconfig +++ b/drivers/dpll/zl3073x/Kconfig @@ -2,7 +2,7 @@ config ZL3073X tristate "Microchip Azurite DPLL/PTP/SyncE devices" if COMPILE_TEST - depends on NET + depends on NET && PTP_1588_CLOCK select DPLL select NET_DEVLINK select REGMAP @@ -16,7 +16,7 @@ config ZL3073X config ZL3073X_I2C tristate "I2C bus implementation for Microchip Azurite devices" - depends on I2C && NET + depends on I2C && NET && PTP_1588_CLOCK select REGMAP_I2C select ZL3073X help @@ -28,7 +28,7 @@ config ZL3073X_I2C config ZL3073X_SPI tristate "SPI bus implementation for Microchip Azurite devices" - depends on NET && SPI + depends on NET && SPI && PTP_1588_CLOCK select REGMAP_SPI select ZL3073X help diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c index 83bd3027dbaa..f2e137475b40 100644 --- a/drivers/dpll/zl3073x/dpll.c +++ b/drivers/dpll/zl3073x/dpll.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include #include #include @@ -2313,45 +2315,384 @@ zl3073x_dpll_init_fine_phase_adjust(struct zl3073x_dev *zldev) return zl3073x_write_u8(zldev, ZL_REG_SYNTH_PHASE_SHIFT_CTRL, 0x01); } +/* Maximum frequency adjustment: +-1% of nominal in ppb */ +#define ZL3073X_DPLL_PTP_MAX_ADJ 10000000 + /** - * zl3073x_dpll_alloc - allocate DPLL device - * @zldev: pointer to zl3073x device - * @ch: DPLL channel number + * zl3073x_dpll_ptp_gettimex64 - read current time from ToD counters + * @info: PTP clock info + * @ts: timespec to store current time + * @sts: optional system timestamp pair for cross-timestamping * - * Allocates DPLL device structure for given DPLL channel. - * - * Return: pointer to DPLL device on success, error pointer on error + * Return: 0 on success, <0 on error */ -struct zl3073x_dpll * -zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch) +static int zl3073x_dpll_ptp_gettimex64(struct ptp_clock_info *info, + struct timespec64 *ts, + struct ptp_system_timestamp *sts) { - struct zl3073x_dpll *zldpll; + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); - zldpll = kzalloc_obj(*zldpll); - if (!zldpll) - return ERR_PTR(-ENOMEM); + guard(mutex)(&zldpll->lock); - zldpll->dev = zldev; - zldpll->id = ch; - mutex_init(&zldpll->lock); - INIT_LIST_HEAD(&zldpll->pins); - - return zldpll; + return zl3073x_chan_tod_read(zldpll->dev, zldpll->id, false, ts, sts); } /** - * zl3073x_dpll_free - free DPLL device - * @zldpll: pointer to zl3073x_dpll structure + * zl3073x_dpll_ptp_settime64 - set ToD counters to given time + * @info: PTP clock info + * @ts: timespec with time to set * - * Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc. + * Return: 0 on success, <0 on error */ -void -zl3073x_dpll_free(struct zl3073x_dpll *zldpll) +static int zl3073x_dpll_ptp_settime64(struct ptp_clock_info *info, + const struct timespec64 *ts) { - WARN(zldpll->dpll_dev, "DPLL device is still registered\n"); + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); - mutex_destroy(&zldpll->lock); - kfree(zldpll); + guard(mutex)(&zldpll->lock); + + return zl3073x_chan_tod_write(zldpll->dev, zldpll->id, *ts); +} + +/** + * zl3073x_dpll_ptp_adjtime_phase_step - adjust sub-second time via phase step + * @zldpll: DPLL channel + * @delta: time adjustment in nanoseconds (must be within (-NSEC_PER_SEC, + * NSEC_PER_SEC)) + * + * Uses the output phase step mechanism with tod_step=1 to adjust both + * the output clock phase and the ToD counter simultaneously. This keeps + * outputs and ToD coherent. Only valid for NCO. + * + * Outputs are grouped by synthesizer since the phase step value is in + * synthesizer clock cycles. The first synth group with enabled outputs + * uses tod_step to adjust both outputs and the ToD counter. Remaining + * groups step outputs only. If no synth has enabled outputs, the ToD + * counter is stepped alone using an empty output mask (the FW uses + * the first enabled synth's period for the conversion). + * + * Return: + * * %0 - success (or partial success if a later synth group + * failed after the first was already stepped) + * * %-EOPNOTSUPP - no synths available + * * negative - error + */ +static int zl3073x_dpll_ptp_adjtime_phase_step(struct zl3073x_dpll *zldpll, + s64 delta) +{ + u16 synth_mask[ZL3073X_NUM_SYNTHS] = {}; + struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_synth *synth; + struct zl3073x_dpll_pin *pin; + u32 first_synth_freq = 0; + bool tod_stepped = false; + s32 step_cycles; + u32 synth_freq; + int rc; + u8 i; + + /* Build per-synth output masks from registered output pins */ + list_for_each_entry(pin, &zldpll->pins, list) { + u8 out_id, synth_id; + + if (zl3073x_dpll_is_input_pin(pin)) + continue; + + out_id = zl3073x_output_pin_out_get(pin->id); + + if (!zl3073x_dev_out_is_stepped(zldev, out_id)) + continue; + + synth_id = zl3073x_dev_out_synth_get(zldev, out_id); + if (synth_id >= ZL3073X_NUM_SYNTHS) { + dev_warn(zldev->dev, "Unexpected synth id for OUT%u\n", + out_id); + continue; + } + synth_mask[synth_id] |= BIT(out_id); + } + + /* Process each synth group */ + for (i = 0; i < ZL3073X_NUM_SYNTHS; i++) { + synth = zl3073x_synth_state_get(zldev, i); + if (!zl3073x_synth_is_enabled(synth) || + zl3073x_synth_dpll_get(synth) != zldpll->id) + continue; + + synth_freq = zl3073x_synth_freq_get(synth); + + /* Remember first enabled synth freq for ToD-only fallback */ + if (!first_synth_freq) + first_synth_freq = synth_freq; + + if (!synth_mask[i]) + continue; + + /* Safe for s32: max synth freq is 750 MHz */ + step_cycles = div_s64(delta * synth_freq, NSEC_PER_SEC); + + rc = zl3073x_chan_phase_step(zldev, zldpll->id, + synth_mask[i], step_cycles, + !tod_stepped); + if (rc) { + if (tod_stepped) { + dev_warn(zldev->dev, + "Partial phase step failure\n"); + return 0; + } + return rc; + } + tod_stepped = true; + } + + if (!first_synth_freq) + return -EOPNOTSUPP; + + /* No enabled outputs found; step ToD counter only using the + * first enabled synth's period (empty output mask). + */ + if (!tod_stepped) { + step_cycles = div_s64(delta * first_synth_freq, NSEC_PER_SEC); + return zl3073x_chan_phase_step(zldev, zldpll->id, 0, + step_cycles, true); + } + + return 0; +} + +/** + * zl3073x_dpll_ptp_adjtime - adjust PTP clock time + * @info: PTP clock info + * @delta: time adjustment in nanoseconds + * + * For NCO, large deltas (>= 1 second) are split into a ToD + * read-modify-write for the seconds part and an output phase step for + * the sub-second remainder. Sub-second deltas use phase step directly, + * falling back to ToD read-modify-write if phase step or TIE write + * fails. In AUTO/REFLOCK modes, large deltas are split into ToD + * read-modify-write for seconds and TIE write for the sub-second + * remainder. Sub-second deltas use TIE write directly. + * + * If the seconds part was already committed when the sub-second + * mechanism fails, returns 0 to prevent the PTP servo from retrying + * the full delta and applying seconds again. + * + * Return: 0 on success (or partial success), <0 on error + */ +static int zl3073x_dpll_ptp_adjtime(struct ptp_clock_info *info, s64 delta) +{ + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); + struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_chan *chan; + bool sec_adjusted = false; + struct timespec64 ts; + int rc; + + if (!delta) + return 0; + + guard(mutex)(&zldpll->lock); + + /* Modes without phase step or TIE use plain ToD adjust */ + chan = zl3073x_chan_state_get(zldev, zldpll->id); + if (!zl3073x_chan_mode_is_nco(chan) && + !zl3073x_chan_mode_supports_tie(chan)) + return zl3073x_chan_tod_adjust(zldev, zldpll->id, + ns_to_timespec64(delta)); + + /* Split off seconds via ToD read-modify-write so the sub-second + * remainder can be applied through the output-coherent mechanism + * (phase step or TIE write). + */ + if (delta >= NSEC_PER_SEC || delta <= -NSEC_PER_SEC) { + s32 remainder; + + ts.tv_sec = div_s64_rem(delta, NSEC_PER_SEC, &remainder); + ts.tv_nsec = 0; + delta = remainder; + + rc = zl3073x_chan_tod_adjust(zldev, zldpll->id, ts); + if (rc) + return rc; + + /* No sub-second remainder, done */ + if (!delta) + return 0; + + /* Wait for the ToD write to be applied at the 1 Hz edge + * before issuing phase step or TIE write, so the pending + * WR_NEXT_1HZ does not overwrite the sub-second adjustment. + */ + rc = zl3073x_chan_tod_ready_wait(zldev, zldpll->id); + if (rc) + return rc; + + sec_adjusted = true; + } + + /* Apply sub-second delta via phase step (NCO) or TIE write */ + if (zl3073x_chan_mode_is_nco(chan)) { + rc = zl3073x_dpll_ptp_adjtime_phase_step(zldpll, delta); + if (!rc) + return 0; + } else { + rc = zl3073x_chan_tie_write(zldev, zldpll->id, delta); + if (!rc) + return 0; + } + + /* Phase step or TIE write failed, fall back to ToD adjust */ + rc = zl3073x_chan_tod_adjust(zldev, zldpll->id, + ns_to_timespec64(delta)); + + /* In the unlikely event that both phase step/TIE write and fallback + * ToD adjust fail after seconds were already committed, return + * success to prevent the PTP servo from retrying the full delta and + * applying seconds again. The sub-second residual will self-correct + * in the next servo cycle. + */ + if (rc && sec_adjusted) { + dev_warn(zldev->dev, + "Sub-second adjustment failed after seconds applied\n"); + return 0; + } + + return rc; +} + +/** + * zl3073x_dpll_ptp_adjfine - adjust PTP clock frequency + * @info: PTP clock info + * @scaled_ppm: frequency adjustment in scaled ppm (ppm * 2^16) + * + * Only supported for NCO. Writes the delta frequency offset register. + * + * Return: + * * %0 - success or @scaled_ppm is zero (no-op) + * * %-EOPNOTSUPP - NCO pin is not connected and @scaled_ppm is non-zero + * * negative - other error + */ +static int +zl3073x_dpll_ptp_adjfine(struct ptp_clock_info *info, long scaled_ppm) +{ + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); + const struct zl3073x_chan *chan; + s64 offset; + + /* Convert scaled_ppm to df_offset in 2^-48 steps: + * df_offset = -(scaled_ppm * 2^32) / 10^6 + * + * Simplify to avoid overflow: + * df_offset = -(scaled_ppm * 2^26) / 5^6 + * df_offset = -(scaled_ppm * 67108864) / 15625 + */ + offset = -div_s64((s64)scaled_ppm * 67108864LL, 15625); + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (!zl3073x_chan_mode_is_nco(chan)) + return scaled_ppm ? -EOPNOTSUPP : 0; + if (offset == chan->df_offset) + return 0; + + return zl3073x_chan_df_offset_set(zldpll->dev, zldpll->id, offset); +} + +/** + * zl3073x_dpll_ptp_adjphase - adjust PTP clock phase + * @info: PTP clock info + * @delta: phase adjustment in nanoseconds + * + * Only supported in AUTO and REFLOCK modes. Uses TIE write for + * nanosecond resolution phase adjustment. + * + * Return: + * * %0 - success or @delta is zero (no-op) + * * %-EOPNOTSUPP - mode does not support TIE and @delta is non-zero + * * negative - other error + */ +static int zl3073x_dpll_ptp_adjphase(struct ptp_clock_info *info, s32 delta) +{ + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); + struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_chan *chan; + + if (!delta) + return 0; + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldev, zldpll->id); + + if (!zl3073x_chan_mode_supports_tie(chan)) + return -EOPNOTSUPP; + + return zl3073x_chan_tie_write(zldev, zldpll->id, delta); +} + +static s32 +zl3073x_dpll_ptp_getmaxphase(struct ptp_clock_info *info __always_unused) +{ + /* HW limits TIE write to +-1 second. Return the constant HW + * limit and let adjphase handle mode-specific checks. + */ + return NSEC_PER_SEC - 1; +} + +static const struct ptp_clock_info zl3073x_dpll_ptp_clock_info = { + .owner = THIS_MODULE, + .max_adj = ZL3073X_DPLL_PTP_MAX_ADJ, + .gettimex64 = zl3073x_dpll_ptp_gettimex64, + .settime64 = zl3073x_dpll_ptp_settime64, + .adjtime = zl3073x_dpll_ptp_adjtime, + .adjfine = zl3073x_dpll_ptp_adjfine, + .adjphase = zl3073x_dpll_ptp_adjphase, + .getmaxphase = zl3073x_dpll_ptp_getmaxphase, +}; + +/** + * zl3073x_dpll_ptp_register - register PTP clock for a DPLL channel + * @zldpll: DPLL channel to register PTP clock for + * + * Return: 0 on success, <0 on error + */ +static int zl3073x_dpll_ptp_register(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dev *zldev = zldpll->dev; + struct ptp_clock *ptp_clock; + + zldpll->ptp_info = zl3073x_dpll_ptp_clock_info; + snprintf(zldpll->ptp_info.name, sizeof(zldpll->ptp_info.name), + "%s-dpll%u", dev_name(zldev->dev), zldpll->id); + + ptp_clock = ptp_clock_register(&zldpll->ptp_info, zldev->dev); + if (IS_ERR(ptp_clock)) { + dev_err(zldev->dev, "Failed to register PTP clock for DPLL%u\n", + zldpll->id); + return PTR_ERR(ptp_clock); + } + + zldpll->ptp_clock = ptp_clock; + + return 0; +} + +/** + * zl3073x_dpll_ptp_unregister - unregister PTP clock for a DPLL channel + * @zldpll: DPLL channel to unregister PTP clock for + */ +static void zl3073x_dpll_ptp_unregister(struct zl3073x_dpll *zldpll) +{ + if (!IS_ERR_OR_NULL(zldpll->ptp_clock)) { + ptp_clock_unregister(zldpll->ptp_clock); + zldpll->ptp_clock = NULL; + } } /** @@ -2436,6 +2777,47 @@ zl3073x_dpll_ref_sync_pairs_register(struct zl3073x_dpll *zldpll) return 0; } +/** + * zl3073x_dpll_alloc - allocate DPLL device + * @zldev: pointer to zl3073x device + * @ch: DPLL channel number + * + * Allocates DPLL device structure for given DPLL channel. + * + * Return: pointer to DPLL device on success, error pointer on error + */ +struct zl3073x_dpll * +zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch) +{ + struct zl3073x_dpll *zldpll; + + zldpll = kzalloc_obj(*zldpll); + if (!zldpll) + return ERR_PTR(-ENOMEM); + + zldpll->dev = zldev; + zldpll->id = ch; + mutex_init(&zldpll->lock); + INIT_LIST_HEAD(&zldpll->pins); + + return zldpll; +} + +/** + * zl3073x_dpll_free - free DPLL device + * @zldpll: pointer to zl3073x_dpll structure + * + * Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc. + */ +void +zl3073x_dpll_free(struct zl3073x_dpll *zldpll) +{ + WARN(zldpll->dpll_dev, "DPLL device is still registered\n"); + + mutex_destroy(&zldpll->lock); + kfree(zldpll); +} + /** * zl3073x_dpll_register - register DPLL device and all its pins * @zldpll: pointer to zl3073x_dpll structure @@ -2466,6 +2848,13 @@ zl3073x_dpll_register(struct zl3073x_dpll *zldpll) return rc; } + rc = zl3073x_dpll_ptp_register(zldpll); + if (rc) { + zl3073x_dpll_pins_unregister(zldpll); + zl3073x_dpll_device_unregister(zldpll); + return rc; + } + return 0; } @@ -2479,7 +2868,7 @@ zl3073x_dpll_register(struct zl3073x_dpll *zldpll) void zl3073x_dpll_unregister(struct zl3073x_dpll *zldpll) { - /* Unregister all pins and dpll */ + zl3073x_dpll_ptp_unregister(zldpll); zl3073x_dpll_pins_unregister(zldpll); zl3073x_dpll_device_unregister(zldpll); } diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h index faebc402ba1b..993221dc6324 100644 --- a/drivers/dpll/zl3073x/dpll.h +++ b/drivers/dpll/zl3073x/dpll.h @@ -5,6 +5,7 @@ #include #include +#include #include "core.h" @@ -22,6 +23,8 @@ * @type: DPLL type (PPS or EEC) * @lock_status: last saved DPLL lock status * @pins: list of pins + * @ptp_info: PTP clock info + * @ptp_clock: registered PTP clock (or NULL) */ struct zl3073x_dpll { struct list_head list; @@ -36,6 +39,8 @@ struct zl3073x_dpll { enum dpll_type type; enum dpll_lock_status lock_status; struct list_head pins; + struct ptp_clock_info ptp_info; + struct ptp_clock *ptp_clock; }; struct zl3073x_dpll *zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch); From 47e15a8d12e366d0d261bcbc394394f44418938d Mon Sep 17 00:00:00 2001 From: Hyunwoo Kim Date: Sat, 15 Aug 2026 07:36:18 +0900 Subject: [PATCH 1429/1433] sctp: stop processing a packet once its association is deleted sctp_endpoint_bh_rcv() looks the association up only when chunk->asoc is NULL, and caches the result in chunk->asoc and chunk->transport without taking a reference. A packet that matches no association is handed to the endpoint, so a peer can bundle COOKIE ECHO, SHUTDOWN and SHUTDOWN ACK in one packet. The COOKIE ECHO creates the association, the SHUTDOWN chunk caches it, and with the outqueue empty the SHUTDOWN ACK reaches sctp_sf_do_9_2_final(), so the association and its transports are freed. The endpoint loop has no counterpart to the asoc->base.dead check in sctp_assoc_bh_rcv(). The next chunk writes to last_time_heard in the freed transport and is then passed to sctp_do_sm() with the freed association. The transport is freed through RCU, so this needs the packet to come off the socket backlog, where the loop runs in task context. The endpoint loop cannot do the same check: it holds no reference on the association, so reading asoc->base.dead would itself be a use-after-free. Mark the packet for discard in the command interpreter, just before it deletes the association. That is also before sctp_inq_free() releases the chunk on the association receive path. sctp_sf_do_5_2_4_dupcook() issues SCTP_CMD_DELETE_TCB for the temporary association, while the one the packet belongs to stays alive. A restarting peer can bundle DATA behind its COOKIE ECHO, so compare against chunk->asoc and leave that case alone. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Hyunwoo Kim Acked-by: Xin Long Link: https://patch.msgid.link/an-YYtoqw1QpTXUL@v4bel Signed-off-by: Jakub Kicinski --- net/sctp/sm_sideeffect.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/sctp/sm_sideeffect.c b/net/sctp/sm_sideeffect.c index 424f10a6fdba..94716406d602 100644 --- a/net/sctp/sm_sideeffect.c +++ b/net/sctp/sm_sideeffect.c @@ -1332,6 +1332,10 @@ static int sctp_cmd_interpreter(enum sctp_event_type event_type, sctp_outq_uncork(&asoc->outqueue, gfp); local_cork = 0; } + /* No chunk left in this packet may use this asoc. */ + if (event_type == SCTP_EVENT_T_CHUNK && + chunk->asoc == asoc) + chunk->pdiscard = 1; /* Delete the current association. */ sctp_cmd_delete_tcb(commands, asoc); asoc = NULL; From 4e30317ff67a2eb12b4d890d39f72fd7e7117d48 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Sat, 15 Aug 2026 02:58:56 +0200 Subject: [PATCH 1430/1433] net: openvswitch: fix flow mask use-after-free on flow deletion The commit in the Fixes tag below made so flow->mask free is scheduled via RCU right after it is removed from the flow table. The pointer stays in the flow structure and it can be accessible while in the same RCU critical section. This is done to avoid requiring ovs_mutex for the ovs_flow_free(). However, while removing the flow during processing of CMD_DEL, we do not take RCU read lock before the removal, and ovs_flow_cmd_fill_info() uses the flow->mask pointer afterwards. The RCU read lock is taken, but it's already late at that point. The comment on that line acknowledges that the lock is cosmetic and doesn't serve a real purpose. This leads to use-after-free if the RCU grace period passes between removal and the filling. It is a short race window, but it is there and can lead to a real crash in case memory allocation for the info takes a bit longer: BUG: KASAN: slab-use-after-free in __ovs_nla_put_key net/openvswitch/flow_netlink.c:1996 BUG: KASAN: slab-use-after-free in ovs_nla_put_key+0x2463/0x2e30 net/openvswitch/flow_netlink.c:2250 Read of size 4 at addr ffff88801ee89970 by task ovs_flow_del_ec/9487 Call Trace: __ovs_nla_put_key net/openvswitch/flow_netlink.c:1996 ovs_nla_put_key+0x2463/0x2e30 net/openvswitch/flow_netlink.c:2250 ovs_flow_cmd_fill_info+0x420/0x9c0 net/openvswitch/datapath.c:930 ovs_flow_cmd_del+0x53a/0x970 net/openvswitch/datapath.c:1467 ... netlink_rcv_skb+0x156/0x420 net/netlink/af_netlink.c:2556 Allocated by task 9487: mask_alloc net/openvswitch/flow_table.c:967 flow_mask_insert net/openvswitch/flow_table.c:1012 ovs_flow_tbl_insert+0xea2/0x1a90 net/openvswitch/flow_table.c:1084 ovs_flow_cmd_new+0x7e3/0xd90 net/openvswitch/datapath.c:1086 ... netlink_rcv_skb+0x156/0x420 net/netlink/af_netlink.c:2556 Freed by task 9485: rcu_free_sheaf+0x1e/0x100 mm/slub.c:5978 rcu_do_batch kernel/rcu/tree.c:2645 rcu_core+0x59c/0x10c0 kernel/rcu/tree.c:2897 handle_softirqs+0x1e4/0x9a0 kernel/softirq.c:622 ... instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1062 ovs_flow_tbl_remove() must be called after the ovs_flow_cmd_fill_info() to avoid this race. This also helps with cleaning up the forced cast and the cosmetic RCU read lock. Before the commit in the Fixes tag the order did not matter as long as the flow object itself was not freed. A wider RCU critical section could be another option, but we have a GFP_KERNEL allocation in the way. Reported by Trend Micro's Zero Day Initiative as ZDI-CAN-32042. Fixes: 56c19868e115 ("openvswitch: Make flow mask removal symmetric.") Cc: stable@vger.kernel.org Signed-off-by: Ilya Maximets Reviewed-by: Aaron Conole Link: https://patch.msgid.link/20260815005915.1097270-1-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- net/openvswitch/datapath.c | 47 +++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c index ae69b2cabab9..ded46d993a4e 100644 --- a/net/openvswitch/datapath.c +++ b/net/openvswitch/datapath.c @@ -1473,33 +1473,34 @@ static int ovs_flow_cmd_del(struct sk_buff *skb, struct genl_info *info) goto unlock; } + reply = ovs_flow_cmd_alloc_info(ovsl_dereference(flow->sf_acts), + &flow->id, info, false, ufid_flags); + if (IS_ERR(reply)) { + netlink_set_err(sock_net(skb->sk)->genl_sock, 0, 0, + PTR_ERR(reply)); + reply = NULL; + } + + if (likely(reply)) { + err = ovs_flow_cmd_fill_info(flow, ovs_header->dp_ifindex, + reply, info->snd_portid, + info->snd_seq, 0, + OVS_FLOW_CMD_DEL, ufid_flags); + if (WARN_ON_ONCE(err < 0)) { + kfree_skb(reply); + reply = NULL; + } + } + /* Removal has to happen after ovs_flow_cmd_fill_info(), as it uses + * the flow->mask that can be scheduled to be freed by the + * ovs_flow_tbl_remove() and we're not holding the RCU read lock. + */ ovs_flow_tbl_remove(&dp->table, flow); ovs_unlock(); - reply = ovs_flow_cmd_alloc_info((const struct sw_flow_actions __force *) flow->sf_acts, - &flow->id, info, false, ufid_flags); - if (likely(reply)) { - if (!IS_ERR(reply)) { - rcu_read_lock(); /*To keep RCU checker happy. */ - err = ovs_flow_cmd_fill_info(flow, ovs_header->dp_ifindex, - reply, info->snd_portid, - info->snd_seq, 0, - OVS_FLOW_CMD_DEL, - ufid_flags); - rcu_read_unlock(); - if (WARN_ON_ONCE(err < 0)) { - kfree_skb(reply); - goto out_free; - } + if (likely(reply)) + ovs_notify(&dp_flow_genl_family, reply, info); - ovs_notify(&dp_flow_genl_family, reply, info); - } else { - netlink_set_err(sock_net(skb->sk)->genl_sock, 0, 0, - PTR_ERR(reply)); - } - } - -out_free: ovs_flow_free(flow, true); return 0; unlock: From 0b1c2af8a22c35cb099c735c2f63ea3ba757557d Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Sat, 15 Aug 2026 15:50:13 +0900 Subject: [PATCH 1431/1433] net: add missing ref_tracker_dir_exit() to alloc_netdev_mqs() sashiko is reporting that trying to read /sys/kernel/debug/ref_tracker/* causes use-afer-free crash when either alloc_percpu() or dev_addr_init() in alloc_netdev_mqs() failed, for commit 4d92b95ff2f9 ("net: add net device refcount tracker infrastructure") added ref_tracker_dir_exit() to only free_netdev() path. Closes: https://sashiko.dev/#/patchset/56c707e7-1fb0-43ec-b8fb-cf6f451e513e%40I-love.SAKURA.ne.jp Fixes: 4d92b95ff2f9 ("net: add net device refcount tracker infrastructure") Signed-off-by: Tetsuo Handa Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/b06ce35d-e7bc-47a5-8e0a-e82be7e4dd08@I-love.SAKURA.ne.jp Signed-off-by: Jakub Kicinski --- net/core/dev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/core/dev.c b/net/core/dev.c index 5ac31370df93..39807b68ff26 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -12167,6 +12167,7 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name, free_percpu(dev->pcpu_refcnt); free_dev: #endif + ref_tracker_dir_exit(&dev->refcnt_tracker); kvfree(dev); return NULL; } From f9de5db270a4c2641de87ee558c16a9bc6eb4cd8 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Sat, 15 Aug 2026 23:17:29 +0800 Subject: [PATCH 1432/1433] net: openvswitch: fix nf_connlabels leak in ovs_ct_init ovs_ct_init() acquires a connlabels reference before initializing the conntrack limit state. If ovs_ct_limit_init() fails, its error is returned directly. The pernet core does not invoke the exit callback for the operation whose initialization failed, so ovs_ct_exit() cannot drop the reference. This leaves labels_used elevated when Open vSwitch pernet registration fails for an existing network namespace. Subsequent conntrack entries in that namespace may allocate label extensions even though Open vSwitch failed to register. Drop the connlabels reference before returning a conntrack limit initialization error. ovs_ct_limit_init() already releases its partial state, and the original error remains unchanged. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 11efd5cb04a1 ("openvswitch: Support conntrack zone limit") Cc: stable@vger.kernel.org Signed-off-by: Ruoyu Wang Reviewed-by: Ilya Maximets Link: https://patch.msgid.link/20260815151729.3757984-1-ruoyuw560@gmail.com Signed-off-by: Jakub Kicinski --- net/openvswitch/conntrack.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c index 95697d4e16e6..38c6f34776c2 100644 --- a/net/openvswitch/conntrack.c +++ b/net/openvswitch/conntrack.c @@ -2001,6 +2001,7 @@ int ovs_ct_init(struct net *net) { unsigned int n_bits = sizeof(struct ovs_key_ct_labels) * BITS_PER_BYTE; struct ovs_net *ovs_net = net_generic(net, ovs_net_id); + int err = 0; if (nf_connlabels_get(net, n_bits - 1)) { ovs_net->xt_label = false; @@ -2010,10 +2011,11 @@ int ovs_ct_init(struct net *net) } #if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT) - return ovs_ct_limit_init(net, ovs_net); -#else - return 0; + err = ovs_ct_limit_init(net, ovs_net); + if (err && ovs_net->xt_label) + nf_connlabels_put(net); #endif + return err; } void ovs_ct_exit(struct net *net) From e2466392a0b8496000e12181cb1ee1535eb0da25 Mon Sep 17 00:00:00 2001 From: Glenn Judd Date: Sun, 16 Aug 2026 09:42:59 +0300 Subject: [PATCH 1433/1433] net/mlx5e: do not HW-GRO coalesce small frames When hardware GRO (SHAMPO) coalesces a small IPv4/TCP segment that was padded up to the 60-byte minimum Ethernet frame, the trailing padding is folded into the merged payload causing padding to be delivered to the user as payload. Detecting and reproducing the issue: the selftest tools/testing/selftests/drivers/net/gro.py subtest hw_ipv4_data_lrg_1byte sends {100, 1} expecting to receive {101}. In current code, it receives {106} (100 + 1 payload + 5 pad) instead. This patch avoids giving the user padding as payload by simply not coalescing small packets (which fails the subtest; the same approach and behavior as sw gro). This gains code simplicity at the cost of more computation (passing an extra skb up the stack) for small packets that could be coalesced. The threshold is chosen as ETH_ZLEN + 2 * VLAN_HLEN. This is the largest frame that may still contain minimum-frame padding (+ 2 VLAN tags), so anything larger is safe to consider for coalesce. (We do not include ETH_FCS_LEN in that threshold computation as netdev_fix_features() drops NETIF_F_GRO_HW whenever NETIF_F_RXFCS is set, so retained FCS can't reach this path.) Fixes: 92552d3abd32 ("net/mlx5e: HW_GRO cqe handler implementation") Cc: stable@vger.kernel.org Signed-off-by: Glenn Judd Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260816064259.3279548-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_rx.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c index 6fbc0441c4b8..6fc6605d2054 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c @@ -2263,6 +2263,11 @@ static void mlx5e_handle_rx_cqe_mpwrq_shampo(struct mlx5e_rq *rq, struct mlx5_cq data_offset = wqe_offset & (page_size - 1); page_idx = wqe_offset >> rq->mpwqe.page_shift; + if (unlikely(cqe_bcnt <= ETH_ZLEN + 2 * VLAN_HLEN)) { + match = false; + flush = true; + } + if (*skb && !(match && mlx5e_hw_gro_skb_has_enough_space(*skb, data_bcnt, page_size))) {